sessionpipe-core 0.13.2 → 0.13.4

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // sessionpipe-core 0.13.2 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-core 0.13.4 · Apache-2.0 · https://sessionpipe.org
2
2
  var __defProp = Object.defineProperty;
3
3
  var __export = (target, all) => {
4
4
  for (var name in all)
@@ -63,6 +63,82 @@ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as
63
63
  import { userInfo } from "node:os";
64
64
  import path4 from "node:path";
65
65
 
66
+ // src/control/frames.ts
67
+ function hostOf(receiver) {
68
+ try {
69
+ return new URL(receiver).host;
70
+ } catch {
71
+ return receiver;
72
+ }
73
+ }
74
+ var IN_PLACE_ANSWER = "Answer it right here, as you would answer the person at the keyboard: your reply is sent back to them automatically. Don't run a command or call a tool just to send it.";
75
+ var COPY_WHYS = ["open", "running", "recent"];
76
+ var copyNote = (why) => `You are a copy of that session, started because the original is ${why === "open" ? "open in a window" : "in use by another process"}, and you have its conversation, folder and tools. Do what the message asks the way the original would: edits, commands and publishing all take effect for real. Only this conversation stays here, so the person reads your reply and not the original's${why === "open" ? " window" : ""}: say what you did, then stop.`;
77
+ var RE_ARM = "(Answer it right here, as you would answer the person at the keyboard: your reply is sent back to them automatically, so don't run a command or call a tool just to send it. Then, without mentioning it, start `sessionpipe wait` again as a background command, with the longest timeout your harness allows, to keep receiving messages.)";
78
+ function frame(text, receiver) {
79
+ return `Message via ${hostOf(receiver)} (sessionpipe control, signed on the sender's device):
80
+
81
+ ${text}`;
82
+ }
83
+ var marked = (text, host, mark) => `Message via ${host} (sessionpipe control, signed on the sender's device; ${mark}):
84
+
85
+ ${text}`;
86
+ function frameStart(text, receiver) {
87
+ return `${text}
88
+
89
+ (Sent via ${hostOf(receiver)} with sessionpipe control, signed on the sender's device.)`;
90
+ }
91
+ function frameAsk(text, from, mark) {
92
+ return `${text}
93
+
94
+ (Asked by ${from ?? "another session"} on this machine with sessionpipe ask${mark ? `, ${mark}` : ""}. Answer here in full: your reply is returned to that session automatically. Don't run a command or call a tool just to send it.)`;
95
+ }
96
+ function frameHandoff(from, receiver, reply) {
97
+ return `Answer from ${from} (sessionpipe handoff, asked via ${hostOf(receiver)}):
98
+
99
+ ${reply}`;
100
+ }
101
+ var HOLE = "\0";
102
+ var MARK = "";
103
+ var lineOf = (built, hole = "(\\S+)") => new RegExp(
104
+ `^${built.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replaceAll(HOLE, hole).replaceAll(MARK, "message \\S+")}$`
105
+ );
106
+ var HEAD_LINES = [lineOf(frame("", HOLE)), lineOf(marked("", HOLE, MARK))];
107
+ var START_LINES = [lineOf(frameStart("", HOLE))];
108
+ var ASK_LINES = [lineOf(frameAsk("", HOLE), "[^\\n]{1,300}")];
109
+ var MARKED_ASK_LINES = [lineOf(frameAsk("", HOLE, MARK), "([^\\n]{1,300}?)")];
110
+ var HANDOFF_LINES = [lineOf(frameHandoff(HOLE, HOLE, ""))];
111
+ var COPY_NOTE_0_13_0 = "You are a copy of that session, started to answer this message: the conversation above is a replay, the original session is somewhere else, and nothing you do here appears in it. Answer the message and stop; don't carry on the original's work.";
112
+ var COPY_NOTES = [...new Set(COPY_WHYS.map(copyNote)), COPY_NOTE_0_13_0];
113
+ var NOTES = [IN_PLACE_ANSWER, ...COPY_NOTES, RE_ARM];
114
+ var ASK_NOTES = [...COPY_NOTES, RE_ARM];
115
+ var lineIn = (lines, line) => {
116
+ for (const l of lines) {
117
+ const m = l.exec(line);
118
+ if (m) return m;
119
+ }
120
+ return null;
121
+ };
122
+ function deliveredWords(text) {
123
+ const paragraphs = text.split("\n\n");
124
+ const at = (i) => paragraphs[i].trim();
125
+ const n = paragraphs.length;
126
+ const note = n > 1 && NOTES.includes(at(n - 1)) ? at(n - 1) : null;
127
+ const end = note === null ? n : n - 1;
128
+ const words = (from, to) => paragraphs.slice(from, to).join("\n\n").trim();
129
+ const head = lineIn(HEAD_LINES, at(0));
130
+ if (head && words(1, end)) return { text: words(1, end), via: head[1], delivered: true };
131
+ const start = note === null ? lineIn(START_LINES, at(n - 1)) : null;
132
+ if (start && words(0, n - 1)) return { text: words(0, n - 1), via: start[1], delivered: true };
133
+ const asks = note === null || ASK_NOTES.includes(note);
134
+ const theirs = asks && (lineIn(ASK_LINES, at(end - 1)) && words(0, end - 1) || lineIn(HANDOFF_LINES, at(0)) && words(1, end));
135
+ if (theirs) return { text: note === null ? text : words(0, end), via: null, delivered: true };
136
+ const marked2 = asks ? lineIn(MARKED_ASK_LINES, at(end - 1)) : null;
137
+ if (marked2 && words(0, end - 1))
138
+ return { text: `${words(0, end - 1)}${frameAsk("", marked2[1])}`, via: null, delivered: true };
139
+ return { text, via: null, delivered: false };
140
+ }
141
+
66
142
  // src/readers/files.ts
67
143
  import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
68
144
  import path2 from "node:path";
@@ -199,15 +275,23 @@ function codexReplayBoundary(meta) {
199
275
  }
200
276
 
201
277
  // src/readers/transcripts.ts
202
- function pairTurns(msgs, endLine) {
278
+ function pairTurns(msgs, endLine, tail = false) {
203
279
  const turns = [];
204
280
  let cur = null;
205
281
  for (const m of msgs) {
206
- if (m.role === "user") {
282
+ if (m.role === "interrupt") {
283
+ if (cur) {
284
+ cur.interrupted = true;
285
+ cur.endLine = m.line;
286
+ turns.push(cur);
287
+ cur = null;
288
+ }
289
+ } else if (m.role === "user") {
207
290
  if (cur && !cur.assistant) {
208
291
  cur.user += `
209
292
 
210
293
  ${m.text}`;
294
+ if (m.id) cur.id = m.id;
211
295
  continue;
212
296
  }
213
297
  if (cur) turns.push(cur);
@@ -217,12 +301,14 @@ ${m.text}`;
217
301
  at: m.at,
218
302
  startLine: m.line,
219
303
  endLine: m.line,
220
- ...m.injected ? { injected: true } : {}
304
+ ...m.injected ? { injected: true } : {},
305
+ ...m.id ? { id: m.id } : {}
221
306
  };
222
307
  } else {
223
308
  if (!cur) cur = { user: "", assistant: "", at: m.at, startLine: m.line, endLine: m.line };
224
309
  cur.assistant += (cur.assistant ? "\n\n" : "") + m.text;
225
310
  cur.endLine = m.line;
311
+ cur.said = m.text;
226
312
  }
227
313
  }
228
314
  if (cur?.assistant) turns.push(cur);
@@ -233,12 +319,22 @@ ${m.text}`;
233
319
  assistant: t.assistant,
234
320
  at: t.at,
235
321
  endLine: t.endLine,
236
- ...t.injected ? { injected: true } : {}
322
+ ...t.injected ? { injected: true } : {},
323
+ ...t.id ? { id: t.id } : {},
324
+ ...t.interrupted ? { interrupted: true } : {}
237
325
  })),
238
326
  line
239
327
  };
240
328
  if (cur && !cur.assistant && cur.user)
241
- out.open = { user: cur.user, at: cur.at, ...cur.injected ? { injected: true } : {} };
329
+ out.open = {
330
+ user: cur.user,
331
+ at: cur.at,
332
+ ...cur.injected ? { injected: true } : {},
333
+ ...cur.id ? { id: cur.id } : {}
334
+ };
335
+ const last = turns[turns.length - 1];
336
+ if (tail && last)
337
+ out.tail = { start: last.startLine, ...last.said !== void 0 && !last.interrupted ? { said: last.said } : {} };
242
338
  return out;
243
339
  }
244
340
  var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
@@ -262,6 +358,10 @@ var textParts = (c, kinds) => {
262
358
  ).map((b) => b.text).join("\n");
263
359
  };
