@securecoms/agent-bridge 0.6.0 → 0.7.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/bin.js CHANGED
@@ -7042,7 +7042,139 @@ function uninstallService(input, deps) {
7042
7042
  }
7043
7043
 
7044
7044
  // src/service/install-prep.ts
7045
- import { join as join8 } from "node:path";
7045
+ import { join as join9 } from "node:path";
7046
+
7047
+ // src/service/wire.ts
7048
+ import { join as join8, dirname as dirname5 } from "node:path";
7049
+ import { fileURLToPath } from "node:url";
7050
+ function detectHarness(home, statDir) {
7051
+ if (statDir(`${home}/.openclaw`)) return "openclaw";
7052
+ if (statDir(`${home}/.hermes`)) return "hermes";
7053
+ return null;
7054
+ }
7055
+ function connectorTargetDir(home, harness) {
7056
+ if (harness === "openclaw") return `${home}/.openclaw/local-plugins/securecoms`;
7057
+ return `${home}/.hermes/local-plugins/securecoms`;
7058
+ }
7059
+ function connectorsSrcDir(importMetaUrl) {
7060
+ const pkgRoot = dirname5(dirname5(fileURLToPath(importMetaUrl)));
7061
+ return join8(pkgRoot, "connectors", "openclaw");
7062
+ }
7063
+ function parseArg(argv, flag) {
7064
+ const i = argv.indexOf(flag);
7065
+ return i >= 0 ? argv[i + 1] : void 0;
7066
+ }
7067
+ function resolveEntry(deps) {
7068
+ const identityArg = parseArg(deps.argv, "--identity");
7069
+ if (!identityArg) {
7070
+ deps.log("wire: missing --identity <id|slug>");
7071
+ return null;
7072
+ }
7073
+ const entry = deps.readFleet().find((e) => e.identityId === identityArg || e.slug === identityArg);
7074
+ if (!entry) {
7075
+ deps.log(`wire: unknown identity "${identityArg}" (not found in the local fleet)`);
7076
+ return null;
7077
+ }
7078
+ const harnessArg = parseArg(deps.argv, "--harness");
7079
+ if (harnessArg !== "openclaw" && harnessArg !== "hermes") {
7080
+ deps.log("wire: missing/invalid --harness <openclaw|hermes> (auto-detection did not resolve one)");
7081
+ return null;
7082
+ }
7083
+ return { entry, harness: harnessArg };
7084
+ }
7085
+ function runWire(deps) {
7086
+ const resolved = resolveEntry(deps);
7087
+ if (!resolved) return 1;
7088
+ const { entry, harness } = resolved;
7089
+ if (harness === "hermes") {
7090
+ deps.log(
7091
+ "[securecoms] Hermes connector is not available yet (fast-follow); detection and target dir only. Nothing installed."
7092
+ );
7093
+ return 1;
7094
+ }
7095
+ const targetDir = connectorTargetDir(deps.home, harness);
7096
+ deps.copyConnector(deps.connectorsSrcDir, targetDir);
7097
+ deps.writeBinding(targetDir, entry.identityId);
7098
+ if (typeof entry.port === "number") {
7099
+ const h = deps.getHealth(entry.port);
7100
+ if (h && (h.agent_sessions ?? 0) >= 1) {
7101
+ deps.log(`wire: ${entry.slug} attached (agent_sessions=${h.agent_sessions})`);
7102
+ } else {
7103
+ deps.log(`wire: connector installed but not yet attached \u2014 restart the harness to pick it up.`);
7104
+ }
7105
+ } else {
7106
+ deps.log(`wire: connector installed (no bridge port known yet) \u2014 restart the harness to pick it up.`);
7107
+ }
7108
+ return 0;
7109
+ }
7110
+ function runUnwire(deps) {
7111
+ const resolved = resolveEntry(deps);
7112
+ if (!resolved) return 1;
7113
+ const { harness } = resolved;
7114
+ if (harness === "hermes") {
7115
+ deps.log("[securecoms] Hermes connector is not available yet (fast-follow); nothing to remove.");
7116
+ return 0;
7117
+ }
7118
+ deps.removeBinding(connectorTargetDir(deps.home, harness));
7119
+ return 0;
7120
+ }
7121
+
7122
+ // src/service/harness-wire.ts
7123
+ function customFacts(input) {
7124
+ const ws = `ws://127.0.0.1:${input.port ?? "<PORT>"}/v1/ws`;
7125
+ return [
7126
+ ` bridge WS: ${ws}`,
7127
+ ` token file: ${input.tokenFilePath}`,
7128
+ ` Bring your own connector: open the WS with the bearer token read from that file.`,
7129
+ ` Protocol reference: the "WebSocket" section of the agent-bridge README.`
7130
+ ].join("\n");
7131
+ }
7132
+ function autoWireHarness(input, deps) {
7133
+ const h = input.harness;
7134
+ if (!h || h === "none") return { echo: "", wired: false, error: false };
7135
+ const retry = `agent-bridge wire --harness openclaw --identity ${input.slug}`;
7136
+ if (h === "openclaw") {
7137
+ const parent = `${input.home}/.openclaw`;
7138
+ if (!deps.statDir(parent)) {
7139
+ return {
7140
+ echo: `[securecoms] OpenClaw not found at ~/.openclaw \u2014 the bridge service is installed, but the harness was NOT wired.
7141
+ Install OpenClaw, then re-run install, or run: ${retry}
7142
+ (or re-run install with --harness custom).`,
7143
+ wired: false,
7144
+ error: true
7145
+ };
7146
+ }
7147
+ const targetDir = connectorTargetDir(input.home, "openclaw");
7148
+ try {
7149
+ const prev = deps.readBinding(targetDir);
7150
+ deps.copyConnector(input.connectorsSrcDir, targetDir);
7151
+ deps.writeBinding(targetDir, input.identityId);
7152
+ const rebind = prev && prev !== input.identityId ? `
7153
+ rebind: ${prev} \u2192 ${input.identityId}` : "";
7154
+ return {
7155
+ echo: `[securecoms] OpenClaw connector installed and bound to identity ${input.identityId}.${rebind}
7156
+ Now restart OpenClaw to pick up the connector.`,
7157
+ wired: true,
7158
+ error: false
7159
+ };
7160
+ } catch (e) {
7161
+ return {
7162
+ echo: `[securecoms] the bridge service is installed, but wiring the OpenClaw connector FAILED: ${e.message}
7163
+ Retry after fixing the path: ${retry}`,
7164
+ wired: false,
7165
+ error: true
7166
+ };
7167
+ }
7168
+ }
7169
+ if (h === "hermes") {
7170
+ return { echo: `[securecoms] Hermes turnkey connector is coming (Phase 2). For now drive Hermes via the custom WS path:
7171
+ ${customFacts(input)}`, wired: false, error: false };
7172
+ }
7173
+ return { echo: `[securecoms] Custom harness \u2014 bring your own connector:
7174
+ ${customFacts(input)}`, wired: false, error: false };
7175
+ }
7176
+
7177
+ // src/service/install-prep.ts
7046
7178
  function assertReadableCredential(absPath, deps) {
7047
7179
  let raw;
7048
7180
  try {
@@ -7065,10 +7197,10 @@ function assertReadableCredential(absPath, deps) {
7065
7197
  return { deviceId: json2.device_id, identityId: json2.identity_id, issuedAt };
7066
7198
  }
7067
7199
  function durableDir(home, slug) {
7068
- return join8(home, `securecoms-agent-${slug}`);
7200
+ return join9(home, `securecoms-agent-${slug}`);
7069
7201
  }
7070
7202
  function durableCredentialPath(home, slug) {
7071
- return join8(durableDir(home, slug), `${slug}.json`);
7203
+ return join9(durableDir(home, slug), `${slug}.json`);
7072
7204
  }
7073
7205
  function takeCustody(args2, deps) {
7074
7206
  const dir = durableDir(args2.home, args2.slug);
@@ -7115,7 +7247,7 @@ function readSiblingDefs(dir, prefix, deps) {
7115
7247
  for (const n of names) {
7116
7248
  if (!n.startsWith(prefix)) continue;
7117
7249
  try {
7118
- out.push(deps.readFile(join8(dir, n)));
7250
+ out.push(deps.readFile(join9(dir, n)));
7119
7251
  } catch {
7120
7252
  }
7121
7253
  }
@@ -7147,15 +7279,6 @@ async function verifyDaemonUp(args2, deps) {
7147
7279
  }
7148
7280
  return false;
7149
7281
  }
7150
- function harnessSnippet(args2) {
7151
- const label = args2.harness === "hermes" ? "Hermes" : args2.harness === "openclaw" ? "OpenClaw" : "your harness";
7152
- return [
7153
- `# ${label}: optional daemon-supervised path. Prefer 'agent-bridge wire' + the connection file.`,
7154
- `SECURECOMS_BRIDGE_URL=ws://127.0.0.1:${args2.port}/v1/ws`,
7155
- `SECURECOMS_CONNECTION_FILE=~/.securecoms/connections/${args2.identityId}.json`,
7156
- `SECURECOMS_BRIDGE_TOKEN_FILE=${args2.tokenFilePath}`
7157
- ].join("\n");
7158
- }
7159
7282
  async function orchestrateInstall(ctx) {
7160
7283
  const parsed = assertReadableCredential(ctx.credentialAbsPath, ctx.prepDeps);
7161
7284
  const stemForSlug = ctx.label ?? ctx.credentialAbsPath.replace(/^.*[\\/]/, "").replace(/\.[^.]*$/, "");
@@ -7189,22 +7312,24 @@ async function orchestrateInstall(ctx) {
7189
7312
  ` token: ${result.tokenHint}`,
7190
7313
  ` (Re-running install is the only way to update a live credential \u2014 replacing the download alone does nothing.)`
7191
7314
  ];
7192
- let harnessSnippetShown = false;
7193
- if (ctx.harness && ctx.harness !== "none") {
7194
- lines.push(
7195
- "",
7196
- `Primary: run 'agent-bridge wire --identity ${slug}' to attach a harness; the daemon writes ~/.securecoms/connections/${parsed.identityId}.json on startup.`,
7197
- "",
7198
- "Optional (daemon-supervised):",
7199
- harnessSnippet({ slug, port, tokenFilePath: result.tokenHint, harness: ctx.harness, identityId: parsed.identityId })
7200
- );
7201
- harnessSnippetShown = true;
7202
- }
7203
- return { echo: lines.join("\n") + "\n", harnessSnippetShown };
7315
+ const wire = autoWireHarness(
7316
+ {
7317
+ harness: ctx.harness,
7318
+ identityId: parsed.identityId,
7319
+ slug,
7320
+ port,
7321
+ home: ctx.home,
7322
+ tokenFilePath: result.tokenHint,
7323
+ connectorsSrcDir: ctx.connectorsSrcDir
7324
+ },
7325
+ ctx.autoWireDeps
7326
+ );
7327
+ if (wire.echo) lines.push("", wire.echo);
7328
+ return { echo: lines.join("\n") + "\n", harnessWired: wire.wired, harnessError: wire.error };
7204
7329
  }
7205
7330
 
7206
7331
  // src/service/fleet.ts
7207
- import { dirname as dirname5 } from "node:path";
7332
+ import { dirname as dirname6 } from "node:path";
7208
7333
  function parseArgFromDef(def, flag) {
7209
7334
  const esc = flag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7210
7335
  const re = new RegExp(`${esc}(?:<\\/string>\\s*<string>|\\s+)([^<\\s]+)`);
@@ -7248,7 +7373,7 @@ function readFleet(deps) {
7248
7373
  let identityId = `unknown:${slug}`, deviceId = "", label = slug, env = "unknown";
7249
7374
  let credDir = null;
7250
7375
  if (credPath) {
7251
- credDir = dirname5(credPath);
7376
+ credDir = dirname6(credPath);
7252
7377
  try {
7253
7378
  const c = JSON.parse(deps.readFile(credPath));
7254
7379
  if (c.identity_id) identityId = c.identity_id;
@@ -7314,81 +7439,6 @@ function renderStatusTable(rows) {
7314
7439
  return [fmt(head), ...body.map(fmt)].join("\n") + "\n";
7315
7440
  }
7316
7441
 
7317
- // src/service/wire.ts
7318
- import { join as join9, dirname as dirname6 } from "node:path";
7319
- import { fileURLToPath } from "node:url";
7320
- function detectHarness(home, statDir) {
7321
- if (statDir(`${home}/.openclaw`)) return "openclaw";
7322
- if (statDir(`${home}/.hermes`)) return "hermes";
7323
- return null;
7324
- }
7325
- function connectorTargetDir(home, harness) {
7326
- if (harness === "openclaw") return `${home}/.openclaw/local-plugins/securecoms`;
7327
- return `${home}/.hermes/local-plugins/securecoms`;
7328
- }
7329
- function connectorsSrcDir(importMetaUrl) {
7330
- const pkgRoot = dirname6(dirname6(fileURLToPath(importMetaUrl)));
7331
- return join9(pkgRoot, "connectors", "openclaw");
7332
- }
7333
- function parseArg(argv, flag) {
7334
- const i = argv.indexOf(flag);
7335
- return i >= 0 ? argv[i + 1] : void 0;
7336
- }
7337
- function resolveEntry(deps) {
7338
- const identityArg = parseArg(deps.argv, "--identity");
7339
- if (!identityArg) {
7340
- deps.log("wire: missing --identity <id|slug>");
7341
- return null;
7342
- }
7343
- const entry = deps.readFleet().find((e) => e.identityId === identityArg || e.slug === identityArg);
7344
- if (!entry) {
7345
- deps.log(`wire: unknown identity "${identityArg}" (not found in the local fleet)`);
7346
- return null;
7347
- }
7348
- const harnessArg = parseArg(deps.argv, "--harness");
7349
- if (harnessArg !== "openclaw" && harnessArg !== "hermes") {
7350
- deps.log("wire: missing/invalid --harness <openclaw|hermes> (auto-detection did not resolve one)");
7351
- return null;
7352
- }
7353
- return { entry, harness: harnessArg };
7354
- }
7355
- function runWire(deps) {
7356
- const resolved = resolveEntry(deps);
7357
- if (!resolved) return 1;
7358
- const { entry, harness } = resolved;
7359
- if (harness === "hermes") {
7360
- deps.log(
7361
- "[securecoms] Hermes connector is not available yet (fast-follow); detection and target dir only. Nothing installed."
7362
- );
7363
- return 1;
7364
- }
7365
- const targetDir = connectorTargetDir(deps.home, harness);
7366
- deps.copyConnector(deps.connectorsSrcDir, targetDir);
7367
- deps.writeBinding(targetDir, entry.identityId);
7368
- if (typeof entry.port === "number") {
7369
- const h = deps.getHealth(entry.port);
7370
- if (h && (h.agent_sessions ?? 0) >= 1) {
7371
- deps.log(`wire: ${entry.slug} attached (agent_sessions=${h.agent_sessions})`);
7372
- } else {
7373
- deps.log(`wire: connector installed but not yet attached \u2014 restart the harness to pick it up.`);
7374
- }
7375
- } else {
7376
- deps.log(`wire: connector installed (no bridge port known yet) \u2014 restart the harness to pick it up.`);
7377
- }
7378
- return 0;
7379
- }
7380
- function runUnwire(deps) {
7381
- const resolved = resolveEntry(deps);
7382
- if (!resolved) return 1;
7383
- const { harness } = resolved;
7384
- if (harness === "hermes") {
7385
- deps.log("[securecoms] Hermes connector is not available yet (fast-follow); nothing to remove.");
7386
- return 0;
7387
- }
7388
- deps.removeBinding(connectorTargetDir(deps.home, harness));
7389
- return 0;
7390
- }
7391
-
7392
7442
  // src/service/connection-file.ts
7393
7443
  import {
7394
7444
  chmodSync,
@@ -7622,6 +7672,14 @@ function realWriteBinding(dstDir, identityId) {
7622
7672
  mkdirSync2(dstDir, { recursive: true });
7623
7673
  writeFileSync3(join11(dstDir, "securecoms.binding.json"), JSON.stringify({ identity_id: identityId }) + "\n", { mode: 384 });
7624
7674
  }
7675
+ function realReadBinding(dstDir) {
7676
+ try {
7677
+ const parsed = JSON.parse(readFileSync7(join11(dstDir, "securecoms.binding.json"), "utf8"));
7678
+ return parsed.identity_id ?? null;
7679
+ } catch {
7680
+ return null;
7681
+ }
7682
+ }
7625
7683
  function realRemoveBinding(dstDir) {
7626
7684
  rmSync(join11(dstDir, "securecoms.binding.json"), { force: true });
7627
7685
  }
@@ -7684,6 +7742,17 @@ async function runServiceVerb(verb) {
7684
7742
  explicitPort: portStr ? Number(portStr) : void 0,
7685
7743
  harness: argOrEnv("--harness", "SECURECOMS_HARNESS"),
7686
7744
  home: homedir2(),
7745
+ connectorsSrcDir: connectorsSrcDir(import.meta.url),
7746
+ autoWireDeps: {
7747
+ statDir: realStatDir,
7748
+ // reuse bin.ts:235
7749
+ readBinding: realReadBinding,
7750
+ copyConnector: realCopyConnector,
7751
+ // bin.ts:212 — one copy impl
7752
+ writeBinding: realWriteBinding,
7753
+ // bin.ts:226 — writes {identity_id}
7754
+ log: (s) => process.stderr.write(s + "\n")
7755
+ },
7687
7756
  prepDeps: realPrepDeps(),
7688
7757
  // O-1: the real installService is 2-arg; partially apply the deps so
7689
7758
  // orchestrateInstall only ever sees the 1-arg seam.
@@ -7701,6 +7770,7 @@ async function runServiceVerb(verb) {
7701
7770
  })
7702
7771
  });
7703
7772
  process.stdout.write(r.echo);
7773
+ if (r.harnessError) process.exit(1);
7704
7774
  } else {
7705
7775
  const input = {
7706
7776
  credentialPath: credentialPath ?? "",
@@ -0,0 +1,21 @@
1
+ export interface AutoWireInput {
2
+ harness: string | undefined;
3
+ identityId: string;
4
+ slug: string;
5
+ port: number | null;
6
+ home: string;
7
+ tokenFilePath: string;
8
+ connectorsSrcDir: string;
9
+ }
10
+ export interface AutoWireDeps {
11
+ statDir: (p: string) => boolean;
12
+ readBinding: (dstDir: string) => string | null;
13
+ copyConnector: (srcDir: string, dstDir: string) => void;
14
+ writeBinding: (dstDir: string, identityId: string) => void;
15
+ log: (s: string) => void;
16
+ }
17
+ export declare function autoWireHarness(input: AutoWireInput, deps: AutoWireDeps): {
18
+ echo: string;
19
+ wired: boolean;
20
+ error: boolean;
21
+ };
@@ -1,4 +1,5 @@
1
1
  import { type ServiceInput } from "./config.js";
2
+ import { type AutoWireDeps } from "./harness-wire.js";
2
3
  export interface PrepDeps {
3
4
  readFile: (p: string) => string;
4
5
  writeFileMode: (p: string, data: string, mode: number) => void;
@@ -37,13 +38,6 @@ export declare function verifyDaemonUp(args: {
37
38
  attempts?: number;
38
39
  delayMs?: number;
39
40
  }, deps: PrepDeps): Promise<boolean>;
40
- export declare function harnessSnippet(args: {
41
- slug: string;
42
- port: number;
43
- tokenFilePath: string;
44
- harness: string;
45
- identityId: string;
46
- }): string;
47
41
  export interface OrchestrateInstallCtx {
48
42
  credentialAbsPath: string;
49
43
  label?: string;
@@ -52,7 +46,9 @@ export interface OrchestrateInstallCtx {
52
46
  home: string;
53
47
  launchAgentsDir?: string;
54
48
  systemdDir?: string;
49
+ connectorsSrcDir: string;
55
50
  prepDeps: PrepDeps;
51
+ autoWireDeps: AutoWireDeps;
56
52
  installService: (input: ServiceInput) => {
57
53
  label: string;
58
54
  filePath: string;
@@ -65,5 +61,6 @@ export interface OrchestrateInstallCtx {
65
61
  }
66
62
  export declare function orchestrateInstall(ctx: OrchestrateInstallCtx): Promise<{
67
63
  echo: string;
68
- harnessSnippetShown: boolean;
64
+ harnessWired: boolean;
65
+ harnessError: boolean;
69
66
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securecoms/agent-bridge",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",