schwarm-cli 0.1.7 → 0.1.9

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: 601a7ca203a5837a47d4bd0348300054e07fe3577a57c99865a64c282e310d8f
4
- data.tar.gz: 857487dd8fa4d92e575466ebe1ed63c70fd3b1c744c6b041eb3d4c675c4657fc
3
+ metadata.gz: 6eb34f4bf3e7d00ae361c8f3e2e92543b70a3cf607bca49c2621db6308ff7347
4
+ data.tar.gz: '081ef605e386eef7aa68d0f41646a16c270cf5760bfeb1b5f7270f4b3b5ecf7e'
5
5
  SHA512:
6
- metadata.gz: 899c50f55a6b91d9a8690603de5cbf7dc429b022f18198d205c3679eb6a97911e1fb5afe6bc96cb8ae76f852bb38edf9ca7ef82df245249e5ef1fc5e8188ccc9
7
- data.tar.gz: 506dd09f7f837606a154cd61f0e2f79e7b231af0daa1e495115ba16b2e7c6fc5290dc101fd0c30ca8370132bc08178e39158295100a68c3a36a0e21e2079ca58
6
+ metadata.gz: 259bfe442c01d5d6a7906fcbf474927013374d8283f7083552b5e30137603e94ae94d2f92c3211c173807236d37b188de25e45594179c108fb49562af521a903
7
+ data.tar.gz: 118a0a5418bd88cc5f17e6f06bd80221830fdc2d8b5a4e9a83276894cd64779bdc1b9015012f5e5f1068ee1805800260f8f9dbafbde9327247bf1853cd78b0d6
@@ -3,8 +3,8 @@
3
3
  module SchwarmCli
4
4
  class Client
5
5
  class SecretFiles < Resource
6
- def list(repository_id: nil, page: nil, per_page: nil)
7
- params = { repository_id:, page:, per_page: }.compact
6
+ def list(repository_id: nil, query: nil, page: nil, per_page: nil)
7
+ params = { repository_id:, q: query, page:, per_page: }.compact
8
8
  get("/api/v2/secret_files", params).body
9
9
  end
10
10
 
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "faraday/multipart"
4
+
3
5
  module SchwarmCli
4
6
  class Client
7
+ # The skill_files endpoint expects path as a top-level form param plus a
8
+ # multipart-attached `file` (Active Storage), NOT a JSON body wrapped in
9
+ # `{skill_file: {...}}`. Build a multipart form for create/update.
5
10
  class SkillFiles < Resource
6
11
  def list(skill_id:, page: nil, per_page: nil)
7
12
  params = { page:, per_page: }.compact
@@ -12,18 +17,34 @@ module SchwarmCli
12
17
  get("/api/v2/skills/#{skill_id}/files/#{id}").body
13
18
  end
14
19
 
15
- def create(skill_id:, **attributes)
16
- post("/api/v2/skills/#{skill_id}/files", { skill_file: attributes }).body
20
+ def create(skill_id:, path:, content:)
21
+ post("/api/v2/skills/#{skill_id}/files", multipart_body(path:, content:)).body
17
22
  end
18
23
 
19
- def update(skill_id:, id:, **attributes)
20
- patch("/api/v2/skills/#{skill_id}/files/#{id}", { skill_file: attributes }).body
24
+ def update(skill_id:, id:, path: nil, content: nil)
25
+ body = {}
26
+ body[:path] = path if path
27
+ body[:file] = file_part(path:, content:) if content
28
+ patch("/api/v2/skills/#{skill_id}/files/#{id}", body).body
21
29
  end
22
30
 
23
31
  def destroy(skill_id:, id:)
24
32
  delete("/api/v2/skills/#{skill_id}/files/#{id}")
25
33
  nil
26
34
  end
35
+
36
+ private
37
+
38
+ def multipart_body(path:, content:)
39
+ { path:, file: file_part(path:, content:) }
40
+ end
41
+
42
+ # Active Storage needs a real IO with a filename. Wrap the in-memory
43
+ # content in a StringIO and hand it to Faraday::Multipart::FilePart.
44
+ def file_part(path:, content:)
45
+ io = StringIO.new(content.to_s)
46
+ Faraday::Multipart::FilePart.new(io, "application/octet-stream", File.basename(path.to_s))
47
+ end
27
48
  end
28
49
  end
29
50
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "faraday"
4
+ require "faraday/multipart"
4
5
  require "faraday/net_http_persistent"
