@pushary/agent-hooks 0.89.9 → 0.89.11

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 (68) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/bin/pushary-bell-hook.js +4 -4
  3. package/dist/bin/pushary-bell.js +7 -7
  4. package/dist/bin/pushary-claude.js +84 -36
  5. package/dist/bin/pushary-clean.js +52 -45
  6. package/dist/bin/pushary-codex-bridge.js +15 -9
  7. package/dist/bin/pushary-codex-hook.js +12 -8
  8. package/dist/bin/pushary-codex.js +7 -7
  9. package/dist/bin/pushary-connect.js +6 -6
  10. package/dist/bin/pushary-daemon.js +16 -16
  11. package/dist/bin/pushary-disconnect.js +34 -9
  12. package/dist/bin/pushary-doctor.js +19 -19
  13. package/dist/bin/pushary-elicitation-hook.js +4 -4
  14. package/dist/bin/pushary-gemini-bridge.js +15 -8
  15. package/dist/bin/pushary-gemini-hook.js +8 -8
  16. package/dist/bin/pushary-hook.js +11 -10
  17. package/dist/bin/pushary-login.js +4 -4
  18. package/dist/bin/pushary-logout.js +4 -4
  19. package/dist/bin/pushary-mode.js +3 -3
  20. package/dist/bin/pushary-notification-hook.js +7 -7
  21. package/dist/bin/pushary-opencode-hook.js +8 -8
  22. package/dist/bin/pushary-permission-denied-hook.js +11 -10
  23. package/dist/bin/pushary-permission-hook.js +11 -10
  24. package/dist/bin/pushary-post-hook.js +7 -7
  25. package/dist/bin/pushary-prompt-hook.js +7 -7
  26. package/dist/bin/pushary-session-end-hook.js +7 -7
  27. package/dist/bin/pushary-session-start-hook.js +7 -7
  28. package/dist/bin/pushary-setup.js +52 -54
  29. package/dist/bin/pushary-stats.js +2 -2
  30. package/dist/bin/pushary-status.js +8 -8
  31. package/dist/bin/pushary-stop-hook.js +7 -7
  32. package/dist/bin/pushary-stopfailure-hook.js +7 -7
  33. package/dist/bin/pushary-upgrade.js +15 -15
  34. package/dist/bin/pushary-wait.js +3 -3
  35. package/dist/{chunk-SS72Z7FV.js → chunk-253WC6O5.js} +1 -1
  36. package/dist/{chunk-T3LRWZKI.js → chunk-2LPEZYMC.js} +1 -1
  37. package/dist/{chunk-O5B7EK5U.js → chunk-34GN3IPX.js} +1 -1
  38. package/dist/{chunk-V6N4Q223.js → chunk-3CUDMTL4.js} +1 -1
  39. package/dist/{chunk-VZKXBFUA.js → chunk-3HFK3UXM.js} +1 -1
  40. package/dist/{chunk-543KNJKC.js → chunk-3SQYKLBW.js} +3 -3
  41. package/dist/{chunk-ATBKJNWS.js → chunk-6EKQZA6R.js} +5 -1
  42. package/dist/{chunk-NKHEHEUM.js → chunk-6XUABEX2.js} +1 -1
  43. package/dist/{chunk-I5L6SK5V.js → chunk-BLODMQKJ.js} +1 -1
  44. package/dist/{chunk-DKDDLF5P.js → chunk-DKWM5T3P.js} +1 -1
  45. package/dist/{chunk-4XZCDSNG.js → chunk-FXIL7RCL.js} +5 -5
  46. package/dist/{chunk-BAE45YLP.js → chunk-FZ73G2RO.js} +3 -3
  47. package/dist/{chunk-PMHTCDEO.js → chunk-IUN6VOKV.js} +1 -1
  48. package/dist/{chunk-OFRNZWC4.js → chunk-IUWRVTQK.js} +1 -1
  49. package/dist/{chunk-EJT2ER6G.js → chunk-J4K75V4D.js} +46 -35
  50. package/dist/{chunk-GKAKAOKC.js → chunk-JHJHCMAF.js} +65 -12
  51. package/dist/{chunk-J2JZJZKB.js → chunk-KVE5RCQL.js} +1 -1
  52. package/dist/{chunk-A4O5ROZK.js → chunk-MZGWKIUZ.js} +2 -2
  53. package/dist/chunk-NCVIWGVA.js +18 -0
  54. package/dist/{chunk-C623LWJR.js → chunk-OLAGKKSR.js} +1 -1
  55. package/dist/{chunk-EA3E74P2.js → chunk-PZRWFWSR.js} +6 -6
  56. package/dist/{chunk-VA7VUNRD.js → chunk-Q223OM5J.js} +58 -8
  57. package/dist/{chunk-GMI5QZLG.js → chunk-QA72MKIT.js} +5 -1
  58. package/dist/{chunk-AIGDBRIJ.js → chunk-SYUIXXQW.js} +1 -1
  59. package/dist/{chunk-TF247BET.js → chunk-TEZ2FZD6.js} +1 -1
  60. package/dist/{chunk-KOPUTJQC.js → chunk-TG3T3M4K.js} +1 -1
  61. package/dist/{chunk-FX5QSMPY.js → chunk-TRVIRETT.js} +1 -1
  62. package/dist/{chunk-U5TQCQ25.js → chunk-TY5HVMXT.js} +1 -1
  63. package/dist/chunk-UDBJHQMG.js +240 -0
  64. package/dist/{chunk-PZA7G4CN.js → chunk-X4ZUUFKV.js} +1 -0
  65. package/dist/{chunk-L4C2JXJS.js → chunk-X6RXX2NF.js} +5 -0
  66. package/dist/src/index.d.ts +5 -0
  67. package/dist/src/index.js +11 -10
  68. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.89.11
