shapeup-cli 0.3.3 → 0.4.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: 919e0d1e776b34cb20cb2854bedcb2b1c12806b79fc1cd5297df8e1506d70eab
4
- data.tar.gz: 7e4a40392f129a6262627ace44cd164bff87ac160be9d8b93f495acab1838a35
3
+ metadata.gz: cddf715cbc5ec20b37a27a84a33daa1fcbe1c16a959b8dac6cf45645d5f40adc
4
+ data.tar.gz: 7de65ac1329b084a8b43e10eeb59e63f6699b9a7763dc2ea8b76b52d4dc9d4b1
5
5
  SHA512:
6
- metadata.gz: b3ef79a008f0f118d315621fe3980ba4d06f557f9276b19b569285e922647fd1e6f6e07924b25b21d3024281214297990859cf220dec3376890957f916cd3e4c
7
- data.tar.gz: fc234742413cfe3e58d1cf75e19e48a88f28ff8fa300e4e6549a3948e09c53b4dbc02695e9792f1d2cc92fa03eef0acd8d19bfc27f0d40604b602750a9bb74ea
6
+ metadata.gz: 9d68e45dda572611c45d9ff32efdb52834016d793d2c919da995326a786b618741f7904d807983bb3b396f418f0c32acef7356f4cda83f84bc2ca1340ea51edf
7
+ data.tar.gz: 32716fe62a9838e31c4485336d524aca1ba0e07400f06af72811e12565d9ebbae6f17cf323cecf5757e05dc04d0748a3f7d985e85c2b7be83df31dc994dd8eb8
@@ -114,6 +114,29 @@ module ShapeupCli
114
114
  !!@remaining.delete(flag)
115
115
  end
116
116
 
117
+ # Consume a confirmation bypass flag (--yes / -y). Call before reading
118
+ # positionals so the flag isn't mistaken for one.
119
+ def assume_yes?
120
+ # both forms; -y does not start with "--" so it must be consumed explicitly
121
+ consume_flag("--yes") | consume_flag("-y")
122
+ end
123
+
124
+ # Guard a destructive action. `assume_yes` skips the prompt (the caller
125
+ # passes the result of assume_yes?). With a TTY we ask for confirmation;
126
+ # without one we refuse rather than delete unattended — so agents and
127
+ # scripts must opt in explicitly with --yes.
128
+ def confirm_destructive!(action, assume_yes)
129
+ return if assume_yes
130
+
131
+ unless $stdin.tty?
132
+ abort "Refusing to #{action} without confirmation. Re-run with --yes to proceed."
133
+ end
134
+
135
+ $stderr.print "#{action}? This cannot be undone [y/N]: "
136
+ answer = $stdin.gets&.strip&.downcase
137
+ abort "Aborted." unless answer == "y" || answer == "yes"
138
+ end
139
+
117
140
  # Parse --comments/--no-comments + --comments-limit N into MCP args.
118
141
  def comment_flags
119
142
  args = {}
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShapeupCli
4
+ module Commands
5
+ class Checklist < Base
6
+ def self.metadata
7
+ {
8
+ command: "checklist",
9
+ path: "shapeup checklist",
10
+ short: "Manage the checklist on a pitch or issue",
11
+ subcommands: [
12
+ { name: "list", short: "List items (default)", path: "shapeup checklist --pitch <id>" },
13
+ { name: "add", short: "Add an item", path: 'shapeup checklist add --pitch <id> "Item text"' },
14
+ { name: "tick", short: "Mark an item complete", path: "shapeup checklist tick <item_id>" },
15
+ { name: "untick", short: "Mark an item incomplete", path: "shapeup checklist untick <item_id>" },
16
+ { name: "edit", short: "Rename an item", path: 'shapeup checklist edit <item_id> "New text"' },
17
+ { name: "remove", short: "Delete an item", path: "shapeup checklist remove <item_id>" }
18
+ ],
19
+ flags: [
20
+ { name: "pitch", type: "string", usage: "Pitch ID (for list/add)" },
21
+ { name: "issue", type: "string", usage: "Issue ID (for list/add)" }
22
+ ],
23
+ examples: [
24
+ "shapeup checklist --pitch 42",
25
+ "shapeup checklist --issue 7",
26
+ 'shapeup checklist add --pitch 42 "Confirm Slack webhook"',
27
+ "shapeup checklist tick 18",
28
+ "shapeup checklist untick 18",
29
+ 'shapeup checklist edit 18 "Confirm Slack webhook secret rotation"',
30
+ "shapeup checklist remove 18"
31
+ ]
32
+ }
33
+ end
34
+
35
+ def execute
36
+ subcommand = positional_arg(0)
37
+
38
+ case subcommand
39
+ when "add" then add
40
+ when "tick" then tick
41
+ when "untick" then untick
42
+ when "edit" then edit
43
+ when "remove" then remove
44
+ when "list", nil then list
45
+ else list
46
+ end
47
+ end
48
+
49
+ private
50
+ def list
51
+ type, id = resolve_target
52
+ result = call_tool("list_checklist_items", checklistable_type: type, checklistable_id: id.to_s)
53
+
54
+ render result,
55
+ summary: "Checklist on #{type} ##{id}",
56
+ breadcrumbs: [
57
+ { cmd: "shapeup checklist add --#{type.downcase == 'package' ? 'pitch' : 'issue'} #{id} \"Item\"", description: "Add an item" },
58
+ { cmd: "shapeup checklist tick <item_id>", description: "Tick an item" }
59
+ ]
60
+ end
61
+
62
+ def add
63
+ type, id = resolve_target
64
+ text = positional_arg(1) || abort('Usage: shapeup checklist add --pitch <id> "Item text"')
65
+
66
+ result = call_tool("create_checklist_item", checklistable_type: type, checklistable_id: id.to_s, content: text)
67
+
68
+ render result,
69
+ summary: "Item added to #{type} ##{id}",
70
+ breadcrumbs: [
71
+ { cmd: "shapeup checklist --#{type.downcase == 'package' ? 'pitch' : 'issue'} #{id}", description: "View checklist" }
72
+ ]
73
+ end
74
+
75
+ def tick
76
+ item_id = positional_arg(1) || abort("Usage: shapeup checklist tick <item_id>")
77
+
78
+ result = call_tool("update_checklist_item", checklist_item: item_id.to_s, completed: true)
79
+
80
+ render result, summary: "Item ##{item_id} ticked"
81
+ end
82
+
83
+ def untick
84
+ item_id = positional_arg(1) || abort("Usage: shapeup checklist untick <item_id>")
85
+
86
+ result = call_tool("update_checklist_item", checklist_item: item_id.to_s, completed: false)
87
+
88
+ render result, summary: "Item ##{item_id} unticked"
89
+ end
90
+
91
+ def edit
92
+ item_id = positional_arg(1) || abort('Usage: shapeup checklist edit <item_id> "New text"')
93
+ text = positional_arg(2) || abort('Usage: shapeup checklist edit <item_id> "New text"')
94
+
95
+ result = call_tool("update_checklist_item", checklist_item: item_id.to_s, content: text)
96
+
97
+ render result, summary: "Item ##{item_id} updated"
98
+ end
99
+
100
+ def remove
101
+ item_id = positional_arg(1) || abort("Usage: shapeup checklist remove <item_id>")
102
+
103
+ result = call_tool("delete_checklist_item", checklist_item: item_id.to_s)
104
+
105
+ render result, summary: "Item ##{item_id} removed"
106
+ end
107
+
108
+ def resolve_target
109
+ pitch = extract_option("--pitch")
110
+ issue = extract_option("--issue")
111
+
112
+ if pitch
113
+ [ "Package", pitch ]
114
+ elsif issue
115
+ [ "Issue", issue ]
116
+ else
117
+ abort("Specify a target: --pitch <id> or --issue <id>")
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -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")
@@ -25,6 +25,8 @@ module ShapeupCli
25
25
  { name: "watch", short: "Watch an issue", path: "shapeup issues watch <id>" },
26
26
  { name: "unwatch", short: "Stop watching an issue", path: "shapeup issues unwatch <id>" },
27
27
  { name: "watching", short: "List issues you are watching", path: "shapeup watching" },
28
+ { name: "convert", short: "Convert issue into a new pitch", path: "shapeup issues convert <id>" },
29
+ { name: "add-to-pitch", short: "Fold issue content into an existing pitch", path: "shapeup issues add-to-pitch <id> --pitch <pitch_id>" },
28
30
  { name: "delete", short: "Delete an issue", path: "shapeup issues delete <id>" }
29
31
  ],
30
32
  flags: [
@@ -37,6 +39,7 @@ module ShapeupCli
37
39
  { name: "all", type: "bool", usage: "Include done/closed issues (hidden by default)" },
38
40
  { name: "content", type: "string", usage: "Issue content/description" },
39
41
  { name: "title", type: "string", usage: "Issue title (for update)" },
42
+ { name: "pitch", type: "string", usage: "Pitch ID (for add-to-pitch)" },
40
43
  { name: "archived", type: "bool", usage: "Include iceboxed issues in list" },
41
44
  { name: "no-comments", type: "bool", usage: "Hide embedded comments on show (default: show)" },
42
45
  { name: "comments-limit", type: "integer", usage: "Max comments to embed on show (default: 10, max: 50)" }
@@ -60,7 +63,9 @@ module ShapeupCli
60
63
  "shapeup issues assign 42 --user 7",
61
64
  "shapeup issues unassign 42",
62
65
  "shapeup issues watch 42",
63
- "shapeup watching"
66
+ "shapeup watching",
67
+ "shapeup issues convert 42",
68
+ "shapeup issues add-to-pitch 42 --pitch 10"
64
69
  ]
65
70
  }
66
71
  end
@@ -69,22 +74,24 @@ module ShapeupCli
69
74
  subcommand = positional_arg(0)
70
75
 
71
76
  case subcommand
72
- when "show" then show
73
- when "create" then create
74
- when "update" then update
75
- when "move" then move
76
- when "done" then mark_done
77
- when "close" then close
78
- when "reopen" then reopen
79
- when "icebox" then icebox
80
- when "defrost" then defrost
81
- when "assign" then assign
82
- when "unassign" then unassign
83
- when "watch" then watch
84
- when "unwatch" then unwatch
85
- when "watching" then watching
86
- when "delete" then delete
87
- when "list", nil then list
77
+ when "show" then show
78
+ when "create" then create
79
+ when "update" then update
80
+ when "move" then move
81
+ when "done" then mark_done
82
+ when "close" then close
83
+ when "reopen" then reopen
84
+ when "icebox" then icebox
85
+ when "defrost" then defrost
86
+ when "assign" then assign
87
+ when "unassign" then unassign
88
+ when "watch" then watch
89
+ when "unwatch" then unwatch
90
+ when "watching" then watching
91
+ when "convert" then convert
92
+ when "add-to-pitch" then add_to_pitch
93
+ when "delete" then delete
94
+ when "list", nil then list
88
95
  else
