shapeup-cli 0.3.4 → 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: 167d55101c1c27e4ce1bd6dc8e6f52599937489e4a6218c248f901a150282273
4
- data.tar.gz: d511b7da6d77e6437ee431951dd591f59b6ed70807be55618564c79b2e9a828b
3
+ metadata.gz: cddf715cbc5ec20b37a27a84a33daa1fcbe1c16a959b8dac6cf45645d5f40adc
4
+ data.tar.gz: 7de65ac1329b084a8b43e10eeb59e63f6699b9a7763dc2ea8b76b52d4dc9d4b1
5
5
  SHA512:
6
- metadata.gz: 6cc0fc54f960c09e7338a5ca49ed2eb61820b9246438c2533ce1f1686f5dd4e4b053aed3f63a79e5855a046bff5b859efcf137b1f8db0bdceb8a014220a2c0aa
7
- data.tar.gz: f4dbcbd90cd4ac33b2d9096fb683b08e76f71f042184faee5a4b91bd7a88802655a593c5b4a60ade0925ad14d07c5c8e7d37991dd31327a6f8c6adef598c403f
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 = {}
@@ -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")
@@ -331,7 +331,9 @@ module ShapeupCli
331
331
  end
332
332
 
333
333
  def delete
334
- 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)
335
337
 
336
338
  result = call_tool("delete_issue", issue: id.to_s)
337
339
 
@@ -356,8 +358,8 @@ module ShapeupCli
356
358
  end
357
359
 
358
360
  def add_to_pitch
359
- id = positional_arg(1) || abort('Usage: shapeup issues add-to-pitch <id> --pitch <pitch_id>')
360
- pitch_id = extract_option("--pitch") || abort('Usage: shapeup issues add-to-pitch <id> --pitch <pitch_id>')
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>")
361
363
 
362
364
  result = call_tool("add_issue_to_pitch", issue: id.to_s, package: pitch_id.to_s)
363
365
 
@@ -12,16 +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)" },
20
24
  { name: "tag", type: "string", usage: "Filter by tag name" },
21
25
  { name: "limit", type: "integer", usage: "Limit number of results" },
22
- { name: "stream", type: "string", usage: "Stream name or ID (for create)" },
23
- { name: "appetite", type: "string", usage: "Appetite: unknown, small_batch, big_batch (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)" },
24
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)" },
25
32
  { name: "no-comments", type: "bool", usage: "Hide embedded comments on show (default: show)" },
26
33
  { name: "comments-limit", type: "integer", usage: "Max comments to embed on show (default: 10, max: 50)" }
27
34
  ],
@@ -33,7 +40,13 @@ module ShapeupCli
33
40
  "shapeup pitch 42",
34
41
  "shapeup pitch 42 --json",
35
42
  "shapeup pitches create \"Redesign Search\" --stream \"Platform\"",
36
- "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"
37
50
  ]
38
51
  }
39
52
  end
@@ -44,6 +57,10 @@ module ShapeupCli
44
57
  case subcommand
45
58
  when "show" then show
46
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
47
64
  when "list", nil then list
48
65
  when "help" then help
49
66
  else
@@ -140,6 +157,68 @@ module ShapeupCli
140
157
  ]
141
158
  end
142
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
+
143
222
  def help
144
223
  puts <<~HELP
145
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
@@ -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,7 +76,7 @@ module ShapeupCli
61
76
  issues watch <id> Watch an issue
62
77
  issues unwatch <id> Stop watching
63
78
  watching List issues you are watching
64
- issues delete <id> Delete an issue
79
+ issues delete <id> Delete an issue (asks to confirm; --yes to skip)
65
80
  issues convert <id> Convert issue to a new pitch
66
81
  issues add-to-pitch <id> --pitch <pid>
67
82
  Fold issue into an existing pitch
@@ -71,6 +86,8 @@ module ShapeupCli
71
86
  comments list --pitch <id> List comments on a pitch
