nylorun 0.14.0-beta → 0.15.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +226 -144
  3. package/dist/cli.js +141 -47
  4. package/dist/client/agents.d.ts +6 -0
  5. package/dist/client/agents.js +148 -0
  6. package/dist/client/artifacts.d.ts +4 -0
  7. package/dist/client/artifacts.js +210 -0
  8. package/dist/client/doctor.d.ts +7 -2
  9. package/dist/client/doctor.js +14 -13
  10. package/dist/client/environment-manifests.d.ts +10 -0
  11. package/dist/client/environment-manifests.js +173 -0
  12. package/dist/client/environments.d.ts +4 -0
  13. package/dist/client/environments.js +295 -0
  14. package/dist/client/io.d.ts +31 -0
  15. package/dist/client/io.js +72 -0
  16. package/dist/{stack → client}/mcp.d.ts +2 -2
  17. package/dist/{stack → client}/mcp.js +14 -12
  18. package/dist/client/model/pi-ai.d.ts +3 -3
  19. package/dist/client/model/pi-ai.js +4 -4
  20. package/dist/client/models.d.ts +13 -0
  21. package/dist/client/models.js +203 -0
  22. package/dist/client/runtime.d.ts +40 -0
  23. package/dist/client/runtime.js +134 -0
  24. package/dist/client/sessions.d.ts +4 -0
  25. package/dist/client/sessions.js +295 -0
  26. package/dist/client/settings.d.ts +12 -0
  27. package/dist/client/settings.js +103 -0
  28. package/dist/client/signing-keys.d.ts +3 -0
  29. package/dist/client/signing-keys.js +32 -0
  30. package/dist/client/target.d.ts +33 -0
  31. package/dist/client/target.js +65 -0
  32. package/dist/client/tenant.d.ts +22 -0
  33. package/dist/client/tenant.js +176 -0
  34. package/dist/doctor.js +3 -3
  35. package/dist/nylo.js +20 -4
  36. package/dist/sandbox/cluster-file.d.ts +1 -1
  37. package/dist/sandbox/commands.d.ts +1 -1
  38. package/dist/sandbox/commands.js +7 -7
  39. package/dist/sandbox/manifests.d.ts +1 -1
  40. package/dist/sandbox/manifests.js +1 -1
  41. package/dist/sandbox/probe.d.ts +1 -1
  42. package/dist/stack/commands.d.ts +9 -4
  43. package/dist/stack/commands.js +22 -26
  44. package/dist/stack/compose-file.d.ts +1 -1
  45. package/dist/stack/compose-file.js +12 -12
  46. package/dist/stack/env-file.d.ts +2 -2
  47. package/dist/stack/env-file.js +2 -2
  48. package/dist/stack/images.d.ts +1 -1
  49. package/dist/stack/images.js +1 -1
  50. package/dist/stack/index.d.ts +1 -10
  51. package/dist/stack/index.js +3 -19
  52. package/dist/stack/keys.d.ts +2 -2
  53. package/dist/stack/keys.js +14 -11
  54. package/dist/stack/paths.d.ts +1 -1
  55. package/dist/stack/prepare.d.ts +1 -1
  56. package/dist/stack/stacks.js +1 -1
  57. package/dist/telemetry.d.ts +2 -2
  58. package/dist/telemetry.js +4 -4
  59. package/package.json +6 -5
  60. package/dist/client/access/commands.d.ts +0 -8
  61. package/dist/client/access/commands.js +0 -33
  62. package/dist/client/cli.d.ts +0 -2
  63. package/dist/client/cli.js +0 -106
  64. package/dist/client/installation/commands.d.ts +0 -7
  65. package/dist/client/installation/commands.js +0 -82
  66. package/dist/client/project/connection.d.ts +0 -36
  67. package/dist/client/project/connection.js +0 -80
  68. package/dist/client/project/env.d.ts +0 -3
  69. package/dist/client/project/env.js +0 -19
  70. package/dist/stack/sandbox.d.ts +0 -4
  71. package/dist/stack/sandbox.js +0 -107
package/dist/cli.js CHANGED
@@ -3,88 +3,182 @@ import "./baseline.js";
3
3
  import { baselineEnv } from "./baseline.js";
