npcsh 1.1.19__tar.gz → 1.1.21__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.19/npcsh.egg-info → npcsh-1.1.21}/PKG-INFO +2 -2
- {npcsh-1.1.19 → npcsh-1.1.21}/README.md +1 -1
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/_state.py +16 -78
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/diff_viewer.py +3 -3
- npcsh-1.1.21/npcsh/npc_team/jinxs/lib/core/compress.jinx +428 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +18 -7
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +18 -7
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +20 -9
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +53 -15
- {npcsh-1.1.19/npcsh/npc_team/jinxs/bin → npcsh-1.1.21/npcsh/npc_team/jinxs/lib/utils}/benchmark.jinx +2 -2
- npcsh-1.1.21/npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +407 -0
- npcsh-1.1.21/npcsh/npc_team/jinxs/lib/utils/models.jinx +343 -0
- {npcsh-1.1.19/npcsh/npc_team/jinxs/bin → npcsh-1.1.21/npcsh/npc_team/jinxs/lib/utils}/setup.jinx +8 -7
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/alicanto.jinx +1633 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/modes/arxiv.jinx +6 -6
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/modes/corca.jinx +4 -4
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/git.jinx +795 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/modes/guac.jinx +4 -4
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/kg.jinx +941 -0
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/nql.jinx +460 -0
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/plonk.jinx +565 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/modes/pti.jinx +1 -1
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/modes/reattach.jinx +4 -4
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/modes/spool.jinx +4 -4
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/team.jinx +504 -0
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
- npcsh-1.1.21/npcsh/npc_team/jinxs/modes/wander.jinx +728 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/modes/yap.jinx +10 -3
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npcsh.py +112 -47
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/routes.py +12 -3
- npcsh-1.1.21/npcsh/salmon_simulation.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21/npcsh.egg-info}/PKG-INFO +2 -2
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh.egg-info/SOURCES.txt +17 -17
- npcsh-1.1.21/npcsh.egg-info/entry_points.txt +11 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/setup.py +1 -1
- npcsh-1.1.19/npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -299
- npcsh-1.1.19/npcsh/npc_team/jinxs/bin/memories.jinx +0 -316
- npcsh-1.1.19/npcsh/npc_team/jinxs/bin/nql.jinx +0 -141
- npcsh-1.1.19/npcsh/npc_team/jinxs/bin/team_tui.jinx +0 -327
- npcsh-1.1.19/npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
- npcsh-1.1.19/npcsh/npc_team/jinxs/lib/core/compress.jinx +0 -140
- npcsh-1.1.19/npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
- npcsh-1.1.19/npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
- npcsh-1.1.19/npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
- npcsh-1.1.19/npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
- npcsh-1.1.19/npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +0 -331
- npcsh-1.1.19/npcsh/npc_team/jinxs/modes/alicanto.jinx +0 -356
- npcsh-1.1.19/npcsh/npc_team/jinxs/modes/plonk.jinx +0 -379
- npcsh-1.1.19/npcsh/npc_team/jinxs/modes/wander.jinx +0 -455
- npcsh-1.1.19/npcsh/npc_team/plonkjr.npc +0 -23
- npcsh-1.1.19/npcsh.egg-info/entry_points.txt +0 -22
- {npcsh-1.1.19 → npcsh-1.1.21}/LICENSE +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/MANIFEST.in +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/__init__.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/alicanto.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/benchmark/__init__.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/benchmark/npcsh_agent.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/benchmark/runner.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/benchmark/templates/install-npcsh.sh.j2 +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/build.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/completion.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/config.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/conversation_viewer.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/corca.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/execution.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/guac.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/mcp_helpers.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/mcp_server.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/add_tab.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/close_pane.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/close_tab.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/confirm.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/focus_pane.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/incognide.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/list_panes.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/navigate.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/notify.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/open_pane.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/read_pane.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/run_terminal.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/send_message.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/split_pane.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/switch_npc.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/switch_tab.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/write_file.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/incognide/zen_mode.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/browser/close_browser.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/computer_use/click.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/computer_use/trigger.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/chat.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/cmd.jinx +0 -0
- {npcsh-1.1.19/npcsh/npc_team/jinxs/lib/orchestration → npcsh-1.1.21/npcsh/npc_team/jinxs/lib/core}/convene.jinx +0 -0
- {npcsh-1.1.19/npcsh/npc_team/jinxs/lib/orchestration → npcsh-1.1.21/npcsh/npc_team/jinxs/lib/core}/delegate.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/edit_file.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/load_file.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/ots.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/paste.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/python.jinx +0 -0
- {npcsh-1.1.19/npcsh/npc_team/jinxs/bin → npcsh-1.1.21/npcsh/npc_team/jinxs/lib/core}/sample.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/search.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/sh.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/sleep.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/core/sql.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/build.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/compile.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/help.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/init.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/serve.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/set.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/shh.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/switch.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/switches.jinx +0 -0
- {npcsh-1.1.19/npcsh/npc_team/jinxs/bin → npcsh-1.1.21/npcsh/npc_team/jinxs/lib/utils}/sync.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/usage.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/jinxs/lib/utils/verbose.jinx +0 -0
- {npcsh-1.1.19/npcsh/npc_team/jinxs/bin → npcsh-1.1.21/npcsh/npc_team/jinxs/modes}/roll.jinx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/parsing.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/plonk.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/pti.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/spool.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/ui.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/wander.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh/yap.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh.egg-info/dependency_links.txt +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh.egg-info/requires.txt +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/npcsh.egg-info/top_level.txt +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/project/__init__.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/setup.cfg +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/tests/test_config.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/tests/test_jinxs.py +0 -0
- {npcsh-1.1.19 → npcsh-1.1.21}/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.
|
|
3
|
+
Version: 1.1.21
|
|
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
|
|
@@ -108,7 +108,7 @@ Dynamic: summary
|
|
|
108
108
|
|
|
109
109
|
# npcsh
|
|
110
110
|
|
|
111
|
-
The NPC shell (`npcsh`) makes the most of multi-modal LLMs and agents through a powerful set of simple slash commands and novel interactive modes, all from the comfort of the command line. Build teams of agents and schedule them on jobs, engineer context, and design custom interaction modes and Jinja Execution templates (Jinxs for you and your agents to invoke, all managed scalably for organizations of any size through the NPC
|
|
111
|
+
The NPC shell (`npcsh`) makes the most of multi-modal LLMs and agents through a powerful set of simple slash commands and novel interactive modes, all from the comfort of the command line. Build teams of agents and schedule them on jobs, engineer context, and design custom interaction modes and Jinja Execution templates (Jinxs for you and your agents to invoke, all managed scalably for organizations of any size through the NPC data layer.
|
|
112
112
|
|
|
113
113
|
To get started:
|
|
114
114
|
For users who want to mainly use models through APIs (`ollama`, `gemini`, `kimi`, `grok`, `deepseek`, `anthropic`, `openai`, `mistral`, or any others provided by litellm )
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
# npcsh
|
|
7
7
|
|
|
8
|
-
The NPC shell (`npcsh`) makes the most of multi-modal LLMs and agents through a powerful set of simple slash commands and novel interactive modes, all from the comfort of the command line. Build teams of agents and schedule them on jobs, engineer context, and design custom interaction modes and Jinja Execution templates (Jinxs for you and your agents to invoke, all managed scalably for organizations of any size through the NPC
|
|
8
|
+
The NPC shell (`npcsh`) makes the most of multi-modal LLMs and agents through a powerful set of simple slash commands and novel interactive modes, all from the comfort of the command line. Build teams of agents and schedule them on jobs, engineer context, and design custom interaction modes and Jinja Execution templates (Jinxs for you and your agents to invoke, all managed scalably for organizations of any size through the NPC data layer.
|
|
9
9
|
|
|
10
10
|
To get started:
|
|
11
11
|
For users who want to mainly use models through APIs (`ollama`, `gemini`, `kimi`, `grok`, `deepseek`, `anthropic`, `openai`, `mistral`, or any others provided by litellm )
|
|
@@ -2760,15 +2760,19 @@ def process_pipeline_command(
|
|
|
2760
2760
|
if cmd_to_process.startswith("/"):
|
|
2761
2761
|
command_name = cmd_to_process.split()[0].lstrip('/')
|
|
2762
2762
|
|
|
2763
|
-
# Check if this is an interactive mode
|
|
2763
|
+
# Check if this is an interactive mode
|
|
2764
2764
|
is_interactive_mode = False
|
|
2765
|
-
|
|
2766
|
-
# Check
|
|
2767
|
-
|
|
2768
|
-
if os.path.exists(global_modes_jinx):
|
|
2765
|
+
|
|
2766
|
+
# Check if the jinx declares interactive: true
|
|
2767
|
+
if router.is_interactive(command_name):
|
|
2769
2768
|
is_interactive_mode = True
|
|
2770
|
-
|
|
2771
|
-
#
|
|
2769
|
+
|
|
2770
|
+
# Also check modes/ directory (legacy)
|
|
2771
|
+
if not is_interactive_mode:
|
|
2772
|
+
global_modes_jinx = os.path.expanduser(f'~/.npcsh/npc_team/jinxs/modes/{command_name}.jinx')
|
|
2773
|
+
if os.path.exists(global_modes_jinx):
|
|
2774
|
+
is_interactive_mode = True
|
|
2775
|
+
|
|
2772
2776
|
if not is_interactive_mode and state.team and state.team.team_path:
|
|
2773
2777
|
team_modes_jinx = os.path.join(state.team.team_path, 'jinxs', 'modes', f'{command_name}.jinx')
|
|
2774
2778
|
if os.path.exists(team_modes_jinx):
|
|
@@ -3440,9 +3444,9 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
3440
3444
|
command_history = CommandHistory(db_path)
|
|
3441
3445
|
|
|
3442
3446
|
if not is_npcsh_initialized():
|
|
3443
|
-
print("
|
|
3447
|
+
print("Setting up npcsh for first use...")
|
|
3444
3448
|
initialize_base_npcs_if_needed(db_path)
|
|
3445
|
-
print("
|
|
3449
|
+
print("Setup complete.")
|
|
3446
3450
|
|
|
3447
3451
|
try:
|
|
3448
3452
|
setup_readline()
|
|
@@ -3454,80 +3458,17 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
3454
3458
|
project_team_path = os.path.abspath(PROJECT_NPC_TEAM_PATH)
|
|
3455
3459
|
global_team_path = os.path.expanduser(DEFAULT_NPC_TEAM_PATH)
|
|
3456
3460
|
|
|
3457
|
-
team_dir = None
|
|
3458
|
-
default_forenpc_name = None
|
|
3459
|
-
global_team_path = os.path.expanduser(DEFAULT_NPC_TEAM_PATH)
|
|
3460
3461
|
if not os.path.exists(global_team_path):
|
|
3461
|
-
print("Global NPC team directory doesn't exist. Initializing...")
|
|
3462
3462
|
initialize_base_npcs_if_needed(db_path)
|
|
3463
3463
|
if os.path.exists(project_team_path):
|
|
3464
3464
|
team_dir = project_team_path
|
|
3465
3465
|
default_forenpc_name = "forenpc"
|
|
3466
3466
|
else:
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
resp = input(f"No npc_team found in {os.getcwd()}. Create a new team here? [Y/n]: ").strip().lower()
|
|
3470
|
-
except (KeyboardInterrupt, EOFError):
|
|
3471
|
-
print("\nAborted.")
|
|
3472
|
-
sys.exit(0)
|
|
3473
|
-
if resp in ("", "y", "yes"):
|
|
3474
|
-
team_dir = project_team_path
|
|
3475
|
-
os.makedirs(team_dir, exist_ok=True)
|
|
3476
|
-
default_forenpc_name = "forenpc"
|
|
3477
|
-
try:
|
|
3478
|
-
forenpc_directive = input(
|
|
3479
|
-
f"Enter a primary directive for {default_forenpc_name} (default: 'You are the forenpc of the team...'): "
|
|
3480
|
-
).strip() or "You are the forenpc of the team, coordinating activities between NPCs on the team, verifying that results from NPCs are high quality and can help to adequately answer user requests."
|
|
3481
|
-
forenpc_model = input("Enter a model for your forenpc (default: llama3.2): ").strip() or "llama3.2"
|
|
3482
|
-
forenpc_provider = input("Enter a provider for your forenpc (default: ollama): ").strip() or "ollama"
|
|
3483
|
-
except (KeyboardInterrupt, EOFError):
|
|
3484
|
-
print("\nAborted.")
|
|
3485
|
-
sys.exit(0)
|
|
3486
|
-
|
|
3487
|
-
with open(os.path.join(team_dir, f"{default_forenpc_name}.npc"), "w") as f:
|
|
3488
|
-
yaml.dump({
|
|
3489
|
-
"name": default_forenpc_name, "primary_directive": forenpc_directive,
|
|
3490
|
-
"model": forenpc_model, "provider": forenpc_provider
|
|
3491
|
-
}, f)
|
|
3492
|
-
|
|
3493
|
-
ctx_path = os.path.join(team_dir, "team.ctx")
|
|
3494
|
-
try:
|
|
3495
|
-
folder_context = input("Enter a short description for this project/team (optional): ").strip()
|
|
3496
|
-
team_ctx_data = {
|
|
3497
|
-
"forenpc": default_forenpc_name,
|
|
3498
|
-
"model": forenpc_model,
|
|
3499
|
-
"provider": forenpc_provider,
|
|
3500
|
-
"context": folder_context if folder_context else None
|
|
3501
|
-
}
|
|
3502
|
-
use_jinxs = input("Use global jinxs folder (g) or copy to this project (c)? [g/c, default: g]: ").strip().lower()
|
|
3503
|
-
except (KeyboardInterrupt, EOFError):
|
|
3504
|
-
print("\nAborted.")
|
|
3505
|
-
sys.exit(0)
|
|
3506
|
-
if use_jinxs == "c":
|
|
3507
|
-
global_jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
|
|
3508
|
-
if os.path.exists(global_jinxs_dir):
|
|
3509
|
-
# Create the 'jinxs' subfolder within the new team's directory
|
|
3510
|
-
destination_jinxs_dir = os.path.join(team_dir, "jinxs")
|
|
3511
|
-
os.makedirs(destination_jinxs_dir, exist_ok=True)
|
|
3512
|
-
shutil.copytree(global_jinxs_dir, destination_jinxs_dir, dirs_exist_ok=True)
|
|
3513
|
-
else:
|
|
3514
|
-
team_ctx_data["use_global_jinxs"] = True
|
|
3515
|
-
with open(ctx_path, "w") as f:
|
|
3516
|
-
yaml.dump(team_ctx_data, f)
|
|
3517
|
-
else:
|
|
3518
|
-
render_markdown('From now on, npcsh will assume you will use the global team when activating from this folder. \n If you change your mind and want to initialize a team, use /init from within npcsh, `npc init` or `rm .npcsh_global` from the current working directory.')
|
|
3519
|
-
with open(".npcsh_global", "w") as f:
|
|
3520
|
-
pass
|
|
3521
|
-
team_dir = global_team_path
|
|
3522
|
-
default_forenpc_name = "sibiji"
|
|
3523
|
-
else:
|
|
3524
|
-
team_dir = global_team_path
|
|
3525
|
-
default_forenpc_name = "sibiji"
|
|
3526
|
-
|
|
3527
|
-
if team_dir is None:
|
|
3467
|
+
# No project team in this directory - use global team.
|
|
3468
|
+
# To create a project team, use /init from within npcsh or `npc init`.
|
|
3528
3469
|
team_dir = global_team_path
|
|
3529
3470
|
default_forenpc_name = "sibiji"
|
|
3530
|
-
|
|
3471
|
+
|
|
3531
3472
|
if not os.path.exists(team_dir):
|
|
3532
3473
|
print(f"Creating team directory: {team_dir}")
|
|
3533
3474
|
os.makedirs(team_dir, exist_ok=True)
|
|
@@ -3544,11 +3485,8 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
3544
3485
|
forenpc_name = team_ctx.get("forenpc", default_forenpc_name)
|
|
3545
3486
|
if forenpc_name is None:
|
|
3546
3487
|
forenpc_name = "sibiji"
|
|
3547
|
-
|
|
3548
|
-
print('forenpc_name:', forenpc_name)
|
|
3549
3488
|
|
|
3550
3489
|
forenpc_path = os.path.join(team_dir, f"{forenpc_name}.npc")
|
|
3551
|
-
print('forenpc_path:', forenpc_path)
|
|
3552
3490
|
|
|
3553
3491
|
team = Team(team_path=team_dir, db_conn=command_history.engine)
|
|
3554
3492
|
|
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import sys
|
|
7
7
|
import difflib
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
|
-
from typing import List, Dict,
|
|
9
|
+
from typing import List, Dict, Tuple
|
|
10
10
|
from enum import Enum
|
|
11
11
|
|
|
12
12
|
# Platform-specific imports
|
|
@@ -324,8 +324,8 @@ class DiffViewer:
|
|
|
324
324
|
start = hunk.start_original - 1 + offset
|
|
325
325
|
|
|
326
326
|
# Count removals and additions in this hunk
|
|
327
|
-
removals = [
|
|
328
|
-
additions = [
|
|
327
|
+
removals = [ln[1:] for ln in hunk.lines if ln.startswith('-')]
|
|
328
|
+
additions = [ln[1:] for ln in hunk.lines if ln.startswith('+')]
|
|
329
329
|
|
|
330
330
|
# Remove old lines
|
|
331
331
|
del result_lines[start:start + len(removals)]
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
jinx_name: "compress"
|
|
2
|
+
description: "Manages conversation and knowledge context - compress, flush, sleep, dream"
|
|
3
|
+
interactive: true
|
|
4
|
+
inputs:
|
|
5
|
+
- flush: ""
|
|
6
|
+
- sleep: False
|
|
7
|
+
- dream: False
|
|
8
|
+
- ops: ""
|
|
9
|
+
- model: ""
|
|
10
|
+
- provider: ""
|
|
11
|
+
steps:
|
|
12
|
+
- name: "manage_context_and_memory"
|
|
13
|
+
engine: "python"
|
|
14
|
+
code: |
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import traceback
|
|
18
|
+
from npcpy.llm_funcs import breathe
|
|
19
|
+
from npcpy.memory.command_history import CommandHistory, load_kg_from_db, save_kg_to_db
|
|
20
|
+
from npcpy.memory.knowledge_graph import kg_sleep_process, kg_dream_process
|
|
21
|
+
|
|
22
|
+
# --- Get all inputs from context ---
|
|
23
|
+
flush_n_str = context.get('flush')
|
|
24
|
+
is_sleeping = context.get('sleep')
|
|
25
|
+
is_dreaming = context.get('dream')
|
|
26
|
+
operations_str = context.get('ops')
|
|
27
|
+
llm_model = context.get('model')
|
|
28
|
+
llm_provider = context.get('provider')
|
|
29
|
+
output_messages = context.get('messages', [])
|
|
30
|
+
|
|
31
|
+
# --- Detect if called with explicit flags ---
|
|
32
|
+
is_flushing = flush_n_str is not None and str(flush_n_str).strip() != ''
|
|
33
|
+
has_explicit_args = is_flushing or is_sleeping or is_dreaming
|
|
34
|
+
|
|
35
|
+
# ========== Execution Functions ==========
|
|
36
|
+
def do_compress():
|
|
37
|
+
"""Compact conversation context via breathe()"""
|
|
38
|
+
try:
|
|
39
|
+
result = breathe(**context)
|
|
40
|
+
if isinstance(result, dict):
|
|
41
|
+
return result.get('output', 'Context compressed.')
|
|
42
|
+
return "Context compression process initiated."
|
|
43
|
+
except Exception as e:
|
|
44
|
+
traceback.print_exc()
|
|
45
|
+
return "Error during context compression: " + str(e)
|
|
46
|
+
|
|
47
|
+
def do_flush(n):
|
|
48
|
+
"""Remove last N messages from context"""
|
|
49
|
+
messages_list = list(output_messages)
|
|
50
|
+
original_len = len(messages_list)
|
|
51
|
+
if messages_list and messages_list[0].get("role") == "system":
|
|
52
|
+
system_message = messages_list.pop(0)
|
|
53
|
+
num_to_remove = min(n, len(messages_list))
|
|
54
|
+
final_messages = [system_message] + messages_list[:-num_to_remove] if num_to_remove < len(messages_list) else [system_message]
|
|
55
|
+
else:
|
|
56
|
+
num_to_remove = min(n, original_len)
|
|
57
|
+
final_messages = messages_list[:-num_to_remove] if num_to_remove < original_len else []
|
|
58
|
+
removed_count = original_len - len(final_messages)
|
|
59
|
+
context['messages'] = final_messages
|
|
60
|
+
return "Flushed " + str(removed_count) + " message(s). Context is now " + str(len(final_messages)) + " messages."
|
|
61
|
+
|
|
62
|
+
def do_sleep(model, provider, ops_str, dream=False):
|
|
63
|
+
"""Evolve knowledge graph via sleep/dream"""
|
|
64
|
+
current_npc = context.get('npc')
|
|
65
|
+
current_team = context.get('team')
|
|
66
|
+
operations_config = [op.strip() for op in ops_str.split(',')] if ops_str else None
|
|
67
|
+
if not model and current_npc: model = current_npc.model
|
|
68
|
+
if not provider and current_npc: provider = current_npc.provider
|
|
69
|
+
if not model: model = state.chat_model if state else "llama3.2"
|
|
70
|
+
if not provider: provider = state.chat_provider if state else "ollama"
|
|
71
|
+
team_name = current_team.name if current_team else "__none__"
|
|
72
|
+
npc_name = current_npc.name if current_npc else "__none__"
|
|
73
|
+
current_path = os.getcwd()
|
|
74
|
+
scope_str = "Team: '" + team_name + "', NPC: '" + npc_name + "', Path: '" + current_path + "'"
|
|
75
|
+
command_history = None
|
|
76
|
+
try:
|
|
77
|
+
db_path = os.getenv("NPCSH_DB_PATH", os.path.expanduser("~/npcsh_history.db"))
|
|
78
|
+
command_history = CommandHistory(db_path)
|
|
79
|
+
engine = command_history.engine
|
|
80
|
+
current_kg = load_kg_from_db(engine, team_name, npc_name, current_path)
|
|
81
|
+
if not current_kg or not current_kg.get('facts'):
|
|
82
|
+
return "Knowledge graph for the current scope is empty. Nothing to process.\n- Scope: " + scope_str
|
|
83
|
+
original_facts = len(current_kg.get('facts', []))
|
|
84
|
+
original_concepts = len(current_kg.get('concepts', []))
|
|
85
|
+
evolved_kg, _ = kg_sleep_process(existing_kg=current_kg, model=model, provider=provider, npc=current_npc, operations_config=operations_config)
|
|
86
|
+
process_type = "Sleep"
|
|
87
|
+
if dream:
|
|
88
|
+
evolved_kg, _ = kg_dream_process(existing_kg=evolved_kg, model=model, provider=provider, npc=current_npc)
|
|
89
|
+
process_type += " & Dream"
|
|
90
|
+
save_kg_to_db(engine, evolved_kg, team_name, npc_name, current_path)
|
|
91
|
+
new_facts = len(evolved_kg.get('facts', []))
|
|
92
|
+
new_concepts = len(evolved_kg.get('concepts', []))
|
|
93
|
+
return (process_type + " process complete.\n"
|
|
94
|
+
"- Facts: " + str(original_facts) + " -> " + str(new_facts) + " (" + str(new_facts - original_facts) + ")\n"
|
|
95
|
+
"- Concepts: " + str(original_concepts) + " -> " + str(new_concepts) + " (" + str(new_concepts - original_concepts) + ")")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
traceback.print_exc()
|
|
98
|
+
return "Error during KG evolution: " + str(e)
|
|
99
|
+
finally:
|
|
100
|
+
if command_history: command_history.close()
|
|
101
|
+
|
|
102
|
+
# ========== Direct execution (flags provided) ==========
|
|
103
|
+
if has_explicit_args:
|
|
104
|
+
if is_sleeping and is_flushing:
|
|
105
|
+
context['output'] = "Error: --sleep and --flush are mutually exclusive."
|
|
106
|
+
context['messages'] = output_messages
|
|
107
|
+
elif is_sleeping:
|
|
108
|
+
context['output'] = do_sleep(llm_model, llm_provider, operations_str, dream=bool(is_dreaming))
|
|
109
|
+
context['messages'] = output_messages
|
|
110
|
+
elif is_flushing:
|
|
111
|
+
try:
|
|
112
|
+
n = int(flush_n_str)
|
|
113
|
+
if n <= 0:
|
|
114
|
+
context['output'] = "Error: Number of messages to flush must be positive."
|
|
115
|
+
else:
|
|
116
|
+
context['output'] = do_flush(n)
|
|
117
|
+
except ValueError:
|
|
118
|
+
context['output'] = "Error: Invalid number '" + str(flush_n_str) + "'."
|
|
119
|
+
|
|
120
|
+
# ========== Interactive TUI (no flags) ==========
|
|
121
|
+
elif sys.stdin.isatty():
|
|
122
|
+
import tty
|
|
123
|
+
import termios
|
|
124
|
+
import select as _sel
|
|
125
|
+
|
|
126
|
+
class CompressState:
|
|
127
|
+
def __init__(self):
|
|
128
|
+
self.actions = [
|
|
129
|
+
{'name': 'Compress', 'desc': 'Compact conversation context (breathe)', 'key': 'compress'},
|
|
130
|
+
{'name': 'Flush', 'desc': 'Remove last N messages from context', 'key': 'flush'},
|
|
131
|
+
{'name': 'Sleep', 'desc': 'Evolve knowledge graph', 'key': 'sleep'},
|
|
132
|
+
{'name': 'Sleep + Dream', 'desc': 'Evolve KG with creative synthesis', 'key': 'dream'},
|
|
133
|
+
]
|
|
134
|
+
self.sel = 0
|
|
135
|
+
self.mode = 'menu' # menu, params, editing, running
|
|
136
|
+
# Params for each action
|
|
137
|
+
self.flush_n = "5"
|
|
138
|
+
self.sleep_model = ""
|
|
139
|
+
self.sleep_provider = ""
|
|
140
|
+
self.sleep_ops = ""
|
|
141
|
+
self.status = "Select an action"
|
|
142
|
+
self.msg_count = len(output_messages)
|
|
143
|
+
# Param editing
|
|
144
|
+
self.param_sel = 0
|
|
145
|
+
self.edit_buf = ""
|
|
146
|
+
self.edit_cursor = 0
|
|
147
|
+
|
|
148
|
+
def get_params(self):
|
|
149
|
+
"""Return list of (label, value) for current action"""
|
|
150
|
+
key = self.actions[self.sel]['key']
|
|
151
|
+
if key == 'compress':
|
|
152
|
+
return [('Messages in context', str(self.msg_count), False)]
|
|
153
|
+
elif key == 'flush':
|
|
154
|
+
return [
|
|
155
|
+
('Messages to flush', self.flush_n, True),
|
|
156
|
+
('Messages in context', str(self.msg_count), False),
|
|
157
|
+
]
|
|
158
|
+
elif key == 'sleep':
|
|
159
|
+
return [
|
|
160
|
+
('Model', self.sleep_model or '(default)', True),
|
|
161
|
+
('Provider', self.sleep_provider or '(default)', True),
|
|
162
|
+
('Operations', self.sleep_ops or '(all)', True),
|
|
163
|
+
]
|
|
164
|
+
elif key == 'dream':
|
|
165
|
+
return [
|
|
166
|
+
('Model', self.sleep_model or '(default)', True),
|
|
167
|
+
('Provider', self.sleep_provider or '(default)', True),
|
|
168
|
+
('Operations', self.sleep_ops or '(all)', True),
|
|
169
|
+
]
|
|
170
|
+
return []
|
|
171
|
+
|
|
172
|
+
def set_param(self, idx, val):
|
|
173
|
+
key = self.actions[self.sel]['key']
|
|
174
|
+
if key == 'flush':
|
|
175
|
+
if idx == 0: self.flush_n = val
|
|
176
|
+
elif key in ('sleep', 'dream'):
|
|
177
|
+
if idx == 0: self.sleep_model = val
|
|
178
|
+
elif idx == 1: self.sleep_provider = val
|
|
179
|
+
elif idx == 2: self.sleep_ops = val
|
|
180
|
+
|
|
181
|
+
st = CompressState()
|
|
182
|
+
|
|
183
|
+
def get_size():
|
|
184
|
+
try:
|
|
185
|
+
s = os.get_terminal_size()
|
|
186
|
+
return s.columns, s.lines
|
|
187
|
+
except:
|
|
188
|
+
return 80, 24
|
|
189
|
+
|
|
190
|
+
def render():
|
|
191
|
+
width, height = get_size()
|
|
192
|
+
out = []
|
|
193
|
+
out.append("\033[H")
|
|
194
|
+
|
|
195
|
+
# Header
|
|
196
|
+
header = " COMPRESS - Context & Memory Manager "
|
|
197
|
+
out.append("\033[1;1H\033[7;1m" + header.ljust(width) + "\033[0m")
|
|
198
|
+
|
|
199
|
+
if st.mode == 'menu':
|
|
200
|
+
out.append("\033[3;1H\033[36;1m Actions \033[90m" + ("-" * (width - 11)) + "\033[0m")
|
|
201
|
+
for i, act in enumerate(st.actions):
|
|
202
|
+
row = 4 + i
|
|
203
|
+
out.append("\033[" + str(row) + ";1H\033[K")
|
|
204
|
+
line = " " + act['name'].ljust(18) + "\033[90m" + act['desc'][:width-24] + "\033[0m"
|
|
205
|
+
if i == st.sel:
|
|
206
|
+
out.append("\033[7m>" + line + "\033[0m")
|
|
207
|
+
else:
|
|
208
|
+
out.append(" " + line)
|
|
209
|
+
|
|
210
|
+
# Show params preview below
|
|
211
|
+
params = st.get_params()
|
|
212
|
+
param_start = 4 + len(st.actions) + 1
|
|
213
|
+
out.append("\033[" + str(param_start) + ";1H\033[33;1m Parameters \033[90m" + ("-" * (width - 14)) + "\033[0m")
|
|
214
|
+
for j, (label, val, editable) in enumerate(params):
|
|
215
|
+
row = param_start + 1 + j
|
|
216
|
+
out.append("\033[" + str(row) + ";1H\033[K")
|
|
217
|
+
marker = "[e]" if editable else " "
|
|
218
|
+
out.append(" " + label.ljust(22) + val[:width-30] + " \033[90m" + marker + "\033[0m")
|
|
219
|
+
|
|
220
|
+
# Clear remaining lines
|
|
221
|
+
clear_start = param_start + 1 + len(params)
|
|
222
|
+
for r in range(clear_start, height - 2):
|
|
223
|
+
out.append("\033[" + str(r) + ";1H\033[K")
|
|
224
|
+
|
|
225
|
+
elif st.mode == 'params':
|
|
226
|
+
params = st.get_params()
|
|
227
|
+
act = st.actions[st.sel]
|
|
228
|
+
out.append("\033[3;1H\033[36;1m " + act['name'] + " Parameters \033[90m" + ("-" * (width - len(act['name']) - 16)) + "\033[0m")
|
|
229
|
+
for j, (label, val, editable) in enumerate(params):
|
|
230
|
+
row = 4 + j
|
|
231
|
+
out.append("\033[" + str(row) + ";1H\033[K")
|
|
232
|
+
if st.mode == 'editing' and j == st.param_sel:
|
|
233
|
+
line = " " + label.ljust(22) + "\033[7m " + st.edit_buf + " \033[0m"
|
|
234
|
+
else:
|
|
235
|
+
marker = " [e]" if editable else ""
|
|
236
|
+
line = " " + label.ljust(22) + val[:width-30] + "\033[90m" + marker + "\033[0m"
|
|
237
|
+
if j == st.param_sel:
|
|
238
|
+
out.append("\033[7m>" + line + "\033[0m")
|
|
239
|
+
else:
|
|
240
|
+
out.append(" " + line)
|
|
241
|
+
|
|
242
|
+
clear_start = 4 + len(params)
|
|
243
|
+
for r in range(clear_start, height - 2):
|
|
244
|
+
out.append("\033[" + str(r) + ";1H\033[K")
|
|
245
|
+
|
|
246
|
+
elif st.mode == 'editing':
|
|
247
|
+
# Same as params but with edit field
|
|
248
|
+
params = st.get_params()
|
|
249
|
+
act = st.actions[st.sel]
|
|
250
|
+
out.append("\033[3;1H\033[36;1m " + act['name'] + " Parameters \033[90m" + ("-" * (width - len(act['name']) - 16)) + "\033[0m")
|
|
251
|
+
for j, (label, val, editable) in enumerate(params):
|
|
252
|
+
row = 4 + j
|
|
253
|
+
out.append("\033[" + str(row) + ";1H\033[K")
|
|
254
|
+
if j == st.param_sel:
|
|
255
|
+
line = " " + label.ljust(22) + "\033[7m " + st.edit_buf + " \033[0m"
|
|
256
|
+
out.append(">" + line)
|
|
257
|
+
else:
|
|
258
|
+
marker = " [e]" if editable else ""
|
|
259
|
+
line = " " + label.ljust(22) + val[:width-30] + "\033[90m" + marker + "\033[0m"
|
|
260
|
+
out.append(" " + line)
|
|
261
|
+
|
|
262
|
+
clear_start = 4 + len(params)
|
|
263
|
+
for r in range(clear_start, height - 2):
|
|
264
|
+
out.append("\033[" + str(r) + ";1H\033[K")
|
|
265
|
+
|
|
266
|
+
elif st.mode == 'running':
|
|
267
|
+
out.append("\033[3;1H\033[K")
|
|
268
|
+
out.append("\033[4;1H\033[K Running " + st.actions[st.sel]['name'] + "...")
|
|
269
|
+
for r in range(5, height - 2):
|
|
270
|
+
out.append("\033[" + str(r) + ";1H\033[K")
|
|
271
|
+
|
|
272
|
+
# Status + footer
|
|
273
|
+
out.append("\033[" + str(height-2) + ";1H\033[K\033[90m" + ("-" * width) + "\033[0m")
|
|
274
|
+
out.append("\033[" + str(height-1) + ";1H\033[K " + st.status[:width-2])
|
|
275
|
+
|
|
276
|
+
if st.mode == 'menu':
|
|
277
|
+
footer = " j/k:Nav Enter:Configure G:Run Now q:Quit "
|
|
278
|
+
elif st.mode == 'params':
|
|
279
|
+
footer = " j/k:Nav e:Edit Enter:Execute b:Back q:Quit "
|
|
280
|
+
elif st.mode == 'editing':
|
|
281
|
+
footer = " Type value Enter:Confirm Esc:Cancel "
|
|
282
|
+
else:
|
|
283
|
+
footer = " Running... "
|
|
284
|
+
out.append("\033[" + str(height) + ";1H\033[K\033[7m" + footer.ljust(width) + "\033[0m")
|
|
285
|
+
|
|
286
|
+
sys.stdout.write(''.join(out))
|
|
287
|
+
sys.stdout.flush()
|
|
288
|
+
|
|
289
|
+
def execute_action():
|
|
290
|
+
"""Run the selected action and return result text"""
|
|
291
|
+
key = st.actions[st.sel]['key']
|
|
292
|
+
st.mode = 'running'
|
|
293
|
+
render()
|
|
294
|
+
|
|
295
|
+
if key == 'compress':
|
|
296
|
+
return do_compress()
|
|
297
|
+
elif key == 'flush':
|
|
298
|
+
try:
|
|
299
|
+
n = int(st.flush_n)
|
|
300
|
+
if n <= 0:
|
|
301
|
+
return "Error: Number must be positive."
|
|
302
|
+
return do_flush(n)
|
|
303
|
+
except ValueError:
|
|
304
|
+
return "Error: Invalid number '" + st.flush_n + "'."
|
|
305
|
+
elif key == 'sleep':
|
|
306
|
+
return do_sleep(st.sleep_model or None, st.sleep_provider or None, st.sleep_ops or None, dream=False)
|
|
307
|
+
elif key == 'dream':
|
|
308
|
+
return do_sleep(st.sleep_model or None, st.sleep_provider or None, st.sleep_ops or None, dream=True)
|
|
309
|
+
return "Unknown action."
|
|
310
|
+
|
|
311
|
+
fd = sys.stdin.fileno()
|
|
312
|
+
old_settings = termios.tcgetattr(fd)
|
|
313
|
+
result_text = None
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
tty.setcbreak(fd)
|
|
317
|
+
sys.stdout.write('\033[?25l')
|
|
318
|
+
sys.stdout.write('\033[2J')
|
|
319
|
+
render()
|
|
320
|
+
|
|
321
|
+
running = True
|
|
322
|
+
while running:
|
|
323
|
+
c = os.read(fd, 1).decode('latin-1')
|
|
324
|
+
|
|
325
|
+
if st.mode == 'editing':
|
|
326
|
+
if c == '\x1b':
|
|
327
|
+
if _sel.select([fd], [], [], 0.05)[0]:
|
|
328
|
+
os.read(fd, 2)
|
|
329
|
+
st.mode = 'params'
|
|
330
|
+
elif c in ('\r', '\n'):
|
|
331
|
+
st.set_param(st.param_sel, st.edit_buf)
|
|
332
|
+
st.mode = 'params'
|
|
333
|
+
st.status = "Parameter updated."
|
|
334
|
+
elif c == '\x7f' or c == '\x08':
|
|
335
|
+
if st.edit_cursor > 0:
|
|
336
|
+
st.edit_buf = st.edit_buf[:st.edit_cursor-1] + st.edit_buf[st.edit_cursor:]
|
|
337
|
+
st.edit_cursor -= 1
|
|
338
|
+
elif c >= ' ' and c <= '~':
|
|
339
|
+
st.edit_buf = st.edit_buf[:st.edit_cursor] + c + st.edit_buf[st.edit_cursor:]
|
|
340
|
+
st.edit_cursor += 1
|
|
341
|
+
render()
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
if c == '\x1b':
|
|
345
|
+
if _sel.select([fd], [], [], 0.05)[0]:
|
|
346
|
+
c2 = os.read(fd, 1).decode('latin-1')
|
|
347
|
+
if c2 == '[':
|
|
348
|
+
c3 = os.read(fd, 1).decode('latin-1')
|
|
349
|
+
if c3 == 'A': # Up
|
|
350
|
+
if st.mode == 'menu':
|
|
351
|
+
st.sel = max(0, st.sel - 1)
|
|
352
|
+
elif st.mode == 'params':
|
|
353
|
+
st.param_sel = max(0, st.param_sel - 1)
|
|
354
|
+
elif c3 == 'B': # Down
|
|
355
|
+
if st.mode == 'menu':
|
|
356
|
+
st.sel = min(len(st.actions) - 1, st.sel + 1)
|
|
357
|
+
elif st.mode == 'params':
|
|
358
|
+
params = st.get_params()
|
|
359
|
+
st.param_sel = min(len(params) - 1, st.param_sel + 1)
|
|
360
|
+
else:
|
|
361
|
+
if st.mode == 'params':
|
|
362
|
+
st.mode = 'menu'
|
|
363
|
+
st.status = "Select an action"
|
|
364
|
+
else:
|
|
365
|
+
result_text = "Cancelled."
|
|
366
|
+
running = False
|
|
367
|
+
render()
|
|
368
|
+
continue
|
|
369
|
+
|
|
370
|
+
if c == 'q' or c == '\x03':
|
|
371
|
+
result_text = "Cancelled."
|
|
372
|
+
running = False
|
|
373
|
+
elif st.mode == 'menu':
|
|
374
|
+
if c == 'j':
|
|
375
|
+
st.sel = min(len(st.actions) - 1, st.sel + 1)
|
|
376
|
+
elif c == 'k':
|
|
377
|
+
st.sel = max(0, st.sel - 1)
|
|
378
|
+
elif c in ('\r', '\n'):
|
|
379
|
+
params = st.get_params()
|
|
380
|
+
editable = [p for p in params if p[2]]
|
|
381
|
+
if editable:
|
|
382
|
+
st.mode = 'params'
|
|
383
|
+
st.param_sel = 0
|
|
384
|
+
st.status = "Configure parameters, then Enter to execute"
|
|
385
|
+
else:
|
|
386
|
+
result_text = execute_action()
|
|
387
|
+
running = False
|
|
388
|
+
elif c == 'G':
|
|
389
|
+
result_text = execute_action()
|
|
390
|
+
running = False
|
|
391
|
+
elif st.mode == 'params':
|
|
392
|
+
if c == 'j':
|
|
393
|
+
params = st.get_params()
|
|
394
|
+
st.param_sel = min(len(params) - 1, st.param_sel + 1)
|
|
395
|
+
elif c == 'k':
|
|
396
|
+
st.param_sel = max(0, st.param_sel - 1)
|
|
397
|
+
elif c == 'e':
|
|
398
|
+
params = st.get_params()
|
|
399
|
+
if st.param_sel < len(params) and params[st.param_sel][2]:
|
|
400
|
+
st.mode = 'editing'
|
|
401
|
+
val = params[st.param_sel][1]
|
|
402
|
+
st.edit_buf = "" if val.startswith('(') else val
|
|
403
|
+
st.edit_cursor = len(st.edit_buf)
|
|
404
|
+
elif c == 'b':
|
|
405
|
+
st.mode = 'menu'
|
|
406
|
+
st.status = "Select an action"
|
|
407
|
+
elif c in ('\r', '\n'):
|
|
408
|
+
result_text = execute_action()
|
|
409
|
+
running = False
|
|
410
|
+
|
|
411
|
+
render()
|
|
412
|
+
|
|
413
|
+
finally:
|
|
414
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
415
|
+
sys.stdout.write('\033[?25h')
|
|
416
|
+
sys.stdout.write('\033[2J\033[H')
|
|
417
|
+
sys.stdout.flush()
|
|
418
|
+
|
|
419
|
+
context['output'] = result_text or "Cancelled."
|
|
420
|
+
if 'messages' not in context:
|
|
421
|
+
context['messages'] = output_messages
|
|
422
|
+
|
|
423
|
+
# ========== Non-interactive fallback ==========
|
|
424
|
+
else:
|
|
425
|
+
result = do_compress()
|
|
426
|
+
context['output'] = result
|
|
427
|
+
if 'messages' not in context:
|
|
428
|
+
context['messages'] = output_messages
|