@mediamonks/canopy-cli 0.7.6 → 0.7.7

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.
@@ -0,0 +1,51 @@
1
+ ---
2
+ title: canopy feedback
3
+ description: Read and triage a project's feedback inbox, or file a bug/feedback report — against a project or against Canopy itself.
4
+ ---
5
+
6
+ `canopy feedback` is the CLI side of [Feedback & bug reports](https://docs.canopy.website/platform/feedback):
7
+ agents (and humans) pull reports, append findings, move statuses, and file new
8
+ ones.
9
+
10
+ ## Usage
11
+
12
+ ```bash
13
+ canopy feedback list --status accepted
14
+ canopy feedback show <id>
15
+ canopy feedback comment <id> "root cause: the upload grant expires too early"
16
+ canopy feedback status <id> resolved --comment "fixed in v42"
17
+ canopy feedback mine
18
+ canopy feedback report "canopy deploy exits 1 after upload" --command "canopy deploy"
19
+ ```
20
+
21
+ ## Subcommands
22
+
23
+ Inbox commands (`list`, `show`, `comment`, `status`) resolve the project the
24
+ usual way — `canopy.json` in the target directory, or `--project <slug>` — and
25
+ require project membership. `list` and `show` take `--json` for
26
+ machine-readable output.
27
+
28
+ - `list [--status <s>] [--kind <k>]` — the project's inbox. Statuses:
29
+ `new`, `accepted`, `in_progress`, `resolved`, `declined`; kinds: `bug`,
30
+ `feedback`, `docs`.
31
+ - `show <id>` — full detail: body, captured context (page URL, deploy
32
+ version, console errors — or CLI context), and the comment trail.
33
+ - `comment <id> <text>` — append a comment (findings, questions, updates).
34
+ - `status <id> <new-status> [--comment <note>]` — move the ticket; the
35
+ transition is recorded on the trail.
36
+ - `mine [--project <slug>] [--json]` — your own reports across all projects,
37
+ with current statuses.
38
+ - `report <text> [--kind bug|feedback] [--project <slug>] [--command <cmd>] [--error <output>]` —
39
+ file a new report. **Without `--project` it goes to the platform inbox**
40
+ (Canopy itself) — the right target when a `canopy` command misbehaves. The
41
+ CLI attaches its version, the active site, your platform, and the project
42
+ you were working in.
43
+
44
+ ## For agents
45
+
46
+ The intended loop: a human marks items `accepted` on the dashboard; the agent
47
+ runs `list --status accepted`, reproduces using the context in `show`
48
+ (including the deploy version that served the page), comments findings, and
49
+ resolves — or declines with a comment saying why. When a `canopy` command
50
+ itself fails: rule out user error, check `canopy feedback mine` for a
51
+ duplicate, then `report` with the exact command and error output.
@@ -129,6 +129,11 @@
129
129
  "title": "Members & access",
130
130
  "summary": "Everything on Canopy sits behind Monks sign-in. Collaborators manage a project; other Monks can view the live app — unless you tighten it."
131
131
  },
132
+ {
133
+ "slug": "platform-feedback",
134
+ "title": "Feedback & bug reports",
135
+ "summary": "A built-in feedback widget on every project, a ticket-lite inbox behind it, and a CLI that lets agents work the backlog."
136
+ },
132
137
  {
133
138
  "slug": "platform-client-access",
134
139
  "title": "Client access",
@@ -241,6 +246,11 @@
241
246
  "title": "canopy usage",
242
247
  "summary": "Show a project's AI spend, storage footprint, and deploy count from the terminal."
243
248
  },
