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,16 +1,22 @@
1
1
  jinx_name: jinxs
2
- description: "Show available jinxs organized by folder. Use /jinxs <path> for details on a specific folder."
2
+ description: Interactive browser for available jinxs
3
3
  inputs:
4
- - path: "" # Optional path to show details for (e.g., "lib/core", "bin")
4
+ - path: ""
5
+ - text: "false"
6
+
5
7
  steps:
6
8
  - name: list_jinxs
7
9
  engine: python
8
10
  code: |
9
11
  import os
12
+ import sys
13
+ import tty
14
+ import termios
10
15
  from pathlib import Path
11
16
  import yaml
12
17
 
13
18
  filter_path = context.get('path', '').strip()
19
+ text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
14
20
 
15
21
  # Find jinxs directory from team or fallback
16
22
  jinxs_dir = None
@@ -23,154 +29,303 @@ steps:
23
29
  jinxs_dir = candidate
24
30
 
25
31
  if not jinxs_dir:
26
- # Fallback to global jinxs
27
32
  global_jinxs = Path.home() / ".npcsh" / "npc_team" / "jinxs"
28
33
  if global_jinxs.exists():
29
34
  jinxs_dir = global_jinxs
30
35
 
31
36
  if not jinxs_dir or not jinxs_dir.exists():
32
- output = "Error: Could not find jinxs directory"
33
- exit()
34
-
35
- def get_jinx_info(jinx_path):
36
- """Extract name and description from a jinx file."""
37
- try:
38
- with open(jinx_path, 'r') as f:
39
- content = f.read()
40
- # Parse just the header (before steps:)
41
- header = content.split('steps:')[0] if 'steps:' in content else content
42
- data = yaml.safe_load(header)
43
- name = data.get('jinx_name', jinx_path.stem)
44
- desc = data.get('description', 'No description')
45
- return name, desc
46
- except:
47
- return jinx_path.stem, 'No description'
48
-
49
- def get_folder_structure(base_path):
50
- """Get jinxs organized by folder."""
51
- structure = {}
52
- for root, dirs, files in os.walk(base_path):
53
- # Skip hidden directories
54
- dirs[:] = [d for d in dirs if not d.startswith('.')]
55
-
56
- jinx_files = [f for f in files if f.endswith('.jinx')]
57
- if jinx_files:
37
+ context['output'] = "Error: Could not find jinxs directory"
38
+ else:
39
+ def get_jinx_info(jinx_path):
40
+ try:
41
+ with open(jinx_path, 'r') as f:
42
+ content = f.read()
43
+ header = content.split('steps:')[0] if 'steps:' in content else content
44
+ data = yaml.safe_load(header)
45
+ name = data.get('jinx_name', jinx_path.stem)
46
+ desc = data.get('description', 'No description')
47
+ inputs = data.get('inputs', [])
48
+ return name, desc, inputs
49
+ except:
50
+ return jinx_path.stem, 'No description', []
51
+
52
+ def get_all_jinxs(base_path):
53
+ jinxs = []
54
+ folders = set()
55
+ for root, dirs, files in os.walk(base_path):
56
+ dirs[:] = [d for d in dirs if not d.startswith('.')]
58
57
  rel_path = Path(root).relative_to(base_path)
