@pushary/agent-hooks 0.89.10 → 0.89.12
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/CHANGELOG.md +69 -0
- package/README.md +3 -2
- package/dist/bin/pushary-bell-hook.js +5 -5
- package/dist/bin/pushary-bell.js +11 -11
- package/dist/bin/pushary-claude.js +125 -36
- package/dist/bin/pushary-clean.js +39 -28
- package/dist/bin/pushary-codex-bridge.js +17 -10
- package/dist/bin/pushary-codex-hook.js +12 -11
- package/dist/bin/pushary-codex.js +8 -8
- package/dist/bin/pushary-connect.js +12 -12
- package/dist/bin/pushary-daemon-supervisor.d.ts +1 -0
- package/dist/bin/pushary-daemon-supervisor.js +27 -0
- package/dist/bin/pushary-daemon.js +22 -22
- package/dist/bin/pushary-disconnect.js +9 -9
- package/dist/bin/pushary-doctor.js +59 -49
- package/dist/bin/pushary-elicitation-hook.js +5 -5
- package/dist/bin/pushary-gemini-bridge.js +17 -9
- package/dist/bin/pushary-gemini-hook.js +11 -10
- package/dist/bin/pushary-hook.js +14 -14
- package/dist/bin/pushary-login.js +11 -11
- package/dist/bin/pushary-logout.js +11 -11
- package/dist/bin/pushary-mode.js +5 -5
- package/dist/bin/pushary-notification-hook.js +8 -8
- package/dist/bin/pushary-opencode-hook.js +11 -10
- package/dist/bin/pushary-permission-denied-hook.js +12 -12
- package/dist/bin/pushary-permission-hook.js +12 -12
- package/dist/bin/pushary-post-hook.js +8 -8
- package/dist/bin/pushary-prompt-hook.js +8 -8
- package/dist/bin/pushary-session-end-hook.js +8 -8
- package/dist/bin/pushary-session-start-hook.js +8 -8
- package/dist/bin/pushary-setup.js +88 -60
- package/dist/bin/pushary-stats.js +4 -4
- package/dist/bin/pushary-status.js +14 -14
- package/dist/bin/pushary-stop-hook.js +8 -8
- package/dist/bin/pushary-stopfailure-hook.js +8 -8
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-upgrade.js +21 -21
- package/dist/bin/pushary-wait.js +5 -5
- package/dist/bin/pushary.js +1 -1
- package/dist/{chunk-253WC6O5.js → chunk-327YMKP5.js} +1 -1
- package/dist/{chunk-6XUABEX2.js → chunk-3OXHYPZG.js} +1 -1
- package/dist/{chunk-MZGWKIUZ.js → chunk-3XSP5SML.js} +7 -3
- package/dist/{chunk-XX2V4QS5.js → chunk-4LL6LG5X.js} +2 -2
- package/dist/{chunk-DKWM5T3P.js → chunk-5NDB3YOE.js} +2 -2
- package/dist/{chunk-TEZ2FZD6.js → chunk-6GU55AQQ.js} +2 -2
- package/dist/{chunk-Q223OM5J.js → chunk-6V7ZDH3A.js} +4 -4
- package/dist/{chunk-OLAGKKSR.js → chunk-6VXNA5JB.js} +16 -6
- package/dist/{chunk-34GN3IPX.js → chunk-7DK3ZH3N.js} +1 -1
- package/dist/{chunk-PZRWFWSR.js → chunk-7HYJPOGG.js} +7 -7
- package/dist/{chunk-TY5HVMXT.js → chunk-DHR4F2CR.js} +1 -1
- package/dist/{chunk-3HFK3UXM.js → chunk-FCUUVHTS.js} +1 -1
- package/dist/{chunk-NCVIWGVA.js → chunk-GKPURU7P.js} +1 -1
- package/dist/{chunk-UXHMWF4H.js → chunk-JHJHCMAF.js} +1 -1
- package/dist/chunk-JJV5Q4KG.js +20 -0
- package/dist/{chunk-TG3T3M4K.js → chunk-JVZX5YRJ.js} +6 -2
- package/dist/{chunk-6EKQZA6R.js → chunk-KVHP5ZME.js} +1 -1
- package/dist/{chunk-X4ZUUFKV.js → chunk-L4ZCFMWW.js} +63 -1
- package/dist/{chunk-2LPEZYMC.js → chunk-MFNQ5IMW.js} +1 -1
- package/dist/{chunk-KVE5RCQL.js → chunk-NBRM3FMD.js} +1 -1
- package/dist/{chunk-FZ73G2RO.js → chunk-NESKLV6R.js} +25 -6
- package/dist/{chunk-GTZLL7F4.js → chunk-OADGBOP7.js} +5 -5
- package/dist/{chunk-GSSFSVAQ.js → chunk-OCMOTJTW.js} +42 -24
- package/dist/{chunk-LURY4WNF.js → chunk-OTPJ4FUR.js} +1 -1
- package/dist/{chunk-QA72MKIT.js → chunk-OVEGEQSB.js} +5 -5
- package/dist/{chunk-3SQYKLBW.js → chunk-PS7DQ2SU.js} +46 -18
- package/dist/{chunk-UDBJHQMG.js → chunk-QFJZHVVL.js} +1 -1
- package/dist/chunk-QZCFVWA7.js +58 -0
- package/dist/{chunk-3XTFX4CL.js → chunk-RLQWNEPM.js} +3 -2
- package/dist/{chunk-3CUDMTL4.js → chunk-RNKPLUWC.js} +10 -3
- package/dist/{chunk-SYUIXXQW.js → chunk-VF3RTVWY.js} +1 -1
- package/dist/{chunk-TRVIRETT.js → chunk-VPLQFXCH.js} +1 -1
- package/dist/{chunk-IUN6VOKV.js → chunk-WDVQ6O4C.js} +2 -42
- package/dist/{chunk-IUWRVTQK.js → chunk-XREW7RQ3.js} +1 -1
- package/dist/{chunk-BLODMQKJ.js → chunk-Y4WVZIEI.js} +1 -1
- package/dist/src/index.d.ts +8 -1
- package/dist/src/index.js +16 -12
- package/package.json +2 -1
- /package/dist/{chunk-X6RXX2NF.js → chunk-3MCEES6Z.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.89.12
|
|
4
|
+
|
|
5
|
+
### Phone control installs as a native service, without a VM
|
|
6
|
+
|
|
7
|
+
Fresh setup enables phone-start when a supported provider and the operating
|
|
8
|
+
system's own per-user service manager are available: launchd on macOS, systemd
|
|
9
|
+
on Linux, and Task Scheduler on Windows. Existing installs keep their stored
|
|
10
|
+
choice. Service replacement is transactional, verifies the new process before
|
|
11
|
+
committing, and restores the previous running or stopped state on failure.
|
|
12
|
+
|
|
13
|
+
Claude Code, Codex and Gemini sessions started from the phone now use the same
|
|
14
|
+
supervised control path. Setup checks both the local heartbeat and the server's
|
|
15
|
+
view of the machine before calling control ready.
|
|
16
|
+
|
|
17
|
+
### Fresh and partial installs repair themselves
|
|
18
|
+
|
|
19
|
+
Setup now verifies every global command, its local JavaScript imports, and the
|
|
20
|
+
Cursor and VS Code runtime assets before reusing an installed version. A
|
|
21
|
+
same-version package missing any of them is reinstalled instead of failing the
|
|
22
|
+
same setup forever. Fresh installs also cover Codex on Windows and recover from
|
|
23
|
+
partially configured providers without disturbing unrelated settings.
|
|
24
|
+
|
|
25
|
+
### The CLI and macOS app keep their own wiring
|
|
26
|
+
|
|
27
|
+
Cursor setup writes one gate per event even when the other installer ran first.
|
|
28
|
+
`pushary clean` removes the CLI gate while preserving the app's bridge, and
|
|
29
|
+
`pushary doctor` validates the MCP entry from the installer that actually wired
|
|
30
|
+
Cursor. This keeps mixed CLI/app machines working and avoids false repair
|
|
31
|
+
failures on app-only installs.
|
|
32
|
+
|
|
33
|
+
### Transcript control works in every shell
|
|
34
|
+
|
|
35
|
+
Setup discloses encrypted transcript sync and its audited compliance-recovery
|
|
36
|
+
path before enabling it for a new install; older installs remain off until setup
|
|
37
|
+
runs again. `pushary setup --transcripts on|off` now provides the same persistent
|
|
38
|
+
choice in POSIX shells, PowerShell and Command Prompt. The existing
|
|
39
|
+
`PUSHARY_TRANSCRIPTS` environment override remains compatible.
|
|
40
|
+
|
|
41
|
+
## 0.89.11
|
|
42
|
+
|
|
43
|
+
### Phone-spawned Codex and Gemini sessions record their conversation again
|
|
44
|
+
|
|
45
|
+
Both bridges relay their turns by default and stop only when told to. The
|
|
46
|
+
launchers told them to stop on every run: the value they passed was an equality
|
|
47
|
+
test against an environment variable nobody sets, which is false whenever it is
|
|
48
|
+
absent and never the "unset" the bridge was looking for. The relay was therefore
|
|
49
|
+
never built, and a session started from the phone had nothing to show but its
|
|
50
|
+
task title. The opt-out is now passed only when it is asked for.
|
|
51
|
+
|
|
52
|
+
### An agent standing by stops reading as working
|
|
53
|
+
|
|
54
|
+
`pushary claude` and the Gemini bridge reported that a session had started, that
|
|
55
|
+
it was still alive, and that it had ended, with nothing in between. Their
|
|
56
|
+
heartbeat kept resetting the clock that would otherwise have aged the session out
|
|
57
|
+
of "active", so the phone showed a working agent for one that had been waiting at
|
|
58
|
+
its prompt since its last reply. That is exactly when someone is looking at the
|
|
59
|
+
screen deciding whether to send it something. Both now report the turn boundary
|
|
60
|
+
they already detect, as the Codex bridge always has.
|
|
61
|
+
|
|
62
|
+
### Taking a session over from your phone says so, and is ready when you ask
|
|
63
|
+
|
|
64
|
+
An instruction from the phone hands the terminal to Pushary so it can drive an
|
|
65
|
+
agent that would otherwise sit idle. That has not changed. The handover is now
|
|
66
|
+
announced before the session is signalled, instead of the terminal appearing to
|
|
67
|
+
lose its agent for no visible reason. The Agent SDK is resolved when the session
|
|
68
|
+
starts rather than during the handover, so the wait is gone, and a machine that
|
|
69
|
+
cannot reach npm reports the instruction as undelivered instead of accepting it
|
|
70
|
+
and then dropping it. An instruction that is dropped now says it was.
|
|
71
|
+
|
|
3
72
|
## 0.89.10
|
|
4
73
|
|
|
5
74
|
### Phone control delivery is crash-safe
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@pushary/agent-hooks)
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
|
|
7
|
-
Push notifications, questions, and permission approvals for AI
|
|
7
|
+
Push notifications, questions, and permission approvals for AI agents. Get a push when your agent finishes work, answer its questions from your phone, and approve risky commands before they run. Works with Claude Code, Codex, Cursor, VS Code, OpenCode, Gemini CLI, and Hermes.
|
|
8
8
|
|
|
9
9
|
Your agent froze waiting for a yes. This is how it reaches you.
|
|
10
10
|
|
|
@@ -110,6 +110,7 @@ npx @pushary/agent-hooks@latest doctor
|
|
|
110
110
|
| Gemini CLI | yes | yes | yes |
|
|
111
111
|
| Hermes | yes | yes | yes |
|
|
112
112
|
| VS Code | yes | yes | yes |
|
|
113
|
+
| OpenCode | yes | yes | yes |
|
|
113
114
|
|
|
114
115
|
Any other MCP client can use notify and ask by pointing at the Pushary MCP endpoint directly.
|
|
115
116
|
|
|
@@ -182,7 +183,7 @@ failing closed would let one malformed download silence every hook on the machin
|
|
|
182
183
|
asserts this list matches it. Do not edit one without the other. -->
|
|
183
184
|
|
|
184
185
|
```
|
|
185
|
-
setup Configure Claude Code, Codex, Gemini CLI, Hermes, Cursor,
|
|
186
|
+
setup Configure Claude Code, Codex, Gemini CLI, Hermes, Cursor, VS Code, or OpenCode with Pushary
|
|
186
187
|
claude Run Claude Code through Pushary, reachable from your phone even when idle
|
|
187
188
|
daemon Keep this machine reachable so your phone can START a new session
|
|
188
189
|
status One screen: which key is in force, whether the server accepts it, what can receive an approval
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleBell
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-VPLQFXCH.js";
|
|
5
5
|
import {
|
|
6
6
|
readHookInput
|
|
7
7
|
} from "../chunk-6CUUA7HO.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-RNKPLUWC.js";
|
|
9
9
|
import "../chunk-BC3VCZ3E.js";
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-XREW7RQ3.js";
|
|
11
|
+
import "../chunk-4LL6LG5X.js";
|
|
12
12
|
import "../chunk-2UMNXADU.js";
|
|
13
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-3MCEES6Z.js";
|
|
14
14
|
|
|
15
15
|
// bin/pushary-bell-hook.ts
|
|
16
16
|
var main = async () => {
|
package/dist/bin/pushary-bell.js
CHANGED
|
@@ -5,38 +5,38 @@ import {
|
|
|
5
5
|
liveAgentCount,
|
|
6
6
|
ring,
|
|
7
7
|
upgradeThreshold
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-VPLQFXCH.js";
|
|
9
9
|
import {
|
|
10
10
|
guardBinary
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
11
|
+
} from "../chunk-VF3RTVWY.js";
|
|
12
|
+
import "../chunk-L4ZCFMWW.js";
|
|
13
13
|
import {
|
|
14
14
|
claudeSettings
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-OVEGEQSB.js";
|
|
16
16
|
import {
|
|
17
17
|
createIo
|
|
18
18
|
} from "../chunk-5RUIFDTP.js";
|
|
19
|
-
import "../chunk-3CUDMTL4.js";
|
|
20
19
|
import {
|
|
21
20
|
parseFlags
|
|
22
21
|
} from "../chunk-GMXKITVA.js";
|
|
23
22
|
import {
|
|
24
23
|
exitOnHelpFlag
|
|
25
|
-
} from "../chunk-
|
|
26
|
-
import "../chunk-
|
|
27
|
-
import "../chunk-BC3VCZ3E.js";
|
|
24
|
+
} from "../chunk-OTPJ4FUR.js";
|
|
25
|
+
import "../chunk-RLQWNEPM.js";
|
|
28
26
|
import "../chunk-DINDL2CF.js";
|
|
29
|
-
import "../chunk-
|
|
27
|
+
import "../chunk-RNKPLUWC.js";
|
|
28
|
+
import "../chunk-BC3VCZ3E.js";
|
|
30
29
|
import "../chunk-7QLSKOSU.js";
|
|
31
30
|
import {
|
|
32
31
|
EXIT
|
|
33
32
|
} from "../chunk-KZERVKTD.js";
|
|
33
|
+
import "../chunk-XREW7RQ3.js";
|
|
34
34
|
import {
|
|
35
35
|
readJsonSafe,
|
|
36
36
|
writeJsonAtomic
|
|
37
|
-
} from "../chunk-
|
|
37
|
+
} from "../chunk-4LL6LG5X.js";
|
|
38
38
|
import "../chunk-2UMNXADU.js";
|
|
39
|
-
import "../chunk-
|
|
39
|
+
import "../chunk-3MCEES6Z.js";
|
|
40
40
|
|
|
41
41
|
// src/bell/install.ts
|
|
42
42
|
import { join } from "path";
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ensureDaemonRunning
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-PS7DQ2SU.js";
|
|
5
5
|
import {
|
|
6
6
|
buildTranscriptRecords,
|
|
7
7
|
decodePublicKey,
|
|
8
|
-
fetchAccountPublicKey,
|
|
9
8
|
generateSessionKey,
|
|
10
|
-
uploadTranscriptRecords,
|
|
11
9
|
wrapSessionKey
|
|
12
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-WDVQ6O4C.js";
|
|
11
|
+
import {
|
|
12
|
+
fetchTranscriptRecipients,
|
|
13
|
+
uploadTranscriptRecords
|
|
14
|
+
} from "../chunk-QZCFVWA7.js";
|
|
15
|
+
import {
|
|
16
|
+
transcriptsEnabled
|
|
17
|
+
} from "../chunk-JJV5Q4KG.js";
|
|
13
18
|
import {
|
|
14
19
|
startProcessOwnership,
|
|
15
20
|
withSpawnProcessOwnership
|
|
16
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-6GU55AQQ.js";
|
|
17
22
|
import {
|
|
18
23
|
needsShell,
|
|
19
24
|
spawnClaude
|
|
20
25
|
} from "../chunk-XHKBHWLX.js";
|
|
21
|
-
import "../chunk-
|
|
26
|
+
import "../chunk-L4ZCFMWW.js";
|
|
22
27
|
import {
|
|
23
28
|
isDeferAnswer
|
|
24
29
|
} from "../chunk-YHG74UFF.js";
|
|
@@ -27,7 +32,7 @@ import {
|
|
|
27
32
|
getPolicy,
|
|
28
33
|
repoKeyFor,
|
|
29
34
|
reportEvent
|
|
30
|
-
} from "../chunk-
|
|
35
|
+
} from "../chunk-OADGBOP7.js";
|
|
31
36
|
import {
|
|
32
37
|
DEFAULT_SESSION,
|
|
33
38
|
askUser,
|
|
@@ -39,23 +44,26 @@ import {
|
|
|
39
44
|
removePendingCommand,
|
|
40
45
|
savePendingCommand,
|
|
41
46
|
waitForAnswer
|
|
42
|
-
} from "../chunk-
|
|
47
|
+
} from "../chunk-JHJHCMAF.js";
|
|
43
48
|
import {
|
|
49
|
+
UNNAMED_ACTION,
|
|
44
50
|
deriveToolTarget,
|
|
45
51
|
describeToolCall,
|
|
46
52
|
scopePathFor
|
|
47
|
-
} from "../chunk-
|
|
53
|
+
} from "../chunk-JVZX5YRJ.js";
|
|
48
54
|
import {
|
|
49
55
|
getMachineId
|
|
50
|
-
} from "../chunk-
|
|
51
|
-
import "../chunk-
|
|
56
|
+
} from "../chunk-5NDB3YOE.js";
|
|
57
|
+
import "../chunk-OVEGEQSB.js";
|
|
52
58
|
import "../chunk-DINDL2CF.js";
|
|
59
|
+
import "../chunk-RNKPLUWC.js";
|
|
60
|
+
import "../chunk-BC3VCZ3E.js";
|
|
53
61
|
import "../chunk-BSZYIAZL.js";
|
|
54
62
|
import "../chunk-SAF6HGAA.js";
|
|
55
|
-
import "../chunk-IUWRVTQK.js";
|
|
56
63
|
import "../chunk-7QLSKOSU.js";
|
|
57
64
|
import "../chunk-KZERVKTD.js";
|
|
58
|
-
import "../chunk-
|
|
65
|
+
import "../chunk-XREW7RQ3.js";
|
|
66
|
+
import "../chunk-4LL6LG5X.js";
|
|
59
67
|
import {
|
|
60
68
|
getApiKey,
|
|
61
69
|
getBaseUrl
|
|
@@ -63,7 +71,7 @@ import {
|
|
|
63
71
|
import {
|
|
64
72
|
KILL_REASON,
|
|
65
73
|
resolveGate
|
|
66
|
-
} from "../chunk-
|
|
74
|
+
} from "../chunk-3MCEES6Z.js";
|
|
67
75
|
|
|
68
76
|
// src/wrapper/claudeBinary.ts
|
|
69
77
|
import { statSync } from "fs";
|
|
@@ -142,7 +150,6 @@ import { basename } from "path";
|
|
|
142
150
|
import { homedir } from "os";
|
|
143
151
|
import { join as join2, resolve } from "path";
|
|
144
152
|
import { existsSync, readFileSync } from "fs";
|
|
145
|
-
var transcriptsEnabled = () => process.env.PUSHARY_TRANSCRIPTS === "1";
|
|
146
153
|
var claudeTranscriptPath = (cwd, sessionId) => {
|
|
147
154
|
const projectId = resolve(cwd).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
148
155
|
const configDir = process.env.CLAUDE_CONFIG_DIR || join2(homedir(), ".claude");
|
|
@@ -151,6 +158,8 @@ var claudeTranscriptPath = (cwd, sessionId) => {
|
|
|
151
158
|
var createSyncState = () => ({
|
|
152
159
|
sessionKey: null,
|
|
153
160
|
wrappedKey: null,
|
|
161
|
+
providerWrappedKey: null,
|
|
162
|
+
providerKeyId: null,
|
|
154
163
|
seen: /* @__PURE__ */ new Set(),
|
|
155
164
|
keyUploaded: false,
|
|
156
165
|
keyRejected: false
|
|
@@ -159,30 +168,77 @@ var syncTick = async (lines, state, uploader, sessionId, machineId) => {
|
|
|
159
168
|
if (state.keyRejected) return state;
|
|
160
169
|
let sessionKey = state.sessionKey;
|
|
161
170
|
let wrappedKey = state.wrappedKey;
|
|
171
|
+
let providerWrappedKey = state.providerWrappedKey;
|
|
172
|
+
let providerKeyId = state.providerKeyId;
|
|
162
173
|
if (!sessionKey) {
|
|
163
|
-
const
|
|
164
|
-
if (!
|
|
174
|
+
const recipients = await uploader.fetchTranscriptRecipients();
|
|
175
|
+
if (!recipients) return state;
|
|
165
176
|
sessionKey = generateSessionKey();
|
|
166
|
-
wrappedKey = wrapSessionKey(sessionKey, decodePublicKey(
|
|
177
|
+
wrappedKey = wrapSessionKey(sessionKey, decodePublicKey(recipients.phonePublicKey));
|
|
178
|
+
if (recipients.provider) {
|
|
179
|
+
providerWrappedKey = wrapSessionKey(sessionKey, decodePublicKey(recipients.provider.publicKey));
|
|
180
|
+
providerKeyId = recipients.provider.keyId;
|
|
181
|
+
}
|
|
167
182
|
}
|
|
168
183
|
const built = buildTranscriptRecords(lines, sessionKey, state.seen);
|
|
169
184
|
if (built.records.length === 0) {
|
|
170
|
-
return { ...state, sessionKey, wrappedKey, seen: built.seen };
|
|
185
|
+
return { ...state, sessionKey, wrappedKey, providerWrappedKey, providerKeyId, seen: built.seen };
|
|
186
|
+
}
|
|
187
|
+
const seen = new Set(state.seen);
|
|
188
|
+
let keyUploaded = state.keyUploaded;
|
|
189
|
+
let keyRejected = false;
|
|
190
|
+
let rejected = 0;
|
|
191
|
+
for (const chunk of chunkRecords(built.records)) {
|
|
192
|
+
const result = await uploader.uploadRecords({
|
|
193
|
+
sessionId,
|
|
194
|
+
machineId,
|
|
195
|
+
dataEncryptionKey: keyUploaded ? void 0 : wrappedKey ?? void 0,
|
|
196
|
+
providerWrappedSessionKey: keyUploaded ? void 0 : providerWrappedKey ?? void 0,
|
|
197
|
+
providerKeyId: keyUploaded ? void 0 : providerKeyId ?? void 0,
|
|
198
|
+
records: chunk
|
|
199
|
+
});
|
|
200
|
+
keyUploaded = keyUploaded || result.keyRegistered;
|
|
201
|
+
if (result.keyConflict) {
|
|
202
|
+
keyRejected = true;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
if (!result.ok) break;
|
|
206
|
+
for (const id of result.settledIds ?? chunk.map((r) => r.localId)) seen.add(id);
|
|
207
|
+
rejected += result.rejectedIds.length;
|
|
208
|
+
}
|
|
209
|
+
if (rejected > 0) {
|
|
210
|
+
process.stderr.write(`[pushary] transcript: ${rejected} record${rejected === 1 ? "" : "s"} could not be stored and will not be retried.
|
|
211
|
+
`);
|
|
171
212
|
}
|
|
172
|
-
const result = await uploader.uploadRecords({
|
|
173
|
-
sessionId,
|
|
174
|
-
machineId,
|
|
175
|
-
dataEncryptionKey: state.keyUploaded ? void 0 : wrappedKey ?? void 0,
|
|
176
|
-
records: built.records
|
|
177
|
-
});
|
|
178
213
|
return {
|
|
179
214
|
sessionKey,
|
|
180
215
|
wrappedKey,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
216
|
+
providerWrappedKey,
|
|
217
|
+
providerKeyId,
|
|
218
|
+
seen,
|
|
219
|
+
keyUploaded,
|
|
220
|
+
keyRejected
|
|
184
221
|
};
|
|
185
222
|
};
|
|
223
|
+
var MAX_RECORDS_PER_BATCH = 200;
|
|
224
|
+
var MAX_BATCH_BYTES = 3e6;
|
|
225
|
+
var chunkRecords = (records) => {
|
|
226
|
+
const chunks = [];
|
|
227
|
+
let current = [];
|
|
228
|
+
let bytes = 0;
|
|
229
|
+
for (const record of records) {
|
|
230
|
+
const size = record.content.length;
|
|
231
|
+
if (current.length > 0 && (current.length >= MAX_RECORDS_PER_BATCH || bytes + size > MAX_BATCH_BYTES)) {
|
|
232
|
+
chunks.push(current);
|
|
233
|
+
current = [];
|
|
234
|
+
bytes = 0;
|
|
235
|
+
}
|
|
236
|
+
current.push(record);
|
|
237
|
+
bytes += size;
|
|
238
|
+
}
|
|
239
|
+
if (current.length > 0) chunks.push(current);
|
|
240
|
+
return chunks;
|
|
241
|
+
};
|
|
186
242
|
var readLines = (path) => {
|
|
187
243
|
try {
|
|
188
244
|
return existsSync(path) ? readFileSync(path, "utf-8").split("\n") : [];
|
|
@@ -190,9 +246,11 @@ var readLines = (path) => {
|
|
|
190
246
|
return [];
|
|
191
247
|
}
|
|
192
248
|
};
|
|
249
|
+
var IDLE_CEILING_MS = 6e4;
|
|
250
|
+
var nextInterval = (previousMs, idle, baseMs, maxMs) => idle ? Math.min(previousMs * 2, maxMs) : baseMs;
|
|
193
251
|
var startTranscriptSync = (opts) => {
|
|
194
252
|
const uploader = {
|
|
195
|
-
|
|
253
|
+
fetchTranscriptRecipients: () => fetchTranscriptRecipients(opts.apiKey),
|
|
196
254
|
uploadRecords: (params) => uploadTranscriptRecords(opts.apiKey, params)
|
|
197
255
|
};
|
|
198
256
|
const path = claudeTranscriptPath(opts.cwd, opts.sessionId);
|
|
@@ -209,12 +267,26 @@ var startTranscriptSync = (opts) => {
|
|
|
209
267
|
inFlight = false;
|
|
210
268
|
}
|
|
211
269
|
};
|
|
212
|
-
const
|
|
213
|
-
|
|
270
|
+
const baseIntervalMs = opts.intervalMs ?? 3e3;
|
|
271
|
+
const maxIntervalMs = Math.max(baseIntervalMs, IDLE_CEILING_MS);
|
|
272
|
+
let timer;
|
|
273
|
+
const schedule = (delayMs) => {
|
|
274
|
+
if (!running) return;
|
|
275
|
+
timer = setTimeout(() => {
|
|
276
|
+
void (async () => {
|
|
277
|
+
const before = state.sessionKey;
|
|
278
|
+
await tick();
|
|
279
|
+
const idle = state.sessionKey === null && before === null;
|
|
280
|
+
schedule(nextInterval(delayMs, idle, baseIntervalMs, maxIntervalMs));
|
|
281
|
+
})();
|
|
282
|
+
}, delayMs);
|
|
283
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
284
|
+
};
|
|
214
285
|
void tick();
|
|
286
|
+
schedule(baseIntervalMs);
|
|
215
287
|
return () => {
|
|
216
288
|
running = false;
|
|
217
|
-
|
|
289
|
+
if (timer) clearTimeout(timer);
|
|
218
290
|
};
|
|
219
291
|
};
|
|
220
292
|
|
|
@@ -342,9 +414,13 @@ var BoundedInputQueue = class {
|
|
|
342
414
|
}
|
|
343
415
|
// Drop every buffered item without closing the queue. Used when phone control
|
|
344
416
|
// turns out to be unavailable (offline SDK setup) so a pending instruction does
|
|
345
|
-
// not make the local leg switch again into a mode that cannot run.
|
|
417
|
+
// not make the local leg switch again into a mode that cannot run. Returns how
|
|
418
|
+
// many were dropped, because these are somebody's words and the caller has to
|
|
419
|
+
// be able to say they are gone.
|
|
346
420
|
clear() {
|
|
421
|
+
const dropped = this.buffer.length;
|
|
347
422
|
this.buffer.length = 0;
|
|
423
|
+
return dropped;
|
|
348
424
|
}
|
|
349
425
|
get isClosed() {
|
|
350
426
|
return this.closed;
|
|
@@ -482,7 +558,7 @@ var createRemoteApprover = (deps) => {
|
|
|
482
558
|
let question;
|
|
483
559
|
try {
|
|
484
560
|
question = await askUser2(deps.apiKey, {
|
|
485
|
-
question: `Allow ${description}?`,
|
|
561
|
+
question: `Allow ${description || UNNAMED_ACTION}?`,
|
|
486
562
|
type: "confirm",
|
|
487
563
|
context: `Your agent wants to run this in ${deps.projectName} (you are away)`,
|
|
488
564
|
agentName: `Claude Code - ${deps.projectName}`,
|
|
@@ -1128,7 +1204,12 @@ var runLocalLeg = (session, deps = {}) => {
|
|
|
1128
1204
|
const control = {
|
|
1129
1205
|
onPhoneCommand: () => {
|
|
1130
1206
|
if (!exitReason) exitReason = "switch";
|
|
1131
|
-
if (!legAbort.signal.aborted)
|
|
1207
|
+
if (!legAbort.signal.aborted) {
|
|
1208
|
+
process.stderr.write(
|
|
1209
|
+
"\n[pushary] instruction from your phone \u2014 taking this session over to run it (Ctrl-] hands the terminal back)\n"
|
|
1210
|
+
);
|
|
1211
|
+
legAbort.abort();
|
|
1212
|
+
}
|
|
1132
1213
|
},
|
|
1133
1214
|
stop: (reason) => {
|
|
1134
1215
|
if (!exitReason) exitReason = { type: "exit", code: reason === "signal" ? 130 : 0 };
|
|
@@ -1482,6 +1563,7 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1482
1563
|
} else if (message.type === "result") {
|
|
1483
1564
|
if (typeof message.total_cost_usd === "number") totalCostUsd = message.total_cost_usd;
|
|
1484
1565
|
process.stderr.write("[pushary] turn complete \u2014 standing by for phone commands\n");
|
|
1566
|
+
void announcePresence("stop", "Turn complete");
|
|
1485
1567
|
}
|
|
1486
1568
|
};
|
|
1487
1569
|
const poller = startCommandPoller({
|
|
@@ -1546,6 +1628,7 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1546
1628
|
void announcePresence("session_start", "Session ready \u2014 reachable from your phone");
|
|
1547
1629
|
ensurePresenceHeartbeat();
|
|
1548
1630
|
ensureTranscriptSync();
|
|
1631
|
+
void ensureSdk();
|
|
1549
1632
|
}
|
|
1550
1633
|
if (startingMode === "remote" && initialPrompt) input.push(userMessage(initialPrompt));
|
|
1551
1634
|
const exitCode = await driveModeLoop({
|
|
@@ -1555,13 +1638,19 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1555
1638
|
const ready = await ensureSdk();
|
|
1556
1639
|
if (!ready) {
|
|
1557
1640
|
remoteAvailable = false;
|
|
1558
|
-
input.clear();
|
|
1641
|
+
const dropped = input.clear();
|
|
1559
1642
|
if (!noticedUnavailable) {
|
|
1560
1643
|
noticedUnavailable = true;
|
|
1561
1644
|
process.stderr.write(
|
|
1562
1645
|
"[pushary] could not start phone control (offline?); staying in the terminal. Relaunch when online to enable.\n"
|
|
1563
1646
|
);
|
|
1564
1647
|
}
|
|
1648
|
+
if (dropped > 0) {
|
|
1649
|
+
process.stderr.write(
|
|
1650
|
+
`[pushary] ${dropped} instruction(s) from your phone could not be delivered and were dropped.
|
|
1651
|
+
`
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1565
1654
|
return "switch";
|
|
1566
1655
|
}
|
|
1567
1656
|
return runRemoteLeg(session, ready);
|
|
@@ -3,13 +3,16 @@ import {
|
|
|
3
3
|
HERMES_CONFIG_SCRIPT,
|
|
4
4
|
resolveHermesPython,
|
|
5
5
|
runHermesPythonScript
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-QFJZHVVL.js";
|
|
7
7
|
import {
|
|
8
8
|
removeInstructionBlock,
|
|
9
9
|
shortenHome,
|
|
10
10
|
unregisterPluginLocation,
|
|
11
11
|
vscodeSettingsTargets
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-3XSP5SML.js";
|
|
13
|
+
import {
|
|
14
|
+
pusharyHookExecutable
|
|
15
|
+
} from "../chunk-NESKLV6R.js";
|
|
13
16
|
import {
|
|
14
17
|
OPENCODE_PLUGIN_SIBLING,
|
|
15
18
|
OPENCODE_PLUGIN_SIBLING_CONTENTS,
|
|
@@ -19,25 +22,25 @@ import {
|
|
|
19
22
|
openCodePluginDir,
|
|
20
23
|
openCodePluginFile,
|
|
21
24
|
removeOpenCodeMcpServer
|
|
22
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-7DK3ZH3N.js";
|
|
23
26
|
import {
|
|
24
27
|
isClaudeMcpSnapshot,
|
|
25
28
|
removeClaudeMcpServers,
|
|
26
29
|
removeGeminiSettings,
|
|
27
30
|
removePusharySettings,
|
|
28
31
|
restoreClaudeMcpServer
|
|
29
|
-
} from "../chunk-
|
|
30
|
-
import "../chunk-
|
|
31
|
-
import "../chunk-
|
|
32
|
+
} from "../chunk-6V7ZDH3A.js";
|
|
33
|
+
import "../chunk-KVHP5ZME.js";
|
|
34
|
+
import "../chunk-VF3RTVWY.js";
|
|
32
35
|
import {
|
|
33
36
|
disableDaemonService
|
|
34
|
-
} from "../chunk-
|
|
37
|
+
} from "../chunk-PS7DQ2SU.js";
|
|
35
38
|
import "../chunk-XHKBHWLX.js";
|
|
36
39
|
import {
|
|
37
40
|
execNpm
|
|
38
|
-
} from "../chunk-
|
|
39
|
-
import "../chunk-
|
|
40
|
-
import "../chunk-
|
|
41
|
+
} from "../chunk-L4ZCFMWW.js";
|
|
42
|
+
import "../chunk-JVZX5YRJ.js";
|
|
43
|
+
import "../chunk-5NDB3YOE.js";
|
|
41
44
|
import {
|
|
42
45
|
claudeJson,
|
|
43
46
|
claudeMcpReceipt,
|
|
@@ -55,32 +58,33 @@ import {
|
|
|
55
58
|
geminiMd,
|
|
56
59
|
geminiSettings,
|
|
57
60
|
hermesConfigReceipt,
|
|
61
|
+
isProtectedByScope,
|
|
58
62
|
pusharyDir,
|
|
59
63
|
removeCodexHookTrust,
|
|
60
64
|
removeCodexHooks,
|
|
61
65
|
vscodePluginDir
|
|
62
|
-
} from "../chunk-
|
|
66
|
+
} from "../chunk-OVEGEQSB.js";
|
|
63
67
|
import {
|
|
64
68
|
rejectUnknownFlags
|
|
65
69
|
} from "../chunk-GMXKITVA.js";
|
|
66
70
|
import {
|
|
67
71
|
exitOnHelpFlag
|
|
68
|
-
} from "../chunk-
|
|
69
|
-
import "../chunk-
|
|
72
|
+
} from "../chunk-OTPJ4FUR.js";
|
|
73
|
+
import "../chunk-RLQWNEPM.js";
|
|
74
|
+
import "../chunk-DINDL2CF.js";
|
|
70
75
|
import {
|
|
71
76
|
removeClaudeAlias
|
|
72
77
|
} from "../chunk-BC3VCZ3E.js";
|
|
73
|
-
import "../chunk-DINDL2CF.js";
|
|
74
78
|
import "../chunk-7QLSKOSU.js";
|
|
75
79
|
import {
|
|
76
80
|
EXIT
|
|
77
81
|
} from "../chunk-KZERVKTD.js";
|
|
78
|
-
import "../chunk-
|
|
82
|
+
import "../chunk-4LL6LG5X.js";
|
|
79
83
|
import "../chunk-2UMNXADU.js";
|
|
80
84
|
import {
|
|
81
85
|
CURSOR_GATE_EVENTS,
|
|
82
86
|
HOOK_LOCATOR_BINARY
|
|
83
|
-
} from "../chunk-
|
|
87
|
+
} from "../chunk-3MCEES6Z.js";
|
|
84
88
|
|
|
85
89
|
// bin/pushary-clean.ts
|
|
86
90
|
import { existsSync, readFileSync, copyFileSync, readdirSync } from "fs";
|
|
@@ -146,9 +150,9 @@ var VSCODE_PLUGIN_DIR = vscodePluginDir();
|
|
|
146
150
|
var PUSHARY_DIR = pusharyDir();
|
|
147
151
|
var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
|
|
148
152
|
var macAppInstalled = () => existsSync(join(PUSHARY_DIR, "bin", HOOK_LOCATOR_BINARY));
|
|
149
|
-
var removalScope = () => ({
|
|
153
|
+
var removalScope = (keepMcpServer) => ({
|
|
150
154
|
keepAppWiring: true,
|
|
151
|
-
keepMcpServer
|
|
155
|
+
keepMcpServer
|
|
152
156
|
});
|
|
153
157
|
var readJson = (path) => {
|
|
154
158
|
try {
|
|
@@ -189,7 +193,7 @@ var guardedAliasRemoval = () => {
|
|
|
189
193
|
var writeJson = (path, data) => {
|
|
190
194
|
guardedWrite(path, JSON.stringify(data, null, 2) + "\n");
|
|
191
195
|
};
|
|
192
|
-
var removeCursorUserHook = () => {
|
|
196
|
+
var removeCursorUserHook = (scope) => {
|
|
193
197
|
if (!existsSync(CURSOR_USER_HOOKS)) return "absent";
|
|
194
198
|
let parsed;
|
|
195
199
|
try {
|
|
@@ -202,7 +206,10 @@ var removeCursorUserHook = () => {
|
|
|
202
206
|
let removedAny = false;
|
|
203
207
|
for (const event of CURSOR_GATE_EVENTS) {
|
|
204
208
|
const existing = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
205
|
-
const others = existing.filter((h) =>
|
|
209
|
+
const others = existing.filter((h) => {
|
|
210
|
+
const command = String(h.command ?? "");
|
|
211
|
+
return pusharyHookExecutable(command) === null || isProtectedByScope(command, scope);
|
|
212
|
+
});
|
|
206
213
|
if (others.length === existing.length) continue;
|
|
207
214
|
removedAny = true;
|
|
208
215
|
if (others.length === 0) delete hooks[event];
|
|
@@ -221,7 +228,10 @@ var removeCursorUserHook = () => {
|
|
|
221
228
|
const afterHooks = after?.hooks ?? {};
|
|
222
229
|
const stillThere = CURSOR_GATE_EVENTS.some((event) => {
|
|
223
230
|
const entries = afterHooks[event];
|
|
224
|
-
return Array.isArray(entries) && entries.some((h) =>
|
|
231
|
+
return Array.isArray(entries) && entries.some((h) => {
|
|
232
|
+
const command = String(h?.command ?? "");
|
|
233
|
+
return pusharyHookExecutable(command) !== null && !isProtectedByScope(command, scope);
|
|
234
|
+
});
|
|
225
235
|
});
|
|
226
236
|
if (stillThere) return "failed";
|
|
227
237
|
} catch {
|
|
@@ -229,13 +239,13 @@ var removeCursorUserHook = () => {
|
|
|
229
239
|
}
|
|
230
240
|
return "removed";
|
|
231
241
|
};
|
|
232
|
-
var cleanSettingsFile = (path, label) => {
|
|
242
|
+
var cleanSettingsFile = (path, label, scope) => {
|
|
233
243
|
const data = readJson(path);
|
|
234
244
|
if (!data) {
|
|
235
245
|
console.log(` ${skip} ${label} ${dim("(not found)")}`);
|
|
236
246
|
return;
|
|
237
247
|
}
|
|
238
|
-
const changed = removePusharySettings(data,
|
|
248
|
+
const changed = removePusharySettings(data, scope);
|
|
239
249
|
if (changed) {
|
|
240
250
|
writeJson(path, data);
|
|
241
251
|
console.log(` ${check} ${label} ${dim(did("cleaned"))}`);
|
|
@@ -280,6 +290,7 @@ var main = async () => {
|
|
|
280
290
|
}
|
|
281
291
|
console.log();
|
|
282
292
|
const keepShared = macAppInstalled();
|
|
293
|
+
const scope = removalScope(keepShared);
|
|
283
294
|
if (keepShared) {
|
|
284
295
|
console.log(` ${dim("The Mac app is installed, so its wiring and the state it owns are left alone.")}`);
|
|
285
296
|
console.log();
|
|
@@ -293,8 +304,8 @@ var main = async () => {
|
|
|
293
304
|
process.exit(EXIT.FAILED);
|
|
294
305
|
}
|
|
295
306
|
console.log(` ${check} Background service ${dim(did("uninstalled"))}`);
|
|
296
|
-
cleanSettingsFile(CLAUDE_SETTINGS, "Claude Code settings");
|
|
297
|
-
cleanSettingsFile(CLAUDE_SETTINGS_LOCAL, "Claude Code settings.local");
|
|
307
|
+
cleanSettingsFile(CLAUDE_SETTINGS, "Claude Code settings", scope);
|
|
308
|
+
cleanSettingsFile(CLAUDE_SETTINGS_LOCAL, "Claude Code settings.local", scope);
|
|
298
309
|
const claudeJson2 = keepShared ? null : readJson(CLAUDE_JSON);
|
|
299
310
|
if (keepShared) {
|
|
300
311
|
console.log(` ${skip} Claude Code MCP servers ${dim("(kept: the Mac app uses it)")}`);
|
|
@@ -332,7 +343,7 @@ var main = async () => {
|
|
|
332
343
|
} else {
|
|
333
344
|
console.log(` ${skip} Cursor MCP config ${dim("(not found)")}`);
|
|
334
345
|
}
|
|
335
|
-
const cursorGate = removeCursorUserHook();
|
|
346
|
+
const cursorGate = removeCursorUserHook(scope);
|
|
336
347
|
if (cursorGate === "removed") {
|
|
337
348
|
console.log(` ${check} Cursor gate ${dim("(removed from ~/.cursor/hooks.json)")}`);
|
|
338
349
|
} else if (cursorGate === "absent") {
|
|
@@ -410,7 +421,7 @@ var main = async () => {
|
|
|
410
421
|
console.log(` ${skip} Codex config ${dim("(not found)")}`);
|
|
411
422
|
}
|
|
412
423
|
if (codexHooksData) {
|
|
413
|
-
if (removeCodexHooks(codexHooksData,
|
|
424
|
+
if (removeCodexHooks(codexHooksData, scope)) {
|
|
414
425
|
writeJson(codexHooksJson2, codexHooksData);
|
|
415
426
|
console.log(` ${check} Codex hooks ${dim("(removed from ~/.codex/hooks.json)")}`);
|
|
416
427
|
} else {
|
|
@@ -435,7 +446,7 @@ var main = async () => {
|
|
|
435
446
|
const geminiSettingsPath = geminiSettings();
|
|
436
447
|
const geminiSettings2 = readJson(geminiSettingsPath);
|
|
437
448
|
if (geminiSettings2) {
|
|
438
|
-
if (removeGeminiSettings(geminiSettings2,
|
|
449
|
+
if (removeGeminiSettings(geminiSettings2, scope)) {
|
|
439
450
|
writeJson(geminiSettingsPath, geminiSettings2);
|
|
440
451
|
console.log(` ${check} Gemini CLI settings ${dim(did("cleaned"))}`);
|
|
441
452
|
} else {
|