72
87
  comments add --issue <id> "Text" Add a comment to an issue
73
88
  comments add --pitch <id> "Text" Add a comment to a pitch
89
+ comments edit <comment_id> "Text" Edit your own comment
90
+ comments remove <comment_id> Delete your own comment (asks to confirm)
74
91
 
75
92
  Checklist:
76
93
  checklist --pitch <id> List the checklist on a pitch
@@ -116,6 +133,8 @@ module ShapeupCli
116
133
  Flags:
117
134
  --org <id|name> Override default organisation
118
135
  --host <url> Override ShapeUp host (default: https://shapeup.cc)
136
+ --yes, -y Skip the confirmation prompt on delete commands
137
+ (required when deleting non-interactively)
119
138
 
120
139
  Environment variables:
121
140
  SHAPEUP_TOKEN Bearer token (skips OAuth, for CI/scripts)
@@ -139,18 +158,20 @@ module ShapeupCli
139
158
  logout Clear all credentials
140
159
  auth Manage profiles (status, list, switch, remove)
141
160
  orgs List organisations
142
- pitches List/show pitches (list, show)
161
+ pitches Manage pitches (list, show, create, update, extend, detach, delete)
143
162
  pitch Show a pitch (shortcut)
144
163
  cycles List cycles
145
164
  cycle Show cycle details (list, show)
146
- scopes Manage scopes (list, create, update)
147
- 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)
148
168
  todo Create a task (shortcut)
149
169
  done Complete task(s) (shortcut)
150
- issues Manage issues (list, show, create, move, icebox, watch, convert, add-to-pitch)
170
+ undone Mark a task incomplete (shortcut)
171
+ issues Manage issues (list, show, create, move, icebox, watch, convert, add-to-pitch, delete)
151
172
  issue Show an issue (shortcut)
152
173
  watching List watched issues (shortcut)
153
- comments List and add comments (list, add)
174
+ comments Manage comments (list, add, edit, remove)
154
175
  checklist Manage checklist items on pitches/issues (list, add, tick, untick, edit, remove)
155
176
  tags List/add/remove tags on pitches and issues
156
177
  my-work / me Show my assigned work
@@ -171,6 +192,7 @@ require_relative "commands/logout"
171
192
  require_relative "commands/orgs"
172
193
  require_relative "commands/pitches"
173
194
  require_relative "commands/cycle"
195
+ require_relative "commands/streams"
174
196
  require_relative "commands/scopes"
175
197
  require_relative "commands/tasks"
176
198
  require_relative "commands/issues"
@@ -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.4"
19
+ VERSION = "0.4.0"
20
20
  DEFAULT_HOST = "https://shapeup.cc"
21
21
 
22
22
  # Exit codes
@@ -33,6 +33,7 @@ module ShapeupCli
33
33
  "orgs" => Commands::Orgs,
34
34
  "pitches" => Commands::Pitches,
35
35
  "cycle" => Commands::Cycle,
36
+ "streams" => Commands::Streams,
36
37
  "scopes" => Commands::Scopes,
37
38
  "tasks" => Commands::Tasks,
38
39
  "issues" => Commands::Issues,
@@ -68,16 +69,18 @@ module ShapeupCli
68
69
  when "auth" then Commands::Auth.run(args)
69
70
  when "orgs" then Commands::Orgs.run(args)
70
71
  when "pitches" then Commands::Pitches.run(args)
71
- when "pitch" then Commands::Pitches.run(["show"] + args)
72
+ when "pitch" then Commands::Pitches.run([ "show" ] + args)
72
73
  when "cycle" then Commands::Cycle.run(args)
73
- when "cycles" then Commands::Cycle.run(["list"] + args)
74
+ when "cycles" then Commands::Cycle.run([ "list" ] + args)
75
+ when "streams" then Commands::Streams.run(args)
74
76
  when "scopes" then Commands::Scopes.run(args)
