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 +4 -4
- data/lib/shapeup_cli/commands/base.rb +23 -0
- data/lib/shapeup_cli/commands/checklist.rb +122 -0
- data/lib/shapeup_cli/commands/comments.rb +27 -2
- data/lib/shapeup_cli/commands/issues.rb +53 -18
- data/lib/shapeup_cli/commands/pitches.rb +89 -5
- data/lib/shapeup_cli/commands/scopes.rb +19 -2
- data/lib/shapeup_cli/commands/streams.rb +66 -0
- data/lib/shapeup_cli/commands/tags.rb +85 -0
- data/lib/shapeup_cli/commands/tasks.rb +66 -8
- data/lib/shapeup_cli/commands.rb +51 -6
- data/lib/shapeup_cli/output.rb +3 -3
- data/lib/shapeup_cli.rb +27 -19
- data/skills/shapeup/SKILL.md +30 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cddf715cbc5ec20b37a27a84a33daa1fcbe1c16a959b8dac6cf45645d5f40adc
|
|
4
|
+
data.tar.gz: 7de65ac1329b084a8b43e10eeb59e63f6699b9a7763dc2ea8b76b52d4dc9d4b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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"
|
|
73
|
-
when "create"
|
|
74
|
-
when "update"
|
|
75
|
-
when "move"
|
|
76
|
-
when "done"
|
|
77
|
-
when "close"
|
|
78
|
-
when "reopen"
|
|
79
|
-
when "icebox"
|
|
80
|
-
when "defrost"
|
|
81
|
-
when "assign"
|
|
82
|
-
when "unassign"
|
|
83
|
-
when "watch"
|
|
84
|
-
when "unwatch"
|
|
85
|
-
when "watching"
|
|
86
|
-
when "
|
|
87
|
-
when "
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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"
|
|
37
|
-
when "complete"
|
|
38
|
-
when "
|
|
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
|
data/lib/shapeup_cli/commands.rb
CHANGED
|
@@ -24,20 +24,35 @@ module ShapeupCli
|
|
|
24
24
|
pitches list List pitches (packages)
|
|
25
25
|
pitches show <id> Show pitch details with scopes and tasks
|
|
26
26
|
pitch <id> Shortcut for pitches show
|
|
27
|
+
pitches create "Title" --stream "Name" [--appetite small_batch] [--cycle-id <id>]
|
|
28
|
+
pitches update <id> [--title|--status|--appetite|--stream|--content|--cycle ...]
|
|
29
|
+
pitches extend <id> --predecessor <id> Continue a previous pitch
|
|
30
|
+
pitches detach <id> Remove the predecessor link
|
|
31
|
+
pitches delete <id> Delete a pitch (asks to confirm; --yes to skip)
|
|
27
32
|
|
|
28
33
|
Cycles:
|
|
29
34
|
cycles List all cycles
|
|
30
35
|
cycle show <id> Show cycle details with pitches and progress
|
|
31
36
|
|
|
37
|
+
Streams:
|
|
38
|
+
streams List streams (product areas)
|
|
39
|
+
streams --all Include archived streams
|
|
40
|
+
streams show <id> Show stream details
|
|
41
|
+
|
|
32
42
|
Scopes:
|
|
33
43
|
scopes list --pitch <id> List scopes for a pitch
|
|
34
44
|
scopes create --pitch <id> "Title"
|
|
35
45
|
scopes update <id> --title "New title"
|
|
46
|
+
scopes position <id> <0-100> Update hill chart position
|
|
47
|
+
scopes delete <id> Delete a scope (asks to confirm; --yes to skip)
|
|
36
48
|
|
|
37
49
|
Tasks:
|
|
38
50
|
tasks list --scope <id> List tasks for a scope
|
|
39
51
|
todo "Description" --pitch <id> [--scope <id>]
|
|
40
52
|
done <id> [<id>...] Mark task(s) as complete
|
|
53
|
+
undone <id> Mark a task as incomplete
|
|
54
|
+
tasks update <id> [--description "New"] [--scope <id>|none]
|
|
55
|
+
tasks delete <id> Delete a task (asks to confirm; --yes to skip)
|
|
41
56
|
|
|
42
57
|
Issues:
|
|
43
58
|
issues List open issues
|
|
@@ -61,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
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
|
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"
|
data/lib/shapeup_cli/output.rb
CHANGED
|
@@ -92,7 +92,7 @@ module ShapeupCli
|
|
|
92
92
|
when Hash
|
|
93
93
|
render_markdown_hash(data)
|
|
94
94
|
else
|
|
95
|
-
puts data
|
|
95
|
+
puts data
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
@@ -106,7 +106,7 @@ module ShapeupCli
|
|
|
106
106
|
when Hash
|
|
107
107
|
render_styled_hash(data)
|
|
108
108
|
else
|
|
109
|
-
puts data
|
|
109
|
+
puts data
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
if breadcrumbs.any?
|
|
@@ -177,7 +177,7 @@ module ShapeupCli
|
|
|
177
177
|
when Hash
|
|
178
178
|
id = value["id"]
|
|
179
179
|
label = value["title"] || value["name"] || value["description"]
|
|
180
|
-
[id, label].compact.join(" ")
|
|
180
|
+
[ id, label ].compact.join(" ")
|
|
181
181
|
else
|
|
182
182
|
value.to_s
|
|
183
183
|
end
|
data/lib/shapeup_cli.rb
CHANGED
|
@@ -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.
|
|
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"
|
|
34
|
-
"pitches"
|
|
35
|
-
"cycle"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
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"
|
data/skills/shapeup/SKILL.md
CHANGED
|
@@ -78,6 +78,7 @@ Manage pitches, scopes, tasks, issues, and cycles via the ShapeUp CLI. Columns a
|
|
|
78
78
|
5. **"Pitch" = "Package" in code** — users say "pitch", the API uses "package". The CLI uses "pitch" everywhere.
|
|
79
79
|
6. **Use 'me' and 'none'** — `--assignee me` for current user, `--assignee none` for unassigned items.
|
|
80
80
|
7. **Check exit codes** — 0=OK, 2=not found, 3=auth error, 4=permission denied, 5=API error. Branch on exit code without parsing error text.
|
|
81
|
+
8. **Deletes need confirmation** — `delete`/`remove` commands (pitches, scopes, tasks, issues, comments) prompt `[y/N]` interactively and **refuse when run non-interactively** unless you pass `--yes` (or `-y`). As an agent you have no TTY, so add `--yes` only after you have confirmed the deletion is intended.
|
|
81
82
|
|
|
82
83
|
### Output Modes
|
|
83
84
|
|
|
@@ -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.
|
|
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:
|
|
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: []
|