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