75
77
  when "tasks" then Commands::Tasks.run(args)
76
- when "todo" then Commands::Tasks.run(["create"] + args)
77
- when "done" then Commands::Tasks.run(["complete"] + args)
78
+ when "todo" then Commands::Tasks.run([ "create" ] + args)
79
+ when "done" then Commands::Tasks.run([ "complete" ] + args)
80
+ when "undone" then Commands::Tasks.run([ "uncomplete" ] + args)
78
81
  when "issues" then Commands::Issues.run(args)
79
- when "issue" then Commands::Issues.run(["show"] + args)
80
- when "watching" then Commands::Issues.run(["watching"] + args)
82
+ when "issue" then Commands::Issues.run([ "show" ] + args)
83
+ when "watching" then Commands::Issues.run([ "watching" ] + args)
81
84
  when "comments" then Commands::Comments.run(args)
82
85
  when "checklist" then Commands::Checklist.run(args)
83
86
  when "tags" then Commands::Tags.run(args)
@@ -126,6 +129,7 @@ module ShapeupCli
126
129
  "cycles" => "cycle list",
127
130
  "todo \"...\"" => "tasks create \"...\"",
128
131
  "done <id>" => "tasks complete <id>",
132
+ "undone <id>" => "tasks uncomplete <id>",
129
133
  "issue <id>" => "issues show <id>",
130
134
  "watching" => "issues watching",
131
135
  "me" => "my-work"
@@ -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
 
@@ -140,6 +141,8 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
140
141
  | List comments on pitch | `shapeup comments list --pitch <id> --json` |
141
142
  | Add comment to issue | `shapeup comments add --issue <id> "Comment text"` |
142
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` |
143
146
  | **Checklist** | |
144
147
  | List checklist on pitch | `shapeup checklist --pitch <id>` |
145
148
  | List checklist on issue | `shapeup checklist --issue <id>` |
@@ -159,17 +162,29 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
159
162
  | Show pitch detail | `shapeup pitch <id> --json` |
160
163
  | Create pitch | `shapeup pitches create "Title" --stream "Name"` |
161
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` |
162
169
  | **Cycles** | |
163
170
  | List cycles | `shapeup cycles --json` |
164
171
  | Active cycles | `shapeup cycles --status active --json` |
165
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` |
166
177
  | **Scopes & Tasks** | |
167
178
  | List scopes | `shapeup scopes list --pitch <id> --json` |
168
179
  | Create scope | `shapeup scopes create --pitch <id> "Title"` |
169
180
  | Update hill position | `shapeup scopes position <id> <0-100>` |
181
+ | Delete scope | `shapeup scopes delete <id> --yes` |
170
182
  | List tasks | `shapeup tasks list --pitch <id> --json` |
171
183
  | Create task | `shapeup todo "Description" --pitch <id>` |
172
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` |
173
188
  | **My Work** | |
174
189
  | All my assignments | `shapeup me --json` |
175
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.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ShapeUp
@@ -40,6 +40,7 @@ files:
40
40
  - lib/shapeup_cli/commands/scopes.rb
41
41
  - lib/shapeup_cli/commands/search.rb
42
42
  - lib/shapeup_cli/commands/setup.rb
43
+ - lib/shapeup_cli/commands/streams.rb
43
44
  - lib/shapeup_cli/commands/tags.rb
44
45
  - lib/shapeup_cli/commands/tasks.rb
45
46
  - lib/shapeup_cli/config.rb
@@ -66,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
67
  - !ruby/object:Gem::Version
67
68
  version: '0'
68
69
  requirements: []
69
- rubygems_version: 4.0.6
70
+ rubygems_version: 4.0.10
70
71
  specification_version: 4
71
72
  summary: ShapeUp CLI — manage pitches, scopes, tasks, and cycles from the terminal
72
73
  test_files: []