npcsh 1.1.12__py3-none-any.whl → 1.1.14__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 (99) hide show
  1. npcsh/_state.py +700 -377
  2. npcsh/alicanto.py +54 -1153
  3. npcsh/completion.py +206 -0
  4. npcsh/config.py +163 -0
  5. npcsh/corca.py +35 -1462
  6. npcsh/execution.py +185 -0
  7. npcsh/guac.py +31 -1986
  8. npcsh/npc_team/jinxs/code/sh.jinx +11 -15
  9. npcsh/npc_team/jinxs/modes/alicanto.jinx +186 -80
  10. npcsh/npc_team/jinxs/modes/corca.jinx +243 -22
  11. npcsh/npc_team/jinxs/modes/guac.jinx +313 -42
  12. npcsh/npc_team/jinxs/modes/plonk.jinx +209 -48
  13. npcsh/npc_team/jinxs/modes/pti.jinx +167 -25
  14. npcsh/npc_team/jinxs/modes/spool.jinx +158 -37
  15. npcsh/npc_team/jinxs/modes/wander.jinx +179 -74
  16. npcsh/npc_team/jinxs/modes/yap.jinx +258 -21
  17. npcsh/npc_team/jinxs/utils/chat.jinx +39 -12
  18. npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
  19. npcsh/npc_team/jinxs/utils/search.jinx +3 -3
  20. npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
  21. npcsh/npcsh.py +76 -20
  22. npcsh/parsing.py +118 -0
  23. npcsh/plonk.py +41 -329
  24. npcsh/pti.py +41 -201
  25. npcsh/spool.py +34 -239
  26. npcsh/ui.py +199 -0
  27. npcsh/wander.py +54 -542
  28. npcsh/yap.py +38 -570
  29. npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +194 -0
  30. npcsh-1.1.14.data/data/npcsh/npc_team/chat.jinx +44 -0
  31. npcsh-1.1.14.data/data/npcsh/npc_team/cmd.jinx +44 -0
  32. npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +249 -0
  33. npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +317 -0
  34. npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +214 -0
  35. npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +170 -0
  36. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/search.jinx +3 -3
  37. npcsh-1.1.14.data/data/npcsh/npc_team/sh.jinx +34 -0
  38. npcsh-1.1.14.data/data/npcsh/npc_team/spool.jinx +161 -0
  39. npcsh-1.1.14.data/data/npcsh/npc_team/usage.jinx +33 -0
  40. npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +186 -0
  41. npcsh-1.1.14.data/data/npcsh/npc_team/yap.jinx +262 -0
  42. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/METADATA +1 -1
  43. npcsh-1.1.14.dist-info/RECORD +135 -0
  44. npcsh-1.1.12.data/data/npcsh/npc_team/alicanto.jinx +0 -88
  45. npcsh-1.1.12.data/data/npcsh/npc_team/chat.jinx +0 -17
  46. npcsh-1.1.12.data/data/npcsh/npc_team/corca.jinx +0 -28
  47. npcsh-1.1.12.data/data/npcsh/npc_team/guac.jinx +0 -46
  48. npcsh-1.1.12.data/data/npcsh/npc_team/plonk.jinx +0 -53
  49. npcsh-1.1.12.data/data/npcsh/npc_team/pti.jinx +0 -28
  50. npcsh-1.1.12.data/data/npcsh/npc_team/sh.jinx +0 -38
  51. npcsh-1.1.12.data/data/npcsh/npc_team/spool.jinx +0 -40
  52. npcsh-1.1.12.data/data/npcsh/npc_team/wander.jinx +0 -81
  53. npcsh-1.1.12.data/data/npcsh/npc_team/yap.jinx +0 -25
  54. npcsh-1.1.12.dist-info/RECORD +0 -126
  55. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/agent.jinx +0 -0
  56. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  57. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.png +0 -0
  58. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/build.jinx +0 -0
  59. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compile.jinx +0 -0
  60. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compress.jinx +0 -0
  61. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.npc +0 -0
  62. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.png +0 -0
  63. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca_example.png +0 -0
  64. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  65. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/foreman.npc +0 -0
  66. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic.npc +0 -0
  67. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic4.png +0 -0
  68. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/guac.png +0 -0
  69. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/help.jinx +0 -0
  70. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/init.jinx +0 -0
  71. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
  72. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  73. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  74. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  75. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
  76. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  77. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  78. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/ots.jinx +0 -0
  79. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.npc +0 -0
  80. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.png +0 -0
  81. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  82. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  83. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/python.jinx +0 -0
  84. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/roll.jinx +0 -0
  85. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sample.jinx +0 -0
  86. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/serve.jinx +0 -0
  87. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/set.jinx +0 -0
  88. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  89. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.png +0 -0
  90. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  91. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/spool.png +0 -0
  92. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sql.jinx +0 -0
  93. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  94. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  95. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/yap.png +0 -0
  96. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/WHEEL +0 -0
  97. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/entry_points.txt +0 -0
  98. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/licenses/LICENSE +0 -0
  99. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/top_level.txt +0 -0
