npcsh 1.1.17__py3-none-any.whl → 1.1.18__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 +114 -91
- npcsh/alicanto.py +2 -2
- npcsh/benchmark/__init__.py +8 -2
- npcsh/benchmark/npcsh_agent.py +46 -12
- 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 +1 -3
- npcsh/conversation_viewer.py +389 -0
- npcsh/corca.py +0 -1
- 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/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 +544 -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.18.data/data/npcsh/npc_team/alicanto.jinx +356 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/arxiv.jinx +720 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/benchmark.jinx +1 -1
- npcsh-1.1.18.data/data/npcsh/npc_team/corca.jinx +430 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/db_search.jinx +348 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/file_search.jinx +339 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/guac.jinx +544 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/jinxs.jinx +331 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/kg_search.jinx +418 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/mem_review.jinx +73 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/mem_search.jinx +388 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/paper_search.jinx +412 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/plonk.jinx +379 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/pti.jinx +357 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/reattach.jinx +291 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/semantic_scholar.jinx +386 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sleep.jinx +22 -11
- npcsh-1.1.18.data/data/npcsh/npc_team/spool.jinx +350 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/sql.jinx +20 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/wander.jinx +455 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/web_search.jinx +283 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.jinx +13 -7
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/METADATA +90 -1
- npcsh-1.1.18.dist-info/RECORD +235 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/WHEEL +1 -1
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/entry_points.txt +0 -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/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.18.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/click.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/confirm.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/convene.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/delegate.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/incognide.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/key_press.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/navigate.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/notify.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/nql.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/paste.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/search.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/send_message.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sh.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/shh.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switches.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sync.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/type_text.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/verbose.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/wait.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/write_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
jinx_name: web_search
|
|
2
|
-
description: Search the web
|
|
2
|
+
description: Search the web with interactive TUI
|
|
3
3
|
inputs:
|
|
4
4
|
- query: ""
|
|
5
5
|
- provider: ""
|
|
6
6
|
- num_results: "10"
|
|
7
|
+
- text: "false"
|
|
7
8
|
|
|
8
9
|
steps:
|
|
9
10
|
- name: search_web
|
|
10
11
|
engine: python
|
|
11
12
|
code: |
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import tty
|
|
16
|
+
import termios
|
|
17
|
+
import webbrowser
|
|
18
|
+
import subprocess
|
|
12
19
|
from npcpy.data.web import search_web
|
|
13
20
|
|
|
14
21
|
query = context.get('query', '').strip()
|
|
22
|
+
text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
|
|
23
|
+
|
|
15
24
|
if not query:
|
|
16
25
|
lines = [
|
|
17
26
|
"Usage: /web_search <query>",
|
|
@@ -19,6 +28,15 @@ steps:
|
|
|
19
28
|
"Options:",
|
|
20
29
|
" provider - Search provider (default uses state.search_provider)",
|
|
21
30
|
" num_results - Number of results (default 10)",
|
|
31
|
+
" text - Text-only output, no TUI (true/false)",
|
|
32
|
+
"",
|
|
33
|
+
"TUI Controls:",
|
|
34
|
+
" j/k or arrows - Navigate",
|
|
35
|
+
" p - Preview page content",
|
|
36
|
+
" o - Open in browser",
|
|
37
|
+
" i - Open in incognide",
|
|
38
|
+
" c - Copy URL to clipboard",
|
|
39
|
+
" q/ESC - Quit",
|
|
22
40
|
"",
|
|
23
41
|
"Examples:",
|
|
24
42
|
" /web_search python asyncio tutorial",
|
|
@@ -32,20 +50,234 @@ steps:
|
|
|
32
50
|
except:
|
|
33
51
|
pass
|
|
34
52
|
num_results = int(context.get('num_results') or 10)
|
|
53
|
+
|
|
35
54
|
try:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
55
|
+
raw_results = search_web(query, provider=provider, num_results=num_results)
|
|
56
|
+
|
|
57
|
+
# Normalize results
|
|
58
|
+
results = []
|
|
59
|
+
if raw_results:
|
|
60
|
+
for res in raw_results:
|
|
40
61
|
if isinstance(res, dict):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
results.append({
|
|
63
|
+
'title': res.get('title', 'No title'),
|
|
64
|
+
'url': res.get('url', res.get('link', '')),
|
|
65
|
+
'snippet': res.get('snippet', res.get('description', ''))
|
|
66
|
+
})
|
|
44
67
|
else:
|
|
45
|
-
|
|
68
|
+
results.append({'title': str(res)[:50], 'url': str(res), 'snippet': ''})
|
|
69
|
+
|
|
70
|
+
if not results:
|
|
71
|
+
context['output'] = "No web results found."
|
|
72
|
+
elif text_mode:
|
|
73
|
+
# Text-only output
|
|
74
|
+
lines = []
|
|
75
|
+
for res in results:
|
|
76
|
+
lines.append(f"- {res['title']}")
|
|
77
|
+
lines.append(f" {res['url']}")
|
|
78
|
+
if res.get('snippet'):
|
|
79
|
+
lines.append(f" {res['snippet'][:100]}...")
|
|
80
|
+
lines.append("")
|
|
46
81
|
context['output'] = "\n".join(lines)
|
|
47
82
|
else:
|
|
48
|
-
|
|
83
|
+
# Interactive TUI mode
|
|
84
|
+
def get_terminal_size():
|
|
85
|
+
try:
|
|
86
|
+
size = os.get_terminal_size()
|
|
87
|
+
return size.columns, size.lines
|
|
88
|
+
except:
|
|
89
|
+
return 80, 24
|
|
90
|
+
|
|
91
|
+
def get_domain(url):
|
|
92
|
+
try:
|
|
93
|
+
from urllib.parse import urlparse
|
|
94
|
+
return urlparse(url).netloc[:25]
|
|
95
|
+
except:
|
|
96
|
+
return url[:25]
|
|
97
|
+
|
|
98
|
+
width, height = get_terminal_size()
|
|
99
|
+
selected = 0
|
|
100
|
+
scroll = 0
|
|
101
|
+
list_height = height - 5
|
|
102
|
+
mode = 'list'
|
|
103
|
+
preview_scroll = 0
|
|
104
|
+
preview_content = []
|
|
105
|
+
|
|
106
|
+
fd = sys.stdin.fileno()
|
|
107
|
+
old_settings = termios.tcgetattr(fd)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
tty.setcbreak(fd)
|
|
111
|
+
sys.stdout.write('\033[?25l')
|
|
112
|
+
sys.stdout.write('\033[2J\033[H')
|
|
113
|
+
|
|
114
|
+
while True:
|
|
115
|
+
width, height = get_terminal_size()
|
|
116
|
+
list_height = height - 5
|
|
117
|
+
|
|
118
|
+
if mode == 'list':
|
|
119
|
+
if selected < scroll:
|
|
120
|
+
scroll = selected
|
|
121
|
+
elif selected >= scroll + list_height:
|
|
122
|
+
scroll = selected - list_height + 1
|
|
123
|
+
|
|
124
|
+
sys.stdout.write('\033[H')
|
|
125
|
+
|
|
126
|
+
# Header
|
|
127
|
+
if mode == 'list':
|
|
128
|
+
header = f" WEB SEARCH ({len(results)} results): '{query}' "
|
|
129
|
+
else:
|
|
130
|
+
header = f" PREVIEW: {results[selected]['title'][:width-12]} "
|
|
131
|
+
sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
|
|
132
|
+
|
|
133
|
+
# Column headers
|
|
134
|
+
if mode == 'list':
|
|
135
|
+
col_header = f' {"#":<3} {"DOMAIN":<25} {"TITLE":<50}'
|
|
136
|
+
sys.stdout.write(f'\033[90m{col_header[:width]}\033[0m\n')
|
|
137
|
+
else:
|
|
138
|
+
sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
|
|
139
|
+
|
|
140
|
+
if mode == 'list':
|
|
141
|
+
for i in range(list_height):
|
|
142
|
+
idx = scroll + i
|
|
143
|
+
sys.stdout.write(f'\033[{3+i};1H\033[K')
|
|
144
|
+
if idx >= len(results):
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
r = results[idx]
|
|
148
|
+
num = str(idx + 1)
|
|
149
|
+
domain = get_domain(r['url'])
|
|
150
|
+
title = r['title'][:55].replace('\n', ' ')
|
|
151
|
+
|
|
152
|
+
line = f" {num:<3} {domain:<25} {title}"
|
|
153
|
+
line = line[:width-1]
|
|
154
|
+
|
|
155
|
+
if idx == selected:
|
|
156
|
+
sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
|
|
157
|
+
else:
|
|
158
|
+
sys.stdout.write(f' {line}')
|
|
159
|
+
|
|
160
|
+
# Status bar
|
|
161
|
+
sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
|
|
162
|
+
sel = results[selected] if results else {}
|
|
163
|
+
snippet = (sel.get('snippet') or '')[:width-2]
|
|
164
|
+
sys.stdout.write(f'\033[{height-1};1H\033[K {snippet}'.ljust(width))
|
|
165
|
+
sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav p:Preview o:Open i:Incog c:Copy q:Quit [{selected+1}/{len(results)}] \033[0m')
|
|
166
|
+
|
|
167
|
+
else: # preview mode
|
|
168
|
+
for i in range(list_height):
|
|
169
|
+
idx = preview_scroll + i
|
|
170
|
+
sys.stdout.write(f'\033[{3+i};1H\033[K')
|
|
171
|
+
if idx < len(preview_content):
|
|
172
|
+
sys.stdout.write(preview_content[idx][:width-1])
|
|
173
|
+
|
|
174
|
+
sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
|
|
175
|
+
sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(preview_content)} lines]')
|
|
176
|
+
sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back o:Open c:Copy q:Quit \033[0m')
|
|
177
|
+
|
|
178
|
+
sys.stdout.flush()
|
|
179
|
+
|
|
180
|
+
c = sys.stdin.read(1)
|
|
181
|
+
|
|
182
|
+
if c == '\x1b':
|
|
183
|
+
c2 = sys.stdin.read(1)
|
|
184
|
+
if c2 == '[':
|
|
185
|
+
c3 = sys.stdin.read(1)
|
|
186
|
+
if c3 == 'A':
|
|
187
|
+
if mode == 'list' and selected > 0:
|
|
188
|
+
selected -= 1
|
|
189
|
+
elif mode == 'preview' and preview_scroll > 0:
|
|
190
|
+
preview_scroll -= 1
|
|
191
|
+
elif c3 == 'B':
|
|
192
|
+
if mode == 'list' and selected < len(results) - 1:
|
|
193
|
+
selected += 1
|
|
194
|
+
elif mode == 'preview' and preview_scroll < max(0, len(preview_content) - list_height):
|
|
195
|
+
preview_scroll += 1
|
|
196
|
+
else:
|
|
197
|
+
if mode == 'preview':
|
|
198
|
+
mode = 'list'
|
|
199
|
+
sys.stdout.write('\033[2J\033[H')
|
|
200
|
+
else:
|
|
201
|
+
context['output'] = "Cancelled."
|
|
202
|
+
break
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
if c == 'q' or c == '\x03':
|
|
206
|
+
context['output'] = "Cancelled."
|
|
207
|
+
break
|
|
208
|
+
elif c == 'k':
|
|
209
|
+
if mode == 'list' and selected > 0:
|
|
210
|
+
selected -= 1
|
|
211
|
+
elif mode == 'preview' and preview_scroll > 0:
|
|
212
|
+
preview_scroll -= 1
|
|
213
|
+
elif c == 'j':
|
|
214
|
+
if mode == 'list' and selected < len(results) - 1:
|
|
215
|
+
selected += 1
|
|
216
|
+
elif mode == 'preview' and preview_scroll < max(0, len(preview_content) - list_height):
|
|
217
|
+
preview_scroll += 1
|
|
218
|
+
elif c == 'p' and mode == 'list' and results:
|
|
219
|
+
# Build preview from available info
|
|
220
|
+
sel = results[selected]
|
|
221
|
+
preview_content = [
|
|
222
|
+
f"Title: {sel['title']}",
|
|
223
|
+
"",
|
|
224
|
+
f"URL: {sel['url']}",
|
|
225
|
+
f"Domain: {get_domain(sel['url'])}",
|
|
226
|
+
"",
|
|
227
|
+
]
|
|
228
|
+
if sel.get('snippet'):
|
|
229
|
+
preview_content.append("Snippet:")
|
|
230
|
+
# Word wrap snippet
|
|
231
|
+
words = sel['snippet'].split()
|
|
232
|
+
line = ""
|
|
233
|
+
for w in words:
|
|
234
|
+
if len(line) + len(w) + 1 > width - 4:
|
|
235
|
+
preview_content.append(f" {line}")
|
|
236
|
+
line = w
|
|
237
|
+
else:
|
|
238
|
+
line = f"{line} {w}" if line else w
|
|
239
|
+
if line:
|
|
240
|
+
preview_content.append(f" {line}")
|
|
241
|
+
|
|
242
|
+
mode = 'preview'
|
|
243
|
+
preview_scroll = 0
|
|
244
|
+
sys.stdout.write('\033[2J\033[H')
|
|
245
|
+
elif c == 'b' and mode == 'preview':
|
|
246
|
+
mode = 'list'
|
|
247
|
+
sys.stdout.write('\033[2J\033[H')
|
|
248
|
+
elif c == 'o' and results:
|
|
249
|
+
url = results[selected].get('url', '')
|
|
250
|
+
if url:
|
|
251
|
+
webbrowser.open(url)
|
|
252
|
+
elif c == 'i' and results:
|
|
253
|
+
url = results[selected].get('url', '')
|
|
254
|
+
if url:
|
|
255
|
+
try:
|
|
256
|
+
subprocess.run(['npcsh', '-c', f'/navigate url={url}'], check=False, capture_output=True)
|
|
257
|
+
except:
|
|
258
|
+
webbrowser.open(url)
|
|
259
|
+
elif c == 'c' and results:
|
|
260
|
+
url = results[selected].get('url', '')
|
|
261
|
+
if url:
|
|
262
|
+
try:
|
|
263
|
+
# Try xclip or xsel
|
|
264
|
+
subprocess.run(['xclip', '-selection', 'clipboard'], input=url.encode(), check=True)
|
|
265
|
+
except:
|
|
266
|
+
try:
|
|
267
|
+
subprocess.run(['xsel', '--clipboard', '--input'], input=url.encode(), check=True)
|
|
268
|
+
except:
|
|
269
|
+
pass
|
|
270
|
+
elif c in ('\r', '\n') and results:
|
|
271
|
+
sel = results[selected]
|
|
272
|
+
context['output'] = f"Selected: {sel['title']}\nURL: {sel['url']}"
|
|
273
|
+
break
|
|
274
|
+
|
|
275
|
+
finally:
|
|
276
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
277
|
+
sys.stdout.write('\033[?25h')
|
|
278
|
+
sys.stdout.write('\033[2J\033[H')
|
|
279
|
+
sys.stdout.flush()
|
|
280
|
+
|
|
49
281
|
except Exception as e:
|
|
50
282
|
import traceback
|
|
51
283
|
context['output'] = "Web search error: " + str(e) + "\n" + traceback.format_exc()
|
|
@@ -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
|
|
|
@@ -8,9 +8,13 @@ steps:
|
|
|
8
8
|
engine: python
|
|
9
9
|
code: |
|
|
10
10
|
import pandas as pd
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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)
|