@securecoms/agent-bridge 0.4.0 → 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.
package/README.md CHANGED
@@ -348,6 +348,16 @@ pm2 start "npx @agentvault/connect-agent-bridge --credential ./agent.json" \
348
348
  --name securecoms-bridge
349
349
  ```
350
350
 
351
+ ### Install behavior
352
+
353
+ `agent-bridge install --credential <path>` (launchd on macOS, systemd user unit on Linux) prepares and verifies the service rather than pointing it at your download:
354
+
355
+ - The credential file is validated (readable, valid JSON, all required fields) and **copied into a durable per-agent directory** `~/securecoms-agent-<slug>/` (dir `0700`, file `0600`). The service runs from that copy, so moving or deleting the original download can't break the daemon. A directory already owned by a *different* agent identity is refused, never overwritten.
356
+ - A **free port is auto-selected** from `7077-7276` (lowest free wins; sibling service definitions and live binds both count as taken). Pass `--port N` to override; the explicit port is honored as-is.
357
+ - Install **verifies the daemon actually came up** and fails loudly with the log path if it didn't — no silent crash-loops.
358
+ - Install echoes the credential's `device_id`, `issued_at`, and a redacted api-key fingerprint so you can confirm you installed the newest download. **Re-running install is the only way to update a live (e.g. rotated) credential** — replacing the downloaded file alone does nothing.
359
+ - Optional `--harness openclaw|hermes|custom` prints a paste-ready config snippet with the chosen port and the token-file path (the token itself never appears in output).
360
+
351
361
  ---
352
362
 
353
363
  ## Security
package/dist/bin.js CHANGED
@@ -2725,6 +2725,15 @@ function parseServerFrame(frame) {
2725
2725
  }
2726
2726
 
2727
2727
  // ../connect-sdk-ts/dist/transport/ws-pings.js
2728
+ function base64UrlNoPad(s) {
2729
+ const bytes = new TextEncoder().encode(s);
2730
+ let bin = "";
2731
+ for (const b of bytes)
2732
+ bin += String.fromCharCode(b);
2733
+ const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bytes).toString("base64");
2734
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2735
+ }
2736
+ var BEARER_SUBPROTOCOL = "securecoms.bearer.v1";
2728
2737
  function openDeliveryPings(o) {
2729
2738
  const base = o.backoffBaseMs ?? 500;
2730
2739
  const cap = o.backoffCapMs ?? 8e3;
@@ -2873,8 +2882,7 @@ function openDeliveryPings(o) {
2873
2882
  }
2874
2883
  if (closed || gen !== connectGen)
2875
2884
  return;
2876
- const sep = o.url.includes("?") ? "&" : "?";
2877
- const ws = new o.wsImpl(`${o.url}${sep}token=${encodeURIComponent(token)}`);
2885
+ const ws = new o.wsImpl(o.url, [BEARER_SUBPROTOCOL, base64UrlNoPad(token)]);
2878
2886
  sock = ws;
2879
2887
  ws.onopen = () => {
2880
2888
  push({ reconnected: true });
@@ -2974,8 +2982,8 @@ function openDeliveryPings(o) {
2974
2982
  return Promise.resolve({ value: void 0, done: true });
2975
2983
  if (queue.length)
2976
2984
  return Promise.resolve({ value: queue.shift(), done: false });
2977
- return new Promise((resolve2) => {
2978
- waiter = resolve2;
2985
+ return new Promise((resolve3) => {
2986
+ waiter = resolve3;
2979
2987
  });
2980
2988
  }
2981
2989
  };
@@ -5693,7 +5701,7 @@ var BridgeAgent = class _BridgeAgent {
5693
5701
  let got = this.takeBuffered(channelId);
5694
5702
  if (got.length || waitSeconds <= 0) return got;
5695
5703
  const cap = Math.min(waitSeconds, 50) * 1e3;
5696
- await new Promise((resolve2) => {
5704
+ await new Promise((resolve3) => {
5697
5705
  let fired = false;
5698
5706
  let timer;
5699
5707
  const wake = () => {
@@ -5702,7 +5710,7 @@ var BridgeAgent = class _BridgeAgent {
5702
5710
  clearTimeout(timer);
5703
5711
  const i = this.waiters.indexOf(wake);
5704
5712
  if (i >= 0) this.waiters.splice(i, 1);
5705
- resolve2();
5713
+ resolve3();
5706
5714
  };
5707
5715
  timer = setTimeout(wake, cap);
5708
5716
  this.waiters.push(wake);
@@ -5961,7 +5969,7 @@ function jsonError(res, code, detail) {
5961
5969
  json(res, httpStatus(code), errorBody(code, detail));
5962
5970
  }
5963
5971
  function readBody(req, cap) {
5964
- return new Promise((resolve2, reject) => {
5972
+ return new Promise((resolve3, reject) => {
5965
5973
  let size = 0;
5966
5974
  let done = false;
5967
5975
  const chunks = [];
@@ -5981,7 +5989,7 @@ function readBody(req, cap) {
5981
5989
  req.on("end", () => {
5982
5990
  if (!done) {
5983
5991
  done = true;
5984
- resolve2(Buffer.concat(chunks).toString("utf8"));
5992
+ resolve3(Buffer.concat(chunks).toString("utf8"));
5985
5993
  }
5986
5994
  });
5987
5995
  req.on("error", (e) => {
@@ -6395,8 +6403,8 @@ async function handleInbound(sock, agent, data, inflight) {
6395
6403
  }
6396
6404
 
6397
6405
  // src/bin.ts
6398
- import { readFileSync as readFileSync6, mkdirSync, writeFileSync as writeFileSync2, rmSync, existsSync as existsSync2, realpathSync } from "node:fs";
6399
- import { join as join8, dirname as dirname5 } from "node:path";
6406
+ import { readFileSync as readFileSync6, mkdirSync, writeFileSync as writeFileSync2, rmSync, existsSync as existsSync2, realpathSync, readdirSync } from "node:fs";
6407
+ import { join as join9, dirname as dirname5, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
6400
6408
  import { fileURLToPath } from "node:url";
6401
6409
  import { homedir as homedir2, userInfo } from "node:os";
6402
6410
  import { spawnSync } from "node:child_process";
@@ -6531,6 +6539,161 @@ function uninstallService(input, deps) {
6531
6539
  throw new ServiceUnsupportedError(`Service uninstall isn't supported on ${deps.platform} yet.`);
