open-swarm 0.1.1745275181__py3-none-any.whl → 0.1.1748636259__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 (296) hide show
  1. open_swarm-0.1.1748636259.dist-info/METADATA +188 -0
  2. open_swarm-0.1.1748636259.dist-info/RECORD +82 -0
  3. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636259.dist-info}/WHEEL +2 -1
  4. open_swarm-0.1.1748636259.dist-info/entry_points.txt +3 -0
  5. open_swarm-0.1.1748636259.dist-info/top_level.txt +1 -0
  6. swarm/agent/agent.py +49 -0
  7. swarm/auth.py +48 -113
  8. swarm/consumers.py +0 -19
  9. swarm/extensions/blueprint/__init__.py +16 -30
  10. swarm/{core → extensions/blueprint}/agent_utils.py +1 -1
  11. swarm/extensions/blueprint/blueprint_base.py +458 -0
  12. swarm/extensions/blueprint/blueprint_discovery.py +112 -0
  13. swarm/extensions/blueprint/output_utils.py +95 -0
  14. swarm/{core → extensions/blueprint}/spinner.py +21 -30
  15. swarm/extensions/cli/cli_args.py +0 -6
  16. swarm/extensions/cli/commands/blueprint_management.py +9 -47
  17. swarm/extensions/cli/commands/config_management.py +6 -5
  18. swarm/extensions/cli/commands/edit_config.py +7 -16
  19. swarm/extensions/cli/commands/list_blueprints.py +1 -1
  20. swarm/extensions/cli/commands/validate_env.py +4 -11
  21. swarm/extensions/cli/commands/validate_envvars.py +6 -6
  22. swarm/extensions/cli/interactive_shell.py +2 -16
  23. swarm/extensions/config/config_loader.py +201 -107
  24. swarm/{core → extensions/config}/config_manager.py +38 -50
  25. swarm/{core → extensions/config}/server_config.py +0 -32
  26. swarm/extensions/launchers/build_launchers.py +14 -0
  27. swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
  28. swarm/extensions/launchers/swarm_api.py +64 -8
  29. swarm/extensions/launchers/swarm_cli.py +300 -8
  30. swarm/llm/chat_completion.py +195 -0
  31. swarm/serializers.py +5 -96
  32. swarm/settings.py +111 -99
  33. swarm/urls.py +74 -57
  34. swarm/utils/context_utils.py +4 -10
  35. swarm/utils/general_utils.py +0 -21
  36. swarm/utils/redact.py +36 -23
  37. swarm/views/api_views.py +39 -48
  38. swarm/views/chat_views.py +70 -237
  39. swarm/views/core_views.py +87 -80
  40. swarm/views/model_views.py +121 -64
  41. swarm/views/utils.py +441 -65
  42. swarm/views/web_views.py +2 -2
  43. open_swarm-0.1.1745275181.dist-info/METADATA +0 -874
  44. open_swarm-0.1.1745275181.dist-info/RECORD +0 -319
  45. open_swarm-0.1.1745275181.dist-info/entry_points.txt +0 -4
  46. swarm/blueprints/README.md +0 -68
  47. swarm/blueprints/blueprint_audit_status.json +0 -27
  48. swarm/blueprints/chatbot/README.md +0 -40
  49. swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
  50. swarm/blueprints/chatbot/metadata.json +0 -23
  51. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
  52. swarm/blueprints/chucks_angels/README.md +0 -11
  53. swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
  54. swarm/blueprints/chucks_angels/test_basic.py +0 -3
  55. swarm/blueprints/codey/CODEY.md +0 -15
  56. swarm/blueprints/codey/README.md +0 -115
  57. swarm/blueprints/codey/blueprint_codey.py +0 -1072
  58. swarm/blueprints/codey/codey_cli.py +0 -373
  59. swarm/blueprints/codey/instructions.md +0 -17
  60. swarm/blueprints/codey/metadata.json +0 -23
  61. swarm/blueprints/common/operation_box_utils.py +0 -83
  62. swarm/blueprints/digitalbutlers/README.md +0 -11
  63. swarm/blueprints/digitalbutlers/__init__.py +0 -1
  64. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
  65. swarm/blueprints/digitalbutlers/test_basic.py +0 -3
  66. swarm/blueprints/divine_code/README.md +0 -3
  67. swarm/blueprints/divine_code/__init__.py +0 -10
  68. swarm/blueprints/divine_code/apps.py +0 -11
  69. swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
  70. swarm/blueprints/django_chat/apps.py +0 -6
  71. swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
  72. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
  73. swarm/blueprints/django_chat/urls.py +0 -8
  74. swarm/blueprints/django_chat/views.py +0 -32
  75. swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
  76. swarm/blueprints/flock/README.md +0 -11
  77. swarm/blueprints/flock/__init__.py +0 -8
  78. swarm/blueprints/flock/blueprint_flock.py +0 -7
  79. swarm/blueprints/flock/test_basic.py +0 -3
  80. swarm/blueprints/geese/README.md +0 -10
  81. swarm/blueprints/geese/__init__.py +0 -8
  82. swarm/blueprints/geese/blueprint_geese.py +0 -384
  83. swarm/blueprints/geese/geese_cli.py +0 -102
  84. swarm/blueprints/jeeves/README.md +0 -41
  85. swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
  86. swarm/blueprints/jeeves/jeeves_cli.py +0 -55
  87. swarm/blueprints/jeeves/metadata.json +0 -24
  88. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
  89. swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
  90. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
  91. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
  92. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
  93. swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
  94. swarm/blueprints/poets/blueprint_poets.py +0 -546
  95. swarm/blueprints/poets/poets_cli.py +0 -23
  96. swarm/blueprints/rue_code/README.md +0 -8
  97. swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
  98. swarm/blueprints/rue_code/rue_code_cli.py +0 -43
  99. swarm/blueprints/stewie/apps.py +0 -12
  100. swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
  101. swarm/blueprints/stewie/models.py +0 -19
  102. swarm/blueprints/stewie/serializers.py +0 -10
  103. swarm/blueprints/stewie/settings.py +0 -17
  104. swarm/blueprints/stewie/urls.py +0 -11
  105. swarm/blueprints/stewie/views.py +0 -26
  106. swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
  107. swarm/blueprints/whinge_surf/README.md +0 -22
  108. swarm/blueprints/whinge_surf/__init__.py +0 -1
  109. swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
  110. swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
  111. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  112. swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
  113. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
  114. swarm/blueprints/zeus/__init__.py +0 -2
  115. swarm/blueprints/zeus/apps.py +0 -4
  116. swarm/blueprints/zeus/blueprint_zeus.py +0 -270
  117. swarm/blueprints/zeus/zeus_cli.py +0 -13
  118. swarm/cli/async_input.py +0 -65
  119. swarm/cli/async_input_demo.py +0 -32
  120. swarm/core/blueprint_base.py +0 -769
  121. swarm/core/blueprint_discovery.py +0 -125
  122. swarm/core/blueprint_runner.py +0 -59
  123. swarm/core/blueprint_ux.py +0 -109
  124. swarm/core/build_launchers.py +0 -15
  125. swarm/core/cli/__init__.py +0 -1
  126. swarm/core/cli/commands/__init__.py +0 -1
  127. swarm/core/cli/commands/blueprint_management.py +0 -7
  128. swarm/core/cli/interactive_shell.py +0 -14
  129. swarm/core/cli/main.py +0 -50
  130. swarm/core/cli/utils/__init__.py +0 -1
  131. swarm/core/cli/utils/discover_commands.py +0 -18
  132. swarm/core/config_loader.py +0 -122
  133. swarm/core/output_utils.py +0 -193
  134. swarm/core/session_logger.py +0 -42
  135. swarm/core/slash_commands.py +0 -89
  136. swarm/core/swarm_api.py +0 -68
  137. swarm/core/swarm_cli.py +0 -216
  138. swarm/core/utils/__init__.py +0 -0
  139. swarm/extensions/blueprint/cli_handler.py +0 -197
  140. swarm/extensions/blueprint/runnable_blueprint.py +0 -42
  141. swarm/extensions/cli/utils/__init__.py +0 -1
  142. swarm/extensions/cli/utils/async_input.py +0 -46
  143. swarm/extensions/cli/utils/prompt_user.py +0 -3
  144. swarm/management/__init__.py +0 -0
  145. swarm/management/commands/__init__.py +0 -0
  146. swarm/management/commands/runserver.py +0 -58
  147. swarm/middleware.py +0 -65
  148. swarm/permissions.py +0 -38
  149. swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
  150. swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
  151. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
  152. swarm/static/contrib/markedjs/marked.min.js +0 -6
  153. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
  154. swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
  155. swarm/static/contrib/tabler-icons/archive.svg +0 -21
  156. swarm/static/contrib/tabler-icons/artboard.svg +0 -27
  157. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
  158. swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
  159. swarm/static/contrib/tabler-icons/carambola.svg +0 -19
  160. swarm/static/contrib/tabler-icons/copy.svg +0 -20
  161. swarm/static/contrib/tabler-icons/download.svg +0 -21
  162. swarm/static/contrib/tabler-icons/edit.svg +0 -21
  163. swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
  164. swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
  165. swarm/static/contrib/tabler-icons/headset.svg +0 -22
  166. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
  167. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
  168. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
  169. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
  170. swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
  171. swarm/static/contrib/tabler-icons/message-star.svg +0 -22
  172. swarm/static/contrib/tabler-icons/message-x.svg +0 -23
  173. swarm/static/contrib/tabler-icons/message.svg +0 -21
  174. swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
  175. swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
  176. swarm/static/contrib/tabler-icons/robot.svg +0 -26
  177. swarm/static/contrib/tabler-icons/search.svg +0 -19
  178. swarm/static/contrib/tabler-icons/settings.svg +0 -20
  179. swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
  180. swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
  181. swarm/static/css/dropdown.css +0 -22
  182. swarm/static/htmx/htmx.min.js +0 -0
  183. swarm/static/js/dropdown.js +0 -23
  184. swarm/static/rest_mode/css/base.css +0 -470
  185. swarm/static/rest_mode/css/chat-history.css +0 -286
  186. swarm/static/rest_mode/css/chat.css +0 -251
  187. swarm/static/rest_mode/css/chatbot.css +0 -74
  188. swarm/static/rest_mode/css/chatgpt.css +0 -62
  189. swarm/static/rest_mode/css/colors/corporate.css +0 -74
  190. swarm/static/rest_mode/css/colors/pastel.css +0 -81
  191. swarm/static/rest_mode/css/colors/tropical.css +0 -82
  192. swarm/static/rest_mode/css/general.css +0 -142
  193. swarm/static/rest_mode/css/layout.css +0 -167
  194. swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
  195. swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
  196. swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
  197. swarm/static/rest_mode/css/messages.css +0 -84
  198. swarm/static/rest_mode/css/messenger.css +0 -135
  199. swarm/static/rest_mode/css/settings.css +0 -91
  200. swarm/static/rest_mode/css/simple.css +0 -44
  201. swarm/static/rest_mode/css/slack.css +0 -58
  202. swarm/static/rest_mode/css/style.css +0 -156
  203. swarm/static/rest_mode/css/theme.css +0 -30
  204. swarm/static/rest_mode/css/toast.css +0 -40
  205. swarm/static/rest_mode/js/auth.js +0 -9
  206. swarm/static/rest_mode/js/blueprint.js +0 -41
  207. swarm/static/rest_mode/js/blueprintUtils.js +0 -12
  208. swarm/static/rest_mode/js/chatLogic.js +0 -79
  209. swarm/static/rest_mode/js/debug.js +0 -63
  210. swarm/static/rest_mode/js/events.js +0 -98
  211. swarm/static/rest_mode/js/main.js +0 -19
  212. swarm/static/rest_mode/js/messages.js +0 -264
  213. swarm/static/rest_mode/js/messengerLogic.js +0 -355
  214. swarm/static/rest_mode/js/modules/apiService.js +0 -84
  215. swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
  216. swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
  217. swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
  218. swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
  219. swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
  220. swarm/static/rest_mode/js/modules/state.js +0 -7
  221. swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
  222. swarm/static/rest_mode/js/modules/validation.js +0 -23
  223. swarm/static/rest_mode/js/rendering.js +0 -119
  224. swarm/static/rest_mode/js/settings.js +0 -130
  225. swarm/static/rest_mode/js/sidebar.js +0 -94
  226. swarm/static/rest_mode/js/simpleLogic.js +0 -37
  227. swarm/static/rest_mode/js/slackLogic.js +0 -66
  228. swarm/static/rest_mode/js/splash.js +0 -76
  229. swarm/static/rest_mode/js/theme.js +0 -111
  230. swarm/static/rest_mode/js/toast.js +0 -36
  231. swarm/static/rest_mode/js/ui.js +0 -265
  232. swarm/static/rest_mode/js/validation.js +0 -57
  233. swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
  234. swarm/static/rest_mode/svg/arrow_down.svg +0 -5
  235. swarm/static/rest_mode/svg/arrow_left.svg +0 -5
  236. swarm/static/rest_mode/svg/arrow_right.svg +0 -5
  237. swarm/static/rest_mode/svg/arrow_up.svg +0 -5
  238. swarm/static/rest_mode/svg/attach.svg +0 -8
  239. swarm/static/rest_mode/svg/avatar.svg +0 -7
  240. swarm/static/rest_mode/svg/canvas.svg +0 -6
  241. swarm/static/rest_mode/svg/chat_history.svg +0 -4
  242. swarm/static/rest_mode/svg/close.svg +0 -5
  243. swarm/static/rest_mode/svg/copy.svg +0 -4
  244. swarm/static/rest_mode/svg/dark_mode.svg +0 -3
  245. swarm/static/rest_mode/svg/edit.svg +0 -5
  246. swarm/static/rest_mode/svg/layout.svg +0 -9
  247. swarm/static/rest_mode/svg/logo.svg +0 -29
  248. swarm/static/rest_mode/svg/logout.svg +0 -5
  249. swarm/static/rest_mode/svg/mobile.svg +0 -5
  250. swarm/static/rest_mode/svg/new_chat.svg +0 -4
  251. swarm/static/rest_mode/svg/not_visible.svg +0 -5
  252. swarm/static/rest_mode/svg/plus.svg +0 -7
  253. swarm/static/rest_mode/svg/run_code.svg +0 -6
  254. swarm/static/rest_mode/svg/save.svg +0 -4
  255. swarm/static/rest_mode/svg/search.svg +0 -6
  256. swarm/static/rest_mode/svg/settings.svg +0 -4
  257. swarm/static/rest_mode/svg/speaker.svg +0 -5
  258. swarm/static/rest_mode/svg/stop.svg +0 -6
  259. swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
  260. swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
  261. swarm/static/rest_mode/svg/toggle_off.svg +0 -6
  262. swarm/static/rest_mode/svg/toggle_on.svg +0 -6
  263. swarm/static/rest_mode/svg/trash.svg +0 -10
  264. swarm/static/rest_mode/svg/undo.svg +0 -3
  265. swarm/static/rest_mode/svg/visible.svg +0 -8
  266. swarm/static/rest_mode/svg/voice.svg +0 -10
  267. swarm/templates/account/login.html +0 -22
  268. swarm/templates/account/signup.html +0 -32
  269. swarm/templates/base.html +0 -30
  270. swarm/templates/chat.html +0 -43
  271. swarm/templates/index.html +0 -35
  272. swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
  273. swarm/templates/rest_mode/components/header.html +0 -45
  274. swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
  275. swarm/templates/rest_mode/components/settings_dialog.html +0 -97
  276. swarm/templates/rest_mode/components/splash_screen.html +0 -7
  277. swarm/templates/rest_mode/components/top_bar.html +0 -28
  278. swarm/templates/rest_mode/message_ui.html +0 -50
  279. swarm/templates/rest_mode/slackbot.html +0 -30
  280. swarm/templates/simple_blueprint_page.html +0 -24
  281. swarm/templates/websocket_partials/final_system_message.html +0 -3
  282. swarm/templates/websocket_partials/system_message.html +0 -4
  283. swarm/templates/websocket_partials/user_message.html +0 -5
  284. swarm/utils/ansi_box.py +0 -34
  285. swarm/utils/disable_tracing.py +0 -38
  286. swarm/utils/log_utils.py +0 -63
  287. swarm/utils/openai_patch.py +0 -33
  288. swarm/ux/ansi_box.py +0 -43
  289. swarm/ux/spinner.py +0 -53
  290. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636259.dist-info}/licenses/LICENSE +0 -0
  291. /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
  292. /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
  293. /swarm/{core → extensions/config}/setup_wizard.py +0 -0
  294. /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
  295. /swarm/{core → extensions/config}/utils/logger.py +0 -0
  296. /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
