shapeup-cli 0.4.0 → 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 +19 -1
- 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 +13 -1
- data/lib/shapeup_cli/commands/pitches.rb +5 -0
- data/lib/shapeup_cli/commands/scopes.rb +37 -4
- data/lib/shapeup_cli/commands/streams.rb +36 -3
- data/lib/shapeup_cli/commands/tasks.rb +6 -1
- data/lib/shapeup_cli/version.rb +5 -0
- data/lib/shapeup_cli.rb +1 -1
- data/skills/shapeup/SKILL.md +14 -0
- metadata +3 -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)
|
|
@@ -121,6 +123,22 @@ module ShapeupCli
|
|
|
121
123
|
consume_flag("--yes") | consume_flag("-y")
|
|
122
124
|
end
|
|
123
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
|
+
|
|
124
142
|
# Guard a destructive action. `assume_yes` skips the prompt (the caller
|
|
125
143
|
# passes the result of assume_yes?). With a TTY we ask for confirmation;
|
|
126
144
|
# without one we refuse rather than delete unattended — so agents and
|
|
@@ -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",
|
|
@@ -16,9 +16,12 @@ module ShapeupCli
|
|
|
16
16
|
{ name: "extend", short: "Link a pitch as a continuation of a predecessor", path: "shapeup pitches extend <id> --predecessor <id>" },
|
|
17
17
|
{ name: "detach", short: "Remove a pitch's predecessor link", path: "shapeup pitches detach <id>" },
|
|
18
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>]" },
|
|
19
21
|
{ name: "help", short: "Show usage", path: "shapeup pitches help" }
|
|
20
22
|
],
|
|
21
23
|
flags: [
|
|
24
|
+
{ name: "user", type: "string", usage: "User ID or 'me' (assign/unassign; defaults to me)" },
|
|
22
25
|
{ name: "status", type: "string", usage: "Filter by, or set, status: idea, framed, shaped" },
|
|
23
26
|
{ name: "cycle", type: "string", usage: "Filter by cycle ID (list), or assign to cycle ID (update)" },
|
|
24
27
|
{ name: "tag", type: "string", usage: "Filter by tag name" },
|
|
@@ -61,6 +64,8 @@ module ShapeupCli
|
|
|
61
64
|
when "extend" then extend_pitch
|
|
62
65
|
when "detach" then detach
|
|
63
66
|
when "delete" then delete
|
|
67
|
+
when "assign" then assign_to("Package", "pitches")
|
|
68
|
+
when "unassign" then unassign_from("Package", "pitches")
|
|
64
69
|
when "list", nil then list
|
|
65
70
|
when "help" then help
|
|
66
71
|
else
|
|
@@ -10,12 +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
16
|
{ name: "position", short: "Update hill chart position (0-100)", path: "shapeup scopes position <id> <position>" },
|
|
16
|
-
{ name: "
|
|
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>]" }
|
|
17
21
|
],
|
|
18
22
|
flags: [
|
|
23
|
+
{ name: "user", type: "string", usage: "User ID or 'me' (assign/unassign; defaults to me)" },
|
|
19
24
|
{ name: "pitch", type: "string", usage: "Pitch ID (required for list and create)" },
|
|
20
25
|
{ name: "title", type: "string", usage: "Scope title (for create/update)" },
|
|
21
26
|
{ name: "color", type: "string", usage: "Hex color code (for update)" }
|
|
@@ -34,10 +39,14 @@ module ShapeupCli
|
|
|
34
39
|
subcommand = positional_arg(0)
|
|
35
40
|
|
|
36
41
|
case subcommand
|
|
42
|
+
when "show" then show
|
|
37
43
|
when "create" then create
|
|
38
44
|
when "update" then update
|
|
39
45
|
when "position" then position
|
|
46
|
+
when "history" then history
|
|
40
47
|
when "delete" then delete
|
|
48
|
+
when "assign" then assign_to("Scope", "scopes")
|
|
49
|
+
when "unassign" then unassign_from("Scope", "scopes")
|
|
41
50
|
when "list", nil then list
|
|
42
51
|
else list
|
|
43
52
|
end
|
|
@@ -47,13 +56,37 @@ module ShapeupCli
|
|
|
47
56
|
def list
|
|
48
57
|
pitch_id = extract_option("--pitch") || abort("Usage: shapeup scopes list --pitch <id>")
|
|
49
58
|
|
|
50
|
-
result = call_tool("
|
|
59
|
+
result = call_tool("list_scopes", package: pitch_id.to_s)
|
|
51
60
|
|
|
52
61
|
render result,
|
|
53
62
|
summary: "Scopes for Pitch ##{pitch_id}",
|
|
54
63
|
breadcrumbs: [
|
|
55
|
-
{ cmd: "shapeup scopes
|
|
56
|
-
{ 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" }
|
|
57
90
|
]
|
|
58
91
|
end
|
|
59
92
|
|
|
@@ -10,15 +10,22 @@ module ShapeupCli
|
|
|
10
10
|
short: "List and show streams (product areas)",
|
|
11
11
|
subcommands: [
|
|
12
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>" }
|
|
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\"" }
|
|
14
16
|
],
|
|
15
17
|
flags: [
|
|
16
|
-
{ name: "all", type: "bool", usage: "Include archived streams (for list)" }
|
|
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)" }
|
|
17
22
|
],
|
|
18
23
|
examples: [
|
|
19
24
|
"shapeup streams",
|
|
20
25
|
"shapeup streams --all --json",
|
|
21
|
-
"shapeup streams show 3"
|
|
26
|
+
"shapeup streams show 3",
|
|
27
|
+
"shapeup streams create \"Platform\"",
|
|
28
|
+
"shapeup streams edit 3 --title \"Platform & Infra\" --color \"#3b82f6\""
|
|
22
29
|
]
|
|
23
30
|
}
|
|
24
31
|
end
|
|
@@ -28,6 +35,8 @@ module ShapeupCli
|
|
|
28
35
|
|
|
29
36
|
case subcommand
|
|
30
37
|
when "show" then show
|
|
38
|
+
when "create" then create
|
|
39
|
+
when "edit" then edit
|
|
31
40
|
when "list", nil then list
|
|
32
41
|
else
|
|
33
42
|
subcommand.match?(/\A\d+\z/) ? show(subcommand) : list
|
|
@@ -35,6 +44,30 @@ module ShapeupCli
|
|
|
35
44
|
end
|
|
36
45
|
|
|
37
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
|
+
|
|
38
71
|
def list
|
|
39
72
|
args = {}
|
|
40
73
|
args[:include_archived] = true if consume_flag("--all")
|
|
@@ -15,9 +15,12 @@ module ShapeupCli
|
|
|
15
15
|
{ name: "complete", short: "Mark task(s) as complete", path: "shapeup done <id> [<id>...]" },
|
|
16
16
|
{ name: "uncomplete", short: "Mark task as incomplete", path: "shapeup undone <id>" },
|
|
17
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>" }
|
|
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>]" }
|
|
19
21
|
],
|
|
20
22
|
flags: [
|
|
23
|
+
{ name: "user", type: "string", usage: "User ID or 'me' (assign/unassign; defaults to me)" },
|
|
21
24
|
{ name: "pitch", type: "string", usage: "Pitch ID (required for create)" },
|
|
22
25
|
{ name: "scope", type: "string", usage: "Scope ID (filter, create target, or update target; use 'none' to unmap)" },
|
|
23
26
|
{ name: "assignee", type: "string", usage: "User ID or 'me' (for list)" },
|
|
@@ -47,6 +50,8 @@ module ShapeupCli
|
|
|
47
50
|
when "uncomplete" then uncomplete
|
|
48
51
|
when "update" then update
|
|
49
52
|
when "delete" then delete
|
|
53
|
+
when "assign" then assign_to("Task", "tasks")
|
|
54
|
+
when "unassign" then unassign_from("Task", "tasks")
|
|
50
55
|
when "list", nil then list
|
|
51
56
|
else list
|
|
52
57
|
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.4.0"
|
|
20
20
|
DEFAULT_HOST = "https://shapeup.cc"
|
|
21
21
|
|
|
22
22
|
# Exit codes
|
data/skills/shapeup/SKILL.md
CHANGED
|
@@ -125,6 +125,7 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
|
|
|
125
125
|
| Filter by stream | `shapeup issues --stream "Platform" --json` |
|
|
126
126
|
| Show issue detail | `shapeup issue <id> --json` |
|
|
127
127
|
| Create issue | `shapeup issues create "Title" --stream "Platform"` |
|
|
128
|
+
| List kanban columns | `shapeup issues columns --json` |
|
|
128
129
|
| Move to column | `shapeup issues move <id> --column doing` |
|
|
129
130
|
| Mark issue done | `shapeup issues done <id>` |
|
|
130
131
|
| Close issue (won't fix) | `shapeup issues close <id>` |
|
|
@@ -166,25 +167,38 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
|
|
|
166
167
|
| Extend a pitch | `shapeup pitches extend <id> --predecessor <id>` |
|
|
167
168
|
| Detach predecessor | `shapeup pitches detach <id>` |
|
|
168
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>` |
|
|
169
172
|
| **Cycles** | |
|
|
170
173
|
| List cycles | `shapeup cycles --json` |
|
|
171
174
|
| Active cycles | `shapeup cycles --status active --json` |
|
|
172
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` |
|
|
173
179
|
| **Streams** | |
|
|
174
180
|
| List streams | `shapeup streams --json` |
|
|
175
181
|
| Include archived | `shapeup streams --all --json` |
|
|
176
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"` |
|
|
177
185
|
| **Scopes & Tasks** | |
|
|
178
186
|
| List scopes | `shapeup scopes list --pitch <id> --json` |
|
|
187
|
+
| Show scope + tasks | `shapeup scopes show <id> --json` |
|
|
179
188
|
| Create scope | `shapeup scopes create --pitch <id> "Title"` |
|
|
180
189
|
| Update hill position | `shapeup scopes position <id> <0-100>` |
|
|
190
|
+
| Hill chart history | `shapeup scopes history <id> --json` |
|
|
181
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>` |
|
|
182
194
|
| List tasks | `shapeup tasks list --pitch <id> --json` |
|
|
183
195
|
| Create task | `shapeup todo "Description" --pitch <id>` |
|
|
184
196
|
| Complete task(s) | `shapeup done <id> [<id>...]` |
|
|
185
197
|
| Uncomplete task | `shapeup undone <id>` |
|
|
186
198
|
| Update task | `shapeup tasks update <id> --description "New"` |
|
|
187
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>` |
|
|
188
202
|
| **My Work** | |
|
|
189
203
|
| All my assignments | `shapeup me --json` |
|
|
190
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
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- lib/shapeup_cli/commands/tasks.rb
|
|
46
46
|
- lib/shapeup_cli/config.rb
|
|
47
47
|
- lib/shapeup_cli/output.rb
|
|
48
|
+
- lib/shapeup_cli/version.rb
|
|
48
49
|
- skills/shapeup/SKILL.md
|
|
49
50
|
homepage: https://github.com/shapeupcc/shapeup-cli
|
|
50
51
|
licenses:
|
|
@@ -67,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
67
68
|
- !ruby/object:Gem::Version
|
|
68
69
|
version: '0'
|
|
69
70
|
requirements: []
|
|
70
|
-
rubygems_version:
|
|
71
|
+
rubygems_version: 3.6.9
|
|
71
72
|
specification_version: 4
|
|
72
73
|
summary: ShapeUp CLI — manage pitches, scopes, tasks, and cycles from the terminal
|
|
73
74
|
test_files: []
|