npcsh 1.1.18__py3-none-any.whl → 1.1.20__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 (165) hide show
  1. npcsh/_state.py +19 -7
  2. npcsh/benchmark/npcsh_agent.py +47 -16
  3. npcsh/config.py +1 -0
  4. npcsh/diff_viewer.py +452 -0
  5. npcsh/npc_team/jinxs/bin/config_tui.jinx +300 -0
  6. npcsh/npc_team/jinxs/bin/jinxs.jinx +407 -0
  7. npcsh/npc_team/jinxs/bin/kg.jinx +941 -0
  8. npcsh/npc_team/jinxs/bin/memories.jinx +317 -0
  9. npcsh/npc_team/jinxs/bin/models.jinx +343 -0
  10. npcsh/npc_team/jinxs/bin/nql.jinx +380 -50
  11. npcsh/npc_team/jinxs/bin/setup.jinx +241 -0
  12. npcsh/npc_team/jinxs/bin/sync.jinx +143 -150
  13. npcsh/npc_team/jinxs/bin/team.jinx +504 -0
  14. npcsh/npc_team/jinxs/incognide/add_tab.jinx +1 -1
  15. npcsh/npc_team/jinxs/incognide/close_pane.jinx +1 -1
  16. npcsh/npc_team/jinxs/incognide/close_tab.jinx +1 -1
  17. npcsh/npc_team/jinxs/incognide/confirm.jinx +1 -1
  18. npcsh/npc_team/jinxs/incognide/focus_pane.jinx +1 -1
  19. npcsh/npc_team/jinxs/incognide/list_panes.jinx +1 -1
  20. npcsh/npc_team/jinxs/incognide/navigate.jinx +1 -1
  21. npcsh/npc_team/jinxs/incognide/notify.jinx +1 -1
  22. npcsh/npc_team/jinxs/incognide/open_pane.jinx +1 -1
  23. npcsh/npc_team/jinxs/incognide/read_pane.jinx +1 -1
  24. npcsh/npc_team/jinxs/incognide/run_terminal.jinx +1 -1
  25. npcsh/npc_team/jinxs/incognide/send_message.jinx +1 -1
  26. npcsh/npc_team/jinxs/incognide/split_pane.jinx +1 -1
  27. npcsh/npc_team/jinxs/incognide/switch_npc.jinx +1 -1
  28. npcsh/npc_team/jinxs/incognide/switch_tab.jinx +1 -1
  29. npcsh/npc_team/jinxs/incognide/write_file.jinx +1 -1
  30. npcsh/npc_team/jinxs/incognide/zen_mode.jinx +1 -1
  31. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +1 -1
  32. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +1 -1
  33. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +1 -1
  34. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +1 -1
  35. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +1 -1
  36. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +1 -1
  37. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +1 -1
  38. npcsh/npc_team/jinxs/modes/alicanto.jinx +1 -1
  39. npcsh/npc_team/jinxs/modes/arxiv.jinx +1 -1
  40. npcsh/npc_team/jinxs/modes/corca.jinx +1 -1
  41. npcsh/npc_team/jinxs/modes/guac.jinx +4 -6
  42. npcsh/npc_team/jinxs/modes/plonk.jinx +1 -1
  43. npcsh/npc_team/jinxs/modes/pti.jinx +1 -1
  44. npcsh/npc_team/jinxs/modes/reattach.jinx +1 -1
  45. npcsh/npc_team/jinxs/modes/spool.jinx +1 -1
  46. npcsh/npc_team/jinxs/modes/wander.jinx +1 -1
  47. npcsh/routes.py +8 -2
  48. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/add_tab.jinx +1 -1
  49. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/alicanto.jinx +1 -1
  50. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/arxiv.jinx +1 -1
  51. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/close_pane.jinx +1 -1
  52. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/close_tab.jinx +1 -1
  53. npcsh-1.1.20.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  54. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/confirm.jinx +1 -1
  55. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca.jinx +1 -1
  56. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/db_search.jinx +1 -1
  57. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/file_search.jinx +1 -1
  58. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/focus_pane.jinx +1 -1
  59. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/guac.jinx +4 -6
  60. npcsh-1.1.20.data/data/npcsh/npc_team/jinxs.jinx +407 -0
  61. npcsh-1.1.20.data/data/npcsh/npc_team/kg.jinx +941 -0
  62. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/kg_search.jinx +1 -1
  63. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/list_panes.jinx +1 -1
  64. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/mem_search.jinx +1 -1
  65. npcsh-1.1.20.data/data/npcsh/npc_team/memories.jinx +317 -0
  66. npcsh-1.1.20.data/data/npcsh/npc_team/models.jinx +343 -0
  67. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/navigate.jinx +1 -1
  68. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/notify.jinx +1 -1
  69. npcsh-1.1.20.data/data/npcsh/npc_team/nql.jinx +471 -0
  70. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/open_pane.jinx +1 -1
  71. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/paper_search.jinx +1 -1
  72. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonk.jinx +1 -1
  73. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/pti.jinx +1 -1
  74. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/read_pane.jinx +1 -1
  75. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/reattach.jinx +1 -1
  76. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/run_terminal.jinx +1 -1
  77. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/semantic_scholar.jinx +1 -1
  78. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/send_message.jinx +1 -1
  79. npcsh-1.1.20.data/data/npcsh/npc_team/setup.jinx +241 -0
  80. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/split_pane.jinx +1 -1
  81. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/spool.jinx +1 -1
  82. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switch_npc.jinx +1 -1
  83. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switch_tab.jinx +1 -1
  84. npcsh-1.1.20.data/data/npcsh/npc_team/sync.jinx +223 -0
  85. npcsh-1.1.20.data/data/npcsh/npc_team/team.jinx +504 -0
  86. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/wander.jinx +1 -1
  87. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/web_search.jinx +1 -1
  88. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/write_file.jinx +1 -1
  89. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/zen_mode.jinx +1 -1
  90. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/METADATA +21 -14
  91. npcsh-1.1.20.dist-info/RECORD +248 -0
  92. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/entry_points.txt +7 -0
  93. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +0 -331
  94. npcsh-1.1.18.data/data/npcsh/npc_team/jinxs.jinx +0 -331
  95. npcsh-1.1.18.data/data/npcsh/npc_team/nql.jinx +0 -141
  96. npcsh-1.1.18.data/data/npcsh/npc_team/sync.jinx +0 -230
  97. npcsh-1.1.18.dist-info/RECORD +0 -235
  98. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  99. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/alicanto.png +0 -0
  100. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/benchmark.jinx +0 -0
  101. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  102. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  103. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/build.jinx +0 -0
  104. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/chat.jinx +0 -0
  105. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/click.jinx +0 -0
  106. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  107. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  108. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/compile.jinx +0 -0
  109. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/compress.jinx +0 -0
  110. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/convene.jinx +0 -0
  111. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca.npc +0 -0
  112. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca.png +0 -0
  113. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca_example.png +0 -0
  114. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  115. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  116. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/frederic.npc +0 -0
  117. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/frederic4.png +0 -0
  118. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/guac.npc +0 -0
  119. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/guac.png +0 -0
  120. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/help.jinx +0 -0
  121. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  122. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/init.jinx +0 -0
  123. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  124. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  125. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  126. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  127. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  128. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/mem_review.jinx +0 -0
  129. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  130. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  131. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  132. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/ots.jinx +0 -0
  133. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/paste.jinx +0 -0
  134. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonk.npc +0 -0
  135. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonk.png +0 -0
  136. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  137. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  138. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/python.jinx +0 -0
  139. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/roll.jinx +0 -0
  140. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sample.jinx +0 -0
  141. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  142. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/search.jinx +0 -0
  143. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/serve.jinx +0 -0
  144. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/set.jinx +0 -0
  145. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sh.jinx +0 -0
  146. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/shh.jinx +0 -0
  147. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  148. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sibiji.png +0 -0
  149. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  150. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/spool.png +0 -0
  151. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sql.jinx +0 -0
  152. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switch.jinx +0 -0
  153. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switches.jinx +0 -0
  154. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  155. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  156. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  157. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/usage.jinx +0 -0
  158. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  159. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  160. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/wait.jinx +0 -0
  161. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/yap.jinx +0 -0
  162. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/yap.png +0 -0
  163. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/WHEEL +0 -0
  164. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/licenses/LICENSE +0 -0
  165. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/top_level.txt +0 -0
