@kryd/cli 0.8.1 → 0.9.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 +2 -1
  2. package/dist/index.js +239 -32
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -20,6 +20,7 @@ kryd init # link this folder to a Kryd project (+ a `kryd` g
20
20
  kryd push # push the current branch → build → deploy → live, with live progress
21
21
  kryd logs # the full build log of the latest deploy (kryd push shows progress, not log lines)
22
22
  kryd logs <project> --runtime # tail the live app's stdout/stderr
23
+ kryd logs <project> --worker # tail the workflow worker's stdout/stderr
23
24
  ```
24
25
 
25
26
  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).
@@ -32,7 +33,7 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
32
33
  | `kryd init` | Link the current repo to a Kryd project + register the deploy webhook + add a `kryd` git remote. |
33
34
  | `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. |
34
35
  | `kryd deploy [project]` | Re-deploy the production-branch HEAD already on the forge — no new commit. |
35
- | `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). |
36
+ | `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). |
36
37
  | `kryd rollback [project] [deployment]` | Roll back to a previous successful deploy — no rebuild. |
37
38
  | `kryd db add \| remove \| status [project]` | Attach, tear down or inspect managed Postgres (shared or bring-your-own). `remove` destroys the data and asks first. |
38
39
  | `kryd storage add \| remove \| status [project]` | Attach, tear down or inspect S3-compatible object storage. `remove` destroys every object and asks first. |
package/dist/index.js CHANGED
@@ -56,6 +56,7 @@ function parseDeployEvent(payload) {
56
56
  }
57
57
 
58
58
  // ../../packages/shared-types/dist/runtime-log.js
59
+ var RUNTIME_REFUSAL_CODES = ["no-runtime", "pending"];
59
60
  function parseRuntimeStreamFrame(payload) {
60
61
  let parsed;
61
62
  try {
@@ -67,7 +68,8 @@ function parseRuntimeStreamFrame(payload) {
67
68
  return null;
68
69
  const e = parsed;
69
70
  if (e.kind === "error" && typeof e.message === "string") {
70
- return { kind: "error", message: e.message };
71
+ const code = RUNTIME_REFUSAL_CODES.includes(e.code) ? e.code : void 0;
72
+ return code ? { kind: "error", message: e.message, code } : { kind: "error", message: e.message };
71
73
  }
72
74
  if (e.kind !== "runtime" || e.stream !== "stdout" && e.stream !== "stderr" || typeof e.line !== "string") {
73
75
  return null;
@@ -14902,6 +14904,7 @@ function isTerminalResourceStatus(status) {
14902
14904
  import {
14903
14905
  existsSync as existsSync3,
14904
14906
  mkdirSync as mkdirSync2,
14907
+ readFileSync as readFileSync3,
14905
14908
  realpathSync,
14906
14909
  rmSync as rmSync2,
14907
14910
  writeFileSync as writeFileSync2
@@ -15874,13 +15877,13 @@ async function streamDeploy(apiUrl, token, deploymentId, handlers, opts = {}) {
15874
15877
  return terminal;
15875
15878
  }
15876
15879
  async function streamRuntime(apiUrl, token, id, handlers, opts = {}) {
15877
- const basePath = opts.resource === "deployment" ? "deployments" : "projects";
15880
+ const path = opts.resource === "deployment" ? `deployments/${id}/runtime-logs` : opts.resource === "worker" ? `projects/${id}/workflow-logs` : `projects/${id}/runtime-logs`;
15878
15881
  const doFetch = opts.fetchImpl ?? fetch;
15879
15882
  const reconnectMs = opts.reconnectMs ?? 1e3;
15880
15883
  let afterNs;
15881
15884
  while (!opts.signal?.aborted) {
15882
15885
  try {
15883
- const url2 = new URL(`${apiUrl}/${basePath}/${id}/runtime-logs`);
15886
+ const url2 = new URL(`${apiUrl}/${path}`);
15884
15887
  if (afterNs) url2.searchParams.set("after", afterNs);
15885
15888
  else if (opts.since) url2.searchParams.set("since", opts.since);
15886
15889
  const res = await doFetch(url2, {
@@ -15988,7 +15991,13 @@ function isFramework(value) {
15988
15991
  return FRAMEWORKS.includes(value);
15989
15992
  }
15990
15993
  var DECLARATION_FILE = "kryd.json";
15991
- var DECLARATION_KIND_WORKFLOW = "workflow";
15994
+ var DEFAULT_WORKFLOWS_PATH = "workflows";
15995
+ function declarationJson(decl) {
15996
+ return {
15997
+ ...decl.web ? {} : { web: false },
15998
+ ...decl.workflowsPath !== null ? { workflows: { path: decl.workflowsPath } } : {}
15999
+ };
16000
+ }
15992
16001
  var VITE_META_FRAMEWORKS = [
15993
16002
  "@sveltejs/kit",
15994
16003
  "astro",
@@ -16030,22 +16039,26 @@ function readPackageJson(cwd) {
16030
16039
  return null;
16031
16040
  }
16032
16041
  }
16033
- function declaredKind(cwd) {
16042
+ function readDeclaration(cwd) {
16043
+ let parsed;
16034
16044
  try {
16035
- const parsed = JSON.parse(
16036
- readFileSync2(join2(cwd, DECLARATION_FILE), "utf8")
16037
- );
16038
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
16039
- return null;
16040
- }
16041
- const kind = parsed.kind;
16042
- return typeof kind === "string" ? kind : null;
16045
+ parsed = JSON.parse(readFileSync2(join2(cwd, DECLARATION_FILE), "utf8"));
16043
16046
  } catch {
16044
16047
  return null;
16045
16048
  }
16049
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
16050
+ return null;
16051
+ }
16052
+ const decl = parsed;
16053
+ const workflows = decl.workflows;
16054
+ const workflowsPath = workflows && typeof workflows === "object" && typeof workflows.path === "string" ? workflows.path : null;
16055
+ return { web: decl.web !== false, workflowsPath };
16046
16056
  }
16047
16057
  function frameworkFrom(pkg, cwd) {
16048
- if (declaredKind(cwd) === DECLARATION_KIND_WORKFLOW) return "workflow";
16058
+ const declaration = readDeclaration(cwd);
16059
+ if (declaration && !declaration.web && declaration.workflowsPath !== null) {
16060
+ return "workflow";
16061
+ }
16049
16062
  const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
16050
16063
  const has = (name) => name in deps;
16051
16064
  if (has("next")) return "nextjs";
@@ -16140,6 +16153,10 @@ export const hatchet = HatchetClient.init();
16140
16153
  * One task, to prove the wiring end to end. Add your own beside it and list them in
16141
16154
  * \`src/worker.ts\` \u2014 everything about how a task runs (retries, timeouts, concurrency, crons,
16142
16155
  * DAGs) belongs to the engine's SDK, not to Kryd.
16156
+ *
16157
+ * A task can be cut off by a deploy or a restart: measured, a TypeScript worker stops within seconds
16158
+ * and the engine runs the task again from the start on another worker ~30 s later, even with
16159
+ * \`retries: 0\` \u2014 so write every task so that running it again is safe.
16143
16160
  */
16144
16161
  export const greet = hatchet.task({
16145
16162
  name: "greet",
@@ -16188,7 +16205,15 @@ async function main(): Promise<void> {
16188
16205
  const { hatchet } = await import("./hatchet");
16189
16206
  const { greet } = await import("./tasks");
16190
16207
 
16191
- const worker = await hatchet.worker(WORKER_NAME, { workflows: [greet] });
16208
+ // Kryd sets KRYD_DEPLOYMENT_ID on a deployed worker, and the label is how its deploy gate tells
16209
+ // THIS deploy's worker from the previous one, which keeps running while it drains. Keep the line:
16210
+ // the gate requires it, and a worker without it never counts as this deploy's, so the deploy
16211
+ // never goes live. Locally the variable is absent, so no label.
16212
+ const deployId = process.env.KRYD_DEPLOYMENT_ID;
16213
+ const worker = await hatchet.worker(WORKER_NAME, {
16214
+ workflows: [greet],
16215
+ labels: deployId ? { "kryd-deploy": deployId } : undefined,
16216
+ });
16192
16217
 
16193
16218
  // start() resolves when the worker stops, so it is not awaited here; waitUntilReady() flips the
16194
16219
  // health flag once the engine has the worker. A failed registration rejects \`running\` and the
@@ -16254,6 +16279,11 @@ from hatchet_sdk import Context, EmptyModel
16254
16279
  # One task, to prove the wiring end to end. Add your own beside it and list them in main.py \u2014
16255
16280
  # everything about how a task runs (retries, timeouts, concurrency, crons, DAGs) belongs to the
16256
16281
  # engine's SDK, not to Kryd.
16282
+ #
16283
+ # A task can be cut off by a deploy or a restart: a Python worker finishes a running task before it
16284
+ # stops (measured with 33 s left), but the stop has a 90 s grace period, and a task still running
16285
+ # after it is killed and run again from the start on another worker, even with retries=0 (measured)
16286
+ # \u2014 so write every task so that running it again is safe.
16257
16287
  @hatchet.task(name="greet")
16258
16288
  def greet(input: EmptyModel, ctx: Context) -> dict[str, str]:
16259
16289
  return {"greeting": "Hello, world"}
@@ -16430,6 +16460,11 @@ type GreetOutput struct {
16430
16460
  // Greet is one task, to prove the wiring end to end. Add your own beside it and register them in
16431
16461
  // main.go \u2014 everything about how a task runs (retries, timeouts, concurrency, crons, DAGs) belongs
16432
16462
  // to the engine's SDK, not to Kryd.
16463
+ //
16464
+ // A task can be cut off by a deploy or a restart: measured, a Go worker exits within a second of the
16465
+ // stop signal without waiting for running tasks, and the engine runs the task again from the start
16466
+ // on another worker ~30 s later, even with retries set to 0 \u2014 so write every task so that running it
16467
+ // again is safe.
16433
16468
  func Greet(c *hatchet.Client) *hatchet.StandaloneTask {
16434
16469
  return c.NewStandaloneTask("greet", func(ctx hatchet.Context, input GreetInput) (GreetOutput, error) {
16435
16470
  name := input.Name
@@ -16512,7 +16547,15 @@ func main() {
16512
16547
  log.Fatalf("could not create the Hatchet client: %v", err)
16513
16548
  }
16514
16549
 
16515
- worker, err := client.NewWorker(workerName, hatchet.WithWorkflows(Greet(client)))
16550
+ // Kryd sets KRYD_DEPLOYMENT_ID on a deployed worker, and the label is how its deploy gate tells
16551
+ // THIS deploy's worker from the previous one, which keeps running while it drains. Keep it:
16552
+ // the gate requires it, and a worker without it never counts as this deploy's, so the deploy
16553
+ // never goes live. Locally the variable is absent, so no label.
16554
+ opts := []hatchet.WorkerOption{hatchet.WithWorkflows(Greet(client))}
16555
+ if deployID := os.Getenv("KRYD_DEPLOYMENT_ID"); deployID != "" {
16556
+ opts = append(opts, hatchet.WithLabels(map[string]any{"kryd-deploy": deployID}))
16557
+ }
16558
+ worker, err := client.NewWorker(workerName, opts...)
16516
16559
  if err != nil {
16517
16560
  log.Fatalf("could not create the worker: %v", err)
16518
16561
  }
@@ -16571,15 +16614,19 @@ var TEMPLATES = {
16571
16614
  python: PYTHON_TEMPLATE,
16572
16615
  go: GO_TEMPLATE
16573
16616
  };
16574
- function declarationFile() {
16617
+ function declarationFile(decl) {
16575
16618
  return {
16576
16619
  path: DECLARATION_FILE,
16577
- contents: `${JSON.stringify({ kind: DECLARATION_KIND_WORKFLOW }, null, 2)}
16620
+ contents: `${JSON.stringify(declarationJson(decl), null, 2)}
16578
16621
  `
16579
16622
  };
16580
16623
  }
16624
+ var WORKER_ONLY = { web: false, workflowsPath: "." };
16625
+ function workerFiles(language, workerName) {
16626
+ return TEMPLATES[language].files(workerName);
16627
+ }
16581
16628
  function scaffoldFiles(language, workerName) {
16582
- return [...TEMPLATES[language].files(workerName), declarationFile()];
16629
+ return [...workerFiles(language, workerName), declarationFile(WORKER_ONLY)];
16583
16630
  }
16584
16631
  function firstCommand(language) {
16585
16632
  return TEMPLATES[language].firstCommand;
@@ -17313,11 +17360,14 @@ Install git (https://git-scm.com/downloads), then re-run \`kryd init\` here.
17313
17360
  `;
17314
17361
  break;
17315
17362
  }
17316
- const routing = project2.framework === "workflow" ? `No public URL: a workflow worker dials out to the engine and serves nothing.
17363
+ const declaration = readDeclaration(cwd);
17364
+ const workerOnly = declaration !== null ? !declaration.web && declaration.workflowsPath !== null : project2.framework === "workflow";
17365
+ const routing = workerOnly ? `No public URL: a workflow worker dials out to the engine and serves nothing.
17317
17366
  Scheduled and triggered work runs on your production deploy; preview branches build but do not start a worker.
17318
17367
  ` : `Production URL: https://${project2.subdomain}.${PRODUCTION_TLD}
17319
17368
  Preview branches deploy to https://<branch>-<hash>-${project2.subdomain}.${PREVIEW_TLD}
17320
- `;
17369
+ ` + (declaration?.workflowsPath ? `Workflows in ${declaration.workflowsPath}/ run as a worker beside it on production deploys; previews run the web app only.
17370
+ ` : "");
17321
17371
  process.stdout.write(
17322
17372
  `Linked "${name}" (${project2.framework}) \u2192 ${repo.htmlUrl}
17323
17373
  ` + routing + `${linkNote}
@@ -17452,9 +17502,16 @@ async function runRuntimeLogs(opts) {
17452
17502
  return;
17453
17503
  }
17454
17504
  const isDeployment = opts.project?.startsWith("dpl_") ?? false;
17505
+ if (opts.worker && isDeployment) {
17506
+ process.stderr.write(
17507
+ "--worker takes a project, not a deployment id: use `kryd logs <project> --worker` to follow the project's worker.\n"
17508
+ );
17509
+ process.exitCode = 1;
17510
+ return;
17511
+ }
17455
17512
  const target = isDeployment ? opts.project : resolveProjectId(opts.project, opts.cwd);
17456
17513
  if (!target) {
17457
- reportNotLinked("kryd logs <projectId> --runtime");
17514
+ reportNotLinked(opts.worker ? "kryd logs <projectId> --worker" : "kryd logs <projectId> --runtime");
17458
17515
  return;
17459
17516
  }
17460
17517
  if (opts.since !== void 0 && !/^\d+(s|m|h|d)$/.test(opts.since.trim())) {
@@ -17483,7 +17540,7 @@ async function runRuntimeLogs(opts) {
17483
17540
  {
17484
17541
  ...opts.since ? { since: opts.since } : {},
17485
17542
  signal: controller.signal,
17486
- ...isDeployment ? { resource: "deployment" } : {}
17543
+ ...isDeployment ? { resource: "deployment" } : opts.worker ? { resource: "worker" } : {}
17487
17544
  }
17488
17545
  );
17489
17546
  } catch (err) {
@@ -18346,6 +18403,22 @@ async function runWorkflowInit(opts) {
18346
18403
  }
18347
18404
  const language = opts.language;
18348
18405
  const target = opts.dir ? resolve2(cwd, opts.dir) : cwd;
18406
+ if (!opts.only) {
18407
+ scaffoldContainedWorker({
18408
+ language,
18409
+ target,
18410
+ ...opts.path !== void 0 ? { path: opts.path } : {},
18411
+ ...opts.name !== void 0 ? { name: opts.name } : {}
18412
+ });
18413
+ return;
18414
+ }
18415
+ if (opts.path !== void 0) {
18416
+ process.stderr.write(
18417
+ "--path is for a worker inside an app; with --only the worker IS the repository root.\n"
18418
+ );
18419
+ process.exitCode = 1;
18420
+ return;
18421
+ }
18349
18422
  const workerName = workerNameFrom(opts.name ?? basename2(target));
18350
18423
  const files = scaffoldFiles(language, workerName);
18351
18424
  const clashes = files.map((f) => f.path).filter((p) => existsSync3(join3(target, p)));
@@ -18391,9 +18464,10 @@ Nothing was left behind.
18391
18464
  if (repo.status === "nested") {
18392
18465
  process.stderr.write(
18393
18466
  `That directory is inside the git repository at ${repo.root}.
18394
- A worker has to be its own repository: Kryd builds one app per repository, and git would
18395
- write the deploy remote into the enclosing repo, so \`kryd push\` would push that tree instead.
18467
+ A worker-only project (--only) has to be its own repository: git would write the deploy
18468
+ remote into the enclosing repo, so \`kryd push\` would push that tree instead.
18396
18469
  The files above were written \u2014 move them to a directory of their own, or run this outside that repository.
18470
+ To add a worker to the app in that repository instead, run \`kryd workflow init\` (without --only) at its root.
18397
18471
  `
18398
18472
  );
18399
18473
  process.exitCode = 1;
@@ -18440,6 +18514,131 @@ HATCHET_CLIENT_TOKEN is injected into your container on deploy; it is never avai
18440
18514
  `
18441
18515
  );
18442
18516
  }
18517
+ function scaffoldContainedWorker(opts) {
18518
+ const { language, target } = opts;
18519
+ if (!directoryIsLinked(target)) {
18520
+ process.stderr.write(
18521
+ "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"
18522
+ );
18523
+ process.exitCode = 1;
18524
+ return;
18525
+ }
18526
+ const raw = (opts.path ?? DEFAULT_WORKFLOWS_PATH).trim();
18527
+ const segments = raw.split("/").filter((seg) => seg !== "" && seg !== ".");
18528
+ if (raw.startsWith("/") || raw.includes("\\") || segments.includes("..") || segments.length === 0) {
18529
+ process.stderr.write(
18530
+ `--path must be a folder inside this project (e.g. "${DEFAULT_WORKFLOWS_PATH}"), not "${raw}". For a worker at the repository root, use --only in a directory of its own.
18531
+ `
18532
+ );
18533
+ process.exitCode = 1;
18534
+ return;
18535
+ }
18536
+ const folder = segments.join("/");
18537
+ const declarationPath = join3(target, DECLARATION_FILE);
18538
+ let existing = {};
18539
+ let existingText = null;
18540
+ if (existsSync3(declarationPath)) {
18541
+ existingText = readFileSync3(declarationPath, "utf8");
18542
+ let parsed;
18543
+ try {
18544
+ parsed = JSON.parse(existingText);
18545
+ } catch {
18546
+ parsed = void 0;
18547
+ }
18548
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
18549
+ process.stderr.write(
18550
+ `${DECLARATION_FILE} is not a JSON object, so a worker cannot be added to it. Fix or remove it, then run this again.
18551
+ `
18552
+ );
18553
+ process.exitCode = 1;
18554
+ return;
18555
+ }
18556
+ existing = parsed;
18557
+ if ("kind" in existing) {
18558
+ process.stderr.write(
18559
+ `${DECLARATION_FILE} still uses \`{"kind":"workflow"}\`, which is no longer read. A worker-only repository is \`{"web": false, "workflows": {"path": "."}}\`; an app with workflows in a folder is \`{"workflows": {"path": "workflows"}}\`. Update it, then run this again if you still want a worker folder. Nothing was written.
18560
+ `
18561
+ );
18562
+ process.exitCode = 1;
18563
+ return;
18564
+ }
18565
+ if ("workflows" in existing || existing["web"] === false) {
18566
+ process.stderr.write(
18567
+ `${DECLARATION_FILE} already declares a worker for this project \u2014 a project has one. Nothing was written.
18568
+ `
18569
+ );
18570
+ process.exitCode = 1;
18571
+ return;
18572
+ }
18573
+ }
18574
+ const workerName = workerNameFrom(opts.name ?? basename2(target));
18575
+ const files = workerFiles(language, workerName).map((f) => ({
18576
+ path: `${folder}/${f.path}`,
18577
+ contents: f.contents
18578
+ }));
18579
+ const clashes = files.map((f) => f.path).filter((p) => existsSync3(join3(target, p)));
18580
+ if (clashes.length > 0) {
18581
+ process.stderr.write(
18582
+ `Refusing to overwrite: ${clashes.join(", ")}.
18583
+ Nothing was written. Pick another folder with --path <folder>.
18584
+ `
18585
+ );
18586
+ process.exitCode = 1;
18587
+ return;
18588
+ }
18589
+ const declaration = { ...existing, workflows: { path: folder } };
18590
+ const written = [];
18591
+ try {
18592
+ for (const file2 of files) {
18593
+ const full = join3(target, file2.path);
18594
+ mkdirSync2(dirname2(full), { recursive: true });
18595
+ writeFileSync2(full, file2.contents);
18596
+ written.push(full);
18597
+ }
18598
+ writeFileSync2(declarationPath, `${JSON.stringify(declaration, null, 2)}
18599
+ `);
18600
+ } catch (err) {
18601
+ for (const path of written.reverse()) {
18602
+ try {
18603
+ rmSync2(path);
18604
+ } catch {
18605
+ }
18606
+ }
18607
+ try {
18608
+ if (existingText === null) rmSync2(declarationPath, { force: true });
18609
+ else writeFileSync2(declarationPath, existingText);
18610
+ } catch {
18611
+ }
18612
+ process.stderr.write(
18613
+ `Could not write the scaffold (${err instanceof Error ? err.message : String(err)}).
18614
+ Nothing was left behind.
18615
+ `
18616
+ );
18617
+ process.exitCode = 1;
18618
+ return;
18619
+ }
18620
+ const next = [];
18621
+ const first = firstCommand(language);
18622
+ if (first) next.push(`(cd ${folder} && ${first})`);
18623
+ next.push(
18624
+ "kryd workflow add",
18625
+ `git add ${DECLARATION_FILE} ${folder} && git commit -m "add workflows"`,
18626
+ "kryd push"
18627
+ );
18628
+ process.stdout.write(
18629
+ `Scaffolded a ${language} workflow worker in ${folder}/:
18630
+ ` + files.map((f) => ` ${f.path}
18631
+ `).join("") + `and declared it in ${DECLARATION_FILE}: ${JSON.stringify({ workflows: { path: folder } })}
18632
+
18633
+ Every production push now builds your app AND this worker, and goes live only when both are healthy. Previews run the app only.
18634
+ Next: ${next.map((c) => `\`${c}\``).join(", then ")}.
18635
+ HATCHET_CLIENT_TOKEN is injected into your app and your worker on deploy \u2014 your app triggers tasks with it; it is never available locally.
18636
+ ` + // The app is still built (and type-checked) from the root, so a tsconfig that includes every
18637
+ // file would compile the worker too — against an SDK the app does not depend on.
18638
+ (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.
18639
+ ` : "")
18640
+ );
18641
+ }
18443
18642
  async function runStorageRemove(opts) {
18444
18643
  const apiUrl = resolveApiUrl(opts.apiUrl);
18445
18644
  const token = loadConfig().token;
@@ -18926,7 +19125,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
18926
19125
  await reportError(err);
18927
19126
  }
18928
19127
  }
18929
- var CLI_VERSION = true ? "0.8.1" : "0.0.0-dev";
19128
+ var CLI_VERSION = true ? "0.9.0" : "0.0.0-dev";
18930
19129
  var program = new Command();
18931
19130
  program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
18932
19131
  program.hook("postAction", async () => {
@@ -18948,7 +19147,7 @@ program.command("git-credential <operation>", { hidden: true }).description("git
18948
19147
  });
18949
19148
  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(
18950
19149
  "--framework <framework>",
18951
- "react-router | nextjs | vite-spa | node (auto-detected if omitted; `workflow` comes from a committed kryd.json)"
19150
+ 'react-router | nextjs | vite-spa | node (auto-detected if omitted; `workflow` comes from a committed kryd.json with "web": false)'
18952
19151
  ).option(
18953
19152
  "--tenant <slug>",
18954
19153
  // KRYD-265 / KRYD-188: this used to say "claimed once on first init", which was false — signup
@@ -18961,15 +19160,23 @@ program.command("init").description("Link this project's repo + register its pus
18961
19160
  "create the repository PUBLIC \u2014 readable by anyone signed in to the forge (default: private). Set at creation only"
18962
19161
  ).option("--api-url <url>", "control-plane API base URL").action((opts) => runInit(opts));
18963
19162
  program.command("logs [target]").description(
18964
- "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)."
19163
+ "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."
18965
19164
  ).option(
18966
19165
  "--runtime",
18967
19166
  "stream a container's runtime stdout/stderr instead of build/deploy logs \u2014 the live app (project id) or one deploy's container (dpl_ id)"
19167
+ ).option(
19168
+ "--worker",
19169
+ "with a project id: stream its workflow WORKER's stdout/stderr (implies --runtime) \u2014 for a project with a web app too, --runtime shows the web app"
18968
19170
  ).option(
18969
19171
  "--since <dur>",
18970
- "with --runtime: backfill this window before going live (e.g. 30m, 2h, 1d; max 7d)"
19172
+ "with --runtime/--worker: backfill this window before going live (e.g. 30m, 2h, 1d; max 7d)"
18971
19173
  ).option("--api-url <url>", "control-plane API base URL").action(
18972
- (target, opts) => opts.runtime ? runRuntimeLogs({ project: target, since: opts.since, apiUrl: opts.apiUrl }) : runLogs({ apiUrl: opts.apiUrl, deployment: target })
19174
+ (target, opts) => opts.runtime || opts.worker ? runRuntimeLogs({
19175
+ project: target,
19176
+ since: opts.since,
19177
+ apiUrl: opts.apiUrl,
19178
+ ...opts.worker ? { worker: true } : {}
19179
+ }) : runLogs({ apiUrl: opts.apiUrl, deployment: target })
18973
19180
  );
18974
19181
  program.command("push [branch]").description(
18975
19182
  "Push to the kryd remote and follow the deploy it triggers (defaults to the current branch)"
@@ -19038,11 +19245,11 @@ workflow.command("add [project]").description("Give the project a workflow tenan
19038
19245
  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 }));
19039
19246
  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 }));
19040
19247
  workflow.command("init").description(
19041
- "Scaffold a workflow worker here (with the health listener Kryd's deploy gate needs) and link it"
19248
+ "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"
19042
19249
  ).requiredOption(
19043
19250
  "--language <language>",
19044
19251
  `worker language: ${SCAFFOLD_LANGUAGES.join(" | ")}`
19045
- ).option("--dir <path>", "write into this directory instead of the current one").option("--name <name>", "project and worker name (defaults to the directory name)").option("--no-link", "only write the files; do not create a Kryd project").option("--api-url <url>", "control-plane API base URL").action((opts) => runWorkflowInit(opts));
19252
+ ).option("--path <folder>", "the worker's folder inside the app (default: workflows)").option("--only", "a project that runs ONLY a worker: scaffold it at the root, git init, and link").option("--dir <path>", "the project directory, instead of the current one").option("--name <name>", "worker name (and, with --only, project name); defaults to the directory name").option("--no-link", "with --only: only write the files; do not create a Kryd project").option("--api-url <url>", "control-plane API base URL").action((opts) => runWorkflowInit(opts));
19046
19253
  function invokedDirectly() {
19047
19254
  const entry = process.argv[1];
19048
19255
  if (!entry) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryd/cli",
3
- "version": "0.8.1",
3
+ "version": "0.9.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",
@@ -50,8 +50,8 @@
50
50
  "typescript": "^5.6.3",
51
51
  "vitest": "^2.1.8",
52
52
  "@kryd/shared-types": "0.0.0",
53
- "@kryd/config-ts": "0.0.0",
54
- "@kryd/config-eslint": "0.0.0"
53
+ "@kryd/config-eslint": "0.0.0",
54
+ "@kryd/config-ts": "0.0.0"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "esbuild src/index.ts --bundle --platform=node --format=esm --external:commander --define:__KRYD_VERSION__=\\\"$npm_package_version\\\" --outfile=dist/index.js",