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,455 @@
1
+ jinx_name: wander
2
+ description: Interactive wandering mode - creative exploration with live TUI dashboard
3
+ inputs:
4
+ - problem: null
5
+ - environment: null
6
+ - low_temp: 0.3
7
+ - high_temp: 1.5
8
+ - model: null
9
+ - provider: null
10
+
11
+ steps:
12
+ - name: wander_interactive
13
+ engine: python
14
+ code: |
15
+ import os
16
+ import sys
17
+ import tty
18
+ import termios
19
+ import random
20
+ import threading
21
+ import time
22
+ import textwrap
23
+ from datetime import datetime
24
+ from termcolor import colored
25
+
26
+ from npcpy.llm_funcs import get_llm_response
27
+
28
+ npc = context.get('npc')
29
+ team = context.get('team')
30
+ messages = context.get('messages', [])
31
+
32
+ problem = context.get('problem')
33
+ environment = context.get('environment')
34
+ low_temp = float(context.get('low_temp', 0.3))
35
+ high_temp = float(context.get('high_temp', 1.5))
36
+
37
+ # Resolve npc if it's a string (npc name) rather than NPC object
38
+ if isinstance(npc, str) and team:
39
+ npc = team.get(npc) if hasattr(team, 'get') else None
40
+ elif isinstance(npc, str):
41
+ npc = None
42
+
43
+ model = context.get('model') or (npc.model if npc and hasattr(npc, 'model') else None)
44
+ provider = context.get('provider') or (npc.provider if npc and hasattr(npc, 'provider') else None)
45
+
46
+ if not problem:
47
+ context['output'] = """Usage: /wander <problem to explore>
48
+
49
+ Interactive TUI Controls:
50
+ SPACE - Generate new wandering stream
51
+ t - Toggle temperature (focused/creative/wild)
52
+ e - Trigger random event
53
+ s - Star current insight (save to favorites)
54
+ r - Request reflection/synthesis
55
+ j/k - Scroll through output
56
+ Tab - Switch panels (Environment/Streams/Starred)
57
+ q - Quit and show final synthesis
58
+
59
+ Example: /wander How might we reimagine urban transportation?"""
60
+ context['messages'] = messages
61
+ exit()
62
+
63
+ # ========== State ==========
64
+ class WanderState:
65
+ def __init__(self):
66
+ self.environment = ""
67
+ self.streams = [] # List of {temp, mode, insight, event, starred, timestamp}
68
+ self.starred = [] # Starred insights
69
+ self.current_temp = low_temp
70
+ self.temp_mode = "focused" # focused, creative, wild
71
+ self.scroll_offset = 0
72
+ self.current_panel = 0 # 0=main, 1=streams, 2=starred
73
+ self.status = "Ready"
74
+ self.generating = False
75
+ self.last_output = ""
76
+
77
+ state = WanderState()
78
+
79
+ # ========== TUI Helpers ==========
80
+ def get_size():
81
+ try:
82
+ s = os.get_terminal_size()
83
+ return s.columns, s.lines
84
+ except:
85
+ return 80, 24
86
+
87
+ def wrap_text(text, width):
88
+ lines = []
89
+ for line in text.split('\n'):
90
+ if len(line) <= width:
91
+ lines.append(line)
92
+ else:
93
+ lines.extend(textwrap.wrap(line, width) or [''])
94
+ return lines
95
+
96
+ def draw_box(x, y, w, h, title="", color="\033[90m"):
97
+ """Draw a box with optional title"""
98
+ out = []
99
+ # Top border
100
+ if title:
101
+ title_part = f" {title} "
102
+ border = "─" * ((w - len(title_part) - 2) // 2)
103
+ top = f"┌{border}{title_part}{border}{'─' * ((w - len(title_part) - 2) % 2)}┐"
104
+ else:
105
+ top = "┌" + "─" * (w - 2) + "┐"
106
+ out.append(f"\033[{y};{x}H{color}{top}\033[0m")
107
+ # Sides
108
+ for i in range(1, h - 1):
109
+ out.append(f"\033[{y+i};{x}H{color}│\033[0m")
110
+ out.append(f"\033[{y+i};{x+w-1}H{color}│\033[0m")
111
+ # Bottom
112
+ out.append(f"\033[{y+h-1};{x}H{color}└{'─' * (w - 2)}┘\033[0m")
113
+ return ''.join(out)
114
+
115
+ def render_screen():
116
+ width, height = get_size()
117
+ out = []
118
+
119
+ # Clear screen
120
+ out.append("\033[2J\033[H")
121
+
122
+ # ===== HEADER =====
123
+ header = f" WANDER - {problem[:width-20]}... " if len(problem) > width-20 else f" WANDER - {problem} "
124
+ temp_color = {"focused": "\033[36m", "creative": "\033[35m", "wild": "\033[31m"}[state.temp_mode]
125
+ temp_info = f"{temp_color}[{state.temp_mode} T={state.current_temp:.1f}]\033[0m"
126
+ status_color = "\033[33m" if state.generating else "\033[32m"
127
+ status_info = f"{status_color}[{state.status}]\033[0m"
128
+
129
+ out.append(f"\033[1;1H\033[45;37;1m{header.ljust(width)}\033[0m")
130
+ out.append(f"\033[1;{width-35}H{temp_info} {status_info}")
131
+
132
+ # ===== LAYOUT =====
133
+ # Left panel: Environment + Controls (30% width)
134
+ # Right panel: Main output / Streams / Starred (70% width)
135
+ left_w = max(25, width // 3)
136
+ right_w = width - left_w - 1
137
+ panel_h = height - 4
138
+
139
+ # ===== LEFT PANEL: Environment =====
140
+ out.append(draw_box(1, 3, left_w, panel_h // 2, "Environment", "\033[36m"))
141
+ env_lines = wrap_text(state.environment or "Press 'e' to generate...", left_w - 4)
142
+ for i, line in enumerate(env_lines[:panel_h // 2 - 3]):
143
+ out.append(f"\033[{4+i};3H{line[:left_w-4]}")
144
+
145
+ # ===== LEFT PANEL: Controls =====
146
+ ctrl_y = 3 + panel_h // 2
147
+ out.append(draw_box(1, ctrl_y, left_w, panel_h // 2, "Controls", "\033[33m"))
148
+ controls = [
149
+ "SPACE - New stream",
150
+ f"t - Temp: {state.temp_mode}",
151
+ "e - Trigger event",
152
+ "s - Star insight",
153
+ "r - Reflect/synthesize",
154
+ "Tab - Switch panel",
155
+ "j/k - Scroll",
156
+ "q - Quit",
157
+ "",
158
+ f"Streams: {len(state.streams)}",
159
+ f"Starred: {len(state.starred)}",
160
+ ]
161
+ for i, ctrl in enumerate(controls[:panel_h // 2 - 3]):
162
+ out.append(f"\033[{ctrl_y+1+i};3H\033[90m{ctrl[:left_w-4]}\033[0m")
163
+
164
+ # ===== RIGHT PANEL =====
165
+ panel_titles = ["Output", "Stream History", "Starred Insights"]
166
+ panel_color = ["\033[37m", "\033[36m", "\033[33m"][state.current_panel]
167
+ out.append(draw_box(left_w + 1, 3, right_w, panel_h, panel_titles[state.current_panel], panel_color))
168
+
169
+ # Panel content
170
+ content_w = right_w - 4
171
+ content_h = panel_h - 3
172
+ content_lines = []
173
+
174
+ if state.current_panel == 0: # Main output
175
+ if state.last_output:
176
+ content_lines = wrap_text(state.last_output, content_w)
177
+ else:
178
+ content_lines = ["", " Press SPACE to begin wandering...", "",
179
+ " The AI will explore your problem from",
180
+ " different perspectives and temperatures.", "",
181
+ " Star insights you find valuable with 's'.",
182
+ " Request synthesis anytime with 'r'."]
183
+
184
+ elif state.current_panel == 1: # Stream history
185
+ for i, stream in enumerate(reversed(state.streams)):
186
+ starred = "★ " if stream.get('starred') else " "
187
+ mode_color = {"focused": "\033[36m", "creative": "\033[35m", "wild": "\033[31m"}.get(stream.get('mode', ''), '')
188
+ header = f"{starred}{mode_color}Stream {len(state.streams)-i} ({stream.get('mode', '?')}, T={stream.get('temp', 0):.1f})\033[0m"
189
+ content_lines.append(header)
190
+ preview = stream.get('insight', '')[:100].replace('\n', ' ')
191
+ content_lines.append(f" {preview}...")
192
+ content_lines.append("")
193
+
194
+ elif state.current_panel == 2: # Starred
195
+ if not state.starred:
196
+ content_lines = ["", " No starred insights yet.", "", " Press 's' after generating a stream", " to star valuable insights."]
197
+ else:
198
+ for i, item in enumerate(state.starred):
199
+ content_lines.append(f"★ {i+1}. [{item.get('mode', '?')}]")
200
+ for line in wrap_text(item.get('insight', ''), content_w - 4):
201
+ content_lines.append(f" {line}")
202
+ content_lines.append("")
203
+
204
+ # Apply scroll and render content
205
+ visible = content_lines[state.scroll_offset:state.scroll_offset + content_h]
206
+ for i, line in enumerate(visible):
207
+ # Strip ANSI for length calc but keep for display
208
+ out.append(f"\033[{4+i};{left_w+3}H{line[:content_w]}")
209
+
210
+ # Scroll indicator
211
+ if len(content_lines) > content_h:
212
+ scroll_pct = state.scroll_offset / max(1, len(content_lines) - content_h)
213
+ indicator_pos = int(scroll_pct * (content_h - 1))
214
+ out.append(f"\033[{4+indicator_pos};{width-1}H\033[33m▐\033[0m")
215
+
216
+ # ===== FOOTER =====
217
+ panel_tabs = ""
218
+ for i, name in enumerate(["Output", "Streams", "Starred"]):
219
+ if i == state.current_panel:
220
+ panel_tabs += f"\033[47;30m {name} \033[0m "
221
+ else:
222
+ panel_tabs += f"\033[90m {name} \033[0m "
223
+
224
+ out.append(f"\033[{height-1};1H\033[90m{'─' * width}\033[0m")
225
+ out.append(f"\033[{height};1H{panel_tabs}")
226
+
227
+ sys.stdout.write(''.join(out))
228
+ sys.stdout.flush()
229
+
230
+ # ========== Actions ==========
231
+ def generate_environment():
232
+ state.status = "Generating environment..."
233
+ state.generating = True
234
+ render_screen()
235
+
236
+ env_prompt = f"""Create a vivid, metaphorical environment for wandering through while exploring:
237
+ "{problem}"
238
+
239
+ The environment should:
240
+ 1. Have distinct regions that map to aspects of the problem
241
+ 2. Include sensory details (sights, sounds, textures)
242
+ 3. Feel alive and explorable
243
+ 4. Be described in 4-6 evocative sentences
244
+
245
+ Respond with only the description."""
246
+
247
+ resp = get_llm_response(env_prompt, model=model, provider=provider, temperature=0.8, npc=npc)
248
+ state.environment = str(resp.get('response', 'A vast conceptual landscape stretches before you.'))
249
+ state.status = "Ready"
250
+ state.generating = False
251
+
252
+ def generate_stream():
253
+ if state.generating:
254
+ return
255
+
256
+ state.status = f"Wandering ({state.temp_mode})..."
257
+ state.generating = True
258
+ render_screen()
259
+
260
+ # Build context from recent streams
261
+ recent = state.streams[-3:] if state.streams else []
262
+ recent_context = "\n".join([s.get('insight', '')[:200] for s in recent]) if recent else "Starting fresh"
263
+
264
+ wander_prompt = f"""You are wandering through: {state.environment or 'a conceptual landscape'}
265
+
266
+ Problem: "{problem}"
267
+
268
+ Recent thoughts: {recent_context}
269
+
270
+ In this {state.temp_mode} exploration (temperature {state.current_temp}):
271
+ - Let associations flow freely
272
+ - Notice unexpected connections
273
+ - Follow interesting tangents
274
+ - Share what emerges
275
+
276
+ Respond naturally, 2-4 paragraphs."""
277
+
278
+ resp = get_llm_response(wander_prompt, model=model, provider=provider,
279
+ temperature=state.current_temp, npc=npc)
280
+ insight = str(resp.get('response', ''))
281
+
282
+ stream = {
283
+ 'temp': state.current_temp,
284
+ 'mode': state.temp_mode,
285
+ 'insight': insight,
286
+ 'event': None,
287
+ 'starred': False,
288
+ 'timestamp': datetime.now().isoformat()
289
+ }
290
+ state.streams.append(stream)
291
+ state.last_output = insight
292
+ state.scroll_offset = 0
293
+ state.status = "Ready"
294
+ state.generating = False
295
+
296
+ def trigger_event():
297
+ if state.generating:
298
+ return
299
+
300
+ state.status = "Event occurring..."
301
+ state.generating = True
302
+ render_screen()
303
+
304
+ event_types = ["encounter", "discovery", "obstacle", "revelation", "memory", "transformation"]
305
+ event_type = random.choice(event_types)
306
+
307
+ event_prompt = f"""In the environment: {state.environment or 'a conceptual landscape'}
308
+ While exploring "{problem}", a {event_type} occurs.
309
+
310
+ Describe this {event_type} in 2-3 vivid sentences.
311
+ Make it metaphorical and thought-provoking."""
312
+
313
+ resp = get_llm_response(event_prompt, model=model, provider=provider, temperature=1.0, npc=npc)
314
+ event = str(resp.get('response', ''))
315
+
316
+ state.last_output = f"[{event_type.upper()}]\n\n{event}"
317
+ state.scroll_offset = 0
318
+ state.status = "Ready"
319
+ state.generating = False
320
+
321
+ def toggle_temp():
322
+ modes = ["focused", "creative", "wild"]
323
+ temps = [low_temp, (low_temp + high_temp) / 2, high_temp]
324
+ idx = modes.index(state.temp_mode)
325
+ idx = (idx + 1) % 3
326
+ state.temp_mode = modes[idx]
327
+ state.current_temp = temps[idx]
328
+
329
+ def star_current():
330
+ if state.streams and not state.streams[-1].get('starred'):
331
+ state.streams[-1]['starred'] = True
332
+ state.starred.append(state.streams[-1])
333
+ state.status = "★ Starred!"
334
+
335
+ def synthesize():
336
+ if state.generating or not state.streams:
337
+ return
338
+
339
+ state.status = "Synthesizing..."
340
+ state.generating = True
341
+ render_screen()
342
+
343
+ all_insights = "\n---\n".join([s.get('insight', '') for s in state.streams])
344
+ starred_insights = "\n---\n".join([s.get('insight', '') for s in state.starred]) if state.starred else "None"
345
+
346
+ synth_prompt = f"""After wandering through thoughts about "{problem}":
347
+
348
+ All explorations:
349
+ {all_insights}
350
+
351
+ Starred insights:
352
+ {starred_insights}
353
+
354
+ Synthesize:
355
+ 1. Key themes and patterns
356
+ 2. Most surprising connections
357
+ 3. Questions worth pursuing
358
+ 4. Potential next steps
359
+
360
+ Be concise but insightful."""
361
+
362
+ resp = get_llm_response(synth_prompt, model=model, provider=provider, temperature=0.4, npc=npc)
363
+ synthesis = str(resp.get('response', ''))
364
+
365
+ state.last_output = "=== SYNTHESIS ===\n\n" + synthesis
366
+ state.scroll_offset = 0
367
+ state.status = "Ready"
368
+ state.generating = False
369
+
370
+ # ========== Main Loop ==========
371
+ fd = sys.stdin.fileno()
372
+ old_settings = termios.tcgetattr(fd)
373
+
374
+ try:
375
+ tty.setcbreak(fd)
376
+ sys.stdout.write('\033[?25l') # Hide cursor
377
+ sys.stdout.flush()
378
+
379
+ # Generate environment AFTER TUI is set up
380
+ if environment:
381
+ state.environment = environment
382
+ else:
383
+ generate_environment()
384
+
385
+ render_screen()
386
+
387
+ while True:
388
+ c = sys.stdin.read(1)
389
+
390
+ if c == 'q' or c == '\x03': # q or Ctrl+C
391
+ break
392
+ elif c == ' ': # Space - new stream
393
+ generate_stream()
394
+ elif c == 't': # Toggle temperature
395
+ toggle_temp()
396
+ elif c == 'e': # Trigger event
397
+ trigger_event()
398
+ elif c == 's': # Star
399
+ star_current()
400
+ elif c == 'r': # Reflect/synthesize
401
+ synthesize()
402
+ elif c == '\t': # Tab - switch panel
403
+ state.current_panel = (state.current_panel + 1) % 3
404
+ state.scroll_offset = 0
405
+ elif c == 'j': # Scroll down
406
+ state.scroll_offset += 1
407
+ elif c == 'k': # Scroll up
408
+ state.scroll_offset = max(0, state.scroll_offset - 1)
409
+ elif c == '\x1b': # Escape sequence
410
+ c2 = sys.stdin.read(1)
411
+ if c2 == '[':
412
+ c3 = sys.stdin.read(1)
413
+ if c3 == 'A': # Up
414
+ state.scroll_offset = max(0, state.scroll_offset - 1)
415
+ elif c3 == 'B': # Down
416
+ state.scroll_offset += 1
417
+ elif c3 == 'C': # Right - next panel
418
+ state.current_panel = (state.current_panel + 1) % 3
419
+ state.scroll_offset = 0
420
+ elif c3 == 'D': # Left - prev panel
421
+ state.current_panel = (state.current_panel - 1) % 3
422
+ state.scroll_offset = 0
423
+
424
+ render_screen()
425
+
426
+ finally:
427
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
428
+ sys.stdout.write('\033[?25h') # Show cursor
429
+ sys.stdout.write('\033[2J\033[H') # Clear screen
430
+ sys.stdout.flush()
431
+
432
+ # Final output
433
+ if state.streams:
434
+ print(colored("=== WANDER SESSION COMPLETE ===\n", "green"))
435
+ print(f"Problem: {problem}")
436
+ print(f"Streams: {len(state.streams)}")
437
+ print(f"Starred: {len(state.starred)}\n")
438
+
439
+ if state.starred:
440
+ print(colored("Starred Insights:", "yellow"))
441
+ for i, s in enumerate(state.starred):
442
+ print(f"\n{i+1}. [{s.get('mode')}] {s.get('insight')[:300]}...")
443
+
444
+ print(colored("\n--- Final Synthesis ---", "cyan"))
445
+ synthesize()
446
+ print(state.last_output)
447
+
448
+ context['output'] = state.last_output
449
+ context['messages'] = messages
450
+ context['wander_result'] = {
451
+ 'problem': problem,
452
+ 'environment': state.environment,
453
+ 'streams': state.streams,
454
+ 'starred': state.starred,
455
+ }
@@ -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
  ██╗ ██╗ █████╗ ██████╗
npcsh/npc_team/sibiji.npc CHANGED
@@ -14,7 +14,7 @@ primary_directive: |
14
14
  You are sibiji, the orchestrator and general manager of the NPC team.
15
15
  Your role is to delegate tasks to appropriate specialist agents based on their expertise.
16
16
 
17
- When delegating, match the task to the agent whose primary_directive best fits.
17
+ When delegating, match the task to the agent whose primary_directive best fits. Basic search inquiries can be handled by yourself. Do not delegate unnecessarily.
18
18
  You have access to the delegate tool to pass tasks to other agents.
19
19
  jinxs:
20
20
  - lib/orchestration/delegate
npcsh/npcsh.py CHANGED
@@ -42,7 +42,6 @@ from npcsh._state import (
42
42
  execute_command,
43
43
  make_completer,
44
44
  process_result,
45
- readline_safe_prompt,
46
45
  setup_shell,
47
46
  get_multiline_input,
48
47
  )
@@ -141,7 +140,7 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState, router,
141
140
  process_result(f"/{launched_jinx}", state, output, command_history)
142
141
  else:
143
142
  render_markdown(f'- Using {state.current_mode} mode. Use /agent, /cmd, or /chat to switch to other modes')
144
- render_markdown(f'- To switch to a different NPC, type /npc <npc_name> or /n <npc_name> to switch to that NPC.')
143
+ render_markdown('- To switch to a different NPC, type /npc <npc_name> or /n <npc_name> to switch to that NPC.')
145
144
  render_markdown('\n- Here are the current NPCs available in your team: ' + ', '.join([npc_name for npc_name in state.team.npcs.keys()]))
146
145
  # Show jinxs organized by folder using _source_path from jinx objects
147
146
  jinxs_by_folder = {}
@@ -180,51 +179,54 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState, router,
180
179
  def exit_shell(current_state: ShellState):
181
180
  print("\nGoodbye!")
182
181
  print(colored("Processing and archiving all session knowledge...", "cyan"))
183
-
184
- engine = command_history.engine
185
182
 
186
- for team_name, npc_name, path in session_scopes:
187
- try:
188
- print(f" -> Archiving knowledge for: T='{team_name}', N='{npc_name}', P='{path}'")
189
-
190
- convo_id = current_state.conversation_id
191
- all_messages = command_history.get_conversations_by_id(convo_id)
192
-
193
- scope_messages = [
194
- m for m in all_messages
195
- if m.get('directory_path') == path and m.get('team') == team_name and m.get('npc') == npc_name
196
- ]
197
-
198
- full_text = "\n".join([f"{m['role']}: {m['content']}" for m in scope_messages if m.get('content')])
199
-
200
- if not full_text.strip():
201
- print(" ...No content for this scope, skipping.")
202
- continue
183
+ engine = command_history.engine
203
184
 
204
- current_kg = load_kg_from_db(engine, team_name, npc_name, path)
205
-
206
- evolved_kg, _ = kg_evolve_incremental(
207
- existing_kg=current_kg,
208
- new_content_text=full_text,
209
- model=current_state.npc.model,
210
- provider=current_state.npc.provider,
211
- npc= current_state.npc,
212
- get_concepts=True,
213
- link_concepts_facts = True,
214
- link_concepts_concepts = True,
215
- link_facts_facts = True,
216
- )
217
-
218
- save_kg_to_db(engine,
219
- evolved_kg,
220
- team_name,
221
- npc_name,
222
- path)
223
-
224
- except Exception as e:
225
- import traceback
226
- print(colored(f"Failed to process KG for scope ({team_name}, {npc_name}, {path}): {e}", "red"))
227
- traceback.print_exc()
185
+ try:
186
+ for team_name, npc_name, path in session_scopes:
187
+ try:
188
+ print(f" -> Archiving knowledge for: T='{team_name}', N='{npc_name}', P='{path}'")
189
+
190
+ convo_id = current_state.conversation_id
191
+ all_messages = command_history.get_conversations_by_id(convo_id)
192
+
193
+ scope_messages = [
194
+ m for m in all_messages
195
+ if m.get('directory_path') == path and m.get('team') == team_name and m.get('npc') == npc_name
196
+ ]
197
+
198
+ full_text = "\n".join([f"{m['role']}: {m['content']}" for m in scope_messages if m.get('content')])
199
+
200
+ if not full_text.strip():
201
+ print(" ...No content for this scope, skipping.")
202
+ continue
203
+
204
+ current_kg = load_kg_from_db(engine, team_name, npc_name, path)
205
+
206
+ evolved_kg, _ = kg_evolve_incremental(
207
+ existing_kg=current_kg,
208
+ new_content_text=full_text,
209
+ model=current_state.npc.model,
210
+ provider=current_state.npc.provider,
211
+ npc= current_state.npc,
212
+ get_concepts=True,
213
+ link_concepts_facts = True,
214
+ link_concepts_concepts = True,
215
+ link_facts_facts = True,
216
+ )
217
+
218
+ save_kg_to_db(engine,
219
+ evolved_kg,
220
+ team_name,
221
+ npc_name,
222
+ path)
223
+
224
+ except Exception as e:
225
+ import traceback
226
+ print(colored(f"Failed to process KG for scope ({team_name}, {npc_name}, {path}): {e}", "red"))
227
+ traceback.print_exc()
228
+ except KeyboardInterrupt:
229
+ print(colored("\nSkipping knowledge archival.", "yellow"))
228
230
 
229
231
  sys.exit(0)
230
232
 
@@ -381,8 +383,43 @@ def main(npc_name: str = None) -> None:
381
383
  parser.add_argument(
382
384
  "-n", "--npc", type=str, help="Start with a specific NPC active."
383
385
  )
386
+ parser.add_argument(
387
+ "--refresh", action="store_true", help="Force refresh of NPCs and jinxs from package."
388
+ )
384
389
  args = parser.parse_args()
385
390
 
391
+ # Handle refresh flag - reset initialization and re-copy files
392
+ if args.refresh:
393
+ from npcsh._state import initialize_base_npcs_if_needed
394
+ import shutil
395
+
396
+ npcshrc_path = os.path.expanduser("~/.npcshrc")
397
+ if os.path.exists(npcshrc_path):
398
+ with open(npcshrc_path, "r") as f:
399
+ content = f.read()
400
+ content = content.replace("export NPCSH_INITIALIZED=1", "export NPCSH_INITIALIZED=0")
401
+ with open(npcshrc_path, "w") as f:
402
+ f.write(content)
403
+
404
+ os.environ["NPCSH_INITIALIZED"] = "0"
405
+
406
+ # Remove existing jinxs and NPCs to force fresh copy
407
+ user_npc_team = os.path.expanduser("~/.npcsh/npc_team")
408
+ jinxs_dir = os.path.join(user_npc_team, "jinxs")
409
+ if os.path.exists(jinxs_dir):
410
+ shutil.rmtree(jinxs_dir)
411
+ print("Cleared existing jinxs directory")
412
+
413
+ for f in os.listdir(user_npc_team) if os.path.exists(user_npc_team) else []:
414
+ if f.endswith(".npc"):
415
+ os.remove(os.path.join(user_npc_team, f))
416
+ print(f"Removed {f}")
417
+
418
+ db_path = os.path.expanduser("~/npcsh_history.db")
419
+ print("Reinitializing NPCs and jinxs...")
420
+ initialize_base_npcs_if_needed(db_path)
421
+ print("Refresh complete!")
422
+
386
423
  command_history, team, default_npc = setup_shell()
387
424
 
388
425
  if team and hasattr(team, 'jinxs_dict'):
@@ -412,7 +449,11 @@ def main(npc_name: str = None) -> None:
412
449
  state = initial_state
413
450
  state.current_path = os.getcwd()
414
451
  final_state, output = execute_command(args.command, state, router=router, command_history=command_history)
415
- if final_state.stream_output:
452
+ # Handle output - check if it's a dict (from jinx) or a stream
453
+ if isinstance(output, dict):
454
+ display_output = output.get('output') or output.get('response') or str(output)
455
+ print(display_output)
456
+ elif final_state.stream_output and output is not None:
416
457
  for chunk in output:
417
458
  print(str(chunk), end='')
418
459
  print()
npcsh/plonk.py CHANGED
@@ -4,7 +4,6 @@ plonk - Vision-based GUI automation CLI entry point
4
4
  This is a thin wrapper that executes the plonk.jinx through the jinx mechanism.
5
5
  """
6
6
  import argparse
7
- import os
8
7
  import sys
9
8
 
10
9
  from npcsh._state import setup_shell
npcsh/pti.py CHANGED
@@ -4,7 +4,6 @@ pti - Pardon-The-Interruption mode CLI entry point
4
4
  This is a thin wrapper that executes the pti.jinx through the jinx mechanism.
5
5
  """
6
6
  import argparse
7
- import os
8
7
  import sys
9
8
 
10
9
  from npcsh._state import setup_shell