skalpel 4.0.5 → 4.0.7

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
@@ -4,13 +4,18 @@ Behavioral graph for AI-assisted coding. Learns how you work from your Claude Co
4
4
  steers the model in real time — off the loops, re-explains, and re-fixes that eat your day.
5
5
 
6
6
  ```bash
7
- npm install -g skalpel
8
- skalpel # sign in with Google → build your graph → see your insights → you're live
7
+ npx skalpel@latest # sign in with Google → build your graph → see your insights → you're live
9
8
  ```
10
9
 
10
+ That's it — no global install. `npx` fetches the latest and runs the one-time setup (which copies
11
+ the hooks into `~/.skalpel` so they persist afterward). It sidesteps every machine-specific install
12
+ failure: no global-bin `PATH` to configure, no `sudo`, and it's always the newest version — so a
13
+ stale copy can never leave you on old code. Prefer a permanent install? `npm install -g skalpel`
14
+ still works and behaves identically.
15
+
11
16
  ## What it does
12
17
 
13
- - **`skalpel`** (or the postinstall nudge) runs the onboarding: red-banner wordmark → **Sign in with
18
+ - **`npx skalpel@latest`** (or `skalpel` after a global install) runs the onboarding: red-banner wordmark → **Sign in with
14
19
  Google** (AWS-hosted, no localhost) → **builds your graph** (async, live progress) → **reveals your
15
20
  insights** (your hours, rework %, signature trap, biggest time-sink) → wires the hooks → you're live.
16
21
  - After that, every prompt in Claude Code / Codex pulls your own behavioral graph via a fast hook, and a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skalpel",
3
- "version": "4.0.5",
3
+ "version": "4.0.7",
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) {
package/skalpel-setup.mjs CHANGED
@@ -727,6 +727,48 @@ async function revealInsights(report) {
727
727
  );
728
728
  }
729
729
 
730
+ // Self-heal preflight. The install failures friends hit were never our code — they were the MACHINE:
731
+ // a ~/.skalpel left root-owned by a past `sudo npm i -g` (so we can't stage hooks into it), or a Node
732
+ // too old for the hook runtime. Catch both HERE with a one-line fix instead of a cryptic EACCES stack
733
+ // trace five calls deep. Runs only on the setup path (login/logout/uninstall return before this).
734
+ function preflight() {
735
+ const major = Number.parseInt(process.versions.node.split(".")[0], 10);
736
+ if (major < 18) {
737
+ console.error(
738
+ ` ${R}✗${X} skalpel needs Node 18+ — you have ${process.versions.node}. Update Node, then re-run.`,
739
+ );
740
+ process.exit(1);
741
+ }
742
+ const dir = join(homedir(), ".skalpel");
743
+ try {
744
+ mkdirSync(dir, { recursive: true });
745
+ accessSync(dir, constants.W_OK);
746
+ return; // writable — good to go
747
+ } catch {
748
+ /* not writable — try to self-heal, then re-check below */
749
+ }
750
+ // If we're root via `sudo` (SUDO_UID set), hand ownership back to the real user so the later
751
+ // non-root hook writes don't EACCES. On Windows there's no uid model — getuid is undefined, skip.
752
+ if (typeof process.getuid === "function" && process.getuid() === 0 && process.env.SUDO_UID) {
753
+ spawnSync(
754
+ "chown",
755
+ ["-R", `${process.env.SUDO_UID}:${process.env.SUDO_GID || process.env.SUDO_UID}`, dir],
756
+ { stdio: "ignore" },
757
+ );
758
+ }
759
+ try {
760
+ accessSync(dir, constants.W_OK);
761
+ } catch {
762
+ const who = process.env.USER || process.env.LOGNAME || "$(whoami)";
763
+ console.error(
764
+ ` ${R}✗${X} ~/.skalpel isn't writable — a past \`sudo\` install left it root-owned.\n` +
765
+ ` One line fixes it, then re-run:\n\n` +
766
+ ` ${B}sudo chown -R ${who} ~/.skalpel${X}\n`,
767
+ );
768
+ process.exit(1);
769
+ }
770
+ }
771
+
730
772
  async function main() {
731
773
  if (sub === "uninstall") {
732
774
  spawnSync("node", [join(__dir, "install.mjs"), "--uninstall"], { stdio: "inherit" });
@@ -766,6 +808,7 @@ async function main() {
766
808
  mkdirSync(cfgDir, { recursive: true });
767
809
  writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
768
810
  }
811
+ preflight(); // fail fast + fixable if the machine can't take the install (root-owned dir / old Node)
769
812
  banner();
770
813
 
771
814
  // 1) SIGN IN with Google (once). SKALPEL_USER (dev) or an existing ~/.skalpel/auth.json skips it.