@pushary/agent-hooks 0.84.1 → 0.85.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +24 -0
  3. package/dist/bin/pushary-bell-hook.js +5 -3
  4. package/dist/bin/pushary-bell.js +8 -7
  5. package/dist/bin/pushary-claude.js +5 -5
  6. package/dist/bin/pushary-clean.js +6 -5
  7. package/dist/bin/pushary-codex-bridge.js +2 -2
  8. package/dist/bin/pushary-codex-hook.js +7 -13
  9. package/dist/bin/pushary-codex.js +1 -1
  10. package/dist/bin/pushary-connect.js +5 -5
  11. package/dist/bin/pushary-daemon.js +5 -5
  12. package/dist/bin/pushary-disconnect.d.ts +1 -0
  13. package/dist/bin/pushary-disconnect.js +125 -0
  14. package/dist/bin/pushary-doctor.js +9 -9
  15. package/dist/bin/pushary-gemini-bridge.js +2 -2
  16. package/dist/bin/pushary-gemini-hook.js +7 -13
  17. package/dist/bin/pushary-hook.js +13 -20
  18. package/dist/bin/pushary-login.js +2 -2
  19. package/dist/bin/pushary-logout.js +3 -3
  20. package/dist/bin/pushary-mode.js +2 -2
  21. package/dist/bin/pushary-notification-hook.js +6 -9
  22. package/dist/bin/pushary-permission-denied-hook.js +11 -9
  23. package/dist/bin/pushary-permission-hook.js +11 -9
  24. package/dist/bin/pushary-post-hook.js +6 -9
  25. package/dist/bin/pushary-prompt-hook.js +6 -9
  26. package/dist/bin/pushary-session-end-hook.js +6 -6
  27. package/dist/bin/pushary-session-start-hook.js +6 -6
  28. package/dist/bin/pushary-setup.js +23 -24
  29. package/dist/bin/pushary-stats.js +2 -2
  30. package/dist/bin/pushary-status.js +2 -2
  31. package/dist/bin/pushary-stop-hook.js +6 -6
  32. package/dist/bin/pushary-stopfailure-hook.js +6 -6
  33. package/dist/bin/pushary-suggestions.js +2 -2
  34. package/dist/bin/pushary-upgrade.js +13 -8
  35. package/dist/bin/pushary-wait.js +2 -2
  36. package/dist/bin/pushary.js +2 -1
  37. package/dist/{chunk-HZNOAJGB.js → chunk-4QPOMJUB.js} +1 -1
  38. package/dist/chunk-64PXDATC.js +187 -0
  39. package/dist/chunk-6CUUA7HO.js +59 -0
  40. package/dist/{chunk-UXWEE3QI.js → chunk-ADISVXZL.js} +1 -1
  41. package/dist/chunk-AXRATIS6.js +70 -0
  42. package/dist/{chunk-CCCJ6BPW.js → chunk-EXMUM226.js} +19 -7
  43. package/dist/{chunk-YR5TSPXB.js → chunk-GFM3STWO.js} +3 -3
  44. package/dist/{chunk-MGJBC6DD.js → chunk-GOEXZ5QJ.js} +1 -0
  45. package/dist/{chunk-BLDDXHHA.js → chunk-GXIFADGD.js} +1 -1
  46. package/dist/{chunk-SEAHGHCE.js → chunk-HVZIHTOO.js} +33 -0
  47. package/dist/chunk-PWTKKQQI.js +27 -0
  48. package/dist/{chunk-J4KFVOCQ.js → chunk-SQOFGPNX.js} +1 -1
  49. package/dist/{chunk-CCH775Y6.js → chunk-WEAQQSHQ.js} +1 -1
  50. package/dist/{chunk-ILB6UOAF.js → chunk-YKW6JCWI.js} +1 -1
  51. package/dist/src/index.d.ts +1 -1
  52. package/dist/src/index.js +6 -4
  53. package/package.json +3 -2
  54. package/dist/chunk-UKSQWTBH.js +0 -208
