@vellumai/cli 0.4.43 → 0.4.45

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.
@@ -34,6 +34,7 @@ export const ANSI = {
34
34
  } as const;
35
35
 
36
36
  export const SLASH_COMMANDS = [
37
+ "/btw",
37
38
  "/clear",
38
39
  "/doctor",
39
40
  "/exit",
@@ -98,7 +99,7 @@ const MIN_FEED_ROWS = 3;
98
99
  // Feed item height estimation
99
100
  const TOOL_CALL_CHROME_LINES = 2; // header (┌) + footer (└)
100
101
  const MESSAGE_SPACING = 1;
101
- const HELP_DISPLAY_HEIGHT = 6;
102
+ const HELP_DISPLAY_HEIGHT = 8;
102
103
 
103
104
  interface ListMessagesResponse {
104
105
  messages: RuntimeMessage[];
@@ -152,6 +153,19 @@ interface HealthResponse {
152
153
  message?: string;
153
154
  }
154
155
 
156
+ /** Extract human-readable message from a daemon JSON error response. */
157
+ function friendlyErrorMessage(status: number, body: string): string {
158
+ try {
159
+ const parsed = JSON.parse(body) as { error?: { message?: string } };
160
+ if (parsed?.error?.message) {
161
+ return parsed.error.message;
162
+ }
163
+ } catch {
164
+ // Not JSON — fall through
165
+ }
166
+ return `HTTP ${status}: ${body || "Unknown error"}`;
167
+ }
168
+
155
169
  async function runtimeRequest<T>(
156
170
  baseUrl: string,
157
171
  assistantId: string,
@@ -171,7 +185,7 @@ async function runtimeRequest<T>(
171
185
 
172
186
  if (!response.ok) {
173
187
  const body = await response.text().catch(() => "");
174
- throw new Error(`HTTP ${response.status}: ${body || response.statusText}`);
188
+ throw new Error(friendlyErrorMessage(response.status, body));
175
189
  }
176
190
 
177
191
  if (response.status === 204) {
@@ -359,13 +373,7 @@ async function handleConfirmationPrompt(
359
373
  const index = await chatApp.showSelection("Tool Approval", options);
360
374
 
361
375
  if (index === 0) {
362
- await submitDecision(
363
- baseUrl,
364
- assistantId,
365
- requestId,
366
- "allow",
367
- bearerToken,
368
- );
376
+ await submitDecision(baseUrl, assistantId, requestId, "allow", bearerToken);
369
377
  chatApp.addStatus("\u2714 Allowed", "green");
370
378
  return;
371
379
  }
@@ -394,13 +402,7 @@ async function handleConfirmationPrompt(
394
402
  return;
395
403
  }
396
404
 
397
- await submitDecision(
398
- baseUrl,
399
- assistantId,
400
- requestId,
401
- "deny",
402
- bearerToken,
403
- );
405
+ await submitDecision(baseUrl, assistantId, requestId, "deny", bearerToken);
404
406
  chatApp.addStatus("\u2718 Denied", "yellow");
405
407
  }
406
408
 
@@ -437,13 +439,7 @@ async function handlePatternSelection(
437
439
  return;
438
440
  }
439
441
 
440
- await submitDecision(
441
- baseUrl,
442
- assistantId,
443
- requestId,
444
- "deny",
445
- bearerToken,
446
- );
442
+ await submitDecision(baseUrl, assistantId, requestId, "deny", bearerToken);
447
443
  chatApp.addStatus("\u2718 Denied", "yellow");
448
444
  }
449
445
 
@@ -491,13 +487,7 @@ async function handleScopeSelection(
491
487
  return;
492
488
  }
493
489
 
494
- await submitDecision(
495
- baseUrl,
496
- assistantId,
497
- requestId,
498
- "deny",
499
- bearerToken,
500
- );
490
+ await submitDecision(baseUrl, assistantId, requestId, "deny", bearerToken);
501
491
  chatApp.addStatus("\u2718 Denied", "yellow");
502
492
  }
503
493
 
@@ -629,6 +619,10 @@ function HelpDisplay(): ReactElement {
629
619
  return (
630
620
  <Box flexDirection="column">
631
621
  <Text bold>Commands:</Text>
622
+ <Text>
623
+ {" /btw <question> "}
624
+ <Text dimColor>Ask a side question while the assistant is working</Text>
625
+ </Text>
632
626
  <Text>
633
627
  {" /doctor [question] "}
634
628
  <Text dimColor>Run diagnostics on the remote instance via SSH</Text>
@@ -1252,7 +1246,6 @@ function ChatApp({
1252
1246
 
1253
1247
  const showSpinner = useCallback((text: string) => {
1254
1248
  setSpinnerText(text);
1255
- setInputFocused(false);
1256
1249
  }, []);
1257
1250
 
1258
1251
  const hideSpinner = useCallback(() => {
@@ -1488,79 +1481,6 @@ function ChatApp({
1488
1481
  return;
1489
1482
  }
1490
1483
 
1491
- if (trimmed === "/pair") {
1492
- h.showSpinner("Generating pairing credentials...");
1493
-
1494
- const isConnected = await ensureConnected();
1495
- if (!isConnected) {
1496
- h.hideSpinner();
1497
- h.showError("Cannot pair — not connected to the assistant runtime.");
1498
- return;
1499
- }
1500
-
1501
- try {
1502
- const pairingRequestId = randomUUID();
1503
- const pairingSecret = randomBytes(32).toString("hex");
1504
- const gatewayUrl = runtimeUrl;
1505
-
1506
- // Call /pairing/register on the gateway (dedicated pairing proxy route)
1507
- const registerUrl = `${runtimeUrl}/pairing/register`;
1508
- const registerRes = await fetch(registerUrl, {
1509
- method: "POST",
1510
- headers: {
1511
- "Content-Type": "application/json",
1512
- ...(bearerToken
1513
- ? { Authorization: `Bearer ${bearerToken}` }
1514
- : {}),
1515
- },
1516
- body: JSON.stringify({
1517
- pairingRequestId,
1518
- pairingSecret,
1519
- gatewayUrl,
1520
- }),
1521
- });
1522
-
1523
- if (!registerRes.ok) {
1524
- const body = await registerRes.text().catch(() => "");
1525
- throw new Error(
1526
- `HTTP ${registerRes.status}: ${body || registerRes.statusText}`,
1527
- );
1528
- }
1529
-
1530
- const hostId = createHash("sha256")
1531
- .update(hostname() + userInfo().username)
1532
- .digest("hex");
1533
- const payload = JSON.stringify({
1534
- type: "vellum-daemon",
1535
- v: 4,
1536
- id: hostId,
1537
- g: gatewayUrl,
1538
- pairingRequestId,
1539
- pairingSecret,
1540
- });
1541
-
1542
- const qrString = await new Promise<string>((resolve) => {
1543
- qrcode.generate(payload, { small: true }, (code: string) => {
1544
- resolve(code);
1545
- });
1546
- });
1547
-
1548
- h.hideSpinner();
1549
- h.addStatus(
1550
- `Pairing Ready\n\n` +
1551
- `Scan this QR code with the Vellum iOS app:\n\n` +
1552
- `${qrString}\n` +
1553
- `This pairing request expires in 5 minutes. Run /pair again to generate a new one.`,
1554
- );
1555
- } catch (err) {
1556
- h.hideSpinner();
1557
- h.showError(
1558
- `Pairing failed: ${err instanceof Error ? err.message : err}`,
1559
- );
1560
- }
1561
- return;
1562
- }
1563
-
1564
1484
  if (trimmed === "/retire") {
1565
1485
  if (!project || !zone) {
1566
1486
  h.showError(
@@ -1714,6 +1634,214 @@ function ChatApp({
1714
1634
  return;
1715
1635
  }
1716
1636
 
1637
+ // If a connection attempt is already in progress, don't silently drop input
1638
+ if (connectingRef.current) {
1639
+ h.addStatus(
1640
+ "Still connecting — please wait a moment and try again.",
1641
+ "yellow",
1642
+ );
1643
+ return;
1644
+ }
1645
+
1646
+ if (trimmed.startsWith("/btw ")) {
1647
+ const question = trimmed.slice(5).trim();
1648
+ if (!question) return;
1649
+
1650
+ h.addStatus(`/btw ${question}`, "gray");
1651
+
1652
+ const isConnected = await ensureConnected();
1653
+ if (!isConnected) return;
1654
+
1655
+ try {
1656
+ const res = await fetch(
1657
+ `${runtimeUrl}/v1/assistants/${assistantId}/btw`,
1658
+ {
1659
+ method: "POST",
1660
+ headers: {
1661
+ "Content-Type": "application/json",
1662
+ ...(bearerToken
1663
+ ? { Authorization: `Bearer ${bearerToken}` }
1664
+ : {}),
1665
+ },
1666
+ body: JSON.stringify({
1667
+ conversationKey: assistantId,
1668
+ content: question,
1669
+ }),
1670
+ signal: AbortSignal.timeout(30_000),
1671
+ },
1672
+ );
1673
+
1674
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1675
+
1676
+ let fullText = "";
1677
+ let sseError = "";
1678
+ const reader = res.body?.getReader();
1679
+ const decoder = new TextDecoder();
1680
+ if (reader) {
1681
+ let buffer = "";
1682
+ let currentEvent = "";
1683
+ while (true) {
1684
+ const { done, value } = await reader.read();
1685
+ if (done) break;
1686
+ buffer += decoder.decode(value, { stream: true });
1687
+ const lines = buffer.split("\n");
1688
+ buffer = lines.pop() ?? "";
1689
+ for (const line of lines) {
1690
+ if (line.startsWith("event: ")) {
1691
+ currentEvent = line.slice(7).trim();
1692
+ } else if (line.startsWith("data: ")) {
1693
+ try {
1694
+ const data = JSON.parse(line.slice(6));
1695
+ if (currentEvent === "btw_error" || data.error) {
1696
+ sseError = data.error ?? data.text ?? "Unknown error";
1697
+ } else if (data.text) {
1698
+ fullText += data.text;
1699
+ }
1700
+ } catch {
1701
+ /* skip malformed */
1702
+ }
1703
+ } else if (line.trim() === "") {
1704
+ // Empty line marks end of SSE event; reset event type
1705
+ currentEvent = "";
1706
+ }
1707
+ }
1708
+ }
1709
+ }
1710
+ if (sseError) {
1711
+ h.showError(`/btw: ${sseError}`);
1712
+ } else {
1713
+ h.addStatus(fullText || "No response");
1714
+ }
1715
+ } catch (err) {
1716
+ h.showError(
1717
+ `/btw failed: ${err instanceof Error ? err.message : err}`,
1718
+ );
1719
+ }
1720
+ return;
1721
+ }
1722
+
1723
+ if (trimmed === "/pair") {
1724
+ h.showSpinner("Generating pairing credentials...");
1725
+
1726
+ const isConnected = await ensureConnected();
1727
+ if (!isConnected) {
1728
+ h.hideSpinner();
1729
+ h.showError("Cannot pair — not connected to the assistant runtime.");
1730
+ return;
1731
+ }
1732
+
1733
+ try {
1734
+ const pairingRequestId = randomUUID();
1735
+ const pairingSecret = randomBytes(32).toString("hex");
1736
+ const gatewayUrl = runtimeUrl;
1737
+
1738
+ // Call /pairing/register on the gateway (dedicated pairing proxy route)
1739
+ const registerUrl = `${runtimeUrl}/pairing/register`;
1740
+ const registerRes = await fetch(registerUrl, {
1741
+ method: "POST",
1742
+ headers: {
1743
+ "Content-Type": "application/json",
1744
+ ...(bearerToken
1745
+ ? { Authorization: `Bearer ${bearerToken}` }
1746
+ : {}),
1747
+ },
1748
+ body: JSON.stringify({
1749
+ pairingRequestId,
1750
+ pairingSecret,
1751
+ gatewayUrl,
1752
+ }),
1753
+ });
1754
+
1755
+ if (!registerRes.ok) {
1756
+ const body = await registerRes.text().catch(() => "");
1757
+ throw new Error(
1758
+ `HTTP ${registerRes.status}: ${body || registerRes.statusText}`,
1759
+ );
1760
+ }
1761
+
1762
+ const hostId = createHash("sha256")
1763
+ .update(hostname() + userInfo().username)
1764
+ .digest("hex");
1765
+ const payload = JSON.stringify({
1766
+ type: "vellum-daemon",
1767
+ v: 4,
1768
+ id: hostId,
1769
+ g: gatewayUrl,
1770
+ pairingRequestId,
1771
+ pairingSecret,
1772
+ });
1773
+
1774
+ const qrString = await new Promise<string>((resolve) => {
1775
+ qrcode.generate(payload, { small: true }, (code: string) => {
1776
+ resolve(code);
1777
+ });
1778
+ });
1779
+
1780
+ h.hideSpinner();
1781
+ h.addStatus(
1782
+ `Pairing Ready\n\n` +
1783
+ `Scan this QR code with the Vellum iOS app:\n\n` +
1784
+ `${qrString}\n` +
1785
+ `This pairing request expires in 5 minutes. Run /pair again to generate a new one.`,
1786
+ );
1787
+ } catch (err) {
1788
+ h.hideSpinner();
1789
+ h.showError(
1790
+ `Pairing failed: ${err instanceof Error ? err.message : err}`,
1791
+ );
1792
+ }
1793
+ return;
1794
+ }
1795
+
1796
+ if (busyRef.current) {
1797
+ // /btw is already handled above this block
1798
+ if (!trimmed.startsWith("/")) {
1799
+ const userMsg: RuntimeMessage = {
1800
+ id: "local-user-" + Date.now(),
1801
+ role: "user",
1802
+ content: trimmed,
1803
+ timestamp: new Date().toISOString(),
1804
+ };
1805
+ h.addMessage(userMsg);
1806
+ }
1807
+ const isConnected = await ensureConnected();
1808
+ if (!isConnected) {
1809
+ h.showError("Cannot send — not connected to the assistant.");
1810
+ setInputFocused(true);
1811
+ return;
1812
+ }
1813
+ try {
1814
+ const controller = new AbortController();
1815
+ const timeoutId = setTimeout(
1816
+ () => controller.abort(),
1817
+ SEND_TIMEOUT_MS,
1818
+ );
1819
+ const sendResult = await sendMessage(
1820
+ runtimeUrl,
1821
+ assistantId,
1822
+ trimmed,
1823
+ controller.signal,
1824
+ bearerToken,
1825
+ );
1826
+ clearTimeout(timeoutId);
1827
+ if (sendResult.accepted) {
1828
+ chatLogRef.current.push({ role: "user", content: trimmed });
1829
+ h.addStatus(
1830
+ "Message queued — will be processed after current response",
1831
+ "gray",
1832
+ );
1833
+ } else {
1834
+ h.showError("Message was not accepted by the assistant");
1835
+ }
1836
+ } catch (err) {
1837
+ h.showError(
1838
+ `Failed to queue message: ${err instanceof Error ? err.message : String(err)}`,
1839
+ );
1840
+ }
1841
+ setInputFocused(true);
1842
+ return;
1843
+ }
1844
+
1717
1845
  if (!trimmed.startsWith("/")) {
1718
1846
  const userMsg: RuntimeMessage = {
1719
1847
  id: "local-user-" + Date.now(),
@@ -1758,7 +1886,8 @@ function ChatApp({
1758
1886
  clearTimeout(timeoutId);
1759
1887
  h.setBusy(false);
1760
1888
  h.hideSpinner();
1761
- const errorMsg = `Failed to send: ${sendErr instanceof Error ? sendErr.message : sendErr}`;
1889
+ const errorMsg =
1890
+ sendErr instanceof Error ? sendErr.message : String(sendErr);
1762
1891
  h.showError(errorMsg);
1763
1892
  chatLogRef.current.push({ role: "error", content: errorMsg });
1764
1893
  return;
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import cliPkg from "../package.json";
4
+ import { clean } from "./commands/clean";
4
5
  import { client } from "./commands/client";
5
6
  import { hatch } from "./commands/hatch";
6
7
  import { login, logout, whoami } from "./commands/login";
@@ -15,6 +16,7 @@ import { use } from "./commands/use";
15
16
  import { wake } from "./commands/wake";
16
17
 
17
18
  const commands = {
19
+ clean,
18
20
  client,
19
21
  hatch,
20
22
  login,
@@ -46,6 +48,7 @@ async function main() {
46
48
  console.log("Usage: vellum <command> [options]");
47
49
  console.log("");
48
50
  console.log("Commands:");
51
+ console.log(" clean Kill orphaned vellum processes");
49
52
  console.log(" client Connect to a hatched assistant");
50
53
  console.log(" hatch Create a new assistant instance");
51
54
  console.log(" login Log in to the Vellum platform");
@@ -16,7 +16,9 @@ import { probePort } from "./port-probe.js";
16
16
  */
17
17
  export interface LocalInstanceResources {
18
18
  /**
19
- * Instance-specific data root at `~/.local/share/vellum/assistants/<name>/`.
19
+ * Instance-specific data root. The first local assistant uses `~` (home
20
+ * directory) with default ports. Subsequent instances are placed under
21
+ * `~/.local/share/vellum/assistants/<name>/`.
20
22
  * The daemon's `.vellum/` directory lives inside it.
21
23
  */
22
24
  instanceDir: string;
@@ -28,6 +30,7 @@ export interface LocalInstanceResources {
28
30
  qdrantPort: number;
29
31
  /** Absolute path to the daemon PID file */
30
32
  pidFile: string;
33
+ [key: string]: unknown;
31
34
  }
32
35
 
33
36
  export interface AssistantEntry {
@@ -48,10 +51,11 @@ export interface AssistantEntry {
48
51
  hatchedAt?: string;
49
52
  /** Per-instance resource config. Present for local entries in multi-instance setups. */
50
53
  resources?: LocalInstanceResources;
54
+ [key: string]: unknown;
51
55
  }
52
56
 
53
57
  interface LockfileData {
54
- assistants?: AssistantEntry[];
58
+ assistants?: Record<string, unknown>[];
55
59
  activeAssistant?: string;
56
60
  platformBaseUrl?: string;
57
61
  [key: string]: unknown;
@@ -92,14 +96,132 @@ function writeLockfile(data: LockfileData): void {
92
96
  writeFileSync(lockfilePath, JSON.stringify(data, null, 2) + "\n");
93
97
  }
94
98
 
99
+ /**
100
+ * Try to extract a port number from a URL string (e.g. `http://localhost:7830`).
101
+ * Returns undefined if the URL is malformed or has no explicit port.
102
+ */
103
+ function parsePortFromUrl(url: unknown): number | undefined {
104
+ if (typeof url !== "string") return undefined;
105
+ try {
106
+ const parsed = new URL(url);
107
+ const port = parseInt(parsed.port, 10);
108
+ return isNaN(port) ? undefined : port;
109
+ } catch {
110
+ return undefined;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Detect and migrate legacy lockfile entries to the current format.
116
+ *
117
+ * Legacy entries stored `baseDataDir` as a top-level field. The current
118
+ * format nests this under `resources.instanceDir`. This function also
119
+ * synthesises a full `resources` object when one is missing by inferring
120
+ * ports from the entry's `runtimeUrl` and falling back to defaults.
121
+ *
122
+ * Returns `true` if the entry was mutated (so the caller can persist).
123
+ */
124
+ export function migrateLegacyEntry(raw: Record<string, unknown>): boolean {
125
+ if (typeof raw.cloud === "string" && raw.cloud !== "local") {
126
+ return false;
127
+ }
128
+
129
+ let mutated = false;
130
+
131
+ // Migrate top-level `baseDataDir` → `resources.instanceDir`
132
+ if (typeof raw.baseDataDir === "string" && raw.baseDataDir) {
133
+ if (!raw.resources || typeof raw.resources !== "object") {
134
+ raw.resources = {};
135
+ }
136
+ const res = raw.resources as Record<string, unknown>;
137
+ if (!res.instanceDir) {
138
+ res.instanceDir = raw.baseDataDir;
139
+ mutated = true;
140
+ }
141
+ delete raw.baseDataDir;
142
+ mutated = true;
143
+ }
144
+
145
+ // Synthesise missing `resources` for local entries
146
+ if (!raw.resources || typeof raw.resources !== "object") {
147
+ const gatewayPort =
148
+ parsePortFromUrl(raw.runtimeUrl) ?? DEFAULT_GATEWAY_PORT;
149
+ const instanceDir = join(
150
+ homedir(),
151
+ ".local",
152
+ "share",
153
+ "vellum",
154
+ "assistants",
155
+ typeof raw.assistantId === "string" ? raw.assistantId : "default",
156
+ );
157
+ raw.resources = {
158
+ instanceDir,
159
+ daemonPort: DEFAULT_DAEMON_PORT,
160
+ gatewayPort,
161
+ qdrantPort: DEFAULT_QDRANT_PORT,
162
+ pidFile: join(instanceDir, ".vellum", "vellum.pid"),
163
+ };
164
+ mutated = true;
165
+ } else {
166
+ // Backfill any missing fields on an existing partial `resources` object
167
+ const res = raw.resources as Record<string, unknown>;
168
+ if (!res.instanceDir) {
169
+ res.instanceDir = join(
170
+ homedir(),
171
+ ".local",
172
+ "share",
173
+ "vellum",
174
+ "assistants",
175
+ typeof raw.assistantId === "string" ? raw.assistantId : "default",
176
+ );
177
+ mutated = true;
178
+ }
179
+ if (typeof res.daemonPort !== "number") {
180
+ res.daemonPort = DEFAULT_DAEMON_PORT;
181
+ mutated = true;
182
+ }
183
+ if (typeof res.gatewayPort !== "number") {
184
+ res.gatewayPort =
185
+ parsePortFromUrl(raw.runtimeUrl) ?? DEFAULT_GATEWAY_PORT;
186
+ mutated = true;
187
+ }
188
+ if (typeof res.qdrantPort !== "number") {
189
+ res.qdrantPort = DEFAULT_QDRANT_PORT;
190
+ mutated = true;
191
+ }
192
+ if (typeof res.pidFile !== "string") {
193
+ res.pidFile = join(
194
+ res.instanceDir as string,
195
+ ".vellum",
196
+ "vellum.pid",
197
+ );
198
+ mutated = true;
199
+ }
200
+ }
201
+
202
+ return mutated;
203
+ }
204
+
95
205
  function readAssistants(): AssistantEntry[] {
96
206
  const data = readLockfile();
97
207
  const entries = data.assistants;
98
208
  if (!Array.isArray(entries)) {
99
209
  return [];
100
210
  }
211
+
212
+ let migrated = false;
213
+ for (const entry of entries) {
214
+ if (migrateLegacyEntry(entry)) {
215
+ migrated = true;
216
+ }
217
+ }
218
+
219
+ if (migrated) {
220
+ writeLockfile(data);
221
+ }
222
+
101
223
  return entries.filter(
102
- (e) =>
224
+ (e): e is AssistantEntry =>
103
225
  typeof e.assistantId === "string" && typeof e.runtimeUrl === "string",
104
226
  );
105
227
  }
@@ -131,14 +253,14 @@ export function findAssistantByName(name: string): AssistantEntry | null {
131
253
  export function removeAssistantEntry(assistantId: string): void {
132
254
  const data = readLockfile();
133
255
  const entries = (data.assistants ?? []).filter(
134
- (e: AssistantEntry) => e.assistantId !== assistantId,
256
+ (e) => e.assistantId !== assistantId,
135
257
  );
136
258
  data.assistants = entries;
137
259
  // Reassign active assistant if it matches the removed entry
138
260
  if (data.activeAssistant === assistantId) {
139
261
  const remaining = entries[0];
140
262
  if (remaining) {
141
- data.activeAssistant = remaining.assistantId;
263
+ data.activeAssistant = String(remaining.assistantId);
142
264
  } else {
143
265
  delete data.activeAssistant;
144
266
  }
@@ -229,12 +351,28 @@ async function findAvailablePort(
229
351
 
230
352
  /**
231
353
  * Allocate an isolated set of resources for a named local instance.
232
- * Each assistant is placed under
354
+ * The first local assistant uses the home directory with default ports.
355
+ * Subsequent assistants are placed under
233
356
  * `~/.local/share/vellum/assistants/<name>/` with scanned ports.
234
357
  */
235
358
  export async function allocateLocalResources(
236
359
  instanceName: string,
237
360
  ): Promise<LocalInstanceResources> {
361
+ // First local assistant gets the home directory with default ports.
362
+ const existingLocals = loadAllAssistants().filter((e) => e.cloud === "local");
363
+ if (existingLocals.length === 0) {
364
+ const home = homedir();
365
+ const vellumDir = join(home, ".vellum");
366
+ mkdirSync(vellumDir, { recursive: true });
367
+ return {
368
+ instanceDir: home,
369
+ daemonPort: DEFAULT_DAEMON_PORT,
370
+ gatewayPort: DEFAULT_GATEWAY_PORT,
371
+ qdrantPort: DEFAULT_QDRANT_PORT,
372
+ pidFile: join(vellumDir, "vellum.pid"),
373
+ };
374
+ }
375
+
238
376
  const instanceDir = join(
239
377
  homedir(),
240
378
  ".local",
package/src/lib/aws.ts CHANGED
@@ -513,7 +513,6 @@ export async function hatchAws(
513
513
  const awsEntry: AssistantEntry = {
514
514
  assistantId: instanceName,
515
515
  runtimeUrl,
516
- bearerToken,
517
516
  cloud: "aws",
518
517
  instanceId,
519
518
  region,