@kryd/cli 0.14.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 +198 -32
  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
@@ -15931,6 +15931,10 @@ function loadConfig() {
15931
15931
  return {};
15932
15932
  }
15933
15933
  }
15934
+ function resolveToken() {
15935
+ const fromEnv = process.env.KRYD_TOKEN?.trim();
15936
+ return fromEnv ? fromEnv : loadConfig().token;
15937
+ }
15934
15938
  function writeConfig(config2) {
15935
15939
  const path = configPath();
15936
15940
  mkdirSync(dirname(path), { recursive: true, mode: 448 });
@@ -16156,7 +16160,7 @@ function reportSupportError(err) {
16156
16160
  process.exitCode = 1;
16157
16161
  }
16158
16162
  function requireToken() {
16159
- const token = loadConfig().token;
16163
+ const token = resolveToken();
16160
16164
  if (!token) usage("Not logged in \u2014 run `kryd login` first.");
16161
16165
  return token ?? null;
16162
16166
  }
@@ -16308,6 +16312,154 @@ async function runSupportReply(raw, opts) {
16308
16312
  }
16309
16313
  }
16310
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
+
16311
16463
  // src/browser-login.ts
16312
16464
  import { spawn } from "node:child_process";
16313
16465
  import { randomUUID } from "node:crypto";
@@ -16396,7 +16548,7 @@ async function runGitCredential(operation, opts = {}) {
16396
16548
  const input = parseCredentialInput(opts.stdin ?? "");
16397
16549
  if (input.protocol && input.protocol !== "https") return;
16398
16550
  const cwd = opts.cwd ?? process.cwd();
16399
- const token = loadConfig().token;
16551
+ const token = resolveToken();
16400
16552
  if (!token) return;
16401
16553
  const projectId = resolveProjectId(void 0, cwd);
16402
16554
  if (!projectId) return;
@@ -17607,7 +17759,7 @@ async function explainMissingProject(err) {
17607
17759
  if (err.status !== 404) return "";
17608
17760
  const resolution = resolvedLink();
17609
17761
  if (!resolution) return "";
17610
- const token = loadConfig().token;
17762
+ const token = resolveToken();
17611
17763
  if (!token) return "";
17612
17764
  if (await projectIsVisible(token, resolution.link.projectId) !== false) return "";
17613
17765
  let session;
@@ -17635,7 +17787,7 @@ async function backfillLinkAccount() {
17635
17787
  const resolution = resolvedLink();
17636
17788
  if (!resolution) return;
17637
17789
  if (resolution.link.accountId && resolution.link.accountSlug) return;
17638
- const token = loadConfig().token;
17790
+ const token = resolveToken();
17639
17791
  if (!token) return;
17640
17792
  if (await projectIsVisible(token, resolution.link.projectId) !== true) return;
17641
17793
  try {
@@ -17660,15 +17812,17 @@ async function runLogin(opts) {
17660
17812
  if (pastedToken) {
17661
17813
  saveToken(pastedToken, apiUrl);
17662
17814
  process.stdout.write("Token saved.\n");
17815
+ warnIfEnvTokenOverrides();
17663
17816
  return;
17664
17817
  }
17665
17818
  const { token, apiUrl: learnedApiUrl } = await browserLogin(resolveDashboardUrl(opts.dashboardUrl));
17666
17819
  saveToken(token, learnedApiUrl ?? apiUrl);
17667
17820
  process.stdout.write("Logged in.\n");
17821
+ warnIfEnvTokenOverrides();
17668
17822
  }
17669
17823
  async function runWhoami(opts = {}) {
17670
17824
  const apiUrl = resolveApiUrl(opts.apiUrl);
17671
- const token = loadConfig().token ?? "";
17825
+ const token = resolveToken() ?? "";
17672
17826
  try {
17673
17827
  const me = await whoami(apiUrl, token);
17674
17828
  process.stdout.write(
@@ -17679,14 +17833,22 @@ async function runWhoami(opts = {}) {
17679
17833
  await reportError(err);
17680
17834
  }
17681
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
+ }
17682
17843
  function runLogout() {
17683
17844
  clearToken();
17684
17845
  process.stdout.write("Logged out.\n");
17846
+ warnIfEnvTokenOverrides();
17685
17847
  }
17686
17848
  async function runInit(opts) {
17687
17849
  const apiUrl = resolveApiUrl(opts.apiUrl);
17688
17850
  const cwd = opts.cwd ?? process.cwd();
17689
- const token = loadConfig().token;
17851
+ const token = resolveToken();
17690
17852
  if (!token) {
17691
17853
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
17692
17854
  process.exitCode = 1;
@@ -17909,7 +18071,7 @@ async function replayStoredLog(apiUrl, token, deploymentId) {
17909
18071
  }
17910
18072
  async function runLogs(opts) {
17911
18073
  const apiUrl = resolveApiUrl(opts.apiUrl);
17912
- const token = loadConfig().token;
18074
+ const token = resolveToken();
17913
18075
  if (!token) {
17914
18076
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
17915
18077
  process.exitCode = 1;
@@ -17961,7 +18123,7 @@ async function runLogs(opts) {
17961
18123
  }
17962
18124
  async function runRuntimeLogs(opts) {
17963
18125
  const apiUrl = resolveApiUrl(opts.apiUrl);
17964
- const token = loadConfig().token;
18126
+ const token = resolveToken();
17965
18127
  if (!token) {
17966
18128
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
17967
18129
  process.exitCode = 1;
@@ -18018,7 +18180,7 @@ async function runRuntimeLogs(opts) {
18018
18180
  }
18019
18181
  async function runDeploy(opts) {
18020
18182
  const apiUrl = resolveApiUrl(opts.apiUrl);
18021
- const token = loadConfig().token;
18183
+ const token = resolveToken();
18022
18184
  if (!token) {
18023
18185
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18024
18186
  process.exitCode = 1;
@@ -18054,7 +18216,7 @@ function renderFor(logs) {
18054
18216
  var LOGS_FLAG_HELP = "stream the full build log line by line instead of the progress display";
18055
18217
  async function runPush(opts) {
18056
18218
  const apiUrl = resolveApiUrl(opts.apiUrl);
18057
- const token = loadConfig().token;
18219
+ const token = resolveToken();
18058
18220
  if (!token) {
18059
18221
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18060
18222
  process.exitCode = 1;
@@ -18206,7 +18368,7 @@ async function followApps(apiUrl, token, deployments, render, estimateFrom) {
18206
18368
  }
18207
18369
  async function runRollback(opts) {
18208
18370
  const apiUrl = resolveApiUrl(opts.apiUrl);
18209
- const token = loadConfig().token;
18371
+ const token = resolveToken();
18210
18372
  if (!token) {
18211
18373
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18212
18374
  process.exitCode = 1;
@@ -18237,7 +18399,7 @@ async function runRollback(opts) {
18237
18399
  }
18238
18400
  async function runRedeploy(opts) {
18239
18401
  const apiUrl = resolveApiUrl(opts.apiUrl);
18240
- const token = loadConfig().token;
18402
+ const token = resolveToken();
18241
18403
  if (!token) {
18242
18404
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18243
18405
  process.exitCode = 1;
@@ -18267,7 +18429,7 @@ ${note}
18267
18429
  }
18268
18430
  async function runDbAdd(opts) {
18269
18431
  const apiUrl = resolveApiUrl(opts.apiUrl);
18270
- const token = loadConfig().token;
18432
+ const token = resolveToken();
18271
18433
  if (!token) {
18272
18434
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18273
18435
  process.exitCode = 1;
@@ -18348,7 +18510,7 @@ function reportSettleResult(result, opts) {
18348
18510
  }
18349
18511
  async function runStorageAdd(opts) {
18350
18512
  const apiUrl = resolveApiUrl(opts.apiUrl);
18351
- const token = loadConfig().token;
18513
+ const token = resolveToken();
18352
18514
  if (!token) {
18353
18515
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18354
18516
  process.exitCode = 1;
@@ -18442,7 +18604,7 @@ ${listed}Re-run \`kryd project rm <id>\` with the one you mean.
18442
18604
  }
18443
18605
  async function runProjectRemove(opts) {
18444
18606
  const apiUrl = resolveApiUrl(opts.apiUrl);
18445
- const token = loadConfig().token;
18607
+ const token = resolveToken();
18446
18608
  if (!token) {
18447
18609
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18448
18610
  process.exitCode = 1;
@@ -18557,7 +18719,7 @@ async function describeProjectResources(apiUrl, token, projectId) {
18557
18719
  }
18558
18720
  async function runDbRemove(opts) {
18559
18721
  const apiUrl = resolveApiUrl(opts.apiUrl);
18560
- const token = loadConfig().token;
18722
+ const token = resolveToken();
18561
18723
  if (!token) {
18562
18724
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18563
18725
  process.exitCode = 1;
@@ -18596,7 +18758,7 @@ async function runDbRemove(opts) {
18596
18758
  }
18597
18759
  async function runAiAdd(opts) {
18598
18760
  const apiUrl = resolveApiUrl(opts.apiUrl);
18599
- const token = loadConfig().token;
18761
+ const token = resolveToken();
18600
18762
  if (!token) {
18601
18763
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18602
18764
  process.exitCode = 1;
@@ -18621,7 +18783,7 @@ async function runAiAdd(opts) {
18621
18783
  }
18622
18784
  async function runAiRemove(opts) {
18623
18785
  const apiUrl = resolveApiUrl(opts.apiUrl);
18624
- const token = loadConfig().token;
18786
+ const token = resolveToken();
18625
18787
  if (!token) {
18626
18788
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18627
18789
  process.exitCode = 1;
@@ -18688,7 +18850,7 @@ Remove it before attaching another.
18688
18850
  }
18689
18851
  async function runDbStatus(opts) {
18690
18852
  const apiUrl = resolveApiUrl(opts.apiUrl);
18691
- const token = loadConfig().token;
18853
+ const token = resolveToken();
18692
18854
  if (!token) {
18693
18855
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18694
18856
  process.exitCode = 1;
@@ -18708,7 +18870,7 @@ async function runDbStatus(opts) {
18708
18870
  }
18709
18871
  async function runStorageStatus(opts) {
18710
18872
  const apiUrl = resolveApiUrl(opts.apiUrl);
18711
- const token = loadConfig().token;
18873
+ const token = resolveToken();
18712
18874
  if (!token) {
18713
18875
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18714
18876
  process.exitCode = 1;
@@ -18728,7 +18890,7 @@ async function runStorageStatus(opts) {
18728
18890
  }
18729
18891
  async function runAiStatus(opts) {
18730
18892
  const apiUrl = resolveApiUrl(opts.apiUrl);
18731
- const token = loadConfig().token;
18893
+ const token = resolveToken();
18732
18894
  if (!token) {
18733
18895
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18734
18896
  process.exitCode = 1;
@@ -18764,7 +18926,7 @@ function daysUntil(iso) {
18764
18926
  var TOKEN_EXPIRY_WARNING_DAYS = 30;
18765
18927
  async function runWorkflowAdd(opts) {
18766
18928
  const apiUrl = resolveApiUrl(opts.apiUrl);
18767
- const token = loadConfig().token;
18929
+ const token = resolveToken();
18768
18930
  if (!token) {
18769
18931
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18770
18932
  process.exitCode = 1;
@@ -18797,7 +18959,7 @@ HATCHET_CLIENT_TOKEN will be injected on your next production deploy (kryd deplo
18797
18959
  }
18798
18960
  async function runWorkflowRemove(opts) {
18799
18961
  const apiUrl = resolveApiUrl(opts.apiUrl);
18800
- const token = loadConfig().token;
18962
+ const token = resolveToken();
18801
18963
  if (!token) {
18802
18964
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18803
18965
  process.exitCode = 1;
@@ -18842,7 +19004,7 @@ Workflow history, crons and schedules stay until the project is deleted.
18842
19004
  }
18843
19005
  async function runWorkflowStatus(opts) {
18844
19006
  const apiUrl = resolveApiUrl(opts.apiUrl);
18845
- const token = loadConfig().token;
19007
+ const token = resolveToken();
18846
19008
  if (!token) {
18847
19009
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18848
19010
  process.exitCode = 1;
@@ -18901,7 +19063,7 @@ function formatDomainRecords(records) {
18901
19063
  }
18902
19064
  function domainContext(opts, usage2) {
18903
19065
  const apiUrl = resolveApiUrl(opts.apiUrl);
18904
- const token = loadConfig().token;
19066
+ const token = resolveToken();
18905
19067
  if (!token) {
18906
19068
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
18907
19069
  process.exitCode = 1;
@@ -19308,7 +19470,7 @@ HATCHET_CLIENT_TOKEN is injected into your app and your worker on production dep
19308
19470
  }
19309
19471
  async function runStorageRemove(opts) {
19310
19472
  const apiUrl = resolveApiUrl(opts.apiUrl);
19311
- const token = loadConfig().token;
19473
+ const token = resolveToken();
19312
19474
  if (!token) {
19313
19475
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19314
19476
  process.exitCode = 1;
@@ -19371,7 +19533,7 @@ function envAnnotation(environments) {
19371
19533
  }
19372
19534
  async function runProjectList(opts) {
19373
19535
  const apiUrl = resolveApiUrl(opts.apiUrl);
19374
- const token = loadConfig().token;
19536
+ const token = resolveToken();
19375
19537
  if (!token) {
19376
19538
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19377
19539
  process.exitCode = 1;
@@ -19422,7 +19584,7 @@ ${rows}`;
19422
19584
  }
19423
19585
  async function runEnvList(opts) {
19424
19586
  const apiUrl = resolveApiUrl(opts.apiUrl);
19425
- const token = loadConfig().token;
19587
+ const token = resolveToken();
19426
19588
  if (!token) {
19427
19589
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19428
19590
  process.exitCode = 1;
@@ -19504,7 +19666,7 @@ function toDotenvLine(key, value) {
19504
19666
  }
19505
19667
  async function runEnvPull(opts) {
19506
19668
  const apiUrl = resolveApiUrl(opts.apiUrl);
19507
- const token = loadConfig().token;
19669
+ const token = resolveToken();
19508
19670
  if (!token) {
19509
19671
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19510
19672
  process.exitCode = 1;
@@ -19605,7 +19767,7 @@ async function runEnvSet(opts) {
19605
19767
  const scope = opts.build ? "build" : "runtime";
19606
19768
  const environment = opts.preview ? "preview" : "production";
19607
19769
  const apiUrl = resolveApiUrl(opts.apiUrl);
19608
- const token = loadConfig().token;
19770
+ const token = resolveToken();
19609
19771
  if (!token) {
19610
19772
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19611
19773
  process.exitCode = 1;
@@ -19700,7 +19862,7 @@ async function runEnvRemove(opts) {
19700
19862
  const scope = opts.build ? "build" : "runtime";
19701
19863
  const environment = opts.preview ? "preview" : "production";
19702
19864
  const apiUrl = resolveApiUrl(opts.apiUrl);
19703
- const token = loadConfig().token;
19865
+ const token = resolveToken();
19704
19866
  if (!token) {
19705
19867
  process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
19706
19868
  process.exitCode = 1;
@@ -19794,7 +19956,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
19794
19956
  await reportError(err);
19795
19957
  }
19796
19958
  }
19797
- var CLI_VERSION = true ? "0.14.0" : "0.0.0-dev";
19959
+ var CLI_VERSION = true ? "0.15.0" : "0.0.0-dev";
19798
19960
  var program = new Command();
19799
19961
  program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
19800
19962
  program.hook("postAction", async () => {
@@ -19942,6 +20104,10 @@ support.command("new").description(
19942
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));
19943
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));
19944
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));
19945
20111
  function invokedDirectly() {
19946
20112
  const entry = process.argv[1];
19947
20113
  if (!entry) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryd/cli",
3
- "version": "0.14.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",
@@ -41,9 +41,9 @@
41
41
  "tsx": "^4.19.2",
42
42
  "typescript": "^5.6.3",
43
43
  "vitest": "^2.1.8",
44
- "@kryd/config-eslint": "0.0.0",
44
+ "@kryd/shared-types": "0.0.0",
45
45
  "@kryd/config-ts": "0.0.0",
46
- "@kryd/shared-types": "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",