59
- rel_str = str(rel_path) if str(rel_path) != '.' else 'root'
60
- structure[rel_str] = []
61
- for jf in sorted(jinx_files):
62
- jinx_path = Path(root) / jf
63
- name, desc = get_jinx_info(jinx_path)
64
- structure[rel_str].append((name, desc, jf))
65
- return structure
66
-
67
- output_lines = []
68
-
69
- if filter_path:
70
- # Show details for a specific path
71
- target_path = jinxs_dir / filter_path
72
- if not target_path.exists():
73
- # Try to find a matching folder
74
- matches = []
75
- for root, dirs, files in os.walk(jinxs_dir):
76
- rel = Path(root).relative_to(jinxs_dir)
77
- if filter_path in str(rel) or filter_path in Path(root).name:
78
- matches.append(rel)
79
-
80
- if matches:
81
- output_lines.append(f"No exact match for '{filter_path}'. Did you mean:\n")
82
- for m in matches[:5]:
83
- output_lines.append(f" /jinxs {m}\n")
84
- output = "".join(output_lines)
85
- exit()
86
- else:
87
- output = f"No jinxs found at path: {filter_path}"
88
- exit()
89
-
90
- # Get jinxs in this path
91
- structure = get_folder_structure(target_path)
92
- if not structure:
93
- # Check if it's a single folder with jinxs
94
- jinx_files = list(target_path.glob("*.jinx"))
95
- if jinx_files:
96
- output_lines.append(f"Jinxs in {filter_path}:\n\n")
97
- for jf in sorted(jinx_files):
98
- name, desc = get_jinx_info(jf)
99
- output_lines.append(f" /{name}\n")
100
- output_lines.append(f" {desc}\n\n")
101
- else:
102
- output = f"No jinxs found at path: {filter_path}"
103
- exit()
104
- else:
105
- output_lines.append(f"Jinxs in {filter_path}:\n\n")
106
- for folder, jinxs in sorted(structure.items()):
58
+ folder = str(rel_path) if str(rel_path) != '.' else 'root'
107
59
  if folder != 'root':
108
- output_lines.append(f" {folder}/\n")
109
- for name, desc, filename in jinxs:
110
- prefix = " " if folder != 'root' else " "
111
- output_lines.append(f"{prefix}/{name} - {desc}\n")
112
- output_lines.append("\n")
60
+ folders.add(folder.split('/')[0])
61
+ for jf in files:
62
+ if jf.endswith('.jinx'):
63
+ jinx_path = Path(root) / jf
64
+ name, desc, inputs = get_jinx_info(jinx_path)
65
+ jinxs.append({
66
+ 'name': name,
67
+ 'description': desc,
68
+ 'inputs': inputs,
69
+ 'folder': folder,
70
+ 'path': str(jinx_path)
71
+ })
72
+ return jinxs, sorted(folders)
113
73
 
