npcsh 1.1.21__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 (188) hide show
  1. npcsh/_state.py +282 -125
  2. npcsh/benchmark/npcsh_agent.py +77 -232
  3. npcsh/benchmark/templates/install-npcsh.sh.j2 +12 -4
  4. npcsh/config.py +5 -2
  5. npcsh/mcp_server.py +9 -1
  6. npcsh/npc_team/alicanto.npc +8 -6
  7. npcsh/npc_team/corca.npc +5 -12
  8. npcsh/npc_team/frederic.npc +6 -9
  9. npcsh/npc_team/guac.npc +4 -4
  10. npcsh/npc_team/jinxs/lib/core/delegate.jinx +1 -1
  11. npcsh/npc_team/jinxs/lib/core/edit_file.jinx +84 -62
  12. npcsh/npc_team/jinxs/lib/core/sh.jinx +1 -1
  13. npcsh/npc_team/jinxs/lib/core/skill.jinx +59 -0
  14. npcsh/npc_team/jinxs/lib/utils/help.jinx +194 -10
  15. npcsh/npc_team/jinxs/lib/utils/init.jinx +528 -37
  16. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +0 -1
  17. npcsh/npc_team/jinxs/lib/utils/serve.jinx +938 -21
  18. npcsh/npc_team/jinxs/modes/alicanto.jinx +102 -41
  19. npcsh/npc_team/jinxs/modes/build.jinx +378 -0
  20. npcsh-1.1.21.data/data/npcsh/npc_team/config_tui.jinx → npcsh/npc_team/jinxs/modes/config.jinx +1 -1
  21. npcsh/npc_team/jinxs/modes/convene.jinx +670 -0
  22. npcsh/npc_team/jinxs/modes/corca.jinx +777 -387
  23. npcsh/npc_team/jinxs/modes/crond.jinx +818 -0
  24. npcsh/npc_team/jinxs/modes/kg.jinx +69 -2
  25. npcsh/npc_team/jinxs/modes/plonk.jinx +86 -15
  26. npcsh/npc_team/jinxs/modes/roll.jinx +368 -55
  27. npcsh/npc_team/jinxs/modes/skills.jinx +621 -0
  28. npcsh/npc_team/jinxs/modes/yap.jinx +1092 -177
  29. npcsh/npc_team/jinxs/skills/code-review/SKILL.md +45 -0
  30. npcsh/npc_team/jinxs/skills/debugging/SKILL.md +44 -0
  31. npcsh/npc_team/jinxs/skills/git-workflow.jinx +44 -0
  32. npcsh/npc_team/kadiefa.npc +6 -6
  33. npcsh/npc_team/npcsh.ctx +16 -0
  34. npcsh/npc_team/plonk.npc +5 -9
  35. npcsh/npc_team/sibiji.npc +15 -7
  36. npcsh/npcsh.py +1 -0
  37. npcsh/routes.py +0 -4
  38. npcsh/yap.py +22 -4
  39. npcsh-1.1.23.data/data/npcsh/npc_team/SKILL.md +44 -0
  40. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/alicanto.jinx +102 -41
  41. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/alicanto.npc +8 -6
  42. npcsh-1.1.23.data/data/npcsh/npc_team/build.jinx +378 -0
  43. npcsh/npc_team/jinxs/modes/config_tui.jinx → npcsh-1.1.23.data/data/npcsh/npc_team/config.jinx +1 -1
  44. npcsh-1.1.23.data/data/npcsh/npc_team/convene.jinx +670 -0
  45. npcsh-1.1.23.data/data/npcsh/npc_team/corca.jinx +820 -0
  46. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca.npc +5 -12
  47. npcsh-1.1.23.data/data/npcsh/npc_team/crond.jinx +818 -0
  48. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/delegate.jinx +1 -1
  49. npcsh-1.1.23.data/data/npcsh/npc_team/edit_file.jinx +119 -0
  50. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/frederic.npc +6 -9
  51. npcsh-1.1.23.data/data/npcsh/npc_team/git-workflow.jinx +44 -0
  52. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/guac.npc +4 -4
  53. npcsh-1.1.23.data/data/npcsh/npc_team/help.jinx +236 -0
  54. npcsh-1.1.23.data/data/npcsh/npc_team/init.jinx +532 -0
  55. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/jinxs.jinx +0 -1
  56. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/kadiefa.npc +6 -6
  57. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/kg.jinx +69 -2
  58. npcsh-1.1.23.data/data/npcsh/npc_team/npcsh.ctx +34 -0
  59. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonk.jinx +86 -15
  60. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonk.npc +5 -9
  61. npcsh-1.1.23.data/data/npcsh/npc_team/roll.jinx +378 -0
  62. npcsh-1.1.23.data/data/npcsh/npc_team/serve.jinx +943 -0
  63. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sh.jinx +1 -1
  64. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sibiji.npc +15 -7
  65. npcsh-1.1.23.data/data/npcsh/npc_team/skill.jinx +59 -0
  66. npcsh-1.1.23.data/data/npcsh/npc_team/skills.jinx +621 -0
  67. npcsh-1.1.23.data/data/npcsh/npc_team/yap.jinx +1190 -0
  68. {npcsh-1.1.21.dist-info → npcsh-1.1.23.dist-info}/METADATA +404 -278
  69. npcsh-1.1.23.dist-info/RECORD +216 -0
  70. npcsh/npc_team/jinxs/incognide/add_tab.jinx +0 -11
  71. npcsh/npc_team/jinxs/incognide/close_pane.jinx +0 -9
  72. npcsh/npc_team/jinxs/incognide/close_tab.jinx +0 -10
  73. npcsh/npc_team/jinxs/incognide/confirm.jinx +0 -10
  74. npcsh/npc_team/jinxs/incognide/focus_pane.jinx +0 -9
  75. npcsh/npc_team/jinxs/incognide/list_panes.jinx +0 -8
  76. npcsh/npc_team/jinxs/incognide/navigate.jinx +0 -10
  77. npcsh/npc_team/jinxs/incognide/notify.jinx +0 -10
  78. npcsh/npc_team/jinxs/incognide/open_pane.jinx +0 -13
  79. npcsh/npc_team/jinxs/incognide/read_pane.jinx +0 -9
  80. npcsh/npc_team/jinxs/incognide/run_terminal.jinx +0 -10
  81. npcsh/npc_team/jinxs/incognide/send_message.jinx +0 -10
  82. npcsh/npc_team/jinxs/incognide/split_pane.jinx +0 -12
  83. npcsh/npc_team/jinxs/incognide/switch_npc.jinx +0 -10
  84. npcsh/npc_team/jinxs/incognide/switch_tab.jinx +0 -10
  85. npcsh/npc_team/jinxs/incognide/write_file.jinx +0 -11
  86. npcsh/npc_team/jinxs/incognide/zen_mode.jinx +0 -9
  87. npcsh/npc_team/jinxs/lib/core/convene.jinx +0 -232
  88. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +0 -429
  89. npcsh/npc_team/jinxs/lib/core/search.jinx +0 -54
  90. npcsh/npc_team/jinxs/lib/utils/build.jinx +0 -65
  91. npcsh-1.1.21.data/data/npcsh/npc_team/add_tab.jinx +0 -11
  92. npcsh-1.1.21.data/data/npcsh/npc_team/build.jinx +0 -65
  93. npcsh-1.1.21.data/data/npcsh/npc_team/close_pane.jinx +0 -9
  94. npcsh-1.1.21.data/data/npcsh/npc_team/close_tab.jinx +0 -10
  95. npcsh-1.1.21.data/data/npcsh/npc_team/confirm.jinx +0 -10
  96. npcsh-1.1.21.data/data/npcsh/npc_team/convene.jinx +0 -232
  97. npcsh-1.1.21.data/data/npcsh/npc_team/corca.jinx +0 -430
  98. npcsh-1.1.21.data/data/npcsh/npc_team/edit_file.jinx +0 -97
  99. npcsh-1.1.21.data/data/npcsh/npc_team/focus_pane.jinx +0 -9
  100. npcsh-1.1.21.data/data/npcsh/npc_team/help.jinx +0 -52
  101. npcsh-1.1.21.data/data/npcsh/npc_team/init.jinx +0 -41
  102. npcsh-1.1.21.data/data/npcsh/npc_team/kg_search.jinx +0 -429
  103. npcsh-1.1.21.data/data/npcsh/npc_team/list_panes.jinx +0 -8
  104. npcsh-1.1.21.data/data/npcsh/npc_team/navigate.jinx +0 -10
  105. npcsh-1.1.21.data/data/npcsh/npc_team/notify.jinx +0 -10
  106. npcsh-1.1.21.data/data/npcsh/npc_team/npcsh.ctx +0 -18
  107. npcsh-1.1.21.data/data/npcsh/npc_team/open_pane.jinx +0 -13
  108. npcsh-1.1.21.data/data/npcsh/npc_team/read_pane.jinx +0 -9
  109. npcsh-1.1.21.data/data/npcsh/npc_team/roll.jinx +0 -65
  110. npcsh-1.1.21.data/data/npcsh/npc_team/run_terminal.jinx +0 -10
  111. npcsh-1.1.21.data/data/npcsh/npc_team/search.jinx +0 -54
  112. npcsh-1.1.21.data/data/npcsh/npc_team/send_message.jinx +0 -10
  113. npcsh-1.1.21.data/data/npcsh/npc_team/serve.jinx +0 -26
  114. npcsh-1.1.21.data/data/npcsh/npc_team/split_pane.jinx +0 -12
  115. npcsh-1.1.21.data/data/npcsh/npc_team/switch_npc.jinx +0 -10
  116. npcsh-1.1.21.data/data/npcsh/npc_team/switch_tab.jinx +0 -10
  117. npcsh-1.1.21.data/data/npcsh/npc_team/write_file.jinx +0 -11
  118. npcsh-1.1.21.data/data/npcsh/npc_team/yap.jinx +0 -275
  119. npcsh-1.1.21.data/data/npcsh/npc_team/zen_mode.jinx +0 -9
  120. npcsh-1.1.21.dist-info/RECORD +0 -243
  121. /npcsh/npc_team/jinxs/lib/{core → utils}/chat.jinx +0 -0
  122. /npcsh/npc_team/jinxs/lib/{core → utils}/cmd.jinx +0 -0
  123. /npcsh/npc_team/jinxs/{incognide → lib/utils}/incognide.jinx +0 -0
  124. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/alicanto.png +0 -0
  125. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/arxiv.jinx +0 -0
  126. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/benchmark.jinx +0 -0
  127. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  128. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  129. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/chat.jinx +0 -0
  130. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/click.jinx +0 -0
  131. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  132. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  133. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/compile.jinx +0 -0
  134. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/compress.jinx +0 -0
  135. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca.png +0 -0
  136. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca_example.png +0 -0
  137. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/db_search.jinx +0 -0
  138. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/file_search.jinx +0 -0
  139. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/frederic4.png +0 -0
  140. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/git.jinx +0 -0
  141. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/guac.jinx +0 -0
  142. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/guac.png +0 -0
  143. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  144. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  145. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  146. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  147. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  148. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/memories.jinx +0 -0
  149. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/models.jinx +0 -0
  150. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  151. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/nql.jinx +0 -0
  152. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  153. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/ots.jinx +0 -0
  154. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/papers.jinx +0 -0
  155. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/paste.jinx +0 -0
  156. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonk.png +0 -0
  157. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  158. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/pti.jinx +0 -0
  159. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/python.jinx +0 -0
  160. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/reattach.jinx +0 -0
  161. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sample.jinx +0 -0
  162. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  163. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/set.jinx +0 -0
  164. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/setup.jinx +0 -0
  165. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/shh.jinx +0 -0
  166. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sibiji.png +0 -0
  167. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  168. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/spool.jinx +0 -0
  169. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/spool.png +0 -0
  170. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sql.jinx +0 -0
  171. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/switch.jinx +0 -0
  172. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/switches.jinx +0 -0
  173. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sync.jinx +0 -0
  174. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/team.jinx +0 -0
  175. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  176. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  177. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  178. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/usage.jinx +0 -0
  179. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  180. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  181. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/wait.jinx +0 -0
  182. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/wander.jinx +0 -0
  183. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/web_search.jinx +0 -0
  184. {npcsh-1.1.21.data → npcsh-1.1.23.data}/data/npcsh/npc_team/yap.png +0 -0
  185. {npcsh-1.1.21.dist-info → npcsh-1.1.23.dist-info}/WHEEL +0 -0
  186. {npcsh-1.1.21.dist-info → npcsh-1.1.23.dist-info}/entry_points.txt +0 -0
  187. {npcsh-1.1.21.dist-info → npcsh-1.1.23.dist-info}/licenses/LICENSE +0 -0
  188. {npcsh-1.1.21.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,134 +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 explicit jinx usage directions and retry logic
57
- tool_instruction = f"""You have access to jinxs including 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 use these jinxs to complete the task. Do NOT just output code as text - use the edit_file jinx to actually write files to disk.
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.
65
-
66
- Task: {instruction}
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
+ )
67
77
 
68
- WORKFLOW:
69
- 1. Implement your solution using edit_file and sh
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
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
+ )
74
84
 
75
- Remember: Use edit_file to write code files. Use sh to run commands. VERIFY your solution works before concluding."""
85
+ await super().setup(environment)
76
86
 
77
- escaped_instruction = shlex.quote(tool_instruction)
78
- 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)
79
90
 
