shapeup-cli 0.3.4 → 0.5.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.
- checksums.yaml +4 -4
- data/lib/shapeup_cli/client.rb +27 -30
- data/lib/shapeup_cli/commands/base.rb +42 -1
- data/lib/shapeup_cli/commands/comments.rb +27 -2
- data/lib/shapeup_cli/commands/config_cmd.rb +2 -20
- data/lib/shapeup_cli/commands/cycle.rb +50 -4
- data/lib/shapeup_cli/commands/issues.rb +18 -4
- data/lib/shapeup_cli/commands/pitches.rb +89 -5
- data/lib/shapeup_cli/commands/scopes.rb +55 -5
- data/lib/shapeup_cli/commands/streams.rb +99 -0
- data/lib/shapeup_cli/commands/tasks.rb +71 -8
- data/lib/shapeup_cli/commands.rb +28 -6
- data/lib/shapeup_cli/output.rb +3 -3
- data/lib/shapeup_cli/version.rb +5 -0
- data/lib/shapeup_cli.rb +11 -7
- data/skills/shapeup/SKILL.md +29 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8ff0645627f89f9f4478145566d6386d3137125bfee4f0baea077476da8d7e7
|
|
4
|
+
data.tar.gz: 94260b420442e04c89015fc58ac2536ba312d177876fe7d310064de341e24a3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c334924b153062646d61d9f09f7a98a9a6932a364fe461b76284392de112242437f5d0381d16b6b8882b6d9b4073dd590172a3389dc0393f05d810ff9117eba
|
|
7
|
+
data.tar.gz: 2cced2c61abe233c7bdaef8b9918239105724ca1b59d4524beec770709d269c814343216e6c7d5ee8545a146229db73201f161b7d711ca0e18eff3895cfb6801
|
data/lib/shapeup_cli/client.rb
CHANGED
|
@@ -17,31 +17,12 @@ module ShapeupCli
|
|
|
17
17
|
@request_id = 0
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
call_method("initialize", protocolVersion: MCP_PROTOCOL_VERSION)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# List available tools (useful for --agent --help introspection)
|
|
26
|
-
def list_tools
|
|
27
|
-
call_method("tools/list")
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Call an MCP tool by name with arguments
|
|
20
|
+
# Call an MCP tool by name with arguments. The only method the CLI uses —
|
|
21
|
+
# the MCP session/introspection/resource calls were never wired up.
|
|
31
22
|
def call_tool(name, **arguments)
|
|
32
23
|
call_method("tools/call", name: name, arguments: arguments)
|
|
33
24
|
end
|
|
34
25
|
|
|
35
|
-
# List available resources
|
|
36
|
-
def list_resources
|
|
37
|
-
call_method("resources/list")
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Read a resource by URI
|
|
41
|
-
def read_resource(uri)
|
|
42
|
-
call_method("resources/read", uri: uri)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
26
|
private
|
|
46
27
|
def call_method(method, **params)
|
|
47
28
|
@request_id += 1
|
|
@@ -65,18 +46,28 @@ module ShapeupCli
|
|
|
65
46
|
http.open_timeout = 5
|
|
66
47
|
http.read_timeout = 30
|
|
67
48
|
|
|
68
|
-
response = http.request(request)
|
|
49
|
+
response = with_network_error_handling { http.request(request) }
|
|
50
|
+
handle_response(response)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def with_network_error_handling
|
|
54
|
+
yield
|
|
55
|
+
rescue Net::OpenTimeout, Net::ReadTimeout
|
|
56
|
+
raise ApiError, "The server took too long to respond. Please try again."
|
|
57
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT => e
|
|
58
|
+
raise ApiError, "Couldn't reach #{@host} (#{e.class}). Check your connection and 'shapeup config show'."
|
|
59
|
+
end
|
|
69
60
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
when
|
|
74
|
-
|
|
75
|
-
when
|
|
76
|
-
|
|
61
|
+
def handle_response(response)
|
|
62
|
+
case response.code.to_i
|
|
63
|
+
when 401 then raise AuthError, "Authentication failed — run 'shapeup login'"
|
|
64
|
+
when 403 then raise PermissionError, "Check your subscription or permissions"
|
|
65
|
+
when 429 then raise RateLimitError, "Too many requests — slow down and retry"
|
|
66
|
+
when 200..299 then nil
|
|
67
|
+
else raise ApiError, "The server returned an error (HTTP #{response.code})."
|
|
77
68
|
end
|
|
78
69
|
|
|
79
|
-
parsed =
|
|
70
|
+
parsed = parse_json(response.body)
|
|
80
71
|
|
|
81
72
|
if parsed["error"]
|
|
82
73
|
message = parsed["error"]["message"] || "Unknown error"
|
|
@@ -90,5 +81,11 @@ module ShapeupCli
|
|
|
90
81
|
|
|
91
82
|
parsed["result"]
|
|
92
83
|
end
|
|
84
|
+
|
|
85
|
+
def parse_json(body)
|
|
86
|
+
JSON.parse(body.to_s)
|
|
87
|
+
rescue JSON::ParserError
|
|
88
|
+
raise ApiError, "The server returned an unreadable response."
|
|
89
|
+
end
|
|
93
90
|
end
|
|
94
91
|
end
|
|
@@ -34,7 +34,9 @@ module ShapeupCli
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def org_id
|
|
37
|
-
|
|
37
|
+
# Memoised: a name → id lookup hits the API, and call_tool asks for
|
|
38
|
+
# org_id on every call (a multi-arg command makes several).
|
|
39
|
+
@resolved_org_id ||= resolve_org(@org_id || Config.organisation_id || abort("No organisation set. Run 'shapeup orgs' to see available orgs, then pass --org <id> or --org <name>."))
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def call_tool(name, **args)
|
|
@@ -114,6 +116,45 @@ module ShapeupCli
|
|
|
114
116
|
!!@remaining.delete(flag)
|
|
115
117
|
end
|
|
116
118
|
|
|
119
|
+
# Consume a confirmation bypass flag (--yes / -y). Call before reading
|
|
120
|
+
# positionals so the flag isn't mistaken for one.
|
|
121
|
+
def assume_yes?
|
|
122
|
+
# both forms; -y does not start with "--" so it must be consumed explicitly
|
|
123
|
+
consume_flag("--yes") | consume_flag("-y")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Shared assign/unassign for any assignable (Task, Scope, Package,
|
|
127
|
+
# Issue). --user defaults to 'me'.
|
|
128
|
+
def assign_to(type, noun)
|
|
129
|
+
id = positional_arg(1) || abort("Usage: shapeup #{noun} assign <id> [--user <id>]")
|
|
130
|
+
user_id = extract_option("--user") || "me"
|
|
131
|
+
result = call_tool("assign_user", assignable_type: type, assignable_id: id.to_s, user_id: user_id.to_s)
|
|
132
|
+
render result, summary: "Assigned #{noun} ##{id}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def unassign_from(type, noun)
|
|
136
|
+
id = positional_arg(1) || abort("Usage: shapeup #{noun} unassign <id> [--user <id>]")
|
|
137
|
+
user_id = extract_option("--user") || "me"
|
|
138
|
+
result = call_tool("unassign_user", assignable_type: type, assignable_id: id.to_s, user_id: user_id.to_s)
|
|
139
|
+
render result, summary: "Unassigned #{noun} ##{id}"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Guard a destructive action. `assume_yes` skips the prompt (the caller
|
|
143
|
+
# passes the result of assume_yes?). With a TTY we ask for confirmation;
|
|
144
|
+
# without one we refuse rather than delete unattended — so agents and
|
|
145
|
+
# scripts must opt in explicitly with --yes.
|
|
146
|
+
def confirm_destructive!(action, assume_yes)
|
|
147
|
+
return if assume_yes
|
|
148
|
+
|
|
149
|
+
unless $stdin.tty?
|
|
150
|
+
abort "Refusing to #{action} without confirmation. Re-run with --yes to proceed."
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
$stderr.print "#{action}? This cannot be undone [y/N]: "
|
|
154
|
+
answer = $stdin.gets&.strip&.downcase
|
|
155
|
+
abort "Aborted." unless answer == "y" || answer == "yes"
|
|
156
|
+
end
|
|
157
|
+
|
|
117
158
|
# Parse --comments/--no-comments + --comments-limit N into MCP args.
|
|
118
159
|
def comment_flags
|
|
119
160
|
args = {}
|
|
@@ -10,7 +10,9 @@ module ShapeupCli
|
|
|
10
10
|
short: "List and add comments on issues, pitches, scopes, and tasks",
|
|
11
11
|
subcommands: [
|
|
12
12
|
{ name: "list", short: "List comments", path: "shapeup comments list --issue <id>" },
|
|
13
|
-
{ name: "add", short: "Add a comment", path: 'shapeup comments add --issue <id> "Comment text"' }
|
|
13
|
+
{ name: "add", short: "Add a comment", path: 'shapeup comments add --issue <id> "Comment text"' },
|
|
14
|
+
{ name: "edit", short: "Edit your own comment", path: 'shapeup comments edit <comment_id> "New text"' },
|
|
15
|
+
{ name: "remove", short: "Delete your own comment", path: "shapeup comments remove <comment_id>" }
|
|
14
16
|
],
|
|
15
17
|
flags: [
|
|
16
18
|
{ name: "issue", type: "string", usage: "Issue ID" },
|
|
@@ -22,7 +24,9 @@ module ShapeupCli
|
|
|
22
24
|
"shapeup comments list --issue 42",
|
|
23
25
|
'shapeup comments add --issue 42 "Investigated — this is a CSS issue in the navbar"',
|
|
24
26
|
"shapeup comments list --pitch 10",
|
|
25
|
-
'shapeup comments add --pitch 10 "Shaped and ready for betting"'
|
|
27
|
+
'shapeup comments add --pitch 10 "Shaped and ready for betting"',
|
|
28
|
+
'shapeup comments edit 88 "Updated: this is a navbar z-index issue"',
|
|
29
|
+
"shapeup comments remove 88"
|
|
26
30
|
]
|
|
27
31
|
}
|
|
28
32
|
end
|
|
@@ -32,6 +36,8 @@ module ShapeupCli
|
|
|
32
36
|
|
|
33
37
|
case subcommand
|
|
34
38
|
when "add" then add
|
|
39
|
+
when "edit" then edit
|
|
40
|
+
when "remove" then remove
|
|
35
41
|
when "list", nil then list
|
|
36
42
|
else list
|
|
37
43
|
end
|
|
@@ -63,6 +69,25 @@ module ShapeupCli
|
|
|
63
69
|
]
|
|
64
70
|
end
|
|
65
71
|
|
|
72
|
+
def edit
|
|
73
|
+
comment_id = positional_arg(1) || abort('Usage: shapeup comments edit <comment_id> "New text"')
|
|
74
|
+
text = positional_arg(2) || abort('Usage: shapeup comments edit <comment_id> "New text"')
|
|
75
|
+
|
|
76
|
+
result = call_tool("update_comment", comment_id: comment_id.to_s, content: text)
|
|
77
|
+
|
|
78
|
+
render result, summary: "Comment ##{comment_id} updated"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def remove
|
|
82
|
+
yes = assume_yes?
|
|
83
|
+
comment_id = positional_arg(1) || abort("Usage: shapeup comments remove <comment_id> [--yes]")
|
|
84
|
+
confirm_destructive!("Delete comment ##{comment_id}", yes)
|
|
85
|
+
|
|
86
|
+
result = call_tool("delete_comment", comment_id: comment_id.to_s)
|
|
87
|
+
|
|
88
|
+
render result, summary: "Comment ##{comment_id} deleted"
|
|
89
|
+
end
|
|
90
|
+
|
|
66
91
|
def resolve_commentable
|
|
67
92
|
issue = extract_option("--issue")
|
|
68
93
|
pitch = extract_option("--pitch")
|
|
@@ -44,7 +44,7 @@ module ShapeupCli
|
|
|
44
44
|
|
|
45
45
|
case key
|
|
46
46
|
when "org"
|
|
47
|
-
resolved =
|
|
47
|
+
resolved = resolve_org(value)
|
|
48
48
|
Config.save_config("organisation_id", resolved.to_s)
|
|
49
49
|
puts "Default organisation set to #{resolved}"
|
|
50
50
|
when "host"
|
|
@@ -113,31 +113,13 @@ module ShapeupCli
|
|
|
113
113
|
org_value = positional_arg(1) || extract_option("--org") || @org_id
|
|
114
114
|
abort("Usage: shapeup config init <org>") unless org_value
|
|
115
115
|
|
|
116
|
-
resolved =
|
|
116
|
+
resolved = resolve_org(org_value)
|
|
117
117
|
|
|
118
118
|
FileUtils.mkdir_p(".shapeup")
|
|
119
119
|
File.write(".shapeup/config.json", JSON.pretty_generate(organisation_id: resolved.to_s))
|
|
120
120
|
puts "Created .shapeup/config.json (org: #{resolved})"
|
|
121
121
|
puts "All commands in this directory will use this organisation by default."
|
|
122
122
|
end
|
|
123
|
-
|
|
124
|
-
def resolve_org_value(value)
|
|
125
|
-
return value if value.to_s.match?(/\A\d+\z/)
|
|
126
|
-
|
|
127
|
-
# Need to resolve name to ID
|
|
128
|
-
result = client.call_tool("list_organisations")
|
|
129
|
-
data = Output.extract_data(result)
|
|
130
|
-
orgs = data.is_a?(Hash) ? (data["organisations"] || []) : Array(data)
|
|
131
|
-
|
|
132
|
-
match = orgs.find { |o| o["name"]&.downcase == value.downcase }
|
|
133
|
-
|
|
134
|
-
if match
|
|
135
|
-
match["id"]
|
|
136
|
-
else
|
|
137
|
-
names = orgs.map { |o| " #{o["id"]} #{o["name"]}" }.join("\n")
|
|
138
|
-
abort "Organisation '#{value}' not found. Available:\n#{names}"
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
123
|
end
|
|
142
124
|
end
|
|
143
125
|
end
|
|
@@ -10,15 +10,24 @@ module ShapeupCli
|
|
|
10
10
|
short: "List and show cycles",
|
|
11
11
|
subcommands: [
|
|
12
12
|
{ name: "list", short: "List cycles (default)", path: "shapeup cycles" },
|
|
13
|
-
{ name: "show", short: "Show cycle details with pitches and progress", path: "shapeup cycle show <id>" }
|
|
13
|
+
{ name: "show", short: "Show cycle details with pitches and progress", path: "shapeup cycle show <id>" },
|
|
14
|
+
{ name: "create", short: "Create a cycle", path: "shapeup cycle create \"Title\" --start <date> --end <date>" },
|
|
15
|
+
{ name: "edit", short: "Update a cycle's title or dates", path: "shapeup cycle edit <id> --title \"New\"" },
|
|
16
|
+
{ name: "delete", short: "Delete a cycle (only if it has no pitches)", path: "shapeup cycle delete <id>" }
|
|
14
17
|
],
|
|
15
18
|
flags: [
|
|
16
|
-
{ name: "status", type: "string", usage: "Filter by status: active, past, future, all" }
|
|
19
|
+
{ name: "status", type: "string", usage: "Filter by status: active, past, future, all" },
|
|
20
|
+
{ name: "title", type: "string", usage: "Cycle title (create/edit)" },
|
|
21
|
+
{ name: "start", type: "string", usage: "Start date YYYY-MM-DD (create/edit)" },
|
|
22
|
+
{ name: "end", type: "string", usage: "End date YYYY-MM-DD (create/edit)" }
|
|
17
23
|
],
|
|
18
24
|
examples: [
|
|
19
25
|
"shapeup cycles",
|
|
20
26
|
"shapeup cycles --status active",
|
|
21
|
-
"shapeup cycle show 12"
|
|
27
|
+
"shapeup cycle show 12",
|
|
28
|
+
"shapeup cycle create \"Cycle 13\" --start 2026-08-03 --end 2026-09-11",
|
|
29
|
+
"shapeup cycle edit 12 --title \"Renamed\"",
|
|
30
|
+
"shapeup cycle delete 12"
|
|
22
31
|
]
|
|
23
32
|
}
|
|
24
33
|
end
|
|
@@ -27,7 +36,10 @@ module ShapeupCli
|
|
|
27
36
|
subcommand = positional_arg(0)
|
|
28
37
|
|
|
29
38
|
case subcommand
|
|
30
|
-
when "show"
|
|
39
|
+
when "show" then show
|
|
40
|
+
when "create" then create
|
|
41
|
+
when "edit" then edit
|
|
42
|
+
when "delete" then delete
|
|
31
43
|
when "list", nil then list
|
|
32
44
|
else
|
|
33
45
|
subcommand.match?(/\A\d+\z/) ? show(subcommand) : list
|
|
@@ -35,6 +47,40 @@ module ShapeupCli
|
|
|
35
47
|
end
|
|
36
48
|
|
|
37
49
|
private
|
|
50
|
+
def create
|
|
51
|
+
title = positional_arg(1) || abort("Usage: shapeup cycle create \"Title\" --start <date> --end <date>")
|
|
52
|
+
start_date = extract_option("--start") || abort("Missing --start <YYYY-MM-DD>")
|
|
53
|
+
end_date = extract_option("--end") || abort("Missing --end <YYYY-MM-DD>")
|
|
54
|
+
|
|
55
|
+
result = call_tool("create_cycle", title: title, start_date: start_date, end_date: end_date)
|
|
56
|
+
|
|
57
|
+
render result,
|
|
58
|
+
summary: "Cycle created",
|
|
59
|
+
breadcrumbs: [ { cmd: "shapeup cycle show <id>", description: "View the cycle" } ]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def edit
|
|
63
|
+
id = positional_arg(1) || abort("Usage: shapeup cycle edit <id> [--title ..] [--start ..] [--end ..]")
|
|
64
|
+
args = { cycle: id.to_s }
|
|
65
|
+
args[:title] = extract_option("--title") if @remaining.include?("--title")
|
|
66
|
+
args[:start_date] = extract_option("--start") if @remaining.include?("--start")
|
|
67
|
+
args[:end_date] = extract_option("--end") if @remaining.include?("--end")
|
|
68
|
+
|
|
69
|
+
result = call_tool("update_cycle", **args)
|
|
70
|
+
|
|
71
|
+
render result, summary: "Cycle ##{id} updated"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def delete
|
|
75
|
+
yes = assume_yes?
|
|
76
|
+
id = positional_arg(1) || abort("Usage: shapeup cycle delete <id> [--yes]")
|
|
77
|
+
confirm_destructive!("Delete cycle ##{id}", yes)
|
|
78
|
+
|
|
79
|
+
result = call_tool("delete_cycle", cycle: id.to_s)
|
|
80
|
+
|
|
81
|
+
render result, summary: "Cycle ##{id} deleted"
|
|
82
|
+
end
|
|
83
|
+
|
|
38
84
|
def list
|
|
39
85
|
status = extract_option("--status")
|
|
40
86
|
args = {}
|
|
@@ -15,6 +15,7 @@ module ShapeupCli
|
|
|
15
15
|
{ name: "create", short: "Create an issue", path: "shapeup issues create \"Title\" --stream <id>" },
|
|
16
16
|
{ name: "update", short: "Update an issue", path: "shapeup issues update <id> --title \"New title\"" },
|
|
17
17
|
{ name: "move", short: "Move to a kanban column", path: "shapeup issues move <id> --column <id>" },
|
|
18
|
+
{ name: "columns", short: "List kanban columns and their IDs", path: "shapeup issues columns" },
|
|
18
19
|
{ name: "done", short: "Mark issue as done", path: "shapeup issues done <id>" },
|
|
19
20
|
{ name: "close", short: "Close issue (won't fix)", path: "shapeup issues close <id>" },
|
|
20
21
|
{ name: "reopen", short: "Reopen a done/closed issue", path: "shapeup issues reopen <id>" },
|
|
@@ -78,6 +79,7 @@ module ShapeupCli
|
|
|
78
79
|
when "create" then create
|
|
79
80
|
when "update" then update
|
|
80
81
|
when "move" then move
|
|
82
|
+
when "columns" then columns
|
|
81
83
|
when "done" then mark_done
|
|
82
84
|
when "close" then close
|
|
83
85
|
when "reopen" then reopen
|
|
@@ -187,6 +189,16 @@ module ShapeupCli
|
|
|
187
189
|
]
|
|
188
190
|
end
|
|
189
191
|
|
|
192
|
+
def columns
|
|
193
|
+
result = call_tool("list_kanban_columns")
|
|
194
|
+
|
|
195
|
+
render result,
|
|
196
|
+
summary: "Kanban Columns",
|
|
197
|
+
breadcrumbs: [
|
|
198
|
+
{ cmd: "shapeup issues move <id> --column <id>", description: "Move an issue to a column" }
|
|
199
|
+
]
|
|
200
|
+
end
|
|
201
|
+
|
|
190
202
|
def move
|
|
191
203
|
id = positional_arg(1) || abort("Usage: shapeup issues move <id> --column <id>")
|
|
192
204
|
column_id = extract_option("--column") || abort("Usage: shapeup issues move <id> --column <id>")
|
|
@@ -230,7 +242,7 @@ module ShapeupCli
|
|
|
230
242
|
def close
|
|
231
243
|
id = positional_arg(1) || abort("Usage: shapeup issues close <id>")
|
|
232
244
|
|
|
233
|
-
result = call_tool("close_issue", issue: id.to_s, resolution: "
|
|
245
|
+
result = call_tool("close_issue", issue: id.to_s, resolution: "wont_do")
|
|
234
246
|
|
|
235
247
|
render result,
|
|
236
248
|
summary: "Issue ##{id} closed",
|
|
@@ -331,7 +343,9 @@ module ShapeupCli
|
|
|
331
343
|
end
|
|
332
344
|
|
|
333
345
|
def delete
|
|
334
|
-
|
|
346
|
+
yes = assume_yes?
|
|
347
|
+
id = positional_arg(1) || abort("Usage: shapeup issues delete <id> [--yes]")
|
|
348
|
+
confirm_destructive!("Delete issue ##{id}", yes)
|
|
335
349
|
|
|
336
350
|
result = call_tool("delete_issue", issue: id.to_s)
|
|
337
351
|
|
|
@@ -356,8 +370,8 @@ module ShapeupCli
|
|
|
356
370
|
end
|
|
357
371
|
|
|
358
372
|
def add_to_pitch
|
|
359
|
-
id = positional_arg(1) || abort(
|
|
360
|
-
pitch_id = extract_option("--pitch") || abort(
|
|
373
|
+
id = positional_arg(1) || abort("Usage: shapeup issues add-to-pitch <id> --pitch <pitch_id>")
|
|
374
|
+
pitch_id = extract_option("--pitch") || abort("Usage: shapeup issues add-to-pitch <id> --pitch <pitch_id>")
|
|
361
375
|
|
|
362
376
|
result = call_tool("add_issue_to_pitch", issue: id.to_s, package: pitch_id.to_s)
|
|
363
377
|
|
|
@@ -12,16 +12,26 @@ module ShapeupCli
|
|
|
12
12
|
{ name: "list", short: "List pitches (default)", path: "shapeup pitches list" },
|
|
13
13
|
{ name: "show", short: "Show pitch details with scopes and tasks", path: "shapeup pitches show <id>" },
|
|
14
14
|
{ name: "create", short: "Create a new pitch", path: "shapeup pitches create \"Title\" --stream \"Name\"" },
|
|
15
|
+
{ name: "update", short: "Update a pitch's title, status, appetite, stream, or cycle", path: "shapeup pitches update <id> --status shaped" },
|
|
16
|
+
{ name: "extend", short: "Link a pitch as a continuation of a predecessor", path: "shapeup pitches extend <id> --predecessor <id>" },
|
|
17
|
+
{ name: "detach", short: "Remove a pitch's predecessor link", path: "shapeup pitches detach <id>" },
|
|
18
|
+
{ name: "delete", short: "Delete a pitch (must have no scopes)", path: "shapeup pitches delete <id>" },
|
|
19
|
+
{ name: "assign", short: "Assign a user", path: "shapeup pitches assign <id> [--user <id>]" },
|
|
20
|
+
{ name: "unassign", short: "Unassign a user", path: "shapeup pitches unassign <id> [--user <id>]" },
|
|
15
21
|
{ name: "help", short: "Show usage", path: "shapeup pitches help" }
|
|
16
22
|
],
|
|
17
23
|
flags: [
|
|
18
|
-
{ name: "
|
|
19
|
-
{ name: "
|
|
24
|
+
{ name: "user", type: "string", usage: "User ID or 'me' (assign/unassign; defaults to me)" },
|
|
25
|
+
{ name: "status", type: "string", usage: "Filter by, or set, status: idea, framed, shaped" },
|
|
26
|
+
{ name: "cycle", type: "string", usage: "Filter by cycle ID (list), or assign to cycle ID (update)" },
|
|
20
27
|
{ name: "tag", type: "string", usage: "Filter by tag name" },
|
|
21
28
|
{ name: "limit", type: "integer", usage: "Limit number of results" },
|
|
22
|
-
{ name: "stream", type: "string", usage: "Stream name or ID (for create)" },
|
|
23
|
-
{ name: "appetite", type: "string", usage: "Appetite: unknown, small_batch, big_batch (
|
|
29
|
+
{ name: "stream", type: "string", usage: "Stream name or ID (for create/update)" },
|
|
30
|
+
{ name: "appetite", type: "string", usage: "Appetite: unknown, small_batch, big_batch (create default: big_batch)" },
|
|
24
31
|
{ name: "cycle-id", type: "string", usage: "Assign to cycle ID (for create)" },
|
|
32
|
+
{ name: "title", type: "string", usage: "New title (for update)" },
|
|
33
|
+
{ name: "content", type: "string", usage: "New description (for update)" },
|
|
34
|
+
{ name: "predecessor", type: "string", usage: "Predecessor pitch ID (for extend)" },
|
|
25
35
|
{ name: "no-comments", type: "bool", usage: "Hide embedded comments on show (default: show)" },
|
|
26
36
|
{ name: "comments-limit", type: "integer", usage: "Max comments to embed on show (default: 10, max: 50)" }
|
|
27
37
|
],
|
|
@@ -33,7 +43,13 @@ module ShapeupCli
|
|
|
33
43
|
"shapeup pitch 42",
|
|
34
44
|
"shapeup pitch 42 --json",
|
|
35
45
|
"shapeup pitches create \"Redesign Search\" --stream \"Platform\"",
|
|
36
|
-
"shapeup pitches create \"Auth Overhaul\" --stream \"Platform\" --appetite small_batch"
|
|
46
|
+
"shapeup pitches create \"Auth Overhaul\" --stream \"Platform\" --appetite small_batch",
|
|
47
|
+
"shapeup pitches update 42 --status shaped",
|
|
48
|
+
"shapeup pitches update 42 --title \"Redesign Search v2\" --appetite small_batch",
|
|
49
|
+
"shapeup pitches update 42 --cycle 5",
|
|
50
|
+
"shapeup pitches extend 42 --predecessor 30",
|
|
51
|
+
"shapeup pitches detach 42",
|
|
52
|
+
"shapeup pitches delete 42"
|
|
37
53
|
]
|
|
38
54
|
}
|
|
39
55
|
end
|
|
@@ -44,6 +60,12 @@ module ShapeupCli
|
|
|
44
60
|
case subcommand
|
|
45
61
|
when "show" then show
|
|
46
62
|
when "create" then create
|
|
63
|
+
when "update" then update
|
|
64
|
+
when "extend" then extend_pitch
|
|
65
|
+
when "detach" then detach
|
|
66
|
+
when "delete" then delete
|
|
67
|
+
when "assign" then assign_to("Package", "pitches")
|
|
68
|
+
when "unassign" then unassign_from("Package", "pitches")
|
|
47
69
|
when "list", nil then list
|
|
48
70
|
when "help" then help
|
|
49
71
|
else
|
|
@@ -140,6 +162,68 @@ module ShapeupCli
|
|
|
140
162
|
]
|
|
141
163
|
end
|
|
142
164
|
|
|
165
|
+
def update
|
|
166
|
+
id = positional_arg(1) || abort("Usage: shapeup pitches update <id> [--title \"...\"] [--status idea|framed|shaped] [--appetite ...] [--stream \"Name\"] [--content \"...\"] [--cycle <id>]")
|
|
167
|
+
|
|
168
|
+
args = { package: id.to_s }
|
|
169
|
+
args[:title] = extract_option("--title") if @remaining.include?("--title")
|
|
170
|
+
args[:status] = extract_option("--status") if @remaining.include?("--status")
|
|
171
|
+
args[:appetite] = extract_option("--appetite") if @remaining.include?("--appetite")
|
|
172
|
+
args[:stream] = extract_option("--stream") if @remaining.include?("--stream")
|
|
173
|
+
args[:content] = extract_option("--content") if @remaining.include?("--content")
|
|
174
|
+
args[:cycle] = extract_option("--cycle") if @remaining.include?("--cycle")
|
|
175
|
+
|
|
176
|
+
abort("Nothing to update. Pass at least one of --title, --status, --appetite, --stream, --content, --cycle") if args.size == 1
|
|
177
|
+
|
|
178
|
+
result = call_tool("update_package", **args)
|
|
179
|
+
|
|
180
|
+
render result,
|
|
181
|
+
summary: "Pitch ##{id} updated",
|
|
182
|
+
breadcrumbs: [
|
|
183
|
+
{ cmd: "shapeup pitch #{id}", description: "View pitch details" }
|
|
184
|
+
]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def extend_pitch
|
|
188
|
+
id = positional_arg(1) || abort("Usage: shapeup pitches extend <id> --predecessor <id>")
|
|
189
|
+
predecessor = extract_option("--predecessor") || abort("Usage: shapeup pitches extend <id> --predecessor <id>")
|
|
190
|
+
|
|
191
|
+
result = call_tool("extend_package", package: id.to_s, predecessor: predecessor.to_s)
|
|
192
|
+
|
|
193
|
+
render result,
|
|
194
|
+
summary: "Pitch ##{id} now extends ##{predecessor}",
|
|
195
|
+
breadcrumbs: [
|
|
196
|
+
{ cmd: "shapeup pitch #{id}", description: "View pitch details" },
|
|
197
|
+
{ cmd: "shapeup pitches detach #{id}", description: "Remove the predecessor link" }
|
|
198
|
+
]
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def detach
|
|
202
|
+
id = positional_arg(1) || abort("Usage: shapeup pitches detach <id>")
|
|
203
|
+
|
|
204
|
+
result = call_tool("detach_package", package: id.to_s)
|
|
205
|
+
|
|
206
|
+
render result,
|
|
207
|
+
summary: "Pitch ##{id} detached from its predecessor",
|
|
208
|
+
breadcrumbs: [
|
|
209
|
+
{ cmd: "shapeup pitch #{id}", description: "View pitch details" }
|
|
210
|
+
]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def delete
|
|
214
|
+
yes = assume_yes?
|
|
215
|
+
id = positional_arg(1) || abort("Usage: shapeup pitches delete <id> [--yes]")
|
|
216
|
+
confirm_destructive!("Delete pitch ##{id}", yes)
|
|
217
|
+
|
|
218
|
+
result = call_tool("delete_package", package: id.to_s)
|
|
219
|
+
|
|
220
|
+
render result,
|
|
221
|
+
summary: "Pitch ##{id} deleted",
|
|
222
|
+
breadcrumbs: [
|
|
223
|
+
{ cmd: "shapeup pitches list", description: "List remaining pitches" }
|
|
224
|
+
]
|
|
225
|
+
end
|
|
226
|
+
|
|
143
227
|
def help
|
|
144
228
|
puts <<~HELP
|
|
145
229
|
Usage: shapeup pitches <subcommand> [options]
|
|
@@ -10,11 +10,17 @@ module ShapeupCli
|
|
|
10
10
|
short: "Manage scopes within a pitch",
|
|
11
11
|
subcommands: [
|
|
12
12
|
{ name: "list", short: "List scopes for a pitch", path: "shapeup scopes list --pitch <id>" },
|
|
13
|
+
{ name: "show", short: "Show a scope with its tasks", path: "shapeup scopes show <id>" },
|
|
13
14
|
{ name: "create", short: "Create a new scope", path: "shapeup scopes create --pitch <id> \"Title\"" },
|
|
14
15
|
{ name: "update", short: "Update scope title or color", path: "shapeup scopes update <id> --title \"New\"" },
|
|
15
|
-
{ name: "position", short: "Update hill chart position (0-100)", path: "shapeup scopes position <id> <position>" }
|
|
16
|
+
{ name: "position", short: "Update hill chart position (0-100)", path: "shapeup scopes position <id> <position>" },
|
|
17
|
+
{ name: "history", short: "Show hill chart trajectory", path: "shapeup scopes history <id>" },
|
|
18
|
+
{ name: "delete", short: "Delete a scope (must have no tasks or comments)", path: "shapeup scopes delete <id>" },
|
|
19
|
+
{ name: "assign", short: "Assign a user", path: "shapeup scopes assign <id> [--user <id>]" },
|
|
20
|
+
{ name: "unassign", short: "Unassign a user", path: "shapeup scopes unassign <id> [--user <id>]" }
|
|
16
21
|
],
|
|
17
22
|
flags: [
|
|
23
|
+
{ name: "user", type: "string", usage: "User ID or 'me' (assign/unassign; defaults to me)" },
|
|
18
24
|
{ name: "pitch", type: "string", usage: "Pitch ID (required for list and create)" },
|
|
19
25
|
{ name: "title", type: "string", usage: "Scope title (for create/update)" },
|
|
20
26
|
{ name: "color", type: "string", usage: "Hex color code (for update)" }
|
|
@@ -23,7 +29,8 @@ module ShapeupCli
|
|
|
23
29
|
"shapeup scopes list --pitch 42",
|
|
24
30
|
"shapeup scopes create --pitch 42 \"User onboarding\"",
|
|
25
31
|
"shapeup scopes update 7 --title \"Revised onboarding\"",
|
|
26
|
-
"shapeup scopes position 7 50"
|
|
32
|
+
"shapeup scopes position 7 50",
|
|
33
|
+
"shapeup scopes delete 7"
|
|
27
34
|
]
|
|
28
35
|
}
|
|
29
36
|
end
|
|
@@ -32,9 +39,14 @@ module ShapeupCli
|
|
|
32
39
|
subcommand = positional_arg(0)
|
|
33
40
|
|
|
34
41
|
case subcommand
|
|
42
|
+
when "show" then show
|
|
35
43
|
when "create" then create
|
|
36
44
|
when "update" then update
|
|
37
45
|
when "position" then position
|
|
46
|
+
when "history" then history
|
|
47
|
+
when "delete" then delete
|
|
48
|
+
when "assign" then assign_to("Scope", "scopes")
|
|
49
|
+
when "unassign" then unassign_from("Scope", "scopes")
|
|
38
50
|
when "list", nil then list
|
|
39
51
|
else list
|
|
40
52
|
end
|
|
@@ -44,13 +56,37 @@ module ShapeupCli
|
|
|
44
56
|
def list
|
|
45
57
|
pitch_id = extract_option("--pitch") || abort("Usage: shapeup scopes list --pitch <id>")
|
|
46
58
|
|
|
47
|
-
result = call_tool("
|
|
59
|
+
result = call_tool("list_scopes", package: pitch_id.to_s)
|
|
48
60
|
|
|
49
61
|
render result,
|
|
50
62
|
summary: "Scopes for Pitch ##{pitch_id}",
|
|
51
63
|
breadcrumbs: [
|
|
52
|
-
{ cmd: "shapeup scopes
|
|
53
|
-
{ cmd: "shapeup
|
|
64
|
+
{ cmd: "shapeup scopes show <id>", description: "Show a scope with its tasks" },
|
|
65
|
+
{ cmd: "shapeup scopes create --pitch #{pitch_id} \"Title\"", description: "Add a scope" }
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def show
|
|
70
|
+
scope_id = positional_arg(1) || abort("Usage: shapeup scopes show <id>")
|
|
71
|
+
|
|
72
|
+
result = call_tool("show_scope", scope: scope_id.to_s)
|
|
73
|
+
|
|
74
|
+
render result,
|
|
75
|
+
summary: "Scope ##{scope_id}",
|
|
76
|
+
breadcrumbs: [
|
|
77
|
+
{ cmd: "shapeup scopes history #{scope_id}", description: "Hill chart trajectory" }
|
|
78
|
+
]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def history
|
|
82
|
+
scope_id = positional_arg(1) || abort("Usage: shapeup scopes history <id>")
|
|
83
|
+
|
|
84
|
+
result = call_tool("list_scope_history", scope: scope_id.to_s)
|
|
85
|
+
|
|
86
|
+
render result,
|
|
87
|
+
summary: "Scope ##{scope_id} history",
|
|
88
|
+
breadcrumbs: [
|
|
89
|
+
{ cmd: "shapeup scopes position #{scope_id} <0-100>", description: "Update hill position" }
|
|
54
90
|
]
|
|
55
91
|
end
|
|
56
92
|
|
|
@@ -91,6 +127,20 @@ module ShapeupCli
|
|
|
91
127
|
|
|
92
128
|
render result, summary: "Scope ##{scope_id} updated"
|
|
93
129
|
end
|
|
130
|
+
|
|
131
|
+
def delete
|
|
132
|
+
yes = assume_yes?
|
|
133
|
+
scope_id = positional_arg(1) || abort("Usage: shapeup scopes delete <id> [--yes]")
|
|
134
|
+
confirm_destructive!("Delete scope ##{scope_id}", yes)
|
|
135
|
+
|
|
136
|
+
result = call_tool("delete_scope", scope: scope_id.to_s)
|
|
137
|
+
|
|
138
|
+
render result,
|
|
139
|
+
summary: "Scope ##{scope_id} deleted",
|
|
140
|
+
breadcrumbs: [
|
|
141
|
+
{ cmd: "shapeup scopes list --pitch <id>", description: "List remaining scopes" }
|
|
142
|
+
]
|
|
143
|
+
end
|
|
94
144
|
end
|
|
95
145
|
end
|
|
96
146
|
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShapeupCli
|
|
4
|
+
module Commands
|
|
5
|
+
class Streams < Base
|
|
6
|
+
def self.metadata
|
|
7
|
+
{
|
|
8
|
+
command: "streams",
|
|
9
|
+
path: "shapeup streams",
|
|
10
|
+
short: "List and show streams (product areas)",
|
|
11
|
+
subcommands: [
|
|
12
|
+
{ name: "list", short: "List streams (default)", path: "shapeup streams list" },
|
|
13
|
+
{ name: "show", short: "Show stream details with pitches and issue counts", path: "shapeup streams show <id>" },
|
|
14
|
+
{ name: "create", short: "Create a stream", path: "shapeup streams create \"Title\"" },
|
|
15
|
+
{ name: "edit", short: "Update a stream's title, description, or color", path: "shapeup streams edit <id> --title \"New\"" }
|
|
16
|
+
],
|
|
17
|
+
flags: [
|
|
18
|
+
{ name: "all", type: "bool", usage: "Include archived streams (for list)" },
|
|
19
|
+
{ name: "title", type: "string", usage: "Stream title (create/edit)" },
|
|
20
|
+
{ name: "description", type: "string", usage: "Stream description (create/edit)" },
|
|
21
|
+
{ name: "color", type: "string", usage: "Hex color code (edit)" }
|
|
22
|
+
],
|
|
23
|
+
examples: [
|
|
24
|
+
"shapeup streams",
|
|
25
|
+
"shapeup streams --all --json",
|
|
26
|
+
"shapeup streams show 3",
|
|
27
|
+
"shapeup streams create \"Platform\"",
|
|
28
|
+
"shapeup streams edit 3 --title \"Platform & Infra\" --color \"#3b82f6\""
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def execute
|
|
34
|
+
subcommand = positional_arg(0)
|
|
35
|
+
|
|
36
|
+
case subcommand
|
|
37
|
+
when "show" then show
|
|
38
|
+
when "create" then create
|
|
39
|
+
when "edit" then edit
|
|
40
|
+
when "list", nil then list
|
|
41
|
+
else
|
|
42
|
+
subcommand.match?(/\A\d+\z/) ? show(subcommand) : list
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
def create
|
|
48
|
+
title = positional_arg(1) || abort("Usage: shapeup streams create \"Title\" [--description \"..\"]")
|
|
49
|
+
args = { title: title }
|
|
50
|
+
args[:description] = extract_option("--description") if @remaining.include?("--description")
|
|
51
|
+
|
|
52
|
+
result = call_tool("create_stream", **args)
|
|
53
|
+
|
|
54
|
+
render result,
|
|
55
|
+
summary: "Stream created",
|
|
56
|
+
breadcrumbs: [ { cmd: "shapeup streams edit <id> --color \"#3b82f6\"", description: "Set its colour" } ]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def edit
|
|
60
|
+
id = positional_arg(1) || abort("Usage: shapeup streams edit <id> [--title ..] [--description ..] [--color ..]")
|
|
61
|
+
args = { stream: id.to_s }
|
|
62
|
+
args[:title] = extract_option("--title") if @remaining.include?("--title")
|
|
63
|
+
args[:description] = extract_option("--description") if @remaining.include?("--description")
|
|
64
|
+
args[:color] = extract_option("--color") if @remaining.include?("--color")
|
|
65
|
+
|
|
66
|
+
result = call_tool("update_stream", **args)
|
|
67
|
+
|
|
68
|
+
render result, summary: "Stream ##{id} updated"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def list
|
|
72
|
+
args = {}
|
|
73
|
+
args[:include_archived] = true if consume_flag("--all")
|
|
74
|
+
|
|
75
|
+
result = call_tool("list_streams", **args)
|
|
76
|
+
|
|
77
|
+
render result,
|
|
78
|
+
summary: "Streams",
|
|
79
|
+
breadcrumbs: [
|
|
80
|
+
{ cmd: "shapeup streams show <id>", description: "View stream details" },
|
|
81
|
+
{ cmd: "shapeup pitches create \"Title\" --stream \"Name\"", description: "Create a pitch in a stream" }
|
|
82
|
+
]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def show(id = nil)
|
|
86
|
+
id ||= positional_arg(1) || abort("Usage: shapeup streams show <id>")
|
|
87
|
+
|
|
88
|
+
result = call_tool("show_stream", stream: id.to_s)
|
|
89
|
+
|
|
90
|
+
render result,
|
|
91
|
+
summary: "Stream ##{id}",
|
|
92
|
+
breadcrumbs: [
|
|
93
|
+
{ cmd: "shapeup pitches list --json", description: "List pitches" },
|
|
94
|
+
{ cmd: "shapeup issues --stream \"Name\"", description: "List issues in this stream" }
|
|
95
|
+
]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -8,23 +8,35 @@ module ShapeupCli
|
|
|
8
8
|
command: "tasks",
|
|
9
9
|
path: "shapeup tasks",
|
|
10
10
|
short: "Manage tasks within scopes and pitches",
|
|
11
|
-
aliases: { "todo" => "tasks create", "done" => "tasks complete" },
|
|
11
|
+
aliases: { "todo" => "tasks create", "done" => "tasks complete", "undone" => "tasks uncomplete" },
|
|
12
12
|
subcommands: [
|
|
13
13
|
{ name: "list", short: "List tasks", path: "shapeup tasks list" },
|
|
14
14
|
{ name: "create", short: "Create a task", path: "shapeup todo \"Description\" --pitch <id>" },
|
|
15
|
-
{ name: "complete", short: "Mark task(s) as complete", path: "shapeup done <id> [<id>...]" }
|
|
15
|
+
{ name: "complete", short: "Mark task(s) as complete", path: "shapeup done <id> [<id>...]" },
|
|
16
|
+
{ name: "uncomplete", short: "Mark task as incomplete", path: "shapeup undone <id>" },
|
|
17
|
+
{ name: "update", short: "Edit a task's description or move it to a scope", path: "shapeup tasks update <id> --description \"New\"" },
|
|
18
|
+
{ name: "delete", short: "Delete a task", path: "shapeup tasks delete <id>" },
|
|
19
|
+
{ name: "assign", short: "Assign a user", path: "shapeup tasks assign <id> [--user <id>]" },
|
|
20
|
+
{ name: "unassign", short: "Unassign a user", path: "shapeup tasks unassign <id> [--user <id>]" }
|
|
16
21
|
],
|
|
17
22
|
flags: [
|
|
23
|
+
{ name: "user", type: "string", usage: "User ID or 'me' (assign/unassign; defaults to me)" },
|
|
18
24
|
{ name: "pitch", type: "string", usage: "Pitch ID (required for create)" },
|
|
19
|
-
{ name: "scope", type: "string", usage: "Scope ID (
|
|
20
|
-
{ name: "assignee", type: "string", usage: "User ID or 'me' (for list)" }
|
|
25
|
+
{ name: "scope", type: "string", usage: "Scope ID (filter, create target, or update target; use 'none' to unmap)" },
|
|
26
|
+
{ name: "assignee", type: "string", usage: "User ID or 'me' (for list)" },
|
|
27
|
+
{ name: "description", type: "string", usage: "New description (for update)" }
|
|
21
28
|
],
|
|
22
29
|
examples: [
|
|
23
30
|
"shapeup tasks list --pitch 42",
|
|
24
31
|
"shapeup tasks list --assignee me",
|
|
25
32
|
"shapeup todo \"Fix login bug\" --pitch 42 --scope 7",
|
|
26
33
|
"shapeup done 123",
|
|
27
|
-
"shapeup done 123 124 125"
|
|
34
|
+
"shapeup done 123 124 125",
|
|
35
|
+
"shapeup undone 123",
|
|
36
|
+
"shapeup tasks update 123 --description \"Fix login + signup bug\"",
|
|
37
|
+
"shapeup tasks update 123 --scope 7",
|
|
38
|
+
"shapeup tasks update 123 --scope none",
|
|
39
|
+
"shapeup tasks delete 123"
|
|
28
40
|
]
|
|
29
41
|
}
|
|
30
42
|
end
|
|
@@ -33,9 +45,14 @@ module ShapeupCli
|
|
|
33
45
|
subcommand = positional_arg(0)
|
|
34
46
|
|
|
35
47
|
case subcommand
|
|
36
|
-
when "create"
|
|
37
|
-
when "complete"
|
|
38
|
-
when "
|
|
48
|
+
when "create" then create
|
|
49
|
+
when "complete" then complete
|
|
50
|
+
when "uncomplete" then uncomplete
|
|
51
|
+
when "update" then update
|
|
52
|
+
when "delete" then delete
|
|
53
|
+
when "assign" then assign_to("Task", "tasks")
|
|
54
|
+
when "unassign" then unassign_from("Task", "tasks")
|
|
55
|
+
when "list", nil then list
|
|
39
56
|
else list
|
|
40
57
|
end
|
|
41
58
|
end
|
|
@@ -91,10 +108,56 @@ module ShapeupCli
|
|
|
91
108
|
render result,
|
|
92
109
|
summary: "Task ##{id} completed",
|
|
93
110
|
breadcrumbs: [
|
|
111
|
+
{ cmd: "shapeup undone #{id}", description: "Mark incomplete again" },
|
|
94
112
|
{ cmd: "shapeup me", description: "Show remaining work" }
|
|
95
113
|
]
|
|
96
114
|
end
|
|
97
115
|
end
|
|
116
|
+
|
|
117
|
+
def uncomplete
|
|
118
|
+
id = positional_arg(1) || abort("Usage: shapeup undone <id>")
|
|
119
|
+
|
|
120
|
+
result = call_tool("uncomplete_task", task: id.to_s)
|
|
121
|
+
|
|
122
|
+
render result,
|
|
123
|
+
summary: "Task ##{id} marked incomplete",
|
|
124
|
+
breadcrumbs: [
|
|
125
|
+
{ cmd: "shapeup done #{id}", description: "Complete it again" }
|
|
126
|
+
]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def update
|
|
130
|
+
id = positional_arg(1) || abort("Usage: shapeup tasks update <id> [--description \"New\"] [--scope <id>|none]")
|
|
131
|
+
description = extract_option("--description")
|
|
132
|
+
scope = extract_option("--scope")
|
|
133
|
+
abort("Nothing to update. Pass --description \"...\" and/or --scope <id>|none") unless description || scope
|
|
134
|
+
|
|
135
|
+
args = { task: id.to_s }
|
|
136
|
+
args[:description] = description if description
|
|
137
|
+
args[:scope] = scope if scope
|
|
138
|
+
|
|
139
|
+
result = call_tool("update_task", **args)
|
|
140
|
+
|
|
141
|
+
render result,
|
|
142
|
+
summary: "Task ##{id} updated",
|
|
143
|
+
breadcrumbs: [
|
|
144
|
+
{ cmd: "shapeup tasks list --pitch <id>", description: "List tasks" }
|
|
145
|
+
]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def delete
|
|
149
|
+
yes = assume_yes?
|
|
150
|
+
id = positional_arg(1) || abort("Usage: shapeup tasks delete <id> [--yes]")
|
|
151
|
+
confirm_destructive!("Delete task ##{id}", yes)
|
|
152
|
+
|
|
153
|
+
result = call_tool("delete_task", task: id.to_s)
|
|
154
|
+
|
|
155
|
+
render result,
|
|
156
|
+
summary: "Task ##{id} deleted",
|
|
157
|
+
breadcrumbs: [
|
|
158
|
+
{ cmd: "shapeup tasks list --pitch <id>", description: "List remaining tasks" }
|
|
159
|
+
]
|
|
160
|
+
end
|
|
98
161
|
end
|
|
99
162
|
end
|
|
100
163
|
end
|
data/lib/shapeup_cli/commands.rb
CHANGED
|
@@ -24,20 +24,35 @@ module ShapeupCli
|
|
|
24
24
|
pitches list List pitches (packages)
|
|
25
25
|
pitches show <id> Show pitch details with scopes and tasks
|
|
26
26
|
pitch <id> Shortcut for pitches show
|
|
27
|
+
pitches create "Title" --stream "Name" [--appetite small_batch] [--cycle-id <id>]
|
|
28
|
+
pitches update <id> [--title|--status|--appetite|--stream|--content|--cycle ...]
|
|
29
|
+
pitches extend <id> --predecessor <id> Continue a previous pitch
|
|
30
|
+
pitches detach <id> Remove the predecessor link
|
|
31
|
+
pitches delete <id> Delete a pitch (asks to confirm; --yes to skip)
|
|
27
32
|
|
|
28
33
|
Cycles:
|
|
29
34
|
cycles List all cycles
|
|
30
35
|
cycle show <id> Show cycle details with pitches and progress
|
|
31
36
|
|
|
37
|
+
Streams:
|
|
38
|
+
streams List streams (product areas)
|
|
39
|
+
streams --all Include archived streams
|
|
40
|
+
streams show <id> Show stream details
|
|
41
|
+
|
|
32
42
|
Scopes:
|
|
33
43
|
scopes list --pitch <id> List scopes for a pitch
|
|
34
44
|
scopes create --pitch <id> "Title"
|
|
35
45
|
scopes update <id> --title "New title"
|
|
46
|
+
scopes position <id> <0-100> Update hill chart position
|
|
47
|
+
scopes delete <id> Delete a scope (asks to confirm; --yes to skip)
|
|
36
48
|
|
|
37
49
|
Tasks:
|
|
38
50
|
tasks list --scope <id> List tasks for a scope
|
|
39
51
|
todo "Description" --pitch <id> [--scope <id>]
|
|
40
52
|
done <id> [<id>...] Mark task(s) as complete
|
|
53
|
+
undone <id> Mark a task as incomplete
|
|
54
|
+
tasks update <id> [--description "New"] [--scope <id>|none]
|
|
55
|
+
tasks delete <id> Delete a task (asks to confirm; --yes to skip)
|
|
41
56
|
|
|
42
57
|
Issues:
|
|
43
58
|
issues List open issues
|
|
@@ -61,7 +76,7 @@ module ShapeupCli
|
|
|
61
76
|
issues watch <id> Watch an issue
|
|
62
77
|
issues unwatch <id> Stop watching
|
|
63
78
|
watching List issues you are watching
|
|
64
|
-
issues delete <id> Delete an issue
|
|
79
|
+
issues delete <id> Delete an issue (asks to confirm; --yes to skip)
|
|
65
80
|
issues convert <id> Convert issue to a new pitch
|
|
66
81
|
issues add-to-pitch <id> --pitch <pid>
|
|
67
82
|
Fold issue into an existing pitch
|
|
@@ -71,6 +86,8 @@ module ShapeupCli
|
|
|
71
86
|
comments list --pitch <id> List comments on a pitch
|
|
72
87
|
comments add --issue <id> "Text" Add a comment to an issue
|
|
73
88
|
comments add --pitch <id> "Text" Add a comment to a pitch
|
|
89
|
+
comments edit <comment_id> "Text" Edit your own comment
|
|
90
|
+
comments remove <comment_id> Delete your own comment (asks to confirm)
|
|
74
91
|
|
|
75
92
|
Checklist:
|
|
76
93
|
checklist --pitch <id> List the checklist on a pitch
|
|
@@ -116,6 +133,8 @@ module ShapeupCli
|
|
|
116
133
|
Flags:
|
|
117
134
|
--org <id|name> Override default organisation
|
|
118
135
|
--host <url> Override ShapeUp host (default: https://shapeup.cc)
|
|
136
|
+
--yes, -y Skip the confirmation prompt on delete commands
|
|
137
|
+
(required when deleting non-interactively)
|
|
119
138
|
|
|
120
139
|
Environment variables:
|
|
121
140
|
SHAPEUP_TOKEN Bearer token (skips OAuth, for CI/scripts)
|
|
@@ -139,18 +158,20 @@ module ShapeupCli
|
|
|
139
158
|
logout Clear all credentials
|
|
140
159
|
auth Manage profiles (status, list, switch, remove)
|
|
141
160
|
orgs List organisations
|
|
142
|
-
pitches
|
|
161
|
+
pitches Manage pitches (list, show, create, update, extend, detach, delete)
|
|
143
162
|
pitch Show a pitch (shortcut)
|
|
144
163
|
cycles List cycles
|
|
145
164
|
cycle Show cycle details (list, show)
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
streams List/show streams (list, show)
|
|
166
|
+
scopes Manage scopes (list, create, update, position, delete)
|
|
167
|
+
tasks Manage tasks (list, create, complete, uncomplete, update, delete)
|
|
148
168
|
todo Create a task (shortcut)
|
|
149
169
|
done Complete task(s) (shortcut)
|
|
150
|
-
|
|
170
|
+
undone Mark a task incomplete (shortcut)
|
|
171
|
+
issues Manage issues (list, show, create, move, icebox, watch, convert, add-to-pitch, delete)
|
|
151
172
|
issue Show an issue (shortcut)
|
|
152
173
|
watching List watched issues (shortcut)
|
|
153
|
-
comments
|
|
174
|
+
comments Manage comments (list, add, edit, remove)
|
|
154
175
|
checklist Manage checklist items on pitches/issues (list, add, tick, untick, edit, remove)
|
|
155
176
|
tags List/add/remove tags on pitches and issues
|
|
156
177
|
my-work / me Show my assigned work
|
|
@@ -171,6 +192,7 @@ require_relative "commands/logout"
|
|
|
171
192
|
require_relative "commands/orgs"
|
|
172
193
|
require_relative "commands/pitches"
|
|
173
194
|
require_relative "commands/cycle"
|
|
195
|
+
require_relative "commands/streams"
|
|
174
196
|
require_relative "commands/scopes"
|
|
175
197
|
require_relative "commands/tasks"
|
|
176
198
|
require_relative "commands/issues"
|
data/lib/shapeup_cli/output.rb
CHANGED
|
@@ -92,7 +92,7 @@ module ShapeupCli
|
|
|
92
92
|
when Hash
|
|
93
93
|
render_markdown_hash(data)
|
|
94
94
|
else
|
|
95
|
-
puts data
|
|
95
|
+
puts data
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
@@ -106,7 +106,7 @@ module ShapeupCli
|
|
|
106
106
|
when Hash
|
|
107
107
|
render_styled_hash(data)
|
|
108
108
|
else
|
|
109
|
-
puts data
|
|
109
|
+
puts data
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
if breadcrumbs.any?
|
|
@@ -177,7 +177,7 @@ module ShapeupCli
|
|
|
177
177
|
when Hash
|
|
178
178
|
id = value["id"]
|
|
179
179
|
label = value["title"] || value["name"] || value["description"]
|
|
180
|
-
[id, label].compact.join(" ")
|
|
180
|
+
[ id, label ].compact.join(" ")
|
|
181
181
|
else
|
|
182
182
|
value.to_s
|
|
183
183
|
end
|
data/lib/shapeup_cli.rb
CHANGED
|
@@ -9,6 +9,7 @@ require "digest"
|
|
|
9
9
|
require "base64"
|
|
10
10
|
require "socket"
|
|
11
11
|
|
|
12
|
+
require_relative "shapeup_cli/version"
|
|
12
13
|
require_relative "shapeup_cli/config"
|
|
13
14
|
require_relative "shapeup_cli/auth"
|
|
14
15
|
require_relative "shapeup_cli/client"
|
|
@@ -16,7 +17,6 @@ require_relative "shapeup_cli/output"
|
|
|
16
17
|
require_relative "shapeup_cli/commands"
|
|
17
18
|
|
|
18
19
|
module ShapeupCli
|
|
19
|
-
VERSION = "0.3.4"
|
|
20
20
|
DEFAULT_HOST = "https://shapeup.cc"
|
|
21
21
|
|
|
22
22
|
# Exit codes
|
|
@@ -33,6 +33,7 @@ module ShapeupCli
|
|
|
33
33
|
"orgs" => Commands::Orgs,
|
|
34
34
|
"pitches" => Commands::Pitches,
|
|
35
35
|
"cycle" => Commands::Cycle,
|
|
36
|
+
"streams" => Commands::Streams,
|
|
36
37
|
"scopes" => Commands::Scopes,
|
|
37
38
|
"tasks" => Commands::Tasks,
|
|
38
39
|
"issues" => Commands::Issues,
|
|
@@ -68,16 +69,18 @@ module ShapeupCli
|
|
|
68
69
|
when "auth" then Commands::Auth.run(args)
|
|
69
70
|
when "orgs" then Commands::Orgs.run(args)
|
|
70
71
|
when "pitches" then Commands::Pitches.run(args)
|
|
71
|
-
when "pitch" then Commands::Pitches.run(["show"] + args)
|
|
72
|
+
when "pitch" then Commands::Pitches.run([ "show" ] + args)
|
|
72
73
|
when "cycle" then Commands::Cycle.run(args)
|
|
73
|
-
when "cycles" then Commands::Cycle.run(["list"] + args)
|
|
74
|
+
when "cycles" then Commands::Cycle.run([ "list" ] + args)
|
|
75
|
+
when "streams" then Commands::Streams.run(args)
|
|
74
76
|
when "scopes" then Commands::Scopes.run(args)
|
|
75
77
|
when "tasks" then Commands::Tasks.run(args)
|
|
76
|
-
when "todo" then Commands::Tasks.run(["create"] + args)
|
|
77
|
-
when "done" then Commands::Tasks.run(["complete"] + args)
|
|
78
|
+
when "todo" then Commands::Tasks.run([ "create" ] + args)
|
|
79
|
+
when "done" then Commands::Tasks.run([ "complete" ] + args)
|
|
80
|
+
when "undone" then Commands::Tasks.run([ "uncomplete" ] + args)
|
|
78
81
|
when "issues" then Commands::Issues.run(args)
|
|
79
|
-
when "issue" then Commands::Issues.run(["show"] + args)
|
|
80
|
-
when "watching" then Commands::Issues.run(["watching"] + args)
|
|
82
|
+
when "issue" then Commands::Issues.run([ "show" ] + args)
|
|
83
|
+
when "watching" then Commands::Issues.run([ "watching" ] + args)
|
|
81
84
|
when "comments" then Commands::Comments.run(args)
|
|
82
85
|
when "checklist" then Commands::Checklist.run(args)
|
|
83
86
|
when "tags" then Commands::Tags.run(args)
|
|
@@ -126,6 +129,7 @@ module ShapeupCli
|
|
|
126
129
|
"cycles" => "cycle list",
|
|
127
130
|
"todo \"...\"" => "tasks create \"...\"",
|
|
128
131
|
"done <id>" => "tasks complete <id>",
|
|
132
|
+
"undone <id>" => "tasks uncomplete <id>",
|
|
129
133
|
"issue <id>" => "issues show <id>",
|
|
130
134
|
"watching" => "issues watching",
|
|
131
135
|
"me" => "my-work"
|
data/skills/shapeup/SKILL.md
CHANGED
|
@@ -78,6 +78,7 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
|
|
|
78
78
|
5. **"Pitch" = "Package" in code** — users say "pitch", the API uses "package". The CLI uses "pitch" everywhere.
|
|
79
79
|
6. **Use 'me' and 'none'** — `--assignee me` for current user, `--assignee none` for unassigned items.
|
|
80
80
|
7. **Check exit codes** — 0=OK, 2=not found, 3=auth error, 4=permission denied, 5=API error. Branch on exit code without parsing error text.
|
|
81
|
+
8. **Deletes need confirmation** — `delete`/`remove` commands (pitches, scopes, tasks, issues, comments) prompt `[y/N]` interactively and **refuse when run non-interactively** unless you pass `--yes` (or `-y`). As an agent you have no TTY, so add `--yes` only after you have confirmed the deletion is intended.
|
|
81
82
|
|
|
82
83
|
### Output Modes
|
|
83
84
|
|
|
@@ -124,6 +125,7 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
|
|
|
124
125
|
| Filter by stream | `shapeup issues --stream "Platform" --json` |
|
|
125
126
|
| Show issue detail | `shapeup issue <id> --json` |
|
|
126
127
|
| Create issue | `shapeup issues create "Title" --stream "Platform"` |
|
|
128
|
+
| List kanban columns | `shapeup issues columns --json` |
|
|
127
129
|
| Move to column | `shapeup issues move <id> --column doing` |
|
|
128
130
|
| Mark issue done | `shapeup issues done <id>` |
|
|
129
131
|
| Close issue (won't fix) | `shapeup issues close <id>` |
|
|
@@ -140,6 +142,8 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
|
|
|
140
142
|
| List comments on pitch | `shapeup comments list --pitch <id> --json` |
|
|
141
143
|
| Add comment to issue | `shapeup comments add --issue <id> "Comment text"` |
|
|
142
144
|
| Add comment to pitch | `shapeup comments add --pitch <id> "Comment text"` |
|
|
145
|
+
| Edit your comment | `shapeup comments edit <comment_id> "New text"` |
|
|
146
|
+
| Delete your comment | `shapeup comments remove <comment_id> --yes` |
|
|
143
147
|
| **Checklist** | |
|
|
144
148
|
| List checklist on pitch | `shapeup checklist --pitch <id>` |
|
|
145
149
|
| List checklist on issue | `shapeup checklist --issue <id>` |
|
|
@@ -159,17 +163,42 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
|
|
|
159
163
|
| Show pitch detail | `shapeup pitch <id> --json` |
|
|
160
164
|
| Create pitch | `shapeup pitches create "Title" --stream "Name"` |
|
|
161
165
|
| Create with appetite | `shapeup pitches create "Title" --stream "Name" --appetite small_batch` |
|
|
166
|
+
| Update pitch | `shapeup pitches update <id> --status shaped` |
|
|
167
|
+
| Extend a pitch | `shapeup pitches extend <id> --predecessor <id>` |
|
|
168
|
+
| Detach predecessor | `shapeup pitches detach <id>` |
|
|
169
|
+
| Delete pitch | `shapeup pitches delete <id> --yes` |
|
|
170
|
+
| Assign to pitch | `shapeup pitches assign <id>` (self) / `--user <id>` |
|
|
171
|
+
| Unassign from pitch | `shapeup pitches unassign <id>` (self) / `--user <id>` |
|
|
162
172
|
| **Cycles** | |
|
|
163
173
|
| List cycles | `shapeup cycles --json` |
|
|
164
174
|
| Active cycles | `shapeup cycles --status active --json` |
|
|
165
175
|
| Show cycle | `shapeup cycle show <id> --json` |
|
|
176
|
+
| Create cycle | `shapeup cycle create "Q3" --start 2026-07-01 --end 2026-08-12` |
|
|
177
|
+
| Edit cycle | `shapeup cycle edit <id> --title "New" --end 2026-08-19` |
|
|
178
|
+
| Delete cycle | `shapeup cycle delete <id> --yes` |
|
|
179
|
+
| **Streams** | |
|
|
180
|
+
| List streams | `shapeup streams --json` |
|
|
181
|
+
| Include archived | `shapeup streams --all --json` |
|
|
182
|
+
| Show stream | `shapeup streams show <id> --json` |
|
|
183
|
+
| Create stream | `shapeup streams create "Platform" --description "..."` |
|
|
184
|
+
| Edit stream | `shapeup streams edit <id> --title "New" --color "#3b82f6"` |
|
|
166
185
|
| **Scopes & Tasks** | |
|
|
167
186
|
| List scopes | `shapeup scopes list --pitch <id> --json` |
|
|
187
|
+
| Show scope + tasks | `shapeup scopes show <id> --json` |
|
|
168
188
|
| Create scope | `shapeup scopes create --pitch <id> "Title"` |
|
|
169
189
|
| Update hill position | `shapeup scopes position <id> <0-100>` |
|
|
190
|
+
| Hill chart history | `shapeup scopes history <id> --json` |
|
|
191
|
+
| Delete scope | `shapeup scopes delete <id> --yes` |
|
|
192
|
+
| Assign to scope | `shapeup scopes assign <id>` (self) / `--user <id>` |
|
|
193
|
+
| Unassign from scope | `shapeup scopes unassign <id>` (self) / `--user <id>` |
|
|
170
194
|
| List tasks | `shapeup tasks list --pitch <id> --json` |
|
|
171
195
|
| Create task | `shapeup todo "Description" --pitch <id>` |
|
|
172
196
|
| Complete task(s) | `shapeup done <id> [<id>...]` |
|
|
197
|
+
| Uncomplete task | `shapeup undone <id>` |
|
|
198
|
+
| Update task | `shapeup tasks update <id> --description "New"` |
|
|
199
|
+
| Delete task | `shapeup tasks delete <id> --yes` |
|
|
200
|
+
| Assign to task | `shapeup tasks assign <id>` (self) / `--user <id>` |
|
|
201
|
+
| Unassign from task | `shapeup tasks unassign <id>` (self) / `--user <id>` |
|
|
173
202
|
| **My Work** | |
|
|
174
203
|
| All my assignments | `shapeup me --json` |
|
|
175
204
|
| My work (alias) | `shapeup my-work --json` |
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shapeup-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ShapeUp
|
|
@@ -40,10 +40,12 @@ files:
|
|
|
40
40
|
- lib/shapeup_cli/commands/scopes.rb
|
|
41
41
|
- lib/shapeup_cli/commands/search.rb
|
|
42
42
|
- lib/shapeup_cli/commands/setup.rb
|
|
43
|
+
- lib/shapeup_cli/commands/streams.rb
|
|
43
44
|
- lib/shapeup_cli/commands/tags.rb
|
|
44
45
|
- lib/shapeup_cli/commands/tasks.rb
|
|
45
46
|
- lib/shapeup_cli/config.rb
|
|
46
47
|
- lib/shapeup_cli/output.rb
|
|
48
|
+
- lib/shapeup_cli/version.rb
|
|
47
49
|
- skills/shapeup/SKILL.md
|
|
48
50
|
homepage: https://github.com/shapeupcc/shapeup-cli
|
|
49
51
|
licenses:
|
|
@@ -66,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
66
68
|
- !ruby/object:Gem::Version
|
|
67
69
|
version: '0'
|
|
68
70
|
requirements: []
|
|
69
|
-
rubygems_version:
|
|
71
|
+
rubygems_version: 3.6.9
|
|
70
72
|
specification_version: 4
|
|
71
73
|
summary: ShapeUp CLI — manage pitches, scopes, tasks, and cycles from the terminal
|
|
72
74
|
test_files: []
|