@vellumai/cli 0.8.4 → 0.8.5

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 CHANGED
@@ -16,7 +16,17 @@ Examples: `hatch`, `wake`, `sleep`, `retire`, `ps`, `ssh` belong here. `config`,
16
16
 
17
17
  ## Assistant targeting convention
18
18
 
19
- Commands that act on a specific assistant should accept an assistant name or ID as an argument. When none is specified, default to the most recently created local assistant. Use `loadAllAssistants()` and `findAssistantByName()` from `lib/assistant-config` for resolution.
19
+ New or modified commands that act on a specific assistant should accept an assistant display name or ID as an argument. Exact assistant ID matches must win over display-name matches. Unique display-name matches may resolve to the matching assistant ID, but ambiguous display names must fail with an error that lists the matching IDs.
20
+
21
+ Use the shared helpers from `lib/assistant-config` instead of hand-rolled lookup:
22
+
23
+ - `lookupAssistantByIdentifier()` for commands that require an explicit target and need custom error handling.
24
+ - `resolveTargetAssistant()` for commands that may fall back to the active assistant or sole lockfile entry.
25
+ - `formatAssistantReference()` for user-facing output that should include both display name and ID when they differ.
26
+
27
+ Use `parseAssistantTargetArg()` from `lib/assistant-target-args` when parsing command arguments that may contain an unquoted multi-word display name. Do not store raw display names in `activeAssistant`; persist the resolved `assistantId`.
28
+
29
+ New or modified destructive lifecycle commands must be explicit and safe. A command that deletes, retires, archives, or removes assistant state must print the resolved assistant identity before acting and require an interactive confirmation, with a documented `--yes` bypass only for automation or higher-level clients that already own confirmation. Do not expose destructive lifecycle actions as `vellum client` slash commands.
20
30
 
21
31
  ## Conventions
