@mediamonks/canopy-cli 0.7.3 → 0.7.5

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/sdk.md CHANGED
@@ -38,10 +38,11 @@ const canopy = createCanopy();
38
38
  // canopy.auth, canopy.db, canopy.blob, canopy.ai, canopy.services
39
39
  ```
40
40
 
41
- | Option | Type | Default | What it does |
42
- | --------- | -------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
43
- | `baseUrl` | `string` | `"/_api"` | Base URL of the platform data API. The default is same-origin, so it works untouched in the browser. |
44
- | `fetch` | `typeof fetch` | global `fetch` | Override how requests are sent — a backend worker passes its platform service binding here. |
41
+ | Option | Type | Default | What it does |
42
+ | --------- | -------------- | -------------- | --------------------------------------------------------------------------------------------------------------------- |
43
+ | `baseUrl` | `string` | `"/_api"` | Base URL of the platform data API. The default is same-origin, so it works untouched in the browser. |
44
+ | `token` | `string` | none | A project-scoped `cnpy_` token sent as a bearer on every request — for scripts and CI running outside Canopy hosting. |
45
+ | `fetch` | `typeof fetch` | global `fetch` | Override how requests are sent — a backend worker passes its platform service binding here. |
45
46
 
46
47
  In the browser no config is needed. In a backend worker (the `worker`
47
48
  template), the platform is reachable through the `CANOPY` service binding, so
@@ -55,6 +56,18 @@ app.get("/api/hello", async (c) => {
55
56
  });
56
57
  ```
57
58
 
59
+ Outside Canopy hosting — a local Node script, a CI job — there is no session
60
+ and no service binding. Mint a dev token (`canopy dev` does this for you, or
61
+ `POST /control/v1/projects/:slug/dev-token`) and pass it with the platform's
62
+ API host:
63
+
64
+ ```ts
65
+ const canopy = createCanopy({
66
+ baseUrl: "https://api.canopy.website",
67
+ token: process.env.CANOPY_TOKEN, // a project-scoped cnpy_ token
68
+ });
69
+ ```
70
+
58
71
  ## One instance, shared
59
72
 
60
73
  In the browser, create the client once and import it everywhere. The scaffolds
@@ -77,6 +77,19 @@ or use dashboard Settings → Outbound hosts. Details and entry rules:
77
77
  target outbound requests are currently unrestricted, so this error only
78
78
  appears on GCP.
79
79
 
80
+ ## The worker returns errors and you can't see why
81
+
82
+ Read its console output instead of adding diagnostic endpoints:
83
+
84
+ ```bash
85
+ canopy logs --since 15m
86
+ ```
87
+
88
+ `console.log` / `console.error` from your deployed worker, straight in the
89
+ terminal; `--follow` tails it live while you reproduce the problem. See
90
+ [canopy logs](https://docs.canopy.website/cli/logs). (On sites without worker log capture the command
91
+ answers `not_supported_on_target`.)
92
+
80
93
  ## Docs are stale
81
94
 
82
95
  `canopy docs` caches the platform's docs. Force a refresh:
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-BjMP21nL.mjs";
1
+ import { t as version } from "./package-yVoO2pq4.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";
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "./package-BjMP21nL.mjs";
2
+ import { t as version } from "./package-yVoO2pq4.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";
@@ -15,7 +15,7 @@ import { defineCommand, runMain } from "citty";
15
15
  * deploy build & deploy assets (+ worker) [phase 1/2]
16
16
  * token create|list|revoke deploy tokens for CI [phase 2]
17
17
  * db pull|snapshot|restore [phase 2]
18
- * logs tail the project's worker logs [phase 2]
18
+ * logs [--follow] read the project's worker logs [phase 2]
19
19
  * usage AI spend + storage for a project [phase 1]
20
20
  * open open the deployed project / dashboard [phase 1]
21
21
  */
