npcsh 1.1.20__py3-none-any.whl → 1.1.22__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 (186) hide show
  1. npcsh/_state.py +15 -76
  2. npcsh/benchmark/npcsh_agent.py +22 -14
  3. npcsh/benchmark/templates/install-npcsh.sh.j2 +2 -2
  4. npcsh/diff_viewer.py +3 -3
  5. npcsh/mcp_server.py +9 -1
  6. npcsh/npc_team/alicanto.npc +12 -6
  7. npcsh/npc_team/corca.npc +0 -1
  8. npcsh/npc_team/frederic.npc +2 -3
  9. npcsh/npc_team/jinxs/lib/core/compress.jinx +373 -85
  10. npcsh/npc_team/jinxs/lib/core/edit_file.jinx +83 -61
  11. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +17 -6
  12. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +17 -6
  13. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +52 -14
  14. npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
  15. npcsh/npc_team/jinxs/{bin → lib/utils}/jinxs.jinx +12 -12
  16. npcsh/npc_team/jinxs/{bin → lib/utils}/models.jinx +7 -7
  17. npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +6 -6
  18. npcsh/npc_team/jinxs/modes/alicanto.jinx +1633 -295
  19. npcsh/npc_team/jinxs/modes/arxiv.jinx +5 -5
  20. npcsh/npc_team/jinxs/modes/build.jinx +378 -0
  21. npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
  22. npcsh/npc_team/jinxs/modes/convene.jinx +597 -0
  23. npcsh/npc_team/jinxs/modes/corca.jinx +777 -387
  24. npcsh/npc_team/jinxs/modes/git.jinx +795 -0
  25. {npcsh-1.1.20.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/modes}/kg.jinx +82 -15
  26. npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
  27. npcsh/npc_team/jinxs/{bin → modes}/nql.jinx +10 -21
  28. npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
  29. npcsh/npc_team/jinxs/modes/plonk.jinx +503 -308
  30. npcsh/npc_team/jinxs/modes/reattach.jinx +3 -3
  31. npcsh/npc_team/jinxs/modes/spool.jinx +3 -3
  32. npcsh/npc_team/jinxs/{bin → modes}/team.jinx +12 -12
  33. npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
  34. npcsh/npc_team/jinxs/modes/wander.jinx +454 -181
  35. npcsh/npc_team/jinxs/modes/yap.jinx +630 -182
  36. npcsh/npc_team/kadiefa.npc +2 -1
  37. npcsh/npc_team/sibiji.npc +3 -3
  38. npcsh/npcsh.py +112 -47
  39. npcsh/routes.py +4 -1
  40. npcsh/salmon_simulation.py +0 -0
  41. npcsh-1.1.22.data/data/npcsh/npc_team/alicanto.jinx +1694 -0
  42. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.npc +12 -6
  43. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/arxiv.jinx +5 -5
  44. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
  45. npcsh-1.1.22.data/data/npcsh/npc_team/build.jinx +378 -0
  46. npcsh-1.1.22.data/data/npcsh/npc_team/compress.jinx +428 -0
  47. npcsh-1.1.22.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  48. npcsh-1.1.22.data/data/npcsh/npc_team/corca.jinx +820 -0
  49. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.npc +0 -1
  50. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/db_search.jinx +17 -6
  51. npcsh-1.1.22.data/data/npcsh/npc_team/edit_file.jinx +119 -0
  52. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/file_search.jinx +17 -6
  53. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic.npc +2 -3
  54. npcsh-1.1.22.data/data/npcsh/npc_team/git.jinx +795 -0
  55. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/jinxs.jinx +12 -12
  56. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.npc +2 -1
  57. {npcsh/npc_team/jinxs/bin → npcsh-1.1.22.data/data/npcsh/npc_team}/kg.jinx +82 -15
  58. npcsh-1.1.22.data/data/npcsh/npc_team/memories.jinx +414 -0
  59. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/models.jinx +7 -7
  60. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/nql.jinx +10 -21
  61. npcsh-1.1.22.data/data/npcsh/npc_team/papers.jinx +578 -0
  62. npcsh-1.1.22.data/data/npcsh/npc_team/plonk.jinx +574 -0
  63. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/reattach.jinx +3 -3
  64. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/setup.jinx +6 -6
  65. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.npc +3 -3
  66. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.jinx +3 -3
  67. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/team.jinx +12 -12
  68. npcsh-1.1.22.data/data/npcsh/npc_team/vixynt.jinx +388 -0
  69. npcsh-1.1.22.data/data/npcsh/npc_team/wander.jinx +728 -0
  70. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/web_search.jinx +52 -14
  71. npcsh-1.1.22.data/data/npcsh/npc_team/yap.jinx +716 -0
  72. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/METADATA +246 -281
  73. npcsh-1.1.22.dist-info/RECORD +240 -0
  74. npcsh-1.1.22.dist-info/entry_points.txt +11 -0
  75. npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -300
  76. npcsh/npc_team/jinxs/bin/memories.jinx +0 -317
  77. npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
  78. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +0 -418
  79. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
  80. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
  81. npcsh/npc_team/jinxs/lib/core/search.jinx +0 -54
  82. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
  83. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
  84. npcsh/npc_team/jinxs/lib/utils/build.jinx +0 -65
  85. npcsh/npc_team/plonkjr.npc +0 -23
  86. npcsh-1.1.20.data/data/npcsh/npc_team/alicanto.jinx +0 -356
  87. npcsh-1.1.20.data/data/npcsh/npc_team/build.jinx +0 -65
  88. npcsh-1.1.20.data/data/npcsh/npc_team/compress.jinx +0 -140
  89. npcsh-1.1.20.data/data/npcsh/npc_team/config_tui.jinx +0 -300
  90. npcsh-1.1.20.data/data/npcsh/npc_team/corca.jinx +0 -430
  91. npcsh-1.1.20.data/data/npcsh/npc_team/edit_file.jinx +0 -97
  92. npcsh-1.1.20.data/data/npcsh/npc_team/kg_search.jinx +0 -418
  93. npcsh-1.1.20.data/data/npcsh/npc_team/mem_review.jinx +0 -73
  94. npcsh-1.1.20.data/data/npcsh/npc_team/mem_search.jinx +0 -388
  95. npcsh-1.1.20.data/data/npcsh/npc_team/memories.jinx +0 -317
  96. npcsh-1.1.20.data/data/npcsh/npc_team/paper_search.jinx +0 -412
  97. npcsh-1.1.20.data/data/npcsh/npc_team/plonk.jinx +0 -379
  98. npcsh-1.1.20.data/data/npcsh/npc_team/plonkjr.npc +0 -23
  99. npcsh-1.1.20.data/data/npcsh/npc_team/search.jinx +0 -54
  100. npcsh-1.1.20.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
  101. npcsh-1.1.20.data/data/npcsh/npc_team/vixynt.jinx +0 -122
  102. npcsh-1.1.20.data/data/npcsh/npc_team/wander.jinx +0 -455
  103. npcsh-1.1.20.data/data/npcsh/npc_team/yap.jinx +0 -268
  104. npcsh-1.1.20.dist-info/RECORD +0 -248
  105. npcsh-1.1.20.dist-info/entry_points.txt +0 -25
  106. /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
  107. /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
  108. /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
  109. /npcsh/npc_team/jinxs/lib/{core → utils}/chat.jinx +0 -0
  110. /npcsh/npc_team/jinxs/lib/{core → utils}/cmd.jinx +0 -0
  111. /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
  112. /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
  113. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  114. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.png +0 -0
  115. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  116. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  117. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/chat.jinx +0 -0
  118. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/click.jinx +0 -0
  119. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  120. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  121. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  122. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  123. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/compile.jinx +0 -0
  124. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  125. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/convene.jinx +0 -0
  126. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.png +0 -0
  127. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca_example.png +0 -0
  128. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  129. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  130. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic4.png +0 -0
  131. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.jinx +0 -0
  132. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.npc +0 -0
  133. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.png +0 -0
  134. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/help.jinx +0 -0
  135. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  136. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/init.jinx +0 -0
  137. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  138. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  139. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  140. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  141. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  142. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  143. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/notify.jinx +0 -0
  144. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  145. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  146. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  147. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  148. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/ots.jinx +0 -0
  149. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/paste.jinx +0 -0
  150. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.npc +0 -0
  151. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.png +0 -0
  152. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  153. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/pti.jinx +0 -0
  154. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/python.jinx +0 -0
  155. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  156. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/roll.jinx +0 -0
  157. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  158. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sample.jinx +0 -0
  159. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  160. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  161. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/serve.jinx +0 -0
  162. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/set.jinx +0 -0
  163. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sh.jinx +0 -0
  164. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/shh.jinx +0 -0
  165. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.png +0 -0
  166. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  167. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  168. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.png +0 -0
  169. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sql.jinx +0 -0
  170. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch.jinx +0 -0
  171. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  172. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  173. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switches.jinx +0 -0
  174. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sync.jinx +0 -0
  175. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  176. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  177. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  178. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/usage.jinx +0 -0
  179. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  180. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/wait.jinx +0 -0
  181. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  182. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/yap.png +0 -0
  183. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  184. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/WHEEL +0 -0
  185. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/licenses/LICENSE +0 -0
  186. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/top_level.txt +0 -0
