@pushary/agent-hooks 1.7.1 → 1.7.3

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/data/cursor-plugin/CHANGELOG.md +1 -0
  3. package/data/cursor-plugin/scripts/pushary-gate.mjs +3 -3
  4. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +23 -1
  5. package/data/vscode-plugin/CHANGELOG.md +1 -0
  6. package/data/vscode-plugin/scripts/pushary-gate.mjs +3 -3
  7. package/dist/bin/pushary-bell-hook.js +23 -23
  8. package/dist/bin/pushary-bell.js +10 -10
  9. package/dist/bin/pushary-claude.js +48 -37
  10. package/dist/bin/pushary-clean.js +107 -60
  11. package/dist/bin/pushary-codex-bridge.js +18 -17
  12. package/dist/bin/pushary-codex-hook.js +53 -39
  13. package/dist/bin/pushary-codex.js +14 -12
  14. package/dist/bin/pushary-connect.js +16 -16
  15. package/dist/bin/pushary-cowork.js +5 -5
  16. package/dist/bin/pushary-daemon.js +27 -27
  17. package/dist/bin/pushary-disconnect.js +19 -17
  18. package/dist/bin/pushary-doctor.js +63 -35
  19. package/dist/bin/pushary-elicitation-hook.js +35 -26
  20. package/dist/bin/pushary-gemini-bridge.js +12 -12
  21. package/dist/bin/pushary-gemini-hook.js +47 -32
  22. package/dist/bin/pushary-hook.js +34 -32
  23. package/dist/bin/pushary-login.js +16 -16
  24. package/dist/bin/pushary-logout.js +8 -8
  25. package/dist/bin/pushary-mcp.js +9 -8
  26. package/dist/bin/pushary-mode.js +10 -10
  27. package/dist/bin/pushary-notification-hook.js +29 -27
  28. package/dist/bin/pushary-opencode-hook.js +46 -32
  29. package/dist/bin/pushary-permission-denied-hook.js +34 -32
  30. package/dist/bin/pushary-permission-hook.js +33 -31
  31. package/dist/bin/pushary-post-hook.js +29 -27
  32. package/dist/bin/pushary-prompt-hook.js +29 -27
  33. package/dist/bin/pushary-session-end-hook.js +29 -27
  34. package/dist/bin/pushary-session-start-hook.js +33 -31
  35. package/dist/bin/pushary-setup.js +69 -56
  36. package/dist/bin/pushary-stats.js +5 -5
  37. package/dist/bin/pushary-status.js +19 -19
  38. package/dist/bin/pushary-stop-hook.js +29 -27
  39. package/dist/bin/pushary-stopfailure-hook.js +29 -27
  40. package/dist/bin/pushary-suggestions.js +5 -5
  41. package/dist/bin/pushary-transcript-register.js +19 -19
  42. package/dist/bin/pushary-transcripts.js +7 -7
  43. package/dist/bin/pushary-upgrade.js +20 -20
  44. package/dist/bin/pushary-wait.js +10 -10
  45. package/dist/bin/pushary.js +6 -6
  46. package/dist/{chunk-MWVCDP64.js → chunk-3EUBQARL.js} +1 -1
  47. package/dist/{chunk-3QORH5EJ.js → chunk-5GB22QYL.js} +6 -4
  48. package/dist/chunk-5OP5MQI7.js +45 -0
  49. package/dist/{chunk-6PV2JQ64.js → chunk-5S2VBSJI.js} +2 -2
  50. package/dist/{chunk-UPKP67TA.js → chunk-62CVUUNU.js} +2 -2
  51. package/dist/{chunk-42XR73I2.js → chunk-62VT3BPH.js} +35 -35
  52. package/dist/chunk-6JQFIHZC.js +99 -0
  53. package/dist/{chunk-2FU43B5Q.js → chunk-7OA34PNW.js} +2 -2
  54. package/dist/{chunk-7GI6QC3M.js → chunk-7RUJKCR4.js} +2 -2
  55. package/dist/{chunk-H6JV44TE.js → chunk-AWVNAYGD.js} +1 -1
  56. package/dist/{chunk-XMN37YJH.js → chunk-BGBLLHZ4.js} +1 -1
  57. package/dist/{chunk-VCMFKZON.js → chunk-BIH2Y2HP.js} +2 -2
  58. package/dist/{chunk-YZKS3ME3.js → chunk-D6OODZCM.js} +2 -2
  59. package/dist/{chunk-JTQDP4ZL.js → chunk-DUQO4C4I.js} +3 -3
  60. package/dist/{chunk-3QE3BHBW.js → chunk-EADXTSHE.js} +2 -2
  61. package/dist/{chunk-43SSQCWO.js → chunk-EEEJ2JIL.js} +3 -3
  62. package/dist/{chunk-4C4NRDIC.js → chunk-EUGNVLMG.js} +1 -1
  63. package/dist/{chunk-WSC53O7I.js → chunk-G3OMOTJ4.js} +3 -3
  64. package/dist/{chunk-XFNVG3OV.js → chunk-G4GLCLKK.js} +44 -9
  65. package/dist/{chunk-62TEPRWQ.js → chunk-G62JL2NT.js} +1 -1
  66. package/dist/{chunk-QPR33SHS.js → chunk-G6RRVJOG.js} +6 -6
  67. package/dist/{chunk-Y6GKOZBT.js → chunk-IPSRMLCJ.js} +1 -1
  68. package/dist/{chunk-MCNS4725.js → chunk-IRZG3HEU.js} +1 -1
  69. package/dist/{chunk-55RO62FV.js → chunk-IWJKSIB4.js} +1 -1
  70. package/dist/{chunk-NID4UZDN.js → chunk-KEL5MXRL.js} +1 -1
  71. package/dist/{chunk-BYVB3RX3.js → chunk-KJXUIPS4.js} +2 -2
  72. package/dist/{chunk-SNWNA3MQ.js → chunk-LHC45YUC.js} +1 -1
  73. package/dist/{chunk-XPTCPU6V.js → chunk-LPNJLKBQ.js} +1 -1
  74. package/dist/{chunk-WZWXBTRF.js → chunk-MIXWMA73.js} +3 -3
  75. package/dist/{chunk-454KMJHF.js → chunk-MZBRL5H5.js} +34 -1
  76. package/dist/{chunk-KSNAAMU5.js → chunk-N5MZH5A6.js} +4 -4
  77. package/dist/chunk-N6IOSZOE.js +185 -0
  78. package/dist/{chunk-XPMOCJHN.js → chunk-NOI3XTCY.js} +1 -1
  79. package/dist/{chunk-NAAZCNKI.js → chunk-O3JDWNLU.js} +351 -37
  80. package/dist/{chunk-7GYGVQBU.js → chunk-OTNOANMX.js} +8 -8
  81. package/dist/{chunk-P5CBNSYL.js → chunk-OVC5QXN5.js} +1 -1
  82. package/dist/{chunk-O5JD7UI5.js → chunk-P2VIBGXA.js} +3 -3
  83. package/dist/{chunk-3T35OE7C.js → chunk-PRNFSORD.js} +4 -4
  84. package/dist/{chunk-OAL44FM2.js → chunk-R6OYCUNK.js} +1 -1
  85. package/dist/{chunk-6SJQ442W.js → chunk-RLW3JWDY.js} +2 -2
  86. package/dist/{chunk-SVEMF2VO.js → chunk-TCVMK6GI.js} +4 -4
  87. package/dist/{chunk-O5PRIYFZ.js → chunk-U4TMAANR.js} +3 -0
  88. package/dist/{chunk-WM3U2RUK.js → chunk-U6RVDUJ5.js} +75 -232
  89. package/dist/{chunk-KBLIVEFJ.js → chunk-UOQRM7JQ.js} +5 -5
  90. package/dist/{chunk-NQFGVAT6.js → chunk-VE7TXLR4.js} +19 -2
  91. package/dist/{chunk-IGBDOZUO.js → chunk-VKVSRCBO.js} +1 -1
  92. package/dist/{chunk-ZEMSCGDD.js → chunk-VYPJHGG6.js} +8 -3
  93. package/dist/{chunk-YQN7S57T.js → chunk-WGMW7OXW.js} +6 -6
  94. package/dist/{chunk-OTZHT34O.js → chunk-WPX67AKT.js} +1 -1
  95. package/dist/{chunk-XDASEYRS.js → chunk-WTJM7PSL.js} +3 -3
  96. package/dist/{chunk-Q7QRR6YL.js → chunk-Y4ZH64UK.js} +2 -2
  97. package/dist/{chunk-ZIIYTQHC.js → chunk-YC27PROF.js} +12 -4
  98. package/dist/{chunk-AHWJHLG5.js → chunk-YPUBA2FL.js} +7 -5
  99. package/dist/{chunk-EIZW73P5.js → chunk-YYCTF4Q5.js} +1 -1
  100. package/dist/{chunk-AAGLDXH4.js → chunk-ZKJ34T2F.js} +5 -5
  101. package/dist/{reapply-66GQIBXN.js → reapply-PH5T4BGR.js} +9 -9
  102. package/dist/src/index.d.ts +1 -0
  103. package/dist/src/index.js +22 -20
  104. package/package.json +2 -1
  105. package/dist/chunk-JMBG47WV.js +0 -336
  106. package/dist/chunk-YHG74UFF.js +0 -12
