@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
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { readFileSync, rmSync } from "fs";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
import { buildNestedConfig, writeInitialConfig } from "../lib/config-utils.js";
|
|
5
|
+
|
|
6
|
+
function readInitialConfig(
|
|
7
|
+
configValues: Record<string, string>,
|
|
8
|
+
): Record<string, unknown> {
|
|
9
|
+
const path = writeInitialConfig(configValues);
|
|
10
|
+
expect(path).toBeDefined();
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(readFileSync(path!, "utf-8")) as Record<string, unknown>;
|
|
13
|
+
} finally {
|
|
14
|
+
if (path !== undefined) rmSync(path, { force: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("config-utils", () => {
|
|
19
|
+
test("buildNestedConfig only converts dot-notation values", () => {
|
|
20
|
+
expect(
|
|
21
|
+
buildNestedConfig({
|
|
22
|
+
"llm.default.provider": "anthropic",
|
|
23
|
+
"llm.default.model": "claude-sonnet-4-6",
|
|
24
|
+
}),
|
|
25
|
+
).toEqual({
|
|
26
|
+
llm: {
|
|
27
|
+
default: {
|
|
28
|
+
provider: "anthropic",
|
|
29
|
+
model: "claude-sonnet-4-6",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("writeInitialConfig does not add a mainAgent callSite for Anthropic defaults", () => {
|
|
36
|
+
expect(
|
|
37
|
+
readInitialConfig({
|
|
38
|
+
"llm.default.provider": "anthropic",
|
|
39
|
+
"llm.default.model": "claude-opus-4-7",
|
|
40
|
+
}),
|
|
41
|
+
).toEqual({
|
|
42
|
+
llm: {
|
|
43
|
+
default: {
|
|
44
|
+
provider: "anthropic",
|
|
45
|
+
model: "claude-opus-4-7",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("writeInitialConfig preserves profile-based Anthropic model selection", () => {
|
|
52
|
+
expect(
|
|
53
|
+
readInitialConfig({
|
|
54
|
+
"llm.activeProfile": "quality-optimized",
|
|
55
|
+
"llm.profiles.quality-optimized.provider": "anthropic",
|
|
56
|
+
"llm.profiles.quality-optimized.model": "claude-opus-4-7",
|
|
57
|
+
"llm.profiles.quality-optimized.maxTokens": "32000",
|
|
58
|
+
}),
|
|
59
|
+
).toEqual({
|
|
60
|
+
llm: {
|
|
61
|
+
activeProfile: "quality-optimized",
|
|
62
|
+
profiles: {
|
|
63
|
+
"quality-optimized": {
|
|
64
|
+
provider: "anthropic",
|
|
65
|
+
model: "claude-opus-4-7",
|
|
66
|
+
maxTokens: "32000",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("writeInitialConfig preserves explicit mainAgent overrides without rewriting them", () => {
|
|
74
|
+
expect(
|
|
75
|
+
readInitialConfig({
|
|
76
|
+
"llm.default.provider": "anthropic",
|
|
77
|
+
"llm.default.model": "claude-opus-4-7",
|
|
78
|
+
"llm.callSites.mainAgent.model": "claude-haiku-4-5-20251001",
|
|
79
|
+
}),
|
|
80
|
+
).toEqual({
|
|
81
|
+
llm: {
|
|
82
|
+
default: {
|
|
83
|
+
provider: "anthropic",
|
|
84
|
+
model: "claude-opus-4-7",
|
|
85
|
+
},
|
|
86
|
+
callSites: {
|
|
87
|
+
mainAgent: {
|
|
88
|
+
model: "claude-haiku-4-5-20251001",
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("writeInitialConfig respects explicit non-default Anthropic models", () => {
|
|
96
|
+
expect(
|
|
97
|
+
readInitialConfig({
|
|
98
|
+
"llm.default.provider": "anthropic",
|
|
99
|
+
"llm.default.model": "claude-haiku-4-5-20251001",
|
|
100
|
+
}),
|
|
101
|
+
).toEqual({
|
|
102
|
+
llm: {
|
|
103
|
+
default: {
|
|
104
|
+
provider: "anthropic",
|
|
105
|
+
model: "claude-haiku-4-5-20251001",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("writeInitialConfig leaves active OpenAI profile config unchanged", () => {
|
|
112
|
+
expect(
|
|
113
|
+
readInitialConfig({
|
|
114
|
+
"llm.activeProfile": "fast",
|
|
115
|
+
"llm.profiles.fast.provider": "openai",
|
|
116
|
+
"llm.profiles.fast.model": "gpt-5.5",
|
|
117
|
+
}),
|
|
118
|
+
).toEqual({
|
|
119
|
+
llm: {
|
|
120
|
+
activeProfile: "fast",
|
|
121
|
+
profiles: {
|
|
122
|
+
fast: {
|
|
123
|
+
provider: "openai",
|
|
124
|
+
model: "gpt-5.5",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("writeInitialConfig does not add Opus for non-Anthropic providers", () => {
|
|
132
|
+
expect(
|
|
133
|
+
readInitialConfig({
|
|
134
|
+
"llm.default.provider": "openai",
|
|
135
|
+
"llm.default.model": "gpt-5.5",
|
|
136
|
+
}),
|
|
137
|
+
).toEqual({
|
|
138
|
+
llm: {
|
|
139
|
+
default: {
|
|
140
|
+
provider: "openai",
|
|
141
|
+
model: "gpt-5.5",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -4,25 +4,21 @@ import { join } from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import { SEEDS } from "../lib/environments/seeds.js";
|
|
6
6
|
|
|
7
|
-
// Drift guard for the
|
|
8
|
-
//
|
|
7
|
+
// Drift guard for the TypeScript sites that hardcode the set of known
|
|
8
|
+
// environment names:
|
|
9
9
|
//
|
|
10
|
-
// 1. cli/src/lib/environments/seeds.ts
|
|
11
|
-
// 2. assistant/src/util/platform.ts
|
|
12
|
-
// 3. clients/chrome-extension/native-host/
|
|
13
|
-
// src/lockfile.ts — NON_PRODUCTION_ENVIRONMENTS set
|
|
10
|
+
// 1. cli/src/lib/environments/seeds.ts — SEEDS record (source of truth)
|
|
11
|
+
// 2. assistant/src/util/platform.ts — KNOWN_ENVIRONMENTS set
|
|
14
12
|
//
|
|
15
13
|
// Cross-package relative imports don't work here: assistant's tsconfig
|
|
16
|
-
// restricts `include` to its own src tree
|
|
17
|
-
//
|
|
18
|
-
// literal sets out of the two external files and asserts they agree with
|
|
19
|
-
// CLI's SEEDS.
|
|
14
|
+
// restricts `include` to its own src tree. So this test parses the literal
|
|
15
|
+
// set out of the external file and asserts it agrees with CLI's SEEDS.
|
|
20
16
|
//
|
|
21
17
|
// FOLLOW-UP: split the env name list into a shared `packages/environments`
|
|
22
|
-
// package (mirroring `packages/
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
18
|
+
// package (mirroring `packages/service-contracts`, `credential-storage`) so
|
|
19
|
+
// both sites can `import { KNOWN_ENVIRONMENTS }` from one place and this
|
|
20
|
+
// drift guard becomes a compile-time check. Planned alongside CLI-driven
|
|
21
|
+
// context support — see the "Environments" design doc.
|
|
26
22
|
|
|
27
23
|
const REPO_ROOT = join(import.meta.dir, "..", "..", "..");
|
|
28
24
|
const ASSISTANT_PLATFORM = join(
|
|
@@ -32,14 +28,6 @@ const ASSISTANT_PLATFORM = join(
|
|
|
32
28
|
"util",
|
|
33
29
|
"platform.ts",
|
|
34
30
|
);
|
|
35
|
-
const NATIVE_HOST_LOCKFILE = join(
|
|
36
|
-
REPO_ROOT,
|
|
37
|
-
"clients",
|
|
38
|
-
"chrome-extension",
|
|
39
|
-
"native-host",
|
|
40
|
-
"src",
|
|
41
|
-
"lockfile.ts",
|
|
42
|
-
);
|
|
43
31
|
|
|
44
32
|
/**
|
|
45
33
|
* Extract the string literals from a Set constructor body in a TS source
|
|
@@ -74,14 +62,4 @@ describe("KNOWN_ENVIRONMENTS drift guard (TS-side)", () => {
|
|
|
74
62
|
);
|
|
75
63
|
expect([...assistantNames].sort()).toEqual([...seedNames].sort());
|
|
76
64
|
});
|
|
77
|
-
|
|
78
|
-
test("native-host/src/lockfile.ts NON_PRODUCTION_ENVIRONMENTS matches CLI SEEDS minus production", () => {
|
|
79
|
-
const source = readFileSync(NATIVE_HOST_LOCKFILE, "utf8");
|
|
80
|
-
const nativeNames = new Set(
|
|
81
|
-
extractSetLiterals(source, "NON_PRODUCTION_ENVIRONMENTS"),
|
|
82
|
-
);
|
|
83
|
-
const expected = new Set(seedNames);
|
|
84
|
-
expected.delete("production");
|
|
85
|
-
expect([...nativeNames].sort()).toEqual([...expected].sort());
|
|
86
|
-
});
|
|
87
65
|
});
|
|
@@ -2,10 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { describe, expect, test } from "bun:test";
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
LLM_PROVIDER_ENV_VAR_NAMES,
|
|
7
|
-
SEARCH_PROVIDER_ENV_VAR_NAMES,
|
|
8
|
-
} from "../shared/provider-env-vars.js";
|
|
5
|
+
import { LLM_PROVIDER_ENV_VAR_NAMES } from "../shared/provider-env-vars.js";
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
8
|
* Drift guard for the CLI-side LLM provider env-var mirror.
|
|
@@ -16,8 +13,6 @@ import {
|
|
|
16
13
|
* `meta/llm-provider-catalog.json` — which is kept in sync with
|
|
17
14
|
* `PROVIDER_CATALOG` by `assistant/src/__tests__/llm-catalog-parity.test.ts` —
|
|
18
15
|
* and asserts the CLI's mirror matches the catalog's `envVar` entries.
|
|
19
|
-
*
|
|
20
|
-
* It also asserts the search-provider mirror matches `meta/provider-env-vars.json`.
|
|
21
16
|
*/
|
|
22
17
|
|
|
23
18
|
const REPO_ROOT = join(import.meta.dir, "..", "..", "..");
|
|
@@ -32,21 +27,11 @@ interface LlmCatalog {
|
|
|
32
27
|
providers: LlmCatalogEntry[];
|
|
33
28
|
}
|
|
34
29
|
|
|
35
|
-
interface SearchProviderRegistry {
|
|
36
|
-
version: number;
|
|
37
|
-
providers: Record<string, string>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
30
|
function loadLlmCatalog(): LlmCatalog {
|
|
41
31
|
const path = join(REPO_ROOT, "meta", "llm-provider-catalog.json");
|
|
42
32
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
function loadSearchProviderRegistry(): SearchProviderRegistry {
|
|
46
|
-
const path = join(REPO_ROOT, "meta", "provider-env-vars.json");
|
|
47
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
35
|
describe("CLI provider env-var parity", () => {
|
|
51
36
|
test("LLM_PROVIDER_ENV_VAR_NAMES matches meta/llm-provider-catalog.json entries with envVar", () => {
|
|
52
37
|
const catalog = loadLlmCatalog();
|
|
@@ -56,9 +41,4 @@ describe("CLI provider env-var parity", () => {
|
|
|
56
41
|
}
|
|
57
42
|
expect(LLM_PROVIDER_ENV_VAR_NAMES).toEqual(expected);
|
|
58
43
|
});
|
|
59
|
-
|
|
60
|
-
test("SEARCH_PROVIDER_ENV_VAR_NAMES matches meta/provider-env-vars.json", () => {
|
|
61
|
-
const registry = loadSearchProviderRegistry();
|
|
62
|
-
expect(SEARCH_PROVIDER_ENV_VAR_NAMES).toEqual(registry.providers);
|
|
63
|
-
});
|
|
64
44
|
});
|
|
@@ -114,11 +114,6 @@ describe("multi-local", () => {
|
|
|
114
114
|
expect(res.daemonPort).toBe(DEFAULT_DAEMON_PORT);
|
|
115
115
|
expect(res.gatewayPort).toBe(DEFAULT_GATEWAY_PORT);
|
|
116
116
|
expect(res.qdrantPort).toBe(DEFAULT_QDRANT_PORT);
|
|
117
|
-
|
|
118
|
-
// AND the PID file is under the instance's .vellum/
|
|
119
|
-
expect(res.pidFile).toBe(
|
|
120
|
-
join(res.instanceDir, ".vellum", "vellum.pid"),
|
|
121
|
-
);
|
|
122
117
|
} finally {
|
|
123
118
|
if (prevXdg !== undefined) {
|
|
124
119
|
process.env.XDG_DATA_HOME = prevXdg;
|
|
@@ -52,7 +52,6 @@ function writeLockfile(): void {
|
|
|
52
52
|
daemonPort: DEFAULT_DAEMON_PORT,
|
|
53
53
|
gatewayPort: DEFAULT_GATEWAY_PORT,
|
|
54
54
|
qdrantPort: DEFAULT_QDRANT_PORT,
|
|
55
|
-
pidFile: join(assistantRootDir, "vellum.pid"),
|
|
56
55
|
},
|
|
57
56
|
},
|
|
58
57
|
],
|
|
@@ -158,7 +157,7 @@ describe("sleep command", () => {
|
|
|
158
157
|
expect(stopProcessByPidFileMock).toHaveBeenCalledTimes(2);
|
|
159
158
|
expect(stopProcessByPidFileMock).toHaveBeenNthCalledWith(
|
|
160
159
|
1,
|
|
161
|
-
join(assistantRootDir, "vellum.pid"),
|
|
160
|
+
join(assistantRootDir, "workspace", "vellum.pid"),
|
|
162
161
|
"assistant",
|
|
163
162
|
);
|
|
164
163
|
expect(stopProcessByPidFileMock).toHaveBeenNthCalledWith(
|