@securecoms/agent-bridge 0.3.2 → 0.5.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.
Files changed (47) hide show
  1. package/README.md +21 -1
  2. package/dist/bin.js +1073 -90
  3. package/dist/bridge-agent.d.ts +68 -7
  4. package/dist/config.d.ts +7 -0
  5. package/dist/http-server.d.ts +42 -0
  6. package/dist/index.js +837 -63
  7. package/dist/memory/hermes-provider.d.ts +21 -0
  8. package/dist/memory/hermes-session-map.d.ts +8 -0
  9. package/dist/memory/hermes-store.d.ts +32 -0
  10. package/dist/memory/hermes-test-fixture.d.ts +5 -0
  11. package/dist/memory/openclaw-provider.d.ts +24 -0
  12. package/dist/memory/openclaw-session-log.d.ts +15 -0
  13. package/dist/memory/openclaw-session-map.d.ts +7 -0
  14. package/dist/memory/provider.d.ts +29 -0
  15. package/dist/memory-taps.d.ts +13 -0
  16. package/dist/preflight-modern.cjs +35 -0
  17. package/dist/preflight-modern.d.cts +1 -0
  18. package/dist/preflight-upgrade.d.cts +30 -0
  19. package/dist/preflight.cjs +277 -0
  20. package/dist/preflight.d.cts +14 -0
  21. package/dist/service/config.d.ts +1 -0
  22. package/dist/service/install-prep.d.ts +68 -0
  23. package/package.json +3 -5
  24. package/dist/attachments.js +0 -49
  25. package/dist/auth.js +0 -56
  26. package/dist/bin.js.pre-c1-f803007 +0 -5795
  27. package/dist/bin.js.pre-c3-096a9a3 +0 -5832
  28. package/dist/bin.js.pre-ffrebuild-b162d4a +0 -5639
  29. package/dist/bin.js.pre-kpheal-20260708-150924 +0 -5531
  30. package/dist/bin.js.pre-kppersist-20260708-152341 +0 -5543
  31. package/dist/bin.js.pre-p0-20260708-102415 +0 -5196
  32. package/dist/bin.js.pre-p1b-02f47e1 +0 -5639
  33. package/dist/bin.js.pre-warmfix-20260708-153705 +0 -5613
  34. package/dist/bin.js.pretrace-113205 +0 -5395
  35. package/dist/bridge-agent.js +0 -831
  36. package/dist/config.js +0 -74
  37. package/dist/coordination.js +0 -63
  38. package/dist/errors.js +0 -25
  39. package/dist/http-server.js +0 -220
  40. package/dist/index.js.pre-p0-20260708-102415 +0 -4879
  41. package/dist/latency.js +0 -87
  42. package/dist/rate-limit.js +0 -56
  43. package/dist/service/config.js +0 -26
  44. package/dist/service/install.js +0 -60
  45. package/dist/service/launchd.js +0 -30
  46. package/dist/service/systemd.js +0 -25
  47. package/dist/ws-server.js +0 -254