@@ -136,8 +136,8 @@ steps:
136
136
  team_name = state.team.name or 'team'
137
137
  hdr = f" {team_name} "
138
138
  pad = '=' * W
139
- out.append(wline(1, f"\033[44;37;1m{pad}\033[0m"))
140
- out.append(f"\033[1;{max(1,(W - len(hdr)) // 2)}H\033[44;37;1m{hdr}\033[0m")
139
+ out.append(wline(1, f"\033[7;1m{pad}\033[0m"))
140
+ out.append(f"\033[1;{max(1,(W - len(hdr)) // 2)}H\033[7;1m{hdr}\033[0m")
141
141
 
142
142
  # ── tabs ──
143
143
  tb = ""
@@ -182,7 +182,7 @@ steps:
182
182
  foot = " [j/k] Navigate [e/Enter] Edit [s] Save [q/Esc] Back"
183
183
  else:
184
184
  foot = " [Tab] Switch [j/k] Nav [Enter] Open [s] Save [q] Quit"
185
- out.append(wline(H, f"\033[44;37m{foot[:W].ljust(W)}\033[0m"))
185
+ out.append(wline(H, f"\033[7m{foot[:W].ljust(W)}\033[0m"))
186
186
 
187
187
  sys.stdout.write(''.join(out))
