1
1
import asyncio
2
- import dataclasses
3
2
import json
4
3
import os
5
4
import platform
6
5
import sys
7
- import threading
8
- import time
9
6
import traceback
10
7
import uuid
11
- from collections .abc import Callable
12
8
from datetime import datetime
13
9
from typing import Any , List , Optional , cast
14
10
15
11
from prompt_toolkit import PromptSession
16
12
from prompt_toolkit .formatted_text import HTML
17
-
18
- prompt_session = PromptSession ()
13
+ from readchar import readchar
19
14
20
15
try :
21
16
from enum import StrEnum
45
40
BetaToolResultBlockParam ,
46
41
BetaToolUseBlockParam ,
47
42
)
48
- from yaspin import yaspin
49
- from yaspin . spinners import Spinners
43
+
44
+ from . misc . spinner import SimpleSpinner
50
45
51
46
# Local imports
52
47
from .profiles import Profile
@@ -167,7 +162,8 @@ def __init__(self, profile=None):
167
162
setattr (self , key , value )
168
163
169
164
self ._client = None
170
- self ._spinner = yaspin (Spinners .simpleDots , text = "" )
165
+ self ._spinner = SimpleSpinner ("" )
166
+ self ._prompt_session = None
171
167
172
168
def to_dict (self ):
173
169
"""Convert current settings to dictionary"""
@@ -336,21 +332,23 @@ async def async_respond(self):
336
332
}
337
333
)
338
334
335
+ content_blocks = cast (list [BetaContentBlock ], response .content )
336
+ tool_use_blocks = [b for b in content_blocks if b .type == "tool_use" ]
337
+
338
+ # If there are no tool use blocks, we're done
339
+ if not tool_use_blocks :
340
+ break
341
+
339
342
user_approval = None
340
343
if getattr (self , "auto_run" , False ):
341
344
user_approval = "y"
342
345
else :
343
346
if not sys .stdin .isatty ():
344
347
print (
345
- "Error: Non-interactive environment requires auto_run=True"
348
+ "Error: Non-interactive environment requires auto_run=True to run tools "
346
349
)
347
350
exit (1 )
348
351
349
- content_blocks = cast (list [BetaContentBlock ], response .content )
350
- tool_use_blocks = [
351
- b for b in content_blocks if b .type == "tool_use"
352
- ]
353
-
354
352
if len (tool_use_blocks ) > 1 :
355
353
# Check if all tools are pre-approved
356
354
all_approved = all (
@@ -360,7 +358,7 @@ async def async_respond(self):
360
358
user_approval = "y"
361
359
else :
362
360
print (f"\n \033 [38;5;240mRun all actions above\033 [0m?" )
363
- user_approval = input ( " \n (y/n/a): " ). lower (). strip ()
361
+ user_approval = self . _ask_user_approval ()
364
362
elif len (tool_use_blocks ) == 1 :
365
363
tool_block = tool_use_blocks [0 ]
366
364
if self ._is_tool_approved (tool_block ):
@@ -394,21 +392,21 @@ async def async_respond(self):
394
392
else :
395
393
print (f"\n \033 [38;5;240mRun tool?\033 [0m" )
396
394
397
- user_approval = input ( " \n (y/n/a): " ). lower (). strip ()
395
+ user_approval = self . _ask_user_approval ()
398
396
399
397
# Handle adding to allowed lists
400
398
if user_approval == "a" :
401
399
if tool_block .name == "editor" :
402
400
path = tool_block .input .get ("path" )
403
401
if path :
404
- self .allowed_paths .add (path )
402
+ self .allowed_paths .append (path )
405
403
print (
406
404
f"\n \033 [38;5;240mEdits to { path } will be auto-approved in this session.\033 [0m\n "
407
405
)
408
406
else : # bash/computer tools
409
407
command = tool_block .input .get ("command" , "" )
410
408
if command :
411
- self .allowed_commands .add (command )
409
+ self .allowed_commands .append (command )
412
410
print (
413
411
f"\n \033 [38;5;240mThe command '{ command } ' will be auto-approved in this session.\033 [0m\n "
414
412
)
@@ -455,6 +453,23 @@ async def async_respond(self):
455
453
# (I can add this if you'd like, but focusing on the Anthropic path for now)
456
454
pass
457
455
456
+ def _ask_user_approval (self ) -> str :
457
+ """Ask user for approval to run a tool"""
458
+ # print("\n\033[38;5;240m(\033[0my\033[38;5;240m)es (\033[0mn\033[38;5;240m)o (\033[0ma\033[38;5;240m)lways approve this command: \033[0m", end="", flush=True)
459
+ # Simpler y/n prompt
460
+ print (
461
+ "\n \033 [38;5;240m(\033 [0my\033 [38;5;240m/\033 [0mn\033 [38;5;240m): \033 [0m" ,
462
+ end = "" ,
463
+ flush = True ,
464
+ )
465
+ try :
466
+ user_approval = readchar ().lower ()
467
+ print (user_approval )
468
+ return user_approval
469
+ except KeyboardInterrupt :
470
+ print ()
471
+ return "n"
472
+
458
473
def _handle_command (self , cmd : str , parts : list [str ]) -> bool :
459
474
"""Handle / commands for controlling interpreter settings"""
460
475
@@ -521,6 +536,7 @@ def print_help():
521
536
path = os .path .expanduser (self ._profile .profile_path )
522
537
if not os .path .exists (path ):
523
538
print (f"Profile does not exist yet. Current path would be: { path } " )
539
+ print ("Use /profile save to create it" )
524
540
return True
525
541
526
542
if platform .system () == "Darwin" : # macOS
@@ -638,7 +654,9 @@ def chat(self):
638
654
placeholder = HTML (
639
655
f"<{ placeholder_color } >{ placeholder_text } </{ placeholder_color } >"
640
656
)
641
- user_input = prompt_session .prompt (
657
+ if self ._prompt_session is None :
658
+ self ._prompt_session = PromptSession ()
659
+ user_input = self ._prompt_session .prompt (
642
660
"> " , placeholder = placeholder
643
661
).strip ()
644
662
print ()
@@ -651,7 +669,7 @@ def chat(self):
651
669
placeholder = HTML (
652
670
f'<{ placeholder_color } >Use """ again to finish</{ placeholder_color } >'
653
671
)
654
- line = prompt_session .prompt (
672
+ line = self . _prompt_session .prompt (
655
673
"" , placeholder = placeholder
656
674
).strip ()
657
675
if line == '"""' :
@@ -667,6 +685,13 @@ def chat(self):
667
685
if self ._handle_command (cmd , parts ):
668
686
continue
669
687
688
+ if user_input == "" :
689
+ if message_count in range (4 , 7 ):
690
+ print ("Error: Cat is asleep on Enter key\n " )
691
+ else :
692
+ print ("Error: No input provided\n " )
693
+ continue
694
+
670
695
self .messages .append ({"role" : "user" , "content" : user_input })
671
696
672
697
for _ in self .respond ():
0 commit comments