6532
6540
  }
6533
6541
 
6542
+ // src/service/install-prep.ts
6543
+ import { join as join8 } from "node:path";
6544
+ function assertReadableCredential(absPath, deps) {
6545
+ let raw;
6546
+ try {
6547
+ raw = deps.readFile(absPath);
6548
+ } catch {
6549
+ throw new Error(`install: credential file not found or unreadable: ${absPath}`);
6550
+ }
6551
+ let json2;
6552
+ try {
6553
+ json2 = JSON.parse(raw);
6554
+ } catch {
6555
+ throw new Error(`install: credential file is not valid JSON: ${absPath}`);
6556
+ }
6557
+ for (const k of REQUIRED) {
6558
+ if (typeof json2[k] !== "string" || !json2[k]) {
6559
+ throw new Error(`install: credential file missing required field '${k}': ${absPath}`);
6560
+ }
6561
+ }
6562
+ const issuedAt = typeof json2.issued_at === "string" && json2.issued_at ? json2.issued_at : null;
6563
+ return { deviceId: json2.device_id, identityId: json2.identity_id, issuedAt };
6564
+ }
6565
+ function durableDir(home, slug) {
6566
+ return join8(home, `securecoms-agent-${slug}`);
6567
+ }
6568
+ function durableCredentialPath(home, slug) {
6569
+ return join8(durableDir(home, slug), `${slug}.json`);
6570
+ }
6571
+ function takeCustody(args2, deps) {
6572
+ const dir = durableDir(args2.home, args2.slug);
6573
+ const dest = durableCredentialPath(args2.home, args2.slug);
6574
+ try {
6575
+ if (deps.fileExists(dest) && deps.realpath(args2.sourceAbsPath) === deps.realpath(dest)) return dest;
6576
+ } catch {
6577
+ }
6578
+ if (deps.fileExists(dest)) {
6579
+ try {
6580
+ const existing = JSON.parse(deps.readFile(dest));
6581
+ if (existing.device_id && existing.device_id !== args2.parsed.deviceId) {
6582
+ throw new Error(
6583
+ `install: ${dir} already belongs to a different agent (device ${existing.device_id}). Pass a distinct --label to install this agent in its own directory.`
6584
+ );
6585
+ }
6586
+ } catch (e) {
6587
+ if (e instanceof Error && e.message.startsWith("install:")) throw e;
6588
+ }
6589
+ }
6590
+ deps.mkdirp(dir, 448);
6591
+ deps.writeFileMode(dest, deps.readFile(args2.sourceAbsPath), 384);
6592
+ return dest;
6593
+ }
6594
+ var PORT_MIN = 7077;
6595
+ var PORT_MAX = 7276;
6596
+ function parseClaimedPorts(fileContents) {
6597
+ const claimed = /* @__PURE__ */ new Set();
6598
+ const re = /--port(?:<\/string>\s*<string>|\s+)(\d+)/g;
6599
+ for (const text of fileContents) {
6600
+ let m;
6601
+ while ((m = re.exec(text)) !== null) claimed.add(Number(m[1]));
6602
+ }
6603
+ return claimed;
6604
+ }
6605
+ function readSiblingDefs(dir, prefix, deps) {
6606
+ let names;
6607
+ try {
6608
+ names = deps.listDir(dir);
6609
+ } catch {
6610
+ return [];
6611
+ }
6612
+ const out = [];
6613
+ for (const n of names) {
6614
+ if (!n.startsWith(prefix)) continue;
6615
+ try {
6616
+ out.push(deps.readFile(join8(dir, n)));
6617
+ } catch {
6618
+ }
6619
+ }
6620
+ return out;
6621
+ }
6622
+ function selectPort(args2, deps) {
6623
+ const defs = [
6624
+ ...readSiblingDefs(args2.launchAgentsDir, "com.securecoms.bridge.", deps),
6625
+ ...readSiblingDefs(args2.systemdDir, "securecoms-bridge-", deps)
6626
+ ];
6627
+ const claimed = parseClaimedPorts(defs);
6628
+ for (let port = PORT_MIN; port <= PORT_MAX; port++) {
6629
+ if (claimed.has(port)) continue;
6630
+ if (!deps.probeBindFree(port)) continue;
6631
+ return port;
6632
+ }
6633
+ throw new Error(`install: no free port in ${PORT_MIN}-${PORT_MAX} \u2014 stop an unused bridge or pass --port`);
6634
+ }
6635
+ async function verifyDaemonUp(args2, deps) {
6636
+ const attempts = args2.attempts ?? 20;
6637
+ const delayMs = args2.delayMs ?? 250;
6638
+ for (let i = 0; i < attempts; i++) {
6639
+ if (deps.probeConnect(args2.port)) return true;
6640
+ try {
6641
+ if (deps.readFile(args2.logPath).includes("[bridge] listening on")) return true;
6642
+ } catch {
6643
+ }
6644
+ await deps.sleep(delayMs);
6645
+ }
6646
+ return false;
6647
+ }
6648
+ function harnessSnippet(args2) {
6649
+ const label = args2.harness === "hermes" ? "Hermes" : args2.harness === "openclaw" ? "OpenClaw" : "your harness";
6650
+ return [
6651
+ `# ${label}: point the agent at this bridge`,
6652
+ `SECURECOMS_BRIDGE_URL=ws://127.0.0.1:${args2.port}/v1/ws`,
6653
+ `SECURECOMS_BRIDGE_TOKEN="$(cat ${args2.tokenFilePath})"`
6654
+ ].join("\n");
6655
+ }
6656
+ async function orchestrateInstall(ctx) {
6657
+ const parsed = assertReadableCredential(ctx.credentialAbsPath, ctx.prepDeps);
6658
+ const stemForSlug = ctx.label ?? ctx.credentialAbsPath.replace(/^.*[\\/]/, "").replace(/\.[^.]*$/, "");
6659
+ const slug = slugify(stemForSlug);
6660
+ const durablePath = takeCustody(
6661
+ { home: ctx.home, slug, sourceAbsPath: ctx.credentialAbsPath, parsed },
6662
+ ctx.prepDeps
6663
+ );
6664
+ const port = ctx.explicitPort ?? selectPort(
6665
+ { launchAgentsDir: ctx.launchAgentsDir ?? `${ctx.home}/Library/LaunchAgents`, systemdDir: ctx.systemdDir ?? `${ctx.home}/.config/systemd/user` },
6666
+ ctx.prepDeps
6667
+ );
6668
+ const input = ctx.buildServiceInput(durablePath, port, slug);
6669
+ const result = ctx.installService(input);
6670
+ const up = await verifyDaemonUp(
6671
+ { port, logPath: result.logHint, attempts: ctx.verifyAttempts, delayMs: ctx.verifyDelayMs },
6672
+ ctx.prepDeps
6673
+ );
6674
+ if (!up) {
6675
+ throw new Error(
6676
+ `install: the daemon did not come up \u2014 check its log: ${result.logHint}`
6677
+ );
6678
+ }
6679
+ const apiKey = JSON.parse(ctx.prepDeps.readFile(durablePath)).api_key;
6680
+ const lines = [
6681
+ `Installed service ${result.label}`,
6682
+ ` device: ${parsed.deviceId}`,
6683
+ parsed.issuedAt ? ` issued: ${parsed.issuedAt}` : ` issued: (unknown \u2014 older credential)`,
6684
+ ` api key: ${tokenFingerprint(apiKey)}`,
6685
+ ` port: ${port}`,
6686
+ ` token: ${result.tokenHint}`,
6687
+ ` (Re-running install is the only way to update a live credential \u2014 replacing the download alone does nothing.)`
6688
+ ];
6689
+ let harnessSnippetShown = false;
6690
+ if (ctx.harness && ctx.harness !== "none") {
6691
+ lines.push("", harnessSnippet({ slug, port, tokenFilePath: result.tokenHint, harness: ctx.harness }));
6692
+ harnessSnippetShown = true;
6693
+ }
6694
+ return { echo: lines.join("\n") + "\n", harnessSnippetShown };
6695
+ }
6696
+
6534
6697
  // src/bin.ts
