schwarm-cli 0.1.4 → 0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fda5a7c0a0a68a6e07573a5509cadb8ea7ff600f94a3feabf065317c332a9b40
4
- data.tar.gz: bfd4c31bca63d5862c9df68514c8ab3bdeefb0a1ebbd944c316aeed4522a626d
3
+ metadata.gz: cb4e9030ccc6a70a2d35c94e5c2db414bae4b56550ae21128b2e3f612b56b6cf
4
+ data.tar.gz: 4c4bfd79d9b120f01764b4b482200757a5a3f4d1a1e8dba5c344486befebf48d
5
5
  SHA512:
6
- metadata.gz: cf2aac68753184cd3ef7fcd28ff24eb75010b84237f2b661e76a00e1582ead6ad07b0789941f9d3c518bd40eadc34bea14d463bc83e7d69dfb62f68f165920a3
7
- data.tar.gz: 9bdf3cbb26adecc1f40e90d681e9cb722bd2ae477cd7faa1ac258f3bf690cc77aa1374cb2744a8fe9bb6583b7b328b242f81e80f19ab79afa2dedad23c5d6b55
6
+ metadata.gz: 326c318ea026081fb23c05cd552e5a78204af6f35dc96e2ff7d5da6c838d0b48646534ab914cc1bc6afa250844e6a6d2ab3895cf2da37fee44f671308c10fbc4
7
+ data.tar.gz: c15ba6b8d00b97e44e8b0642ab88753423043cbb4eafad9593fc8fdba0a22bc3a67168576fe76f5165386316bfe14e505411471d08dca4eb6e474ddea25a9d97
@@ -32,6 +32,13 @@ module SchwarmCli
32
32
  abort "Error: #{e.message}"
33
33
  end
34
34
 
35
+ # Resolves --template input (ID or exact name) to a schwarm template ID.
36
+ def resolve_template(ref)
37
+ SchwarmCli::TemplateResolver.new(client: client).resolve(ref)
38
+ rescue SchwarmCli::TemplateResolver::ResolutionError => e
39
+ abort "Error: #{e.message}"
40
+ end
41
+
35
42
  def output_list(data, columns:)
36
43
  if options[:json]
37
44
  Formatters::Json.format(data)
@@ -32,10 +32,9 @@ module SchwarmCli
32
32
  desc "create", "Create a new task"
33
33
  option :name, type: :string, required: true, desc: "Task name"
34
34
  option :prompt, type: :string, desc: "Task prompt"
35
- option :prompt_file, type: :string, desc: "Read prompt from file"
36
35
  option :repo, type: :string, desc: "Repository ID, owner/repo, or GitHub URL"
37
36
  option :status, type: :string, desc: "Initial status (draft/waiting)"
38
- option :template, type: :string, desc: "Template ID"
37
+ option :template, type: :string, desc: "Template ID or name"
39
38
  option :depends_on, type: :array, desc: "Dependency task IDs"
40
39
  def create
41
40
  handle_errors do
@@ -47,7 +46,6 @@ module SchwarmCli
47
46
  desc "update ID", "Update a task"
48
47
  option :name, type: :string, desc: "Task name"
49
48
  option :prompt, type: :string, desc: "Task prompt"
50
- option :prompt_file, type: :string, desc: "Read prompt from file"
51
49
  option :repo, type: :string, desc: "Repository ID, owner/repo, or GitHub URL"
52
50
  option :depends_on, type: :array, desc: "Dependency task IDs"
53
51
  def update(id)
@@ -116,17 +114,15 @@ module SchwarmCli
116
114
 
117
115
  desc "handoff", "Hand off the current local branch to schwarm as a ready task"
118
116
  option :prompt, type: :string, desc: "Task prompt"
119
- option :prompt_file, type: :string, desc: "Read prompt from file"
120
117
  option :repo, type: :string, desc: "Repository ID, owner/repo, or GitHub URL (skips origin auto-detect)"
121
118
  option :name, type: :string, desc: "Task name (defaults to the branch name)"
119
+ option :template, type: :string, desc: "Task template ID or name"
122
120
  def handoff
