@vellumai/cli 0.6.6 → 0.7.1
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 +8 -2
- package/README.md +49 -0
- package/package.json +1 -1
- package/src/__tests__/assistant-config.test.ts +1 -7
- package/src/__tests__/backup.test.ts +475 -0
- package/src/__tests__/config-utils.test.ts +146 -0
- package/src/__tests__/env-drift.test.ts +10 -32
- package/src/__tests__/llm-provider-env-var-parity.test.ts +1 -21
- package/src/__tests__/multi-local.test.ts +0 -5
- package/src/__tests__/sleep.test.ts +1 -2
- package/src/__tests__/teleport.test.ts +988 -1266
- package/src/commands/backup.ts +117 -71
- package/src/commands/client.ts +10 -9
- package/src/commands/env.ts +93 -0
- package/src/commands/events.ts +2 -0
- package/src/commands/exec.ts +58 -13
- package/src/commands/login.ts +77 -12
- package/src/commands/logs.ts +2 -7
- package/src/commands/ps.ts +144 -25
- package/src/commands/restore.ts +26 -47
- package/src/commands/sleep.ts +5 -2
- package/src/commands/ssh.ts +17 -7
- package/src/commands/teleport.ts +462 -584
- package/src/commands/terminal.ts +9 -221
- package/src/commands/tunnel.ts +2 -7
- package/src/commands/upgrade.ts +108 -7
- package/src/commands/wake.ts +2 -1
- package/src/components/DefaultMainScreen.tsx +328 -154
- package/src/index.ts +5 -7
- package/src/lib/__tests__/docker.test.ts +50 -74
- package/src/lib/__tests__/job-polling.test.ts +278 -0
- package/src/lib/__tests__/local-runtime-client.test.ts +480 -0
- package/src/lib/__tests__/platform-client-signed-url.test.ts +405 -0
- package/src/lib/__tests__/runtime-url.test.ts +87 -0
- package/src/lib/__tests__/terminal-session.test.ts +202 -0
- package/src/lib/assistant-client.ts +5 -21
- package/src/lib/assistant-config.ts +46 -24
- package/src/lib/cli-error.ts +1 -0
- package/src/lib/client-identity.ts +67 -0
- package/src/lib/docker.ts +75 -77
- package/src/lib/environments/__tests__/paths.test.ts +2 -0
- package/src/lib/environments/resolve.ts +89 -7
- package/src/lib/environments/seeds.ts +8 -5
- package/src/lib/environments/types.ts +10 -0
- package/src/lib/hatch-local.ts +15 -120
- package/src/lib/health-check.ts +98 -0
- package/src/lib/job-polling.ts +195 -0
- package/src/lib/local-runtime-client.ts +231 -0
- package/src/lib/local.ts +165 -72
- package/src/lib/orphan-detection.ts +2 -35
- package/src/lib/platform-client.ts +190 -194
- package/src/lib/platform-releases.ts +23 -0
- package/src/lib/retire-local.ts +6 -2
- package/src/lib/runtime-url.ts +30 -0
- package/src/lib/sync-cloud-assistants.ts +126 -0
- package/src/lib/terminal-client.ts +6 -1
- package/src/lib/terminal-session.ts +536 -0
- package/src/lib/tui-log.ts +60 -0
- package/src/lib/xdg-log.ts +10 -4
- package/src/shared/provider-env-vars.ts +2 -3
- package/src/__tests__/orphan-detection.test.ts +0 -214
package/src/commands/terminal.ts
CHANGED
|
@@ -11,19 +11,17 @@
|
|
|
11
11
|
* vellum terminal list — List tmux sessions
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
findAssistantByName,
|
|
16
|
-
loadLatestAssistant,
|
|
17
|
-
resolveCloud,
|
|
18
|
-
} from "../lib/assistant-config.js";
|
|
19
|
-
import { getPlatformUrl, readPlatformToken } from "../lib/platform-client.js";
|
|
20
14
|
import {
|
|
21
15
|
closeTerminalSession,
|
|
22
16
|
createTerminalSession,
|
|
23
|
-
resizeTerminalSession,
|
|
24
17
|
sendTerminalInput,
|
|
25
18
|
subscribeTerminalEvents,
|
|
26
19
|
} from "../lib/terminal-client.js";
|
|
20
|
+
import {
|
|
21
|
+
interactiveSession,
|
|
22
|
+
resolveManagedAssistant,
|
|
23
|
+
} from "../lib/terminal-session.js";
|
|
24
|
+
import type { ResolvedManagedAssistant } from "../lib/terminal-session.js";
|
|
27
25
|
|
|
28
26
|
// ---------------------------------------------------------------------------
|
|
29
27
|
// Helpers
|
|
@@ -60,223 +58,13 @@ function printHelp(): void {
|
|
|
60
58
|
console.log(" vellum terminal --assistant my-assistant");
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
interface ResolvedAssistant {
|
|
64
|
-
assistantId: string;
|
|
65
|
-
token: string;
|
|
66
|
-
platformUrl: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function resolveAssistant(nameArg?: string): ResolvedAssistant {
|
|
70
|
-
const entry = nameArg ? findAssistantByName(nameArg) : loadLatestAssistant();
|
|
71
|
-
|
|
72
|
-
if (!entry) {
|
|
73
|
-
if (nameArg) {
|
|
74
|
-
console.error(`No assistant instance found with name '${nameArg}'.`);
|
|
75
|
-
} else {
|
|
76
|
-
console.error("No assistant instance found. Run `vellum hatch` first.");
|
|
77
|
-
}
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const cloud = resolveCloud(entry);
|
|
82
|
-
if (cloud !== "vellum") {
|
|
83
|
-
if (cloud === "local") {
|
|
84
|
-
console.error(
|
|
85
|
-
"This assistant runs locally on your machine. You can access it directly.",
|
|
86
|
-
);
|
|
87
|
-
} else if (cloud === "docker") {
|
|
88
|
-
console.error(
|
|
89
|
-
`Use 'vellum exec -it -- /bin/bash' or 'vellum ssh' for ${cloud} instances.`,
|
|
90
|
-
);
|
|
91
|
-
} else {
|
|
92
|
-
console.error(
|
|
93
|
-
`'vellum terminal' is for managed (cloud-hosted) assistants. This assistant uses '${cloud}'.`,
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const token = readPlatformToken();
|
|
100
|
-
if (!token) {
|
|
101
|
-
console.error(
|
|
102
|
-
"Not logged in. Run `vellum login` first to authenticate with the platform.",
|
|
103
|
-
);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
assistantId: entry.assistantId,
|
|
109
|
-
token,
|
|
110
|
-
platformUrl: getPlatformUrl(),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
// Interactive session
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
|
|
118
|
-
async function interactiveSession(
|
|
119
|
-
assistant: ResolvedAssistant,
|
|
120
|
-
initialCommand?: string,
|
|
121
|
-
): Promise<void> {
|
|
122
|
-
const cols = process.stdout.columns || 80;
|
|
123
|
-
const rows = process.stdout.rows || 24;
|
|
124
|
-
|
|
125
|
-
console.error(`\x1b[2m🔗 Connecting to ${assistant.assistantId}...\x1b[0m`);
|
|
126
|
-
|
|
127
|
-
const { session_id: sessionId } = await createTerminalSession(
|
|
128
|
-
assistant.token,
|
|
129
|
-
assistant.assistantId,
|
|
130
|
-
cols,
|
|
131
|
-
rows,
|
|
132
|
-
assistant.platformUrl,
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
// --- TTY raw mode setup ---
|
|
136
|
-
const wasRaw = process.stdin.isRaw;
|
|
137
|
-
if (process.stdin.isTTY) {
|
|
138
|
-
process.stdin.setRawMode(true);
|
|
139
|
-
}
|
|
140
|
-
process.stdin.resume();
|
|
141
|
-
process.stdin.setEncoding("utf-8");
|
|
142
|
-
|
|
143
|
-
// Abort controller for the SSE stream
|
|
144
|
-
const abortController = new AbortController();
|
|
145
|
-
let exiting = false;
|
|
146
|
-
|
|
147
|
-
// --- Cleanup function (idempotent) ---
|
|
148
|
-
async function cleanup(): Promise<void> {
|
|
149
|
-
if (exiting) return;
|
|
150
|
-
exiting = true;
|
|
151
|
-
|
|
152
|
-
// Restore tty
|
|
153
|
-
if (process.stdin.isTTY) {
|
|
154
|
-
process.stdin.setRawMode(wasRaw ?? false);
|
|
155
|
-
}
|
|
156
|
-
process.stdin.pause();
|
|
157
|
-
|
|
158
|
-
// Abort SSE stream
|
|
159
|
-
abortController.abort();
|
|
160
|
-
|
|
161
|
-
// Close remote session (best-effort)
|
|
162
|
-
try {
|
|
163
|
-
await closeTerminalSession(
|
|
164
|
-
assistant.token,
|
|
165
|
-
assistant.assistantId,
|
|
166
|
-
sessionId,
|
|
167
|
-
assistant.platformUrl,
|
|
168
|
-
);
|
|
169
|
-
} catch {
|
|
170
|
-
// Best-effort cleanup
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// --- Signal handlers ---
|
|
175
|
-
const onSigInt = () => {
|
|
176
|
-
cleanup().then(() => process.exit(0));
|
|
177
|
-
};
|
|
178
|
-
const onSigTerm = () => {
|
|
179
|
-
cleanup().then(() => process.exit(0));
|
|
180
|
-
};
|
|
181
|
-
process.on("SIGINT", onSigInt);
|
|
182
|
-
process.on("SIGTERM", onSigTerm);
|
|
183
|
-
|
|
184
|
-
// --- SIGWINCH (terminal resize) ---
|
|
185
|
-
const onResize = () => {
|
|
186
|
-
const newCols = process.stdout.columns || 80;
|
|
187
|
-
const newRows = process.stdout.rows || 24;
|
|
188
|
-
resizeTerminalSession(
|
|
189
|
-
assistant.token,
|
|
190
|
-
assistant.assistantId,
|
|
191
|
-
sessionId,
|
|
192
|
-
newCols,
|
|
193
|
-
newRows,
|
|
194
|
-
assistant.platformUrl,
|
|
195
|
-
).catch(() => {
|
|
196
|
-
// Resize failures are non-fatal
|
|
197
|
-
});
|
|
198
|
-
};
|
|
199
|
-
process.stdout.on("resize", onResize);
|
|
200
|
-
|
|
201
|
-
// --- Input: stdin → remote ---
|
|
202
|
-
let inputBuffer = "";
|
|
203
|
-
let inputTimer: ReturnType<typeof setTimeout> | null = null;
|
|
204
|
-
const INPUT_DEBOUNCE_MS = 30;
|
|
205
|
-
|
|
206
|
-
function flushInput(): void {
|
|
207
|
-
if (inputBuffer.length === 0) return;
|
|
208
|
-
const data = inputBuffer;
|
|
209
|
-
inputBuffer = "";
|
|
210
|
-
sendTerminalInput(
|
|
211
|
-
assistant.token,
|
|
212
|
-
assistant.assistantId,
|
|
213
|
-
sessionId,
|
|
214
|
-
data,
|
|
215
|
-
assistant.platformUrl,
|
|
216
|
-
).catch((err) => {
|
|
217
|
-
if (!exiting) {
|
|
218
|
-
console.error(`\r\nInput error: ${err.message}\r\n`);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
process.stdin.on("data", (chunk: string) => {
|
|
224
|
-
if (exiting) return;
|
|
225
|
-
inputBuffer += chunk;
|
|
226
|
-
if (inputTimer) clearTimeout(inputTimer);
|
|
227
|
-
inputTimer = setTimeout(flushInput, INPUT_DEBOUNCE_MS);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// --- Send initial command (for `attach` subcommand) ---
|
|
231
|
-
if (initialCommand) {
|
|
232
|
-
// Brief delay to let the shell initialize
|
|
233
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
234
|
-
await sendTerminalInput(
|
|
235
|
-
assistant.token,
|
|
236
|
-
assistant.assistantId,
|
|
237
|
-
sessionId,
|
|
238
|
-
initialCommand + "\r",
|
|
239
|
-
assistant.platformUrl,
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// --- Output: remote SSE → stdout ---
|
|
244
|
-
try {
|
|
245
|
-
for await (const event of subscribeTerminalEvents(
|
|
246
|
-
assistant.token,
|
|
247
|
-
assistant.assistantId,
|
|
248
|
-
sessionId,
|
|
249
|
-
assistant.platformUrl,
|
|
250
|
-
abortController.signal,
|
|
251
|
-
)) {
|
|
252
|
-
if (exiting) break;
|
|
253
|
-
// Decode base64 output and write raw bytes to stdout
|
|
254
|
-
const bytes = Buffer.from(event.data, "base64");
|
|
255
|
-
process.stdout.write(bytes);
|
|
256
|
-
}
|
|
257
|
-
} catch (err) {
|
|
258
|
-
if (!exiting) {
|
|
259
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
260
|
-
// AbortError is expected on cleanup
|
|
261
|
-
if (!msg.includes("abort")) {
|
|
262
|
-
console.error(`\r\nConnection lost: ${msg}\r\n`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
} finally {
|
|
266
|
-
await cleanup();
|
|
267
|
-
|
|
268
|
-
// Remove listeners
|
|
269
|
-
process.off("SIGINT", onSigInt);
|
|
270
|
-
process.off("SIGTERM", onSigTerm);
|
|
271
|
-
process.stdout.off("resize", onResize);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
61
|
// ---------------------------------------------------------------------------
|
|
276
62
|
// List tmux sessions
|
|
277
63
|
// ---------------------------------------------------------------------------
|
|
278
64
|
|
|
279
|
-
async function listTmuxSessions(
|
|
65
|
+
async function listTmuxSessions(
|
|
66
|
+
assistant: ResolvedManagedAssistant,
|
|
67
|
+
): Promise<void> {
|
|
280
68
|
const cols = 120;
|
|
281
69
|
const rows = 24;
|
|
282
70
|
|
|
@@ -411,7 +199,7 @@ export async function terminal(): Promise<void> {
|
|
|
411
199
|
}
|
|
412
200
|
}
|
|
413
201
|
|
|
414
|
-
const assistant =
|
|
202
|
+
const assistant = resolveManagedAssistant(assistantName);
|
|
415
203
|
|
|
416
204
|
if (subcommand === "list") {
|
|
417
205
|
await listTmuxSessions(assistant);
|
package/src/commands/tunnel.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
findAssistantByName,
|
|
3
|
-
loadLatestAssistant,
|
|
4
|
-
} from "../lib/assistant-config";
|
|
1
|
+
import { resolveAssistant } from "../lib/assistant-config";
|
|
5
2
|
import { runNgrokTunnel } from "../lib/ngrok";
|
|
6
3
|
|
|
7
4
|
const VALID_PROVIDERS = ["vellum", "ngrok", "cloudflare", "tailscale"] as const;
|
|
@@ -63,9 +60,7 @@ function parseArgs(): TunnelArgs {
|
|
|
63
60
|
export async function tunnel(): Promise<void> {
|
|
64
61
|
const { assistantName, provider } = parseArgs();
|
|
65
62
|
|
|
66
|
-
const entry = assistantName
|
|
67
|
-
? findAssistantByName(assistantName)
|
|
68
|
-
: loadLatestAssistant();
|
|
63
|
+
const entry = resolveAssistant(assistantName ?? undefined);
|
|
69
64
|
|
|
70
65
|
if (!entry) {
|
|
71
66
|
if (assistantName) {
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "crypto";
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
2
3
|
|
|
3
4
|
import cliPkg from "../../package.json";
|
|
4
5
|
|
|
@@ -16,7 +17,10 @@ import {
|
|
|
16
17
|
startContainers,
|
|
17
18
|
stopContainers,
|
|
18
19
|
} from "../lib/docker";
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
fetchLatestStableVersion,
|
|
22
|
+
resolveImageRefs,
|
|
23
|
+
} from "../lib/platform-releases";
|
|
20
24
|
import {
|
|
21
25
|
authHeaders,
|
|
22
26
|
getPlatformUrl,
|
|
@@ -47,6 +51,7 @@ import { compareVersions } from "../lib/version-compat.js";
|
|
|
47
51
|
interface UpgradeArgs {
|
|
48
52
|
name: string | null;
|
|
49
53
|
version: string | null;
|
|
54
|
+
latest: boolean;
|
|
50
55
|
prepare: boolean;
|
|
51
56
|
finalize: boolean;
|
|
52
57
|
}
|
|
@@ -55,6 +60,7 @@ function parseArgs(): UpgradeArgs {
|
|
|
55
60
|
const args = process.argv.slice(3);
|
|
56
61
|
let name: string | null = null;
|
|
57
62
|
let version: string | null = null;
|
|
63
|
+
let latest = false;
|
|
58
64
|
let prepare = false;
|
|
59
65
|
let finalize = false;
|
|
60
66
|
|
|
@@ -73,7 +79,10 @@ function parseArgs(): UpgradeArgs {
|
|
|
73
79
|
console.log("");
|
|
74
80
|
console.log("Options:");
|
|
75
81
|
console.log(
|
|
76
|
-
" --version <version> Target version to upgrade to (default:
|
|
82
|
+
" --version <version> Target version to upgrade to (default: CLI version)",
|
|
83
|
+
);
|
|
84
|
+
console.log(
|
|
85
|
+
" --latest Upgrade to the latest stable release, updating the CLI first if needed",
|
|
77
86
|
);
|
|
78
87
|
console.log(
|
|
79
88
|
" --prepare Run pre-upgrade steps only (backup, notify) without swapping versions",
|
|
@@ -84,7 +93,10 @@ function parseArgs(): UpgradeArgs {
|
|
|
84
93
|
console.log("");
|
|
85
94
|
console.log("Examples:");
|
|
86
95
|
console.log(
|
|
87
|
-
" vellum upgrade # Upgrade the active assistant to the
|
|
96
|
+
" vellum upgrade # Upgrade the active assistant to the CLI's version",
|
|
97
|
+
);
|
|
98
|
+
console.log(
|
|
99
|
+
" vellum upgrade --latest # Upgrade CLI + assistant to the latest stable release",
|
|
88
100
|
);
|
|
89
101
|
console.log(
|
|
90
102
|
" vellum upgrade my-assistant # Upgrade a specific assistant by name",
|
|
@@ -102,6 +114,8 @@ function parseArgs(): UpgradeArgs {
|
|
|
102
114
|
}
|
|
103
115
|
version = next;
|
|
104
116
|
i++;
|
|
117
|
+
} else if (arg === "--latest") {
|
|
118
|
+
latest = true;
|
|
105
119
|
} else if (arg === "--prepare") {
|
|
106
120
|
prepare = true;
|
|
107
121
|
} else if (arg === "--finalize") {
|
|
@@ -121,7 +135,13 @@ function parseArgs(): UpgradeArgs {
|
|
|
121
135
|
process.exit(1);
|
|
122
136
|
}
|
|
123
137
|
|
|
124
|
-
|
|
138
|
+
if (latest && version) {
|
|
139
|
+
console.error("Error: --latest and --version are mutually exclusive.");
|
|
140
|
+
emitCliError("UNKNOWN", "--latest and --version are mutually exclusive");
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { name, version, latest, prepare, finalize };
|
|
125
145
|
}
|
|
126
146
|
|
|
127
147
|
function resolveCloud(entry: AssistantEntry): string {
|
|
@@ -867,8 +887,80 @@ async function upgradeFinalize(
|
|
|
867
887
|
);
|
|
868
888
|
}
|
|
869
889
|
|
|
890
|
+
/**
|
|
891
|
+
* When `--latest` is passed, resolve the latest stable version from the
|
|
892
|
+
* platform API. If the running CLI is older than that version, self-update
|
|
893
|
+
* the CLI via `bun install -g` and re-exec so the new CLI's upgrade logic
|
|
894
|
+
* (and its cliPkg.version) drives the rest of the upgrade.
|
|
895
|
+
*
|
|
896
|
+
* Returns the resolved latest version string (e.g. "v0.7.0") for callers
|
|
897
|
+
* that need it. If the CLI was updated and re-exec'd, this function never
|
|
898
|
+
* returns — the process is replaced.
|
|
899
|
+
*/
|
|
900
|
+
async function resolveLatestAndMaybeSelfUpdate(
|
|
901
|
+
name: string | null,
|
|
902
|
+
): Promise<string> {
|
|
903
|
+
console.log("🔍 Fetching latest stable release...");
|
|
904
|
+
const latestVersion = await fetchLatestStableVersion();
|
|
905
|
+
if (!latestVersion) {
|
|
906
|
+
console.error(
|
|
907
|
+
"Error: Could not determine the latest stable release from the platform API.",
|
|
908
|
+
);
|
|
909
|
+
emitCliError(
|
|
910
|
+
"UNKNOWN",
|
|
911
|
+
"Could not determine the latest stable release from the platform API",
|
|
912
|
+
);
|
|
913
|
+
process.exit(1);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const latestTag = latestVersion.startsWith("v")
|
|
917
|
+
? latestVersion
|
|
918
|
+
: `v${latestVersion}`;
|
|
919
|
+
const currentTag = cliPkg.version ? `v${cliPkg.version}` : null;
|
|
920
|
+
|
|
921
|
+
console.log(` Latest stable: ${latestTag}`);
|
|
922
|
+
console.log(` CLI version: ${currentTag ?? "unknown"}\n`);
|
|
923
|
+
|
|
924
|
+
// Check if the CLI needs updating
|
|
925
|
+
const cmp = currentTag ? compareVersions(latestTag, currentTag) : null;
|
|
926
|
+
if (cmp !== null && cmp > 0) {
|
|
927
|
+
console.log(`🔄 Updating CLI to ${latestTag}...`);
|
|
928
|
+
const installResult = spawnSync(
|
|
929
|
+
"bun",
|
|
930
|
+
["install", "-g", `vellum@${latestVersion}`],
|
|
931
|
+
{ stdio: "inherit" },
|
|
932
|
+
);
|
|
933
|
+
if (installResult.error || installResult.status !== 0) {
|
|
934
|
+
const detail =
|
|
935
|
+
installResult.error?.message ?? `exited with code ${installResult.status}`;
|
|
936
|
+
console.error(`\n❌ CLI self-update failed: ${detail}`);
|
|
937
|
+
emitCliError("CLI_UPDATE_FAILED", "CLI self-update failed", detail);
|
|
938
|
+
process.exit(1);
|
|
939
|
+
}
|
|
940
|
+
console.log(`✅ CLI updated to ${latestTag}\n`);
|
|
941
|
+
|
|
942
|
+
// Re-exec with the updated CLI. Pass --version instead of --latest
|
|
943
|
+
// to avoid re-fetching and to prevent infinite re-exec loops.
|
|
944
|
+
const reexecArgs = ["upgrade"];
|
|
945
|
+
if (name) reexecArgs.push(name);
|
|
946
|
+
reexecArgs.push("--version", latestTag);
|
|
947
|
+
|
|
948
|
+
console.log(`🚀 Re-running upgrade with updated CLI...\n`);
|
|
949
|
+
const reexecResult = spawnSync("vellum", reexecArgs, {
|
|
950
|
+
stdio: "inherit",
|
|
951
|
+
});
|
|
952
|
+
process.exit(reexecResult.status ?? 1);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (cmp !== null && cmp === 0) {
|
|
956
|
+
console.log(`✅ CLI is already on the latest version (${latestTag})\n`);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
return latestTag;
|
|
960
|
+
}
|
|
961
|
+
|
|
870
962
|
export async function upgrade(): Promise<void> {
|
|
871
|
-
const { name, version, prepare, finalize } = parseArgs();
|
|
963
|
+
const { name, version, latest, prepare, finalize } = parseArgs();
|
|
872
964
|
const entry = resolveTargetAssistant(name);
|
|
873
965
|
|
|
874
966
|
if (prepare) {
|
|
@@ -881,16 +973,25 @@ export async function upgrade(): Promise<void> {
|
|
|
881
973
|
return;
|
|
882
974
|
}
|
|
883
975
|
|
|
976
|
+
// When --latest is passed, resolve the target from the platform API and
|
|
977
|
+
// self-update the CLI if it's behind. The resolved version is then used
|
|
978
|
+
// as the explicit target for the rest of the upgrade flow.
|
|
979
|
+
let effectiveVersion = version;
|
|
980
|
+
if (latest) {
|
|
981
|
+
const latestTag = await resolveLatestAndMaybeSelfUpdate(name);
|
|
982
|
+
effectiveVersion = latestTag;
|
|
983
|
+
}
|
|
984
|
+
|
|
884
985
|
const cloud = resolveCloud(entry);
|
|
885
986
|
|
|
886
987
|
try {
|
|
887
988
|
if (cloud === "docker") {
|
|
888
|
-
await upgradeDocker(entry,
|
|
989
|
+
await upgradeDocker(entry, effectiveVersion);
|
|
889
990
|
return;
|
|
890
991
|
}
|
|
891
992
|
|
|
892
993
|
if (cloud === "vellum") {
|
|
893
|
-
await upgradePlatform(entry,
|
|
994
|
+
await upgradePlatform(entry, effectiveVersion);
|
|
894
995
|
return;
|
|
895
996
|
}
|
|
896
997
|
} catch (err) {
|
package/src/commands/wake.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
getDaemonPidPath,
|
|
5
6
|
resolveTargetAssistant,
|
|
6
7
|
saveAssistantEntry,
|
|
7
8
|
} from "../lib/assistant-config.js";
|
|
@@ -82,7 +83,7 @@ export async function wake(): Promise<void> {
|
|
|
82
83
|
}
|
|
83
84
|
const resources = entry.resources;
|
|
84
85
|
|
|
85
|
-
const pidFile = resources
|
|
86
|
+
const pidFile = getDaemonPidPath(resources);
|
|
86
87
|
|
|
87
88
|
// Check if daemon is already running
|
|
88
89
|
let daemonRunning = false;
|