@mediamonks/canopy-cli 0.7.7 → 0.7.9

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.
Files changed (37) hide show
  1. package/dist/{db-D3RKaLVU.mjs → db-8Etjmzxh.mjs} +2 -2
  2. package/dist/{deploy-ClioT9ZC.mjs → deploy-y75cgauK.mjs} +2 -2
  3. package/dist/dev-BYxSHUH6.mjs +2 -0
  4. package/dist/{dev-BbAVZcAR.mjs → dev-DtoZrexp.mjs} +63 -8
  5. package/dist/dev-worker-BZgtEM9V.mjs +313 -0
  6. package/dist/docs/cli-dev.md +19 -0
  7. package/dist/docs/cli-feedback.md +38 -14
  8. package/dist/docs/dev.md +19 -0
  9. package/dist/docs/overview-how-it-works.md +2 -1
  10. package/dist/docs/platform-client-access.md +2 -1
  11. package/dist/docs/platform-feedback.md +46 -11
  12. package/dist/docs/platform-members-and-access.md +2 -2
  13. package/dist/docs/platform-project-data.md +19 -9
  14. package/dist/{docs-DOiL2Sw-.mjs → docs-VmcoH1An.mjs} +2 -2
  15. package/dist/{egress-BkkG9Jzs.mjs → egress-CN_axsAN.mjs} +1 -1
  16. package/dist/feedback-DUuWXS_D.mjs +390 -0
  17. package/dist/index.mjs +24 -24
  18. package/dist/{local-BHxSZwUT.mjs → local-C5AhA4GM.mjs} +160 -6
  19. package/dist/{login-C1x1l3qg.mjs → login-D7dI8D9N.mjs} +28 -12
  20. package/dist/{logs-DuzM-_Rn.mjs → logs-DNiZquki.mjs} +1 -1
  21. package/dist/{migrations-DpWHuqkQ.mjs → migrations-C-5x6SYf.mjs} +1 -1
  22. package/dist/{open-CLSh6oez.mjs → open-DRPp4QJg.mjs} +1 -1
  23. package/dist/{package-BNiOibhj.mjs → package-CGrKr6IT.mjs} +1 -1
  24. package/dist/{projects-C9TBoiW7.mjs → projects-C6JQjVqg.mjs} +1 -1
  25. package/dist/{skill-DRTg-mJR.mjs → skill-CZP1AqQX.mjs} +2 -2
  26. package/dist/{staging-95xeXyZN.mjs → staging-Bw1D_J8M.mjs} +1 -1
  27. package/dist/templates/worker/.dev.vars.example +9 -0
  28. package/dist/templates/worker/AGENTS.md +7 -1
  29. package/dist/templates/worker/README.md +4 -1
  30. package/dist/templates/worker/_gitignore +1 -0
  31. package/dist/{token-BjlqT2Xj.mjs → token-D7zrIF8G.mjs} +1 -1
  32. package/dist/{ui-CpdtK7mt.mjs → ui-2mrJcjYC.mjs} +30 -1
  33. package/dist/{upgrade-BDaM4M6Y.mjs → upgrade-CmhSO_r6.mjs} +2 -2
  34. package/dist/{usage-wpew0a2M.mjs → usage-CX1Izj_l.mjs} +1 -1
  35. package/package.json +1 -1
  36. package/dist/dev-D6Sy15Wv.mjs +0 -2
  37. package/dist/feedback-BZG8E8zG.mjs +0 -258
@@ -29,12 +29,41 @@ boards.
29
29
  "Bug" for "Docs issue", and `canopy-feedback-prompt` replaces the textarea
30
30
  placeholder. Kinds are limited to `bug`, `feedback`, `docs`.
31
31
 