5
6
  require "faraday/retry"
6
7
 
@@ -21,7 +22,7 @@ require_relative "client/user_messages"
21
22
  module SchwarmCli
22
23
  class Client
23
24
  attr_reader :tasks, :repositories, :skills, :skill_files, :templates,
24
- :recurring, :agents, :shared_agents, :secrets, :sessions,
25
+ :recurring, :agents, :subscriptions, :secrets, :sessions,
25
26
  :repo_skills, :messages
26
27
 
27
28
  def get(...) = conn.get(...)
@@ -52,8 +53,8 @@ module SchwarmCli
52
53
  @skill_files = SkillFiles.new(client: self)
53
54
  @templates = TaskTemplates.new(client: self)
54
55
  @recurring = RecurringTasks.new(client: self)
55
- @agents = RepositoryAgents.new(client: self)
56
- @shared_agents = SharedAgents.new(client: self)
56
+ @agents = SharedAgents.new(client: self)
57
+ @subscriptions = RepositoryAgents.new(client: self)
57
58
  @secrets = SecretFiles.new(client: self)
58
59
  @sessions = AgentSessions.new(client: self)
59
60
  @repo_skills = RepositorySkills.new(client: self)
@@ -62,6 +63,7 @@ module SchwarmCli
62
63
 
63
64
  def conn
64
65
  @conn ||= Faraday.new(url:, headers:, **faraday_options) do |f|
66
+ f.request :multipart
65
67
  f.request :json
66
68
  f.request :authorization, "Bearer", api_key
67
69
  f.request :retry, retry_options
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ module Commands
5
+ class AgentSubscriptions < Base
6
+ desc "list", "List repository subscriptions"
7
+ option :repo, type: :string, desc: "Filter by repository ID, owner/repo, or GitHub URL"
8
+ option :query, type: :string, desc: "Search by name"
9
+ pagination_options
10
+ def list
11
+ handle_errors do
12
+ data = fetch_paged do |page_params|
13
+ client.subscriptions.list(repository_id: resolve_repo(options[:repo]), query: options[:query],
14
+ **page_params)
15
+ end
16
+ output_list(data, columns: [%w[ID id], %w[NAME name], %w[REPO github_repository_name],
17
+ %w[ENABLED enabled], %w[SCHEDULE schedule]])
18
+ end
19
+ end
20
+
21
+ desc "show ID", "Show subscription details"
22
+ def show(id)
23
+ handle_errors do
24
+ data = client.subscriptions.find(id)
25
+ output_record(data, fields: {
26
+ "ID" => "id", "Name" => "name", "Repository" => "github_repository_name",
27
+ "Agent" => "shared_agent_id", "Enabled" => "enabled",
28
+ "Schedule" => "schedule",
29
+ "Additional Instructions" => "additional_instructions",
30
+ "Created" => "created_at", "Updated" => "updated_at"
31
+ })
32
+ end
33
+ end
34
+
35
+ desc "create", "Subscribe an agent to a repository"
36
+ option :repo, type: :string, required: true, desc: "Repository ID, owner/repo, or GitHub URL"
37
+ option :agent, type: :string, required: true,
38
+ desc: "Agent ID to subscribe to this repository"
39
+ option :additional_instructions, type: :string,
40
+ desc: "Per-repository additional instructions appended to the shared prompt"
41
+ option :enabled, type: :boolean, default: false,
42
+ desc: "Whether the subscription is active (default: false; toggle to enable)"
43
+ def create
44
+ handle_errors do
45
+ data = client.subscriptions.create(**create_attrs)
46
+ output_record(data, fields: { "ID" => "id", "Name" => "name", "Enabled" => "enabled" })
47
+ end
48
+ end
49
+
50
+ desc "update ID", "Update a subscription"
51
+ option :additional_instructions, type: :string,
52
+ desc: "Per-repository additional instructions"
53
+ option :enabled, type: :boolean, desc: "Whether the subscription is active"
54
+ def update(id)
55
+ handle_errors do
56
+ data = client.subscriptions.update(id, **update_attrs)
57
+ output_record(data, fields: { "ID" => "id", "Name" => "name", "Enabled" => "enabled" })
58
+ end
59
+ end
60
+
61
+ desc "delete ID", "Delete a subscription"
62
+ def delete(id)
63
+ handle_errors do
64
+ client.subscriptions.destroy(id)
65
+ puts "Subscription #{id} deleted."
66
+ end
67
+ end
68
+
69
+ desc "toggle ID", "Toggle subscription enabled/disabled"
70
+ def toggle(id)
71
+ handle_errors do
72
+ data = client.subscriptions.toggle(id)
73
+ status = data.dig("data", "enabled") ? "enabled" : "disabled"
74
+ puts "Subscription #{id} #{status}."
75
+ end
76
+ end
77
+
78
+ desc "run-now ID", "Trigger subscription to run immediately"
79
+ map "run-now" => :run_now
80
+ def run_now(id)
81
+ handle_errors do
82
+ client.subscriptions.run_now(id)
83
+ puts "Subscription #{id} triggered."
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def create_attrs
90
+ attrs = {
91
+ shared_agent_id: options[:agent],
92
+ github_repository_id: resolve_repo(options[:repo]),
93
+ enabled: options[:enabled]
94
+ }
95
+ attrs[:additional_instructions] = options[:additional_instructions] if options[:additional_instructions]
96
+ attrs
97
+ end
98
+
99
+ def update_attrs
100
+ {}.tap do |attrs|
101
+ attrs[:additional_instructions] = options[:additional_instructions] if options[:additional_instructions]
102
+ attrs[:enabled] = options[:enabled] unless options[:enabled].nil?
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -1,19 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "agent_subscriptions"
4
+
3
5
  module SchwarmCli