4
4
  import { refuseNativeWindows, runCommand } from "./command.js";
5
5
  import { CliError } from "./errors.js";
6
- import { isStackCommand, keyCommand, keyUsage, mcpCommand, mcpUsage, sandboxCommand, sandboxUsage, stackCommand, stackUsage, studioCommand, } from "./stack/index.js";
7
- const usage = `nylorun <up|down|start|stop|status|logs|studio|reset|ls|delete|sandbox|key|mcp|doctor|telemetry>
6
+ import { defaultStackDeps, stackCommand, stackUsage, studioCommand } from "./stack/index.js";
7
+ const usage = `nylorun <command>
8
8
 
9
- Local Tenants (Docker Compose), one per project:
9
+ The installation:
10
10
  ${stackUsage}
11
- ${sandboxUsage}
12
- ${keyUsage}
13
- ${mcpUsage}
14
11
  doctor [--json] check Node, Docker and Compose v2, and the Tenant's health
15
- telemetry [status|enable|disable] Studio's anonymous usage analytics (on unless disabled,
16
- NYLORUN_TELEMETRY_DISABLED=1, DO_NOT_TRACK=1 or CI)
12
+ doctor sandbox [--tenant <name>] [--json]
13
+ the sandbox backend the Tenant's Host offers
14
+
15
+ Groups, each with its own --help:
16
+ agents list | get | save the Tenant's agents (Runtime API, an application key)
17
+ sessions list | create | get | send | approve | respond | cancel | watch | history
18
+ usage | calls | trace | exec
19
+ the Tenant's sessions (Runtime API)
20
+ environments list | get | put | stop | reset | delete | snapshot | restore | versions
21
+ events | secrets the Tenant's Environments (Runtime API)
22
+ artifacts list | get | upload | download | tree | diff | zip | link | rm
23
+ the Tenant's file artifacts (Runtime API)
24
+ tenant reset | keys | signing-keys | models | usage | calls | budgets | tool-policy |
25
+ settings | coverage | mcp
26
+ the Tenant's settings (Management API, a management key)
27
+ local ls | delete | logs | reset | sandbox | telemetry
28
+ the Tenants on this machine (Docker and kubectl)
17
29
 
18
- A command acts on the Tenant --tenant or NYLORUN_TENANT names, else the linked project's,
19
- else (outside a project) the default Tenant; NYLORUN_HOME sets its Host root
20
- (default ~/.nylorun/tenants/<name>).
30
+ A command acts on the local Tenant --tenant names, else the linked project's, else (outside a
31
+ project) the default Tenant; NYLORUN_HOME sets its Host root (default
32
+ ~/.nylorun/tenants/<name>). Without --tenant, NYLORUN_RUNTIME_URL names a remote installation:
33
+ with NYLORUN_MANAGEMENT_KEY for status, doctor sandbox and tenant; with NYLORUN_SERVER_KEY, or an
34
+ identity provider's token in NYLORUN_TOKEN, for agents, sessions, environments and artifacts.
35
+ The Runtime API groups act for a person with --as <subject> [--scope <scope>]...`;
36
+ const localUsage = `nylorun local <command>
21
37
 