249
+ {
250
+ "slug": "cli-feedback",
251
+ "title": "canopy feedback",
252
+ "summary": "Read and triage a project's feedback inbox, or file a bug/feedback report — against a project or against Canopy itself."
253
+ },
244
254
  {
245
255
  "slug": "cli-open",
246
256
  "title": "canopy open",
@@ -0,0 +1,79 @@
1
+ ---
2
+ title: Feedback & bug reports
3
+ description: A built-in feedback widget on every project, a ticket-lite inbox behind it, and a CLI that lets agents work the backlog.
4
+ ---
5
+
6
+ Every Canopy project gets an in-app feedback channel without writing a line of
7
+ code: signed-in Monks see a small floating button on the live app, pick **Bug**
8
+ or **Feedback**, type what happened, and send. The report lands in the
9
+ project's **Feedback tab** on the dashboard, with everything an investigator
10
+ needs captured automatically — page URL, the deploy version that served it,
11
+ viewport, and recent console errors.
12
+
13
+ It is deliberately **not** a ticketing product: an item has a status, an
14
+ append-only comment trail, and nothing else. No assignees, priorities, or
15
+ boards.
16
+
17
+ ## The widget
18
+
19
+ - Appears only for **signed-in Monks**. Client-password visitors never see it —
20
+ the platform doesn't even send them the script.
21
+ - On by default for every project. Turn it off per project in
22
+ **Settings → Feedback widget**; the change applies on the next page load.
23
+ - Submissions are always identified (your company sign-in), so there's no
24
+ anonymous spam and no reporter field to fill in.
25
+ - The widget also shows your own previous reports for that project with their
26
+ current status.
27
+ - A page can reshape the chooser with meta tags — this docs site does exactly
28
+ that: `<meta name="canopy-feedback-kinds" content="docs,feedback">` swaps
29
+ "Bug" for "Docs issue", and `canopy-feedback-prompt` replaces the textarea
30
+ placeholder. Kinds are limited to `bug`, `feedback`, `docs`.
31
+
32
+ ## The inbox
33
+
34
+ Project members triage reports on the **Feedback tab**:
35
+
36
+ - Statuses: `new → accepted → in_progress → resolved`, plus `declined`.
37
+ **`accepted` means "this is valid — an agent may pick it up."**
38
+ - Every status change and comment is recorded on the item's trail.
39
+ - Reporters follow their submissions on the **Reports page** (dashboard
40
+ header) and can comment on or withdraw their own items; only project
41
+ members change statuses.
42
+
43
+ ## Working the backlog with an agent
44
+
45
+ Because the CLI exposes the inbox, "work through the accepted feedback" is a
46
+ prompt, not a process:
47
+
48
+ ```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
52
+ canopy feedback status <id> resolved --comment "fixed in v42"
53
+ ```
54
+
55
+ `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.
60
+
61
+ ## Reporting problems with Canopy itself
62
+
63
+ When a `canopy` command fails while you (or your agent) are building a
64
+ project, file it against the **platform inbox** — not your own project:
65
+
66
+ ```bash
67
+ canopy feedback report "deploy failed after upload" \
68
+ --command "canopy deploy --staging" --error "upload_grant_expired …"
69
+ ```
70
+
71
+ With no `--project`, the report goes to the platform team automatically, with
72
+ your CLI version, site, and the project you were working on attached. Before
73
+ filing: rule out user error first (`canopy docs troubleshooting`,
74
+ `vp env doctor` for toolchain issues), and skim `canopy feedback mine` to
75
+ avoid filing the same issue twice.
76
+
77
+ ## CLI reference
78
+
79
+ See [canopy feedback](https://docs.canopy.website/cli/feedback) for every subcommand and flag.
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-CPXtNnfc.mjs";
1
+ import { t as version } from "./package-BNiOibhj.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
4
  import { n as reportErrors } from "./ui-CpdtK7mt.mjs";
@@ -0,0 +1,258 @@
1
+ import { t as version } from "./package-BNiOibhj.mjs";
2
+ import { a as projectDirArgs, c as resolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-BUlQR117.mjs";
3
+ import { c as apiRequest, n as reportErrors } from "./ui-CpdtK7mt.mjs";
4
+ import { defineCommand } from "citty";
5
+ //#region src/feedback.ts
6
+ /**
7
+ * canopy feedback — the ticket-lite feedback system (docs/19, docs topic
8
+ * `platform-feedback`). Two jobs:
9
+ *
10
+ * - Work a project's inbox: `list`, `show`, `comment`, `status` — the loop an
11
+ * agent runs over items a human marked `accepted`.
12
+ * - File reports: `report` sends a bug/feedback item. With no `--project` it
13
+ * lands on the **platform inbox** (canopy itself) — the right target when a
14
+ * canopy command misbehaves while you're building something else.
15
+ */
16
+ const STATUSES = [
17
+ "new",
18
+ "accepted",
19
+ "in_progress",
20
+ "resolved",
21
+ "declined"
22
+ ];
23
+ const KINDS = [
24
+ "bug",
25
+ "feedback",
26
+ "docs"
27
+ ];
28
+ const jsonArg = { json: {
29
+ type: "boolean",
30
+ description: "Machine-readable JSON output"
31
+ } };
32
+ function firstLine(body) {
33
+ const line = body.split("\n")[0] ?? "";
34
+ return line.length > 72 ? `${line.slice(0, 71)}…` : line;
35
+ }
36
+ function printItems(items) {
37
+ if (items.length === 0) {
38
+ console.log("No matching feedback items.");
39
+ return;
40
+ }
41
+ for (const item of items) {
42
+ const comments = item.comment_count ? ` 💬${item.comment_count}` : "";
43
+ console.log(`${item.id} ${item.status} ${item.kind} ${firstLine(item.body)}${comments}`);
44
+ }
45
+ }
46
+ const feedbackCommand = defineCommand({
47
+ meta: {
48
+ name: "feedback",
49
+ description: "Work a project's feedback inbox, or file a report (see docs: platform-feedback)"
50
+ },
51
+ subCommands: {
52
+ list: defineCommand({
53
+ meta: {
54
+ name: "list",
55
+ description: "List the project's feedback inbox (members only)"
56
+ },
57
+ args: {
58
+ ...projectDirArgs,
59
+ ...jsonArg,
60
+ status: {
61
+ type: "string",
62
+ description: `Filter: ${STATUSES.join(" | ")}`
63
+ },
64
+ kind: {
65
+ type: "string",
66
+ description: `Filter: ${KINDS.join(" | ")}`
67
+ }
68
+ },
69
+ run: reportErrors("feedback list", async ({ args }) => {
70
+ const { slug } = resolveProject(args);
71
+ const params = new URLSearchParams();
72
+ if (args.status) params.set("status", args.status);
73
+ if (args.kind) params.set("kind", args.kind);
74
+ const { items } = await apiRequest("GET", `/projects/${slug}/feedback${params.size > 0 ? `?${params}` : ""}`);
75
+ if (args.json) console.log(JSON.stringify(items, null, 2));
76
+ else printItems(items);
77
+ })
78
+ }),
79
+ show: defineCommand({
80
+ meta: {
81
+ name: "show",
82
+ description: "Full detail of one item: body, context, comment trail"
83
+ },
84
+ args: {
85
+ id: {
86
+ type: "positional",
87
+ description: "Item id (from `canopy feedback list`)",
88
+ required: true
89
+ },
90
+ ...projectDirArgs,
91
+ ...jsonArg
92
+ },
93
+ run: reportErrors("feedback show", async ({ args }) => {
94
+ const { slug } = resolveProject(args);
95
+ const detail = await apiRequest("GET", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}`);
96
+ if (args.json) {
97
+ console.log(JSON.stringify(detail, null, 2));
98
+ return;
99
+ }
100
+ const { item, comments } = detail;
101
+ console.log(`${item.kind} · ${item.status} · via ${item.source} · ${item.created_at}`);
102
+ console.log(`reported by ${item.reporter_name || item.reporter_email || "unknown"}` + (item.deployment_id ? ` on ${item.deployment_id}` : "") + (item.channel === "staging" ? " (staging)" : ""));
103
+ console.log(`\n${item.body}\n`);
104
+ if (item.context) {
105
+ console.log("context:");
106
+ try {
107
+ console.log(JSON.stringify(JSON.parse(item.context), null, 2));
108
+ } catch {
109
+ console.log(item.context);
110
+ }
111
+ console.log("");
112
+ }
113
+ for (const entry of comments) {
114
+ const author = entry.author_name || entry.author_email || "unknown";
115
+ const move = entry.status_to ? ` [${entry.status_from} → ${entry.status_to}]` : "";
116
+ console.log(`--- ${author} · ${entry.created_at}${move}`);
117
+ if (entry.body) console.log(entry.body);
118
+ }
119
+ })
120
+ }),
121
+ comment: defineCommand({
122
+ meta: {
123
+ name: "comment",
124
+ description: "Append a comment to an item (e.g. debug findings)"
125
+ },
126
+ args: {
127
+ id: {
128
+ type: "positional",
129
+ description: "Item id",
130
+ required: true
131
+ },
132
+ text: {
133
+ type: "positional",
134
+ description: "Comment text",
135
+ required: true
136
+ },
137
+ ...projectDirArgs
138
+ },
139
+ run: reportErrors("feedback comment", async ({ args }) => {
140
+ const { slug } = resolveProject(args);
141
+ await apiRequest("POST", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}/comments`, { body: args.text });
142
+ console.log("Comment added.");
143
+ })
144
+ }),
145
+ status: defineCommand({
146
+ meta: {
147
+ name: "status",
148
+ description: "Move an item to a new status (members only)"
149
+ },
150
+ args: {
151
+ id: {
152
+ type: "positional",
153
+ description: "Item id",
154
+ required: true
155
+ },
156
+ to: {
157
+ type: "positional",
158
+ description: STATUSES.join(" | "),
159
+ required: true
160
+ },
161
+ comment: {
162
+ type: "string",
163
+ description: "Optional note recorded with the change"
164
+ },
165
+ ...projectDirArgs
166
+ },
167
+ run: reportErrors("feedback status", async ({ args }) => {
168
+ const { slug } = resolveProject(args);
169
+ await apiRequest("POST", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}/status`, {
170
+ status: args.to,
171
+ ...args.comment ? { comment: args.comment } : {}
172
+ });
173
+ console.log(`Marked ${args.id} as ${args.to}.`);
174
+ })
175
+ }),
176
+ mine: defineCommand({
177
+ meta: {
178
+ name: "mine",
179
+ description: "Your own reports across all projects"
180
+ },
181
+ args: {
182
+ ...jsonArg,
183
+ project: {
184
+ type: "string",
185
+ description: "Only reports on this project slug"
186
+ }
187
+ },
188
+ run: reportErrors("feedback mine", async ({ args }) => {
189
+ const { items } = await apiRequest("GET", `/feedback/mine${args.project ? `?project=${encodeURIComponent(args.project)}` : ""}`);
190
+ if (args.json) {
191
+ console.log(JSON.stringify(items, null, 2));
192
+ return;
193
+ }
194
+ if (items.length === 0) {
195
+ console.log("You haven't filed any reports.");
196
+ return;
197
+ }
198
+ for (const item of items) console.log(`${item.id} ${item.status} ${item.kind} [${item.project_slug}] ${firstLine(item.body)}`);
199
+ })
200
+ }),
201
+ report: defineCommand({
202
+ meta: {
203
+ name: "report",
204
+ description: "File a report. Default target is the platform inbox (canopy itself); use --project for a specific project"
205
+ },
206
+ args: {
207
+ text: {
208
+ type: "positional",
209
+ description: "What happened / what you suggest",
210
+ required: true
211
+ },
212
+ kind: {
213
+ type: "string",
214
+ description: "bug | feedback (default: bug)"
215
+ },
216
+ project: {
217
+ type: "string",
218
+ description: "Project slug to file against (default: the platform inbox)"
219
+ },
220
+ command: {
221
+ type: "string",
222
+ description: "The canopy command that failed (goes in context)"
223
+ },
224
+ error: {
225
+ type: "string",
226
+ description: "The error output you saw (goes in context)"
227
+ }
228
+ },
229
+ run: reportErrors("feedback report", async ({ args }) => {
230
+ const kind = args.kind ?? "bug";
231
+ if (kind !== "bug" && kind !== "feedback") {
232
+ console.error(`--kind must be bug or feedback (got "${kind}")`);
233
+ process.exit(1);
234
+ }
235
+ const context = {
236
+ cli_version: version,
237
+ site: siteConfig().profileId ?? siteConfig().apiUrl,
238
+ platform: `${process.platform} node ${process.versions.node}`
239
+ };
240
+ const workingOn = tryReadCanopyJson(".")?.slug;
241
+ if (workingOn) context["working_on"] = workingOn;
242
+ if (args.command) context["command"] = args.command;
243
+ if (args.error) context["error"] = args.error;
244
+ const { item } = await apiRequest("POST", "/feedback", {
245
+ kind,
246
+ body: args.text,
247
+ source: "cli",
248
+ ...args.project ? { project: args.project } : {},
249
+ context
250
+ });
251
+ console.log(`Filed ${kind} ${item.id} on ${item.project}.`);
252
+ console.log(`Follow it with \`canopy feedback mine\` or on the dashboard's Reports page.`);
253
+ })
254
+ })
255
+ }
256
+ });
257
+ //#endregion
258
+ export { feedbackCommand };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "./package-CPXtNnfc.mjs";
2
+ import { t as version } from "./package-BNiOibhj.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";
@@ -75,12 +75,13 @@ const main = defineCommand({
75
75
  download: () => import("./deploy-ClioT9ZC.mjs").then((m) => m.downloadCommand),
76
76
  token: () => import("./token-BjlqT2Xj.mjs").then((m) => m.tokenCommand),
77
77
  egress: () => import("./egress-BkkG9Jzs.mjs").then((m) => m.egressCommand),
78
+ feedback: () => import("./feedback-BZG8E8zG.mjs").then((m) => m.feedbackCommand),
78
79
  db: () => import("./db-D3RKaLVU.mjs").then((m) => m.dbCommand),
79
80
  usage: () => import("./usage-wpew0a2M.mjs").then((m) => m.usageCommand),
80
81
  open: () => import("./open-CLSh6oez.mjs").then((m) => m.openCommand),
81
- docs: () => import("./docs-DhAz6QWS.mjs").then((m) => m.docsCommand),
82
- skill: () => import("./skill-BCX4cUeP.mjs").then((m) => m.skillCommand),
83
- upgrade: () => import("./upgrade-jLu7jI0r.mjs").then((m) => m.upgradeCommand),
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),
84
85
  logs: () => import("./logs-DuzM-_Rn.mjs").then((m) => m.logsCommand)
85
86
  }