npcsh/parsing.py ADDED
@@ -0,0 +1,118 @@
1
+ """
2
+ Command parsing utilities for npcsh
3
+ """
4
+ import shlex
5
+ from typing import List
6
+
7
+
8
+ def split_by_pipes(command: str) -> List[str]:
9
+ """
10
+ Split a command by pipes, preserving quoted strings.
11
+
12
+ Examples:
13
+ 'foo | bar' -> ['foo', 'bar']
14
+ 'foo "hello|world" | bar' -> ['foo "hello|world"', 'bar']
15
+ """
16
+ result = []
17
+ current = []
18
+ in_single_quote = False
19
+ in_double_quote = False
20
+ i = 0
21
+
22
+ while i < len(command):
23
+ char = command[i]
24
+
25
+ if char == "'" and not in_double_quote:
26
+ in_single_quote = not in_single_quote
27
+ current.append(char)
28
+ elif char == '"' and not in_single_quote:
29
+ in_double_quote = not in_double_quote
30
+ current.append(char)
31
+ elif char == '|' and not in_single_quote and not in_double_quote:
32
+ result.append(''.join(current).strip())
33
+ current = []
34
+ else:
35
+ current.append(char)
36
+
37
+ i += 1
38
+
39
+ # Add final segment
40
+ if current:
41
+ result.append(''.join(current).strip())
42
+
43
+ return [s for s in result if s]
44
+
45
+
46
+ def parse_command_safely(cmd: str) -> List[str]:
47
+ """
48
+ Safely parse a command string into parts using shlex.
49
+
50
+ Returns an empty list on parse errors.
51
+ """
52
+ try:
53
+ return shlex.split(cmd)
54
+ except ValueError:
55
+ # Handle unmatched quotes, etc
56
+ return cmd.split()
57
+
58
+
59
+ def parse_generic_command_flags(parts: List[str]) -> tuple:
60
+ """
61
+ Parse command flags in a generic way.
62
+
63
+ Returns:
64
+ Tuple of (parsed_flags dict, remaining_parts list)
65
+ """
66
+ parsed_flags = {}
67
+ remaining = []
68
+
69
+ i = 0
70
+ while i < len(parts):
71
+ part = parts[i]
72
+
73
+ if part.startswith('--'):
74
+ key = part[2:]
75
+ if '=' in key:
76
+ key, value = key.split('=', 1)
77
+ parsed_flags[key] = _try_convert_type(value)
78
+ elif i + 1 < len(parts) and not parts[i + 1].startswith('-'):
79
+ parsed_flags[key] = _try_convert_type(parts[i + 1])
80
+ i += 1
81
+ else:
82
+ parsed_flags[key] = True
83
+ elif part.startswith('-') and len(part) == 2:
84
+ key = part[1]
85
+ if i + 1 < len(parts) and not parts[i + 1].startswith('-'):
86
+ parsed_flags[key] = _try_convert_type(parts[i + 1])
87
+ i += 1
88
+ else:
89
+ parsed_flags[key] = True
90
+ else:
91
+ remaining.append(part)
92
+
93
+ i += 1
94
+
95
+ return parsed_flags, remaining
96
+
97
+
98
+ def _try_convert_type(value: str):
99
+ """Try to convert string value to appropriate Python type"""
100
+ # Try int
101
+ try:
102
+ return int(value)
103
+ except ValueError:
104
+ pass
105
+
106
+ # Try float
107
+ try:
108
+ return float(value)
109
+ except ValueError:
110
+ pass
111
+
112
+ # Try bool
113
+ if value.lower() in ('true', 'yes', '1'):
114
+ return True
115
+ if value.lower() in ('false', 'no', '0'):
116
+ return False
117
+
118
+ return value
npcsh/plonk.py CHANGED
@@ -1,342 +1,54 @@
1
- from npcpy.data.image import capture_screenshot
2
- import time
3
- import os
4
- import platform
5
- from npcpy.llm_funcs import get_llm_response
6
- from npcpy.work.desktop import perform_action
7
- import matplotlib.pyplot as plt
8
- import matplotlib.patches as patches
9
- from PIL import Image
10
- import numpy as np
11
- import imagehash
12
- from npcsh._state import NPCSH_VISION_MODEL, NPCSH_VISION_PROVIDER
13
- import argparse
14
- from npcpy.npc_compiler import NPC
15
-
16
- def get_system_examples():
17
- system = platform.system()
18
- if system == "Windows":
19
- return "Examples: start firefox, notepad, calc, explorer"
20
- elif system == "Darwin":
21
- return "Examples: open -a Firefox, open -a TextEdit, open -a Calculator"
22
- else:
23
- return "Examples: firefox &, gedit &, gnome-calculator &"
24
-
25
- def format_plonk_summary(synthesized_summary: list) -> str:
26
- """Formats the summary of a plonk session into a readable markdown report."""
27
- if not synthesized_summary:
28
- return "Plonk session ended with no actions performed."
29
-
30
- output = "## Plonk Session Summary\n\n"
31
- for info in synthesized_summary:
32
- iteration = info.get('iteration', 'N/A')
33
- feedback = info.get('last_action_feedback', 'None')
34
- coords = info.get('last_click_coords', 'None')
35
- output += f"### Iteration {iteration}\n"
36
- output += f"- **Feedback:** {feedback}\n"
37
- output += f"- **Last Click:** {coords}\n\n"
38
- return output
39
-
40
- def get_image_hash(image_path):
41
- """Generate a perceptual hash of the image to detect screen changes intelligently."""
42
- try:
43
-
44
- return imagehash.phash(Image.open(image_path))
45
- except Exception as e:
46
- print(f"Could not generate image hash: {e}")
47
- return None
48
-
49
- def add_click_vector_trail(image_path, click_history, output_path):
50
- """Add click markers showing the progression/trail of clicks with arrows and numbers."""
51
- try:
52
- img = Image.open(image_path)
53
- img_array = np.array(img)
54
- height, width = img_array.shape[:2]
55
-
56
- fig, ax = plt.subplots(1, 1, figsize=(width/100, height/100), dpi=100)
57
- ax.imshow(img_array)
58
-
59
- font_size = max(12, min(width, height) // 80)
60
- colors = plt.cm.viridis(np.linspace(0.3, 1.0, len(click_history)))
61
-
62
-
63
- if len(click_history) > 1:
64
- for i in range(len(click_history) - 1):
65
- x1, y1 = (click_history[i]['x'] * width / 100, click_history[i]['y'] * height / 100)
66
- x2, y2 = (click_history[i+1]['x'] * width / 100, click_history[i+1]['y'] * height / 100)
67
- ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
68
- arrowprops=dict(arrowstyle='->,head_width=0.6,head_length=0.8',
69
- lw=3, color='cyan', alpha=0.9, shrinkA=25, shrinkB=25))
70
-
71
-
72
- for i, click in enumerate(click_history):
73
- x_pixel = int(click['x'] * width / 100)
74
- y_pixel = int(click['y'] * height / 100)
75
-
76
- radius = 25
77
- circle = patches.Circle((x_pixel, y_pixel), radius=radius,
78
- linewidth=3, edgecolor='white',
79
- facecolor=colors[i], alpha=0.9)
80
- ax.add_patch(circle)
81
-
82
-
83
- ax.text(x_pixel, y_pixel, str(i+1),
84
- fontsize=font_size + 4,
85
- color='white', weight='bold', ha='center', va='center')
86
-
87
-
88
- coord_text = f"({click['x']}, {click['y']})"
89
- ax.text(x_pixel + radius + 5,
90
- y_pixel,
91
- coord_text,
92
- fontsize=font_size,
93
- color='white',
94
- weight='bold',
95
- ha='left', va='center',
96
- bbox=dict(boxstyle="round,pad=0.2", facecolor=colors[i],
97
- alpha=0.9, edgecolor='white'))
98
-
99
- ax.set_xlim(0, width)
100
- ax.set_ylim(height, 0)
101
- ax.axis('off')
102
- plt.tight_layout(pad=0)
103
- plt.savefig(output_path, bbox_inches='tight', pad_inches=0, dpi=100)
104
- plt.close()
105
-
106
- return True
107
- except Exception as e:
108
- print(f"Failed to add click trail with matplotlib: {e}")
109
- return False
110
-
111
- def execute_plonk_command(request, model, provider, npc=None, plonk_context=None, max_iterations=10, debug=False):
112
- system_examples = get_system_examples()
113
- messages = []
114
- last_action_feedback = "None"
115
- last_click_coords = None
116
- synthesized_summary = []
117
-
118
- current_screen_hash = None
119
- click_history = []
120
- HASH_DISTANCE_THRESHOLD = 3
121
-
122
- for iteration_count in range(max_iterations):
123
- try:
124
- screenshot_info = capture_screenshot(full=True)
125
- screenshot_path = screenshot_info.get('file_path') if screenshot_info else None
126
-
127
- if not screenshot_path:
128
- last_action_feedback = "Error: Failed to capture screenshot."
129
- time.sleep(1)
130
- continue
131
-
132
- new_screen_hash = get_image_hash(screenshot_path)
133
-
134
- if current_screen_hash is None or (new_screen_hash - current_screen_hash > HASH_DISTANCE_THRESHOLD):
135
- if debug and current_screen_hash is not None:
136
- print(f"Screen changed (hash distance: {new_screen_hash - current_screen_hash}) - resetting click history.")
137
- click_history = []
138
- current_screen_hash = new_screen_hash
139
-
140
- summary_info = {
141
- 'iteration': iteration_count + 1,
142
- 'last_action_feedback': last_action_feedback,
143
- 'last_click_coords': click_history[-1] if click_history else None
144
- }
145
- synthesized_summary.append(summary_info)
146
-
147
- if debug:
148
- print(f"Iteration {iteration_count + 1}/{max_iterations}")
149
-
150
- context_injection = ""
151
- if plonk_context:
152
- context_injection = f"""
153
- ---
154
- IMPORTANT TEAM CONTEXT FOR THIS TASK:
155
- {plonk_context}
156
- ---
157
- """
158
-
159
- completion_example_text = """
160
- {
161
- "actions": [],
162
- "status": "Task appears complete. Waiting for user approval to proceed or finish."
163
- }
164
- """
165
-
166
- quit_rule_text = 'NEVER include {"type": "quit"} in your actions - the user controls when to stop.'
167
-
168
- prompt_examples = """
169
- ---
170
- EXAMPLE 1: Task "Create and save a file named 'memo.txt' with the text 'Meeting at 3pm'"
171
- {
172
- "actions": [
173
- { "type": "bash", "command": "gedit &" },
1
+ """
2
+ plonk - Vision-based GUI automation CLI entry point
174
3
 
