schwarm-cli 0.1.6 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb4e9030ccc6a70a2d35c94e5c2db414bae4b56550ae21128b2e3f612b56b6cf
4
- data.tar.gz: 4c4bfd79d9b120f01764b4b482200757a5a3f4d1a1e8dba5c344486befebf48d
3
+ metadata.gz: 601a7ca203a5837a47d4bd0348300054e07fe3577a57c99865a64c282e310d8f
4
+ data.tar.gz: 857487dd8fa4d92e575466ebe1ed63c70fd3b1c744c6b041eb3d4c675c4657fc
5
5
  SHA512:
6
- metadata.gz: 326c318ea026081fb23c05cd552e5a78204af6f35dc96e2ff7d5da6c838d0b48646534ab914cc1bc6afa250844e6a6d2ab3895cf2da37fee44f671308c10fbc4
7
- data.tar.gz: c15ba6b8d00b97e44e8b0642ab88753423043cbb4eafad9593fc8fdba0a22bc3a67168576fe76f5165386316bfe14e505411471d08dca4eb6e474ddea25a9d97
6
+ metadata.gz: 899c50f55a6b91d9a8690603de5cbf7dc429b022f18198d205c3679eb6a97911e1fb5afe6bc96cb8ae76f852bb38edf9ca7ef82df245249e5ef1fc5e8188ccc9
7
+ data.tar.gz: 506dd09f7f837606a154cd61f0e2f79e7b231af0daa1e495115ba16b2e7c6fc5290dc101fd0c30ca8370132bc08178e39158295100a68c3a36a0e21e2079ca58
@@ -22,18 +22,23 @@ module SchwarmCli
22
22
  handle_errors do
23
23
  data = client.agents.find(id)
24
24
  output_record(data, fields: {
25
- "ID" => "id", "Name" => "name", "Repository" => "github_repository_id",
26
- "Enabled" => "enabled", "Schedule" => "schedule", "Prompt" => "prompt",
25
+ "ID" => "id", "Name" => "name", "Repository" => "github_repository_name",
26
+ "Shared Agent" => "shared_agent_id", "Enabled" => "enabled",
27
+ "Schedule" => "schedule",
28
+ "Additional Instructions" => "additional_instructions",
27
29
  "Created" => "created_at", "Updated" => "updated_at"
28
30
  })
29
31
  end
30
32
  end
31
33
 
32
- desc "create", "Create a repository agent"
33
- option :name, type: :string, required: true, desc: "Agent name"
34
+ desc "create", "Create a repository agent (subscribe a shared agent to a repo)"
34
35
  option :repo, type: :string, required: true, desc: "Repository ID, owner/repo, or GitHub URL"
35
- option :prompt, type: :string, required: true, desc: "Agent prompt"
36
- option :schedule, type: :string, desc: "Cron schedule"
36
+ option :shared_agent, type: :string, required: true,
37
+ desc: "Shared agent ID to subscribe to this repository"
38
+ option :additional_instructions, type: :string,
39
+ desc: "Per-repository additional instructions appended to the shared prompt"
40
+ option :enabled, type: :boolean, default: true,
41
+ desc: "Whether the agent runs (default: true)"
37
42
  def create
38
43
  handle_errors do
39
44
  data = client.agents.create(**create_attrs)
@@ -42,9 +47,9 @@ module SchwarmCli
42
47
  end
43
48
 
44
49
  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"
50
+ option :additional_instructions, type: :string,
51
+ desc: "Per-repository additional instructions"
52
+ option :enabled, type: :boolean, desc: "Whether the agent runs"
48
53
  def update(id)
49
54
  handle_errors do
50
55
  data = client.agents.update(id, **update_attrs)
@@ -82,18 +87,18 @@ module SchwarmCli
82
87
 
83
88
  def create_attrs
84
89
  attrs = {
85
- name: options[:name], github_repository_id: resolve_repo(options[:repo]),
86
- prompt: options[:prompt]
90
+ shared_agent_id: options[:shared_agent],
91
+ github_repository_id: resolve_repo(options[:repo]),
92
+ enabled: options[:enabled]
87
93
  }
88
- attrs[:schedule] = options[:schedule] if options[:schedule]
94
+ attrs[:additional_instructions] = options[:additional_instructions] if options[:additional_instructions]
89
95
  attrs
90
96
  end
91
97
 
92
98
  def update_attrs
93
99
  {}.tap do |attrs|
