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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cddf715cbc5ec20b37a27a84a33daa1fcbe1c16a959b8dac6cf45645d5f40adc
4
- data.tar.gz: 7de65ac1329b084a8b43e10eeb59e63f6699b9a7763dc2ea8b76b52d4dc9d4b1
3
+ metadata.gz: d8ff0645627f89f9f4478145566d6386d3137125bfee4f0baea077476da8d7e7
4
+ data.tar.gz: 94260b420442e04c89015fc58ac2536ba312d177876fe7d310064de341e24a3c
5
5
  SHA512:
6
- metadata.gz: 9d68e45dda572611c45d9ff32efdb52834016d793d2c919da995326a786b618741f7904d807983bb3b396f418f0c32acef7356f4cda83f84bc2ca1340ea51edf
7
- data.tar.gz: 32716fe62a9838e31c4485336d524aca1ba0e07400f06af72811e12565d9ebbae6f17cf323cecf5757e05dc04d0748a3f7d985e85c2b7be83df31dc994dd8eb8
6
+ metadata.gz: 0c334924b153062646d61d9f09f7a98a9a6932a364fe461b76284392de112242437f5d0381d16b6b8882b6d9b4073dd590172a3389dc0393f05d810ff9117eba
7
+ data.tar.gz: 2cced2c61abe233c7bdaef8b9918239105724ca1b59d4524beec770709d269c814343216e6c7d5ee8545a146229db73201f161b7d711ca0e18eff3895cfb6801
@@ -17,31 +17,12 @@ module ShapeupCli
17
17
  @request_id = 0
18
18
  end
19
19
 
20
- # Initialise the MCP session
21
- def initialize_session
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
- case response
71
- when Net::HTTPUnauthorized
72
- raise AuthError, "Authentication failed — run 'shapeup login'"
73
- when Net::HTTPForbidden
74
- raise PermissionError, "Check your subscription or permissions"
75
- when Net::HTTPTooManyRequests
76
- raise RateLimitError, "Too many requests"
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 = JSON.parse(response.body)
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
- 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>."))
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 = resolve_org_value(value)
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 = resolve_org_value(org_value)
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" then 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: "closed")
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: "delete", short: "Delete a scope (must have no tasks or comments)", path: "shapeup scopes delete <id>" }
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("show_package", package: pitch_id.to_s)
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 create --pitch #{pitch_id} \"Title\"", description: "Add a scope" },
56
- { cmd: "shapeup tasks list --pitch #{pitch_id}", description: "List all tasks" }
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShapeupCli
4
+ VERSION = "0.5.0"
5
+ 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
@@ -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.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: 4.0.10
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: []