32
+ ## Screenshots
33
+
34
+ A screenshot is often the whole report, so you can attach up to six to any
35
+ report:
36
+
37
+ - **In the widget:** click **Attach screenshot**, or paste one straight from
38
+ the clipboard into the message box, or drop an image on the panel. Thumbnails
39
+ show what will be sent, and each one can be removed before you hit Send.
40
+ - **On the dashboard:** the same three ways, from the report itself — so you can
41
+ add a picture to a report after filing it, or to a comment as you write it
42
+ ("here's what it looks like after the fix"). Click any thumbnail for the full
43
+ image, then use the arrow keys to page through the rest.
44
+ - **From the CLI:** `canopy feedback report "…" --attach ./shot.png`, or
45
+ `canopy feedback attach <id> ./after-fix.png` for a report that already
46
+ exists. `canopy feedback show <id> --download <dir>` saves a report's
47
+ screenshots so an agent can look at them.
48
+
49
+ PNG, JPEG, WebP and GIF, up to 5 MB each. Large screenshots are scaled down in
50
+ your browser before they're sent, so a full-screen retina capture goes up small
51
+ without you doing anything. Screenshots are visible to the same people as the
52
+ report itself, and are deleted with it.
53
+
32
54
  ## The inbox
33
55
 
34
56
  Project members triage reports on the **Feedback tab**:
35
57
 
36
- - Statuses: `new → accepted → in_progress → resolved`, plus `declined`.
37
- **`accepted` means "this is valid — an agent may pick it up."**
58
+ - Statuses: `investigate → investigated → accepted → in_progress → resolved`,
59
+ plus `declined`.
60
+ - Everything arrives as **investigate** — nobody has looked at it yet. An agent
61
+ can pick those up, work out what is actually going on, and write the findings
62
+ on the report as **investigated**.
63
+ - **`accepted` means a person said "build this."** It is the one status an
64
+ agent should never reach for on its own.
65
+ - The order is a suggestion, not a track. Accept something straight away, skip
66
+ the investigation, or decline it at any point.
38
67
  - Every status change and comment is recorded on the item's trail.
39
68
  - Reporters follow their submissions on the **Reports page** (dashboard
40
69
  header) and can comment on or withdraw their own items; only project
@@ -42,21 +71,27 @@ Project members triage reports on the **Feedback tab**:
42
71
 
43
72
  ## Working the backlog with an agent
44
73
 
45
- Because the CLI exposes the inbox, "work through the accepted feedback" is a
46
- prompt, not a process:
74
+ Because the CLI exposes the inbox, "look into the new reports" and "work
75
+ through the accepted feedback" are both prompts, not processes:
47
76
 
48
77
  ```bash
49
- canopy feedback list --status accepted # what a human marked valid
50
- canopy feedback show <id> # body + captured context + trail
51
- canopy feedback comment <id> "root cause: …" # append findings
78
+ canopy feedback list --status investigate # what nobody has looked at yet
79
+ canopy feedback show <id> # body + captured context + trail
80
+ canopy feedback status <id> investigated \
81
+ --comment "root cause: the upload grant expires too early"
82
+ canopy feedback list --status accepted # what a person asked for
52
83
  canopy feedback status <id> resolved --comment "fixed in v42"
53
84
  ```
54
85
 
86
+ `investigated` always carries the findings — the note is required, because a
87
+ report that claims someone looked into it and shows nothing is worse than an
88
+ untouched one. Then it waits: **accepting is the person's call**, so an agent
89
+ sets `accepted` only when the person it is working with says to build the
90
+ thing.
91
+
55
92
  `show` includes the deploy version that served the page when the bug was
56
- reported — reproduce against that, not whatever is live now. An agent that
57
- investigates but can't fix should `comment` its findings and leave the status
58
- for a human; one that pushes back should `status <id> declined` with a comment
59
- saying why.
93
+ reported — reproduce against that, not whatever is live now. A report worth
94
+ pushing back on gets `status <id> declined` with a comment saying why.
60
95
 
61
96
  ## Reporting problems with Canopy itself
62
97
 
@@ -44,8 +44,8 @@ from the same card.
44
44
 
45
45
  Sometimes "any Monk can look" is too open — payroll prototypes, sensitive
46
46
  client work, unfinished things you'd rather not demo by accident. Flip on
47
- **Settings → Collaborators only** and the live app is limited to the
48
- project's roster:
47
+ **Settings → Who at Monks can open it → Restrict to collaborators** and the
48
+ live app is limited to the project's roster:
49
49
 
