nylorun 0.12.2-beta → 0.13.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # nylorun
2
2
 
3
+ ## 0.13.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - 1345f61: **`nylo` ships in `nylorun`; `@nylorun/cli` is deprecated.** One package runs a project's local Tenant (`nylorun`) and is the Runtime client of the linked installation (`nylo`). MIGRATION.md ("`nylo` ships in `nylorun`") has the details.
8
+
9
+ - `nylorun`: a second bin, `nylo` (`npx -p nylorun nylo <command>`), with the same commands, flags, output and exit codes as `@nylorun/cli`'s. The `nylorun` command never loads it. nylorun now depends on `@nylorun/admin`, which `nylo` uses. `nylo configure`'s provider sign-in comes from `@earendil-works/pi-ai` (about 100 MB with its provider SDKs), which is not a dependency: the first `nylo configure` installs the tested version into `~/.nylorun/lib/pi-ai-<version>/` with npm, without install scripts, unless it resolves beside nylorun, so `npx nylorun` stays about 11 MB. `nylorun configure`, `nylorun status --env` and `nylorun doctor sandbox` still exit 2, now naming `npx -p nylorun nylo …`. `nylorun status|reset` (the local Tenant's containers and volumes) and `nylo status|reset` (the Management API) keep their meanings; both usages and the README say which is which.
10
+ - **Deprecated (`@nylorun/cli`):** its `nylo` prints one line on stderr and runs nylorun's `nylo` with the same arguments and exit code; stdout is unchanged, so `eval "$(npx @nylorun/cli env)"` still works. It depends on `nylorun` only. A later release removes it.
11
+ - **Breaking (`@nylorun/admin`): the `./project` subpath is removed.** It re-exported `@nylorun/core/project` for `nylo`, which now imports Core directly. Import `@nylorun/core/project` instead.
12
+ - `@nylorun/create-agent`: `compatibility.json` no longer pins `@nylorun/cli`; the examples run `nylo` from their `nylorun` devDependency.
13
+
14
+ ### Patch Changes
15
+
16
+ - 554df94: **Fixes for the one client layer.**
17
+
18
+ - `@nylorun/admin`: `createAdmin` reads the Project link only in its local-Host step. Explicit `url` and `key`, or `NYLORUN_RUNTIME_URL` with `NYLORUN_MANAGEMENT_KEY`, no longer fail on a `.nylorun/link.json` that cannot be read (EACCES for a root-owned file after `sudo npx nylorun start`, EISDIR); the local-Host step refuses such a link, naming it, as it refuses a broken one (when nothing else names the Host root).
19
+ - `@nylorun/admin`: a refusal without the Runtime's rejection body takes its code from the status: `invalid_request` (400), `credential_invalid` (401), `not_found` (404), `unsupported_media_type` (415), `internal_error` (5xx), else `request_rejected`; it was always `not_found`. Studio's own answers (`{ message }`, from its server and proxy, such as "This Studio session is invalid or has expired.") keep their message instead of one naming only the request and the status.
20
+ - `nylorun`: `start` refuses Project or Host root credentials in a newer format than it reads ("Upgrade nylorun"), as it refuses a newer link, instead of replacing them with new keys in its own format.
21
+
22
+ - Pin admin to the tested release.
23
+ - Pin runtime to the tested release.
24
+ - Pin studio to the tested release.
25
+ - Updated dependencies [554df94]
26
+ - Updated dependencies [1345f61]
27
+ - @nylorun/admin@0.15.0-beta
28
+
3
29
  ## 0.12.2-beta
4
30
 
5
31
  ### Patch Changes
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # nylorun
2
2
 
3
3
  Sets up and runs local Nylorun Tenants: the Runtime and Studio, in Docker
4
- Compose, one Tenant per project. It needs nothing in your project; run it with
4
+ Compose, one Tenant per project. Its second command, `nylo`, is the Runtime
5
+ client of the linked installation. It needs nothing in your project; run it with
5
6
  `npx` in the project's directory:
6
7
 
7
8
  ```sh
@@ -14,10 +15,13 @@ and S2, serving exactly that Tenant, and its name is the Tenant's name.
14
15
  `nylorun start` in a project creates the project's Tenant (the Runtime creates
15
16
  it on its first start) and links the project to it; anywhere else, it starts
16
17
  the Tenant `default`. Projects that must not share agents, credentials or
17
- history run separate Tenants. Agents, sessions and model providers belong to
18
- the Runtime client, [`@nylorun/cli`](../cli/README.md) (command `nylo`).
19
- Depends on `@nylorun/core` only among Nylorun packages and never imports
20
- `@nylorun/runtime`. Vocabulary: [runtime/src/CONTEXT.md](../runtime/src/CONTEXT.md).
18
+ history run separate Tenants. The Tenant's status, data, signing keys and model
19
+ provider over its APIs belong to the Runtime client, `nylo`
20
+ ([below](#the-runtime-client-nylo)), which works on a remote installation too.
21
+ Depends on `@nylorun/core` and `@nylorun/admin` (for `nylo`) among Nylorun
22
+ packages and never imports `@nylorun/runtime`; the `nylorun` command loads
23
+ neither `nylo` nor `@nylorun/admin`. Vocabulary:
24
+ [runtime/src/CONTEXT.md](../runtime/src/CONTEXT.md).
21
25
 
22
26
  Prerequisites, installed by the developer (nylorun never downloads them): Node
23
27
  24 or newer, and Docker with Compose v2 ([Docker
@@ -59,10 +63,13 @@ nylorun sandbox status [--tenant <name>] [--json] # pods on a Kubernetes conte
59
63
 
60
64
  `up` and `down` are the Docker Compose spellings of `start` and `stop`: `down`
61
65
  stops the containers and keeps the volumes, so no Tenant data is lost. Removed
62
- commands exit 2 naming the replacement: `nylorun dev`, `nylorun configure`,
63
- `nylorun status --env`, `nylorun doctor sandbox` and the MCP OAuth `connect`
66
+ commands exit 2 naming the replacement: `nylorun dev` and the MCP OAuth `connect`
64
67
  subcommand of `nylorun mcp` (protocol 10: add a key or a gateway credential to a
65
68
  vault instead, and preview the server with `nylorun mcp inspect`).
69
+ `nylorun configure`, `nylorun status --env` and `nylorun doctor sandbox` exit 2
70
+ naming `nylo configure`, `nylo env` and `nylo doctor sandbox`.
71
+ `nylorun status` and `nylorun reset` are not `nylo status` and `nylo reset`: see
72
+ [`status` and `reset`](#status-and-reset-nylorun-or-nylo).
66
73
 
67
74
  ## Tenants
68
75
 
@@ -350,8 +357,95 @@ its files and ports; the next start creates the Tenant anew (with a new id) and
350
357
  relinks the project. `nylorun delete <tenant> --yes` removes the Tenant's
351
358
  containers, volumes and Host root: the vault key (KEK) and all its data go with it.
352
359
 
360
+ ## The Runtime client: `nylo`
361
+
362
+ `nylo` acts on the one Tenant of the linked installation: a local Tenant or any
363
+ Runtime reachable by URL and key. Run it without installing, or in a project
364
+ that has `nylorun` as a devDependency:
365
+
366
+ ```sh
367
+ npx -p nylorun nylo status # anywhere
368
+ npx nylo status # in a project with nylorun installed
369
+ ```
370
+
371
+ Outside such a project, `npx nylo` looks for an npm package named `nylo`, which
372
+ is not this one: use `npx -p nylorun nylo`. The deprecated
373
+ [`@nylorun/cli`](../cli/README.md) runs this `nylo` for one more release.
374
+
375
+ ```sh
376
+ nylo status [--json] # the Tenant, its checks and counts
377
+ nylo reset [--sessions|--sandboxes|--all] [--yes]
378
+ nylo access signing-keys … # the Tenant's signing keys: list, rotate, revoke
379
+ nylo configure # set or replace the Tenant's model provider
380
+ nylo env # export lines for the linked Project
381
+ nylo doctor sandbox [--json] # sandbox backend via the Management API
382
+ ```
383
+
384
+ Every command acts on the linked installation: the [Project link and
385
+ credentials](#the-project-link), or `NYLORUN_RUNTIME_URL` and
386
+ `NYLORUN_SERVER_KEY` when the Project has no link. An installation serves one
387
+ Tenant, so nothing selects it. `status`, `reset`, `access`, `configure` and
388
+ `doctor` use the Management API with the Project's management key, or
389
+ `NYLORUN_MANAGEMENT_KEY` (with `NYLORUN_RUNTIME_URL` when there is no link);
390
+ `env` prints the Runtime URL and the application key, for the Runtime API:
391
+
392
+ ```sh
393
+ eval "$(npx -p nylorun nylo env)"
394
+ # → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY
395
+ ```
396
+
397
+ `nylo` only reads the link, with `@nylorun/core/project`'s reader. It refuses a
398
+ link from an older nylorun (format 0 to 2) and says to run `npx nylorun start`,
399
+ which links the project again. `createClient` in `@nylorun/agents` reads the
400
+ link (or the two variables) too, so the project's `npm run dev` and `npm start`
401
+ need no Nylorun tool.
402
+
403
+ Local Tenant commands (`up`, `down`, `start`, `stop`, `logs`, `studio`) exit 2
404
+ naming `npx nylorun <command>`. `nylo tenant …` exits 2: `npx nylorun start`
405
+ creates the project's Tenant and the link, and `nylo status|reset` replace
406
+ `nylo tenant status|reset`. `nylo endpoints` was removed with Action endpoints
407
+ (protocol 8: the Runtime runs no code of yours during a session) and exits 2.
408
+
409
+ ### `status` and `reset`: `nylorun` or `nylo`
410
+
411
+ Both commands have a `status` and a `reset`, which act on different things:
412
+
413
+ | | `nylorun` | `nylo` |
414
+ | --- | --- | --- |
415
+ | Acts on | the local Tenant on this machine (`--tenant`, `NYLORUN_TENANT` or the link), through Docker and `nylorun-operate` in its runtime container | the linked installation, local or remote, through the Management API with a management key |
416
+ | `status` | the containers, endpoints and Runtime health, and the Tenant's state: it answers when the Tenant is stopped or not open, and says why | the open Tenant's checks, counts and sandbox backend (`GET /v1/tenant`); when the Tenant does not answer it fails and points at `nylorun status` |
417
+ | `reset` | deletes all the Tenant's data: volumes, `tenant/` and the vault key; the next `start` creates it anew, with a new id, and relinks the project | clears the sessions (the default), the sandboxes or all the data (`--all`) after draining work in flight; the Tenant, its keys and the Project link are kept |
418
+
419
+ `nylo reset --all` asks first; pass `--yes` when not in a terminal.
420
+ `nylorun reset` asks too, or takes `--yes`.
421
+
422
+ ### `nylo configure`
423
+
424
+ Lists the Tenant's model catalog (`GET /v1/tenant/models`), asks for a provider,
425
+ a model and its sign-in (an API key, or the provider's OAuth), and sets the
426
+ Tenant's model provider (`PUT /v1/tenant/model`). The sign-in flows come from
427
+ `@earendil-works/pi-ai`, about 100 MB with its provider SDKs, which no other
428
+ command needs, so it is not a dependency of nylorun and `npx nylorun` stays
429
+ small. When it does not resolve beside nylorun, the first `nylo configure`
430
+ installs the version nylorun was tested with, with npm and without install
431
+ scripts, into `~/.nylorun/lib/pi-ai-<version>/` (under `NYLORUN_HOME` when that
432
+ is set), and later runs reuse it. Studio's model settings, and `.env` at
433
+ `nylorun start`, set the provider without it.
434
+
435
+ ### `nylo` exit codes
436
+
437
+ | Code | Meaning |
438
+ | --- | --- |
439
+ | 0 | Success |
440
+ | 1 | Generic failure |
441
+ | 2 | Usage error, or a moved or removed command |
442
+ | 6 | No Runtime at the linked or local URL |
443
+ | 130 / 143 | SIGINT / SIGTERM |
444
+
353
445
  ## Exit codes
354
446
 
447
+ These are `nylorun`'s; [`nylo`'s](#nylo-exit-codes) are above.
448
+
355
449
  | Code | Meaning |
356
450
  | --- | --- |
357
451
  | 0 | Success |
@@ -376,5 +470,9 @@ containers, volumes and Host root: the vault key (KEK) and all its data go with
376
470
  | Studio login expired | `nylorun studio` |
377
471
  | Which Studio is which | `nylorun ls` lists each Tenant's Studio URL; `nylorun studio --tenant <name>` opens one signed in |
378
472
  | `Tenant … was created by nylorun 0.5` (exit 3) | `nylorun reset --tenant <name>` (its data starts fresh) |
473
+ | `nylo`: no Runtime answers, or the Tenant is not open | `npx nylorun start`, then `npx nylorun status`, which shows the cause and its `repair` |
474
+ | `nylo`: no management key | `npx nylorun start` in the project writes one; elsewhere set `NYLORUN_MANAGEMENT_KEY` |
475
+ | `nylo`: `403 key_role_mismatch` | The key belongs to the other API: a management key for the Management API, an application key for the Runtime API |
476
+ | `nylo`: old Project link refused | `npx nylorun start` in the project |
379
477
 
380
478
  See [MIGRATION.md](../MIGRATION.md).
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import "./baseline.js";
3
3
  import { baselineEnv } from "./baseline.js";
4
+ import { refuseNativeWindows, runCommand } from "./command.js";
4
5
  import { CliError } from "./errors.js";
5
6
  import { isStackCommand, keyCommand, keyUsage, mcpCommand, mcpUsage, sandboxCommand, sandboxUsage, stackCommand, stackUsage, studioCommand, } from "./stack/index.js";
6
7
  const usage = `nylorun <up|down|start|stop|status|logs|studio|reset|ls|delete|sandbox|key|mcp|doctor|telemetry>
@@ -20,12 +21,16 @@ else (outside a project) the default Tenant; NYLORUN_HOME sets its Host root
20
21
 
21
22
  nylorun sets up and runs local Tenants. "nylorun start" in a project creates the
22
23
  project's Tenant and the Project link; anywhere else, it starts the default Tenant.
23
- Agents, sessions and model providers belong to the Runtime client: npx @nylorun/cli --help`;
24
- const CLIENT = "npx @nylorun/cli";
25
- /** Commands that were removed, or moved to the Runtime client (`@nylorun/cli`, command `nylo`). */
24
+ nylorun status and reset act on the local Tenant's containers and volumes on this machine.
25
+ The package's other command, nylo, is the Runtime client of the linked installation: its
26
+ status and reset go through the Management API, and it sets the model provider (nylo --help,
27
+ or npx -p nylorun nylo --help).`;
28
+ /** `nylo`, the Runtime client (this package's second command), run without installing. */
29
+ const CLIENT = "npx -p nylorun nylo";
30
+ /** Commands that were removed, or that are the Runtime client's (`nylo`). */
26
31
  const MOVED_TO_CLIENT = {
27
32
  dev: "nylorun dev was removed: run `npx nylorun start` in your project (it creates the project's Tenant and the Project link), then your project's `npm run dev`.",
28
- configure: `nylorun configure moved to the Runtime client: ${CLIENT} configure`,
33
+ configure: `nylorun configure is the Runtime client's: ${CLIENT} configure`,
29
34
  serve: "nylorun serve was removed. Use node dist/src/main.js with NYLORUN_RUNTIME_URL and NYLORUN_SERVER_KEY.",
30
35
  runtime: "nylorun runtime was removed: the local Runtime runs in Docker Compose. Use nylorun up|down|status|logs.",
31
36
  };
@@ -35,13 +40,12 @@ async function main() {
35
40
  const [command, ...args] = process.argv.slice(2);
36
41
  if (!command || command === "--help" || command === "-h")
37
42
  return void console.log(usage);
38
- if (process.platform === "win32")
39
- throw new CliError("Nylorun does not run on native Windows. Use WSL2: install Node 24 and Docker (Docker Desktop's WSL integration) inside your WSL distribution and run nylorun there (https://learn.microsoft.com/windows/wsl/install).", 1);
43
+ refuseNativeWindows();
40
44
  const moved = MOVED_TO_CLIENT[command];
41
45
  if (moved)
42
46
  throw usageError(moved);
43
47
  if (command === "status" && args.includes("--env"))
44
- throw usageError(`nylorun status --env moved to the Runtime client: ${CLIENT} env`);
48
+ throw usageError(`nylorun status --env is the Runtime client's: ${CLIENT} env`);
45
49
  if (command === "studio") {
46
50
  if (args.includes("--local-ui"))
47
51
  throw usageError(LOCAL_UI_REMOVED);
@@ -76,7 +80,7 @@ async function main() {
76
80
  }
77
81
  if (command === "doctor") {
78
82
  if (args[0] === "sandbox")
79
- throw usageError(`nylorun doctor sandbox moved to the Runtime client: ${CLIENT} doctor sandbox`);
83
+ throw usageError(`nylorun doctor sandbox is the Runtime client's: ${CLIENT} doctor sandbox`);
80
84
  if (args.some((option) => option !== "--json"))
81
85
  throw usageError("Usage: nylorun doctor [--json]");
82
86
  const { doctorStack } = await import("./doctor.js");
@@ -85,13 +89,4 @@ async function main() {
85
89
  }
86
90
  throw usageError(usage);
87
91
  }
88
- async function finish(code) {
89
- process.exitCode = code;
90
- for (const stream of [process.stdout, process.stderr])
91
- await new Promise((resolve) => stream.write("", () => resolve()));
92
- process.exit(code);
93
- }
94
- void main().then(() => finish(process.exitCode === undefined ? 0 : Number(process.exitCode)), (error) => {
95
- console.error(error instanceof Error ? error.message : String(error));
96
- return finish(error instanceof CliError ? error.exitCode : 1);
97
- });
92
+ runCommand(main);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `nylo access`: the linked Tenant's signing keys, which sign capability links and run tokens,
3
+ * through the Management API with the Project's management key. (Subject tokens, the
4
+ * access policy, browser keys and revocations left the Runtime in protocol 7.)
5
+ */
6
+ import type { ManagementClient } from "@nylorun/admin";
7
+ export declare const accessUsage = "nylo access signing-keys <list|rotate|revoke>\n\n signing-keys list list the signing keys\n signing-keys rotate [--force] rotate; --force ends outstanding links and run tokens\n signing-keys revoke <kid> revoke a previous or standby key";
8
+ export declare function accessCommand(input: readonly string[], client?: () => Promise<ManagementClient>): Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { findProjectRoot } from "@nylorun/core/project";
2
+ import { CliError } from "../../errors.js";
3
+ import { linkedConnection, managementClient } from "../project/connection.js";
4
+ export const accessUsage = `nylo access signing-keys <list|rotate|revoke>
5
+
6
+ signing-keys list list the signing keys
7
+ signing-keys rotate [--force] rotate; --force ends outstanding links and run tokens
8
+ signing-keys revoke <kid> revoke a previous or standby key`;
9
+ const usageError = (message = accessUsage) => new CliError(message, 2);
10
+ async function linkedClient() {
11
+ return managementClient(await linkedConnection(findProjectRoot() ?? process.cwd()));
12
+ }
13
+ const print = (value) => console.log(JSON.stringify(value, null, 2));
14
+ export async function accessCommand(input, client = linkedClient) {
15
+ const [topic, action, ...args] = input;
16
+ if (topic === "signing-keys") {
17
+ const keys = async () => (await client()).signingKeys;
18
+ if (action === "list" && args.length === 0)
19
+ return print(await (await keys()).list());
20
+ if (action === "rotate") {
21
+ const force = args[0] === "--force";
22
+ if (args.length > (force ? 1 : 0))
23
+ throw usageError();
24
+ return print(await (await keys()).rotate({ force }));
25
+ }
26
+ if (action === "revoke" && args.length === 1)
27
+ return print(await (await keys()).revoke(args[0]));
28
+ throw usageError();
29
+ }
30
+ if (topic === "policy" || topic === "keys" || topic === "revoke" || topic === "token")
31
+ throw usageError(`nylo access ${topic} was removed: the Runtime no longer mints subject tokens or keeps an access policy, browser keys or revocations (protocol 7). Trust your identity provider's tokens with an identity file, and give servers an application key (nylorun key put <id>).`);
32
+ throw usageError();
33
+ }
@@ -0,0 +1,2 @@
1
+ /** `nylo <command> [...args]`. */
2
+ export declare function main(argv: readonly string[]): Promise<void>;
@@ -0,0 +1,106 @@
1
+ import { findProjectRoot } from "@nylorun/core/project";
2
+ import { CliError } from "../errors.js";
3
+ import { refuseNativeWindows } from "../command.js";
4
+ import { printLinkedEnvExports } from "./project/env.js";
5
+ import { linkedConnection, managementClient } from "./project/connection.js";
6
+ import { accessCommand } from "./access/commands.js";
7
+ import { resetCommand, statusCommand } from "./installation/commands.js";
8
+ const usage = `nylo <status|reset|access|configure|env|doctor>
9
+
10
+ Runtime client for the linked installation and its one Tenant (the Project link that
11
+ npx nylorun start writes, or NYLORUN_RUNTIME_URL with NYLORUN_SERVER_KEY, and
12
+ NYLORUN_MANAGEMENT_KEY for status, reset, access, configure and doctor):
13
+ status [--json] the Tenant, its checks and counts
14
+ reset [--sessions|--sandboxes|--all] [--yes]
15
+ clear the Tenant's sessions, sandboxes or all its data
16
+ access signing-keys list|rotate|revoke the Tenant's token signing keys (nylo access --help)
17
+ configure set the linked Tenant's model provider
18
+ env print the linked Project's NYLORUN_* variables as exports
19
+ doctor sandbox [--json] show which sandbox backend this Tenant's Host offers
20
+
21
+ nylo status and reset go through the Management API, to a local or a remote installation.
22
+ Local Tenants are nylorun's, the other command of this package: npx nylorun start in a
23
+ project creates its Tenant and the Project link, and nylorun status and reset act on the
24
+ local Tenant's containers and volumes.`;
25
+ /** Local Tenant commands, which `nylorun` runs. */
26
+ const LOCAL_COMMANDS = new Set([
27
+ "up", "down", "start", "stop", "logs", "studio", "runtime", "restart", "run",
28
+ ]);
29
+ const TENANT_REMOVED = `nylo tenant was removed: an installation serves one Tenant.
30
+ Run "npx nylorun start" in your project to create its Tenant and the Project link.
31
+ Then use nylo status and nylo reset on the linked installation.`;
32
+ const ENDPOINTS_REMOVED = `nylo endpoints was removed: the Runtime runs no code of yours during a session,
33
+ so there are no Action endpoints. An agent's tools are http() tools and remote MCP servers
34
+ (see MIGRATION.md).`;
35
+ const usageError = (message) => new CliError(message, 2);
36
+ /** `nylo <command> [...args]`. */
37
+ export async function main(argv) {
38
+ const [command, ...args] = argv;
39
+ if (!command || command === "--help" || command === "-h")
40
+ return void console.log(usage);
41
+ refuseNativeWindows();
42
+ if (command === "tenant")
43
+ throw usageError(TENANT_REMOVED);
44
+ if (command === "status")
45
+ return await statusCommand(args);
46
+ if (command === "reset")
47
+ return await resetCommand(args);
48
+ if (command === "endpoints")
49
+ throw usageError(ENDPOINTS_REMOVED);
50
+ if (command === "access") {
51
+ if (args[0] === "--help" || args[0] === "-h" || args.length === 0) {
52
+ const { accessUsage } = await import("./access/commands.js");
53
+ return void console.log(accessUsage);
54
+ }
55
+ return await accessCommand(args);
56
+ }
57
+ if (command === "env") {
58
+ if (args.length)
59
+ throw usageError("Usage: nylo env");
60
+ await printLinkedEnvExports(findProjectRoot() ?? process.cwd());
61
+ return;
62
+ }
63
+ if (command === "doctor") {
64
+ const [topic, ...options] = args;
65
+ if (topic !== "sandbox" || options.some((option) => option !== "--json"))
66
+ throw usageError("Usage: nylo doctor sandbox [--json]. Check the local Tenant with npx nylorun doctor.");
67
+ const { doctorSandbox } = await import("./doctor.js");
68
+ await doctorSandbox({ json: options.includes("--json") });
69
+ return;
70
+ }
71
+ if (command === "dev")
72
+ throw usageError('nylorun dev was removed: run "npx nylorun start" once in your project, then your project\'s npm run dev.');
73
+ if (LOCAL_COMMANDS.has(command))
74
+ throw usageError(`Local Tenants are nylorun's: npx nylorun ${command}`);
75
+ if (command === "configure") {
76
+ for (const arg of args) {
77
+ if (arg === "--global" || arg === "--db")
78
+ throw usageError(`${arg} was removed. Use the Runtime Host root (NYLORUN_HOME) and a Project link instead.`);
79
+ if (arg.startsWith("-"))
80
+ throw usageError(usage);
81
+ }
82
+ if (args.length)
83
+ throw usageError(usage);
84
+ const projectRoot = findProjectRoot() ?? process.cwd();
85
+ const auth = await linkedConnection(projectRoot);
86
+ const admin = managementClient(auth);
87
+ // The provider login flows (pi-ai) load only here.
88
+ const { ConfigurationCancelled, configureProvider, fetchModelCatalog } = await import("./model/configure.js");
89
+ const { putHostModel } = await import("./model/host-model.js");
90
+ const controller = new AbortController();
91
+ const cancel = (signal) => controller.abort(new ConfigurationCancelled(signal));
92
+ process.once("SIGINT", () => cancel("SIGINT"));
93
+ process.once("SIGTERM", () => cancel("SIGTERM"));
94
+ const health = await fetch(`${auth.url}/health`).catch(() => undefined);
95
+ if (!health?.ok)
96
+ throw new CliError(`No Runtime is listening at ${auth.url}. Start the local Tenant with "npx nylorun start".`, 6);
97
+ const catalog = await fetchModelCatalog(admin);
98
+ const prompted = await configureProvider({
99
+ signal: controller.signal,
100
+ catalog,
101
+ });
102
+ await putHostModel(admin, prompted);
103
+ return;
104
+ }
105
+ throw usageError(usage);
106
+ }
@@ -0,0 +1,25 @@
1
+ export type SandboxReport = {
2
+ preference: string;
3
+ backend: string | null;
4
+ isolation?: string;
5
+ reason?: string;
6
+ probes: {
7
+ name: string;
8
+ isolation: string;
9
+ available: boolean;
10
+ reason?: string;
11
+ version?: string;
12
+ }[];
13
+ defaultImage?: string;
14
+ /** The Tenant's sandbox configuration (Runtimes with Sandboxes v3). */
15
+ config?: {
16
+ default: "none" | "virtual" | Record<string, unknown>;
17
+ limits?: {
18
+ network?: readonly string[];
19
+ };
20
+ };
21
+ };
22
+ /** `nylo doctor sandbox`: Tenant sandbox report via the Management API (F2-4). */
23
+ export declare function doctorSandbox(options: {
24
+ json: boolean;
25
+ }): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { release } from "node:os";
2
+ import { findProjectRoot } from "@nylorun/core/project";
3
+ import { CliError } from "../errors.js";
4
+ import { linkedConnection, managementClient } from "./project/connection.js";
5
+ function platformLine() {
6
+ const os = process.platform === "darwin"
7
+ ? "macOS"
8
+ : process.env.WSL_DISTRO_NAME
9
+ ? `Linux (WSL: ${process.env.WSL_DISTRO_NAME})`
10
+ : process.platform;
11
+ return `${os} (kernel ${release()}) · ${process.arch}`;
12
+ }
13
+ /** The Tenant's sandbox report (`GET /v1/tenant/sandbox`, Management API). */
14
+ async function fetchSandboxReport() {
15
+ const connection = await linkedConnection(findProjectRoot() ?? process.cwd());
16
+ return (await managementClient(connection).settings.sandbox.get());
17
+ }
18
+ /** `nylo doctor sandbox`: Tenant sandbox report via the Management API (F2-4). */
19
+ export async function doctorSandbox(options) {
20
+ let report;
21
+ try {
22
+ report = await fetchSandboxReport();
23
+ }
24
+ catch (error) {
25
+ throw new CliError(error instanceof Error
26
+ ? error.message
27
+ : `Could not read Tenant sandbox status: ${String(error)}`, 1);
28
+ }
29
+ if (options.json) {
30
+ console.log(JSON.stringify({ platform: platformLine(), ...report }, null, 2));
31
+ return;
32
+ }
33
+ const rows = [["platform", platformLine()]];
34
+ for (const probe of report.probes)
35
+ rows.push([
36
+ probe.name,
37
+ `${probe.available ? "✓" : "✗"} ${probe.reason ?? ""}${probe.version ? ` · ${probe.version}` : ""}`,
38
+ ]);
39
+ rows.push([
40
+ "preference",
41
+ report.preference === "auto"
42
+ ? "auto (npx nylorun start seeds the Tenant's sandbox.backend from .env NYLORUN_SANDBOX)"
43
+ : report.preference,
44
+ ]);
45
+ if (report.config)
46
+ rows.push([
47
+ "default",
48
+ report.config.default === "none"
49
+ ? "none: sessions get a sandbox only when opened with one (set a default with PUT /v1/tenant/sandbox)"
50
+ : report.config.default === "virtual"
51
+ ? "virtual: sessions that name no sandbox get one"
52
+ : "a Tenant sandbox: sessions that name no sandbox get it",
53
+ ]);
54
+ rows.push([
55
+ "selected",
56
+ report.backend
57
+ ? `${report.backend} (${report.isolation} isolation)`
58
+ : `none: ${report.reason}`,
59
+ ]);
60
+ const width = Math.max(...rows.map(([key]) => key.length)) + 2;
61
+ for (const [key, value] of rows)
62
+ console.log(` ${key.padEnd(width)}${value}`);
63
+ if (report.backend === "virtual")
64
+ console.log("\n The virtual shell emulates bash in the Runtime process; it is not a VM boundary.");
65
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `nylo status [--json]`: the Tenant, its checks and counts. A Tenant that is not open does not
3
+ * answer: `npx nylorun status` reports why.
4
+ */
5
+ export declare function statusCommand(args: readonly string[]): Promise<void>;
6
+ /** `nylo reset [--sessions|--sandboxes|--all] [--yes]`; `--all` asks first. */
7
+ export declare function resetCommand(args: readonly string[]): Promise<void>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * `nylo status` and `nylo reset`: the linked installation's one Tenant, through the Management
3
+ * API (the Project's management key).
4
+ */
5
+ import { createInterface } from "node:readline/promises";
6
+ import { findProjectRoot } from "@nylorun/core/project";
7
+ import { CliError } from "../../errors.js";
8
+ import { linkedConnection, managementClient } from "../project/connection.js";
9
+ const projectRoot = () => findProjectRoot() ?? process.cwd();
10
+ /**
11
+ * `nylo status [--json]`: the Tenant, its checks and counts. A Tenant that is not open does not
12
+ * answer: `npx nylorun status` reports why.
13
+ */
14
+ export async function statusCommand(args) {
15
+ const json = args.includes("--json");
16
+ if (args.some((a) => a !== "--json"))
17
+ throw new CliError("Usage: nylo status [--json]", 2);
18
+ const root = projectRoot();
19
+ const connection = await linkedConnection(root);
20
+ const admin = managementClient(connection);
21
+ let body;
22
+ try {
23
+ body = await admin.tenant.status();
24
+ }
25
+ catch (error) {
26
+ throw new CliError(`${(error instanceof Error ? error.message : `The Tenant at ${connection.url} is not reachable`).replace(/\.?$/, ".")} Run "npx nylorun status" to see the Tenant's state and why it is not open.`, 1);
27
+ }
28
+ if (json) {
29
+ console.log(JSON.stringify(body, null, 2));
30
+ return;
31
+ }
32
+ console.log(`${body.tenant.name} ${body.tenant.id}`);
33
+ console.log(`runtime ${connection.url}`);
34
+ console.log(`path ${body.path}`);
35
+ console.log(`checks ${Object.entries(body.checks)
36
+ .map(([k, v]) => `${k}=${v ? "ok" : "fail"}`)
37
+ .join(" ")}`);
38
+ console.log(`counts sessions=${body.counts.sessions} running=${body.counts.runningSessions} uncertain=${body.counts.uncertainEffects}`);
39
+ console.log(`sandbox ${body.sandbox.backend ?? "none"}`);
40
+ }
41
+ const RESET_USAGE = "Usage: nylo reset [--sessions|--sandboxes|--all] [--yes]";
42
+ const SCOPES = { "--sessions": "sessions", "--sandboxes": "sandboxes", "--all": "all" };
43
+ /** `nylo reset [--sessions|--sandboxes|--all] [--yes]`; `--all` asks first. */
44
+ export async function resetCommand(args) {
45
+ const yes = args.includes("--yes");
46
+ const flags = args.filter((a) => a !== "--yes");
47
+ if (flags.some((f) => !(f in SCOPES)) || args.filter((a) => a === "--yes").length > 1)
48
+ throw new CliError(RESET_USAGE, 2);
49
+ if (flags.length > 1)
50
+ throw new CliError("Pass only one of --sessions, --sandboxes, --all.", 2);
51
+ const scope = flags.length ? SCOPES[flags[0]] : "sessions";
52
+ const connection = await linkedConnection(projectRoot());
53
+ const tenant = connection.link?.tenant;
54
+ const target = tenant ? `Tenant ${tenant} (${connection.url})` : `the Tenant on ${connection.url}`;
55
+ const admin = managementClient(connection);
56
+ if (scope === "all" && !yes)
57
+ await confirmOrThrow(`Reset ALL data of ${target}? The Project link and credentials are kept. [y/N] `);
58
+ try {
59
+ await admin.tenant.reset({ scope, activeWork: "drain" });
60
+ }
61
+ catch (error) {
62
+ throw new CliError(error instanceof Error ? error.message : `Tenant reset failed: ${String(error)}`, 1);
63
+ }
64
+ console.log(`Reset ${target} (${scope}).`);
65
+ }
66
+ async function confirmOrThrow(prompt) {
67
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
68
+ throw new CliError("Confirmation required; pass --yes for non-interactive use.", 2);
69
+ }
70
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
71
+ try {
72
+ const answer = (await rl.question(prompt)).trim().toLowerCase();
73
+ if (answer !== "y" && answer !== "yes") {
74
+ throw new CliError("Cancelled.", 1);
75
+ }
76
+ }
77
+ finally {
78
+ rl.close();
79
+ }
80
+ }
@@ -0,0 +1,58 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ import type { Credential } from "@earendil-works/pi-ai";
3
+ import type { ManagementClient } from "@nylorun/admin";
4
+ import { CliError } from "../../errors.js";
5
+ /** Exit 130 on SIGINT, 143 on SIGTERM. */
6
+ export declare class ConfigurationCancelled extends CliError {
7
+ readonly signal: "SIGINT" | "SIGTERM";
8
+ constructor(signal: "SIGINT" | "SIGTERM");
9
+ }
10
+ export type PromptedModel = {
11
+ provider: string;
12
+ model: string;
13
+ baseUrl?: string;
14
+ /** A custom endpoint's context window and output limit, when given. */
15
+ settings?: {
16
+ contextWindow?: number;
17
+ maxTokens?: number;
18
+ };
19
+ auth: Credential;
20
+ };
21
+ export type ModelCatalog = {
22
+ providers: {
23
+ id: string;
24
+ name: string;
25
+ models: {
26
+ id: string;
27
+ name: string;
28
+ }[];
29
+ auth?: {
30
+ apiKey?: unknown;
31
+ oauth?: unknown;
32
+ };
33
+ }[];
34
+ };
35
+ export type Selection = Readonly<{
36
+ provider: string;
37
+ model: string;
38
+ custom?: Readonly<{
39
+ baseUrl: string;
40
+ }>;
41
+ }>;
42
+ /** The Tenant's model catalog (`GET /v1/tenant/models`, Management API). */
43
+ export declare function fetchModelCatalog(admin: ManagementClient): Promise<ModelCatalog>;
44
+ export declare function configureProvider(options?: {
45
+ signal?: AbortSignal;
46
+ root?: string;
47
+ input?: Readable;
48
+ output?: Writable;
49
+ /** Tenant API catalog from GET /v1/tenant/models. */
50
+ catalog?: ModelCatalog;
51
+ /** This installation's stable id, for OAuth flows that need one (OpenAI). */
52
+ deviceId?: () => string;
53
+ }): Promise<PromptedModel>;
54
+ /**
55
+ * A stable id for this CLI installation, created on first use under the Host root.
56
+ * OpenAI's "Sign in with ChatGPT" sends it as the agent host id.
57
+ */
58
+ export declare function installationId(home?: string): string;