@mediamonks/canopy-cli 0.7.38 → 0.7.39
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/README.md +5 -2
- package/dist/{ai-CjvrcBIz.mjs → ai-EyrXJC5z.mjs} +2 -2
- package/dist/{api-BPEYm9ts.mjs → api-ArfD6-t5.mjs} +40 -5
- package/dist/{browse-CBelCgnW.mjs → browse-DEhnJAPT.mjs} +1 -1
- package/dist/{db-CbqgjfYz.mjs → db-DGrCgj-R.mjs} +3 -3
- package/dist/{deploy-CqCyry4o.mjs → deploy-B7KItUnh.mjs} +3 -3
- package/dist/{dev-BtnEuAnS.mjs → dev-BMWj4NDZ.mjs} +5 -5
- package/dist/dev-BdSq6CGD.mjs +2 -0
- package/dist/docs/cli-feedback.md +27 -1
- package/dist/docs/platform-schedules.md +21 -0
- package/dist/{docs-T1nuu00A.mjs → docs-Bplp4sOY.mjs} +7 -3
- package/dist/{egress-ButFsQ2N.mjs → egress-CzxRD6Tp.mjs} +2 -2
- package/dist/{feedback-C5NKCOXQ.mjs → feedback-DFK0TXlt.mjs} +358 -14
- package/dist/index.mjs +31 -31
- package/dist/{local-BfqTheMP.mjs → local-BnfAEwso.mjs} +2 -2
- package/dist/{login-CtDWXICZ.mjs → login-B2FJGIrD.mjs} +24 -7
- package/dist/{logs-BltUi4Gp.mjs → logs-aJwWWaMx.mjs} +2 -2
- package/dist/{mcp-BwLRcfqz.mjs → mcp-DuT6_wei.mjs} +2 -2
- package/dist/{migrations-CF1iNZ7F.mjs → migrations-CG_O5Ghw.mjs} +1 -1
- package/dist/{open-DXo60Ikf.mjs → open-COFMburt.mjs} +1 -1
- package/dist/{package-CdDH35SE.mjs → package-CJJpjVh_.mjs} +1 -1
- package/dist/project-template-DlzMx8Oj.mjs +2 -0
- package/dist/{project-template-BEr_tgIY.mjs → project-template-DtTfXyLI.mjs} +2 -2
- package/dist/{projects-Bm4oTodB.mjs → projects-B3Wi01lw.mjs} +4 -4
- package/dist/{repo-CE22HpqZ.mjs → repo-4x-CJwCp.mjs} +6 -3
- package/dist/repo-CmbakFVi.mjs +2 -0
- package/dist/{schedule-DdMq5eXC.mjs → schedule-CbaDIJXr.mjs} +2 -2
- package/dist/{secrets-BOKuWBYV.mjs → secrets-CmY4dUHz.mjs} +2 -2
- package/dist/skill/canopy/SKILL.md +7 -4
- package/dist/{skill-3wOwhzDx.mjs → skill-C2lGGz9n.mjs} +2 -2
- package/dist/{staging-CU4QrzFX.mjs → staging-ypR79MlD.mjs} +2 -2
- package/dist/{token-BWnLsWLg.mjs → token-CDrHQ5Xb.mjs} +2 -2
- package/dist/{ui-CHfKV6lG.mjs → ui-CaHgvvkJ.mjs} +1 -1
- package/dist/{upgrade-BJ2dWjCG.mjs → upgrade-Caj1RsA7.mjs} +2 -2
- package/dist/{usage-Cqqvnzrf.mjs → usage-CP8rBVZi.mjs} +2 -2
- package/package.json +1 -1
- package/dist/dev-D_qzeLng.mjs +0 -2
- package/dist/project-template-DdT0dqpn.mjs +0 -2
- package/dist/repo-DlYX_1tN.mjs +0 -2
package/README.md
CHANGED
|
@@ -11,12 +11,15 @@ canopy deploy [--no-build] # authorize → signed upload → complete
|
|
|
11
11
|
canopy rollback / deploys / download
|
|
12
12
|
canopy db pull | snapshot | snapshots | restore
|
|
13
13
|
canopy token create | list | revoke
|
|
14
|
-
canopy
|
|
14
|
+
canopy feedback list | show | comment | note | status | report | watch
|
|
15
|
+
canopy usage / docs / whoami [--json] / logout
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
Control-plane calls go through `src/api.ts` (`/control/v1` on the active
|
|
18
19
|
site's `api.` host, bearer token — `CANOPY_DEPLOY_TOKEN` in CI wins over the
|
|
19
|
-
login token
|
|
20
|
+
login token; every request says which AI agent drives the CLI, if any, as
|
|
21
|
+
`x-canopy-agent`: `CANOPY_AGENT=<name>`, `none` to opt out, else `claude-code`
|
|
22
|
+
under `CLAUDECODE=1`); database safety runs on the data plane (`apiDataRequest` with
|
|
20
23
|
`X-Canopy-Project`). `canopy deploy` streams bundle bytes straight to
|
|
21
24
|
`/data/v1/uploads/:grant` under a short-lived signed grant — they never
|
|
22
25
|
transit the control plane (docs/09-plane-split.md D-d). Multi-site profiles:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as reportErrors } from "./ui-
|
|
1
|
+
import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
|
|
2
|
+
import { r as reportErrors } from "./ui-CaHgvvkJ.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/ai.ts
|
|
5
5
|
/**
|
|
@@ -213,6 +213,41 @@ function randomHex(bytes) {
|
|
|
213
213
|
function traceHeaders() {
|
|
214
214
|
return { traceparent: `00-${TRACE_ID}-${randomHex(8)}-01` };
|
|
215
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Which AI agent drives this process, if any (report-agent). The platform
|
|
218
|
+
* records it next to what the caller writes — "Arjan's agent (claude-code)" —
|
|
219
|
+
* without changing who the author is or what they may do. `CANOPY_AGENT`
|
|
220
|
+
* names it (`none` opts out, e.g. a person running the CLI from a shell that
|
|
221
|
+
* inherited the variable); otherwise Claude Code is recognised by the
|
|
222
|
+
* `CLAUDECODE=1` it sets in every shell it spawns.
|
|
223
|
+
*/
|
|
224
|
+
const AGENT_RE = /^[a-z0-9-]{1,40}$/;
|
|
225
|
+
const warnedAgents = /* @__PURE__ */ new Set();
|
|
226
|
+
function agentName(env = process.env) {
|
|
227
|
+
const named = env["CANOPY_AGENT"];
|
|
228
|
+
if (named !== void 0 && named !== "") {
|
|
229
|
+
if (named === "none") return void 0;
|
|
230
|
+
if (AGENT_RE.test(named)) return named;
|
|
231
|
+
if (!warnedAgents.has(named)) {
|
|
232
|
+
warnedAgents.add(named);
|
|
233
|
+
console.error(`warning: ignoring CANOPY_AGENT=${JSON.stringify(named)} — use 1–40 characters from a-z, 0-9 and -, or \`none\` to send no agent name.`);
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
return env["CLAUDECODE"] === "1" ? "claude-code" : void 0;
|
|
238
|
+
}
|
|
239
|
+
/** The `x-canopy-agent` header for every platform request (empty when no agent drives us). */
|
|
240
|
+
function agentHeaders(env = process.env) {
|
|
241
|
+
const agent = agentName(env);
|
|
242
|
+
return agent ? { "x-canopy-agent": agent } : {};
|
|
243
|
+
}
|
|
244
|
+
/** Headers every control/data-plane request carries besides auth: trace + agent. */
|
|
245
|
+
function commonHeaders() {
|
|
246
|
+
return {
|
|
247
|
+
...traceHeaders(),
|
|
248
|
+
...agentHeaders()
|
|
249
|
+
};
|
|
250
|
+
}
|
|
216
251
|
var ApiError = class extends Error {
|
|
217
252
|
status;
|
|
218
253
|
code;
|
|
@@ -297,7 +332,7 @@ async function apiRequest(method, path, body, rawContentType, headers) {
|
|
|
297
332
|
const response = await fetch(`${apiUrl}/control/v1${path}`, {
|
|
298
333
|
method,
|
|
299
334
|
headers: {
|
|
300
|
-
...
|
|
335
|
+
...commonHeaders(),
|
|
301
336
|
authorization: `Bearer ${token}`,
|
|
302
337
|
...body === void 0 || isForm ? {} : { "content-type": rawContentType ?? "application/json" },
|
|
303
338
|
...headers
|
|
@@ -317,7 +352,7 @@ async function apiUpload(path, bytes, contentType) {
|
|
|
317
352
|
const response = await fetch(`${apiUrl}/control/v1${path}`, {
|
|
318
353
|
method: "POST",
|
|
319
354
|
headers: {
|
|
320
|
-
...
|
|
355
|
+
...commonHeaders(),
|
|
321
356
|
authorization: `Bearer ${token}`,
|
|
322
357
|
"content-type": contentType
|
|
323
358
|
},
|
|
@@ -330,7 +365,7 @@ async function apiUpload(path, bytes, contentType) {
|
|
|
330
365
|
async function apiDownload(path) {
|
|
331
366
|
const { apiUrl, token, isDeployToken } = credentials();
|
|
332
367
|
const response = await fetch(`${apiUrl}/control/v1${path}`, { headers: {
|
|
333
|
-
...
|
|
368
|
+
...commonHeaders(),
|
|
334
369
|
authorization: `Bearer ${token}`
|
|
335
370
|
} });
|
|
336
371
|
if (!response.ok) return fail(response, isDeployToken, "GET", path);
|
|
@@ -346,7 +381,7 @@ async function apiDataRequest(method, path, projectSlug, body, rawContentType, c
|
|
|
346
381
|
const response = await fetch(`${apiUrl}/data/v1${path}`, {
|
|
347
382
|
method,
|
|
348
383
|
headers: {
|
|
349
|
-
...
|
|
384
|
+
...commonHeaders(),
|
|
350
385
|
authorization: `Bearer ${token}`,
|
|
351
386
|
"x-canopy-project": projectSlug,
|
|
352
387
|
...channel === "staging" ? { "x-canopy-channel": "staging" } : {},
|
|
@@ -359,4 +394,4 @@ async function apiDataRequest(method, path, projectSlug, body, rawContentType, c
|
|
|
359
394
|
return await response.json();
|
|
360
395
|
}
|
|
361
396
|
//#endregion
|
|
362
|
-
export {
|
|
397
|
+
export { apiRequest as a, credentials as c, apiDownload as i, withContext as l, agentHeaders as n, apiUpload as o, apiDataRequest as r, commonHeaders as s, ApiError as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as tryResolveProject, n as siteConfig } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as apiRequest, t as ApiError } from "./api-ArfD6-t5.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/browse.ts
|
|
5
5
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
4
|
-
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-
|
|
2
|
+
import { a as apiRequest, l as withContext, r as apiDataRequest } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CaHgvvkJ.mjs";
|
|
4
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-CG_O5Ghw.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
7
7
|
import * as p from "@clack/prompts";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
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-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-
|
|
4
|
-
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-
|
|
2
|
+
import { a as apiRequest, i as apiDownload, l as withContext, r as apiDataRequest } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
|
|
4
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-CG_O5Ghw.mjs";
|
|
5
5
|
import { builtinModules } from "node:module";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
7
7
|
import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject, i as projectDialect, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { r as reportErrors, t as formatDateTime } from "./ui-
|
|
4
|
-
import { r as readMigrationFiles } from "./migrations-
|
|
2
|
+
import { a as apiRequest, l as withContext, t as ApiError } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
|
|
4
|
+
import { r as readMigrationFiles } from "./migrations-CG_O5Ghw.mjs";
|
|
5
5
|
import { i as stampWorkerHeaders, n as WORKER_ATTEST_HEADER } from "./src-CxzGOEhP.mjs";
|
|
6
6
|
import { n as writeDevServerRecord } from "./dev-server-file-Dk9pk4AR.mjs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
@@ -251,7 +251,7 @@ async function devRemote(flags, slug) {
|
|
|
251
251
|
async function devLocal(flags, slug) {
|
|
252
252
|
let dump;
|
|
253
253
|
if (flags.pull) {
|
|
254
|
-
const { fetchProjectDump } = await import("./local-
|
|
254
|
+
const { fetchProjectDump } = await import("./local-BnfAEwso.mjs");
|
|
255
255
|
console.log(`Pulling a fresh export of ${slug}${flags.staging ? " [staging]" : ""}…`);
|
|
256
256
|
dump = await fetchProjectDump(slug, flags.staging ? "staging" : "live");
|
|
257
257
|
}
|
|
@@ -267,7 +267,7 @@ async function devLocal(flags, slug) {
|
|
|
267
267
|
console.log(`Note: ai/services will be unavailable — could not reach the platform (${reason}).`);
|
|
268
268
|
}
|
|
269
269
|
const workerAttest = randomBytes(24).toString("base64url");
|
|
270
|
-
const { createSandbox } = await import("./local-
|
|
270
|
+
const { createSandbox } = await import("./local-BnfAEwso.mjs");
|
|
271
271
|
const sandbox = await createSandbox({
|
|
272
272
|
slug,
|
|
273
273
|
persistDir: join(flags.dir, ".canopy", "local"),
|
|
@@ -13,10 +13,12 @@ ones.
|
|
|
13
13
|
canopy feedback list --status investigate
|
|
14
14
|
canopy feedback show <id>
|
|
15
15
|
canopy feedback comment <id> "root cause: the upload grant expires too early"
|
|
16
|
+
canopy feedback note <id> "stack trace and table names for the team"
|
|
16
17
|
canopy feedback status <id> investigated --comment "root cause: the grant expires too early"
|
|
17
18
|
canopy feedback list --status accepted
|
|
18
19
|
canopy feedback status <id> resolved --comment "fixed in v42"
|
|
19
20
|
canopy feedback mine
|
|
21
|
+
canopy feedback watch --project my-project
|
|
20
22
|
canopy feedback report "canopy deploy exits 1 after upload" --command "canopy deploy"
|
|
21
23
|
canopy feedback report "the header overlaps the logo" --attach ./shot.png
|
|
22
24
|
canopy feedback attach <id> ./after-fix.png --comment "fixed — see the shot"
|
|
@@ -46,6 +48,9 @@ machine-readable output.
|
|
|
46
48
|
updates). `--internal` (members only) keeps the note off the reporter's
|
|
47
49
|
view — for stack traces, table names, and other working detail that would
|
|
48
50
|
only confuse them.
|
|
51
|
+
- `note <id> <text>` — post an internal note: the same as
|
|
52
|
+
`comment <id> <text> --internal`, with no way to make it public. Handy
|
|
53
|
+
when you let an agent post notes but not replies the reporter sees.
|
|
49
54
|
- `attach <id> <file> [--attach <more>] [--caption <text>] [--comment <text>] [--internal]` —
|
|
50
55
|
add screenshots to an existing report. With `--comment`, the comment is
|
|
51
56
|
posted first and the screenshots hang off that trail entry — how you show
|
|
@@ -56,6 +61,12 @@ machine-readable output.
|
|
|
56
61
|
findings: pass `--comment`, or comment first.
|
|
57
62
|
- `mine [--project <slug>] [--json]` — your own reports across all projects,
|
|
58
63
|
with current statuses.
|
|
64
|
+
- `watch [--project <slug>]... [--since <time>] [--after <cursor>] [--json]` —
|
|
65
|
+
follow new reports, comments and status changes on projects you're a member
|
|
66
|
+
of, live, until you stop it. Repeat `--project` to follow several. With
|
|
67
|
+
`--json` each event is one line of JSON carrying an `id`; pass the last one
|
|
68
|
+
back as `--after` to pick up where you left off. Events say what happened
|
|
69
|
+
and who did it, not the text — use `show` for that.
|
|
59
70
|
- `report <text> [--kind bug|feedback] [--project <slug>] [--command <cmd>] [--error <output>] [--attach <file>] [--caption <text>]` —
|
|
60
71
|
file a new report. **Without `--project` it goes to the platform inbox**
|
|
61
72
|
(Canopy itself) — the right target when a `canopy` command misbehaves. The
|
|
@@ -65,6 +76,21 @@ machine-readable output.
|
|
|
65
76
|
itself is the write-up of what you found, `accepted` when the person you are
|
|
66
77
|
working with asked for it.
|
|
67
78
|
|
|
79
|
+
## Agents and people
|
|
80
|
+
|
|
81
|
+
Every report and comment remembers how it was made: from the dashboard or
|
|
82
|
+
widget, by a person at the CLI, or by an AI agent driving the CLI. Agent
|
|
83
|
+
entries read as "Ana's agent (claude-code)" in the dashboard, in Slack and
|
|
84
|
+
in `show`, so you can tell what you wrote from what your agent wrote.
|
|
85
|
+
|
|
86
|
+
The CLI notices on its own when Claude Code runs it. For another agent, set
|
|
87
|
+
`CANOPY_AGENT=<name>` (letters, digits and dashes) in its environment. To
|
|
88
|
+
mark a command as yours even inside an agent's shell, set
|
|
89
|
+
`CANOPY_AGENT=none`.
|
|
90
|
+
|
|
91
|
+
You get a Slack message when your own agent does something on a report you
|
|
92
|
+
follow, even though you never get one for your own actions.
|
|
93
|
+
|
|
68
94
|
## For agents
|
|
69
95
|
|
|
70
96
|
Two loops.
|
|
@@ -77,7 +103,7 @@ Stop there — say what it would take to fix and let the person decide.
|
|
|
77
103
|
Split your findings by audience: the status comment is what the **reporter**
|
|
78
104
|
reads, so keep it plain ("the upload fails for files over 5 MB — looking into
|
|
79
105
|
it"). The tech-loaded version — stack traces, table names, reproduction
|
|
80
|
-
queries — goes in `
|
|
106
|
+
queries — goes in `note <id> "<detail>"`, which only project
|
|
81
107
|
members see. An internal note counts as findings for `investigated`.
|
|
82
108
|
|
|
83
109
|
**Build:** `list --status accepted` is what a person has asked for. Do the
|
|
@@ -91,6 +91,27 @@ of truth for _when_ a job runs, so you can change `daily-digest` from 6:00 to
|
|
|
91
91
|
7:30 — or pause it — without touching code or redeploying. The name is the
|
|
92
92
|
stable contract between the dashboard and your handler.
|
|
93
93
|
|
|
94
|
+
## How long a run may take
|
|
95
|
+
|
|
96
|
+
Plan for **at most 5 minutes** per run. A run that takes longer can be cut
|
|
97
|
+
off before it finishes, and is then recorded as failed. If a job grows past
|
|
98
|
+
that, split it: handle a batch per run and remember where you stopped (in
|
|
99
|
+
the database), or run the schedule more often.
|
|
100
|
+
|
|
101
|
+
What happens around a run:
|
|
102
|
+
|
|
103
|
+
- **No retries.** A failed run is recorded on the schedule row, members are
|
|
104
|
+
notified the first time it fails, and the next attempt is simply the next
|
|
105
|
+
tick.
|
|
106
|
+
- **No overlap protection.** Every tick starts a fresh run, and **Run now**
|
|
107
|
+
can run alongside a scheduled one. Make the handler safe to run twice: write
|
|
108
|
+
idempotently, or take a lock row in the database and skip when it's held.
|
|
109
|
+
- **Deploys don't stop a run.** A run in progress finishes on the version it
|
|
110
|
+
started with, and keeps its data access while it does. New code takes over
|
|
111
|
+
from the next run. Database migrations in a deploy apply right away, so a
|
|
112
|
+
run on the old version may meet the new schema. Additive migrations keep
|
|
113
|
+
that safe.
|
|
114
|
+
|
|
94
115
|
## Things to know
|
|
95
116
|
|
|
96
117
|
- **Cron runs as the project, not as a person.** Data access works (see
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-CJJpjVh_.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
|
-
import {
|
|
4
|
+
import { n as agentHeaders } from "./api-ArfD6-t5.mjs";
|
|
5
|
+
import { r as reportErrors } from "./ui-CaHgvvkJ.mjs";
|
|
5
6
|
import { defineCommand } from "citty";
|
|
6
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
8
|
import { homedir } from "node:os";
|
|
@@ -62,7 +63,10 @@ function parseFrontmatter(raw) {
|
|
|
62
63
|
/** Bearer header from the deploy token (CI) or the CLI login, if either exists. */
|
|
63
64
|
function authHeaders() {
|
|
64
65
|
const token = process.env["CANOPY_DEPLOY_TOKEN"] || siteConfig().token;
|
|
65
|
-
return token ? {
|
|
66
|
+
return token ? {
|
|
67
|
+
authorization: `Bearer ${token}`,
|
|
68
|
+
...agentHeaders()
|
|
69
|
+
} : {};
|
|
66
70
|
}
|
|
67
71
|
async function fetchRes(url) {
|
|
68
72
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-
|
|
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
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
//#region src/egress.ts
|
|
@@ -1,21 +1,272 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-CJJpjVh_.mjs";
|
|
2
2
|
import { a as projectDirArgs, c as resolveProject, d as tryResolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
3
|
-
import { a as
|
|
4
|
-
import { r as reportErrors, t as formatDateTime } from "./ui-
|
|
3
|
+
import { a as apiRequest, c as credentials, i as apiDownload, o as apiUpload, s as commonHeaders } from "./api-ArfD6-t5.mjs";
|
|
4
|
+
import { r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { basename, extname, join } from "node:path";
|
|
7
7
|
import { readFile, writeFile } from "node:fs/promises";
|
|
8
|
+
//#region src/sse.ts
|
|
9
|
+
/** Push-style parser: feed decoded text, collect events from the callback. */
|
|
10
|
+
function createSseParser(onEvent) {
|
|
11
|
+
let buffer = "";
|
|
12
|
+
let first = true;
|
|
13
|
+
let lastId;
|
|
14
|
+
let event = "";
|
|
15
|
+
let data = [];
|
|
16
|
+
let sawId = false;
|
|
17
|
+
const dispatch = () => {
|
|
18
|
+
if (data.length > 0 || sawId) onEvent({
|
|
19
|
+
id: lastId,
|
|
20
|
+
event: event || "message",
|
|
21
|
+
data: data.join("\n")
|
|
22
|
+
});
|
|
23
|
+
event = "";
|
|
24
|
+
data = [];
|
|
25
|
+
sawId = false;
|
|
26
|
+
};
|
|
27
|
+
const line = (text) => {
|
|
28
|
+
if (text === "") return dispatch();
|
|
29
|
+
if (text.startsWith(":")) return;
|
|
30
|
+
const colon = text.indexOf(":");
|
|
31
|
+
const field = colon === -1 ? text : text.slice(0, colon);
|
|
32
|
+
let value = colon === -1 ? "" : text.slice(colon + 1);
|
|
33
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
34
|
+
if (field === "data") data.push(value);
|
|
35
|
+
else if (field === "event") event = value;
|
|
36
|
+
else if (field === "id" && !value.includes("\0")) {
|
|
37
|
+
lastId = value;
|
|
38
|
+
sawId = true;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
feed(text) {
|
|
43
|
+
buffer += text;
|
|
44
|
+
if (first && buffer.length > 0) {
|
|
45
|
+
if (buffer.startsWith("")) buffer = buffer.slice(1);
|
|
46
|
+
first = false;
|
|
47
|
+
}
|
|
48
|
+
let start = 0;
|
|
49
|
+
for (;;) {
|
|
50
|
+
const cr = buffer.indexOf("\r", start);
|
|
51
|
+
const lf = buffer.indexOf("\n", start);
|
|
52
|
+
if (cr === -1 && lf === -1) break;
|
|
53
|
+
const end = cr === -1 ? lf : lf === -1 ? cr : Math.min(cr, lf);
|
|
54
|
+
if (buffer[end] === "\r" && end === buffer.length - 1) break;
|
|
55
|
+
line(buffer.slice(start, end));
|
|
56
|
+
start = end + (buffer[end] === "\r" && buffer[end + 1] === "\n" ? 2 : 1);
|
|
57
|
+
}
|
|
58
|
+
buffer = buffer.slice(start);
|
|
59
|
+
},
|
|
60
|
+
/** The stream ended: a trailing lone "\r" still ends its line. */
|
|
61
|
+
end() {
|
|
62
|
+
if (buffer.endsWith("\r")) {
|
|
63
|
+
line(buffer.slice(0, -1));
|
|
64
|
+
buffer = "";
|
|
65
|
+
}
|
|
66
|
+
buffer = "";
|
|
67
|
+
event = "";
|
|
68
|
+
data = [];
|
|
69
|
+
sawId = false;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Read a fetch body as SSE events, one at a time. */
|
|
74
|
+
async function* readSse(body) {
|
|
75
|
+
const queue = [];
|
|
76
|
+
const parser = createSseParser((event) => queue.push(event));
|
|
77
|
+
const decoder = new TextDecoder();
|
|
78
|
+
const reader = body.getReader();
|
|
79
|
+
try {
|
|
80
|
+
for (;;) {
|
|
81
|
+
const { done, value } = await reader.read();
|
|
82
|
+
if (done) {
|
|
83
|
+
parser.feed(decoder.decode());
|
|
84
|
+
parser.end();
|
|
85
|
+
yield* queue.splice(0);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
89
|
+
yield* queue.splice(0);
|
|
90
|
+
}
|
|
91
|
+
} finally {
|
|
92
|
+
reader.cancel().catch(() => {});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/feedback-watch.ts
|
|
97
|
+
/**
|
|
98
|
+
* `canopy feedback watch` (report-agent): follow report activity on one or
|
|
99
|
+
* more projects through GET /control/v1/feedback/events (SSE). The server
|
|
100
|
+
* ends each stream after a few minutes; this loop reconnects from the last
|
|
101
|
+
* cursor it saw, forever, until Ctrl-C or a refusal (401/403/404) it can't
|
|
102
|
+
* fix by retrying.
|
|
103
|
+
*/
|
|
104
|
+
/**
|
|
105
|
+
* How a report or trail entry names its author: the person, or — when their
|
|
106
|
+
* agent did it — "Arjan van Wijk's agent (claude-code)".
|
|
107
|
+
*/
|
|
108
|
+
function authorLabel(name, via, agent) {
|
|
109
|
+
const person = name || "unknown";
|
|
110
|
+
return via === "agent" && agent ? `${person}'s agent (${agent})` : person;
|
|
111
|
+
}
|
|
112
|
+
/** One text line per event: time, key, type, actor, then status change / internal / kind. */
|
|
113
|
+
function formatEvent(event) {
|
|
114
|
+
const type = event.type.replace(/^report\./, "");
|
|
115
|
+
const actor = authorLabel(event.actor?.name || event.actor?.email, event.via, event.agent);
|
|
116
|
+
const extra = [];
|
|
117
|
+
if (event.type === "report.created") {
|
|
118
|
+
if (event.kind) extra.push(event.kind);
|
|
119
|
+
if (event.status) extra.push(event.status);
|
|
120
|
+
}
|
|
121
|
+
if (event.status_to) extra.push(`${event.status_from ?? "?"} → ${event.status_to}`);
|
|
122
|
+
if (event.internal) extra.push("internal");
|
|
123
|
+
return [
|
|
124
|
+
formatDateTime(event.at),
|
|
125
|
+
event.key ?? event.item_id,
|
|
126
|
+
type,
|
|
127
|
+
actor,
|
|
128
|
+
...extra.length > 0 ? [extra.join(" · ")] : []
|
|
129
|
+
].join(" ");
|
|
130
|
+
}
|
|
131
|
+
/** The stream refused us for a reason retrying won't fix (401/403/404/other 4xx). */
|
|
132
|
+
var WatchRefused = class extends Error {
|
|
133
|
+
status;
|
|
134
|
+
code;
|
|
135
|
+
detail;
|
|
136
|
+
constructor(status, code, detail) {
|
|
137
|
+
super(detail ? `${status} ${code}: ${detail}` : `${status} ${code}`);
|
|
138
|
+
this.status = status;
|
|
139
|
+
this.code = code;
|
|
140
|
+
this.detail = detail;
|
|
141
|
+
this.name = "WatchRefused";
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const BACKOFF_START_MS = 1e3;
|
|
145
|
+
const BACKOFF_MAX_MS = 3e4;
|
|
146
|
+
/** A stream that ends sooner than this without an event counts as a failure (no hot loop). */
|
|
147
|
+
const MIN_HEALTHY_STREAM_MS = 1e3;
|
|
148
|
+
function defaultSleep(ms, signal) {
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
if (signal?.aborted) return resolve();
|
|
151
|
+
const timer = setTimeout(done, ms);
|
|
152
|
+
function done() {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
signal?.removeEventListener("abort", done);
|
|
155
|
+
resolve();
|
|
156
|
+
}
|
|
157
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** The events URL for the next connection: the cursor, once we have one, replaces --since/--after. */
|
|
161
|
+
function eventsUrl(apiUrl, projects, cursor, since) {
|
|
162
|
+
const params = new URLSearchParams({ projects: projects.join(",") });
|
|
163
|
+
if (cursor) params.set("after", cursor);
|
|
164
|
+
else if (since) params.set("since", since);
|
|
165
|
+
return `${apiUrl}/control/v1/feedback/events?${params.toString()}`;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Follow the stream until `signal` aborts (resolves) or the server refuses
|
|
169
|
+
* the request (rejects with WatchRefused). Network errors, 5xx and 429 back
|
|
170
|
+
* off 1s, 2s, 4s… up to 30s; a stream that ends normally reconnects at once.
|
|
171
|
+
*/
|
|
172
|
+
async function watchFeedback(options) {
|
|
173
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
174
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
175
|
+
const now = options.now ?? Date.now;
|
|
176
|
+
const { signal } = options;
|
|
177
|
+
let cursor = options.after || void 0;
|
|
178
|
+
let backoff = BACKOFF_START_MS;
|
|
179
|
+
let announced = false;
|
|
180
|
+
const wait = async (reason) => {
|
|
181
|
+
options.err(`${reason} — reconnecting in ${Math.round(backoff / 1e3)}s…`);
|
|
182
|
+
await sleep(backoff, signal);
|
|
183
|
+
backoff = Math.min(backoff * 2, BACKOFF_MAX_MS);
|
|
184
|
+
};
|
|
185
|
+
while (!signal?.aborted) {
|
|
186
|
+
const startedAt = now();
|
|
187
|
+
let response;
|
|
188
|
+
try {
|
|
189
|
+
response = await doFetch(eventsUrl(options.apiUrl, options.projects, cursor, options.since), {
|
|
190
|
+
headers: {
|
|
191
|
+
...options.headers?.(),
|
|
192
|
+
accept: "text/event-stream",
|
|
193
|
+
authorization: `Bearer ${options.token}`,
|
|
194
|
+
...cursor ? { "last-event-id": cursor } : {}
|
|
195
|
+
},
|
|
196
|
+
signal
|
|
197
|
+
});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (signal?.aborted) return;
|
|
200
|
+
await wait(`connection failed (${errorText(error)})`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
const payload = await response.json().catch(() => ({}));
|
|
205
|
+
const code = payload.error ?? `http_${response.status}`;
|
|
206
|
+
if (response.status >= 500 || response.status === 429) {
|
|
207
|
+
await wait(`stream answered ${response.status} ${code}`);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
throw new WatchRefused(response.status, code, payload.detail);
|
|
211
|
+
}
|
|
212
|
+
if (!response.body) {
|
|
213
|
+
await wait("stream had no body");
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (!announced) {
|
|
217
|
+
announced = true;
|
|
218
|
+
options.err(`Watching ${options.projects.join(", ")}… (Ctrl-C to stop)`);
|
|
219
|
+
}
|
|
220
|
+
let delivered = 0;
|
|
221
|
+
let dropped;
|
|
222
|
+
try {
|
|
223
|
+
for await (const event of readSse(response.body)) {
|
|
224
|
+
if (event.id) cursor = event.id;
|
|
225
|
+
if (event.data === "") continue;
|
|
226
|
+
let data;
|
|
227
|
+
try {
|
|
228
|
+
data = JSON.parse(event.data);
|
|
229
|
+
} catch {
|
|
230
|
+
options.err(`skipping an event that is not JSON: ${event.data.slice(0, 120)}`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
delivered++;
|
|
234
|
+
options.out(options.json ? JSON.stringify({
|
|
235
|
+
...data,
|
|
236
|
+
id: event.id ?? null
|
|
237
|
+
}) : formatEvent(data));
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
dropped = error ?? /* @__PURE__ */ new Error("stream error");
|
|
241
|
+
}
|
|
242
|
+
if (signal?.aborted) return;
|
|
243
|
+
const healthy = delivered > 0 || now() - startedAt >= MIN_HEALTHY_STREAM_MS;
|
|
244
|
+
if (healthy) backoff = BACKOFF_START_MS;
|
|
245
|
+
if (dropped !== void 0) await wait(`stream dropped (${errorText(dropped)})`);
|
|
246
|
+
else if (!healthy) await wait("stream closed immediately");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function errorText(error) {
|
|
250
|
+
if (error instanceof Error) {
|
|
251
|
+
const cause = error.cause;
|
|
252
|
+
return cause instanceof Error ? `${error.message}: ${cause.message}` : error.message;
|
|
253
|
+
}
|
|
254
|
+
return String(error);
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
8
257
|
//#region src/feedback.ts
|
|
9
258
|
/**
|
|
10
259
|
* canopy feedback — the ticket-lite feedback system (docs/19, docs topic
|
|
11
260
|
* `platform-feedback`). Two jobs:
|
|
12
261
|
*
|
|
13
|
-
* - Work a project's inbox: `list`, `show`, `comment`, `status` — investigate
|
|
262
|
+
* - Work a project's inbox: `list`, `show`, `comment`, `note`, `status` — investigate
|
|
14
263
|
* what came in (`investigate` → comment the findings → `investigated`), and
|
|
15
264
|
* build what a human marked `accepted`.
|
|
16
265
|
* - File reports: `report` sends a bug/feedback item. With no `--project` it
|
|
17
266
|
* lands on the **platform inbox** (canopy itself) — the right target when a
|
|
18
267
|
* canopy command misbehaves while you're building something else.
|
|
268
|
+
* - Follow activity: `watch` streams new reports, comments and status
|
|
269
|
+
* changes as they happen (report-agent, feedback-watch.ts).
|
|
19
270
|
*/
|
|
20
271
|
const STATUSES = [
|
|
21
272
|
"investigate",
|
|
@@ -45,13 +296,16 @@ const IMAGE_TYPES = {
|
|
|
45
296
|
* `--attach a.png,b.png` works as well, since agents write it both ways.
|
|
46
297
|
*/
|
|
47
298
|
function attachedPaths(value) {
|
|
48
|
-
|
|
299
|
+
return repeatedFlag("--attach", value);
|
|
300
|
+
}
|
|
301
|
+
/** Every occurrence of a repeatable string flag (citty keeps only the last), comma lists split. */
|
|
302
|
+
function repeatedFlag(flag, value) {
|
|
49
303
|
const repeated = process.argv.flatMap((arg, index) => {
|
|
50
304
|
if (arg === flag) {
|
|
51
305
|
const next = process.argv[index + 1];
|
|
52
306
|
return next && !next.startsWith("-") ? [next] : [];
|
|
53
307
|
}
|
|
54
|
-
return arg.startsWith(`${flag}=`) ? [arg.slice(
|
|
308
|
+
return arg.startsWith(`${flag}=`) ? [arg.slice(flag.length + 1)] : [];
|
|
55
309
|
});
|
|
56
310
|
return (repeated.length > 0 ? repeated : value === void 0 ? [] : [String(value)]).flatMap((entry) => entry.split(",")).map((path) => path.trim()).filter((path) => path !== "");
|
|
57
311
|
}
|
|
@@ -180,7 +434,7 @@ const showCommand = defineCommand({
|
|
|
180
434
|
const { item, comments } = detail;
|
|
181
435
|
if (item.title) console.log(item.title);
|
|
182
436
|
console.log(`${refOf(item)} · ${item.kind} · ${item.status} · via ${item.source} · ${formatDateTime(item.created_at)}`);
|
|
183
|
-
console.log(`reported by ${item.reporter_name || item.reporter_email
|
|
437
|
+
console.log(`reported by ${authorLabel(item.reporter_name || item.reporter_email, item.source === "agent" ? "agent" : null, item.agent)}` + (item.deployment_id ? ` on ${item.deployment_id}` : "") + (item.channel === "staging" ? " (staging)" : ""));
|
|
184
438
|
console.log(`\n${item.body}\n`);
|
|
185
439
|
if (item.context) {
|
|
186
440
|
console.log("context:");
|
|
@@ -202,7 +456,7 @@ const showCommand = defineCommand({
|
|
|
202
456
|
console.log("");
|
|
203
457
|
}
|
|
204
458
|
for (const entry of comments) {
|
|
205
|
-
const author = entry.author_name || entry.author_email
|
|
459
|
+
const author = authorLabel(entry.author_name || entry.author_email, entry.via, entry.agent);
|
|
206
460
|
const move = entry.status_to ? ` [${entry.status_from} → ${entry.status_to}]` : "";
|
|
207
461
|
const internal = entry.internal ? " [internal]" : "";
|
|
208
462
|
console.log(`--- ${author} · ${formatDateTime(entry.created_at)}${move}${internal}`);
|
|
@@ -243,11 +497,99 @@ const commentCommand = defineCommand({
|
|
|
243
497
|
...projectDirArgs
|
|
244
498
|
},
|
|
245
499
|
run: reportErrors("feedback comment", async ({ args }) => {
|
|
246
|
-
await
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
500
|
+
await postComment(args, Boolean(args.internal));
|
|
501
|
+
})
|
|
502
|
+
});
|
|
503
|
+
/** `comment` and `note` share one path: same ref resolution, same request. */
|
|
504
|
+
async function postComment(args, internal) {
|
|
505
|
+
await apiRequest("POST", `/projects/${await resolveItemSlug(args, args.id)}/feedback/${encodeURIComponent(args.id)}/comments`, {
|
|
506
|
+
body: args.text,
|
|
507
|
+
...internal ? { internal: true } : {}
|
|
508
|
+
});
|
|
509
|
+
console.log(internal ? "Internal note added (hidden from the reporter)." : "Comment added.");
|
|
510
|
+
}
|
|
511
|
+
const noteCommand = defineCommand({
|
|
512
|
+
meta: {
|
|
513
|
+
name: "note",
|
|
514
|
+
description: "Add an internal note to an item — triager-only, never shown to the reporter (same as `comment --internal`; members only)"
|
|
515
|
+
},
|
|
516
|
+
args: {
|
|
517
|
+
id: {
|
|
518
|
+
type: "positional",
|
|
519
|
+
description: "Report key (my-project-17), or its number inside the project",
|
|
520
|
+
required: true
|
|
521
|
+
},
|
|
522
|
+
text: {
|
|
523
|
+
type: "positional",
|
|
524
|
+
description: "Note text",
|
|
525
|
+
required: true
|
|
526
|
+
},
|
|
527
|
+
...projectDirArgs
|
|
528
|
+
},
|
|
529
|
+
run: reportErrors("feedback note", async ({ args }) => {
|
|
530
|
+
await postComment(args, true);
|
|
531
|
+
})
|
|
532
|
+
});
|
|
533
|
+
const watchCommand = defineCommand({
|
|
534
|
+
meta: {
|
|
535
|
+
name: "watch",
|
|
536
|
+
description: "Follow report activity live (new reports, comments, status changes) on one or more projects; reconnects on its own until Ctrl-C (members only)"
|
|
537
|
+
},
|
|
538
|
+
args: {
|
|
539
|
+
project: {
|
|
540
|
+
type: "string",
|
|
541
|
+
description: "Project slug to follow; repeat (or comma-separate) for several (default: canopy.json)"
|
|
542
|
+
},
|
|
543
|
+
since: {
|
|
544
|
+
type: "string",
|
|
545
|
+
description: "Start with activity since this ISO time (at most 7 days back)"
|
|
546
|
+
},
|
|
547
|
+
after: {
|
|
548
|
+
type: "string",
|
|
549
|
+
description: "Resume after this cursor (the `id` of the last --json line you handled)"
|
|
550
|
+
},
|
|
551
|
+
json: {
|
|
552
|
+
type: "boolean",
|
|
553
|
+
description: "One JSON object per line: the event plus its `id` cursor (diagnostics on stderr)"
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
run: reportErrors("feedback watch", async ({ args }) => {
|
|
557
|
+
let projects = [...new Set(repeatedFlag("--project", args.project))];
|
|
558
|
+
if (projects.length === 0) {
|
|
559
|
+
const { slug } = tryResolveProject({});
|
|
560
|
+
if (!slug) throw new Error("No project to watch — pass --project <slug> (repeat it for several), or run inside a project with a canopy.json.");
|
|
561
|
+
projects = [slug];
|
|
562
|
+
} else tryResolveProject({});
|
|
563
|
+
if (args.since && args.after) throw new Error("Pass --since or --after, not both (--after already says where to resume).");
|
|
564
|
+
if (args.since && Number.isNaN(Date.parse(args.since))) throw new Error(`--since must be an ISO time such as 2026-10-23T09:00:00Z (got "${args.since}").`);
|
|
565
|
+
const { apiUrl, token } = credentials();
|
|
566
|
+
const controller = new AbortController();
|
|
567
|
+
const stop = () => controller.abort();
|
|
568
|
+
process.once("SIGINT", stop);
|
|
569
|
+
process.once("SIGTERM", stop);
|
|
570
|
+
try {
|
|
571
|
+
await watchFeedback({
|
|
572
|
+
apiUrl,
|
|
573
|
+
token,
|
|
574
|
+
projects,
|
|
575
|
+
since: args.since,
|
|
576
|
+
after: args.after,
|
|
577
|
+
json: Boolean(args.json),
|
|
578
|
+
out: (line) => process.stdout.write(`${line}\n`),
|
|
579
|
+
err: (line) => process.stderr.write(`${line}\n`),
|
|
580
|
+
headers: commonHeaders,
|
|
581
|
+
signal: controller.signal
|
|
582
|
+
});
|
|
583
|
+
} catch (error) {
|
|
584
|
+
if (error instanceof WatchRefused) {
|
|
585
|
+
const fix = error.status === 401 ? " — run `canopy login` again." : error.status === 403 ? " — watch needs your own CLI login and access to every project you name." : "";
|
|
586
|
+
throw new Error(`the activity stream refused the request: ${error.message}${fix}`);
|
|
587
|
+
}
|
|
588
|
+
throw error;
|
|
589
|
+
} finally {
|
|
590
|
+
process.off("SIGINT", stop);
|
|
591
|
+
process.off("SIGTERM", stop);
|
|
592
|
+
}
|
|
251
593
|
})
|
|
252
594
|
});
|
|
253
595
|
const statusCommand = defineCommand({
|
|
@@ -383,6 +725,7 @@ const feedbackCommand = defineCommand({
|
|
|
383
725
|
list: listCommand,
|
|
384
726
|
show: showCommand,
|
|
385
727
|
comment: commentCommand,
|
|
728
|
+
note: noteCommand,
|
|
386
729
|
attach: defineCommand({
|
|
387
730
|
meta: {
|
|
388
731
|
name: "attach",
|
|
@@ -439,7 +782,8 @@ const feedbackCommand = defineCommand({
|
|
|
439
782
|
}),
|
|
440
783
|
status: statusCommand,
|
|
441
784
|
mine: mineCommand,
|
|
442
|
-
report: reportCommand
|
|
785
|
+
report: reportCommand,
|
|
786
|
+
watch: watchCommand
|
|
443
787
|
}
|
|
444
788
|
});
|
|
445
789
|
//#endregion
|
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-CJJpjVh_.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";
|
|
@@ -63,36 +63,36 @@ const main = defineCommand({
|
|
|
63
63
|
description: "canopy — create and deploy vibecoded projects"
|
|
64
64
|
},
|
|
65
65
|
subCommands: {
|
|
66
|
-
login: () => import("./login-
|
|
67
|
-
whoami: () => import("./login-
|
|
68
|
-
logout: () => import("./login-
|
|
69
|
-
create: () => import("./projects-
|
|
70
|
-
init: () => import("./projects-
|
|
71
|
-
projects: () => import("./projects-
|
|
72
|
-
delete: () => import("./projects-
|
|
73
|
-
dev: () => import("./dev-
|
|
74
|
-
deploy: () => import("./deploy-
|
|
75
|
-
promote: () => import("./deploy-
|
|
76
|
-
staging: () => import("./staging-
|
|
77
|
-
rollback: () => import("./deploy-
|
|
78
|
-
download: () => import("./deploy-
|
|
79
|
-
token: () => import("./token-
|
|
80
|
-
egress: () => import("./egress-
|
|
81
|
-
secrets: () => import("./secrets-
|
|
82
|
-
schedule: () => import("./schedule-
|
|
83
|
-
feedback: () => import("./feedback-
|
|
84
|
-
db: () => import("./db-
|
|
85
|
-
usage: () => import("./usage-
|
|
86
|
-
ai: () => import("./ai-
|
|
87
|
-
open: () => import("./open-
|
|
88
|
-
browse: () => import("./browse-
|
|
89
|
-
docs: () => import("./docs-
|
|
90
|
-
skill: () => import("./skill-
|
|
91
|
-
upgrade: () => import("./upgrade-
|
|
92
|
-
logs: () => import("./logs-
|
|
93
|
-
mcp: () => import("./mcp-
|
|
94
|
-
repo: () => import("./repo-
|
|
95
|
-
template: () => import("./project-template-
|
|
66
|
+
login: () => import("./login-B2FJGIrD.mjs").then((m) => m.loginCommand),
|
|
67
|
+
whoami: () => import("./login-B2FJGIrD.mjs").then((m) => m.whoamiCommand),
|
|
68
|
+
logout: () => import("./login-B2FJGIrD.mjs").then((m) => m.logoutCommand),
|
|
69
|
+
create: () => import("./projects-B3Wi01lw.mjs").then((m) => m.createCommand),
|
|
70
|
+
init: () => import("./projects-B3Wi01lw.mjs").then((m) => m.initCommand),
|
|
71
|
+
projects: () => import("./projects-B3Wi01lw.mjs").then((m) => m.projectsCommand),
|
|
72
|
+
delete: () => import("./projects-B3Wi01lw.mjs").then((m) => m.deleteCommand),
|
|
73
|
+
dev: () => import("./dev-BdSq6CGD.mjs").then((m) => m.devCommand),
|
|
74
|
+
deploy: () => import("./deploy-B7KItUnh.mjs").then((m) => m.deployCommand),
|
|
75
|
+
promote: () => import("./deploy-B7KItUnh.mjs").then((m) => m.promoteCommand),
|
|
76
|
+
staging: () => import("./staging-ypR79MlD.mjs").then((m) => m.stagingCommand),
|
|
77
|
+
rollback: () => import("./deploy-B7KItUnh.mjs").then((m) => m.rollbackCommand),
|
|
78
|
+
download: () => import("./deploy-B7KItUnh.mjs").then((m) => m.downloadCommand),
|
|
79
|
+
token: () => import("./token-CDrHQ5Xb.mjs").then((m) => m.tokenCommand),
|
|
80
|
+
egress: () => import("./egress-CzxRD6Tp.mjs").then((m) => m.egressCommand),
|
|
81
|
+
secrets: () => import("./secrets-CmY4dUHz.mjs").then((m) => m.secretsCommand),
|
|
82
|
+
schedule: () => import("./schedule-CbaDIJXr.mjs").then((m) => m.scheduleCommand),
|
|
83
|
+
feedback: () => import("./feedback-DFK0TXlt.mjs").then((m) => m.feedbackCommand),
|
|
84
|
+
db: () => import("./db-DGrCgj-R.mjs").then((m) => m.dbCommand),
|
|
85
|
+
usage: () => import("./usage-CP8rBVZi.mjs").then((m) => m.usageCommand),
|
|
86
|
+
ai: () => import("./ai-EyrXJC5z.mjs").then((m) => m.aiCommand),
|
|
87
|
+
open: () => import("./open-COFMburt.mjs").then((m) => m.openCommand),
|
|
88
|
+
browse: () => import("./browse-DEhnJAPT.mjs").then((m) => m.browseCommand),
|
|
89
|
+
docs: () => import("./docs-Bplp4sOY.mjs").then((m) => m.docsCommand),
|
|
90
|
+
skill: () => import("./skill-C2lGGz9n.mjs").then((m) => m.skillCommand),
|
|
91
|
+
upgrade: () => import("./upgrade-Caj1RsA7.mjs").then((m) => m.upgradeCommand),
|
|
92
|
+
logs: () => import("./logs-aJwWWaMx.mjs").then((m) => m.logsCommand),
|
|
93
|
+
mcp: () => import("./mcp-DuT6_wei.mjs").then((m) => m.mcpCommand),
|
|
94
|
+
repo: () => import("./repo-CmbakFVi.mjs").then((m) => m.repoCommand),
|
|
95
|
+
template: () => import("./project-template-DlzMx8Oj.mjs").then((m) => m.templateCommand)
|
|
96
96
|
}
|
|
97
97
|
});
|
|
98
98
|
const quiet = process.argv[2] === "mcp" && process.argv[3] === "serve" || process.argv[2] === "repo" && process.argv[3] === "credential";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as apiDataRequest } from "./api-ArfD6-t5.mjs";
|
|
2
2
|
import { a as realtimeRouter, c as META_COLUMNS, l as blobRouter, n as WORKER_ATTEST_HEADER, o as dbRouter, s as hasPrivateNames } from "./src-CxzGOEhP.mjs";
|
|
3
|
-
import { a as rewriteApiPath } from "./dev-
|
|
3
|
+
import { a as rewriteApiPath } from "./dev-BMWj4NDZ.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,6 @@
|
|
|
1
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-DM2J10CB.mjs";
|
|
2
|
-
import { n as
|
|
2
|
+
import { n as agentHeaders } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { n as openBrowser, r as reportErrors } from "./ui-CaHgvvkJ.mjs";
|
|
3
4
|
import { n as clearRepoTokenCache } from "./repo-credential-CNDRM-tG.mjs";
|
|
4
5
|
import { defineCommand } from "citty";
|
|
5
6
|
import { hostname } from "node:os";
|
|
@@ -37,16 +38,29 @@ const whoamiCommand = defineCommand({
|
|
|
37
38
|
name: "whoami",
|
|
38
39
|
description: "Show the current Canopy login"
|
|
39
40
|
},
|
|
40
|
-
|
|
41
|
+
args: { json: {
|
|
42
|
+
type: "boolean",
|
|
43
|
+
description: "Machine-readable JSON: { profile, email, apiUrl, cloud, tokenExpiresAt, expired }"
|
|
44
|
+
} },
|
|
45
|
+
run: reportErrors("whoami", async ({ args }) => {
|
|
41
46
|
const config = siteConfig();
|
|
42
47
|
if (!config.token) throw new Error("Not logged in — run `canopy login`.");
|
|
48
|
+
const expired = config.tokenExpiresAt !== void 0 && Date.parse(config.tokenExpiresAt) < Date.now();
|
|
49
|
+
if (args.json) {
|
|
50
|
+
console.log(JSON.stringify({
|
|
51
|
+
profile: config.profileId ?? null,
|
|
52
|
+
email: config.email ?? null,
|
|
53
|
+
apiUrl: config.apiUrl,
|
|
54
|
+
cloud: config.cloud ?? null,
|
|
55
|
+
tokenExpiresAt: config.tokenExpiresAt ?? null,
|
|
56
|
+
expired
|
|
57
|
+
}));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
43
60
|
console.log(config.email ?? "<unknown email>");
|
|
44
61
|
if (config.cloud) console.log(`Cloud: ${config.cloud}`);
|
|
45
62
|
console.log(`API: ${config.apiUrl}`);
|
|
46
|
-
if (config.tokenExpiresAt !== void 0) {
|
|
47
|
-
const expired = Date.parse(config.tokenExpiresAt) < Date.now();
|
|
48
|
-
console.log(expired ? `Token EXPIRED ${config.tokenExpiresAt} — run \`canopy login\` again.` : `Token expires ${config.tokenExpiresAt}.`);
|
|
49
|
-
}
|
|
63
|
+
if (config.tokenExpiresAt !== void 0) console.log(expired ? `Token EXPIRED ${config.tokenExpiresAt} — run \`canopy login\` again.` : `Token expires ${config.tokenExpiresAt}.`);
|
|
50
64
|
})
|
|
51
65
|
});
|
|
52
66
|
/**
|
|
@@ -60,7 +74,10 @@ async function revokeRemote(apiUrl, token) {
|
|
|
60
74
|
try {
|
|
61
75
|
const res = await fetch(`${apiUrl}/control/v1/account/token`, {
|
|
62
76
|
method: "DELETE",
|
|
63
|
-
headers: {
|
|
77
|
+
headers: {
|
|
78
|
+
authorization: `Bearer ${token}`,
|
|
79
|
+
...agentHeaders()
|
|
80
|
+
}
|
|
64
81
|
});
|
|
65
82
|
if (res.ok || res.status === 401) return { ok: true };
|
|
66
83
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { r as reportErrors } from "./ui-
|
|
2
|
+
import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { r as reportErrors } from "./ui-CaHgvvkJ.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
//#region src/logs.ts
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-CJJpjVh_.mjs";
|
|
2
2
|
import { d as tryResolveProject, l as stagingUrl, n as siteConfig, s as projectUrl } from "./site-DM2J10CB.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { a as apiRequest, t as ApiError } from "./api-ArfD6-t5.mjs";
|
|
4
4
|
import { t as liveDevServer } from "./dev-server-file-Dk9pk4AR.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { r as apiDataRequest } from "./api-ArfD6-t5.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
1
|
import { a as projectDirArgs, c as resolveProject, l as stagingUrl, s as projectUrl } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { n as openBrowser, r as reportErrors } from "./ui-
|
|
2
|
+
import { n as openBrowser, r as reportErrors } from "./ui-CaHgvvkJ.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/open.ts
|
|
5
5
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
2
|
+
import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CaHgvvkJ.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
/** Plain words for the API's template errors. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as siteConfig, s as projectUrl, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
4
|
-
import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-
|
|
5
|
-
import { i as offerRepoOnCreate, n as cloneProjectRepo } from "./repo-
|
|
2
|
+
import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CaHgvvkJ.mjs";
|
|
4
|
+
import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-DtTfXyLI.mjs";
|
|
5
|
+
import { i as offerRepoOnCreate, n as cloneProjectRepo } from "./repo-4x-CJwCp.mjs";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, relative, resolve, sep } from "node:path";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { a as apiRequest, l as withContext, n as agentHeaders } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
|
|
4
4
|
import { a as parseCredentialInput, i as helperCommand, o as runCredentialHelper, r as fileTokenCache, t as RepoCredentialError } from "./repo-credential-CNDRM-tG.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { existsSync, realpathSync } from "node:fs";
|
|
@@ -322,7 +322,10 @@ async function mintRepoToken(profile, slug) {
|
|
|
322
322
|
const path = `/projects/${encodeURIComponent(slug)}/repo/token`;
|
|
323
323
|
const res = await fetch(`${config.apiUrl}/control/v1${path}`, {
|
|
324
324
|
method: "POST",
|
|
325
|
-
headers: {
|
|
325
|
+
headers: {
|
|
326
|
+
authorization: `Bearer ${config.token}`,
|
|
327
|
+
...agentHeaders()
|
|
328
|
+
}
|
|
326
329
|
}).catch(() => null);
|
|
327
330
|
if (!res) throw new RepoCredentialError(`could not reach Canopy (${config.apiUrl}).`);
|
|
328
331
|
if (res.status === 201) return await res.json();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
//#region src/schedule.ts
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
//#region src/secrets.ts
|
|
@@ -107,7 +107,7 @@ and never go stale.
|
|
|
107
107
|
- Use AI (`canopy.ai`): run `canopy ai models` before naming a model in code.
|
|
108
108
|
Prefer the aliases `auto/low` (default) / `auto/mid` / `auto/high` over
|
|
109
109
|
hard-coded ids — model ids outside that list are refused.
|
|
110
|
-
- Work bug reports & feedback: `canopy feedback list | show | comment | status | report`
|
|
110
|
+
- Work bug reports & feedback: `canopy feedback list | show | comment | note | status | report | watch`
|
|
111
111
|
— see [Working feedback reports](#working-feedback-reports-issues) below
|
|
112
112
|
- CI credentials: `canopy token create | list | revoke`
|
|
113
113
|
- Git: `canopy repo show --json` (is there a repository, and its remote) ·
|
|
@@ -127,15 +127,18 @@ short version:
|
|
|
127
127
|
`canopy feedback show <id>` has the body, captured context, and comment
|
|
128
128
|
trail. The context includes the deploy version that served the bug —
|
|
129
129
|
reproduce against that, not whatever is live now. The id is enough on its
|
|
130
|
-
own — `show`, `comment`, `status`, and `attach` work from any directory,
|
|
130
|
+
own — `show`, `comment`, `note`, `status`, and `attach` work from any directory,
|
|
131
131
|
no `--project` needed.
|
|
132
132
|
- Investigated ≠ fixed: write findings with
|
|
133
133
|
`canopy feedback status <id> investigated --comment "<root cause>"`.
|
|
134
134
|
- **`accepted` means a person said "build this."** Never set it on your own —
|
|
135
135
|
only when the user you're working with says to build the thing.
|
|
136
136
|
- Put tech-heavy notes (stack traces, table names, root-cause detail) in an
|
|
137
|
-
internal
|
|
138
|
-
never reach the reporter.
|
|
137
|
+
internal note (`canopy feedback note <id> "…"`, same as `comment --internal`)
|
|
138
|
+
so they never reach the reporter.
|
|
139
|
+
- `canopy feedback watch [--project <slug>]… [--json]` follows new reports,
|
|
140
|
+
comments and status changes live; it reconnects by itself. With `--json`
|
|
141
|
+
each line carries an `id` cursor — restart with `--after <id>` to resume.
|
|
139
142
|
- Resolve only once the fix is actually live, and name the version:
|
|
140
143
|
`canopy feedback status <id> resolved --comment "fixed in v42"`. Attach
|
|
141
144
|
proof when it's visual: `canopy feedback attach <id> ./after-fix.png`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import { r as reportErrors } from "./ui-
|
|
1
|
+
import { t as version } from "./package-CJJpjVh_.mjs";
|
|
2
|
+
import { r as reportErrors } from "./ui-CaHgvvkJ.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";
|
|
@@ -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 {
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-
|
|
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
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
//#region src/staging.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { a as apiRequest } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { r as reportErrors, t as formatDateTime } from "./ui-CaHgvvkJ.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
const tokenCommand = defineCommand({
|
|
6
6
|
meta: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as PACKAGE_NAME } from "./version-check-CcsA1QjI.mjs";
|
|
2
|
-
import { r as reportErrors } from "./ui-
|
|
2
|
+
import { r as reportErrors } from "./ui-CaHgvvkJ.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-
|
|
115
|
+
const { skillInstalled } = await import("./skill-C2lGGz9n.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 {
|
|
3
|
-
import { r as reportErrors } from "./ui-
|
|
2
|
+
import { a as apiRequest, l as withContext } from "./api-ArfD6-t5.mjs";
|
|
3
|
+
import { r as reportErrors } from "./ui-CaHgvvkJ.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
//#region src/usage.ts
|
|
6
6
|
/**
|
package/package.json
CHANGED
package/dist/dev-D_qzeLng.mjs
DELETED
package/dist/repo-DlYX_1tN.mjs
DELETED