@pushary/agent-hooks 0.49.1 → 0.51.0

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.
@@ -16,12 +16,14 @@ import {
16
16
  reportEvent,
17
17
  resolvePolicy,
18
18
  waitForAnswer
19
- } from "../chunk-O5MFSRWV.js";
19
+ } from "../chunk-BEK4SJNX.js";
20
20
  import {
21
21
  getMachineId
22
22
  } from "../chunk-RN3NOEJF.js";
23
23
  import "../chunk-DWED7BS3.js";
24
- import "../chunk-Z5PL3K7C.js";
24
+ import {
25
+ redactSecrets
26
+ } from "../chunk-CJBVT33U.js";
25
27
  import {
26
28
  getApiKey,
27
29
  getBaseUrl
@@ -63,7 +65,7 @@ var SIGNAL_NUMBERS = {
63
65
  };
64
66
  var FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
65
67
  var runLocalPassthrough = (binary, args2) => {
66
- return new Promise((resolve) => {
68
+ return new Promise((resolve2) => {
67
69
  let child;
68
70
  try {
69
71
  child = spawnClaude(binary, args2, {
@@ -71,7 +73,7 @@ var runLocalPassthrough = (binary, args2) => {
71
73
  env: { ...process.env, [WRAPPER_ACTIVE_ENV]: "1" }
72
74
  });
73
75
  } catch {
74
- resolve(127);
76
+ resolve2(127);
75
77
  return;
76
78
  }
77
79
  const forward = (signal) => {
@@ -86,13 +88,13 @@ var runLocalPassthrough = (binary, args2) => {
86
88
  };
87
89
  child.on("error", () => {
88
90
  cleanup();
89
- resolve(127);
91
+ resolve2(127);
90
92
  });
91
93
  child.on("exit", (code, signal) => {
92
94
  cleanup();
93
- if (typeof code === "number") resolve(code);
94
- else if (signal) resolve(128 + (SIGNAL_NUMBERS[signal] ?? 0));
95
- else resolve(0);
95
+ if (typeof code === "number") resolve2(code);
96
+ else if (signal) resolve2(128 + (SIGNAL_NUMBERS[signal] ?? 0));
97
+ else resolve2(0);
96
98
  });
97
99
  });
98
100
  };
@@ -100,11 +102,194 @@ var runLocalPassthrough = (binary, args2) => {
100
102
  // src/wrapper/dualMode.ts
101
103
  import { basename } from "path";
102
104
 
105
+ // src/transcript-sync.ts
106
+ import { homedir } from "os";
107
+ import { join as join2, resolve } from "path";
108
+ import { existsSync, readFileSync } from "fs";
109
+
110
+ // src/transcript-capture.ts
111
+ import { createHash } from "crypto";
112
+
113
+ // src/crypto.ts
114
+ import nacl from "tweetnacl";
115
+ var EPH_PUB_LEN = nacl.box.publicKeyLength;
116
+ var BOX_NONCE_LEN = nacl.box.nonceLength;
117
+ var SECRETBOX_NONCE_LEN = nacl.secretbox.nonceLength;
118
+ var SESSION_KEY_LEN = nacl.secretbox.keyLength;
119
+ var toBase64 = (bytes) => Buffer.from(bytes).toString("base64");
120
+ var fromBase64 = (value) => new Uint8Array(Buffer.from(value, "base64"));
121
+ var utf8Encode = (text) => new Uint8Array(Buffer.from(text, "utf-8"));
122
+ var decodePublicKey = (value) => fromBase64(value);
123
+ var generateSessionKey = () => nacl.randomBytes(SESSION_KEY_LEN);
124
+ var wrapSessionKey = (sessionKey, accountPublicKey) => {
125
+ const ephemeral = nacl.box.keyPair();
126
+ const nonce = nacl.randomBytes(BOX_NONCE_LEN);
127
+ const box = nacl.box(sessionKey, nonce, accountPublicKey, ephemeral.secretKey);
128
+ const bundle = new Uint8Array(EPH_PUB_LEN + BOX_NONCE_LEN + box.length);
129
+ bundle.set(ephemeral.publicKey, 0);
130
+ bundle.set(nonce, EPH_PUB_LEN);
131
+ bundle.set(box, EPH_PUB_LEN + BOX_NONCE_LEN);
132
+ return toBase64(bundle);
133
+ };
134
+ var encryptRecord = (plaintext, sessionKey) => {
135
+ const nonce = nacl.randomBytes(SECRETBOX_NONCE_LEN);
136
+ const box = nacl.secretbox(utf8Encode(plaintext), nonce, sessionKey);
137
+ const bundle = new Uint8Array(SECRETBOX_NONCE_LEN + box.length);
138
+ bundle.set(nonce, 0);
139
+ bundle.set(box, SECRETBOX_NONCE_LEN);
140
+ return toBase64(bundle);
141
+ };
142
+
143
+ // src/transcript-capture.ts
144
+ var INTERNAL_TYPES = /* @__PURE__ */ new Set([
145
+ "file-history-snapshot",
146
+ "change",
147
+ "queue-operation",
148
+ "summary"
149
+ ]);
150
+ var redactDeep = (value) => {
151
+ if (typeof value === "string") return redactSecrets(value);
152
+ if (Array.isArray(value)) return value.map(redactDeep);
153
+ if (value && typeof value === "object") {
154
+ const out = {};
155
+ for (const [key, inner] of Object.entries(value)) {
156
+ out[key] = redactDeep(inner);
157
+ }
158
+ return out;
159
+ }
160
+ return value;
161
+ };
162
+ var localIdFor = (parsed, rawLine) => typeof parsed.uuid === "string" && parsed.uuid.length > 0 ? parsed.uuid : createHash("sha256").update(rawLine).digest("hex").slice(0, 32);
163
+ var buildTranscriptRecords = (lines, sessionKey, seen = /* @__PURE__ */ new Set()) => {
164
+ const records = [];
165
+ const nextSeen = new Set(seen);
166
+ for (const line of lines) {
167
+ const trimmed = line.trim();
168
+ if (!trimmed) continue;
169
+ let parsed;
170
+ try {
171
+ parsed = JSON.parse(trimmed);
172
+ } catch {
173
+ continue;
174
+ }
175
+ const type = typeof parsed.type === "string" ? parsed.type : "";
176
+ if (INTERNAL_TYPES.has(type)) continue;
177
+ const localId = localIdFor(parsed, trimmed);
178
+ if (nextSeen.has(localId)) continue;
179
+ nextSeen.add(localId);
180
+ const content = encryptRecord(JSON.stringify(redactDeep(parsed)), sessionKey);
181
+ records.push({ localId, content });
182
+ }
183
+ return { records, seen: nextSeen };
184
+ };
185
+
186
+ // src/transcript-api.ts
187
+ var fetchAccountPublicKey = async (apiKey) => {
188
+ try {
189
+ const res = await fetch(`${getBaseUrl()}/api/agent/encryption-account`, {
190
+ headers: { Authorization: `Bearer ${apiKey}` },
191
+ signal: AbortSignal.timeout(5e3)
192
+ });
193
+ if (!res.ok) return null;
194
+ const data = await res.json();
195
+ return typeof data.publicKey === "string" && data.publicKey.length > 0 ? data.publicKey : null;
196
+ } catch {
197
+ return null;
198
+ }
199
+ };
200
+ var uploadTranscriptRecords = async (apiKey, params) => {
201
+ try {
202
+ const res = await fetch(`${getBaseUrl()}/api/agent/transcript`, {
203
+ method: "POST",
204
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
205
+ body: JSON.stringify(params),
206
+ signal: AbortSignal.timeout(15e3)
207
+ });
208
+ return res.ok;
209
+ } catch {
210
+ return false;
211
+ }
212
+ };
213
+
214
+ // src/transcript-sync.ts
215
+ var transcriptsEnabled = () => process.env.PUSHARY_TRANSCRIPTS === "1";
216
+ var claudeTranscriptPath = (cwd, sessionId) => {
217
+ const projectId = resolve(cwd).replace(/[^a-zA-Z0-9-]/g, "-");
218
+ const configDir = process.env.CLAUDE_CONFIG_DIR || join2(homedir(), ".claude");
219
+ return join2(configDir, "projects", projectId, `${sessionId}.jsonl`);
220
+ };
221
+ var createSyncState = () => ({
222
+ sessionKey: null,
223
+ wrappedKey: null,
224
+ seen: /* @__PURE__ */ new Set(),
225
+ keyUploaded: false
226
+ });
227
+ var syncTick = async (lines, state, uploader, sessionId, machineId) => {
228
+ let sessionKey = state.sessionKey;
229
+ let wrappedKey = state.wrappedKey;
230
+ if (!sessionKey) {
231
+ const publicKey = await uploader.fetchAccountPublicKey();
232
+ if (!publicKey) return state;
233
+ sessionKey = generateSessionKey();
234
+ wrappedKey = wrapSessionKey(sessionKey, decodePublicKey(publicKey));
235
+ }
236
+ const built = buildTranscriptRecords(lines, sessionKey, state.seen);
237
+ if (built.records.length === 0) {
238
+ return { sessionKey, wrappedKey, seen: built.seen, keyUploaded: state.keyUploaded };
239
+ }
240
+ const ok = await uploader.uploadRecords({
241
+ sessionId,
242
+ machineId,
243
+ dataEncryptionKey: state.keyUploaded ? void 0 : wrappedKey ?? void 0,
244
+ records: built.records
245
+ });
246
+ return {
247
+ sessionKey,
248
+ wrappedKey,
249
+ seen: ok ? built.seen : state.seen,
250
+ keyUploaded: state.keyUploaded || ok
251
+ };
252
+ };
253
+ var readLines = (path) => {
254
+ try {
255
+ return existsSync(path) ? readFileSync(path, "utf-8").split("\n") : [];
256
+ } catch {
257
+ return [];
258
+ }
259
+ };
260
+ var startTranscriptSync = (opts) => {
261
+ const uploader = {
262
+ fetchAccountPublicKey: () => fetchAccountPublicKey(opts.apiKey),
263
+ uploadRecords: (params) => uploadTranscriptRecords(opts.apiKey, params)
264
+ };
265
+ const path = claudeTranscriptPath(opts.cwd, opts.sessionId);
266
+ let state = createSyncState();
267
+ let running = true;
268
+ let inFlight = false;
269
+ const tick = async () => {
270
+ if (!running || inFlight) return;
271
+ inFlight = true;
272
+ try {
273
+ state = await syncTick(readLines(path), state, uploader, opts.sessionId, opts.machineId);
274
+ } catch {
275
+ } finally {
276
+ inFlight = false;
277
+ }
278
+ };
279
+ const timer = setInterval(() => void tick(), opts.intervalMs ?? 3e3);
280
+ if (typeof timer.unref === "function") timer.unref();
281
+ void tick();
282
+ return () => {
283
+ running = false;
284
+ clearInterval(timer);
285
+ };
286
+ };
287
+
103
288
  // src/wrapper/sdkLoader.ts
104
289
  import { spawn } from "child_process";
105
- import { existsSync, mkdirSync, writeFileSync } from "fs";
106
- import { homedir } from "os";
107
- import { join as join2 } from "path";
290
+ import { existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
291
+ import { homedir as homedir2 } from "os";
292
+ import { join as join3 } from "path";
108
293
  import { pathToFileURL } from "url";
109
294
  var userMessage = (text) => ({
110
295
  type: "user",
@@ -114,8 +299,8 @@ var userMessage = (text) => ({
114
299
  var SDK_VERSION = "0.3.207";
115
300
  var SDK_PACKAGE = "@anthropic-ai/claude-agent-sdk";
116
301
  var SDK_SPEC = `${SDK_PACKAGE}@${SDK_VERSION}`;
117
- var sdkCacheDir = () => process.env.PUSHARY_REMOTE_SDK_DIR?.trim() || join2(homedir(), ".pushary", "remote-sdk");
118
- var cachedSdkEntry = () => join2(sdkCacheDir(), "node_modules", "@anthropic-ai", "claude-agent-sdk", "sdk.mjs");
302
+ var sdkCacheDir = () => process.env.PUSHARY_REMOTE_SDK_DIR?.trim() || join3(homedir2(), ".pushary", "remote-sdk");
303
+ var cachedSdkEntry = () => join3(sdkCacheDir(), "node_modules", "@anthropic-ai", "claude-agent-sdk", "sdk.mjs");
119
304
  var importSdk = async (specifierOrUrl) => {
120
305
  try {
121
306
  const mod = await import(specifierOrUrl);
@@ -130,7 +315,7 @@ var loadClaudeSdk = async () => {
130
315
  const fromResolution = await importSdk(SDK_PACKAGE);
131
316
  if (fromResolution) return fromResolution;
132
317
  const entry = cachedSdkEntry();
133
- if (existsSync(entry)) return importSdk(pathToFileURL(entry).href);
318
+ if (existsSync2(entry)) return importSdk(pathToFileURL(entry).href);
134
319
  return null;
135
320
  };
136
321
  var ensureClaudeSdk = async (log) => {
@@ -139,8 +324,8 @@ var ensureClaudeSdk = async (log) => {
139
324
  const dir = sdkCacheDir();
140
325
  try {
141
326
  mkdirSync(dir, { recursive: true });
142
- const pkgJson = join2(dir, "package.json");
143
- if (!existsSync(pkgJson)) {
327
+ const pkgJson = join3(dir, "package.json");
328
+ if (!existsSync2(pkgJson)) {
144
329
  writeFileSync(pkgJson, `${JSON.stringify({ name: "pushary-remote-sdk", private: true })}
145
330
  `);
146
331
  }
@@ -149,14 +334,14 @@ var ensureClaudeSdk = async (log) => {
149
334
  }
150
335
  log?.("[pushary] setting up phone control (one-time, fetching the Claude Agent SDK)...");
151
336
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
152
- const installed = await new Promise((resolve) => {
337
+ const installed = await new Promise((resolve2) => {
153
338
  const child = spawn(
154
339
  npmCmd,
155
340
  ["install", "--no-optional", "--no-audit", "--no-fund", "--loglevel=error", SDK_SPEC],
156
341
  { cwd: dir, stdio: "ignore", shell: process.platform === "win32" }
157
342
  );
158
- child.on("error", () => resolve(false));
159
- child.on("close", (code) => resolve(code === 0));
343
+ child.on("error", () => resolve2(false));
344
+ child.on("close", (code) => resolve2(code === 0));
160
345
  });
161
346
  if (!installed) {
162
347
  log?.(`[pushary] could not auto-install the SDK. Install it manually: npm i -g ${SDK_SPEC}`);
@@ -185,10 +370,10 @@ var BoundedInputQueue = class {
185
370
  if (this.closed) return;
186
371
  this.pushedCount++;
187
372
  if (this.waiter) {
188
- const resolve = this.waiter;
373
+ const resolve2 = this.waiter;
189
374
  this.waiter = void 0;
190
375
  this.deliveredCount++;
191
- resolve({ value: item, done: false });
376
+ resolve2({ value: item, done: false });
192
377
  return;
193
378
  }
194
379
  this.buffer.push(item);
@@ -205,9 +390,9 @@ var BoundedInputQueue = class {
205
390
  this.closed = true;
206
391
  this.buffer.length = 0;
207
392
  if (this.waiter) {
208
- const resolve = this.waiter;
393
+ const resolve2 = this.waiter;
209
394
  this.waiter = void 0;
210
- resolve({ value: void 0, done: true });
395
+ resolve2({ value: void 0, done: true });
211
396
  }
212
397
  }
213
398
  // End the CURRENT consumer without closing the queue, so a fresh query can
@@ -216,9 +401,9 @@ var BoundedInputQueue = class {
216
401
  // instead of hanging on a promise that would otherwise never settle.
217
402
  detachConsumer() {
218
403
  if (this.waiter) {
219
- const resolve = this.waiter;
404
+ const resolve2 = this.waiter;
220
405
  this.waiter = void 0;
221
- resolve({ value: void 0, done: true });
406
+ resolve2({ value: void 0, done: true });
222
407
  }
223
408
  }
224
409
  // Drop every buffered item without closing the queue. Used when phone control
@@ -257,8 +442,8 @@ var BoundedInputQueue = class {
257
442
  new Error("BoundedInputQueue: concurrent consumers are not supported")
258
443
  );
259
444
  }
260
- return new Promise((resolve) => {
261
- this.waiter = resolve;
445
+ return new Promise((resolve2) => {
446
+ this.waiter = resolve2;
262
447
  });
263
448
  },
264
449
  return: () => {
@@ -300,8 +485,8 @@ var createRemoteApprover = (deps) => {
300
485
  const deadline = now() + windowMs;
301
486
  while (now() < deadline && !entry.controller.signal.aborted) {
302
487
  const remaining = Math.min(Math.max(deadline - now(), 1e3), 3e4);
303
- const abortPromise = new Promise((resolve) => {
304
- entry.controller.signal.addEventListener("abort", () => resolve({ answered: false, aborted: true }), {
488
+ const abortPromise = new Promise((resolve2) => {
489
+ entry.controller.signal.addEventListener("abort", () => resolve2({ answered: false, aborted: true }), {
305
490
  once: true
306
491
  });
307
492
  });
@@ -343,7 +528,7 @@ var createRemoteApprover = (deps) => {
343
528
  const sessionId = deps.getSessionId?.();
344
529
  const modeState = await fetchModeState2(deps.apiKey, sessionId);
345
530
  if (modeState.kill) return deny("Stopped by user \u2014 this agent was halted from Pushary");
346
- const policyConfig = await getPolicy2(deps.apiKey, modeState.policyVersion);
531
+ const policyConfig = await getPolicy2(deps.apiKey, modeState.policyVersion, "claude_code");
347
532
  const policy = resolvePolicy2(policyConfig, toolName, modeState.mode, input);
348
533
  if (policy.timeoutSeconds === 0 && policy.timeoutAction === "approve") return allow(input);
349
534
  if (policy.timeoutSeconds === 0 && policy.timeoutAction === "deny") {
@@ -878,7 +1063,7 @@ var nativeArgs = (session) => {
878
1063
  };
879
1064
  var runLocalLeg = (session, deps = {}) => {
880
1065
  const spawn2 = deps.spawn ?? spawnClaude;
881
- return new Promise((resolve) => {
1066
+ return new Promise((resolve2) => {
882
1067
  const legAbort = new AbortController();
883
1068
  let exitReason = null;
884
1069
  let settled = false;
@@ -886,7 +1071,7 @@ var runLocalLeg = (session, deps = {}) => {
886
1071
  if (settled) return;
887
1072
  settled = true;
888
1073
  session.setActiveLeg(null);
889
- resolve(exitReason ?? { type: "exit", code: 0 });
1074
+ resolve2(exitReason ?? { type: "exit", code: 0 });
890
1075
  };
891
1076
  const control = {
892
1077
  onPhoneCommand: () => {
@@ -1183,12 +1368,20 @@ var runDualMode = async (binary, args2, startingMode) => {
1183
1368
  }).catch(() => {
1184
1369
  });
1185
1370
  };
1371
+ let stopTranscript;
1372
+ let transcriptStarted = false;
1373
+ const ensureTranscriptSync = () => {
1374
+ if (transcriptStarted || !transcriptsEnabled() || !sessionId) return;
1375
+ transcriptStarted = true;
1376
+ stopTranscript = startTranscriptSync({ apiKey, sessionId, cwd: process.cwd(), machineId });
1377
+ };
1186
1378
  const setSessionId = (id) => {
1187
1379
  const isNew = sessionId !== id;
1188
1380
  sessionId = id;
1189
1381
  sessionCreated = true;
1190
1382
  relay?.reannounce();
1191
1383
  if (isNew) void announcePresence("session_start", "Remote session ready \u2014 reachable from your phone");
1384
+ ensureTranscriptSync();
1192
1385
  };
1193
1386
  const onPhoneCommand = (command) => {
1194
1387
  if (!remoteAvailable) {
@@ -1271,6 +1464,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1271
1464
  "[pushary] reachable from your phone \u2014 send an instruction from the app when this goes idle (Ctrl-] keeps the terminal).\n"
1272
1465
  );
1273
1466
  void announcePresence("session_start", "Session ready \u2014 reachable from your phone");
1467
+ ensureTranscriptSync();
1274
1468
  }
1275
1469
  if (startingMode === "remote" && initialPrompt) input.push(userMessage(initialPrompt));
1276
1470
  const exitCode = await driveModeLoop({
@@ -1300,6 +1494,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1300
1494
  process.removeListener("exit", restoreStdin);
1301
1495
  relay?.stop();
1302
1496
  poller.stop();
1497
+ stopTranscript?.();
1303
1498
  approver.teardown();
1304
1499
  input.close();
1305
1500
  stdin.dispose();
@@ -6,15 +6,15 @@ import {
6
6
  removeCodexHooks,
7
7
  removeGeminiSettings,
8
8
  removeInstructionBlock
9
- } from "../chunk-SVVEC4QV.js";
9
+ } from "../chunk-ORKNGSJI.js";
10
10
  import {
11
11
  execNpm
12
12
  } from "../chunk-J7JWI3KU.js";
13
13
  import {
14
14
  removeClaudeMcpServers,
15
15
  removePusharySettings
16
- } from "../chunk-7EW3USQF.js";
17
- import "../chunk-Z5PL3K7C.js";
16
+ } from "../chunk-K74PMS6N.js";
17
+ import "../chunk-CJBVT33U.js";
18
18
 
19
19
  // bin/pushary-clean.ts
20
20
  import { existsSync, readFileSync, writeFileSync, rmSync, readdirSync } from "fs";
@@ -31,21 +31,21 @@ import {
31
31
  toCodexWire,
32
32
  toPolicyLookup,
33
33
  waitForAnswer
34
- } from "../chunk-O5MFSRWV.js";
34
+ } from "../chunk-BEK4SJNX.js";
35
35
  import {
36
36
  getMachineId
37
37
  } from "../chunk-RN3NOEJF.js";
38
38
  import {
39
39
  isGatingMoment,
40
40
  recordKeylessMoment
41
- } from "../chunk-R5AJNXZS.js";
41
+ } from "../chunk-LRGSMQH2.js";
42
42
  import "../chunk-DWED7BS3.js";
43
43
  import {
44
44
  DECISION_LINE_MAX,
45
45
  effectiveWaitSeconds,
46
46
  hookWaitClamped,
47
47
  hookWaitDeadline
48
- } from "../chunk-Z5PL3K7C.js";
48
+ } from "../chunk-CJBVT33U.js";
49
49
  import {
50
50
  getApiKey
51
51
  } from "../chunk-NKXSILEW.js";
@@ -170,7 +170,7 @@ var decidePermissionRequest = async (input) => {
170
170
  const modeState = await fetchModeState(apiKey, input.session_id);
171
171
  if (modeState.kill) return codexDeny(KILL_REASON);
172
172
  const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
173
- const policy = await getPolicy(apiKey, modeState.policyVersion);
173
+ const policy = await getPolicy(apiKey, modeState.policyVersion, "codex");
174
174
  const toolPolicy = resolvePolicy(policy, lookup.tool, modeState.mode, lookup.input);
175
175
  if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "approve") return codexAllow();
176
176
  if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "deny") {
@@ -229,7 +229,7 @@ var decidePreToolUse = async (input) => {
229
229
  const modeState = await fetchModeState(apiKey, input.session_id);
230
230
  if (modeState.kill) return codexDeny(KILL_REASON);
231
231
  const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
232
- const policy = await getPolicy(apiKey, modeState.policyVersion);
232
+ const policy = await getPolicy(apiKey, modeState.policyVersion, "codex");
233
233
  const toolPolicy = resolvePolicy(policy, lookup.tool, modeState.mode, lookup.input);
234
234
  if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "approve") return codexPass();
235
235
  if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "deny") {
@@ -3,12 +3,12 @@ import {
3
3
  askUser,
4
4
  reportEvent,
5
5
  waitForAnswer
6
- } from "../chunk-O5MFSRWV.js";
6
+ } from "../chunk-BEK4SJNX.js";
7
7
  import {
8
8
  getMachineId
9
9
  } from "../chunk-RN3NOEJF.js";
10
10
  import "../chunk-DWED7BS3.js";
11
- import "../chunk-Z5PL3K7C.js";
11
+ import "../chunk-CJBVT33U.js";
12
12
  import {
13
13
  getApiKey
14
14
  } from "../chunk-NKXSILEW.js";
@@ -26,6 +26,18 @@ var MAX_ERROR_STREAK = 6;
26
26
  var REQUEST_TIMEOUT_MS = 1e4;
27
27
  var MAX_SPAWNS_PER_WINDOW = 5;
28
28
  var SPAWN_WINDOW_MS = 6e4;
29
+ var NATIVE_AGENT_BINARIES = { codex: "codex", gemini: "gemini" };
30
+ var buildSpawnLaunch = (req, opts) => {
31
+ const agent = req.agent === "codex" || req.agent === "gemini" ? req.agent : "claude";
32
+ const model = typeof req.model === "string" && req.model.length > 0 ? ["--model", req.model] : [];
33
+ if (agent === "claude") {
34
+ const claudeArgs = ["claude", "--remote", ...model, "-p", req.prompt];
35
+ return opts.spawnDirect ? { direct: true, command: opts.execPath, args: [opts.siblingEntry, ...claudeArgs], agent } : { direct: false, command: opts.pusharyBin, args: claudeArgs, agent };
36
+ }
37
+ const command = opts.resolveNative(NATIVE_AGENT_BINARIES[agent]);
38
+ const args = agent === "codex" ? ["exec", ...model, req.prompt] : [...model, "-p", req.prompt];
39
+ return { direct: false, command, args, agent };
40
+ };
29
41
  var spawnRateAllows = (timestamps, now, maxPerWindow = MAX_SPAWNS_PER_WINDOW, windowMs = SPAWN_WINDOW_MS) => {
30
42
  while (timestamps.length > 0 && now - timestamps[0] > windowMs) timestamps.shift();
31
43
  return timestamps.length < maxPerWindow;
@@ -55,13 +67,20 @@ var runSpawnDaemon = async () => {
55
67
  const launch = (req) => {
56
68
  spawnTimestamps.push(Date.now());
57
69
  const cwd = req.cwd && existsSync(req.cwd) ? req.cwd : process.cwd();
58
- const args = ["claude", "--remote", "-p", req.prompt];
70
+ const env = { ...process.env, PUSHARY_SPAWN_ID: req.id };
71
+ const plan = buildSpawnLaunch(req, {
72
+ spawnDirect,
73
+ execPath: process.execPath,
74
+ siblingEntry,
75
+ pusharyBin,
76
+ resolveNative: (name) => resolveGlobalBinary(name) ?? name
77
+ });
59
78
  try {
60
- const child = spawnDirect ? spawn(process.execPath, [siblingEntry, ...args], { cwd, detached: true, stdio: "ignore", env: process.env }) : spawnClaude(pusharyBin, args, { cwd, detached: true, stdio: "ignore", env: process.env });
79
+ const child = plan.direct ? spawn(plan.command, plan.args, { cwd, detached: true, stdio: "ignore", env }) : spawnClaude(plan.command, plan.args, { cwd, detached: true, stdio: "ignore", env });
61
80
  child.on("error", (err) => stderr(`[pushary] could not launch session: ${err.message}
62
81
  `));
63
82
  child.unref();
64
- stderr(`[pushary] launched a phone-requested session in ${basename(cwd)}
83
+ stderr(`[pushary] launched a phone-requested ${plan.agent} session in ${basename(cwd)}
65
84
  `);
66
85
  } catch (err) {
67
86
  stderr(`[pushary] could not launch session: ${err instanceof Error ? err.message : String(err)}
@@ -11,18 +11,18 @@ import {
11
11
  missingGeminiHookEvents,
12
12
  readCodexMcpAuth,
13
13
  untrustedCodexHookEvents
14
- } from "../chunk-SVVEC4QV.js";
14
+ } from "../chunk-ORKNGSJI.js";
15
15
  import {
16
16
  execNpm
17
17
  } from "../chunk-J7JWI3KU.js";
18
18
  import {
19
19
  readLedgerSummary
20
- } from "../chunk-R5AJNXZS.js";
20
+ } from "../chunk-LRGSMQH2.js";
21
21
  import {
22
22
  callMcpTool,
23
23
  sendMcpRequest
24
24
  } from "../chunk-DWED7BS3.js";
25
- import "../chunk-Z5PL3K7C.js";
25
+ import "../chunk-CJBVT33U.js";
26
26
  import {
27
27
  getBaseUrl
28
28
  } from "../chunk-NKXSILEW.js";
@@ -23,21 +23,21 @@ import {
23
23
  savePendingQuestion,
24
24
  sendNotification,
25
25
  waitForAnswer
26
- } from "../chunk-O5MFSRWV.js";
26
+ } from "../chunk-BEK4SJNX.js";
27
27
  import {
28
28
  getMachineId
29
29
  } from "../chunk-RN3NOEJF.js";
30
30
  import {
31
31
  isGatingMoment,
32
32
  recordKeylessMoment
33
- } from "../chunk-R5AJNXZS.js";
33
+ } from "../chunk-LRGSMQH2.js";
34
34
  import "../chunk-DWED7BS3.js";
35
35
  import {
36
36
  DECISION_LINE_MAX,
37
37
  effectiveWaitSeconds,
38
38
  hookWaitClamped,
39
39
  hookWaitDeadline
40
- } from "../chunk-Z5PL3K7C.js";
40
+ } from "../chunk-CJBVT33U.js";
41
41
  import {
42
42
  getApiKey
43
43
  } from "../chunk-NKXSILEW.js";
@@ -209,7 +209,7 @@ var decideBeforeTool = async (input) => {
209
209
  const modeState = await fetchModeState(apiKey, input.session_id);
210
210
  if (modeState.kill) return geminiDeny(KILL_REASON);
211
211
  const lookup = toGeminiPolicyLookup(input.tool_name ?? "", input.tool_input ?? {});
212
- const policy = await getPolicy(apiKey, modeState.policyVersion);
212
+ const policy = await getPolicy(apiKey, modeState.policyVersion, "gemini_cli");
213
213
  const toolPolicy = resolvePolicy(policy, lookup.tool, modeState.mode, lookup.input);
214
214
  if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "approve") return geminiAllow();
215
215
  if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "deny") {
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-OQMEGREX.js";
5
- import "../chunk-7EW3USQF.js";
4
+ } from "../chunk-ZIQKFF4S.js";
5
+ import "../chunk-K74PMS6N.js";
6
6
  import "../chunk-KQYIHZ5E.js";
7
- import "../chunk-O5MFSRWV.js";
7
+ import "../chunk-BEK4SJNX.js";
8
8
  import "../chunk-RN3NOEJF.js";
9
- import "../chunk-R5AJNXZS.js";
9
+ import "../chunk-LRGSMQH2.js";
10
10
  import "../chunk-DWED7BS3.js";
11
- import "../chunk-Z5PL3K7C.js";
11
+ import "../chunk-CJBVT33U.js";
12
12
  import "../chunk-NKXSILEW.js";
13
13
 
14
14
  // bin/pushary-hook.ts
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleNotification
4
- } from "../chunk-O5MFSRWV.js";
4
+ } from "../chunk-BEK4SJNX.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
6
  import "../chunk-DWED7BS3.js";
7
- import "../chunk-Z5PL3K7C.js";
7
+ import "../chunk-CJBVT33U.js";
8
8
  import "../chunk-NKXSILEW.js";
9
9
 
10
10
  // bin/pushary-notification-hook.ts
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionDenied
4
- } from "../chunk-OQMEGREX.js";
5
- import "../chunk-7EW3USQF.js";
4
+ } from "../chunk-ZIQKFF4S.js";
5
+ import "../chunk-K74PMS6N.js";
6
6
  import "../chunk-KQYIHZ5E.js";
7
- import "../chunk-O5MFSRWV.js";
7
+ import "../chunk-BEK4SJNX.js";
8
8
  import "../chunk-RN3NOEJF.js";
9
- import "../chunk-R5AJNXZS.js";
9
+ import "../chunk-LRGSMQH2.js";
10
10
  import "../chunk-DWED7BS3.js";
11
- import "../chunk-Z5PL3K7C.js";
11
+ import "../chunk-CJBVT33U.js";
12
12
  import "../chunk-NKXSILEW.js";
13
13
 
14
14
  // bin/pushary-permission-denied-hook.ts
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionRequest
4
- } from "../chunk-OQMEGREX.js";
5
- import "../chunk-7EW3USQF.js";
4
+ } from "../chunk-ZIQKFF4S.js";
5
+ import "../chunk-K74PMS6N.js";
6
6
  import "../chunk-KQYIHZ5E.js";
7
- import "../chunk-O5MFSRWV.js";
7
+ import "../chunk-BEK4SJNX.js";
8
8
  import "../chunk-RN3NOEJF.js";
9
- import "../chunk-R5AJNXZS.js";
9
+ import "../chunk-LRGSMQH2.js";
10
10
  import "../chunk-DWED7BS3.js";
11
- import "../chunk-Z5PL3K7C.js";
11
+ import "../chunk-CJBVT33U.js";
12
12
  import "../chunk-NKXSILEW.js";
13
13
 
14
14
  // bin/pushary-permission-hook.ts