npcsh 1.1.17__py3-none-any.whl → 1.1.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- npcsh/_state.py +122 -91
- npcsh/alicanto.py +2 -2
- npcsh/benchmark/__init__.py +8 -2
- npcsh/benchmark/npcsh_agent.py +87 -22
- npcsh/benchmark/runner.py +85 -43
- npcsh/benchmark/templates/install-npcsh.sh.j2 +35 -0
- npcsh/build.py +2 -4
- npcsh/completion.py +2 -6
- npcsh/config.py +2 -3
- npcsh/conversation_viewer.py +389 -0
- npcsh/corca.py +0 -1
- npcsh/diff_viewer.py +452 -0
- npcsh/execution.py +0 -1
- npcsh/guac.py +0 -1
- npcsh/mcp_helpers.py +2 -3
- npcsh/mcp_server.py +5 -10
- npcsh/npc.py +10 -11
- npcsh/npc_team/jinxs/bin/benchmark.jinx +1 -1
- npcsh/npc_team/jinxs/bin/config_tui.jinx +299 -0
- npcsh/npc_team/jinxs/bin/memories.jinx +316 -0
- npcsh/npc_team/jinxs/bin/setup.jinx +240 -0
- npcsh/npc_team/jinxs/bin/sync.jinx +143 -150
- npcsh/npc_team/jinxs/bin/team_tui.jinx +327 -0
- npcsh/npc_team/jinxs/incognide/add_tab.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/close_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/close_tab.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/confirm.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/focus_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/list_panes.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/navigate.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/notify.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/open_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/read_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/run_terminal.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/send_message.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/split_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/switch_npc.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/switch_tab.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/write_file.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/zen_mode.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +321 -17
- npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +312 -67
- npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +366 -44
- npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +73 -0
- npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +328 -20
- npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +242 -10
- npcsh/npc_team/jinxs/lib/core/sleep.jinx +22 -11
- npcsh/npc_team/jinxs/lib/core/sql.jinx +10 -6
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +387 -76
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +372 -55
- npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +299 -144
- npcsh/npc_team/jinxs/modes/alicanto.jinx +356 -0
- npcsh/npc_team/jinxs/modes/arxiv.jinx +720 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +430 -0
- npcsh/npc_team/jinxs/modes/guac.jinx +542 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +379 -0
- npcsh/npc_team/jinxs/modes/pti.jinx +357 -0
- npcsh/npc_team/jinxs/modes/reattach.jinx +291 -0
- npcsh/npc_team/jinxs/modes/spool.jinx +350 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +455 -0
- npcsh/npc_team/jinxs/{bin → modes}/yap.jinx +13 -7
- npcsh/npcsh.py +7 -4
- npcsh/plonk.py +0 -1
- npcsh/pti.py +0 -1
- npcsh/routes.py +1 -3
- npcsh/spool.py +0 -1
- npcsh/ui.py +0 -1
- npcsh/wander.py +0 -1
- npcsh/yap.py +0 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/add_tab.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/alicanto.jinx +356 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/arxiv.jinx +720 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/benchmark.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_pane.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_tab.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/config_tui.jinx +299 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/confirm.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/corca.jinx +430 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/db_search.jinx +348 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/file_search.jinx +339 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/focus_pane.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/guac.jinx +542 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/jinxs.jinx +331 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/kg_search.jinx +418 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/list_panes.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/mem_review.jinx +73 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/mem_search.jinx +388 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/memories.jinx +316 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/navigate.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/notify.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_pane.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/paper_search.jinx +412 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/plonk.jinx +379 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/pti.jinx +357 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/read_pane.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/reattach.jinx +291 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/run_terminal.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/semantic_scholar.jinx +386 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/send_message.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/setup.jinx +240 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sleep.jinx +22 -11
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/split_pane.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/spool.jinx +350 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/sql.jinx +20 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_npc.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_tab.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/sync.jinx +223 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/team_tui.jinx +327 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/wander.jinx +455 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/web_search.jinx +283 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/write_file.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.jinx +13 -7
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/zen_mode.jinx +1 -1
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/METADATA +110 -14
- npcsh-1.1.19.dist-info/RECORD +244 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/WHEEL +1 -1
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/entry_points.txt +4 -3
- npcsh/npc_team/jinxs/bin/spool.jinx +0 -161
- npcsh/npc_team/jinxs/bin/wander.jinx +0 -242
- npcsh/npc_team/jinxs/lib/research/arxiv.jinx +0 -76
- npcsh-1.1.17.data/data/npcsh/npc_team/arxiv.jinx +0 -76
- npcsh-1.1.17.data/data/npcsh/npc_team/db_search.jinx +0 -44
- npcsh-1.1.17.data/data/npcsh/npc_team/file_search.jinx +0 -94
- npcsh-1.1.17.data/data/npcsh/npc_team/jinxs.jinx +0 -176
- npcsh-1.1.17.data/data/npcsh/npc_team/kg_search.jinx +0 -96
- npcsh-1.1.17.data/data/npcsh/npc_team/mem_search.jinx +0 -80
- npcsh-1.1.17.data/data/npcsh/npc_team/paper_search.jinx +0 -101
- npcsh-1.1.17.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -69
- npcsh-1.1.17.data/data/npcsh/npc_team/spool.jinx +0 -161
- npcsh-1.1.17.data/data/npcsh/npc_team/sql.jinx +0 -16
- npcsh-1.1.17.data/data/npcsh/npc_team/sync.jinx +0 -230
- npcsh-1.1.17.data/data/npcsh/npc_team/wander.jinx +0 -242
- npcsh-1.1.17.data/data/npcsh/npc_team/web_search.jinx +0 -51
- npcsh-1.1.17.dist-info/RECORD +0 -219
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/click.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/convene.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/delegate.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/incognide.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/key_press.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/nql.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/paste.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/search.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sh.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/shh.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switches.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/type_text.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/verbose.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/wait.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
jinx_name: "sleep"
|
|
2
|
-
description: "Evolve knowledge graph. Use --dream to
|
|
2
|
+
description: "Evolve knowledge graph. Use --dream for creative synthesis, --backfill to import approved memories."
|
|
3
3
|
inputs:
|
|
4
4
|
- dream: False
|
|
5
|
+
- backfill: False
|
|
5
6
|
- ops: ""
|
|
6
7
|
- model: ""
|
|
7
8
|
- provider: ""
|
|
@@ -12,10 +13,10 @@ steps:
|
|
|
12
13
|
import os
|
|
13
14
|
import traceback
|
|
14
15
|
from npcpy.memory.command_history import CommandHistory, load_kg_from_db, save_kg_to_db
|
|
15
|
-
from npcpy.memory.knowledge_graph import kg_sleep_process, kg_dream_process
|
|
16
|
-
# Assuming render_markdown is available if needed for logging progress
|
|
16
|
+
from npcpy.memory.knowledge_graph import kg_sleep_process, kg_dream_process, kg_backfill_from_memories
|
|
17
17
|
|
|
18
18
|
is_dreaming = context.get('dream')
|
|
19
|
+
do_backfill = context.get('backfill')
|
|
19
20
|
operations_str = context.get('ops')
|
|
20
21
|
llm_model = context.get('model')
|
|
21
22
|
llm_provider = context.get('provider')
|
|
@@ -26,25 +27,22 @@ steps:
|
|
|
26
27
|
operations_config = None
|
|
27
28
|
if operations_str and isinstance(operations_str, str):
|
|
28
29
|
operations_config = [op.strip() for op in operations_str.split(',')]
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
# Fallback for model/provider if not explicitly set in Jinx inputs
|
|
31
32
|
if not llm_model and current_npc and current_npc.model:
|
|
32
33
|
llm_model = current_npc.model
|
|
33
34
|
if not llm_provider and current_npc and current_npc.provider:
|
|
34
35
|
llm_provider = current_npc.provider
|
|
35
|
-
|
|
36
|
+
|
|
36
37
|
# Final fallbacks from state
|
|
37
38
|
if not llm_model: llm_model = state.chat_model if state else "llama3.2"
|
|
38
39
|
if not llm_provider: llm_provider = state.chat_provider if state else "ollama"
|
|
39
40
|
|
|
40
41
|
team_name = current_team.name if current_team else "__none__"
|
|
41
|
-
npc_name = current_npc.name if
|
|
42
|
+
npc_name = current_npc.name if current_npc else "__none__"
|
|
42
43
|
current_path = os.getcwd()
|
|
43
44
|
scope_str = f"Team: '{team_name}', NPC: '{npc_name}', Path: '{current_path}'"
|
|
44
45
|
|
|
45
|
-
# Assume render_markdown exists
|
|
46
|
-
# render_markdown(f"- Checking knowledge graph for scope: {scope_str}")
|
|
47
|
-
|
|
48
46
|
command_history = None
|
|
49
47
|
try:
|
|
50
48
|
db_path = os.getenv("NPCSH_DB_PATH", os.path.expanduser("~/npcsh_history.db"))
|
|
@@ -57,13 +55,26 @@ steps:
|
|
|
57
55
|
|
|
58
56
|
output_result = ""
|
|
59
57
|
try:
|
|
58
|
+
# Run backfill first if requested
|
|
59
|
+
if do_backfill:
|
|
60
|
+
print("Running backfill from approved memories...")
|
|
61
|
+
stats = kg_backfill_from_memories(
|
|
62
|
+
engine,
|
|
63
|
+
model=llm_model,
|
|
64
|
+
provider=llm_provider,
|
|
65
|
+
npc=current_npc,
|
|
66
|
+
get_concepts=True,
|
|
67
|
+
dry_run=False
|
|
68
|
+
)
|
|
69
|
+
output_result += f"Backfill: +{stats['facts_after'] - stats['facts_before']} facts, +{stats['concepts_after'] - stats['concepts_before']} concepts\n"
|
|
70
|
+
|
|
60
71
|
current_kg = load_kg_from_db(engine, team_name, npc_name, current_path)
|
|
61
72
|
|
|
62
73
|
if not current_kg or not current_kg.get('facts'):
|
|
63
74
|
output_msg = f"Knowledge graph for the current scope is empty. Nothing to process.\n"
|
|
64
75
|
output_msg += f" - Scope Checked: {scope_str}\n\n"
|
|
65
|
-
output_msg += "**Hint:**
|
|
66
|
-
context['output'] = output_msg
|
|
76
|
+
output_msg += "**Hint:** Run `/sleep backfill=true` to import approved memories, or have conversations first."
|
|
77
|
+
context['output'] = output_result + output_msg if output_result else output_msg
|
|
67
78
|
context['messages'] = output_messages
|
|
68
79
|
exit()
|
|
69
80
|
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
jinx_name: spool
|
|
2
|
+
description: Interactive chat mode - simple conversational interface with an NPC
|
|
3
|
+
inputs:
|
|
4
|
+
- model: null
|
|
5
|
+
- provider: null
|
|
6
|
+
- attachments: null
|
|
7
|
+
- stream: true
|
|
8
|
+
|
|
9
|
+
steps:
|
|
10
|
+
- name: spool_repl
|
|
11
|
+
engine: python
|
|
12
|
+
code: |
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import tty
|
|
16
|
+
import termios
|
|
17
|
+
from termcolor import colored
|
|
18
|
+
|
|
19
|
+
from npcpy.llm_funcs import get_llm_response
|
|
20
|
+
from npcpy.npc_sysenv import get_system_message, render_markdown
|
|
21
|
+
from npcpy.data.load import load_file_contents
|
|
22
|
+
from npcpy.data.text import rag_search
|
|
23
|
+
|
|
24
|
+
npc = context.get('npc')
|
|
25
|
+
team = context.get('team')
|
|
26
|
+
messages = context.get('messages', [])
|
|
27
|
+
stream = context.get('stream', True)
|
|
28
|
+
attachments = context.get('attachments')
|
|
29
|
+
|
|
30
|
+
# Resolve npc if it's a string (npc name) rather than NPC object
|
|
31
|
+
if isinstance(npc, str) and team:
|
|
32
|
+
npc = team.get(npc) if hasattr(team, 'get') else None
|
|
33
|
+
elif isinstance(npc, str):
|
|
34
|
+
npc = None
|
|
35
|
+
|
|
36
|
+
# Use NPC's model/provider or fallback
|
|
37
|
+
model = context.get('model') or (npc.model if npc and hasattr(npc, 'model') else None)
|
|
38
|
+
provider = context.get('provider') or (npc.provider if npc and hasattr(npc, 'provider') else None)
|
|
39
|
+
|
|
40
|
+
# ========== TUI Helper Functions ==========
|
|
41
|
+
def get_terminal_size():
|
|
42
|
+
try:
|
|
43
|
+
size = os.get_terminal_size()
|
|
44
|
+
return size.columns, size.lines
|
|
45
|
+
except:
|
|
46
|
+
return 80, 24
|
|
47
|
+
|
|
48
|
+
def history_tui_browser(messages):
|
|
49
|
+
"""Interactive TUI browser for conversation history"""
|
|
50
|
+
# Filter out system messages for display
|
|
51
|
+
history = [m for m in messages if m.get('role') != 'system']
|
|
52
|
+
|
|
53
|
+
if not history:
|
|
54
|
+
print(colored("No conversation history yet.", "yellow"))
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
width, height = get_terminal_size()
|
|
58
|
+
selected = len(history) - 1 # Start at most recent
|
|
59
|
+
scroll = max(0, selected - (height - 6))
|
|
60
|
+
list_height = height - 5
|
|
61
|
+
mode = 'list'
|
|
62
|
+
preview_scroll = 0
|
|
63
|
+
preview_lines = []
|
|
64
|
+
|
|
65
|
+
fd = sys.stdin.fileno()
|
|
66
|
+
old_settings = termios.tcgetattr(fd)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
tty.setcbreak(fd)
|
|
70
|
+
sys.stdout.write('\033[?25l')
|
|
71
|
+
sys.stdout.write('\033[2J\033[H')
|
|
72
|
+
|
|
73
|
+
while True:
|
|
74
|
+
width, height = get_terminal_size()
|
|
75
|
+
list_height = height - 5
|
|
76
|
+
|
|
77
|
+
if mode == 'list':
|
|
78
|
+
if selected < scroll:
|
|
79
|
+
scroll = selected
|
|
80
|
+
elif selected >= scroll + list_height:
|
|
81
|
+
scroll = selected - list_height + 1
|
|
82
|
+
|
|
83
|
+
sys.stdout.write('\033[H')
|
|
84
|
+
|
|
85
|
+
# Header
|
|
86
|
+
if mode == 'list':
|
|
87
|
+
header = f" CONVERSATION HISTORY ({len(history)} messages) "
|
|
88
|
+
else:
|
|
89
|
+
header = f" MESSAGE {selected + 1} "
|
|
90
|
+
sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
|
|
91
|
+
|
|
92
|
+
if mode == 'list':
|
|
93
|
+
col_header = f' {"#":<4} {"ROLE":<12} {"PREVIEW":<60}'
|
|
94
|
+
sys.stdout.write(f'\033[90m{col_header[:width]}\033[0m\n')
|
|
95
|
+
else:
|
|
96
|
+
sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
|
|
97
|
+
|
|
98
|
+
if mode == 'list':
|
|
99
|
+
for i in range(list_height):
|
|
100
|
+
idx = scroll + i
|
|
101
|
+
sys.stdout.write(f'\033[{3+i};1H\033[K')
|
|
102
|
+
if idx >= len(history):
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
msg = history[idx]
|
|
106
|
+
role = msg.get('role', '?')[:12]
|
|
107
|
+
content = msg.get('content', '')[:60].replace('\n', ' ')
|
|
108
|
+
|
|
109
|
+
line = f" {idx+1:<4} {role:<12} {content}"
|
|
110
|
+
line = line[:width-1]
|
|
111
|
+
|
|
112
|
+
# Color by role
|
|
113
|
+
if msg.get('role') == 'user':
|
|
114
|
+
color = '\033[32m' # green
|
|
115
|
+
elif msg.get('role') == 'assistant':
|
|
116
|
+
color = '\033[34m' # blue
|
|
117
|
+
else:
|
|
118
|
+
color = ''
|
|
119
|
+
|
|
120
|
+
if idx == selected:
|
|
121
|
+
sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
|
|
122
|
+
elif color:
|
|
123
|
+
sys.stdout.write(f'{color}{line}\033[0m')
|
|
124
|
+
else:
|
|
125
|
+
sys.stdout.write(f' {line}')
|
|
126
|
+
|
|
127
|
+
# Status bar
|
|
128
|
+
sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
|
|
129
|
+
user_msgs = len([m for m in history if m.get('role') == 'user'])
|
|
130
|
+
asst_msgs = len([m for m in history if m.get('role') == 'assistant'])
|
|
131
|
+
sys.stdout.write(f'\033[{height-1};1H\033[K User: {user_msgs} | Assistant: {asst_msgs}'.ljust(width)[:width])
|
|
132
|
+
sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav p:Preview c:Copy q:Quit [{selected+1}/{len(history)}] \033[0m')
|
|
133
|
+
|
|
134
|
+
else: # preview mode
|
|
135
|
+
for i in range(list_height):
|
|
136
|
+
idx = preview_scroll + i
|
|
137
|
+
sys.stdout.write(f'\033[{3+i};1H\033[K')
|
|
138
|
+
if idx < len(preview_lines):
|
|
139
|
+
sys.stdout.write(preview_lines[idx][:width-1])
|
|
140
|
+
|
|
141
|
+
sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
|
|
142
|
+
sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(preview_lines)} lines]')
|
|
143
|
+
sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back c:Copy q:Quit \033[0m')
|
|
144
|
+
|
|
145
|
+
sys.stdout.flush()
|
|
146
|
+
|
|
147
|
+
c = sys.stdin.read(1)
|
|
148
|
+
|
|
149
|
+
if c == '\x1b':
|
|
150
|
+
c2 = sys.stdin.read(1)
|
|
151
|
+
if c2 == '[':
|
|
152
|
+
c3 = sys.stdin.read(1)
|
|
153
|
+
if c3 == 'A': # Up
|
|
154
|
+
if mode == 'list' and selected > 0:
|
|
155
|
+
selected -= 1
|
|
156
|
+
elif mode == 'preview' and preview_scroll > 0:
|
|
157
|
+
preview_scroll -= 1
|
|
158
|
+
elif c3 == 'B': # Down
|
|
159
|
+
if mode == 'list' and selected < len(history) - 1:
|
|
160
|
+
selected += 1
|
|
161
|
+
elif mode == 'preview' and preview_scroll < max(0, len(preview_lines) - list_height):
|
|
162
|
+
preview_scroll += 1
|
|
163
|
+
else:
|
|
164
|
+
if mode == 'preview':
|
|
165
|
+
mode = 'list'
|
|
166
|
+
sys.stdout.write('\033[2J\033[H')
|
|
167
|
+
else:
|
|
168
|
+
return
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
if c == 'q' or c == '\x03':
|
|
172
|
+
return
|
|
173
|
+
elif c == 'k':
|
|
174
|
+
if mode == 'list' and selected > 0:
|
|
175
|
+
selected -= 1
|
|
176
|
+
elif mode == 'preview' and preview_scroll > 0:
|
|
177
|
+
preview_scroll -= 1
|
|
178
|
+
elif c == 'j':
|
|
179
|
+
if mode == 'list' and selected < len(history) - 1:
|
|
180
|
+
selected += 1
|
|
181
|
+
elif mode == 'preview' and preview_scroll < max(0, len(preview_lines) - list_height):
|
|
182
|
+
preview_scroll += 1
|
|
183
|
+
elif c == 'p' and mode == 'list':
|
|
184
|
+
# Preview message
|
|
185
|
+
msg = history[selected]
|
|
186
|
+
preview_str = f"Role: {msg.get('role', '?')}\n"
|
|
187
|
+
preview_str += f"{'=' * 40}\n\n"
|
|
188
|
+
preview_str += msg.get('content', '')
|
|
189
|
+
preview_lines = preview_str.split('\n')
|
|
190
|
+
mode = 'preview'
|
|
191
|
+
preview_scroll = 0
|
|
192
|
+
sys.stdout.write('\033[2J\033[H')
|
|
193
|
+
elif c == 'b' and mode == 'preview':
|
|
194
|
+
mode = 'list'
|
|
195
|
+
sys.stdout.write('\033[2J\033[H')
|
|
196
|
+
elif c == 'c':
|
|
197
|
+
# Copy to clipboard
|
|
198
|
+
msg = history[selected]
|
|
199
|
+
content = msg.get('content', '')
|
|
200
|
+
try:
|
|
201
|
+
import subprocess
|
|
202
|
+
subprocess.run(['xclip', '-selection', 'clipboard'], input=content.encode(), check=True)
|
|
203
|
+
except:
|
|
204
|
+
try:
|
|
205
|
+
subprocess.run(['xsel', '--clipboard', '--input'], input=content.encode(), check=True)
|
|
206
|
+
except:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
finally:
|
|
210
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
211
|
+
sys.stdout.write('\033[?25h')
|
|
212
|
+
sys.stdout.write('\033[2J\033[H')
|
|
213
|
+
sys.stdout.flush()
|
|
214
|
+
|
|
215
|
+
# ASCII art
|
|
216
|
+
print("""
|
|
217
|
+
_____ ____ ____ ____ _
|
|
218
|
+
/ ___/| _ \ / __ \ / __ \| |
|
|
219
|
+
\___ \| |_) | | | | | | | |
|
|
220
|
+
___) | __/| | | | | | | |___
|
|
221
|
+
|____/|_| \____/ \____/|_____|
|
|
222
|
+
""")
|
|
223
|
+
|
|
224
|
+
npc_name = npc.name if npc else "chat"
|
|
225
|
+
print(f"Entering spool mode (NPC: {npc_name}). Type '/sq' to exit.")
|
|
226
|
+
print(" - /history: Browse conversation history")
|
|
227
|
+
|
|
228
|
+
# Load attachments if provided
|
|
229
|
+
loaded_chunks = {}
|
|
230
|
+
if attachments:
|
|
231
|
+
if isinstance(attachments, str):
|
|
232
|
+
attachments = [f.strip() for f in attachments.split(',')]
|
|
233
|
+
for file_path in attachments:
|
|
234
|
+
file_path = os.path.expanduser(file_path)
|
|
235
|
+
if os.path.exists(file_path):
|
|
236
|
+
try:
|
|
237
|
+
chunks = load_file_contents(file_path)
|
|
238
|
+
loaded_chunks[file_path] = chunks
|
|
239
|
+
print(colored(f"Loaded {len(chunks)} chunks from: {file_path}", "green"))
|
|
240
|
+
except Exception as e:
|
|
241
|
+
print(colored(f"Error loading {file_path}: {e}", "red"))
|
|
242
|
+
|
|
243
|
+
# Ensure system message
|
|
244
|
+
if not messages or messages[0].get("role") != "system":
|
|
245
|
+
sys_msg = get_system_message(npc) if npc else "You are a helpful assistant."
|
|
246
|
+
messages.insert(0, {"role": "system", "content": sys_msg})
|
|
247
|
+
|
|
248
|
+
# REPL loop
|
|
249
|
+
while True:
|
|
250
|
+
try:
|
|
251
|
+
prompt_str = f"{npc_name}> "
|
|
252
|
+
user_input = input(prompt_str).strip()
|
|
253
|
+
|
|
254
|
+
if not user_input:
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
if user_input.lower() == "/sq":
|
|
258
|
+
print("Exiting spool mode.")
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
# Handle /history for conversation browser
|
|
262
|
+
if user_input.lower() == "/history":
|
|
263
|
+
history_tui_browser(messages)
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
# Handle /ots for screenshots inline
|
|
267
|
+
if user_input.startswith("/ots"):
|
|
268
|
+
from npcpy.data.image import capture_screenshot
|
|
269
|
+
parts = user_input.split()
|
|
270
|
+
image_paths = []
|
|
271
|
+
if len(parts) > 1:
|
|
272
|
+
for p in parts[1:]:
|
|
273
|
+
fp = os.path.expanduser(p)
|
|
274
|
+
if os.path.exists(fp):
|
|
275
|
+
image_paths.append(fp)
|
|
276
|
+
else:
|
|
277
|
+
ss = capture_screenshot()
|
|
278
|
+
if ss and "file_path" in ss:
|
|
279
|
+
image_paths.append(ss["file_path"])
|
|
280
|
+
print(colored(f"Screenshot: {ss['filename']}", "green"))
|
|
281
|
+
|
|
282
|
+
if image_paths:
|
|
283
|
+
vision_prompt = input("Prompt for image(s): ").strip() or "Describe these images."
|
|
284
|
+
resp = get_llm_response(
|
|
285
|
+
vision_prompt,
|
|
286
|
+
model=npc.vision_model if hasattr(npc, 'vision_model') else model,
|
|
287
|
+
provider=npc.vision_provider if hasattr(npc, 'vision_provider') else provider,
|
|
288
|
+
messages=messages,
|
|
289
|
+
images=image_paths,
|
|
290
|
+
stream=stream,
|
|
291
|
+
npc=npc
|
|
292
|
+
)
|
|
293
|
+
messages = resp.get('messages', messages)
|
|
294
|
+
render_markdown(str(resp.get('response', '')))
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
# Add RAG context if files loaded
|
|
298
|
+
current_prompt = user_input
|
|
299
|
+
if loaded_chunks:
|
|
300
|
+
context_content = ""
|
|
301
|
+
for filename, chunks in loaded_chunks.items():
|
|
302
|
+
full_text = "\n".join(chunks)
|
|
303
|
+
retrieved = rag_search(user_input, full_text, similarity_threshold=0.3)
|
|
304
|
+
if retrieved:
|
|
305
|
+
context_content += f"\n\nContext from {filename}:\n{retrieved}\n"
|
|
306
|
+
if context_content:
|
|
307
|
+
current_prompt += f"\n\n--- Relevant context ---{context_content}"
|
|
308
|
+
|
|
309
|
+
# Get response
|
|
310
|
+
resp = get_llm_response(
|
|
311
|
+
current_prompt,
|
|
312
|
+
model=model,
|
|
313
|
+
provider=provider,
|
|
314
|
+
messages=messages,
|
|
315
|
+
stream=stream,
|
|
316
|
+
npc=npc
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
messages = resp.get('messages', messages)
|
|
320
|
+
response_text = resp.get('response', '')
|
|
321
|
+
|
|
322
|
+
# Handle streaming vs non-streaming
|
|
323
|
+
if hasattr(response_text, '__iter__') and not isinstance(response_text, str):
|
|
324
|
+
full_response = ""
|
|
325
|
+
for chunk in response_text:
|
|
326
|
+
if hasattr(chunk, 'choices') and chunk.choices:
|
|
327
|
+
delta = chunk.choices[0].delta
|
|
328
|
+
if hasattr(delta, 'content') and delta.content:
|
|
329
|
+
print(delta.content, end='', flush=True)
|
|
330
|
+
full_response += delta.content
|
|
331
|
+
print()
|
|
332
|
+
else:
|
|
333
|
+
render_markdown(str(response_text))
|
|
334
|
+
|
|
335
|
+
# Track usage if available
|
|
336
|
+
if 'usage' in resp and npc and hasattr(npc, 'shared_context'):
|
|
337
|
+
usage = resp['usage']
|
|
338
|
+
npc.shared_context['session_input_tokens'] += usage.get('input_tokens', 0)
|
|
339
|
+
npc.shared_context['session_output_tokens'] += usage.get('output_tokens', 0)
|
|
340
|
+
npc.shared_context['turn_count'] += 1
|
|
341
|
+
|
|
342
|
+
except KeyboardInterrupt:
|
|
343
|
+
print("\nUse '/sq' to exit or continue.")
|
|
344
|
+
continue
|
|
345
|
+
except EOFError:
|
|
346
|
+
print("\nExiting spool mode.")
|
|
347
|
+
break
|
|
348
|
+
|
|
349
|
+
context['output'] = "Exited spool mode."
|
|
350
|
+
context['messages'] = messages
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
jinx_name: sql
|
|
2
|
+
description: Execute queries on the ~/npcsh_history.db to pull data. The database contains only information about conversations and other user-provided data. It does not store any information about individual files.
|
|
3
|
+
inputs:
|
|
4
|
+
- sql_query: ""
|
|
5
|
+
|
|
6
|
+
steps:
|
|
7
|
+
- name: execute_sql
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
query = context.get('sql_query', '').strip()
|
|
13
|
+
if not query:
|
|
14
|
+
context['output'] = "Usage: /sql <query>"
|
|
15
|
+
else:
|
|
16
|
+
try:
|
|
17
|
+
df = pd.read_sql_query(query, npc.db_conn)
|
|
18
|
+
context['output'] = df.to_string()
|
|
19
|
+
except Exception as e:
|
|
20
|
+
context['output'] = "SQL Error: " + str(e)
|