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.
Files changed (169) hide show
  1. npcsh/_state.py +114 -91
  2. npcsh/alicanto.py +2 -2
  3. npcsh/benchmark/__init__.py +8 -2
  4. npcsh/benchmark/npcsh_agent.py +46 -12
  5. npcsh/benchmark/runner.py +85 -43
  6. npcsh/benchmark/templates/install-npcsh.sh.j2 +35 -0
  7. npcsh/build.py +2 -4
  8. npcsh/completion.py +2 -6
  9. npcsh/config.py +1 -3
  10. npcsh/conversation_viewer.py +389 -0
  11. npcsh/corca.py +0 -1
  12. npcsh/execution.py +0 -1
  13. npcsh/guac.py +0 -1
  14. npcsh/mcp_helpers.py +2 -3
  15. npcsh/mcp_server.py +5 -10
  16. npcsh/npc.py +10 -11
  17. npcsh/npc_team/jinxs/bin/benchmark.jinx +1 -1
  18. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +321 -17
  19. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +312 -67
  20. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +366 -44
  21. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +73 -0
  22. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +328 -20
  23. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +242 -10
  24. npcsh/npc_team/jinxs/lib/core/sleep.jinx +22 -11
  25. npcsh/npc_team/jinxs/lib/core/sql.jinx +10 -6
  26. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +387 -76
  27. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +372 -55
  28. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +299 -144
  29. npcsh/npc_team/jinxs/modes/alicanto.jinx +356 -0
  30. npcsh/npc_team/jinxs/modes/arxiv.jinx +720 -0
  31. npcsh/npc_team/jinxs/modes/corca.jinx +430 -0
  32. npcsh/npc_team/jinxs/modes/guac.jinx +544 -0
  33. npcsh/npc_team/jinxs/modes/plonk.jinx +379 -0
  34. npcsh/npc_team/jinxs/modes/pti.jinx +357 -0
  35. npcsh/npc_team/jinxs/modes/reattach.jinx +291 -0
  36. npcsh/npc_team/jinxs/modes/spool.jinx +350 -0
  37. npcsh/npc_team/jinxs/modes/wander.jinx +455 -0
  38. npcsh/npc_team/jinxs/{bin → modes}/yap.jinx +13 -7
  39. npcsh/npcsh.py +7 -4
  40. npcsh/plonk.py +0 -1
  41. npcsh/pti.py +0 -1
  42. npcsh/routes.py +1 -3
  43. npcsh/spool.py +0 -1
  44. npcsh/ui.py +0 -1
  45. npcsh/wander.py +0 -1
  46. npcsh/yap.py +0 -1
  47. npcsh-1.1.18.data/data/npcsh/npc_team/alicanto.jinx +356 -0
  48. npcsh-1.1.18.data/data/npcsh/npc_team/arxiv.jinx +720 -0
  49. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/benchmark.jinx +1 -1
  50. npcsh-1.1.18.data/data/npcsh/npc_team/corca.jinx +430 -0
  51. npcsh-1.1.18.data/data/npcsh/npc_team/db_search.jinx +348 -0
  52. npcsh-1.1.18.data/data/npcsh/npc_team/file_search.jinx +339 -0
  53. npcsh-1.1.18.data/data/npcsh/npc_team/guac.jinx +544 -0
  54. npcsh-1.1.18.data/data/npcsh/npc_team/jinxs.jinx +331 -0
  55. npcsh-1.1.18.data/data/npcsh/npc_team/kg_search.jinx +418 -0
  56. npcsh-1.1.18.data/data/npcsh/npc_team/mem_review.jinx +73 -0
  57. npcsh-1.1.18.data/data/npcsh/npc_team/mem_search.jinx +388 -0
  58. npcsh-1.1.18.data/data/npcsh/npc_team/paper_search.jinx +412 -0
  59. npcsh-1.1.18.data/data/npcsh/npc_team/plonk.jinx +379 -0
  60. npcsh-1.1.18.data/data/npcsh/npc_team/pti.jinx +357 -0
  61. npcsh-1.1.18.data/data/npcsh/npc_team/reattach.jinx +291 -0
  62. npcsh-1.1.18.data/data/npcsh/npc_team/semantic_scholar.jinx +386 -0
  63. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sleep.jinx +22 -11
  64. npcsh-1.1.18.data/data/npcsh/npc_team/spool.jinx +350 -0
  65. npcsh-1.1.18.data/data/npcsh/npc_team/sql.jinx +20 -0
  66. npcsh-1.1.18.data/data/npcsh/npc_team/wander.jinx +455 -0
  67. npcsh-1.1.18.data/data/npcsh/npc_team/web_search.jinx +283 -0
  68. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.jinx +13 -7
  69. {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/METADATA +90 -1
  70. npcsh-1.1.18.dist-info/RECORD +235 -0
  71. {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/WHEEL +1 -1
  72. {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/entry_points.txt +0 -3
  73. npcsh/npc_team/jinxs/bin/spool.jinx +0 -161
  74. npcsh/npc_team/jinxs/bin/wander.jinx +0 -242
  75. npcsh/npc_team/jinxs/lib/research/arxiv.jinx +0 -76
  76. npcsh-1.1.17.data/data/npcsh/npc_team/arxiv.jinx +0 -76
  77. npcsh-1.1.17.data/data/npcsh/npc_team/db_search.jinx +0 -44
  78. npcsh-1.1.17.data/data/npcsh/npc_team/file_search.jinx +0 -94
  79. npcsh-1.1.17.data/data/npcsh/npc_team/jinxs.jinx +0 -176
  80. npcsh-1.1.17.data/data/npcsh/npc_team/kg_search.jinx +0 -96
  81. npcsh-1.1.17.data/data/npcsh/npc_team/mem_search.jinx +0 -80
  82. npcsh-1.1.17.data/data/npcsh/npc_team/paper_search.jinx +0 -101
  83. npcsh-1.1.17.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -69
  84. npcsh-1.1.17.data/data/npcsh/npc_team/spool.jinx +0 -161
  85. npcsh-1.1.17.data/data/npcsh/npc_team/sql.jinx +0 -16
  86. npcsh-1.1.17.data/data/npcsh/npc_team/wander.jinx +0 -242
  87. npcsh-1.1.17.data/data/npcsh/npc_team/web_search.jinx +0 -51
  88. npcsh-1.1.17.dist-info/RECORD +0 -219
  89. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  90. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  91. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.png +0 -0
  92. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  93. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  94. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/build.jinx +0 -0
  95. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/chat.jinx +0 -0
  96. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/click.jinx +0 -0
  97. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  98. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  99. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  100. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  101. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compile.jinx +0 -0
  102. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compress.jinx +0 -0
  103. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  104. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/convene.jinx +0 -0
  105. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.npc +0 -0
  106. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.png +0 -0
  107. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca_example.png +0 -0
  108. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  109. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  110. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  111. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic.npc +0 -0
  112. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic4.png +0 -0
  113. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.npc +0 -0
  114. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.png +0 -0
  115. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/help.jinx +0 -0
  116. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  117. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/init.jinx +0 -0
  118. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  119. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  120. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  121. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  122. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  123. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  124. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  125. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/notify.jinx +0 -0
  126. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  127. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  128. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/nql.jinx +0 -0
  129. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  130. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  131. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/ots.jinx +0 -0
  132. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/paste.jinx +0 -0
  133. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.npc +0 -0
  134. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.png +0 -0
  135. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  136. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  137. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/python.jinx +0 -0
  138. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  139. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/roll.jinx +0 -0
  140. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  141. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sample.jinx +0 -0
  142. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  143. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/search.jinx +0 -0
  144. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  145. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/serve.jinx +0 -0
  146. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/set.jinx +0 -0
  147. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sh.jinx +0 -0
  148. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/shh.jinx +0 -0
  149. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  150. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.png +0 -0
  151. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  152. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/spool.png +0 -0
  153. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch.jinx +0 -0
  154. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  155. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  156. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switches.jinx +0 -0
  157. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sync.jinx +0 -0
  158. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  159. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  160. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  161. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/usage.jinx +0 -0
  162. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  163. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  164. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/wait.jinx +0 -0
  165. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  166. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.png +0 -0
  167. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  168. {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/licenses/LICENSE +0 -0
  169. {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
- model = context.get('model') or (npc.model if npc else None)
53
- provider = context.get('provider') or (npc.provider if npc else None)
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.17
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