@pushary/agent-hooks 0.49.0 → 0.49.1

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.
@@ -509,7 +509,7 @@ var startCommandPoller = (opts) => {
509
509
  schedule(nextDelay());
510
510
  };
511
511
  schedule(fast);
512
- opts.log?.("[pushary] wrapper command poller active (experimental)");
512
+ opts.log?.("[pushary] command poller active");
513
513
  return {
514
514
  stop() {
515
515
  stopped = true;
@@ -1130,6 +1130,11 @@ var runDualMode = async (binary, args2, startingMode) => {
1130
1130
  const agentName = `Claude Code - ${projectName}`;
1131
1131
  const machineId = getMachineId();
1132
1132
  const stdin = createStdinHandoff();
1133
+ const verbose = process.env.PUSHARY_VERBOSE === "1";
1134
+ const verboseLog = (message) => {
1135
+ if (verbose) process.stderr.write(`${message}
1136
+ `);
1137
+ };
1133
1138
  const { initialPrompt, resume, permissionMode } = parseRemoteArgs(args2);
1134
1139
  const userManagesSession = args2.some((a) => SESSION_FLAG_RE.test(a));
1135
1140
  let sessionId = resume;
@@ -1164,10 +1169,8 @@ var runDualMode = async (binary, args2, startingMode) => {
1164
1169
  agentType: "claude_code",
1165
1170
  getSessionId: () => sessionId,
1166
1171
  onCommand: (command) => onPhoneCommand(command),
1167
- onFatal: (message) => process.stderr.write(`[pushary] relay unavailable (${message}); using polling
1168
- `),
1169
- log: (message) => process.stderr.write(`${message}
1170
- `)
1172
+ onFatal: (message) => verboseLog(`[pushary] relay unavailable (${message}); using polling`),
1173
+ log: verboseLog
1171
1174
  });
1172
1175
  relay.start();
1173
1176
  }
@@ -1228,8 +1231,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1228
1231
  activeLeg?.stop("kill");
1229
1232
  }
1230
1233
  },
1231
- log: (message) => process.stderr.write(`${message}
1232
- `)
1234
+ log: verboseLog
1233
1235
  });
1234
1236
  const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