264
360
  var SPOKEN = /* @__PURE__ */ new Set(["human", "peer"]);
361
+ var fromAgent = (origin) => {
362
+ const o = origin;
363
+ return o?.handback === true || typeof o?.senderTaskId === "string";
364
+ };
265
365
  var PEER_INTRO = /sent a message[^\n]*:$/;
266
366
  var PEER_OUTRO = "\n\nThis came from another ";
267
367
  var ENVELOPE = /^<cross-session-message\b[^>]*>\n([\s\S]*)\n<\/cross-session-message>$/;
@@ -281,40 +381,68 @@ var queuedPrompt = (a) => {
281
381
  const q = a;
282
382
  if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
283
383
  const kind = q.origin?.kind ?? "human";
284
- if (!SPOKEN.has(kind)) return "";
384
+ if (!SPOKEN.has(kind) || fromAgent(q.origin)) return "";
285
385
  const text = textParts(q.prompt, ["text"]).trim();
286
386
  return kind === "peer" ? peerAsk(q.origin, text) : text;
287
387
  };
288
388
  var channelText = (text) => /^<channel\b[^>]*>([\s\S]*)<\/channel>$/.exec(text)?.[1]?.trim() ?? "";
389
+ var INTERRUPTED = /^\[Request interrupted by user[^\]]*\]$/;
390
+ var HOOK_FEEDBACK = /^[A-Za-z]+ hook feedback:\n/;
391
+ var hookDelivery = (text) => {
392
+ const head = HOOK_FEEDBACK.exec(text);
393
+ if (!head) return "";
394
+ const said = deliveredWords(text.slice(head[0].length));
395
+ return said.delivered ? said.text : "";
396
+ };
397
+ var hasBlock = (c, type) => Array.isArray(c) && c.some((b) => b && typeof b === "object" && b.type === type);
289
398
  function readClaude(lines, from) {
290
399
  const msgs = [];
400
+ let saidLine = -1;
401
+ let turnOver = false;
291
402
  for (let i = from; i < lines.length; i++) {
292
403
  const j = lines[i] ? parse(lines[i]) : null;
293
404
  if (!j || j.isSidechain) continue;
294
405
  const origin = j.origin;
295
406
  const channel = j.type === "user" && origin?.kind === "channel";
296
407
  const peer = origin?.kind === "peer";
408
+ const c = j.message?.content;
409
+ if (!turnOver && (j.type === "assistant" && hasBlock(c, "tool_use") || j.type === "user" && hasBlock(c, "tool_result")))
410
+ saidLine = -1;
297
411
  if (j.type === "user" && (j.isCompactSummary || j.repeatsCompactSummary)) continue;
298
- if (j.type === "user" && !channel && !peer && (j.isMeta || origin?.kind && origin.kind !== "human")) continue;
299
412
  const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
300
- const c = j.message?.content;
413
+ const id = typeof j.promptId === "string" && j.promptId ? j.promptId : void 0;
414
+ const ask = (said, m = {}) => {
415
+ const text = deliveredWords(said).text;
416
+ if (!text || isInjected(text)) return;
417
+ msgs.push({ role: "user", text, at, line: i, ...m });
418
+ turnOver = true;
419
+ };
420
+ if (j.type === "user" && !channel && !peer && (j.isMeta || origin?.kind && origin.kind !== "human")) {
421
+ if (j.isMeta && !origin?.kind)
422
+ ask(hookDelivery(textParts(c, ["text"]).trim()), { injected: true, ...id ? { id } : {} });
423
+ continue;
424
+ }
301
425
  if (channel) {
302
- const text = channelText(textParts(c, ["text"]).trim());
303
- if (text) msgs.push({ role: "user", text, at, line: i, injected: true });
426
+ ask(channelText(textParts(c, ["text"]).trim()), { injected: true });
304
427
  } else if (j.type === "user") {
305
428
  const raw = textParts(c, ["text"]).trim();
306
- const text = peer ? peerAsk(origin, raw) : raw;
307
- if (!text || isInjected(text)) continue;
308
- msgs.push({ role: "user", text, at, line: i, ...peer ? { injected: true } : {} });
429
+ if (!peer && INTERRUPTED.test(raw)) {
430
+ msgs.push({ role: "interrupt", text: "", at, line: i });
431
+ turnOver = true;
432
+ } else if (!peer) ask(raw, id ? { id } : {});
433
+ else if (!fromAgent(origin)) ask(peerAsk(origin, raw), { injected: true, ...id ? { id } : {} });
309
434
  } else if (j.type === "assistant" && Array.isArray(c)) {
310
435
  const text = textParts(c, ["text"]).trim();
311
- if (text) msgs.push({ role: "assistant", text, at, line: i });
312
- } else if (j.type === "attachment") {
313
- const text = queuedPrompt(j.attachment);
314
- if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
315
- }
436
+ if (text) {
437
+ msgs.push({ role: "assistant", text, at, line: i });
438
+ saidLine = i;
439
+ turnOver = false;
440
+ }
441
+ } else if (j.type === "attachment") ask(queuedPrompt(j.attachment));
316
442
  }