22
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,78 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import { checkProviderApiKey } from "../lib/api-key-check.js";
4
+
5
+ const PROVIDER_KEYS = [
6
+ "ANTHROPIC_API_KEY",
7
+ "OPENAI_API_KEY",
8
+ "GEMINI_API_KEY",
9
+ "FIREWORKS_API_KEY",
10
+ "OPENROUTER_API_KEY",
11
+ ] as const;
12
+
13
+ beforeEach(() => {
14
+ for (const key of PROVIDER_KEYS) {
15
+ delete process.env[key];
16
+ }
17
+ });
18
+
19
+ afterEach(() => {
20
+ for (const key of PROVIDER_KEYS) {
21
+ delete process.env[key];
22
+ }
23
+ });
24
+
25
+ describe("checkProviderApiKey", () => {
26
+ test("returns hasKey:false when no provider keys are in process.env", () => {
27
+ const result = checkProviderApiKey();
28
+ expect(result.hasKey).toBe(false);
29
+ });
30
+
31
+ test("returns hasKey:false when ANTHROPIC_API_KEY is a placeholder", () => {
32
+ process.env.ANTHROPIC_API_KEY = "sk-ant-...";
33
+ const result = checkProviderApiKey();
34
+ expect(result.hasKey).toBe(false);
35
+ });
36
+
37
+ test("returns hasKey:false when OPENAI_API_KEY is a placeholder", () => {
38
+ process.env.OPENAI_API_KEY = "sk-...";
39
+ const result = checkProviderApiKey();
40
+ expect(result.hasKey).toBe(false);
41
+ });
42
+
43
+ test("returns hasKey:false when key is empty", () => {
44
+ process.env.ANTHROPIC_API_KEY = "";
45
+ const result = checkProviderApiKey();
46
+ expect(result.hasKey).toBe(false);
47
+ });
48
+
49
+ test("returns hasKey:true when ANTHROPIC_API_KEY is a real key", () => {
50
+ process.env.ANTHROPIC_API_KEY = "sk-ant-api03-realkey123";
51
+ const result = checkProviderApiKey();
52
+ expect(result.hasKey).toBe(true);
53
+ });
54
+
55
+ test("returns hasKey:true when OPENAI_API_KEY is a real key", () => {
56
+ process.env.OPENAI_API_KEY = "sk-proj-realkey123";
57
+ const result = checkProviderApiKey();
58
+ expect(result.hasKey).toBe(true);
59
+ });
60
+
61
+ test("returns hasKey:true when GEMINI_API_KEY is a real key", () => {
62
+ process.env.GEMINI_API_KEY = "AIzaSyRealKey123";
63
+ const result = checkProviderApiKey();
64
+ expect(result.hasKey).toBe(true);
65
+ });
66
+
67
+ test("returns hasKey:true when FIREWORKS_API_KEY is a real key", () => {
68
+ process.env.FIREWORKS_API_KEY = "fw-realkey123";
69
+ const result = checkProviderApiKey();
70
+ expect(result.hasKey).toBe(true);
71
+ });
72
+
73
+ test("returns hasKey:true when OPENROUTER_API_KEY is a real key", () => {
74
+ process.env.OPENROUTER_API_KEY = "sk-or-realkey123";
75
+ const result = checkProviderApiKey();
76
+ expect(result.hasKey).toBe(true);
77
+ });
78
+ });
@@ -0,0 +1,241 @@
1
+ import {
2
+ afterAll,
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ mock,
8
+ spyOn,
9
+ test,
10
+ } from "bun:test";
11
+ import {
12
+ mkdirSync,
13
+ mkdtempSync,
14
+ readFileSync,
15
+ rmSync,
16
+ writeFileSync,
17
+ } from "node:fs";
18
+ import { tmpdir } from "node:os";
19
+ import { join } from "node:path";
20
+
21
+ import type { AssistantEntry } from "../lib/assistant-config.js";
22
+ import { loadAllAssistants } from "../lib/assistant-config.js";
23
+ import * as retireLocalModule from "../lib/retire-local.js";
24
+
25
+ const testDir = mkdtempSync(join(tmpdir(), "cli-retire-test-"));
26
+ const originalArgv = [...process.argv];
27
+ const originalExit = process.exit;
28
+ const originalLockfileDir = process.env.VELLUM_LOCKFILE_DIR;
29
+ const originalStdinIsTTY = process.stdin.isTTY;
30
+ const originalStdoutIsTTY = process.stdout.isTTY;
31
+ const originalStdinIsRaw = process.stdin.isRaw;
32
+ const originalSetRawMode = process.stdin.setRawMode;
33
+ const originalStdoutWrite = process.stdout.write;
34
+ const realRetireLocalModule = { ...retireLocalModule };
35
+
36
+ const retireLocalMock = mock(async () => {});
37
+
38
+ mock.module("../lib/retire-local.js", () => ({
39
+ ...realRetireLocalModule,
40
+ retireLocal: retireLocalMock,
41
+ }));
42
+
43
+ import { retire } from "../commands/retire.js";
44
+
45
+ let consoleLogSpy: ReturnType<typeof spyOn>;
46
+ let consoleErrorSpy: ReturnType<typeof spyOn>;
47
+
48
+ function makeEntry(
49
+ assistantId: string,
50
+ extra: Partial<AssistantEntry> = {},
51
+ ): AssistantEntry {
52
+ return {
53
+ assistantId,
54
+ runtimeUrl: `http://127.0.0.1:${7800 + assistantId.length}`,
55
+ cloud: "local",
56
+ resources: {
57
+ instanceDir: join(testDir, assistantId),
58
+ daemonPort: 7801,
59
+ gatewayPort: 7831,
60
+ qdrantPort: 6334,
61
+ cesPort: 7790,
62
+ },
63
+ ...extra,
64
+ };
65
+ }
66
+
67
+ function writeLockfile(entries: AssistantEntry[]): void {
68
+ mkdirSync(testDir, { recursive: true });
69
+ writeFileSync(
70
+ join(testDir, ".vellum.lock.json"),
71
+ JSON.stringify({ assistants: entries }, null, 2) + "\n",
72
+ );
73
+ }
74
+
75
+ function readLockfile(): string {
76
+ return readFileSync(join(testDir, ".vellum.lock.json"), "utf-8");
77
+ }
78
+
79
+ function setTerminalMode(isTTY: boolean): void {
80
+ Object.defineProperty(process.stdin, "isTTY", {
81
+ configurable: true,
82
+ value: isTTY,
83
+ });
84
+ Object.defineProperty(process.stdout, "isTTY", {
85
+ configurable: true,
86
+ value: isTTY,
87
+ });
88
+ }
89
+
90
+ function setInteractiveTerminal(): void {
91
+ setTerminalMode(true);
92
+ Object.defineProperty(process.stdin, "isRaw", {
93
+ configurable: true,
94
+ value: false,
95
+ });
96
+ Object.defineProperty(process.stdin, "setRawMode", {
97
+ configurable: true,
98
+ value: mock(() => process.stdin),
99
+ });
100
+ process.stdout.write = (() => true) as typeof process.stdout.write;
101
+ }
102
+
103
+ function restoreTerminal(): void {
104
+ Object.defineProperty(process.stdin, "isTTY", {
105
+ configurable: true,
106
+ value: originalStdinIsTTY,
107
+ });
108
+ Object.defineProperty(process.stdout, "isTTY", {
109
+ configurable: true,
110
+ value: originalStdoutIsTTY,
111
+ });
112
+ Object.defineProperty(process.stdin, "isRaw", {
113
+ configurable: true,
114
+ value: originalStdinIsRaw,
115
+ });
116
+ Object.defineProperty(process.stdin, "setRawMode", {
117
+ configurable: true,
118
+ value: originalSetRawMode,
119
+ });
120
+ process.stdout.write = originalStdoutWrite;
121
+ }
122
+
123
+ describe("vellum retire", () => {
124
+ beforeEach(() => {
125
+ process.env.VELLUM_LOCKFILE_DIR = testDir;
126
+ rmSync(join(testDir, ".vellum.lock.json"), { force: true });
127
+ process.argv = ["bun", "vellum", "retire"];
128
+ process.exit = ((code?: number) => {
129
+ throw new Error(`process.exit:${code}`);
130
+ }) as typeof process.exit;
131
+ retireLocalMock.mockReset();
132
+ retireLocalMock.mockResolvedValue(undefined);
133
+ setTerminalMode(false);
134
+ consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
135
+ consoleErrorSpy = spyOn(console, "error").mockImplementation(() => {});
136
+ });
137
+
138
+ afterEach(() => {
139
+ process.argv = originalArgv;
140
+ process.exit = originalExit;
141
+ restoreTerminal();
142
+ consoleLogSpy.mockRestore();
143
+ consoleErrorSpy.mockRestore();
144
+ });
145
+
146
+ afterAll(() => {
147
+ mock.module("../lib/retire-local.js", () => realRetireLocalModule);
148
+ if (originalLockfileDir === undefined) {
149
+ delete process.env.VELLUM_LOCKFILE_DIR;
150
+ } else {
151
+ process.env.VELLUM_LOCKFILE_DIR = originalLockfileDir;
152
+ }
153
+ rmSync(testDir, { recursive: true, force: true });
154
+ });
155
+
156
+ test("--yes retires by unquoted display name and removes by assistant ID", async () => {
157
+ const entry = makeEntry("assistant-1", { name: "Example Assistant" });
158
+ writeLockfile([entry]);
159
+ process.argv = ["bun", "vellum", "retire", "Example", "Assistant", "--yes"];
160
+
161
+ await retire();
162
+
163
+ expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
164
+ expect(loadAllAssistants()).toEqual([]);
165
+ const output = consoleLogSpy.mock.calls.flat().join("\n");
166
+ expect(output).toContain("Name: Example Assistant");
167
+ expect(output).toContain("ID: assistant-1");
168
+ expect(output).toContain(
169
+ "Removed Example Assistant (assistant-1) from config.",
170
+ );
171
+ });
172
+
173
+ test("non-interactive retire without --yes fails before deleting", async () => {
174
+ const entry = makeEntry("assistant-1", { name: "Example Assistant" });
175
+ writeLockfile([entry]);
176
+ const before = readLockfile();
177
+ process.argv = ["bun", "vellum", "retire", "Example", "Assistant"];
178
+
179
+ await expect(retire()).rejects.toThrow("process.exit:1");
180
+
181
+ expect(retireLocalMock).not.toHaveBeenCalled();
182
+ expect(readLockfile()).toBe(before);
183
+ const output = consoleErrorSpy.mock.calls.flat().join("\n");
184
+ expect(output).toContain("Refusing to retire without confirmation");
185
+ expect(output).toContain("--yes");
186
+ });
187
+
188
+ test("interactive cancel leaves the assistant untouched", async () => {
189
+ const entry = makeEntry("assistant-1", { name: "Example Assistant" });
190
+ writeLockfile([entry]);
191
+ const before = readLockfile();
192
+ setInteractiveTerminal();
193
+ process.argv = ["bun", "vellum", "retire", "Example", "Assistant"];
194
+
195
+ const pending = retire();
196
+ queueMicrotask(() => {
197
+ process.stdin.emit("data", Buffer.from("q"));
198
+ });
199
+
200
+ await expect(pending).rejects.toThrow("process.exit:1");
201
+ expect(retireLocalMock).not.toHaveBeenCalled();
202
+ expect(readLockfile()).toBe(before);
203
+ expect(consoleLogSpy.mock.calls.flat().join("\n")).toContain(
204
+ "Retire cancelled.",
205
+ );
206
+ });
207
+
208
+ test("interactive confirmation retires the assistant", async () => {
209
+ const entry = makeEntry("assistant-1", { name: "Example Assistant" });
210
+ writeLockfile([entry]);
211
+ setInteractiveTerminal();
212
+ process.argv = ["bun", "vellum", "retire", "Example", "Assistant"];
213
+
214
+ const pending = retire();
215
+ queueMicrotask(() => {
216
+ process.stdin.emit("data", Buffer.from([13]));
217
+ });
218
+
219
+ await pending;
220
+ expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
221
+ expect(loadAllAssistants()).toEqual([]);
222
+ });
223
+
224
+ test("ambiguous display names fail before deleting", async () => {
225
+ writeLockfile([
226
+ makeEntry("assistant-1", { name: "Example Assistant" }),
227
+ makeEntry("assistant-2", { name: "Example Assistant" }),
228
+ ]);
229
+ const before = readLockfile();
230
+ process.argv = ["bun", "vellum", "retire", "Example", "Assistant", "--yes"];
231
+
232
+ await expect(retire()).rejects.toThrow("process.exit:1");
233
+
234
+ expect(retireLocalMock).not.toHaveBeenCalled();
235
+ expect(readLockfile()).toBe(before);
236
+ const output = consoleErrorSpy.mock.calls.flat().join("\n");
237
+ expect(output).toContain("Multiple assistants match 'Example Assistant'");
238
+ expect(output).toContain("assistant-1");
239
+ expect(output).toContain("assistant-2");
240
+ });
241
+ });
@@ -2,30 +2,34 @@ import { existsSync, unlinkSync } from "fs";
2
2
  import { join } from "path";
