npcsh 1.1.15__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.
- npcsh-1.1.17/MANIFEST.in +2 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/PKG-INFO +10 -7
- {npcsh-1.1.15 → npcsh-1.1.17}/README.md +6 -6
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/_state.py +69 -12
- npcsh-1.1.17/npcsh/benchmark/__init__.py +22 -0
- npcsh-1.1.17/npcsh/benchmark/npcsh_agent.py +262 -0
- npcsh-1.1.17/npcsh/benchmark/runner.py +569 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/bin/benchmark.jinx +146 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/nql.jinx +7 -7
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/roll.jinx +20 -23
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/sample.jinx +6 -7
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/spool.jinx +4 -4
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/sync.jinx +6 -6
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/vixynt.jinx +8 -8
- npcsh-1.1.17/npcsh/npc_team/jinxs/bin/wander.jinx +242 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/bin/yap.jinx +5 -5
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/add_tab.jinx +11 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/close_pane.jinx +9 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/close_tab.jinx +10 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/confirm.jinx +10 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/focus_pane.jinx +9 -0
- npcsh-1.1.15/npcsh/npc_team/jinxs/npc_studio/npc-studio.jinx → npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/incognide.jinx +2 -2
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/list_panes.jinx +8 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/navigate.jinx +10 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/notify.jinx +10 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/open_pane.jinx +13 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/read_pane.jinx +9 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/run_terminal.jinx +10 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/send_message.jinx +10 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/split_pane.jinx +12 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/switch_npc.jinx +10 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/switch_tab.jinx +10 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/write_file.jinx +11 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/incognide/zen_mode.jinx +9 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +4 -4
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +2 -2
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/click.jinx +2 -2
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/trigger.jinx +2 -2
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/chat.jinx +4 -4
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/cmd.jinx +4 -4
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/compress.jinx +8 -8
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/edit_file.jinx +3 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/ots.jinx +7 -7
- npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +44 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +94 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +96 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +80 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +51 -0
- npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/search.jinx +54 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/sh.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/sleep.jinx +7 -7
- npcsh-1.1.17/npcsh/npc_team/jinxs/lib/core/sql.jinx +16 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +7 -7
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +8 -9
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/research/arxiv.jinx +2 -2
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/research/paper_search.jinx +3 -3
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +2 -2
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/build.jinx +5 -5
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/compile.jinx +2 -2
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/help.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/init.jinx +5 -5
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/serve.jinx +2 -2
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/set.jinx +2 -2
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/switch.jinx +3 -3
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/switches.jinx +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +2 -2
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/sibiji.npc +1 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npcsh.py +81 -43
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh.egg-info/PKG-INFO +10 -7
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh.egg-info/SOURCES.txt +28 -1
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh.egg-info/entry_points.txt +2 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh.egg-info/requires.txt +4 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/setup.py +34 -3
- npcsh-1.1.15/npcsh/npc_team/jinxs/bin/wander.jinx +0 -152
- npcsh-1.1.15/npcsh/npc_team/jinxs/lib/core/search.jinx +0 -131
- npcsh-1.1.15/npcsh/npc_team/jinxs/lib/core/sql.jinx +0 -16
- {npcsh-1.1.15 → npcsh-1.1.17}/LICENSE +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/__init__.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/alicanto.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/build.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/completion.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/config.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/corca.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/execution.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/guac.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/mcp_helpers.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/mcp_server.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/browser/close_browser.jinx +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/load_file.jinx +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/paste.jinx +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/core/python.jinx +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/shh.jinx +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/usage.jinx +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/jinxs/lib/utils/verbose.jinx +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/parsing.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/plonk.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/pti.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/routes.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/spool.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/ui.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/wander.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh/yap.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh.egg-info/dependency_links.txt +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/npcsh.egg-info/top_level.txt +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/project/__init__.py +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/setup.cfg +0 -0
- {npcsh-1.1.15 → npcsh-1.1.17}/tests/test_tool_routing.py +0 -0
npcsh-1.1.17/MANIFEST.in
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcsh
|
|
3
|
-
Version: 1.1.
|
|
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
|
-
| `/
|
|
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
|
-
##
|
|
634
|
-
|
|
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
|
-
/
|
|
640
|
+
/incognide
|
|
638
641
|
```
|
|
639
|
-
which will download and set up and serve the
|
|
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
|
|
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
|
-
| `/
|
|
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
|
-
##
|
|
534
|
-
|
|
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
|
-
/
|
|
537
|
+
/incognide
|
|
538
538
|
```
|
|
539
|
-
which will download and set up and serve the
|
|
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
|
|
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
|
-
|
|
355
|
-
return
|
|
354
|
+
already_initialized = is_npcsh_initialized()
|
|
356
355
|
|
|
357
356
|
conn = sqlite3.connect(db_path)
|
|
358
357
|
cursor = conn.cursor()
|
|
@@ -368,10 +367,27 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
368
367
|
"""
|
|
369
368
|
)
|
|
370
369
|
|
|
371
|
-
# Package directories
|
|
372
|
-
package_dir =
|
|
370
|
+
# Package directories - use helper that handles PyInstaller bundles
|
|
371
|
+
package_dir = get_package_dir()
|
|
373
372
|
package_npc_team_dir = os.path.join(package_dir, "npc_team")
|
|
374
373
|
|
|
374
|
+
# Debug logging for package path resolution
|
|
375
|
+
if os.environ.get("NPCSH_DEBUG", "0") == "1":
|
|
376
|
+
print(f"[DEBUG] Package dir: {package_dir}")
|
|
377
|
+
print(f"[DEBUG] Package npc_team dir: {package_npc_team_dir}")
|
|
378
|
+
print(f"[DEBUG] npc_team exists: {os.path.exists(package_npc_team_dir)}")
|
|
379
|
+
if os.path.exists(package_npc_team_dir):
|
|
380
|
+
print(f"[DEBUG] npc_team contents: {os.listdir(package_npc_team_dir)}")
|
|
381
|
+
|
|
382
|
+
if not os.path.exists(package_npc_team_dir):
|
|
383
|
+
print(f"Warning: Package npc_team directory not found at {package_npc_team_dir}")
|
|
384
|
+
# For bundled executables, try to find it
|
|
385
|
+
if getattr(sys, 'frozen', False):
|
|
386
|
+
print(f"Running as frozen executable, _MEIPASS: {getattr(sys, '_MEIPASS', 'N/A')}")
|
|
387
|
+
if hasattr(sys, '_MEIPASS'):
|
|
388
|
+
print(f"Contents of _MEIPASS: {os.listdir(sys._MEIPASS)}")
|
|
389
|
+
return
|
|
390
|
+
|
|
375
391
|
user_npc_team_dir = os.path.expanduser("~/.npcsh/npc_team")
|
|
376
392
|
|
|
377
393
|
user_jinxs_dir = os.path.join(user_npc_team_dir, "jinxs")
|
|
@@ -491,8 +507,10 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
|
491
507
|
print(f"Copied template {file} to {destination_template_path}")
|
|
492
508
|
conn.commit()
|
|
493
509
|
conn.close()
|
|
494
|
-
|
|
495
|
-
|
|
510
|
+
|
|
511
|
+
if not already_initialized:
|
|
512
|
+
set_npcsh_initialized()
|
|
513
|
+
add_npcshrc_to_shell_config()
|
|
496
514
|
|
|
497
515
|
|
|
498
516
|
def get_shell_config_file() -> str:
|
|
@@ -1121,6 +1139,31 @@ def set_npcsh_initialized() -> None:
|
|
|
1121
1139
|
|
|
1122
1140
|
|
|
1123
1141
|
|
|
1142
|
+
def get_package_dir() -> str:
|
|
1143
|
+
"""
|
|
1144
|
+
Get the package directory, handling both normal Python and PyInstaller executables.
|
|
1145
|
+
|
|
1146
|
+
For normal Python: returns os.path.dirname(__file__)
|
|
1147
|
+
For PyInstaller: returns the bundled data directory (sys._MEIPASS/npcsh)
|
|
1148
|
+
"""
|
|
1149
|
+
# Check if running as a PyInstaller bundle
|
|
1150
|
+
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
|
1151
|
+
# Running as PyInstaller bundle - look for npcsh folder in _MEIPASS
|
|
1152
|
+
meipass = sys._MEIPASS
|
|
1153
|
+
# The package data should be at _MEIPASS/npcsh (based on PyInstaller config)
|
|
1154
|
+
bundled_path = os.path.join(meipass, 'npcsh')
|
|
1155
|
+
if os.path.exists(bundled_path):
|
|
1156
|
+
return bundled_path
|
|
1157
|
+
# Fallback: check if npc_team is directly in _MEIPASS
|
|
1158
|
+
if os.path.exists(os.path.join(meipass, 'npc_team')):
|
|
1159
|
+
return meipass
|
|
1160
|
+
# Last resort: return meipass and let caller handle
|
|
1161
|
+
return meipass
|
|
1162
|
+
else:
|
|
1163
|
+
# Normal Python execution
|
|
1164
|
+
return os.path.dirname(__file__)
|
|
1165
|
+
|
|
1166
|
+
|
|
1124
1167
|
def file_has_changed(source_path: str, destination_path: str) -> bool:
|
|
1125
1168
|
"""
|
|
1126
1169
|
Function Description:
|
|
@@ -1134,7 +1177,7 @@ def file_has_changed(source_path: str, destination_path: str) -> bool:
|
|
|
1134
1177
|
A boolean indicating whether the files are different
|
|
1135
1178
|
"""
|
|
1136
1179
|
|
|
1137
|
-
|
|
1180
|
+
|
|
1138
1181
|
return not filecmp.cmp(source_path, destination_path, shallow=False)
|
|
1139
1182
|
|
|
1140
1183
|
|
|
@@ -2498,8 +2541,19 @@ def collect_llm_tools(state: ShellState) -> Tuple[List[Dict[str, Any]], Dict[str
|
|
|
2498
2541
|
if not jinja_env_for_jinx and state.team and isinstance(state.team, Team):
|
|
2499
2542
|
jinja_env_for_jinx = getattr(state.team, "jinja_env", None)
|
|
2500
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
|
+
|
|
2501
2555
|
for name, jinx_obj in aggregated_jinxs.items():
|
|
2502
|
-
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):
|
|
2503
2557
|
def runner(**kwargs):
|
|
2504
2558
|
input_values = kwargs if isinstance(kwargs, dict) else {}
|
|
2505
2559
|
try:
|
|
@@ -2507,7 +2561,7 @@ def collect_llm_tools(state: ShellState) -> Tuple[List[Dict[str, Any]], Dict[str
|
|
|
2507
2561
|
input_values=input_values,
|
|
2508
2562
|
npc=npc_obj,
|
|
2509
2563
|
messages=state.messages,
|
|
2510
|
-
extra_globals=
|
|
2564
|
+
extra_globals=extras,
|
|
2511
2565
|
jinja_env=jinja_env
|
|
2512
2566
|
)
|
|
2513
2567
|
return ctx.get("output", ctx)
|
|
@@ -2903,14 +2957,17 @@ def process_pipeline_command(
|
|
|
2903
2957
|
tool_name = msg.get("name", "tool")
|
|
2904
2958
|
tool_content = msg.get("content", "")
|
|
2905
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')
|
|
2906
2963
|
print(colored(f"\n⚡ {tool_name}:", "cyan"))
|
|
2907
2964
|
lines = tool_content.split('\n')
|
|
2908
2965
|
if len(lines) > 50:
|
|
2909
|
-
|
|
2966
|
+
render_markdown('\n'.join(lines[:25]))
|
|
2910
2967
|
print(colored(f"\n... ({len(lines) - 50} lines hidden) ...\n", "white", attrs=["dark"]))
|
|
2911
|
-
|
|
2968
|
+
render_markdown('\n'.join(lines[-25:]))
|
|
2912
2969
|
else:
|
|
2913
|
-
|
|
2970
|
+
render_markdown(tool_content)
|
|
2914
2971
|
|
|
2915
2972
|
# Check if LLM made tool calls - if not, it's done
|
|
2916
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
|