pushary 1.9.29 → 1.9.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.
Files changed (104) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/data/SKILL.md +46 -12
  3. package/data/cursor-plugin/skills/pushary/SKILL.md +46 -12
  4. package/data/vscode-plugin/skills/pushary/SKILL.md +46 -12
  5. package/dist/bin/pushary-bell-hook.js +26 -26
  6. package/dist/bin/pushary-bell.js +9 -9
  7. package/dist/bin/pushary-claude.js +30 -27
  8. package/dist/bin/pushary-clean.js +29 -29
  9. package/dist/bin/pushary-codex-bridge.js +17 -17
  10. package/dist/bin/pushary-codex-hook.js +41 -40
  11. package/dist/bin/pushary-codex.js +12 -12
  12. package/dist/bin/pushary-connect.js +24 -24
  13. package/dist/bin/pushary-cowork.js +5 -5
  14. package/dist/bin/pushary-daemon.js +33 -33
  15. package/dist/bin/pushary-disconnect.js +24 -24
  16. package/dist/bin/pushary-doctor.js +33 -33
  17. package/dist/bin/pushary-elicitation-hook.js +26 -26
  18. package/dist/bin/pushary-gemini-bridge.js +13 -13
  19. package/dist/bin/pushary-gemini-hook.js +28 -28
  20. package/dist/bin/pushary-hook.js +30 -30
  21. package/dist/bin/pushary-login.js +12 -12
  22. package/dist/bin/pushary-logout.js +24 -24
  23. package/dist/bin/pushary-mcp.js +9 -9
  24. package/dist/bin/pushary-mode.js +12 -12
  25. package/dist/bin/pushary-notification-hook.js +25 -25
  26. package/dist/bin/pushary-opencode-hook.js +27 -27
  27. package/dist/bin/pushary-permission-denied-hook.js +30 -30
  28. package/dist/bin/pushary-permission-hook.js +29 -29
  29. package/dist/bin/pushary-post-hook.js +25 -25
  30. package/dist/bin/pushary-prompt-hook.js +25 -25
  31. package/dist/bin/pushary-session-end-hook.js +25 -25
  32. package/dist/bin/pushary-session-start-hook.js +29 -29
  33. package/dist/bin/pushary-setup.js +35 -35
  34. package/dist/bin/pushary-stats.js +5 -5
  35. package/dist/bin/pushary-status.js +26 -26
  36. package/dist/bin/pushary-stop-hook.js +25 -25
  37. package/dist/bin/pushary-stopfailure-hook.js +25 -25
  38. package/dist/bin/pushary-suggestions.js +12 -12
  39. package/dist/bin/pushary-transcript-register.js +18 -18
  40. package/dist/bin/pushary-transcripts.js +8 -8
  41. package/dist/bin/pushary-upgrade.js +30 -30
  42. package/dist/bin/pushary-wait.js +12 -12
  43. package/dist/bin/pushary.js +6 -6
  44. package/dist/{chunk-QOMUEL44.js → chunk-2ROXUEZV.js} +1 -1
  45. package/dist/{chunk-WJUYWTNT.js → chunk-35RJKPIC.js} +1 -1
  46. package/dist/{chunk-SLKVEJLM.js → chunk-3EIJDBVB.js} +5 -5
  47. package/dist/{chunk-Y3OZ3CT6.js → chunk-464FX6W5.js} +6 -5
  48. package/dist/{chunk-KMTSU366.js → chunk-4JOWKPUF.js} +1 -1
  49. package/dist/{chunk-35CMXPES.js → chunk-4WGKAGIU.js} +3 -3
  50. package/dist/{chunk-TXKAPRIA.js → chunk-5EX2SD5N.js} +1 -1
  51. package/dist/{chunk-WLDFMVRY.js → chunk-5F32ZTWD.js} +2 -2
  52. package/dist/{chunk-NRV5PIPA.js → chunk-5FNE6ZVV.js} +2 -2
  53. package/dist/{chunk-L7PGUSAU.js → chunk-5GDNIEL5.js} +1 -1
  54. package/dist/{chunk-OUWDJIJF.js → chunk-7447DYNW.js} +1 -1
  55. package/dist/{chunk-5L7H32JK.js → chunk-75UCMLT5.js} +1 -1
  56. package/dist/{chunk-7MYGOFVX.js → chunk-77J4RVB2.js} +4 -4
  57. package/dist/{chunk-CCI2UBYR.js → chunk-7S4Z36H7.js} +3 -3
  58. package/dist/{chunk-LSB66XQC.js → chunk-ALSIMOFS.js} +4 -4
  59. package/dist/{chunk-M2L4EHYW.js → chunk-AXANUGOV.js} +2 -2
  60. package/dist/{chunk-Z2WS466S.js → chunk-BMUTA66M.js} +15 -13
  61. package/dist/{chunk-XH7NMH3Q.js → chunk-CJCVPCOB.js} +2 -2
  62. package/dist/{chunk-PRVG3COH.js → chunk-D3TVRNZC.js} +2 -2
  63. package/dist/{chunk-CPZZTS4I.js → chunk-DP2FAIW6.js} +1 -1
  64. package/dist/{chunk-VTR4VRL2.js → chunk-DWQYCOPI.js} +2 -4
  65. package/dist/{chunk-LRLF6PZ2.js → chunk-EPVY3JU3.js} +1 -1
  66. package/dist/{chunk-GVIPEBEL.js → chunk-FEICGRNI.js} +3 -3
  67. package/dist/{chunk-C2ZIVQ6W.js → chunk-FGFFUCEL.js} +6 -6
  68. package/dist/{chunk-TJXCBTL4.js → chunk-FXJ5SEQT.js} +5 -5
  69. package/dist/{chunk-G4X3Z3YS.js → chunk-GROYT52D.js} +4 -4
  70. package/dist/{chunk-2FOAQ64R.js → chunk-GSBOLJZ4.js} +177 -56
  71. package/dist/{chunk-UW37WHVP.js → chunk-H2PQXK26.js} +2 -2
  72. package/dist/{chunk-BUOLA2AM.js → chunk-IDGESFKZ.js} +1 -1
  73. package/dist/{chunk-BAVNBGH4.js → chunk-IIP4LEO3.js} +7 -7
  74. package/dist/{chunk-GMVZYB3W.js → chunk-IKJJB4QG.js} +3 -3
  75. package/dist/{chunk-S3DP6DCZ.js → chunk-KBRKQMCI.js} +2 -2
  76. package/dist/{chunk-AZMHKUUU.js → chunk-MG523AVN.js} +1 -1
  77. package/dist/{chunk-AVQ6ETA7.js → chunk-MRR4NQ3H.js} +1 -1
  78. package/dist/{chunk-ETLVZHRG.js → chunk-N4LLBUU2.js} +2 -2
  79. package/dist/{chunk-YFEHKEGQ.js → chunk-NTWTU6FA.js} +1 -1
  80. package/dist/{chunk-UM2HNUJA.js → chunk-OS6AE2MR.js} +3 -3
  81. package/dist/{chunk-DQGM57ZW.js → chunk-OSUAY4SA.js} +1 -1
  82. package/dist/{chunk-IWPEAA2J.js → chunk-OTDRNEED.js} +1 -1
  83. package/dist/{chunk-BP3WMLXJ.js → chunk-OULHOX5C.js} +5 -5
  84. package/dist/{chunk-4DTKE3AS.js → chunk-Q2GSTQ3R.js} +2 -2
  85. package/dist/{chunk-DUVHHQEL.js → chunk-Q7H2IMJQ.js} +2 -2
  86. package/dist/{chunk-PX4RRHEB.js → chunk-QCOE42GO.js} +4 -4
  87. package/dist/{chunk-BKOWTW5P.js → chunk-QGCEQGH6.js} +4 -4
  88. package/dist/{chunk-JGDBHB6L.js → chunk-RN3KZDWP.js} +2 -2
  89. package/dist/{chunk-BJMVQ6D5.js → chunk-SAHD5DYW.js} +4 -4
  90. package/dist/{chunk-62LJB5U3.js → chunk-SJJA2GPD.js} +2 -2
  91. package/dist/{chunk-IM73P6JT.js → chunk-SQIZGUAN.js} +5 -5
  92. package/dist/{chunk-CBOHZ6NY.js → chunk-SRRJLA24.js} +4 -4
  93. package/dist/{chunk-TILXDMUR.js → chunk-SYUHM6V3.js} +3 -3
  94. package/dist/{chunk-H7QTZTIE.js → chunk-TDLEFGAM.js} +1641 -178
  95. package/dist/{chunk-CJQEJWLU.js → chunk-VFNYTVTJ.js} +5 -5
  96. package/dist/{chunk-OSVDGVLL.js → chunk-X427XA76.js} +12 -12
  97. package/dist/{chunk-2XM4TGIM.js → chunk-X5HK4XUN.js} +4 -4
  98. package/dist/{chunk-EO2GYHWH.js → chunk-ZYDSHR5P.js} +3 -3
  99. package/dist/{global-upgrade-ZPULZXOY.js → global-upgrade-YMBTZHDP.js} +6 -6
  100. package/dist/{paths-Y7ZWWCFK.js → paths-BPFRUWMB.js} +2 -2
  101. package/dist/{reapply-I4YMQP7F.js → reapply-ZC2LSUVZ.js} +20 -20
  102. package/dist/src/index.js +19 -19
  103. package/dist/{upgrade-lock-IDAAFSH2.js → upgrade-lock-TTZMLZPB.js} +3 -3
  104. package/package.json +1 -1