50
50
  - Other Monks who open the URL get a clear "limited to its collaborators"
51
51
  page instead of the app.
@@ -8,17 +8,27 @@ created automatically the moment the project exists. There are no connection
8
8
  strings to configure and no servers to run — your app reads and writes through
9
9
  the Canopy SDK, and no other project can ever see your data.
10
10
 
11
- ## The Data tab
11
+ ## The Data tab and the data explorer
12
12
 
13
13
  Open a project in the dashboard and the **Data** tab shows what your app has
14
- stored:
15
-
16
- - **Collections** — the document-style data your app writes with
17
- `canopy.db.collection(...)`. Pick a collection and page through its rows.
18
- - **Tables** — a browser for the underlying database tables, for when your
19
- app uses raw SQL or you want to see exactly what's on disk.
20
- - **SQL console** — run a query directly against the project's database from
21
- the browser. Useful for quick checks and one-off fixes.
14
+ stored — every collection and table at a glance — plus the door into the
15
+ **data explorer**, a full-page workspace for actually digging in:
16
+
17
+ - **Schema sidebar** — collections and tables, each expandable to its
18
+ columns (types, primary keys) and indexes.
19
+ - **Browse** — click any table or collection to page through it with real
20
+ totals, sort by any column, and stack filters (equals, contains,
21
+ comparisons, null checks) without writing SQL. Click a row for the full
22
+ record, JSON pretty-printed. Export the current page as CSV or JSON.
23
+ - **SQL console** — a real editor with syntax highlighting and autocomplete
24
+ fed by your schema. Run with <kbd>⌘⏎</kbd>, see row counts and timing,
25
+ recall earlier queries from history, and hit **Explain** to see the query
26
+ plan.
27
+
28
+ The console is **read-only by default** — the platform rejects anything but
29
+ reads until you flip the writes toggle, so exploring production data is safe
30
+ by design. The explorer is fixed to the channel (live or staging) you opened
31
+ it from; to look at the other one, go back to the Data tab and switch there.
22
32
 
23
33
  The **Files** tab next to it lists the project's blob storage — the uploads
24
34
  and files your app has saved.
@@ -1,7 +1,7 @@
1
- import { t as version } from "./package-BNiOibhj.mjs";
1
+ import { t as version } from "./package-CGrKr6IT.mjs";
2
2
  import { n as siteConfig } from "./site-BUlQR117.mjs";
3
3
  import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
4
- import { n as reportErrors } from "./ui-CpdtK7mt.mjs";
4
+ import { n as reportErrors } from "./ui-2mrJcjYC.mjs";
5
5
  import { defineCommand } from "citty";
6
6
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
7
7
  import { homedir } from "node:os";
@@ -1,5 +1,5 @@
1
1
  import { a as projectDirArgs, c as resolveProject } from "./site-BUlQR117.mjs";
