npcsh 1.0.25__py3-none-any.whl → 1.0.27__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- npcsh/_state.py +105 -105
- npcsh/alicanto.py +88 -88
- npcsh/corca.py +423 -81
- npcsh/guac.py +110 -107
- npcsh/mcp_helpers.py +45 -45
- npcsh/mcp_server.py +16 -17
- npcsh/npc.py +16 -17
- npcsh/npc_team/jinxs/bash_executer.jinx +1 -1
- npcsh/npc_team/jinxs/edit_file.jinx +6 -6
- npcsh/npc_team/jinxs/image_generation.jinx +5 -5
- npcsh/npc_team/jinxs/screen_cap.jinx +2 -2
- npcsh/npcsh.py +5 -2
- npcsh/plonk.py +8 -8
- npcsh/routes.py +110 -90
- npcsh/spool.py +13 -13
- npcsh/wander.py +37 -37
- npcsh/yap.py +72 -72
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/bash_executer.jinx +1 -1
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/edit_file.jinx +6 -6
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/image_generation.jinx +5 -5
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/screen_cap.jinx +2 -2
- {npcsh-1.0.25.dist-info → npcsh-1.0.27.dist-info}/METADATA +12 -6
- npcsh-1.0.27.dist-info/RECORD +73 -0
- npcsh-1.0.25.dist-info/RECORD +0 -73
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/internet_search.jinx +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/python_executor.jinx +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.0.25.dist-info → npcsh-1.0.27.dist-info}/WHEEL +0 -0
- {npcsh-1.0.25.dist-info → npcsh-1.0.27.dist-info}/entry_points.txt +0 -0
- {npcsh-1.0.25.dist-info → npcsh-1.0.27.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.0.25.dist-info → npcsh-1.0.27.dist-info}/top_level.txt +0 -0
npcsh/_state.py
CHANGED
|
@@ -104,16 +104,16 @@ except importlib.metadata.PackageNotFoundError:
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
NPCSH_CHAT_MODEL = os.environ.get("NPCSH_CHAT_MODEL", "gemma3:4b")
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
NPCSH_CHAT_PROVIDER = os.environ.get("NPCSH_CHAT_PROVIDER", "ollama")
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
NPCSH_DB_PATH = os.path.expanduser(
|
|
111
111
|
os.environ.get("NPCSH_DB_PATH", "~/npcsh_history.db")
|
|
112
112
|
)
|
|
113
113
|
NPCSH_VECTOR_DB_PATH = os.path.expanduser(
|
|
114
114
|
os.environ.get("NPCSH_VECTOR_DB_PATH", "~/npcsh_chroma.db")
|
|
115
115
|
)
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
|
|
118
118
|
NPCSH_DEFAULT_MODE = os.path.expanduser(os.environ.get("NPCSH_DEFAULT_MODE", "agent"))
|
|
119
119
|
NPCSH_VISION_MODEL = os.environ.get("NPCSH_VISION_MODEL", "gemma3:4b")
|
|
@@ -181,45 +181,45 @@ class ShellState:
|
|
|
181
181
|
elif model_type == "video_gen":
|
|
182
182
|
return self.video_gen_model, self.video_gen_provider
|
|
183
183
|
else:
|
|
184
|
-
return self.chat_model, self.chat_provider
|
|
184
|
+
return self.chat_model, self.chat_provider
|
|
185
185
|
CONFIG_KEY_MAP = {
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
"model": "NPCSH_CHAT_MODEL",
|
|
188
188
|
"chatmodel": "NPCSH_CHAT_MODEL",
|
|
189
189
|
"provider": "NPCSH_CHAT_PROVIDER",
|
|
190
190
|
"chatprovider": "NPCSH_CHAT_PROVIDER",
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
"vmodel": "NPCSH_VISION_MODEL",
|
|
194
194
|
"visionmodel": "NPCSH_VISION_MODEL",
|
|
195
195
|
"vprovider": "NPCSH_VISION_PROVIDER",
|
|
196
196
|
"visionprovider": "NPCSH_VISION_PROVIDER",
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
"emodel": "NPCSH_EMBEDDING_MODEL",
|
|
200
200
|
"embeddingmodel": "NPCSH_EMBEDDING_MODEL",
|
|
201
201
|
"eprovider": "NPCSH_EMBEDDING_PROVIDER",
|
|
202
202
|
"embeddingprovider": "NPCSH_EMBEDDING_PROVIDER",
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
|
|
205
205
|
"rmodel": "NPCSH_REASONING_MODEL",
|
|
206
206
|
"reasoningmodel": "NPCSH_REASONING_MODEL",
|
|
207
207
|
"rprovider": "NPCSH_REASONING_PROVIDER",
|
|
208
208
|
"reasoningprovider": "NPCSH_REASONING_PROVIDER",
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
|
|
211
211
|
"igmodel": "NPCSH_IMAGE_GEN_MODEL",
|
|
212
212
|
"imagegenmodel": "NPCSH_IMAGE_GEN_MODEL",
|
|
213
213
|
"igprovider": "NPCSH_IMAGE_GEN_PROVIDER",
|
|
214
214
|
"imagegenprovider": "NPCSH_IMAGE_GEN_PROVIDER",
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
"vgmodel": "NPCSH_VIDEO_GEN_MODEL",
|
|
218
218
|
"videogenmodel": "NPCSH_VIDEO_GEN_MODEL",
|
|
219
219
|
"vgprovider": "NPCSH_VIDEO_GEN_PROVIDER",
|
|
220
220
|
"videogenprovider": "NPCSH_VIDEO_GEN_PROVIDER",
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
"sprovider": "NPCSH_SEARCH_PROVIDER",
|
|
224
224
|
"mode": "NPCSH_DEFAULT_MODE",
|
|
225
225
|
"stream": "NPCSH_STREAM_OUTPUT",
|
|
@@ -233,13 +233,13 @@ def set_npcsh_config_value(key: str, value: str):
|
|
|
233
233
|
Set NPCSH config values at runtime using shorthand (case-insensitive) or full keys.
|
|
234
234
|
Updates os.environ, globals, and ShellState defaults.
|
|
235
235
|
"""
|
|
236
|
-
|
|
236
|
+
|
|
237
237
|
env_key = CONFIG_KEY_MAP.get(key.lower(), key)
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
|
|
240
240
|
os.environ[env_key] = value
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
|
|
243
243
|
if env_key in ["NPCSH_STREAM_OUTPUT", "NPCSH_BUILD_KG"]:
|
|
244
244
|
parsed_val = value.strip().lower() in ["1", "true", "yes"]
|
|
245
245
|
elif env_key.endswith("_PATH"):
|
|
@@ -247,10 +247,10 @@ def set_npcsh_config_value(key: str, value: str):
|
|
|
247
247
|
else:
|
|
248
248
|
parsed_val = value
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
|
|
251
251
|
globals()[env_key] = parsed_val
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
|
|
254
254
|
field_map = {
|
|
255
255
|
"NPCSH_CHAT_MODEL": "chat_model",
|
|
256
256
|
"NPCSH_CHAT_PROVIDER": "chat_provider",
|
|
@@ -298,7 +298,7 @@ def get_npc_path(npc_name: str, db_path: str) -> str:
|
|
|
298
298
|
except Exception as e:
|
|
299
299
|
print(f"Database query error: {e}")
|
|
300
300
|
|
|
301
|
-
|
|
301
|
+
|
|
302
302
|
if os.path.exists(project_npc_path):
|
|
303
303
|
return project_npc_path
|
|
304
304
|
|
|
@@ -327,7 +327,7 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
327
327
|
conn = sqlite3.connect(db_path)
|
|
328
328
|
cursor = conn.cursor()
|
|
329
329
|
|
|
330
|
-
|
|
330
|
+
|
|
331
331
|
cursor.execute(
|
|
332
332
|
"""
|
|
333
333
|
CREATE TABLE IF NOT EXISTS compiled_npcs (
|
|
@@ -338,7 +338,7 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
338
338
|
"""
|
|
339
339
|
)
|
|
340
340
|
|
|
341
|
-
|
|
341
|
+
|
|
342
342
|
package_dir = os.path.dirname(__file__)
|
|
343
343
|
package_npc_team_dir = os.path.join(package_dir, "npc_team")
|
|
344
344
|
|
|
@@ -368,7 +368,7 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
368
368
|
shutil.copy2(source_path, destination_path)
|
|
369
369
|
print(f"Copied ctx {filename} to {destination_path}")
|
|
370
370
|
|
|
371
|
-
|
|
371
|
+
|
|
372
372
|
package_jinxs_dir = os.path.join(package_npc_team_dir, "jinxs")
|
|
373
373
|
if os.path.exists(package_jinxs_dir):
|
|
374
374
|
for filename in os.listdir(package_jinxs_dir):
|
|
@@ -416,19 +416,19 @@ def get_shell_config_file() -> str:
|
|
|
416
416
|
Returns:
|
|
417
417
|
The path to the shell configuration file.
|
|
418
418
|
"""
|
|
419
|
-
|
|
419
|
+
|
|
420
420
|
shell = os.environ.get("SHELL", "")
|
|
421
421
|
|
|
422
422
|
if "zsh" in shell:
|
|
423
423
|
return os.path.expanduser("~/.zshrc")
|
|
424
424
|
elif "bash" in shell:
|
|
425
|
-
|
|
425
|
+
|
|
426
426
|
if platform.system() == "Darwin":
|
|
427
427
|
return os.path.expanduser("~/.bash_profile")
|
|
428
428
|
else:
|
|
429
429
|
return os.path.expanduser("~/.bashrc")
|
|
430
430
|
else:
|
|
431
|
-
|
|
431
|
+
|
|
432
432
|
return os.path.expanduser("~/.bashrc")
|
|
433
433
|
|
|
434
434
|
|
|
@@ -569,14 +569,14 @@ def get_argument_help() -> Dict[str, List[str]]:
|
|
|
569
569
|
arg_map = {arg: [] for arg in CANONICAL_ARGS}
|
|
570
570
|
|
|
571
571
|
for arg in CANONICAL_ARGS:
|
|
572
|
-
|
|
572
|
+
|
|
573
573
|
for i in range(1, len(arg)):
|
|
574
574
|
prefix = arg[:i]
|
|
575
575
|
|
|
576
|
-
|
|
576
|
+
|
|
577
577
|
matches = [canonical for canonical in CANONICAL_ARGS if canonical.startswith(prefix)]
|
|
578
578
|
|
|
579
|
-
|
|
579
|
+
|
|
580
580
|
if len(matches) == 1 and matches[0] == arg:
|
|
581
581
|
arg_map[arg].append(prefix)
|
|
582
582
|
|
|
@@ -668,7 +668,7 @@ BASH_COMMANDS = [
|
|
|
668
668
|
"until",
|
|
669
669
|
"wait",
|
|
670
670
|
"while",
|
|
671
|
-
|
|
671
|
+
|
|
672
672
|
"ls",
|
|
673
673
|
"cp",
|
|
674
674
|
"mv",
|
|
@@ -768,23 +768,23 @@ def start_interactive_session(command: str) -> int:
|
|
|
768
768
|
if ON_WINDOWS or termios is None or tty is None or pty is None or select is None or signal is None:
|
|
769
769
|
print("Interactive terminal sessions are not supported on Windows.")
|
|
770
770
|
return 1
|
|
771
|
-
|
|
771
|
+
|
|
772
772
|
old_tty = termios.tcgetattr(sys.stdin)
|
|
773
773
|
try:
|
|
774
|
-
|
|
774
|
+
|
|
775
775
|
master_fd, slave_fd = pty.openpty()
|
|
776
776
|
|
|
777
|
-
|
|
777
|
+
|
|
778
778
|
p = subprocess.Popen(
|
|
779
779
|
command,
|
|
780
780
|
stdin=slave_fd,
|
|
781
781
|
stdout=slave_fd,
|
|
782
782
|
stderr=slave_fd,
|
|
783
783
|
shell=True,
|
|
784
|
-
preexec_fn=os.setsid,
|
|
784
|
+
preexec_fn=os.setsid,
|
|
785
785
|
)
|
|
786
786
|
|
|
787
|
-
|
|
787
|
+
|
|
788
788
|
tty.setraw(sys.stdin.fileno())
|
|
789
789
|
|
|
790
790
|
def handle_timeout(signum, frame):
|
|
@@ -802,9 +802,9 @@ def start_interactive_session(command: str) -> int:
|
|
|
802
802
|
else:
|
|
803
803
|
break
|
|
804
804
|
|
|
805
|
-
|
|
805
|
+
|
|
806
806
|
signal.signal(signal.SIGALRM, handle_timeout)
|
|
807
|
-
signal.alarm(5)
|
|
807
|
+
signal.alarm(5)
|
|
808
808
|
try:
|
|
809
809
|
p.wait()
|
|
810
810
|
except TimeoutError:
|
|
@@ -817,7 +817,7 @@ def start_interactive_session(command: str) -> int:
|
|
|
817
817
|
signal.alarm(0)
|
|
818
818
|
|
|
819
819
|
finally:
|
|
820
|
-
|
|
820
|
+
|
|
821
821
|
termios.tcsetattr(sys.stdin, termios.TCSAFLUSH, old_tty)
|
|
822
822
|
|
|
823
823
|
return p.returncode
|
|
@@ -1009,21 +1009,21 @@ def validate_bash_command(command_parts: list) -> bool:
|
|
|
1009
1009
|
base_command = command_parts[0]
|
|
1010
1010
|
|
|
1011
1011
|
if base_command == 'which':
|
|
1012
|
-
return False
|
|
1012
|
+
return False
|
|
1013
1013
|
|
|
1014
1014
|
|
|
1015
|
-
|
|
1015
|
+
|
|
1016
1016
|
INTERACTIVE_COMMANDS = ["ipython", "python", "sqlite3", "r"]
|
|
1017
1017
|
TERMINAL_EDITORS = ["vim", "nano", "emacs"]
|
|
1018
1018
|
if base_command in TERMINAL_EDITORS or base_command in INTERACTIVE_COMMANDS:
|
|
1019
1019
|
return True
|
|
1020
1020
|
|
|
1021
1021
|
if base_command not in COMMAND_PATTERNS and base_command not in BASH_COMMANDS:
|
|
1022
|
-
return False
|
|
1022
|
+
return False
|
|
1023
1023
|
|
|
1024
1024
|
pattern = COMMAND_PATTERNS.get(base_command)
|
|
1025
1025
|
if not pattern:
|
|
1026
|
-
return True
|
|
1026
|
+
return True
|
|
1027
1027
|
|
|
1028
1028
|
args = []
|
|
1029
1029
|
flags = []
|
|
@@ -1033,14 +1033,14 @@ def validate_bash_command(command_parts: list) -> bool:
|
|
|
1033
1033
|
if part.startswith("-"):
|
|
1034
1034
|
flags.append(part)
|
|
1035
1035
|
if part not in pattern["flags"]:
|
|
1036
|
-
return False
|
|
1036
|
+
return False
|
|
1037
1037
|
else:
|
|
1038
1038
|
args.append(part)
|
|
1039
1039
|
|
|
1040
|
-
|
|
1040
|
+
|
|
1041
1041
|
if base_command == "who" and args:
|
|
1042
1042
|
return False
|
|
1043
|
-
|
|
1043
|
+
|
|
1044
1044
|
if pattern.get("requires_arg", False) and not args:
|
|
1045
1045
|
return False
|
|
1046
1046
|
|
|
@@ -1077,7 +1077,7 @@ def execute_set_command(command: str, value: str) -> str:
|
|
|
1077
1077
|
|
|
1078
1078
|
config_path = os.path.expanduser("~/.npcshrc")
|
|
1079
1079
|
|
|
1080
|
-
|
|
1080
|
+
|
|
1081
1081
|
var_map = {
|
|
1082
1082
|
"model": "NPCSH_CHAT_MODEL",
|
|
1083
1083
|
"provider": "NPCSH_CHAT_PROVIDER",
|
|
@@ -1089,14 +1089,14 @@ def execute_set_command(command: str, value: str) -> str:
|
|
|
1089
1089
|
|
|
1090
1090
|
env_var = var_map[command]
|
|
1091
1091
|
|
|
1092
|
-
|
|
1092
|
+
|
|
1093
1093
|
if os.path.exists(config_path):
|
|
1094
1094
|
with open(config_path, "r") as f:
|
|
1095
1095
|
lines = f.readlines()
|
|
1096
1096
|
else:
|
|
1097
1097
|
lines = []
|
|
1098
1098
|
|
|
1099
|
-
|
|
1099
|
+
|
|
1100
1100
|
property_exists = False
|
|
1101
1101
|
for i, line in enumerate(lines):
|
|
1102
1102
|
if line.startswith(f"export {env_var}="):
|
|
@@ -1107,7 +1107,7 @@ def execute_set_command(command: str, value: str) -> str:
|
|
|
1107
1107
|
if not property_exists:
|
|
1108
1108
|
lines.append(f"export {env_var}='{value}'\n")
|
|
1109
1109
|
|
|
1110
|
-
|
|
1110
|
+
|
|
1111
1111
|
with open(config_path, "w") as f:
|
|
1112
1112
|
f.writelines(lines)
|
|
1113
1113
|
|
|
@@ -1139,7 +1139,7 @@ def set_npcsh_initialized() -> None:
|
|
|
1139
1139
|
npcshrc.write(content)
|
|
1140
1140
|
npcshrc.truncate()
|
|
1141
1141
|
|
|
1142
|
-
|
|
1142
|
+
|
|
1143
1143
|
os.environ["NPCSH_INITIALIZED"] = "1"
|
|
1144
1144
|
print("NPCSH initialization flag set in .npcshrc")
|
|
1145
1145
|
|
|
@@ -1158,7 +1158,7 @@ def file_has_changed(source_path: str, destination_path: str) -> bool:
|
|
|
1158
1158
|
A boolean indicating whether the files are different
|
|
1159
1159
|
"""
|
|
1160
1160
|
|
|
1161
|
-
|
|
1161
|
+
|
|
1162
1162
|
return not filecmp.cmp(source_path, destination_path, shallow=False)
|
|
1163
1163
|
|
|
1164
1164
|
|
|
@@ -1245,7 +1245,7 @@ def read_rc_file_windows(path):
|
|
|
1245
1245
|
for line in f:
|
|
1246
1246
|
line = line.strip()
|
|
1247
1247
|
if line and not line.startswith("#"):
|
|
1248
|
-
|
|
1248
|
+
|
|
1249
1249
|
match = re.match(r'^([A-Z_]+)\s*=\s*[\'"](.*?)[\'"]$', line)
|
|
1250
1250
|
if match:
|
|
1251
1251
|
key, value = match.groups()
|
|
@@ -1254,11 +1254,11 @@ def read_rc_file_windows(path):
|
|
|
1254
1254
|
|
|
1255
1255
|
|
|
1256
1256
|
def get_setting_windows(key, default=None):
|
|
1257
|
-
|
|
1257
|
+
|
|
1258
1258
|
if env_value := os.getenv(key):
|
|
1259
1259
|
return env_value
|
|
1260
1260
|
|
|
1261
|
-
|
|
1261
|
+
|
|
1262
1262
|
config = read_rc_file_windows(get_npcshrc_path_windows())
|
|
1263
1263
|
return config.get(key, default)
|
|
1264
1264
|
|
|
@@ -1303,7 +1303,7 @@ READLINE_HISTORY_FILE = os.path.expanduser("~/.npcsh_readline_history")
|
|
|
1303
1303
|
DEFAULT_NPC_TEAM_PATH = os.path.expanduser("~/.npcsh/npc_team/")
|
|
1304
1304
|
PROJECT_NPC_TEAM_PATH = "./npc_team/"
|
|
1305
1305
|
|
|
1306
|
-
|
|
1306
|
+
|
|
1307
1307
|
try:
|
|
1308
1308
|
chroma_client = chromadb.PersistentClient(path=EMBEDDINGS_DB_PATH) if chromadb else None
|
|
1309
1309
|
except Exception as e:
|
|
@@ -1333,11 +1333,11 @@ def get_path_executables() -> List[str]:
|
|
|
1333
1333
|
|
|
1334
1334
|
import logging
|
|
1335
1335
|
|
|
1336
|
-
|
|
1336
|
+
|
|
1337
1337
|
completion_logger = logging.getLogger('npcsh.completion')
|
|
1338
|
-
completion_logger.setLevel(logging.WARNING)
|
|
1338
|
+
completion_logger.setLevel(logging.WARNING)
|
|
1339
|
+
|
|
1339
1340
|
|
|
1340
|
-
# Add handler if not already present
|
|
1341
1341
|
if not completion_logger.handlers:
|
|
1342
1342
|
handler = logging.StreamHandler(sys.stderr)
|
|
1343
1343
|
formatter = logging.Formatter('[%(name)s] %(message)s')
|
|
@@ -1356,7 +1356,7 @@ def make_completer(shell_state: ShellState, router: Any):
|
|
|
1356
1356
|
|
|
1357
1357
|
matches = []
|
|
1358
1358
|
|
|
1359
|
-
|
|
1359
|
+
|
|
1360
1360
|
if begidx > 0 and buffer[begidx-1] == '/':
|
|
1361
1361
|
completion_logger.debug(f"Slash command completion - text='{text}'")
|
|
1362
1362
|
slash_commands = get_slash_commands(shell_state, router)
|
|
@@ -1414,19 +1414,19 @@ def get_slash_commands(state: ShellState, router: Any) -> List[str]:
|
|
|
1414
1414
|
commands.extend(router_cmds)
|
|
1415
1415
|
completion_logger.debug(f"Router commands: {router_cmds}")
|
|
1416
1416
|
|
|
1417
|
-
|
|
1417
|
+
|
|
1418
1418
|
if state.team and hasattr(state.team, 'jinxs_dict'):
|
|
1419
1419
|
jinx_cmds = [f"/{jinx}" for jinx in state.team.jinxs_dict.keys()]
|
|
1420
1420
|
commands.extend(jinx_cmds)
|
|
1421
1421
|
completion_logger.debug(f"Jinx commands: {jinx_cmds}")
|
|
1422
1422
|
|
|
1423
|
-
|
|
1423
|
+
|
|
1424
1424
|
if state.team and hasattr(state.team, 'npcs'):
|
|
1425
1425
|
npc_cmds = [f"/{npc}" for npc in state.team.npcs.keys()]
|
|
1426
1426
|
commands.extend(npc_cmds)
|
|
1427
1427
|
completion_logger.debug(f"NPC commands: {npc_cmds}")
|
|
1428
1428
|
|
|
1429
|
-
|
|
1429
|
+
|
|
1430
1430
|
mode_cmds = ['/cmd', '/agent', '/chat']
|
|
1431
1431
|
commands.extend(mode_cmds)
|
|
1432
1432
|
completion_logger.debug(f"Mode commands: {mode_cmds}")
|
|
@@ -1460,7 +1460,7 @@ def get_file_completions(text: str) -> List[str]:
|
|
|
1460
1460
|
else:
|
|
1461
1461
|
completion = os.path.join(basedir, item)
|
|
1462
1462
|
|
|
1463
|
-
|
|
1463
|
+
|
|
1464
1464
|
matches.append(completion)
|
|
1465
1465
|
except (PermissionError, OSError):
|
|
1466
1466
|
pass
|
|
@@ -1470,15 +1470,15 @@ def get_file_completions(text: str) -> List[str]:
|
|
|
1470
1470
|
return []
|
|
1471
1471
|
def is_command_position(buffer: str, begidx: int) -> bool:
|
|
1472
1472
|
"""Determine if cursor is at a command position"""
|
|
1473
|
-
|
|
1473
|
+
|
|
1474
1474
|
before_word = buffer[:begidx]
|
|
1475
1475
|
|
|
1476
|
-
|
|
1476
|
+
|
|
1477
1477
|
parts = re.split(r'[|;&]', before_word)
|
|
1478
1478
|
current_command_part = parts[-1].strip()
|
|
1479
1479
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1480
|
+
|
|
1481
|
+
|
|
1482
1482
|
return len(current_command_part) == 0
|
|
1483
1483
|
|
|
1484
1484
|
|
|
@@ -1608,10 +1608,10 @@ def format_file_listing(output: str) -> str:
|
|
|
1608
1608
|
colored_filepath = colored(filepath_guess, color, attrs=attrs)
|
|
1609
1609
|
|
|
1610
1610
|
if len(parts) > 1 :
|
|
1611
|
-
|
|
1611
|
+
|
|
1612
1612
|
colored_line = " ".join(parts[:-1] + [colored_filepath])
|
|
1613
1613
|
else:
|
|
1614
|
-
|
|
1614
|
+
|
|
1615
1615
|
colored_line = colored_filepath
|
|
1616
1616
|
|
|
1617
1617
|
colored_lines.append(colored_line)
|
|
@@ -1627,7 +1627,7 @@ def wrap_text(text: str, width: int = 80) -> str:
|
|
|
1627
1627
|
lines.append(paragraph)
|
|
1628
1628
|
return "\n".join(lines)
|
|
1629
1629
|
|
|
1630
|
-
|
|
1630
|
+
|
|
1631
1631
|
|
|
1632
1632
|
def setup_readline() -> str:
|
|
1633
1633
|
"""Setup readline with history and completion"""
|
|
@@ -1635,7 +1635,7 @@ def setup_readline() -> str:
|
|
|
1635
1635
|
readline.read_history_file(READLINE_HISTORY_FILE)
|
|
1636
1636
|
readline.set_history_length(1000)
|
|
1637
1637
|
|
|
1638
|
-
|
|
1638
|
+
|
|
1639
1639
|
readline.parse_and_bind("tab: complete")
|
|
1640
1640
|
|
|
1641
1641
|
readline.parse_and_bind("set enable-bracketed-paste on")
|
|
@@ -1666,7 +1666,7 @@ def store_command_embeddings(command: str, output: Any, state: ShellState):
|
|
|
1666
1666
|
|
|
1667
1667
|
try:
|
|
1668
1668
|
output_str = str(output) if output else ""
|
|
1669
|
-
if not command and not output_str: return
|
|
1669
|
+
if not command and not output_str: return
|
|
1670
1670
|
|
|
1671
1671
|
texts_to_embed = [command, output_str]
|
|
1672
1672
|
|
|
@@ -1716,7 +1716,7 @@ def handle_interactive_command(cmd_parts: List[str], state: ShellState) -> Tuple
|
|
|
1716
1716
|
command_name = cmd_parts[0]
|
|
1717
1717
|
print(f"Starting interactive {command_name} session...")
|
|
1718
1718
|
try:
|
|
1719
|
-
|
|
1719
|
+
|
|
1720
1720
|
full_command_str = " ".join(cmd_parts)
|
|
1721
1721
|
return_code = start_interactive_session(full_command_str)
|
|
1722
1722
|
output = f"Interactive {command_name} session ended with return code {return_code}"
|
|
@@ -1735,7 +1735,7 @@ def handle_cd_command(cmd_parts: List[str], state: ShellState) -> Tuple[ShellSta
|
|
|
1735
1735
|
output = colored(f"cd: no such file or directory: {target_path}", "red")
|
|
1736
1736
|
except Exception as e:
|
|
1737
1737
|
output = colored(f"cd: error changing directory: {e}", "red")
|
|
1738
|
-
os.chdir(original_path)
|
|
1738
|
+
os.chdir(original_path)
|
|
1739
1739
|
|
|
1740
1740
|
return state, output
|
|
1741
1741
|
|
|
@@ -1806,21 +1806,21 @@ def parse_generic_command_flags(parts: List[str]) -> Tuple[Dict[str, Any], List[
|
|
|
1806
1806
|
key, value = key_part.split('=', 1)
|
|
1807
1807
|
parsed_kwargs[key] = _try_convert_type(value)
|
|
1808
1808
|
else:
|
|
1809
|
-
|
|
1809
|
+
|
|
1810
1810
|
if i + 1 < len(parts) and not parts[i + 1].startswith('-'):
|
|
1811
1811
|
parsed_kwargs[key_part] = _try_convert_type(parts[i + 1])
|
|
1812
|
-
i += 1
|
|
1812
|
+
i += 1
|
|
1813
1813
|
else:
|
|
1814
|
-
parsed_kwargs[key_part] = True
|
|
1814
|
+
parsed_kwargs[key_part] = True
|
|
1815
1815
|
|
|
1816
1816
|
elif part.startswith('-'):
|
|
1817
1817
|
key = part[1:]
|
|
1818
|
-
|
|
1818
|
+
|
|
1819
1819
|
if i + 1 < len(parts) and not parts[i + 1].startswith('-'):
|
|
1820
1820
|
parsed_kwargs[key] = _try_convert_type(parts[i + 1])
|
|
1821
|
-
i += 1
|
|
1821
|
+
i += 1
|
|
1822
1822
|
else:
|
|
1823
|
-
parsed_kwargs[key] = True
|
|
1823
|
+
parsed_kwargs[key] = True
|
|
1824
1824
|
|
|
1825
1825
|
elif '=' in part and not part.startswith('-'):
|
|
1826
1826
|
key, value = part.split('=', 1)
|
|
@@ -1837,7 +1837,7 @@ def parse_generic_command_flags(parts: List[str]) -> Tuple[Dict[str, Any], List[
|
|
|
1837
1837
|
def should_skip_kg_processing(user_input: str, assistant_output: str) -> bool:
|
|
1838
1838
|
"""Determine if this interaction is too trivial for KG processing"""
|
|
1839
1839
|
|
|
1840
|
-
|
|
1840
|
+
|
|
1841
1841
|
if len(user_input.strip()) < 10:
|
|
1842
1842
|
return True
|
|
1843
1843
|
|
|
@@ -1866,7 +1866,7 @@ def execute_slash_command(command: str,
|
|
|
1866
1866
|
all_command_parts = shlex.split(command)
|
|
1867
1867
|
command_name = all_command_parts[0].lstrip('/')
|
|
1868
1868
|
|
|
1869
|
-
|
|
1869
|
+
|
|
1870
1870
|
if command_name in ['n', 'npc']:
|
|
1871
1871
|
npc_to_switch_to = all_command_parts[1] if len(all_command_parts) > 1 else None
|
|
1872
1872
|
if npc_to_switch_to and state.team and npc_to_switch_to in state.team.npcs:
|
|
@@ -1876,7 +1876,7 @@ def execute_slash_command(command: str,
|
|
|
1876
1876
|
available_npcs = list(state.team.npcs.keys()) if state.team else []
|
|
1877
1877
|
return state, colored(f"NPC '{npc_to_switch_to}' not found. Available NPCs: {', '.join(available_npcs)}", "red")
|
|
1878
1878
|
|
|
1879
|
-
|
|
1879
|
+
|
|
1880
1880
|
handler = router.get_route(command_name)
|
|
1881
1881
|
if handler:
|
|
1882
1882
|
parsed_flags, positional_args = parse_generic_command_flags(all_command_parts[1:])
|
|
@@ -1892,12 +1892,12 @@ def execute_slash_command(command: str,
|
|
|
1892
1892
|
'positional_args': positional_args,
|
|
1893
1893
|
'plonk_context': state.team.shared_context.get('PLONK_CONTEXT') if state.team and hasattr(state.team, 'shared_context') else None,
|
|
1894
1894
|
|
|
1895
|
-
|
|
1895
|
+
|
|
1896
1896
|
'model': state.npc.model if isinstance(state.npc, NPC) and state.npc.model else state.chat_model,
|
|
1897
1897
|
'provider': state.npc.provider if isinstance(state.npc, NPC) and state.npc.provider else state.chat_provider,
|
|
1898
1898
|
'npc': state.npc,
|
|
1899
1899
|
|
|
1900
|
-
|
|
1900
|
+
|
|
1901
1901
|
'sprovider': state.search_provider,
|
|
1902
1902
|
'emodel': state.embedding_model,
|
|
1903
1903
|
'eprovider': state.embedding_provider,
|
|
@@ -1918,7 +1918,7 @@ def execute_slash_command(command: str,
|
|
|
1918
1918
|
|
|
1919
1919
|
render_markdown(f'- Calling {command_name} handler {kwarg_part} ')
|
|
1920
1920
|
|
|
1921
|
-
|
|
1921
|
+
|
|
1922
1922
|
if 'model' in normalized_flags and 'provider' not in normalized_flags:
|
|
1923
1923
|
inferred_provider = lookup_provider(normalized_flags['model'])
|
|
1924
1924
|
if inferred_provider:
|
|
@@ -1947,7 +1947,7 @@ def execute_slash_command(command: str,
|
|
|
1947
1947
|
traceback.print_exc()
|
|
1948
1948
|
return state, colored(f"Error executing slash command '{command_name}': {e}", "red")
|
|
1949
1949
|
|
|
1950
|
-
|
|
1950
|
+
|
|
1951
1951
|
active_npc = state.npc if isinstance(state.npc, NPC) else None
|
|
1952
1952
|
jinx_to_execute = None
|
|
1953
1953
|
executor = None
|
|
@@ -1959,16 +1959,16 @@ def execute_slash_command(command: str,
|
|
|
1959
1959
|
jinx_to_execute = state.team.jinxs_dict[command_name]
|
|
1960
1960
|
executor = state.team
|
|
1961
1961
|
if jinx_to_execute:
|
|
1962
|
-
args = all_command_parts[1:]
|
|
1962
|
+
args = all_command_parts[1:]
|
|
1963
1963
|
try:
|
|
1964
|
-
|
|
1964
|
+
|
|
1965
1965
|
input_values = {}
|
|
1966
1966
|
if hasattr(jinx_to_execute, 'inputs') and jinx_to_execute.inputs:
|
|
1967
1967
|
for i, input_name in enumerate(jinx_to_execute.inputs):
|
|
1968
1968
|
if i < len(args):
|
|
1969
1969
|
input_values[input_name] = args[i]
|
|
1970
1970
|
|
|
1971
|
-
|
|
1971
|
+
|
|
1972
1972
|
if isinstance(executor, NPC):
|
|
1973
1973
|
jinx_output = jinx_to_execute.execute(
|
|
1974
1974
|
input_values=input_values,
|
|
@@ -1976,7 +1976,7 @@ def execute_slash_command(command: str,
|
|
|
1976
1976
|
npc=executor,
|
|
1977
1977
|
messages=state.messages
|
|
1978
1978
|
)
|
|
1979
|
-
else:
|
|
1979
|
+
else:
|
|
1980
1980
|
jinx_output = jinx_to_execute.execute(
|
|
1981
1981
|
input_values=input_values,
|
|
1982
1982
|
jinxs_dict=executor.jinxs_dict if hasattr(executor, 'jinxs_dict') else {},
|
|
@@ -2097,7 +2097,7 @@ def process_pipeline_command(
|
|
|
2097
2097
|
stream=stream_final,
|
|
2098
2098
|
context=info,
|
|
2099
2099
|
)
|
|
2100
|
-
|
|
2100
|
+
|
|
2101
2101
|
|
|
2102
2102
|
if not review:
|
|
2103
2103
|
if isinstance(llm_result, dict):
|
|
@@ -2131,7 +2131,7 @@ def review_and_iterate_command(
|
|
|
2131
2131
|
Simple iteration on LLM command result to improve quality.
|
|
2132
2132
|
"""
|
|
2133
2133
|
|
|
2134
|
-
|
|
2134
|
+
|
|
2135
2135
|
if isinstance(initial_result, dict):
|
|
2136
2136
|
current_output = initial_result.get("output")
|
|
2137
2137
|
current_messages = initial_result.get("messages", state.messages)
|
|
@@ -2139,7 +2139,7 @@ def review_and_iterate_command(
|
|
|
2139
2139
|
current_output = initial_result
|
|
2140
2140
|
current_messages = state.messages
|
|
2141
2141
|
|
|
2142
|
-
|
|
2142
|
+
|
|
2143
2143
|
refinement_prompt = f"""
|
|
2144
2144
|
The previous response to "{original_command}" was:
|
|
2145
2145
|
{current_output}
|
|
@@ -2147,7 +2147,7 @@ The previous response to "{original_command}" was:
|
|
|
2147
2147
|
Please review and improve this response if needed. Provide a better, more complete answer.
|
|
2148
2148
|
"""
|
|
2149
2149
|
|
|
2150
|
-
|
|
2150
|
+
|
|
2151
2151
|
refined_result = check_llm_command(
|
|
2152
2152
|
refinement_prompt,
|
|
2153
2153
|
model=exec_model,
|
|
@@ -2162,7 +2162,7 @@ Please review and improve this response if needed. Provide a better, more comple
|
|
|
2162
2162
|
context=info,
|
|
2163
2163
|
)
|
|
2164
2164
|
|
|
2165
|
-
|
|
2165
|
+
|
|
2166
2166
|
if isinstance(refined_result, dict):
|
|
2167
2167
|
state.messages = refined_result.get("messages", current_messages)
|
|
2168
2168
|
return state, refined_result.get("output", current_output)
|
|
@@ -2198,8 +2198,8 @@ def execute_command(
|
|
|
2198
2198
|
active_model = npc_model or state.chat_model
|
|
2199
2199
|
active_provider = npc_provider or state.chat_provider
|
|
2200
2200
|
if state.current_mode == 'agent':
|
|
2201
|
-
|
|
2202
|
-
|
|
2201
|
+
|
|
2202
|
+
|
|
2203
2203
|
for i, cmd_segment in enumerate(commands):
|
|
2204
2204
|
render_markdown(f'- Executing command {i+1}/{len(commands)}')
|
|
2205
2205
|
is_last_command = (i == len(commands) - 1)
|
|
@@ -2234,7 +2234,7 @@ def execute_command(
|
|
|
2234
2234
|
except Exception:
|
|
2235
2235
|
print(f"Warning: Cannot convert output to string for piping: {type(output)}", file=sys.stderr)
|
|
2236
2236
|
stdin_for_next = None
|
|
2237
|
-
else:
|
|
2237
|
+
else:
|
|
2238
2238
|
stdin_for_next = None
|
|
2239
2239
|
except Exception as pipeline_error:
|
|
2240
2240
|
import traceback
|
|
@@ -2249,7 +2249,7 @@ def execute_command(
|
|
|
2249
2249
|
|
|
2250
2250
|
|
|
2251
2251
|
elif state.current_mode == 'chat':
|
|
2252
|
-
|
|
2252
|
+
|
|
2253
2253
|
cmd_parts = parse_command_safely(command)
|
|
2254
2254
|
is_probably_bash = (
|
|
2255
2255
|
cmd_parts
|
|
@@ -2274,9 +2274,9 @@ def execute_command(
|
|
|
2274
2274
|
except Exception as bash_err:
|
|
2275
2275
|
return state, colored(f"Bash execution failed: {bash_err}", "red")
|
|
2276
2276
|
except Exception:
|
|
2277
|
-
pass
|
|
2277
|
+
pass
|
|
2278
2278
|
|
|
2279
|
-
|
|
2279
|
+
|
|
2280
2280
|
response = get_llm_response(
|
|
2281
2281
|
command,
|
|
2282
2282
|
model=active_model,
|
|
@@ -2390,7 +2390,7 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2390
2390
|
print(f"Warning: Could not load context file {filename}: {e}")
|
|
2391
2391
|
|
|
2392
2392
|
forenpc_name = team_ctx.get("forenpc", default_forenpc_name)
|
|
2393
|
-
|
|
2393
|
+
|
|
2394
2394
|
|
|
2395
2395
|
if team_ctx.get("use_global_jinxs", False):
|
|
2396
2396
|
jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
|
|
@@ -2404,7 +2404,7 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2404
2404
|
forenpc_path = os.path.join(team_dir, f"{forenpc_name}.npc")
|
|
2405
2405
|
|
|
2406
2406
|
|
|
2407
|
-
|
|
2407
|
+
|
|
2408
2408
|
|
|
2409
2409
|
|
|
2410
2410
|
|
|
@@ -2430,7 +2430,7 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2430
2430
|
if not npc_obj.provider:
|
|
2431
2431
|
npc_obj.provider = initial_state.chat_provider
|
|
2432
2432
|
|
|
2433
|
-
|
|
2433
|
+
|
|
2434
2434
|
if team.forenpc and isinstance(team.forenpc, NPC):
|
|
2435
2435
|
if not team.forenpc.model:
|
|
2436
2436
|
team.forenpc.model = initial_state.chat_model
|
|
@@ -2442,7 +2442,7 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2442
2442
|
elif team_dir and os.path.basename(team_dir) != 'npc_team':
|
|
2443
2443
|
team.name = os.path.basename(team_dir)
|
|
2444
2444
|
else:
|
|
2445
|
-
team.name = "global_team"
|
|
2445
|
+
team.name = "global_team"
|
|
2446
2446
|
|
|
2447
2447
|
return command_history, team, forenpc_obj
|
|
2448
2448
|
|
|
@@ -2460,7 +2460,7 @@ def process_result(
|
|
|
2460
2460
|
team_name = result_state.team.name if result_state.team else "__none__"
|
|
2461
2461
|
npc_name = result_state.npc.name if isinstance(result_state.npc, NPC) else "__none__"
|
|
2462
2462
|
|
|
2463
|
-
|
|
2463
|
+
|
|
2464
2464
|
active_npc = result_state.npc if isinstance(result_state.npc, NPC) else NPC(
|
|
2465
2465
|
name="default",
|
|
2466
2466
|
model=result_state.chat_model,
|
|
@@ -2543,7 +2543,7 @@ def process_result(
|
|
|
2543
2543
|
except Exception as e:
|
|
2544
2544
|
print(colored(f"Error during real-time KG evolution: {e}", "red"))
|
|
2545
2545
|
|
|
2546
|
-
|
|
2546
|
+
|
|
2547
2547
|
result_state.turn_count += 1
|
|
2548
2548
|
|
|
2549
2549
|
if result_state.turn_count > 0 and result_state.turn_count % 10 == 0:
|