npcsh 1.1.19__py3-none-any.whl → 1.1.21__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 (173) hide show
  1. npcsh/_state.py +16 -78
  2. npcsh/diff_viewer.py +3 -3
  3. npcsh/npc_team/jinxs/lib/core/compress.jinx +373 -85
  4. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +18 -7
  5. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +18 -7
  6. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +20 -9
  7. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +53 -15
  8. npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
  9. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +393 -317
  10. npcsh/npc_team/jinxs/lib/utils/models.jinx +343 -0
  11. npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +8 -7
  12. npcsh/npc_team/jinxs/modes/alicanto.jinx +1573 -296
  13. npcsh/npc_team/jinxs/modes/arxiv.jinx +6 -6
  14. npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
  15. npcsh/npc_team/jinxs/modes/corca.jinx +4 -4
  16. npcsh/npc_team/jinxs/modes/git.jinx +795 -0
  17. npcsh/npc_team/jinxs/modes/guac.jinx +4 -4
  18. npcsh/npc_team/jinxs/modes/kg.jinx +941 -0
  19. npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
  20. npcsh/npc_team/jinxs/modes/nql.jinx +460 -0
  21. npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
  22. npcsh/npc_team/jinxs/modes/plonk.jinx +490 -304
  23. npcsh/npc_team/jinxs/modes/pti.jinx +1 -1
  24. npcsh/npc_team/jinxs/modes/reattach.jinx +4 -4
  25. npcsh/npc_team/jinxs/modes/spool.jinx +4 -4
  26. npcsh/npc_team/jinxs/modes/team.jinx +504 -0
  27. npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
  28. npcsh/npc_team/jinxs/modes/wander.jinx +455 -182
  29. npcsh/npc_team/jinxs/modes/yap.jinx +10 -3
  30. npcsh/npcsh.py +112 -47
  31. npcsh/routes.py +12 -3
  32. npcsh/salmon_simulation.py +0 -0
  33. npcsh-1.1.21.data/data/npcsh/npc_team/alicanto.jinx +1633 -0
  34. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/arxiv.jinx +6 -6
  35. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
  36. npcsh-1.1.21.data/data/npcsh/npc_team/compress.jinx +428 -0
  37. npcsh-1.1.21.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  38. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.jinx +4 -4
  39. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/db_search.jinx +18 -7
  40. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/file_search.jinx +18 -7
  41. npcsh-1.1.21.data/data/npcsh/npc_team/git.jinx +795 -0
  42. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.jinx +4 -4
  43. npcsh-1.1.21.data/data/npcsh/npc_team/jinxs.jinx +407 -0
  44. npcsh-1.1.21.data/data/npcsh/npc_team/kg.jinx +941 -0
  45. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kg_search.jinx +20 -9
  46. npcsh-1.1.21.data/data/npcsh/npc_team/memories.jinx +414 -0
  47. npcsh-1.1.21.data/data/npcsh/npc_team/models.jinx +343 -0
  48. npcsh-1.1.21.data/data/npcsh/npc_team/nql.jinx +460 -0
  49. npcsh-1.1.21.data/data/npcsh/npc_team/papers.jinx +578 -0
  50. npcsh-1.1.21.data/data/npcsh/npc_team/plonk.jinx +565 -0
  51. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/pti.jinx +1 -1
  52. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/reattach.jinx +4 -4
  53. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/setup.jinx +8 -7
  54. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.jinx +4 -4
  55. npcsh-1.1.21.data/data/npcsh/npc_team/team.jinx +504 -0
  56. npcsh-1.1.21.data/data/npcsh/npc_team/vixynt.jinx +388 -0
  57. npcsh-1.1.21.data/data/npcsh/npc_team/wander.jinx +728 -0
  58. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/web_search.jinx +53 -15
  59. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.jinx +10 -3
  60. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/METADATA +2 -2
  61. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/RECORD +147 -148
  62. npcsh-1.1.21.dist-info/entry_points.txt +11 -0
  63. npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -299
  64. npcsh/npc_team/jinxs/bin/memories.jinx +0 -316
  65. npcsh/npc_team/jinxs/bin/nql.jinx +0 -141
  66. npcsh/npc_team/jinxs/bin/team_tui.jinx +0 -327
  67. npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
  68. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
  69. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
  70. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
  71. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
  72. npcsh/npc_team/plonkjr.npc +0 -23
  73. npcsh-1.1.19.data/data/npcsh/npc_team/alicanto.jinx +0 -356
  74. npcsh-1.1.19.data/data/npcsh/npc_team/compress.jinx +0 -140
  75. npcsh-1.1.19.data/data/npcsh/npc_team/config_tui.jinx +0 -299
  76. npcsh-1.1.19.data/data/npcsh/npc_team/jinxs.jinx +0 -331
  77. npcsh-1.1.19.data/data/npcsh/npc_team/mem_review.jinx +0 -73
  78. npcsh-1.1.19.data/data/npcsh/npc_team/mem_search.jinx +0 -388
  79. npcsh-1.1.19.data/data/npcsh/npc_team/memories.jinx +0 -316
  80. npcsh-1.1.19.data/data/npcsh/npc_team/nql.jinx +0 -141
  81. npcsh-1.1.19.data/data/npcsh/npc_team/paper_search.jinx +0 -412
  82. npcsh-1.1.19.data/data/npcsh/npc_team/plonk.jinx +0 -379
  83. npcsh-1.1.19.data/data/npcsh/npc_team/plonkjr.npc +0 -23
  84. npcsh-1.1.19.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
  85. npcsh-1.1.19.data/data/npcsh/npc_team/team_tui.jinx +0 -327
  86. npcsh-1.1.19.data/data/npcsh/npc_team/vixynt.jinx +0 -122
  87. npcsh-1.1.19.data/data/npcsh/npc_team/wander.jinx +0 -455
  88. npcsh-1.1.19.dist-info/entry_points.txt +0 -22
  89. /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
  90. /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
  91. /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
  92. /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
  93. /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
  94. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  95. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  96. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.png +0 -0
  97. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  98. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  99. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/build.jinx +0 -0
  100. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/chat.jinx +0 -0
  101. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/click.jinx +0 -0
  102. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  103. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  104. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  105. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  106. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/compile.jinx +0 -0
  107. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  108. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/convene.jinx +0 -0
  109. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.npc +0 -0
  110. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.png +0 -0
  111. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca_example.png +0 -0
  112. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  113. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  114. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  115. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic.npc +0 -0
  116. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic4.png +0 -0
  117. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.npc +0 -0
  118. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.png +0 -0
  119. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/help.jinx +0 -0
  120. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  121. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/init.jinx +0 -0
  122. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  123. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  124. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  125. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  126. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  127. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  128. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  129. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/notify.jinx +0 -0
  130. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  131. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  132. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  133. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  134. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/ots.jinx +0 -0
  135. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/paste.jinx +0 -0
  136. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.npc +0 -0
  137. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.png +0 -0
  138. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  139. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/python.jinx +0 -0
  140. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  141. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/roll.jinx +0 -0
  142. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  143. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sample.jinx +0 -0
  144. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  145. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/search.jinx +0 -0
  146. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  147. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/serve.jinx +0 -0
  148. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/set.jinx +0 -0
  149. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sh.jinx +0 -0
  150. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/shh.jinx +0 -0
  151. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  152. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.png +0 -0
  153. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  154. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  155. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.png +0 -0
  156. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sql.jinx +0 -0
  157. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch.jinx +0 -0
  158. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  159. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  160. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switches.jinx +0 -0
  161. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sync.jinx +0 -0
  162. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  163. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  164. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  165. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/usage.jinx +0 -0
  166. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  167. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/wait.jinx +0 -0
  168. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  169. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.png +0 -0
  170. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  171. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/WHEEL +0 -0
  172. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/licenses/LICENSE +0 -0
  173. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,565 @@
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()
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 += "TASK: " + task['text'] + "\n"
147
+ prompt += history_context + "\n"
148
+ prompt += "Available actions:\n"
149
+ prompt += "- click: Click at x,y coordinates (0-100 percentage of screen)\n"
150
+ prompt += "- type: Type text (use 'text' field)\n"
151
+ prompt += "- key: Press key like enter, tab, escape (use 'text' field)\n"
152
+ prompt += "- launch: Launch application (use 'command' field, e.g. " + app_examples + ")\n"
153
+ prompt += "- wait: Wait for 'duration' seconds\n"
154
+ prompt += "- done: Task completed successfully\n"
155
+ prompt += "- fail: Task cannot be completed\n\n"
156
+ prompt += "Respond with JSON, e.g.: " + json_schema_example
157
+
158
+ ui.status = "Thinking... (iter " + str(ui.iteration) + "/" + str(ui.max_iter) + ")"
159
+ render_screen()
160
+
161
+ resp = get_llm_response(
162
+ prompt,
163
+ model=vision_model,
164
+ provider=vision_provider,
165
+ images=[screenshot_path],
166
+ format="json",
167
+ npc=npc
168
+ )
169
+
170
+ action_response = resp.get('response', {})
171
+ if isinstance(action_response, str):
172
+ try:
173
+ action_response = json.loads(action_response)
174
+ except:
175
+ task['actions'].append({"action": "error", "reason": "Invalid JSON from model"})
176
+ ui.status = "Bad response, retrying..."
177
+ return
178
+
179
+ action = action_response.get('action', 'fail')
180
+ reason = action_response.get('reason', '')
181
+
182
+ if action == 'done':
183
+ task['status'] = 'done'
184
+ task['actions'].append({"action": "done", "reason": reason})
185
+ advance_to_next_task()
186
+ return
187
+
188
+ if action == 'fail':
189
+ task['status'] = 'failed'
190
+ task['actions'].append({"action": "fail", "reason": reason})
191
+ advance_to_next_task()
192
+ return
193
+
194
+ # Execute the action
195
+ act_record = {"action": action, "reason": reason}
196
+
197
+ if action == 'click':
198
+ x, y = action_response.get('x', 50), action_response.get('y', 50)
199
+ perform_action('click', x=x, y=y)
200
+ act_record['x'] = x
201
+ act_record['y'] = y
202
+ elif action == 'type':
203
+ txt = action_response.get('text', '')
204
+ perform_action('type', text=txt)
205
+ act_record['text'] = txt
206
+ elif action == 'key':
207
+ key = action_response.get('text', 'enter')
208
+ perform_action('key', key=key)
209
+ act_record['key'] = key
210
+ elif action == 'launch':
211
+ cmd = action_response.get('command', '')
212
+ perform_action('launch', command=cmd)
213
+ act_record['command'] = cmd
214
+ time.sleep(2)
215
+ elif action == 'wait':
216
+ dur = action_response.get('duration', 1)
217
+ time.sleep(dur)
218
+ act_record['duration'] = dur
219
+
220
+ task['actions'].append(act_record)
221
+ ui.status = action + " - " + reason[:40]
222
+ time.sleep(0.3)
223
+
224
+ if ui.mode == 'step':
225
+ ui.mode = 'paused'
226
+ ui.status = "Paused (step done)"
227
+
228
+ except Exception as e:
229
+ task['actions'].append({"action": "error", "reason": str(e)})
230
+ ui.status = "Error: " + str(e)[:40]
231
+
232
+ def advance_to_next_task():
233
+ # Find next pending task
234
+ for i in range(len(ui.tasks)):
235
+ if ui.tasks[i]['status'] == 'pending':
236
+ start_task(i)
237
+ return
238
+ # All done
239
+ ui.mode = 'idle'
240
+ ui.current_task = -1
241
+ done_count = sum(1 for t in ui.tasks if t['status'] == 'done')
242
+ fail_count = sum(1 for t in ui.tasks if t['status'] == 'failed')
243
+ ui.status = "Complete: " + str(done_count) + " done, " + str(fail_count) + " failed"
244
+
245
+ def start_task(idx):
246
+ ui.current_task = idx
247
+ ui.tasks[idx]['status'] = 'running'
248
+ ui.iteration = 0
249
+ ui.mode = 'running'
250
+ ui.status = "Running: " + ui.tasks[idx]['text'][:30]
251
+
252
+ def start_selected():
253
+ if not ui.tasks:
254
+ return
255
+ if ui.tasks[ui.sel]['status'] in ('pending', 'failed'):
256
+ ui.tasks[ui.sel]['status'] = 'pending'
257
+ ui.tasks[ui.sel]['actions'] = []
258
+ start_task(ui.sel)
259
+
260
+ def run_all():
261
+ if not ui.tasks:
262
+ return
263
+ for t in ui.tasks:
264
+ if t['status'] != 'done':
265
+ t['status'] = 'pending'
266
+ t['actions'] = []
267
+ # Find first pending
268
+ for i, t in enumerate(ui.tasks):
269
+ if t['status'] == 'pending':
270
+ start_task(i)
271
+ return
272
+
273
+ # ========== Rendering ==========
274
+ def render_screen():
275
+ width, height = get_size()
276
+ out = []
277
+ out.append("\033[H")
278
+
279
+ # Header
280
+ mode_str = ui.mode.upper()
281
+ if ui.mode == 'running' and ui.current_task >= 0:
282
+ mode_str = "RUNNING [" + str(ui.iteration) + "/" + str(ui.max_iter) + "]"
283
+ header = " PLONK - Visual Task Automation "
284
+ mode_display = " [" + mode_str + "] "
285
+ out.append("\033[1;1H\033[7;1m" + header.ljust(width) + "\033[0m")
286
+ out.append("\033[1;" + str(width - len(mode_display) - 1) + "H\033[33;1m" + mode_display + "\033[0m")
287
+
288
+ # Split: top = tasks, bottom = actions
289
+ split = max(6, (height - 4) // 2)
290
+ task_h = split - 2
291
+ action_h = height - split - 4
292
+
293
+ # ── Tasks panel ──
294
+ task_label = " Tasks (" + str(len(ui.tasks)) + ") "
295
+ if ui.panel == 0:
296
+ out.append("\033[3;1H\033[36;1m" + task_label + "\033[90m" + ("-" * (width - len(task_label))) + "\033[0m")
297
+ else:
298
+ out.append("\033[3;1H\033[90m" + task_label + ("-" * (width - len(task_label))) + "\033[0m")
299
+
300
+ if ui.mode == 'input':
301
+ # Show input line
302
+ out.append("\033[4;1H\033[K New task: \033[7m " + ui.input_buf + " \033[0m")
303
+ for i in range(1, task_h):
304
+ out.append("\033[" + str(4+i) + ";1H\033[K")
305
+ elif not ui.tasks:
306
+ out.append("\033[4;1H\033[K\033[90m No tasks. Press 'a' to add a task.\033[0m")
307
+ for i in range(1, task_h):
308
+ out.append("\033[" + str(4+i) + ";1H\033[K")
309
+ else:
310
+ if ui.sel < ui.scroll:
311
+ ui.scroll = ui.sel
312
+ elif ui.sel >= ui.scroll + task_h:
313
+ ui.scroll = ui.sel - task_h + 1
314
+
315
+ for i in range(task_h):
316
+ idx = ui.scroll + i
317
+ row = 4 + i
318
+ out.append("\033[" + str(row) + ";1H\033[K")
319
+ if idx >= len(ui.tasks):
320
+ continue
321
+
322
+ t = ui.tasks[idx]
323
+ status = t['status']
324
+ icon = {"pending": "\033[90m-", "running": "\033[33m>", "done": "\033[32m+", "failed": "\033[31mx"}.get(status, " ")
325
+ action_count = str(len(t.get('actions', [])))
326
+ text = t['text'][:width - 25]
327
+
328
+ line = " " + icon + "\033[0m " + text + " \033[90m[" + status + "] (" + action_count + " acts)\033[0m"
329
+
330
+ if idx == ui.sel and ui.panel == 0:
331
+ out.append("\033[7m>" + line + "\033[0m")
332
+ else:
333
+ out.append(" " + line)
334
+
335
+ # ── Actions panel ──
336
+ action_row = 3 + split
337
+ acts = get_current_actions()
338
+ act_label = " Actions (" + str(len(acts)) + ") "
339
+ if ui.panel == 1:
340
+ out.append("\033[" + str(action_row) + ";1H\033[36;1m" + act_label + "\033[90m" + ("-" * (width - len(act_label))) + "\033[0m")
341
+ else:
342
+ out.append("\033[" + str(action_row) + ";1H\033[90m" + act_label + ("-" * (width - len(act_label))) + "\033[0m")
343
+
344
+ if not acts:
345
+ out.append("\033[" + str(action_row+1) + ";1H\033[K\033[90m No actions yet.\033[0m")
346
+ for i in range(1, action_h):
347
+ out.append("\033[" + str(action_row+1+i) + ";1H\033[K")
348
+ else:
349
+ if ui.action_scroll < 0:
350
+ ui.action_scroll = 0
351
+
352
+ for i in range(action_h):
353
+ idx = ui.action_scroll + i
354
+ row = action_row + 1 + i
355
+ out.append("\033[" + str(row) + ";1H\033[K")
356
+ if idx >= len(acts):
357
+ continue
358
+
359
+ a = acts[idx]
360
+ action = a.get('action', '?')
361
+ reason = a.get('reason', '')[:width - 35]
362
+ coords = ""
363
+ if a.get('x') is not None:
364
+ coords = "(" + str(a.get('x', '')) + "," + str(a.get('y', '')) + ") "
365
+ elif a.get('text'):
366
+ coords = '"' + str(a['text'])[:15] + '" '
367
+ elif a.get('key'):
368
+ coords = '[' + str(a['key']) + '] '
369
+ elif a.get('command'):
370
+ coords = str(a['command'])[:20] + ' '
371
+
372
+ act_color = {"click": "\033[33m", "type": "\033[36m", "key": "\033[35m",
373
+ "launch": "\033[34m", "done": "\033[32m", "fail": "\033[31m",
374
+ "error": "\033[31m", "wait": "\033[90m"}.get(action, "")
375
+
376
+ line = " " + str(idx+1) + ". " + act_color + action + "\033[0m " + coords + "\033[90m" + reason + "\033[0m"
377
+
378
+ if idx == ui.action_scroll and ui.panel == 1:
379
+ out.append("\033[7m " + line + "\033[0m")
380
+ else:
381
+ out.append(" " + line)
382
+
383
+ # ── Status bar ──
384
+ out.append("\033[" + str(height-2) + ";1H\033[K\033[90m" + ("-" * width) + "\033[0m")
385
+ status_line = " " + ui.status + " | Model: " + str(vision_model) + " | Max: " + str(ui.max_iter)
386
+ out.append("\033[" + str(height-1) + ";1H\033[K" + status_line[:width])
387
+
388
+ # ── Footer ──
389
+ if ui.mode == 'input':
390
+ footer = " Type task, Enter:Confirm Esc:Cancel "
391
+ elif ui.mode in ('running', 'step'):
392
+ footer = " p:Pause s:Step Q:Abort Tab:Panel j/k:Scroll "
393
+ elif ui.mode == 'paused':
394
+ footer = " r:Resume s:Step Q:Abort Tab:Panel j/k:Scroll "
395
+ else:
396
+ footer = " a:Add d:Delete Enter:Run R:RunAll Tab:Panel j/k:Nav q:Quit "
397
+ out.append("\033[" + str(height) + ";1H\033[K\033[7m" + footer.ljust(width) + "\033[0m")
398
+
399
+ sys.stdout.write(''.join(out))
400
+ sys.stdout.flush()
401
+
402
+ # ========== Input Handling ==========
403
+ def handle_input(c, fd):
404
+ if ui.mode == 'input':
405
+ return handle_input_mode(c, fd)
406
+
407
+ # Escape sequences
408
+ if c == '\x1b':
409
+ if select.select([fd], [], [], 0.05)[0]:
410
+ c2 = os.read(fd, 1).decode('latin-1')
411
+ if c2 == '[':
412
+ c3 = os.read(fd, 1).decode('latin-1')
413
+ if c3 == 'A': # Up
414
+ nav_up()
415
+ elif c3 == 'B': # Down
416
+ nav_down()
417
+ else:
418
+ # Bare Esc
419
+ if ui.mode == 'paused':
420
+ ui.mode = 'idle'
421
+ ui.status = "Aborted"
422
+ if ui.current_task >= 0 and ui.current_task < len(ui.tasks):
423
+ ui.tasks[ui.current_task]['status'] = 'failed'
424
+ ui.current_task = -1
425
+ return True
426
+
427
+ if c == 'q' and ui.mode == 'idle':
428
+ return False
429
+ if c == 'Q':
430
+ # Abort running
431
+ if ui.mode in ('running', 'paused', 'step'):
432
+ ui.mode = 'idle'
433
+ if ui.current_task >= 0 and ui.current_task < len(ui.tasks):
434
+ ui.tasks[ui.current_task]['status'] = 'failed'
435
+ ui.current_task = -1
436
+ ui.status = "Aborted"
437
+ elif ui.mode == 'idle':
438
+ return False
439
+ return True
440
+
441
+ if c == 'j':
442
+ nav_down()
443
+ elif c == 'k':
444
+ nav_up()
445
+ elif c == '\t':
446
+ ui.panel = 1 - ui.panel
447
+ ui.action_scroll = 0
448
+ elif c == 'a' and ui.mode == 'idle':
449
+ ui.mode = 'input'
450
+ ui.input_buf = ""
451
+ ui.input_cursor = 0
452
+ elif c == 'd' and ui.mode == 'idle':
453
+ delete_task()
454
+ elif c in ('\r', '\n') and ui.mode == 'idle':
455
+ start_selected()
456
+ elif c == 'R' and ui.mode == 'idle':
457
+ run_all()
458
+ elif c == 'p' and ui.mode == 'running':
459
+ ui.mode = 'paused'
460
+ ui.status = "Paused"
461
+ elif c == 'r' and ui.mode == 'paused':
462
+ ui.mode = 'running'
463
+ ui.status = "Resumed"
464
+ elif c == 's' and ui.mode in ('paused', 'idle'):
465
+ if ui.current_task >= 0:
466
+ ui.mode = 'step'
467
+ elif ui.tasks and ui.tasks[ui.sel]['status'] in ('pending', 'failed'):
468
+ ui.tasks[ui.sel]['actions'] = []
469
+ start_task(ui.sel)
470
+ ui.mode = 'step'
471
+
472
+ return True
473
+
474
+ def handle_input_mode(c, fd):
475
+ if c == '\x1b':
476
+ # Cancel input
477
+ if select.select([fd], [], [], 0.05)[0]:
478
+ os.read(fd, 2) # consume rest of escape seq
479
+ ui.mode = 'idle'
480
+ ui.input_buf = ""
481
+ return True
482
+
483
+ if c in ('\r', '\n'):
484
+ if ui.input_buf.strip():
485
+ add_task(ui.input_buf)
486
+ ui.sel = len(ui.tasks) - 1
487
+ ui.mode = 'idle'
488
+ ui.input_buf = ""
489
+ return True
490
+
491
+ if c == '\x7f' or c == '\x08': # Backspace
492
+ if ui.input_cursor > 0:
493
+ ui.input_buf = ui.input_buf[:ui.input_cursor-1] + ui.input_buf[ui.input_cursor:]
494
+ ui.input_cursor -= 1
495
+ elif c >= ' ' and c <= '~':
496
+ ui.input_buf = ui.input_buf[:ui.input_cursor] + c + ui.input_buf[ui.input_cursor:]
497
+ ui.input_cursor += 1
498
+
499
+ return True
500
+
501
+ def nav_up():
502
+ if ui.panel == 0:
503
+ ui.sel = max(0, ui.sel - 1)
504
+ else:
505
+ ui.action_scroll = max(0, ui.action_scroll - 1)
506
+
507
+ def nav_down():
508
+ if ui.panel == 0:
509
+ ui.sel = min(max(0, len(ui.tasks) - 1), ui.sel + 1)
510
+ else:
511
+ acts = get_current_actions()
512
+ ui.action_scroll = min(max(0, len(acts) - 1), ui.action_scroll + 1)
513
+
514
+ # ========== Auto-add task from CLI ==========
515
+ task_arg = context.get('task')
516
+ if task_arg:
517
+ add_task(str(task_arg))
518
+
519
+ # ========== Main Loop ==========
520
+ if not sys.stdin.isatty():
521
+ context['output'] = "Plonk requires an interactive terminal."
522
+ else:
523
+ fd = sys.stdin.fileno()
524
+ old_settings = termios.tcgetattr(fd)
525
+
526
+ try:
527
+ tty.setcbreak(fd)
528
+ sys.stdout.write('\033[?25l')
529
+ sys.stdout.write('\033[2J')
530
+ render_screen()
531
+
532
+ running = True
533
+ while running:
534
+ if ui.mode in ('running', 'step'):
535
+ # Non-blocking check for user input
536
+ if select.select([fd], [], [], 0.05)[0]:
537
+ c = os.read(fd, 1).decode('latin-1')
538
+ running = handle_input(c, fd)
539
+ else:
540
+ run_one_step()
541
+ else:
542
+ # Blocking wait for input
543
+ c = os.read(fd, 1).decode('latin-1')
544
+ running = handle_input(c, fd)
545
+
546
+ render_screen()
547
+
548
+ finally:
549
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
550
+ sys.stdout.write('\033[?25h')
551
+ sys.stdout.write('\033[2J\033[H')
552
+ sys.stdout.flush()
553
+
554
+ # Summary output
555
+ if ui.tasks:
556
+ lines = ["PLONK SESSION SUMMARY", "=" * 40]
557
+ for i, t in enumerate(ui.tasks):
558
+ lines.append(str(i+1) + ". [" + t['status'] + "] " + t['text'])
559
+ for j, a in enumerate(t.get('actions', [])):
560
+ lines.append(" " + str(j+1) + ". " + a.get('action', '?') + " - " + a.get('reason', '')[:50])
561
+ context['output'] = "\n".join(lines)
562
+ else:
563
+ context['output'] = "Plonk session ended."
564
+
565
+ context['messages'] = messages
@@ -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
 
@@ -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':
@@ -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
 
@@ -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
 
@@ -1,5 +1,6 @@
1
1
  jinx_name: setup
2
2
  description: Interactive setup wizard for npcsh - detect local models, configure defaults
3
+ interactive: true
3
4
  inputs:
4
5
  - skip_detection: ""
5
6
  steps:
@@ -102,8 +103,8 @@ steps:
102
103
  out = []
103
104
  out.append("\033[2J\033[H")
104
105
  header = " NPCSH Setup Wizard "
105
- out.append(f"\033[1;1H\033[44;37;1m{'=' * width}\033[0m")
106
- 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")
107
108
 
108
109
  if state.phase == 'detect':
109
110
  out.append(f"\033[3;2H\033[1mDetected Providers:\033[0m")
@@ -136,7 +137,7 @@ steps:
136
137
  row = 5 + i
137
138
  idx = i + state.scroll_offset
138
139
  if idx == state.selected_idx:
139
- out.append(f"\033[{row};4H\033[47;30m> {model} ({provider})\033[0m")
140
+ out.append(f"\033[{row};4H\033[7m> {model} ({provider})\033[0m")
140
141
  else:
141
142
  out.append(f"\033[{row};4H {model} \033[90m({provider})\033[0m")
142
143
 
@@ -148,10 +149,10 @@ steps:
148
149
  if c == 'q':
149
150
  return False
150
151
  if c == '\x1b':
151
- if select.select([sys.stdin], [], [], 0.05)[0]:
152
- c2 = sys.stdin.read(1)
152
+ if select.select([fd], [], [], 0.05)[0]:
153
+ c2 = os.read(fd, 1).decode('latin-1')
153
154
  if c2 == '[':
154
- c3 = sys.stdin.read(1)
155
+ c3 = os.read(fd, 1).decode('latin-1')
155
156
  if c3 == 'A' and state.phase == 'select_chat':
156
157
  state.selected_idx = max(0, state.selected_idx - 1)
157
158
  if state.selected_idx < state.scroll_offset:
@@ -224,7 +225,7 @@ steps:
224
225
  run_detection()
225
226
  render_screen()
226
227
  while True:
227
- c = sys.stdin.read(1)
228
+ c = os.read(fd, 1).decode('latin-1')
228
229
  if not handle_input(c):
229
230
  break
230
231
  render_screen()