175
- {"type":"click", "x": 10, "y": 30}
176
- ]
177
- }
178
- ---
179
- EXAMPLE 2: Task "Search for news about space exploration"
180
- {
181
- "actions": [
182
- { "type": "bash", "command": "firefox &" },
183
-
184
- ]
185
- }
186
- ---
187
- EXAMPLE 3: Task "Click the red button on the form"
188
- {
189
- "actions": [
190
- { "type": "click", "x": 75, "y": 45 }
191
- ]
192
- }
193
- ---
194
- EXAMPLE 4: Task "Open Gmail and draft a reply to most recent email"
195
- {
196
- "actions": [
197
- { "type": "bash", "command": "open -a Safari" },
198
-
199
- ]
200
- }
201
- """
202
-
203
- prompt_template = f"""
204
- Goal: {request}
205
- Feedback from last action: {last_action_feedback}
206
-
207
- {context_injection}
208
-
209
- Your task is to control the computer to achieve the goal.
210
-
211
- IMPORTANT: You should take actions step-by-step and verify each step works before proceeding.
212
- DO NOT plan all actions at once - take a few actions, then look at the screen again.
213
-
214
- CRITICAL: NEVER use the 'quit' action automatically. Even if the task appears complete,
215
- continue working or wait for user guidance. The user will decide when to quit.
216
-
217
- THOUGHT PROCESS:
218
- 1. Analyze the screen. Is the application I need (e.g., a web browser) already open?
219
- 2. If YES, `click` it. If NO, use `bash` to launch it. Use the examples: {system_examples}.
220
- 3. Take 2-3 actions maximum, then let me see the screen again to verify progress.
221
- 4. If task appears complete, explain status but DO NOT quit - wait for user direction.
222
-
223
- Your response MUST be a JSON object with an "actions" key.
224
- All clicking actions should use percentage coordinates relative
225
- to the screen size.
226
- The x and y are (0,0) at the TOP LEFT CORNER OF THE SCREEN.
227
-
228
- MAXIMUM 3 ACTIONS PER RESPONSE - then let me see the screen to verify progress.
229
- Never do more than one click, type, or hotkey event per response. It is important to take a sequence of
230
- slow actions separated to avoid making mistakes and falling in loops.
231
-
232
- If the task appears complete, you can include an empty actions list and explain:
233
- {completion_example_text}
234
-
235
- {quit_rule_text}
236
- """ + prompt_examples
4
+ This is a thin wrapper that executes the plonk.jinx through the jinx mechanism.
5
+ """
6
+ import argparse
7
+ import os
8
+ import sys
237
9
 