3
3
 
4
4
  import {
5
- findAssistantByName,
5
+ formatAssistantLookupError,
6
+ formatAssistantReference,
7
+ getAssistantDisplayName,
6
8
  loadAllAssistants,
9
+ lookupAssistantByIdentifier,
7
10
  removeAssistantEntry,
8
- } from "../lib/assistant-config";
9
- import type { AssistantEntry } from "../lib/assistant-config";
10
- import { getConfigDir } from "../lib/environments/paths";
11
- import { getCurrentEnvironment } from "../lib/environments/resolve";
11
+ } from "../lib/assistant-config.js";
12
+ import type { AssistantEntry } from "../lib/assistant-config.js";
13
+ import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
14
+ import { getConfigDir } from "../lib/environments/paths.js";
15
+ import { getCurrentEnvironment } from "../lib/environments/resolve.js";
12
16
  import {
13
17
  authHeaders,
14
18
  getPlatformUrl,
15
19
  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";
20
+ } from "../lib/platform-client.js";
21
+ import { retireInstance as retireAwsInstance } from "../lib/aws.js";
22
+ import { retireDocker } from "../lib/docker.js";
23
+ import { retireInstance as retireGcpInstance } from "../lib/gcp.js";
24
+ import { retireLocal } from "../lib/retire-local.js";
25
+ import { retireAppleContainer } from "../lib/retire-apple-container.js";
26
+ import { exec } from "../lib/step-runner.js";
23
27
  import {
24
28
  openLogFile,
25
29
  closeLogFile,
26
30
  resetLogFile,
27
31
  writeToLogFile,
28
- } from "../lib/xdg-log";
32
+ } from "../lib/xdg-log.js";
29
33
 
