@kryd/cli 0.17.0 → 0.19.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 +21 -0
- package/dist/index.js +425 -113
- package/package.json +3 -3
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). |
|
|
@@ -41,6 +44,7 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
|
|
|
41
44
|
| `kryd storage add \| remove \| status [project]` | Attach, tear down or inspect S3-compatible object storage. `remove` destroys every object and asks first. |
|
|
42
45
|
| `kryd env list \| set \| rm [project]` | Manage your own environment variables. `set KEY --stdin` (or a prompt) keeps a secret out of `ps` and your shell history; values are never printed back. Add `--build` for build-time variables (`VITE_*`, `NEXT_PUBLIC_*`) — these are compiled into your public bundle, so they must never be secrets, and they take effect at your next build rather than your next deploy. |
|
|
43
46
|
| `kryd ai add \| remove \| status [project]` | Give the project an authenticated EU AI-gateway endpoint (injected on the next deploy), take it away again, or show what it has. |
|
|
47
|
+
| `kryd ai models` | List the AI models Kryd offers right now — the same catalogue the dashboard's AI tab shows, in the same order (popular ones marked `*`). A request's `model` field carries the `MODEL` value. The list is the same for every account, but the call runs in your current account; needs a login, no project. |
|
|
44
48
|
| `kryd workflow add \| remove \| status [project]` | Give the project its own durable-workflow tenant — `HATCHET_CLIENT_TOKEN` is injected on the next deploy — revoke that token again, or show whether it has one and when it expires. `remove` keeps the tenant's history, crons and schedules until the project is deleted. |
|
|
45
49
|
| `kryd workflow init --language <typescript\|python\|go>` | Scaffold a workflow worker here and link it as a new project. The worker ships with the small HTTP health listener Kryd's deploy gate needs — without it a deploy is torn down with "nothing listening yet" — plus a `kryd.json` that tells `kryd init` what this project is. `--no-link` writes the files only. |
|
|
46
50
|
| `kryd token create \| list \| revoke` | Deploy tokens for CI and scripts. `create <name> [--expires 30\|90\|365\|never]` (default 90 days) prints the token once; it acts as you, with your role in the **current account**, is bound to that one account for good, and cannot create tokens itself. Managing tokens needs `kryd login`. A token can do what you can on your projects — read env values and push — so after a leak, revoke it and also rotate the secrets it could read. |
|
|
@@ -53,6 +57,23 @@ kryd support new --type bug --subject "Deploy hangs on build" --message "Every d
|
|
|
53
57
|
kryd support show T-1042 --json
|
|
54
58
|
```
|
|
55
59
|
|
|
60
|
+
**`--json` on the read commands.** `kryd project list`, `whoami`, `db status`, `storage status`, `ai status`, `ai models`, `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:
|
|
61
|
+
|
|
62
|
+
| Command | `--json` prints |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `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 |
|
|
65
|
+
| `kryd whoami` | the `GET /me` answer as is: you, the account commands run in, your role and your `memberships` |
|
|
66
|
+
| `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 |
|
|
67
|
+
| `kryd storage status` | `{ attached, status, failureReason }` |
|
|
68
|
+
| `kryd ai status` | `{ enabled, url }` |
|
|
69
|
+
| `kryd ai models` | `{ items }`: the AI catalogue rows as the API returns them (`id`, `model`, `provider`, `tasks`, `stage`, `popular`, `sortOrder`), in its order. An empty catalogue is `{ "items": [] }` |
|
|
70
|
+
| `kryd workflow status` | `{ enabled, tokenExpiresAt, tokenExpiresInDays }` — both `null` without an expiry; the days are negative once the token has expired |
|
|
71
|
+
| `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 |
|
|
72
|
+
| `kryd domain status <host>` | that one domain, the same fields, its DNS `records` included |
|
|
73
|
+
| `kryd env list` | `{ items }`, each `{ key, source, scope, updatedAt, environments? }` — names and origin, never values |
|
|
74
|
+
|
|
75
|
+
`kryd token list --json` and `kryd account list --json` print a bare array.
|
|
76
|
+
|
|
56
77
|
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
78
|
|
|
58
79
|
```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
|
|
@@ -15364,6 +15364,28 @@ async function getAiStatus(apiUrl, token, projectId) {
|
|
|
15364
15364
|
}
|
|
15365
15365
|
return { enabled: parsed.enabled, url: parsed.url };
|
|
15366
15366
|
}
|
|
15367
|
+
async function getAiModels(apiUrl, token) {
|
|
15368
|
+
const res = await fetch(`${apiUrl}/ai-models`, {
|
|
15369
|
+
headers: { ...authHeaders(token) }
|
|
15370
|
+
});
|
|
15371
|
+
if (!res.ok) {
|
|
15372
|
+
throw new ApiError(
|
|
15373
|
+
`AI catalogue read failed (${res.status})`,
|
|
15374
|
+
await parseEnvelope(res),
|
|
15375
|
+
res.status
|
|
15376
|
+
);
|
|
15377
|
+
}
|
|
15378
|
+
const parsed = await res.json().catch(() => void 0);
|
|
15379
|
+
const isRow = (row) => {
|
|
15380
|
+
if (!row || typeof row !== "object") return false;
|
|
15381
|
+
const r = row;
|
|
15382
|
+
return typeof r.model === "string" && typeof r.provider === "string" && Array.isArray(r.tasks) && r.tasks.every((t) => typeof t === "string") && typeof r.stage === "string" && typeof r.popular === "boolean";
|
|
15383
|
+
};
|
|
15384
|
+
if (!Array.isArray(parsed) || !parsed.every(isRow)) {
|
|
15385
|
+
throw new ApiError("The API returned an unexpected AI-catalogue response.");
|
|
15386
|
+
}
|
|
15387
|
+
return parsed;
|
|
15388
|
+
}
|
|
15367
15389
|
async function removeAi(apiUrl, token, projectId) {
|
|
15368
15390
|
const res = await fetch(`${apiUrl}/ai/${encodeURIComponent(projectId)}`, {
|
|
15369
15391
|
method: "DELETE",
|
|
@@ -16796,9 +16818,125 @@ function parseCredentialInput(raw) {
|
|
|
16796
16818
|
return out;
|
|
16797
16819
|
}
|
|
16798
16820
|
|
|
16821
|
+
// src/agents-md.ts
|
|
16822
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
16823
|
+
import { join as join2 } from "node:path";
|
|
16824
|
+
var AGENTS_FILE = "AGENTS.md";
|
|
16825
|
+
var CLAUDE_FILE = "CLAUDE.md";
|
|
16826
|
+
var BEGIN_MARKER = "<!-- kryd:begin \u2014 written by the kryd CLI; edits inside are replaced on the next run -->";
|
|
16827
|
+
var END_MARKER = "<!-- kryd:end -->";
|
|
16828
|
+
var BEGIN_LINE = /^<!-- kryd:begin/gm;
|
|
16829
|
+
var END_LINE = /^<!-- kryd:end/gm;
|
|
16830
|
+
var AGENTS_PROSE = `## Deploying on Kryd
|
|
16831
|
+
|
|
16832
|
+
This project deploys to Kryd, a European cloud platform. Use what it already provides instead of adding another service:
|
|
16833
|
+
|
|
16834
|
+
- **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.
|
|
16835
|
+
- **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.
|
|
16836
|
+
- **File storage:** S3-compatible object storage \u2014 \`kryd storage add\`.
|
|
16837
|
+
- **AI:** an EU-hosted, OpenAI-compatible gateway with several models \u2014 \`kryd ai add\`; list the current models with \`kryd ai models\`. Don't add provider API keys.
|
|
16838
|
+
- **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.
|
|
16839
|
+
- **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.
|
|
16840
|
+
- **Your own domain:** \`kryd domain add <host>\` prints the DNS records to create; the HTTPS certificate is automatic.
|
|
16841
|
+
- Everything runs in the EU; keep data and model calls there.
|
|
16842
|
+
|
|
16843
|
+
### Rules
|
|
16844
|
+
|
|
16845
|
+
- **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.
|
|
16846
|
+
- **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.
|
|
16847
|
+
- **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\`).
|
|
16848
|
+
- **Call the AI gateway from server code only.** \`AI_GATEWAY_TOKEN\` must never reach a browser.
|
|
16849
|
+
- **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.
|
|
16850
|
+
- **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.
|
|
16851
|
+
- **Destructive commands** ask for confirmation; without a terminal they need \`--yes\`.
|
|
16852
|
+
- **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 ai models --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.
|
|
16853
|
+
`;
|
|
16854
|
+
var AGENTS_FOOTER = "Run `kryd <command> --help` for details. After updating the CLI, refresh this block with `kryd init --agents`.\n";
|
|
16855
|
+
function isHidden(cmd) {
|
|
16856
|
+
return cmd._hidden === true;
|
|
16857
|
+
}
|
|
16858
|
+
function leafCommands(program2) {
|
|
16859
|
+
const out = [];
|
|
16860
|
+
const walk = (cmd, prefix) => {
|
|
16861
|
+
if (isHidden(cmd)) return;
|
|
16862
|
+
const path = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
|
|
16863
|
+
if (cmd.commands.length > 0) {
|
|
16864
|
+
for (const child of cmd.commands) walk(child, path);
|
|
16865
|
+
return;
|
|
16866
|
+
}
|
|
16867
|
+
const args = cmd.registeredArguments.map((a) => {
|
|
16868
|
+
const inner = `${a.name()}${a.variadic ? "..." : ""}`;
|
|
16869
|
+
return a.required ? `<${inner}>` : `[${inner}]`;
|
|
16870
|
+
});
|
|
16871
|
+
out.push({ path, usage: [path, ...args].join(" "), description: cmd.description() });
|
|
16872
|
+
};
|
|
16873
|
+
for (const child of program2.commands) walk(child, "");
|
|
16874
|
+
return out;
|
|
16875
|
+
}
|
|
16876
|
+
function renderAgentsBlock(program2) {
|
|
16877
|
+
const commands = leafCommands(program2).map((c) => `- \`kryd ${c.usage}\` \u2014 ${c.description}
|
|
16878
|
+
`).join("");
|
|
16879
|
+
return `${BEGIN_MARKER}
|
|
16880
|
+
` + AGENTS_PROSE + "\n### Commands\n\n" + commands + "\n" + AGENTS_FOOTER + `${END_MARKER}
|
|
16881
|
+
`;
|
|
16882
|
+
}
|
|
16883
|
+
function lineStartsMatching(text, re) {
|
|
16884
|
+
return [...text.matchAll(re)].map((m) => m.index);
|
|
16885
|
+
}
|
|
16886
|
+
function writeAgentsBlock(dir, block) {
|
|
16887
|
+
const path = join2(dir, AGENTS_FILE);
|
|
16888
|
+
if (!existsSync2(path)) {
|
|
16889
|
+
writeFileSync2(path, block);
|
|
16890
|
+
return "created";
|
|
16891
|
+
}
|
|
16892
|
+
const text = readFileSync2(path, "utf8");
|
|
16893
|
+
const begins = lineStartsMatching(text, BEGIN_LINE);
|
|
16894
|
+
const ends = lineStartsMatching(text, END_LINE);
|
|
16895
|
+
let next;
|
|
16896
|
+
if (begins.length === 0 && ends.length === 0) {
|
|
16897
|
+
let base = text;
|
|
16898
|
+
if (base.length > 0) {
|
|
16899
|
+
if (!base.endsWith("\n")) base += "\n";
|
|
16900
|
+
if (!base.endsWith("\n\n")) base += "\n";
|
|
16901
|
+
}
|
|
16902
|
+
next = base + block;
|
|
16903
|
+
} else {
|
|
16904
|
+
const [begin] = begins;
|
|
16905
|
+
const [end] = ends;
|
|
16906
|
+
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
|
|
16907
|
+
// the slice run on into the customer's text.
|
|
16908
|
+
!text.startsWith(END_MARKER, end)) {
|
|
16909
|
+
throw new Error(
|
|
16910
|
+
`${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`
|
|
16911
|
+
);
|
|
16912
|
+
}
|
|
16913
|
+
const endOfMarker = end + END_MARKER.length;
|
|
16914
|
+
next = text.slice(0, begin) + block.replace(/\n$/, "") + text.slice(endOfMarker);
|
|
16915
|
+
}
|
|
16916
|
+
if (next === text) return "unchanged";
|
|
16917
|
+
writeFileSync2(path, next);
|
|
16918
|
+
return "updated";
|
|
16919
|
+
}
|
|
16920
|
+
function ensureClaudeImport(dir) {
|
|
16921
|
+
const path = join2(dir, CLAUDE_FILE);
|
|
16922
|
+
if (!existsSync2(path)) return false;
|
|
16923
|
+
const agents = join2(dir, AGENTS_FILE);
|
|
16924
|
+
if (existsSync2(agents) && realpathSync(path) === realpathSync(agents)) return false;
|
|
16925
|
+
const text = readFileSync2(path, "utf8");
|
|
16926
|
+
if (/^[ \t]*@(\.\/)?AGENTS\.md[ \t]*\r?$/m.test(text)) return false;
|
|
16927
|
+
let base = text;
|
|
16928
|
+
if (base.length > 0) {
|
|
16929
|
+
if (!base.endsWith("\n")) base += "\n";
|
|
16930
|
+
if (!base.endsWith("\n\n")) base += "\n";
|
|
16931
|
+
}
|
|
16932
|
+
writeFileSync2(path, `${base}@AGENTS.md
|
|
16933
|
+
`);
|
|
16934
|
+
return true;
|
|
16935
|
+
}
|
|
16936
|
+
|
|
16799
16937
|
// src/framework.ts
|
|
16800
|
-
import { existsSync as
|
|
16801
|
-
import { basename, join as
|
|
16938
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
16939
|
+
import { basename, join as join3 } from "node:path";
|
|
16802
16940
|
var FRAMEWORKS = [
|
|
16803
16941
|
"react-router",
|
|
16804
16942
|
"nextjs",
|
|
@@ -16853,7 +16991,7 @@ function startScriptLooksLikePlainNode(start) {
|
|
|
16853
16991
|
}
|
|
16854
16992
|
function readPackageJson(cwd) {
|
|
16855
16993
|
try {
|
|
16856
|
-
return JSON.parse(
|
|
16994
|
+
return JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
|
|
16857
16995
|
} catch {
|
|
16858
16996
|
return null;
|
|
16859
16997
|
}
|
|
@@ -16861,7 +16999,7 @@ function readPackageJson(cwd) {
|
|
|
16861
16999
|
function readDeclaration(cwd) {
|
|
16862
17000
|
let parsed;
|
|
16863
17001
|
try {
|
|
16864
|
-
parsed = JSON.parse(
|
|
17002
|
+
parsed = JSON.parse(readFileSync3(join3(cwd, DECLARATION_FILE), "utf8"));
|
|
16865
17003
|
} catch {
|
|
16866
17004
|
return null;
|
|
16867
17005
|
}
|
|
@@ -16881,7 +17019,7 @@ function frameworkFrom(pkg, cwd) {
|
|
|
16881
17019
|
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
16882
17020
|
const has = (name) => name in deps;
|
|
16883
17021
|
if (has("next")) return "nextjs";
|
|
16884
|
-
if (has("@react-router/dev") ||
|
|
17022
|
+
if (has("@react-router/dev") || existsSync3(join3(cwd, "react-router.config.ts")) || existsSync3(join3(cwd, "react-router.config.js"))) {
|
|
16885
17023
|
return "react-router";
|
|
16886
17024
|
}
|
|
16887
17025
|
if (VITE_META_FRAMEWORKS.some(has)) return null;
|
|
@@ -18032,6 +18170,10 @@ function reportNotLinked(example) {
|
|
|
18032
18170
|
);
|
|
18033
18171
|
process.exitCode = 1;
|
|
18034
18172
|
}
|
|
18173
|
+
function printJson2(value) {
|
|
18174
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
18175
|
+
`);
|
|
18176
|
+
}
|
|
18035
18177
|
async function runLogin(opts) {
|
|
18036
18178
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
18037
18179
|
const pastedToken = opts.token ?? process.env.KRYD_TOKEN;
|
|
@@ -18055,6 +18197,10 @@ async function runWhoami(opts = {}) {
|
|
|
18055
18197
|
const token = resolveToken() ?? "";
|
|
18056
18198
|
try {
|
|
18057
18199
|
const me = await whoami(apiUrl, token);
|
|
18200
|
+
if (opts.json) {
|
|
18201
|
+
printJson2(me);
|
|
18202
|
+
return;
|
|
18203
|
+
}
|
|
18058
18204
|
const current = me.memberships?.find((m) => m.accountId === me.accountId);
|
|
18059
18205
|
const label = current?.slug ?? me.accountSlug ?? me.accountId ?? "?";
|
|
18060
18206
|
process.stdout.write(`${me.email} (account ${label}, role ${current?.role ?? me.role ?? "?"})
|
|
@@ -18082,48 +18228,110 @@ function runLogout() {
|
|
|
18082
18228
|
process.stdout.write("Logged out.\n");
|
|
18083
18229
|
warnIfEnvTokenOverrides();
|
|
18084
18230
|
}
|
|
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;
|
|
18231
|
+
function installAgentsBlock(dir) {
|
|
18232
|
+
let note = "";
|
|
18233
|
+
const changed = [];
|
|
18234
|
+
let result;
|
|
18235
|
+
try {
|
|
18236
|
+
result = writeAgentsBlock(dir, renderAgentsBlock(program));
|
|
18237
|
+
if (result !== "unchanged") changed.push(AGENTS_FILE);
|
|
18238
|
+
if (result === "created") {
|
|
18239
|
+
note += "Wrote AGENTS.md \u2014 what coding agents need to know to deploy this project on Kryd.\n";
|
|
18240
|
+
} else if (result === "updated") {
|
|
18241
|
+
note += "Updated the Kryd block in AGENTS.md.\n";
|
|
18104
18242
|
}
|
|
18105
|
-
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18109
|
-
|
|
18243
|
+
} catch (err) {
|
|
18244
|
+
process.stderr.write(
|
|
18245
|
+
`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.
|
|
18246
|
+
`
|
|
18247
|
+
);
|
|
18248
|
+
return { note, block: "failed", changed };
|
|
18249
|
+
}
|
|
18250
|
+
try {
|
|
18251
|
+
if (ensureClaudeImport(dir)) {
|
|
18252
|
+
changed.push(CLAUDE_FILE);
|
|
18253
|
+
note += "Added `@AGENTS.md` to CLAUDE.md, so Claude Code reads that block too.\n";
|
|
18110
18254
|
}
|
|
18111
|
-
|
|
18112
|
-
|
|
18113
|
-
|
|
18114
|
-
|
|
18255
|
+
} catch (err) {
|
|
18256
|
+
process.stderr.write(
|
|
18257
|
+
`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.
|
|
18258
|
+
`
|
|
18259
|
+
);
|
|
18260
|
+
}
|
|
18261
|
+
return { note, block: result, changed };
|
|
18262
|
+
}
|
|
18263
|
+
async function settleLogin(opts) {
|
|
18264
|
+
let token = resolveToken();
|
|
18265
|
+
if (token) return { token, apiUrl: opts.apiUrl };
|
|
18266
|
+
const refusal = "Not logged in \u2014 run `kryd login` first.\n";
|
|
18267
|
+
const io = opts.io ?? defaultPromptIO();
|
|
18268
|
+
if (!opts.offerLogin) {
|
|
18269
|
+
process.stderr.write(refusal);
|
|
18270
|
+
process.exitCode = 1;
|
|
18271
|
+
return null;
|
|
18272
|
+
}
|
|
18273
|
+
if (!isInteractive(io)) {
|
|
18274
|
+
process.stderr.write(
|
|
18275
|
+
refusal + // KRYD-385 iteration 1: no KRYD_TOKEN advice — `POST /repos` refuses a deploy token (KRYD-578).
|
|
18276
|
+
`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.
|
|
18277
|
+
`
|
|
18278
|
+
);
|
|
18279
|
+
process.exitCode = 1;
|
|
18280
|
+
return null;
|
|
18281
|
+
}
|
|
18282
|
+
io.output.write(`You are not logged in to Kryd; \`${opts.command}\` can log you in through your browser first.
|
|
18115
18283
|
`);
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
18122
|
-
|
|
18123
|
-
|
|
18124
|
-
}
|
|
18125
|
-
|
|
18284
|
+
if (!await promptConfirm("Log in now in your browser?", io)) {
|
|
18285
|
+
process.stderr.write(refusal);
|
|
18286
|
+
process.exitCode = 1;
|
|
18287
|
+
return null;
|
|
18288
|
+
}
|
|
18289
|
+
try {
|
|
18290
|
+
await browserLoginAndSave(opts.apiUrl);
|
|
18291
|
+
} catch (err) {
|
|
18292
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
18293
|
+
`);
|
|
18294
|
+
process.exitCode = 1;
|
|
18295
|
+
return null;
|
|
18296
|
+
}
|
|
18297
|
+
token = resolveToken();
|
|
18298
|
+
if (!token) {
|
|
18299
|
+
process.stderr.write(refusal);
|
|
18300
|
+
process.exitCode = 1;
|
|
18301
|
+
return null;
|
|
18126
18302
|
}
|
|
18303
|
+
return { token, apiUrl: opts.apiUrlFlag ? opts.apiUrl : resolveApiUrl() };
|
|
18304
|
+
}
|
|
18305
|
+
function runInitAgents(cwd) {
|
|
18306
|
+
const dir = findProjectLinkDir(cwd);
|
|
18307
|
+
if (!dir) {
|
|
18308
|
+
process.stderr.write(
|
|
18309
|
+
"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"
|
|
18310
|
+
);
|
|
18311
|
+
process.exitCode = 1;
|
|
18312
|
+
return;
|
|
18313
|
+
}
|
|
18314
|
+
const { note, block } = installAgentsBlock(dir);
|
|
18315
|
+
process.stdout.write(block === "unchanged" ? `${note}The Kryd block in AGENTS.md is up to date.
|
|
18316
|
+
` : note);
|
|
18317
|
+
}
|
|
18318
|
+
async function runInit(opts) {
|
|
18319
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
18320
|
+
if (opts.agents) {
|
|
18321
|
+
runInitAgents(cwd);
|
|
18322
|
+
return;
|
|
18323
|
+
}
|
|
18324
|
+
let apiUrl = resolveApiUrl(opts.apiUrl);
|
|
18325
|
+
const session = await settleLogin({
|
|
18326
|
+
apiUrl,
|
|
18327
|
+
...opts.apiUrl ? { apiUrlFlag: opts.apiUrl } : {},
|
|
18328
|
+
...opts.io ? { io: opts.io } : {},
|
|
18329
|
+
offerLogin: opts.offerLogin !== false,
|
|
18330
|
+
command: "kryd init"
|
|
18331
|
+
});
|
|
18332
|
+
if (!session) return;
|
|
18333
|
+
const { token } = session;
|
|
18334
|
+
apiUrl = session.apiUrl;
|
|
18127
18335
|
const existingLink = directoryIsLinked(cwd);
|
|
18128
18336
|
if (existingLink) {
|
|
18129
18337
|
const remote = hasRemote(cwd, "kryd");
|
|
@@ -18133,6 +18341,7 @@ async function runInit(opts) {
|
|
|
18133
18341
|
`This folder is already linked to ${existingLink.projectId} (.kryd/project.json).
|
|
18134
18342
|
Running \`kryd init\` again would create a SECOND project and leave that one orphaned.
|
|
18135
18343
|
To deploy it: \`kryd push\`. To start over: \`kryd project rm\` first, or delete .kryd/project.json to unlink this folder.
|
|
18344
|
+
To write or refresh the Kryd block in AGENTS.md: \`kryd init --agents\`.
|
|
18136
18345
|
`
|
|
18137
18346
|
);
|
|
18138
18347
|
process.exitCode = 1;
|
|
@@ -18182,10 +18391,10 @@ To deploy it: \`kryd push\`. To start over: \`kryd project rm\` first, or delete
|
|
|
18182
18391
|
try {
|
|
18183
18392
|
saveProjectLink(cwd, { projectId: project2.id });
|
|
18184
18393
|
try {
|
|
18185
|
-
const
|
|
18394
|
+
const session2 = await whoami(apiUrl, token);
|
|
18186
18395
|
rememberLinkAccount(cwd, {
|
|
18187
|
-
accountId:
|
|
18188
|
-
accountSlug:
|
|
18396
|
+
accountId: session2.accountId,
|
|
18397
|
+
accountSlug: session2.accountSlug
|
|
18189
18398
|
});
|
|
18190
18399
|
} catch {
|
|
18191
18400
|
}
|
|
@@ -18244,9 +18453,10 @@ Scheduled and triggered work runs on your production deploy; preview branches bu
|
|
|
18244
18453
|
Preview branches deploy to https://<branch>-<hash>-${project2.subdomain}.${PREVIEW_TLD}
|
|
18245
18454
|
` + (declaration?.workflowsPath ? `Workflows in ${declaration.workflowsPath}/ run as a worker beside it on production deploys; previews run the web app only.
|
|
18246
18455
|
` : "");
|
|
18456
|
+
const agentsNote = installAgentsBlock(cwd).note;
|
|
18247
18457
|
process.stdout.write(
|
|
18248
18458
|
`Linked "${name}" (${project2.framework}) \u2192 ${repo.htmlUrl}
|
|
18249
|
-
` + routing + `${linkNote}
|
|
18459
|
+
` + routing + `${linkNote}` + agentsNote + `
|
|
18250
18460
|
${nextStep}`
|
|
18251
18461
|
);
|
|
18252
18462
|
} catch (err) {
|
|
@@ -19265,26 +19475,34 @@ AI_GATEWAY_URL and AI_GATEWAY_TOKEN stop being injected from your next deploy (k
|
|
|
19265
19475
|
await reportError(err);
|
|
19266
19476
|
}
|
|
19267
19477
|
}
|
|
19268
|
-
async function
|
|
19478
|
+
async function readResourceStatus(opts) {
|
|
19269
19479
|
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
|
-
`);
|
|
19480
|
+
return toResourceState(await opts.read());
|
|
19281
19481
|
} catch (err) {
|
|
19282
19482
|
if (err instanceof ApiError && err.status === 404 && await projectIsVisible(opts.token, opts.project) === true) {
|
|
19283
|
-
|
|
19284
|
-
`);
|
|
19285
|
-
return;
|
|
19483
|
+
return NOT_ATTACHED;
|
|
19286
19484
|
}
|
|
19287
19485
|
await reportError(err);
|
|
19486
|
+
return null;
|
|
19487
|
+
}
|
|
19488
|
+
}
|
|
19489
|
+
var NOT_ATTACHED = { attached: false, status: null, failureReason: null };
|
|
19490
|
+
function toResourceState(row) {
|
|
19491
|
+
return { attached: true, status: row.status, failureReason: row.failureReason ?? null };
|
|
19492
|
+
}
|
|
19493
|
+
function printResourceStatus(state, noun, project2) {
|
|
19494
|
+
if (!state.attached) {
|
|
19495
|
+
process.stdout.write(`${project2}: no ${noun} attached.
|
|
19496
|
+
`);
|
|
19497
|
+
} else if (state.status === "failed") {
|
|
19498
|
+
process.stdout.write(
|
|
19499
|
+
`${project2}: ${noun} failed \u2014 ${state.failureReason ?? "unknown error"}.
|
|
19500
|
+
Remove it before attaching another.
|
|
19501
|
+
`
|
|
19502
|
+
);
|
|
19503
|
+
} else {
|
|
19504
|
+
process.stdout.write(`${project2}: ${noun} ${state.status}.
|
|
19505
|
+
`);
|
|
19288
19506
|
}
|
|
19289
19507
|
}
|
|
19290
19508
|
async function runDbStatus(opts) {
|
|
@@ -19300,21 +19518,22 @@ async function runDbStatus(opts) {
|
|
|
19300
19518
|
reportNotLinked("kryd db status <projectId>");
|
|
19301
19519
|
return;
|
|
19302
19520
|
}
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
read: async () => {
|
|
19306
|
-
const row = await getDatabaseStatus(apiUrl, token, project2);
|
|
19307
|
-
productionAttached = true;
|
|
19308
|
-
return row;
|
|
19309
|
-
},
|
|
19310
|
-
noun: "database",
|
|
19521
|
+
const production = await readResourceStatus({
|
|
19522
|
+
read: () => getDatabaseStatus(apiUrl, token, project2),
|
|
19311
19523
|
project: project2,
|
|
19312
19524
|
token
|
|
19313
19525
|
});
|
|
19314
|
-
if (!
|
|
19526
|
+
if (!production) return;
|
|
19527
|
+
if (!opts.json) printResourceStatus(production, "database", project2);
|
|
19528
|
+
if (!production.attached) {
|
|
19529
|
+
if (opts.json) printJson2({ production, preview: NOT_ATTACHED });
|
|
19530
|
+
return;
|
|
19531
|
+
}
|
|
19315
19532
|
try {
|
|
19316
19533
|
const preview = await readPreviewDatabase(apiUrl, token, project2);
|
|
19317
|
-
if (
|
|
19534
|
+
if (opts.json) {
|
|
19535
|
+
printJson2({ production, preview: preview ? toResourceState(preview) : NOT_ATTACHED });
|
|
19536
|
+
} else if (!preview) {
|
|
19318
19537
|
process.stdout.write(
|
|
19319
19538
|
`${project2}: no preview database \u2014 preview deploys use the production database.
|
|
19320
19539
|
`
|
|
@@ -19353,12 +19572,14 @@ async function runStorageStatus(opts) {
|
|
|
19353
19572
|
reportNotLinked("kryd storage status <projectId>");
|
|
19354
19573
|
return;
|
|
19355
19574
|
}
|
|
19356
|
-
await
|
|
19575
|
+
const state = await readResourceStatus({
|
|
19357
19576
|
read: () => getStorageStatus(apiUrl, token, project2),
|
|
19358
|
-
noun: "object storage",
|
|
19359
19577
|
project: project2,
|
|
19360
19578
|
token
|
|
19361
19579
|
});
|
|
19580
|
+
if (!state) return;
|
|
19581
|
+
if (opts.json) printJson2(state);
|
|
19582
|
+
else printResourceStatus(state, "object storage", project2);
|
|
19362
19583
|
}
|
|
19363
19584
|
async function runAiStatus(opts) {
|
|
19364
19585
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
@@ -19375,6 +19596,10 @@ async function runAiStatus(opts) {
|
|
|
19375
19596
|
}
|
|
19376
19597
|
try {
|
|
19377
19598
|
const status = await getAiStatus(apiUrl, token, project2);
|
|
19599
|
+
if (opts.json) {
|
|
19600
|
+
printJson2(status);
|
|
19601
|
+
return;
|
|
19602
|
+
}
|
|
19378
19603
|
if (!status.enabled) {
|
|
19379
19604
|
process.stdout.write(`${project2}: no AI endpoint.
|
|
19380
19605
|
`);
|
|
@@ -19389,6 +19614,50 @@ async function runAiStatus(opts) {
|
|
|
19389
19614
|
await reportError(err);
|
|
19390
19615
|
}
|
|
19391
19616
|
}
|
|
19617
|
+
async function runAiModels(opts) {
|
|
19618
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
19619
|
+
const token = resolveToken();
|
|
19620
|
+
if (!token) {
|
|
19621
|
+
process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
|
|
19622
|
+
process.exitCode = 1;
|
|
19623
|
+
return;
|
|
19624
|
+
}
|
|
19625
|
+
let models;
|
|
19626
|
+
try {
|
|
19627
|
+
models = await getAiModels(apiUrl, token);
|
|
19628
|
+
} catch (err) {
|
|
19629
|
+
await reportError(err);
|
|
19630
|
+
return;
|
|
19631
|
+
}
|
|
19632
|
+
if (opts.json) {
|
|
19633
|
+
printJson2({ items: models });
|
|
19634
|
+
return;
|
|
19635
|
+
}
|
|
19636
|
+
if (models.length === 0) {
|
|
19637
|
+
process.stdout.write("No models in the AI catalogue right now.\n");
|
|
19638
|
+
return;
|
|
19639
|
+
}
|
|
19640
|
+
const rows = models.map((m) => ({
|
|
19641
|
+
mark: m.popular ? "*" : " ",
|
|
19642
|
+
model: m.model,
|
|
19643
|
+
provider: m.provider,
|
|
19644
|
+
tasks: m.tasks.join(","),
|
|
19645
|
+
stage: m.stage
|
|
19646
|
+
}));
|
|
19647
|
+
const width = (label, pick2) => rows.reduce((max, r) => Math.max(max, pick2(r).length), label.length);
|
|
19648
|
+
const modelW = width("MODEL", (r) => r.model);
|
|
19649
|
+
const providerW = width("PROVIDER", (r) => r.provider);
|
|
19650
|
+
const tasksW = width("TASKS", (r) => r.tasks);
|
|
19651
|
+
const line = (mark, model, provider, tasks, stage) => `${mark} ${model.padEnd(modelW)} ${provider.padEnd(providerW)} ${tasks.padEnd(tasksW)} ${stage}
|
|
19652
|
+
`;
|
|
19653
|
+
process.stdout.write(line(" ", "MODEL", "PROVIDER", "TASKS", "STAGE"));
|
|
19654
|
+
for (const r of rows) process.stdout.write(line(r.mark, r.model, r.provider, r.tasks, r.stage));
|
|
19655
|
+
process.stdout.write("\n");
|
|
19656
|
+
if (rows.some((r) => r.mark === "*")) process.stdout.write("* popular\n");
|
|
19657
|
+
process.stdout.write(
|
|
19658
|
+
"Use one by putting its MODEL value in the request's `model` field (after `kryd ai add`).\n"
|
|
19659
|
+
);
|
|
19660
|
+
}
|
|
19392
19661
|
function isoDay(iso) {
|
|
19393
19662
|
return iso.slice(0, 10);
|
|
19394
19663
|
}
|
|
@@ -19489,6 +19758,14 @@ async function runWorkflowStatus(opts) {
|
|
|
19489
19758
|
}
|
|
19490
19759
|
try {
|
|
19491
19760
|
const status = await getWorkflowStatus(apiUrl, token, project2);
|
|
19761
|
+
if (opts.json) {
|
|
19762
|
+
printJson2({
|
|
19763
|
+
enabled: status.enabled,
|
|
19764
|
+
tokenExpiresAt: status.tokenExpiresAt,
|
|
19765
|
+
tokenExpiresInDays: status.tokenExpiresAt ? daysUntil(status.tokenExpiresAt) : null
|
|
19766
|
+
});
|
|
19767
|
+
return;
|
|
19768
|
+
}
|
|
19492
19769
|
if (!status.enabled) {
|
|
19493
19770
|
process.stdout.write(`${project2}: no workflows.
|
|
19494
19771
|
`);
|
|
@@ -19644,6 +19921,10 @@ async function runDomainStatus(opts) {
|
|
|
19644
19921
|
const own = list.projectHost ? `${ctx.project} is always served at ${list.projectHost}.
|
|
19645
19922
|
` : "";
|
|
19646
19923
|
if (!opts.host) {
|
|
19924
|
+
if (opts.json) {
|
|
19925
|
+
printJson2(list);
|
|
19926
|
+
return;
|
|
19927
|
+
}
|
|
19647
19928
|
if (list.domains.length === 0) {
|
|
19648
19929
|
process.stdout.write(`${ctx.project}: no custom domains.
|
|
19649
19930
|
${own}`);
|
|
@@ -19668,6 +19949,10 @@ ${own}`);
|
|
|
19668
19949
|
process.exitCode = 1;
|
|
19669
19950
|
return;
|
|
19670
19951
|
}
|
|
19952
|
+
if (opts.json) {
|
|
19953
|
+
printJson2(d);
|
|
19954
|
+
return;
|
|
19955
|
+
}
|
|
19671
19956
|
let out = `${d.host}: ${DOMAIN_STATUS_TEXT[d.status]}.
|
|
19672
19957
|
`;
|
|
19673
19958
|
if (d.redirectTo) out += ` \u2192 redirects to ${d.redirectTo}
|
|
@@ -19722,7 +20007,7 @@ async function runWorkflowInit(opts) {
|
|
|
19722
20007
|
}
|
|
19723
20008
|
const workerName = workerNameFrom(opts.name ?? basename2(target));
|
|
19724
20009
|
const files = scaffoldFiles(language, workerName);
|
|
19725
|
-
const clashes = files.map((f) => f.path).filter((p) =>
|
|
20010
|
+
const clashes = files.map((f) => f.path).filter((p) => existsSync4(join4(target, p)));
|
|
19726
20011
|
if (clashes.length > 0) {
|
|
19727
20012
|
process.stderr.write(
|
|
19728
20013
|
`Refusing to overwrite: ${clashes.join(", ")}.
|
|
@@ -19732,12 +20017,22 @@ Nothing was written. Run this in an empty directory, or pass --dir <path>.
|
|
|
19732
20017
|
process.exitCode = 1;
|
|
19733
20018
|
return;
|
|
19734
20019
|
}
|
|
20020
|
+
if (opts.link !== false) {
|
|
20021
|
+
const session = await settleLogin({
|
|
20022
|
+
apiUrl: resolveApiUrl(opts.apiUrl),
|
|
20023
|
+
...opts.apiUrl ? { apiUrlFlag: opts.apiUrl } : {},
|
|
20024
|
+
...opts.io ? { io: opts.io } : {},
|
|
20025
|
+
offerLogin: true,
|
|
20026
|
+
command: "kryd workflow init"
|
|
20027
|
+
});
|
|
20028
|
+
if (!session) return;
|
|
20029
|
+
}
|
|
19735
20030
|
const written = [];
|
|
19736
20031
|
try {
|
|
19737
20032
|
for (const file2 of files) {
|
|
19738
|
-
const full =
|
|
20033
|
+
const full = join4(target, file2.path);
|
|
19739
20034
|
mkdirSync2(dirname2(full), { recursive: true });
|
|
19740
|
-
|
|
20035
|
+
writeFileSync3(full, file2.contents);
|
|
19741
20036
|
written.push(full);
|
|
19742
20037
|
}
|
|
19743
20038
|
} catch (err) {
|
|
@@ -19774,6 +20069,7 @@ To add a worker to the app in that repository instead, run \`kryd workflow init\
|
|
|
19774
20069
|
process.exitCode = 1;
|
|
19775
20070
|
return;
|
|
19776
20071
|
}
|
|
20072
|
+
process.stdout.write(installAgentsBlock(target).note);
|
|
19777
20073
|
const next = [];
|
|
19778
20074
|
if (opts.dir) next.push(`cd ${opts.dir}`);
|
|
19779
20075
|
const first = firstCommand(language);
|
|
@@ -19823,7 +20119,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19823
20119
|
"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
20120
|
);
|
|
19825
20121
|
process.exitCode = 1;
|
|
19826
|
-
return;
|
|
20122
|
+
return false;
|
|
19827
20123
|
}
|
|
19828
20124
|
const raw = (opts.path ?? DEFAULT_WORKFLOWS_PATH).trim();
|
|
19829
20125
|
const segments = raw.split("/").filter((seg) => seg !== "" && seg !== ".");
|
|
@@ -19833,14 +20129,14 @@ function scaffoldContainedWorker(opts) {
|
|
|
19833
20129
|
`
|
|
19834
20130
|
);
|
|
19835
20131
|
process.exitCode = 1;
|
|
19836
|
-
return;
|
|
20132
|
+
return false;
|
|
19837
20133
|
}
|
|
19838
20134
|
const folder = segments.join("/");
|
|
19839
|
-
const declarationPath =
|
|
20135
|
+
const declarationPath = join4(target, DECLARATION_FILE);
|
|
19840
20136
|
let existing = {};
|
|
19841
20137
|
let existingText = null;
|
|
19842
|
-
if (
|
|
19843
|
-
existingText =
|
|
20138
|
+
if (existsSync4(declarationPath)) {
|
|
20139
|
+
existingText = readFileSync4(declarationPath, "utf8");
|
|
19844
20140
|
let parsed;
|
|
19845
20141
|
try {
|
|
19846
20142
|
parsed = JSON.parse(existingText);
|
|
@@ -19853,7 +20149,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19853
20149
|
`
|
|
19854
20150
|
);
|
|
19855
20151
|
process.exitCode = 1;
|
|
19856
|
-
return;
|
|
20152
|
+
return false;
|
|
19857
20153
|
}
|
|
19858
20154
|
existing = parsed;
|
|
19859
20155
|
if ("kind" in existing) {
|
|
@@ -19862,7 +20158,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19862
20158
|
`
|
|
19863
20159
|
);
|
|
19864
20160
|
process.exitCode = 1;
|
|
19865
|
-
return;
|
|
20161
|
+
return false;
|
|
19866
20162
|
}
|
|
19867
20163
|
if ("workflows" in existing || existing["web"] === false) {
|
|
19868
20164
|
process.stderr.write(
|
|
@@ -19870,7 +20166,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19870
20166
|
`
|
|
19871
20167
|
);
|
|
19872
20168
|
process.exitCode = 1;
|
|
19873
|
-
return;
|
|
20169
|
+
return false;
|
|
19874
20170
|
}
|
|
19875
20171
|
}
|
|
19876
20172
|
const workerName = workerNameFrom(opts.name ?? basename2(target));
|
|
@@ -19878,7 +20174,7 @@ function scaffoldContainedWorker(opts) {
|
|
|
19878
20174
|
path: `${folder}/${f.path}`,
|
|
19879
20175
|
contents: f.contents
|
|
19880
20176
|
}));
|
|
19881
|
-
const clashes = files.map((f) => f.path).filter((p) =>
|
|
20177
|
+
const clashes = files.map((f) => f.path).filter((p) => existsSync4(join4(target, p)));
|
|
19882
20178
|
if (clashes.length > 0) {
|
|
19883
20179
|
process.stderr.write(
|
|
19884
20180
|
`Refusing to overwrite: ${clashes.join(", ")}.
|
|
@@ -19886,18 +20182,18 @@ Nothing was written. Pick another folder with --path <folder>.
|
|
|
19886
20182
|
`
|
|
19887
20183
|
);
|
|
19888
20184
|
process.exitCode = 1;
|
|
19889
|
-
return;
|
|
20185
|
+
return false;
|
|
19890
20186
|
}
|
|
19891
20187
|
const declaration = { ...existing, workflows: { path: folder } };
|
|
19892
20188
|
const written = [];
|
|
19893
20189
|
try {
|
|
19894
20190
|
for (const file2 of files) {
|
|
19895
|
-
const full =
|
|
20191
|
+
const full = join4(target, file2.path);
|
|
19896
20192
|
mkdirSync2(dirname2(full), { recursive: true });
|
|
19897
|
-
|
|
20193
|
+
writeFileSync3(full, file2.contents);
|
|
19898
20194
|
written.push(full);
|
|
19899
20195
|
}
|
|
19900
|
-
|
|
20196
|
+
writeFileSync3(declarationPath, `${JSON.stringify(declaration, null, 2)}
|
|
19901
20197
|
`);
|
|
19902
20198
|
} catch (err) {
|
|
19903
20199
|
for (const path of written.reverse()) {
|
|
@@ -19908,7 +20204,7 @@ Nothing was written. Pick another folder with --path <folder>.
|
|
|
19908
20204
|
}
|
|
19909
20205
|
try {
|
|
19910
20206
|
if (existingText === null) rmSync2(declarationPath, { force: true });
|
|
19911
|
-
else
|
|
20207
|
+
else writeFileSync3(declarationPath, existingText);
|
|
19912
20208
|
} catch {
|
|
19913
20209
|
}
|
|
19914
20210
|
process.stderr.write(
|
|
@@ -19917,14 +20213,15 @@ Nothing was left behind.
|
|
|
19917
20213
|
`
|
|
19918
20214
|
);
|
|
19919
20215
|
process.exitCode = 1;
|
|
19920
|
-
return;
|
|
20216
|
+
return false;
|
|
19921
20217
|
}
|
|
20218
|
+
const agents = installAgentsBlock(target);
|
|
19922
20219
|
const next = [];
|
|
19923
20220
|
const first = firstCommand(language);
|
|
19924
20221
|
if (first) next.push(`(cd ${folder} && ${first})`);
|
|
19925
20222
|
next.push(
|
|
19926
20223
|
"kryd workflow add",
|
|
19927
|
-
`git add ${DECLARATION_FILE
|
|
20224
|
+
`git add ${[DECLARATION_FILE, folder, ...agents.changed].join(" ")} && git commit -m "add workflows"`,
|
|
19928
20225
|
"kryd push"
|
|
19929
20226
|
);
|
|
19930
20227
|
process.stdout.write(
|
|
@@ -19937,9 +20234,10 @@ Next: ${next.map((c) => `\`${c}\``).join(", then ")}.
|
|
|
19937
20234
|
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
20235
|
` + // The app is still built (and type-checked) from the root, so a tsconfig that includes every
|
|
19939
20236
|
// file would compile the worker too — against an SDK the app does not depend on.
|
|
19940
|
-
(
|
|
19941
|
-
` : "")
|
|
20237
|
+
(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.
|
|
20238
|
+
` : "") + agents.note
|
|
19942
20239
|
);
|
|
20240
|
+
return true;
|
|
19943
20241
|
}
|
|
19944
20242
|
async function runStorageRemove(opts) {
|
|
19945
20243
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
@@ -20019,6 +20317,11 @@ async function runProjectList(opts) {
|
|
|
20019
20317
|
await reportError(err);
|
|
20020
20318
|
return;
|
|
20021
20319
|
}
|
|
20320
|
+
if (opts.json) {
|
|
20321
|
+
const linked = resolveProjectId(void 0, opts.cwd);
|
|
20322
|
+
printJson2({ items: projects.map((p) => ({ ...p, linked: p.id === linked })) });
|
|
20323
|
+
return;
|
|
20324
|
+
}
|
|
20022
20325
|
if (projects.length === 0) {
|
|
20023
20326
|
process.stdout.write("No projects yet \u2014 run `kryd init` in a project directory.\n");
|
|
20024
20327
|
return;
|
|
@@ -20070,6 +20373,10 @@ async function runEnvList(opts) {
|
|
|
20070
20373
|
}
|
|
20071
20374
|
try {
|
|
20072
20375
|
const vars = await listEnvVars(apiUrl, token, project2);
|
|
20376
|
+
if (opts.json) {
|
|
20377
|
+
printJson2({ items: vars });
|
|
20378
|
+
return;
|
|
20379
|
+
}
|
|
20073
20380
|
if (vars.length === 0) {
|
|
20074
20381
|
process.stdout.write(
|
|
20075
20382
|
`No environment variables for ${project2} yet.
|
|
@@ -20155,7 +20462,7 @@ async function runEnvPull(opts) {
|
|
|
20155
20462
|
const root = findProjectLinkDir(cwd) ?? cwd;
|
|
20156
20463
|
const outRel = opts.out ?? ".env.kryd";
|
|
20157
20464
|
const outPath = resolve2(root, outRel);
|
|
20158
|
-
if (opts.out !== void 0 &&
|
|
20465
|
+
if (opts.out !== void 0 && existsSync4(outPath) && !opts.force) {
|
|
20159
20466
|
process.stderr.write(
|
|
20160
20467
|
`Refusing to overwrite ${outRel} \u2014 pass --force to replace it (or omit --out to write .env.kryd).
|
|
20161
20468
|
`
|
|
@@ -20429,7 +20736,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
|
|
|
20429
20736
|
await reportError(err);
|
|
20430
20737
|
}
|
|
20431
20738
|
}
|
|
20432
|
-
var CLI_VERSION = true ? "0.
|
|
20739
|
+
var CLI_VERSION = true ? "0.19.0" : "0.0.0-dev";
|
|
20433
20740
|
var program = new Command();
|
|
20434
20741
|
program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
|
|
20435
20742
|
program.hook("postAction", async () => {
|
|
@@ -20439,7 +20746,7 @@ program.command("login").description("Sign in via the browser (default) and stor
|
|
|
20439
20746
|
"--token <token>",
|
|
20440
20747
|
"supply a token instead of the browser flow (prefer $KRYD_TOKEN \u2014 flags are visible in `ps`)"
|
|
20441
20748
|
).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));
|
|
20749
|
+
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
20750
|
var accountCommand = program.command("account").description("The accounts you belong to, and which one commands run in");
|
|
20444
20751
|
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
20752
|
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 +20774,10 @@ program.command("init").description(
|
|
|
20467
20774
|
"--public",
|
|
20468
20775
|
// KRYD-184a. Chosen at creation; there is no command to flip it afterwards yet (KRYD-341).
|
|
20469
20776
|
"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").
|
|
20777
|
+
).option("--api-url <url>", "control-plane API base URL").option(
|
|
20778
|
+
"--agents",
|
|
20779
|
+
"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"
|
|
20780
|
+
).action((opts) => runInit(opts));
|
|
20471
20781
|
program.command("logs [target]").description(
|
|
20472
20782
|
"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
20783
|
).option(
|
|
@@ -20504,7 +20814,7 @@ program.command("redeploy [project]").description(
|
|
|
20504
20814
|
"Redeploy the current live commit (no rebuild) to apply config/env changes, and follow it live"
|
|
20505
20815
|
).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
20816
|
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));
|
|
20817
|
+
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
20818
|
project.command("rm [project]").description(
|
|
20509
20819
|
"Permanently delete a project and everything Kryd created for it (by name or id; asks you to type its name)"
|
|
20510
20820
|
).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 +20831,13 @@ db.command("add [project]").description("Attach a shared or bring-your-own Postg
|
|
|
20521
20831
|
).option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbAdd({ ...opts, project: project2 }));
|
|
20522
20832
|
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
20833
|
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 }));
|
|
20834
|
+
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
20835
|
var storage = program.command("storage").description("Manage project object storage");
|
|
20526
20836
|
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
20837
|
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 }));
|
|
20838
|
+
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
20839
|
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 }));
|
|
20840
|
+
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
20841
|
env.command("set <key> [project]").description(
|
|
20532
20842
|
"Set one of your environment variables \u2014 `KEY=VALUE`, or `KEY` with --stdin / a prompt"
|
|
20533
20843
|
).option(
|
|
@@ -20556,14 +20866,15 @@ env.command("pull [project]").description(
|
|
|
20556
20866
|
"--out <path>",
|
|
20557
20867
|
"file to write, relative to the project root (default: .env.kryd \u2014 never your hand-maintained .env)"
|
|
20558
20868
|
).option("--force", "overwrite the --out file if it already exists").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runEnvPull({ ...opts, project: project2 }));
|
|
20559
|
-
var ai = program.command("ai").description("Manage the app's EU AI gateway");
|
|
20869
|
+
var ai = program.command("ai").description("Manage the app's EU AI gateway and list its models");
|
|
20560
20870
|
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
20871
|
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 }));
|
|
20872
|
+
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 }));
|
|
20873
|
+
ai.command("models").description("List the AI models Kryd offers right now (the catalogue the dashboard shows)").option("--json", "print { items } as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runAiModels(opts));
|
|
20563
20874
|
var workflow = program.command("workflow").description("Manage the project's durable workflows (its own workflow tenant)");
|
|
20564
20875
|
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
20876
|
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 }));
|
|
20877
|
+
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
20878
|
workflow.command("init").description(
|
|
20568
20879
|
"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
20880
|
).requiredOption(
|
|
@@ -20576,7 +20887,7 @@ domain2.command("add <host>").description("Add a custom domain and print the DNS
|
|
|
20576
20887
|
"redirect instead of serving: every request answers 301 to https://<host>, path and query kept"
|
|
20577
20888
|
).option("--api-url <url>", "control-plane API base URL").action((host, opts) => runDomainAdd({ ...opts, host }));
|
|
20578
20889
|
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 }));
|
|
20890
|
+
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
20891
|
var support = program.command("support").description(
|
|
20581
20892
|
"Ask Kryd support a question, report a bug or a wish, and read the answer \u2014 non-interactive, with --json for agents"
|
|
20582
20893
|
);
|
|
@@ -20594,7 +20905,7 @@ function invokedDirectly() {
|
|
|
20594
20905
|
const entry = process.argv[1];
|
|
20595
20906
|
if (!entry) return false;
|
|
20596
20907
|
try {
|
|
20597
|
-
return import.meta.url === pathToFileURL(
|
|
20908
|
+
return import.meta.url === pathToFileURL(realpathSync2(entry)).href;
|
|
20598
20909
|
} catch {
|
|
20599
20910
|
return false;
|
|
20600
20911
|
}
|
|
@@ -20612,6 +20923,7 @@ export {
|
|
|
20612
20923
|
backfillLinkAccount,
|
|
20613
20924
|
program,
|
|
20614
20925
|
runAiAdd,
|
|
20926
|
+
runAiModels,
|
|
20615
20927
|
runAiRemove,
|
|
20616
20928
|
runAiStatus,
|
|
20617
20929
|
runDbAdd,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kryd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"typescript": "^5.6.3",
|
|
43
43
|
"vitest": "^2.1.8",
|
|
44
44
|
"@kryd/shared-types": "0.0.0",
|
|
45
|
-
"@kryd/config-
|
|
46
|
-
"@kryd/config-
|
|
45
|
+
"@kryd/config-ts": "0.0.0",
|
|
46
|
+
"@kryd/config-eslint": "0.0.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --external:commander --define:__KRYD_VERSION__=\\\"$npm_package_version\\\" --outfile=dist/index.js",
|