@vellumai/cli 0.8.4 → 0.8.6
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/AGENTS.md +17 -1
- package/knip.json +2 -1
- package/package.json +1 -1
- package/src/__tests__/api-key-check.test.ts +78 -0
- package/src/__tests__/backup.test.ts +38 -0
- package/src/__tests__/recover.test.ts +307 -0
- package/src/__tests__/retire.test.ts +241 -0
- package/src/__tests__/wake.test.ts +215 -0
- package/src/commands/backup.ts +2 -0
- package/src/commands/client.ts +62 -32
- package/src/commands/flags.ts +197 -0
- package/src/commands/gateway/token.ts +73 -0
- package/src/commands/gateway.ts +29 -0
- package/src/commands/logs.ts +6 -18
- package/src/commands/ps.ts +41 -41
- package/src/commands/recover.ts +47 -9
- package/src/commands/restore.ts +8 -1
- package/src/commands/retire.ts +145 -55
- package/src/commands/roadmap.ts +449 -0
- package/src/commands/rollback.ts +2 -14
- package/src/commands/ssh.ts +5 -24
- package/src/commands/teleport.ts +34 -26
- package/src/commands/upgrade.ts +8 -16
- package/src/commands/wake.ts +68 -45
- package/src/index.ts +9 -0
- package/src/lib/__tests__/port-allocator.test.ts +117 -0
- package/src/lib/__tests__/step-runner.test.ts +133 -0
- package/src/lib/api-key-check.ts +40 -0
- package/src/lib/assistant-config.ts +13 -0
- package/src/lib/config-utils.ts +24 -3
- package/src/lib/docker.ts +72 -8
- package/src/lib/hatch-local.ts +15 -2
- package/src/lib/http-client.ts +1 -3
- package/src/lib/local.ts +173 -292
- package/src/lib/orphan-detection.ts +9 -5
- package/src/lib/pgrep.ts +5 -1
- package/src/lib/platform-client.ts +97 -49
- package/src/lib/port-allocator.ts +93 -0
- package/src/lib/process.ts +109 -39
- package/src/lib/statefulset.ts +0 -10
- package/src/lib/step-runner.ts +102 -9
- package/src/lib/sync-cloud-assistants.ts +17 -0
- package/src/shared/provider-env-vars.ts +1 -0
package/src/commands/retire.ts
CHANGED
|
@@ -2,55 +2,45 @@ import { existsSync, unlinkSync } from "fs";
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
extractHostFromUrl,
|
|
6
|
+
formatAssistantLookupError,
|
|
7
|
+
formatAssistantReference,
|
|
8
|
+
getAssistantDisplayName,
|
|
6
9
|
loadAllAssistants,
|
|
10
|
+
lookupAssistantByIdentifier,
|
|
7
11
|
removeAssistantEntry,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
+
resolveCloud,
|
|
13
|
+
type AssistantEntry,
|
|
14
|
+
} from "../lib/assistant-config.js";
|
|
15
|
+
import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
|
|
16
|
+
import { getConfigDir } from "../lib/environments/paths.js";
|
|
17
|
+
import { getCurrentEnvironment } from "../lib/environments/resolve.js";
|
|
12
18
|
import {
|
|
13
19
|
authHeaders,
|
|
14
20
|
getPlatformUrl,
|
|
15
21
|
readPlatformToken,
|
|
16
|
-
} from "../lib/platform-client";
|
|
17
|
-
import { retireInstance as retireAwsInstance } from "../lib/aws";
|
|
18
|
-
import { retireDocker } from "../lib/docker";
|
|
19
|
-
import { retireInstance as retireGcpInstance } from "../lib/gcp";
|
|
20
|
-
import { retireLocal } from "../lib/retire-local";
|
|
21
|
-
import { retireAppleContainer } from "../lib/retire-apple-container";
|
|
22
|
-
import { exec } from "../lib/step-runner";
|
|
22
|
+
} from "../lib/platform-client.js";
|
|
23
|
+
import { retireInstance as retireAwsInstance } from "../lib/aws.js";
|
|
24
|
+
import { retireDocker } from "../lib/docker.js";
|
|
25
|
+
import { retireInstance as retireGcpInstance } from "../lib/gcp.js";
|
|
26
|
+
import { retireLocal } from "../lib/retire-local.js";
|
|
27
|
+
import { retireAppleContainer } from "../lib/retire-apple-container.js";
|
|
28
|
+
import { exec } from "../lib/step-runner.js";
|
|
23
29
|
import {
|
|
24
30
|
openLogFile,
|
|
25
31
|
closeLogFile,
|
|
26
32
|
resetLogFile,
|
|
27
33
|
writeToLogFile,
|
|
28
|
-
} from "../lib/xdg-log";
|
|
34
|
+
} from "../lib/xdg-log.js";
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
if (entry.cloud) {
|
|
32
|
-
return entry.cloud;
|
|
33
|
-
}
|
|
34
|
-
if (entry.project) {
|
|
35
|
-
return "gcp";
|
|
36
|
-
}
|
|
37
|
-
if (entry.sshUser) {
|
|
38
|
-
return "custom";
|
|
39
|
-
}
|
|
40
|
-
return "local";
|
|
41
|
-
}
|
|
36
|
+
export { retireLocal };
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} catch {
|
|
48
|
-
return url.replace(/^https?:\/\//, "").split(":")[0];
|
|
49
|
-
}
|
|
38
|
+
interface RetireArgs {
|
|
39
|
+
name?: string;
|
|
40
|
+
source?: string;
|
|
41
|
+
yes: boolean;
|
|
50
42
|
}
|
|
51
43
|
|
|
52
|
-
export { retireLocal };
|
|
53
|
-
|
|
54
44
|
async function retireCustom(entry: AssistantEntry): Promise<void> {
|
|
55
45
|
const host = extractHostFromUrl(entry.runtimeUrl);
|
|
56
46
|
const sshUser = entry.sshUser ?? "root";
|
|
@@ -129,14 +119,82 @@ async function retireVellum(
|
|
|
129
119
|
}
|
|
130
120
|
}
|
|
131
121
|
|
|
132
|
-
function
|
|
133
|
-
|
|
122
|
+
function parseRetireArgs(args: string[]): RetireArgs {
|
|
123
|
+
let source: string | undefined;
|
|
134
124
|
for (let i = 0; i < args.length; i++) {
|
|
135
125
|
if (args[i] === "--source" && args[i + 1]) {
|
|
136
|
-
|
|
126
|
+
source = args[i + 1];
|
|
127
|
+
i++;
|
|
137
128
|
}
|
|
138
129
|
}
|
|
139
|
-
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
name: parseAssistantTargetArg(args, ["--source"]),
|
|
133
|
+
source,
|
|
134
|
+
yes: args.includes("--yes"),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatRuntimeUrl(entry: AssistantEntry): string {
|
|
139
|
+
return entry.localUrl ?? entry.runtimeUrl;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function printRetireTarget(entry: AssistantEntry, cloud: string): void {
|
|
143
|
+
const displayName = getAssistantDisplayName(entry);
|
|
144
|
+
|
|
145
|
+
console.log("Assistant to retire:");
|
|
146
|
+
if (displayName !== entry.assistantId) {
|
|
147
|
+
console.log(` Name: ${displayName}`);
|
|
148
|
+
}
|
|
149
|
+
console.log(` ID: ${entry.assistantId}`);
|
|
150
|
+
console.log(` Cloud: ${cloud}`);
|
|
151
|
+
console.log(` Runtime: ${formatRuntimeUrl(entry)}`);
|
|
152
|
+
console.log("");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function canPromptForRetireConfirmation(): boolean {
|
|
156
|
+
return (
|
|
157
|
+
process.stdin.isTTY === true &&
|
|
158
|
+
process.stdout.isTTY === true &&
|
|
159
|
+
typeof process.stdin.setRawMode === "function"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function confirmRetireInteractive(): Promise<boolean> {
|
|
164
|
+
const stdin = process.stdin;
|
|
165
|
+
const stdout = process.stdout;
|
|
166
|
+
const wasRaw = stdin.isRaw === true;
|
|
167
|
+
const wasPaused = stdin.isPaused();
|
|
168
|
+
|
|
169
|
+
stdout.write("Press Enter to retire, or Esc/q to cancel: ");
|
|
170
|
+
stdin.setRawMode(true);
|
|
171
|
+
stdin.resume();
|
|
172
|
+
|
|
173
|
+
return await new Promise<boolean>((resolve) => {
|
|
174
|
+
const cleanup = () => {
|
|
175
|
+
stdin.off("data", onData);
|
|
176
|
+
stdin.setRawMode(wasRaw);
|
|
177
|
+
if (wasPaused) {
|
|
178
|
+
stdin.pause();
|
|
179
|
+
}
|
|
180
|
+
stdout.write("\n");
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const onData = (chunk: Buffer) => {
|
|
184
|
+
const byte = chunk[0];
|
|
185
|
+
if (byte === 13 || byte === 10) {
|
|
186
|
+
cleanup();
|
|
187
|
+
resolve(true);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (byte === 27 || byte === 3 || byte === 113 || byte === 81) {
|
|
191
|
+
cleanup();
|
|
192
|
+
resolve(false);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
stdin.on("data", onData);
|
|
197
|
+
});
|
|
140
198
|
}
|
|
141
199
|
|
|
142
200
|
/** Patch console methods to also append output to the given log file descriptor. */
|
|
@@ -188,38 +246,70 @@ export async function retire(): Promise<void> {
|
|
|
188
246
|
async function retireInner(): Promise<void> {
|
|
189
247
|
const args = process.argv.slice(3);
|
|
190
248
|
if (args.includes("--help") || args.includes("-h")) {
|
|
191
|
-
console.log(
|
|
249
|
+
console.log(
|
|
250
|
+
"Usage: vellum retire <name-or-id> [--source <source>] [--yes]",
|
|
251
|
+
);
|
|
192
252
|
console.log("");
|
|
193
253
|
console.log("Delete an assistant instance and archive its data.");
|
|
254
|
+
console.log(
|
|
255
|
+
"By default, retire prints the assistant name, ID, cloud, and runtime before asking for confirmation.",
|
|
256
|
+
);
|
|
194
257
|
console.log("");
|
|
195
258
|
console.log("Arguments:");
|
|
196
|
-
console.log(
|
|
259
|
+
console.log(
|
|
260
|
+
" <name-or-id> Assistant display name or ID to retire",
|
|
261
|
+
);
|
|
197
262
|
console.log("");
|
|
198
263
|
console.log("Options:");
|
|
199
264
|
console.log(" --source <source> Source identifier for the retirement");
|
|
265
|
+
console.log(
|
|
266
|
+
" --yes Skip the interactive confirmation prompt",
|
|
267
|
+
);
|
|
200
268
|
process.exit(0);
|
|
201
269
|
}
|
|
202
270
|
|
|
203
|
-
const
|
|
271
|
+
const parsed = parseRetireArgs(args);
|
|
272
|
+
const name = parsed.name;
|
|
204
273
|
|
|
205
274
|
if (!name) {
|
|
206
|
-
console.error("Error:
|
|
207
|
-
console.error(
|
|
275
|
+
console.error("Error: Assistant name or ID is required.");
|
|
276
|
+
console.error(
|
|
277
|
+
"Usage: vellum retire <name-or-id> [--source <source>] [--yes]",
|
|
278
|
+
);
|
|
208
279
|
process.exit(1);
|
|
209
280
|
}
|
|
210
281
|
|
|
211
|
-
const
|
|
212
|
-
if (
|
|
213
|
-
console.error(
|
|
282
|
+
const lookup = lookupAssistantByIdentifier(name);
|
|
283
|
+
if (lookup.status !== "found") {
|
|
284
|
+
console.error(formatAssistantLookupError(name, lookup));
|
|
214
285
|
console.error("Run 'vellum hatch' first, or check the instance name.");
|
|
215
286
|
process.exit(1);
|
|
216
287
|
}
|
|
217
288
|
|
|
218
|
-
const
|
|
289
|
+
const entry = lookup.entry;
|
|
290
|
+
const assistantId = entry.assistantId;
|
|
291
|
+
const source = parsed.source;
|
|
219
292
|
const cloud = resolveCloud(entry);
|
|
293
|
+
printRetireTarget(entry, cloud);
|
|
294
|
+
|
|
295
|
+
if (!parsed.yes) {
|
|
296
|
+
if (!canPromptForRetireConfirmation()) {
|
|
297
|
+
console.error(
|
|
298
|
+
"Error: Refusing to retire without confirmation in a non-interactive terminal.",
|
|
299
|
+
);
|
|
300
|
+
console.error("Re-run with --yes to confirm from automation.");
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const confirmed = await confirmRetireInteractive();
|
|
305
|
+
if (!confirmed) {
|
|
306
|
+
console.log("Retire cancelled.");
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
220
310
|
|
|
221
311
|
if (cloud === "apple-container") {
|
|
222
|
-
await retireAppleContainer(
|
|
312
|
+
await retireAppleContainer(assistantId, entry);
|
|
223
313
|
} else if (cloud === "gcp") {
|
|
224
314
|
const project = entry.project;
|
|
225
315
|
const zone = entry.zone;
|
|
@@ -229,29 +319,29 @@ async function retireInner(): Promise<void> {
|
|
|
229
319
|
);
|
|
230
320
|
process.exit(1);
|
|
231
321
|
}
|
|
232
|
-
await retireGcpInstance(
|
|
322
|
+
await retireGcpInstance(assistantId, project, zone, source);
|
|
233
323
|
} else if (cloud === "aws") {
|
|
234
324
|
const region = entry.region;
|
|
235
325
|
if (!region) {
|
|
236
326
|
console.error("Error: AWS region not found in assistant config.");
|
|
237
327
|
process.exit(1);
|
|
238
328
|
}
|
|
239
|
-
await retireAwsInstance(
|
|
329
|
+
await retireAwsInstance(assistantId, region, source);
|
|
240
330
|
} else if (cloud === "docker") {
|
|
241
|
-
await retireDocker(
|
|
331
|
+
await retireDocker(assistantId);
|
|
242
332
|
} else if (cloud === "local") {
|
|
243
|
-
await retireLocal(
|
|
333
|
+
await retireLocal(assistantId, entry);
|
|
244
334
|
} else if (cloud === "custom") {
|
|
245
335
|
await retireCustom(entry);
|
|
246
336
|
} else if (cloud === "vellum") {
|
|
247
|
-
await retireVellum(
|
|
337
|
+
await retireVellum(assistantId, entry.runtimeUrl);
|
|
248
338
|
} else {
|
|
249
339
|
console.error(`Error: Unknown cloud type '${cloud}'.`);
|
|
250
340
|
process.exit(1);
|
|
251
341
|
}
|
|
252
342
|
|
|
253
|
-
removeAssistantEntry(
|
|
254
|
-
console.log(`Removed ${
|
|
343
|
+
removeAssistantEntry(assistantId);
|
|
344
|
+
console.log(`Removed ${formatAssistantReference(entry)} from config.`);
|
|
255
345
|
|
|
256
346
|
// When no assistants remain, remove the dock-display-name sentinel so
|
|
257
347
|
// the next build.sh run falls back to "Vellum" instead of using the
|