nylorun 0.12.2-beta → 0.14.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,36 @@
1
+ import { type ManagementClient } from "@nylorun/admin";
2
+ import { type ProjectCredentials, type ProjectLink as LinkFile } from "@nylorun/core/project";
3
+ /**
4
+ * The Project link (format 3), which `nylorun start` writes and `nylo` only reads with
5
+ * `@nylorun/core/project`'s reader. `tenantId` is information: the installation serves one
6
+ * Tenant, so nothing selects it.
7
+ */
8
+ export type ProjectLink = LinkFile & {
9
+ format: 3;
10
+ };
11
+ /** The Project's link; one of an older nylorun (formats 0 to 2) is refused. */
12
+ export declare function readLink(projectRoot: string): Promise<ProjectLink | undefined>;
13
+ /**
14
+ * The Project's credentials (mode 0600), which `nylorun start` writes beside the link: the
15
+ * application key and its principal, and the management key and its principal when it issued
16
+ * one. A format 0 file's legacy `executors` map is ignored.
17
+ */
18
+ export declare function readCredentials(projectRoot: string): Promise<ProjectCredentials | undefined>;
19
+ /** The installation `nylo` acts on: a Runtime URL, an application key and a management key. */
20
+ export interface LinkedConnection {
21
+ url: string;
22
+ /** The application key (Runtime API); absent when only a management key is set. */
23
+ key?: string;
24
+ /** The management key (Management API), when there is one. */
25
+ managementKey?: string;
26
+ /** The Project link it came from, when it came from one. */
27
+ link?: ProjectLink;
28
+ }
29
+ /**
30
+ * The Project's link and credentials, else `NYLORUN_RUNTIME_URL` with `NYLORUN_SERVER_KEY`, or
31
+ * `NYLORUN_MANAGEMENT_KEY`, or both; `NYLORUN_MANAGEMENT_KEY` also stands in for a Project
32
+ * credentials file without one. The installation serves one Tenant, so nothing names it.
33
+ */
34
+ export declare function linkedConnection(projectRoot: string, env?: Readonly<Record<string, string | undefined>>): Promise<LinkedConnection>;
35
+ /** The Management API client of `connection`; refused without a management key. */
36
+ export declare function managementClient(connection: LinkedConnection): ManagementClient;
@@ -0,0 +1,80 @@
1
+ import { chmod } from "node:fs/promises";
2
+ import { createManagementClient } from "@nylorun/admin";
3
+ import { ProjectFileError, projectCredentialsPath, projectLinkPath, readProjectCredentials, readProjectLink, } from "@nylorun/core/project";
4
+ import { CliError } from "../../errors.js";
5
+ /** The Project's link; one of an older nylorun (formats 0 to 2) is refused. */
6
+ export async function readLink(projectRoot) {
7
+ const path = projectLinkPath(projectRoot);
8
+ let link;
9
+ try {
10
+ link = readProjectLink(projectRoot);
11
+ }
12
+ catch (error) {
13
+ if (!(error instanceof ProjectFileError))
14
+ throw error;
15
+ throw new CliError(error.reason === "newer"
16
+ ? `Project link at ${path} has an unsupported format. Upgrade nylorun.`
17
+ : `Invalid Project link at ${path}. Remove .nylorun/link.json and run "npx nylorun start".`, 1);
18
+ }
19
+ if (link === undefined)
20
+ return undefined;
21
+ if (link.format !== 3)
22
+ throw new CliError(`The Project link at ${path} is from an older nylorun. Run "npx nylorun start" in this project to link it again.`, 1);
23
+ return { ...link, format: 3 };
24
+ }
25
+ /**
26
+ * The Project's credentials (mode 0600), which `nylorun start` writes beside the link: the
27
+ * application key and its principal, and the management key and its principal when it issued
28
+ * one. A format 0 file's legacy `executors` map is ignored.
29
+ */
30
+ export async function readCredentials(projectRoot) {
31
+ const path = projectCredentialsPath(projectRoot);
32
+ let credentials;
33
+ try {
34
+ credentials = readProjectCredentials(projectRoot);
35
+ }
36
+ catch (error) {
37
+ if (!(error instanceof ProjectFileError))
38
+ throw error;
39
+ throw new CliError(error.reason === "newer"
40
+ ? `Project credentials at ${path} have an unsupported format. Upgrade nylorun.`
41
+ : `Invalid Project credentials at ${path}. Remove .nylorun/credentials.json and run "npx nylorun start".`, 1);
42
+ }
43
+ if (credentials)
44
+ await chmod(path, 0o600);
45
+ return credentials;
46
+ }
47
+ /**
48
+ * The Project's link and credentials, else `NYLORUN_RUNTIME_URL` with `NYLORUN_SERVER_KEY`, or
49
+ * `NYLORUN_MANAGEMENT_KEY`, or both; `NYLORUN_MANAGEMENT_KEY` also stands in for a Project
50
+ * credentials file without one. The installation serves one Tenant, so nothing names it.
51
+ */
52
+ export async function linkedConnection(projectRoot, env = process.env) {
53
+ const link = await readLink(projectRoot);
54
+ const credentials = await readCredentials(projectRoot);
55
+ const managementKey = env.NYLORUN_MANAGEMENT_KEY?.trim();
56
+ if (link && credentials) {
57
+ const management = credentials.managementKey ?? managementKey;
58
+ return {
59
+ url: link.hostUrl,
60
+ key: credentials.applicationKey,
61
+ ...(management ? { managementKey: management } : {}),
62
+ link,
63
+ };
64
+ }
65
+ const url = env.NYLORUN_RUNTIME_URL?.trim();
66
+ const key = env.NYLORUN_SERVER_KEY?.trim();
67
+ if (url && (key || managementKey))
68
+ return {
69
+ url: url.replace(/\/$/, ""),
70
+ ...(key ? { key } : {}),
71
+ ...(managementKey ? { managementKey } : {}),
72
+ };
73
+ throw new CliError(`No Project link in ${projectRoot}. Run "npx nylorun start" in this project to start its Tenant and link it, or set NYLORUN_RUNTIME_URL and NYLORUN_SERVER_KEY (or NYLORUN_MANAGEMENT_KEY).`, 1);
74
+ }
75
+ /** The Management API client of `connection`; refused without a management key. */
76
+ export function managementClient(connection) {
77
+ if (!connection.managementKey)
78
+ throw new CliError(`No management key for the Tenant at ${connection.url}: run npx nylorun start, or set NYLORUN_MANAGEMENT_KEY.`, 1);
79
+ return createManagementClient({ url: connection.url, key: connection.managementKey });
80
+ }
@@ -0,0 +1,3 @@
1
+ /** Two `export` lines for `nylo env` (was `nylorun status --env`, F2-7). */
2
+ export declare function printLinkedEnvExports(projectRoot?: string): Promise<void>;
3
+ export declare function shellQuote(value: string): string;
@@ -0,0 +1,19 @@
1
+ import { findProjectRoot } from "@nylorun/core/project";
2
+ import { readCredentials, readLink } from "./connection.js";
3
+ /** Two `export` lines for `nylo env` (was `nylorun status --env`, F2-7). */
4
+ export async function printLinkedEnvExports(projectRoot = process.cwd()) {
5
+ const root = findProjectRoot(projectRoot) ?? projectRoot;
6
+ const link = await readLink(root);
7
+ const credentials = await readCredentials(root);
8
+ if (!link || !credentials) {
9
+ console.log('# No Project link in this directory. Run "npx nylorun start" in this project to start its Tenant and link it.');
10
+ return;
11
+ }
12
+ console.log(`export NYLORUN_RUNTIME_URL=${shellQuote(link.hostUrl)}`);
13
+ console.log(`export NYLORUN_SERVER_KEY=${shellQuote(credentials.applicationKey)}`);
14
+ }
15
+ export function shellQuote(value) {
16
+ if (/^[A-Za-z0-9_./:-]+$/.test(value))
17
+ return value;
18
+ return `'${value.replace(/'/g, `'\\''`)}'`;
19
+ }
@@ -0,0 +1,8 @@
1
+ /** Both commands refuse native Windows before acting; WSL2 runs them. */
2
+ export declare function refuseNativeWindows(): void;
3
+ /**
4
+ * Run a command (`nylorun` or `nylo`) and exit once stdout and stderr are flushed: with
5
+ * `process.exitCode` (0 when unset), or after printing a failure's message with its
6
+ * `CliError` exit code (else 1).
7
+ */
8
+ export declare function runCommand(main: () => Promise<void>): void;
@@ -0,0 +1,23 @@
1
+ import { CliError } from "./errors.js";
2
+ /** Both commands refuse native Windows before acting; WSL2 runs them. */
3
+ export function refuseNativeWindows() {
4
+ if (process.platform === "win32")
5
+ 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);
6
+ }
7
+ /**
8
+ * Run a command (`nylorun` or `nylo`) and exit once stdout and stderr are flushed: with
9
+ * `process.exitCode` (0 when unset), or after printing a failure's message with its
10
+ * `CliError` exit code (else 1).
11
+ */
12
+ export function runCommand(main) {
13
+ void main().then(() => finish(process.exitCode === undefined ? 0 : Number(process.exitCode)), (error) => {
14
+ console.error(error instanceof Error ? error.message : String(error));
15
+ return finish(error instanceof CliError ? error.exitCode : 1);
16
+ });
17
+ }
18
+ async function finish(code) {
19
+ process.exitCode = code;
20
+ for (const stream of [process.stdout, process.stderr])
21
+ await new Promise((resolve) => stream.write("", () => resolve()));
22
+ process.exit(code);
23
+ }
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** Exit codes are part of the CLI contract; see cli/README.md. */
1
+ /** Exit codes are part of both commands' contract; see cli/nylorun/README.md ("Exit codes"). */
2
2
  export declare class CliError extends Error {
3
3
  readonly exitCode: number;
4
4
  constructor(message: string, exitCode?: number);
package/dist/errors.js CHANGED
@@ -1,4 +1,4 @@
1
- /** Exit codes are part of the CLI contract; see cli/README.md. */
1
+ /** Exit codes are part of both commands' contract; see cli/nylorun/README.md ("Exit codes"). */
2
2
  export class CliError extends Error {
3
3
  exitCode;
4
4
  constructor(message, exitCode = 1) {
package/dist/nylo.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "./baseline.js";
package/dist/nylo.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import "./baseline.js";
3
+ import { runCommand } from "./command.js";
4
+ // `nylo`, the Runtime client (client/). `nylorun`'s entry never imports it, so `npx nylorun start`
5
+ // loads none of it, nor @nylorun/admin.
6
+ runCommand(async () => {
7
+ const { main } = await import("./client/cli.js");
8
+ await main(process.argv.slice(2));
9
+ });
@@ -31,7 +31,8 @@ export interface KeyCredentials {
31
31
  export declare function readProjectCredentials(projectRoot: string): Promise<KeyCredentials | undefined>;
32
32
  /**
33
33
  * A credentials file (format 0 or 1): the Project's, or one the nylorun commands keep in the
34
- * Host root. Undefined when absent or unreadable, so `start` replaces it.
34
+ * Host root. Undefined when absent or unreadable, so `start` replaces it; a newer format is an
35
+ * error, as for the link, so this release never rewrites it in its own.
35
36
  */
36
37
  export declare function readCredentialsFile(path: string): Promise<KeyCredentials | undefined>;
37
38
  /** `.nylorun/credentials.json` (format 1, mode 0600). */
@@ -49,14 +49,17 @@ export async function readProjectCredentials(projectRoot) {
49
49
  }
50
50
  /**
51
51
  * A credentials file (format 0 or 1): the Project's, or one the nylorun commands keep in the
52
- * Host root. Undefined when absent or unreadable, so `start` replaces it.
52
+ * Host root. Undefined when absent or unreadable, so `start` replaces it; a newer format is an
53
+ * error, as for the link, so this release never rewrites it in its own.
53
54
  */
54
55
  export async function readCredentialsFile(path) {
55
56
  let value;
56
57
  try {
57
58
  value = readCredentials(path);
58
59
  }
59
- catch {
60
+ catch (error) {
61
+ if (error instanceof ProjectFileError && error.reason === "newer")
62
+ throw new CliError(`Newer credentials at ${error.path}. Upgrade nylorun, or remove the file and run "npx nylorun start" again.`, 1);
60
63
  return undefined;
61
64
  }
62
65
  if (!value)
@@ -561,7 +561,7 @@ async function linkProject(ctx, started, tenantId) {
561
561
  deps.err(`Seeded the Tenant from .env: ${set.join(", ")}.`);
562
562
  }
563
563
  catch (error) {
564
- deps.err(`Warning: could not seed the Tenant from .env (${error instanceof Error ? error.message : String(error)}). Set the model in Studio or with "npx @nylorun/cli configure".`);
564
+ deps.err(`Warning: could not seed the Tenant from .env (${error instanceof Error ? error.message : String(error)}). Set the model in Studio or with "npx -p nylorun nylo configure".`);
565
565
  }
566
566
  }
567
567
  const START_USAGE = "nylorun start [--tenant <name>] [--no-link] [--no-studio] [--allow-downgrade] [--studio-embed-origin <origin>]... [--studio-embed-origin-reset] [--restate-ui]";
@@ -238,7 +238,16 @@ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness
238
238
  NYLORUN_HARNESS: \${NYLORUN_HARNESS:-remote}
239
239
  NYLORUN_HARNESS_LISTEN_PORT: "4200"
240
240
  NYLORUN_HARNESS_ALLOWED_HOSTS: runtime:4200${sandboxes ? SANDBOXES_HARNESS_HOST : ""}
241
- NYLORUN_HARNESS_TOKEN: \${NYLORUN_HARNESS_TOKEN:?run nylorun start}${sandboxes ? SANDBOXES_RUNTIME_ENV : ""}${identity ? IDENTITY_RUNTIME_ENV : ""}
241
+ NYLORUN_HARNESS_TOKEN: \${NYLORUN_HARNESS_TOKEN:?run nylorun start}
242
+ # Traces (R7.2): from the shell that runs \`nylorun start\`. Unset, they are
243
+ # empty and the Runtime exports nothing. A collector on this machine is host.docker.internal.
244
+ OTEL_EXPORTER_OTLP_ENDPOINT: \${OTEL_EXPORTER_OTLP_ENDPOINT:-}
245
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: \${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-}
246
+ OTEL_EXPORTER_OTLP_HEADERS: \${OTEL_EXPORTER_OTLP_HEADERS:-}
247
+ OTEL_SERVICE_NAME: \${OTEL_SERVICE_NAME:-}
248
+ OTEL_RESOURCE_ATTRIBUTES: \${OTEL_RESOURCE_ATTRIBUTES:-}
249
+ OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT: \${OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT:-}
250
+ NYLORUN_TRACE_CONTENT: \${NYLORUN_TRACE_CONTENT:-}${sandboxes ? SANDBOXES_RUNTIME_ENV : ""}${identity ? IDENTITY_RUNTIME_ENV : ""}
242
251
  extra_hosts:
243
252
  host.docker.internal: host-gateway # the Docker host, also on Linux Docker Engine
244
253
  volumes:
@@ -1,9 +1,11 @@
1
1
  import type { McpPreview } from "@nylorun/core/contracts";
2
2
  import { type StackDeps } from "./commands.js";
3
- export declare const mcpUsage = " mcp inspect <url> [--server <name>] [--vault <id>] [--sse] [--tenant <name>] [--json]\n list a remote MCP server's tools with the installation vault's\n credential for <url>: model names, schema sizes, renames, or\n that it needs a person's sign-in (the Runtime calls no tool)";
3
+ export declare const mcpUsage = " mcp inspect <url> [--server <name>] [--environment <id>] [--sse] [--tenant <name>] [--json]\n list a remote MCP server's tools, with Environment <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)";
4
+ /** What `--vault` says since protocol 11. */
5
+ export declare const MCP_VAULT_REMOVED = "--vault was removed in protocol 11: a credential is a secret of an Environment, in its own vault. Pass --environment <id> to send that Environment's credential for the URL.";
4
6
  /** What the removed `connect` subcommand says since protocol 10. */
5
- export declare const MCP_CONNECT_REMOVED = "The connect subcommand of nylorun mcp was removed with MCP OAuth (protocol 10): add the server's key to an installation vault as a bearer or headers credential (Studio's Credentials page, or @nylorun/admin), or reach a server that needs a person's sign-in through a gateway (a credential with via). See its tools with nylorun mcp inspect <url>.";
7
+ export declare const MCP_CONNECT_REMOVED: string;
6
8
  /** A preview as a person reads it: the server, a table of tools, the renames, or the sign-in hint. */
7
- export declare function previewLines(preview: McpPreview): string[];
9
+ export declare function previewLines(preview: McpPreview, environmentId?: string): string[];
8
10
  /** `nylorun mcp <inspect|connect> ...` */
9
11
  export declare function mcpCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
package/dist/stack/mcp.js CHANGED
@@ -2,30 +2,39 @@
2
2
  * `nylorun mcp inspect <url>`: a remote MCP server's tools before an agent names it (R2b C12),
3
3
  * through the running local Tenant's Management API (`POST /v1/tenant/mcp/preview`) with the
4
4
  * Project link's management key, else the Host root's (`runningTenantApi`). It never starts the
5
- * Tenant, and the Runtime never calls a tool. Its `connect` subcommand (MCP OAuth) was removed
6
- * in protocol 10, and says so.
5
+ * Tenant, and the Runtime never calls a tool. `--environment <id>` sends that Environment's
6
+ * credential for the URL (protocol 11; it replaced `--vault`); an Environment's secrets are
7
+ * written on the Runtime API (`client.environments.secrets.put` of `@nylorun/agents`). Its
8
+ * `connect` subcommand (MCP OAuth) was removed in protocol 10, and says so.
7
9
  */
8
10
  import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "@nylorun/core/compatibility";
9
11
  import { CliError } from "../errors.js";
10
12
  import { parseStackFlags, runningTenantApi } from "./commands.js";
11
- export const mcpUsage = ` mcp inspect <url> [--server <name>] [--vault <id>] [--sse] [--tenant <name>] [--json]
12
- list a remote MCP server's tools with the installation vault's
13
- credential for <url>: model names, schema sizes, renames, or
14
- that it needs a person's sign-in (the Runtime calls no tool)`;
15
- const INSPECT_USAGE = "nylorun mcp inspect <url> [--server <name>] [--vault <id>] [--sse] [--tenant <name>] [--json]";
13
+ export const mcpUsage = ` mcp inspect <url> [--server <name>] [--environment <id>] [--sse] [--tenant <name>] [--json]
14
+ list a remote MCP server's tools, with Environment <id>'s
15
+ credential for <url> (none without it): model names, schema
16
+ sizes, renames, or that it needs a person's sign-in (the
17
+ Runtime calls no tool)`;
18
+ const INSPECT_USAGE = "nylorun mcp inspect <url> [--server <name>] [--environment <id>] [--sse] [--tenant <name>] [--json]";
19
+ /** Where a credential for an MCP server goes since protocol 11. */
20
+ const SECRET_HINT = "write it as a bearer or headers secret of an Environment (client.environments.secrets.put of @nylorun/agents, or Studio)";
21
+ /** What `--vault` says since protocol 11. */
22
+ export const MCP_VAULT_REMOVED = "--vault was removed in protocol 11: a credential is a secret of an Environment, in its own vault. Pass --environment <id> to send that Environment's credential for the URL.";
16
23
  /** What the removed `connect` subcommand says since protocol 10. */
17
- export const MCP_CONNECT_REMOVED = "The connect subcommand of nylorun mcp was removed with MCP OAuth (protocol 10): add the server's key to an installation vault as a bearer or headers credential (Studio's Credentials page, or @nylorun/admin), or reach a server that needs a person's sign-in through a gateway (a credential with via). See its tools with nylorun mcp inspect <url>.";
24
+ export const MCP_CONNECT_REMOVED = `The connect subcommand of nylorun mcp was removed with MCP OAuth (protocol 10): ${SECRET_HINT.replace("write it", "write the server's key")}, or reach a server that needs a person's sign-in through a gateway (a secret with via). See its tools with nylorun mcp inspect <url> --environment <id>.`;
18
25
  /** The preview takes up to 15 s in the Runtime; the hop gets a margin. */
19
26
  const PREVIEW_TIMEOUT_MS = 30_000;
20
27
  const DESCRIPTION_CHARS = 60;
21
28
  const usageError = (message) => new CliError(message, 2);
22
29
  async function inspect(deps, args) {
23
- const flags = parseStackFlags(args, { booleans: ["--json", "--sse"], values: ["--server", "--vault", "--tenant"] }, INSPECT_USAGE);
30
+ if (args.some((arg) => arg === "--vault" || arg.startsWith("--vault=")))
31
+ throw usageError(MCP_VAULT_REMOVED);
32
+ const flags = parseStackFlags(args, { booleans: ["--json", "--sse"], values: ["--server", "--environment", "--tenant"] }, INSPECT_USAGE);
24
33
  if (flags.rest.length !== 1)
25
34
  throw usageError(`Usage: ${INSPECT_USAGE}`);
26
35
  const url = flags.rest[0];
27
36
  const name = flags.values.get("--server");
28
- const vaultId = flags.values.get("--vault");
37
+ const environmentId = flags.values.get("--environment");
29
38
  const tenant = flags.values.get("--tenant");
30
39
  const api = await runningTenantApi(deps, tenant === undefined ? {} : { name: tenant });
31
40
  const response = await deps.fetch(`${api.runtimeUrl}/v1/tenant/mcp/preview`, {
@@ -40,7 +49,7 @@ async function inspect(deps, args) {
40
49
  url,
41
50
  ...(flags.booleans.has("--sse") ? { type: "sse" } : {}),
42
51
  ...(name === undefined ? {} : { name }),
43
- ...(vaultId === undefined ? {} : { vaultId }),
52
+ ...(environmentId === undefined ? {} : { environmentId }),
44
53
  }),
45
54
  redirect: "error",
46
55
  signal: AbortSignal.timeout(PREVIEW_TIMEOUT_MS),
@@ -60,15 +69,17 @@ async function inspect(deps, args) {
60
69
  deps.out(JSON.stringify(preview, null, 2));
61
70
  return 0;
62
71
  }
63
- for (const line of previewLines(preview))
72
+ for (const line of previewLines(preview, environmentId))
64
73
  deps.out(line);
65
74
  return 0;
66
75
  }
67
76
  /** A preview as a person reads it: the server, a table of tools, the renames, or the sign-in hint. */
68
- export function previewLines(preview) {
77
+ export function previewLines(preview, environmentId) {
69
78
  const credential = preview.credentialSent
70
- ? "with the installation vault's credential"
71
- : "with no credential (no installation vault holds one for this URL)";
79
+ ? `with Environment ${environmentId ?? "(unnamed)"}'s credential`
80
+ : environmentId === undefined
81
+ ? "with no credential (pass --environment <id> to send an Environment's)"
82
+ : `with no credential (Environment ${environmentId} holds none for this URL)`;
72
83
  if (preview.authRequired) {
73
84
  const metadata = preview.authRequired.resourceMetadata;
74
85
  const servers = Array.isArray(metadata?.authorization_servers)
@@ -76,13 +87,14 @@ export function previewLines(preview) {
76
87
  : [];
77
88
  return [
78
89
  preview.credentialSent
79
- ? `The MCP server at ${preview.url} answered 401: it rejected the installation vault's credential.`
90
+ ? `The MCP server at ${preview.url} answered 401: it rejected Environment ${environmentId ?? "(unnamed)"}'s credential.`
80
91
  : `The MCP server at ${preview.url} answered 401: it needs a credential.`,
81
92
  ...(servers.length > 0
82
93
  ? [`It signs people in with ${servers.join(", ")} (${preview.authRequired.resourceMetadataUrl}).`]
83
94
  : []),
84
- "Nylorun holds no OAuth client: add the server's API key to an installation vault, or reach it",
85
- "through a gateway that keeps each person's sign-in (a credential with via and an identity header).",
95
+ `Nylorun holds no OAuth client: ${SECRET_HINT.replace("write it", "write the server's API key")},`,
96
+ "and pass --environment <id>; or reach it through a gateway that keeps each person's sign-in",
97
+ "(a secret with via and an identity header).",
86
98
  ];
87
99
  }
88
100
  const info = preview.serverInfo;
@@ -1,4 +1,4 @@
1
1
  import { type StackDeps } from "./commands.js";
2
- export declare const sandboxUsage = " sandbox ls [--tenant <name>] [--label <key=value>]... [--json]\n the Tenant's sandboxes: id, kind, state, attached sessions and labels\n sandbox rm <id> [--tenant <name>] delete a sandbox and its files (refused while a turn runs in it)\n sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--no-pull]\n run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox v1.0.5)\n sandbox disable [--tenant <name>] [--delete-namespace]\n stop the sandboxes service and forget the cluster\n sandbox status [--tenant <name>] [--json]\n the cluster, the sandboxes service's readiness and what it reports";
2
+ export declare const sandboxUsage = " sandbox ls [--tenant <name>] [--label <key=value>]... [--json]\n the Tenant's Environments: id, kind, state, sessions and labels\n sandbox rm <id> [--tenant <name>] delete an Environment: its sandbox, files and vault\n (refused while a turn runs in it)\n sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--no-pull]\n run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox v1.0.5)\n sandbox disable [--tenant <name>] [--delete-namespace]\n stop the sandboxes service and forget the cluster\n sandbox status [--tenant <name>] [--json]\n the cluster, the sandboxes service's readiness and what it reports";
3
3
  /** `nylorun sandbox <ls|rm|enable|disable|status> ...` */
4
4
  export declare function sandboxCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * `nylorun sandbox ls | rm`: the running local Tenant's sandbox resources (Host feature
3
- * `sandboxes`), through its Tenant API with an operator key (`runningTenantApi`). Neither starts the
4
- * Tenant. `nylorun sandbox enable|disable|status` (sandbox pods on a cluster, F7.2) live in
5
- * `../sandbox/commands.ts`.
2
+ * `nylorun sandbox ls | rm`: the running local Tenant's Environments (protocol 11: each holds one
3
+ * sandbox and one vault), through its Runtime API with the application key (`runningTenantApi`).
4
+ * Neither starts the Tenant. `nylorun sandbox enable|disable|status` (sandbox pods on a cluster,
5
+ * F7.2) live in `../sandbox/commands.ts`.
6
6
  */
7
7
  import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "@nylorun/core/compatibility";
8
8
  import { CliError } from "../errors.js";
@@ -10,8 +10,9 @@ import { runSandboxCommand, sandboxUsage as clusterUsage } from "../sandbox/comm
10
10
  import { spawnKubectl } from "../sandbox/kubectl.js";
11
11
  import { parseStackFlags, runningTenantApi } from "./commands.js";
12
12
  export const sandboxUsage = ` sandbox ls [--tenant <name>] [--label <key=value>]... [--json]
13
- the Tenant's sandboxes: id, kind, state, attached sessions and labels
14
- sandbox rm <id> [--tenant <name>] delete a sandbox and its files (refused while a turn runs in it)
13
+ the Tenant's Environments: id, kind, state, sessions and labels
14
+ sandbox rm <id> [--tenant <name>] delete an Environment: its sandbox, files and vault
15
+ (refused while a turn runs in it)
15
16
  ${clusterUsage}`;
16
17
  const usageError = (message) => new CliError(message, 2);
17
18
  async function call(deps, api, method, path) {
@@ -26,8 +27,8 @@ async function call(deps, api, method, path) {
26
27
  signal: AbortSignal.timeout(15_000),
27
28
  });
28
29
  const body = (await response.json().catch(() => ({})));
29
- if (response.status === 404 && path === "/v1/sandboxes")
30
- throw new CliError(`Tenant ${api.name} runs a Runtime without sandboxes. Update it: npx nylorun@latest start.`, 1);
30
+ if (response.status === 404 && /^\/v1\/environments(\?|$)/.test(path))
31
+ throw new CliError(`Tenant ${api.name} runs a Runtime without Environments. Update it: npx nylorun@latest start.`, 1);
31
32
  if (!response.ok && response.status !== 404)
32
33
  throw new CliError(`${method} ${path} returned ${response.status}${typeof body.message === "string" ? `: ${body.message}` : ""}`, 1);
33
34
  return { status: response.status, body };
@@ -50,24 +51,24 @@ async function ls(deps, args) {
50
51
  const name = flags.values.get("--tenant");
51
52
  const api = await runningTenantApi(deps, name === undefined ? {} : { name });
52
53
  const search = query.toString();
53
- const { body } = await call(deps, api, "GET", `/v1/sandboxes${search ? `?${search}` : ""}`);
54
- const sandboxes = (body.sandboxes ?? []);
54
+ const { body } = await call(deps, api, "GET", `/v1/environments${search ? `?${search}` : ""}`);
55
+ const environments = (body.environments ?? []);
55
56
  if (flags.booleans.has("--json")) {
56
- deps.out(JSON.stringify(sandboxes, null, 2));
57
+ deps.out(JSON.stringify(environments, null, 2));
57
58
  return 0;
58
59
  }
59
- if (sandboxes.length === 0) {
60
- deps.out(`No sandboxes on Tenant ${api.name}.`);
60
+ if (environments.length === 0) {
61
+ deps.out(`No Environments on Tenant ${api.name}.`);
61
62
  return 0;
62
63
  }
63
64
  const rows = [
64
65
  ["ID", "KIND", "STATE", "SESSIONS", "LABELS"],
65
- ...sandboxes.map((sandbox) => [
66
- sandbox.id,
67
- sandbox.kind,
68
- sandbox.state,
69
- String(sandbox.sessions.length),
70
- labelsText(sandbox.labels),
66
+ ...environments.map((environment) => [
67
+ environment.id,
68
+ environment.kind,
69
+ environment.state,
70
+ String(environment.sessions.length),
71
+ labelsText(environment.labels),
71
72
  ]),
72
73
  ];
73
74
  const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => row[column].length)));
@@ -85,12 +86,12 @@ async function rm(deps, args) {
85
86
  const id = flags.rest[0];
86
87
  const name = flags.values.get("--tenant");
87
88
  const api = await runningTenantApi(deps, name === undefined ? {} : { name });
88
- const { status, body } = await call(deps, api, "DELETE", `/v1/sandboxes/${encodeURIComponent(id)}`);
89
+ const { status, body } = await call(deps, api, "DELETE", `/v1/environments/${encodeURIComponent(id)}`);
89
90
  if (status === 404 || body.deleted !== true) {
90
- deps.err(`No sandbox ${id} on Tenant ${api.name}.`);
91
+ deps.err(`No Environment ${id} on Tenant ${api.name}.`);
91
92
  return 1;
92
93
  }
93
- deps.out(`Deleted sandbox ${id} and its files.`);
94
+ deps.out(`Deleted Environment ${id}: its sandbox, files and vault.`);
94
95
  return 0;
95
96
  }
96
97
  /** `nylorun sandbox <ls|rm|enable|disable|status> ...` */
@@ -1,7 +1,7 @@
1
- /** An image this CLI release pins in `cli/package.json` `nylorun` (D7). */
1
+ /** An image this CLI release pins in its `package.json` `nylorun` (D7). */
2
2
  export type PinnedImage = "runtime" | "studio";
3
3
  /**
4
- * The version this CLI release pins for an image, from `cli/package.json`
4
+ * The version this CLI release pins for an image, from its `package.json`
5
5
  * `nylorun.runtime` or `nylorun.studio`: the tag of `ghcr.io/nylorun/<name>`
6
6
  * a local Tenant runs unless `NYLORUN_RUNTIME_IMAGE` / `NYLORUN_STUDIO_IMAGE`
7
7
  * overrides it. The release tooling writes both pins.
@@ -3,11 +3,11 @@ import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { CliError } from "../errors.js";
5
5
  function cliPackageJsonPath() {
6
- // src/stack/*.ts and dist/stack/*.js both sit two levels below cli/.
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
9
  /**
10
- * The version this CLI release pins for an image, from `cli/package.json`
10
+ * The version this CLI release pins for an image, from its `package.json`
11
11
  * `nylorun.runtime` or `nylorun.studio`: the tag of `ghcr.io/nylorun/<name>`
12
12
  * a local Tenant runs unless `NYLORUN_RUNTIME_IMAGE` / `NYLORUN_STUDIO_IMAGE`
13
13
  * overrides it. The release tooling writes both pins.
@@ -22,7 +22,7 @@ export function pinnedVersion(name) {
22
22
  }
23
23
  const version = raw?.nylorun?.[name];
24
24
  if (typeof version !== "string" || version.trim() === "") {
25
- throw new CliError(`cli/package.json is missing "nylorun.${name}" (the pinned ${name} image version).`, 1);
25
+ throw new CliError(`nylorun's package.json is missing "nylorun.${name}" (the pinned ${name} image version).`, 1);
26
26
  }
27
27
  return version;
28
28
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "nylorun",
3
- "version": "0.12.2-beta",
3
+ "version": "0.14.0-beta",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
- "description": "Set up and run local Nylorun Tenants (Runtime and Studio) with Docker Compose",
6
+ "description": "Set up and run local Nylorun Tenants (Runtime and Studio) with Docker Compose (nylorun), and the Runtime client of the linked installation (nylo)",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/nylorun/agents.git",
10
- "directory": "nylorun"
10
+ "directory": "cli/nylorun"
11
11
  },
12
12
  "files": [
13
13
  "dist",
@@ -23,25 +23,29 @@
23
23
  "node": ">=24.0.0"
24
24
  },
25
25
  "scripts": {
26
- "build": "npm run clean && node ../scripts/check-typescript-version.mjs && tsc -p tsconfig.json",
26
+ "build": "npm run clean && node ../../scripts/check-typescript-version.mjs && tsc -p tsconfig.json",
27
27
  "clean": "node -e \"import('node:fs').then(({rmSync})=>rmSync('dist',{recursive:true,force:true}))\"",
28
28
  "check": "npm run build && npm test && node scripts/check-package.mjs",
29
29
  "test": "vitest run --passWithNoTests",
30
30
  "prepack": "npm run build"
31
31
  },
32
32
  "dependencies": {
33
- "@nylorun/core": "0.19.0-beta"
33
+ "@nylorun/core": "0.20.0-beta",
34
+ "@nylorun/admin": "0.16.0-beta"
34
35
  },
35
36
  "devDependencies": {
37
+ "@earendil-works/pi-ai": "0.99.1",
38
+ "@nylorun/agents": "file:../../sdks/agents",
36
39
  "typescript": "npm:@typescript/typescript6@^6.0.2",
37
- "@types/node": "^26.5.0",
40
+ "@types/node": "^26.6.4",
38
41
  "vitest": "^5.0.2"
39
42
  },
40
43
  "bin": {
41
- "nylorun": "dist/cli.js"
44
+ "nylorun": "dist/cli.js",
45
+ "nylo": "dist/nylo.js"
42
46
  },
43
47
  "nylorun": {
44
- "runtime": "0.22.0-beta",
45
- "studio": "0.23.2-beta"
48
+ "runtime": "0.24.0-beta",
49
+ "studio": "0.24.0-beta"
46
50
  }
47
51
  }