@firedmosquito831/my-claude-code 6.63.0 → 6.65.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/bin/postinstall.js +12 -0
- package/bin/runtime-install.js +195 -2
- package/package.json +1 -2
package/bin/postinstall.js
CHANGED
|
@@ -63,6 +63,18 @@ function skipReason(env) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
async function main() {
|
|
66
|
+
// Take back the `my-claude-code` command this package published up to
|
|
67
|
+
// 6.63.0, on EVERY global install -- before the opt-outs, because this is a
|
|
68
|
+
// migration and not an install. It is a console script of the wheel, and on
|
|
69
|
+
// Windows npm's global bin sits ahead of the uv tool bin directory on PATH,
|
|
70
|
+
// so the shim shadowed the real launcher and made `install.ps1` refuse to
|
|
71
|
+
// verify an install that had in fact worked. Somebody who sets
|
|
72
|
+
// MCC_NPM_SKIP_INSTALL=1 still wants the broken shim gone; npm does not
|
|
73
|
+
// reliably reap a bin its package has stopped declaring.
|
|
74
|
+
if (process.env.npm_config_global === "true") {
|
|
75
|
+
runtime.removeStaleGlobalShim({ log: note });
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
const reason = skipReason(process.env);
|
|
67
79
|
if (reason !== null) {
|
|
68
80
|
note(reason);
|
package/bin/runtime-install.js
CHANGED
|
@@ -287,6 +287,173 @@ function windowsInstallArgv(installer, directory) {
|
|
|
287
287
|
return { command: installer, args: argv };
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
/**
|
|
291
|
+
* The name this package used to publish, and must never publish again.
|
|
292
|
+
*
|
|
293
|
+
* `my-claude-code` is a console script of the WHEEL (`[project.scripts]`), and
|
|
294
|
+
* on Windows npm's global bin directory precedes `~/.local/bin` on PATH. So an
|
|
295
|
+
* `npm install -g` of 6.53.1 through 6.63.0 left `%APPDATA%\npm\my-claude-code.cmd`
|
|
296
|
+
* sitting in front of the real launcher -- and `install.ps1` verified its
|
|
297
|
+
* launchers by asking PATH, resolved npm's shim, concluded that a complete
|
|
298
|
+
* install had put its files somewhere illegal, and threw. Permanently: every
|
|
299
|
+
* later run of the one-liner failed the same way, on a machine where nothing
|
|
300
|
+
* was wrong.
|
|
301
|
+
*
|
|
302
|
+
* 6.64.0 publishes one bin, `mcc`, which no wheel entry point claims. This
|
|
303
|
+
* removes the leftover from the earlier versions, because npm does not
|
|
304
|
+
* reliably reap a bin its package stopped declaring, and because the machines
|
|
305
|
+
* that need it most are exactly the ones already broken.
|
|
306
|
+
*/
|
|
307
|
+
const STALE_BIN_NAME = "my-claude-code";
|
|
308
|
+
const PACKAGE_NAME = "@firedmosquito831/my-claude-code";
|
|
309
|
+
|
|
310
|
+
/** npm's global bin directory, or null when this is not a global install. */
|
|
311
|
+
function npmGlobalBinDirectory(env, platform) {
|
|
312
|
+
const prefix = env.npm_config_global_prefix || env.npm_config_prefix;
|
|
313
|
+
if (!prefix) return null;
|
|
314
|
+
// On Windows the prefix IS the directory holding the shims; everywhere else
|
|
315
|
+
// they are in bin/ under it.
|
|
316
|
+
return platform === "win32" ? prefix : path.join(prefix, "bin");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Every shape npm writes a global bin in, for one name. */
|
|
320
|
+
function globalShimPaths(binDir, name, platform) {
|
|
321
|
+
if (platform === "win32") {
|
|
322
|
+
return [
|
|
323
|
+
path.join(binDir, name),
|
|
324
|
+
path.join(binDir, `${name}.cmd`),
|
|
325
|
+
path.join(binDir, `${name}.ps1`),
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
return [path.join(binDir, name)];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Is this file a shim npm wrote for THIS package?
|
|
333
|
+
*
|
|
334
|
+
* A symlink's target and a .cmd shim's text both name the package directory
|
|
335
|
+
* they point into, so the check is the package name -- never the file name.
|
|
336
|
+
* Deleting a `my-claude-code` that belongs to somebody else would be exactly
|
|
337
|
+
* the kind of damage this whole change exists to stop.
|
|
338
|
+
*/
|
|
339
|
+
function isShimForThisPackage(file) {
|
|
340
|
+
let text = null;
|
|
341
|
+
try {
|
|
342
|
+
const stat = fs.lstatSync(file);
|
|
343
|
+
text = stat.isSymbolicLink() ? fs.readlinkSync(file) : fs.readFileSync(file, "utf8");
|
|
344
|
+
} catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
if (typeof text !== "string") return false;
|
|
348
|
+
return text.replace(/\\/g, "/").includes(PACKAGE_NAME);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Remove the `my-claude-code` shim an earlier version of this package left. */
|
|
352
|
+
function removeStaleGlobalShim(options) {
|
|
353
|
+
const env = options.env ?? process.env;
|
|
354
|
+
const platform = options.platform ?? process.platform;
|
|
355
|
+
const log = options.log ?? ((message) => console.log(`my-claude-code: ${message}`));
|
|
356
|
+
const binDir = options.binDir ?? npmGlobalBinDirectory(env, platform);
|
|
357
|
+
if (!binDir) return [];
|
|
358
|
+
|
|
359
|
+
const removed = [];
|
|
360
|
+
for (const file of globalShimPaths(binDir, STALE_BIN_NAME, platform)) {
|
|
361
|
+
if (!isShimForThisPackage(file)) continue;
|
|
362
|
+
try {
|
|
363
|
+
fs.rmSync(file, { force: true });
|
|
364
|
+
removed.push(file);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
367
|
+
log(
|
|
368
|
+
`could not remove the old ${STALE_BIN_NAME} command at ${file}: ${message}. ` +
|
|
369
|
+
`Remove it by hand, or run \`npm uninstall -g ${PACKAGE_NAME}\` and install again.`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (removed.length > 0) {
|
|
374
|
+
log(
|
|
375
|
+
`removed the old ${STALE_BIN_NAME} command this package used to publish (${removed.join(", ")}). ` +
|
|
376
|
+
"It shadowed the launcher the installer provides, which is what made the installer refuse to verify itself."
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return removed;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Where the installer's full output is kept for a hook that cannot show it,
|
|
384
|
+
* or null when nothing here can be written.
|
|
385
|
+
*
|
|
386
|
+
* `os.tmpdir()` is a guess -- it reads TMPDIR/TEMP/TMP and falls back to the
|
|
387
|
+
* system directory, and on a Windows runner with a stripped environment that
|
|
388
|
+
* fallback was `C:\Windows\temp`, which did not exist. So the directory is
|
|
389
|
+
* created and the path is probed HERE, where a failure is a missing log and
|
|
390
|
+
* not a crashed install.
|
|
391
|
+
*/
|
|
392
|
+
function installerLogPath() {
|
|
393
|
+
const candidate = path.join(os.tmpdir(), `mcc-install-${process.pid}.log`);
|
|
394
|
+
try {
|
|
395
|
+
fs.mkdirSync(path.dirname(candidate), { recursive: true });
|
|
396
|
+
fs.writeFileSync(candidate, "");
|
|
397
|
+
return candidate;
|
|
398
|
+
} catch {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Run the official installer, streaming its output into a log file.
|
|
405
|
+
*
|
|
406
|
+
* `npm install -g` swallows a postinstall's stdout, and on failure replays the
|
|
407
|
+
* WHOLE captured stream under an `npm error` prefix -- which is how a fresh
|
|
408
|
+
* machine came to read a page of ordinary uv progress as if every line were an
|
|
409
|
+
* error. So the full text goes to a file, only the installer's own step lines
|
|
410
|
+
* (`==> ...`) and its warnings reach the console, and the failure message
|
|
411
|
+
* names both the file and `--foreground-scripts`.
|
|
412
|
+
*/
|
|
413
|
+
function runInstallerLogged(command, args, log, logPath) {
|
|
414
|
+
return new Promise((resolve, reject) => {
|
|
415
|
+
let stream = null;
|
|
416
|
+
if (logPath) {
|
|
417
|
+
try {
|
|
418
|
+
stream = fs.createWriteStream(logPath, { flags: "a" });
|
|
419
|
+
// A WriteStream reports a failed open ASYNCHRONOUSLY, as an 'error'
|
|
420
|
+
// event -- and an unhandled 'error' on a stream throws out of the
|
|
421
|
+
// event loop and kills the process. That is a log file taking an
|
|
422
|
+
// install down with it, which is the opposite of the point.
|
|
423
|
+
stream.on("error", () => {
|
|
424
|
+
stream = null;
|
|
425
|
+
});
|
|
426
|
+
} catch {
|
|
427
|
+
stream = null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const child = childProcess.spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
431
|
+
let pending = "";
|
|
432
|
+
const consume = (chunk) => {
|
|
433
|
+
const text = chunk.toString();
|
|
434
|
+
if (stream) stream.write(text);
|
|
435
|
+
pending += text;
|
|
436
|
+
const lines = pending.split(/\r?\n/);
|
|
437
|
+
pending = lines.pop() ?? "";
|
|
438
|
+
for (const line of lines) {
|
|
439
|
+
if (line.startsWith("==> ") || line.startsWith("WARNING:")) {
|
|
440
|
+
log(line);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
child.stdout.on("data", consume);
|
|
445
|
+
child.stderr.on("data", consume);
|
|
446
|
+
child.on("error", (error) => {
|
|
447
|
+
if (stream) stream.end();
|
|
448
|
+
reject(new Error(`could not run ${command}: ${error.message}`));
|
|
449
|
+
});
|
|
450
|
+
child.on("close", (code) => {
|
|
451
|
+
if (stream) stream.end();
|
|
452
|
+
resolve(code ?? 1);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
290
457
|
function run(command, args, options) {
|
|
291
458
|
const result = childProcess.spawnSync(command, args, { stdio: "inherit", ...options });
|
|
292
459
|
if (result.error) throw new Error(`could not run ${command}: ${result.error.message}`);
|
|
@@ -425,10 +592,28 @@ async function performInstall(options) {
|
|
|
425
592
|
|
|
426
593
|
if (decision.server) {
|
|
427
594
|
const { command, args } = serverInstallerCommand(platform, decision.desktopFlag);
|
|
428
|
-
const
|
|
595
|
+
const logPath = options.logPath ?? installerLogPath();
|
|
596
|
+
log(
|
|
597
|
+
logPath
|
|
598
|
+
? `installing the server; the installer's full output goes to ${logPath}`
|
|
599
|
+
: "installing the server (no writable temporary directory, so the full output is not being kept)"
|
|
600
|
+
);
|
|
601
|
+
const status = await runInstallerLogged(command, args, log, logPath);
|
|
429
602
|
if (status !== 0) {
|
|
603
|
+
// Say which half landed. This used to claim "Nothing was left
|
|
604
|
+
// half-installed by this hook" unconditionally -- and on the machine
|
|
605
|
+
// that prompted this change the server WAS installed and the desktop app
|
|
606
|
+
// was not, so the one sentence the user had was the false one.
|
|
430
607
|
console.error(
|
|
431
|
-
`my-claude-code: the installer exited ${status}
|
|
608
|
+
`my-claude-code: the server installer exited ${status}.\n` +
|
|
609
|
+
`my-claude-code: the desktop app was not attempted. The server may be partly installed: check with \`mcc-server --version\`, ` +
|
|
610
|
+
"and the installer names any command that is missing.\n" +
|
|
611
|
+
(logPath
|
|
612
|
+
? `my-claude-code: the full output is in ${logPath}.\n`
|
|
613
|
+
: "my-claude-code: the full output could not be kept (no writable temporary directory).\n") +
|
|
614
|
+
"my-claude-code: retry the server with `npx @firedmosquito831/my-claude-code install --server-only`, the app with " +
|
|
615
|
+
"`npx @firedmosquito831/my-claude-code install --desktop-only`, or rerun " +
|
|
616
|
+
"`npm install -g --foreground-scripts @firedmosquito831/my-claude-code` to watch the installer live."
|
|
432
617
|
);
|
|
433
618
|
return status;
|
|
434
619
|
}
|
|
@@ -465,7 +650,15 @@ async function performInstall(options) {
|
|
|
465
650
|
|
|
466
651
|
module.exports = {
|
|
467
652
|
DESKTOP_ASSETS,
|
|
653
|
+
PACKAGE_NAME,
|
|
468
654
|
REPO_RAW,
|
|
655
|
+
STALE_BIN_NAME,
|
|
656
|
+
globalShimPaths,
|
|
657
|
+
installerLogPath,
|
|
658
|
+
isShimForThisPackage,
|
|
659
|
+
npmGlobalBinDirectory,
|
|
660
|
+
removeStaleGlobalShim,
|
|
661
|
+
runInstallerLogged,
|
|
469
662
|
performInstall,
|
|
470
663
|
serverInstallerCommand,
|
|
471
664
|
HELP,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firedmosquito831/my-claude-code",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.65.0",
|
|
4
4
|
"description": "My Claude Code (MCC): route Claude Code and other coding agents to any model provider through one local proxy with a dashboard. This npm package installs and launches the Python server.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "FiredMosquito831",
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"postinstall": "node bin/postinstall.js"
|
|
29
29
|
},
|
|
30
30
|
"bin": {
|
|
31
|
-
"my-claude-code": "bin/my-claude-code.js",
|
|
32
31
|
"mcc": "bin/my-claude-code.js"
|
|
33
32
|
},
|
|
34
33
|
"files": [
|