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 +4 -4
- data/lib/schwarm_cli/commands/base.rb +7 -0
- data/lib/schwarm_cli/commands/tasks.rb +15 -22
- data/lib/schwarm_cli/hands_off_task.rb +21 -8
- data/lib/schwarm_cli/template_resolver.rb +43 -0
- data/lib/schwarm_cli/version.rb +1 -1
- data/lib/schwarm_cli.rb +1 -0
- data/schwarm-skill.md +65 -5
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb4e9030ccc6a70a2d35c94e5c2db414bae4b56550ae21128b2e3f612b56b6cf
|
|
4
|
+
data.tar.gz: 4c4bfd79d9b120f01764b4b482200757a5a3f4d1a1e8dba5c344486befebf48d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 =
|
|
124
|
-
abort "Error: --prompt
|
|
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:
|
|
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] =
|
|
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
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
data/lib/schwarm_cli/version.rb
CHANGED
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
|
-
|
|
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 <
|
|
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
|
-
-
|
|
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
|
+
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
|