80
- 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:
81
105
  provider, model = model_name.split("/", 1)
82
- elif model_name:
83
- provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
84
- model = model_name
85
106
  else:
86
107
  provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
87
- model = os.environ.get("NPCSH_CHAT_MODEL", "")
88
-
89
- # Map provider names to npcsh provider format
90
- provider_map = {
91
- "anthropic": "anthropic",
92
- "openai": "openai",
93
- "google": "gemini",
94
- "gemini": "gemini",
95
- "deepseek": "deepseek",
96
- "ollama": "ollama",
97
- "groq": "groq",
98
- "openrouter": "openrouter",
99
- }
100
- npcsh_provider = provider_map.get(provider.lower(), provider)
101
-
102
- # Build environment variables for API keys
103
- env_vars = []
104
- api_key_map = {
105
- "anthropic": ["ANTHROPIC_API_KEY"],
106
- "openai": ["OPENAI_API_KEY"],
107
- "gemini": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
108
- "google": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
109
- "deepseek": ["DEEPSEEK_API_KEY"],
110
- "groq": ["GROQ_API_KEY"],
111
- "openrouter": ["OPENROUTER_API_KEY"],
112
- }
113
-
114
- added_keys = set()
115
- for prov, env_keys in api_key_map.items():
116
- for env_key in env_keys:
117
- if env_key in os.environ:
118
- # For Gemini, always pass as GOOGLE_API_KEY (what litellm expects)
119
- target_key = "GOOGLE_API_KEY" if env_key == "GEMINI_API_KEY" else env_key
120
- if target_key not in added_keys:
121
- env_vars.append(f'{target_key}="{os.environ[env_key]}"')
122
- added_keys.add(target_key)
123
- break
124
-
125
- env_prefix = " ".join(env_vars) + " " if env_vars else ""
126
-
127
- # Output directory for logs
128
- output_dir = str(self.logs_dir / "npcsh_output")
129
- output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
108
+ model = model_name or os.environ.get("NPCSH_CHAT_MODEL", "")
130
109
 