4
+
5
+ ### Phone-spawned Codex and Gemini sessions record their conversation again
6
+
7
+ Both bridges relay their turns by default and stop only when told to. The
8
+ launchers told them to stop on every run: the value they passed was an equality
9
+ test against an environment variable nobody sets, which is false whenever it is
10
+ absent and never the "unset" the bridge was looking for. The relay was therefore
11
+ never built, and a session started from the phone had nothing to show but its
12
+ task title. The opt-out is now passed only when it is asked for.
13
+
14
+ ### An agent standing by stops reading as working
15
+
16
+ `pushary claude` and the Gemini bridge reported that a session had started, that
17
+ it was still alive, and that it had ended, with nothing in between. Their
18
+ heartbeat kept resetting the clock that would otherwise have aged the session out
19
+ of "active", so the phone showed a working agent for one that had been waiting at
20
+ its prompt since its last reply. That is exactly when someone is looking at the
21
+ screen deciding whether to send it something. Both now report the turn boundary
22
+ they already detect, as the Codex bridge always has.
23
+
24
+ ### Taking a session over from your phone says so, and is ready when you ask
25
+
26
+ An instruction from the phone hands the terminal to Pushary so it can drive an
27
+ agent that would otherwise sit idle. That has not changed. The handover is now
28
+ announced before the session is signalled, instead of the terminal appearing to
29
+ lose its agent for no visible reason. The Agent SDK is resolved when the session
30
+ starts rather than during the handover, so the wait is gone, and a machine that
31
+ cannot reach npm reports the instruction as undelivered instead of accepting it
32
+ and then dropping it. An instruction that is dropped now says it was.
33
+
34
+ ## 0.89.10
35
+
36
+ ### Phone control delivery is crash-safe
37
+
38
+ Phone instructions now use per-command receipts across polling and relay
39
+ delivery. A process restart acknowledges the recorded outcome instead of
40
+ silently losing or repeating an instruction, multiple queued commands no longer
41
+ overwrite each other, and expired receipts are pruned when new commands arrive.
42
+
43
+ ### Setup and clean reverse only Pushary-owned changes
44
+
45
+ Setup records the Claude and Hermes configuration it replaces. Clean restores
46
+ that prior shape without deleting later user edits, and retains the receipt when
47
+ service removal, Hermes rollback, or plugin uninstall is incomplete so a retry
48
+ can finish safely. Installations created by older releases keep their compatible
49
+ best-effort cleanup path.
50
+
51
+ ### Native permission modes keep their intended owner
52
+
53
+ Codex now reads the permission mode it already reports, and Claude
54
+ `PermissionRequest` remains answerable from Pushary after the agent itself asks
55
+ for a human. Kill switches and standing denies still take precedence.
56
+
3
57
  ## 0.89.9
4
58
 
5
59
  ### Setup proves managed control reaches the server
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleBell
4
- } from "../chunk-FX5QSMPY.js";
4
+ } from "../chunk-TRVIRETT.js";
5
5
  import {
6
6
  readHookInput
7
7
  } from "../chunk-6CUUA7HO.js";
8
- import "../chunk-V6N4Q223.js";
8
+ import "../chunk-3CUDMTL4.js";
9
9
  import "../chunk-BC3VCZ3E.js";
10
- import "../chunk-OFRNZWC4.js";
10
+ import "../chunk-IUWRVTQK.js";
11
11
  import "../chunk-XX2V4QS5.js";
12
12
  import "../chunk-2UMNXADU.js";
13
- import "../chunk-L4C2JXJS.js";
13
+ import "../chunk-X6RXX2NF.js";
14
14
 
15
15
  // bin/pushary-bell-hook.ts
