swarm_memory 2.1.4 → 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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. data/lib/swarm_memory.rb +7 -2
  4. metadata +6 -185
  5. data/lib/claude_swarm/base_executor.rb +0 -133
  6. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  7. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  8. data/lib/claude_swarm/cli.rb +0 -697
  9. data/lib/claude_swarm/commands/ps.rb +0 -215
  10. data/lib/claude_swarm/commands/show.rb +0 -139
  11. data/lib/claude_swarm/configuration.rb +0 -373
  12. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  13. data/lib/claude_swarm/json_handler.rb +0 -91
  14. data/lib/claude_swarm/mcp_generator.rb +0 -243
  15. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  16. data/lib/claude_swarm/openai/executor.rb +0 -256
  17. data/lib/claude_swarm/openai/responses.rb +0 -319
  18. data/lib/claude_swarm/orchestrator.rb +0 -878
  19. data/lib/claude_swarm/process_tracker.rb +0 -78
  20. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  21. data/lib/claude_swarm/session_path.rb +0 -42
  22. data/lib/claude_swarm/settings_generator.rb +0 -77
  23. data/lib/claude_swarm/system_utils.rb +0 -46
  24. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  25. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  27. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  28. data/lib/claude_swarm/version.rb +0 -5
  29. data/lib/claude_swarm/worktree_manager.rb +0 -475
  30. data/lib/claude_swarm/yaml_loader.rb +0 -22
  31. data/lib/claude_swarm.rb +0 -67
  32. data/lib/swarm_cli/cli.rb +0 -201
  33. data/lib/swarm_cli/command_registry.rb +0 -61
  34. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  35. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  36. data/lib/swarm_cli/commands/migrate.rb +0 -55
  37. data/lib/swarm_cli/commands/run.rb +0 -173
  38. data/lib/swarm_cli/config_loader.rb +0 -98
  39. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  40. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  41. data/lib/swarm_cli/interactive_repl.rb +0 -924
  42. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  43. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  44. data/lib/swarm_cli/migrate_options.rb +0 -54
  45. data/lib/swarm_cli/migrator.rb +0 -132
  46. data/lib/swarm_cli/options.rb +0 -151
  47. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  48. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  49. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  50. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  51. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  52. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  53. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  54. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  55. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  56. data/lib/swarm_cli/ui/icons.rb +0 -36
  57. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  58. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  59. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  60. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  61. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  62. data/lib/swarm_cli/version.rb +0 -5
  63. data/lib/swarm_cli.rb +0 -46
  64. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  65. data/lib/swarm_sdk/agent/builder.rb +0 -552
  66. data/lib/swarm_sdk/agent/chat.rb +0 -774
  67. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  68. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  69. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  70. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  71. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  72. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  73. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  75. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  76. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  77. data/lib/swarm_sdk/agent/context.rb +0 -116
  78. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  79. data/lib/swarm_sdk/agent/definition.rb +0 -477
  80. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  81. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  82. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  83. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  84. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  85. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  86. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  87. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  88. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  89. data/lib/swarm_sdk/configuration.rb +0 -135
  90. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  91. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  92. data/lib/swarm_sdk/context_compactor.rb +0 -335
  93. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  94. data/lib/swarm_sdk/context_management/context.rb +0 -328
  95. data/lib/swarm_sdk/defaults.rb +0 -196
  96. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  97. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  98. data/lib/swarm_sdk/hooks/context.rb +0 -197
  99. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  100. data/lib/swarm_sdk/hooks/error.rb +0 -29
  101. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  102. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  103. data/lib/swarm_sdk/hooks/result.rb +0 -150
  104. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  105. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  106. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  107. data/lib/swarm_sdk/log_collector.rb +0 -227
  108. data/lib/swarm_sdk/log_stream.rb +0 -127
  109. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  110. data/lib/swarm_sdk/model_aliases.json +0 -8
  111. data/lib/swarm_sdk/models.json +0 -1
  112. data/lib/swarm_sdk/models.rb +0 -120
  113. data/lib/swarm_sdk/node_context.rb +0 -245
  114. data/lib/swarm_sdk/observer/builder.rb +0 -81
  115. data/lib/swarm_sdk/observer/config.rb +0 -45
  116. data/lib/swarm_sdk/observer/manager.rb +0 -236
  117. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  118. data/lib/swarm_sdk/permissions/config.rb +0 -239
  119. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  120. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  121. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  122. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  123. data/lib/swarm_sdk/plugin.rb +0 -309
  124. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  125. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  126. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  127. data/lib/swarm_sdk/restore_result.rb +0 -65
  128. data/lib/swarm_sdk/result.rb +0 -123
  129. data/lib/swarm_sdk/snapshot.rb +0 -156
  130. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  131. data/lib/swarm_sdk/state_restorer.rb +0 -476
  132. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  133. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  134. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  135. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  136. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  137. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  138. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  139. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  140. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  141. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  142. data/lib/swarm_sdk/swarm.rb +0 -717
  143. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  144. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  145. data/lib/swarm_sdk/tools/bash.rb +0 -282
  146. data/lib/swarm_sdk/tools/clock.rb +0 -44
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  160. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  161. data/lib/swarm_sdk/tools/read.rb +0 -261
  162. data/lib/swarm_sdk/tools/registry.rb +0 -205
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  166. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  167. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  168. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  169. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  170. data/lib/swarm_sdk/tools/think.rb +0 -98
  171. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  172. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  173. data/lib/swarm_sdk/tools/write.rb +0 -112
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  182. data/lib/swarm_sdk/workflow.rb +0 -554
  183. data/lib/swarm_sdk.rb +0 -524
  184. /data/lib/swarm_memory/{errors.rb → error.rb} +0 -0
@@ -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