@vellumai/cli 0.4.40 → 0.4.42

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.4.40",
3
+ "version": "0.4.42",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -23,6 +23,7 @@ describe("constants", () => {
23
23
  expect(VALID_REMOTE_HOSTS).toContain("local");
24
24
  expect(VALID_REMOTE_HOSTS).toContain("gcp");
25
25
  expect(VALID_REMOTE_HOSTS).toContain("aws");
26
+ expect(VALID_REMOTE_HOSTS).toContain("docker");
26
27
  expect(VALID_REMOTE_HOSTS).toContain("custom");
27
28
  });
28
29
 
@@ -39,7 +39,11 @@ import {
39
39
  saveAssistantEntry,
40
40
  type AssistantEntry,
41
41
  } from "../lib/assistant-config.js";
42
- import { DEFAULT_DAEMON_PORT } from "../lib/constants.js";
42
+ import {
43
+ DEFAULT_DAEMON_PORT,
44
+ DEFAULT_GATEWAY_PORT,
45
+ DEFAULT_QDRANT_PORT,
46
+ } from "../lib/constants.js";
43
47
 
44
48
  afterAll(() => {
45
49
  rmSync(testDir, { recursive: true, force: true });
@@ -91,40 +95,50 @@ describe("multi-local", () => {
91
95
  });
92
96
 
93
97
  describe("allocateLocalResources() produces non-conflicting ports", () => {
94
- test("two instances get distinct ports and dirs when first instance ports are occupied", async () => {
95
- // After the first allocation grabs its ports, simulate those ports
96
- // being in-use so the second allocation must pick different ones.
97
- const a = await allocateLocalResources("instance-a");
98
+ test("first instance returns default legacy resources", async () => {
99
+ // GIVEN no local assistants exist in the lockfile
100
+
101
+ // WHEN we allocate resources for the first instance
102
+ const res = await allocateLocalResources("instance-a");
103
+
104
+ // THEN it returns the default legacy layout (home dir, default ports)
105
+ expect(res.instanceDir).toBe(testDir);
106
+ expect(res.daemonPort).toBe(DEFAULT_DAEMON_PORT);
107
+ expect(res.gatewayPort).toBe(DEFAULT_GATEWAY_PORT);
108
+ expect(res.qdrantPort).toBe(DEFAULT_QDRANT_PORT);
109
+ });
110
+
111
+ test("second instance gets distinct ports and dir when first instance is saved", async () => {
112
+ // GIVEN a first local assistant already exists in the lockfile
113
+ saveAssistantEntry(makeEntry("instance-a"));
114
+
115
+ // AND the default ports are occupied
98
116
  const occupiedPorts = new Set([
99
- a.daemonPort,
100
- a.gatewayPort,
101
- a.qdrantPort,
117
+ DEFAULT_DAEMON_PORT,
118
+ DEFAULT_GATEWAY_PORT,
119
+ DEFAULT_QDRANT_PORT,
102
120
  ]);
103
121
  probePortMock.mockImplementation((port: number) =>
104
122
  Promise.resolve(occupiedPorts.has(port)),
105
123
  );
106
124
 
125
+ // WHEN we allocate resources for a second instance
107
126
  const b = await allocateLocalResources("instance-b");
108
127
 
109
- // All six ports must be unique across both instances
110
- const allPorts = [
111
- a.daemonPort,
112
- a.gatewayPort,
113
- a.qdrantPort,
114
- b.daemonPort,
115
- b.gatewayPort,
116
- b.qdrantPort,
117
- ];
118
- expect(new Set(allPorts).size).toBe(6);
119
-
120
- // Instance dirs must be distinct
121
- expect(a.instanceDir).not.toBe(b.instanceDir);
122
- expect(a.instanceDir).toContain("instance-a");
128
+ // THEN the second instance gets non-default ports
129
+ expect(occupiedPorts.has(b.daemonPort)).toBe(false);
130
+ expect(occupiedPorts.has(b.gatewayPort)).toBe(false);
131
+ expect(occupiedPorts.has(b.qdrantPort)).toBe(false);
132
+
133
+ // AND it gets its own dedicated instance directory
123
134
  expect(b.instanceDir).toContain("instance-b");
124
135
  });
125
136
 
126
137
  test("skips ports that probePort reports as in-use", async () => {
127
- // Simulate the default ports being occupied
138
+ // GIVEN a first local assistant already exists in the lockfile
139
+ saveAssistantEntry(makeEntry("existing"));
140
+
141
+ // AND the default daemon ports are occupied
128
142
  const portsInUse = new Set([
129
143
  DEFAULT_DAEMON_PORT,
130
144
  DEFAULT_DAEMON_PORT + 1,
@@ -133,7 +147,10 @@ describe("multi-local", () => {
133
147
  Promise.resolve(portsInUse.has(port)),
134
148
  );
135
149
 
150
+ // WHEN we allocate resources for a new instance
136
151
  const res = await allocateLocalResources("probe-test");
152
+
153
+ // THEN the daemon port skips all occupied ports
137
154
  expect(res.daemonPort).toBeGreaterThan(DEFAULT_DAEMON_PORT + 1);
138
155
  expect(portsInUse.has(res.daemonPort)).toBe(false);
139
156
  });
@@ -201,7 +201,7 @@ function parseArgs(): HatchArgs {
201
201
  console.log(" -d Run in detached mode");
202
202
  console.log(" --name <name> Custom instance name");
203
203
  console.log(
204
- " --remote <host> Remote host (local, gcp, aws, custom)",
204
+ " --remote <host> Remote host (local, gcp, aws, docker, custom)",
205
205
  );
206
206
  console.log(
207
207
  " --daemon-only Start assistant only, skip gateway",
@@ -830,6 +830,11 @@ export async function hatch(): Promise<void> {
830
830
  return;
831
831
  }
832
832
 
833
+ if (remote === "docker") {
834
+ console.error("Error: Docker remote host is not yet implemented.");
835
+ process.exit(1);
836
+ }
837
+
833
838
  console.error(`Error: Remote host '${remote}' is not yet supported.`);
834
839
  process.exit(1);
835
840
  }
@@ -8,7 +8,7 @@ export const DEFAULT_DAEMON_PORT = 7821;
8
8
  export const DEFAULT_GATEWAY_PORT = 7830;
9
9
  export const DEFAULT_QDRANT_PORT = 6333;
10
10
 
11
- export const VALID_REMOTE_HOSTS = ["local", "gcp", "aws", "custom"] as const;
11
+ export const VALID_REMOTE_HOSTS = ["local", "gcp", "aws", "docker", "custom"] as const;
12
12
  export type RemoteHost = (typeof VALID_REMOTE_HOSTS)[number];
13
13
  export const VALID_SPECIES = ["openclaw", "vellum"] as const;
14
14
  export type Species = (typeof VALID_SPECIES)[number];