94
- attrs[:name] = options[:name] if options[:name]
95
- attrs[:prompt] = options[:prompt] if options[:prompt]
96
- attrs[:schedule] = options[:schedule] if options[:schedule]
100
+ attrs[:additional_instructions] = options[:additional_instructions] if options[:additional_instructions]
101
+ attrs[:enabled] = options[:enabled] unless options[:enabled].nil?
97
102
  end
98
103
  end
99
104
  end
@@ -9,6 +9,8 @@ module SchwarmCli
9
9
  class_option :url, type: :string, desc: "Schwarm server URL (overrides config)"
10
10
  class_option :token, type: :string, desc: "API key (overrides config)"
11
11
 
12
+ def self.exit_on_failure? = true
13
+
12
14
  DEFAULT_LIST_LIMIT = 20
13
15
 
14
16
  # Declares --limit, --page, --all options for the next method.
@@ -87,12 +89,12 @@ module SchwarmCli
87
89
  yield
88
90
  rescue Faraday::UnauthorizedError
89
91
  abort "Error: Unauthorized. Check your API key or run `schwarm configure`."
90
- rescue Faraday::ResourceNotFound
91
- abort "Error: Not found."
92
+ rescue Faraday::ResourceNotFound => e
93
+ abort "Error: #{parse_error_body(e, fallback: 'Not found.')}"
92
94
  rescue Faraday::UnprocessableEntityError => e
93
- abort "Error: #{parse_error_body(e)}"
95
+ abort "Error: #{parse_error_body(e, fallback: 'Validation failed')}"
94
96
  rescue Faraday::ClientError => e
95
- abort "Error: #{e.message}"
97
+ abort "Error: #{parse_error_body(e, fallback: e.message)}"
96
98
  rescue Faraday::ServerError
97
99
  abort "Error: Server error. Try again later."
98
100
  rescue Faraday::ConnectionFailed
@@ -101,14 +103,14 @@ module SchwarmCli
101
103
  abort "Error: Request timed out."
102
104
  end
103
105
 
104
- def parse_error_body(error)
106
+ def parse_error_body(error, fallback:)
105
107
  body = error.response&.dig(:body)
106
- return "Validation failed" unless body.is_a?(String)
108
+ return fallback unless body.is_a?(String) && !body.empty?
107
109
 
108
110
  parsed = JSON.parse(body)
109
- parsed.dig("error", "message") || "Validation failed"
111
+ parsed.dig("error", "message") || fallback
110
112
  rescue JSON::ParserError
111
- "Validation failed"
113
+ fallback
112
114
  end
113
115
  end
114
116
  end
@@ -17,6 +17,8 @@ require_relative "configure"
17
17
  module SchwarmCli
18
18
  module Commands
19
19
  class Main < Thor
20
+ def self.exit_on_failure? = true
21
+
20
22
  desc "tasks SUBCOMMAND", "Manage tasks"
21
23
  subcommand "tasks", Tasks
22
24
 
@@ -30,13 +32,13 @@ module SchwarmCli
30
32
  subcommand "skills", SkillsCmd
31
33
 
32
34
  desc "skill-files SUBCOMMAND", "Manage skill files"
33
- subcommand "skill-files", SkillFilesCmd
35
+ subcommand "skill_files", SkillFilesCmd
34
36
 
35
37
  desc "agents SUBCOMMAND", "Manage repository agents"
36
38
  subcommand "agents", Agents
37
39
 
38
40
  desc "shared-agents SUBCOMMAND", "Manage shared agents"
39
- subcommand "shared-agents", SharedAgentsCmd
41
+ subcommand "shared_agents", SharedAgentsCmd
40
42
 
41
43
  desc "recurring SUBCOMMAND", "Manage recurring tasks"
42
44
  subcommand "recurring", Recurring
@@ -48,7 +50,7 @@ module SchwarmCli
48
50
  subcommand "sessions", Sessions
49
51
 
50
52
  desc "repo-skills SUBCOMMAND", "Manage repository-skill associations"
51
- subcommand "repo-skills", RepoSkills
53
+ subcommand "repo_skills", RepoSkills
52
54
 
53
55
  desc "configure", "Set up Schwarm CLI configuration"
54
56
  subcommand "configure", Configure
@@ -13,7 +13,7 @@ module SchwarmCli
13
13
  client.recurring.list(repository_id: resolve_repo(options[:repo]), query: options[:query], **page_params)
14
14
  end