@@ -53,19 +53,6 @@ function takeGlobalProfile(argv) {
53
53
  const profileFlag = takeGlobalProfile(process.argv);
54
54
  if (profileFlag) setInvocationProfile(profileFlag);
55
55
  else if (process.env["CANOPY_PROFILE"]) setInvocationProfile(process.env["CANOPY_PROFILE"]);
56
- /** A roadmap command that has not landed yet (docs/04-roadmap.md). */
57
- function planned(name, description, phase) {
58
- return defineCommand({
59
- meta: {
60
- name,
61
- description: `${description} (not implemented yet — Phase ${phase})`
62
- },
63
- run() {
64
- console.error(`canopy ${name}: not implemented yet — lands in Phase ${phase} (docs/04-roadmap.md).`);
65
- process.exit(1);
66
- }
67
- });
68
- }
69
56
  const main = defineCommand({
70
57
  meta: {
71
58
  name: "canopy",
@@ -91,10 +78,10 @@ const main = defineCommand({
91
78
  db: () => import("./db-D3RKaLVU.mjs").then((m) => m.dbCommand),
92
79
  usage: () => import("./usage-wpew0a2M.mjs").then((m) => m.usageCommand),
93
80
  open: () => import("./open-CLSh6oez.mjs").then((m) => m.openCommand),
94
- docs: () => import("./docs-BwtpnE8W.mjs").then((m) => m.docsCommand),
95
- skill: () => import("./skill-aTr-RQil.mjs").then((m) => m.skillCommand),
96
- upgrade: () => import("./upgrade-Dl6DiMhL.mjs").then((m) => m.upgradeCommand),
97
- logs: planned("logs", "Tail worker logs", "2")
81
+ docs: () => import("./docs-DQmUReJR.mjs").then((m) => m.docsCommand),
82
+ skill: () => import("./skill-12Ga-2UT.mjs").then((m) => m.skillCommand),
83
+ upgrade: () => import("./upgrade-Du6U0xjp.mjs").then((m) => m.upgradeCommand),
84
+ logs: () => import("./logs-DBLDS3aQ.mjs").then((m) => m.logsCommand)
98
85
  }
99
86
  });
100
87
  if (process.argv[2] !== "upgrade") notifyOfUpdate(version);
@@ -0,0 +1,109 @@
1
+ import { c as resolveProject, o as projectFlagArgs } from "./site-BUlQR117.mjs";
2
+ import { c as apiRequest, l as withContext, n as reportErrors } from "./ui-CpdtK7mt.mjs";
3
+ import { defineCommand } from "citty";
4
+ //#region src/logs.ts
5
+ /**
6
+ * canopy logs — read the project worker's stdout/stderr
7
+ * (GET /v1/projects/:slug/logs, docs/17-worker-logs.md). `--follow` is a
8
+ * polling loop on since=<last ts>; output is stdout-clean for piping.
9
+ */
10
+ const FOLLOW_INTERVAL_MS = 4e3;
11
+ /** `--since` accepts a duration (30s, 15m, 2h, 1d) or an RFC 3339 timestamp. */
12
+ function parseSince(value, now = Date.now()) {
13
+ const duration = /^(\d+)([smhd])$/.exec(value.trim());
14
+ if (duration) {
15
+ const unitMs = {
16
+ s: 1e3,
17
+ m: 6e4,
18
+ h: 36e5,
19
+ d: 864e5
20
+ }[duration[2]];
21
+ return new Date(now - Number(duration[1]) * unitMs).toISOString();
22
+ }
23
+ const ms = Date.parse(value);
24
+ if (Number.isNaN(ms)) throw new Error(`--since must be a duration (30s, 15m, 2h, 1d) or a timestamp, not ${JSON.stringify(value)}.`);
25
+ return new Date(ms).toISOString();
26
+ }
27
+ /** `SEVERITY ts text` — severity padded so lines column-align. */
28
+ function formatEntry(entry) {
29
+ return `${entry.severity.padEnd(8)} ${entry.ts} ${entry.text}`;
30
+ }
31
+ async function fetchLogs(slug, params) {
32
+ const query = new URLSearchParams({ channel: params.channel });
33
+ if (params.since) query.set("since", params.since);
34
+ if (params.limit) query.set("limit", String(params.limit));
35
+ return withContext(apiRequest("GET", `/projects/${slug}/logs?${query}`), {
36
+ action: `read worker logs for "${slug}"`,
37
+ fixes: { not_supported_on_target: "Worker logs aren't available on this site yet — GCP sites only." }
38
+ });
39
+ }
40
+ const logsCommand = defineCommand({
41
+ meta: {
42
+ name: "logs",
43
+ description: "Show the project worker's logs (stdout/stderr)"
44
+ },
45
+ args: {
46
+ ...projectFlagArgs,
47
+ staging: {
48
+ type: "boolean",
49
+ default: false,
50
+ description: "Read the staging channel's worker instead of live"
51
+ },
52
+ since: {
53
+ type: "string",
54
+ description: "How far back to read: a duration (30s, 15m, 2h, 1d) or a timestamp",
55
+ default: "1h"
56
+ },
57
+ limit: {
58
+ type: "string",
59
+ description: "Max entries to print (1–1000)",
60
+ default: "100"
61
+ },
62
+ follow: {
63
+ type: "boolean",
64
+ alias: "f",
65
+ default: false,
66
+ description: "Keep polling for new entries (Ctrl-C to stop)"
67
+ },
68
+ json: {
69
+ type: "boolean",
70
+ default: false,
71
+ description: "Print entries as JSON lines instead of formatted text"
72
+ }
73
+ },
74
+ run: reportErrors("logs", async ({ args }) => {
75
+ const { slug } = resolveProject(args);
76
+ const channel = args.staging ? "staging" : "live";
77
+ const limit = Number(args.limit);
78
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) throw new Error("--limit must be an integer between 1 and 1000.");
79
+ let since = parseSince(args.since);
80
+ const print = (entries) => {
81
+ for (const entry of entries) console.log(args.json ? JSON.stringify(entry) : formatEntry(entry));
82
+ };
83
+ const initial = [...(await fetchLogs(slug, {
84
+ channel,
85
+ since,
86
+ limit
87
+ })).entries].reverse();
88
+ print(initial);
89
+ if (!args.follow) {
90
+ if (initial.length === 0) console.error(`No log entries since ${since} — is the worker getting traffic?`);
91
+ return;
92
+ }
93
+ console.error(`Following ${slug}${channel === "staging" ? " [staging]" : ""}… (Ctrl-C to stop)`);
94
+ let lastTs = initial.at(-1)?.ts ?? since;
95
+ for (;;) {
96
+ await new Promise((resolve) => setTimeout(resolve, FOLLOW_INTERVAL_MS));
97
+ const fresh = [...(await fetchLogs(slug, {
98
+ channel,
99
+ since: lastTs,
100
+ limit: 1e3
101
+ })).entries].reverse().filter((e) => e.ts > lastTs);
102
+ if (fresh.length === 0) continue;
103
+ print(fresh);
104
+ lastTs = fresh.at(-1).ts;
105
+ }
106
+ })
107
+ });
108
+ //#endregion
109
+ export { logsCommand };
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.7.3";
2
+ var version = "0.7.5";
3
3
  //#endregion
4
4
  export { version as t };
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-BjMP21nL.mjs";
1
+ import { t as version } from "./package-yVoO2pq4.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";
@@ -7,7 +7,7 @@
7
7
  "build": "tsc && vite build"
8
8
  },
9
9
  "dependencies": {
10
- "@mediamonks/canopy": "^0.0.1",
10
+ "@mediamonks/canopy": "^0.0.4",
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.1",
10
+ "@mediamonks/canopy": "^0.0.4",
11
11
  "hono": "^4.8.0",
12
12
  "react": "^19.1.0",
13
13
  "react-dom": "^19.1.0"
@@ -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-aTr-RQil.mjs");
115
+ const { skillInstalled } = await import("./skill-12Ga-2UT.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.3",
3
+ "version": "0.7.5",
4
4
  "description": "Canopy CLI — create, develop and deploy Canopy projects.",
5
5
  "bin": {
6
6
  "canopy": "./dist/index.mjs"
@@ -20,7 +20,7 @@
20
20
  "esbuild": "^0.28.1",
21
21
  "hono": "^4.8.0",
22
22
  "vite-server": "npm:vite@^7",
23
- "@mediamonks/canopy": "0.0.3",
23
+ "@mediamonks/canopy": "0.0.4",
24
24
  "@mediamonks/canopy-data-plane": "0.0.2"
25
25
  },
26
26
  "devDependencies": {