nylorun 0.19.0-beta → 0.21.0-beta

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.
@@ -0,0 +1,86 @@
1
+ /** The most diff lines one change prints. */
2
+ const MAX_LINES = 40;
3
+ /** Instructions as text: a string, or each block of an array, a blank line between. */
4
+ function textOf(value) {
5
+ if (value === undefined)
6
+ return "";
7
+ if (typeof value === "string")
8
+ return value;
9
+ if (Array.isArray(value) && value.every((item) => typeof item === "string"))
10
+ return value.join("\n\n");
11
+ return undefined;
12
+ }
13
+ /**
14
+ * The lines that differ between `before` and `after`, in order: `- line` for a removed one and
15
+ * `+ line` for an added one, with `…` between changes that are apart. A longest common
16
+ * subsequence of lines, so a moved block shows as removed and added.
17
+ */
18
+ export function lineDiff(before, after) {
19
+ const a = before === "" ? [] : before.split("\n");
20
+ const b = after === "" ? [] : after.split("\n");
21
+ // Lines both start and end with stay out of the table.
22
+ let start = 0;
23
+ while (start < a.length && start < b.length && a[start] === b[start])
24
+ start += 1;
25
+ let endA = a.length;
26
+ let endB = b.length;
27
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
28
+ endA -= 1;
29
+ endB -= 1;
30
+ }
31
+ const x = a.slice(start, endA);
32
+ const y = b.slice(start, endB);
33
+ const ops = [];
34
+ if (x.length * y.length > 4_000_000) {
35
+ ops.push(...x.map((line) => ({ op: "-", line })), ...y.map((line) => ({ op: "+", line })));
36
+ }
37
+ else {
38
+ // lengths[i][j]: the longest common subsequence of x[i..] and y[j..].
39
+ const lengths = Array.from({ length: x.length + 1 }, () => new Uint32Array(y.length + 1));
40
+ for (let i = x.length - 1; i >= 0; i -= 1)
41
+ for (let j = y.length - 1; j >= 0; j -= 1)
42
+ lengths[i][j] = x[i] === y[j] ? lengths[i + 1][j + 1] + 1 : Math.max(lengths[i + 1][j], lengths[i][j + 1]);
43
+ let i = 0;
44
+ let j = 0;
45
+ while (i < x.length || j < y.length) {
46
+ if (i < x.length && j < y.length && x[i] === y[j]) {
47
+ ops.push({ op: "=", line: x[i] });
48
+ i += 1;
49
+ j += 1;
50
+ }
51
+ else if (i < x.length && (j === y.length || lengths[i + 1][j] >= lengths[i][j + 1])) {
52
+ ops.push({ op: "-", line: x[i] });
53
+ i += 1;
54
+ }
55
+ else {
56
+ ops.push({ op: "+", line: y[j] });
57
+ j += 1;
58
+ }
59
+ }
60
+ }
61
+ const lines = [];
62
+ let gap = false;
63
+ for (const { op, line } of ops) {
64
+ if (op === "=") {
65
+ gap = lines.length > 0;
66
+ continue;
67
+ }
68
+ if (gap)
69
+ lines.push("…");
70
+ gap = false;
71
+ lines.push(`${op} ${line}`.trimEnd());
72
+ }
73
+ return lines;
74
+ }
75
+ /** The diff lines to print under one of a plan's changes: the instructions' text, else none. */
76
+ export function changeDiffLines(change, indent = " ") {
77
+ if (change.section !== "harness" || change.path !== "instructions")
78
+ return [];
79
+ const before = textOf(change.from);
80
+ const after = textOf(change.to);
81
+ if (before === undefined || after === undefined)
82
+ return [];
83
+ const lines = lineDiff(before, after);
84
+ const shown = lines.length > MAX_LINES ? [...lines.slice(0, MAX_LINES), `… ${lines.length - MAX_LINES} more lines`] : lines;
85
+ return shown.map((line) => `${indent}${line}`);
86
+ }
@@ -6,7 +6,7 @@
6
6
  import type { ManagementClient } from "@nylorun/admin";
7
7
  import { type StackDeps } from "../stack/commands.js";
8
8
  import { type Connection } from "./target.js";
9
- export declare const tenantUsage = "nylorun tenant <command>\n\nThe Management API of the installation's one Tenant, with a management key: the local Tenant\n--tenant names (else the linked project's, else the default one), or the installation that\nNYLORUN_RUNTIME_URL and NYLORUN_MANAGEMENT_KEY name.\n\n reset [--sessions|--agents|--all] [--yes] [--tenant <name>]\n clear the Tenant's sessions, its Agents (their vaults,\n disks and sessions) or all its data (the Project link and\n keys stay)\n keys put <id> [--management] [--tenant <name>]\n create the Tenant's application key <id> (--management: a\n management key, on the Runtime's machine only), or rotate it\n (the old key stops working); prints the key once\n keys list [--tenant <name>] [--json]\n the Tenant's keys: id, role, when issued (never the keys)\n keys rm <id> [--tenant <name>] delete key <id>: it stops working at once\n signing-keys list [--tenant <name>]\n the Tenant's token signing keys\n signing-keys rotate [--force] [--tenant <name>]\n rotate; --force ends outstanding links and run tokens\n signing-keys revoke <kid> [--tenant <name>]\n revoke a previous or standby key\n models catalog [--tenant <name>] [--json]\n every provider and model the Runtime knows\n models providers [--tenant <name>] [--json]\n the providers with a credential, and the one in use\n models get [--tenant <name>] [--json]\n the Tenant's model\n models set [--tenant <name>] sign in to a model provider and set the Tenant's model\n models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]\n the same with an API key piped in on stdin (CI)\n models select <provider> [--model <id>] [--tenant <name>]\n use a provider that has a credential (default: its model)\n usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>]\n what the model calls used and cost (JSON); --scope\n manifest: the Agents made from agent manifest <id>\n calls [--after <cursor>] [--limit <n>] [--tenant <name>]\n every recorded model call, one JSON line each, until\n caught up; prints the cursor to resume from on stderr\n budgets [get | put <file|->] [--tenant <name>]\n the model spend caps (scope tenant, manifest with id an\n agent manifest's name, or turn); put replaces them all\n tool-policy [get | put <file|->] [--tenant <name>]\n the Tool Gate's rules; put replaces them all\n settings sandbox [get | put <file|->] [--tenant <name>]\n the Agents' environments: limits, stop grace, placement\n settings artifacts [get | put <file|->] [--tenant <name>]\n the artifact store's limits\n settings skills [get | put <file|->] [--tenant <name>]\n the most bytes one skill may hold\n settings limits [get | put <file|->] [--tenant <name>]\n the Runtime limits: sizes, counts and deadlines (record\n entries, request bodies, Agents, model calls, tools)\n coverage --agent <id> [--tenant <name>] [--json]\n which secret of Agent <id>'s vault each MCP server and\n HTTP tool of its manifest would get\n mcp inspect <url> [--server <name>] [--agent <id>] [--sse] [--tenant <name>] [--json]\n list a remote MCP server's tools, with Agent <id>'s\n credential for <url> (none without it): model names, schema\n sizes, renames, or that it needs a person's sign-in (the\n Runtime calls no tool)\n\nA put reads a JSON body from a file, or from stdin for -, and replaces the whole document; what\nbudgets, tool-policy, settings artifacts, settings skills and settings limits get print, put takes back. An API key is read from\nstdin, never from the arguments.";
9
+ export declare const tenantUsage = "nylorun tenant <command>\n\nThe Management API of the installation's one Tenant, with a management key: the local Tenant\n--tenant names (else the linked project's, else the default one), or the installation that\nNYLORUN_RUNTIME_URL and NYLORUN_MANAGEMENT_KEY name.\n\n reset [--sessions|--agents|--all] [--yes] [--tenant <name>]\n clear the Tenant's sessions, its Agents (their vaults,\n disks and sessions) or all its data (the Project link and\n keys stay)\n keys put <id> [--management] [--tenant <name>]\n create the Tenant's application key <id> (--management: a\n management key, on the Runtime's machine only), or rotate it\n (the old key stops working); prints the key once\n keys list [--tenant <name>] [--json]\n the Tenant's keys: id, role, when issued (never the keys)\n keys rm <id> [--tenant <name>] delete key <id>: it stops working at once\n signing-keys list [--tenant <name>]\n the Tenant's token signing keys\n signing-keys rotate [--force] [--tenant <name>]\n rotate; --force ends outstanding links and run tokens\n signing-keys revoke <kid> [--tenant <name>]\n revoke a previous or standby key\n models catalog [--tenant <name>] [--json]\n every provider and model the Runtime knows\n models providers [--tenant <name>] [--json]\n the providers with a credential, and the one in use\n models get [--tenant <name>] [--json]\n the Tenant's model\n models set [--tenant <name>] sign in to a model provider and set the Tenant's model\n models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]\n the same with an API key piped in on stdin (CI)\n models set --provider google-vertex --model <id> --host-identity --project <id> --location <location> [--tenant <name>]\n the same with the Host's Google identity, no key: on a Host\n whose operator sets NYLORUN_MODEL_HOST_IDENTITY\n models select <provider> [--model <id>] [--tenant <name>]\n use a provider that has a credential (default: its model)\n usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>]\n what the model calls used and cost (JSON); --scope\n manifest: the Agents made from agent manifest <id>\n calls [--after <cursor>] [--limit <n>] [--tenant <name>]\n every recorded model call, one JSON line each, until\n caught up; prints the cursor to resume from on stderr\n budgets [get | put <file|->] [--tenant <name>]\n the model spend caps (scope tenant, manifest with id an\n agent manifest's name, or turn); put replaces them all\n tool-policy [get | put <file|->] [--tenant <name>]\n the Tool Gate's rules; put replaces them all\n settings sandbox [get | put <file|->] [--tenant <name>]\n the Agents' environments: limits, stop grace, placement\n settings artifacts [get | put <file|->] [--tenant <name>]\n the artifact store's limits\n settings skills [get | put <file|->] [--tenant <name>]\n the most bytes one skill may hold\n settings limits [get | put <file|->] [--tenant <name>]\n the Runtime limits: sizes, counts and deadlines (record\n entries, request bodies, Agents, model calls, tools)\n coverage --agent <id> [--tenant <name>] [--json]\n which secret of Agent <id>'s vault each MCP server and\n HTTP tool of its manifest would get\n mcp inspect <url> [--server <name>] [--agent <id>] [--sse] [--tenant <name>] [--json]\n list a remote MCP server's tools, with Agent <id>'s\n credential for <url> (none without it): model names, schema\n sizes, renames, or that it needs a person's sign-in (the\n Runtime calls no tool)\n\nA put reads a JSON body from a file, or from stdin for -, and replaces the whole document; what\nbudgets, tool-policy, settings artifacts, settings skills and settings limits get print, put takes back. An API key is read from\nstdin, never from the arguments.";
10
10
  /** The Management API client of the installation `--tenant` (or the environment) names. */
