@pushary/agent-hooks 0.95.2 → 0.95.5

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 (98) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -1
  3. package/data/cursor-plugin/.cursor-plugin/plugin.json +1 -1
  4. package/data/cursor-plugin/CHANGELOG.md +5 -0
  5. package/data/cursor-plugin/README.md +6 -8
  6. package/data/cursor-plugin/hooks/hooks.json +56 -1
  7. package/data/cursor-plugin/scripts/pushary-gate.mjs +120 -71
  8. package/data/vscode-plugin/.claude-plugin/plugin.json +1 -1
  9. package/data/vscode-plugin/CHANGELOG.md +5 -0
  10. package/data/vscode-plugin/README.md +7 -15
  11. package/data/vscode-plugin/hooks/hooks.json +49 -0
  12. package/data/vscode-plugin/scripts/pushary-gate.mjs +208 -88
  13. package/dist/bin/pushary-bell-hook.js +20 -22
  14. package/dist/bin/pushary-bell.js +9 -11
  15. package/dist/bin/pushary-claude.js +58 -46
  16. package/dist/bin/pushary-clean.js +21 -20
  17. package/dist/bin/pushary-codex-bridge.js +120 -25
  18. package/dist/bin/pushary-codex-hook.js +43 -63
  19. package/dist/bin/pushary-codex.js +11 -10
  20. package/dist/bin/pushary-connect.js +8 -10
  21. package/dist/bin/pushary-cowork.js +5 -5
  22. package/dist/bin/pushary-daemon.js +26 -28
  23. package/dist/bin/pushary-disconnect.js +11 -10
  24. package/dist/bin/pushary-doctor.js +25 -25
  25. package/dist/bin/pushary-elicitation-hook.js +30 -31
  26. package/dist/bin/pushary-gemini-bridge.js +9 -11
  27. package/dist/bin/pushary-gemini-hook.js +45 -79
  28. package/dist/bin/pushary-hook.js +27 -29
  29. package/dist/bin/pushary-login.js +57 -31
  30. package/dist/bin/pushary-logout.js +6 -8
  31. package/dist/bin/pushary-mode.js +5 -5
  32. package/dist/bin/pushary-notification-hook.js +22 -24
  33. package/dist/bin/pushary-opencode-hook.js +38 -74
  34. package/dist/bin/pushary-permission-denied-hook.js +25 -27
  35. package/dist/bin/pushary-permission-hook.js +25 -27
  36. package/dist/bin/pushary-post-hook.js +22 -24
  37. package/dist/bin/pushary-prompt-hook.js +22 -24
  38. package/dist/bin/pushary-session-end-hook.js +22 -24
  39. package/dist/bin/pushary-session-start-hook.js +25 -27
  40. package/dist/bin/pushary-setup.js +78 -55
  41. package/dist/bin/pushary-stats.js +4 -4
  42. package/dist/bin/pushary-status.js +10 -12
  43. package/dist/bin/pushary-stop-hook.js +22 -24
  44. package/dist/bin/pushary-stopfailure-hook.js +22 -24
  45. package/dist/bin/pushary-suggestions.js +2 -2
  46. package/dist/bin/pushary-transcript-register.js +18 -20
  47. package/dist/bin/pushary-transcripts.js +7 -9
  48. package/dist/bin/pushary-upgrade.js +17 -19
  49. package/dist/bin/pushary-wait.js +5 -5
  50. package/dist/bin/pushary.js +1 -1
  51. package/dist/{chunk-7LUFYRH4.js → chunk-2D7BAOMY.js} +7 -7
  52. package/dist/{chunk-3ZUTNTZA.js → chunk-2ZTL3PGN.js} +1 -1
  53. package/dist/{chunk-EPFDN2ZT.js → chunk-32RMQO5Z.js} +1 -1
  54. package/dist/{chunk-EZIEXHYM.js → chunk-3AJIK5AO.js} +5 -2
  55. package/dist/{chunk-IPHOUBTU.js → chunk-4PX4QK2Z.js} +1 -1
  56. package/dist/{chunk-UW4LWAWY.js → chunk-4RF52LSU.js} +8 -8
  57. package/dist/{chunk-UOTSWS5N.js → chunk-67YNQ3QA.js} +43 -98
  58. package/dist/{chunk-G4TL4YWH.js → chunk-6AEM5RCS.js} +1 -1
  59. package/dist/{chunk-G24SEVCI.js → chunk-6VANQN5Q.js} +3 -7
  60. package/dist/{chunk-CKOLDI47.js → chunk-BDI2MXTT.js} +2 -2
  61. package/dist/{chunk-YKLCWWEI.js → chunk-BMANRGJQ.js} +1 -1
  62. package/dist/{chunk-2XUPP4WM.js → chunk-CUEZMPLJ.js} +1 -1
  63. package/dist/{chunk-A4R6BZCK.js → chunk-D62EONG3.js} +1 -1
  64. package/dist/{chunk-ZDAT5KMU.js → chunk-D6PRC3YQ.js} +1 -1
  65. package/dist/{chunk-MOY5X6HJ.js → chunk-DLLAWCQB.js} +1 -1
  66. package/dist/{chunk-DSTLA2TC.js → chunk-GOPIMNVC.js} +1 -1
  67. package/dist/{chunk-CH6DTGQR.js → chunk-HBHEX3JQ.js} +3 -3
  68. package/dist/{chunk-DNVDKHQD.js → chunk-HCMM3BOW.js} +226 -58
  69. package/dist/{chunk-76XICHYT.js → chunk-HGUZOUB2.js} +4 -4
  70. package/dist/{chunk-E3NF5CL2.js → chunk-HQIVR7MM.js} +6 -3
  71. package/dist/{chunk-JDFB3YKG.js → chunk-I6GZX3FT.js} +3 -3
  72. package/dist/{chunk-3OXHYPZG.js → chunk-IM2A5H33.js} +4 -1
  73. package/dist/{chunk-V27AHID3.js → chunk-IVQMNLSA.js} +3 -14
  74. package/dist/{chunk-4LL6LG5X.js → chunk-KAVDAZ7A.js} +43 -1
  75. package/dist/chunk-KBITFQST.js +6 -0
  76. package/dist/{chunk-AMZJ3CKW.js → chunk-KI4PA7MQ.js} +1 -1
  77. package/dist/{chunk-IAEXPTXA.js → chunk-N4VNLISN.js} +2 -2
  78. package/dist/{chunk-VX3BWMC5.js → chunk-OEKFD5BC.js} +1 -1
  79. package/dist/{chunk-QF4RI5CD.js → chunk-OI3DJA7N.js} +1 -1
  80. package/dist/{chunk-WB2C7VPF.js → chunk-OW6AZDY2.js} +3 -3
  81. package/dist/{chunk-4XXUHWIK.js → chunk-QHWXUNMN.js} +1 -1
  82. package/dist/{chunk-M7WWEFG6.js → chunk-QQGNFHIK.js} +2 -2
  83. package/dist/{chunk-YJAQRKH5.js → chunk-RLXVW4AM.js} +1 -1
  84. package/dist/{chunk-GUVCYYDK.js → chunk-S5CMLR35.js} +94 -15
  85. package/dist/{chunk-EUAQ4YAN.js → chunk-UCS65VVR.js} +1 -1
  86. package/dist/{chunk-WM5XMCID.js → chunk-VOSGZ6PH.js} +4 -4
  87. package/dist/{chunk-QUE76J4H.js → chunk-VTO3TNIB.js} +1 -1
  88. package/dist/{chunk-6FAHPGCZ.js → chunk-WDKHZRAS.js} +183 -25
  89. package/dist/{chunk-5GLPYGKS.js → chunk-WRDLHR2F.js} +2 -2
  90. package/dist/{chunk-RVSEGFPD.js → chunk-XLOELLAS.js} +9 -4
  91. package/dist/chunk-XS33QBJA.js +112 -0
  92. package/dist/{chunk-FQUV5KTM.js → chunk-Z2UWFEJW.js} +8 -8
  93. package/dist/src/index.d.ts +8 -1
  94. package/dist/src/index.js +11 -12
  95. package/package.json +1 -1
  96. package/dist/chunk-BC3VCZ3E.js +0 -86
  97. package/dist/chunk-RER3FRTD.js +0 -265
  98. package/dist/chunk-X7YWD6IS.js +0 -44
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readTranscriptPreference
3
- } from "./chunk-GUVCYYDK.js";
3
+ } from "./chunk-S5CMLR35.js";
4
4
 
