@nylorun/cli 0.1.1-beta → 0.2.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +45 -10
- package/dist/cli.js +268 -59
- package/dist/dev-entry.js +1 -0
- package/dist/dev.d.ts +5 -0
- package/dist/dev.js +11 -6
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +61 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +9 -0
- package/dist/launcher.d.ts +5 -1
- package/dist/launcher.js +2 -2
- package/dist/model/configure.d.ts +8 -1
- package/dist/model/configure.js +33 -36
- package/dist/model/host-model.d.ts +20 -0
- package/dist/model/host-model.js +84 -0
- package/dist/project-runner.d.ts +16 -4
- package/dist/project-runner.js +68 -92
- package/dist/runtime-host.d.ts +39 -0
- package/dist/runtime-host.js +355 -0
- package/dist/scope.d.ts +65 -0
- package/dist/scope.js +191 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# @nylorun/cli
|
|
2
2
|
|
|
3
|
+
## 0.2.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b8d822a: Remove `nylorun start`. The compiled agent process is now `nylorun serve [entry]`, with the
|
|
8
|
+
same `dist/agents/index.js` default, and generated projects must change `scripts.start` to
|
|
9
|
+
`nylorun serve`. The local Runtime becomes a persistent process of its own under
|
|
10
|
+
`nylorun runtime start|stop|status|logs`, with `nylorun up` and `nylorun down` as aliases.
|
|
11
|
+
`dev` and `serve` attach to that Runtime, start one when nothing is listening, and leave it
|
|
12
|
+
running, so a watch restart re-registers agents and reconnects executors instead of
|
|
13
|
+
destroying in-flight sessions; `--no-autostart` fails instead and is what continuous
|
|
14
|
+
integration should use. Scope is per project by default in `.nylorun/`, created on first use,
|
|
15
|
+
with `--global` and `NYLORUN_HOME` for a shared Runtime in the home directory. Commands
|
|
16
|
+
report a resolved scope in every message, accept `--port` and `--db` alongside the existing
|
|
17
|
+
environment variables, expose `nylorun runtime status --output json`, and use documented exit
|
|
18
|
+
codes for usage errors, port conflicts, version skew, refused autostart and failed starts.
|
|
19
|
+
- b8d822a: Add `sandbox()`: one `.use(sandbox())` gives an agent Runtime-executed `bash`, `read`, `write`, `edit`, `grep` and `glob` tools on an isolated machine with a persistent `/workspace`. The Runtime selects a microsandbox microVM where available, otherwise an in-process virtual shell, enforces deny-by-default egress presets, owns sandbox lifecycle, and reports its choice through `GET /v1/host/sandbox`, the `nylorun dev` banner and `nylorun doctor sandbox`.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Pin agents to the tested release.
|
|
24
|
+
- Pin runtime to the tested release.
|
|
25
|
+
- Updated dependencies [b8d822a]
|
|
26
|
+
- Updated dependencies [b8d822a]
|
|
27
|
+
- Updated dependencies [b8d822a]
|
|
28
|
+
- Updated dependencies [b8d822a]
|
|
29
|
+
- Updated dependencies [b8d822a]
|
|
30
|
+
- Updated dependencies [b8d822a]
|
|
31
|
+
- Updated dependencies [b8d822a]
|
|
32
|
+
- Updated dependencies
|
|
33
|
+
- Updated dependencies
|
|
34
|
+
- Updated dependencies
|
|
35
|
+
- @nylorun/agents@0.4.0-beta
|
|
36
|
+
- @nylorun/runtime@0.8.0-beta
|
|
37
|
+
|
|
38
|
+
## 0.1.2-beta
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- 3a88f51: Ship Agent-Plugins (`plugin()` / `loadPlugin`), Skills (`load_skill` / skill resources), Runtime MCP pool + vault credentials, and manifest v3 capability fields. Validate completed tool `output` against the tool output schema so ordinary tools with `outputSchema` no longer false-fail as `tool.invalid-output`.
|
|
43
|
+
- Pin agents to the tested release.
|
|
44
|
+
- Pin runtime to the tested release.
|
|
45
|
+
- Updated dependencies [3a88f51]
|
|
46
|
+
- Updated dependencies
|
|
47
|
+
- Updated dependencies
|
|
48
|
+
- Updated dependencies
|
|
49
|
+
- @nylorun/agents@0.3.0-beta
|
|
50
|
+
- @nylorun/runtime@0.7.0-beta
|
|
51
|
+
|
|
3
52
|
## 0.1.1-beta
|
|
4
53
|
|
|
5
54
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -3,21 +3,56 @@
|
|
|
3
3
|
The local `nylorun` executable. Requires Node 24+.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
nylorun
|
|
7
|
-
nylorun dev
|
|
6
|
+
nylorun up # start the local Runtime (alias for "nylorun runtime start")
|
|
7
|
+
nylorun dev # watch agents/index.ts, open Studio
|
|
8
8
|
nylorun dev --no-studio
|
|
9
|
-
nylorun
|
|
10
|
-
nylorun
|
|
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
|
|
11
14
|
```
|
|
12
15
|
|
|
13
|
-
The
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
## The Runtime is a separate, persistent process
|
|
17
|
+
|
|
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.
|
|
25
|
+
|
|
26
|
+
## Scope
|
|
27
|
+
|
|
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.
|
|
36
|
+
|
|
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.
|
|
40
|
+
|
|
41
|
+
## Exit codes
|
|
42
|
+
|
|
43
|
+
| Code | Meaning |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| 0 | Success, including `down` when nothing was running |
|
|
46
|
+
| 1 | Generic failure |
|
|
47
|
+
| 2 | Usage error |
|
|
48
|
+
| 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 |
|
|
53
|
+
| 130 / 143 | SIGINT / SIGTERM |
|
|
18
54
|
|
|
19
55
|
`dev` loads `agents/index.ts` using project-installed `tsx` and watches changes.
|
|
20
|
-
`start [entry]` defaults to `dist/agents/index.js` without Studio or watching.
|
|
21
56
|
Studio and `tsx` are optional development dependencies; the CLI is a production
|
|
22
57
|
dependency when the application's `start` script uses it.
|
|
23
58
|
|
package/dist/cli.js
CHANGED
|
@@ -3,14 +3,83 @@ import { join } from "node:path";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { ConfigurationCancelled, configureProvider, } from "./model/configure.js";
|
|
6
|
-
import {
|
|
7
|
-
import { develop } from "./dev.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
import { putHostModel } from "./model/host-model.js";
|
|
7
|
+
import { develop, developmentPreflight } from "./dev.js";
|
|
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>
|
|
13
|
+
|
|
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"
|
|
21
|
+
|
|
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
|
+
*/
|
|
31
|
+
function parseFlags(args, allowed = {}) {
|
|
32
|
+
const booleans = new Set();
|
|
33
|
+
const values = new Map();
|
|
34
|
+
const rest = [];
|
|
35
|
+
const scope = {};
|
|
36
|
+
const booleanNames = new Set(["--global", ...(allowed.booleans ?? [])]);
|
|
37
|
+
const valueNames = new Set(["--port", "--db", ...(allowed.values ?? [])]);
|
|
38
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
39
|
+
const arg = args[index];
|
|
40
|
+
if (!arg.startsWith("-")) {
|
|
41
|
+
rest.push(arg);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (booleanNames.has(arg)) {
|
|
45
|
+
if (booleans.has(arg))
|
|
46
|
+
throw usageError(`${arg} may only be supplied once.`);
|
|
47
|
+
booleans.add(arg);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (valueNames.has(arg)) {
|
|
51
|
+
if (values.has(arg))
|
|
52
|
+
throw usageError(`${arg} may only be supplied once.`);
|
|
53
|
+
const value = args[++index];
|
|
54
|
+
if (!value || value.startsWith("-"))
|
|
55
|
+
throw usageError(`${arg} requires a value.`);
|
|
56
|
+
values.set(arg, value);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
throw usageError(usage);
|
|
60
|
+
}
|
|
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 };
|
|
68
|
+
}
|
|
69
|
+
const usageError = (message) => new CliError(message, 2);
|
|
70
|
+
function parsePort(value) {
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const port = Number(value);
|
|
74
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
75
|
+
throw usageError("--port must be an integer between 1 and 65535.");
|
|
76
|
+
return port;
|
|
77
|
+
}
|
|
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) {
|
|
14
83
|
let entry;
|
|
15
84
|
try {
|
|
16
85
|
entry = createRequire(join(process.cwd(), "package.json")).resolve("@nylorun/studio");
|
|
@@ -21,74 +90,203 @@ async function startStudio(agentServerUrl, open, port) {
|
|
|
21
90
|
const studio = await import(pathToFileURL(entry).href);
|
|
22
91
|
return studio.startStudio({
|
|
23
92
|
runtimeUrl: agentServerUrl,
|
|
24
|
-
serverKey
|
|
25
|
-
(await (await import("./project-runner.js")).localCredentials())
|
|
26
|
-
.serverKey,
|
|
93
|
+
serverKey,
|
|
27
94
|
open,
|
|
28
95
|
...(port === undefined ? {} : { port }),
|
|
29
96
|
});
|
|
30
97
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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);
|
|
38
199
|
}
|
|
39
200
|
async function main() {
|
|
40
|
-
const [
|
|
41
|
-
if (!
|
|
201
|
+
const [rawCommand, ...rawArgs] = process.argv.slice(2);
|
|
202
|
+
if (!rawCommand || rawCommand === "--help" || rawCommand === "-h")
|
|
42
203
|
return void console.log(usage);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
204
|
+
const aliases = {
|
|
205
|
+
up: "start",
|
|
206
|
+
down: "stop",
|
|
207
|
+
logs: "logs",
|
|
208
|
+
};
|
|
209
|
+
const command = rawCommand in aliases ? "runtime" : rawCommand;
|
|
210
|
+
const args = rawCommand in aliases ? [aliases[rawCommand], ...rawArgs] : rawArgs;
|
|
211
|
+
if (command === "runtime")
|
|
212
|
+
return await runtimeCommand(args);
|
|
213
|
+
if (command === "doctor") {
|
|
214
|
+
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") });
|
|
49
219
|
return;
|
|
50
220
|
}
|
|
51
221
|
if (command === "dev") {
|
|
52
|
-
|
|
222
|
+
const flags = parseFlags(args, {
|
|
223
|
+
booleans: ["--no-studio", "--no-open", "--no-autostart"],
|
|
224
|
+
});
|
|
225
|
+
if (flags.rest.length)
|
|
226
|
+
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"),
|
|
237
|
+
});
|
|
238
|
+
process.exitCode = await develop([...flags.booleans]);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (command === "serve") {
|
|
242
|
+
const flags = parseFlags(args, { booleans: ["--no-autostart"] });
|
|
243
|
+
if (flags.rest.length > 1)
|
|
244
|
+
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,
|
|
253
|
+
});
|
|
53
254
|
return;
|
|
54
255
|
}
|
|
55
256
|
if (command === "configure") {
|
|
56
|
-
|
|
57
|
-
|
|
257
|
+
const flags = parseFlags(args);
|
|
258
|
+
if (flags.rest.length)
|
|
259
|
+
throw usageError(usage);
|
|
260
|
+
const scope = resolveScope(flags.scope);
|
|
261
|
+
loadScopeEnvironment(scope);
|
|
58
262
|
const controller = new AbortController();
|
|
59
263
|
const cancel = (signal) => controller.abort(new ConfigurationCancelled(signal));
|
|
60
264
|
process.once("SIGINT", () => cancel("SIGINT"));
|
|
61
265
|
process.once("SIGTERM", () => cancel("SIGTERM"));
|
|
62
|
-
|
|
266
|
+
const serverKey = await serverKeyFor(scope);
|
|
267
|
+
const health = await fetch(`${scope.url}/health`).catch(() => undefined);
|
|
268
|
+
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);
|
|
63
272
|
return;
|
|
64
273
|
}
|
|
65
274
|
if (command !== "studio")
|
|
66
|
-
throw
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
throw new Error("--port may only be supplied once.");
|
|
82
|
-
port = parsePort(args[++index]);
|
|
83
|
-
}
|
|
84
|
-
else if (arg === "--no-open" && open)
|
|
85
|
-
open = false;
|
|
86
|
-
else
|
|
87
|
-
throw new Error(usage);
|
|
88
|
-
}
|
|
89
|
-
if (!agentUrl)
|
|
90
|
-
throw new Error("--runtime-url is required.");
|
|
91
|
-
const dashboard = await startStudio(agentUrl, open, port);
|
|
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);
|
|
92
290
|
console.log(`Studio on ${dashboard.address}`);
|
|
93
291
|
await new Promise((resolve, reject) => {
|
|
94
292
|
const close = () => void dashboard.close().then(resolve, reject);
|
|
@@ -96,8 +294,19 @@ async function main() {
|
|
|
96
294
|
process.once("SIGTERM", close);
|
|
97
295
|
});
|
|
98
296
|
}
|
|
99
|
-
|
|
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
|
+
async function finish(code) {
|
|
302
|
+
process.exitCode = code;
|
|
303
|
+
for (const stream of [process.stdout, process.stderr])
|
|
304
|
+
await new Promise((resolve) => stream.write("", () => resolve()));
|
|
305
|
+
process.exit(code);
|
|
306
|
+
}
|
|
307
|
+
void main().then(() => finish(process.exitCode === undefined ? 0 : Number(process.exitCode)), (error) => {
|
|
100
308
|
console.error(error instanceof Error ? error.message : String(error));
|
|
101
|
-
|
|
102
|
-
|
|
309
|
+
return finish(error instanceof ConfigurationCancelled || error instanceof CliError
|
|
310
|
+
? error.exitCode
|
|
311
|
+
: 1);
|
|
103
312
|
});
|
package/dist/dev-entry.js
CHANGED
package/dist/dev.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
+
/**
|
|
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.
|
|
4
|
+
*/
|
|
5
|
+
export declare function developmentPreflight(args: readonly string[]): string;
|
|
1
6
|
export declare function develop(args: readonly string[]): Promise<number>;
|
package/dist/dev.js
CHANGED
|
@@ -2,13 +2,14 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
5
|
+
/**
|
|
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.
|
|
8
|
+
*/
|
|
9
|
+
export function developmentPreflight(args) {
|
|
6
10
|
if (new Set(args).size !== args.length ||
|
|
7
|
-
args.some((arg) => !["--no-studio", "--no-open"].includes(arg)))
|
|
8
|
-
throw new Error("Usage: nylorun dev [--no-studio] [--no-open]");
|
|
9
|
-
const port = Number(process.env.PORT ?? 8787);
|
|
10
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
11
|
-
throw new Error("PORT must be between 1 and 65535");
|
|
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]");
|
|
12
13
|
const require = createRequire(join(process.cwd(), "package.json"));
|
|
13
14
|
let tsx;
|
|
14
15
|
try {
|
|
@@ -24,6 +25,10 @@ export async function develop(args) {
|
|
|
24
25
|
catch {
|
|
25
26
|
throw new Error("Install @nylorun/studio or use --no-studio");
|
|
26
27
|
}
|
|
28
|
+
return tsx;
|
|
29
|
+
}
|
|
30
|
+
export async function develop(args) {
|
|
31
|
+
const tsx = developmentPreflight(args);
|
|
27
32
|
const child = spawn(process.execPath, [
|
|
28
33
|
tsx,
|
|
29
34
|
"watch",
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentManifest } from "@nylorun/agents";
|
|
2
|
+
/** `nylorun doctor sandbox`: what this machine offers and what a Runtime here would pick. */
|
|
3
|
+
export declare function doctorSandbox(options: {
|
|
4
|
+
json: boolean;
|
|
5
|
+
}): Promise<void>;
|
|
6
|
+
/** 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>;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { release } from "node:os";
|
|
2
|
+
import { probeSandboxBackends, } from "@nylorun/runtime/node";
|
|
3
|
+
const LABEL = {
|
|
4
|
+
microsandbox: "microsandbox VM",
|
|
5
|
+
virtual: "virtual shell",
|
|
6
|
+
};
|
|
7
|
+
/** `nylorun doctor sandbox`: what this machine offers and what a Runtime here would pick. */
|
|
8
|
+
export async function doctorSandbox(options) {
|
|
9
|
+
const report = await probeSandboxBackends();
|
|
10
|
+
if (options.json) {
|
|
11
|
+
console.log(JSON.stringify({ platform: platform(), ...report }, null, 2));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const rows = [["platform", platform()]];
|
|
15
|
+
for (const probe of report.probes)
|
|
16
|
+
rows.push([
|
|
17
|
+
probe.name,
|
|
18
|
+
`${probe.available ? "✓" : "✗"} ${probe.reason ?? ""}${probe.version ? ` · ${probe.version}` : ""}`,
|
|
19
|
+
]);
|
|
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}`]);
|
|
22
|
+
const width = Math.max(...rows.map(([key]) => key.length)) + 2;
|
|
23
|
+
for (const [key, value] of rows)
|
|
24
|
+
console.log(` ${key.padEnd(width)}${value}`);
|
|
25
|
+
if (report.backend === "virtual")
|
|
26
|
+
console.log("\n The virtual shell emulates bash in the Runtime process; it is not a VM boundary.\n" +
|
|
27
|
+
" For hardware isolation use macOS on Apple Silicon or Linux with KVM.");
|
|
28
|
+
}
|
|
29
|
+
/** One line for the dev banner, or undefined when no connected agent declares a sandbox. */
|
|
30
|
+
export async function sandboxBanner(runtimeUrl, serverKey, manifests) {
|
|
31
|
+
const capability = manifests
|
|
32
|
+
.flatMap((manifest) => manifest.capabilities)
|
|
33
|
+
.find((item) => item.sandbox);
|
|
34
|
+
if (!capability)
|
|
35
|
+
return undefined;
|
|
36
|
+
let report;
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch(`${runtimeUrl}/v1/host/sandbox`, {
|
|
39
|
+
headers: { authorization: `Bearer ${serverKey}` },
|
|
40
|
+
signal: AbortSignal.timeout(10_000),
|
|
41
|
+
});
|
|
42
|
+
if (response.ok)
|
|
43
|
+
report = await response.json();
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
if (!report)
|
|
47
|
+
return undefined;
|
|
48
|
+
const doctor = "run `npx nylorun doctor sandbox` for options";
|
|
49
|
+
if (!report.backend)
|
|
50
|
+
return `sandbox: unavailable (${report.reason}) · ${doctor}`;
|
|
51
|
+
const image = capability.sandbox?.image ?? report.defaultImage;
|
|
52
|
+
const network = capability.sandbox?.network?.preset ?? "dev";
|
|
53
|
+
const fellBack = report.preference === "auto" && report.backend !== report.probes[0]?.name;
|
|
54
|
+
return fellBack
|
|
55
|
+
? `sandbox: ${LABEL[report.backend] ?? report.backend} (${report.reason}) · ${doctor}`
|
|
56
|
+
: `sandbox: ${LABEL[report.backend] ?? report.backend}${report.backend === "microsandbox" && image ? ` · image ${image}` : ""} · network: ${network}`;
|
|
57
|
+
}
|
|
58
|
+
function platform() {
|
|
59
|
+
const os = process.platform === "darwin" ? "macOS" : process.platform === "win32" ? "Windows" : process.platform;
|
|
60
|
+
return `${os} (kernel ${release()}) · ${process.arch}`;
|
|
61
|
+
}
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
package/dist/launcher.d.ts
CHANGED
package/dist/launcher.js
CHANGED