4
6
  module Commands
5
7
  class Agents < Base
6
- desc "list", "List repository agents"
7
- option :repo, type: :string, desc: "Filter by repository ID, owner/repo, or GitHub URL"
8
+ desc "subscriptions SUBCOMMAND", "Manage repository subscriptions"
9
+ subcommand "subscriptions", AgentSubscriptions
10
+
11
+ desc "list", "List agents"
8
12
  option :query, type: :string, desc: "Search by name"
9
13
  pagination_options
10
14
  def list
11
15
  handle_errors do
12
16
  data = fetch_paged do |page_params|
13
- client.agents.list(repository_id: resolve_repo(options[:repo]), query: options[:query], **page_params)
17
+ client.agents.list(query: options[:query], **page_params)
14
18
  end
15
- output_list(data, columns: [%w[ID id], %w[NAME name], %w[REPO github_repository_name],
16
- %w[ENABLED enabled], %w[SCHEDULE schedule]])
19
+ output_list(data, columns: [%w[ID id], %w[NAME name], %w[ENABLED enabled],
20
+ %w[SCHEDULE cron_expression]])
17
21
  end
18
22
  end
19
23
 
@@ -21,43 +25,40 @@ module SchwarmCli
21
25
  def show(id)
22
26
  handle_errors do
23
27
  data = client.agents.find(id)
24
- output_record(data, fields: {
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",
29
- "Created" => "created_at", "Updated" => "updated_at"
30
- })
28
+ output_record(data, fields: agent_fields)
31
29
  end
32
30
  end
33
31
 
34
- desc "create", "Create a repository agent (subscribe a shared agent to a repo)"
35
- option :repo, type: :string, required: true, desc: "Repository ID, owner/repo, or GitHub URL"
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)"
32
+ desc "create", "Create an agent"
33
+ option :name, type: :string, required: true, desc: "Agent name"
34
+ option :prompt, type: :string, required: true, desc: "Agent prompt"
35
+ option :schedule, type: :string, required: true, desc: "Cron schedule (e.g. \"0 0 * * *\")"
36
+ option :enabled, type: :boolean, default: false,
37
+ desc: "Whether the agent runs (default: false; toggle to enable)"
42
38
  def create
43
39
  handle_errors do
44
- data = client.agents.create(**create_attrs)
45
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Enabled" => "enabled" })
40
+ attrs = {
41
+ name: options[:name], prompt: options[:prompt],
42
+ cron_expression: options[:schedule], enabled: options[:enabled]
43
+ }
44
+
45
+ data = client.agents.create(**attrs)
46
+ output_record(data, fields: agent_fields)
46
47
  end
47
48
  end
48
49
 
49
- desc "update ID", "Update a repository agent"
50
- option :additional_instructions, type: :string,
51
- desc: "Per-repository additional instructions"
52
- option :enabled, type: :boolean, desc: "Whether the agent runs"
50
+ desc "update ID", "Update an agent"
51
+ option :name, type: :string, desc: "Agent name"
52
+ option :prompt, type: :string, desc: "Agent prompt"
53
+ option :schedule, type: :string, desc: "Cron schedule"
53
54
  def update(id)
54
55
  handle_errors do
55
56
  data = client.agents.update(id, **update_attrs)
56
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Enabled" => "enabled" })
57
+ output_record(data, fields: agent_fields)
57
58
  end
58
59
  end
59
60
 
60
- desc "delete ID", "Delete a repository agent"
61
+ desc "delete ID", "Delete an agent"
61
62
  def delete(id)
62
63
  handle_errors do
63
64
  client.agents.destroy(id)
@@ -74,33 +75,23 @@ module SchwarmCli
74
75
  end
75
76
  end
76
77
 
77
- desc "run-now ID", "Trigger agent to run immediately"
78
- map "run-now" => :run_now
79
- def run_now(id)
80
- handle_errors do
81
- client.agents.run_now(id)
82
- puts "Agent #{id} triggered."
83
- end
84
- end
85
-
86
78
  private
87
79
 
88
- def create_attrs
89
- attrs = {
90
- shared_agent_id: options[:shared_agent],
91
- github_repository_id: resolve_repo(options[:repo]),
92
- enabled: options[:enabled]
93
- }
94
- attrs[:additional_instructions] = options[:additional_instructions] if options[:additional_instructions]
95
- attrs
96
- end
97
-
98
80
  def update_attrs
99
81
  {}.tap do |attrs|
100
- attrs[:additional_instructions] = options[:additional_instructions] if options[:additional_instructions]
101
- attrs[:enabled] = options[:enabled] unless options[:enabled].nil?
82
+ attrs[:name] = options[:name] if options[:name]
83
+ attrs[:prompt] = options[:prompt] if options[:prompt]
84
+ attrs[:cron_expression] = options[:schedule] if options[:schedule]
102
85
  end
103
86
  end
87
+
88
+ def agent_fields
89
+ {
90
+ "ID" => "id", "Name" => "name", "Enabled" => "enabled",
91
+ "Schedule" => "cron_expression", "Prompt" => "prompt",
92
+ "Created" => "created_at", "Updated" => "updated_at"
93
+ }
94
+ end
104
95
  end
105
96
  end
106
97
  end
@@ -5,12 +5,39 @@ require "thor"
5
5
  module SchwarmCli
6
6
  module Commands
7
7
  class Base < Thor
8
- class_option :json, type: :boolean, default: false, desc: "Output as JSON"
8
+ # No `default: false` on :json we need to distinguish "not provided"
9
+ # (nil) from "explicitly negated" (--no-json => false) so nested
10
+ # subcommands can opt out of an inherited --json (see options below).
11
+ class_option :json, type: :boolean, desc: "Output as JSON"
9
12
  class_option :url, type: :string, desc: "Schwarm server URL (overrides config)"
10
13
  class_option :token, type: :string, desc: "API key (overrides config)"
11
14
 
12
15
  def self.exit_on_failure? = true
13
16
 
17
+ # Thor parses class_options at each subcommand level independently, so
18
+ # `--json` at a doubly-nested invocation (`agents subscriptions list
19
+ # --json`) is consumed by the outer class and never reaches the inner.
20
+ # Merge parent_options for global flags (json, url, token) so they
21
+ # propagate transparently into nested subcommands.
22
+ #
23
+ # We can't use Hash#merge with a block because Thor returns a
24
+ # HashWithIndifferentAccess whose `merge` ignores the block argument
25
+ # (lib/thor/core_ext/hash_with_indifferent_access.rb:53). Walk the
26
+ # inherited keys explicitly so an explicit child value (including
27
+ # `false` from --no-json) wins over an inherited parent value.
28
+ INHERITED_OPTIONS = %w[json url token].freeze
29
+
30
+ no_commands do
31
+ def options
32
+ merged = super.dup
33
+ parent = parent_options || {}
34
+ INHERITED_OPTIONS.each do |key|
35
+ merged[key] = parent[key] if merged[key].nil? && !parent[key].nil?
36
+ end
37
+ merged
38
+ end
39
+ end
40
+
14
41
  DEFAULT_LIST_LIMIT = 20
15
42
 
16
43
  # Declares --limit, --page, --all options for the next method.
@@ -7,7 +7,6 @@ require_relative "templates"
7
7
  require_relative "skills"
