@kryd/cli 0.13.0 → 0.15.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 +8 -0
  2. package/dist/index.js +199 -62
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -43,6 +43,7 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
43
43
  | `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. |
44
44
  | `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. |
45
45
  | `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. |
46
+ | `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, inside your account, 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. |
46
47
  | `kryd support new \| list \| show \| reply` | Ask Kryd support a question, report a bug or a wish, and read the answer. Non-interactive (every input is a flag) and `--json` prints one JSON document on stdout — so an agent can use it too. In a linked folder, `new` adds the project and its last production deploy; when that deploy failed, the last lines of its log are sent to Kryd support too, with secret-looking values masked. `--no-context` leaves all of it out. |
47
48
 
48
49
  For an agent, for example:
@@ -52,6 +53,13 @@ kryd support new --type bug --subject "Deploy hangs on build" --message "Every d
52
53
  kryd support show T-1042 --json
53
54
  ```
54
55
 
56
+ 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).
57
+
58
+ ```sh
59
+ kryd token create github-actions # prints kryd_… once — store it as the CI secret KRYD_TOKEN
60
+ KRYD_TOKEN=kryd_… kryd deploy <project>
61
+ ```
62
+
55
63
  Run `kryd <command> --help` for options. Every project-scoped command accepts an explicit `<project>` id, or resolves it from the `.kryd` link (walking up from the current directory, git-style).
56
64
 
57
65
  ## Sovereignty
package/dist/index.js CHANGED
@@ -14963,25 +14963,6 @@ async function parseEnvelope(res) {
14963
14963
  return void 0;
14964
14964
  }
14965
14965
  }
14966
- async function login(apiUrl, creds) {
14967
- const res = await fetch(`${apiUrl}/api/auth/sign-in/email`, {
14968
- method: "POST",
14969
- headers: { "content-type": "application/json", origin: apiUrl },
14970
- body: JSON.stringify(creds)
14971
- });
14972
- if (!res.ok) {
14973
- throw new ApiError(
14974
- `Login failed (${res.status})`,
14975
- await parseEnvelope(res),
14976
- res.status
14977
- );
14978
- }
14979
- const token = res.headers.get("set-auth-token");
14980
- if (!token) {
14981
- throw new ApiError("Login succeeded but the API returned no token.");
14982
- }
14983
- return token;
14984
- }
14985
14966
  async function whoami(apiUrl, token) {
14986
14967
  const res = await fetch(`${apiUrl}/me`, {
14987
14968
  headers: { authorization: `Bearer ${token}` }
@@ -15950,6 +15931,10 @@ function loadConfig() {
15950
15931
  return {};
15951
15932
  }
15952
15933
  }
15934
+ function resolveToken() {
15935
+ const fromEnv = process.env.KRYD_TOKEN?.trim();
15936
+ return fromEnv ? fromEnv : loadConfig().token;
15937
+ }
15953
15938
  function writeConfig(config2) {
15954
15939
  const path = configPath();
15955
15940
  mkdirSync(dirname(path), { recursive: true, mode: 448 });
@@ -16175,7 +16160,7 @@ function reportSupportError(err) {
16175
16160
  process.exitCode = 1;
16176
16161
  }
16177
16162
  function requireToken() {
16178
- const token = loadConfig().token;
16163
+ const token = resolveToken();
16179
16164
  if (!token) usage("Not logged in \u2014 run `kryd login` first.");
16180
16165
  return token ?? null;
16181
16166
  }
@@ -16327,6 +16312,154 @@ async function runSupportReply(raw, opts) {
16327
16312
  }
16328
16313
  }
16329
16314
 
16315
+ // src/tokens.ts
16316
+ var TOKEN_EXPIRY_CHOICES = ["30", "90", "365", "never"];
16317
+ var DEFAULT_TOKEN_EXPIRY = "90";
16318
+ var MAX_TOKEN_NAME = 32;
16319
+ function expiresInSeconds(choice) {
16320
+ if (choice === "never") return null;
16321
+ if (!TOKEN_EXPIRY_CHOICES.includes(choice)) return void 0;
16322
+ return Number(choice) * 24 * 60 * 60;
16323
+ }
16324
+ async function tokenFetch(apiUrl, token, path, what, body) {
16325
+ const res = await fetch(`${apiUrl}/api/auth/api-key/${path}`, {
16326
+ method: body === void 0 ? "GET" : "POST",
16327
+ headers: {
16328
+ authorization: `Bearer ${token}`,
16329
+ ...body !== void 0 ? { "content-type": "application/json" } : {}
16330
+ },
16331
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
16332
+ });
16333
+ if (!res.ok) {
16334
+ const parsed = await res.json().catch(() => void 0);
16335
+ const message = typeof parsed?.message === "string" ? parsed.message : `${what} failed (${res.status})`;
16336
+ const code = typeof parsed?.code === "string" ? parsed.code : `http.${res.status}`;
16337
+ throw new ApiError(`${what} failed (${res.status})`, { error: { code, message } }, res.status);
16338
+ }
16339
+ try {
16340
+ return await res.json();
16341
+ } catch {
16342
+ throw new ApiError(`The API returned an unexpected answer to "${what.toLowerCase()}".`);
16343
+ }
16344
+ }
16345
+ var isToken = (v) => {
16346
+ const t = v;
16347
+ return typeof t?.id === "string" && typeof t?.createdAt === "string";
16348
+ };
16349
+ async function createDeployToken(apiUrl, token, input) {
16350
+ const body = await tokenFetch(apiUrl, token, "create", "Creating the token", input);
16351
+ if (!isToken(body) || typeof body.key !== "string" || body.key.length === 0) {
16352
+ throw new ApiError("The API returned an unexpected answer to creating the token.");
16353
+ }
16354
+ return body;
16355
+ }
16356
+ async function listDeployTokens(apiUrl, token) {
16357
+ const body = await tokenFetch(apiUrl, token, "list", "Listing the tokens");
16358
+ if (!Array.isArray(body?.apiKeys) || !body.apiKeys.every(isToken)) {
16359
+ throw new ApiError("The API returned an unexpected answer to listing the tokens.");
16360
+ }
16361
+ return body.apiKeys;
16362
+ }
16363
+ async function revokeDeployToken(apiUrl, token, id) {
16364
+ await tokenFetch(apiUrl, token, "delete", "Revoking the token", { keyId: id });
16365
+ }
16366
+ function fail(message) {
16367
+ process.stderr.write(`${message}
16368
+ `);
16369
+ process.exitCode = 1;
16370
+ }
16371
+ function reportTokenError(err) {
16372
+ if (err instanceof ApiError && err.status === 401) {
16373
+ fail("Not logged in, or the session expired \u2014 run `kryd login`.");
16374
+ } else if (err instanceof ApiError && err.status === 403 && resolveToken()?.startsWith("kryd_")) {
16375
+ fail(
16376
+ "A deploy token cannot manage tokens. Sign in with `kryd login` (and unset KRYD_TOKEN for this command)."
16377
+ );
16378
+ } else if (err instanceof ApiError && err.status === 404) {
16379
+ fail("No such token on your account. `kryd token list` shows their ids.");
16380
+ } else if (err instanceof ApiError && err.envelope) {
16381
+ fail(err.envelope.error.message);
16382
+ } else {
16383
+ fail(err instanceof Error ? err.message : String(err));
16384
+ }
16385
+ }
16386
+ function requireToken2() {
16387
+ const token = resolveToken();
16388
+ if (!token) fail("Not logged in \u2014 run `kryd login` first.");
16389
+ return token ?? null;
16390
+ }
16391
+ var day = (iso) => iso ? iso.slice(0, 10) : "\u2014";
16392
+ async function runTokenCreate(name, opts) {
16393
+ const trimmed = name.trim();
16394
+ if (trimmed.length === 0 || trimmed.length > MAX_TOKEN_NAME) {
16395
+ return fail(`A token name is 1 to ${MAX_TOKEN_NAME} characters.`);
16396
+ }
16397
+ const choice = opts.expires ?? DEFAULT_TOKEN_EXPIRY;
16398
+ const expiresIn = expiresInSeconds(choice);
16399
+ if (expiresIn === void 0) {
16400
+ return fail(`--expires is one of ${TOKEN_EXPIRY_CHOICES.join(", ")} (days, or never).`);
16401
+ }
16402
+ const token = requireToken2();
16403
+ if (!token) return;
16404
+ try {
16405
+ const created = await createDeployToken(resolveApiUrl(opts.apiUrl), token, { name: trimmed, expiresIn });
16406
+ process.stdout.write(`${created.key}
16407
+ `);
16408
+ process.stderr.write(
16409
+ `Created deploy token "${trimmed}" (${created.id}), ${created.expiresAt ? `valid until ${day(created.expiresAt)}` : "without expiry"}.
16410
+ This token will not be shown again. Store it now, e.g. as the CI secret KRYD_TOKEN.
16411
+ `
16412
+ );
16413
+ } catch (err) {
16414
+ reportTokenError(err);
16415
+ }
16416
+ }
16417
+ async function runTokenList(opts) {
16418
+ const token = requireToken2();
16419
+ if (!token) return;
16420
+ try {
16421
+ const list = await listDeployTokens(resolveApiUrl(opts.apiUrl), token);
16422
+ if (opts.json) {
16423
+ process.stdout.write(`${JSON.stringify(list, null, 2)}
16424
+ `);
16425
+ return;
16426
+ }
16427
+ if (list.length === 0) {
16428
+ process.stdout.write("No deploy tokens. Create one with `kryd token create <name>`.\n");
16429
+ return;
16430
+ }
16431
+ const rows = [
16432
+ ["ID", "NAME", "STARTS WITH", "CREATED", "LAST USED", "EXPIRES"],
16433
+ ...list.map((t) => [
16434
+ t.id,
16435
+ t.name ?? "\u2014",
16436
+ t.start ? `${t.start}\u2026` : "\u2014",
16437
+ day(t.createdAt),
16438
+ day(t.lastRequest),
16439
+ t.expiresAt ? day(t.expiresAt) : "never"
16440
+ ])
16441
+ ];
16442
+ const widths = rows[0].map((_, i) => Math.max(...rows.map((r) => r[i].length)));
16443
+ for (const r of rows) {
16444
+ process.stdout.write(`${r.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd()}
16445
+ `);
16446
+ }
16447
+ } catch (err) {
16448
+ reportTokenError(err);
16449
+ }
16450
+ }
16451
+ async function runTokenRevoke(id, opts) {
16452
+ const token = requireToken2();
16453
+ if (!token) return;
16454
+ try {
16455
+ await revokeDeployToken(resolveApiUrl(opts.apiUrl), token, id);
16456
+ process.stdout.write(`Revoked ${id}. Anything still using it now gets 401.
16457
+ `);
16458
+ } catch (err) {
16459
+ reportTokenError(err);
16460
+ }
16461
+ }
16462
+
16330
16463
  // src/browser-login.ts
16331
16464
  import { spawn } from "node:child_process";
16332
16465
  import { randomUUID } from "node:crypto";
@@ -16415,7 +16548,7 @@ async function runGitCredential(operation, opts = {}) {
16415
16548
  const input = parseCredentialInput(opts.stdin ?? "");
16416
16549
  if (input.protocol && input.protocol !== "https") return;
16417
16550
  const cwd = opts.cwd ?? process.cwd();
16418
- const token = loadConfig().token;
16551
+ const token = resolveToken();
16419
16552
  if (!token) return;
16420
16553
  const projectId = resolveProjectId(void 0, cwd);
16421
16554
  if (!projectId) return;
@@ -17626,7 +17759,7 @@ async function explainMissingProject(err) {
17626
17759
  if (err.status !== 404) return "";
17627
17760
  const resolution = resolvedLink();
17628
17761
  if (!resolution) return "";
17629
- const token = loadConfig().token;
17762
+ const token = resolveToken();
17630
17763
  if (!token) return "";
17631
17764
  if (await projectIsVisible(token, resolution.link.projectId) !== false) return "";
17632
17765
  let session;
@@ -17654,7 +17787,7 @@ async function backfillLinkAccount() {
17654
17787
  const resolution = resolvedLink();
17655
17788
  if (!resolution) return;
17656
17789
  if (resolution.link.accountId && resolution.link.accountSlug) return;
17657
- const token = loadConfig().token;
17790
+ const token = resolveToken();
17658
17791
  if (!token) return;
17659
17792
  if (await projectIsVisible(token, resolution.link.projectId) !== true) return;
17660
17793
  try {
@@ -17679,25 +17812,17 @@ async function runLogin(opts) {
17679
17812
  if (pastedToken) {
17680
17813
  saveToken(pastedToken, apiUrl);
17681
17814
  process.stdout.write("Token saved.\n");
17815
+ warnIfEnvTokenOverrides();
17682
17816
  return;
17683
17817
  }
17684
- if (opts.email && opts.password) {
17685
- const token2 = await login(apiUrl, { email: opts.email, password: opts.password });
17686
- saveToken(token2, apiUrl);
17687
- process.stdout.write(`Logged in as ${opts.email}.
17688
- `);
17689
- return;
17690
- }
17691
- if (opts.email || opts.password) {
17692
- throw new Error("Provide BOTH --email and --password, or omit them to log in via the browser.");
17693
- }
17694
17818
  const { token, apiUrl: learnedApiUrl } = await browserLogin(resolveDashboardUrl(opts.dashboardUrl));
17695
17819
  saveToken(token, learnedApiUrl ?? apiUrl);
17696
17820
  process.stdout.write("Logged in.\n");
17821
+ warnIfEnvTokenOverrides();
17697
17822
  }
17698
17823
  async function runWhoami(opts = {}) {
17699
17824
  const apiUrl = resolveApiUrl(opts.apiUrl);
17700
- const token = loadConfig().token ?? "";
17825
+ const token = resolveToken() ?? "";
17701
17826
  try {
17702
17827
  const me = await whoami(apiUrl, token);
17703
17828
  process.stdout.write(
@@ -17708,14 +17833,22 @@ async function runWhoami(opts = {}) {
17708
17833
  await reportError(err);
17709
17834
  }
17710
17835
  }
17836
+ function warnIfEnvTokenOverrides() {
17837
+ if (process.env.KRYD_TOKEN?.trim()) {
17838
+ process.stderr.write(
17839
+ "KRYD_TOKEN is set in this shell and overrides the stored login; commands keep using it until you unset it.\n"
17840
+ );
17841
+ }
17842
+ }
17711
17843
  function runLogout() {
17712
17844
  clearToken();
17713
17845
  process.stdout.write("Logged out.\n");
17846
+ warnIfEnvTokenOverrides();
17714
17847
  }
17715
17848
  async function runInit(opts) {
17716
17849
  const apiUrl = resolveApiUrl(opts.apiUrl);
17717
17850
  const cwd = opts.cwd ?? process.cwd();
17718
- const token = loadConfig().token;
17851
+ const token = resolveToken();
17719
17852
  if (!token) {
17720
17853
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
17721
17854
  process.exitCode = 1;
@@ -17938,7 +18071,7 @@ async function replayStoredLog(apiUrl, token, deploymentId) {
17938
18071
  }
17939
18072
  async function runLogs(opts) {
17940
18073
  const apiUrl = resolveApiUrl(opts.apiUrl);
17941
- const token = loadConfig().token;
18074
+ const token = resolveToken();
17942
18075
  if (!token) {
17943
18076
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
17944
18077
  process.exitCode = 1;
@@ -17990,7 +18123,7 @@ async function runLogs(opts) {
17990
18123
  }
17991
18124
  async function runRuntimeLogs(opts) {
17992
18125
  const apiUrl = resolveApiUrl(opts.apiUrl);
17993
- const token = loadConfig().token;
18126
+ const token = resolveToken();
17994
18127
  if (!token) {
17995
18128
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
17996
18129
  process.exitCode = 1;
@@ -18047,7 +18180,7 @@ async function runRuntimeLogs(opts) {
18047
18180
  }
18048
18181
  async function runDeploy(opts) {
18049
18182
  const apiUrl = resolveApiUrl(opts.apiUrl);
18050
- const token = loadConfig().token;
18183
+ const token = resolveToken();
18051
18184
  if (!token) {
18052
18185
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18053
18186
  process.exitCode = 1;
@@ -18083,7 +18216,7 @@ function renderFor(logs) {
18083
18216
  var LOGS_FLAG_HELP = "stream the full build log line by line instead of the progress display";
18084
18217
  async function runPush(opts) {
18085
18218
  const apiUrl = resolveApiUrl(opts.apiUrl);
18086
- const token = loadConfig().token;
18219
+ const token = resolveToken();
18087
18220
  if (!token) {
18088
18221
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18089
18222
  process.exitCode = 1;
@@ -18235,7 +18368,7 @@ async function followApps(apiUrl, token, deployments, render, estimateFrom) {
18235
18368
  }
18236
18369
  async function runRollback(opts) {
18237
18370
  const apiUrl = resolveApiUrl(opts.apiUrl);
18238
- const token = loadConfig().token;
18371
+ const token = resolveToken();
18239
18372
  if (!token) {
18240
18373
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18241
18374
  process.exitCode = 1;
@@ -18266,7 +18399,7 @@ async function runRollback(opts) {
18266
18399
  }
18267
18400
  async function runRedeploy(opts) {
18268
18401
  const apiUrl = resolveApiUrl(opts.apiUrl);
18269
- const token = loadConfig().token;
18402
+ const token = resolveToken();
18270
18403
  if (!token) {
18271
18404
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18272
18405
  process.exitCode = 1;
@@ -18296,7 +18429,7 @@ ${note}
18296
18429
  }
18297
18430
  async function runDbAdd(opts) {
18298
18431
  const apiUrl = resolveApiUrl(opts.apiUrl);
18299
- const token = loadConfig().token;
18432
+ const token = resolveToken();
18300
18433
  if (!token) {
18301
18434
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18302
18435
  process.exitCode = 1;
@@ -18377,7 +18510,7 @@ function reportSettleResult(result, opts) {
18377
18510
  }
18378
18511
  async function runStorageAdd(opts) {
18379
18512
  const apiUrl = resolveApiUrl(opts.apiUrl);
18380
- const token = loadConfig().token;
18513
+ const token = resolveToken();
18381
18514
  if (!token) {
18382
18515
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18383
18516
  process.exitCode = 1;
@@ -18471,7 +18604,7 @@ ${listed}Re-run \`kryd project rm <id>\` with the one you mean.
18471
18604
  }
18472
18605
  async function runProjectRemove(opts) {
18473
18606
  const apiUrl = resolveApiUrl(opts.apiUrl);
18474
- const token = loadConfig().token;
18607
+ const token = resolveToken();
18475
18608
  if (!token) {
18476
18609
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18477
18610
  process.exitCode = 1;
@@ -18586,7 +18719,7 @@ async function describeProjectResources(apiUrl, token, projectId) {
18586
18719
  }
18587
18720
  async function runDbRemove(opts) {
18588
18721
  const apiUrl = resolveApiUrl(opts.apiUrl);
18589
- const token = loadConfig().token;
18722
+ const token = resolveToken();
18590
18723
  if (!token) {
18591
18724
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18592
18725
  process.exitCode = 1;
@@ -18625,7 +18758,7 @@ async function runDbRemove(opts) {
18625
18758
  }
18626
18759
  async function runAiAdd(opts) {
18627
18760
  const apiUrl = resolveApiUrl(opts.apiUrl);
18628
- const token = loadConfig().token;
18761
+ const token = resolveToken();
18629
18762
  if (!token) {
18630
18763
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18631
18764
  process.exitCode = 1;
@@ -18650,7 +18783,7 @@ async function runAiAdd(opts) {
18650
18783
  }
18651
18784
  async function runAiRemove(opts) {
18652
18785
  const apiUrl = resolveApiUrl(opts.apiUrl);
18653
- const token = loadConfig().token;
18786
+ const token = resolveToken();
18654
18787
  if (!token) {
18655
18788
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18656
18789
  process.exitCode = 1;
@@ -18717,7 +18850,7 @@ Remove it before attaching another.
18717
18850
  }
18718
18851
  async function runDbStatus(opts) {
18719
18852
  const apiUrl = resolveApiUrl(opts.apiUrl);
18720
- const token = loadConfig().token;
18853
+ const token = resolveToken();
18721
18854
  if (!token) {
18722
18855
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18723
18856
  process.exitCode = 1;
@@ -18737,7 +18870,7 @@ async function runDbStatus(opts) {
18737
18870
  }
18738
18871
  async function runStorageStatus(opts) {
18739
18872
  const apiUrl = resolveApiUrl(opts.apiUrl);
18740
- const token = loadConfig().token;
18873
+ const token = resolveToken();
18741
18874
  if (!token) {
18742
18875
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18743
18876
  process.exitCode = 1;
@@ -18757,7 +18890,7 @@ async function runStorageStatus(opts) {
18757
18890
  }
18758
18891
  async function runAiStatus(opts) {
18759
18892
  const apiUrl = resolveApiUrl(opts.apiUrl);
18760
- const token = loadConfig().token;
18893
+ const token = resolveToken();
18761
18894
  if (!token) {
18762
18895
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18763
18896
  process.exitCode = 1;
@@ -18793,7 +18926,7 @@ function daysUntil(iso) {
18793
18926
  var TOKEN_EXPIRY_WARNING_DAYS = 30;
18794
18927
  async function runWorkflowAdd(opts) {
18795
18928
  const apiUrl = resolveApiUrl(opts.apiUrl);
18796
- const token = loadConfig().token;
18929
+ const token = resolveToken();
18797
18930
  if (!token) {
18798
18931
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18799
18932
  process.exitCode = 1;
@@ -18826,7 +18959,7 @@ HATCHET_CLIENT_TOKEN will be injected on your next production deploy (kryd deplo
18826
18959
  }
18827
18960
  async function runWorkflowRemove(opts) {
18828
18961
  const apiUrl = resolveApiUrl(opts.apiUrl);
18829
- const token = loadConfig().token;
18962
+ const token = resolveToken();
18830
18963
  if (!token) {
18831
18964
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18832
18965
  process.exitCode = 1;
@@ -18871,7 +19004,7 @@ Workflow history, crons and schedules stay until the project is deleted.
18871
19004
  }
18872
19005
  async function runWorkflowStatus(opts) {
18873
19006
  const apiUrl = resolveApiUrl(opts.apiUrl);
18874
- const token = loadConfig().token;
19007
+ const token = resolveToken();
18875
19008
  if (!token) {
18876
19009
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18877
19010
  process.exitCode = 1;
@@ -18930,7 +19063,7 @@ function formatDomainRecords(records) {
18930
19063
  }
18931
19064
  function domainContext(opts, usage2) {
18932
19065
  const apiUrl = resolveApiUrl(opts.apiUrl);
18933
- const token = loadConfig().token;
19066
+ const token = resolveToken();
18934
19067
  if (!token) {
18935
19068
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18936
19069
  process.exitCode = 1;
@@ -19337,7 +19470,7 @@ HATCHET_CLIENT_TOKEN is injected into your app and your worker on production dep
19337
19470
  }
19338
19471
  async function runStorageRemove(opts) {
19339
19472
  const apiUrl = resolveApiUrl(opts.apiUrl);
19340
- const token = loadConfig().token;
19473
+ const token = resolveToken();
19341
19474
  if (!token) {
19342
19475
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19343
19476
  process.exitCode = 1;
@@ -19400,7 +19533,7 @@ function envAnnotation(environments) {
19400
19533
  }
19401
19534
  async function runProjectList(opts) {
19402
19535
  const apiUrl = resolveApiUrl(opts.apiUrl);
19403
- const token = loadConfig().token;
19536
+ const token = resolveToken();
19404
19537
  if (!token) {
19405
19538
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19406
19539
  process.exitCode = 1;
@@ -19451,7 +19584,7 @@ ${rows}`;
19451
19584
  }
19452
19585
  async function runEnvList(opts) {
19453
19586
  const apiUrl = resolveApiUrl(opts.apiUrl);
19454
- const token = loadConfig().token;
19587
+ const token = resolveToken();
19455
19588
  if (!token) {
19456
19589
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19457
19590
  process.exitCode = 1;
@@ -19533,7 +19666,7 @@ function toDotenvLine(key, value) {
19533
19666
  }
19534
19667
  async function runEnvPull(opts) {
19535
19668
  const apiUrl = resolveApiUrl(opts.apiUrl);
19536
- const token = loadConfig().token;
19669
+ const token = resolveToken();
19537
19670
  if (!token) {
19538
19671
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19539
19672
  process.exitCode = 1;
@@ -19634,7 +19767,7 @@ async function runEnvSet(opts) {
19634
19767
  const scope = opts.build ? "build" : "runtime";
19635
19768
  const environment = opts.preview ? "preview" : "production";
19636
19769
  const apiUrl = resolveApiUrl(opts.apiUrl);
19637
- const token = loadConfig().token;
19770
+ const token = resolveToken();
19638
19771
  if (!token) {
19639
19772
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19640
19773
  process.exitCode = 1;
@@ -19729,7 +19862,7 @@ async function runEnvRemove(opts) {
19729
19862
  const scope = opts.build ? "build" : "runtime";
19730
19863
  const environment = opts.preview ? "preview" : "production";
19731
19864
  const apiUrl = resolveApiUrl(opts.apiUrl);
19732
- const token = loadConfig().token;
19865
+ const token = resolveToken();
19733
19866
  if (!token) {
19734
19867
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19735
19868
  process.exitCode = 1;
@@ -19823,13 +19956,13 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
19823
19956
  await reportError(err);
19824
19957
  }
19825
19958
  }
19826
- var CLI_VERSION = true ? "0.13.0" : "0.0.0-dev";
19959
+ var CLI_VERSION = true ? "0.15.0" : "0.0.0-dev";
19827
19960
  var program = new Command();
19828
19961
  program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
19829
19962
  program.hook("postAction", async () => {
19830
19963
  await backfillLinkAccount();
19831
19964
  });
19832
- program.command("login").description("Sign in via the browser (default) and store a token").option("--email <email>", "account email (with --password; non-interactive escape hatch)").option("--password <password>", "account password (with --email; visible in `ps` \u2014 prefer the browser flow)").option(
19965
+ program.command("login").description("Sign in via the browser (default) and store a token").option(
19833
19966
  "--token <token>",
19834
19967
  "supply a token instead of the browser flow (prefer $KRYD_TOKEN \u2014 flags are visible in `ps`)"
19835
19968
  ).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));
@@ -19971,6 +20104,10 @@ support.command("new").description(
19971
20104
  support.command("list").description("Your account's tickets, with their status").option("--json", "print { items } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runSupportList(opts));
19972
20105
  support.command("show <ticket>").description("One ticket and its conversation, e.g. `kryd support show T-1042`").option("--json", "print the ticket and its messages as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((ticket, opts) => runSupportShow(ticket, opts));
19973
20106
  support.command("reply <ticket>").description("Answer in a ticket's conversation \u2014 the ticket goes back to Kryd support (status open)").option("--message <text>", "your reply (required)").option("--json", "print the updated ticket as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((ticket, opts) => runSupportReply(ticket, opts));
20107
+ var tokenCommand = program.command("token").description("Deploy tokens for CI and scripts: set one as KRYD_TOKEN and every command uses it, no login needed");
20108
+ tokenCommand.command("create <name>").description("Create a deploy token and print it once \u2014 it acts as you, inside your account").option("--expires <days>", "30 | 90 | 365 | never", DEFAULT_TOKEN_EXPIRY).option("--api-url <url>", "control-plane API base URL").action((name, opts) => runTokenCreate(name, opts));
20109
+ tokenCommand.command("list").description("Your deploy tokens: name, first characters, created, last used, expiry \u2014 never the value").option("--json", "print the tokens as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runTokenList(opts));
20110
+ tokenCommand.command("revoke <id>").description("Revoke a deploy token; anything still using it gets 401").option("--api-url <url>", "control-plane API base URL").action((id, opts) => runTokenRevoke(id, opts));
19974
20111
  function invokedDirectly() {
19975
20112
  const entry = process.argv[1];
19976
20113
  if (!entry) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryd/cli",
3
- "version": "0.13.0",
3
+ "version": "0.15.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",