@@ -0,0 +1,187 @@
1
+ import {
2
+ guardBinary
3
+ } from "./chunk-PWTKKQQI.js";
4
+ import {
5
+ HOOK_BUDGETS
6
+ } from "./chunk-MQMPG5X4.js";
7
+
8
+ // src/claude-config.ts
9
+ import { join } from "path";
10
+
11
+ // src/agent-version.ts
12
+ var parseVersion = (raw) => {
13
+ const match = raw.match(/(\d+)\.(\d+)\.(\d+)/);
14
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
15
+ };
16
+ var compareVersion = (a, b) => {
17
+ for (let index = 0; index < 3; index++) {
18
+ if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1;
19
+ }
20
+ return 0;
21
+ };
22
+ var atLeast = (version, floor) => compareVersion(version, floor) >= 0;
23
+
24
+ // src/claude-tools.ts
25
+ var PRETOOLUSE_GATED_TOOLS = [
26
+ "Bash",
27
+ "Write",
28
+ "Edit",
29
+ "MultiEdit",
30
+ "NotebookEdit",
31
+ "PowerShell",
32
+ "Monitor"
33
+ ];
34
+ var POSTTOOLUSE_MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "TodoWrite"];
35
+ var PRETOOLUSE_HANDLED_TOOLS = /* @__PURE__ */ new Set([...PRETOOLUSE_GATED_TOOLS, "AskUserQuestion"]);
36
+
37
+ // src/claude-events.ts
38
+ var GATED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "AskUserQuestion"].join("|");
39
+ var MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "TodoWrite"].join("|");
40
+ var ESTABLISHED = [1, 0, 0];
41
+ var OBSERVED = [2, 0, 0];
42
+ var CLAUDE_HOOK_EVENTS = [
43
+ { event: "PreToolUse", binary: "pushary-hook", matcher: GATED_TOOLS, timeout: HOOK_BUDGETS.claude.budgetSeconds, since: ESTABLISHED },
44
+ { event: "PostToolUse", binary: "pushary-post-hook", matcher: MIRRORED_TOOLS, timeout: 10, since: ESTABLISHED },
45
+ { event: "Stop", binary: "pushary-stop-hook", timeout: 10, since: ESTABLISHED },
46
+ { event: "UserPromptSubmit", binary: "pushary-prompt-hook", timeout: 10, since: ESTABLISHED },
47
+ { event: "Notification", binary: "pushary-notification-hook", matcher: "permission_prompt|idle_prompt|agent_needs_input|agent_completed", timeout: 10, since: ESTABLISHED },
48
+ { event: "SessionStart", binary: "pushary-session-start-hook", matcher: "startup|resume|clear", timeout: 10, since: ESTABLISHED },
49
+ { event: "SessionEnd", binary: "pushary-session-end-hook", timeout: 10, since: ESTABLISHED },
50
+ { event: "StopFailure", binary: "pushary-stopfailure-hook", timeout: 10, since: ESTABLISHED },
51
+ { event: "PermissionRequest", binary: "pushary-permission-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, since: ESTABLISHED },
52
+ { event: "PermissionDenied", binary: "pushary-permission-denied-hook", timeout: 60, since: ESTABLISHED },
53
+ // Observability only. Each carries session shape the island cannot get any other
54
+ // way, and none of them decides anything.
55
+ { event: "SubagentStart", binary: "pushary-session-start-hook", timeout: 10, since: OBSERVED },
56
+ { event: "SubagentStop", binary: "pushary-stop-hook", timeout: 10, since: OBSERVED },
57
+ { event: "PreCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, since: OBSERVED },
58
+ { event: "PostCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, since: OBSERVED },
59
+ { event: "TeammateIdle", binary: "pushary-notification-hook", timeout: 10, since: OBSERVED }
60
+ ];
61
+ var supportedClaudeEvents = (version) => CLAUDE_HOOK_EVENTS.filter((hook) => version ? atLeast(version, hook.since) : hook.since === ESTABLISHED);
62
+
63
+ // src/claude-config.ts
64
+ var PUSHARY_MCP_URL = "https://pushary.com/api/mcp/mcp";
65
+ var PUSHARY_PERMISSION_RULE = "mcp__pushary__*";
66
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
67
+ var ensureRecord = (target, key) => {
68
+ const existing = asRecord(target[key]);
69
+ if (existing) return existing;
70
+ const created = {};
71
+ target[key] = created;
72
+ return created;
73
+ };
74
+ var isPusharyPermission = (rule) => typeof rule === "string" && (rule.includes("pushary") || rule.includes("MCP(pushary"));
75
+ var HOOK_BINARIES = new Set(CLAUDE_HOOK_EVENTS.map((hook) => hook.binary));
76
+ var namesPusharyBinary = (command) => command.split(/[\s/\\"']+/).some((segment) => HOOK_BINARIES.has(segment.replace(/\.(cmd|exe)$/i, "")));
77
+ var isPusharyHook = (entry2) => {
78
+ const hooks = asRecord(entry2)?.hooks;
79
+ if (!Array.isArray(hooks)) return false;
80
+ return hooks.some((hook) => namesPusharyBinary(String(asRecord(hook)?.command ?? "")));
81
+ };
82
+ var addClaudeMcpServer = (config, apiKey) => {
83
+ const mcpServers = ensureRecord(config, "mcpServers");
84
+ mcpServers.pushary = {
85
+ type: "http",
86
+ url: PUSHARY_MCP_URL,
87
+ headers: { Authorization: `Bearer ${apiKey}` }
88
+ };
89
+ };
90
+ var removeClaudeMcpServers = (config) => {
91
+ let changed = false;
92
+ const removeFrom = (target) => {
93
+ const mcpServers = asRecord(target.mcpServers);
94
+ if (!mcpServers?.pushary) return;
95
+ delete mcpServers.pushary;
96
+ if (Object.keys(mcpServers).length === 0) delete target.mcpServers;
97
+ changed = true;
98
+ };
99
+ removeFrom(config);
100
+ const projects = asRecord(config.projects);
101
+ if (projects) {
102
+ for (const project of Object.values(projects)) {
103
+ const projectConfig = asRecord(project);
104
+ if (projectConfig) removeFrom(projectConfig);
105
+ }
106
+ }
107
+ return changed;
108
+ };
109
+ var addPusharyToolPermissions = (settings) => {
110
+ const permissions = ensureRecord(settings, "permissions");
111
+ const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
112
+ const filtered = allow.filter((rule) => !isPusharyPermission(rule));
113
+ if (!filtered.includes(PUSHARY_PERMISSION_RULE)) {
114
+ filtered.push(PUSHARY_PERMISSION_RULE);
115
+ }
116
+ permissions.allow = filtered;
117
+ };
118
+ var addPusharyHooks = (settings, binDir, version = null) => {
119
+ const resolve = (name) => guardBinary(binDir ? join(binDir, name) : name);
120
+ const hooks = ensureRecord(settings, "hooks");
121
+ const supported = new Map(supportedClaudeEvents(version).map((hook) => [hook.event, hook]));
122
+ for (const known of CLAUDE_HOOK_EVENTS) {
123
+ const current = hooks[known.event];
124
+ const others = (Array.isArray(current) ? current : []).filter((item) => !isPusharyHook(item));
125
+ const definition = supported.get(known.event);
126
+ if (definition) {
127
+ hooks[known.event] = [...others, entry(definition, resolve)];
128
+ } else if (others.length > 0) {
129
+ hooks[known.event] = others;
130
+ } else {
131
+ delete hooks[known.event];
132
+ }
133
+ }
134
+ };
135
+ var entry = (definition, resolve) => ({
136
+ ...definition.matcher ? { matcher: definition.matcher } : {},
137
+ hooks: [{
138
+ type: "command",
139
+ command: resolve(definition.binary),
140
+ timeout: definition.timeout
141
+ }]
142
+ });
143
+ var removePusharySettings = (settings) => {
144
+ let changed = removeClaudeMcpServers(settings);
145
+ const permissions = asRecord(settings.permissions);
146
+ if (permissions && Array.isArray(permissions.allow)) {
147
+ const filtered = permissions.allow.filter((rule) => !isPusharyPermission(rule));
148
+ if (filtered.length !== permissions.allow.length) {
149
+ if (filtered.length === 0) {
150
+ delete permissions.allow;
151
+ } else {
152
+ permissions.allow = filtered;
153
+ }
154
+ if (Object.keys(permissions).length === 0) delete settings.permissions;
155
+ changed = true;
156
+ }
157
+ }
158
+ const hooks = asRecord(settings.hooks);
159
+ if (hooks) {
160
+ for (const { event: key } of CLAUDE_HOOK_EVENTS) {
161
+ const entries = hooks[key];
162
+ if (!Array.isArray(entries)) continue;
163
+ const filtered = entries.filter((entry2) => !isPusharyHook(entry2));
164
+ if (filtered.length !== entries.length) {
165
+ if (filtered.length === 0) {
166
+ delete hooks[key];
167
+ } else {
168
+ hooks[key] = filtered;
169
+ }
170
+ changed = true;
171
+ }
172
+ }
173
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
174
+ }
175
+ return changed;
176
+ };
177
+
178
+ export {
179
+ parseVersion,
180
+ compareVersion,
181
+ PRETOOLUSE_HANDLED_TOOLS,
182
+ addClaudeMcpServer,
183
+ removeClaudeMcpServers,
184
+ addPusharyToolPermissions,
185
+ addPusharyHooks,
186
+ removePusharySettings
187
+ };
@@ -0,0 +1,59 @@
1
+ // src/admission.ts
2
+ import { readFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+ var NO_RULES = { version: 1, disabled: [] };
6
+ var SUPPORTED_VERSION = 1;
7
+ var parseAdmissionRules = (raw) => {
8
+ if (!raw) return NO_RULES;
9
+ try {
10
+ const parsed = JSON.parse(raw);
11
+ if (parsed.version !== SUPPORTED_VERSION) return NO_RULES;
12
+ if (!Array.isArray(parsed.disabled)) return NO_RULES;
13
+ return {
14
+ version: SUPPORTED_VERSION,
15
+ disabled: parsed.disabled.filter((rule) => typeof rule === "string")
16
+ };
17
+ } catch {
18
+ return NO_RULES;
19
+ }
20
+ };
21
+ var admissionRulesFile = () => process.env.PUSHARY_ADMISSION_FILE?.trim() || join(homedir(), ".pushary", "admission-rules.json");
22
+ var readAdmissionRules = () => {
23
+ try {
24
+ return parseAdmissionRules(readFileSync(admissionRulesFile(), "utf-8"));
25
+ } catch {
26
+ return NO_RULES;
27
+ }
28
+ };
29
+ var matches = (rule, source, event) => {
30
+ const colon = rule.indexOf(":");
31
+ if (colon <= 0 || colon === rule.length - 1) return false;
32
+ const ruleSource = rule.slice(0, colon);
33
+ const ruleEvent = rule.slice(colon + 1);
34
+ return (ruleSource === "*" || ruleSource === source) && (ruleEvent === "*" || ruleEvent === event);
35
+ };
36
+ var isAdmitted = (rules, source, event) => !rules.disabled.some((rule) => matches(rule, source, event));
37
+ var readHookInput = async (source, fallbackEvent = "", stdin = process.stdin) => {
38
+ let raw = "";
39
+ for await (const chunk of stdin) raw += chunk;
40
+ if (!raw.trim()) return null;
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(raw);
44
+ } catch {
45
+ return null;
46
+ }
47
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
48
+ const event = eventNameOf(parsed) ?? fallbackEvent;
49
+ return isAdmitted(readAdmissionRules(), source, event) ? parsed : null;
50
+ };
51
+ var eventNameOf = (input) => {
52
+ if (!("hook_event_name" in input)) return void 0;
53
+ const name = input.hook_event_name;
54
+ return typeof name === "string" && name.length > 0 ? name : void 0;
55
+ };
56
+
57
+ export {
58
+ readHookInput
59
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  codexHomeFrom
3
- } from "./chunk-SEAHGHCE.js";
3
+ } from "./chunk-HVZIHTOO.js";
4
4
 
5
5
  // src/vscode-config.ts
6
6
  import { homedir } from "os";
@@ -0,0 +1,70 @@
1
+ import {
2
+ parseVersion
3
+ } from "./chunk-64PXDATC.js";
4
+
5
+ // src/setup/agent-probe.ts
6
+ import { execSync } from "child_process";
7
+ import { readFileSync, realpathSync } from "fs";
8
+
9
+ // src/setup/claude-version.ts
10
+ import { dirname, basename, join } from "path";
11
+ var versionFromInstallPath = (path) => {
12
+ const name = basename(path);
13
+ return /^\d+\.\d+\.\d+$/.test(name) ? parseVersion(name) : null;
14
+ };
15
+ var versionFromManifest = (probe, binPath) => {
16
+ let dir = dirname(binPath);
17
+ for (let depth = 0; depth < 4; depth++) {
18
+ const raw = probe.readFile(join(dir, "package.json"));
19
+ if (raw) {
20
+ try {
21
+ const { version } = JSON.parse(raw);
22
+ if (typeof version === "string") {
23
+ const parsed = parseVersion(version);
24
+ if (parsed) return parsed;
25
+ }
26
+ } catch {
27
+ }
28
+ }
29
+ const parent = dirname(dir);
30
+ if (parent === dir) break;
31
+ dir = parent;
32
+ }
33
+ return null;
34
+ };
35
+ var readClaudeVersion = (probe) => {
36
+ const onPath2 = probe.resolveOnPath();
37
+ if (!onPath2) return null;
38
+ const resolved = probe.realpath(onPath2) ?? onPath2;
39
+ return versionFromInstallPath(resolved) ?? versionFromManifest(probe, resolved);
40
+ };
41
+
42
+ // src/setup/agent-probe.ts
43
+ var onPath = (name) => {
44
+ const command = process.platform === "win32" ? "where" : "which";
45
+ try {
46
+ const found = execSync(`${command} ${name}`, { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).split("\n")[0].trim();
47
+ return found || null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ };
52
+ var realpath = (path) => {
53
+ try {
54
+ return realpathSync(path);
55
+ } catch {
56
+ return null;
57
+ }
58
+ };
59
+ var readFile = (path) => {
60
+ try {
61
+ return readFileSync(path, "utf-8");
62
+ } catch {
63
+ return null;
64
+ }
65
+ };
66
+ var detectClaudeVersion = () => readClaudeVersion({ resolveOnPath: () => onPath("claude"), realpath, readFile });
67
+
68
+ export {
69
+ detectClaudeVersion
70
+ };
@@ -196,9 +196,10 @@ var readLastGoodMode = (apiKey) => {
196
196
  return null;
197
197
  }
198
198
  };
199
- var writeLastGoodMode = (apiKey, state) => {
199
+ var writeLastGoodMode = (apiKey, state, sessionId) => {
200
200
  try {
201
- writeFileSync(modeStateCacheFile(apiKey), JSON.stringify({ state, at: Date.now() }), { encoding: "utf-8", mode: 384 });
201
+ const cached = { state, at: Date.now(), ...sessionId ? { sessionId } : {} };
202
+ writeFileSync(modeStateCacheFile(apiKey), JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
202
203
  } catch {
203
204
  }
204
205
  };
@@ -220,7 +221,18 @@ var degradedFallback = (apiKey, authoritative = false) => {
220
221
  return unknownMode();
221
222
  };
222
223
  var toPolicyVersion = (value) => typeof value === "string" || typeof value === "number" ? String(value) : null;
223
- var fetchModeState = async (apiKey, sessionId) => {
224
+ var MODE_REUSE_MS = CACHE_TTL_MS;
225
+ var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
226
+ const last = readLastGoodMode(apiKey);
227
+ if (!last || (last.sessionId ?? null) !== (sessionId ?? null)) return null;
228
+ if (Date.now() - last.at >= maxAgeMs) return null;
229
+ return { ...last.state, degraded: false };
230
+ };
231
+ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
232
+ if (reuseWithinMs > 0) {
233
+ const reusable = readReusableMode(apiKey, sessionId, reuseWithinMs);
234
+ if (reusable) return reusable;
235
+ }
224
236
  try {
225
237
  const baseUrl = getBaseUrl();
226
238
  const url = sessionId ? `${baseUrl}/api/mcp/mode?session=${encodeURIComponent(sessionId)}` : `${baseUrl}/api/mcp/mode`;
@@ -244,7 +256,7 @@ var fetchModeState = async (apiKey, sessionId) => {
244
256
  scope: isScopeContract(data.scope) ? data.scope : null,
245
257
  degraded: false
246
258
  };
247
- writeLastGoodMode(apiKey, state);
259
+ writeLastGoodMode(apiKey, state, sessionId);
248
260
  return state;
249
261
  } catch {
250
262
  return degradedFallback(apiKey);
@@ -1053,7 +1065,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
1053
1065
  const isError = isToolResultError(toolResult);
1054
1066
  const receiptsEnabled = process.env.PUSHARY_RECEIPTS !== "off";
1055
1067
  const sessionKey = input.session_id || DEFAULT_SESSION;
1056
- const liveMode = await fetchModeState(getApiKey(), input.session_id);
1068
+ const liveMode = await fetchModeState(getApiKey(), input.session_id, MODE_REUSE_MS);
1057
1069
  const late = await reconcilePendingQuestions(sessionKey);
1058
1070
  let additionalContext;
1059
1071
  if (late.length > 0 && agent.type === CLAUDE_CODE_AGENT.type) {
@@ -1239,7 +1251,7 @@ var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
1239
1251
  } catch {
1240
1252
  return;
1241
1253
  }
1242
- const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
1254
+ const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
1243
1255
  if (mode?.kill || mode?.mode === "terminal_only") return;
1244
1256
  await sendNotification(apiKey, {
1245
1257
  title: `${agent.label} is waiting`,
@@ -1302,7 +1314,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1302
1314
  return;
1303
1315
  }
1304
1316
  const projectName = basename2(input.cwd ?? process.cwd());
1305
- const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
1317
+ const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
1306
1318
  if (mode?.kill || mode?.mode === "terminal_only") return;
1307
1319
  const reason = describeStopFailure(input.error);
1308
1320
  await sendNotification(apiKey, {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PRETOOLUSE_HANDLED_TOOLS
3
- } from "./chunk-UKSQWTBH.js";
3
+ } from "./chunk-64PXDATC.js";
4
4
  import {
5
5
  isGatingMoment,
6
6
  recordKeylessMoment
@@ -9,7 +9,7 @@ import {
9
9
  denyReasonFrom,
10
10
  isDeferAnswer,
11
11
  resolveGate
12
- } from "./chunk-BLDDXHHA.js";
12
+ } from "./chunk-GXIFADGD.js";
13
13
  import {
14
14
  DEFAULT_SESSION,
15
15
  askUser,
@@ -29,7 +29,7 @@ import {
29
29
  sendNotification,
30
30
  throttlePass,
31
31
  waitForAnswer
32
- } from "./chunk-CCCJ6BPW.js";
32
+ } from "./chunk-EXMUM226.js";
33
33
  import {
34
34
  getMachineId
35
35
  } from "./chunk-RN3NOEJF.js";
@@ -53,6 +53,7 @@ var COMMAND_LIST = [
53
53
  ]
54
54
  },
55
55
  { name: "doctor", module: "pushary-doctor", summary: "Verify your Pushary installation is working" },
56
+ { name: "disconnect", module: "pushary-disconnect", summary: "Turn off one agent (claude|codex|gemini|cursor)" },
56
57
  { name: "clean", module: "pushary-clean", summary: "Remove all Pushary configuration (--yes for non-interactive)" },
57
58
  { name: "mode", module: "pushary-mode", summary: "Switch approval mode (push_only, push_first, terminal_only)" },
58
59
  { name: "wait", module: "pushary-wait", summary: 'Show or set the "wait for your phone" ladder (pushary wait 45)' },
@@ -2,7 +2,7 @@ import {
2
2
  isModeStateDegraded,
3
3
  resolveAutoResolveOrigin,
4
4
  resolvePolicyAcross
5
- } from "./chunk-CCCJ6BPW.js";
5
+ } from "./chunk-EXMUM226.js";
6
6
  import {
7
7
  evaluateScope,
8
8
  scopeChangeReason,
@@ -139,6 +139,38 @@ var untrustedCodexHookEvents = (config, hooksJsonPath, command) => {
139
139
  return stored !== codexHookTrustHash(definition, command);
140
140
  }).map((definition) => definition.event);
141
141
  };
142
+ var removeCodexMcpServer = (config) => {
143
+ const servers = asRecord(config.mcp_servers);
144
+ if (!servers?.pushary) return false;
145
+ delete servers.pushary;
146
+ if (Object.keys(servers).length === 0) delete config.mcp_servers;
147
+ return true;
148
+ };
149
+ var removeCodexHookTrust = (config, hooksJsonPath) => {
150
+ const state = asRecord(asRecord(config.hooks)?.state);
151
+ if (!state) return false;
152
+ let changed = false;
153
+ for (const definition of CODEX_HOOK_EVENTS) {
154
+ const key = codexHookStateKey(hooksJsonPath, definition.event);
155
+ if (key in state) {
156
+ delete state[key];
157
+ changed = true;
158
+ }
159
+ }
160
+ if (changed && Object.keys(state).length === 0) {
161
+ const hooks = asRecord(config.hooks);
162
+ if (hooks) {
163
+ delete hooks.state;
164
+ if (Object.keys(hooks).length === 0) delete config.hooks;
165
+ }
166
+ }
167
+ return changed;
168
+ };
169
+ var removeCodexSettings = (config, hooksJsonPath) => {
170
+ const mcp = removeCodexMcpServer(config);
171
+ const trust = removeCodexHookTrust(config, hooksJsonPath);
172
+ return mcp || trust;
173
+ };
142
174
  var addCodexHookTrust = (config, hooksJsonPath, command) => {
143
175
  const hooks = ensureRecord(config, "hooks");
144
176
  const state = ensureRecord(hooks, "state");
@@ -183,6 +215,7 @@ export {
183
215
  hasCodexHooks,
184
216
  missingCodexHookEvents,
185
217
  untrustedCodexHookEvents,
218
+ removeCodexSettings,
186
219
  addCodexHookTrust,
187
220
  claudeSettings,
188
221
  claudeJson,
@@ -0,0 +1,27 @@
1
+ import {
2
+ quoteIfNeeded
3
+ } from "./chunk-VT4IVERX.js";
4
+
5
+ // src/hook-command.ts
6
+ var SHELL = "/bin/sh -c";
7
+ var LOCATE = 'B=$(command -v "$0") || exit 0; [ -f "$B" ] || exit 0; [ -x "$B" ] || exit 0';
8
+ var NODE_PRESENT = "command -v node >/dev/null 2>&1 || exit 0";
9
+ var RESOLVE = {
10
+ native: `${LOCATE}; exec "$B"`,
11
+ node: `${LOCATE}; ${NODE_PRESENT}; exec "$B"`
12
+ };
13
+ var guardHookCommand = (target, platform = process.platform) => {
14
+ if (target.kind === "node") {
15
+ const script = quoteIfNeeded(target.script);
16
+ return platform === "win32" ? `node ${script}` : `${SHELL} '[ -f "$0" ] || exit 0; command -v node >/dev/null 2>&1 || exit 0; exec node "$0"' ${script}`;
17
+ }
18
+ const path = quoteIfNeeded(target.path);
19
+ return platform === "win32" ? path : `${SHELL} '${RESOLVE[target.runtime]}' ${path}`;
20
+ };
21
+ var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
22
+ var guardNodeScript = (script, platform) => guardHookCommand({ kind: "node", script }, platform);
23
+
24
+ export {
25
+ guardBinary,
26
+ guardNodeScript
27
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  renderCommandHelp
3
- } from "./chunk-MGJBC6DD.js";
3
+ } from "./chunk-GOEXZ5QJ.js";
4
4
  import {
5
5
  getPackageVersion
6
6
  } from "./chunk-DINDL2CF.js";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VT4IVERX.js";
8
8
  import {
9
9
  pusharyDir
10
- } from "./chunk-SEAHGHCE.js";
10
+ } from "./chunk-HVZIHTOO.js";
11
11
  import {
12
12
  getMachineId
13
13
  } from "./chunk-RN3NOEJF.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-SEAHGHCE.js";
3
+ } from "./chunk-HVZIHTOO.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-6MTNS63X.js";
@@ -39,7 +39,7 @@ interface ModeState {
39
39
  readonly scope?: ScopeContract | null;
40
40
  readonly degraded?: boolean;
41
41
  }
42
- declare const fetchModeState: (apiKey: string, sessionId?: string) => Promise<ModeState>;
42
+ declare const fetchModeState: (apiKey: string, sessionId?: string, reuseWithinMs?: number) => Promise<ModeState>;
43
43
  declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
44
44
 
45
45
  interface UsageReport {
package/dist/src/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-YR5TSPXB.js";
4
- import "../chunk-UKSQWTBH.js";
3
+ } from "../chunk-GFM3STWO.js";
4
+ import "../chunk-64PXDATC.js";
5
+ import "../chunk-PWTKKQQI.js";
6
+ import "../chunk-VT4IVERX.js";
5
7
  import "../chunk-NJ7JT26G.js";
6
- import "../chunk-BLDDXHHA.js";
8
+ import "../chunk-GXIFADGD.js";
7
9
  import {
8
10
  askUser,
9
11
  cancelQuestion,
@@ -16,7 +18,7 @@ import {
16
18
  reportEvent,
17
19
  resolvePolicy,
18
20
  waitForAnswer
19
- } from "../chunk-CCCJ6BPW.js";
21
+ } from "../chunk-EXMUM226.js";
20
22
  import "../chunk-RN3NOEJF.js";
21
23
  import "../chunk-BSZYIAZL.js";
22
24
  import "../chunk-SAF6HGAA.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.84.1",
3
+ "version": "0.85.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -68,7 +68,8 @@
68
68
  "pushary-suggestions": "./dist/bin/pushary-suggestions.js",
69
69
  "pushary-upgrade": "./dist/bin/pushary-upgrade.js",
70
70
  "pushary-bell": "./dist/bin/pushary-bell.js",
71
- "pushary-bell-hook": "./dist/bin/pushary-bell-hook.js"
71
+ "pushary-bell-hook": "./dist/bin/pushary-bell-hook.js",
72
+ "pushary-disconnect": "./dist/bin/pushary-disconnect.js"
72
73
  },
73
74
  "files": [
74
75
  "dist",