5
5
  // src/transcript-setting.ts
6
6
  var transcriptEnvironmentPreference = () => {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HOOK_BUDGETS,
3
3
  HOOK_LOCATOR_BINARY
4
- } from "./chunk-6FAHPGCZ.js";
4
+ } from "./chunk-WDKHZRAS.js";
5
5
 
6
6
  // src/removal-scope.ts
7
7
  var namesAppLocator = (command) => command.includes(HOOK_LOCATOR_BINARY);
@@ -30,8 +30,8 @@ var codexAgentsMd = () => join(codexHome(), "AGENTS.md");
30
30
  var codexSkillDir = () => join(codexHome(), "skills", "pushary");
31
31
  var CODEX_HOOK_EVENTS = [
32
32
  { event: "PermissionRequest", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
33
- { event: "PreToolUse", matcher: "Bash|apply_patch", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
34
- { event: "PostToolUse", matcher: "Bash|apply_patch", delivery: "telemetry", timeout: 10 },
33
+ { event: "PreToolUse", matcher: "Bash|apply_patch|mcp__.*", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
34
+ { event: "PostToolUse", matcher: "Bash|apply_patch|mcp__.*", delivery: "telemetry", timeout: 10 },
35
35
  { event: "UserPromptSubmit", delivery: "telemetry", timeout: 10 },
36
36
  { event: "Stop", delivery: "telemetry", timeout: 10 },
37
37
  // `clear` alongside startup and resume, matching what we already do on Claude
@@ -3,7 +3,7 @@ import {
3
3
  decodePublicKey,
4
4
  generateSessionKey,
5
5
  wrapSessionKey
6
- } from "./chunk-IPHOUBTU.js";
6
+ } from "./chunk-4PX4QK2Z.js";
7
7
  import {
8
8
  fetchTranscriptRecipients,
9
9
  uploadTranscriptRecords
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-WB2C7VPF.js";
3
+ } from "./chunk-OW6AZDY2.js";
4
4
  import {
5
5
  readJsonSafe
6
- } from "./chunk-4LL6LG5X.js";
6
+ } from "./chunk-KAVDAZ7A.js";
7
7
 
8
8
  // src/process-ownership.ts
9
9
  import { createHash, randomUUID } from "crypto";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  describeKeyCheck,
3
3
  keyCheckFromResponse
4
- } from "./chunk-JDFB3YKG.js";
4
+ } from "./chunk-I6GZX3FT.js";
5
5
  import {
6
6
  createIo
7
7
  } from "./chunk-5RUIFDTP.js";
