polymath-society 0.2.29 → 0.2.31

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/dist/cli.js CHANGED
@@ -55,13 +55,10 @@ function electronAppData() {
55
55
  function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData, home = os.homedir()) {
56
56
  const fromEnv = env.POLYMATH_DATA_DIR;
57
57
  if (fromEnv && fromEnv.trim()) return path.resolve(fromEnv);
58
- const local = path.join(cwd, ".data");
59
58
  const dflt = path.join(home, ".polymath-society", "data");
60
- if (existsSync(local) && hasCompletedAnalysis(local)) return local;
61
59
  const fromApp = appData();
62
60
  if (fromApp && hasCompletedAnalysis(fromApp)) return fromApp;
63
61
  if (hasCompletedAnalysis(dflt)) return dflt;
64
- if (existsSync(local)) return local;
65
62
  if (fromApp) return fromApp;
66
63
  return dflt;
67
64
  }
@@ -484,7 +481,8 @@ var init_cursor = __esm({
484
481
  });
485
482
 
486
483
  // ../coding-core/dist/raw.js
487
- import { promises as fs2 } from "fs";
484
+ import { promises as fs2, createReadStream } from "fs";
485
+ import readline from "readline";
488
486
  import path2 from "path";
489
487
  import os2 from "os";
490
488
  function resolveSourceRoot(envVar, ...defaultSegments) {
@@ -600,10 +598,18 @@ function blank(sessionId, source, file2) {
600
598
  klassReason: ""
601
599
  };
602
600
  }
