npcsh 1.1.20__py3-none-any.whl → 1.1.22__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 +15 -76
- npcsh/benchmark/npcsh_agent.py +22 -14
- npcsh/benchmark/templates/install-npcsh.sh.j2 +2 -2
- npcsh/diff_viewer.py +3 -3
- npcsh/mcp_server.py +9 -1
- npcsh/npc_team/alicanto.npc +12 -6
- npcsh/npc_team/corca.npc +0 -1
- npcsh/npc_team/frederic.npc +2 -3
- npcsh/npc_team/jinxs/lib/core/compress.jinx +373 -85
- npcsh/npc_team/jinxs/lib/core/edit_file.jinx +83 -61
- npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +17 -6
- npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +17 -6
- npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +52 -14
- npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
- npcsh/npc_team/jinxs/{bin → lib/utils}/jinxs.jinx +12 -12
- npcsh/npc_team/jinxs/{bin → lib/utils}/models.jinx +7 -7
- npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +6 -6
- npcsh/npc_team/jinxs/modes/alicanto.jinx +1633 -295
- npcsh/npc_team/jinxs/modes/arxiv.jinx +5 -5
- npcsh/npc_team/jinxs/modes/build.jinx +378 -0
- npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
- npcsh/npc_team/jinxs/modes/convene.jinx +597 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +777 -387
- npcsh/npc_team/jinxs/modes/git.jinx +795 -0
- {npcsh-1.1.20.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/modes}/kg.jinx +82 -15
- npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
- npcsh/npc_team/jinxs/{bin → modes}/nql.jinx +10 -21
- npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +503 -308
- npcsh/npc_team/jinxs/modes/reattach.jinx +3 -3
- npcsh/npc_team/jinxs/modes/spool.jinx +3 -3
- npcsh/npc_team/jinxs/{bin → modes}/team.jinx +12 -12
- npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +454 -181
- npcsh/npc_team/jinxs/modes/yap.jinx +630 -182
- npcsh/npc_team/kadiefa.npc +2 -1
- npcsh/npc_team/sibiji.npc +3 -3
- npcsh/npcsh.py +112 -47
- npcsh/routes.py +4 -1
- npcsh/salmon_simulation.py +0 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/alicanto.jinx +1694 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.npc +12 -6
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/arxiv.jinx +5 -5
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
- npcsh-1.1.22.data/data/npcsh/npc_team/build.jinx +378 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/compress.jinx +428 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/config_tui.jinx +300 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/corca.jinx +820 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.npc +0 -1
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/db_search.jinx +17 -6
- npcsh-1.1.22.data/data/npcsh/npc_team/edit_file.jinx +119 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/file_search.jinx +17 -6
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic.npc +2 -3
- npcsh-1.1.22.data/data/npcsh/npc_team/git.jinx +795 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/jinxs.jinx +12 -12
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.npc +2 -1
- {npcsh/npc_team/jinxs/bin → npcsh-1.1.22.data/data/npcsh/npc_team}/kg.jinx +82 -15
- npcsh-1.1.22.data/data/npcsh/npc_team/memories.jinx +414 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/models.jinx +7 -7
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/nql.jinx +10 -21
- npcsh-1.1.22.data/data/npcsh/npc_team/papers.jinx +578 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/plonk.jinx +574 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/reattach.jinx +3 -3
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/setup.jinx +6 -6
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.npc +3 -3
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.jinx +3 -3
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/team.jinx +12 -12
- npcsh-1.1.22.data/data/npcsh/npc_team/vixynt.jinx +388 -0
- npcsh-1.1.22.data/data/npcsh/npc_team/wander.jinx +728 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/web_search.jinx +52 -14
- npcsh-1.1.22.data/data/npcsh/npc_team/yap.jinx +716 -0
- {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/METADATA +246 -281
- npcsh-1.1.22.dist-info/RECORD +240 -0
- npcsh-1.1.22.dist-info/entry_points.txt +11 -0
- npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -300
- npcsh/npc_team/jinxs/bin/memories.jinx +0 -317
- npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
- npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +0 -418
- npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
- npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
- npcsh/npc_team/jinxs/lib/core/search.jinx +0 -54
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
- npcsh/npc_team/jinxs/lib/utils/build.jinx +0 -65
- npcsh/npc_team/plonkjr.npc +0 -23
- npcsh-1.1.20.data/data/npcsh/npc_team/alicanto.jinx +0 -356
- npcsh-1.1.20.data/data/npcsh/npc_team/build.jinx +0 -65
- npcsh-1.1.20.data/data/npcsh/npc_team/compress.jinx +0 -140
- npcsh-1.1.20.data/data/npcsh/npc_team/config_tui.jinx +0 -300
- npcsh-1.1.20.data/data/npcsh/npc_team/corca.jinx +0 -430
- npcsh-1.1.20.data/data/npcsh/npc_team/edit_file.jinx +0 -97
- npcsh-1.1.20.data/data/npcsh/npc_team/kg_search.jinx +0 -418
- npcsh-1.1.20.data/data/npcsh/npc_team/mem_review.jinx +0 -73
- npcsh-1.1.20.data/data/npcsh/npc_team/mem_search.jinx +0 -388
- npcsh-1.1.20.data/data/npcsh/npc_team/memories.jinx +0 -317
- npcsh-1.1.20.data/data/npcsh/npc_team/paper_search.jinx +0 -412
- npcsh-1.1.20.data/data/npcsh/npc_team/plonk.jinx +0 -379
- npcsh-1.1.20.data/data/npcsh/npc_team/plonkjr.npc +0 -23
- npcsh-1.1.20.data/data/npcsh/npc_team/search.jinx +0 -54
- npcsh-1.1.20.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
- npcsh-1.1.20.data/data/npcsh/npc_team/vixynt.jinx +0 -122
- npcsh-1.1.20.data/data/npcsh/npc_team/wander.jinx +0 -455
- npcsh-1.1.20.data/data/npcsh/npc_team/yap.jinx +0 -268
- npcsh-1.1.20.dist-info/RECORD +0 -248
- npcsh-1.1.20.dist-info/entry_points.txt +0 -25
- /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
- /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
- /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
- /npcsh/npc_team/jinxs/lib/{core → utils}/chat.jinx +0 -0
- /npcsh/npc_team/jinxs/lib/{core → utils}/cmd.jinx +0 -0
- /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
- /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/click.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/confirm.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/convene.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/delegate.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/incognide.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/key_press.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/navigate.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/notify.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/paste.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/pti.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/send_message.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sh.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/shh.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sql.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switches.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sync.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/type_text.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/verbose.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/wait.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/write_file.jinx +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
- {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/WHEEL +0 -0
- {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
jinx_name: kg
|
|
2
2
|
description: Interactive knowledge graph browser - explore facts, concepts, and links
|
|
3
3
|
interactive: true
|
|
4
|
-
inputs:
|
|
4
|
+
inputs:
|
|
5
|
+
- action: ""
|
|
6
|
+
- dream: false
|
|
7
|
+
- backfill: false
|
|
8
|
+
- ops: ""
|
|
5
9
|
steps:
|
|
6
10
|
- name: kg_browser
|
|
7
11
|
engine: python
|
|
@@ -12,7 +16,70 @@ steps:
|
|
|
12
16
|
import termios
|
|
13
17
|
import select
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
_kg_action = (context.get('action') or '').strip().lower()
|
|
20
|
+
|
|
21
|
+
if _kg_action in ('sleep', 'evolve', 'dream'):
|
|
22
|
+
# Route to KG evolution operations
|
|
23
|
+
import traceback
|
|
24
|
+
from npcpy.memory.command_history import CommandHistory, load_kg_from_db, save_kg_to_db
|
|
25
|
+
from npcpy.memory.knowledge_graph import kg_sleep_process, kg_dream_process, kg_backfill_from_memories
|
|
26
|
+
|
|
27
|
+
_npc = context.get('npc')
|
|
28
|
+
_team = context.get('team')
|
|
29
|
+
_msgs = context.get('messages', [])
|
|
30
|
+
_do_dream = _kg_action == 'dream' or str(context.get('dream', '')).lower() in ('true', '1', 'yes')
|
|
31
|
+
_do_backfill = str(context.get('backfill', '')).lower() in ('true', '1', 'yes')
|
|
32
|
+
_ops_str = context.get('ops', '')
|
|
33
|
+
_ops_config = [op.strip() for op in _ops_str.split(',') if op.strip()] if _ops_str else None
|
|
34
|
+
|
|
35
|
+
_model = (_npc.model if _npc and hasattr(_npc, 'model') else None) or (state.chat_model if state else 'llama3.2')
|
|
36
|
+
_provider = (_npc.provider if _npc and hasattr(_npc, 'provider') else None) or (state.chat_provider if state else 'ollama')
|
|
37
|
+
|
|
38
|
+
_team_name = _team.name if _team else '__none__'
|
|
39
|
+
_npc_name = _npc.name if _npc else '__none__'
|
|
40
|
+
_cur_path = os.getcwd()
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
_db_path = os.getenv('NPCSH_DB_PATH', os.path.expanduser('~/npcsh_history.db'))
|
|
44
|
+
_ch = CommandHistory(_db_path)
|
|
45
|
+
_eng = _ch.engine
|
|
46
|
+
|
|
47
|
+
_result = ''
|
|
48
|
+
if _do_backfill:
|
|
49
|
+
print('Backfilling from approved memories...')
|
|
50
|
+
_stats = kg_backfill_from_memories(_eng, model=_model, provider=_provider, npc=_npc, get_concepts=True, dry_run=False)
|
|
51
|
+
_result += f"Backfill: +{_stats['facts_after'] - _stats['facts_before']} facts, +{_stats['concepts_after'] - _stats['concepts_before']} concepts\n"
|
|
52
|
+
|
|
53
|
+
_kg = load_kg_from_db(_eng, _team_name, _npc_name, _cur_path)
|
|
54
|
+
if not _kg or not _kg.get('facts'):
|
|
55
|
+
context['output'] = _result + 'Knowledge graph is empty. Use /kg backfill=true or have conversations first.'
|
|
56
|
+
context['messages'] = _msgs
|
|
57
|
+
_ch.close()
|
|
58
|
+
exit()
|
|
59
|
+
|
|
60
|
+
_f0 = len(_kg.get('facts', []))
|
|
61
|
+
_c0 = len(_kg.get('concepts', []))
|
|
62
|
+
_label = 'Sleep'
|
|
63
|
+
|
|
64
|
+
_kg, _ = kg_sleep_process(existing_kg=_kg, model=_model, provider=_provider, npc=_npc, operations_config=_ops_config)
|
|
65
|
+
|
|
66
|
+
if _do_dream:
|
|
67
|
+
_label += ' & Dream'
|
|
68
|
+
_kg, _ = kg_dream_process(existing_kg=_kg, model=_model, provider=_provider, npc=_npc)
|
|
69
|
+
|
|
70
|
+
save_kg_to_db(_eng, _kg, _team_name, _npc_name, _cur_path)
|
|
71
|
+
_f1 = len(_kg.get('facts', []))
|
|
72
|
+
_c1 = len(_kg.get('concepts', []))
|
|
73
|
+
_result += f"{_label} complete. Facts: {_f0} -> {_f1} ({_f1-_f0:+}), Concepts: {_c0} -> {_c1} ({_c1-_c0:+})"
|
|
74
|
+
context['output'] = _result
|
|
75
|
+
context['messages'] = _msgs
|
|
76
|
+
_ch.close()
|
|
77
|
+
except Exception as e:
|
|
78
|
+
traceback.print_exc()
|
|
79
|
+
context['output'] = f'KG evolution error: {e}'
|
|
80
|
+
context['messages'] = _msgs
|
|
81
|
+
|
|
82
|
+
elif not sys.stdin.isatty():
|
|
16
83
|
context['output'] = "KG browser requires an interactive terminal."
|
|
17
84
|
|
|
18
85
|
else:
|
|
@@ -166,12 +233,12 @@ steps:
|
|
|
166
233
|
|
|
167
234
|
def do_search(query):
|
|
168
235
|
ui.search_results = []
|
|
169
|
-
|
|
236
|
+
pat = f"%{query}%"
|
|
170
237
|
with engine.connect() as conn:
|
|
171
238
|
r = conn.execute(text(
|
|
172
239
|
"SELECT statement, source_text, type, generation, origin "
|
|
173
|
-
"FROM kg_facts WHERE statement LIKE :
|
|
174
|
-
), {"
|
|
240
|
+
"FROM kg_facts WHERE statement LIKE :pat ORDER BY rowid DESC"
|
|
241
|
+
), {"pat": pat})
|
|
175
242
|
for row in r:
|
|
176
243
|
ui.search_results.append({
|
|
177
244
|
'kind': 'fact',
|
|
@@ -377,8 +444,8 @@ steps:
|
|
|
377
444
|
# ── header ──
|
|
378
445
|
hdr = " Knowledge Graph "
|
|
379
446
|
pad = '=' * W
|
|
380
|
-
out.append(wline(1, f"\033[
|
|
381
|
-
out.append(f"\033[1;{max(1,(W - len(hdr))//2)}H\033[
|
|
447
|
+
out.append(wline(1, f"\033[7;1m{pad}\033[0m"))
|
|
448
|
+
out.append(f"\033[1;{max(1,(W - len(hdr))//2)}H\033[7;1m{hdr}\033[0m")
|
|
382
449
|
|
|
383
450
|
# ── tabs ──
|
|
384
451
|
tb = ""
|
|
@@ -439,7 +506,7 @@ steps:
|
|
|
439
506
|
foot = " [Tab] Switch [j/k] Select [Enter] Center [Backspace] Back [q] Quit"
|
|
440
507
|
else:
|
|
441
508
|
foot = " [Tab] Switch [j/k] Nav [Enter] Detail [/] Search [g] Gen [q] Quit"
|
|
442
|
-
out.append(wline(H, f"\033[
|
|
509
|
+
out.append(wline(H, f"\033[7m{foot[:W].ljust(W)}\033[0m"))
|
|
443
510
|
|
|
444
511
|
sys.stdout.write(''.join(out))
|
|
445
512
|
sys.stdout.flush()
|
|
@@ -779,10 +846,10 @@ steps:
|
|
|
779
846
|
return True
|
|
780
847
|
|
|
781
848
|
def handle_esc():
|
|
782
|
-
if select.select([
|
|
783
|
-
c2 =
|
|
849
|
+
if select.select([fd], [], [], 0.05)[0]:
|
|
850
|
+
c2 = os.read(fd, 1).decode('latin-1')
|
|
784
851
|
if c2 == '[':
|
|
785
|
-
c3 =
|
|
852
|
+
c3 = os.read(fd, 1).decode('latin-1')
|
|
786
853
|
if c3 == 'A':
|
|
787
854
|
nav_up()
|
|
788
855
|
elif c3 == 'B':
|
|
@@ -796,10 +863,10 @@ steps:
|
|
|
796
863
|
|
|
797
864
|
def handle_search_input(c):
|
|
798
865
|
if c == '\x1b':
|
|
799
|
-
if select.select([
|
|
800
|
-
c2 =
|
|
866
|
+
if select.select([fd], [], [], 0.05)[0]:
|
|
867
|
+
c2 = os.read(fd, 1).decode('latin-1')
|
|
801
868
|
if c2 == '[':
|
|
802
|
-
|
|
869
|
+
os.read(fd, 1).decode('latin-1')
|
|
803
870
|
else:
|
|
804
871
|
ui.search_mode = False
|
|
805
872
|
ui.search_buf = ""
|
|
@@ -929,7 +996,7 @@ steps:
|
|
|
929
996
|
sys.stdout.flush()
|
|
930
997
|
render()
|
|
931
998
|
while True:
|
|
932
|
-
c =
|
|
999
|
+
c = os.read(fd, 1).decode('latin-1')
|
|
933
1000
|
if not handle(c):
|
|
934
1001
|
break
|
|
935
1002
|
render()
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
jinx_name: memories
|
|
2
|
+
description: Interactive TUI for browsing and managing npcsh memories
|
|
3
|
+
interactive: true
|
|
4
|
+
inputs:
|
|
5
|
+
- scope: ""
|
|
6
|
+
steps:
|
|
7
|
+
- name: memory_browser
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import tty
|
|
13
|
+
import termios
|
|
14
|
+
import select
|
|
15
|
+
import time
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
|
|
18
|
+
if not sys.stdin.isatty():
|
|
19
|
+
context['output'] = "Memory browser requires an interactive terminal."
|
|
20
|
+
|
|
21
|
+
else:
|
|
22
|
+
from npcpy.memory.command_history import CommandHistory
|
|
23
|
+
from npcsh.config import NPCSH_DB_PATH
|
|
24
|
+
from sqlalchemy import text
|
|
25
|
+
|
|
26
|
+
db_path = os.path.expanduser(NPCSH_DB_PATH)
|
|
27
|
+
command_history = CommandHistory(db_path)
|
|
28
|
+
|
|
29
|
+
# ========== Discover actual status values from DB ==========
|
|
30
|
+
db_statuses = []
|
|
31
|
+
try:
|
|
32
|
+
with command_history.engine.connect() as conn:
|
|
33
|
+
rows = conn.execute(text("SELECT DISTINCT status FROM memory_lifecycle ORDER BY status"))
|
|
34
|
+
db_statuses = [r[0] for r in rows if r[0]]
|
|
35
|
+
except:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
# Build tabs: All + pending first, then rest
|
|
39
|
+
ordered = []
|
|
40
|
+
for s in db_statuses:
|
|
41
|
+
if 'pending' in s.lower():
|
|
42
|
+
ordered.insert(0, s)
|
|
43
|
+
else:
|
|
44
|
+
ordered.append(s)
|
|
45
|
+
tab_names = ['All'] + ordered
|
|
46
|
+
tab_filters = [None] + ordered
|
|
47
|
+
|
|
48
|
+
# ========== State ==========
|
|
49
|
+
class MemState:
|
|
50
|
+
tab = 0
|
|
51
|
+
tabs = tab_names
|
|
52
|
+
tab_filt = tab_filters
|
|
53
|
+
memories = []
|
|
54
|
+
sel = 0
|
|
55
|
+
scroll = 0
|
|
56
|
+
preview = False
|
|
57
|
+
msg = ""
|
|
58
|
+
msg_color = "33"
|
|
59
|
+
npc_filter = None
|
|
60
|
+
team_filter = None
|
|
61
|
+
approved_count = 0
|
|
62
|
+
rejected_count = 0
|
|
63
|
+
|
|
64
|
+
st = MemState()
|
|
65
|
+
|
|
66
|
+
# ========== Helpers ==========
|
|
67
|
+
def get_size():
|
|
68
|
+
try:
|
|
69
|
+
s = os.get_terminal_size()
|
|
70
|
+
return s.columns, s.lines
|
|
71
|
+
except:
|
|
72
|
+
return 80, 24
|
|
73
|
+
|
|
74
|
+
def status_icon(sv):
|
|
75
|
+
if not sv:
|
|
76
|
+
return '\033[90m?\033[0m'
|
|
77
|
+
s = sv.lower()
|
|
78
|
+
if 'approved' in s:
|
|
79
|
+
return '\033[1;32m+\033[0m'
|
|
80
|
+
if 'edited' in s:
|
|
81
|
+
return '\033[1;36m~\033[0m'
|
|
82
|
+
if 'rejected' in s:
|
|
83
|
+
return '\033[1;31m-\033[0m'
|
|
84
|
+
if 'pending' in s:
|
|
85
|
+
return '\033[1;33m*\033[0m'
|
|
86
|
+
return '\033[90m?\033[0m'
|
|
87
|
+
|
|
88
|
+
def status_color(sv):
|
|
89
|
+
if not sv:
|
|
90
|
+
return '0'
|
|
91
|
+
s = sv.lower()
|
|
92
|
+
if 'approved' in s:
|
|
93
|
+
return '32'
|
|
94
|
+
if 'edited' in s:
|
|
95
|
+
return '36'
|
|
96
|
+
if 'rejected' in s:
|
|
97
|
+
return '31'
|
|
98
|
+
if 'pending' in s:
|
|
99
|
+
return '33'
|
|
100
|
+
return '0'
|
|
101
|
+
|
|
102
|
+
def load_memories():
|
|
103
|
+
st.memories = []
|
|
104
|
+
try:
|
|
105
|
+
with command_history.engine.connect() as conn:
|
|
106
|
+
sf = st.tab_filt[st.tab] if st.tab < len(st.tab_filt) else None
|
|
107
|
+
q = "SELECT id, created_at, npc, team, directory_path, initial_memory, final_memory, status FROM memory_lifecycle"
|
|
108
|
+
conds = []
|
|
109
|
+
params = {}
|
|
110
|
+
|
|
111
|
+
if sf:
|
|
112
|
+
conds.append("status = :sf")
|
|
113
|
+
params['sf'] = sf
|
|
114
|
+
if st.npc_filter:
|
|
115
|
+
conds.append("npc = :npc")
|
|
116
|
+
params['npc'] = st.npc_filter
|
|
117
|
+
if st.team_filter:
|
|
118
|
+
conds.append("team = :team")
|
|
119
|
+
params['team'] = st.team_filter
|
|
120
|
+
|
|
121
|
+
if conds:
|
|
122
|
+
q += " WHERE " + " AND ".join(conds)
|
|
123
|
+
q += " ORDER BY created_at DESC LIMIT 200"
|
|
124
|
+
|
|
125
|
+
result = conn.execute(text(q), params)
|
|
126
|
+
for row in result:
|
|
127
|
+
st.memories.append({
|
|
128
|
+
'id': row[0],
|
|
129
|
+
'created_at': row[1],
|
|
130
|
+
'npc': row[2],
|
|
131
|
+
'team': row[3],
|
|
132
|
+
'scope': row[4] or '',
|
|
133
|
+
'original': row[5],
|
|
134
|
+
'final': row[6],
|
|
135
|
+
'status': row[7]
|
|
136
|
+
})
|
|
137
|
+
except Exception as e:
|
|
138
|
+
st.msg = "DB Error: " + str(e)
|
|
139
|
+
st.msg_color = "31"
|
|
140
|
+
|
|
141
|
+
def do_update(memory_id, new_status, final_mem=None):
|
|
142
|
+
"""Update memory status in DB and reload list."""
|
|
143
|
+
try:
|
|
144
|
+
command_history.update_memory_status(memory_id, new_status, final_mem)
|
|
145
|
+
old_count = len(st.memories)
|
|
146
|
+
old_sel = st.sel
|
|
147
|
+
load_memories()
|
|
148
|
+
new_count = len(st.memories)
|
|
149
|
+
|
|
150
|
+
# Clamp selection
|
|
151
|
+
if st.memories:
|
|
152
|
+
st.sel = min(old_sel, len(st.memories) - 1)
|
|
153
|
+
else:
|
|
154
|
+
st.sel = 0
|
|
155
|
+
|
|
156
|
+
# Fix scroll
|
|
157
|
+
_, height = get_size()
|
|
158
|
+
vis = max(1, height - 7)
|
|
159
|
+
if st.sel < st.scroll:
|
|
160
|
+
st.scroll = st.sel
|
|
161
|
+
elif st.sel >= st.scroll + vis:
|
|
162
|
+
st.scroll = st.sel - vis + 1
|
|
163
|
+
|
|
164
|
+
return True
|
|
165
|
+
except Exception as e:
|
|
166
|
+
st.msg = "UPDATE FAILED: " + str(e)
|
|
167
|
+
st.msg_color = "31"
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
def format_date(dt_str):
|
|
171
|
+
if not dt_str:
|
|
172
|
+
return " "
|
|
173
|
+
try:
|
|
174
|
+
if isinstance(dt_str, str):
|
|
175
|
+
dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
|
|
176
|
+
else:
|
|
177
|
+
dt = dt_str
|
|
178
|
+
return dt.strftime('%m-%d %H:%M')
|
|
179
|
+
except:
|
|
180
|
+
return str(dt_str)[:10]
|
|
181
|
+
|
|
182
|
+
# ========== Rendering ==========
|
|
183
|
+
def render():
|
|
184
|
+
width, height = get_size()
|
|
185
|
+
out = []
|
|
186
|
+
|
|
187
|
+
# Header with session stats
|
|
188
|
+
stats = ""
|
|
189
|
+
if st.approved_count or st.rejected_count:
|
|
190
|
+
stats = " [+" + str(st.approved_count) + " -" + str(st.rejected_count) + "]"
|
|
191
|
+
header = " MEMORIES (" + str(len(st.memories)) + ")" + stats + " "
|
|
192
|
+
out.append("\033[1;1H\033[K\033[7;1m" + header.ljust(width) + "\033[0m")
|
|
193
|
+
|
|
194
|
+
# Tabs
|
|
195
|
+
tab_str = ""
|
|
196
|
+
for i, tab in enumerate(st.tabs):
|
|
197
|
+
if i == st.tab:
|
|
198
|
+
tab_str += "\033[1;7m [" + tab + "] \033[0m"
|
|
199
|
+
else:
|
|
200
|
+
tab_str += " \033[90m" + tab + "\033[0m "
|
|
201
|
+
out.append("\033[2;1H\033[K " + tab_str)
|
|
202
|
+
|
|
203
|
+
# Separator
|
|
204
|
+
out.append("\033[3;1H\033[K\033[90m" + ("-" * width) + "\033[0m")
|
|
205
|
+
|
|
206
|
+
if st.preview and st.memories:
|
|
207
|
+
render_preview(out, width, height)
|
|
208
|
+
else:
|
|
209
|
+
render_list(out, width, height)
|
|
210
|
+
|
|
211
|
+
# Status bar
|
|
212
|
+
out.append("\033[" + str(height-2) + ";1H\033[K\033[90m" + ("-" * width) + "\033[0m")
|
|
213
|
+
out.append("\033[" + str(height-1) + ";1H\033[K")
|
|
214
|
+
if st.msg:
|
|
215
|
+
out.append(" \033[" + st.msg_color + ";1m" + st.msg[:width-2] + "\033[0m")
|
|
216
|
+
|
|
217
|
+
# Footer
|
|
218
|
+
if st.preview:
|
|
219
|
+
foot = " [Esc] Back [a] Approve [x] Reject [j/k] Prev/Next [q] Quit "
|
|
220
|
+
else:
|
|
221
|
+
foot = " [Tab] Filter [j/k] Nav [Enter] Preview [a] Approve [x] Reject [q] Quit "
|
|
222
|
+
out.append("\033[" + str(height) + ";1H\033[K\033[7m" + foot.ljust(width) + "\033[0m")
|
|
223
|
+
|
|
224
|
+
sys.stdout.write(''.join(out))
|
|
225
|
+
sys.stdout.flush()
|
|
226
|
+
|
|
227
|
+
def render_list(out, width, height):
|
|
228
|
+
vis_h = height - 7
|
|
229
|
+
if vis_h < 1:
|
|
230
|
+
vis_h = 1
|
|
231
|
+
|
|
232
|
+
for i in range(vis_h):
|
|
233
|
+
row = 4 + i
|
|
234
|
+
out.append("\033[" + str(row) + ";1H\033[K")
|
|
235
|
+
idx = st.scroll + i
|
|
236
|
+
if idx >= len(st.memories):
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
mem = st.memories[idx]
|
|
240
|
+
icon = status_icon(mem['status'])
|
|
241
|
+
date_str = format_date(mem['created_at'])
|
|
242
|
+
npc_str = (mem['npc'] or '-')[:8]
|
|
243
|
+
content = (mem['final'] or mem['original'] or '')[:width-35].replace('\n', ' ')
|
|
244
|
+
|
|
245
|
+
line = icon + " " + date_str + " " + npc_str.ljust(9) + content
|
|
246
|
+
|
|
247
|
+
if idx == st.sel:
|
|
248
|
+
out.append("\033[7m " + line + " \033[0m")
|
|
249
|
+
else:
|
|
250
|
+
out.append(" " + line)
|
|
251
|
+
|
|
252
|
+
# Scroll indicator
|
|
253
|
+
if len(st.memories) > vis_h and vis_h > 0:
|
|
254
|
+
total = max(1, len(st.memories) - vis_h)
|
|
255
|
+
pct = int((st.scroll / total) * 100) if total > 0 else 0
|
|
256
|
+
out.append("\033[4;" + str(width-6) + "H\033[90m[" + str(pct) + "%]\033[0m")
|
|
257
|
+
|
|
258
|
+
if not st.memories:
|
|
259
|
+
out.append("\033[6;4H\033[90mNo memories found for this filter.\033[0m")
|
|
260
|
+
out.append("\033[7;4H\033[90mTry switching tabs with Tab key.\033[0m")
|
|
261
|
+
|
|
262
|
+
def render_preview(out, width, height):
|
|
263
|
+
if not st.memories or st.sel >= len(st.memories):
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
mem = st.memories[st.sel]
|
|
267
|
+
|
|
268
|
+
row = 4
|
|
269
|
+
sc = status_color(mem['status'])
|
|
270
|
+
out.append("\033[" + str(row) + ";1H\033[K\033[1m Memory #" + str(mem['id']) + " \033[" + sc + "m[" + str(mem['status']) + "]\033[0m")
|
|
271
|
+
row += 1
|
|
272
|
+
|
|
273
|
+
out.append("\033[" + str(row) + ";1H\033[K\033[90m Date: " + format_date(mem['created_at']) + " NPC: " + str(mem['npc'] or '-') + " Team: " + str(mem['team'] or '-') + "\033[0m")
|
|
274
|
+
row += 1
|
|
275
|
+
out.append("\033[" + str(row) + ";1H\033[K\033[90m Scope: " + str(mem['scope'] or '-')[:60] + "\033[0m")
|
|
276
|
+
row += 1
|
|
277
|
+
out.append("\033[" + str(row) + ";1H\033[K")
|
|
278
|
+
row += 1
|
|
279
|
+
|
|
280
|
+
out.append("\033[" + str(row) + ";1H\033[K\033[1m Content:\033[0m")
|
|
281
|
+
row += 1
|
|
282
|
+
|
|
283
|
+
content = mem['final'] or mem['original'] or '(empty)'
|
|
284
|
+
for line in content.split('\n')[:height - row - 3]:
|
|
285
|
+
out.append("\033[" + str(row) + ";1H\033[K " + line[:width-5])
|
|
286
|
+
row += 1
|
|
287
|
+
|
|
288
|
+
if mem['final'] and mem['original'] and mem['final'] != mem['original']:
|
|
289
|
+
out.append("\033[" + str(row) + ";1H\033[K")
|
|
290
|
+
row += 1
|
|
291
|
+
out.append("\033[" + str(row) + ";1H\033[K\033[90;1m Original:\033[0m")
|
|
292
|
+
row += 1
|
|
293
|
+
for line in mem['original'].split('\n')[:3]:
|
|
294
|
+
if row >= height - 3:
|
|
295
|
+
break
|
|
296
|
+
out.append("\033[" + str(row) + ";1H\033[K\033[90m " + line[:width-5] + "\033[0m")
|
|
297
|
+
row += 1
|
|
298
|
+
|
|
299
|
+
while row < height - 2:
|
|
300
|
+
out.append("\033[" + str(row) + ";1H\033[K")
|
|
301
|
+
row += 1
|
|
302
|
+
|
|
303
|
+
# ========== Input ==========
|
|
304
|
+
def handle_input(c):
|
|
305
|
+
if c == 'q' or c == '\x03':
|
|
306
|
+
return False
|
|
307
|
+
|
|
308
|
+
if c == '\x1b':
|
|
309
|
+
if select.select([fd], [], [], 0.05)[0]:
|
|
310
|
+
c2 = os.read(fd, 1).decode('latin-1')
|
|
311
|
+
if c2 == '[':
|
|
312
|
+
c3 = os.read(fd, 1).decode('latin-1')
|
|
313
|
+
if c3 == 'A':
|
|
314
|
+
move_up()
|
|
315
|
+
elif c3 == 'B':
|
|
316
|
+
move_down()
|
|
317
|
+
elif c3 == 'Z':
|
|
318
|
+
# Shift+Tab = prev tab
|
|
319
|
+
st.tab = (st.tab - 1) % len(st.tabs)
|
|
320
|
+
st.sel = 0
|
|
321
|
+
st.scroll = 0
|
|
322
|
+
load_memories()
|
|
323
|
+
st.msg = ""
|
|
324
|
+
else:
|
|
325
|
+
if st.preview:
|
|
326
|
+
st.preview = False
|
|
327
|
+
return True
|
|
328
|
+
|
|
329
|
+
if c == '\t':
|
|
330
|
+
st.tab = (st.tab + 1) % len(st.tabs)
|
|
331
|
+
st.sel = 0
|
|
332
|
+
st.scroll = 0
|
|
333
|
+
load_memories()
|
|
334
|
+
st.msg = ""
|
|
335
|
+
st.msg_color = "33"
|
|
336
|
+
elif c == 'k':
|
|
337
|
+
move_up()
|
|
338
|
+
elif c == 'j':
|
|
339
|
+
move_down()
|
|
340
|
+
elif c == '\r' or c == '\n' or c == 'p':
|
|
341
|
+
if st.memories:
|
|
342
|
+
st.preview = not st.preview
|
|
343
|
+
elif c == 'a':
|
|
344
|
+
approve_current()
|
|
345
|
+
elif c == 'x':
|
|
346
|
+
reject_current()
|
|
347
|
+
|
|
348
|
+
return True
|
|
349
|
+
|
|
350
|
+
def move_up():
|
|
351
|
+
st.sel = max(0, st.sel - 1)
|
|
352
|
+
if st.sel < st.scroll:
|
|
353
|
+
st.scroll = st.sel
|
|
354
|
+
st.msg = ""
|
|
355
|
+
|
|
356
|
+
def move_down():
|
|
357
|
+
_, height = get_size()
|
|
358
|
+
vis = max(1, height - 7)
|
|
359
|
+
st.sel = min(max(0, len(st.memories) - 1), st.sel + 1)
|
|
360
|
+
if st.sel >= st.scroll + vis:
|
|
361
|
+
st.scroll = st.sel - vis + 1
|
|
362
|
+
st.msg = ""
|
|
363
|
+
|
|
364
|
+
def approve_current():
|
|
365
|
+
if not st.memories or st.sel >= len(st.memories):
|
|
366
|
+
st.msg = "No memory selected"
|
|
367
|
+
st.msg_color = "33"
|
|
368
|
+
return
|
|
369
|
+
mem = st.memories[st.sel]
|
|
370
|
+
final = mem.get('final') or mem.get('original')
|
|
371
|
+
if do_update(mem['id'], 'human-approved', final):
|
|
372
|
+
st.approved_count += 1
|
|
373
|
+
st.msg = "APPROVED #" + str(mem['id'])
|
|
374
|
+
st.msg_color = "32"
|
|
375
|
+
|
|
376
|
+
def reject_current():
|
|
377
|
+
if not st.memories or st.sel >= len(st.memories):
|
|
378
|
+
st.msg = "No memory selected"
|
|
379
|
+
st.msg_color = "33"
|
|
380
|
+
return
|
|
381
|
+
mem = st.memories[st.sel]
|
|
382
|
+
if do_update(mem['id'], 'human-rejected'):
|
|
383
|
+
st.rejected_count += 1
|
|
384
|
+
st.msg = "REJECTED #" + str(mem['id'])
|
|
385
|
+
st.msg_color = "31"
|
|
386
|
+
|
|
387
|
+
# ========== Main Loop ==========
|
|
388
|
+
load_memories()
|
|
389
|
+
|
|
390
|
+
fd = sys.stdin.fileno()
|
|
391
|
+
old_settings = termios.tcgetattr(fd)
|
|
392
|
+
|
|
393
|
+
try:
|
|
394
|
+
tty.setcbreak(fd)
|
|
395
|
+
sys.stdout.write('\033[?25l')
|
|
396
|
+
sys.stdout.write('\033[2J')
|
|
397
|
+
render()
|
|
398
|
+
|
|
399
|
+
while True:
|
|
400
|
+
c = os.read(fd, 1).decode('latin-1')
|
|
401
|
+
if not handle_input(c):
|
|
402
|
+
break
|
|
403
|
+
render()
|
|
404
|
+
|
|
405
|
+
finally:
|
|
406
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
407
|
+
sys.stdout.write('\033[?25h')
|
|
408
|
+
sys.stdout.write('\033[2J\033[H')
|
|
409
|
+
sys.stdout.flush()
|
|
410
|
+
|
|
411
|
+
summary = "Memory browser closed."
|
|
412
|
+
if st.approved_count or st.rejected_count:
|
|
413
|
+
summary += " Session: " + str(st.approved_count) + " approved, " + str(st.rejected_count) + " rejected."
|
|
414
|
+
context['output'] = summary
|
|
@@ -65,8 +65,8 @@ steps:
|
|
|
65
65
|
sys.stdout.flush()
|
|
66
66
|
time.sleep(0.1)
|
|
67
67
|
# Flush any pending input
|
|
68
|
-
while select.select([
|
|
69
|
-
|
|
68
|
+
while select.select([fd], [], [], 0)[0]:
|
|
69
|
+
os.read(fd, 1).decode('latin-1')
|
|
70
70
|
except:
|
|
71
71
|
pass
|
|
72
72
|
|
|
@@ -146,8 +146,8 @@ steps:
|
|
|
146
146
|
out.append("\033[2J\033[H")
|
|
147
147
|
|
|
148
148
|
header = f" NQL: {os.path.basename(db_path)} "
|
|
149
|
-
out.append(f"\033[1;1H\033[
|
|
150
|
-
out.append(f"\033[1;{(width - len(header)) // 2}H\033[
|
|
149
|
+
out.append(f"\033[1;1H\033[7;1m{'=' * width}\033[0m")
|
|
150
|
+
out.append(f"\033[1;{(width - len(header)) // 2}H\033[7;1m{header}\033[0m")
|
|
151
151
|
|
|
152
152
|
modes = ['Tables', 'Query']
|
|
153
153
|
tab_str = ""
|
|
@@ -264,10 +264,10 @@ steps:
|
|
|
264
264
|
|
|
265
265
|
def handle_input(c):
|
|
266
266
|
if c == '\x1b':
|
|
267
|
-
if select.select([
|
|
268
|
-
c2 =
|
|
267
|
+
if select.select([fd], [], [], 0.1)[0]:
|
|
268
|
+
c2 = os.read(fd, 1).decode('latin-1')
|
|
269
269
|
if c2 == '[':
|
|
270
|
-
c3 =
|
|
270
|
+
c3 = os.read(fd, 1).decode('latin-1')
|
|
271
271
|
if c3 == 'A':
|
|
272
272
|
if db_state.mode == 'tables':
|
|
273
273
|
move_up()
|
|
@@ -324,19 +324,8 @@ steps:
|
|
|
324
324
|
return True
|
|
325
325
|
|
|
326
326
|
def handle_query_input(c):
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return True
|
|
330
|
-
if c == 'j':
|
|
331
|
-
db_state.row_scroll = min(max(0, len(db_state.rows) - 1), db_state.row_scroll + 1)
|
|
332
|
-
return True
|
|
333
|
-
if c == 'h':
|
|
334
|
-
db_state.col_scroll = max(0, db_state.col_scroll - 1)
|
|
335
|
-
return True
|
|
336
|
-
if c == 'l':
|
|
337
|
-
db_state.col_scroll = min(max(0, len(db_state.columns) - 1), db_state.col_scroll + 1)
|
|
338
|
-
return True
|
|
339
|
-
|
|
327
|
+
# In query mode, all printable chars go to the buffer.
|
|
328
|
+
# Use arrow keys (handled in escape sequence block) for result navigation.
|
|
340
329
|
if c == '\r' or c == '\n':
|
|
341
330
|
if db_state.query_buffer.strip():
|
|
342
331
|
run_query(db_state.query_buffer)
|
|
@@ -375,7 +364,7 @@ steps:
|
|
|
375
364
|
render_screen()
|
|
376
365
|
|
|
377
366
|
while True:
|
|
378
|
-
c =
|
|
367
|
+
c = os.read(fd, 1).decode('latin-1')
|
|
379
368
|
if not handle_input(c):
|
|
380
369
|
break
|
|
381
370
|
render_screen()
|