nylorun 0.16.0-beta → 0.18.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +118 -70
  3. package/dist/cli.js +39 -14
  4. package/dist/client/agent-manifests.d.ts +4 -0
  5. package/dist/client/agent-manifests.js +100 -0
  6. package/dist/client/agents.d.ts +2 -4
  7. package/dist/client/agents.js +467 -117
  8. package/dist/client/apply.d.ts +7 -0
  9. package/dist/client/apply.js +99 -0
  10. package/dist/client/artifacts.js +3 -3
  11. package/dist/client/doctor.d.ts +1 -1
  12. package/dist/client/doctor.js +2 -2
  13. package/dist/client/io.d.ts +2 -0
  14. package/dist/client/io.js +11 -0
  15. package/dist/client/mcp.d.ts +5 -3
  16. package/dist/client/mcp.js +30 -23
  17. package/dist/client/migrate.d.ts +4 -0
  18. package/dist/client/migrate.js +140 -0
  19. package/dist/client/model/pi-ai.d.ts +1 -1
  20. package/dist/client/model/pi-ai.js +1 -1
  21. package/dist/client/models.d.ts +5 -2
  22. package/dist/client/models.js +17 -7
  23. package/dist/client/runtime.d.ts +1 -1
  24. package/dist/client/runtime.js +8 -6
  25. package/dist/client/sessions.js +30 -19
  26. package/dist/client/settings.d.ts +4 -4
  27. package/dist/client/settings.js +21 -20
  28. package/dist/client/signing-keys.js +3 -1
  29. package/dist/client/tenant.d.ts +1 -1
  30. package/dist/client/tenant.js +12 -7
  31. package/dist/command.d.ts +1 -1
  32. package/dist/command.js +6 -2
  33. package/dist/errors.d.ts +6 -0
  34. package/dist/errors.js +9 -0
  35. package/dist/nylo.js +4 -4
  36. package/dist/stack/commands.d.ts +11 -0
  37. package/dist/stack/commands.js +24 -1
  38. package/dist/stack/compose-file.js +5 -3
  39. package/dist/telemetry.js +3 -1
  40. package/package.json +9 -9
  41. package/dist/client/environment-manifests.d.ts +0 -10
  42. package/dist/client/environment-manifests.js +0 -173
  43. package/dist/client/environments.d.ts +0 -4
  44. package/dist/client/environments.js +0 -295
@@ -1,148 +1,498 @@
1
- /**
2
- * `nylorun agents`: the Tenant's saved agents (`/v1/agents`, Runtime API). `save` takes a manifest
3
- * and the folders that hold its files: a manifest names each definition file by the SHA-256 of
4
- * its bytes (a skill's `files`), so the files are matched by content, whatever their paths.
5
- */
6
- import { readdir, readFile } from "node:fs/promises";
7
- import { join } from "node:path";
8
- import { definitionFileHash } from "@nylorun/agents";
9
- import { CliError } from "../errors.js";
10
- import { parseFlags, positional, printJson, printTable, readJson, runtimeCall, runtimeClient, runtimeFlagsUsage, usageError, } from "./runtime.js";
11
- export const agentsUsage = `nylorun agents <list|get|save>
1
+ import { parseAgentManifestRef } from "@nylorun/core/contracts";
2
+ import { HelpRequest, isHelp } from "../errors.js";
3
+ import { usageLinesOf } from "../stack/commands.js";
4
+ import { readSecret, readText } from "./io.js";
5
+ import { integerFlag, labelsOf, parseFlags, positional, printCursor, printJson, printTable, readJson, runtimeCall, runtimeClient, runtimeFlagsUsage, usageError, } from "./runtime.js";
6
+ export const agentsUsage = `nylorun agents <command>
12
7
 
13
- The Tenant's saved agents, through the Runtime API with an application key (or NYLORUN_TOKEN).
8
+ The Tenant's Agents (each one version of an agent manifest made live, with one environment and
9
+ one vault; sessions run on them), through the Runtime API with an application key (or
10
+ NYLORUN_TOKEN).
14
11
 
