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
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
jinx_name: web_search
|
|
2
|
+
description: Search the web with interactive TUI
|
|
3
|
+
inputs:
|
|
4
|
+
- query: ""
|
|
5
|
+
- provider: ""
|
|
6
|
+
- num_results: "10"
|
|
7
|
+
- text: "false"
|
|
8
|
+
|
|
9
|
+
steps:
|
|
10
|
+
- name: search_web
|
|
11
|
+
engine: python
|
|
12
|
+
code: |
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import tty
|
|
16
|
+
import termios
|
|
17
|
+
import webbrowser
|
|
18
|
+
import subprocess
|
|
19
|
+
from npcpy.data.web import search_web
|
|
20
|
+
|
|
21
|
+
query = context.get('query', '').strip()
|
|
22
|
+
text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
|
|
23
|
+
|
|
24
|
+
if not query:
|
|
25
|
+
lines = [
|
|
26
|
+
"Usage: /web_search <query>",
|
|
27
|
+
"",
|
|
28
|
+
"Options:",
|
|
29
|
+
" provider - Search provider (default uses state.search_provider)",
|
|
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",
|
|
40
|
+
"",
|
|
41
|
+
"Examples:",
|
|
42
|
+
" /web_search python asyncio tutorial",
|
|
43
|
+
" /web_search react hooks num_results=20",
|
|
44
|
+
]
|
|
45
|
+
context['output'] = "\n".join(lines)
|
|
46
|
+
else:
|
|
47
|
+
provider = context.get('provider') or None
|
|
48
|
+
try:
|
|
49
|
+
provider = provider or (state.search_provider if 'state' in dir() and state else None)
|
|
50
|
+
except:
|
|
51
|
+
pass
|
|
52
|
+
num_results = int(context.get('num_results') or 10)
|
|
53
|
+
|
|
54
|
+
try:
|
|
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:
|
|
61
|
+
if isinstance(res, dict):
|
|
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
|
+
})
|
|
67
|
+
else:
|
|
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("")
|
|
81
|
+
context['output'] = "\n".join(lines)
|
|
82
|
+
else:
|
|
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
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
import traceback
|
|
283
|
+
context['output'] = "Web search error: " + str(e) + "\n" + traceback.format_exc()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
jinx_name: yap
|
|
2
2
|
description: Voice chat mode - speech-to-text input, text-to-speech output
|
|
3
3
|
inputs:
|
|
4
|
-
- model: null
|
|
5
|
-
- provider: null
|
|
6
|
-
- tts_model: kokoro
|
|
7
|
-
- voice: af_heart
|
|
8
|
-
- files: null
|
|
4
|
+
- model: null
|
|
5
|
+
- provider: null
|
|
6
|
+
- tts_model: kokoro
|
|
7
|
+
- voice: af_heart
|
|
8
|
+
- files: null
|
|
9
9
|
|
|
10
10
|
steps:
|
|
11
11
|
- name: yap_repl
|
|
@@ -49,8 +49,14 @@ steps:
|
|
|
49
49
|
tts_model = context.get('tts_model', 'kokoro')
|
|
50
50
|
voice = context.get('voice', 'af_heart')
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
# Resolve npc if it's a string (npc name) rather than NPC object
|
|
53
|
+
if isinstance(npc, str) and team:
|
|
54
|
+
npc = team.get(npc) if hasattr(team, 'get') else None
|
|
55
|
+
elif isinstance(npc, str):
|
|
56
|
+
npc = None
|
|
57
|
+
|
|
58
|
+
model = context.get('model') or (npc.model if npc and hasattr(npc, 'model') else None)
|
|
59
|
+
provider = context.get('provider') or (npc.provider if npc and hasattr(npc, 'provider') else None)
|
|
54
60
|
|
|
55
61
|
print("""
|
|
56
62
|
██╗ ██╗ █████╗ ██████╗
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcsh
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.18
|
|
4
4
|
Summary: npcsh is a command-line toolkit for using AI agents in novel ways.
|
|
5
5
|
Home-page: https://github.com/NPC-Worldwide/npcsh
|
|
6
6
|
Author: Christopher Agostino
|
|
@@ -596,6 +596,9 @@ npc vixynt "a sunset over mountains"
|
|
|
596
596
|
| `/teamviz` | Visualize team structure. Usage: `/teamviz save=output.png` |
|
|
597
597
|
| `/ots` | Screenshot analysis. Usage: `/ots` then select area |
|
|
598
598
|
| `/sleep` | Evolve knowledge graph. Usage: `/sleep --ops link_facts,deepen` |
|
|
599
|
+
| `/kg_search` | Search knowledge graph with multiple modes. Usage: `/kg_search query mode=hybrid depth=2` |
|
|
600
|
+
| `/mem_search` | Search approved memories. Usage: `/mem_search query status=approved top_k=10` |
|
|
601
|
+
| `/mem_review` | Review pending memories interactively. Usage: `/mem_review limit=50` |
|
|
599
602
|
|
|
600
603
|
### System & Config
|
|
601
604
|
| Command | Description |
|
|
@@ -626,6 +629,92 @@ npc vixynt "a sunset over mountains"
|
|
|
626
629
|
--format (-f) | --num_frames (-num_f) | --sprovider (-s) |
|
|
627
630
|
```
|
|
628
631
|
|
|
632
|
+
## Memory & Knowledge Graph
|
|
633
|
+
|
|
634
|
+
`npcsh` maintains a memory lifecycle system that allows agents to learn and grow from past interactions. Memories progress through stages and can be incorporated into a knowledge graph for advanced retrieval.
|
|
635
|
+
|
|
636
|
+
### Memory Lifecycle
|
|
637
|
+
|
|
638
|
+
Memories are extracted from conversations and follow this lifecycle:
|
|
639
|
+
|
|
640
|
+
1. **pending_approval** - New memories awaiting review
|
|
641
|
+
2. **human-approved** - Approved and ready for KG integration
|
|
642
|
+
3. **human-rejected** - Rejected (used as negative examples)
|
|
643
|
+
4. **human-edited** - Modified by user before approval
|
|
644
|
+
5. **skipped** - Deferred for later review
|
|
645
|
+
|
|
646
|
+
### Memory Commands
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
# Search through approved memories
|
|
650
|
+
/mem_search python # Keyword search
|
|
651
|
+
/mem_search python status=approved # Filter by status
|
|
652
|
+
/mem_search python top_k=20 # Limit results
|
|
653
|
+
|
|
654
|
+
# Review pending memories interactively
|
|
655
|
+
/mem_review # Review with default limit
|
|
656
|
+
/mem_review limit=50 # Review more at once
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
### Knowledge Graph
|
|
660
|
+
|
|
661
|
+
The knowledge graph stores facts and concepts extracted from approved memories, enabling semantic search and reasoning. Facts are linked to concepts, allowing traversal-based discovery.
|
|
662
|
+
|
|
663
|
+
```bash
|
|
664
|
+
# Keyword search
|
|
665
|
+
/kg_search python # Simple keyword match
|
|
666
|
+
|
|
667
|
+
# Semantic similarity search
|
|
668
|
+
/kg_search python mode=embedding # Find semantically similar facts
|
|
669
|
+
|
|
670
|
+
# Graph traversal search
|
|
671
|
+
/kg_search python mode=link depth=3 # Traverse graph links
|
|
672
|
+
|
|
673
|
+
# Hybrid search (combines methods)
|
|
674
|
+
/kg_search python mode=all # All methods combined
|
|
675
|
+
|
|
676
|
+
# Explore concepts
|
|
677
|
+
/kg_search type=concepts # List all concepts
|
|
678
|
+
/kg_search concept="Machine Learning" # Explore a specific concept
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Knowledge Graph Evolution
|
|
682
|
+
|
|
683
|
+
The `/sleep` command evolves the knowledge graph through consolidation, abstraction, and creative synthesis:
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
# Basic sleep (consolidation)
|
|
687
|
+
/sleep
|
|
688
|
+
|
|
689
|
+
# Import approved memories first, then evolve
|
|
690
|
+
/sleep backfill=true
|
|
691
|
+
|
|
692
|
+
# Dream mode - creative synthesis across domains
|
|
693
|
+
/sleep dream=true
|
|
694
|
+
|
|
695
|
+
# Combined backfill and dream
|
|
696
|
+
/sleep backfill=true dream=true
|
|
697
|
+
|
|
698
|
+
# Specific operations
|
|
699
|
+
/sleep ops=prune,deepen,abstract
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
**Operations:**
|
|
703
|
+
- **prune** - Remove redundant or low-value facts
|
|
704
|
+
- **deepen** - Add detail to existing facts
|
|
705
|
+
- **abstract** - Create higher-level generalizations
|
|
706
|
+
- **link** - Connect related facts and concepts
|
|
707
|
+
|
|
708
|
+
### Environment Variables
|
|
709
|
+
|
|
710
|
+
```bash
|
|
711
|
+
# Enable/disable automatic KG building (default: enabled)
|
|
712
|
+
export NPCSH_BUILD_KG=1
|
|
713
|
+
|
|
714
|
+
# Database path
|
|
715
|
+
export NPCSH_DB_PATH=~/npcsh_history.db
|
|
716
|
+
```
|
|
717
|
+
|
|
629
718
|
## Read the Docs
|
|
630
719
|
To see more about how to use the jinxs and modes in the NPC Shell, read the docs at [npc-shell.readthedocs.io](https://npc-shell.readthedocs.io/en/latest/)
|
|
631
720
|
|