@pushary/agent-hooks 0.10.0 → 0.11.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.
@@ -88,11 +88,15 @@ var startSpinner = (getLabel) => {
88
88
  `);
89
89
  };
90
90
  };
91
- var printQr = (url) => new Promise((resolve) => {
92
- qrcodeTerminal.generate(url, { small: true }, (qr) => {
93
- process.stdout.write("\n" + qr.split("\n").map((line) => " " + line).join("\n") + "\n");
94
- resolve();
95
- });
91
+ var printQr = (url) => new Promise((resolve, reject) => {
92
+ try {
93
+ qrcodeTerminal.generate(url, { small: true }, (qr) => {
94
+ process.stdout.write("\n" + qr.split("\n").map((line) => " " + line).join("\n") + "\n");
95
+ resolve();
96
+ });
97
+ } catch (err) {
98
+ reject(err);
99
+ }
96
100
  });
97
101
  var waitForDevice = async (apiKey) => {
98
102
  const deadline = Date.now() + CONNECT_TIMEOUT_MS;
@@ -200,17 +204,17 @@ var isInstalled = (command) => {
200
204
  return false;
201
205
  }
202
206
  };
203
- var readJson = (path) => {
207
+ var readJson = (filePath) => {
204
208
  try {
205
- return JSON.parse(readFileSync(path, "utf-8"));
209
+ return JSON.parse(readFileSync(filePath, "utf-8"));
206
210
  } catch {
207
211
  return {};
208
212
  }
209
213
  };
210
- var writeJson = (path, data) => {
211
- const dir = path.substring(0, path.lastIndexOf("/"));
214
+ var writeJson = (filePath, data) => {
215
+ const dir = dirname(filePath);
212
216
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
213
- writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
217
+ writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
214
218
  };
215
219
  var formatError = (err) => {
216
220
  if (err instanceof Error) {
@@ -340,32 +344,42 @@ var setupClaudeCode = async (apiKey) => {
340
344
  console.log(` ${dim2("\u2022")} Hooks: route permission approvals through push notifications`);
341
345
  console.log(` ${dim2("\u2022")} Auto-allowed tools: no permission prompts for Pushary MCP calls`);
342
346
  };
343
- var findPython310Plus = () => {
344
- const candidates = ["python3.13", "python3.12", "python3.11", "python3.10", "python3", "python"];
345
- for (const py of candidates) {
346
- try {
347
- const version = execSync(`${py} --version 2>&1`, { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).trim();
348
- const match = version.match(/Python (\d+)\.(\d+)/);
349
- if (match && (Number(match[1]) > 3 || Number(match[1]) === 3 && Number(match[2]) >= 10)) {
350
- return py;
347
+ var resolveHermesPython = () => {
348
+ try {
349
+ const launcher = execSync("command -v hermes", { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).trim();
350
+ if (launcher) {
351
+ const shebang = readFileSync(launcher, "utf-8").split("\n", 1)[0];
352
+ if (shebang.startsWith("#!")) {
353
+ const interpreter = shebang.slice(2).trim().split(/\s+/)[0];
354
+ if (interpreter && /python/i.test(interpreter) && existsSync(interpreter)) return interpreter;
351
355
  }
352
- } catch {
353
356
  }
357
+ } catch {
354
358
  }
359
+ const venvPython = join(homedir(), ".hermes", "hermes-agent", "venv", "bin", "python3");
360
+ if (existsSync(venvPython)) return venvPython;
355
361
  return null;
356
362
  };
357
- var installPythonPlugin = (pythonBin) => {
363
+ var ensurePip = (python) => {
358
364
  try {
359
- execSync(`${pythonBin} -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe", timeout: 12e4 });
360
- } catch (err) {
361
- const msg = npmErrorMessage(err);
362
- if (!msg.includes("externally-managed-environment")) throw err;
363
- execSync(
364
- `${pythonBin} -m pip install --upgrade --user --break-system-packages hermes-plugin-pushary`,
365
- { stdio: "pipe", timeout: 12e4 }
366
- );
365
+ execSync(`"${python}" -m pip --version`, { stdio: "ignore", timeout: 15e3 });
366
+ return;
367
+ } catch {
368
+ }
369
+ try {
370
+ execSync(`"${python}" -m ensurepip --upgrade`, { stdio: "pipe", timeout: 6e4 });
371
+ } catch {
367
372
  }
368
373
  };
