@mediamonks/canopy-cli 0.7.26 → 0.7.28
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/docs/cli-feedback.md +9 -6
- package/dist/docs/cli-index.md +1 -1
- package/dist/docs/getting-started.md +1 -1
- package/dist/docs/platform-egress.md +25 -0
- package/dist/{docs-x3hMtUzT.mjs → docs-DMFMPnxG.mjs} +1 -1
- package/dist/{feedback-C9m3dNkJ.mjs → feedback-OMAkwHpR.mjs} +38 -18
- package/dist/index.mjs +5 -5
- package/dist/{package-CBso2611.mjs → package-BL6hRqtr.mjs} +1 -1
- package/dist/skill/canopy/SKILL.md +1 -1
- package/dist/{skill-DmVlZXgv.mjs → skill-C83Za5BU.mjs} +1 -1
- package/dist/templates/spa/.github/workflows/canopy-deploy.yml +3 -3
- package/dist/templates/worker/.github/workflows/canopy-deploy.yml +3 -3
- package/dist/{upgrade-C7u8pxeV.mjs → upgrade-BjQguwG6.mjs} +1 -1
- package/package.json +1 -1
|
@@ -24,12 +24,15 @@ canopy feedback attach <id> ./after-fix.png --comment "fixed — see the shot"
|
|
|
24
24
|
|
|
25
25
|
## Subcommands
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
Every report has a key made of its project and a number, like
|
|
28
|
+
`my-project-17`. Inbox commands resolve the project the usual way —
|
|
29
|
+
`canopy.json` in the target directory, or `--project <slug>` — and require
|
|
30
|
+
project membership; inside the project you can refer to a report by its
|
|
31
|
+
number alone (`canopy feedback show 17`). The item-scoped commands (`show`,
|
|
32
|
+
`comment`, `status`, `attach`) also take the full key from anywhere, with no
|
|
33
|
+
project context: the key names its project. Older ids from before keys
|
|
34
|
+
existed still work wherever a key does. `list` and `show` take `--json` for
|
|
35
|
+
machine-readable output.
|
|
33
36
|
|
|
34
37
|
- `list [--status <s>] [--kind <k>]` — the project's inbox. Statuses:
|
|
35
38
|
`investigate`, `investigated`, `accepted`, `in_progress`, `resolved`,
|
package/dist/docs/cli-index.md
CHANGED
|
@@ -7,7 +7,7 @@ Everything on Canopy is driven by one command-line tool: `canopy`. It scaffolds
|
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
|
-
The CLI is published on npm as `@mediamonks/canopy-cli`. You need Node.js
|
|
10
|
+
The CLI is published on npm as `@mediamonks/canopy-cli`. You need Node.js 24 or newer; then install it globally:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
npm install -g @mediamonks/canopy-cli
|
|
@@ -24,7 +24,7 @@ Confirm it's on your PATH:
|
|
|
24
24
|
canopy --version
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
If `npm` isn't available, install Node.js
|
|
27
|
+
If `npm` isn't available, install Node.js 24+ first (https://nodejs.org), then
|
|
28
28
|
re-run the install.
|
|
29
29
|
|
|
30
30
|
## 2. Sign in
|
|
@@ -112,6 +112,31 @@ Switching back is one command (`canopy egress mode allowlist`), and the
|
|
|
112
112
|
dashboard offers to **seed the allowlist from the observed hosts** so
|
|
113
113
|
tightening up later doesn't break the app.
|
|
114
114
|
|
|
115
|
+
## Allowlisting Canopy on the other side
|
|
116
|
+
|
|
117
|
+
Some external services only accept calls from IP addresses you register with
|
|
118
|
+
them first — a partner API, a corporate database, a payment provider's
|
|
119
|
+
allowlist. Projects hosted on **Google Cloud** (the `gcp-pilot` site,
|
|
120
|
+
`canopy.monks.zone`) make every outbound request from a fixed pair of
|
|
121
|
+
addresses, so you can hand them over:
|
|
122
|
+
|
|
123
|
+
| Address |
|
|
124
|
+
| ---------------- |
|
|
125
|
+
| `34.187.107.103` |
|
|
126
|
+
| `34.158.168.43` |
|
|
127
|
+
|
|
128
|
+
Register **both**. Traffic may leave from either one, and the second gives the
|
|
129
|
+
platform room to grow without asking you to update your allowlist. These
|
|
130
|
+
addresses are shared by every project on that site, so registering them lets
|
|
131
|
+
any Canopy project on Google Cloud reach the service — the other service's own
|
|
132
|
+
authentication (API keys, tokens) still decides who gets in.
|
|
133
|
+
|
|
134
|
+
The addresses are a platform commitment: they will not change without notice
|
|
135
|
+
and a migration period. Projects hosted on Cloudflare have **no** fixed
|
|
136
|
+
outbound address (requests leave from Cloudflare's shared edge), so if a
|
|
137
|
+
service you depend on insists on IP allowlisting, run that project on the
|
|
138
|
+
Google Cloud site.
|
|
139
|
+
|
|
115
140
|
## The outbound traffic log
|
|
116
141
|
|
|
117
142
|
In both modes, the platform keeps a per-project log of the hosts the worker
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-BL6hRqtr.mjs";
|
|
2
2
|
import { n as siteConfig } from "./site-DM2J10CB.mjs";
|
|
3
3
|
import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
|
|
4
4
|
import { r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-BL6hRqtr.mjs";
|
|
2
2
|
import { a as projectDirArgs, c as resolveProject, d as tryResolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
3
3
|
import { c as apiDownload, l as apiRequest, r as reportErrors, t as formatDateTime, u as apiUpload } from "./ui-DR-siu2S.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
@@ -72,19 +72,38 @@ const jsonArg = { json: {
|
|
|
72
72
|
type: "boolean",
|
|
73
73
|
description: "Machine-readable JSON output"
|
|
74
74
|
} };
|
|
75
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
76
|
+
const KEY_RE = /^([a-z][a-z0-9-]*)-(\d+)$/;
|
|
75
77
|
/**
|
|
76
|
-
* The project
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* an id from a notification or a Slack thread, in a directory that isn't the
|
|
80
|
-
* project.
|
|
78
|
+
* The project slug a full key names (`kitchen-sink-17` → `kitchen-sink`), or
|
|
79
|
+
* null for a UUID or a bare number. UUIDs are checked first: one whose last
|
|
80
|
+
* group is all digits would otherwise look like a key.
|
|
81
81
|
*/
|
|
82
|
-
|
|
82
|
+
function slugOfKey(ref) {
|
|
83
|
+
if (UUID_RE.test(ref)) return null;
|
|
84
|
+
return KEY_RE.exec(ref.toLowerCase())?.[1] ?? null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The project an item-scoped command targets. A full key names its own
|
|
88
|
+
* project and wins over the directory — `canopy feedback show other-3` from
|
|
89
|
+
* inside `sink` means other's report, not a miss on sink's. Otherwise
|
|
90
|
+
* `--project` or canopy.json; and failing those, the API's project-less
|
|
91
|
+
* lookup for a UUID from an old link. A bare number is only meaningful with a
|
|
92
|
+
* project in hand.
|
|
93
|
+
*/
|
|
94
|
+
async function resolveItemSlug(args, ref) {
|
|
95
|
+
const fromKey = slugOfKey(ref);
|
|
96
|
+
if (fromKey) return fromKey;
|
|
83
97
|
const { slug } = tryResolveProject(args);
|
|
84
98
|
if (slug) return slug;
|
|
85
|
-
|
|
99
|
+
if (/^\d+$/.test(ref)) throw new Error(`"${ref}" is a bare report number — run this inside the project, pass --project, or use the full key (e.g. my-project-${ref}).`);
|
|
100
|
+
const { project } = await apiRequest("GET", `/feedback/${encodeURIComponent(ref)}`);
|
|
86
101
|
return project.slug;
|
|
87
102
|
}
|
|
103
|
+
/** What a report is called in output: its key, or the UUID on pre-key rows. */
|
|
104
|
+
function refOf(item) {
|
|
105
|
+
return item.key ?? item.id;
|
|
106
|
+
}
|
|
88
107
|
function firstLine(body) {
|
|
89
108
|
const line = body.split("\n")[0] ?? "";
|
|
90
109
|
return line.length > 72 ? `${line.slice(0, 71)}…` : line;
|
|
@@ -101,7 +120,7 @@ function printItems(items) {
|
|
|
101
120
|
for (const item of items) {
|
|
102
121
|
const comments = item.comment_count ? ` 💬${item.comment_count}` : "";
|
|
103
122
|
const shots = item.attachment_count ? ` 📎${item.attachment_count}` : "";
|
|
104
|
-
console.log(`${item
|
|
123
|
+
console.log(`${refOf(item)} ${item.status} ${item.kind} ${itemLabel(item)}${comments}${shots}`);
|
|
105
124
|
}
|
|
106
125
|
}
|
|
107
126
|
const listCommand = defineCommand({
|
|
@@ -139,7 +158,7 @@ const showCommand = defineCommand({
|
|
|
139
158
|
args: {
|
|
140
159
|
id: {
|
|
141
160
|
type: "positional",
|
|
142
|
-
description: "
|
|
161
|
+
description: "Report key like my-project-17 (from `canopy feedback list`), or its number inside the project",
|
|
143
162
|
required: true
|
|
144
163
|
},
|
|
145
164
|
...projectDirArgs,
|
|
@@ -150,7 +169,8 @@ const showCommand = defineCommand({
|
|
|
150
169
|
}
|
|
151
170
|
},
|
|
152
171
|
run: reportErrors("feedback show", async ({ args }) => {
|
|
153
|
-
const
|
|
172
|
+
const slug = slugOfKey(args.id) ?? tryResolveProject(args).slug;
|
|
173
|
+
if (!slug && /^\d+$/.test(args.id)) throw new Error(`"${args.id}" is a bare report number — run this inside the project, pass --project, or use the full key (e.g. my-project-${args.id}).`);
|
|
154
174
|
const detail = await apiRequest("GET", slug ? `/projects/${slug}/feedback/${encodeURIComponent(args.id)}` : `/feedback/${encodeURIComponent(args.id)}`);
|
|
155
175
|
if (args.json) {
|
|
156
176
|
console.log(JSON.stringify(detail, null, 2));
|
|
@@ -158,7 +178,7 @@ const showCommand = defineCommand({
|
|
|
158
178
|
}
|
|
159
179
|
const { item, comments } = detail;
|
|
160
180
|
if (item.title) console.log(item.title);
|
|
161
|
-
console.log(`${item.kind} · ${item.status} · via ${item.source} · ${formatDateTime(item.created_at)}`);
|
|
181
|
+
console.log(`${refOf(item)} · ${item.kind} · ${item.status} · via ${item.source} · ${formatDateTime(item.created_at)}`);
|
|
162
182
|
console.log(`reported by ${item.reporter_name || item.reporter_email || "unknown"}` + (item.deployment_id ? ` on ${item.deployment_id}` : "") + (item.channel === "staging" ? " (staging)" : ""));
|
|
163
183
|
console.log(`\n${item.body}\n`);
|
|
164
184
|
if (item.context) {
|
|
@@ -207,7 +227,7 @@ const commentCommand = defineCommand({
|
|
|
207
227
|
args: {
|
|
208
228
|
id: {
|
|
209
229
|
type: "positional",
|
|
210
|
-
description: "
|
|
230
|
+
description: "Report key (my-project-17), or its number inside the project",
|
|
211
231
|
required: true
|
|
212
232
|
},
|
|
213
233
|
text: {
|
|
@@ -237,7 +257,7 @@ const statusCommand = defineCommand({
|
|
|
237
257
|
args: {
|
|
238
258
|
id: {
|
|
239
259
|
type: "positional",
|
|
240
|
-
description: "
|
|
260
|
+
description: "Report key (my-project-17), or its number inside the project",
|
|
241
261
|
required: true
|
|
242
262
|
},
|
|
243
263
|
to: {
|
|
@@ -281,7 +301,7 @@ const mineCommand = defineCommand({
|
|
|
281
301
|
console.log("You haven't filed any reports.");
|
|
282
302
|
return;
|
|
283
303
|
}
|
|
284
|
-
for (const item of items) console.log(`${item
|
|
304
|
+
for (const item of items) console.log(`${refOf(item)} ${item.status} ${item.kind} [${item.project_slug}] ${itemLabel(item)}`);
|
|
285
305
|
})
|
|
286
306
|
});
|
|
287
307
|
const reportCommand = defineCommand({
|
|
@@ -348,8 +368,8 @@ const reportCommand = defineCommand({
|
|
|
348
368
|
...args.status ? { status: args.status } : {},
|
|
349
369
|
context
|
|
350
370
|
});
|
|
351
|
-
console.log(`Filed ${kind} ${item.
|
|
352
|
-
if (paths.length > 0) await attachFiles(item.project, item.
|
|
371
|
+
console.log(`Filed ${kind} ${item.key} on ${item.project} as ${item.status}.`);
|
|
372
|
+
if (paths.length > 0) await attachFiles(item.project, item.key, paths, args.caption);
|
|
353
373
|
console.log(`Follow it with \`canopy feedback mine\` or on the dashboard's Reports page.`);
|
|
354
374
|
})
|
|
355
375
|
});
|
|
@@ -370,7 +390,7 @@ const feedbackCommand = defineCommand({
|
|
|
370
390
|
args: {
|
|
371
391
|
id: {
|
|
372
392
|
type: "positional",
|
|
373
|
-
description: "
|
|
393
|
+
description: "Report key (my-project-17), or its number inside the project",
|
|
374
394
|
required: true
|
|
375
395
|
},
|
|
376
396
|
file: {
|
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-BL6hRqtr.mjs";
|
|
3
3
|
import { r as siteHeaderLine, t as setInvocationProfile } from "./site-DM2J10CB.mjs";
|
|
4
4
|
import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
@@ -77,13 +77,13 @@ const main = defineCommand({
|
|
|
77
77
|
egress: () => import("./egress-Cn-P46O3.mjs").then((m) => m.egressCommand),
|
|
78
78
|
secrets: () => import("./secrets-BD3DaTih.mjs").then((m) => m.secretsCommand),
|
|
79
79
|
schedule: () => import("./schedule-Buu71RWQ.mjs").then((m) => m.scheduleCommand),
|
|
80
|
-
feedback: () => import("./feedback-
|
|
80
|
+
feedback: () => import("./feedback-OMAkwHpR.mjs").then((m) => m.feedbackCommand),
|
|
81
81
|
db: () => import("./db-DVMwCLlU.mjs").then((m) => m.dbCommand),
|
|
82
82
|
usage: () => import("./usage-81JSiWIx.mjs").then((m) => m.usageCommand),
|
|
83
83
|
open: () => import("./open-rS1ARzOy.mjs").then((m) => m.openCommand),
|
|
84
|
-
docs: () => import("./docs-
|
|
85
|
-
skill: () => import("./skill-
|
|
86
|
-
upgrade: () => import("./upgrade-
|
|
84
|
+
docs: () => import("./docs-DMFMPnxG.mjs").then((m) => m.docsCommand),
|
|
85
|
+
skill: () => import("./skill-C83Za5BU.mjs").then((m) => m.skillCommand),
|
|
86
|
+
upgrade: () => import("./upgrade-BjQguwG6.mjs").then((m) => m.upgradeCommand),
|
|
87
87
|
logs: () => import("./logs-CfDINw7U.mjs").then((m) => m.logsCommand)
|
|
88
88
|
}
|
|
89
89
|
});
|
|
@@ -30,7 +30,7 @@ canopy whoami # is there a valid login?
|
|
|
30
30
|
npm install -g @mediamonks/canopy-cli
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
(Needs Node.js
|
|
33
|
+
(Needs Node.js 24+. If `npm` is missing, tell the user to install Node first.)
|
|
34
34
|
|
|
35
35
|
- **`canopy whoami` says not logged in / expired** → run:
|
|
36
36
|
```bash
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-BL6hRqtr.mjs";
|
|
2
2
|
import { r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
@@ -18,10 +18,10 @@ jobs:
|
|
|
18
18
|
env:
|
|
19
19
|
CANOPY_DEPLOY_TOKEN: ${{ secrets.CANOPY_DEPLOY_TOKEN }}
|
|
20
20
|
steps:
|
|
21
|
-
- uses: actions/checkout@
|
|
22
|
-
- uses: actions/setup-node@
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
- uses: actions/setup-node@v7
|
|
23
23
|
with:
|
|
24
|
-
node-version:
|
|
24
|
+
node-version: 24
|
|
25
25
|
- name: Install dependencies
|
|
26
26
|
if: env.CANOPY_DEPLOY_TOKEN != ''
|
|
27
27
|
run: |
|
|
@@ -18,10 +18,10 @@ jobs:
|
|
|
18
18
|
env:
|
|
19
19
|
CANOPY_DEPLOY_TOKEN: ${{ secrets.CANOPY_DEPLOY_TOKEN }}
|
|
20
20
|
steps:
|
|
21
|
-
- uses: actions/checkout@
|
|
22
|
-
- uses: actions/setup-node@
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
- uses: actions/setup-node@v7
|
|
23
23
|
with:
|
|
24
|
-
node-version:
|
|
24
|
+
node-version: 24
|
|
25
25
|
- name: Install dependencies
|
|
26
26
|
if: env.CANOPY_DEPLOY_TOKEN != ''
|
|
27
27
|
run: |
|
|
@@ -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-
|
|
115
|
+
const { skillInstalled } = await import("./skill-C83Za5BU.mjs");
|
|
116
116
|
const { homedir } = await import("node:os");
|
|
117
117
|
const refreshGlobal = skillInstalled(homedir());
|
|
118
118
|
const refreshRepo = process.cwd() !== homedir() && skillInstalled(process.cwd());
|