@agentvault/claude-bridge 0.7.16 → 0.8.0

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/dist/log.d.ts CHANGED
@@ -19,4 +19,16 @@ export declare function stamp(message: string): string;
19
19
  * just anchored to a clock.
20
20
  */
21
21
  export declare function logLine(...args: unknown[]): void;
22
+ /**
23
+ * Plain stdout, no timestamp. For CLI answers a human or a script reads —
24
+ * `doctor`'s report — not for daemon logging.
25
+ *
26
+ * `logLine` stamps every line and writes to stderr, which is right for a daemon
27
+ * and wrong here: a report a customer is meant to read must not wear a date on
28
+ * every row. Anything a script would parse belongs on stdout.
29
+ *
30
+ * This lives in log.ts because the "no bare console sinks" guard excludes only
31
+ * this file — the sink belongs here, and nowhere else.
32
+ */
33
+ export declare function printLine(text: string): void;
22
34
  //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1,24 @@
1
+ export declare function pendingInvitePath(dataDir: string): string;
2
+ /** Persist the token for the service's first boot. Owner-only, unconditionally. */
3
+ export declare function writePendingInvite(dataDir: string, token: string): void;
4
+ /** The pending token, or undefined when there is none to consume. */
5
+ export declare function readPendingInvite(dataDir: string): string | undefined;
6
+ /** Remove it. Idempotent: nothing to remove is a success, not an error. */
7
+ export declare function clearPendingInvite(dataDir: string): void;
8
+ /**
9
+ * Delete the pending invite once there is EVIDENCE it was consumed — i.e.
10
+ * credentials have actually persisted to the data dir.
11
+ *
12
+ * Timing is the whole design here. Deleting on READ (inside loadConfig) would
13
+ * throw the token away before enrolment finished, so a process that died in
14
+ * between would leave a service that can never enrol and a customer whose
15
+ * single-use invite is gone. Never deleting leaves a credential on disk long
16
+ * after it is useless.
17
+ *
18
+ * `credsExist` is injected so the caller supplies `hasPersistedCreds` and this
19
+ * module stays free of a config import cycle.
20
+ *
21
+ * @returns true when a file was actually removed.
22
+ */
23
+ export declare function clearPendingInviteIfConsumed(dataDir: string, credsExist: (dataDir: string) => boolean): boolean;
24
+ //# sourceMappingURL=pending-invite.d.ts.map
@@ -1,7 +1,18 @@
1
1
  import type { ServiceSpec } from "./spec.js";