123
- prompt = read_prompt
124
- abort "Error: --prompt or --prompt-file is required." if prompt.nil? || prompt.strip.empty?
121
+ prompt = options[:prompt]
122
+ abort "Error: --prompt is required." if prompt.nil? || prompt.strip.empty?
125
123
 
126
124
  handle_errors do
127
- result = SchwarmCli::HandsOffTask.new(client: client).call(
128
- prompt: prompt, repo_override: resolve_repo(options[:repo]), name_override: options[:name]
129
- )
125
+ result = SchwarmCli::HandsOffTask.new(client: client).call(**handoff_attrs(prompt))
130
126
  print_handoff_result(result)
131
127
  end
132
128
  end
@@ -151,11 +147,18 @@ module SchwarmCli
151
147
  end
152
148
  end
153
149
 
150
+ def handoff_attrs(prompt)
151
+ {
152
+ prompt: prompt, repo_override: resolve_repo(options[:repo]),
153
+ name_override: options[:name], template_override: options[:template]
154
+ }
155
+ end
156
+
154
157
  def create_attrs
155
158
  {
156
- name: options[:name], prompt: read_prompt,
159
+ name: options[:name], prompt: options[:prompt],
157
160
  github_repository_id: resolve_repo(options[:repo]), status: options[:status],
158
- task_template_id: options[:template], dependency_ids: options[:depends_on]
161
+ task_template_id: resolve_template(options[:template]), dependency_ids: options[:depends_on]
159
162
  }.compact
160
163
  end
161
164
 
@@ -164,17 +167,7 @@ module SchwarmCli
164
167
  name: options[:name], github_repository_id: resolve_repo(options[:repo]),
165
168
  dependency_ids: options[:depends_on]
166
169
  }.compact.tap do |attrs|
167
- attrs[:prompt] = read_prompt if options[:prompt] || options[:prompt_file]
168
- end
169
- end
170
-
171
- def read_prompt
172
- if options[:prompt_file]
173
- file_path = File.expand_path(options[:prompt_file])
174
- abort "Error: File not found: #{file_path}" unless File.exist?(file_path)
175
- File.read(file_path)
176
- else
177
- options[:prompt]
170
+ attrs[:prompt] = options[:prompt] if options[:prompt]
178
171
  end
179
172
  end
180
173
  end
@@ -12,14 +12,18 @@ module SchwarmCli
12
12
  @client = client
13
13
  end
14
14
 
15
- def call(prompt:, repo_override:, name_override:)
15
+ def call(prompt:, repo_override:, name_override:, template_override: nil)
16
16
  repo, branch, error = run_preflight(repo_override)
17
17
  return error if error
18
18
 
19
+ template_id, template_error = resolve_template_id(template_override)
20
+ return template_error if template_error
21
+
19
22
  push_result = @git.run("push", "-u", "origin", "HEAD")
20
23
  return failure(push_result.stderr.strip) unless push_result.success?
21
24
 
22
- create_task(repo: repo, branch: branch, prompt: prompt, name_override: name_override)
25
+ create_task(repo: repo, branch: branch, prompt: prompt, name_override: name_override,
26
+ template_id: template_id)
23
27
  rescue Faraday::Error => e
24
28
  failure(parse_api_error(e))
25
29
  end
@@ -97,13 +101,22 @@ module SchwarmCli
97
101
  nil
98
102
  end
99
103
 
100
- def create_task(repo:, branch:, prompt:, name_override:)
104
+ def resolve_template_id(ref)
105
+ [TemplateResolver.new(client: @client).resolve(ref), nil]
106
+ rescue TemplateResolver::ResolutionError => e
107
+ [nil, failure(e.message)]
108
+ end
109
+
110
+ def create_task(repo:, branch:, prompt:, name_override:, template_id:)
101
111
  response = @client.tasks.create(
102
- name: name_override || branch,
103
- branch_name: branch,
104
- prompt: prompt,
105
- status: "ready",
106
- github_repository_id: repo["id"]
112
+ **{
113
+ name: name_override || branch,
114
+ branch_name: branch,
115
+ prompt: prompt,
116
+ status: "ready",
117
+ github_repository_id: repo["id"],
118
+ task_template_id: template_id
119
+ }.compact
107
120
  )
108
121
  data = response["data"]
