npcsh 1.1.16__tar.gz → 1.1.17__tar.gz

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 (135) hide show
  1. {npcsh-1.1.16/npcsh.egg-info → npcsh-1.1.17}/PKG-INFO +10 -7
  2. {npcsh-1.1.16 → npcsh-1.1.17}/README.md +6 -6
  3. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/_state.py +24 -9
  4. npcsh-1.1.17/npcsh/benchmark/__init__.py +22 -0
  5. npcsh-1.1.17/npcsh/benchmark/npcsh_agent.py +262 -0
  6. npcsh-1.1.17/npcsh/benchmark/runner.py +569 -0
  7. npcsh-1.1.17/npcsh/npc_team/jinxs/bin/benchmark.jinx +146 -0
  8. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/nql.jinx +7 -7
  9. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/roll.jinx +20 -23
  10. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/sample.jinx +6 -7
  11. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/spool.jinx +4 -4
  12. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/sync.jinx +6 -6
  13. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/vixynt.jinx +8 -8
  14. npcsh-1.1.17/npcsh/npc_team/jinxs/bin/wander.jinx +242 -0
  15. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/yap.jinx +5 -5
  16. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/add_tab.jinx +11 -0
  17. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/close_pane.jinx +9 -0
  18. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/close_tab.jinx +10 -0
  19. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/confirm.jinx +10 -0
  20. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/focus_pane.jinx +9 -0
  21. npcsh-1.1.16/npcsh/npc_team/jinxs/npc_studio/npc-studio.jinx → npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/incognide.jinx +2 -2
  22. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/list_panes.jinx +8 -0
  23. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/navigate.jinx +10 -0
  24. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/notify.jinx +10 -0
  25. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/open_pane.jinx +13 -0
  26. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/read_pane.jinx +9 -0
  27. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/run_terminal.jinx +10 -0
  28. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/send_message.jinx +10 -0
  29. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/split_pane.jinx +12 -0
  30. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/switch_npc.jinx +10 -0
  31. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/switch_tab.jinx +10 -0
  32. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/write_file.jinx +11 -0
  33. npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/zen_mode.jinx +9 -0
  34. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +4 -4
  35. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +1 -1
  36. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +2 -2
  37. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/click.jinx +2 -2
  38. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +1 -1
  39. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +1 -1
  40. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +1 -1
  41. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/trigger.jinx +2 -2
  42. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +1 -1
  43. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +1 -1
  44. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/chat.jinx +4 -4
  45. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/cmd.jinx +4 -4
  46. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/compress.jinx +8 -8
  47. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/edit_file.jinx +3 -0
  48. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/ots.jinx +7 -7
  49. npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +44 -0
  50. npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +94 -0
  51. npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +96 -0
  52. npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +80 -0
  53. npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +51 -0
  54. npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search.jinx +54 -0
  55. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/sh.jinx +1 -1
  56. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/sleep.jinx +7 -7
  57. npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/sql.jinx +16 -0
  58. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +7 -7
  59. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +8 -9
  60. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/research/arxiv.jinx +2 -2
  61. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/research/paper_search.jinx +3 -3
  62. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +2 -2
  63. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/build.jinx +5 -5
  64. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/compile.jinx +2 -2
  65. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/help.jinx +1 -1
  66. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/init.jinx +5 -5
  67. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +1 -1
  68. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/serve.jinx +2 -2
  69. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/set.jinx +2 -2
  70. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/switch.jinx +3 -3
  71. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/switches.jinx +1 -1
  72. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +2 -2
  73. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/sibiji.npc +1 -1
  74. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npcsh.py +81 -43
  75. {npcsh-1.1.16 → npcsh-1.1.17/npcsh.egg-info}/PKG-INFO +10 -7
  76. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh.egg-info/SOURCES.txt +27 -1
  77. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh.egg-info/entry_points.txt +2 -0
  78. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh.egg-info/requires.txt +4 -0
  79. {npcsh-1.1.16 → npcsh-1.1.17}/setup.py +13 -3
  80. npcsh-1.1.16/npcsh/npc_team/jinxs/bin/wander.jinx +0 -152
  81. npcsh-1.1.16/npcsh/npc_team/jinxs/lib/core/search.jinx +0 -131
  82. npcsh-1.1.16/npcsh/npc_team/jinxs/lib/core/sql.jinx +0 -16
  83. {npcsh-1.1.16 → npcsh-1.1.17}/LICENSE +0 -0
  84. {npcsh-1.1.16 → npcsh-1.1.17}/MANIFEST.in +0 -0
  85. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/__init__.py +0 -0
  86. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/alicanto.py +0 -0
  87. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/build.py +0 -0
  88. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/completion.py +0 -0
  89. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/config.py +0 -0
  90. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/corca.py +0 -0
  91. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/execution.py +0 -0
  92. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/guac.py +0 -0
  93. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/mcp_helpers.py +0 -0
  94. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/mcp_server.py +0 -0
  95. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc.py +0 -0
  96. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/alicanto.npc +0 -0
  97. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/alicanto.png +0 -0
  98. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/corca.npc +0 -0
  99. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/corca.png +0 -0
  100. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/corca_example.png +0 -0
  101. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/frederic.npc +0 -0
  102. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/frederic4.png +0 -0
  103. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/guac.npc +0 -0
  104. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/guac.png +0 -0
  105. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/browser/close_browser.jinx +0 -0
  106. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/load_file.jinx +0 -0
  107. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/paste.jinx +0 -0
  108. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/python.jinx +0 -0
  109. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/shh.jinx +0 -0
  110. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/usage.jinx +0 -0
  111. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/verbose.jinx +0 -0
  112. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/kadiefa.npc +0 -0
  113. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/kadiefa.png +0 -0
  114. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/npcsh.ctx +0 -0
  115. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/npcsh_sibiji.png +0 -0
  116. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/plonk.npc +0 -0
  117. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/plonk.png +0 -0
  118. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/plonkjr.npc +0 -0
  119. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/plonkjr.png +0 -0
  120. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/sibiji.png +0 -0
  121. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/spool.png +0 -0
  122. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/npc_team/yap.png +0 -0
  123. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/parsing.py +0 -0
  124. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/plonk.py +0 -0
  125. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/pti.py +0 -0
  126. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/routes.py +0 -0
  127. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/spool.py +0 -0
  128. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/ui.py +0 -0
  129. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/wander.py +0 -0
  130. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh/yap.py +0 -0
  131. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh.egg-info/dependency_links.txt +0 -0
  132. {npcsh-1.1.16 → npcsh-1.1.17}/npcsh.egg-info/top_level.txt +0 -0
  133. {npcsh-1.1.16 → npcsh-1.1.17}/project/__init__.py +0 -0
  134. {npcsh-1.1.16 → npcsh-1.1.17}/setup.cfg +0 -0
  135. {npcsh-1.1.16 → npcsh-1.1.17}/tests/test_tool_routing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcsh
3
- Version: 1.1.16
3
+ Version: 1.1.17
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
@@ -66,6 +66,9 @@ Requires-Dist: playsound==1.2.2; extra == "yap"
66
66
  Requires-Dist: pygame; extra == "yap"
67
67
  Requires-Dist: faster_whisper; extra == "yap"
68
68
  Requires-Dist: pyttsx3; extra == "yap"
69
+ Provides-Extra: bench
70
+ Requires-Dist: harbor; extra == "bench"
71
+ Requires-Dist: terminal-bench; extra == "bench"
69
72
  Provides-Extra: all
70
73
  Requires-Dist: anthropic; extra == "all"
71
74
  Requires-Dist: openai; extra == "all"
@@ -604,7 +607,7 @@ npc vixynt "a sunset over mountains"
604
607
  | `/set` | Set config values. Usage: `/set model gemma3:4b`, `/set provider ollama` |
605
608
  | `/help` | Show help. Usage: `/help` |
606
609
  | `/jinxs` | List available jinxs. Usage: `/jinxs` |
607
- | `/npc-studio` | Launch NPC Studio GUI. Usage: `/npc-studio` |
610
+ | `/incognide` | Launch Incognide GUI. Usage: `/incognide` |
608
611
  | `/trigger` | Set up system triggers. Usage: `/trigger 'description' -m gemma3:27b` |