2
+ /** One installed service found on disk, with the env its definition bakes in. */
3
+ export interface InstalledService {
4
+ label: string;
5
+ /** Absolute path of the plist / unit file. */
6
+ path: string;
7
+ env: Record<string, string>;
8
+ }
2
9
  export interface ServiceBackend {
3
10
  install(spec: ServiceSpec): void;
4
11
  uninstall(label: string): void;
12
+ /** #913 item 3 — every installed service whose label starts with `prefix`,
13
+ * read from the definition DIRECTORY, not from the running-service list. An
14
+ * orphan is installed-but-not-running by construction. */
15
+ list(prefix: string): InstalledService[];
5
16
  /** Restart an ALREADY-INSTALLED service without touching its spec (#837).
6
17
  * Distinct from install, which re-bakes the plist from the current shell. */
7
18
  restart(label: string): void;
@@ -23,6 +34,10 @@ export interface BackendDeps {
23
34
  chmod: (path: string, mode: number) => void;
24
35
  rm: (path: string) => void;
25
36
  exists: (path: string) => boolean;
37
+ /** Names in a directory. Returns [] when the directory does not exist — a
38
+ * machine with no services installed is not an error. */
39
+ readdir: (path: string) => string[];
40
+ readFile: (path: string) => string;
26
41
  }
27
42
  export declare function defaultDeps(): BackendDeps;
28
43
  export declare function selectBackend(platform: NodeJS.Platform, deps?: BackendDeps): ServiceBackend;
@@ -1 +1 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/service/backend.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;kFAC8E;IAC9E,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AAED,wBAAgB,WAAW,IAAI,WAAW,CAoBzC;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,cAAc,CAU3F"}
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/service/backend.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;+DAE2D;IAC3D,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAAC;IACzC;kFAC8E;IAC9E,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC;8DAC0D;IAC1D,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACpC;AAED,wBAAgB,WAAW,IAAI,WAAW,CAoCzC;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,cAAc,CAU3F"}
@@ -0,0 +1,36 @@
1
+ import { type DeviceProbe, type DoctorDeps } from "./doctor.js";
2
+ /**
3
+ * Ask the public status endpoint about one device.
4
+ *
5
+ * `GET /api/v1/devices/{id}/status` needs no auth and is rate limited per
6
+ * device_id, so checking several agents in a row is fine.
7
+ *
8
+ * ⚠️ A 404 is the strongest claim this command makes — it is what turns into
9
+ * "orphan, run uninstall". So it is only believed when the body is JSON, i.e.
10
+ * an actual API error shape. `agentvault.dev` serves a GoDaddy parked page that
11
+ * answers with confident HTTP codes and no API behind it, and on 2026-07-25
12
+ * that fooled a smoke check into reporting a healthy backend. A wrong
13
+ * AV_API_URL must read as "I could not check", never as "all your agents are
14
+ * dead".
15
+ */
16
+ export declare function fetchDeviceStatus(apiUrl: string, deviceId: string, doFetch?: typeof fetch): Promise<DeviceProbe>;
17
+ export declare function realDoctorDeps(env: NodeJS.ProcessEnv): DoctorDeps;
18
+ /**
19
+ * Handle `agentvault-claude-bridge doctor`. Returns true when handled.
20
+ *
21
+ * Async, which is why it does not live in maybeRunServiceSubcommand: that
22
+ * dispatcher is synchronous and doctor has to await the network. index.ts runs
23
+ * this FIRST. If it ever did not, `doctor` would fall through to the
24
+ * unknown-subcommand guard and throw — loud, and safe. A typo must never boot a
25
+ * daemon (#921), and neither must a command we forgot to wire.
26
+ *
27
+ * READ-ONLY by construction: it never calls install, uninstall or restart. It
28
+ * prints the command instead, because deciding to remove a customer's service
29
+ * is theirs to make with the evidence in front of them.
30
+ */
31
+ export declare function maybeRunDoctorSubcommand(argv: string[], opts?: {
32
+ log?: (msg: string) => void;
33
+ deps?: DoctorDeps;
34
+ env?: NodeJS.ProcessEnv;
35
+ }): Promise<boolean>;
36
+ //# sourceMappingURL=doctor-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-cli.d.ts","sourceRoot":"","sources":["../../src/service/doctor-cli.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AAsBrB;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,WAAW,CAAC,CAuBtB;AA+BD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,UAAU,CAsBjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,GACrF,OAAO,CAAC,OAAO,CAAC,CAelB"}
@@ -0,0 +1,93 @@
1
+ import type { ServiceBackend } from "./backend.js";
2
+ /**
3
+ * #913 item 3 — `doctor`: list what is installed, check each against the API,
4
+ * and say which ones are orphaned.
5
+ *
6
+ * Deleting an agent in the dashboard removes the DEVICE ROW and nothing else.
7
+ * The service stays installed, pointing at a device that no longer exists, and
8
+ * the customer has no signal at all. Item 1 names the uninstall command AT
9
+ * DELETE TIME; item 2 removes the service when the running bridge is told the
10
+ * device is gone. Neither helps the machine that already accumulated orphans —
11
+ * from a delete done in another browser, before those shipped, or while the
12
+ * bridge was not running. That is what this reads.
13
+ *
14
+ * TWO RULES, both earned the hard way:
15
+ *
16
+ * 1. AN UNREACHABLE API IS NOT AN ORPHAN. #919: a default for a missing input is
17
+ * a guess, and reporting on it is a lie. Every inconclusive probe is SKIP,
18
+ * never FAIL — including a status string this build does not recognise.
19
+ * Printing FAIL on a network blip sends a customer to uninstall a healthy
20
+ * agent.
21
+ *
22
+ * 2. SILENCE ABOUT WHAT IT CANNOT SEE IS A FALSE ALL-CLEAR. #941: the delete
23
+ * modal covered 3 of 15 devices and said nothing for the other 12, which
24
+ * reads as "nothing to clean up". This command can only check its own family
25
+ * — the agents whose credentials it can read — so it LISTS the other
26
+ * AgentVault services it found and says it did not check them.
27
+ */
28
+ export type Verdict = "PASS" | "WARN" | "FAIL" | "SKIP";
29
+ /** What the server said about one device. Exactly one field is meaningful. */
30
+ export interface DeviceProbe {
31
+ /** The device's status string, verbatim from the API. */
32
+ status?: string;
33
+ /** 404 — the device row does not exist. */
34
+ gone?: boolean;
35
+ /** Why the check could not conclude. Present => the answer is UNKNOWN. */
36
+ inconclusive?: string;
37
+ }
38
+ export interface ServiceFinding {
39
+ label: string;
40
+ agentName: string;
41
+ dataDir: string;
42
+ loaded: boolean;
43
+ deviceId: string;
44
+ verdict: Verdict;
45
+ detail: string;
46
+ /** The exact command that fixes it. Set ONLY for a confirmed orphan. */
47
+ fix?: string;
48
+ }
49
+ export interface UnsupervisedFinding {
50
+ dataDir: string;
51
+ agentName: string;
52
+ deviceId: string;
53
+ verdict: Verdict;
54
+ detail: string;
55
+ /** How to bring a live agent back. Never a suggestion to delete key material:
56
+ * `uninstall` leaves credentials ON PURPOSE, and a doctor that says "remove
57
+ * this directory" turns a recoverable mistake into a lost identity. */
58
+ fix?: string;
59
+ }
60
+ export interface DoctorReport {
61
+ labelPrefixes: string[];
62
+ services: ServiceFinding[];
63
+ /** Credentials on disk that no installed service points at — CHECKED against
64
+ * the API, not merely listed. "Leftover from a deleted agent" and "a live
65
+ * agent whose service was removed" look identical on disk and need opposite
66
+ * responses, so the verdict is the whole point. */
67
+ unsupervised: UnsupervisedFinding[];
68
+ /** AgentVault services from other families. Found, listed, NOT checked. */
69
+ notChecked: string[];
70
+ }
71
+ export interface DoctorDeps {
72
+ backend: ServiceBackend;
73
+ /** Every label prefix this bridge OWNS — plural because #837 renamed the
74
+ * claude label and pre-rename installs are still registered under the old
75
+ * one. Those are the longest-running services on customer machines, i.e. the
76
+ * ones most likely to have accumulated an orphan. */
77
+ labelPrefixes: string[];
78
+ base: string;
79
+ apiUrl: string;
80
+ uninstallCommand: (agentName: string) => string;
81
+ installCommand: (agentName: string) => string;
82
+ readCreds: (dataDir: string) => {
83
+ deviceId?: string;
84
+ } | null;
85
+ /** Absolute data dirs under `base` that hold credentials. */
86
+ credDirs: () => string[];
87
+ fetchStatus: (apiUrl: string, deviceId: string) => Promise<DeviceProbe>;
88
+ /** Labels belonging to other AgentVault families installed on this machine. */
89
+ otherLabels: () => string[];
90
+ }
91
+ export declare function buildDoctorReport(d: DoctorDeps): Promise<DoctorReport>;
92
+ export declare function formatDoctorReport(r: DoctorReport): string[];
93
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/service/doctor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAExD,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf;;4EAEwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B;;;wDAGoD;IACpD,YAAY,EAAE,mBAAmB,EAAE,CAAC;IACpC,2EAA2E;IAC3E,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,cAAc,CAAC;IACxB;;;0DAGsD;IACtD,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9C,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7D,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACxE,+EAA+E;IAC/E,WAAW,EAAE,MAAM,MAAM,EAAE,CAAC;CAC7B;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAqB5E;AAiJD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,CAqD5D"}
@@ -1,7 +1,26 @@
1
1
  import type { ServiceSpec } from "./spec.js";
2
- import type { BackendDeps, ServiceBackend } from "./backend.js";
2
+ import type { BackendDeps, ServiceBackend, InstalledService } from "./backend.js";
3
3
  /** Render a launchd LaunchAgent plist for a bridge ServiceSpec. Pure string. */
4
4
  export declare function renderLaunchdPlist(spec: ServiceSpec): string;
5
+ /**
6
+ * Read the EnvironmentVariables dict back out of an installed plist.
7
+ *
8
+ * Deliberately tolerant of formatting, and deliberately NOT tolerant of scope.
9
+ *
10
+ * TOLERANT: `loopita`'s plist is hand-edited, so it keeps none of the
11
+ * whitespace `renderLaunchdPlist` writes. A parser that only understood its own
12
+ * output would report "no data dir" for precisely the long-lived install a
13
+ * customer is most likely to be asking `doctor` about.
14
+ *
15
+ * NOT TOLERANT: it reads ONLY the EnvironmentVariables dict. `Label`,
16
+ * `WorkingDirectory` and `StandardOutPath` are <key>/<string> pairs too, so a
17
+ * whole-file scan would hand back a log path as if it were AV_DATA_DIR and then
18
+ * go looking for credentials in it.
19
+ *
20
+ * Returns {} for junk. A doctor that throws on one malformed plist reports
21
+ * nothing about the other five.
22
+ */
23
+ export declare function parsePlistEnv(xml: string): Record<string, string>;
5
24
  export declare class LaunchdBackend implements ServiceBackend {
6
25
  private deps;
7
26
  constructor(deps: BackendDeps);
@@ -17,5 +36,12 @@ export declare class LaunchdBackend implements ServiceBackend {
17
36
  loaded: boolean;
18
37
  detail: string;
19
38
  };
39
+ /** #913 item 3 — every installed service whose label starts with `prefix`.
40
+ *
41
+ * Reads the plist directory rather than `launchctl list`, because an ORPHAN
42
+ * is typically installed-but-not-loaded: the revoked device exits 0 and
43
+ * KeepAlive leaves it down. `launchctl list` would not show the very thing
44
+ * doctor exists to find. */
45
+ list(prefix: string): InstalledService[];
20
46
  }
21
47
  //# sourceMappingURL=launchd.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"launchd.d.ts","sourceRoot":"","sources":["../../src/service/launchd.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAUhE,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAkC5D;AAED,qBAAa,cAAe,YAAW,cAAc;IACvC,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,WAAW;IAErC,OAAO,CAAC,SAAS;IAGjB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IA2BhC,OAAO,CAAC,aAAa;IAOrB;+EAC2E;IAC3E,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI5B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CAI3D"}
1
+ {"version":3,"file":"launchd.d.ts","sourceRoot":"","sources":["../../src/service/launchd.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAUlF,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAkC5D;AAGD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA6BjE;AAOD,qBAAa,cAAe,YAAW,cAAc;IACvC,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,WAAW;IAErC,OAAO,CAAC,SAAS;IAGjB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IA2BhC,OAAO,CAAC,aAAa;IAOrB;+EAC2E;IAC3E,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI5B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAK1D;;;;;iCAK6B;IAC7B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE;CAezC"}
@@ -0,0 +1,19 @@
1
+ import { type ServiceBackend } from "./backend.js";
2
+ export interface SelfUninstallDeps {
3
+ backend?: ServiceBackend;
4
+ log?: (msg: string) => void;
5
+ }
6
+ /**
7
+ * Remove this agent's supervised service after its device was revoked.
8
+ *
9
+ * Returns whether a service was actually removed. NEVER throws: this runs on
10
+ * the shutdown path, and a cleanup that cannot happen must not stop the bridge
11
+ * exiting cleanly — `KeepAlive {SuccessfulExit: false}` depends on that exit(0)
12
+ * to leave the service down rather than crash-looping it.
13
+ *
14
+ * The stakes are asymmetric and worth stating: `uninstall` removes the SERVICE
15
+ * and LEAVES the credentials on disk. A false positive therefore costs the
16
+ * customer a re-install, not their identity.
17
+ */
18
+ export declare function selfUninstallOnTerminal(reason: string, env: NodeJS.ProcessEnv, deps?: SelfUninstallDeps): boolean;
19
+ //# sourceMappingURL=self-uninstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-uninstall.d.ts","sourceRoot":"","sources":["../../src/service/self-uninstall.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAuBlE,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAoCT"}
@@ -4,6 +4,18 @@ export interface ServiceCmdDeps {
4
4
  entrypoint: string;
5
5
  log: (msg: string) => void;
6
6
  }
7
+ /**
8
+ * #837: resolve the label that is ACTUALLY loaded rather than assuming the
9
+ * computed one.
10
+ *
11
+ * The launchd label was renamed (`com.agentvault.claude-bridge.<name>` ->
12
+ * `dev.agentvault.bridge-<name>`), so a hardcoded name fails with "Could not
13
+ * find service" on precisely the long-standing installs where an operator most
14
+ * needs `restart` or `status` to work. Prefer the current label, fall back to
15
+ * the legacy one, and if neither is loaded return the current label so the
16
+ * error names what we tried.
17
+ */
18
+ export declare function resolveLabel(backend: ServiceBackend, env: NodeJS.ProcessEnv): string;
7
19
  /** Handle `agentvault-claude-bridge <install|uninstall|status|logs>` before boot.
8
20
  * Returns true when handled (caller should exit). Secrets are never placed on
9
21
  * the service command line — config flows via the plist env; the token is omitted. */
@@ -1 +1 @@
1
- {"version":3,"file":"subcommand.d.ts","sourceRoot":"","sources":["../../src/service/subcommand.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAElE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B;AAuDD;;uFAEuF;AACvF,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CA0FT"}
1
+ {"version":3,"file":"subcommand.d.ts","sourceRoot":"","sources":["../../src/service/subcommand.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAElE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B;AAoCD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAMpF;AAED;;uFAEuF;AACvF,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAsHT"}
@@ -1,10 +1,21 @@
1
1
  import type { ServiceSpec } from "./spec.js";
2
- import type { BackendDeps, ServiceBackend } from "./backend.js";
2
+ import type { BackendDeps, ServiceBackend, InstalledService } from "./backend.js";
3
3
  /** Render a systemd --user service unit for a bridge ServiceSpec. Pure string.
4
4
  * Restart=on-failure + SuccessExitStatus=0 is the KeepAlive={SuccessfulExit:false}
5
5
  * equivalent: a clean exit(0) (revoked device) is treated as success => NOT
6
6
  * restarted; a non-zero crash is restarted. WantedBy=default.target is RunAtLoad. */
7
7
  export declare function renderSystemdUnit(spec: ServiceSpec): string;
8
+ /**
9
+ * Read `Environment=` back out of an installed systemd unit.
10
+ *
11
+ * Reverses `q` exactly: un-double the '%' first, then walk the backslash
12
+ * escapes left to right in ONE pass. Two passes would turn a literal
13
+ * backslash-n (written as \\n) into a newline.
14
+ *
15
+ * Returns {} for junk — a doctor that throws on one malformed unit reports
16
+ * nothing about the others.
17
+ */
18
+ export declare function parseUnitEnv(text: string): Record<string, string>;
8
19
  export declare class SystemdBackend implements ServiceBackend {
9
20
  private deps;
10
21
  constructor(deps: BackendDeps);
@@ -18,5 +29,12 @@ export declare class SystemdBackend implements ServiceBackend {
18
29
  loaded: boolean;
19
30
  detail: string;
20
31
  };
32
+ /** #913 item 3 — every installed unit whose label starts with `prefix`.
33
+ *
34
+ * Reads the unit directory rather than `systemctl list-units`, because an
35
+ * ORPHAN is installed-but-inactive: the revoked device exits 0, which
36
+ * SuccessExitStatus=0 treats as success, so it is never restarted. A
37
+ * running-units listing would not show the thing doctor exists to find. */
38
+ list(prefix: string): InstalledService[];
21
39
  }
22
40
  //# sourceMappingURL=systemd.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"systemd.d.ts","sourceRoot":"","sources":["../../src/service/systemd.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAmBhE;;;sFAGsF;AACtF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAsB3D;AAED,qBAAa,cAAe,YAAW,cAAc;IACvC,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,WAAW;IAErC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAoBhC;2DACuD;IACvD,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI5B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CAQ3D"}
1
+ {"version":3,"file":"systemd.d.ts","sourceRoot":"","sources":["../../src/service/systemd.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAmBlF;;;sFAGsF;AACtF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAsB3D;AAGD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWjE;AAQD,qBAAa,cAAe,YAAW,cAAc;IACvC,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,WAAW;IAErC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAoBhC;2DACuD;IACvD,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI5B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAS1D;;;;;gFAK4E;IAC5E,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE;CAczC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.7.16",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "AgentVault Claude Bridge — daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
6
6
  "main": "dist/index.js",