@mediamonks/canopy-cli 0.7.36 → 0.7.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ai-DHLnyqfq.mjs → ai-CjvrcBIz.mjs} +2 -2
- package/dist/{api-C4rMhE7i.mjs → api-BPEYm9ts.mjs} +21 -0
- package/dist/{browse-Dycwj0V_.mjs → browse-CBelCgnW.mjs} +1 -1
- package/dist/{db-BB6O6085.mjs → db-CbqgjfYz.mjs} +159 -13
- package/dist/{deploy-Ka2lHkyy.mjs → deploy-CqCyry4o.mjs} +3 -3
- package/dist/{dev-BdXslHoA.mjs → dev-BtnEuAnS.mjs} +5 -5
- package/dist/dev-D_qzeLng.mjs +2 -0
- package/dist/docs/cli-create.md +2 -0
- package/dist/docs/cli-db.md +30 -1
- package/dist/docs/cli-projects.md +2 -0
- package/dist/docs/cli-secrets.md +2 -1
- package/dist/docs/cli-template.md +47 -0
- package/dist/docs/data.md +11 -0
- package/dist/docs/index.json +10 -0
- package/dist/docs/platform-project-data.md +23 -3
- package/dist/docs/templates.md +84 -0
- package/dist/{docs-Bb9hSk4h.mjs → docs-T1nuu00A.mjs} +2 -2
- package/dist/{egress-If_Gsx_M.mjs → egress-ButFsQ2N.mjs} +2 -2
- package/dist/{feedback-DzEKQlP6.mjs → feedback-C5NKCOXQ.mjs} +3 -3
- package/dist/index.mjs +31 -30
- package/dist/{local-Cyl39_Zu.mjs → local-BfqTheMP.mjs} +2 -2
- package/dist/{login-D0J5s_Ex.mjs → login-CtDWXICZ.mjs} +3 -3
- package/dist/{logs-DQQOawOG.mjs → logs-BltUi4Gp.mjs} +2 -2
- package/dist/{mcp-pNp-sbfJ.mjs → mcp-BwLRcfqz.mjs} +2 -2
- package/dist/{migrations-Czlcddyz.mjs → migrations-CF1iNZ7F.mjs} +1 -1
- package/dist/{open-DcaQKi1v.mjs → open-DXo60Ikf.mjs} +1 -1
- package/dist/{package-lYhex2Gp.mjs → package-CdDH35SE.mjs} +1 -1
- package/dist/project-template-BEr_tgIY.mjs +125 -0
- package/dist/project-template-DdT0dqpn.mjs +2 -0
- package/dist/{projects-DQAhCZm8.mjs → projects-Bm4oTodB.mjs} +74 -8
- package/dist/{repo-Bb9ntNa4.mjs → repo-CE22HpqZ.mjs} +18 -15
- package/dist/repo-DlYX_1tN.mjs +2 -0
- package/dist/{schedule-C5lMf3ny.mjs → schedule-DdMq5eXC.mjs} +2 -2
- package/dist/{secrets-BSEjZnxg.mjs → secrets-BOKuWBYV.mjs} +6 -5
- package/dist/{skill-BUA2uRR2.mjs → skill-3wOwhzDx.mjs} +2 -2
- package/dist/{staging-BZVty57b.mjs → staging-CU4QrzFX.mjs} +2 -2
- package/dist/{token-HHlUR0CF.mjs → token-BWnLsWLg.mjs} +2 -2
- package/dist/{ui-C_jSc1Kz.mjs → ui-CHfKV6lG.mjs} +1 -1
- package/dist/{upgrade-ZSb56qBJ.mjs → upgrade-BJ2dWjCG.mjs} +2 -2
- package/dist/{usage-CUYrWxv0.mjs → usage-Cqqvnzrf.mjs} +2 -2
- package/package.json +1 -1
- package/dist/dev-BeFX_wc5.mjs +0 -2
- package/dist/repo-1RQ1vBMv.mjs +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as apiRequest, o as withContext } from "./api-
|
|
2
|
-
import { r as reportErrors } from "./ui-
|
|
1
|
+
import { i as apiRequest, o as withContext } from "./api-BPEYm9ts.mjs";
|
|
2
|
+
import { r as reportErrors } from "./ui-CHfKV6lG.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/ai.ts
|
|
5
5
|
/**
|
|
@@ -92,6 +92,26 @@ const ERROR_COPY = {
|
|
|
92
92
|
why: "the description is invalid (at most 1000 characters)",
|
|
93
93
|
fix: "Shorten the description to 1000 characters or fewer."
|
|
94
94
|
},
|
|
95
|
+
user_session_required: {
|
|
96
|
+
why: "this needs your own sign-in, and a project-scoped token (dev, agent, worker, deploy) was used",
|
|
97
|
+
fix: "Run it yourself after `canopy login`, or use the dashboard."
|
|
98
|
+
},
|
|
99
|
+
invalid_retention: {
|
|
100
|
+
why: "retention must be a whole number of days from 1 to 365, or off",
|
|
101
|
+
fix: "Pass e.g. `--retention 30`, or `--retention off` to keep snapshots until deleted."
|
|
102
|
+
},
|
|
103
|
+
retention_shorter_than_schedule: {
|
|
104
|
+
why: "retention is shorter than the schedule, so some days would have no snapshot at all",
|
|
105
|
+
fix: "Use at least 1 day for a daily schedule and 7 for a weekly one."
|
|
106
|
+
},
|
|
107
|
+
invalid_schedule: {
|
|
108
|
+
why: "the schedule must be off, daily or weekly",
|
|
109
|
+
fix: "Pass `--schedule off`, `--schedule daily` or `--schedule weekly`."
|
|
110
|
+
},
|
|
111
|
+
invalid_retention_scope: {
|
|
112
|
+
why: "the retention scope must be all or scheduled",
|
|
113
|
+
fix: "Pass `--retention-scope all` (every snapshot) or `--retention-scope scheduled`."
|
|
114
|
+
},
|
|
95
115
|
snapshot_not_found: {
|
|
96
116
|
why: "no snapshot with that id exists for this project",
|
|
97
117
|
fix: "Run `canopy db snapshots` to list available snapshots."
|
|
@@ -335,6 +355,7 @@ async function apiDataRequest(method, path, projectSlug, body, rawContentType, c
|
|
|
335
355
|
body: body === void 0 ? void 0 : rawContentType !== void 0 ? body : JSON.stringify(body)
|
|
336
356
|
});
|
|
337
357
|
if (!response.ok) return fail(response, isDeployToken, method, path);
|
|
358
|
+
if (response.status === 204) return void 0;
|
|
338
359
|
return await response.json();
|
|
339
360
|
}
|
|
340
361
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as tryResolveProject, n as siteConfig } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { i as apiRequest, t as ApiError } from "./api-
|
|
2
|
+
import { i as apiRequest, t as ApiError } from "./api-BPEYm9ts.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 { i as apiRequest, n as apiDataRequest, o as withContext } from "./api-
|
|
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 { i as apiRequest, n as apiDataRequest, o as withContext } from "./api-BPEYm9ts.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CHfKV6lG.mjs";
|
|
4
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-CF1iNZ7F.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
7
7
|
import * as p from "@clack/prompts";
|
|
@@ -12,7 +12,8 @@ import * as p from "@clack/prompts";
|
|
|
12
12
|
*
|
|
13
13
|
* pull download a fresh SQL dump of the project database
|
|
14
14
|
* snapshot store a dump server-side (restorable from CLI or dashboard)
|
|
15
|
-
* snapshots list stored snapshots
|
|
15
|
+
* snapshots list stored snapshots; `snapshots delete` and `snapshots settings`
|
|
16
|
+
* (schedule and retention, OpenSpec snapshot-schedules)
|
|
16
17
|
* restore replace ALL project data from a snapshot or a local .sql file
|
|
17
18
|
* migrate apply pending schema migrations (docs/13-migrations-and-drizzle.md)
|
|
18
19
|
* access show or set what the app's pages may do with the database (docs/35)
|
|
@@ -84,25 +85,170 @@ const snapshotCommand = defineCommand({
|
|
|
84
85
|
console.log(`Restore anytime with \`canopy db restore --snapshot ${created.key}\`.`);
|
|
85
86
|
})
|
|
86
87
|
});
|
|
87
|
-
|
|
88
|
+
/** `--retention`: a whole number of days, or off. Range is the server's call. */
|
|
89
|
+
function parseRetention(value) {
|
|
90
|
+
const v = value.trim().toLowerCase();
|
|
91
|
+
if (v === "off" || v === "none") return null;
|
|
92
|
+
if (!/^\d+$/.test(v)) throw new Error(`--retention takes a number of days or "off", not "${value}".`);
|
|
93
|
+
return Number(v);
|
|
94
|
+
}
|
|
95
|
+
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
96
|
+
/** One line on when scheduled snapshots happen, e.g. "daily at 03:00 UTC". */
|
|
97
|
+
function describeSchedule(settings) {
|
|
98
|
+
if (settings.schedule === "off") return "off";
|
|
99
|
+
const time = `${String(settings.slot.hour).padStart(2, "0")}:00 UTC`;
|
|
100
|
+
return settings.schedule === "weekly" && settings.slot.weekday ? `weekly on ${capitalize(settings.slot.weekday)} at ${time}` : `daily at ${time}`;
|
|
101
|
+
}
|
|
102
|
+
function describeRetention(settings) {
|
|
103
|
+
if (settings.retentionDays === null) return "off (snapshots are kept until deleted)";
|
|
104
|
+
const days = `${settings.retentionDays} day${settings.retentionDays === 1 ? "" : "s"}`;
|
|
105
|
+
return settings.retentionScope === "scheduled" ? `${days}, scheduled snapshots only` : `${days}, all snapshots (manual, scheduled, pre-migrate, pre-promote)`;
|
|
106
|
+
}
|
|
107
|
+
function describeLastRun(lastRun) {
|
|
108
|
+
if (!lastRun) return "never";
|
|
109
|
+
return lastRun.ok ? `${lastRun.at} — stored ${lastRun.detail ?? ""}`.trimEnd() : `${lastRun.at} — FAILED: ${lastRun.detail ?? "unknown error"}`;
|
|
110
|
+
}
|
|
111
|
+
function printSettings(slug, settings) {
|
|
112
|
+
console.log(`${slug} snapshot settings`);
|
|
113
|
+
console.log(` schedule ${describeSchedule(settings)}`);
|
|
114
|
+
console.log(` retention ${describeRetention(settings)}`);
|
|
115
|
+
if (settings.schedule !== "off" || settings.lastRun) console.log(` last run ${describeLastRun(settings.lastRun)}`);
|
|
116
|
+
}
|
|
117
|
+
let snapshotsSubcommandRan = false;
|
|
118
|
+
const snapshotsDeleteCommand = defineCommand({
|
|
88
119
|
meta: {
|
|
89
|
-
name: "
|
|
90
|
-
description: "
|
|
120
|
+
name: "delete",
|
|
121
|
+
description: "Delete a stored snapshot for good"
|
|
91
122
|
},
|
|
92
123
|
args: {
|
|
93
124
|
...projectFlagArgs,
|
|
94
|
-
...stagingFlagArgs
|
|
125
|
+
...stagingFlagArgs,
|
|
126
|
+
key: {
|
|
127
|
+
type: "positional",
|
|
128
|
+
description: "Snapshot key (from `canopy db snapshots`)",
|
|
129
|
+
required: true
|
|
130
|
+
},
|
|
131
|
+
yes: {
|
|
132
|
+
type: "boolean",
|
|
133
|
+
default: false,
|
|
134
|
+
description: "Skip the confirmation"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
run: reportErrors("db snapshots delete", async ({ args }) => {
|
|
138
|
+
snapshotsSubcommandRan = true;
|
|
139
|
+
const { slug } = resolveProject(args);
|
|
140
|
+
if (!args.yes) {
|
|
141
|
+
requireTty("pass --yes to delete without confirmation.");
|
|
142
|
+
if (!unwrap(await p.confirm({
|
|
143
|
+
message: `Delete snapshot ${args.key} of "${slug}"${channelNote(args)} for good? It can't be restored afterwards.`,
|
|
144
|
+
initialValue: false
|
|
145
|
+
}))) {
|
|
146
|
+
p.cancel("Aborted.");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await withContext(apiDataRequest("DELETE", `/db/snapshots/${encodeURIComponent(args.key)}`, slug, void 0, void 0, channelOf(args)), { action: `delete snapshot ${args.key} of "${slug}"` });
|
|
151
|
+
p.log.success(`Deleted ${args.key}${channelNote(args)}.`);
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
const snapshotsSettingsCommand = defineCommand({
|
|
155
|
+
meta: {
|
|
156
|
+
name: "settings",
|
|
157
|
+
description: "Show or change the snapshot schedule and retention"
|
|
158
|
+
},
|
|
159
|
+
args: {
|
|
160
|
+
...projectFlagArgs,
|
|
161
|
+
schedule: {
|
|
162
|
+
type: "string",
|
|
163
|
+
description: "off | daily | weekly"
|
|
164
|
+
},
|
|
165
|
+
retention: {
|
|
166
|
+
type: "string",
|
|
167
|
+
description: "Delete snapshots older than this many days (1–365), or off"
|
|
168
|
+
},
|
|
169
|
+
"retention-scope": {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Which snapshots retention deletes: all (default) | scheduled"
|
|
172
|
+
},
|
|
173
|
+
yes: {
|
|
174
|
+
type: "boolean",
|
|
175
|
+
default: false,
|
|
176
|
+
description: "Don't ask before a retention change that deletes snapshots"
|
|
177
|
+
},
|
|
178
|
+
json: {
|
|
179
|
+
type: "boolean",
|
|
180
|
+
default: false,
|
|
181
|
+
description: "Print the settings as JSON"
|
|
182
|
+
}
|
|
95
183
|
},
|
|
96
|
-
run: reportErrors("db snapshots", async ({ args }) => {
|
|
184
|
+
run: reportErrors("db snapshots settings", async ({ args }) => {
|
|
185
|
+
snapshotsSubcommandRan = true;
|
|
97
186
|
const { slug } = resolveProject(args);
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
187
|
+
const path = `/projects/${slug}/snapshot-settings`;
|
|
188
|
+
const change = {};
|
|
189
|
+
if (args.schedule !== void 0) change.schedule = args.schedule.trim().toLowerCase();
|
|
190
|
+
if (args.retention !== void 0) change.retentionDays = parseRetention(args.retention);
|
|
191
|
+
if (args["retention-scope"] !== void 0) change.retentionScope = args["retention-scope"].trim().toLowerCase();
|
|
192
|
+
if (Object.keys(change).length === 0) {
|
|
193
|
+
const settings = await withContext(apiRequest("GET", path), { action: `read the snapshot settings of "${slug}"` });
|
|
194
|
+
if (args.json) console.log(JSON.stringify(settings, null, 2));
|
|
195
|
+
else printSettings(slug, settings);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const context = { action: `change the snapshot settings of "${slug}"` };
|
|
199
|
+
const preview = await withContext(apiRequest("PUT", path, {
|
|
200
|
+
...change,
|
|
201
|
+
dryRun: true
|
|
202
|
+
}), context);
|
|
203
|
+
if ((preview.wouldExpire ?? 0) > 0 && !args.yes) {
|
|
204
|
+
requireTty("pass --yes to apply a retention that deletes existing snapshots.");
|
|
205
|
+
const n = preview.wouldExpire;
|
|
206
|
+
if (!unwrap(await p.confirm({
|
|
207
|
+
message: `This deletes ${n} existing snapshot${n === 1 ? "" : "s"} of "${slug}" within the hour. Continue?`,
|
|
208
|
+
initialValue: false
|
|
209
|
+
}))) {
|
|
210
|
+
p.cancel("Aborted.");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const settings = await withContext(apiRequest("PUT", path, change), context);
|
|
215
|
+
if (args.json) {
|
|
216
|
+
console.log(JSON.stringify(settings, null, 2));
|
|
101
217
|
return;
|
|
102
218
|
}
|
|
103
|
-
|
|
219
|
+
printSettings(slug, settings);
|
|
220
|
+
if ((settings.wouldExpire ?? 0) > 0) console.log(`\n${settings.wouldExpire} snapshot(s) past retention will be deleted within the hour.`);
|
|
104
221
|
})
|
|
105
222
|
});
|
|
223
|
+
const listSnapshotsRun = reportErrors("db snapshots", async ({ args }) => {
|
|
224
|
+
const { slug } = resolveProject(args);
|
|
225
|
+
const [{ snapshots }, settings] = await Promise.all([apiDataRequest("GET", "/db/snapshots", slug, void 0, void 0, channelOf(args)), apiRequest("GET", `/projects/${slug}/snapshot-settings`).catch(() => null)]);
|
|
226
|
+
if (settings) {
|
|
227
|
+
console.log(`Schedule: ${describeSchedule(settings)} · retention: ${describeRetention(settings)}`);
|
|
228
|
+
if (settings.lastRun) console.log(`Last scheduled run: ${describeLastRun(settings.lastRun)}`);
|
|
229
|
+
console.log("");
|
|
230
|
+
}
|
|
231
|
+
if (snapshots.length === 0) {
|
|
232
|
+
console.log(`No snapshots for ${slug}${channelNote(args)} yet — run \`canopy db snapshot\`.`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
for (const snapshot of snapshots) console.log(`${snapshot.key} ${(snapshot.kind ?? "").padEnd(11)} ${formatSize(snapshot.size).padStart(9)} ${snapshot.createdAt}`);
|
|
236
|
+
});
|
|
237
|
+
const snapshotsCommand = defineCommand({
|
|
238
|
+
meta: {
|
|
239
|
+
name: "snapshots",
|
|
240
|
+
description: "List stored snapshots (and delete, settings)"
|
|
241
|
+
},
|
|
242
|
+
args: {
|
|
243
|
+
...projectFlagArgs,
|
|
244
|
+
...stagingFlagArgs
|
|
245
|
+
},
|
|
246
|
+
subCommands: {
|
|
247
|
+
delete: snapshotsDeleteCommand,
|
|
248
|
+
settings: snapshotsSettingsCommand
|
|
249
|
+
},
|
|
250
|
+
run: (context) => snapshotsSubcommandRan ? void 0 : listSnapshotsRun(context)
|
|
251
|
+
});
|
|
106
252
|
const restoreCommand = defineCommand({
|
|
107
253
|
meta: {
|
|
108
254
|
name: "restore",
|
|
@@ -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 { i as apiRequest, n as apiDataRequest, o as withContext, r as apiDownload } from "./api-
|
|
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 { i as apiRequest, n as apiDataRequest, o as withContext, r as apiDownload } from "./api-BPEYm9ts.mjs";
|
|
3
|
+
import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-CHfKV6lG.mjs";
|
|
4
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-CF1iNZ7F.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 { i as apiRequest, o as withContext, t as ApiError } from "./api-
|
|
3
|
-
import { r as reportErrors, t as formatDateTime } from "./ui-
|
|
4
|
-
import { r as readMigrationFiles } from "./migrations-
|
|
2
|
+
import { i as apiRequest, o as withContext, t as ApiError } from "./api-BPEYm9ts.mjs";
|
|
3
|
+
import { r as reportErrors, t as formatDateTime } from "./ui-CHfKV6lG.mjs";
|
|
4
|
+
import { r as readMigrationFiles } from "./migrations-CF1iNZ7F.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-BfqTheMP.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-BfqTheMP.mjs");
|
|
271
271
|
const sandbox = await createSandbox({
|
|
272
272
|
slug,
|
|
273
273
|
persistDir: join(flags.dir, ".canopy", "local"),
|
package/dist/docs/cli-create.md
CHANGED
|
@@ -11,6 +11,7 @@ description: Register a new project on Canopy and scaffold it locally from a sta
|
|
|
11
11
|
canopy create # fully interactive
|
|
12
12
|
canopy create "My Portfolio" --slug my-portfolio --template spa
|
|
13
13
|
canopy create existing-app --no-scaffold # register only, no files written
|
|
14
|
+
canopy create "My Retro" --from retro-board # start from a template
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
## Arguments & flags
|
|
@@ -20,6 +21,7 @@ canopy create existing-app --no-scaffold # register only, no files written
|
|
|
20
21
|
- `--template <id>` — starter to scaffold: `static` (plain HTML/CSS/JS, no build step, untyped SDK from the platform — simplest apps), `spa` (Vite + React + TypeScript with the typed SDK from npm — best for real SDK use or growth), or `worker` (`spa` plus a backend worker in `worker/` — for server-side code: secrets, schedules, calling external APIs). A picker appears when omitted on a TTY; `static` is the default otherwise.
|
|
21
22
|
- `--scaffold` / `--no-scaffold` — whether to write `./<slug>/` from the template. When neither is given, the CLI scaffolds unless the current directory already has a `canopy.json` (in which case it assumes you're registering an existing app and only registers).
|
|
22
23
|
- `--repo` / `--no-repo` — also create a git repository for the project on Canopy and set up the new folder to push to it (see [`canopy repo`](https://docs.canopy.website/cli/repo)). When neither is given, an interactive run asks (default no) on sites that offer repositories; non-interactive runs create none.
|
|
24
|
+
- `--from <template>` — start from a template project instead of a starter: the new project gets a copy of the template's code in its own git repository, cloned into `./<slug>/`, plus the setup the code needs. The CLI shows what comes along and asks before creating. `--template`, `--scaffold` and `--repo` don't apply. See [Start from a template](https://docs.canopy.website/guides/templates).
|
|
23
25
|
- `--profile <site>` — global flag (or `CANOPY_PROFILE`): which site to create the project on. The chosen profile is pinned into the scaffolded `canopy.json`.
|
|
24
26
|
|
|
25
27
|
## How it works
|
package/dist/docs/cli-db.md
CHANGED
|
@@ -13,6 +13,9 @@ The habit that makes restores safe: **always `canopy db snapshot` before `canopy
|
|
|
13
13
|
canopy db pull [--out <file>] [--project <slug>]
|
|
14
14
|
canopy db snapshot [--project <slug>]
|
|
15
15
|
canopy db snapshots [--project <slug>]
|
|
16
|
+
canopy db snapshots delete <key> [--yes] [--project <slug>]
|
|
17
|
+
canopy db snapshots settings [--schedule off|daily|weekly] [--retention <days>|off]
|
|
18
|
+
[--retention-scope all|scheduled] [--yes] [--json] [--project <slug>]
|
|
16
19
|
canopy db restore [file.sql] [--snapshot <key>] [--yes] [--project <slug>]
|
|
17
20
|
canopy db migrate [--plan] [--project <slug>]
|
|
18
21
|
```
|
|
@@ -37,7 +40,27 @@ Stores a dump **server-side**, restorable anytime from the CLI or the dashboard.
|
|
|
37
40
|
|
|
38
41
|
### canopy db snapshots
|
|
39
42
|
|
|
40
|
-
Lists stored snapshots for the project — one line per snapshot with its key, size, and creation time. If there are none yet, it points you at `canopy db snapshot`.
|
|
43
|
+
Lists stored snapshots for the project — one line per snapshot with its key, kind, size, and creation time — under a line with the project's schedule, retention and last scheduled run. The kind says what made it: `manual` (you), `scheduled` (Canopy, on the schedule), `pre-migrate` or `pre-promote` (Canopy, before applying migrations). If there are none yet, it points you at `canopy db snapshot`.
|
|
44
|
+
|
|
45
|
+
### canopy db snapshots delete
|
|
46
|
+
|
|
47
|
+
Deletes one stored snapshot for good: it can't be restored afterwards. Asks for confirmation unless `--yes` is passed. Use `--staging` for a snapshot in the staging history.
|
|
48
|
+
|
|
49
|
+
Deleting needs your own sign-in (`canopy login`). Project tokens, such as the ones `canopy dev` or an agent uses, can't delete snapshots.
|
|
50
|
+
|
|
51
|
+
### canopy db snapshots settings
|
|
52
|
+
|
|
53
|
+
Shows or changes when Canopy takes snapshots by itself and how long snapshots are kept. With no flags it prints the current settings.
|
|
54
|
+
|
|
55
|
+
- `--schedule off|daily|weekly` — Canopy snapshots the live database once a day or once a week, at a fixed time it picks for the project (shown in UTC). The staging database is not snapshotted on a schedule.
|
|
56
|
+
- `--retention <days>` — delete snapshots older than this many days (1 to 365), once a day. `--retention off` keeps snapshots until you delete them. Retention works with or without a schedule, and covers both the live and the staging history.
|
|
57
|
+
- `--retention-scope all|scheduled` — which snapshots retention deletes: `all` (the default) includes the ones you took by hand and the ones taken before migrations; `scheduled` only deletes the ones Canopy took on the schedule.
|
|
58
|
+
- `--yes` — when a new retention would delete existing snapshots, the CLI says how many and asks first. `--yes` skips that.
|
|
59
|
+
- `--json` — print the settings as JSON.
|
|
60
|
+
|
|
61
|
+
A daily schedule needs at least 1 day of retention and a weekly one at least 7, so there is never a stretch without a snapshot. Like deleting, changing the settings needs your own sign-in.
|
|
62
|
+
|
|
63
|
+
Canopy also keeps short-lived backups of its own databases and storage for disaster recovery. Those expire on their own within 30 days and can't be restored from the CLI.
|
|
41
64
|
|
|
42
65
|
### canopy db restore
|
|
43
66
|
|
|
@@ -73,6 +96,12 @@ canopy db snapshot && canopy db pull
|
|
|
73
96
|
|
|
74
97
|
Belt and braces before risky work: a server-side snapshot you can restore from the dashboard, plus a local dump on disk.
|
|
75
98
|
|
|
99
|
+
```bash
|
|
100
|
+
canopy db snapshots settings --schedule daily --retention 30
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
A daily backup, with snapshots older than 30 days deleted automatically.
|
|
104
|
+
|
|
76
105
|
```bash
|
|
77
106
|
canopy db restore --snapshot snap-2026-08-11-1200 --yes --project my-app
|
|
78
107
|
```
|
|
@@ -10,12 +10,14 @@ description: List your Canopy projects with their status, role, and live URLs.
|
|
|
10
10
|
```bash
|
|
11
11
|
canopy projects
|
|
12
12
|
canopy projects --all
|
|
13
|
+
canopy projects --templates
|
|
13
14
|
canopy --profile staging projects
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
## Arguments & flags
|
|
17
18
|
|
|
18
19
|
- `--all` — include projects you can't edit. By default the list shows only projects where you're an owner or editor (admins always see everything, since they can edit everything).
|
|
20
|
+
- `--templates` — list the templates anyone can start a project from, with their owners. See [Start from a template](https://docs.canopy.website/guides/templates).
|
|
19
21
|
- `--profile <site>` — global flag (or `CANOPY_PROFILE`): each site has its own project registry, so listing on `staging` shows a completely different set than `main`.
|
|
20
22
|
|
|
21
23
|
## Details
|
package/dist/docs/cli-secrets.md
CHANGED
|
@@ -25,7 +25,8 @@ target directory, or `--project <slug>` to override — and honor the global
|
|
|
25
25
|
`--profile <site>` / `CANOPY_PROFILE`.
|
|
26
26
|
|
|
27
27
|
- `list` — print the secret names and when each was last set. Values are
|
|
28
|
-
never shown.
|
|
28
|
+
never shown. For a project started from a template, names it still needs a
|
|
29
|
+
value for are listed as missing.
|
|
29
30
|
- `set <name>` — store a value. Names are `UPPER_SNAKE_CASE`, up to 64
|
|
30
31
|
characters. The value comes from **stdin** when piped (one trailing newline
|
|
31
32
|
is trimmed, so `echo` works too), from `--from-env <VAR>` to read an
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: canopy template
|
|
3
|
+
description: Offer a project as a template, or see what a template gives a new project.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`canopy template` controls whether others can start a project from yours, and
|
|
7
|
+
shows what a template gives a new project. To start from one, use
|
|
8
|
+
[`canopy create --from`](https://docs.canopy.website/cli/create).
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
canopy template mark [--yes]
|
|
14
|
+
canopy template unmark
|
|
15
|
+
canopy template show <slug>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Arguments & flags
|
|
19
|
+
|
|
20
|
+
`mark` and `unmark` resolve the project the usual way: `canopy.json` in the
|
|
21
|
+
target folder, or `--project <slug>`. All of them honour the global
|
|
22
|
+
`--profile <site>` / `CANOPY_PROFILE`.
|
|
23
|
+
|
|
24
|
+
- `mark` — let anyone on the site start a project from this one. Owner only.
|
|
25
|
+
The project needs a Canopy git repository with code pushed to it. Asks for
|
|
26
|
+
confirmation; `--yes` skips it in scripts. Warns when `canopy.json` names no
|
|
27
|
+
migrations folder, because new projects then start without any tables.
|
|
28
|
+
- `unmark` — stop new projects starting from this one. Existing copies are not
|
|
29
|
+
affected.
|
|
30
|
+
- `show <slug>` — what a new project gets from a template: the code, outbound
|
|
31
|
+
hosts, page settings, Google access, schedules (turned off) and secret names.
|
|
32
|
+
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
canopy template mark
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Offers the project in this folder as a template.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
canopy template show retro-board
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Shows what `canopy create --from retro-board` would give you.
|
|
46
|
+
|
|
47
|
+
Read [Start from a template](https://docs.canopy.website/guides/templates) for the bigger picture.
|
package/dist/docs/data.md
CHANGED
|
@@ -61,6 +61,17 @@ canopy db snapshots # list stored snapshots
|
|
|
61
61
|
Snapshots live on the platform and can be restored at any time — handy before a
|
|
62
62
|
risky change.
|
|
63
63
|
|
|
64
|
+
Canopy can also take them for you, and clean up old ones:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
canopy db snapshots settings --schedule daily --retention 30 # daily backup, keep 30 days
|
|
68
|
+
canopy db snapshots settings --retention-scope scheduled # never auto-delete your own
|
|
69
|
+
canopy db snapshots delete <key> # delete one for good
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The same settings are on the dashboard's Data tab. See
|
|
73
|
+
[`canopy db`](https://docs.canopy.website/cli/db#canopy-db-snapshots-settings) for every option.
|
|
74
|
+
|
|
64
75
|
## Restore — destructive
|
|
65
76
|
|
|
66
77
|
> **Warning:** Restoring **replaces ALL project data**. Take a snapshot first — a snapshot taken seconds earlier
|
package/dist/docs/index.json
CHANGED
|
@@ -72,6 +72,11 @@
|
|
|
72
72
|
"title": "Keep your code in git",
|
|
73
73
|
"summary": "Give your project a git repository on Canopy — history, backup and sharing, no GitHub account needed."
|
|
74
74
|
},
|
|
75
|
+
{
|
|
76
|
+
"slug": "templates",
|
|
77
|
+
"title": "Start from a template",
|
|
78
|
+
"summary": "Begin a project from someone else's working one — a copy of their code and setup, never their data."
|
|
79
|
+
},
|
|
75
80
|
{
|
|
76
81
|
"slug": "agent-tools",
|
|
77
82
|
"title": "Agent tools (MCP)",
|
|
@@ -266,6 +271,11 @@
|
|
|
266
271
|
"title": "canopy repo",
|
|
267
272
|
"summary": "Create, clone and set up the project's git repository on Canopy."
|
|
268
273
|
},
|
|
274
|
+
{
|
|
275
|
+
"slug": "cli-template",
|
|
276
|
+
"title": "canopy template",
|
|
277
|
+
"summary": "Offer a project as a template, or see what a template gives a new project."
|
|
278
|
+
},
|
|
269
279
|
{
|
|
270
280
|
"slug": "cli-egress",
|
|
271
281
|
"title": "canopy egress",
|
|
@@ -45,8 +45,28 @@ The Data tab's **Data safety** card is your undo button for data:
|
|
|
45
45
|
confirm.
|
|
46
46
|
- **Export** downloads a dump to your machine.
|
|
47
47
|
|
|
48
|
-
Snapshots live server-side
|
|
49
|
-
|
|
48
|
+
Snapshots live server-side, so you can restore from days ago, not just from
|
|
49
|
+
your last session. Each one shows what made it: you, the schedule, or Canopy
|
|
50
|
+
before applying migrations. You can delete one you no longer need; it's gone
|
|
51
|
+
for good.
|
|
52
|
+
|
|
53
|
+
## Scheduled snapshots and retention
|
|
54
|
+
|
|
55
|
+
In the same card you can have Canopy take a snapshot of the live database
|
|
56
|
+
**daily** or **weekly**, at a fixed time it picks for the project. You don't
|
|
57
|
+
need a worker or any code for it.
|
|
58
|
+
|
|
59
|
+
**Retention** deletes snapshots older than a number of days you choose, once a
|
|
60
|
+
day. Choose whether it applies to all snapshots or only the scheduled ones, so
|
|
61
|
+
your own restore points stay until you delete them. Retention also works
|
|
62
|
+
without a schedule. Without retention, snapshots stay until you delete them or
|
|
63
|
+
the project.
|
|
64
|
+
|
|
65
|
+
Changing these settings and deleting snapshots needs a project member signed
|
|
66
|
+
in as themselves.
|
|
67
|
+
|
|
68
|
+
Canopy also keeps short-lived backups of its own databases and storage for
|
|
69
|
+
disaster recovery. Those expire on their own within 30 days.
|
|
50
70
|
|
|
51
71
|
> **Warning:** Restoring replaces **all** current data with the snapshot's contents. Take a fresh snapshot first
|
|
52
72
|
> if there's anything recent you might want back.
|
|
@@ -58,7 +78,7 @@ Everything above is also available to you (and your coding agent) via the CLI:
|
|
|
58
78
|
```bash
|
|
59
79
|
canopy db pull # download a fresh SQL dump
|
|
60
80
|
canopy db snapshot # store a dump server-side
|
|
61
|
-
canopy db snapshots # list stored snapshots
|
|
81
|
+
canopy db snapshots # list stored snapshots (delete, settings)
|
|
62
82
|
canopy db restore # replace project data with a snapshot
|
|
63
83
|
canopy db access # what the app's own pages may do with the database
|
|
64
84
|
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Start from a template
|
|
3
|
+
description: Begin a project from someone else's working one — a copy of their code and setup, never their data.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Some projects are worth starting from: a retro board, a feedback form, a small
|
|
7
|
+
dashboard on an internal API. When the owner offers one as a **template**,
|
|
8
|
+
anyone on Canopy can start a project of their own from it. You get a copy of
|
|
9
|
+
the code and the setup the code needs, then change it however you like.
|
|
10
|
+
|
|
11
|
+
Your copy is yours. Later changes to the template don't reach it, and nothing
|
|
12
|
+
you do reaches the template.
|
|
13
|
+
|
|
14
|
+
## Find one
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
canopy projects --templates
|
|
18
|
+
canopy template show retro-board
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`show` lists exactly what a new project gets. In the dashboard, switch on the
|
|
22
|
+
**Templates** filter on the project list; each template has a **Use as
|
|
23
|
+
template** button that shows the same list.
|
|
24
|
+
|
|
25
|
+
## Start your project
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
canopy create "My Retro" --slug my-retro --from retro-board
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Canopy creates the project, gives it its own git repository with the
|
|
32
|
+
template's code in one fresh commit, and clones it into `./my-retro/`. It then
|
|
33
|
+
tells you what is left, usually:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd my-retro
|
|
37
|
+
canopy secrets set OPENAI_KEY
|
|
38
|
+
canopy deploy
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The first deploy runs the template's migrations, so your database starts with
|
|
42
|
+
its tables and nothing else.
|
|
43
|
+
|
|
44
|
+
## What you get, and what you don't
|
|
45
|
+
|
|
46
|
+
You get:
|
|
47
|
+
|
|
48
|
+
- the code in the template's repository, as it is right now, without its
|
|
49
|
+
history;
|
|
50
|
+
- its allowed outbound hosts and page settings;
|
|
51
|
+
- the Google access it asks visitors for (restricted Google access stays off
|
|
52
|
+
until a Canopy admin unlocks it for your project);
|
|
53
|
+
- its schedules, **turned off** — turn each one on when you're ready;
|
|
54
|
+
- the **names** of its secrets, so you know what to fill in. `canopy secrets
|
|
55
|
+
list` shows the ones still missing.
|
|
56
|
+
|
|
57
|
+
You never get its data, stored files, secret values, members, client password,
|
|
58
|
+
budgets, feedback or deploy history. Your project starts with the usual
|
|
59
|
+
defaults for those.
|
|
60
|
+
|
|
61
|
+
## Offer your project as a template
|
|
62
|
+
|
|
63
|
+
Your project needs a Canopy git repository with your code pushed to it (see
|
|
64
|
+
[Keep your code in git](https://docs.canopy.website/guides/git)). Then:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
canopy template mark
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
or **Settings → Template** in the dashboard. Only the owner can do this.
|
|
71
|
+
|
|
72
|
+
Before you do:
|
|
73
|
+
|
|
74
|
+
- **Everyone on the site can copy the code.** Make sure the repository holds no
|
|
75
|
+
keys, passwords or client data.
|
|
76
|
+
- **Put sample data in migrations.** The database never comes along, so any
|
|
77
|
+
starting rows belong in your migrations. Without a migrations folder, new
|
|
78
|
+
projects start with an empty database.
|
|
79
|
+
- **Explain setup in the README.** It's the first thing people read in their
|
|
80
|
+
copy.
|
|
81
|
+
|
|
82
|
+
`canopy template unmark` stops new copies. Projects already started from it
|
|
83
|
+
are not affected. The template's Settings page shows how many projects started
|
|
84
|
+
from it.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-CdDH35SE.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 { r as reportErrors } from "./ui-
|
|
4
|
+
import { r as reportErrors } from "./ui-CHfKV6lG.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { homedir } from "node:os";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { i as apiRequest, o as withContext } from "./api-
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { i as apiRequest, o as withContext } from "./api-BPEYm9ts.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CHfKV6lG.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-
|
|
1
|
+
import { t as version } from "./package-CdDH35SE.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 apiUpload, i as apiRequest, r as apiDownload } from "./api-
|
|
4
|
-
import { r as reportErrors, t as formatDateTime } from "./ui-
|
|
3
|
+
import { a as apiUpload, i as apiRequest, r as apiDownload } from "./api-BPEYm9ts.mjs";
|
|
4
|
+
import { r as reportErrors, t as formatDateTime } from "./ui-CHfKV6lG.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";
|