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
@@ -0,0 +1,574 @@
1
+ jinx_name: plonk
2
+ description: Vision-based GUI automation TUI - visual task management and action automation
3
+ interactive: true
4
+ inputs:
5
+ - task: null
6
+ - vmodel: null
7
+ - vprovider: null
8
+ - max_iterations: 10
9
+ - debug: false
10
+
11
+ steps:
12
+ - name: plonk_tui
13
+ engine: python
14
+ code: |
15
+ import os
16
+ import sys
17
+ import tty
18
+ import termios
19
+ import select
20
+ import time
21
+ import json
22
+ import platform
23
+ from datetime import datetime
24
+
25
+ from npcpy.llm_funcs import get_llm_response
26
+
27
+ try:
28
+ from npcpy.data.image import capture_screenshot
29
+ from npcpy.work.desktop import perform_action
30
+ VISION_AVAILABLE = True
31
+ except ImportError:
32
+ VISION_AVAILABLE = False
33
+
34
+ npc = context.get('npc')
35
+ team = context.get('team')
36
+ messages = context.get('messages', [])
37
+
38
+ if isinstance(npc, str) and team:
39
+ npc = team.get(npc) if hasattr(team, 'get') else None
40
+ elif isinstance(npc, str):
41
+ npc = None
42
+
43
+ vision_model = context.get('vmodel') or (npc.model if npc and hasattr(npc, 'model') else None)
44
+ vision_provider = context.get('vprovider') or (npc.provider if npc and hasattr(npc, 'provider') else None)
45
+ max_iterations = int(context.get('max_iterations', 10))
46
+
47
+ system_name = platform.system()
48
+ if system_name == "Windows":
49
+ app_examples = "start firefox, notepad, calc"
50
+ elif system_name == "Darwin":
51
+ app_examples = "open -a Firefox, open -a TextEdit"
52
+ else:
53
+ app_examples = "firefox &, gedit &, gnome-calculator &"
54
+
55
+ json_schema_example = '{"action":"click","x":50,"y":30,"reason":"Click the URL bar"}'
56
+
57
+ # ========== State ==========
58
+ class PlonkState:
59
+ def __init__(self):
60
+ self.tasks = [] # [{"text":str, "status":"pending|running|done|failed", "actions":[]}]
61
+ self.sel = 0
62
+ self.scroll = 0
63
+ self.panel = 0 # 0=tasks, 1=actions
64
+ self.mode = 'idle' # idle, input, running, paused, step
65
+ self.input_buf = ""
66
+ self.input_cursor = 0
67
+ self.current_task = -1
68
+ self.iteration = 0
69
+ self.max_iter = max_iterations
70
+ self.status = "Ready"
71
+ self.action_scroll = 0
72
+ self.last_screenshot = ""
73
+
74
+ ui = PlonkState()
75
+
76
+ # ========== Helpers ==========
77
+ def get_size():
78
+ try:
79
+ s = os.get_terminal_size()
80
+ return s.columns, s.lines
81
+ except:
82
+ return 80, 24
83
+
84
+ def add_task(text):
85
+ ui.tasks.append({"text": text.strip(), "status": "pending", "actions": []})
86
+
87
+ def delete_task():
88
+ if ui.tasks and ui.mode == 'idle':
89
+ del ui.tasks[ui.sel]
90
+ if ui.sel >= len(ui.tasks):
91
+ ui.sel = max(0, len(ui.tasks) - 1)
92
+
93
+ def get_current_actions():
94
+ if ui.panel == 1 and 0 <= ui.sel < len(ui.tasks):
95
+ return ui.tasks[ui.sel].get('actions', [])
96
+ if ui.current_task >= 0 and ui.current_task < len(ui.tasks):
97
+ return ui.tasks[ui.current_task].get('actions', [])
98
+ return []
99
+
100
+ # ========== Automation ==========
101
+ def run_one_step():
102
+ if ui.current_task < 0 or ui.current_task >= len(ui.tasks):
103
+ ui.mode = 'idle'
104
+ ui.status = "No task"
105
+ return
106
+
107
+ task = ui.tasks[ui.current_task]
108
+
109
+ if ui.iteration >= ui.max_iter:
110
+ task['status'] = 'failed'
111
+ task['actions'].append({"action": "fail", "reason": "Max iterations reached"})
112
+ advance_to_next_task()
113
+ return
114
+
115
+ if not VISION_AVAILABLE:
116
+ task['status'] = 'failed'
117
+ task['actions'].append({"action": "fail", "reason": "Vision/desktop modules not available"})
118
+ advance_to_next_task()
119
+ return
120
+
121
+ ui.iteration += 1
122
+ ui.status = "Capturing screen..."
123
+ render_screen()
124
+
125
+ try:
126
+ ss = capture_screenshot(full=True)
127
+ if not ss or 'file_path' not in ss:
128
+ task['actions'].append({"action": "fail", "reason": "Screenshot failed"})
129
+ task['status'] = 'failed'
130
+ advance_to_next_task()
131
+ return
132
+
133
+ screenshot_path = ss['file_path']
134
+ ui.last_screenshot = screenshot_path
135
+
136
+ history_context = ""
137
+ if task['actions']:
138
+ history_context = "\nPrevious actions:\n"
139
+ for i, act in enumerate(task['actions'][-5:], 1):
140
+ history_context += " " + str(i) + ". " + act.get('action', '?')
141
+ if act.get('x'):
142
+ history_context += " at (" + str(act.get('x', '?')) + ", " + str(act.get('y', '?')) + ")"
143
+ history_context += " - " + act.get('reason', '') + "\n"
144
+
145
+ prompt = "You are a GUI automation assistant. Analyze this screenshot and determine the next action.\n\n"
146
+ prompt += "IMPORTANT: Before taking new actions, VERIFY the current screenshot shows the expected result of your previous actions. "
147
+ prompt += "If the page is still loading or hasn't changed yet, use 'wait' with duration 2-3 seconds. "
148
+ prompt += "Do NOT blindly proceed — confirm each step worked before moving on.\n\n"
149
+ prompt += "TASK: " + task['text'] + "\n"
150
+ prompt += history_context + "\n"
151
+ prompt += "Available actions:\n"
152
+ prompt += "- click: Click at x,y coordinates (0-100 percentage of screen)\n"
153
+ prompt += "- type: Type text (use 'text' field)\n"
154
+ prompt += "- key: Press key like enter, tab, escape (use 'text' field)\n"
155
+ prompt += "- launch: Launch application (use 'command' field, e.g. " + app_examples + ")\n"
156
+ prompt += "- wait: Wait for 'duration' seconds (use when page is loading or UI hasn't updated)\n"
157
+ prompt += "- done: Task completed successfully\n"
158
+ prompt += "- fail: Task cannot be completed\n\n"
159
+ prompt += "Respond with JSON, e.g.: " + json_schema_example
160
+
161
+ ui.status = "Thinking... (iter " + str(ui.iteration) + "/" + str(ui.max_iter) + ")"
162
+ render_screen()
163
+
164
+ resp = get_llm_response(
165
+ prompt,
166
+ model=vision_model,
167
+ provider=vision_provider,
168
+ images=[screenshot_path],
169
+ format="json",
170
+ npc=npc
171
+ )
172
+
173
+ action_response = resp.get('response', {})
174
+ if isinstance(action_response, str):
175
+ try:
176
+ action_response = json.loads(action_response)
177
+ except:
178
+ task['actions'].append({"action": "error", "reason": "Invalid JSON from model"})
179
+ ui.status = "Bad response, retrying..."
180
+ return
181
+
182
+ action = action_response.get('action', 'fail')
183
+ reason = action_response.get('reason', '')
184
+
185
+ if action == 'done':
186
+ task['status'] = 'done'
187
+ task['actions'].append({"action": "done", "reason": reason})
188
+ advance_to_next_task()
189
+ return
190
+
191
+ if action == 'fail':
192
+ task['status'] = 'failed'
193
+ task['actions'].append({"action": "fail", "reason": reason})
194
+ advance_to_next_task()
195
+ return
196
+
197
+ # Execute the action
198
+ act_record = {"action": action, "reason": reason}
199
+
200
+ if action == 'click':
201
+ x, y = action_response.get('x', 50), action_response.get('y', 50)
202
+ perform_action({"type": "click", "x": x, "y": y})
203
+ act_record['x'] = x
204
+ act_record['y'] = y
205
+ elif action == 'type':
206
+ txt = action_response.get('text', '')
207
+ perform_action({"type": "type", "text": txt})
208
+ act_record['text'] = txt
209
+ elif action == 'key':
210
+ key = action_response.get('text', 'enter')
211
+ perform_action({"type": "key", "key": key})
212
+ act_record['key'] = key
213
+ elif action == 'launch':
214
+ cmd = action_response.get('command', '')
215
+ perform_action({"type": "launch", "command": cmd})
216
+ act_record['command'] = cmd
217
+ time.sleep(2)
218
+ elif action == 'wait':
219
+ dur = action_response.get('duration', 1)
220
+ time.sleep(dur)
221
+ act_record['duration'] = dur
222
+
223
+ task['actions'].append(act_record)
224
+ ui.status = action + " - " + reason[:40]
225
+ # Wait for UI to settle after state-changing actions
226
+ if action in ('key', 'click'):
227
+ time.sleep(2.0)
228
+ elif action == 'type':
229
+ time.sleep(0.5)
230
+ else:
231
+ time.sleep(0.3)
232
+
233
+ if ui.mode == 'step':
234
+ ui.mode = 'paused'
235
+ ui.status = "Paused (step done)"
236
+
237
+ except Exception as e:
238
+ task['actions'].append({"action": "error", "reason": str(e)})
239
+ ui.status = "Error: " + str(e)[:40]
240
+
241
+ def advance_to_next_task():
242
+ # Find next pending task
243
+ for i in range(len(ui.tasks)):
244
+ if ui.tasks[i]['status'] == 'pending':
245
+ start_task(i)
246
+ return
247
+ # All done
248
+ ui.mode = 'idle'
249
+ ui.current_task = -1
250
+ done_count = sum(1 for t in ui.tasks if t['status'] == 'done')
251
+ fail_count = sum(1 for t in ui.tasks if t['status'] == 'failed')
252
+ ui.status = "Complete: " + str(done_count) + " done, " + str(fail_count) + " failed"
253
+
254
+ def start_task(idx):
255
+ ui.current_task = idx
256
+ ui.tasks[idx]['status'] = 'running'
257
+ ui.iteration = 0
258
+ ui.mode = 'running'
259
+ ui.status = "Running: " + ui.tasks[idx]['text'][:30]
260
+
261
+ def start_selected():
262
+ if not ui.tasks:
263
+ return
264
+ if ui.tasks[ui.sel]['status'] in ('pending', 'failed'):
265
+ ui.tasks[ui.sel]['status'] = 'pending'
266
+ ui.tasks[ui.sel]['actions'] = []
267
+ start_task(ui.sel)
268
+
269
+ def run_all():
270
+ if not ui.tasks:
271
+ return
272
+ for t in ui.tasks:
273
+ if t['status'] != 'done':
274
+ t['status'] = 'pending'
275
+ t['actions'] = []
276
+ # Find first pending
277
+ for i, t in enumerate(ui.tasks):
278
+ if t['status'] == 'pending':
279
+ start_task(i)
280
+ return
281
+
282
+ # ========== Rendering ==========
283
+ def render_screen():
284
+ width, height = get_size()
285
+ out = []
286
+ out.append("\033[H")
287
+
288
+ # Header
289
+ mode_str = ui.mode.upper()
290
+ if ui.mode == 'running' and ui.current_task >= 0:
291
+ mode_str = "RUNNING [" + str(ui.iteration) + "/" + str(ui.max_iter) + "]"
292
+ header = " PLONK - Visual Task Automation "
293
+ mode_display = " [" + mode_str + "] "
294
+ out.append("\033[1;1H\033[7;1m" + header.ljust(width) + "\033[0m")
295
+ out.append("\033[1;" + str(width - len(mode_display) - 1) + "H\033[33;1m" + mode_display + "\033[0m")
296
+
297
+ # Split: top = tasks, bottom = actions
298
+ split = max(6, (height - 4) // 2)
299
+ task_h = split - 2
300
+ action_h = height - split - 4
301
+
302
+ # ── Tasks panel ──
303
+ task_label = " Tasks (" + str(len(ui.tasks)) + ") "
304
+ if ui.panel == 0:
305
+ out.append("\033[3;1H\033[36;1m" + task_label + "\033[90m" + ("-" * (width - len(task_label))) + "\033[0m")
306
+ else:
307
+ out.append("\033[3;1H\033[90m" + task_label + ("-" * (width - len(task_label))) + "\033[0m")
308
+
309
+ if ui.mode == 'input':
310
+ # Show input line
311
+ out.append("\033[4;1H\033[K New task: \033[7m " + ui.input_buf + " \033[0m")
312
+ for i in range(1, task_h):
313
+ out.append("\033[" + str(4+i) + ";1H\033[K")
314
+ elif not ui.tasks:
315
+ out.append("\033[4;1H\033[K\033[90m No tasks. Press 'a' to add a task.\033[0m")
316
+ for i in range(1, task_h):
317
+ out.append("\033[" + str(4+i) + ";1H\033[K")
318
+ else:
319
+ if ui.sel < ui.scroll:
320
+ ui.scroll = ui.sel
321
+ elif ui.sel >= ui.scroll + task_h:
322
+ ui.scroll = ui.sel - task_h + 1
323
+
324
+ for i in range(task_h):
325
+ idx = ui.scroll + i
326
+ row = 4 + i
327
+ out.append("\033[" + str(row) + ";1H\033[K")
328
+ if idx >= len(ui.tasks):
329
+ continue
330
+
331
+ t = ui.tasks[idx]
332
+ status = t['status']
333
+ icon = {"pending": "\033[90m-", "running": "\033[33m>", "done": "\033[32m+", "failed": "\033[31mx"}.get(status, " ")
334
+ action_count = str(len(t.get('actions', [])))
335
+ text = t['text'][:width - 25]
336
+
337
+ line = " " + icon + "\033[0m " + text + " \033[90m[" + status + "] (" + action_count + " acts)\033[0m"
338
+
339
+ if idx == ui.sel and ui.panel == 0:
340
+ out.append("\033[7m>" + line + "\033[0m")
341
+ else:
342
+ out.append(" " + line)
343
+
344
+ # ── Actions panel ──
345
+ action_row = 3 + split
346
+ acts = get_current_actions()
347
+ act_label = " Actions (" + str(len(acts)) + ") "
348
+ if ui.panel == 1:
349
+ out.append("\033[" + str(action_row) + ";1H\033[36;1m" + act_label + "\033[90m" + ("-" * (width - len(act_label))) + "\033[0m")
350
+ else:
351
+ out.append("\033[" + str(action_row) + ";1H\033[90m" + act_label + ("-" * (width - len(act_label))) + "\033[0m")
352
+
353
+ if not acts:
354
+ out.append("\033[" + str(action_row+1) + ";1H\033[K\033[90m No actions yet.\033[0m")
355
+ for i in range(1, action_h):
356
+ out.append("\033[" + str(action_row+1+i) + ";1H\033[K")
357
+ else:
358
+ if ui.action_scroll < 0:
359
+ ui.action_scroll = 0
360
+
361
+ for i in range(action_h):
362
+ idx = ui.action_scroll + i
363
+ row = action_row + 1 + i
364
+ out.append("\033[" + str(row) + ";1H\033[K")
365
+ if idx >= len(acts):
366
+ continue
367
+
368
+ a = acts[idx]
369
+ action = a.get('action', '?')
370
+ reason = a.get('reason', '')[:width - 35]
371
+ coords = ""
372
+ if a.get('x') is not None:
373
+ coords = "(" + str(a.get('x', '')) + "," + str(a.get('y', '')) + ") "
374
+ elif a.get('text'):
375
+ coords = '"' + str(a['text'])[:15] + '" '
376
+ elif a.get('key'):
377
+ coords = '[' + str(a['key']) + '] '
378
+ elif a.get('command'):
379
+ coords = str(a['command'])[:20] + ' '
380
+
381
+ act_color = {"click": "\033[33m", "type": "\033[36m", "key": "\033[35m",
382
+ "launch": "\033[34m", "done": "\033[32m", "fail": "\033[31m",
383
+ "error": "\033[31m", "wait": "\033[90m"}.get(action, "")
384
+
385
+ line = " " + str(idx+1) + ". " + act_color + action + "\033[0m " + coords + "\033[90m" + reason + "\033[0m"
386
+
387
+ if idx == ui.action_scroll and ui.panel == 1:
388
+ out.append("\033[7m " + line + "\033[0m")
389
+ else:
390
+ out.append(" " + line)
391
+
392
+ # ── Status bar ──
393
+ out.append("\033[" + str(height-2) + ";1H\033[K\033[90m" + ("-" * width) + "\033[0m")
394
+ status_line = " " + ui.status + " | Model: " + str(vision_model) + " | Max: " + str(ui.max_iter)
395
+ out.append("\033[" + str(height-1) + ";1H\033[K" + status_line[:width])
396
+
397
+ # ── Footer ──
398
+ if ui.mode == 'input':
399
+ footer = " Type task, Enter:Confirm Esc:Cancel "
400
+ elif ui.mode in ('running', 'step'):
401
+ footer = " p:Pause s:Step Q:Abort Tab:Panel j/k:Scroll "
402
+ elif ui.mode == 'paused':
403
+ footer = " r:Resume s:Step Q:Abort Tab:Panel j/k:Scroll "
404
+ else:
405
+ footer = " a:Add d:Delete Enter:Run R:RunAll Tab:Panel j/k:Nav q:Quit "
406
+ out.append("\033[" + str(height) + ";1H\033[K\033[7m" + footer.ljust(width) + "\033[0m")
407
+
408
+ sys.stdout.write(''.join(out))
409
+ sys.stdout.flush()
410
+
411
+ # ========== Input Handling ==========
412
+ def handle_input(c, fd):
413
+ if ui.mode == 'input':
414
+ return handle_input_mode(c, fd)
415
+
416
+ # Escape sequences
417
+ if c == '\x1b':
418
+ if select.select([fd], [], [], 0.05)[0]:
419
+ c2 = os.read(fd, 1).decode('latin-1')
420
+ if c2 == '[':
421
+ c3 = os.read(fd, 1).decode('latin-1')
422
+ if c3 == 'A': # Up
423
+ nav_up()
424
+ elif c3 == 'B': # Down
425
+ nav_down()
426
+ else:
427
+ # Bare Esc
428
+ if ui.mode == 'paused':
429
+ ui.mode = 'idle'
430
+ ui.status = "Aborted"
431
+ if ui.current_task >= 0 and ui.current_task < len(ui.tasks):
432
+ ui.tasks[ui.current_task]['status'] = 'failed'
433
+ ui.current_task = -1
434
+ return True
435
+
436
+ if c == 'q' and ui.mode == 'idle':
437
+ return False
438
+ if c == 'Q':
439
+ # Abort running
440
+ if ui.mode in ('running', 'paused', 'step'):
441
+ ui.mode = 'idle'
442
+ if ui.current_task >= 0 and ui.current_task < len(ui.tasks):
443
+ ui.tasks[ui.current_task]['status'] = 'failed'
444
+ ui.current_task = -1
445
+ ui.status = "Aborted"
446
+ elif ui.mode == 'idle':
447
+ return False
448
+ return True
449
+
450
+ if c == 'j':
451
+ nav_down()
452
+ elif c == 'k':
453
+ nav_up()
454
+ elif c == '\t':
455
+ ui.panel = 1 - ui.panel
456
+ ui.action_scroll = 0
457
+ elif c == 'a' and ui.mode == 'idle':
458
+ ui.mode = 'input'
459
+ ui.input_buf = ""
460
+ ui.input_cursor = 0
461
+ elif c == 'd' and ui.mode == 'idle':
462
+ delete_task()
463
+ elif c in ('\r', '\n') and ui.mode == 'idle':
464
+ start_selected()
465
+ elif c == 'R' and ui.mode == 'idle':
466
+ run_all()
467
+ elif c == 'p' and ui.mode == 'running':
468
+ ui.mode = 'paused'
469
+ ui.status = "Paused"
470
+ elif c == 'r' and ui.mode == 'paused':
471
+ ui.mode = 'running'
472
+ ui.status = "Resumed"
473
+ elif c == 's' and ui.mode in ('paused', 'idle'):
474
+ if ui.current_task >= 0:
475
+ ui.mode = 'step'
476
+ elif ui.tasks and ui.tasks[ui.sel]['status'] in ('pending', 'failed'):
477
+ ui.tasks[ui.sel]['actions'] = []
478
+ start_task(ui.sel)
479
+ ui.mode = 'step'
480
+
481
+ return True
482
+
483
+ def handle_input_mode(c, fd):
484
+ if c == '\x1b':
485
+ # Cancel input
486
+ if select.select([fd], [], [], 0.05)[0]:
487
+ os.read(fd, 2) # consume rest of escape seq
488
+ ui.mode = 'idle'
489
+ ui.input_buf = ""
490
+ return True
491
+
492
+ if c in ('\r', '\n'):
493
+ if ui.input_buf.strip():
494
+ add_task(ui.input_buf)
495
+ ui.sel = len(ui.tasks) - 1
496
+ ui.mode = 'idle'
497
+ ui.input_buf = ""
498
+ return True
499
+
500
+ if c == '\x7f' or c == '\x08': # Backspace
501
+ if ui.input_cursor > 0:
502
+ ui.input_buf = ui.input_buf[:ui.input_cursor-1] + ui.input_buf[ui.input_cursor:]
503
+ ui.input_cursor -= 1
504
+ elif c >= ' ' and c <= '~':
505
+ ui.input_buf = ui.input_buf[:ui.input_cursor] + c + ui.input_buf[ui.input_cursor:]
506
+ ui.input_cursor += 1
507
+
508
+ return True
509
+
510
+ def nav_up():
511
+ if ui.panel == 0:
512
+ ui.sel = max(0, ui.sel - 1)
513
+ else:
514
+ ui.action_scroll = max(0, ui.action_scroll - 1)
515
+
516
+ def nav_down():
517
+ if ui.panel == 0:
518
+ ui.sel = min(max(0, len(ui.tasks) - 1), ui.sel + 1)
519
+ else:
520
+ acts = get_current_actions()
521
+ ui.action_scroll = min(max(0, len(acts) - 1), ui.action_scroll + 1)
522
+
523
+ # ========== Auto-add task from CLI ==========
524
+ task_arg = context.get('task')
525
+ if task_arg:
526
+ add_task(str(task_arg))
527
+
528
+ # ========== Main Loop ==========
529
+ if not sys.stdin.isatty():
530
+ context['output'] = "Plonk requires an interactive terminal."
531
+ else:
532
+ fd = sys.stdin.fileno()
533
+ old_settings = termios.tcgetattr(fd)
534
+
535
+ try:
536
+ tty.setcbreak(fd)
537
+ sys.stdout.write('\033[?25l')
538
+ sys.stdout.write('\033[2J')
539
+ render_screen()
540
+
541
+ running = True
542
+ while running:
543
+ if ui.mode in ('running', 'step'):
544
+ # Non-blocking check for user input
545
+ if select.select([fd], [], [], 0.05)[0]:
546
+ c = os.read(fd, 1).decode('latin-1')
547
+ running = handle_input(c, fd)
548
+ else:
549
+ run_one_step()
550
+ else:
551
+ # Blocking wait for input
552
+ c = os.read(fd, 1).decode('latin-1')
553
+ running = handle_input(c, fd)
554
+
555
+ render_screen()
556
+
557
+ finally:
558
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
559
+ sys.stdout.write('\033[?25h')
560
+ sys.stdout.write('\033[2J\033[H')
561
+ sys.stdout.flush()
562
+
563
+ # Summary output
564
+ if ui.tasks:
565
+ lines = ["PLONK SESSION SUMMARY", "=" * 40]
566
+ for i, t in enumerate(ui.tasks):
567
+ lines.append(str(i+1) + ". [" + t['status'] + "] " + t['text'])
568
+ for j, a in enumerate(t.get('actions', [])):
569
+ lines.append(" " + str(j+1) + ". " + a.get('action', '?') + " - " + a.get('reason', '')[:50])
570
+ context['output'] = "\n".join(lines)
571
+ else:
572
+ context['output'] = "Plonk session ended."
573
+
574
+ context['messages'] = messages
@@ -123,7 +123,7 @@ steps:
123
123
  header = f" REATTACH ({len(convos)} convos): {target_path[:width-30]} "
124
124
  else:
125
125
  header = f" PREVIEW: {convos[selected]['conversation_id'][:width-12]} "
126
- sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
126
+ sys.stdout.write(f'\033[7;1m{header.ljust(width)}\033[0m\n')
127
127
  sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
128
128
 
129
129
  if mode == 'list':
@@ -173,7 +173,7 @@ steps:
173
173
  cost_str = f"${cost:.4f}" if cost else "-"
174
174
  tok_str = f"{in_tok}in/{out_tok}out" if (in_tok or out_tok) else "-"
175
175
  sys.stdout.write(f'\033[{height-1};1H\033[K {sel["conversation_id"][:16]} {sel_model} tokens:{tok_str} cost:{cost_str}'.ljust(width))
176
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav Enter:Select p:Preview q:Quit [{selected+1}/{len(convos)}] \033[0m')
176
+ sys.stdout.write(f'\033[{height};1H\033[K\033[7m j/k:Nav Enter:Select p:Preview q:Quit [{selected+1}/{len(convos)}] \033[0m')
177
177
  else:
178
178
  for i in range(list_height):
179
179
  idx = preview_scroll + i
@@ -216,7 +216,7 @@ steps:
216
216
 
217
217
  sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
218
218
  sys.stdout.write(f'\033[{height-1};1H\033[K {len(preview_msgs)} messages')
219
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back Enter:Select q:Quit \033[0m')
219
+ sys.stdout.write(f'\033[{height};1H\033[K\033[7m j/k:Scroll b:Back Enter:Select q:Quit \033[0m')
220
220
 
221
221
  sys.stdout.flush()
222
222
 
@@ -103,8 +103,8 @@ steps:
103
103
  out = []
104
104
  out.append("\033[2J\033[H")
105
105
  header = " NPCSH Setup Wizard "
106
- out.append(f"\033[1;1H\033[44;37;1m{'=' * width}\033[0m")
107
- out.append(f"\033[1;{(width - len(header)) // 2}H\033[44;37;1m{header}\033[0m")
106
+ out.append(f"\033[1;1H\033[7;1m{'=' * width}\033[0m")
107
+ out.append(f"\033[1;{(width - len(header)) // 2}H\033[7;1m{header}\033[0m")
108
108
 
109
109
  if state.phase == 'detect':
110
110
  out.append(f"\033[3;2H\033[1mDetected Providers:\033[0m")
@@ -149,10 +149,10 @@ steps:
149
149
  if c == 'q':
150
150
  return False
151
151
  if c == '\x1b':
152
- if select.select([sys.stdin], [], [], 0.05)[0]:
153
- c2 = sys.stdin.read(1)
152
+ if select.select([fd], [], [], 0.05)[0]:
153
+ c2 = os.read(fd, 1).decode('latin-1')
154
154
  if c2 == '[':
155
- c3 = sys.stdin.read(1)
155
+ c3 = os.read(fd, 1).decode('latin-1')
156
156
  if c3 == 'A' and state.phase == 'select_chat':
157
157
  state.selected_idx = max(0, state.selected_idx - 1)
158
158
  if state.selected_idx < state.scroll_offset:
@@ -225,7 +225,7 @@ steps:
225
225
  run_detection()
226
226
  render_screen()
227
227
  while True:
228
- c = sys.stdin.read(1)
228
+ c = os.read(fd, 1).decode('latin-1')
229
229
  if not handle_input(c):
230
230
  break
231
231
  render_screen()
@@ -17,8 +17,8 @@ primary_directive: |
17
17
  When delegating, match the task to the agent whose primary_directive best fits. Basic search inquiries can be handled by yourself. Do not delegate unnecessarily.
18
18
  You have access to the delegate tool to pass tasks to other agents.
19
19
  jinxs:
20
- - lib/orchestration/delegate
21
- - lib/orchestration/convene
20
+ - lib/core/delegate
21
+ - lib/core/convene
22
22
  - lib/core/sh
23
23
  - lib/core/python
24
- - lib/core/search
24
+ - lib/core/search/web_search
@@ -87,7 +87,7 @@ steps:
87
87
  header = f" CONVERSATION HISTORY ({len(history)} messages) "
88
88
  else:
89
89
  header = f" MESSAGE {selected + 1} "
90
- sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
90
+ sys.stdout.write(f'\033[7;1m{header.ljust(width)}\033[0m\n')
91
91
 
92
92
  if mode == 'list':
93
93
  col_header = f' {"#":<4} {"ROLE":<12} {"PREVIEW":<60}'
@@ -129,7 +129,7 @@ steps:
129
129
  user_msgs = len([m for m in history if m.get('role') == 'user'])
130
130
  asst_msgs = len([m for m in history if m.get('role') == 'assistant'])
131
131
  sys.stdout.write(f'\033[{height-1};1H\033[K User: {user_msgs} | Assistant: {asst_msgs}'.ljust(width)[:width])
132
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav p:Preview c:Copy q:Quit [{selected+1}/{len(history)}] \033[0m')
132
+ sys.stdout.write(f'\033[{height};1H\033[K\033[7m j/k:Nav p:Preview c:Copy q:Quit [{selected+1}/{len(history)}] \033[0m')
133
133
 
134
134
  else: # preview mode
135
135
  for i in range(list_height):
@@ -140,7 +140,7 @@ steps:
140
140
 
141
141
  sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
142
142
  sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(preview_lines)} lines]')
143
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back c:Copy q:Quit \033[0m')
143
+ sys.stdout.write(f'\033[{height};1H\033[K\033[7m j/k:Scroll b:Back c:Copy q:Quit \033[0m')
144
144
 
145
145
  sys.stdout.flush()
146
146