@@ -1,22 +0,0 @@
1
- # Whinge Surf Blueprint
2
-
3
- **Whinge Surf** is a blueprint for Open Swarm that lets you launch background subprocesses, check on their status, and view their console output—perfect for monitoring long-running or noisy tasks without blocking your main workflow.
4
-
5
- ## Features
6
- - Launch subprocesses in the background
7
- - Check if a subprocess has finished
8
- - View live or completed console output for any subprocess
9
-
10
- ## Why is it special?
11
- Whinge Surf is your "background task butler"—it lets you surf the waves of whinging (output) from your processes, without getting bogged down. Great for CI jobs, long scripts, or anything you want to keep an eye on from afar.
12
-
13
- ## Example Usage
14
- ```python
15
- from swarm.blueprints.whinge_surf.blueprint_whinge_surf import WhingeSurfBlueprint
16
- ws = WhingeSurfBlueprint()
17
- pid = ws.run_subprocess_in_background(["python", "my_script.py"])
18
- status = ws.check_subprocess_status(pid)
19
- output = ws.get_subprocess_output(pid)
20
- ```
21
-
22
- ---
@@ -1 +0,0 @@
1
- # Whinge Surf blueprint package
@@ -1,565 +0,0 @@
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)
@@ -1,99 +0,0 @@
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()