16
16
  var main = async () => {
@@ -5,18 +5,18 @@ import {
5
5
  liveAgentCount,
6
6
  ring,
7
7
  upgradeThreshold
8
- } from "../chunk-FX5QSMPY.js";
8
+ } from "../chunk-TRVIRETT.js";
9
9
  import {
10
10
  guardBinary
11
- } from "../chunk-AIGDBRIJ.js";
12
- import "../chunk-PZA7G4CN.js";
11
+ } from "../chunk-SYUIXXQW.js";
12
+ import "../chunk-X4ZUUFKV.js";
13
13
  import {
14
14
  claudeSettings
15
- } from "../chunk-GMI5QZLG.js";
15
+ } from "../chunk-QA72MKIT.js";
16
16
  import {
17
17
  createIo
18
18
  } from "../chunk-5RUIFDTP.js";
19
- import "../chunk-V6N4Q223.js";
19
+ import "../chunk-3CUDMTL4.js";
20
20
  import {
21
21
  parseFlags
22
22
  } from "../chunk-GMXKITVA.js";
@@ -26,7 +26,7 @@ import {
26
26
  import "../chunk-3XTFX4CL.js";
27
27
  import "../chunk-BC3VCZ3E.js";
28
28
  import "../chunk-DINDL2CF.js";
29
- import "../chunk-OFRNZWC4.js";
29
+ import "../chunk-IUWRVTQK.js";
30
30
  import "../chunk-7QLSKOSU.js";
31
31
  import {
32
32
  EXIT
@@ -36,7 +36,7 @@ import {
36
36
  writeJsonAtomic
37
37
  } from "../chunk-XX2V4QS5.js";
38
38
  import "../chunk-2UMNXADU.js";
39
- import "../chunk-L4C2JXJS.js";
39
+ import "../chunk-X6RXX2NF.js";
40
40
 
41
41
  // src/bell/install.ts
42
42
  import { join } from "path";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ensureDaemonRunning
4
- } from "../chunk-543KNJKC.js";
4
+ } from "../chunk-3SQYKLBW.js";
5
5
  import {
6
6
  buildTranscriptRecords,
7
7
  decodePublicKey,
@@ -9,16 +9,16 @@ import {
9
9
  generateSessionKey,
10
10
  uploadTranscriptRecords,
11
11
  wrapSessionKey
12
- } from "../chunk-PMHTCDEO.js";
12
+ } from "../chunk-IUN6VOKV.js";
13
13
  import {
14
14
  startProcessOwnership,
15
15
  withSpawnProcessOwnership
16
- } from "../chunk-TF247BET.js";
16
+ } from "../chunk-TEZ2FZD6.js";
17
17
  import {
18
18
  needsShell,
19
19
  spawnClaude
20
20
  } from "../chunk-XHKBHWLX.js";
21
- import "../chunk-PZA7G4CN.js";
21
+ import "../chunk-X4ZUUFKV.js";
22
22
  import {
23
23
  isDeferAnswer
24
24
  } from "../chunk-YHG74UFF.js";
@@ -27,7 +27,7 @@ import {
27
27
  getPolicy,
28
28
  repoKeyFor,
29
29
  reportEvent
30
- } from "../chunk-4XZCDSNG.js";
30
+ } from "../chunk-FXIL7RCL.js";
31
31
  import {
32
32
  DEFAULT_SESSION,
33
33
  askUser,
@@ -35,23 +35,24 @@ import {
35
35
  markPendingCommandDelivered,
36
36
  markPendingCommandFailed,
37
37
  readPendingCommand,
38
+ readPendingCommandById,
38
39
  removePendingCommand,
39
40
  savePendingCommand,
40
41
  waitForAnswer
41
- } from "../chunk-GKAKAOKC.js";
42
+ } from "../chunk-JHJHCMAF.js";
42
43
  import {
43
44
  deriveToolTarget,
44
45
  describeToolCall,
45
46
  scopePathFor
46
- } from "../chunk-KOPUTJQC.js";
47
+ } from "../chunk-TG3T3M4K.js";
47
48
  import {
48
49
  getMachineId
49
- } from "../chunk-DKDDLF5P.js";
50
- import "../chunk-GMI5QZLG.js";
50
+ } from "../chunk-DKWM5T3P.js";
51
+ import "../chunk-QA72MKIT.js";
51
52
  import "../chunk-DINDL2CF.js";
52
53
  import "../chunk-BSZYIAZL.js";
53
54
  import "../chunk-SAF6HGAA.js";
54
- import "../chunk-OFRNZWC4.js";
55
+ import "../chunk-IUWRVTQK.js";
55
56
  import "../chunk-7QLSKOSU.js";
56
57
  import "../chunk-KZERVKTD.js";
57
58
  import "../chunk-XX2V4QS5.js";
@@ -62,7 +63,7 @@ import {
62
63
  import {
63
64
  KILL_REASON,
64
65
  resolveGate
65
- } from "../chunk-L4C2JXJS.js";
66
+ } from "../chunk-X6RXX2NF.js";
66
67
 