30
34
  function resolveCloud(entry: AssistantEntry): string {
31
35
  if (entry.cloud) {
@@ -51,6 +55,12 @@ function extractHostFromUrl(url: string): string {
51
55
 
52
56
  export { retireLocal };
53
57
 
58
+ interface RetireArgs {
59
+ name?: string;
60
+ source?: string;
61
+ yes: boolean;
62
+ }
63
+
54
64
  async function retireCustom(entry: AssistantEntry): Promise<void> {
55
65
  const host = extractHostFromUrl(entry.runtimeUrl);
56
66
  const sshUser = entry.sshUser ?? "root";
@@ -129,14 +139,82 @@ async function retireVellum(
129
139
  }
130
140
  }
131
141
 
132
- function parseSource(): string | undefined {
133
- const args = process.argv.slice(4);
142
+ function parseRetireArgs(args: string[]): RetireArgs {
143
+ let source: string | undefined;
134
144
  for (let i = 0; i < args.length; i++) {
135
145
  if (args[i] === "--source" && args[i + 1]) {
136
- return args[i + 1];
146
+ source = args[i + 1];
147
+ i++;
137
148
  }
138
149
  }
139
- return undefined;
150
+
151
+ return {
152
+ name: parseAssistantTargetArg(args, ["--source"]),
153
+ source,
154
+ yes: args.includes("--yes"),
155
+ };
156
+ }
157
+
158
+ function formatRuntimeUrl(entry: AssistantEntry): string {
159
+ return entry.localUrl ?? entry.runtimeUrl;
160
+ }
161
+
162
+ function printRetireTarget(entry: AssistantEntry, cloud: string): void {
163
+ const displayName = getAssistantDisplayName(entry);
164
+
165
+ console.log("Assistant to retire:");
166
+ if (displayName !== entry.assistantId) {
167
+ console.log(` Name: ${displayName}`);
168
+ }
169
+ console.log(` ID: ${entry.assistantId}`);
170
+ console.log(` Cloud: ${cloud}`);
171
+ console.log(` Runtime: ${formatRuntimeUrl(entry)}`);
172
+ console.log("");
173
+ }
174
+
175
+ function canPromptForRetireConfirmation(): boolean {
176
+ return (
177
+ process.stdin.isTTY === true &&
178
+ process.stdout.isTTY === true &&
179
+ typeof process.stdin.setRawMode === "function"
180
+ );
181
+ }
182
+
183
+ async function confirmRetireInteractive(): Promise<boolean> {
184
+ const stdin = process.stdin;
185
+ const stdout = process.stdout;
186
+ const wasRaw = stdin.isRaw === true;
187
+ const wasPaused = stdin.isPaused();
188
+
189
+ stdout.write("Press Enter to retire, or Esc/q to cancel: ");
190
+ stdin.setRawMode(true);
191
+ stdin.resume();
192
+
193
+ return await new Promise<boolean>((resolve) => {
194
+ const cleanup = () => {
195
+ stdin.off("data", onData);
196
+ stdin.setRawMode(wasRaw);
197
+ if (wasPaused) {
198
+ stdin.pause();
199
+ }
200
+ stdout.write("\n");
201
+ };
202
+
203
+ const onData = (chunk: Buffer) => {
204
+ const byte = chunk[0];
205
+ if (byte === 13 || byte === 10) {
206
+ cleanup();
207
+ resolve(true);
208
+ return;
209
+ }
210
+ if (byte === 27 || byte === 3 || byte === 113 || byte === 81) {
211
+ cleanup();
212
+ resolve(false);
213
+ }
214
+ };
215
+
216
+ stdin.on("data", onData);
217
+ });
140
218
  }
141
219
 
142
220
  /** Patch console methods to also append output to the given log file descriptor. */
@@ -188,38 +266,70 @@ export async function retire(): Promise<void> {
188
266
  async function retireInner(): Promise<void> {
189
267
  const args = process.argv.slice(3);
190
268
  if (args.includes("--help") || args.includes("-h")) {
191
- console.log("Usage: vellum retire <name> [--source <source>]");
269
+ console.log(
270
+ "Usage: vellum retire <name-or-id> [--source <source>] [--yes]",
271
+ );
192
272
  console.log("");
193
273
  console.log("Delete an assistant instance and archive its data.");
274
+ console.log(
275
+ "By default, retire prints the assistant name, ID, cloud, and runtime before asking for confirmation.",
276
+ );
194
277
  console.log("");
195
278
  console.log("Arguments:");
196
- console.log(" <name> Name of the assistant to retire");
279
+ console.log(
280
+ " <name-or-id> Assistant display name or ID to retire",
281
+ );
197
282
  console.log("");
198
283
  console.log("Options:");
199
284
  console.log(" --source <source> Source identifier for the retirement");
285
+ console.log(
286
+ " --yes Skip the interactive confirmation prompt",
287
+ );
200
288
  process.exit(0);
201
289
  }
202
290
 
203
- const name = process.argv[3];
291
+ const parsed = parseRetireArgs(args);
292
+ const name = parsed.name;
204
293
 
205
294
  if (!name) {
206
- console.error("Error: Instance name is required.");
207
- console.error("Usage: vellum retire <name> [--source <source>]");
295
+ console.error("Error: Assistant name or ID is required.");
296
+ console.error(
297
+ "Usage: vellum retire <name-or-id> [--source <source>] [--yes]",
298
+ );
208
299
  process.exit(1);
209
300
  }
210
301
 
211
- const entry = findAssistantByName(name);
212
- if (!entry) {
213
- console.error(`No assistant found with name '${name}'.`);
302
+ const lookup = lookupAssistantByIdentifier(name);
303
+ if (lookup.status !== "found") {
304
+ console.error(formatAssistantLookupError(name, lookup));
214
305
  console.error("Run 'vellum hatch' first, or check the instance name.");
215
306
  process.exit(1);
216
307
  }
217
308
 
218
- const source = parseSource();
309
+ const entry = lookup.entry;
310
+ const assistantId = entry.assistantId;
311
+ const source = parsed.source;
219
312
  const cloud = resolveCloud(entry);
313
+ printRetireTarget(entry, cloud);
314
+
315
+ if (!parsed.yes) {
316
+ if (!canPromptForRetireConfirmation()) {
317
+ console.error(
318
+ "Error: Refusing to retire without confirmation in a non-interactive terminal.",
319
+ );
320
+ console.error("Re-run with --yes to confirm from automation.");
321
+ process.exit(1);
322
+ }
323
+
324
+ const confirmed = await confirmRetireInteractive();
325
+ if (!confirmed) {
326
+ console.log("Retire cancelled.");
327
+ process.exit(1);
328
+ }
329
+ }
220
330
 
221
331
  if (cloud === "apple-container") {
222
- await retireAppleContainer(name, entry);
332
+ await retireAppleContainer(assistantId, entry);
223
333
  } else if (cloud === "gcp") {
224
334
  const project = entry.project;
225
335
  const zone = entry.zone;
@@ -229,29 +339,29 @@ async function retireInner(): Promise<void> {
229
339
  );
230
340
  process.exit(1);
231
341
  }
232
- await retireGcpInstance(name, project, zone, source);
342
+ await retireGcpInstance(assistantId, project, zone, source);
233
343
  } else if (cloud === "aws") {
234
344
  const region = entry.region;
235
345
  if (!region) {
236
346
  console.error("Error: AWS region not found in assistant config.");
237
347
  process.exit(1);
238
348
  }
239
- await retireAwsInstance(name, region, source);
349
+ await retireAwsInstance(assistantId, region, source);
240
350
  } else if (cloud === "docker") {
241
- await retireDocker(name);
351
+ await retireDocker(assistantId);
242
352
  } else if (cloud === "local") {
243
- await retireLocal(name, entry);
353
+ await retireLocal(assistantId, entry);
244
354
  } else if (cloud === "custom") {
245
355
  await retireCustom(entry);
246
356
  } else if (cloud === "vellum") {
247
- await retireVellum(entry.assistantId, entry.runtimeUrl);
357
+ await retireVellum(assistantId, entry.runtimeUrl);
248
358
  } else {
249
359
  console.error(`Error: Unknown cloud type '${cloud}'.`);
250
360
  process.exit(1);
251
361
  }
252
362
 
253
- removeAssistantEntry(name);
254
- console.log(`Removed ${name} from config.`);
363
+ removeAssistantEntry(assistantId);
364
+ console.log(`Removed ${formatAssistantReference(entry)} from config.`);
255
365
 
256
366
  // When no assistants remain, remove the dock-display-name sentinel so
257
367
  // the next build.sh run falls back to "Vellum" instead of using the