1235
1237
  const onSignal = () => {
@@ -1265,6 +1267,9 @@ var runDualMode = async (binary, args2, startingMode) => {
1265
1267
  };
1266
1268
  try {
1267
1269
  if (sessionId && startingMode === "local") {
1270
+ process.stderr.write(
1271
+ "[pushary] reachable from your phone \u2014 send an instruction from the app when this goes idle (Ctrl-] keeps the terminal).\n"
1272
+ );
1268
1273
  void announcePresence("session_start", "Session ready \u2014 reachable from your phone");
1269
1274
  }
1270
1275
  if (startingMode === "remote" && initialPrompt) input.push(userMessage(initialPrompt));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  removeClaudeAlias
4
- } from "../chunk-OW2AJ74U.js";
4
+ } from "../chunk-BC3VCZ3E.js";
5
5
  import {
6
6
  removeCodexHooks,
7
7
  removeGeminiSettings,
@@ -277,6 +277,13 @@ var main = async () => {
277
277
  console.log();
278
278
  console.log(` ${green(bold("Clean complete."))}`);
279
279
  console.log(` ${dim("Run")} npx @pushary/agent-hooks@latest setup ${dim("to reinstall.")}`);
280
+ if (aliasRemovedFrom.length > 0) {
281
+ console.log();
282
+ console.log(` ${yellow("!")} ${bold("One thing left in your current terminal.")}`);
283
+ console.log(` ${dim("It still has the")} ${bold("claude")} ${dim("alias loaded from before. Run")} ${bold("unalias claude")}`);
284
+ console.log(` ${dim("(or open a new terminal) so")} ${bold("claude")} ${dim("runs Claude Code again.")}`);
285
+ }
286
+ console.log(` ${dim("If a")} ${bold("pushary claude")} ${dim("or")} ${bold("pushary daemon")} ${dim("is still running anywhere, stop it (Ctrl-C).")}`);
280
287
  console.log();
281
288
  };
282
289
  main();
@@ -14,8 +14,9 @@ import {
14
14
  } from "../chunk-NKXSILEW.js";
15
15
 
16
16
  // src/spawn-daemon.ts
17
+ import { spawn } from "child_process";
17
18
  import { existsSync } from "fs";
18
- import { basename } from "path";
19
+ import { basename, dirname, join } from "path";
19
20
  var DRAIN_PATH = "/api/agent/spawn/drain";
20
21
  var FAST_POLL_MS = 3e3;
21
22
  var SLOW_POLL_MS = 3e4;
@@ -43,6 +44,8 @@ var runSpawnDaemon = async () => {
43
44
  const machineId = getMachineId();
44
45
  const baseUrl = getBaseUrl();
45
46
  const pusharyBin = resolveGlobalBinary("pushary") ?? "pushary";
47
+ const siblingEntry = process.argv[1] ? join(dirname(process.argv[1]), "pushary.js") : "";
48
+ const spawnDirect = siblingEntry.length > 0 && existsSync(siblingEntry);
46
49
  let stopped = false;
47
50
  let idleTicks = 0;
48
51
  let errorStreak = 0;
@@ -50,20 +53,11 @@ var runSpawnDaemon = async () => {
50
53
  let timer;
51
54
  let inflight;
52
55
  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
56
  spawnTimestamps.push(Date.now());
59
57
  const cwd = req.cwd && existsSync(req.cwd) ? req.cwd : process.cwd();
58
+ const args = ["claude", "--remote", "-p", req.prompt];
60
59
  try {
61
- const child = spawnClaude(pusharyBin, ["claude", "--remote", "-p", req.prompt], {
62
- cwd,
63
- detached: true,
64
- stdio: "ignore",
65
- env: process.env
66
- });
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 });
67
61
  child.on("error", (err) => stderr(`[pushary] could not launch session: ${err.message}
68
62
  `));
69
63
  child.unref();
@@ -83,8 +77,8 @@ var runSpawnDaemon = async () => {
83
77
  });
84
78
  if (!res.ok) throw new Error(`drain ${res.status}`);
85
79
  const data = await res.json();
86
- const spawn = data.spawn;
87
- return spawn && typeof spawn.prompt === "string" && typeof spawn.id === "string" ? spawn : null;
80
+ const spawn2 = data.spawn;
81
+ return spawn2 && typeof spawn2.prompt === "string" && typeof spawn2.id === "string" ? spawn2 : null;
88
82
  };