131
- commands = []
132
-
133
- # Create output directory
134
- commands.append(ExecInput(
135
- command=f"mkdir -p {shlex.quote(output_dir)}",
136
- timeout_sec=30
137
- ))
138
-
139
- # Create .npcsh_global file to use global team and avoid interactive prompts
140
- commands.append(ExecInput(
141
- command="touch /app/.npcsh_global",
142
- timeout_sec=10
143
- ))
144
-
145
- # Run npcsh with the instruction
146
- # Using corca NPC which has edit_file tool for writing files
147
- # Using the npc CLI which supports single-command execution
148
- # NPCSH_DEFAULT_MODE=agent enables automatic tool execution
149
- npcsh_cmd = (
150
- f'{env_prefix}'
151
- f'NPCSH_CHAT_MODEL="{model}" '
152
- f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
153
- f'NPCSH_STREAM_OUTPUT=0 '
154
- f'NPCSH_DEFAULT_MODE=agent '
155
- f'npc --npc corca {escaped_instruction} '
156
- f'2>&1 | tee {shlex.quote(output_file)}'
157
- )
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')
158
114
 
159
- commands.append(ExecInput(
160
- command=npcsh_cmd,
161
- timeout_sec=600, # 10 minute timeout for complex tasks
162
- ))
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')
163
120
 