109
122
  Result.new(
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwarmCli
4
+ # Turns a user-provided `--template` reference into a task template ID.
5
+ #
6
+ # Accepted inputs:
7
+ # - a numeric template ID (looked up directly)
8
+ # - an exact template name (resolved via search + exact match)
9
+ class TemplateResolver
10
+ class ResolutionError < StandardError; end
11
+
12
+ def initialize(client:)
13
+ @client = client
14
+ end
15
+
16
+ def resolve(ref)
17
+ return nil if blank?(ref)
18
+
19
+ ref = ref.to_s.strip
20
+ template = ref.match?(/\A\d+\z/) ? find_by_id(ref) : find_by_name(ref)
21
+ raise ResolutionError, "no schwarm task template matches `#{ref}`." if template.nil?
22
+
23
+ template["id"]
24
+ end
25
+
26
+ private
27
+
28
+ def blank?(ref)
29
+ ref.nil? || ref.to_s.strip.empty?
30
+ end
31
+
32
+ def find_by_id(id)
33
+ @client.templates.find(id)["data"]
34
+ rescue Faraday::ResourceNotFound
35
+ nil
36
+ end
37
+
38
+ def find_by_name(name)
39
+ response = @client.templates.list(query: name)
40
+ Array(response["data"]).find { |t| t["name"] == name }
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SchwarmCli
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.6"
5
5
  end
data/lib/schwarm_cli.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "schwarm_cli/config"
5
5
  require_relative "schwarm_cli/client"
6
6
  require_relative "schwarm_cli/git"
7
7
  require_relative "schwarm_cli/repository_resolver"
8
+ require_relative "schwarm_cli/template_resolver"
8
9
  require_relative "schwarm_cli/hands_off_task"
9
10
  require_relative "schwarm_cli/formatters/json"
10
11
  require_relative "schwarm_cli/formatters/table"
data/schwarm-skill.md CHANGED
@@ -32,7 +32,14 @@ The `schwarm` CLI is installed and configured (`schwarm configure`).
32
32
  ```bash
33
33
  schwarm tasks create --repo <REPO_ID> --name "Fix login bug" --prompt "The login form crashes when email is empty. Fix the validation in app/models/user.rb" --status ready
34
34
  ```
35
- Use `--prompt-file ./prompt.md` for long prompts.
35
+ For a multi-line prompt, pipe via HEREDOC:
36
+ ```bash
37
+ schwarm tasks create --repo <REPO_ID> --name "Fix login bug" --status ready --prompt "$(cat <<'EOF'
38
+ The login form crashes when empty. Fix validation in app/models/user.rb
39
+ and add a regression test.
40
+ EOF
41
+ )"
42
+ ```
36
43
  3. Check the created task:
37
44
  ```bash
38
45
  schwarm tasks show <TASK_ID>
@@ -53,10 +60,24 @@ continues committing to it.
53
60
  ```bash
54
61
  schwarm tasks handoff --prompt "continue the validation logic, add tests"
55
62
  ```
56
- Use `--prompt-file ./handoff.md` for long prompts.
57
63
  3. Take note of the task ID printed on success; you can monitor it like any
58
64
  other task.
59
65
 
66
+ **Keep the prompt short.** The prompt is a pointer, not a spec. Before handing
67
+ off, commit any detailed instructions, design notes, or TODOs into the repo
68
+ (e.g. a `HANDOFF.md`, `AGENTS.md`, or inline comments) so the next agent reads
69
+ them from the working tree. Then the `--prompt` just needs to say what to do
70
+ next and where to look, e.g. `"continue per HANDOFF.md"`.
71
+
72
+ For a multi-line prompt, use a HEREDOC rather than a file flag:
73
+ ```bash
74
+ schwarm tasks handoff --prompt "$(cat <<'EOF'
75
+ Continue the validation work. Start from the TODOs in app/models/user.rb
76
+ and follow the plan in HANDOFF.md.
77
+ EOF
78
+ )"
79
+ ```
80
+
60
81
  **Watch for:**
61
82
  - The command refuses to hand off the base branch (`main`). Create a feature
62
83
  branch first.
@@ -65,6 +86,42 @@ continues committing to it.
65
86
  will print the conflicting task ID. Archive or reset it first.
66
87
  - The repository is auto-detected from `git remote get-url origin`. Use
67
88
  `--repo <id>` if you need to override it (e.g. in a fork).
89
+ - Pass `--template <id-or-name>` to apply a task template to the handoff. The
90
+ value can be a template ID or its exact name.
91
+
92
+ ### 1c. Hand off a WIP branch with follow-up tasks
93
+
94
+ **When:** You're handing off a WIP branch and want other tasks to run after it
95
+ on the same repo (e.g. add docs, cut a release PR, update a dashboard). The
96
+ handoff task starts immediately; the follow-ups wait until it's archived.
97
+
98
+ **Steps:**
99
+ 1. Hand off the current branch first and note the task ID it prints:
100
+ ```bash
101
+ schwarm tasks handoff --prompt "continue per HANDOFF.md"
102
+ # => Task a1b2c3 created (status: ready).
103
+ ```
104
+ 2. Create follow-up tasks as drafts depending on the handoff task ID:
105
+ ```bash
106
+ schwarm tasks create --repo <REPO_ID> --name "Update docs" \
107
+ --prompt "Update README and CHANGELOG once the branch lands" --depends-on a1b2c3
108
+ schwarm tasks create --repo <REPO_ID> --name "Announce in #releases" \
109
+ --prompt "Post the summary to Slack" --depends-on a1b2c3
110
+ ```
111
+ 3. Start each follow-up — it will move to `waiting` until the handoff archives:
112
+ ```bash
113
+ schwarm tasks start <FOLLOWUP_ID>
114
+ ```
115
+
116
+ **Watch for:**
117
+ - The handoff task is created with `status: ready` and runs immediately. You
118
+ don't need to start it.
119
+ - Follow-ups run on fresh worktrees off the repo's base branch by default, not
120
+ off the handoff branch. `--depends-on` keeps them in `waiting` until the
121
+ handoff task is archived, so by the time they start the handoff's changes are
122
+ already on the base branch.
123
+ - Pass `--repo <id>` on `tasks create` (handoff auto-detects it from `origin`,
124
+ but `create` does not).
68
125
 
69
126
  ### 2. Dispatch a DAG of tasks
70
127
 
@@ -171,9 +228,10 @@ must `start` again.
171
228
  schwarm templates list
172
229
  schwarm templates show <TEMPLATE_ID>
173
230
  ```
