@mediamonks/canopy-cli 0.7.41 → 0.7.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/{ai-PXXMv7kO.mjs → ai-DpQ1nLka.mjs} +2 -2
  2. package/dist/{api-BHzCvDcY.mjs → api-Do9c8tZ_.mjs} +61 -3
  3. package/dist/{browse-DRz1DowL.mjs → browse-Dp5HytqK.mjs} +2 -2
  4. package/dist/{db-5LfBG6N7.mjs → db-BNsX7sXo.mjs} +4 -4
  5. package/dist/{deploy-6rYq1Iq8.mjs → deploy-CXMLABvH.mjs} +8 -8
  6. package/dist/{dev-D_gyIeUq.mjs → dev-CPR-9slK.mjs} +6 -6
  7. package/dist/dev-Dj3wFKHe.mjs +2 -0
  8. package/dist/docs/ci.md +19 -1
  9. package/dist/docs/cli-rename.md +51 -0
  10. package/dist/docs/git.md +5 -0
  11. package/dist/docs/index.json +11 -1
  12. package/dist/docs/rename.md +65 -0
  13. package/dist/{docs-B9xGnLSR.mjs → docs-CkJGcCpR.mjs} +4 -4
  14. package/dist/{egress-CCpOsYpS.mjs → egress-D6qyaaAn.mjs} +3 -3
  15. package/dist/{feedback-CdjtPumG.mjs → feedback-DMgUUl0V.mjs} +4 -4
  16. package/dist/index.mjs +33 -32
  17. package/dist/{local-Dshy0NsJ.mjs → local-w2wXfE1d.mjs} +2 -2
  18. package/dist/{login-CKNFCp7c.mjs → login-CeNvHLUv.mjs} +3 -3
  19. package/dist/{logs-D3WakFME.mjs → logs-BzgJfWNP.mjs} +3 -3
  20. package/dist/{mcp-CoVS5Mj3.mjs → mcp-DFLaXiY_.mjs} +8 -6
  21. package/dist/{migrations-DDJ5JJqj.mjs → migrations-Bsu4quya.mjs} +2 -2
  22. package/dist/{open-BPYozPQo.mjs → open-D71Ms1nG.mjs} +2 -2
  23. package/dist/{package-SWgTmctH.mjs → package-CVDpzGgA.mjs} +1 -1
  24. package/dist/{project-template-DACdMdpn.mjs → project-template-BXfHpsI8.mjs} +3 -3
  25. package/dist/project-template-BbmSmraT.mjs +2 -0
  26. package/dist/{projects-naWQ89Qj.mjs → projects-BWTIk80a.mjs} +5 -5
  27. package/dist/rename-BdIFrqvG.mjs +81 -0
  28. package/dist/repo-JBKJqyoS.mjs +2 -0
  29. package/dist/{repo-BCua4L-I.mjs → repo-LSOxnmUk.mjs} +5 -3
  30. package/dist/{schedule-DjHcaxyt.mjs → schedule-DpMIpCGC.mjs} +3 -3
  31. package/dist/{secrets-COwRvCev.mjs → secrets-BVx8L0Gj.mjs} +3 -3
  32. package/dist/{site-DM2J10CB.mjs → site-0i7wAJqg.mjs} +24 -1
  33. package/dist/{skill-DSYQA4Ls.mjs → skill-D58yV6K8.mjs} +2 -2
  34. package/dist/{staging-CYMTZyyM.mjs → staging-ynPlMiHW.mjs} +3 -3
  35. package/dist/{token-CCpJ1ES4.mjs → token-CxiPrKn_.mjs} +3 -3
  36. package/dist/{ui-SBUdC9rX.mjs → ui-CDUJq-X-.mjs} +1 -1
  37. package/dist/{upgrade-B_tLCpc4.mjs → upgrade-Dll5rhOQ.mjs} +2 -2
  38. package/dist/{usage-BSPnsMXd.mjs → usage-BtPsb7Iu.mjs} +3 -3
  39. package/package.json +1 -1
  40. package/dist/dev-D38FQ2LD.mjs +0 -2
  41. package/dist/project-template-CMgD5hnV.mjs +0 -2
  42. package/dist/repo-Un_p617X.mjs +0 -2
@@ -1,5 +1,5 @@
1
- import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
2
- import { r as reportErrors } from "./ui-SBUdC9rX.mjs";
1
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
2
+ import { r as reportErrors } from "./ui-CDUJq-X-.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  //#region src/ai.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { n as siteConfig } from "./site-DM2J10CB.mjs";
1
+ import { r as siteConfig, t as projectDir, u as rewriteCanopyJsonSlug } from "./site-0i7wAJqg.mjs";
2
2
  //#region src/api.ts
3
3
  /**
4
4
  * Authenticated requests to the Canopy control plane. Credentials, in order:
@@ -29,7 +29,7 @@ const ERROR_COPY = {
29
29
  fix: "Choose a different slug."
30
30
  },
31
31
  invalid_slug: {
32
- why: "the slug is not a valid format",
32
+ why: "the slug breaks one of the naming rules",
33
33
  fix: "Use lowercase letters, digits and hyphens only (e.g. my-project)."
34
34
  },
35
35
  project_paused: {
@@ -191,6 +191,26 @@ const ERROR_COPY = {
191
191
  worker_secret_sync_failed: {
192
192
  why: "the secret was saved but redeploying the running worker with it failed",
193
193
  fix: "Retry, or redeploy the project (`canopy deploy`) to apply it."
194
+ },
195
+ slug_unchanged: {
196
+ why: "the project already has that slug",
197
+ fix: "Pass the new slug you want, e.g. `canopy rename my-new-name`."
198
+ },
199
+ rename_not_allowed: {
200
+ why: "Canopy's own projects (dashboard, www, docs) keep their names",
201
+ fix: "Nothing to do — those addresses are fixed."
202
+ },
203
+ rename_too_soon: {
204
+ why: "a project can be renamed once a day",
205
+ fix: "Try again after the time shown, or ask a Canopy admin if it can't wait."
206
+ },
207
+ too_many_old_slugs: {
208
+ why: "a project keeps at most 5 old slugs, and each one stays reserved for it",
209
+ fix: "Rename back to one of its old slugs instead (`canopy projects` shows them in the dashboard's Settings), or ask a Canopy admin."
210
+ },
211
+ rename_conflict: {
212
+ why: "the project changed while the rename ran",
213
+ fix: "Run the command again."
194
214
  }
195
215
  };
196
216
  /** Generic explanation for a status code when we have no per-code copy. */
