@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.
- package/CHANGELOG.md +34 -0
- package/README.md +87 -40
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +132 -220
- package/dist/dev-entry.d.ts +5 -1
- package/dist/dev-entry.js +7 -6
- package/dist/dev.d.ts +45 -4
- package/dist/dev.js +245 -32
- package/dist/doctor.d.ts +25 -3
- package/dist/doctor.js +104 -16
- package/dist/environment.d.ts +4 -1
- package/dist/environment.js +12 -3
- package/dist/launcher.d.ts +5 -5
- package/dist/launcher.js +6 -3
- package/dist/model/configure.d.ts +30 -0
- package/dist/model/configure.js +117 -11
- package/dist/model/host-model.d.ts +5 -3
- package/dist/model/host-model.js +36 -15
- package/dist/project/attach.d.ts +32 -0
- package/dist/project/attach.js +241 -0
- package/dist/project/create-tenant.d.ts +26 -0
- package/dist/project/create-tenant.js +65 -0
- package/dist/project/credentials.d.ts +18 -0
- package/dist/project/credentials.js +64 -0
- package/dist/project/link.d.ts +20 -0
- package/dist/project/link.js +77 -0
- package/dist/project/root.d.ts +9 -0
- package/dist/project/root.js +51 -0
- package/dist/project/seed.d.ts +19 -0
- package/dist/project/seed.js +94 -0
- package/dist/runtime/baseline.d.ts +2 -0
- package/dist/runtime/baseline.js +11 -0
- package/dist/runtime/commands.d.ts +16 -0
- package/dist/runtime/commands.js +292 -0
- package/dist/runtime/ephemeral.d.ts +37 -0
- package/dist/runtime/ephemeral.js +84 -0
- package/dist/runtime/exit-codes.d.ts +9 -0
- package/dist/runtime/exit-codes.js +43 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.js +6 -0
- package/dist/runtime/launcher.d.ts +81 -0
- package/dist/runtime/launcher.js +243 -0
- package/dist/runtime/progress.d.ts +10 -0
- package/dist/runtime/progress.js +8 -0
- package/dist/runtime/version.d.ts +6 -0
- package/dist/runtime/version.js +27 -0
- package/dist/tenant/commands.d.ts +4 -0
- package/dist/tenant/commands.js +262 -0
- package/package.json +6 -3
- package/dist/project-runner.d.ts +0 -20
- package/dist/project-runner.js +0 -141
- package/dist/runtime-host.d.ts +0 -39
- package/dist/runtime-host.js +0 -355
- package/dist/scope.d.ts +0 -65
- package/dist/scope.js +0 -191
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { createAdmin } from "@nylorun/admin";
|
|
4
|
+
import { createClient, isTenantId } from "@nylorun/agents";
|
|
5
|
+
import { CliError } from "../errors.js";
|
|
6
|
+
import { resolveHome } from "../runtime/launcher.js";
|
|
7
|
+
import { readCredentials, removeCredentials, } from "../project/credentials.js";
|
|
8
|
+
import { readLink, removeLink, writeLink, } from "../project/link.js";
|
|
9
|
+
import { requireProjectRoot } from "../project/root.js";
|
|
10
|
+
/** Exact-and-unique name or id match. */
|
|
11
|
+
export function matchTenant(tenants, nameOrId) {
|
|
12
|
+
if (isTenantId(nameOrId)) {
|
|
13
|
+
const byId = tenants.find((t) => t.id === nameOrId);
|
|
14
|
+
if (!byId)
|
|
15
|
+
throw new CliError(`No Tenant with id ${nameOrId}.`, 1);
|
|
16
|
+
return byId;
|
|
17
|
+
}
|
|
18
|
+
const matches = tenants.filter((t) => t.name === nameOrId);
|
|
19
|
+
if (matches.length === 0) {
|
|
20
|
+
throw new CliError(`No Tenant named ${JSON.stringify(nameOrId)}.`, 1);
|
|
21
|
+
}
|
|
22
|
+
if (matches.length > 1) {
|
|
23
|
+
throw new CliError(`Tenant name ${JSON.stringify(nameOrId)} is not unique (${matches.length} matches). Use the Tenant id.`, 1);
|
|
24
|
+
}
|
|
25
|
+
return matches[0];
|
|
26
|
+
}
|
|
27
|
+
function localAdmin(home) {
|
|
28
|
+
return createAdmin(home ? { home } : undefined);
|
|
29
|
+
}
|
|
30
|
+
export async function tenantCommand(args) {
|
|
31
|
+
const [verb, ...rest] = args;
|
|
32
|
+
if (!verb || verb === "--help" || verb === "-h") {
|
|
33
|
+
console.log(`nylorun tenant current|list [--json]|use <name-or-id>|status [--json]|reset [--sessions|--sandboxes|--all] [--yes]|delete <name-or-id> [--yes]`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const home = resolveHome();
|
|
37
|
+
if (verb === "current") {
|
|
38
|
+
const root = requireProjectRoot();
|
|
39
|
+
const link = await readLink(root);
|
|
40
|
+
if (!link) {
|
|
41
|
+
console.log("No Project link. Run nylorun dev to create one.");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const admin = localAdmin(home);
|
|
45
|
+
const tenants = await admin.listTenants();
|
|
46
|
+
const match = tenants.find((t) => t.id === link.tenantId);
|
|
47
|
+
const name = match?.name ?? match?.envelope?.name ?? "(unknown)";
|
|
48
|
+
console.log(`${name} ${link.tenantId}`);
|
|
49
|
+
console.log(`Host ${link.hostUrl} ${link.hostId}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (verb === "list") {
|
|
53
|
+
const json = rest.includes("--json");
|
|
54
|
+
if (rest.some((a) => a !== "--json")) {
|
|
55
|
+
throw new CliError("Usage: nylorun tenant list [--json]", 2);
|
|
56
|
+
}
|
|
57
|
+
const admin = localAdmin(home);
|
|
58
|
+
const tenants = await admin.listTenants();
|
|
59
|
+
if (json) {
|
|
60
|
+
console.log(JSON.stringify(tenants, null, 2));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (tenants.length === 0) {
|
|
64
|
+
console.log("No Tenants on this Host.");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const tenant of tenants) {
|
|
68
|
+
const name = tenant.name ?? "(unreadable)";
|
|
69
|
+
console.log(`${name} ${tenant.id} ${tenant.state}`);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (verb === "use") {
|
|
74
|
+
const nameOrId = rest[0];
|
|
75
|
+
if (!nameOrId || rest.length > 1) {
|
|
76
|
+
throw new CliError("Usage: nylorun tenant use <name-or-id>", 2);
|
|
77
|
+
}
|
|
78
|
+
const root = requireProjectRoot();
|
|
79
|
+
const admin = localAdmin(home);
|
|
80
|
+
const status = await admin.status();
|
|
81
|
+
const hostId = status.host?.hostId;
|
|
82
|
+
if (!hostId) {
|
|
83
|
+
throw new CliError(`Host at ${admin.url} did not report hostId in admin status.`, 1);
|
|
84
|
+
}
|
|
85
|
+
const tenants = await admin.listTenants();
|
|
86
|
+
const selected = matchTenant(tenants, nameOrId);
|
|
87
|
+
console.warn("Sharing a Tenant shares executor registrations across Projects that link to it.");
|
|
88
|
+
const credentials = await readCredentials(root);
|
|
89
|
+
if (!credentials) {
|
|
90
|
+
throw new CliError("No Project credentials. Run nylorun dev to create a Tenant first, or copy credentials for the target Tenant.", 1);
|
|
91
|
+
}
|
|
92
|
+
const client = createClient({
|
|
93
|
+
url: admin.url,
|
|
94
|
+
key: credentials.applicationKey,
|
|
95
|
+
tenant: selected.id,
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
await client.transport.json("/v1/tenant", "GET");
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new CliError(`Credentials do not authorize Tenant ${selected.id}. Create a new Tenant with nylorun dev instead of reusing another Project's Tenant without its key.`, 1);
|
|
102
|
+
}
|
|
103
|
+
await writeLink(root, {
|
|
104
|
+
format: 1,
|
|
105
|
+
hostUrl: admin.url,
|
|
106
|
+
hostId,
|
|
107
|
+
tenantId: selected.id,
|
|
108
|
+
});
|
|
109
|
+
console.log(`Linked to ${selected.name ?? selected.id} ${selected.id}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (verb === "status") {
|
|
113
|
+
const json = rest.includes("--json");
|
|
114
|
+
if (rest.some((a) => a !== "--json")) {
|
|
115
|
+
throw new CliError("Usage: nylorun tenant status [--json]", 2);
|
|
116
|
+
}
|
|
117
|
+
const root = requireProjectRoot();
|
|
118
|
+
const link = await readLink(root);
|
|
119
|
+
const credentials = await readCredentials(root);
|
|
120
|
+
if (!link || !credentials) {
|
|
121
|
+
throw new CliError("No Project link. Run nylorun dev to create one.", 1);
|
|
122
|
+
}
|
|
123
|
+
const client = createClient({
|
|
124
|
+
url: link.hostUrl,
|
|
125
|
+
key: credentials.applicationKey,
|
|
126
|
+
tenant: link.tenantId,
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
const body = await client.transport.json("/v1/tenant", "GET");
|
|
130
|
+
if (json) {
|
|
131
|
+
console.log(JSON.stringify(body, null, 2));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
console.log(`${body.tenant.name} ${body.tenant.id}`);
|
|
135
|
+
console.log(`path ${body.path}`);
|
|
136
|
+
console.log(`checks ${Object.entries(body.checks)
|
|
137
|
+
.map(([k, v]) => `${k}=${v ? "ok" : "fail"}`)
|
|
138
|
+
.join(" ")}`);
|
|
139
|
+
console.log(`counts sessions=${body.counts.sessions} running=${body.counts.runningSessions} pending=${body.counts.pendingActions}`);
|
|
140
|
+
console.log(`sandbox ${body.sandbox.backend ?? "none"}`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Quarantined / opaque — fall back to admin status.
|
|
145
|
+
}
|
|
146
|
+
const admin = localAdmin(home);
|
|
147
|
+
try {
|
|
148
|
+
const body = await admin.getTenant(link.tenantId);
|
|
149
|
+
if (json) {
|
|
150
|
+
console.log(JSON.stringify(body, null, 2));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
console.log(`${body.name ?? "(unreadable)"} ${body.id} ${body.state}`);
|
|
154
|
+
if (body.quarantine) {
|
|
155
|
+
console.log(`reason ${body.quarantine.code}: ${body.quarantine.message}`);
|
|
156
|
+
console.log(`repair ${body.quarantine.repair}`);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
throw new CliError(error instanceof Error
|
|
162
|
+
? error.message
|
|
163
|
+
: `Tenant ${link.tenantId} is not reachable.`, 1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (verb === "reset") {
|
|
167
|
+
const yes = rest.includes("--yes");
|
|
168
|
+
const flags = rest.filter((a) => a !== "--yes");
|
|
169
|
+
let scope = "sessions";
|
|
170
|
+
if (flags.includes("--all"))
|
|
171
|
+
scope = "all";
|
|
172
|
+
else if (flags.includes("--sandboxes"))
|
|
173
|
+
scope = "sandboxes";
|
|
174
|
+
else if (flags.includes("--sessions") || flags.length === 0)
|
|
175
|
+
scope = "sessions";
|
|
176
|
+
else
|
|
177
|
+
throw new CliError("Usage: nylorun tenant reset [--sessions|--sandboxes|--all] [--yes]", 2);
|
|
178
|
+
if (flags.filter((f) => ["--sessions", "--sandboxes", "--all"].includes(f)).length > 1) {
|
|
179
|
+
throw new CliError("Pass only one of --sessions, --sandboxes, --all.", 2);
|
|
180
|
+
}
|
|
181
|
+
const root = requireProjectRoot();
|
|
182
|
+
const link = await readLink(root);
|
|
183
|
+
const credentials = await readCredentials(root);
|
|
184
|
+
if (!link || !credentials) {
|
|
185
|
+
throw new CliError("No Project link. Run nylorun dev first.", 1);
|
|
186
|
+
}
|
|
187
|
+
const admin = localAdmin(home);
|
|
188
|
+
const tenants = await admin.listTenants();
|
|
189
|
+
const match = tenants.find((t) => t.id === link.tenantId);
|
|
190
|
+
const name = match?.name ?? link.tenantId;
|
|
191
|
+
if (scope === "all" && !yes) {
|
|
192
|
+
await confirmOrThrow(`Reset ALL data for Tenant ${name} (${link.tenantId})? Link and credentials are kept. [y/N] `);
|
|
193
|
+
}
|
|
194
|
+
const client = createClient({
|
|
195
|
+
url: link.hostUrl,
|
|
196
|
+
key: credentials.applicationKey,
|
|
197
|
+
tenant: link.tenantId,
|
|
198
|
+
});
|
|
199
|
+
try {
|
|
200
|
+
await client.transport.json("/v1/tenant/reset", "POST", {
|
|
201
|
+
requestId: randomUUID(),
|
|
202
|
+
scope,
|
|
203
|
+
activeWork: "drain",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
throw new CliError(error instanceof Error
|
|
208
|
+
? error.message
|
|
209
|
+
: `Tenant reset failed: ${String(error)}`, 1);
|
|
210
|
+
}
|
|
211
|
+
console.log(`Reset Tenant ${name} (${scope}).`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (verb === "delete") {
|
|
215
|
+
const yes = rest.includes("--yes");
|
|
216
|
+
const nameOrId = rest.find((a) => a !== "--yes");
|
|
217
|
+
if (!nameOrId || rest.filter((a) => a !== "--yes").length !== 1) {
|
|
218
|
+
throw new CliError("Usage: nylorun tenant delete <name-or-id> [--yes]", 2);
|
|
219
|
+
}
|
|
220
|
+
const root = requireProjectRoot();
|
|
221
|
+
const admin = localAdmin(home);
|
|
222
|
+
const tenants = await admin.listTenants();
|
|
223
|
+
const selected = matchTenant(tenants, nameOrId);
|
|
224
|
+
if (!yes) {
|
|
225
|
+
await confirmOrThrow(`Delete Tenant ${selected.name ?? selected.id} (${selected.id})?\nThe KEK moves to trash with the Tenant directory. [y/N] `);
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
await admin.deleteTenant(selected.id, { activeWork: "refuse" });
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
232
|
+
if (/active_work|409|live work/i.test(message)) {
|
|
233
|
+
throw new CliError(`Tenant ${selected.id} has live work. Retry with the Host idle, or drain sessions first.`, 1);
|
|
234
|
+
}
|
|
235
|
+
throw new CliError(message, 1);
|
|
236
|
+
}
|
|
237
|
+
const link = await readLink(root);
|
|
238
|
+
if (link?.tenantId === selected.id) {
|
|
239
|
+
await removeLink(root);
|
|
240
|
+
await removeCredentials(root);
|
|
241
|
+
console.log("Removed Project link and credentials for the deleted Tenant.");
|
|
242
|
+
}
|
|
243
|
+
console.log(`Deleted Tenant ${selected.name ?? selected.id}.`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
throw new CliError(`Unknown tenant command ${verb}.\nUsage: nylorun tenant current|list|use|status|reset|delete`, 2);
|
|
247
|
+
}
|
|
248
|
+
async function confirmOrThrow(prompt) {
|
|
249
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
250
|
+
throw new CliError("Confirmation required; pass --yes for non-interactive use.", 2);
|
|
251
|
+
}
|
|
252
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
253
|
+
try {
|
|
254
|
+
const answer = (await rl.question(prompt)).trim().toLowerCase();
|
|
255
|
+
if (answer !== "y" && answer !== "yes") {
|
|
256
|
+
throw new CliError("Cancelled.", 1);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
rl.close();
|
|
261
|
+
}
|
|
262
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Nylorun local development CLI",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"prepack": "npm run build"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@nylorun/agents": "0.
|
|
34
|
-
"@nylorun/
|
|
33
|
+
"@nylorun/agents": "0.6.0-beta",
|
|
34
|
+
"@nylorun/admin": "0.2.0-beta",
|
|
35
35
|
"@earendil-works/pi-ai": "0.85.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
@@ -41,5 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"bin": {
|
|
43
43
|
"nylorun": "dist/cli.js"
|
|
44
|
+
},
|
|
45
|
+
"nylorun": {
|
|
46
|
+
"runtime": "0.10.0-beta"
|
|
44
47
|
}
|
|
45
48
|
}
|
package/dist/project-runner.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type Scope } from "./scope.js";
|
|
2
|
-
export interface LocalCredentials {
|
|
3
|
-
serverKey: string;
|
|
4
|
-
executors: Record<string, string>;
|
|
5
|
-
}
|
|
6
|
-
export declare function localCredentials(path: string): Promise<LocalCredentials>;
|
|
7
|
-
export interface ExecutorScopeToken {
|
|
8
|
-
token: string;
|
|
9
|
-
agentId: string;
|
|
10
|
-
implementationVersion: string;
|
|
11
|
-
}
|
|
12
|
-
/** Registers or rotates this project's executor credentials on the running host. */
|
|
13
|
-
export declare function putExecutors(url: string, serverKey: string, executors: readonly ExecutorScopeToken[]): Promise<void>;
|
|
14
|
-
export interface RunProjectOptions {
|
|
15
|
-
studio?: boolean;
|
|
16
|
-
open?: boolean;
|
|
17
|
-
autostart?: boolean;
|
|
18
|
-
scope?: Scope;
|
|
19
|
-
}
|
|
20
|
-
export declare function runProject(entry: string, options?: RunProjectOptions): Promise<void>;
|
package/dist/project-runner.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { randomBytes } from "node:crypto";
|
|
3
|
-
import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import { pathToFileURL } from "node:url";
|
|
6
|
-
import { createClient, connectAgents, } from "@nylorun/agents";
|
|
7
|
-
import { ensureHostModel } from "./model/host-model.js";
|
|
8
|
-
import { CliError } from "./errors.js";
|
|
9
|
-
import { ensureDataDir, loadScopeEnvironment, probeHealth, resolveScope, } from "./scope.js";
|
|
10
|
-
import { ensureRuntime } from "./runtime-host.js";
|
|
11
|
-
export async function localCredentials(path) {
|
|
12
|
-
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
13
|
-
try {
|
|
14
|
-
const value = JSON.parse(await readFile(path, "utf8"));
|
|
15
|
-
if (typeof value.serverKey !== "string" || !value.executors)
|
|
16
|
-
throw new Error("Invalid local credentials");
|
|
17
|
-
await chmod(path, 0o600);
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
if (error.code !== "ENOENT")
|
|
22
|
-
throw error;
|
|
23
|
-
const value = { serverKey: randomBytes(32).toString("hex"), executors: {} };
|
|
24
|
-
await writeFile(path, JSON.stringify(value), { mode: 0o600, flag: "wx" });
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/** Registers or rotates this project's executor credentials on the running host. */
|
|
29
|
-
export async function putExecutors(url, serverKey, executors) {
|
|
30
|
-
const response = await fetch(`${url}/v1/executors`, {
|
|
31
|
-
method: "PUT",
|
|
32
|
-
headers: {
|
|
33
|
-
authorization: `Bearer ${serverKey}`,
|
|
34
|
-
"content-type": "application/json",
|
|
35
|
-
},
|
|
36
|
-
body: JSON.stringify({ executors }),
|
|
37
|
-
});
|
|
38
|
-
if (!response.ok)
|
|
39
|
-
throw new CliError(`The Runtime at ${url} rejected this project's executors (${response.status}): ${await response
|
|
40
|
-
.text()
|
|
41
|
-
.catch(() => "")}`);
|
|
42
|
-
}
|
|
43
|
-
export async function runProject(entry, options = {}) {
|
|
44
|
-
const scope = options.scope ?? resolveScope();
|
|
45
|
-
loadScopeEnvironment(scope);
|
|
46
|
-
const registry = await import(pathToFileURL(resolve(entry)).href);
|
|
47
|
-
if (!Array.isArray(registry.agents) || !registry.agents.length)
|
|
48
|
-
throw new Error(`${entry} must export a non-empty agents array`);
|
|
49
|
-
const agents = registry.agents;
|
|
50
|
-
await ensureDataDir(scope);
|
|
51
|
-
const credentials = await localCredentials(scope.credentialsPath);
|
|
52
|
-
const version = process.env.NYLORUN_IMPLEMENTATION_VERSION ?? "dev";
|
|
53
|
-
const scopes = agents.map((agent) => {
|
|
54
|
-
const token = (credentials.executors[agent.id] ??=
|
|
55
|
-
randomBytes(32).toString("hex"));
|
|
56
|
-
return { token, agentId: agent.id, implementationVersion: version };
|
|
57
|
-
});
|
|
58
|
-
await writeFile(scope.credentialsPath, JSON.stringify(credentials), {
|
|
59
|
-
mode: 0o600,
|
|
60
|
-
});
|
|
61
|
-
const url = scope.url;
|
|
62
|
-
let studio;
|
|
63
|
-
const connections = [];
|
|
64
|
-
let watchdog;
|
|
65
|
-
let stopping;
|
|
66
|
-
const close = () => (stopping ??= (async () => {
|
|
67
|
-
if (watchdog)
|
|
68
|
-
clearInterval(watchdog);
|
|
69
|
-
await Promise.allSettled(connections.map((c) => c.close()));
|
|
70
|
-
await studio?.close().catch(() => { });
|
|
71
|
-
for (const agent of agents)
|
|
72
|
-
await agent.close?.();
|
|
73
|
-
})());
|
|
74
|
-
// The Runtime is a separate, persistent process now, so shutting this one down leaves it up.
|
|
75
|
-
const stop = () => void close().finally(() => (process.exitCode = 0));
|
|
76
|
-
process.on("SIGINT", stop);
|
|
77
|
-
process.on("SIGTERM", stop);
|
|
78
|
-
try {
|
|
79
|
-
await ensureRuntime(scope, { autostart: options.autostart !== false });
|
|
80
|
-
await putExecutors(url, credentials.serverKey, scopes);
|
|
81
|
-
if (process.env.NYLORUN_DEV_MODEL !== "fixture")
|
|
82
|
-
await ensureHostModel({ runtimeUrl: url, serverKey: credentials.serverKey });
|
|
83
|
-
const client = createClient({ url, key: credentials.serverKey });
|
|
84
|
-
for (let index = 0; index < agents.length; index++) {
|
|
85
|
-
await client.saveAgent(agents[index], {
|
|
86
|
-
implementationVersion: version,
|
|
87
|
-
});
|
|
88
|
-
const connection = connectAgents({
|
|
89
|
-
agents: [agents[index]],
|
|
90
|
-
implementationVersion: version,
|
|
91
|
-
runtime: { url, key: scopes[index].token },
|
|
92
|
-
onError: (error) => console.error("Executor:", error instanceof Error ? error.message : error),
|
|
93
|
-
});
|
|
94
|
-
connections.push(connection);
|
|
95
|
-
await connection.ready;
|
|
96
|
-
}
|
|
97
|
-
if (options.studio) {
|
|
98
|
-
const modulePath = createRequire(join(process.cwd(), "package.json")).resolve("@nylorun/studio");
|
|
99
|
-
const module = await import(pathToFileURL(modulePath).href);
|
|
100
|
-
studio = await module.startStudio({
|
|
101
|
-
runtimeUrl: url,
|
|
102
|
-
serverKey: credentials.serverKey,
|
|
103
|
-
open: options.open !== false,
|
|
104
|
-
});
|
|
105
|
-
console.log(`Studio on ${studio.address}`);
|
|
106
|
-
}
|
|
107
|
-
console.log(`Local project ready at ${url}; ${agents.length} connected agent(s).`);
|
|
108
|
-
const sandbox = await (await import("./doctor.js")).sandboxBanner(url, credentials.serverKey, agents.map((agent) => agent.manifest));
|
|
109
|
-
if (sandbox)
|
|
110
|
-
console.log(sandbox);
|
|
111
|
-
await new Promise((settle) => {
|
|
112
|
-
const finish = () => {
|
|
113
|
-
if (!stopping)
|
|
114
|
-
void close();
|
|
115
|
-
settle();
|
|
116
|
-
};
|
|
117
|
-
process.once("SIGINT", finish);
|
|
118
|
-
process.once("SIGTERM", finish);
|
|
119
|
-
// Without a child to watch, a Runtime that dies would leave this process connected to
|
|
120
|
-
// nothing; three consecutive failed probes end the run with a pointer to the log.
|
|
121
|
-
let failures = 0;
|
|
122
|
-
watchdog = setInterval(async () => {
|
|
123
|
-
if (stopping)
|
|
124
|
-
return;
|
|
125
|
-
if ((await probeHealth(url, 2000)).health)
|
|
126
|
-
return void (failures = 0);
|
|
127
|
-
if (++failures < 3)
|
|
128
|
-
return;
|
|
129
|
-
console.error('Runtime stopped unexpectedly; see "nylorun logs".');
|
|
130
|
-
process.exitCode = 1;
|
|
131
|
-
finish();
|
|
132
|
-
}, 5000);
|
|
133
|
-
watchdog.unref();
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
finally {
|
|
137
|
-
process.removeListener("SIGINT", stop);
|
|
138
|
-
process.removeListener("SIGTERM", stop);
|
|
139
|
-
await close();
|
|
140
|
-
}
|
|
141
|
-
}
|
package/dist/runtime-host.d.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { type RuntimeState, type Scope } from "./scope.js";
|
|
2
|
-
export type HostState = "running" | "stopped" | "stale" | "unhealthy" | "pid-recycled" | "port-conflict";
|
|
3
|
-
export interface Status {
|
|
4
|
-
state: HostState;
|
|
5
|
-
scope: Scope;
|
|
6
|
-
runtime?: RuntimeState;
|
|
7
|
-
version?: string;
|
|
8
|
-
scopeId?: string;
|
|
9
|
-
pid?: number;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Derived from the resolved server entry rather than a "./package.json" export, so the version
|
|
13
|
-
* is readable for every installed Runtime — including one too old to declare that export,
|
|
14
|
-
* which is exactly when skew detection matters.
|
|
15
|
-
*/
|
|
16
|
-
export declare const cliRuntimeVersion: () => string | undefined;
|
|
17
|
-
export declare function hostStatus(scope: Scope): Promise<Status>;
|
|
18
|
-
export declare function startHost(scope: Scope, options?: {
|
|
19
|
-
foreground?: boolean;
|
|
20
|
-
restart?: boolean;
|
|
21
|
-
}): Promise<Status>;
|
|
22
|
-
export declare function stopHost(scope: Scope, options?: {
|
|
23
|
-
timeoutMs?: number;
|
|
24
|
-
}): Promise<"stopped" | "not-running">;
|
|
25
|
-
export interface EnsureOptions {
|
|
26
|
-
autostart: boolean;
|
|
27
|
-
quiet?: boolean;
|
|
28
|
-
}
|
|
29
|
-
export declare function ensureRuntime(scope: Scope, options: EnsureOptions): Promise<Status>;
|
|
30
|
-
/**
|
|
31
|
-
* A daemon from an older install would reject executor registration with a bare 404, so the
|
|
32
|
-
* commands that depend on that route fail early with the remedy instead.
|
|
33
|
-
*/
|
|
34
|
-
export declare function assertCompatible(scope: Scope, status: Status): void;
|
|
35
|
-
export declare function warnIncompatible(scope: Scope, status: Status): void;
|
|
36
|
-
export declare function tailLog(scope: Scope, options: {
|
|
37
|
-
follow: boolean;
|
|
38
|
-
lines: number;
|
|
39
|
-
}): Promise<void>;
|