nylorun 0.14.0-beta → 0.15.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 +105 -0
- package/README.md +226 -144
- package/dist/cli.js +141 -47
- package/dist/client/agents.d.ts +6 -0
- package/dist/client/agents.js +148 -0
- package/dist/client/artifacts.d.ts +4 -0
- package/dist/client/artifacts.js +210 -0
- package/dist/client/doctor.d.ts +7 -2
- package/dist/client/doctor.js +14 -13
- package/dist/client/environment-manifests.d.ts +10 -0
- package/dist/client/environment-manifests.js +173 -0
- package/dist/client/environments.d.ts +4 -0
- package/dist/client/environments.js +295 -0
- package/dist/client/io.d.ts +31 -0
- package/dist/client/io.js +72 -0
- package/dist/{stack → client}/mcp.d.ts +2 -2
- package/dist/{stack → client}/mcp.js +14 -12
- package/dist/client/model/pi-ai.d.ts +3 -3
- package/dist/client/model/pi-ai.js +4 -4
- package/dist/client/models.d.ts +13 -0
- package/dist/client/models.js +203 -0
- package/dist/client/runtime.d.ts +40 -0
- package/dist/client/runtime.js +134 -0
- package/dist/client/sessions.d.ts +4 -0
- package/dist/client/sessions.js +295 -0
- package/dist/client/settings.d.ts +12 -0
- package/dist/client/settings.js +103 -0
- package/dist/client/signing-keys.d.ts +3 -0
- package/dist/client/signing-keys.js +32 -0
- package/dist/client/target.d.ts +33 -0
- package/dist/client/target.js +65 -0
- package/dist/client/tenant.d.ts +22 -0
- package/dist/client/tenant.js +176 -0
- package/dist/doctor.js +3 -3
- package/dist/nylo.js +20 -4
- package/dist/sandbox/cluster-file.d.ts +1 -1
- package/dist/sandbox/commands.d.ts +1 -1
- package/dist/sandbox/commands.js +7 -7
- package/dist/sandbox/manifests.d.ts +1 -1
- package/dist/sandbox/manifests.js +1 -1
- package/dist/sandbox/probe.d.ts +1 -1
- package/dist/stack/commands.d.ts +9 -4
- package/dist/stack/commands.js +22 -26
- package/dist/stack/compose-file.d.ts +1 -1
- package/dist/stack/compose-file.js +12 -12
- package/dist/stack/env-file.d.ts +2 -2
- package/dist/stack/env-file.js +2 -2
- package/dist/stack/images.d.ts +1 -1
- package/dist/stack/images.js +1 -1
- package/dist/stack/index.d.ts +1 -10
- package/dist/stack/index.js +3 -19
- package/dist/stack/keys.d.ts +2 -2
- package/dist/stack/keys.js +14 -11
- package/dist/stack/paths.d.ts +1 -1
- package/dist/stack/prepare.d.ts +1 -1
- package/dist/stack/stacks.js +1 -1
- package/dist/telemetry.d.ts +2 -2
- package/dist/telemetry.js +4 -4
- package/package.json +6 -5
- package/dist/client/access/commands.d.ts +0 -8
- package/dist/client/access/commands.js +0 -33
- package/dist/client/cli.d.ts +0 -2
- package/dist/client/cli.js +0 -106
- package/dist/client/installation/commands.d.ts +0 -7
- package/dist/client/installation/commands.js +0 -82
- package/dist/client/project/connection.d.ts +0 -36
- package/dist/client/project/connection.js +0 -80
- package/dist/client/project/env.d.ts +0 -3
- package/dist/client/project/env.js +0 -19
- package/dist/stack/sandbox.d.ts +0 -4
- package/dist/stack/sandbox.js +0 -107
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nylorun sessions`: the Tenant's sessions (`/v1/sessions`, Runtime API): open one, send it a
|
|
3
|
+
* message, answer what it waits for, watch its events, and read its history, usage, calls and
|
|
4
|
+
* traces. `exec` runs a command in the session's sandbox, as an app's own tool would.
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { CliError } from "../errors.js";
|
|
8
|
+
import { integerFlag, parseFlags, parseJsonArgument, positional, printCursor, printJson, printTable, runtimeCall, runtimeClient, runtimeFlagsUsage, usageError, } from "./runtime.js";
|
|
9
|
+
export const sessionsUsage = `nylorun sessions <command>
|
|
10
|
+
|
|
11
|
+
The Tenant's sessions, through the Runtime API with an application key (or NYLORUN_TOKEN).
|
|
12
|
+
|
|
13
|
+
list [--agent <id>] [--status <status>] [--environment <id>] [--owner <user>]
|
|
14
|
+
[--limit <n>] [--cursor <c>] [--json]
|
|
15
|
+
create --agent <id> --owner <user> [--environment <id>] [--id <id>] [--info <json>]
|
|
16
|
+
open a session; prints its id
|
|
17
|
+
get <id> the session: status, active turn, what it waits for
|
|
18
|
+
send <id> [<text>] [--data <json>] [--file <artifact>[@<version>]]... [--idempotency-key <k>]
|
|
19
|
+
start a turn with text, JSON data or files (artifacts)
|
|
20
|
+
approve <id> <interaction> [--deny] approve (or deny) a held tool call
|
|
21
|
+
respond <id> <interaction> <json> answer a question the agent asked
|
|
22
|
+
cancel <id> [--reason <text>] cancel the running turn
|
|
23
|
+
watch <id> [--follow] [--cursor <c>]
|
|
24
|
+
the session's events as JSON lines until interrupted;
|
|
25
|
+
--follow adds the sessions of agents it uses as tools
|
|
26
|
+
history <id> [--agent <a>] [--limit <n>] [--cursor <c>]
|
|
27
|
+
usage <id> [--turn <id>] tokens and cost
|
|
28
|
+
calls <id> [--tool] [--turn <id>] [--limit <n>] [--cursor <c>]
|
|
29
|
+
model calls (--tool: tool calls)
|
|
30
|
+
trace <id> <turn> the turn's spans as OTLP JSON
|
|
31
|
+
exec <id> [--timeout <s>] -- <command>...
|
|
32
|
+
run a command in the session's sandbox (bash); exits with
|
|
33
|
+
its exit code
|
|
34
|
+
|
|
35
|
+
${runtimeFlagsUsage}`;
|
|
36
|
+
async function list(deps, args) {
|
|
37
|
+
const usage = "nylorun sessions list [--agent <id>] [--status <s>] [--environment <id>] [--owner <user>] [--limit <n>] [--cursor <c>] [--json]";
|
|
38
|
+
const flags = parseFlags(args, { booleans: ["--json"], values: ["--agent", "--status", "--environment", "--owner", "--limit", "--cursor"] }, usage);
|
|
39
|
+
positional(flags, 0, usage);
|
|
40
|
+
const { client } = await runtimeClient(deps, flags);
|
|
41
|
+
const value = (name) => flags.values.get(name);
|
|
42
|
+
const limit = integerFlag(flags, "--limit");
|
|
43
|
+
const page = await runtimeCall(() => client.sessions.page({
|
|
44
|
+
...(value("--agent") ? { agentId: value("--agent") } : {}),
|
|
45
|
+
...(value("--status") ? { status: value("--status") } : {}),
|
|
46
|
+
...(value("--environment") ? { environmentId: value("--environment") } : {}),
|
|
47
|
+
...(value("--owner") ? { ownerUserId: value("--owner") } : {}),
|
|
48
|
+
...(limit === undefined ? {} : { limit }),
|
|
49
|
+
...(value("--cursor") ? { cursor: value("--cursor") } : {}),
|
|
50
|
+
}));
|
|
51
|
+
if (flags.booleans.has("--json")) {
|
|
52
|
+
printJson(deps, page);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
if (page.sessions.length === 0)
|
|
56
|
+
deps.out("No sessions.");
|
|
57
|
+
else
|
|
58
|
+
printTable(deps, [
|
|
59
|
+
["ID", "AGENT", "OWNER", "STATUS", "ENVIRONMENT", "LAST EVENT"],
|
|
60
|
+
...page.sessions.map((session) => [
|
|
61
|
+
session.id,
|
|
62
|
+
session.agentId,
|
|
63
|
+
session.ownerUserId,
|
|
64
|
+
session.status,
|
|
65
|
+
session.environmentId ?? "-",
|
|
66
|
+
session.lastEventAt ?? "-",
|
|
67
|
+
]),
|
|
68
|
+
]);
|
|
69
|
+
printCursor(deps, page.nextCursor);
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
async function create(deps, args) {
|
|
73
|
+
const usage = "nylorun sessions create --agent <id> --owner <user> [--environment <id>] [--id <id>] [--info <json>]";
|
|
74
|
+
const flags = parseFlags(args, { values: ["--agent", "--owner", "--environment", "--id", "--info"] }, usage);
|
|
75
|
+
positional(flags, 0, usage);
|
|
76
|
+
const agentId = flags.values.get("--agent");
|
|
77
|
+
const ownerUserId = flags.values.get("--owner");
|
|
78
|
+
if (!agentId || !ownerUserId)
|
|
79
|
+
throw usageError(`Usage: ${usage}`);
|
|
80
|
+
const info = flags.values.get("--info");
|
|
81
|
+
const parsedInfo = info === undefined ? undefined : parseJsonArgument(info, "--info");
|
|
82
|
+
if (parsedInfo !== undefined && (!parsedInfo || typeof parsedInfo !== "object" || Array.isArray(parsedInfo)))
|
|
83
|
+
throw usageError("--info must be a JSON object.");
|
|
84
|
+
const environment = flags.values.get("--environment");
|
|
85
|
+
const id = flags.values.get("--id");
|
|
86
|
+
const { client } = await runtimeClient(deps, flags);
|
|
87
|
+
const session = await runtimeCall(() => client.createSession({
|
|
88
|
+
agentId,
|
|
89
|
+
ownerUserId,
|
|
90
|
+
...(id ? { id } : {}),
|
|
91
|
+
...(environment ? { environment } : {}),
|
|
92
|
+
...(parsedInfo ? { info: parsedInfo } : {}),
|
|
93
|
+
}));
|
|
94
|
+
deps.out(session.id);
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
async function get(deps, args) {
|
|
98
|
+
const usage = "nylorun sessions get <id>";
|
|
99
|
+
const flags = parseFlags(args, {}, usage);
|
|
100
|
+
const [id] = positional(flags, 1, usage);
|
|
101
|
+
const { client } = await runtimeClient(deps, flags);
|
|
102
|
+
printJson(deps, await runtimeCall(() => client.session(id).inspect()));
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
/** `artifact` or `artifact@version`, as a file part. */
|
|
106
|
+
function filePart(value) {
|
|
107
|
+
const at = value.lastIndexOf("@");
|
|
108
|
+
if (at <= 0)
|
|
109
|
+
return { type: "file", artifactId: value };
|
|
110
|
+
const version = value.slice(at + 1);
|
|
111
|
+
if (!/^[1-9]\d*$/.test(version))
|
|
112
|
+
throw usageError(`--file ${value}: the version must be a positive number.`);
|
|
113
|
+
return { type: "file", artifactId: value.slice(0, at), version: Number(version) };
|
|
114
|
+
}
|
|
115
|
+
async function send(deps, args) {
|
|
116
|
+
const usage = "nylorun sessions send <id> [<text>] [--data <json>] [--file <artifact>[@<version>]]... [--idempotency-key <k>]";
|
|
117
|
+
const flags = parseFlags(args, { values: ["--data", "--idempotency-key"], lists: ["--file"] }, usage);
|
|
118
|
+
if (flags.rest.length < 1 || flags.rest.length > 2)
|
|
119
|
+
throw usageError(`Usage: ${usage}`);
|
|
120
|
+
const [id, text] = flags.rest;
|
|
121
|
+
const data = flags.values.get("--data");
|
|
122
|
+
const files = flags.lists.get("--file") ?? [];
|
|
123
|
+
if (data !== undefined && (text !== undefined || files.length))
|
|
124
|
+
throw usageError("--data is a message of its own: pass it without text or --file.");
|
|
125
|
+
if (data === undefined && text === undefined && files.length === 0)
|
|
126
|
+
throw usageError(`Nothing to send. Usage: ${usage}`);
|
|
127
|
+
const parts = files.map(filePart);
|
|
128
|
+
const options = { idempotencyKey: flags.values.get("--idempotency-key") ?? randomUUID() };
|
|
129
|
+
const { client } = await runtimeClient(deps, flags);
|
|
130
|
+
const session = client.session(id);
|
|
131
|
+
const accepted = await runtimeCall(() => data !== undefined
|
|
132
|
+
? session.input(parseJsonArgument(data, "--data"), options)
|
|
133
|
+
: parts.length
|
|
134
|
+
? session.inputParts([...(text ? [{ type: "text", text }] : []), ...parts], options)
|
|
135
|
+
: session.input(text, options));
|
|
136
|
+
printJson(deps, accepted);
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
async function approve(deps, args) {
|
|
140
|
+
const usage = "nylorun sessions approve <id> <interaction> [--deny]";
|
|
141
|
+
const flags = parseFlags(args, { booleans: ["--deny"] }, usage);
|
|
142
|
+
const [id, interaction] = positional(flags, 2, usage);
|
|
143
|
+
const { client } = await runtimeClient(deps, flags);
|
|
144
|
+
printJson(deps, await runtimeCall(() => client.session(id).approve(interaction, !flags.booleans.has("--deny"), { idempotencyKey: randomUUID() })));
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
async function respond(deps, args) {
|
|
148
|
+
const usage = "nylorun sessions respond <id> <interaction> <json>";
|
|
149
|
+
const flags = parseFlags(args, {}, usage);
|
|
150
|
+
const [id, interaction, value] = positional(flags, 3, usage);
|
|
151
|
+
const answer = parseJsonArgument(value, "The answer");
|
|
152
|
+
const { client } = await runtimeClient(deps, flags);
|
|
153
|
+
printJson(deps, await runtimeCall(() => client.session(id).respond(interaction, answer, { idempotencyKey: randomUUID() })));
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
async function cancel(deps, args) {
|
|
157
|
+
const usage = "nylorun sessions cancel <id> [--reason <text>]";
|
|
158
|
+
const flags = parseFlags(args, { values: ["--reason"] }, usage);
|
|
159
|
+
const [id] = positional(flags, 1, usage);
|
|
160
|
+
const reason = flags.values.get("--reason");
|
|
161
|
+
const { client } = await runtimeClient(deps, flags);
|
|
162
|
+
printJson(deps, await runtimeCall(() => client.session(id).cancel({ idempotencyKey: randomUUID(), ...(reason ? { reason } : {}) })));
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
async function watch(deps, args) {
|
|
166
|
+
const usage = "nylorun sessions watch <id> [--follow] [--cursor <c>]";
|
|
167
|
+
const flags = parseFlags(args, { booleans: ["--follow"], values: ["--cursor"] }, usage);
|
|
168
|
+
const [id] = positional(flags, 1, usage);
|
|
169
|
+
const cursor = flags.values.get("--cursor");
|
|
170
|
+
const { client } = await runtimeClient(deps, flags);
|
|
171
|
+
const controller = new AbortController();
|
|
172
|
+
const stop = () => controller.abort();
|
|
173
|
+
process.once("SIGINT", stop);
|
|
174
|
+
process.once("SIGTERM", stop);
|
|
175
|
+
try {
|
|
176
|
+
await runtimeCall(async () => {
|
|
177
|
+
for await (const event of client.session(id).observe({
|
|
178
|
+
follow: flags.booleans.has("--follow"),
|
|
179
|
+
signal: controller.signal,
|
|
180
|
+
...(cursor ? { cursor } : {}),
|
|
181
|
+
}))
|
|
182
|
+
deps.out(JSON.stringify(event));
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (!controller.signal.aborted)
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
process.off("SIGINT", stop);
|
|
191
|
+
process.off("SIGTERM", stop);
|
|
192
|
+
}
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
async function history(deps, args) {
|
|
196
|
+
const usage = "nylorun sessions history <id> [--agent <a>] [--limit <n>] [--cursor <c>]";
|
|
197
|
+
const flags = parseFlags(args, { values: ["--agent", "--limit", "--cursor"] }, usage);
|
|
198
|
+
const [id] = positional(flags, 1, usage);
|
|
199
|
+
const limit = integerFlag(flags, "--limit");
|
|
200
|
+
const agent = flags.values.get("--agent");
|
|
201
|
+
const cursor = flags.values.get("--cursor");
|
|
202
|
+
const { client } = await runtimeClient(deps, flags);
|
|
203
|
+
printJson(deps, await runtimeCall(() => client.session(id).history({
|
|
204
|
+
...(agent ? { agent } : {}),
|
|
205
|
+
...(cursor ? { cursor } : {}),
|
|
206
|
+
...(limit === undefined ? {} : { limit }),
|
|
207
|
+
})));
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
async function usageOf(deps, args) {
|
|
211
|
+
const usage = "nylorun sessions usage <id> [--turn <id>]";
|
|
212
|
+
const flags = parseFlags(args, { values: ["--turn"] }, usage);
|
|
213
|
+
const [id] = positional(flags, 1, usage);
|
|
214
|
+
const turnId = flags.values.get("--turn");
|
|
215
|
+
const { client } = await runtimeClient(deps, flags);
|
|
216
|
+
printJson(deps, await runtimeCall(() => client.session(id).usage(turnId ? { turnId } : {})));
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
async function calls(deps, args) {
|
|
220
|
+
const usage = "nylorun sessions calls <id> [--tool] [--turn <id>] [--limit <n>] [--cursor <c>]";
|
|
221
|
+
const flags = parseFlags(args, { booleans: ["--tool"], values: ["--turn", "--limit", "--cursor"] }, usage);
|
|
222
|
+
const [id] = positional(flags, 1, usage);
|
|
223
|
+
const turnId = flags.values.get("--turn");
|
|
224
|
+
const cursor = flags.values.get("--cursor");
|
|
225
|
+
const limit = integerFlag(flags, "--limit");
|
|
226
|
+
const options = {
|
|
227
|
+
...(turnId ? { turnId } : {}),
|
|
228
|
+
...(cursor ? { cursor } : {}),
|
|
229
|
+
...(limit === undefined ? {} : { limit }),
|
|
230
|
+
};
|
|
231
|
+
const { client } = await runtimeClient(deps, flags);
|
|
232
|
+
const session = client.session(id);
|
|
233
|
+
const page = flags.booleans.has("--tool")
|
|
234
|
+
? await runtimeCall(() => session.toolCalls(options))
|
|
235
|
+
: await runtimeCall(() => session.modelCalls(options));
|
|
236
|
+
printJson(deps, page);
|
|
237
|
+
return 0;
|
|
238
|
+
}
|
|
239
|
+
async function trace(deps, args) {
|
|
240
|
+
const usage = "nylorun sessions trace <id> <turn>";
|
|
241
|
+
const flags = parseFlags(args, {}, usage);
|
|
242
|
+
const [id, turn] = positional(flags, 2, usage);
|
|
243
|
+
const { client } = await runtimeClient(deps, flags);
|
|
244
|
+
printJson(deps, await runtimeCall(() => client.session(id).trace(turn)));
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
async function exec(deps, args) {
|
|
248
|
+
const usage = "nylorun sessions exec <id> [--timeout <s>] -- <command>...";
|
|
249
|
+
const split = args.indexOf("--");
|
|
250
|
+
if (split < 0 || split === args.length - 1)
|
|
251
|
+
throw usageError(`Usage: ${usage}`);
|
|
252
|
+
const flags = parseFlags(args.slice(0, split), { values: ["--timeout"] }, usage);
|
|
253
|
+
const [id] = positional(flags, 1, usage);
|
|
254
|
+
const timeout = integerFlag(flags, "--timeout");
|
|
255
|
+
const command = args.slice(split + 1).join(" ");
|
|
256
|
+
const { client } = await runtimeClient(deps, flags);
|
|
257
|
+
// The Runtime answers the tool's outcome: `completed` with its output, or why it did not run.
|
|
258
|
+
const outcome = (await runtimeCall(() => client.session(id).sandbox.bash({ command, ...(timeout === undefined ? {} : { timeout }) })));
|
|
259
|
+
if (outcome.kind !== "completed")
|
|
260
|
+
throw new CliError(`The command did not run (${outcome.kind ?? "no outcome"}): ${outcome.error?.message ?? JSON.stringify(outcome)}`, 1);
|
|
261
|
+
const result = outcome.output;
|
|
262
|
+
if (result.stdout)
|
|
263
|
+
deps.out(result.stdout.replace(/\n$/, ""));
|
|
264
|
+
if (result.stderr)
|
|
265
|
+
deps.err(result.stderr.replace(/\n$/, ""));
|
|
266
|
+
return typeof result.exitCode === "number" ? result.exitCode : 0;
|
|
267
|
+
}
|
|
268
|
+
const COMMANDS = {
|
|
269
|
+
list,
|
|
270
|
+
ls: list,
|
|
271
|
+
create,
|
|
272
|
+
get,
|
|
273
|
+
send,
|
|
274
|
+
approve,
|
|
275
|
+
respond,
|
|
276
|
+
cancel,
|
|
277
|
+
watch,
|
|
278
|
+
history,
|
|
279
|
+
usage: usageOf,
|
|
280
|
+
calls,
|
|
281
|
+
trace,
|
|
282
|
+
exec,
|
|
283
|
+
};
|
|
284
|
+
/** `nylorun sessions <command> ...` */
|
|
285
|
+
export async function sessionsCommand(deps, args) {
|
|
286
|
+
const [sub, ...rest] = args;
|
|
287
|
+
if (sub === "--help" || sub === "-h") {
|
|
288
|
+
deps.out(sessionsUsage);
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
291
|
+
const command = sub !== undefined && Object.hasOwn(COMMANDS, sub) ? COMMANDS[sub] : undefined;
|
|
292
|
+
if (!command)
|
|
293
|
+
throw usageError(sub ? `Unknown sessions command ${sub}.\n${sessionsUsage}` : sessionsUsage);
|
|
294
|
+
return command(deps, rest);
|
|
295
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type StackDeps } from "../stack/commands.js";
|
|
2
|
+
export declare const settingsUsage = " budgets [get | put <file|->] [--tenant <name>]\n the model spend caps; put replaces them all\n tool-policy [get | put <file|->] [--tenant <name>]\n the Tool Gate's rules; put replaces them all\n settings sandbox [get | put <file|->] [--tenant <name>]\n the Environment settings: limits, stop grace, placement\n settings artifacts [get | put <file|->] [--tenant <name>]\n the artifact store's limits\n coverage --agent <id> [--environment <id>] [--tenant <name>] [--json]\n which secret of the Environment each of the agent's MCP\n servers and HTTP tools would get";
|
|
3
|
+
export declare const budgetsCommand: (deps: StackDeps, args: readonly string[]) => Promise<number>;
|
|
4
|
+
export declare const toolPolicyCommand: (deps: StackDeps, args: readonly string[]) => Promise<number>;
|
|
5
|
+
/** `nylorun tenant settings sandbox | artifacts ...` */
|
|
6
|
+
export declare function settingsCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
7
|
+
/**
|
|
8
|
+
* `nylorun tenant coverage`: which secret of `--environment` each remote MCP server and HTTP tool
|
|
9
|
+
* of saved agent `--agent` would get in a session (none without an Environment). It reads no
|
|
10
|
+
* secret and calls no server.
|
|
11
|
+
*/
|
|
12
|
+
export declare function coverageCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { PutModelBudgetsRequestSchema, PutTenantArtifactsRequestSchema, PutTenantEnvironmentsRequestSchema, PutToolPolicyRequestSchema, } from "@nylorun/core/contracts";
|
|
2
|
+
import { CliError } from "../errors.js";
|
|
3
|
+
import { parseStackFlags } from "../stack/commands.js";
|
|
4
|
+
import { printJson, printTable, readBody } from "./io.js";
|
|
5
|
+
import { connect, managementClient } from "./target.js";
|
|
6
|
+
export const settingsUsage = ` budgets [get | put <file|->] [--tenant <name>]
|
|
7
|
+
the model spend caps; put replaces them all
|
|
8
|
+
tool-policy [get | put <file|->] [--tenant <name>]
|
|
9
|
+
the Tool Gate's rules; put replaces them all
|
|
10
|
+
settings sandbox [get | put <file|->] [--tenant <name>]
|
|
11
|
+
the Environment settings: limits, stop grace, placement
|
|
12
|
+
settings artifacts [get | put <file|->] [--tenant <name>]
|
|
13
|
+
the artifact store's limits
|
|
14
|
+
coverage --agent <id> [--environment <id>] [--tenant <name>] [--json]
|
|
15
|
+
which secret of the Environment each of the agent's MCP
|
|
16
|
+
servers and HTTP tools would get`;
|
|
17
|
+
const usageError = (message = `Usage:\n${settingsUsage}`) => new CliError(message, 2);
|
|
18
|
+
/* The bodies' `requestId` is optional in the contracts; the client adds one when it is absent. */
|
|
19
|
+
const budgets = {
|
|
20
|
+
name: "budgets",
|
|
21
|
+
schema: PutModelBudgetsRequestSchema,
|
|
22
|
+
get: (admin) => admin.models.budgets.get(),
|
|
23
|
+
put: (admin, body) => admin.models.budgets.put(body),
|
|
24
|
+
};
|
|
25
|
+
const toolPolicy = {
|
|
26
|
+
name: "tool-policy",
|
|
27
|
+
schema: PutToolPolicyRequestSchema,
|
|
28
|
+
readOnly: ["updatedAt"],
|
|
29
|
+
get: (admin) => admin.toolPolicy.get(),
|
|
30
|
+
put: (admin, body) => admin.toolPolicy.put(body),
|
|
31
|
+
};
|
|
32
|
+
const sandbox = {
|
|
33
|
+
name: "settings sandbox",
|
|
34
|
+
schema: PutTenantEnvironmentsRequestSchema,
|
|
35
|
+
get: (admin) => admin.settings.sandbox.get(),
|
|
36
|
+
put: (admin, body) => admin.settings.sandbox.put(body),
|
|
37
|
+
};
|
|
38
|
+
const artifacts = {
|
|
39
|
+
name: "settings artifacts",
|
|
40
|
+
schema: PutTenantArtifactsRequestSchema,
|
|
41
|
+
readOnly: ["usedBytes"],
|
|
42
|
+
get: (admin) => admin.settings.artifacts.get(),
|
|
43
|
+
put: (admin, body) => admin.settings.artifacts.put(body),
|
|
44
|
+
};
|
|
45
|
+
/** `<document> [get | put <file|->]`: `get` when no verb is given. */
|
|
46
|
+
async function documentCommand(deps, document, args) {
|
|
47
|
+
const usage = `nylorun tenant ${document.name} [get | put <file|->] [--tenant <name>]`;
|
|
48
|
+
const flags = parseStackFlags(args, { values: ["--tenant"] }, usage);
|
|
49
|
+
const [verb = "get", ...operands] = flags.rest;
|
|
50
|
+
if (!((verb === "get" && !operands.length) || (verb === "put" && operands.length === 1)))
|
|
51
|
+
throw usageError(`Usage: ${usage}`);
|
|
52
|
+
const body = verb === "put" ? await readBody(deps, operands[0], document.schema, document.readOnly) : undefined;
|
|
53
|
+
const admin = managementClient(deps, await connect(deps, "management", flags.values.get("--tenant")));
|
|
54
|
+
printJson(deps, body === undefined ? await document.get(admin) : await document.put(admin, body));
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
export const budgetsCommand = (deps, args) => documentCommand(deps, budgets, args);
|
|
58
|
+
export const toolPolicyCommand = (deps, args) => documentCommand(deps, toolPolicy, args);
|
|
59
|
+
/** `nylorun tenant settings sandbox | artifacts ...` */
|
|
60
|
+
export async function settingsCommand(deps, args) {
|
|
61
|
+
const [which, ...rest] = args;
|
|
62
|
+
if (which === "sandbox")
|
|
63
|
+
return documentCommand(deps, sandbox, rest);
|
|
64
|
+
if (which === "artifacts")
|
|
65
|
+
return documentCommand(deps, artifacts, rest);
|
|
66
|
+
throw usageError();
|
|
67
|
+
}
|
|
68
|
+
const COVERAGE_USAGE = "nylorun tenant coverage --agent <id> [--environment <id>] [--tenant <name>] [--json]";
|
|
69
|
+
/**
|
|
70
|
+
* `nylorun tenant coverage`: which secret of `--environment` each remote MCP server and HTTP tool
|
|
71
|
+
* of saved agent `--agent` would get in a session (none without an Environment). It reads no
|
|
72
|
+
* secret and calls no server.
|
|
73
|
+
*/
|
|
74
|
+
export async function coverageCommand(deps, args) {
|
|
75
|
+
const flags = parseStackFlags(args, { booleans: ["--json"], values: ["--tenant", "--agent", "--environment"] }, COVERAGE_USAGE);
|
|
76
|
+
const agentId = flags.values.get("--agent");
|
|
77
|
+
const environmentId = flags.values.get("--environment");
|
|
78
|
+
if (flags.rest.length || agentId === undefined)
|
|
79
|
+
throw usageError(`Usage: ${COVERAGE_USAGE}`);
|
|
80
|
+
const admin = managementClient(deps, await connect(deps, "management", flags.values.get("--tenant")));
|
|
81
|
+
const coverage = await admin.coverage({
|
|
82
|
+
agentId,
|
|
83
|
+
...(environmentId === undefined ? {} : { environmentId }),
|
|
84
|
+
});
|
|
85
|
+
if (flags.booleans.has("--json")) {
|
|
86
|
+
printJson(deps, coverage);
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
const missing = coverage.entries.filter((entry) => entry.status === "missing").length;
|
|
90
|
+
deps.out(`${coverage.agentId} in ${coverage.environmentId ?? "no Environment"}: ${coverage.complete ? "every credential is covered" : `${missing} of ${coverage.entries.length} missing`}`);
|
|
91
|
+
if (coverage.entries.length)
|
|
92
|
+
printTable(deps, [
|
|
93
|
+
["STATUS", "KIND", "NAME", "URL", "SECRET"],
|
|
94
|
+
...coverage.entries.map((entry) => [
|
|
95
|
+
entry.status,
|
|
96
|
+
entry.kind,
|
|
97
|
+
entry.agentId ? `${entry.agentId}/${entry.name}` : entry.name,
|
|
98
|
+
entry.url,
|
|
99
|
+
entry.credential?.name ?? (entry.required ? "(required)" : ""),
|
|
100
|
+
]),
|
|
101
|
+
]);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type StackDeps } from "../stack/commands.js";
|
|
2
|
+
export declare const signingKeysUsage = " signing-keys list [--tenant <name>]\n the Tenant's token signing keys\n signing-keys rotate [--force] [--tenant <name>]\n rotate; --force ends outstanding links and run tokens\n signing-keys revoke <kid> [--tenant <name>]\n revoke a previous or standby key";
|
|
3
|
+
export declare function signingKeysCommand(deps: StackDeps, input: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nylorun tenant signing-keys`: the Tenant's signing keys, which sign capability links and run
|
|
3
|
+
* tokens, through the Management API. (Subject tokens, the access policy, browser keys and
|
|
4
|
+
* revocations left the Runtime in protocol 7.)
|
|
5
|
+
*/
|
|
6
|
+
import { CliError } from "../errors.js";
|
|
7
|
+
import { parseStackFlags } from "../stack/commands.js";
|
|
8
|
+
import { connect, managementClient } from "./target.js";
|
|
9
|
+
export const signingKeysUsage = ` signing-keys list [--tenant <name>]
|
|
10
|
+
the Tenant's token signing keys
|
|
11
|
+
signing-keys rotate [--force] [--tenant <name>]
|
|
12
|
+
rotate; --force ends outstanding links and run tokens
|
|
13
|
+
signing-keys revoke <kid> [--tenant <name>]
|
|
14
|
+
revoke a previous or standby key`;
|
|
15
|
+
const usageError = (message = `Usage:\n${signingKeysUsage}`) => new CliError(message, 2);
|
|
16
|
+
export async function signingKeysCommand(deps, input) {
|
|
17
|
+
const [action, ...args] = input;
|
|
18
|
+
const flags = parseStackFlags(args, { booleans: action === "rotate" ? ["--force"] : [], values: ["--tenant"] }, signingKeysUsage);
|
|
19
|
+
const arity = action === "revoke" ? 1 : 0;
|
|
20
|
+
if (!["list", "rotate", "revoke"].includes(action ?? "") || flags.rest.length !== arity)
|
|
21
|
+
throw usageError();
|
|
22
|
+
const keys = managementClient(deps, await connect(deps, "management", flags.values.get("--tenant")))
|
|
23
|
+
.signingKeys;
|
|
24
|
+
const print = (value) => deps.out(JSON.stringify(value, null, 2));
|
|
25
|
+
if (action === "list")
|
|
26
|
+
print(await keys.list());
|
|
27
|
+
else if (action === "rotate")
|
|
28
|
+
print(await keys.rotate({ force: flags.booleans.has("--force") }));
|
|
29
|
+
else
|
|
30
|
+
print(await keys.revoke(flags.rest[0]));
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The installation a client command acts on. `--tenant <name>` names a local Tenant. Without it,
|
|
3
|
+
* `NYLORUN_RUNTIME_URL` with the key the API needs names a remote installation:
|
|
4
|
+
* `NYLORUN_MANAGEMENT_KEY` for the Management API (`nylorun tenant`), `NYLORUN_SERVER_KEY` or an
|
|
5
|
+
* identity provider's token in `NYLORUN_TOKEN` for the Runtime API (`NYLORUN_RUNTIME_URL` alone is
|
|
6
|
+
* an app's, as for `createAdmin`). Otherwise the local
|
|
7
|
+
* Tenant the Project link names, else the default one, while it runs (`runningTenantApi`: the
|
|
8
|
+
* Project's keys while they authenticate, else the Host root's own).
|
|
9
|
+
*/
|
|
10
|
+
import { type ManagementClient } from "@nylorun/admin";
|
|
11
|
+
import { type StackDeps } from "../stack/commands.js";
|
|
12
|
+
export type Api = "management" | "runtime";
|
|
13
|
+
export interface Connection {
|
|
14
|
+
/** How messages name it: `Tenant <name>`, or the remote URL. */
|
|
15
|
+
label: string;
|
|
16
|
+
url: string;
|
|
17
|
+
/** A local Tenant (Docker on this machine), else a remote installation. */
|
|
18
|
+
local: boolean;
|
|
19
|
+
/** The local Tenant's name. */
|
|
20
|
+
tenant?: string;
|
|
21
|
+
applicationKey?: string;
|
|
22
|
+
managementKey?: string;
|
|
23
|
+
/** A trusted issuer's token (`NYLORUN_TOKEN`), sent in place of the application key. */
|
|
24
|
+
token?: string;
|
|
25
|
+
}
|
|
26
|
+
/** The remote installation the environment names for `api`, if it names one. */
|
|
27
|
+
export declare function remoteConnection(env: StackDeps["env"], api: Api): Connection | undefined;
|
|
28
|
+
/** The installation to act on for `api` (module comment). */
|
|
29
|
+
export declare function connect(deps: StackDeps, api: Api, tenant?: string): Promise<Connection>;
|
|
30
|
+
/** The Management API client of `connection`, through `deps.fetch`. */
|
|
31
|
+
export declare function managementClient(deps: StackDeps, connection: Connection): ManagementClient;
|
|
32
|
+
/** `error`'s message, ending in one full stop. */
|
|
33
|
+
export declare function messageOf(error: unknown): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The installation a client command acts on. `--tenant <name>` names a local Tenant. Without it,
|
|
3
|
+
* `NYLORUN_RUNTIME_URL` with the key the API needs names a remote installation:
|
|
4
|
+
* `NYLORUN_MANAGEMENT_KEY` for the Management API (`nylorun tenant`), `NYLORUN_SERVER_KEY` or an
|
|
5
|
+
* identity provider's token in `NYLORUN_TOKEN` for the Runtime API (`NYLORUN_RUNTIME_URL` alone is
|
|
6
|
+
* an app's, as for `createAdmin`). Otherwise the local
|
|
7
|
+
* Tenant the Project link names, else the default one, while it runs (`runningTenantApi`: the
|
|
8
|
+
* Project's keys while they authenticate, else the Host root's own).
|
|
9
|
+
*/
|
|
10
|
+
import { createManagementClient } from "@nylorun/admin";
|
|
11
|
+
import { CliError } from "../errors.js";
|
|
12
|
+
import { runningTenantApi } from "../stack/commands.js";
|
|
13
|
+
function variable(env, name) {
|
|
14
|
+
const value = env[name]?.trim();
|
|
15
|
+
return value ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
/** The remote installation the environment names for `api`, if it names one. */
|
|
18
|
+
export function remoteConnection(env, api) {
|
|
19
|
+
const url = variable(env, "NYLORUN_RUNTIME_URL");
|
|
20
|
+
const managementKey = variable(env, "NYLORUN_MANAGEMENT_KEY");
|
|
21
|
+
const applicationKey = variable(env, "NYLORUN_SERVER_KEY");
|
|
22
|
+
const token = variable(env, "NYLORUN_TOKEN");
|
|
23
|
+
if (!url || (api === "management" ? !managementKey : !applicationKey && !token))
|
|
24
|
+
return undefined;
|
|
25
|
+
return {
|
|
26
|
+
label: url,
|
|
27
|
+
url: url.replace(/\/$/, ""),
|
|
28
|
+
local: false,
|
|
29
|
+
...(applicationKey ? { applicationKey } : {}),
|
|
30
|
+
...(managementKey ? { managementKey } : {}),
|
|
31
|
+
// A token is a person's: it wins over the key for the Runtime API.
|
|
32
|
+
...(token && api === "runtime" ? { token } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** The installation to act on for `api` (module comment). */
|
|
36
|
+
export async function connect(deps, api, tenant) {
|
|
37
|
+
if (tenant === undefined) {
|
|
38
|
+
const remote = remoteConnection(deps.env, api);
|
|
39
|
+
if (remote)
|
|
40
|
+
return remote;
|
|
41
|
+
}
|
|
42
|
+
const local = await runningTenantApi(deps, tenant === undefined ? {} : { name: tenant });
|
|
43
|
+
return {
|
|
44
|
+
label: `Tenant ${local.name}`,
|
|
45
|
+
url: local.runtimeUrl,
|
|
46
|
+
local: true,
|
|
47
|
+
tenant: local.name,
|
|
48
|
+
applicationKey: local.applicationKey,
|
|
49
|
+
managementKey: local.managementKey,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The Management API client of `connection`, through `deps.fetch`. */
|
|
53
|
+
export function managementClient(deps, connection) {
|
|
54
|
+
if (!connection.managementKey)
|
|
55
|
+
throw new CliError(`No management key for ${connection.label}: set NYLORUN_MANAGEMENT_KEY, or on the Runtime's machine run "nylorun tenant keys put <id> --management".`, 1);
|
|
56
|
+
return createManagementClient({
|
|
57
|
+
url: connection.url,
|
|
58
|
+
key: connection.managementKey,
|
|
59
|
+
fetch: (input, init) => deps.fetch(input instanceof Request ? input.url : String(input), init),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** `error`'s message, ending in one full stop. */
|
|
63
|
+
export function messageOf(error) {
|
|
64
|
+
return (error instanceof Error ? error.message : String(error)).replace(/\.?$/, ".");
|
|
65
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nylorun tenant`: the Management API (`/v1/tenant/*`) of the installation's one Tenant, with a
|
|
3
|
+
* management key (`target.ts` picks the installation). Issuing a management key is the one
|
|
4
|
+
* exception: only the Runtime's machine does it, through `nylorun-operate` (`stack/keys.ts`).
|
|
5
|
+
*/
|
|
6
|
+
import type { ManagementClient } from "@nylorun/admin";
|
|
7
|
+
import { type StackDeps } from "../stack/commands.js";
|
|
8
|
+
import { type Connection } from "./target.js";
|
|
9
|
+
export declare const tenantUsage = "nylorun tenant <command>\n\nThe Management API of the installation's one Tenant, with a management key: the local Tenant\n--tenant names (else the linked project's, else the default one), or the installation that\nNYLORUN_RUNTIME_URL and NYLORUN_MANAGEMENT_KEY name.\n\n reset [--sessions|--environments|--all] [--yes] [--tenant <name>]\n clear the Tenant's sessions, Environments or all its data\n (the Project link and keys stay)\n keys put <id> [--management] [--tenant <name>]\n create the Tenant's application key <id> (--management: a\n management key, on the Runtime's machine only), or rotate it\n (the old key stops working); prints the key once\n keys list [--tenant <name>] [--json]\n the Tenant's keys: id, role, when issued (never the keys)\n keys rm <id> [--tenant <name>] delete key <id>: it stops working at once\n signing-keys list [--tenant <name>]\n the Tenant's token signing keys\n signing-keys rotate [--force] [--tenant <name>]\n rotate; --force ends outstanding links and run tokens\n signing-keys revoke <kid> [--tenant <name>]\n revoke a previous or standby key\n models catalog [--tenant <name>] [--json]\n every provider and model the Runtime knows\n models providers [--tenant <name>] [--json]\n the providers with a credential, and the one in use\n models get [--tenant <name>] [--json]\n the Tenant's model\n models set [--tenant <name>] sign in to a model provider and set the Tenant's model\n models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]\n the same with an API key piped in on stdin (CI)\n models select <provider> [--model <id>] [--tenant <name>]\n use a provider that has a credential (default: its model)\n usage [--scope tenant|agent|turn] [--id <id>] [--period day|month|total] [--tenant <name>]\n what the model calls used and cost (JSON)\n calls [--after <cursor>] [--limit <n>] [--tenant <name>]\n every recorded model call, one JSON line each, until\n caught up; prints the cursor to resume from on stderr\n budgets [get | put <file|->] [--tenant <name>]\n the model spend caps; put replaces them all\n tool-policy [get | put <file|->] [--tenant <name>]\n the Tool Gate's rules; put replaces them all\n settings sandbox [get | put <file|->] [--tenant <name>]\n the Environment settings: limits, stop grace, placement\n settings artifacts [get | put <file|->] [--tenant <name>]\n the artifact store's limits\n coverage --agent <id> [--environment <id>] [--tenant <name>] [--json]\n which secret of the Environment each of the agent's MCP\n servers and HTTP tools would get\n mcp inspect <url> [--server <name>] [--environment <id>] [--sse] [--tenant <name>] [--json]\n list a remote MCP server's tools, with Environment <id>'s\n credential for <url> (none without it): model names, schema\n sizes, renames, or that it needs a person's sign-in (the\n Runtime calls no tool)\n\nA put reads a JSON body from a file, or from stdin for -, and replaces the whole document; what\nbudgets, tool-policy and settings artifacts get print, put takes back. An API key is read from\nstdin, never from the arguments.";
|
|
10
|
+
/** The Management API client of the installation `--tenant` (or the environment) names. */
|
|
11
|
+
export declare function tenantClient(deps: StackDeps, tenant: string | undefined): Promise<{
|
|
12
|
+
connection: Connection;
|
|
13
|
+
admin: ManagementClient;
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* `nylorun status` for an installation the environment names (`NYLORUN_RUNTIME_URL` and
|
|
17
|
+
* `NYLORUN_MANAGEMENT_KEY`): the Tenant, its checks and counts. A local Tenant's status is the
|
|
18
|
+
* stack's (`stack/commands.ts`).
|
|
19
|
+
*/
|
|
20
|
+
export declare function remoteStatus(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
21
|
+
/** `nylorun tenant <group> ...` */
|
|
22
|
+
export declare function tenantCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|