6535
6698
  function nodeMajorOk(version) {
6536
6699
  return Number(version.split(".")[0]) >= 24;
@@ -6571,11 +6734,47 @@ function realResolveDeps() {
6571
6734
  fileExists: (p) => existsSync2(p)
6572
6735
  };
6573
6736
  }
6574
- function runServiceVerb(verb) {
6737
+ function realPrepDeps() {
6738
+ return {
6739
+ readFile: (p) => readFileSync6(p, "utf8"),
6740
+ writeFileMode: (p, data, mode) => writeFileSync2(p, data, { mode }),
6741
+ mkdirp: (p, mode) => {
6742
+ mkdirSync(p, { recursive: true, mode: mode ?? 493 });
6743
+ },
6744
+ fileExists: (p) => existsSync2(p),
6745
+ realpath: (p) => realpathSync(p),
6746
+ listDir: (p) => {
6747
+ try {
6748
+ return readdirSync(p);
6749
+ } catch {
6750
+ return [];
6751
+ }
6752
+ },
6753
+ // Synchronous spawn-free-loop probes: node's net API is async-only, so each
6754
+ // probe is a tiny `node -e` child that binds/connects and exits 0/1.
6755
+ probeBindFree: (port) => {
6756
+ const r = spawnSync(process.execPath, [
6757
+ "-e",
6758
+ `const s=require('net').createServer();s.once('error',()=>process.exit(1));s.listen(${port},'127.0.0.1',()=>{s.close(()=>process.exit(0))});`
6759
+ ]);
6760
+ return r.status === 0;
6761
+ },
6762
+ probeConnect: (port) => {
6763
+ const r = spawnSync(process.execPath, [
6764
+ "-e",
6765
+ `const s=require('net').connect(${port},'127.0.0.1');s.once('connect',()=>{s.destroy();process.exit(0)});s.once('error',()=>process.exit(1));setTimeout(()=>process.exit(1),500);`
6766
+ ]);
6767
+ return r.status === 0;
6768
+ },
6769
+ // No unref: an unref'd timer would let the process exit mid-verification.
6770
+ sleep: (ms) => new Promise((res) => setTimeout(res, ms))
6771
+ };
6772
+ }
6773
+ async function runServiceVerb(verb) {
6575
6774
  const credentialPath = argOrEnv("--credential", "SECURECOMS_CREDENTIAL");
6576
6775
  const label = argOrEnv("--label", "SECURECOMS_LABEL");
6577
- if (!credentialPath && !label) {
6578
- process.stderr.write(`usage: agent-bridge ${verb} --credential <path> [--label <name>] [--store <path>] [--port N]
6776
+ if (verb === "install" ? !credentialPath : !credentialPath && !label) {
6777
+ process.stderr.write(`usage: agent-bridge ${verb} --credential <path> [--label <name>] [--store <path>] [--port N] [--harness openclaw|hermes|custom]
6579
6778
  `);
6580
6779
  process.exit(2);
6581
6780
  }
@@ -6585,25 +6784,43 @@ function runServiceVerb(verb) {
6585
6784
  const meta = loadPackageMeta(import.meta.url);
6586
6785
  binScript = resolveStableBinScript({ binScript, packageName: meta.name }, realResolveDeps());
6587
6786
  }
6588
- const input = {
6589
- credentialPath: credentialPath ?? "",
6590
- storePath: argOrEnv("--store", "SECURECOMS_STORE"),
6591
- port: portStr ? Number(portStr) : void 0,
6592
- label,
6593
- execPath: process.execPath,
6594
- binScript,
6595
- home: homedir2(),
6596
- uid: userInfo().uid
6597
- };
6598
6787
  try {
6599
6788
  if (verb === "install") {
6600
- const r = installService(input, realServiceDeps());
6601
- process.stdout.write(`Installed service ${r.label}
6602
- file: ${r.filePath}
6603
- logs: ${r.logHint}
6604
- token: ${r.tokenHint} (the local BRIDGE_TOKEN)
6605
- `);
6789
+ const credAbs = isAbsolute2(credentialPath) ? credentialPath : resolve2(credentialPath);
6790
+ const r = await orchestrateInstall({
6791
+ credentialAbsPath: credAbs,
6792
+ label,
6793
+ explicitPort: portStr ? Number(portStr) : void 0,
6794
+ harness: argOrEnv("--harness", "SECURECOMS_HARNESS"),
6795
+ home: homedir2(),
6796
+ prepDeps: realPrepDeps(),
6797
+ // O-1: the real installService is 2-arg; partially apply the deps so
6798
+ // orchestrateInstall only ever sees the 1-arg seam.
6799
+ installService: (input) => installService(input, realServiceDeps()),
6800
+ buildServiceInput: (durableCredentialPath2, port, _slug) => ({
6801
+ credentialPath: durableCredentialPath2,
6802
+ storePath: argOrEnv("--store", "SECURECOMS_STORE"),
6803
+ // O-2: not dropped
6804
+ port,
6805
+ label,
6806
+ execPath: process.execPath,
6807
+ binScript,
6808
+ home: homedir2(),
6809
+ uid: userInfo().uid
6810
+ })
6811
+ });
6812
+ process.stdout.write(r.echo);
6606
6813
  } else {
6814
+ const input = {
6815
+ credentialPath: credentialPath ?? "",
6816
+ storePath: argOrEnv("--store", "SECURECOMS_STORE"),
6817
+ port: portStr ? Number(portStr) : void 0,
6818
+ label,
6819
+ execPath: process.execPath,
6820
+ binScript,
6821
+ home: homedir2(),
6822
+ uid: userInfo().uid
6823
+ };
6607
6824
  const r = uninstallService(input, realServiceDeps());
6608
6825
  process.stdout.write(r.removed ? `Removed service (${r.filePath})
6609
6826
  ` : `No service installed for this agent
@@ -6625,7 +6842,7 @@ async function main() {
6625
6842
  }
6626
6843
  const verb = parseVerb(process.argv);
6627
6844
  if (verb) {
6628
- runServiceVerb(verb);
6845
+ await runServiceVerb(verb);
6629
6846
  return;
6630
6847
  }
6631
6848
  const credentialPath = argOrEnv("--credential", "SECURECOMS_CREDENTIAL");
@@ -6644,7 +6861,7 @@ async function main() {
6644
6861
  `);
6645
6862
  process.exit(1);
6646
6863
  }
6647
- const tokenFile = daemon.tokenFile ?? join8(dirname5(credentialPath), "bridge-token");
6864
+ const tokenFile = daemon.tokenFile ?? join9(dirname5(credentialPath), "bridge-token");
6648
6865
  const token = resolveToken({ tokenFile, token: daemon.token });
6649
6866
  const agent = new BridgeAgent(cfg);
6650
6867
  await agent.start();
package/dist/config.d.ts CHANGED
@@ -24,6 +24,7 @@ export interface BridgeConfig {
24
24
  /** For memoryProvider="openclaw": the OpenClaw agent dir slug, e.g. "cortina" (→ <base_dir>/agents/<agent>/). */
25
25
  memoryAgent?: string;
26
26
  }
27
+ export declare const REQUIRED: readonly ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
27
28
  export declare function loadConfig(opts: {
28
29
  credentialPath: string;
29
30
  storePath?: string;
package/dist/index.js CHANGED
@@ -2630,6 +2630,15 @@ function parseServerFrame(frame) {
2630
2630
  }
2631
2631
 
2632
2632
  // ../connect-sdk-ts/dist/transport/ws-pings.js
2633
+ function base64UrlNoPad(s) {
2634
+ const bytes = new TextEncoder().encode(s);
2635
+ let bin = "";
2636
+ for (const b of bytes)
2637
+ bin += String.fromCharCode(b);
2638
+ const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bytes).toString("base64");
2639
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2640
+ }
2641
+ var BEARER_SUBPROTOCOL = "securecoms.bearer.v1";
2633
2642
  function openDeliveryPings(o) {
2634
2643
  const base = o.backoffBaseMs ?? 500;
2635
2644
  const cap = o.backoffCapMs ?? 8e3;
@@ -2778,8 +2787,7 @@ function openDeliveryPings(o) {
2778
2787
  }
2779
2788
  if (closed || gen !== connectGen)
2780
2789
  return;
2781
- const sep = o.url.includes("?") ? "&" : "?";
2782
- const ws = new o.wsImpl(`${o.url}${sep}token=${encodeURIComponent(token)}`);
2790
+ const ws = new o.wsImpl(o.url, [BEARER_SUBPROTOCOL, base64UrlNoPad(token)]);
2783
2791
  sock = ws;
2784
2792
  ws.onopen = () => {
2785
2793
  push({ reconnected: true });
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@securecoms/agent-bridge",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",