86
87
  });
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.7.6";
2
+ var version = "0.7.7";
3
3
  //#endregion
4
4
  export { version as t };
@@ -96,6 +96,8 @@ and never go stale.
96
96
  - Roll back: `canopy rollback`
97
97
  - Open the live site: `canopy open`
98
98
  - Manage data: `canopy db pull | snapshot | snapshots | restore | migrate`
99
+ - Work bug reports & feedback: `canopy feedback list | show | comment | status | report`
100
+ — see `canopy docs platform-feedback` for the triage and reporting loops
99
101
  - CI credentials: `canopy token create | list | revoke`
100
102
 
101
103
  ## Custom tables & migrations (devs; Drizzle recommended)
@@ -151,3 +153,8 @@ Non-interactive: `canopy create "My App" --template spa`. **When unsure between
151
153
  - **Deploys are public.** `canopy deploy` makes a version live. Confirm before
152
154
  deploying unless the user clearly asked you to.
153
155
  - Keep the CLI current: `canopy upgrade` (this also refreshes this skill).
156
+ - **A `canopy` command itself failing?** First rule out user error (re-read
157
+ `canopy docs troubleshooting` and the command's `--help`). If the platform
158
+ really misbehaved, check `canopy feedback mine` for a duplicate, then file it:
159
+ `canopy feedback report "<what happened>" --command "<the command>" --error "<output>"`
160
+ — it reaches the platform team directly (no `--project` needed).
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-CPXtNnfc.mjs";
1
+ import { t as version } from "./package-BNiOibhj.mjs";
2
2
  import { n as reportErrors } from "./ui-CpdtK7mt.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
@@ -112,7 +112,7 @@ const upgradeCommand = defineCommand({
112
112
  const spec = `${PACKAGE_NAME}@${args.version ?? "latest"}`;
113
113
  const command = pmCommand(pm);
114
114
  const cmd = installArgs(pm, spec);
115
- const { skillInstalled } = await import("./skill-BCX4cUeP.mjs");
115
+ const { skillInstalled } = await import("./skill-DRTg-mJR.mjs");
116
116
  const refreshSkill = skillInstalled(process.cwd());
117
117
  console.log(`Upgrading via ${command}: ${command} ${cmd.join(" ")}`);
118
118
  await run(command, cmd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediamonks/canopy-cli",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
4
4
  "description": "Canopy CLI — create, develop and deploy Canopy projects.",
5
5
  "bin": {
6
6
  "canopy": "./dist/index.mjs"
@@ -20,8 +20,8 @@
20
20
  "esbuild": "^0.28.1",
21
21
  "hono": "^4.8.0",
22
22
  "vite-server": "npm:vite@^7",
23
- "@mediamonks/canopy-data-plane": "0.0.2",
24
- "@mediamonks/canopy": "0.0.4"
23
+ "@mediamonks/canopy": "0.0.4",
24
+ "@mediamonks/canopy-data-plane": "0.0.2"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^26.1.1",