164
- return commands
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")
124
+
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
+ ]
165
133
 
166
134
  def populate_context_post_run(self, context: AgentContext) -> None:
167
135
  """
168
136
  Populate the context with results of the agent execution.
169
137
 
170
138
  Parses the output file to extract token usage metrics.
171
-
172
- Args:
173
- context: The AgentContext to populate with metrics
174
139
  """
175
140
  output_file = self.logs_dir / "npcsh_output" / "output.jsonl"
176
141
 
@@ -183,145 +148,25 @@ Remember: Use edit_file to write code files. Use sh to run commands. VERIFY your
183
148
  with open(output_file, 'r') as f:
184
149
  content = f.read()
185
150
 
186
- # Try to parse as JSONL first
187
151
  for line in content.strip().split('\n'):
188
152
  if not line.strip():
189
153
  continue
190
154
  try:
191
155
  event = json.loads(line)
192
- # Extract token usage from events if present
193
156
  if isinstance(event, dict):
194
157
  usage = event.get('usage', {})
195
158
  total_input_tokens += usage.get('input_tokens', 0)
196
159
  total_output_tokens += usage.get('output_tokens', 0)
197
160
  total_cost_usd += usage.get('cost_usd', 0.0)
198
161
  except json.JSONDecodeError:
199
- # Not JSON, just regular output
200
162
  pass