89
83
  const nextDelay = () => {
90
84
  if (errorStreak > 0) return Math.min(SLOW_POLL_MS * 2 ** (errorStreak - 1), MAX_ERROR_BACKOFF_MS);
@@ -93,6 +87,10 @@ var runSpawnDaemon = async () => {
93
87
  const jitter = (ms) => Math.max(1, Math.round(ms * (0.85 + Math.random() * 0.3)));
94
88
  const tick = async () => {
95
89
  if (stopped) return;
90
+ if (!spawnRateAllows(spawnTimestamps, Date.now())) {
91
+ timer = setTimeout(tick, jitter(nextDelay()));
92
+ return;
93
+ }
96
94
  const controller = new AbortController();
97
95
  inflight = controller;
98
96
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
@@ -119,18 +117,18 @@ var runSpawnDaemon = async () => {
119
117
  if (timer) clearTimeout(timer);
120
118
  inflight?.abort();
121
119
  };
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
120
  stderr(
129
121
  `[pushary] daemon online \u2014 this machine (${machineId}) can now be sent a new session from your phone. Ctrl-C to stop.
130
122
  `
131
123
  );
132
124
  timer = setTimeout(tick, FAST_POLL_MS);
133
- await new Promise(() => {
125
+ const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
126
+ await new Promise((resolve) => {
127
+ const onSignal = () => {
128
+ teardown();
129
+ resolve();
130
+ };
131
+ for (const signal of signals) process.on(signal, onSignal);
134
132
  });
135
133
  return 0;
136
134
  };
@@ -342,6 +342,45 @@ var main = async () => {
342
342
  }
343
343
  }
344
344
  console.log();
345
+ console.log(` ${dim("Remote control")}`);
346
+ const aliasRc = SHELL_FILES.find((f) => {
347
+ try {
348
+ return /alias\s+claude=['"]?pushary claude/.test(readFileSync(f, "utf-8"));
349
+ } catch {
350
+ return false;
351
+ }
352
+ });
353
+ if (aliasRc) {
354
+ if (globalVersion) {
355
+ check(true, "claude alias \u2192 pushary claude", `set in ${aliasRc.split("/").pop()}`);
356
+ } else {
357
+ check(false, "claude alias \u2192 pushary claude", "alias is set but pushary is not installed \u2014 run `unalias claude` (this shell) or reinstall");
358
+ }
359
+ } else {
360
+ console.log(` ${dim("\u2013")} claude alias not set ${dim("(optional \u2014 run `pushary claude` directly, or re-run setup to add it)")}`);
361
+ }
362
+ if (apiKey) {
363
+ try {
364
+ const modeRes = await fetch(`${getBaseUrl()}/api/mcp/mode`, {
365
+ headers: { Authorization: `Bearer ${apiKey}` },
366
+ signal: AbortSignal.timeout(8e3)
367
+ });
368
+ const relayUrl = modeRes.ok ? (await modeRes.json()).relayUrl : void 0;
369
+ if (typeof relayUrl === "string" && relayUrl.length > 0) {
370
+ const healthUrl = relayUrl.replace(/^wss:/, "https:").replace(/\/relay$/, "/health/ready");
371
+ try {
372
+ const h = await fetch(healthUrl, { signal: AbortSignal.timeout(6e3) });
373
+ console.log(h.ok ? ` ${pass} Relay reachable ${dim("(sub-second phone commands)")}` : ` ${dim("\u2013")} Relay advertised but returned ${h.status} ${dim("(commands still delivered via polling)")}`);
374
+ } catch {
375
+ console.log(` ${dim("\u2013")} Relay advertised but unreachable ${dim("(commands still delivered via polling)")}`);
376
+ }
377
+ } else {
378
+ console.log(` ${dim("\u2013")} Relay not enabled ${dim("(phone commands delivered via polling)")}`);
379
+ }
380
+ } catch {
381
+ }
382
+ }
383
+ console.log();
345
384
  console.log(` ${dim("Connectivity")}`);
346
385
  if (!apiKey) {
347
386
  check(false, "MCP server reachable", "skipped \u2014 no API key");
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  installClaudeAlias,
4
4
  resolveShellRc
5
- } from "../chunk-OW2AJ74U.js";
5
+ } from "../chunk-BC3VCZ3E.js";
6
6
  import {
7
7
  GEMINI_HOOK_BINARY,
8
8
  addCodexHookTrust,
@@ -37,7 +37,9 @@ var resolveShellRc = () => {
37
37
  if (shell === "zsh") return { path: join(home, ".zshrc"), shell };
38
38
  if (shell === "bash") {
39
39
  const bashrc = join(home, ".bashrc");
40
- return { path: existsSync(bashrc) ? bashrc : join(home, ".bash_profile"), shell };
40
+ const profile = join(home, ".bash_profile");
41
+ if (process.platform === "darwin") return { path: profile, shell };
42
+ return { path: existsSync(bashrc) ? bashrc : profile, shell };
41
43
  }
42
44
  return null;
43
45
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.49.0",
3
+ "version": "0.49.1",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",