22
- nylorun sets up and runs local Tenants. "nylorun start" in a project creates the
23
- project's Tenant and the Project link; anywhere else, it starts the default Tenant.
24
- nylorun status and reset act on the local Tenant's containers and volumes on this machine.
25
- The package's other command, nylo, is the Runtime client of the linked installation: its
26
- status and reset go through the Management API, and it sets the model provider (nylo --help,
27
- or npx -p nylorun nylo --help).`;
28
- /** `nylo`, the Runtime client (this package's second command), run without installing. */
29
- const CLIENT = "npx -p nylorun nylo";
30
- /** Commands that were removed, or that are the Runtime client's (`nylo`). */
31
- const MOVED_TO_CLIENT = {
38
+ The Tenants on this machine (Docker Compose), one per project:
39
+ ls [--json] the Tenants on this machine, with their memory in use
40
+ delete <tenant> --yes remove a Tenant: containers, volumes and its Host root, with
41
+ the vault key (KEK)
42
+ logs [service] [--tenant <name>] [-f] [--tail <n>]
43
+ container logs
44
+ reset [--tenant <name>] [--yes] delete the Tenant's data (volumes, tenant/ and vault key); the
45
+ next start creates it anew
46
+ sandbox enable | disable | status run the Tenant's sandboxes as pods on a Kubernetes cluster
47
+ (nylorun local sandbox --help)
48
+ telemetry [status|enable|disable] Studio's anonymous usage analytics (on unless disabled,
49
+ NYLORUN_TELEMETRY_DISABLED=1, DO_NOT_TRACK=1 or CI)`;
50
+ /** Commands that moved into a group, or were removed. */
51
+ const MOVED = {
52
+ logs: "nylorun logs moved: nylorun local logs",
53
+ ls: "nylorun ls moved: nylorun local ls",
54
+ delete: "nylorun delete moved: nylorun local delete",
55
+ telemetry: "nylorun telemetry moved: nylorun local telemetry",
56
+ reset: "nylorun reset moved: nylorun local reset deletes a local Tenant's volumes; nylorun tenant reset clears its sessions or Environments through the Management API",
57
+ key: "nylorun key moved: nylorun tenant keys put|list|rm",
58
+ keys: "nylorun keys is nylorun tenant keys put|list|rm",
59
+ mcp: "nylorun mcp moved: nylorun tenant mcp inspect <url>",
60
+ configure: "nylorun configure is nylorun tenant models set",
61
+ access: "nylorun access is nylorun tenant signing-keys list|rotate|revoke",
62
+ env: 'nylorun env was removed: the SDKs read the Project link themselves. For a process outside the project, give it a key of its own (nylorun tenant keys put <id>) and the Runtime URL "nylorun status" prints.',
32
63
  dev: "nylorun dev was removed: run `npx nylorun start` in your project (it creates the project's Tenant and the Project link), then your project's `npm run dev`.",
33
- configure: `nylorun configure is the Runtime client's: ${CLIENT} configure`,
34
64
  serve: "nylorun serve was removed. Use node dist/src/main.js with NYLORUN_RUNTIME_URL and NYLORUN_SERVER_KEY.",
35
- runtime: "nylorun runtime was removed: the local Runtime runs in Docker Compose. Use nylorun up|down|status|logs.",
65
+ runtime: "nylorun runtime was removed: the local Runtime runs in Docker Compose. Use nylorun up|down|status and nylorun local logs.",
66
+ };
67
+ const SANDBOX_MOVED = {
68
+ ls: "nylorun sandbox ls moved: nylorun environments list",
69
+ list: "nylorun sandbox ls moved: nylorun environments list",
70
+ rm: "nylorun sandbox rm moved: nylorun environments delete <id>",
71
+ delete: "nylorun sandbox rm moved: nylorun environments delete <id>",
36
72
  };
37
73
  const LOCAL_UI_REMOVED = "--local-ui was removed: Studio runs in its own container. Run nylorun studio.";
38
74
  const usageError = (message) => new CliError(message, 2);