238
- image_to_send_path = screenshot_path
239
-
240
- if click_history:
241
- marked_image_path = "/tmp/marked_screenshot.png"
242
- if add_click_vector_trail(screenshot_path, click_history, marked_image_path):
243
- image_to_send_path = marked_image_path
244
- if debug:
245
- print(f"Drew click trail with {len(click_history)} points.")
10
+ from npcsh._state import setup_shell
246
11
 
247
- response = get_llm_response(prompt_template, model=model, provider=provider, npc=npc,
248
- images=[image_to_send_path], messages=messages, format="json")
249
- messages = response.get("messages", messages)
250
- response_data = response.get('response')
251
-
252
- if debug:
253
- print(response_data)
254
12
 
255
- if not isinstance(response_data, dict) or "actions" not in response_data:
256
- last_action_feedback = f"Invalid JSON response from model: {response_data}"
257
- continue
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="plonk - Vision-based GUI automation")
15
+ parser.add_argument("task", nargs="*", help="Task description for GUI automation")
16
+ parser.add_argument("--vmodel", type=str, help="Vision model to use (default: gpt-4o)")
17
+ parser.add_argument("--vprovider", type=str, help="Vision provider (default: openai)")
18
+ parser.add_argument("--max-iterations", type=int, default=10, help="Maximum iterations")
19
+ parser.add_argument("--no-debug", action="store_true", help="Disable debug output")
20
+ args = parser.parse_args()
258
21
 
