npcsh 1.1.16__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 (217) hide show
  1. npcsh/_state.py +138 -100
  2. npcsh/alicanto.py +2 -2
  3. npcsh/benchmark/__init__.py +28 -0
  4. npcsh/benchmark/npcsh_agent.py +296 -0
  5. npcsh/benchmark/runner.py +611 -0
  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 +146 -0
  18. npcsh/npc_team/jinxs/bin/nql.jinx +7 -7
  19. npcsh/npc_team/jinxs/bin/roll.jinx +20 -23
  20. npcsh/npc_team/jinxs/bin/sample.jinx +6 -7
  21. npcsh/npc_team/jinxs/bin/sync.jinx +6 -6
  22. npcsh/npc_team/jinxs/bin/vixynt.jinx +8 -8
  23. npcsh/npc_team/jinxs/incognide/add_tab.jinx +11 -0
  24. npcsh/npc_team/jinxs/incognide/close_pane.jinx +9 -0
  25. npcsh/npc_team/jinxs/incognide/close_tab.jinx +10 -0
  26. npcsh/npc_team/jinxs/incognide/confirm.jinx +10 -0
  27. npcsh/npc_team/jinxs/incognide/focus_pane.jinx +9 -0
  28. npcsh/npc_team/jinxs/{npc_studio/npc-studio.jinx → incognide/incognide.jinx} +2 -2
  29. npcsh/npc_team/jinxs/incognide/list_panes.jinx +8 -0
  30. npcsh/npc_team/jinxs/incognide/navigate.jinx +10 -0
  31. npcsh/npc_team/jinxs/incognide/notify.jinx +10 -0
  32. npcsh/npc_team/jinxs/incognide/open_pane.jinx +13 -0
  33. npcsh/npc_team/jinxs/incognide/read_pane.jinx +9 -0
  34. npcsh/npc_team/jinxs/incognide/run_terminal.jinx +10 -0
  35. npcsh/npc_team/jinxs/incognide/send_message.jinx +10 -0
  36. npcsh/npc_team/jinxs/incognide/split_pane.jinx +12 -0
  37. npcsh/npc_team/jinxs/incognide/switch_npc.jinx +10 -0
  38. npcsh/npc_team/jinxs/incognide/switch_tab.jinx +10 -0
  39. npcsh/npc_team/jinxs/incognide/write_file.jinx +11 -0
  40. npcsh/npc_team/jinxs/incognide/zen_mode.jinx +9 -0
  41. npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +4 -4
  42. npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +1 -1
  43. npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +2 -2
  44. npcsh/npc_team/jinxs/lib/computer_use/click.jinx +2 -2
  45. npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +1 -1
  46. npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +1 -1
  47. npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +1 -1
  48. npcsh/npc_team/jinxs/lib/computer_use/trigger.jinx +2 -2
  49. npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +1 -1
  50. npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +1 -1
  51. npcsh/npc_team/jinxs/lib/core/chat.jinx +4 -4
  52. npcsh/npc_team/jinxs/lib/core/cmd.jinx +4 -4
  53. npcsh/npc_team/jinxs/lib/core/compress.jinx +8 -8
  54. npcsh/npc_team/jinxs/lib/core/edit_file.jinx +3 -0
  55. npcsh/npc_team/jinxs/lib/core/ots.jinx +7 -7
  56. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +348 -0
  57. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +339 -0
  58. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +418 -0
  59. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +73 -0
  60. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +388 -0
  61. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +283 -0
  62. npcsh/npc_team/jinxs/lib/core/search.jinx +52 -129
  63. npcsh/npc_team/jinxs/lib/core/sh.jinx +1 -1
  64. npcsh/npc_team/jinxs/lib/core/sleep.jinx +29 -18
  65. npcsh/npc_team/jinxs/lib/core/sql.jinx +15 -11
  66. npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +7 -7
  67. npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +8 -9
  68. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +389 -78
  69. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +373 -56
  70. npcsh/npc_team/jinxs/lib/utils/build.jinx +5 -5
  71. npcsh/npc_team/jinxs/lib/utils/compile.jinx +2 -2
  72. npcsh/npc_team/jinxs/lib/utils/help.jinx +1 -1
  73. npcsh/npc_team/jinxs/lib/utils/init.jinx +5 -5
  74. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +300 -145
  75. npcsh/npc_team/jinxs/lib/utils/serve.jinx +2 -2
  76. npcsh/npc_team/jinxs/lib/utils/set.jinx +2 -2
  77. npcsh/npc_team/jinxs/lib/utils/switch.jinx +3 -3
  78. npcsh/npc_team/jinxs/lib/utils/switches.jinx +1 -1
  79. npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +2 -2
  80. npcsh/npc_team/jinxs/modes/alicanto.jinx +356 -0
  81. npcsh/npc_team/jinxs/modes/arxiv.jinx +720 -0
  82. npcsh/npc_team/jinxs/modes/corca.jinx +430 -0
  83. npcsh/npc_team/jinxs/modes/guac.jinx +544 -0
  84. npcsh/npc_team/jinxs/modes/plonk.jinx +379 -0
  85. npcsh/npc_team/jinxs/modes/pti.jinx +357 -0
  86. npcsh/npc_team/jinxs/modes/reattach.jinx +291 -0
  87. npcsh/npc_team/jinxs/modes/spool.jinx +350 -0
  88. npcsh/npc_team/jinxs/modes/wander.jinx +455 -0
  89. {npcsh-1.1.16.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/modes}/yap.jinx +8 -2
  90. npcsh/npc_team/sibiji.npc +1 -1
  91. npcsh/npcsh.py +87 -46
  92. npcsh/plonk.py +0 -1
  93. npcsh/pti.py +0 -1
  94. npcsh/routes.py +1 -3
  95. npcsh/spool.py +0 -1
  96. npcsh/ui.py +0 -1
  97. npcsh/wander.py +0 -1
  98. npcsh/yap.py +0 -1
  99. npcsh-1.1.18.data/data/npcsh/npc_team/add_tab.jinx +11 -0
  100. npcsh-1.1.18.data/data/npcsh/npc_team/alicanto.jinx +356 -0
  101. npcsh-1.1.18.data/data/npcsh/npc_team/arxiv.jinx +720 -0
  102. npcsh-1.1.18.data/data/npcsh/npc_team/benchmark.jinx +146 -0
  103. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_action.jinx +4 -4
  104. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_screenshot.jinx +1 -1
  105. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/build.jinx +5 -5
  106. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/chat.jinx +4 -4
  107. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/click.jinx +2 -2
  108. npcsh-1.1.18.data/data/npcsh/npc_team/close_pane.jinx +9 -0
  109. npcsh-1.1.18.data/data/npcsh/npc_team/close_tab.jinx +10 -0
  110. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/cmd.jinx +4 -4
  111. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compile.jinx +2 -2
  112. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compress.jinx +8 -8
  113. npcsh-1.1.18.data/data/npcsh/npc_team/confirm.jinx +10 -0
  114. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/convene.jinx +7 -7
  115. npcsh-1.1.18.data/data/npcsh/npc_team/corca.jinx +430 -0
  116. npcsh-1.1.18.data/data/npcsh/npc_team/db_search.jinx +348 -0
  117. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/delegate.jinx +8 -9
  118. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/edit_file.jinx +3 -0
  119. npcsh-1.1.18.data/data/npcsh/npc_team/file_search.jinx +339 -0
  120. npcsh-1.1.18.data/data/npcsh/npc_team/focus_pane.jinx +9 -0
  121. npcsh-1.1.18.data/data/npcsh/npc_team/guac.jinx +544 -0
  122. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/help.jinx +1 -1
  123. npcsh-1.1.16.data/data/npcsh/npc_team/npc-studio.jinx → npcsh-1.1.18.data/data/npcsh/npc_team/incognide.jinx +2 -2
  124. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/init.jinx +5 -5
  125. npcsh-1.1.18.data/data/npcsh/npc_team/jinxs.jinx +331 -0
  126. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/key_press.jinx +1 -1
  127. npcsh-1.1.18.data/data/npcsh/npc_team/kg_search.jinx +418 -0
  128. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/launch_app.jinx +1 -1
  129. npcsh-1.1.18.data/data/npcsh/npc_team/list_panes.jinx +8 -0
  130. npcsh-1.1.18.data/data/npcsh/npc_team/mem_review.jinx +73 -0
  131. npcsh-1.1.18.data/data/npcsh/npc_team/mem_search.jinx +388 -0
  132. npcsh-1.1.18.data/data/npcsh/npc_team/navigate.jinx +10 -0
  133. npcsh-1.1.18.data/data/npcsh/npc_team/notify.jinx +10 -0
  134. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/nql.jinx +7 -7
  135. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_browser.jinx +2 -2
  136. npcsh-1.1.18.data/data/npcsh/npc_team/open_pane.jinx +13 -0
  137. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/ots.jinx +7 -7
  138. npcsh-1.1.18.data/data/npcsh/npc_team/paper_search.jinx +412 -0
  139. npcsh-1.1.18.data/data/npcsh/npc_team/plonk.jinx +379 -0
  140. npcsh-1.1.18.data/data/npcsh/npc_team/pti.jinx +357 -0
  141. npcsh-1.1.18.data/data/npcsh/npc_team/read_pane.jinx +9 -0
  142. npcsh-1.1.18.data/data/npcsh/npc_team/reattach.jinx +291 -0
  143. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/roll.jinx +20 -23
  144. npcsh-1.1.18.data/data/npcsh/npc_team/run_terminal.jinx +10 -0
  145. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sample.jinx +6 -7
  146. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/screenshot.jinx +1 -1
  147. npcsh-1.1.18.data/data/npcsh/npc_team/search.jinx +54 -0
  148. npcsh-1.1.18.data/data/npcsh/npc_team/semantic_scholar.jinx +386 -0
  149. npcsh-1.1.18.data/data/npcsh/npc_team/send_message.jinx +10 -0
  150. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/serve.jinx +2 -2
  151. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/set.jinx +2 -2
  152. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sh.jinx +1 -1
  153. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.npc +1 -1
  154. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sleep.jinx +29 -18
  155. npcsh-1.1.18.data/data/npcsh/npc_team/split_pane.jinx +12 -0
  156. npcsh-1.1.18.data/data/npcsh/npc_team/spool.jinx +350 -0
  157. npcsh-1.1.18.data/data/npcsh/npc_team/sql.jinx +20 -0
  158. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch.jinx +3 -3
  159. npcsh-1.1.18.data/data/npcsh/npc_team/switch_npc.jinx +10 -0
  160. npcsh-1.1.18.data/data/npcsh/npc_team/switch_tab.jinx +10 -0
  161. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switches.jinx +1 -1
  162. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sync.jinx +6 -6
  163. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/teamviz.jinx +2 -2
  164. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/trigger.jinx +2 -2
  165. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/type_text.jinx +1 -1
  166. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/vixynt.jinx +8 -8
  167. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/wait.jinx +1 -1
  168. npcsh-1.1.18.data/data/npcsh/npc_team/wander.jinx +455 -0
  169. npcsh-1.1.18.data/data/npcsh/npc_team/web_search.jinx +283 -0
  170. npcsh-1.1.18.data/data/npcsh/npc_team/write_file.jinx +11 -0
  171. {npcsh/npc_team/jinxs/bin → npcsh-1.1.18.data/data/npcsh/npc_team}/yap.jinx +8 -2
  172. npcsh-1.1.18.data/data/npcsh/npc_team/zen_mode.jinx +9 -0
  173. {npcsh-1.1.16.dist-info → npcsh-1.1.18.dist-info}/METADATA +99 -7
  174. npcsh-1.1.18.dist-info/RECORD +235 -0
  175. {npcsh-1.1.16.dist-info → npcsh-1.1.18.dist-info}/WHEEL +1 -1
  176. {npcsh-1.1.16.dist-info → npcsh-1.1.18.dist-info}/entry_points.txt +2 -3
  177. npcsh/npc_team/jinxs/bin/spool.jinx +0 -161
  178. npcsh/npc_team/jinxs/bin/wander.jinx +0 -152
  179. npcsh/npc_team/jinxs/lib/research/arxiv.jinx +0 -76
  180. npcsh-1.1.16.data/data/npcsh/npc_team/arxiv.jinx +0 -76
  181. npcsh-1.1.16.data/data/npcsh/npc_team/jinxs.jinx +0 -176
  182. npcsh-1.1.16.data/data/npcsh/npc_team/paper_search.jinx +0 -101
  183. npcsh-1.1.16.data/data/npcsh/npc_team/search.jinx +0 -131
  184. npcsh-1.1.16.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -69
  185. npcsh-1.1.16.data/data/npcsh/npc_team/spool.jinx +0 -161
  186. npcsh-1.1.16.data/data/npcsh/npc_team/sql.jinx +0 -16
  187. npcsh-1.1.16.data/data/npcsh/npc_team/wander.jinx +0 -152
  188. npcsh-1.1.16.dist-info/RECORD +0 -170
  189. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  190. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.png +0 -0
  191. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  192. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.npc +0 -0
  193. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.png +0 -0
  194. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca_example.png +0 -0
  195. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic.npc +0 -0
  196. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic4.png +0 -0
  197. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.npc +0 -0
  198. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.png +0 -0
  199. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  200. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  201. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  202. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  203. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  204. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/paste.jinx +0 -0
  205. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.npc +0 -0
  206. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.png +0 -0
  207. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  208. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  209. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/python.jinx +0 -0
  210. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/shh.jinx +0 -0
  211. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.png +0 -0
  212. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/spool.png +0 -0
  213. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/usage.jinx +0 -0
  214. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  215. {npcsh-1.1.16.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.png +0 -0
  216. {npcsh-1.1.16.dist-info → npcsh-1.1.18.dist-info}/licenses/LICENSE +0 -0
  217. {npcsh-1.1.16.dist-info → npcsh-1.1.18.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,388 @@
1
+ jinx_name: mem_search
2
+ description: Search memories with interactive TUI and approval workflow
3
+ inputs:
4
+ - query: ""
5
+ - status: "all"
6
+ - npc_name: ""
7
+ - team_name: ""
8
+ - max_results: "20"
9
+ - db_path: ""
10
+ - text: "false"
11
+
12
+ steps:
13
+ - name: search_memories
14
+ engine: python
15
+ code: |
16
+ import os
17
+ import sys
18
+ import tty
19
+ import termios
20
+ from datetime import datetime
21
+ from npcpy.memory.command_history import CommandHistory
22
+ from npcpy.memory.memory_processor import get_relevant_memories
23
+
24
+ query = context.get('query', '').strip()
25
+ text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
26
+
27
+ if not query:
28
+ lines = [
29
+ "Usage: /mem_search <query> [status=all|approved|pending]",
30
+ "",
31
+ "Options:",
32
+ " status - Filter by status (all, approved, pending). Default is all",
33
+ " npc_name - Filter by NPC name",
34
+ " team_name - Filter by team name",
35
+ " max_results - Max results to return (default 20)",
36
+ " db_path - Path to history database",
37
+ " text - Text-only output, no TUI (true/false)",
38
+ "",
39
+ "TUI Controls:",
40
+ " j/k or arrows - Navigate",
41
+ " 1/2/3 - Sort by time/status/npc",
42
+ " f - Filter by status (all/approved/pending)",
43
+ " p - Preview full memory",
44
+ " a - Approve selected memory",
45
+ " x - Reject selected memory",
46
+ " q/ESC - Quit",
47
+ "",
48
+ "Examples:",
49
+ " /mem_search python",
50
+ " /mem_search debugging status=pending",
51
+ ]
52
+ context['output'] = "\n".join(lines)
53
+ else:
54
+ status_filter = context.get('status', 'all').lower()
55
+ npc_name = context.get('npc_name') or (npc.name if 'npc' in dir() and npc else None)
56
+ team_name = context.get('team_name') or None
57
+ try:
58
+ team_name = team_name or (state.team.name if 'state' in dir() and state and state.team else None)
59
+ except:
60
+ pass
61
+ max_results = int(context.get('max_results') or 20)
62
+ db_path = context.get('db_path') or os.path.expanduser("~/npcsh_history.db")
63
+ current_path = os.getcwd()
64
+
65
+ try:
66
+ cmd_history = CommandHistory(db_path)
67
+
68
+ if status_filter == 'approved':
69
+ state_obj = state if 'state' in dir() else None
70
+ memories = get_relevant_memories(
71
+ command_history=cmd_history,
72
+ npc_name=npc_name or '__none__',
73
+ team_name=team_name or '__none__',
74
+ path=current_path,
75
+ query=query,
76
+ max_memories=max_results,
77
+ state=state_obj
78
+ )
79
+ else:
80
+ memories = cmd_history.search_memory(
81
+ query=query,
82
+ npc=npc_name,
83
+ team=team_name,
84
+ status_filter=status_filter if status_filter != 'all' else None,
85
+ limit=max_results
86
+ )
87
+
88
+ # Normalize to list of dicts
89
+ if memories:
90
+ normalized = []
91
+ for mem in memories:
92
+ if isinstance(mem, dict):
93
+ normalized.append(mem)
94
+ else:
95
+ normalized.append({'content': str(mem), 'status': 'unknown', 'timestamp': '', 'npc': ''})
96
+ memories = normalized
97
+ else:
98
+ memories = []
99
+
100
+ if not memories:
101
+ context['output'] = f"No memories found for '{query}' (status={status_filter})"
102
+ elif text_mode:
103
+ # Text-only output
104
+ lines = [f"Found {len(memories)} memories (status={status_filter}):", ""]
105
+ for i, mem in enumerate(memories, 1):
106
+ ts = mem.get('timestamp', 'unknown')
107
+ content = mem.get('final_memory') or mem.get('initial_memory') or mem.get('content', '')
108
+ status = mem.get('status', '')
109
+ lines.append(f"{i}. [{ts}] ({status}) {str(content)[:80]}")
110
+ context['output'] = "\n".join(lines)
111
+ else:
112
+ # Interactive TUI mode
113
+ def get_terminal_size():
114
+ try:
115
+ size = os.get_terminal_size()
116
+ return size.columns, size.lines
117
+ except:
118
+ return 80, 24
119
+
120
+ def format_ts(ts):
121
+ if not ts:
122
+ return 'unknown'
123
+ try:
124
+ if 'T' in str(ts):
125
+ dt = datetime.fromisoformat(str(ts).replace('Z', '+00:00'))
126
+ else:
127
+ dt = datetime.strptime(str(ts)[:19], '%Y-%m-%d %H:%M:%S')
128
+ now = datetime.now()
129
+ diff = now - dt.replace(tzinfo=None)
130
+ if diff.days == 0:
131
+ return f"Today {dt.strftime('%H:%M')}"
132
+ elif diff.days == 1:
133
+ return f"Yesterday {dt.strftime('%H:%M')}"
134
+ elif diff.days < 7:
135
+ return dt.strftime('%a %H:%M')
136
+ else:
137
+ return dt.strftime('%b %d')
138
+ except:
139
+ return str(ts)[:12]
140
+
141
+ width, height = get_terminal_size()
142
+ selected = 0
143
+ scroll = 0
144
+ list_height = height - 5
145
+ mode = 'list'
146
+ preview_scroll = 0
147
+ sort_mode = 'time' # time, status, npc
148
+ current_filter = status_filter
149
+
150
+ def sort_memories(mems, sort_mode):
151
+ if sort_mode == 'time':
152
+ return sorted(mems, key=lambda x: x.get('timestamp') or '', reverse=True)
153
+ elif sort_mode == 'status':
154
+ return sorted(mems, key=lambda x: (x.get('status') or '', x.get('timestamp') or ''), reverse=True)
155
+ elif sort_mode == 'npc':
156
+ return sorted(mems, key=lambda x: (x.get('npc') or '', x.get('timestamp') or ''), reverse=True)
157
+ return mems
158
+
159
+ def filter_memories(mems, filter_status):
160
+ if filter_status == 'all':
161
+ return mems
162
+ return [m for m in mems if m.get('status') == filter_status]
163
+
164
+ display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
165
+
166
+ fd = sys.stdin.fileno()
167
+ old_settings = termios.tcgetattr(fd)
168
+
169
+ try:
170
+ tty.setcbreak(fd)
171
+ sys.stdout.write('\033[?25l')
172
+ sys.stdout.write('\033[2J\033[H')
173
+
174
+ while True:
175
+ width, height = get_terminal_size()
176
+ list_height = height - 5
177
+
178
+ if mode == 'list':
179
+ if selected < scroll:
180
+ scroll = selected
181
+ elif selected >= scroll + list_height:
182
+ scroll = selected - list_height + 1
183
+
184
+ sys.stdout.write('\033[H')
185
+
186
+ # Header
187
+ if mode == 'list':
188
+ sort_ind = {'time': '1', 'status': '2', 'npc': '3'}[sort_mode]
189
+ header = f" MEM SEARCH ({len(display_memories)} results): '{query}' [sort:{sort_mode}({sort_ind}) filter:{current_filter}] "
190
+ else:
191
+ header = f" PREVIEW MEMORY "
192
+ sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
193
+
194
+ # Column headers
195
+ if mode == 'list':
196
+ col_header = f' {"STATUS":<10} {"TIMESTAMP":<14} {"NPC":<12} {"CONTENT":<40}'
197
+ sys.stdout.write(f'\033[90m{col_header[:width]}\033[0m\n')
198
+ else:
199
+ sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
200
+
201
+ if mode == 'list':
202
+ for i in range(list_height):
203
+ idx = scroll + i
204
+ sys.stdout.write(f'\033[{3+i};1H\033[K')
205
+ if idx >= len(display_memories):
206
+ continue
207
+
208
+ m = display_memories[idx]
209
+ status = (m.get('status') or '?')[:10]
210
+ ts = format_ts(m.get('timestamp'))
211
+ npc_str = (m.get('npc') or 'default')[:12]
212
+ content = (m.get('final_memory') or m.get('initial_memory') or m.get('content', ''))
213
+ content = str(content)[:50].replace('\n', ' ')
214
+
215
+ # Color by status
216
+ if m.get('status') == 'approved':
217
+ status_color = '\033[32m' # green
218
+ elif m.get('status') == 'pending':
219
+ status_color = '\033[33m' # yellow
220
+ elif m.get('status') == 'rejected':
221
+ status_color = '\033[31m' # red
222
+ else:
223
+ status_color = '\033[90m' # gray
224
+
225
+ line = f" {status_color}{status:<10}\033[0m {ts:<14} {npc_str:<12} {content}"
226
+ line = line[:width+15]
227
+
228
+ if idx == selected:
229
+ sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
230
+ else:
231
+ sys.stdout.write(f' {line}')
232
+
233
+ # Status bar
234
+ sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
235
+ sel = display_memories[selected] if display_memories else {}
236
+ mem_id = sel.get('id', '') or sel.get('memory_id', '')
237
+ sys.stdout.write(f'\033[{height-1};1H\033[K ID: {mem_id}'.ljust(width))
238
+ sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav 1/2/3:Sort f:Filter p:Preview a:Approve x:Reject q:Quit [{selected+1}/{len(display_memories)}] \033[0m')
239
+
240
+ else: # preview mode
241
+ sel = display_memories[selected]
242
+ content = sel.get('final_memory') or sel.get('initial_memory') or sel.get('content', '')
243
+ content_lines = str(content).split('\n')
244
+
245
+ # Add metadata at top
246
+ meta_lines = [
247
+ f"Status: {sel.get('status', '')}",
248
+ f"Timestamp: {sel.get('timestamp', '')}",
249
+ f"NPC: {sel.get('npc', '')}",
250
+ f"Team: {sel.get('team', '')}",
251
+ f"ID: {sel.get('id', '') or sel.get('memory_id', '')}",
252
+ "─" * 40,
253
+ ]
254
+ all_lines = meta_lines + content_lines
255
+
256
+ for i in range(list_height):
257
+ idx = preview_scroll + i
258
+ sys.stdout.write(f'\033[{3+i};1H\033[K')
259
+ if idx < len(all_lines):
260
+ sys.stdout.write(all_lines[idx][:width-1])
261
+
262
+ sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
263
+ sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(all_lines)} lines]')
264
+ sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back a:Approve x:Reject q:Quit \033[0m')
265
+
266
+ sys.stdout.flush()
267
+
268
+ c = sys.stdin.read(1)
269
+
270
+ if c == '\x1b':
271
+ c2 = sys.stdin.read(1)
272
+ if c2 == '[':
273
+ c3 = sys.stdin.read(1)
274
+ if c3 == 'A': # Up
275
+ if mode == 'list' and selected > 0:
276
+ selected -= 1
277
+ elif mode == 'preview' and preview_scroll > 0:
278
+ preview_scroll -= 1
279
+ elif c3 == 'B': # Down
280
+ if mode == 'list' and selected < len(display_memories) - 1:
281
+ selected += 1
282
+ elif mode == 'preview':
283
+ sel = display_memories[selected]
284
+ content = str(sel.get('final_memory') or sel.get('content', ''))
285
+ all_lines = content.split('\n')
286
+ if preview_scroll < max(0, len(all_lines) + 6 - list_height):
287
+ preview_scroll += 1
288
+ else:
289
+ if mode == 'preview':
290
+ mode = 'list'
291
+ sys.stdout.write('\033[2J\033[H')
292
+ else:
293
+ context['output'] = "Cancelled."
294
+ break
295
+ continue
296
+
297
+ if c == 'q' or c == '\x03':
298
+ context['output'] = "Cancelled."
299
+ break
300
+ elif c == 'k':
301
+ if mode == 'list' and selected > 0:
302
+ selected -= 1
303
+ elif mode == 'preview' and preview_scroll > 0:
304
+ preview_scroll -= 1
305
+ elif c == 'j':
306
+ if mode == 'list' and selected < len(display_memories) - 1:
307
+ selected += 1
308
+ elif mode == 'preview':
309
+ sel = display_memories[selected]
310
+ content = str(sel.get('final_memory') or sel.get('content', ''))
311
+ all_lines = content.split('\n')
312
+ if preview_scroll < max(0, len(all_lines) + 6 - list_height):
313
+ preview_scroll += 1
314
+ elif c == '1':
315
+ sort_mode = 'time'
316
+ display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
317
+ selected = 0
318
+ scroll = 0
319
+ elif c == '2':
320
+ sort_mode = 'status'
321
+ display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
322
+ selected = 0
323
+ scroll = 0
324
+ elif c == '3':
325
+ sort_mode = 'npc'
326
+ display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
327
+ selected = 0
328
+ scroll = 0
329
+ elif c == 'f' and mode == 'list':
330
+ # Cycle through filters
331
+ if current_filter == 'all':
332
+ current_filter = 'pending'
333
+ elif current_filter == 'pending':
334
+ current_filter = 'approved'
335
+ else:
336
+ current_filter = 'all'
337
+ display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
338
+ selected = 0
339
+ scroll = 0
340
+ elif c == 'a' and display_memories:
341
+ # Approve memory
342
+ sel = display_memories[selected]
343
+ mem_id = sel.get('id') or sel.get('memory_id')
344
+ if mem_id:
345
+ try:
346
+ cmd_history.update_memory_status(mem_id, 'approved')
347
+ sel['status'] = 'approved'
348
+ # Update in original list too
349
+ for m in memories:
350
+ if (m.get('id') or m.get('memory_id')) == mem_id:
351
+ m['status'] = 'approved'
352
+ except Exception as e:
353
+ pass
354
+ elif c == 'x' and display_memories:
355
+ # Reject memory
356
+ sel = display_memories[selected]
357
+ mem_id = sel.get('id') or sel.get('memory_id')
358
+ if mem_id:
359
+ try:
360
+ cmd_history.update_memory_status(mem_id, 'rejected')
361
+ sel['status'] = 'rejected'
362
+ for m in memories:
363
+ if (m.get('id') or m.get('memory_id')) == mem_id:
364
+ m['status'] = 'rejected'
365
+ except Exception as e:
366
+ pass
367
+ elif c == 'p' and mode == 'list' and display_memories:
368
+ mode = 'preview'
369
+ preview_scroll = 0
370
+ sys.stdout.write('\033[2J\033[H')
371
+ elif c == 'b' and mode == 'preview':
372
+ mode = 'list'
373
+ sys.stdout.write('\033[2J\033[H')
374
+ elif c in ('\r', '\n') and display_memories:
375
+ sel = display_memories[selected]
376
+ content = sel.get('final_memory') or sel.get('content', '')
377
+ context['output'] = f"Selected memory:\n\n{content}"
378
+ break
379
+
380
+ finally:
381
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
382
+ sys.stdout.write('\033[?25h')
383
+ sys.stdout.write('\033[2J\033[H')
384
+ sys.stdout.flush()
385
+
386
+ except Exception as e:
387
+ import traceback
388
+ context['output'] = "Memory search error: " + str(e) + "\n" + traceback.format_exc()
@@ -0,0 +1,10 @@
1
+ jinx_name: studio.navigate
2
+ description: Navigate a browser pane to a specific URL.
3
+ inputs:
4
+ - paneId: "active"
5
+ - url: ""
6
+ steps:
7
+ - name: frontend_action
8
+ engine: python
9
+ code: |
10
+ context['output'] = "Action executed by frontend"
@@ -0,0 +1,10 @@
1
+ jinx_name: studio.notify
2
+ description: Show a notification toast in NPC Studio.
3
+ inputs:
4
+ - message: ""
5
+ - type: "info"
6
+ steps:
7
+ - name: frontend_action
8
+ engine: python
9
+ code: |
10
+ context['output'] = "Action executed by frontend"
@@ -1,13 +1,13 @@
1
1
  jinx_name: nql
2
2
  description: "Run NPC-SQL models with AI-powered transformations. Supports cron scheduling."
3
3
  inputs:
4
- - models_dir: "~/.npcsh/npc_team/models"
5
- - db: "~/npcsh_history.db"
6
- - model: ""
7
- - schema: ""
8
- - show: ""
9
- - cron: ""
10
- - install_cron: ""
4
+ - models_dir: "~/.npcsh/npc_team/models"
5
+ - db: "~/npcsh_history.db"
6
+ - model: ""
7
+ - schema: ""
8
+ - show: ""
9
+ - cron: ""
10
+ - install_cron: ""
11
11
 
12
12
  steps:
13
13
  - name: run_nql
@@ -3,9 +3,9 @@ description: |
3
3
  Open a browser and navigate to a URL. The browser stays open for follow-up commands.
4
4
  Use this to start browser automation.
5
5
  inputs:
6
- - url:
6
+ - url:
7
7
  description: "URL to navigate to"
8
- - browser: "firefox"
8
+ - browser: "firefox"
9
9
 
10
10
  steps:
11
11
  - name: open_browser
@@ -0,0 +1,13 @@
1
+ jinx_name: studio.open_pane
2
+ description: Open a new pane in NPC Studio. Supports editor, terminal, browser, pdf, csv, chat, image, folder, and other content types.
3
+ inputs:
4
+ - type: ""
5
+ - path: ""
6
+ - position: "right"
7
+ steps:
8
+ - name: frontend_action
9
+ engine: python
10
+ code: |
11
+ # This action is executed by the NPC Studio frontend
12
+ # The frontend intercepts studio.* tool calls and handles them directly
13
+ context['output'] = "Action executed by frontend"
@@ -1,10 +1,10 @@
1
1
  jinx_name: "ots"
2
2
  description: "Take screenshot and analyze with vision model. Usage: /ots <prompt>"
3
3
  inputs:
4
- - prompt
5
- - image_paths_args: ""
6
- - vmodel: ""
7
- - vprovider: ""
4
+ - prompt
5
+ - image_paths_args: ""
6
+ - vmodel: ""
7
+ - vprovider: ""
8
8
  steps:
9
9
  - name: "analyze_screenshot_or_image"
10
10
  engine: "python"
@@ -38,10 +38,10 @@ steps:
38
38
  print(f"📸 Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
39
39
 
40
40
  if not vision_model:
41
- vision_model = getattr(current_npc, 'model', 'gemma3:4b')
42
-
41
+ vision_model = getattr(current_npc, 'model', None) or (state.vision_model if state else 'llama3.2')
42
+
43
43
  if not vision_provider:
44
- vision_provider = getattr(current_npc, 'provider', 'ollama')
44
+ vision_provider = getattr(current_npc, 'provider', None) or (state.vision_provider if state else 'ollama')
45
45
 
46
46
  response_data = get_llm_response(
47
47
  prompt=user_prompt,