11
11
  export declare function tenantClient(deps: StackDeps, tenant: string | undefined): Promise<{
12
12
  connection: Connection;
@@ -0,0 +1,23 @@
1
+ import type { StackDeps } from "../stack/commands.js";
2
+ /** Watches `paths` (folders recursively, or files), calling `onChange` with a path that changed. */
3
+ export type FolderWatcher = (paths: readonly string[], onChange: (path: string) => void) => {
4
+ close(): void;
5
+ };
6
+ /** `node:fs` `watch`, recursive for a folder. */
7
+ export declare const nodeWatcher: FolderWatcher;
8
+ export interface WatchOptions {
9
+ watcher?: FolderWatcher;
10
+ /** Ends the watch; by default SIGINT or SIGTERM. */
11
+ signal?: AbortSignal;
12
+ /** How long changes settle before a cycle (default 200 ms). */
13
+ debounceMs?: number;
14
+ }
15
+ /**
16
+ * Checks and plans `dirs` (apply's other flags in `args`) now and on each change, saving with
17
+ * `save`, until the signal ends it.
18
+ */
19
+ export declare function watchApply(deps: StackDeps, run: {
20
+ dirs: readonly string[];
21
+ args: readonly string[];
22
+ save: boolean;
23
+ }, options?: WatchOptions): Promise<number>;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `nylorun apply --watch [--save]` (track T7, design §6): checks the folders and plans their
3
+ * manifests at once, then again on each change to them (debounced), until interrupted. It saves
4
+ * only with `--save`: each save is a new immutable version that moves the Agents tracking the
5
+ * name, so `--watch --save` is for a development manifest. A cycle that fails (a Runtime that
6
+ * refuses, a folder that does not pack) prints why and the watch goes on. The file a
7
+ * `runtime.json` `extends` is watched too.
8
+ */
9
+ import { watch as watchFiles } from "node:fs";
10
+ import { relative, resolve } from "node:path";
11
+ import { applyCommand } from "./apply.js";
12
+ import { agentFolders, checkCommand, packed } from "./folder.js";
13
+ /** `node:fs` `watch`, recursive for a folder. */
14
+ export const nodeWatcher = (paths, onChange) => {
15
+ const watchers = paths.map((path) => watchFiles(path, { recursive: true }, (_event, file) => onChange(file ? resolve(path, String(file)) : path)));
16
+ return { close: () => watchers.forEach((watcher) => watcher.close()) };
17
+ };
18
+ /** What changes that never start a cycle: version control and dependencies. */
19
+ const QUIET = /(^|[\\/])(\.git|node_modules|\.hg|\.svn)([\\/]|$)/;
20
+ /**
21
+ * Checks and plans `dirs` (apply's other flags in `args`) now and on each change, saving with
22
+ * `save`, until the signal ends it.
23
+ */
24
+ export async function watchApply(deps, run, options = {}) {
25
+ const cwd = deps.cwd ?? process.cwd();
26
+ const dirs = run.dirs.length > 0 ? run.dirs : ["."];
27
+ const controller = new AbortController();
28
+ const signal = options.signal ?? controller.signal;
29
+ const stop = () => controller.abort();
30
+ if (!options.signal) {
31
+ process.once("SIGINT", stop);
32
+ process.once("SIGTERM", stop);
33
+ }
34
+ const cycle = async () => {
35
+ try {
36
+ if ((await checkCommand(deps, dirs)) === 0)
37
+ await applyCommand(deps, run.args, { planOnly: !run.save, checked: true });
38
+ }
39
+ catch (error) {
40
+ deps.err(error instanceof Error ? error.message : String(error));
41
+ }
42
+ deps.out(`Watching ${dirs.join(", ")} for changes${run.save ? ", saving each" : ""} (Ctrl-C stops).`);
43
+ };
44
+ // The folders, and the files their runtime.json extends.
45
+ const paths = new Set(dirs.map((dir) => resolve(cwd, dir)));
46
+ for (const dir of dirs)
47
+ for (const folder of await agentFolders(resolve(cwd, dir)).catch(() => []))
48
+ for (const file of (await packed(folder).catch(() => undefined))?.extended ?? [])
49
+ paths.add(file);
50
+ let running = cycle();
51
+ let timer;
52
+ let changed;
53
+ const watcher = (options.watcher ?? nodeWatcher)([...paths], (path) => {
54
+ if (QUIET.test(path))
55
+ return;
56
+ changed = path;
57
+ clearTimeout(timer);
58
+ timer = setTimeout(() => {
59
+ const what = changed;
60
+ running = running.then(async () => {
61
+ if (signal.aborted)
62
+ return;
63
+ deps.out("");
64
+ deps.out(`${what === undefined ? "A file" : relative(cwd, what) || "."} changed: checking again.`);
65
+ await cycle();
66
+ });
67
+ }, options.debounceMs ?? 200);
68
+ });
69
+ try {
70
+ await new Promise((done) => {
71
+ if (signal.aborted)
72
+ done();
73
+ else
74
+ signal.addEventListener("abort", () => done(), { once: true });
75
+ });
76
+ }
77
+ finally {
78
+ clearTimeout(timer);
79
+ watcher.close();
80
+ if (!options.signal) {
81
+ process.off("SIGINT", stop);
82
+ process.off("SIGTERM", stop);
83
+ }
84
+ }
85
+ await running;
86
+ return 0;
87
+ }
package/dist/nylo.js CHANGED
@@ -3,7 +3,7 @@ import "./baseline.js";
3
3
  import { runCommand } from "./command.js";
4
4
  import { CliError } from "./errors.js";
5
5
  /**
6
- * `nylo` became part of `nylorun`. Until nylorun 0.19, which removes it, this command names the
6
+ * `nylo` became part of `nylorun`. Until a later release removes it, this command names the
7
7
  * `nylorun` command that replaces each of its own and exits 2.
8
8
  */
9
9
  const REPLACEMENTS = {
@@ -18,7 +18,7 @@ function nyloMessage(argv) {
18
18
  const replacement = REPLACEMENTS[argv[0] ?? ""];
19
19
  return `${replacement
20
20
  ? `nylo is part of nylorun now: nylo ${argv[0]} is ${replacement}.`
21
- : "nylo is part of nylorun now: run nylorun --help."} nylorun 0.19 removes nylo.`;
21
+ : "nylo is part of nylorun now: run nylorun --help."} A later nylorun release removes nylo.`;
22
22
  }
23
23
  runCommand(async () => {
24
24
  throw new CliError(nyloMessage(process.argv.slice(2)), 2);
@@ -182,7 +182,11 @@ ${healthcheck("rustfs")} restart: unless-stopped
182
182
  # MCP servers and HTTP tools on this machine: \`localhost\` in their URLs means the Docker host.
183
183
  NYLORUN_ENDPOINT_LOOPBACK: docker-host
184
184
  # Host tuning (timings, retries, internal sizes; JSON): from the shell that runs \`nylorun start\`.
185
- NYLORUN_TUNING: \${NYLORUN_TUNING:-}${sandboxes ? SANDBOXES_GATEWAY_ENV : ""}
185
+ NYLORUN_TUNING: \${NYLORUN_TUNING:-}
186
+ # Providers whose models may call with this machine's own identity, such as google-vertex
187
+ # with Google's Application Default Credentials (ADR-71): from the shell that runs
188
+ # \`nylorun start\`. Empty: none. Every Tenant on this Host may use it.
189
+ NYLORUN_MODEL_HOST_IDENTITY: \${NYLORUN_MODEL_HOST_IDENTITY:-}${sandboxes ? SANDBOXES_GATEWAY_ENV : ""}
186
190
  extra_hosts:
187
191
  host.docker.internal: host-gateway # model servers, MCP servers and HTTP tools on this machine
188
192
  volumes:
@@ -1,3 +1,5 @@
1
+ /** This CLI's version, from its `package.json` (`nylorun --version`). */
2
+ export declare function cliVersion(): string;
1
3
  /** An image this CLI release pins in its `package.json` `nylorun` (D7). */
2
4
  export type PinnedImage = "runtime" | "studio";
3
5
  /**
@@ -6,6 +6,18 @@ function cliPackageJsonPath() {
6
6
  // src/stack/*.ts and dist/stack/*.js both sit two levels below the package root.
7
7
  return join(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
8
8
  }
9
+ /** This CLI's version, from its `package.json` (`nylorun --version`). */
10
+ export function cliVersion() {
11
+ try {
12
+ const version = JSON.parse(readFileSync(cliPackageJsonPath(), "utf8")).version;
13
+ if (typeof version === "string")
14
+ return version;
15
+ }
16
+ catch (error) {
17
+ throw new CliError(`Cannot read CLI package.json: ${error instanceof Error ? error.message : String(error)}`, 1);
18
+ }
19
+ throw new CliError("nylorun's package.json has no version.", 1);
20
+ }
9
21
  /**
10
22
  * The version this CLI release pins for an image, from its `package.json`
11
23
  * `nylorun.runtime` or `nylorun.studio`: the tag of `ghcr.io/nylorun/<name>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nylorun",
3
- "version": "0.19.0-beta",
3
+ "version": "0.21.0-beta",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Set up and run local Nylorun Tenants (Runtime and Studio) with Docker Compose, and act on a Tenant through its Runtime and Management APIs",
@@ -30,12 +30,12 @@
30
30
  "prepack": "npm run build"
31
31
  },
32
32
  "dependencies": {
33
- "@nylorun/admin": "0.19.3-beta",
34
- "@nylorun/agents": "0.27.0-beta",
35
- "@nylorun/core": "0.27.0-beta"
33
+ "@nylorun/admin": "0.19.5-beta",
34
+ "@nylorun/agents": "0.29.0-beta",
35
+ "@nylorun/core": "0.29.0-beta"
36
36
  },
37
37
  "devDependencies": {
38
- "@earendil-works/pi-ai": "1.2.0",
38
+ "@earendil-works/pi-ai": "1.3.0",
39
39
  "@nylorun/agents": "file:../../sdks/agents",
40
40
  "typescript": "npm:@typescript/typescript6@^6.0.2",
41
41
  "@types/node": "^26.6.5",
@@ -46,7 +46,7 @@
46
46
  "nylo": "dist/nylo.js"
47
47
  },
48
48
  "nylorun": {
49
- "runtime": "0.31.0-beta",
50
- "studio": "0.31.1-beta"
49
+ "runtime": "0.33.0-beta",
50
+ "studio": "0.33.0-beta"
51
51
  }
52
52
  }