@@ -225,7 +225,7 @@ steps:
225
225
  line = line[:width+10]
226
226
 
227
227
  if idx == selected:
228
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
228
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
229
229
  else:
230
230
  sys.stdout.write(f' {line}')
231
231
 
@@ -181,7 +181,7 @@ steps:
181
181
  line = line[:width-1]
182
182
 
183
183
  if idx == selected:
184
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
184
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
185
185
  else:
186
186
  sys.stdout.write(f' {line}')
187
187
 
@@ -114,7 +114,7 @@ steps:
114
114
 
115
115
  line = str(items[idx])[:width-2]
116
116
  if current_tab in [0, 1, 2] and idx == selected:
117
- sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
117
+ sys.stdout.write(f'\033[7;1m>{line.ljust(width-2)}\033[0m')
118
118
  else:
119
119
  # Color gold/cliff markers
120
120
  if '[GOLD]' in line:
@@ -451,7 +451,7 @@ steps:
451
451
  line = line[:width-1]
452
452
 
453
453
  if idx == selected:
454
- sys.stdout.write('\033[47;30;1m>' + line.ljust(width-1) + '\033[0m')
454
+ sys.stdout.write('\033[7;1m>' + line.ljust(width-1) + '\033[0m')
455
455
  else:
456
456
  sys.stdout.write(' ' + line)
457
457
 
@@ -129,7 +129,7 @@ steps:
129
129
  line = line[:width-1]
130
130
 
131
131
  if idx == selected:
132
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
132
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
133
133
  else:
134
134
  sys.stdout.write(f' {line}')
135
135
 
@@ -1,5 +1,5 @@
1
1
  jinx_name: guac
2
- description: Interactive Python data analysis TUI - live variable inspector, DataFrame viewer, code execution
2
+ description: Interactive Python TUI - live variable inspector, code execution, DataFrame viewer
3
3
  inputs:
4
4
  - model: null
5
5
  - provider: null
@@ -24,8 +24,6 @@ steps:
24
24
 
25
25
  import numpy as np
26
26
  import pandas as pd
27
- import matplotlib
28
- matplotlib.use('Agg') # Non-interactive backend for TUI
29
27
  import matplotlib.pyplot as plt
30
28
 
31
29
  from npcpy.llm_funcs import get_llm_response
@@ -204,7 +202,7 @@ steps:
204
202
  # ===== HEADER =====
205
203
  mode_colors = {"code": "\033[32m", "natural": "\033[35m", "inspect": "\033[33m"}
206
204
  mode_str = f"{mode_colors[state.mode]}[{state.mode}]\033[0m"
207
- header = f" GUAC - Python Data Analysis {mode_str} "
205
+ header = f" GUAC - Interactive Python {mode_str} "
208
206
  status_color = "\033[33m" if "..." in state.status else "\033[32m"
209
207
  out.append(f"\033[1;1H\033[42;30;1m{header.ljust(width)}\033[0m")
210
208
  out.append(f"\033[1;{width-len(state.status)-3}H{status_color}[{state.status}]\033[0m")
@@ -277,7 +275,7 @@ steps:
277
275
  tabs = ""
278
276
  for i, name in enumerate(panel_names):
279
277
  if i == state.panel:
280
- tabs += f"\033[47;30m {name} \033[0m"
278
+ tabs += f"\033[7m {name} \033[0m"
281
279
  else:
282
280
  tabs += f"\033[90m {name} \033[0m"
283
281
  out.append(f"\033[3;{right_x}H{tabs}")
@@ -293,7 +291,7 @@ steps:
293
291
  info = var_info(name, value)
294
292
  display = f"{name[:10]:<10} {info[:rpanel_w-12]}"
295
293
  if idx == state.selected_var:
296
- out.append(f"\033[{4+i};{right_x}H\033[47;30m>{display[:rpanel_w]}\033[0m")
294
+ out.append(f"\033[{4+i};{right_x}H\033[7m>{display[:rpanel_w]}\033[0m")
297
295
  elif isinstance(value, pd.DataFrame):
298
296
  out.append(f"\033[{4+i};{right_x}H\033[34m {display[:rpanel_w]}\033[0m")
299
297
  elif isinstance(value, np.ndarray):
@@ -130,7 +130,7 @@ steps:
130
130
  color = ''
131
131
 
132
132
  if idx == selected:
133
- sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
133
+ sys.stdout.write(f'\033[7;1m>{line.ljust(width-2)}\033[0m')
134
134
  elif color:
135
135
  sys.stdout.write(f'{color}{line}\033[0m')
136
136
  else:
@@ -123,7 +123,7 @@ steps:
123
123
  line = line[:width-1]
124
124
 
125
125
  if idx == selected:
126
- sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
126
+ sys.stdout.write(f'\033[7;1m>{line.ljust(width-2)}\033[0m')
127
127
  else:
128
128
  sys.stdout.write(f'\033[33m{line}\033[0m')
129
129
 
@@ -160,7 +160,7 @@ steps:
160
160
  line = line[:width-2].ljust(width-1)
161
161
 
162
162
  if idx == selected:
163
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
163
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
164
164
  else:
165
165
  sys.stdout.write(f' {line}')
166
166
 
@@ -118,7 +118,7 @@ steps:
118
118
  color = ''
119
119
 
120
120
  if idx == selected:
121
- sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
121
+ sys.stdout.write(f'\033[7;1m>{line.ljust(width-2)}\033[0m')
122
122
  elif color:
123
123
  sys.stdout.write(f'{color}{line}\033[0m')
124
124
  else:
@@ -217,7 +217,7 @@ steps:
217
217
  panel_tabs = ""
218
218
  for i, name in enumerate(["Output", "Streams", "Starred"]):
219
219
  if i == state.current_panel:
220
- panel_tabs += f"\033[47;30m {name} \033[0m "
220
+ panel_tabs += f"\033[7m {name} \033[0m "
221
221
  else:
222
222
  panel_tabs += f"\033[90m {name} \033[0m "
223
223
 
npcsh/routes.py CHANGED
@@ -12,6 +12,7 @@ class CommandRouter:
12
12
  self.routes = {}
13
13
  self.help_info = {}
14
14
  self.jinx_routes = {}
15
+ self.jinx_objects = {} # command_name -> Jinx object
15
16
 
16
17
  def route(self, command: str, help_text: str = "") -> Callable:
17
18
  def wrapper(func):
@@ -42,12 +43,17 @@ class CommandRouter:
42
43
 
43
44
  def register_jinx(self, jinx: Jinx):
44
45
  command_name = jinx.jinx_name
45
-
46
+
46
47
  def jinx_handler(command: str, **kwargs):
47
48
  return self._execute_jinx(jinx, command, **kwargs)
48
-
49
+
49
50
  self.jinx_routes[command_name] = jinx_handler
51
+ self.jinx_objects[command_name] = jinx
50
52
  self.help_info[command_name] = jinx.description or "Jinx command"
53
+
54
+ def is_interactive(self, command_name: str) -> bool:
55
+ jinx = self.jinx_objects.get(command_name)
56
+ return bool(jinx and getattr(jinx, 'interactive', False))
51
57
 
52
58
  def _execute_jinx(self, jinx: Jinx, command: str, **kwargs):
53
59
  messages = kwargs.get("messages", [])
@@ -1,4 +1,4 @@
1
- jinx_name: studio.add_tab
1
+ jinx_name: studio_add_tab
2
2
  description: Add a new tab to a pane.
3
3
  inputs:
4
4
  - paneId: "active"
@@ -114,7 +114,7 @@ steps:
114
114
 
115
115
  line = str(items[idx])[:width-2]
116
116
  if current_tab in [0, 1, 2] and idx == selected:
117
- sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
117
+ sys.stdout.write(f'\033[7;1m>{line.ljust(width-2)}\033[0m')
118
118
  else:
119
119
  # Color gold/cliff markers
120
120
  if '[GOLD]' in line:
@@ -451,7 +451,7 @@ steps:
451
451
  line = line[:width-1]
452
452
 
453
453
  if idx == selected:
454
- sys.stdout.write('\033[47;30;1m>' + line.ljust(width-1) + '\033[0m')
454
+ sys.stdout.write('\033[7;1m>' + line.ljust(width-1) + '\033[0m')
455
455
  else:
456
456
  sys.stdout.write(' ' + line)
457
457
 
@@ -1,4 +1,4 @@
1
- jinx_name: studio.close_pane
1
+ jinx_name: studio_close_pane
2
2
  description: Close a pane in NPC Studio. Use paneId="active" or omit to close the active pane.
