@nylorun/cli 0.2.1-beta → 0.3.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +87 -40
  3. package/dist/cli.d.ts +1 -1
  4. package/dist/cli.js +132 -220
  5. package/dist/dev-entry.d.ts +5 -1
  6. package/dist/dev-entry.js +7 -6
  7. package/dist/dev.d.ts +45 -4
  8. package/dist/dev.js +245 -32
  9. package/dist/doctor.d.ts +25 -3
  10. package/dist/doctor.js +104 -16
  11. package/dist/environment.d.ts +4 -1
  12. package/dist/environment.js +12 -3
  13. package/dist/launcher.d.ts +5 -5
  14. package/dist/launcher.js +6 -3
  15. package/dist/model/configure.d.ts +30 -0
  16. package/dist/model/configure.js +117 -11
  17. package/dist/model/host-model.d.ts +5 -3
  18. package/dist/model/host-model.js +36 -15
  19. package/dist/project/attach.d.ts +32 -0
  20. package/dist/project/attach.js +241 -0
  21. package/dist/project/create-tenant.d.ts +26 -0
  22. package/dist/project/create-tenant.js +65 -0
  23. package/dist/project/credentials.d.ts +18 -0
  24. package/dist/project/credentials.js +64 -0
  25. package/dist/project/link.d.ts +20 -0
  26. package/dist/project/link.js +77 -0
  27. package/dist/project/root.d.ts +9 -0
  28. package/dist/project/root.js +51 -0
  29. package/dist/project/seed.d.ts +19 -0
  30. package/dist/project/seed.js +94 -0
  31. package/dist/runtime/baseline.d.ts +2 -0
  32. package/dist/runtime/baseline.js +11 -0
  33. package/dist/runtime/commands.d.ts +16 -0
  34. package/dist/runtime/commands.js +292 -0
  35. package/dist/runtime/ephemeral.d.ts +37 -0
  36. package/dist/runtime/ephemeral.js +84 -0
  37. package/dist/runtime/exit-codes.d.ts +9 -0
  38. package/dist/runtime/exit-codes.js +43 -0
  39. package/dist/runtime/index.d.ts +8 -0
  40. package/dist/runtime/index.js +6 -0
  41. package/dist/runtime/launcher.d.ts +81 -0
  42. package/dist/runtime/launcher.js +243 -0
  43. package/dist/runtime/progress.d.ts +10 -0
  44. package/dist/runtime/progress.js +8 -0
  45. package/dist/runtime/version.d.ts +6 -0
  46. package/dist/runtime/version.js +27 -0
  47. package/dist/tenant/commands.d.ts +4 -0
  48. package/dist/tenant/commands.js +262 -0
  49. package/package.json +6 -3
  50. package/dist/project-runner.d.ts +0 -20
  51. package/dist/project-runner.js +0 -141
  52. package/dist/runtime-host.d.ts +0 -39
  53. package/dist/runtime-host.js +0 -355
  54. package/dist/scope.d.ts +0 -65
  55. package/dist/scope.js +0 -191
package/dist/dev.d.ts CHANGED
@@ -1,6 +1,47 @@
1
+ export interface DevelopOptions {
2
+ entry?: string;
3
+ flags?: readonly string[];
4
+ projectRoot?: string;
5
+ home?: string;
6
+ }
7
+ export type DevelopmentPreflight = {
8
+ tsx: string;
9
+ entry: string;
10
+ ephemeral: boolean;
11
+ localUi: boolean;
12
+ studio: boolean;
13
+ open: boolean;
14
+ };
1
15
  /**
2
- * Everything that can be checked without contacting the host, so a project that cannot run
3
- * never causes a Runtime to be started on its behalf.
16
+ * Everything that can be checked without contacting the Host, so a Project that
17
+ * cannot run never causes a Runtime Host to be started on its behalf.
4
18
  */
