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
@@ -1,10 +1,10 @@
1
1
  jinx_name: chat
2
2
  description: Simple chat mode - LLM conversation without tool execution
3
3
  inputs:
4
- - query: null
5
- - model: null
6
- - provider: null
7
- - stream: true
4
+ - query: null
5
+ - model: null
6
+ - provider: null
7
+ - stream: true
8
8
 
9
9
  steps:
10
10
  - name: chat_response
@@ -1,10 +1,10 @@
1
1
  jinx_name: cmd
2
2
  description: Command mode - LLM generates and executes shell commands
3
3
  inputs:
4
- - query: null
5
- - model: null
6
- - provider: null
7
- - stream: true
4
+ - query: null
5
+ - model: null
6
+ - provider: null
7
+ - stream: true
8
8
 
9
9
  steps:
10
10
  - name: cmd_execute
@@ -1,12 +1,12 @@
1
1
  jinx_name: "compress"
2
2
  description: "Manages conversation and knowledge context. Defaults to compacting context. Use flags for other operations."
3
3
  inputs:
4
- - flush: "" # The number of recent messages to flush.
5
- - sleep: False # If true, evolves the knowledge graph.
6
- - dream: False # Used with --sleep. Runs creative synthesis.
7
- - ops: "" # Used with --sleep. Comma-separated list of KG operations.
8
- - model: "" # Used with --sleep. LLM model for KG evolution.
9
- - provider: "" # Used with --sleep. LLM provider for KG evolution.
4
+ - flush: ""
5
+ - sleep: False
6
+ - dream: False
7
+ - ops: ""
8
+ - model: ""
9
+ - provider: ""
10
10
  steps:
11
11
  - name: "manage_context_and_memory"
12
12
  engine: "python"
@@ -53,8 +53,8 @@ steps:
53
53
  operations_config = [op.strip() for op in operations_str.split(',')] if operations_str else None
54
54
  if not llm_model and current_npc: llm_model = current_npc.model
55
55
  if not llm_provider and current_npc: llm_provider = current_npc.provider
56
- if not llm_model: llm_model = "gemini-1.5-pro"
57
- if not llm_provider: llm_provider = "gemini"
56
+ if not llm_model: llm_model = state.chat_model if state else "llama3.2"
57
+ if not llm_provider: llm_provider = state.chat_provider if state else "ollama"
58
58
 
59
59
  team_name = current_team.name if current_team else "__none__"
60
60
  npc_name = current_npc.name if current_npc else "__none__"
@@ -45,6 +45,9 @@ steps:
45
45
  - "replacement": For "replace", the text to replace with
46
46
  - "insertion": For "insert_after" and "insert_before", the text to insert
47
47
  2. "explanation": Brief explanation of the changes made