3
3
  inputs:
4
4
  - paneId: "active"
@@ -1,4 +1,4 @@
1
- jinx_name: studio.close_tab
1
+ jinx_name: studio_close_tab
2
2
  description: Close a specific tab in a pane.
3
3
  inputs:
4
4
  - paneId: "active"
@@ -0,0 +1,300 @@
1
+ jinx_name: config_tui
2
+ description: Interactive TUI editor for npcsh configuration (~/.npcshrc)
3
+ interactive: true
4
+ inputs: []
5
+ steps:
6
+ - name: config_editor
7
+ engine: python
8
+ code: |
9
+ import os
10
+ import sys
11
+ import tty
12
+ import termios
13
+ import select
14
+ from pathlib import Path
15
+
16
+ if not sys.stdin.isatty():
17
+ context['output'] = "Config TUI requires an interactive terminal."
18
+ return
19
+
20
+ # ========== Config Items ==========
21
+ CONFIG_ITEMS = [
22
+ {'key': 'NPCSH_CHAT_MODEL', 'label': 'Chat Model', 'type': 'text', 'shortcut': 'model'},
23
+ {'key': 'NPCSH_CHAT_PROVIDER', 'label': 'Chat Provider', 'type': 'text', 'shortcut': 'provider'},
24
+ {'key': 'NPCSH_VISION_MODEL', 'label': 'Vision Model', 'type': 'text'},
25
+ {'key': 'NPCSH_VISION_PROVIDER', 'label': 'Vision Provider', 'type': 'text'},
26
+ {'key': 'NPCSH_EMBEDDING_MODEL', 'label': 'Embedding Model', 'type': 'text'},
27
+ {'key': 'NPCSH_EMBEDDING_PROVIDER', 'label': 'Embedding Provider', 'type': 'text'},
28
+ {'key': 'NPCSH_REASONING_MODEL', 'label': 'Reasoning Model', 'type': 'text'},
29
+ {'key': 'NPCSH_REASONING_PROVIDER', 'label': 'Reasoning Provider', 'type': 'text'},
30
+ {'key': 'NPCSH_DEFAULT_MODE', 'label': 'Default Mode', 'type': 'choice', 'choices': ['agent', 'chat', 'code']},
31
+ {'key': 'NPCSH_STREAM_OUTPUT', 'label': 'Stream Output', 'type': 'toggle'},
32
+ {'key': 'NPCSH_BUILD_KG', 'label': 'Build Knowledge Graph', 'type': 'toggle'},
33
+ {'key': 'NPCSH_SEARCH_PROVIDER', 'label': 'Search Provider', 'type': 'choice', 'choices': ['duckduckgo', 'google', 'bing']},
34
+ ]
35
+
36
+ # ========== State ==========
37
+ class ConfigState:
38
+ def __init__(self):
39
+ self.selected_idx = 0
40
+ self.scroll_offset = 0
41
+ self.editing = False
42
+ self.edit_buffer = ""
43
+ self.edit_cursor = 0
44
+ self.values = {}
45
+ self.modified = set()
46
+ self.status = ""
47
+
48
+ state = ConfigState()
49
+
50
+ # ========== Helpers ==========
51
+ def get_size():
52
+ try:
53
+ s = os.get_terminal_size()
54
+ return s.columns, s.lines
55
+ except:
56
+ return 80, 24
57
+
58
+ def load_values():
59
+ """Load current values from environment and npcshrc."""
60
+ for item in CONFIG_ITEMS:
61
+ key = item['key']
62
+ # First try environment
63
+ value = os.environ.get(key, '')
64
+ if not value:
65
+ # Try reading from npcshrc
66
+ npcshrc = Path.home() / '.npcshrc'
67
+ if npcshrc.exists():
68
+ with open(npcshrc) as f:
69
+ for line in f:
70
+ if line.strip().startswith(f'export {key}='):
71
+ value = line.split('=', 1)[1].strip().strip('"').strip("'")
72
+ break
73
+ state.values[key] = value
74
+
75
+ def save_values():
76
+ """Save modified values to ~/.npcshrc."""
77
+ from npcsh.config import set_npcsh_config_value
78
+ for key in state.modified:
79
+ set_npcsh_config_value(key, state.values[key])
80
+ state.modified.clear()
81
+ state.status = "Saved!"
82
+
83
+ def format_value(item, value):
84
+ """Format value for display."""
85
+ if item['type'] == 'toggle':
86
+ return '\033[32mON\033[0m' if value in ('1', 'true', 'True', True) else '\033[31mOFF\033[0m'
87
+ elif not value:
88
+ return '\033[90m(not set)\033[0m'
89
+ return value
90
+
91
+ # ========== Rendering ==========
92
+ def render_screen():
93
+ width, height = get_size()
94
+ out = []
95
+ out.append("\033[2J\033[H")
96
+
97
+ # Header
98
+ header = " NPCSH Configuration "
99
+ out.append(f"\033[1;1H\033[44;37;1m{'=' * width}\033[0m")
100
+ out.append(f"\033[1;{(width - len(header)) // 2}H\033[44;37;1m{header}\033[0m")
101
+
102
+ # Config items
103
+ visible_height = height - 6
104
+ visible = CONFIG_ITEMS[state.scroll_offset:state.scroll_offset + visible_height]
105
+
106
+ label_width = max(len(item['label']) for item in CONFIG_ITEMS) + 2
107
+ value_width = width - label_width - 10
108
+
109
+ for i, item in enumerate(visible):
110
+ row = 3 + i
111
+ idx = i + state.scroll_offset
112
+ key = item['key']
113
+ value = state.values.get(key, '')
114
+ display_value = format_value(item, value)
115
+
116
+ # Indicator for modified
117
+ mod_indicator = '*' if key in state.modified else ' '
118
+
119
+ if idx == state.selected_idx:
120
+ if state.editing:
121
+ # Show edit mode
122
+ out.append(f"\033[{row};2H\033[7m{item['label']:<{label_width}}\033[0m")
123
+ # Edit buffer with cursor
124
+ cursor_pos = min(state.edit_cursor, len(state.edit_buffer))
125
+ before = state.edit_buffer[:cursor_pos]
126
+ after = state.edit_buffer[cursor_pos:]
127
+ out.append(f"\033[{row};{label_width+4}H{before}\033[7m \033[0m{after}")
128
+ else:
129
+ out.append(f"\033[{row};2H\033[7m{mod_indicator}{item['label']:<{label_width}} {display_value[:value_width]}\033[0m")
130
+ else:
131
+ out.append(f"\033[{row};2H{mod_indicator}{item['label']:<{label_width}} {display_value[:value_width]}")
132
+
133
+ # Type indicator
134
+ type_hint = {'text': '[e]', 'toggle': '[t]', 'choice': '[c]'}.get(item['type'], '')
135
+ out.append(f"\033[{row};{width-4}H\033[90m{type_hint}\033[0m")
136
+
137
+ # Status line
138
+ if state.status:
139
+ out.append(f"\033[{height-2};2H\033[33m{state.status}\033[0m")
140
+
141
+ # Footer
142
+ if state.editing:
143
+ footer = "[Enter] Save [Esc] Cancel"
144
+ else:
145
+ footer = "[j/k] Navigate [e] Edit [t] Toggle [s] Save All [q] Quit"
146
+ out.append(f"\033[{height};1H\033[90m{footer[:width]}\033[0m")
147
+
148
+ sys.stdout.write(''.join(out))
149
+ sys.stdout.flush()
150
+
151
+ # ========== Input Handling ==========
152
+ def handle_input(c):
153
+ if state.editing:
154
+ return handle_edit_input(c)
155
+
156
+ if c == 'q':
157
+ if state.modified:
158
+ state.status = "Unsaved changes! Press 's' to save or 'q' again to discard."
159
+ return True
160
+ return False
161
+
162
+ if c == '\x1b': # Escape sequence
163
+ if select.select([sys.stdin], [], [], 0.05)[0]:
164
+ c2 = sys.stdin.read(1)
165
+ if c2 == '[':
166
+ c3 = sys.stdin.read(1)
167
+ if c3 == 'A': # Up
168
+ move_up()
169
+ elif c3 == 'B': # Down
170
+ move_down()
171
+ return True
172
+
173
+ if c == 'k':
174
+ move_up()
175
+ elif c == 'j':
176
+ move_down()
177
+ elif c == 'e' or c == '\r' or c == '\n':
178
+ start_edit()
179
+ elif c == 't':
180
+ toggle_value()
181
+ elif c == 'c':
182
+ cycle_choice()
183
+ elif c == 's':
184
+ save_values()
185
+
186
+ return True
187
+
188
+ def handle_edit_input(c):
189
+ if c == '\x1b': # Escape - cancel edit
190
+ state.editing = False
191
+ state.edit_buffer = ""
192
+ state.status = "Edit cancelled"
193
+ return True
194
+
195
+ if c == '\r' or c == '\n': # Enter - save edit
196
+ key = CONFIG_ITEMS[state.selected_idx]['key']
197
+ state.values[key] = state.edit_buffer
198
+ state.modified.add(key)
199
+ state.editing = False
200
+ state.edit_buffer = ""
201
+ state.status = f"Changed {key}"
202
+ return True
203
+
204
+ if c == '\x7f' or c == '\x08': # Backspace
205
+ if state.edit_cursor > 0:
206
+ state.edit_buffer = state.edit_buffer[:state.edit_cursor-1] + state.edit_buffer[state.edit_cursor:]
207
+ state.edit_cursor -= 1
208
+ return True
209
+
210
+ if c >= ' ' and c <= '~': # Printable
211
+ state.edit_buffer = state.edit_buffer[:state.edit_cursor] + c + state.edit_buffer[state.edit_cursor:]
212
+ state.edit_cursor += 1
213
+ return True
214
+
215
+ return True
216
+
217
+ def move_up():
218
+ state.selected_idx = max(0, state.selected_idx - 1)
219
+ if state.selected_idx < state.scroll_offset:
220
+ state.scroll_offset = state.selected_idx
221
+ state.status = ""
222
+
223
+ def move_down():
224
+ _, height = get_size()
225
+ visible_height = height - 6
226
+ state.selected_idx = min(len(CONFIG_ITEMS) - 1, state.selected_idx + 1)
227
+ if state.selected_idx >= state.scroll_offset + visible_height:
228
+ state.scroll_offset = state.selected_idx - visible_height + 1
229
+ state.status = ""
230
+
231
+ def start_edit():
232
+ item = CONFIG_ITEMS[state.selected_idx]
233
+ if item['type'] == 'toggle':
234
+ toggle_value()
235
+ elif item['type'] == 'choice':
236
+ cycle_choice()
237
+ else:
238
+ key = item['key']
239
+ state.edit_buffer = state.values.get(key, '')
240
+ state.edit_cursor = len(state.edit_buffer)
241
+ state.editing = True
242
+ state.status = "Editing... Enter to save, Esc to cancel"
243
+
244
+ def toggle_value():
245
+ item = CONFIG_ITEMS[state.selected_idx]
246
+ if item['type'] != 'toggle':
247
+ return
248
+ key = item['key']
249
+ current = state.values.get(key, '0')
250
+ new_value = '0' if current in ('1', 'true', 'True') else '1'
251
+ state.values[key] = new_value
252
+ state.modified.add(key)
253
+ state.status = f"Toggled {item['label']}"
254
+
255
+ def cycle_choice():
256
+ item = CONFIG_ITEMS[state.selected_idx]
257
+ if item['type'] != 'choice':
258
+ return
259
+ key = item['key']
260
+ choices = item.get('choices', [])
261
+ if not choices:
262
+ return
263
+ current = state.values.get(key, '')
264
+ try:
265
+ idx = choices.index(current)
266
+ next_idx = (idx + 1) % len(choices)
267
+ except ValueError:
268
+ next_idx = 0
269
+ state.values[key] = choices[next_idx]
270
+ state.modified.add(key)
271
+ state.status = f"Changed to {choices[next_idx]}"
272
+
273
+ # ========== Main Loop ==========
274
+ load_values()
275
+
276
+ fd = sys.stdin.fileno()
277
+ old_settings = termios.tcgetattr(fd)
278
+
279
+ try:
280
+ tty.setcbreak(fd)
281
+ sys.stdout.write('\033[?25l') # Hide cursor
282
+
283
+ render_screen()
284
+
285
+ while True:
286
+ c = sys.stdin.read(1)
287
+ if not handle_input(c):
288
+ break
289
+ render_screen()
290
+
291
+ finally:
292
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
293
+ sys.stdout.write('\033[?25h') # Show cursor
294
+ sys.stdout.write('\033[2J\033[H') # Clear screen
295
+ sys.stdout.flush()
296
+
297
+ if state.modified:
298
+ context['output'] = f"Exited with unsaved changes: {', '.join(state.modified)}"
299
+ else:
300
+ context['output'] = "Configuration editor closed."
@@ -1,4 +1,4 @@
1
- jinx_name: studio.confirm
1
+ jinx_name: studio_confirm
2
2
  description: Show a confirmation dialog and return the user's choice.
