open-swarm 0.1.1745275181__py3-none-any.whl → 0.1.1748636295__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.1748636295.dist-info/METADATA +257 -0
- open_swarm-0.1.1748636295.dist-info/RECORD +89 -0
- {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/WHEEL +2 -1
- open_swarm-0.1.1748636295.dist-info/entry_points.txt +3 -0
- open_swarm-0.1.1748636295.dist-info/top_level.txt +1 -0
- swarm/__init__.py +2 -0
- swarm/agent/agent.py +49 -0
- swarm/auth.py +48 -113
- swarm/consumers.py +0 -19
- swarm/core.py +411 -0
- swarm/extensions/blueprint/__init__.py +16 -30
- swarm/extensions/blueprint/agent_utils.py +45 -0
- swarm/extensions/blueprint/blueprint_base.py +562 -0
- swarm/extensions/blueprint/blueprint_discovery.py +112 -0
- swarm/extensions/blueprint/django_utils.py +79 -181
- swarm/extensions/blueprint/interactive_mode.py +72 -67
- swarm/extensions/blueprint/output_utils.py +82 -0
- swarm/{core → extensions/blueprint}/spinner.py +21 -30
- swarm/extensions/cli/cli_args.py +0 -6
- swarm/extensions/cli/commands/blueprint_management.py +9 -47
- swarm/extensions/cli/commands/config_management.py +6 -5
- swarm/extensions/cli/commands/edit_config.py +7 -16
- swarm/extensions/cli/commands/list_blueprints.py +1 -1
- swarm/extensions/cli/commands/validate_env.py +4 -11
- swarm/extensions/cli/commands/validate_envvars.py +6 -6
- swarm/extensions/cli/interactive_shell.py +2 -16
- swarm/extensions/config/config_loader.py +345 -107
- swarm/{core → extensions/config}/config_manager.py +38 -50
- swarm/{core → extensions/config}/server_config.py +0 -32
- swarm/extensions/launchers/build_launchers.py +14 -0
- swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
- swarm/extensions/launchers/swarm_api.py +64 -8
- swarm/extensions/launchers/swarm_cli.py +300 -8
- swarm/extensions/mcp/__init__.py +1 -0
- swarm/extensions/mcp/cache_utils.py +32 -0
- swarm/extensions/mcp/mcp_client.py +233 -0
- swarm/extensions/mcp/mcp_tool_provider.py +135 -0
- swarm/extensions/mcp/mcp_utils.py +260 -0
- swarm/llm/chat_completion.py +166 -0
- swarm/serializers.py +5 -96
- swarm/settings.py +133 -85
- swarm/types.py +91 -0
- swarm/urls.py +74 -57
- swarm/utils/context_utils.py +4 -10
- swarm/utils/general_utils.py +0 -21
- swarm/utils/redact.py +36 -23
- swarm/views/api_views.py +39 -48
- swarm/views/chat_views.py +76 -236
- swarm/views/core_views.py +87 -80
- swarm/views/model_views.py +121 -64
- swarm/views/utils.py +439 -65
- swarm/views/web_views.py +2 -2
- open_swarm-0.1.1745275181.dist-info/METADATA +0 -874
- open_swarm-0.1.1745275181.dist-info/RECORD +0 -319
- open_swarm-0.1.1745275181.dist-info/entry_points.txt +0 -4
- swarm/blueprints/README.md +0 -68
- swarm/blueprints/blueprint_audit_status.json +0 -27
- swarm/blueprints/chatbot/README.md +0 -40
- swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
- swarm/blueprints/chatbot/metadata.json +0 -23
- swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
- swarm/blueprints/chucks_angels/README.md +0 -11
- swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
- swarm/blueprints/chucks_angels/test_basic.py +0 -3
- swarm/blueprints/codey/CODEY.md +0 -15
- swarm/blueprints/codey/README.md +0 -115
- swarm/blueprints/codey/blueprint_codey.py +0 -1072
- swarm/blueprints/codey/codey_cli.py +0 -373
- swarm/blueprints/codey/instructions.md +0 -17
- swarm/blueprints/codey/metadata.json +0 -23
- swarm/blueprints/common/operation_box_utils.py +0 -83
- swarm/blueprints/digitalbutlers/README.md +0 -11
- swarm/blueprints/digitalbutlers/__init__.py +0 -1
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
- swarm/blueprints/digitalbutlers/test_basic.py +0 -3
- swarm/blueprints/divine_code/README.md +0 -3
- swarm/blueprints/divine_code/__init__.py +0 -10
- swarm/blueprints/divine_code/apps.py +0 -11
- swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
- swarm/blueprints/django_chat/apps.py +0 -6
- swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
- swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
- swarm/blueprints/django_chat/urls.py +0 -8
- swarm/blueprints/django_chat/views.py +0 -32
- swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
- swarm/blueprints/flock/README.md +0 -11
- swarm/blueprints/flock/__init__.py +0 -8
- swarm/blueprints/flock/blueprint_flock.py +0 -7
- swarm/blueprints/flock/test_basic.py +0 -3
- swarm/blueprints/geese/README.md +0 -10
- swarm/blueprints/geese/__init__.py +0 -8
- swarm/blueprints/geese/blueprint_geese.py +0 -384
- swarm/blueprints/geese/geese_cli.py +0 -102
- swarm/blueprints/jeeves/README.md +0 -41
- swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
- swarm/blueprints/jeeves/jeeves_cli.py +0 -55
- swarm/blueprints/jeeves/metadata.json +0 -24
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
- swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
- swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
- swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
- swarm/blueprints/poets/blueprint_poets.py +0 -546
- swarm/blueprints/poets/poets_cli.py +0 -23
- swarm/blueprints/rue_code/README.md +0 -8
- swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
- swarm/blueprints/rue_code/rue_code_cli.py +0 -43
- swarm/blueprints/stewie/apps.py +0 -12
- swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
- swarm/blueprints/stewie/models.py +0 -19
- swarm/blueprints/stewie/serializers.py +0 -10
- swarm/blueprints/stewie/settings.py +0 -17
- swarm/blueprints/stewie/urls.py +0 -11
- swarm/blueprints/stewie/views.py +0 -26
- swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
- swarm/blueprints/whinge_surf/README.md +0 -22
- swarm/blueprints/whinge_surf/__init__.py +0 -1
- swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
- swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
- swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
- swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
- swarm/blueprints/zeus/__init__.py +0 -2
- swarm/blueprints/zeus/apps.py +0 -4
- swarm/blueprints/zeus/blueprint_zeus.py +0 -270
- swarm/blueprints/zeus/zeus_cli.py +0 -13
- swarm/cli/async_input.py +0 -65
- swarm/cli/async_input_demo.py +0 -32
- swarm/core/agent_utils.py +0 -21
- swarm/core/blueprint_base.py +0 -769
- swarm/core/blueprint_discovery.py +0 -125
- swarm/core/blueprint_runner.py +0 -59
- swarm/core/blueprint_ux.py +0 -109
- swarm/core/build_launchers.py +0 -15
- swarm/core/cli/__init__.py +0 -1
- swarm/core/cli/commands/__init__.py +0 -1
- swarm/core/cli/commands/blueprint_management.py +0 -7
- swarm/core/cli/interactive_shell.py +0 -14
- swarm/core/cli/main.py +0 -50
- swarm/core/cli/utils/__init__.py +0 -1
- swarm/core/cli/utils/discover_commands.py +0 -18
- swarm/core/config_loader.py +0 -122
- swarm/core/output_utils.py +0 -193
- swarm/core/session_logger.py +0 -42
- swarm/core/slash_commands.py +0 -89
- swarm/core/swarm_api.py +0 -68
- swarm/core/swarm_cli.py +0 -216
- swarm/core/utils/__init__.py +0 -0
- swarm/extensions/blueprint/cli_handler.py +0 -197
- swarm/extensions/blueprint/runnable_blueprint.py +0 -42
- swarm/extensions/cli/utils/__init__.py +0 -1
- swarm/extensions/cli/utils/async_input.py +0 -46
- swarm/extensions/cli/utils/prompt_user.py +0 -3
- swarm/management/__init__.py +0 -0
- swarm/management/commands/__init__.py +0 -0
- swarm/management/commands/runserver.py +0 -58
- swarm/middleware.py +0 -65
- swarm/permissions.py +0 -38
- swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
- swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
- swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
- swarm/static/contrib/markedjs/marked.min.js +0 -6
- swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
- swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
- swarm/static/contrib/tabler-icons/archive.svg +0 -21
- swarm/static/contrib/tabler-icons/artboard.svg +0 -27
- swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
- swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
- swarm/static/contrib/tabler-icons/carambola.svg +0 -19
- swarm/static/contrib/tabler-icons/copy.svg +0 -20
- swarm/static/contrib/tabler-icons/download.svg +0 -21
- swarm/static/contrib/tabler-icons/edit.svg +0 -21
- swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
- swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
- swarm/static/contrib/tabler-icons/headset.svg +0 -22
- swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
- swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
- swarm/static/contrib/tabler-icons/message-star.svg +0 -22
- swarm/static/contrib/tabler-icons/message-x.svg +0 -23
- swarm/static/contrib/tabler-icons/message.svg +0 -21
- swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
- swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
- swarm/static/contrib/tabler-icons/robot.svg +0 -26
- swarm/static/contrib/tabler-icons/search.svg +0 -19
- swarm/static/contrib/tabler-icons/settings.svg +0 -20
- swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
- swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
- swarm/static/css/dropdown.css +0 -22
- swarm/static/htmx/htmx.min.js +0 -0
- swarm/static/js/dropdown.js +0 -23
- swarm/static/rest_mode/css/base.css +0 -470
- swarm/static/rest_mode/css/chat-history.css +0 -286
- swarm/static/rest_mode/css/chat.css +0 -251
- swarm/static/rest_mode/css/chatbot.css +0 -74
- swarm/static/rest_mode/css/chatgpt.css +0 -62
- swarm/static/rest_mode/css/colors/corporate.css +0 -74
- swarm/static/rest_mode/css/colors/pastel.css +0 -81
- swarm/static/rest_mode/css/colors/tropical.css +0 -82
- swarm/static/rest_mode/css/general.css +0 -142
- swarm/static/rest_mode/css/layout.css +0 -167
- swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
- swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
- swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
- swarm/static/rest_mode/css/messages.css +0 -84
- swarm/static/rest_mode/css/messenger.css +0 -135
- swarm/static/rest_mode/css/settings.css +0 -91
- swarm/static/rest_mode/css/simple.css +0 -44
- swarm/static/rest_mode/css/slack.css +0 -58
- swarm/static/rest_mode/css/style.css +0 -156
- swarm/static/rest_mode/css/theme.css +0 -30
- swarm/static/rest_mode/css/toast.css +0 -40
- swarm/static/rest_mode/js/auth.js +0 -9
- swarm/static/rest_mode/js/blueprint.js +0 -41
- swarm/static/rest_mode/js/blueprintUtils.js +0 -12
- swarm/static/rest_mode/js/chatLogic.js +0 -79
- swarm/static/rest_mode/js/debug.js +0 -63
- swarm/static/rest_mode/js/events.js +0 -98
- swarm/static/rest_mode/js/main.js +0 -19
- swarm/static/rest_mode/js/messages.js +0 -264
- swarm/static/rest_mode/js/messengerLogic.js +0 -355
- swarm/static/rest_mode/js/modules/apiService.js +0 -84
- swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
- swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
- swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
- swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
- swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
- swarm/static/rest_mode/js/modules/state.js +0 -7
- swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
- swarm/static/rest_mode/js/modules/validation.js +0 -23
- swarm/static/rest_mode/js/rendering.js +0 -119
- swarm/static/rest_mode/js/settings.js +0 -130
- swarm/static/rest_mode/js/sidebar.js +0 -94
- swarm/static/rest_mode/js/simpleLogic.js +0 -37
- swarm/static/rest_mode/js/slackLogic.js +0 -66
- swarm/static/rest_mode/js/splash.js +0 -76
- swarm/static/rest_mode/js/theme.js +0 -111
- swarm/static/rest_mode/js/toast.js +0 -36
- swarm/static/rest_mode/js/ui.js +0 -265
- swarm/static/rest_mode/js/validation.js +0 -57
- swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
- swarm/static/rest_mode/svg/arrow_down.svg +0 -5
- swarm/static/rest_mode/svg/arrow_left.svg +0 -5
- swarm/static/rest_mode/svg/arrow_right.svg +0 -5
- swarm/static/rest_mode/svg/arrow_up.svg +0 -5
- swarm/static/rest_mode/svg/attach.svg +0 -8
- swarm/static/rest_mode/svg/avatar.svg +0 -7
- swarm/static/rest_mode/svg/canvas.svg +0 -6
- swarm/static/rest_mode/svg/chat_history.svg +0 -4
- swarm/static/rest_mode/svg/close.svg +0 -5
- swarm/static/rest_mode/svg/copy.svg +0 -4
- swarm/static/rest_mode/svg/dark_mode.svg +0 -3
- swarm/static/rest_mode/svg/edit.svg +0 -5
- swarm/static/rest_mode/svg/layout.svg +0 -9
- swarm/static/rest_mode/svg/logo.svg +0 -29
- swarm/static/rest_mode/svg/logout.svg +0 -5
- swarm/static/rest_mode/svg/mobile.svg +0 -5
- swarm/static/rest_mode/svg/new_chat.svg +0 -4
- swarm/static/rest_mode/svg/not_visible.svg +0 -5
- swarm/static/rest_mode/svg/plus.svg +0 -7
- swarm/static/rest_mode/svg/run_code.svg +0 -6
- swarm/static/rest_mode/svg/save.svg +0 -4
- swarm/static/rest_mode/svg/search.svg +0 -6
- swarm/static/rest_mode/svg/settings.svg +0 -4
- swarm/static/rest_mode/svg/speaker.svg +0 -5
- swarm/static/rest_mode/svg/stop.svg +0 -6
- swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
- swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
- swarm/static/rest_mode/svg/toggle_off.svg +0 -6
- swarm/static/rest_mode/svg/toggle_on.svg +0 -6
- swarm/static/rest_mode/svg/trash.svg +0 -10
- swarm/static/rest_mode/svg/undo.svg +0 -3
- swarm/static/rest_mode/svg/visible.svg +0 -8
- swarm/static/rest_mode/svg/voice.svg +0 -10
- swarm/templates/account/login.html +0 -22
- swarm/templates/account/signup.html +0 -32
- swarm/templates/base.html +0 -30
- swarm/templates/chat.html +0 -43
- swarm/templates/index.html +0 -35
- swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
- swarm/templates/rest_mode/components/header.html +0 -45
- swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
- swarm/templates/rest_mode/components/settings_dialog.html +0 -97
- swarm/templates/rest_mode/components/splash_screen.html +0 -7
- swarm/templates/rest_mode/components/top_bar.html +0 -28
- swarm/templates/rest_mode/message_ui.html +0 -50
- swarm/templates/rest_mode/slackbot.html +0 -30
- swarm/templates/simple_blueprint_page.html +0 -24
- swarm/templates/websocket_partials/final_system_message.html +0 -3
- swarm/templates/websocket_partials/system_message.html +0 -4
- swarm/templates/websocket_partials/user_message.html +0 -5
- swarm/utils/ansi_box.py +0 -34
- swarm/utils/disable_tracing.py +0 -38
- swarm/utils/log_utils.py +0 -63
- swarm/utils/openai_patch.py +0 -33
- swarm/ux/ansi_box.py +0 -43
- swarm/ux/spinner.py +0 -53
- {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/licenses/LICENSE +0 -0
- /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
- /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
- /swarm/{core → extensions/config}/setup_wizard.py +0 -0
- /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
- /swarm/{core → extensions/config}/utils/logger.py +0 -0
- /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
@@ -1,448 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
RueCode Blueprint
|
3
|
-
|
4
|
-
Viral docstring update: Operational as of 2025-04-18T10:14:18Z (UTC).
|
5
|
-
Self-healing, fileops-enabled, swarm-scalable.
|
6
|
-
"""
|
7
|
-
import logging
|
8
|
-
import os
|
9
|
-
import sys
|
10
|
-
import json
|
11
|
-
import subprocess
|
12
|
-
from typing import Dict, List, Any, AsyncGenerator, Optional
|
13
|
-
from pathlib import Path
|
14
|
-
import re
|
15
|
-
from datetime import datetime
|
16
|
-
import pytz
|
17
|
-
from swarm.core.blueprint_ux import BlueprintUX
|
18
|
-
from swarm.core.config_loader import load_full_configuration
|
19
|
-
import time
|
20
|
-
from swarm.blueprints.common.operation_box_utils import display_operation_box
|
21
|
-
|
22
|
-
# Configure logging
|
23
|
-
logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(asctime)s - %(name)s - %(message)s')
|
24
|
-
logger = logging.getLogger(__name__)
|
25
|
-
|
26
|
-
# Last swarm update: {{ datetime.now(pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ') }}
|
27
|
-
# Patch: Expose underlying fileops functions for direct testing
|
28
|
-
class PatchedFunctionTool:
|
29
|
-
def __init__(self, func, name):
|
30
|
-
self.func = func
|
31
|
-
self.name = name
|
32
|
-
|
33
|
-
def read_file(path: str) -> str:
|
34
|
-
try:
|
35
|
-
with open(path, 'r') as f:
|
36
|
-
return f.read()
|
37
|
-
except Exception as e:
|
38
|
-
return f"ERROR: {e}"
|
39
|
-
def write_file(path: str, content: str) -> str:
|
40
|
-
try:
|
41
|
-
with open(path, 'w') as f:
|
42
|
-
f.write(content)
|
43
|
-
return "OK: file written"
|
44
|
-
except Exception as e:
|
45
|
-
return f"ERROR: {e}"
|
46
|
-
def list_files(directory: str = '.') -> str:
|
47
|
-
try:
|
48
|
-
return '\n'.join(os.listdir(directory))
|
49
|
-
except Exception as e:
|
50
|
-
return f"ERROR: {e}"
|
51
|
-
def execute_shell_command(command: str) -> str:
|
52
|
-
"""
|
53
|
-
Executes a shell command and returns its stdout and stderr.
|
54
|
-
Security Note: Ensure commands are properly sanitized or restricted.
|
55
|
-
Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 60s).
|
56
|
-
"""
|
57
|
-
logger.info(f"Executing shell command: {command}")
|
58
|
-
try:
|
59
|
-
import os
|
60
|
-
timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "60"))
|
61
|
-
result = subprocess.run(
|
62
|
-
command,
|
63
|
-
shell=True,
|
64
|
-
check=False, # Don't raise exception on non-zero exit code
|
65
|
-
stdout=subprocess.PIPE,
|
66
|
-
stderr=subprocess.PIPE,
|
67
|
-
text=True,
|
68
|
-
timeout=timeout
|
69
|
-
)
|
70
|
-
output = f"Exit Code: {result.returncode}\n"
|
71
|
-
if result.stdout:
|
72
|
-
output += f"STDOUT:\n{result.stdout}\n"
|
73
|
-
if result.stderr:
|
74
|
-
output += f"STDERR:\n{result.stderr}\n"
|
75
|
-
logger.info(f"Command finished. Exit Code: {result.returncode}")
|
76
|
-
return output.strip()
|
77
|
-
except subprocess.TimeoutExpired:
|
78
|
-
logger.error(f"Command timed out: {command}")
|
79
|
-
return f"Error: Command timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '60')} seconds."
|
80
|
-
except Exception as e:
|
81
|
-
logger.error(f"Error executing command '{command}': {e}", exc_info=True)
|
82
|
-
return f"Error executing command: {e}"
|
83
|
-
read_file_tool = PatchedFunctionTool(read_file, 'read_file')
|
84
|
-
write_file_tool = PatchedFunctionTool(write_file, 'write_file')
|
85
|
-
list_files_tool = PatchedFunctionTool(list_files, 'list_files')
|
86
|
-
execute_shell_command_tool = PatchedFunctionTool(execute_shell_command, 'execute_shell_command')
|
87
|
-
|
88
|
-
# Attempt to import BlueprintBase, handle potential ImportError during early setup/testing
|
89
|
-
try:
|
90
|
-
from swarm.core.blueprint_base import BlueprintBase
|
91
|
-
except ImportError as e:
|
92
|
-
logger.error(f"Import failed: {e}. Check 'openai-agents' install and project structure.")
|
93
|
-
# *** REMOVED sys.exit(1) ***
|
94
|
-
# Define a dummy class if import fails, allowing module to load for inspection/debugging
|
95
|
-
class BlueprintBase:
|
96
|
-
metadata = {}
|
97
|
-
def __init__(self, *args, **kwargs): pass
|
98
|
-
async def run(self, *args, **kwargs): yield {}
|
99
|
-
|
100
|
-
# --- Tool Definitions ---
|
101
|
-
|
102
|
-
def execute_shell_command(command: str) -> str:
|
103
|
-
"""
|
104
|
-
Executes a shell command and returns its stdout and stderr.
|
105
|
-
Security Note: Ensure commands are properly sanitized or restricted.
|
106
|
-
"""
|
107
|
-
logger.info(f"Executing shell command: {command}")
|
108
|
-
try:
|
109
|
-
result = subprocess.run(
|
110
|
-
command,
|
111
|
-
shell=True,
|
112
|
-
check=False, # Don't raise exception on non-zero exit code
|
113
|
-
stdout=subprocess.PIPE,
|
114
|
-
stderr=subprocess.PIPE,
|
115
|
-
text=True,
|
116
|
-
timeout=60 # Add a timeout
|
117
|
-
)
|
118
|
-
output = f"Exit Code: {result.returncode}\n"
|
119
|
-
if result.stdout:
|
120
|
-
output += f"STDOUT:\n{result.stdout}\n"
|
121
|
-
if result.stderr:
|
122
|
-
output += f"STDERR:\n{result.stderr}\n"
|
123
|
-
logger.info(f"Command finished. Exit Code: {result.returncode}")
|
124
|
-
return output.strip()
|
125
|
-
except subprocess.TimeoutExpired:
|
126
|
-
logger.error(f"Command timed out: {command}")
|
127
|
-
return "Error: Command timed out after 60 seconds."
|
128
|
-
except Exception as e:
|
129
|
-
logger.error(f"Error executing command '{command}': {e}", exc_info=True)
|
130
|
-
return f"Error executing command: {e}"
|
131
|
-
|
132
|
-
def read_file(file_path: str) -> str:
|
133
|
-
"""Reads the content of a specified file."""
|
134
|
-
logger.info(f"📄 Reading file: {file_path}")
|
135
|
-
try:
|
136
|
-
if ".." in file_path:
|
137
|
-
logger.warning(f"Attempted path traversal detected in read_file: {file_path}")
|
138
|
-
return "\033[91m❌ Error: Invalid file path (potential traversal).\033[0m"
|
139
|
-
path = Path(file_path)
|
140
|
-
if not path.is_file():
|
141
|
-
logger.warning(f"File not found: {file_path}")
|
142
|
-
return f"\033[91m❌ Error: File not found at {file_path}\033[0m"
|
143
|
-
content = path.read_text(encoding='utf-8')
|
144
|
-
logger.info(f"Successfully read {len(content)} characters from {file_path}")
|
145
|
-
max_len = 10000
|
146
|
-
if len(content) > max_len:
|
147
|
-
logger.warning(f"File {file_path} truncated to {max_len} characters.")
|
148
|
-
return f"\033[93m⚠️ {content[:max_len]}\n... [File Truncated]\033[0m"
|
149
|
-
return f"\033[92m✅ File read successfully!\033[0m\n\033[94m{content}\033[0m"
|
150
|
-
except Exception as e:
|
151
|
-
logger.error(f"Error reading file '{file_path}': {e}", exc_info=True)
|
152
|
-
return f"\033[91m❌ Error reading file: {e}\033[0m"
|
153
|
-
|
154
|
-
def write_file(file_path: str, content: str) -> str:
|
155
|
-
"""Writes content to a specified file, creating directories if needed."""
|
156
|
-
logger.info(f"✏️ Writing to file: {file_path}")
|
157
|
-
try:
|
158
|
-
if ".." in file_path:
|
159
|
-
logger.warning(f"Attempted path traversal detected in write_file: {file_path}")
|
160
|
-
return "\033[91m❌ Error: Invalid file path (potential traversal).\033[0m"
|
161
|
-
path = Path(file_path)
|
162
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
163
|
-
path.write_text(content, encoding='utf-8')
|
164
|
-
logger.info(f"Successfully wrote {len(content)} characters to {file_path}")
|
165
|
-
return f"\033[92m✅ Successfully wrote to {file_path}\033[0m"
|
166
|
-
except Exception as e:
|
167
|
-
logger.error(f"Error writing file '{file_path}': {e}", exc_info=True)
|
168
|
-
return f"\033[91m❌ Error writing file: {e}\033[0m"
|
169
|
-
|
170
|
-
def list_files(directory_path: str = ".") -> str:
|
171
|
-
"""Lists files and directories in a specified path."""
|
172
|
-
logger.info(f"Listing files in directory: {directory_path}")
|
173
|
-
try:
|
174
|
-
# Basic path traversal check
|
175
|
-
if ".." in directory_path:
|
176
|
-
logger.warning(f"Attempted path traversal detected in list_files: {directory_path}")
|
177
|
-
return "Error: Invalid directory path (potential traversal)."
|
178
|
-
# Consider restricting base path
|
179
|
-
|
180
|
-
path = Path(directory_path)
|
181
|
-
if not path.is_dir():
|
182
|
-
return f"Error: Directory not found at {directory_path}"
|
183
|
-
|
184
|
-
entries = []
|
185
|
-
for entry in path.iterdir():
|
186
|
-
entry_type = "d" if entry.is_dir() else "f"
|
187
|
-
entries.append(f"{entry_type} {entry.name}")
|
188
|
-
|
189
|
-
logger.info(f"Found {len(entries)} entries in {directory_path}")
|
190
|
-
return "\n".join(entries) if entries else "Directory is empty."
|
191
|
-
except Exception as e:
|
192
|
-
logger.error(f"Error listing files in '{directory_path}': {e}", exc_info=True)
|
193
|
-
return f"Error listing files: {e}"
|
194
|
-
|
195
|
-
# --- FileOps Tool Logic Definitions ---
|
196
|
-
def read_file_fileops(path: str) -> str:
|
197
|
-
try:
|
198
|
-
with open(path, 'r') as f:
|
199
|
-
return f.read()
|
200
|
-
except Exception as e:
|
201
|
-
return f"ERROR: {e}"
|
202
|
-
def write_file_fileops(path: str, content: str) -> str:
|
203
|
-
try:
|
204
|
-
with open(path, 'w') as f:
|
205
|
-
f.write(content)
|
206
|
-
return "OK: file written"
|
207
|
-
except Exception as e:
|
208
|
-
return f"ERROR: {e}"
|
209
|
-
def list_files_fileops(directory: str = '.') -> str:
|
210
|
-
try:
|
211
|
-
return '\n'.join(os.listdir(directory))
|
212
|
-
except Exception as e:
|
213
|
-
return f"ERROR: {e}"
|
214
|
-
def execute_shell_command_fileops(command: str) -> str:
|
215
|
-
import subprocess
|
216
|
-
try:
|
217
|
-
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
218
|
-
return result.stdout + result.stderr
|
219
|
-
except Exception as e:
|
220
|
-
return f"ERROR: {e}"
|
221
|
-
|
222
|
-
# --- LLM Cost Estimation Tool ---
|
223
|
-
def calculate_llm_cost(model: str, prompt_tokens: int, completion_tokens: int = 0, config: dict = None) -> float:
|
224
|
-
"""
|
225
|
-
Calculate the estimated cost (USD) for an LLM API call based on model and token usage.
|
226
|
-
Pricing is pulled from swarm_config.json if available, else defaults.
|
227
|
-
"""
|
228
|
-
# Default prices (per 1K tokens)
|
229
|
-
default_price = {'prompt': 0.002, 'completion': 0.004}
|
230
|
-
price = None
|
231
|
-
model_key = model.lower()
|
232
|
-
if config:
|
233
|
-
llm_config = config.get('llm', {})
|
234
|
-
for key, val in llm_config.items():
|
235
|
-
m = val.get('model', '').lower()
|
236
|
-
if m == model_key or key.lower() == model_key:
|
237
|
-
# Support both single cost (same for prompt/completion) or dict
|
238
|
-
if isinstance(val.get('cost'), dict):
|
239
|
-
price = val['cost']
|
240
|
-
elif 'cost' in val:
|
241
|
-
price = {'prompt': float(val['cost']), 'completion': float(val['cost'])}
|
242
|
-
break
|
243
|
-
if price is None:
|
244
|
-
price = default_price
|
245
|
-
cost = (prompt_tokens / 1000.0) * price['prompt'] + (completion_tokens / 1000.0) * price['completion']
|
246
|
-
return round(cost, 6)
|
247
|
-
|
248
|
-
def llm_cost_tool(model: str, prompt_tokens: int, completion_tokens: int = 0, config: dict = None) -> str:
|
249
|
-
try:
|
250
|
-
cost = calculate_llm_cost(model, prompt_tokens, completion_tokens, config)
|
251
|
-
return f"Estimated cost for {model}: ${cost} (prompt: {prompt_tokens}, completion: {completion_tokens} tokens)"
|
252
|
-
except Exception as e:
|
253
|
-
return f"Error: {e}"
|
254
|
-
|
255
|
-
llm_cost_tool_fn = PatchedFunctionTool(llm_cost_tool, 'llm_cost')
|
256
|
-
|
257
|
-
# --- RueCodeBlueprint Definition ---
|
258
|
-
# === OpenAI GPT-4.1 Prompt Engineering Guide ===
|
259
|
-
# See: https://github.com/openai/openai-cookbook/blob/main/examples/gpt4-1_prompting_guide.ipynb
|
260
|
-
#
|
261
|
-
# Agentic System Prompt Example (recommended for code generation/repair agents):
|
262
|
-
SYS_PROMPT_AGENTIC = """
|
263
|
-
You are an agent - please keep going until the user’s query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved.
|
264
|
-
If you are not sure about file content or codebase structure pertaining to the user’s request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer.
|
265
|
-
You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully.
|
266
|
-
"""
|
267
|
-
|
268
|
-
class RueSpinner:
|
269
|
-
FRAMES = ["Generating.", "Generating..", "Generating...", "Running..."]
|
270
|
-
LONG_WAIT_MSG = "Generating... Taking longer than expected"
|
271
|
-
INTERVAL = 0.12
|
272
|
-
SLOW_THRESHOLD = 10
|
273
|
-
|
274
|
-
def __init__(self):
|
275
|
-
self._idx = 0
|
276
|
-
self._start_time = None
|
277
|
-
self._last_frame = self.FRAMES[0]
|
278
|
-
|
279
|
-
def start(self):
|
280
|
-
self._start_time = time.time()
|
281
|
-
self._idx = 0
|
282
|
-
self._last_frame = self.FRAMES[0]
|
283
|
-
|
284
|
-
def _spin(self):
|
285
|
-
self._idx = (self._idx + 1) % len(self.FRAMES)
|
286
|
-
self._last_frame = self.FRAMES[self._idx]
|
287
|
-
|
288
|
-
def current_spinner_state(self):
|
289
|
-
if self._start_time and (time.time() - self._start_time) > self.SLOW_THRESHOLD:
|
290
|
-
return self.LONG_WAIT_MSG
|
291
|
-
return self._last_frame
|
292
|
-
|
293
|
-
class RueCodeBlueprint(BlueprintBase):
|
294
|
-
"""
|
295
|
-
A blueprint designed for code generation, execution, and file system interaction.
|
296
|
-
Uses Jinja2 for templating prompts and provides tools for shell commands and file operations.
|
297
|
-
"""
|
298
|
-
metadata = {
|
299
|
-
"name": "RueCode",
|
300
|
-
"description": "Generates, executes code, and interacts with the file system.",
|
301
|
-
"author": "Matthew Hand",
|
302
|
-
"version": "0.1.0",
|
303
|
-
"tags": ["code", "execution", "filesystem", "developer"],
|
304
|
-
"llm_profile": "default_dev" # Example: Suggests a profile suitable for coding
|
305
|
-
}
|
306
|
-
|
307
|
-
def __init__(self, blueprint_id: str = "rue_code", config=None, config_path=None, **kwargs):
|
308
|
-
super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
|
309
|
-
self.blueprint_id = blueprint_id
|
310
|
-
self.config_path = config_path
|
311
|
-
self._config = config if config is not None else None
|
312
|
-
self._llm_profile_name = None
|
313
|
-
self._llm_profile_data = None
|
314
|
-
self._markdown_output = None
|
315
|
-
# Use standardized config loader
|
316
|
-
if self._config is None:
|
317
|
-
self._config = load_full_configuration(
|
318
|
-
blueprint_class_name=self.__class__.__name__,
|
319
|
-
default_config_path=Path(os.path.dirname(__file__)).parent.parent.parent / 'swarm_config.json',
|
320
|
-
config_path_override=config_path,
|
321
|
-
profile_override=None,
|
322
|
-
cli_config_overrides=None
|
323
|
-
)
|
324
|
-
# Minimal LLM stub for demo
|
325
|
-
class DummyLLM:
|
326
|
-
def chat_completion_stream(self, messages, **_):
|
327
|
-
class DummyStream:
|
328
|
-
def __aiter__(self): return self
|
329
|
-
async def __anext__(self):
|
330
|
-
raise StopAsyncIteration
|
331
|
-
return DummyStream()
|
332
|
-
self.llm = DummyLLM()
|
333
|
-
# Use silly style for RueCode
|
334
|
-
self.ux = BlueprintUX(style="silly")
|
335
|
-
self.spinner = RueSpinner()
|
336
|
-
|
337
|
-
def render_prompt(self, template_name: str, context: dict) -> str:
|
338
|
-
# Minimal fallback: just format the user request directly for now
|
339
|
-
# (No Jinja2 dependency, just a stub for demo)
|
340
|
-
return f"User request: {context.get('user_request', '')}\nHistory: {context.get('history', '')}\nAvailable tools: {', '.join(context.get('available_tools', []))}"
|
341
|
-
|
342
|
-
def code_vs_semantic(self, label, results):
|
343
|
-
"""Format code or semantic results for display."""
|
344
|
-
return f"{label.title()} Results:\n" + "\n".join(f"- {r}" for r in results)
|
345
|
-
|
346
|
-
def summary(self, label, count, params):
|
347
|
-
return f"{label} ({count} results) for: {params}"
|
348
|
-
|
349
|
-
async def run(self, messages: List[Dict[str, str]]):
|
350
|
-
logger.info("RueCodeBlueprint run method called.")
|
351
|
-
last_user_message = next((m['content'] for m in reversed(messages) if m['role'] == 'user'), None)
|
352
|
-
if not last_user_message:
|
353
|
-
display_operation_box(
|
354
|
-
title="RueCode Error",
|
355
|
-
content="I need a user message to proceed.",
|
356
|
-
emoji="📝"
|
357
|
-
)
|
358
|
-
spinner_frames = ["Generating.", "Generating..", "Generating...", "Running..."]
|
359
|
-
for frame in spinner_frames:
|
360
|
-
yield frame
|
361
|
-
yield 'RueCode Error'
|
362
|
-
yield {"messages": [{"role": "assistant", "content": self.ux.box("Error", "I need a user message to proceed.")}]}
|
363
|
-
return
|
364
|
-
prompt_context = {
|
365
|
-
"user_request": last_user_message,
|
366
|
-
"history": messages[:-1],
|
367
|
-
"available_tools": ["rue_code"]
|
368
|
-
}
|
369
|
-
rendered_prompt = self.render_prompt("rue_code_prompt.j2", prompt_context)
|
370
|
-
self.spinner.start()
|
371
|
-
prompt_tokens = len(rendered_prompt) // 4
|
372
|
-
completion_tokens = 64
|
373
|
-
model = self._config.get('llm', {}).get('default', {}).get('model', 'gpt-3.5-turbo')
|
374
|
-
cost_str = llm_cost_tool(model, prompt_tokens, completion_tokens, self._config)
|
375
|
-
code_results = ["def foo(): ...", "def bar(): ..."]
|
376
|
-
semantic_results = ["This function sorts a list.", "This function calculates a sum."]
|
377
|
-
spinner_frames = ["Generating.", "Generating..", "Generating...", "Running..."]
|
378
|
-
for frame in spinner_frames:
|
379
|
-
yield frame
|
380
|
-
yield 'RueCode Code Results'
|
381
|
-
yield 'RueCode Semantic Results'
|
382
|
-
yield 'RueCode Summary'
|
383
|
-
for idx, label in enumerate(["code", "semantic"]):
|
384
|
-
self.spinner._spin()
|
385
|
-
display_operation_box(
|
386
|
-
title=f"RueCode {label.title()} Results",
|
387
|
-
content=self.code_vs_semantic(label, code_results if label=="code" else semantic_results),
|
388
|
-
style="bold cyan" if label=="code" else "bold magenta",
|
389
|
-
result_count=len(code_results if label=="code" else semantic_results),
|
390
|
-
params={"user_request": prompt_context["user_request"]},
|
391
|
-
spinner_state=self.spinner.current_spinner_state(),
|
392
|
-
progress_line=idx+1,
|
393
|
-
total_lines=2,
|
394
|
-
emoji="📝"
|
395
|
-
)
|
396
|
-
display_operation_box(
|
397
|
-
title="RueCode Summary",
|
398
|
-
content=f"{self.summary('Analyzed codebase', 4, prompt_context['user_request'])}\n\n{cost_str}",
|
399
|
-
emoji="📝"
|
400
|
-
)
|
401
|
-
yield {"messages": [{"role": "assistant", "content": self.ux.box(
|
402
|
-
"RueCode Results",
|
403
|
-
self.code_vs_semantic("code", code_results) + "\n" + self.code_vs_semantic("semantic", semantic_results) + f"\n\n{cost_str}",
|
404
|
-
summary=self.summary("Analyzed codebase", 4, prompt_context["user_request"])
|
405
|
-
)}]}
|
406
|
-
logger.info("RueCodeBlueprint run finished.")
|
407
|
-
|
408
|
-
if __name__ == "__main__":
|
409
|
-
import asyncio
|
410
|
-
import json
|
411
|
-
print("\033[1;36m\n╔══════════════════════════════════════════════════════════════╗\n║ 📝 RUE CODE: SWARM TEMPLATING & EXECUTION DEMO ║\n╠══════════════════════════════════════════════════════════════╣\n║ This blueprint demonstrates viral doc propagation, ║\n║ code templating, and swarm-powered execution. ║\n║ Try running: python blueprint_rue_code.py ║\n╚══════════════════════════════════════════════════════════════╝\033[0m")
|
412
|
-
messages = [
|
413
|
-
{"role": "user", "content": "Show me how Rue Code does templating and swarm execution."}
|
414
|
-
]
|
415
|
-
blueprint = RueCodeBlueprint(blueprint_id="demo-1")
|
416
|
-
async def run_and_print():
|
417
|
-
spinner = RueSpinner()
|
418
|
-
spinner.start()
|
419
|
-
try:
|
420
|
-
all_results = []
|
421
|
-
async for response in blueprint.run(messages):
|
422
|
-
content = response["messages"][0]["content"] if (isinstance(response, dict) and "messages" in response and response["messages"]) else str(response)
|
423
|
-
all_results.append(content)
|
424
|
-
# Enhanced progressive output
|
425
|
-
if isinstance(response, dict) and (response.get("progress") or response.get("matches")):
|
426
|
-
display_operation_box(
|
427
|
-
title="Progressive Operation",
|
428
|
-
content="\n".join(response.get("matches", [])),
|
429
|
-
style="bold cyan" if response.get("type") == "code_search" else "bold magenta",
|
430
|
-
result_count=len(response.get("matches", [])) if response.get("matches") is not None else None,
|
431
|
-
params={k: v for k, v in response.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
|
432
|
-
progress_line=response.get('progress'),
|
433
|
-
total_lines=response.get('total'),
|
434
|
-
spinner_state=spinner.current_spinner_state() if hasattr(spinner, 'current_spinner_state') else None,
|
435
|
-
op_type=response.get("type", "search"),
|
436
|
-
emoji="🔍" if response.get("type") == "code_search" else "🧠"
|
437
|
-
)
|
438
|
-
finally:
|
439
|
-
spinner.stop()
|
440
|
-
display_operation_box(
|
441
|
-
title="RueCode Output",
|
442
|
-
content="\n".join(all_results),
|
443
|
-
style="bold green",
|
444
|
-
result_count=len(all_results),
|
445
|
-
params={"prompt": messages[0]["content"]},
|
446
|
-
op_type="rue_code"
|
447
|
-
)
|
448
|
-
asyncio.run(run_and_print())
|
@@ -1,43 +0,0 @@
|
|
1
|
-
import argparse
|
2
|
-
from swarm.blueprints.rue_code.blueprint_rue_code import RueCodeBlueprint
|
3
|
-
from swarm.blueprints.common.operation_box_utils import display_operation_box
|
4
|
-
|
5
|
-
def main():
|
6
|
-
parser = argparse.ArgumentParser(description="RueCode: LLM code search/cost demo")
|
7
|
-
parser.add_argument("--message", type=str, help="User message to process", default="Show me code cost demo")
|
8
|
-
args = parser.parse_args()
|
9
|
-
bp = RueCodeBlueprint(blueprint_id="cli")
|
10
|
-
import asyncio
|
11
|
-
async def run():
|
12
|
-
async for result in bp.run([{"role": "user", "content": args.message}]):
|
13
|
-
if isinstance(result, dict) and "messages" in result:
|
14
|
-
print(result["messages"][0]["content"])
|
15
|
-
elif isinstance(result, str):
|
16
|
-
print(result)
|
17
|
-
elif isinstance(result, dict) and (result.get("matches") or result.get("progress")):
|
18
|
-
# Print the actual operation box for progressive output
|
19
|
-
display_operation_box(
|
20
|
-
title="Progressive Operation",
|
21
|
-
content="\n".join(result.get("matches", [])),
|
22
|
-
style="bold cyan" if result.get("type") == "code_search" else "bold magenta",
|
23
|
-
result_count=len(result.get("matches", [])) if result.get("matches") is not None else None,
|
24
|
-
params={k: v for k, v in result.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
|
25
|
-
progress_line=result.get('progress'),
|
26
|
-
total_lines=result.get('total'),
|
27
|
-
spinner_state=result.get('spinner_state'),
|
28
|
-
op_type=result.get("type", "search"),
|
29
|
-
emoji="🔍" if result.get("type") == "code_search" else "🧠"
|
30
|
-
)
|
31
|
-
else:
|
32
|
-
print(str(result))
|
33
|
-
asyncio.run(run())
|
34
|
-
|
35
|
-
if __name__ == "__main__":
|
36
|
-
import sys
|
37
|
-
if sys.argv[0].endswith("rue_code_cli.py") or sys.argv[0].endswith("rue_code_cli"): # legacy
|
38
|
-
print("[INFO] For future use, invoke this CLI as 'rue' instead of 'rue_code_cli'.")
|
39
|
-
main()
|
40
|
-
elif sys.argv[0].endswith("rue"): # preferred new name
|
41
|
-
main()
|
42
|
-
else:
|
43
|
-
main()
|
swarm/blueprints/stewie/apps.py
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
from django.apps import AppConfig
|
2
|
-
import logging
|
3
|
-
|
4
|
-
logger = logging.getLogger(__name__)
|
5
|
-
|
6
|
-
class StewieConfig(AppConfig):
|
7
|
-
default_auto_field = 'django.db.models.BigAutoField'
|
8
|
-
name = 'swarm.blueprints.stewie'
|
9
|
-
verbose_name = "Family Ties Blueprint"
|
10
|
-
|
11
|
-
def ready(self):
|
12
|
-
logger.debug(f"Registering {self.name} via AppConfig")
|