skalpel 4.0.4 → 4.0.6

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/install.mjs CHANGED
@@ -67,14 +67,17 @@ if (!uninstall && !process.env.SKALPEL_HOOK_BIN) {
67
67
 
68
68
  // The wired command. Defaults to the staged absolute `node <path>`; SKALPEL_HOOK_BIN /
69
69
  // SKALPEL_HOOK_SESSION_BIN still override (dev, or a deliberate global-bin install).
70
- const CMD = process.env.SKALPEL_HOOK_BIN || `node ${HOOK_FILE}`;
71
- const SESSION_CMD = process.env.SKALPEL_HOOK_SESSION_BIN || `node ${SESSION_FILE}`;
70
+ // QUOTE the path: on Windows the staged path is `C:\Users\First Last\.skalpel\hooks\…`, and an
71
+ // UNQUOTED `node <path>` splits at the space so node gets `C:\Users\First` → ERR_MODULE_NOT_FOUND
72
+ // (the esm/resolve crash on every hook). Double-quotes are safe on Windows cmd/PowerShell AND POSIX.
73
+ const CMD = process.env.SKALPEL_HOOK_BIN || `node "${HOOK_FILE}"`;
74
+ const SESSION_CMD = process.env.SKALPEL_HOOK_SESSION_BIN || `node "${SESSION_FILE}"`;
72
75
  const sessionEndRuntime =
73
76
  process.env.SKALPEL_HOOK_BIN && PKG_DIR !== HOOKS_DIR
74
77
  ? join(PKG_DIR, "skalpel-hook-session-end.mjs")
75
78
  : SESSION_END_FILE;
76
- const SESSION_END_CMD = process.env.SKALPEL_HOOK_SESSION_END_BIN || `node ${sessionEndRuntime}`;
77
- const STATUSLINE_CMD = process.env.SKALPEL_STATUSLINE_BIN || `node ${STATUSLINE_FILE}`;
79
+ const SESSION_END_CMD = process.env.SKALPEL_HOOK_SESSION_END_BIN || `node "${sessionEndRuntime}"`;
80
+ const STATUSLINE_CMD = process.env.SKALPEL_STATUSLINE_BIN || `node "${STATUSLINE_FILE}"`;
78
81
  const CLAUDE = join(homedir(), ".claude", "settings.json");
79
82
  const CODEX_DIR = join(homedir(), ".codex");
80
83
  const CODEX = join(CODEX_DIR, "hooks.json");
@@ -83,6 +86,8 @@ const CODEX_TOML = join(CODEX_DIR, "config.toml"); // Codex reads hooks from con
83
86
  // installer wired, including bare-bin names and absolute `node <path>` commands.
84
87
  const markerOf = (cmd) =>
85
88
  cmd
89
+ .replace(/"/g, "") // tolerate the quoted `node "<path>"` form (Windows-safe) — strip quotes first
90
+ .trim()
86
91
  .split(/[/\\]/)
87
92
  .pop()
88
93
  .replace(/\.(mjs|js|exe)$/, "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skalpel",
3
- "version": "4.0.4",
3
+ "version": "4.0.6",
4
4
  "description": "Behavioral graph for AI-assisted coding — learns how you work and steers Claude Code + Codex in real time.",
5
5
  "type": "module",
6
6
  "bin": {
package/postinstall.mjs CHANGED
@@ -57,8 +57,26 @@ function stageStatusline() {
57
57
  }
58
58
  }
59
59
 
60
+ // If this postinstall ran under `sudo npm install -g`, we're root but a REAL user invoked it — so
61
+ // anything we just created in their HOME (the staged ~/.skalpel) is root-owned, a landmine for the
62
+ // later non-root `skalpel` (that's Ryaan's EACCES on copyfile into ~/.skalpel/hooks). Chown it back to
63
+ // the invoking user so the sudo install path isn't left broken.
64
+ function fixSudoOwnership() {
65
+ try {
66
+ if (process.platform === "win32") return; // no uid / sudo model
67
+ if (typeof process.getuid !== "function" || process.getuid() !== 0) return; // not root — nothing to undo
68
+ const uid = process.env.SUDO_UID;
69
+ if (!uid) return; // genuinely root (not via sudo) — don't touch their files
70
+ const gid = process.env.SUDO_GID || uid;
71
+ spawnSync("chown", ["-R", `${uid}:${gid}`, join(homedir(), ".skalpel")], q);
72
+ } catch {
73
+ /* best-effort — worst case the user runs `sudo chown -R $(whoami) ~/.skalpel` themselves */
74
+ }
75
+ }
76
+
60
77
  retireLegacyDaemon();
61
78
  stageStatusline();
79
+ fixSudoOwnership();
62
80
 
63
81
  // A quiet nudge — the real onboarding (Google sign-in → build graph → insights) runs on `skalpel`.
64
82
  if (process.stdout.isTTY && !process.env.CI) {