@@ -3,46 +3,46 @@ import {
3
3
  loadPin,
4
4
  savePin,
5
5
  verifyRecipients
6
- } from "./chunk-VTR4VRL2.js";
6
+ } from "./chunk-DWQYCOPI.js";
7
7
  import {
8
8
  ensureDaemonRunning
9
- } from "./chunk-CJQEJWLU.js";
9
+ } from "./chunk-VFNYTVTJ.js";
10
10
  import {
11
11
  buildTranscriptRecords,
12
12
  decodePublicKey,
13
13
  generateSessionKey,
14
14
  wrapSessionKey
15
- } from "./chunk-OUWDJIJF.js";
15
+ } from "./chunk-7447DYNW.js";
16
16
  import {
17
17
  fetchTranscriptRecipients,
18
18
  uploadTranscriptRecords
19
- } from "./chunk-LRLF6PZ2.js";
19
+ } from "./chunk-EPVY3JU3.js";
20
20
  import {
21
21
  transcriptEnvironmentPreference,
22
22
  transcriptsEnabled
23
- } from "./chunk-AVQ6ETA7.js";
23
+ } from "./chunk-MRR4NQ3H.js";
24
24
  import {
25
25
  hookOwnerOf
26
- } from "./chunk-TJXCBTL4.js";
26
+ } from "./chunk-FXJ5SEQT.js";
27
27
  import {
28
28
  readTranscriptPreference
29
- } from "./chunk-GMVZYB3W.js";
29
+ } from "./chunk-IKJJB4QG.js";
30
30
  import {
31
31
  codexHome,
32
32
  pusharyDir
33
- } from "./chunk-CBOHZ6NY.js";
33
+ } from "./chunk-SRRJLA24.js";
34
34
  import {
35
35
  writeJsonAtomic
36
36
  } from "./chunk-AQFEP65J.js";
37
37
  import {
38
38
  getApiKey,
39
39
  getBaseUrl
40
- } from "./chunk-TXKAPRIA.js";
40
+ } from "./chunk-5EX2SD5N.js";
41
41
 
42
42
  // src/transcript-registration.ts
43
43
  import { createHash } from "crypto";
44
44
  import { readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3, unlinkSync } from "fs";