188
188
  sys.stdout.flush()
@@ -201,7 +201,7 @@ steps:
201
201
 
202
202
  if i == ui.sel:
203
203
  if ui.editing:
204
- out.append(wline(row, f" \033[1m{key}:\033[0m \033[44;37m{ui.edit_buf}\033[0m\033[K"))
204
+ out.append(wline(row, f" \033[1m{key}:\033[0m \033[7m{ui.edit_buf}\033[0m\033[K"))
205
205
  else:
206
206
  line = f" {key}: {val}"
207
207
  out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
@@ -283,7 +283,7 @@ steps:
283
283
  fval = fmt_val(data.get(fkey, ''), W - 25)
284
284
  if fi == ui.detail_idx:
285
285
  if ui.editing:
286
- out.append(wline(row, f" \033[1m{fkey}:\033[0m \033[44;37m{ui.edit_buf}\033[0m"))
286
+ out.append(wline(row, f" \033[1m{fkey}:\033[0m \033[7m{ui.edit_buf}\033[0m"))
287
287
  else:
288
288
  line = f" {fkey}: {fval}"
289
289
  out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
@@ -319,10 +319,10 @@ steps:
319
319
  return True
320
320
 
321
321
  def handle_esc():
322
- if select.select([sys.stdin], [], [], 0.05)[0]:
323
- c2 = sys.stdin.read(1)
322
+ if select.select([fd], [], [], 0.05)[0]:
323
+ c2 = os.read(fd, 1).decode('latin-1')
324
324
  if c2 == '[':
325
- c3 = sys.stdin.read(1)
325
+ c3 = os.read(fd, 1).decode('latin-1')
326
326
  if c3 == 'A':
327
327
  nav_up()
328
328
  elif c3 == 'B':
@@ -338,10 +338,10 @@ steps:
338
338
  def handle_edit(c):
339
339
  if c == '\x1b':
340
340
  # Check if arrow key or bare esc
341
- if select.select([sys.stdin], [], [], 0.05)[0]:
342
- c2 = sys.stdin.read(1)
341
+ if select.select([fd], [], [], 0.05)[0]:
342
+ c2 = os.read(fd, 1).decode('latin-1')
343
343
  if c2 == '[':
