shapeup-cli 0.8.0 → 0.9.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: c4b97dfcf5b4550677ef419ad28ae3900f770c5f3c3795e7b005d2a09582f472
4
- data.tar.gz: 8ad78fe52812199514232eadbf8d5e52506e916c391ac6c240ad7730edd6d8fe
3
+ metadata.gz: ecb0aff2791935bf727f5afbb283ec92bb181325bb57b8ba0d6778d05b18db26
4
+ data.tar.gz: 8f938f4be9b1dafc98c44916bcf539b936ba89fa0ca9c245cf78b1531c6a64c6
5
5
  SHA512:
6
- metadata.gz: 0467cb5be9b5e7d830406d00495cb9929dfd15772aa7ae09e8abb30237ebceb4e8cc2ad3286e2ef191d5a4186ced7cf900b1d66016edb9f31719a4bead6b06da
7
- data.tar.gz: 9a8eea1d289458f647e4c9352a479e8f2bcc726d268a4577e3659c0b4f00132bf0d06a62f8d8f1a5251b01080c69611fe2baea7e0ebe54c53d203ed1432221f7
6
+ metadata.gz: f337c2f8aade0196c9cdaff8f48d7e10a4216736844e7b2381aa348dcc0855a12d8a0d3ee557fee7778599f1ab8bb15399676afcf87c8150b446e54e5328d323
7
+ data.tar.gz: 517305b0931a662d4b3b8a4cfb2910a3a879b64bfce4d303edaf7da7cf390a6f0c2c8f1155ab9f2f0261ec64c6ef69574fed070f564c0ad0b9f8b9ebe820149f
@@ -100,6 +100,14 @@ module ShapeupCli
100
100
  value
101
101
  end
102
102
 
103
+ def extract_options(flag)
104
+ values = []
105
+ while (value = extract_option(flag))
106
+ values << value
107
+ end
108
+ values
109
+ end
110
+
103
111
  # Get the first positional arg (not a flag)
104
112
  def positional_arg(position = 0)
105
113
  positionals = @remaining.reject { |a| a.start_with?("--") }
@@ -20,7 +20,8 @@ module ShapeupCli
20
20
  { name: "pitch", type: "string", usage: "Pitch ID" },
21
21
  { name: "scope", type: "string", usage: "Scope ID" },
22
22
  { name: "task", type: "string", usage: "Task ID" },
23
- { name: "parent", type: "string", usage: "Comment ID to reply under" }
23
+ { name: "parent", type: "string", usage: "Comment ID to reply under" },
24
+ { name: "attach", type: "string", usage: "Image to show under the comment (repeatable). Local ![description](path) images in the text are attached too" }
24
25
  ],