174
- 3. Create a task from a template:
231
+ 3. Create a task from a template. `--template` accepts either the template ID or
232
+ its exact name:
175
233
  ```bash
176
- schwarm tasks create --repo <REPO_ID> --name "Apply template" --template <TEMPLATE_ID> --status ready
234
+ schwarm tasks create --repo <REPO_ID> --name "Apply template" --template <TEMPLATE_ID_OR_NAME> --status ready
177
235
  ```
178
236
 
179
237
  ### 7. Manage recurring tasks
@@ -223,7 +281,9 @@ for tasks in a repository.
223
281
  - Check task status before taking actions (e.g., only `retry` works on `error`
224
282
  tasks, only `start` works on `draft` tasks).
225
283
  - When building a DAG, create all tasks as drafts first, then start them all.
226
- - For long prompts, write them to a file and use `--prompt-file`.
284
+ - Keep `--prompt` short commit long-form instructions (plans, TODOs,
285
+ `HANDOFF.md`) into the repo and point the prompt at them. For multi-line
286
+ prompts, pipe via HEREDOC: `--prompt "$(cat <<'EOF' ... EOF)"`.
227
287
  - Run `schwarm <command> help` for full flag documentation on any command.
228
288
  - `schwarm tasks list` excludes archived tasks by default. Use `--all` to
229
289
  include them or `--status archived` to see only archived.
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.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Garrigues
@@ -123,6 +123,7 @@ files:
123
123
  - lib/schwarm_cli/git.rb
124
124
  - lib/schwarm_cli/hands_off_task.rb
125
125
  - lib/schwarm_cli/repository_resolver.rb
126
+ - lib/schwarm_cli/template_resolver.rb
126
127
  - lib/schwarm_cli/version.rb
127
128
  - schwarm-skill.md
128
129
  homepage: https://github.com/getdexter/schwarm