89
96
  # Bare numeric arg = show
90
97
  if subcommand&.match?(/\A\d+\z/)
@@ -324,7 +331,9 @@ module ShapeupCli
324
331
  end
325
332
 
326
333
  def delete
327
- id = positional_arg(1) || abort("Usage: shapeup issues delete <id>")
334
+ yes = assume_yes?
335
+ id = positional_arg(1) || abort("Usage: shapeup issues delete <id> [--yes]")
336
+ confirm_destructive!("Delete issue ##{id}", yes)
328
337
 
329
338
  result = call_tool("delete_issue", issue: id.to_s)
330
339
 
@@ -334,6 +343,32 @@ module ShapeupCli
334
343
  { cmd: "shapeup issues", description: "List remaining issues" }
335
344
  ]
336
345
  end
346
+
347
+ def convert
348
+ id = positional_arg(1) || abort("Usage: shapeup issues convert <id>")
349
+
350
+ result = call_tool("convert_issue_to_pitch", issue: id.to_s)
351
+
352
+ render result,
353
+ summary: "Issue ##{id} converted to a pitch",
354
+ breadcrumbs: [
355
+ { cmd: "shapeup pitches", description: "List pitches" },
356
+ { cmd: "shapeup pitch <id>", description: "Open the new pitch" }
357
+ ]
358
+ end
359
+
360
+ def add_to_pitch
361
+ id = positional_arg(1) || abort("Usage: shapeup issues add-to-pitch <id> --pitch <pitch_id>")
362
+ pitch_id = extract_option("--pitch") || abort("Usage: shapeup issues add-to-pitch <id> --pitch <pitch_id>")
363
+
364
+ result = call_tool("add_issue_to_pitch", issue: id.to_s, package: pitch_id.to_s)
365
+
366
+ render result,
367
+ summary: "Issue ##{id} folded into pitch ##{pitch_id}",
368
+ breadcrumbs: [
369
+ { cmd: "shapeup pitch #{pitch_id}", description: "Open the pitch" }
370
+ ]
371
+ end
337
372
  end
338
373
  end
339
374
  end
@@ -12,15 +12,23 @@ 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>" },
15
19
  { name: "help", short: "Show usage", path: "shapeup pitches help" }
16
20
  ],
17
21
  flags: [
18
- { name: "status", type: "string", usage: "Filter by status: idea, framed, shaped" },
19
- { name: "cycle", type: "string", usage: "Filter by cycle ID" },
22
+ { name: "status", type: "string", usage: "Filter by, or set, status: idea, framed, shaped" },
23
+ { name: "cycle", type: "string", usage: "Filter by cycle ID (list), or assign to cycle ID (update)" },
24
+ { name: "tag", type: "string", usage: "Filter by tag name" },
20
25
  { name: "limit", type: "integer", usage: "Limit number of results" },
21
- { name: "stream", type: "string", usage: "Stream name or ID (for create)" },
22
- { name: "appetite", type: "string", usage: "Appetite: unknown, small_batch, big_batch (for create, default: big_batch)" },
26
+ { name: "stream", type: "string", usage: "Stream name or ID (for create/update)" },
27
+ { name: "appetite", type: "string", usage: "Appetite: unknown, small_batch, big_batch (create default: big_batch)" },
23
28
  { name: "cycle-id", type: "string", usage: "Assign to cycle ID (for create)" },
29
+ { name: "title", type: "string", usage: "New title (for update)" },
30
+ { name: "content", type: "string", usage: "New description (for update)" },
31
+ { name: "predecessor", type: "string", usage: "Predecessor pitch ID (for extend)" },
24
32
  { name: "no-comments", type: "bool", usage: "Hide embedded comments on show (default: show)" },
25
33
  { name: "comments-limit", type: "integer", usage: "Max comments to embed on show (default: 10, max: 50)" }
26
34
  ],
@@ -28,10 +36,17 @@ module ShapeupCli
28
36
  "shapeup pitches list",
29
37
  "shapeup pitches list --status shaped",
30
38
  "shapeup pitches list --cycle 5",
39
+ "shapeup pitches list --tag q3-plan",
31
40
  "shapeup pitch 42",
32
41
  "shapeup pitch 42 --json",
33
42
  "shapeup pitches create \"Redesign Search\" --stream \"Platform\"",
34
- "shapeup pitches create \"Auth Overhaul\" --stream \"Platform\" --appetite small_batch"
43
+ "shapeup pitches create \"Auth Overhaul\" --stream \"Platform\" --appetite small_batch",
44
+ "shapeup pitches update 42 --status shaped",
45
+ "shapeup pitches update 42 --title \"Redesign Search v2\" --appetite small_batch",
46
+ "shapeup pitches update 42 --cycle 5",
47
+ "shapeup pitches extend 42 --predecessor 30",
48
+ "shapeup pitches detach 42",
49
+ "shapeup pitches delete 42"
35
50
  ]
36
51
  }
37
52
  end
@@ -42,6 +57,10 @@ module ShapeupCli
42
57
  case subcommand
43
58
  when "show" then show
44
59
  when "create" then create
60
+ when "update" then update
61
+ when "extend" then extend_pitch
62
+ when "detach" then detach
63
+ when "delete" then delete
45
64
  when "list", nil then list
46
65
  when "help" then help
47
66
  else
@@ -53,9 +72,11 @@ module ShapeupCli
53
72
  def list
54
73
  cycle_id = extract_option("--cycle")
55
74
  status = extract_option("--status")
