@kryd/cli 0.17.0 → 0.18.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.
- package/README.md +19 -0
- package/dist/index.js +356 -112
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@ kryd logs <project> --worker # tail the workflow worker's stdout/stderr
|
|
|
26
26
|
|
|
27
27
|
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).
|
|
28
28
|
|
|
29
|
+
`kryd init` and `kryd workflow init` also write a short **Kryd block into `AGENTS.md`** — the file Codex, Cursor, Copilot and Claude Code read — so a coding agent knows the rules before it breaks one: listen on `$PORT`, which env vars Kryd injects, keep the AI gateway token on the server, make workflow tasks safe to run twice, and the CLI's commands (generated from the CLI itself). It sits between `<!-- kryd:begin … -->` and `<!-- kryd:end -->`; a later run replaces only what is between those markers and leaves the rest of your file alone. After updating the CLI, `kryd init --agents` refreshes the block in a linked project (no login needed) — also the way to add it to a project linked before the block existed. If the folder has a `CLAUDE.md`, one `@AGENTS.md` line is added to it so Claude Code reads the block too.
|
|
30
|
+
|
|
29
31
|
## Commands
|
|
30
32
|
|
|
31
33
|
| Command | What it does |
|
|
@@ -33,6 +35,7 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
|
|
|
33
35
|
| `kryd login` / `logout` / `whoami` | Authenticate the CLI (browser flow); the token is stored in `~/.kryd`. `whoami` shows the account commands run in, your role there, and your other accounts. |
|
|
34
36
|
| `kryd account list \| use <slug>` | You can belong to several accounts (your own, a customer's as a member). `list` shows them with your role in each, `*` marking the current one; `use` makes every following command run in that account. |
|
|
35
37
|
| `kryd init` | Link the current repo to a Kryd project + register the deploy webhook + add a `kryd` git remote. Not logged in and at a terminal? It asks, then runs the same browser login as `kryd login`; without a terminal it refuses (a deploy token, `KRYD_TOKEN`, cannot create a project). |
|
|
38
|
+
| `kryd init --agents` | Only write or refresh the Kryd block in `AGENTS.md` of the linked project (walks up to the link), plus the `@AGENTS.md` line in a `CLAUDE.md`. No login, no API call. In a linked folder a plain `kryd init` refuses and names this. |
|
|
36
39
|
| `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. |
|
|
37
40
|
| `kryd deploy [project]` | Re-deploy the production-branch HEAD already on the forge — no new commit. |
|
|
38
41
|
| `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). |
|
|
@@ -53,6 +56,22 @@ kryd support new --type bug --subject "Deploy hangs on build" --message "Every d
|
|
|
53
56
|
kryd support show T-1042 --json
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
**`--json` on the read commands.** `kryd project list`, `whoami`, `db status`, `storage status`, `ai status`, `workflow status`, `domain status [host]` and `env list` take `--json` too, with the same contract: exactly one JSON document on stdout; an error is text on stderr with exit 1 and an empty stdout. "Nothing attached" is an answer, not an error (exit 0). The shapes:
|
|
60
|
+
|
|
61
|
+
| Command | `--json` prints |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `kryd project list` | `{ items }`: each project as the API lists it (`id`, `name`, `subdomain`, `framework`, `repoUrl`, `productionBranch`, `previewsPublic`, `status`, `createdAt`, `updatedAt`, …) plus `linked` — `true` for the project this folder is linked to |
|
|
64
|
+
| `kryd whoami` | the `GET /me` answer as is: you, the account commands run in, your role and your `memberships` |
|
|
65
|
+
| `kryd db status` | `{ production, preview }`, each `{ attached, status, failureReason }`. When production is not attached the preview is not read, and is reported as not attached |
|
|
66
|
+
| `kryd storage status` | `{ attached, status, failureReason }` |
|
|
67
|
+
| `kryd ai status` | `{ enabled, url }` |
|
|
68
|
+
| `kryd workflow status` | `{ enabled, tokenExpiresAt, tokenExpiresInDays }` — both `null` without an expiry; the days are negative once the token has expired |
|
|
69
|
+
| `kryd domain status` | `{ projectHost, domains }`; each domain is `{ id, host, app?, redirectTo?, status, detail, checkedAt, createdAt, records }`, with `status` the raw value (`pending`, `verified`, `active`, `failed`), not the text-mode wording |
|
|
70
|
+
| `kryd domain status <host>` | that one domain, the same fields, its DNS `records` included |
|
|
71
|
+
| `kryd env list` | `{ items }`, each `{ key, source, scope, updatedAt, environments? }` — names and origin, never values |
|
|
72
|
+
|
|
73
|
+
`kryd token list --json` and `kryd account list --json` print a bare array.
|
|
74
|
+
|
|
56
75
|
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
76
|
|
|
58
77
|
```sh
|
package/dist/index.js
CHANGED
|
@@ -14989,14 +14989,14 @@ function isTerminalResourceStatus(status) {
|
|
|
14989
14989
|
|
|
14990
14990
|
// src/index.ts
|
|
14991
14991
|
import {
|
|
14992
|
-
existsSync as
|
|
14992
|
+
existsSync as existsSync4,
|
|
14993
14993
|
mkdirSync as mkdirSync2,
|
|
14994
|
-
readFileSync as
|
|
14995
|
-
realpathSync,
|
|
14994
|
+
readFileSync as readFileSync4,
|
|
14995
|
+
realpathSync as realpathSync2,
|
|
14996
14996
|
rmSync as rmSync2,
|
|
14997
|
-
writeFileSync as
|
|
14997
|
+
writeFileSync as writeFileSync3
|
|
14998
14998
|
} from "node:fs";
|
|
14999
|
-
import { basename as basename2, dirname as dirname2, isAbsolute, join as
|
|
14999
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join4, relative, resolve as resolve2 } from "node:path";
|
|
15000
15000
|
import { pathToFileURL } from "node:url";
|
|
15001
15001
|
|
|
15002
15002
|
// src/config.ts
|
|
@@ -16796,9 +16796,125 @@ function parseCredentialInput(raw) {
|
|
|
16796
16796
|
return out;
|
|
16797
16797
|
}
|
|
16798
16798
|
|
|
16799
|
+
// src/agents-md.ts
|
|
16800
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
16801
|
+
import { join as join2 } from "node:path";
|
|
16802
|
+
var AGENTS_FILE = "AGENTS.md";
|
|
16803
|
+
var CLAUDE_FILE = "CLAUDE.md";
|
|
16804
|
+
var BEGIN_MARKER = "<!-- kryd:begin \u2014 written by the kryd CLI; edits inside are replaced on the next run -->";
|
|
16805
|
+
var END_MARKER = "<!-- kryd:end -->";
|
|
16806
|
+
var BEGIN_LINE = /^<!-- kryd:begin/gm;
|
|
16807
|
+
var END_LINE = /^<!-- kryd:end/gm;
|
|
16808
|
+
var AGENTS_PROSE = `## Deploying on Kryd
|
|
16809
|
+
|
|
16810
|
+
This project deploys to Kryd, a European cloud platform. Use what it already provides instead of adding another service:
|
|
16811
|
+
|
|
16812
|
+
- **Git is the deploy.** The repository lives on Kryd's own git server in the EU (remote \`kryd\`). \`git push kryd <branch>\` or \`kryd push\` builds and deploys; every other branch gets its own preview URL. Don't add CI deploy pipelines, hosting Dockerfiles or another platform's config.
|
|
16813
|
+
- **Database:** managed Postgres \u2014 \`kryd db add\`, then use \`DATABASE_URL\`. Preview deploys can get a database of their own: \`kryd db add --preview\`. Don't add a database container or SQLite for production data.
|
|
16814
|
+
- **File storage:** S3-compatible object storage \u2014 \`kryd storage add\`.
|
|
16815
|
+
- **AI:** an EU-hosted, OpenAI-compatible gateway with several models \u2014 \`kryd ai add\`. Don't add provider API keys.
|
|
16816
|
+
- **Long-running or background work** \u2014 jobs, retries, schedules \u2014 belongs in a workflow (\`kryd workflow init\`), not in a request handler, a timer loop or a separate queue.
|
|
16817
|
+
- **Incoming webhooks** (Stripe, GitHub, \u2026) go to a workflow webhook, created in the Kryd dashboard under Workflows, so every delivery is stored and retried instead of handled inside a web request.
|
|
16818
|
+
- **Your own domain:** \`kryd domain add <host>\` prints the DNS records to create; the HTTPS certificate is automatic.
|
|
16819
|
+
- Everything runs in the EU; keep data and model calls there.
|
|
16820
|
+
|
|
16821
|
+
### Rules
|
|
16822
|
+
|
|
16823
|
+
- **Listen on \`$PORT\`.** The platform sets it; never hardcode a port. An app that listens anywhere else fails the health check, and its deploy never goes live.
|
|
16824
|
+
- **The project link** is \`.kryd/project.json\` (per developer, gitignored). Commands find it by walking up to the git root, so they need no project id inside this repository.
|
|
16825
|
+
- **Injected env vars \u2014 never set these yourself:** \`DATABASE_URL\`; \`AI_GATEWAY_URL\` (already ends in \`/v1\`) and \`AI_GATEWAY_TOKEN\`; \`HATCHET_CLIENT_TOKEN\`; and for storage \`AWS_ACCESS_KEY_ID\`, \`AWS_SECRET_ACCESS_KEY\`, \`AWS_REGION\`, \`AWS_ENDPOINT_URL_S3\`, \`BUCKET_NAME\`. Kryd injects each one once its resource is added (\`kryd db add\`, \`kryd ai add\`, \`kryd workflow add\`, \`kryd storage add\`).
|
|
16826
|
+
- **Call the AI gateway from server code only.** \`AI_GATEWAY_TOKEN\` must never reach a browser.
|
|
16827
|
+
- **Workflows:** \`kryd workflow init --language typescript|python|go\` scaffolds a worker, declared in \`kryd.json\` (\`"web": false\` = the project is a worker only). Make every task safe to run twice: a worker stops within seconds on SIGTERM, and the engine re-runs the task.
|
|
16828
|
+
- **Login:** without a terminal there is no login \u2014 ask the user to run \`kryd init\` or \`kryd login\`. A deploy token (\`KRYD_TOKEN\`) can deploy but cannot create a project.
|
|
16829
|
+
- **Destructive commands** ask for confirmation; without a terminal they need \`--yes\`.
|
|
16830
|
+
- **Read commands take \`--json\`** for one JSON document on stdout \u2014 \`kryd project list --json\`, \`kryd whoami --json\`, \`kryd db status --json\`, \`kryd storage status --json\`, \`kryd ai status --json\`, \`kryd workflow status --json\`, \`kryd domain status --json\`, \`kryd env list --json\`. "Nothing attached" is an answer (exit 0); an error is text on stderr with exit 1 and an empty stdout.
|
|
16831
|
+
`;
|
|
16832
|
+
var AGENTS_FOOTER = "Run `kryd <command> --help` for details. After updating the CLI, refresh this block with `kryd init --agents`.\n";
|
|
16833
|
+
function isHidden(cmd) {
|
|
16834
|
+
return cmd._hidden === true;
|
|
16835
|
+
}
|
|
16836
|
+
function leafCommands(program2) {
|
|
16837
|
+
const out = [];
|
|
16838
|
+
const walk = (cmd, prefix) => {
|
|
16839
|
+
if (isHidden(cmd)) return;
|
|
16840
|
+
const path = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
|
|
16841
|
+
if (cmd.commands.length > 0) {
|
|
16842
|
+
for (const child of cmd.commands) walk(child, path);
|
|
16843
|
+
return;
|
|
16844
|
+
}
|
|
16845
|
+
const args = cmd.registeredArguments.map((a) => {
|
|
16846
|
+
const inner = `${a.name()}${a.variadic ? "..." : ""}`;
|
|
16847
|
+
return a.required ? `<${inner}>` : `[${inner}]`;
|
|
16848
|
+
});
|
|
16849
|
+
out.push({ path, usage: [path, ...args].join(" "), description: cmd.description() });
|
|
16850
|
+
};
|
|
16851
|
+
for (const child of program2.commands) walk(child, "");
|
|
16852
|
+
return out;
|
|
16853
|
+
}
|
|
16854
|
+
function renderAgentsBlock(program2) {
|
|
16855
|
+
const commands = leafCommands(program2).map((c) => `- \`kryd ${c.usage}\` \u2014 ${c.description}
|
|
16856
|
+
`).join("");
|
|
16857
|
+
return `${BEGIN_MARKER}
|
|
16858
|
+
` + AGENTS_PROSE + "\n### Commands\n\n" + commands + "\n" + AGENTS_FOOTER + `${END_MARKER}
|
|
16859
|
+
`;
|
|
16860
|
+
}
|
|
16861
|
+
function lineStartsMatching(text, re) {
|
|
16862
|
+
return [...text.matchAll(re)].map((m) => m.index);
|
|
16863
|
+
}
|
|
16864
|
+
function writeAgentsBlock(dir, block) {
|
|
16865
|
+
const path = join2(dir, AGENTS_FILE);
|
|
16866
|
+
if (!existsSync2(path)) {
|
|
16867
|
+
writeFileSync2(path, block);
|
|
16868
|
+
return "created";
|
|
16869
|
+
}
|
|
16870
|
+
const text = readFileSync2(path, "utf8");
|
|
16871
|
+
const begins = lineStartsMatching(text, BEGIN_LINE);
|
|
16872
|
+
const ends = lineStartsMatching(text, END_LINE);
|
|
16873
|
+
let next;
|
|
16874
|
+
if (begins.length === 0 && ends.length === 0) {
|
|
16875
|
+
let base = text;
|
|
16876
|
+
if (base.length > 0) {
|
|
16877
|
+
if (!base.endsWith("\n")) base += "\n";
|
|
16878
|
+
if (!base.endsWith("\n\n")) base += "\n";
|
|
16879
|
+
}
|
|
16880
|
+
next = base + block;
|
|
16881
|
+
} else {
|
|
16882
|
+
const [begin] = begins;
|
|
16883
|
+
const [end] = ends;
|
|
16884
|
+
if (begins.length !== 1 || ends.length !== 1 || begin === void 0 || end === void 0 || end < begin || // An end marker that is not exactly ours (say `<!-- kryd:end` without its `-->`) would make
|
|
16885
|
+
// the slice run on into the customer's text.
|
|
16886
|
+
!text.startsWith(END_MARKER, end)) {
|
|
16887
|
+
throw new Error(
|
|
16888
|
+
`${AGENTS_FILE} has broken kryd markers (a begin without an end, or one of them twice) \u2014 left it untouched; fix or remove the markers and run this again`
|
|
16889
|
+
);
|
|
16890
|
+
}
|
|
16891
|
+
const endOfMarker = end + END_MARKER.length;
|
|
16892
|
+
next = text.slice(0, begin) + block.replace(/\n$/, "") + text.slice(endOfMarker);
|
|
16893
|
+
}
|
|
16894
|
+
if (next === text) return "unchanged";
|
|
16895
|
+
writeFileSync2(path, next);
|
|
16896
|
+
return "updated";
|
|
16897
|
+
}
|
|
16898
|
+
function ensureClaudeImport(dir) {
|
|
16899
|
+
const path = join2(dir, CLAUDE_FILE);
|
|
16900
|
+
if (!existsSync2(path)) return false;
|
|
16901
|
+
const agents = join2(dir, AGENTS_FILE);
|
|
16902
|
+
if (existsSync2(agents) && realpathSync(path) === realpathSync(agents)) return false;
|
|
16903
|
+
const text = readFileSync2(path, "utf8");
|
|
16904
|
+
if (/^[ \t]*@(\.\/)?AGENTS\.md[ \t]*\r?$/m.test(text)) return false;
|
|
16905
|
+
let base = text;
|
|
16906
|
+
if (base.length > 0) {
|
|
16907
|
+
if (!base.endsWith("\n")) base += "\n";
|
|
16908
|
+
if (!base.endsWith("\n\n")) base += "\n";
|
|
16909
|
+
}
|
|
16910
|
+
writeFileSync2(path, `${base}@AGENTS.md
|
|
16911
|
+
`);
|
|
16912
|
+
return true;
|
|
16913
|
+
}
|
|
16914
|
+
|
|
16799
16915
|
// src/framework.ts
|
|
16800
|
-
import { existsSync as
|
|
16801
|
-
import { basename, join as
|
|
16916
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
16917
|
+
import { basename, join as join3 } from "node:path";
|
|
16802
16918
|
var FRAMEWORKS = [
|
|
16803
16919
|
"react-router",
|
|
16804
16920
|
"nextjs",
|
|
@@ -16853,7 +16969,7 @@ function startScriptLooksLikePlainNode(start) {
|
|
|
16853
16969
|
}
|
|
16854
16970
|
function readPackageJson(cwd) {
|
|
16855
16971
|
try {
|
|
16856
|
-
return JSON.parse(
|
|
16972
|
+
return JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
|
|
16857
16973
|
} catch {
|
|
16858
16974
|
return null;
|
|
16859
16975
|
}
|
|
@@ -16861,7 +16977,7 @@ function readPackageJson(cwd) {
|
|
|
16861
16977
|
function readDeclaration(cwd) {
|
|
16862
16978
|
let parsed;
|
|
16863
16979
|
try {
|
|
16864
|
-
parsed = JSON.parse(
|
|
16980
|
+
parsed = JSON.parse(readFileSync3(join3(cwd, DECLARATION_FILE), "utf8"));
|
|
16865
16981
|
} catch {
|
|
16866
16982
|
return null;
|
|
16867
16983
|
}
|
|
@@ -16881,7 +16997,7 @@ function frameworkFrom(pkg, cwd) {
|
|
|
16881
16997
|
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
16882
16998
|
const has = (name) => name in deps;
|
|
16883
16999
|
if (has("next")) return "nextjs";
|
|
16884
|
-
if (has("@react-router/dev") ||
|
|
17000
|
+
if (has("@react-router/dev") || existsSync3(join3(cwd, "react-router.config.ts")) || existsSync3(join3(cwd, "react-router.config.js"))) {
|
|
16885
17001
|
return "react-router";
|
|
16886
17002
|
}
|
|
16887
17003
|
if (VITE_META_FRAMEWORKS.some(has)) return null;
|
|
@@ -18032,6 +18148,10 @@ function reportNotLinked(example) {
|
|
|
18032
18148
|
);
|
|
18033
18149
|
process.exitCode = 1;
|
|
18034
18150
|
}
|
|
18151
|
+
function printJson2(value) {
|
|
18152
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
18153
|
+
`);
|
|
18154
|
+
}
|
|
18035
18155
|
async function runLogin(opts) {
|
|
18036
18156
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
18037
18157
|
const pastedToken = opts.token ?? process.env.KRYD_TOKEN;
|
|
@@ -18055,6 +18175,10 @@ async function runWhoami(opts = {}) {
|
|
|
18055
18175
|
const token = resolveToken() ?? "";
|
|
18056
18176
|
try {
|
|
18057
18177
|
const me = await whoami(apiUrl, token);
|
|
18178
|
+
if (opts.json) {
|
|
18179
|
+
printJson2(me);
|
|
18180
|
+
return;
|
|
18181
|
+
}
|
|
18058
18182
|
const current = me.memberships?.find((m) => m.accountId === me.accountId);
|
|
18059
18183
|
const label = current?.slug ?? me.accountSlug ?? me.accountId ?? "?";
|
|
18060
18184
|
process.stdout.write(`${me.email} (account ${label}, role ${current?.role ?? me.role ?? "?"})
|
|
@@ -18082,48 +18206,110 @@ function runLogout() {
|
|
|
18082
18206
|
process.stdout.write("Logged out.\n");
|
|
18083
18207
|
warnIfEnvTokenOverrides();
|
|
18084
18208
|
}
|
|
18085
|
-
|
|
18086
|
-
let
|
|
18087
|
-
const
|
|
18088
|
-
let
|
|
18089
|
-
|
|
18090
|
-
|
|
18091
|
-
|
|
18092
|
-
if (
|
|
18093
|
-
|
|
18094
|
-
|
|
18095
|
-
|
|
18096
|
-
}
|
|
18097
|
-
if (!isInteractive(io)) {
|
|
18098
|
-
process.stderr.write(
|
|
18099
|
-
refusal + // KRYD-385 iteration 1: no KRYD_TOKEN advice — `POST /repos` refuses a deploy token (KRYD-578).
|
|
18100
|
-
"No terminal to log in from: ask the user to run `kryd init` (or `kryd login`) in their own terminal. A deploy token (KRYD_TOKEN) cannot create a project.\n"
|
|
18101
|
-
);
|
|
18102
|
-
process.exitCode = 1;
|
|
18103
|
-
return;
|
|
18209
|
+
function installAgentsBlock(dir) {
|
|
18210
|
+
let note = "";
|
|
18211
|
+
const changed = [];
|
|
18212
|
+
let result;
|
|
18213
|
+
try {
|
|
18214
|
+
result = writeAgentsBlock(dir, renderAgentsBlock(program));
|
|
18215
|
+
if (result !== "unchanged") changed.push(AGENTS_FILE);
|
|
18216
|
+
if (result === "created") {
|
|
18217
|
+
note += "Wrote AGENTS.md \u2014 what coding agents need to know to deploy this project on Kryd.\n";
|
|
18218
|
+
} else if (result === "updated") {
|
|
18219
|
+
note += "Updated the Kryd block in AGENTS.md.\n";
|
|
18104
18220
|
}
|
|
18105
|
-
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18109
|
-
|
|
18221
|
+
} catch (err) {
|
|
18222
|
+
process.stderr.write(
|
|
18223
|
+
`warning: could not write the Kryd block to AGENTS.md (${err instanceof Error ? err.message : String(err)}) \u2014 everything else worked; coding agents just will not see it.
|
|
18224
|
+
`
|
|
18225
|
+
);
|
|
18226
|
+
return { note, block: "failed", changed };
|
|
18227
|
+
}
|
|
18228
|
+
try {
|
|
18229
|
+
if (ensureClaudeImport(dir)) {
|
|
18230
|
+
changed.push(CLAUDE_FILE);
|
|
18231
|
+
note += "Added `@AGENTS.md` to CLAUDE.md, so Claude Code reads that block too.\n";
|
|
18110
18232
|
}
|
|
18111
|
-
|
|
18112
|
-
|
|
18113
|
-
|
|
18114
|
-
|
|
18233
|
+
} catch (err) {
|
|
18234
|
+
process.stderr.write(
|
|
18235
|
+
`warning: could not add \`@AGENTS.md\` to CLAUDE.md (${err instanceof Error ? err.message : String(err)}) \u2014 add that line yourself so Claude Code reads AGENTS.md.
|
|
18236
|
+
`
|
|
18237
|
+
);
|
|
18238
|
+
}
|
|
18239
|
+
return { note, block: result, changed };
|
|
18240
|
+
}
|
|
18241
|
+
async function settleLogin(opts) {
|
|
18242
|
+
let token = resolveToken();
|
|
18243
|
+
if (token) return { token, apiUrl: opts.apiUrl };
|
|
18244
|
+
const refusal = "Not logged in \u2014 run `kryd login` first.\n";
|
|
18245
|
+
const io = opts.io ?? defaultPromptIO();
|
|
18246
|
+
if (!opts.offerLogin) {
|
|
18247
|
+
process.stderr.write(refusal);
|
|
18248
|
+
process.exitCode = 1;
|
|
18249
|
+
return null;
|
|
18250
|
+
}
|
|
18251
|
+
if (!isInteractive(io)) {
|
|
18252
|
+
process.stderr.write(
|
|
18253
|
+
refusal + // KRYD-385 iteration 1: no KRYD_TOKEN advice — `POST /repos` refuses a deploy token (KRYD-578).
|
|
18254
|
+
`No terminal to log in from: ask the user to run \`${opts.command}\` (or \`kryd login\`) in their own terminal. A deploy token (KRYD_TOKEN) cannot create a project.
|
|
18255
|
+
`
|
|
18256
|
+
);
|
|
18257
|
+
process.exitCode = 1;
|
|
18258
|
+
return null;
|
|
18259
|
+
}
|
|
18260
|
+
io.output.write(`You are not logged in to Kryd; \`${opts.command}\` can log you in through your browser first.
|
|
18115
18261
|
`);
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
if (!token) {
|
|
18121
|
-
process.stderr.write(refusal);
|
|
18122
|
-
process.exitCode = 1;
|
|
18123
|
-
return;
|
|
18124
|
-
}
|
|
18125
|
-
if (!opts.apiUrl) apiUrl = resolveApiUrl();
|
|
18262
|
+
if (!await promptConfirm("Log in now in your browser?", io)) {
|
|
18263
|
+
process.stderr.write(refusal);
|
|
18264
|
+
process.exitCode = 1;
|
|
18265
|
+
return null;
|
|
18126
18266
|
}
|
|
18267
|
+
try {
|
|
18268
|
+
await browserLoginAndSave(opts.apiUrl);
|
|
18269
|
+
} catch (err) {
|
|
18270
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
18271
|
+
`);
|
|
18272
|
+
process.exitCode = 1;
|
|
18273
|
+
return null;
|
|
18274
|
+
}
|
|
18275
|
+
token = resolveToken();
|
|
18276
|
+
if (!token) {
|
|
18277
|
+
process.stderr.write(refusal);
|
|
18278
|
+
process.exitCode = 1;
|
|
18279
|
+
return null;
|
|
18280
|
+
}
|
|
18281
|
+
return { token, apiUrl: opts.apiUrlFlag ? opts.apiUrl : resolveApiUrl() };
|
|
18282
|
+
}
|
|
18283
|
+
function runInitAgents(cwd) {
|
|
18284
|
+
const dir = findProjectLinkDir(cwd);
|
|
18285
|
+
if (!dir) {
|
|
18286
|
+
process.stderr.write(
|
|
18287
|
+
"This folder is not linked to a Kryd project (no .kryd/project.json up to the git root), so there is no block to refresh.\nRun `kryd init` to link it \u2014 that writes the Kryd block into AGENTS.md as well.\n"
|
|
18288
|
+
);
|
|
18289
|
+
process.exitCode = 1;
|
|
18290
|
+
return;
|
|
18291
|
+
}
|
|
18292
|
+
const { note, block } = installAgentsBlock(dir);
|
|
18293
|
+
process.stdout.write(block === "unchanged" ? `${note}The Kryd block in AGENTS.md is up to date.
|
|
18294
|
+
` : note);
|
|
18295
|
+
}
|
|
18296
|
+
async function runInit(opts) {
|
|
18297
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
18298
|
+
if (opts.agents) {
|
|
18299
|
+
runInitAgents(cwd);
|
|
18300
|
+
return;
|
|
18301
|
+
}
|
|
18302
|
+
let apiUrl = resolveApiUrl(opts.apiUrl);
|
|
18303
|
+
const session = await settleLogin({
|
|
18304
|
+
apiUrl,
|
|
18305
|
+
...opts.apiUrl ? { apiUrlFlag: opts.apiUrl } : {},
|
|
18306
|
+
...opts.io ? { io: opts.io } : {},
|
|
18307
|
+
offerLogin: opts.offerLogin !== false,
|
|
18308
|
+
command: "kryd init"
|
|
18309
|
+
});
|
|
18310
|
+
if (!session) return;
|
|
18311
|
+
const { token } = session;
|
|
18312
|
+
apiUrl = session.apiUrl;
|
|
18127
18313
|
const existingLink = directoryIsLinked(cwd);
|
|
18128
18314
|
if (existingLink) {
|
|
18129
18315
|
const remote = hasRemote(cwd, "kryd");
|
|
@@ -18133,6 +18319,7 @@ async function runInit(opts) {
|
|
|
18133
18319
|
`This folder is already linked to ${existingLink.projectId} (.kryd/project.json).
|
|
18134
18320
|
Running \`kryd init\` again would create a SECOND project and leave that one orphaned.
|
|
18135
18321
|
To deploy it: \`kryd push\`. To start over: \`kryd project rm\` first, or delete .kryd/project.json to unlink this folder.
|
|
18322
|
+
To write or refresh the Kryd block in AGENTS.md: \`kryd init --agents\`.
|
|
18136
18323
|
`
|
|
18137
18324
|
);
|
|
18138
18325
|
process.exitCode = 1;
|
|
@@ -18182,10 +18369,10 @@ To deploy it: \`kryd push\`. To start over: \`kryd project rm\` first, or delete
|
|
|
18182
18369
|
try {
|
|
18183
18370
|
saveProjectLink(cwd, { projectId: project2.id });
|
|
18184
18371
|
try {
|
|
18185
|
-
const
|
|
18372
|
+
const session2 = await whoami(apiUrl, token);
|
|
18186
18373
|
rememberLinkAccount(cwd, {
|
|
18187
|
-
accountId:
|
|
18188
|
-
accountSlug:
|
|
18374
|
+
accountId: session2.accountId,
|
|
18375
|
+
accountSlug: session2.accountSlug
|
|
18189
18376
|
});
|
|
18190
18377
|
} catch {
|
|
18191
18378
|
}
|
|
@@ -18244,9 +18431,10 @@ Scheduled and triggered work runs on your production deploy; preview branches bu
|
|
|
18244
18431
|
Preview branches deploy to https://<branch>-<hash>-${project2.subdomain}.${PREVIEW_TLD}
|
|
18245
18432
|
` + (declaration?.workflowsPath ? `Workflows in ${declaration.workflowsPath}/ run as a worker beside it on production deploys; previews run the web app only.
|
|
18246
18433
|
` : "");
|
|
18434
|
+
const agentsNote = installAgentsBlock(cwd).note;
|
|
18247
18435
|
process.stdout.write(
|
|
18248
18436
|
`Linked "${name}" (${project2.framework}) \u2192 ${repo.htmlUrl}
|
|
18249
|
-
` + routing + `${linkNote}
|
|
18437
|
+
` + routing + `${linkNote}` + agentsNote + `
|
|
18250
18438
|
${nextStep}`
|
|
18251
18439
|
);
|
|
18252
18440
|
} catch (err) {
|
|
@@ -19265,26 +19453,34 @@ AI_GATEWAY_URL and AI_GATEWAY_TOKEN stop being injected from your next deploy (k
|
|
|
19265
19453
|
await reportError(err);
|
|
19266
19454
|
}
|
|
19267
19455
|
}
|
|
19268
|
-
async function
|
|
19456
|
+
async function readResourceStatus(opts) {
|
|
19269
19457
|
try {
|
|
19270
|
-
|
|
19271
|
-
if (result.status === "failed") {
|
|
19272
|
-
process.stdout.write(
|
|
19273
|
-
`${opts.project}: ${opts.noun} failed \u2014 ${result.failureReason ?? "unknown error"}.
|
|
19274
|
-
Remove it before attaching another.
|
|
19275
|
-
`
|
|
19276
|
-
);
|
|
19277
|
-
return;
|
|
19278
|
-
}
|
|
19279
|
-
process.stdout.write(`${opts.project}: ${opts.noun} ${result.status}.
|
|
19280
|
-
`);
|
|
19458
|
+
return toResourceState(await opts.read());
|
|
19281
19459
|
} catch (err) {
|
|
19282
19460
|
if (err instanceof ApiError && err.status === 404 && await projectIsVisible(opts.token, opts.project) === true) {
|
|
19283
|
-
|
|
19284
|
-
`);
|
|
19285
|
-
return;
|
|
19461
|
+
return NOT_ATTACHED;
|
|
19286
19462
|
}
|
|
19287
19463
|
await reportError(err);
|
|
19464
|
+
return null;
|
|
19465
|
+
}
|
|
19466
|
+
}
|
|
19467
|
+
var NOT_ATTACHED = { attached: false, status: null, failureReason: null };
|
|
19468
|
+
function toResourceState(row) {
|
|
19469
|
+
return { attached: true, status: row.status, failureReason: row.failureReason ?? null };
|
|
19470
|
+
}
|
|
19471
|
+
function printResourceStatus(state, noun, project2) {
|
|
19472
|
+
if (!state.attached) {
|
|
19473
|
+
process.stdout.write(`${project2}: no ${noun} attached.
|
|
19474
|
+
`);
|
|
19475
|
+
} else if (state.status === "failed") {
|
|
19476
|
+
process.stdout.write(
|
|
19477
|
+
`${project2}: ${noun} failed \u2014 ${state.failureReason ?? "unknown error"}.
|
|
19478
|
+
Remove it before attaching another.
|
|
19479
|
+
`
|
|
19480
|
+
);
|
|
19481
|
+
} else {
|
|
19482
|
+
process.stdout.write(`${project2}: ${noun} ${state.status}.
|
|
19483
|
+
`);
|
|
19288
19484
|
}
|
|
19289
19485
|
}
|
|
19290
19486
|
async function runDbStatus(opts) {
|
|
@@ -19300,21 +19496,22 @@ async function runDbStatus(opts) {
|
|
|
19300
19496
|
reportNotLinked("kryd db status <projectId>");
|
|
19301
19497
|
return;
|
|
19302
19498
|
}
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
read: async () => {
|
|
19306
|
-
const row = await getDatabaseStatus(apiUrl, token, project2);
|
|
19307
|
-
productionAttached = true;
|
|
19308
|
-
return row;
|
|
19309
|
-
},
|
|
19310
|
-
noun: "database",
|
|
19499
|
+
const production = await readResourceStatus({
|
|
19500
|
+
read: () => getDatabaseStatus(apiUrl, token, project2),
|
|
19311
19501
|
project: project2,
|
|
19312
19502
|
token
|
|
19313
19503
|
});
|
|
19314
|
-
if (!
|
|
19504
|
+
if (!production) return;
|
|
19505
|
+
if (!opts.json) printResourceStatus(production, "database", project2);
|
|
19506
|
+
if (!production.attached) {
|
|
19507
|
+
if (opts.json) printJson2({ production, preview: NOT_ATTACHED });
|
|
19508
|
+
return;
|
|
19509
|
+
}
|
|
19315
19510
|
try {
|
|
19316
19511
|
const preview = await readPreviewDatabase(apiUrl, token, project2);
|
|
19317
|
-
if (
|
|
19512
|
+
if (opts.json) {
|
|
19513
|
+
printJson2({ production, preview: preview ? toResourceState(preview) : NOT_ATTACHED });
|
|
19514
|
+
} else if (!preview) {
|
|
19318
19515
|
process.stdout.write(
|
|
19319
19516
|
`${project2}: no preview database \u2014 preview deploys use the production database.
|
|
19320
19517
|
`
|
|
@@ -19353,12 +19550,14 @@ async function runStorageStatus(opts) {
|
|
|
19353
19550
|
reportNotLinked("kryd storage status <projectId>");
|
|
19354
19551
|
return;
|
|
19355
19552
|
}
|
|
19356
|
-
await
|
|
19553
|
+
const state = await readResourceStatus({
|
|
19357
19554
|
read: () => getStorageStatus(apiUrl, token, project2),
|
|
19358
|
-
noun: "object storage",
|
|
19359
19555
|
project: project2,
|
|
19360
19556
|
token
|
|
19361
19557
|
});
|
|
19558
|
+
if (!state) return;
|
|
19559
|
+
if (opts.json) printJson2(state);
|
|
19560
|
+
else printResourceStatus(state, "object storage", project2);
|
|
19362
19561
|
}
|
|
19363
19562
|
async function runAiStatus(opts) {
|
|
19364
19563
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
@@ -19375,6 +19574,10 @@ async function runAiStatus(opts) {
|
|
|
19375
19574
|
}
|
|
19376
19575
|
try {
|
|
19377
19576
|
const status = await getAiStatus(apiUrl, token, project2);
|
|
19577
|
+
if (opts.json) {
|
|
19578
|
+
printJson2(status);
|
|
19579
|
+
return;
|
|
19580
|
+
}
|
|
19378
19581
|
if (!status.enabled) {
|
|
19379
19582
|
process.stdout.write(`${project2}: no AI endpoint.
|
|
19380
19583
|
`);
|
|
@@ -19489,6 +19692,14 @@ async function runWorkflowStatus(opts) {
|
|
|
19489
19692
|
}
|
|
19490
19693
|
try {
|
|
19491
19694
|
const status = await getWorkflowStatus(apiUrl, token, project2);
|
|
19695
|
+
if (opts.json) {
|
|
19696
|
+
printJson2({
|
|
19697
|
+
enabled: status.enabled,
|
|
19698
|
+
tokenExpiresAt: status.tokenExpiresAt,
|
|
19699
|
+
tokenExpiresInDays: status.tokenExpiresAt ? daysUntil(status.tokenExpiresAt) : null
|
|
19700
|
+
});
|
|
19701
|
+
return;
|
|
19702
|
+
}
|
|
19492
19703
|
if (!status.enabled) {
|
|
19493
19704
|
process.stdout.write(`${project2}: no workflows.
|
|
19494
19705
|
`);
|
|
@@ -19644,6 +19855,10 @@ async function runDomainStatus(opts) {
|
|
|
19644
19855
|
const own = list.projectHost ? `${ctx.project} is always served at ${list.projectHost}.
|
|
19645
19856
|
` : "";
|
|
19646
19857
|
if (!opts.host) {
|
|
19858
|
+
if (opts.json) {
|
|
19859
|
+
printJson2(list);
|
|
19860
|
+
return;
|
|
19861
|
+
}
|
|
19647
19862
|
if (list.domains.length === 0) {
|
|
19648
19863
|
process.stdout.write(`${ctx.project}: no custom domains.
|
|
19649
19864
|
${own}`);
|
|
@@ -19668,6 +19883,10 @@ ${own}`);
|
|
|
19668
19883
|
process.exitCode = 1;
|
|
19669
19884
|
return;
|
|
19670
19885
|
}
|
|
19886
|
+
if (opts.json) {
|
|
19887
|
+
printJson2(d);
|
|
19888
|
+
return;
|
|
19889
|
+
}
|
|
19671
19890
|
let out = `${d.host}: ${DOMAIN_STATUS_TEXT[d.status]}.
|
|
19672
19891
|
`;
|
|
19673
19892
|
if (d.redirectTo) out += ` \u2192 redirects to ${d.redirectTo}
|
|
@@ -19722,7 +19941,7 @@ async function runWorkflowInit(opts) {
|
|
|
19722
19941
|
}
|
|
19723
19942
|
const workerName = workerNameFrom(opts.name ?? basename2(target));
|
|
19724
19943
|
const files = scaffoldFiles(language, workerName);
|
|
19725
|
-
const clashes = files.map((f) => f.path).filter((p) =>
|
|
19944
|
+
const clashes = files.map((f) => f.path).filter((p) => existsSync4(join4(target, p)));
|
|
19726
19945
|
if (clashes.length > 0) {
|
|
19727
19946
|
process.stderr.write(
|
|
19728
19947
|
`Refusing to overwrite: ${clashes.join(", ")}.
|
|
@@ -19732,12 +19951,22 @@ Nothing was written. Run this in an empty directory, or pass --dir <path>.
|
|
|
19732
19951
|
process.exitCode = 1;
|
|
19733
19952
|
return;
|
|
19734
19953
|
}
|
|
19954
|
+
if (opts.link !== false) {
|
|
19955
|
+
const session = await settleLogin({
|
|
19956
|
+
apiUrl: resolveApiUrl(opts.apiUrl),
|
|
19957
|
+
...opts.apiUrl ? { apiUrlFlag: opts.apiUrl } : {},
|
|
19958
|
+
...opts.io ? { io: opts.io } : {},
|
|
19959
|
+
offerLogin: true,
|
|
19960
|
+
command: "kryd workflow init"
|
|
19961
|
+
});
|
|
19962
|
+
if (!session) return;
|
|
19963
|
+
}
|
|
19735
19964
|
const written = [];
|
|
19736
19965
|
try {
|
|
19737
19966
|
for (const file2 of files) {
|
|
19738
|
-
const full =
|
|
19967
|
+
const full = join4(target, file2.path);
|
|
19739
19968
|
mkdirSync2(dirname2(full), { recursive: true });
|
|
19740
|
-
|
|
19969
|
+
writeFileSync3(full, file2.contents);
|
|
19741
19970
|
written.push(full);
|
|
19742
19971
|
}
|
|
19743
19972
|
} catch (err) {
|
|
@@ -19774,6 +20003,7 @@ To add a worker to the app in that repository instead, run \`kryd workflow init\
|
|
|
19774
20003
|
process.exitCode = 1;
|
|
19775
20004
|
return;
|
|
19776
20005
|
}
|
|
20006
|
+
process.stdout.write(installAgentsBlock(target).note);
|
|
19777
20007
|
const next = [];
|
|
19778
20008
|
if (opts.dir) next.push(`cd ${opts.dir}`);
|
|
19779
20009
|
const first = firstCommand(language);
|
|
@@ -19823,7 +20053,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19823
20053
|
"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"
|
|
19824
20054
|
);
|
|
19825
20055
|
process.exitCode = 1;
|
|
19826
|
-
return;
|
|
20056
|
+
return false;
|
|
19827
20057
|
}
|
|
19828
20058
|
const raw = (opts.path ?? DEFAULT_WORKFLOWS_PATH).trim();
|
|
19829
20059
|
const segments = raw.split("/").filter((seg) => seg !== "" && seg !== ".");
|
|
@@ -19833,14 +20063,14 @@ function scaffoldContainedWorker(opts) {
|
|
|
19833
20063
|
`
|
|
19834
20064
|
);
|
|
19835
20065
|
process.exitCode = 1;
|
|
19836
|
-
return;
|
|
20066
|
+
return false;
|
|
19837
20067
|
}
|
|
19838
20068
|
const folder = segments.join("/");
|
|
19839
|
-
const declarationPath =
|
|
20069
|
+
const declarationPath = join4(target, DECLARATION_FILE);
|
|
19840
20070
|
let existing = {};
|
|
19841
20071
|
let existingText = null;
|
|
19842
|
-
if (
|
|
19843
|
-
existingText =
|
|
20072
|
+
if (existsSync4(declarationPath)) {
|
|
20073
|
+
existingText = readFileSync4(declarationPath, "utf8");
|
|
19844
20074
|
let parsed;
|
|
19845
20075
|
try {
|
|
19846
20076
|
parsed = JSON.parse(existingText);
|
|
@@ -19853,7 +20083,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19853
20083
|
`
|
|
19854
20084
|
);
|
|
19855
20085
|
process.exitCode = 1;
|
|
19856
|
-
return;
|
|
20086
|
+
return false;
|
|
19857
20087
|
}
|
|
19858
20088
|
existing = parsed;
|
|
19859
20089
|
if ("kind" in existing) {
|
|
@@ -19862,7 +20092,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19862
20092
|
`
|
|
19863
20093
|
);
|
|
19864
20094
|
process.exitCode = 1;
|
|
19865
|
-
return;
|
|
20095
|
+
return false;
|
|
19866
20096
|
}
|
|
19867
20097
|
if ("workflows" in existing || existing["web"] === false) {
|
|
19868
20098
|
process.stderr.write(
|
|
@@ -19870,7 +20100,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19870
20100
|
`
|
|
19871
20101
|
);
|
|
19872
20102
|
process.exitCode = 1;
|
|
19873
|
-
return;
|
|
20103
|
+
return false;
|
|
19874
20104
|
}
|
|
19875
20105
|
}
|
|
19876
20106
|
const workerName = workerNameFrom(opts.name ?? basename2(target));
|
|
@@ -19878,7 +20108,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19878
20108
|
path: `${folder}/${f.path}`,
|
|
19879
20109
|
contents: f.contents
|
|
19880
20110
|
}));
|
|
19881
|
-
const clashes = files.map((f) => f.path).filter((p) =>
|
|
20111
|
+
const clashes = files.map((f) => f.path).filter((p) => existsSync4(join4(target, p)));
|
|
19882
20112
|
if (clashes.length > 0) {
|
|
19883
20113
|
process.stderr.write(
|
|
19884
20114
|
`Refusing to overwrite: ${clashes.join(", ")}.
|
|
@@ -19886,18 +20116,18 @@ Nothing was written. Pick another folder with --path <folder>.
|
|
|
19886
20116
|
`
|
|
19887
20117
|
);
|
|
19888
20118
|
process.exitCode = 1;
|
|
19889
|
-
return;
|
|
20119
|
+
return false;
|
|
19890
20120
|
}
|
|
19891
20121
|
const declaration = { ...existing, workflows: { path: folder } };
|
|
19892
20122
|
const written = [];
|
|
19893
20123
|
try {
|
|
19894
20124
|
for (const file2 of files) {
|
|
19895
|
-
const full =
|
|
20125
|
+
const full = join4(target, file2.path);
|
|
19896
20126
|
mkdirSync2(dirname2(full), { recursive: true });
|
|
19897
|
-
|
|
20127
|
+
writeFileSync3(full, file2.contents);
|
|
19898
20128
|
written.push(full);
|
|
19899
20129
|
}
|
|
19900
|
-
|
|
20130
|
+
writeFileSync3(declarationPath, `${JSON.stringify(declaration, null, 2)}
|
|
19901
20131
|
`);
|
|
19902
20132
|
} catch (err) {
|
|
19903
20133
|
for (const path of written.reverse()) {
|
|
@@ -19908,7 +20138,7 @@ Nothing was written. Pick another folder with --path <folder>.
|
|
|
19908
20138
|
}
|
|
19909
20139
|
try {
|
|
19910
20140
|
if (existingText === null) rmSync2(declarationPath, { force: true });
|
|
19911
|
-
else
|
|
20141
|
+
else writeFileSync3(declarationPath, existingText);
|
|
19912
20142
|
} catch {
|
|
19913
20143
|
}
|
|
19914
20144
|
process.stderr.write(
|
|
@@ -19917,14 +20147,15 @@ Nothing was left behind.
|
|
|
19917
20147
|
`
|
|
19918
20148
|
);
|
|
19919
20149
|
process.exitCode = 1;
|
|
19920
|
-
return;
|
|
20150
|
+
return false;
|
|
19921
20151
|
}
|
|
20152
|
+
const agents = installAgentsBlock(target);
|
|
19922
20153
|
const next = [];
|
|
19923
20154
|
const first = firstCommand(language);
|
|
19924
20155
|
if (first) next.push(`(cd ${folder} && ${first})`);
|
|
19925
20156
|
next.push(
|
|
19926
20157
|
"kryd workflow add",
|
|
19927
|
-
`git add ${DECLARATION_FILE
|
|
20158
|
+
`git add ${[DECLARATION_FILE, folder, ...agents.changed].join(" ")} && git commit -m "add workflows"`,
|
|
19928
20159
|
"kryd push"
|
|
19929
20160
|
);
|
|
19930
20161
|
process.stdout.write(
|
|
@@ -19937,9 +20168,10 @@ Next: ${next.map((c) => `\`${c}\``).join(", then ")}.
|
|
|
19937
20168
|
HATCHET_CLIENT_TOKEN is injected into your app and your worker on production deploys (previews get none) \u2014 your app triggers tasks with it; it is never available locally.
|
|
19938
20169
|
` + // The app is still built (and type-checked) from the root, so a tsconfig that includes every
|
|
19939
20170
|
// file would compile the worker too — against an SDK the app does not depend on.
|
|
19940
|
-
(
|
|
19941
|
-
` : "")
|
|
20171
|
+
(existsSync4(join4(target, "tsconfig.json")) ? `Add "${folder}" to the "exclude" of your tsconfig.json, so your app's build does not type-check the worker.
|
|
20172
|
+
` : "") + agents.note
|
|
19942
20173
|
);
|
|
20174
|
+
return true;
|
|
19943
20175
|
}
|
|
19944
20176
|
async function runStorageRemove(opts) {
|
|
19945
20177
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
@@ -20019,6 +20251,11 @@ async function runProjectList(opts) {
|
|
|
20019
20251
|
await reportError(err);
|
|
20020
20252
|
return;
|
|
20021
20253
|
}
|
|
20254
|
+
if (opts.json) {
|
|
20255
|
+
const linked = resolveProjectId(void 0, opts.cwd);
|
|
20256
|
+
printJson2({ items: projects.map((p) => ({ ...p, linked: p.id === linked })) });
|
|
20257
|
+
return;
|
|
20258
|
+
}
|
|
20022
20259
|
if (projects.length === 0) {
|
|
20023
20260
|
process.stdout.write("No projects yet \u2014 run `kryd init` in a project directory.\n");
|
|
20024
20261
|
return;
|
|
@@ -20070,6 +20307,10 @@ async function runEnvList(opts) {
|
|
|
20070
20307
|
}
|
|
20071
20308
|
try {
|
|
20072
20309
|
const vars = await listEnvVars(apiUrl, token, project2);
|
|
20310
|
+
if (opts.json) {
|
|
20311
|
+
printJson2({ items: vars });
|
|
20312
|
+
return;
|
|
20313
|
+
}
|
|
20073
20314
|
if (vars.length === 0) {
|
|
20074
20315
|
process.stdout.write(
|
|
20075
20316
|
`No environment variables for ${project2} yet.
|
|
@@ -20155,7 +20396,7 @@ async function runEnvPull(opts) {
|
|
|
20155
20396
|
const root = findProjectLinkDir(cwd) ?? cwd;
|
|
20156
20397
|
const outRel = opts.out ?? ".env.kryd";
|
|
20157
20398
|
const outPath = resolve2(root, outRel);
|
|
20158
|
-
if (opts.out !== void 0 &&
|
|
20399
|
+
if (opts.out !== void 0 && existsSync4(outPath) && !opts.force) {
|
|
20159
20400
|
process.stderr.write(
|
|
20160
20401
|
`Refusing to overwrite ${outRel} \u2014 pass --force to replace it (or omit --out to write .env.kryd).
|
|
20161
20402
|
`
|
|
@@ -20429,7 +20670,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
|
|
|
20429
20670
|
await reportError(err);
|
|
20430
20671
|
}
|
|
20431
20672
|
}
|
|
20432
|
-
var CLI_VERSION = true ? "0.
|
|
20673
|
+
var CLI_VERSION = true ? "0.18.0" : "0.0.0-dev";
|
|
20433
20674
|
var program = new Command();
|
|
20434
20675
|
program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
|
|
20435
20676
|
program.hook("postAction", async () => {
|
|
@@ -20439,7 +20680,7 @@ program.command("login").description("Sign in via the browser (default) and stor
|
|
|
20439
20680
|
"--token <token>",
|
|
20440
20681
|
"supply a token instead of the browser flow (prefer $KRYD_TOKEN \u2014 flags are visible in `ps`)"
|
|
20441
20682
|
).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));
|
|
20442
|
-
program.command("whoami").description("Show the current account").option("--api-url <url>", "control-plane API base URL").action((opts) => runWhoami(opts));
|
|
20683
|
+
program.command("whoami").description("Show the current account").option("--json", "print the user and their accounts as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runWhoami(opts));
|
|
20443
20684
|
var accountCommand = program.command("account").description("The accounts you belong to, and which one commands run in");
|
|
20444
20685
|
accountCommand.command("list").description("List your accounts and your role in each; * marks the one commands run in").option("--json", "print the accounts as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runAccountList(opts));
|
|
20445
20686
|
accountCommand.command("use <slug>").description("Run every following command in this account").option("--api-url <url>", "control-plane API base URL").action((slug, opts) => runAccountUse(slug, opts));
|
|
@@ -20467,7 +20708,10 @@ program.command("init").description(
|
|
|
20467
20708
|
"--public",
|
|
20468
20709
|
// KRYD-184a. Chosen at creation; there is no command to flip it afterwards yet (KRYD-341).
|
|
20469
20710
|
"create the repository PUBLIC \u2014 readable by anyone signed in to the forge (default: private). Set at creation only"
|
|
20470
|
-
).option("--api-url <url>", "control-plane API base URL").
|
|
20711
|
+
).option("--api-url <url>", "control-plane API base URL").option(
|
|
20712
|
+
"--agents",
|
|
20713
|
+
"only write or refresh the Kryd block in AGENTS.md of the linked project (no login, no API call) \u2014 e.g. after updating the CLI"
|
|
20714
|
+
).action((opts) => runInit(opts));
|
|
20471
20715
|
program.command("logs [target]").description(
|
|
20472
20716
|
"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."
|
|
20473
20717
|
).option(
|
|
@@ -20504,7 +20748,7 @@ program.command("redeploy [project]").description(
|
|
|
20504
20748
|
"Redeploy the current live commit (no rebuild) to apply config/env changes, and follow it live"
|
|
20505
20749
|
).option("--app <name>", "a project with several web apps: which app to redeploy").option("--logs", LOGS_FLAG_HELP).option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runRedeploy({ ...opts, project: project2 }));
|
|
20506
20750
|
var project = program.command("project").description("Manage your projects");
|
|
20507
|
-
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));
|
|
20751
|
+
project.command("list").description("List your projects \u2014 name, subdomain, status and id").option("--json", "print { items } as JSON on stdout, each with `linked`").option("--api-url <url>", "control-plane API base URL").action((opts) => runProjectList(opts));
|
|
20508
20752
|
project.command("rm [project]").description(
|
|
20509
20753
|
"Permanently delete a project and everything Kryd created for it (by name or id; asks you to type its name)"
|
|
20510
20754
|
).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 }));
|
|
@@ -20521,13 +20765,13 @@ db.command("add [project]").description("Attach a shared or bring-your-own Postg
|
|
|
20521
20765
|
).option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbAdd({ ...opts, project: project2 }));
|
|
20522
20766
|
db.command("remove [project]").description("Tear down the project's database and everything in it (asks for confirmation)").option("--preview", "remove the preview database; production is not touched").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbRemove({ ...opts, project: project2 }));
|
|
20523
20767
|
db.command("reset [project]").description("Empty the preview database: every table dropped, credentials kept (asks for confirmation)").option("--preview", "required \u2014 only the preview database can be reset").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbReset({ ...opts, project: project2 }));
|
|
20524
|
-
db.command("status [project]").description("Show the project's database and preview database, and what state they are in").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbStatus({ ...opts, project: project2 }));
|
|
20768
|
+
db.command("status [project]").description("Show the project's database and preview database, and what state they are in").option("--json", "print { production, preview } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbStatus({ ...opts, project: project2 }));
|
|
20525
20769
|
var storage = program.command("storage").description("Manage project object storage");
|
|
20526
20770
|
storage.command("add [project]").description("Create an S3-compatible object-storage bucket for a project").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageAdd({ ...opts, project: project2 }));
|
|
20527
20771
|
storage.command("remove [project]").description("Tear down the project's bucket and every object in it (asks for confirmation)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageRemove({ ...opts, project: project2 }));
|
|
20528
|
-
storage.command("status [project]").description("Show whether the project has object storage, and what state it is in").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageStatus({ ...opts, project: project2 }));
|
|
20772
|
+
storage.command("status [project]").description("Show whether the project has object storage, and what state it is in").option("--json", "print { attached, status, failureReason } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageStatus({ ...opts, project: project2 }));
|
|
20529
20773
|
var env = program.command("env").description("Manage your app's environment variables");
|
|
20530
|
-
env.command("list [project]").description("List the project's environment variables (names and origin \u2014 never values)").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runEnvList({ ...opts, project: project2 }));
|
|
20774
|
+
env.command("list [project]").description("List the project's environment variables (names and origin \u2014 never values)").option("--json", "print { items } as JSON on stdout (never values)").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runEnvList({ ...opts, project: project2 }));
|
|
20531
20775
|
env.command("set <key> [project]").description(
|
|
20532
20776
|
"Set one of your environment variables \u2014 `KEY=VALUE`, or `KEY` with --stdin / a prompt"
|
|
20533
20777
|
).option(
|
|
@@ -20559,11 +20803,11 @@ env.command("pull [project]").description(
|
|
|
20559
20803
|
var ai = program.command("ai").description("Manage the app's EU AI gateway");
|
|
20560
20804
|
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 }));
|
|
20561
20805
|
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 }));
|
|
20562
|
-
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 }));
|
|
20806
|
+
ai.command("status [project]").description("Show whether the project has an AI endpoint, and what it is").option("--json", "print { enabled, url } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runAiStatus({ ...opts, project: project2 }));
|
|
20563
20807
|
var workflow = program.command("workflow").description("Manage the project's durable workflows (its own workflow tenant)");
|
|
20564
20808
|
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 }));
|
|
20565
20809
|
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 }));
|
|
20566
|
-
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 }));
|
|
20810
|
+
workflow.command("status [project]").description("Show whether the project has workflows, and when its token expires").option("--json", "print { enabled, tokenExpiresAt, tokenExpiresInDays } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runWorkflowStatus({ ...opts, project: project2 }));
|
|
20567
20811
|
workflow.command("init").description(
|
|
20568
20812
|
"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"
|
|
20569
20813
|
).requiredOption(
|
|
@@ -20576,7 +20820,7 @@ domain2.command("add <host>").description("Add a custom domain and print the DNS
|
|
|
20576
20820
|
"redirect instead of serving: every request answers 301 to https://<host>, path and query kept"
|
|
20577
20821
|
).option("--api-url <url>", "control-plane API base URL").action((host, opts) => runDomainAdd({ ...opts, host }));
|
|
20578
20822
|
domain2.command("remove <host>").description("Remove a custom domain \u2014 its route and certificate go (asks for confirmation)").option("--project <projectId>", "the project (default: the one linked here)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((host, opts) => runDomainRemove({ ...opts, host }));
|
|
20579
|
-
domain2.command("status [host]").description("Show the project's custom domains, or one in detail with what is missing").option("--project <projectId>", "the project (default: the one linked here)").option("--api-url <url>", "control-plane API base URL").action((host, opts) => runDomainStatus({ ...opts, host }));
|
|
20823
|
+
domain2.command("status [host]").description("Show the project's custom domains, or one in detail with what is missing").option("--project <projectId>", "the project (default: the one linked here)").option("--json", "print the domain list, or the one domain, as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((host, opts) => runDomainStatus({ ...opts, host }));
|
|
20580
20824
|
var support = program.command("support").description(
|
|
20581
20825
|
"Ask Kryd support a question, report a bug or a wish, and read the answer \u2014 non-interactive, with --json for agents"
|
|
20582
20826
|
);
|
|
@@ -20594,7 +20838,7 @@ function invokedDirectly() {
|
|
|
20594
20838
|
const entry = process.argv[1];
|
|
20595
20839
|
if (!entry) return false;
|
|
20596
20840
|
try {
|
|
20597
|
-
return import.meta.url === pathToFileURL(
|
|
20841
|
+
return import.meta.url === pathToFileURL(realpathSync2(entry)).href;
|
|
20598
20842
|
} catch {
|
|
20599
20843
|
return false;
|
|
20600
20844
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kryd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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",
|