npcsh 1.1.22__py3-none-any.whl → 1.1.23__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 (172) hide show
  1. npcsh/_state.py +272 -120
  2. npcsh/benchmark/npcsh_agent.py +77 -240
  3. npcsh/benchmark/templates/install-npcsh.sh.j2 +12 -4
  4. npcsh/config.py +5 -2
  5. npcsh/npc_team/alicanto.npc +4 -8
  6. npcsh/npc_team/corca.npc +5 -11
  7. npcsh/npc_team/frederic.npc +4 -6
  8. npcsh/npc_team/guac.npc +4 -4
  9. npcsh/npc_team/jinxs/lib/core/delegate.jinx +1 -1
  10. npcsh/npc_team/jinxs/lib/core/edit_file.jinx +1 -1
  11. npcsh/npc_team/jinxs/lib/core/sh.jinx +1 -1
  12. npcsh/npc_team/jinxs/lib/core/skill.jinx +59 -0
  13. npcsh/npc_team/jinxs/lib/utils/help.jinx +194 -10
  14. npcsh/npc_team/jinxs/lib/utils/init.jinx +528 -37
  15. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +0 -1
  16. npcsh/npc_team/jinxs/lib/utils/serve.jinx +938 -21
  17. npcsh-1.1.22.data/data/npcsh/npc_team/config_tui.jinx → npcsh/npc_team/jinxs/modes/config.jinx +1 -1
  18. npcsh/npc_team/jinxs/modes/convene.jinx +76 -3
  19. npcsh/npc_team/jinxs/modes/crond.jinx +818 -0
  20. npcsh/npc_team/jinxs/modes/plonk.jinx +76 -14
  21. npcsh/npc_team/jinxs/modes/roll.jinx +368 -55
  22. npcsh/npc_team/jinxs/modes/skills.jinx +621 -0
  23. npcsh/npc_team/jinxs/modes/yap.jinx +504 -30
  24. npcsh/npc_team/jinxs/skills/code-review/SKILL.md +45 -0
  25. npcsh/npc_team/jinxs/skills/debugging/SKILL.md +44 -0
  26. npcsh/npc_team/jinxs/skills/git-workflow.jinx +44 -0
  27. npcsh/npc_team/kadiefa.npc +4 -5
  28. npcsh/npc_team/npcsh.ctx +16 -0
  29. npcsh/npc_team/plonk.npc +5 -9
  30. npcsh/npc_team/sibiji.npc +13 -5
  31. npcsh/npcsh.py +1 -0
  32. npcsh/routes.py +0 -4
  33. npcsh/yap.py +22 -4
  34. npcsh-1.1.23.data/data/npcsh/npc_team/SKILL.md +44 -0
  35. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/alicanto.npc +4 -8
  36. npcsh/npc_team/jinxs/modes/config_tui.jinx → npcsh-1.1.23.data/data/npcsh/npc_team/config.jinx +1 -1
  37. npcsh-1.1.23.data/data/npcsh/npc_team/convene.jinx +670 -0
  38. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca.npc +5 -11
  39. npcsh-1.1.23.data/data/npcsh/npc_team/crond.jinx +818 -0
  40. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/delegate.jinx +1 -1
  41. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/edit_file.jinx +1 -1
  42. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/frederic.npc +4 -6
  43. npcsh-1.1.23.data/data/npcsh/npc_team/git-workflow.jinx +44 -0
  44. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/guac.npc +4 -4
  45. npcsh-1.1.23.data/data/npcsh/npc_team/help.jinx +236 -0
  46. npcsh-1.1.23.data/data/npcsh/npc_team/init.jinx +532 -0
  47. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/jinxs.jinx +0 -1
  48. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/kadiefa.npc +4 -5
  49. npcsh-1.1.23.data/data/npcsh/npc_team/npcsh.ctx +34 -0
  50. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonk.jinx +76 -14
  51. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonk.npc +5 -9
  52. npcsh-1.1.23.data/data/npcsh/npc_team/roll.jinx +378 -0
  53. npcsh-1.1.23.data/data/npcsh/npc_team/serve.jinx +943 -0
  54. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sh.jinx +1 -1
  55. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sibiji.npc +13 -5
  56. npcsh-1.1.23.data/data/npcsh/npc_team/skill.jinx +59 -0
  57. npcsh-1.1.23.data/data/npcsh/npc_team/skills.jinx +621 -0
  58. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/yap.jinx +504 -30
  59. {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/METADATA +168 -7
  60. npcsh-1.1.23.dist-info/RECORD +216 -0
  61. npcsh/npc_team/jinxs/incognide/add_tab.jinx +0 -11
  62. npcsh/npc_team/jinxs/incognide/close_pane.jinx +0 -9
  63. npcsh/npc_team/jinxs/incognide/close_tab.jinx +0 -10
  64. npcsh/npc_team/jinxs/incognide/confirm.jinx +0 -10
  65. npcsh/npc_team/jinxs/incognide/focus_pane.jinx +0 -9
  66. npcsh/npc_team/jinxs/incognide/list_panes.jinx +0 -8
  67. npcsh/npc_team/jinxs/incognide/navigate.jinx +0 -10
  68. npcsh/npc_team/jinxs/incognide/notify.jinx +0 -10
  69. npcsh/npc_team/jinxs/incognide/open_pane.jinx +0 -13
  70. npcsh/npc_team/jinxs/incognide/read_pane.jinx +0 -9
  71. npcsh/npc_team/jinxs/incognide/run_terminal.jinx +0 -10
  72. npcsh/npc_team/jinxs/incognide/send_message.jinx +0 -10
  73. npcsh/npc_team/jinxs/incognide/split_pane.jinx +0 -12
  74. npcsh/npc_team/jinxs/incognide/switch_npc.jinx +0 -10
  75. npcsh/npc_team/jinxs/incognide/switch_tab.jinx +0 -10
  76. npcsh/npc_team/jinxs/incognide/write_file.jinx +0 -11
  77. npcsh/npc_team/jinxs/incognide/zen_mode.jinx +0 -9
  78. npcsh/npc_team/jinxs/lib/core/convene.jinx +0 -232
  79. npcsh-1.1.22.data/data/npcsh/npc_team/add_tab.jinx +0 -11
  80. npcsh-1.1.22.data/data/npcsh/npc_team/close_pane.jinx +0 -9
  81. npcsh-1.1.22.data/data/npcsh/npc_team/close_tab.jinx +0 -10
  82. npcsh-1.1.22.data/data/npcsh/npc_team/confirm.jinx +0 -10
  83. npcsh-1.1.22.data/data/npcsh/npc_team/convene.jinx +0 -232
  84. npcsh-1.1.22.data/data/npcsh/npc_team/focus_pane.jinx +0 -9
  85. npcsh-1.1.22.data/data/npcsh/npc_team/help.jinx +0 -52
  86. npcsh-1.1.22.data/data/npcsh/npc_team/init.jinx +0 -41
  87. npcsh-1.1.22.data/data/npcsh/npc_team/list_panes.jinx +0 -8
  88. npcsh-1.1.22.data/data/npcsh/npc_team/navigate.jinx +0 -10
  89. npcsh-1.1.22.data/data/npcsh/npc_team/notify.jinx +0 -10
  90. npcsh-1.1.22.data/data/npcsh/npc_team/npcsh.ctx +0 -18
  91. npcsh-1.1.22.data/data/npcsh/npc_team/open_pane.jinx +0 -13
  92. npcsh-1.1.22.data/data/npcsh/npc_team/read_pane.jinx +0 -9
  93. npcsh-1.1.22.data/data/npcsh/npc_team/roll.jinx +0 -65
  94. npcsh-1.1.22.data/data/npcsh/npc_team/run_terminal.jinx +0 -10
  95. npcsh-1.1.22.data/data/npcsh/npc_team/send_message.jinx +0 -10
  96. npcsh-1.1.22.data/data/npcsh/npc_team/serve.jinx +0 -26
  97. npcsh-1.1.22.data/data/npcsh/npc_team/split_pane.jinx +0 -12
  98. npcsh-1.1.22.data/data/npcsh/npc_team/switch_npc.jinx +0 -10
  99. npcsh-1.1.22.data/data/npcsh/npc_team/switch_tab.jinx +0 -10
  100. npcsh-1.1.22.data/data/npcsh/npc_team/write_file.jinx +0 -11
  101. npcsh-1.1.22.data/data/npcsh/npc_team/zen_mode.jinx +0 -9
  102. npcsh-1.1.22.dist-info/RECORD +0 -240
  103. /npcsh/npc_team/jinxs/{incognide → lib/utils}/incognide.jinx +0 -0
  104. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/alicanto.jinx +0 -0
  105. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/alicanto.png +0 -0
  106. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/arxiv.jinx +0 -0
  107. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/benchmark.jinx +0 -0
  108. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  109. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  110. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/build.jinx +0 -0
  111. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/chat.jinx +0 -0
  112. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/click.jinx +0 -0
  113. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  114. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  115. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/compile.jinx +0 -0
  116. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/compress.jinx +0 -0
  117. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca.jinx +0 -0
  118. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca.png +0 -0
  119. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca_example.png +0 -0
  120. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/db_search.jinx +0 -0
  121. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/file_search.jinx +0 -0
  122. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/frederic4.png +0 -0
  123. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/git.jinx +0 -0
  124. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/guac.jinx +0 -0
  125. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/guac.png +0 -0
  126. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  127. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  128. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  129. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/kg.jinx +0 -0
  130. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  131. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  132. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/memories.jinx +0 -0
  133. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/models.jinx +0 -0
  134. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  135. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/nql.jinx +0 -0
  136. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  137. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/ots.jinx +0 -0
  138. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/papers.jinx +0 -0
  139. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/paste.jinx +0 -0
  140. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonk.png +0 -0
  141. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  142. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/pti.jinx +0 -0
  143. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/python.jinx +0 -0
  144. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/reattach.jinx +0 -0
  145. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sample.jinx +0 -0
  146. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  147. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/set.jinx +0 -0
  148. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/setup.jinx +0 -0
  149. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/shh.jinx +0 -0
  150. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sibiji.png +0 -0
  151. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  152. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/spool.jinx +0 -0
  153. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/spool.png +0 -0
  154. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sql.jinx +0 -0
  155. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/switch.jinx +0 -0
  156. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/switches.jinx +0 -0
  157. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sync.jinx +0 -0
  158. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/team.jinx +0 -0
  159. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  160. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  161. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  162. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/usage.jinx +0 -0
  163. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  164. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  165. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/wait.jinx +0 -0
  166. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/wander.jinx +0 -0
  167. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/web_search.jinx +0 -0
  168. {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/yap.png +0 -0
  169. {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/WHEEL +0 -0
  170. {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/entry_points.txt +0 -0
  171. {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/licenses/LICENSE +0 -0
  172. {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/top_level.txt +0 -0
@@ -31,6 +31,9 @@ class NpcshAgent(BaseInstalledAgent):
31
31
 
32
32
  SUPPORTS_ATIF = True # Agent Trajectory Interchange Format
33
33
 
34
+ # Root of the npcsh source tree (two levels up from this file)
35
+ _NPCSH_SRC = Path(__file__).resolve().parent.parent.parent
36
+
34
37
  def __init__(self, logs_dir: Path = None, model_name: str = None, logger=None, **kwargs):
35
38
  super().__init__(logs_dir=logs_dir, model_name=model_name, logger=logger, **kwargs)
36
39
 
@@ -43,138 +46,96 @@ class NpcshAgent(BaseInstalledAgent):
43
46
  """Path to the jinja template script for installing npcsh in the container."""
44
47
  return Path(__file__).parent / "templates" / "install-npcsh.sh.j2"
45
48
 
46
- def create_run_agent_commands(self, instruction: str) -> list:
47
- """
48
- Create the commands to run npcsh in the container.
49
-
50
- Args:
51
- instruction: The task instruction from Terminal-Bench
52
-
53
- Returns:
54
- List of ExecInput commands to execute
55
- """
56
- # Wrap the instruction with tool usage directions and retry logic
57
- tool_instruction = f"""You have access to tools: edit_file (for writing/creating files), sh (for running shell commands), and python (for running Python code).
58
-
59
- IMPORTANT RULES:
60
- 1. You MUST call these tools using the function calling interface to complete the task. Do NOT write tool names as text - invoke them as function calls.
61
- 2. After implementing a solution, you MUST verify it works by running any provided test scripts.
62
- 3. If a test fails or produces an error, you MUST try a DIFFERENT approach. Do not give up.
63
- 4. Keep trying different approaches until you succeed or have tried at least 10 different solutions.
64
- 5. NEVER assume success - always check the actual output of test commands.
49
+ async def setup(self, environment) -> None:
50
+ """Upload local npcsh + npcpy source, then run install script."""
51
+ import shutil
52
+ import tempfile
53
+
54
+ npcsh_src = self._NPCSH_SRC
55
+ npcpy_src = npcsh_src.parent / "npcpy"
56
+
57
+ # Create /src in container
58
+ await environment.exec(command="mkdir -p /src")
59
+
60
+ # Copy source to temp dir excluding .git and caches
61
+ def _copy_clean(src, name):
62
+ tmp = Path(tempfile.mkdtemp()) / name
63
+ shutil.copytree(
64
+ src, tmp,
65
+ ignore=shutil.ignore_patterns(
66
+ '.git', '__pycache__', '*.pyc', 'dist', 'build',
67
+ '*.egg-info', 'jobs', 'dataset_cache',
68
+ ),
69
+ )
70
+ return tmp
71
+
72
+ clean_npcsh = _copy_clean(npcsh_src, "npcsh")
73
+ await environment.upload_dir(
74
+ source_dir=str(clean_npcsh),
75
+ target_dir="/src/npcsh",
76
+ )
65
77
 
66
- Task: {instruction}
78
+ if npcpy_src.exists():
79
+ clean_npcpy = _copy_clean(npcpy_src, "npcpy")
80
+ await environment.upload_dir(
81
+ source_dir=str(clean_npcpy),
82
+ target_dir="/src/npcpy",
83
+ )
67
84
 
68
- WORKFLOW:
69
- 1. Call edit_file to write code files. Call sh to run commands.
70
- 2. Run any test scripts mentioned in the task
71
- 3. Check the output carefully - look for "PASS", "SUCCESS", "OK" or similar
72
- 4. If the test failed, analyze why and try a completely different approach
73
- 5. Repeat until the test passes"""
85
+ await super().setup(environment)
74
86
 
75
- escaped_instruction = shlex.quote(tool_instruction)
76
- model_name = self.model_name
87
+ def create_run_agent_commands(self, instruction: str) -> list:
88
+ """Run instruction through npcsh -c, which handles everything."""
89
+ escaped_instruction = shlex.quote(instruction)
77
90
 
78
- if model_name and "/" in model_name:
91
+ # Forward env vars into the container — npcsh reads these directly
92
+ env_vars = []
93
+ for key in [
94
+ "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_API_KEY",
95
+ "GEMINI_API_KEY", "DEEPSEEK_API_KEY", "GROQ_API_KEY",
96
+ "OPENROUTER_API_KEY", "OLLAMA_HOST",
97
+ ]:
98
+ val = os.environ.get(key)
99
+ if val:
100
+ env_vars.append(f'{key}="{val}"')
101
+
102
+ # Model/provider from Harbor's model_name (e.g. "ollama/phi4")
103
+ model_name = self.model_name or ""
104
+ if "/" in model_name:
79
105
  provider, model = model_name.split("/", 1)
80
- elif model_name:
81
- provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
82
- model = model_name
83
106
  else:
84
107
  provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
85
- model = os.environ.get("NPCSH_CHAT_MODEL", "")
86
-
87
- # Map provider names to npcsh provider format
88
- provider_map = {
89
- "anthropic": "anthropic",
90
- "openai": "openai",
91
- "google": "gemini",
92
- "gemini": "gemini",
93
- "deepseek": "deepseek",
94
- "ollama": "ollama",
95
- "groq": "groq",
96
- "openrouter": "openrouter",
97
- }
98
- npcsh_provider = provider_map.get(provider.lower(), provider)
99
-
100
- # Build environment variables for API keys
101
- env_vars = []
102
- api_key_map = {
103
- "anthropic": ["ANTHROPIC_API_KEY"],
104
- "openai": ["OPENAI_API_KEY"],
105
- "gemini": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
106
- "google": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
107
- "deepseek": ["DEEPSEEK_API_KEY"],
108
- "groq": ["GROQ_API_KEY"],
109
- "openrouter": ["OPENROUTER_API_KEY"],
110
- }
111
-
112
- added_keys = set()
113
- for prov, env_keys in api_key_map.items():
114
- for env_key in env_keys:
115
- if env_key in os.environ:
116
- # For Gemini, always pass as GOOGLE_API_KEY (what litellm expects)
117
- target_key = "GOOGLE_API_KEY" if env_key == "GEMINI_API_KEY" else env_key
118
- if target_key not in added_keys:
119
- env_vars.append(f'{target_key}="{os.environ[env_key]}"')
120
- added_keys.add(target_key)
121
- break
122
-
123
- env_prefix = " ".join(env_vars) + " " if env_vars else ""
124
-
125
- # Output directory for logs
126
- output_dir = str(self.logs_dir / "npcsh_output")
127
- output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
108
+ model = model_name or os.environ.get("NPCSH_CHAT_MODEL", "")
128
109
 
129
- commands = []
130
-
131
- # Create output directory
132
- commands.append(ExecInput(
133
- command=f"mkdir -p {shlex.quote(output_dir)}",
134
- timeout_sec=30
135
- ))
136
-
137
- # Create .npcsh_global file to use global team and avoid interactive prompts
138
- commands.append(ExecInput(
139
- command="touch /app/.npcsh_global",
140
- timeout_sec=10
141
- ))
142
-
143
- # Run npcsh with the instruction
144
- # Using corca NPC which has edit_file tool for writing files
145
- # Using the npc CLI which supports single-command execution
146
- # NPCSH_DEFAULT_MODE=agent enables automatic tool execution
147
- ollama_env = ""
148
- if npcsh_provider == "ollama":
149
- ollama_host = os.environ.get("OLLAMA_HOST", "http://host.docker.internal:11434")
150
- ollama_env = f'OLLAMA_HOST="{ollama_host}" '
151
-
152
- npcsh_cmd = (
153
- f'{env_prefix}'
154
- f'{ollama_env}'
155
- f'NPCSH_CHAT_MODEL="{model}" '
156
- f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
157
- f'NPCSH_STREAM_OUTPUT=0 '
158
- f'NPCSH_DEFAULT_MODE=agent '
159
- f'npc --npc corca {escaped_instruction} '
160
- f'2>&1 | tee {shlex.quote(output_file)}'
161
- )
110
+ env_vars.append(f'NPCSH_CHAT_MODEL="{model}"')
111
+ env_vars.append(f'NPCSH_CHAT_PROVIDER="{provider}"')
112
+ env_vars.append('NPCSH_STREAM_OUTPUT=0')
113
+ env_vars.append('NPCSH_DEBUG=1')
114
+
115
+ if provider == "ollama":
116
+ if "OLLAMA_HOST" not in os.environ:
117
+ env_vars.append('OLLAMA_HOST="http://host.docker.internal:11434"')
118
+ # Use 16k context for ollama models to avoid losing instructions
119
+ env_vars.append('NPCSH_OLLAMA_NUM_CTX=16384')
162
120
 
163
- commands.append(ExecInput(
164
- command=npcsh_cmd,
165
- timeout_sec=600, # 10 minute timeout for complex tasks
166
- ))
121
+ env_prefix = " ".join(env_vars)
122
+ output_dir = str(self.logs_dir / "npcsh_output")
123
+ output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
167
124
 
168
- return commands
125
+ return [
126
+ ExecInput(command=f"mkdir -p {shlex.quote(output_dir)}", timeout_sec=30),
127
+ ExecInput(command="touch /app/.npcsh_global", timeout_sec=10),
128
+ ExecInput(
129
+ command=f'{env_prefix} npcsh -c {escaped_instruction} 2>&1 | tee {shlex.quote(output_file)}',
130
+ timeout_sec=1800,
131
+ ),
132
+ ]
169
133
 
170
134
  def populate_context_post_run(self, context: AgentContext) -> None:
171
135
  """
172
136
  Populate the context with results of the agent execution.
173
137
 
174
138
  Parses the output file to extract token usage metrics.
175
-
176
- Args:
177
- context: The AgentContext to populate with metrics
178
139
  """
179
140
  output_file = self.logs_dir / "npcsh_output" / "output.jsonl"
180
141
 
@@ -187,149 +148,25 @@ WORKFLOW:
187
148
  with open(output_file, 'r') as f:
188
149
  content = f.read()
189
150
 
190
- # Try to parse as JSONL first
191
151
  for line in content.strip().split('\n'):
192
152
  if not line.strip():
193
153
  continue
194
154
  try:
195
155
  event = json.loads(line)
196
- # Extract token usage from events if present
197
156
  if isinstance(event, dict):
198
157
  usage = event.get('usage', {})
199
158
  total_input_tokens += usage.get('input_tokens', 0)
200
159
  total_output_tokens += usage.get('output_tokens', 0)
201
160
  total_cost_usd += usage.get('cost_usd', 0.0)
202
161
  except json.JSONDecodeError:
203
- # Not JSON, just regular output
204
162
  pass
205
163
 
206
164
  except Exception as e:
207
165
  self.logger.warning(f"Failed to parse npcsh output: {e}")
208
166
 
209
- # Set context metrics
210
167
  if hasattr(context, 'input_tokens'):
211
168
  context.input_tokens = total_input_tokens
212
169
  if hasattr(context, 'output_tokens'):
213
170
  context.output_tokens = total_output_tokens
214
171
  if hasattr(context, 'cost_usd'):
215
172
  context.cost_usd = total_cost_usd
216
-
217
-
218
- class NpcshAgentWithNpc(NpcshAgent):
219
- """
220
- Variant that uses a specific NPC for task execution.
221
-
222
- This allows benchmarking specific NPCs like sibiji (orchestrator),
223
- corca (coding), or custom NPCs.
224
-
225
- Usage:
226
- harbor run -d terminal-bench@2.0 \\
227
- --agent-import-path "npcsh.benchmark:NpcshAgentWithNpc" \\
228
- -m anthropic/claude-sonnet-4-20250514 -n 4
229
- """
230
-
231
- def __init__(self, *args, npc_name: str = "sibiji", **kwargs):
232
- super().__init__(*args, **kwargs)
233
- self.npc_name = npc_name
234
-
235
- @staticmethod
236
- def name() -> str:
237
- return "npcsh-npc"
238
-
239
- def create_run_agent_commands(self, instruction: str) -> list:
240
- """Create commands using a specific NPC."""
241
- # Wrap the instruction with tool usage directions and retry logic
242
- tool_instruction = f"""You have access to tools: edit_file (for writing/creating files), sh (for running shell commands), and python (for running Python code).
243
-
244
- IMPORTANT RULES:
245
- 1. You MUST call these tools using the function calling interface to complete the task. Do NOT write tool names as text - invoke them as function calls.
246
- 2. After implementing a solution, you MUST verify it works by running any provided test scripts.
247
- 3. If a test fails or produces an error, you MUST try a DIFFERENT approach. Do not give up.
248
- 4. Keep trying different approaches until you succeed or have tried at least 10 different solutions.
249
- 5. NEVER assume success - always check the actual output of test commands.
250
-
251
- Task: {instruction}
252
-
253
- WORKFLOW:
254
- 1. Call edit_file to write code files. Call sh to run commands.
255
- 2. Run any test scripts mentioned in the task
256
- 3. Check the output carefully - look for "PASS", "SUCCESS", "OK" or similar
257
- 4. If the test failed, analyze why and try a completely different approach
258
- 5. Repeat until the test passes"""
259
-
260
- escaped_instruction = shlex.quote(tool_instruction)
261
- model_name = self.model_name
262
-
263
- if model_name and "/" in model_name:
264
- provider, model = model_name.split("/", 1)
265
- elif model_name:
266
- provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
267
- model = model_name
268
- else:
269
- provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
270
- model = os.environ.get("NPCSH_CHAT_MODEL", "")
271
-
272
- provider_map = {
273
- "anthropic": "anthropic",
274
- "openai": "openai",
275
- "google": "gemini",
276
- "gemini": "gemini",
277
- "deepseek": "deepseek",
278
- "ollama": "ollama",
279
- }
280
- npcsh_provider = provider_map.get(provider.lower(), provider)
281
-
282
- env_vars = []
283
- api_key_map = {
284
- "anthropic": "ANTHROPIC_API_KEY",
285
- "openai": "OPENAI_API_KEY",
286
- "gemini": "GOOGLE_API_KEY",
287
- "deepseek": "DEEPSEEK_API_KEY",
288
- }
289
-
290
- for prov, env_key in api_key_map.items():
291
- if env_key in os.environ:
292
- env_vars.append(f'{env_key}="{os.environ[env_key]}"')
293
-
294
- env_prefix = " ".join(env_vars) + " " if env_vars else ""
295
-
296
- output_dir = str(self.logs_dir / "npcsh_output")
297
- output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
298
-
299
- commands = []
300
-
301
- commands.append(ExecInput(
302
- command=f"mkdir -p {shlex.quote(output_dir)}",
303
- timeout_sec=30
304
- ))
305
-
306
- # Create .npcsh_global file to use global team and avoid interactive prompts
307
- commands.append(ExecInput(
308
- command="touch /app/.npcsh_global",
309
- timeout_sec=10
310
- ))
311
-
312
- # Use specific NPC with --npc flag
313
- # NPCSH_DEFAULT_MODE=agent enables automatic tool execution
314
- ollama_env = ""
315
- if npcsh_provider == "ollama":
316
- ollama_host = os.environ.get("OLLAMA_HOST", "http://host.docker.internal:11434")
317
- ollama_env = f'OLLAMA_HOST="{ollama_host}" '
318
-
319
- npcsh_cmd = (
320
- f'{env_prefix}'
321
- f'{ollama_env}'
322
- f'NPCSH_CHAT_MODEL="{model}" '
323
- f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
324
- f'NPCSH_STREAM_OUTPUT=0 '
325
- f'NPCSH_DEFAULT_MODE=agent '
326
- f'npc --npc {self.npc_name} {escaped_instruction} '
327
- f'2>&1 | tee {shlex.quote(output_file)}'
328
- )
329
-
330
- commands.append(ExecInput(
331
- command=npcsh_cmd,
332
- timeout_sec=600,
333
- ))
334
-
335
- return commands
@@ -12,10 +12,18 @@ if ! command -v pip &> /dev/null; then
12
12
  apt-get update && apt-get install -y python3-pip
13
13
  fi
14
14
 
15
- # Install npcsh with lite dependencies (API providers only, no local models)
16
- # Use --break-system-packages for PEP 668 compliance (Ubuntu 24.04+)
17
- echo "Installing npcsh[lite] + ollama..."
18
- pip install --quiet --break-system-packages npcsh[lite] ollama || pip install --quiet npcsh[lite] ollama
15
+ # Upgrade pip first to handle modern packages and extras
16
+ echo "Upgrading pip..."
17
+ pip install --upgrade pip 2>/dev/null || pip install --break-system-packages --upgrade pip 2>/dev/null || true
18
+
19
+ # Install from local source (uploaded by agent setup)
20
+ echo "Installing npcpy from local source..."
21
+ if [ -d /src/npcpy ]; then
22
+ pip install --break-system-packages -e /src/npcpy 2>/dev/null || pip install -e /src/npcpy
23
+ fi
24
+
25
+ echo "Installing npcsh from local source..."
26
+ pip install --break-system-packages -e /src/npcsh ollama 2>/dev/null || pip install -e /src/npcsh ollama
19
27
 
20
28
  # Verify installation
21
29
  echo "Verifying npcsh installation..."
npcsh/config.py CHANGED
@@ -28,9 +28,9 @@ NPCSH_DEFAULT_MODE = os.environ.get("NPCSH_DEFAULT_MODE", "agent")
28
28
  NPCSH_VISION_MODEL = os.environ.get("NPCSH_VISION_MODEL", "gemma3:4b")
29
29
  NPCSH_VISION_PROVIDER = os.environ.get("NPCSH_VISION_PROVIDER", "ollama")
30
30
  NPCSH_IMAGE_GEN_MODEL = os.environ.get(
31
- "NPCSH_IMAGE_GEN_MODEL", "runwayml/stable-diffusion-v1-5"
31
+ "NPCSH_IMAGE_GEN_MODEL", "x/z-image-turbo"
32
32
  )
33
- NPCSH_IMAGE_GEN_PROVIDER = os.environ.get("NPCSH_IMAGE_GEN_PROVIDER", "diffusers")
33
+ NPCSH_IMAGE_GEN_PROVIDER = os.environ.get("NPCSH_IMAGE_GEN_PROVIDER", "ollama")
34
34
  NPCSH_VIDEO_GEN_MODEL = os.environ.get(
35
35
  "NPCSH_VIDEO_GEN_MODEL", "damo-vilab/text-to-video-ms-1.7b"
36
36
  )
@@ -44,6 +44,9 @@ NPCSH_API_URL = os.environ.get("NPCSH_API_URL", None)
44
44
  NPCSH_SEARCH_PROVIDER = os.environ.get("NPCSH_SEARCH_PROVIDER", "duckduckgo")
45
45
  NPCSH_BUILD_KG = os.environ.get("NPCSH_BUILD_KG", "1") != "0"
46
46
  NPCSH_EDIT_APPROVAL = os.environ.get("NPCSH_EDIT_APPROVAL", "off") # off, interactive, auto
47
+ NPCSH_TTS_ENGINE = os.environ.get("NPCSH_TTS_ENGINE", "")
48
+ NPCSH_TTS_VOICE = os.environ.get("NPCSH_TTS_VOICE", "")
49
+ NPCSH_YAP_SETUP_DONE = os.environ.get("NPCSH_YAP_SETUP_DONE", "0") == "1"
47
50
 
48
51
 
49
52
  def get_shell_config_file() -> str:
@@ -11,14 +11,10 @@ colors:
11
11
  top: "255,215,0"
12
12
  bottom: "218,165,32"
13
13
  primary_directive: |
14
- You are alicanto, a research agent. You investigate hypotheses through experimentation and evidence gathering.
15
- Search academic papers to ground your work in existing literature.
16
- Search the web for data, documentation, and recent findings.
17
- Write and execute Python code to analyze data, compute statistics, and generate results.
18
- Use shell commands for data processing and system tasks.
19
- Create files to record your findings, analyses, and evidence.
20
- When exploring a hypothesis, gather evidence from multiple sources, analyze it quantitatively where possible, and document what you find.
21
- Say RESEARCH_COMPLETE when you have sufficient evidence to evaluate your hypothesis.
14
+ You are alicanto, the deep research specialist. You produce thorough research reports.
15
+ Search multiple sources, read papers, gather evidence, then synthesize into a report.
16
+ Use web_search and file_search to find sources. Use python to analyze data.
17
+ Say RESEARCH_COMPLETE when you have enough evidence to answer.
22
18
  jinxs:
23
19
  - lib/core/search/web_search
24
20
  - lib/core/search/file_search
npcsh/npc_team/corca.npc CHANGED
@@ -11,17 +11,11 @@ colors:
11
11
  top: "64,224,208"
12
12
  bottom: "255,165,0"
13
13
  primary_directive: |
14
- You are corca, the software development specialist of the NPC team.
15
- Your expertise is in writing, reviewing, and debugging code.
16
- You think through problems carefully and favor solutions that prioritize simplicity and clarity.
17
- Always consider how suggestions may increase rather than reduce tech debt unnecessarily.
18
- When in doubt, ask for clarification with concrete options that make it easy for users to choose.
19
-
20
- CRITICAL: You MUST ALWAYS use FULL ABSOLUTE PATHS for all file operations.
21
- - NEVER use relative paths like "apps/api" or "./src"
22
- - ALWAYS expand paths starting from root like "/Users/username/project/apps/api"
23
- - When given a task, first determine the absolute path of the working directory using pwd
24
- - Prefix all file paths with the full absolute path
14
+ You are corca, the software development and shell specialist.
15
+ You run commands, write code, debug, edit files, and handle system tasks.
16
+ Always use FULL ABSOLUTE PATHS. Run pwd first if you need the working directory.
17
+ When counting things, use wc -l. When debugging, read the error, fix it, retry.
18
+ Keep it simple. Do the task, report the result.
25
19
  jinxs:
26
20
  - lib/core/sh
27
21
  - lib/core/python
@@ -11,12 +11,10 @@ colors:
11
11
  top: "224,255,255"
12
12
  bottom: "173,216,230"
13
13
  primary_directive: |
14
- You are frederic the polar bear - a fusion of Richard Feynman and Frederic Chopin.
15
- You have Feynman's playful curiosity, his ability to explain complex physics simply,
16
- and his irreverent wit. You also have Chopin's romantic soul, his passion for music,
17
- and his ability to find beauty in mathematical structures.
18
- You help users with hard math, physics problems, and music composition.
19
- Cut through the ice to get to what matters. Make the complex feel simple and beautiful.
14
+ You are frederic the polar bear Feynman's curiosity meets Chopin's soul.
15
+ Make the complex simple and beautiful. Find elegance in structure.
16
+ Use analogies, strip away jargon, show why things are beautiful not just correct.
17
+ Use python to demonstrate ideas visually when math is involved.
20
18
  jinxs:
21
19
  - lib/core/python
22
20
  - lib/core/sql
npcsh/npc_team/guac.npc CHANGED
@@ -11,10 +11,10 @@ ascii_art: |
11
11
  🟢 🟢 🟢 🟢 ⚫🥑🍅⚫ 🟢
12
12
  🟢🟢🟢🟢🟢🟢 🟢🟢🟢🟢 ⚫⚫🟢 🟢🟢🟢
13
13
  primary_directive: |
14
- You are guac, the data analysis specialist of the NPC team.
15
- Your expertise is in loading, analyzing, and visualizing data.
16
- You work with pandas DataFrames, numpy arrays, and matplotlib plots.
17
- Help users load data files, run Python code for analysis, and create visualizations.
14
+ You are guac, the data analysis specialist.
15
+ You load, analyze, and visualize data with pandas, numpy, matplotlib, and SQL.
16
+ Load data first, inspect it, do the analysis, report results with numbers.
17
+ For plots, save to file with plt.savefig() and report the path.
18
18
  jinxs:
19
19
  - lib/core/python
20
20
  - lib/core/sql
@@ -1,5 +1,5 @@
1
1
  jinx_name: delegate
2
- description: Delegate a task to another NPC with review and feedback loop until completion. Choose the NPC whose directive best matches the task.
2
+ description: ONLY for complex multi-step tasks. Sends a task to a specialist NPC who works on it with feedback until done. Do NOT use this for simple commands use sh or python instead and answer directly.
3
3
  inputs:
4
4
  - npc_name:
5
5
  description: "Name of the NPC to delegate to"
@@ -4,7 +4,7 @@ description: Creates or edits a file. If the file does not exist, creates it wit
4
4
  inputs:
5
5
  - file_path
6
6
  - edit_instructions
7
- - backup: true
7
+ - backup: false
8
8
  steps:
9
9
  - name: "edit_file"
10
10
  engine: "python"
@@ -1,5 +1,5 @@
1
1
  jinx_name: sh
2
- description: Execute bash queries. Should be used to grep for file contents, list directories, explore information to answer user questions more practically. NEVER use ls -R on directories that may contain node_modules, .git, or other large dependency folders - this will exceed token limits. Use targeted ls commands instead.
2
+ description: Run a shell command and get the output. Use for counting files (ls /etc | wc -l), checking paths, listing directories. After you get the result, stop and answer the user. Do not call any other tools after this.
3
3
  inputs:
4
4
  - bash_command
5
5
  steps:
@@ -0,0 +1,59 @@
1
+ jinx_name: skill
2
+ description: Constructs and serves a structured skill. Receives the full skill definition and returns the requested part. Do not call directly.
3
+ inputs:
4
+ - skill_name
5
+ - skill_description
6
+ - sections
7
+ - scripts_json
8
+ - references_json
9
+ - assets_json
10
+ - section
11
+ steps:
12
+ - name: handle_skill
13
+ engine: python
14
+ code: |
15
+ import json as _json, base64 as _b64
16
+
17
+ _name = {{ skill_name | tojson }}
18
+ _desc = {{ skill_description | tojson }}
19
+ _sections_raw = {{ sections | tojson }}
20
+ if isinstance(_sections_raw, dict):
21
+ _sections = _sections_raw
22
+ elif isinstance(_sections_raw, str):
23
+ try:
24
+ _sections = _json.loads(_b64.b64decode(_sections_raw).decode('utf-8'))
25
+ except Exception:
26
+ _sections = _json.loads(_sections_raw)
27
+ else:
28
+ _sections = {}
29
+ _scripts = _json.loads({{ scripts_json | tojson }}) if {{ scripts_json | tojson }} else []
30
+ _references = _json.loads({{ references_json | tojson }}) if {{ references_json | tojson }} else []
31
+ _assets = _json.loads({{ assets_json | tojson }}) if {{ assets_json | tojson }} else []
32
+ _section = {{ section | tojson }} or 'all'
33
+ _section = _section.strip()
34
+
35
+ if _section == 'meta':
36
+ output = _json.dumps({
37
+ 'name': _name,
38
+ 'description': _desc,
39
+ 'sections': list(_sections.keys()),
40
+ 'scripts': _scripts,
41
+ 'references': _references,
42
+ 'assets': _assets,
43
+ }, indent=2)
44
+ elif _section == 'list':
45
+ output = 'Available sections: ' + ', '.join(_sections.keys())
46
+ elif _section == 'all':
47
+ _parts = []
48
+ for _k, _v in _sections.items():
49
+ _parts.append(f'## {_k}\n{_v.strip()}')
50
+ output = '\n\n'.join(_parts)
51
+ else:
52
+ if _section in _sections:
53
+ output = _sections[_section].strip()
54
+ else:
55
+ _matches = [_k for _k in _sections if _section.lower() in _k.lower()]
56
+ if _matches:
57
+ output = _sections[_matches[0]].strip()
58
+ else:
59
+ output = f"Section '{_section}' not found. Available sections: " + ', '.join(_sections.keys())