@vellumai/cli 0.8.11-dev.202606122025.f06346f → 0.8.11-dev.202606122104.e5b9dd5

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.
@@ -41,6 +41,25 @@ describe("runWake", () => {
41
41
  expect(spawnArgs[0]).toEqual(["bun", ["run", "cli", "wake", "asst-42"]]);
42
42
  });
43
43
 
44
+ test("repairGuardian: true appends --repair-guardian to the CLI args", async () => {
45
+ const pending = runWake(invocation, "asst-42", { repairGuardian: true });
46
+ lastChild.emit("close", 0);
47
+
48
+ expect(await pending).toEqual({ ok: true });
49
+ expect(spawnArgs[0]).toEqual([
50
+ "bun",
51
+ ["run", "cli", "wake", "asst-42", "--repair-guardian"],
52
+ ]);
53
+ });
54
+
55
+ test("repairGuardian: false omits the flag", async () => {
56
+ const pending = runWake(invocation, "asst-42", { repairGuardian: false });
57
+ lastChild.emit("close", 0);
58
+
59
+ expect(await pending).toEqual({ ok: true });
60
+ expect(spawnArgs[0]).toEqual(["bun", ["run", "cli", "wake", "asst-42"]]);
61
+ });
62
+
44
63
  test("a non-zero exit resolves to a failure carrying the CLI's output", async () => {
45
64
  const pending = runWake(invocation, "asst-42");
46
65
  lastChild.stderr.emit("data", Buffer.from("no sibling environment to seed from"));
@@ -36,7 +36,7 @@ export type { HatchResult } from "./hatch";
36
36
  export { runRetire } from "./retire";
37
37
  export type { RetireResult } from "./retire";
38
38
  export { runWake } from "./wake";
39
- export type { WakeResult } from "./wake";
39
+ export type { WakeOptions, WakeResult } from "./wake";
40
40
  export { getGuardianAccessToken } from "./guardian-token";
41
41
  export type { TokenResult } from "./guardian-token";
42
42
  export {
@@ -14,6 +14,11 @@ export type WakeResult =
14
14
  | { ok: true }
15
15
  | { ok: false; status: number; error: string };
16
16
 
17
+ export interface WakeOptions {
18
+ /** Pass --repair-guardian to re-provision a missing/expired guardian token. Revokes the assistant's other device-bound tokens, so callers must gate this behind explicit user confirmation. */
19
+ repairGuardian?: boolean;
20
+ }
21
+
17
22
  /**
18
23
  * Start (or restart) a local assistant's daemon and gateway via the CLI's
19
24
  * `wake`, which also re-seeds the guardian token from a sibling environment.
@@ -27,11 +32,17 @@ export type WakeResult =
27
32
  export function runWake(
28
33
  invocation: CliInvocation,
29
34
  assistantId: string,
35
+ options?: WakeOptions,
30
36
  ): Promise<WakeResult> {
31
37
  return new Promise((resolve) => {
32
38
  const child = spawn(
33
39
  invocation.command,
34
- [...invocation.baseArgs, "wake", assistantId],
40
+ [
41
+ ...invocation.baseArgs,
42
+ "wake",
43
+ assistantId,
44
+ ...(options?.repairGuardian ? ["--repair-guardian"] : []),
45
+ ],
35
46
  { stdio: ["ignore", "pipe", "pipe"] },
36
47
  );
37
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.8.11-dev.202606122025.f06346f",
3
+ "version": "0.8.11-dev.202606122104.e5b9dd5",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -272,12 +272,23 @@ describe("vellum wake", () => {
272
272
  expect(leaseGuardianTokenMock).not.toHaveBeenCalled();
273
273
  });
274
274
 
275
- test("skips re-provision when a guardian token already exists", async () => {
275
+ test("re-provisions even when a guardian token already exists", async () => {
276
+ // A connect can 401 off a token whose local state looks healthy
277
+ // (revoked, mis-seeded, wrong principal). The user explicitly confirmed
278
+ // the destructive repair, so the flag forces a re-lease instead of
279
+ // guessing from local token state and recreating the no-op loop.
276
280
  process.argv = ["bun", "vellum", "wake", "--repair-guardian", "local-assistant"];
277
- // loadGuardianToken returns a token by default — recovery must not run.
281
+ // loadGuardianToken returns a healthy-looking token by default.
278
282
  await wake();
279
283
 
280
- expect(resetGuardianBootstrapMock).not.toHaveBeenCalled();
281
- expect(leaseGuardianTokenMock).not.toHaveBeenCalled();
284
+ expect(resetGuardianBootstrapMock).toHaveBeenCalledWith(
285
+ "http://127.0.0.1:7830",
286
+ "generated-bootstrap-secret",
287
+ );
288
+ expect(leaseGuardianTokenMock).toHaveBeenCalledWith(
289
+ "http://127.0.0.1:7830",
290
+ "local-assistant",
291
+ "generated-bootstrap-secret",
292
+ );
282
293
  });
283
294
  });
@@ -84,7 +84,9 @@ describe("buildAuthorizeUrl", () => {
84
84
  });
85
85
 
86
86
  test("login hint is forwarded", () => {
87
+ // generic-examples:ignore-next-line — reason: test fixture for URL encoding, not a real email
87
88
  const url = new URL(buildAuthorizeUrl({ ...base, loginHint: "a@b.co" }));
89
+ // generic-examples:ignore-next-line — reason: test fixture for URL encoding, not a real email
88
90
  expect(url.searchParams.get("login_hint")).toBe("a@b.co");
89
91
 
90
92
  const noHint = new URL(buildAuthorizeUrl(base));
@@ -9,7 +9,6 @@ import {
9
9
  import { dockerResourceNames, wakeContainers } from "../lib/docker.js";
10
10
  import {
11
11
  leaseGuardianToken,
12
- loadGuardianToken,
13
12
  resetGuardianBootstrap,
14
13
  seedGuardianTokenFromSiblingEnv,
15
14
  } from "../lib/guardian-token.js";
@@ -43,7 +42,7 @@ export async function wake(): Promise<void> {
43
42
  " --foreground Run assistant in foreground with logs printed to terminal",
44
43
  );
45
44
  console.log(
46
- " --repair-guardian Re-provision the guardian token if missing (resets the\n" +
45
+ " --repair-guardian Force-re-provision the guardian token (resets the\n" +
47
46
  " gateway bootstrap and re-leases — REVOKES other device-bound\n" +
48
47
  " tokens, so only use deliberately, never from auto-repair)",
49
48
  );
@@ -238,8 +237,11 @@ export async function wake(): Promise<void> {
238
237
  console.log(" Seeded guardian token from sibling environment.");
239
238
  }
240
239
 
241
- // Last-resort recovery (explicit `--repair-guardian` only): if no guardian
242
- // token exists for this env even after sibling seeding, re-provision one. The
240
+ // Last-resort recovery (explicit `--repair-guardian` only): force a
241
+ // re-provision. Token health can't be judged locally a connect can 401
242
+ // off a token whose local expiry looks fine (revoked, mis-seeded, wrong
243
+ // principal) — and the user explicitly confirmed the destructive repair,
244
+ // so guessing "looks healthy, skip" just recreates the no-op loop. The
243
245
  // single-use bootstrap secret may already be spent — a prior connect can
244
246
  // lease a token that's then lost, or the gateway marks the secret consumed
245
247
  // before the client persists it — which otherwise bricks connect into a
@@ -248,7 +250,7 @@ export async function wake(): Promise<void> {
248
250
  // by the lockfile secret — mirrors the macOS client's forceReBootstrap), then
249
251
  // re-lease. Gated behind the flag because the re-lease revokes other
250
252
  // device-bound tokens; it must never run from the automatic repair path.
251
- if (repairGuardian && !loadGuardianToken(entry.assistantId)) {
253
+ if (repairGuardian) {
252
254
  const loopbackUrl = `http://127.0.0.1:${resources.gatewayPort}`;
253
255
  const maxAttempts = 3;
254
256
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {