@kryd/cli 0.16.0 → 0.17.0

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 (3) hide show
  1. package/README.md +2 -3
  2. package/dist/index.js +297 -20
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -17,8 +17,7 @@ Requires Node.js ≥ 22 and git (the CLI shells out to git for `kryd init` and `
17
17
  ## Quickstart
18
18
 
19
19
  ```sh
20
- kryd login # sign in via the browser
21
- kryd init # link this folder to a Kryd project (+ a `kryd` git remote)
20
+ kryd init # link this folder to a Kryd project (+ a `kryd` git remote); not signed in? it asks, then signs you in via the browser
22
21
  kryd push # push the current branch → build → deploy → live, with live progress
23
22
  kryd logs # the full build log of the latest deploy (kryd push shows progress, not log lines)
24
23
  kryd logs <project> --runtime # tail the live app's stdout/stderr
@@ -33,7 +32,7 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
33
32
  |---|---|
34
33
  | `kryd login` / `logout` / `whoami` | Authenticate the CLI (browser flow); the token is stored in `~/.kryd`. `whoami` shows the account commands run in, your role there, and your other accounts. |
35
34
  | `kryd account list \| use <slug>` | You can belong to several accounts (your own, a customer's as a member). `list` shows them with your role in each, `*` marking the current one; `use` makes every following command run in that account. |
36
- | `kryd init` | Link the current repo to a Kryd project + register the deploy webhook + add a `kryd` git remote. |
35
+ | `kryd init` | Link the current repo to a Kryd project + register the deploy webhook + add a `kryd` git remote. Not logged in and at a terminal? It asks, then runs the same browser login as `kryd login`; without a terminal it refuses (a deploy token, `KRYD_TOKEN`, cannot create a project). |
37
36
  | `kryd push [branch]` | Push to the `kryd` remote and follow the deploy it triggers (defaults to the current branch). Shows which step the deploy is on, how long it has taken and a progress bar; `--logs` streams the full build log instead. |
38
37
  | `kryd deploy [project]` | Re-deploy the production-branch HEAD already on the forge — no new commit. |
39
38
  | `kryd logs [target]` | Follow a deploy's build/deploy log **in full** — this is where the build output lives (`--runtime` tails the live container instead; `--worker` tails the project's workflow worker, also when it has a web app). |
package/dist/index.js CHANGED
@@ -21,7 +21,11 @@ var ERROR_CODES = {
21
21
  // KRYD-516b: a request that names no account while the caller is in several (400), and a member
22
22
  // whose role lacks the route's permission (403, `details.permission` names it).
23
23
  accountRequired: "account.required",
24
- permissionRequired: "auth.permission_required"
24
+ permissionRequired: "auth.permission_required",
25
+ // KRYD-592: the analytics store (Umami's database) is unconfigured, down or too slow — a 503 the
26
+ // Analytics tab shows as its own error band while the rest of the dashboard carries on. (The spec
27
+ // wrote `analytics_unavailable`; dotted like every code here, which `api.test.ts` enforces.)
28
+ analyticsUnavailable: "analytics.unavailable"
25
29
  };
26
30
 
27
31
  // ../../packages/shared-types/dist/db-url.js
@@ -14737,6 +14741,12 @@ var accountUsageSchema = external_exports.object({
14737
14741
  storageBytes: external_exports.number().int().nonnegative()
14738
14742
  });
14739
14743
 
14744
+ // ../../packages/shared-types/dist/resource.js
14745
+ var BUCKET_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
14746
+ var BUCKET_UPLOAD_MAX_SIZE_LABEL = `${BUCKET_UPLOAD_MAX_BYTES / (1024 * 1024)} MB`;
14747
+ var BUCKET_UPLOAD_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
14748
+ var BUCKET_UPLOAD_TOTAL_REFUSAL = `Je kunt maximaal ${BUCKET_UPLOAD_MAX_TOTAL_BYTES / (1024 * 1024)} MB per upload versturen.`;
14749
+
14740
14750
  // ../../packages/shared-types/dist/env-vars.js
14741
14751
  var MANAGED_ENV_VAR_NAMES = [
14742
14752
  "AI_GATEWAY_TOKEN",
@@ -14928,6 +14938,7 @@ var ROLES = {
14928
14938
  var ACCOUNT_HEADER = "Kryd-Account";
14929
14939
 
14930
14940
  // ../../packages/shared-types/dist/workflow-run.js
14941
+ var WORKFLOW_RUN_PAYLOAD_MAX_BYTES = 64 * 1024;
14931
14942
  var MAX_WEBHOOK_NAME = 32;
14932
14943
  var WEBHOOK_NAME_RE = new RegExp(`^[a-z0-9](?:[a-z0-9-]{0,${MAX_WEBHOOK_NAME - 2}}[a-z0-9])?$`);
14933
14944
 
@@ -15255,6 +15266,9 @@ async function linkProject(apiUrl, token, input) {
15255
15266
  }
15256
15267
  return parsed;
15257
15268
  }
15269
+ function environmentQuery(environment) {
15270
+ return environment === "preview" ? "?environment=preview" : "";
15271
+ }
15258
15272
  async function attachDatabase(apiUrl, token, input) {
15259
15273
  const res = await fetch(`${apiUrl}/databases`, {
15260
15274
  method: "POST",
@@ -15266,7 +15280,9 @@ async function attachDatabase(apiUrl, token, input) {
15266
15280
  projectId: input.projectId,
15267
15281
  mode: input.mode,
15268
15282
  // Conditional (exactOptionalPropertyTypes) — only BYO carries a connection string.
15269
- ...input.connectionString ? { connectionString: input.connectionString } : {}
15283
+ ...input.connectionString ? { connectionString: input.connectionString } : {},
15284
+ // Only sent for preview, so a production attach is byte-identical to what older CLIs send.
15285
+ ...input.environment === "preview" ? { environment: "preview" } : {}
15270
15286
  })
15271
15287
  });
15272
15288
  if (!res.ok) {
@@ -15283,7 +15299,8 @@ async function attachDatabase(apiUrl, token, input) {
15283
15299
  return {
15284
15300
  id: parsed.id,
15285
15301
  mode: parsed.mode,
15286
- status: parsed.status
15302
+ status: parsed.status,
15303
+ ...parsed.environment === "production" || parsed.environment === "preview" ? { environment: parsed.environment } : {}
15287
15304
  };
15288
15305
  }
15289
15306
  async function createStorage(apiUrl, token, input) {
@@ -15534,14 +15551,15 @@ async function fetchResourceStatus(apiUrl, token, path) {
15534
15551
  }
15535
15552
  return {
15536
15553
  status: parsed.status,
15537
- failureReason: typeof parsed.failureReason === "string" ? parsed.failureReason : null
15554
+ failureReason: typeof parsed.failureReason === "string" ? parsed.failureReason : null,
15555
+ ...parsed.environment === "production" || parsed.environment === "preview" ? { environment: parsed.environment } : {}
15538
15556
  };
15539
15557
  }
15540
- function getDatabaseStatus(apiUrl, token, projectId) {
15558
+ function getDatabaseStatus(apiUrl, token, projectId, environment) {
15541
15559
  return fetchResourceStatus(
15542
15560
  apiUrl,
15543
15561
  token,
15544
- `/databases/${encodeURIComponent(projectId)}`
15562
+ `/databases/${encodeURIComponent(projectId)}${environmentQuery(environment)}`
15545
15563
  );
15546
15564
  }
15547
15565
  function getStorageStatus(apiUrl, token, projectId) {
@@ -15599,9 +15617,9 @@ async function pollResourceDetached(fetchStatus, opts) {
15599
15617
  await sleep(intervalMs);
15600
15618
  }
15601
15619
  }
15602
- async function detachDatabase(apiUrl, token, projectId) {
15620
+ async function detachDatabase(apiUrl, token, projectId, environment) {
15603
15621
  const res = await fetch(
15604
- `${apiUrl}/databases/${encodeURIComponent(projectId)}`,
15622
+ `${apiUrl}/databases/${encodeURIComponent(projectId)}${environmentQuery(environment)}`,
15605
15623
  { method: "DELETE", headers: { ...authHeaders(token) } }
15606
15624
  );
15607
15625
  if (!res.ok) {
@@ -15612,6 +15630,19 @@ async function detachDatabase(apiUrl, token, projectId) {
15612
15630
  );
15613
15631
  }
15614
15632
  }
15633
+ async function resetPreviewDatabase(apiUrl, token, projectId) {
15634
+ const res = await fetch(
15635
+ `${apiUrl}/databases/${encodeURIComponent(projectId)}/reset?environment=preview`,
15636
+ { method: "POST", headers: { ...authHeaders(token) } }
15637
+ );
15638
+ if (!res.ok) {
15639
+ throw new ApiError(
15640
+ `Database reset failed (${res.status})`,
15641
+ await parseEnvelope(res),
15642
+ res.status
15643
+ );
15644
+ }
15645
+ }
15615
15646
  async function getProject(apiUrl, token, projectId) {
15616
15647
  const res = await fetch(`${apiUrl}/projects/${encodeURIComponent(projectId)}`, {
15617
15648
  headers: { ...authHeaders(token) }
@@ -18010,11 +18041,14 @@ async function runLogin(opts) {
18010
18041
  warnIfEnvTokenOverrides();
18011
18042
  return;
18012
18043
  }
18013
- const { token, apiUrl: learnedApiUrl } = await browserLogin(resolveDashboardUrl(opts.dashboardUrl));
18044
+ await browserLoginAndSave(apiUrl, opts.dashboardUrl);
18045
+ warnIfEnvTokenOverrides();
18046
+ }
18047
+ async function browserLoginAndSave(apiUrl, dashboardUrl) {
18048
+ const { token, apiUrl: learnedApiUrl } = await browserLogin(resolveDashboardUrl(dashboardUrl));
18014
18049
  saveToken(token, learnedApiUrl ?? apiUrl);
18015
18050
  process.stdout.write("Logged in.\n");
18016
18051
  await settleAccountAfterLogin(learnedApiUrl ?? apiUrl, token);
18017
- warnIfEnvTokenOverrides();
18018
18052
  }
18019
18053
  async function runWhoami(opts = {}) {
18020
18054
  const apiUrl = resolveApiUrl(opts.apiUrl);
@@ -18049,13 +18083,46 @@ function runLogout() {
18049
18083
  warnIfEnvTokenOverrides();
18050
18084
  }
18051
18085
  async function runInit(opts) {
18052
- const apiUrl = resolveApiUrl(opts.apiUrl);
18086
+ let apiUrl = resolveApiUrl(opts.apiUrl);
18053
18087
  const cwd = opts.cwd ?? process.cwd();
18054
- const token = resolveToken();
18088
+ let token = resolveToken();
18055
18089
  if (!token) {
18056
- process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18057
- process.exitCode = 1;
18058
- return;
18090
+ const refusal = "Not logged in \u2014 run `kryd login` first.\n";
18091
+ const io = opts.io ?? defaultPromptIO();
18092
+ if (opts.offerLogin === false) {
18093
+ process.stderr.write(refusal);
18094
+ process.exitCode = 1;
18095
+ return;
18096
+ }
18097
+ if (!isInteractive(io)) {
18098
+ process.stderr.write(
18099
+ refusal + // KRYD-385 iteration 1: no KRYD_TOKEN advice — `POST /repos` refuses a deploy token (KRYD-578).
18100
+ "No terminal to log in from: ask the user to run `kryd init` (or `kryd login`) in their own terminal. A deploy token (KRYD_TOKEN) cannot create a project.\n"
18101
+ );
18102
+ process.exitCode = 1;
18103
+ return;
18104
+ }
18105
+ io.output.write("You are not logged in to Kryd; `kryd init` can log you in through your browser first.\n");
18106
+ if (!await promptConfirm("Log in now in your browser?", io)) {
18107
+ process.stderr.write(refusal);
18108
+ process.exitCode = 1;
18109
+ return;
18110
+ }
18111
+ try {
18112
+ await browserLoginAndSave(apiUrl);
18113
+ } catch (err) {
18114
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}
18115
+ `);
18116
+ process.exitCode = 1;
18117
+ return;
18118
+ }
18119
+ token = resolveToken();
18120
+ if (!token) {
18121
+ process.stderr.write(refusal);
18122
+ process.exitCode = 1;
18123
+ return;
18124
+ }
18125
+ if (!opts.apiUrl) apiUrl = resolveApiUrl();
18059
18126
  }
18060
18127
  const existingLink = directoryIsLinked(cwd);
18061
18128
  if (existingLink) {
@@ -18650,6 +18717,17 @@ async function runDbAdd(opts) {
18650
18717
  process.exitCode = 1;
18651
18718
  return;
18652
18719
  }
18720
+ if (opts.preview && opts.connectionString !== void 0) {
18721
+ process.stderr.write(
18722
+ "A preview database is always managed by Kryd \u2014 --preview cannot take a --connection-string.\n"
18723
+ );
18724
+ process.exitCode = 1;
18725
+ return;
18726
+ }
18727
+ if (opts.preview) {
18728
+ await addPreviewDatabase(apiUrl, token, project2);
18729
+ return;
18730
+ }
18653
18731
  let mode;
18654
18732
  let connectionString;
18655
18733
  if (opts.connectionString !== void 0) {
@@ -18695,6 +18773,46 @@ The connection string will be injected as DATABASE_URL on your next deploy.
18695
18773
  await reportError(err);
18696
18774
  }
18697
18775
  }
18776
+ async function addPreviewDatabase(apiUrl, token, project2) {
18777
+ try {
18778
+ const db2 = await attachDatabase(apiUrl, token, {
18779
+ projectId: project2,
18780
+ mode: "shared",
18781
+ environment: "preview"
18782
+ });
18783
+ if (db2.environment !== "preview") {
18784
+ process.stderr.write(
18785
+ `The Kryd API did not create a preview database (it does not support them yet) \u2014 run \`kryd db status ${project2}\` to see what is attached.
18786
+ `
18787
+ );
18788
+ process.exitCode = 1;
18789
+ return;
18790
+ }
18791
+ process.stdout.write(`Provisioning a preview database for ${project2}\u2026
18792
+ `);
18793
+ const result = await pollResourceUntilTerminal(
18794
+ () => getDatabaseStatus(apiUrl, token, project2, "preview")
18795
+ );
18796
+ reportSettleResult(result, {
18797
+ readyMsg: `Attached preview database ${db2.id} to ${project2}. It starts empty.
18798
+ Every preview deploy gets it as DATABASE_URL from its next deploy; production keeps its own.
18799
+ `,
18800
+ failedLabel: "Preview database provisioning",
18801
+ retryCmd: `kryd db status ${project2}`
18802
+ });
18803
+ } catch (err) {
18804
+ await reportError(err);
18805
+ }
18806
+ }
18807
+ async function readPreviewDatabase(apiUrl, token, project2) {
18808
+ try {
18809
+ const row = await getDatabaseStatus(apiUrl, token, project2, "preview");
18810
+ return row.environment === "preview" ? row : null;
18811
+ } catch (err) {
18812
+ if (err instanceof ApiError && err.status === 404) return null;
18813
+ throw err;
18814
+ }
18815
+ }
18698
18816
  function reportSettleResult(result, opts) {
18699
18817
  if (result.status === "ready") {
18700
18818
  process.stdout.write(opts.readyMsg);
@@ -18915,6 +19033,8 @@ async function describeProjectResources(apiUrl, token, projectId) {
18915
19033
  const out = ["its deployed app, its containers and its built images"];
18916
19034
  const db2 = await getDatabaseStatus(apiUrl, token, projectId).catch(() => null);
18917
19035
  if (db2) out.push("its Postgres database \u2014 and all the data in it");
19036
+ const preview = db2 ? await readPreviewDatabase(apiUrl, token, projectId).catch(() => null) : null;
19037
+ if (preview) out.push("its preview database \u2014 and all the data in it");
18918
19038
  const storage2 = await getStorageStatus(apiUrl, token, projectId).catch(() => null);
18919
19039
  if (storage2) out.push("its object-storage bucket \u2014 and every object in it");
18920
19040
  out.push("its Git repository on the forge, and its push token");
@@ -18933,6 +19053,23 @@ async function runDbRemove(opts) {
18933
19053
  reportNotLinked("kryd db remove <projectId>");
18934
19054
  return;
18935
19055
  }
19056
+ if (opts.preview) {
19057
+ await removePreviewDatabase(apiUrl, token, project2, opts);
19058
+ return;
19059
+ }
19060
+ try {
19061
+ if (await readPreviewDatabase(apiUrl, token, project2)) {
19062
+ process.stderr.write(
19063
+ `${project2} has a preview database \u2014 remove the preview first (\`kryd db remove --preview\`).
19064
+ `
19065
+ );
19066
+ process.exitCode = 1;
19067
+ return;
19068
+ }
19069
+ } catch (err) {
19070
+ await reportError(err);
19071
+ return;
19072
+ }
18936
19073
  const proceed = await confirmResourceRemoval({
18937
19074
  yes: opts.yes,
18938
19075
  io: opts.io,
@@ -18959,6 +19096,105 @@ async function runDbRemove(opts) {
18959
19096
  await reportError(err);
18960
19097
  }
18961
19098
  }
19099
+ async function removePreviewDatabase(apiUrl, token, project2, opts) {
19100
+ try {
19101
+ if (!await readPreviewDatabase(apiUrl, token, project2)) {
19102
+ process.stderr.write(`${project2} has no preview database.
19103
+ `);
19104
+ process.exitCode = 1;
19105
+ return;
19106
+ }
19107
+ } catch (err) {
19108
+ await reportError(err);
19109
+ return;
19110
+ }
19111
+ const proceed = await confirmResourceRemoval({
19112
+ yes: opts.yes,
19113
+ io: opts.io,
19114
+ subject: `the preview database from ${project2}`,
19115
+ question: `Remove the preview database from ${project2}? It is torn down and all the data in it is destroyed \u2014 this cannot be undone. Production is not touched; preview deploys use the production database again from their next deploy.`,
19116
+ declined: `Left the preview database on ${project2} in place.
19117
+ `
19118
+ });
19119
+ if (!proceed) return;
19120
+ try {
19121
+ await detachDatabase(apiUrl, token, project2, "preview");
19122
+ process.stdout.write(`Removing the preview database from ${project2}\u2026
19123
+ `);
19124
+ const result = await pollResourceDetached(
19125
+ () => getDatabaseStatus(apiUrl, token, project2, "preview")
19126
+ );
19127
+ reportDetachResult(result, {
19128
+ goneMsg: `Removed the preview database from ${project2}.
19129
+ Preview deploys use the production database again from their next deploy.
19130
+ `,
19131
+ failedLabel: "Preview database removal",
19132
+ retryCmd: `kryd db remove --preview ${project2}`
19133
+ });
19134
+ } catch (err) {
19135
+ await reportError(err);
19136
+ }
19137
+ }
19138
+ async function runDbReset(opts) {
19139
+ if (!opts.preview) {
19140
+ process.stderr.write(
19141
+ "Only the preview database can be reset \u2014 run `kryd db reset --preview`. Production is never emptied.\n"
19142
+ );
19143
+ process.exitCode = 1;
19144
+ return;
19145
+ }
19146
+ const apiUrl = resolveApiUrl(opts.apiUrl);
19147
+ const token = resolveToken();
19148
+ if (!token) {
19149
+ process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19150
+ process.exitCode = 1;
19151
+ return;
19152
+ }
19153
+ const project2 = resolveProjectId(opts.project, opts.cwd);
19154
+ if (!project2) {
19155
+ reportNotLinked("kryd db reset --preview <projectId>");
19156
+ return;
19157
+ }
19158
+ try {
19159
+ if (!await readPreviewDatabase(apiUrl, token, project2)) {
19160
+ process.stderr.write(
19161
+ `${project2} has no preview database \u2014 add one with \`kryd db add --preview ${project2}\`.
19162
+ `
19163
+ );
19164
+ process.exitCode = 1;
19165
+ return;
19166
+ }
19167
+ } catch (err) {
19168
+ await reportError(err);
19169
+ return;
19170
+ }
19171
+ const proceed = await confirmResourceRemoval({
19172
+ yes: opts.yes,
19173
+ io: opts.io,
19174
+ subject: `everything in the preview database of ${project2}`,
19175
+ question: `Empty the preview database of ${project2}? Every table and all the data in it is dropped \u2014 this cannot be undone. Production is not touched.`,
19176
+ declined: `Left the preview database of ${project2} as it is.
19177
+ `
19178
+ });
19179
+ if (!proceed) return;
19180
+ try {
19181
+ await resetPreviewDatabase(apiUrl, token, project2);
19182
+ process.stdout.write(`Emptying the preview database of ${project2}\u2026
19183
+ `);
19184
+ const result = await pollResourceUntilTerminal(
19185
+ () => getDatabaseStatus(apiUrl, token, project2, "preview")
19186
+ );
19187
+ reportSettleResult(result, {
19188
+ readyMsg: `The preview database of ${project2} is empty again.
19189
+ Its credentials did not change, so no deploy is needed.
19190
+ `,
19191
+ failedLabel: "Preview database reset",
19192
+ retryCmd: `kryd db status ${project2}`
19193
+ });
19194
+ } catch (err) {
19195
+ await reportError(err);
19196
+ }
19197
+ }
18962
19198
  async function runAiAdd(opts) {
18963
19199
  const apiUrl = resolveApiUrl(opts.apiUrl);
18964
19200
  const token = resolveToken();
@@ -19064,12 +19300,45 @@ async function runDbStatus(opts) {
19064
19300
  reportNotLinked("kryd db status <projectId>");
19065
19301
  return;
19066
19302
  }
19303
+ let productionAttached = false;
19067
19304
  await reportResourceStatus({
19068
- read: () => getDatabaseStatus(apiUrl, token, project2),
19305
+ read: async () => {
19306
+ const row = await getDatabaseStatus(apiUrl, token, project2);
19307
+ productionAttached = true;
19308
+ return row;
19309
+ },
19069
19310
  noun: "database",
19070
19311
  project: project2,
19071
19312
  token
19072
19313
  });
19314
+ if (!productionAttached) return;
19315
+ try {
19316
+ const preview = await readPreviewDatabase(apiUrl, token, project2);
19317
+ if (!preview) {
19318
+ process.stdout.write(
19319
+ `${project2}: no preview database \u2014 preview deploys use the production database.
19320
+ `
19321
+ );
19322
+ } else if (preview.status === "failed") {
19323
+ process.stdout.write(
19324
+ `${project2}: preview database failed \u2014 ${preview.failureReason ?? "unknown error"}.
19325
+ Remove it with \`kryd db remove --preview\` and add it again.
19326
+ `
19327
+ );
19328
+ } else if (preview.status === "ready") {
19329
+ process.stdout.write(
19330
+ `${project2}: preview database ready \u2014 preview deploys get it as DATABASE_URL.
19331
+ `
19332
+ );
19333
+ } else {
19334
+ process.stdout.write(
19335
+ `${project2}: preview database ${preview.status} \u2014 preview deploys use the production database until it is ready.
19336
+ `
19337
+ );
19338
+ }
19339
+ } catch (err) {
19340
+ await reportError(err);
19341
+ }
19073
19342
  }
19074
19343
  async function runStorageStatus(opts) {
19075
19344
  const apiUrl = resolveApiUrl(opts.apiUrl);
@@ -19529,6 +19798,7 @@ HATCHET_CLIENT_TOKEN is injected into your container on production deploys (prev
19529
19798
  process.exitCode = 0;
19530
19799
  await runInit({
19531
19800
  cwd: target,
19801
+ offerLogin: false,
19532
19802
  ...opts.name ? { name: opts.name } : {},
19533
19803
  ...opts.apiUrl ? { apiUrl: opts.apiUrl } : {}
19534
19804
  });
@@ -20159,7 +20429,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
20159
20429
  await reportError(err);
20160
20430
  }
20161
20431
  }
20162
- var CLI_VERSION = true ? "0.16.0" : "0.0.0-dev";
20432
+ var CLI_VERSION = true ? "0.17.0" : "0.0.0-dev";
20163
20433
  var program = new Command();
20164
20434
  program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
20165
20435
  program.hook("postAction", async () => {
@@ -20182,7 +20452,9 @@ program.command("git-credential <operation>", { hidden: true }).description("git
20182
20452
  ...opts.apiUrl ? { apiUrl: opts.apiUrl } : {}
20183
20453
  });
20184
20454
  });
20185
- program.command("init").description("Link this project's repo + register its push webhook").option("--name <name>", "project name (defaults to package.json name / dir)").option(
20455
+ program.command("init").description(
20456
+ "Link this project's repo + register its push webhook; not logged in \u2192 offers the browser login first (terminal only)"
20457
+ ).option("--name <name>", "project name (defaults to package.json name / dir)").option(
20186
20458
  "--framework <framework>",
20187
20459
  'react-router | nextjs | vite-spa | node (auto-detected if omitted; `workflow` comes from a committed kryd.json with "web": false)'
20188
20460
  ).option(
@@ -20243,9 +20515,13 @@ var db = program.command("db").description("Manage project databases");
20243
20515
  db.command("add [project]").description("Attach a shared or bring-your-own Postgres database to a project").option("--shared", "attach a shared managed database (the default)").option(
20244
20516
  "--connection-string <url>",
20245
20517
  "attach an existing database (BYO) by pasting its connection string"
20518
+ ).option(
20519
+ "--preview",
20520
+ "attach an empty preview database that every preview deploy gets instead of production's"
20246
20521
  ).option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbAdd({ ...opts, project: project2 }));
20247
- db.command("remove [project]").description("Tear down the project's database and everything in it (asks for confirmation)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbRemove({ ...opts, project: project2 }));
20248
- db.command("status [project]").description("Show whether the project has a database, and what state it is in").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbStatus({ ...opts, project: project2 }));
20522
+ db.command("remove [project]").description("Tear down the project's database and everything in it (asks for confirmation)").option("--preview", "remove the preview database; production is not touched").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbRemove({ ...opts, project: project2 }));
20523
+ db.command("reset [project]").description("Empty the preview database: every table dropped, credentials kept (asks for confirmation)").option("--preview", "required \u2014 only the preview database can be reset").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbReset({ ...opts, project: project2 }));
20524
+ db.command("status [project]").description("Show the project's database and preview database, and what state they are in").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbStatus({ ...opts, project: project2 }));
20249
20525
  var storage = program.command("storage").description("Manage project object storage");
20250
20526
  storage.command("add [project]").description("Create an S3-compatible object-storage bucket for a project").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageAdd({ ...opts, project: project2 }));
20251
20527
  storage.command("remove [project]").description("Tear down the project's bucket and every object in it (asks for confirmation)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageRemove({ ...opts, project: project2 }));
@@ -20340,6 +20616,7 @@ export {
20340
20616
  runAiStatus,
20341
20617
  runDbAdd,
20342
20618
  runDbRemove,
20619
+ runDbReset,
20343
20620
  runDbStatus,
20344
20621
  runDeploy,
20345
20622
  runDeployHookCreate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryd/cli",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Kryd CLI — git push → live on the European cloud for your AI: SSL, managed Postgres & object storage, and an EU-hosted AI gateway. Your code and model calls stay in the EU. Beta: sign up at https://kryd.eu/beta",
5
5
  "keywords": [
6
6
  "kryd",
@@ -42,8 +42,8 @@
42
42
  "typescript": "^5.6.3",
43
43
  "vitest": "^2.1.8",
44
44
  "@kryd/shared-types": "0.0.0",
45
- "@kryd/config-ts": "0.0.0",
46
- "@kryd/config-eslint": "0.0.0"
45
+ "@kryd/config-eslint": "0.0.0",
46
+ "@kryd/config-ts": "0.0.0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "esbuild src/index.ts --bundle --platform=node --format=esm --external:commander --define:__KRYD_VERSION__=\\\"$npm_package_version\\\" --outfile=dist/index.js",