8
8
  require_relative "skill_files"
9
9
  require_relative "agents"
10
- require_relative "shared_agents"
11
10
  require_relative "recurring"
12
11
  require_relative "secrets"
13
12
  require_relative "sessions"
@@ -34,12 +33,9 @@ module SchwarmCli
34
33
  desc "skill-files SUBCOMMAND", "Manage skill files"
35
34
  subcommand "skill_files", SkillFilesCmd
36
35
 
37
- desc "agents SUBCOMMAND", "Manage repository agents"
36
+ desc "agents SUBCOMMAND", "Manage agents and their repository subscriptions"
38
37
  subcommand "agents", Agents
39
38
 
40
- desc "shared-agents SUBCOMMAND", "Manage shared agents"
41
- subcommand "shared_agents", SharedAgentsCmd
42
-
43
39
  desc "recurring SUBCOMMAND", "Manage recurring tasks"
44
40
  subcommand "recurring", Recurring
45
41
 
@@ -21,11 +21,7 @@ module SchwarmCli
21
21
  def show(id)
22
22
  handle_errors do
23
23
  data = client.recurring.find(id)
24
- output_record(data, fields: {
25
- "ID" => "id", "Name" => "name", "Repository" => "github_repository_id",
26
- "Enabled" => "enabled", "Schedule" => "cron_expression", "Prompt" => "prompt",
27
- "Created" => "created_at", "Updated" => "updated_at"
28
- })
24
+ output_record(data, fields: recurring_fields)
29
25
  end
30
26
  end
31
27
 
@@ -34,15 +30,12 @@ module SchwarmCli
34
30
  option :repo, type: :string, required: true, desc: "Repository ID, owner/repo, or GitHub URL"
35
31
  option :prompt, type: :string, required: true, desc: "Task prompt"
36
32
  option :schedule, type: :string, required: true, desc: "Cron schedule"
33
+ option :enabled, type: :boolean, default: false,
34
+ desc: "Whether the recurring task fires (default: false; toggle to enable)"
37
35
  def create
38
36
  handle_errors do
39
- attrs = {
40
- name: options[:name], github_repository_id: resolve_repo(options[:repo]),
41
- prompt: options[:prompt], cron_expression: options[:schedule]
42
- }
43
-
44
- data = client.recurring.create(**attrs)
45
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Schedule" => "cron_expression" })
37
+ data = client.recurring.create(**create_attrs)
38
+ output_record(data, fields: recurring_fields)
46
39
  end
47
40
  end
48
41
 
@@ -53,7 +46,7 @@ module SchwarmCli
53
46
  def update(id)
54
47
  handle_errors do
55
48
  data = client.recurring.update(id, **update_attrs)
56
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Schedule" => "cron_expression" })
49
+ output_record(data, fields: recurring_fields)
57
50
  end
58
51
  end
59
52
 
@@ -76,6 +69,20 @@ module SchwarmCli
76
69
 
77
70
  private
78
71
 
72
+ def create_attrs
73
+ {
74
+ name: options[:name], github_repository_id: resolve_repo(options[:repo]),
75
+ prompt: options[:prompt], cron_expression: options[:schedule],
76
+ enabled: options[:enabled]
77
+ }
78
+ end
79
+
80
+ def recurring_fields
81
+ { "ID" => "id", "Name" => "name", "Repository" => "github_repository_id",
82
+ "Enabled" => "enabled", "Schedule" => "cron_expression", "Prompt" => "prompt",
83
+ "Created" => "created_at", "Updated" => "updated_at" }
84
+ end
85
+
79
86
  def update_attrs
80
87
  {}.tap do |attrs|
81
88
  attrs[:name] = options[:name] if options[:name]
@@ -23,10 +23,7 @@ module SchwarmCli
23
23
  def show(id)
24
24
  handle_errors do
25
25
  data = client.repo_skills.find(id)
26
- output_record(data, fields: {
27
- "ID" => "id", "Repository" => "github_repository_id",
28
- "Skill" => "skill_id", "Created" => "created_at"
29
- })
26
+ output_record(data, fields: repo_skill_fields)
30
27
  end
31
28
  end
32
29
 
@@ -38,8 +35,7 @@ module SchwarmCli
38
35
  data = client.repo_skills.create(
39
36
  github_repository_id: resolve_repo(options[:repo]), skill_id: options[:skill]
40
37
  )
