@pushary/agent-hooks 0.48.0 → 0.49.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.
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ needsShell,
4
+ spawnClaude
5
+ } from "../chunk-XHKBHWLX.js";
2
6
  import {
3
7
  isDeferAnswer
4
8
  } from "../chunk-KQYIHZ5E.js";
@@ -8,12 +12,14 @@ import {
8
12
  deriveToolTarget,
9
13
  describeToolCall,
10
14
  fetchModeState,
11
- getMachineId,
12
15
  getPolicy,
13
16
  reportEvent,
14
17
  resolvePolicy,
15
18
  waitForAnswer
16
- } from "../chunk-VZNP4R5I.js";
19
+ } from "../chunk-O5MFSRWV.js";
20
+ import {
21
+ getMachineId
22
+ } from "../chunk-RN3NOEJF.js";
17
23
  import "../chunk-DWED7BS3.js";
18
24
  import "../chunk-Z5PL3K7C.js";
19
25
  import {
@@ -47,16 +53,6 @@ var findClaudeBinary = () => {
47
53
  return null;
48
54
  };
49
55
 
50
- // src/wrapper/spawnClaude.ts
51
- import { spawn } from "child_process";
52
- var needsShell = (binary, platform = process.platform) => platform === "win32" && /\.(cmd|bat)$/i.test(binary);
53
- var spawnClaude = (binary, args2, options) => {
54
- if (needsShell(binary)) {
55
- return spawn(`"${binary}"`, args2, { ...options, shell: true });
56
- }
57
- return spawn(binary, args2, options);
58
- };
59
-
60
56
  // src/wrapper/localPassthrough.ts
61
57
  var SIGNAL_NUMBERS = {
62
58
  SIGHUP: 1,
@@ -105,7 +101,7 @@ var runLocalPassthrough = (binary, args2) => {
105
101
  import { basename } from "path";
106
102
 
107
103
  // src/wrapper/sdkLoader.ts
108
- import { spawn as spawn2 } from "child_process";
104
+ import { spawn } from "child_process";
109
105
  import { existsSync, mkdirSync, writeFileSync } from "fs";
110
106
  import { homedir } from "os";
111
107
  import { join as join2 } from "path";
@@ -154,7 +150,7 @@ var ensureClaudeSdk = async (log) => {
154
150
  log?.("[pushary] setting up phone control (one-time, fetching the Claude Agent SDK)...");
155
151
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
156
152
  const installed = await new Promise((resolve) => {
157
- const child = spawn2(
153
+ const child = spawn(
158
154
  npmCmd,
159
155
  ["install", "--no-optional", "--no-audit", "--no-fund", "--loglevel=error", SDK_SPEC],
160
156
  { cwd: dir, stdio: "ignore", shell: process.platform === "win32" }
@@ -881,7 +877,7 @@ var nativeArgs = (session) => {
881
877
  return args2;
882
878
  };
883
879
  var runLocalLeg = (session, deps = {}) => {
884
- const spawn3 = deps.spawn ?? spawnClaude;
880
+ const spawn2 = deps.spawn ?? spawnClaude;
885
881
  return new Promise((resolve) => {
886
882
  const legAbort = new AbortController();
887
883
  let exitReason = null;
@@ -911,7 +907,7 @@ var runLocalLeg = (session, deps = {}) => {
911
907
  session.stdin.releaseToChild();
912
908
  let child;
913
909
  try {
914
- child = spawn3(session.binary, nativeArgs(session), {
910
+ child = spawn2(session.binary, nativeArgs(session), {
915
911
  stdio: "inherit",
916
912
  env: { ...process.env, [WRAPPER_ACTIVE_ENV]: "1" },
917
913
  signal: legAbort.signal
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ removeClaudeAlias
4
+ } from "../chunk-OW2AJ74U.js";
2
5
  import {
3
6
  removeCodexHooks,
4
7
  removeGeminiSettings,
@@ -240,6 +243,12 @@ var main = async () => {
240
243
  } catch {
241
244
  }
242
245
  }
246
+ const aliasRemovedFrom = removeClaudeAlias();
247
+ if (aliasRemovedFrom.length > 0) {
248
+ console.log(` ${check} claude alias ${dim(`(removed from ${aliasRemovedFrom.map((p) => p.split("/").pop()).join(", ")})`)}`);
249
+ } else {
250
+ console.log(` ${skip} claude alias ${dim("(not set)")}`);
251
+ }
243
252
  if (existsSync(PUSHARY_DIR)) {
244
253
  rmSync(PUSHARY_DIR, { recursive: true });
245
254
  console.log(` ${check} Local state ${dim("(~/.pushary removed: stored API key, ledger)")}`);
@@ -17,7 +17,6 @@ import {
17
17
  describeApplyPatch,
18
18
  describeToolCall,
19
19
  fetchModeState,
20
- getMachineId,
21
20
  getPolicy,
22
21
  handlePostToolUse,
23
22
  handleStop,
@@ -32,7 +31,10 @@ import {
32
31
  toCodexWire,
33
32
  toPolicyLookup,
34
33
  waitForAnswer
35
- } from "../chunk-VZNP4R5I.js";
34
+ } from "../chunk-O5MFSRWV.js";
35
+ import {
36
+ getMachineId
37
+ } from "../chunk-RN3NOEJF.js";
36
38
  import {
37
39
  isGatingMoment,
38
40
  recordKeylessMoment
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  askUser,
4
- getMachineId,
5
4
  reportEvent,
6
5
  waitForAnswer
7
- } from "../chunk-VZNP4R5I.js";
6
+ } from "../chunk-O5MFSRWV.js";
7
+ import {
8
+ getMachineId
9
+ } from "../chunk-RN3NOEJF.js";
8
10
  import "../chunk-DWED7BS3.js";
9
11
  import "../chunk-Z5PL3K7C.js";
10
12
  import {
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ spawnClaude
4
+ } from "../chunk-XHKBHWLX.js";
5
+ import {
6
+ resolveGlobalBinary
7
+ } from "../chunk-J7JWI3KU.js";
8
+ import {
9
+ getMachineId
10
+ } from "../chunk-RN3NOEJF.js";
11
+ import {
12
+ getApiKey,
13
+ getBaseUrl
14
+ } from "../chunk-NKXSILEW.js";
15
+
16
+ // src/spawn-daemon.ts
17
+ import { existsSync } from "fs";
18
+ import { basename } from "path";
19
+ var DRAIN_PATH = "/api/agent/spawn/drain";
20
+ var FAST_POLL_MS = 3e3;
21
+ var SLOW_POLL_MS = 3e4;
22
+ var FAST_DECAY_TICKS = 3;
23
+ var MAX_ERROR_BACKOFF_MS = 12e4;
24
+ var MAX_ERROR_STREAK = 6;
25
+ var REQUEST_TIMEOUT_MS = 1e4;
26
+ var MAX_SPAWNS_PER_WINDOW = 5;
27
+ var SPAWN_WINDOW_MS = 6e4;
28
+ var spawnRateAllows = (timestamps, now, maxPerWindow = MAX_SPAWNS_PER_WINDOW, windowMs = SPAWN_WINDOW_MS) => {
29
+ while (timestamps.length > 0 && now - timestamps[0] > windowMs) timestamps.shift();
30
+ return timestamps.length < maxPerWindow;
31
+ };
32
+ var stderr = (msg) => {
33
+ process.stderr.write(msg);
34
+ };
35
+ var runSpawnDaemon = async () => {
36
+ let apiKey;
37
+ try {
38
+ apiKey = getApiKey();
39
+ } catch {
40
+ stderr("[pushary] daemon needs an API key. Run `npx @pushary/agent-hooks setup`.\n");
41
+ return 1;
42
+ }
43
+ const machineId = getMachineId();
44
+ const baseUrl = getBaseUrl();
45
+ const pusharyBin = resolveGlobalBinary("pushary") ?? "pushary";
46
+ let stopped = false;
47
+ let idleTicks = 0;
48
+ let errorStreak = 0;
49
+ const spawnTimestamps = [];
50
+ let timer;
51
+ let inflight;
52
+ const launch = (req) => {
53
+ if (!spawnRateAllows(spawnTimestamps, Date.now())) {
54
+ stderr(`[pushary] spawn rate limit reached; skipping a queued session
55
+ `);
56
+ return;
57
+ }
58
+ spawnTimestamps.push(Date.now());
59
+ const cwd = req.cwd && existsSync(req.cwd) ? req.cwd : process.cwd();
60
+ try {
61
+ const child = spawnClaude(pusharyBin, ["claude", "--remote", "-p", req.prompt], {
62
+ cwd,
63
+ detached: true,
64
+ stdio: "ignore",
65
+ env: process.env
66
+ });
67
+ child.on("error", (err) => stderr(`[pushary] could not launch session: ${err.message}
68
+ `));
69
+ child.unref();
70
+ stderr(`[pushary] launched a phone-requested session in ${basename(cwd)}
71
+ `);
72
+ } catch (err) {
73
+ stderr(`[pushary] could not launch session: ${err instanceof Error ? err.message : String(err)}
74
+ `);
75
+ }
76
+ };
77
+ const drain = async (signal) => {
78
+ const res = await fetch(`${baseUrl}${DRAIN_PATH}`, {
79
+ method: "POST",
80
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
81
+ body: JSON.stringify({ machineId }),
82
+ signal
83
+ });
84
+ if (!res.ok) throw new Error(`drain ${res.status}`);
85
+ const data = await res.json();
86
+ const spawn = data.spawn;
87
+ return spawn && typeof spawn.prompt === "string" && typeof spawn.id === "string" ? spawn : null;
88
+ };
89
+ const nextDelay = () => {
90
+ if (errorStreak > 0) return Math.min(SLOW_POLL_MS * 2 ** (errorStreak - 1), MAX_ERROR_BACKOFF_MS);
91
+ return idleTicks >= FAST_DECAY_TICKS ? SLOW_POLL_MS : FAST_POLL_MS;
92
+ };
93
+ const jitter = (ms) => Math.max(1, Math.round(ms * (0.85 + Math.random() * 0.3)));
94
+ const tick = async () => {
95
+ if (stopped) return;
96
+ const controller = new AbortController();
97
+ inflight = controller;
98
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
99
+ try {
100
+ const req = await drain(controller.signal);
101
+ errorStreak = 0;
102
+ if (req) {
103
+ idleTicks = 0;
104
+ launch(req);
105
+ } else {
106
+ idleTicks++;
107
+ }
108
+ } catch {
109
+ errorStreak = Math.min(errorStreak + 1, MAX_ERROR_STREAK);
110
+ } finally {
111
+ clearTimeout(timeout);
112
+ inflight = void 0;
113
+ }
114
+ if (!stopped) timer = setTimeout(tick, jitter(nextDelay()));
115
+ };
116
+ const teardown = () => {
117
+ if (stopped) return;
118
+ stopped = true;
119
+ if (timer) clearTimeout(timer);
120
+ inflight?.abort();
121
+ };
122
+ const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
123
+ const onSignal = () => {
124
+ teardown();
125
+ process.exit(0);
126
+ };
127
+ for (const signal of signals) process.on(signal, onSignal);
128
+ stderr(
129
+ `[pushary] daemon online \u2014 this machine (${machineId}) can now be sent a new session from your phone. Ctrl-C to stop.
130
+ `
131
+ );
132
+ timer = setTimeout(tick, FAST_POLL_MS);
133
+ await new Promise(() => {
134
+ });
135
+ return 0;
136
+ };
137
+
138
+ // bin/pushary-daemon.ts
139
+ runSpawnDaemon().then((code) => process.exit(code)).catch((err) => {
140
+ process.stderr.write(
141
+ `[pushary] daemon error: ${err instanceof Error ? err.message : String(err)}
142
+ `
143
+ );
144
+ process.exit(1);
145
+ });
@@ -12,7 +12,6 @@ import {
12
12
  deriveToolTarget,
13
13
  describeToolCall,
14
14
  fetchModeState,
15
- getMachineId,
16
15
  getPolicy,
17
16
  handlePostToolUse,
18
17
  handleStop,
@@ -24,7 +23,10 @@ import {
24
23
  savePendingQuestion,
25
24
  sendNotification,
26
25
  waitForAnswer
27
- } from "../chunk-VZNP4R5I.js";
26
+ } from "../chunk-O5MFSRWV.js";
27
+ import {
28
+ getMachineId
29
+ } from "../chunk-RN3NOEJF.js";
28
30
  import {
29
31
  isGatingMoment,
30
32
  recordKeylessMoment
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-E3Q3KYZF.js";
4
+ } from "../chunk-OQMEGREX.js";
5
5
  import "../chunk-7EW3USQF.js";
6
6
  import "../chunk-KQYIHZ5E.js";
7
- import "../chunk-VZNP4R5I.js";
7
+ import "../chunk-O5MFSRWV.js";
8
+ import "../chunk-RN3NOEJF.js";
8
9
  import "../chunk-R5AJNXZS.js";
9
10
  import "../chunk-DWED7BS3.js";
10
11
  import "../chunk-Z5PL3K7C.js";
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleNotification
4
- } from "../chunk-VZNP4R5I.js";
4
+ } from "../chunk-O5MFSRWV.js";
5
+ import "../chunk-RN3NOEJF.js";
5
6
  import "../chunk-DWED7BS3.js";
6
7
  import "../chunk-Z5PL3K7C.js";
7
8
  import "../chunk-NKXSILEW.js";
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionDenied
4
- } from "../chunk-E3Q3KYZF.js";
4
+ } from "../chunk-OQMEGREX.js";
5
5
  import "../chunk-7EW3USQF.js";
6
6
  import "../chunk-KQYIHZ5E.js";
7
- import "../chunk-VZNP4R5I.js";
7
+ import "../chunk-O5MFSRWV.js";
8
+ import "../chunk-RN3NOEJF.js";
8
9
  import "../chunk-R5AJNXZS.js";
9
10
  import "../chunk-DWED7BS3.js";
10
11
  import "../chunk-Z5PL3K7C.js";
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionRequest
4
- } from "../chunk-E3Q3KYZF.js";
4
+ } from "../chunk-OQMEGREX.js";
5
5
  import "../chunk-7EW3USQF.js";
6
6
  import "../chunk-KQYIHZ5E.js";
7
- import "../chunk-VZNP4R5I.js";
7
+ import "../chunk-O5MFSRWV.js";
8
+ import "../chunk-RN3NOEJF.js";
8
9
  import "../chunk-R5AJNXZS.js";
9
10
  import "../chunk-DWED7BS3.js";
10
11
  import "../chunk-Z5PL3K7C.js";
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePostToolUse
4
- } from "../chunk-VZNP4R5I.js";
4
+ } from "../chunk-O5MFSRWV.js";
5
+ import "../chunk-RN3NOEJF.js";
5
6
  import "../chunk-DWED7BS3.js";
6
7
  import "../chunk-Z5PL3K7C.js";
7
8
  import "../chunk-NKXSILEW.js";
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleUserPrompt
4
- } from "../chunk-VZNP4R5I.js";
4
+ } from "../chunk-O5MFSRWV.js";
5
+ import "../chunk-RN3NOEJF.js";
5
6
  import "../chunk-DWED7BS3.js";
6
7
  import "../chunk-Z5PL3K7C.js";
7
8
  import "../chunk-NKXSILEW.js";
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleSessionEnd
4
- } from "../chunk-VZNP4R5I.js";
4
+ } from "../chunk-O5MFSRWV.js";
5
+ import "../chunk-RN3NOEJF.js";
5
6
  import "../chunk-DWED7BS3.js";
6
7
  import "../chunk-Z5PL3K7C.js";
7
8
  import "../chunk-NKXSILEW.js";
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleSessionStart
4
- } from "../chunk-VZNP4R5I.js";
4
+ } from "../chunk-O5MFSRWV.js";
5
+ import "../chunk-RN3NOEJF.js";
5
6
  import "../chunk-DWED7BS3.js";
6
7
  import "../chunk-Z5PL3K7C.js";
7
8
  import "../chunk-NKXSILEW.js";
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ installClaudeAlias,
4
+ resolveShellRc
5
+ } from "../chunk-OW2AJ74U.js";
2
6
  import {
3
7
  GEMINI_HOOK_BINARY,
4
8
  addCodexHookTrust,
@@ -24,7 +28,8 @@ import {
24
28
  } from "../chunk-7EW3USQF.js";
25
29
  import {
26
30
  reportEvent
27
- } from "../chunk-VZNP4R5I.js";
31
+ } from "../chunk-O5MFSRWV.js";
32
+ import "../chunk-RN3NOEJF.js";
28
33
  import "../chunk-DWED7BS3.js";
29
34
  import {
30
35
  isValidApiKey
@@ -876,6 +881,27 @@ var offerProjectInstructions = async (agents) => {
876
881
  }
877
882
  console.log(` ${dim2("Commit the file to share it. Teammates without a key fall back to the terminal.")}`);
878
883
  };
884
+ var offerClaudeAlias = async () => {
885
+ if (!process.stdout.isTTY) return;
886
+ const rc = resolveShellRc();
887
+ console.log();
888
+ if (!rc) {
889
+ console.log(` ${dim2("Tip: alias")} ${cyan2("claude='pushary claude'")} ${dim2("in your shell to make plain")} ${cyan2("claude")} ${dim2("reachable from your phone.")}`);
890
+ return;
891
+ }
892
+ const wanted = await confirm({
893
+ message: `Make plain ${bold2("claude")} reachable from your phone by default? (adds an alias to ${basename(rc.path)}, reversible)`,
894
+ default: false
895
+ });
896
+ if (!wanted) return;
897
+ const result = installClaudeAlias();
898
+ if (result.installed) {
899
+ console.log(` ${check2} Aliased ${cyan2("claude")} \u2192 ${cyan2("pushary claude")} in ${dim2(result.path ?? rc.path)}`);
900
+ console.log(` ${dim2("Open a new terminal (or")} ${cyan2(`source ${rc.path}`)}${dim2(") to use it. Undo any time with")} ${cyan2("pushary clean")}${dim2(".")}`);
901
+ } else if (result.reason === "exists") {
902
+ console.log(` ${check2} ${dim2("The")} ${cyan2("claude")} ${dim2("alias is already set.")}`);
903
+ }
904
+ };
879
905
  var main = async () => {
880
906
  const version = getPackageVersion();
881
907
  console.log();
@@ -974,6 +1000,9 @@ var main = async () => {
974
1000
  console.log(` ${yellow2("!")} Failed: ${failed.join(", ")} ${dim2("(others completed successfully)")}`);
975
1001
  }
976
1002
  await offerProjectInstructions(agents);
1003
+ if (completed.includes("claude_code")) {
1004
+ await offerClaudeAlias();
1005
+ }
977
1006
  if (completed.length > 0) {
978
1007
  try {
979
1008
  await reportEvent(
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-VZNP4R5I.js";
4
+ } from "../chunk-O5MFSRWV.js";
5
+ import "../chunk-RN3NOEJF.js";
5
6
  import "../chunk-DWED7BS3.js";
6
7
  import "../chunk-Z5PL3K7C.js";
7
8
  import "../chunk-NKXSILEW.js";
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStopFailure
4
- } from "../chunk-VZNP4R5I.js";
4
+ } from "../chunk-O5MFSRWV.js";
5
+ import "../chunk-RN3NOEJF.js";
5
6
  import "../chunk-DWED7BS3.js";
6
7
  import "../chunk-Z5PL3K7C.js";
7
8
  import "../chunk-NKXSILEW.js";
@@ -20,6 +20,8 @@ if (command === "setup") {
20
20
  await import("./pushary-upgrade.js");
21
21
  } else if (command === "claude") {
22
22
  await import("./pushary-claude.js");
23
+ } else if (command === "daemon") {
24
+ await import("./pushary-daemon.js");
23
25
  } else {
24
26
  console.log(`
25
27
  Pushary Agent Hooks
@@ -29,6 +31,9 @@ Commands:
29
31
  claude Run Claude Code through Pushary \u2014 the native terminal, and reachable from
30
32
  your phone: send an instruction and it drives even a fully idle agent
31
33
  (press Ctrl-] to take the terminal back). Add --remote to start headless.
34
+ daemon Keep this machine reachable to START a new session from your phone, even
35
+ with nothing running. Leave it in a project dir; it launches a headless
36
+ 'pushary claude --remote' when you send a prompt from the app.
32
37
  doctor Verify your Pushary installation is working
33
38
  clean Remove all Pushary configuration (--yes for non-interactive)
34
39
  mode Switch approval mode (push_only, push_first, terminal_only)
@@ -51,5 +56,6 @@ Usage:
51
56
  npx @pushary/agent-hooks@latest wait 45
52
57
  pushary claude # native terminal, reachable from your phone when idle
53
58
  pushary claude --remote -p "start the refactor" # start headless, drive entirely from your phone
59
+ pushary daemon # keep this machine ready to spawn a session from your phone
54
60
  `);
55
61
  }
@@ -1,3 +1,6 @@
1
+ import {
2
+ getMachineId
3
+ } from "./chunk-RN3NOEJF.js";
1
4
  import {
2
5
  callMcpTool,
3
6
  withRetry
@@ -18,14 +21,8 @@ import {
18
21
  getBaseUrl
19
22
  } from "./chunk-NKXSILEW.js";
20
23
 
21
- // src/identity.ts
22
- import { createHash } from "crypto";
23
- import { hostname } from "os";
24
- var deriveMachineId = (host) => createHash("sha256").update(host).digest("hex").slice(0, 8);
25
- var getMachineId = () => deriveMachineId(hostname());
26
-
27
24
  // src/policy.ts
28
- import { createHash as createHash2 } from "crypto";
25
+ import { createHash } from "crypto";
29
26
  import { existsSync, readFileSync, writeFileSync } from "fs";
30
27
  import { join } from "path";
31
28
  import { tmpdir } from "os";
@@ -50,7 +47,7 @@ var isWaitForAnswerResponse = (data) => {
50
47
  // src/policy.ts
51
48
  var CACHE_TTL_MS = 60 * 1e3;
52
49
  var cacheFile = (apiKey) => {
53
- const hash = createHash2("sha256").update(apiKey).digest("hex").slice(0, 12);
50
+ const hash = createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
54
51
  return join(tmpdir(), `pushary-policy-${hash}.json`);
55
52
  };
56
53
  var fetchPolicy = async (apiKey) => {
@@ -632,10 +629,10 @@ var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within
632
629
  // src/throttle.ts
633
630
  import { join as join4 } from "path";
634
631
  import { tmpdir as tmpdir4 } from "os";
635
- import { createHash as createHash3 } from "crypto";
632
+ import { createHash as createHash2 } from "crypto";
636
633
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
637
634
  var THROTTLE_DIR = join4(tmpdir4(), "pushary-throttle");
638
- var markerPath = (key) => join4(THROTTLE_DIR, createHash3("sha256").update(key).digest("hex").slice(0, 16));
635
+ var markerPath = (key) => join4(THROTTLE_DIR, createHash2("sha256").update(key).digest("hex").slice(0, 16));
639
636
  var throttlePass = (key, windowMs) => {
640
637
  try {
641
638
  const path = markerPath(key);
@@ -650,7 +647,7 @@ var throttlePass = (key, windowMs) => {
650
647
 
651
648
  // src/events.ts
652
649
  import { basename, join as join5 } from "path";
653
- import { createHash as createHash4 } from "crypto";
650
+ import { createHash as createHash3 } from "crypto";
654
651
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
655
652
  import { tmpdir as tmpdir5 } from "os";
656
653
  var INTENT_DIR = join5(tmpdir5(), "pushary-intent");
@@ -727,7 +724,7 @@ var notifyLateAnswers = async (apiKey, late, agentName, sessionId) => {
727
724
  var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
728
725
  var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
729
726
  var readFreshCachedPolicy = (apiKey) => {
730
- const hash = createHash4("sha256").update(apiKey).digest("hex").slice(0, 12);
727
+ const hash = createHash3("sha256").update(apiKey).digest("hex").slice(0, 12);
731
728
  const path = join5(tmpdir5(), `pushary-policy-${hash}.json`);
732
729
  if (!existsSync4(path)) return null;
733
730
  const cached = JSON.parse(readFileSync3(path, "utf-8"));
@@ -1087,7 +1084,6 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1087
1084
  };
1088
1085
 
1089
1086
  export {
1090
- getMachineId,
1091
1087
  getPolicy,
1092
1088
  resolvePolicy,
1093
1089
  fetchModeState,
@@ -15,7 +15,6 @@ import {
15
15
  deriveToolTarget,
16
16
  describeToolCall,
17
17
  fetchModeState,
18
- getMachineId,
19
18
  getPolicy,
20
19
  readLastPrompt,
21
20
  readLastUserPrompt,
@@ -24,7 +23,10 @@ import {
24
23
  sendNotification,
25
24
  throttlePass,
26
25
  waitForAnswer
27
- } from "./chunk-VZNP4R5I.js";
26
+ } from "./chunk-O5MFSRWV.js";
27
+ import {
28
+ getMachineId
29
+ } from "./chunk-RN3NOEJF.js";
28
30
  import {
29
31
  isGatingMoment,
30
32
  recordKeylessMoment
@@ -0,0 +1,84 @@
1
+ // src/shell-alias.ts
2
+ import { homedir } from "os";
3
+ import { existsSync, readFileSync, appendFileSync, writeFileSync } from "fs";
4
+ import { join } from "path";
5
+ var MARKER_START = "# >>> pushary >>>";
6
+ var MARKER_END = "# <<< pushary <<<";
7
+ var CANDIDATE_RCS = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"];
8
+ var aliasBlock = () => `${MARKER_START}
9
+ # Make \`claude\` reachable from your phone when idle (runs it through Pushary).
10
+ # Remove this block, or run \`pushary clean\`, to undo.
11
+ alias claude='pushary claude'
12
+ ${MARKER_END}
13
+ `;
14
+ var hasClaudeAlias = (content) => content.includes(MARKER_START) || /alias\s+claude=['"]?pushary claude/.test(content);
15
+ var stripPusharyBlock = (content) => {
16
+ const lines = content.split("\n");
17
+ const out = [];
18
+ let inBlock = false;
19
+ for (const line of lines) {
20
+ const trimmed = line.trim();
21
+ if (trimmed === MARKER_START) {
22
+ inBlock = true;
23
+ continue;
24
+ }
25
+ if (trimmed === MARKER_END) {
26
+ inBlock = false;
27
+ continue;
28
+ }
29
+ if (!inBlock) out.push(line);
30
+ }
31
+ return out.join("\n").replace(/\n{3,}/g, "\n\n");
32
+ };
33
+ var resolveShellRc = () => {
34
+ if (process.platform === "win32") return null;
35
+ const shell = (process.env.SHELL ?? "").split("/").pop() ?? "";
36
+ const home = homedir();
37
+ if (shell === "zsh") return { path: join(home, ".zshrc"), shell };
38
+ if (shell === "bash") {
39
+ const bashrc = join(home, ".bashrc");
40
+ return { path: existsSync(bashrc) ? bashrc : join(home, ".bash_profile"), shell };
41
+ }
42
+ return null;
43
+ };
44
+ var installClaudeAlias = () => {
45
+ const rc = resolveShellRc();
46
+ if (!rc) return { installed: false, reason: "unsupported-shell" };
47
+ let content = "";
48
+ try {
49
+ content = readFileSync(rc.path, "utf-8");
50
+ } catch {
51
+ }
52
+ if (hasClaudeAlias(content)) return { installed: false, path: rc.path, reason: "exists" };
53
+ const gap = content.length > 0 && !content.endsWith("\n") ? "\n\n" : content.length > 0 ? "\n" : "";
54
+ appendFileSync(rc.path, `${gap}${aliasBlock()}`, "utf-8");
55
+ return { installed: true, path: rc.path };
56
+ };
57
+ var removeClaudeAlias = () => {
58
+ const removed = [];
59
+ for (const name of CANDIDATE_RCS) {
60
+ const path = join(homedir(), name);
61
+ if (!existsSync(path)) continue;
62
+ let content;
63
+ try {
64
+ content = readFileSync(path, "utf-8");
65
+ } catch {
66
+ continue;
67
+ }
68
+ const stripped = stripPusharyBlock(content);
69
+ if (stripped !== content) {
70
+ try {
71
+ writeFileSync(path, stripped, "utf-8");
72
+ removed.push(path);
73
+ } catch {
74
+ }
75
+ }
76
+ }
77
+ return removed;
78
+ };
79
+
80
+ export {
81
+ resolveShellRc,
82
+ installClaudeAlias,
83
+ removeClaudeAlias
84
+ };
@@ -0,0 +1,9 @@
1
+ // src/identity.ts
2
+ import { createHash } from "crypto";
3
+ import { hostname } from "os";
4
+ var deriveMachineId = (host) => createHash("sha256").update(host).digest("hex").slice(0, 8);
5
+ var getMachineId = () => deriveMachineId(hostname());
6
+
7
+ export {
8
+ getMachineId
9
+ };
@@ -0,0 +1,14 @@
1
+ // src/wrapper/spawnClaude.ts
2
+ import { spawn } from "child_process";
3
+ var needsShell = (binary, platform = process.platform) => platform === "win32" && /\.(cmd|bat)$/i.test(binary);
4
+ var spawnClaude = (binary, args, options) => {
5
+ if (needsShell(binary)) {
6
+ return spawn(`"${binary}"`, args, { ...options, shell: true });
7
+ }
8
+ return spawn(binary, args, options);
9
+ };
10
+
11
+ export {
12
+ needsShell,
13
+ spawnClaude
14
+ };
package/dist/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-E3Q3KYZF.js";
3
+ } from "../chunk-OQMEGREX.js";
4
4
  import "../chunk-7EW3USQF.js";
5
5
  import "../chunk-KQYIHZ5E.js";
6
6
  import {
@@ -15,7 +15,8 @@ import {
15
15
  reportEvent,
16
16
  resolvePolicy,
17
17
  waitForAnswer
18
- } from "../chunk-VZNP4R5I.js";
18
+ } from "../chunk-O5MFSRWV.js";
19
+ import "../chunk-RN3NOEJF.js";
19
20
  import "../chunk-R5AJNXZS.js";
20
21
  import "../chunk-DWED7BS3.js";
21
22
  import "../chunk-Z5PL3K7C.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -54,6 +54,7 @@
54
54
  "pushary-permission-hook": "./dist/bin/pushary-permission-hook.js",
55
55
  "pushary-permission-denied-hook": "./dist/bin/pushary-permission-denied-hook.js",
56
56
  "pushary-claude": "./dist/bin/pushary-claude.js",
57
+ "pushary-daemon": "./dist/bin/pushary-daemon.js",
57
58
  "pushary-codex": "./dist/bin/pushary-codex.js",
58
59
  "pushary-codex-hook": "./dist/bin/pushary-codex-hook.js",
59
60
  "pushary-gemini-hook": "./dist/bin/pushary-gemini-hook.js",