@nodetool-ai/cli 0.7.0-rc.11
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/dist/ExecutionTree.d.ts +19 -0
- package/dist/ExecutionTree.d.ts.map +1 -0
- package/dist/ExecutionTree.js +100 -0
- package/dist/ExecutionTree.js.map +1 -0
- package/dist/app.d.ts +50 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +767 -0
- package/dist/app.js.map +1 -0
- package/dist/commands/agent.d.ts +11 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +483 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/deploy-helpers.d.ts +46 -0
- package/dist/commands/deploy-helpers.d.ts.map +1 -0
- package/dist/commands/deploy-helpers.js +448 -0
- package/dist/commands/deploy-helpers.js.map +1 -0
- package/dist/commands/deploy.d.ts +8 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +645 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/models-hf.d.ts +9 -0
- package/dist/commands/models-hf.d.ts.map +1 -0
- package/dist/commands/models-hf.js +235 -0
- package/dist/commands/models-hf.js.map +1 -0
- package/dist/commands/models-recommended.d.ts +10 -0
- package/dist/commands/models-recommended.d.ts.map +1 -0
- package/dist/commands/models-recommended.js +128 -0
- package/dist/commands/models-recommended.js.map +1 -0
- package/dist/commands/package-helpers.d.ts +14 -0
- package/dist/commands/package-helpers.d.ts.map +1 -0
- package/dist/commands/package-helpers.js +76 -0
- package/dist/commands/package-helpers.js.map +1 -0
- package/dist/commands/package.d.ts +3 -0
- package/dist/commands/package.d.ts.map +1 -0
- package/dist/commands/package.js +254 -0
- package/dist/commands/package.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +233 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +9 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +48 -0
- package/dist/markdown.js.map +1 -0
- package/dist/nodetool.d.ts +16 -0
- package/dist/nodetool.d.ts.map +1 -0
- package/dist/nodetool.js +1154 -0
- package/dist/nodetool.js.map +1 -0
- package/dist/providers.d.ts +47 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +173 -0
- package/dist/providers.js.map +1 -0
- package/dist/readline-input.d.ts +29 -0
- package/dist/readline-input.d.ts.map +1 -0
- package/dist/readline-input.js +199 -0
- package/dist/readline-input.js.map +1 -0
- package/dist/run-dsl.d.ts +11 -0
- package/dist/run-dsl.d.ts.map +1 -0
- package/dist/run-dsl.js +31 -0
- package/dist/run-dsl.js.map +1 -0
- package/dist/settings.d.ts +18 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +86 -0
- package/dist/settings.js.map +1 -0
- package/dist/stdin.d.ts +48 -0
- package/dist/stdin.d.ts.map +1 -0
- package/dist/stdin.js +352 -0
- package/dist/stdin.js.map +1 -0
- package/dist/useExecutionState.d.ts +44 -0
- package/dist/useExecutionState.d.ts.map +1 -0
- package/dist/useExecutionState.js +281 -0
- package/dist/useExecutionState.js.map +1 -0
- package/dist/websocket-client.d.ts +100 -0
- package/dist/websocket-client.d.ts.map +1 -0
- package/dist/websocket-client.js +351 -0
- package/dist/websocket-client.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nodetool deploy` CLI commands — registered on a Commander `Command`
|
|
3
|
+
* via `registerDeployCommands(program)`. See docs/ for the full spec.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as fsp from "node:fs/promises";
|
|
7
|
+
import * as yaml from "js-yaml";
|
|
8
|
+
import { ComputeType, CPUFlavor, DataCenter, GPUType, configureDocker, configureGCP, configureRunPod, getDeploymentConfigPath, initDeploymentConfig, loadDeploymentConfig, saveDeploymentConfig, WorkflowSyncer } from "@nodetool-ai/deploy";
|
|
9
|
+
import { getCollection } from "@nodetool-ai/vectorstore";
|
|
10
|
+
import { asJson, buildStubDeployment, confirm, getAdminClient, getDeploymentOrExit, getManager, getUserManager, loadConfigOrExit, makeSyncerDeps, parseParamPairs, printTable, promptLine, runEditor } from "./deploy-helpers.js";
|
|
11
|
+
const SUPPORTED_TYPES = [
|
|
12
|
+
"docker",
|
|
13
|
+
"runpod",
|
|
14
|
+
"gcp",
|
|
15
|
+
"fly",
|
|
16
|
+
"railway",
|
|
17
|
+
"huggingface"
|
|
18
|
+
];
|
|
19
|
+
/** Common GCP Cloud Run regions. Hardcoded because not exported by @nodetool-ai/deploy. */
|
|
20
|
+
const GCP_REGIONS = [
|
|
21
|
+
"us-central1",
|
|
22
|
+
"us-east1",
|
|
23
|
+
"us-east4",
|
|
24
|
+
"us-east5",
|
|
25
|
+
"us-south1",
|
|
26
|
+
"us-west1",
|
|
27
|
+
"us-west2",
|
|
28
|
+
"us-west3",
|
|
29
|
+
"us-west4",
|
|
30
|
+
"northamerica-northeast1",
|
|
31
|
+
"northamerica-northeast2",
|
|
32
|
+
"southamerica-east1",
|
|
33
|
+
"southamerica-west1",
|
|
34
|
+
"europe-central2",
|
|
35
|
+
"europe-north1",
|
|
36
|
+
"europe-southwest1",
|
|
37
|
+
"europe-west1",
|
|
38
|
+
"europe-west2",
|
|
39
|
+
"europe-west3",
|
|
40
|
+
"europe-west4",
|
|
41
|
+
"europe-west6",
|
|
42
|
+
"europe-west8",
|
|
43
|
+
"europe-west9",
|
|
44
|
+
"europe-west10",
|
|
45
|
+
"europe-west12",
|
|
46
|
+
"asia-east1",
|
|
47
|
+
"asia-east2",
|
|
48
|
+
"asia-northeast1",
|
|
49
|
+
"asia-northeast2",
|
|
50
|
+
"asia-northeast3",
|
|
51
|
+
"asia-south1",
|
|
52
|
+
"asia-south2",
|
|
53
|
+
"asia-southeast1",
|
|
54
|
+
"asia-southeast2",
|
|
55
|
+
"australia-southeast1",
|
|
56
|
+
"australia-southeast2",
|
|
57
|
+
"me-central1",
|
|
58
|
+
"me-central2",
|
|
59
|
+
"me-west1",
|
|
60
|
+
"africa-south1"
|
|
61
|
+
];
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Wrapper to unify error handling on every sub-command
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
function runAction(fn) {
|
|
66
|
+
return async (...args) => {
|
|
67
|
+
try {
|
|
68
|
+
await fn(...args);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error(String(err));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Parse a positive integer string or throw a clear error. */
|
|
77
|
+
function parsePositiveInt(raw, label) {
|
|
78
|
+
const n = Number.parseInt(raw, 10);
|
|
79
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
|
|
80
|
+
throw new Error(`Invalid ${label} '${raw}': expected a positive integer.`);
|
|
81
|
+
}
|
|
82
|
+
return n;
|
|
83
|
+
}
|
|
84
|
+
/** Parse a port number (1–65535) or throw. */
|
|
85
|
+
function parsePort(raw, label) {
|
|
86
|
+
const n = Number.parseInt(raw, 10);
|
|
87
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1 || n > 65535) {
|
|
88
|
+
throw new Error(`Invalid ${label} '${raw}': expected an integer in the range 1–65535.`);
|
|
89
|
+
}
|
|
90
|
+
return n;
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Public registration hooks
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
export function registerDeployCommands(program) {
|
|
96
|
+
const deploy = program
|
|
97
|
+
.command("deploy")
|
|
98
|
+
.description("Manage deployments (docker, runpod, gcp, fly, railway, huggingface)");
|
|
99
|
+
registerInit(deploy);
|
|
100
|
+
registerList(deploy);
|
|
101
|
+
registerShow(deploy);
|
|
102
|
+
registerAdd(deploy);
|
|
103
|
+
registerEdit(deploy);
|
|
104
|
+
registerPlan(deploy);
|
|
105
|
+
registerApply(deploy);
|
|
106
|
+
registerStatus(deploy);
|
|
107
|
+
registerLogs(deploy);
|
|
108
|
+
registerDestroy(deploy);
|
|
109
|
+
registerWorkflows(deploy);
|
|
110
|
+
registerDatabase(deploy);
|
|
111
|
+
registerCollections(deploy);
|
|
112
|
+
registerUsers(deploy);
|
|
113
|
+
}
|
|
114
|
+
export function registerListGcpOptions(program) {
|
|
115
|
+
program
|
|
116
|
+
.command("list-gcp-options")
|
|
117
|
+
.description("List supported GCP Cloud Run regions and RunPod GPU/CPU/data-center options")
|
|
118
|
+
.option("--json", "Output as JSON")
|
|
119
|
+
.action(runAction(async (opts) => {
|
|
120
|
+
const data = {
|
|
121
|
+
gcp_regions: GCP_REGIONS,
|
|
122
|
+
runpod_gpu_types: Object.values(GPUType),
|
|
123
|
+
runpod_cpu_flavors: Object.values(CPUFlavor),
|
|
124
|
+
runpod_data_centers: Object.values(DataCenter),
|
|
125
|
+
runpod_compute_types: Object.values(ComputeType)
|
|
126
|
+
};
|
|
127
|
+
if (opts.json) {
|
|
128
|
+
asJson(data);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
console.log("\nGCP Cloud Run regions:");
|
|
132
|
+
printTable(data.gcp_regions.map((r) => ({ region: r })));
|
|
133
|
+
console.log("\nRunPod GPU types:");
|
|
134
|
+
printTable(data.runpod_gpu_types.map((g) => ({ gpu_type: g })));
|
|
135
|
+
console.log("\nRunPod CPU flavors:");
|
|
136
|
+
printTable(data.runpod_cpu_flavors.map((c) => ({ cpu_flavor: c })));
|
|
137
|
+
console.log("\nRunPod compute types:");
|
|
138
|
+
printTable(data.runpod_compute_types.map((c) => ({ compute_type: c })));
|
|
139
|
+
console.log("\nRunPod data centers:");
|
|
140
|
+
printTable(data.runpod_data_centers.map((d) => ({ data_center: d })));
|
|
141
|
+
console.log();
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// init / list / show / add / edit
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
function registerInit(deploy) {
|
|
148
|
+
deploy
|
|
149
|
+
.command("init")
|
|
150
|
+
.description("Initialize deployment.yaml with defaults")
|
|
151
|
+
.action(runAction(async () => {
|
|
152
|
+
const configPath = getDeploymentConfigPath();
|
|
153
|
+
if (fs.existsSync(configPath)) {
|
|
154
|
+
const ok = await confirm(`${configPath} already exists. Overwrite?`);
|
|
155
|
+
if (!ok)
|
|
156
|
+
return;
|
|
157
|
+
await fsp.unlink(configPath);
|
|
158
|
+
}
|
|
159
|
+
await initDeploymentConfig();
|
|
160
|
+
console.log(`Created ${configPath}`);
|
|
161
|
+
console.log("\nNext steps:");
|
|
162
|
+
console.log(" nodetool deploy add <name> --type docker|runpod|gcp|fly|railway|huggingface");
|
|
163
|
+
console.log(" nodetool deploy plan <name>");
|
|
164
|
+
console.log(" nodetool deploy apply <name>");
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
function registerList(deploy) {
|
|
168
|
+
deploy
|
|
169
|
+
.command("list")
|
|
170
|
+
.description("List configured deployments with status")
|
|
171
|
+
.option("--json", "Output as JSON")
|
|
172
|
+
.action(runAction(async (opts) => {
|
|
173
|
+
const mgr = await getManager();
|
|
174
|
+
const rows = await mgr.listDeployments();
|
|
175
|
+
if (opts.json) {
|
|
176
|
+
asJson(rows);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (rows.length === 0) {
|
|
180
|
+
console.log("(no deployments configured)");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
printTable(rows, [
|
|
184
|
+
"name",
|
|
185
|
+
"type",
|
|
186
|
+
"status",
|
|
187
|
+
"last_deployed",
|
|
188
|
+
"host",
|
|
189
|
+
"container",
|
|
190
|
+
"pod_id",
|
|
191
|
+
"project",
|
|
192
|
+
"region",
|
|
193
|
+
"service"
|
|
194
|
+
]);
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
function registerShow(deploy) {
|
|
198
|
+
deploy
|
|
199
|
+
.command("show <name>")
|
|
200
|
+
.description("Print deployment config as YAML")
|
|
201
|
+
.action(runAction(async (name) => {
|
|
202
|
+
const config = await loadConfigOrExit();
|
|
203
|
+
const dep = getDeploymentOrExit(config, name);
|
|
204
|
+
process.stdout.write(yaml.dump({ [name]: dep }, { sortKeys: false, noCompatMode: true }));
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
function registerAdd(deploy) {
|
|
208
|
+
deploy
|
|
209
|
+
.command("add <name>")
|
|
210
|
+
.description("Add a deployment via interactive prompts")
|
|
211
|
+
.requiredOption("--type <type>", `Deployment type (${SUPPORTED_TYPES.join("|")})`)
|
|
212
|
+
.action(runAction(async (name, opts) => {
|
|
213
|
+
const type = opts.type;
|
|
214
|
+
if (!SUPPORTED_TYPES.includes(type)) {
|
|
215
|
+
throw new Error(`Invalid --type '${opts.type}'. Must be one of: ${SUPPORTED_TYPES.join(", ")}`);
|
|
216
|
+
}
|
|
217
|
+
let config;
|
|
218
|
+
try {
|
|
219
|
+
config = await loadDeploymentConfig();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
const shouldInit = await confirm("No deployment.yaml found. Create one now?");
|
|
223
|
+
if (!shouldInit) {
|
|
224
|
+
console.error("Run 'nodetool deploy init' first to create deployment.yaml.");
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
config = await initDeploymentConfig();
|
|
228
|
+
}
|
|
229
|
+
if (config.deployments[name]) {
|
|
230
|
+
throw new Error(`Deployment '${name}' already exists`);
|
|
231
|
+
}
|
|
232
|
+
let deployment;
|
|
233
|
+
switch (type) {
|
|
234
|
+
case "docker": {
|
|
235
|
+
const host = await promptLine("Docker host (IP/hostname or localhost)", {
|
|
236
|
+
default: "localhost"
|
|
237
|
+
});
|
|
238
|
+
const isLocal = host === "localhost" ||
|
|
239
|
+
host === "127.0.0.1" ||
|
|
240
|
+
host === "::1" ||
|
|
241
|
+
host === "0.0.0.0";
|
|
242
|
+
const sshUser = isLocal
|
|
243
|
+
? undefined
|
|
244
|
+
: await promptLine("SSH user", { default: "root" });
|
|
245
|
+
const sshKeyPath = isLocal
|
|
246
|
+
? undefined
|
|
247
|
+
: await promptLine("SSH key path", { default: "~/.ssh/id_rsa" });
|
|
248
|
+
const imageName = await promptLine("Docker image name", {
|
|
249
|
+
default: "ghcr.io/nodetool-ai/nodetool"
|
|
250
|
+
});
|
|
251
|
+
const imageTag = await promptLine("Image tag", {
|
|
252
|
+
default: "latest"
|
|
253
|
+
});
|
|
254
|
+
const containerName = await promptLine("Container name", {
|
|
255
|
+
default: `nodetool-${name}`
|
|
256
|
+
});
|
|
257
|
+
const containerPortRaw = await promptLine("Container port", {
|
|
258
|
+
default: "8000"
|
|
259
|
+
});
|
|
260
|
+
const containerPort = parsePort(containerPortRaw, "Container port");
|
|
261
|
+
deployment = configureDocker(name, {
|
|
262
|
+
host,
|
|
263
|
+
sshUser,
|
|
264
|
+
sshKeyPath,
|
|
265
|
+
imageName,
|
|
266
|
+
imageTag,
|
|
267
|
+
containerName,
|
|
268
|
+
containerPort
|
|
269
|
+
});
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case "runpod": {
|
|
273
|
+
const imageName = await promptLine("RunPod Docker image name (e.g. user/nodetool)");
|
|
274
|
+
const imageTag = await promptLine("Image tag", {
|
|
275
|
+
default: "latest"
|
|
276
|
+
});
|
|
277
|
+
const registry = await promptLine("Registry", {
|
|
278
|
+
default: "docker.io"
|
|
279
|
+
});
|
|
280
|
+
deployment = configureRunPod(name, { imageName, imageTag, registry });
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "gcp": {
|
|
284
|
+
const projectId = await promptLine("GCP project ID");
|
|
285
|
+
const region = await promptLine("Region", {
|
|
286
|
+
default: "us-central1"
|
|
287
|
+
});
|
|
288
|
+
const serviceName = await promptLine("Cloud Run service name", {
|
|
289
|
+
default: name
|
|
290
|
+
});
|
|
291
|
+
const imageRepository = await promptLine("Image repository (e.g. project/repo/image)");
|
|
292
|
+
const imageTag = await promptLine("Image tag", {
|
|
293
|
+
default: "latest"
|
|
294
|
+
});
|
|
295
|
+
deployment = configureGCP(name, {
|
|
296
|
+
projectId,
|
|
297
|
+
region,
|
|
298
|
+
serviceName,
|
|
299
|
+
imageRepository,
|
|
300
|
+
imageTag
|
|
301
|
+
});
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case "fly":
|
|
305
|
+
case "railway":
|
|
306
|
+
case "huggingface":
|
|
307
|
+
deployment = buildStubDeployment(type, name);
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
config.deployments[name] = deployment;
|
|
311
|
+
await saveDeploymentConfig(config);
|
|
312
|
+
console.log(`Added '${name}' to ${getDeploymentConfigPath()}`);
|
|
313
|
+
if (type === "fly" || type === "railway" || type === "huggingface") {
|
|
314
|
+
console.log(`Stub deployment written — run 'nodetool deploy edit ${name}' to finish configuring.`);
|
|
315
|
+
}
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
function registerEdit(deploy) {
|
|
319
|
+
deploy
|
|
320
|
+
.command("edit")
|
|
321
|
+
.description("Open deployment.yaml in $EDITOR")
|
|
322
|
+
.action(runAction(async () => {
|
|
323
|
+
const configPath = getDeploymentConfigPath();
|
|
324
|
+
if (!fs.existsSync(configPath)) {
|
|
325
|
+
throw new Error(`No deployment.yaml at ${configPath}. Run 'nodetool deploy init' first.`);
|
|
326
|
+
}
|
|
327
|
+
const status = runEditor(configPath);
|
|
328
|
+
if (status !== 0)
|
|
329
|
+
process.exit(status);
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
// lifecycle: plan / apply / status / logs / destroy
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
function registerPlan(deploy) {
|
|
336
|
+
deploy
|
|
337
|
+
.command("plan <name>")
|
|
338
|
+
.description("Show what 'apply' would do")
|
|
339
|
+
.action(runAction(async (name) => {
|
|
340
|
+
const mgr = await getManager();
|
|
341
|
+
const plan = await mgr.plan(name);
|
|
342
|
+
asJson(plan);
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
function registerApply(deploy) {
|
|
346
|
+
deploy
|
|
347
|
+
.command("apply <name>")
|
|
348
|
+
.description("Deploy to its target platform")
|
|
349
|
+
.option("--dry-run", "Show what would be done without executing")
|
|
350
|
+
.action(runAction(async (name, opts) => {
|
|
351
|
+
const mgr = await getManager();
|
|
352
|
+
const result = await mgr.apply(name, { dryRun: Boolean(opts.dryRun) });
|
|
353
|
+
asJson(result);
|
|
354
|
+
if (result["status"] === "error")
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
function registerStatus(deploy) {
|
|
359
|
+
deploy
|
|
360
|
+
.command("status <name>")
|
|
361
|
+
.description("Show the current status of a deployment")
|
|
362
|
+
.action(runAction(async (name) => {
|
|
363
|
+
const mgr = await getManager();
|
|
364
|
+
const status = await mgr.status(name);
|
|
365
|
+
asJson(status);
|
|
366
|
+
}));
|
|
367
|
+
}
|
|
368
|
+
function registerLogs(deploy) {
|
|
369
|
+
deploy
|
|
370
|
+
.command("logs <name>")
|
|
371
|
+
.description("Fetch deployment logs (streaming for --follow depends on platform support)")
|
|
372
|
+
.option("--service <service>", "Service/container name")
|
|
373
|
+
.option("-f, --follow", "Follow logs")
|
|
374
|
+
.option("--tail <n>", "Number of lines to tail", "100")
|
|
375
|
+
.action(runAction(async (name, opts) => {
|
|
376
|
+
const tail = parsePositiveInt(opts.tail, "--tail");
|
|
377
|
+
const mgr = await getManager();
|
|
378
|
+
const text = await mgr.logs(name, {
|
|
379
|
+
service: opts.service,
|
|
380
|
+
follow: Boolean(opts.follow),
|
|
381
|
+
tail
|
|
382
|
+
});
|
|
383
|
+
process.stdout.write(text);
|
|
384
|
+
if (!text.endsWith("\n"))
|
|
385
|
+
process.stdout.write("\n");
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
388
|
+
function registerDestroy(deploy) {
|
|
389
|
+
deploy
|
|
390
|
+
.command("destroy <name>")
|
|
391
|
+
.description("Tear down a deployment")
|
|
392
|
+
.option("--force", "Skip confirmation")
|
|
393
|
+
.action(runAction(async (name, opts) => {
|
|
394
|
+
const ok = await confirm(`Destroy '${name}'? This action is irreversible.`, { force: Boolean(opts.force) });
|
|
395
|
+
if (!ok)
|
|
396
|
+
return;
|
|
397
|
+
const mgr = await getManager();
|
|
398
|
+
const result = await mgr.destroy(name, {
|
|
399
|
+
force: Boolean(opts.force)
|
|
400
|
+
});
|
|
401
|
+
asJson(result);
|
|
402
|
+
}));
|
|
403
|
+
}
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
// workflows subgroup
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
function registerWorkflows(deploy) {
|
|
408
|
+
const wf = deploy
|
|
409
|
+
.command("workflows")
|
|
410
|
+
.description("Manage workflows on a remote deployment");
|
|
411
|
+
wf.command("sync <deployment> <workflow_id>")
|
|
412
|
+
.description("Sync a local workflow (and its assets/models) to the deployment")
|
|
413
|
+
.option("--token <token>", "Admin bearer token")
|
|
414
|
+
.action(runAction(async (deploymentName, workflowId, opts) => {
|
|
415
|
+
const config = await loadConfigOrExit();
|
|
416
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
417
|
+
const client = await getAdminClient(dep, deploymentName, opts);
|
|
418
|
+
const syncer = new WorkflowSyncer(client, makeSyncerDeps());
|
|
419
|
+
const ok = await syncer.syncWorkflow(workflowId);
|
|
420
|
+
if (!ok)
|
|
421
|
+
process.exit(1);
|
|
422
|
+
console.log(`Synced '${workflowId}' to '${deploymentName}'`);
|
|
423
|
+
}));
|
|
424
|
+
wf.command("list <deployment>")
|
|
425
|
+
.description("List workflows on a deployment")
|
|
426
|
+
.option("--token <token>", "Admin bearer token")
|
|
427
|
+
.option("--json", "Output as JSON")
|
|
428
|
+
.action(runAction(async (deploymentName, opts) => {
|
|
429
|
+
const config = await loadConfigOrExit();
|
|
430
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
431
|
+
const client = await getAdminClient(dep, deploymentName, opts);
|
|
432
|
+
const result = await client.listWorkflows();
|
|
433
|
+
const rows = (result["workflows"] ?? []);
|
|
434
|
+
if (opts.json) {
|
|
435
|
+
asJson(rows);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
printTable(rows.map((r) => ({
|
|
439
|
+
id: r["id"],
|
|
440
|
+
name: r["name"],
|
|
441
|
+
updated_at: r["updated_at"]
|
|
442
|
+
})));
|
|
443
|
+
}));
|
|
444
|
+
wf.command("delete <deployment> <workflow_id>")
|
|
445
|
+
.description("Delete a workflow on the deployment")
|
|
446
|
+
.option("--force", "Skip confirmation")
|
|
447
|
+
.option("--token <token>", "Admin bearer token")
|
|
448
|
+
.action(runAction(async (deploymentName, workflowId, opts) => {
|
|
449
|
+
const ok = await confirm(`Delete workflow '${workflowId}' on '${deploymentName}'?`, { force: Boolean(opts.force) });
|
|
450
|
+
if (!ok)
|
|
451
|
+
return;
|
|
452
|
+
const config = await loadConfigOrExit();
|
|
453
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
454
|
+
const client = await getAdminClient(dep, deploymentName, opts);
|
|
455
|
+
await client.deleteWorkflow(workflowId);
|
|
456
|
+
console.log(`deleted '${workflowId}'`);
|
|
457
|
+
}));
|
|
458
|
+
wf.command("run <deployment> <workflow_id>")
|
|
459
|
+
.description("Run a workflow on the deployment")
|
|
460
|
+
.option("-p, --param <k=v>", "Parameter k=v (repeatable)", (val, acc = []) => {
|
|
461
|
+
acc.push(val);
|
|
462
|
+
return acc;
|
|
463
|
+
}, [])
|
|
464
|
+
.option("--token <token>", "Admin bearer token")
|
|
465
|
+
.action(runAction(async (deploymentName, workflowId, opts) => {
|
|
466
|
+
const params = parseParamPairs(opts.param ?? []);
|
|
467
|
+
const config = await loadConfigOrExit();
|
|
468
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
469
|
+
const client = await getAdminClient(dep, deploymentName, opts);
|
|
470
|
+
const result = await client.runWorkflow(workflowId, params);
|
|
471
|
+
asJson(result);
|
|
472
|
+
}));
|
|
473
|
+
}
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
// database subgroup
|
|
476
|
+
// ---------------------------------------------------------------------------
|
|
477
|
+
function registerDatabase(deploy) {
|
|
478
|
+
const db = deploy.command("database").description("Remote DB row management");
|
|
479
|
+
db.command("get <deployment> <table> <key>")
|
|
480
|
+
.description("Read a DB row")
|
|
481
|
+
.option("--token <token>", "Admin bearer token")
|
|
482
|
+
.action(runAction(async (deploymentName, table, key, opts) => {
|
|
483
|
+
const config = await loadConfigOrExit();
|
|
484
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
485
|
+
const client = await getAdminClient(dep, deploymentName, opts);
|
|
486
|
+
const row = await client.dbGet(table, key);
|
|
487
|
+
asJson(row);
|
|
488
|
+
}));
|
|
489
|
+
db.command("save <deployment> <table> <json_data>")
|
|
490
|
+
.description("Upsert a DB row (positional JSON string)")
|
|
491
|
+
.option("--token <token>", "Admin bearer token")
|
|
492
|
+
.action(runAction(async (deploymentName, table, jsonData, opts) => {
|
|
493
|
+
let parsed;
|
|
494
|
+
try {
|
|
495
|
+
parsed = JSON.parse(jsonData);
|
|
496
|
+
}
|
|
497
|
+
catch (e) {
|
|
498
|
+
throw new Error(`Invalid JSON for <json_data>: ${String(e)}`);
|
|
499
|
+
}
|
|
500
|
+
const config = await loadConfigOrExit();
|
|
501
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
502
|
+
const client = await getAdminClient(dep, deploymentName, opts);
|
|
503
|
+
const row = await client.dbSave(table, parsed);
|
|
504
|
+
asJson(row);
|
|
505
|
+
}));
|
|
506
|
+
db.command("delete <deployment> <table> <key>")
|
|
507
|
+
.description("Delete a DB row")
|
|
508
|
+
.option("--force", "Skip confirmation")
|
|
509
|
+
.option("--token <token>", "Admin bearer token")
|
|
510
|
+
.action(runAction(async (deploymentName, table, key, opts) => {
|
|
511
|
+
const ok = await confirm(`Delete '${table}/${key}' on '${deploymentName}'?`, { force: Boolean(opts.force) });
|
|
512
|
+
if (!ok)
|
|
513
|
+
return;
|
|
514
|
+
const config = await loadConfigOrExit();
|
|
515
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
516
|
+
const client = await getAdminClient(dep, deploymentName, opts);
|
|
517
|
+
await client.dbDelete(table, key);
|
|
518
|
+
console.log("deleted");
|
|
519
|
+
}));
|
|
520
|
+
}
|
|
521
|
+
// ---------------------------------------------------------------------------
|
|
522
|
+
// collections sync
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
function registerCollections(deploy) {
|
|
525
|
+
const coll = deploy
|
|
526
|
+
.command("collections")
|
|
527
|
+
.description("Sync local vector collections to a deployment");
|
|
528
|
+
coll
|
|
529
|
+
.command("sync <deployment> <collection>")
|
|
530
|
+
.description("Sync a local collection to the deployment")
|
|
531
|
+
.option("--token <token>", "Admin bearer token")
|
|
532
|
+
.option("--batch-size <n>", "Batch size for uploads", "100")
|
|
533
|
+
.action(runAction(async (deploymentName, collectionName, opts) => {
|
|
534
|
+
const batchSize = parsePositiveInt(opts.batchSize, "--batch-size");
|
|
535
|
+
const collection = await getCollection(collectionName);
|
|
536
|
+
if (!collection) {
|
|
537
|
+
throw new Error(`Local collection not found: ${collectionName}`);
|
|
538
|
+
}
|
|
539
|
+
const metadata = collection
|
|
540
|
+
.metadata ?? {};
|
|
541
|
+
const embeddingModel = metadata["embedding_model"] ?? "";
|
|
542
|
+
if (!embeddingModel) {
|
|
543
|
+
throw new Error(`Local collection '${collectionName}' has no embedding_model in metadata — cannot sync without embeddings.`);
|
|
544
|
+
}
|
|
545
|
+
// Inspect the local collection BEFORE creating anything on the
|
|
546
|
+
// remote, so partial failures don't leave an empty/orphan collection
|
|
547
|
+
// on the deployment.
|
|
548
|
+
const withGet = collection;
|
|
549
|
+
const page = await withGet.get({ limit: batchSize, offset: 0 });
|
|
550
|
+
if (page.ids.length > 0) {
|
|
551
|
+
throw new Error("deploy collections sync: re-embedding of local documents during sync is not yet implemented in the Node CLI. " +
|
|
552
|
+
`Found ${page.ids.length} document(s) locally but cannot upload without embeddings. ` +
|
|
553
|
+
"No remote collection was created.");
|
|
554
|
+
}
|
|
555
|
+
// Empty collection — safe to create on the remote as a no-op sync.
|
|
556
|
+
const config = await loadConfigOrExit();
|
|
557
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
558
|
+
const client = await getAdminClient(dep, deploymentName, opts);
|
|
559
|
+
await client.createCollection(collectionName, embeddingModel);
|
|
560
|
+
console.log(`Collection '${collectionName}' is empty — created an empty collection on '${deploymentName}'.`);
|
|
561
|
+
}));
|
|
562
|
+
}
|
|
563
|
+
// ---------------------------------------------------------------------------
|
|
564
|
+
// users-* commands
|
|
565
|
+
// ---------------------------------------------------------------------------
|
|
566
|
+
function registerUsers(deploy) {
|
|
567
|
+
deploy
|
|
568
|
+
.command("users-add <deployment> <username>")
|
|
569
|
+
.description("Add an API user to the deployment")
|
|
570
|
+
.option("--role <role>", "User role (admin|user)", "user")
|
|
571
|
+
.option("--token <token>", "Admin bearer token")
|
|
572
|
+
.action(runAction(async (deploymentName, username, opts) => {
|
|
573
|
+
if (opts.role !== "admin" && opts.role !== "user") {
|
|
574
|
+
throw new Error(`Invalid --role '${opts.role}'. Must be 'admin' or 'user'.`);
|
|
575
|
+
}
|
|
576
|
+
const config = await loadConfigOrExit();
|
|
577
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
578
|
+
const mgr = await getUserManager(dep, deploymentName, opts);
|
|
579
|
+
const result = await mgr.addUser(username, opts.role);
|
|
580
|
+
console.log(`Created user '${result.username}' (role: ${result.role}, id: ${result.user_id})`);
|
|
581
|
+
if (result.token) {
|
|
582
|
+
console.log("");
|
|
583
|
+
console.log(` Token: ${result.token}`);
|
|
584
|
+
console.log("");
|
|
585
|
+
console.log(" WARNING: save this token now — it will not be shown again.");
|
|
586
|
+
}
|
|
587
|
+
}));
|
|
588
|
+
deploy
|
|
589
|
+
.command("users-list <deployment>")
|
|
590
|
+
.description("List API users on the deployment")
|
|
591
|
+
.option("--token <token>", "Admin bearer token")
|
|
592
|
+
.option("--json", "Output as JSON")
|
|
593
|
+
.action(runAction(async (deploymentName, opts) => {
|
|
594
|
+
const config = await loadConfigOrExit();
|
|
595
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
596
|
+
const mgr = await getUserManager(dep, deploymentName, opts);
|
|
597
|
+
const users = await mgr.listUsers();
|
|
598
|
+
if (opts.json) {
|
|
599
|
+
asJson(users);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
printTable(users.map((u) => ({
|
|
603
|
+
username: u.username,
|
|
604
|
+
user_id: u.user_id,
|
|
605
|
+
role: u.role,
|
|
606
|
+
token_hash_preview: u.token_hash
|
|
607
|
+
? `${u.token_hash.slice(0, 16)}...`
|
|
608
|
+
: "",
|
|
609
|
+
created_at: (u.created_at ?? "").slice(0, 19)
|
|
610
|
+
})), ["username", "user_id", "role", "token_hash_preview", "created_at"]);
|
|
611
|
+
}));
|
|
612
|
+
deploy
|
|
613
|
+
.command("users-remove <deployment> <username>")
|
|
614
|
+
.description("Remove an API user")
|
|
615
|
+
.option("--force", "Skip confirmation")
|
|
616
|
+
.option("--token <token>", "Admin bearer token")
|
|
617
|
+
.action(runAction(async (deploymentName, username, opts) => {
|
|
618
|
+
const ok = await confirm(`Remove user '${username}' from '${deploymentName}'?`, { force: Boolean(opts.force) });
|
|
619
|
+
if (!ok)
|
|
620
|
+
return;
|
|
621
|
+
const config = await loadConfigOrExit();
|
|
622
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
623
|
+
const mgr = await getUserManager(dep, deploymentName, opts);
|
|
624
|
+
await mgr.removeUser(username);
|
|
625
|
+
console.log(`removed '${username}'`);
|
|
626
|
+
}));
|
|
627
|
+
deploy
|
|
628
|
+
.command("users-reset-token <deployment> <username>")
|
|
629
|
+
.description("Rotate a user's API token")
|
|
630
|
+
.option("--token <token>", "Admin bearer token")
|
|
631
|
+
.action(runAction(async (deploymentName, username, opts) => {
|
|
632
|
+
const config = await loadConfigOrExit();
|
|
633
|
+
const dep = getDeploymentOrExit(config, deploymentName);
|
|
634
|
+
const mgr = await getUserManager(dep, deploymentName, opts);
|
|
635
|
+
const result = await mgr.resetToken(username);
|
|
636
|
+
console.log(`Rotated token for '${result.username}'`);
|
|
637
|
+
if (result.token) {
|
|
638
|
+
console.log("");
|
|
639
|
+
console.log(` New token: ${result.token}`);
|
|
640
|
+
console.log("");
|
|
641
|
+
console.log(" The previous token is now invalid. Save this token — it will not be shown again.");
|
|
642
|
+
}
|
|
643
|
+
}));
|
|
644
|
+
}
|
|
645
|
+
//# sourceMappingURL=deploy.js.map
|