@vellumai/cli 0.6.0 → 0.6.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/package.json +1 -1
- package/src/__tests__/teleport.test.ts +4 -21
- package/src/__tests__/version-compat.test.ts +206 -0
- package/src/commands/backup.ts +1 -15
- package/src/commands/events.ts +146 -0
- package/src/commands/hatch.ts +1 -14
- package/src/commands/message.ts +105 -0
- package/src/commands/restore.ts +1 -21
- package/src/commands/retire.ts +2 -7
- package/src/commands/rollback.ts +8 -37
- package/src/commands/teleport.ts +18 -109
- package/src/commands/upgrade.ts +43 -43
- package/src/index.ts +6 -0
- package/src/lib/arg-utils.ts +13 -0
- package/src/lib/assistant-client.ts +228 -0
- package/src/lib/docker.ts +141 -49
- package/src/lib/hatch-local.ts +5 -2
- package/src/lib/health-check.ts +3 -8
- package/src/lib/ngrok.ts +11 -1
- package/src/lib/platform-client.ts +77 -56
- package/src/lib/upgrade-lifecycle.ts +5 -15
- package/src/lib/version-compat.ts +67 -5
package/package.json
CHANGED
|
@@ -64,7 +64,6 @@ mock.module("../lib/guardian-token.js", () => ({
|
|
|
64
64
|
}));
|
|
65
65
|
|
|
66
66
|
const readPlatformTokenMock = mock((): string | null => "platform-token");
|
|
67
|
-
const fetchOrganizationIdMock = mock(async () => "org-123");
|
|
68
67
|
const getPlatformUrlMock = mock(() => "https://platform.vellum.ai");
|
|
69
68
|
const hatchAssistantMock = mock(async () => ({
|
|
70
69
|
id: "platform-new-id",
|
|
@@ -142,7 +141,6 @@ const platformImportBundleFromGcsMock = mock(async () => ({
|
|
|
142
141
|
|
|
143
142
|
mock.module("../lib/platform-client.js", () => ({
|
|
144
143
|
readPlatformToken: readPlatformTokenMock,
|
|
145
|
-
fetchOrganizationId: fetchOrganizationIdMock,
|
|
146
144
|
getPlatformUrl: getPlatformUrlMock,
|
|
147
145
|
hatchAssistant: hatchAssistantMock,
|
|
148
146
|
platformInitiateExport: platformInitiateExportMock,
|
|
@@ -240,8 +238,6 @@ beforeEach(() => {
|
|
|
240
238
|
|
|
241
239
|
readPlatformTokenMock.mockReset();
|
|
242
240
|
readPlatformTokenMock.mockReturnValue("platform-token");
|
|
243
|
-
fetchOrganizationIdMock.mockReset();
|
|
244
|
-
fetchOrganizationIdMock.mockResolvedValue("org-123");
|
|
245
241
|
getPlatformUrlMock.mockReset();
|
|
246
242
|
getPlatformUrlMock.mockReturnValue("https://platform.vellum.ai");
|
|
247
243
|
hatchAssistantMock.mockReset();
|
|
@@ -757,10 +753,7 @@ describe("resolveOrHatchTarget", () => {
|
|
|
757
753
|
findAssistantByNameMock.mockReturnValue(null);
|
|
758
754
|
|
|
759
755
|
const result = await resolveOrHatchTarget("platform", "nonexistent");
|
|
760
|
-
expect(hatchAssistantMock).toHaveBeenCalledWith(
|
|
761
|
-
"platform-token",
|
|
762
|
-
"org-123",
|
|
763
|
-
);
|
|
756
|
+
expect(hatchAssistantMock).toHaveBeenCalledWith("platform-token");
|
|
764
757
|
expect(saveAssistantEntryMock).toHaveBeenCalledWith(
|
|
765
758
|
expect.objectContaining({
|
|
766
759
|
assistantId: "platform-new-id",
|
|
@@ -1099,7 +1092,6 @@ describe("signed-URL upload flow", () => {
|
|
|
1099
1092
|
expect(platformImportBundleFromGcsMock).toHaveBeenCalledWith(
|
|
1100
1093
|
"bundle-key-123",
|
|
1101
1094
|
"platform-token",
|
|
1102
|
-
"org-123",
|
|
1103
1095
|
"https://platform.vellum.ai",
|
|
1104
1096
|
);
|
|
1105
1097
|
// Inline import should NOT be called
|
|
@@ -1145,7 +1137,6 @@ describe("signed-URL upload flow", () => {
|
|
|
1145
1137
|
expect(platformImportPreflightFromGcsMock).toHaveBeenCalledWith(
|
|
1146
1138
|
"bundle-key-123",
|
|
1147
1139
|
"platform-token",
|
|
1148
|
-
"org-123",
|
|
1149
1140
|
"https://platform.vellum.ai",
|
|
1150
1141
|
);
|
|
1151
1142
|
// Inline preflight should NOT be called
|
|
@@ -1284,7 +1275,7 @@ describe("signed-URL upload flow", () => {
|
|
|
1284
1275
|
// ---------------------------------------------------------------------------
|
|
1285
1276
|
|
|
1286
1277
|
describe("platform teleport org ID and reordered flow", () => {
|
|
1287
|
-
test("hatchAssistant is called
|
|
1278
|
+
test("hatchAssistant is called without orgId (authHeaders fetches it internally)", async () => {
|
|
1288
1279
|
setArgv("--from", "my-local", "--platform");
|
|
1289
1280
|
|
|
1290
1281
|
const localEntry = makeEntry("my-local", { cloud: "local" });
|
|
@@ -1294,21 +1285,14 @@ describe("platform teleport org ID and reordered flow", () => {
|
|
|
1294
1285
|
return null;
|
|
1295
1286
|
});
|
|
1296
1287
|
|
|
1297
|
-
fetchOrganizationIdMock.mockResolvedValue("test-org-456");
|
|
1298
|
-
|
|
1299
1288
|
const originalFetch = globalThis.fetch;
|
|
1300
1289
|
globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
|
|
1301
1290
|
|
|
1302
1291
|
try {
|
|
1303
1292
|
await teleport();
|
|
1304
1293
|
|
|
1305
|
-
//
|
|
1306
|
-
expect(
|
|
1307
|
-
// hatchAssistant must be called with (token, orgId)
|
|
1308
|
-
expect(hatchAssistantMock).toHaveBeenCalledWith(
|
|
1309
|
-
"platform-token",
|
|
1310
|
-
"test-org-456",
|
|
1311
|
-
);
|
|
1294
|
+
// hatchAssistant should be called with just the token (orgId is resolved internally by authHeaders)
|
|
1295
|
+
expect(hatchAssistantMock).toHaveBeenCalledWith("platform-token");
|
|
1312
1296
|
} finally {
|
|
1313
1297
|
globalThis.fetch = originalFetch;
|
|
1314
1298
|
}
|
|
@@ -1432,7 +1416,6 @@ describe("platform teleport org ID and reordered flow", () => {
|
|
|
1432
1416
|
"pre-uploaded-key-789",
|
|
1433
1417
|
"platform-token",
|
|
1434
1418
|
expect.any(String),
|
|
1435
|
-
expect.any(String),
|
|
1436
1419
|
);
|
|
1437
1420
|
// Inline import should NOT be used since signed upload succeeded
|
|
1438
1421
|
expect(platformImportBundleMock).not.toHaveBeenCalled();
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
parseVersion,
|
|
5
|
+
compareVersions,
|
|
6
|
+
isVersionCompatible,
|
|
7
|
+
} from "../lib/version-compat.js";
|
|
8
|
+
|
|
9
|
+
describe("parseVersion", () => {
|
|
10
|
+
test("parses basic semver", () => {
|
|
11
|
+
expect(parseVersion("1.2.3")).toEqual({
|
|
12
|
+
major: 1,
|
|
13
|
+
minor: 2,
|
|
14
|
+
patch: 3,
|
|
15
|
+
pre: null,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("strips v prefix", () => {
|
|
20
|
+
expect(parseVersion("v1.2.3")).toEqual({
|
|
21
|
+
major: 1,
|
|
22
|
+
minor: 2,
|
|
23
|
+
patch: 3,
|
|
24
|
+
pre: null,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("strips V prefix", () => {
|
|
29
|
+
expect(parseVersion("V1.2.3")).toEqual({
|
|
30
|
+
major: 1,
|
|
31
|
+
minor: 2,
|
|
32
|
+
patch: 3,
|
|
33
|
+
pre: null,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("defaults missing patch to 0", () => {
|
|
38
|
+
expect(parseVersion("1.2")).toEqual({
|
|
39
|
+
major: 1,
|
|
40
|
+
minor: 2,
|
|
41
|
+
patch: 0,
|
|
42
|
+
pre: null,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("captures pre-release suffix", () => {
|
|
47
|
+
expect(parseVersion("0.6.0-staging.5")).toEqual({
|
|
48
|
+
major: 0,
|
|
49
|
+
minor: 6,
|
|
50
|
+
patch: 0,
|
|
51
|
+
pre: "staging.5",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("captures pre-release with v prefix", () => {
|
|
56
|
+
expect(parseVersion("v0.6.0-staging.1")).toEqual({
|
|
57
|
+
major: 0,
|
|
58
|
+
minor: 6,
|
|
59
|
+
patch: 0,
|
|
60
|
+
pre: "staging.1",
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("captures hyphenated pre-release suffix", () => {
|
|
65
|
+
expect(parseVersion("1.0.0-pre-release-1")).toEqual({
|
|
66
|
+
major: 1,
|
|
67
|
+
minor: 0,
|
|
68
|
+
patch: 0,
|
|
69
|
+
pre: "pre-release-1",
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("returns null for single segment", () => {
|
|
74
|
+
expect(parseVersion("1")).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("returns null for non-numeric segments", () => {
|
|
78
|
+
expect(parseVersion("abc.def")).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("returns null for empty string", () => {
|
|
82
|
+
expect(parseVersion("")).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("compareVersions", () => {
|
|
87
|
+
// ── Basic numeric comparison ──────────────────────────────────────
|
|
88
|
+
test("equal versions return 0", () => {
|
|
89
|
+
expect(compareVersions("1.2.3", "1.2.3")).toBe(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("higher major returns positive", () => {
|
|
93
|
+
expect(compareVersions("2.0.0", "1.0.0")).toBeGreaterThan(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("lower major returns negative", () => {
|
|
97
|
+
expect(compareVersions("1.0.0", "2.0.0")).toBeLessThan(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("higher minor returns positive", () => {
|
|
101
|
+
expect(compareVersions("1.3.0", "1.2.0")).toBeGreaterThan(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("higher patch returns positive", () => {
|
|
105
|
+
expect(compareVersions("1.2.4", "1.2.3")).toBeGreaterThan(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ── v prefix handling ─────────────────────────────────────────────
|
|
109
|
+
test("strips v prefix for comparison", () => {
|
|
110
|
+
expect(compareVersions("v1.2.3", "1.2.3")).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── Pre-release vs release ────────────────────────────────────────
|
|
114
|
+
test("pre-release sorts lower than release", () => {
|
|
115
|
+
expect(compareVersions("0.6.0-staging.1", "0.6.0")).toBeLessThan(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("release sorts higher than pre-release", () => {
|
|
119
|
+
expect(compareVersions("0.6.0", "0.6.0-staging.1")).toBeGreaterThan(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ── Pre-release numeric comparison ────────────────────────────────
|
|
123
|
+
test("staging.1 < staging.2", () => {
|
|
124
|
+
expect(compareVersions("0.6.0-staging.1", "0.6.0-staging.2")).toBeLessThan(
|
|
125
|
+
0,
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("staging.10 > staging.2 (numeric, not lexical)", () => {
|
|
130
|
+
expect(
|
|
131
|
+
compareVersions("0.6.0-staging.10", "0.6.0-staging.2"),
|
|
132
|
+
).toBeGreaterThan(0);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ── Pre-release lexical comparison ────────────────────────────────
|
|
136
|
+
test("alpha < beta (lexical)", () => {
|
|
137
|
+
expect(compareVersions("1.0.0-alpha", "1.0.0-beta")).toBeLessThan(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ── Mixed numeric vs non-numeric per §11.4.4 ─────────────────────
|
|
141
|
+
test("numeric identifier sorts lower than non-numeric", () => {
|
|
142
|
+
expect(compareVersions("1.0.0-1", "1.0.0-alpha")).toBeLessThan(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ── Fewer pre-release identifiers sorts earlier ───────────────────
|
|
146
|
+
test("fewer pre-release identifiers sorts earlier", () => {
|
|
147
|
+
expect(compareVersions("1.0.0-alpha", "1.0.0-alpha.1")).toBeLessThan(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ── Returns null for unparseable input ────────────────────────────
|
|
151
|
+
test("returns null if first version is unparseable", () => {
|
|
152
|
+
expect(compareVersions("bad", "1.0.0")).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("returns null if second version is unparseable", () => {
|
|
156
|
+
expect(compareVersions("1.0.0", "bad")).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// ── Different major.minor.patch trumps pre-release ────────────────
|
|
160
|
+
test("higher patch wins regardless of pre-release", () => {
|
|
161
|
+
expect(compareVersions("0.6.1", "0.6.0-staging.99")).toBeGreaterThan(0);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ── Sort integration ──────────────────────────────────────────────
|
|
165
|
+
test("Array.sort produces correct semver order", () => {
|
|
166
|
+
const versions = [
|
|
167
|
+
"0.6.0",
|
|
168
|
+
"0.6.0-staging.2",
|
|
169
|
+
"0.5.9",
|
|
170
|
+
"0.6.0-staging.10",
|
|
171
|
+
"v0.6.0-staging.1",
|
|
172
|
+
"0.6.1",
|
|
173
|
+
];
|
|
174
|
+
const sorted = [...versions].sort((a, b) => compareVersions(a, b) ?? 0);
|
|
175
|
+
expect(sorted).toEqual([
|
|
176
|
+
"0.5.9",
|
|
177
|
+
"v0.6.0-staging.1",
|
|
178
|
+
"0.6.0-staging.2",
|
|
179
|
+
"0.6.0-staging.10",
|
|
180
|
+
"0.6.0",
|
|
181
|
+
"0.6.1",
|
|
182
|
+
]);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("isVersionCompatible", () => {
|
|
187
|
+
test("same major.minor are compatible", () => {
|
|
188
|
+
expect(isVersionCompatible("1.2.3", "1.2.5")).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("different minor are incompatible", () => {
|
|
192
|
+
expect(isVersionCompatible("1.2.3", "1.3.0")).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("different major are incompatible", () => {
|
|
196
|
+
expect(isVersionCompatible("1.2.3", "2.2.3")).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("pre-release on same major.minor is compatible", () => {
|
|
200
|
+
expect(isVersionCompatible("0.6.0-staging.5", "0.6.0")).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("returns false for unparseable input", () => {
|
|
204
|
+
expect(isVersionCompatible("bad", "1.0.0")).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
});
|
package/src/commands/backup.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { getBackupsDir, formatSize } from "../lib/backup-ops.js";
|
|
|
6
6
|
import { loadGuardianToken, leaseGuardianToken } from "../lib/guardian-token";
|
|
7
7
|
import {
|
|
8
8
|
readPlatformToken,
|
|
9
|
-
fetchOrganizationId,
|
|
10
9
|
platformInitiateExport,
|
|
11
10
|
platformPollExportStatus,
|
|
12
11
|
platformDownloadExport,
|
|
@@ -196,24 +195,11 @@ async function backupPlatform(
|
|
|
196
195
|
process.exit(1);
|
|
197
196
|
}
|
|
198
197
|
|
|
199
|
-
let orgId: string;
|
|
200
|
-
try {
|
|
201
|
-
orgId = await fetchOrganizationId(token, runtimeUrl);
|
|
202
|
-
} catch (err) {
|
|
203
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
204
|
-
if (msg.includes("401") || msg.includes("403")) {
|
|
205
|
-
console.error("Authentication failed. Run 'vellum login' to refresh.");
|
|
206
|
-
process.exit(1);
|
|
207
|
-
}
|
|
208
|
-
throw err;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
198
|
// Step 2 — Initiate export job
|
|
212
199
|
let jobId: string;
|
|
213
200
|
try {
|
|
214
201
|
const result = await platformInitiateExport(
|
|
215
202
|
token,
|
|
216
|
-
orgId,
|
|
217
203
|
"CLI backup",
|
|
218
204
|
runtimeUrl,
|
|
219
205
|
);
|
|
@@ -244,7 +230,7 @@ async function backupPlatform(
|
|
|
244
230
|
while (Date.now() < deadline) {
|
|
245
231
|
let status: { status: string; downloadUrl?: string; error?: string };
|
|
246
232
|
try {
|
|
247
|
-
status = await platformPollExportStatus(jobId, token,
|
|
233
|
+
status = await platformPollExportStatus(jobId, token, runtimeUrl);
|
|
248
234
|
} catch (err) {
|
|
249
235
|
const msg = err instanceof Error ? err.message : String(err);
|
|
250
236
|
// Let non-transient errors (e.g. 404 "job not found") propagate immediately
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vellum events [assistant]`
|
|
3
|
+
*
|
|
4
|
+
* Subscribe to assistant events via the SSE endpoint and stream them
|
|
5
|
+
* to stdout. By default, events are rendered as human-readable
|
|
6
|
+
* markdown. Pass `--json` to emit one JSON object per event,
|
|
7
|
+
* separated by newlines.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { extractFlag } from "../lib/arg-utils.js";
|
|
11
|
+
import { AssistantClient } from "../lib/assistant-client.js";
|
|
12
|
+
|
|
13
|
+
function printUsage(): void {
|
|
14
|
+
console.log(`vellum events - Stream events from a running assistant
|
|
15
|
+
|
|
16
|
+
USAGE:
|
|
17
|
+
vellum events [assistant] [options]
|
|
18
|
+
|
|
19
|
+
ARGUMENTS:
|
|
20
|
+
[assistant] Instance name (default: active assistant)
|
|
21
|
+
|
|
22
|
+
OPTIONS:
|
|
23
|
+
--conversation-key <key> Scope to a single conversation
|
|
24
|
+
--json Output raw JSON events (one per line)
|
|
25
|
+
-h, --help Show this help message
|
|
26
|
+
|
|
27
|
+
EXAMPLES:
|
|
28
|
+
vellum events
|
|
29
|
+
vellum events my-assistant
|
|
30
|
+
vellum events --json
|
|
31
|
+
vellum events --conversation-key my-thread
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface AssistantEvent {
|
|
36
|
+
id: string;
|
|
37
|
+
assistantId: string;
|
|
38
|
+
conversationId?: string;
|
|
39
|
+
emittedAt: string;
|
|
40
|
+
message: {
|
|
41
|
+
type: string;
|
|
42
|
+
text?: string;
|
|
43
|
+
thinking?: string;
|
|
44
|
+
toolName?: string;
|
|
45
|
+
input?: Record<string, unknown>;
|
|
46
|
+
result?: string;
|
|
47
|
+
isError?: boolean;
|
|
48
|
+
content?: string;
|
|
49
|
+
message?: string;
|
|
50
|
+
chunk?: string;
|
|
51
|
+
conversationId?: string;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Render an event as human-readable markdown to stdout. */
|
|
57
|
+
function renderMarkdown(event: AssistantEvent): void {
|
|
58
|
+
const msg = event.message;
|
|
59
|
+
switch (msg.type) {
|
|
60
|
+
case "assistant_text_delta":
|
|
61
|
+
process.stdout.write(msg.text ?? "");
|
|
62
|
+
break;
|
|
63
|
+
case "assistant_thinking_delta":
|
|
64
|
+
process.stdout.write(msg.thinking ?? "");
|
|
65
|
+
break;
|
|
66
|
+
case "tool_use_start":
|
|
67
|
+
console.log(`\n> **Tool call:** \`${msg.toolName}\``);
|
|
68
|
+
if (msg.input && Object.keys(msg.input).length > 0) {
|
|
69
|
+
console.log("```json");
|
|
70
|
+
console.log(JSON.stringify(msg.input, null, 2));
|
|
71
|
+
console.log("```");
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
case "tool_input_delta":
|
|
75
|
+
process.stdout.write(msg.content ?? "");
|
|
76
|
+
break;
|
|
77
|
+
case "tool_result":
|
|
78
|
+
if (msg.isError) {
|
|
79
|
+
console.log(`\n> **Tool error** (\`${msg.toolName}\`): ${msg.result}`);
|
|
80
|
+
} else {
|
|
81
|
+
console.log(`\n> **Tool result** (\`${msg.toolName}\`): ${msg.result}`);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case "tool_output_chunk":
|
|
85
|
+
process.stdout.write(msg.chunk ?? "");
|
|
86
|
+
break;
|
|
87
|
+
case "message_complete":
|
|
88
|
+
console.log("\n");
|
|
89
|
+
break;
|
|
90
|
+
case "error":
|
|
91
|
+
console.error(`\n**Error:** ${msg.message}`);
|
|
92
|
+
break;
|
|
93
|
+
case "user_message_echo":
|
|
94
|
+
console.log(`\n**You:** ${msg.text}`);
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
// Silently skip events that don't have a markdown representation
|
|
98
|
+
// (e.g. heartbeat comments, activity states, etc.)
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function events(): Promise<void> {
|
|
104
|
+
const rawArgs = process.argv.slice(3);
|
|
105
|
+
|
|
106
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
107
|
+
printUsage();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const jsonOutput = rawArgs.includes("--json");
|
|
112
|
+
let args = rawArgs.filter((a) => a !== "--json");
|
|
113
|
+
|
|
114
|
+
const [conversationKey, filteredArgs] = extractFlag(
|
|
115
|
+
args,
|
|
116
|
+
"--conversation-key",
|
|
117
|
+
);
|
|
118
|
+
args = filteredArgs;
|
|
119
|
+
|
|
120
|
+
const assistantId = args[0];
|
|
121
|
+
|
|
122
|
+
const client = new AssistantClient({ assistantId });
|
|
123
|
+
|
|
124
|
+
// Use an explicit AbortController so we can clean up on SIGINT
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
process.on("SIGINT", () => {
|
|
127
|
+
controller.abort();
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const query: Record<string, string> = {};
|
|
132
|
+
if (conversationKey) {
|
|
133
|
+
query.conversationKey = conversationKey;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for await (const event of client.stream<AssistantEvent>("/events", {
|
|
137
|
+
signal: controller.signal,
|
|
138
|
+
query,
|
|
139
|
+
})) {
|
|
140
|
+
if (jsonOutput) {
|
|
141
|
+
console.log(JSON.stringify(event));
|
|
142
|
+
} else {
|
|
143
|
+
renderMarkdown(event);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
package/src/commands/hatch.ts
CHANGED
|
@@ -21,7 +21,6 @@ import { hatchGcp } from "../lib/gcp";
|
|
|
21
21
|
import type { PollResult, WatchHatchingResult } from "../lib/gcp";
|
|
22
22
|
import { hatchLocal } from "../lib/hatch-local";
|
|
23
23
|
import {
|
|
24
|
-
fetchOrganizationId,
|
|
25
24
|
getPlatformUrl,
|
|
26
25
|
hatchAssistant,
|
|
27
26
|
readPlatformToken,
|
|
@@ -594,19 +593,7 @@ async function hatchVellumPlatform(): Promise<void> {
|
|
|
594
593
|
console.log(" Hatching assistant on Vellum platform...");
|
|
595
594
|
console.log("");
|
|
596
595
|
|
|
597
|
-
|
|
598
|
-
try {
|
|
599
|
-
orgId = await fetchOrganizationId(token);
|
|
600
|
-
} catch (err) {
|
|
601
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
602
|
-
if (msg.includes("401") || msg.includes("403")) {
|
|
603
|
-
console.error("Authentication failed. Run 'vellum login' to refresh.");
|
|
604
|
-
process.exit(1);
|
|
605
|
-
}
|
|
606
|
-
throw err;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
const result = await hatchAssistant(token, orgId);
|
|
596
|
+
const result = await hatchAssistant(token);
|
|
610
597
|
|
|
611
598
|
const platformUrl = getPlatformUrl();
|
|
612
599
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vellum message <assistant> <message>`
|
|
3
|
+
*
|
|
4
|
+
* Send a message to a running assistant via its runtime HTTP API and
|
|
5
|
+
* print the result. This is a fire-and-send command — it does NOT
|
|
6
|
+
* subscribe to SSE events (use `vellum events` for that).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { extractFlag } from "../lib/arg-utils.js";
|
|
10
|
+
import { AssistantClient } from "../lib/assistant-client.js";
|
|
11
|
+
|
|
12
|
+
function printUsage(): void {
|
|
13
|
+
console.log(`vellum message - Send a message to a running assistant
|
|
14
|
+
|
|
15
|
+
USAGE:
|
|
16
|
+
vellum message [assistant] <message>
|
|
17
|
+
|
|
18
|
+
ARGUMENTS:
|
|
19
|
+
[assistant] Instance name (default: active assistant)
|
|
20
|
+
<message> Message content to send
|
|
21
|
+
|
|
22
|
+
OPTIONS:
|
|
23
|
+
--conversation-key <key> Conversation key (default: stable key per channel/interface)
|
|
24
|
+
--json Output raw JSON response
|
|
25
|
+
|
|
26
|
+
EXAMPLES:
|
|
27
|
+
vellum message "hello"
|
|
28
|
+
vellum message my-assistant "ping"
|
|
29
|
+
vellum message --conversation-key my-thread "hello"
|
|
30
|
+
vellum message --json "hello"
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function message(): Promise<void> {
|
|
35
|
+
const rawArgs = process.argv.slice(3);
|
|
36
|
+
|
|
37
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
38
|
+
printUsage();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const jsonOutput = rawArgs.includes("--json");
|
|
43
|
+
let args = rawArgs.filter((a) => a !== "--json");
|
|
44
|
+
|
|
45
|
+
const [conversationKey, filteredArgs] = extractFlag(
|
|
46
|
+
args,
|
|
47
|
+
"--conversation-key",
|
|
48
|
+
);
|
|
49
|
+
args = filteredArgs;
|
|
50
|
+
|
|
51
|
+
let assistantId: string | undefined;
|
|
52
|
+
let messageContent: string | undefined;
|
|
53
|
+
|
|
54
|
+
if (args.length >= 2) {
|
|
55
|
+
// vellum message <assistant> <message>
|
|
56
|
+
assistantId = args[0];
|
|
57
|
+
messageContent = args[1];
|
|
58
|
+
} else if (args.length === 1) {
|
|
59
|
+
// vellum message <message> (uses active/latest assistant)
|
|
60
|
+
messageContent = args[0];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!messageContent) {
|
|
64
|
+
console.error("Error: message content is required.");
|
|
65
|
+
console.error("");
|
|
66
|
+
printUsage();
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const client = new AssistantClient({ assistantId });
|
|
71
|
+
|
|
72
|
+
const payload: Record<string, string> = {
|
|
73
|
+
content: messageContent,
|
|
74
|
+
sourceChannel: "vellum",
|
|
75
|
+
interface: "cli",
|
|
76
|
+
};
|
|
77
|
+
if (conversationKey) {
|
|
78
|
+
payload.conversationKey = conversationKey;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const response = await client.post("/messages/", payload);
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const body = await response.text().catch(() => "");
|
|
85
|
+
console.error(
|
|
86
|
+
`Error: HTTP ${response.status}: ${body || response.statusText}`,
|
|
87
|
+
);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const result = (await response.json()) as {
|
|
92
|
+
accepted: boolean;
|
|
93
|
+
messageId: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (jsonOutput) {
|
|
97
|
+
console.log(JSON.stringify(result, null, 2));
|
|
98
|
+
} else {
|
|
99
|
+
if (result.accepted) {
|
|
100
|
+
console.log(`Message accepted (id: ${result.messageId})`);
|
|
101
|
+
} else {
|
|
102
|
+
console.log(`Message rejected (id: ${result.messageId})`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/commands/restore.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
} from "../lib/guardian-token.js";
|
|
9
9
|
import {
|
|
10
10
|
readPlatformToken,
|
|
11
|
-
fetchOrganizationId,
|
|
12
11
|
rollbackPlatformAssistant,
|
|
13
12
|
platformImportPreflight,
|
|
14
13
|
platformImportBundle,
|
|
@@ -177,18 +176,6 @@ async function restorePlatform(
|
|
|
177
176
|
process.exit(1);
|
|
178
177
|
}
|
|
179
178
|
|
|
180
|
-
let orgId: string;
|
|
181
|
-
try {
|
|
182
|
-
orgId = await fetchOrganizationId(token, entry.runtimeUrl);
|
|
183
|
-
} catch (err) {
|
|
184
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
185
|
-
if (msg.includes("401") || msg.includes("403")) {
|
|
186
|
-
console.error("Authentication failed. Run 'vellum login' to refresh.");
|
|
187
|
-
process.exit(1);
|
|
188
|
-
}
|
|
189
|
-
throw err;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
179
|
// Step 2 — Dry-run path
|
|
193
180
|
if (opts.dryRun) {
|
|
194
181
|
if (opts.version) {
|
|
@@ -205,7 +192,6 @@ async function restorePlatform(
|
|
|
205
192
|
preflightResult = await platformImportPreflight(
|
|
206
193
|
new Uint8Array(bundleData),
|
|
207
194
|
token,
|
|
208
|
-
orgId,
|
|
209
195
|
entry.runtimeUrl,
|
|
210
196
|
);
|
|
211
197
|
} catch (err) {
|
|
@@ -316,12 +302,7 @@ async function restorePlatform(
|
|
|
316
302
|
);
|
|
317
303
|
|
|
318
304
|
try {
|
|
319
|
-
await rollbackPlatformAssistant(
|
|
320
|
-
token,
|
|
321
|
-
orgId,
|
|
322
|
-
opts.version,
|
|
323
|
-
entry.runtimeUrl,
|
|
324
|
-
);
|
|
305
|
+
await rollbackPlatformAssistant(token, opts.version, entry.runtimeUrl);
|
|
325
306
|
} catch (err) {
|
|
326
307
|
const msg = err instanceof Error ? err.message : String(err);
|
|
327
308
|
if (msg.includes("401") || msg.includes("403")) {
|
|
@@ -345,7 +326,6 @@ async function restorePlatform(
|
|
|
345
326
|
importResult = await platformImportBundle(
|
|
346
327
|
new Uint8Array(bundleData),
|
|
347
328
|
token,
|
|
348
|
-
orgId,
|
|
349
329
|
entry.runtimeUrl,
|
|
350
330
|
);
|
|
351
331
|
} catch (err) {
|