41
- output_record(data, fields: { "ID" => "id", "Repository" => "github_repository_id",
42
- "Skill" => "skill_id" })
38
+ output_record(data, fields: repo_skill_fields)
43
39
  end
44
40
  end
45
41
 
@@ -50,6 +46,13 @@ module SchwarmCli
50
46
  puts "Repository-skill association #{id} deleted."
51
47
  end
52
48
  end
49
+
50
+ private
51
+
52
+ def repo_skill_fields
53
+ { "ID" => "id", "Repository" => "github_repository_name",
54
+ "Skill" => "skill_name", "Created" => "created_at" }
55
+ end
53
56
  end
54
57
  end
55
58
  end
@@ -5,11 +5,14 @@ module SchwarmCli
5
5
  class Secrets < Base
6
6
  desc "list", "List secret files"
7
7
  option :repo, type: :string, desc: "Filter by repository ID, owner/repo, or GitHub URL"
8
+ option :query, type: :string, desc: "Search by path"
8
9
  pagination_options
9
10
  def list
10
11
  handle_errors do
11
12
  data = fetch_paged do |page_params|
12
- client.secrets.list(repository_id: resolve_repo(options[:repo]), **page_params)
13
+ client.secrets.list(
14
+ repository_id: resolve_repo(options[:repo]), query: options[:query], **page_params
15
+ )
13
16
  end
14
17
  output_list(data, columns: [%w[ID id], %w[REPO github_repository_name], %w[PATH path]])
15
18
  end
@@ -3,9 +3,8 @@
3
3
  module SchwarmCli
4
4
  module Commands
5
5
  class SkillFilesCmd < Base
6
- class_option :skill, type: :string, required: true, desc: "Skill ID"
7
-
8
6
  desc "list", "List skill files"
7
+ option :skill, type: :string, required: true, desc: "Skill ID"
9
8
  pagination_options
10
9
  def list
11
10
  handle_errors do
@@ -17,39 +16,46 @@ module SchwarmCli
17
16
  end
18
17
 
19
18
  desc "show ID", "Show skill file details"
19
+ option :skill, type: :string, required: true, desc: "Skill ID"
20
20
  def show(id)
21
21
  handle_errors do
22
22
  data = client.skill_files.find(skill_id: options[:skill], id:)
23
- output_record(data, fields: {
24
- "ID" => "id", "Path" => "path", "Content" => "content", "Created" => "created_at"
25
- })
23
+ output_record(data, fields: skill_file_fields)
26
24
  end
27
25
  end
28
26
 
29
27
  desc "create", "Create a skill file"
30
- option :path, type: :string, required: true, desc: "File path"
31
- option :content, type: :string, desc: "File content"
32
- option :file, type: :string, desc: "Read content from file"
28
+ option :skill, type: :string, required: true, desc: "Skill ID"
29
+ option :path, type: :string, required: true, desc: "File path within the skill (e.g. SKILL.md)"
30
+ option :content, type: :string, desc: "File content (mutually exclusive with --file)"
31
+ option :file, type: :string, desc: "Read content from file (mutually exclusive with --content)"
33
32
  def create
34
33
  handle_errors do
35
- content = read_content
36
- data = client.skill_files.create(skill_id: options[:skill], path: options[:path], content:)
37
- output_record(data, fields: { "ID" => "id", "Path" => "path" })
34
+ data = client.skill_files.create(
35
+ skill_id: options[:skill], path: options[:path],
36
+ content: read_content(required: true)
37
+ )
38
+ output_record(data, fields: skill_file_fields)
38
39
  end
39
40
  end
40
41
 
41
42
  desc "update ID", "Update a skill file"
43
+ option :skill, type: :string, required: true, desc: "Skill ID"
42
44
  option :path, type: :string, desc: "File path"
43
- option :content, type: :string, desc: "File content"
44
- option :file, type: :string, desc: "Read content from file"
45
+ option :content, type: :string, desc: "File content (mutually exclusive with --file)"
46
+ option :file, type: :string, desc: "Read content from file (mutually exclusive with --content)"
45
47
  def update(id)
46
48
  handle_errors do
47
- data = client.skill_files.update(skill_id: options[:skill], id:, **update_attrs)
48
- output_record(data, fields: { "ID" => "id", "Path" => "path" })
49
+ data = client.skill_files.update(
50
+ skill_id: options[:skill], id:,
51
+ path: options[:path], content: read_content(required: false)
52
+ )
53
+ output_record(data, fields: skill_file_fields)
49
54
  end
