@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/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @nylorun/cli
2
2
 
3
+ ## 0.3.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - c49efed: **Breaking (pre-1.0 minor):** CLI reaches the local Runtime only through the installed Runtime's launcher.
8
+
9
+ - Dependencies: `@nylorun/agents` and `@nylorun/admin` only (no `@nylorun/runtime`, no `@nylorun/studio`).
10
+ - `nylorun runtime …` finds `nylorun-runtime` on PATH and checks its launcher and Host protocol; a missing or incompatible Runtime exits 1 with the install command for the recommended version (`package.json` `nylorun.runtime`). Nothing is downloaded. `nylorun doctor runtime` checks the prerequisites.
11
+ - `nylorun dev` runs the application entry under `tsx watch`; creates Tenants through `@nylorun/admin`.
12
+ - **Removed:** `nylorun serve`, `nylorun studio`, `--no-studio`, in-process Project runner.
13
+ - Install as a **devDependency**; production `start` is `node dist/src/main.js`.
14
+
15
+ - c49efed: **Breaking (pre-1.0 minor):** Replace a single SQLite Runtime per Project or home directory with a **Runtime Host** that serves isolated **Tenants**, selected by `Nylorun-Tenant` and negotiated with `Nylorun-Protocol` (protocol `2`, feature `runtime-tenants`). Vocabulary: Host root + Tenant + Project link.
16
+
17
+ - **core:** `PROTOCOL_VERSION = 2`, `HOST_PROTOCOL`, `TENANT_HEADER`, `PROTOCOL_HEADER`, `newTenantId` / `isTenantId`, `checkCompatibility`; health schema gains `hostId` + `protocol` (`service: "nylorun-runtime"`); Tenant/admin wire schemas; Tenant model routes under `/v1/tenant/*`.
18
+ - **runtime:** Host process + Tenant module; Tenant model routes under `/v1/tenant/*`; `startEphemeralRuntime` for tests/embeds; executors via `PUT /v1/executors`; sandbox prefix `nylorun-<tenant-id>-`.
19
+ - **agents:** `createClient({ url, key, tenant })`; Transport sends Tenant + protocol headers; `/health` compatibility cache; `IncompatibleRuntimeError` with upgrade remedies.
20
+ - **cli:** Host root lifecycle (`runtime up|down|status|logs|restart|run`); Project link (`.nylorun/link.json` + `credentials.json`); `tenant` commands; `runtime status --env` exports `NYLORUN_RUNTIME_URL`, `NYLORUN_SERVER_KEY`, `NYLORUN_TENANT`; removed Project/home SQLite selectors.
21
+ - **studio:** `startStudio({ …, tenant: { id, name } })`; proxy forwards Tenant + protocol headers; UI shows Tenant name/short id.
22
+
23
+ ### Patch Changes
24
+
25
+ - Pin agents to the tested release.
26
+ - Pin admin to the tested release.
27
+ - Pin runtime to the tested release.
28
+ - Updated dependencies [c49efed]
29
+ - Updated dependencies [c49efed]
30
+ - Updated dependencies [c49efed]
31
+ - Updated dependencies [fd9fd87]
32
+ - Updated dependencies
33
+ - Updated dependencies
34
+ - @nylorun/agents@0.6.0-beta
35
+ - @nylorun/admin@0.2.0-beta
36
+
3
37
  ## 0.2.1-beta
4
38
 
5
39
  ### Patch Changes
package/README.md CHANGED
@@ -1,61 +1,108 @@
1
1
  # @nylorun/cli
2
2
 
3
- The local `nylorun` executable. Requires Node 24+.
3
+ The local `nylorun` executable. Depends on `@nylorun/agents` and `@nylorun/admin`
4
+ only among Nylorun packages — it runs the installed Runtime's **launcher** as a
5
+ process and never imports `@nylorun/runtime`. Vocabulary:
6
+ [runtime/src/CONTEXT.md](../runtime/src/CONTEXT.md).
7
+
8
+ Prerequisites, installed by the developer (the CLI never downloads them):
9
+
10
+ ```sh
11
+ node --version # 24 or newer
12
+ npm install --global @nylorun/runtime # provides nylorun-runtime
13
+ nylorun doctor runtime # checks both
14
+ ```
4
15
 
