@kryd/cli 0.16.0 → 0.18.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 +21 -3
  2. package/dist/index.js +604 -83
  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
@@ -27,13 +26,16 @@ kryd logs <project> --worker # tail the workflow worker's stdout/stderr
27
26
 
28
27
  Once you've run `kryd init` in a directory, the commands below work **arg-less** there — the folder is linked via `.kryd/project.json` (git-ignored, no secret).
29
28
 
29
+ `kryd init` and `kryd workflow init` also write a short **Kryd block into `AGENTS.md`** — the file Codex, Cursor, Copilot and Claude Code read — so a coding agent knows the rules before it breaks one: listen on `$PORT`, which env vars Kryd injects, keep the AI gateway token on the server, make workflow tasks safe to run twice, and the CLI's commands (generated from the CLI itself). It sits between `<!-- kryd:begin … -->` and `<!-- kryd:end -->`; a later run replaces only what is between those markers and leaves the rest of your file alone. After updating the CLI, `kryd init --agents` refreshes the block in a linked project (no login needed) — also the way to add it to a project linked before the block existed. If the folder has a `CLAUDE.md`, one `@AGENTS.md` line is added to it so Claude Code reads the block too.
30
+
30
31
  ## Commands
31
32
 
32
33
  | Command | What it does |
33
34
  |---|---|
34
35
  | `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
36
  | `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. |
37
+ | `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). |
38
+ | `kryd init --agents` | Only write or refresh the Kryd block in `AGENTS.md` of the linked project (walks up to the link), plus the `@AGENTS.md` line in a `CLAUDE.md`. No login, no API call. In a linked folder a plain `kryd init` refuses and names this. |
37
39
  | `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
40
  | `kryd deploy [project]` | Re-deploy the production-branch HEAD already on the forge — no new commit. |
39
41
  | `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). |
@@ -54,6 +56,22 @@ kryd support new --type bug --subject "Deploy hangs on build" --message "Every d
54
56
  kryd support show T-1042 --json
55
57
  ```
56
58
 
59
+ **`--json` on the read commands.** `kryd project list`, `whoami`, `db status`, `storage status`, `ai status`, `workflow status`, `domain status [host]` and `env list` take `--json` too, with the same contract: exactly one JSON document on stdout; an error is text on stderr with exit 1 and an empty stdout. "Nothing attached" is an answer, not an error (exit 0). The shapes:
60
+
61
+ | Command | `--json` prints |
62
+ |---|---|
63
+ | `kryd project list` | `{ items }`: each project as the API lists it (`id`, `name`, `subdomain`, `framework`, `repoUrl`, `productionBranch`, `previewsPublic`, `status`, `createdAt`, `updatedAt`, …) plus `linked` — `true` for the project this folder is linked to |
64
+ | `kryd whoami` | the `GET /me` answer as is: you, the account commands run in, your role and your `memberships` |
65
+ | `kryd db status` | `{ production, preview }`, each `{ attached, status, failureReason }`. When production is not attached the preview is not read, and is reported as not attached |
66
+ | `kryd storage status` | `{ attached, status, failureReason }` |
67
+ | `kryd ai status` | `{ enabled, url }` |
68
+ | `kryd workflow status` | `{ enabled, tokenExpiresAt, tokenExpiresInDays }` — both `null` without an expiry; the days are negative once the token has expired |
69
+ | `kryd domain status` | `{ projectHost, domains }`; each domain is `{ id, host, app?, redirectTo?, status, detail, checkedAt, createdAt, records }`, with `status` the raw value (`pending`, `verified`, `active`, `failed`), not the text-mode wording |
70
+ | `kryd domain status <host>` | that one domain, the same fields, its DNS `records` included |
71
+ | `kryd env list` | `{ items }`, each `{ key, source, scope, updatedAt, environments? }` — names and origin, never values |
72
+
73
+ `kryd token list --json` and `kryd account list --json` print a bare array.
74
+
57
75
  In CI, no `kryd login` and no config file: set a deploy token as the secret `KRYD_TOKEN` and every command uses it (it wins over a stored login).
58
76
 
59
77
  ```sh
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
 
@@ -14978,14 +14989,14 @@ function isTerminalResourceStatus(status) {
14978
14989
 
14979
14990
  // src/index.ts
14980
14991
  import {
14981
- existsSync as existsSync3,
14992
+ existsSync as existsSync4,
14982
14993
  mkdirSync as mkdirSync2,
14983
- readFileSync as readFileSync3,
14984
- realpathSync,
14994
+ readFileSync as readFileSync4,
14995
+ realpathSync as realpathSync2,
14985
14996
  rmSync as rmSync2,
14986
- writeFileSync as writeFileSync2
14997
+ writeFileSync as writeFileSync3
14987
14998
  } from "node:fs";
14988
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join3, relative, resolve as resolve2 } from "node:path";
14999
+ import { basename as basename2, dirname as dirname2, isAbsolute, join as join4, relative, resolve as resolve2 } from "node:path";
14989
15000
  import { pathToFileURL } from "node:url";
14990
15001
 
14991
15002
  // src/config.ts
@@ -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) }
@@ -16765,9 +16796,125 @@ function parseCredentialInput(raw) {
16765
16796
  return out;
16766
16797
  }
16767
16798
 
16799
+ // src/agents-md.ts
16800
+ import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
16801
+ import { join as join2 } from "node:path";
16802
+ var AGENTS_FILE = "AGENTS.md";
16803
+ var CLAUDE_FILE = "CLAUDE.md";
16804
+ var BEGIN_MARKER = "<!-- kryd:begin \u2014 written by the kryd CLI; edits inside are replaced on the next run -->";
16805
+ var END_MARKER = "<!-- kryd:end -->";
16806
+ var BEGIN_LINE = /^<!-- kryd:begin/gm;
16807
+ var END_LINE = /^<!-- kryd:end/gm;
16808
+ var AGENTS_PROSE = `## Deploying on Kryd
16809
+
16810
+ This project deploys to Kryd, a European cloud platform. Use what it already provides instead of adding another service:
16811
+
16812
+ - **Git is the deploy.** The repository lives on Kryd's own git server in the EU (remote \`kryd\`). \`git push kryd <branch>\` or \`kryd push\` builds and deploys; every other branch gets its own preview URL. Don't add CI deploy pipelines, hosting Dockerfiles or another platform's config.
16813
+ - **Database:** managed Postgres \u2014 \`kryd db add\`, then use \`DATABASE_URL\`. Preview deploys can get a database of their own: \`kryd db add --preview\`. Don't add a database container or SQLite for production data.
16814
+ - **File storage:** S3-compatible object storage \u2014 \`kryd storage add\`.
16815
+ - **AI:** an EU-hosted, OpenAI-compatible gateway with several models \u2014 \`kryd ai add\`. Don't add provider API keys.
16816
+ - **Long-running or background work** \u2014 jobs, retries, schedules \u2014 belongs in a workflow (\`kryd workflow init\`), not in a request handler, a timer loop or a separate queue.
16817
+ - **Incoming webhooks** (Stripe, GitHub, \u2026) go to a workflow webhook, created in the Kryd dashboard under Workflows, so every delivery is stored and retried instead of handled inside a web request.
16818
+ - **Your own domain:** \`kryd domain add <host>\` prints the DNS records to create; the HTTPS certificate is automatic.
16819
+ - Everything runs in the EU; keep data and model calls there.
16820
+
16821
+ ### Rules
16822
+
16823
+ - **Listen on \`$PORT\`.** The platform sets it; never hardcode a port. An app that listens anywhere else fails the health check, and its deploy never goes live.
16824
+ - **The project link** is \`.kryd/project.json\` (per developer, gitignored). Commands find it by walking up to the git root, so they need no project id inside this repository.
16825
+ - **Injected env vars \u2014 never set these yourself:** \`DATABASE_URL\`; \`AI_GATEWAY_URL\` (already ends in \`/v1\`) and \`AI_GATEWAY_TOKEN\`; \`HATCHET_CLIENT_TOKEN\`; and for storage \`AWS_ACCESS_KEY_ID\`, \`AWS_SECRET_ACCESS_KEY\`, \`AWS_REGION\`, \`AWS_ENDPOINT_URL_S3\`, \`BUCKET_NAME\`. Kryd injects each one once its resource is added (\`kryd db add\`, \`kryd ai add\`, \`kryd workflow add\`, \`kryd storage add\`).
16826
+ - **Call the AI gateway from server code only.** \`AI_GATEWAY_TOKEN\` must never reach a browser.
16827
+ - **Workflows:** \`kryd workflow init --language typescript|python|go\` scaffolds a worker, declared in \`kryd.json\` (\`"web": false\` = the project is a worker only). Make every task safe to run twice: a worker stops within seconds on SIGTERM, and the engine re-runs the task.
16828
+ - **Login:** without a terminal there is no login \u2014 ask the user to run \`kryd init\` or \`kryd login\`. A deploy token (\`KRYD_TOKEN\`) can deploy but cannot create a project.
16829
+ - **Destructive commands** ask for confirmation; without a terminal they need \`--yes\`.
16830
+ - **Read commands take \`--json\`** for one JSON document on stdout \u2014 \`kryd project list --json\`, \`kryd whoami --json\`, \`kryd db status --json\`, \`kryd storage status --json\`, \`kryd ai status --json\`, \`kryd workflow status --json\`, \`kryd domain status --json\`, \`kryd env list --json\`. "Nothing attached" is an answer (exit 0); an error is text on stderr with exit 1 and an empty stdout.
16831
+ `;
16832
+ var AGENTS_FOOTER = "Run `kryd <command> --help` for details. After updating the CLI, refresh this block with `kryd init --agents`.\n";
16833
+ function isHidden(cmd) {
16834
+ return cmd._hidden === true;
16835
+ }
16836
+ function leafCommands(program2) {
16837
+ const out = [];
16838
+ const walk = (cmd, prefix) => {
16839
+ if (isHidden(cmd)) return;
16840
+ const path = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
16841
+ if (cmd.commands.length > 0) {
16842
+ for (const child of cmd.commands) walk(child, path);
16843
+ return;
16844
+ }
16845
+ const args = cmd.registeredArguments.map((a) => {
16846
+ const inner = `${a.name()}${a.variadic ? "..." : ""}`;
16847
+ return a.required ? `<${inner}>` : `[${inner}]`;
16848
+ });
16849
+ out.push({ path, usage: [path, ...args].join(" "), description: cmd.description() });
16850
+ };
16851
+ for (const child of program2.commands) walk(child, "");
16852
+ return out;
16853
+ }
16854
+ function renderAgentsBlock(program2) {
16855
+ const commands = leafCommands(program2).map((c) => `- \`kryd ${c.usage}\` \u2014 ${c.description}
16856
+ `).join("");
16857
+ return `${BEGIN_MARKER}
16858
+ ` + AGENTS_PROSE + "\n### Commands\n\n" + commands + "\n" + AGENTS_FOOTER + `${END_MARKER}
16859
+ `;
16860
+ }
16861
+ function lineStartsMatching(text, re) {
16862
+ return [...text.matchAll(re)].map((m) => m.index);
16863
+ }
16864
+ function writeAgentsBlock(dir, block) {
16865
+ const path = join2(dir, AGENTS_FILE);
16866
+ if (!existsSync2(path)) {
16867
+ writeFileSync2(path, block);
16868
+ return "created";
16869
+ }
16870
+ const text = readFileSync2(path, "utf8");
16871
+ const begins = lineStartsMatching(text, BEGIN_LINE);
16872
+ const ends = lineStartsMatching(text, END_LINE);
16873
+ let next;
16874
+ if (begins.length === 0 && ends.length === 0) {
16875
+ let base = text;
16876
+ if (base.length > 0) {
16877
+ if (!base.endsWith("\n")) base += "\n";
16878
+ if (!base.endsWith("\n\n")) base += "\n";
16879
+ }
16880
+ next = base + block;
16881
+ } else {
16882
+ const [begin] = begins;
16883
+ const [end] = ends;
16884
+ if (begins.length !== 1 || ends.length !== 1 || begin === void 0 || end === void 0 || end < begin || // An end marker that is not exactly ours (say `<!-- kryd:end` without its `-->`) would make
16885
+ // the slice run on into the customer's text.
16886
+ !text.startsWith(END_MARKER, end)) {
16887
+ throw new Error(
16888
+ `${AGENTS_FILE} has broken kryd markers (a begin without an end, or one of them twice) \u2014 left it untouched; fix or remove the markers and run this again`
16889
+ );
16890
+ }
16891
+ const endOfMarker = end + END_MARKER.length;
16892
+ next = text.slice(0, begin) + block.replace(/\n$/, "") + text.slice(endOfMarker);
16893
+ }
16894
+ if (next === text) return "unchanged";
16895
+ writeFileSync2(path, next);
16896
+ return "updated";
16897
+ }
16898
+ function ensureClaudeImport(dir) {
16899
+ const path = join2(dir, CLAUDE_FILE);
16900
+ if (!existsSync2(path)) return false;
16901
+ const agents = join2(dir, AGENTS_FILE);
16902
+ if (existsSync2(agents) && realpathSync(path) === realpathSync(agents)) return false;
16903
+ const text = readFileSync2(path, "utf8");
16904
+ if (/^[ \t]*@(\.\/)?AGENTS\.md[ \t]*\r?$/m.test(text)) return false;
16905
+ let base = text;
16906
+ if (base.length > 0) {
16907
+ if (!base.endsWith("\n")) base += "\n";
16908
+ if (!base.endsWith("\n\n")) base += "\n";
16909
+ }
16910
+ writeFileSync2(path, `${base}@AGENTS.md
16911
+ `);
16912
+ return true;
16913
+ }
16914
+
16768
16915
  // src/framework.ts
16769
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
16770
- import { basename, join as join2 } from "node:path";
16916
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
16917
+ import { basename, join as join3 } from "node:path";
16771
16918
  var FRAMEWORKS = [
16772
16919
  "react-router",
16773
16920
  "nextjs",
@@ -16822,7 +16969,7 @@ function startScriptLooksLikePlainNode(start) {
16822
16969
  }
16823
16970
  function readPackageJson(cwd) {
16824
16971
  try {
16825
- return JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
16972
+ return JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
16826
16973
  } catch {
16827
16974
  return null;
16828
16975
  }
@@ -16830,7 +16977,7 @@ function readPackageJson(cwd) {
16830
16977
  function readDeclaration(cwd) {
16831
16978
  let parsed;
16832
16979
  try {
16833
- parsed = JSON.parse(readFileSync2(join2(cwd, DECLARATION_FILE), "utf8"));
16980
+ parsed = JSON.parse(readFileSync3(join3(cwd, DECLARATION_FILE), "utf8"));
16834
16981
  } catch {
16835
16982
  return null;
16836
16983
  }
@@ -16850,7 +16997,7 @@ function frameworkFrom(pkg, cwd) {
16850
16997
  const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
16851
16998
  const has = (name) => name in deps;
16852
16999
  if (has("next")) return "nextjs";
16853
- if (has("@react-router/dev") || existsSync2(join2(cwd, "react-router.config.ts")) || existsSync2(join2(cwd, "react-router.config.js"))) {
17000
+ if (has("@react-router/dev") || existsSync3(join3(cwd, "react-router.config.ts")) || existsSync3(join3(cwd, "react-router.config.js"))) {
16854
17001
  return "react-router";
16855
17002
  }
16856
17003
  if (VITE_META_FRAMEWORKS.some(has)) return null;
@@ -18001,6 +18148,10 @@ function reportNotLinked(example) {
18001
18148
  );
18002
18149
  process.exitCode = 1;
18003
18150
  }
18151
+ function printJson2(value) {
18152
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
18153
+ `);
18154
+ }
18004
18155
  async function runLogin(opts) {
18005
18156
  const apiUrl = resolveApiUrl(opts.apiUrl);
18006
18157
  const pastedToken = opts.token ?? process.env.KRYD_TOKEN;
@@ -18010,17 +18161,24 @@ async function runLogin(opts) {
18010
18161
  warnIfEnvTokenOverrides();
18011
18162
  return;
18012
18163
  }
18013
- const { token, apiUrl: learnedApiUrl } = await browserLogin(resolveDashboardUrl(opts.dashboardUrl));
18164
+ await browserLoginAndSave(apiUrl, opts.dashboardUrl);
18165
+ warnIfEnvTokenOverrides();
18166
+ }
18167
+ async function browserLoginAndSave(apiUrl, dashboardUrl) {
18168
+ const { token, apiUrl: learnedApiUrl } = await browserLogin(resolveDashboardUrl(dashboardUrl));
18014
18169
  saveToken(token, learnedApiUrl ?? apiUrl);
18015
18170
  process.stdout.write("Logged in.\n");
18016
18171
  await settleAccountAfterLogin(learnedApiUrl ?? apiUrl, token);
18017
- warnIfEnvTokenOverrides();
18018
18172
  }
18019
18173
  async function runWhoami(opts = {}) {
18020
18174
  const apiUrl = resolveApiUrl(opts.apiUrl);
18021
18175
  const token = resolveToken() ?? "";
18022
18176
  try {
18023
18177
  const me = await whoami(apiUrl, token);
18178
+ if (opts.json) {
18179
+ printJson2(me);
18180
+ return;
18181
+ }
18024
18182
  const current = me.memberships?.find((m) => m.accountId === me.accountId);
18025
18183
  const label = current?.slug ?? me.accountSlug ?? me.accountId ?? "?";
18026
18184
  process.stdout.write(`${me.email} (account ${label}, role ${current?.role ?? me.role ?? "?"})
@@ -18048,15 +18206,110 @@ function runLogout() {
18048
18206
  process.stdout.write("Logged out.\n");
18049
18207
  warnIfEnvTokenOverrides();
18050
18208
  }
18051
- async function runInit(opts) {
18052
- const apiUrl = resolveApiUrl(opts.apiUrl);
18053
- const cwd = opts.cwd ?? process.cwd();
18054
- const token = resolveToken();
18209
+ function installAgentsBlock(dir) {
18210
+ let note = "";
18211
+ const changed = [];
18212
+ let result;
18213
+ try {
18214
+ result = writeAgentsBlock(dir, renderAgentsBlock(program));
18215
+ if (result !== "unchanged") changed.push(AGENTS_FILE);
18216
+ if (result === "created") {
18217
+ note += "Wrote AGENTS.md \u2014 what coding agents need to know to deploy this project on Kryd.\n";
18218
+ } else if (result === "updated") {
18219
+ note += "Updated the Kryd block in AGENTS.md.\n";
18220
+ }
18221
+ } catch (err) {
18222
+ process.stderr.write(
18223
+ `warning: could not write the Kryd block to AGENTS.md (${err instanceof Error ? err.message : String(err)}) \u2014 everything else worked; coding agents just will not see it.
18224
+ `
18225
+ );
18226
+ return { note, block: "failed", changed };
18227
+ }
18228
+ try {
18229
+ if (ensureClaudeImport(dir)) {
18230
+ changed.push(CLAUDE_FILE);
18231
+ note += "Added `@AGENTS.md` to CLAUDE.md, so Claude Code reads that block too.\n";
18232
+ }
18233
+ } catch (err) {
18234
+ process.stderr.write(
18235
+ `warning: could not add \`@AGENTS.md\` to CLAUDE.md (${err instanceof Error ? err.message : String(err)}) \u2014 add that line yourself so Claude Code reads AGENTS.md.
18236
+ `
18237
+ );
18238
+ }
18239
+ return { note, block: result, changed };
18240
+ }
18241
+ async function settleLogin(opts) {
18242
+ let token = resolveToken();
18243
+ if (token) return { token, apiUrl: opts.apiUrl };
18244
+ const refusal = "Not logged in \u2014 run `kryd login` first.\n";
18245
+ const io = opts.io ?? defaultPromptIO();
18246
+ if (!opts.offerLogin) {
18247
+ process.stderr.write(refusal);
18248
+ process.exitCode = 1;
18249
+ return null;
18250
+ }
18251
+ if (!isInteractive(io)) {
18252
+ process.stderr.write(
18253
+ refusal + // KRYD-385 iteration 1: no KRYD_TOKEN advice — `POST /repos` refuses a deploy token (KRYD-578).
18254
+ `No terminal to log in from: ask the user to run \`${opts.command}\` (or \`kryd login\`) in their own terminal. A deploy token (KRYD_TOKEN) cannot create a project.
18255
+ `
18256
+ );
18257
+ process.exitCode = 1;
18258
+ return null;
18259
+ }
18260
+ io.output.write(`You are not logged in to Kryd; \`${opts.command}\` can log you in through your browser first.
18261
+ `);
18262
+ if (!await promptConfirm("Log in now in your browser?", io)) {
18263
+ process.stderr.write(refusal);
18264
+ process.exitCode = 1;
18265
+ return null;
18266
+ }
18267
+ try {
18268
+ await browserLoginAndSave(opts.apiUrl);
18269
+ } catch (err) {
18270
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}
18271
+ `);
18272
+ process.exitCode = 1;
18273
+ return null;
18274
+ }
18275
+ token = resolveToken();
18055
18276
  if (!token) {
18056
- process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18277
+ process.stderr.write(refusal);
18278
+ process.exitCode = 1;
18279
+ return null;
18280
+ }
18281
+ return { token, apiUrl: opts.apiUrlFlag ? opts.apiUrl : resolveApiUrl() };
18282
+ }
18283
+ function runInitAgents(cwd) {
18284
+ const dir = findProjectLinkDir(cwd);
18285
+ if (!dir) {
18286
+ process.stderr.write(
18287
+ "This folder is not linked to a Kryd project (no .kryd/project.json up to the git root), so there is no block to refresh.\nRun `kryd init` to link it \u2014 that writes the Kryd block into AGENTS.md as well.\n"
18288
+ );
18057
18289
  process.exitCode = 1;
18058
18290
  return;
18059
18291
  }
18292
+ const { note, block } = installAgentsBlock(dir);
18293
+ process.stdout.write(block === "unchanged" ? `${note}The Kryd block in AGENTS.md is up to date.
18294
+ ` : note);
18295
+ }
18296
+ async function runInit(opts) {
18297
+ const cwd = opts.cwd ?? process.cwd();
18298
+ if (opts.agents) {
18299
+ runInitAgents(cwd);
18300
+ return;
18301
+ }
18302
+ let apiUrl = resolveApiUrl(opts.apiUrl);
18303
+ const session = await settleLogin({
18304
+ apiUrl,
18305
+ ...opts.apiUrl ? { apiUrlFlag: opts.apiUrl } : {},
18306
+ ...opts.io ? { io: opts.io } : {},
18307
+ offerLogin: opts.offerLogin !== false,
18308
+ command: "kryd init"
18309
+ });
18310
+ if (!session) return;
18311
+ const { token } = session;
18312
+ apiUrl = session.apiUrl;
18060
18313
  const existingLink = directoryIsLinked(cwd);
18061
18314
  if (existingLink) {
18062
18315
  const remote = hasRemote(cwd, "kryd");
@@ -18066,6 +18319,7 @@ async function runInit(opts) {
18066
18319
  `This folder is already linked to ${existingLink.projectId} (.kryd/project.json).
18067
18320
  Running \`kryd init\` again would create a SECOND project and leave that one orphaned.
18068
18321
  To deploy it: \`kryd push\`. To start over: \`kryd project rm\` first, or delete .kryd/project.json to unlink this folder.
18322
+ To write or refresh the Kryd block in AGENTS.md: \`kryd init --agents\`.
18069
18323
  `
18070
18324
  );
18071
18325
  process.exitCode = 1;
@@ -18115,10 +18369,10 @@ To deploy it: \`kryd push\`. To start over: \`kryd project rm\` first, or delete
18115
18369
  try {
18116
18370
  saveProjectLink(cwd, { projectId: project2.id });
18117
18371
  try {
18118
- const session = await whoami(apiUrl, token);
18372
+ const session2 = await whoami(apiUrl, token);
18119
18373
  rememberLinkAccount(cwd, {
18120
- accountId: session.accountId,
18121
- accountSlug: session.accountSlug
18374
+ accountId: session2.accountId,
18375
+ accountSlug: session2.accountSlug
18122
18376
  });
18123
18377
  } catch {
18124
18378
  }
@@ -18177,9 +18431,10 @@ Scheduled and triggered work runs on your production deploy; preview branches bu
18177
18431
  Preview branches deploy to https://<branch>-<hash>-${project2.subdomain}.${PREVIEW_TLD}
18178
18432
  ` + (declaration?.workflowsPath ? `Workflows in ${declaration.workflowsPath}/ run as a worker beside it on production deploys; previews run the web app only.
18179
18433
  ` : "");
18434
+ const agentsNote = installAgentsBlock(cwd).note;
18180
18435
  process.stdout.write(
18181
18436
  `Linked "${name}" (${project2.framework}) \u2192 ${repo.htmlUrl}
18182
- ` + routing + `${linkNote}
18437
+ ` + routing + `${linkNote}` + agentsNote + `
18183
18438
  ${nextStep}`
18184
18439
  );
18185
18440
  } catch (err) {
@@ -18650,6 +18905,17 @@ async function runDbAdd(opts) {
18650
18905
  process.exitCode = 1;
18651
18906
  return;
18652
18907
  }
18908
+ if (opts.preview && opts.connectionString !== void 0) {
18909
+ process.stderr.write(
18910
+ "A preview database is always managed by Kryd \u2014 --preview cannot take a --connection-string.\n"
18911
+ );
18912
+ process.exitCode = 1;
18913
+ return;
18914
+ }
18915
+ if (opts.preview) {
18916
+ await addPreviewDatabase(apiUrl, token, project2);
18917
+ return;
18918
+ }
18653
18919
  let mode;
18654
18920
  let connectionString;
18655
18921
  if (opts.connectionString !== void 0) {
@@ -18695,6 +18961,46 @@ The connection string will be injected as DATABASE_URL on your next deploy.
18695
18961
  await reportError(err);
18696
18962
  }
18697
18963
  }
18964
+ async function addPreviewDatabase(apiUrl, token, project2) {
18965
+ try {
18966
+ const db2 = await attachDatabase(apiUrl, token, {
18967
+ projectId: project2,
18968
+ mode: "shared",
18969
+ environment: "preview"
18970
+ });
18971
+ if (db2.environment !== "preview") {
18972
+ process.stderr.write(
18973
+ `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.
18974
+ `
18975
+ );
18976
+ process.exitCode = 1;
18977
+ return;
18978
+ }
18979
+ process.stdout.write(`Provisioning a preview database for ${project2}\u2026
18980
+ `);
18981
+ const result = await pollResourceUntilTerminal(
18982
+ () => getDatabaseStatus(apiUrl, token, project2, "preview")
18983
+ );
18984
+ reportSettleResult(result, {
18985
+ readyMsg: `Attached preview database ${db2.id} to ${project2}. It starts empty.
18986
+ Every preview deploy gets it as DATABASE_URL from its next deploy; production keeps its own.
18987
+ `,
18988
+ failedLabel: "Preview database provisioning",
18989
+ retryCmd: `kryd db status ${project2}`
18990
+ });
18991
+ } catch (err) {
18992
+ await reportError(err);
18993
+ }
18994
+ }
18995
+ async function readPreviewDatabase(apiUrl, token, project2) {
18996
+ try {
18997
+ const row = await getDatabaseStatus(apiUrl, token, project2, "preview");
18998
+ return row.environment === "preview" ? row : null;
18999
+ } catch (err) {
19000
+ if (err instanceof ApiError && err.status === 404) return null;
19001
+ throw err;
19002
+ }
19003
+ }
18698
19004
  function reportSettleResult(result, opts) {
18699
19005
  if (result.status === "ready") {
18700
19006
  process.stdout.write(opts.readyMsg);
@@ -18915,6 +19221,8 @@ async function describeProjectResources(apiUrl, token, projectId) {
18915
19221
  const out = ["its deployed app, its containers and its built images"];
18916
19222
  const db2 = await getDatabaseStatus(apiUrl, token, projectId).catch(() => null);
18917
19223
  if (db2) out.push("its Postgres database \u2014 and all the data in it");
19224
+ const preview = db2 ? await readPreviewDatabase(apiUrl, token, projectId).catch(() => null) : null;
19225
+ if (preview) out.push("its preview database \u2014 and all the data in it");
18918
19226
  const storage2 = await getStorageStatus(apiUrl, token, projectId).catch(() => null);
18919
19227
  if (storage2) out.push("its object-storage bucket \u2014 and every object in it");
18920
19228
  out.push("its Git repository on the forge, and its push token");
@@ -18933,6 +19241,23 @@ async function runDbRemove(opts) {
18933
19241
  reportNotLinked("kryd db remove <projectId>");
18934
19242
  return;
18935
19243
  }
19244
+ if (opts.preview) {
19245
+ await removePreviewDatabase(apiUrl, token, project2, opts);
19246
+ return;
19247
+ }
19248
+ try {
19249
+ if (await readPreviewDatabase(apiUrl, token, project2)) {
19250
+ process.stderr.write(
19251
+ `${project2} has a preview database \u2014 remove the preview first (\`kryd db remove --preview\`).
19252
+ `
19253
+ );
19254
+ process.exitCode = 1;
19255
+ return;
19256
+ }
19257
+ } catch (err) {
19258
+ await reportError(err);
19259
+ return;
19260
+ }
18936
19261
  const proceed = await confirmResourceRemoval({
18937
19262
  yes: opts.yes,
18938
19263
  io: opts.io,
@@ -18959,6 +19284,105 @@ async function runDbRemove(opts) {
18959
19284
  await reportError(err);
18960
19285
  }
18961
19286
  }
19287
+ async function removePreviewDatabase(apiUrl, token, project2, opts) {
19288
+ try {
19289
+ if (!await readPreviewDatabase(apiUrl, token, project2)) {
19290
+ process.stderr.write(`${project2} has no preview database.
19291
+ `);
19292
+ process.exitCode = 1;
19293
+ return;
19294
+ }
19295
+ } catch (err) {
19296
+ await reportError(err);
19297
+ return;
19298
+ }
19299
+ const proceed = await confirmResourceRemoval({
19300
+ yes: opts.yes,
19301
+ io: opts.io,
19302
+ subject: `the preview database from ${project2}`,
19303
+ 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.`,
19304
+ declined: `Left the preview database on ${project2} in place.
19305
+ `
19306
+ });
19307
+ if (!proceed) return;
19308
+ try {
19309
+ await detachDatabase(apiUrl, token, project2, "preview");
19310
+ process.stdout.write(`Removing the preview database from ${project2}\u2026
19311
+ `);
19312
+ const result = await pollResourceDetached(
19313
+ () => getDatabaseStatus(apiUrl, token, project2, "preview")
19314
+ );
19315
+ reportDetachResult(result, {
19316
+ goneMsg: `Removed the preview database from ${project2}.
19317
+ Preview deploys use the production database again from their next deploy.
19318
+ `,
19319
+ failedLabel: "Preview database removal",
19320
+ retryCmd: `kryd db remove --preview ${project2}`
19321
+ });
19322
+ } catch (err) {
19323
+ await reportError(err);
19324
+ }
19325
+ }
19326
+ async function runDbReset(opts) {
19327
+ if (!opts.preview) {
19328
+ process.stderr.write(
19329
+ "Only the preview database can be reset \u2014 run `kryd db reset --preview`. Production is never emptied.\n"
19330
+ );
19331
+ process.exitCode = 1;
19332
+ return;
19333
+ }
19334
+ const apiUrl = resolveApiUrl(opts.apiUrl);
19335
+ const token = resolveToken();
19336
+ if (!token) {
19337
+ process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19338
+ process.exitCode = 1;
19339
+ return;
19340
+ }
19341
+ const project2 = resolveProjectId(opts.project, opts.cwd);
19342
+ if (!project2) {
19343
+ reportNotLinked("kryd db reset --preview <projectId>");
19344
+ return;
19345
+ }
19346
+ try {
19347
+ if (!await readPreviewDatabase(apiUrl, token, project2)) {
19348
+ process.stderr.write(
19349
+ `${project2} has no preview database \u2014 add one with \`kryd db add --preview ${project2}\`.
19350
+ `
19351
+ );
19352
+ process.exitCode = 1;
19353
+ return;
19354
+ }
19355
+ } catch (err) {
19356
+ await reportError(err);
19357
+ return;
19358
+ }
19359
+ const proceed = await confirmResourceRemoval({
19360
+ yes: opts.yes,
19361
+ io: opts.io,
19362
+ subject: `everything in the preview database of ${project2}`,
19363
+ 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.`,
19364
+ declined: `Left the preview database of ${project2} as it is.
19365
+ `
19366
+ });
19367
+ if (!proceed) return;
19368
+ try {
19369
+ await resetPreviewDatabase(apiUrl, token, project2);
19370
+ process.stdout.write(`Emptying the preview database of ${project2}\u2026
19371
+ `);
19372
+ const result = await pollResourceUntilTerminal(
19373
+ () => getDatabaseStatus(apiUrl, token, project2, "preview")
19374
+ );
19375
+ reportSettleResult(result, {
19376
+ readyMsg: `The preview database of ${project2} is empty again.
19377
+ Its credentials did not change, so no deploy is needed.
19378
+ `,
19379
+ failedLabel: "Preview database reset",
19380
+ retryCmd: `kryd db status ${project2}`
19381
+ });
19382
+ } catch (err) {
19383
+ await reportError(err);
19384
+ }
19385
+ }
18962
19386
  async function runAiAdd(opts) {
18963
19387
  const apiUrl = resolveApiUrl(opts.apiUrl);
18964
19388
  const token = resolveToken();
@@ -19029,26 +19453,34 @@ AI_GATEWAY_URL and AI_GATEWAY_TOKEN stop being injected from your next deploy (k
19029
19453
  await reportError(err);
19030
19454
  }
19031
19455
  }
19032
- async function reportResourceStatus(opts) {
19456
+ async function readResourceStatus(opts) {
19033
19457
  try {
19034
- const result = await opts.read();
19035
- if (result.status === "failed") {
19036
- process.stdout.write(
19037
- `${opts.project}: ${opts.noun} failed \u2014 ${result.failureReason ?? "unknown error"}.
19038
- Remove it before attaching another.
19039
- `
19040
- );
19041
- return;
19042
- }
19043
- process.stdout.write(`${opts.project}: ${opts.noun} ${result.status}.
19044
- `);
19458
+ return toResourceState(await opts.read());
19045
19459
  } catch (err) {
19046
19460
  if (err instanceof ApiError && err.status === 404 && await projectIsVisible(opts.token, opts.project) === true) {
19047
- process.stdout.write(`${opts.project}: no ${opts.noun} attached.
19048
- `);
19049
- return;
19461
+ return NOT_ATTACHED;
19050
19462
  }
19051
19463
  await reportError(err);
19464
+ return null;
19465
+ }
19466
+ }
19467
+ var NOT_ATTACHED = { attached: false, status: null, failureReason: null };
19468
+ function toResourceState(row) {
19469
+ return { attached: true, status: row.status, failureReason: row.failureReason ?? null };
19470
+ }
19471
+ function printResourceStatus(state, noun, project2) {
19472
+ if (!state.attached) {
19473
+ process.stdout.write(`${project2}: no ${noun} attached.
19474
+ `);
19475
+ } else if (state.status === "failed") {
19476
+ process.stdout.write(
19477
+ `${project2}: ${noun} failed \u2014 ${state.failureReason ?? "unknown error"}.
19478
+ Remove it before attaching another.
19479
+ `
19480
+ );
19481
+ } else {
19482
+ process.stdout.write(`${project2}: ${noun} ${state.status}.
19483
+ `);
19052
19484
  }
19053
19485
  }
19054
19486
  async function runDbStatus(opts) {
@@ -19064,12 +19496,46 @@ async function runDbStatus(opts) {
19064
19496
  reportNotLinked("kryd db status <projectId>");
19065
19497
  return;
19066
19498
  }
19067
- await reportResourceStatus({
19499
+ const production = await readResourceStatus({
19068
19500
  read: () => getDatabaseStatus(apiUrl, token, project2),
19069
- noun: "database",
19070
19501
  project: project2,
19071
19502
  token
19072
19503
  });
19504
+ if (!production) return;
19505
+ if (!opts.json) printResourceStatus(production, "database", project2);
19506
+ if (!production.attached) {
19507
+ if (opts.json) printJson2({ production, preview: NOT_ATTACHED });
19508
+ return;
19509
+ }
19510
+ try {
19511
+ const preview = await readPreviewDatabase(apiUrl, token, project2);
19512
+ if (opts.json) {
19513
+ printJson2({ production, preview: preview ? toResourceState(preview) : NOT_ATTACHED });
19514
+ } else if (!preview) {
19515
+ process.stdout.write(
19516
+ `${project2}: no preview database \u2014 preview deploys use the production database.
19517
+ `
19518
+ );
19519
+ } else if (preview.status === "failed") {
19520
+ process.stdout.write(
19521
+ `${project2}: preview database failed \u2014 ${preview.failureReason ?? "unknown error"}.
19522
+ Remove it with \`kryd db remove --preview\` and add it again.
19523
+ `
19524
+ );
19525
+ } else if (preview.status === "ready") {
19526
+ process.stdout.write(
19527
+ `${project2}: preview database ready \u2014 preview deploys get it as DATABASE_URL.
19528
+ `
19529
+ );
19530
+ } else {
19531
+ process.stdout.write(
19532
+ `${project2}: preview database ${preview.status} \u2014 preview deploys use the production database until it is ready.
19533
+ `
19534
+ );
19535
+ }
19536
+ } catch (err) {
19537
+ await reportError(err);
19538
+ }
19073
19539
  }
19074
19540
  async function runStorageStatus(opts) {
19075
19541
  const apiUrl = resolveApiUrl(opts.apiUrl);
@@ -19084,12 +19550,14 @@ async function runStorageStatus(opts) {
19084
19550
  reportNotLinked("kryd storage status <projectId>");
19085
19551
  return;
19086
19552
  }
19087
- await reportResourceStatus({
19553
+ const state = await readResourceStatus({
19088
19554
  read: () => getStorageStatus(apiUrl, token, project2),
19089
- noun: "object storage",
19090
19555
  project: project2,
19091
19556
  token
19092
19557
  });
19558
+ if (!state) return;
19559
+ if (opts.json) printJson2(state);
19560
+ else printResourceStatus(state, "object storage", project2);
19093
19561
  }
19094
19562
  async function runAiStatus(opts) {
19095
19563
  const apiUrl = resolveApiUrl(opts.apiUrl);
@@ -19106,6 +19574,10 @@ async function runAiStatus(opts) {
19106
19574
  }
19107
19575
  try {
19108
19576
  const status = await getAiStatus(apiUrl, token, project2);
19577
+ if (opts.json) {
19578
+ printJson2(status);
19579
+ return;
19580
+ }
19109
19581
  if (!status.enabled) {
19110
19582
  process.stdout.write(`${project2}: no AI endpoint.
19111
19583
  `);
@@ -19220,6 +19692,14 @@ async function runWorkflowStatus(opts) {
19220
19692
  }
19221
19693
  try {
19222
19694
  const status = await getWorkflowStatus(apiUrl, token, project2);
19695
+ if (opts.json) {
19696
+ printJson2({
19697
+ enabled: status.enabled,
19698
+ tokenExpiresAt: status.tokenExpiresAt,
19699
+ tokenExpiresInDays: status.tokenExpiresAt ? daysUntil(status.tokenExpiresAt) : null
19700
+ });
19701
+ return;
19702
+ }
19223
19703
  if (!status.enabled) {
19224
19704
  process.stdout.write(`${project2}: no workflows.
19225
19705
  `);
@@ -19375,6 +19855,10 @@ async function runDomainStatus(opts) {
19375
19855
  const own = list.projectHost ? `${ctx.project} is always served at ${list.projectHost}.
19376
19856
  ` : "";
19377
19857
  if (!opts.host) {
19858
+ if (opts.json) {
19859
+ printJson2(list);
19860
+ return;
19861
+ }
19378
19862
  if (list.domains.length === 0) {
19379
19863
  process.stdout.write(`${ctx.project}: no custom domains.
19380
19864
  ${own}`);
@@ -19399,6 +19883,10 @@ ${own}`);
19399
19883
  process.exitCode = 1;
19400
19884
  return;
19401
19885
  }
19886
+ if (opts.json) {
19887
+ printJson2(d);
19888
+ return;
19889
+ }
19402
19890
  let out = `${d.host}: ${DOMAIN_STATUS_TEXT[d.status]}.
19403
19891
  `;
19404
19892
  if (d.redirectTo) out += ` \u2192 redirects to ${d.redirectTo}
@@ -19453,7 +19941,7 @@ async function runWorkflowInit(opts) {
19453
19941
  }
19454
19942
  const workerName = workerNameFrom(opts.name ?? basename2(target));
19455
19943
  const files = scaffoldFiles(language, workerName);
19456
- const clashes = files.map((f) => f.path).filter((p) => existsSync3(join3(target, p)));
19944
+ const clashes = files.map((f) => f.path).filter((p) => existsSync4(join4(target, p)));
19457
19945
  if (clashes.length > 0) {
19458
19946
  process.stderr.write(
19459
19947
  `Refusing to overwrite: ${clashes.join(", ")}.
@@ -19463,12 +19951,22 @@ Nothing was written. Run this in an empty directory, or pass --dir <path>.
19463
19951
  process.exitCode = 1;
19464
19952
  return;
19465
19953
  }
19954
+ if (opts.link !== false) {
19955
+ const session = await settleLogin({
19956
+ apiUrl: resolveApiUrl(opts.apiUrl),
19957
+ ...opts.apiUrl ? { apiUrlFlag: opts.apiUrl } : {},
19958
+ ...opts.io ? { io: opts.io } : {},
19959
+ offerLogin: true,
19960
+ command: "kryd workflow init"
19961
+ });
19962
+ if (!session) return;
19963
+ }
19466
19964
  const written = [];
19467
19965
  try {
19468
19966
  for (const file2 of files) {
19469
- const full = join3(target, file2.path);
19967
+ const full = join4(target, file2.path);
19470
19968
  mkdirSync2(dirname2(full), { recursive: true });
19471
- writeFileSync2(full, file2.contents);
19969
+ writeFileSync3(full, file2.contents);
19472
19970
  written.push(full);
19473
19971
  }
19474
19972
  } catch (err) {
@@ -19505,6 +20003,7 @@ To add a worker to the app in that repository instead, run \`kryd workflow init\
19505
20003
  process.exitCode = 1;
19506
20004
  return;
19507
20005
  }
20006
+ process.stdout.write(installAgentsBlock(target).note);
19508
20007
  const next = [];
19509
20008
  if (opts.dir) next.push(`cd ${opts.dir}`);
19510
20009
  const first = firstCommand(language);
@@ -19529,6 +20028,7 @@ HATCHET_CLIENT_TOKEN is injected into your container on production deploys (prev
19529
20028
  process.exitCode = 0;
19530
20029
  await runInit({
19531
20030
  cwd: target,
20031
+ offerLogin: false,
19532
20032
  ...opts.name ? { name: opts.name } : {},
19533
20033
  ...opts.apiUrl ? { apiUrl: opts.apiUrl } : {}
19534
20034
  });
@@ -19553,7 +20053,7 @@ function scaffoldContainedWorker(opts) {
19553
20053
  "This directory is not linked to a Kryd project, so there is no app to add a worker to.\nLink your app first (`kryd init`), then run this again \u2014 or pass --only for a project that runs nothing but a worker.\n"
19554
20054
  );
19555
20055
  process.exitCode = 1;
19556
- return;
20056
+ return false;
19557
20057
  }
19558
20058
  const raw = (opts.path ?? DEFAULT_WORKFLOWS_PATH).trim();
19559
20059
  const segments = raw.split("/").filter((seg) => seg !== "" && seg !== ".");
@@ -19563,14 +20063,14 @@ function scaffoldContainedWorker(opts) {
19563
20063
  `
19564
20064
  );
19565
20065
  process.exitCode = 1;
19566
- return;
20066
+ return false;
19567
20067
  }
19568
20068
  const folder = segments.join("/");
19569
- const declarationPath = join3(target, DECLARATION_FILE);
20069
+ const declarationPath = join4(target, DECLARATION_FILE);
19570
20070
  let existing = {};
19571
20071
  let existingText = null;
19572
- if (existsSync3(declarationPath)) {
19573
- existingText = readFileSync3(declarationPath, "utf8");
20072
+ if (existsSync4(declarationPath)) {
20073
+ existingText = readFileSync4(declarationPath, "utf8");
19574
20074
  let parsed;
19575
20075
  try {
19576
20076
  parsed = JSON.parse(existingText);
@@ -19583,7 +20083,7 @@ function scaffoldContainedWorker(opts) {
19583
20083
  `
19584
20084
  );
19585
20085
  process.exitCode = 1;
19586
- return;
20086
+ return false;
19587
20087
  }
19588
20088
  existing = parsed;
19589
20089
  if ("kind" in existing) {
@@ -19592,7 +20092,7 @@ function scaffoldContainedWorker(opts) {
19592
20092
  `
19593
20093
  );
19594
20094
  process.exitCode = 1;
19595
- return;
20095
+ return false;
19596
20096
  }
19597
20097
  if ("workflows" in existing || existing["web"] === false) {
19598
20098
  process.stderr.write(
@@ -19600,7 +20100,7 @@ function scaffoldContainedWorker(opts) {
19600
20100
  `
19601
20101
  );
19602
20102
  process.exitCode = 1;
19603
- return;
20103
+ return false;
19604
20104
  }
19605
20105
  }
19606
20106
  const workerName = workerNameFrom(opts.name ?? basename2(target));
@@ -19608,7 +20108,7 @@ function scaffoldContainedWorker(opts) {
19608
20108
  path: `${folder}/${f.path}`,
19609
20109
  contents: f.contents
19610
20110
  }));
19611
- const clashes = files.map((f) => f.path).filter((p) => existsSync3(join3(target, p)));
20111
+ const clashes = files.map((f) => f.path).filter((p) => existsSync4(join4(target, p)));
19612
20112
  if (clashes.length > 0) {
19613
20113
  process.stderr.write(
19614
20114
  `Refusing to overwrite: ${clashes.join(", ")}.
@@ -19616,18 +20116,18 @@ Nothing was written. Pick another folder with --path <folder>.
19616
20116
  `
19617
20117
  );
19618
20118
  process.exitCode = 1;
19619
- return;
20119
+ return false;
19620
20120
  }
19621
20121
  const declaration = { ...existing, workflows: { path: folder } };
19622
20122
  const written = [];
19623
20123
  try {
19624
20124
  for (const file2 of files) {
19625
- const full = join3(target, file2.path);
20125
+ const full = join4(target, file2.path);
19626
20126
  mkdirSync2(dirname2(full), { recursive: true });
19627
- writeFileSync2(full, file2.contents);
20127
+ writeFileSync3(full, file2.contents);
19628
20128
  written.push(full);
19629
20129
  }
19630
- writeFileSync2(declarationPath, `${JSON.stringify(declaration, null, 2)}
20130
+ writeFileSync3(declarationPath, `${JSON.stringify(declaration, null, 2)}
19631
20131
  `);
19632
20132
  } catch (err) {
19633
20133
  for (const path of written.reverse()) {
@@ -19638,7 +20138,7 @@ Nothing was written. Pick another folder with --path <folder>.
19638
20138
  }
19639
20139
  try {
19640
20140
  if (existingText === null) rmSync2(declarationPath, { force: true });
19641
- else writeFileSync2(declarationPath, existingText);
20141
+ else writeFileSync3(declarationPath, existingText);
19642
20142
  } catch {
19643
20143
  }
19644
20144
  process.stderr.write(
@@ -19647,14 +20147,15 @@ Nothing was left behind.
19647
20147
  `
19648
20148
  );
19649
20149
  process.exitCode = 1;
19650
- return;
20150
+ return false;
19651
20151
  }
20152
+ const agents = installAgentsBlock(target);
19652
20153
  const next = [];
19653
20154
  const first = firstCommand(language);
19654
20155
  if (first) next.push(`(cd ${folder} && ${first})`);
19655
20156
  next.push(
19656
20157
  "kryd workflow add",
19657
- `git add ${DECLARATION_FILE} ${folder} && git commit -m "add workflows"`,
20158
+ `git add ${[DECLARATION_FILE, folder, ...agents.changed].join(" ")} && git commit -m "add workflows"`,
19658
20159
  "kryd push"
19659
20160
  );
19660
20161
  process.stdout.write(
@@ -19667,9 +20168,10 @@ Next: ${next.map((c) => `\`${c}\``).join(", then ")}.
19667
20168
  HATCHET_CLIENT_TOKEN is injected into your app and your worker on production deploys (previews get none) \u2014 your app triggers tasks with it; it is never available locally.
19668
20169
  ` + // The app is still built (and type-checked) from the root, so a tsconfig that includes every
19669
20170
  // file would compile the worker too — against an SDK the app does not depend on.
19670
- (existsSync3(join3(target, "tsconfig.json")) ? `Add "${folder}" to the "exclude" of your tsconfig.json, so your app's build does not type-check the worker.
19671
- ` : "")
20171
+ (existsSync4(join4(target, "tsconfig.json")) ? `Add "${folder}" to the "exclude" of your tsconfig.json, so your app's build does not type-check the worker.
20172
+ ` : "") + agents.note
19672
20173
  );
20174
+ return true;
19673
20175
  }
19674
20176
  async function runStorageRemove(opts) {
19675
20177
  const apiUrl = resolveApiUrl(opts.apiUrl);
@@ -19749,6 +20251,11 @@ async function runProjectList(opts) {
19749
20251
  await reportError(err);
19750
20252
  return;
19751
20253
  }
20254
+ if (opts.json) {
20255
+ const linked = resolveProjectId(void 0, opts.cwd);
20256
+ printJson2({ items: projects.map((p) => ({ ...p, linked: p.id === linked })) });
20257
+ return;
20258
+ }
19752
20259
  if (projects.length === 0) {
19753
20260
  process.stdout.write("No projects yet \u2014 run `kryd init` in a project directory.\n");
19754
20261
  return;
@@ -19800,6 +20307,10 @@ async function runEnvList(opts) {
19800
20307
  }
19801
20308
  try {
19802
20309
  const vars = await listEnvVars(apiUrl, token, project2);
20310
+ if (opts.json) {
20311
+ printJson2({ items: vars });
20312
+ return;
20313
+ }
19803
20314
  if (vars.length === 0) {
19804
20315
  process.stdout.write(
19805
20316
  `No environment variables for ${project2} yet.
@@ -19885,7 +20396,7 @@ async function runEnvPull(opts) {
19885
20396
  const root = findProjectLinkDir(cwd) ?? cwd;
19886
20397
  const outRel = opts.out ?? ".env.kryd";
19887
20398
  const outPath = resolve2(root, outRel);
19888
- if (opts.out !== void 0 && existsSync3(outPath) && !opts.force) {
20399
+ if (opts.out !== void 0 && existsSync4(outPath) && !opts.force) {
19889
20400
  process.stderr.write(
19890
20401
  `Refusing to overwrite ${outRel} \u2014 pass --force to replace it (or omit --out to write .env.kryd).
19891
20402
  `
@@ -20159,7 +20670,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
20159
20670
  await reportError(err);
20160
20671
  }
20161
20672
  }
20162
- var CLI_VERSION = true ? "0.16.0" : "0.0.0-dev";
20673
+ var CLI_VERSION = true ? "0.18.0" : "0.0.0-dev";
20163
20674
  var program = new Command();
20164
20675
  program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
20165
20676
  program.hook("postAction", async () => {
@@ -20169,7 +20680,7 @@ program.command("login").description("Sign in via the browser (default) and stor
20169
20680
  "--token <token>",
20170
20681
  "supply a token instead of the browser flow (prefer $KRYD_TOKEN \u2014 flags are visible in `ps`)"
20171
20682
  ).option("--api-url <url>", "control-plane API base URL").option("--dashboard-url <url>", "dashboard base URL for the browser approve page (default app.kryd.eu)").action((opts) => runLogin(opts));
20172
- program.command("whoami").description("Show the current account").option("--api-url <url>", "control-plane API base URL").action((opts) => runWhoami(opts));
20683
+ program.command("whoami").description("Show the current account").option("--json", "print the user and their accounts as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runWhoami(opts));
20173
20684
  var accountCommand = program.command("account").description("The accounts you belong to, and which one commands run in");
20174
20685
  accountCommand.command("list").description("List your accounts and your role in each; * marks the one commands run in").option("--json", "print the accounts as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runAccountList(opts));
20175
20686
  accountCommand.command("use <slug>").description("Run every following command in this account").option("--api-url <url>", "control-plane API base URL").action((slug, opts) => runAccountUse(slug, opts));
@@ -20182,7 +20693,9 @@ program.command("git-credential <operation>", { hidden: true }).description("git
20182
20693
  ...opts.apiUrl ? { apiUrl: opts.apiUrl } : {}
20183
20694
  });
20184
20695
  });
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(
20696
+ program.command("init").description(
20697
+ "Link this project's repo + register its push webhook; not logged in \u2192 offers the browser login first (terminal only)"
20698
+ ).option("--name <name>", "project name (defaults to package.json name / dir)").option(
20186
20699
  "--framework <framework>",
20187
20700
  'react-router | nextjs | vite-spa | node (auto-detected if omitted; `workflow` comes from a committed kryd.json with "web": false)'
20188
20701
  ).option(
@@ -20195,7 +20708,10 @@ program.command("init").description("Link this project's repo + register its pus
20195
20708
  "--public",
20196
20709
  // KRYD-184a. Chosen at creation; there is no command to flip it afterwards yet (KRYD-341).
20197
20710
  "create the repository PUBLIC \u2014 readable by anyone signed in to the forge (default: private). Set at creation only"
20198
- ).option("--api-url <url>", "control-plane API base URL").action((opts) => runInit(opts));
20711
+ ).option("--api-url <url>", "control-plane API base URL").option(
20712
+ "--agents",
20713
+ "only write or refresh the Kryd block in AGENTS.md of the linked project (no login, no API call) \u2014 e.g. after updating the CLI"
20714
+ ).action((opts) => runInit(opts));
20199
20715
  program.command("logs [target]").description(
20200
20716
  "Follow logs. Default: a deploy's build/deploy logs ([target]=deployment id, latest if omitted). With --runtime: a container's stdout/stderr \u2014 [target]=a project id tails its live app, a deployment id (dpl_\u2026) tails THAT deploy's container (incl. a failed one, to see why it crashed). With --worker: a project's workflow worker, also when the project has a web app."
20201
20717
  ).option(
@@ -20232,7 +20748,7 @@ program.command("redeploy [project]").description(
20232
20748
  "Redeploy the current live commit (no rebuild) to apply config/env changes, and follow it live"
20233
20749
  ).option("--app <name>", "a project with several web apps: which app to redeploy").option("--logs", LOGS_FLAG_HELP).option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runRedeploy({ ...opts, project: project2 }));
20234
20750
  var project = program.command("project").description("Manage your projects");
20235
- project.command("list").description("List your projects \u2014 name, subdomain, status and id").option("--api-url <url>", "control-plane API base URL").action((opts) => runProjectList(opts));
20751
+ project.command("list").description("List your projects \u2014 name, subdomain, status and id").option("--json", "print { items } as JSON on stdout, each with `linked`").option("--api-url <url>", "control-plane API base URL").action((opts) => runProjectList(opts));
20236
20752
  project.command("rm [project]").description(
20237
20753
  "Permanently delete a project and everything Kryd created for it (by name or id; asks you to type its name)"
20238
20754
  ).option("--yes", "skip the confirmation (for non-interactive use)").option("--api-url <url>", "control-plane API base URL").action((projectArg, opts) => runProjectRemove({ ...opts, project: projectArg }));
@@ -20243,15 +20759,19 @@ var db = program.command("db").description("Manage project databases");
20243
20759
  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
20760
  "--connection-string <url>",
20245
20761
  "attach an existing database (BYO) by pasting its connection string"
20762
+ ).option(
20763
+ "--preview",
20764
+ "attach an empty preview database that every preview deploy gets instead of production's"
20246
20765
  ).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 }));
20766
+ 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 }));
20767
+ 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 }));
20768
+ db.command("status [project]").description("Show the project's database and preview database, and what state they are in").option("--json", "print { production, preview } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbStatus({ ...opts, project: project2 }));
20249
20769
  var storage = program.command("storage").description("Manage project object storage");
20250
20770
  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
20771
  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 }));
20252
- storage.command("status [project]").description("Show whether the project has object storage, and what state it is in").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageStatus({ ...opts, project: project2 }));
20772
+ storage.command("status [project]").description("Show whether the project has object storage, and what state it is in").option("--json", "print { attached, status, failureReason } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageStatus({ ...opts, project: project2 }));
20253
20773
  var env = program.command("env").description("Manage your app's environment variables");
20254
- env.command("list [project]").description("List the project's environment variables (names and origin \u2014 never values)").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runEnvList({ ...opts, project: project2 }));
20774
+ env.command("list [project]").description("List the project's environment variables (names and origin \u2014 never values)").option("--json", "print { items } as JSON on stdout (never values)").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runEnvList({ ...opts, project: project2 }));
20255
20775
  env.command("set <key> [project]").description(
20256
20776
  "Set one of your environment variables \u2014 `KEY=VALUE`, or `KEY` with --stdin / a prompt"
20257
20777
  ).option(
@@ -20283,11 +20803,11 @@ env.command("pull [project]").description(
20283
20803
  var ai = program.command("ai").description("Manage the app's EU AI gateway");
20284
20804
  ai.command("add [project]").description("Give the project an authenticated EU AI endpoint (injects on next deploy)").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runAiAdd({ ...opts, project: project2 }));
20285
20805
  ai.command("remove [project]").description("Stop injecting the project's AI endpoint (asks for confirmation)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runAiRemove({ ...opts, project: project2 }));
20286
- ai.command("status [project]").description("Show whether the project has an AI endpoint, and what it is").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runAiStatus({ ...opts, project: project2 }));
20806
+ ai.command("status [project]").description("Show whether the project has an AI endpoint, and what it is").option("--json", "print { enabled, url } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runAiStatus({ ...opts, project: project2 }));
20287
20807
  var workflow = program.command("workflow").description("Manage the project's durable workflows (its own workflow tenant)");
20288
20808
  workflow.command("add [project]").description("Give the project a workflow tenant and inject HATCHET_CLIENT_TOKEN on next deploy").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runWorkflowAdd({ ...opts, project: project2 }));
20289
20809
  workflow.command("remove [project]").description("Revoke the project's workflow token and stop injecting it (asks for confirmation)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runWorkflowRemove({ ...opts, project: project2 }));
20290
- workflow.command("status [project]").description("Show whether the project has workflows, and when its token expires").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runWorkflowStatus({ ...opts, project: project2 }));
20810
+ workflow.command("status [project]").description("Show whether the project has workflows, and when its token expires").option("--json", "print { enabled, tokenExpiresAt, tokenExpiresInDays } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runWorkflowStatus({ ...opts, project: project2 }));
20291
20811
  workflow.command("init").description(
20292
20812
  "Add a workflow worker to the linked app here (in ./workflows, declared in kryd.json); --only scaffolds a worker-only project instead and links it"
20293
20813
  ).requiredOption(
@@ -20300,7 +20820,7 @@ domain2.command("add <host>").description("Add a custom domain and print the DNS
20300
20820
  "redirect instead of serving: every request answers 301 to https://<host>, path and query kept"
20301
20821
  ).option("--api-url <url>", "control-plane API base URL").action((host, opts) => runDomainAdd({ ...opts, host }));
20302
20822
  domain2.command("remove <host>").description("Remove a custom domain \u2014 its route and certificate go (asks for confirmation)").option("--project <projectId>", "the project (default: the one linked here)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((host, opts) => runDomainRemove({ ...opts, host }));
20303
- domain2.command("status [host]").description("Show the project's custom domains, or one in detail with what is missing").option("--project <projectId>", "the project (default: the one linked here)").option("--api-url <url>", "control-plane API base URL").action((host, opts) => runDomainStatus({ ...opts, host }));
20823
+ domain2.command("status [host]").description("Show the project's custom domains, or one in detail with what is missing").option("--project <projectId>", "the project (default: the one linked here)").option("--json", "print the domain list, or the one domain, as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((host, opts) => runDomainStatus({ ...opts, host }));
20304
20824
  var support = program.command("support").description(
20305
20825
  "Ask Kryd support a question, report a bug or a wish, and read the answer \u2014 non-interactive, with --json for agents"
20306
20826
  );
@@ -20318,7 +20838,7 @@ function invokedDirectly() {
20318
20838
  const entry = process.argv[1];
20319
20839
  if (!entry) return false;
20320
20840
  try {
20321
- return import.meta.url === pathToFileURL(realpathSync(entry)).href;
20841
+ return import.meta.url === pathToFileURL(realpathSync2(entry)).href;
20322
20842
  } catch {
20323
20843
  return false;
20324
20844
  }
@@ -20340,6 +20860,7 @@ export {
20340
20860
  runAiStatus,
20341
20861
  runDbAdd,
20342
20862
  runDbRemove,
20863
+ runDbReset,
20343
20864
  runDbStatus,
20344
20865
  runDeploy,
20345
20866
  runDeployHookCreate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryd/cli",
3
- "version": "0.16.0",
3
+ "version": "0.18.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",