609
612
 
610
613
  ## Common Command-Line Flags:
@@ -630,16 +633,16 @@ To see more about how to use the jinxs and modes in the NPC Shell, read the docs
630
633
  ## Inference Capabilities
631
634
  - `npcsh` works with local and enterprise LLM providers through its LiteLLM integration, allowing users to run inference from Ollama, LMStudio, vLLM, MLX, OpenAI, Anthropic, Gemini, and Deepseek, making it a versatile tool for both simple commands and sophisticated AI-driven tasks.
632
635
 
633
- ## NPC Studio
634
- There is a graphical user interface that makes use of the NPC Toolkit through the NPC Studio. See the source code for NPC Studio [here](https://github.com/npc-worldwide/npc-studio). Download the executables at [our website](https://enpisi.com/downloads). For the most up to date development version, you can use NPC Studio by invoking it in npcsh
636
+ ## Incognide
637
+ Incognide is a desktop workspace environment for integrating LLMs into your workflows in an organized and seamless manner. See the source code for Incognide [here](https://github.com/npc-worldwide/incognide). Download the executables at [our website](https://enpisi.com/downloads). For the most up to date development version, you can use Incognide by invoking it in npcsh
635
638
 
636
639
  ```
637
- /npc-studio
640
+ /incognide
638
641
  ```
639
- which will download and set up and serve the NPC Studio application within your `~/.npcsh` folder. It requires `npm` and `node` to work, and of course npcpy !
642
+ which will download and set up and serve the Incognide application within your `~/.npcsh` folder. It requires `npm` and `node` to work, and of course npcpy !
640
643
 
641
644
  ## Mailing List and Community
642
- Interested to stay in the loop and to hear the latest and greatest about `npcpy`, `npcsh`, and NPC Studio? Be sure to sign up for the [newsletter](https://forms.gle/n1NzQmwjsV4xv1B2A)!
645
+ Interested to stay in the loop and to hear the latest and greatest about `npcpy`, `npcsh`, and Incognide? Be sure to sign up for the [newsletter](https://forms.gle/n1NzQmwjsV4xv1B2A)!
643
646
 
644
647
  [Join the discord to discuss ideas for npc tools](https://discord.gg/VvYVT5YC)
645
648
  ## Support
@@ -504,7 +504,7 @@ npc vixynt "a sunset over mountains"
504
504
  | `/set` | Set config values. Usage: `/set model gemma3:4b`, `/set provider ollama` |
505
505
  | `/help` | Show help. Usage: `/help` |
506
506
  | `/jinxs` | List available jinxs. Usage: `/jinxs` |
507
- | `/npc-studio` | Launch NPC Studio GUI. Usage: `/npc-studio` |
507
+ | `/incognide` | Launch Incognide GUI. Usage: `/incognide` |
508
508
  | `/trigger` | Set up system triggers. Usage: `/trigger 'description' -m gemma3:27b` |
509
509
 
510
510
  ## Common Command-Line Flags:
@@ -530,16 +530,16 @@ To see more about how to use the jinxs and modes in the NPC Shell, read the docs
530
530
  ## Inference Capabilities
531
531
  - `npcsh` works with local and enterprise LLM providers through its LiteLLM integration, allowing users to run inference from Ollama, LMStudio, vLLM, MLX, OpenAI, Anthropic, Gemini, and Deepseek, making it a versatile tool for both simple commands and sophisticated AI-driven tasks.
532
532
 
533
- ## NPC Studio
534
- There is a graphical user interface that makes use of the NPC Toolkit through the NPC Studio. See the source code for NPC Studio [here](https://github.com/npc-worldwide/npc-studio). Download the executables at [our website](https://enpisi.com/downloads). For the most up to date development version, you can use NPC Studio by invoking it in npcsh
533
+ ## Incognide
534
+ Incognide is a desktop workspace environment for integrating LLMs into your workflows in an organized and seamless manner. See the source code for Incognide [here](https://github.com/npc-worldwide/incognide). Download the executables at [our website](https://enpisi.com/downloads). For the most up to date development version, you can use Incognide by invoking it in npcsh
535
535
 
536
536
  ```
537
- /npc-studio
537
+ /incognide
538
538
  ```
539
- which will download and set up and serve the NPC Studio application within your `~/.npcsh` folder. It requires `npm` and `node` to work, and of course npcpy !
539
+ which will download and set up and serve the Incognide application within your `~/.npcsh` folder. It requires `npm` and `node` to work, and of course npcpy !
540
540
 
541
541
  ## Mailing List and Community
542
- Interested to stay in the loop and to hear the latest and greatest about `npcpy`, `npcsh`, and NPC Studio? Be sure to sign up for the [newsletter](https://forms.gle/n1NzQmwjsV4xv1B2A)!
542
+ Interested to stay in the loop and to hear the latest and greatest about `npcpy`, `npcsh`, and Incognide? Be sure to sign up for the [newsletter](https://forms.gle/n1NzQmwjsV4xv1B2A)!
543
543
 
544
544
  [Join the discord to discuss ideas for npc tools](https://discord.gg/VvYVT5YC)
545
545
  ## Support
@@ -351,8 +351,7 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
351
351
  None
352
352
  """
353
353
 
354
- if is_npcsh_initialized():
355
- return
354
+ already_initialized = is_npcsh_initialized()
356
355
 
357
356
  conn = sqlite3.connect(db_path)
358
357
  cursor = conn.cursor()
@@ -508,8 +507,10 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
508
507
  print(f"Copied template {file} to {destination_template_path}")
509
508
  conn.commit()
510
509
  conn.close()
511
- set_npcsh_initialized()
512
- add_npcshrc_to_shell_config()
510
+
511
+ if not already_initialized:
512
+ set_npcsh_initialized()
513
+ add_npcshrc_to_shell_config()
513
514
 
514
515
 
515
516
  def get_shell_config_file() -> str:
@@ -2540,8 +2541,19 @@ def collect_llm_tools(state: ShellState) -> Tuple[List[Dict[str, Any]], Dict[str
2540
2541
  if not jinja_env_for_jinx and state.team and isinstance(state.team, Team):
2541
2542
  jinja_env_for_jinx = getattr(state.team, "jinja_env", None)
2542
2543
 
2544
+ jinx_globals = {
2545
+ "state": state,
2546
+ "CommandHistory": CommandHistory,
2547
+ "load_kg_from_db": load_kg_from_db,
2548
+ "execute_rag_command": execute_rag_command,
2549
+ "execute_brainblast_command": execute_brainblast_command,
2550
+ "load_file_contents": load_file_contents,
2551
+ "search_web": search_web,
2552
+ "get_relevant_memories": get_relevant_memories,
2553
+ }
2554
+
2543
2555
  for name, jinx_obj in aggregated_jinxs.items():
2544
- def _make_runner(jinx=jinx_obj, jinja_env=jinja_env_for_jinx, tool_name=name):
2556
+ def _make_runner(jinx=jinx_obj, jinja_env=jinja_env_for_jinx, tool_name=name, extras=jinx_globals):
2545
2557
  def runner(**kwargs):
2546
2558
  input_values = kwargs if isinstance(kwargs, dict) else {}
2547
2559
  try:
@@ -2549,7 +2561,7 @@ def collect_llm_tools(state: ShellState) -> Tuple[List[Dict[str, Any]], Dict[str
2549
2561
  input_values=input_values,
2550
2562
  npc=npc_obj,
2551
2563
  messages=state.messages,
2552
- extra_globals={"state": state},
2564
+ extra_globals=extras,
2553
2565
  jinja_env=jinja_env
2554
2566
  )
2555
2567
  return ctx.get("output", ctx)
@@ -2945,14 +2957,17 @@ def process_pipeline_command(
2945
2957
  tool_name = msg.get("name", "tool")
2946
2958
  tool_content = msg.get("content", "")
2947
2959
  if tool_content and tool_content.strip():
2960
+ # Decode escaped newlines if present
2961
+ if isinstance(tool_content, str):
2962
+ tool_content = tool_content.replace('\\n', '\n').replace('\\t', '\t')
2948
2963
  print(colored(f"\n⚡ {tool_name}:", "cyan"))
2949
2964
  lines = tool_content.split('\n')
2950
2965
  if len(lines) > 50:
2951
- print('\n'.join(lines[:25]))
2966
+ render_markdown('\n'.join(lines[:25]))
2952
2967
  print(colored(f"\n... ({len(lines) - 50} lines hidden) ...\n", "white", attrs=["dark"]))
2953
- print('\n'.join(lines[-25:]))
2968
+ render_markdown('\n'.join(lines[-25:]))
2954
2969
  else:
2955
- print(tool_content)
2970
+ render_markdown(tool_content)
2956
2971
 
2957
2972
  # Check if LLM made tool calls - if not, it's done
2958
2973
  tool_calls_made = isinstance(llm_result, dict) and llm_result.get("tool_calls")
@@ -0,0 +1,22 @@
1
+ """
2
+ npcsh benchmark integration for Terminal-Bench.
3
+
4
+ This module provides integration with Terminal-Bench (tbench.ai) for benchmarking
5
+ npcsh against standardized terminal/CLI agent evaluation tasks.
6
+
7
+ Usage:
8
+ # Install terminal-bench
9
+ pip install terminal-bench harbor
10
+
11
+ # Run benchmarks with npcsh
12
+ harbor run -d terminal-bench@2.0 --agent-import-path npcsh.benchmark:NpcshAgent -m anthropic/claude-sonnet-4-20250514
13
+
14
+ # Or use the convenience function
15
+ from npcsh.benchmark import run_benchmark
16
+ run_benchmark(model="claude-sonnet-4-20250514", provider="anthropic")
17
+ """
18
+
19
+ from .npcsh_agent import NpcshAgent
20
+ from .runner import run_benchmark, BenchmarkRunner
21
+
22
+ __all__ = ["NpcshAgent", "run_benchmark", "BenchmarkRunner"]
@@ -0,0 +1,262 @@
1
+ """
2
+ npcsh Harbor Agent Adapter for Terminal-Bench.
3
+
4
+ This module implements the BaseInstalledAgent interface for running npcsh
5
+ as an agent in Terminal-Bench evaluations.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ import shlex
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ from harbor.agents.installed.base import BaseInstalledAgent, ExecInput
15
+ from harbor.models.agent.context import AgentContext
16
+
17
+
18
+ class NpcshAgent(BaseInstalledAgent):
19
+ """
20
+ Harbor agent adapter for npcsh.
21
+
22
+ This allows npcsh to be evaluated on Terminal-Bench tasks by:
23
+ 1. Installing npcsh in the benchmark container
24
+ 2. Running npcsh with the task instruction
25
+ 3. Parsing output for token usage and results
26
+
27
+ Usage:
28
+ harbor run -d terminal-bench@2.0 \\
29
+ --agent-import-path npcsh.benchmark:NpcshAgent \\
30
+ -m anthropic/claude-sonnet-4-20250514 -n 4
31
+ """
32
+
33
+ SUPPORTS_ATIF = True # Agent Trajectory Interchange Format
34
+
35
+ def __init__(self, logs_dir: Path = None, model_name: str = None, logger=None, **kwargs):
36
+ super().__init__(logs_dir=logs_dir, model_name=model_name, logger=logger, **kwargs)
37
+
38
+ @staticmethod
39
+ def name() -> str:
40
+ return "npcsh"
41
+
42
+ @property
43
+ def _install_agent_template_path(self) -> Path:
44
+ """Path to the jinja template script for installing npcsh in the container."""
45
+ return Path(__file__).parent / "templates" / "install-npcsh.sh.j2"
46
+
47
+ def create_run_agent_commands(self, instruction: str) -> list:
48
+ """
49
+ Create the commands to run npcsh in the container.
50
+
51
+ Args:
52
+ instruction: The task instruction from Terminal-Bench
53
+
54
+ Returns:
55
+ List of ExecInput commands to execute
56
+ """
57
+ escaped_instruction = shlex.quote(instruction)
58
+ model_name = self.model_name
59
+
60
+ if model_name and "/" in model_name:
61
+ provider, model = model_name.split("/", 1)
62
+ elif model_name:
63
+ provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
64
+ model = model_name
65
+ else:
66
+ provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
67
+ model = os.environ.get("NPCSH_CHAT_MODEL", "")
68
+
69
+ # Map provider names to npcsh provider format
70
+ provider_map = {
71
+ "anthropic": "anthropic",
72
+ "openai": "openai",
73
+ "google": "gemini",
74
+ "gemini": "gemini",
75
+ "deepseek": "deepseek",
76
+ "ollama": "ollama",
77
+ "groq": "groq",
78
+ "openrouter": "openrouter",
79
+ }
80
+ npcsh_provider = provider_map.get(provider.lower(), provider)
81
+
82
+ # Build environment variables for API keys
83
+ env_vars = []
84
+ api_key_map = {
85
+ "anthropic": "ANTHROPIC_API_KEY",
86
+ "openai": "OPENAI_API_KEY",
87
+ "gemini": "GOOGLE_API_KEY",
88
+ "google": "GOOGLE_API_KEY",
89
+ "deepseek": "DEEPSEEK_API_KEY",
90
+ "groq": "GROQ_API_KEY",
91
+ "openrouter": "OPENROUTER_API_KEY",
92
+ }
93
+
94
+ for prov, env_key in api_key_map.items():
95
+ if env_key in os.environ:
96
+ env_vars.append(f'{env_key}="{os.environ[env_key]}"')
97
+
98
+ env_prefix = " ".join(env_vars) + " " if env_vars else ""
99
+
100
+ # Output directory for logs
101
+ output_dir = str(self.logs_dir / "npcsh_output")
102
+ output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
103
+
104
+ commands = []
105
+
106
+ # Create output directory
107
+ commands.append(ExecInput(
108
+ cmd=f"mkdir -p {shlex.quote(output_dir)}",
109
+ timeout=30
110
+ ))
111
+
112
+ # Run npcsh with the instruction
113
+ # Using the npc CLI which supports single-command execution
114
+ npcsh_cmd = (
115
+ f'{env_prefix}'
116
+ f'NPCSH_CHAT_MODEL="{model}" '
117
+ f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
118
+ f'NPCSH_STREAM_OUTPUT=0 '
119
+ f'npc {escaped_instruction} '
120
+ f'2>&1 | tee {shlex.quote(output_file)}'
121
+ )
122
+
123
+ commands.append(ExecInput(
124
+ cmd=npcsh_cmd,
125
+ timeout=600, # 10 minute timeout for complex tasks
126
+ ))
127
+
128
+ return commands
129
+
130
+ def populate_context_post_run(self, context: AgentContext) -> None:
131
+ """
132
+ Populate the context with results of the agent execution.
133
+
134
+ Parses the output file to extract token usage metrics.
135
+
136
+ Args:
137
+ context: The AgentContext to populate with metrics
138
+ """
139
+ output_file = self.logs_dir / "npcsh_output" / "output.jsonl"
140
+
141
+ total_input_tokens = 0
142
+ total_output_tokens = 0
143
+ total_cost_usd = 0.0
144
+
145
+ if output_file.exists():
146
+ try:
147
+ with open(output_file, 'r') as f:
148
+ content = f.read()
149
+
150
+ # Try to parse as JSONL first
151
+ for line in content.strip().split('\n'):
152
+ if not line.strip():
153
+ continue
154
+ try:
155
+ event = json.loads(line)
156
+ # Extract token usage from events if present
157
+ if isinstance(event, dict):
158
+ usage = event.get('usage', {})
159
+ total_input_tokens += usage.get('input_tokens', 0)
160
+ total_output_tokens += usage.get('output_tokens', 0)
161
+ total_cost_usd += usage.get('cost_usd', 0.0)
162
+ except json.JSONDecodeError:
163
+ # Not JSON, just regular output
164
+ pass
165
+
166
+ except Exception as e:
167
+ self.logger.warning(f"Failed to parse npcsh output: {e}")
168
+
169
+ # Set context metrics
170
+ if hasattr(context, 'input_tokens'):
171
+ context.input_tokens = total_input_tokens
172
+ if hasattr(context, 'output_tokens'):
173
+ context.output_tokens = total_output_tokens
174
+ if hasattr(context, 'cost_usd'):
175
+ context.cost_usd = total_cost_usd
176
+
177
+
178
+ class NpcshAgentWithNpc(NpcshAgent):
179
+ """
180
+ Variant that uses a specific NPC for task execution.
181
+
182
+ This allows benchmarking specific NPCs like sibiji (orchestrator),
183
+ corca (coding), or custom NPCs.
184
+
185
+ Usage:
186
+ harbor run -d terminal-bench@2.0 \\
187
+ --agent-import-path "npcsh.benchmark:NpcshAgentWithNpc" \\
188
+ -m anthropic/claude-sonnet-4-20250514 -n 4
189
+ """
190
+
191
+ def __init__(self, *args, npc_name: str = "sibiji", **kwargs):
192
+ super().__init__(*args, **kwargs)
193
+ self.npc_name = npc_name
194
+
195
+ @staticmethod
196
+ def name() -> str:
197
+ return "npcsh-npc"
198
+
199
+ def create_run_agent_commands(self, instruction: str) -> list:
200
+ """Create commands using a specific NPC."""
201
+ escaped_instruction = shlex.quote(instruction)
202
+ model_name = self.model_name
203
+
204
+ if model_name and "/" in model_name:
205
+ provider, model = model_name.split("/", 1)
206
+ elif model_name:
207
+ provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
208
+ model = model_name
209
+ else:
210
+ provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
211
+ model = os.environ.get("NPCSH_CHAT_MODEL", "")
212
+
213
+ provider_map = {
214
+ "anthropic": "anthropic",
215
+ "openai": "openai",
216
+ "google": "gemini",
217
+ "gemini": "gemini",
218
+ "deepseek": "deepseek",
219
+ "ollama": "ollama",
220
+ }
221
+ npcsh_provider = provider_map.get(provider.lower(), provider)
222
+
223
+ env_vars = []
224
+ api_key_map = {
225
+ "anthropic": "ANTHROPIC_API_KEY",
226
+ "openai": "OPENAI_API_KEY",
227
+ "gemini": "GOOGLE_API_KEY",
228
+ "deepseek": "DEEPSEEK_API_KEY",
229
+ }
230
+
231
+ for prov, env_key in api_key_map.items():
232
+ if env_key in os.environ:
233
+ env_vars.append(f'{env_key}="{os.environ[env_key]}"')
234
+
235
+ env_prefix = " ".join(env_vars) + " " if env_vars else ""
236
+
237
+ output_dir = str(self.logs_dir / "npcsh_output")
238
+ output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
239
+
240
+ commands = []
241
+
242
+ commands.append(ExecInput(
243
+ cmd=f"mkdir -p {shlex.quote(output_dir)}",
244
+ timeout=30
245
+ ))
246
+
247
+ # Use specific NPC with --npc flag
248
+ npcsh_cmd = (
249
+ f'{env_prefix}'
250
+ f'NPCSH_CHAT_MODEL="{model}" '
251
+ f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
252
+ f'NPCSH_STREAM_OUTPUT=0 '
253
+ f'npc --npc {self.npc_name} {escaped_instruction} '
254
+ f'2>&1 | tee {shlex.quote(output_file)}'
255
+ )
256
+
257
+ commands.append(ExecInput(
258
+ cmd=npcsh_cmd,
259
+ timeout=600,
260
+ ))
261
+
262
+ return commands