75
+ tag = extract_option("--tag")
56
76
  limit = extract_option("--limit")&.to_i
57
77
  args = {}
58
78
  args[:cycle] = cycle_id if cycle_id
79
+ args[:tag] = tag if tag
59
80
 
60
81
  result = call_tool("list_packages", **args)
61
82
  data = Output.extract_data(result)
@@ -68,6 +89,7 @@ module ShapeupCli
68
89
  summary = "Pitches"
69
90
  summary += " (#{status})" if status
70
91
  summary += " in cycle #{cycle_id}" if cycle_id
92
+ summary += " tagged #{tag}" if tag
71
93
  summary += " — #{packages.length} results"
72
94
 
73
95
  render_list(packages, summary)
@@ -135,6 +157,68 @@ module ShapeupCli
135
157
  ]
136
158
  end
137
159
 
160
+ def update
161
+ id = positional_arg(1) || abort("Usage: shapeup pitches update <id> [--title \"...\"] [--status idea|framed|shaped] [--appetite ...] [--stream \"Name\"] [--content \"...\"] [--cycle <id>]")
162
+
163
+ args = { package: id.to_s }
164
+ args[:title] = extract_option("--title") if @remaining.include?("--title")
165
+ args[:status] = extract_option("--status") if @remaining.include?("--status")
166
+ args[:appetite] = extract_option("--appetite") if @remaining.include?("--appetite")
167
+ args[:stream] = extract_option("--stream") if @remaining.include?("--stream")
168
+ args[:content] = extract_option("--content") if @remaining.include?("--content")
169
+ args[:cycle] = extract_option("--cycle") if @remaining.include?("--cycle")
170
+
171
+ abort("Nothing to update. Pass at least one of --title, --status, --appetite, --stream, --content, --cycle") if args.size == 1
172
+
173
+ result = call_tool("update_package", **args)
174
+
175
+ render result,
176
+ summary: "Pitch ##{id} updated",
177
+ breadcrumbs: [
178
+ { cmd: "shapeup pitch #{id}", description: "View pitch details" }
179
+ ]
180
+ end
181
+
182
+ def extend_pitch
183
+ id = positional_arg(1) || abort("Usage: shapeup pitches extend <id> --predecessor <id>")
184
+ predecessor = extract_option("--predecessor") || abort("Usage: shapeup pitches extend <id> --predecessor <id>")
185
+
186
+ result = call_tool("extend_package", package: id.to_s, predecessor: predecessor.to_s)
187
+
188
+ render result,
189
+ summary: "Pitch ##{id} now extends ##{predecessor}",
190
+ breadcrumbs: [
191
+ { cmd: "shapeup pitch #{id}", description: "View pitch details" },
192
+ { cmd: "shapeup pitches detach #{id}", description: "Remove the predecessor link" }
193
+ ]
194
+ end
195
+
196
+ def detach
197
+ id = positional_arg(1) || abort("Usage: shapeup pitches detach <id>")
198
+
199
+ result = call_tool("detach_package", package: id.to_s)
200
+
201
+ render result,
202
+ summary: "Pitch ##{id} detached from its predecessor",
203
+ breadcrumbs: [
204
+ { cmd: "shapeup pitch #{id}", description: "View pitch details" }
205
+ ]
206
+ end
207
+
208
+ def delete
209
+ yes = assume_yes?
210
+ id = positional_arg(1) || abort("Usage: shapeup pitches delete <id> [--yes]")
211
+ confirm_destructive!("Delete pitch ##{id}", yes)
212
+
213
+ result = call_tool("delete_package", package: id.to_s)
214
+
215
+ render result,
216
+ summary: "Pitch ##{id} deleted",
217
+ breadcrumbs: [
218
+ { cmd: "shapeup pitches list", description: "List remaining pitches" }
219
+ ]
220
+ end
221
+
138
222
  def help
139
223
  puts <<~HELP
140
224
  Usage: shapeup pitches <subcommand> [options]
@@ -12,7 +12,8 @@ module ShapeupCli
12
12
  { name: "list", short: "List scopes for a pitch", path: "shapeup scopes list --pitch <id>" },
13
13
  { name: "create", short: "Create a new scope", path: "shapeup scopes create --pitch <id> \"Title\"" },
14
14
  { 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>" }
15
+ { 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>" }
16
17
  ],
17
18
  flags: [
18
19
  { name: "pitch", type: "string", usage: "Pitch ID (required for list and create)" },
@@ -23,7 +24,8 @@ module ShapeupCli
23
24
  "shapeup scopes list --pitch 42",
24
25
  "shapeup scopes create --pitch 42 \"User onboarding\"",
25
26
  "shapeup scopes update 7 --title \"Revised onboarding\"",
26
- "shapeup scopes position 7 50"
27
+ "shapeup scopes position 7 50",
28
+ "shapeup scopes delete 7"
27
29
  ]
28
30
  }
29
31
  end
@@ -35,6 +37,7 @@ module ShapeupCli
35
37
  when "create" then create
36
38
  when "update" then update
37
39
  when "position" then position
40
+ when "delete" then delete
38
41
  when "list", nil then list
39
42
  else list
40
43
  end
@@ -91,6 +94,20 @@ module ShapeupCli
91
94
 
92
95
  render result, summary: "Scope ##{scope_id} updated"
93
96
  end
