@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.
- package/dist/{db-D3RKaLVU.mjs → db-8Etjmzxh.mjs} +2 -2
- package/dist/{deploy-ClioT9ZC.mjs → deploy-y75cgauK.mjs} +2 -2
- package/dist/dev-BYxSHUH6.mjs +2 -0
- package/dist/{dev-BbAVZcAR.mjs → dev-DtoZrexp.mjs} +63 -8
- package/dist/dev-worker-BZgtEM9V.mjs +313 -0
- package/dist/docs/cli-dev.md +19 -0
- package/dist/docs/cli-feedback.md +38 -14
- package/dist/docs/dev.md +19 -0
- package/dist/docs/overview-how-it-works.md +2 -1
- package/dist/docs/platform-client-access.md +2 -1
- package/dist/docs/platform-feedback.md +46 -11
- package/dist/docs/platform-members-and-access.md +2 -2
- package/dist/docs/platform-project-data.md +19 -9
- package/dist/{docs-DOiL2Sw-.mjs → docs-VmcoH1An.mjs} +2 -2
- package/dist/{egress-BkkG9Jzs.mjs → egress-CN_axsAN.mjs} +1 -1
- package/dist/feedback-DUuWXS_D.mjs +390 -0
- package/dist/index.mjs +24 -24
- package/dist/{local-BHxSZwUT.mjs → local-C5AhA4GM.mjs} +160 -6
- package/dist/{login-C1x1l3qg.mjs → login-D7dI8D9N.mjs} +28 -12
- package/dist/{logs-DuzM-_Rn.mjs → logs-DNiZquki.mjs} +1 -1
- package/dist/{migrations-DpWHuqkQ.mjs → migrations-C-5x6SYf.mjs} +1 -1
- package/dist/{open-CLSh6oez.mjs → open-DRPp4QJg.mjs} +1 -1
- package/dist/{package-BNiOibhj.mjs → package-CGrKr6IT.mjs} +1 -1
- package/dist/{projects-C9TBoiW7.mjs → projects-C6JQjVqg.mjs} +1 -1
- package/dist/{skill-DRTg-mJR.mjs → skill-CZP1AqQX.mjs} +2 -2
- package/dist/{staging-95xeXyZN.mjs → staging-Bw1D_J8M.mjs} +1 -1
- package/dist/templates/worker/.dev.vars.example +9 -0
- package/dist/templates/worker/AGENTS.md +7 -1
- package/dist/templates/worker/README.md +4 -1
- package/dist/templates/worker/_gitignore +1 -0
- package/dist/{token-BjlqT2Xj.mjs → token-D7zrIF8G.mjs} +1 -1
- package/dist/{ui-CpdtK7mt.mjs → ui-2mrJcjYC.mjs} +30 -1
- package/dist/{upgrade-BDaM4M6Y.mjs → upgrade-CmhSO_r6.mjs} +2 -2
- package/dist/{usage-wpew0a2M.mjs → usage-CX1Izj_l.mjs} +1 -1
- package/package.json +1 -1
- package/dist/dev-D6Sy15Wv.mjs +0 -2
- 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: `
|
|
37
|
-
|
|
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, "
|
|
46
|
-
|
|
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
|
|
50
|
-
canopy feedback show <id>
|
|
51
|
-
canopy feedback
|
|
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.
|
|
57
|
-
|
|
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 →
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
64
|
-
whoami: () => import("./login-
|
|
65
|
-
logout: () => import("./login-
|
|
66
|
-
create: () => import("./projects-
|
|
67
|
-
init: () => import("./projects-
|
|
68
|
-
projects: () => import("./projects-
|
|
69
|
-
delete: () => import("./projects-
|
|
70
|
-
dev: () => import("./dev-
|
|
71
|
-
deploy: () => import("./deploy-
|
|
72
|
-
promote: () => import("./deploy-
|
|
73
|
-
staging: () => import("./staging-
|
|
74
|
-
rollback: () => import("./deploy-
|
|
75
|
-
download: () => import("./deploy-
|
|
76
|
-
token: () => import("./token-
|
|
77
|
-
egress: () => import("./egress-
|
|
78
|
-
feedback: () => import("./feedback-
|
|
79
|
-
db: () => import("./db-
|
|
80
|
-
usage: () => import("./usage-
|
|
81
|
-
open: () => import("./open-
|
|
82
|
-
docs: () => import("./docs-
|
|
83
|
-
skill: () => import("./skill-
|
|
84
|
-
upgrade: () => import("./upgrade-
|
|
85
|
-
logs: () => import("./logs-
|
|
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);
|