5
- export declare function developmentPreflight(args: readonly string[]): string;
6
- export declare function develop(args: readonly string[]): Promise<number>;
19
+ export declare function developmentPreflight(args?: readonly string[]): DevelopmentPreflight;
20
+ /**
21
+ * Resolve `@nylorun/studio` from the Project and call `startStudio`.
22
+ * Used by `nylorun studio` and `nylorun dev --local-ui`.
23
+ */
24
+ export declare function startStudio(options: {
25
+ runtimeUrl: string;
26
+ serverKey: string;
27
+ tenant: {
28
+ id: string;
29
+ name: string;
30
+ };
31
+ open: boolean;
32
+ port?: number;
33
+ /** When true, force local UI. When false/omit, use startStudio default (hosted). */
34
+ localUi?: boolean;
35
+ cacheDir?: string;
36
+ projectRoot?: string;
37
+ }): Promise<{
38
+ address: string;
39
+ launchUrl: string;
40
+ close(): Promise<void>;
41
+ }>;
42
+ /**
43
+ * `nylorun dev [entry]` — D§12 steps 1–4.
44
+ * Spawns `tsx watch <entry>` with the three Project environment variables.
45
+ * `--local-ui` also starts the Studio proxy with a local dashboard.
46
+ */
47
+ export declare function develop(options?: DevelopOptions): Promise<number>;
package/dist/dev.js CHANGED
@@ -1,15 +1,41 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createRequire } from "node:module";
3
- import { join } from "node:path";
4
- import { fileURLToPath } from "node:url";
3
+ import { basename, join, resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { loadProjectEnvironment } from "./environment.js";
6
+ import { attachProject } from "./project/attach.js";
7
+ import { seedTenantFromProject } from "./project/seed.js";
8
+ import { requireProjectRoot } from "./project/root.js";
9
+ import { resolveHome } from "./runtime/launcher.js";
10
+ const DEV_FLAGS = ["--ephemeral", "--local-ui", "--no-studio", "--no-open"];
5
11
  /**
6
- * Everything that can be checked without contacting the host, so a project that cannot run
7
- * never causes a Runtime to be started on its behalf.
12
+ * Everything that can be checked without contacting the Host, so a Project that
13
+ * cannot run never causes a Runtime Host to be started on its behalf.
8
14
  */
9
- export function developmentPreflight(args) {
10
- if (new Set(args).size !== args.length ||
11
- args.some((arg) => !["--no-studio", "--no-open", "--no-autostart"].includes(arg)))
12
- throw new Error("Usage: nylorun dev [--no-studio] [--no-open] [--no-autostart]");
15
+ export function developmentPreflight(args = []) {
16
+ const flags = [];
17
+ let entry;
18
+ for (const arg of args) {
19
+ if (DEV_FLAGS.includes(arg)) {
20
+ flags.push(arg);
21
+ continue;
22
+ }
23
+ if (arg.startsWith("-")) {
24
+ throw new Error("Usage: nylorun dev [entry] [--ephemeral] [--local-ui] [--no-studio] [--no-open]\nDefault entry: src/main.ts");
25
+ }
26
+ if (entry !== undefined) {
27
+ throw new Error("Usage: nylorun dev [entry] [--ephemeral] [--local-ui] [--no-studio] [--no-open]\nDefault entry: src/main.ts");
28
+ }
29
+ entry = arg;
30
+ }
31
+ if (new Set(flags).size !== flags.length) {
32
+ throw new Error("Usage: nylorun dev [entry] [--ephemeral] [--local-ui] [--no-studio] [--no-open]\nDefault entry: src/main.ts");
33
+ }
34
+ const localUi = flags.includes("--local-ui");
35
+ const noStudio = flags.includes("--no-studio");
36
+ if (localUi && noStudio) {
37
+ throw new Error("--local-ui cannot be combined with --no-studio.");
38
+ }
13
39
  const require = createRequire(join(process.cwd(), "package.json"));
14
40
  let tsx;
15
41
  try {
@@ -18,48 +44,235 @@ export function developmentPreflight(args) {
18
44
  catch {
19
45
  throw new Error("Install tsx to use nylorun dev");
20
46
  }
21
- if (!args.includes("--no-studio"))
47
+ if (localUi) {
22
48
  try {
23
49
  require.resolve("@nylorun/studio");
24
50
  }
25
51
  catch {
26
- throw new Error("Install @nylorun/studio or use --no-studio");
52
+ throw new Error("Install @nylorun/studio to use the Studio dashboard.");
27
53
  }
28
- return tsx;
29
- }
30
- export async function develop(args) {
31
- const tsx = developmentPreflight(args);
32
- const child = spawn(process.execPath, [
54
+ }
55
+ return {
33
56
  tsx,
34
- "watch",
35
- "--clear-screen=false",
36
- fileURLToPath(new URL("./dev-entry.js", import.meta.url)),
37
- ...args,
38
- ],
39
- // Deliver terminal shutdown once, through the supervisor, instead of twice through the terminal group.
40
- {
41
- stdio: "inherit",
42
- env: process.env,
43
- detached: process.platform !== "win32",
57
+ entry: entry ?? "src/main.ts",
58
+ ephemeral: flags.includes("--ephemeral"),
59
+ localUi,
60
+ studio: localUi,
61
+ open: !flags.includes("--no-open"),
62
+ };
63
+ }
64
+ /**
65
+ * Resolve `@nylorun/studio` from the Project and call `startStudio`.
66
+ * Used by `nylorun studio` and `nylorun dev --local-ui`.
67
+ */
68
+ export async function startStudio(options) {
69
+ const projectRoot = options.projectRoot ?? process.cwd();
70
+ let entry;
71
+ try {
72
+ entry = createRequire(join(projectRoot, "package.json")).resolve("@nylorun/studio");
73
+ }
74
+ catch {
75
+ throw new Error("Install @nylorun/studio to use the Studio dashboard.");
76
+ }
77
+ const studio = await import(pathToFileURL(entry).href);
78
+ const cacheDir = options.cacheDir ?? resolveHome();
79
+ const ui = options.localUi ? "local" : undefined;
80
+ return studio.startStudio({
81
+ runtimeUrl: options.runtimeUrl,
82
+ serverKey: options.serverKey,
83
+ tenant: options.tenant,
84
+ open: options.open,
85
+ cacheDir,
86
+ ...(ui === undefined ? {} : { ui }),
87
+ ...(options.port === undefined ? {} : { port: options.port }),
44
88
  });
45
- let stopping = false;
89
+ }
90
+ /**
91
+ * `nylorun dev [entry]` — D§12 steps 1–4.
92
+ * Spawns `tsx watch <entry>` with the three Project environment variables.
93
+ * `--local-ui` also starts the Studio proxy with a local dashboard.
94
+ */
95
+ export async function develop(options = {}) {
96
+ const normalized = options;
97
+ const projectRoot = normalized.projectRoot ?? requireProjectRoot();
98
+ const previous = process.cwd();
99
+ process.chdir(projectRoot);
100
+ let preflight;
101
+ try {
102
+ preflight = developmentPreflight([
103
+ ...(normalized.entry ? [normalized.entry] : []),
104
+ ...(normalized.flags ?? []),
105
+ ]);
106
+ }
107
+ finally {
108
+ process.chdir(previous);
109
+ }
110
+ if (preflight.ephemeral) {
111
+ const { startEphemeral } = await import("./runtime/ephemeral.js");
112
+ const { writeLink } = await import("./project/link.js");
113
+ const { writeCredentials } = await import("./project/credentials.js");
114
+ const ephemeral = await startEphemeral({
115
+ name: basename(projectRoot),
116
+ });
117
+ try {
118
+ await writeCredentials(projectRoot, {
119
+ applicationKey: ephemeral.applicationKey,
120
+ principalId: "pr_ephemeral",
121
+ });
122
+ await writeLink(projectRoot, {
123
+ hostUrl: ephemeral.url,
124
+ hostId: ephemeral.hostId,
125
+ tenantId: ephemeral.tenant.id,
126
+ });
127
+ const envMap = loadProjectEnvironment(projectRoot);
128
+ await seedTenantFromProject({
129
+ hostUrl: ephemeral.url,
130
+ tenantId: ephemeral.tenant.id,
131
+ applicationKey: ephemeral.applicationKey,
132
+ projectRoot,
133
+ env: envMap,
134
+ });
135
+ return await spawnWatcher({
136
+ projectRoot,
137
+ entry: preflight.entry,
138
+ tsx: preflight.tsx,
139
+ envMap,
140
+ hostUrl: ephemeral.url,
141
+ tenantId: ephemeral.tenant.id,
142
+ applicationKey: ephemeral.applicationKey,
143
+ tenantName: ephemeral.tenant.name,
144
+ hostStarted: true,
145
+ ephemeral: true,
146
+ localUi: preflight.localUi,
147
+ openStudio: preflight.open,
148
+ home: normalized.home,
149
+ });
150
+ }
151
+ finally {
152
+ await ephemeral.close();
153
+ }
154
+ }
155
+ const attached = await attachProject({
156
+ projectRoot,
157
+ ...(normalized.home ? { home: normalized.home } : {}),
158
+ });
159
+ const envMap = loadProjectEnvironment(projectRoot);
160
+ await seedTenantFromProject({
161
+ hostUrl: attached.link.hostUrl,
162
+ tenantId: attached.link.tenantId,
163
+ applicationKey: attached.credentials.applicationKey,
164
+ projectRoot,
165
+ env: envMap,
166
+ });
167
+ return spawnWatcher({
168
+ projectRoot,
169
+ entry: preflight.entry,
170
+ tsx: preflight.tsx,
171
+ envMap,
172
+ hostUrl: attached.link.hostUrl,
173
+ tenantId: attached.link.tenantId,
174
+ applicationKey: attached.credentials.applicationKey,
175
+ tenantName: attached.tenantName,
176
+ hostStarted: attached.hostStarted,
177
+ ephemeral: false,
178
+ localUi: preflight.localUi,
179
+ openStudio: preflight.open,
180
+ home: normalized.home ?? attached.home,
181
+ });
182
+ }
183
+ async function spawnWatcher(options) {
184
+ const entryPath = resolve(options.projectRoot, options.entry);
185
+ const childEnv = {
186
+ ...process.env,
187
+ ...options.envMap,
188
+ NYLORUN_RUNTIME_URL: options.hostUrl,
189
+ NYLORUN_TENANT: options.tenantId,
190
+ NYLORUN_SERVER_KEY: options.applicationKey,
191
+ };
192
+ let studio;
193
+ let studioMode;
194
+ if (options.localUi) {
195
+ studio = await startStudio({
196
+ runtimeUrl: options.hostUrl,
197
+ serverKey: options.applicationKey,
198
+ tenant: { id: options.tenantId, name: options.tenantName },
199
+ open: options.openStudio,
200
+ localUi: true,
201
+ cacheDir: resolveHome(options.home),
202
+ projectRoot: options.projectRoot,
203
+ });
204
+ studioMode = "local";
205
+ }
206
+ // Handle signals before the banner: a supervisor may stop us as soon as it
207
+ // reads it, and an unhandled SIGTERM would kill this process and orphan the
208
+ // detached watcher.
209
+ let child;
210
+ let stopping;
46
211
  const stop = (signal) => {
47
212
  if (stopping)
48
213
  return;
49
- stopping = true;
50
- child.kill(signal);
214
+ stopping = signal;
215
+ child?.kill(signal);
51
216
  };
52
- const interrupt = () => stop("SIGINT"), terminate = () => stop("SIGTERM");
217
+ const interrupt = () => stop("SIGINT");
218
+ const terminate = () => stop("SIGTERM");
53
219
  process.on("SIGINT", interrupt);
54
220
  process.on("SIGTERM", terminate);
55
221
  try {
56
- return await new Promise((resolve, reject) => {
57
- child.once("error", reject);
58
- child.once("exit", (code, signal) => resolve(code ?? (signal === "SIGINT" ? 130 : 143)));
222
+ printBanner({
223
+ hostUrl: options.hostUrl,
224
+ hostStarted: options.hostStarted,
225
+ ephemeral: options.ephemeral,
226
+ tenantName: options.tenantName,
227
+ tenantId: options.tenantId,
228
+ entry: options.entry,
229
+ studioLaunchUrl: studio?.launchUrl,
230
+ studioMode,
231
+ });
232
+ if (stopping)
233
+ return stopping === "SIGINT" ? 130 : 143;
234
+ const watcher = spawn(process.execPath, [options.tsx, "watch", "--clear-screen=false", entryPath], {
235
+ cwd: options.projectRoot,
236
+ stdio: "inherit",
237
+ env: childEnv,
238
+ detached: process.platform !== "win32",
239
+ });
240
+ child = watcher;
241
+ return await new Promise((resolvePromise, reject) => {
242
+ watcher.once("error", reject);
243
+ watcher.once("exit", (code, signal) => resolvePromise(code ?? (signal === "SIGINT" ? 130 : 143)));
59
244
  });
60
245
  }
61
246
  finally {
62
247
  process.removeListener("SIGINT", interrupt);
63
248
  process.removeListener("SIGTERM", terminate);
249
+ await studio?.close().catch(() => { });
250
+ }
251
+ }
252
+ function printBanner(options) {
253
+ const hostNote = options.ephemeral
254
+ ? "(ephemeral; removed on exit)"
255
+ : options.hostStarted
256
+ ? "(started; stays running)"
257
+ : "(already running)";
258
+ const short = options.tenantId.length > 12
259
+ ? `${options.tenantId.slice(0, 12)}…`
260
+ : options.tenantId;
261
+ console.log(`Host ${options.hostUrl} ${hostNote}`);
262
+ console.log(`Tenant ${options.tenantName} ${short}`);
263
+ console.log(`Entry ${options.entry}`);
264
+ if (options.studioLaunchUrl) {
265
+ console.log(`Studio ${options.studioLaunchUrl}`);
266
+ if (options.studioMode === "hosted") {
267
+ console.log(`Safari or offline: nylorun studio --local-ui`);
268
+ }
269
+ }
270
+ else {
271
+ console.log(`Studio: npm run studio`);
272
+ }
273
+ console.log("");
274
+ console.log("Ctrl-C stops this Project only.");
275
+ if (!options.ephemeral) {
276
+ console.log("nylorun runtime down stops the Host.");
64
277
  }
65
278
  }
package/dist/doctor.d.ts CHANGED
@@ -1,7 +1,29 @@
1
- import type { AgentManifest } from "@nylorun/agents";
2
- /** `nylorun doctor sandbox`: what this machine offers and what a Runtime here would pick. */
1
+ import { type AgentManifest } from "@nylorun/agents";
2
+ export type SandboxReport = {
3
+ preference: string;
4
+ backend: string | null;
5
+ isolation?: string;
6
+ reason?: string;
7
+ probes: {
8
+ name: string;
9
+ isolation: string;
10
+ available: boolean;
11
+ reason?: string;
12
+ version?: string;
13
+ }[];
14
+ defaultImage?: string;
15
+ };
16
+ /**
17
+ * `nylorun doctor runtime`: check the two prerequisites (Node 24+ and an
18
+ * installed `@nylorun/runtime`) and print the fix for each one that fails.
19
+ * Exits 1 when a prerequisite is missing; installs nothing.
20
+ */
21
+ export declare function doctorRuntime(options: {
22
+ json: boolean;
23
+ }): Promise<void>;
24
+ /** `nylorun doctor sandbox`: Tenant sandbox report via the Tenant API (F2-4). */
3
25
  export declare function doctorSandbox(options: {
4
26
  json: boolean;
5
27
  }): Promise<void>;
6
28
  /** One line for the dev banner, or undefined when no connected agent declares a sandbox. */
7
- export declare function sandboxBanner(runtimeUrl: string, serverKey: string, manifests: readonly AgentManifest[]): Promise<string | undefined>;
29
+ export declare function sandboxBanner(runtimeUrl: string, serverKey: string, manifests: readonly AgentManifest[], tenantId: string): Promise<string | undefined>;
package/dist/doctor.js CHANGED
@@ -1,24 +1,110 @@
1
1
  import { release } from "node:os";
2
- import { probeSandboxBackends, } from "@nylorun/runtime/node";
2
+ import { PROTOCOL_HEADER, PROTOCOL_VERSION, TENANT_HEADER, createClient, resolveConnection, } from "@nylorun/agents";
3
+ import { CliError } from "./errors.js";
4
+ import { launcher, resolveHome, runtimeInstallCommand } from "./runtime/launcher.js";
5
+ /** Oldest Node the Runtime runs on (it needs node:sqlite). */
6
+ const MIN_NODE_MAJOR = 24;
3
7
  const LABEL = {
4
8
  microsandbox: "microsandbox VM",
5
9
  virtual: "virtual shell",
6
10
  };
7
- /** `nylorun doctor sandbox`: what this machine offers and what a Runtime here would pick. */
11
+ function platformLine() {
12
+ const os = process.platform === "darwin"
13
+ ? "macOS"
14
+ : process.platform === "win32"
15
+ ? "Windows"
16
+ : process.platform;
17
+ return `${os} (kernel ${release()}) · ${process.arch}`;
18
+ }
19
+ async function fetchSandboxReport(options) {
20
+ const connection = await resolveConnection(options);
21
+ const client = createClient({
22
+ url: connection.url,
23
+ key: connection.key,
24
+ tenant: connection.tenant,
25
+ });
26
+ return client.transport.json("/v1/tenant/sandbox", "GET", undefined);
27
+ }
28
+ /**
29
+ * `nylorun doctor runtime`: check the two prerequisites (Node 24+ and an
30
+ * installed `@nylorun/runtime`) and print the fix for each one that fails.
31
+ * Exits 1 when a prerequisite is missing; installs nothing.
32
+ */
33
+ export async function doctorRuntime(options) {
34
+ const nodeOk = Number(process.versions.node.split(".")[0]) >= MIN_NODE_MAJOR;
35
+ let runtime;
36
+ try {
37
+ const handle = await launcher(resolveHome());
38
+ runtime = { ok: true, ...handle.runtime };
39
+ }
40
+ catch (error) {
41
+ runtime = {
42
+ ok: false,
43
+ problem: error instanceof Error ? error.message : String(error),
44
+ };
45
+ }
46
+ if (options.json) {
47
+ console.log(JSON.stringify({
48
+ node: { version: process.versions.node, ok: nodeOk },
49
+ runtime,
50
+ install: runtimeInstallCommand(),
51
+ }, null, 2));
52
+ }
53
+ else {
54
+ const rows = [
55
+ [
56
+ "node",
57
+ nodeOk
58
+ ? `✓ ${process.versions.node}`
59
+ : `✗ ${process.versions.node}: install Node ${MIN_NODE_MAJOR} or newer`,
60
+ ],
61
+ [
62
+ "runtime",
63
+ runtime.ok
64
+ ? `✓ ${runtime.version} · ${runtime.bin}`
65
+ : `✗ ${runtime.problem.split("\n")[0]}\n ${runtimeInstallCommand()}`,
66
+ ],
67
+ ];
68
+ const width = Math.max(...rows.map(([key]) => key.length)) + 2;
69
+ for (const [key, value] of rows)
70
+ console.log(` ${key.padEnd(width)}${value}`);
71
+ }
72
+ if (!nodeOk || !runtime.ok)
73
+ process.exitCode = 1;
74
+ }
75
+ /** `nylorun doctor sandbox`: Tenant sandbox report via the Tenant API (F2-4). */
8
76
  export async function doctorSandbox(options) {
9
- const report = await probeSandboxBackends();
77
+ let report;
78
+ try {
79
+ report = await fetchSandboxReport();
80
+ }
81
+ catch (error) {
82
+ throw new CliError(error instanceof Error
83
+ ? error.message
84
+ : `Could not read Tenant sandbox status: ${String(error)}`, 1);
85
+ }
10
86
  if (options.json) {
11
- console.log(JSON.stringify({ platform: platform(), ...report }, null, 2));
87
+ console.log(JSON.stringify({ platform: platformLine(), ...report }, null, 2));
12
88
  return;
13
89
  }
14
- const rows = [["platform", platform()]];
90
+ const rows = [["platform", platformLine()]];
15
91
  for (const probe of report.probes)
16
92
  rows.push([
17
93
  probe.name,
18
94
  `${probe.available ? "✓" : "✗"} ${probe.reason ?? ""}${probe.version ? ` · ${probe.version}` : ""}`,
19
95
  ]);
20
- rows.push(["preference", report.preference === "auto" ? "auto (set NYLORUN_SANDBOX to force one)" : report.preference]);
21
- rows.push(["selected", report.backend ? `${report.backend} (${report.isolation} isolation)` : `none: ${report.reason}`]);
96
+ rows.push([
97
+ "preference",
98
+ report.preference === "auto"
99
+ ? "auto (seed Tenant sandbox.backend via nylorun dev / .env NYLORUN_SANDBOX)"
100
+ : report.preference,
101
+ ]);
102
+ rows.push([
103
+ "selected",
104
+ report.backend
105
+ ? `${report.backend} (${report.isolation} isolation)`
106
+ : `none: ${report.reason}`,
107
+ ]);
22
108
  const width = Math.max(...rows.map(([key]) => key.length)) + 2;
23
109
  for (const [key, value] of rows)
24
110
  console.log(` ${key.padEnd(width)}${value}`);
@@ -27,7 +113,7 @@ export async function doctorSandbox(options) {
27
113
  " For hardware isolation use macOS on Apple Silicon or Linux with KVM.");
28
114
  }
29
115
  /** One line for the dev banner, or undefined when no connected agent declares a sandbox. */
30
- export async function sandboxBanner(runtimeUrl, serverKey, manifests) {
116
+ export async function sandboxBanner(runtimeUrl, serverKey, manifests, tenantId) {
31
117
  const capability = manifests
32
118
  .flatMap((manifest) => manifest.capabilities)
33
119
  .find((item) => item.sandbox);
@@ -35,14 +121,20 @@ export async function sandboxBanner(runtimeUrl, serverKey, manifests) {
35
121
  return undefined;
36
122
  let report;
37
123
  try {
38
- const response = await fetch(`${runtimeUrl}/v1/host/sandbox`, {
39
- headers: { authorization: `Bearer ${serverKey}` },
124
+ const response = await fetch(`${runtimeUrl}/v1/tenant/sandbox`, {
125
+ headers: {
126
+ authorization: `Bearer ${serverKey}`,
127
+ [TENANT_HEADER]: tenantId,
128
+ [PROTOCOL_HEADER]: String(PROTOCOL_VERSION),
129
+ },
40
130
  signal: AbortSignal.timeout(10_000),
41
131
  });
42
132
  if (response.ok)
43
- report = await response.json();
133
+ report = (await response.json());
134
+ }
135
+ catch {
136
+ /* ignore */
44
137
  }
45
- catch { }
46
138
  if (!report)
47
139
  return undefined;
48
140
  const doctor = "run `npx nylorun doctor sandbox` for options";
@@ -55,7 +147,3 @@ export async function sandboxBanner(runtimeUrl, serverKey, manifests) {
55
147
  ? `sandbox: ${LABEL[report.backend] ?? report.backend} (${report.reason}) · ${doctor}`
56
148
  : `sandbox: ${LABEL[report.backend] ?? report.backend}${report.backend === "microsandbox" && image ? ` · image ${image}` : ""} · network: ${network}`;
57
149
  }
58
- function platform() {
59
- const os = process.platform === "darwin" ? "macOS" : process.platform === "win32" ? "Windows" : process.platform;
60
- return `${os} (kernel ${release()}) · ${process.arch}`;
61
- }
@@ -1,2 +1,5 @@
1
- export declare function loadProjectEnvironment(root?: string): void;
1
+ /**
2
+ * Read the Project `.env` into a value map. Does not mutate `process.env` (F3).
3
+ */
4
+ export declare function loadProjectEnvironment(root?: string): Record<string, string>;
2
5
  export declare function saveEnvironment(root: string, updates: Record<string, string | undefined>, signal?: AbortSignal): Promise<void>;
@@ -2,7 +2,10 @@ import { readFileSync, statSync } from "node:fs";
2
2
  import { writeFile, rename, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { randomUUID } from "node:crypto";
5
- import { loadEnvFile } from "node:process";
5
+ import { parseEnv } from "node:util";
6
+ /**
7
+ * Read the Project `.env` into a value map. Does not mutate `process.env` (F3).
8
+ */
6
9
  export function loadProjectEnvironment(root = process.cwd()) {
7
10
  const file = join(root, ".env");
8
11
  try {
@@ -11,10 +14,16 @@ export function loadProjectEnvironment(root = process.cwd()) {
11
14
  }
12
15
  catch (error) {
13
16
  if (error.code === "ENOENT")
14
- return;
17
+ return {};
15
18
  throw error;
16
19
  }
17
- loadEnvFile(file);
20
+ const parsed = parseEnv(readFileSync(file, "utf8"));
21
+ const result = {};
22
+ for (const [key, value] of Object.entries(parsed)) {
23
+ if (value !== undefined)
24
+ result[key] = value;
25
+ }
26
+ return result;
18
27
  }
19
28
  // Match complete dotenv assignments, including quoted multiline values.
20
29
  const assignment = /^(?:export\s+)?([\w]+)[\t ]*=[\t ]*(?:"[^"]*"|'[^']*'|`[^`]*`|[^#\r\n]*)([^\r\n]*)(?:\r?\n|$)/gm;
@@ -1,5 +1,5 @@
1
- import type { Scope } from "./scope.js";
2
- export declare function serve(entry?: string, options?: {
3
- autostart?: boolean;
4
- scope?: Scope;
5
- }): Promise<void>;
1
+ /**
2
+ * `nylorun serve` was removed (D§12 / F2-5). Use `nylorun dev` in development
3
+ * and `node dist/src/main.js` (with the three environment variables) in production.
4
+ */
5
+ export declare function serve(): Promise<never>;
package/dist/launcher.js CHANGED
@@ -1,4 +1,7 @@
1
- import { runProject } from "./project-runner.js";
2
- export async function serve(entry = "dist/agents/index.js", options = {}) {
3
- await runProject(entry, options);
1
+ /**
2
+ * `nylorun serve` was removed (D§12 / F2-5). Use `nylorun dev` in development
3
+ * and `node dist/src/main.js` (with the three environment variables) in production.
4
+ */
5
+ export async function serve() {
6
+ throw new Error("nylorun serve was removed. Use nylorun dev [entry] in development, or node dist/src/main.js with NYLORUN_RUNTIME_URL, NYLORUN_TENANT and NYLORUN_SERVER_KEY.");
4
7
  }
@@ -11,9 +11,39 @@ export type PromptedModel = {
11
11
  baseUrl?: string;
12
12
  auth: Credential;
13
13
  };
14
+ export type ModelCatalog = {
15
+ providers: {
16
+ id: string;
17
+ name: string;
18
+ models: {
19
+ id: string;
20
+ name: string;
21
+ }[];
22
+ auth?: {
23
+ apiKey?: unknown;
24
+ oauth?: unknown;
25
+ };
26
+ }[];
27
+ };
28
+ export type Selection = Readonly<{
29
+ provider: string;
30
+ model: string;
31
+ custom?: Readonly<{
32
+ baseUrl: string;
33
+ }>;
34
+ }>;
35
+ /** Fetch GET /v1/tenant/models when a connection is available. */
36
+ export declare function fetchModelCatalog(options: {
37
+ url: string;
38
+ key: string;
39
+ tenantId: string;
40
+ fetchImpl?: typeof fetch;
41
+ }): Promise<ModelCatalog>;
14
42
  export declare function configureProvider(options?: {
15
43
  signal?: AbortSignal;
16
44
  root?: string;
17
45
  input?: Readable;
18
46
  output?: Writable;
47
+ /** Tenant API catalog from GET /v1/tenant/models. */
48
+ catalog?: ModelCatalog;
19
49
  }): Promise<PromptedModel>;