97
+
98
+ def delete
99
+ yes = assume_yes?
100
+ scope_id = positional_arg(1) || abort("Usage: shapeup scopes delete <id> [--yes]")
101
+ confirm_destructive!("Delete scope ##{scope_id}", yes)
102
+
103
+ result = call_tool("delete_scope", scope: scope_id.to_s)
104
+
105
+ render result,
106
+ summary: "Scope ##{scope_id} deleted",
107
+ breadcrumbs: [
108
+ { cmd: "shapeup scopes list --pitch <id>", description: "List remaining scopes" }
109
+ ]
110
+ end
94
111
  end
95
112
  end
96
113
  end
@@ -0,0 +1,66 @@
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
+ ],
15
+ flags: [
16
+ { name: "all", type: "bool", usage: "Include archived streams (for list)" }
17
+ ],
18
+ examples: [
19
+ "shapeup streams",
20
+ "shapeup streams --all --json",
21
+ "shapeup streams show 3"
22
+ ]
23
+ }
24
+ end
25
+
26
+ def execute
27
+ subcommand = positional_arg(0)
28
+
29
+ case subcommand
30
+ when "show" then show
31
+ when "list", nil then list
32
+ else
33
+ subcommand.match?(/\A\d+\z/) ? show(subcommand) : list
34
+ end
35
+ end
36
+
37
+ private
38
+ def list
39
+ args = {}
40
+ args[:include_archived] = true if consume_flag("--all")
41
+
42
+ result = call_tool("list_streams", **args)
43
+
44
+ render result,
45
+ summary: "Streams",
46
+ breadcrumbs: [
47
+ { cmd: "shapeup streams show <id>", description: "View stream details" },
48
+ { cmd: "shapeup pitches create \"Title\" --stream \"Name\"", description: "Create a pitch in a stream" }
49
+ ]
50
+ end
51
+
52
+ def show(id = nil)
53
+ id ||= positional_arg(1) || abort("Usage: shapeup streams show <id>")
54
+
55
+ result = call_tool("show_stream", stream: id.to_s)
56
+
57
+ render result,
58
+ summary: "Stream ##{id}",
59
+ breadcrumbs: [
60
+ { cmd: "shapeup pitches list --json", description: "List pitches" },
61
+ { cmd: "shapeup issues --stream \"Name\"", description: "List issues in this stream" }
62
+ ]
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShapeupCli
4
+ module Commands
5
+ class Tags < Base
6
+ def self.metadata
7
+ {
8
+ command: "tags",
9
+ path: "shapeup tags",
10
+ short: "List, add, and remove tags on pitches and issues",
11
+ subcommands: [
12
+ { name: "list", short: "List the org's tag vocabulary (default)", path: "shapeup tags" },
13
+ { name: "add", short: "Tag a pitch or issue", path: "shapeup tags add --pitch <id> <name>" },
14
+ { name: "remove", short: "Untag a pitch or issue", path: "shapeup tags remove --pitch <id> <name>" }
15
+ ],
16
+ flags: [
17
+ { name: "pitch", type: "string", usage: "Pitch ID (for add/remove)" },
18
+ { name: "issue", type: "string", usage: "Issue ID (for add/remove)" }
19
+ ],
20
+ examples: [
21
+ "shapeup tags",
22
+ "shapeup tags add --pitch 42 q3-plan",
23
+ "shapeup tags add --issue 7 needs-design",
24
+ "shapeup tags remove --pitch 42 q3-plan",
25
+ "shapeup pitches list --tag q3-plan"
26
+ ]
27
+ }
28
+ end
29
+
30
+ def execute
31
+ subcommand = positional_arg(0)
32
+
33
+ case subcommand
34
+ when "add" then add
35
+ when "remove" then remove
36
+ when "list", nil then list
37
+ else list
38
+ end
39
+ end
40
+
41
+ private
42
+ def list
43
+ result = call_tool("list_tags")
44
+
45
+ render result,
46
+ summary: "Tags",
47
+ breadcrumbs: [
48
+ { cmd: "shapeup tags add --pitch <id> <name>", description: "Tag a pitch" },
49
+ { cmd: "shapeup pitches list --tag <name>", description: "Filter pitches by tag" }
50
+ ]
51
+ end
52
+
53
+ def add
54
+ type, id = resolve_target
55
+ name = positional_arg(1) || abort("Usage: shapeup tags add --pitch <id> <name>")
56
+
57
+ result = call_tool("add_tag", taggable_type: type, taggable_id: id.to_s, name: name)
58
+
59
+ render result, summary: "Tagged #{type} ##{id}"
60
+ end
61
+
62
+ def remove
63
+ type, id = resolve_target
64
+ name = positional_arg(1) || abort("Usage: shapeup tags remove --pitch <id> <name>")
65
+
66
+ result = call_tool("remove_tag", taggable_type: type, taggable_id: id.to_s, name: name)
67
+
68
+ render result, summary: "Untagged #{type} ##{id}"
69
+ end
70
+
71
+ def resolve_target
72
+ pitch = extract_option("--pitch")
73
+ issue = extract_option("--issue")
74
+
75
+ if pitch
76
+ [ "Package", pitch ]
77
+ elsif issue
78
+ [ "Issue", issue ]
79
+ else
80
+ abort("Specify a target: --pitch <id> or --issue <id>")
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -8,23 +8,32 @@ 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>" }
16
19
  ],
17
20
  flags: [
18
21
  { name: "pitch", type: "string", usage: "Pitch ID (required for create)" },
19
- { name: "scope", type: "string", usage: "Scope ID (optional filter or target)" },
20
- { name: "assignee", type: "string", usage: "User ID or 'me' (for list)" }
22
+ { name: "scope", type: "string", usage: "Scope ID (filter, create target, or update target; use 'none' to unmap)" },
23
+ { name: "assignee", type: "string", usage: "User ID or 'me' (for list)" },
24
+ { name: "description", type: "string", usage: "New description (for update)" }
21
25
  ],