@@ -1,23 +1,99 @@
1
- import {
2
- resolveShellRc
3
- } from "./chunk-BC3VCZ3E.js";
4
- import {
5
- resolveInstallSourceFor
6
- } from "./chunk-X7YWD6IS.js";
7
1
  import {
8
2
  backupFile,
9
3
  readJsonSafe,
4
+ resolveInstallSourceFor,
10
5
  writeFileAtomic,
11
6
  writeJsonAtomic
12
- } from "./chunk-4LL6LG5X.js";
7
+ } from "./chunk-KAVDAZ7A.js";
13
8
  import {
14
9
  configFilePath
15
10
  } from "./chunk-2UMNXADU.js";
16
11
 
17
- // src/keystore.ts
18
- import { chmodSync, existsSync, readFileSync, realpathSync, writeFileSync } from "fs";
12
+ // src/shell-alias.ts
19
13
  import { homedir } from "os";
14
+ import { existsSync, readFileSync, appendFileSync, writeFileSync } from "fs";
20
15
  import { join } from "path";
16
+ var MARKER_START = "# >>> pushary >>>";
17
+ var MARKER_END = "# <<< pushary <<<";
18
+ var CANDIDATE_RCS = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"];
19
+ var aliasBlock = () => `${MARKER_START}
20
+ # Make \`claude\` reachable from your phone when idle (runs it through Pushary).
21
+ # Remove this block, or run \`pushary clean\`, to undo.
22
+ alias claude='pushary claude'
23
+ ${MARKER_END}
24
+ `;
25
+ var hasClaudeAlias = (content) => content.includes(MARKER_START) || /alias\s+claude=['"]?pushary claude/.test(content);
26
+ var stripPusharyBlock = (content) => {
27
+ const lines = content.split("\n");
28
+ const out = [];
29
+ let inBlock = false;
30
+ for (const line of lines) {
31
+ const trimmed = line.trim();
32
+ if (trimmed === MARKER_START) {
33
+ inBlock = true;
34
+ continue;
35
+ }
36
+ if (trimmed === MARKER_END) {
37
+ inBlock = false;
38
+ continue;
39
+ }
40
+ if (!inBlock) out.push(line);
41
+ }
42
+ return out.join("\n").replace(/\n{3,}/g, "\n\n");
43
+ };
44
+ var resolveShellRc = () => {
45
+ if (process.platform === "win32") return null;
46
+ const shell = (process.env.SHELL ?? "").split("/").pop() ?? "";
47
+ const home = homedir();
48
+ if (shell === "zsh") return { path: join(home, ".zshrc"), shell };
49
+ if (shell === "bash") {
50
+ const bashrc = join(home, ".bashrc");
51
+ const profile = join(home, ".bash_profile");
52
+ if (process.platform === "darwin") return { path: profile, shell };
53
+ return { path: existsSync(bashrc) ? bashrc : profile, shell };
54
+ }
55
+ return null;
56
+ };
57
+ var installClaudeAlias = () => {
58
+ const rc = resolveShellRc();
59
+ if (!rc) return { installed: false, reason: "unsupported-shell" };
60
+ let content = "";
61
+ try {
62
+ content = readFileSync(rc.path, "utf-8");
63
+ } catch {
64
+ }
65
+ if (hasClaudeAlias(content)) return { installed: false, path: rc.path, reason: "exists" };
66
+ const gap = content.length > 0 && !content.endsWith("\n") ? "\n\n" : content.length > 0 ? "\n" : "";
67
+ appendFileSync(rc.path, `${gap}${aliasBlock()}`, "utf-8");
68
+ return { installed: true, path: rc.path };
69
+ };
70
+ var removeClaudeAlias = () => {
71
+ const removed = [];
72
+ for (const name of CANDIDATE_RCS) {
73
+ const path = join(homedir(), name);
74
+ if (!existsSync(path)) continue;
75
+ let content;
76
+ try {
77
+ content = readFileSync(path, "utf-8");
78
+ } catch {
79
+ continue;
80
+ }
81
+ const stripped = stripPusharyBlock(content);
82
+ if (stripped !== content) {
83
+ try {
84
+ writeFileSync(path, stripped, "utf-8");
85
+ removed.push(path);
86
+ } catch {
87
+ }
88
+ }
89
+ }
90
+ return removed;
91
+ };
92
+
93
+ // src/keystore.ts
94
+ import { chmodSync, existsSync as existsSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
95
+ import { homedir as homedir2 } from "os";
96
+ import { join as join2 } from "path";
21
97
  var KEY_FILE_MODE = 384;
22
98
  var FALLBACK_RCS = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"];
23
99
  var OUR_EXPORT = /^[ \t]*export[ \t]+PUSHARY_API_KEY=(['"]?)(pk_[a-f0-9]+\.[a-f0-9]+)\1[ \t]*$/;
@@ -46,7 +122,7 @@ var rewriteRcExport = (rcPath, apiKey, shell = null) => {
46
122
  const target = realPathOf(rcPath);
47
123
  let content = "";
48
124
  try {
49
- content = readFileSync(target, "utf-8");
125
+ content = readFileSync2(target, "utf-8");
50
126
  } catch (err) {
51
127
  const error = err;
52
128
  if (error.code !== "ENOENT") {
@@ -101,7 +177,7 @@ var rewriteRcExport = (rcPath, apiKey, shell = null) => {
101
177
  var resolveRcTarget = () => {
102
178
  const resolved = resolveShellRc();
103
179
  if (resolved) return { path: resolved.path, shell: resolved.shell };
104
- const fallback = FALLBACK_RCS.map((name) => join(homedir(), name)).find(existsSync);
180
+ const fallback = FALLBACK_RCS.map((name) => join2(homedir2(), name)).find(existsSync2);
105
181
  return fallback ? { path: fallback, shell: null } : null;
106
182
  };
107
183
  var writeKey = (apiKey, options = {}) => {
@@ -151,13 +227,13 @@ var clearKey = (options = {}) => {
151
227
  if (target) {
152
228
  try {
153
229
  const real = realpathSync(target.path);
154
- const lines = readFileSync(real, "utf8").split("\n");
230
+ const lines = readFileSync2(real, "utf8").split("\n");
155
231
  const next = lines.map((line) => {
156
232
  if (!OUR_EXPORT.test(line)) return line;
157
233
  rcLinesDisabled += 1;
158
234
  return `# ${line.trim()} # removed by pushary logout`;
159
235
  });
160
- if (rcLinesDisabled > 0) writeFileSync(real, next.join("\n"));
236
+ if (rcLinesDisabled > 0) writeFileSync2(real, next.join("\n"));
161
237
  } catch {
162
238
  rcLinesDisabled = 0;
163
239
  }
@@ -183,8 +259,8 @@ var readConfigKey = (configPath) => {
183
259
  return key || null;
184
260
  };
185
261
  var readConfigFileKey = () => readConfigKey(configFilePath());
186
- var readControlMode = () => {
187
- const result = readJsonSafe(configFilePath());
262
+ var readControlMode = (configPath = configFilePath()) => {
263
+ const result = readJsonSafe(configPath);
188
264
  if (result.kind !== "ok") return null;
189
265
  const value = result.value.controlMode;
190
266
  return value === "auto" || value === "on" || value === "off" ? value : null;
@@ -205,6 +281,9 @@ var readKeySource = () => {
205
281
  };
206
282
 
207
283
  export {
284
+ resolveShellRc,
285
+ installClaudeAlias,
286
+ removeClaudeAlias,
208
287
  KEY_FILE_MODE,
209
288
  exportLine,
210
289
  keyPrefix,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-6FAHPGCZ.js";
3
+ } from "./chunk-WDKHZRAS.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  AGENT_CHOICES
3
- } from "./chunk-3ZUTNTZA.js";
3
+ } from "./chunk-2ZTL3PGN.js";
4
4
  import {
5
5
  isBridgeLocator
6
- } from "./chunk-2XUPP4WM.js";
6
+ } from "./chunk-CUEZMPLJ.js";
7
7
  import {
8
8
  pusharyHookExecutable
9
- } from "./chunk-CH6DTGQR.js";
9
+ } from "./chunk-HBHEX3JQ.js";
10
10
  import {
11
11
  BELL_HOOK_BINARY
12
- } from "./chunk-DSTLA2TC.js";
12
+ } from "./chunk-GOPIMNVC.js";
13
13
 
14
14
  // src/setup/hook-ownership.ts
15
15
  var commandsIn = (value) => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-GUVCYYDK.js";
3
+ } from "./chunk-S5CMLR35.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -21,6 +21,166 @@ var COWORK_STANDING_INSTRUCTIONS = `Pushary is connected as a custom connector.
21
21
  - Cancel a question when it is no longer needed. Once this turn ends, reopen Cowork for follow-up work; this connector cannot restart it.
22
22
  - If Pushary tools are missing or disabled, report that once and continue within my authorization. Do not claim a notification or approval was delivered.`;
23
23
 
24
+ // ../contracts/src/agent-manifest.ts
25
+ var defaults = {
26
+ hasStoredIdentity: true,
27
+ hookSource: null,
28
+ policyAgent: null,
29
+ repoAware: false,
30
+ disconnectable: false,
31
+ managedProvider: false,
32
+ enforcement: "cooperative",
33
+ connectVia: "Plain MCP",
34
+ setupCommand: null,
35
+ setupChoice: null,
36
+ setupGuide: "",
37
+ enforcementDetail: "Cooperative. The agent chooses when to ask; no native permission interception.",
38
+ capabilityNote: "",
39
+ documented: true
40
+ };
41
+ var AGENTS = {
42
+ claude_code: {
43
+ ...defaults,
44
+ label: "Claude Code",
45
+ hookSource: "claude",
46
+ policyAgent: "claude_code",
47
+ repoAware: true,
48
+ disconnectable: true,
49
+ managedProvider: true,
50
+ enforcement: "enforced",
51
+ connectVia: "Native hooks + MCP",
52
+ setupCommand: "npx @pushary/agent-hooks setup",
53
+ enforcementDetail: "Shell, writes and edits, subject to Claude Code permission mode and explicit policy."
54
+ },
55
+ claude_chat: {
56
+ ...defaults,
57
+ label: "Claude Chat",
58
+ connectVia: "Custom MCP connector",
59
+ setupGuide: "[Claude.ai and Claude Desktop setup](/docs/agents/guides/claude-desktop)"
60
+ },
61
+ claude_cowork: {
62
+ ...defaults,
63
+ label: "Claude Cowork",
64
+ connectVia: "Custom MCP connector + optional Cowork skill",
65
+ setupGuide: "[Cowork setup](/docs/agents/guides/claude-desktop#use-it-in-cowork)",
66
+ enforcementDetail: COWORK_CONNECTOR_LIMIT
67
+ },
68
+ codex: {
69
+ ...defaults,
70
+ label: "Codex",
71
+ hookSource: "codex",
72
+ policyAgent: "codex",
73
+ repoAware: true,
74
+ disconnectable: true,
75
+ managedProvider: true,
76
+ enforcement: "enforced",
77
+ connectVia: "Native hooks + MCP",
78
+ setupCommand: "npx @pushary/agent-hooks setup",
79
+ enforcementDetail: "Shell and apply_patch edits through native permission hooks; requires a compatible Codex runtime."
80
+ },
81
+ chatgpt: {
82
+ ...defaults,
83
+ label: "ChatGPT",
84
+ connectVia: "Custom MCP connector",
85
+ setupGuide: "[ChatGPT setup](/docs/agents/guides/chatgpt)"
86
+ },
87
+ gemini_cli: {
88
+ ...defaults,
89
+ label: "Gemini CLI",
90
+ hookSource: "gemini",
91
+ policyAgent: "gemini_cli",
92
+ repoAware: true,
93
+ disconnectable: true,
94
+ managedProvider: true,
95
+ enforcement: "enforced",
96
+ connectVia: "Native hooks + MCP",
97
+ setupCommand: "npx @pushary/agent-hooks setup",
98
+ enforcementDetail: "Shell, writes and edits through native BeforeTool hooks."
99
+ },
100
+ hermes: {
101
+ ...defaults,
102
+ label: "Hermes",
103
+ policyAgent: "hermes",
104
+ enforcement: "enforced",
105
+ connectVia: "Native Python plugin + approval transport",
106
+ setupCommand: "npx @pushary/agent-hooks setup",
107
+ enforcementDetail: "Hermes dangerous-command approvals when the Pushary transport is selected; optional PUSHARY_GATE_TOOLS gates named tools.",
108
+ capabilityNote: "Uses the Python plugin, not a macOS bridge HookSource. CLI disconnect and managed-provider controls are not implemented for Hermes."
109
+ },
110
+ cursor: {
111
+ ...defaults,
112
+ label: "Cursor",
113
+ hookSource: "cursor",
114
+ policyAgent: "cursor",
115
+ disconnectable: true,
116
+ enforcement: "enforced",
117
+ connectVia: "Editor plugin hooks + MCP",
118
+ setupCommand: "npx @pushary/agent-hooks setup",
119
+ enforcementDetail: "With installed hooks: shell, MCP execution and Write/Delete/Edit tool calls. Plain MCP remains cooperative."
120
+ },
121
+ vscode: {
122
+ ...defaults,
123
+ label: "VS Code",
124
+ hookSource: "vscode",
125
+ policyAgent: "vscode",
126
+ enforcement: "enforced",
127
+ connectVia: "Agent plugin hooks + MCP",
128
+ setupCommand: "npx @pushary/agent-hooks setup",
129
+ enforcementDetail: "PreToolUse approval hooks from the installed agent plugin; the host decides which events it exposes.",
130
+ capabilityNote: "Has both a bridge HookSource and a per-agent policy profile. CLI disconnect and managed-provider controls are not implemented for VS Code."
131
+ },
132
+ opencode: {
133
+ ...defaults,
134
+ label: "OpenCode",
135
+ hookSource: "opencode",
136
+ policyAgent: "opencode",
137
+ repoAware: true,
138
+ disconnectable: true,
139
+ enforcement: "enforced",
140
+ connectVia: "Generated plugin + MCP",
141
+ setupCommand: "npx @pushary/agent-hooks setup",
142
+ enforcementDetail: "Shell, writes, edits and web fetches when OpenCode itself asks for permission."
143
+ },
144
+ windsurf: {
145
+ ...defaults,
146
+ label: "Windsurf",
147
+ setupCommand: "npx @pushary/agent-hooks setup",
148
+ setupChoice: "Other"
149
+ },
150
+ unknown: {
151
+ ...defaults,
152
+ label: "Agent",
153
+ documented: false
154
+ },
155
+ lovable: {
156
+ ...defaults,
157
+ label: "Lovable",
158
+ setupGuide: "[Lovable setup](/docs/agents/guides/lovable)"
159
+ },
160
+ mcp: {
161
+ ...defaults,
162
+ hasStoredIdentity: false,
163
+ label: "Any MCP client",
164
+ setupCommand: "npx @pushary/agent-hooks setup",
165
+ setupChoice: "Other"
166
+ },
167
+ http: {
168
+ ...defaults,
169
+ hasStoredIdentity: false,
170
+ label: "No-code and HTTP",
171
+ connectVia: "REST with a Bearer token",
172
+ setupGuide: "[Connect any agent](/docs/agents/connect-any-agent)"
173
+ }
174
+ };
175
+ var HOOK_SOURCES = Object.freeze(Object.values(AGENTS).flatMap((agent) => agent.hookSource === null ? [] : [agent.hookSource]));
176
+ var POLICY_AGENTS = [...Object.values(AGENTS).flatMap((agent) => agent.policyAgent === null ? [] : [agent.policyAgent]), "*"];
177
+ var AGENT_LABELS_BY_TYPE = Object.fromEntries(
178
+ Object.entries(AGENTS).flatMap(([id, agent]) => agent.hasStoredIdentity ? [[id, agent.label]] : [])
179
+ );
180
+ var DISCONNECTABLE_AGENTS = Object.freeze(Object.values(AGENTS).flatMap((agent) => agent.disconnectable ? [agent.hookSource] : []));
181
+ var REPO_AWARE_AGENTS = Object.freeze(Object.values(AGENTS).flatMap((agent) => agent.repoAware ? [agent.policyAgent] : []));
182
+ var MANAGED_PROVIDERS = Object.freeze(Object.values(AGENTS).flatMap((agent) => agent.managedProvider ? [agent.hookSource] : []));
183
+
24
184
  // ../contracts/src/index.ts
25
185
  var KEY_SCOPES = ["full_access", "agent", "connector", "bound"];
26
186
  var normalizeKeyScope = (value) => typeof value === "string" && KEY_SCOPES.includes(value) ? value : null;
@@ -140,23 +300,8 @@ var hookWaitDeadline = (startMs, policyWaitSeconds, agent, nowMs) => {
140
300
  };
141
301
  var hookWaitClamped = (startMs, policyWaitSeconds, agent, nowMs) => startMs + hookMaxWaitSeconds(agent) * 1e3 < nowMs + Math.max(policyWaitSeconds, 0) * 1e3;
142
302
  var effectiveWaitSeconds = (timeoutAction, policySeconds, agent) => timeoutAction === "wait" ? hookMaxWaitSeconds(agent) : policySeconds;
143
- var HOOK_SOURCES = ["claude", "codex", "gemini", "cursor", "vscode", "opencode"];
144
- var AGENT_LABELS_BY_TYPE = {
145
- claude_code: "Claude Code",
146
- claude_chat: "Claude Chat",
147
- claude_cowork: "Claude Cowork",
148
- codex: "Codex",
149
- chatgpt: "ChatGPT",
150
- gemini_cli: "Gemini CLI",
151
- hermes: "Hermes",
152
- cursor: "Cursor",
153
- vscode: "VS Code",
154
- opencode: "OpenCode",
155
- windsurf: "Windsurf",
156
- unknown: "Agent"
157
- };
158
303
  var OPENCODE_APPROVAL_EVENT = "permission.ask";
159
- var CURSOR_GATE_EVENTS = ["beforeShellExecution", "beforeMCPExecution"];
304
+ var CURSOR_GATE_EVENTS = ["beforeShellExecution", "beforeMCPExecution", "preToolUse"];
160
305
  var HOOK_LOCATOR_BINARY = "pushary-bridge";
161
306
  var HOOK_BATCH_MAX_BYTES = 4 * 1024 * 1024;
162
307
  var REPO_KEY_MAX_LENGTH = 200;
@@ -1111,6 +1256,17 @@ var shouldDeferToNativeMode = (permissionMode, toolName, options = {}) => {
1111
1256
  var ASKED_IN_EVERY_MODE_TOOLS = ["AskUserQuestion", "ExitPlanMode"];
1112
1257
  var ASKED_IN_EVERY_MODE = new Set(ASKED_IN_EVERY_MODE_TOOLS);
1113
1258
  var isAskedInEveryMode = (toolName) => ASKED_IN_EVERY_MODE.has(toolName);
1259
+ var PRETOOLUSE_GATED_TOOLS = [
1260
+ "Bash",
1261
+ "Write",
1262
+ "Edit",
1263
+ "MultiEdit",
1264
+ "NotebookEdit",
1265
+ "PowerShell",
1266
+ "Monitor"
1267
+ ];
1268
+ var PRETOOLUSE_HANDLED_TOOLS = /* @__PURE__ */ new Set([...PRETOOLUSE_GATED_TOOLS, ...ASKED_IN_EVERY_MODE_TOOLS]);
1269
+ var isPermissionRequestOwnedByPreToolUse = (toolName, permissionMode, defers = shouldDeferToNativeMode) => PRETOOLUSE_HANDLED_TOOLS.has(toolName) && (isAskedInEveryMode(toolName) || !defers(permissionMode, toolName));
1114
1270
  var resolveGate = (input) => {
1115
1271
  const { modeState, config, toolName, toolInputs, cwd, repoKey, sessionId } = input;
1116
1272
  if (isPusharyOwnTool(toolName)) {
@@ -1189,14 +1345,10 @@ var pendingCommandEvents = (source) => {
1189
1345
  return ["PostToolUse", "PreToolUse", "UserPromptSubmit"];
1190
1346
  case "gemini":
1191
1347
  return ["AfterTool", "BeforeTool", "BeforeAgent"];
1192
- // Registered events only. Cursor's carriers are `postToolUse` and `stop`,
1193
- // which this build does not write into the config; the ones it does write
1194
- // (`afterShellExecution`, `afterFileEdit`, `afterAgentResponse`) are
1195
- // observational and discard whatever they print.
1196
1348
  case "cursor":
1197
- return [];
1349
+ return ["postToolUse"];
1198
1350
  case "vscode":
1199
- return [];
1351
+ return ["PostToolUse", "UserPromptSubmit"];
1200
1352
  // The prompt is appended through the TUI rather than through a hook return,
1201
1353
  // so there is nothing for this to print.
1202
1354
  case "opencode":
@@ -1239,7 +1391,7 @@ var gateOutputFor = (source, event, decision, reason) => {
1239
1391
  } else if (source === "opencode" && event === OPENCODE_APPROVAL_EVENT) {
1240
1392
  output = decision === "deny" ? { status: "deny", reason: reason ?? "Denied from Pushary" } : { status: decision };
1241
1393
  } else if (source === "cursor" && CURSOR_GATE_EVENTS.includes(event)) {
1242
- output = decision === "allow" ? { permission: "allow" } : decision === "ask" ? { permission: "ask" } : {
1394
+ output = decision === "allow" ? { permission: "allow" } : decision === "ask" && event !== "preToolUse" ? { permission: "ask" } : {
1243
1395
  permission: "deny",
1244
1396
  user_message: "Command denied via Pushary.",
1245
1397
  agent_message: reason ?? "Denied from Pushary"
@@ -1379,7 +1531,13 @@ export {
1379
1531
  CLAUDE_CONNECTOR_STEPS,
1380
1532
  CLAUDE_CONNECTOR_TEST,
1381
1533
  COWORK_STANDING_INSTRUCTIONS,
1534
+ HOOK_SOURCES,
1535
+ AGENT_LABELS_BY_TYPE,
1536
+ DISCONNECTABLE_AGENTS,
1537
+ REPO_AWARE_AGENTS,
1538
+ MANAGED_PROVIDERS,
1382
1539
  normalizeKeyScope,
1540
+ parseAgentQuestion,
1383
1541
  parseAgentQuestions,
1384
1542
  parseAgentQuestionAnswers,
1385
1543
  normalizeInstallSource,
@@ -1387,8 +1545,6 @@ export {
1387
1545
  hookWaitDeadline,
1388
1546
  hookWaitClamped,
1389
1547
  effectiveWaitSeconds,
1390
- HOOK_SOURCES,
1391
- AGENT_LABELS_BY_TYPE,
1392
1548
  OPENCODE_APPROVAL_EVENT,
1393
1549
  CURSOR_GATE_EVENTS,
1394
1550
  HOOK_LOCATOR_BINARY,
@@ -1409,6 +1565,8 @@ export {
1409
1565
  isPusharyOwnTool,
1410
1566
  shouldDeferToNativeMode,
1411
1567
  ASKED_IN_EVERY_MODE_TOOLS,
1568
+ PRETOOLUSE_GATED_TOOLS,
1569
+ isPermissionRequestOwnedByPreToolUse,
1412
1570
  resolveGate,
1413
1571
  modeStateFromResponse,
1414
1572
  stoppedOutput,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SETUP_FLAGS
3
- } from "./chunk-3ZUTNTZA.js";
3
+ } from "./chunk-2ZTL3PGN.js";
4
4
  import {
5
5
  parseFlags
6
6
  } from "./chunk-GMXKITVA.js";
@@ -14,7 +14,7 @@ import {
14
14
  CLAUDE_CONNECTOR_URL,
15
15
  COWORK_CONNECTOR_LIMIT,
16
16
  COWORK_STANDING_INSTRUCTIONS
17
- } from "./chunk-6FAHPGCZ.js";
17
+ } from "./chunk-WDKHZRAS.js";
18
18
 
19
19
  // src/cli/cowork.ts
20
20
  import { readFileSync } from "fs";
@@ -7,19 +7,22 @@ import {
7
7
  } from "./chunk-L4ZCFMWW.js";
8
8
  import {
9
9
  getMachineId
10
- } from "./chunk-IAEXPTXA.js";
10
+ } from "./chunk-N4VNLISN.js";
11
11
  import {
12
12
  pusharyDir
13
- } from "./chunk-WB2C7VPF.js";
13
+ } from "./chunk-OW6AZDY2.js";
14
14
  import {
15
15
  getPackageVersion
16
16
  } from "./chunk-DINDL2CF.js";
17
+ import {
18
+ readControlMode
19
+ } from "./chunk-S5CMLR35.js";
17
20
  import {
18
21
  readJsonSafe,
19
22
  writeFileAtomic,
20
23
  writeJsonAtomic,
21
24
  writeJsonAtomicDurable
22
- } from "./chunk-4LL6LG5X.js";
25
+ } from "./chunk-KAVDAZ7A.js";
23
26
  import {
24
27
  getApiKey
25
28
  } from "./chunk-2UMNXADU.js";
@@ -737,7 +740,9 @@ var clearDaemonNeedsLogin = (options = {}) => {
737
740
  var describeDaemonLoginBlock = (record) => record.reason === "key_rejected" ? `this machine's key was rejected at ${record.at}; run \`pushary login\` to sign in again` : `this workspace had no active plan at ${record.at}; pick one at pushary.com/dashboard/settings?tab=billing, then run \`pushary login\``;
738
741
  var recoverDaemonAfterLogin = (options = {}) => {
739
742
  const cleared = clearDaemonNeedsLogin(options);
740
- if (!cleared || !hasDaemonServiceDefinition(options)) return { cleared, restarted: false, detail: null };
743
+ if (!cleared || readControlMode(join(options.dir ?? pusharyDir(), "config.json")) === "off" || !hasDaemonServiceDefinition(options)) {
744
+ return { cleared, restarted: false, detail: null };
745
+ }
741
746
  const started = manageDaemonService("start", options);
742
747
  return started.kind === "ok" ? { cleared, restarted: true, detail: null } : { cleared, restarted: false, detail: started.detail };
743
748
  };