48
+
49
+ Example response:
50
+ {"modifications": [{"type": "replace", "original": "old code", "replacement": "new code"}], "explanation": "Updated the code"}
48
51
  """
49
52
 
50
53
  response = get_llm_response(prompt, model=npc.model, provider=npc.provider, npc=npc, format="json")
@@ -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,
@@ -0,0 +1,348 @@
1
+ jinx_name: db_search
2
+ description: Search conversation history database with interactive TUI
3
+ inputs:
4
+ - query: ""
5
+ - path: ""
6
+ - limit: "100"
7
+ - text: "false"
8
+
9
+ steps:
10
+ - name: search_db
11
+ engine: python
12
+ code: |
13
+ import os
14
+ import sys
15
+ import tty
16
+ import termios
17
+ from datetime import datetime
18
+ from sqlalchemy import create_engine, text
19
+
20
+ query = context.get('query', '').strip()
21
+ text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
22
+
23
+ if not query:
24
+ lines = [
25
+ "Usage: /db_search <query>",
26
+ "",
27
+ "Searches conversation history for matching content.",
28
+ "",
29
+ "Options:",
30
+ " path - Filter by directory path",
31
+ " limit - Max results (default 100)",
32
+ " text - Text-only output, no TUI (true/false)",
33
+ "",
34
+ "TUI Controls:",
35
+ " j/k or arrows - Navigate",
36
+ " 1/2/3 - Sort by time/role/npc",
37
+ " p - Preview full message",
38
+ " r - Reattach to conversation",
39
+ " f - Filter by role (user/assistant/all)",
40
+ " q/ESC - Quit",
41
+ "",
42
+ "Examples:",
43
+ " /db_search python debugging",
44
+ " /db_search api errors path=/home/user/project",
45
+ " /db_search errors text=true",
46
+ ]
47
+ context['output'] = "\n".join(lines)
48
+ else:
49
+ db_path = os.getenv("NPCSH_DB_PATH", os.path.expanduser("~/npcsh_history.db"))
50
+ limit = int(context.get('limit') or 100)
51
+ filter_path = context.get('path', '').strip()
52
+
53
+ engine = create_engine(f'sqlite:///{db_path}')
54
+
55
+ try:
56
+ with engine.connect() as conn:
57
+ if filter_path:
58
+ result = conn.execute(text("""
59
+ SELECT conversation_id, timestamp, role, content, npc, directory_path
60
+ FROM conversation_history
61
+ WHERE LOWER(content) LIKE LOWER(:query)
62
+ AND (directory_path = :path OR directory_path LIKE :path_pattern)
63
+ ORDER BY timestamp DESC
64
+ LIMIT :limit
65
+ """), {
66
+ "query": f"%{query}%",
67
+ "path": filter_path,
68
+ "path_pattern": filter_path + "/%",
69
+ "limit": limit
70
+ })
71
+ else:
72
+ result = conn.execute(text("""
73
+ SELECT conversation_id, timestamp, role, content, npc, directory_path
74
+ FROM conversation_history
75
+ WHERE LOWER(content) LIKE LOWER(:query)
76
+ ORDER BY timestamp DESC
77
+ LIMIT :limit
78
+ """), {"query": f"%{query}%", "limit": limit})
79
+
80
+ rows = [dict(row._mapping) for row in result.fetchall()]
81
+
82
+ if not rows:
83
+ context['output'] = f"No results found for '{query}'"
84
+ elif text_mode:
85
+ # Text-only output
86
+ lines = [f"Found {len(rows)} results for '{query}':", ""]
87
+ for row in rows:
88
+ cid = row['conversation_id'][:8] if row['conversation_id'] else '?'
89
+ ts = str(row['timestamp'])[:16] if row['timestamp'] else '?'
90
+ role = row['role'] or '?'
91
+ content = (row['content'] or '')[:100].replace('\n', ' ')
92
+ npc_name = row['npc'] or 'default'
93
+ path = row['directory_path'] or ''
94
+
95
+ lines.append(f"[{cid}] {ts} ({role}/{npc_name})")
96
+ lines.append(f" {content}")
97
+ if path:
98
+ lines.append(f" @ {path}")
99
+ lines.append("")
100
+ context['output'] = "\n".join(lines)
101
+ else:
102
+ # Interactive TUI mode
103
+ def get_terminal_size():
104
+ try:
105
+ size = os.get_terminal_size()
106
+ return size.columns, size.lines
107
+ except:
108
+ return 80, 24
109
+
110
+ def format_ts(ts):
111
+ if not ts:
112
+ return 'unknown'
113
+ try:
114
+ if 'T' in str(ts):
115
+ dt = datetime.fromisoformat(str(ts).replace('Z', '+00:00'))
116
+ else:
117
+ dt = datetime.strptime(str(ts)[:19], '%Y-%m-%d %H:%M:%S')
118
+ now = datetime.now()
119
+ diff = now - dt.replace(tzinfo=None)
120
+ if diff.days == 0:
121
+ return f"Today {dt.strftime('%H:%M')}"
122
+ elif diff.days == 1:
123
+ return f"Yesterday {dt.strftime('%H:%M')}"
124
+ elif diff.days < 7:
125
+ return dt.strftime('%a %H:%M')
126
+ else:
127
+ return dt.strftime('%b %d %H:%M')
128
+ except:
129
+ return str(ts)[:16]
130
+
131
+ width, height = get_terminal_size()
132
+ selected = 0
133
+ scroll = 0
134
+ list_height = height - 5
135
+ mode = 'list'
136
+ preview_scroll = 0
137
+ sort_mode = 'time' # time, role, npc
138
+ role_filter = 'all' # all, user, assistant
139
+
140
+ def sort_rows(rows, sort_mode):
141
+ if sort_mode == 'time':
142
+ return sorted(rows, key=lambda x: x.get('timestamp') or '', reverse=True)
143
+ elif sort_mode == 'role':
144
+ return sorted(rows, key=lambda x: (x.get('role') or '', x.get('timestamp') or ''), reverse=True)
145
+ elif sort_mode == 'npc':
146
+ return sorted(rows, key=lambda x: (x.get('npc') or '', x.get('timestamp') or ''), reverse=True)
147
+ return rows
148
+
149
+ def filter_rows(rows, role_filter):
150
+ if role_filter == 'all':
151
+ return rows
152
+ return [r for r in rows if r.get('role') == role_filter]
153
+
154
+ display_rows = filter_rows(sort_rows(rows, sort_mode), role_filter)
155
+
156
+ fd = sys.stdin.fileno()
157
+ old_settings = termios.tcgetattr(fd)
158
+
159
+ try:
160
+ tty.setcbreak(fd)
161
+ sys.stdout.write('\033[?25l')
162
+ sys.stdout.write('\033[2J\033[H')
163
+
164
+ while True:
165
+ width, height = get_terminal_size()
166
+ list_height = height - 5
167
+
168
+ if mode == 'list':
169
+ if selected < scroll:
170
+ scroll = selected
171
+ elif selected >= scroll + list_height:
172
+ scroll = selected - list_height + 1
173
+
174
+ sys.stdout.write('\033[H')
175
+
176
+ # Header
177
+ if mode == 'list':
178
+ sort_ind = {'time': '1', 'role': '2', 'npc': '3'}[sort_mode]
179
+ header = f" DB SEARCH ({len(display_rows)} results): '{query}' [sort:{sort_mode}({sort_ind}) filter:{role_filter}] "
180
+ else:
181
+ header = f" PREVIEW: {display_rows[selected]['conversation_id'][:16]} "
182
+ sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
183
+
184
+ # Column headers
185
+ if mode == 'list':
186
+ col_header = f' {"TIMESTAMP":<16} {"ROLE":<10} {"NPC":<12} {"CONTENT":<40}'
187
+ sys.stdout.write(f'\033[90m{col_header[:width]}\033[0m\n')
188
+ else:
189
+ sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
190
+
191
+ if mode == 'list':
192
+ for i in range(list_height):
193
+ idx = scroll + i
194
+ sys.stdout.write(f'\033[{3+i};1H\033[K')
195
+ if idx >= len(display_rows):
196
+ continue
197
+
198
+ r = display_rows[idx]
199
+ ts = format_ts(r.get('timestamp'))
200
+ role = (r.get('role') or '?')[:10]
201
+ npc_name = (r.get('npc') or 'default')[:12]
202
+ content = (r.get('content') or '')[:60].replace('\n', ' ')
203
+
204
+ # Color by role
205
+ if r.get('role') == 'user':
206
+ role_color = '\033[32m' # green
207
+ elif r.get('role') == 'assistant':
208
+ role_color = '\033[34m' # blue
209
+ else:
210
+ role_color = '\033[90m' # gray
211
+
212
+ line = f" {ts:<16} {role_color}{role:<10}\033[0m {npc_name:<12} {content}"
213
+ line = line[:width+20] # allow for color codes
214
+
215
+ if idx == selected:
216
+ sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
217
+ else:
218
+ sys.stdout.write(f' {line}')
219
+
220
+ # Status bar
221
+ sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
222
+ sel = display_rows[selected] if display_rows else {}
223
+ cid = sel.get('conversation_id', '')[:20]
224
+ path = sel.get('directory_path', '')
225
+ if len(path) > 40:
226
+ path = '...' + path[-37:]
227
+ sys.stdout.write(f'\033[{height-1};1H\033[K {cid} @ {path}'.ljust(width))
228
+ sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav 1/2/3:Sort f:Filter p:Preview r:Reattach q:Quit [{selected+1}/{len(display_rows)}] \033[0m')
229
+
230
+ else: # preview mode
231
+ sel = display_rows[selected]
232
+ content = sel.get('content') or ''
233
+ lines = content.split('\n')
234
+
235
+ for i in range(list_height):
236
+ idx = preview_scroll + i
237
+ sys.stdout.write(f'\033[{3+i};1H\033[K')
238
+ if idx < len(lines):
239
+ sys.stdout.write(lines[idx][:width-1])
240
+
241
+ sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
242
+ role = sel.get('role') or '?'
243
+ npc_name = sel.get('npc') or 'default'
244
+ ts = format_ts(sel.get('timestamp'))
245
+ sys.stdout.write(f'\033[{height-1};1H\033[K {role}/{npc_name} - {ts} [{preview_scroll+1}/{len(lines)} lines]')
246
+ sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back r:Reattach q:Quit \033[0m')
247
+
248
+ sys.stdout.flush()
249
+
250
+ c = sys.stdin.read(1)
251
+
252
+ if c == '\x1b':
253
+ c2 = sys.stdin.read(1)
254
+ if c2 == '[':
255
+ c3 = sys.stdin.read(1)
256
+ if c3 == 'A': # Up
257
+ if mode == 'list' and selected > 0:
258
+ selected -= 1
259
+ elif mode == 'preview' and preview_scroll > 0:
260
+ preview_scroll -= 1
261
+ elif c3 == 'B': # Down
262
+ if mode == 'list' and selected < len(display_rows) - 1:
263
+ selected += 1
264
+ elif mode == 'preview':
265
+ sel = display_rows[selected]
266
+ lines = (sel.get('content') or '').split('\n')
267
+ if preview_scroll < max(0, len(lines) - list_height):
268
+ preview_scroll += 1
269
+ else:
270
+ if mode == 'preview':
271
+ mode = 'list'
272
+ sys.stdout.write('\033[2J\033[H')
273
+ else:
274
+ context['output'] = "Cancelled."
275
+ break
276
+ continue
277
+
278
+ if c == 'q' or c == '\x03':
279
+ context['output'] = "Cancelled."
280
+ break
281
+ elif c == 'k':
282
+ if mode == 'list' and selected > 0:
283
+ selected -= 1
284
+ elif mode == 'preview' and preview_scroll > 0:
285
+ preview_scroll -= 1
286
+ elif c == 'j':
287
+ if mode == 'list' and selected < len(display_rows) - 1:
288
+ selected += 1
289
+ elif mode == 'preview':
290
+ sel = display_rows[selected]
291
+ lines = (sel.get('content') or '').split('\n')
292
+ if preview_scroll < max(0, len(lines) - list_height):
293
+ preview_scroll += 1
294
+ elif c == '1':
295
+ sort_mode = 'time'
296
+ display_rows = filter_rows(sort_rows(rows, sort_mode), role_filter)
297
+ selected = 0
298
+ scroll = 0
299
+ elif c == '2':
300
+ sort_mode = 'role'
301
+ display_rows = filter_rows(sort_rows(rows, sort_mode), role_filter)
302
+ selected = 0
303
+ scroll = 0
304
+ elif c == '3':
305
+ sort_mode = 'npc'
306
+ display_rows = filter_rows(sort_rows(rows, sort_mode), role_filter)
307
+ selected = 0
308
+ scroll = 0
309
+ elif c == 'f' and mode == 'list':
310
+ # Cycle through filters
311
+ if role_filter == 'all':
312
+ role_filter = 'user'
313
+ elif role_filter == 'user':
314
+ role_filter = 'assistant'
315
+ else:
316
+ role_filter = 'all'
317
+ display_rows = filter_rows(sort_rows(rows, sort_mode), role_filter)
318
+ selected = 0
319
+ scroll = 0
320
+ elif c == 'p' and mode == 'list' and display_rows:
321
+ mode = 'preview'
322
+ preview_scroll = 0
323
+ sys.stdout.write('\033[2J\033[H')
324
+ elif c == 'b' and mode == 'preview':
325
+ mode = 'list'
326
+ sys.stdout.write('\033[2J\033[H')
327
+ elif c == 'r' and display_rows:
328
+ cid = display_rows[selected]['conversation_id']
329
+ if 'state' in dir() and state is not None:
330
+ state.conversation_id = cid
331
+ context['output'] = f"Reattached to: {cid}"
332
+ else:
333
+ context['output'] = f"Selected: {cid}\n\nRun: /set conversation_id={cid}"
334
+ break
335
+ elif c in ('\r', '\n') and display_rows:
336
+ cid = display_rows[selected]['conversation_id']
337
+ context['output'] = f"Selected: {cid}\n\nRun: /set conversation_id={cid}"
338
+ break
339
+
340
+ finally:
341
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
342
+ sys.stdout.write('\033[?25h')
343
+ sys.stdout.write('\033[2J\033[H')
344
+ sys.stdout.flush()
345
+
346
+ except Exception as e:
347
+ import traceback
348
+ context['output'] = f"Search error: {e}\n{traceback.format_exc()}"