nylorun 0.14.0-beta → 0.15.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +105 -0
- package/README.md +226 -144
- package/dist/cli.js +141 -47
- package/dist/client/agents.d.ts +6 -0
- package/dist/client/agents.js +148 -0
- package/dist/client/artifacts.d.ts +4 -0
- package/dist/client/artifacts.js +210 -0
- package/dist/client/doctor.d.ts +7 -2
- package/dist/client/doctor.js +14 -13
- package/dist/client/environment-manifests.d.ts +10 -0
- package/dist/client/environment-manifests.js +173 -0
- package/dist/client/environments.d.ts +4 -0
- package/dist/client/environments.js +295 -0
- package/dist/client/io.d.ts +31 -0
- package/dist/client/io.js +72 -0
- package/dist/{stack → client}/mcp.d.ts +2 -2
- package/dist/{stack → client}/mcp.js +14 -12
- package/dist/client/model/pi-ai.d.ts +3 -3
- package/dist/client/model/pi-ai.js +4 -4
- package/dist/client/models.d.ts +13 -0
- package/dist/client/models.js +203 -0
- package/dist/client/runtime.d.ts +40 -0
- package/dist/client/runtime.js +134 -0
- package/dist/client/sessions.d.ts +4 -0
- package/dist/client/sessions.js +295 -0
- package/dist/client/settings.d.ts +12 -0
- package/dist/client/settings.js +103 -0
- package/dist/client/signing-keys.d.ts +3 -0
- package/dist/client/signing-keys.js +32 -0
- package/dist/client/target.d.ts +33 -0
- package/dist/client/target.js +65 -0
- package/dist/client/tenant.d.ts +22 -0
- package/dist/client/tenant.js +176 -0
- package/dist/doctor.js +3 -3
- package/dist/nylo.js +20 -4
- package/dist/sandbox/cluster-file.d.ts +1 -1
- package/dist/sandbox/commands.d.ts +1 -1
- package/dist/sandbox/commands.js +7 -7
- package/dist/sandbox/manifests.d.ts +1 -1
- package/dist/sandbox/manifests.js +1 -1
- package/dist/sandbox/probe.d.ts +1 -1
- package/dist/stack/commands.d.ts +9 -4
- package/dist/stack/commands.js +22 -26
- package/dist/stack/compose-file.d.ts +1 -1
- package/dist/stack/compose-file.js +12 -12
- package/dist/stack/env-file.d.ts +2 -2
- package/dist/stack/env-file.js +2 -2
- package/dist/stack/images.d.ts +1 -1
- package/dist/stack/images.js +1 -1
- package/dist/stack/index.d.ts +1 -10
- package/dist/stack/index.js +3 -19
- package/dist/stack/keys.d.ts +2 -2
- package/dist/stack/keys.js +14 -11
- package/dist/stack/paths.d.ts +1 -1
- package/dist/stack/prepare.d.ts +1 -1
- package/dist/stack/stacks.js +1 -1
- package/dist/telemetry.d.ts +2 -2
- package/dist/telemetry.js +4 -4
- package/package.json +6 -5
- package/dist/client/access/commands.d.ts +0 -8
- package/dist/client/access/commands.js +0 -33
- package/dist/client/cli.d.ts +0 -2
- package/dist/client/cli.js +0 -106
- package/dist/client/installation/commands.d.ts +0 -7
- package/dist/client/installation/commands.js +0 -82
- package/dist/client/project/connection.d.ts +0 -36
- package/dist/client/project/connection.js +0 -80
- package/dist/client/project/env.d.ts +0 -3
- package/dist/client/project/env.js +0 -19
- package/dist/stack/sandbox.d.ts +0 -4
- package/dist/stack/sandbox.js +0 -107
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 {
|
|
7
|
-
const usage = `nylorun <
|
|
6
|
+
import { defaultStackDeps, stackCommand, stackUsage, studioCommand } from "./stack/index.js";
|
|
7
|
+
const usage = `nylorun <command>
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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,
|
|
127
|
+
process.exitCode = await studioCommand(args, env);
|
|
53
128
|
return;
|
|
54
129
|
}
|
|
55
|
-
if (command === "
|
|
56
|
-
|
|
57
|
-
|
|
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 (
|
|
60
|
-
process.exitCode = await
|
|
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 === "
|
|
64
|
-
process.exitCode = await
|
|
143
|
+
if (command === "local") {
|
|
144
|
+
process.exitCode = await local(args);
|
|
65
145
|
return;
|
|
66
146
|
}
|
|
67
|
-
if (
|
|
68
|
-
|
|
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 === "
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
|
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,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
|
+
}
|
package/dist/client/doctor.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
24
|
-
|
|
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>;
|