@@ -0,0 +1,21 @@
1
+ import type { MemoryProvider } from "./provider.js";
2
+ import type { MemoryRecordInput, MemoryContextResult } from "@agentvault/connect-mls-store";
3
+ /**
4
+ * Backs the MemoryProvider interface with Hermes's own ~/.hermes brain:
5
+ * context() reads state.db recent turns; record() writes voice turns back.
6
+ * Text records are no-ops (Hermes persists text natively). Fully fail-safe.
7
+ */
8
+ export declare class HermesMemoryProvider implements MemoryProvider {
9
+ private readonly baseDir;
10
+ private readonly store;
11
+ private noSession;
12
+ private skipped;
13
+ private writeFailed;
14
+ constructor(baseDir: string);
15
+ record(input: MemoryRecordInput): Promise<{
16
+ seq: number;
17
+ deduped: boolean;
18
+ }>;
19
+ context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
20
+ stats(): Record<string, number>;
21
+ }
@@ -0,0 +1,8 @@
1
+ /** Expand a leading ~ to the user's home dir. Absolute/relative paths pass through. */
2
+ export declare function expandHome(p: string): string;
3
+ /**
4
+ * Resolve a SecureComs DM channel to its Hermes state.db session id via
5
+ * sessions.json, or null. DM keys only (group keys are out of scope this slice).
6
+ * Fail-safe: any read/parse error returns null.
7
+ */
8
+ export declare function resolveHermesSession(baseDir: string, conversationId: string): string | null;
@@ -0,0 +1,32 @@
1
+ export interface HermesTurn {
2
+ id: number;
3
+ role: "user" | "assistant";
4
+ content: string;
5
+ timestamp: number;
6
+ }
7
+ export declare class HermesStore {
8
+ private readonly dbPath;
9
+ constructor(baseDir: string);
10
+ /** Recent-K conversational turns for a session, chronological. Fail-safe → []. */
11
+ readRecent(sessionId: string, limit: number): HermesTurn[];
12
+ private open;
13
+ /**
14
+ * Append a voice turn to a session in one synchronous BEGIN IMMEDIATE block
15
+ * (no await between statements → atomic against Hermes and our own retries).
16
+ * Guards: skip when the session is absent/archived/ended; in-transaction dedup
17
+ * on (session_id, platform_message_id). Writes only `messages`. Fail-safe.
18
+ */
19
+ writeVoiceTurn(input: {
20
+ sessionId: string;
21
+ role: "user" | "assistant";
22
+ content: string;
23
+ dedupKey: string | null;
24
+ timestamp: number;
25
+ }): "written" | "deduped" | "skipped" | "failed";
26
+ /**
27
+ * Startup write-capability probe: insert a sentinel against any existing
28
+ * session inside a rolled-back transaction. Throws if the write path is broken
29
+ * (e.g. a schema regression). No-op when the db has no sessions.
30
+ */
31
+ probeWrite(): void;
32
+ }
@@ -0,0 +1,5 @@
1
+ /** Build a state.db that mirrors the load-bearing parts of Hermes's schema. */
2
+ export declare function makeHermesFixture(base: string, sessionId: string, opts?: {
3
+ archived?: number;
4
+ endedAt?: number | null;
5
+ }): void;
@@ -0,0 +1,24 @@
1
+ import type { MemoryProvider } from "./provider.js";
2
+ import type { SqliteMlsStateStore, MemoryRecordInput, MemoryContextResult } from "@agentvault/connect-mls-store";
3
+ /**
4
+ * MemoryProvider for an OpenClaw-hosted agent (Cortina). context() merges
5
+ * OpenClaw's own text history (read-only, plain session .jsonl) with the
6
+ * SecureComs daemon-local voice turns, ordered on one wall-clock. record()
7
+ * stores voice turns in the SC store ([voice]-prefixed); text is a no-op
8
+ * (OpenClaw owns text). OpenClaw files are NEVER written. Fully fail-safe:
9
+ * any text-side failure degrades to voice-only.
10
+ */
11
+ export declare class OpenClawMemoryProvider implements MemoryProvider {
12
+ private readonly store;
13
+ private readonly baseDir;
14
+ private readonly agent;
15
+ private noSession;
16
+ private readFailed;
17
+ constructor(store: SqliteMlsStateStore, baseDir: string, agent: string);
18
+ record(input: MemoryRecordInput): Promise<{
19
+ seq: number;
20
+ deduped: boolean;
21
+ }>;
22
+ context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
23
+ stats(): Record<string, number>;
24
+ }
@@ -0,0 +1,15 @@
1
+ export interface OpenClawTurn {
2
+ role: "peer" | "agent";
3
+ text: string;
4
+ ts: number;
5
+ }
6
+ /**
7
+ * Read the last K conversational turns from the agent's owner-DM session log,
8
+ * the CLEAN plain <baseDir>/agents/<agent>/sessions/<sessionId>.jsonl (NEVER the
9
+ * .trajectory.jsonl, a .reset.* snapshot, or the `main` log). Ordering uses the
10
+ * top-level ISO `timestamp`; the inner message.timestamp is unreliable and unused.
11
+ * Normalization is an allowlist gated on role: keep user (string) and assistant
12
+ * (joined `text` blocks only); skip every other role and empty (tool-only)
13
+ * assistant turns. Fail-safe: any read/parse error returns [].
14
+ */
15
+ export declare function readRecentTurns(baseDir: string, agent: string, sessionId: string, k: number): OpenClawTurn[];
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Resolve the agent's single OpenClaw owner-DM session id from sessions.json.
3
+ * OpenClaw collapses every owner DM into one `…:direct:…:owner` session and
4
+ * discards the SecureComs channel id, so the SC conversationId is NOT used here.
5
+ * Fail-safe: any read/parse error, or no owner-direct key, returns null.
6
+ */
7
+ export declare function resolveOwnerDmSession(baseDir: string, agent: string): string | null;
@@ -0,0 +1,29 @@
1
+ import type { SqliteMlsStateStore, MemoryRecordInput, MemoryContextResult } from "@agentvault/connect-mls-store";
2
+ export interface MemoryProvider {
3
+ record(input: MemoryRecordInput): Promise<{
4
+ seq: number;
5
+ deduped: boolean;
6
+ }>;
7
+ context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
8
+ /** Optional adapter counters surfaced on the daemon healthcheck. */
9
+ stats?(): Record<string, number>;
10
+ }
11
+ /** Default: the SecureComs-owned, daemon-local store (encrypted at rest). */
12
+ export declare class SecureComsLocalMemory implements MemoryProvider {
13
+ private readonly store;
14
+ constructor(store: SqliteMlsStateStore);
15
+ record(input: MemoryRecordInput): Promise<{
16
+ seq: number;
17
+ deduped: boolean;
18
+ }>;
19
+ context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
20
+ }
21
+ /** BYO / disabled: the harness owns memory; SecureComs keeps none. */
22
+ export declare class NullMemory implements MemoryProvider {
23
+ record(): Promise<{
24
+ seq: number;
25
+ deduped: boolean;
26
+ }>;
27
+ context(): Promise<MemoryContextResult>;
28
+ }
29
+ export declare function createMemoryProvider(provider: "securecoms" | "none" | "hermes" | "openclaw", store: SqliteMlsStateStore, baseDir?: string, agent?: string): MemoryProvider;
@@ -0,0 +1,13 @@
1
+ import type { MemoryRecordInput } from "@agentvault/connect-mls-store";
2
+ interface MemorySink {
3
+ record(input: MemoryRecordInput): Promise<{
4
+ seq: number;
5
+ deduped: boolean;
6
+ }>;
7
+ }
8
+ /** Record an inbound (peer) text turn the agent actually consumed. Best-effort:
9
+ * never throws, so a memory failure can't break message delivery. */
10
+ export declare function recordInboundMemory(sink: MemorySink, channelId: string, senderId: string, text: string, messageId: string): Promise<void>;
11
+ /** Record the agent's own outbound text reply. Best-effort. */
12
+ export declare function recordOutboundMemory(sink: MemorySink, channelId: string, text: string, messageId: string): Promise<void>;
13
+ export {};
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/preflight-modern.cts
21
+ var preflight_modern_exports = {};
22
+ __export(preflight_modern_exports, {
23
+ passThrough: () => passThrough
24
+ });
25
+ module.exports = __toCommonJS(preflight_modern_exports);
26
+ var path = require("node:path");
27
+ function passThrough() {
28
+ const binPath = path.join(__dirname, "bin.js");
29
+ process.argv[1] = binPath;
30
+ return import(binPath);
31
+ }
32
+ // Annotate the CommonJS export names for ESM import in node:
33
+ 0 && (module.exports = {
34
+ passThrough
35
+ });
@@ -0,0 +1 @@
1
+ export declare function passThrough(): Promise<unknown>;
@@ -0,0 +1,30 @@
1
+ export declare function parseShasums(text: string, arch: string): {
2
+ filename: string;
3
+ sha256: string;
4
+ } | null;
5
+ export declare function sha256Hex(buf: Buffer): string;
6
+ export declare function promptYesNo(question: string, deps: {
7
+ isTTY: boolean;
8
+ ask: (q: string) => Promise<string>;
9
+ stderr: (s: string) => void;
10
+ }): Promise<boolean>;
11
+ export interface UpgradeDeps {
12
+ platform: string;
13
+ arch: string;
14
+ isTTY: boolean;
15
+ stderr: (s: string) => void;
16
+ ask: (q: string) => Promise<string>;
17
+ hasBrew: () => boolean;
18
+ runBrew: () => number;
19
+ fetchText: (url: string) => Promise<string>;
20
+ fetchBinary: (url: string, destPath: string) => Promise<void>;
21
+ fileSha256: (p: string) => string;
22
+ runSudoInstaller: (pkgPath: string) => number;
23
+ tmpdir: () => string;
24
+ cleanup: (p: string) => void;
25
+ resolveNewNode: () => string | null;
26
+ reexec: (nodePath: string) => boolean;
27
+ }
28
+ export declare function guidedUpgrade(deps: UpgradeDeps): Promise<boolean>;
29
+ export declare function resolveNewNodeFrom(probe: (p: string) => string | null, candidates?: string[]): string | null;
30
+ export declare function realUpgradeDeps(originalArgv: string[]): UpgradeDeps;
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
+
24
+ // src/preflight-upgrade.cts
25
+ var preflight_upgrade_exports = {};
26
+ __export(preflight_upgrade_exports, {
27
+ guidedUpgrade: () => guidedUpgrade,
28
+ parseShasums: () => parseShasums,
29
+ promptYesNo: () => promptYesNo,
30
+ realUpgradeDeps: () => realUpgradeDeps,
31
+ resolveNewNodeFrom: () => resolveNewNodeFrom,
32
+ sha256Hex: () => sha256Hex
33
+ });
34
+ function parseShasums(text, arch) {
35
+ const want = "-darwin-" + arch + ".pkg";
36
+ const lines = String(text).split("\n");
37
+ for (let i = 0; i < lines.length; i++) {
38
+ const m = lines[i].trim().match(/^([0-9a-f]{6,64})\s+(\S+)$/);
39
+ if (m && m[2].indexOf(want) !== -1 && m[2].indexOf("node-v24.") === 0) {
40
+ return { filename: m[2], sha256: m[1] };
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ function sha256Hex(buf) {
46
+ const crypto = require("crypto");
47
+ return crypto.createHash("sha256").update(buf).digest("hex");
48
+ }
49
+ async function promptYesNo(question, deps) {
50
+ if (!deps.isTTY) return false;
51
+ const a = (await deps.ask(question)).trim().toLowerCase();
52
+ return a === "y" || a === "yes";
53
+ }
54
+ async function guidedUpgrade(deps) {
55
+ if (deps.platform !== "darwin") return false;
56
+ try {
57
+ const viaBrew = deps.hasBrew();
58
+ const source = viaBrew ? "Homebrew (brew install node \u2014 user-space, no sudo)" : "the official installer from " + DIST_BASE + " (needs your admin password)";
59
+ const ok = await promptYesNo(
60
+ "[bridge] Node 24 LTS is required. Install it now via " + source + "? [y/N] ",
61
+ deps
62
+ );
63
+ if (!ok) return false;
64
+ if (viaBrew) {
65
+ deps.stderr("[bridge] running: brew install node\n");
66
+ if (deps.runBrew() !== 0) return false;
67
+ } else {
68
+ const shasums = await deps.fetchText(DIST_BASE + "/SHASUMS256.txt");
69
+ const entry = parseShasums(shasums, deps.arch);
70
+ if (!entry) return false;
71
+ const dest = deps.tmpdir() + "/" + entry.filename;
72
+ deps.stderr("[bridge] downloading " + DIST_BASE + "/" + entry.filename + "\n");
73
+ try {
74
+ await deps.fetchBinary(DIST_BASE + "/" + entry.filename, dest);
75
+ if (deps.fileSha256(dest) !== entry.sha256) {
76
+ deps.stderr("[bridge] checksum mismatch \u2014 aborting install (file discarded)\n");
77
+ return false;
78
+ }
79
+ deps.stderr("[bridge] running: sudo installer -pkg " + entry.filename + " -target /\n");
80
+ if (deps.runSudoInstaller(dest) !== 0) return false;
81
+ } finally {
82
+ deps.cleanup(dest);
83
+ }
84
+ }
85
+ const fresh = deps.resolveNewNode();
86
+ if (!fresh) return false;
87
+ deps.stderr("[bridge] Node 24 installed \u2014 continuing under " + fresh + "\n");
88
+ return deps.reexec(fresh);
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+ function resolveNewNodeFrom(probe, candidates) {
94
+ const list = candidates || NODE_CANDIDATES;
95
+ for (let i = 0; i < list.length; i++) {
96
+ const v = probe(list[i]);
97
+ if (v && Number(String(v).replace(/^v/, "").split(".")[0]) >= 24) return list[i];
98
+ }
99
+ return null;
100
+ }
101
+ function realUpgradeDeps(originalArgv) {
102
+ const { spawnSync, execFileSync } = require("child_process");
103
+ const { createWriteStream, readFileSync, rmSync } = require("fs");
104
+ const os = require("os");
105
+ const https = require("https");
106
+ const readline = require("readline");
107
+ function fetch(url, redirects) {
108
+ return new Promise((resolve, reject) => {
109
+ https.get(url, (res) => {
110
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location && redirects > 0) {
111
+ res.resume();
112
+ fetch(res.headers.location, redirects - 1).then(resolve, reject);
113
+ } else if (res.statusCode !== 200) {
114
+ res.resume();
115
+ reject(new Error("HTTP " + res.statusCode + " for " + url));
116
+ } else resolve(res);
117
+ }).on("error", reject);
118
+ });
119
+ }
120
+ return {
121
+ platform: process.platform,
122
+ arch: process.arch,
123
+ isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
124
+ stderr: (s) => process.stderr.write(s),
125
+ ask: (q) => new Promise((resolve) => {
126
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
127
+ rl.question(q, (a) => {
128
+ rl.close();
129
+ resolve(a);
130
+ });
131
+ }),
132
+ hasBrew: () => spawnSync("brew", ["--version"], { stdio: "ignore" }).status === 0,
133
+ runBrew: () => {
134
+ var _a;
135
+ return (_a = spawnSync("brew", ["install", "node"], { stdio: "inherit" }).status) != null ? _a : 1;
136
+ },
137
+ fetchText: async (url) => {
138
+ const res = await fetch(url, 3);
139
+ let out = "";
140
+ for await (const chunk of res) out += chunk;
141
+ return out;
142
+ },
143
+ fetchBinary: async (url, destPath) => {
144
+ const res = await fetch(url, 3);
145
+ await new Promise((resolve, reject) => {
146
+ const ws = createWriteStream(destPath);
147
+ res.pipe(ws);
148
+ ws.on("finish", resolve);
149
+ ws.on("error", reject);
150
+ res.on("error", reject);
151
+ });
152
+ },
153
+ fileSha256: (p) => sha256Hex(readFileSync(p)),
154
+ runSudoInstaller: (pkgPath) => {
155
+ var _a;
156
+ return (_a = spawnSync("sudo", ["installer", "-pkg", pkgPath, "-target", "/"], { stdio: "inherit" }).status) != null ? _a : 1;
157
+ },
158
+ tmpdir: () => os.tmpdir(),
159
+ cleanup: (p) => {
160
+ try {
161
+ rmSync(p, { force: true });
162
+ } catch {
163
+ }
164
+ },
165
+ resolveNewNode: () => resolveNewNodeFrom((p) => {
166
+ try {
167
+ return String(execFileSync(p, ["--version"], { encoding: "utf8" })).trim();
168
+ } catch {
169
+ return null;
170
+ }
171
+ }),
172
+ reexec: (nodePath) => {
173
+ const child = spawnSync(nodePath, originalArgv.slice(1), { stdio: "inherit" });
174
+ process.exit(child.status !== null && child.status !== void 0 ? child.status : child.signal ? 130 : 1);
175
+ }
176
+ };
177
+ }
178
+ var DIST_BASE, NODE_CANDIDATES;
179
+ var init_preflight_upgrade = __esm({
180
+ "src/preflight-upgrade.cts"() {
181
+ "use strict";
182
+ DIST_BASE = "https://nodejs.org/dist/latest-v24.x";
183
+ NODE_CANDIDATES = [
184
+ "/usr/local/bin/node",
185
+ // nodejs.org pkg (stable across upgrades)
186
+ "/opt/homebrew/bin/node",
187
+ // brew symlink (Apple Silicon)
188
+ "/opt/homebrew/opt/node/bin/node"
189
+ ];
190
+ }
191
+ });
192
+
193
+ // src/preflight.cts
194
+ var preflight_exports = {};
195
+ __export(preflight_exports, {
196
+ effectiveVersion: () => effectiveVersion,
197
+ instructionsBlock: () => instructionsBlock,
198
+ meetsFloor: () => meetsFloor,
199
+ parseNodeMajor: () => parseNodeMajor,
200
+ runPreflight: () => runPreflight
201
+ });
202
+ module.exports = __toCommonJS(preflight_exports);
203
+ var NODE_FLOOR = 24;
204
+ var FLOOR_REASON = "requires >= 24 (node:sqlite stable API)";
205
+ function parseNodeMajor(version) {
206
+ const n = Number(String(version).split(".")[0]);
207
+ return Number.isFinite(n) && n > 0 ? n : 0;
208
+ }
209
+ function meetsFloor(version) {
210
+ return parseNodeMajor(version) >= NODE_FLOOR;
211
+ }
212
+ function effectiveVersion(real, faked) {
213
+ if (!faked) return real;
214
+ const f = parseNodeMajor(faked);
215
+ if (f >= 1 && f < parseNodeMajor(real)) return faked;
216
+ return real;
217
+ }
218
+ function instructionsBlock(platform, current) {
219
+ const lines = [
220
+ "[bridge] Node " + current + " is too old \u2014 SecureComs " + FLOOR_REASON + ".",
221
+ "",
222
+ "How to get Node 24 LTS:",
223
+ " 1. Official installer (recommended): https://nodejs.org \u2014 download the",
224
+ " Node 24 LTS installer for your platform and run it."
225
+ ];
226
+ if (platform === "darwin") {
227
+ lines.push(" 2. Homebrew: brew install node");
228
+ }
229
+ lines.push(" " + (platform === "darwin" ? "3" : "2") + ". nvm: nvm install 24 && nvm use 24");
230
+ lines.push(
231
+ "",
232
+ "Then re-run the install command. (Always re-run the install command after",
233
+ "upgrading Node \u2014 the background service pins the Node path it was",
234
+ "installed with.)"
235
+ );
236
+ return lines.join("\n") + "\n";
237
+ }
238
+ async function runPreflight(deps) {
239
+ if (meetsFloor(deps.version)) {
240
+ await deps.delegate();
241
+ return 0;
242
+ }
243
+ if (deps.upgrade) {
244
+ if (await deps.upgrade()) return 0;
245
+ }
246
+ deps.stderr(instructionsBlock(deps.platform, deps.version));
247
+ return 1;
248
+ }
249
+ if (require.main === module) {
250
+ const version = effectiveVersion(
251
+ process.versions.node,
252
+ process.env.SECURECOMS_PREFLIGHT_FAKE_NODE_VERSION
253
+ );
254
+ runPreflight({
255
+ version,
256
+ platform: process.platform,
257
+ isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
258
+ stderr: (s) => process.stderr.write(s),
259
+ // The modern half is only ever require()d here, AFTER the gate passed —
260
+ // old Node never parses its dynamic-import syntax.
261
+ delegate: () => require("./preflight-modern.cjs").passThrough(),
262
+ upgrade: () => {
263
+ const up = (init_preflight_upgrade(), __toCommonJS(preflight_upgrade_exports));
264
+ return up.guidedUpgrade(up.realUpgradeDeps(process.argv));
265
+ }
266
+ }).then((code) => {
267
+ if (code !== 0) process.exit(code);
268
+ });
269
+ }
270
+ // Annotate the CommonJS export names for ESM import in node:
271
+ 0 && (module.exports = {
272
+ effectiveVersion,
273
+ instructionsBlock,
274
+ meetsFloor,
275
+ parseNodeMajor,
276
+ runPreflight
277
+ });
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ export declare function parseNodeMajor(version: string): number;
3
+ export declare function meetsFloor(version: string): boolean;
4
+ export declare function effectiveVersion(real: string, faked: string | undefined): string;
5
+ export declare function instructionsBlock(platform: string, current: string): string;
6
+ export interface PreflightDeps {
7
+ version: string;
8
+ platform: string;
9
+ isTTY: boolean;
10
+ stderr: (s: string) => void;
11
+ delegate: () => Promise<unknown>;
12
+ upgrade: (() => Promise<boolean>) | null;
13
+ }
14
+ export declare function runPreflight(deps: PreflightDeps): Promise<number>;
@@ -20,4 +20,5 @@ export interface ServiceConfig {
20
20
  unitPath: string;
21
21
  logPath: string;
22
22
  }
23
+ export declare function slugify(s: string): string;
23
24
  export declare function deriveServiceConfig(input: ServiceInput): ServiceConfig;
@@ -0,0 +1,68 @@
1
+ import { type ServiceInput } from "./config.js";
2
+ export interface PrepDeps {
3
+ readFile: (p: string) => string;
4
+ writeFileMode: (p: string, data: string, mode: number) => void;
5
+ mkdirp: (p: string, mode?: number) => void;
6
+ fileExists: (p: string) => boolean;
7
+ realpath: (p: string) => string;
8
+ listDir: (p: string) => string[];
9
+ probeBindFree: (port: number) => boolean;
10
+ probeConnect: (port: number) => boolean;
11
+ sleep: (ms: number) => Promise<void>;
12
+ }
13
+ export interface ParsedCredential {
14
+ deviceId: string;
15
+ identityId: string;
16
+ issuedAt: string | null;
17
+ }
18
+ export declare function assertReadableCredential(absPath: string, deps: PrepDeps): ParsedCredential;
19
+ export declare function durableDir(home: string, slug: string): string;
20
+ export declare function durableCredentialPath(home: string, slug: string): string;
21
+ export declare function takeCustody(args: {
22
+ home: string;
23
+ slug: string;
24
+ sourceAbsPath: string;
25
+ parsed: ParsedCredential;
26
+ }, deps: PrepDeps): string;
27
+ export declare const PORT_MIN = 7077;
28
+ export declare const PORT_MAX = 7276;
29
+ export declare function parseClaimedPorts(fileContents: string[]): Set<number>;
30
+ export declare function selectPort(args: {
31
+ launchAgentsDir: string;
32
+ systemdDir: string;
33
+ }, deps: PrepDeps): number;
34
+ export declare function verifyDaemonUp(args: {
35
+ port: number;
36
+ logPath: string;
37
+ attempts?: number;
38
+ delayMs?: number;
39
+ }, deps: PrepDeps): Promise<boolean>;
40
+ export declare function harnessSnippet(args: {
41
+ slug: string;
42
+ port: number;
43
+ tokenFilePath: string;
44
+ harness: string;
45
+ }): string;
46
+ export interface OrchestrateInstallCtx {
47
+ credentialAbsPath: string;
48
+ label?: string;
49
+ explicitPort?: number;
50
+ harness?: string;
51
+ home: string;
52
+ launchAgentsDir?: string;
53
+ systemdDir?: string;
54
+ prepDeps: PrepDeps;
55
+ installService: (input: ServiceInput) => {
56
+ label: string;
57
+ filePath: string;
58
+ logHint: string;
59
+ tokenHint: string;
60
+ };
61
+ buildServiceInput: (durableCredentialPath: string, port: number, slug: string) => ServiceInput;
62
+ verifyAttempts?: number;
63
+ verifyDelayMs?: number;
64
+ }
65
+ export declare function orchestrateInstall(ctx: OrchestrateInstallCtx): Promise<{
66
+ echo: string;
67
+ harnessSnippetShown: boolean;
68
+ }>;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@securecoms/agent-bridge",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
8
- "agent-bridge": "dist/bin.js"
8
+ "agent-bridge": "dist/preflight.cjs"
9
9
  },
10
10
  "exports": {
11
11
  ".": {
@@ -17,13 +17,11 @@
17
17
  "dist",
18
18
  "README.md"
19
19
  ],
20
- "engines": {
21
- "node": ">=24"
22
- },
23
20
  "publishConfig": {
24
21
  "access": "public"
25
22
  },
26
23
  "scripts": {
24
+ "prepack": "npm run build",
27
25
  "build": "tsc && node esbuild.config.mjs",
28
26
  "test": "vitest run",
29
27
  "test:watch": "vitest"