@kryd/cli 0.18.0 → 0.19.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.
package/README.md CHANGED
@@ -44,6 +44,7 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
44
44
  | `kryd storage add \| remove \| status [project]` | Attach, tear down or inspect S3-compatible object storage. `remove` destroys every object and asks first. |
45
45
  | `kryd env list \| set \| rm [project]` | Manage your own environment variables. `set KEY --stdin` (or a prompt) keeps a secret out of `ps` and your shell history; values are never printed back. Add `--build` for build-time variables (`VITE_*`, `NEXT_PUBLIC_*`) — these are compiled into your public bundle, so they must never be secrets, and they take effect at your next build rather than your next deploy. |
46
46
  | `kryd ai add \| remove \| status [project]` | Give the project an authenticated EU AI-gateway endpoint (injected on the next deploy), take it away again, or show what it has. |
47
+ | `kryd ai models` | List the AI models Kryd offers right now — the same catalogue the dashboard's AI tab shows, in the same order (popular ones marked `*`). A request's `model` field carries the `MODEL` value. The list is the same for every account, but the call runs in your current account; needs a login, no project. |
47
48
  | `kryd workflow add \| remove \| status [project]` | Give the project its own durable-workflow tenant — `HATCHET_CLIENT_TOKEN` is injected on the next deploy — revoke that token again, or show whether it has one and when it expires. `remove` keeps the tenant's history, crons and schedules until the project is deleted. |
48
49
  | `kryd workflow init --language <typescript\|python\|go>` | Scaffold a workflow worker here and link it as a new project. The worker ships with the small HTTP health listener Kryd's deploy gate needs — without it a deploy is torn down with "nothing listening yet" — plus a `kryd.json` that tells `kryd init` what this project is. `--no-link` writes the files only. |
49
50
  | `kryd token create \| list \| revoke` | Deploy tokens for CI and scripts. `create <name> [--expires 30\|90\|365\|never]` (default 90 days) prints the token once; it acts as you, with your role in the **current account**, is bound to that one account for good, and cannot create tokens itself. Managing tokens needs `kryd login`. A token can do what you can on your projects — read env values and push — so after a leak, revoke it and also rotate the secrets it could read. |
@@ -56,7 +57,7 @@ kryd support new --type bug --subject "Deploy hangs on build" --message "Every d
56
57
  kryd support show T-1042 --json
57
58
  ```
58
59
 
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
+ **`--json` on the read commands.** `kryd project list`, `whoami`, `db status`, `storage status`, `ai status`, `ai models`, `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
 
61
62
  | Command | `--json` prints |
62
63
  |---|---|
@@ -65,6 +66,7 @@ kryd support show T-1042 --json
65
66
  | `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
67
  | `kryd storage status` | `{ attached, status, failureReason }` |
67
68
  | `kryd ai status` | `{ enabled, url }` |
69
+ | `kryd ai models` | `{ items }`: the AI catalogue rows as the API returns them (`id`, `model`, `provider`, `tasks`, `stage`, `popular`, `sortOrder`), in its order. An empty catalogue is `{ "items": [] }` |
68
70
  | `kryd workflow status` | `{ enabled, tokenExpiresAt, tokenExpiresInDays }` — both `null` without an expiry; the days are negative once the token has expired |
69
71
  | `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
72
  | `kryd domain status <host>` | that one domain, the same fields, its DNS `records` included |
package/dist/index.js CHANGED
@@ -15364,6 +15364,28 @@ async function getAiStatus(apiUrl, token, projectId) {
15364
15364
  }
15365
15365
  return { enabled: parsed.enabled, url: parsed.url };
15366
15366
  }
15367
+ async function getAiModels(apiUrl, token) {
15368
+ const res = await fetch(`${apiUrl}/ai-models`, {
15369
+ headers: { ...authHeaders(token) }
15370
+ });
15371
+ if (!res.ok) {
15372
+ throw new ApiError(
15373
+ `AI catalogue read failed (${res.status})`,
15374
+ await parseEnvelope(res),
15375
+ res.status
15376
+ );
15377
+ }
15378
+ const parsed = await res.json().catch(() => void 0);
15379
+ const isRow = (row) => {
15380
+ if (!row || typeof row !== "object") return false;
15381
+ const r = row;
15382
+ return typeof r.model === "string" && typeof r.provider === "string" && Array.isArray(r.tasks) && r.tasks.every((t) => typeof t === "string") && typeof r.stage === "string" && typeof r.popular === "boolean";
15383
+ };
15384
+ if (!Array.isArray(parsed) || !parsed.every(isRow)) {
15385
+ throw new ApiError("The API returned an unexpected AI-catalogue response.");
15386
+ }
15387
+ return parsed;
15388
+ }
15367
15389
  async function removeAi(apiUrl, token, projectId) {
15368
15390
  const res = await fetch(`${apiUrl}/ai/${encodeURIComponent(projectId)}`, {
15369
15391
  method: "DELETE",
@@ -16812,7 +16834,7 @@ This project deploys to Kryd, a European cloud platform. Use what it already pro
16812
16834
  - **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
16835
  - **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
16836
  - **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.
16837
+ - **AI:** an EU-hosted, OpenAI-compatible gateway with several models \u2014 \`kryd ai add\`; list the current models with \`kryd ai models\`. Don't add provider API keys.
16816
16838
  - **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
16839
  - **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
16840
  - **Your own domain:** \`kryd domain add <host>\` prints the DNS records to create; the HTTPS certificate is automatic.
@@ -16827,7 +16849,7 @@ This project deploys to Kryd, a European cloud platform. Use what it already pro
16827
16849
  - **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
16850
  - **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
16851
  - **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.
16852
+ - **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 ai models --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
16853
  `;
16832
16854
  var AGENTS_FOOTER = "Run `kryd <command> --help` for details. After updating the CLI, refresh this block with `kryd init --agents`.\n";
16833
16855
  function isHidden(cmd) {
@@ -19592,6 +19614,50 @@ async function runAiStatus(opts) {
19592
19614
  await reportError(err);
19593
19615
  }
19594
19616
  }
19617
+ async function runAiModels(opts) {
19618
+ const apiUrl = resolveApiUrl(opts.apiUrl);
19619
+ const token = resolveToken();
19620
+ if (!token) {
19621
+ process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19622
+ process.exitCode = 1;
19623
+ return;
19624
+ }
19625
+ let models;
19626
+ try {
19627
+ models = await getAiModels(apiUrl, token);
19628
+ } catch (err) {
19629
+ await reportError(err);
19630
+ return;
19631
+ }
19632
+ if (opts.json) {
19633
+ printJson2({ items: models });
19634
+ return;
19635
+ }
19636
+ if (models.length === 0) {
19637
+ process.stdout.write("No models in the AI catalogue right now.\n");
19638
+ return;
19639
+ }
19640
+ const rows = models.map((m) => ({
19641
+ mark: m.popular ? "*" : " ",
19642
+ model: m.model,
19643
+ provider: m.provider,
19644
+ tasks: m.tasks.join(","),
19645
+ stage: m.stage
19646
+ }));
19647
+ const width = (label, pick2) => rows.reduce((max, r) => Math.max(max, pick2(r).length), label.length);
19648
+ const modelW = width("MODEL", (r) => r.model);
19649
+ const providerW = width("PROVIDER", (r) => r.provider);
19650
+ const tasksW = width("TASKS", (r) => r.tasks);
19651
+ const line = (mark, model, provider, tasks, stage) => `${mark} ${model.padEnd(modelW)} ${provider.padEnd(providerW)} ${tasks.padEnd(tasksW)} ${stage}
19652
+ `;
19653
+ process.stdout.write(line(" ", "MODEL", "PROVIDER", "TASKS", "STAGE"));
19654
+ for (const r of rows) process.stdout.write(line(r.mark, r.model, r.provider, r.tasks, r.stage));
19655
+ process.stdout.write("\n");
19656
+ if (rows.some((r) => r.mark === "*")) process.stdout.write("* popular\n");
19657
+ process.stdout.write(
19658
+ "Use one by putting its MODEL value in the request's `model` field (after `kryd ai add`).\n"
19659
+ );
19660
+ }
19595
19661
  function isoDay(iso) {
19596
19662
  return iso.slice(0, 10);
19597
19663
  }
@@ -20670,7 +20736,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
20670
20736
  await reportError(err);
20671
20737
  }
20672
20738
  }
20673
- var CLI_VERSION = true ? "0.18.0" : "0.0.0-dev";
20739
+ var CLI_VERSION = true ? "0.19.0" : "0.0.0-dev";
20674
20740
  var program = new Command();
20675
20741
  program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
20676
20742
  program.hook("postAction", async () => {
@@ -20800,10 +20866,11 @@ env.command("pull [project]").description(
20800
20866
  "--out <path>",
20801
20867
  "file to write, relative to the project root (default: .env.kryd \u2014 never your hand-maintained .env)"
20802
20868
  ).option("--force", "overwrite the --out file if it already exists").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runEnvPull({ ...opts, project: project2 }));
20803
- var ai = program.command("ai").description("Manage the app's EU AI gateway");
20869
+ var ai = program.command("ai").description("Manage the app's EU AI gateway and list its models");
20804
20870
  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 }));
20805
20871
  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 }));
20806
20872
  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 }));
20873
+ ai.command("models").description("List the AI models Kryd offers right now (the catalogue the dashboard shows)").option("--json", "print { items } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runAiModels(opts));
20807
20874
  var workflow = program.command("workflow").description("Manage the project's durable workflows (its own workflow tenant)");
20808
20875
  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 }));
20809
20876
  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 }));
@@ -20856,6 +20923,7 @@ export {
20856
20923
  backfillLinkAccount,
20857
20924
  program,
20858
20925
  runAiAdd,
20926
+ runAiModels,
20859
20927
  runAiRemove,
20860
20928
  runAiStatus,
20861
20929
  runDbAdd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryd/cli",
3
- "version": "0.18.0",
3
+ "version": "0.19.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-eslint": "0.0.0",
46
- "@kryd/config-ts": "0.0.0"
45
+ "@kryd/config-ts": "0.0.0",
46
+ "@kryd/config-eslint": "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",