114
- else:
115
- # Show overview organized by folder
116
- structure = get_folder_structure(jinxs_dir)
117
-
118
- output_lines.append("Available Jinxs\n")
119
- output_lines.append("=" * 40 + "\n\n")
120
-
121
- # Group by top-level folder
122
- top_level = {}
123
- for folder, jinxs in structure.items():
124
- if folder == 'root':
125
- top = 'root'
126
- else:
127
- top = folder.split('/')[0] if '/' in folder else folder
128
-
129
- if top not in top_level:
130
- top_level[top] = {'subfolders': {}, 'jinxs': []}
131
-
132
- if folder == top or folder == 'root':
133
- top_level[top]['jinxs'].extend(jinxs)
134
- else:
135
- subfolder = '/'.join(folder.split('/')[1:])
136
- if subfolder not in top_level[top]['subfolders']:
137
- top_level[top]['subfolders'][subfolder] = []
138
- top_level[top]['subfolders'][subfolder].extend(jinxs)
139
-
140
- # Display
141
- folder_order = ['bin', 'lib', 'npc_studio', 'root']
142
- sorted_folders = sorted(top_level.keys(), key=lambda x: (folder_order.index(x) if x in folder_order else 99, x))
143
-
144
- for top in sorted_folders:
145
- data = top_level[top]
146
-
147
- if top == 'root':
148
- if data['jinxs']:
149
- output_lines.append("Root Jinxs:\n")
150
- for name, desc, _ in data['jinxs']:
151
- output_lines.append(f" /{name} - {desc}\n")
152
- output_lines.append("\n")
153
- else:
154
- total = len(data['jinxs'])
155
- for sf_jinxs in data['subfolders'].values():
156
- total += len(sf_jinxs)
157
-
158
- output_lines.append(f"{top}/ ({total} jinxs)\n")
159
-
160
- # Show direct jinxs
161
- if data['jinxs']:
162
- for name, desc, _ in data['jinxs'][:3]:
163
- output_lines.append(f" /{name} - {desc}\n")
164
- if len(data['jinxs']) > 3:
165
- output_lines.append(f" ... and {len(data['jinxs']) - 3} more\n")
166
-
167
- # Show subfolders summary
168
- if data['subfolders']:
169
- for subfolder, jinxs in sorted(data['subfolders'].items()):
170
- output_lines.append(f" {subfolder}/ ({len(jinxs)} jinxs)\n")
171
-
172
- output_lines.append(f" → /jinxs {top} for details\n\n")
173
-
174
- output_lines.append("Use /jinxs <path> for details (e.g., /jinxs lib/core)\n")
175
-
176
- output = "".join(output_lines)
74
+ all_jinxs, folders = get_all_jinxs(jinxs_dir)
75
+ folders = ['root'] + list(folders)
76
+
77
+ if text_mode or not all_jinxs:
78
+ # Text-only output
79
+ output_lines = ["Available Jinxs", "=" * 40, ""]
80
+ by_folder = {}
81
+ for j in all_jinxs:
82
+ f = j['folder']
83
+ if f not in by_folder:
84
+ by_folder[f] = []
85
+ by_folder[f].append(j)
86
+
87
+ for folder in sorted(by_folder.keys()):
88
+ if folder == 'root':
89
+ output_lines.append("Root:")
90
+ else:
91
+ output_lines.append(f"{folder}/:")
92
+ for j in by_folder[folder]:
93
+ output_lines.append(f" /{j['name']} - {j['description'][:50]}")
94
+ output_lines.append("")
95
+
96
+ output_lines.append("Use /jinxs path=<folder> for details")
97
+ output_lines.append("Use text=false for interactive TUI")
98
+ context['output'] = "\n".join(output_lines)
99
+ else:
100
+ # Interactive TUI mode
101
+ def get_terminal_size():
102
+ try:
103
+ size = os.get_terminal_size()
104
+ return size.columns, size.lines
105
+ except:
106
+ return 80, 24
107
+
108
+ width, height = get_terminal_size()
109
+ selected_folder = 0
110
+ selected_jinx = 0
111
+ jinx_scroll = 0
112
+ list_height = height - 5
113
+ mode = 'list' # list or preview
114
+ preview_scroll = 0
115
+ filter_text = ''
116
+
117
+ def get_jinxs_in_folder(folder):
118
+ if folder == 'root':
119
+ return [j for j in all_jinxs if j['folder'] == 'root']
120
+ return [j for j in all_jinxs if j['folder'].startswith(folder)]
121
+
122
+ def filter_jinxs(jinxs, text):
123
+ if not text:
124
+ return jinxs
125
+ text = text.lower()
126
+ return [j for j in jinxs if text in j['name'].lower() or text in j['description'].lower()]
127
+
128
+ current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
129
+
130
+ fd = sys.stdin.fileno()
131
+ old_settings = termios.tcgetattr(fd)
132
+
133
+ try:
134
+ tty.setcbreak(fd)
135
+ sys.stdout.write('\033[?25l')
136
+ sys.stdout.write('\033[2J\033[H')
137
+
138
+ while True:
139
+ width, height = get_terminal_size()
140
+ list_height = height - 5
141
+
142
+ if mode == 'list':
143
+ if selected_jinx < jinx_scroll:
144
+ jinx_scroll = selected_jinx
145
+ elif selected_jinx >= jinx_scroll + list_height:
146
+ jinx_scroll = selected_jinx - list_height + 1
147
+
148
+ sys.stdout.write('\033[H')
149
+
150
+ # Header
151
+ if mode == 'list':
152
+ f = folders[selected_folder] if folders else 'none'
153
+ flt = f" filter:'{filter_text}'" if filter_text else ""
154
+ header = f" JINXS ({len(current_jinxs)}): {f}{flt} "
155
+ else:
156
+ j = current_jinxs[selected_jinx] if current_jinxs else {}
157
+ header = f" PREVIEW: /{j.get('name', '')} "
158
+ sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
159
+
160
+ # Folder bar
161
+ folder_bar = ""
162
+ for i, f in enumerate(folders[:8]):
163
+ if i == selected_folder:
164
+ folder_bar += f'\033[47;30m [{f[:8]}] \033[0m'
165
+ else:
166
+ folder_bar += f' {f[:8]} '
167
+ if len(folders) > 8:
168
+ folder_bar += f'...+{len(folders)-8}'
169
+ sys.stdout.write(f'{folder_bar[:width]}\n')
170
+
171
+ if mode == 'list':
172
+ for i in range(list_height):
173
+ idx = jinx_scroll + i
174
+ sys.stdout.write(f'\033[{3+i};1H\033[K')
175
+ if idx >= len(current_jinxs):
176
+ continue
177
+
178
+ j = current_jinxs[idx]
179
+ name = j['name'][:20]
180
+ desc = j['description'][:width-25]
181
+
182
+ if idx == selected_jinx:
183
+ sys.stdout.write(f'\033[47;30;1m>/{name:<20} {desc}\033[0m')
184
+ else:
185
+ sys.stdout.write(f' /{name:<20} {desc}')
186
+
187
+ # Status bar
188
+ sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
189
+ j = current_jinxs[selected_jinx] if current_jinxs else {}
190
+ path = j.get('path', '')[-50:] if j else ''
191
+ sys.stdout.write(f'\033[{height-1};1H\033[K {path}'.ljust(width))
192
+ sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m h/l:Folder j/k:Jinx p:Preview Enter:Run f:Filter q:Quit [{selected_jinx+1}/{len(current_jinxs)}] \033[0m')
193
+
194
+ else: # preview mode
195
+ j = current_jinxs[selected_jinx]
196
+ preview_lines = [
197
+ f"Name: /{j['name']}",
198
+ "",
199
+ f"Description:",
200
+ f" {j['description']}",
201
+ "",
202
+ f"Path: {j['path']}",
203
+ f"Folder: {j['folder']}",
204
+ "",
205
+ ]
206
+
207
+ if j.get('inputs'):
208
+ preview_lines.append("Inputs:")
209
+ for inp in j['inputs']:
210
+ if isinstance(inp, dict):
211
+ for k, v in inp.items():
212
+ default = f" (default: {v})" if v else ""
213
+ preview_lines.append(f" - {k}{default}")
214
+ else:
215
+ preview_lines.append(f" - {inp}")
216
+ preview_lines.append("")
217
+
218
+ preview_lines.append("Usage:")
219
+ usage = f" /{j['name']}"
220
+ if j.get('inputs'):
221
+ for inp in j['inputs'][:3]:
222
+ if isinstance(inp, dict):
223
+ for k, v in inp.items():
224
+ usage += f" {k}=<value>"
225
+ preview_lines.append(usage)
226
+
227
+ for i in range(list_height):
228
+ idx = preview_scroll + i
229
+ sys.stdout.write(f'\033[{3+i};1H\033[K')
230
+ if idx < len(preview_lines):
231
+ sys.stdout.write(preview_lines[idx][:width-1])
232
+
233
+ sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
234
+ sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(preview_lines)} lines]')
235
+ sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back Enter:Run q:Quit \033[0m')
236
+
237
+ sys.stdout.flush()
238
+
239
+ c = sys.stdin.read(1)
240
+
241
+ if c == '\x1b':
242
+ c2 = sys.stdin.read(1)
243
+ if c2 == '[':
244
+ c3 = sys.stdin.read(1)
245
+ if c3 == 'A': # Up
246
+ if mode == 'list' and selected_jinx > 0:
247
+ selected_jinx -= 1
248
+ elif mode == 'preview' and preview_scroll > 0:
249
+ preview_scroll -= 1
250
+ elif c3 == 'B': # Down
251
+ if mode == 'list' and selected_jinx < len(current_jinxs) - 1:
252
+ selected_jinx += 1
253
+ elif mode == 'preview' and preview_scroll < 50:
254
+ preview_scroll += 1
255
+ elif c3 == 'C': # Right
256
+ if mode == 'list' and selected_folder < len(folders) - 1:
257
+ selected_folder += 1
258
+ current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
259
+ selected_jinx = 0
260
+ jinx_scroll = 0
261
+ elif c3 == 'D': # Left
262
+ if mode == 'list' and selected_folder > 0:
263
+ selected_folder -= 1
264
+ current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
265
+ selected_jinx = 0
266
+ jinx_scroll = 0
267
+ else:
268
+ if mode == 'preview':
269
+ mode = 'list'
270
+ sys.stdout.write('\033[2J\033[H')
271
+ else:
272
+ context['output'] = "Cancelled."
273
+ break
274
+ continue
275
+
276
+ if c == 'q' or c == '\x03':
277
+ context['output'] = "Cancelled."
278
+ break
279
+ elif c == 'k':
280
+ if mode == 'list' and selected_jinx > 0:
281
+ selected_jinx -= 1
282
+ elif mode == 'preview' and preview_scroll > 0:
283
+ preview_scroll -= 1
284
+ elif c == 'j':
285
+ if mode == 'list' and selected_jinx < len(current_jinxs) - 1:
286
+ selected_jinx += 1
287
+ elif mode == 'preview' and preview_scroll < 50:
288
+ preview_scroll += 1
289
+ elif c == 'h' and mode == 'list':
290
+ if selected_folder > 0:
291
+ selected_folder -= 1
292
+ current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
293
+ selected_jinx = 0
294
+ jinx_scroll = 0
295
+ elif c == 'l' and mode == 'list':
296
+ if selected_folder < len(folders) - 1:
297
+ selected_folder += 1
298
+ current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
299
+ selected_jinx = 0
300
+ jinx_scroll = 0
301
+ elif c == 'f' and mode == 'list':
302
+ # Toggle filter - cycle through common filters or clear
303
+ if not filter_text:
304
+ filter_text = 'search'
305
+ elif filter_text == 'search':
306
+ filter_text = 'core'
307
+ elif filter_text == 'core':
308
+ filter_text = 'browser'
309
+ else:
310
+ filter_text = ''
311
+ current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
312
+ selected_jinx = 0
313
+ jinx_scroll = 0
314
+ elif c == 'p' and mode == 'list' and current_jinxs:
315
+ mode = 'preview'
316
+ preview_scroll = 0
317
+ sys.stdout.write('\033[2J\033[H')
318
+ elif c == 'b' and mode == 'preview':
319
+ mode = 'list'
320
+ sys.stdout.write('\033[2J\033[H')
321
+ elif c in ('\r', '\n') and current_jinxs:
322
+ j = current_jinxs[selected_jinx]
323
+ context['output'] = f"Run: /{j['name']}\n\nDescription: {j['description']}"
324
+ context['selected_jinx'] = j
325
+ break
326
+
327
+ finally:
328
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
329
+ sys.stdout.write('\033[?25h')
330
+ sys.stdout.write('\033[2J\033[H')
331
+ sys.stdout.flush()
@@ -1,8 +1,8 @@
1
1
  jinx_name: "serve"
2
2
  description: "Serve an NPC Team"
3
3
  inputs:
4
- - port: 5337 # The port to run the Flask server on.
5
- - cors: "" # Comma-separated CORS origins.
4
+ - port: 5337
5
+ - cors: ""
6
6
  steps:
7
7
  - name: "start_flask_server"
8
8
  engine: "python"
@@ -1,8 +1,8 @@
1
1
  jinx_name: "set"
2
2
  description: "Set configuration values"
3
3
  inputs:
4
- - key: "" # The configuration key to set.
5
- - value: "" # The value to set for the configuration key.
4
+ - key: ""
5
+ - value: ""
6
6
  steps:
7
7
  - name: "set_config_value"
8
8
  engine: "python"
@@ -1,9 +1,9 @@
1
1
  jinx_name: switch
2
2
  description: Get or set a switch in the .ctx file
3
3
  inputs:
4
- - name: "" # Switch name
5
- - value: null # Value to set (omit to get current value)
6
- - scope: "workspace" # "workspace" or "global"
4
+ - name: ""
5
+ - value: null
6
+ - scope: "workspace"
7
7
 
8
8
  steps:
9
9
  - name: manage_switch
@@ -1,7 +1,7 @@
1
1
  jinx_name: switches
2
2
  description: List all switches from .ctx files
3
3
  inputs:
4
- - scope: "all" # "workspace", "global", or "all"
4
+ - scope: "all"
5
5
 
6
6
  steps:
7
7
  - name: list_switches
@@ -1,8 +1,8 @@
1
1
  jinx_name: teamviz
2
2
  description: "Visualize NPC team structure - NPCs, jinxs, and their relationships"
3
3
  inputs:
4
- - team_path: ""
5
- - save: ""
4
+ - team_path: ""
5
+ - save: ""
6
6
 
7
7
  steps:
8
8
  - name: visualize_team