259
- actions_list = response_data.get("actions", [])
260
- if not isinstance(actions_list, list):
261
- last_action_feedback = "Model did not return a list in the 'actions' key."
262
- continue
263
-
264
- for action in actions_list:
265
- if debug:
266
- print(f"Executing action: {action}")
267
-
268
- if action.get("type") == "quit":
269
- print("⚠️ Model attempted to quit automatically. Ignoring.")
270
- continue
271
-
272
- result = perform_action(action)
273
- last_action_feedback = result.get("message") or result.get("output")
22
+ if not args.task:
23
+ parser.print_help()
24
+ sys.exit(1)
274
25
 
275
- if action.get("type") == "click":
276
- click_info = {"x": action.get("x"), "y": action.get("y")}
277
- click_history.append(click_info)
278
- if len(click_history) > 6:
279
- click_history.pop(0)
280
-
281
- if result.get("status") == "error":
282
- last_action_feedback = f"Action failed: {last_action_feedback}"
283
- print(f"Action failed, providing feedback to model: {last_action_feedback}")
284
- break
285
- time.sleep(1)
26
+ # Setup shell to get team and default NPC
27
+ command_history, team, default_npc = setup_shell()
286
28
 
287
- if response_data.get("status") and "complete" in response_data.get("status", "").lower():
288
- print(f"🎯 Model reports: {response_data.get('status')}")
289
- print(" Press Ctrl+C to provide guidance or approval, or let it continue...")
29
+ if not team or "plonk" not in team.jinxs_dict:
30
+ print("Error: plonk jinx not found. Ensure npc_team/jinxs/modes/plonk.jinx exists.")
31
+ sys.exit(1)
290
32
 