@@ -260,6 +280,40 @@ function commonHeaders() {
260
280
  ...agentHeaders()
261
281
  };
262
282
  }
283
+ /**
284
+ * Renamed projects (OpenSpec project-slug-rename). A request that names an
285
+ * old slug still reaches the project, and the answer carries its current
286
+ * slug in `Canopy-Project-Slug`. By default the CLI then points canopy.json
287
+ * at it (when that file named the old slug) and says so once; `canopy mcp
288
+ * serve` and the git credential helper only say so, because what names the
289
+ * slug there is the user's own configuration, not ours to edit.
290
+ */
291
+ const RENAMED_HEADER = "canopy-project-slug";
292
+ const renames = /* @__PURE__ */ new Map();
293
+ let renameHandling = "rewrite";
294
+ /** Report renames without editing files (mcp serve, the git credential helper). */
295
+ function noticeRenamesOnly() {
296
+ renameHandling = "notice";
297
+ }
298
+ /** The current slug of a project this process saw renamed; `slug` when it saw none. */
299
+ function currentSlug(slug) {
300
+ return renames.get(slug) ?? slug;
301
+ }
302
+ /** Act on a response that says `named` is an old slug. Exported for the credential helper. */
303
+ function followRename(response, named) {
304
+ const current = response.headers.get(RENAMED_HEADER);
305
+ if (!named || !current || current === named || renames.has(named)) return;
306
+ renames.set(named, current);
307
+ if (renameHandling === "rewrite" && rewriteCanopyJsonSlug(projectDir(), named, current)) {
308
+ console.error(`canopy: project "${named}" is now "${current}" — updated canopy.json.`);
309
+ return;
310
+ }
311
+ console.error(`canopy: project "${named}" is now "${current}" — use "${current}" where you name it (MCP client config, scripts); the old name keeps working for now.`);
312
+ }
313
+ /** The slug a control-plane path names (`/projects/<slug>/…`), if any. */
314
+ function slugInPath(path) {
315
+ return /^\/projects\/([^/?#]+)/.exec(path)?.[1];
316
+ }
263
317
  var ApiError = class extends Error {
264
318
  status;
265
319
  code;
@@ -351,6 +405,7 @@ async function apiRequest(method, path, body, rawContentType, headers) {
351
405
  },
352
406
  body: body === void 0 ? void 0 : isForm ? body : rawContentType !== void 0 ? body : JSON.stringify(body)
353
407
  });
408
+ followRename(response, slugInPath(path));
354
409
  if (!response.ok) return fail(response, isDeployToken, method, path);
355
410
  if (response.status === 204) return void 0;
356
411
  return await response.json();
@@ -370,6 +425,7 @@ async function apiUpload(path, bytes, contentType) {
370
425
  },
371
426
  body: bytes
372
427
  });
428
+ followRename(response, slugInPath(path));
373
429
  if (!response.ok) return fail(response, isDeployToken, "POST", path);
374
430
  return await response.json();
375
431
  }
@@ -380,6 +436,7 @@ async function apiDownload(path) {
380
436
  ...commonHeaders(),
381
437
  authorization: `Bearer ${token}`
382
438
  } });
439
+ followRename(response, slugInPath(path));
383
440
  if (!response.ok) return fail(response, isDeployToken, "GET", path);
384
441
  return Buffer.from(await response.arrayBuffer());
385
442
  }
@@ -401,9 +458,10 @@ async function apiDataRequest(method, path, projectSlug, body, rawContentType, c
401
458
  },
402
459
  body: body === void 0 ? void 0 : rawContentType !== void 0 ? body : JSON.stringify(body)
403
460
  });
461
+ followRename(response, projectSlug);
404
462
  if (!response.ok) return fail(response, isDeployToken, method, path);
405
463
  if (response.status === 204) return void 0;
406
464
  return await response.json();
407
465
  }
408
466
  //#endregion
