@ganglia/cli 0.9.187 → 0.9.191
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/bin/gng.js +167 -2
- package/package.json +3 -3
package/bin/gng.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// silently. At runtime we locate that package and spawn its binary.
|
|
7
7
|
|
|
8
8
|
const { spawnSync } = require('child_process');
|
|
9
|
-
const path = require('path');
|
|
10
9
|
|
|
10
|
+
const WRAPPER_VERSION = require('../package.json').version;
|
|
11
11
|
const PLATFORM = `${process.platform}-${process.arch}`;
|
|
12
12
|
|
|
13
13
|
const PKG = {
|
|
@@ -41,6 +41,171 @@ try {
|
|
|
41
41
|
process.exit(1);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
const argv = process.argv.slice(2);
|
|
45
|
+
|
|
46
|
+
// ── Stale-binary notice ────────────────────────────────────────────────────
|
|
47
|
+
// The wrapper is pure JS and republishes on every release; a platform package
|
|
48
|
+
// only republishes when that platform's binary is rebuilt (today: linux). So
|
|
49
|
+
// `npx @ganglia/cli@latest` can hand a user a wrapper at X and a binary from
|
|
50
|
+
// an older release — which reads as "npm keeps installing the old version"
|
|
51
|
+
// with nothing on screen to explain it. Say it out loud, once, on the
|
|
52
|
+
// commands a human is watching (never on the stdio protocols).
|
|
53
|
+
const PROTOCOL_CMDS = new Set(['mcp', 'serve', 'daemon', 'watch']);
|
|
54
|
+
let binaryVersion = null;
|
|
55
|
+
try {
|
|
56
|
+
binaryVersion = require(`${PKG}/package.json`).version;
|
|
57
|
+
} catch (_) { /* version unknown — skip the notice */ }
|
|
58
|
+
|
|
59
|
+
if (binaryVersion && binaryVersion !== WRAPPER_VERSION && !PROTOCOL_CMDS.has(argv[0])) {
|
|
60
|
+
console.error(
|
|
61
|
+
`[ganglia] ${PKG} is at ${binaryVersion}, but @ganglia/cli is ${WRAPPER_VERSION} — ` +
|
|
62
|
+
`no ${WRAPPER_VERSION} binary has been published for ${PLATFORM} yet, so \`gng --version\` ` +
|
|
63
|
+
`reports ${binaryVersion}.`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── install / uninstall: register through the claude CLI from here ─────────
|
|
68
|
+
// `gng install` shells out to `claude mcp add`, and where `--scope` may sit
|
|
69
|
+
// in that command line is not stable across claude releases: 2.1.x parses
|
|
70
|
+
// `add <name> <cmd> --scope user -- <args>` but rejects the same flag placed
|
|
71
|
+
// before the positionals ("error: unknown option '--scope'") whenever a `--`
|
|
72
|
+
// tail is present; other builds want the flags first. The binary is the part
|
|
73
|
+
// we can't rebuild for every platform, so own the registration here and try
|
|
74
|
+
// both orders instead of betting the install on one of them.
|
|
75
|
+
if ((argv[0] === 'install' || argv[0] === 'uninstall') && !argv.includes('-h') && !argv.includes('--help')) {
|
|
76
|
+
process.exit(argv[0] === 'install' ? runInstall(argv) : runUninstall(argv));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function optValue(args, name, fallback) {
|
|
80
|
+
const i = args.indexOf(`--${name}`);
|
|
81
|
+
if (i !== -1 && args[i + 1] !== undefined) return args[i + 1];
|
|
82
|
+
const inline = args.find((a) => a.startsWith(`--${name}=`));
|
|
83
|
+
return inline ? inline.slice(name.length + 3) : fallback;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function claude(args) {
|
|
87
|
+
return spawnSync('claude', args, { encoding: 'utf8', env: process.env });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function claudeMissing(r) {
|
|
91
|
+
return r.error && r.error.code === 'ENOENT';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function help(args) {
|
|
95
|
+
const r = spawnSync(binary, args, { encoding: 'utf8', env: process.env });
|
|
96
|
+
return `${r.stdout || ''}${r.stderr || ''}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The binary's own MCP entry point: older builds took `--redis-port`, current
|
|
100
|
+
// ones `--graph-port`. Ask the binary we are about to register which it knows.
|
|
101
|
+
function portFlag() {
|
|
102
|
+
return help(['--help']).includes('--graph-port') ? '--graph-port' : '--redis-port';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Path form: current builds take the project dir positionally, older ones only
|
|
106
|
+
// via `-p`. Prefer the positional — `-p` anywhere on the line breaks claude's
|
|
107
|
+
// own parse (see below).
|
|
108
|
+
function pathArgs() {
|
|
109
|
+
return /Usage: gng mcp .*\[PATH\]/.test(help(['mcp', '--help'])) ? ['.'] : ['-p', '.'];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function runInstall(args) {
|
|
113
|
+
const global = args.includes('--global');
|
|
114
|
+
const port = optValue(args, 'redis-port', '6381');
|
|
115
|
+
const compress = optValue(args, 'compress', 'smart');
|
|
116
|
+
const scopeArgs = global ? ['--scope', 'user'] : [];
|
|
117
|
+
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(' Ganglia · MCP Install');
|
|
120
|
+
console.log('');
|
|
121
|
+
console.log(` scope ${global ? 'global (all projects)' : 'current project'}`);
|
|
122
|
+
console.log(` compression ${compress}`);
|
|
123
|
+
console.log('');
|
|
124
|
+
|
|
125
|
+
// Drop any existing entry first — the current name and the legacy
|
|
126
|
+
// "code-graph" one — so re-running install is idempotent.
|
|
127
|
+
for (const name of ['ganglia', 'code-graph']) {
|
|
128
|
+
const r = claude(['mcp', 'remove', ...scopeArgs, name]);
|
|
129
|
+
if (claudeMissing(r)) return notFound();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// No `-p` in the server's own args: claude scans the whole command line for
|
|
133
|
+
// its global `-p/--print` flag before `mcp add` parses, and that re-parse
|
|
134
|
+
// rejects our `--scope` wherever it sits. `gng mcp` takes the project path
|
|
135
|
+
// positionally (default "."), so a bare `.` does the same job.
|
|
136
|
+
const serverArgs = ['--', portFlag(), String(port), 'mcp', ...pathArgs(), '--compress', compress];
|
|
137
|
+
|
|
138
|
+
// Order 1 works on claude 2.1.x; order 2 is what older builds expect.
|
|
139
|
+
const forms = [
|
|
140
|
+
['mcp', 'add', 'ganglia', binary, ...scopeArgs, ...serverArgs],
|
|
141
|
+
['mcp', 'add', ...scopeArgs, 'ganglia', binary, ...serverArgs],
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
let r;
|
|
145
|
+
let ok = false;
|
|
146
|
+
for (const form of forms) {
|
|
147
|
+
r = claude(form);
|
|
148
|
+
if (claudeMissing(r)) return notFound();
|
|
149
|
+
if (r.status !== 0) continue;
|
|
150
|
+
// A zero exit isn't proof: a CLI that took the flag positionally echoes it
|
|
151
|
+
// back as part of the server's command line, which means it landed in the
|
|
152
|
+
// args instead of setting the scope. Undo that and try the other order.
|
|
153
|
+
if (`${r.stdout || ''}`.includes('--scope')) {
|
|
154
|
+
claude(['mcp', 'remove', ...scopeArgs, 'ganglia']);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
ok = true;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!ok) {
|
|
162
|
+
const err = `${r.stdout || ''}${r.stderr || ''}`.trim();
|
|
163
|
+
console.error(` ✗ Installation failed: ${err}`);
|
|
164
|
+
console.error('');
|
|
165
|
+
console.error(' Register the server directly instead (writes ~/.claude.json):');
|
|
166
|
+
console.error(` ${binary} register`);
|
|
167
|
+
console.error('');
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(' ✓ MCP server registered (ganglia)');
|
|
172
|
+
console.log('');
|
|
173
|
+
console.log(' Restart your AI client to activate.');
|
|
174
|
+
console.log(' Then: cd your-project && gng init (installs per-project hooks)');
|
|
175
|
+
console.log('');
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function runUninstall(args) {
|
|
180
|
+
const global = args.includes('--global');
|
|
181
|
+
const scopeArgs = global ? ['--scope', 'user'] : [];
|
|
182
|
+
|
|
183
|
+
console.log('Uninstalling the Ganglia MCP server...');
|
|
184
|
+
console.log(` Scope: ${global ? 'global' : 'current project'}`);
|
|
185
|
+
|
|
186
|
+
let removed = false;
|
|
187
|
+
for (const name of ['ganglia', 'code-graph']) {
|
|
188
|
+
const r = claude(['mcp', 'remove', ...scopeArgs, name]);
|
|
189
|
+
if (claudeMissing(r)) return notFound();
|
|
190
|
+
if (r.status === 0) removed = true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log(removed
|
|
194
|
+
? '\n✓ MCP server uninstalled.'
|
|
195
|
+
: '\n✓ MCP server was not registered (nothing to remove).');
|
|
196
|
+
|
|
197
|
+
// Per-project hooks are the binary's business — it knows which files it
|
|
198
|
+
// wrote. Best-effort; an old binary without the flag just no-ops.
|
|
199
|
+
spawnSync(binary, ['hooks', '--off'], { stdio: 'ignore', env: process.env });
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function notFound() {
|
|
204
|
+
console.error(" ✗ 'claude' not found — install the CLI and add it to PATH.");
|
|
205
|
+
console.error('');
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
|
|
44
209
|
// spawnSync is used intentionally:
|
|
45
210
|
// - It blocks the Node process synchronously, so there is no event loop
|
|
46
211
|
// running and no async child/close event handlers can fire.
|
|
@@ -49,7 +214,7 @@ try {
|
|
|
49
214
|
// the Rust process, which reaps it via libc::waitpid in a background thread.
|
|
50
215
|
// - Do NOT switch to spawn() with a close-handler; that would make the
|
|
51
216
|
// wrapper exit when any grandchild exits, collapsing the MCP stdio bridge.
|
|
52
|
-
const result = spawnSync(binary,
|
|
217
|
+
const result = spawnSync(binary, argv, {
|
|
53
218
|
stdio: 'inherit',
|
|
54
219
|
env: process.env,
|
|
55
220
|
windowsHide: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ganglia/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.191",
|
|
4
4
|
"description": "Ganglia — MCP tools + Code Mode for every major AI coding assistant (Claude Code, Cursor, Windsurf, Cline, OpenCode, Continue, Zed, Codex CLI, Gemini CLI)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"arm64"
|
|
38
38
|
],
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"@ganglia/cli-linux-x64": "0.9.
|
|
41
|
-
"@ganglia/cli-linux-arm64": "0.9.
|
|
40
|
+
"@ganglia/cli-linux-x64": "0.9.191",
|
|
41
|
+
"@ganglia/cli-linux-arm64": "0.9.191",
|
|
42
42
|
"@ganglia/cli-darwin-x64": "0.9.78",
|
|
43
43
|
"@ganglia/cli-darwin-arm64": "0.9.78",
|
|
44
44
|
"@ganglia/cli-win32-x64": "0.9.78"
|