@firedmosquito831/my-claude-code 7.78.10 → 7.79.1

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
@@ -21,11 +21,31 @@ release of the right shape:
21
21
  `install.sh`), which brings `uv`, Python 3.14 and every `mcc-*` command;
22
22
  - **the native desktop application, where there is a desktop session** —
23
23
  Windows and macOS always, Linux only with `DISPLAY` or `WAYLAND_DISPLAY`.
24
- Over SSH, in CI, on a headless server or in WSL without a display it
25
- installs the server alone, without even the installer's desktop flag, so no
24
+ Over SSH, in CI, on a headless server or in WSL — with or without a display,
25
+ because the desktop app for a Windows PC is the Windows one — it installs
26
+ the server alone, without even the installer's desktop flag, so no
26
27
  shortcuts are created for a screen that does not exist.
27
28
 
28
- It prints one line saying what it decided and why before it does anything.
29
+ It prints one line saying what it decided and why before it does anything
30
+ (npm shows a hook's output only with `--foreground-scripts`).
31
+
32
+ ### With a system Node, sudo, and a user-level prefix
33
+
34
+ My Claude Code installs **per user**. With a system Node whose global folder
35
+ belongs to root, `sudo npm install -g` gives you the `mcc` command and nothing
36
+ else: run as root on behalf of your user, the hook installs no server for root
37
+ (a root-owned copy you could not use, and a root server on your port) and the
38
+ server is installed by your first `mcc` — as you, not root. Simpler still, give
39
+ npm a folder you own and skip sudo entirely:
40
+
41
+ ```sh
42
+ npm config set prefix ~/.npm-global # then add ~/.npm-global/bin to PATH
43
+ npm install -g @firedmosquito831/my-claude-code
44
+ ```
45
+
46
+ To install for root on purpose, set `MCC_INSTALL_ALLOW_ROOT=1` (or run
47
+ `mcc install --allow-root`). A root shell, a root VPS or a Docker image — no
48
+ `SUDO_USER` — is not affected.
29
49
 
30
50
  | Platform | What the desktop half installs |
31
51
  | --- | --- |
@@ -64,8 +84,10 @@ too, each printing one line saying so.
64
84
 
65
85
  If the server installs and the desktop half fails — a flaky network, a 404 —
66
86
  the install stays green and tells you how to retry just that half. A failing
67
- *server* install fails `npm install -g` with it; nothing is left
68
- half-installed.
87
+ *server* install fails `npm install -g` with it — and that includes `curl`
88
+ being missing or the installer download failing on Linux and macOS, which
89
+ fail with a `WARNING:` line saying so rather than a "done" with nothing
90
+ installed.
69
91
 
70
92
  ## Run without installing globally
71
93
 
@@ -89,7 +111,9 @@ npm uninstall -g @firedmosquito831/my-claude-code # removes only this launcher
89
111
 
90
112
  Run `mcc uninstall` **first**: npm removes the launcher it installed and nothing
91
113
  else, so uninstalling the package on its own leaves the Python server in place.
92
- The desktop application is uninstalled the way its platform expects — Add or
114
+ On Windows `mcc uninstall` keeps the Start Menu entry when it is the
115
+ installed desktop app's own. The desktop application is uninstalled the way
116
+ its platform expects — Add or
93
117
  remove programs on Windows, deleting it from `~/Applications` on macOS,
94
118
  `sudo dpkg -r my-claude-code-desktop` or `install-desktop.sh --uninstall` on
95
119
  Linux.
@@ -88,8 +88,21 @@ async function main() {
88
88
 
89
89
  // No argv: `npm install -g` passes none. Overrides here are the environment
90
90
  // variable, which is the only channel a package manager or Dockerfile has.
91
- const status = await runtime.performInstall({ argv: [], log: note });
92
- if (status === 0) {
91
+ //
92
+ // 7.79.1: as root through sudo on behalf of a user, nothing is installed
93
+ // for root and the hook still exits 0 (`refusedStatus: 0`), so npm keeps the
94
+ // `mcc` command it wrote -- with a system Node that is the one thing sudo
95
+ // was needed for. The user's first `mcc` then installs the server, as them.
96
+ let refused = false;
97
+ const status = await runtime.performInstall({
98
+ argv: [],
99
+ log: note,
100
+ refusedStatus: 0,
101
+ onRefused: () => {
102
+ refused = true;
103
+ },
104
+ });
105
+ if (status === 0 && !refused) {
93
106
  note("done. Open a new terminal so PATH picks up the mcc-* commands.");
94
107
  }
95
108
  return status;
@@ -62,6 +62,10 @@ const OVERRIDE_FLAGS = [
62
62
  // environment without any help from here.
63
63
  "--no-restart",
64
64
  "--no-start",
65
+ // 7.79.1: install for root even when this runs as root through sudo on
66
+ // behalf of another user. Passed through to install.sh, which refuses that
67
+ // case too; the environment form is MCC_INSTALL_ALLOW_ROOT=1.
68
+ "--allow-root",
65
69
  ];
66
70
 
67
71
  /**
@@ -89,7 +93,11 @@ By default it installs the server (always) and, when this machine has a
89
93
  desktop session, the desktop application in its native form: the setup.exe on
90
94
  Windows, the .dmg into ~/Applications on macOS, the .deb (or the tarball's
91
95
  per-user installer) on Linux. On a headless box, over SSH, in CI or in WSL
92
- without a display it installs the server only and says so.
96
+ (display or not -- the desktop app for a Windows PC is the Windows one) it
97
+ installs the server only and says so.
98
+
99
+ My Claude Code installs per user. Run as root through sudo on behalf of
100
+ another user, it installs nothing and says how to install for that user.
93
101
 
94
102
  --server-only install the server, never the desktop app
95
103
  --desktop-only install the desktop app, leave the server alone
@@ -98,6 +106,7 @@ without a display it installs the server only and says so.
98
106
  --no-restart do not stop a My Claude Code server that is already
99
107
  running. One is still started if nothing answers.
100
108
  --no-start do not stop and do not start any server
109
+ --allow-root install for root even under sudo (MCC_INSTALL_ALLOW_ROOT=1)
101
110
  --help this text
102
111
 
103
112
  The official installer restarts the server on the configured port by default
@@ -120,7 +129,7 @@ the same release before it is run, and deleted if the digest does not match.`;
120
129
  * this same command runs in far more often. CI and SSH override everything:
121
130
  * both mean "no human is looking at this screen", whatever the platform says.
122
131
  */
123
- function displayState(platform, env) {
132
+ function displayState(platform, env, wsl) {
124
133
  if (env.CI) return { desktop: false, why: "CI is set" };
125
134
  if (env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT) {
126
135
  return { desktop: false, why: "this is an SSH session" };
@@ -128,14 +137,39 @@ function displayState(platform, env) {
128
137
  if (platform === "win32") return { desktop: true, why: "Windows always has a session" };
129
138
  if (platform === "darwin") return { desktop: true, why: "macOS always has a session" };
130
139
  if (platform === "linux") {
140
+ // 7.79.1: WSL is server only, display or not. WSLg sets DISPLAY and
141
+ // WAYLAND_DISPLAY, so this used to install the LINUX desktop app inside
142
+ // WSL -- a second, separate install on a PC whose desktop app is the
143
+ // Windows one (and `mcc-desktop` refuses to run in WSL at all).
144
+ if (wsl ?? isWsl(platform, env)) {
145
+ return { desktop: false, why: "WSL, which gets the server only -- the desktop app for this PC is the Windows one" };
146
+ }
131
147
  if (env.WAYLAND_DISPLAY) return { desktop: true, why: `WAYLAND_DISPLAY=${env.WAYLAND_DISPLAY}` };
132
148
  if (env.DISPLAY) return { desktop: true, why: `DISPLAY=${env.DISPLAY}` };
133
- const wsl = env.WSL_DISTRO_NAME ? "WSL without a display" : "no DISPLAY or WAYLAND_DISPLAY";
134
- return { desktop: false, why: wsl };
149
+ return { desktop: false, why: "no DISPLAY or WAYLAND_DISPLAY" };
135
150
  }
136
151
  return { desktop: false, why: `${platform} has no desktop build` };
137
152
  }
138
153
 
154
+ /** The kernel file the product reads to recognise WSL (config/paths.py). */
155
+ const WSL_OSRELEASE_PATH = "/proc/sys/kernel/osrelease";
156
+
157
+ /**
158
+ * Whether this Linux is WSL, by the product's own tests: the kernel's release
159
+ * string names Microsoft (config/paths.py, config/claude_discovery.py), or
160
+ * WSL_DISTRO_NAME / WSL_INTEROP are set (the sign-in flows' test). sudo's
161
+ * env_reset strips the two variables; it cannot strip the kernel string.
162
+ */
163
+ function isWsl(platform, env) {
164
+ if (platform !== "linux") return false;
165
+ if (env.WSL_DISTRO_NAME || env.WSL_INTEROP) return true;
166
+ try {
167
+ return /microsoft/i.test(fs.readFileSync(WSL_OSRELEASE_PATH, "utf8"));
168
+ } catch {
169
+ return false;
170
+ }
171
+ }
172
+
139
173
  /** The overrides present in `argv`, and any argument that is not one. */
140
174
  function parseFlags(argv) {
141
175
  const flags = new Set();
@@ -229,11 +263,15 @@ function decide(options) {
229
263
  if (flags.has("--no-start")) serverFlags.push("--no-start");
230
264
  else if (flags.has("--no-restart")) serverFlags.push("--no-restart");
231
265
  if (!desktop && !flags.has("--no-start")) serverFlags.push("--no-desktop");
266
+ // install.sh's own sudo guard reads the same choice. install.ps1 has no
267
+ // such guard (and no such switch), so Windows never passes it.
268
+ if (flags.has("--allow-root") && platform !== "win32") serverFlags.push("--allow-root");
232
269
 
233
270
  return {
234
271
  server,
235
272
  desktop,
236
273
  serverFlags,
274
+ allowRoot: flags.has("--allow-root"),
237
275
  // The server installer's `-Desktop`/`--desktop` flag adds the `mcc-desktop`
238
276
  // launcher and its shortcuts. A headless box has nowhere to put them.
239
277
  desktopFlag: desktop,
@@ -606,7 +644,59 @@ function serverInstallerCommand(platform, withDesktop, script, serverFlags) {
606
644
  }
607
645
  const posix = (withDesktop ? ["--desktop"] : []).concat(extras);
608
646
  const flag = posix.length ? ` -s -- ${posix.join(" ")}` : "";
609
- return { command: "sh", args: ["-c", `curl -fsSL "${REPO_RAW}/${name}.sh" | sh${flag}`] };
647
+ // 7.79.1: download first, then run -- never `curl ... | sh`. A pipeline
648
+ // exits with its LAST command's status, and `sh` fed an empty script exits
649
+ // 0, so a missing curl or a failed download was reported as a successful
650
+ // install ("done", with nothing installed) and `mcc uninstall` as a
651
+ // successful uninstall. Holding the whole script before running it also
652
+ // means a download cut off half way is never executed. The WARNING: prefix
653
+ // is what the npm hook's console filter lets through.
654
+ const url = `${REPO_RAW}/${name}.sh`;
655
+ const posixCommand =
656
+ `command -v curl >/dev/null 2>&1 || { echo "WARNING: curl is not installed, and it is what downloads ${name}.sh. ` +
657
+ `Install curl (for example: sudo apt install curl) and run this again." >&2; exit 127; }; ` +
658
+ `script=$(curl -fsSL "${url}") || { status=$?; echo "WARNING: could not download ${url} (curl exit $status). ` +
659
+ `Nothing was run. Check the network (and HTTPS_PROXY, if you use one) and run this again." >&2; exit $status; }; ` +
660
+ `[ -n "$script" ] || { echo "WARNING: ${url} came back empty. Nothing was run." >&2; exit 1; }; ` +
661
+ `printf '%s\\n' "$script" | sh${flag}`;
662
+ return { command: "sh", args: ["-c", posixCommand] };
663
+ }
664
+
665
+ /**
666
+ * The user this run acts for when it is root through sudo, or null.
667
+ *
668
+ * 7.79.1. My Claude Code is a per-user install (uv's tool directory,
669
+ * ~/.local/bin, the config home), so `sudo npm install -g` -- which a system
670
+ * Node needs only to write the `mcc` command into a root-owned prefix --
671
+ * installed a second, root-owned copy for root and started a root server on
672
+ * the user's port (specs/INVESTIGATION-NPM-SUDO.md). Only root acting FOR a
673
+ * real user is refused: a root shell, a root VPS and a Docker image have no
674
+ * SUDO_USER (or SUDO_USER=root) and are unaffected.
675
+ */
676
+ function sudoInvoker(platform, env, uid, allowRoot) {
677
+ if (platform === "win32" || uid !== 0) return null;
678
+ const user = env.SUDO_USER;
679
+ if (!user || user === "root") return null;
680
+ if (allowRoot || env.MCC_INSTALL_ALLOW_ROOT === "1") return null;
681
+ return user;
682
+ }
683
+
684
+ /** This process's uid, or -1 where there is none (Windows). */
685
+ function currentUid() {
686
+ return typeof process.getuid === "function" ? process.getuid() : -1;
687
+ }
688
+
689
+ /** What a sudo run says instead of installing anything for root. */
690
+ function sudoRefusal(user) {
691
+ return (
692
+ `this is running as root through sudo, on behalf of ${user}, and My Claude Code installs per user. ` +
693
+ `As root it would install a second, root-owned copy that ${user} cannot use or update, and start a root-owned ` +
694
+ `server on ${user}'s port in place of ${user}'s own -- so nothing was installed for root. ` +
695
+ `The \`mcc\` command itself is in place: run \`mcc install\` (or just \`mcc\`) as ${user}, without sudo. ` +
696
+ `Next time, a user-level npm prefix needs no sudo at all: \`npm config set prefix ~/.npm-global\` ` +
697
+ "(and add ~/.npm-global/bin to PATH), then `npm install -g @firedmosquito831/my-claude-code`. " +
698
+ "To install for root on purpose: MCC_INSTALL_ALLOW_ROOT=1, or `mcc install --allow-root`."
699
+ );
610
700
  }
611
701
 
612
702
  /**
@@ -640,6 +730,21 @@ async function performInstall(options) {
640
730
 
641
731
  // The one line the spec asks for: what, where, and why.
642
732
  log(decision.reason);
733
+ // 7.79.1: nothing at all for root acting for another user -- no server and
734
+ // no desktop app. `npm install -g` passes `refusedStatus: 0`, so the `mcc`
735
+ // command it wrote (the only thing sudo was needed for) stays; `mcc
736
+ // install` and a bare `mcc` keep the default 1 and fail honestly.
737
+ const invoker = sudoInvoker(
738
+ platform,
739
+ options.env ?? process.env,
740
+ options.uid ?? currentUid(),
741
+ decision.allowRoot
742
+ );
743
+ if (invoker) {
744
+ log(sudoRefusal(invoker));
745
+ if (options.onRefused) options.onRefused(invoker);
746
+ return options.refusedStatus ?? 1;
747
+ }
643
748
  if (!decision.server && !decision.desktop) {
644
749
  log("nothing to do (MCC_NPM_INSTALL=none).");
645
750
  return 0;
@@ -730,8 +835,12 @@ module.exports = {
730
835
  displayState,
731
836
  downloadVerified,
732
837
  installDesktopFrom,
838
+ isWsl,
733
839
  parseFlags,
734
840
  parseSums,
735
841
  sha256,
842
+ sudoInvoker,
843
+ sudoRefusal,
736
844
  windowsInstallArgv,
845
+ WSL_OSRELEASE_PATH,
737
846
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firedmosquito831/my-claude-code",
3
- "version": "7.78.10",
3
+ "version": "7.79.1",
4
4
  "description": "My Claude Code (MCC): a local multi-provider LLM proxy, model router and control plane for Claude Code, Codex, OpenCode, Gemini CLI and other AI coding agents, with 57 providers, fallback routing, credential health, a native web-search tool proxy, and analytics with cost provenance. This npm package installs and launches the Python server.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "author": "FiredMosquito831",