@vellumai/cli 0.11.2 → 0.11.3-staging.2
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/knip.json +1 -0
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
- package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
- package/node_modules/@vellumai/environments/src/index.ts +17 -6
- package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
- package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/ipc-framing.ts +295 -0
- package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +444 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +236 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/index.ts +17 -6
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/seeds.ts +29 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/LICENSE +20 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/README.md +38 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/bin/nanoid.js +55 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.browser.js +29 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.d.ts +106 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.js +47 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/nanoid.js +1 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/non-secure/index.d.ts +48 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/non-secure/index.js +21 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/package.json +46 -0
- package/node_modules/@vellumai/local-mode/node_modules/nanoid/url-alphabet/index.js +2 -0
- package/node_modules/@vellumai/local-mode/package.json +1 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/environment.test.ts +63 -1
- package/node_modules/@vellumai/local-mode/src/__tests__/gateway-proxy.test.ts +503 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/guardian-token.test.ts +203 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/loopback-auth.test.ts +46 -1
- package/node_modules/@vellumai/local-mode/src/__tests__/package-boundary.test.ts +6 -4
- package/node_modules/@vellumai/local-mode/src/__tests__/pair.test.ts +621 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/status.test.ts +5 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/unpair.test.ts +232 -0
- package/node_modules/@vellumai/local-mode/src/config.ts +126 -20
- package/node_modules/@vellumai/local-mode/src/environment.ts +36 -15
- package/node_modules/@vellumai/local-mode/src/gateway-proxy.ts +313 -25
- package/node_modules/@vellumai/local-mode/src/guardian-token.ts +222 -13
- package/node_modules/@vellumai/local-mode/src/index.ts +35 -3
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.test.ts +18 -0
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.ts +72 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.test.ts +130 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.ts +134 -45
- package/node_modules/@vellumai/local-mode/src/pair.ts +354 -0
- package/node_modules/@vellumai/local-mode/src/paths.ts +49 -0
- package/node_modules/@vellumai/local-mode/src/status.ts +10 -8
- package/node_modules/@vellumai/local-mode/src/unpair.ts +101 -0
- package/node_modules/@vellumai/local-mode/src/util.ts +34 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/package.json +3 -1
- package/src/__tests__/client-token.test.ts +26 -1
- package/src/__tests__/guardian-token.test.ts +4 -0
- package/src/__tests__/nginx-ingress-command.test.ts +176 -16
- package/src/__tests__/nginx-ingress.test.ts +1821 -101
- package/src/__tests__/pair.test.ts +104 -2
- package/src/__tests__/sleep.test.ts +7 -3
- package/src/__tests__/tailscale-tunnel.test.ts +0 -1
- package/src/__tests__/tunnel.test.ts +1099 -55
- package/src/__tests__/wake.test.ts +166 -99
- package/src/commands/client.ts +182 -27
- package/src/commands/connect/import.ts +35 -156
- package/src/commands/nginx-ingress.ts +87 -110
- package/src/commands/pair.ts +84 -21
- package/src/commands/tunnel.ts +196 -85
- package/src/commands/upgrade.ts +3 -2
- package/src/commands/wake.ts +5 -163
- package/src/index.ts +1 -1
- package/src/lib/__tests__/local-ces.test.ts +10 -1
- package/src/lib/cloudflare-tunnel.ts +3 -16
- package/src/lib/environments/__tests__/paths.test.ts +20 -1
- package/src/lib/environments/paths.ts +29 -41
- package/src/lib/environments/resolve.ts +7 -5
- package/src/lib/guardian-token.ts +22 -62
- package/src/lib/ingress-config.ts +25 -0
- package/src/lib/local.ts +77 -20
- package/src/lib/nginx-ingress.ts +523 -76
- package/src/lib/ngrok.ts +154 -61
- package/src/lib/orphan-detection.test.ts +167 -0
- package/src/lib/orphan-detection.ts +40 -0
- package/src/lib/pair.test.ts +87 -0
- package/src/lib/pair.ts +55 -0
- package/src/lib/platform-client.ts +8 -5
- package/src/lib/tailscale-tunnel.ts +7 -18
- package/src/lib/tunnel-edge.ts +114 -0
- package/src/lib/xdg-log.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vellumai/cli",
|
|
3
|
-
"version": "0.11.2",
|
|
3
|
+
"version": "0.11.3-staging.2",
|
|
4
4
|
"description": "CLI tools for vellum-assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@vellumai/environments": "file:../packages/environments",
|
|
33
|
+
"@vellumai/ipc-server-utils": "file:../packages/ipc-server-utils",
|
|
33
34
|
"@vellumai/local-mode": "file:../packages/local-mode",
|
|
34
35
|
"@vellumai/service-contracts": "file:../packages/service-contracts",
|
|
35
36
|
"chalk": "5.6.2",
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
},
|
|
41
42
|
"bundledDependencies": [
|
|
42
43
|
"@vellumai/environments",
|
|
44
|
+
"@vellumai/ipc-server-utils",
|
|
43
45
|
"@vellumai/local-mode",
|
|
44
46
|
"@vellumai/service-contracts"
|
|
45
47
|
],
|
|
@@ -27,11 +27,36 @@ const testDir = mkdtempSync(join(tmpdir(), "client-token-test-"));
|
|
|
27
27
|
const ORIGINAL_LOCKFILE_DIR = process.env.VELLUM_LOCKFILE_DIR;
|
|
28
28
|
const ORIGINAL_ARGV = [...process.argv];
|
|
29
29
|
|
|
30
|
-
import { parseArgs } from "../commands/client.js";
|
|
30
|
+
import { isSameOriginRequest, parseArgs } from "../commands/client.js";
|
|
31
31
|
|
|
32
32
|
// A clearly non-local URL so maybeSwapToLocalhost won't rewrite it to 127.0.0.1.
|
|
33
33
|
const REMOTE_URL = "http://192.0.2.50:7830";
|
|
34
34
|
|
|
35
|
+
describe("client web proxy credential proof", () => {
|
|
36
|
+
test("rejects a request without browser same-origin proof", () => {
|
|
37
|
+
expect(
|
|
38
|
+
isSameOriginRequest(
|
|
39
|
+
new Request("http://127.0.0.1:3000/assistant", {
|
|
40
|
+
headers: { host: "127.0.0.1:3000" },
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("accepts a request with a matching Origin", () => {
|
|
47
|
+
expect(
|
|
48
|
+
isSameOriginRequest(
|
|
49
|
+
new Request("http://127.0.0.1:3000/assistant", {
|
|
50
|
+
headers: {
|
|
51
|
+
host: "127.0.0.1:3000",
|
|
52
|
+
origin: "http://127.0.0.1:3000",
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
35
60
|
describe("client --token (ephemeral)", () => {
|
|
36
61
|
beforeEach(() => {
|
|
37
62
|
process.env.VELLUM_LOCKFILE_DIR = testDir;
|
|
@@ -261,6 +261,7 @@ describe("refreshGuardianToken", () => {
|
|
|
261
261
|
isNew: false,
|
|
262
262
|
deviceId: "dev",
|
|
263
263
|
leasedAt: new Date().toISOString(),
|
|
264
|
+
pairedGatewayUrl: "https://gw.example.com",
|
|
264
265
|
});
|
|
265
266
|
}
|
|
266
267
|
|
|
@@ -302,6 +303,9 @@ describe("refreshGuardianToken", () => {
|
|
|
302
303
|
|
|
303
304
|
expect(result?.accessToken).toBe("new-acc");
|
|
304
305
|
expect(loadGuardianToken("px")?.accessToken).toBe("new-acc");
|
|
306
|
+
expect(loadGuardianToken("px")?.pairedGatewayUrl).toBe(
|
|
307
|
+
"https://gw.example.com",
|
|
308
|
+
);
|
|
305
309
|
expect(sawSignal).toBe(true); // fetch carries a timeout AbortSignal
|
|
306
310
|
expect(existsSync(lockPath("px"))).toBe(false); // lock released
|
|
307
311
|
});
|
|
@@ -1,9 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
mock,
|
|
8
|
+
spyOn,
|
|
9
|
+
test,
|
|
10
|
+
} from "bun:test";
|
|
2
11
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
12
|
import { tmpdir } from "node:os";
|
|
4
13
|
import { join } from "node:path";
|
|
5
14
|
|
|
6
|
-
import
|
|
15
|
+
import * as nginxIngressLib from "../lib/nginx-ingress.js";
|
|
16
|
+
|
|
17
|
+
const realNginxIngressLib = { ...nginxIngressLib };
|
|
18
|
+
|
|
19
|
+
const ensureTunnelEdgeMock = mock<typeof nginxIngressLib.ensureTunnelEdge>(
|
|
20
|
+
async () => ({ port: 7840, started: true, includesWebApp: true }),
|
|
21
|
+
);
|
|
22
|
+
const getIngressPidMock = mock<typeof nginxIngressLib.getIngressPid>(
|
|
23
|
+
() => null,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
mock.module("../lib/nginx-ingress.js", () => ({
|
|
27
|
+
...nginxIngressLib,
|
|
28
|
+
ensureTunnelEdge: ensureTunnelEdgeMock,
|
|
29
|
+
getIngressPid: getIngressPidMock,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
// Restore the real module once this file finishes so the mock does not leak
|
|
33
|
+
// into sibling test files in the same `bun test` run.
|
|
34
|
+
afterAll(() => {
|
|
35
|
+
mock.module("../lib/nginx-ingress.js", () => realNginxIngressLib);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
resolveNginxIngressTarget,
|
|
40
|
+
status,
|
|
41
|
+
up,
|
|
42
|
+
} from "../commands/nginx-ingress.js";
|
|
7
43
|
import type { AssistantEntry } from "../lib/assistant-config.js";
|
|
8
44
|
|
|
9
45
|
const testDir = mkdtempSync(join(tmpdir(), "cli-nginx-ingress-command-test-"));
|
|
@@ -29,6 +65,20 @@ function writeLockfile(
|
|
|
29
65
|
);
|
|
30
66
|
}
|
|
31
67
|
|
|
68
|
+
afterAll(() => {
|
|
69
|
+
if (originalLockfileDir === undefined) {
|
|
70
|
+
delete process.env.VELLUM_LOCKFILE_DIR;
|
|
71
|
+
} else {
|
|
72
|
+
process.env.VELLUM_LOCKFILE_DIR = originalLockfileDir;
|
|
73
|
+
}
|
|
74
|
+
if (originalWorkspaceDir === undefined) {
|
|
75
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
76
|
+
} else {
|
|
77
|
+
process.env.VELLUM_WORKSPACE_DIR = originalWorkspaceDir;
|
|
78
|
+
}
|
|
79
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
80
|
+
});
|
|
81
|
+
|
|
32
82
|
describe("resolveNginxIngressTarget", () => {
|
|
33
83
|
beforeEach(() => {
|
|
34
84
|
process.env.VELLUM_LOCKFILE_DIR = testDir;
|
|
@@ -36,20 +86,6 @@ describe("resolveNginxIngressTarget", () => {
|
|
|
36
86
|
rmSync(join(testDir, ".vellum.lock.json"), { force: true });
|
|
37
87
|
});
|
|
38
88
|
|
|
39
|
-
afterAll(() => {
|
|
40
|
-
if (originalLockfileDir === undefined) {
|
|
41
|
-
delete process.env.VELLUM_LOCKFILE_DIR;
|
|
42
|
-
} else {
|
|
43
|
-
process.env.VELLUM_LOCKFILE_DIR = originalLockfileDir;
|
|
44
|
-
}
|
|
45
|
-
if (originalWorkspaceDir === undefined) {
|
|
46
|
-
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
47
|
-
} else {
|
|
48
|
-
process.env.VELLUM_WORKSPACE_DIR = originalWorkspaceDir;
|
|
49
|
-
}
|
|
50
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
51
|
-
});
|
|
52
|
-
|
|
53
89
|
test("derives the gateway port from runtimeUrl when resources are absent", () => {
|
|
54
90
|
writeLockfile([
|
|
55
91
|
{
|
|
@@ -67,3 +103,127 @@ describe("resolveNginxIngressTarget", () => {
|
|
|
67
103
|
});
|
|
68
104
|
});
|
|
69
105
|
});
|
|
106
|
+
|
|
107
|
+
describe("up", () => {
|
|
108
|
+
let logs: string[];
|
|
109
|
+
let logSpy: ReturnType<typeof spyOn<typeof console, "log">>;
|
|
110
|
+
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
ensureTunnelEdgeMock.mockClear();
|
|
113
|
+
logs = [];
|
|
114
|
+
logSpy = spyOn(console, "log").mockImplementation((...args: unknown[]) => {
|
|
115
|
+
logs.push(args.join(" "));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
logSpy.mockRestore();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("starts the webhooks-only edge when the flag is off and states the mode", async () => {
|
|
124
|
+
ensureTunnelEdgeMock.mockResolvedValue({
|
|
125
|
+
port: 7845,
|
|
126
|
+
started: true,
|
|
127
|
+
includesWebApp: false,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await up({
|
|
131
|
+
assistantId: "assistant-1",
|
|
132
|
+
workspaceDir,
|
|
133
|
+
gatewayPort: 7830,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(ensureTunnelEdgeMock).toHaveBeenCalledTimes(1);
|
|
137
|
+
expect(ensureTunnelEdgeMock.mock.calls[0]?.[0]).toMatchObject({
|
|
138
|
+
assistantId: "assistant-1",
|
|
139
|
+
workspaceDir,
|
|
140
|
+
gatewayPort: 7830,
|
|
141
|
+
});
|
|
142
|
+
const output = logs.join("\n");
|
|
143
|
+
expect(output).toContain("http://127.0.0.1:7845 (webhooks only)");
|
|
144
|
+
expect(output).toContain("web-remote-ingress");
|
|
145
|
+
expect(output).toContain("vellum tunnel --provider ngrok");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("states the remote web mode when the flag is on", async () => {
|
|
149
|
+
ensureTunnelEdgeMock.mockResolvedValue({
|
|
150
|
+
port: 7840,
|
|
151
|
+
started: true,
|
|
152
|
+
includesWebApp: true,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await up({
|
|
156
|
+
assistantId: "assistant-1",
|
|
157
|
+
workspaceDir,
|
|
158
|
+
gatewayPort: 7830,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const output = logs.join("\n");
|
|
162
|
+
expect(output).toContain("http://127.0.0.1:7840 (remote web + webhooks)");
|
|
163
|
+
expect(output).not.toContain("Enable the web-remote-ingress feature flag");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("status", () => {
|
|
168
|
+
const statusWorkspace = join(testDir, "status-workspace");
|
|
169
|
+
let logs: string[];
|
|
170
|
+
let logSpy: ReturnType<typeof spyOn<typeof console, "log">>;
|
|
171
|
+
|
|
172
|
+
beforeEach(() => {
|
|
173
|
+
getIngressPidMock.mockReset();
|
|
174
|
+
getIngressPidMock.mockReturnValue(4242);
|
|
175
|
+
logs = [];
|
|
176
|
+
logSpy = spyOn(console, "log").mockImplementation((...args: unknown[]) => {
|
|
177
|
+
logs.push(args.join(" "));
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
afterEach(() => {
|
|
182
|
+
logSpy.mockRestore();
|
|
183
|
+
rmSync(statusWorkspace, { recursive: true, force: true });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
function writeIngressState(nginx: Record<string, unknown>): void {
|
|
187
|
+
mkdirSync(statusWorkspace, { recursive: true });
|
|
188
|
+
writeFileSync(
|
|
189
|
+
join(statusWorkspace, "config.json"),
|
|
190
|
+
JSON.stringify({ ingress: { nginx } }) + "\n",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
test("prints the recorded gateway upstream, not the requested one", async () => {
|
|
195
|
+
writeIngressState({
|
|
196
|
+
listenPort: 7845,
|
|
197
|
+
includeWebApp: false,
|
|
198
|
+
gatewayPort: 7900,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await status({ workspaceDir: statusWorkspace, gatewayPort: 7830 });
|
|
202
|
+
|
|
203
|
+
const output = logs.join("\n");
|
|
204
|
+
expect(output).toContain("nginx ingress: running");
|
|
205
|
+
expect(output).toContain("Listen: http://127.0.0.1:7845");
|
|
206
|
+
expect(output).toContain("Gateway: http://127.0.0.1:7900");
|
|
207
|
+
expect(output).not.toContain("http://127.0.0.1:7830");
|
|
208
|
+
expect(output).not.toContain("(unverified)");
|
|
209
|
+
expect(output).toContain("Mode: webhooks only");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("marks the requested gateway port unverified when the record predates the field", async () => {
|
|
213
|
+
writeIngressState({ listenPort: 7845 });
|
|
214
|
+
|
|
215
|
+
await status({ workspaceDir: statusWorkspace, gatewayPort: 7830 });
|
|
216
|
+
|
|
217
|
+
const output = logs.join("\n");
|
|
218
|
+
expect(output).toContain("Gateway: http://127.0.0.1:7830 (unverified)");
|
|
219
|
+
expect(output).toContain("Mode: remote web + webhooks");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("reports a stopped edge", async () => {
|
|
223
|
+
getIngressPidMock.mockReturnValue(null);
|
|
224
|
+
|
|
225
|
+
await status({ workspaceDir: statusWorkspace, gatewayPort: 7830 });
|
|
226
|
+
|
|
227
|
+
expect(logs.join("\n")).toContain("nginx ingress: not running");
|
|
228
|
+
});
|
|
229
|
+
});
|