schwarm-cli 0.1.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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/bin/schwarm +14 -0
  3. data/lib/schwarm_cli/client/agent_sessions.rb +16 -0
  4. data/lib/schwarm_cli/client/recurring_tasks.rb +33 -0
  5. data/lib/schwarm_cli/client/repositories.rb +37 -0
  6. data/lib/schwarm_cli/client/repository_agents.rb +37 -0
  7. data/lib/schwarm_cli/client/repository_skills.rb +25 -0
  8. data/lib/schwarm_cli/client/resource.rb +20 -0
  9. data/lib/schwarm_cli/client/secret_files.rb +29 -0
  10. data/lib/schwarm_cli/client/shared_agents.rb +33 -0
  11. data/lib/schwarm_cli/client/skill_files.rb +29 -0
  12. data/lib/schwarm_cli/client/skills.rb +29 -0
  13. data/lib/schwarm_cli/client/task_templates.rb +29 -0
  14. data/lib/schwarm_cli/client/tasks.rb +49 -0
  15. data/lib/schwarm_cli/client/user_messages.rb +11 -0
  16. data/lib/schwarm_cli/client.rb +101 -0
  17. data/lib/schwarm_cli/commands/agents.rb +92 -0
  18. data/lib/schwarm_cli/commands/base.rb +63 -0
  19. data/lib/schwarm_cli/commands/configure.rb +31 -0
  20. data/lib/schwarm_cli/commands/main.rb +64 -0
  21. data/lib/schwarm_cli/commands/recurring.rb +85 -0
  22. data/lib/schwarm_cli/commands/repo_skills.rb +50 -0
  23. data/lib/schwarm_cli/commands/repos.rb +86 -0
  24. data/lib/schwarm_cli/commands/secrets.rb +82 -0
  25. data/lib/schwarm_cli/commands/sessions.rb +30 -0
  26. data/lib/schwarm_cli/commands/shared_agents.rb +81 -0
  27. data/lib/schwarm_cli/commands/skill_files.rb +77 -0
  28. data/lib/schwarm_cli/commands/skills.rb +68 -0
  29. data/lib/schwarm_cli/commands/tasks.rb +146 -0
  30. data/lib/schwarm_cli/commands/templates.rb +58 -0
  31. data/lib/schwarm_cli/config.rb +46 -0
  32. data/lib/schwarm_cli/formatters/json.rb +13 -0
  33. data/lib/schwarm_cli/formatters/table.rb +39 -0
  34. data/lib/schwarm_cli/version.rb +5 -0
  35. data/lib/schwarm_cli.rb +12 -0
  36. metadata +148 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 485f701799de73d084bf43fff3cc0ebf1244a41397775baea03de793b860b4b9