5
16
  ```sh
6
- nylorun up # start the local Runtime (alias for "nylorun runtime start")
7
- nylorun dev # watch agents/index.ts, open Studio
8
- nylorun dev --no-studio
9
- nylorun serve [entry] # run the compiled build, default dist/agents/index.js
10
- nylorun configure
11
- nylorun studio
12
- nylorun runtime status
13
- nylorun down # stop the Runtime, keeping SQLite and credentials
17
+ nylorun runtime up # launcher: start the Host
18
+ nylorun runtime down # stop the Host; keep Tenants and host.json
19
+ nylorun runtime status # Host id, address, version, Tenant list
20
+ nylorun runtime status --env # export lines for the linked Project
21
+ nylorun runtime logs [--follow] # Host + Tenant logs
22
+ nylorun runtime restart # restart onto the installed Runtime
23
+ nylorun runtime run # attached foreground Host
24
+ nylorun dev [entry] # up → link/create Tenant → tsx watch entry
25
+ nylorun dev --ephemeral # temporary Host root + one Tenant
26
+ nylorun dev --local-ui # also start Studio with a local dashboard
27
+ nylorun studio [--local-ui] # start Studio (launch URL + proxy token)
28
+ nylorun configure # replace model credential on the linked Tenant
29
+ nylorun tenant list|delete|status|reset
30
+ nylorun doctor runtime # prerequisites: Node 24+, installed Runtime
31
+ nylorun doctor sandbox # sandbox backend via Tenant API
14
32
  ```
15
33
 
16
- ## The Runtime is a separate, persistent process
34
+ `nylorun serve` remains removed. Production `start` is
35
+ `node dist/src/main.js` with `connectAgents` in the application.
36
+ `--local-ui` cannot be combined with `--no-studio`.
37
+
38
+ ## The launcher
39
+
40
+ `nylorun runtime …` finds `nylorun-runtime` on PATH, checks that it speaks
41
+ launcher protocol 1 and a compatible Host protocol, and invokes it with
42
+ `--json`. The Host runs on the same Node, from the installed
43
+ `@nylorun/runtime`. When the launcher is missing or incompatible, the command
44
+ exits 1 with the install command for the recommended version
45
+ (`cli/package.json` `nylorun.runtime`). A project devDependency on
46
+ `@nylorun/runtime` also works, because npm scripts put `node_modules/.bin` on
47
+ PATH. See [building a desktop client](../docs/building-a-desktop-client.md)
48
+ for the launcher contract.
49
+
50
+ `dev` starts (or reuses) the Host via the launcher, creates a Tenant through
51
+ `@nylorun/admin` when the Project has no link, writes format-1 link +
52
+ credentials, and spawns `tsx watch <entry>` with
53
+ `NYLORUN_RUNTIME_URL`, `NYLORUN_TENANT` and `NYLORUN_SERVER_KEY`. The ready
54
+ banner hints `Studio: npm run studio`, or prints the Studio `launchUrl` when
55
+ started with `--local-ui`. Ctrl-C stops the child; the Host stays up.
56
+ `nylorun studio` resolves `@nylorun/studio`, supplies `cacheDir` from the Host
57
+ root (`NYLORUN_HOME` / `~/.nylorun`, never cwd), and prints `launchUrl`.
17
58
 
18
- `nylorun up` starts `@nylorun/runtime/server` in the background and returns the
19
- terminal. `dev` and `serve` attach to it, registering the project's exported
20
- agents and connecting scoped executors through `PUT /v1/executors`. They start
21
- the Runtime themselves when nothing is listening, and they leave it running on
22
- exit, so a watch restart re-registers agents without losing sessions. Pass
23
- `--no-autostart` to fail instead, which is what CI should do. `--foreground`
24
- keeps `runtime start` attached for logs.
59
+ ## Host root, Tenants and Project link
25
60
 
26
- ## Scope
61
+ The **Host root** is `NYLORUN_HOME` or `~/.nylorun` (resolved absolute once).
62
+ It holds `host.json`, admin credentials and every Tenant; the Runtime itself
63
+ is installed by npm. Isolation is per **Tenant**, not per Project directory.
27
64
 