75
+ /** `nylorun local <command>`: the Docker and kubectl commands of this machine's Tenants. */
76
+ async function local(args) {
77
+ const [command, ...rest] = args;
78
+ if (command === "--help" || command === "-h") {
79
+ console.log(localUsage);
80
+ return 0;
81
+ }
82
+ if (command === "ls" || command === "delete" || command === "logs" || command === "reset")
83
+ return stackCommand(command, rest, baselineEnv());
84
+ if (command === "sandbox") {
85
+ const [sub] = rest;
86
+ if (sub && SANDBOX_MOVED[sub])
87
+ throw usageError(SANDBOX_MOVED[sub]);
88
+ const { runSandboxCommand, sandboxUsage } = await import("./sandbox/commands.js");
89
+ if (sub === "--help" || sub === "-h") {
90
+ console.log(`nylorun local sandbox <enable|disable|status>\n\n${sandboxUsage}`);
91
+ return 0;
92
+ }
93
+ const { spawnKubectl } = await import("./sandbox/kubectl.js");
94
+ const deps = defaultStackDeps(baselineEnv());
95
+ return runSandboxCommand(rest, { stack: deps, kubectl: spawnKubectl(deps.env) });
96
+ }
97
+ if (command === "telemetry") {
98
+ const { telemetryCommand } = await import("./telemetry.js");
99
+ const { defaultNylorunRoot } = await import("./stack/stacks.js");
100
+ return telemetryCommand(rest, {
101
+ nylorunRoot: defaultNylorunRoot(),
102
+ env: baselineEnv(),
103
+ out: (line) => console.log(line),
104
+ });
105
+ }
106
+ throw usageError(command ? `Unknown local command ${command}.\n${localUsage}` : localUsage);
107
+ }
39
108
  async function main() {
40
109
  const [command, ...args] = process.argv.slice(2);
41
110
  if (!command || command === "--help" || command === "-h")
42
111
  return void console.log(usage);
43
112
  refuseNativeWindows();
44
- const moved = MOVED_TO_CLIENT[command];
113
+ if (command === "sandbox") {
114
+ const sub = args[0];
115
+ throw usageError((sub && SANDBOX_MOVED[sub]) ??
116
+ `nylorun sandbox moved: nylorun local sandbox ${sub && !sub.startsWith("-") ? sub : "enable|disable|status"}`);
117
+ }
118
+ const moved = MOVED[command];
45
119
  if (moved)
46
120
  throw usageError(moved);
47
121
  if (command === "status" && args.includes("--env"))
48
- throw usageError(`nylorun status --env is the Runtime client's: ${CLIENT} env`);
122
+ throw usageError(MOVED.env);
123
+ const env = baselineEnv();
49
124
  if (command === "studio") {
50
125
  if (args.includes("--local-ui"))
51
126
  throw usageError(LOCAL_UI_REMOVED);
52
- process.exitCode = await studioCommand(args, baselineEnv());
127
+ process.exitCode = await studioCommand(args, env);
53
128
  return;
54
129
  }
55
- if (command === "sandbox") {
56
- process.exitCode = await sandboxCommand(args, baselineEnv());
57
- return;
130
+ if (command === "status" && !args.includes("--tenant")) {
131
+ // An installation the environment names answers through the Management API.
132
+ const { remoteConnection } = await import("./client/target.js");
133
+ if (remoteConnection(env, "management")) {
134
+ const { remoteStatus } = await import("./client/tenant.js");
135
+ process.exitCode = await remoteStatus(defaultStackDeps(env), args);
136
+ return;
137
+ }
58
138
  }
59
- if (command === "key") {
60
- process.exitCode = await keyCommand(args, baselineEnv());
139
+ if (["up", "start", "down", "stop", "status"].includes(command)) {
140
+ process.exitCode = await stackCommand(command, args, env);
61
141
  return;
62
142
  }
63
- if (command === "mcp") {
64
- process.exitCode = await mcpCommand(args, baselineEnv());
143
+ if (command === "local") {
144
+ process.exitCode = await local(args);
65
145
  return;
66
146
  }
67
- if (isStackCommand(command)) {
68
- process.exitCode = await stackCommand(command, args, baselineEnv());
147
+ if (command === "tenant") {
148
+ const { tenantCommand } = await import("./client/tenant.js");
149
+ process.exitCode = await tenantCommand(defaultStackDeps(env), args);
69
150
  return;
70
151
  }
71
- if (command === "telemetry") {
72
- const { telemetryCommand } = await import("./telemetry.js");
73
- const { defaultNylorunRoot } = await import("./stack/stacks.js");
74
- process.exitCode = await telemetryCommand(args, {
75
- nylorunRoot: defaultNylorunRoot(),
76
- env: baselineEnv(),
77
- out: (line) => console.log(line),
78
- });
152
+ if (command === "agents" || command === "sessions" || command === "environments" || command === "artifacts") {
153
+ const group = {
154
+ agents: async () => (await import("./client/agents.js")).agentsCommand,
155
+ sessions: async () => (await import("./client/sessions.js")).sessionsCommand,
156
+ environments: async () => (await import("./client/environments.js")).environmentsCommand,
157
+ artifacts: async () => (await import("./client/artifacts.js")).artifactsCommand,
158
+ }[command];
159
+ process.exitCode = await (await group())(defaultStackDeps(env), args);
79
160
  return;
80
161
  }
81
162
  if (command === "doctor") {
82
- if (args[0] === "sandbox")
83
- throw usageError(`nylorun doctor sandbox is the Runtime client's: ${CLIENT} doctor sandbox`);
163
+ const [topic, ...options] = args;
164
+ if (topic === "sandbox") {
165
+ const usage = "Usage: nylorun doctor sandbox [--tenant <name>] [--json]";
166
+ const tenantAt = options.indexOf("--tenant");
167
+ const tenant = tenantAt < 0 ? undefined : options[tenantAt + 1];
168
+ const rest = options.filter((_, index) => index !== tenantAt && index !== tenantAt + 1);
169
+ if ((tenantAt >= 0 && (!tenant || tenant.startsWith("-"))) || rest.some((option) => option !== "--json"))
170
+ throw usageError(usage);
171
+ const { doctorSandbox } = await import("./client/doctor.js");
172
+ await doctorSandbox(defaultStackDeps(env), {
173
+ json: rest.includes("--json"),
174
+ ...(tenant ? { tenant } : {}),
175
+ });
176
+ return;
177
+ }
84
178
  if (args.some((option) => option !== "--json"))
85
- throw usageError("Usage: nylorun doctor [--json]");
179
+ throw usageError("Usage: nylorun doctor [--json] | nylorun doctor sandbox [--tenant <name>] [--json]");
86
180
  const { doctorStack } = await import("./doctor.js");
87
- process.exitCode = await doctorStack({ json: args.includes("--json"), env: baselineEnv() });
181
+ process.exitCode = await doctorStack({ json: args.includes("--json"), env });
88
182
  return;
89
183
  }
90
184
  throw usageError(usage);
@@ -0,0 +1,6 @@
1
+ import type { StackDeps } from "../stack/commands.js";
2
+ export declare const agentsUsage: string;
3
+ /** Every `sha256:<hex>` a manifest names under a `files` map, with where it names it. */
4
+ export declare function namedFiles(manifest: unknown): Map<string, string>;
5
+ /** `nylorun agents <list|get|save> ...` */
6
+ export declare function agentsCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
@@ -0,0 +1,148 @@
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>
12
+
13
+ The Tenant's saved agents, through the Runtime API with an application key (or NYLORUN_TOKEN).
14
+
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
22
+
23
+ ${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
+ }
68
+ async function list(deps, args) {
69
+ const usage = "nylorun agents list [--json]";
70
+ const flags = parseFlags(args, { booleans: ["--json"] }, usage);
71
+ positional(flags, 0, usage);
72
+ const { client } = await runtimeClient(deps, flags);
73
+ const { agents } = await runtimeCall(() => client.listAgents());
74
+ if (flags.booleans.has("--json")) {
75
+ printJson(deps, agents);
76
+ return 0;
77
+ }
78
+ if (agents.length === 0) {
79
+ deps.out("No agents saved.");
80
+ return 0;
81
+ }
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
+ ]);
90
+ return 0;
91
+ }
92
+ async function get(deps, args) {
93
+ const usage = "nylorun agents get <id>";
94
+ const flags = parseFlags(args, {}, usage);
95
+ const [id] = positional(flags, 1, usage);
96
+ 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
+ }
103
+ printJson(deps, agent);
104
+ return 0;
105
+ }
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);
119
+ 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);
132
+ return 0;
133
+ }
134
+ /** `nylorun agents <list|get|save> ...` */
135
+ export async function agentsCommand(deps, args) {
136
+ const [sub, ...rest] = args;
137
+ if (sub === "--help" || sub === "-h") {
138
+ deps.out(agentsUsage);
139
+ return 0;
140
+ }
141
+ if (sub === "list" || sub === "ls")
142
+ return list(deps, rest);
143
+ if (sub === "get")
144
+ return get(deps, rest);
145
+ if (sub === "save")
146
+ return save(deps, rest);
147
+ throw usageError(sub ? `Unknown agents command ${sub}.\n${agentsUsage}` : agentsUsage);
148
+ }
@@ -0,0 +1,4 @@
1
+ import type { StackDeps } from "../stack/commands.js";
2
+ export declare const artifactsUsage: string;
3
+ /** `nylorun artifacts <command> ...` */
4
+ export declare function artifactsCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * `nylorun artifacts`: the Tenant's file artifacts (`/v1/artifacts`, Runtime API): upload a file
3
+ * (to name in a message with `sessions send --file`), list, download, browse a folder artifact
4
+ * (an Environment's disk), share a version with a capability link, and delete.
5
+ */
6
+ import { createWriteStream } from "node:fs";
7
+ import { readFile } from "node:fs/promises";
8
+ import { basename } from "node:path";
9
+ import { Readable } from "node:stream";
10
+ import { pipeline } from "node:stream/promises";
11
+ import { integerFlag, labelsOf, parseFlags, positional, printCursor, printJson, printTable, readStdin, runtimeCall, runtimeClient, runtimeFlagsUsage, usageError, } from "./runtime.js";
12
+ export const artifactsUsage = `nylorun artifacts <command>
13
+
14
+ The Tenant's file artifacts, through the Runtime API with an application key (or NYLORUN_TOKEN).
15
+
16
+ list [--session <id>] [--label <key=value>]... [--limit <n>] [--cursor <c>] [--json]
17
+ newest first: id, kind, name, latest version, owner
18
+ get <id> the artifact and its versions, as JSON
19
+ upload <file|-> [--name <name>] [--content-type <type>] [--session <id>] [--label <key=value>]...
20
+ upload a file as a new artifact (--to <id>: as its next
21
+ version); prints its id
22
+ download <id> [--version <n>] [--output <file>]
23
+ the bytes, to a file or standard output
24
+ tree <id> [--version <n>] a folder artifact's files
25
+ diff <id> [--version <n>] [--from <n>]
26
+ what changed between two versions of a folder
27
+ zip <id> [--version <n>] --output <file>
28
+ a folder artifact as a zip
29
+ link <id> [--version <n>] [--expires-in <s>] [--file <path>]
30
+ a capability link anyone holding it may download
31
+ rm <id> delete the artifact and its versions
32
+
33
+ ${runtimeFlagsUsage}`;
34
+ async function list(deps, args) {
35
+ const usage = "nylorun artifacts list [--session <id>] [--label <key=value>]... [--limit <n>] [--cursor <c>] [--json]";
36
+ const flags = parseFlags(args, { booleans: ["--json"], values: ["--session", "--limit", "--cursor"], lists: ["--label"] }, usage);
37
+ positional(flags, 0, usage);
38
+ const sessionId = flags.values.get("--session");
39
+ const cursor = flags.values.get("--cursor");
40
+ const limit = integerFlag(flags, "--limit");
41
+ const labels = labelsOf(flags);
42
+ const { client } = await runtimeClient(deps, flags);
43
+ const page = await runtimeCall(() => client.artifacts.page({
44
+ ...(sessionId ? { sessionId } : {}),
45
+ ...(labels ? { labels } : {}),
46
+ ...(limit === undefined ? {} : { limit }),
47
+ ...(cursor ? { cursor } : {}),
48
+ }));
49
+ if (flags.booleans.has("--json")) {
50
+ printJson(deps, page);
51
+ return 0;
52
+ }
53
+ if (page.artifacts.length === 0)
54
+ deps.out("No artifacts.");
55
+ else
56
+ printTable(deps, [
57
+ ["ID", "KIND", "NAME", "VERSION", "OWNER"],
58
+ ...page.artifacts.map((item) => [
59
+ item.artifactId,
60
+ item.kind,
61
+ item.name,
62
+ String(item.latestVersion),
63
+ item.sessionId
64
+ ? `session ${item.sessionId}`
65
+ : item.environmentId
66
+ ? `environment ${item.environmentId}`
67
+ : "tenant",
68
+ ]),
69
+ ]);
70
+ printCursor(deps, page.nextCursor);
71
+ return 0;
72
+ }
73
+ async function get(deps, args) {
74
+ const usage = "nylorun artifacts get <id>";
75
+ const flags = parseFlags(args, {}, usage);
76
+ const [id] = positional(flags, 1, usage);
77
+ const { client } = await runtimeClient(deps, flags);
78
+ printJson(deps, await runtimeCall(() => client.artifacts.get(id)));
79
+ return 0;
80
+ }
81
+ async function upload(deps, args) {
82
+ const usage = "nylorun artifacts upload <file|-> [--name <name>] [--content-type <type>] [--session <id>] [--label <key=value>]... [--to <id>]";
83
+ const flags = parseFlags(args, { values: ["--name", "--content-type", "--session", "--to"], lists: ["--label"] }, usage);
84
+ const [path] = positional(flags, 1, usage);
85
+ const to = flags.values.get("--to");
86
+ const name = flags.values.get("--name") ?? (path === "-" ? undefined : basename(path));
87
+ if (!to && !name)
88
+ throw usageError("Name the file with --name when it comes from standard input.");
89
+ if (to && (flags.values.has("--name") || flags.values.has("--session") || flags.lists.has("--label")))
90
+ throw usageError("--to uploads a version: --name, --session and --label belong to the artifact.");
91
+ const bytes = new Uint8Array(path === "-" ? await readStdin() : await readFile(path));
92
+ const contentType = flags.values.get("--content-type");
93
+ const sessionId = flags.values.get("--session");
94
+ const labels = labelsOf(flags);
95
+ const { client } = await runtimeClient(deps, flags);
96
+ const uploaded = await runtimeCall(() => to
97
+ ? client.artifacts.uploadVersion(to, bytes, contentType ? { contentType } : {})
98
+ : client.artifacts.upload(bytes, {
99
+ name: name,
100
+ ...(contentType ? { contentType } : {}),
101
+ ...(sessionId ? { sessionId } : {}),
102
+ ...(labels ? { labels } : {}),
103
+ }));
104
+ deps.err(`Uploaded ${uploaded.artifact.name}: version ${uploaded.version.version} of artifact ${uploaded.artifact.artifactId}.`);
105
+ deps.out(uploaded.artifact.artifactId);
106
+ return 0;
107
+ }
108
+ /** Writes `response`'s body to `output`, or to standard output. */
109
+ async function save(response, output) {
110
+ if (!response.body)
111
+ return;
112
+ const source = Readable.fromWeb(response.body);
113
+ await pipeline(source, output === undefined ? process.stdout : createWriteStream(output));
114
+ }
115
+ async function download(deps, args) {
116
+ const usage = "nylorun artifacts download <id> [--version <n>] [--output <file>]";
117
+ const flags = parseFlags(args, { values: ["--version", "--output"] }, usage);
118
+ const [id] = positional(flags, 1, usage);
119
+ const version = integerFlag(flags, "--version");
120
+ const { client } = await runtimeClient(deps, flags);
121
+ const response = await runtimeCall(() => client.artifacts.download(id, version === undefined ? {} : { version }));
122
+ await save(response, flags.values.get("--output"));
123
+ return 0;
124
+ }
125
+ async function tree(deps, args) {
126
+ const usage = "nylorun artifacts tree <id> [--version <n>]";
127
+ const flags = parseFlags(args, { values: ["--version"] }, usage);
128
+ const [id] = positional(flags, 1, usage);
129
+ const version = integerFlag(flags, "--version");
130
+ const { client } = await runtimeClient(deps, flags);
131
+ printJson(deps, await runtimeCall(() => client.artifacts.tree(id, version === undefined ? {} : { version })));
132
+ return 0;
133
+ }
134
+ async function diff(deps, args) {
135
+ const usage = "nylorun artifacts diff <id> [--version <n>] [--from <n>]";
136
+ const flags = parseFlags(args, { values: ["--version", "--from"] }, usage);
137
+ const [id] = positional(flags, 1, usage);
138
+ const version = integerFlag(flags, "--version");
139
+ const from = integerFlag(flags, "--from");
140
+ const { client } = await runtimeClient(deps, flags);
141
+ printJson(deps, await runtimeCall(() => client.artifacts.diff(id, {
142
+ ...(version === undefined ? {} : { version }),
143
+ ...(from === undefined ? {} : { from }),
144
+ })));
145
+ return 0;
146
+ }
147
+ async function zip(deps, args) {
148
+ const usage = "nylorun artifacts zip <id> [--version <n>] --output <file>";
149
+ const flags = parseFlags(args, { values: ["--version", "--output"] }, usage);
150
+ const [id] = positional(flags, 1, usage);
151
+ const output = flags.values.get("--output");
152
+ if (!output)
153
+ throw usageError(`Usage: ${usage}`);
154
+ const version = integerFlag(flags, "--version");
155
+ const { client } = await runtimeClient(deps, flags);
156
+ const response = await runtimeCall(() => client.artifacts.zip(id, version === undefined ? {} : { version }));
157
+ await save(response, output);
158
+ return 0;
159
+ }
160
+ async function link(deps, args) {
161
+ const usage = "nylorun artifacts link <id> [--version <n>] [--expires-in <s>] [--file <path>]";
162
+ const flags = parseFlags(args, { values: ["--version", "--expires-in", "--file"] }, usage);
163
+ const [id] = positional(flags, 1, usage);
164
+ const version = integerFlag(flags, "--version");
165
+ const expiresIn = integerFlag(flags, "--expires-in");
166
+ const file = flags.values.get("--file");
167
+ const { client } = await runtimeClient(deps, flags);
168
+ const created = await runtimeCall(() => client.artifacts.link(id, {
169
+ ...(version === undefined ? {} : { version }),
170
+ ...(expiresIn === undefined ? {} : { expiresIn }),
171
+ ...(file ? { file } : {}),
172
+ }));
173
+ deps.err(JSON.stringify(created));
174
+ deps.out(created.url);
175
+ return 0;
176
+ }
177
+ async function rm(deps, args) {
178
+ const usage = "nylorun artifacts rm <id>";
179
+ const flags = parseFlags(args, {}, usage);
180
+ const [id] = positional(flags, 1, usage);
181
+ const { client } = await runtimeClient(deps, flags);
182
+ await runtimeCall(() => client.artifacts.delete(id));
183
+ deps.out(`Deleted artifact ${id}.`);
184
+ return 0;
185
+ }
186
+ const COMMANDS = {
187
+ list,
188
+ ls: list,
189
+ get,
190
+ upload,
191
+ download,
192
+ tree,
193
+ diff,
194
+ zip,
195
+ link,
196
+ rm,
197
+ delete: rm,
198
+ };
199
+ /** `nylorun artifacts <command> ...` */
200
+ export async function artifactsCommand(deps, args) {
201
+ const [sub, ...rest] = args;
202
+ if (sub === "--help" || sub === "-h") {
203
+ deps.out(artifactsUsage);
204
+ return 0;
205
+ }
206
+ const command = sub !== undefined && Object.hasOwn(COMMANDS, sub) ? COMMANDS[sub] : undefined;
207
+ if (!command)
208
+ throw usageError(sub ? `Unknown artifacts command ${sub}.\n${artifactsUsage}` : artifactsUsage);
209
+ return command(deps, rest);
210
+ }
@@ -1,3 +1,4 @@
1
+ import type { StackDeps } from "../stack/commands.js";
1
2
  export type SandboxReport = {
2
3
  preference: string;
3
4
  backend: string | null;
@@ -20,7 +21,11 @@ export type SandboxReport = {
20
21
  };
21
22
  };
22
23
  };
23
- /** `nylo doctor sandbox`: Tenant sandbox report via the Management API (F2-4). */
24
- export declare function doctorSandbox(options: {
24
+ /**
25
+ * `nylorun doctor sandbox`: the Tenant's sandbox report (`GET /v1/tenant/sandbox`, Management API)
26
+ * of the installation `target.ts` picks (F2-4).
27
+ */
28
+ export declare function doctorSandbox(deps: StackDeps, options: {
25
29
  json: boolean;
30
+ tenant?: string;
26
31
  }): Promise<void>;