201
163
 
202
164
  except Exception as e:
203
165
  self.logger.warning(f"Failed to parse npcsh output: {e}")
204
166
 
205
- # Set context metrics
206
167
  if hasattr(context, 'input_tokens'):
207
168
  context.input_tokens = total_input_tokens
208
169
  if hasattr(context, 'output_tokens'):
209
170
  context.output_tokens = total_output_tokens
210
171
  if hasattr(context, 'cost_usd'):
211
172
  context.cost_usd = total_cost_usd
212
-
213
-
214
- class NpcshAgentWithNpc(NpcshAgent):
215
- """
216
- Variant that uses a specific NPC for task execution.
217
-
218
- This allows benchmarking specific NPCs like sibiji (orchestrator),
219
- corca (coding), or custom NPCs.
220
-
221
- Usage:
222
- harbor run -d terminal-bench@2.0 \\
223
- --agent-import-path "npcsh.benchmark:NpcshAgentWithNpc" \\
224
- -m anthropic/claude-sonnet-4-20250514 -n 4
225
- """
226
-
227
- def __init__(self, *args, npc_name: str = "sibiji", **kwargs):
228
- super().__init__(*args, **kwargs)
229
- self.npc_name = npc_name
230
-
231
- @staticmethod
232
- def name() -> str:
233
- return "npcsh-npc"
234
-
235
- def create_run_agent_commands(self, instruction: str) -> list:
236
- """Create commands using a specific NPC."""
237
- # Wrap the instruction with explicit jinx usage directions and retry logic
238
- tool_instruction = f"""You have access to jinxs including edit_file (for writing/creating files), sh (for running shell commands), and python (for running Python code).
239
-
240
- IMPORTANT RULES:
241
- 1. You MUST use these jinxs to complete the task. Do NOT just output code as text - use the edit_file jinx to actually write files to disk.
242
- 2. After implementing a solution, you MUST verify it works by running any provided test scripts.
243
- 3. If a test fails or produces an error, you MUST try a DIFFERENT approach. Do not give up.
244
- 4. Keep trying different approaches until you succeed or have tried at least 10 different solutions.
245
- 5. NEVER assume success - always check the actual output of test commands.
246
-
247
- Task: {instruction}
248
-
249
- WORKFLOW:
250
- 1. Implement your solution using edit_file and sh
251
- 2. Run any test scripts mentioned in the task
252
- 3. Check the output carefully - look for "PASS", "SUCCESS", "OK" or similar
253
- 4. If the test failed, analyze why and try a completely different approach
254
- 5. Repeat until the test passes
255
-
256
- Remember: Use edit_file to write code files. Use sh to run commands. VERIFY your solution works before concluding."""
257
-
258
- escaped_instruction = shlex.quote(tool_instruction)
259
- model_name = self.model_name
260
-
261
- if model_name and "/" in model_name:
262
- provider, model = model_name.split("/", 1)
263
- elif model_name:
264
- provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
265
- model = model_name
266
- else:
267
- provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
268
- model = os.environ.get("NPCSH_CHAT_MODEL", "")
269
-
270
- provider_map = {
271
- "anthropic": "anthropic",
272
- "openai": "openai",
273
- "google": "gemini",
274
- "gemini": "gemini",
275
- "deepseek": "deepseek",
276
- "ollama": "ollama",
277
- }
278
- npcsh_provider = provider_map.get(provider.lower(), provider)
279
-
280
- env_vars = []
281
- api_key_map = {
282
- "anthropic": "ANTHROPIC_API_KEY",
283
- "openai": "OPENAI_API_KEY",
284
- "gemini": "GOOGLE_API_KEY",
285
- "deepseek": "DEEPSEEK_API_KEY",
286
- }
287
-
288
- for prov, env_key in api_key_map.items():
289
- if env_key in os.environ:
290
- env_vars.append(f'{env_key}="{os.environ[env_key]}"')
291
-
292
- env_prefix = " ".join(env_vars) + " " if env_vars else ""
293
-
294
- output_dir = str(self.logs_dir / "npcsh_output")
295
- output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
296
-
297
- commands = []
298
-
299
- commands.append(ExecInput(
300
- command=f"mkdir -p {shlex.quote(output_dir)}",
301
- timeout_sec=30
302
- ))
303
-
304
- # Create .npcsh_global file to use global team and avoid interactive prompts
305
- commands.append(ExecInput(
306
- command="touch /app/.npcsh_global",
307
- timeout_sec=10
308
- ))
309
-
310
- # Use specific NPC with --npc flag
311
- # NPCSH_DEFAULT_MODE=agent enables automatic tool execution
312
- npcsh_cmd = (
313
- f'{env_prefix}'
314
- f'NPCSH_CHAT_MODEL="{model}" '
315
- f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
316
- f'NPCSH_STREAM_OUTPUT=0 '
317
- f'NPCSH_DEFAULT_MODE=agent '
318
- f'npc --npc {self.npc_name} {escaped_instruction} '
319
- f'2>&1 | tee {shlex.quote(output_file)}'
320
- )
321
-
322
- commands.append(ExecInput(
323
- command=npcsh_cmd,
324
- timeout_sec=600,
325
- ))
326
-
327
- 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]..."
18
- pip install --quiet --break-system-packages npcsh[lite] || pip install --quiet npcsh[lite]
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:
npcsh/mcp_server.py CHANGED
@@ -1,9 +1,17 @@
1
1
 
