@kryd/cli 0.6.0 → 0.7.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 +1 -0
  2. package/dist/index.js +185 -1
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -38,6 +38,7 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
38
38
  | `kryd storage add \| remove \| status [project]` | Attach, tear down or inspect S3-compatible object storage. `remove` destroys every object and asks first. |
39
39
  | `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. |
40
40
  | `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. |
41
+ | `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. |
41
42
 
42
43
  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).
43
44
 
package/dist/index.js CHANGED
@@ -14729,6 +14729,7 @@ var MANAGED_ENV_VAR_NAMES = [
14729
14729
  "DATABASE_URL",
14730
14730
  "DEPLOY_TOKEN",
14731
14731
  "FORGE_PASSWORD",
14732
+ "HATCHET_CLIENT_TOKEN",
14732
14733
  "PUSH_TOKEN",
14733
14734
  "WEBHOOK_SECRET"
14734
14735
  ];
@@ -15296,6 +15297,57 @@ async function removeAi(apiUrl, token, projectId) {
15296
15297
  );
15297
15298
  }
15298
15299
  }
15300
+ function parseWorkflowStatus(body, label) {
15301
+ const parsed = body;
15302
+ if (!parsed || typeof parsed.enabled !== "boolean" || typeof parsed.tokenExpiresAt !== "string" && parsed.tokenExpiresAt !== null) {
15303
+ throw new ApiError(`The API returned an unexpected ${label} response.`);
15304
+ }
15305
+ return { enabled: parsed.enabled, tokenExpiresAt: parsed.tokenExpiresAt };
15306
+ }
15307
+ async function addWorkflows(apiUrl, token, input) {
15308
+ const res = await fetch(`${apiUrl}/workflows`, {
15309
+ method: "POST",
15310
+ headers: {
15311
+ "content-type": "application/json",
15312
+ authorization: `Bearer ${token}`
15313
+ },
15314
+ body: JSON.stringify({ projectId: input.projectId })
15315
+ });
15316
+ if (!res.ok) {
15317
+ throw new ApiError(
15318
+ `Workflow add failed (${res.status})`,
15319
+ await parseEnvelope(res),
15320
+ res.status
15321
+ );
15322
+ }
15323
+ return parseWorkflowStatus(await res.json().catch(() => void 0), "workflow-add");
15324
+ }
15325
+ async function getWorkflowStatus(apiUrl, token, projectId) {
15326
+ const res = await fetch(`${apiUrl}/workflows/${encodeURIComponent(projectId)}`, {
15327
+ headers: { authorization: `Bearer ${token}` }
15328
+ });
15329
+ if (!res.ok) {
15330
+ throw new ApiError(
15331
+ `Workflow status check failed (${res.status})`,
15332
+ await parseEnvelope(res),
15333
+ res.status
15334
+ );
15335
+ }
15336
+ return parseWorkflowStatus(await res.json().catch(() => void 0), "workflow-status");
15337
+ }
15338
+ async function removeWorkflows(apiUrl, token, projectId) {
15339
+ const res = await fetch(`${apiUrl}/workflows/${encodeURIComponent(projectId)}`, {
15340
+ method: "DELETE",
15341
+ headers: { authorization: `Bearer ${token}` }
15342
+ });
15343
+ if (!res.ok) {
15344
+ throw new ApiError(
15345
+ `Workflow removal failed (${res.status})`,
15346
+ await parseEnvelope(res),
15347
+ res.status
15348
+ );
15349
+ }
15350
+ }
15299
15351
  async function fetchResourceStatus(apiUrl, token, path) {
15300
15352
  const res = await fetch(`${apiUrl}${path}`, {
15301
15353
  headers: { authorization: `Bearer ${token}` }
@@ -17377,6 +17429,131 @@ async function runAiStatus(opts) {
17377
17429
  reportError(err);
17378
17430
  }
17379
17431
  }
17432
+ function isoDay(iso) {
17433
+ return iso.slice(0, 10);
17434
+ }
17435
+ function daysUntil(iso) {
17436
+ return Math.floor((new Date(iso).getTime() - Date.now()) / 864e5);
17437
+ }
17438
+ var TOKEN_EXPIRY_WARNING_DAYS = 30;
17439
+ async function runWorkflowAdd(opts) {
17440
+ const apiUrl = resolveApiUrl(opts.apiUrl);
17441
+ const token = loadConfig().token;
17442
+ if (!token) {
17443
+ process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
17444
+ process.exitCode = 1;
17445
+ return;
17446
+ }
17447
+ const project2 = resolveProjectId(opts.project, opts.cwd);
17448
+ if (!project2) {
17449
+ reportNotLinked("kryd workflow add <projectId>");
17450
+ return;
17451
+ }
17452
+ try {
17453
+ const current = await getWorkflowStatus(apiUrl, token, project2);
17454
+ if (current.enabled) {
17455
+ process.stdout.write(
17456
+ `${project2} already has workflows \u2014 HATCHET_CLIENT_TOKEN is injected on deploy` + (current.tokenExpiresAt ? ` and the token expires ${isoDay(current.tokenExpiresAt)}.
17457
+ ` : ".\n") + `Nothing was changed. To rotate the token, run \`kryd workflow remove\` and then \`kryd workflow add\`.
17458
+ `
17459
+ );
17460
+ return;
17461
+ }
17462
+ const status = await addWorkflows(apiUrl, token, { projectId: project2 });
17463
+ process.stdout.write(
17464
+ `Workflows added to ${project2}.
17465
+ HATCHET_CLIENT_TOKEN will be injected on your next deploy (kryd deploy)` + (status.tokenExpiresAt ? `; the token expires ${isoDay(status.tokenExpiresAt)}.
17466
+ ` : ".\n")
17467
+ );
17468
+ } catch (err) {
17469
+ reportError(err);
17470
+ }
17471
+ }
17472
+ async function runWorkflowRemove(opts) {
17473
+ const apiUrl = resolveApiUrl(opts.apiUrl);
17474
+ const token = loadConfig().token;
17475
+ if (!token) {
17476
+ process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
17477
+ process.exitCode = 1;
17478
+ return;
17479
+ }
17480
+ const project2 = resolveProjectId(opts.project, opts.cwd);
17481
+ if (!project2) {
17482
+ reportNotLinked("kryd workflow remove <projectId>");
17483
+ return;
17484
+ }
17485
+ let current;
17486
+ try {
17487
+ current = await getWorkflowStatus(apiUrl, token, project2);
17488
+ } catch (err) {
17489
+ reportError(err);
17490
+ return;
17491
+ }
17492
+ if (!current.enabled) {
17493
+ process.stdout.write(`${project2}: no workflow token \u2014 nothing to remove.
17494
+ `);
17495
+ return;
17496
+ }
17497
+ const proceed = await confirmResourceRemoval({
17498
+ yes: opts.yes,
17499
+ io: opts.io,
17500
+ subject: `workflows from ${project2}`,
17501
+ question: `Remove workflows from ${project2}? The token is revoked \u2014 a running worker loses its connection within seconds \u2014 and HATCHET_CLIENT_TOKEN stops being injected from your next deploy. Workflow history, crons and schedules stay until the project is deleted.`,
17502
+ declined: `Left the workflows on ${project2} in place.
17503
+ `
17504
+ });
17505
+ if (!proceed) return;
17506
+ try {
17507
+ await removeWorkflows(apiUrl, token, project2);
17508
+ process.stdout.write(
17509
+ `Workflows removed from ${project2}: the token is revoked (the engine refuses it within seconds; the HTTP API within a minute) and HATCHET_CLIENT_TOKEN stops being injected from your next deploy (kryd deploy).
17510
+ Workflow history, crons and schedules stay until the project is deleted.
17511
+ `
17512
+ );
17513
+ } catch (err) {
17514
+ reportError(err);
17515
+ }
17516
+ }
17517
+ async function runWorkflowStatus(opts) {
17518
+ const apiUrl = resolveApiUrl(opts.apiUrl);
17519
+ const token = loadConfig().token;
17520
+ if (!token) {
17521
+ process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
17522
+ process.exitCode = 1;
17523
+ return;
17524
+ }
17525
+ const project2 = resolveProjectId(opts.project, opts.cwd);
17526
+ if (!project2) {
17527
+ reportNotLinked("kryd workflow status <projectId>");
17528
+ return;
17529
+ }
17530
+ try {
17531
+ const status = await getWorkflowStatus(apiUrl, token, project2);
17532
+ if (!status.enabled) {
17533
+ process.stdout.write(`${project2}: no workflows.
17534
+ `);
17535
+ return;
17536
+ }
17537
+ if (!status.tokenExpiresAt) {
17538
+ process.stdout.write(`${project2}: workflows enabled.
17539
+ `);
17540
+ return;
17541
+ }
17542
+ const days = daysUntil(status.tokenExpiresAt);
17543
+ process.stdout.write(
17544
+ `${project2}: workflows enabled \u2014 the token expires ${isoDay(status.tokenExpiresAt)}.
17545
+ `
17546
+ );
17547
+ if (days <= TOKEN_EXPIRY_WARNING_DAYS) {
17548
+ process.stdout.write(
17549
+ (days < 0 ? `\u26A0\uFE0F The token has EXPIRED; your worker cannot connect.` : `\u26A0\uFE0F The token expires in ${days} day${days === 1 ? "" : "s"}.`) + ` Rotate it: \`kryd workflow remove\`, then \`kryd workflow add\`, then deploy.
17550
+ `
17551
+ );
17552
+ }
17553
+ } catch (err) {
17554
+ reportError(err);
17555
+ }
17556
+ }
17380
17557
  async function runStorageRemove(opts) {
17381
17558
  const apiUrl = resolveApiUrl(opts.apiUrl);
17382
17559
  const token = loadConfig().token;
@@ -17863,7 +18040,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
17863
18040
  reportError(err);
17864
18041
  }
17865
18042
  }
17866
- var CLI_VERSION = true ? "0.6.0" : "0.0.0-dev";
18043
+ var CLI_VERSION = true ? "0.7.0" : "0.0.0-dev";
17867
18044
  var program = new Command();
17868
18045
  program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
17869
18046
  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(
@@ -17959,6 +18136,10 @@ var ai = program.command("ai").description("Manage the app's EU AI gateway");
17959
18136
  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 }));
17960
18137
  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 }));
17961
18138
  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 }));
18139
+ var workflow = program.command("workflow").description("Manage the project's durable workflows (its own workflow tenant)");
18140
+ 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 }));
18141
+ 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 }));
18142
+ 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 }));
17962
18143
  function invokedDirectly() {
17963
18144
  const entry = process.argv[1];
17964
18145
  if (!entry) return false;
@@ -18004,5 +18185,8 @@ export {
18004
18185
  runStorageRemove,
18005
18186
  runStorageStatus,
18006
18187
  runWhoami,
18188
+ runWorkflowAdd,
18189
+ runWorkflowRemove,
18190
+ runWorkflowStatus,
18007
18191
  splitKeyValue
18008
18192
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryd/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Kryd CLI — push a React / Vite / Next.js app to the European cloud for your AI: git push → live with SSL, one-click managed Postgres & object storage, and an EU-hosted AI gateway already wired in. Your code and your model calls stay in the EU.",
5
5
  "keywords": [
6
6
  "kryd",
@@ -49,8 +49,8 @@
49
49
  "tsx": "^4.19.2",
50
50
  "typescript": "^5.6.3",
51
51
  "vitest": "^2.1.8",
52
- "@kryd/config-ts": "0.0.0",
53
52
  "@kryd/config-eslint": "0.0.0",
53
+ "@kryd/config-ts": "0.0.0",
54
54
  "@kryd/shared-types": "0.0.0"
55
55
  },
56
56
  "scripts": {