28
- A project is the default scope: the Runtime's SQLite, credentials, log and pid
29
- live in `.nylorun/` beside the nearest `package.json`, created on first use and
30
- added to an existing `.gitignore`. `--global` (and running outside a project)
31
- uses `~/.nylorun` instead, relocatable with `NYLORUN_HOME`. Both default to
32
- `127.0.0.1:8787`; `--port` beats `NYLORUN_PORT`, then `PORT`, then the port
33
- recorded for a running Runtime. `--db` or `NYLORUN_SQLITE_PATH` moves the
34
- database. `nylorun runtime status --output json` prints the resolved scope for
35
- scripts, and `nylorun logs [-f]` tails the Runtime's own output.
65
+ A **Project** stores only:
36
66
 
37
- Runtime and executor credentials remain separate. On first `configure`, `dev` or
38
- `serve`, an interactive terminal saves the model provider credential into the
39
- Runtime vault; `configure` replaces it while the Runtime is already running.
67
+ - `.nylorun/link.json` — `{ format, hostUrl, hostId, tenantId }`
68
+ - `.nylorun/credentials.json` — application key and principal id (0600); no
69
+ executor tokens
70
+ - `.nylorun/.gitignore` containing `*`
71
+
72
+ ```sh
73
+ eval "$(npx nylorun runtime status --env)"
74
+ # → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY, NYLORUN_TENANT
75
+ ```
76
+
77
+ Port defaults to `8787` on loopback. On first Host setup, if that port is
78
+ taken, the launcher picks a free loopback port and persists it. Explicit
79
+ `--port` is strict.
40
80
 
41
81
  ## Exit codes
42
82
 
43
83
  | Code | Meaning |
44
84
  | --- | --- |
45
85
  | 0 | Success, including `down` when nothing was running |
46
- | 1 | Generic failure |
86
+ | 1 | Generic failure / launcher operation failed |
47
87
  | 2 | Usage error |
48
88
  | 3 | `runtime status` or `logs`: not running |
49
- | 4 | Port held by another process or another scope |
50
- | 5 | The running Runtime does not match this CLI |
51
- | 6 | `--no-autostart` and nothing is listening |
52
- | 7 | The Runtime did not become healthy |
89
+ | 4 | Port held by another process |
90
+ | 5 | Protocol / feature incompatible with this CLI |
91
+ | 7 | The Host did not become ready |
53
92
  | 130 / 143 | SIGINT / SIGTERM |
54
93
 
55
- `dev` loads `agents/index.ts` using project-installed `tsx` and watches changes.
56
- Studio and `tsx` are optional development dependencies; the CLI is a production
57
- dependency when the application's `start` script uses it.
94
+ Install the CLI as a **devDependency**. Generated applications keep
95
+ `@nylorun/agents` alone in production dependencies.
96
+
97
+ ## Troubleshooting
98
+
99
+ | Symptom | What to do |
100
+ | --- | --- |
101
+ | Quarantined Tenant | `nylorun tenant status` shows reason and `repair` |
102
+ | `426` / exit 5 | Upgrade CLI / Runtime pin, or pin a matching older set |
103
+ | Port conflict | Stop the other process or `nylorun runtime up --port <n>` |
104
+ | Logs | `nylorun runtime logs --follow` |
105
+ | Runtime not installed | `npm install --global @nylorun/runtime`; `nylorun doctor runtime` checks |
58
106
 