22
26
  examples: [
23
27
  "shapeup tasks list --pitch 42",
24
28
  "shapeup tasks list --assignee me",
25
29
  "shapeup todo \"Fix login bug\" --pitch 42 --scope 7",
26
30
  "shapeup done 123",
27
- "shapeup done 123 124 125"
31
+ "shapeup done 123 124 125",
32
+ "shapeup undone 123",
33
+ "shapeup tasks update 123 --description \"Fix login + signup bug\"",
34
+ "shapeup tasks update 123 --scope 7",
35
+ "shapeup tasks update 123 --scope none",
36
+ "shapeup tasks delete 123"
28
37
  ]
29
38
  }
30
39
  end
@@ -33,9 +42,12 @@ module ShapeupCli
33
42
  subcommand = positional_arg(0)
34
43
 
35
44
  case subcommand
36
- when "create" then create
37
- when "complete" then complete
38
- when "list", nil then list
45
+ when "create" then create
46
+ when "complete" then complete
47
+ when "uncomplete" then uncomplete
48
+ when "update" then update
49
+ when "delete" then delete
50
+ when "list", nil then list
39
51
  else list
40
52
  end
41
53
  end
@@ -91,10 +103,56 @@ module ShapeupCli
91
103
  render result,
92
104
  summary: "Task ##{id} completed",
93
105
  breadcrumbs: [
106
+ { cmd: "shapeup undone #{id}", description: "Mark incomplete again" },
94
107
  { cmd: "shapeup me", description: "Show remaining work" }
95
108
  ]
96
109
  end
97
110
  end
111
+
112
+ def uncomplete
113
+ id = positional_arg(1) || abort("Usage: shapeup undone <id>")
114
+
115
+ result = call_tool("uncomplete_task", task: id.to_s)
116
+
117
+ render result,
118
+ summary: "Task ##{id} marked incomplete",
119
+ breadcrumbs: [
120
+ { cmd: "shapeup done #{id}", description: "Complete it again" }
121
+ ]
122
+ end
123
+
124
+ def update
125
+ id = positional_arg(1) || abort("Usage: shapeup tasks update <id> [--description \"New\"] [--scope <id>|none]")
126
+ description = extract_option("--description")
127
+ scope = extract_option("--scope")
128
+ abort("Nothing to update. Pass --description \"...\" and/or --scope <id>|none") unless description || scope
129
+
130
+ args = { task: id.to_s }
131
+ args[:description] = description if description
132
+ args[:scope] = scope if scope
133
+
134
+ result = call_tool("update_task", **args)
135
+
136
+ render result,
137
+ summary: "Task ##{id} updated",
138
+ breadcrumbs: [
139
+ { cmd: "shapeup tasks list --pitch <id>", description: "List tasks" }
140
+ ]
141
+ end
142
+
143
+ def delete
144
+ yes = assume_yes?
145
+ id = positional_arg(1) || abort("Usage: shapeup tasks delete <id> [--yes]")
146
+ confirm_destructive!("Delete task ##{id}", yes)
147
+
148
+ result = call_tool("delete_task", task: id.to_s)
149
+
150
+ render result,
151
+ summary: "Task ##{id} deleted",
152
+ breadcrumbs: [
153
+ { cmd: "shapeup tasks list --pitch <id>", description: "List remaining tasks" }
154
+ ]
155
+ end
98
156
  end
99
157
  end
100
158
  end
@@ -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,13 +76,34 @@ 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)
80
+ issues convert <id> Convert issue to a new pitch
81
+ issues add-to-pitch <id> --pitch <pid>
82
+ Fold issue into an existing pitch
65
83
 
66
84
  Comments:
67
85
  comments list --issue <id> List comments on an issue
68
86
  comments list --pitch <id> List comments on a pitch
69
87
  comments add --issue <id> "Text" Add a comment to an issue
70
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)
91
+
92
+ Checklist:
93
+ checklist --pitch <id> List the checklist on a pitch
94
+ checklist --issue <id> List the checklist on an issue
95
+ checklist add --pitch <id> "Text" Add an item
96
+ checklist tick <item_id> Mark an item complete
97
+ checklist untick <item_id> Mark an item incomplete
98
+ checklist edit <item_id> "Text" Rename an item
99
+ checklist remove <item_id> Delete an item
100
+
101
+ Tags:
102
+ tags List the org's tag vocabulary
103
+ tags add --pitch <id> <name> Tag a pitch
104
+ tags add --issue <id> <name> Tag an issue
105
+ tags remove --pitch <id> <name> Untag a pitch
106
+ tags remove --issue <id> <name> Untag an issue
71
107
 
72
108
  My Work:
73
109
  my-work, me Show everything assigned to me
@@ -97,6 +133,8 @@ module ShapeupCli
97
133
  Flags:
98
134
  --org <id|name> Override default organisation
99
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)
100
138
 
101
139
  Environment variables:
102
140
  SHAPEUP_TOKEN Bearer token (skips OAuth, for CI/scripts)
@@ -120,18 +158,22 @@ module ShapeupCli
120
158
  logout Clear all credentials
121
159
  auth Manage profiles (status, list, switch, remove)
122
160
  orgs List organisations
123
- pitches List/show pitches (list, show)
161
+ pitches Manage pitches (list, show, create, update, extend, detach, delete)
124
162
  pitch Show a pitch (shortcut)