603
- function parseClaudeCode(raw, sessionId, file2) {
601
+ function jsonlLines(file2) {
602
+ return readline.createInterface({ input: createReadStream(file2, { encoding: "utf-8" }), crlfDelay: Infinity });
603
+ }
604
+ function shedAgentBulk(s) {
605
+ if (s.klass === "agent" && s.events.length > 2)
606
+ s.events = [s.events[0], s.events[s.events.length - 1]];
607
+ return s;
608
+ }
609
+ async function parseClaudeCode(input, sessionId, file2) {
604
610
  const s = blank(sessionId, "claude-code", file2);
605
611
  const seen = /* @__PURE__ */ new Set();
606
- for (const lineRaw of raw.split("\n")) {
612
+ for await (const lineRaw of typeof input === "string" ? input.split("\n") : input) {
607
613
  const line = lineRaw.trim();
608
614
  if (!line)
609
615
  continue;
@@ -705,7 +711,7 @@ function parseClaudeCode(raw, sessionId, file2) {
705
711
  const c = classify(s);
706
712
  s.klass = c.klass;
707
713
  s.klassReason = c.reason;
708
- return s;
714
+ return shedAgentBulk(s);
709
715
  }
710
716
  function codexText(content) {
711
717
  if (typeof content === "string")
@@ -727,10 +733,10 @@ function codexText(content) {
727
733
  }
728
734
  return "";
729
735
  }
730
- function parseCodex(raw, file2) {
736
+ async function parseCodex(input, file2) {
731
737
  let sessionId = path2.basename(file2, ".jsonl");
732
738
  const s = blank(sessionId, "codex", file2);
733
- for (const lineRaw of raw.split("\n")) {
739
+ for await (const lineRaw of typeof input === "string" ? input.split("\n") : input) {
734
740
  const line = lineRaw.trim();
735
741
  if (!line)
736
742
  continue;
@@ -796,7 +802,7 @@ function parseCodex(raw, file2) {
796
802
  const c = classify(s);
797
803
  s.klass = c.klass;
798
804
  s.klassReason = c.reason;
799
- return s;
805
+ return shedAgentBulk(s);
800
806
  }
801
807
  async function readClaudeCodeAt(base2, machine) {
802
808
  const out = [];
@@ -820,13 +826,12 @@ async function readClaudeCodeAt(base2, machine) {
820
826
  }
821
827
  for (const f of files) {
822
828
  const file2 = path2.join(full, f);
823
- let raw;
829
+ let s = null;
824
830
  try {
825
- raw = await fs2.readFile(file2, "utf-8");
831
+ s = await parseClaudeCode(jsonlLines(file2), path2.basename(f, ".jsonl"), file2);
826
832
  } catch {
827
833
  continue;
828
834
  }
829
- const s = parseClaudeCode(raw, path2.basename(f, ".jsonl"), file2);
830
835
  if (s) {
831
836
  if (machine)
832
837
  s.machine = machine;
@@ -861,13 +866,12 @@ async function readCodexAt(base2, machine) {
861
866
  await walkJsonl(base2, files);
862
867
  const out = [];
863
868
  for (const file2 of files) {
864
- let raw;
869
+ let s = null;
865
870
  try {
866
- raw = await fs2.readFile(file2, "utf-8");
871
+ s = await parseCodex(jsonlLines(file2), file2);
867
872
  } catch {
868
873
  continue;
869
874
  }
870
- const s = parseCodex(raw, file2);
871
875
  if (s) {
872
876
  if (machine)
873
877
  s.machine = machine;
@@ -1073,7 +1077,7 @@ function rawSessionFromCursorComposer(c, file2, cwd) {
1073
1077
  const cl = classify(s);
1074
1078
  s.klass = cl.klass;
1075
1079
  s.klassReason = cl.reason;
1076
- return s;
1080
+ return shedAgentBulk(s);
1077
1081
  }
1078
1082
  function parseCursorTranscript(raw, composerId, file2, ws, fileMtime) {
1079
1083
  const s = blank(composerId, "cursor", file2);
@@ -1118,7 +1122,7 @@ function parseCursorTranscript(raw, composerId, file2, ws, fileMtime) {
1118
1122
  const c = classify(s);
1119
1123
  s.klass = c.klass;
1120
1124
  s.klassReason = c.reason;
1121
- return s;
1125
+ return shedAgentBulk(s);
1122
1126
  }
1123
1127
  function cursorWorkspaceSession(ws, idx) {
1124
1128
  if (ws.generations.length === 0)
@@ -1140,7 +1144,7 @@ function cursorWorkspaceSession(ws, idx) {
1140
1144
  const c = classify(s);
1141
1145
  s.klass = c.klass;
1142
1146
  s.klassReason = c.reason;
1143
- return s;
1147
+ return shedAgentBulk(s);
1144
1148
  }
1145
1149
  async function readCursorAt(roots, machine) {
1146
1150
  const workspaces = await readCursorWorkspaces(roots.workspace);
@@ -1254,6 +1258,8 @@ async function readAllSessions(opts2 = {}) {
1254
1258
  ]);
1255
1259
  const all = [...cc, ...cx, ...cr, ...mm];
1256
1260
  demoteTemplateFanouts(all);
1261
+ for (const s of all)
1262
+ shedAgentBulk(s);
1257
1263
  return all;
1258
1264
  }
1259
1265
  var SOURCE_ROOT_ENV_VARS, anyRootOverridden, claudeProjectsRoot, codexSessionsRoot, cursorWorkspaceRoot, cursorProjectsRoot, cursorGlobalRoot, MACHINE_CURSOR_FILE_RE, SKIP_DIR_RE, AGENT_CWD_RE, PERSONA_RE, OUTPUT_CONTRACT_RES, CAPS_HEADER_RE, PING_RE, ms, CURSOR_USER_QUERY_RE2, NODE_SQLITE2, FANOUT_PREFIX, FANOUT_MIN_SESSIONS;
@@ -18779,7 +18785,7 @@ var init_runStats = __esm({
18779
18785
  "../../lib/agents/shared/runStats.ts"() {
18780
18786
  "use strict";
18781
18787
  G = globalThis;
18782
- file = () => path11.join(process.cwd(), ".data", "run-stats.jsonl");
18788
+ file = () => path11.join(process.env.POLYMATH_DATA_DIR || path11.join(process.cwd(), ".data"), "run-stats.jsonl");
18783
18789
  }
18784
18790
  });
18785
18791
 
@@ -22822,15 +22828,167 @@ var init_publicCodingPageApi = __esm({
22822
22828
  }
22823
22829
  });
22824
22830
 
22831
+ // src/plan.ts
22832
+ var plan_exports = {};
22833
+ __export(plan_exports, {
22834
+ UNSUPPORTED_FULL_TIERS: () => UNSUPPORTED_FULL_TIERS,
22835
+ detectCodexPlan: () => detectCodexPlan,
22836
+ detectPlan: () => detectPlan,
22837
+ estimateLaneCosts: () => estimateLaneCosts,
22838
+ opusAvailable: () => opusAvailable,
22839
+ remainingFractions: () => remainingFractions,
22840
+ scheduleLanes: () => scheduleLanes,
22841
+ supportsFullAnalysis: () => supportsFullAnalysis,
22842
+ tierFromChatgptPlan: () => tierFromChatgptPlan,
22843
+ tierFromStrings: () => tierFromStrings
22844
+ });
22845
+ import { promises as fs32 } from "fs";
22846
+ import os8 from "os";
22847
+ import path38 from "path";
22848
+ function supportsFullAnalysis(tier) {
22849
+ if (process.env.POLYMATH_FORCE_FULL === "1") return true;
22850
+ return !UNSUPPORTED_FULL_TIERS.includes(tier);
22851
+ }
22852
+ function opusAvailable(tier) {
22853
+ return tier === "max_20x" || tier === "max_5x";
22854
+ }
22855
+ function tierFromStrings(...hints) {
22856
+ for (const h of hints) {
22857
+ if (!h) continue;
22858
+ const s = h.toLowerCase();
22859
+ if (s.includes("max_20x") || s.includes("max20x")) return "max_20x";
22860
+ if (s.includes("max_5x") || s.includes("max5x")) return "max_5x";
22861
+ if (s.includes("pro")) return "pro";
22862
+ if (s.includes("claude_max")) return "max_5x";
22863
+ }
22864
+ return "unknown";
22865
+ }
22866
+ function tierFromChatgptPlan(planType) {
22867
+ const s = (planType ?? "").toLowerCase();
22868
+ if (s === "pro") return "gpt_pro";
22869
+ if (s === "plus") return "gpt_plus";
22870
+ if (s === "team" || s === "enterprise" || s === "business") return "gpt_team";
22871
+ if (s === "free") return "gpt_free";
22872
+ return "unknown";
22873
+ }
22874
+ async function detectCodexPlan(home = os8.homedir()) {
22875
+ try {
22876
+ const raw = JSON.parse(await fs32.readFile(path38.join(home, ".codex", "auth.json"), "utf8"));
22877
+ for (const tok of [raw.tokens?.id_token, raw.tokens?.access_token]) {
22878
+ if (!tok) continue;
22879
+ try {
22880
+ const payload = JSON.parse(Buffer.from(tok.split(".")[1], "base64url").toString("utf8"));
22881
+ const t = tierFromChatgptPlan(payload["https://api.openai.com/auth"]?.chatgpt_plan_type);
22882
+ if (t !== "unknown") return t;
22883
+ } catch {
22884
+ }
22885
+ }
22886
+ } catch {
22887
+ }
22888
+ return "unknown";
22889
+ }
22890
+ async function detectPlan(home = os8.homedir()) {
22891
+ let tier = "unknown";
22892
+ try {
22893
+ const raw = JSON.parse(await fs32.readFile(path38.join(home, ".claude.json"), "utf8"));
22894
+ const oa = raw.oauthAccount ?? {};
22895
+ tier = tierFromStrings(oa.userRateLimitTier, oa.organizationRateLimitTier, oa.organizationType);
22896
+ } catch {
22897
+ }
22898
+ if (tier === "unknown") tier = await detectCodexPlan(home);
22899
+ return { tier, label: LABEL[tier], windowUsd: WINDOW_USD[tier] };
22900
+ }
22901
+ function estimateLaneCosts(input) {
22902
+ const codingUsd = Math.max(5, Math.min(32, Math.round(input.agentLogFiles * 9e-4)));
22903
+ const chatUsd = input.chatIncluded ? Math.round(40 + input.exportTotalMB * 0.8) : 0;
22904
+ return { codingUsd, chatUsd };
22905
+ }
22906
+ async function remainingFractions(dataRoot, agentLogFiles) {
22907
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
22908
+ let coding = 1;
22909
+ try {
22910
+ const graded = (await fs32.readdir(path38.join(dataRoot, "coding", "grades"))).filter((f) => f.endsWith(".json")).length;
22911
+ const expected = Math.max(1, agentLogFiles * 56e-4);
22912
+ coding = clamp(1 - graded / expected, 0.05, 1);
22913
+ } catch {
22914
+ }
22915
+ let chatDone = 0;
22916
+ try {
22917
+ const st = await fs32.stat(path38.join(dataRoot, "signals", "index.jsonl"));
22918
+ if (st.size > 0) chatDone += 0.3;
22919
+ } catch {
22920
+ }
22921
+ try {
22922
+ const lenses = await fs32.readdir(path38.join(dataRoot, "engine-pilot", "grades"));
22923
+ let withPerson = 0;
22924
+ for (const l of lenses) {
22925
+ try {
22926
+ await fs32.stat(path38.join(dataRoot, "engine-pilot", "grades", l, "_person.json"));
22927
+ withPerson++;
22928
+ } catch {
22929
+ }
22930
+ }
22931
+ chatDone += 0.6 * Math.min(1, withPerson / 5);
22932
+ } catch {
22933
+ }
22934
+ return { coding, chat: clamp(1 - chatDone, 0.1, 1) };
22935
+ }
22936
+ function scheduleLanes(plan, est) {
22937
+ const chat = est.chatUsd === 0 || est.chatUsd <= plan.windowUsd * 0.9 ? "now" : "overnight";
22938
+ return {
22939
+ coding: "now",
22940
+ chat,
22941
+ // No plan name here — the schedule header names the plan once, and
22942
+ // repeating it doubled the clumsy unknown-plan label (2026-07-22
22943
+ // walkthrough). Phrased for the two-column "Proposed schedule" rows:
22944
+ // "<lane> <verdict> — <reason>".
22945
+ codingReason: `runs now \u2014 the report link appears right away and fills in as it goes`,
22946
+ chatReason: est.chatUsd === 0 ? "not planned \u2014 no chat sources included" : chat === "overnight" ? `starts at 2:00 am tonight \u2014 a chat corpus this size is an overnight job; it runs while you sleep` : `runs now, alongside coding`
22947
+ };
22948
+ }
22949
+ var UNSUPPORTED_FULL_TIERS, WINDOW_USD, LABEL;
22950
+ var init_plan = __esm({
22951
+ "src/plan.ts"() {
22952
+ "use strict";
22953
+ UNSUPPORTED_FULL_TIERS = ["gpt_plus", "gpt_free"];
22954
+ WINDOW_USD = {
22955
+ max_20x: 300,
22956
+ max_5x: 75,
22957
+ pro: 15,
22958
+ // ChatGPT tiers, mapped to rough Claude-window parity by monthly price
22959
+ // ($200 Pro ≈ Max 20x; $20 Plus ≈ Claude Pro; Team between; free ≈ nothing).
22960
+ gpt_pro: 300,
22961
+ gpt_plus: 20,
22962
+ gpt_team: 50,
22963
+ gpt_free: 5,
22964
+ unknown: 15
22965
+ // conservative: schedule like Pro when we can't tell
22966
+ };
22967
+ LABEL = {
22968
+ max_20x: "Claude Max 20x",
22969
+ max_5x: "Claude Max 5x",
22970
+ pro: "Claude Pro",
22971
+ gpt_pro: "ChatGPT Pro (Codex)",
22972
+ gpt_plus: "ChatGPT Plus (Codex)",
22973
+ gpt_team: "ChatGPT Team (Codex)",
22974
+ gpt_free: "ChatGPT Free (Codex)",
22975
+ unknown: "an undetected plan (scheduling conservatively)"
22976
+ };
22977
+ }
22978
+ });
22979
+
22825
22980
  // src/pipeline.ts
22826
22981
  var pipeline_exports = {};
22827
22982
  __export(pipeline_exports, {
22983
+ TAIL_MODEL_ENV: () => TAIL_MODEL_ENV,
22984
+ applyTailModelEnv: () => applyTailModelEnv,
22828
22985
  apportionHeavy: () => apportionHeavy,
22829
22986
  codingDataRoot: () => codingDataRoot,
22830
22987
  evalUpToDate: () => evalUpToDate,
22831
22988
  expectedLlmArtifacts: () => expectedLlmArtifacts,
22832
22989
  hasPipeline: () => hasPipeline,
22833
22990
  machineBudget: () => machineBudget,
22991
+ resolveTailModel: () => resolveTailModel,
22834
22992
  runPipeline: () => runPipeline,
22835
22993
  stages: () => stages,
22836
22994
  startKeepAwake: () => startKeepAwake,
@@ -22839,11 +22997,11 @@ __export(pipeline_exports, {
22839
22997
  upToDateSkip: () => upToDateSkip
22840
22998
  });
22841
22999
  import { spawn as spawn7 } from "child_process";
22842
- import os8 from "os";
22843
- import path38 from "path";
22844
- import { existsSync as existsSync5, promises as fs32 } from "fs";
23000
+ import os9 from "os";
23001
+ import path39 from "path";
23002
+ import { existsSync as existsSync5, promises as fs33 } from "fs";
22845
23003
  import { fileURLToPath as fileURLToPath3 } from "url";
22846
- function machineBudget(totalMemBytes = os8.totalmem(), cpus = os8.availableParallelism?.() ?? os8.cpus().length, envOverride = process.env.POLYMATH_CONC) {
23004
+ function machineBudget(totalMemBytes = os9.totalmem(), cpus = os9.availableParallelism?.() ?? os9.cpus().length, envOverride = process.env.POLYMATH_CONC) {
22847
23005
  const envB = Number(envOverride);
22848
23006
  if (Number.isFinite(envB) && envB >= 1) return Math.min(32, Math.floor(envB));
22849
23007
  const byMem = Math.floor(totalMemBytes / 2 ** 30 * 0.35 / 0.45);
@@ -22870,6 +23028,8 @@ function stages(o) {
22870
23028
  if (o.idleGapMin != null) buildArgs.push("--idle", String(o.idleGapMin));
22871
23029
  const gradeArgs = ["--all", "--model", gradeModel, "--conc", conc];
22872
23030
  if (o.regrade) gradeArgs.push("--regrade");
23031
+ const tailModel = o.model ?? o.tailModel;
23032
+ const tailArgs = tailModel ? ["--model", tailModel] : [];
22873
23033
  return [
22874
23034
  // ---- deterministic foundation (fast, free) ----
22875
23035
  { script: "coding-build.mts", label: "Scrape + classify your sessions", llm: false, args: buildArgs, out: "coding/sessions.json" },
@@ -22901,10 +23061,10 @@ function stages(o) {
22901
23061
  // ---- corpus aggregate (reads grades; deterministic rollup) ----
22902
23062
  { script: "coding-aggregate.mts", label: "Corpus aggregate \u2014 ability ceiling + flow", llm: false, args: [], out: "coding/aggregate.json" },
22903
23063
  // ---- grade readers (grades/* is complete once aggregate ran) ----
22904
- // No default --model here: the agglomerate script defaults itself to Opus
22905
- // (one call per run; the axis verdicts are the most judgment-heavy output).
22906
- // An explicit user model choice still wins.
22907
- { script: "coding-agglomerate.mts", label: "Agglomerated criteria + verdicts + signature moves", llm: true, weight: 4, args: o.model ? ["--model", o.model] : [], out: "coding/agglomerate.json", batch: "grade-readers" },
23064
+ // The agglomerate script defaults itself to Opus (one call per run; the
23065
+ // axis verdicts are the most judgment-heavy output). tailArgs overrides
23066
+ // that on plans that can't run Opus; an explicit user model choice wins.
23067
+ { script: "coding-agglomerate.mts", label: "Agglomerated criteria + verdicts + signature moves", llm: true, weight: 4, args: [...tailArgs], out: "coding/agglomerate.json", batch: "grade-readers" },
22908
23068
  // Domain-expertise map DISABLED (owner call 2026-07-22): the single most
22909
23069
  // expensive stage of the whole run ($13.88 of $49.34 measured — 129 sonnet
22910
23070
  // calls) and for most users the hiring-bar filter correctly returns ZERO
@@ -22918,34 +23078,44 @@ function stages(o) {
22918
23078
  // Work brief — the hiring-manager narrative + continuous timeline. Consumes
22919
23079
  // day-digest.json (heavy batch, already complete) + concurrency/projects;
22920
23080
  // disjoint output, so it rides the synthesis batch. Defaults itself to Opus
22921
- // (the journey's significance calls are judgment-heavy); an explicit model
22922
- // choice wins. Prompt-hash gated + up-to-date-skip like its siblings.
22923
- { script: "coding-workbrief.mts", label: "Work brief \u2014 narrative timeline", llm: true, weight: 3, args: o.model ? ["--model", o.model] : [], out: "coding/workbrief.json", batch: "synthesis" },
23081
+ // (the journey's significance calls are judgment-heavy); tailArgs / an
23082
+ // explicit model choice win. Prompt-hash gated + up-to-date-skip like its siblings.
23083
+ { script: "coding-workbrief.mts", label: "Work brief \u2014 narrative timeline", llm: true, weight: 3, args: [...tailArgs], out: "coding/workbrief.json", batch: "synthesis" },
22924
23084
  // Growth reads grades/* + deterministic artifacts only (no per-session AI
22925
23085
  // calls — it deliberately ignores the AI recency window and covers the full
22926
23086
  // history); disjoint output, so it rides the synthesis batch. Like
22927
- // agglomerate it defaults itself to Opus; an explicit model choice wins.
22928
- { script: "coding-growth.mts", label: "Growth arcs + learning rate", llm: true, weight: 3, args: o.model ? ["--model", o.model] : [], out: "coding/growth.json", batch: "synthesis" },
23087
+ // agglomerate it defaults itself to Opus; tailArgs / an explicit model choice win.
23088
+ { script: "coding-growth.mts", label: "Growth arcs + learning rate", llm: true, weight: 3, args: [...tailArgs], out: "coding/growth.json", batch: "synthesis" },
22929
23089
  // nutshell stays LAST + alone: it merges INTO aggregate.json while the two
22930
23090
  // above read the compiled profile — racing them re-reads mid-write.
22931
23091
  { script: "coding-nutshell.mts", label: "Big-picture nutshell", llm: true, weight: 1, args: [], out: "coding/aggregate.json" }
22932
23092
  // merges bigPicture INTO aggregate.json
22933
23093
  ];
22934
23094
  }
23095
+ function applyTailModelEnv(env, tailModel) {
23096
+ if (!tailModel) return;
23097
+ for (const key2 of Object.values(TAIL_MODEL_ENV)) if (!env[key2]) env[key2] = tailModel;
23098
+ }
23099
+ async function resolveTailModel(o, home) {
23100
+ if (o.model || o.tailModel) return o.tailModel;
23101
+ const { detectPlan: detectPlan2, opusAvailable: opusAvailable2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
23102
+ const { tier } = await detectPlan2(home);
23103
+ return opusAvailable2(tier) ? void 0 : "sonnet";
23104
+ }
22935
23105
  function bundledPipelineDir() {
22936
- const d = path38.join(MODULE_DIR2, "pipeline");
22937
- return existsSync5(path38.join(d, "coding-build.js")) ? d : null;
23106
+ const d = path39.join(MODULE_DIR2, "pipeline");
23107
+ return existsSync5(path39.join(d, "coding-build.js")) ? d : null;
22938
23108
  }
22939
23109
  function stageInvocation(repoRoot, scriptRel, args) {
22940
- const scriptAbs = path38.join(repoRoot, "scripts", scriptRel);
23110
+ const scriptAbs = path39.join(repoRoot, "scripts", scriptRel);
22941
23111
  if (existsSync5(scriptAbs)) {
22942
- const local = path38.join(repoRoot, "node_modules", ".bin", "tsx");
23112
+ const local = path39.join(repoRoot, "node_modules", ".bin", "tsx");
22943
23113
  if (existsSync5(local)) return { cmd: local, argv: [scriptAbs, ...args], cwd: repoRoot };
22944
23114
  return { cmd: "npx", argv: ["tsx", scriptAbs, ...args], cwd: repoRoot };
22945
23115
  }
22946
23116
  const bundled = bundledPipelineDir();
22947
23117
  if (bundled) {
22948
- const js = path38.join(bundled, scriptRel.replace(/\.mts$/, ".js"));
23118
+ const js = path39.join(bundled, scriptRel.replace(/\.mts$/, ".js"));
22949
23119
  return { cmd: process.execPath, argv: [js, ...args], cwd: process.cwd() };
22950
23120
  }
22951
23121
  throw new Error(`pipeline stage ${scriptRel}: neither repo scripts/ nor bundled dist/pipeline/ found`);
@@ -23001,17 +23171,17 @@ function runStage(repoRoot, st, env, onLine) {
23001
23171
  });
23002
23172
  }
23003
23173
  function hasPipeline(repoRoot) {
23004
- return existsSync5(path38.join(repoRoot, "scripts", "coding-build.mts")) || bundledPipelineDir() != null;
23174
+ return existsSync5(path39.join(repoRoot, "scripts", "coding-build.mts")) || bundledPipelineDir() != null;
23005
23175
  }
23006
23176
  function codingDataRoot(repoRoot) {
23007
- if (!process.env.POLYMATH_DATA_DIR && existsSync5(path38.join(repoRoot, "scripts", "coding-build.mts"))) {
23008
- return path38.join(repoRoot, ".data");
23177
+ if (!process.env.POLYMATH_DATA_DIR && existsSync5(path39.join(repoRoot, "scripts", "coding-build.mts"))) {
23178
+ return path39.join(repoRoot, ".data");
23009
23179
  }
23010
23180
  return resolveDataRoot();
23011
23181
  }
23012
23182
  async function finalCodingArtifacts(codingDir) {
23013
- const files = await fs32.readdir(codingDir).catch(() => []);
23014
- return files.filter((f) => f.endsWith(".json") && f !== "report-run.json" && !f.startsWith(".")).sort().map((f) => path38.join(codingDir, f));
23183
+ const files = await fs33.readdir(codingDir).catch(() => []);
23184
+ return files.filter((f) => f.endsWith(".json") && f !== "report-run.json" && !f.startsWith(".")).sort().map((f) => path39.join(codingDir, f));
23015
23185
  }
23016
23186
  function upToDateSkip(i) {
23017
23187
  const pending2 = i.gradableIds.filter((id) => !i.gradedIds.has(id)).length;
@@ -23024,22 +23194,22 @@ function expectedLlmArtifacts(o) {
23024
23194
  }
23025
23195
  async function evalUpToDate(codingDir, threshold, regrade, opts2) {
23026
23196
  try {
23027
- const dataDir = path38.dirname(codingDir);
23197
+ const dataDir = path39.dirname(codingDir);
23028
23198
  const missing = [];
23029
23199
  for (const rel of expectedLlmArtifacts(opts2 ?? {})) {
23030
- const ok = await fs32.stat(path38.join(dataDir, rel)).then(() => true).catch(() => false);
23200
+ const ok = await fs33.stat(path39.join(dataDir, rel)).then(() => true).catch(() => false);
23031
23201
  if (!ok) missing.push(rel);
23032
23202
  }
23033
23203
  const { partition: partition2, aiWindowCutoff: aiWindowCutoff2, capGradable: capGradable2 } = await Promise.resolve().then(() => (init_gate2(), gate_exports));
23034
- const sessions = JSON.parse(await fs32.readFile(path38.join(codingDir, "sessions.json"), "utf8"));
23204
+ const sessions = JSON.parse(await fs33.readFile(path39.join(codingDir, "sessions.json"), "utf8"));
23035
23205
  const live = sessions.filter((s) => s.klass === "interactive" && !s.duplicateOf);
23036
23206
  const cutoff = aiWindowCutoff2(live);
23037
23207
  const { gradable: uncapped } = partition2(cutoff ? live.filter((s) => (s.start ?? "") >= cutoff) : live);
23038
23208
  const gradable = capGradable2(uncapped);
23039
23209
  const graded = new Set(
23040
- (await fs32.readdir(path38.join(codingDir, "grades")).catch(() => [])).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5))
23210
+ (await fs33.readdir(path39.join(codingDir, "grades")).catch(() => [])).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5))
23041
23211
  );
23042
- const agg = JSON.parse(await fs32.readFile(path38.join(codingDir, "aggregate.json"), "utf8").catch(() => "{}"));
23212
+ const agg = JSON.parse(await fs33.readFile(path39.join(codingDir, "aggregate.json"), "utf8").catch(() => "{}"));
23043
23213
  const hasPriorRun = typeof agg.bigPicture === "string" && agg.bigPicture.trim().length > 0;
23044
23214
  return {
23045
23215
  ...upToDateSkip({
@@ -23104,14 +23274,19 @@ async function runPipeline(o) {
23104
23274
  const dataRoot = codingDataRoot(o.repoRoot);
23105
23275
  const env = { ...process.env, POLYMATH_RESILIENT: "1", POLYMATH_DATA_DIR: dataRoot };
23106
23276
  if (!/max-old-space-size/.test(env.NODE_OPTIONS ?? "")) {
23107
- const headroomMb = Math.min(12288, Math.max(4096, Math.round(os8.totalmem() / 2 ** 20 * 0.6)));
23277
+ const headroomMb = Math.min(12288, Math.max(4096, Math.round(os9.totalmem() / 2 ** 20 * 0.6)));
23108
23278
  env.NODE_OPTIONS = `${env.NODE_OPTIONS ?? ""} --max-old-space-size=${headroomMb}`.trim();
23109
23279
  }
23110
23280
  if (!o.overnight) env.POLYMATH_RESILIENT_NO_WINDOW = "1";
23111
23281
  const stopKeepAwake = o.overnight ? startKeepAwake() : () => {
23112
23282
  };
23113
- const list = stages(o).filter((st) => !o.deterministicOnly || !st.llm);
23114
- const codingDir = path38.join(dataRoot, "coding");
23283
+ const tailModel = await resolveTailModel(o);
23284
+ applyTailModelEnv(env, tailModel);
23285
+ if (tailModel && !o.deterministicOnly) {
23286
+ o.onLine?.(`\u24D8 synthesis stages run on ${tailModel} \u2014 this plan doesn't include Opus (Max plans keep the Opus default)`);
23287
+ }
23288
+ const list = stages({ ...o, tailModel }).filter((st) => !o.deterministicOnly || !st.llm);
23289
+ const codingDir = path39.join(dataRoot, "coding");
23115
23290
  const central = centralConfig();
23116
23291
  const t0 = Date.now();
23117
23292
  const log = await startRunLog(
@@ -23120,7 +23295,7 @@ async function runPipeline(o) {
23120
23295
  supabaseUrl: central.supabaseUrl,
23121
23296
  supabaseAnonKey: central.supabaseAnonKey,
23122
23297
  artifacts: () => finalCodingArtifacts(codingDir),
23123
- refPath: path38.join(codingDir, "report-run.json")
23298
+ refPath: path39.join(codingDir, "report-run.json")
23124
23299
  // next to the served artifacts
23125
23300
  }
23126
23301
  );
@@ -23157,7 +23332,7 @@ async function runPipeline(o) {
23157
23332
  failed.push({ label: st.label, script: st.script, error });
23158
23333
  o.onLine?.(`\u26A0\uFE0F stage FAILED (continuing): ${st.label} \u2014 ${error}`);
23159
23334
  }
23160
- const sha = st.out ? await fileSha(path38.join(dataRoot, st.out)) : null;
23335
+ const sha = st.out ? await fileSha(path39.join(dataRoot, st.out)) : null;
23161
23336
  log.event(st.script.replace(/\.mts$/, ""), { ok, ms: Date.now() - stStart, ...sha ? { sha } : {} });
23162
23337
  stageTimes.push({ script: st.script, label: st.label, ms: Date.now() - stStart, ok });
23163
23338
  o.onStageDone?.(st.label, st.llm, ok, st.script);
@@ -23193,7 +23368,7 @@ async function runPipeline(o) {
23193
23368
  model: o.model ?? "sonnet(+haiku grade)",
23194
23369
  budget: machineBudget()
23195
23370
  };
23196
- await fs32.appendFile(path38.join(codingDir, "run-times.jsonl"), JSON.stringify(record) + "\n");
23371
+ await fs33.appendFile(path39.join(codingDir, "run-times.jsonl"), JSON.stringify(record) + "\n");
23197
23372
  const slowest = stageTimes.filter((s) => !s.skipped).sort((a, b) => b.ms - a.ms).slice(0, 3).map((s) => `${s.script.replace(/^coding-|\.mts$/g, "")} ${fmt(s.ms)}`).join(" \xB7 ");
23198
23373
  o.onLine?.(`\u23F1 run took ${fmt(totalMs)} (${record.kind}) \u2014 slowest: ${slowest} \xB7 ledger: .data/coding/run-times.jsonl`);
23199
23374
  } catch {
@@ -23201,14 +23376,18 @@ async function runPipeline(o) {
23201
23376
  stopKeepAwake();
23202
23377
  return { failed, ...skippedLlm ? { skippedLlm } : {} };
23203
23378
  }
23204
- var MODULE_DIR2, liveStages, killHooked;
23379
+ var MODULE_DIR2, TAIL_MODEL_ENV, liveStages, killHooked;
23205
23380
  var init_pipeline2 = __esm({
23206
23381
  "src/pipeline.ts"() {
23207
23382
  "use strict";
23208
23383
  init_central();
23209
23384
  init_dataHome();
23210
23385
  init_runLog();
23211
- MODULE_DIR2 = path38.dirname(fileURLToPath3(import.meta.url));
23386
+ MODULE_DIR2 = path39.dirname(fileURLToPath3(import.meta.url));
23387
+ TAIL_MODEL_ENV = {
23388
+ "coding-coaching.mts": "AGENT_MODEL",
23389
+ "coding-focus.mts": "FOCUS_JUDGE_MODEL"
23390
+ };
23212
23391
  liveStages = /* @__PURE__ */ new Set();
23213
23392
  killHooked = false;
23214
23393
  }
@@ -23224,9 +23403,9 @@ __export(chatPipeline_exports, {
23224
23403
  runChatPipeline: () => runChatPipeline
23225
23404
  });
23226
23405
  import { spawn as spawn8 } from "child_process";
23227
- import os9 from "os";
23228
- import path39 from "path";
23229
- import { existsSync as existsSync6, promises as fs33 } from "fs";
23406
+ import os10 from "os";
23407
+ import path40 from "path";
23408
+ import { existsSync as existsSync6, promises as fs34 } from "fs";
23230
23409
  import { fileURLToPath as fileURLToPath4 } from "url";
23231
23410
  function chatEngineStages() {
23232
23411
  return [
@@ -23246,40 +23425,40 @@ function chatEngineStages() {
23246
23425
  ];
23247
23426
  }
23248
23427
  function bundledEngineDir() {
23249
- const d = path39.join(MODULE_DIR3, "engine");
23250
- return existsSync6(path39.join(d, "run-tagger.js")) ? d : null;
23428
+ const d = path40.join(MODULE_DIR3, "engine");
23429
+ return existsSync6(path40.join(d, "run-tagger.js")) ? d : null;
23251
23430
  }
23252
23431
  function stageInvocation2(repoRoot, scriptRel, args) {
23253
- const scriptAbs = path39.join(repoRoot, "scripts", scriptRel);
23432
+ const scriptAbs = path40.join(repoRoot, "scripts", scriptRel);
23254
23433
  if (existsSync6(scriptAbs)) {
23255
- const local = path39.join(repoRoot, "node_modules", ".bin", "tsx");
23434
+ const local = path40.join(repoRoot, "node_modules", ".bin", "tsx");
23256
23435
  if (existsSync6(local)) return { cmd: local, argv: [scriptAbs, ...args], cwd: repoRoot };
23257
23436
  return { cmd: "npx", argv: ["tsx", scriptAbs, ...args], cwd: repoRoot };
23258
23437
  }
23259
23438
  const bundled = bundledEngineDir();
23260
23439
  if (bundled) {
23261
- const js = path39.join(bundled, scriptRel.replace(/\.mts$/, ".js"));
23440
+ const js = path40.join(bundled, scriptRel.replace(/\.mts$/, ".js"));
23262
23441
  return { cmd: process.execPath, argv: [js, ...args], cwd: process.cwd() };
23263
23442
  }
23264
23443
  throw new Error(`chat-engine stage ${scriptRel}: neither repo scripts/ nor bundled dist/engine/ found`);
23265
23444
  }
23266
23445
  function hasChatPipeline(repoRoot) {
23267
- return existsSync6(path39.join(repoRoot, "scripts", "run-tagger.mts")) || bundledEngineDir() != null;
23446
+ return existsSync6(path40.join(repoRoot, "scripts", "run-tagger.mts")) || bundledEngineDir() != null;
23268
23447
  }
23269
23448
  function chatDataRoot(repoRoot) {
23270
- if (!process.env.POLYMATH_DATA_DIR && existsSync6(path39.join(repoRoot, "scripts", "run-tagger.mts"))) {
23271
- return path39.join(repoRoot, ".data");
23449
+ if (!process.env.POLYMATH_DATA_DIR && existsSync6(path40.join(repoRoot, "scripts", "run-tagger.mts"))) {
23450
+ return path40.join(repoRoot, ".data");
23272
23451
  }
23273
23452
  return resolveDataRoot();
23274
23453
  }
23275
23454
  async function finalChatArtifacts(dataRoot) {
23276
- const gradesDir = path39.join(dataRoot, "engine-pilot", "grades");
23455
+ const gradesDir = path40.join(dataRoot, "engine-pilot", "grades");
23277
23456
  const out = [];
23278
- for (const lens of (await fs33.readdir(gradesDir).catch(() => [])).sort()) {
23279
- const p = path39.join(gradesDir, lens, "_person.json");
23457
+ for (const lens of (await fs34.readdir(gradesDir).catch(() => [])).sort()) {
23458
+ const p = path40.join(gradesDir, lens, "_person.json");
23280
23459
  if (existsSync6(p)) out.push(p);
23281
23460
  }
23282
- out.push(path39.join(dataRoot, "engine-pilot", "public-report.json"));
23461
+ out.push(path40.join(dataRoot, "engine-pilot", "public-report.json"));
23283
23462
  return out;
23284
23463
  }
23285
23464
  function runStage2(repoRoot, st, env, onLine) {
@@ -23329,7 +23508,7 @@ async function runChatPipeline(o) {
23329
23508
  };
23330
23509
  if (!o.overnight) env.POLYMATH_RESILIENT_NO_WINDOW = "1";
23331
23510
  if (!/max-old-space-size/.test(env.NODE_OPTIONS ?? "")) {
23332
- const headroomMb = Math.min(12288, Math.max(4096, Math.round(os9.totalmem() / 2 ** 20 * 0.6)));
23511
+ const headroomMb = Math.min(12288, Math.max(4096, Math.round(os10.totalmem() / 2 ** 20 * 0.6)));
23333
23512
  env.NODE_OPTIONS = `${env.NODE_OPTIONS ?? ""} --max-old-space-size=${headroomMb}`.trim();
23334
23513
  }
23335
23514
  const failed = [];
@@ -23348,12 +23527,12 @@ async function runChatPipeline(o) {
23348
23527
  supabaseUrl: central.supabaseUrl,
23349
23528
  supabaseAnonKey: central.supabaseAnonKey,
23350
23529
  artifacts: () => finalChatArtifacts(dataRoot),
23351
- refPath: path39.join(dataRoot, "report-run.json")
23530
+ refPath: path40.join(dataRoot, "report-run.json")
23352
23531
  }
23353
23532
  );
23354
23533
  let idx = 0;
23355
23534
  for (const f of ingestable) {
23356
- const st = { script: "ingest-export.mts", label: `Ingest ${f.kind} \u2014 ${path39.basename(f.path)}`, llm: false, args: [f.kind, f.path] };
23535
+ const st = { script: "ingest-export.mts", label: `Ingest ${f.kind} \u2014 ${path40.basename(f.path)}`, llm: false, args: [f.kind, f.path] };
23357
23536
  o.onStage?.(++idx, total, st.label, false);
23358
23537
  const stStart = Date.now();
23359
23538
  let ok = true;
@@ -23370,7 +23549,7 @@ async function runChatPipeline(o) {
23370
23549
  }
23371
23550
  const minNewDocs = Number(process.env.POLYMATH_MIN_NEW_DOCS ?? 10);
23372
23551
  const signalDocs = async () => {
23373
- const t = await fs33.readFile(path39.join(dataRoot, "signals", "index.jsonl"), "utf8").catch(() => "");
23552
+ const t = await fs34.readFile(path40.join(dataRoot, "signals", "index.jsonl"), "utf8").catch(() => "");
23374
23553
  return t ? t.split("\n").filter(Boolean).length : 0;
23375
23554
  };
23376
23555
  const docsBeforeTagger = await signalDocs();
@@ -23392,11 +23571,11 @@ async function runChatPipeline(o) {
23392
23571
  failed.push({ label: st.label, script: st.script, error });
23393
23572
  o.onLine?.(`\u26A0\uFE0F stage FAILED (continuing): ${st.label} \u2014 ${error}`);
23394
23573
  }
23395
- const sha = st.out ? await fileSha(path39.join(dataRoot, st.out)) : null;
23574
+ const sha = st.out ? await fileSha(path40.join(dataRoot, st.out)) : null;
23396
23575
  log.event(st.script.replace(/\.mts$/, ""), { ok, ms: Date.now() - stStart, ...sha ? { sha } : {} });
23397
23576
  if (st.script === "run-tagger.mts" && ok && minNewDocs > 0) {
23398
23577
  const newDocs = await signalDocs() - docsBeforeTagger;
23399
- const report = await fs33.readFile(path39.join(dataRoot, "engine-pilot", "public-report.json"), "utf8").then((s) => JSON.parse(s)).catch(() => null);
23578
+ const report = await fs34.readFile(path40.join(dataRoot, "engine-pilot", "public-report.json"), "utf8").then((s) => JSON.parse(s)).catch(() => null);
23400
23579
  if (report?.headline && (report.spikes?.length ?? 0) > 0 && newDocs < minNewDocs) {
23401
23580
  upToDate = true;
23402
23581
  o.onUpToDate?.();
@@ -23419,11 +23598,79 @@ var init_chatPipeline = __esm({
23419
23598
  init_central();
23420
23599
  init_dataHome();
23421
23600
  init_runLog();
23422
- MODULE_DIR3 = path39.dirname(fileURLToPath4(import.meta.url));
23601
+ MODULE_DIR3 = path40.dirname(fileURLToPath4(import.meta.url));
23423
23602
  INGESTABLE_KINDS = ["chatgpt", "claude", "notion", "obsidian"];
23424
23603
  }
23425
23604
  });
23426
23605
 
23606
+ // src/ttyGuard.ts
23607
+ import { spawnSync as spawnSync3 } from "node:child_process";
23608
+ function stty(...args) {
23609
+ try {
23610
+ const r = spawnSync3("stty", args, { stdio: ["inherit", "pipe", "ignore"] });
23611
+ return r.status === 0 ? String(r.stdout ?? "").trim() : null;
23612
+ } catch {
23613
+ return null;
23614
+ }
23615
+ }
23616
+ function guardTty() {
23617
+ if (guarded || process.platform === "win32" || !process.stdin.isTTY) return;
23618
+ guarded = true;
23619
+ stty("isig", "icrnl");
23620
+ const saved = stty("-g");
23621
+ if (saved) process.on("exit", () => void stty(saved));
23622
+ }
23623
+ function attachPromptAbort(rl) {
23624
+ rl.on("SIGINT", () => {
23625
+ process.stderr.write("^C\n");
23626
+ rl.close();
23627
+ process.exit(130);
23628
+ });
23629
+ }
23630
+ function watchCtrlC(onCtrlC) {
23631
+ const stdin = process.stdin;
23632
+ if (process.platform === "win32" || !stdin.isTTY || typeof stdin.setRawMode !== "function") {
23633
+ return () => {
23634
+ };
23635
+ }
23636
+ const onData = (b) => {
23637
+ for (const byte of b) {
23638
+ if (byte === 3 || byte === 28) {
23639
+ onCtrlC();
23640
+ return;
23641
+ }
23642
+ if (byte === 26) {
23643
+ stdin.setRawMode(false);
23644
+ process.once("SIGCONT", () => {
23645
+ try {
23646
+ stdin.setRawMode(true);
23647
+ } catch {
23648
+ }
23649
+ });
23650
+ process.kill(process.pid, "SIGTSTP");
23651
+ }
23652
+ }
23653
+ };
23654
+ stdin.setRawMode(true);
23655
+ stdin.resume();
23656
+ stdin.on("data", onData);
23657
+ return () => {
23658
+ stdin.off("data", onData);
23659
+ try {
23660
+ stdin.setRawMode(false);
23661
+ } catch {
23662
+ }
23663
+ stdin.pause();
23664
+ };
23665
+ }
23666
+ var guarded;
23667
+ var init_ttyGuard = __esm({
23668
+ "src/ttyGuard.ts"() {
23669
+ "use strict";
23670
+ guarded = false;
23671
+ }
23672
+ });
23673
+
23427
23674
  // src/notify.ts
23428
23675
  import { spawn as spawn9 } from "node:child_process";
23429
23676
  function systemNotify(title, body) {
@@ -23561,10 +23808,10 @@ __export(exportScan_exports, {
23561
23808
  scanForExports: () => scanForExports,
23562
23809
  scanForObsidianVaults: () => scanForObsidianVaults
23563
23810
  });
23564
- import { promises as fs34 } from "fs";
23811
+ import { promises as fs35 } from "fs";
23565
23812
  import { execFile as execFile2 } from "child_process";
23566
23813
  import { promisify as promisify2 } from "util";
23567
- import path40 from "path";
23814
+ import path41 from "path";
23568
23815
  function latestPerSource(found) {
23569
23816
  const newest = /* @__PURE__ */ new Map();
23570
23817
  const keyOf = (f) => f.kind === "obsidian" || f.kind === "unknown" ? null : `${f.kind}:${f.account ?? "(unknown-account)"}`;
@@ -23634,7 +23881,7 @@ async function identifyZip(zip, stat) {
23634
23881
  note: ""
23635
23882
  };
23636
23883
  if (!entries) {
23637
- const n = path40.basename(zip).toLowerCase();
23884
+ const n = path41.basename(zip).toLowerCase();
23638
23885
  if (n.includes("chatgpt") || n.includes("openai")) {
23639
23886
  found.kind = "chatgpt";
23640
23887
  found.note = "matched by filename only (no unzip binary to verify)";
@@ -23654,7 +23901,7 @@ async function identifyZip(zip, stat) {
23654
23901
  }
23655
23902
  let { kind, note } = classify2(entries);
23656
23903
  if (kind === "unknown") {
23657
- const nb = path40.basename(zip).toLowerCase();
23904
+ const nb = path41.basename(zip).toLowerCase();
23658
23905
  const nameHint = nb.includes("notion") || /(^|[-_ ])export-/.test(nb);
23659
23906
  const inner = entries.filter((e) => e.toLowerCase().endsWith(".zip"));
23660
23907
  const partZips = inner.some((e) => /export[^/]*part[-_ ]?\d+\.zip$/i.test(e.toLowerCase()));
@@ -23683,17 +23930,17 @@ async function identifyZip(zip, stat) {
23683
23930
  async function identifyDir(dir) {
23684
23931
  let names;
23685
23932
  try {
23686
- names = await fs34.readdir(dir);
23933
+ names = await fs35.readdir(dir);
23687
23934
  } catch {
23688
23935
  return null;
23689
23936
  }
23690
23937
  const { kind, note } = classify2(names);
23691
23938
  if (kind === "unknown") return null;
23692
- const st = await fs34.stat(dir);
23939
+ const st = await fs35.stat(dir);
23693
23940
  const found = { kind, path: dir, account: null, sizeMB: 0, modified: st.mtime.toISOString().slice(0, 10), note };
23694
23941
  const read = async (n) => {
23695
23942
  try {
23696
- return await fs34.readFile(path40.join(dir, n), "utf8");
23943
+ return await fs35.readFile(path41.join(dir, n), "utf8");
23697
23944
  } catch {
23698
23945
  return null;
23699
23946
  }
@@ -23706,7 +23953,7 @@ async function identifyPath(p) {
23706
23953
  if (/\/\.data\//.test(p)) return null;
23707
23954
  let st;
23708
23955
  try {
23709
- st = await fs34.stat(p);
23956
+ st = await fs35.stat(p);
23710
23957
  } catch {
23711
23958
  return null;
23712
23959
  }
@@ -23768,14 +24015,14 @@ async function countMdNotes(root, cap = 2e3) {
23768
24015
  if (n >= cap || depth > 6) return;
23769
24016
  let entries;
23770
24017
  try {
23771
- entries = await fs34.readdir(d, { withFileTypes: true });
24018
+ entries = await fs35.readdir(d, { withFileTypes: true });
23772
24019
  } catch {
23773
24020
  return;
23774
24021
  }
23775
24022
  for (const e of entries) {
23776
24023
  if (n >= cap) return;
23777
24024
  if (e.name.startsWith(".")) continue;
23778
- if (e.isDirectory()) await walk(path40.join(d, e.name), depth + 1);
24025
+ if (e.isDirectory()) await walk(path41.join(d, e.name), depth + 1);
23779
24026
  else if (e.name.endsWith(".md")) n++;
23780
24027
  }
23781
24028
  }
@@ -23784,12 +24031,12 @@ async function countMdNotes(root, cap = 2e3) {
23784
24031
  }
23785
24032
  async function identifyObsidianVault(dir) {
23786
24033
  try {
23787
- const st = await fs34.stat(path40.join(dir, ".obsidian"));
24034
+ const st = await fs35.stat(path41.join(dir, ".obsidian"));
23788
24035
  if (!st.isDirectory()) return null;
23789
24036
  } catch {
23790
24037
  return null;
23791
24038
  }
23792
- const rootSt = await fs34.stat(dir).catch(() => null);
24039
+ const rootSt = await fs35.stat(dir).catch(() => null);
23793
24040
  if (!rootSt) return null;
23794
24041
  const notes = await countMdNotes(dir);
23795
24042
  return {
@@ -23811,21 +24058,21 @@ async function scanForObsidianVaults(home) {
23811
24058
  }
23812
24059
  };
23813
24060
  const roots = [
23814
- path40.join(home, "Library", "Mobile Documents", "iCloud~md~obsidian", "Documents"),
23815
- path40.join(home, "Documents"),
23816
- path40.join(home, "Desktop"),
24061
+ path41.join(home, "Library", "Mobile Documents", "iCloud~md~obsidian", "Documents"),
24062
+ path41.join(home, "Documents"),
24063
+ path41.join(home, "Desktop"),
23817
24064
  home
23818
24065
  ];
23819
24066
  for (const root of roots) {
23820
24067
  let names = [];
23821
24068
  try {
23822
- names = await fs34.readdir(root);
24069
+ names = await fs35.readdir(root);
23823
24070
  } catch {
23824
24071
  continue;
23825
24072
  }
23826
24073
  for (const name17 of names) {
23827
24074
  if (name17.startsWith(".")) continue;
23828
- add(await identifyObsidianVault(path40.join(root, name17)));
24075
+ add(await identifyObsidianVault(path41.join(root, name17)));
23829
24076
  }
23830
24077
  }
23831
24078
  return out;
@@ -23834,16 +24081,16 @@ async function scanForExports(root) {
23834
24081
  const out = [];
23835
24082
  let names;
23836
24083
  try {
23837
- names = await fs34.readdir(root);
24084
+ names = await fs35.readdir(root);
23838
24085
  } catch {
23839
24086
  return out;
23840
24087
  }
23841
24088
  for (const name17 of names) {
23842
24089
  if (name17.startsWith(".")) continue;
23843
- const p = path40.join(root, name17);
24090
+ const p = path41.join(root, name17);
23844
24091
  let st;
23845
24092
  try {
23846
- st = await fs34.stat(p);
24093
+ st = await fs35.stat(p);
23847
24094
  } catch {
23848
24095
  continue;
23849
24096
  }
@@ -23971,16 +24218,16 @@ __export(reminderState_exports, {
23971
24218
  recordQueued: () => recordQueued,
23972
24219
  trackedLinkUrl: () => trackedLinkUrl
23973
24220
  });
23974
- import { promises as fs35 } from "node:fs";
23975
- import path41 from "node:path";
24221
+ import { promises as fs36 } from "node:fs";
24222
+ import path42 from "node:path";
23976
24223
  import crypto4 from "node:crypto";
23977
24224
  function newToken() {
23978
24225
  return crypto4.randomBytes(12).toString("base64url");
23979
24226
  }
23980
24227
  async function ensureReminderState(dataDir) {
23981
- const file2 = path41.join(dataDir, FILE);
24228
+ const file2 = path42.join(dataDir, FILE);
23982
24229
  try {
23983
- const s = JSON.parse(await fs35.readFile(file2, "utf8"));
24230
+ const s = JSON.parse(await fs36.readFile(file2, "utf8"));
23984
24231
  if (typeof s.token === "string" && s.token.length >= 8) {
23985
24232
  return {
23986
24233
  token: s.token,
@@ -23994,8 +24241,8 @@ async function ensureReminderState(dataDir) {
23994
24241
  }
23995
24242
  const state = { token: newToken(), queuedKinds: [] };
23996
24243
  try {
23997
- await fs35.mkdir(dataDir, { recursive: true });
23998
- await fs35.writeFile(file2, JSON.stringify(state));
24244
+ await fs36.mkdir(dataDir, { recursive: true });
24245
+ await fs36.writeFile(file2, JSON.stringify(state));
23999
24246
  } catch {
24000
24247
  }
24001
24248
  return state;
@@ -24010,8 +24257,8 @@ async function recordQueued(dataDir, state, kinds) {
24010
24257
  queuedAt: (/* @__PURE__ */ new Date()).toISOString()
24011
24258
  };
24012
24259
  try {
24013
- await fs35.mkdir(dataDir, { recursive: true });
24014
- await fs35.writeFile(path41.join(dataDir, FILE), JSON.stringify(next));
24260
+ await fs36.mkdir(dataDir, { recursive: true });
24261
+ await fs36.writeFile(path42.join(dataDir, FILE), JSON.stringify(next));
24015
24262
  } catch {
24016
24263
  }
24017
24264
  return next;
@@ -24024,8 +24271,8 @@ async function recordOffered(dataDir, state, kinds) {
24024
24271
  offeredAt: (/* @__PURE__ */ new Date()).toISOString()
24025
24272
  };
24026
24273
  try {
24027
- await fs35.mkdir(dataDir, { recursive: true });
24028
- await fs35.writeFile(path41.join(dataDir, FILE), JSON.stringify(next));
24274
+ await fs36.mkdir(dataDir, { recursive: true });
24275
+ await fs36.writeFile(path42.join(dataDir, FILE), JSON.stringify(next));
24029
24276
  } catch {
24030
24277
  }
24031
24278
  return next;
@@ -24042,151 +24289,6 @@ var init_reminderState = __esm({
24042
24289
  }
24043
24290
  });
24044
24291
 
24045
- // src/plan.ts
24046
- var plan_exports = {};
24047
- __export(plan_exports, {
24048
- UNSUPPORTED_FULL_TIERS: () => UNSUPPORTED_FULL_TIERS,
24049
- detectCodexPlan: () => detectCodexPlan,
24050
- detectPlan: () => detectPlan,
24051
- estimateLaneCosts: () => estimateLaneCosts,
24052
- remainingFractions: () => remainingFractions,
24053
- scheduleLanes: () => scheduleLanes,
24054
- supportsFullAnalysis: () => supportsFullAnalysis,
24055
- tierFromChatgptPlan: () => tierFromChatgptPlan,
24056
- tierFromStrings: () => tierFromStrings
24057
- });
24058
- import { promises as fs36 } from "fs";
24059
- import os10 from "os";
24060
- import path42 from "path";
24061
- function supportsFullAnalysis(tier) {
24062
- if (process.env.POLYMATH_FORCE_FULL === "1") return true;
24063
- return !UNSUPPORTED_FULL_TIERS.includes(tier);
24064
- }
24065
- function tierFromStrings(...hints) {
24066
- for (const h of hints) {
24067
- if (!h) continue;
24068
- const s = h.toLowerCase();
24069
- if (s.includes("max_20x") || s.includes("max20x")) return "max_20x";
24070
- if (s.includes("max_5x") || s.includes("max5x")) return "max_5x";
24071
- if (s.includes("pro")) return "pro";
24072
- if (s.includes("claude_max")) return "max_5x";
24073
- }
24074
- return "unknown";
24075
- }
24076
- function tierFromChatgptPlan(planType) {
24077
- const s = (planType ?? "").toLowerCase();
24078
- if (s === "pro") return "gpt_pro";
24079
- if (s === "plus") return "gpt_plus";
24080
- if (s === "team" || s === "enterprise" || s === "business") return "gpt_team";
24081
- if (s === "free") return "gpt_free";
24082
- return "unknown";
24083
- }
24084
- async function detectCodexPlan(home = os10.homedir()) {
24085
- try {
24086
- const raw = JSON.parse(await fs36.readFile(path42.join(home, ".codex", "auth.json"), "utf8"));
24087
- for (const tok of [raw.tokens?.id_token, raw.tokens?.access_token]) {
24088
- if (!tok) continue;
24089
- try {
24090
- const payload = JSON.parse(Buffer.from(tok.split(".")[1], "base64url").toString("utf8"));
24091
- const t = tierFromChatgptPlan(payload["https://api.openai.com/auth"]?.chatgpt_plan_type);
24092
- if (t !== "unknown") return t;
24093
- } catch {
24094
- }
24095
- }
24096
- } catch {
24097
- }
24098
- return "unknown";
24099
- }
24100
- async function detectPlan(home = os10.homedir()) {
24101
- let tier = "unknown";
24102
- try {
24103
- const raw = JSON.parse(await fs36.readFile(path42.join(home, ".claude.json"), "utf8"));
24104
- const oa = raw.oauthAccount ?? {};
24105
- tier = tierFromStrings(oa.userRateLimitTier, oa.organizationRateLimitTier, oa.organizationType);
24106
- } catch {
24107
- }
24108
- if (tier === "unknown") tier = await detectCodexPlan(home);
24109
- return { tier, label: LABEL[tier], windowUsd: WINDOW_USD[tier] };
24110
- }
24111
- function estimateLaneCosts(input) {
24112
- const codingUsd = Math.max(5, Math.min(32, Math.round(input.agentLogFiles * 9e-4)));
24113
- const chatUsd = input.chatIncluded ? Math.round(40 + input.exportTotalMB * 0.8) : 0;
24114
- return { codingUsd, chatUsd };
24115
- }
24116
- async function remainingFractions(dataRoot, agentLogFiles) {
24117
- const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
24118
- let coding = 1;
24119
- try {
24120
- const graded = (await fs36.readdir(path42.join(dataRoot, "coding", "grades"))).filter((f) => f.endsWith(".json")).length;
24121
- const expected = Math.max(1, agentLogFiles * 56e-4);
24122
- coding = clamp(1 - graded / expected, 0.05, 1);
24123
- } catch {
24124
- }
24125
- let chatDone = 0;
24126
- try {
24127
- const st = await fs36.stat(path42.join(dataRoot, "signals", "index.jsonl"));
24128
- if (st.size > 0) chatDone += 0.3;
24129
- } catch {
24130
- }
24131
- try {
24132
- const lenses = await fs36.readdir(path42.join(dataRoot, "engine-pilot", "grades"));
24133
- let withPerson = 0;
24134
- for (const l of lenses) {
24135
- try {
24136
- await fs36.stat(path42.join(dataRoot, "engine-pilot", "grades", l, "_person.json"));
24137
- withPerson++;
24138
- } catch {
24139
- }
24140
- }
24141
- chatDone += 0.6 * Math.min(1, withPerson / 5);
24142
- } catch {
24143
- }
24144
- return { coding, chat: clamp(1 - chatDone, 0.1, 1) };
24145
- }
24146
- function scheduleLanes(plan, est) {
24147
- const chat = est.chatUsd === 0 || est.chatUsd <= plan.windowUsd * 0.9 ? "now" : "overnight";
24148
- return {
24149
- coding: "now",
24150
- chat,
24151
- // No plan name here — the schedule header names the plan once, and
24152
- // repeating it doubled the clumsy unknown-plan label (2026-07-22
24153
- // walkthrough). Phrased for the two-column "Proposed schedule" rows:
24154
- // "<lane> <verdict> — <reason>".
24155
- codingReason: `runs now \u2014 the report link appears right away and fills in as it goes`,
24156
- chatReason: est.chatUsd === 0 ? "not planned \u2014 no chat sources included" : chat === "overnight" ? `starts at 2:00 am tonight \u2014 a chat corpus this size is an overnight job; it runs while you sleep` : `runs now, alongside coding`
24157
- };
24158
- }
24159
- var UNSUPPORTED_FULL_TIERS, WINDOW_USD, LABEL;
24160
- var init_plan = __esm({
24161
- "src/plan.ts"() {
24162
- "use strict";
24163
- UNSUPPORTED_FULL_TIERS = ["pro", "gpt_plus", "gpt_free"];
24164
- WINDOW_USD = {
24165
- max_20x: 300,
24166
- max_5x: 75,
24167
- pro: 15,
24168
- // ChatGPT tiers, mapped to rough Claude-window parity by monthly price
24169
- // ($200 Pro ≈ Max 20x; $20 Plus ≈ Claude Pro; Team between; free ≈ nothing).
24170
- gpt_pro: 300,
24171
- gpt_plus: 20,
24172
- gpt_team: 50,
24173
- gpt_free: 5,
24174
- unknown: 15
24175
- // conservative: schedule like Pro when we can't tell
24176
- };
24177
- LABEL = {
24178
- max_20x: "Claude Max 20x",
24179
- max_5x: "Claude Max 5x",
24180
- pro: "Claude Pro",
24181
- gpt_pro: "ChatGPT Pro (Codex)",
24182
- gpt_plus: "ChatGPT Plus (Codex)",
24183
- gpt_team: "ChatGPT Team (Codex)",
24184
- gpt_free: "ChatGPT Free (Codex)",
24185
- unknown: "an undetected plan (scheduling conservatively)"
24186
- };
24187
- }
24188
- });
24189
-
24190
24292
  // src/wizard.ts
24191
24293
  var wizard_exports = {};
24192
24294
  __export(wizard_exports, {
@@ -24211,7 +24313,7 @@ __export(wizard_exports, {
24211
24313
  import { promises as fs37 } from "fs";
24212
24314
  import os11 from "os";
24213
24315
  import path43 from "path";
24214
- import * as readline from "node:readline/promises";
24316
+ import * as readline2 from "node:readline/promises";
24215
24317
  async function scanJsonlDir(label, dir, maxDepth = 1) {
24216
24318
  let files = 0;
24217
24319
  let newest = 0;
@@ -24324,7 +24426,8 @@ function describeExport(f, i, included, hadPrevious) {
24324
24426
  ${dim(path43.basename(f.path))} ${dim(`(${f.note})`)}`;
24325
24427
  }
24326
24428
  async function runWizard() {
24327
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
24429
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
24430
+ attachPromptAbort(rl);
24328
24431
  const say = (s = "") => console.error(s);
24329
24432
  const lineQueue = [];
24330
24433
  let waiting = null;
@@ -24454,22 +24557,19 @@ async function runWizard() {
24454
24557
  {
24455
24558
  const { looksLikeEmail: looksLikeEmail2, recordContact: recordContact2 } = await Promise.resolve().then(() => (init_reminderEmail(), reminderEmail_exports));
24456
24559
  say();
24457
- if (notifyEmail) {
24458
- say(` Updates go to ${bold(notifyEmail)}`);
24459
- } else {
24460
- const e = (await ask(` Your email ${dim("(recommended \u2014 we'll send your report link and occasional Polymath updates, and can nudge you when an export arrives; Enter to skip)")}: `)).trim();
24461
- if (e && looksLikeEmail2(e)) {
24462
- notifyEmail = e;
24463
- try {
24464
- await fs37.mkdir(dataDir, { recursive: true });
24465
- await fs37.writeFile(path43.join(dataDir, "notify-email.json"), JSON.stringify({ email: e }));
24466
- } catch {
24467
- }
24468
- await recordContact2(e, "wizard", reminderState.token).catch(() => false);
24469
- say(` ${green("\u2713")} Got it, we'll keep you posted at ${bold(e)}.`);
24470
- } else if (e) {
24471
- say(dim(` "${e}" doesn't look like an email \u2014 skipping.`));
24560
+ const e = (await ask(notifyEmail ? ` Your email ${dim(`(currently ${notifyEmail} \u2014 Enter if this is correct, or type a new one)`)}: ` : ` Your email ${dim("(recommended \u2014 we'll send your report link and occasional Polymath updates, and can nudge you when an export arrives; Enter to skip)")}: `)).trim();
24561
+ if (e && looksLikeEmail2(e)) {
24562
+ const correcting = !!notifyEmail;
24563
+ notifyEmail = e;
24564
+ try {
24565
+ await fs37.mkdir(dataDir, { recursive: true });
24566
+ await fs37.writeFile(path43.join(dataDir, "notify-email.json"), JSON.stringify({ email: e }));
24567
+ } catch {
24472
24568
  }
24569
+ await recordContact2(e, correcting ? "wizard-correction" : "wizard", reminderState.token).catch(() => false);
24570
+ say(` ${green("\u2713")} Got it, we'll keep you posted at ${bold(e)}.`);
24571
+ } else if (e) {
24572
+ say(dim(notifyEmail ? ` "${e}" doesn't look like an email \u2014 keeping ${notifyEmail}.` : ` "${e}" doesn't look like an email \u2014 skipping.`));
24473
24573
  }
24474
24574
  }
24475
24575
  const tracked = { token: reminderState.token };
@@ -24582,12 +24682,52 @@ async function runWizard() {
24582
24682
  say(` ${bold("Proposed schedule")} \u2014 sized to your plan (${plan.label}):`);
24583
24683
  say();
24584
24684
  say(` ${bold("Your analysis")} runs now \u2014 everything measured from your logs: words/day, flow state, focus, projects, parallelism`);
24585
- say(` ${bold("AI sections")} support for your plan is coming soon! Only the AI-graded sections (judgement, agency, taste, growth) won't be visible yet`);
24685
+ const planPhrase = plan.tier === "gpt_plus" ? "the $20 ChatGPT Plus plan" : "the free ChatGPT plan";
24686
+ say(` ${bold("AI sections")} not supported on ${planPhrase} just yet \u2014 coming soon! Only the AI-graded sections (judgement, agency, taste, growth) won't be visible`);
24586
24687
  say();
24587
24688
  await ask(` Press Enter to run your analysis`).catch(() => "");
24588
24689
  say(dim(" On it. The report link appears right away.\n"));
24589
24690
  return { cmd: "serve", grade: false, overnight: false, chatOvernight: false, open: true };
24590
24691
  }
24692
+ if (plan.tier === "pro") {
24693
+ say(` ${bold("Proposed schedule")} \u2014 sized to your plan (${plan.label}):`);
24694
+ if (resumed) say(dim(" (an earlier run already processed most of this \u2014 only what's new is scheduled; nothing done is redone)"));
24695
+ say();
24696
+ say(` ${bold("Deterministic sections")} run now \u2014 everything measured from your logs: words/day, flow state, focus, projects, parallelism`);
24697
+ say();
24698
+ say(` ${bold("AI sections")} run overnight, starting 1:00 am \u2014 grades, judgement, agency, taste, growth`);
24699
+ say();
24700
+ say(` Why overnight: a full AI run costs about two of your plan's 5-hour usage windows.`);
24701
+ say(` Running it at night spends windows you're asleep for \u2014 it hits the limit around`);
24702
+ say(` halfway, pauses, and resumes on its own when the window resets (~6:00 am).`);
24703
+ say();
24704
+ say(` ${bold("1) Run on this schedule \u2014 RECOMMENDED")}`);
24705
+ say(` \u2022 keep your laptop plugged in`);
24706
+ say(` \u2022 keep the lid open \u2014 closed means sleep, and the run stalls until morning`);
24707
+ say(` ${bold("2)")} Run the AI sections now instead \u2014 you'll likely hit your plan's usage limit partway;`);
24708
+ say(` the run pauses and resumes by itself when the limit resets (~5h), and the report`);
24709
+ say(` link works the whole time, filling in as stages finish`);
24710
+ say();
24711
+ const proDepth = await ask(` Choose ${dim("[1/2, default 1]")}: `) || "1";
24712
+ const proOvernight = proDepth !== "2";
24713
+ if (proOvernight) {
24714
+ if (!process.env.POLYMATH_NIGHTLY_START) process.env.POLYMATH_NIGHTLY_START = "1";
24715
+ say(` ${bold("VERY IMPORTANT for the overnight part:")}`);
24716
+ say(` \u2022 keep your laptop plugged in`);
24717
+ say(` \u2022 keep the lid open \u2014 closed means sleep, and the run stalls until morning`);
24718
+ say(dim(` (the report page shows this too)`));
24719
+ }
24720
+ say(dim(" On it. The report link appears right away and fills in as stages finish \u2014 Ctrl-C anytime.\n"));
24721
+ return {
24722
+ cmd: "serve",
24723
+ grade: true,
24724
+ overnight: proOvernight,
24725
+ // The chat corpus is unbounded (~$390 measured on 450 MB) — on a $15
24726
+ // window it is ALWAYS an overnight job, whatever the coding choice.
24727
+ chatOvernight: est.chatUsd > 0 ? true : proOvernight,
24728
+ open: true
24729
+ };
24730
+ }
24591
24731
  const planBit = plan.label.includes("undetected") ? "your corpus (plan undetected \u2014 scheduling conservatively)" : `your plan (${plan.label}) and your corpus`;
24592
24732
  say(` ${bold("Proposed schedule")} \u2014 sized to ${planBit}:`);
24593
24733
  if (resumed) say(dim(" (an earlier run already processed most of this \u2014 only what's new is scheduled; nothing done is redone)"));
@@ -24645,7 +24785,8 @@ function newExportsSince(found, m) {
24645
24785
  }
24646
24786
  async function returningUserFlow(dataDir) {
24647
24787
  const say = (s = "") => console.error(s);
24648
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
24788
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
24789
+ attachPromptAbort(rl);
24649
24790
  try {
24650
24791
  say();
24651
24792
  say(bold(" What would you like to do?"));
@@ -24692,17 +24833,18 @@ async function askEmailAndQueueReminder(dataDir, say, ask) {
24692
24833
  }
24693
24834
  const clicked = await fetchClickedKinds2(state.token);
24694
24835
  if (!clicked.length) return;
24695
- if (!email) {
24696
- const e = (await ask(` Leave your email and we'll remind you when your exports should be ready ${dim("(Enter to skip)")}`)).trim();
24836
+ {
24837
+ const e = (await ask(email ? ` Your email ${dim(`(currently ${email} \u2014 Enter if this is correct, or type a new one)`)}: ` : ` Leave your email and we'll remind you when your exports should be ready ${dim("(Enter to skip)")}`)).trim();
24697
24838
  if (e && looksLikeEmail2(e)) {
24839
+ const correcting = !!email;
24698
24840
  email = e;
24699
24841
  await fs37.mkdir(dataDir, { recursive: true }).catch(() => {
24700
24842
  });
24701
24843
  await fs37.writeFile(path43.join(dataDir, "notify-email.json"), JSON.stringify({ email: e })).catch(() => {
24702
24844
  });
24703
- await recordContact2(e, "add-sources", state.token).catch(() => false);
24845
+ await recordContact2(e, correcting ? "wizard-correction" : "add-sources", state.token).catch(() => false);
24704
24846
  } else if (e) {
24705
- say(dim(` "${e}" doesn't look like an email \u2014 skipping the reminder.`));
24847
+ say(dim(email ? ` "${e}" doesn't look like an email \u2014 keeping ${email}.` : ` "${e}" doesn't look like an email \u2014 skipping the reminder.`));
24706
24848
  }
24707
24849
  }
24708
24850
  if (!email) return;
@@ -24844,6 +24986,7 @@ var init_wizard = __esm({
24844
24986
  init_exportScan();
24845
24987
  init_manifest();
24846
24988
  init_dataHome();
24989
+ init_ttyGuard();
24847
24990
  init_raw2();
24848
24991
  color = process.stdout.isTTY && !process.env.NO_COLOR;
24849
24992
  dim = (s) => color ? `\x1B[2m${s}\x1B[0m` : s;
@@ -24891,6 +25034,7 @@ function makeMultiBar(out = process.stderr) {
24891
25034
  return ` ${name17.padEnd(6)} [${"\u2588".repeat(fill)}${"\u2591".repeat(width - fill)}] ${String(Math.round(l.shown * 100)).padStart(3)}%`;
24892
25035
  };
24893
25036
  const out2 = [];
25037
+ out2.push("");
24894
25038
  out2.push("This is a background process. We'll let you know when it's finished.");
24895
25039
  out2.push("It may take anywhere from 30 minutes to an hour.");
24896
25040
  out2.push("");
@@ -25068,8 +25212,8 @@ try {
25068
25212
  import { promises as fs38, existsSync as existsSync7 } from "fs";
25069
25213
  import path44 from "path";
25070
25214
  import { fileURLToPath as fileURLToPath5 } from "url";
25071
- import { spawnSync as spawnSync3 } from "child_process";
25072
- import readline2 from "readline";
25215
+ import { spawnSync as spawnSync4 } from "child_process";
25216
+ import readline3 from "readline";
25073
25217
 
25074
25218
  // src/openBrowser.ts
25075
25219
  import { spawn } from "child_process";
@@ -28105,6 +28249,7 @@ init_auth();
28105
28249
  init_pipeline2();
28106
28250
  init_chatPipeline();
28107
28251
  init_manifest();
28252
+ init_ttyGuard();
28108
28253
  init_profile();
28109
28254
  var __dirname2 = path44.dirname(fileURLToPath5(import.meta.url));
28110
28255
  var REPO_ROOT = path44.resolve(__dirname2, "..", "..", "..");
@@ -28256,8 +28401,9 @@ PROJECTS`);
28256
28401
  log(``);
28257
28402
  }
28258
28403
  async function askYesNo(q, def = true) {
28259
- const readline3 = await import("node:readline/promises");
28260
- const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
28404
+ const readline4 = await import("node:readline/promises");
28405
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
28406
+ attachPromptAbort(rl);
28261
28407
  try {
28262
28408
  for (; ; ) {
28263
28409
  const a = (await rl.question(`${q} `)).trim().toLowerCase();
@@ -28286,6 +28432,7 @@ async function runGrade(opts2, provider, profile) {
28286
28432
  await provider.criteriaMean(profile.sessions);
28287
28433
  }
28288
28434
  async function main() {
28435
+ guardTty();
28289
28436
  if (process.argv[2] === "remind-exports") {
28290
28437
  const { runReminder: runReminder2 } = await Promise.resolve().then(() => (init_remind(), remind_exports));
28291
28438
  const argv = process.argv.slice(3);
@@ -28313,8 +28460,13 @@ async function main() {
28313
28460
  await printBanner2();
28314
28461
  console.error("");
28315
28462
  await maybePrintUpdateNotice(opts2);
28316
- console.error(` data home: ${DATA_ROOT}`);
28317
- console.error(" Checking for existing analysis\u2026");
28463
+ {
28464
+ const { osc8: osc82 } = await Promise.resolve().then(() => (init_exportLinks(), exportLinks_exports));
28465
+ const boldTty = process.stderr.isTTY && !process.env.NO_COLOR;
28466
+ const b = (t) => boldTty ? `\x1B[1m${t}\x1B[0m` : t;
28467
+ console.error(` ${b("Step 0 \xB7 Want to see some sample reports before you run?")} ${osc82("https://polymathsociety.us/sample", "https://polymathsociety.us/sample")}`);
28468
+ console.error("");
28469
+ }
28318
28470
  const prior = process.env.POLYMATH_WIZARD === "1" ? null : await readPriorAnalysis();
28319
28471
  if (prior) {
28320
28472
  console.error(` Found your finished report \u2014 ${prior.graded} graded sessions, last updated ${prior.when}.`);
@@ -28769,6 +28921,7 @@ No analysis here yet \u2014 computing the instant deterministic metrics now (fre
28769
28921
  };
28770
28922
  process.on("SIGINT", shutdown(130));
28771
28923
  process.on("SIGTERM", shutdown(143));
28924
+ watchCtrlC(() => void shutdown(130)());
28772
28925
  return;
28773
28926
  }
28774
28927
  const profile = await analyze({ codex: opts2.codex, idleGapMin: opts2.idleGapMin, tz: opts2.tz, spanHourMin: opts2.spanHourMin });
@@ -28805,7 +28958,8 @@ async function promptAutoUpdate(current, latest) {
28805
28958
  console.error(`
28806
28959
  New version available: polymath-society ${current} \u2192 ${latest}`);
28807
28960
  const answer = await new Promise((resolve2) => {
28808
- const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
28961
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
28962
+ attachPromptAbort(rl);
28809
28963
  const timer = setTimeout(() => {
28810
28964
  rl.close();
28811
28965
  resolve2("");
@@ -28823,7 +28977,7 @@ async function promptAutoUpdate(current, latest) {
28823
28977
  console.error(`
28824
28978
  Updating\u2026
28825
28979
  `);
28826
- const install = spawnSync3("npm", ["install", "-g", "polymath-society@latest"], {
28980
+ const install = spawnSync4("npm", ["install", "-g", "polymath-society@latest"], {
28827
28981
  stdio: "inherit",
28828
28982
  shell: process.platform === "win32"
28829
28983
  });
@@ -28837,7 +28991,7 @@ async function promptAutoUpdate(current, latest) {
28837
28991
  console.error(`
28838
28992
  Updated to ${latest}. Relaunching\u2026
28839
28993
  `);
28840
- const relaunch = spawnSync3("polymath-society", process.argv.slice(2), {
28994
+ const relaunch = spawnSync4("polymath-society", process.argv.slice(2), {
28841
28995
  stdio: "inherit",
28842
28996
  shell: process.platform === "win32"
28843
28997
  });