59
- Reusable provider configuration is imported from `@nylorun/runtime/configuration`;
60
- interactive prompts and process supervision belong here. Neither runtime nor
61
- harness depends on this package. See [package architecture](../docs/design/package-architecture.md).
107
+ See [package architecture](../docs/design/package-architecture.md) and
108
+ [MIGRATION.md](../MIGRATION.md#runtime-clients-and-admin-api-breaking-beta).
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import "./runtime/baseline.js";
package/dist/cli.js CHANGED
@@ -1,46 +1,43 @@
1
1
  #!/usr/bin/env node
2
- import { join } from "node:path";
3
- import { pathToFileURL } from "node:url";
2
+ import "./runtime/baseline.js";
4
3
  import { createRequire } from "node:module";
5
- import { ConfigurationCancelled, configureProvider, } from "./model/configure.js";
4
+ import { join } from "node:path";
5
+ import { ConfigurationCancelled, configureProvider, fetchModelCatalog, } from "./model/configure.js";
6
6
  import { putHostModel } from "./model/host-model.js";
7
- import { develop, developmentPreflight } from "./dev.js";
7
+ import { develop, developmentPreflight, startStudio } from "./dev.js";
8
8
  import { CliError } from "./errors.js";
9
- import { loadScopeEnvironment, resolveScope, } from "./scope.js";
10
- import { cliRuntimeVersion, ensureRuntime, hostStatus, startHost, stopHost, tailLog, warnIncompatible, } from "./runtime-host.js";
11
- import { localCredentials } from "./project-runner.js";
12
- const usage = `nylorun <runtime|up|down|logs|dev|serve|configure|studio|doctor>
9
+ import { runtimeCommand, runtimeUsage } from "./runtime/commands.js";
10
+ import { findProjectRoot, requireProjectRoot } from "./project/root.js";
11
+ import { printLinkedEnvExports } from "./project/attach.js";
12
+ import { readLink as readProjectLink } from "./project/link.js";
13
+ import { readCredentials as readProjectCredentials } from "./project/credentials.js";
14
+ import { tenantCommand } from "./tenant/commands.js";
15
+ import { resolveHome } from "./runtime/launcher.js";
16
+ const usage = `nylorun <runtime|up|down|logs|dev|studio|configure|doctor|tenant>
13
17
 
14
- runtime start [--foreground] [--restart] [--port <n>] [--db <path>] [--global]
15
- runtime stop [--global] [--timeout <seconds>]
16
- runtime status [--output json] [--global]
17
- runtime logs [-f] [-n <lines>] [--global]
18
- up alias for "runtime start"
19
- down alias for "runtime stop"
20
- logs alias for "runtime logs"
18
+ ${runtimeUsage}
21
19
 
22
- dev [--no-studio] [--no-open] [--no-autostart] [--port <n>]
23
- serve [entry] [--no-autostart] [--port <n>]
24
- configure [--global]
25
- studio [--runtime-url <http(s)-url>] [--port <n>] [--no-open]
26
- doctor sandbox [--json] show which sandbox backend this machine offers`;
27
- /**
28
- * Flags are parsed before the scope is resolved so --port can win over a PORT in .env, which
29
- * is only loaded once the project root is known.
30
- */
20
+ dev [entry] [--ephemeral] [--local-ui] [--no-studio] [--no-open]
21
+ studio [--local-ui] [--port <n>] [--no-open]
22
+ configure
23
+ doctor runtime [--json] check the prerequisites: Node 24+ and an installed Runtime
24
+ doctor sandbox [--json] show which sandbox backend this Tenant's Host offers
25
+ tenant current|list [--json]|use <name-or-id>|status [--json]|reset|delete`;
31
26
  function parseFlags(args, allowed = {}) {
32
27
  const booleans = new Set();
33
28
  const values = new Map();
34
29
  const rest = [];
35
- const scope = {};
36
- const booleanNames = new Set(["--global", ...(allowed.booleans ?? [])]);
37
- const valueNames = new Set(["--port", "--db", ...(allowed.values ?? [])]);
30
+ const booleanNames = new Set(allowed.booleans ?? []);
31
+ const valueNames = new Set(allowed.values ?? []);
38
32
  for (let index = 0; index < args.length; index += 1) {
39
33
  const arg = args[index];
40
34
  if (!arg.startsWith("-")) {
41
35
  rest.push(arg);
42
36
  continue;
43
37
  }
38
+ if (arg === "--global" || arg === "--db") {
39
+ throw usageError(`${arg} was removed. Use the Runtime Host root (NYLORUN_HOME) and a Project link instead.`);
40
+ }
44
41
  if (booleanNames.has(arg)) {
45
42
  if (booleans.has(arg))
46
43
  throw usageError(`${arg} may only be supplied once.`);
@@ -58,198 +55,131 @@ function parseFlags(args, allowed = {}) {
58
55
  }
59
56
  throw usageError(usage);
60
57
  }
61
- if (booleans.has("--global"))
62
- scope.global = true;
63
- if (values.has("--port"))
64
- scope.port = parsePort(values.get("--port"));
65
- if (values.has("--db"))
66
- scope.db = values.get("--db");
67
- return { scope, rest, booleans, values };
58
+ return { rest, booleans, values };
68
59
  }
69
60
  const usageError = (message) => new CliError(message, 2);
70
61
  function parsePort(value) {
71
- if (value === undefined)
72
- return undefined;
62
+ if (value === undefined || !/^\d+$/u.test(value))
63
+ throw usageError(`Invalid --port: ${value ?? "(missing)"}`);
73
64
  const port = Number(value);
74
65
  if (!Number.isInteger(port) || port < 1 || port > 65535)
75
- throw usageError("--port must be an integer between 1 and 65535.");
66
+ throw usageError(`Invalid --port: ${value}`);
76
67
  return port;
77
68
  }
78
- async function serverKeyFor(scope) {
79
- return (process.env.NYLORUN_SERVER_KEY ??
80
- (await localCredentials(scope.credentialsPath)).serverKey);
81
- }
82
- async function startStudio(agentServerUrl, serverKey, open, port) {
83
- let entry;
84
- try {
85
- entry = createRequire(join(process.cwd(), "package.json")).resolve("@nylorun/studio");
69
+ async function resolveLinkedAuth(projectRoot) {
70
+ const link = await readProjectLink(projectRoot);
71
+ const credentials = await readProjectCredentials(projectRoot);
72
+ if (link && credentials) {
73
+ return {
74
+ url: link.hostUrl,
75
+ key: credentials.applicationKey,
76
+ tenantId: link.tenantId,
77
+ tenantName: link.tenantId,
78
+ };
86
79
  }
87
- catch {
88
- throw new Error("Install @nylorun/studio to use the Studio dashboard.");
80
+ const url = process.env.NYLORUN_RUNTIME_URL?.trim();
81
+ const key = process.env.NYLORUN_SERVER_KEY?.trim();
82
+ const tenantId = process.env.NYLORUN_TENANT?.trim();
83
+ if (url && key && tenantId) {
84
+ return { url, key, tenantId, tenantName: tenantId };
89
85
  }
90
- const studio = await import(pathToFileURL(entry).href);
91
- return studio.startStudio({
92
- runtimeUrl: agentServerUrl,
93
- serverKey,
94
- open,
95
- ...(port === undefined ? {} : { port }),
96
- });
97
- }
98
- function printStatus(status) {
99
- const { scope, runtime } = status;
100
- const lines = [
101
- ["Scope", `${scope.kind} (${scope.kind === "project" ? scope.root : scope.dataDir})`],
102
- ["State", status.state],
103
- ["URL", scope.url],
104
- ["PID", runtime?.pid ? String(runtime.pid) : status.pid ? String(status.pid) : undefined],
105
- ["Runtime", status.version],
106
- ["CLI expects", cliRuntimeVersion()],
107
- ["Database", scope.sqlitePath],
108
- ["Log", scope.logPath],
109
- ["Started", runtime?.startedAt],
110
- ];
111
- const width = Math.max(...lines.map(([label]) => label.length));
112
- for (const [label, value] of lines)
113
- if (value !== undefined)
114
- console.log(`${label.padEnd(width)} ${value}`);
115
- }
116
- async function runtimeCommand(args) {
117
- const [verb, ...rest] = args;
118
- if (verb === "start") {
119
- const flags = parseFlags(rest, { booleans: ["--foreground", "--restart"] });
120
- if (flags.rest.length)
121
- throw usageError(usage);
122
- const scope = resolveScope(flags.scope);
123
- loadScopeEnvironment(scope);
124
- const before = await hostStatus(scope);
125
- if (before.state === "running" && !flags.booleans.has("--restart")) {
126
- console.log(`Runtime already running for ${scope.label} at ${scope.url} (pid ${before.runtime?.pid ?? before.pid}).`);
127
- warnIncompatible(scope, before);
128
- return;
129
- }
130
- const status = await startHost(scope, {
131
- foreground: flags.booleans.has("--foreground"),
132
- restart: flags.booleans.has("--restart"),
133
- });
134
- printStatus(status);
135
- console.log(`\n→ npx nylorun dev`);
136
- return;
137
- }
138
- if (verb === "stop") {
139
- const flags = parseFlags(rest, { values: ["--timeout"] });
140
- if (flags.rest.length)
141
- throw usageError(usage);
142
- const scope = resolveScope(flags.scope);
143
- const seconds = flags.values.has("--timeout")
144
- ? Number(flags.values.get("--timeout"))
145
- : undefined;
146
- if (seconds !== undefined && (!Number.isFinite(seconds) || seconds <= 0))
147
- throw usageError("--timeout must be a positive number of seconds.");
148
- const result = await stopHost(scope, {
149
- ...(seconds === undefined ? {} : { timeoutMs: seconds * 1000 }),
150
- });
151
- console.log(result === "stopped"
152
- ? `Stopped the Runtime for ${scope.label}. SQLite and credentials are kept in ${scope.dataDir}.`
153
- : `No Runtime is running for ${scope.label}.`);
154
- return;
155
- }
156
- if (verb === "status") {
157
- const flags = parseFlags(rest, { values: ["--output"] });
158
- if (flags.rest.length)
159
- throw usageError(usage);
160
- const output = flags.values.get("--output") ?? "text";
161
- if (output !== "text" && output !== "json")
162
- throw usageError("--output must be text or json.");
163
- const scope = resolveScope(flags.scope);
164
- const status = await hostStatus(scope);
165
- if (output === "json")
166
- console.log(JSON.stringify({
167
- scope: scope.kind,
168
- root: scope.kind === "project" ? scope.root : scope.dataDir,
169
- url: scope.url,
170
- state: status.state,
171
- pid: status.runtime?.pid ?? status.pid ?? null,
172
- runtimeVersion: status.version ?? null,
173
- cliVersion: cliRuntimeVersion() ?? null,
174
- scopeId: status.scopeId ?? null,
175
- sqlitePath: scope.sqlitePath,
176
- logPath: scope.logPath,
177
- startedAt: status.runtime?.startedAt ?? null,
178
- }, null, 2));
179
- else
180
- printStatus(status);
181
- if (status.state !== "running")
182
- process.exitCode = status.state === "port-conflict" ? 4 : 3;
183
- return;
184
- }
185
- if (verb === "logs") {
186
- const flags = parseFlags(rest, { booleans: ["-f"], values: ["-n"] });
187
- if (flags.rest.length)
188
- throw usageError(usage);
189
- const lines = flags.values.has("-n") ? Number(flags.values.get("-n")) : 200;
190
- if (!Number.isInteger(lines) || lines < 1)
191
- throw usageError("-n must be a positive integer.");
192
- await tailLog(resolveScope(flags.scope), {
193
- follow: flags.booleans.has("-f"),
194
- lines,
195
- });
196
- return;
197
- }
198
- throw usageError(usage);
86
+ throw new CliError(`No Project link in ${projectRoot}. Run nylorun dev, or set NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY and NYLORUN_TENANT.`, 1);
199
87
  }
200
88
  async function main() {
201
89
  const [rawCommand, ...rawArgs] = process.argv.slice(2);
202
90
  if (!rawCommand || rawCommand === "--help" || rawCommand === "-h")
203
91
  return void console.log(usage);
204
92
  const aliases = {
205
- up: "start",
206
- down: "stop",
93
+ up: "up",
94
+ down: "down",
207
95
  logs: "logs",
208
96
  };
209
97
  const command = rawCommand in aliases ? "runtime" : rawCommand;
210
98
  const args = rawCommand in aliases ? [aliases[rawCommand], ...rawArgs] : rawArgs;
211
- if (command === "runtime")
212
- return await runtimeCommand(args);
99
+ if (command === "tenant")
100
+ return await tenantCommand(args);
101
+ if (command === "runtime") {
102
+ // F2-7: envHook prints the linked Project's three variables.
103
+ return await runtimeCommand(args, {
104
+ envHook: async () => {
105
+ const root = findProjectRoot() ?? process.cwd();
106
+ await printLinkedEnvExports(root);
107
+ },
108
+ });
109
+ }
213
110
  if (command === "doctor") {
214
111
  const [topic, ...options] = args;
215
- if (topic !== "sandbox" || options.some((option) => option !== "--json"))
216
- throw usageError("Usage: nylorun doctor sandbox [--json]");
217
- const { doctorSandbox } = await import("./doctor.js");
218
- await doctorSandbox({ json: options.includes("--json") });
112
+ if ((topic !== "sandbox" && topic !== "runtime") ||
113
+ options.some((option) => option !== "--json"))
114
+ throw usageError("Usage: nylorun doctor runtime|sandbox [--json]");
115
+ const { doctorRuntime, doctorSandbox } = await import("./doctor.js");
116
+ const json = options.includes("--json");
117
+ if (topic === "runtime")
118
+ await doctorRuntime({ json });
119
+ else
120
+ await doctorSandbox({ json });
219
121
  return;
220
122
  }
221
123
  if (command === "dev") {
222
124
  const flags = parseFlags(args, {
223
- booleans: ["--no-studio", "--no-open", "--no-autostart"],
125
+ booleans: ["--ephemeral", "--local-ui", "--no-studio", "--no-open"],
224
126
  });
225
- if (flags.rest.length)
127
+ if (flags.rest.length > 1)
226
128
  throw usageError(usage);
227
- const scope = resolveScope(flags.scope);
228
- loadScopeEnvironment(scope);
229
- // The port is re-resolved after .env so a project file can set PORT, while --port wins.
230
- const resolved = resolveScope(flags.scope);
231
- process.env.PORT = String(resolved.port);
232
- developmentPreflight([...flags.booleans]);
233
- // The supervisor checks the host before spawning tsx so port conflicts, version skew and
234
- // --no-autostart surface with their own exit codes instead of a generic watcher failure.
235
- await ensureRuntime(resolved, {
236
- autostart: !flags.booleans.has("--no-autostart"),
129
+ if (flags.booleans.has("--local-ui") && flags.booleans.has("--no-studio"))
130
+ throw usageError("--local-ui cannot be combined with --no-studio.");
131
+ requireProjectRoot();
132
+ developmentPreflight([
133
+ ...flags.rest,
134
+ ...[...flags.booleans],
135
+ ]);
136
+ process.exitCode = await develop({
137
+ ...(flags.rest[0] ? { entry: flags.rest[0] } : {}),
138
+ flags: [...flags.booleans],
237
139
  });
238
- process.exitCode = await develop([...flags.booleans]);
239
140
  return;
240
141
  }
241
142
  if (command === "serve") {
242
- const flags = parseFlags(args, { booleans: ["--no-autostart"] });
243
- if (flags.rest.length > 1)
143
+ throw usageError("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.");
144
+ }
145
+ if (command === "studio") {
146
+ const flags = parseFlags(args, {
147
+ booleans: ["--local-ui", "--no-open"],
148
+ values: ["--port"],
149
+ });
150
+ if (flags.rest.length)
244
151
  throw usageError(usage);
245
- const scope = resolveScope(flags.scope);
246
- loadScopeEnvironment(scope);
247
- const resolved = resolveScope(flags.scope);
248
- // serve runs the project in this process, so runProject's own errors carry their exit
249
- // codes out; it validates the agent registry before it touches the host.
250
- await (await import("./launcher.js")).serve(flags.rest[0], {
251
- autostart: !flags.booleans.has("--no-autostart"),
252
- scope: resolved,
152
+ const projectRoot = findProjectRoot() ?? process.cwd();
153
+ // Surface missing Studio early with the same Install message as preflight.
154
+ try {
155
+ createRequire(join(projectRoot, "package.json")).resolve("@nylorun/studio");
156
+ }
157
+ catch {
158
+ throw new Error("Install @nylorun/studio to use the Studio dashboard.");
159
+ }
160
+ const auth = await resolveLinkedAuth(projectRoot);
161
+ const localUi = flags.booleans.has("--local-ui");
162
+ // I2: startStudio default is hosted; `--local-ui` forces local + cacheDir.
163
+ const dashboard = await startStudio({
164
+ runtimeUrl: auth.url,
165
+ serverKey: auth.key,
166
+ tenant: { id: auth.tenantId, name: auth.tenantName },
167
+ open: !flags.booleans.has("--no-open"),
168
+ localUi,
169
+ cacheDir: resolveHome(),
170
+ projectRoot,
171
+ ...(flags.values.has("--port")
172
+ ? { port: parsePort(flags.values.get("--port")) }
173
+ : {}),
174
+ });
175
+ console.log(`Studio ${dashboard.launchUrl}`);
176
+ if (!localUi && dashboard.launchUrl.startsWith("https://local.nylorun.studio")) {
177
+ console.log(`Safari or offline: nylorun studio --local-ui`);
178
+ }
179
+ await new Promise((resolve, reject) => {
180
+ const close = () => void dashboard.close().then(resolve, reject);
181
+ process.once("SIGINT", close);
182
+ process.once("SIGTERM", close);
253
183
  });
254
184
  return;
255
185
  }
@@ -257,47 +187,29 @@ async function main() {
257
187
  const flags = parseFlags(args);
258
188
  if (flags.rest.length)
259
189
  throw usageError(usage);
260
- const scope = resolveScope(flags.scope);
261
- loadScopeEnvironment(scope);
190
+ const projectRoot = findProjectRoot() ?? process.cwd();
191
+ const auth = await resolveLinkedAuth(projectRoot);
262
192
  const controller = new AbortController();
263
193
  const cancel = (signal) => controller.abort(new ConfigurationCancelled(signal));
264
194
  process.once("SIGINT", () => cancel("SIGINT"));
265
195
  process.once("SIGTERM", () => cancel("SIGTERM"));
266
- const serverKey = await serverKeyFor(scope);
267
- const health = await fetch(`${scope.url}/health`).catch(() => undefined);
196
+ const health = await fetch(`${auth.url}/health`).catch(() => undefined);
268
197
  if (!health?.ok)
269
- throw new CliError(`No Runtime is listening at ${scope.url} for ${scope.label}. Start one with "nylorun up".`, 6);
270
- const prompted = await configureProvider({ signal: controller.signal });
271
- await putHostModel(scope.url, serverKey, prompted);
198
+ throw new CliError(`No Runtime Host is listening at ${auth.url}. Start one with "nylorun runtime up".`, 6);
199
+ const catalog = await fetchModelCatalog({
200
+ url: auth.url,
201
+ key: auth.key,
202
+ tenantId: auth.tenantId,
203
+ });
204
+ const prompted = await configureProvider({
205
+ signal: controller.signal,
206
+ catalog,
207
+ });
208
+ await putHostModel(auth.url, auth.key, prompted, auth.tenantId);
272
209
  return;
273
210
  }
274
- if (command !== "studio")
275
- throw usageError(usage);
276
- const flags = parseFlags(args, {
277
- booleans: ["--no-open"],
278
- values: ["--runtime-url", "--studio-port"],
279
- });
280
- if (flags.rest.length)
281
- throw usageError(usage);
282
- const scope = resolveScope(flags.scope);
283
- const runtimeUrl = flags.values.get("--runtime-url") ?? scope.url;
284
- const studioPort = flags.values.has("--studio-port")
285
- ? parsePort(flags.values.get("--studio-port"))
286
- : flags.scope.port !== undefined && flags.values.has("--runtime-url")
287
- ? flags.scope.port
288
- : undefined;
289
- const dashboard = await startStudio(runtimeUrl, await serverKeyFor(scope), !flags.booleans.has("--no-open"), studioPort);
290
- console.log(`Studio on ${dashboard.address}`);
291
- await new Promise((resolve, reject) => {
292
- const close = () => void dashboard.close().then(resolve, reject);
293
- process.once("SIGINT", close);
294
- process.once("SIGTERM", close);
295
- });
211
+ throw usageError(usage);
296
212
  }
297
- /**
298
- * Probing a port that answers but is not a Runtime can leave a pooled connection holding the
299
- * event loop open, so a finished command exits explicitly once its output has drained.
300
- */
301
213
  async function finish(code) {
302
214
  process.exitCode = code;
303
215
  for (const stream of [process.stdout, process.stderr])
@@ -1 +1,5 @@
1
- export {};
1
+ /**
2
+ * Legacy watcher entry. `nylorun dev` now runs the Project entry with
3
+ * `tsx watch` directly (D§12).
4
+ */
5
+ export declare function legacyDevEntryRemoved(): never;
package/dist/dev-entry.js CHANGED
@@ -1,6 +1,7 @@
1
- import { runProject } from "./project-runner.js";
2
- await runProject("agents/index.ts", {
3
- studio: !process.argv.includes("--no-studio"),
4
- open: !process.argv.includes("--no-open"),
5
- autostart: !process.argv.includes("--no-autostart"),
6
- });
1
+ /**
2
+ * Legacy watcher entry. `nylorun dev` now runs the Project entry with
3
+ * `tsx watch` directly (D§12).
4
+ */
5
+ export function legacyDevEntryRemoved() {
6
+ throw new Error("cli/src/dev-entry.ts is no longer used. Point nylorun dev at your Project entry (default src/main.ts).");
7
+ }