45
- import { isAbsolute, join as join3, resolve as resolve2, sep } from "path";
45
+ import { basename, isAbsolute, join as join3, resolve as resolve2, sep } from "path";
46
46
 
47
47
  // src/transcript-source.ts
48
48
  var normalizeTranscriptLines = (lines, source) => {
@@ -59,11 +59,42 @@ var normalizeTranscriptLines = (lines, source) => {
59
59
  }
60
60
  });
61
61
  };
62
+ var createdDuringEpoch = (sighting, epochStartedAtMs) => sighting.birthtimeMs !== null && sighting.birthtimeMs >= epochStartedAtMs && sighting.birthtimeMs <= sighting.seenAtMs;
63
+ var everythingAlreadyWritten = (sighting) => ({ identity: sighting.identity, offset: sighting.sizeBytes, startedBeforeEpoch: true });
64
+ var cutoffAtFirstSight = (sighting, epochStartedAtMs) => sighting.identity === null || createdDuringEpoch(sighting, epochStartedAtMs) ? { identity: sighting.identity, offset: 0, startedBeforeEpoch: false } : everythingAlreadyWritten(sighting);
65
+ var replacedOrShrunk = (previous, sighting) => previous.identity !== sighting.identity || sighting.sizeBytes < previous.offset;
66
+ var nextTranscriptCutoff = (previous, sighting, epochStartedAtMs) => {
67
+ if (!previous) return cutoffAtFirstSight(sighting, epochStartedAtMs);
68
+ if (sighting.identity === null) return previous;
69
+ if (previous.identity === null) return { ...previous, identity: sighting.identity };
70
+ return replacedOrShrunk(previous, sighting) ? everythingAlreadyWritten(sighting) : previous;
71
+ };
72
+ var advancedTranscriptCutoff = (current, readFrom, settledThroughOffset) => current !== void 0 && current.identity === readFrom.identity && settledThroughOffset > current.offset ? { ...current, offset: settledThroughOffset } : current;
73
+ var isTranscriptObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
74
+ var parsedTranscriptRecord = (line) => {
75
+ try {
76
+ const value = JSON.parse(line);
77
+ return isTranscriptObject(value) ? value : null;
78
+ } catch {
79
+ return null;
80
+ }
81
+ };
82
+ var stampedBefore = (record, momentMs) => {
83
+ if (typeof record.timestamp !== "string") return false;
84
+ const stamped = Date.parse(record.timestamp);
85
+ return Number.isFinite(stamped) && stamped < momentMs;
86
+ };
87
+ var isCompactionSummary = (record) => record.isCompactSummary === true;
88
+ var transcriptLinesSeenLive = (lines, epochStartedAtMs, startedBeforeEpoch) => lines.filter((line) => {
89
+ const record = parsedTranscriptRecord(line);
90
+ if (record === null) return true;
91
+ return !stampedBefore(record, epochStartedAtMs) && !(startedBeforeEpoch && isCompactionSummary(record));
92
+ });
62
93
 
63
94
  // src/transcript-sync.ts
64
95
  import { homedir as homedir2 } from "os";
65
96
  import { join as join2, resolve } from "path";
66
- import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
97
+ import { closeSync, existsSync as existsSync2, fstatSync, openSync, readFileSync as readFileSync2, readSync, statSync as statSync2 } from "fs";
67
98
 
68
99
  // src/transcript-session-key.ts
69
100
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
@@ -72,9 +103,9 @@ import { join } from "path";
72
103
  var sessionDir = () => process.env.PUSHARY_SESSION_KEY_DIR?.trim() || join(homedir(), ".pushary", "sessions");
73
104
  var SESSION_KEY_TTL_MS = 48 * 60 * 60 * 1e3;
74
105
  var SAFE_SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