291
- if not actions_list:
292
- last_action_feedback = "No actions were returned by the model. Re-evaluating."
293
- if debug:
294
- print(last_action_feedback)
295
-
296
- except KeyboardInterrupt:
297
- print("\n⚠️ Plonk paused. Provide additional guidance or press Enter to continue.")
298
- try:
299
- user_guidance = input("Guidance > ").strip()
300
- if user_guidance:
301
- request += f"\n\n---\nUser Guidance: {user_guidance}\n---"
302
- last_action_feedback = "User provided new guidance to correct the course."
303
- print("✅ Guidance received. Resuming with updated instructions...")
304
- else:
305
- last_action_feedback = "User paused and resumed without new guidance."
306
- print("✅ No guidance provided. Resuming...")
307
- continue
308
- except EOFError:
309
- print("\nExiting plonk mode.")
310
- break
33
+ # Build context for jinx execution
34
+ context = {
35
+ "npc": default_npc,
36
+ "team": team,
37
+ "messages": [],
38
+ "task": " ".join(args.task),
39
+ "vmodel": args.vmodel,
40
+ "vprovider": args.vprovider,
41
+ "max_iterations": args.max_iterations,
42
+ "debug": not args.no_debug,
43
+ }
311
44
 
312
- return synthesized_summary
45
+ # Execute the jinx
46
+ plonk_jinx = team.jinxs_dict["plonk"]
47
+ result = plonk_jinx.execute(context=context, npc=default_npc)
313
48
 
314
- def main():
315
- parser = argparse.ArgumentParser(description="Execute GUI automation tasks using vision models")
316
- parser.add_argument("request", help="The task to perform")
317
- parser.add_argument("--model", help="Model to use")
318
- parser.add_argument("--provider", help="Provider to use")
319
- parser.add_argument("--max-iterations", type=int, default=10, help="Maximum iterations")
320
- parser.add_argument("--debug", action="store_true", help="Enable debug output")
321
- parser.add_argument("--npc", type=str, default=os.path.expanduser('~/.npcsh/npc_team/plonk.npc'), help="Path to NPC file")
322
-
323
- args = parser.parse_args()
324
-
325
- npc = NPC(file=args.npc) if os.path.exists(os.path.expanduser(args.npc)) else None
326
-
327
- model = args.model or (npc.model if npc else NPCSH_VISION_MODEL)
328
- provider = args.provider or (npc.provider if npc else NPCSH_VISION_PROVIDER)
49
+ if isinstance(result, dict) and result.get("output"):
50
+ print(result["output"])
329
51
 
330
- summary = execute_plonk_command(
331
- request=args.request,
332
- model=model,
333
- provider=provider,
334
- npc=npc,
335
- max_iterations=args.max_iterations,
336
- debug=args.debug
337
- )
338
-
339
- print(format_plonk_summary(summary))
340
52
 
341
53
  if __name__ == "__main__":
342
- main()
54
+ main()