50
55
  end
51
56
 
52
57
  desc "delete ID", "Delete a skill file"
58
+ option :skill, type: :string, required: true, desc: "Skill ID"
53
59
  def delete(id)
54
60
  handle_errors do
55
61
  client.skill_files.destroy(skill_id: options[:skill], id:)
@@ -59,21 +65,23 @@ module SchwarmCli
59
65
 
60
66
  private
61
67
 
62
- def update_attrs
63
- {}.tap do |attrs|
64
- attrs[:path] = options[:path] if options[:path]
65
- attrs[:content] = read_content if options[:content] || options[:file]
66
- end
68
+ def skill_file_fields
69
+ { "ID" => "id", "Path" => "path", "Skill" => "skill_id",
70
+ "Created" => "created_at", "Updated" => "updated_at" }
67
71
  end
68
72
 
69
- def read_content
70
- if options[:file]
71
- file_path = File.expand_path(options[:file])
72
- abort "Error: File not found: #{file_path}" unless File.exist?(file_path)
73
- File.read(file_path)
74
- else
75
- options[:content]
76
- end
73
+ def read_content(required:)
74
+ abort "Error: --content and --file are mutually exclusive." if options[:content] && options[:file]
75
+ return options[:content] if options[:content]
76
+ return read_file(options[:file]) if options[:file]
77
+
78
+ abort "Error: one of --content or --file is required." if required
79
+ end
80
+
81
+ def read_file(path)
82
+ expanded = File.expand_path(path)
83
+ abort "Error: File not found: #{expanded}" unless File.exist?(expanded)
84
+ File.read(expanded)
77
85
  end
78
86
  end
79
87
  end
@@ -19,10 +19,7 @@ module SchwarmCli
19
19
  def show(id)
20
20
  handle_errors do
21
21
  data = client.templates.find(id)
22
- output_record(data, fields: {
23
- "ID" => "id", "Name" => "name", "Description" => "description",
24
- "Prompt" => "prompt_prefix", "Created" => "created_at"
25
- })
22
+ output_record(data, fields: template_fields)
26
23
  end
27
24
  end
28
25
 
@@ -36,7 +33,7 @@ module SchwarmCli
36
33
  attrs[:description] = options[:description] if options[:description]
37
34
 
38
35
  data = client.templates.create(**attrs)
39
- output_record(data, fields: { "ID" => "id", "Name" => "name" })
36
+ output_record(data, fields: template_fields)
40
37
  end
41
38
  end
42
39
 
@@ -47,7 +44,7 @@ module SchwarmCli
47
44
  def update(id)
48
45
  handle_errors do
49
46
  data = client.templates.update(id, **update_attrs)
50
- output_record(data, fields: { "ID" => "id", "Name" => "name" })
47
+ output_record(data, fields: template_fields)
51
48
  end
52
49
  end
53
50
 
@@ -61,6 +58,11 @@ module SchwarmCli
61
58
 
62
59
  private
63
60
 
61
+ def template_fields
62
+ { "ID" => "id", "Name" => "name", "Description" => "description",
63
+ "Prompt" => "prompt_prefix", "Created" => "created_at", "Updated" => "updated_at" }
64
+ end
65
+
64
66
  def update_attrs
65
67
  {}.tap do |attrs|
66
68
  attrs[:name] = options[:name] if options[:name]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SchwarmCli
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.9"
5
5
  end
data/schwarm-skill.md CHANGED
@@ -266,13 +266,18 @@ for tasks in a repository.
266
266
  ```bash
267
267
  schwarm repo-skills create --repo <REPO_ID> --skill <SKILL_ID>
268
268
  ```
269
- 3. Create a repository agent (custom instructions for all tasks in a repo):
269
+ 3. Create an agent (a reusable definition with a name, prompt, and optional schedule):
270
270
  ```bash
271
- schwarm agents create --repo <REPO_ID> --name "Code style" --prompt "Always follow the project's eslint config"
271
+ schwarm agents create --name "Code style" --prompt "Always follow the project's eslint config"
272
272
  ```
273
- 4. List agents for a repo:
273
+ 4. Subscribe an agent to a repository so it runs against that repo:
274
274
  ```bash
275
- schwarm agents list --repo <REPO_ID>
275
+ schwarm agents subscriptions create --repo <REPO_ID> --agent <AGENT_ID>
276
+ ```
277
+ 5. List subscriptions for a repo, or all agents:
278
+ ```bash
279
+ schwarm agents subscriptions list --repo <REPO_ID>
280
+ schwarm agents list
276
281
  ```