2
- import { c as apiRequest, n as reportErrors } from "./ui-CpdtK7mt.mjs";
2
+ import { c as apiRequest, n as reportErrors } from "./ui-2mrJcjYC.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  //#region src/egress.ts
5
5
  /**
@@ -0,0 +1,390 @@
1
+ import { t as version } from "./package-CGrKr6IT.mjs";
2
+ import { a as projectDirArgs, c as resolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-BUlQR117.mjs";
3
+ import { c as apiRequest, l as apiUpload, n as reportErrors, s as apiDownload } from "./ui-2mrJcjYC.mjs";
4
+ import { defineCommand } from "citty";
5
+ import { basename, extname, join } from "node:path";
6
+ import { readFile, writeFile } from "node:fs/promises";
7
+ //#region src/feedback.ts
8
+ /**
9
+ * canopy feedback — the ticket-lite feedback system (docs/19, docs topic
10
+ * `platform-feedback`). Two jobs:
11
+ *
12
+ * - Work a project's inbox: `list`, `show`, `comment`, `status` — investigate
13
+ * what came in (`investigate` → comment the findings → `investigated`), and
14
+ * build what a human marked `accepted`.
15
+ * - File reports: `report` sends a bug/feedback item. With no `--project` it
16
+ * lands on the **platform inbox** (canopy itself) — the right target when a
17
+ * canopy command misbehaves while you're building something else.
18
+ */
19
+ const STATUSES = [
20
+ "investigate",
21
+ "investigated",
22
+ "accepted",
23
+ "in_progress",
24
+ "resolved",
25
+ "declined"
26
+ ];
27
+ const FILTER_STATUSES = [...STATUSES, "new"];
28
+ const KINDS = [
29
+ "bug",
30
+ "feedback",
31
+ "docs"
32
+ ];
33
+ /** Content-type hint from the extension; the server types the bytes itself. */
34
+ const IMAGE_TYPES = {
35
+ ".png": "image/png",
36
+ ".jpg": "image/jpeg",
37
+ ".jpeg": "image/jpeg",
38
+ ".webp": "image/webp",
39
+ ".gif": "image/gif"
40
+ };
41
+ /**
42
+ * Every `--attach` on the command line. citty 0.2.2 has no array arg type and
43
+ * keeps only the last occurrence, so repeats are read straight from argv;
44
+ * `--attach a.png,b.png` works as well, since agents write it both ways.
45
+ */
46
+ function attachedPaths(value) {
47
+ const flag = "--attach";
48
+ const repeated = process.argv.flatMap((arg, index) => {
49
+ if (arg === flag) {
50
+ const next = process.argv[index + 1];
51
+ return next && !next.startsWith("-") ? [next] : [];
52
+ }
53
+ return arg.startsWith(`${flag}=`) ? [arg.slice(9)] : [];
54
+ });
55
+ return (repeated.length > 0 ? repeated : value === void 0 ? [] : [String(value)]).flatMap((entry) => entry.split(",")).map((path) => path.trim()).filter((path) => path !== "");
56
+ }
57
+ /** Upload each file to a report that already exists (docs/21 create-then-attach). */
58
+ async function attachFiles(slug, itemId, paths, caption, commentId) {
59
+ for (const [index, path] of paths.entries()) {
60
+ const bytes = await readFile(path);
61
+ const params = new URLSearchParams({ filename: basename(path) });
62
+ if (caption) params.set("caption", paths.length > 1 ? `${caption} (${index + 1})` : caption);
63
+ if (commentId) params.set("comment_id", commentId);
64
+ await apiUpload(`/projects/${slug}/feedback/${encodeURIComponent(itemId)}/attachments?${params}`, bytes, IMAGE_TYPES[extname(path).toLowerCase()] ?? "application/octet-stream");
65
+ console.log(`Attached ${basename(path)}.`);
66
+ }
67
+ }
68
+ function sizeLabel(bytes) {
69
+ return bytes >= 1024 * 1024 ? `${(bytes / (1024 * 1024)).toFixed(1)} MB` : `${Math.max(1, Math.round(bytes / 1024))} KB`;
70
+ }
71
+ const jsonArg = { json: {
72
+ type: "boolean",
73
+ description: "Machine-readable JSON output"
74
+ } };
75
+ function firstLine(body) {
76
+ const line = body.split("\n")[0] ?? "";
77
+ return line.length > 72 ? `${line.slice(0, 71)}…` : line;
78
+ }
79
+ function printItems(items) {
80
+ if (items.length === 0) {
81
+ console.log("No matching feedback items.");
82
+ return;
83
+ }
84
+ for (const item of items) {
85
+ const comments = item.comment_count ? ` 💬${item.comment_count}` : "";
86
+ const shots = item.attachment_count ? ` 📎${item.attachment_count}` : "";
87
+ console.log(`${item.id} ${item.status} ${item.kind} ${firstLine(item.body)}${comments}${shots}`);
88
+ }
89
+ }
90
+ const listCommand = defineCommand({
91
+ meta: {
92
+ name: "list",
93
+ description: "List the project's feedback inbox (members only)"
94
+ },
95
+ args: {
96
+ ...projectDirArgs,
97
+ ...jsonArg,
98
+ status: {
99
+ type: "string",
100
+ description: `Filter: ${FILTER_STATUSES.join(" | ")}`
101
+ },
102
+ kind: {
103
+ type: "string",
104
+ description: `Filter: ${KINDS.join(" | ")}`
105
+ }
106
+ },
107
+ run: reportErrors("feedback list", async ({ args }) => {
108
+ const { slug } = resolveProject(args);
109
+ const params = new URLSearchParams();
110
+ if (args.status) params.set("status", args.status);
111
+ if (args.kind) params.set("kind", args.kind);
112
+ const { items } = await apiRequest("GET", `/projects/${slug}/feedback${params.size > 0 ? `?${params}` : ""}`);
113
+ if (args.json) console.log(JSON.stringify(items, null, 2));
114
+ else printItems(items);
115
+ })
116
+ });
117
+ const showCommand = defineCommand({
118
+ meta: {
119
+ name: "show",
120
+ description: "Full detail of one item: body, context, comment trail"
121
+ },
122
+ args: {
123
+ id: {
124
+ type: "positional",
125
+ description: "Item id (from `canopy feedback list`)",
126
+ required: true
127
+ },
128
+ ...projectDirArgs,
129
+ ...jsonArg,
130
+ download: {
131
+ type: "string",
132
+ description: "Save the report's screenshots into this directory"
133
+ }
134
+ },
135
+ run: reportErrors("feedback show", async ({ args }) => {
136
+ const { slug } = resolveProject(args);
137
+ const detail = await apiRequest("GET", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}`);
138
+ if (args.json) {
139
+ console.log(JSON.stringify(detail, null, 2));
140
+ return;
141
+ }
142
+ const { item, comments } = detail;
143
+ console.log(`${item.kind} · ${item.status} · via ${item.source} · ${item.created_at}`);
144
+ console.log(`reported by ${item.reporter_name || item.reporter_email || "unknown"}` + (item.deployment_id ? ` on ${item.deployment_id}` : "") + (item.channel === "staging" ? " (staging)" : ""));
145
+ console.log(`\n${item.body}\n`);
146
+ if (item.context) {
147
+ console.log("context:");
148
+ try {
149
+ console.log(JSON.stringify(JSON.parse(item.context), null, 2));
150
+ } catch {
151
+ console.log(item.context);
152
+ }
153
+ console.log("");
154
+ }
155
+ const shots = detail.attachments;
156
+ if (shots.length > 0) {
157
+ console.log("screenshots:");
158
+ for (const shot of shots) {
159
+ const label = shot.caption ? ` — ${shot.caption}` : "";
160
+ console.log(` ${shot.id} ${shot.filename ?? "screenshot"} ${sizeLabel(shot.size_bytes)}${label}`);
161
+ }
162
+ if (!args.download) console.log(" (save them with --download <dir> to look at them)");
163
+ console.log("");
164
+ }
165
+ for (const entry of comments) {
166
+ const author = entry.author_name || entry.author_email || "unknown";
167
+ const move = entry.status_to ? ` [${entry.status_from} → ${entry.status_to}]` : "";
168
+ console.log(`--- ${author} · ${entry.created_at}${move}`);
169
+ if (entry.body) console.log(entry.body);
170
+ }
171
+ if (args.download && shots.length > 0) {
172
+ console.log("");
173
+ for (const shot of shots) {
174
+ const bytes = await apiDownload(`/projects/${slug}/feedback/${encodeURIComponent(item.id)}/attachments/${shot.id}`);
175
+ const name = shot.filename ? `${shot.id}-${basename(shot.filename)}` : `${shot.id}.png`;
176
+ const target = join(args.download, name);
177
+ await writeFile(target, bytes);
178
+ console.log(`Saved ${target}`);
179
+ }
180
+ }
181
+ })
182
+ });
183
+ const commentCommand = defineCommand({
184
+ meta: {
185
+ name: "comment",
186
+ description: "Append a comment to an item (e.g. debug findings)"
187
+ },
188
+ args: {
189
+ id: {
190
+ type: "positional",
191
+ description: "Item id",
192
+ required: true
193
+ },
194
+ text: {
195
+ type: "positional",
196
+ description: "Comment text",
197
+ required: true
198
+ },
199
+ ...projectDirArgs
200
+ },
201
+ run: reportErrors("feedback comment", async ({ args }) => {
202
+ const { slug } = resolveProject(args);
203
+ await apiRequest("POST", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}/comments`, { body: args.text });
204
+ console.log("Comment added.");
205
+ })
206
+ });
207
+ const statusCommand = defineCommand({
208
+ meta: {
209
+ name: "status",
210
+ description: "Move an item to a new status (members only)"
211
+ },
212
+ args: {
213
+ id: {
214
+ type: "positional",
215
+ description: "Item id",
216
+ required: true
217
+ },
218
+ to: {
219
+ type: "positional",
220
+ description: STATUSES.join(" | "),
221
+ required: true
222
+ },
223
+ comment: {
224
+ type: "string",
225
+ description: "Note recorded with the change (required for `investigated`)"
226
+ },
227
+ ...projectDirArgs
228
+ },
229
+ run: reportErrors("feedback status", async ({ args }) => {
230
+ const { slug } = resolveProject(args);
231
+ await apiRequest("POST", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}/status`, {
232
+ status: args.to,
233
+ ...args.comment ? { comment: args.comment } : {}
234
+ });
235
+ console.log(`Marked ${args.id} as ${args.to}.`);
236
+ })
237
+ });
238
+ const mineCommand = defineCommand({
239
+ meta: {
240
+ name: "mine",
241
+ description: "Your own reports across all projects"
242
+ },
243
+ args: {
244
+ ...jsonArg,
245
+ project: {
246
+ type: "string",
247
+ description: "Only reports on this project slug"
248
+ }
249
+ },
250
+ run: reportErrors("feedback mine", async ({ args }) => {
251
+ const { items } = await apiRequest("GET", `/feedback/mine${args.project ? `?project=${encodeURIComponent(args.project)}` : ""}`);
252
+ if (args.json) {
253
+ console.log(JSON.stringify(items, null, 2));
254
+ return;
255
+ }
256
+ if (items.length === 0) {
257
+ console.log("You haven't filed any reports.");
258
+ return;
259
+ }
260
+ for (const item of items) console.log(`${item.id} ${item.status} ${item.kind} [${item.project_slug}] ${firstLine(item.body)}`);
261
+ })
262
+ });
263
+ const reportCommand = defineCommand({
264
+ meta: {
265
+ name: "report",
266
+ description: "File a report. Default target is the platform inbox (canopy itself); use --project for a specific project"
267
+ },
268
+ args: {
269
+ text: {
270
+ type: "positional",
271
+ description: "What happened / what you suggest",
272
+ required: true
273
+ },
274
+ kind: {
275
+ type: "string",
276
+ description: "bug | feedback (default: bug)"
277
+ },
278
+ project: {
279
+ type: "string",
280
+ description: "Project slug to file against (default: the platform inbox)"
281
+ },
282
+ command: {
283
+ type: "string",
284
+ description: "The canopy command that failed (goes in context)"
285
+ },
286
+ error: {
287
+ type: "string",
288
+ description: "The error output you saw (goes in context)"
289
+ },
290
+ attach: {
291
+ type: "string",
292
+ description: "Screenshot to attach (PNG/JPEG/WebP/GIF); repeat for several"
293
+ },
294
+ caption: {
295
+ type: "string",
296
+ description: "One line describing the screenshot(s)"
297
+ },
298
+ status: {
299
+ type: "string",
300
+ description: `Initial status, project members only: ${STATUSES.join(" | ")}`
301
+ }
302
+ },
303
+ run: reportErrors("feedback report", async ({ args }) => {
304
+ const kind = args.kind ?? "bug";
305
+ if (kind !== "bug" && kind !== "feedback") {
306
+ console.error(`--kind must be bug or feedback (got "${kind}")`);
307
+ process.exit(1);
308
+ }
309
+ const context = {
310
+ cli_version: version,
311
+ site: siteConfig().profileId ?? siteConfig().apiUrl,
312
+ platform: `${process.platform} node ${process.versions.node}`
313
+ };
314
+ const workingOn = tryReadCanopyJson(".")?.slug;
315
+ if (workingOn) context["working_on"] = workingOn;
316
+ if (args.command) context["command"] = args.command;
317
+ if (args.error) context["error"] = args.error;
318
+ const paths = attachedPaths(args.attach);
319
+ for (const path of paths) await readFile(path);
320
+ const { item } = await apiRequest("POST", "/feedback", {
321
+ kind,
322
+ body: args.text,
323
+ ...args.project ? { project: args.project } : {},
324
+ ...args.status ? { status: args.status } : {},
325
+ context
326
+ });
327
+ console.log(`Filed ${kind} ${item.id} on ${item.project} as ${item.status}.`);
328
+ if (paths.length > 0) await attachFiles(item.project, item.id, paths, args.caption);
329
+ console.log(`Follow it with \`canopy feedback mine\` or on the dashboard's Reports page.`);
330
+ })
331
+ });
332
+ const feedbackCommand = defineCommand({
333
+ meta: {
334
+ name: "feedback",
335
+ description: "Work a project's feedback inbox, or file a report (see docs: platform-feedback)"
336
+ },
337
+ subCommands: {
338
+ list: listCommand,
339
+ show: showCommand,
340
+ comment: commentCommand,
341
+ attach: defineCommand({
342
+ meta: {
343
+ name: "attach",
344
+ description: "Attach screenshots to an existing item (e.g. proof that a fix works)"
345
+ },
346
+ args: {
347
+ id: {
348
+ type: "positional",
349
+ description: "Item id",
350
+ required: true
351
+ },
352
+ file: {
353
+ type: "positional",
354
+ description: "Image file (repeat --attach for more)",
355
+ required: true
356
+ },
357
+ attach: {
358
+ type: "string",
359
+ description: "Another image file; repeat for several"
360
+ },
361
+ caption: {
362
+ type: "string",
363
+ description: "One line describing the screenshot(s)"
364
+ },
365
+ comment: {
366
+ type: "string",
367
+ description: "Post this comment first and hang the screenshots off it"
368
+ },
369
+ ...projectDirArgs
370
+ },
371
+ run: reportErrors("feedback attach", async ({ args }) => {
372
+ const { slug } = resolveProject(args);
373
+ const paths = [args.file, ...attachedPaths(args.attach)];
374
+ for (const path of paths) await readFile(path);
375
+ let commentId;
376
+ if (args.comment) {
377
+ const { comment } = await apiRequest("POST", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}/comments`, { body: args.comment });
378
+ commentId = comment.id;
379
+ console.log("Comment added.");
380
+ }
381
+ await attachFiles(slug, args.id, paths, args.caption, commentId);
382
+ })
383
+ }),
384
+ status: statusCommand,
385
+ mine: mineCommand,
386
+ report: reportCommand
387
+ }
388
+ });
389
+ //#endregion
390
+ export { feedbackCommand };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "./package-BNiOibhj.mjs";
2
+ import { t as version } from "./package-CGrKr6IT.mjs";
3
3
  import { r as siteHeaderLine, t as setInvocationProfile } from "./site-BUlQR117.mjs";
4
4
  import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
5
5
  import { defineCommand, runMain } from "citty";
@@ -60,29 +60,29 @@ const main = defineCommand({
60
60
  description: "canopy — create and deploy vibecoded projects"
61
61
  },
62
62
  subCommands: {
63
- login: () => import("./login-C1x1l3qg.mjs").then((m) => m.loginCommand),
64
- whoami: () => import("./login-C1x1l3qg.mjs").then((m) => m.whoamiCommand),
65
- logout: () => import("./login-C1x1l3qg.mjs").then((m) => m.logoutCommand),
66
- create: () => import("./projects-C9TBoiW7.mjs").then((m) => m.createCommand),
67
- init: () => import("./projects-C9TBoiW7.mjs").then((m) => m.initCommand),
68
- projects: () => import("./projects-C9TBoiW7.mjs").then((m) => m.projectsCommand),
69
- delete: () => import("./projects-C9TBoiW7.mjs").then((m) => m.deleteCommand),
70
- dev: () => import("./dev-D6Sy15Wv.mjs").then((m) => m.devCommand),
71
- deploy: () => import("./deploy-ClioT9ZC.mjs").then((m) => m.deployCommand),
72
- promote: () => import("./deploy-ClioT9ZC.mjs").then((m) => m.promoteCommand),
73
- staging: () => import("./staging-95xeXyZN.mjs").then((m) => m.stagingCommand),
74
- rollback: () => import("./deploy-ClioT9ZC.mjs").then((m) => m.rollbackCommand),
75
- download: () => import("./deploy-ClioT9ZC.mjs").then((m) => m.downloadCommand),
76
- token: () => import("./token-BjlqT2Xj.mjs").then((m) => m.tokenCommand),
77
- egress: () => import("./egress-BkkG9Jzs.mjs").then((m) => m.egressCommand),
78
- feedback: () => import("./feedback-BZG8E8zG.mjs").then((m) => m.feedbackCommand),
79
- db: () => import("./db-D3RKaLVU.mjs").then((m) => m.dbCommand),
80
- usage: () => import("./usage-wpew0a2M.mjs").then((m) => m.usageCommand),
81
- open: () => import("./open-CLSh6oez.mjs").then((m) => m.openCommand),
82
- docs: () => import("./docs-DOiL2Sw-.mjs").then((m) => m.docsCommand),
83
- skill: () => import("./skill-DRTg-mJR.mjs").then((m) => m.skillCommand),
84
- upgrade: () => import("./upgrade-BDaM4M6Y.mjs").then((m) => m.upgradeCommand),
85
- logs: () => import("./logs-DuzM-_Rn.mjs").then((m) => m.logsCommand)
63
+ login: () => import("./login-D7dI8D9N.mjs").then((m) => m.loginCommand),
64
+ whoami: () => import("./login-D7dI8D9N.mjs").then((m) => m.whoamiCommand),
65
+ logout: () => import("./login-D7dI8D9N.mjs").then((m) => m.logoutCommand),
66
+ create: () => import("./projects-C6JQjVqg.mjs").then((m) => m.createCommand),
67
+ init: () => import("./projects-C6JQjVqg.mjs").then((m) => m.initCommand),
68
+ projects: () => import("./projects-C6JQjVqg.mjs").then((m) => m.projectsCommand),
69
+ delete: () => import("./projects-C6JQjVqg.mjs").then((m) => m.deleteCommand),
70
+ dev: () => import("./dev-BYxSHUH6.mjs").then((m) => m.devCommand),
71
+ deploy: () => import("./deploy-y75cgauK.mjs").then((m) => m.deployCommand),
72
+ promote: () => import("./deploy-y75cgauK.mjs").then((m) => m.promoteCommand),
73
+ staging: () => import("./staging-Bw1D_J8M.mjs").then((m) => m.stagingCommand),
74
+ rollback: () => import("./deploy-y75cgauK.mjs").then((m) => m.rollbackCommand),
75
+ download: () => import("./deploy-y75cgauK.mjs").then((m) => m.downloadCommand),
76
+ token: () => import("./token-D7zrIF8G.mjs").then((m) => m.tokenCommand),
77
+ egress: () => import("./egress-CN_axsAN.mjs").then((m) => m.egressCommand),
78
+ feedback: () => import("./feedback-DUuWXS_D.mjs").then((m) => m.feedbackCommand),
79
+ db: () => import("./db-8Etjmzxh.mjs").then((m) => m.dbCommand),
80
+ usage: () => import("./usage-CX1Izj_l.mjs").then((m) => m.usageCommand),
81
+ open: () => import("./open-DRPp4QJg.mjs").then((m) => m.openCommand),
82
+ docs: () => import("./docs-VmcoH1An.mjs").then((m) => m.docsCommand),
83
+ skill: () => import("./skill-CZP1AqQX.mjs").then((m) => m.skillCommand),
84
+ upgrade: () => import("./upgrade-CmhSO_r6.mjs").then((m) => m.upgradeCommand),
85
+ logs: () => import("./logs-DNiZquki.mjs").then((m) => m.logsCommand)
86
86
  }
87
87
  });
88
88
  if (process.argv[2] !== "upgrade") notifyOfUpdate(version);