344
- sys.stdin.read(1) # consume arrow char
344
+ os.read(fd, 1).decode('latin-1') # consume arrow char
345
345
  else:
346
346
  ui.editing = False
347
347
  ui.edit_buf = ""
@@ -492,7 +492,7 @@ steps:
492
492
  sys.stdout.flush()
493
493
  render()
494
494
  while True:
495
- c = sys.stdin.read(1)
495
+ c = os.read(fd, 1).decode('latin-1')
496
496
  if not handle(c):
497
497
  break
498
498
  render()
@@ -0,0 +1,388 @@
1
+ jinx_name: "vixynt"
2
+ description: "Image creation studio TUI - generate and manage images with parameter controls"
3
+ interactive: true
4
+ inputs:
5
+ - prompt: null
6
+ - model: null
7
+ - provider: null
8
+ - output_name: null
9
+ - attachments: null
10
+ - n_images: null
11
+ - height: null
12
+ - width: null
13
+ steps:
14
+ - name: "vixynt_tui"
15
+ engine: "python"
16
+ code: |
17
+ import os
18
+ import sys
19
+ import tty
20
+ import termios
21
+ import select
22
+ from datetime import datetime
23
+ from PIL import Image
24
+
25
+ from npcpy.llm_funcs import gen_image
26
+
27
+ npc = context.get('npc')
28
+ if isinstance(npc, str):
29
+ npc = None
30
+
31
+ # ========== State ==========
32
+ class VixyntState:
33
+ def __init__(self):
34
+ self.prompt = ""
35
+ self.model = ""
36
+ self.provider = ""
37
+ self.width_val = 1024
38
+ self.height_val = 1024
39
+ self.n_images = 1
40
+ self.output_name = ""
41
+ self.attachments = ""
42
+ # UI state
43
+ self.params = ['prompt', 'model', 'provider', 'width', 'height', 'n_images', 'output', 'attachments']
44
+ self.sel = 0
45
+ self.scroll = 0
46
+ self.mode = 'params' # params, editing, gallery, generating
47
+ self.edit_buf = ""
48
+ self.edit_cursor = 0
49
+ self.status = "Ready"
50
+ self.gallery = [] # [{"path": str, "prompt": str, "timestamp": str}]
51
+ self.gallery_sel = 0
52
+ self.gallery_scroll = 0
53
+
54
+ ui = VixyntState()
55
+
56
+ # Load from context
57
+ ui.prompt = str(context.get('prompt') or '')
58
+ ui.model = str(context.get('model') or os.getenv('NPCSH_IMAGE_GEN_MODEL', ''))
59
+ ui.provider = str(context.get('provider') or os.getenv('NPCSH_IMAGE_GEN_PROVIDER', ''))
60
+ try:
61
+ ui.width_val = int(context.get('width') or 1024)
62
+ except:
63
+ pass
64
+ try:
65
+ ui.height_val = int(context.get('height') or 1024)
66
+ except:
67
+ pass
68
+ try:
69
+ ui.n_images = int(context.get('n_images') or 1)
70
+ except:
71
+ pass
72
+ ui.output_name = str(context.get('output_name') or '')
73
+ ui.attachments = str(context.get('attachments') or '')
74
+
75
+ # Load existing gallery
76
+ img_dir = os.path.expanduser("~/.npcsh/images/")
77
+ if os.path.isdir(img_dir):
78
+ for f in sorted(os.listdir(img_dir), reverse=True)[:50]:
79
+ if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
80
+ ui.gallery.append({"path": os.path.join(img_dir, f), "prompt": "", "timestamp": f})
81
+
82
+ def get_size():
83
+ try:
84
+ s = os.get_terminal_size()
85
+ return s.columns, s.lines
86
+ except:
87
+ return 80, 24
88
+
89
+ def get_param_value(idx):
90
+ p = ui.params[idx]
91
+ if p == 'prompt': return ui.prompt
92
+ if p == 'model': return ui.model or '(env default)'
93
+ if p == 'provider': return ui.provider or '(env default)'
94
+ if p == 'width': return str(ui.width_val)
95
+ if p == 'height': return str(ui.height_val)
96
+ if p == 'n_images': return str(ui.n_images)
97
+ if p == 'output': return ui.output_name or '(auto)'
98
+ if p == 'attachments': return ui.attachments or '(none)'
99
+ return ''
100
+
101
+ def set_param_value(idx, val):
102
+ p = ui.params[idx]
103
+ if p == 'prompt': ui.prompt = val
104
+ elif p == 'model': ui.model = val
105
+ elif p == 'provider': ui.provider = val
106
+ elif p == 'width':
107
+ try: ui.width_val = int(val)
108
+ except: pass
109
+ elif p == 'height':
110
+ try: ui.height_val = int(val)
111
+ except: pass
112
+ elif p == 'n_images':
113
+ try: ui.n_images = max(1, int(val))
114
+ except: pass
115
+ elif p == 'output': ui.output_name = val
116
+ elif p == 'attachments': ui.attachments = val
117
+
118
+ def generate_images():
119
+ ui.mode = 'generating'
120
+ ui.status = "Generating..."
121
+ render_screen()
122
+
123
+ if not ui.prompt:
124
+ ui.status = "Error: No prompt provided"
125
+ ui.mode = 'params'
126
+ return
127
+
128
+ input_images = []
129
+ if ui.attachments.strip():
130
+ input_images = [p.strip() for p in ui.attachments.split(',')]
131
+
132
+ try:
133
+ result = gen_image(
134
+ prompt=ui.prompt,
135
+ model=ui.model or None,
136
+ provider=ui.provider or None,
137
+ npc=npc,
138
+ height=ui.height_val,
139
+ width=ui.width_val,
140
+ n_images=ui.n_images,
141
+ input_images=input_images if input_images else None
142
+ )
143
+
144
+ if not isinstance(result, list):
145
+ images_list = [result] if result is not None else []
146
+ else:
147
+ images_list = result
148
+
149
+ saved = []
150
+ for i, image in enumerate(images_list):
151
+ if image is None:
152
+ continue
153
+ if ui.output_name and ui.output_name.strip():
154
+ base, ext = os.path.splitext(os.path.expanduser(ui.output_name))
155
+ if not ext:
156
+ ext = ".png"
157
+ out_file = base + ("_" + str(i) if len(images_list) > 1 else "") + ext
158
+ else:
159
+ os.makedirs(img_dir, exist_ok=True)
160
+ out_file = os.path.join(img_dir, "vixynt_" + datetime.now().strftime('%Y%m%d_%H%M%S') + "_" + str(i) + ".png")
161
+ image.save(out_file)
162
+ saved.append(out_file)
163
+ ui.gallery.insert(0, {"path": out_file, "prompt": ui.prompt, "timestamp": os.path.basename(out_file)})
164
+
165
+ ui.status = "Generated " + str(len(saved)) + " image(s): " + ", ".join(os.path.basename(f) for f in saved)
166
+ except Exception as e:
167
+ ui.status = "Error: " + str(e)[:60]
168
+
169
+ ui.mode = 'params'
170
+
171
+ # ========== Rendering ==========
172
+ def render_screen():
173
+ width, height = get_size()
174
+ out = []
175
+ out.append("\033[H")
176
+
177
+ header = " VIXYNT - Image Creation Studio "
178
+ out.append("\033[1;1H\033[7;1m" + header.ljust(width) + "\033[0m")
179
+
180
+ if ui.mode in ('params', 'editing', 'generating'):
181
+ # Parameter panel
182
+ out.append("\033[3;1H\033[36;1m Parameters \033[90m" + ("-" * (width - 13)) + "\033[0m")
183
+
184
+ for i, p in enumerate(ui.params):
185
+ row = 4 + i
186
+ out.append("\033[" + str(row) + ";1H\033[K")
187
+ label = p.capitalize() + ":"
188
+ val = get_param_value(i)
189
+
190
+ if ui.mode == 'editing' and i == ui.sel:
191
+ line = " " + label.ljust(14) + "\033[7m " + ui.edit_buf + " \033[0m"
192
+ else:
193
+ line = " " + label.ljust(14) + val[:width - 18]
194
+
195
+ if i == ui.sel and ui.mode != 'editing':
196
+ out.append("\033[7m>" + line + "\033[0m")
197
+ else:
198
+ out.append(" " + line)
199
+
200
+ # Gallery preview
201
+ gallery_row = 4 + len(ui.params) + 1
202
+ out.append("\033[" + str(gallery_row) + ";1H\033[33;1m Gallery (" + str(len(ui.gallery)) + ") \033[90m" + ("-" * (width - 20)) + "\033[0m")
203
+
204
+ gallery_h = height - gallery_row - 4
205
+ for i in range(gallery_h):
206
+ idx = ui.gallery_scroll + i
207
+ row = gallery_row + 1 + i
208
+ out.append("\033[" + str(row) + ";1H\033[K")
209
+ if idx >= len(ui.gallery):
210
+ continue
211
+ g = ui.gallery[idx]
212
+ fname = os.path.basename(g['path'])[:width - 6]
213
+ out.append(" " + fname)
214
+
215
+ elif ui.mode == 'gallery':
216
+ out.append("\033[3;1H\033[33;1m Gallery (" + str(len(ui.gallery)) + ") \033[90m" + ("-" * (width - 20)) + "\033[0m")
217
+
218
+ gallery_h = height - 6
219
+ for i in range(gallery_h):
220
+ idx = ui.gallery_scroll + i
221
+ row = 4 + i
222
+ out.append("\033[" + str(row) + ";1H\033[K")
223
+ if idx >= len(ui.gallery):
224
+ continue
225
+ g = ui.gallery[idx]
226
+ fname = os.path.basename(g['path'])
227
+ if idx == ui.gallery_sel:
228
+ out.append("\033[7m> " + fname[:width-4] + "\033[0m")
229
+ else:
230
+ out.append(" " + fname[:width-4])
231
+
232
+ # Status + footer
233
+ out.append("\033[" + str(height-2) + ";1H\033[K\033[90m" + ("-" * width) + "\033[0m")
234
+ out.append("\033[" + str(height-1) + ";1H\033[K " + ui.status[:width-2])
235
+
236
+ if ui.mode == 'editing':
237
+ footer = " Type value, Enter:Confirm Esc:Cancel "
238
+ elif ui.mode == 'gallery':
239
+ footer = " j/k:Nav o:Open Enter:Select b:Back q:Quit "
240
+ elif ui.mode == 'generating':
241
+ footer = " Generating... "
242
+ else:
243
+ footer = " j/k:Nav e:Edit Enter:Generate g:Gallery q:Quit "
244
+ out.append("\033[" + str(height) + ";1H\033[K\033[7m" + footer.ljust(width) + "\033[0m")
245
+
246
+ sys.stdout.write(''.join(out))
247
+ sys.stdout.flush()
248
+
249
+ # ========== Input Handling ==========
250
+ def handle_input(c, fd):
251
+ if ui.mode == 'editing':
252
+ return handle_edit(c, fd)
253
+ if ui.mode == 'gallery':
254
+ return handle_gallery(c, fd)
255
+
256
+ if c == '\x1b':
257
+ if select.select([fd], [], [], 0.05)[0]:
258
+ c2 = os.read(fd, 1).decode('latin-1')
259
+ if c2 == '[':
260
+ c3 = os.read(fd, 1).decode('latin-1')
261
+ if c3 == 'A':
262
+ ui.sel = max(0, ui.sel - 1)
263
+ elif c3 == 'B':
264
+ ui.sel = min(len(ui.params) - 1, ui.sel + 1)
265
+ return True
266
+
267
+ if c == 'q':
268
+ return False
269
+ elif c == 'j':
270
+ ui.sel = min(len(ui.params) - 1, ui.sel + 1)
271
+ elif c == 'k':
272
+ ui.sel = max(0, ui.sel - 1)
273
+ elif c == 'e' or c in ('\r', '\n'):
274
+ if c in ('\r', '\n') and ui.sel == 0 and ui.prompt:
275
+ # Enter on prompt with existing prompt = generate
276
+ generate_images()
277
+ else:
278
+ ui.mode = 'editing'
279
+ ui.edit_buf = get_param_value(ui.sel)
280
+ if ui.edit_buf in ('(env default)', '(auto)', '(none)'):
281
+ ui.edit_buf = ""
282
+ ui.edit_cursor = len(ui.edit_buf)
283
+ elif c == 'g':
284
+ ui.mode = 'gallery'
285
+ ui.gallery_sel = 0
286
+ ui.gallery_scroll = 0
287
+ elif c == 'G':
288
+ generate_images()
289
+
290
+ return True
291
+
292
+ def handle_edit(c, fd):
293
+ if c == '\x1b':
294
+ if select.select([fd], [], [], 0.05)[0]:
295
+ os.read(fd, 2)
296
+ ui.mode = 'params'
297
+ return True
298
+
299
+ if c in ('\r', '\n'):
300
+ set_param_value(ui.sel, ui.edit_buf)
301
+ ui.mode = 'params'
302
+ return True
303
+
304
+ if c == '\x7f' or c == '\x08':
305
+ if ui.edit_cursor > 0:
306
+ ui.edit_buf = ui.edit_buf[:ui.edit_cursor-1] + ui.edit_buf[ui.edit_cursor:]
307
+ ui.edit_cursor -= 1
308
+ elif c >= ' ' and c <= '~':
309
+ ui.edit_buf = ui.edit_buf[:ui.edit_cursor] + c + ui.edit_buf[ui.edit_cursor:]
310
+ ui.edit_cursor += 1
311
+
312
+ return True
313
+
314
+ def handle_gallery(c, fd):
315
+ if c == '\x1b':
316
+ if select.select([fd], [], [], 0.05)[0]:
317
+ c2 = os.read(fd, 1).decode('latin-1')
318
+ if c2 == '[':
319
+ c3 = os.read(fd, 1).decode('latin-1')
320
+ if c3 == 'A':
321
+ ui.gallery_sel = max(0, ui.gallery_sel - 1)
322
+ elif c3 == 'B':
323
+ ui.gallery_sel = min(max(0, len(ui.gallery) - 1), ui.gallery_sel + 1)
324
+ else:
325
+ ui.mode = 'params'
326
+ return True
327
+
328
+ if c == 'q':
329
+ return False
330
+ elif c == 'b':
331
+ ui.mode = 'params'
332
+ elif c == 'j':
333
+ ui.gallery_sel = min(max(0, len(ui.gallery) - 1), ui.gallery_sel + 1)
334
+ elif c == 'k':
335
+ ui.gallery_sel = max(0, ui.gallery_sel - 1)
336
+ elif c == 'o' and ui.gallery:
337
+ import subprocess
338
+ path = ui.gallery[ui.gallery_sel]['path']
339
+ try:
340
+ subprocess.Popen(['xdg-open', path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
341
+ ui.status = "Opened: " + os.path.basename(path)
342
+ except:
343
+ ui.status = "Could not open image"
344
+
345
+ # Keep gallery_scroll in range
346
+ _, height = get_size()
347
+ gallery_h = height - 6
348
+ if ui.gallery_sel < ui.gallery_scroll:
349
+ ui.gallery_scroll = ui.gallery_sel
350
+ elif ui.gallery_sel >= ui.gallery_scroll + gallery_h:
351
+ ui.gallery_scroll = ui.gallery_sel - gallery_h + 1
352
+
353
+ return True
354
+
355
+ # ========== One-shot mode ==========
356
+ if ui.prompt and context.get('prompt'):
357
+ # If prompt provided via CLI args, generate immediately
358
+ generate_images()
359
+ context['output'] = ui.status
360
+ context['messages'] = context.get('messages', [])
361
+
362
+ # ========== Main TUI Loop ==========
363
+ elif not sys.stdin.isatty():
364
+ context['output'] = "Vixynt requires an interactive terminal."
365
+ else:
366
+ fd = sys.stdin.fileno()
367
+ old_settings = termios.tcgetattr(fd)
368
+
369
+ try:
370
+ tty.setcbreak(fd)
371
+ sys.stdout.write('\033[?25l')
372
+ sys.stdout.write('\033[2J')
373
+ render_screen()
374
+
375
+ running = True
376
+ while running:
377
+ c = os.read(fd, 1).decode('latin-1')
378
+ running = handle_input(c, fd)
379
+ render_screen()
380
+
381
+ finally:
382
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
383
+ sys.stdout.write('\033[?25h')
384
+ sys.stdout.write('\033[2J\033[H')
385
+ sys.stdout.flush()
386
+
387
+ context['output'] = ui.status
388
+ context['messages'] = context.get('messages', [])