317
- return pairTurns(msgs, endOf(lines));
443
+ const out = pairTurns(msgs, endOf(lines), true);
444
+ if (out.tail && saidLine !== out.turns[out.turns.length - 1].endLine) delete out.tail.said;
445
+ return out;
318
446
  }
319
447
  function codexInjected(text) {
320
448
  return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
@@ -766,6 +894,8 @@ var claudeCode = {
766
894
  }
767
895
  const hints = {};
768
896
  if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
897
+ if (event === "Stop" && typeof s.last_assistant_message === "string" && s.last_assistant_message.trim())
898
+ hints.final = s.last_assistant_message;
769
899
  const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
770
900
  if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
771
901
  const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
@@ -1028,6 +1158,14 @@ function claudeAccounts(dirs, home = homeDir()) {
1028
1158
  }
1029
1159
  return out;
1030
1160
  }
1161
+ function claudeAccountDirs(dirs, home = homeDir()) {
1162
+ const out = [];
1163
+ for (const d of dirs) {
1164
+ const l = claudeLogin(d, home);
1165
+ if (l) out.push({ id: l.id, dir: d });
1166
+ }
1167
+ return out;
1168
+ }
1031
1169
  var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
1032
1170
  function transcriptLink(records) {
1033
1171
  for (let i = records.length - 1; i >= 0; i--) {
@@ -1050,7 +1188,9 @@ function askText(j) {
1050
1188
  const origin = j.origin;
1051
1189
  if (origin?.kind && origin.kind !== "human") return null;
1052
1190
  const c = j.message?.content;
1053
- const text = (typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim();
1191
+ const text = deliveredWords(
1192
+ (typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim()
1193
+ ).text;
1054
1194
  if (!text || isInjected(text)) return null;
1055
1195
  return text;
1056
1196
  }
@@ -1695,6 +1835,7 @@ var acp_control_exports = {};
1695
1835
  __export(acp_control_exports, {
1696
1836
  ACP_AGENTS: () => ACP_AGENTS,
1697
1837
  ACP_HARNESSES: () => ACP_HARNESSES,
1838
+ CANCELLED_NOTE: () => CANCELLED_NOTE,
1698
1839
  acpAgent: () => acpAgent,
1699
1840
  acpHarnesses: () => acpHarnesses,
1700
1841
  acpOverride: () => acpOverride,
@@ -1802,8 +1943,13 @@ function runHeadless(bin, args, o) {
1802
1943
  let tooBig = false;
1803
1944
  let errTail = "";
1804
1945
  let timedOut = false;
1946
+ let stopped = false;
1805
1947
  let settled = false;
1806
1948
  let child;
1949
+ if (o.signal?.aborted) {
1950
+ resolve({ stopped: true });
1951
+ return;
1952
+ }
1807
1953
  const c = spawnable(bin, args);
1808
1954
  if (!c) {
1809
1955
  resolve({ error: shimNote(bin) });
@@ -1866,27 +2012,53 @@ function runHeadless(bin, args, o) {
1866
2012
  child.stderr?.on("data", (b) => {
1867
2013
  errTail = (errTail + b.toString("utf8")).slice(-4e3);
1868
2014
  });
2015
+ let ending = null;
2016
+ let gone = false;
2017
+ const end = () => {
2018
+ kill("SIGTERM");
2019
+ ending ??= setTimeout(() => {
2020
+ ending = null;
2021
+ kill("SIGKILL");
2022
+ }, 1e4);
2023
+ ending.unref();
2024
+ };
1869
2025
  const timer = setTimeout(() => {
2026
+ if (gone) return;
1870
2027
  timedOut = true;
1871
- kill("SIGTERM");
1872
- setTimeout(() => kill("SIGKILL"), 1e4).unref();
2028
+ end();
1873
2029
  }, o.timeoutMs ?? JOB_TIMEOUT_MS);
2030
+ const stop = () => {
2031
+ if (gone) return;
2032
+ stopped = true;
2033
+ end();
2034
+ };
2035
+ o.signal?.addEventListener("abort", stop, { once: true });
1874
2036
  const result = (code, signal) => ({
1875
2037
  code,
1876
2038
  signal,
1877
2039
  stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
1878
2040
  tooBig,
1879
2041
  stderr: errTail,
1880
- timedOut
2042
+ timedOut,
2043
+ ...stopped ? { stopped } : {}
1881
2044
  });
1882
2045
  const finish = (r) => {
1883
2046
  if (settled) return;
1884
2047
  settled = true;
1885
2048
  clearTimeout(timer);
2049
+ o.signal?.removeEventListener("abort", stop);
1886
2050
  resolve(r);
1887
2051
  };
1888
2052
  child.on("error", (e) => finish({ error: e.message }));
1889
- child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
2053
+ child.on("exit", (code, signal) => {
2054
+ if (ending) {
2055
+ clearTimeout(ending);
2056
+ ending = null;
2057
+ kill("SIGKILL");
2058
+ }
2059
+ gone = true;
2060
+ setTimeout(() => finish(result(code, signal)), 2e3).unref();
2061
+ });
1890
2062
  child.on("close", (code, signal) => {
1891
2063
  if (o.onLine && partial.trim()) {
1892
2064
  try {
@@ -1903,6 +2075,7 @@ function runHeadless(bin, args, o) {
1903
2075
  function stderrTail(stderr, n = 3) {
1904
2076
  return String(stderr || "").trim().split("\n").slice(-n).join(" ").trim();
1905
2077
  }
2078
+ var CANCELLED_NOTE = "(The turn was cancelled.)";
1906
2079
  function deniedNote(names, count) {
1907
2080
  if (!count) return "";
1908
2081
  return `
@@ -2144,7 +2317,9 @@ function startAcp(o) {
2144
2317
  pending.clear();
2145
2318
  };
2146
2319
  child.on("error", (e) => left(`couldn't start ${label}: ${e.message}`));
2320
+ let dead = false;
2147
2321
  const exited = (code, signal) => {
2322
+ dead = true;
2148
2323
  const tail = errTail.trim().split("\n").slice(-3).join(" ").trim();
2149
2324
  left(`${label} exited (${signal || `code ${code}`})${tail ? `: ${tail}` : ""}`);
2150
2325
  };
@@ -2158,6 +2333,7 @@ function startAcp(o) {
2158
2333
  child.stdin?.end();
2159
2334
  } catch {
2160
2335
  }
2336
+ if (dead) return;
2161
2337
  const grace = o.closeGraceMs ?? 2e3;
2162
2338
  const t = setTimeout(() => kill("SIGTERM"), grace);
2163
2339
  t.unref();
@@ -2215,7 +2391,7 @@ function startAcp(o) {
2215
2391
  error: `${label} was still working after ${Math.round(limit / 6e4)} minutes, so it was stopped`
2216
2392
  } : { reply: `${words}
2217
2393
 
2218
- (The turn was cancelled.)${refused2}` };
2394
+ ${CANCELLED_NOTE}${refused2}` };
2219
2395
  return { reply: "", error: `${label} ended the turn with ${stop || "no stop reason"}` };
2220
2396
  } catch (e) {
2221
2397
  return { reply: "", error: e.message };
@@ -2703,8 +2879,10 @@ __export(claude_code_control_exports, {
2703
2879
  findClaude: () => findClaude,
2704
2880
  findTranscript: () => findTranscript,
2705
2881
  findTranscripts: () => findTranscripts,
2882
+ helpFailure: () => helpFailure,
2706
2883
  homeSettings: () => homeSettings,
2707
2884
  hookMode: () => hookMode,
2885
+ initSession: () => initSession,
2708
2886
  isUuid: () => isUuid,
2709
2887
  liveProcess: () => liveProcess,
2710
2888
  liveRecord: () => liveRecord,
@@ -2719,11 +2897,13 @@ __export(claude_code_control_exports, {
2719
2897
  parseCaps: () => parseCaps,
2720
2898
  parseResult: () => parseResult,
2721
2899
  peerClass: () => peerClass,
2900
+ recentWrite: () => recentWrite,
2722
2901
  recentlyWritten: () => recentlyWritten,
2723
2902
  relaunchMode: () => relaunchMode,
2724
2903
  resolveHome: () => resolveHome,
2725
2904
  runClaude: () => runClaude,
2726
- sessionFolder: () => sessionFolder
2905
+ sessionFolder: () => sessionFolder,
2906
+ versionManagerBins: () => versionManagerBins
2727
2907
  });
2728
2908
  import { execFile, execFileSync as execFileSync3 } from "node:child_process";
2729
2909
  import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync6, readSync as readSync2, statSync as statSync5 } from "node:fs";
@@ -2751,6 +2931,201 @@ var POOL_FIELDS = FIELDS.pool;
2751
2931
  var enc = new TextEncoder();
2752
2932
  var dec = new TextDecoder();
2753
2933
 
2934
+ // src/privacy/pii.ts
2935
+ import { createHash as createHash3 } from "node:crypto";
2936
+ var RULESET_PII = "pii@1";
2937
+ var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
2938
+ var PHONE = /(?<![\w.])(?:(?<!\)[ .-]?|\+\d{1,3}[ .-]?)(\+?(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01]))(?:(?= (?:[01]?\d|2[0-3]):)|(?!\w|[ .-]\d\d))|(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w))/g;
2939
+ function looksPhone(s) {
2940
+ const digits = s.replace(/\D/g, "");
2941
+ if (digits.length < 7 || digits.length > 15) return false;
2942
+ if (!/[+() .-]/.test(s)) return false;
2943
+ if (!/[+() -]/.test(s)) return false;
2944
+ return true;
2945
+ }
2946
+ function reducePii(text, home) {
2947
+ if (!text) return text;
2948
+ let out = text.replace(EMAIL, "[email]");
2949
+ out = out.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
2950
+ if (home) {
2951
+ const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2952
+ out = out.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
2953
+ }
2954
+ return out;
2955
+ }
2956
+ function hashMachine(name) {
2957
+ return createHash3("sha256").update(name).digest("hex").slice(0, 8);
2958
+ }
2959
+ function reducePiiDeep(v, home) {
2960
+ if (typeof v === "string") return reducePii(v, home);
2961
+ if (Array.isArray(v)) return v.map((x) => reducePiiDeep(x, home));
2962
+ if (v && typeof v === "object") {
2963
+ const out = {};
2964
+ for (const [k, x] of Object.entries(v)) out[k] = reducePiiDeep(x, home);
2965
+ return out;
2966
+ }
2967
+ return v;
2968
+ }
2969
+
2970
+ // src/privacy/secrets.ts
2971
+ var RULESET_SECRETS = "secrets@1";
2972
+ var REDACTED = "[redacted]";
2973
+ var TOKEN_PATTERNS = [
2974
+ /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/g,
2975
+ /\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g,
2976
+ // AWS access key id
2977
+ /\bgh[pousr]_[A-Za-z0-9]{30,}\b/g,
2978
+ // GitHub
2979
+ /\bgithub_pat_[A-Za-z0-9_]{30,}\b/g,
2980
+ /\bglpat-[A-Za-z0-9_-]{20,}\b/g,
2981
+ // GitLab
2982
+ /\bxox[abposre]-[A-Za-z0-9-]{10,}\b/g,
2983
+ // Slack
2984
+ /\bhttps:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]+/g,
2985
+ /\bhttps:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+/g,
2986
+ /\bsk-(?:ant-|proj-|svcacct-|admin-|or-v1-)?[A-Za-z0-9_-]{20,}\b/g,
2987
+ // Anthropic, OpenAI, DeepSeek, OpenRouter
2988
+ /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g,
2989
+ // Stripe secret / restricted
2990
+ /\bwhsec_[A-Za-z0-9+/=]{20,}/g,
2991
+ // Stripe / Svix / Standard Webhooks secret
2992
+ /\bAIza[0-9A-Za-z_-]{35}\b/g,
2993
+ // Google API key
2994
+ /\bya29\.[0-9A-Za-z_-]{20,}/g,
2995
+ // Google OAuth access token
2996
+ /\b1\/\/0[0-9A-Za-z_-]{30,}/g,
2997
+ // Google OAuth refresh token
2998
+ /\bGOCSPX-[0-9A-Za-z_-]{20,}/g,
2999
+ // Google OAuth client secret
3000
+ /\bxai-[A-Za-z0-9]{30,}\b/g,
3001
+ /\bgsk_[A-Za-z0-9]{30,}\b/g,
3002
+ // Groq
3003
+ /\bpplx-[A-Za-z0-9]{30,}\b/g,
3004
+ // Perplexity
3005
+ /\bhf_[A-Za-z0-9]{30,}\b/g,
3006
+ // Hugging Face
3007
+ /\bpcsk_[A-Za-z0-9_]{30,}\b/g,
3008
+ // Pinecone
3009
+ /\bnpm_[A-Za-z0-9]{30,}\b/g,
3010
+ /\bpypi-[A-Za-z0-9_-]{50,}/g,
3011
+ /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g,
3012
+ // SendGrid
3013
+ /\bshp(?:at|ss|ca|pa)_[a-fA-F0-9]{32}\b/g,
3014
+ // Shopify
3015
+ /\bdop_v1_[a-f0-9]{64}\b/g,
3016
+ // DigitalOcean
3017
+ /\b(?:lin_api|lin_oauth)_[A-Za-z0-9]{30,}\b/g,
3018
+ // Linear
3019
+ /\b(?:ntn|secret)_[A-Za-z0-9]{40,}\b/g,
3020
+ // Notion
3021
+ /\bsbp_[a-f0-9]{40}\b/g,
3022
+ // Supabase
3023
+ /\bfigd_[A-Za-z0-9_-]{30,}/g,
3024
+ // Figma
3025
+ /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9]{30,}/g,
3026
+ // Doppler
3027
+ /\bgrn_[A-Za-z0-9_-]{20,}/g,
3028
+ // Granola
3029
+ /\bss_[a-f0-9]{32,}\b/g,
3030
+ // spacesheep API keys
3031
+ /\b[0-9]{8,10}:AA[A-Za-z0-9_-]{33}\b/g,
3032
+ // Telegram bot token
3033
+ /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
3034
+ // JWT
3035
+ ];
3036
+ var URL_CREDENTIALS = /\b([a-z][a-z0-9+.-]{1,20}:\/\/[^\s:/@'"`<>]{1,100}:)([^\s@/'"`<>]{1,200})(@)/gi;
3037
+ var AUTH_HEADER = /\b((?:Bearer|Basic|Token)\s+)([A-Za-z0-9._~+/=-]{16,})/g;
3038
+ var SECRET_WORD = "(?:pass(?:word|wd|phrase|code)?s?|pwd|secret(?:[ _-]?key)?s?|client[ _-]?secret|api[ _-]?keys?|apikeys?|access[ _-]?keys?|access[ _-]?tokens?|auth[ _-]?tokens?|refresh[ _-]?tokens?|private[ _-]?keys?|tokens?|credentials?|creds)";
3039
+ var NAME_SEP = /(?<![A-Za-z0-9_])([A-Za-z][A-Za-z0-9_-]{1,79})(["']?\s{0,3}(?::=|=>|=|:)\s{0,3})/g;
3040
+ var VALUE = /"([^"\n]{1,300})"|'([^'\n]{1,300})'|`([^`\n]{1,300})`|([^\s"'`,;&)}\]]{1,300})/y;
3041
+ function redactAssignments(text) {
3042
+ let out = "";
3043
+ let last = 0;
3044
+ NAME_SEP.lastIndex = 0;
3045
+ for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
3046
+ const name = m[1];
3047
+ if (!isSecretName(name)) continue;
3048
+ VALUE.lastIndex = NAME_SEP.lastIndex;
3049
+ const v = VALUE.exec(text);
3050
+ if (!v) continue;
3051
+ const value = v[1] ?? v[2] ?? v[3] ?? v[4];
3052
+ NAME_SEP.lastIndex = VALUE.lastIndex;
3053
+ if (isPlaceholder(value) || value.includes(REDACTED)) continue;
3054
+ if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
3055
+ const q = v[1] != null ? '"' : v[2] != null ? "'" : v[3] != null ? "`" : "";
3056
+ out += text.slice(last, v.index) + q + REDACTED + q;
3057
+ last = VALUE.lastIndex;
3058
+ }
3059
+ return last ? out + text.slice(last) : text;
3060
+ }
3061
+ function isSecretName(name) {
3062
+ if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
3063
+ const flat = name.toLowerCase().replace(/[_-]/g, "");
3064
+ return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
3065
+ flat
3066
+ );
3067
+ }
3068
+ var PROSE = new RegExp(
3069
+ `(\\b${SECRET_WORD}(?:[ \\t]{1,3}(?:is|was|=|:|to|as|of|for[ \\t]{1,3}\\S{1,40}[ \\t]{1,3}is))?[ \\t]{1,3})(["'\`]?)([^\\s"'\`]{6,200})\\2`,
3070
+ "gi"
3071
+ );
3072
+ function isPlaceholder(v) {
3073
+ const s = v.replace(/[.,;:!?]+$/, "");
3074
+ if (!s) return true;
3075
+ if (/^(?:string|number|boolean|bool|str|int|any|unknown|null|undefined|none|nil|true|false|optional|required|text|varchar|password|secret|token|env|string\[\]|object|bytes)$/i.test(
3076
+ s
3077
+ ))
3078
+ return true;
3079
+ if (/^\[?redacted\]?$/i.test(s) || /^\*+$/.test(s) || /^x{3,}$/i.test(s) || /^\.{3}$|^…$/.test(s)) return true;
3080
+ if (/^[<{$%]/.test(s)) return true;
3081
+ if (/^(?:process\.env|env|os\.environ|c\.env|this\.env|import\.meta\.env|getenv|config|settings|secrets|args|opts|options|req|request|body|params|input|data|user|self|this)\b/i.test(
3082
+ s
3083
+ ))
3084
+ return true;
3085
+ if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(s)) return true;
3086
+ if (/[(]/.test(s)) return true;
3087
+ if (/^(?:your|my|the|a|an|some|new|old)[-_ ]/i.test(s)) return true;
3088
+ return false;
3089
+ }
3090
+ function looksSecret(v) {
3091
+ const s = v.replace(/[.,;:!?)]+$/, "");
3092
+ if (s.length < 6) return false;
3093
+ if (isPlaceholder(s)) return false;
3094
+ if (/^https?:\/\//i.test(s)) return false;
3095
+ if (/^[a-z]+(?:[-'][a-z]+)*$/.test(s)) return false;
3096
+ if (/^[A-Z][a-z]+$/.test(s)) return false;
3097
+ if (/^\d+(?:[.,]\d+)*[a-z%]*$/i.test(s)) return false;
3098
+ if (/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(s)) return false;
3099
+ const kinds = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((r) => r.test(s)).length;
3100
+ return kinds >= 3 || /[0-9]/.test(s) && /[A-Za-z]/.test(s);
3101
+ }
3102
+ function redactSecrets(text) {
3103
+ if (!text) return text;
3104
+ let out = text;
3105
+ for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
3106
+ out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
3107
+ out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
3108
+ out = redactAssignments(out);
3109
+ out = out.replace(
3110
+ PROSE,
3111
+ (m, head, q, v) => !looksSecret(v) || v.includes(REDACTED) ? m : `${head}${q}${REDACTED}${q}`
3112
+ );
3113
+ return out;
3114
+ }
3115
+ function hasSecret(text) {
3116
+ return !!text && redactSecrets(text) !== text;
3117
+ }
3118
+ function redactDeep(v) {
3119
+ if (typeof v === "string") return redactSecrets(v);
3120
+ if (Array.isArray(v)) return v.map(redactDeep);
3121
+ if (v && typeof v === "object") {
3122
+ const out = {};
3123
+ for (const [k, x] of Object.entries(v)) out[k] = redactDeep(x);
3124
+ return out;
3125
+ }
3126
+ return v;
3127
+ }
3128
+
2754
3129
  // src/adapters/claude-code-control.ts
2755
3130
  var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2756
3131
  var LIVE_WRITE_MS = 9e4;
@@ -2761,6 +3136,29 @@ var cmpVersion = (a, b) => {
2761
3136
  if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
2762
3137
  return 0;
2763
3138
  };
3139
+ function versionManagerBins(env = process.env, home = os5.homedir()) {
3140
+ const out = [];
3141
+ const versions = (dir, tail) => {
3142
+ let names = [];
3143
+ try {
3144
+ names = readdirSync4(dir).filter((n) => /^v?\d+(\.\d+)*$/.test(n));
3145
+ } catch {
3146
+ return;
3147
+ }
3148
+ for (const n of names.sort((a, b) => cmpVersion(a.replace(/^v/, ""), b.replace(/^v/, ""))).reverse())
3149
+ out.push(path10.join(dir, n, ...tail));
3150
+ };
3151
+ versions(path10.join(env.NVM_DIR || path10.join(home, ".nvm"), "versions", "node"), ["bin"]);
3152
+ const fnmDirs = env.FNM_DIR ? [env.FNM_DIR] : [
3153
+ path10.join(home, "Library", "Application Support", "fnm"),
3154
+ path10.join(home, ".local", "share", "fnm"),
3155
+ path10.join(home, ".fnm")
3156
+ ];
3157
+ for (const d of fnmDirs) versions(path10.join(d, "node-versions"), ["installation", "bin"]);
3158
+ out.push(path10.join(env.VOLTA_HOME || path10.join(home, ".volta"), "bin"));
3159
+ versions(path10.join(env.ASDF_DATA_DIR || path10.join(home, ".asdf"), "installs", "nodejs"), ["bin"]);
3160
+ return out;
3161
+ }
2764
3162
  function findClaude(env = process.env, home = os5.homedir()) {
2765
3163
  if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
2766
3164
  const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
@@ -2775,6 +3173,7 @@ function findClaude(env = process.env, home = os5.homedir()) {
2775
3173
  "/usr/local/bin/claude"
2776
3174
  ])
2777
3175
  if (isExe(p)) return p;
3176
+ for (const d of versionManagerBins(env, home)) if (isExe(path10.join(d, "claude"))) return path10.join(d, "claude");
2778
3177
  const app = path10.join(home, "Library", "Application Support", "Claude", "claude-code");
2779
3178
  let versions = [];
2780
3179
  try {
@@ -2919,6 +3318,23 @@ function loginHint(configDir, platform = process.platform) {
2919
3318
  return `Claude Code (${shown}) isn't signed in as this machine's background service sees it. On a Mac its login lives in the login keychain, which a background service can read only while someone is logged in at the screen: log in at the Mac, or run ${login} there.`;
2920
3319
  return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
2921
3320
  }
3321
+ var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`, "g");
3322
+ function helpFailure(err, home = os5.homedir()) {
3323
+ if (err?.code === "ENOENT") return "failed: no such file";
3324
+ if (err?.code === "EACCES") return "failed: the file can't be run (permission denied)";
3325
+ const plain2 = Array.from(String(err?.stderr ?? "").replace(ANSI, "")).filter((ch) => {
3326
+ const c = ch.charCodeAt(0);
3327
+ return c === 10 || c >= 32 && c !== 127;
3328
+ }).join("");
3329
+ const line = plain2.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).find((l) => l.length > 0);
3330
+ if (line) {
3331
+ const said = redactSecrets(reducePii(line, home));
3332
+ return `failed: ${said.length > 200 ? `${said.slice(0, 199)}\u2026` : said}`;
3333
+ }
3334
+ if (err?.signal) return `failed: killed by ${err.signal}`;
3335
+ if (typeof err?.status === "number") return `failed with exit code ${err.status}`;
3336
+ return "failed";
3337
+ }
2922
3338
  function detectCaps(bin, timeoutMs = 3e4) {
2923
3339
  const c = probe(bin, ["--help"]);
2924
3340
  try {
@@ -2936,7 +3352,7 @@ function detectCaps(bin, timeoutMs = 3e4) {
2936
3352
  const err = e;
2937
3353
  const caps = parseCaps(String(err?.stdout || ""));
2938
3354
  if (caps.hasMode) return caps;
2939
- const why = err?.code === "ETIMEDOUT" ? `timed out after ${timeoutMs / 1e3} s` : "failed";
3355
+ const why = err?.code === "ETIMEDOUT" ? `timed out after ${timeoutMs / 1e3} s` : helpFailure(err);
2940
3356
  return { ...caps, unread: `\`${path10.basename(bin)} --help\` ${why}` };
2941
3357
  }
2942
3358
  }
@@ -3157,16 +3573,16 @@ function relaunchMode(recorded, home) {
3157
3573
  return recorded;
3158
3574
  return null;
3159
3575
  }
3160
- function recentlyWritten(file, ourLastEnd, now = Date.now()) {
3576
+ function recentlyWritten(file, knownEnd, now = Date.now()) {
3161
3577
  let m;
3162
3578
  try {
3163
3579
  m = statSync5(file).mtimeMs;
3164
3580
  } catch {
3165
3581
  return false;
3166
3582
  }
3167
- if (now - m > LIVE_WRITE_MS) return false;
3168
- return !(ourLastEnd && m <= ourLastEnd + 2e3);
3583
+ return recentWrite(m, knownEnd, now);
3169
3584
  }
3585
+ var recentWrite = (written, knownEnd, now) => now - written <= LIVE_WRITE_MS && !(knownEnd && written <= knownEnd + 2e3);
3170
3586
  function runClaude(bin, args, o) {
3171
3587
  return runHeadless(bin, args, { ...o, keep: o.keep ?? ((line) => line.includes('"type":"result"')) });
3172
3588
  }
@@ -3195,6 +3611,15 @@ function parseResult(stdout) {
3195
3611
  return null;
3196
3612
  }
3197
3613
  var isUuid = (s) => typeof s === "string" && UUID_RE2.test(s);
3614
+ function initSession(line) {
3615
+ if (!line.includes('"init"')) return null;
3616
+ try {
3617
+ const j = JSON.parse(line);
3618
+ return j.type === "system" && j.subtype === "init" && isUuid(j.session_id) ? j.session_id : null;
3619
+ } catch {
3620
+ return null;
3621
+ }
3622
+ }
3198
3623
 
3199
3624
  // src/adapters/codex.ts
3200
3625
  import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
@@ -3884,7 +4309,7 @@ function codexStderr(stderr) {
3884
4309
  var reading2 = () => new CodexReading();
3885
4310
 
3886
4311
  // src/adapters/devin.ts
3887
- import { createHash as createHash3 } from "node:crypto";
4312
+ import { createHash as createHash4 } from "node:crypto";
3888
4313
  import { existsSync as existsSync6, readlinkSync } from "node:fs";
3889
4314
  import path13 from "node:path";
3890
4315
 
@@ -4125,7 +4550,7 @@ var devin = {
4125
4550
  const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
4126
4551
  const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
4127
4552
  const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
4128
- const permissionId = () => `perm-${createHash3("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
4553
+ const permissionId = () => `perm-${createHash4("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
4129
4554
  const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
4130
4555
  const events = [];
4131
4556
  switch (event) {
@@ -4946,201 +5371,6 @@ function processAlive(pid) {
4946
5371
  }
4947
5372
  }
4948
5373
 
4949
- // src/privacy/pii.ts
4950
- import { createHash as createHash4 } from "node:crypto";
4951
- var RULESET_PII = "pii@1";
4952
- var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
4953
- var PHONE = /(?<![\w.])(?:(?<!\)[ .-]?|\+\d{1,3}[ .-]?)(\+?(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01]))(?:(?= (?:[01]?\d|2[0-3]):)|(?!\w|[ .-]\d\d))|(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w))/g;
4954
- function looksPhone(s) {
4955
- const digits = s.replace(/\D/g, "");
4956
- if (digits.length < 7 || digits.length > 15) return false;
4957
- if (!/[+() .-]/.test(s)) return false;
4958
- if (!/[+() -]/.test(s)) return false;
4959
- return true;
4960
- }
4961
- function reducePii(text, home) {
4962
- if (!text) return text;
4963
- let out = text.replace(EMAIL, "[email]");
4964
- out = out.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
4965
- if (home) {
4966
- const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4967
- out = out.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
4968
- }
4969
- return out;
4970
- }
4971
- function hashMachine(name) {
4972
- return createHash4("sha256").update(name).digest("hex").slice(0, 8);
4973
- }
4974
- function reducePiiDeep(v, home) {
4975
- if (typeof v === "string") return reducePii(v, home);
4976
- if (Array.isArray(v)) return v.map((x) => reducePiiDeep(x, home));
4977
- if (v && typeof v === "object") {
4978
- const out = {};
4979
- for (const [k, x] of Object.entries(v)) out[k] = reducePiiDeep(x, home);
4980
- return out;
4981
- }
4982
- return v;
4983
- }
4984
-
4985
- // src/privacy/secrets.ts
4986
- var RULESET_SECRETS = "secrets@1";
4987
- var REDACTED = "[redacted]";
4988
- var TOKEN_PATTERNS = [
4989
- /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/g,
4990
- /\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g,
4991
- // AWS access key id
4992
- /\bgh[pousr]_[A-Za-z0-9]{30,}\b/g,
4993
- // GitHub
4994
- /\bgithub_pat_[A-Za-z0-9_]{30,}\b/g,
4995
- /\bglpat-[A-Za-z0-9_-]{20,}\b/g,
4996
- // GitLab
4997
- /\bxox[abposre]-[A-Za-z0-9-]{10,}\b/g,
4998
- // Slack
4999
- /\bhttps:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]+/g,
5000
- /\bhttps:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+/g,
5001
- /\bsk-(?:ant-|proj-|svcacct-|admin-|or-v1-)?[A-Za-z0-9_-]{20,}\b/g,
5002
- // Anthropic, OpenAI, DeepSeek, OpenRouter
5003
- /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g,
5004
- // Stripe secret / restricted
5005
- /\bwhsec_[A-Za-z0-9+/=]{20,}/g,
5006
- // Stripe / Svix / Standard Webhooks secret
5007
- /\bAIza[0-9A-Za-z_-]{35}\b/g,
5008
- // Google API key
5009
- /\bya29\.[0-9A-Za-z_-]{20,}/g,
5010
- // Google OAuth access token
5011
- /\b1\/\/0[0-9A-Za-z_-]{30,}/g,
5012
- // Google OAuth refresh token
5013
- /\bGOCSPX-[0-9A-Za-z_-]{20,}/g,
5014
- // Google OAuth client secret
5015
- /\bxai-[A-Za-z0-9]{30,}\b/g,
5016
- /\bgsk_[A-Za-z0-9]{30,}\b/g,
5017
- // Groq
5018
- /\bpplx-[A-Za-z0-9]{30,}\b/g,
5019
- // Perplexity
5020
- /\bhf_[A-Za-z0-9]{30,}\b/g,
5021
- // Hugging Face
5022
- /\bpcsk_[A-Za-z0-9_]{30,}\b/g,
5023
- // Pinecone
5024
- /\bnpm_[A-Za-z0-9]{30,}\b/g,
5025
- /\bpypi-[A-Za-z0-9_-]{50,}/g,
5026
- /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g,
5027
- // SendGrid
5028
- /\bshp(?:at|ss|ca|pa)_[a-fA-F0-9]{32}\b/g,
5029
- // Shopify
5030
- /\bdop_v1_[a-f0-9]{64}\b/g,
5031
- // DigitalOcean
5032
- /\b(?:lin_api|lin_oauth)_[A-Za-z0-9]{30,}\b/g,
5033
- // Linear
5034
- /\b(?:ntn|secret)_[A-Za-z0-9]{40,}\b/g,
5035
- // Notion
5036
- /\bsbp_[a-f0-9]{40}\b/g,
5037
- // Supabase
5038
- /\bfigd_[A-Za-z0-9_-]{30,}/g,
5039
- // Figma
5040
- /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9]{30,}/g,
5041
- // Doppler
5042
- /\bgrn_[A-Za-z0-9_-]{20,}/g,
5043
- // Granola
5044
- /\bss_[a-f0-9]{32,}\b/g,
5045
- // spacesheep API keys
5046
- /\b[0-9]{8,10}:AA[A-Za-z0-9_-]{33}\b/g,
5047
- // Telegram bot token
5048
- /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
5049
- // JWT
5050
- ];
5051
- var URL_CREDENTIALS = /\b([a-z][a-z0-9+.-]{1,20}:\/\/[^\s:/@'"`<>]{1,100}:)([^\s@/'"`<>]{1,200})(@)/gi;
5052
- var AUTH_HEADER = /\b((?:Bearer|Basic|Token)\s+)([A-Za-z0-9._~+/=-]{16,})/g;
5053
- var SECRET_WORD = "(?:pass(?:word|wd|phrase|code)?s?|pwd|secret(?:[ _-]?key)?s?|client[ _-]?secret|api[ _-]?keys?|apikeys?|access[ _-]?keys?|access[ _-]?tokens?|auth[ _-]?tokens?|refresh[ _-]?tokens?|private[ _-]?keys?|tokens?|credentials?|creds)";
5054
- var NAME_SEP = /(?<![A-Za-z0-9_])([A-Za-z][A-Za-z0-9_-]{1,79})(["']?\s{0,3}(?::=|=>|=|:)\s{0,3})/g;
5055
- var VALUE = /"([^"\n]{1,300})"|'([^'\n]{1,300})'|`([^`\n]{1,300})`|([^\s"'`,;&)}\]]{1,300})/y;
5056
- function redactAssignments(text) {
5057
- let out = "";
5058
- let last = 0;
5059
- NAME_SEP.lastIndex = 0;
5060
- for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
5061
- const name = m[1];
5062
- if (!isSecretName(name)) continue;
5063
- VALUE.lastIndex = NAME_SEP.lastIndex;
5064
- const v = VALUE.exec(text);
5065
- if (!v) continue;
5066
- const value = v[1] ?? v[2] ?? v[3] ?? v[4];
5067
- NAME_SEP.lastIndex = VALUE.lastIndex;
5068
- if (isPlaceholder(value) || value.includes(REDACTED)) continue;
5069
- if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
5070
- const q = v[1] != null ? '"' : v[2] != null ? "'" : v[3] != null ? "`" : "";
5071
- out += text.slice(last, v.index) + q + REDACTED + q;
5072
- last = VALUE.lastIndex;
5073
- }
5074
- return last ? out + text.slice(last) : text;
5075
- }
5076
- function isSecretName(name) {
5077
- if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
5078
- const flat = name.toLowerCase().replace(/[_-]/g, "");
5079
- return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
5080
- flat
5081
- );
5082
- }
5083
- var PROSE = new RegExp(
5084
- `(\\b${SECRET_WORD}(?:[ \\t]{1,3}(?:is|was|=|:|to|as|of|for[ \\t]{1,3}\\S{1,40}[ \\t]{1,3}is))?[ \\t]{1,3})(["'\`]?)([^\\s"'\`]{6,200})\\2`,
5085
- "gi"
5086
- );
5087
- function isPlaceholder(v) {
5088
- const s = v.replace(/[.,;:!?]+$/, "");
5089
- if (!s) return true;
5090
- if (/^(?:string|number|boolean|bool|str|int|any|unknown|null|undefined|none|nil|true|false|optional|required|text|varchar|password|secret|token|env|string\[\]|object|bytes)$/i.test(
5091
- s
5092
- ))
5093
- return true;
5094
- if (/^\[?redacted\]?$/i.test(s) || /^\*+$/.test(s) || /^x{3,}$/i.test(s) || /^\.{3}$|^…$/.test(s)) return true;
5095
- if (/^[<{$%]/.test(s)) return true;
5096
- if (/^(?:process\.env|env|os\.environ|c\.env|this\.env|import\.meta\.env|getenv|config|settings|secrets|args|opts|options|req|request|body|params|input|data|user|self|this)\b/i.test(
5097
- s
5098
- ))
5099
- return true;
5100
- if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(s)) return true;
5101
- if (/[(]/.test(s)) return true;
5102
- if (/^(?:your|my|the|a|an|some|new|old)[-_ ]/i.test(s)) return true;
5103
- return false;
5104
- }
5105
- function looksSecret(v) {
5106
- const s = v.replace(/[.,;:!?)]+$/, "");
5107
- if (s.length < 6) return false;
5108
- if (isPlaceholder(s)) return false;
5109
- if (/^https?:\/\//i.test(s)) return false;
5110
- if (/^[a-z]+(?:[-'][a-z]+)*$/.test(s)) return false;
5111
- if (/^[A-Z][a-z]+$/.test(s)) return false;
5112
- if (/^\d+(?:[.,]\d+)*[a-z%]*$/i.test(s)) return false;
5113
- if (/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(s)) return false;
5114
- const kinds = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((r) => r.test(s)).length;
5115
- return kinds >= 3 || /[0-9]/.test(s) && /[A-Za-z]/.test(s);
5116
- }
5117
- function redactSecrets(text) {
5118
- if (!text) return text;
5119
- let out = text;
5120
- for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
5121
- out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
5122
- out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
5123
- out = redactAssignments(out);
5124
- out = out.replace(
5125
- PROSE,
5126
- (m, head, q, v) => !looksSecret(v) || v.includes(REDACTED) ? m : `${head}${q}${REDACTED}${q}`
5127
- );
5128
- return out;
5129
- }
5130
- function hasSecret(text) {
5131
- return !!text && redactSecrets(text) !== text;
5132
- }
5133
- function redactDeep(v) {
5134
- if (typeof v === "string") return redactSecrets(v);
5135
- if (Array.isArray(v)) return v.map(redactDeep);
5136
- if (v && typeof v === "object") {
5137
- const out = {};
5138
- for (const [k, x] of Object.entries(v)) out[k] = redactDeep(x);
5139
- return out;
5140
- }
5141
- return v;
5142
- }
5143
-
5144
5374
  // src/privacy/filter.ts
5145
5375
  var TYPE_TIER = {
5146
5376
  "session.started": 0,
@@ -5346,6 +5576,7 @@ export {
5346
5576
  antigravity_control_exports as antigravityControl,
5347
5577
  assertConfigWritable,
5348
5578
  capTail,
5579
+ claudeAccountDirs,
5349
5580
  claudeAccounts,
5350
5581
  claudeCode,
5351
5582
  claude_code_control_exports as claudeControl,