125
163
  cycles List cycles
126
164
  cycle Show cycle details (list, show)
127
- scopes Manage scopes (list, create, update)
128
- tasks Manage tasks (list, create, complete)
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)
129
168
  todo Create a task (shortcut)
130
169
  done Complete task(s) (shortcut)
131
- issues Manage issues (list, show, create, move, icebox, watch)
170
+ undone Mark a task incomplete (shortcut)
171
+ issues Manage issues (list, show, create, move, icebox, watch, convert, add-to-pitch, delete)
132
172
  issue Show an issue (shortcut)
133
173
  watching List watched issues (shortcut)
134
- comments List and add comments (list, add)
174
+ comments Manage comments (list, add, edit, remove)
175
+ checklist Manage checklist items on pitches/issues (list, add, tick, untick, edit, remove)
176
+ tags List/add/remove tags on pitches and issues
135
177
  my-work / me Show my assigned work
136
178
  search Search everything
137
179
  config Show/set config (set, show, init)
@@ -150,6 +192,7 @@ require_relative "commands/logout"
150
192
  require_relative "commands/orgs"
151
193
  require_relative "commands/pitches"
152
194
  require_relative "commands/cycle"
195
+ require_relative "commands/streams"
153
196
  require_relative "commands/scopes"
154
197
  require_relative "commands/tasks"
155
198
  require_relative "commands/issues"
@@ -159,3 +202,5 @@ require_relative "commands/auth"
159
202
  require_relative "commands/config_cmd"
160
203
  require_relative "commands/setup"
161
204
  require_relative "commands/comments"
205
+ require_relative "commands/checklist"
206
+ require_relative "commands/tags"
@@ -92,7 +92,7 @@ module ShapeupCli
92
92
  when Hash
93
93
  render_markdown_hash(data)
94
94
  else
95
- puts data.to_s
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.to_s
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
@@ -16,7 +16,7 @@ require_relative "shapeup_cli/output"
16
16
  require_relative "shapeup_cli/commands"
17
17
 
18
18
  module ShapeupCli
19
- VERSION = "0.3.2"
19
+ VERSION = "0.4.0"
20
20
  DEFAULT_HOST = "https://shapeup.cc"
21
21
 
22
22
  # Exit codes
@@ -30,18 +30,21 @@ module ShapeupCli
30
30
  EXIT_INTERRUPTED = 130
31
31
 
32
32
  COMMAND_MAP = {
33
- "orgs" => Commands::Orgs,
34
- "pitches" => Commands::Pitches,
35
- "cycle" => Commands::Cycle,
36
- "scopes" => Commands::Scopes,
37
- "tasks" => Commands::Tasks,
38
- "issues" => Commands::Issues,
39
- "my-work" => Commands::MyWork,
40
- "search" => Commands::Search,
41
- "auth" => Commands::Auth,
42
- "config" => Commands::ConfigCmd,
43
- "setup" => Commands::Setup,
44
- "comments" => Commands::Comments
33
+ "orgs" => Commands::Orgs,
34
+ "pitches" => Commands::Pitches,
35
+ "cycle" => Commands::Cycle,
36
+ "streams" => Commands::Streams,
37
+ "scopes" => Commands::Scopes,
38
+ "tasks" => Commands::Tasks,
39
+ "issues" => Commands::Issues,
40
+ "my-work" => Commands::MyWork,
41
+ "search" => Commands::Search,
42
+ "auth" => Commands::Auth,
43
+ "config" => Commands::ConfigCmd,
44
+ "setup" => Commands::Setup,
45
+ "comments" => Commands::Comments,
46
+ "checklist" => Commands::Checklist,
47
+ "tags" => Commands::Tags
45
48
  }.freeze
46
49
 
47
50
  def self.run(argv)
@@ -66,17 +69,21 @@ module ShapeupCli
66
69
  when "auth" then Commands::Auth.run(args)
67
70
  when "orgs" then Commands::Orgs.run(args)
68
71
  when "pitches" then Commands::Pitches.run(args)
69
- when "pitch" then Commands::Pitches.run(["show"] + args)
72
+ when "pitch" then Commands::Pitches.run([ "show" ] + args)
70
73
  when "cycle" then Commands::Cycle.run(args)
71
- 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)
72
76
  when "scopes" then Commands::Scopes.run(args)
73
77
  when "tasks" then Commands::Tasks.run(args)
74
- when "todo" then Commands::Tasks.run(["create"] + args)
75
- 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)
76
81
  when "issues" then Commands::Issues.run(args)
77
- when "issue" then Commands::Issues.run(["show"] + args)
78
- 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)
79
84
  when "comments" then Commands::Comments.run(args)
85
+ when "checklist" then Commands::Checklist.run(args)
86
+ when "tags" then Commands::Tags.run(args)
80
87
  when "my-work", "me" then Commands::MyWork.run(args)
81
88
  when "search" then Commands::Search.run(args)
82
89
  when "config" then Commands::ConfigCmd.run(args)
@@ -122,6 +129,7 @@ module ShapeupCli
122
129
  "cycles" => "cycle list",
123
130
  "todo \"...\"" => "tasks create \"...\"",
124
131
  "done <id>" => "tasks complete <id>",
132
+ "undone <id>" => "tasks uncomplete <id>",
125
133
  "issue <id>" => "issues show <id>",
126
134
  "watching" => "issues watching",
127
135
  "me" => "my-work"
@@ -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
 
@@ -133,28 +134,57 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
133
134
  | Unassign from issue | `shapeup issues unassign <id>` (self) / `--user <id>` |