2
2
  """
3
- Enhanced MCP server that incorporates functionality from npcpy.routes,
3
+ Enhanced MCP server that incorporates functionality from npcpy.routes,
4
4
  npcpy.llm_funcs, and npcpy.npc_compiler as tools.
5
5
  """
6
6
 
7
+ # When run as a subprocess, Python adds the script directory to sys.path[0].
8
+ # Since this file lives inside the npcsh package, that shadows the package
9
+ # (npcsh.py is found instead of the npcsh/ package). Remove it.
10
+ import sys as _sys, os as _os
11
+ _script_dir = _os.path.dirname(_os.path.abspath(__file__))
12
+ if _script_dir in _sys.path:
13
+ _sys.path.remove(_script_dir)
14
+
7
15
  import os
8
16
  import subprocess
9
17
  import json
@@ -11,13 +11,15 @@ colors:
11
11
  top: "255,215,0"
12
12
  bottom: "218,165,32"
13
13
  primary_directive: |
14
- You are alicanto, the research and exploration specialist of the NPC team.
15
- Like the mythical bird, you lead users to discover valuable information.
16
- Your role is web research, searching, and helping users explore topics.
17
- Use search tools to find information and present findings clearly.
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.
18
18
  jinxs:
19
- - lib/core/search
19
+ - lib/core/search/web_search
20
+ - lib/core/search/file_search
21
+ - lib/core/search/db_search
20
22
  - lib/core/sh
21
23
  - lib/core/python
22
24
  - lib/core/load_file
23
- - lib/research/*
25
+ - lib/core/edit_file
npcsh/npc_team/corca.npc CHANGED
@@ -11,21 +11,14 @@ 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
28
22
  - lib/core/edit_file
29
23
  - lib/core/load_file
30
- - lib/core/search
31
24
 
@@ -11,17 +11,14 @@ 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
23
21
  - lib/core/sh
24
22
  - lib/core/load_file
25
- - lib/core/search
26
- - lib/gen/*
27
- - bin/wander
23
+ - lib/core/search/web_search
24
+ - lib/core/edit_file
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"