swarm_memory 2.1.5 → 2.1.6

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