shapeup-cli 0.3.2
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 +7 -0
- data/bin/shapeup +7 -0
- data/install.md +94 -0
- data/lib/shapeup_cli/auth.rb +199 -0
- data/lib/shapeup_cli/client.rb +94 -0
- data/lib/shapeup_cli/commands/auth.rb +127 -0
- data/lib/shapeup_cli/commands/base.rb +113 -0
- data/lib/shapeup_cli/commands/comments.rb +86 -0
- data/lib/shapeup_cli/commands/config_cmd.rb +143 -0
- data/lib/shapeup_cli/commands/cycle.rb +66 -0
- data/lib/shapeup_cli/commands/issues.rb +336 -0
- data/lib/shapeup_cli/commands/login.rb +73 -0
- data/lib/shapeup_cli/commands/logout.rb +12 -0
- data/lib/shapeup_cli/commands/my_work.rb +38 -0
- data/lib/shapeup_cli/commands/orgs.rb +35 -0
- data/lib/shapeup_cli/commands/pitches.rb +170 -0
- data/lib/shapeup_cli/commands/scopes.rb +96 -0
- data/lib/shapeup_cli/commands/search.rb +33 -0
- data/lib/shapeup_cli/commands/setup.rb +85 -0
- data/lib/shapeup_cli/commands/tasks.rb +100 -0
- data/lib/shapeup_cli/commands.rb +161 -0
- data/lib/shapeup_cli/config.rb +155 -0
- data/lib/shapeup_cli/output.rb +230 -0
- data/lib/shapeup_cli.rb +137 -0
- data/skills/shapeup/SKILL.md +333 -0
- metadata +70 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShapeupCli
|
|
4
|
+
module Commands
|
|
5
|
+
class Comments < Base
|
|
6
|
+
def self.metadata
|
|
7
|
+
{
|
|
8
|
+
command: "comments",
|
|
9
|
+
path: "shapeup comments",
|
|
10
|
+
short: "List and add comments on issues, pitches, scopes, and tasks",
|
|
11
|
+
subcommands: [
|
|
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"' }
|
|
14
|
+
],
|
|
15
|
+
flags: [
|
|
16
|
+
{ name: "issue", type: "string", usage: "Issue ID" },
|
|
17
|
+
{ name: "pitch", type: "string", usage: "Pitch ID" },
|
|
18
|
+
{ name: "scope", type: "string", usage: "Scope ID" },
|
|
19
|
+
{ name: "task", type: "string", usage: "Task ID" }
|
|
20
|
+
],
|
|
21
|
+
examples: [
|
|
22
|
+
"shapeup comments list --issue 42",
|
|
23
|
+
'shapeup comments add --issue 42 "Investigated — this is a CSS issue in the navbar"',
|
|
24
|
+
"shapeup comments list --pitch 10",
|
|
25
|
+
'shapeup comments add --pitch 10 "Shaped and ready for betting"'
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def execute
|
|
31
|
+
subcommand = positional_arg(0)
|
|
32
|
+
|
|
33
|
+
case subcommand
|
|
34
|
+
when "add" then add
|
|
35
|
+
when "list", nil then list
|
|
36
|
+
else list
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def list
|
|
43
|
+
type, id = resolve_commentable
|
|
44
|
+
result = call_tool("list_comments", commentable_type: type, commentable_id: id.to_s)
|
|
45
|
+
|
|
46
|
+
render result,
|
|
47
|
+
summary: "Comments on #{type} ##{id}",
|
|
48
|
+
breadcrumbs: [
|
|
49
|
+
{ cmd: "shapeup comments add --#{type.downcase} #{id} \"Your comment\"", description: "Add a comment" }
|
|
50
|
+
]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def add
|
|
54
|
+
type, id = resolve_commentable
|
|
55
|
+
text = positional_arg(1) || abort('Usage: shapeup comments add --issue <id> "Comment text"')
|
|
56
|
+
|
|
57
|
+
result = call_tool("create_comment", commentable_type: type, commentable_id: id.to_s, content: text)
|
|
58
|
+
|
|
59
|
+
render result,
|
|
60
|
+
summary: "Comment added to #{type} ##{id}",
|
|
61
|
+
breadcrumbs: [
|
|
62
|
+
{ cmd: "shapeup comments list --#{type.downcase} #{id}", description: "View all comments" }
|
|
63
|
+
]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def resolve_commentable
|
|
67
|
+
issue = extract_option("--issue")
|
|
68
|
+
pitch = extract_option("--pitch")
|
|
69
|
+
scope = extract_option("--scope")
|
|
70
|
+
task = extract_option("--task")
|
|
71
|
+
|
|
72
|
+
if issue
|
|
73
|
+
[ "Issue", issue ]
|
|
74
|
+
elsif pitch
|
|
75
|
+
[ "Package", pitch ]
|
|
76
|
+
elsif scope
|
|
77
|
+
[ "Scope", scope ]
|
|
78
|
+
elsif task
|
|
79
|
+
[ "Task", task ]
|
|
80
|
+
else
|
|
81
|
+
abort("Specify a target: --issue <id>, --pitch <id>, --scope <id>, or --task <id>")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShapeupCli
|
|
4
|
+
module Commands
|
|
5
|
+
class ConfigCmd < Base
|
|
6
|
+
def self.metadata
|
|
7
|
+
{
|
|
8
|
+
command: "config",
|
|
9
|
+
path: "shapeup config",
|
|
10
|
+
short: "Show and manage CLI configuration",
|
|
11
|
+
subcommands: [
|
|
12
|
+
{ name: "show", short: "Show current config (default)", path: "shapeup config show" },
|
|
13
|
+
{ name: "set", short: "Set a config value", path: "shapeup config set <key> <value>" },
|
|
14
|
+
{ name: "init", short: "Create .shapeup/config.json for this directory", path: "shapeup config init <org>" }
|
|
15
|
+
],
|
|
16
|
+
flags: [],
|
|
17
|
+
notes: [
|
|
18
|
+
"Config keys: org (organisation name or ID), host (ShapeUp URL)",
|
|
19
|
+
"Resolution order: --org flag > .shapeup/config.json > ~/.config/shapeup/config.json"
|
|
20
|
+
],
|
|
21
|
+
examples: [
|
|
22
|
+
"shapeup config show",
|
|
23
|
+
"shapeup config set org \"Acme Corp\"",
|
|
24
|
+
"shapeup config init \"Acme Corp\""
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def execute
|
|
30
|
+
subcommand = positional_arg(0)
|
|
31
|
+
|
|
32
|
+
case subcommand
|
|
33
|
+
when "set" then set
|
|
34
|
+
when "show" then show
|
|
35
|
+
when "init" then init_project
|
|
36
|
+
else show
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
def set
|
|
42
|
+
key = positional_arg(1) || abort("Usage: shapeup config set <key> <value>")
|
|
43
|
+
value = positional_arg(2) || abort("Usage: shapeup config set #{key} <value>")
|
|
44
|
+
|
|
45
|
+
case key
|
|
46
|
+
when "org"
|
|
47
|
+
resolved = resolve_org_value(value)
|
|
48
|
+
Config.save_config("organisation_id", resolved.to_s)
|
|
49
|
+
puts "Default organisation set to #{resolved}"
|
|
50
|
+
when "host"
|
|
51
|
+
Config.save_config("host", value)
|
|
52
|
+
puts "Host set to #{value}"
|
|
53
|
+
else
|
|
54
|
+
abort "Unknown config key: #{key}. Available: org, host"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def show
|
|
59
|
+
config = Config.load_config
|
|
60
|
+
profile = Config.current_profile
|
|
61
|
+
|
|
62
|
+
puts "Profile:"
|
|
63
|
+
if profile
|
|
64
|
+
puts " active #{profile["profile_name"]}"
|
|
65
|
+
puts " org #{profile["name"]} (#{profile["organisation_id"]})"
|
|
66
|
+
puts " host #{profile["host"]}"
|
|
67
|
+
puts " token #{profile["token"][0..7]}..."
|
|
68
|
+
else
|
|
69
|
+
puts " (not logged in)"
|
|
70
|
+
end
|
|
71
|
+
puts
|
|
72
|
+
|
|
73
|
+
overrides = []
|
|
74
|
+
overrides << "org=#{config["organisation_id"]}" if config["organisation_id"]
|
|
75
|
+
overrides << "host=#{config["host"]}" if config["host"]
|
|
76
|
+
if overrides.any?
|
|
77
|
+
puts "Overrides:"
|
|
78
|
+
puts " #{overrides.join(", ")}"
|
|
79
|
+
puts
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
puts "Files:"
|
|
83
|
+
puts " profiles #{Config::PROFILES_FILE}"
|
|
84
|
+
puts " config #{Config::CONFIG_FILE}"
|
|
85
|
+
puts " project #{Config::PROJECT_CONFIG_NAME} #{find_project_display}"
|
|
86
|
+
|
|
87
|
+
env_vars = []
|
|
88
|
+
env_vars << "SHAPEUP_TOKEN" if ENV["SHAPEUP_TOKEN"]
|
|
89
|
+
env_vars << "SHAPEUP_ORG" if ENV["SHAPEUP_ORG"]
|
|
90
|
+
env_vars << "SHAPEUP_HOST" if ENV["SHAPEUP_HOST"]
|
|
91
|
+
env_vars << "SHAPEUP_PROFILE" if ENV["SHAPEUP_PROFILE"]
|
|
92
|
+
if env_vars.any?
|
|
93
|
+
puts
|
|
94
|
+
puts "Env vars active:"
|
|
95
|
+
puts " #{env_vars.join(", ")}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def find_project_display
|
|
100
|
+
dir = Dir.pwd
|
|
101
|
+
loop do
|
|
102
|
+
candidate = File.join(dir, Config::PROJECT_CONFIG_NAME)
|
|
103
|
+
return "(found: #{candidate})" if File.exist?(candidate)
|
|
104
|
+
parent = File.dirname(dir)
|
|
105
|
+
break if parent == dir
|
|
106
|
+
dir = parent
|
|
107
|
+
end
|
|
108
|
+
"(not found)"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Create .shapeup/config.json in the current directory
|
|
112
|
+
def init_project
|
|
113
|
+
org_value = positional_arg(1) || extract_option("--org") || @org_id
|
|
114
|
+
abort("Usage: shapeup config init <org>") unless org_value
|
|
115
|
+
|
|
116
|
+
resolved = resolve_org_value(org_value)
|
|
117
|
+
|
|
118
|
+
FileUtils.mkdir_p(".shapeup")
|
|
119
|
+
File.write(".shapeup/config.json", JSON.pretty_generate(organisation_id: resolved.to_s))
|
|
120
|
+
puts "Created .shapeup/config.json (org: #{resolved})"
|
|
121
|
+
puts "All commands in this directory will use this organisation by default."
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def resolve_org_value(value)
|
|
125
|
+
return value if value.to_s.match?(/\A\d+\z/)
|
|
126
|
+
|
|
127
|
+
# Need to resolve name to ID
|
|
128
|
+
result = client.call_tool("list_organisations")
|
|
129
|
+
data = Output.extract_data(result)
|
|
130
|
+
orgs = data.is_a?(Hash) ? (data["organisations"] || []) : Array(data)
|
|
131
|
+
|
|
132
|
+
match = orgs.find { |o| o["name"]&.downcase == value.downcase }
|
|
133
|
+
|
|
134
|
+
if match
|
|
135
|
+
match["id"]
|
|
136
|
+
else
|
|
137
|
+
names = orgs.map { |o| " #{o["id"]} #{o["name"]}" }.join("\n")
|
|
138
|
+
abort "Organisation '#{value}' not found. Available:\n#{names}"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShapeupCli
|
|
4
|
+
module Commands
|
|
5
|
+
class Cycle < Base
|
|
6
|
+
def self.metadata
|
|
7
|
+
{
|
|
8
|
+
command: "cycle",
|
|
9
|
+
path: "shapeup cycle",
|
|
10
|
+
short: "List and show cycles",
|
|
11
|
+
subcommands: [
|
|
12
|
+
{ name: "list", short: "List cycles (default)", path: "shapeup cycles" },
|
|
13
|
+
{ name: "show", short: "Show cycle details with pitches and progress", path: "shapeup cycle show <id>" }
|
|
14
|
+
],
|
|
15
|
+
flags: [
|
|
16
|
+
{ name: "status", type: "string", usage: "Filter by status: active, past, future, all" }
|
|
17
|
+
],
|
|
18
|
+
examples: [
|
|
19
|
+
"shapeup cycles",
|
|
20
|
+
"shapeup cycles --status active",
|
|
21
|
+
"shapeup cycle show 12"
|
|
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
|
+
status = extract_option("--status")
|
|
40
|
+
args = {}
|
|
41
|
+
args[:status] = status if status
|
|
42
|
+
|
|
43
|
+
result = call_tool("list_cycles", **args)
|
|
44
|
+
|
|
45
|
+
render result,
|
|
46
|
+
summary: "Cycles",
|
|
47
|
+
breadcrumbs: [
|
|
48
|
+
{ cmd: "shapeup cycle show <id>", description: "View cycle details and progress" },
|
|
49
|
+
{ cmd: "shapeup cycles --status active", description: "Show active cycles only" }
|
|
50
|
+
]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def show(id = nil)
|
|
54
|
+
id ||= positional_arg(1) || abort("Usage: shapeup cycle show <id>")
|
|
55
|
+
|
|
56
|
+
result = call_tool("show_cycle", cycle: id.to_s)
|
|
57
|
+
|
|
58
|
+
render result,
|
|
59
|
+
summary: "Cycle ##{id}",
|
|
60
|
+
breadcrumbs: [
|
|
61
|
+
{ cmd: "shapeup pitches list --cycle #{id}", description: "List pitches in this cycle" }
|
|
62
|
+
]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShapeupCli
|
|
4
|
+
module Commands
|
|
5
|
+
class Issues < Base
|
|
6
|
+
def self.metadata
|
|
7
|
+
{
|
|
8
|
+
command: "issues",
|
|
9
|
+
path: "shapeup issues",
|
|
10
|
+
short: "Manage issues on the kanban board",
|
|
11
|
+
aliases: { "issue" => "issues show", "watching" => "issues watching" },
|
|
12
|
+
subcommands: [
|
|
13
|
+
{ name: "list", short: "List active issues", path: "shapeup issues" },
|
|
14
|
+
{ name: "show", short: "Show issue details", path: "shapeup issue <id>" },
|
|
15
|
+
{ name: "create", short: "Create an issue", path: "shapeup issues create \"Title\" --stream <id>" },
|
|
16
|
+
{ name: "update", short: "Update an issue", path: "shapeup issues update <id> --title \"New title\"" },
|
|
17
|
+
{ name: "move", short: "Move to a kanban column", path: "shapeup issues move <id> --column <id>" },
|
|
18
|
+
{ name: "done", short: "Mark issue as done", path: "shapeup issues done <id>" },
|
|
19
|
+
{ name: "close", short: "Close issue (won't fix)", path: "shapeup issues close <id>" },
|
|
20
|
+
{ name: "reopen", short: "Reopen a done/closed issue", path: "shapeup issues reopen <id>" },
|
|
21
|
+
{ name: "icebox", short: "Move issue to icebox", path: "shapeup issues icebox <id>" },
|
|
22
|
+
{ name: "defrost", short: "Restore issue from icebox", path: "shapeup issues defrost <id>" },
|
|
23
|
+
{ name: "assign", short: "Assign a user to an issue", path: "shapeup issues assign <id> [--user <id>]" },
|
|
24
|
+
{ name: "unassign", short: "Unassign a user from an issue", path: "shapeup issues unassign <id> [--user <id>]" },
|
|
25
|
+
{ name: "watch", short: "Watch an issue", path: "shapeup issues watch <id>" },
|
|
26
|
+
{ name: "unwatch", short: "Stop watching an issue", path: "shapeup issues unwatch <id>" },
|
|
27
|
+
{ name: "watching", short: "List issues you are watching", path: "shapeup watching" },
|
|
28
|
+
{ name: "delete", short: "Delete an issue", path: "shapeup issues delete <id>" }
|
|
29
|
+
],
|
|
30
|
+
flags: [
|
|
31
|
+
{ name: "stream", type: "string", usage: "Stream ID (required for create, optional filter for list)" },
|
|
32
|
+
{ name: "column", type: "string", usage: "Kanban column ID (for move or filter)" },
|
|
33
|
+
{ name: "kind", type: "string", usage: "Filter by kind: bug, request, all" },
|
|
34
|
+
{ name: "assignee", type: "string", usage: "User ID or 'me' (for list)" },
|
|
35
|
+
{ name: "user", type: "string", usage: "User ID or 'me' (for assign/unassign, defaults to 'me')" },
|
|
36
|
+
{ name: "tag", type: "string", usage: "Filter by tag name (for list)" },
|
|
37
|
+
{ name: "all", type: "bool", usage: "Include done/closed issues (hidden by default)" },
|
|
38
|
+
{ name: "content", type: "string", usage: "Issue content/description" },
|
|
39
|
+
{ name: "title", type: "string", usage: "Issue title (for update)" },
|
|
40
|
+
{ name: "archived", type: "bool", usage: "Include iceboxed issues in list" }
|
|
41
|
+
],
|
|
42
|
+
examples: [
|
|
43
|
+
"shapeup issues",
|
|
44
|
+
"shapeup issues --tag seo",
|
|
45
|
+
"shapeup issues --assignee me",
|
|
46
|
+
"shapeup issues --column 3",
|
|
47
|
+
"shapeup issues --all",
|
|
48
|
+
"shapeup issues --stream 3 --kind bug",
|
|
49
|
+
"shapeup issue 42",
|
|
50
|
+
"shapeup issues create \"Fix checkout\" --stream 3 --content \"The button is broken\"",
|
|
51
|
+
"shapeup issues move 42 --column 5",
|
|
52
|
+
"shapeup issues done 42",
|
|
53
|
+
"shapeup issues close 42",
|
|
54
|
+
"shapeup issues reopen 42",
|
|
55
|
+
"shapeup issues icebox 42",
|
|
56
|
+
"shapeup issues defrost 42",
|
|
57
|
+
"shapeup issues assign 42",
|
|
58
|
+
"shapeup issues assign 42 --user 7",
|
|
59
|
+
"shapeup issues unassign 42",
|
|
60
|
+
"shapeup issues watch 42",
|
|
61
|
+
"shapeup watching"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def execute
|
|
67
|
+
subcommand = positional_arg(0)
|
|
68
|
+
|
|
69
|
+
case subcommand
|
|
70
|
+
when "show" then show
|
|
71
|
+
when "create" then create
|
|
72
|
+
when "update" then update
|
|
73
|
+
when "move" then move
|
|
74
|
+
when "done" then mark_done
|
|
75
|
+
when "close" then close
|
|
76
|
+
when "reopen" then reopen
|
|
77
|
+
when "icebox" then icebox
|
|
78
|
+
when "defrost" then defrost
|
|
79
|
+
when "assign" then assign
|
|
80
|
+
when "unassign" then unassign
|
|
81
|
+
when "watch" then watch
|
|
82
|
+
when "unwatch" then unwatch
|
|
83
|
+
when "watching" then watching
|
|
84
|
+
when "delete" then delete
|
|
85
|
+
when "list", nil then list
|
|
86
|
+
else
|
|
87
|
+
# Bare numeric arg = show
|
|
88
|
+
if subcommand&.match?(/\A\d+\z/)
|
|
89
|
+
@remaining.unshift(subcommand)
|
|
90
|
+
show
|
|
91
|
+
else
|
|
92
|
+
list
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def list
|
|
100
|
+
stream = extract_option("--stream")
|
|
101
|
+
column = extract_option("--column")
|
|
102
|
+
kind = extract_option("--kind")
|
|
103
|
+
assignee = extract_option("--assignee")
|
|
104
|
+
tag = extract_option("--tag")
|
|
105
|
+
show_all = @remaining.delete("--all")
|
|
106
|
+
|
|
107
|
+
args = {}
|
|
108
|
+
args[:stream] = stream if stream
|
|
109
|
+
args[:kanban_column] = column if column
|
|
110
|
+
args[:kind] = kind if kind
|
|
111
|
+
args[:assignee] = assignee if assignee
|
|
112
|
+
args[:tag] = tag if tag
|
|
113
|
+
args[:include_closed] = true if show_all
|
|
114
|
+
args[:include_archived] = true if @remaining.delete("--archived")
|
|
115
|
+
|
|
116
|
+
result = call_tool("list_issues", **args)
|
|
117
|
+
|
|
118
|
+
render result,
|
|
119
|
+
summary: "Issues",
|
|
120
|
+
breadcrumbs: [
|
|
121
|
+
{ cmd: "shapeup issue <id>", description: "View issue details" },
|
|
122
|
+
{ cmd: "shapeup issues create \"Title\" --stream <id>", description: "Create an issue" }
|
|
123
|
+
]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def show
|
|
127
|
+
id = positional_arg(1) || positional_arg(0) || abort("Usage: shapeup issue <id>")
|
|
128
|
+
result = call_tool("show_issue", issue: id.to_s)
|
|
129
|
+
|
|
130
|
+
render result,
|
|
131
|
+
summary: "Issue ##{id}",
|
|
132
|
+
breadcrumbs: [
|
|
133
|
+
{ cmd: "shapeup issues move #{id} --column <id>", description: "Move to column" },
|
|
134
|
+
{ cmd: "shapeup issues icebox #{id}", description: "Move to icebox" },
|
|
135
|
+
{ cmd: "shapeup issues watch #{id}", description: "Watch this issue" }
|
|
136
|
+
]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def create
|
|
140
|
+
stream_id = extract_option("--stream") || abort("Usage: shapeup issues create \"Title\" --stream <id> [--content \"...\"] [--kind bug|request]")
|
|
141
|
+
content = extract_option("--content")
|
|
142
|
+
kind = extract_option("--kind")
|
|
143
|
+
column = extract_option("--column")
|
|
144
|
+
title = positional_arg(1) || abort("Usage: shapeup issues create \"Title\" --stream <id>")
|
|
145
|
+
|
|
146
|
+
args = { stream: stream_id.to_s, title: title, content: content || title }
|
|
147
|
+
args[:kind] = kind if kind
|
|
148
|
+
args[:kanban_column] = column if column
|
|
149
|
+
|
|
150
|
+
result = call_tool("create_issue", **args)
|
|
151
|
+
|
|
152
|
+
render result,
|
|
153
|
+
summary: "Issue created",
|
|
154
|
+
breadcrumbs: [
|
|
155
|
+
{ cmd: "shapeup issues", description: "List all issues" },
|
|
156
|
+
{ cmd: "shapeup issue <id>", description: "View the issue" }
|
|
157
|
+
]
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def update
|
|
161
|
+
id = positional_arg(1) || abort("Usage: shapeup issues update <id> [--title \"...\"] [--content \"...\"] [--kind bug|request]")
|
|
162
|
+
title = extract_option("--title")
|
|
163
|
+
content = extract_option("--content")
|
|
164
|
+
kind = extract_option("--kind")
|
|
165
|
+
|
|
166
|
+
args = { issue: id.to_s }
|
|
167
|
+
args[:title] = title if title
|
|
168
|
+
args[:content] = content if content
|
|
169
|
+
args[:kind] = kind if kind
|
|
170
|
+
|
|
171
|
+
result = call_tool("update_issue", **args)
|
|
172
|
+
|
|
173
|
+
render result,
|
|
174
|
+
summary: "Issue ##{id} updated",
|
|
175
|
+
breadcrumbs: [
|
|
176
|
+
{ cmd: "shapeup issue #{id}", description: "View issue" }
|
|
177
|
+
]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def move
|
|
181
|
+
id = positional_arg(1) || abort("Usage: shapeup issues move <id> --column <id>")
|
|
182
|
+
column_id = extract_option("--column") || abort("Usage: shapeup issues move <id> --column <id>")
|
|
183
|
+
|
|
184
|
+
result = call_tool("move_issue", issue: id.to_s, kanban_column: column_id.to_s)
|
|
185
|
+
|
|
186
|
+
render result,
|
|
187
|
+
summary: "Issue ##{id} moved",
|
|
188
|
+
breadcrumbs: [
|
|
189
|
+
{ cmd: "shapeup issue #{id}", description: "View issue" },
|
|
190
|
+
{ cmd: "shapeup issues", description: "List all issues" }
|
|
191
|
+
]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def icebox
|
|
195
|
+
id = positional_arg(1) || abort("Usage: shapeup issues icebox <id>")
|
|
196
|
+
|
|
197
|
+
result = call_tool("archive_issue", issue: id.to_s)
|
|
198
|
+
|
|
199
|
+
render result,
|
|
200
|
+
summary: "Issue ##{id} iceboxed",
|
|
201
|
+
breadcrumbs: [
|
|
202
|
+
{ cmd: "shapeup issues defrost #{id}", description: "Defrost this issue" },
|
|
203
|
+
{ cmd: "shapeup issues --archived", description: "List iceboxed issues" }
|
|
204
|
+
]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def mark_done
|
|
208
|
+
id = positional_arg(1) || abort("Usage: shapeup issues done <id>")
|
|
209
|
+
|
|
210
|
+
result = call_tool("close_issue", issue: id.to_s, resolution: "done")
|
|
211
|
+
|
|
212
|
+
render result,
|
|
213
|
+
summary: "Issue ##{id} done",
|
|
214
|
+
breadcrumbs: [
|
|
215
|
+
{ cmd: "shapeup issues reopen #{id}", description: "Reopen this issue" },
|
|
216
|
+
{ cmd: "shapeup issues", description: "List open issues" }
|
|
217
|
+
]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def close
|
|
221
|
+
id = positional_arg(1) || abort("Usage: shapeup issues close <id>")
|
|
222
|
+
|
|
223
|
+
result = call_tool("close_issue", issue: id.to_s, resolution: "closed")
|
|
224
|
+
|
|
225
|
+
render result,
|
|
226
|
+
summary: "Issue ##{id} closed",
|
|
227
|
+
breadcrumbs: [
|
|
228
|
+
{ cmd: "shapeup issues reopen #{id}", description: "Reopen this issue" },
|
|
229
|
+
{ cmd: "shapeup issues", description: "List open issues" }
|
|
230
|
+
]
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def reopen
|
|
234
|
+
id = positional_arg(1) || abort("Usage: shapeup issues reopen <id>")
|
|
235
|
+
|
|
236
|
+
result = call_tool("reopen_issue", issue: id.to_s)
|
|
237
|
+
|
|
238
|
+
render result,
|
|
239
|
+
summary: "Issue ##{id} reopened",
|
|
240
|
+
breadcrumbs: [
|
|
241
|
+
{ cmd: "shapeup issue #{id}", description: "View issue" },
|
|
242
|
+
{ cmd: "shapeup issues", description: "List all issues" }
|
|
243
|
+
]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def defrost
|
|
247
|
+
id = positional_arg(1) || abort("Usage: shapeup issues defrost <id>")
|
|
248
|
+
|
|
249
|
+
result = call_tool("unarchive_issue", issue: id.to_s)
|
|
250
|
+
|
|
251
|
+
render result,
|
|
252
|
+
summary: "Issue ##{id} defrosted",
|
|
253
|
+
breadcrumbs: [
|
|
254
|
+
{ cmd: "shapeup issue #{id}", description: "View issue" },
|
|
255
|
+
{ cmd: "shapeup issues", description: "List all issues" }
|
|
256
|
+
]
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def assign
|
|
260
|
+
id = positional_arg(1) || abort("Usage: shapeup issues assign <id> [--user <id>]")
|
|
261
|
+
user_id = extract_option("--user") || "me"
|
|
262
|
+
|
|
263
|
+
result = call_tool("assign_user", assignable_type: "Issue", assignable_id: id.to_s, user_id: user_id.to_s)
|
|
264
|
+
|
|
265
|
+
render result,
|
|
266
|
+
summary: "Assigned to issue ##{id}",
|
|
267
|
+
breadcrumbs: [
|
|
268
|
+
{ cmd: "shapeup issues unassign #{id}", description: "Unassign from issue" },
|
|
269
|
+
{ cmd: "shapeup issue #{id}", description: "View issue" }
|
|
270
|
+
]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def unassign
|
|
274
|
+
id = positional_arg(1) || abort("Usage: shapeup issues unassign <id> [--user <id>]")
|
|
275
|
+
user_id = extract_option("--user") || "me"
|
|
276
|
+
|
|
277
|
+
result = call_tool("unassign_user", assignable_type: "Issue", assignable_id: id.to_s, user_id: user_id.to_s)
|
|
278
|
+
|
|
279
|
+
render result,
|
|
280
|
+
summary: "Unassigned from issue ##{id}",
|
|
281
|
+
breadcrumbs: [
|
|
282
|
+
{ cmd: "shapeup issues assign #{id}", description: "Assign to issue" },
|
|
283
|
+
{ cmd: "shapeup issue #{id}", description: "View issue" }
|
|
284
|
+
]
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def watch
|
|
288
|
+
id = positional_arg(1) || abort("Usage: shapeup issues watch <id>")
|
|
289
|
+
|
|
290
|
+
result = call_tool("watch_issue", issue: id.to_s)
|
|
291
|
+
|
|
292
|
+
render result,
|
|
293
|
+
summary: "Watching issue ##{id}",
|
|
294
|
+
breadcrumbs: [
|
|
295
|
+
{ cmd: "shapeup watching", description: "List watched issues" },
|
|
296
|
+
{ cmd: "shapeup issues unwatch #{id}", description: "Stop watching" }
|
|
297
|
+
]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def unwatch
|
|
301
|
+
id = positional_arg(1) || abort("Usage: shapeup issues unwatch <id>")
|
|
302
|
+
|
|
303
|
+
result = call_tool("unwatch_issue", issue: id.to_s)
|
|
304
|
+
|
|
305
|
+
render result,
|
|
306
|
+
summary: "Unwatched issue ##{id}",
|
|
307
|
+
breadcrumbs: [
|
|
308
|
+
{ cmd: "shapeup watching", description: "List watched issues" }
|
|
309
|
+
]
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def watching
|
|
313
|
+
result = call_tool("list_watched_issues")
|
|
314
|
+
|
|
315
|
+
render result,
|
|
316
|
+
summary: "Watched Issues",
|
|
317
|
+
breadcrumbs: [
|
|
318
|
+
{ cmd: "shapeup issue <id>", description: "View issue details" },
|
|
319
|
+
{ cmd: "shapeup issues unwatch <id>", description: "Stop watching" }
|
|
320
|
+
]
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def delete
|
|
324
|
+
id = positional_arg(1) || abort("Usage: shapeup issues delete <id>")
|
|
325
|
+
|
|
326
|
+
result = call_tool("delete_issue", issue: id.to_s)
|
|
327
|
+
|
|
328
|
+
render result,
|
|
329
|
+
summary: "Issue ##{id} deleted",
|
|
330
|
+
breadcrumbs: [
|
|
331
|
+
{ cmd: "shapeup issues", description: "List remaining issues" }
|
|
332
|
+
]
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|