67
68
  // src/wrapper/claudeBinary.ts
68
69
  import { statSync } from "fs";
@@ -341,9 +342,13 @@ var BoundedInputQueue = class {
341
342
  }
342
343
  // Drop every buffered item without closing the queue. Used when phone control
343
344
  // turns out to be unavailable (offline SDK setup) so a pending instruction does
344
- // not make the local leg switch again into a mode that cannot run.
345
+ // not make the local leg switch again into a mode that cannot run. Returns how
346
+ // many were dropped, because these are somebody's words and the caller has to
347
+ // be able to say they are gone.
345
348
  clear() {
349
+ const dropped = this.buffer.length;
346
350
  this.buffer.length = 0;
351
+ return dropped;
347
352
  }
348
353
  get isClosed() {
349
354
  return this.closed;
@@ -664,20 +669,8 @@ var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBase
664
669
  try {
665
670
  const sessionKey = sessionId || DEFAULT_SESSION;
666
671
  const receiptNamespace = `${apiKey}:${machineId}:claude_code`;
667
- const storedCommand = wantCommand && machineId ? readPendingCommand(sessionKey, receiptNamespace) : null;
668
- const response = await fetch(`${baseUrl}/api/agent/poll`, {
669
- method: "POST",
670
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
671
- body: JSON.stringify({
672
- ...sessionId ? { sessionId } : {},
673
- ...machineId ? { machineId } : {},
674
- ...wantCommand && !storedCommand ? {} : { drain: false },
675
- ...wantCommand && !storedCommand && machineId ? { ackMode: true } : {}
676
- }),
677
- signal
678
- });
679
- if (!response.ok) return { command: null, mode: null };
680
- const data = await response.json();
672
+ let storedCommand = machineId ? readPendingCommand(sessionKey, receiptNamespace) : null;
673
+ let acknowledgedCommandId = null;
681
674
  const acknowledge = async (id, state) => {
682
675
  const persisted = state === "delivered" ? markPendingCommandDelivered(sessionKey, id, receiptNamespace) : markPendingCommandFailed(sessionKey, id, receiptNamespace);
683
676
  if (!persisted) return false;
@@ -700,6 +693,29 @@ var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBase
700
693
  return false;
701
694
  }
702
695
  };
696
+ if (storedCommand?.delivered || storedCommand?.failed) {
697
+ const acknowledged = await acknowledge(
698
+ storedCommand.id,
699
+ storedCommand.failed ? "failed" : "delivered"
700
+ );
701
+ if (acknowledged) {
702
+ acknowledgedCommandId = storedCommand.id;
703
+ storedCommand = null;
704
+ }
705
+ }
706
+ const response = await fetch(`${baseUrl}/api/agent/poll`, {
707
+ method: "POST",
708
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
709
+ body: JSON.stringify({
710
+ ...sessionId ? { sessionId } : {},
711
+ ...machineId ? { machineId } : {},
712
+ ...wantCommand && !storedCommand ? {} : { drain: false },
713
+ ...wantCommand && !storedCommand && machineId ? { ackMode: true } : {}
714
+ }),
715
+ signal
716
+ });
717
+ if (!response.ok) return { command: null, mode: null };
718
+ const data = await response.json();
703
719
  const claimedCommand = (id, text) => ({
704
720
  id,
705
721
  text,
@@ -707,15 +723,10 @@ var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBase
707
723
  });
708
724
  let command = typeof data.command === "string" && data.command.length > 0 ? data.command : null;
709
725
  if (storedCommand) {
710
- if (storedCommand.delivered || storedCommand.failed) {
711
- await acknowledge(storedCommand.id, storedCommand.failed ? "failed" : "delivered");
712
- command = null;
713
- } else {
714
- command = claimedCommand(storedCommand.id, storedCommand.text);
715
- }
726
+ command = storedCommand.delivered || storedCommand.failed ? null : claimedCommand(storedCommand.id, storedCommand.text);
716
727
  } else if (data.command && typeof data.command === "object") {
717
728
  const claimed = data.command;
718
- if (typeof claimed.id === "string" && typeof claimed.text === "string") {
729
+ if (typeof claimed.id === "string" && typeof claimed.text === "string" && claimed.id !== acknowledgedCommandId) {
719
730
  const saved = savePendingCommand(sessionKey, {
720
731
  id: claimed.id,
721
732
  text: claimed.text,
@@ -780,6 +791,7 @@ var decodeServerFrame = (raw) => {
780
791
  var DEFAULT_HEARTBEAT_MS = 2e4;
781
792
  var DEFAULT_PONG_TIMEOUT_MS = 1e4;
782
793
  var DEFAULT_MAX_SEEN_COMMANDS = 256;
794
+ var COMMAND_RECEIPT_TTL_MS = 36e5;
783
795
  var defaultBackoff = (attempt) => Math.min(1e3 * 2 ** attempt, 3e4);
784
796
  var defaultSocketFactory = (url) => {
785
797
  const WS = globalThis.WebSocket;
@@ -810,6 +822,17 @@ var createRelayClient = (options) => {
810
822
  const seenCommands = [];
811
823
  const seenSet = /* @__PURE__ */ new Set();
812
824
  const log = (message) => options.log?.(message);
825
+ const receiptNamespace = `${options.apiKey}:${options.machineId}:${options.agentType}`;
826
+ const acknowledgeStored = (id) => {
827
+ const receipt = readPendingCommandById(
828
+ options.getSessionId() || DEFAULT_SESSION,
829
+ id,
830
+ receiptNamespace
831
+ );
832
+ if (!receipt || !receipt.delivered && !receipt.failed) return false;
833
+ sendFrame(receipt.failed ? { v: PROTOCOL_VERSION, t: "ack", id, state: "failed", failureCode: "consumer_rejected" } : { v: PROTOCOL_VERSION, t: "ack", id, state: "delivered" });
834
+ return true;
835
+ };
813
836
  const clearTimers = () => {
814
837
  if (heartbeatTimer) clearInterval(heartbeatTimer);
815
838
  if (pongTimer) clearTimeout(pongTimer);
@@ -870,12 +893,24 @@ var createRelayClient = (options) => {
870
893
  sendFrame({ v: PROTOCOL_VERSION, t: "ack", id: frame.id, state: "delivered" });
871
894
  break;
872
895
  }
896
+ if (acknowledgeStored(frame.id)) break;
897
+ const sessionKey = options.getSessionId() || DEFAULT_SESSION;
898
+ if (!savePendingCommand(sessionKey, {
899
+ id: frame.id,
900
+ text: frame.text,
901
+ expiresAtMs: Date.now() + COMMAND_RECEIPT_TTL_MS,
902
+ delivered: false,
903
+ failed: true
904
+ }, receiptNamespace)) {
905
+ acknowledgeStored(frame.id);
906
+ break;
907
+ }
873
908
  let accepted = false;
874
909
  try {
875
910
  accepted = options.onCommand(frame.text) !== false;
876
911
  } catch {
877
912
  }
878
- if (accepted) {
913
+ if (accepted && markPendingCommandDelivered(sessionKey, frame.id, receiptNamespace)) {
879
914
  rememberCommand(frame.id);
880
915
  sendFrame({ v: PROTOCOL_VERSION, t: "ack", id: frame.id, state: "delivered" });
881
916
  } else {
@@ -1097,7 +1132,12 @@ var runLocalLeg = (session, deps = {}) => {
1097
1132
  const control = {
1098
1133
  onPhoneCommand: () => {
1099
1134
  if (!exitReason) exitReason = "switch";
1100
- if (!legAbort.signal.aborted) legAbort.abort();
1135
+ if (!legAbort.signal.aborted) {
1136
+ process.stderr.write(
1137
+ "\n[pushary] instruction from your phone \u2014 taking this session over to run it (Ctrl-] hands the terminal back)\n"
1138
+ );
1139
+ legAbort.abort();
1140
+ }
1101
1141
  },
1102
1142
  stop: (reason) => {
1103
1143
  if (!exitReason) exitReason = { type: "exit", code: reason === "signal" ? 130 : 0 };
@@ -1451,6 +1491,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1451
1491
  } else if (message.type === "result") {
1452
1492
  if (typeof message.total_cost_usd === "number") totalCostUsd = message.total_cost_usd;
1453
1493
  process.stderr.write("[pushary] turn complete \u2014 standing by for phone commands\n");
1494
+ void announcePresence("stop", "Turn complete");
1454
1495
  }
1455
1496
  };
1456
1497
  const poller = startCommandPoller({
@@ -1515,6 +1556,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1515
1556
  void announcePresence("session_start", "Session ready \u2014 reachable from your phone");
1516
1557
  ensurePresenceHeartbeat();
1517
1558
  ensureTranscriptSync();
1559
+ void ensureSdk();
1518
1560
  }
1519
1561
  if (startingMode === "remote" && initialPrompt) input.push(userMessage(initialPrompt));
1520
1562
  const exitCode = await driveModeLoop({
@@ -1524,13 +1566,19 @@ var runDualMode = async (binary, args2, startingMode) => {
1524
1566
  const ready = await ensureSdk();
1525
1567
  if (!ready) {
1526
1568
  remoteAvailable = false;
1527
- input.clear();
1569
+ const dropped = input.clear();
1528
1570
  if (!noticedUnavailable) {
1529
1571
  noticedUnavailable = true;
1530
1572
  process.stderr.write(
1531
1573
  "[pushary] could not start phone control (offline?); staying in the terminal. Relaunch when online to enable.\n"
1532
1574
  );
1533
1575
  }
1576
+ if (dropped > 0) {
1577
+ process.stderr.write(
1578
+ `[pushary] ${dropped} instruction(s) from your phone could not be delivered and were dropped.
1579
+ `
1580
+ );
1581
+ }
1534
1582
  return "switch";
1535
1583
  }
1536
1584
  return runRemoteLeg(session, ready);
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ HERMES_CONFIG_SCRIPT,
4
+ resolveHermesPython,
5
+ runHermesPythonScript
6
+ } from "../chunk-UDBJHQMG.js";
2
7
  import {
3
8
  removeInstructionBlock,
4
9
  shortenHome,
5
10
  unregisterPluginLocation,
6
11
  vscodeSettingsTargets
7
- } from "../chunk-A4O5ROZK.js";
12
+ } from "../chunk-MZGWKIUZ.js";
8
13
  import {
9
14
  OPENCODE_PLUGIN_SIBLING,
10
15
  OPENCODE_PLUGIN_SIBLING_CONTENTS,
@@ -14,25 +19,28 @@ import {
14
19
  openCodePluginDir,
15
20
  openCodePluginFile,
16
21
  removeOpenCodeMcpServer
17
- } from "../chunk-O5B7EK5U.js";
22
+ } from "../chunk-34GN3IPX.js";
18
23
  import {
24
+ isClaudeMcpSnapshot,
19
25
  removeClaudeMcpServers,
20
26
  removeGeminiSettings,
21
- removePusharySettings
22
- } from "../chunk-VA7VUNRD.js";
23
- import "../chunk-ATBKJNWS.js";
24
- import "../chunk-AIGDBRIJ.js";
27
+ removePusharySettings,
28
+ restoreClaudeMcpServer
29
+ } from "../chunk-Q223OM5J.js";
30
+ import "../chunk-6EKQZA6R.js";
31
+ import "../chunk-SYUIXXQW.js";
25
32
  import {
26
33
  disableDaemonService
27
- } from "../chunk-543KNJKC.js";
34
+ } from "../chunk-3SQYKLBW.js";
28
35
  import "../chunk-XHKBHWLX.js";
29
36
  import {
30
37
  execNpm
31
- } from "../chunk-PZA7G4CN.js";
32
- import "../chunk-KOPUTJQC.js";
33
- import "../chunk-DKDDLF5P.js";
38
+ } from "../chunk-X4ZUUFKV.js";
39
+ import "../chunk-TG3T3M4K.js";
40
+ import "../chunk-DKWM5T3P.js";
34
41
  import {
35
42
  claudeJson,
43
+ claudeMcpReceipt,
36
44
  claudeSettings,
37
45
  claudeSettingsLocal,
38
46
  claudeSkillDir,
@@ -46,11 +54,12 @@ import {
46
54
  cursorUserMcp,
47
55
  geminiMd,
48
56
  geminiSettings,
57
+ hermesConfigReceipt,
49
58
  pusharyDir,
50
59
  removeCodexHookTrust,
51
60
  removeCodexHooks,
52
61
  vscodePluginDir
53
- } from "../chunk-GMI5QZLG.js";
62
+ } from "../chunk-QA72MKIT.js";
54
63
  import {
55
64
  rejectUnknownFlags
56
65
  } from "../chunk-GMXKITVA.js";
@@ -71,13 +80,12 @@ import "../chunk-2UMNXADU.js";
71
80
  import {
72
81
  CURSOR_GATE_EVENTS,
73
82
  HOOK_LOCATOR_BINARY
74
- } from "../chunk-L4C2JXJS.js";
83
+ } from "../chunk-X6RXX2NF.js";
75
84
 
76
85
  // bin/pushary-clean.ts
77
86
  import { existsSync, readFileSync, copyFileSync, readdirSync } from "fs";
78
87
  import { join } from "path";
79
88
  import { homedir, tmpdir } from "os";
80
- import { execSync as execSync2 } from "child_process";
81
89
  import { confirm } from "@inquirer/prompts";
82
90
  import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
83
91
 
@@ -128,6 +136,8 @@ var incompleteClean = false;
128
136
  var CLAUDE_SETTINGS = claudeSettings();
129
137
  var CLAUDE_SETTINGS_LOCAL = claudeSettingsLocal();
130
138
  var CLAUDE_JSON = claudeJson();
139
+ var CLAUDE_MCP_RECEIPT = claudeMcpReceipt();
140
+ var HERMES_CONFIG_RECEIPT = hermesConfigReceipt();
131
141
  var SKILL_DIR = claudeSkillDir();
132
142
  var CURSOR_MCP = cursorUserMcp();
133
143
  var CURSOR_PLUGIN_DIR = cursorPluginDir();
@@ -140,22 +150,6 @@ var removalScope = () => ({
140
150
  keepAppWiring: true,
141
151
  keepMcpServer: macAppInstalled()
142
152
  });
143
- var resolveHermesPython = () => {
144
- try {
145
- const launcher = execSync2("command -v hermes", { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).trim();
146
- if (launcher) {
147
- const shebang = readFileSync(launcher, "utf-8").split("\n", 1)[0];
148
- if (shebang.startsWith("#!")) {
149
- const interpreter = shebang.slice(2).trim().split(/\s+/)[0];
150
- if (interpreter && /python/i.test(interpreter) && existsSync(interpreter)) return interpreter;
151
- }
152
- }
153
- } catch {
154
- }
155
- const venvPython = join(homedir(), ".hermes", "hermes-agent", "venv", "bin", "python3");
156
- if (existsSync(venvPython)) return venvPython;
157
- return null;
158
- };
159
153
  var readJson = (path) => {
160
154
  try {
161
155
  return JSON.parse(readFileSync(path, "utf-8"));
@@ -305,12 +299,21 @@ var main = async () => {
305
299
  if (keepShared) {
306
300
  console.log(` ${skip} Claude Code MCP servers ${dim("(kept: the Mac app uses it)")}`);
307
301
  } else if (claudeJson2) {
308
- if (removeClaudeMcpServers(claudeJson2)) {
302
+ const rawReceipt = readJson(CLAUDE_MCP_RECEIPT);
303
+ const receipt = isClaudeMcpSnapshot(rawReceipt) ? rawReceipt : null;
304
+ if (existsSync(CLAUDE_MCP_RECEIPT) && !receipt) {
305
+ incompleteClean = true;
306
+ console.log(` ${skip} Claude Code MCP servers ${dim("(setup receipt is unreadable; left unchanged)")}`);
307
+ } else if (receipt && restoreClaudeMcpServer(claudeJson2, receipt) || !receipt && removeClaudeMcpServers(claudeJson2)) {
309
308
  writeJson(CLAUDE_JSON, claudeJson2);
310
- console.log(` ${check} Claude Code MCP servers ${dim("(removed from ~/.claude.json)")}`);
309
+ console.log(` ${check} Claude Code MCP servers ${dim(receipt ? "(previous entry restored)" : "(removed from ~/.claude.json)")}`);
311
310
  } else {
312
- console.log(` ${skip} Claude Code MCP servers ${dim("(no pushary entries)")}`);
311
+ if (receipt) incompleteClean = true;
312
+ console.log(` ${skip} Claude Code MCP servers ${dim(receipt ? "(changed after setup; left unchanged)" : "(no pushary entries)")}`);
313
313
  }
314
+ } else if (existsSync(CLAUDE_MCP_RECEIPT)) {
315
+ incompleteClean = true;
316
+ console.log(` ${skip} Claude Code MCP servers ${dim("(config missing; setup receipt kept for retry)")}`);
314
317
  } else {
315
318
  console.log(` ${skip} Claude Code MCP servers ${dim("(not found)")}`);
316
319
  }
@@ -487,19 +490,23 @@ var main = async () => {
487
490
  }
488
491
  const hermesPython = resolveHermesPython();
489
492
  if (hermesPython) {
490
- const snippet = 'from hermes_cli.config import load_config, save_config; c = load_config(); p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}; e = p.get("enabled") if isinstance(p.get("enabled"), list) else []; p["enabled"] = [x for x in e if x != "pushary"]; c["plugins"] = p; a = c.get("agent") if isinstance(c.get("agent"), dict) else {}; d = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []; a["disabled_toolsets"] = [x for x in d if x != "clarify"]; c["agent"] = a; save_config(c)';
491
- try {
492
- guardedExec(`"${hermesPython}" -c '${snippet}'`, { stdio: "pipe", timeout: 15e3 }, "hermes plugin config update");
493
- console.log(` ${check} Hermes config ${dim("(plugin disabled, clarify toolset restored)")}`);
494
- } catch {
493
+ const configUpdated = dryRun ? (wouldNote("run ", "hermes plugin config restore"), true) : runHermesPythonScript(hermesPython, HERMES_CONFIG_SCRIPT, "clean", HERMES_CONFIG_RECEIPT);
494
+ if (!configUpdated) {
495
+ incompleteClean = true;
495
496
  console.log(` ${skip} Hermes config ${dim("(could not update config.yaml)")}`);
497
+ } else {
498
+ console.log(` ${check} Hermes config ${dim("(setup-owned changes restored)")}`);
499
+ try {
500
+ guardedExec(`"${hermesPython}" -m pip uninstall -y hermes-plugin-pushary`, { stdio: "pipe", timeout: 6e4 }, "pip uninstall hermes-plugin-pushary");
501
+ console.log(` ${check} Hermes plugin ${dim("(pip uninstalled)")}`);
502
+ } catch {
503
+ incompleteClean = true;
504
+ console.log(` ${skip} Hermes plugin ${dim("(could not uninstall; setup receipt kept for retry)")}`);
505
+ }
496
506
  }
497
- try {
498
- guardedExec(`"${hermesPython}" -m pip uninstall -y hermes-plugin-pushary`, { stdio: "pipe", timeout: 6e4 }, "pip uninstall hermes-plugin-pushary");
499
- console.log(` ${check} Hermes plugin ${dim("(pip uninstalled)")}`);
500
- } catch {
501
- console.log(` ${skip} Hermes plugin ${dim("(not installed)")}`);
502
- }
507
+ } else if (existsSync(HERMES_CONFIG_RECEIPT)) {
508
+ incompleteClean = true;
509
+ console.log(` ${skip} Hermes ${dim("(runtime not found; setup receipt kept for retry)")}`);
503
510
  } else {
504
511
  console.log(` ${skip} Hermes ${dim("(not found)")}`);
505
512
  }
@@ -534,7 +541,7 @@ var main = async () => {
534
541
  let removed = 0;
535
542
  const kept = [];
536
543
  for (const entry of readdirSync(PUSHARY_DIR)) {
537
- if (keepShared && isAppOwned(entry)) {
544
+ if (keepShared && isAppOwned(entry) || entry === "setup" && (keepShared || incompleteClean)) {
538
545
  kept.push(entry);
539
546
  continue;
540
547
  }
@@ -548,7 +555,7 @@ var main = async () => {
548
555
  console.log(` ${skip} Local state ${dim("(nothing of ours in ~/.pushary)")}`);
549
556
  }
550
557
  if (kept.length > 0) {
551
- console.log(` ${dim(`left ${kept.sort().join(", ")} for the Mac app`)}`);
558
+ console.log(` ${dim(`left ${kept.sort().join(", ")} for ${keepShared ? "the Mac app" : "a cleanup retry"}`)}`);
552
559
  }
553
560
  } else {
554
561
  console.log(` ${skip} Local state ${dim("(~/.pushary not found)")}`);
@@ -1,33 +1,33 @@
1
1
  #!/usr/bin/env node
2
- import "../chunk-NKHEHEUM.js";
2
+ import "../chunk-6XUABEX2.js";
3
3
  import {
4
4
  LiveTranscriptRelay,
5
5
  liveSessionSleep,
6
6
  postLiveSession,
7
7
  postLiveSessionUntilAccepted,
8
8
  startLiveSessionHeartbeat
9
- } from "../chunk-C623LWJR.js";
10
- import "../chunk-PMHTCDEO.js";
9
+ } from "../chunk-OLAGKKSR.js";
10
+ import "../chunk-IUN6VOKV.js";
11
11
  import {
12
12
  startProcessOwnership,
13
13
  withSpawnProcessOwnership
14
- } from "../chunk-TF247BET.js";
14
+ } from "../chunk-TEZ2FZD6.js";
15
15
  import {
16
16
  spawnClaude
17
17
  } from "../chunk-XHKBHWLX.js";
18
18
  import {
19
19
  resolveBinary
20
- } from "../chunk-PZA7G4CN.js";
20
+ } from "../chunk-X4ZUUFKV.js";
21
21
  import {
22
22
  getMachineId
23
- } from "../chunk-DKDDLF5P.js";
24
- import "../chunk-GMI5QZLG.js";
23
+ } from "../chunk-DKWM5T3P.js";
24
+ import "../chunk-QA72MKIT.js";
25
25
  import "../chunk-XX2V4QS5.js";
26
26
  import {
27
27
  getApiKey,
28
28
  getBaseUrl
29
29
  } from "../chunk-2UMNXADU.js";
30
- import "../chunk-L4C2JXJS.js";
30
+ import "../chunk-X6RXX2NF.js";
31
31
 
32
32
  // bin/pushary-codex-bridge.ts
33
33
  import { existsSync } from "fs";
@@ -371,7 +371,13 @@ var outcome = await withSpawnProcessOwnership(spawnId, () => runCodexBridge({
371
371
  args: ["app-server", "--stdio"]
372
372
  },
373
373
  signal: abort.signal,
374
- transcripts: process.env.PUSHARY_TRANSCRIPTS === "1"
374
+ // The bridge's own default is ON (`transcripts?: boolean`, and it opts out only
375
+ // on an explicit `false`), but `=== '1'` can never produce `undefined`, so an
376
+ // unset env var forced the opt-out on every spawn and the live relay was never
377
+ // built. Pass the opt-out only when the user asks for one. The relay stays a
378
+ // hard no-op until this account's phone has registered a public key, and what
379
+ // it sends is encrypted to that key on this machine.
380
+ ...process.env.PUSHARY_TRANSCRIPTS === "0" ? { transcripts: false } : {}
375
381
  }));
376
382
  for (const signal of signals) process.off(signal, stop);
377
383
  process.exit(outcome === "stopped" ? 0 : 1);