15
15
  output_list(data, columns: [%w[ID id], %w[NAME name], %w[REPO github_repository_name],
16
- %w[ENABLED enabled], %w[SCHEDULE schedule]])
16
+ %w[ENABLED enabled], %w[SCHEDULE cron_expression]])
17
17
  end
18
18
  end
19
19
 
@@ -23,7 +23,7 @@ module SchwarmCli
23
23
  data = client.recurring.find(id)
24
24
  output_record(data, fields: {
25
25
  "ID" => "id", "Name" => "name", "Repository" => "github_repository_id",
26
- "Enabled" => "enabled", "Schedule" => "schedule", "Prompt" => "prompt",
26
+ "Enabled" => "enabled", "Schedule" => "cron_expression", "Prompt" => "prompt",
27
27
  "Created" => "created_at", "Updated" => "updated_at"
28
28
  })
29
29
  end
@@ -38,11 +38,11 @@ module SchwarmCli
38
38
  handle_errors do
39
39
  attrs = {
40
40
  name: options[:name], github_repository_id: resolve_repo(options[:repo]),
41
- prompt: options[:prompt], schedule: options[:schedule]
41
+ prompt: options[:prompt], cron_expression: options[:schedule]
42
42
  }
43
43
 
44
44
  data = client.recurring.create(**attrs)
45
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Schedule" => "schedule" })
45
+ output_record(data, fields: { "ID" => "id", "Name" => "name", "Schedule" => "cron_expression" })
46
46
  end
47
47
  end
48
48
 
@@ -53,7 +53,7 @@ module SchwarmCli
53
53
  def update(id)
54
54
  handle_errors do
55
55
  data = client.recurring.update(id, **update_attrs)
56
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Schedule" => "schedule" })
56
+ output_record(data, fields: { "ID" => "id", "Name" => "name", "Schedule" => "cron_expression" })
57
57
  end
58
58
  end
59
59
 
@@ -80,7 +80,7 @@ module SchwarmCli
80
80
  {}.tap do |attrs|
81
81
  attrs[:name] = options[:name] if options[:name]
82
82
  attrs[:prompt] = options[:prompt] if options[:prompt]
83
- attrs[:schedule] = options[:schedule] if options[:schedule]
83
+ attrs[:cron_expression] = options[:schedule] if options[:schedule]
84
84
  end
85
85
  end
86
86
  end
@@ -64,7 +64,11 @@ module SchwarmCli
64
64
  def pause(id)
65
65
  handle_errors do
66
66
  data = client.repositories.pause(id)
67
- puts "Repository #{id} paused (#{data.dig('data', 'status')})."
67
+ if options[:json]
68
+ output_record(data, fields: {})
69
+ else
70
+ puts "Repository #{id} paused (#{data.dig('data', 'status')})."
71
+ end
68
72
  end
69
73
  end
70
74
 
@@ -72,7 +76,11 @@ module SchwarmCli
72
76
  def resume(id)
73
77
  handle_errors do
74
78
  data = client.repositories.resume(id)
75
- puts "Repository #{id} resumed (#{data.dig('data', 'status')})."
79
+ if options[:json]
80
+ output_record(data, fields: {})
81
+ else
82
+ puts "Repository #{id} resumed (#{data.dig('data', 'status')})."
83
+ end
76
84
  end
77
85
  end
78
86
 
@@ -11,50 +11,46 @@ module SchwarmCli
11
11
  data = fetch_paged do |page_params|
12
12
  client.secrets.list(repository_id: resolve_repo(options[:repo]), **page_params)
13
13
  end
14
- output_list(data, columns: [%w[ID id], %w[NAME name], %w[REPO github_repository_name],
15
- %w[PATH path]])
14
+ output_list(data, columns: [%w[ID id], %w[REPO github_repository_name], %w[PATH path]])
16
15
  end
17
16
  end
18
17
 
19
- desc "show ID", "Show secret file details"
18
+ desc "show ID", "Show secret file details (metadata only — content is never returned)"
20
19
  def show(id)
21
20
  handle_errors do
22
21
  data = client.secrets.find(id)
23
22
  output_record(data, fields: {
24
- "ID" => "id", "Name" => "name", "Repository" => "github_repository_id",
23
+ "ID" => "id", "Repository" => "github_repository_name",
25
24
  "Path" => "path", "Created" => "created_at", "Updated" => "updated_at"
26
25
  })
27
26
  end
28
27
  end
29
28
 
30
29
  desc "create", "Create a secret file"
31
- option :name, type: :string, required: true, desc: "Secret name"
32
30
  option :repo, type: :string, required: true, desc: "Repository ID, owner/repo, or GitHub URL"
33
31
  option :path, type: :string, required: true, desc: "File path in workspace"
34
- option :content, type: :string, desc: "Secret content"
35
- option :file, type: :string, desc: "Read content from file"
32
+ option :content, type: :string, desc: "Secret content (mutually exclusive with --file)"
33
+ option :file, type: :string, desc: "Read content from file (mutually exclusive with --content)"
36
34
  def create
37
35
  handle_errors do
38
- content = read_content
39
36
  attrs = {
40
- name: options[:name], github_repository_id: resolve_repo(options[:repo]),
41
- path: options[:path], content:
37
+ github_repository_id: resolve_repo(options[:repo]),
38
+ path: options[:path], content: read_content(required: true)
42
39
  }
43
40
 
44
41
  data = client.secrets.create(**attrs)
45
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Path" => "path" })
42
+ output_record(data, fields: { "ID" => "id", "Path" => "path" })
46
43
  end
47
44
  end
48
45
 
49
46
  desc "update ID", "Update a secret file"
50
- option :name, type: :string, desc: "Secret name"
51
47
  option :path, type: :string, desc: "File path in workspace"
52
- option :content, type: :string, desc: "Secret content"
53
- option :file, type: :string, desc: "Read content from file"
48
+ option :content, type: :string, desc: "Secret content (mutually exclusive with --file)"
49
+ option :file, type: :string, desc: "Read content from file (mutually exclusive with --content)"
54
50
  def update(id)
55
51
  handle_errors do
56
52
  data = client.secrets.update(id, **update_attrs)
57
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Path" => "path" })
53
+ output_record(data, fields: { "ID" => "id", "Path" => "path" })
58
54
  end
59
55
  end
60
56
 
@@ -69,19 +65,24 @@ module SchwarmCli
69
65
  private
70
66
 
71
67
  def update_attrs
72
- { name: options[:name], path: options[:path] }.compact.tap do |attrs|
73
- attrs[:content] = read_content if options[:content] || options[:file]
68
+ { path: options[:path] }.compact.tap do |attrs|
69
+ content = read_content(required: false)
70
+ attrs[:content] = content if content
74
71
  end
75
72
  end
76
73
 
77
- def read_content
78
- if options[:file]
79
- file_path = File.expand_path(options[:file])
80
- abort "Error: File not found: #{file_path}" unless File.exist?(file_path)
81
- File.read(file_path)
82
- else
83
- options[:content]
84
- end
74
+ def read_content(required:)
75
+ abort "Error: --content and --file are mutually exclusive." if options[:content] && options[:file]
76
+ return options[:content] if options[:content]
77
+ return read_file(options[:file]) if options[:file]
78
+
79
+ abort "Error: one of --content or --file is required." if required
80
+ end
81
+
82
+ def read_file(path)
83
+ expanded = File.expand_path(path)
84
+ abort "Error: File not found: #{expanded}" unless File.exist?(expanded)
85
+ File.read(expanded)
85
86
  end
86
87
  end
87
88
  end
@@ -27,7 +27,8 @@ module SchwarmCli
27
27
  data = client.sessions.find(id)
28
28
  output_record(data, fields: {
29
29
  "ID" => "id", "Source" => "source", "Status" => "status",
30
- "Prompt" => "prompt", "Result" => "result",
30
+ "External ID" => "external_id", "Model" => "model_id",
31
+ "Error" => "error_message",
31
32
  "Created" => "created_at", "Updated" => "updated_at"
32
33
  })
33
34
  end
@@ -21,19 +21,24 @@ module SchwarmCli
21
21
  data = client.skills.find(id)
22
22
  output_record(data, fields: {
23
23
  "ID" => "id", "Name" => "name", "Description" => "description",
24
- "Global" => "global", "Created" => "created_at"
24
+ "Instructions" => "instructions", "Global" => "global",
25
+ "Created" => "created_at"
25
26
  })
26
27
  end
27
28
  end
28
29
 
29
30
  desc "create", "Create a skill"
30
31
  option :name, type: :string, required: true, desc: "Skill name"
31
- option :description, type: :string, desc: "Skill description"
32
+ option :description, type: :string, required: true, desc: "Skill description"
33
+ option :instructions, type: :string, desc: "Skill instructions (inline)"
34
+ option :instructions_file, type: :string, desc: "Read instructions from file"
32
35
  option :global, type: :boolean, default: false, desc: "Make skill global"
33
36
  def create
