skalpel 4.0.58 → 4.0.59
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 +21 -26
- package/package.json +1 -1
- package/skalpel-setup.mjs +19 -5
package/install.mjs
CHANGED
|
@@ -587,32 +587,27 @@ function cleanupLocalData() {
|
|
|
587
587
|
return cleaned;
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
-
// Plain uninstall: remove
|
|
591
|
-
// KEEP the saved auth (that's what `--purge` is for).
|
|
592
|
-
//
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
"
|
|
600
|
-
"
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
"
|
|
610
|
-
|
|
611
|
-
"verify-last.json",
|
|
612
|
-
"verify-shadow.lock",
|
|
613
|
-
"verify-reveal.json",
|
|
614
|
-
]) {
|
|
615
|
-
rm(join(SKALPEL_DIR, f), f);
|
|
590
|
+
// Plain uninstall: remove EVERYTHING under ~/.skalpel — staged hooks, transcript snapshots, all
|
|
591
|
+
// state/logs — but KEEP the saved auth (that's what `--purge` is for). This is a SWEEP, not a
|
|
592
|
+
// hand-maintained filename list: the old list silently drifted from what the runtime actually
|
|
593
|
+
// writes (steers.ndjson, verify-verdict.json, verify-ship-last.json, auth-notice.json,
|
|
594
|
+
// build-stalled.json, first-run.json, verify-reveal-notified.json all survived it), breaking the
|
|
595
|
+
// "nothing silently left behind" promise. Everything under ~/.skalpel is ours; only auth.json
|
|
596
|
+
// (legacy local session location) is deliberately preserved. rm()'s PKG_DIR guard still protects
|
|
597
|
+
// a self-hosted layout from deleting the running installer.
|
|
598
|
+
const LABELS = {
|
|
599
|
+
hooks: "staged hooks",
|
|
600
|
+
"ingest-outbox": "raw transcript snapshots (ingest-outbox)",
|
|
601
|
+
};
|
|
602
|
+
let entries = [];
|
|
603
|
+
try {
|
|
604
|
+
entries = existsSync(SKALPEL_DIR) ? readdirSync(SKALPEL_DIR).sort() : [];
|
|
605
|
+
} catch {
|
|
606
|
+
/* unreadable dir — best-effort; an uninstall never fails on it */
|
|
607
|
+
}
|
|
608
|
+
for (const name of entries) {
|
|
609
|
+
if (name === "auth.json") continue; // kept — `--purge` is the deliberate "forget me entirely"
|
|
610
|
+
rm(join(SKALPEL_DIR, name), LABELS[name] || name);
|
|
616
611
|
}
|
|
617
612
|
if (cleaned.length)
|
|
618
613
|
cleaned.push("(auth kept — run `skalpel uninstall --purge` to remove it too)");
|
package/package.json
CHANGED
package/skalpel-setup.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
constants,
|
|
17
17
|
} from "node:fs";
|
|
18
18
|
import { homedir } from "node:os";
|
|
19
|
-
import { join, dirname, delimiter, sep as pathSep } from "node:path";
|
|
19
|
+
import { basename, join, dirname, delimiter, sep as pathSep } from "node:path";
|
|
20
20
|
import { createInterface, emitKeypressEvents } from "node:readline";
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
22
22
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -591,7 +591,7 @@ export async function scanHistory(days = LOOKBACK_DAYS) {
|
|
|
591
591
|
const files = walk(join(homedir(), ".claude", "projects"))
|
|
592
592
|
.concat(walk(join(homedir(), ".codex", "sessions")))
|
|
593
593
|
.filter((f) => {
|
|
594
|
-
if (f
|
|
594
|
+
if (basename(f).startsWith("agent-")) return false; // sub-agent transcripts, not the user
|
|
595
595
|
try {
|
|
596
596
|
return statSync(f).mtimeMs >= cutoff;
|
|
597
597
|
} catch {
|
|
@@ -611,6 +611,19 @@ export async function scanHistory(days = LOOKBACK_DAYS) {
|
|
|
611
611
|
return out;
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
+
// Whether the 401 self-heal below may spawn the INTERACTIVE sign-in. login.mjs opens a Google
|
|
615
|
+
// OAuth browser tab and blocks (~180s) on the callback — acceptable only on a real terminal where
|
|
616
|
+
// the user can see why a tab just popped. The detached background build (`__build`, stdio:"ignore")
|
|
617
|
+
// has neither TTY: spawning login from there pops a zero-context browser tab and stalls the build,
|
|
618
|
+
// so it must instead let the 401 fall through to the ingest error, which backgroundBuild's
|
|
619
|
+
// build-stalled.json marker surfaces next session. Pure decision helper, exported for tests.
|
|
620
|
+
export function shouldSelfHealLogin(
|
|
621
|
+
stdoutIsTTY = process.stdout.isTTY,
|
|
622
|
+
stdinIsTTY = process.stdin.isTTY,
|
|
623
|
+
) {
|
|
624
|
+
return Boolean(stdoutIsTTY && stdinIsTTY);
|
|
625
|
+
}
|
|
626
|
+
|
|
614
627
|
// HOSTED model: the client is thin — it just UPLOADS the raw transcripts; the SERVER filters
|
|
615
628
|
// (real-session detection) + judges (Gemini) + builds the Amplitude dash + aggregate DAG into the
|
|
616
629
|
// hosted store under the user's uid. No Python, no engine, no graph-building on the user's machine.
|
|
@@ -669,7 +682,7 @@ async function buildGraph(sp, files, id) {
|
|
|
669
682
|
curBytes = 0;
|
|
670
683
|
}
|
|
671
684
|
try {
|
|
672
|
-
cur[f
|
|
685
|
+
cur[basename(f)] = readFileSync(f, "utf8");
|
|
673
686
|
curBytes += size;
|
|
674
687
|
} catch {
|
|
675
688
|
/* skip unreadable */
|
|
@@ -695,8 +708,9 @@ async function buildGraph(sp, files, id) {
|
|
|
695
708
|
const label = batches.length > 1 ? ` (batch ${bi + 1}/${batches.length})` : "";
|
|
696
709
|
sp.text(`Building your graph…${label}`);
|
|
697
710
|
let r = await postBatch(batches[bi], token);
|
|
698
|
-
// SELF-HEAL on 401: refresh the Google session once, then retry this batch.
|
|
699
|
-
|
|
711
|
+
// SELF-HEAL on 401: refresh the Google session once, then retry this batch. INTERACTIVE ONLY
|
|
712
|
+
// (shouldSelfHealLogin): in the detached/non-TTY case the 401 falls through to the throw below.
|
|
713
|
+
if (r.status === 401 && !process.env.SKALPEL_USER && shouldSelfHealLogin()) {
|
|
700
714
|
sp.text("Session expired — re-opening sign-in…");
|
|
701
715
|
spawnSync("node", [join(__dir, "login.mjs")], { stdio: "inherit" });
|
|
702
716
|
const fresh = await identity();
|