nylorun 0.13.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 +153 -0
- package/README.md +255 -166
- 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 +10 -4
- package/dist/client/doctor.js +28 -20
- 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/client/mcp.d.ts +11 -0
- package/dist/{stack → client}/mcp.js +40 -26
- 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/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- 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 +22 -13
- 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/stack/versions.d.ts +2 -2
- package/dist/stack/versions.js +3 -3
- package/dist/telemetry.d.ts +2 -2
- package/dist/telemetry.js +4 -4
- package/package.json +10 -9
- 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 -80
- 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/mcp.d.ts +0 -9
- package/dist/stack/sandbox.d.ts +0 -4
- package/dist/stack/sandbox.js +0 -106
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the Runtime API groups (`agents`, `sessions`, `environments`, `artifacts`) share: their
|
|
3
|
+
* flags, the `@nylorun/agents` client of the installation `target.ts` picks, acting for a subject
|
|
4
|
+
* with `--as`, and how they read input and print output.
|
|
5
|
+
*/
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { AgentsClient, RuntimeError, SUBJECT_SCOPES, } from "@nylorun/agents";
|
|
8
|
+
import { CliError } from "../errors.js";
|
|
9
|
+
import { parseStackFlags } from "../stack/commands.js";
|
|
10
|
+
import { connect } from "./target.js";
|
|
11
|
+
/** Flags every Runtime API command takes. */
|
|
12
|
+
export const RUNTIME_FLAGS = { values: ["--tenant", "--as"], lists: ["--scope"] };
|
|
13
|
+
export const runtimeFlagsUsage = `Every command takes --tenant <name>, and --as <subject> [--scope <scope>]... to act for a
|
|
14
|
+
person (default scope sessions:own; scopes: ${SUBJECT_SCOPES.join(", ")}).`;
|
|
15
|
+
export const usageError = (message) => new CliError(message, 2);
|
|
16
|
+
/** `args` parsed with the Runtime flags added to `allowed`. */
|
|
17
|
+
export function parseFlags(args, allowed, usage) {
|
|
18
|
+
return parseStackFlags(args, {
|
|
19
|
+
booleans: allowed.booleans ?? [],
|
|
20
|
+
values: [...RUNTIME_FLAGS.values, ...(allowed.values ?? [])],
|
|
21
|
+
lists: [...RUNTIME_FLAGS.lists, ...(allowed.lists ?? [])],
|
|
22
|
+
}, usage);
|
|
23
|
+
}
|
|
24
|
+
/** Exactly `count` positional arguments, else a usage error. */
|
|
25
|
+
export function positional(flags, count, usage) {
|
|
26
|
+
if (flags.rest.length !== count)
|
|
27
|
+
throw usageError(`Usage: ${usage}`);
|
|
28
|
+
return flags.rest;
|
|
29
|
+
}
|
|
30
|
+
/** The Runtime API client of the installation the flags (or the environment) name. */
|
|
31
|
+
export async function runtimeClient(deps, flags) {
|
|
32
|
+
const scopes = flags.lists.get("--scope");
|
|
33
|
+
const subject = flags.values.get("--as");
|
|
34
|
+
if (scopes && subject === undefined)
|
|
35
|
+
throw usageError("--scope needs --as <subject>.");
|
|
36
|
+
for (const scope of scopes ?? [])
|
|
37
|
+
if (!SUBJECT_SCOPES.includes(scope))
|
|
38
|
+
throw usageError(`Unknown scope ${scope}: one of ${SUBJECT_SCOPES.join(", ")}.`);
|
|
39
|
+
const connection = await connect(deps, "runtime", flags.values.get("--tenant"));
|
|
40
|
+
const fetch = (input, init) => deps.fetch(input instanceof Request ? input.url : String(input), init);
|
|
41
|
+
const token = connection.token;
|
|
42
|
+
if (!token && !connection.applicationKey)
|
|
43
|
+
throw new CliError(`No application key for ${connection.label}: set NYLORUN_SERVER_KEY.`, 1);
|
|
44
|
+
const base = new AgentsClient({
|
|
45
|
+
url: connection.url,
|
|
46
|
+
fetch,
|
|
47
|
+
...(token
|
|
48
|
+
? { token: { get: async () => token, invalidate: () => { } } }
|
|
49
|
+
: { key: connection.applicationKey }),
|
|
50
|
+
});
|
|
51
|
+
const client = subject === undefined
|
|
52
|
+
? base
|
|
53
|
+
: base.as(subject, scopes ? { scopes: scopes } : {});
|
|
54
|
+
return { client, connection };
|
|
55
|
+
}
|
|
56
|
+
/** Runs `command`, turning a Runtime's refusal into a `CliError` with its message (exit 1). */
|
|
57
|
+
export async function runtimeCall(command) {
|
|
58
|
+
try {
|
|
59
|
+
return await command();
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (!(error instanceof RuntimeError))
|
|
63
|
+
throw error;
|
|
64
|
+
const body = error.body;
|
|
65
|
+
const message = typeof body?.message === "string" ? body.message : error.message;
|
|
66
|
+
const code = typeof body?.code === "string" ? ` (${body.code})` : "";
|
|
67
|
+
throw new CliError(`${message.replace(/\.$/, "")}${code}. HTTP ${error.status}.`, 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Standard input, whole. */
|
|
71
|
+
export async function readStdin() {
|
|
72
|
+
const chunks = [];
|
|
73
|
+
for await (const chunk of process.stdin)
|
|
74
|
+
chunks.push(chunk);
|
|
75
|
+
return Buffer.concat(chunks);
|
|
76
|
+
}
|
|
77
|
+
/** A JSON object from file `path`, or from standard input when `path` is `-`. */
|
|
78
|
+
export async function readJson(path, what) {
|
|
79
|
+
let text;
|
|
80
|
+
try {
|
|
81
|
+
text = path === "-" ? (await readStdin()).toString("utf8") : await readFile(path, "utf8");
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw new CliError(`Cannot read ${what.toLowerCase()} from ${path}: ${error.message}`, 1);
|
|
85
|
+
}
|
|
86
|
+
let value;
|
|
87
|
+
try {
|
|
88
|
+
value = JSON.parse(text);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw usageError(`${what} is not JSON: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
94
|
+
throw usageError(`${what} must be a JSON object.`);
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
/** A JSON value given on the command line. */
|
|
98
|
+
export function parseJsonArgument(text, what) {
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(text);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw usageError(`${what} is not JSON: ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** `key=value` pairs (`--label`), as a record. */
|
|
107
|
+
export function labelsOf(flags, name = "--label") {
|
|
108
|
+
const pairs = flags.lists.get(name);
|
|
109
|
+
if (!pairs)
|
|
110
|
+
return undefined;
|
|
111
|
+
const labels = {};
|
|
112
|
+
for (const pair of pairs) {
|
|
113
|
+
const at = pair.indexOf("=");
|
|
114
|
+
if (at <= 0)
|
|
115
|
+
throw usageError(`${name} must be key=value, not ${pair}`);
|
|
116
|
+
labels[pair.slice(0, at)] = pair.slice(at + 1);
|
|
117
|
+
}
|
|
118
|
+
return labels;
|
|
119
|
+
}
|
|
120
|
+
/** A positive integer flag, when given. */
|
|
121
|
+
export function integerFlag(flags, name) {
|
|
122
|
+
const raw = flags.values.get(name);
|
|
123
|
+
if (raw === undefined)
|
|
124
|
+
return undefined;
|
|
125
|
+
if (!/^\d+$/.test(raw))
|
|
126
|
+
throw usageError(`${name} must be a whole number, not ${raw}`);
|
|
127
|
+
return Number(raw);
|
|
128
|
+
}
|
|
129
|
+
export { printJson, printTable } from "./io.js";
|
|
130
|
+
/** A page's `next cursor` hint, when there is one. */
|
|
131
|
+
export function printCursor(deps, cursor) {
|
|
132
|
+
if (cursor)
|
|
133
|
+
deps.err(`More: --cursor ${cursor}`);
|
|
134
|
+
}
|
|
@@ -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;
|