75
- var fileFor = (sessionId) => SAFE_SESSION_ID.test(sessionId) ? join(sessionDir(), `${sessionId}.json`) : null;
76
- var loadSessionKey = (sessionId) => {
77
- const path = fileFor(sessionId);
106
+ var fileFor = (sessionId, directory) => SAFE_SESSION_ID.test(sessionId) ? join(directory, `${sessionId}.json`) : null;
107
+ var loadSessionKey = (sessionId, directory = sessionDir()) => {
108
+ const path = fileFor(sessionId, directory);
78
109
  if (!path || !existsSync(path)) return null;
79
110
  try {
80
111
  if (Date.now() - statSync(path).mtimeMs > SESSION_KEY_TTL_MS) return null;
@@ -92,11 +123,11 @@ var loadSessionKey = (sessionId) => {
92
123
  return null;
93
124
  }
94
125
  };
95
- var saveSessionKey = (sessionId, value) => {
96
- const path = fileFor(sessionId);
126
+ var saveSessionKey = (sessionId, value, directory = sessionDir()) => {
127
+ const path = fileFor(sessionId, directory);
97
128
  if (!path) return;
98
129
  try {
99
- mkdirSync(sessionDir(), { recursive: true, mode: 448 });
130
+ mkdirSync(directory, { recursive: true, mode: 448 });
100
131
  writeFileSync(
101
132
  path,
102
133
  JSON.stringify({
@@ -111,8 +142,7 @@ var saveSessionKey = (sessionId, value) => {
111
142
  } catch {
112
143
  }
113
144
  };
114
- var sweepStaleSessionKeys = (now = Date.now()) => {
115
- const dir = sessionDir();
145
+ var sweepStaleSessionKeys = (now = Date.now(), dir = sessionDir()) => {
116
146
  if (!existsSync(dir)) return 0;
117
147
  let removed = 0;
118
148
  try {
@@ -237,14 +267,75 @@ var readLines = (path) => {
237
267
  }
238
268
  };
239
269
  var IDLE_CEILING_MS = 6e4;
270
+ var recordedBirthtime = (info) => process.platform === "darwin" && info.birthtimeMs > 0 ? info.birthtimeMs : null;
271
+ var sightingOf = (info, seenAtMs) => info ? { identity: `${info.dev}:${info.ino}`, sizeBytes: info.size, birthtimeMs: recordedBirthtime(info), seenAtMs } : { identity: null, sizeBytes: 0, birthtimeMs: null, seenAtMs };
272
+ var storeCutoff = (epoch, path, cutoff) => {
273
+ epoch.cutoffs.set(path, cutoff);
274
+ epoch.positions.save(epoch.cutoffs);
275
+ };
276
+ var recordSighting = (epoch, path, sighting) => {
277
+ const previous = epoch.cutoffs.get(path);
278
+ const cutoff = nextTranscriptCutoff(previous, sighting, epoch.startedAtMs);
279
+ if (cutoff !== previous) storeCutoff(epoch, path, cutoff);
280
+ return cutoff;
281
+ };
282
+ var recordSettledPosition = (epoch, path, readFrom, settledThroughOffset) => {
283
+ const current = epoch.cutoffs.get(path);
284
+ const advanced = advancedTranscriptCutoff(current, readFrom, settledThroughOffset);
285
+ if (advanced !== void 0 && advanced !== current) storeCutoff(epoch, path, advanced);
286
+ };
287
+ var forgetUnregisteredTranscripts = (epoch, registeredPaths) => {
288
+ const unregistered = [...epoch.cutoffs.keys()].filter((path) => !registeredPaths.has(path));
289
+ if (unregistered.length === 0) return;
290
+ for (const path of unregistered) epoch.cutoffs.delete(path);
291
+ epoch.positions.save(epoch.cutoffs);
292
+ };
293
+ var statOrNull = (path) => {
294
+ try {
295
+ return statSync2(path);
296
+ } catch {
297
+ return null;
298
+ }
299
+ };
300
+ var sightTranscript = (epoch, path, seenAtMs) => recordSighting(epoch, path, sightingOf(statOrNull(path), seenAtMs));
301
+ var LINE_FEED = 10;
302
+ var nothingUnread = (cutoff) => ({ lines: [], cutoff, completeThroughOffset: cutoff.offset });
303
+ var readLinesSinceCutoff = (epoch, path, seenAtMs) => {
304
+ let descriptor;
305
+ try {
306
+ descriptor = openSync(path, "r");
307
+ } catch {
308
+ return nothingUnread(sightTranscript(epoch, path, seenAtMs));
309
+ }
310
+ try {
311
+ const info = fstatSync(descriptor);
312
+ const cutoff = recordSighting(epoch, path, sightingOf(info, seenAtMs));
313
+ const unread = info.size - cutoff.offset;
314
+ if (unread <= 0) return nothingUnread(cutoff);
315
+ const buffer = Buffer.alloc(unread);
316
+ const received = buffer.subarray(0, readSync(descriptor, buffer, 0, unread, cutoff.offset));
317
+ return {
318
+ lines: received.toString("utf8").split("\n"),
319
+ cutoff,
320
+ completeThroughOffset: cutoff.offset + received.lastIndexOf(LINE_FEED) + 1
321
+ };
322
+ } finally {
323
+ closeSync(descriptor);
324
+ }
325
+ };
326
+ var linesSeenDuringEpoch = (epoch, path, source, seenAtMs) => {
327
+ const read2 = readLinesSinceCutoff(epoch, path, seenAtMs);
328
+ return { ...read2, lines: transcriptLinesSeenLive(normalizeTranscriptLines(read2.lines, source), epoch.startedAtMs, read2.cutoff.startedBeforeEpoch) };
329
+ };
330
+ var nothingLeftToUpload = (state, lines) => state.sessionKey !== null && !state.keyRejected && buildTranscriptRecords(lines, state.sessionKey, state.seen).records.length === 0;
240
331
  var nextInterval = (previousMs, idle, baseMs, maxMs) => idle ? Math.min(previousMs * 2, maxMs) : baseMs;
241
332
  var startTranscriptSync = (opts) => {
242
333
  let warnedAboutRecipients = false;
243
334
  const fetchPinnedRecipients = async () => {
244
- const offer = await fetchTranscriptRecipients(opts.apiKey);
335
+ const offer = await fetchTranscriptRecipients(opts.apiKey, opts.baseUrl);
245
336
  if (!offer) return null;
246
- const verdict = verifyRecipients(offer, loadPin());
247
- if (verdict.kind === "first-use" && !savePin(verdict.pin)) return null;
337
+ const verdict = verifyRecipients(offer, loadPin(opts.storage?.recipientPinFile));
338
+ if (verdict.kind === "first-use" && !savePin(verdict.pin, opts.storage?.recipientPinFile)) return null;
248
339
  if (verdict.kind === "changed") {
249
340
  if (!warnedAboutRecipients) {
250
341
  warnedAboutRecipients = true;
@@ -257,12 +348,12 @@ var startTranscriptSync = (opts) => {
257
348
  };
258
349
  const uploader = {
259
350
  fetchTranscriptRecipients: fetchPinnedRecipients,
260
- uploadRecords: (params) => (opts.enabled ? opts.enabled() : transcriptsEnabled()) ? uploadTranscriptRecords(opts.apiKey, params) : Promise.resolve({ ok: false, keyRegistered: false, keyConflict: false, keyUnavailable: false, settledIds: null, rejectedIds: [] })
351
+ uploadRecords: (params) => (opts.enabled ? opts.enabled() : transcriptsEnabled()) ? uploadTranscriptRecords(opts.apiKey, params, opts.baseUrl) : Promise.resolve({ ok: false, keyRegistered: false, keyConflict: false, keyUnavailable: false, settledIds: null, rejectedIds: [] })
261
352
  };
262
353
  const path = opts.path ?? claudeTranscriptPath(opts.cwd, opts.sessionId);
263
- const persisted = loadSessionKey(opts.sessionId);
354
+ const persisted = loadSessionKey(opts.sessionId, opts.storage?.sessionKeyDirectory);
264
355
  let state = persisted ? { ...createSyncState(), ...persisted } : createSyncState();
265
- sweepStaleSessionKeys();
356
+ sweepStaleSessionKeys(Date.now(), opts.storage?.sessionKeyDirectory);
266
357
  let persistedUploaded = persisted?.keyUploaded ?? false;
267
358
  let running = true;
268
359
  let inFlight = false;
@@ -273,7 +364,9 @@ var startTranscriptSync = (opts) => {
273
364
  const before = state.sessionKey;
274
365
  const ending = opts.ending?.() === true;
275
366
  const modified = existsSync2(path) ? statSync2(path).mtimeMs : null;
276
- const lines = normalizeTranscriptLines(readLines(path), opts.source ?? "claude");
367
+ const source = opts.source ?? "claude";
368
+ const read2 = opts.epoch ? linesSeenDuringEpoch(opts.epoch, path, source, Date.now()) : null;
369
+ const lines = read2?.lines ?? normalizeTranscriptLines(readLines(path), source);
277
370
  state = await syncTick(lines, state, uploader, opts.sessionId, opts.machineId);
278
371
  if (state.sessionKey && (before !== state.sessionKey || state.keyUploaded !== persistedUploaded)) {
279
372
  persistedUploaded = state.keyUploaded;
@@ -283,9 +376,10 @@ var startTranscriptSync = (opts) => {
283
376
  providerWrappedKey: state.providerWrappedKey,
284
377
  providerKeyId: state.providerKeyId,
285
378
  keyUploaded: state.keyUploaded
286
- });
379
+ }, opts.storage?.sessionKeyDirectory);
287
380
  }
288
- if (ending && opts.ending?.() && existsSync2(path) && statSync2(path).mtimeMs === modified && state.keyUploaded && !state.keyRejected && buildTranscriptRecords(lines, state.sessionKey, state.seen).records.length === 0) opts.onDrained?.();
381
+ if (opts.epoch && read2 && nothingLeftToUpload(state, lines)) recordSettledPosition(opts.epoch, path, read2.cutoff, read2.completeThroughOffset);
382
+ if (ending && opts.ending?.() && existsSync2(path) && statSync2(path).mtimeMs === modified && state.keyUploaded && nothingLeftToUpload(state, lines)) opts.onDrained?.();
289
383
  } catch {
290
384
  } finally {
291
385
  inFlight = false;
@@ -316,17 +410,20 @@ var startTranscriptSync = (opts) => {
316
410
 
317
411
  // src/transcript-registration.ts
318
412
  import { homedir as homedir3 } from "os";
319
- var directory = () => process.env.PUSHARY_TRANSCRIPT_REGISTRY_DIR || join3(pusharyDir(), "transcript-capture");
320
- var accountFor = (apiKey) => createHash("sha256").update(`${getBaseUrl()}\0${apiKey}`).digest("hex");
413
+ var defaultRegistryDirectory = () => process.env.PUSHARY_TRANSCRIPT_REGISTRY_DIR || join3(pusharyDir(), "transcript-capture");
414
+ var cliRegistry = (apiKey = getApiKey()) => ({ apiKey, baseUrl: getBaseUrl(), directory: defaultRegistryDirectory() });
415
+ var accountFor = (registry) => createHash("sha256").update(`${registry.baseUrl}\0${registry.apiKey}`).digest("hex");
321
416
  var TTL = 48 * 60 * 60 * 1e3;
322
417
  var read = (path) => JSON.parse(readFileSync3(path, "utf8"));
323
418
  var consent = (record) => record.consentOverride ?? readTranscriptPreference() ?? false;
324
- var sameServer = (url) => url.replace(/\/+$/, "") === getBaseUrl().replace(/\/+$/, "");
325
- var fileFor2 = (apiKey, sessionId) => join3(directory(), `${createHash("sha256").update(accountFor(apiKey) + sessionId).digest("hex")}.json`);
326
- var permitted = (apiKey, raw) => raw.account === accountFor(apiKey) && consent(raw) && !raw.drained && (raw.source !== "claude" || !!raw.expectedSiteId || !macOwnsClaudeHooks(raw.cwd)) && (!raw.expectedSiteId || raw.expectedSiteId === raw.verifiedSiteId && !!raw.expectedBaseUrl && sameServer(raw.expectedBaseUrl));
419
+ var withoutTrailingSlash = (url) => url.replace(/\/+$/, "");
420
+ var sameServer = (url, registry) => withoutTrailingSlash(url) === withoutTrailingSlash(registry.baseUrl);
421
+ var fileFor2 = (registry, sessionId) => join3(registry.directory, `${createHash("sha256").update(accountFor(registry) + sessionId).digest("hex")}.json`);
422
+ var permitted = (registry, raw) => raw.account === accountFor(registry) && consent(raw) && !raw.drained && (raw.source !== "claude" || !!raw.expectedSiteId || !macOwnsClaudeHooks(raw.cwd)) && (!raw.expectedSiteId || raw.expectedSiteId === raw.verifiedSiteId && !!raw.expectedBaseUrl && sameServer(raw.expectedBaseUrl, registry));
327
423
  var registeredTranscriptEnabled = (apiKey, sessionId) => {
424
+ const registry = cliRegistry(apiKey);
328
425
  try {
329
- return permitted(apiKey, read(fileFor2(apiKey, sessionId)));
426
+ return permitted(registry, read(fileFor2(registry, sessionId)));
330
427
  } catch {
331
428
  return false;
332
429
  }
@@ -342,25 +439,29 @@ var macOwnsClaudeHooks = (cwd) => [
342
439
  return false;
343
440
  }
344
441
  });
442
+ var TRANSCRIPT_ROOT_NAME = { claude: "projects", codex: "sessions" };
345
443
  var transcriptRoots = (source) => {
346
444
  const home = homedir3();
347
- if (source === "codex") return [join3(codexHome(), "sessions")];
445
+ if (source === "codex") return [join3(codexHome(), TRANSCRIPT_ROOT_NAME.codex)];
348
446
  const configured = process.env.CLAUDE_CONFIG_DIR;
349
- return [join3(configured || join3(home, ".claude"), "projects"), join3(home, ".claude", "projects")];
447
+ return [join3(configured || join3(home, ".claude"), TRANSCRIPT_ROOT_NAME.claude), join3(home, ".claude", TRANSCRIPT_ROOT_NAME.claude)];
350
448
  };
351
- var withinTranscriptRoot = (candidate, source) => {
449
+ var isAgentTranscriptRoot = (root, source) => isAbsolute(root) && resolve2(root) === root && basename(root) === TRANSCRIPT_ROOT_NAME[source];
450
+ var acceptedTranscriptRoots = (source, registeredRoot) => typeof registeredRoot === "string" && isAgentTranscriptRoot(registeredRoot, source) ? [...transcriptRoots(source), registeredRoot] : transcriptRoots(source);
451
+ var transcriptRootContaining = (candidate, roots) => {
352
452
  const target = resolve2(candidate);
353
- return transcriptRoots(source).some((root) => target.startsWith(resolve2(root) + sep));
453
+ return roots.map((root) => resolve2(root)).find((root) => target.startsWith(root + sep)) ?? null;
354
454
  };
355
- var captureRegistration = (source, value) => {
455
+ var captureRegistration = (source, value, registeredRoot) => {
356
456
  const input = value;
357
457
  if (source !== "claude" && source !== "codex") return null;
358
458
  if (input.agent_id || typeof input.session_id !== "string" || !/^[\w-]{1,128}$/.test(input.session_id)) return null;
359
459
  if (typeof input.cwd !== "string" || !isAbsolute(input.cwd)) return null;
360
460
  const path = input.transcript_path ?? input.agent_transcript_path ?? (source === "claude" ? claudeTranscriptPath(input.cwd, input.session_id) : null);
361
461
  if (typeof path !== "string" || !isAbsolute(path) || !path.endsWith(".jsonl")) return null;
362
- if (!withinTranscriptRoot(path, source)) return null;
363
- return { sessionId: input.session_id, cwd: input.cwd, path, source };
462
+ const root = transcriptRootContaining(path, acceptedTranscriptRoots(source, registeredRoot));
463
+ if (root === null) return null;
464
+ return { sessionId: input.session_id, cwd: input.cwd, path, source, root };
364
465
  };
365
466
  var registerTranscript = (source, input, startDaemon = () => {
366
467
  void ensureDaemonRunning({ waitTimeoutMs: 0 }).catch(() => {
@@ -369,8 +470,9 @@ var registerTranscript = (source, input, startDaemon = () => {
369
470
  const registration = captureRegistration(source, input);
370
471
  if (!registration) return;
371
472
  try {
372
- const account = accountFor(getApiKey());
373
- const path = fileFor2(getApiKey(), registration.sessionId);
473
+ const registry = options.registry ?? cliRegistry();
474
+ const account = accountFor(registry);
475
+ const path = fileFor2(registry, registration.sessionId);
374
476
  let previous;
375
477
  try {
376
478
  previous = read(path);
@@ -381,7 +483,7 @@ var registerTranscript = (source, input, startDaemon = () => {
381
483
  const stored = {
382
484
  ...registration,
383
485
  account,
384
- consentOverride: transcriptEnvironmentPreference(),
486
+ consentOverride: options.consentOverride ?? transcriptEnvironmentPreference(),
385
487
  // A wrapper refresh must not erase the Mac's account binding.
386
488
  expectedSiteId: options.siteId ?? previous?.expectedSiteId,
387
489
  expectedBaseUrl: options.baseUrl ?? previous?.expectedBaseUrl,
@@ -394,19 +496,33 @@ var registerTranscript = (source, input, startDaemon = () => {
394
496
  } catch {
395
497
  }
396
498
  };
397
- var startRegisteredTranscripts = (apiKey, machineId, intervalMs = 3e3) => {
499
+ var sightRegisteredTranscripts = (epoch, registry, listing) => {
500
+ const seenAtMs = Date.now();
501
+ const registeredPaths = /* @__PURE__ */ new Set();
502
+ for (const raw of listing.records) {
503
+ const registration = captureRegistration(raw.source, { session_id: raw.sessionId, cwd: raw.cwd, transcript_path: raw.path }, raw.root);
504
+ if (!registration) continue;
505
+ registeredPaths.add(registration.path);
506
+ if (raw.account === accountFor(registry) && !raw.drained) sightTranscript(epoch, registration.path, seenAtMs);
507
+ }
508
+ if (listing.complete) forgetUnregisteredTranscripts(epoch, registeredPaths);
509
+ };
510
+ var startRegisteredTranscripts = (apiKey, machineId, options = {}) => {
511
+ const { intervalMs = 3e3, enabled = () => true, storage, epoch } = options;
512
+ const registry = { apiKey, baseUrl: options.baseUrl ?? getBaseUrl(), directory: options.registryDirectory ?? defaultRegistryDirectory() };
398
513
  const active = /* @__PURE__ */ new Map();
399
514
  let stopped = false;
400
515
  let scanning = false;
401
516
  let verifiedSiteId;
402
517
  let nextVerification = 0;
403
518
  const tick = async () => {
404
- if (stopped || scanning) return;
519
+ if (stopped || scanning || !enabled()) return;
405
520
  scanning = true;
406
521
  const wanted = /* @__PURE__ */ new Map();
407
522
  try {
408
- const files = readdirSync2(directory()).filter((name) => name.endsWith(".json")).flatMap((name) => {
409
- const path = join3(directory(), name);
523
+ let unreadable = 0;
524
+ const files = readdirSync2(registry.directory).filter((name) => name.endsWith(".json")).flatMap((name) => {
525
+ const path = join3(registry.directory, name);
410
526
  try {
411
527
  const time = statSync3(path).mtimeMs;
412
528
  if (Date.now() - time > TTL) {
@@ -415,16 +531,18 @@ var startRegisteredTranscripts = (apiKey, machineId, intervalMs = 3e3) => {
415
531
  }
416
532
  return [{ path, time, raw: read(path) }];
417
533
  } catch {
534
+ unreadable += 1;
418
535
  return [];
419
536
  }
420
537
  }).sort((a, b) => Number(!!a.raw.endedAt) - Number(!!b.raw.endedAt) || b.time - a.time);
538
+ if (epoch) sightRegisteredTranscripts(epoch, registry, { records: files.map((file) => file.raw), complete: unreadable === 0 });
421
539
  for (const file of files) {
422
540
  const raw = file.raw;
423
- if (raw.account !== accountFor(apiKey) || !consent(raw) || raw.drained) continue;
424
- if (raw.expectedSiteId && !verifiedSiteId && Date.now() >= nextVerification && raw.expectedBaseUrl && sameServer(raw.expectedBaseUrl)) {
541
+ if (raw.account !== accountFor(registry) || !consent(raw) || raw.drained) continue;
542
+ if (raw.expectedSiteId && !verifiedSiteId && Date.now() >= nextVerification && raw.expectedBaseUrl && sameServer(raw.expectedBaseUrl, registry)) {
425
543
  nextVerification = Date.now() + 3e4;
426
544
  try {
427
- const response = await fetch(`${getBaseUrl()}/api/agent/encryption-account`, {
545
+ const response = await fetch(`${registry.baseUrl}/api/agent/encryption-account`, {
428
546
  headers: { Authorization: `Bearer ${apiKey}` },
429
547
  signal: AbortSignal.timeout(5e3)
430
548
  });
@@ -433,18 +551,18 @@ var startRegisteredTranscripts = (apiKey, machineId, intervalMs = 3e3) => {
433
551
  } catch {
434
552
  }
435
553
  }
436
- if (raw.expectedSiteId && raw.expectedSiteId === verifiedSiteId && raw.expectedBaseUrl && sameServer(raw.expectedBaseUrl) && raw.verifiedSiteId !== verifiedSiteId) {
554
+ if (raw.expectedSiteId && raw.expectedSiteId === verifiedSiteId && raw.expectedBaseUrl && sameServer(raw.expectedBaseUrl, registry) && raw.verifiedSiteId !== verifiedSiteId) {
437
555
  const latest = read(file.path);
438
556
  if (JSON.stringify(latest) !== JSON.stringify(raw)) continue;
439
557
  raw.verifiedSiteId = verifiedSiteId;
440
558
  writeJsonAtomic(file.path, raw, 384);
441
559
  }
442
- if (!permitted(apiKey, raw)) continue;
560
+ if (!permitted(registry, raw)) continue;
443
561
  if (wanted.size >= (raw.endedAt ? 33 : 32)) continue;
444
- const registration = captureRegistration(raw.source, { session_id: raw.sessionId, cwd: raw.cwd, transcript_path: raw.path });
562
+ const registration = captureRegistration(raw.source, { session_id: raw.sessionId, cwd: raw.cwd, transcript_path: raw.path }, raw.root);
445
563
  if (registration) wanted.set(registration.sessionId, { ...registration, registrationPath: file.path });
446
564
  }
447
- if (stopped) return;
565
+ if (stopped || !enabled()) return;
448
566
  for (const [id, reader] of active) {
449
567
  if (wanted.get(id)?.path === reader.path) continue;
450
568
  reader.stop();
@@ -458,9 +576,12 @@ var startRegisteredTranscripts = (apiKey, machineId, intervalMs = 3e3) => {
458
576
  machineId,
459
577
  ...registration,
460
578
  intervalMs,
579
+ baseUrl: registry.baseUrl,
580
+ storage,
581
+ epoch,
461
582
  enabled: () => {
462
583
  try {
463
- return permitted(apiKey, current());
584
+ return !stopped && enabled() && permitted(registry, current());
464
585
  } catch {
465
586
  return false;
466
587
  }
@@ -3,11 +3,11 @@ import {
3
3
  decodePublicKey,
4
4
  generateSessionKey,
5
5
  wrapSessionKey
6
- } from "./chunk-OUWDJIJF.js";
6
+ } from "./chunk-7447DYNW.js";
7
7
  import {
8
8
  fetchTranscriptRecipients,
9
9
  uploadTranscriptRecords
10
- } from "./chunk-LRLF6PZ2.js";
10
+ } from "./chunk-EPVY3JU3.js";
11
11
 
12
12
  // src/live-session.ts
13
13
  var MAX_TRANSCRIPT_QUEUE = 100;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  guardBinary
3
- } from "./chunk-CBOHZ6NY.js";
3
+ } from "./chunk-SRRJLA24.js";
4
4
 
5
5
  // src/bell/install.ts
6
6
  import { join } from "path";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ensureCodexTrustsPusharyHooks
3
- } from "./chunk-4DTKE3AS.js";
3
+ } from "./chunk-Q2GSTQ3R.js";
4
4
  import {
5
5
  detectClaudeVersion
6
6
  } from "./chunk-RIVNNBEK.js";
@@ -12,7 +12,7 @@ import {
12
12
  openCodeHookArgv,
13
13
  openCodePluginDir,
14
14
  openCodeWired
15
- } from "./chunk-BP3WMLXJ.js";
15
+ } from "./chunk-OULHOX5C.js";
16
16
  import {
17
17
  addClaudeMcpServer,
18
18
  addCompatibleHooks,
@@ -20,7 +20,7 @@ import {
20
20
  addPusharyToolPermissions,
21
21
  compatibleConfigFile,
22
22
  hasCompatibleHooks
23
- } from "./chunk-BJMVQ6D5.js";
23
+ } from "./chunk-SAHD5DYW.js";
24
24
  import {
25
25
  claudeUnprovenHolds,
26
26
  claudeWired,
@@ -28,7 +28,7 @@ import {
28
28
  codexWired,
29
29
  geminiWired,
30
30
  planHooks
31
- } from "./chunk-TJXCBTL4.js";
31
+ } from "./chunk-FXJ5SEQT.js";
32
32
  import {
33
33
  CLAUDE_HOOK_BINARY,
34
34
  CLAUDE_HOOK_EVENTS,
@@ -39,7 +39,7 @@ import {
39
39
  needsReregistration,
40
40
  registrationSignatures,
41
41
  supportedClaudeEvents
42
- } from "./chunk-UM2HNUJA.js";
42
+ } from "./chunk-OS6AE2MR.js";
43
43
  import {
44
44
  formatVersion
45
45
  } from "./chunk-6ICPEJY5.js";
@@ -63,7 +63,7 @@ import {
63
63
  resolveGlobalBinDir,
64
64
  resolveGlobalBinary,
65
65
  vscodePluginMcp
66
- } from "./chunk-CBOHZ6NY.js";
66
+ } from "./chunk-SRRJLA24.js";
67
67
  import {
68
68
  readJsonSafe,
69
69
  writeFileAtomic,
@@ -73,7 +73,7 @@ import {
73
73
  HOOK_COMPATIBLE_PROFILES,
74
74
  isCompatibleAgentType,
75
75
  isRecord
76
- } from "./chunk-H7QTZTIE.js";
76
+ } from "./chunk-TDLEFGAM.js";
77
77
 
78
78
  // src/reapply.ts
79
79
  import { dirname, join } from "path";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveInstallSourceFor
3
- } from "./chunk-S3DP6DCZ.js";
3
+ } from "./chunk-KBRKQMCI.js";
4
4
  import {
5
5
  backupFile,
6
6
  readJsonSafe,
@@ -9,11 +9,11 @@ import {
9
9
  } from "./chunk-AQFEP65J.js";
10
10
  import {
11
11
  configFilePath
12
- } from "./chunk-TXKAPRIA.js";
12
+ } from "./chunk-5EX2SD5N.js";
13
13
  import {
14
14
  API_KEY_PREFIX_PATTERN,
15
15
  isRecord
16
- } from "./chunk-H7QTZTIE.js";
16
+ } from "./chunk-TDLEFGAM.js";
17
17
 
18
18
  // src/shell-alias.ts
19
19
  import { homedir } from "os";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  configFilePath
3
- } from "./chunk-TXKAPRIA.js";
3
+ } from "./chunk-5EX2SD5N.js";
4
4
  import {
5
5
  normalizeInstallSource
6
- } from "./chunk-H7QTZTIE.js";
6
+ } from "./chunk-TDLEFGAM.js";
7
7
 
8
8
  // src/install-source.ts
9
9
  import { readFileSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DEFAULT_SESSION
3
- } from "./chunk-OSVDGVLL.js";
3
+ } from "./chunk-X427XA76.js";
4
4
 
5
5
  // src/answer.ts
6
6
  var isDeferAnswer = (value) => value === "defer";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readTranscriptPreference
3
- } from "./chunk-GMVZYB3W.js";
3
+ } from "./chunk-IKJJB4QG.js";
4
4
 
5
5
  // src/transcript-setting.ts
6
6
  var transcriptEnvironmentPreference = () => {
@@ -4,10 +4,10 @@ import {
4
4
  keyPrefix,
5
5
  readConfigFileKey,
6
6
  readMacAppKeyPrefix
7
- } from "./chunk-GMVZYB3W.js";
7
+ } from "./chunk-IKJJB4QG.js";
8
8
  import {
9
9
  NPX_LATEST_COMMAND
10
- } from "./chunk-QOMUEL44.js";
10
+ } from "./chunk-2ROXUEZV.js";
11
11
 
12
12
  // src/key-signed-out.ts
13
13
  var VERDICT_BY_CHECK = {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-CBOHZ6NY.js";
3
+ } from "./chunk-SRRJLA24.js";
4
4
  import {
5
5
  writeFileAtomic
6
6
  } from "./chunk-AQFEP65J.js";
@@ -5,16 +5,16 @@ import {
5
5
  import {
6
6
  NOTIFICATION_MATCHED_TYPES,
7
7
  POSTTOOLUSE_MIRRORED_TOOLS
8
- } from "./chunk-KMTSU366.js";
8
+ } from "./chunk-4JOWKPUF.js";
9
9
  import {
10
10
  isProtectedByScope,
11
11
  registerHookEntry
12
- } from "./chunk-CBOHZ6NY.js";
12
+ } from "./chunk-SRRJLA24.js";
13
13
  import {
14
14
  CLAUDE_PRETOOLUSE_MATCHER,
15
15
  HOOK_BUDGETS,
16
16
  HOOK_LOCATOR_BINARY
17
- } from "./chunk-H7QTZTIE.js";
17
+ } from "./chunk-TDLEFGAM.js";
18
18
 
19
19
  // src/gemini-config.ts
20
20
  var GEMINI_HOOK_BINARY = "pushary-gemini-hook";
@@ -2,7 +2,7 @@ import {
2
2
  autoUpdateLock,
3
3
  installPackageGlobally,
4
4
  npmErrorMessage
5
- } from "./chunk-CBOHZ6NY.js";
5
+ } from "./chunk-SRRJLA24.js";
6
6
  import {
7
7
  writeJsonAtomic
8
8
  } from "./chunk-AQFEP65J.js";
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-3EGEA4KH.js";
10
10
  import {
11
11
  getBaseUrl
12
- } from "./chunk-TXKAPRIA.js";
12
+ } from "./chunk-5EX2SD5N.js";
13
13
 
14
14
  // src/cli/login-ui.ts
15
15
  import { hostname } from "os";