374
+ var enablePusharyPlugin = (python) => {
375
+ const snippet = 'from hermes_cli.config import load_config, save_config; c = load_config(); p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}; e = p.get("enabled") if isinstance(p.get("enabled"), list) else []; p["enabled"] = (e + ["pushary"]) if "pushary" not in e else e; c["plugins"] = p; save_config(c)';
376
+ try {
377
+ execSync(`"${python}" -c '${snippet}'`, { stdio: "pipe", timeout: 15e3 });
378
+ return;
379
+ } catch {
380
+ }
381
+ execSync("hermes plugins enable pushary", { stdio: "ignore", timeout: 1e4 });
382
+ };
369
383
  var setupHermes = async (_apiKey) => {
370
384
  console.log(`
371
385
  ${bold2("Setting up Hermes Agent")}
@@ -375,72 +389,24 @@ var setupHermes = async (_apiKey) => {
375
389
  console.log(` ${dim2("Install Hermes and re-run setup to configure.")}`);
376
390
  return;
377
391
  }
378
- await spinner("Installing hermes-plugin-pushary", async () => {
379
- try {
380
- execSync("uv pip install hermes-plugin-pushary", { stdio: "pipe", timeout: 12e4 });
381
- return;
382
- } catch {
383
- }
384
- if (isInstalled("pipx")) {
385
- try {
386
- execSync("pipx inject hermes hermes-plugin-pushary", { stdio: "pipe", timeout: 12e4 });
387
- return;
388
- } catch {
389
- }
390
- }
391
- let python = findPython310Plus();
392
- if (!python) {
393
- if (process.platform === "darwin") {
394
- try {
395
- execSync("which brew", { stdio: "ignore", timeout: 5e3 });
396
- execSync("brew install python@3.12", { stdio: "pipe", timeout: 3e5 });
397
- python = findPython310Plus();
398
- } catch {
399
- }
400
- } else if (process.platform === "linux") {
401
- for (const [check3, install] of [
402
- ["which apt-get", "sudo apt-get update -qq && sudo apt-get install -y -qq python3 python3-pip"],
403
- ["which dnf", "sudo dnf install -y -q python3 python3-pip"],
404
- ["which yum", "sudo yum install -y -q python3 python3-pip"],
405
- ["which pacman", "sudo pacman -S --noconfirm python python-pip"]
406
- ]) {
407
- try {
408
- execSync(check3, { stdio: "ignore", timeout: 5e3 });
409
- execSync(install, { stdio: "pipe", timeout: 3e5 });
410
- python = findPython310Plus();
411
- if (python) break;
412
- } catch {
413
- }
414
- }
415
- }
416
- }
417
- if (python) {
418
- installPythonPlugin(python);
419
- return;
420
- }
421
- for (const pip of ["pip3", "pip"]) {
422
- try {
423
- execSync(`${pip} install hermes-plugin-pushary`, { stdio: "pipe", timeout: 12e4 });
424
- return;
425
- } catch (err) {
426
- if (npmErrorMessage(err).includes("externally-managed-environment")) {
427
- try {
428
- execSync(`${pip} install --user --break-system-packages hermes-plugin-pushary`, { stdio: "pipe", timeout: 12e4 });
429
- return;
430
- } catch {
431
- }
432
- }
433
- }
434
- }
435
- throw new Error("Python 3.10+ not found and could not be installed");
392
+ const python = resolveHermesPython();
393
+ if (!python) {
394
+ console.log(` ${yellow2("!")} Could not locate the Python that Hermes runs in. Skipping.`);
395
+ console.log(` ${dim2("Install manually: <hermes-python> -m pip install hermes-plugin-pushary")}`);
396
+ return;
397
+ }
398
+ await spinner("Installing hermes-plugin-pushary into Hermes\u2019 environment", async () => {
399
+ ensurePip(python);
400
+ execSync(`"${python}" -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe", timeout: 18e4 });
436
401
  });
437
402
  await spinner("Enabling plugin", async () => {
438
- execSync("hermes plugins enable pushary", { stdio: "ignore", timeout: 1e4 });
403
+ enablePusharyPlugin(python);
439
404
  });
440
405
  console.log();
441
406
  console.log(` ${dim2("What this configured:")}`);
442
407
  console.log(` ${dim2("\u2022")} Native tools: pushary_notify, pushary_ask, pushary_wait, pushary_cancel`);
443
408
  console.log(` ${dim2("\u2022")} Auto-notifications: push alert when tools return errors`);
409
+ console.log(` ${dim2("\u2022")} Permission gating: set ${bold2("PUSHARY_GATE_TOOLS")} to require lock-screen approval for risky tools`);
444
410
  };
445
411
  var setupCodex = async (_apiKey) => {
446
412
  console.log(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "author": "Pushary <business@pushary.com>",
6
6
  "homepage": "https://pushary.com",