34
37
  handle_errors do
35
- attrs = { name: options[:name], global: options[:global] }
36
- attrs[:description] = options[:description] if options[:description]
38
+ attrs = {
39
+ name: options[:name], description: options[:description],
40
+ instructions: read_instructions, global: options[:global]
41
+ }
37
42
 
38
43
  data = client.skills.create(**attrs)
39
44
  output_record(data, fields: { "ID" => "id", "Name" => "name", "Global" => "global" })
@@ -43,6 +48,8 @@ module SchwarmCli
43
48
  desc "update ID", "Update a skill"
44
49
  option :name, type: :string, desc: "Skill name"
45
50
  option :description, type: :string, desc: "Skill description"
51
+ option :instructions, type: :string, desc: "Skill instructions (inline)"
52
+ option :instructions_file, type: :string, desc: "Read instructions from file"
46
53
  option :global, type: :boolean, desc: "Make skill global"
47
54
  def update(id)
48
55
  handle_errors do
@@ -62,10 +69,24 @@ module SchwarmCli
62
69
  private
63
70
 
64
71
  def update_attrs
72
+ instructions = read_instructions
65
73
  { name: options[:name], description: options[:description] }.compact.tap do |attrs|
74
+ attrs[:instructions] = instructions if instructions
66
75
  attrs[:global] = options[:global] unless options[:global].nil?
67
76
  end
68
77
  end
78
+
79
+ def read_instructions
80
+ if options[:instructions] && options[:instructions_file]
81
+ abort "Error: --instructions and --instructions-file are mutually exclusive."
82
+ elsif options[:instructions_file]
83
+ file_path = File.expand_path(options[:instructions_file])
84
+ abort "Error: File not found: #{file_path}" unless File.exist?(file_path)
85
+ File.read(file_path)
86
+ else
87
+ options[:instructions]
88
+ end
89
+ end
69
90
  end
70
91
  end
71
92
  end
@@ -20,7 +20,8 @@ module SchwarmCli
20
20
  handle_errors do
21
21
  data = client.templates.find(id)
22
22
  output_record(data, fields: {
23
- "ID" => "id", "Name" => "name", "Prompt" => "prompt", "Created" => "created_at"
23
+ "ID" => "id", "Name" => "name", "Description" => "description",
24
+ "Prompt" => "prompt_prefix", "Created" => "created_at"
24
25
  })
25
26
  end
26
27
  end
@@ -28,9 +29,13 @@ module SchwarmCli
28
29
  desc "create", "Create a task template"
29
30
  option :name, type: :string, required: true, desc: "Template name"
30
31
  option :prompt, type: :string, required: true, desc: "Template prompt"
32
+ option :description, type: :string, desc: "Template description"
31
33
  def create
32
34
  handle_errors do
33
- data = client.templates.create(name: options[:name], prompt: options[:prompt])
35
+ attrs = { name: options[:name], prompt_prefix: options[:prompt] }
36
+ attrs[:description] = options[:description] if options[:description]
37
+
38
+ data = client.templates.create(**attrs)
34
39
  output_record(data, fields: { "ID" => "id", "Name" => "name" })
35
40
  end
36
41
  end
@@ -38,13 +43,10 @@ module SchwarmCli
38
43
  desc "update ID", "Update a task template"
39
44
  option :name, type: :string, desc: "Template name"
40
45
  option :prompt, type: :string, desc: "Template prompt"
46
+ option :description, type: :string, desc: "Template description"
41
47
  def update(id)
42
48
  handle_errors do
43
- attrs = {}
44
- attrs[:name] = options[:name] if options[:name]
45
- attrs[:prompt] = options[:prompt] if options[:prompt]
46
-
47
- data = client.templates.update(id, **attrs)
49
+ data = client.templates.update(id, **update_attrs)
48
50
  output_record(data, fields: { "ID" => "id", "Name" => "name" })
49
51
  end
50
52
  end
@@ -56,6 +58,16 @@ module SchwarmCli
56
58
  puts "Template #{id} deleted."
57
59
  end
58
60
  end
61
+
62
+ private
63
+
64
+ def update_attrs
65
+ {}.tap do |attrs|
66
+ attrs[:name] = options[:name] if options[:name]
67
+ attrs[:prompt_prefix] = options[:prompt] if options[:prompt]
68
+ attrs[:description] = options[:description] if options[:description]
69
+ end
70
+ end
59
71
  end
60
72
  end
61
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SchwarmCli
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.7"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schwarm-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Garrigues