4
+ data.tar.gz: 6125bc6e805189c3faa9ec3d33ff6545fd77c4c93ab966ccae142a2fc50102a5
5
+ SHA512:
6
+ metadata.gz: 1ef6550c32124fe3a2125dc2e79d10c05ec918bbabafd17918d1c13274954b33fae413cd679f58e5484c4b02b97bda64c6d495a62fb02d38e1afba389de7ac69
7
+ data.tar.gz: 2057d8334094ab913ebc115ed3818a54887cafa922688bcc66f54be4035322d51466ccd7610a1aad0b68f59ccb643604551a7b7e791168ba49f3be4a9e83dbf7
data/bin/schwarm ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/schwarm_cli"
5
+
6
+ begin
7
+ SchwarmCli::Commands::Main.start(ARGV)
8
+ rescue SchwarmCli::Error => e
9
+ warn "Error: #{e.message}"
10
+ exit 1
11
+ rescue Interrupt
12
+ warn "\nInterrupted"
13
+ exit 130
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class AgentSessions < Resource
6
+ def list(source: nil, status: nil, page: nil, per_page: nil)
7
+ params = { source:, status:, page:, per_page: }.compact
8
+ get("/api/v2/agent_sessions", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/agent_sessions/#{id}").body
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class RecurringTasks < Resource
6
+ def list(repository_id: nil, query: nil, page: nil, per_page: nil)
7
+ params = { repository_id:, q: query, page:, per_page: }.compact
8
+ get("/api/v2/recurring_tasks", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/recurring_tasks/#{id}").body
13
+ end
14
+
15
+ def create(**attributes)
16
+ post("/api/v2/recurring_tasks", { recurring_task: attributes }).body
17
+ end
18
+
19
+ def update(id, **attributes)
20
+ patch("/api/v2/recurring_tasks/#{id}", { recurring_task: attributes }).body
21
+ end
22
+
23
+ def destroy(id)
24
+ delete("/api/v2/recurring_tasks/#{id}")
25
+ nil
26
+ end
27
+
28
+ def toggle(id)
29
+ post("/api/v2/recurring_tasks/#{id}/toggle").body
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class Repositories < Resource
6
+ def list(status: nil, query: nil, page: nil, per_page: nil)
7
+ params = { status:, q: query, page:, per_page: }.compact
8
+ get("/api/v2/repositories", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/repositories/#{id}").body
13
+ end
14
+
15
+ def create(**attributes)
16
+ post("/api/v2/repositories", { repository: attributes }).body
17
+ end
18
+
19
+ def update(id, **attributes)
20
+ patch("/api/v2/repositories/#{id}", { repository: attributes }).body
21
+ end
22
+
23
+ def destroy(id)
24
+ delete("/api/v2/repositories/#{id}")
25
+ nil
26
+ end
27
+
28
+ def pause(id)
29
+ post("/api/v2/repositories/#{id}/pause").body
30
+ end
31
+
32
+ def resume(id)
33
+ post("/api/v2/repositories/#{id}/resume").body
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class RepositoryAgents < Resource
6
+ def list(repository_id: nil, query: nil, page: nil, per_page: nil)
7
+ params = { repository_id:, q: query, page:, per_page: }.compact
8
+ get("/api/v2/repository_agents", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/repository_agents/#{id}").body
13
+ end
14
+
15
+ def create(**attributes)
16
+ post("/api/v2/repository_agents", { repository_agent: attributes }).body
17
+ end
18
+
19
+ def update(id, **attributes)
20
+ patch("/api/v2/repository_agents/#{id}", { repository_agent: attributes }).body
21
+ end
22
+
23
+ def destroy(id)
24
+ delete("/api/v2/repository_agents/#{id}")
25
+ nil
26
+ end
27
+
28
+ def toggle(id)
29
+ post("/api/v2/repository_agents/#{id}/toggle").body
30
+ end
31
+
32
+ def run_now(id)
33
+ post("/api/v2/repository_agents/#{id}/run_now").body
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class RepositorySkills < Resource
6
+ def list(repository_id: nil, skill_id: nil, page: nil, per_page: nil)
7
+ params = { repository_id:, skill_id:, page:, per_page: }.compact
8
+ get("/api/v2/repository_skills", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/repository_skills/#{id}").body
13
+ end
14
+
15
+ def create(**attributes)
16
+ post("/api/v2/repository_skills", { repository_skill: attributes }).body
17
+ end
18
+
19
+ def destroy(id)
20
+ delete("/api/v2/repository_skills/#{id}")
21
+ nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class Resource
6
+ def initialize(client:)
7
+ @client = client
8
+ end
9
+
10
+ private
11
+
12
+ attr_reader :client
13
+
14
+ def get(...) = client.get(...)
15
+ def post(...) = client.post(...)
16
+ def patch(...) = client.patch(...)
17
+ def delete(...) = client.delete(...)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class SecretFiles < Resource
6
+ def list(repository_id: nil, page: nil, per_page: nil)
7
+ params = { repository_id:, page:, per_page: }.compact
8
+ get("/api/v2/secret_files", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/secret_files/#{id}").body
13
+ end
14
+
15
+ def create(**attributes)
16
+ post("/api/v2/secret_files", { secret_file: attributes }).body
17
+ end
18
+
19
+ def update(id, **attributes)
20
+ patch("/api/v2/secret_files/#{id}", { secret_file: attributes }).body
21
+ end
22
+
23
+ def destroy(id)
24
+ delete("/api/v2/secret_files/#{id}")
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class SharedAgents < Resource
6
+ def list(query: nil, page: nil, per_page: nil)
7
+ params = { q: query, page:, per_page: }.compact
8
+ get("/api/v2/shared_agents", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/shared_agents/#{id}").body
13
+ end
14
+
15
+ def create(**attributes)
16
+ post("/api/v2/shared_agents", { shared_agent: attributes }).body
17
+ end
18
+
19
+ def update(id, **attributes)
20
+ patch("/api/v2/shared_agents/#{id}", { shared_agent: attributes }).body
21
+ end
22
+
23
+ def destroy(id)
24
+ delete("/api/v2/shared_agents/#{id}")
25
+ nil
26
+ end
27
+
28
+ def toggle(id)
29
+ post("/api/v2/shared_agents/#{id}/toggle").body
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class SkillFiles < Resource
6
+ def list(skill_id:, page: nil, per_page: nil)
7
+ params = { page:, per_page: }.compact
8
+ get("/api/v2/skills/#{skill_id}/files", params).body
9
+ end
10
+
11
+ def find(skill_id:, id:)
12
+ get("/api/v2/skills/#{skill_id}/files/#{id}").body
13
+ end
14
+
15
+ def create(skill_id:, **attributes)
16
+ post("/api/v2/skills/#{skill_id}/files", { skill_file: attributes }).body
17
+ end
18
+
19
+ def update(skill_id:, id:, **attributes)
20
+ patch("/api/v2/skills/#{skill_id}/files/#{id}", { skill_file: attributes }).body
21
+ end
22
+
23
+ def destroy(skill_id:, id:)
24
+ delete("/api/v2/skills/#{skill_id}/files/#{id}")
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class Skills < Resource
6
+ def list(query: nil, page: nil, per_page: nil)
7
+ params = { q: query, page:, per_page: }.compact
8
+ get("/api/v2/skills", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/skills/#{id}").body
13
+ end
14
+
15
+ def create(**attributes)
16
+ post("/api/v2/skills", { skill: attributes }).body
17
+ end
18
+
19
+ def update(id, **attributes)
20
+ patch("/api/v2/skills/#{id}", { skill: attributes }).body
21
+ end
22
+
23
+ def destroy(id)
24
+ delete("/api/v2/skills/#{id}")
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class TaskTemplates < Resource
6
+ def list(query: nil, page: nil, per_page: nil)
7
+ params = { q: query, page:, per_page: }.compact
8
+ get("/api/v2/task_templates", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/task_templates/#{id}").body
13
+ end
14
+
15
+ def create(**attributes)
16
+ post("/api/v2/task_templates", { task_template: attributes }).body
17
+ end
18
+
19
+ def update(id, **attributes)
20
+ patch("/api/v2/task_templates/#{id}", { task_template: attributes }).body
21
+ end
22
+
23
+ def destroy(id)
24
+ delete("/api/v2/task_templates/#{id}")
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class Tasks < Resource
6
+ def list(status: nil, repository_id: nil, page: nil, per_page: nil, query: nil)
7
+ params = { status:, repository_id:, page:, per_page:, q: query }.compact
8
+ get("/api/v2/tasks", params).body
9
+ end
10
+
11
+ def find(id)
12
+ get("/api/v2/tasks/#{id}").body
13
+ end
14
+
15
+ def create(**attributes)
16
+ post("/api/v2/tasks", { task: attributes }).body
17
+ end
18
+
19
+ def update(id, **attributes)
20
+ patch("/api/v2/tasks/#{id}", { task: attributes }).body
21
+ end
22
+
23
+ def destroy(id)
24
+ delete("/api/v2/tasks/#{id}")
25
+ nil
26
+ end
27
+
28
+ def start(id)
29
+ post("/api/v2/tasks/#{id}/start").body
30
+ end
31
+
32
+ def archive(id)
33
+ post("/api/v2/tasks/#{id}/archive").body
34
+ end
35
+
36
+ def retry(id)
37
+ post("/api/v2/tasks/#{id}/retry").body
38
+ end
39
+
40
+ def reset(id)
41
+ post("/api/v2/tasks/#{id}/reset").body
42
+ end
43
+
44
+ def pause(id)
45
+ post("/api/v2/tasks/#{id}/pause").body
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ class Client
5
+ class UserMessages < Resource
6
+ def create(task_id:, content:)
7
+ post("/api/v2/tasks/#{task_id}/messages", { user_message: { content: } }).body
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/net_http_persistent"
5
+ require "faraday/retry"
6
+
7
+ require_relative "client/resource"
8
+ require_relative "client/tasks"
9
+ require_relative "client/repositories"
10
+ require_relative "client/skills"
11
+ require_relative "client/skill_files"
12
+ require_relative "client/task_templates"
13
+ require_relative "client/recurring_tasks"
14
+ require_relative "client/repository_agents"
15
+ require_relative "client/shared_agents"
16
+ require_relative "client/secret_files"
17
+ require_relative "client/agent_sessions"
18
+ require_relative "client/repository_skills"
19
+ require_relative "client/user_messages"
20
+
21
+ module SchwarmCli
22
+ class Client
23
+ attr_reader :tasks, :repositories, :skills, :skill_files, :templates,
24
+ :recurring, :agents, :shared_agents, :secrets, :sessions,
25
+ :repo_skills, :messages
26
+
27
+ def get(...) = conn.get(...)
28
+ def post(...) = conn.post(...)
29
+ def patch(...) = conn.patch(...)
30
+ def delete(...) = conn.delete(...)
31
+
32
+ def initialize(url: nil, api_key: nil, config_path: Config::DEFAULT_PATH)
33
+ config = Config.new(url:, api_key:, config_path:)
34
+ unless config.valid?
35
+ raise Error,
36
+ "Schwarm CLI not configured. Run `schwarm configure` or set SCHWARM_URL and SCHWARM_TOKEN."
37
+ end
38
+
39
+ @url = config.url
40
+ @api_key = config.api_key
41
+ init_resources
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :url, :api_key
47
+
48
+ def init_resources
49
+ @tasks = Tasks.new(client: self)
50
+ @repositories = Repositories.new(client: self)
51
+ @skills = Skills.new(client: self)
52
+ @skill_files = SkillFiles.new(client: self)
53
+ @templates = TaskTemplates.new(client: self)
54
+ @recurring = RecurringTasks.new(client: self)
55
+ @agents = RepositoryAgents.new(client: self)
56
+ @shared_agents = SharedAgents.new(client: self)
57
+ @secrets = SecretFiles.new(client: self)
58
+ @sessions = AgentSessions.new(client: self)
59
+ @repo_skills = RepositorySkills.new(client: self)
60
+ @messages = UserMessages.new(client: self)
61
+ end
62
+
63
+ def conn
64
+ @conn ||= Faraday.new(url:, headers:, **faraday_options) do |f|
65
+ f.request :json
66
+ f.request :authorization, "Bearer", api_key
67
+ f.request :retry, retry_options
68
+
69
+ f.response :json
70
+ f.response :raise_error
71
+
72
+ f.adapter :net_http_persistent
73
+ end
74
+ end
75
+
76
+ def headers
77
+ { "User-Agent" => "schwarm-cli/#{VERSION}" }
78
+ end
79
+
80
+ def faraday_options
81
+ {
82
+ request: {
83
+ open_timeout: 5,
84
+ read_timeout: 30,
85
+ write_timeout: 30
86
+ }
87
+ }
88
+ end
89
+
90
+ def retry_options
91
+ {
92
+ max: 3,
93
+ interval: 0.5,
94
+ backoff_factor: 2,
95
+ interval_randomness: 0.5,
96
+ retry_statuses: [429, 500, 502, 503, 504],
97
+ methods: %i[get head options]
98
+ }
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ module Commands
5
+ class Agents < Base
6
+ desc "list", "List repository agents"
7
+ option :repo, type: :string, desc: "Filter by repository ID"
8
+ option :query, type: :string, desc: "Search by name"
9
+ def list
10
+ handle_errors do
11
+ data = client.agents.list(repository_id: options[:repo], query: options[:query])
12
+ output_list(data, columns: [%w[ID id], %w[NAME name], %w[REPO github_repository_id],
13
+ %w[ENABLED enabled], %w[SCHEDULE schedule]])
14
+ end
15
+ end
16
+
17
+ desc "show ID", "Show agent details"
18
+ def show(id)
19
+ handle_errors do
20
+ data = client.agents.find(id)
21
+ output_record(data, fields: {
22
+ "ID" => "id", "Name" => "name", "Repository" => "github_repository_id",
23
+ "Enabled" => "enabled", "Schedule" => "schedule", "Prompt" => "prompt",
24
+ "Created" => "created_at", "Updated" => "updated_at"
25
+ })
26
+ end
27
+ end
28
+
29
+ desc "create", "Create a repository agent"
30
+ option :name, type: :string, required: true, desc: "Agent name"
31
+ option :repo, type: :string, required: true, desc: "Repository ID"
32
+ option :prompt, type: :string, required: true, desc: "Agent prompt"
33
+ option :schedule, type: :string, desc: "Cron schedule"
34
+ def create
35
+ handle_errors do
36
+ attrs = { name: options[:name], github_repository_id: options[:repo], prompt: options[:prompt] }
37
+ attrs[:schedule] = options[:schedule] if options[:schedule]
38
+
39
+ data = client.agents.create(**attrs)
40
+ output_record(data, fields: { "ID" => "id", "Name" => "name", "Enabled" => "enabled" })
41
+ end
42
+ end
43
+
44
+ desc "update ID", "Update a repository agent"
45
+ option :name, type: :string, desc: "Agent name"
46
+ option :prompt, type: :string, desc: "Agent prompt"
47
+ option :schedule, type: :string, desc: "Cron schedule"
48
+ def update(id)
49
+ handle_errors do
50
+ data = client.agents.update(id, **update_attrs)
51
+ output_record(data, fields: { "ID" => "id", "Name" => "name", "Enabled" => "enabled" })
52
+ end
53
+ end
54
+
55
+ desc "delete ID", "Delete a repository agent"
56
+ def delete(id)
57
+ handle_errors do
58
+ client.agents.destroy(id)
59
+ puts "Agent #{id} deleted."
60
+ end
61
+ end
62
+
63
+ desc "toggle ID", "Toggle agent enabled/disabled"
64
+ def toggle(id)
65
+ handle_errors do
66
+ data = client.agents.toggle(id)
67
+ status = data.dig("data", "enabled") ? "enabled" : "disabled"
68
+ puts "Agent #{id} #{status}."
69
+ end
70
+ end
71
+
72
+ desc "run-now ID", "Trigger agent to run immediately"
73
+ map "run-now" => :run_now
74
+ def run_now(id)
75
+ handle_errors do
76
+ client.agents.run_now(id)
77
+ puts "Agent #{id} triggered."
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def update_attrs
84
+ {}.tap do |attrs|
85
+ attrs[:name] = options[:name] if options[:name]
86
+ attrs[:prompt] = options[:prompt] if options[:prompt]
87
+ attrs[:schedule] = options[:schedule] if options[:schedule]
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module SchwarmCli
6
+ module Commands
7
+ class Base < Thor
8
+ class_option :json, type: :boolean, default: false, desc: "Output as JSON"
9
+ class_option :url, type: :string, desc: "Schwarm server URL (overrides config)"
10
+ class_option :token, type: :string, desc: "API key (overrides config)"
11
+
12
+ private
13
+
14
+ def client
15
+ @client ||= SchwarmCli::Client.new(url: options[:url], api_key: options[:token])
16
+ end
17
+
18
+ def output_list(data, columns:)
19
+ if options[:json]
20
+ Formatters::Json.format(data)
21
+ else
22
+ Formatters::Table.format_list(data["data"], columns:)
23
+ end
24
+ end
25
+
26
+ def output_record(data, fields:)
27
+ if options[:json]
28
+ Formatters::Json.format(data)
29
+ else
30
+ Formatters::Table.format_record(data["data"], fields:)
31
+ end
32
+ end
33
+
34
+ def handle_errors
35
+ yield
36
+ rescue Faraday::UnauthorizedError
37
+ abort "Error: Unauthorized. Check your API key or run `schwarm configure`."
38
+ rescue Faraday::ResourceNotFound
39
+ abort "Error: Not found."
40
+ rescue Faraday::UnprocessableEntityError => e
41
+ abort "Error: #{parse_error_body(e)}"
42
+ rescue Faraday::ClientError => e
43
+ abort "Error: #{e.message}"
44
+ rescue Faraday::ServerError
45
+ abort "Error: Server error. Try again later."
46
+ rescue Faraday::ConnectionFailed
47
+ abort "Error: Could not connect to Schwarm server."
48
+ rescue Faraday::TimeoutError
49
+ abort "Error: Request timed out."
50
+ end
51
+
52
+ def parse_error_body(error)
53
+ body = error.response&.dig(:body)
54
+ return "Validation failed" unless body.is_a?(String)
55
+
56
+ parsed = JSON.parse(body)
57
+ parsed.dig("error", "message") || "Validation failed"
58
+ rescue JSON::ParserError
59
+ "Validation failed"
60
+ end
61
+ end
62
+ end
63
+ end