npcsh 1.0.4__py3-none-any.whl → 1.0.5__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 CHANGED
@@ -411,7 +411,6 @@ BASH_COMMANDS = [
411
411
  "info",
412
412
  "whatis",
413
413
  "whereis",
414
- "which",
415
414
  "date",
416
415
  "cal",
417
416
  "bc",
@@ -548,17 +547,21 @@ def validate_bash_command(command_parts: list) -> bool:
548
547
  "--count",
549
548
  "--heading",
550
549
  ],
551
- "requires_arg": True,
550
+ "requires_arg": False,
552
551
  },
553
552
  "open": {
554
553
  "flags": ["-a", "-e", "-t", "-f", "-F", "-W", "-n", "-g", "-h"],
555
554
  "requires_arg": True,
556
555
  },
557
- "which": {"flags": ["-a", "-s", "-v"], "requires_arg": True},
556
+
558
557
  }
559
558
 
560
559
  base_command = command_parts[0]
561
560
 
561
+ if base_command == 'which':
562
+ return False # disable which arbitrarily cause the command parsing for it is too finnicky.
563
+
564
+
562
565
  if base_command not in COMMAND_PATTERNS:
563
566
  return True # Allow other commands to pass through
564
567
 
@@ -578,11 +581,6 @@ def validate_bash_command(command_parts: list) -> bool:
578
581
  # Check if 'who' has any arguments (it shouldn't)
579
582
  if base_command == "who" and args:
580
583
  return False
581
-
582
- # Handle 'which' with '-a' flag
583
- if base_command == "which" and "-a" in flags:
584
- return True # Allow 'which -a' with or without arguments.
585
-
586
584
  # Check if any required arguments are missing
587
585
  if pattern.get("requires_arg", False) and not args:
588
586
  return False
npcsh/npcsh.py CHANGED
@@ -37,7 +37,7 @@ from npcsh._state import (
37
37
  interactive_commands,
38
38
  BASH_COMMANDS,
39
39
  start_interactive_session,
40
-
40
+ validate_bash_command
41
41
  )
42
42
 
43
43
  from npcpy.npc_sysenv import (
@@ -81,9 +81,6 @@ except Exception as e:
81
81
  print(f"Warning: Failed to initialize ChromaDB client at {EMBEDDINGS_DB_PATH}: {e}")
82
82
  chroma_client = None
83
83
 
84
- # --- Custom Exceptions ---
85
- class CommandNotFoundError(Exception):
86
- pass
87
84
 
88
85
 
89
86
  from npcsh._state import initial_state, ShellState
@@ -404,14 +401,7 @@ def handle_bash_command(
404
401
  cmd_str: str,
405
402
  stdin_input: Optional[str],
406
403
  state: ShellState,
407
- ) -> Tuple[ShellState, str]:
408
-
409
- command_name = cmd_parts[0]
410
-
411
- if command_name in TERMINAL_EDITORS:
412
- output = open_terminal_editor(cmd_str)
413
- return state, output
414
-
404
+ ) -> Tuple[bool, str]:
415
405
  try:
416
406
  process = subprocess.Popen(
417
407
  cmd_parts,
@@ -421,39 +411,23 @@ def handle_bash_command(
421
411
  text=True,
422
412
  cwd=state.current_path
423
413
  )
424
-
425
414
  stdout, stderr = process.communicate(input=stdin_input)
426
415
 
427
416
  if process.returncode != 0:
428
- err_msg = stderr.strip() if stderr else f"Command '{cmd_str}' failed with return code {process.returncode}."
429
- # If it failed because command not found, raise specific error for fallback
430
- if "No such file or directory" in err_msg or "command not found" in err_msg:
431
- raise CommandNotFoundError(err_msg)
432
- # Otherwise, return the error output
433
- full_output = stdout.strip() + ("\n" + colored(f"stderr: {err_msg}", "red") if err_msg else "")
434
- return state, full_output.strip()
435
-
436
-
437
- output = stdout.strip() if stdout else ""
438
- if stderr:
439
- # Log stderr but don't necessarily include in piped output unless requested
440
- print(colored(f"stderr: {stderr.strip()}", "yellow"), file=sys.stderr)
417
+ return False, stderr.strip() if stderr else f"Command '{cmd_str}' failed with return code {process.returncode}."
441
418
 
419
+ if stderr.strip():
420
+ print(colored(f"stderr: {stderr.strip()}", "yellow"), file=sys.stderr)
421
+
422
+ if cmd_parts[0] in ["ls", "find", "dir"]:
423
+ return True, format_file_listing(stdout.strip())
442
424
 
443
- if command_name in ["ls", "find", "dir"]:
444
- output = format_file_listing(output)
445
- elif not output and process.returncode == 0 and not stderr:
446
- output = "" # No output is valid, don't print success message if piping
447
-
448
- return state, output
425
+ return True, stdout.strip()
449
426
 
450
427
  except FileNotFoundError:
451
- raise CommandNotFoundError(f"Command not found: {command_name}")
452
- except PermissionError as e:
453
- return state, colored(f"Error executing '{cmd_str}': Permission denied. {e}", "red")
454
- except Exception as e:
455
- return state, colored(f"Error executing command '{cmd_str}': {e}", "red")
456
-
428
+ return False, f"Command not found: {cmd_parts[0]}"
429
+ except PermissionError:
430
+ return False, f"Permission denied: {cmd_str}"
457
431
 
458
432
  def execute_slash_command(command: str, stdin_input: Optional[str], state: ShellState, stream: bool) -> Tuple[ShellState, Any]:
459
433
  """Executes slash commands using the router or checking NPC/Team jinxs."""
@@ -527,7 +501,6 @@ def execute_slash_command(command: str, stdin_input: Optional[str], state: Shell
527
501
 
528
502
  return state, colored(f"Unknown slash command or jinx: {command_name}", "red")
529
503
 
530
-
531
504
  def process_pipeline_command(
532
505
  cmd_segment: str,
533
506
  stdin_input: Optional[str],
@@ -553,85 +526,60 @@ def process_pipeline_command(
553
526
  if cmd_to_process.startswith("/"):
554
527
  return execute_slash_command(cmd_to_process, stdin_input, state, stream_final)
555
528
 
556
- try:
557
- cmd_parts = parse_command_safely(cmd_to_process)
558
- if not cmd_parts:
559
- return state, stdin_input
529
+ cmd_parts = parse_command_safely(cmd_to_process)
530
+ if not cmd_parts:
531
+ return state, stdin_input
560
532
 
533
+ if validate_bash_command(cmd_parts):
561
534
  command_name = cmd_parts[0]
562
-
563
- is_unambiguous_bash = (
564
- command_name in BASH_COMMANDS or
565
- command_name in interactive_commands or
566
- command_name == "cd" or
567
- cmd_to_process.startswith("./")
568
- )
569
-
570
- if is_unambiguous_bash:
571
- if command_name in interactive_commands:
572
- return handle_interactive_command(cmd_parts, state)
573
- elif command_name == "cd":
574
- return handle_cd_command(cmd_parts, state)
575
- else:
576
- return handle_bash_command(cmd_parts, cmd_to_process, stdin_input, state)
535
+ if command_name in interactive_commands:
536
+ return handle_interactive_command(cmd_parts, state)
537
+ if command_name == "cd":
538
+ return handle_cd_command(cmd_parts, state)
539
+
540
+ success, result = handle_bash_command(cmd_parts, cmd_to_process, stdin_input, state)
541
+ if success:
542
+ return state, result
577
543
  else:
578
- full_llm_cmd = f"{cmd_to_process} {stdin_input}" if stdin_input else cmd_to_process
579
-
580
- path_cmd = 'The current working directory is: ' + state.current_path
581
- ls_files = 'Files in the current directory (full paths):\n' + "\n".join([os.path.join(state.current_path, f) for f in os.listdir(state.current_path)]) if os.path.exists(state.current_path) else 'No files found in the current directory.'
582
- platform_info = f"Platform: {platform.system()} {platform.release()} ({platform.machine()})"
583
- info = path_cmd + '\n' + ls_files + '\n' + platform_info + '\n'
584
-
585
- llm_result = check_llm_command(
586
- full_llm_cmd,
587
- model=exec_model,
588
- provider=exec_provider,
589
- api_url=state.api_url,
590
- api_key=state.api_key,
591
- npc=state.npc,
592
- team=state.team,
593
- messages=state.messages,
594
- images=state.attachments,
595
- stream=stream_final,
596
- context=info,
597
- shell=True,
544
+ print(colored(f"Bash command failed. Asking LLM for a fix: {result}", "yellow"), file=sys.stderr)
545
+ fixer_prompt = f"The command '{cmd_to_process}' failed with the error: '{result}'. Provide the correct command."
546
+ response = execute_llm_command(
547
+ fixer_prompt,
548
+ model=exec_model,
549
+ provider=exec_provider,
550
+ npc=state.npc,
551
+ stream=stream_final,
552
+ messages=state.messages
598
553
  )
599
- if isinstance(llm_result, dict):
600
- state.messages = llm_result.get("messages", state.messages)
601
- output = llm_result.get("output")
602
- return state, output
603
- else:
604
- return state, llm_result
605
-
606
- except CommandNotFoundError as e:
607
- print(colored(f"Command not found, falling back to LLM: {e}", "yellow"), file=sys.stderr)
554
+ state.messages = response['messages']
555
+ return state, response['response']
556
+ else:
608
557
  full_llm_cmd = f"{cmd_to_process} {stdin_input}" if stdin_input else cmd_to_process
558
+ path_cmd = 'The current working directory is: ' + state.current_path
559
+ ls_files = 'Files in the current directory (full paths):\n' + "\n".join([os.path.join(state.current_path, f) for f in os.listdir(state.current_path)]) if os.path.exists(state.current_path) else 'No files found in the current directory.'
560
+ platform_info = f"Platform: {platform.system()} {platform.release()} ({platform.machine()})"
561
+ info = path_cmd + '\n' + ls_files + '\n' + platform_info + '\n'
562
+
609
563
  llm_result = check_llm_command(
610
- full_llm_cmd,
611
- model=exec_model,
564
+ full_llm_cmd,
565
+ model=exec_model,
612
566
  provider=exec_provider,
613
- api_url=state.api_url,
614
- api_key=state.api_key,
567
+ api_url=state.api_url,
568
+ api_key=state.api_key,
615
569
  npc=state.npc,
616
- team=state.team,
617
- messages=state.messages,
570
+ team=state.team,
571
+ messages=state.messages,
618
572
  images=state.attachments,
619
- stream=stream_final,
620
- context=None,
621
- shell=True
573
+ stream=stream_final,
574
+ context=info,
575
+ shell=True,
622
576
  )
623
577
  if isinstance(llm_result, dict):
624
578
  state.messages = llm_result.get("messages", state.messages)
625
579
  output = llm_result.get("output")
626
580
  return state, output
627
581
  else:
628
- return state, llm_result
629
-
630
- except Exception as e:
631
- import traceback
632
- traceback.print_exc()
633
- return state, colored(f"Error processing command '{cmd_segment[:50]}...': {e}", "red")
634
-
582
+ return state, llm_result
635
583
  def check_mode_switch(command:str , state: ShellState):
636
584
  if command in ['/cmd', '/agent', '/chat', '/ride']:
637
585
  state.current_mode = command[1:]
@@ -730,8 +678,6 @@ def execute_command(
730
678
  try:
731
679
  bash_state, bash_output = handle_bash_command(cmd_parts, command, None, state)
732
680
  return bash_state, bash_output
733
- except CommandNotFoundError:
734
- pass # Fall through to LLM
735
681
  except Exception as bash_err:
736
682
  return state, colored(f"Bash execution failed: {bash_err}", "red")
737
683
  except Exception:
@@ -1216,46 +1162,51 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
1216
1162
  team_dir = project_team_path
1217
1163
  default_forenpc_name = "forenpc"
1218
1164
  else:
1219
- resp = input(f"No npc_team found in {os.getcwd()}. Create a new team here? [Y/n]: ").strip().lower()
1220
- if resp in ("", "y", "yes"):
1221
- team_dir = project_team_path
1222
- os.makedirs(team_dir, exist_ok=True)
1223
- default_forenpc_name = "forenpc"
1224
- forenpc_directive = input(
1225
- f"Enter a primary directive for {default_forenpc_name} (default: 'You are the forenpc of the team...'): "
1226
- ).strip() or "You are the forenpc of the team, coordinating activities between NPCs on the team, verifying that results from NPCs are high quality and can help to adequately answer user requests."
1227
- forenpc_model = input("Enter a model for your forenpc (default: llama3.2): ").strip() or "llama3.2"
1228
- forenpc_provider = input("Enter a provider for your forenpc (default: ollama): ").strip() or "ollama"
1229
-
1230
- with open(os.path.join(team_dir, f"{default_forenpc_name}.npc"), "w") as f:
1231
- yaml.dump({
1232
- "name": default_forenpc_name, "primary_directive": forenpc_directive,
1233
- "model": forenpc_model, "provider": forenpc_provider
1234
- }, f)
1235
-
1236
- ctx_path = os.path.join(team_dir, "team.ctx")
1237
- folder_context = input("Enter a short description for this project/team (optional): ").strip()
1238
- team_ctx_data = {
1239
- "forenpc": default_forenpc_name, "model": forenpc_model,
1240
- "provider": forenpc_provider, "api_key": None, "api_url": None,
1241
- "context": folder_context if folder_context else None
1242
- }
1243
- use_jinxs = input("Use global jinxs folder (g) or copy to this project (c)? [g/c, default: g]: ").strip().lower()
1244
- if use_jinxs == "c":
1245
- global_jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
1246
- if os.path.exists(global_jinxs_dir):
1247
- shutil.copytree(global_jinxs_dir, os.path.join(team_dir, "jinxs"), dirs_exist_ok=True)
1248
- else:
1249
- team_ctx_data["use_global_jinxs"] = True
1165
+ if not os.path.exists('.npcsh_global'):
1166
+ resp = input(f"No npc_team found in {os.getcwd()}. Create a new team here? [Y/n]: ").strip().lower()
1167
+ if resp in ("", "y", "yes"):
1168
+ team_dir = project_team_path
1169
+ os.makedirs(team_dir, exist_ok=True)
1170
+ default_forenpc_name = "forenpc"
1171
+ forenpc_directive = input(
1172
+ f"Enter a primary directive for {default_forenpc_name} (default: 'You are the forenpc of the team...'): "
1173
+ ).strip() or "You are the forenpc of the team, coordinating activities between NPCs on the team, verifying that results from NPCs are high quality and can help to adequately answer user requests."
1174
+ forenpc_model = input("Enter a model for your forenpc (default: llama3.2): ").strip() or "llama3.2"
1175
+ forenpc_provider = input("Enter a provider for your forenpc (default: ollama): ").strip() or "ollama"
1176
+
1177
+ with open(os.path.join(team_dir, f"{default_forenpc_name}.npc"), "w") as f:
1178
+ yaml.dump({
1179
+ "name": default_forenpc_name, "primary_directive": forenpc_directive,
1180
+ "model": forenpc_model, "provider": forenpc_provider
1181
+ }, f)
1182
+
1183
+ ctx_path = os.path.join(team_dir, "team.ctx")
1184
+ folder_context = input("Enter a short description for this project/team (optional): ").strip()
1185
+ team_ctx_data = {
1186
+ "forenpc": default_forenpc_name, "model": forenpc_model,
1187
+ "provider": forenpc_provider, "api_key": None, "api_url": None,
1188
+ "context": folder_context if folder_context else None
1189
+ }
1190
+ use_jinxs = input("Use global jinxs folder (g) or copy to this project (c)? [g/c, default: g]: ").strip().lower()
1191
+ if use_jinxs == "c":
1192
+ global_jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
1193
+ if os.path.exists(global_jinxs_dir):
1194
+ shutil.copytree(global_jinxs_dir, os.path.join(team_dir, "jinxs"), dirs_exist_ok=True)
1195
+ else:
1196
+ team_ctx_data["use_global_jinxs"] = True
1250
1197
 
1251
- with open(ctx_path, "w") as f:
1252
- yaml.dump(team_ctx_data, f)
1198
+ with open(ctx_path, "w") as f:
1199
+ yaml.dump(team_ctx_data, f)
1200
+ else:
1201
+ render_markdown('From now on, npcsh will assume you will use the global team when activating from this folder. \n If you change your mind and want to initialize a team, use /init from within npcsh, `npc init` or `rm .npcsh_global` from the current working directory.')
1202
+ with open(".npcsh_global", "w") as f:
1203
+ pass
1204
+ team_dir = global_team_path
1205
+ default_forenpc_name = "sibiji"
1253
1206
  elif os.path.exists(global_team_path):
1254
1207
  team_dir = global_team_path
1255
- default_forenpc_name = "sibiji"
1256
- else:
1257
- print("No global npc_team found. Please run 'npcpy init' or create a team first.")
1258
- sys.exit(1)
1208
+ default_forenpc_name = "sibiji"
1209
+
1259
1210
 
1260
1211
  team_ctx = {}
1261
1212
  for filename in os.listdir(team_dir):
@@ -1369,6 +1320,12 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState):
1369
1320
 
1370
1321
  def exit_shell(state):
1371
1322
  print("\nGoodbye!")
1323
+ # update the team ctx file to update the context and the preferences
1324
+
1325
+
1326
+
1327
+
1328
+
1372
1329
  #print('beginning knowledge consolidation')
1373
1330
  #try:
1374
1331
  # breathe_result = breathe(state.messages, state.chat_model, state.chat_provider, state.npc)
@@ -1432,27 +1389,6 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState):
1432
1389
  # Ctrl+D: exit shell cleanly
1433
1390
  exit_shell(state)
1434
1391
 
1435
- def run_non_interactive(command_history: CommandHistory, initial_state: ShellState):
1436
- state = initial_state
1437
- # print("Running in non-interactive mode...", file=sys.stderr) # Optional debug
1438
-
1439
- for line in sys.stdin:
1440
- user_input = line.strip()
1441
- if not user_input:
1442
- continue
1443
- if user_input.lower() in ["exit", "quit"]:
1444
- break
1445
-
1446
- state.current_path = os.getcwd()
1447
- state, output = execute_command(user_input, state)
1448
- # Non-interactive: just print raw output, don't process results complexly
1449
- if state.stream_output and isgenerator(output):
1450
- for chunk in output: print(str(chunk), end='')
1451
- print()
1452
- elif output is not None:
1453
- print(output)
1454
- # Maybe still log history?
1455
- # process_result(user_input, state, output, command_history)
1456
1392
 
1457
1393
  def main() -> None:
1458
1394
  parser = argparse.ArgumentParser(description="npcsh - An NPC-powered shell.")
@@ -1470,6 +1406,9 @@ def main() -> None:
1470
1406
  initial_state.team = team
1471
1407
  #import pdb
1472
1408
  #pdb.set_trace()
1409
+
1410
+ # add a -g global command to indicate if to use the global or project, otherwise go thru normal flow
1411
+
1473
1412
  if args.command:
1474
1413
  state = initial_state
1475
1414
  state.current_path = os.getcwd()
@@ -1479,9 +1418,6 @@ def main() -> None:
1479
1418
  print()
1480
1419
  elif output is not None:
1481
1420
  print(output)
1482
-
1483
- elif not sys.stdin.isatty():
1484
- run_non_interactive(command_history, initial_state)
1485
1421
  else:
1486
1422
  run_repl(command_history, initial_state)
1487
1423
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcsh
3
- Version: 1.0.4
3
+ Version: 1.0.5
4
4
  Summary: npcsh is a command-line toolkit for using AI agents in novel ways.
5
5
  Home-page: https://github.com/NPC-Worldwide/npcsh
6
6
  Author: Christopher Agostino
@@ -1,21 +1,21 @@
1
1
  npcsh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- npcsh/_state.py,sha256=ScwAYt01ZCksd3fuFBVa_VmgFzd3lR5Pg3q__sFmJQc,27450
2
+ npcsh/_state.py,sha256=3DbJ9X6Ol5ubXFjy-OrV2ioDt6qMLvh4CAsoLCJ5kbg,27347
3
3
  npcsh/alicanto.py,sha256=zJF5YwSNvtbK2EUKXzG45WOCMsSFu5cek5jCR7FgiuE,44709
4
4
  npcsh/guac.py,sha256=Ocmk_c4NUtGsC3JOtmkbgLvD6u-XtBPRFRYcckpgUJU,33099
5
5
  npcsh/mcp_helpers.py,sha256=Ktd2yXuBnLL2P7OMalgGLj84PXJSzaucjqmJVvWx6HA,12723
6
6
  npcsh/mcp_npcsh.py,sha256=SfmplH62GS9iI6q4vuQLVUS6tkrok6L7JxODx_iH7ps,36158
7
7
  npcsh/mcp_server.py,sha256=l2Ra0lpFrUu334pvp0Q9ajF2n73KvZswFi0FgbDhh9k,5884
8
8
  npcsh/npc.py,sha256=JEP0nqbqRGvAthj9uT0ZfbGc322g3Ge3rRDKbpIdI1s,7907
9
- npcsh/npcsh.py,sha256=P4HvwZ4UE4JWLDPtgSy3BFRBhmTRmgOnhVt98nGA9q4,57416
9
+ npcsh/npcsh.py,sha256=yRpTvpy8OiVCyWpHfNGfuueojwy2ZHnoZii91S4fKaQ,55229
10
10
  npcsh/plonk.py,sha256=U2e9yUJZN95Girzzvgrh-40zOdl5zO3AHPsIjoyLv2M,15261
11
11
  npcsh/pti.py,sha256=jGHGE5SeIcDkV8WlOEHCKQCnYAL4IPS-kUBHrUz0oDA,10019
12
12
  npcsh/routes.py,sha256=ufQVc6aqgC14_YHV88iwV53TN1Pk095NB6gFDqQqfB4,37208
13
13
  npcsh/spool.py,sha256=GhnSFX9uAtrB4m_ijuyA5tufH12DrWdABw0z8FmiCHc,11497
14
14
  npcsh/wander.py,sha256=BiN6eYyFnEsFzo8MFLRkdZ8xS9sTKkQpjiCcy9chMcc,23225
15
15
  npcsh/yap.py,sha256=h5KNt9sNOrDPhGe_zfn_yFIeQhizX09zocjcPWH7m3k,20905
16
- npcsh-1.0.4.dist-info/licenses/LICENSE,sha256=IKBvAECHP-aCiJtE4cHGCE5Yl0tozYz02PomGeWS3y4,1070
17
- npcsh-1.0.4.dist-info/METADATA,sha256=OPk2EAs6I_XHJx9kavt1jQM4Ai-c3ckSBXvAseSjB90,22747
18
- npcsh-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- npcsh-1.0.4.dist-info/entry_points.txt,sha256=qxOYTm3ym3JWyWf2nv2Mk71uMcJIdUoNEJ8VYMkyHiY,214
20
- npcsh-1.0.4.dist-info/top_level.txt,sha256=kHSNgKMCkfjV95-DH0YSp1LLBi0HXdF3w57j7MQON3E,6
21
- npcsh-1.0.4.dist-info/RECORD,,
16
+ npcsh-1.0.5.dist-info/licenses/LICENSE,sha256=IKBvAECHP-aCiJtE4cHGCE5Yl0tozYz02PomGeWS3y4,1070
17
+ npcsh-1.0.5.dist-info/METADATA,sha256=f9x6EEZT5YlU83DcV-uLA5egMSVHYF0qyHF3DczuRMs,22747
18
+ npcsh-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ npcsh-1.0.5.dist-info/entry_points.txt,sha256=qxOYTm3ym3JWyWf2nv2Mk71uMcJIdUoNEJ8VYMkyHiY,214
20
+ npcsh-1.0.5.dist-info/top_level.txt,sha256=kHSNgKMCkfjV95-DH0YSp1LLBi0HXdF3w57j7MQON3E,6
21
+ npcsh-1.0.5.dist-info/RECORD,,
File without changes