25
26
  examples: [
26
27
  "shapeup comments list --issue 42",
@@ -62,10 +63,15 @@ module ShapeupCli
62
63
  def add
63
64
  type, id = resolve_commentable
64
65
  parent = extract_option("--parent")
65
- text = positional_arg(1) || abort('Usage: shapeup comments add --issue <id> [--parent <comment_id>] "Comment text"')
66
+ attach_paths = extract_options("--attach")
67
+ text = positional_arg(1) || abort('Usage: shapeup comments add --issue <id> [--parent <comment_id>] [--attach <image>] "Comment text"')
68
+
69
+ text, images = local_images_in(text)
70
+ images += attach_paths.map { |path| [ path, nil ] }
66
71
 
67
72
  arguments = { commentable_type: type, commentable_id: id.to_s, content: text }
68
73
  arguments[:parent_id] = parent.to_s if parent
74
+ arguments[:attachments] = images.map { |path, alt| upload_image(path, alt) } if images.any?
69
75
  result = call_tool("create_comment", **arguments)
70
76
 
71
77
  render result,
@@ -103,6 +109,35 @@ module ShapeupCli
103
109
  render result, summary: "Reacted #{emoji} to comment ##{comment_id}"
104
110
  end
105
111
 
112
+ IMAGE_TYPES = { ".png" => "image/png", ".jpg" => "image/jpeg", ".jpeg" => "image/jpeg", ".gif" => "image/gif", ".webp" => "image/webp" }.freeze
113
+ IMAGE_MAX_SIZE = 10 * 1024 * 1024
114
+ MARKDOWN_IMAGE = /!\[([^\]]*)\]\(([^)\s]+)\)\n?/
115
+
116
+ def local_images_in(text)
117
+ images = []
118
+ text = text.gsub(MARKDOWN_IMAGE) do |markdown|
119
+ alt, path = $1, $2
120
+ if path.match?(%r{\A[a-z][a-z0-9+.-]*://}i) || !File.file?(path)
121
+ markdown
122
+ else
123
+ images << [ path, alt ]
124
+ ""
125
+ end
126
+ end
127
+ [ text.strip, images ]
128
+ end
129
+
130
+ def upload_image(path, alt)
131
+ abort "No such file: #{path}" unless File.file?(path)
132
+ content_type = IMAGE_TYPES[File.extname(path).downcase] or abort "Only PNG, JPEG, GIF and WebP images can be attached: #{path}"
133
+ abort "Images can be at most 10 MB: #{path}" if File.size(path) > IMAGE_MAX_SIZE
134
+
135
+ data = [ File.binread(path) ].pack("m0")
136
+ uploaded = Output.extract_data(call_tool("upload_attachment", filename: File.basename(path), content_type: content_type, data: data))
137
+
138
+ { attachment: uploaded["attachment"], alt: alt.to_s.empty? ? File.basename(path, ".*").tr("-_", " ") : alt }
139
+ end
140
+
106
141
  def resolve_commentable
107
142
  issue = extract_option("--issue")
108
143
  pitch = extract_option("--pitch")
@@ -121,7 +121,7 @@ module ShapeupCli
121
121
 
122
122
  def profile_candidates
123
123
  select_first [
124
- { source: "SHAPEUP_PROFILE env", value: ENV["SHAPEUP_PROFILE"] },
124
+ { source: "--profile flag or SHAPEUP_PROFILE env", value: ENV["SHAPEUP_PROFILE"] },
125
125
  { source: "profiles.json default", value: Config.saved_default_profile }
126
126
  ]
127
127
  end
@@ -130,6 +130,7 @@ module ShapeupCli
130
130
  select_first [
131
131
  { source: "--org flag", value: @org_id },
132
132
  { source: "SHAPEUP_ORG env", value: ENV["SHAPEUP_ORG"] },
133
+ { source: picked_profile_source, value: Config.chosen_profile_value("organisation_id") },
133
134
  { source: project_config_source, value: Config.project_config["organisation_id"] },
134
135
  { source: "~/.config/shapeup/config.json", value: Config.global_config["organisation_id"] },
135
136
  { source: "profile '#{Config.current_profile_name}'", value: Config.current_profile&.dig("organisation_id") }
@@ -139,6 +140,7 @@ module ShapeupCli
139
140
  def host_candidates
140
141
  select_first [
141
142
  { source: "SHAPEUP_HOST env", value: ENV["SHAPEUP_HOST"] },
143
+ { source: picked_profile_source, value: Config.chosen_profile_value("host") },
142
144
  { source: project_config_source, value: Config.project_config["host"] },
143
145
  { source: "~/.config/shapeup/config.json", value: Config.global_config["host"] },
144
146
  { source: "profile '#{Config.current_profile_name}'", value: Config.current_profile&.dig("host") },
@@ -153,6 +155,10 @@ module ShapeupCli
153
155
  ]
154
156
  end
155
157
 
158
+ def picked_profile_source
159
+ "profile picked with --profile (#{ENV["SHAPEUP_PROFILE"] || "none"})"
160
+ end
161
+
156
162
  def project_config_source
157
163
  Config.project_config_path || Config::PROJECT_CONFIG_NAME
158
164
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShapeupCli
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.0"
5
5
  end
@@ -222,6 +222,7 @@ Branch on `retryable` to decide retry-vs-abandon; `false` means "no known reason
222
222
  | Watch the inbox as JSON lines | `SHAPEUP_PROFILE=<agent> shapeup connect` |
223
223
  | Reply under a comment | `shapeup comments add --issue <id> --parent <comment_id> "Reply"` |
224
224
  | React to a comment | `shapeup comments react <comment_id> 👀` |
225
+ | Attach screenshots to a comment | `shapeup comments add --issue <id> "Text ![What it shows](./shot.png)"` or `--attach shot.png` |
225
226
 
226
227
  ## Common Workflows
227
228
 
@@ -64,6 +64,23 @@ themselves.
64
64
  }
65
65
  ```
66
66
 
67
+ Always fill in the test command before asking, so the agent can check its work unattended.
68
+ Work out how this project runs its tests from what is in the directory, and list each way
69
+ you find:
70
+
71
+ | You find | Add |
72
+ |----------|-----|
73
+ | `bin/rails` | `"Bash(bin/rails test *)"` |
74
+ | `Rakefile` | `"Bash(bundle exec rake *)"` |
75
+ | a `test/` folder of Ruby files and no Rakefile | `"Bash(ruby test/*)"` |
76
+ | `package.json` with a `test` script | `"Bash(npm test *)"` (or the yarn/pnpm equivalent the lockfile shows) |
77
+ | `Makefile` with a test or check target | `"Bash(make test *)"` / `"Bash(make check *)"` |
78
+ | `pyproject.toml`, `pytest.ini` or `tests/` with Python | `"Bash(pytest *)"` |
79
+ | `go.mod` | `"Bash(go test *)"` |
80
+ | `Cargo.toml` | `"Bash(cargo test *)"` |
81
+
82
+ If you can't tell, ask the user how they run the tests rather than leaving it out.
83
+
67
84
  Merge with what is there, never replace it, and make sure `.claude/settings.local.json` is in
68
85
  `.gitignore`. If they say no, carry on: they will approve each command instead. If prompts keep
69
86
  appearing after writing it, tell them a new session picks the rules up.
@@ -125,13 +142,20 @@ Give the subagent this, filled in:
125
142
  > commit there. Never push, deploy, merge or delete branches unless the owner said so in this
126
143
  > session.
127
144
  > 4. If the ask is unclear or you are blocked, ask on the issue rather than guess.
128
- > 5. Reply once, under the comment that asked:
145
+ > 5. If the work changes anything people see, show it. Use whatever browser tool you have to take
146
+ > screenshots of each place it touches, before (from the main branch) and after (from yours),
147
+ > at the size people use it and on a phone when it matters. Save them as PNG files outside the
148
+ > repository, with names that say what they show.
149
+ > 6. Reply once, under the comment that asked:
129
150
  > `shapeup --profile <profile> comments add --issue <issue.id> --parent <reply_to.comment_id> "<reply>"`
130
151
  > (leave out `--parent` when `reply_to.comment_id` is null). Start with `@<requester.name>`.
131
152
  > Say what changed, the branch and commit, how you checked it, and anything still open.
132
- > Keep it short and plain.
133
- > 6. Then take it out of the inbox: `shapeup --profile <profile> inbox done <id>`.
134
- > 7. Report back in one line.
153
+ > Keep it short and plain. Put each screenshot in the reply as a local Markdown image,
154
+ > `![Toolbar after the fix, on a phone](/tmp/toolbar-after-phone.png)`: the CLI uploads it and
155
+ > the words in brackets become its description for people who can't see it, so write them
156
+ > properly. Mention anyone the requester asked you to hand over to.
157
+ > 7. Then take it out of the inbox: `shapeup --profile <profile> inbox done <id>`.
158
+ > 8. Report back in one line.
135
159
 
136
160
  ## Rules
137
161
 
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.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ShapeUp