@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.
- package/CHANGELOG.md +44 -0
- package/README.md +24 -0
- package/dist/bin/pushary-bell-hook.js +5 -3
- package/dist/bin/pushary-bell.js +8 -7
- package/dist/bin/pushary-claude.js +5 -5
- package/dist/bin/pushary-clean.js +6 -5
- package/dist/bin/pushary-codex-bridge.js +2 -2
- package/dist/bin/pushary-codex-hook.js +7 -13
- package/dist/bin/pushary-codex.js +1 -1
- package/dist/bin/pushary-connect.js +5 -5
- package/dist/bin/pushary-daemon.js +5 -5
- package/dist/bin/pushary-disconnect.d.ts +1 -0
- package/dist/bin/pushary-disconnect.js +125 -0
- package/dist/bin/pushary-doctor.js +9 -9
- package/dist/bin/pushary-gemini-bridge.js +2 -2
- package/dist/bin/pushary-gemini-hook.js +7 -13
- package/dist/bin/pushary-hook.js +13 -20
- package/dist/bin/pushary-login.js +2 -2
- package/dist/bin/pushary-logout.js +3 -3
- package/dist/bin/pushary-mode.js +2 -2
- package/dist/bin/pushary-notification-hook.js +6 -9
- package/dist/bin/pushary-permission-denied-hook.js +11 -9
- package/dist/bin/pushary-permission-hook.js +11 -9
- package/dist/bin/pushary-post-hook.js +6 -9
- package/dist/bin/pushary-prompt-hook.js +6 -9
- package/dist/bin/pushary-session-end-hook.js +6 -6
- package/dist/bin/pushary-session-start-hook.js +6 -6
- package/dist/bin/pushary-setup.js +23 -24
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +2 -2
- package/dist/bin/pushary-stop-hook.js +6 -6
- package/dist/bin/pushary-stopfailure-hook.js +6 -6
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-upgrade.js +13 -8
- package/dist/bin/pushary-wait.js +2 -2
- package/dist/bin/pushary.js +2 -1
- package/dist/{chunk-HZNOAJGB.js → chunk-4QPOMJUB.js} +1 -1
- package/dist/chunk-64PXDATC.js +187 -0
- package/dist/chunk-6CUUA7HO.js +59 -0
- package/dist/{chunk-UXWEE3QI.js → chunk-ADISVXZL.js} +1 -1
- package/dist/chunk-AXRATIS6.js +70 -0
- package/dist/{chunk-CCCJ6BPW.js → chunk-EXMUM226.js} +19 -7
- package/dist/{chunk-YR5TSPXB.js → chunk-GFM3STWO.js} +3 -3
- package/dist/{chunk-MGJBC6DD.js → chunk-GOEXZ5QJ.js} +1 -0
- package/dist/{chunk-BLDDXHHA.js → chunk-GXIFADGD.js} +1 -1
- package/dist/{chunk-SEAHGHCE.js → chunk-HVZIHTOO.js} +33 -0
- package/dist/chunk-PWTKKQQI.js +27 -0
- package/dist/{chunk-J4KFVOCQ.js → chunk-SQOFGPNX.js} +1 -1
- package/dist/{chunk-CCH775Y6.js → chunk-WEAQQSHQ.js} +1 -1
- package/dist/{chunk-ILB6UOAF.js → chunk-YKW6JCWI.js} +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +6 -4
- package/package.json +3 -2
- 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
|
+
};
|
|
@@ -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
|
-
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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)' },
|
|
@@ -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
|
+
};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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-
|
|
4
|
-
import "../chunk-
|
|
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-
|
|
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-
|
|
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.
|
|
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",
|