@mediamonks/canopy-cli 0.7.39 → 0.7.41

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 (49) hide show
  1. package/dist/{ai-EyrXJC5z.mjs → ai-PXXMv7kO.mjs} +2 -2
  2. package/dist/{api-ArfD6-t5.mjs → api-BHzCvDcY.mjs} +12 -0
  3. package/dist/{browse-DEhnJAPT.mjs → browse-DRz1DowL.mjs} +1 -1
  4. package/dist/{db-DGrCgj-R.mjs → db-5LfBG6N7.mjs} +3 -3
  5. package/dist/{deploy-B7KItUnh.mjs → deploy-6rYq1Iq8.mjs} +3 -3
  6. package/dist/dev-D38FQ2LD.mjs +2 -0
  7. package/dist/{dev-BMWj4NDZ.mjs → dev-D_gyIeUq.mjs} +7 -7
  8. package/dist/{dev-worker-CpSB_Okh.mjs → dev-worker-D78leKBJ.mjs} +30 -2
  9. package/dist/docs/cli-dev.md +1 -1
  10. package/dist/docs/cli-feedback.md +5 -3
  11. package/dist/docs/developers-architecture.md +17 -5
  12. package/dist/docs/platform-client-access.md +8 -0
  13. package/dist/docs/platform-egress.md +69 -12
  14. package/dist/docs/porting-nextjs.md +13 -8
  15. package/dist/docs/sdk-auth.md +32 -0
  16. package/dist/docs/sdk-mcp.md +5 -4
  17. package/dist/docs/troubleshooting.md +16 -0
  18. package/dist/{docs-Bplp4sOY.mjs → docs-B9xGnLSR.mjs} +3 -3
  19. package/dist/egress-CCpOsYpS.mjs +397 -0
  20. package/dist/{feedback-DFK0TXlt.mjs → feedback-CdjtPumG.mjs} +6 -6
  21. package/dist/index.mjs +31 -31
  22. package/dist/{local-BnfAEwso.mjs → local-Dshy0NsJ.mjs} +3 -3
  23. package/dist/{login-B2FJGIrD.mjs → login-CKNFCp7c.mjs} +2 -2
  24. package/dist/{logs-aJwWWaMx.mjs → logs-D3WakFME.mjs} +3 -3
  25. package/dist/{mcp-DuT6_wei.mjs → mcp-CoVS5Mj3.mjs} +2 -2
  26. package/dist/{migrations-CG_O5Ghw.mjs → migrations-DDJ5JJqj.mjs} +1 -1
  27. package/dist/{open-COFMburt.mjs → open-BPYozPQo.mjs} +1 -1
  28. package/dist/{package-CJJpjVh_.mjs → package-SWgTmctH.mjs} +1 -1
  29. package/dist/project-template-CMgD5hnV.mjs +2 -0
  30. package/dist/{project-template-DtTfXyLI.mjs → project-template-DACdMdpn.mjs} +2 -2
  31. package/dist/{projects-B3Wi01lw.mjs → projects-naWQ89Qj.mjs} +4 -4
  32. package/dist/{repo-4x-CJwCp.mjs → repo-BCua4L-I.mjs} +2 -2
  33. package/dist/repo-Un_p617X.mjs +2 -0
  34. package/dist/{schedule-CbaDIJXr.mjs → schedule-DjHcaxyt.mjs} +2 -2
  35. package/dist/{secrets-CmY4dUHz.mjs → secrets-COwRvCev.mjs} +2 -2
  36. package/dist/{skill-C2lGGz9n.mjs → skill-DSYQA4Ls.mjs} +2 -2
  37. package/dist/{src-CxzGOEhP.mjs → src-RUf3Gfuh.mjs} +6 -2
  38. package/dist/{staging-ypR79MlD.mjs → staging-CYMTZyyM.mjs} +2 -2
  39. package/dist/templates/spa/package.json +1 -1
  40. package/dist/templates/worker/package.json +1 -1
  41. package/dist/{token-CDrHQ5Xb.mjs → token-CCpJ1ES4.mjs} +2 -2
  42. package/dist/{ui-CaHgvvkJ.mjs → ui-SBUdC9rX.mjs} +1 -1
  43. package/dist/{upgrade-Caj1RsA7.mjs → upgrade-B_tLCpc4.mjs} +2 -2
  44. package/dist/{usage-CP8rBVZi.mjs → usage-BSPnsMXd.mjs} +9 -2
  45. package/package.json +2 -2
  46. package/dist/dev-BdSq6CGD.mjs +0 -2
  47. package/dist/egress-CzxRD6Tp.mjs +0 -188
  48. package/dist/project-template-DlzMx8Oj.mjs +0 -2
  49. package/dist/repo-CmbakFVi.mjs +0 -2
