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.
Files changed (50) hide show
  1. npcsh/_state.py +105 -105
  2. npcsh/alicanto.py +88 -88
  3. npcsh/corca.py +423 -81
  4. npcsh/guac.py +110 -107
  5. npcsh/mcp_helpers.py +45 -45
  6. npcsh/mcp_server.py +16 -17
  7. npcsh/npc.py +16 -17
  8. npcsh/npc_team/jinxs/bash_executer.jinx +1 -1
  9. npcsh/npc_team/jinxs/edit_file.jinx +6 -6
  10. npcsh/npc_team/jinxs/image_generation.jinx +5 -5
  11. npcsh/npc_team/jinxs/screen_cap.jinx +2 -2
  12. npcsh/npcsh.py +5 -2
  13. npcsh/plonk.py +8 -8
  14. npcsh/routes.py +110 -90
  15. npcsh/spool.py +13 -13
  16. npcsh/wander.py +37 -37
  17. npcsh/yap.py +72 -72
  18. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/bash_executer.jinx +1 -1
  19. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/edit_file.jinx +6 -6
  20. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/image_generation.jinx +5 -5
  21. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/screen_cap.jinx +2 -2
  22. {npcsh-1.0.25.dist-info → npcsh-1.0.27.dist-info}/METADATA +12 -6
  23. npcsh-1.0.27.dist-info/RECORD +73 -0
  24. npcsh-1.0.25.dist-info/RECORD +0 -73
  25. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  26. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/alicanto.png +0 -0
  27. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/corca.npc +0 -0
  28. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/corca.png +0 -0
  29. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/foreman.npc +0 -0
  30. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/frederic.npc +0 -0
  31. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/frederic4.png +0 -0
  32. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/guac.png +0 -0
  33. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/internet_search.jinx +0 -0
  34. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  35. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  36. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  37. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  38. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/plonk.npc +0 -0
  39. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/plonk.png +0 -0
  40. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  41. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  42. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/python_executor.jinx +0 -0
  43. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  44. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/sibiji.png +0 -0
  45. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/spool.png +0 -0
  46. {npcsh-1.0.25.data → npcsh-1.0.27.data}/data/npcsh/npc_team/yap.png +0 -0
  47. {npcsh-1.0.25.dist-info → npcsh-1.0.27.dist-info}/WHEEL +0 -0
  48. {npcsh-1.0.25.dist-info → npcsh-1.0.27.dist-info}/entry_points.txt +0 -0
  49. {npcsh-1.0.25.dist-info → npcsh-1.0.27.dist-info}/licenses/LICENSE +0 -0
  50. {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
- # print("NPCSH_CHAT_MODEL", NPCSH_CHAT_MODEL)
107
+
108
108
  NPCSH_CHAT_PROVIDER = os.environ.get("NPCSH_CHAT_PROVIDER", "ollama")
109
- # print("NPCSH_CHAT_PROVIDER", NPCSH_CHAT_PROVIDER)
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
- #DEFAULT MODES = ['CHAT', 'AGENT', 'CODE', ]
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 # Default fallback
184
+ return self.chat_model, self.chat_provider
185
185
  CONFIG_KEY_MAP = {
186
- # Chat
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
- # Vision
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
- # Embedding
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
- # Reasoning
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
- # Image generation
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
- # Video generation
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
- # Other
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
- # case-insensitive lookup for shorthand
236
+
237
237
  env_key = CONFIG_KEY_MAP.get(key.lower(), key)
238
238
 
239
- # update env
239
+
240
240
  os.environ[env_key] = value
241
241
 
242
- # normalize types
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
- # update global
250
+
251
251
  globals()[env_key] = parsed_val
252
252
 
253
- # update ShellState defaults
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
- # Fallback to file paths
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
- # Create the compiled_npcs table if it doesn't exist
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
- # Get the path to the npc_team directory in the package
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
- # Copy jinxs from package to user directory
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
- # Check the current shell
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
- # On macOS, use .bash_profile for login shells
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
- # Default to .bashrc if we can't determine the shell
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
- # Generate all possible prefixes for this argument
572
+
573
573
  for i in range(1, len(arg)):
574
574
  prefix = arg[:i]
575
575
 
576
- # Check if this prefix is an unambiguous shorthand
576
+
577
577
  matches = [canonical for canonical in CANONICAL_ARGS if canonical.startswith(prefix)]
578
578
 
579
- # If this prefix uniquely resolves to our current argument, it's a valid shorthand
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
- # Common Unix commands
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
- # Save the current terminal settings
771
+
772
772
  old_tty = termios.tcgetattr(sys.stdin)
773
773
  try:
774
- # Create a pseudo-terminal
774
+
775
775
  master_fd, slave_fd = pty.openpty()
776
776
 
777
- # Start the process
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, # Create a new process group
784
+ preexec_fn=os.setsid,
785
785
  )
786
786
 
787
- # Set the terminal to raw mode
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
- # Wait for the process to terminate with a timeout
805
+
806
806
  signal.signal(signal.SIGALRM, handle_timeout)
807
- signal.alarm(5) # 5 second timeout
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
- # Restore the terminal settings
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 # disable which arbitrarily cause the command parsing for it is too finnicky.
1012
+ return False
1013
1013
 
1014
1014
 
1015
- # Allow interactive commands (ipython, python, sqlite3, r) as valid commands
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 # Not a recognized command
1022
+ return False
1023
1023
 
1024
1024
  pattern = COMMAND_PATTERNS.get(base_command)
1025
1025
  if not pattern:
1026
- return True # Allow commands in BASH_COMMANDS but not in COMMAND_PATTERNS
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 # Invalid flag
1036
+ return False
1037
1037
  else:
1038
1038
  args.append(part)
1039
1039
 
1040
- # Check if 'who' has any arguments (it shouldn't)
1040
+
1041
1041
  if base_command == "who" and args:
1042
1042
  return False
1043
- # Check if any required arguments are missing
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
- # Map command to environment variable name
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
- # Read the current configuration
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
- # Check if the property exists and update it, or add it if it doesn't exist
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
- # Save the updated configuration
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
- # Also set it for the current session
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
- # Compare file modification times or contents to decide whether to update the file
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
- # Match KEY='value' or KEY="value" format
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
- # Try environment variable first
1257
+
1258
1258
  if env_value := os.getenv(key):
1259
1259
  return env_value
1260
1260
 
1261
- # Fall back to .npcshrc file
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
- # --- Global Clients ---
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
- # Set up completion logger
1336
+
1337
1337
  completion_logger = logging.getLogger('npcsh.completion')
1338
- completion_logger.setLevel(logging.WARNING) # Default to WARNING (quiet)
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
- # Check if we're completing a slash command
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
- # Team jinxs
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
- # NPC names for switching
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
- # Mode switching commands
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
- # Just return the name, let readline handle spacing/slashes
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
- # Get the part of buffer before the current word
1473
+
1474
1474
  before_word = buffer[:begidx]
1475
1475
 
1476
- # Split by command separators
1476
+
1477
1477
  parts = re.split(r'[|;&]', before_word)
1478
1478
  current_command_part = parts[-1].strip()
1479
1479
 
1480
- # If there's nothing before the current word in this command part,
1481
- # or only whitespace, we're at command position
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
- # Handle cases like 'ls -l' where filename is last
1611
+
1612
1612
  colored_line = " ".join(parts[:-1] + [colored_filepath])
1613
1613
  else:
1614
- # Handle cases where line is just the filename
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
- # --- Readline Setup and Completion ---
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
- # Don't set completer here - it will be set in run_repl with state
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 # Avoid empty embeddings
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
- # CORRECTED: Join all parts into one string to pass to the function.
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) # Revert if error
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
- # Look ahead for a value
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 # Consume the value
1812
+ i += 1
1813
1813
  else:
1814
- parsed_kwargs[key_part] = True # Boolean flag
1814
+ parsed_kwargs[key_part] = True
1815
1815
 
1816
1816
  elif part.startswith('-'):
1817
1817
  key = part[1:]
1818
- # Look ahead for a value
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 # Consume the value
1821
+ i += 1
1822
1822
  else:
1823
- parsed_kwargs[key] = True # Boolean flag
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
- # Skip if user input is very short (less than 10 chars)
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
- # Handle NPC switching commands
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
- # Check router commands first
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
- # Default chat model/provider
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
- # All other specific defaults
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
- # Handle model/provider inference
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
- # Check for jinxs in active NPC
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:] # Fix: use all_command_parts instead of command_parts
1962
+ args = all_command_parts[1:]
1963
1963
  try:
1964
- # Create input dictionary from args based on jinx inputs
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
- # Execute the jinx with proper parameters
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: # Team executor
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
- # Extract current state
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
- # Simple refinement prompt
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
- # Iterate with check_llm_command
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
- # Update state and return
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
- #print('# of parsed commands: ', len(commands))
2202
- #print('Commands:' '\n'.join(commands))
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: # Output was None
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
- # Only treat as bash if it looks like a shell command (starts with known command or is a slash command)
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 # Fall through to LLM
2277
+ pass
2278
2278
 
2279
- # Otherwise, treat as chat (LLM)
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
- #render_markdown(f"- Using forenpc: {forenpc_name}")
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
- #render_markdown('- Loaded team context'+ json.dumps(team_ctx, indent=2))
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
- # Also apply to the forenpc specifically
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" # fallback for ~/.npcsh/npc_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
- # Determine the actual NPC object to use for this turn's operations
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
- # --- Part 3: Periodic Team Context Suggestions ---
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: