@kryd/cli 0.3.0 → 0.4.1

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 (2) hide show
  1. package/dist/index.js +168 -15
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -14666,6 +14666,23 @@ function date4(params) {
14666
14666
  config(en_default());
14667
14667
 
14668
14668
  // ../../packages/shared-types/dist/usage.js
14669
+ var USAGE_OUTCOME = {
14670
+ ok: "ok",
14671
+ /** Refused by the request-rate limiter. */
14672
+ throttledRate: "throttled_rate",
14673
+ /** Refused by the daily token cap. */
14674
+ throttledCap: "throttled_cap"
14675
+ };
14676
+ var USAGE_OUTCOMES = [
14677
+ USAGE_OUTCOME.ok,
14678
+ USAGE_OUTCOME.throttledRate,
14679
+ USAGE_OUTCOME.throttledCap
14680
+ ];
14681
+ var STORAGE_FRESHNESS_MS = 3 * 60 * 60 * 1e3;
14682
+ var USAGE_THROTTLED_OUTCOMES = [
14683
+ USAGE_OUTCOME.throttledRate,
14684
+ USAGE_OUTCOME.throttledCap
14685
+ ];
14669
14686
  var usageEventSchema = external_exports.object({
14670
14687
  // Gateway-assigned id (stable across POST retries) → the ingest inserts `onConflictDoNothing` on
14671
14688
  // it, so a re-sent batch after a lost ACK never double-counts (KRYD-35 review).
@@ -14688,7 +14705,7 @@ var usageEventSchema = external_exports.object({
14688
14705
  // fail the whole batch — so one malformed outcome can't discard up to 999 valid usage events in the
14689
14706
  // same POST (the batch-loss failure mode the FK-free `account_id` decision already guards, KRYD-35).
14690
14707
  // Because only in-set values are ever persisted, the DB column needs no CHECK constraint.
14691
- outcome: external_exports.enum(["ok", "throttled_rate", "throttled_cap"]).default("ok").catch("ok")
14708
+ outcome: external_exports.enum(USAGE_OUTCOMES).default(USAGE_OUTCOME.ok).catch(USAGE_OUTCOME.ok)
14692
14709
  });
14693
14710
  var usageIngestBatchSchema = external_exports.object({
14694
14711
  events: external_exports.array(usageEventSchema).min(1).max(1e3)
@@ -15327,6 +15344,15 @@ async function getProject(apiUrl, token, projectId) {
15327
15344
  }
15328
15345
  return await res.json();
15329
15346
  }
15347
+ async function listProjects(apiUrl, token) {
15348
+ const res = await fetch(`${apiUrl}/projects`, {
15349
+ headers: { authorization: `Bearer ${token}` }
15350
+ });
15351
+ if (!res.ok) {
15352
+ throw new ApiError(`Listing projects failed (${res.status})`, await parseEnvelope(res));
15353
+ }
15354
+ return (await res.json()).items;
15355
+ }
15330
15356
  async function deleteProject(apiUrl, token, projectId) {
15331
15357
  const res = await fetch(`${apiUrl}/projects/${encodeURIComponent(projectId)}`, {
15332
15358
  method: "DELETE",
@@ -15741,6 +15767,29 @@ var VITE_META_FRAMEWORKS = [
15741
15767
  "vike"
15742
15768
  ];
15743
15769
  var SERVER_FRAMEWORKS = ["hono", "express", "fastify", "koa"];
15770
+ var NON_NODE_START_TOOLS = [
15771
+ "vite",
15772
+ "next",
15773
+ "astro",
15774
+ "nuxt",
15775
+ "remix",
15776
+ "react-router",
15777
+ "svelte",
15778
+ "solid-start",
15779
+ "qwik",
15780
+ "vike",
15781
+ "parcel",
15782
+ "webpack",
15783
+ "rollup",
15784
+ "serve",
15785
+ "http-server"
15786
+ ];
15787
+ function startScriptLooksLikePlainNode(start) {
15788
+ const s = start.toLowerCase();
15789
+ return !NON_NODE_START_TOOLS.some(
15790
+ (tool) => new RegExp(`(^|[\\s/"'\`=])${tool}([\\s@/"'\`]|$)`).test(s)
15791
+ );
15792
+ }
15744
15793
  function readPackageJson(cwd) {
15745
15794
  try {
15746
15795
  return JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
@@ -15758,6 +15807,8 @@ function frameworkFrom(pkg, cwd) {
15758
15807
  if (VITE_META_FRAMEWORKS.some(has)) return null;
15759
15808
  if (SERVER_FRAMEWORKS.some(has)) return "node";
15760
15809
  if (has("vite")) return "vite-spa";
15810
+ const start = pkg?.scripts?.start?.trim();
15811
+ if (start && startScriptLooksLikePlainNode(start)) return "node";
15761
15812
  return null;
15762
15813
  }
15763
15814
  function nameFrom(pkg, cwd) {
@@ -15876,16 +15927,34 @@ function configureGitRemote(cwd, remote, url2) {
15876
15927
  }
15877
15928
  try {
15878
15929
  const remotes = run(["remote"]).split(/\s+/).filter(Boolean);
15879
- if (remotes.includes(remote)) {
15880
- run(["remote", "set-url", remote, url2]);
15881
- return { status: "updated", remote };
15882
- }
15883
- run(["remote", "add", remote, url2]);
15884
- return { status: "added", remote };
15930
+ const status = remotes.includes(remote) ? "updated" : "added";
15931
+ if (status === "updated") run(["remote", "set-url", remote, url2]);
15932
+ else run(["remote", "add", remote, url2]);
15933
+ return { status, remote, tracking: setUpstreamIfUnset(run, remote) };
15885
15934
  } catch {
15886
15935
  return { status: "unavailable" };
15887
15936
  }
15888
15937
  }
15938
+ function setUpstreamIfUnset(run, remote) {
15939
+ let branch;
15940
+ try {
15941
+ branch = run(["symbolic-ref", "--short", "HEAD"]);
15942
+ } catch {
15943
+ return "detached";
15944
+ }
15945
+ if (!branch) return "detached";
15946
+ try {
15947
+ if (run(["config", "--get", `branch.${branch}.remote`])) return "kept-existing";
15948
+ } catch {
15949
+ }
15950
+ try {
15951
+ run(["config", `branch.${branch}.remote`, remote]);
15952
+ run(["config", `branch.${branch}.merge`, `refs/heads/${branch}`]);
15953
+ return "set";
15954
+ } catch {
15955
+ return "unavailable";
15956
+ }
15957
+ }
15889
15958
  function probe(cwd, args) {
15890
15959
  try {
15891
15960
  const value = execFileSync("git", args, {
@@ -16022,7 +16091,10 @@ async function runInit(opts) {
16022
16091
  // First `kryd init` for the account claims the tenant slug (the vanity routing key in
16023
16092
  // every deploy URL); later inits omit it (the account already has one). Story 3.8.
16024
16093
  // Built conditionally (exactOptionalPropertyTypes — don't pass an explicit undefined).
16025
- ...opts.tenant ? { tenantSlug: opts.tenant } : {}
16094
+ ...opts.tenant ? { tenantSlug: opts.tenant } : {},
16095
+ // KRYD-184a: only sent when the flag is present. The API defaults to private and sends
16096
+ // `private` explicitly to the forge on every path, so omitting this is never "public".
16097
+ ...opts.public ? { public: true } : {}
16026
16098
  });
16027
16099
  let linkNote = "";
16028
16100
  try {
@@ -16051,7 +16123,9 @@ async function runInit(opts) {
16051
16123
  case "added":
16052
16124
  case "updated":
16053
16125
  nextStep = `${remote.status === "added" ? "Added" : "Updated"} git remote "${remote.remote}" (with your push credential).
16054
- Next: kryd push # push to deploy (same as \`git push ${remote.remote} ${branch}\`)
16126
+ ` + (remote.tracking === "set" ? `Tracking set: \`git push\` and \`kryd push\` both deploy this branch.
16127
+ ` : remote.tracking === "kept-existing" ? `This branch already tracks another remote, so it was left alone \u2014 deploy with \`kryd push\`.
16128
+ ` : "") + `Next: kryd push # push to deploy (same as \`git push ${remote.remote} ${branch}\`)
16055
16129
  `;
16056
16130
  break;
16057
16131
  case "not-a-repo":
@@ -16522,6 +16596,37 @@ function reportDetachResult(result, opts) {
16522
16596
  process.exitCode = 1;
16523
16597
  }
16524
16598
  }
16599
+ function isProjectId(arg) {
16600
+ return arg.startsWith("proj_");
16601
+ }
16602
+ async function resolveProjectIdByName(apiUrl, token, name) {
16603
+ let projects;
16604
+ try {
16605
+ projects = await listProjects(apiUrl, token);
16606
+ } catch (err) {
16607
+ reportError(err);
16608
+ return null;
16609
+ }
16610
+ const matches = projects.filter((p) => p.name === name);
16611
+ if (matches.length === 0) {
16612
+ process.stderr.write(
16613
+ `No project named ${name} on this account. Run \`kryd project list\` to see the names.
16614
+ `
16615
+ );
16616
+ process.exitCode = 1;
16617
+ return null;
16618
+ }
16619
+ if (matches.length === 1) return matches[0].id;
16620
+ const listed = matches.map((p) => ` ${p.id} ${p.status}
16621
+ `).join("");
16622
+ process.stderr.write(
16623
+ `${matches.length} projects are named ${name}:
16624
+ ${listed}Re-run \`kryd project rm <id>\` with the one you mean.
16625
+ `
16626
+ );
16627
+ process.exitCode = 1;
16628
+ return null;
16629
+ }
16525
16630
  async function runProjectRemove(opts) {
16526
16631
  const apiUrl = resolveApiUrl(opts.apiUrl);
16527
16632
  const token = loadConfig().token;
@@ -16530,9 +16635,15 @@ async function runProjectRemove(opts) {
16530
16635
  process.exitCode = 1;
16531
16636
  return;
16532
16637
  }
16533
- const projectId = resolveProjectId(opts.project, opts.cwd);
16638
+ let projectId;
16639
+ if (opts.project !== void 0 && !isProjectId(opts.project)) {
16640
+ projectId = await resolveProjectIdByName(apiUrl, token, opts.project);
16641
+ if (!projectId) return;
16642
+ } else {
16643
+ projectId = resolveProjectId(opts.project, opts.cwd);
16644
+ }
16534
16645
  if (!projectId) {
16535
- reportNotLinked("kryd project rm <projectId>");
16646
+ reportNotLinked("kryd project rm <name|id>");
16536
16647
  return;
16537
16648
  }
16538
16649
  let project2;
@@ -16540,8 +16651,10 @@ async function runProjectRemove(opts) {
16540
16651
  try {
16541
16652
  project2 = await getProject(apiUrl, token, projectId);
16542
16653
  if (!project2) {
16543
- process.stderr.write(`No project ${projectId} on this account.
16544
- `);
16654
+ process.stderr.write(
16655
+ `No project with id ${projectId} on this account. Run \`kryd project list\` to see your projects.
16656
+ `
16657
+ );
16545
16658
  process.exitCode = 1;
16546
16659
  return;
16547
16660
  }
@@ -16738,6 +16851,40 @@ function envAnnotation(environments) {
16738
16851
  if (preview && !prod) return " \u2192 preview only";
16739
16852
  return "";
16740
16853
  }
16854
+ async function runProjectList(opts) {
16855
+ const apiUrl = resolveApiUrl(opts.apiUrl);
16856
+ const token = loadConfig().token;
16857
+ if (!token) {
16858
+ process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
16859
+ process.exitCode = 1;
16860
+ return;
16861
+ }
16862
+ let projects;
16863
+ try {
16864
+ projects = await listProjects(apiUrl, token);
16865
+ } catch (err) {
16866
+ reportError(err);
16867
+ return;
16868
+ }
16869
+ if (projects.length === 0) {
16870
+ process.stdout.write("No projects yet \u2014 run `kryd init` in a project directory.\n");
16871
+ return;
16872
+ }
16873
+ const linkedId = resolveProjectId(void 0, opts.cwd);
16874
+ const nameWidth = projects.reduce((max, p) => Math.max(max, p.name.length), 0);
16875
+ const subWidth = projects.reduce((max, p) => Math.max(max, p.subdomain.length), 0);
16876
+ const statusWidth = projects.reduce((max, p) => Math.max(max, p.status.length), 0);
16877
+ for (const p of projects) {
16878
+ const mark = p.id === linkedId ? "*" : " ";
16879
+ process.stdout.write(
16880
+ `${mark} ${p.name.padEnd(nameWidth)} ${p.subdomain.padEnd(subWidth)} ${p.status.padEnd(statusWidth)} ${p.id}
16881
+ `
16882
+ );
16883
+ }
16884
+ if (projects.some((p) => p.id === linkedId)) {
16885
+ process.stdout.write("\n* linked to this directory\n");
16886
+ }
16887
+ }
16741
16888
  function renderEnvGroup(heading, vars, opts) {
16742
16889
  const width = vars.reduce((max, v) => Math.max(max, v.key.length), 0);
16743
16890
  const dates = opts.showUpdated ? vars.map((v) => isoDate(v.updatedAt)) : [];
@@ -17127,7 +17274,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
17127
17274
  reportError(err);
17128
17275
  }
17129
17276
  }
17130
- var CLI_VERSION = true ? "0.3.0" : "0.0.0-dev";
17277
+ var CLI_VERSION = true ? "0.4.1" : "0.0.0-dev";
17131
17278
  var program = new Command();
17132
17279
  program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
17133
17280
  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(
@@ -17145,6 +17292,10 @@ program.command("init").description("Link this project's repo + register its pus
17145
17292
  // has always assigned a slug, so the flag could only agree with it or 409. It now changes the
17146
17293
  // slug, and only while nothing derives from it.
17147
17294
  "your tenant slug \u2014 appears in every deploy URL; changeable until your first project"
17295
+ ).option(
17296
+ "--public",
17297
+ // KRYD-184a. Chosen at creation; there is no command to flip it afterwards yet (KRYD-341).
17298
+ "create the repository PUBLIC \u2014 readable by anyone signed in to the forge (default: private). Set at creation only"
17148
17299
  ).option("--api-url <url>", "control-plane API base URL").action((opts) => runInit(opts));
17149
17300
  program.command("logs [target]").description(
17150
17301
  "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)."
@@ -17170,8 +17321,9 @@ program.command("redeploy [project]").description(
17170
17321
  "Redeploy the current live commit (no rebuild) to apply config/env changes, and follow it live"
17171
17322
  ).option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runRedeploy({ ...opts, project: project2 }));
17172
17323
  var project = program.command("project").description("Manage your projects");
17324
+ project.command("list").description("List your projects \u2014 name, subdomain, status and id").option("--api-url <url>", "control-plane API base URL").action((opts) => runProjectList(opts));
17173
17325
  project.command("rm [project]").description(
17174
- "Permanently delete a project and everything Kryd created for it (asks you to type its name)"
17326
+ "Permanently delete a project and everything Kryd created for it (by name or id; asks you to type its name)"
17175
17327
  ).option("--yes", "skip the confirmation (for non-interactive use)").option("--api-url <url>", "control-plane API base URL").action((projectArg, opts) => runProjectRemove({ ...opts, project: projectArg }));
17176
17328
  var db = program.command("db").description("Manage project databases");
17177
17329
  db.command("create [project]").description("Attach a shared or bring-your-own Postgres database to a project").option("--shared", "attach a shared managed database (the default)").option(
@@ -17237,6 +17389,7 @@ export {
17237
17389
  runLogin,
17238
17390
  runLogout,
17239
17391
  runLogs,
17392
+ runProjectList,
17240
17393
  runProjectRemove,
17241
17394
  runPush,
17242
17395
  runRedeploy,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryd/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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-eslint": "0.0.0",
54
- "@kryd/config-ts": "0.0.0"
53
+ "@kryd/config-ts": "0.0.0",
54
+ "@kryd/config-eslint": "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",