xmux-bridge 1.0.39
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 +219 -0
- package/bin/xmux +9 -0
- package/bridge-mcp-server.js +407 -0
- package/dist/bin/xmux-mailbox.js +6 -0
- package/dist/mailbox/cli.js +3 -0
- package/dist/mailbox/core.js +3 -0
- package/package.json +38 -0
- package/scripts/setup_claude_mcp.js +149 -0
- package/scripts/setup_copilot_mcp.js +87 -0
- package/scripts/setup_gemini_mcp.js +89 -0
- package/scripts/setup_xmux_codex_mcp.js +799 -0
- package/scripts/trust_codex_project.js +44 -0
- package/scripts/trust_copilot_project.js +49 -0
- package/scripts/trust_gemini_project.js +47 -0
- package/src/mailbox/cli.js +251 -0
- package/src/mailbox/core.js +887 -0
- package/src/runtime/errors.js +12 -0
- package/src/runtime/json.js +75 -0
- package/src/runtime/lock.js +56 -0
- package/src/runtime/time.js +27 -0
- package/xmux-bridge.zsh +481 -0
- package/xmux-lead-mcp-server.js +486 -0
- package/xmux.zsh +5146 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const os = require("node:os");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
|
|
8
|
+
const SERVER_NAME = "xmux_bridge";
|
|
9
|
+
const LEGACY_NAMES = new Set([
|
|
10
|
+
"xmux_bridge",
|
|
11
|
+
"xmux-bridge",
|
|
12
|
+
"clau_mux_bridge",
|
|
13
|
+
"clau-mux-bridge",
|
|
14
|
+
"amux_bridge",
|
|
15
|
+
"amux-bridge",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
function absolute(inputPath) {
|
|
19
|
+
return path.resolve(inputPath.replace(/^~(?=$|\/)/, os.homedir()));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function stableHomebrewXmuxInstallDir(installDir) {
|
|
23
|
+
const resolved = absolute(installDir);
|
|
24
|
+
const marker = `${path.sep}Cellar${path.sep}xmux${path.sep}`;
|
|
25
|
+
if (!resolved.includes(marker) || !resolved.endsWith(`${path.sep}libexec`)) {
|
|
26
|
+
return resolved;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const prefix = resolved.split(marker, 1)[0];
|
|
30
|
+
const candidate = path.join(prefix, "opt", "xmux", "libexec");
|
|
31
|
+
if (fs.existsSync(path.join(candidate, "xmux.zsh"))) {
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
34
|
+
return resolved;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function stableHomebrewXmuxFilePath(filePath) {
|
|
38
|
+
const resolved = absolute(filePath);
|
|
39
|
+
const installDir = path.dirname(resolved);
|
|
40
|
+
const stableInstallDir = stableHomebrewXmuxInstallDir(installDir);
|
|
41
|
+
if (stableInstallDir === installDir) {
|
|
42
|
+
return resolved;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const candidate = path.join(stableInstallDir, path.basename(resolved));
|
|
46
|
+
if (fs.existsSync(candidate)) {
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function loadJson(filePath) {
|
|
53
|
+
if (!fs.existsSync(filePath)) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
57
|
+
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
58
|
+
throw new Error(`error: ${filePath} must contain a JSON object`);
|
|
59
|
+
}
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function atomicWriteJson(filePath, data) {
|
|
64
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
65
|
+
const tmpPath = path.join(
|
|
66
|
+
path.dirname(filePath),
|
|
67
|
+
`.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`,
|
|
68
|
+
);
|
|
69
|
+
fs.writeFileSync(tmpPath, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
70
|
+
fs.renameSync(tmpPath, filePath);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function usage() {
|
|
74
|
+
process.stderr.write(
|
|
75
|
+
"usage: setup_claude_mcp.js <bridge_js> <project_dir> <outbox> <agent> <team> <state_dir> <install_dir>\n",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function main(argv = process.argv.slice(2)) {
|
|
80
|
+
if (argv.length !== 7) {
|
|
81
|
+
usage();
|
|
82
|
+
return 2;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const bridgeJs = stableHomebrewXmuxFilePath(argv[0]);
|
|
86
|
+
const projectDir = absolute(argv[1]);
|
|
87
|
+
const outbox = absolute(argv[2]);
|
|
88
|
+
const agent = argv[3];
|
|
89
|
+
const team = argv[4];
|
|
90
|
+
const stateDir = absolute(argv[5]);
|
|
91
|
+
const installDir = stableHomebrewXmuxInstallDir(argv[6]);
|
|
92
|
+
|
|
93
|
+
const configPath = path.join(os.homedir(), ".claude.json");
|
|
94
|
+
const config = loadJson(configPath);
|
|
95
|
+
|
|
96
|
+
const projects = config.projects ?? {};
|
|
97
|
+
if (projects === null || Array.isArray(projects) || typeof projects !== "object") {
|
|
98
|
+
throw new Error("error: ~/.claude.json projects must be a JSON object");
|
|
99
|
+
}
|
|
100
|
+
config.projects = projects;
|
|
101
|
+
|
|
102
|
+
const project = projects[projectDir] ?? {};
|
|
103
|
+
if (project === null || Array.isArray(project) || typeof project !== "object") {
|
|
104
|
+
throw new Error(`error: Claude project entry for ${projectDir} must be a JSON object`);
|
|
105
|
+
}
|
|
106
|
+
projects[projectDir] = project;
|
|
107
|
+
|
|
108
|
+
const servers = project.mcpServers ?? {};
|
|
109
|
+
if (servers === null || Array.isArray(servers) || typeof servers !== "object") {
|
|
110
|
+
throw new Error(`error: Claude mcpServers for ${projectDir} must be a JSON object`);
|
|
111
|
+
}
|
|
112
|
+
project.mcpServers = servers;
|
|
113
|
+
|
|
114
|
+
for (const legacyName of LEGACY_NAMES) {
|
|
115
|
+
delete servers[legacyName];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
servers[SERVER_NAME] = {
|
|
119
|
+
type: "stdio",
|
|
120
|
+
command: "node",
|
|
121
|
+
args: [bridgeJs, "--outbox", outbox, "--agent", agent, "--team", team],
|
|
122
|
+
env: {
|
|
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,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
atomicWriteJson(configPath, config);
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (require.main === module) {
|
|
137
|
+
try {
|
|
138
|
+
process.exitCode = main();
|
|
139
|
+
} catch (error) {
|
|
140
|
+
process.stderr.write(`${String(error.message || error)}\n`);
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
main,
|
|
147
|
+
stableHomebrewXmuxInstallDir,
|
|
148
|
+
stableHomebrewXmuxFilePath,
|
|
149
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const os = require("node:os");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
|
|
8
|
+
const SERVER_NAME = "xmux_bridge";
|
|
9
|
+
const LEGACY_NAMES = new Set([
|
|
10
|
+
"xmux_bridge",
|
|
11
|
+
"xmux-bridge",
|
|
12
|
+
"clau_mux_bridge",
|
|
13
|
+
"clau-mux-bridge",
|
|
14
|
+
"amux_bridge",
|
|
15
|
+
"amux-bridge",
|
|
16
|
+
]);
|
|
17
|
+
const TOOLS = ["write_to_lead"];
|
|
18
|
+
|
|
19
|
+
function stableHomebrewXmuxFilePath(inputPath) {
|
|
20
|
+
const resolved = path.resolve(inputPath.replace(/^~(?=$|\/)/, os.homedir()));
|
|
21
|
+
const installDir = path.dirname(resolved);
|
|
22
|
+
const marker = `${path.sep}Cellar${path.sep}xmux${path.sep}`;
|
|
23
|
+
if (!installDir.includes(marker) || !installDir.endsWith(`${path.sep}libexec`)) {
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const prefix = installDir.split(marker, 1)[0];
|
|
28
|
+
const optDir = path.join(prefix, "opt", "xmux", "libexec");
|
|
29
|
+
const candidate = path.join(optDir, path.basename(resolved));
|
|
30
|
+
if (fs.existsSync(path.join(optDir, "xmux.zsh")) && fs.existsSync(candidate)) {
|
|
31
|
+
return candidate;
|
|
32
|
+
}
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function main(argv = process.argv.slice(2)) {
|
|
37
|
+
const cmd = argv[0] ?? "npx";
|
|
38
|
+
const settingsPath = path.join(os.homedir(), ".copilot", "mcp-config.json");
|
|
39
|
+
|
|
40
|
+
let settings = {};
|
|
41
|
+
if (fs.existsSync(settingsPath)) {
|
|
42
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
43
|
+
if (settings === null || Array.isArray(settings) || typeof settings !== "object") {
|
|
44
|
+
settings = {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const servers = settings.mcpServers ?? {};
|
|
49
|
+
if (servers === null || Array.isArray(servers) || typeof servers !== "object") {
|
|
50
|
+
throw new Error("error: ~/.copilot/mcp-config.json mcpServers must be a JSON object");
|
|
51
|
+
}
|
|
52
|
+
settings.mcpServers = servers;
|
|
53
|
+
|
|
54
|
+
for (const legacyName of LEGACY_NAMES) {
|
|
55
|
+
delete servers[legacyName];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (cmd.startsWith("http")) {
|
|
59
|
+
servers[SERVER_NAME] = { type: "sse", url: cmd, tools: TOOLS };
|
|
60
|
+
} else if (cmd === "npx") {
|
|
61
|
+
servers[SERVER_NAME] = { command: "npx", args: ["-y", "xmux-bridge"], tools: TOOLS };
|
|
62
|
+
} else {
|
|
63
|
+
servers[SERVER_NAME] = {
|
|
64
|
+
command: "node",
|
|
65
|
+
args: [stableHomebrewXmuxFilePath(cmd)],
|
|
66
|
+
tools: TOOLS,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
71
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf-8");
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (require.main === module) {
|
|
76
|
+
try {
|
|
77
|
+
process.exitCode = main();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
process.stderr.write(`${String(error.message || error)}\n`);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
main,
|
|
86
|
+
stableHomebrewXmuxFilePath,
|
|
87
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const os = require("node:os");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
|
|
8
|
+
const SERVER_NAME = "xmux_bridge";
|
|
9
|
+
const LEGACY_NAMES = new Set([
|
|
10
|
+
"xmux_bridge",
|
|
11
|
+
"xmux-bridge",
|
|
12
|
+
"clau_mux_bridge",
|
|
13
|
+
"clau-mux-bridge",
|
|
14
|
+
"amux_bridge",
|
|
15
|
+
"amux-bridge",
|
|
16
|
+
]);
|
|
17
|
+
const NPM_PIN = "xmux-bridge@^1.3.0";
|
|
18
|
+
|
|
19
|
+
function stableHomebrewXmuxFilePath(inputPath) {
|
|
20
|
+
const resolved = path.resolve(inputPath.replace(/^~(?=$|\/)/, os.homedir()));
|
|
21
|
+
const installDir = path.dirname(resolved);
|
|
22
|
+
const marker = `${path.sep}Cellar${path.sep}xmux${path.sep}`;
|
|
23
|
+
if (!installDir.includes(marker) || !installDir.endsWith(`${path.sep}libexec`)) {
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const prefix = installDir.split(marker, 1)[0];
|
|
28
|
+
const optDir = path.join(prefix, "opt", "xmux", "libexec");
|
|
29
|
+
const candidate = path.join(optDir, path.basename(resolved));
|
|
30
|
+
if (fs.existsSync(path.join(optDir, "xmux.zsh")) && fs.existsSync(candidate)) {
|
|
31
|
+
return candidate;
|
|
32
|
+
}
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function main(argv = process.argv.slice(2)) {
|
|
37
|
+
const cmd = argv[0] ?? "npx";
|
|
38
|
+
const settingsPath = path.join(os.homedir(), ".gemini", "settings.json");
|
|
39
|
+
|
|
40
|
+
let settings = {};
|
|
41
|
+
if (fs.existsSync(settingsPath)) {
|
|
42
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
43
|
+
if (settings === null || Array.isArray(settings) || typeof settings !== "object") {
|
|
44
|
+
settings = {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const servers = settings.mcpServers ?? {};
|
|
49
|
+
if (servers === null || Array.isArray(servers) || typeof servers !== "object") {
|
|
50
|
+
throw new Error("error: ~/.gemini/settings.json mcpServers must be a JSON object");
|
|
51
|
+
}
|
|
52
|
+
settings.mcpServers = servers;
|
|
53
|
+
|
|
54
|
+
for (const legacyName of LEGACY_NAMES) {
|
|
55
|
+
delete servers[legacyName];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (cmd === "npx") {
|
|
59
|
+
servers[SERVER_NAME] = {
|
|
60
|
+
command: "npx",
|
|
61
|
+
args: ["-y", NPM_PIN],
|
|
62
|
+
trust: true,
|
|
63
|
+
};
|
|
64
|
+
} else {
|
|
65
|
+
servers[SERVER_NAME] = {
|
|
66
|
+
command: "node",
|
|
67
|
+
args: [stableHomebrewXmuxFilePath(cmd)],
|
|
68
|
+
trust: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
73
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf-8");
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (require.main === module) {
|
|
78
|
+
try {
|
|
79
|
+
process.exitCode = main();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
process.stderr.write(`${String(error.message || error)}\n`);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
main,
|
|
88
|
+
stableHomebrewXmuxFilePath,
|
|
89
|
+
};
|