@mediamonks/canopy-cli 0.7.16 → 0.7.18
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-DKnJQPm5.mjs → db-BAaqh_2O.mjs} +3 -3
- package/dist/{deploy-CnxJ4TE6.mjs → deploy-DID3xhMJ.mjs} +4 -4
- package/dist/dev-A2doKMCS.mjs +2 -0
- package/dist/{dev-CyJT6EfQ.mjs → dev-CjaomJ2D.mjs} +6 -6
- package/dist/docs/cli-feedback.md +6 -4
- package/dist/docs/cli-skill.md +16 -9
- package/dist/docs/cli.md +2 -1
- package/dist/docs/index.json +6 -1
- package/dist/docs/notifications.md +44 -0
- package/dist/docs/platform-ai-usage.md +11 -0
- package/dist/docs/porting-nextjs.md +25 -0
- package/dist/docs/sdk-ai.md +2 -2
- package/dist/{docs-D7Ey1ZMQ.mjs → docs-DP_p_mmy.mjs} +3 -3
- package/dist/{egress-PJp0SZy2.mjs → egress-DDM-_7Yo.mjs} +4 -4
- package/dist/{feedback-B0iX3iZ1.mjs → feedback-DX99gDFp.mjs} +24 -13
- package/dist/index.mjs +26 -26
- package/dist/{local-BQxsp-ZQ.mjs → local-dAQdVFEK.mjs} +2 -2
- package/dist/{login-BDTO3nVw.mjs → login-CU0pev0k.mjs} +2 -2
- package/dist/{logs-C53y2eAS.mjs → logs-D60VRbHZ.mjs} +2 -2
- package/dist/{migrations-C6EcKD6a.mjs → migrations-kKKDlR_H.mjs} +2 -2
- package/dist/{open-C5x3tX6I.mjs → open-B5XHmEmv.mjs} +2 -2
- package/dist/{package-BGDIsRxe.mjs → package-x-_qF58q.mjs} +1 -1
- package/dist/{projects-MMbv7MmK.mjs → projects-TE1PVwMy.mjs} +2 -2
- package/dist/{schedule-Cq9K0rnE.mjs → schedule-Blldchmj.mjs} +3 -3
- package/dist/{site-BUlQR117.mjs → site-BFaWfSVu.mjs} +14 -1
- package/dist/skill/canopy/SKILL.md +29 -4
- package/dist/{skill-Be_pqqdH.mjs → skill-CSFhZmBk.mjs} +26 -15
- package/dist/{staging-D8G4amjO.mjs → staging-Cw-Rzbsj.mjs} +3 -3
- package/dist/{token-BUxgRJVA.mjs → token-BqxRIRkT.mjs} +3 -3
- package/dist/{ui-B1Px5y7q.mjs → ui-DLZNEen6.mjs} +14 -2
- package/dist/{upgrade-ChwehW_q.mjs → upgrade-DcU4YMr_.mjs} +18 -7
- package/dist/{usage-DjNngyvy.mjs → usage-2cGnpSVc.mjs} +2 -2
- package/package.json +1 -1
- package/dist/dev-BqLvttKc.mjs +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as resolveProject, o as projectFlagArgs } from "./site-
|
|
2
|
-
import {
|
|
3
|
-
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-
|
|
1
|
+
import { c as resolveProject, o as projectFlagArgs } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { a as unwrap, d as withContext, i as requireTty, r as reportErrors, s as apiDataRequest } from "./ui-DLZNEen6.mjs";
|
|
3
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-kKKDlR_H.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import * as p from "@clack/prompts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as projectDirArgs, c as resolveProject, l as stagingUrl, n as siteConfig, o as projectFlagArgs, s as projectUrl, u as tryReadCanopyJson } from "./site-
|
|
2
|
-
import { c as
|
|
3
|
-
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-
|
|
1
|
+
import { a as projectDirArgs, c as resolveProject, l as stagingUrl, n as siteConfig, o as projectFlagArgs, s as projectUrl, u as tryReadCanopyJson } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { a as unwrap, c as apiDownload, d as withContext, l as apiRequest, r as reportErrors, s as apiDataRequest, t as formatDateTime } from "./ui-DLZNEen6.mjs";
|
|
3
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-kKKDlR_H.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
@@ -290,7 +290,7 @@ async function pickVersion(deploys, live, message, initial) {
|
|
|
290
290
|
message,
|
|
291
291
|
options: deploys.map((deploy) => ({
|
|
292
292
|
value: deploy.version,
|
|
293
|
-
label: `v${deploy.version} ${deploy.created_at} (${deploy.source})`,
|
|
293
|
+
label: `v${deploy.version} ${formatDateTime(deploy.created_at)} (${deploy.source})`,
|
|
294
294
|
hint: deploy.version === live ? "live" : void 0
|
|
295
295
|
})),
|
|
296
296
|
initialValue: initial
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as projectDirArgs, c as resolveProject, i as projectDialect, n as siteConfig, u as tryReadCanopyJson } from "./site-
|
|
2
|
-
import {
|
|
3
|
-
import { r as readMigrationFiles } from "./migrations-
|
|
1
|
+
import { a as projectDirArgs, c as resolveProject, i as projectDialect, n as siteConfig, u as tryReadCanopyJson } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { d as withContext, l as apiRequest, o as ApiError, r as reportErrors, t as formatDateTime } from "./ui-DLZNEen6.mjs";
|
|
3
|
+
import { r as readMigrationFiles } from "./migrations-kKKDlR_H.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
@@ -160,12 +160,12 @@ async function devRemote(flags, slug) {
|
|
|
160
160
|
for (const note of worker?.notes ?? []) console.log(note);
|
|
161
161
|
console.log("");
|
|
162
162
|
server.printUrls();
|
|
163
|
-
console.log(`\n Dev token expires ${expires_at} — restart canopy dev after that.`);
|
|
163
|
+
console.log(`\n Dev token expires ${formatDateTime(expires_at)} — restart canopy dev after that.`);
|
|
164
164
|
}
|
|
165
165
|
async function devLocal(flags, slug) {
|
|
166
166
|
let dump;
|
|
167
167
|
if (flags.pull) {
|
|
168
|
-
const { fetchProjectDump } = await import("./local-
|
|
168
|
+
const { fetchProjectDump } = await import("./local-dAQdVFEK.mjs");
|
|
169
169
|
console.log(`Pulling a fresh export of ${slug}${flags.staging ? " [staging]" : ""}…`);
|
|
170
170
|
dump = await fetchProjectDump(slug, flags.staging ? "staging" : "live");
|
|
171
171
|
}
|
|
@@ -180,7 +180,7 @@ async function devLocal(flags, slug) {
|
|
|
180
180
|
const reason = err instanceof ApiError ? err.explain().why : String(err);
|
|
181
181
|
console.log(`Note: ai/services will be unavailable — could not reach the platform (${reason}).`);
|
|
182
182
|
}
|
|
183
|
-
const { createSandbox } = await import("./local-
|
|
183
|
+
const { createSandbox } = await import("./local-dAQdVFEK.mjs");
|
|
184
184
|
const sandbox = await createSandbox({
|
|
185
185
|
slug,
|
|
186
186
|
persistDir: join(flags.dir, ".canopy", "local"),
|
|
@@ -24,10 +24,12 @@ canopy feedback attach <id> ./after-fix.png --comment "fixed — see the shot"
|
|
|
24
24
|
|
|
25
25
|
## Subcommands
|
|
26
26
|
|
|
27
|
-
Inbox commands
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
Inbox commands resolve the project the usual way — `canopy.json` in the target
|
|
28
|
+
directory, or `--project <slug>` — and require project membership. The
|
|
29
|
+
item-scoped ones (`show`, `comment`, `status`, `attach`) also work with just
|
|
30
|
+
the id from anywhere: report ids are unique across projects, so with no
|
|
31
|
+
project context the item is looked up by id alone. `list` and `show` take
|
|
32
|
+
`--json` for machine-readable output.
|
|
31
33
|
|
|
32
34
|
- `list [--status <s>] [--kind <k>]` — the project's inbox. Statuses:
|
|
33
35
|
`investigate`, `investigated`, `accepted`, `in_progress`, `resolved`,
|
package/dist/docs/cli-skill.md
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: canopy skill
|
|
3
|
-
description: Install the Canopy agent skill
|
|
3
|
+
description: Install the Canopy agent skill so coding agents know how to drive the platform — globally by default, or into a repo.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`canopy skill install` writes the Canopy agent skill
|
|
6
|
+
`canopy skill install` writes the Canopy agent skill, teaching AI coding agents (Claude Code and others) how to use the platform: checking install and login state, creating vs. initializing projects, choosing templates, working feedback reports, reading `canopy docs` before guessing, and treating `db restore`, `delete`, and deploys with appropriate care.
|
|
7
7
|
|
|
8
8
|
## Usage
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
canopy skill install
|
|
11
|
+
canopy skill install # global — applies in every repo (default)
|
|
12
|
+
canopy skill install --repo # into the current repo instead
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
`install` is currently the only subcommand.
|
|
15
16
|
|
|
16
17
|
## Arguments & flags
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
- `--repo` — install into the repository at the current working directory instead of globally. Use this when you want the skill committed with a project so teammates and CI agents get it with the repo.
|
|
19
20
|
|
|
20
21
|
## How it works
|
|
21
22
|
|
|
22
|
-
The skill files ship inside the CLI package, so installing needs no network. Two paths are written
|
|
23
|
+
The skill files ship inside the CLI package, so installing needs no network. Two paths are written — under your home directory by default, or under the repo with `--repo`:
|
|
23
24
|
|
|
24
25
|
- `.agents/skills/canopy/` — the source of truth, in the tool-neutral `.agents/` tree so any agent tooling can read it (no vendor lock-in).
|
|
25
|
-
- `.claude/skills/canopy` — a **symlink** to the `.agents/` copy, so Claude Code picks the skill up. Where symlinks aren't available (e.g. Windows without developer mode), the CLI falls back to a real copy and says so.
|
|
26
|
+
- `.claude/skills/canopy` — a **symlink** to the `.agents/` copy, so Claude Code picks the skill up (the global install lands in `~/.claude/skills/`, Claude Code's user scope, so it applies in every repo). Where symlinks aren't available (e.g. Windows without developer mode), the CLI falls back to a real copy and says so.
|
|
26
27
|
|
|
27
|
-
Installation is a clean replace: any existing skill directory is removed first, and the installed copy is stamped with the CLI version that wrote it. That stamp is how the skill stays fresh — [`canopy upgrade`](https://docs.canopy.website/cli/upgrade)
|
|
28
|
+
Installation is a clean replace: any existing skill directory is removed first, and the installed copy is stamped with the CLI version that wrote it. That stamp is how the skill stays fresh — [`canopy upgrade`](https://docs.canopy.website/cli/upgrade) re-runs `canopy skill install` with the new CLI for the global copy and for a repo copy in the directory you upgrade from, so no stale install shadows a fresh one. If that auto-refresh ever fails, re-run `canopy skill install` yourself.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
With `--repo`, commit the `.agents/` directory (and the `.claude/` link) so teammates and CI agents get the skill with the repo.
|
|
30
31
|
|
|
31
32
|
## Examples
|
|
32
33
|
|
|
@@ -34,6 +35,12 @@ Commit the `.agents/` directory (and the `.claude/` link) so teammates and CI ag
|
|
|
34
35
|
canopy skill install
|
|
35
36
|
```
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
Installs the skill globally, prints where the files went and whether `.claude/skills/canopy` is a symlink or a copy, and suggests trying `canopy docs`.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
canopy skill install --repo
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
From a repo root: same, but into this repository — commit the result.
|
|
38
45
|
|
|
39
46
|
See also [canopy docs](https://docs.canopy.website/cli/docs) — the always-current documentation the skill points agents at — and [canopy upgrade](https://docs.canopy.website/cli/upgrade).
|
package/dist/docs/cli.md
CHANGED
|
@@ -83,4 +83,5 @@ See `canopy docs ci` for wiring push-to-main deploys.
|
|
|
83
83
|
Upgrading also refreshes the installed Canopy agent skill.
|
|
84
84
|
- `canopy docs [topic]` — list docs, or print one. `--refresh` re-fetches from
|
|
85
85
|
the platform.
|
|
86
|
-
- `canopy skill install` — (re)install the Canopy agent skill
|
|
86
|
+
- `canopy skill install` — (re)install the Canopy agent skill, globally by
|
|
87
|
+
default; `--repo` installs into the current repo instead.
|
package/dist/docs/index.json
CHANGED
|
@@ -77,6 +77,11 @@
|
|
|
77
77
|
"title": "CLI command reference",
|
|
78
78
|
"summary": "Every canopy command, its flags, and what it does."
|
|
79
79
|
},
|
|
80
|
+
{
|
|
81
|
+
"slug": "notifications",
|
|
82
|
+
"title": "Slack notifications",
|
|
83
|
+
"summary": "Canopy DMs you about your reports and projects — replies, new reports, blocked fetches, budget hits, failing schedules."
|
|
84
|
+
},
|
|
80
85
|
{
|
|
81
86
|
"slug": "static-serving",
|
|
82
87
|
"title": "How static files are served",
|
|
@@ -274,7 +279,7 @@
|
|
|
274
279
|
{
|
|
275
280
|
"slug": "cli-skill",
|
|
276
281
|
"title": "canopy skill",
|
|
277
|
-
"summary": "Install the Canopy agent skill
|
|
282
|
+
"summary": "Install the Canopy agent skill so coding agents know how to drive the platform — globally by default, or into a repo."
|
|
278
283
|
},
|
|
279
284
|
{
|
|
280
285
|
"slug": "cli-upgrade",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Slack notifications
|
|
3
|
+
description: Canopy DMs you about your reports and projects — replies, new reports, blocked fetches, budget hits, failing schedules.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Canopy sends you Slack DMs when something on the platform needs your
|
|
7
|
+
attention, so nothing waits in a dashboard tab you don't have open. There is
|
|
8
|
+
nothing to install or link — if you're signed in to Canopy, the platform's
|
|
9
|
+
Slack bot can already reach you.
|
|
10
|
+
|
|
11
|
+
## What you get told about
|
|
12
|
+
|
|
13
|
+
**Your reports.** File a bug or feedback report anywhere (the in-app widget
|
|
14
|
+
or `canopy feedback report`) and you'll hear back: a DM when someone replies,
|
|
15
|
+
and when the report's status changes. Everything about one report lands in a
|
|
16
|
+
single Slack thread, so its history reads top to bottom.
|
|
17
|
+
|
|
18
|
+
**Your projects' reports.** If you own or collaborate on a project, new
|
|
19
|
+
reports and report activity on it DM you too — the triage side of the same
|
|
20
|
+
loop.
|
|
21
|
+
|
|
22
|
+
**Project events.** Things your project can't tell you itself:
|
|
23
|
+
|
|
24
|
+
- A fetch to a **new host was blocked** by the egress policy — the usual
|
|
25
|
+
reason a feature "silently fails" after adding an external API. The DM
|
|
26
|
+
names the host; allow it on the Backend tab if it's legitimate.
|
|
27
|
+
- The **daily AI budget ran out** — AI calls answer 429 until midnight UTC.
|
|
28
|
+
- A **schedule started failing** (and a follow-up when it recovers).
|
|
29
|
+
|
|
30
|
+
You're never notified about your own actions: your own deploys, comments,
|
|
31
|
+
and status changes don't DM you. (There's an off-by-default "my own actions"
|
|
32
|
+
toggle if you want them anyway.)
|
|
33
|
+
|
|
34
|
+
## Tuning it
|
|
35
|
+
|
|
36
|
+
Open **Notifications** under your avatar in the dashboard to switch any of
|
|
37
|
+
the categories on or off. To silence one noisy project entirely, use **Mute
|
|
38
|
+
project** on its Settings tab — personal to you, and your own reports
|
|
39
|
+
elsewhere keep notifying.
|
|
40
|
+
|
|
41
|
+
## If DMs don't arrive
|
|
42
|
+
|
|
43
|
+
The bot finds you by your work email. If your Slack account uses a different
|
|
44
|
+
address, the DMs can't reach you — ask the platform team.
|
|
@@ -30,6 +30,17 @@ const reply = await canopy.ai.chat({
|
|
|
30
30
|
URL and model list you can plug into ecosystem tools (like the Vercel AI
|
|
31
31
|
SDK) — same key handling, same budget, same tracking.
|
|
32
32
|
|
|
33
|
+
## Your data and the model providers
|
|
34
|
+
|
|
35
|
+
> **Note:** The gateway only talks to trusted AI sources: our own Anthropic and OpenAI accounts, and Google
|
|
36
|
+
> models through Vertex AI inside our own Google Cloud. Under those agreements, nothing you send is
|
|
37
|
+
> shared with anyone else or used to train models. That makes AI usage through Canopy fine for
|
|
38
|
+
> internal and client work alike — including confidential material.
|
|
39
|
+
|
|
40
|
+
This applies to every way your project reaches AI — `canopy.ai.chat()`,
|
|
41
|
+
streaming, and the OpenAI-compatible endpoint all go through the same gateway
|
|
42
|
+
to the same accounts.
|
|
43
|
+
|
|
33
44
|
## The daily budget
|
|
34
45
|
|
|
35
46
|
Every project has a daily AI budget in USD. When a project's spend for the
|
|
@@ -173,6 +173,31 @@ Rules of the road for the worker:
|
|
|
173
173
|
- Frontend calls are plain `fetch("/api/…")` — same origin, the platform
|
|
174
174
|
session cookie rides along automatically.
|
|
175
175
|
|
|
176
|
+
### Fetching your own API from the worker
|
|
177
|
+
|
|
178
|
+
Ported server code often calls the app's own routes with an absolute URL —
|
|
179
|
+
`fetch(BASE_URL + "/api/…")` — because server-side fetch has no origin to
|
|
180
|
+
resolve against. On Vercel that request loops through their edge; on Canopy a
|
|
181
|
+
worker's fetch is a real outbound request, so it would normally hit the
|
|
182
|
+
egress allowlist, and even an allowed hop would arrive without your session.
|
|
183
|
+
|
|
184
|
+
The platform recognizes this shape and short-circuits it: a fetch to the
|
|
185
|
+
project's **own hostname** (or to `localhost` on any port — the common
|
|
186
|
+
`BASE_URL` fallback) with a path under `/api/` re-enters your worker directly,
|
|
187
|
+
in-process. No network hop, no allowlist entry, and `env.USER` is the same
|
|
188
|
+
caller who made the original request. Two things to know:
|
|
189
|
+
|
|
190
|
+
- The loopback covers `/api/*` only. A self-fetch to any other path fails
|
|
191
|
+
like any other non-allowlisted host.
|
|
192
|
+
- Routes fetching each other in a cycle are cut off after 8 hops with a
|
|
193
|
+
`self_fetch_loop` error, instead of hanging.
|
|
194
|
+
|
|
195
|
+
Set `NEXT_PUBLIC_BASE_URL` (or whatever your helpers read) to the project's
|
|
196
|
+
own origin so absolute URLs get built at all — an unset value that falls back
|
|
197
|
+
to `http://localhost:3000` also works. Where a restructure is cheap, importing
|
|
198
|
+
the shared function instead of fetching your own route is still the simpler
|
|
199
|
+
pattern; the loopback exists so you don't have to.
|
|
200
|
+
|
|
176
201
|
## Step 4 — swap the data layer
|
|
177
202
|
|
|
178
203
|
Replace the ORM and connection strings with the project's own database:
|
package/dist/docs/sdk-ai.md
CHANGED
|
@@ -5,8 +5,8 @@ description: Metered LLM access through the project's budget-capped gateway key
|
|
|
5
5
|
|
|
6
6
|
`canopy.ai` gives your project LLM access without any API keys in your code.
|
|
7
7
|
Every request authenticates as the project and is metered against a hard
|
|
8
|
-
per-project daily budget — spend and
|
|
9
|
-
[AI access & usage](https://docs.canopy.website/platform/ai-usage).
|
|
8
|
+
per-project daily budget — spend, limits, and how prompts are handled by the
|
|
9
|
+
model providers are covered in [AI access & usage](https://docs.canopy.website/platform/ai-usage).
|
|
10
10
|
|
|
11
11
|
## ai.chat(options)
|
|
12
12
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import { n as siteConfig } from "./site-
|
|
1
|
+
import { t as version } from "./package-x-_qF58q.mjs";
|
|
2
|
+
import { n as siteConfig } from "./site-BFaWfSVu.mjs";
|
|
3
3
|
import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { r as reportErrors } from "./ui-DLZNEen6.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
|
-
import { a as projectDirArgs, c as resolveProject } from "./site-
|
|
2
|
-
import {
|
|
1
|
+
import { a as projectDirArgs, c as resolveProject } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { a as unwrap, i as requireTty, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
//#region src/egress.ts
|
|
@@ -20,7 +20,7 @@ function printList({ mode, hosts, denied, enforced }, slug) {
|
|
|
20
20
|
console.log(`\nRecently blocked (allow with \`canopy egress allow <host>\`):`);
|
|
21
21
|
for (const d of denied) {
|
|
22
22
|
const why = d.reason === "rate_limited" ? "rate limited" : "blocked";
|
|
23
|
-
console.log(` ${d.host} ×${d.count} ${why}, last ${d.last_seen}`);
|
|
23
|
+
console.log(` ${d.host} ×${d.count} ${why}, last ${formatDateTime(d.last_seen)}`);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
if (!enforced) console.error("(note: this site's target does not enforce egress yet — the policy is recorded for when it does)");
|
|
@@ -175,7 +175,7 @@ const egressCommand = defineCommand({
|
|
|
175
175
|
console.log(`No outbound traffic recorded for ${slug} yet.`);
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
178
|
-
for (const v of visits) console.log(`${v.host} ×${v.count} last ${v.last_seen}`);
|
|
178
|
+
for (const v of visits) console.log(`${v.host} ×${v.count} last ${formatDateTime(v.last_seen)}`);
|
|
179
179
|
})
|
|
180
180
|
})
|
|
181
181
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import { a as projectDirArgs, c as resolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-
|
|
3
|
-
import { c as
|
|
1
|
+
import { t as version } from "./package-x-_qF58q.mjs";
|
|
2
|
+
import { a as projectDirArgs, c as resolveProject, d as tryResolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-BFaWfSVu.mjs";
|
|
3
|
+
import { c as apiDownload, l as apiRequest, r as reportErrors, t as formatDateTime, u as apiUpload } from "./ui-DLZNEen6.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { basename, extname, join } from "node:path";
|
|
6
6
|
import { readFile, writeFile } from "node:fs/promises";
|
|
@@ -72,6 +72,19 @@ const jsonArg = { json: {
|
|
|
72
72
|
type: "boolean",
|
|
73
73
|
description: "Machine-readable JSON output"
|
|
74
74
|
} };
|
|
75
|
+
/**
|
|
76
|
+
* The project an item-scoped command targets: `--project` or canopy.json when
|
|
77
|
+
* present, otherwise the API's ID-only lookup — item ids are globally unique,
|
|
78
|
+
* so the item names its own project. That's the usual agent situation: holding
|
|
79
|
+
* an id from a notification or a Slack thread, in a directory that isn't the
|
|
80
|
+
* project.
|
|
81
|
+
*/
|
|
82
|
+
async function resolveItemSlug(args, itemId) {
|
|
83
|
+
const { slug } = tryResolveProject(args);
|
|
84
|
+
if (slug) return slug;
|
|
85
|
+
const { project } = await apiRequest("GET", `/feedback/${encodeURIComponent(itemId)}`);
|
|
86
|
+
return project.slug;
|
|
87
|
+
}
|
|
75
88
|
function firstLine(body) {
|
|
76
89
|
const line = body.split("\n")[0] ?? "";
|
|
77
90
|
return line.length > 72 ? `${line.slice(0, 71)}…` : line;
|
|
@@ -133,14 +146,14 @@ const showCommand = defineCommand({
|
|
|
133
146
|
}
|
|
134
147
|
},
|
|
135
148
|
run: reportErrors("feedback show", async ({ args }) => {
|
|
136
|
-
const { slug } =
|
|
137
|
-
const detail = await apiRequest("GET", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}`);
|
|
149
|
+
const { slug } = tryResolveProject(args);
|
|
150
|
+
const detail = await apiRequest("GET", slug ? `/projects/${slug}/feedback/${encodeURIComponent(args.id)}` : `/feedback/${encodeURIComponent(args.id)}`);
|
|
138
151
|
if (args.json) {
|
|
139
152
|
console.log(JSON.stringify(detail, null, 2));
|
|
140
153
|
return;
|
|
141
154
|
}
|
|
142
155
|
const { item, comments } = detail;
|
|
143
|
-
console.log(`${item.kind} · ${item.status} · via ${item.source} · ${item.created_at}`);
|
|
156
|
+
console.log(`${item.kind} · ${item.status} · via ${item.source} · ${formatDateTime(item.created_at)}`);
|
|
144
157
|
console.log(`reported by ${item.reporter_name || item.reporter_email || "unknown"}` + (item.deployment_id ? ` on ${item.deployment_id}` : "") + (item.channel === "staging" ? " (staging)" : ""));
|
|
145
158
|
console.log(`\n${item.body}\n`);
|
|
146
159
|
if (item.context) {
|
|
@@ -166,13 +179,13 @@ const showCommand = defineCommand({
|
|
|
166
179
|
const author = entry.author_name || entry.author_email || "unknown";
|
|
167
180
|
const move = entry.status_to ? ` [${entry.status_from} → ${entry.status_to}]` : "";
|
|
168
181
|
const internal = entry.internal ? " [internal]" : "";
|
|
169
|
-
console.log(`--- ${author} · ${entry.created_at}${move}${internal}`);
|
|
182
|
+
console.log(`--- ${author} · ${formatDateTime(entry.created_at)}${move}${internal}`);
|
|
170
183
|
if (entry.body) console.log(entry.body);
|
|
171
184
|
}
|
|
172
185
|
if (args.download && shots.length > 0) {
|
|
173
186
|
console.log("");
|
|
174
187
|
for (const shot of shots) {
|
|
175
|
-
const bytes = await apiDownload(`/projects/${slug}/feedback/${encodeURIComponent(item.id)}/attachments/${shot.id}`);
|
|
188
|
+
const bytes = await apiDownload(`/projects/${detail.project.slug}/feedback/${encodeURIComponent(item.id)}/attachments/${shot.id}`);
|
|
176
189
|
const name = shot.filename ? `${shot.id}-${basename(shot.filename)}` : `${shot.id}.png`;
|
|
177
190
|
const target = join(args.download, name);
|
|
178
191
|
await writeFile(target, bytes);
|
|
@@ -204,8 +217,7 @@ const commentCommand = defineCommand({
|
|
|
204
217
|
...projectDirArgs
|
|
205
218
|
},
|
|
206
219
|
run: reportErrors("feedback comment", async ({ args }) => {
|
|
207
|
-
|
|
208
|
-
await apiRequest("POST", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}/comments`, {
|
|
220
|
+
await apiRequest("POST", `/projects/${await resolveItemSlug(args, args.id)}/feedback/${encodeURIComponent(args.id)}/comments`, {
|
|
209
221
|
body: args.text,
|
|
210
222
|
...args.internal ? { internal: true } : {}
|
|
211
223
|
});
|
|
@@ -235,8 +247,7 @@ const statusCommand = defineCommand({
|
|
|
235
247
|
...projectDirArgs
|
|
236
248
|
},
|
|
237
249
|
run: reportErrors("feedback status", async ({ args }) => {
|
|
238
|
-
|
|
239
|
-
await apiRequest("POST", `/projects/${slug}/feedback/${encodeURIComponent(args.id)}/status`, {
|
|
250
|
+
await apiRequest("POST", `/projects/${await resolveItemSlug(args, args.id)}/feedback/${encodeURIComponent(args.id)}/status`, {
|
|
240
251
|
status: args.to,
|
|
241
252
|
...args.comment ? { comment: args.comment } : {}
|
|
242
253
|
});
|
|
@@ -381,7 +392,7 @@ const feedbackCommand = defineCommand({
|
|
|
381
392
|
...projectDirArgs
|
|
382
393
|
},
|
|
383
394
|
run: reportErrors("feedback attach", async ({ args }) => {
|
|
384
|
-
const
|
|
395
|
+
const slug = await resolveItemSlug(args, args.id);
|
|
385
396
|
const paths = [args.file, ...attachedPaths(args.attach)];
|
|
386
397
|
for (const path of paths) await readFile(path);
|
|
387
398
|
if (args.internal && !args.comment) {
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "./package-
|
|
3
|
-
import { r as siteHeaderLine, t as setInvocationProfile } from "./site-
|
|
2
|
+
import { t as version } from "./package-x-_qF58q.mjs";
|
|
3
|
+
import { r as siteHeaderLine, t as setInvocationProfile } from "./site-BFaWfSVu.mjs";
|
|
4
4
|
import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
6
6
|
//#region src/index.ts
|
|
@@ -60,30 +60,30 @@ 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
|
-
schedule: () => import("./schedule-
|
|
79
|
-
feedback: () => import("./feedback-
|
|
80
|
-
db: () => import("./db-
|
|
81
|
-
usage: () => import("./usage-
|
|
82
|
-
open: () => import("./open-
|
|
83
|
-
docs: () => import("./docs-
|
|
84
|
-
skill: () => import("./skill-
|
|
85
|
-
upgrade: () => import("./upgrade-
|
|
86
|
-
logs: () => import("./logs-
|
|
63
|
+
login: () => import("./login-CU0pev0k.mjs").then((m) => m.loginCommand),
|
|
64
|
+
whoami: () => import("./login-CU0pev0k.mjs").then((m) => m.whoamiCommand),
|
|
65
|
+
logout: () => import("./login-CU0pev0k.mjs").then((m) => m.logoutCommand),
|
|
66
|
+
create: () => import("./projects-TE1PVwMy.mjs").then((m) => m.createCommand),
|
|
67
|
+
init: () => import("./projects-TE1PVwMy.mjs").then((m) => m.initCommand),
|
|
68
|
+
projects: () => import("./projects-TE1PVwMy.mjs").then((m) => m.projectsCommand),
|
|
69
|
+
delete: () => import("./projects-TE1PVwMy.mjs").then((m) => m.deleteCommand),
|
|
70
|
+
dev: () => import("./dev-A2doKMCS.mjs").then((m) => m.devCommand),
|
|
71
|
+
deploy: () => import("./deploy-DID3xhMJ.mjs").then((m) => m.deployCommand),
|
|
72
|
+
promote: () => import("./deploy-DID3xhMJ.mjs").then((m) => m.promoteCommand),
|
|
73
|
+
staging: () => import("./staging-Cw-Rzbsj.mjs").then((m) => m.stagingCommand),
|
|
74
|
+
rollback: () => import("./deploy-DID3xhMJ.mjs").then((m) => m.rollbackCommand),
|
|
75
|
+
download: () => import("./deploy-DID3xhMJ.mjs").then((m) => m.downloadCommand),
|
|
76
|
+
token: () => import("./token-BqxRIRkT.mjs").then((m) => m.tokenCommand),
|
|
77
|
+
egress: () => import("./egress-DDM-_7Yo.mjs").then((m) => m.egressCommand),
|
|
78
|
+
schedule: () => import("./schedule-Blldchmj.mjs").then((m) => m.scheduleCommand),
|
|
79
|
+
feedback: () => import("./feedback-DX99gDFp.mjs").then((m) => m.feedbackCommand),
|
|
80
|
+
db: () => import("./db-BAaqh_2O.mjs").then((m) => m.dbCommand),
|
|
81
|
+
usage: () => import("./usage-2cGnpSVc.mjs").then((m) => m.usageCommand),
|
|
82
|
+
open: () => import("./open-B5XHmEmv.mjs").then((m) => m.openCommand),
|
|
83
|
+
docs: () => import("./docs-DP_p_mmy.mjs").then((m) => m.docsCommand),
|
|
84
|
+
skill: () => import("./skill-CSFhZmBk.mjs").then((m) => m.skillCommand),
|
|
85
|
+
upgrade: () => import("./upgrade-DcU4YMr_.mjs").then((m) => m.upgradeCommand),
|
|
86
|
+
logs: () => import("./logs-D60VRbHZ.mjs").then((m) => m.logsCommand)
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
if (process.argv[2] !== "upgrade") notifyOfUpdate(version);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { s as apiDataRequest } from "./ui-DLZNEen6.mjs";
|
|
2
2
|
import { n as META_COLUMNS, r as blobRouter, t as dbRouter } from "./src-Bu4Ze32I.mjs";
|
|
3
|
-
import { n as rewriteApiPath } from "./dev-
|
|
3
|
+
import { n as rewriteApiPath } from "./dev-CjaomJ2D.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join, relative, sep } from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import { n as
|
|
1
|
+
import { _ as writeConfigFile, f as clearAllProfileTokens, g as upsertProfileAuth, h as setActiveProfile, m as readConfigFile, n as siteConfig, p as listBakedProfiles, v as getBakedProfile, y as isProfileId } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { n as openBrowser, r as reportErrors } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as resolveProject, o as projectFlagArgs } from "./site-
|
|
2
|
-
import {
|
|
1
|
+
import { c as resolveProject, o as projectFlagArgs } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { d as withContext, l as apiRequest, r as reportErrors } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/logs.ts
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { u as tryReadCanopyJson } from "./site-
|
|
2
|
-
import {
|
|
1
|
+
import { u as tryReadCanopyJson } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { s as apiDataRequest } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
//#region src/migrations.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as projectDirArgs, c as resolveProject, l as stagingUrl, s as projectUrl } from "./site-
|
|
2
|
-
import { n as
|
|
1
|
+
import { a as projectDirArgs, c as resolveProject, l as stagingUrl, s as projectUrl } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { n as openBrowser, r as reportErrors } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/open.ts
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as siteConfig, s as projectUrl, u as tryReadCanopyJson } from "./site-
|
|
2
|
-
import {
|
|
1
|
+
import { n as siteConfig, s as projectUrl, u as tryReadCanopyJson } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { a as unwrap, i as requireTty, l as apiRequest, r as reportErrors } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join, relative, resolve, sep } from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as resolveProject, o as projectFlagArgs } from "./site-
|
|
2
|
-
import {
|
|
1
|
+
import { c as resolveProject, o as projectFlagArgs } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { d as withContext, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/schedule.ts
|
|
5
5
|
/**
|
|
@@ -13,7 +13,7 @@ function renderSchedules(schedules) {
|
|
|
13
13
|
const cronWidth = Math.max(...schedules.map((s) => s.cron.length));
|
|
14
14
|
return schedules.map((s) => {
|
|
15
15
|
const state = s.enabled ? "enabled " : "disabled";
|
|
16
|
-
const lastRun = s.last_run_at ? `last run ${s.last_run_at} ${s.last_status ?? ""}`.trimEnd() + (s.last_error ? ` (${s.last_error})` : "") : "never run";
|
|
16
|
+
const lastRun = s.last_run_at ? `last run ${formatDateTime(s.last_run_at)} ${s.last_status ?? ""}`.trimEnd() + (s.last_error ? ` (${s.last_error})` : "") : "never run";
|
|
17
17
|
return `${s.name.padEnd(nameWidth)} ${s.cron.padEnd(cronWidth)} ${state} ${lastRun}`;
|
|
18
18
|
}).join("\n");
|
|
19
19
|
}
|
|
@@ -227,6 +227,19 @@ function resolveProject(args) {
|
|
|
227
227
|
slug: args.project ?? readCanopyJson(dir).slug
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Like resolveProject, but undefined instead of an error when nothing names a
|
|
232
|
+
* project — for commands that have another way to find it (e.g. the feedback
|
|
233
|
+
* item id names its own project).
|
|
234
|
+
*/
|
|
235
|
+
function tryResolveProject(args) {
|
|
236
|
+
const dir = resolve(args.dir ?? ".");
|
|
237
|
+
setProjectDirForPin(dir);
|
|
238
|
+
return {
|
|
239
|
+
dir,
|
|
240
|
+
slug: args.project ?? tryReadCanopyJson(dir)?.slug
|
|
241
|
+
};
|
|
242
|
+
}
|
|
230
243
|
//#endregion
|
|
231
244
|
//#region src/site.ts
|
|
232
245
|
/**
|
|
@@ -264,4 +277,4 @@ function siteHeaderLine(cliVersion) {
|
|
|
264
277
|
return `canopy ${cliVersion} · ${c.envOverride || !c.profileId ? c.apiUrl : `${c.profileId} (${c.projectDomain ?? new URL(c.apiUrl).hostname})`}`;
|
|
265
278
|
}
|
|
266
279
|
//#endregion
|
|
267
|
-
export {
|
|
280
|
+
export { writeConfigFile as _, projectDirArgs as a, resolveProject as c, tryResolveProject as d, clearAllProfileTokens as f, upsertProfileAuth as g, setActiveProfile as h, projectDialect as i, stagingUrl as l, readConfigFile as m, siteConfig as n, projectFlagArgs as o, listBakedProfiles as p, siteHeaderLine as r, projectUrl as s, setInvocationProfile as t, tryReadCanopyJson as u, getBakedProfile as v, isProfileId as y };
|
|
@@ -4,8 +4,9 @@ description: >-
|
|
|
4
4
|
Build, run, and deploy projects on Canopy — Monks' managed platform for
|
|
5
5
|
vibecoded apps (hosting + database + blob storage + AI). Use this whenever the
|
|
6
6
|
user wants to create a Canopy project, run/deploy one, manage its data or
|
|
7
|
-
deploy tokens,
|
|
8
|
-
|
|
7
|
+
deploy tokens, work a project's feedback inbox (reports/issues/bugs filed by
|
|
8
|
+
users — look one up, investigate, fix, resolve), or asks how to use the
|
|
9
|
+
`canopy` CLI. All platform docs are available offline via `canopy docs`.
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# Canopy
|
|
@@ -98,9 +99,33 @@ and never go stale.
|
|
|
98
99
|
- Open the live site: `canopy open`
|
|
99
100
|
- Manage data: `canopy db pull | snapshot | snapshots | restore | migrate`
|
|
100
101
|
- Work bug reports & feedback: `canopy feedback list | show | comment | status | report`
|
|
101
|
-
— see
|
|
102
|
+
— see [Working feedback reports](#working-feedback-reports-issues) below
|
|
102
103
|
- CI credentials: `canopy token create | list | revoke`
|
|
103
104
|
|
|
105
|
+
## Working feedback reports (issues)
|
|
106
|
+
|
|
107
|
+
Project members file bugs & feedback through the in-app widget; the CLI is how
|
|
108
|
+
an agent works them. Report, issue, feedback item — same thing. Read
|
|
109
|
+
`canopy docs platform-feedback` before triaging; it defines the loop. The
|
|
110
|
+
short version:
|
|
111
|
+
|
|
112
|
+
- `canopy feedback list --status investigate` shows what nobody has looked at;
|
|
113
|
+
`canopy feedback show <id>` has the body, captured context, and comment
|
|
114
|
+
trail. The context includes the deploy version that served the bug —
|
|
115
|
+
reproduce against that, not whatever is live now. The id is enough on its
|
|
116
|
+
own — `show`, `comment`, `status`, and `attach` work from any directory,
|
|
117
|
+
no `--project` needed.
|
|
118
|
+
- Investigated ≠ fixed: write findings with
|
|
119
|
+
`canopy feedback status <id> investigated --comment "<root cause>"`.
|
|
120
|
+
- **`accepted` means a person said "build this."** Never set it on your own —
|
|
121
|
+
only when the user you're working with says to build the thing.
|
|
122
|
+
- Put tech-heavy notes (stack traces, table names, root-cause detail) in an
|
|
123
|
+
internal comment (`canopy feedback comment <id> "…" --internal`) so they
|
|
124
|
+
never reach the reporter.
|
|
125
|
+
- Resolve only once the fix is actually live, and name the version:
|
|
126
|
+
`canopy feedback status <id> resolved --comment "fixed in v42"`. Attach
|
|
127
|
+
proof when it's visual: `canopy feedback attach <id> ./after-fix.png`.
|
|
128
|
+
|
|
104
129
|
## Custom tables & migrations (devs; Drizzle recommended)
|
|
105
130
|
|
|
106
131
|
The document API (`canopy.db.collection(…)`) needs no schema — prefer it. When
|
|
@@ -130,7 +155,7 @@ for it only when the app is genuinely trivial** — a page or two with light SDK
|
|
|
130
155
|
use. Its SDK is served untyped from the platform (`/_api/data/v1/sdk.js`): no
|
|
131
156
|
autocomplete, no type-checking, mistakes only show up in the browser.
|
|
132
157
|
|
|
133
|
-
Prefer **`spa`** the moment either is true — which covers most real projects:
|
|
158
|
+
Prefer **`spa`** over **`static`** the moment either is true — which covers most real projects:
|
|
134
159
|
|
|
135
160
|
- **You lean on the SDK** (`db` queries, `blob`, `ai` — anything past a stray
|
|
136
161
|
`auth.user()`). `spa` installs the SDK from npm (`@mediamonks/canopy`), fully
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import {
|
|
1
|
+
import { t as version } from "./package-x-_qF58q.mjs";
|
|
2
|
+
import { r as reportErrors } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
5
6
|
import { join, relative } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
//#region src/skill.ts
|
|
8
9
|
/**
|
|
9
|
-
* canopy skill install — write the Canopy agent skill
|
|
10
|
+
* canopy skill install — write the Canopy agent skill for coding agents.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Installs globally by default (`~/.agents/skills/canopy`, with
|
|
13
|
+
* `~/.claude/skills/canopy` symlinked to it — Claude Code's user scope, so the
|
|
14
|
+
* skill applies in every repo). `--repo` writes the same layout into the
|
|
15
|
+
* current repo instead, for teams that want the skill committed with a
|
|
16
|
+
* project. To avoid vendor lock-in the skill lives in the tool-neutral
|
|
17
|
+
* `.agents/skills/` tree either way, and `.claude` is only a symlink. The
|
|
18
|
+
* skill files are bundled with the CLI (dist/skill in the published package,
|
|
19
|
+
* packages/cli/skill in dev), so `canopy upgrade` re-runs this to keep every
|
|
20
|
+
* existing install — global and repo — in sync.
|
|
16
21
|
*/
|
|
17
22
|
const SKILL_NAME = "canopy";
|
|
18
23
|
const AGENTS_REL = join(".agents", "skills", SKILL_NAME);
|
|
@@ -64,18 +69,24 @@ function skillInstalled(root) {
|
|
|
64
69
|
const skillCommand = defineCommand({
|
|
65
70
|
meta: {
|
|
66
71
|
name: "skill",
|
|
67
|
-
description: "Manage the Canopy agent skill
|
|
72
|
+
description: "Manage the Canopy agent skill"
|
|
68
73
|
},
|
|
69
74
|
subCommands: { install: () => defineCommand({
|
|
70
75
|
meta: {
|
|
71
76
|
name: "install",
|
|
72
|
-
description: "Install/refresh the Canopy skill
|
|
77
|
+
description: "Install/refresh the Canopy skill — global by default, --repo for this repo"
|
|
73
78
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
args: { repo: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
description: "Install into the current repo (.agents + .claude here) instead of globally"
|
|
82
|
+
} },
|
|
83
|
+
run: reportErrors("skill install", async ({ args }) => {
|
|
84
|
+
const { claudeMode } = installSkill(args.repo ? process.cwd() : homedir());
|
|
85
|
+
const where = args.repo ? "into this repo" : "globally (~)";
|
|
86
|
+
const prefix = args.repo ? "" : "~/";
|
|
87
|
+
console.log(`Installed the Canopy skill ${where} (v${version}):`);
|
|
88
|
+
console.log(` ${prefix}.agents/skills/${SKILL_NAME}/ (source of truth)`);
|
|
89
|
+
console.log(claudeMode === "symlink" ? ` ${prefix}.claude/skills/${SKILL_NAME} → symlink to .agents` : ` ${prefix}.claude/skills/${SKILL_NAME} (copy — symlinks unavailable here)`);
|
|
79
90
|
console.log("\nYour agent can now use Canopy. Try: canopy docs");
|
|
80
91
|
})
|
|
81
92
|
}) }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as resolveProject, l as stagingUrl, o as projectFlagArgs, s as projectUrl } from "./site-
|
|
2
|
-
import {
|
|
1
|
+
import { c as resolveProject, l as stagingUrl, o as projectFlagArgs, s as projectUrl } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { a as unwrap, d as withContext, i as requireTty, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
//#region src/staging.ts
|
|
@@ -62,7 +62,7 @@ const stagingCommand = defineCommand({
|
|
|
62
62
|
message: "Version to serve on staging",
|
|
63
63
|
options: deploys.map((deploy) => ({
|
|
64
64
|
value: deploy.version,
|
|
65
|
-
label: `v${deploy.version} ${deploy.created_at} (${deploy.source})`,
|
|
65
|
+
label: `v${deploy.version} ${formatDateTime(deploy.created_at)} (${deploy.source})`,
|
|
66
66
|
hint: deploy.version === staged ? "staged" : deploy.version === live ? "live" : void 0
|
|
67
67
|
}))
|
|
68
68
|
}));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as projectDirArgs, c as resolveProject, o as projectFlagArgs } from "./site-
|
|
2
|
-
import {
|
|
1
|
+
import { a as projectDirArgs, c as resolveProject, o as projectFlagArgs } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
const tokenCommand = defineCommand({
|
|
5
5
|
meta: {
|
|
@@ -42,7 +42,7 @@ const tokenCommand = defineCommand({
|
|
|
42
42
|
console.log(`No deploy tokens for ${slug} — create one with \`canopy token create\`.`);
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
-
for (const t of tokens) console.log(`${t.id} ${t.name ?? "(unnamed)"} created ${t.created_at} by ${t.created_by ?? "?"}`);
|
|
45
|
+
for (const t of tokens) console.log(`${t.id} ${t.name ?? "(unnamed)"} created ${formatDateTime(t.created_at)} by ${t.created_by ?? "?"}`);
|
|
46
46
|
})
|
|
47
47
|
}),
|
|
48
48
|
revoke: defineCommand({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as siteConfig } from "./site-
|
|
1
|
+
import { n as siteConfig } from "./site-BFaWfSVu.mjs";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { cancel, isCancel } from "@clack/prompts";
|
|
4
4
|
//#region src/api.ts
|
|
@@ -383,6 +383,18 @@ function reportErrors(name, action) {
|
|
|
383
383
|
function requireTty(hint) {
|
|
384
384
|
if (!process.stdin.isTTY) throw new Error(`This needs an interactive terminal — ${hint}`);
|
|
385
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* API timestamps are UTC — ISO with a Z suffix, though a lagging API site may
|
|
388
|
+
* still send the bare "YYYY-MM-DD HH:MM:SS" form. Parse either as UTC and
|
|
389
|
+
* render compact local wall-clock time ("2026-08-25 14:23").
|
|
390
|
+
*/
|
|
391
|
+
function formatDateTime(raw) {
|
|
392
|
+
const hasZone = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(raw);
|
|
393
|
+
const date = new Date(hasZone ? raw : raw.replace(" ", "T") + "Z");
|
|
394
|
+
if (Number.isNaN(date.getTime())) return raw;
|
|
395
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
396
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
397
|
+
}
|
|
386
398
|
/** Open a URL in the default browser, fire-and-forget. */
|
|
387
399
|
function openBrowser(url) {
|
|
388
400
|
spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open", [url], {
|
|
@@ -392,4 +404,4 @@ function openBrowser(url) {
|
|
|
392
404
|
}).on("error", () => {}).unref();
|
|
393
405
|
}
|
|
394
406
|
//#endregion
|
|
395
|
-
export {
|
|
407
|
+
export { unwrap as a, apiDownload as c, withContext as d, requireTty as i, apiRequest as l, openBrowser as n, ApiError as o, reportErrors as r, apiDataRequest as s, formatDateTime as t, apiUpload as u };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as PACKAGE_NAME } from "./version-check-CcsA1QjI.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { r as reportErrors } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -112,16 +112,27 @@ 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-
|
|
116
|
-
const
|
|
115
|
+
const { skillInstalled } = await import("./skill-CSFhZmBk.mjs");
|
|
116
|
+
const { homedir } = await import("node:os");
|
|
117
|
+
const refreshGlobal = skillInstalled(homedir());
|
|
118
|
+
const refreshRepo = process.cwd() !== homedir() && skillInstalled(process.cwd());
|
|
117
119
|
console.log(`Upgrading via ${command}: ${command} ${cmd.join(" ")}`);
|
|
118
120
|
await run(command, cmd);
|
|
119
121
|
console.log(`\nDone. Run \`canopy --version\` to confirm.`);
|
|
120
|
-
|
|
122
|
+
const warnSkill = (label) => (err) => {
|
|
123
|
+
console.warn(`Could not auto-refresh the ${label} skill (${err instanceof Error ? err.message : String(err)}). Run \`canopy skill install\` yourself.`);
|
|
124
|
+
};
|
|
125
|
+
if (refreshGlobal) {
|
|
126
|
+
console.log("Refreshing the global Canopy agent skill…");
|
|
127
|
+
await run("canopy", ["skill", "install"]).catch(warnSkill("global"));
|
|
128
|
+
}
|
|
129
|
+
if (refreshRepo) {
|
|
121
130
|
console.log("Refreshing the Canopy agent skill in this repo…");
|
|
122
|
-
await run("canopy", [
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
await run("canopy", [
|
|
132
|
+
"skill",
|
|
133
|
+
"install",
|
|
134
|
+
"--repo"
|
|
135
|
+
]).catch(warnSkill("repo"));
|
|
125
136
|
}
|
|
126
137
|
})
|
|
127
138
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as resolveProject, o as projectFlagArgs } from "./site-
|
|
2
|
-
import {
|
|
1
|
+
import { c as resolveProject, o as projectFlagArgs } from "./site-BFaWfSVu.mjs";
|
|
2
|
+
import { d as withContext, l as apiRequest, r as reportErrors } from "./ui-DLZNEen6.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/usage.ts
|
|
5
5
|
/**
|
package/package.json
CHANGED
package/dist/dev-BqLvttKc.mjs
DELETED