134
135
  | Watch / unwatch | `shapeup issues watch <id>` / `unwatch <id>` |
135
136
  | My watched issues | `shapeup watching --json` |
137
+ | Convert issue to pitch | `shapeup issues convert <id>` |
138
+ | Fold issue into pitch | `shapeup issues add-to-pitch <id> --pitch <pitch_id>` |
136
139
  | **Comments** | |
137
140
  | List comments on issue | `shapeup comments list --issue <id> --json` |
138
141
  | List comments on pitch | `shapeup comments list --pitch <id> --json` |
139
142
  | Add comment to issue | `shapeup comments add --issue <id> "Comment text"` |
140
143
  | Add comment to pitch | `shapeup comments add --pitch <id> "Comment text"` |
144
+ | Edit your comment | `shapeup comments edit <comment_id> "New text"` |
145
+ | Delete your comment | `shapeup comments remove <comment_id> --yes` |
146
+ | **Checklist** | |
147
+ | List checklist on pitch | `shapeup checklist --pitch <id>` |
148
+ | List checklist on issue | `shapeup checklist --issue <id>` |
149
+ | Add an item | `shapeup checklist add --pitch <id> "Item text"` |
150
+ | Tick / untick an item | `shapeup checklist tick <item_id>` / `untick <item_id>` |
151
+ | Rename an item | `shapeup checklist edit <item_id> "New text"` |
152
+ | Remove an item | `shapeup checklist remove <item_id>` |
153
+ | **Tags** | |
154
+ | List tag vocabulary | `shapeup tags` |
155
+ | Tag a pitch | `shapeup tags add --pitch <id> <name>` |
156
+ | Tag an issue | `shapeup tags add --issue <id> <name>` |
157
+ | Untag | `shapeup tags remove --pitch <id> <name>` |
158
+ | Filter pitches by tag | `shapeup pitches list --tag <name>` |
141
159
  | **Pitches** | |
142
160
  | List pitches | `shapeup pitches list --json` |
143
161
  | List shaped only | `shapeup pitches list --status shaped --json` |
144
162
  | Show pitch detail | `shapeup pitch <id> --json` |
145
163
  | Create pitch | `shapeup pitches create "Title" --stream "Name"` |
146
164
  | Create with appetite | `shapeup pitches create "Title" --stream "Name" --appetite small_batch` |
165
+ | Update pitch | `shapeup pitches update <id> --status shaped` |
166
+ | Extend a pitch | `shapeup pitches extend <id> --predecessor <id>` |
167
+ | Detach predecessor | `shapeup pitches detach <id>` |
168
+ | Delete pitch | `shapeup pitches delete <id> --yes` |
147
169
  | **Cycles** | |
148
170
  | List cycles | `shapeup cycles --json` |
149
171
  | Active cycles | `shapeup cycles --status active --json` |
150
172
  | Show cycle | `shapeup cycle show <id> --json` |
173
+ | **Streams** | |
174
+ | List streams | `shapeup streams --json` |
175
+ | Include archived | `shapeup streams --all --json` |
176
+ | Show stream | `shapeup streams show <id> --json` |
151
177
  | **Scopes & Tasks** | |
152
178
  | List scopes | `shapeup scopes list --pitch <id> --json` |
153
179
  | Create scope | `shapeup scopes create --pitch <id> "Title"` |
154
180
  | Update hill position | `shapeup scopes position <id> <0-100>` |
181
+ | Delete scope | `shapeup scopes delete <id> --yes` |
155
182
  | List tasks | `shapeup tasks list --pitch <id> --json` |
156
183
  | Create task | `shapeup todo "Description" --pitch <id>` |
157
184
  | Complete task(s) | `shapeup done <id> [<id>...]` |
185
+ | Uncomplete task | `shapeup undone <id>` |
186
+ | Update task | `shapeup tasks update <id> --description "New"` |
187
+ | Delete task | `shapeup tasks delete <id> --yes` |
158
188
  | **My Work** | |
159
189
  | All my assignments | `shapeup me --json` |
160
190
  | 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.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ShapeUp
@@ -27,6 +27,7 @@ files:
27
27
  - lib/shapeup_cli/commands.rb
28
28
  - lib/shapeup_cli/commands/auth.rb
29
29
  - lib/shapeup_cli/commands/base.rb
30
+ - lib/shapeup_cli/commands/checklist.rb
30
31
  - lib/shapeup_cli/commands/comments.rb
31
32
  - lib/shapeup_cli/commands/config_cmd.rb
32
33
  - lib/shapeup_cli/commands/cycle.rb
@@ -39,6 +40,8 @@ files:
39
40
  - lib/shapeup_cli/commands/scopes.rb
40
41
  - lib/shapeup_cli/commands/search.rb
41
42
  - lib/shapeup_cli/commands/setup.rb
43
+ - lib/shapeup_cli/commands/streams.rb
44
+ - lib/shapeup_cli/commands/tags.rb
42
45
  - lib/shapeup_cli/commands/tasks.rb
43
46
  - lib/shapeup_cli/config.rb
44
47
  - lib/shapeup_cli/output.rb
@@ -64,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
67
  - !ruby/object:Gem::Version
65
68
  version: '0'
66
69
  requirements: []
67
- rubygems_version: 3.6.9
70
+ rubygems_version: 4.0.10
68
71
  specification_version: 4
69
72
  summary: ShapeUp CLI — manage pitches, scopes, tasks, and cycles from the terminal
70
73
  test_files: []