xmux-bridge 1.0.41 → 1.2.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
@@ -151,9 +151,10 @@ shortcuts for orchestrating that runtime. The MCP command is install-scoped and
151
151
  does not pin `XMUX_PROJECT_DIR`/`XMUX_STATE_DIR`; those values come from the
152
152
  active `xmux -n <session>` lead runtime.
153
153
 
154
- Provider teammates write responses through `mcp/servers/bridge.js`, using the
155
- team runtime environment prepared by XMux. The MCP and mailbox paths are
156
- implementation details behind Codex-led teammate orchestration.
154
+ Provider teammates write responses through the versioned npm `xmux-bridge`
155
+ entrypoint, using the team runtime environment prepared by XMux. MCP and
156
+ mailbox paths are implementation details behind Codex-led teammate
157
+ orchestration.
157
158
 
158
159
  Users can ask for teammate work in natural language. When XMux skills are
159
160
  available in Codex, the official skill shortcuts are:
@@ -6,6 +6,8 @@ const os = require("node:os");
6
6
  const path = require("node:path");
7
7
 
8
8
  const SERVER_NAME = "xmux_bridge";
9
+ const DEFAULT_NPM_PACKAGE = "xmux-bridge";
10
+ const DEFAULT_NPX_PREFIX = path.join(os.homedir(), ".cache", "xmux", "npm-prefix");
9
11
  const LEGACY_NAMES = new Set([
10
12
  "xmux_bridge",
11
13
  "xmux-bridge",
@@ -73,7 +75,7 @@ function atomicWriteJson(filePath, data) {
73
75
 
74
76
  function usage() {
75
77
  process.stderr.write(
76
- "usage: claude.js <bridge_js> <project_dir> <outbox> <agent> <team> <state_dir> <install_dir>\n",
78
+ "usage: claude.js <bridge_js|npx> <project_dir> <outbox> <agent> <team> <state_dir> <install_dir>\n",
77
79
  );
78
80
  }
79
81
 
@@ -83,7 +85,8 @@ function main(argv = process.argv.slice(2)) {
83
85
  return 2;
84
86
  }
85
87
 
86
- const bridgeJs = stableHomebrewXmuxFilePath(argv[0]);
88
+ const bridgeRef = argv[0];
89
+ const bridgeJs = bridgeRef === "npx" ? "" : stableHomebrewXmuxFilePath(bridgeRef);
87
90
  const projectDir = absolute(argv[1]);
88
91
  const outbox = absolute(argv[2]);
89
92
  const agent = argv[3];
@@ -116,20 +119,41 @@ function main(argv = process.argv.slice(2)) {
116
119
  delete servers[legacyName];
117
120
  }
118
121
 
119
- servers[SERVER_NAME] = {
120
- type: "stdio",
121
- command: "node",
122
- args: [bridgeJs, "--outbox", outbox, "--agent", agent, "--team", team],
123
- env: {
124
- XMUX_AGENT: agent,
125
- XMUX_INSTALL_DIR: installDir,
126
- XMUX_OUTBOX: outbox,
127
- XMUX_PROJECT_DIR: projectDir,
128
- XMUX_STATE_DIR: stateDir,
129
- XMUX_TEAM: team,
130
- },
122
+ const commonEnv = {
123
+ XMUX_AGENT: agent,
124
+ XMUX_INSTALL_DIR: installDir,
125
+ XMUX_OUTBOX: outbox,
126
+ XMUX_PROJECT_DIR: projectDir,
127
+ XMUX_STATE_DIR: stateDir,
128
+ XMUX_TEAM: team,
131
129
  };
132
130
 
131
+ if (bridgeRef === "npx") {
132
+ const packageSpec = process.env.XMUX_MCP_PACKAGE_SPEC || DEFAULT_NPM_PACKAGE;
133
+ const npxPrefix = process.env.XMUX_MCP_NPX_PREFIX || DEFAULT_NPX_PREFIX;
134
+ servers[SERVER_NAME] = {
135
+ type: "stdio",
136
+ command: "npx",
137
+ args: [
138
+ "--prefix", npxPrefix,
139
+ "-y",
140
+ "-p", packageSpec,
141
+ "xmux-bridge",
142
+ "--outbox", outbox,
143
+ "--agent", agent,
144
+ "--team", team,
145
+ ],
146
+ env: commonEnv,
147
+ };
148
+ } else {
149
+ servers[SERVER_NAME] = {
150
+ type: "stdio",
151
+ command: "node",
152
+ args: [bridgeJs, "--outbox", outbox, "--agent", agent, "--team", team],
153
+ env: commonEnv,
154
+ };
155
+ }
156
+
133
157
  atomicWriteJson(configPath, config);
134
158
  return 0;
135
159
  }
@@ -151,6 +151,20 @@ function package_spec_has_version(packageSpec) {
151
151
  return text.includes("@");
152
152
  }
153
153
 
154
+ function package_name_from_spec(packageSpec) {
155
+ const text = String(packageSpec || "");
156
+ if (text.startsWith("@")) {
157
+ const slash = text.indexOf("/");
158
+ if (slash < 0) return text;
159
+ const scope = text.slice(0, slash);
160
+ const rest = text.slice(slash + 1);
161
+ const versionIndex = rest.indexOf("@");
162
+ return `${scope}/${versionIndex < 0 ? rest : rest.slice(0, versionIndex)}`;
163
+ }
164
+ const versionIndex = text.indexOf("@");
165
+ return versionIndex < 0 ? text : text.slice(0, versionIndex);
166
+ }
167
+
154
168
  function xmux_version_from_install_dir(xmuxInstallDir) {
155
169
  const root = abs(xmuxInstallDir);
156
170
  const content = read_text(xmux_runtime_shell_path(root)) || read_text(path.join(root, "xmux.zsh"));
@@ -246,6 +260,70 @@ function ensure_mcp_runtime_dirs(mcpConfig) {
246
260
  }
247
261
  }
248
262
 
263
+ function cached_package_root(mcpConfig) {
264
+ if (!mcpConfig || !mcpConfig.npx_prefix || !mcpConfig.package_spec) return "";
265
+ return path.join(abs(mcpConfig.npx_prefix), "node_modules", package_name_from_spec(mcpConfig.package_spec));
266
+ }
267
+
268
+ function cached_mailbox_candidates(mcpConfig) {
269
+ const prefix = mcpConfig && mcpConfig.npx_prefix ? abs(mcpConfig.npx_prefix) : "";
270
+ const root = cached_package_root(mcpConfig);
271
+ return [
272
+ prefix ? path.join(prefix, "node_modules", ".bin", "xmux-mailbox") : "",
273
+ root ? path.join(root, "dist", "bin", "xmux-mailbox.js") : "",
274
+ ].filter(Boolean);
275
+ }
276
+
277
+ function mailbox_source(xmuxInstallDir, mcpConfig) {
278
+ const explicit = process.env.XMUX_MAILBOX_NODE_CLI ? abs(process.env.XMUX_MAILBOX_NODE_CLI) : "";
279
+ if (explicit && fs.existsSync(explicit)) return { ok: true, kind: "env", label: explicit };
280
+
281
+ for (const candidate of cached_mailbox_candidates(mcpConfig)) {
282
+ if (fs.existsSync(candidate)) return { ok: true, kind: "npm-cache", label: candidate };
283
+ }
284
+
285
+ const bundled = path.join(abs(xmuxInstallDir), "dist", "bin", "xmux-mailbox.js");
286
+ if (fs.existsSync(bundled)) return { ok: true, kind: "brew-bundled", label: bundled };
287
+
288
+ const npx = spawnSync("npx", ["--version"], { encoding: "utf8" });
289
+ if (npx.status === 0 && mcpConfig && mcpConfig.mode === "npx" && mcpConfig.package_spec) {
290
+ return { ok: true, kind: "npx", label: `${mcpConfig.package_spec} via ${mcpConfig.npx_prefix}` };
291
+ }
292
+
293
+ return { ok: false, kind: "missing", label: "no mailbox CLI source found" };
294
+ }
295
+
296
+ function ensure_mcp_package_cache(mcpConfig, enabled = true) {
297
+ if (!enabled || !mcpConfig || mcpConfig.mode !== "npx") {
298
+ return { status: "skipped", message: "disabled" };
299
+ }
300
+ if (!mcpConfig.npx_prefix || !mcpConfig.package_spec) {
301
+ return { status: "skipped", message: "missing npx package metadata" };
302
+ }
303
+ ensure_mcp_runtime_dirs(mcpConfig);
304
+
305
+ const root = cached_package_root(mcpConfig);
306
+ if (root && fs.existsSync(root)) {
307
+ return { status: "ok", message: `using existing cache at ${root}` };
308
+ }
309
+
310
+ const npm = spawnSync("npm", [
311
+ "install",
312
+ "--prefix",
313
+ abs(mcpConfig.npx_prefix),
314
+ "--no-save",
315
+ "--omit=dev",
316
+ mcpConfig.package_spec,
317
+ ], { encoding: "utf8" });
318
+
319
+ if (npm.status === 0) {
320
+ return { status: "ok", message: `installed ${mcpConfig.package_spec} under ${mcpConfig.npx_prefix}` };
321
+ }
322
+
323
+ const detail = (npm.stderr || npm.stdout || "").trim().split(/\r?\n/).slice(-2).join(" ");
324
+ return { status: "failed", message: detail || `npm install exited with ${npm.status}` };
325
+ }
326
+
249
327
  function build_block(mcpConfigOrServerPath, xmuxInstallDir) {
250
328
  const mcpConfig = normalize_mcp_config(mcpConfigOrServerPath, xmuxInstallDir);
251
329
  const pathEnv = resolve_path_with_node();
@@ -659,6 +737,10 @@ function doctor_codex(configPath, xmuxInstallDir, mcpConfigOrServerPath, skillsD
659
737
  notes.push(["OK", "legacy XMux plugin cache is absent"]);
660
738
  }
661
739
 
740
+ const mailbox = mailbox_source(xmuxInstallDir, mcpConfig);
741
+ if (mailbox.ok) notes.push(["OK", `mailbox source: ${mailbox.kind} (${mailbox.label})`]);
742
+ else issues.push(`mailbox source is unavailable: ${mailbox.label}`);
743
+
662
744
  const staleProcesses = stale_xmux_lead_mcp_processes(mcpConfig);
663
745
  for (const proc of staleProcesses.slice(0, 5)) {
664
746
  notes.push([
@@ -700,6 +782,7 @@ function parse_args(argv) {
700
782
  mcp_version: "",
701
783
  mcp_bin: DEFAULT_MCP_BIN,
702
784
  mcp_npx_prefix: "",
785
+ cache_mcp: true,
703
786
  };
704
787
  for (let i = 0; i < argv.length;) {
705
788
  const arg = argv[i];
@@ -711,6 +794,10 @@ function parse_args(argv) {
711
794
  opts.quiet = true; i += 1;
712
795
  } else if (arg === "--without-skills") {
713
796
  opts.install_skills = false; i += 1;
797
+ } else if (arg === "--cache-mcp") {
798
+ opts.cache_mcp = true; i += 1;
799
+ } else if (arg === "--no-cache-mcp") {
800
+ opts.cache_mcp = false; i += 1;
714
801
  } else if ([
715
802
  "--skills-dir",
716
803
  "--home",
@@ -774,6 +861,10 @@ function main(argv = process.argv.slice(2)) {
774
861
  }
775
862
 
776
863
  ensure_mcp_runtime_dirs(mcpConfig);
864
+ const cacheResult = ensure_mcp_package_cache(mcpConfig, opts.cache_mcp);
865
+ if (cacheResult.status === "failed") {
866
+ console.error(`[WARN] XMux MCP package cache failed: ${cacheResult.message}`);
867
+ }
777
868
 
778
869
  let content = remove_xmux_blocks(read_text(configPath));
779
870
  if (opts.remove) {
@@ -805,6 +896,8 @@ function main(argv = process.argv.slice(2)) {
805
896
 
806
897
  console.log(`[OK] Wrote ${SERVER_NAME} to ${configPath}`);
807
898
  console.log(` mcp: ${mcpConfig.label}`);
899
+ if (cacheResult.status === "ok") console.log(` mcp_cache: ${cacheResult.message}`);
900
+ else if (cacheResult.status === "skipped") console.log(` mcp_cache: ${cacheResult.message}`);
808
901
  console.log(` xmux_install_dir: ${xmuxInstallDir}`);
809
902
  console.log(" xmux_project_dir: inherited from xmux-launched Codex runtime");
810
903
  console.log(" xmux_state_dir: inherited from xmux-launched Codex runtime");
@@ -830,6 +923,7 @@ module.exports = {
830
923
  build_block,
831
924
  default_mcp_package_spec,
832
925
  default_mcp_npx_prefix,
926
+ mailbox_source,
833
927
  resolve_mcp_config,
834
928
  path_with_xmux_bin,
835
929
  ensure_codex_shell_environment,
@@ -6,6 +6,8 @@ const os = require("node:os");
6
6
  const path = require("node:path");
7
7
 
8
8
  const SERVER_NAME = "xmux_bridge";
9
+ const DEFAULT_NPM_PACKAGE = "xmux-bridge";
10
+ const DEFAULT_NPX_PREFIX = path.join(os.homedir(), ".cache", "xmux", "npm-prefix");
9
11
  const LEGACY_NAMES = new Set([
10
12
  "xmux_bridge",
11
13
  "xmux-bridge",
@@ -57,7 +59,13 @@ function main(argv = process.argv.slice(2)) {
57
59
  if (cmd.startsWith("http")) {
58
60
  servers[SERVER_NAME] = { type: "sse", url: cmd, tools: TOOLS };
59
61
  } else if (cmd === "npx") {
60
- servers[SERVER_NAME] = { command: "npx", args: ["-y", "xmux-bridge"], tools: TOOLS };
62
+ const packageSpec = process.env.XMUX_MCP_PACKAGE_SPEC || DEFAULT_NPM_PACKAGE;
63
+ const npxPrefix = process.env.XMUX_MCP_NPX_PREFIX || DEFAULT_NPX_PREFIX;
64
+ servers[SERVER_NAME] = {
65
+ command: "npx",
66
+ args: ["--prefix", npxPrefix, "-y", "-p", packageSpec, "xmux-bridge"],
67
+ tools: TOOLS,
68
+ };
61
69
  } else {
62
70
  servers[SERVER_NAME] = {
63
71
  command: "node",
@@ -14,7 +14,8 @@ const LEGACY_NAMES = new Set([
14
14
  "amux_bridge",
15
15
  "amux-bridge",
16
16
  ]);
17
- const NPM_PIN = "xmux-bridge@^1.3.0";
17
+ const DEFAULT_NPM_PACKAGE = "xmux-bridge";
18
+ const DEFAULT_NPX_PREFIX = path.join(os.homedir(), ".cache", "xmux", "npm-prefix");
18
19
 
19
20
  function stableHomebrewXmuxFilePath(inputPath) {
20
21
  const resolved = path.resolve(inputPath.replace(/^~(?=$|\/)/, os.homedir()));
@@ -55,9 +56,11 @@ function main(argv = process.argv.slice(2)) {
55
56
  }
56
57
 
57
58
  if (cmd === "npx") {
59
+ const packageSpec = process.env.XMUX_MCP_PACKAGE_SPEC || DEFAULT_NPM_PACKAGE;
60
+ const npxPrefix = process.env.XMUX_MCP_NPX_PREFIX || DEFAULT_NPX_PREFIX;
58
61
  servers[SERVER_NAME] = {
59
62
  command: "npx",
60
- args: ["-y", NPM_PIN],
63
+ args: ["--prefix", npxPrefix, "-y", "-p", packageSpec, "xmux-bridge"],
61
64
  trust: true,
62
65
  };
63
66
  } else {
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "xmux-bridge",
3
- "version": "1.0.41",
3
+ "version": "1.2.0",
4
4
  "description": "MCP bridge and lead server package for XMux",
5
5
  "bin": {
6
- "xmux-lead-mcp": "./mcp/servers/lead.js",
7
- "xmux-bridge": "./mcp/servers/bridge.js",
8
- "xmux-mailbox": "./dist/bin/xmux-mailbox.js"
6
+ "xmux-lead-mcp": "mcp/servers/lead.js",
7
+ "xmux-bridge": "mcp/servers/bridge.js",
8
+ "xmux-mailbox": "dist/bin/xmux-mailbox.js"
9
9
  },
10
10
  "files": [
11
11
  "mcp/servers",
@@ -27,6 +27,6 @@
27
27
  "license": "MIT",
28
28
  "repository": {
29
29
  "type": "git",
30
- "url": "https://github.com/DwvN-Lee/XMux.git"
30
+ "url": "git+https://github.com/DwvN-Lee/XMux.git"
31
31
  }
32
32
  }