@@ -2,17 +2,17 @@ import {
2
2
  CLAUDE_HOOK_EVENTS,
3
3
  GEMINI_HOOK_EVENTS,
4
4
  hasGeminiHooks
5
- } from "./chunk-WZWXBTRF.js";
5
+ } from "./chunk-MIXWMA73.js";
6
6
  import {
7
7
  CODEX_HOOK_EVENTS,
8
8
  hasCodexHooks,
9
9
  holdBudgetOf
10
- } from "./chunk-454KMJHF.js";
10
+ } from "./chunk-MZBRL5H5.js";
11
11
  import {
12
12
  CURSOR_GATE_EVENTS,
13
13
  HOOK_LOCATOR_BINARY,
14
14
  hookCommandHoldBudget
15
- } from "./chunk-XFNVG3OV.js";
15
+ } from "./chunk-G4GLCLKK.js";
16
16
 
17
17
  // src/diagnostics/wiring.ts
18
18
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -2,10 +2,10 @@ import {
2
2
  resolveBinary,
3
3
  resolveGlobalPackageVersion,
4
4
  resolvePackageVersionFromBinary
5
- } from "./chunk-454KMJHF.js";
5
+ } from "./chunk-MZBRL5H5.js";
6
6
  import {
7
7
  MANAGED_PROVIDERS
8
- } from "./chunk-XFNVG3OV.js";
8
+ } from "./chunk-G4GLCLKK.js";
9
9
 
10
10
  // src/live-capabilities.ts
11
11
  var CLAUDE_BRIDGE_CAPABILITY = "claude-bridge";
@@ -1,16 +1,17 @@
1
1
  import {
2
2
  clientErrorKindOf,
3
3
  reportClientError
4
- } from "./chunk-KSNAAMU5.js";
4
+ } from "./chunk-N5MZH5A6.js";
5
5
  import {
6
+ SubscriptionRequiredError,
6
7
  callMcpTool
7
- } from "./chunk-NQFGVAT6.js";
8
+ } from "./chunk-VE7TXLR4.js";
8
9
  import {
9
10
  installNetworkDefaults
10
- } from "./chunk-JTQDP4ZL.js";
11
+ } from "./chunk-DUQO4C4I.js";
11
12
  import {
12
13
  getBaseUrl
13
- } from "./chunk-OAL44FM2.js";
14
+ } from "./chunk-R6OYCUNK.js";
14
15
 
15
16
  // src/api.ts
16
17
  import { randomUUID } from "crypto";
@@ -53,6 +54,11 @@ var DECISION_STOP_REASON = "Approval was cancelled or became unavailable. Retry
53
54
  var isDecisionStopped = (result) => result.handoffAction === "stop" || ["cancelled", "unavailable", "stopped", "missing"].includes(result.status ?? "");
54
55
  var isLocalQuestionHandoff = (result) => result.suppressed === true || result.noDevices === true || result.status === "terminal" || result.status === "notified";
55
56
  var STOPPED = { answered: false, handoffAction: "stop" };
57
+ var UNVERIFIABLE_APPROVAL_REASON = "Pushary could not create a verifiable approval. Retry the action.";
58
+ var unavailableQuestionUnlessSubscriptionRequired = (error) => {
59
+ if (error instanceof SubscriptionRequiredError) throw error;
60
+ return { correlationId: "", status: "unavailable" };
61
+ };
56
62
  var askUser = async (apiKey, params, timeoutMs = 15e3) => {
57
63
  const identified = params.toolUseId || params.requestId ? params : { ...params, requestId: randomUUID() };
58
64
  const result = await callMcpTool(apiKey, "ask_user", { ...identified, wait: false }, { maxRetries: 3, timeoutMs });
@@ -156,6 +162,8 @@ export {
156
162
  DECISION_STOP_REASON,
157
163
  isDecisionStopped,
158
164
  isLocalQuestionHandoff,
165
+ UNVERIFIABLE_APPROVAL_REASON,
166
+ unavailableQuestionUnlessSubscriptionRequired,
159
167
  askUser,
160
168
  waitForAnswer,
161
169
  nextLongPollMs,
@@ -11,20 +11,20 @@ import {
11
11
  clipToRow,
12
12
  terminalColumns,
13
13
  useColor
14
- } from "./chunk-O5PRIYFZ.js";
14
+ } from "./chunk-U4TMAANR.js";
15
15
  import {
16
16
  callMcpTool
17
- } from "./chunk-NQFGVAT6.js";
17
+ } from "./chunk-VE7TXLR4.js";
18
18
  import {
19
19
  reportedInstallSource
20
- } from "./chunk-UPKP67TA.js";
20
+ } from "./chunk-62CVUUNU.js";
21
21
  import {
22
22
  NPX_LATEST_COMMAND
23
- } from "./chunk-EIZW73P5.js";
23
+ } from "./chunk-YYCTF4Q5.js";
24
24
  import {
25
25
  PAIR_TTL_SECONDS,
26
26
  isAnswerSource
27
- } from "./chunk-XFNVG3OV.js";
27
+ } from "./chunk-G4GLCLKK.js";
28
28
 
29
29
  // src/reach.ts
30
30
  var reachVerdict = (channels) => {
@@ -766,6 +766,8 @@ var describePairFailure = (reason) => {
766
766
  return "Finish setting up in the Pushary app, then run setup again";
767
767
  case "authorize_failed":
768
768
  return "The app could not authorize this terminal. Re-run setup to try again";
769
+ case "payment_required":
770
+ return "Your Pushary workspace has no active subscription. Subscribe in the Pushary app or at pushary.com, then run setup again";
769
771
  default:
770
772
  return "Pairing failed. Re-run setup to try again";
771
773
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SETUP_AGENT_CHOICES
3
- } from "./chunk-XFNVG3OV.js";
3
+ } from "./chunk-G4GLCLKK.js";
4
4
 
5
5
  // src/cli/commands.ts
6
6
  var NPX_LATEST_COMMAND = "npx @pushary/agent-hooks@latest";
@@ -4,14 +4,14 @@ import {
4
4
  } from "./chunk-XHKBHWLX.js";
5
5
  import {
6
6
  readControlMode
7
- } from "./chunk-YZKS3ME3.js";
7
+ } from "./chunk-D6OODZCM.js";
8
8
  import {
9
9
  getMachineId
10
- } from "./chunk-P5CBNSYL.js";
10
+ } from "./chunk-OVC5QXN5.js";
11
11
  import {
12
12
  pusharyDir,
13
13
  resolveGlobalBinary
14
- } from "./chunk-454KMJHF.js";
14
+ } from "./chunk-MZBRL5H5.js";
15
15
  import {
16
16
  readJsonSafe,
17
17
  writeFileAtomic,
@@ -20,10 +20,10 @@ import {
20
20
  } from "./chunk-T7E75EKA.js";
21
21
  import {
22
22
  getApiKey
23
- } from "./chunk-OAL44FM2.js";
23
+ } from "./chunk-R6OYCUNK.js";
24
24
  import {
25
25
  getPackageVersion
26
- } from "./chunk-XFNVG3OV.js";
26
+ } from "./chunk-G4GLCLKK.js";
27
27
 
28
28
  // src/daemon-lifecycle.ts
29
29
  import { dirname, join, win32 as win32Path } from "path";
@@ -9,17 +9,17 @@ import {
9
9
  reapplyGeminiSettings,
10
10
  reapplyVsCodePlugin,
11
11
  repairCodexHolds
12
- } from "./chunk-QPR33SHS.js";
13
- import "./chunk-Y6GKOZBT.js";
14
- import "./chunk-6SJQ442W.js";
15
- import "./chunk-WSC53O7I.js";
16
- import "./chunk-XDASEYRS.js";
17
- import "./chunk-WZWXBTRF.js";
12
+ } from "./chunk-G6RRVJOG.js";
13
+ import "./chunk-IPSRMLCJ.js";
14
+ import "./chunk-RLW3JWDY.js";
15
+ import "./chunk-G3OMOTJ4.js";
16
+ import "./chunk-WTJM7PSL.js";
17
+ import "./chunk-MIXWMA73.js";
18
18
  import "./chunk-XHKBHWLX.js";
19
- import "./chunk-4C4NRDIC.js";
20
- import "./chunk-454KMJHF.js";
19
+ import "./chunk-EUGNVLMG.js";
20
+ import "./chunk-MZBRL5H5.js";
21
21
  import "./chunk-T7E75EKA.js";
22
- import "./chunk-XFNVG3OV.js";
22
+ import "./chunk-G4GLCLKK.js";
23
23
  export {
24
24
  ensureCodexTrustAfterReapply,
25
25
  inspectConfiguredManagedProviders,
@@ -109,6 +109,7 @@ declare const handlePostToolUse: (input: {
109
109
  tool_result?: unknown;
110
110
  cwd?: string;
111
111
  session_id?: string;
112
+ agent_id?: string;
112
113
  transcript_path?: string;
113
114
  tool_use_id?: string;
114
115
  }, agent?: AgentIdentity) => Promise<string | undefined>;
package/dist/src/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-42XR73I2.js";
4
- import "../chunk-KBLIVEFJ.js";
5
- import "../chunk-WSC53O7I.js";
6
- import "../chunk-3QORH5EJ.js";
7
- import "../chunk-MCNS4725.js";
8
- import "../chunk-YHG74UFF.js";
9
- import "../chunk-WZWXBTRF.js";
3
+ } from "../chunk-62VT3BPH.js";
4
+ import "../chunk-UOQRM7JQ.js";
5
+ import "../chunk-G3OMOTJ4.js";
6
+ import "../chunk-5GB22QYL.js";
7
+ import "../chunk-IRZG3HEU.js";
8
+ import "../chunk-5OP5MQI7.js";
9
+ import "../chunk-MIXWMA73.js";
10
10
  import {
11
11
  fetchAccountPublicKey
12
- } from "../chunk-OTZHT34O.js";
12
+ } from "../chunk-WPX67AKT.js";
13
13
  import {
14
14
  fetchModeOverride,
15
15
  fetchModeState,
@@ -18,31 +18,33 @@ import {
18
18
  handlePostToolUse,
19
19
  handleStop,
20
20
  reportEvent
21
- } from "../chunk-WM3U2RUK.js";
22
- import "../chunk-4C4NRDIC.js";
21
+ } from "../chunk-U6RVDUJ5.js";
22
+ import "../chunk-N6IOSZOE.js";
23
+ import "../chunk-EUGNVLMG.js";
23
24
  import {
24
25
  askUser,
25
26
  cancelQuestion,
26
27
  waitForAnswer
27
- } from "../chunk-ZIIYTQHC.js";
28
- import "../chunk-KSNAAMU5.js";
28
+ } from "../chunk-YC27PROF.js";
29
+ import "../chunk-N5MZH5A6.js";
29
30
  import "../chunk-POFBE6ZY.js";
30
- import "../chunk-NQFGVAT6.js";
31
- import "../chunk-JTQDP4ZL.js";
32
- import "../chunk-P5CBNSYL.js";
31
+ import "../chunk-VE7TXLR4.js";
32
+ import "../chunk-EEEJ2JIL.js";
33
+ import "../chunk-DUQO4C4I.js";
34
+ import "../chunk-OVC5QXN5.js";
33
35
  import "../chunk-7QLSKOSU.js";
34
36
  import "../chunk-KZERVKTD.js";
35
- import "../chunk-UPKP67TA.js";
36
- import "../chunk-454KMJHF.js";
37
+ import "../chunk-62CVUUNU.js";
38
+ import "../chunk-MZBRL5H5.js";
37
39
  import "../chunk-T7E75EKA.js";
38
40
  import {
39
41
  getApiKey,
40
42
  getBaseUrl
41
- } from "../chunk-OAL44FM2.js";
42
- import "../chunk-EIZW73P5.js";
43
+ } from "../chunk-R6OYCUNK.js";
44
+ import "../chunk-YYCTF4Q5.js";
43
45
  import {
44
46
  resolvePolicy
45
- } from "../chunk-XFNVG3OV.js";
47
+ } from "../chunk-G4GLCLKK.js";
46
48
  export {
47
49
  askUser,
48
50
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -89,6 +89,7 @@
89
89
  "test": "node ../../scripts/ci/run-tests.mjs src bin",
90
90
  "typecheck": "tsc --noEmit",
91
91
  "conformance": "bun scripts/hook-conformance.ts",
92
+ "live:phone-session-hooks": "bun scripts/phone-session-hooks.ts",
92
93
  "gen:hooks-spec": "bun scripts/generate-hooks-spec.ts"
93
94
  },
94
95
  "dependencies": {
@@ -1,336 +0,0 @@
1
- import {
2
- pusharyHookExecutable
3
- } from "./chunk-XDASEYRS.js";
4
- import {
5
- isProtectedByScope,
6
- registerHookEntry,
7
- resolvePathBinary
8
- } from "./chunk-454KMJHF.js";
9
-
10
- // src/hermes-config.ts
11
- import { execFileSync } from "child_process";
12
- import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
13
- import { homedir, tmpdir } from "os";
14
- import { join } from "path";
15
- var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
16
- var isSnapshot = (value) => isRecord(value) && typeof value.present === "boolean" && Object.hasOwn(value, "value");
17
- var isHermesConfigReceipt = (value) => {
18
- if (!isRecord(value)) return false;
19
- return ["security", "approval", "transport", "fallback", "plugins", "enabled", "agent", "disabled"].every((key) => isSnapshot(value[key])) && typeof value.pushary_enabled === "boolean" && typeof value.clarify_disabled === "boolean";
20
- };
21
- var HERMES_PIP_PACKAGE = "hermes-plugin-pushary";
22
- var HERMES_CONFIG_SCRIPT = String.raw`
23
- import json, os, sys
24
- from hermes_cli.config import load_config, save_config
25
-
26
- mode, receipt_path = sys.argv[1], sys.argv[2]
27
- c = load_config()
28
-
29
- def snap(obj, key):
30
- return {"present": key in obj, "value": obj.get(key)}
31
-
32
- restore_conflicts = []
33
-
34
- def restore(obj, key, before, ours):
35
- if before.get("present") and before.get("value") == ours:
36
- return
37
- if before.get("present"):
38
- if key in obj and obj.get(key) == before.get("value"):
39
- return
40
- elif key not in obj:
41
- return
42
- if obj.get(key) != ours:
43
- restore_conflicts.append(key)
44
- return
45
- if before.get("present"):
46
- obj[key] = before.get("value")
47
- else:
48
- obj.pop(key, None)
49
-
50
- def restore_container(obj, key, before, current, changed):
51
- raw = obj.get(key)
52
- if isinstance(before.get("value"), dict):
53
- if not isinstance(raw, dict):
54
- if changed:
55
- restore_conflicts.append(key)
56
- return
57
- if current or before.get("present"):
58
- obj[key] = current
59
- else:
60
- obj.pop(key, None)
61
- elif isinstance(raw, dict) and current:
62
- restore_conflicts.append(key)
63
- else:
64
- restore(obj, key, before, {})
65
-
66
- def restore_list_member(obj, key, before, ours, ours_before):
67
- raw = obj.get(key)
68
- if isinstance(before.get("value"), list):
69
- if ours_before:
70
- return
71
- if not isinstance(raw, list):
72
- restore_conflicts.append(key)
73
- return
74
- remaining = [item for item in raw if item != ours]
75
- if remaining or before.get("present"):
76
- obj[key] = remaining
77
- else:
78
- obj.pop(key, None)
79
- return
80
- if before.get("present") and raw == before.get("value"):
81
- return
82
- if not before.get("present") and key not in obj:
83
- return
84
- if not isinstance(raw, list):
85
- restore_conflicts.append(key)
86
- return
87
- remaining = [item for item in raw if item != ours]
88
- if remaining:
89
- restore_conflicts.append(key)
90
- else:
91
- restore(obj, key, before, raw)
92
-
93
- def valid_snapshot(value):
94
- return isinstance(value, dict) and isinstance(value.get("present"), bool) and "value" in value
95
-
96
- def valid_receipt(value):
97
- snapshots = ["security", "approval", "transport", "fallback", "plugins", "enabled", "agent", "disabled"]
98
- return (isinstance(value, dict)
99
- and all(valid_snapshot(value.get(key)) for key in snapshots)
100
- and isinstance(value.get("pushary_enabled"), bool)
101
- and isinstance(value.get("clarify_disabled"), bool))
102
-
103
- if mode == "setup":
104
- security_before = snap(c, "security")
105
- s = c.get("security") if isinstance(c.get("security"), dict) else {}
106
- approval_before = snap(s, "approval")
107
- ap = s.get("approval") if isinstance(s.get("approval"), dict) else {}
108
- plugins_before = snap(c, "plugins")
109
- p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}
110
- agent_before = snap(c, "agent")
111
- a = c.get("agent") if isinstance(c.get("agent"), dict) else {}
112
- enabled = p.get("enabled") if isinstance(p.get("enabled"), list) else []
113
- disabled = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []
114
- receipt = {
115
- "security": security_before,
116
- "approval": approval_before,
117
- "transport": snap(ap, "transport"),
118
- "fallback": snap(ap, "transport_fallback"),
119
- "plugins": plugins_before,
120
- "enabled": snap(p, "enabled"),
121
- "pushary_enabled": "pushary" in enabled,
122
- "agent": agent_before,
123
- "disabled": snap(a, "disabled_toolsets"),
124
- "clarify_disabled": "clarify" in disabled,
125
- }
126
- if os.path.exists(receipt_path):
127
- with open(receipt_path, encoding="utf-8") as handle:
128
- existing_receipt = json.load(handle)
129
- if not valid_receipt(existing_receipt):
130
- raise ValueError("invalid Pushary Hermes setup receipt")
131
- else:
132
- os.makedirs(os.path.dirname(receipt_path), exist_ok=True)
133
- temp = receipt_path + ".tmp"
134
- with open(temp, "x", encoding="utf-8") as handle:
135
- json.dump(receipt, handle)
136
- os.chmod(temp, 0o600)
137
- os.replace(temp, receipt_path)
138
- ap["transport"] = "pushary"
139
- ap["transport_fallback"] = "builtin"
140
- s["approval"] = ap
141
- c["security"] = s
142
- p["enabled"] = enabled if "pushary" in enabled else enabled + ["pushary"]
143
- c["plugins"] = p
144
- a["disabled_toolsets"] = disabled if "clarify" in disabled else disabled + ["clarify"]
145
- c["agent"] = a
146
- else:
147
- receipt_exists = os.path.exists(receipt_path)
148
- try:
149
- with open(receipt_path, encoding="utf-8") as handle:
150
- receipt = json.load(handle)
151
- except (OSError, ValueError):
152
- if receipt_exists:
153
- raise
154
- receipt = None
155
- s = c.get("security") if isinstance(c.get("security"), dict) else {}
156
- ap = s.get("approval") if isinstance(s.get("approval"), dict) else {}
157
- if receipt:
158
- if not valid_receipt(receipt):
159
- raise ValueError("invalid Pushary Hermes setup receipt")
160
- approval_changed = (not receipt["transport"].get("present")
161
- or receipt["transport"].get("value") != "pushary"
162
- or not receipt["fallback"].get("present")
163
- or receipt["fallback"].get("value") != "builtin")
164
- plugins_changed = not (isinstance(receipt["enabled"].get("value"), list)
165
- and receipt["pushary_enabled"])
166
- agent_changed = not (isinstance(receipt["disabled"].get("value"), list)
167
- and receipt["clarify_disabled"])
168
- restore(ap, "transport", receipt["transport"], "pushary")
169
- restore(ap, "transport_fallback", receipt["fallback"], "builtin")
170
- else:
171
- if ap.get("transport") == "pushary": ap.pop("transport", None)
172
- if ap.get("transport_fallback") == "builtin": ap.pop("transport_fallback", None)
173
- if receipt:
174
- restore_container(s, "approval", receipt["approval"], ap, approval_changed)
175
- restore_container(c, "security", receipt["security"], s, approval_changed)
176
- else:
177
- if ap: s["approval"] = ap
178
- else: s.pop("approval", None)
179
- if s: c["security"] = s
180
- else: c.pop("security", None)
181
-
182
- p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}
183
- if receipt:
184
- restore_list_member(p, "enabled", receipt["enabled"], "pushary", receipt["pushary_enabled"])
185
- restore_container(c, "plugins", receipt["plugins"], p, plugins_changed)
186
- else:
187
- enabled = p.get("enabled") if isinstance(p.get("enabled"), list) else []
188
- enabled = [item for item in enabled if item != "pushary"]
189
- if enabled: p["enabled"] = enabled
190
- else: p.pop("enabled", None)
191
- if p: c["plugins"] = p
192
- else: c.pop("plugins", None)
193
-
194
- a = c.get("agent") if isinstance(c.get("agent"), dict) else {}
195
- if receipt:
196
- restore_list_member(a, "disabled_toolsets", receipt["disabled"], "clarify", receipt["clarify_disabled"])
197
- restore_container(c, "agent", receipt["agent"], a, agent_changed)
198
- else:
199
- disabled = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []
200
- disabled = [item for item in disabled if item != "clarify"]
201
- if disabled: a["disabled_toolsets"] = disabled
202
- else: a.pop("disabled_toolsets", None)
203
- if a: c["agent"] = a
204
- else: c.pop("agent", None)
205
-
206
- if receipt and restore_conflicts:
207
- raise ValueError("Hermes config changed after Pushary setup")
208
-
209
- save_config(c)
210
- `;
211
- var resolveHermesPython = () => {
212
- const launcher = resolvePathBinary("hermes");
213
- if (launcher && process.platform !== "win32") {
214
- try {
215
- const shebang = readFileSync(launcher, "utf-8").split("\n", 1)[0];
216
- const interpreter = shebang?.startsWith("#!") ? shebang.slice(2).trim().split(/\s+/)[0] : void 0;
217
- if (interpreter && /python/i.test(interpreter) && existsSync(interpreter)) return interpreter;
218
- } catch {
219
- }
220
- }
221
- const root = join(homedir(), ".hermes", "hermes-agent", "venv");
222
- const candidates = process.platform === "win32" ? [join(root, "Scripts", "python.exe"), join(root, "Scripts", "python3.exe")] : [join(root, "bin", "python3"), join(root, "bin", "python")];
223
- return candidates.find(existsSync) ?? null;
224
- };
225
- var runHermesPythonScript = (python, script, ...args) => {
226
- const path = join(tmpdir(), `pushary-hermes-${process.pid}-${Date.now()}.py`);
227
- try {
228
- writeFileSync(path, script, { encoding: "utf-8", mode: 384 });
229
- execFileSync(python, [path, ...args], { env: { ...process.env }, stdio: "pipe", timeout: 15e3 });
230
- return true;
231
- } catch {
232
- return false;
233
- } finally {
234
- try {
235
- rmSync(path, { force: true });
236
- } catch {
237
- }
238
- }
239
- };
240
-
241
- // src/skills-cli.ts
242
- import { execSync } from "child_process";
243
- var PUSHARY_SKILL_SOURCE = "Pushary/pushary-skill";
244
- var PUSHARY_SKILL_NAME = "pushary";
245
- var installSkillViaSkillsCli = (agent) => {
246
- try {
247
- execSync(`npx -y skills@latest add ${PUSHARY_SKILL_SOURCE}@${PUSHARY_SKILL_NAME} -y -g --agent ${agent}`, {
248
- stdio: "pipe",
249
- timeout: 9e4
250
- });
251
- return true;
252
- } catch {
253
- return false;
254
- }
255
- };
256
- var installAgentSkill = async (request, effects) => {
257
- if (request.skillsCliInUse) {
258
- const startedAt = effects.now();
259
- if (effects.runSkillsCli(request.agent)) {
260
- const modifiedAt = effects.skillModifiedAt(request.agentSkillDir);
261
- if (modifiedAt !== null && modifiedAt >= startedAt) return "skills-cli";
262
- }
263
- }
264
- await effects.writePackagedSkill(request.agentSkillDir);
265
- return "packaged-copy";
266
- };
267
-
268
- // src/cursor-config.ts
269
- var hookCommandOf = (entry) => {
270
- const command = entry && typeof entry === "object" && !Array.isArray(entry) ? entry.command : void 0;
271
- return typeof command === "string" ? command : "";
272
- };
273
- var isPusharyCursorEntry = (entry) => pusharyHookExecutable(hookCommandOf(entry)) !== null;
274
- var registerCursorGate = (existing, entry) => registerHookEntry(existing, isPusharyCursorEntry, entry);
275
- var withoutPusharyCursorHooks = (hooks, scope) => {
276
- let removed = 0;
277
- const kept = {};
278
- for (const [event, entries] of Object.entries(hooks)) {
279
- if (!Array.isArray(entries)) {
280
- kept[event] = entries;
281
- continue;
282
- }
283
- const others = entries.filter((entry) => !isPusharyCursorEntry(entry) || isProtectedByScope(hookCommandOf(entry), scope));
284
- removed += entries.length - others.length;
285
- if (others.length > 0 || entries.length === 0) kept[event] = others;
286
- }
287
- return { hooks: kept, removed };
288
- };
289
-
290
- // src/cli/dry-run.ts
291
- import { execFileSync as execFileSync2 } from "child_process";
292
- import { rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
293
- var createGuards = (isDryRun, note) => ({
294
- write: (path, contents) => {
295
- if (isDryRun()) return note("rewrite", path);
296
- writeFileSync2(path, contents, "utf-8");
297
- },
298
- remove: (path, options = {}) => {
299
- if (isDryRun()) return note("remove ", path);
300
- rmSync2(path, { recursive: true, force: options.force ?? false });
301
- },
302
- execFile: (file, args, options, describe) => {
303
- if (isDryRun()) return note("run ", describe);
304
- execFileSync2(file, args, options);
305
- }
306
- });
307
- var describeService = (effects) => {
308
- if (effects.controlMode === "off") return "skip the phone-start background service (--control off)";
309
- const where = effects.serviceDefinitionPath ?? "the phone-start service definition";
310
- const condition = effects.controlMode === "on" ? "" : ", when a supported agent and a service manager are present";
311
- return `write ${where} and register it with the OS${condition}`;
312
- };
313
- var describeSetupSideEffects = (effects) => [
314
- `run npm install -g @pushary/agent-hooks@${effects.version} (the hooks run from the global copy; up to two minutes)`,
315
- `write ${effects.configFile} (the API key, mode 600)`,
316
- effects.rcPath ? `edit ${effects.rcPath} (export PUSHARY_API_KEY)` : "print the export line for your shell, since no rc file was found",
317
- describeService(effects),
318
- "ask Add Pushary instructions to this project's CLAUDE.md/AGENTS.md? (default no)",
319
- "ask Alias claude to pushary claude? (default no)"
320
- ];
321
-
322
- export {
323
- isHermesConfigReceipt,
324
- HERMES_PIP_PACKAGE,
325
- HERMES_CONFIG_SCRIPT,
326
- resolveHermesPython,
327
- runHermesPythonScript,
328
- PUSHARY_SKILL_SOURCE,
329
- PUSHARY_SKILL_NAME,
330
- installSkillViaSkillsCli,
331
- installAgentSkill,
332
- registerCursorGate,
333
- withoutPusharyCursorHooks,
334
- createGuards,
335
- describeSetupSideEffects
336
- };
@@ -1,12 +0,0 @@
1
- // src/answer.ts
2
- var denyReasonFrom = (value, note) => {
3
- const trimmed = note?.trim();
4
- if (trimmed) return `Denied from your phone: ${trimmed}`;
5
- return value && value !== "no" && value !== "yes" ? `Denied from your phone: ${value}` : "Denied via push notification";
6
- };
7
- var isDeferAnswer = (value) => value === "defer";
8
-
9
- export {
10
- denyReasonFrom,
11
- isDeferAnswer
12
- };