@pushary/agent-hooks 0.89.11 → 0.89.13
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 +55 -0
- package/README.md +4 -2
- package/dist/bin/pushary-bell-hook.js +5 -5
- package/dist/bin/pushary-bell.js +11 -11
- package/dist/bin/pushary-claude.js +225 -48
- package/dist/bin/pushary-clean.js +49 -29
- package/dist/bin/pushary-codex-bridge.js +17 -16
- package/dist/bin/pushary-codex-hook.js +11 -10
- package/dist/bin/pushary-codex.js +7 -7
- 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 +4 -4
- package/dist/bin/pushary-gemini-bridge.js +16 -15
- package/dist/bin/pushary-gemini-hook.js +10 -9
- package/dist/bin/pushary-hook.js +13 -13
- 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 +7 -7
- package/dist/bin/pushary-opencode-hook.js +10 -9
- package/dist/bin/pushary-permission-denied-hook.js +11 -11
- package/dist/bin/pushary-permission-hook.js +11 -11
- package/dist/bin/pushary-post-hook.js +7 -7
- package/dist/bin/pushary-prompt-hook.js +7 -7
- package/dist/bin/pushary-session-end-hook.js +7 -7
- package/dist/bin/pushary-session-start-hook.js +7 -7
- package/dist/bin/pushary-setup.js +87 -59
- package/dist/bin/pushary-stats.js +4 -4
- package/dist/bin/pushary-status.js +14 -14
- package/dist/bin/pushary-stop-hook.js +7 -7
- package/dist/bin/pushary-stopfailure-hook.js +7 -7
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-transcripts.d.ts +1 -0
- package/dist/bin/pushary-transcripts.js +79 -0
- package/dist/bin/pushary-upgrade.js +21 -21
- package/dist/bin/pushary-wait.js +5 -5
- package/dist/bin/pushary.js +3 -2
- package/dist/{chunk-6XUABEX2.js → chunk-3OXHYPZG.js} +1 -1
- package/dist/{chunk-QA72MKIT.js → chunk-3YGGEDJK.js} +5 -5
- package/dist/{chunk-IUN6VOKV.js → chunk-4IPSMEGT.js} +68 -46
- package/dist/{chunk-XX2V4QS5.js → chunk-4LL6LG5X.js} +2 -2
- package/dist/{chunk-J4K75V4D.js → chunk-5MNTWAAJ.js} +10 -9
- package/dist/{chunk-KVE5RCQL.js → chunk-5YEF55AX.js} +1 -1
- package/dist/{chunk-3SQYKLBW.js → chunk-7JZCADBR.js} +46 -18
- package/dist/{chunk-IUWRVTQK.js → chunk-ANMHIZZY.js} +1 -1
- package/dist/{chunk-6EKQZA6R.js → chunk-B2HND22S.js} +1 -1
- package/dist/{chunk-TRVIRETT.js → chunk-C56VHSXU.js} +1 -1
- package/dist/{chunk-TEZ2FZD6.js → chunk-CBGGUEKE.js} +2 -2
- package/dist/{chunk-3HFK3UXM.js → chunk-FOGW6O2Y.js} +1 -1
- package/dist/{chunk-3XTFX4CL.js → chunk-GMJDQDXU.js} +17 -2
- package/dist/{chunk-DKWM5T3P.js → chunk-JTBDBKHU.js} +2 -2
- package/dist/{chunk-Q223OM5J.js → chunk-JVLB5CAJ.js} +4 -4
- package/dist/{chunk-2LPEZYMC.js → chunk-KNLKHJZR.js} +1 -1
- package/dist/{chunk-X4ZUUFKV.js → chunk-L4ZCFMWW.js} +63 -1
- package/dist/chunk-M3UABYRT.js +20 -0
- package/dist/{chunk-PZRWFWSR.js → chunk-MNTZKFZI.js} +7 -7
- package/dist/{chunk-FZ73G2RO.js → chunk-MSTHT2M7.js} +25 -6
- package/dist/{chunk-TG3T3M4K.js → chunk-N4QJ3CLX.js} +6 -2
- package/dist/{chunk-3CUDMTL4.js → chunk-NBDU3R3G.js} +10 -3
- package/dist/{chunk-FXIL7RCL.js → chunk-OX35BSPV.js} +4 -4
- package/dist/{chunk-UDBJHQMG.js → chunk-QFJZHVVL.js} +1 -1
- package/dist/chunk-QZCFVWA7.js +58 -0
- package/dist/{chunk-TY5HVMXT.js → chunk-RAEPAUNJ.js} +1 -1
- package/dist/{chunk-NCVIWGVA.js → chunk-S4BLNILH.js} +1 -1
- package/dist/{chunk-MZGWKIUZ.js → chunk-VASZ6TWW.js} +7 -3
- package/dist/{chunk-SYUIXXQW.js → chunk-VF3RTVWY.js} +1 -1
- package/dist/{chunk-LURY4WNF.js → chunk-WHDSJ6PD.js} +1 -1
- package/dist/chunk-YKLCWWEI.js +76 -0
- package/dist/{chunk-253WC6O5.js → chunk-YXT55PYF.js} +1 -1
- package/dist/{chunk-BLODMQKJ.js → chunk-ZO43JTT3.js} +1 -1
- package/dist/{chunk-OLAGKKSR.js → chunk-ZP3SNUP7.js} +16 -6
- package/dist/{chunk-34GN3IPX.js → chunk-ZWEKONU6.js} +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +15 -11
- package/package.json +4 -2
- /package/dist/{chunk-X6RXX2NF.js → chunk-QINYXLA5.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.89.13
|
|
4
|
+
|
|
5
|
+
### Synced transcripts survive large turns and restarts safely
|
|
6
|
+
|
|
7
|
+
Oversized file reads and diffs are shortened before encryption instead of being
|
|
8
|
+
rejected by the server, with the removed UTF-8 byte count recorded in the turn.
|
|
9
|
+
Stable HMAC identifiers prevent transcript contents leaking through record ids,
|
|
10
|
+
and a resumed session reuses its persisted encryption key rather than conflicting
|
|
11
|
+
with the key already registered for that session.
|
|
12
|
+
|
|
13
|
+
Transcript recipients are now pinned on first use and later key changes stop sync
|
|
14
|
+
until the user explicitly runs `pushary transcripts trust --rotate`. First use
|
|
15
|
+
also fails closed when that pin cannot be stored, so no session key is wrapped to
|
|
16
|
+
an unrecorded recipient. New sessions can independently wrap their key for the
|
|
17
|
+
owner's phone and an optional audited compliance-recovery recipient; installations
|
|
18
|
+
without one remain compatible.
|
|
19
|
+
|
|
20
|
+
## 0.89.12
|
|
21
|
+
|
|
22
|
+
### Phone control installs as a native service, without a VM
|
|
23
|
+
|
|
24
|
+
Fresh setup enables phone-start when a supported provider and the operating
|
|
25
|
+
system's own per-user service manager are available: launchd on macOS, systemd
|
|
26
|
+
on Linux, and Task Scheduler on Windows. Existing installs keep their stored
|
|
27
|
+
choice. Service replacement is transactional, verifies the new process before
|
|
28
|
+
committing, and restores the previous running or stopped state on failure.
|
|
29
|
+
|
|
30
|
+
Claude Code, Codex and Gemini sessions started from the phone now use the same
|
|
31
|
+
supervised control path. Setup checks both the local heartbeat and the server's
|
|
32
|
+
view of the machine before calling control ready.
|
|
33
|
+
|
|
34
|
+
### Fresh and partial installs repair themselves
|
|
35
|
+
|
|
36
|
+
Setup now verifies every global command, its local JavaScript imports, and the
|
|
37
|
+
Cursor and VS Code runtime assets before reusing an installed version. A
|
|
38
|
+
same-version package missing any of them is reinstalled instead of failing the
|
|
39
|
+
same setup forever. Fresh installs also cover Codex on Windows and recover from
|
|
40
|
+
partially configured providers without disturbing unrelated settings.
|
|
41
|
+
|
|
42
|
+
### The CLI and macOS app keep their own wiring
|
|
43
|
+
|
|
44
|
+
Cursor setup writes one gate per event even when the other installer ran first.
|
|
45
|
+
`pushary clean` removes the CLI gate while preserving the app's bridge, and
|
|
46
|
+
`pushary doctor` validates the MCP entry from the installer that actually wired
|
|
47
|
+
Cursor. This keeps mixed CLI/app machines working and avoids false repair
|
|
48
|
+
failures on app-only installs.
|
|
49
|
+
|
|
50
|
+
### Transcript control works in every shell
|
|
51
|
+
|
|
52
|
+
Setup discloses encrypted transcript sync and its audited compliance-recovery
|
|
53
|
+
path before enabling it for a new install; older installs remain off until setup
|
|
54
|
+
runs again. `pushary setup --transcripts on|off` now provides the same persistent
|
|
55
|
+
choice in POSIX shells, PowerShell and Command Prompt. The existing
|
|
56
|
+
`PUSHARY_TRANSCRIPTS` environment override remains compatible.
|
|
57
|
+
|
|
3
58
|
## 0.89.11
|
|
4
59
|
|
|
5
60
|
### Phone-spawned Codex and Gemini sessions record their conversation again
|
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
|
|
@@ -199,6 +200,7 @@ stats Show the approval moments your agents hit while not connected
|
|
|
199
200
|
suggestions List rules mined from your own approvals
|
|
200
201
|
upgrade Update the globally installed hooks to the latest version
|
|
201
202
|
hook Run as a PreToolUse hook (reads stdin, writes stdout)
|
|
203
|
+
transcripts Show or re-trust the keys this machine seals transcripts to
|
|
202
204
|
```
|
|
203
205
|
|
|
204
206
|
Run `pushary <command> --help` for one command's options.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleBell
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-C56VHSXU.js";
|
|
5
5
|
import {
|
|
6
6
|
readHookInput
|
|
7
7
|
} from "../chunk-6CUUA7HO.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-NBDU3R3G.js";
|
|
9
9
|
import "../chunk-BC3VCZ3E.js";
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-ANMHIZZY.js";
|
|
11
|
+
import "../chunk-4LL6LG5X.js";
|
|
12
12
|
import "../chunk-2UMNXADU.js";
|
|
13
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-QINYXLA5.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-C56VHSXU.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-3YGGEDJK.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-WHDSJ6PD.js";
|
|
25
|
+
import "../chunk-GMJDQDXU.js";
|
|
28
26
|
import "../chunk-DINDL2CF.js";
|
|
29
|
-
import "../chunk-
|
|
27
|
+
import "../chunk-NBDU3R3G.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-ANMHIZZY.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-QINYXLA5.js";
|
|
40
40
|
|
|
41
41
|
// src/bell/install.ts
|
|
42
42
|
import { join } from "path";
|
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
changedRecipientsMessage,
|
|
4
|
+
loadPin,
|
|
5
|
+
savePin,
|
|
6
|
+
verifyRecipients
|
|
7
|
+
} from "../chunk-YKLCWWEI.js";
|
|
2
8
|
import {
|
|
3
9
|
ensureDaemonRunning
|
|
4
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-7JZCADBR.js";
|
|
5
11
|
import {
|
|
6
12
|
buildTranscriptRecords,
|
|
7
13
|
decodePublicKey,
|
|
8
|
-
fetchAccountPublicKey,
|
|
9
14
|
generateSessionKey,
|
|
10
|
-
uploadTranscriptRecords,
|
|
11
15
|
wrapSessionKey
|
|
12
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-4IPSMEGT.js";
|
|
17
|
+
import {
|
|
18
|
+
fetchTranscriptRecipients,
|
|
19
|
+
uploadTranscriptRecords
|
|
20
|
+
} from "../chunk-QZCFVWA7.js";
|
|
21
|
+
import {
|
|
22
|
+
transcriptsEnabled
|
|
23
|
+
} from "../chunk-M3UABYRT.js";
|
|
13
24
|
import {
|
|
14
25
|
startProcessOwnership,
|
|
15
26
|
withSpawnProcessOwnership
|
|
16
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-CBGGUEKE.js";
|
|
17
28
|
import {
|
|
18
29
|
needsShell,
|
|
19
30
|
spawnClaude
|
|
20
31
|
} from "../chunk-XHKBHWLX.js";
|
|
21
|
-
import "../chunk-
|
|
32
|
+
import "../chunk-L4ZCFMWW.js";
|
|
22
33
|
import {
|
|
23
34
|
isDeferAnswer
|
|
24
35
|
} from "../chunk-YHG74UFF.js";
|
|
@@ -27,7 +38,7 @@ import {
|
|
|
27
38
|
getPolicy,
|
|
28
39
|
repoKeyFor,
|
|
29
40
|
reportEvent
|
|
30
|
-
} from "../chunk-
|
|
41
|
+
} from "../chunk-OX35BSPV.js";
|
|
31
42
|
import {
|
|
32
43
|
DEFAULT_SESSION,
|
|
33
44
|
askUser,
|
|
@@ -41,21 +52,24 @@ import {
|
|
|
41
52
|
waitForAnswer
|
|
42
53
|
} from "../chunk-JHJHCMAF.js";
|
|
43
54
|
import {
|
|
55
|
+
UNNAMED_ACTION,
|
|
44
56
|
deriveToolTarget,
|
|
45
57
|
describeToolCall,
|
|
46
58
|
scopePathFor
|
|
47
|
-
} from "../chunk-
|
|
59
|
+
} from "../chunk-N4QJ3CLX.js";
|
|
48
60
|
import {
|
|
49
61
|
getMachineId
|
|
50
|
-
} from "../chunk-
|
|
51
|
-
import "../chunk-
|
|
62
|
+
} from "../chunk-JTBDBKHU.js";
|
|
63
|
+
import "../chunk-3YGGEDJK.js";
|
|
52
64
|
import "../chunk-DINDL2CF.js";
|
|
65
|
+
import "../chunk-NBDU3R3G.js";
|
|
66
|
+
import "../chunk-BC3VCZ3E.js";
|
|
53
67
|
import "../chunk-BSZYIAZL.js";
|
|
54
68
|
import "../chunk-SAF6HGAA.js";
|
|
55
|
-
import "../chunk-IUWRVTQK.js";
|
|
56
69
|
import "../chunk-7QLSKOSU.js";
|
|
57
70
|
import "../chunk-KZERVKTD.js";
|
|
58
|
-
import "../chunk-
|
|
71
|
+
import "../chunk-ANMHIZZY.js";
|
|
72
|
+
import "../chunk-4LL6LG5X.js";
|
|
59
73
|
import {
|
|
60
74
|
getApiKey,
|
|
61
75
|
getBaseUrl
|
|
@@ -63,7 +77,7 @@ import {
|
|
|
63
77
|
import {
|
|
64
78
|
KILL_REASON,
|
|
65
79
|
resolveGate
|
|
66
|
-
} from "../chunk-
|
|
80
|
+
} from "../chunk-QINYXLA5.js";
|
|
67
81
|
|
|
68
82
|
// src/wrapper/claudeBinary.ts
|
|
69
83
|
import { statSync } from "fs";
|
|
@@ -139,18 +153,88 @@ var runLocalPassthrough = (binary, args2) => {
|
|
|
139
153
|
import { basename } from "path";
|
|
140
154
|
|
|
141
155
|
// src/transcript-sync.ts
|
|
156
|
+
import { homedir as homedir2 } from "os";
|
|
157
|
+
import { join as join3, resolve } from "path";
|
|
158
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
159
|
+
|
|
160
|
+
// src/transcript-session-key.ts
|
|
161
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
|
|
142
162
|
import { homedir } from "os";
|
|
143
|
-
import { join as join2
|
|
144
|
-
|
|
145
|
-
var
|
|
163
|
+
import { join as join2 } from "path";
|
|
164
|
+
var sessionDir = () => process.env.PUSHARY_SESSION_KEY_DIR?.trim() || join2(homedir(), ".pushary", "sessions");
|
|
165
|
+
var SESSION_KEY_TTL_MS = 48 * 60 * 60 * 1e3;
|
|
166
|
+
var SAFE_SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
|
|
167
|
+
var fileFor = (sessionId) => SAFE_SESSION_ID.test(sessionId) ? join2(sessionDir(), `${sessionId}.json`) : null;
|
|
168
|
+
var loadSessionKey = (sessionId) => {
|
|
169
|
+
const path = fileFor(sessionId);
|
|
170
|
+
if (!path || !existsSync(path)) return null;
|
|
171
|
+
try {
|
|
172
|
+
if (Date.now() - statSync2(path).mtimeMs > SESSION_KEY_TTL_MS) return null;
|
|
173
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
174
|
+
const key = typeof raw.sessionKey === "string" ? Buffer.from(raw.sessionKey, "base64") : null;
|
|
175
|
+
if (!key || key.length !== 32) return null;
|
|
176
|
+
return {
|
|
177
|
+
sessionKey: new Uint8Array(key),
|
|
178
|
+
wrappedKey: typeof raw.wrappedKey === "string" ? raw.wrappedKey : null,
|
|
179
|
+
providerWrappedKey: typeof raw.providerWrappedKey === "string" ? raw.providerWrappedKey : null,
|
|
180
|
+
providerKeyId: typeof raw.providerKeyId === "string" ? raw.providerKeyId : null,
|
|
181
|
+
keyUploaded: raw.keyUploaded === true
|
|
182
|
+
};
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var saveSessionKey = (sessionId, value) => {
|
|
188
|
+
const path = fileFor(sessionId);
|
|
189
|
+
if (!path) return;
|
|
190
|
+
try {
|
|
191
|
+
mkdirSync(sessionDir(), { recursive: true, mode: 448 });
|
|
192
|
+
writeFileSync(
|
|
193
|
+
path,
|
|
194
|
+
JSON.stringify({
|
|
195
|
+
sessionKey: Buffer.from(value.sessionKey).toString("base64"),
|
|
196
|
+
wrappedKey: value.wrappedKey,
|
|
197
|
+
providerWrappedKey: value.providerWrappedKey,
|
|
198
|
+
providerKeyId: value.providerKeyId,
|
|
199
|
+
keyUploaded: value.keyUploaded
|
|
200
|
+
}),
|
|
201
|
+
{ mode: 384 }
|
|
202
|
+
);
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var sweepStaleSessionKeys = (now = Date.now()) => {
|
|
207
|
+
const dir = sessionDir();
|
|
208
|
+
if (!existsSync(dir)) return 0;
|
|
209
|
+
let removed = 0;
|
|
210
|
+
try {
|
|
211
|
+
for (const name of readdirSync(dir)) {
|
|
212
|
+
if (!name.endsWith(".json")) continue;
|
|
213
|
+
const path = join2(dir, name);
|
|
214
|
+
try {
|
|
215
|
+
if (now - statSync2(path).mtimeMs <= SESSION_KEY_TTL_MS) continue;
|
|
216
|
+
rmSync(path, { force: true });
|
|
217
|
+
removed += 1;
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
return removed;
|
|
223
|
+
}
|
|
224
|
+
return removed;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// src/transcript-sync.ts
|
|
146
228
|
var claudeTranscriptPath = (cwd, sessionId) => {
|
|
147
229
|
const projectId = resolve(cwd).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
148
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR ||
|
|
149
|
-
return
|
|
230
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR || join3(homedir2(), ".claude");
|
|
231
|
+
return join3(configDir, "projects", projectId, `${sessionId}.jsonl`);
|
|
150
232
|
};
|
|
151
233
|
var createSyncState = () => ({
|
|
152
234
|
sessionKey: null,
|
|
153
235
|
wrappedKey: null,
|
|
236
|
+
providerWrappedKey: null,
|
|
237
|
+
providerKeyId: null,
|
|
154
238
|
seen: /* @__PURE__ */ new Set(),
|
|
155
239
|
keyUploaded: false,
|
|
156
240
|
keyRejected: false
|
|
@@ -159,70 +243,163 @@ var syncTick = async (lines, state, uploader, sessionId, machineId) => {
|
|
|
159
243
|
if (state.keyRejected) return state;
|
|
160
244
|
let sessionKey = state.sessionKey;
|
|
161
245
|
let wrappedKey = state.wrappedKey;
|
|
246
|
+
let providerWrappedKey = state.providerWrappedKey;
|
|
247
|
+
let providerKeyId = state.providerKeyId;
|
|
162
248
|
if (!sessionKey) {
|
|
163
|
-
const
|
|
164
|
-
if (!
|
|
249
|
+
const recipients = await uploader.fetchTranscriptRecipients();
|
|
250
|
+
if (!recipients) return state;
|
|
165
251
|
sessionKey = generateSessionKey();
|
|
166
|
-
wrappedKey = wrapSessionKey(sessionKey, decodePublicKey(
|
|
252
|
+
wrappedKey = wrapSessionKey(sessionKey, decodePublicKey(recipients.phonePublicKey));
|
|
253
|
+
if (recipients.provider) {
|
|
254
|
+
providerWrappedKey = wrapSessionKey(sessionKey, decodePublicKey(recipients.provider.publicKey));
|
|
255
|
+
providerKeyId = recipients.provider.keyId;
|
|
256
|
+
}
|
|
167
257
|
}
|
|
168
258
|
const built = buildTranscriptRecords(lines, sessionKey, state.seen);
|
|
169
259
|
if (built.records.length === 0) {
|
|
170
|
-
return { ...state, sessionKey, wrappedKey, seen: built.seen };
|
|
260
|
+
return { ...state, sessionKey, wrappedKey, providerWrappedKey, providerKeyId, seen: built.seen };
|
|
261
|
+
}
|
|
262
|
+
const seen = new Set(state.seen);
|
|
263
|
+
let keyUploaded = state.keyUploaded;
|
|
264
|
+
let keyRejected = false;
|
|
265
|
+
let rejected = 0;
|
|
266
|
+
for (const chunk of chunkRecords(built.records)) {
|
|
267
|
+
const result = await uploader.uploadRecords({
|
|
268
|
+
sessionId,
|
|
269
|
+
machineId,
|
|
270
|
+
dataEncryptionKey: keyUploaded ? void 0 : wrappedKey ?? void 0,
|
|
271
|
+
providerWrappedSessionKey: keyUploaded ? void 0 : providerWrappedKey ?? void 0,
|
|
272
|
+
providerKeyId: keyUploaded ? void 0 : providerKeyId ?? void 0,
|
|
273
|
+
records: chunk
|
|
274
|
+
});
|
|
275
|
+
keyUploaded = keyUploaded || result.keyRegistered;
|
|
276
|
+
if (result.keyConflict) {
|
|
277
|
+
keyRejected = true;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
if (!result.ok) break;
|
|
281
|
+
for (const id of result.settledIds ?? chunk.map((r) => r.localId)) seen.add(id);
|
|
282
|
+
rejected += result.rejectedIds.length;
|
|
283
|
+
}
|
|
284
|
+
if (rejected > 0) {
|
|
285
|
+
process.stderr.write(`[pushary] transcript: ${rejected} record${rejected === 1 ? "" : "s"} could not be stored and will not be retried.
|
|
286
|
+
`);
|
|
171
287
|
}
|
|
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
288
|
return {
|
|
179
289
|
sessionKey,
|
|
180
290
|
wrappedKey,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
291
|
+
providerWrappedKey,
|
|
292
|
+
providerKeyId,
|
|
293
|
+
seen,
|
|
294
|
+
keyUploaded,
|
|
295
|
+
keyRejected
|
|
184
296
|
};
|
|
185
297
|
};
|
|
298
|
+
var MAX_RECORDS_PER_BATCH = 200;
|
|
299
|
+
var MAX_BATCH_BYTES = 3e6;
|
|
300
|
+
var chunkRecords = (records) => {
|
|
301
|
+
const chunks = [];
|
|
302
|
+
let current = [];
|
|
303
|
+
let bytes = 0;
|
|
304
|
+
for (const record of records) {
|
|
305
|
+
const size = record.content.length;
|
|
306
|
+
if (current.length > 0 && (current.length >= MAX_RECORDS_PER_BATCH || bytes + size > MAX_BATCH_BYTES)) {
|
|
307
|
+
chunks.push(current);
|
|
308
|
+
current = [];
|
|
309
|
+
bytes = 0;
|
|
310
|
+
}
|
|
311
|
+
current.push(record);
|
|
312
|
+
bytes += size;
|
|
313
|
+
}
|
|
314
|
+
if (current.length > 0) chunks.push(current);
|
|
315
|
+
return chunks;
|
|
316
|
+
};
|
|
186
317
|
var readLines = (path) => {
|
|
187
318
|
try {
|
|
188
|
-
return
|
|
319
|
+
return existsSync2(path) ? readFileSync2(path, "utf-8").split("\n") : [];
|
|
189
320
|
} catch {
|
|
190
321
|
return [];
|
|
191
322
|
}
|
|
192
323
|
};
|
|
324
|
+
var IDLE_CEILING_MS = 6e4;
|
|
325
|
+
var nextInterval = (previousMs, idle, baseMs, maxMs) => idle ? Math.min(previousMs * 2, maxMs) : baseMs;
|
|
193
326
|
var startTranscriptSync = (opts) => {
|
|
327
|
+
let warnedAboutRecipients = false;
|
|
328
|
+
const fetchPinnedRecipients = async () => {
|
|
329
|
+
const offer = await fetchTranscriptRecipients(opts.apiKey);
|
|
330
|
+
if (!offer) return null;
|
|
331
|
+
const verdict = verifyRecipients(offer, loadPin());
|
|
332
|
+
if (verdict.kind === "first-use" && !savePin(verdict.pin)) return null;
|
|
333
|
+
if (verdict.kind === "changed") {
|
|
334
|
+
if (!warnedAboutRecipients) {
|
|
335
|
+
warnedAboutRecipients = true;
|
|
336
|
+
process.stderr.write(`${changedRecipientsMessage(verdict, offer)}
|
|
337
|
+
`);
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
return offer;
|
|
342
|
+
};
|
|
194
343
|
const uploader = {
|
|
195
|
-
|
|
344
|
+
fetchTranscriptRecipients: fetchPinnedRecipients,
|
|
196
345
|
uploadRecords: (params) => uploadTranscriptRecords(opts.apiKey, params)
|
|
197
346
|
};
|
|
198
347
|
const path = claudeTranscriptPath(opts.cwd, opts.sessionId);
|
|
199
|
-
|
|
348
|
+
const persisted = loadSessionKey(opts.sessionId);
|
|
349
|
+
let state = persisted ? { ...createSyncState(), ...persisted } : createSyncState();
|
|
350
|
+
sweepStaleSessionKeys();
|
|
351
|
+
let persistedUploaded = persisted?.keyUploaded ?? false;
|
|
200
352
|
let running = true;
|
|
201
353
|
let inFlight = false;
|
|
202
354
|
const tick = async () => {
|
|
203
355
|
if (!running || inFlight) return;
|
|
204
356
|
inFlight = true;
|
|
205
357
|
try {
|
|
358
|
+
const before = state.sessionKey;
|
|
206
359
|
state = await syncTick(readLines(path), state, uploader, opts.sessionId, opts.machineId);
|
|
360
|
+
if (state.sessionKey && (before !== state.sessionKey || state.keyUploaded !== persistedUploaded)) {
|
|
361
|
+
persistedUploaded = state.keyUploaded;
|
|
362
|
+
saveSessionKey(opts.sessionId, {
|
|
363
|
+
sessionKey: state.sessionKey,
|
|
364
|
+
wrappedKey: state.wrappedKey,
|
|
365
|
+
providerWrappedKey: state.providerWrappedKey,
|
|
366
|
+
providerKeyId: state.providerKeyId,
|
|
367
|
+
keyUploaded: state.keyUploaded
|
|
368
|
+
});
|
|
369
|
+
}
|
|
207
370
|
} catch {
|
|
208
371
|
} finally {
|
|
209
372
|
inFlight = false;
|
|
210
373
|
}
|
|
211
374
|
};
|
|
212
|
-
const
|
|
213
|
-
|
|
375
|
+
const baseIntervalMs = opts.intervalMs ?? 3e3;
|
|
376
|
+
const maxIntervalMs = Math.max(baseIntervalMs, IDLE_CEILING_MS);
|
|
377
|
+
let timer;
|
|
378
|
+
const schedule = (delayMs) => {
|
|
379
|
+
if (!running) return;
|
|
380
|
+
timer = setTimeout(() => {
|
|
381
|
+
void (async () => {
|
|
382
|
+
const before = state.sessionKey;
|
|
383
|
+
await tick();
|
|
384
|
+
const idle = state.sessionKey === null && before === null;
|
|
385
|
+
schedule(nextInterval(delayMs, idle, baseIntervalMs, maxIntervalMs));
|
|
386
|
+
})();
|
|
387
|
+
}, delayMs);
|
|
388
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
389
|
+
};
|
|
214
390
|
void tick();
|
|
391
|
+
schedule(baseIntervalMs);
|
|
215
392
|
return () => {
|
|
216
393
|
running = false;
|
|
217
|
-
|
|
394
|
+
if (timer) clearTimeout(timer);
|
|
218
395
|
};
|
|
219
396
|
};
|
|
220
397
|
|
|
221
398
|
// src/wrapper/sdkLoader.ts
|
|
222
399
|
import { spawn } from "child_process";
|
|
223
|
-
import { existsSync as
|
|
224
|
-
import { homedir as
|
|
225
|
-
import { join as
|
|
400
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
401
|
+
import { homedir as homedir3 } from "os";
|
|
402
|
+
import { join as join4 } from "path";
|
|
226
403
|
import { pathToFileURL } from "url";
|
|
227
404
|
var userMessage = (text) => ({
|
|
228
405
|
type: "user",
|
|
@@ -232,8 +409,8 @@ var userMessage = (text) => ({
|
|
|
232
409
|
var SDK_VERSION = "0.3.207";
|
|
233
410
|
var SDK_PACKAGE = "@anthropic-ai/claude-agent-sdk";
|
|
234
411
|
var SDK_SPEC = `${SDK_PACKAGE}@${SDK_VERSION}`;
|
|
235
|
-
var sdkCacheDir = () => process.env.PUSHARY_REMOTE_SDK_DIR?.trim() ||
|
|
236
|
-
var cachedSdkEntry = () =>
|
|
412
|
+
var sdkCacheDir = () => process.env.PUSHARY_REMOTE_SDK_DIR?.trim() || join4(homedir3(), ".pushary", "remote-sdk");
|
|
413
|
+
var cachedSdkEntry = () => join4(sdkCacheDir(), "node_modules", "@anthropic-ai", "claude-agent-sdk", "sdk.mjs");
|
|
237
414
|
var importSdk = async (specifierOrUrl) => {
|
|
238
415
|
try {
|
|
239
416
|
const mod = await import(specifierOrUrl);
|
|
@@ -248,7 +425,7 @@ var loadClaudeSdk = async () => {
|
|
|
248
425
|
const fromResolution = await importSdk(SDK_PACKAGE);
|
|
249
426
|
if (fromResolution) return fromResolution;
|
|
250
427
|
const entry = cachedSdkEntry();
|
|
251
|
-
if (
|
|
428
|
+
if (existsSync3(entry)) return importSdk(pathToFileURL(entry).href);
|
|
252
429
|
return null;
|
|
253
430
|
};
|
|
254
431
|
var ensureClaudeSdk = async (log) => {
|
|
@@ -256,10 +433,10 @@ var ensureClaudeSdk = async (log) => {
|
|
|
256
433
|
if (present) return present;
|
|
257
434
|
const dir = sdkCacheDir();
|
|
258
435
|
try {
|
|
259
|
-
|
|
260
|
-
const pkgJson =
|
|
261
|
-
if (!
|
|
262
|
-
|
|
436
|
+
mkdirSync2(dir, { recursive: true });
|
|
437
|
+
const pkgJson = join4(dir, "package.json");
|
|
438
|
+
if (!existsSync3(pkgJson)) {
|
|
439
|
+
writeFileSync2(pkgJson, `${JSON.stringify({ name: "pushary-remote-sdk", private: true })}
|
|
263
440
|
`);
|
|
264
441
|
}
|
|
265
442
|
} catch {
|
|
@@ -486,7 +663,7 @@ var createRemoteApprover = (deps) => {
|
|
|
486
663
|
let question;
|
|
487
664
|
try {
|
|
488
665
|
question = await askUser2(deps.apiKey, {
|
|
489
|
-
question: `Allow ${description}?`,
|
|
666
|
+
question: `Allow ${description || UNNAMED_ACTION}?`,
|
|
490
667
|
type: "confirm",
|
|
491
668
|
context: `Your agent wants to run this in ${deps.projectName} (you are away)`,
|
|
492
669
|
agentName: `Claude Code - ${deps.projectName}`,
|