swarm_memory 2.0.0
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/lib/claude_swarm/base_executor.rb +133 -0
- data/lib/claude_swarm/claude_code_executor.rb +349 -0
- data/lib/claude_swarm/claude_mcp_server.rb +77 -0
- data/lib/claude_swarm/cli.rb +712 -0
- data/lib/claude_swarm/commands/ps.rb +216 -0
- data/lib/claude_swarm/commands/show.rb +139 -0
- data/lib/claude_swarm/configuration.rb +363 -0
- data/lib/claude_swarm/hooks/session_start_hook.rb +42 -0
- data/lib/claude_swarm/json_handler.rb +91 -0
- data/lib/claude_swarm/mcp_generator.rb +248 -0
- data/lib/claude_swarm/openai/chat_completion.rb +264 -0
- data/lib/claude_swarm/openai/executor.rb +254 -0
- data/lib/claude_swarm/openai/responses.rb +338 -0
- data/lib/claude_swarm/orchestrator.rb +879 -0
- data/lib/claude_swarm/process_tracker.rb +78 -0
- data/lib/claude_swarm/session_cost_calculator.rb +209 -0
- data/lib/claude_swarm/session_path.rb +42 -0
- data/lib/claude_swarm/settings_generator.rb +77 -0
- data/lib/claude_swarm/system_utils.rb +46 -0
- data/lib/claude_swarm/templates/generation_prompt.md.erb +230 -0
- data/lib/claude_swarm/tools/reset_session_tool.rb +24 -0
- data/lib/claude_swarm/tools/session_info_tool.rb +24 -0
- data/lib/claude_swarm/tools/task_tool.rb +63 -0
- data/lib/claude_swarm/version.rb +5 -0
- data/lib/claude_swarm/worktree_manager.rb +475 -0
- data/lib/claude_swarm/yaml_loader.rb +22 -0
- data/lib/claude_swarm.rb +69 -0
- data/lib/swarm_cli/cli.rb +201 -0
- data/lib/swarm_cli/command_registry.rb +61 -0
- data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
- data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
- data/lib/swarm_cli/commands/migrate.rb +55 -0
- data/lib/swarm_cli/commands/run.rb +173 -0
- data/lib/swarm_cli/config_loader.rb +97 -0
- data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
- data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
- data/lib/swarm_cli/interactive_repl.rb +918 -0
- data/lib/swarm_cli/mcp_serve_options.rb +44 -0
- data/lib/swarm_cli/mcp_tools_options.rb +59 -0
- data/lib/swarm_cli/migrate_options.rb +54 -0
- data/lib/swarm_cli/migrator.rb +132 -0
- data/lib/swarm_cli/options.rb +151 -0
- data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
- data/lib/swarm_cli/ui/components/content_block.rb +120 -0
- data/lib/swarm_cli/ui/components/divider.rb +57 -0
- data/lib/swarm_cli/ui/components/panel.rb +62 -0
- data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
- data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
- data/lib/swarm_cli/ui/formatters/number.rb +58 -0
- data/lib/swarm_cli/ui/formatters/text.rb +77 -0
- data/lib/swarm_cli/ui/formatters/time.rb +73 -0
- data/lib/swarm_cli/ui/icons.rb +59 -0
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
- data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
- data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
- data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
- data/lib/swarm_cli/version.rb +5 -0
- data/lib/swarm_cli.rb +45 -0
- data/lib/swarm_memory/adapters/base.rb +140 -0
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +789 -0
- data/lib/swarm_memory/chat_extension.rb +34 -0
- data/lib/swarm_memory/cli/commands.rb +306 -0
- data/lib/swarm_memory/core/entry.rb +37 -0
- data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
- data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
- data/lib/swarm_memory/core/path_normalizer.rb +75 -0
- data/lib/swarm_memory/core/semantic_index.rb +244 -0
- data/lib/swarm_memory/core/storage.rb +286 -0
- data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
- data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
- data/lib/swarm_memory/dsl/memory_config.rb +113 -0
- data/lib/swarm_memory/embeddings/embedder.rb +36 -0
- data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
- data/lib/swarm_memory/errors.rb +21 -0
- data/lib/swarm_memory/integration/cli_registration.rb +30 -0
- data/lib/swarm_memory/integration/configuration.rb +43 -0
- data/lib/swarm_memory/integration/registration.rb +31 -0
- data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
- data/lib/swarm_memory/optimization/analyzer.rb +244 -0
- data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
- data/lib/swarm_memory/prompts/memory.md.erb +109 -0
- data/lib/swarm_memory/prompts/memory_assistant.md.erb +139 -0
- data/lib/swarm_memory/prompts/memory_researcher.md.erb +201 -0
- data/lib/swarm_memory/prompts/memory_retrieval.md.erb +76 -0
- data/lib/swarm_memory/search/semantic_search.rb +112 -0
- data/lib/swarm_memory/search/text_search.rb +40 -0
- data/lib/swarm_memory/search/text_similarity.rb +80 -0
- data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
- data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
- data/lib/swarm_memory/tools/load_skill.rb +313 -0
- data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
- data/lib/swarm_memory/tools/memory_delete.rb +99 -0
- data/lib/swarm_memory/tools/memory_edit.rb +185 -0
- data/lib/swarm_memory/tools/memory_glob.rb +145 -0
- data/lib/swarm_memory/tools/memory_grep.rb +209 -0
- data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
- data/lib/swarm_memory/tools/memory_read.rb +123 -0
- data/lib/swarm_memory/tools/memory_write.rb +215 -0
- data/lib/swarm_memory/utils.rb +50 -0
- data/lib/swarm_memory/version.rb +5 -0
- data/lib/swarm_memory.rb +166 -0
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
- data/lib/swarm_sdk/agent/builder.rb +461 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
- data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
- data/lib/swarm_sdk/agent/chat.rb +1144 -0
- data/lib/swarm_sdk/agent/context.rb +112 -0
- data/lib/swarm_sdk/agent/context_manager.rb +309 -0
- data/lib/swarm_sdk/agent/definition.rb +556 -0
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
- data/lib/swarm_sdk/configuration.rb +296 -0
- data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
- data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
- data/lib/swarm_sdk/context_compactor.rb +340 -0
- data/lib/swarm_sdk/hooks/adapter.rb +359 -0
- data/lib/swarm_sdk/hooks/context.rb +197 -0
- data/lib/swarm_sdk/hooks/definition.rb +80 -0
- data/lib/swarm_sdk/hooks/error.rb +29 -0
- data/lib/swarm_sdk/hooks/executor.rb +146 -0
- data/lib/swarm_sdk/hooks/registry.rb +147 -0
- data/lib/swarm_sdk/hooks/result.rb +150 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
- data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
- data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
- data/lib/swarm_sdk/log_collector.rb +51 -0
- data/lib/swarm_sdk/log_stream.rb +69 -0
- data/lib/swarm_sdk/markdown_parser.rb +75 -0
- data/lib/swarm_sdk/model_aliases.json +5 -0
- data/lib/swarm_sdk/models.json +1 -0
- data/lib/swarm_sdk/models.rb +120 -0
- data/lib/swarm_sdk/node/agent_config.rb +49 -0
- data/lib/swarm_sdk/node/builder.rb +439 -0
- data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
- data/lib/swarm_sdk/node_context.rb +170 -0
- data/lib/swarm_sdk/node_orchestrator.rb +384 -0
- data/lib/swarm_sdk/permissions/config.rb +239 -0
- data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
- data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
- data/lib/swarm_sdk/permissions/validator.rb +173 -0
- data/lib/swarm_sdk/permissions_builder.rb +122 -0
- data/lib/swarm_sdk/plugin.rb +147 -0
- data/lib/swarm_sdk/plugin_registry.rb +101 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
- data/lib/swarm_sdk/result.rb +97 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
- data/lib/swarm_sdk/swarm/builder.rb +586 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +416 -0
- data/lib/swarm_sdk/swarm.rb +982 -0
- data/lib/swarm_sdk/tools/bash.rb +274 -0
- data/lib/swarm_sdk/tools/clock.rb +44 -0
- data/lib/swarm_sdk/tools/delegate.rb +164 -0
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
- data/lib/swarm_sdk/tools/edit.rb +150 -0
- data/lib/swarm_sdk/tools/glob.rb +158 -0
- data/lib/swarm_sdk/tools/grep.rb +228 -0
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
- data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
- data/lib/swarm_sdk/tools/read.rb +251 -0
- data/lib/swarm_sdk/tools/registry.rb +93 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
- data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
- data/lib/swarm_sdk/tools/think.rb +95 -0
- data/lib/swarm_sdk/tools/todo_write.rb +216 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
- data/lib/swarm_sdk/tools/write.rb +117 -0
- data/lib/swarm_sdk/utils.rb +50 -0
- data/lib/swarm_sdk/version.rb +5 -0
- data/lib/swarm_sdk.rb +167 -0
- metadata +313 -0
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeSwarm
|
|
4
|
+
class CLI < Thor
|
|
5
|
+
include SystemUtils
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def exit_on_failure?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc "start [CONFIG_FILE]", "Start a Claude Swarm from configuration file"
|
|
14
|
+
method_option :vibe,
|
|
15
|
+
type: :boolean,
|
|
16
|
+
default: false,
|
|
17
|
+
desc: "Run with --dangerously-skip-permissions for all instances"
|
|
18
|
+
method_option :prompt,
|
|
19
|
+
aliases: "-p",
|
|
20
|
+
type: :string,
|
|
21
|
+
desc: "Prompt to pass to the main Claude instance (non-interactive mode)"
|
|
22
|
+
method_option :interactive,
|
|
23
|
+
aliases: "-i",
|
|
24
|
+
type: :string,
|
|
25
|
+
desc: "Initial prompt for interactive mode"
|
|
26
|
+
method_option :stream_logs,
|
|
27
|
+
type: :boolean,
|
|
28
|
+
default: false,
|
|
29
|
+
desc: "Stream session logs to stdout (only works with -p)"
|
|
30
|
+
method_option :debug,
|
|
31
|
+
type: :boolean,
|
|
32
|
+
default: false,
|
|
33
|
+
desc: "Enable debug output"
|
|
34
|
+
method_option :worktree,
|
|
35
|
+
type: :string,
|
|
36
|
+
aliases: "-w",
|
|
37
|
+
desc: "Create instances in Git worktrees with the given name (auto-generated if true)",
|
|
38
|
+
banner: "[NAME]"
|
|
39
|
+
method_option :session_id,
|
|
40
|
+
type: :string,
|
|
41
|
+
desc: "Use a specific session ID instead of generating one"
|
|
42
|
+
method_option :root_dir,
|
|
43
|
+
type: :string,
|
|
44
|
+
desc: "Root directory for resolving relative paths (defaults to current directory)"
|
|
45
|
+
def start(config_file = nil)
|
|
46
|
+
# Set root directory early so it's available to all components
|
|
47
|
+
root_dir = options[:root_dir] || Dir.pwd
|
|
48
|
+
ENV["CLAUDE_SWARM_ROOT_DIR"] = File.expand_path(root_dir)
|
|
49
|
+
|
|
50
|
+
# Resolve config path relative to root directory
|
|
51
|
+
config_path = config_file || "claude-swarm.yml"
|
|
52
|
+
config_path = File.expand_path(config_path, root_dir)
|
|
53
|
+
|
|
54
|
+
unless File.exist?(config_path)
|
|
55
|
+
error("Configuration file not found: #{config_path}")
|
|
56
|
+
exit(1)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
say("Starting Claude Swarm from #{config_path}...") unless options[:prompt]
|
|
60
|
+
|
|
61
|
+
# Validate stream_logs option
|
|
62
|
+
if options[:stream_logs] && !options[:prompt]
|
|
63
|
+
error("--stream-logs can only be used with -p/--prompt")
|
|
64
|
+
exit(1)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Validate conflicting options
|
|
68
|
+
if options[:prompt] && options[:interactive]
|
|
69
|
+
error("Cannot use both -p/--prompt and -i/--interactive")
|
|
70
|
+
exit(1)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
begin
|
|
74
|
+
config = Configuration.new(config_path, base_dir: ClaudeSwarm.root_dir, options: options)
|
|
75
|
+
generator = McpGenerator.new(config, vibe: options[:vibe])
|
|
76
|
+
orchestrator = Orchestrator.new(
|
|
77
|
+
config,
|
|
78
|
+
generator,
|
|
79
|
+
vibe: options[:vibe],
|
|
80
|
+
prompt: options[:prompt],
|
|
81
|
+
interactive_prompt: options[:interactive],
|
|
82
|
+
stream_logs: options[:stream_logs],
|
|
83
|
+
debug: options[:debug],
|
|
84
|
+
worktree: options[:worktree],
|
|
85
|
+
session_id: options[:session_id],
|
|
86
|
+
)
|
|
87
|
+
orchestrator.start
|
|
88
|
+
rescue Error => e
|
|
89
|
+
error(e.message)
|
|
90
|
+
exit(1)
|
|
91
|
+
rescue StandardError => e
|
|
92
|
+
error("Unexpected error: #{e.message}")
|
|
93
|
+
error(e.backtrace.join("\n")) if options[:verbose]
|
|
94
|
+
exit(1)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
desc "mcp-serve", "Start an MCP server for a Claude instance"
|
|
99
|
+
method_option :name,
|
|
100
|
+
aliases: "-n",
|
|
101
|
+
type: :string,
|
|
102
|
+
required: true,
|
|
103
|
+
desc: "Instance name"
|
|
104
|
+
method_option :directory,
|
|
105
|
+
aliases: "-d",
|
|
106
|
+
type: :string,
|
|
107
|
+
required: true,
|
|
108
|
+
desc: "Working directory for the instance"
|
|
109
|
+
method_option :directories,
|
|
110
|
+
type: :array,
|
|
111
|
+
desc: "All directories (including main directory) for the instance"
|
|
112
|
+
method_option :model,
|
|
113
|
+
aliases: "-m",
|
|
114
|
+
type: :string,
|
|
115
|
+
required: true,
|
|
116
|
+
desc: "Claude model to use (e.g., opus, sonnet)"
|
|
117
|
+
method_option :prompt,
|
|
118
|
+
aliases: "-p",
|
|
119
|
+
type: :string,
|
|
120
|
+
desc: "System prompt for the instance"
|
|
121
|
+
method_option :description,
|
|
122
|
+
type: :string,
|
|
123
|
+
desc: "Description of the instance's role"
|
|
124
|
+
method_option :allowed_tools,
|
|
125
|
+
aliases: "-t",
|
|
126
|
+
type: :array,
|
|
127
|
+
desc: "Allowed tools for the instance"
|
|
128
|
+
method_option :disallowed_tools,
|
|
129
|
+
type: :array,
|
|
130
|
+
desc: "Disallowed tools for the instance"
|
|
131
|
+
method_option :connections,
|
|
132
|
+
type: :array,
|
|
133
|
+
desc: "Connections to other instances"
|
|
134
|
+
method_option :mcp_config_path,
|
|
135
|
+
type: :string,
|
|
136
|
+
desc: "Path to MCP configuration file"
|
|
137
|
+
method_option :debug,
|
|
138
|
+
type: :boolean,
|
|
139
|
+
default: false,
|
|
140
|
+
desc: "Enable debug output"
|
|
141
|
+
method_option :vibe,
|
|
142
|
+
type: :boolean,
|
|
143
|
+
default: false,
|
|
144
|
+
desc: "Run with --dangerously-skip-permissions"
|
|
145
|
+
method_option :calling_instance,
|
|
146
|
+
type: :string,
|
|
147
|
+
required: true,
|
|
148
|
+
desc: "Name of the instance that launched this MCP server"
|
|
149
|
+
method_option :calling_instance_id,
|
|
150
|
+
type: :string,
|
|
151
|
+
desc: "Unique ID of the instance that launched this MCP server"
|
|
152
|
+
method_option :instance_id,
|
|
153
|
+
type: :string,
|
|
154
|
+
desc: "Unique ID of this instance"
|
|
155
|
+
method_option :claude_session_id,
|
|
156
|
+
type: :string,
|
|
157
|
+
desc: "Claude session ID to resume"
|
|
158
|
+
method_option :provider,
|
|
159
|
+
type: :string,
|
|
160
|
+
desc: "Provider to use (claude or openai)"
|
|
161
|
+
method_option :temperature,
|
|
162
|
+
type: :numeric,
|
|
163
|
+
desc: "Temperature for OpenAI models"
|
|
164
|
+
method_option :api_version,
|
|
165
|
+
type: :string,
|
|
166
|
+
desc: "API version for OpenAI (chat_completion or responses)"
|
|
167
|
+
method_option :openai_token_env,
|
|
168
|
+
type: :string,
|
|
169
|
+
desc: "Environment variable name for OpenAI API key"
|
|
170
|
+
method_option :base_url,
|
|
171
|
+
type: :string,
|
|
172
|
+
desc: "Base URL for OpenAI API"
|
|
173
|
+
method_option :reasoning_effort,
|
|
174
|
+
type: :string,
|
|
175
|
+
desc: "Reasoning effort for OpenAI models"
|
|
176
|
+
def mcp_serve
|
|
177
|
+
# Validate reasoning_effort if provided
|
|
178
|
+
if options[:reasoning_effort]
|
|
179
|
+
# Only validate if provider is openai (or not specified, since it could be set elsewhere)
|
|
180
|
+
if options[:provider] && options[:provider] != "openai"
|
|
181
|
+
error("reasoning_effort is only supported for OpenAI models")
|
|
182
|
+
exit(1)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Validate it's used with an o-series model
|
|
186
|
+
model = options[:model]
|
|
187
|
+
unless model&.match?(ClaudeSwarm::Configuration::O_SERIES_MODEL_PATTERN)
|
|
188
|
+
error("reasoning_effort is only supported for o-series models (o1, o1 Preview, o1-mini, o1-pro, o3, o3-mini, o3-pro, o3-deep-research, o4-mini, o4-mini-deep-research, etc.)")
|
|
189
|
+
error("Current model: #{model}")
|
|
190
|
+
exit(1)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Validate the value
|
|
194
|
+
unless ClaudeSwarm::Configuration::VALID_REASONING_EFFORTS.include?(options[:reasoning_effort])
|
|
195
|
+
error("reasoning_effort must be 'low', 'medium', or 'high'")
|
|
196
|
+
exit(1)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Validate temperature is not used with o-series models
|
|
201
|
+
if options[:temperature] && options[:provider] == "openai"
|
|
202
|
+
model = options[:model]
|
|
203
|
+
if model&.match?(ClaudeSwarm::Configuration::O_SERIES_MODEL_PATTERN)
|
|
204
|
+
error("temperature parameter is not supported for o-series models (#{model})")
|
|
205
|
+
error("O-series models use deterministic reasoning and don't accept temperature settings")
|
|
206
|
+
exit(1)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
instance_config = {
|
|
211
|
+
name: options[:name],
|
|
212
|
+
directory: options[:directory],
|
|
213
|
+
directories: options[:directories] || [options[:directory]],
|
|
214
|
+
model: options[:model],
|
|
215
|
+
prompt: options[:prompt],
|
|
216
|
+
description: options[:description],
|
|
217
|
+
allowed_tools: options[:allowed_tools] || [],
|
|
218
|
+
disallowed_tools: options[:disallowed_tools] || [],
|
|
219
|
+
connections: options[:connections] || [],
|
|
220
|
+
mcp_config_path: options[:mcp_config_path],
|
|
221
|
+
vibe: options[:vibe] || false,
|
|
222
|
+
instance_id: options[:instance_id],
|
|
223
|
+
claude_session_id: options[:claude_session_id],
|
|
224
|
+
provider: options[:provider],
|
|
225
|
+
temperature: options[:temperature],
|
|
226
|
+
api_version: options[:api_version],
|
|
227
|
+
openai_token_env: options[:openai_token_env],
|
|
228
|
+
base_url: options[:base_url],
|
|
229
|
+
reasoning_effort: options[:reasoning_effort],
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
begin
|
|
233
|
+
server = ClaudeMcpServer.new(
|
|
234
|
+
instance_config,
|
|
235
|
+
calling_instance: options[:calling_instance],
|
|
236
|
+
calling_instance_id: options[:calling_instance_id],
|
|
237
|
+
debug: options[:debug],
|
|
238
|
+
)
|
|
239
|
+
server.start
|
|
240
|
+
rescue StandardError => e
|
|
241
|
+
error("Error starting MCP server: #{e.message}")
|
|
242
|
+
error(e.backtrace.join("\n")) if options[:debug]
|
|
243
|
+
exit(1)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
desc "init", "Initialize a new claude-swarm.yml configuration file"
|
|
248
|
+
method_option :force,
|
|
249
|
+
aliases: "-f",
|
|
250
|
+
type: :boolean,
|
|
251
|
+
default: false,
|
|
252
|
+
desc: "Overwrite existing configuration file"
|
|
253
|
+
def init
|
|
254
|
+
config_path = "claude-swarm.yml"
|
|
255
|
+
|
|
256
|
+
if File.exist?(config_path) && !options[:force]
|
|
257
|
+
error("Configuration file already exists: #{config_path}")
|
|
258
|
+
error("Use --force to overwrite")
|
|
259
|
+
exit(1)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
template = <<~YAML
|
|
263
|
+
version: 1
|
|
264
|
+
swarm:
|
|
265
|
+
name: "Swarm Name"
|
|
266
|
+
main: lead_developer
|
|
267
|
+
# before: # Optional: commands to run before launching swarm (executed in sequence)
|
|
268
|
+
# - "echo 'Setting up environment...'"
|
|
269
|
+
# - "npm install"
|
|
270
|
+
# - "docker-compose up -d"
|
|
271
|
+
instances:
|
|
272
|
+
lead_developer:
|
|
273
|
+
description: "Lead developer who coordinates the team and makes architectural decisions"
|
|
274
|
+
directory: .
|
|
275
|
+
model: sonnet
|
|
276
|
+
prompt: |
|
|
277
|
+
You are the lead developer coordinating the team
|
|
278
|
+
allowed_tools: [Read, Edit, Bash, Write]
|
|
279
|
+
# connections: [frontend_dev, backend_dev]
|
|
280
|
+
|
|
281
|
+
# Example instances (uncomment and modify as needed):
|
|
282
|
+
|
|
283
|
+
# frontend_dev:
|
|
284
|
+
# description: "Frontend developer specializing in React and modern web technologies"
|
|
285
|
+
# directory: ./frontend
|
|
286
|
+
# model: sonnet
|
|
287
|
+
# prompt: |
|
|
288
|
+
# You specialize in frontend development with React, TypeScript, and modern web technologies
|
|
289
|
+
# allowed_tools: [Read, Edit, Write, "Bash(npm:*)", "Bash(yarn:*)", "Bash(pnpm:*)"]
|
|
290
|
+
|
|
291
|
+
# backend_dev:
|
|
292
|
+
# description: |
|
|
293
|
+
# Backend developer focusing on APIs, databases, and server architecture
|
|
294
|
+
# directory: ../other-app/backend
|
|
295
|
+
# model: sonnet
|
|
296
|
+
# prompt: |
|
|
297
|
+
# You specialize in backend development, APIs, databases, and server architecture
|
|
298
|
+
# allowed_tools: [Read, Edit, Write, Bash]
|
|
299
|
+
|
|
300
|
+
# devops_engineer:
|
|
301
|
+
# description: "DevOps engineer managing infrastructure, CI/CD, and deployments"
|
|
302
|
+
# directory: .
|
|
303
|
+
# model: sonnet
|
|
304
|
+
# prompt: |
|
|
305
|
+
# You specialize in infrastrujcture, CI/CD, containerization, and deployment
|
|
306
|
+
# allowed_tools: [Read, Edit, Write, "Bash(docker:*)", "Bash(kubectl:*)", "Bash(terraform:*)"]
|
|
307
|
+
|
|
308
|
+
# qa_engineer:
|
|
309
|
+
# description: "QA engineer ensuring quality through comprehensive testing"
|
|
310
|
+
# directory: ./tests
|
|
311
|
+
# model: sonnet
|
|
312
|
+
# prompt: |
|
|
313
|
+
# You specialize in testing, quality assurance, and test automation
|
|
314
|
+
# allowed_tools: [Read, Edit, Write, Bash]
|
|
315
|
+
YAML
|
|
316
|
+
|
|
317
|
+
File.write(config_path, template)
|
|
318
|
+
say("Created #{config_path}", :green)
|
|
319
|
+
say("Edit this file to configure your swarm, then run 'claude-swarm' to start")
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
desc "generate", "Launch Claude to help generate a swarm configuration interactively"
|
|
323
|
+
method_option :output,
|
|
324
|
+
aliases: "-o",
|
|
325
|
+
type: :string,
|
|
326
|
+
desc: "Output file path for the generated configuration"
|
|
327
|
+
method_option :model,
|
|
328
|
+
aliases: "-m",
|
|
329
|
+
type: :string,
|
|
330
|
+
default: "sonnet",
|
|
331
|
+
desc: "Claude model to use for generation"
|
|
332
|
+
def generate
|
|
333
|
+
# Check if claude command exists
|
|
334
|
+
begin
|
|
335
|
+
system!("command -v claude > /dev/null 2>&1")
|
|
336
|
+
rescue Error
|
|
337
|
+
error("Claude CLI is not installed or not in PATH")
|
|
338
|
+
error("To install Claude CLI, visit: https://docs.anthropic.com/en/docs/claude-code")
|
|
339
|
+
exit(1)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Read README for context about claude-swarm capabilities
|
|
343
|
+
readme_path = File.join(__dir__, "../../README.md")
|
|
344
|
+
readme_content = File.exist?(readme_path) ? File.read(readme_path) : ""
|
|
345
|
+
|
|
346
|
+
# Build the pre-prompt
|
|
347
|
+
preprompt = build_generation_prompt(readme_content, options[:output])
|
|
348
|
+
|
|
349
|
+
# Launch Claude in interactive mode with the initial prompt
|
|
350
|
+
cmd = [
|
|
351
|
+
"claude",
|
|
352
|
+
"--model",
|
|
353
|
+
options[:model],
|
|
354
|
+
preprompt,
|
|
355
|
+
]
|
|
356
|
+
|
|
357
|
+
# Execute and let the user take over
|
|
358
|
+
exec(*cmd)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
desc "version", "Show Claude Swarm version"
|
|
362
|
+
def version
|
|
363
|
+
say("Claude Swarm #{VERSION}")
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
desc "ps", "List running Claude Swarm sessions"
|
|
367
|
+
def ps
|
|
368
|
+
Commands::Ps.new.execute
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
desc "show SESSION_ID", "Show detailed session information"
|
|
372
|
+
def show(session_id)
|
|
373
|
+
Commands::Show.new.execute(session_id)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
desc "clean", "Remove stale session symlinks and orphaned worktrees"
|
|
377
|
+
method_option :days,
|
|
378
|
+
aliases: "-d",
|
|
379
|
+
type: :numeric,
|
|
380
|
+
default: 7,
|
|
381
|
+
desc: "Remove sessions older than N days"
|
|
382
|
+
def clean
|
|
383
|
+
# Clean stale symlinks
|
|
384
|
+
cleaned_symlinks = clean_stale_symlinks(options[:days])
|
|
385
|
+
|
|
386
|
+
# Clean orphaned worktrees
|
|
387
|
+
cleaned_worktrees = clean_orphaned_worktrees(options[:days])
|
|
388
|
+
|
|
389
|
+
if cleaned_symlinks.positive? || cleaned_worktrees.positive?
|
|
390
|
+
say("Cleaned #{cleaned_symlinks} stale symlink#{"s" unless cleaned_symlinks == 1}", :green)
|
|
391
|
+
say("Cleaned #{cleaned_worktrees} orphaned worktree#{"s" unless cleaned_worktrees == 1}", :green)
|
|
392
|
+
else
|
|
393
|
+
say("No cleanup needed", :green)
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
desc "restore SESSION_ID", "Restore a previous session by ID"
|
|
398
|
+
def restore(session_id)
|
|
399
|
+
restore_session(session_id)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
desc "watch SESSION_ID", "Watch session logs"
|
|
403
|
+
method_option :lines,
|
|
404
|
+
aliases: "-n",
|
|
405
|
+
type: :numeric,
|
|
406
|
+
default: 100,
|
|
407
|
+
desc: "Number of lines to show initially"
|
|
408
|
+
def watch(session_id)
|
|
409
|
+
# Find session path
|
|
410
|
+
run_symlink = ClaudeSwarm.joined_run_dir(session_id)
|
|
411
|
+
session_path = if File.symlink?(run_symlink)
|
|
412
|
+
File.readlink(run_symlink)
|
|
413
|
+
else
|
|
414
|
+
# Search in sessions directory
|
|
415
|
+
Dir.glob(ClaudeSwarm.joined_sessions_dir("*", "*")).find do |path|
|
|
416
|
+
File.basename(path) == session_id
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
unless session_path && Dir.exist?(session_path)
|
|
421
|
+
error("Session not found: #{session_id}")
|
|
422
|
+
exit(1)
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
log_file = File.join(session_path, "session.log")
|
|
426
|
+
unless File.exist?(log_file)
|
|
427
|
+
error("Log file not found for session: #{session_id}")
|
|
428
|
+
exit(1)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
exec("tail", "-f", "-n", options[:lines].to_s, log_file)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
desc "list-sessions", "List all available Claude Swarm sessions"
|
|
435
|
+
method_option :limit,
|
|
436
|
+
aliases: "-l",
|
|
437
|
+
type: :numeric,
|
|
438
|
+
default: 10,
|
|
439
|
+
desc: "Maximum number of sessions to display"
|
|
440
|
+
def list_sessions
|
|
441
|
+
sessions_dir = ClaudeSwarm.joined_sessions_dir
|
|
442
|
+
unless Dir.exist?(sessions_dir)
|
|
443
|
+
say("No sessions found", :yellow)
|
|
444
|
+
return
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Find all sessions with MCP configs
|
|
448
|
+
sessions = []
|
|
449
|
+
Dir.glob("#{sessions_dir}/*/*/*.mcp.json").each do |mcp_path|
|
|
450
|
+
session_path = File.dirname(mcp_path)
|
|
451
|
+
session_id = File.basename(session_path)
|
|
452
|
+
project_name = File.basename(File.dirname(session_path))
|
|
453
|
+
|
|
454
|
+
# Skip if we've already processed this session
|
|
455
|
+
next if sessions.any? { |s| s[:path] == session_path }
|
|
456
|
+
|
|
457
|
+
# Try to load session info
|
|
458
|
+
config_file = File.join(session_path, "config.yml")
|
|
459
|
+
next unless File.exist?(config_file)
|
|
460
|
+
|
|
461
|
+
# Load the config to get swarm info
|
|
462
|
+
begin
|
|
463
|
+
config_data = YamlLoader.load_config_file(config_file)
|
|
464
|
+
swarm_name = config_data.dig("swarm", "name") || "Unknown"
|
|
465
|
+
main_instance = config_data.dig("swarm", "main") || "Unknown"
|
|
466
|
+
rescue ClaudeSwarm::Error => e
|
|
467
|
+
# Warn about corrupted config files but continue
|
|
468
|
+
say_error("⚠️ Skipping session #{session_id} - #{e.message}")
|
|
469
|
+
next
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
mcp_files = Dir.glob(File.join(session_path, "*.mcp.json"))
|
|
473
|
+
|
|
474
|
+
# Get creation time from directory
|
|
475
|
+
created_at = File.stat(session_path).ctime
|
|
476
|
+
|
|
477
|
+
sessions << {
|
|
478
|
+
path: session_path,
|
|
479
|
+
id: session_id,
|
|
480
|
+
project: project_name,
|
|
481
|
+
created_at: created_at,
|
|
482
|
+
main_instance: main_instance,
|
|
483
|
+
instances_count: mcp_files.size,
|
|
484
|
+
swarm_name: swarm_name,
|
|
485
|
+
config_path: config_file,
|
|
486
|
+
}
|
|
487
|
+
rescue StandardError
|
|
488
|
+
# Skip invalid manifests
|
|
489
|
+
next
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
if sessions.empty?
|
|
493
|
+
say("No sessions found", :yellow)
|
|
494
|
+
return
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Sort by creation time (newest first)
|
|
498
|
+
sessions.sort_by! { |s| -s[:created_at].to_i }
|
|
499
|
+
sessions = sessions.first(options[:limit])
|
|
500
|
+
|
|
501
|
+
# Display sessions
|
|
502
|
+
say("\nAvailable sessions (newest first):\n", :bold)
|
|
503
|
+
sessions.each do |session|
|
|
504
|
+
say("\n#{session[:project]}/#{session[:id]}", :green)
|
|
505
|
+
say(" Created: #{session[:created_at].strftime("%Y-%m-%d %H:%M:%S")}")
|
|
506
|
+
say(" Main: #{session[:main_instance]}")
|
|
507
|
+
say(" Instances: #{session[:instances_count]}")
|
|
508
|
+
say(" Swarm: #{session[:swarm_name]}")
|
|
509
|
+
say(" Config: #{session[:config_path]}", :cyan)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
say("\nTo resume a session, run:", :bold)
|
|
513
|
+
say(" claude-swarm restore <session-id>", :cyan)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
default_task :start
|
|
517
|
+
|
|
518
|
+
private
|
|
519
|
+
|
|
520
|
+
def error(message)
|
|
521
|
+
$stderr.puts(Thor::Shell::Color.new.set_color(message, :red))
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def restore_session(session_id)
|
|
525
|
+
say("Restoring session: #{session_id}", :green)
|
|
526
|
+
|
|
527
|
+
# Find the session path
|
|
528
|
+
session_path = find_session_path(session_id)
|
|
529
|
+
unless session_path
|
|
530
|
+
error("Session not found: #{session_id}")
|
|
531
|
+
exit(1)
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
begin
|
|
535
|
+
# Load session info from instance ID in MCP config
|
|
536
|
+
mcp_files = Dir.glob(File.join(session_path, "*.mcp.json"))
|
|
537
|
+
if mcp_files.empty?
|
|
538
|
+
error("No MCP configuration files found in session")
|
|
539
|
+
exit(1)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Load the configuration from the session directory
|
|
543
|
+
config_file = File.join(session_path, "config.yml")
|
|
544
|
+
|
|
545
|
+
unless File.exist?(config_file)
|
|
546
|
+
error("Configuration file not found in session")
|
|
547
|
+
exit(1)
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Change to the original root directory if it exists
|
|
551
|
+
root_dir_file = File.join(session_path, "root_directory")
|
|
552
|
+
if File.exist?(root_dir_file)
|
|
553
|
+
original_dir = File.read(root_dir_file).strip
|
|
554
|
+
if Dir.exist?(original_dir)
|
|
555
|
+
Dir.chdir(original_dir)
|
|
556
|
+
ENV["CLAUDE_SWARM_ROOT_DIR"] = original_dir
|
|
557
|
+
say("Changed to original directory: #{original_dir}", :green) unless options[:prompt]
|
|
558
|
+
else
|
|
559
|
+
error("Original directory no longer exists: #{original_dir}")
|
|
560
|
+
exit(1)
|
|
561
|
+
end
|
|
562
|
+
else
|
|
563
|
+
# If no root_directory file, use current directory
|
|
564
|
+
ENV["CLAUDE_SWARM_ROOT_DIR"] = Dir.pwd
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
config = Configuration.new(config_file, base_dir: ClaudeSwarm.root_dir)
|
|
568
|
+
|
|
569
|
+
# Load session metadata if it exists to check for worktree info
|
|
570
|
+
session_metadata_file = File.join(session_path, "session_metadata.json")
|
|
571
|
+
worktree_name = nil
|
|
572
|
+
metadata = JsonHandler.parse_file(session_metadata_file)
|
|
573
|
+
if metadata && metadata["worktree"] && metadata["worktree"]["enabled"]
|
|
574
|
+
worktree_name = metadata["worktree"]["name"]
|
|
575
|
+
say("Restoring with worktree: #{worktree_name}", :green) unless options[:prompt]
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Create orchestrator with restoration mode
|
|
579
|
+
generator = McpGenerator.new(config, vibe: options[:vibe], restore_session_path: session_path)
|
|
580
|
+
orchestrator = Orchestrator.new(
|
|
581
|
+
config,
|
|
582
|
+
generator,
|
|
583
|
+
vibe: options[:vibe],
|
|
584
|
+
prompt: options[:prompt],
|
|
585
|
+
stream_logs: options[:stream_logs],
|
|
586
|
+
debug: options[:debug],
|
|
587
|
+
restore_session_path: session_path,
|
|
588
|
+
worktree: worktree_name,
|
|
589
|
+
session_id: options[:session_id],
|
|
590
|
+
)
|
|
591
|
+
orchestrator.start
|
|
592
|
+
rescue StandardError => e
|
|
593
|
+
error("Failed to restore session: #{e.message}")
|
|
594
|
+
error(e.backtrace.join("\n")) if options[:debug]
|
|
595
|
+
exit(1)
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def find_session_path(session_id)
|
|
600
|
+
sessions_dir = ClaudeSwarm.joined_sessions_dir
|
|
601
|
+
|
|
602
|
+
# Search for the session ID in all projects
|
|
603
|
+
Dir.glob("#{sessions_dir}/*/#{session_id}").each do |path|
|
|
604
|
+
config_path = File.join(path, "config.yml")
|
|
605
|
+
return path if File.exist?(config_path)
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
nil
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def clean_stale_symlinks(days)
|
|
612
|
+
run_dir = ClaudeSwarm.joined_run_dir
|
|
613
|
+
return 0 unless Dir.exist?(run_dir)
|
|
614
|
+
|
|
615
|
+
cleaned = 0
|
|
616
|
+
Dir.glob("#{run_dir}/*").each do |symlink|
|
|
617
|
+
next unless File.symlink?(symlink)
|
|
618
|
+
|
|
619
|
+
begin
|
|
620
|
+
# Remove if target doesn't exist (stale)
|
|
621
|
+
unless File.exist?(File.readlink(symlink))
|
|
622
|
+
File.unlink(symlink)
|
|
623
|
+
cleaned += 1
|
|
624
|
+
next
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Remove if older than specified days
|
|
628
|
+
if File.stat(symlink).mtime < Time.now - (days * 86_400)
|
|
629
|
+
File.unlink(symlink)
|
|
630
|
+
cleaned += 1
|
|
631
|
+
end
|
|
632
|
+
rescue StandardError
|
|
633
|
+
# Skip problematic symlinks
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
cleaned
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
def clean_orphaned_worktrees(days)
|
|
641
|
+
worktrees_dir = ClaudeSwarm.joined_worktrees_dir
|
|
642
|
+
return 0 unless Dir.exist?(worktrees_dir)
|
|
643
|
+
|
|
644
|
+
sessions_dir = ClaudeSwarm.joined_sessions_dir
|
|
645
|
+
cleaned = 0
|
|
646
|
+
|
|
647
|
+
Dir.glob("#{worktrees_dir}/*").each do |session_worktree_dir|
|
|
648
|
+
session_id = File.basename(session_worktree_dir)
|
|
649
|
+
|
|
650
|
+
# Skip if session still exists
|
|
651
|
+
next if Dir.glob("#{sessions_dir}/*/#{session_id}").any? { |path| File.exist?(File.join(path, "config.yml")) }
|
|
652
|
+
|
|
653
|
+
# Check age of worktree directory
|
|
654
|
+
begin
|
|
655
|
+
if File.stat(session_worktree_dir).mtime < Time.now - (days * 86_400)
|
|
656
|
+
# Remove all git worktrees in this session directory
|
|
657
|
+
Dir.glob("#{session_worktree_dir}/*/*").each do |worktree_path|
|
|
658
|
+
next unless File.directory?(worktree_path)
|
|
659
|
+
|
|
660
|
+
# Try to find the git repo and remove the worktree properly
|
|
661
|
+
git_dir = File.join(worktree_path, ".git")
|
|
662
|
+
if File.exist?(git_dir)
|
|
663
|
+
# Read the gitdir file to find the repo
|
|
664
|
+
gitdir_content = File.read(git_dir).strip
|
|
665
|
+
if gitdir_content.start_with?("gitdir:")
|
|
666
|
+
repo_git_path = gitdir_content.sub("gitdir: ", "")
|
|
667
|
+
# Extract repo path from .git/worktrees path
|
|
668
|
+
repo_path = repo_git_path.split("/.git/worktrees/").first
|
|
669
|
+
|
|
670
|
+
# Try to remove worktree via git
|
|
671
|
+
system!(
|
|
672
|
+
"git",
|
|
673
|
+
"-C",
|
|
674
|
+
repo_path,
|
|
675
|
+
"worktree",
|
|
676
|
+
"remove",
|
|
677
|
+
worktree_path,
|
|
678
|
+
"--force",
|
|
679
|
+
out: File::NULL,
|
|
680
|
+
err: File::NULL,
|
|
681
|
+
)
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
# Force remove directory if it still exists
|
|
686
|
+
FileUtils.rm_rf(worktree_path)
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# Remove the session worktree directory
|
|
690
|
+
FileUtils.rm_rf(session_worktree_dir)
|
|
691
|
+
cleaned += 1
|
|
692
|
+
end
|
|
693
|
+
rescue StandardError => e
|
|
694
|
+
say("Warning: Failed to clean worktree directory #{session_worktree_dir}: #{e.message}", :yellow) if options[:debug]
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
cleaned
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
def build_generation_prompt(readme_content, output_file)
|
|
702
|
+
template_path = File.expand_path("templates/generation_prompt.md.erb", __dir__)
|
|
703
|
+
template = File.read(template_path)
|
|
704
|
+
<<~PROMPT
|
|
705
|
+
#{ERB.new(template, trim_mode: "-").result(binding)}
|
|
706
|
+
|
|
707
|
+
Start the conversation by greeting the user and asking: 'What kind of project would you like to create a Claude Swarm for?'
|
|
708
|
+
Say: 'I am ready to start'
|
|
709
|
+
PROMPT
|
|
710
|
+
end
|
|
711
|
+
end
|
|
712
|
+
end
|