409
- 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 };
467
+ export { apiRequest as a, credentials as c, noticeRenamesOnly as d, withContext as f, apiDownload as i, currentSlug as l, agentHeaders as n, apiUpload as o, apiDataRequest as r, commonHeaders as s, ApiError as t, followRename as u };
@@ -1,5 +1,5 @@
1
- import { d as tryResolveProject, n as siteConfig } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, t as ApiError } from "./api-BHzCvDcY.mjs";
1
+ import { p as tryResolveProject, r as siteConfig } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, t as ApiError } from "./api-Do9c8tZ_.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  //#region src/browse.ts
5
5
  /**
@@ -1,7 +1,7 @@
1
- import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext, r as apiDataRequest } from "./api-BHzCvDcY.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-SBUdC9rX.mjs";
4
- import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-DDJ5JJqj.mjs";
1
+ import { l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext, r as apiDataRequest } from "./api-Do9c8tZ_.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CDUJq-X-.mjs";
4
+ import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-Bsu4quya.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
- 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 { a as apiRequest, i as apiDownload, l as withContext, r as apiDataRequest } from "./api-BHzCvDcY.mjs";
3
- import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
4
- import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-DDJ5JJqj.mjs";
1
+ import { c as projectUrl, d as stagingUrl, f as tryReadCanopyJson, l as resolveProject, o as projectDirArgs, r as siteConfig, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext, i as apiDownload, l as currentSlug, r as apiDataRequest } from "./api-Do9c8tZ_.mjs";
3
+ import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.mjs";
4
+ import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-Bsu4quya.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";
@@ -314,10 +314,10 @@ const deployCommand = defineCommand({
314
314
  }
315
315
  const backend = result.worker?.serviceUrl ? ` · backend ${result.worker.serviceUrl}` : "";
316
316
  if (channel === "staging") {
317
- spin.stop(`Staging: ${stagingUrl(slug)} (v${result.version})${backend}`);
317
+ spin.stop(`Staging: ${stagingUrl(currentSlug(slug))} (v${result.version})${backend}`);
318
318
  if (stagingCreated) console.log(" staging channel created — database cloned from live");
319
319
  console.log(` happy with it? \`canopy promote\` makes v${result.version} live`);
320
- } else spin.stop(`Live: ${projectUrl(slug)} (v${result.version})${backend}`);
320
+ } else spin.stop(`Live: ${projectUrl(currentSlug(slug))} (v${result.version})${backend}`);
321
321
  if (migrateResult !== void 0) for (const line of describeMigrateResult(migrateResult)) console.log(` ${line}`);
322
322
  })
323
323
  });
@@ -371,7 +371,7 @@ const promoteCommand = defineCommand({
371
371
  }
372
372
  const backend = result.worker?.serviceUrl ? ` · backend ${result.worker.serviceUrl}` : "";
373
373
  const was = result.previous === null ? "" : `, was v${result.previous}`;
374
- spin.stop(`Live: ${projectUrl(slug)} (v${result.version}${was})${backend}`);
374
+ spin.stop(`Live: ${projectUrl(currentSlug(slug))} (v${result.version}${was})${backend}`);
375
375
  for (const name of result.migrationsApplied) console.log(` applied ${name}`);
376
376
  if (result.snapshot !== void 0) console.log(` pre-promote snapshot: ${result.snapshot} (canopy db restore --snapshot …)`);
377
377
  })
@@ -445,7 +445,7 @@ const rollbackCommand = defineCommand({
445
445
  console.log(`Rolling back ${slug} to v${target}`);
446
446
  const { live: result } = await apiRequest("POST", `/projects/${slug}/deploys/${target}/rollback`);
447
447
  const worker = result.worker ? ` · worker v${result.worker.version}` : "";
448
- console.log(`Live: ${projectUrl(slug)} (v${result.version}, was v${result.previous ?? "?"})${worker}`);
448
+ console.log(`Live: ${projectUrl(currentSlug(slug))} (v${result.version}, was v${result.previous ?? "?"})${worker}`);
449
449
  })
450
450
  });
451
451
  const downloadCommand = defineCommand({
@@ -1,7 +1,7 @@
1
- import { a as projectDirArgs, c as resolveProject, i as projectDialect, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext, t as ApiError } from "./api-BHzCvDcY.mjs";
3
- import { r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
4
- import { r as readMigrationFiles } from "./migrations-DDJ5JJqj.mjs";
1
+ import { a as projectDialect, f as tryReadCanopyJson, l as resolveProject, o as projectDirArgs, r as siteConfig } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext, t as ApiError } from "./api-Do9c8tZ_.mjs";
3
+ import { r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.mjs";
4
+ import { r as readMigrationFiles } from "./migrations-Bsu4quya.mjs";
5
5
  import { i as stampWorkerHeaders, n as WORKER_ATTEST_HEADER } from "./src-RUf3Gfuh.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-Dshy0NsJ.mjs");
254
+ const { fetchProjectDump } = await import("./local-w2wXfE1d.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-Dshy0NsJ.mjs");
270
+ const { createSandbox } = await import("./local-w2wXfE1d.mjs");
271
271
  const sandbox = await createSandbox({
272
272
  slug,
273
273
  persistDir: join(flags.dir, ".canopy", "local"),
@@ -0,0 +1,2 @@
1
+ import { n as devCommand } from "./dev-CPR-9slK.mjs";
2
+ export { devCommand };
package/dist/docs/ci.md CHANGED
@@ -1,8 +1,26 @@
1
1
  ---
2
2
  title: CI and deploy tokens
3
- description: Push-to-main deploys using a project-scoped deploy token.
3
+ description: Push-to-main deploys using a project-scoped deploy token, and why we recommend deploying from your own machine instead.
4
4
  ---
5
5
 
6
+ ## Our recommendation: deploy manually
7
+
8
+ Most Canopy users don't have a GitHub Enterprise seat, so they can't see or
9
+ interact with GitHub Actions. That is why Canopy repositories are created with
10
+ Actions turned **off**: pushing a workflow file works, but nothing runs, and
11
+ there is no Actions tab for you to look at. Turning Actions on for a repository
12
+ is something only a GitHub administrator can do, and you would still not see
13
+ the runs.
14
+
15
+ Deploy from your machine (or let your coding agent do it): run
16
+ `canopy deploy --staging`, check the result, then `canopy promote`. It is
17
+ usually quicker than waiting for a pipeline, and your agent can verify the
18
+ deploy right afterwards. Run your typecheck, lint and tests locally before you
19
+ deploy.
20
+
21
+ The rest of this page is for teams that run their own CI somewhere they can
22
+ see it.
23
+
6
24
  Interactive `canopy login` is for humans at a terminal. For automated deploys
7
25
  (GitHub Actions, etc.) use a **deploy token**: a project-scoped credential that
8
26
  can deploy that one project and nothing else.
@@ -0,0 +1,51 @@
1
+ ---
2
+ title: canopy rename
3
+ description: Change a project's slug — its address — and update canopy.json.
4
+ ---
5
+
6
+ `canopy rename` gives a project a new slug, and with it a new address. The old
7
+ slug stays with the project: old links redirect, and everything that still
8
+ names it keeps working. See [Rename a project](https://docs.canopy.website/guides/rename) for what changes
9
+ and what doesn't.
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ canopy rename my-new-name # in the project's folder
15
+ canopy rename my-new-name --project old-name # name the project yourself
16
+ canopy rename my-new-name --yes # skip the confirmation
17
+ ```
18
+
19
+ ## Arguments & flags
20
+
21
+ - `<slug>` — the new slug. Same rules as `canopy create`: 2–40 lowercase letters,
22
+ digits and dashes, starting with a letter.
23
+ - `--project <slug>` — the project to rename. Defaults to the `slug` in
24
+ `canopy.json`.
25
+ - `[dir]` — the project folder (defaults to the current directory). Its
26
+ `canopy.json` is updated after the rename.
27
+ - `--yes` — rename without asking. Required on non-interactive runs.
28
+ - `--json` — print the result as JSON: the project, its old slugs, and whether
29
+ the backend refresh and Slack app update are still to come.
30
+
31
+ ## Details
32
+
33
+ Before renaming, the CLI lists what changes for people: the address, the email
34
+ sender, client-password visitors signing in again, and visitors' browser storage
35
+ starting empty. Only the owner or a Canopy admin can rename. A project can be
36
+ renamed once a day and keeps at most 5 old slugs; renaming back to one of them
37
+ is always allowed.
38
+
39
+ Other commands notice a rename too. When `canopy.json` still names the old
40
+ slug, the next command that talks to the project updates it and says so in one
41
+ line. `canopy mcp serve` and the git credential helper only print a notice,
42
+ since they're started from your own configuration.
43
+
44
+ ## Examples
45
+
46
+ ```bash
47
+ canopy rename q4-dashboard
48
+ ```
49
+
50
+ Shows what changes, asks to confirm, renames, and points `canopy.json` at
51
+ `q4-dashboard`.
package/dist/docs/git.md CHANGED
@@ -79,6 +79,11 @@ sign in to a Canopy repository; use the terminal for those.
79
79
  - **Deleting is for good.** `canopy repo delete` (owner only) removes the
80
80
  repository and all its history. Deleting the project deletes its repository
81
81
  too.
82
+ - **No CI on the repository.** GitHub Actions are turned off on Canopy
83
+ repositories, so workflow files you push don't run, and you can't see
84
+ Actions runs without a GitHub seat. Run your checks locally and deploy with
85
+ `canopy deploy --staging` then `canopy promote` (see
86
+ [CI and deploy tokens](https://docs.canopy.website/guides/ci)).
82
87
  - **Moving to your own GitHub later** is ordinary git: create an empty
83
88
  repository there, then `git remote add github <its url>` and
84
89
  `git push github --all`.
@@ -65,7 +65,7 @@
65
65
  {
66
66
  "slug": "ci",
67
67
  "title": "CI and deploy tokens",
68
- "summary": "Push-to-main deploys using a project-scoped deploy token."
68
+ "summary": "Push-to-main deploys using a project-scoped deploy token, and why we recommend deploying from your own machine instead."
69
69
  },
70
70
  {
71
71
  "slug": "git",
@@ -77,6 +77,11 @@
77
77
  "title": "Start from a template",
78
78
  "summary": "Begin a project from someone else's working one — a copy of their code and setup, never their data."
79
79
  },
80
+ {
81
+ "slug": "rename",
82
+ "title": "Rename a project",
83
+ "summary": "Change a project's slug — its address — without breaking links, checkouts or integrations."
84
+ },
80
85
  {
81
86
  "slug": "agent-tools",
82
87
  "title": "Agent tools (MCP)",
@@ -231,6 +236,11 @@
231
236
  "title": "canopy delete",
232
237
  "summary": "Permanently delete a Canopy project — its deploys, database, and files."
233
238
  },
239
+ {
240
+ "slug": "cli-rename",
241
+ "title": "canopy rename",
242
+ "summary": "Change a project's slug — its address — and update canopy.json."
243
+ },
234
244
  {
235
245
  "slug": "cli-dev",
236
246
  "title": "canopy dev",
@@ -0,0 +1,65 @@
1
+ ---
2
+ title: Rename a project
3
+ description: Change a project's slug — its address — without breaking links, checkouts or integrations.
4
+ ---
5
+
6
+ A project's **slug** is its address: `<slug>.canopy.website`. You can change it
7
+ from the dashboard or the CLI. Nothing needs to be redeployed, and everything
8
+ that still uses the old slug keeps reaching the project.
9
+
10
+ ## How to rename
11
+
12
+ In the dashboard, open the project's **Settings** and use **Rename address**
13
+ on the _Project address_ card. Type the new slug, read what changes, and type
14
+ it again to confirm.
15
+
16
+ From the CLI, run it inside the project's folder:
17
+
18
+ ```bash
19
+ canopy rename my-new-name
20
+ ```
21
+
22
+ The CLI shows what changes and asks before it renames. It also updates
23
+ `canopy.json` in that folder. See [`canopy rename`](https://docs.canopy.website/cli/rename) for the flags.
24
+
25
+ Only the project's **owner** (or a Canopy admin) can rename it.
26
+
27
+ ## What changes
28
+
29
+ - **The address.** The project is now at `my-new-name.canopy.website`. The old
30
+ address sends visitors there, keeping the rest of the link
31
+ (`old-name.canopy.website/reports/42` opens `my-new-name.canopy.website/reports/42`).
32
+ The same goes for `staging-dot-` and pinned `v12-dot-` addresses.
33
+ - **The email sender.** Mail your project sends comes from `my-new-name@` instead
34
+ of `old-name@`.
35
+ - **Client-password visitors sign in again** on the new address. Their sign-in
36
+ belonged to the old one.
37
+ - **Browser storage starts empty** on the new address. Anything your app keeps
38
+ in the visitor's browser (localStorage, IndexedDB) stays with the old one.
39
+ - **New report keys** use the new slug: `my-new-name-18`.
40
+
41
+ ## What keeps working
42
+
43
+ The old slug stays with your project. Nobody else can create a project with it.
44
+
45
+ - Old links and bookmarks redirect, as above. Pages that were already open keep
46
+ working until they're reloaded.
47
+ - `canopy.json` files, CI deploy tokens, scripts and MCP client configs that
48
+ still say the old slug keep working. The CLI updates a `canopy.json` it finds
49
+ with the old slug, and tells you when anything else should be updated.
50
+ - Your backend keeps running, and the rename updates the `PROJECT_SLUG` it
51
+ sees. If that update fails, Settings says so and has a retry button; Canopy
52
+ also retries on its own, and the next deploy updates it too.
53
+ - Report keys with the old slug (`old-name-17`) still open the report.
54
+ - Your Slack app keeps receiving events and slash commands.
55
+ - `git push` and `git pull` keep working. The repository itself keeps its name.
56
+ - Database, files, deploys, schedules, secrets and members don't change.
57
+
58
+ ## Limits
59
+
60
+ - A project can be renamed **once a day**.
61
+ - A project keeps at most **5 old slugs**. Renaming back to one of them is always
62
+ allowed, and doesn't count as a new one.
63
+ - The new slug follows the same rules as a new project: 2–40 lowercase letters,
64
+ digits and dashes, starting with a letter.
65
+ - Canopy's own projects (`dashboard`, `www`, `docs`) can't be renamed.
@@ -1,8 +1,8 @@
1
- import { t as version } from "./package-SWgTmctH.mjs";
2
- import { n as siteConfig } from "./site-DM2J10CB.mjs";
1
+ import { t as version } from "./package-CVDpzGgA.mjs";
2
+ import { r as siteConfig } from "./site-0i7wAJqg.mjs";
3
3
  import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
4
- import { n as agentHeaders } from "./api-BHzCvDcY.mjs";
5
- import { r as reportErrors } from "./ui-SBUdC9rX.mjs";
4
+ import { n as agentHeaders } from "./api-Do9c8tZ_.mjs";
5
+ import { r as reportErrors } from "./ui-CDUJq-X-.mjs";
6
6
  import { defineCommand } from "citty";
7
7
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
8
8
  import { homedir } from "node:os";
@@ -1,6 +1,6 @@
1
- import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
1
+ import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  import * as p from "@clack/prompts";
6
6
  //#region src/egress.ts
@@ -1,7 +1,7 @@
1
- import { t as version } from "./package-SWgTmctH.mjs";
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 apiRequest, c as credentials, i as apiDownload, o as apiUpload, s as commonHeaders } from "./api-BHzCvDcY.mjs";
4
- import { r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
1
+ import { t as version } from "./package-CVDpzGgA.mjs";
2
+ import { f as tryReadCanopyJson, l as resolveProject, o as projectDirArgs, p as tryResolveProject, r as siteConfig } from "./site-0i7wAJqg.mjs";
3
+ import { a as apiRequest, c as credentials, i as apiDownload, o as apiUpload, s as commonHeaders } from "./api-Do9c8tZ_.mjs";
4
+ import { r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.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";
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "./package-SWgTmctH.mjs";
3
- import { r as siteHeaderLine, t as setInvocationProfile } from "./site-DM2J10CB.mjs";
2
+ import { t as version } from "./package-CVDpzGgA.mjs";
3
+ import { i as siteHeaderLine, n as setInvocationProfile } from "./site-0i7wAJqg.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
@@ -63,36 +63,37 @@ const main = defineCommand({
63
63
  description: "canopy — create and deploy vibecoded projects"
64
64
  },
65
65
  subCommands: {
66
- login: () => import("./login-CKNFCp7c.mjs").then((m) => m.loginCommand),
67
- whoami: () => import("./login-CKNFCp7c.mjs").then((m) => m.whoamiCommand),
68
- logout: () => import("./login-CKNFCp7c.mjs").then((m) => m.logoutCommand),
69
- create: () => import("./projects-naWQ89Qj.mjs").then((m) => m.createCommand),
70
- init: () => import("./projects-naWQ89Qj.mjs").then((m) => m.initCommand),
71
- projects: () => import("./projects-naWQ89Qj.mjs").then((m) => m.projectsCommand),
72
- delete: () => import("./projects-naWQ89Qj.mjs").then((m) => m.deleteCommand),
73
- dev: () => import("./dev-D38FQ2LD.mjs").then((m) => m.devCommand),
74
- deploy: () => import("./deploy-6rYq1Iq8.mjs").then((m) => m.deployCommand),
75
- promote: () => import("./deploy-6rYq1Iq8.mjs").then((m) => m.promoteCommand),
76
- staging: () => import("./staging-CYMTZyyM.mjs").then((m) => m.stagingCommand),
77
- rollback: () => import("./deploy-6rYq1Iq8.mjs").then((m) => m.rollbackCommand),
78
- download: () => import("./deploy-6rYq1Iq8.mjs").then((m) => m.downloadCommand),
79
- token: () => import("./token-CCpJ1ES4.mjs").then((m) => m.tokenCommand),
80
- egress: () => import("./egress-CCpOsYpS.mjs").then((m) => m.egressCommand),
81
- secrets: () => import("./secrets-COwRvCev.mjs").then((m) => m.secretsCommand),
82
- schedule: () => import("./schedule-DjHcaxyt.mjs").then((m) => m.scheduleCommand),
83
- feedback: () => import("./feedback-CdjtPumG.mjs").then((m) => m.feedbackCommand),
84
- db: () => import("./db-5LfBG6N7.mjs").then((m) => m.dbCommand),
85
- usage: () => import("./usage-BSPnsMXd.mjs").then((m) => m.usageCommand),
86
- ai: () => import("./ai-PXXMv7kO.mjs").then((m) => m.aiCommand),
87
- open: () => import("./open-BPYozPQo.mjs").then((m) => m.openCommand),
88
- browse: () => import("./browse-DRz1DowL.mjs").then((m) => m.browseCommand),
89
- docs: () => import("./docs-B9xGnLSR.mjs").then((m) => m.docsCommand),
90
- skill: () => import("./skill-DSYQA4Ls.mjs").then((m) => m.skillCommand),
91
- upgrade: () => import("./upgrade-B_tLCpc4.mjs").then((m) => m.upgradeCommand),
92
- logs: () => import("./logs-D3WakFME.mjs").then((m) => m.logsCommand),
93
- mcp: () => import("./mcp-CoVS5Mj3.mjs").then((m) => m.mcpCommand),
94
- repo: () => import("./repo-Un_p617X.mjs").then((m) => m.repoCommand),
95
- template: () => import("./project-template-CMgD5hnV.mjs").then((m) => m.templateCommand)
66
+ login: () => import("./login-CeNvHLUv.mjs").then((m) => m.loginCommand),
67
+ whoami: () => import("./login-CeNvHLUv.mjs").then((m) => m.whoamiCommand),
68
+ logout: () => import("./login-CeNvHLUv.mjs").then((m) => m.logoutCommand),
69
+ create: () => import("./projects-BWTIk80a.mjs").then((m) => m.createCommand),
70
+ init: () => import("./projects-BWTIk80a.mjs").then((m) => m.initCommand),
71
+ projects: () => import("./projects-BWTIk80a.mjs").then((m) => m.projectsCommand),
72
+ delete: () => import("./projects-BWTIk80a.mjs").then((m) => m.deleteCommand),
73
+ rename: () => import("./rename-BdIFrqvG.mjs").then((m) => m.renameCommand),
74
+ dev: () => import("./dev-Dj3wFKHe.mjs").then((m) => m.devCommand),
75
+ deploy: () => import("./deploy-CXMLABvH.mjs").then((m) => m.deployCommand),
76
+ promote: () => import("./deploy-CXMLABvH.mjs").then((m) => m.promoteCommand),
77
+ staging: () => import("./staging-ynPlMiHW.mjs").then((m) => m.stagingCommand),
78
+ rollback: () => import("./deploy-CXMLABvH.mjs").then((m) => m.rollbackCommand),
79
+ download: () => import("./deploy-CXMLABvH.mjs").then((m) => m.downloadCommand),
80
+ token: () => import("./token-CxiPrKn_.mjs").then((m) => m.tokenCommand),
81
+ egress: () => import("./egress-D6qyaaAn.mjs").then((m) => m.egressCommand),
82
+ secrets: () => import("./secrets-BVx8L0Gj.mjs").then((m) => m.secretsCommand),
83
+ schedule: () => import("./schedule-DpMIpCGC.mjs").then((m) => m.scheduleCommand),
84
+ feedback: () => import("./feedback-DMgUUl0V.mjs").then((m) => m.feedbackCommand),
85
+ db: () => import("./db-BNsX7sXo.mjs").then((m) => m.dbCommand),
86
+ usage: () => import("./usage-BtPsb7Iu.mjs").then((m) => m.usageCommand),
87
+ ai: () => import("./ai-DpQ1nLka.mjs").then((m) => m.aiCommand),
88
+ open: () => import("./open-D71Ms1nG.mjs").then((m) => m.openCommand),
89
+ browse: () => import("./browse-Dp5HytqK.mjs").then((m) => m.browseCommand),
90
+ docs: () => import("./docs-CkJGcCpR.mjs").then((m) => m.docsCommand),
91
+ skill: () => import("./skill-D58yV6K8.mjs").then((m) => m.skillCommand),
92
+ upgrade: () => import("./upgrade-Dll5rhOQ.mjs").then((m) => m.upgradeCommand),
93
+ logs: () => import("./logs-BzgJfWNP.mjs").then((m) => m.logsCommand),
94
+ mcp: () => import("./mcp-DFLaXiY_.mjs").then((m) => m.mcpCommand),
95
+ repo: () => import("./repo-JBKJqyoS.mjs").then((m) => m.repoCommand),
96
+ template: () => import("./project-template-BbmSmraT.mjs").then((m) => m.templateCommand)
96
97
  }
97
98
  });
98
99
  const quiet = process.argv[2] === "mcp" && process.argv[3] === "serve" || process.argv[2] === "repo" && process.argv[3] === "credential";
@@ -1,6 +1,6 @@
1
- import { r as apiDataRequest } from "./api-BHzCvDcY.mjs";
1
+ import { r as apiDataRequest } from "./api-Do9c8tZ_.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-RUf3Gfuh.mjs";
3
- import { a as rewriteApiPath } from "./dev-D_gyIeUq.mjs";
3
+ import { a as rewriteApiPath } from "./dev-CPR-9slK.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,6 +1,6 @@
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 agentHeaders } from "./api-BHzCvDcY.mjs";
3
- import { n as openBrowser, r as reportErrors } from "./ui-SBUdC9rX.mjs";
1
+ import { _ as setActiveProfile, b as getBakedProfile, g as readConfigFile, h as listBakedProfiles, m as clearAllProfileTokens, r as siteConfig, v as upsertProfileAuth, x as isProfileId, y as writeConfigFile } from "./site-0i7wAJqg.mjs";
2
+ import { n as agentHeaders } from "./api-Do9c8tZ_.mjs";
3
+ import { n as openBrowser, r as reportErrors } from "./ui-CDUJq-X-.mjs";
4
4
  import { n as clearRepoTokenCache } from "./repo-credential-CNDRM-tG.mjs";
5
5
  import { defineCommand } from "citty";
6
6
  import { hostname } from "node:os";
@@ -1,6 +1,6 @@
1
- import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
- import { r as reportErrors } from "./ui-SBUdC9rX.mjs";
1
+ import { l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
3
+ import { r as reportErrors } from "./ui-CDUJq-X-.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-SWgTmctH.mjs";
2
- import { d as tryResolveProject, l as stagingUrl, n as siteConfig, s as projectUrl } from "./site-DM2J10CB.mjs";
3
- import { a as apiRequest, t as ApiError } from "./api-BHzCvDcY.mjs";
1
+ import { t as version } from "./package-CVDpzGgA.mjs";
2
+ import { c as projectUrl, d as stagingUrl, p as tryResolveProject, r as siteConfig } from "./site-0i7wAJqg.mjs";
3
+ import { a as apiRequest, d as noticeRenamesOnly, l as currentSlug, t as ApiError } from "./api-Do9c8tZ_.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";
@@ -342,15 +342,16 @@ async function connect(args) {
342
342
  requireSignIn();
343
343
  const tokens = agentTokens(slug, channel);
344
344
  await tokens.token();
345
+ const current = currentSlug(slug);
345
346
  const upstream = await connectUpstream({
346
- url: `${channel === "staging" ? stagingUrl(slug) : projectUrl(slug)}${MCP_PATH}`,
347
- project: slug,
347
+ url: `${channel === "staging" ? stagingUrl(current) : projectUrl(current)}${MCP_PATH}`,
348
+ project: current,
348
349
  tokens
349
350
  });
350
351
  const config = siteConfig();
351
352
  return {
352
353
  upstream,
353
- slug,
354
+ slug: current,
354
355
  channel,
355
356
  where: `${config.profileId ?? config.apiUrl}, ${channel}`
356
357
  };
@@ -375,6 +376,7 @@ const serveCommand = defineCommand({
375
376
  async run({ args }) {
376
377
  console.log = console.error;
377
378
  console.info = console.error;
379
+ noticeRenamesOnly();
378
380
  let connection;
379
381
  try {
380
382
  connection = await connect(args);
@@ -1,5 +1,5 @@
1
- import { u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
2
- import { r as apiDataRequest } from "./api-BHzCvDcY.mjs";
1
+ import { f as tryReadCanopyJson } from "./site-0i7wAJqg.mjs";
2
+ import { r as apiDataRequest } from "./api-Do9c8tZ_.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-DM2J10CB.mjs";
2
- import { n as openBrowser, r as reportErrors } from "./ui-SBUdC9rX.mjs";
1
+ import { c as projectUrl, d as stagingUrl, l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
2
+ import { n as openBrowser, r as reportErrors } from "./ui-CDUJq-X-.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  //#region src/open.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.7.41";
2
+ var version = "0.7.42";
3
3
  //#endregion
4
4
  export { version as t };
@@ -1,6 +1,6 @@
1
- import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-SBUdC9rX.mjs";
1
+ import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CDUJq-X-.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. */
@@ -0,0 +1,2 @@
1
+ import { a as templateCommand } from "./project-template-BXfHpsI8.mjs";
2
+ export { templateCommand };
@@ -1,8 +1,8 @@
1
- import { n as siteConfig, s as projectUrl, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-SBUdC9rX.mjs";
4
- import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-DACdMdpn.mjs";
5
- import { i as offerRepoOnCreate, n as cloneProjectRepo } from "./repo-BCua4L-I.mjs";
1
+ import { c as projectUrl, f as tryReadCanopyJson, r as siteConfig } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CDUJq-X-.mjs";
4
+ import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-BXfHpsI8.mjs";
5
+ import { i as offerRepoOnCreate, n as cloneProjectRepo } from "./repo-LSOxnmUk.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";
@@ -0,0 +1,81 @@
1
+ import { c as projectUrl, l as resolveProject, o as projectDirArgs, u as rewriteCanopyJsonSlug } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CDUJq-X-.mjs";
4
+ import { defineCommand } from "citty";
5
+ import * as p from "@clack/prompts";
6
+ //#region src/rename.ts
7
+ /**
8
+ * canopy rename <new-slug> — change a project's slug, its address on the
9
+ * site (OpenSpec project-slug-rename). The old slug stays the project's: its
10
+ * hostnames redirect, and everything that still names it (canopy.json
11
+ * elsewhere, CI, MCP configs, git remotes, report keys) keeps reaching the
12
+ * project. The canopy.json in this directory is pointed at the new slug.
13
+ */
14
+ /** What changes for people outside the platform — said before anything happens. */
15
+ function renameConsequences(from, to) {
16
+ return [
17
+ `The address becomes ${projectUrl(to)}; ${projectUrl(from)} redirects there.`,
18
+ `Email your project sends comes from ${to}@ instead of ${from}@.`,
19
+ "Visitors who signed in with the client password sign in again on the new address.",
20
+ "Visitors' browser storage for the project (localStorage, IndexedDB) starts empty on the new address.",
21
+ `Scripts, CI, MCP client configs and git remotes that say "${from}" keep working; update them when convenient.`
22
+ ];
23
+ }
24
+ const renameCommand = defineCommand({
25
+ meta: {
26
+ name: "rename",
27
+ description: "Change a project's slug (its address)"
28
+ },
29
+ args: {
30
+ slug: {
31
+ type: "positional",
32
+ description: "The new slug",
33
+ required: true
34
+ },
35
+ ...projectDirArgs,
36
+ yes: {
37
+ type: "boolean",
38
+ default: false,
39
+ description: "Skip the confirmation"
40
+ },
41
+ json: {
42
+ type: "boolean",
43
+ default: false,
44
+ description: "Print the result as JSON"
45
+ }
46
+ },
47
+ run: reportErrors("rename", async ({ args }) => {
48
+ const { dir, slug: from } = resolveProject(args);
49
+ const to = String(args.slug).trim().toLowerCase();
50
+ if (!args.yes) {
51
+ requireTty("pass --yes to rename without confirmation.");
52
+ p.note(renameConsequences(from, to).join("\n"), `Rename ${from} → ${to}`);
53
+ if (!unwrap(await p.confirm({ message: `Rename "${from}" to "${to}"?` }))) {
54
+ p.cancel("Aborted.");
55
+ return;
56
+ }
57
+ }
58
+ const result = await withContext(apiRequest("POST", `/projects/${from}/rename`, { slug: to }), {
59
+ action: `rename "${from}"`,
60
+ fixes: {
61
+ forbidden: "Only the project's owner or a Canopy admin can rename it — ask the owner.",
62
+ invalid_slug: "Use 2–40 lowercase letters, digits and dashes, starting with a letter — not a name Canopy reserves (www, docs, api, …).",
63
+ slug_taken: "Pick another slug: this one belongs to another project, now or in the past."
64
+ }
65
+ });
66
+ const updated = rewriteCanopyJsonSlug(dir, from, result.project.slug);
67
+ if (args.json) {
68
+ console.log(JSON.stringify({
69
+ ...result,
70
+ canopy_json_updated: updated
71
+ }, null, 2));
72
+ return;
73
+ }
74
+ p.log.success(`Renamed to ${result.project.slug}: ${projectUrl(result.project.slug)}`);
75
+ if (updated) p.log.info("Updated canopy.json.");
76
+ if (result.refresh.pending) p.log.warn("The backend still reports the old slug as PROJECT_SLUG (it keeps working). Canopy retries; `canopy deploy` or Settings → Retry refreshes it now.");
77
+ if (result.slack === "no_config_token" || result.slack === "rejected") p.log.info("The project's Slack app keeps its old URLs, which still work. They move on its next manifest update.");
78
+ })
79
+ });
80
+ //#endregion
81
+ export { renameCommand };
@@ -0,0 +1,2 @@
1
+ import { a as repoCommand } from "./repo-LSOxnmUk.mjs";
2
+ export { repoCommand };
@@ -1,6 +1,6 @@
1
- import { a as projectDirArgs, c as resolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext, n as agentHeaders } from "./api-BHzCvDcY.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
1
+ import { f as tryReadCanopyJson, l as resolveProject, o as projectDirArgs, r as siteConfig } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, d as noticeRenamesOnly, f as withContext, n as agentHeaders, u as followRename } from "./api-Do9c8tZ_.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.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";
@@ -328,6 +328,8 @@ async function mintRepoToken(profile, slug) {
328
328
  }
329
329
  }).catch(() => null);
330
330
  if (!res) throw new RepoCredentialError(`could not reach Canopy (${config.apiUrl}).`);
331
+ noticeRenamesOnly();
332
+ followRename(res, slug);
331
333
  if (res.status === 201) return await res.json();
332
334
  const body = await res.json().catch(() => ({}));
333
335
  if (res.status === 401) throw new RepoCredentialError(`your ${profile} sign-in has expired — ${login}.`);
@@ -1,6 +1,6 @@
1
- import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
- import { r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
1
+ import { l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
3
+ import { r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  //#region src/schedule.ts
6
6
  /**
@@ -1,6 +1,6 @@
1
- import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
- import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
1
+ import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
3
+ import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  import * as p from "@clack/prompts";
6
6
  //#region src/secrets.ts
@@ -198,6 +198,25 @@ function projectDialect(dir) {
198
198
  if (dialect !== "sqlite" && dialect !== "postgres") throw new Error(`canopy.json "dialect" must be "sqlite" or "postgres", not ${JSON.stringify(dialect)}.`);
199
199
  return dialect;
200
200
  }
201
+ /**
202
+ * Point canopy.json at a project's new slug after a rename (OpenSpec
203
+ * project-slug-rename) — only when it still names `from`, and changing
204
+ * nothing else in the file. True when it was rewritten.
205
+ */
206
+ function rewriteCanopyJsonSlug(dir, from, to) {
207
+ let config;
208
+ try {
209
+ config = tryReadCanopyJson(dir);
210
+ } catch {
211
+ return false;
212
+ }
213
+ if (config?.slug !== from) return false;
214
+ writeFileSync(join(dir, "canopy.json"), `${JSON.stringify({
215
+ ...config,
216
+ slug: to
217
+ }, null, 2)}\n`);
218
+ return true;
219
+ }
201
220
  function readCanopyJson(dir) {
202
221
  const config = tryReadCanopyJson(dir);
203
222
  if (config === void 0) throw new Error(`No canopy.json in ${dir} — run from a project created by \`canopy create\`, add one ({"slug": "<slug>", "dist": "dist"}), or pass --project <slug>.`);
@@ -255,6 +274,10 @@ function setInvocationProfile(profile) {
255
274
  function setProjectDirForPin(dir) {
256
275
  projectDirForPin = dir;
257
276
  }
277
+ /** The project directory this invocation resolved (cwd when none was named). */
278
+ function projectDir() {
279
+ return projectDirForPin ?? ".";
280
+ }
258
281
  function projectProfileFromCwd() {
259
282
  return tryReadCanopyJson(projectDirForPin ?? ".")?.profile;
260
283
  }
@@ -277,4 +300,4 @@ function siteHeaderLine(cliVersion) {
277
300
  return `canopy ${cliVersion} · ${c.envOverride || !c.profileId ? c.apiUrl : `${c.profileId} (${c.projectDomain ?? new URL(c.apiUrl).hostname})`}`;
278
301
  }
279
302
  //#endregion
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 };
303
+ export { setActiveProfile as _, projectDialect as a, getBakedProfile as b, projectUrl as c, stagingUrl as d, tryReadCanopyJson as f, readConfigFile as g, listBakedProfiles as h, siteHeaderLine as i, resolveProject as l, clearAllProfileTokens as m, setInvocationProfile as n, projectDirArgs as o, tryResolveProject as p, siteConfig as r, projectFlagArgs as s, projectDir as t, rewriteCanopyJsonSlug as u, upsertProfileAuth as v, isProfileId as x, writeConfigFile as y };
@@ -1,5 +1,5 @@
1
- import { t as version } from "./package-SWgTmctH.mjs";
2
- import { r as reportErrors } from "./ui-SBUdC9rX.mjs";
1
+ import { t as version } from "./package-CVDpzGgA.mjs";
2
+ import { r as reportErrors } from "./ui-CDUJq-X-.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
- import { c as resolveProject, l as stagingUrl, o as projectFlagArgs, s as projectUrl } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
1
+ import { c as projectUrl, d as stagingUrl, l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.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
- import { a as projectDirArgs, c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest } from "./api-BHzCvDcY.mjs";
3
- import { r as reportErrors, t as formatDateTime } from "./ui-SBUdC9rX.mjs";
1
+ import { l as resolveProject, o as projectDirArgs, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest } from "./api-Do9c8tZ_.mjs";
3
+ import { r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  const tokenCommand = defineCommand({
6
6
  meta: {
@@ -1,4 +1,4 @@
1
- import { t as ApiError } from "./api-BHzCvDcY.mjs";
1
+ import { t as ApiError } from "./api-Do9c8tZ_.mjs";
2
2
  import { spawn } from "node:child_process";
3
3
  import { cancel, isCancel } from "@clack/prompts";
4
4
  //#region src/ui.ts
@@ -1,5 +1,5 @@
1
1
  import { t as PACKAGE_NAME } from "./version-check-CcsA1QjI.mjs";
2
- import { r as reportErrors } from "./ui-SBUdC9rX.mjs";
2
+ import { r as reportErrors } from "./ui-CDUJq-X-.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-DSYQA4Ls.mjs");
115
+ const { skillInstalled } = await import("./skill-D58yV6K8.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
- import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
2
- import { a as apiRequest, l as withContext } from "./api-BHzCvDcY.mjs";
3
- import { r as reportErrors } from "./ui-SBUdC9rX.mjs";
1
+ import { l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
+ import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
3
+ import { r as reportErrors } from "./ui-CDUJq-X-.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  //#region src/usage.ts
6
6
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediamonks/canopy-cli",
3
- "version": "0.7.41",
3
+ "version": "0.7.42",
4
4
  "description": "Canopy CLI — create, develop and deploy Canopy projects.",
5
5
  "bin": {
6
6
  "canopy": "./dist/index.mjs"
@@ -1,2 +0,0 @@
1
- import { n as devCommand } from "./dev-D_gyIeUq.mjs";
2
- export { devCommand };
@@ -1,2 +0,0 @@
1
- import { a as templateCommand } from "./project-template-DACdMdpn.mjs";
2
- export { templateCommand };
@@ -1,2 +0,0 @@
1
- import { a as repoCommand } from "./repo-BCua4L-I.mjs";
2
- export { repoCommand };