15
- list [--json] id, kind and description of each agent
16
- get <id> the agent's manifest, as JSON
17
- save <manifest.json|-> [--files <dir>]... [--implementation-version <v>] [--dry-run]
18
- save the agent the manifest describes, first uploading the
19
- definition files it names from the folders given (found by
20
- their SHA-256, whatever their paths); --dry-run checks it
21
- and saves nothing
12
+ list [--state <state>] [--manifest <name>] [--label <key=value>]... [--outdated]
13
+ [--limit <n>] [--cursor <c>] [--json]
14
+ id, manifest version (tracks or pinned, and the version
15
+ it moves to), kind, state, sessions, owner and labels;
16
+ --outdated (or --behind): those moving to another version
17
+ get <id> the Agent, as JSON
18
+ put <id> --manifest <name[@n]> [--owner <user>] [--label <key=value>]... [--dry-run]
19
+ create it from a saved manifest (name tracks its latest
20
+ version, name@n pins version n), or find it, change its
21
+ owner and labels and move it to that version; --dry-run
22
+ prints what the move would do
23
+ pin <id> <version> move it to that version of its manifest, and keep it there
24
+ track <id> move it to its manifest's latest version, and with each save
25
+ restart <id> [--retry] apply the version it waits to start on now (a running pod
26
+ stops; the next turn starts it); --retry: the version that
27
+ failed to start, again
28
+ fork <id> <new-id> [--manifest <name[@n]>] [--disk <latest|n|empty>]
29
+ [--secrets <copy|none>] [--label <key=value>]... [--dry-run]
30
+ a new Agent from a manifest version (default its
31
+ manifest's latest), with this one's disk and the secrets
32
+ it declares; --dry-run prints the plan
33
+ stop <id> | reset <id> stop its sandbox; reset its disk
34
+ rm <id> delete it: its sandbox, files and vault (refused while a
35
+ turn runs on it)
36
+ snapshot <id> keep a version of its disk
37
+ versions <id> its disk versions
38
+ restore <id> <version|latest> put a version of its disk back
39
+ delete-version <id> <version>
40
+ events <id> [--from <n>] [--types <type,…>] [--since <time>] [--until <time>]
41
+ its events (lifecycle, and agent.egress for every
42
+ connection, naming the turns running), as JSON
43
+ secrets list <id> its secrets' names, types and bindings (never a value)
44
+ secrets get <id> <name>
45
+ secrets put <id> <name> the value of a slot its manifest declares, piped in
46
+ on standard input
47
+ secrets put <id> <name> <secret.json|->
48
+ create or replace a secret: {"type": "bearer", ...}, read
49
+ from a file or standard input
50
+ secrets rm <id> <name>
51
+ triggers list <id> its triggers: status, next and last run, as JSON
52
+ triggers pause|resume <id> <trigger>
53
+ triggers run <id> <trigger> [--idempotency-key <k>]
54
+ run it once now, as if it fired
55
+ triggers new-secret <id> <trigger> a webhook trigger's new signing secret (whsec_…), shown
56
+ once with its URL; the one before stops working
57
+ runs <id> [--trigger <id>] [--limit <n>] [--cursor <c>]
58
+ what each fire came to (started, queued, skipped,
59
+ failed), newest first, as JSON
60
+ memories ls <id> [--prefix <folder>] [--limit <n>] [--cursor <c>]
61
+ its memories by path, as JSON
62
+ memories cat <id> <path> one memory's content
63
+ memories put <id> <path> <file|-> write a memory whole (seed it), from a file or stdin
64
+ memories rm <id> <path> delete a memory, or every memory in a folder
65
+ memories versions <id> [--path <path>] [--limit <n>] [--cursor <c>]
66
+ every change to its memories, newest first, as JSON
22
67
 
23
68
  ${runtimeFlagsUsage}`;
24
- const HASH = /^sha256:[0-9a-f]{64}$/;
25
- /** Every `sha256:<hex>` a manifest names under a `files` map, with where it names it. */
26
- export function namedFiles(manifest) {
27
- const named = new Map();
28
- const walk = (value, path) => {
29
- if (!value || typeof value !== "object")
30
- return;
31
- if (Array.isArray(value))
32
- return value.forEach((item, index) => walk(item, `${path}[${index}]`));
33
- for (const [key, child] of Object.entries(value)) {
34
- if (key === "files" && child && typeof child === "object" && !Array.isArray(child)) {
35
- for (const [file, hash] of Object.entries(child))
36
- if (typeof hash === "string" && HASH.test(hash) && !named.has(hash))
37
- named.set(hash, `${path}.files["${file}"]`);
38
- }
39
- else
40
- walk(child, `${path}.${key}`);
41
- }
42
- };
43
- walk(manifest, "manifest");
44
- return named;
45
- }
46
- /** The files under `dirs`, by their hash. */
47
- async function filesByHash(dirs) {
48
- const found = new Map();
49
- const visit = async (dir) => {
50
- for (const entry of await readdir(dir, { withFileTypes: true })) {
51
- const path = join(dir, entry.name);
52
- if (entry.isDirectory())
53
- await visit(path);
54
- else if (entry.isFile())
55
- found.set(await definitionFileHash(new Uint8Array(await readFile(path))), path);
56
- }
57
- };
58
- for (const dir of dirs) {
59
- try {
60
- await visit(dir);
61
- }
62
- catch (error) {
63
- throw new CliError(`Cannot read --files ${dir}: ${error.message}`, 1);
64
- }
65
- }
66
- return found;
69
+ /** What `agents save` (a definition, before protocol 16) says now. */
70
+ const SAVE_REMOVED = "nylorun agents save was removed in protocol 16: agents run from saved agent manifests. Save one with nylorun apply <dir> (agent.manifest.json; nylorun migrate v8 <agent.json> writes one from a v7 manifest), then make an Agent from it with nylorun agents put <id> --manifest <name>.";
71
+ /**
72
+ * The manifest an Agent runs, as the list prints it: `name@version`, the version it moves to
73
+ * (track T3), and whether it tracks.
74
+ */
75
+ function manifestOf(agent) {
76
+ const name = parseAgentManifestRef(agent.manifest)?.name ?? agent.manifest;
77
+ const target = agent.targetVersion ? ` → ${agent.targetVersion}` : "";
78
+ return `${name}@${agent.resolvedVersion}${target} ${agent.tracking === "tracking" ? "tracks" : "pinned"}`;
79
+ }
80
+ /** What a move left pending, for a person: the version, and what it waits for. */
81
+ function pendingOf(agent) {
82
+ if (!agent.targetVersion)
83
+ return "";
84
+ return agent.waitingFor === "secrets"
85
+ ? `; version ${agent.targetVersion} waits for its required secrets (nylorun agents secrets put ${agent.id} <name>)`
86
+ : `; version ${agent.targetVersion} applies at its pod's next start (nylorun agents restart ${agent.id} applies it now)`;
67
87
  }