277
282
 
278
283
  ## Tips
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.7
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Garrigues
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday-multipart
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: faraday-net_http_persistent
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -103,6 +117,7 @@ files:
103
117
  - lib/schwarm_cli/client/task_templates.rb
104
118
  - lib/schwarm_cli/client/tasks.rb
105
119
  - lib/schwarm_cli/client/user_messages.rb
120
+ - lib/schwarm_cli/commands/agent_subscriptions.rb
106
121
  - lib/schwarm_cli/commands/agents.rb
107
122
  - lib/schwarm_cli/commands/base.rb
108
123
  - lib/schwarm_cli/commands/configure.rb
@@ -112,7 +127,6 @@ files:
112
127
  - lib/schwarm_cli/commands/repos.rb
113
128
  - lib/schwarm_cli/commands/secrets.rb
114
129
  - lib/schwarm_cli/commands/sessions.rb
115
- - lib/schwarm_cli/commands/shared_agents.rb
116
130
  - lib/schwarm_cli/commands/skill_files.rb
117
131
  - lib/schwarm_cli/commands/skills.rb
118
132
  - lib/schwarm_cli/commands/tasks.rb
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SchwarmCli
4
- module Commands
5
- class SharedAgentsCmd < Base
6
- desc "list", "List shared agents"
7
- option :query, type: :string, desc: "Search by name"
8
- pagination_options
9
- def list
10
- handle_errors do
11
- data = fetch_paged do |page_params|
12
- client.shared_agents.list(query: options[:query], **page_params)
13
- end
14
- output_list(data, columns: [%w[ID id], %w[NAME name], %w[ENABLED enabled],
15
- %w[SCHEDULE schedule]])
16
- end
17
- end
18
-
19
- desc "show ID", "Show shared agent details"
20
- def show(id)
21
- handle_errors do
22
- data = client.shared_agents.find(id)
23
- output_record(data, fields: {
24
- "ID" => "id", "Name" => "name", "Enabled" => "enabled",
25
- "Schedule" => "schedule", "Prompt" => "prompt",
26
- "Created" => "created_at", "Updated" => "updated_at"
27
- })
28
- end
29
- end
30
-
31
- desc "create", "Create a shared agent"
32
- option :name, type: :string, required: true, desc: "Agent name"
33
- option :prompt, type: :string, required: true, desc: "Agent prompt"
34
- option :schedule, type: :string, desc: "Cron schedule"
35
- def create
36
- handle_errors do
37
- attrs = { name: options[:name], prompt: options[:prompt] }
38
- attrs[:schedule] = options[:schedule] if options[:schedule]
39
-
40
- data = client.shared_agents.create(**attrs)
41
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Enabled" => "enabled" })
42
- end
43
- end
44
-
45
- desc "update ID", "Update a shared agent"
46
- option :name, type: :string, desc: "Agent name"
47
- option :prompt, type: :string, desc: "Agent prompt"
48
- option :schedule, type: :string, desc: "Cron schedule"
49
- def update(id)
50
- handle_errors do
51
- data = client.shared_agents.update(id, **update_attrs)
52
- output_record(data, fields: { "ID" => "id", "Name" => "name", "Enabled" => "enabled" })
53
- end
54
- end
55
-
56
- desc "delete ID", "Delete a shared agent"
57
- def delete(id)
58
- handle_errors do
59
- client.shared_agents.destroy(id)
60
- puts "Shared agent #{id} deleted."
61
- end
62
- end
63
-
64
- desc "toggle ID", "Toggle shared agent enabled/disabled"
65
- def toggle(id)
66
- handle_errors do
67
- data = client.shared_agents.toggle(id)
68
- status = data.dig("data", "enabled") ? "enabled" : "disabled"
69
- puts "Shared agent #{id} #{status}."
70
- end
71
- end
72
-
73
- private
74
-
75
- def update_attrs
76
- {}.tap do |attrs|
77
- attrs[:name] = options[:name] if options[:name]
78
- attrs[:prompt] = options[:prompt] if options[:prompt]
79
- attrs[:schedule] = options[:schedule] if options[:schedule]
80
- end
81
- end
82
- end
83
- end
84
- end