@@ -1,5 +1,5 @@
1
- import { t as version } from "./package-CJJpjVh_.mjs";
2
- import { r as reportErrors } from "./ui-CaHgvvkJ.mjs";
1
+ import { t as version } from "./package-SWgTmctH.mjs";
2
+ import { r as reportErrors } from "./ui-SBUdC9rX.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
5
5
  import { homedir } from "node:os";
@@ -852,11 +852,15 @@ function realtimeRouter(config) {
852
852
  * `worker-request.cases.ts` alongside this implementation, so the two cannot
853
853
  * drift silently.
854
854
  */
855
- /** Gateway-stamped identity, for the worker's own `env.USER` — never relayed. */
855
+ /** Gateway-stamped identity, for the worker's own `env.USER` / `env.CALLER` — never relayed. */
856
856
  const IDENTITY_STAMP_HEADERS = [
857
857
  "x-canopy-user-sub",
858
858
  "x-canopy-user-email",
859
- "x-canopy-user-name"
859
+ "x-canopy-user-name",
860
+ "x-canopy-identity-kind",
861
+ "x-canopy-identity-id",
862
+ "x-canopy-identity-email",
863
+ "x-canopy-identity-name"
860
864
  ];
861
865
  /**
862
866
  * Header carrying the project's worker token as runtime-origin proof (docs/35).
@@ -1,6 +1,6 @@
1
1
  import { c as resolveProject, l as stagingUrl, o as projectFlagArgs, s as projectUrl } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
2
+ import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  import * as p from "@clack/prompts";
6
6
  //#region src/staging.ts
@@ -7,7 +7,7 @@
7
7
  "build": "tsc && vite build"
8
8
  },
9
9
  "dependencies": {
10
- "@mediamonks/canopy": "^0.0.11",
10
+ "@mediamonks/canopy": "^0.0.12",
11
11
  "react": "^19.1.0",
12
12
  "react-dom": "^19.1.0"
13
13
  },
@@ -7,7 +7,7 @@
7
7
  "build": "tsc && vite build"
8
8
  },
9
9
  "dependencies": {
10
- "@mediamonks/canopy": "^0.0.11",
10
+ "@mediamonks/canopy": "^0.0.12",
11
11
  "hono": "^4.8.0",
12
12
  "react": "^19.1.0",
13
13
  "react-dom": "^19.1.0"
@@ -1,6 +1,6 @@
1
1
  import { a as projectDirArgs, c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest } from "./api-ArfD6-t5.mjs";
3
- import { r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
2
+ import { a as apiRequest } from "./api-BHzCvDcY.mjs";
3
+ import { r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  const tokenCommand = defineCommand({
6
6
  meta: {
@@ -1,4 +1,4 @@
1
- import { t as ApiError } from "./api-ArfD6-t5.mjs";
1
+ import { t as ApiError } from "./api-BHzCvDcY.mjs";
2
2
  import { spawn } from "node:child_process";
3
3
  import { cancel, isCancel } from "@clack/prompts";
4
4
  //#region src/ui.ts
@@ -1,5 +1,5 @@
1
1
  import { t as PACKAGE_NAME } from "./version-check-CcsA1QjI.mjs";
2
- import { r as reportErrors } from "./ui-CaHgvvkJ.mjs";
2
+ import { r as reportErrors } from "./ui-SBUdC9rX.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import { spawn } from "node:child_process";
5
5
  import { fileURLToPath } from "node:url";
@@ -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-C2lGGz9n.mjs");
115
+ const { skillInstalled } = await import("./skill-DSYQA4Ls.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());
@@ -1,6 +1,6 @@
1
1
  import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
3
- import { r as reportErrors } from "./ui-CaHgvvkJ.mjs";
2
+ import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
+ import { r as reportErrors } from "./ui-SBUdC9rX.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  //#region src/usage.ts
6
6
  /**
@@ -60,6 +60,13 @@ function renderUsage(slug, usage) {
60
60
  const deploys = usage.deploys.count;
61
61
  const live = usage.deploys.latestVersion != null ? ` · v${usage.deploys.latestVersion} live` : "";
62
62
  lines.push(` ${pad("deploys")}${deploys}${live}`);
63
+ const fixed = usage.fixedIp;
64
+ if (fixed && (fixed.mode !== "off" || fixed.bytesToday > 0)) {
65
+ lines.push("");
66
+ lines.push("Fixed outbound addresses");
67
+ const gb = (n) => `${Number((n / 1e9).toFixed(n < 1e8 ? 3 : 2))} GB`;
68
+ lines.push(` ${pad("today")}${gb(fixed.bytesToday)} of ${gb(fixed.dailyQuotaBytes)}`);
69
+ }
63
70
  return lines.join("\n");
64
71
  }
65
72
  const usageCommand = defineCommand({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediamonks/canopy-cli",
3
- "version": "0.7.39",
3
+ "version": "0.7.41",
4
4
  "description": "Canopy CLI — create, develop and deploy Canopy projects.",
5
5
  "bin": {
6
6
  "canopy": "./dist/index.mjs"
@@ -22,7 +22,7 @@
22
22
  "esbuild": "^0.28.1",
23
23
  "hono": "^4.8.0",
24
24
  "vite-server": "npm:vite@^7",
25
- "@mediamonks/canopy": "0.0.11",
25
+ "@mediamonks/canopy": "0.0.12",
26
26
  "@mediamonks/canopy-data-plane": "0.0.4"
27
27
  },
28
28
  "devDependencies": {
@@ -1,2 +0,0 @@
1
- import { n as devCommand } from "./dev-BMWj4NDZ.mjs";
2
- export { devCommand };
@@ -1,188 +0,0 @@
1
- import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
4
- import { defineCommand } from "citty";
5
- import * as p from "@clack/prompts";
6
- //#region src/egress.ts
7
- /**
8
- * canopy egress — manage the project's outbound-host policy: the allowlist
9
- * (docs/16-egress-allowlist.md) and the egress mode (docs/25-egress-modes.md).
10
- * In allowlist mode, worker fetches to hosts outside the list are denied
11
- * (`egress_denied`) on targets that enforce egress control; open mode allows
12
- * any public hostname behind a built-in blocklist and an outbound rate limit.
13
- * Policy lives on the platform, not in canopy.json, so a repo file can never
14
- * widen egress silently on deploy.
15
- */
16
- function printList({ mode, hosts, denied, enforced }, slug) {
17
- if (mode === "open") console.log(`${slug} is in OPEN egress mode — the worker may fetch any public host (the allowlist below is kept but not enforced; \`canopy egress mode allowlist\` restores it).`);
18
- if (hosts.length === 0) console.log(`No extra egress hosts for ${slug} — the worker can reach platform hosts only.`);
19
- else for (const host of hosts) console.log(host);
20
- if (denied.length > 0) {
21
- console.log(`\nRecently blocked (allow with \`canopy egress allow <host>\`):`);
22
- for (const d of denied) {
23
- const why = d.reason === "rate_limited" ? "rate limited" : "blocked";
24
- console.log(` ${d.host} ×${d.count} ${why}, last ${formatDateTime(d.last_seen)}`);
25
- }
26
- }
27
- if (!enforced) console.error("(note: this site's target does not enforce egress yet — the policy is recorded for when it does)");
28
- }
29
- const listCommand = defineCommand({
30
- meta: {
31
- name: "list",
32
- description: "List the project's allowed outbound hosts"
33
- },
34
- args: { ...projectDirArgs },
35
- run: reportErrors("egress list", async ({ args }) => {
36
- const { slug } = resolveProject(args);
37
- printList(await apiRequest("GET", `/projects/${slug}/egress`), slug);
38
- })
39
- });
40
- const allowCommand = defineCommand({
41
- meta: {
42
- name: "allow",
43
- description: "Allow the worker to fetch a host (e.g. api.example.com or *.googleapis.com)"
44
- },
45
- args: {
46
- host: {
47
- type: "positional",
48
- description: "Hostname to allow",
49
- required: true
50
- },
51
- ...projectDirArgs
52
- },
53
- run: reportErrors("egress allow", async ({ args }) => {
54
- const { slug } = resolveProject(args);
55
- const current = await apiRequest("GET", `/projects/${slug}/egress`);
56
- const updated = await apiRequest("PUT", `/projects/${slug}/egress`, { hosts: [...current.hosts, args.host] });
57
- console.log(`Allowed ${args.host} for ${slug} — running workers pick it up now.\n`);
58
- printList(updated, slug);
59
- })
60
- });
61
- const removeCommand = defineCommand({
62
- meta: {
63
- name: "remove",
64
- description: "Remove a host from the allowlist"
65
- },
66
- args: {
67
- host: {
68
- type: "positional",
69
- description: "Hostname to remove",
70
- required: true
71
- },
72
- ...projectDirArgs
73
- },
74
- run: reportErrors("egress remove", async ({ args }) => {
75
- const { slug } = resolveProject(args);
76
- const current = await apiRequest("GET", `/projects/${slug}/egress`);
77
- const wanted = args.host.trim().toLowerCase();
78
- if (!current.hosts.includes(wanted)) {
79
- console.error(`${wanted} is not on the allowlist for ${slug} (see \`canopy egress list\`).`);
80
- process.exit(1);
81
- }
82
- const updated = await apiRequest("PUT", `/projects/${slug}/egress`, { hosts: current.hosts.filter((h) => h !== wanted) });
83
- console.log(`Removed ${wanted} from ${slug} — running workers stop reaching it now.\n`);
84
- printList(updated, slug);
85
- })
86
- });
87
- /** The plain-language risk summary shown before open mode is enabled (docs/25 §2). */
88
- function openModeRiskSummary(slug, clientAccess) {
89
- const lines = [
90
- `Open egress mode lets ${slug}'s worker contact ANY public host:`,
91
- "",
92
- " - If the URLs it fetches come from user input, users control where it connects.",
93
- " - Abusive outbound traffic can get the project rate-limited or paused.",
94
- " - A built-in blocklist (private IPs, localhost, cloud metadata) and an",
95
- " outbound rate limit still apply, and every visited host is logged",
96
- " (`canopy egress log`)."
97
- ];
98
- if (clientAccess) lines.push("", " ! This project has client access enabled — untrusted visitors can drive", " what the worker fetches.");
99
- lines.push("", "It's the right choice for apps that fetch arbitrary user-provided URLs", "(link collectors, feed readers, scrapers). Everything else is better served", "by the allowlist.");
100
- return lines.join("\n");
101
- }
102
- const egressCommand = defineCommand({
103
- meta: {
104
- name: "egress",
105
- description: "Manage which outbound hosts the project's worker may fetch"
106
- },
107
- subCommands: {
108
- list: listCommand,
109
- allow: allowCommand,
110
- remove: removeCommand,
111
- mode: defineCommand({
112
- meta: {
113
- name: "mode",
114
- description: "Show or switch the egress mode (allowlist | open)"
115
- },
116
- args: {
117
- mode: {
118
- type: "positional",
119
- description: "allowlist | open (omit to show)",
120
- required: false
121
- },
122
- "accept-risk": {
123
- type: "boolean",
124
- description: "Acknowledge the open-mode risk summary without an interactive prompt"
125
- },
126
- ...projectDirArgs
127
- },
128
- run: reportErrors("egress mode", async ({ args }) => {
129
- const { slug } = resolveProject(args);
130
- const current = await apiRequest("GET", `/projects/${slug}/egress`);
131
- const wanted = args.mode?.trim().toLowerCase();
132
- if (!wanted) {
133
- console.log(current.mode);
134
- return;
135
- }
136
- if (wanted !== "allowlist" && wanted !== "open") {
137
- console.error(`unknown mode "${wanted}" — expected allowlist or open.`);
138
- process.exit(1);
139
- }
140
- if (wanted === current.mode) {
141
- console.log(`${slug} is already in ${wanted} mode.`);
142
- return;
143
- }
144
- if (wanted === "open") {
145
- const access = await apiRequest("GET", `/projects/${slug}/client-password`).catch(() => ({ enabled: false }));
146
- console.log(openModeRiskSummary(slug, access.enabled));
147
- console.log("");
148
- if (!args["accept-risk"]) {
149
- requireTty("pass --accept-risk to enable open mode without confirmation.");
150
- if (unwrap(await p.text({ message: `Type the project slug ("${slug}") to enable open mode.` })).trim() !== slug) {
151
- p.cancel("Aborted — egress mode unchanged.");
152
- return;
153
- }
154
- }
155
- }
156
- const updated = await withContext(apiRequest("PUT", `/projects/${slug}/egress`, { mode: wanted }), {
157
- action: `switch ${slug} to ${wanted} egress mode`,
158
- fixes: { owner_only: "Only the project owner can switch egress to open mode — ask them to run this command or flip it in the dashboard." }
159
- });
160
- if (wanted === "open") {
161
- console.log(`Open egress mode is ON for ${slug} — running workers pick it up now.`);
162
- console.log(`Switch back anytime with \`canopy egress mode allowlist\`.`);
163
- } else {
164
- console.log(`Allowlist mode restored for ${slug} — only listed hosts are reachable.\n`);
165
- printList(updated, slug);
166
- }
167
- })
168
- }),
169
- log: defineCommand({
170
- meta: {
171
- name: "log",
172
- description: "Show the outbound traffic log — hosts the worker actually fetched"
173
- },
174
- args: { ...projectDirArgs },
175
- run: reportErrors("egress log", async ({ args }) => {
176
- const { slug } = resolveProject(args);
177
- const { visits } = await apiRequest("GET", `/projects/${slug}/egress/log`);
178
- if (visits.length === 0) {
179
- console.log(`No outbound traffic recorded for ${slug} yet.`);
180
- return;
181
- }
182
- for (const v of visits) console.log(`${v.host} ×${v.count} last ${formatDateTime(v.last_seen)}`);
183
- })
184
- })
185
- }
186
- });
187
- //#endregion
188
- export { egressCommand };
@@ -1,2 +0,0 @@
1
- import { a as templateCommand } from "./project-template-DtTfXyLI.mjs";
2
- export { templateCommand };
@@ -1,2 +0,0 @@
1
- import { a as repoCommand } from "./repo-4x-CJwCp.mjs";
2
- export { repoCommand };