3
3
  inputs:
4
4
  - message: ""
@@ -129,7 +129,7 @@ steps:
129
129
  line = line[:width-1]
130
130
 
131
131
  if idx == selected:
132
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
132
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
133
133
  else:
134
134
  sys.stdout.write(f' {line}')
135
135
 
@@ -213,7 +213,7 @@ steps:
213
213
  line = line[:width+20] # allow for color codes
214
214
 
215
215
  if idx == selected:
216
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
216
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
217
217
  else:
218
218
  sys.stdout.write(f' {line}')
219
219
 
@@ -207,7 +207,7 @@ steps:
207
207
  line = line[:width-1]
208
208
 
209
209
  if idx == selected:
210
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
210
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
211
211
  else:
212
212
  sys.stdout.write(f' {line}')
213
213
 
@@ -1,4 +1,4 @@
1
- jinx_name: studio.focus_pane
1
+ jinx_name: studio_focus_pane
2
2
  description: Focus/activate a specific pane in NPC Studio.
3
3
  inputs:
4
4
  - paneId: ""
@@ -1,5 +1,5 @@
1
1
  jinx_name: guac
2
- description: Interactive Python data analysis TUI - live variable inspector, DataFrame viewer, code execution
2
+ description: Interactive Python TUI - live variable inspector, code execution, DataFrame viewer
3
3
  inputs:
4
4
  - model: null
5
5
  - provider: null
@@ -24,8 +24,6 @@ steps:
24
24
 
25
25
  import numpy as np
26
26
  import pandas as pd
27
- import matplotlib
28
- matplotlib.use('Agg') # Non-interactive backend for TUI
29
27
  import matplotlib.pyplot as plt
30
28
 
31
29
  from npcpy.llm_funcs import get_llm_response
@@ -204,7 +202,7 @@ steps:
204
202
  # ===== HEADER =====
205
203
  mode_colors = {"code": "\033[32m", "natural": "\033[35m", "inspect": "\033[33m"}
206
204
  mode_str = f"{mode_colors[state.mode]}[{state.mode}]\033[0m"
207
- header = f" GUAC - Python Data Analysis {mode_str} "
205
+ header = f" GUAC - Interactive Python {mode_str} "
208
206
  status_color = "\033[33m" if "..." in state.status else "\033[32m"
209
207
  out.append(f"\033[1;1H\033[42;30;1m{header.ljust(width)}\033[0m")
210
208
  out.append(f"\033[1;{width-len(state.status)-3}H{status_color}[{state.status}]\033[0m")
@@ -277,7 +275,7 @@ steps:
277
275
  tabs = ""
278
276
  for i, name in enumerate(panel_names):
279
277
  if i == state.panel:
280
- tabs += f"\033[47;30m {name} \033[0m"
278
+ tabs += f"\033[7m {name} \033[0m"
281
279
  else:
282
280
  tabs += f"\033[90m {name} \033[0m"
283
281
  out.append(f"\033[3;{right_x}H{tabs}")
@@ -293,7 +291,7 @@ steps:
293
291
  info = var_info(name, value)
294
292
  display = f"{name[:10]:<10} {info[:rpanel_w-12]}"
295
293
  if idx == state.selected_var:
296
- out.append(f"\033[{4+i};{right_x}H\033[47;30m>{display[:rpanel_w]}\033[0m")
294
+ out.append(f"\033[{4+i};{right_x}H\033[7m>{display[:rpanel_w]}\033[0m")
297
295
  elif isinstance(value, pd.DataFrame):
298
296
  out.append(f"\033[{4+i};{right_x}H\033[34m {display[:rpanel_w]}\033[0m")
299
297
  elif isinstance(value, np.ndarray):