68
88
  async function list(deps, args) {
69
- const usage = "nylorun agents list [--json]";
70
- const flags = parseFlags(args, { booleans: ["--json"] }, usage);
89
+ const usage = "nylorun agents list [--state <state>] [--manifest <name>] [--label <key=value>]... [--outdated] [--limit <n>] [--cursor <c>] [--json]";
90
+ const flags = parseFlags(args, {
91
+ booleans: ["--json", "--outdated", "--behind"],
92
+ values: ["--limit", "--cursor", "--state", "--manifest"],
93
+ lists: ["--label"],
94
+ }, usage);
71
95
  positional(flags, 0, usage);
72
- const { client } = await runtimeClient(deps, flags);
73
- const { agents } = await runtimeCall(() => client.listAgents());
96
+ const labels = labelsOf(flags);
97
+ const state = flags.values.get("--state");
98
+ const manifest = flags.values.get("--manifest");
99
+ // Moving to another version (track T3); --behind is its former name.
100
+ const outdated = flags.booleans.has("--outdated") || flags.booleans.has("--behind");
101
+ const filter = {
102
+ ...(labels ? { labels } : {}),
103
+ ...(state ? { state } : {}),
104
+ ...(manifest ? { manifest } : {}),
105
+ ...(outdated ? { outdated: true } : {}),
106
+ };
107
+ const limit = integerFlag(flags, "--limit");
108
+ const cursor = flags.values.get("--cursor");
109
+ const { client, connection } = await runtimeClient(deps, flags);
110
+ const paged = limit !== undefined || cursor !== undefined;
111
+ const page = paged
112
+ ? await runtimeCall(() => client.agents.page({
113
+ ...filter,
114
+ ...(limit === undefined ? {} : { limit }),
115
+ ...(cursor ? { cursor } : {}),
116
+ }))
117
+ : { agents: await runtimeCall(() => client.agents.list(filter)), nextCursor: null };
118
+ const agents = page.agents;
74
119
  if (flags.booleans.has("--json")) {
75
- printJson(deps, agents);
120
+ printJson(deps, paged ? page : agents);
76
121
  return 0;
77
122
  }
78
- if (agents.length === 0) {
79
- deps.out("No agents saved.");
123
+ if (agents.length === 0)
124
+ deps.out(`No Agents on ${connection.label}.`);
125
+ else
126
+ printTable(deps, [
127
+ ["ID", "MANIFEST", "KIND", "STATE", "SESSIONS", "OWNER", "LABELS"],
128
+ ...agents.map((agent) => [
129
+ agent.id,
130
+ manifestOf(agent),
131
+ agent.kind,
132
+ agent.state,
133
+ String(agent.sessions.length),
134
+ agent.owner ?? "-",
135
+ Object.entries(agent.labels)
136
+ .map(([key, value]) => `${key}=${value}`)
137
+ .join(",") || "-",
138
+ ]),
139
+ ]);
140
+ printCursor(deps, page.nextCursor);
141
+ return 0;
142
+ }
143
+ /** A command that takes one Agent id and prints the Runtime's answer. */
144
+ function byId(name, call) {
145
+ return async (deps, args) => {
146
+ const usage = `nylorun agents ${name} <id>`;
147
+ const flags = parseFlags(args, {}, usage);
148
+ const [id] = positional(flags, 1, usage);
149
+ const { client } = await runtimeClient(deps, flags);
150
+ printJson(deps, await runtimeCall(() => call(client, id)));
151
+ return 0;
152
+ };
153
+ }
154
+ async function put(deps, args) {
155
+ const usage = "nylorun agents put <id> --manifest <name[@n]> [--owner <user>] [--label <key=value>]... [--dry-run]";
156
+ for (const removed of ["--kind", "--image"])
157
+ if (args.includes(removed))
158
+ throw usageError(`${removed} was removed in protocol 16: an Agent is made from a saved manifest only. Put what it runs in the manifest's runtime section (nylorun apply), then --manifest <name>.`);
159
+ const flags = parseFlags(args, { booleans: ["--dry-run"], values: ["--manifest", "--owner"], lists: ["--label"] }, usage);
160
+ const [id] = positional(flags, 1, usage);
161
+ const manifest = flags.values.get("--manifest");
162
+ if (manifest === undefined)
163
+ throw usageError(`Usage: ${usage}`);
164
+ if (parseAgentManifestRef(manifest) === undefined)
165
+ throw usageError(`--manifest is an agent manifest's name, or name@version such as triage@3, not ${manifest}.`);
166
+ const owner = flags.values.get("--owner");
167
+ const labels = labelsOf(flags);
168
+ const { client, connection } = await runtimeClient(deps, flags);
169
+ const input = { manifest, ...(owner ? { owner } : {}), ...(labels ? { labels } : {}) };
170
+ if (flags.booleans.has("--dry-run")) {
171
+ printJson(deps, await runtimeCall(() => client.agents.plan(id, input)));
80
172
  return 0;
81
173
  }
82
- printTable(deps, [
83
- ["ID", "KIND", "DESCRIPTION"],
84
- ...agents.map((agent) => {
85
- const manifest = "manifest" in agent ? agent.manifest : undefined;
86
- const description = ("description" in agent ? agent.description : undefined) ?? manifest?.description;
87
- return [agent.agentId, manifest?.kind ?? "agent", description ?? "-"];
88
- }),
89
- ]);
174
+ const agent = await runtimeCall(() => client.agents.put(id, input));
175
+ deps.err(`Agent ${agent.id} runs ${manifestOf(agent)} on ${connection.label}${pendingOf(agent)}.`);
176
+ printJson(deps, agent);
90
177
  return 0;
91
178
  }
92
- async function get(deps, args) {
93
- const usage = "nylorun agents get <id>";
179
+ /** `pin <id> <version>` and `track <id>`: moves an Agent within its manifest (track T3). */
180
+ async function move(deps, args, pin) {
181
+ const usage = pin ? "nylorun agents pin <id> <version>" : "nylorun agents track <id>";
94
182
  const flags = parseFlags(args, {}, usage);
183
+ const [id, version] = positional(flags, pin ? 2 : 1, usage);
184
+ if (pin && !/^[1-9]\d*$/.test(version))
185
+ throw usageError("The version is a positive number.");
186
+ const { client, connection } = await runtimeClient(deps, flags);
187
+ const current = await runtimeCall(() => client.agents.get(id));
188
+ const name = parseAgentManifestRef(current.manifest)?.name ?? current.manifest;
189
+ const agent = await runtimeCall(() => client.agents.put(id, { manifest: pin ? `${name}@${version}` : name }));
190
+ deps.err(`Agent ${agent.id} runs ${manifestOf(agent)} on ${connection.label}${pendingOf(agent)}.`);
191
+ printJson(deps, agent);
192
+ return 0;
193
+ }
194
+ async function restart(deps, args) {
195
+ const usage = "nylorun agents restart <id> [--retry]";
196
+ const flags = parseFlags(args, { booleans: ["--retry"] }, usage);
95
197
  const [id] = positional(flags, 1, usage);
96
198
  const { client, connection } = await runtimeClient(deps, flags);
97
- const { agents } = await runtimeCall(() => client.listAgents());
98
- const agent = agents.find((item) => item.agentId === id);
99
- if (!agent) {
100
- deps.err(`No agent ${id} on ${connection.label}.`);
101
- return 1;
102
- }
199
+ const agent = await runtimeCall(() => client.agents.restart(id, { ...(flags.booleans.has("--retry") ? { retry: true } : {}) }));
200
+ deps.err(`Agent ${agent.id} runs ${manifestOf(agent)} on ${connection.label}${pendingOf(agent)}.`);
103
201
  printJson(deps, agent);
104
202
  return 0;
105
203
  }
106
- async function save(deps, args) {
107
- const usage = "nylorun agents save <manifest.json|-> [--files <dir>]... [--implementation-version <v>] [--dry-run]";
108
- const flags = parseFlags(args, { booleans: ["--dry-run"], values: ["--implementation-version"], lists: ["--files"] }, usage);
109
- const dryRun = flags.booleans.has("--dry-run");
110
- const [path] = positional(flags, 1, usage);
111
- const manifest = await readJson(path, "The manifest");
112
- if (typeof manifest.id !== "string" || manifest.id === "")
113
- throw usageError("The manifest has no id.");
114
- const named = namedFiles(manifest);
115
- const found = named.size === 0 ? new Map() : await filesByHash(flags.lists.get("--files") ?? []);
116
- const missing = [...named].filter(([hash]) => !found.has(hash));
117
- if (missing.length > 0)
118
- throw new CliError(`The manifest names ${missing.length} ${missing.length === 1 ? "file" : "files"} not found under ${flags.lists.get("--files")?.join(", ") || "any --files folder"}:\n${missing.map(([hash, where]) => ` ${where} = ${hash}`).join("\n")}`, 1);
204
+ async function fork(deps, args) {
205
+ const usage = "nylorun agents fork <id> <new-id> [--manifest <name[@n]>] [--disk <latest|n|empty>] [--secrets <copy|none>] [--label <key=value>]... [--dry-run]";
206
+ const flags = parseFlags(args, { booleans: ["--dry-run"], values: ["--manifest", "--disk", "--secrets"], lists: ["--label"] }, usage);
207
+ const [id, next] = positional(flags, 2, usage);
208
+ const disk = flags.values.get("--disk");
209
+ if (disk !== undefined && disk !== "latest" && disk !== "empty" && !/^[1-9]\d*$/.test(disk))
210
+ throw usageError("--disk is latest, empty or a version number.");
211
+ const secrets = flags.values.get("--secrets");
212
+ if (secrets !== undefined && secrets !== "copy" && secrets !== "none")
213
+ throw usageError("--secrets is copy or none.");
214
+ const manifest = flags.values.get("--manifest");
215
+ const labels = labelsOf(flags);
216
+ const options = {
217
+ id: next,
218
+ ...(manifest ? { manifest } : {}),
219
+ ...(disk === undefined ? {} : { disk: disk === "latest" || disk === "empty" ? disk : Number(disk) }),
220
+ ...(secrets ? { secrets: secrets } : {}),
221
+ ...(labels ? { labels } : {}),
222
+ };
119
223
  const { client, connection } = await runtimeClient(deps, flags);
120
- let uploaded = 0;
121
- for (const hash of named.keys()) {
122
- if (await runtimeCall(() => client.files.has(hash)))
123
- continue;
124
- const bytes = new Uint8Array(await readFile(found.get(hash)));
125
- await runtimeCall(() => client.files.upload(bytes, { sha256: hash }));
126
- uploaded += 1;
127
- }
128
- const version = flags.values.get("--implementation-version");
129
- const saved = await runtimeCall(() => client.saveAgent({ id: manifest.id, manifest: manifest }, { ...(version === undefined ? {} : { implementationVersion: version }), ...(dryRun ? { dryRun } : {}) }));
130
- deps.err(`${dryRun ? "Checked (and saved nothing of)" : "Saved"} agent ${manifest.id} on ${connection.label}${named.size ? ` (${named.size} files, ${uploaded} uploaded)` : ""}.`);
131
- printJson(deps, saved);
224
+ if (flags.booleans.has("--dry-run")) {
225
+ printJson(deps, await runtimeCall(() => client.agents.planFork(id, options)));
226
+ return 0;
227
+ }
228
+ const forked = await runtimeCall(() => client.agents.fork(id, options));
229
+ const { plan } = forked;
230
+ deps.err(`Forked Agent ${id} into ${next} on ${connection.label}: ${plan.manifest.name}@${plan.manifest.version}, ${plan.disk === null ? "an empty disk" : `disk version ${plan.disk}`}, ${plan.secrets.copied.length} secrets copied${plan.setup ? "; its setup runs before the first turn" : ""}.`);
231
+ if (plan.secrets.missing.length > 0)
232
+ deps.err(` set before use: ${plan.secrets.missing.join(", ")} (nylorun agents secrets put ${next} <name>)`);
233
+ printJson(deps, forked);
234
+ return 0;
235
+ }
236
+ async function deleteAgent(deps, args) {
237
+ const usage = "nylorun agents rm <id>";
238
+ const flags = parseFlags(args, {}, usage);
239
+ const [id] = positional(flags, 1, usage);
240
+ const { client, connection } = await runtimeClient(deps, flags);
241
+ const deleted = await runtimeCall(async () => {
242
+ try {
243
+ return await client.agents.delete(id);
244
+ }
245
+ catch (error) {
246
+ if (error.status === 404)
247
+ return undefined;
248
+ throw error;
249
+ }
250
+ });
251
+ if (!deleted || deleted.deleted === false) {
252
+ deps.err(`No Agent ${id} on ${connection.label}.`);
253
+ return 1;
254
+ }
255
+ deps.out(`Deleted Agent ${id}: its sandbox, files and vault.`);
256
+ return 0;
257
+ }
258
+ async function restore(deps, args) {
259
+ const usage = "nylorun agents restore <id> <version|latest>";
260
+ const flags = parseFlags(args, {}, usage);
261
+ const [id, version] = positional(flags, 2, usage);
262
+ if (version !== "latest" && !/^[1-9]\d*$/.test(version))
263
+ throw usageError("The version is a positive number or latest.");
264
+ const { client } = await runtimeClient(deps, flags);
265
+ printJson(deps, await runtimeCall(() => client.agents.restore(id, version === "latest" ? "latest" : Number(version))));
266
+ return 0;
267
+ }
268
+ async function deleteVersion(deps, args) {
269
+ const usage = "nylorun agents delete-version <id> <version>";
270
+ const flags = parseFlags(args, {}, usage);
271
+ const [id, version] = positional(flags, 2, usage);
272
+ if (!/^[1-9]\d*$/.test(version))
273
+ throw usageError("The version is a positive number.");
274
+ const { client } = await runtimeClient(deps, flags);
275
+ printJson(deps, await runtimeCall(() => client.agents.deleteVersion(id, Number(version))));
276
+ return 0;
277
+ }
278
+ async function events(deps, args) {
279
+ const usage = "nylorun agents events <id> [--from <n>] [--types <type,…>] [--since <time>] [--until <time>]";
280
+ const flags = parseFlags(args, { values: ["--from", "--types", "--since", "--until"] }, usage);
281
+ const [id] = positional(flags, 1, usage);
282
+ const from = integerFlag(flags, "--from");
283
+ const types = flags.values.get("--types")?.split(",").map((type) => type.trim()).filter(Boolean);
284
+ const since = flags.values.get("--since");
285
+ const until = flags.values.get("--until");
286
+ const { client } = await runtimeClient(deps, flags);
287
+ printJson(deps, await runtimeCall(() => client.agents.events(id, {
288
+ ...(from === undefined ? {} : { from }),
289
+ ...(types === undefined || types.length === 0 ? {} : { types }),
290
+ ...(since === undefined ? {} : { since }),
291
+ ...(until === undefined ? {} : { until }),
292
+ })));
293
+ return 0;
294
+ }
295
+ async function secrets(deps, args) {
296
+ const [verb, ...rest] = args;
297
+ if (isHelp(verb))
298
+ throw new HelpRequest(usageLinesOf(agentsUsage, "secrets"));
299
+ if (verb === "list" || verb === "ls") {
300
+ const usage = "nylorun agents secrets list <id>";
301
+ const flags = parseFlags(rest, {}, usage);
302
+ const [id] = positional(flags, 1, usage);
303
+ const { client } = await runtimeClient(deps, flags);
304
+ printJson(deps, await runtimeCall(() => client.agents.secrets.list(id)));
305
+ return 0;
306
+ }
307
+ if (verb === "get") {
308
+ const usage = "nylorun agents secrets get <id> <name>";
309
+ const flags = parseFlags(rest, {}, usage);
310
+ const [id, name] = positional(flags, 2, usage);
311
+ const { client } = await runtimeClient(deps, flags);
312
+ printJson(deps, await runtimeCall(() => client.agents.secrets.get(id, name)));
313
+ return 0;
314
+ }
315
+ if (verb === "put") {
316
+ const usage = "nylorun agents secrets put <id> <name> [<secret.json|->]";
317
+ const flags = parseFlags(rest, {}, usage);
318
+ if (flags.rest.length < 2 || flags.rest.length > 3)
319
+ throw usageError(`Usage: ${usage}`);
320
+ const [id, name, path] = flags.rest;
321
+ if (path === undefined) {
322
+ // A slot its manifest declares (D64): the value only, piped in; the binding is the slot's.
323
+ const value = await readSecret(deps, `value of ${name}`, `printf %s "$TOKEN" | nylorun agents secrets put ${id} ${name}`);
324
+ const { client } = await runtimeClient(deps, flags);
325
+ printJson(deps, await runtimeCall(() => client.agents.secrets.put(id, name, { value })));
326
+ return 0;
327
+ }
328
+ const secret = await readJson(path, "The secret");
329
+ if (typeof secret.type !== "string")
330
+ throw usageError('The secret needs a "type".');
331
+ const { client } = await runtimeClient(deps, flags);
332
+ printJson(deps, await runtimeCall(() => client.agents.secrets.put(id, name, secret)));
333
+ return 0;
334
+ }
335
+ if (verb === "rm" || verb === "delete") {
336
+ const usage = "nylorun agents secrets rm <id> <name>";
337
+ const flags = parseFlags(rest, {}, usage);
338
+ const [id, name] = positional(flags, 2, usage);
339
+ const { client } = await runtimeClient(deps, flags);
340
+ await runtimeCall(() => client.agents.secrets.delete(id, name));
341
+ deps.out(`Deleted secret ${name} of Agent ${id}.`);
342
+ return 0;
343
+ }
344
+ throw usageError(`Usage: nylorun agents secrets <list|get|put|rm> …\n\n${agentsUsage}`);
345
+ }
346
+ /** `nylorun agents triggers <list|pause|resume|run> …` (track T4). */
347
+ async function triggers(deps, args) {
348
+ const [verb, ...rest] = args;
349
+ if (isHelp(verb))
350
+ throw new HelpRequest(usageLinesOf(agentsUsage, "triggers"));
351
+ if (verb === "list" || verb === "ls") {
352
+ const usage = "nylorun agents triggers list <id>";
353
+ const flags = parseFlags(rest, {}, usage);
354
+ const [id] = positional(flags, 1, usage);
355
+ const { client } = await runtimeClient(deps, flags);
356
+ printJson(deps, await runtimeCall(() => client.agents.triggers(id)));
357
+ return 0;
358
+ }
359
+ if (verb === "pause" || verb === "resume") {
360
+ const usage = `nylorun agents triggers ${verb} <id> <trigger>`;
361
+ const flags = parseFlags(rest, {}, usage);
362
+ const [id, trigger] = positional(flags, 2, usage);
363
+ const { client } = await runtimeClient(deps, flags);
364
+ printJson(deps, await runtimeCall(() => verb === "pause" ? client.agents.pauseTrigger(id, trigger) : client.agents.resumeTrigger(id, trigger)));
365
+ return 0;
366
+ }
367
+ if (verb === "run") {
368
+ const usage = "nylorun agents triggers run <id> <trigger> [--idempotency-key <k>]";
369
+ const flags = parseFlags(rest, { values: ["--idempotency-key"] }, usage);
370
+ const [id, trigger] = positional(flags, 2, usage);
371
+ const key = flags.values.get("--idempotency-key");
372
+ const { client } = await runtimeClient(deps, flags);
373
+ printJson(deps, await runtimeCall(() => client.agents.runTrigger(id, trigger, key === undefined ? {} : { idempotencyKey: key })));
374
+ return 0;
375
+ }
376
+ if (verb === "new-secret") {
377
+ const usage = "nylorun agents triggers new-secret <id> <trigger>";
378
+ const flags = parseFlags(rest, {}, usage);
379
+ const [id, trigger] = positional(flags, 2, usage);
380
+ const { client } = await runtimeClient(deps, flags);
381
+ printJson(deps, await runtimeCall(() => client.agents.rotateWebhookSecret(id, trigger)));
382
+ return 0;
383
+ }
384
+ throw usageError(`Usage: nylorun agents triggers <list|pause|resume|run|new-secret> …\n\n${agentsUsage}`);
385
+ }
386
+ /** `nylorun agents memories <ls|cat|put|rm|versions> …` (track T5). */
387
+ async function memories(deps, args) {
388
+ const [verb, ...rest] = args;
389
+ if (isHelp(verb))
390
+ throw new HelpRequest(usageLinesOf(agentsUsage, "memories"));
391
+ const pageOf = (flags) => {
392
+ const limit = flags.values.get("--limit");
393
+ if (limit !== undefined && !/^[1-9]\d*$/.test(limit))
394
+ throw usageError("--limit must be a positive number.");
395
+ const cursor = flags.values.get("--cursor");
396
+ return { ...(limit === undefined ? {} : { limit: Number(limit) }), ...(cursor === undefined ? {} : { cursor }) };
397
+ };
398
+ if (verb === "ls" || verb === "list") {
399
+ const usage = "nylorun agents memories ls <id> [--prefix <folder>] [--limit <n>] [--cursor <c>]";
400
+ const flags = parseFlags(rest, { values: ["--prefix", "--limit", "--cursor"] }, usage);
401
+ const [id] = positional(flags, 1, usage);
402
+ const prefix = flags.values.get("--prefix");
403
+ const { client } = await runtimeClient(deps, flags);
404
+ printJson(deps, await runtimeCall(() => client.agents.memories.list(id, { ...(prefix === undefined ? {} : { pathPrefix: prefix }), ...pageOf(flags) })));
405
+ return 0;
406
+ }
407
+ if (verb === "cat") {
408
+ const usage = "nylorun agents memories cat <id> <path>";
409
+ const flags = parseFlags(rest, {}, usage);
410
+ const [id, path] = positional(flags, 2, usage);
411
+ const { client } = await runtimeClient(deps, flags);
412
+ deps.out((await runtimeCall(() => client.agents.memories.get(id, path))).content);
413
+ return 0;
414
+ }
415
+ if (verb === "put") {
416
+ const usage = "nylorun agents memories put <id> <path> <file|->";
417
+ const flags = parseFlags(rest, {}, usage);
418
+ const [id, path, source] = positional(flags, 3, usage);
419
+ const content = await readText(deps, source);
420
+ const { client } = await runtimeClient(deps, flags);
421
+ printJson(deps, await runtimeCall(() => client.agents.memories.put(id, path, content)));
422
+ return 0;
423
+ }
424
+ if (verb === "rm" || verb === "delete") {
425
+ const usage = "nylorun agents memories rm <id> <path>";
426
+ const flags = parseFlags(rest, {}, usage);
427
+ const [id, path] = positional(flags, 2, usage);
428
+ const { client } = await runtimeClient(deps, flags);
429
+ const deleted = await runtimeCall(() => client.agents.memories.delete(id, path));
430
+ deps.out(`Deleted ${deleted.deleted} ${deleted.deleted === 1 ? "memory" : "memories"} at ${deleted.path} of Agent ${id}.`);
431
+ return 0;
432
+ }
433
+ if (verb === "versions") {
434
+ const usage = "nylorun agents memories versions <id> [--path <path>] [--limit <n>] [--cursor <c>]";
435
+ const flags = parseFlags(rest, { values: ["--path", "--limit", "--cursor"] }, usage);
436
+ const [id] = positional(flags, 1, usage);
437
+ const path = flags.values.get("--path");
438
+ const { client } = await runtimeClient(deps, flags);
439
+ printJson(deps, await runtimeCall(() => client.agents.memories.versions(id, { ...(path === undefined ? {} : { path }), ...pageOf(flags) })));
440
+ return 0;
441
+ }
442
+ throw usageError(`Usage: nylorun agents memories <ls|cat|put|rm|versions> …\n\n${agentsUsage}`);
443
+ }
444
+ /** `nylorun agents runs <id>` (track T4). */
445
+ async function runs(deps, args) {
446
+ const usage = "nylorun agents runs <id> [--trigger <id>] [--limit <n>] [--cursor <c>]";
447
+ const flags = parseFlags(args, { values: ["--trigger", "--limit", "--cursor"] }, usage);
448
+ const [id] = positional(flags, 1, usage);
449
+ const limit = flags.values.get("--limit");
450
+ if (limit !== undefined && !/^[1-9]\d*$/.test(limit))
451
+ throw usageError("--limit must be a positive number.");
452
+ const trigger = flags.values.get("--trigger");
453
+ const cursor = flags.values.get("--cursor");
454
+ const { client } = await runtimeClient(deps, flags);
455
+ printJson(deps, await runtimeCall(() => client.agents.runs(id, {
456
+ ...(trigger === undefined ? {} : { triggerId: trigger }),
457
+ ...(limit === undefined ? {} : { limit: Number(limit) }),
458
+ ...(cursor === undefined ? {} : { cursor }),
459
+ })));
132
460
  return 0;
133
461
  }
134
- /** `nylorun agents <list|get|save> ...` */
462
+ const COMMANDS = {
463
+ list,
464
+ ls: list,
465
+ get: byId("get", (client, id) => client.agents.get(id)),
466
+ put,
467
+ pin: (deps, args) => move(deps, args, true),
468
+ track: (deps, args) => move(deps, args, false),
469
+ restart,
470
+ stop: byId("stop", (client, id) => client.agents.stop(id)),
471
+ reset: byId("reset", (client, id) => client.agents.reset(id)),
472
+ rm: deleteAgent,
473
+ delete: deleteAgent,
474
+ snapshot: byId("snapshot", (client, id) => client.agents.snapshot(id)),
475
+ versions: byId("versions", (client, id) => client.agents.versions(id)),
476
+ restore,
477
+ "delete-version": deleteVersion,
478
+ events,
479
+ secrets,
480
+ fork,
481
+ triggers,
482
+ runs,
483
+ memories,
484
+ };
485
+ /** `nylorun agents <command> ...` */
135
486
  export async function agentsCommand(deps, args) {
136
487
  const [sub, ...rest] = args;
137
488
  if (sub === "--help" || sub === "-h") {
138
489
  deps.out(agentsUsage);
139
490
  return 0;
140
491
  }
141
- if (sub === "list" || sub === "ls")
142
- return list(deps, rest);
143
- if (sub === "get")
144
- return get(deps, rest);
145
492
  if (sub === "save")
146
- return save(deps, rest);
147
- throw usageError(sub ? `Unknown agents command ${sub}.\n${agentsUsage}` : agentsUsage);
493
+ throw usageError(SAVE_REMOVED);
494
+ const command = sub !== undefined && Object.hasOwn(COMMANDS, sub) ? COMMANDS[sub] : undefined;
495
+ if (!command)
496
+ throw usageError(sub ? `Unknown agents command ${sub}.\n${agentsUsage}` : agentsUsage);
497
+ return command(deps, rest);
148
498
  }
@@ -0,0 +1,7 @@
1
+ import { type AgentManifestPlan } from "@nylorun/agents";
2
+ import type { StackDeps } from "../stack/commands.js";
3
+ export declare const applyUsage: string;
4
+ /** A plan as lines: the move, then each change, warning and error. */
5
+ export declare function planLines(plan: AgentManifestPlan): string[];
6
+ /** `nylorun apply [dir…] [--dry-run] [--if-match <n>]` */
7
+ export declare function applyCommand(deps: StackDeps, args: readonly string[]): Promise<number>;