open-swarm 0.1.1745125933__py3-none-any.whl → 0.1.1745126277__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.
- {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/METADATA +12 -8
- {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/RECORD +52 -25
- swarm/blueprints/README.md +19 -18
- swarm/blueprints/blueprint_audit_status.json +1 -1
- swarm/blueprints/chatbot/blueprint_chatbot.py +160 -72
- swarm/blueprints/codey/README.md +88 -8
- swarm/blueprints/codey/blueprint_codey.py +1116 -210
- swarm/blueprints/codey/codey_cli.py +10 -0
- swarm/blueprints/codey/session_logs/session_2025-04-19T01-15-31.md +17 -0
- swarm/blueprints/codey/session_logs/session_2025-04-19T01-16-03.md +17 -0
- swarm/blueprints/common/operation_box_utils.py +83 -0
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +21 -298
- swarm/blueprints/divine_code/blueprint_divine_code.py +182 -9
- swarm/blueprints/django_chat/blueprint_django_chat.py +150 -24
- swarm/blueprints/echocraft/blueprint_echocraft.py +142 -13
- swarm/blueprints/geese/README.md +97 -0
- swarm/blueprints/geese/blueprint_geese.py +677 -93
- swarm/blueprints/geese/geese_cli.py +102 -0
- swarm/blueprints/jeeves/blueprint_jeeves.py +712 -0
- swarm/blueprints/jeeves/jeeves_cli.py +55 -0
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +109 -22
- swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +172 -40
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +79 -41
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +82 -35
- swarm/blueprints/omniplex/blueprint_omniplex.py +56 -24
- swarm/blueprints/poets/blueprint_poets.py +141 -100
- swarm/blueprints/poets/poets_cli.py +23 -0
- swarm/blueprints/rue_code/README.md +8 -0
- swarm/blueprints/rue_code/blueprint_rue_code.py +188 -20
- swarm/blueprints/rue_code/rue_code_cli.py +43 -0
- swarm/blueprints/stewie/apps.py +12 -0
- swarm/blueprints/stewie/blueprint_family_ties.py +349 -0
- swarm/blueprints/stewie/models.py +19 -0
- swarm/blueprints/stewie/serializers.py +10 -0
- swarm/blueprints/stewie/settings.py +17 -0
- swarm/blueprints/stewie/urls.py +11 -0
- swarm/blueprints/stewie/views.py +26 -0
- swarm/blueprints/suggestion/blueprint_suggestion.py +54 -39
- swarm/blueprints/whinge_surf/README.md +22 -0
- swarm/blueprints/whinge_surf/__init__.py +1 -0
- swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +565 -0
- swarm/blueprints/whinge_surf/whinge_surf_cli.py +99 -0
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +66 -37
- swarm/blueprints/zeus/__init__.py +2 -0
- swarm/blueprints/zeus/apps.py +4 -0
- swarm/blueprints/zeus/blueprint_zeus.py +270 -0
- swarm/blueprints/zeus/zeus_cli.py +13 -0
- swarm/cli/async_input.py +65 -0
- swarm/cli/async_input_demo.py +32 -0
- {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/WHEEL +0 -0
- {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/entry_points.txt +0 -0
- {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,565 @@
|
|
1
|
+
import subprocess
|
2
|
+
import threading
|
3
|
+
import os
|
4
|
+
import signal
|
5
|
+
from typing import Optional, Dict
|
6
|
+
from swarm.core.blueprint_ux import BlueprintUXImproved
|
7
|
+
from swarm.core.blueprint_base import BlueprintBase
|
8
|
+
import json
|
9
|
+
import time
|
10
|
+
import psutil # For resource usage
|
11
|
+
from swarm.blueprints.common.operation_box_utils import display_operation_box
|
12
|
+
|
13
|
+
class WhingeSpinner:
|
14
|
+
FRAMES = ["Generating.", "Generating..", "Generating...", "Running..."]
|
15
|
+
LONG_WAIT_MSG = "Generating... Taking longer than expected"
|
16
|
+
INTERVAL = 0.12
|
17
|
+
SLOW_THRESHOLD = 10
|
18
|
+
|
19
|
+
def __init__(self):
|
20
|
+
self._idx = 0
|
21
|
+
self._start_time = None
|
22
|
+
self._last_frame = self.FRAMES[0]
|
23
|
+
|
24
|
+
def start(self):
|
25
|
+
self._start_time = time.time()
|
26
|
+
self._idx = 0
|
27
|
+
self._last_frame = self.FRAMES[0]
|
28
|
+
|
29
|
+
def _spin(self):
|
30
|
+
self._idx = (self._idx + 1) % len(self.FRAMES)
|
31
|
+
self._last_frame = self.FRAMES[self._idx]
|
32
|
+
|
33
|
+
def current_spinner_state(self):
|
34
|
+
if self._start_time and (time.time() - self._start_time) > self.SLOW_THRESHOLD:
|
35
|
+
return self.LONG_WAIT_MSG
|
36
|
+
return self._last_frame
|
37
|
+
|
38
|
+
class WhingeSurfBlueprint(BlueprintBase):
|
39
|
+
"""
|
40
|
+
Blueprint to run subprocesses in the background and check on their status/output.
|
41
|
+
Now supports self-update via prompt (LLM/agent required for code generation).
|
42
|
+
"""
|
43
|
+
NAME = "whinge_surf"
|
44
|
+
CLI_NAME = "whinge_surf"
|
45
|
+
DESCRIPTION = "Background subprocess manager: run, check, view output, cancel, and self-update."
|
46
|
+
VERSION = "0.3.0"
|
47
|
+
JOBS_FILE = os.path.expanduser("~/.whinge_surf_jobs.json")
|
48
|
+
|
49
|
+
def __init__(self, blueprint_id: str = "whinge_surf", config=None, config_path=None, **kwargs):
|
50
|
+
super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
|
51
|
+
self.blueprint_id = blueprint_id
|
52
|
+
self.config_path = config_path
|
53
|
+
self._config = config if config is not None else None
|
54
|
+
self._llm_profile_name = None
|
55
|
+
self._llm_profile_data = None
|
56
|
+
self._markdown_output = None
|
57
|
+
self.spinner = WhingeSpinner()
|
58
|
+
self._procs: Dict[int, Dict] = {} # pid -> {proc, output, thread, status}
|
59
|
+
self.ux = BlueprintUXImproved(style="serious")
|
60
|
+
self._load_jobs()
|
61
|
+
|
62
|
+
def _load_jobs(self):
|
63
|
+
if os.path.exists(self.JOBS_FILE):
|
64
|
+
try:
|
65
|
+
with open(self.JOBS_FILE, "r") as f:
|
66
|
+
self._jobs = json.load(f)
|
67
|
+
except Exception:
|
68
|
+
self._jobs = {}
|
69
|
+
else:
|
70
|
+
self._jobs = {}
|
71
|
+
|
72
|
+
def _save_jobs(self):
|
73
|
+
with open(self.JOBS_FILE, "w") as f:
|
74
|
+
json.dump(self._jobs, f, indent=2)
|
75
|
+
|
76
|
+
def _display_job_status(self, job_id, status, output=None, progress=None, total=None):
|
77
|
+
self.spinner._spin()
|
78
|
+
display_operation_box(
|
79
|
+
title=f"WhingeSurf Job {job_id}",
|
80
|
+
content=f"Status: {status}\nOutput: {output if output else ''}",
|
81
|
+
spinner_state=self.spinner.current_spinner_state(),
|
82
|
+
progress_line=progress,
|
83
|
+
total_lines=total,
|
84
|
+
emoji="🌊"
|
85
|
+
)
|
86
|
+
|
87
|
+
def run_subprocess_in_background(self, cmd) -> int:
|
88
|
+
"""Start a subprocess in the background. Returns the PID."""
|
89
|
+
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
|
90
|
+
output = []
|
91
|
+
status = {'finished': False, 'exit_code': None}
|
92
|
+
start_time = time.time()
|
93
|
+
# --- PATCH: Ensure instant jobs finalize output and status ---
|
94
|
+
def reader():
|
95
|
+
try:
|
96
|
+
for line in proc.stdout:
|
97
|
+
output.append(line)
|
98
|
+
proc.stdout.close()
|
99
|
+
proc.wait()
|
100
|
+
finally:
|
101
|
+
status['finished'] = True
|
102
|
+
status['exit_code'] = proc.returncode
|
103
|
+
self._jobs[str(proc.pid)]["end_time"] = time.time()
|
104
|
+
self._jobs[str(proc.pid)]["exit_code"] = proc.returncode
|
105
|
+
self._jobs[str(proc.pid)]["status"] = "finished"
|
106
|
+
self._jobs[str(proc.pid)]["output"] = ''.join(output)
|
107
|
+
self._save_jobs()
|
108
|
+
t = threading.Thread(target=reader, daemon=True)
|
109
|
+
t.start()
|
110
|
+
self._procs[proc.pid] = {'proc': proc, 'output': output, 'thread': t, 'status': status}
|
111
|
+
# Add to job table
|
112
|
+
self._jobs[str(proc.pid)] = {
|
113
|
+
"pid": proc.pid,
|
114
|
+
"cmd": cmd,
|
115
|
+
"start_time": start_time,
|
116
|
+
"status": "running",
|
117
|
+
"output": None,
|
118
|
+
"exit_code": None,
|
119
|
+
"end_time": None
|
120
|
+
}
|
121
|
+
self._save_jobs()
|
122
|
+
# --- If process already finished, finalize immediately ---
|
123
|
+
if proc.poll() is not None:
|
124
|
+
status['finished'] = True
|
125
|
+
status['exit_code'] = proc.returncode
|
126
|
+
self._jobs[str(proc.pid)]["end_time"] = time.time()
|
127
|
+
self._jobs[str(proc.pid)]["exit_code"] = proc.returncode
|
128
|
+
self._jobs[str(proc.pid)]["status"] = "finished"
|
129
|
+
try:
|
130
|
+
proc.stdout.close()
|
131
|
+
except Exception:
|
132
|
+
pass
|
133
|
+
self._jobs[str(proc.pid)]["output"] = ''.join(output)
|
134
|
+
self._save_jobs()
|
135
|
+
self._display_job_status(proc.pid, "Started")
|
136
|
+
return proc.pid
|
137
|
+
|
138
|
+
def list_jobs(self):
|
139
|
+
jobs = list(self._jobs.values())
|
140
|
+
jobs.sort(key=lambda j: j["start_time"] or 0)
|
141
|
+
lines = []
|
142
|
+
for job in jobs:
|
143
|
+
dur = (job["end_time"] or time.time()) - job["start_time"] if job["start_time"] else 0
|
144
|
+
lines.append(f"PID: {job['pid']} | Status: {job['status']} | Exit: {job['exit_code']} | Duration: {dur:.1f}s | Cmd: {' '.join(job['cmd'])}")
|
145
|
+
return self.ux.ansi_emoji_box(
|
146
|
+
"Job List",
|
147
|
+
'\n'.join(lines) or 'No jobs found.',
|
148
|
+
summary="All subprocess jobs.",
|
149
|
+
op_type="list_jobs",
|
150
|
+
params={},
|
151
|
+
result_count=len(jobs)
|
152
|
+
)
|
153
|
+
|
154
|
+
def show_output(self, pid: int) -> str:
|
155
|
+
job = self._jobs.get(str(pid))
|
156
|
+
if not job:
|
157
|
+
return self.ux.ansi_emoji_box("Show Output", f"No such job: {pid}", op_type="show_output", params={"pid": pid}, result_count=0)
|
158
|
+
out = job.get("output")
|
159
|
+
if out is None:
|
160
|
+
return self.ux.ansi_emoji_box("Show Output", f"Job {pid} still running.", op_type="show_output", params={"pid": pid}, result_count=0)
|
161
|
+
return self.ux.ansi_emoji_box("Show Output", out[-1000:], summary="Last 1000 chars of output.", op_type="show_output", params={"pid": pid}, result_count=len(out))
|
162
|
+
|
163
|
+
def tail_output(self, pid: int) -> str:
|
164
|
+
import time
|
165
|
+
import itertools
|
166
|
+
job = self._jobs.get(str(pid))
|
167
|
+
if not job:
|
168
|
+
return self.ux.ansi_emoji_box("Tail Output", f"No such job: {pid}", op_type="tail_output", params={"pid": pid}, result_count=0)
|
169
|
+
spinner_cycle = itertools.cycle([
|
170
|
+
"Generating.", "Generating..", "Generating...", "Running..."
|
171
|
+
])
|
172
|
+
start = time.time()
|
173
|
+
last_len = 0
|
174
|
+
spinner_message = next(spinner_cycle)
|
175
|
+
while True:
|
176
|
+
job = self._jobs.get(str(pid))
|
177
|
+
out = job.get("output")
|
178
|
+
lines = out.splitlines()[-10:] if out else []
|
179
|
+
elapsed = int(time.time() - start)
|
180
|
+
# Spinner escalation if taking long
|
181
|
+
if elapsed > 10:
|
182
|
+
spinner_message = "Generating... Taking longer than expected"
|
183
|
+
else:
|
184
|
+
spinner_message = next(spinner_cycle)
|
185
|
+
print(self.ux.ansi_emoji_box(
|
186
|
+
f"Tail Output | {spinner_message}",
|
187
|
+
'\n'.join(f"{i+1}: {line}" for i, line in enumerate(lines)),
|
188
|
+
op_type="tail_output",
|
189
|
+
params={"pid": pid, "elapsed": elapsed},
|
190
|
+
result_count=len(lines)
|
191
|
+
))
|
192
|
+
if job["status"] == "finished":
|
193
|
+
break
|
194
|
+
time.sleep(1)
|
195
|
+
return "[Tail finished]"
|
196
|
+
|
197
|
+
def check_subprocess_status(self, pid: int) -> Optional[Dict]:
|
198
|
+
entry = self._procs.get(pid)
|
199
|
+
if not entry:
|
200
|
+
# Check persistent job table
|
201
|
+
job = self._jobs.get(str(pid))
|
202
|
+
if job:
|
203
|
+
return {"finished": job["status"] == "finished", "exit_code": job["exit_code"]}
|
204
|
+
return None
|
205
|
+
return entry['status']
|
206
|
+
|
207
|
+
def get_subprocess_output(self, pid: int) -> Optional[str]:
|
208
|
+
entry = self._procs.get(pid)
|
209
|
+
if not entry:
|
210
|
+
# Check persistent job table
|
211
|
+
job = self._jobs.get(str(pid))
|
212
|
+
if job:
|
213
|
+
return job.get("output")
|
214
|
+
return None
|
215
|
+
return ''.join(entry['output'])
|
216
|
+
|
217
|
+
def kill_subprocess(self, pid: int) -> str:
|
218
|
+
entry = self._procs.get(pid)
|
219
|
+
if not entry:
|
220
|
+
# Try to kill by pid if not tracked
|
221
|
+
try:
|
222
|
+
os.kill(pid, signal.SIGTERM)
|
223
|
+
return f"Sent SIGTERM to {pid}."
|
224
|
+
except Exception as e:
|
225
|
+
return f"No such subprocess: {pid} ({e})"
|
226
|
+
proc = entry['proc']
|
227
|
+
if entry['status']['finished']:
|
228
|
+
return f"Process {pid} already finished."
|
229
|
+
try:
|
230
|
+
proc.terminate()
|
231
|
+
proc.wait(timeout=5)
|
232
|
+
entry['status']['finished'] = True
|
233
|
+
entry['status']['exit_code'] = proc.returncode
|
234
|
+
self._jobs[str(pid)]["status"] = "finished"
|
235
|
+
self._jobs[str(pid)]["exit_code"] = proc.returncode
|
236
|
+
self._jobs[str(pid)]["end_time"] = time.time()
|
237
|
+
self._save_jobs()
|
238
|
+
return f"Process {pid} killed."
|
239
|
+
except Exception as e:
|
240
|
+
return f"Error killing process {pid}: {e}"
|
241
|
+
|
242
|
+
def resource_usage(self, pid: int) -> str:
|
243
|
+
try:
|
244
|
+
p = psutil.Process(pid)
|
245
|
+
cpu = p.cpu_percent(interval=0.1)
|
246
|
+
mem = p.memory_info().rss // 1024
|
247
|
+
return self.ux.ansi_emoji_box("Resource Usage", f"CPU: {cpu}% | Mem: {mem} KB", op_type="resource_usage", params={"pid": pid}, result_count=1)
|
248
|
+
except Exception as e:
|
249
|
+
return self.ux.ansi_emoji_box("Resource Usage", f"Error: {e}", op_type="resource_usage", params={"pid": pid}, result_count=0)
|
250
|
+
|
251
|
+
def self_update_from_prompt(self, prompt: str, test: bool = True) -> str:
|
252
|
+
"""
|
253
|
+
Update the blueprint's own code based on a user prompt. This version will append a comment with the prompt to prove self-modification.
|
254
|
+
"""
|
255
|
+
import shutil, os, time
|
256
|
+
src_file = os.path.abspath(__file__)
|
257
|
+
backup_file = src_file + ".bak"
|
258
|
+
# Step 1: Backup current file
|
259
|
+
shutil.copy2(src_file, backup_file)
|
260
|
+
# Step 2: Read current code
|
261
|
+
with open(src_file, "r") as f:
|
262
|
+
code = f.read()
|
263
|
+
# Step 3: Apply improvement (append a comment with the prompt)
|
264
|
+
new_code = code + f"\n# SELF-IMPROVEMENT: {prompt} ({time.strftime('%Y-%m-%d %H:%M:%S')})\n"
|
265
|
+
with open(src_file, "w") as f:
|
266
|
+
f.write(new_code)
|
267
|
+
# Step 4: Optionally test (skip for proof)
|
268
|
+
return self.ux.ansi_emoji_box(
|
269
|
+
"Self-Update",
|
270
|
+
f"Appended self-improvement comment: {prompt}",
|
271
|
+
summary="Self-update completed.",
|
272
|
+
op_type="self_update",
|
273
|
+
params={"prompt": prompt},
|
274
|
+
result_count=1
|
275
|
+
)
|
276
|
+
|
277
|
+
def analyze_self(self, output_format: str = "ansi") -> str:
|
278
|
+
"""
|
279
|
+
Ultra-enhanced: Analyze the whinge_surf blueprint's own code and return a concise, actionable summary.
|
280
|
+
- Classes/functions/lines, coverage, imports
|
281
|
+
- TODOs/FIXMEs with line numbers
|
282
|
+
- Longest/most complex function with code snippet
|
283
|
+
- Suggestions if code smells detected
|
284
|
+
- Output as ANSI box (default), plain text, or JSON
|
285
|
+
"""
|
286
|
+
import inspect, ast, re, json
|
287
|
+
src_file = inspect.getfile(self.__class__)
|
288
|
+
with open(src_file, 'r') as f:
|
289
|
+
code = f.read()
|
290
|
+
tree = ast.parse(code, filename=src_file)
|
291
|
+
lines = code.splitlines()
|
292
|
+
num_lines = len(lines)
|
293
|
+
# Classes & functions
|
294
|
+
classes = [n for n in ast.walk(tree) if isinstance(n, ast.ClassDef)]
|
295
|
+
class_names = [c.name for c in classes]
|
296
|
+
functions = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
297
|
+
func_names = [f.name for f in functions]
|
298
|
+
# TODOs/FIXMEs with line numbers
|
299
|
+
todos = [(i+1, l.strip()) for i,l in enumerate(lines) if 'TODO' in l or 'FIXME' in l]
|
300
|
+
# Docstring/type hint coverage
|
301
|
+
docstring_count = sum(1 for f in functions if ast.get_docstring(f))
|
302
|
+
typehint_count = sum(1 for f in functions if f.returns or any(a.annotation for a in f.args.args))
|
303
|
+
doc_cov = f"{docstring_count}/{len(functions)} ({int(100*docstring_count/max(1,len(functions)))}%)"
|
304
|
+
hint_cov = f"{typehint_count}/{len(functions)} ({int(100*typehint_count/max(1,len(functions)))}%)"
|
305
|
+
# Function length stats
|
306
|
+
func_lens = []
|
307
|
+
for f in functions:
|
308
|
+
start = f.lineno-1
|
309
|
+
end = max([getattr(f, 'end_lineno', start+1), start+1])
|
310
|
+
func_lens.append(end-start)
|
311
|
+
avg_len = int(sum(func_lens)/max(1,len(func_lens))) if func_lens else 0
|
312
|
+
max_len = max(func_lens) if func_lens else 0
|
313
|
+
longest_func = func_names[func_lens.index(max_len)] if func_lens else 'N/A'
|
314
|
+
# Code snippet for longest function
|
315
|
+
if func_lens:
|
316
|
+
f = functions[func_lens.index(max_len)]
|
317
|
+
snippet = '\n'.join(lines[f.lineno-1:getattr(f, 'end_lineno', f.lineno)])
|
318
|
+
else:
|
319
|
+
snippet = ''
|
320
|
+
# Imports
|
321
|
+
stdlib = set()
|
322
|
+
third_party = set()
|
323
|
+
import_lines = [line for line in lines if line.strip().startswith('import') or line.strip().startswith('from')]
|
324
|
+
for line in import_lines:
|
325
|
+
match = re.match(r'(?:from|import)\s+([\w_\.]+)', line)
|
326
|
+
if match:
|
327
|
+
mod = match.group(1).split('.')[0]
|
328
|
+
if mod in ('os','sys','threading','subprocess','signal','inspect','ast','re','shutil','time','typing','logging'): stdlib.add(mod)
|
329
|
+
else: third_party.add(mod)
|
330
|
+
# Suggestions
|
331
|
+
suggestions = []
|
332
|
+
if docstring_count < len(functions)//2: suggestions.append('Add more docstrings for clarity.')
|
333
|
+
if max_len > 50: suggestions.append(f'Split function {longest_func} ({max_len} lines) into smaller parts.')
|
334
|
+
if todos: suggestions.append('Resolve TODOs/FIXMEs for production readiness.')
|
335
|
+
# Output construction
|
336
|
+
summary_table = (
|
337
|
+
f"File: {src_file}\n"
|
338
|
+
f"Classes: {class_names}\n"
|
339
|
+
f"Functions: {func_names}\n"
|
340
|
+
f"Lines: {num_lines}\n"
|
341
|
+
f"Docstring/typehint coverage: {doc_cov} / {hint_cov}\n"
|
342
|
+
f"Function avg/max length: {avg_len}/{max_len}\n"
|
343
|
+
f"Stdlib imports: {sorted(stdlib)}\n"
|
344
|
+
f"Third-party imports: {sorted(third_party)}\n"
|
345
|
+
)
|
346
|
+
todos_section = '\n'.join([f"Line {ln}: {txt}" for ln,txt in todos]) or 'None'
|
347
|
+
snippet_section = f"Longest function: {longest_func} ({max_len} lines)\n---\n{snippet}\n---" if snippet else ''
|
348
|
+
suggest_section = '\n'.join(suggestions) or 'No major issues detected.'
|
349
|
+
docstring = ast.get_docstring(tree)
|
350
|
+
if output_format == 'json':
|
351
|
+
return json.dumps({
|
352
|
+
'file': src_file,
|
353
|
+
'classes': class_names,
|
354
|
+
'functions': func_names,
|
355
|
+
'lines': num_lines,
|
356
|
+
'docstring_coverage': doc_cov,
|
357
|
+
'typehint_coverage': hint_cov,
|
358
|
+
'todos': todos,
|
359
|
+
'longest_func': longest_func,
|
360
|
+
'longest_func_len': max_len,
|
361
|
+
'longest_func_snippet': snippet,
|
362
|
+
'suggestions': suggestions,
|
363
|
+
'imports': {'stdlib': sorted(stdlib), 'third_party': sorted(third_party)},
|
364
|
+
'docstring': docstring,
|
365
|
+
}, indent=2)
|
366
|
+
text = (
|
367
|
+
summary_table +
|
368
|
+
f"\nTODOs/FIXMEs:\n{todos_section}\n" +
|
369
|
+
(f"\n{snippet_section}\n" if snippet else '') +
|
370
|
+
f"\nSuggestions:\n{suggest_section}\n" +
|
371
|
+
(f"\nTop-level docstring: {docstring}\n" if docstring else '')
|
372
|
+
)
|
373
|
+
if output_format == 'text':
|
374
|
+
return text
|
375
|
+
# Default: ANSI/emoji box
|
376
|
+
return self.ux.ansi_emoji_box(
|
377
|
+
"Self Analysis",
|
378
|
+
text,
|
379
|
+
summary="Ultra-enhanced code analysis.",
|
380
|
+
op_type="analyze_self",
|
381
|
+
params={"file": src_file},
|
382
|
+
result_count=len(func_names) + len(class_names)
|
383
|
+
)
|
384
|
+
|
385
|
+
def _generate_code_from_prompt(self, prompt: str, src_file: str) -> str:
|
386
|
+
"""
|
387
|
+
Placeholder for LLM/agent call. Should return the full new code for src_file based on prompt.
|
388
|
+
"""
|
389
|
+
# TODO: Integrate with your LLM/agent backend.
|
390
|
+
# For now, just return the current code (no-op)
|
391
|
+
with open(src_file, "r") as f:
|
392
|
+
return f.read()
|
393
|
+
|
394
|
+
def prune_jobs(self, keep_running=True):
|
395
|
+
"""Remove jobs that are finished (unless keep_running=False, then clear all)."""
|
396
|
+
to_remove = []
|
397
|
+
for pid, job in self._jobs.items():
|
398
|
+
if job["status"] == "finished" or not keep_running:
|
399
|
+
to_remove.append(pid)
|
400
|
+
for pid in to_remove:
|
401
|
+
del self._jobs[pid]
|
402
|
+
self._save_jobs()
|
403
|
+
return self.ux.ansi_emoji_box(
|
404
|
+
"Prune Jobs",
|
405
|
+
f"Removed {len(to_remove)} finished jobs.",
|
406
|
+
summary="Job table pruned.",
|
407
|
+
op_type="prune_jobs",
|
408
|
+
params={"keep_running": keep_running},
|
409
|
+
result_count=len(to_remove)
|
410
|
+
)
|
411
|
+
|
412
|
+
async def run_and_print(self, messages):
|
413
|
+
spinner = WhingeSpinner()
|
414
|
+
spinner.start()
|
415
|
+
try:
|
416
|
+
all_results = []
|
417
|
+
async for response in self.run(messages):
|
418
|
+
content = response["messages"][0]["content"] if (isinstance(response, dict) and "messages" in response and response["messages"]) else str(response)
|
419
|
+
all_results.append(content)
|
420
|
+
# Enhanced progressive output
|
421
|
+
if isinstance(response, dict) and (response.get("progress") or response.get("matches")):
|
422
|
+
display_operation_box(
|
423
|
+
title="Progressive Operation",
|
424
|
+
content="\n".join(response.get("matches", [])),
|
425
|
+
style="bold cyan" if response.get("type") == "code_search" else "bold magenta",
|
426
|
+
result_count=len(response.get("matches", [])) if response.get("matches") is not None else None,
|
427
|
+
params={k: v for k, v in response.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
|
428
|
+
progress_line=response.get('progress'),
|
429
|
+
total_lines=response.get('total'),
|
430
|
+
spinner_state=spinner.current_spinner_state() if hasattr(spinner, 'current_spinner_state') else None,
|
431
|
+
op_type=response.get("type", "search"),
|
432
|
+
emoji="🔍" if response.get("type") == "code_search" else "🧠"
|
433
|
+
)
|
434
|
+
finally:
|
435
|
+
spinner.stop()
|
436
|
+
display_operation_box(
|
437
|
+
title="WhingeSurf Output",
|
438
|
+
content="\n".join(all_results),
|
439
|
+
style="bold green",
|
440
|
+
result_count=len(all_results),
|
441
|
+
params={"prompt": messages[0]["content"]},
|
442
|
+
op_type="whinge_surf"
|
443
|
+
)
|
444
|
+
|
445
|
+
# SELF-IMPROVEMENT: add a proof of self-improvement (2025-04-19 05:17:27)
|
446
|
+
|
447
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:20:22)
|
448
|
+
|
449
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:22:57)
|
450
|
+
|
451
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:24:30)
|
452
|
+
|
453
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:26:19)
|
454
|
+
|
455
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:28:02)
|
456
|
+
|
457
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:30:18)
|
458
|
+
|
459
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:31:26)
|
460
|
+
|
461
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:32:37)
|
462
|
+
|
463
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:35:24)
|
464
|
+
|
465
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:36:26)
|
466
|
+
|
467
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:39:09)
|
468
|
+
|
469
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:40:10)
|
470
|
+
|
471
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:43:04)
|
472
|
+
|
473
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 20:44:05)
|
474
|
+
|
475
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:34:27)
|
476
|
+
|
477
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:36:05)
|
478
|
+
|
479
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:36:58)
|
480
|
+
|
481
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:38:09)
|
482
|
+
|
483
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:39:00)
|
484
|
+
|
485
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:41:18)
|
486
|
+
|
487
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:42:13)
|
488
|
+
|
489
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:44:26)
|
490
|
+
|
491
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:45:29)
|
492
|
+
|
493
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:54:16)
|
494
|
+
|
495
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 21:59:18)
|
496
|
+
|
497
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:00:25)
|
498
|
+
|
499
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:02:11)
|
500
|
+
|
501
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:04:15)
|
502
|
+
|
503
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:05:25)
|
504
|
+
|
505
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:06:26)
|
506
|
+
|
507
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:07:26)
|
508
|
+
|
509
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:09:13)
|
510
|
+
|
511
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:10:29)
|
512
|
+
|
513
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:13:18)
|
514
|
+
|
515
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:13:42)
|
516
|
+
|
517
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:16:03)
|
518
|
+
|
519
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:18:39)
|
520
|
+
|
521
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:20:36)
|
522
|
+
|
523
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:25:35)
|
524
|
+
|
525
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:26:31)
|
526
|
+
|
527
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:30:05)
|
528
|
+
|
529
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:33:27)
|
530
|
+
|
531
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:33:50)
|
532
|
+
|
533
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:35:57)
|
534
|
+
|
535
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:37:40)
|
536
|
+
|
537
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:40:29)
|
538
|
+
|
539
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:42:50)
|
540
|
+
|
541
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:52:23)
|
542
|
+
|
543
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:53:37)
|
544
|
+
|
545
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:54:56)
|
546
|
+
|
547
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:58:00)
|
548
|
+
|
549
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 22:59:01)
|
550
|
+
|
551
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 23:00:03)
|
552
|
+
|
553
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 23:01:06)
|
554
|
+
|
555
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 23:02:36)
|
556
|
+
|
557
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 23:09:42)
|
558
|
+
|
559
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 23:10:42)
|
560
|
+
|
561
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 23:17:37)
|
562
|
+
|
563
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 23:32:39)
|
564
|
+
|
565
|
+
# SELF-IMPROVEMENT: Add a test comment (2025-04-19 23:36:00)
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import argparse
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
import asyncio
|
5
|
+
from swarm.blueprints.whinge_surf.blueprint_whinge_surf import WhingeSurfBlueprint
|
6
|
+
|
7
|
+
def main():
|
8
|
+
parser = argparse.ArgumentParser(description="whinge-surf: background subprocess butler & self-updater")
|
9
|
+
parser.add_argument('--run', nargs='+', help='Run a subprocess in the background (supply command as args)')
|
10
|
+
parser.add_argument('--status', type=int, help='Check status of a subprocess by PID')
|
11
|
+
parser.add_argument('--output', type=int, help='Get output from a subprocess by PID')
|
12
|
+
parser.add_argument('--kill', type=int, help='Kill/cancel a subprocess by PID')
|
13
|
+
parser.add_argument('--self-update', type=str, help='Prompt to update whinge-surf code (self-improvement)')
|
14
|
+
parser.add_argument('--no-test', action='store_true', help='Skip running tests after self-update')
|
15
|
+
parser.add_argument('--analyze-self', action='store_true', help='Analyze whinge-surf source code and print summary')
|
16
|
+
parser.add_argument('--analyze-output', choices=['ansi', 'text', 'json'], default='ansi', help='Output format for analysis')
|
17
|
+
parser.add_argument('--list-jobs', action='store_true', help='List all subprocess jobs')
|
18
|
+
parser.add_argument('--show-output', type=int, help='Show output of a subprocess by PID')
|
19
|
+
parser.add_argument('--tail', type=int, help='Tail live output of a subprocess by PID')
|
20
|
+
parser.add_argument('--resource-usage', type=int, help='Show resource usage for a subprocess by PID')
|
21
|
+
parser.add_argument('--prune-jobs', action='store_true', help='Remove finished jobs from the job table')
|
22
|
+
args = parser.parse_args()
|
23
|
+
|
24
|
+
ws = WhingeSurfBlueprint()
|
25
|
+
|
26
|
+
if args.run:
|
27
|
+
# If a single string is passed, treat it as a shell command
|
28
|
+
if len(args.run) == 1:
|
29
|
+
cmd = ["/bin/sh", "-c", args.run[0]]
|
30
|
+
else:
|
31
|
+
cmd = args.run
|
32
|
+
pid = ws.run_subprocess_in_background(cmd)
|
33
|
+
print(ws.ux.ansi_emoji_box(
|
34
|
+
"Subprocess Started",
|
35
|
+
f"PID: {pid}\nCommand: {' '.join(cmd)}",
|
36
|
+
op_type="run",
|
37
|
+
params={'cmd': cmd},
|
38
|
+
))
|
39
|
+
return
|
40
|
+
if args.status is not None:
|
41
|
+
status = ws.check_subprocess_status(args.status)
|
42
|
+
print(ws.ux.ansi_emoji_box(
|
43
|
+
"Subprocess Status",
|
44
|
+
str(status) if status else f"No such PID: {args.status}",
|
45
|
+
op_type="status",
|
46
|
+
params={'pid': args.status},
|
47
|
+
))
|
48
|
+
return
|
49
|
+
if args.output is not None:
|
50
|
+
output = ws.get_subprocess_output(args.output)
|
51
|
+
print(ws.ux.ansi_emoji_box(
|
52
|
+
"Subprocess Output",
|
53
|
+
output if output is not None else f"No such PID: {args.output}",
|
54
|
+
op_type="output",
|
55
|
+
params={'pid': args.output},
|
56
|
+
))
|
57
|
+
return
|
58
|
+
if args.kill is not None:
|
59
|
+
result = ws.kill_subprocess(args.kill)
|
60
|
+
print(ws.ux.ansi_emoji_box(
|
61
|
+
"Subprocess Kill",
|
62
|
+
result,
|
63
|
+
op_type="kill",
|
64
|
+
params={'pid': args.kill},
|
65
|
+
))
|
66
|
+
return
|
67
|
+
if args.list_jobs:
|
68
|
+
print(ws.list_jobs())
|
69
|
+
return
|
70
|
+
if args.show_output is not None:
|
71
|
+
print(ws.show_output(args.show_output))
|
72
|
+
return
|
73
|
+
if args.tail is not None:
|
74
|
+
ws.tail_output(args.tail)
|
75
|
+
return
|
76
|
+
if args.resource_usage is not None:
|
77
|
+
print(ws.resource_usage(args.resource_usage))
|
78
|
+
return
|
79
|
+
if args.analyze_self:
|
80
|
+
print(ws.analyze_self(output_format=args.analyze_output))
|
81
|
+
return
|
82
|
+
if args.prune_jobs:
|
83
|
+
print(ws.prune_jobs())
|
84
|
+
return
|
85
|
+
if args.self_update:
|
86
|
+
result = ws.self_update_from_prompt(args.self_update, test=not args.no_test)
|
87
|
+
print(result)
|
88
|
+
return
|
89
|
+
parser.print_help()
|
90
|
+
|
91
|
+
if __name__ == "__main__":
|
92
|
+
import sys
|
93
|
+
if sys.argv[0].endswith("whinge_surf_cli.py") or sys.argv[0].endswith("whinge_surf_cli"): # legacy
|
94
|
+
print("[INFO] For future use, invoke this CLI as 'whinge' instead of 'whinge_surf_cli'.")
|
95
|
+
main()
|
96
|
+
elif sys.argv[0].endswith("whinge"): # preferred new name
|
97
|
+
main()
|
98
|
+
else:
|
99
|
+
main()
|