@pushary/agent-hooks 1.9.63 → 1.9.65
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 +20 -1
- package/README.md +1 -1
- package/dist/{api-UHUUAONF.js → api-32B3XLJR.js} +10 -11
- package/dist/bin/pushary-bell-hook.js +27 -30
- package/dist/bin/pushary-bell.js +12 -13
- package/dist/bin/pushary-claude.js +28 -31
- package/dist/bin/pushary-clean.js +68 -43
- package/dist/bin/pushary-codex-bridge.js +22 -24
- package/dist/bin/pushary-codex-hook.js +34 -37
- package/dist/bin/pushary-codex-session.js +8 -8
- package/dist/bin/pushary-codex.js +13 -15
- package/dist/bin/pushary-connect.js +26 -29
- package/dist/bin/pushary-cowork.js +5 -5
- package/dist/bin/pushary-daemon.js +39 -42
- package/dist/bin/pushary-disconnect.js +62 -43
- package/dist/bin/pushary-doctor.js +109 -44
- package/dist/bin/pushary-elicitation-hook.js +27 -30
- package/dist/bin/pushary-gemini-bridge.js +16 -18
- package/dist/bin/pushary-gemini-hook.js +29 -32
- package/dist/bin/pushary-hook.js +31 -34
- package/dist/bin/pushary-login.js +19 -20
- package/dist/bin/pushary-logout.js +26 -29
- package/dist/bin/pushary-mcp.js +11 -12
- package/dist/bin/pushary-mode.js +14 -15
- package/dist/bin/pushary-notification-hook.js +26 -29
- package/dist/bin/pushary-opencode-hook.js +28 -31
- package/dist/bin/pushary-permission-denied-hook.js +31 -34
- package/dist/bin/pushary-permission-hook.js +30 -33
- package/dist/bin/pushary-post-hook.js +26 -29
- package/dist/bin/pushary-prompt-hook.js +26 -29
- package/dist/bin/pushary-session-end-hook.js +28 -31
- package/dist/bin/pushary-session-start-hook.js +35 -38
- package/dist/bin/pushary-setup.js +129 -74
- package/dist/bin/pushary-stats.js +5 -5
- package/dist/bin/pushary-status.js +31 -34
- package/dist/bin/pushary-stop-hook.js +26 -29
- package/dist/bin/pushary-stopfailure-hook.js +26 -29
- package/dist/bin/pushary-suggestions.js +14 -15
- package/dist/bin/pushary-transcript-register.js +18 -21
- package/dist/bin/pushary-transcripts.js +17 -18
- package/dist/bin/pushary-upgrade.js +40 -44
- package/dist/bin/pushary-wait.js +14 -15
- package/dist/bin/pushary.js +7 -8
- package/dist/{chunk-V5W3CK7F.js → chunk-2VADJ6JH.js} +2 -2
- package/dist/{chunk-3GVV7XVT.js → chunk-2Z7I7DHT.js} +1 -1
- package/dist/{chunk-7GRQGXF4.js → chunk-3KXBRSL3.js} +1 -1
- package/dist/{chunk-P6RF4RQN.js → chunk-3LF7T3NW.js} +6 -6
- package/dist/{chunk-PXAOEFJZ.js → chunk-3UNAPXED.js} +14 -16
- package/dist/{chunk-TMGJAFSD.js → chunk-4LUWNBGD.js} +3 -3
- package/dist/{chunk-CLABCSZC.js → chunk-6CZX6YOE.js} +5 -5
- package/dist/{chunk-VFJSVPNL.js → chunk-7ILQX47W.js} +3 -3
- package/dist/{chunk-4WFAP6QN.js → chunk-ADXHPGBZ.js} +9 -11
- package/dist/{chunk-75ODMNOM.js → chunk-AN7C3CHH.js} +1 -1
- package/dist/{chunk-BQFL2ECS.js → chunk-ANP3DIST.js} +3 -57
- package/dist/chunk-ATPJGHUP.js +1201 -0
- package/dist/{chunk-6Z2VR3BA.js → chunk-BAGBCSXA.js} +1 -1
- package/dist/{chunk-WUOOZE5S.js → chunk-BEQ3MEE3.js} +2 -2
- package/dist/{chunk-ELAK46NR.js → chunk-BKCI53W6.js} +4 -4
- package/dist/{chunk-HAIXS4OX.js → chunk-CSKABVSH.js} +3 -3
- package/dist/{chunk-GNUARBRZ.js → chunk-CU2PXLZ6.js} +4 -4
- package/dist/{chunk-FVERRWDP.js → chunk-CZ65D6Q5.js} +2 -2
- package/dist/{chunk-PJ34FOIV.js → chunk-DM24VZOI.js} +5 -5
- package/dist/{chunk-TBKHQWIH.js → chunk-DTVHZDYM.js} +5 -5
- package/dist/{chunk-PIQ4HHA2.js → chunk-DXMUJTYF.js} +4 -6
- package/dist/{chunk-IOH2T3CB.js → chunk-ED2SWTPC.js} +4 -4
- package/dist/{chunk-FAF5AW3P.js → chunk-EEVE7H5Z.js} +4 -4
- package/dist/{chunk-EHHTQURL.js → chunk-F56GTJFG.js} +5 -5
- package/dist/{chunk-GZS72NCF.js → chunk-FFSIYBBU.js} +4 -4
- package/dist/{chunk-L5CDGXPO.js → chunk-FWS2KLV2.js} +4 -4
- package/dist/{chunk-T3GHXR6N.js → chunk-GA257T2E.js} +5 -5
- package/dist/{chunk-SKUSGWFK.js → chunk-GTBWWCCM.js} +6 -6
- package/dist/{chunk-YK6GR4GU.js → chunk-HDGB6SQ4.js} +2 -2
- package/dist/{chunk-OY6Z4IKB.js → chunk-HIYLAC3C.js} +3 -3
- package/dist/{chunk-RAJ3QEIL.js → chunk-HVOQUPSO.js} +1 -1
- package/dist/{chunk-KUCL7ETL.js → chunk-I2UBF2SS.js} +1 -1
- package/dist/{chunk-VHZAJ6IB.js → chunk-ICFHMJTW.js} +3 -3
- package/dist/{chunk-HZ72EMJE.js → chunk-ITYKN2K3.js} +2 -2
- package/dist/{chunk-XBTWG2VL.js → chunk-IZVEBBDH.js} +5 -5
- package/dist/{chunk-VN7XH6TU.js → chunk-JCWHOB36.js} +2 -2
- package/dist/{chunk-5JK3DQGZ.js → chunk-JFKMPLNF.js} +2 -2
- package/dist/{chunk-FEAGK25K.js → chunk-LMC5UPL7.js} +1 -1
- package/dist/{chunk-7NCLUA65.js → chunk-MNKIR2ZB.js} +11 -0
- package/dist/{chunk-SARPHHTT.js → chunk-NOH3EJFH.js} +1 -1
- package/dist/{chunk-OP5IBEV7.js → chunk-NUEPJ5W6.js} +2 -2
- package/dist/{chunk-OU3BYBJ3.js → chunk-OA6EADFT.js} +3 -3
- package/dist/{chunk-LGZ545TA.js → chunk-ONBL2GCU.js} +12 -6
- package/dist/{chunk-LTT54NOU.js → chunk-P5HZEGT7.js} +65 -8
- package/dist/{chunk-YMZOM6BD.js → chunk-PP7FC3Y7.js} +10 -10
- package/dist/{chunk-T6XQNOZV.js → chunk-Q2BD4IBX.js} +12 -12
- package/dist/{chunk-DMNLFWGY.js → chunk-QNZ6G3LY.js} +210 -151
- package/dist/{chunk-BCVCUDFI.js → chunk-QSW3HAKI.js} +1 -1
- package/dist/{chunk-HOIO66DU.js → chunk-R4FL5JHK.js} +4 -4
- package/dist/{chunk-VUFHYMSP.js → chunk-RABLE5R6.js} +1 -1
- package/dist/{chunk-2MZ6ROWZ.js → chunk-RRHHWYPX.js} +6 -6
- package/dist/{chunk-5LOP4AVD.js → chunk-SCMEVDAR.js} +128 -26
- package/dist/{chunk-XE5N352V.js → chunk-SROWUG5W.js} +1 -1
- package/dist/{chunk-QW3FRUFB.js → chunk-SZBMTOUZ.js} +1 -1
- package/dist/{chunk-V7N5NOZV.js → chunk-TJUJAK5L.js} +1 -1
- package/dist/{chunk-OJEIS5L2.js → chunk-TK4JU2F7.js} +1 -1
- package/dist/{chunk-VDBXSUXP.js → chunk-U2P2LQ5B.js} +7 -7
- package/dist/{chunk-AB3VAQ25.js → chunk-UHS5MMAI.js} +3 -3
- package/dist/{chunk-OW6VBXXI.js → chunk-UM2X7HL6.js} +63 -36
- package/dist/{chunk-KL76XWZ7.js → chunk-UMTMFDL2.js} +1 -1
- package/dist/{chunk-MRCVYYPW.js → chunk-VHKK3US3.js} +3 -3
- package/dist/{chunk-S2Z6VWHH.js → chunk-W5L2ZLHE.js} +6 -7
- package/dist/{chunk-EQ26IKZM.js → chunk-WOHP7EL6.js} +7 -6
- package/dist/{chunk-3QLFB2EY.js → chunk-WY6YAMZW.js} +37 -214
- package/dist/{chunk-XO3KAH7K.js → chunk-XJIIU36L.js} +1 -1
- package/dist/{chunk-QC2W42DE.js → chunk-XZCJC3QO.js} +12 -12
- package/dist/{chunk-BUQU3OPX.js → chunk-Y64JGYTB.js} +1 -1
- package/dist/{chunk-KLZZI5TU.js → chunk-Z3M627TQ.js} +1 -1
- package/dist/{codex-app-cards-switch-LXMAIOFB.js → codex-app-cards-switch-HK4RMOBI.js} +11 -12
- package/dist/{codex-desktop-launch-VE5RLFUU.js → codex-desktop-launch-OCO6N7SA.js} +1 -1
- package/dist/{codex-session-launch-XZZWXCPK.js → codex-session-launch-ETZQTVFS.js} +19 -21
- package/dist/{config-PPX25AV5.js → config-YY54G3CC.js} +3 -3
- package/dist/{global-upgrade-NRMHNLNU.js → global-upgrade-5DN5G3ZQ.js} +10 -12
- package/dist/{identity-ZFQR7JNW.js → identity-EGL6HAWG.js} +4 -5
- package/dist/{live-capabilities-SJRYRKNB.js → live-capabilities-WU3FYJJG.js} +5 -7
- package/dist/{paths-7VTVOQXJ.js → paths-ZG6KDM6Y.js} +7 -3
- package/dist/reapply-AQLNCCTE.js +63 -0
- package/dist/src/index.js +20 -23
- package/dist/{upgrade-lock-VAYJZVTB.js → upgrade-lock-BIRXIKHJ.js} +4 -5
- package/dist/{usage-C4DIYUR6.js → usage-43C2DHDU.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-6ICPEJY5.js +0 -20
- package/dist/chunk-MK4USZYQ.js +0 -615
- package/dist/chunk-QENTGE75.js +0 -86
- package/dist/chunk-VC3HFFMQ.js +0 -41
- package/dist/reapply-WOANOIRZ.js +0 -64
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
atLeast,
|
|
3
|
+
formatVersion,
|
|
4
|
+
quoteIfNeeded,
|
|
5
|
+
shellDoubleQuoted,
|
|
6
|
+
writeFileAtomic
|
|
7
|
+
} from "./chunk-SCMEVDAR.js";
|
|
8
|
+
import {
|
|
9
|
+
CLAUDE_PRETOOLUSE_MATCHER,
|
|
10
|
+
CODEX_QUESTION_TOOLS,
|
|
11
|
+
HOOK_BUDGETS,
|
|
12
|
+
HOOK_LOCATOR_BINARY,
|
|
13
|
+
PRETOOLUSE_GATED_TOOLS,
|
|
14
|
+
withHookHoldBudget
|
|
15
|
+
} from "./chunk-MNKIR2ZB.js";
|
|
16
|
+
|
|
17
|
+
// src/setup/paths.ts
|
|
18
|
+
import { createHash as createHash2 } from "crypto";
|
|
19
|
+
|
|
20
|
+
// src/claude-config-paths.ts
|
|
21
|
+
import { homedir } from "os";
|
|
22
|
+
import { join, resolve } from "path";
|
|
23
|
+
var claudeConfigDir = (home = homedir(), environment = process.env) => resolve(environment.CLAUDE_CONFIG_DIR || join(home, ".claude"));
|
|
24
|
+
var claudeUserConfigFile = (home = homedir(), environment = process.env) => join(environment.CLAUDE_CONFIG_DIR ? claudeConfigDir(home, environment) : home, ".claude.json");
|
|
25
|
+
|
|
26
|
+
// src/setup/paths.ts
|
|
27
|
+
import { homedir as homedir3 } from "os";
|
|
28
|
+
import { join as join5 } from "path";
|
|
29
|
+
|
|
30
|
+
// src/codex-config.ts
|
|
31
|
+
import { createHash } from "crypto";
|
|
32
|
+
import { homedir as homedir2 } from "os";
|
|
33
|
+
import { join as join2 } from "path";
|
|
34
|
+
|
|
35
|
+
// src/registration-holder.ts
|
|
36
|
+
var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
37
|
+
var registeredCommands = (document) => {
|
|
38
|
+
const hooks = asRecord(document?.hooks);
|
|
39
|
+
if (!hooks) return [];
|
|
40
|
+
const found = [];
|
|
41
|
+
for (const [event, value] of Object.entries(hooks)) {
|
|
42
|
+
if (!Array.isArray(value)) continue;
|
|
43
|
+
for (const item of value) {
|
|
44
|
+
const inner = asRecord(item)?.hooks;
|
|
45
|
+
if (!Array.isArray(inner)) continue;
|
|
46
|
+
for (const hook of inner) {
|
|
47
|
+
const command = asRecord(hook)?.command;
|
|
48
|
+
if (typeof command === "string") found.push({ event, command });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return found;
|
|
53
|
+
};
|
|
54
|
+
var wholeSegments = (command) => command.split(/[\s/\\"']+/).map((segment) => segment.replace(/\.(cmd|exe)$/i, ""));
|
|
55
|
+
var namesLocator = (command) => wholeSegments(command).includes(HOOK_LOCATOR_BINARY);
|
|
56
|
+
var namesCliBinary = (command, cliBinaries) => wholeSegments(command).some((segment) => cliBinaries.has(segment));
|
|
57
|
+
var namesOurBinary = (command, cliBinaries) => namesLocator(command) || namesCliBinary(command, cliBinaries);
|
|
58
|
+
var registrationHolderOf = (document, cliBinaries) => {
|
|
59
|
+
const commands = registeredCommands(document);
|
|
60
|
+
if (commands.some(({ command }) => namesLocator(command))) return "app";
|
|
61
|
+
return commands.some(({ command }) => namesCliBinary(command, cliBinaries)) ? "cli" : "none";
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/hook-command.ts
|
|
65
|
+
var HOOK_SHELL = "/bin/sh -c";
|
|
66
|
+
var holdBudgetOf = (hook) => hook.holds ? hook.timeout : void 0;
|
|
67
|
+
var LOCATE = 'B=$(command -v "$0") || exit 0; [ -f "$B" ] || exit 0; [ -x "$B" ] || exit 0';
|
|
68
|
+
var NODE_PRESENT = "command -v node >/dev/null 2>&1 || exit 0";
|
|
69
|
+
var CURSOR_NO_OPINION = { permission: "deny", user_message: "Command denied via Pushary.", agent_message: "Pushary hook is unavailable. Repair the integration before retrying." };
|
|
70
|
+
var CURSOR_NATIVE_PROMPT = { permission: "ask" };
|
|
71
|
+
var CURSOR_NATIVE_PROMPT_EVENTS = ["beforeShellExecution", "beforeMCPExecution"];
|
|
72
|
+
var hookEventIsOneOf = (events) => events.flatMap((event) => [`*\\"hook_event_name\\":\\"${event}\\"*`, `*\\"hook_event_name\\":\\ \\"${event}\\"*`]).join("|");
|
|
73
|
+
var CURSOR_MISS = `{ E=$(cat); case "$E" in ${hookEventIsOneOf(CURSOR_NATIVE_PROMPT_EVENTS)}) printf %s ${shellDoubleQuoted(JSON.stringify(CURSOR_NATIVE_PROMPT))} ;; *) printf %s ${shellDoubleQuoted(JSON.stringify(CURSOR_NO_OPINION))} ;; esac; exit 0; }`;
|
|
74
|
+
var HOOK_GUARD_SCRIPTS = {
|
|
75
|
+
native_binary: `${LOCATE}; exec "$B" "$@"`,
|
|
76
|
+
node_binary: `${LOCATE}; ${NODE_PRESENT}; exec "$B" "$@"`,
|
|
77
|
+
node_script: `[ -f "$0" ] || exit 0; ${NODE_PRESENT}; exec node "$0" "$@"`,
|
|
78
|
+
cursor_gate_script: `[ -f "$0" ] || ${CURSOR_MISS}; command -v node >/dev/null 2>&1 || ${CURSOR_MISS}; exec node "$0" "$@"`
|
|
79
|
+
};
|
|
80
|
+
var BINARY_GUARDS = {
|
|
81
|
+
native: "native_binary",
|
|
82
|
+
node: "node_binary"
|
|
83
|
+
};
|
|
84
|
+
var guardKindOf = (target) => {
|
|
85
|
+
switch (target.kind) {
|
|
86
|
+
case "node":
|
|
87
|
+
return "node_script";
|
|
88
|
+
case "cursor_gate":
|
|
89
|
+
return "cursor_gate_script";
|
|
90
|
+
case "binary":
|
|
91
|
+
return BINARY_GUARDS[target.runtime];
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var targetPath = (target) => target.kind === "binary" ? target.path : target.script;
|
|
95
|
+
var runsThroughNode = (target) => target.kind !== "binary";
|
|
96
|
+
var guardHookCommand = (target, platform = process.platform) => {
|
|
97
|
+
const path = quoteIfNeeded(targetPath(target), platform);
|
|
98
|
+
if (platform === "win32") return runsThroughNode(target) ? `node ${path}` : path;
|
|
99
|
+
return `${HOOK_SHELL} '${HOOK_GUARD_SCRIPTS[guardKindOf(target)]}' ${path}`;
|
|
100
|
+
};
|
|
101
|
+
var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
|
|
102
|
+
var SILENT_MISS = "exit 0";
|
|
103
|
+
var REFUSE = "R";
|
|
104
|
+
var definesRefusal = (refusal) => `${REFUSE}() { printf %s ${shellDoubleQuoted(refusal)}; exit 0; }`;
|
|
105
|
+
var guardBinaryRefusing = (path, refusal, platform = process.platform) => {
|
|
106
|
+
if (platform === "win32") return guardBinary(path, platform);
|
|
107
|
+
const script = `${definesRefusal(refusal)}; ${HOOK_GUARD_SCRIPTS.node_binary.replaceAll(SILENT_MISS, REFUSE)}`;
|
|
108
|
+
return `${HOOK_SHELL} '${script}' ${quoteIfNeeded(path)}`;
|
|
109
|
+
};
|
|
110
|
+
var guardNodeScript = (script, platform) => guardHookCommand({ kind: "node", script }, platform);
|
|
111
|
+
var guardCursorGate = (script, platform) => guardHookCommand({ kind: "cursor_gate", script }, platform);
|
|
112
|
+
var guardedArgv = (target, args = [], platform = process.platform) => {
|
|
113
|
+
const path = targetPath(target);
|
|
114
|
+
if (platform === "win32") {
|
|
115
|
+
return runsThroughNode(target) ? ["node", path, ...args] : [path, ...args];
|
|
116
|
+
}
|
|
117
|
+
const [shell, ...shellArgs] = HOOK_SHELL.split(/\s+/);
|
|
118
|
+
return [shell, ...shellArgs, HOOK_GUARD_SCRIPTS[guardKindOf(target)], path, ...args];
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/hook-entries.ts
|
|
122
|
+
var registerHookEntry = (existing, isOurs, entry2) => {
|
|
123
|
+
const rewritten = [];
|
|
124
|
+
let placed = false;
|
|
125
|
+
for (const item of existing) {
|
|
126
|
+
if (!isOurs(item)) {
|
|
127
|
+
rewritten.push(item);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (placed) continue;
|
|
131
|
+
rewritten.push(entry2);
|
|
132
|
+
placed = true;
|
|
133
|
+
}
|
|
134
|
+
if (!placed) rewritten.push(entry2);
|
|
135
|
+
return rewritten;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/removal-scope.ts
|
|
139
|
+
var namesAppLocator = (command) => command.includes(HOOK_LOCATOR_BINARY);
|
|
140
|
+
var isProtectedByScope = (command, scope) => scope?.keepAppWiring === true && namesAppLocator(command);
|
|
141
|
+
|
|
142
|
+
// src/canonical-json.ts
|
|
143
|
+
var canonicalJson = (value) => {
|
|
144
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
145
|
+
if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
|
|
146
|
+
const record = value;
|
|
147
|
+
return "{" + Object.keys(record).sort().map((key) => JSON.stringify(key) + ":" + canonicalJson(record[key])).join(",") + "}";
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/codex-config.ts
|
|
151
|
+
var CODEX_HOOK_BINARY = "pushary-codex-hook";
|
|
152
|
+
var codexHome = () => codexHomeFrom(homedir2());
|
|
153
|
+
var codexHomeFrom = (home) => process.env.CODEX_HOME?.trim() || join2(home, ".codex");
|
|
154
|
+
var codexConfigToml = () => join2(codexHome(), "config.toml");
|
|
155
|
+
var codexHooksJsonIn = (home) => join2(home, "hooks.json");
|
|
156
|
+
var codexHooksJson = () => codexHooksJsonIn(codexHome());
|
|
157
|
+
var codexAgentsMd = () => join2(codexHome(), "AGENTS.md");
|
|
158
|
+
var codexSkillDir = () => join2(codexHome(), "skills", "pushary");
|
|
159
|
+
var CODEX_TOOL_CALL_MATCHER = "Bash|apply_patch|mcp__.*";
|
|
160
|
+
var CODEX_PRE_TOOL_USE_MATCHER = [CODEX_TOOL_CALL_MATCHER, ...CODEX_QUESTION_TOOLS].join("|");
|
|
161
|
+
var CODEX_HOOK_EVENTS = [
|
|
162
|
+
{ event: "PermissionRequest", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
|
|
163
|
+
{ event: "PreToolUse", matcher: CODEX_PRE_TOOL_USE_MATCHER, delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
|
|
164
|
+
{ event: "PostToolUse", matcher: CODEX_TOOL_CALL_MATCHER, delivery: "telemetry", timeout: 10 },
|
|
165
|
+
{ event: "UserPromptSubmit", delivery: "telemetry", timeout: 10 },
|
|
166
|
+
{ event: "Stop", delivery: "rescue", timeout: HOOK_BUDGETS.codex.budgetSeconds, holds: true },
|
|
167
|
+
// `clear` alongside startup and resume, matching what we already do on Claude
|
|
168
|
+
// Code. Codex names all three in its own SessionStart sources, and without it a
|
|
169
|
+
// session the person started by clearing is one we never see begin.
|
|
170
|
+
{ event: "SessionStart", matcher: "startup|resume|clear", delivery: "telemetry", timeout: 10 },
|
|
171
|
+
// Codex caps SessionEnd at 3s and defaults it to 1s, unlike every other event
|
|
172
|
+
// here. It also runs synchronously whatever `async` says, so the handler must
|
|
173
|
+
// fit the window rather than assume the 10s Claude Code allows.
|
|
174
|
+
{ event: "SessionEnd", delivery: "telemetry", timeout: 3 },
|
|
175
|
+
// Observability only, and the four Codex names we were not registering at all.
|
|
176
|
+
// Every one is in codex-cli 0.151.0's own `HookEventName` enum
|
|
177
|
+
// (subagent_start, subagent_stop, pre_compact, post_compact), and we already
|
|
178
|
+
// carry all four on Claude Code, so the island showed subagent and compaction
|
|
179
|
+
// shape for one agent and not the other. None of them decides anything.
|
|
180
|
+
{ event: "SubagentStart", delivery: "telemetry", timeout: 5 },
|
|
181
|
+
{ event: "SubagentStop", delivery: "telemetry", timeout: 5 },
|
|
182
|
+
{ event: "PreCompact", matcher: "manual|auto", delivery: "telemetry", timeout: 5 },
|
|
183
|
+
{ event: "PostCompact", matcher: "manual|auto", delivery: "telemetry", timeout: 5 }
|
|
184
|
+
];
|
|
185
|
+
var CODEX_REQUIRED_APPROVAL_EVENTS = CODEX_HOOK_EVENTS.filter((event) => event.delivery === "approval").map((event) => event.event);
|
|
186
|
+
var CODEX_EVENT_KEY = {
|
|
187
|
+
PermissionRequest: "permission_request",
|
|
188
|
+
PreToolUse: "pre_tool_use",
|
|
189
|
+
PostToolUse: "post_tool_use",
|
|
190
|
+
UserPromptSubmit: "user_prompt_submit",
|
|
191
|
+
Stop: "stop",
|
|
192
|
+
SessionStart: "session_start",
|
|
193
|
+
SessionEnd: "session_end",
|
|
194
|
+
SubagentStart: "subagent_start",
|
|
195
|
+
SubagentStop: "subagent_stop",
|
|
196
|
+
PreCompact: "pre_compact",
|
|
197
|
+
PostCompact: "post_compact"
|
|
198
|
+
};
|
|
199
|
+
var codexEventKeyOf = (event) => event.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
200
|
+
var asRecord2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
201
|
+
var ensureRecord = (target, key) => {
|
|
202
|
+
const existing = asRecord2(target[key]);
|
|
203
|
+
if (existing) return existing;
|
|
204
|
+
const created = {};
|
|
205
|
+
target[key] = created;
|
|
206
|
+
return created;
|
|
207
|
+
};
|
|
208
|
+
var CODEX_MCP_URL = "https://pushary.com/api/mcp/mcp";
|
|
209
|
+
var CODEX_HTTP_ONLY_FIELDS = ["url", "http_headers", "env_http_headers", "http_headers_helper", "bearer_token", "bearer_token_env_var", "auth", "oauth", "oauth_resource"];
|
|
210
|
+
var NATIVE_MCP_TRANSPORT_FIELDS = [...CODEX_HTTP_ONLY_FIELDS, "args", "env", "env_vars", "cwd"];
|
|
211
|
+
var OLD_CLI_HTTP_FIELDS = /* @__PURE__ */ new Set(["url", "http_headers"]);
|
|
212
|
+
var nativeCodexMcpCommand = () => join2(homedir2(), ".pushary", "bin", "pushary-mcp");
|
|
213
|
+
var isNativeCodexMcpEntry = (entry2, command) => {
|
|
214
|
+
const server = asRecord2(entry2);
|
|
215
|
+
return server?.command === command && NATIVE_MCP_TRANSPORT_FIELDS.every((field) => !(field in server));
|
|
216
|
+
};
|
|
217
|
+
var carriesOnlyAnAuthorizationHeader = (headers) => {
|
|
218
|
+
const record = asRecord2(headers);
|
|
219
|
+
return record !== void 0 && Object.keys(record).length === 1 && typeof record.Authorization === "string";
|
|
220
|
+
};
|
|
221
|
+
var isOldCliMixedCodexMcpEntry = (entry2, command) => {
|
|
222
|
+
const server = asRecord2(entry2);
|
|
223
|
+
return server?.command === command && server.url === CODEX_MCP_URL && carriesOnlyAnAuthorizationHeader(server.http_headers) && NATIVE_MCP_TRANSPORT_FIELDS.every((field) => OLD_CLI_HTTP_FIELDS.has(field) || !(field in server));
|
|
224
|
+
};
|
|
225
|
+
var codexMcpMixesCommandWithHttp = (entry2) => {
|
|
226
|
+
const server = asRecord2(entry2);
|
|
227
|
+
return server !== void 0 && "command" in server && CODEX_HTTP_ONLY_FIELDS.some((field) => field in server);
|
|
228
|
+
};
|
|
229
|
+
var CODEX_RESTART_AFTER_TRANSPORT_CHANGE = "Fully quit and reopen Codex to load the updated Pushary configuration.";
|
|
230
|
+
var codexMcpStartsCommand = (config) => {
|
|
231
|
+
const pushary = asRecord2(asRecord2(config.mcp_servers)?.pushary);
|
|
232
|
+
return pushary !== void 0 && "command" in pushary && pushary.enabled !== false;
|
|
233
|
+
};
|
|
234
|
+
var addCodexMcpServer = (config, apiKey, nativeOwnership = "preserve") => {
|
|
235
|
+
const existing = asRecord2(asRecord2(config.mcp_servers)?.pushary);
|
|
236
|
+
if (existing !== void 0 && "command" in existing) {
|
|
237
|
+
const command = nativeCodexMcpCommand();
|
|
238
|
+
if (!isOldCliMixedCodexMcpEntry(existing, command) && !isNativeCodexMcpEntry(existing, command)) {
|
|
239
|
+
throw new Error("Pushary MCP has an unrecognized or mixed transport. Its configuration was left unchanged.");
|
|
240
|
+
}
|
|
241
|
+
if (nativeOwnership === "preserve") {
|
|
242
|
+
for (const field of OLD_CLI_HTTP_FIELDS) delete existing[field];
|
|
243
|
+
return "kept-native";
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const servers = ensureRecord(config, "mcp_servers");
|
|
247
|
+
const pushary = ensureRecord(servers, "pushary");
|
|
248
|
+
delete pushary.command;
|
|
249
|
+
pushary.url = CODEX_MCP_URL;
|
|
250
|
+
ensureRecord(pushary, "http_headers").Authorization = `Bearer ${apiKey}`;
|
|
251
|
+
pushary.default_tools_approval_mode = "approve";
|
|
252
|
+
delete pushary.bearer_token_env_var;
|
|
253
|
+
delete pushary.tools;
|
|
254
|
+
return "wrote-key";
|
|
255
|
+
};
|
|
256
|
+
var readCodexMcpAuth = (config) => {
|
|
257
|
+
const pushary = asRecord2(asRecord2(config.mcp_servers)?.pushary);
|
|
258
|
+
if (!pushary) return void 0;
|
|
259
|
+
if ("command" in pushary) {
|
|
260
|
+
return isNativeCodexMcpEntry(pushary, nativeCodexMcpCommand()) ? { method: "native" } : void 0;
|
|
261
|
+
}
|
|
262
|
+
const authorization = asRecord2(pushary.http_headers)?.Authorization;
|
|
263
|
+
if (typeof authorization === "string" && authorization.startsWith("Bearer ")) {
|
|
264
|
+
return { method: "embedded", key: authorization.slice("Bearer ".length).trim() };
|
|
265
|
+
}
|
|
266
|
+
if (typeof pushary.bearer_token_env_var === "string") {
|
|
267
|
+
return { method: "env", envVar: pushary.bearer_token_env_var };
|
|
268
|
+
}
|
|
269
|
+
if (typeof pushary.bearer_token === "string") return { method: "legacy-bearer" };
|
|
270
|
+
return void 0;
|
|
271
|
+
};
|
|
272
|
+
var isPusharyCodexCommand = (command) => command.includes(CODEX_HOOK_BINARY) || command.includes(HOOK_LOCATOR_BINARY);
|
|
273
|
+
var isPusharyCommand = (hook, scope) => {
|
|
274
|
+
const command = String(asRecord2(hook)?.command ?? "");
|
|
275
|
+
if (isProtectedByScope(command, scope)) return false;
|
|
276
|
+
return isPusharyCodexCommand(command);
|
|
277
|
+
};
|
|
278
|
+
var isPusharyCodexHook = (entry2, scope) => {
|
|
279
|
+
const hooks = asRecord2(entry2)?.hooks;
|
|
280
|
+
return Array.isArray(hooks) && hooks.some((hook) => isPusharyCommand(hook, scope));
|
|
281
|
+
};
|
|
282
|
+
var CODEX_CLI_BINARIES = /* @__PURE__ */ new Set([CODEX_HOOK_BINARY]);
|
|
283
|
+
var codexRegistrationHolder = (hooksConfig) => registrationHolderOf(hooksConfig, CODEX_CLI_BINARIES);
|
|
284
|
+
var addCodexHooks = (config, command) => {
|
|
285
|
+
const hooks = ensureRecord(config, "hooks");
|
|
286
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
287
|
+
const existing = Array.isArray(hooks[definition.event]) ? hooks[definition.event] : [];
|
|
288
|
+
hooks[definition.event] = registerHookEntry(existing, isPusharyCodexHook, {
|
|
289
|
+
...definition.matcher ? { matcher: definition.matcher } : {},
|
|
290
|
+
hooks: [{
|
|
291
|
+
type: "command",
|
|
292
|
+
command: withHookHoldBudget(command, holdBudgetOf(definition)),
|
|
293
|
+
timeout: definition.timeout,
|
|
294
|
+
...definition.statusMessage ? { statusMessage: definition.statusMessage } : {}
|
|
295
|
+
}]
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
var removeCodexHooks = (config, scope) => {
|
|
300
|
+
const hooks = asRecord2(config.hooks);
|
|
301
|
+
if (!hooks) return false;
|
|
302
|
+
let changed = false;
|
|
303
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
304
|
+
const entries = hooks[definition.event];
|
|
305
|
+
if (!Array.isArray(entries)) continue;
|
|
306
|
+
const filtered = entries.filter((entry2) => !isPusharyCodexHook(entry2, scope));
|
|
307
|
+
if (filtered.length !== entries.length) {
|
|
308
|
+
if (filtered.length === 0) {
|
|
309
|
+
delete hooks[definition.event];
|
|
310
|
+
} else {
|
|
311
|
+
hooks[definition.event] = filtered;
|
|
312
|
+
}
|
|
313
|
+
changed = true;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (Object.keys(hooks).length === 0) delete config.hooks;
|
|
317
|
+
return changed;
|
|
318
|
+
};
|
|
319
|
+
var hasCodexHooks = (config) => {
|
|
320
|
+
const hooks = asRecord2(config.hooks);
|
|
321
|
+
if (!hooks) return false;
|
|
322
|
+
return CODEX_HOOK_EVENTS.some((definition) => {
|
|
323
|
+
const entries = hooks[definition.event];
|
|
324
|
+
return Array.isArray(entries) && entries.some((entry2) => isPusharyCodexHook(entry2));
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
var missingCodexHookEvents = (config) => {
|
|
328
|
+
const hooks = asRecord2(config.hooks);
|
|
329
|
+
return CODEX_HOOK_EVENTS.filter((definition) => {
|
|
330
|
+
const entries = hooks?.[definition.event];
|
|
331
|
+
return !Array.isArray(entries) || !entries.some((entry2) => isPusharyCodexHook(entry2));
|
|
332
|
+
}).map((definition) => definition.event);
|
|
333
|
+
};
|
|
334
|
+
var codexHookTrustHash = (definition, command) => {
|
|
335
|
+
const hook = { command: withHookHoldBudget(command, holdBudgetOf(definition)), timeout: definition.timeout, type: "command" };
|
|
336
|
+
if (definition.statusMessage) hook.statusMessage = definition.statusMessage;
|
|
337
|
+
const entry2 = { hooks: [hook] };
|
|
338
|
+
if (definition.matcher) entry2.matcher = definition.matcher;
|
|
339
|
+
return codexEntryTrustHash(CODEX_EVENT_KEY[definition.event], entry2);
|
|
340
|
+
};
|
|
341
|
+
var codexEntryTrustHash = (eventKey, entry2) => {
|
|
342
|
+
const hooks = Array.isArray(entry2.hooks) ? entry2.hooks : [];
|
|
343
|
+
const identity = {
|
|
344
|
+
event_name: eventKey,
|
|
345
|
+
hooks: hooks.map((hook) => ({ async: false, ...asRecord2(hook) }))
|
|
346
|
+
};
|
|
347
|
+
if (typeof entry2.matcher === "string") identity.matcher = entry2.matcher;
|
|
348
|
+
return "sha256:" + createHash("sha256").update(canonicalJson(identity), "utf8").digest("hex");
|
|
349
|
+
};
|
|
350
|
+
var codexHookPositions = (hooksConfig) => {
|
|
351
|
+
const hooks = asRecord2(hooksConfig?.hooks);
|
|
352
|
+
const positions = {};
|
|
353
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
354
|
+
const entries = hooks?.[definition.event];
|
|
355
|
+
if (!Array.isArray(entries)) continue;
|
|
356
|
+
const entryIndex = entries.findIndex((entry2) => isPusharyCodexHook(entry2));
|
|
357
|
+
if (entryIndex < 0) continue;
|
|
358
|
+
const inner = asRecord2(entries[entryIndex])?.hooks;
|
|
359
|
+
const hookIndex = Array.isArray(inner) ? inner.findIndex((hook) => isPusharyCommand(hook)) : -1;
|
|
360
|
+
positions[definition.event] = [entryIndex, Math.max(hookIndex, 0)];
|
|
361
|
+
}
|
|
362
|
+
return positions;
|
|
363
|
+
};
|
|
364
|
+
var codexHookStateKey = (hooksJsonPath, event, position) => `${hooksJsonPath}:${CODEX_EVENT_KEY[event]}:${position[0]}:${position[1]}`;
|
|
365
|
+
var codexVacatedIndexes = (hooksConfig) => {
|
|
366
|
+
const hooks = asRecord2(hooksConfig?.hooks);
|
|
367
|
+
const vacated = {};
|
|
368
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
369
|
+
const entries = hooks?.[definition.event];
|
|
370
|
+
if (!Array.isArray(entries)) continue;
|
|
371
|
+
const ours = entries.flatMap((entry2, index) => isPusharyCodexHook(entry2) ? [index] : []);
|
|
372
|
+
if (ours.length > 1) vacated[definition.event] = ours[1];
|
|
373
|
+
}
|
|
374
|
+
return vacated;
|
|
375
|
+
};
|
|
376
|
+
var dropVacatedCodexHookTrust = (config, hooksJsonPath, vacated) => {
|
|
377
|
+
const state = asRecord2(asRecord2(config.hooks)?.state);
|
|
378
|
+
if (!state) return false;
|
|
379
|
+
let changed = false;
|
|
380
|
+
for (const [event, from] of Object.entries(vacated)) {
|
|
381
|
+
const prefix = `${hooksJsonPath}:${CODEX_EVENT_KEY[event] ?? codexEventKeyOf(event)}:`;
|
|
382
|
+
for (const key of Object.keys(state)) {
|
|
383
|
+
if (!key.startsWith(prefix)) continue;
|
|
384
|
+
const entryIndex = Number(key.slice(prefix.length).split(":")[0]);
|
|
385
|
+
if (!Number.isInteger(entryIndex) || entryIndex < from) continue;
|
|
386
|
+
delete state[key];
|
|
387
|
+
changed = true;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return changed;
|
|
391
|
+
};
|
|
392
|
+
var captureCodexHookTrust = (config, hooksJsonPath, positions) => {
|
|
393
|
+
const state = asRecord2(asRecord2(config.hooks)?.state);
|
|
394
|
+
if (!state) return {};
|
|
395
|
+
const snapshot = {};
|
|
396
|
+
for (const [event, position] of Object.entries(positions)) {
|
|
397
|
+
const value = state[codexHookStateKey(hooksJsonPath, event, position)];
|
|
398
|
+
if (value !== void 0) snapshot[event] = structuredClone(value);
|
|
399
|
+
}
|
|
400
|
+
return snapshot;
|
|
401
|
+
};
|
|
402
|
+
var restoreCodexHookTrust = (config, hooksJsonPath, positions, snapshot) => {
|
|
403
|
+
const restorable = Object.entries(snapshot).filter(([event]) => positions[event] !== void 0);
|
|
404
|
+
if (restorable.length === 0) return false;
|
|
405
|
+
const state = ensureRecord(ensureRecord(config, "hooks"), "state");
|
|
406
|
+
for (const [event, value] of restorable) {
|
|
407
|
+
state[codexHookStateKey(hooksJsonPath, event, positions[event])] = structuredClone(value);
|
|
408
|
+
}
|
|
409
|
+
return true;
|
|
410
|
+
};
|
|
411
|
+
var codexTrustGaps = (config, hooksConfig, hooksJsonPath, command, positions) => {
|
|
412
|
+
const state = asRecord2(asRecord2(config.hooks)?.state);
|
|
413
|
+
const hooks = asRecord2(hooksConfig?.hooks);
|
|
414
|
+
const gaps = [];
|
|
415
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
416
|
+
const position = positions[definition.event];
|
|
417
|
+
if (!position) continue;
|
|
418
|
+
const hookState = asRecord2(state?.[codexHookStateKey(hooksJsonPath, definition.event, position)]);
|
|
419
|
+
if (definition.delivery === "approval" && hookState?.enabled === false) {
|
|
420
|
+
gaps.push({ event: definition.event, kind: "disabled" });
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const stored = hookState?.trusted_hash;
|
|
424
|
+
const eventKey = CODEX_EVENT_KEY[definition.event] ?? definition.event;
|
|
425
|
+
const onDisk = asRecord2(
|
|
426
|
+
asRecord2(hooks?.[definition.event]) ?? (Array.isArray(hooks?.[definition.event]) ? hooks[definition.event][position[0]] : void 0)
|
|
427
|
+
);
|
|
428
|
+
const handlers = onDisk?.hooks;
|
|
429
|
+
const handler = Array.isArray(handlers) ? asRecord2(handlers[position[1]]) : void 0;
|
|
430
|
+
if (definition.delivery === "approval" && handler?.async === true) {
|
|
431
|
+
gaps.push({ event: definition.event, kind: "asynchronous" });
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
const diskHash = onDisk ? codexEntryTrustHash(eventKey, onDisk) : void 0;
|
|
435
|
+
if (stored !== void 0 && diskHash !== void 0 && stored === diskHash) {
|
|
436
|
+
if (diskHash !== codexHookTrustHash(definition, command)) {
|
|
437
|
+
gaps.push({ event: definition.event, kind: "stale" });
|
|
438
|
+
}
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (stored !== (diskHash ?? codexHookTrustHash(definition, command))) {
|
|
442
|
+
gaps.push({ event: definition.event, kind: "untrusted" });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return gaps;
|
|
446
|
+
};
|
|
447
|
+
var removeCodexMcpServer = (config) => {
|
|
448
|
+
const servers = asRecord2(config.mcp_servers);
|
|
449
|
+
if (!servers?.pushary) return false;
|
|
450
|
+
delete servers.pushary;
|
|
451
|
+
if (Object.keys(servers).length === 0) delete config.mcp_servers;
|
|
452
|
+
return true;
|
|
453
|
+
};
|
|
454
|
+
var removeCodexHookTrust = (config, hooksJsonPath, positions) => {
|
|
455
|
+
const state = asRecord2(asRecord2(config.hooks)?.state);
|
|
456
|
+
if (!state) return false;
|
|
457
|
+
let changed = false;
|
|
458
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
459
|
+
const position = positions[definition.event];
|
|
460
|
+
if (!position) continue;
|
|
461
|
+
const key = codexHookStateKey(hooksJsonPath, definition.event, position);
|
|
462
|
+
if (key in state) {
|
|
463
|
+
delete state[key];
|
|
464
|
+
changed = true;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (changed && Object.keys(state).length === 0) {
|
|
468
|
+
const hooks = asRecord2(config.hooks);
|
|
469
|
+
if (hooks) {
|
|
470
|
+
delete hooks.state;
|
|
471
|
+
if (Object.keys(hooks).length === 0) delete config.hooks;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return changed;
|
|
475
|
+
};
|
|
476
|
+
var removeCodexSettings = (config, hooksJsonPath, positions) => {
|
|
477
|
+
const mcp = removeCodexMcpServer(config);
|
|
478
|
+
const trust = removeCodexHookTrust(config, hooksJsonPath, positions);
|
|
479
|
+
return mcp || trust;
|
|
480
|
+
};
|
|
481
|
+
var addCodexHookTrust = (config, hooksJsonPath, command, positions) => {
|
|
482
|
+
const hooks = ensureRecord(config, "hooks");
|
|
483
|
+
const state = ensureRecord(hooks, "state");
|
|
484
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
485
|
+
const position = positions[definition.event];
|
|
486
|
+
if (!position) continue;
|
|
487
|
+
const key = codexHookStateKey(hooksJsonPath, definition.event, position);
|
|
488
|
+
const ours = codexHookTrustHash(definition, command);
|
|
489
|
+
const existing = asRecord2(state[key]) ?? {};
|
|
490
|
+
state[key] = { ...existing, trusted_hash: ours };
|
|
491
|
+
for (const stale of staleTrustKeys(state, hooksJsonPath, definition.event, key, ours)) {
|
|
492
|
+
delete state[stale];
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
var staleTrustKeys = (state, hooksJsonPath, event, currentKey, ourHash) => {
|
|
497
|
+
const prefix = `${hooksJsonPath}:${CODEX_EVENT_KEY[event]}:`;
|
|
498
|
+
return Object.keys(state).filter(
|
|
499
|
+
(key) => key !== currentKey && key.startsWith(prefix) && asRecord2(state[key])?.trusted_hash === ourHash
|
|
500
|
+
);
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/cline-config.ts
|
|
504
|
+
import { join as join4 } from "path";
|
|
505
|
+
|
|
506
|
+
// src/claude-config.ts
|
|
507
|
+
import { join as join3 } from "path";
|
|
508
|
+
|
|
509
|
+
// src/claude-tools.ts
|
|
510
|
+
var POSTTOOLUSE_MIRROR_ONLY_TOOLS = ["TodoWrite", "Task", "WebFetch", "WebSearch"];
|
|
511
|
+
var POSTTOOLUSE_MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, ...POSTTOOLUSE_MIRROR_ONLY_TOOLS];
|
|
512
|
+
|
|
513
|
+
// src/claude-notifications.ts
|
|
514
|
+
var IDLE_PROMPT_NOTIFICATION = "idle_prompt";
|
|
515
|
+
var NOTIFICATION_MATCHED_TYPES = [
|
|
516
|
+
// Blocks. The agent, or one of its workers, is waiting on a person.
|
|
517
|
+
"permission_prompt",
|
|
518
|
+
"idle_prompt",
|
|
519
|
+
"agent_needs_input",
|
|
520
|
+
"worker_permission_prompt",
|
|
521
|
+
// An MCP server asking the person for a value. The Elicitation hook answers it;
|
|
522
|
+
// this is the feed row saying one is open.
|
|
523
|
+
"elicitation_dialog",
|
|
524
|
+
"elicitation_url_dialog",
|
|
525
|
+
// Turn boundary.
|
|
526
|
+
"agent_completed",
|
|
527
|
+
// Quota. The island already reads plan usage, and these say when a limit hit,
|
|
528
|
+
// went stale, or was turned off, which no polling of the usage endpoint reveals.
|
|
529
|
+
"quota_auto_resume_fired",
|
|
530
|
+
"quota_auto_resume_stale",
|
|
531
|
+
"quota_auto_resume_disabled"
|
|
532
|
+
];
|
|
533
|
+
var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set([
|
|
534
|
+
"idle_prompt",
|
|
535
|
+
"agent_needs_input",
|
|
536
|
+
"worker_permission_prompt"
|
|
537
|
+
]);
|
|
538
|
+
|
|
539
|
+
// src/claude-events.ts
|
|
540
|
+
var MIRRORED_TOOLS = POSTTOOLUSE_MIRRORED_TOOLS.join("|");
|
|
541
|
+
var ESTABLISHED = [1, 0, 0];
|
|
542
|
+
var OBSERVED = [2, 0, 0];
|
|
543
|
+
var CLAUDE_HOOK_BINARY = "pushary-hook";
|
|
544
|
+
var CLAUDE_HOOK_EVENTS = [
|
|
545
|
+
{ event: "PreToolUse", binary: CLAUDE_HOOK_BINARY, matcher: CLAUDE_PRETOOLUSE_MATCHER, timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
|
|
546
|
+
{ event: "PostToolUse", binary: "pushary-post-hook", matcher: MIRRORED_TOOLS, timeout: 10, delivery: "telemetry", since: ESTABLISHED },
|
|
547
|
+
{ event: "Stop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
|
|
548
|
+
{ event: "UserPromptSubmit", binary: "pushary-prompt-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
|
|
549
|
+
{ event: "Notification", binary: "pushary-notification-hook", matcher: NOTIFICATION_MATCHED_TYPES.join("|"), timeout: 10, delivery: "telemetry", since: ESTABLISHED },
|
|
550
|
+
{ event: "SessionStart", binary: "pushary-session-start-hook", matcher: "startup|resume|clear", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
|
|
551
|
+
{ event: "SessionEnd", binary: "pushary-session-end-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
|
|
552
|
+
{ event: "StopFailure", binary: "pushary-stopfailure-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
|
|
553
|
+
{ event: "PermissionRequest", binary: "pushary-permission-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
|
|
554
|
+
{ event: "PermissionDenied", binary: "pushary-permission-denied-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", holds: true, since: ESTABLISHED },
|
|
555
|
+
// Observability only. Each carries session shape the island cannot get any other
|
|
556
|
+
// way, and none of them decides anything.
|
|
557
|
+
{ event: "SubagentStart", binary: "pushary-session-start-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
|
|
558
|
+
{ event: "SubagentStop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
|
|
559
|
+
{ event: "PreCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, delivery: "telemetry", since: OBSERVED },
|
|
560
|
+
{ event: "PostCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, delivery: "telemetry", since: OBSERVED },
|
|
561
|
+
{ event: "TeammateIdle", binary: "pushary-notification-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
|
|
562
|
+
// An MCP server asking the person for a value, answered from the phone rather
|
|
563
|
+
// than the terminal. No matcher: Claude Code matches this event on the MCP
|
|
564
|
+
// server's name, and we have no business preferring one server over another.
|
|
565
|
+
{ event: "Elicitation", binary: "pushary-elicitation-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: OBSERVED }
|
|
566
|
+
];
|
|
567
|
+
var supportedClaudeEvents = (version) => CLAUDE_HOOK_EVENTS.filter((hook) => version ? atLeast(version, hook.since) : hook.since === ESTABLISHED);
|
|
568
|
+
var registrationSignatures = (events, version) => Object.fromEntries(
|
|
569
|
+
events.map((hook) => [hook.event, `registered|${version ? formatVersion(version) : "unknown"}`])
|
|
570
|
+
);
|
|
571
|
+
var needsReregistration = (stored, version) => {
|
|
572
|
+
const expected = registrationSignatures(supportedClaudeEvents(version), version);
|
|
573
|
+
const keys = Object.keys(expected);
|
|
574
|
+
if (!stored) return true;
|
|
575
|
+
if (keys.length !== Object.keys(stored).length) return true;
|
|
576
|
+
return keys.some((event) => stored[event] !== expected[event]);
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// src/claude-config.ts
|
|
580
|
+
import { isDeepStrictEqual } from "util";
|
|
581
|
+
var PUSHARY_MCP_URL = "https://pushary.com/api/mcp/mcp";
|
|
582
|
+
var PUSHARY_PERMISSION_RULE = "mcp__pushary__*";
|
|
583
|
+
var asRecord3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
584
|
+
var ensureRecord2 = (target, key) => {
|
|
585
|
+
const existing = asRecord3(target[key]);
|
|
586
|
+
if (existing) return existing;
|
|
587
|
+
const created = {};
|
|
588
|
+
target[key] = created;
|
|
589
|
+
return created;
|
|
590
|
+
};
|
|
591
|
+
var isPusharyPermission = (rule) => typeof rule === "string" && (rule.includes("pushary") || rule.includes("MCP(pushary"));
|
|
592
|
+
var HOOK_BINARIES = /* @__PURE__ */ new Set([
|
|
593
|
+
...CLAUDE_HOOK_EVENTS.map((hook) => hook.binary),
|
|
594
|
+
HOOK_LOCATOR_BINARY
|
|
595
|
+
]);
|
|
596
|
+
var namesPusharyBinary = (command) => command.split(/[\s/\\"']+/).some((segment) => HOOK_BINARIES.has(segment.replace(/\.(cmd|exe)$/i, "")));
|
|
597
|
+
var isPusharyHook = (entry2, scope) => {
|
|
598
|
+
const hooks = asRecord3(entry2)?.hooks;
|
|
599
|
+
if (!Array.isArray(hooks)) return false;
|
|
600
|
+
return hooks.some((hook) => {
|
|
601
|
+
const command = String(asRecord3(hook)?.command ?? "");
|
|
602
|
+
if (isProtectedByScope(command, scope)) return false;
|
|
603
|
+
return namesPusharyBinary(command);
|
|
604
|
+
});
|
|
605
|
+
};
|
|
606
|
+
var pusharyMcpServer = (apiKey) => ({
|
|
607
|
+
type: "http",
|
|
608
|
+
url: PUSHARY_MCP_URL,
|
|
609
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
610
|
+
});
|
|
611
|
+
var addClaudeMcpServer = (config, apiKey) => {
|
|
612
|
+
const mcpServers = ensureRecord2(config, "mcpServers");
|
|
613
|
+
mcpServers.pushary = pusharyMcpServer(apiKey);
|
|
614
|
+
};
|
|
615
|
+
var isClaudeMcpSnapshot = (value) => {
|
|
616
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
617
|
+
const snapshot = value;
|
|
618
|
+
return typeof snapshot.hadMcpServers === "boolean" && typeof snapshot.mcpServersWasRecord === "boolean" && (!snapshot.hadMcpServers || snapshot.mcpServersWasRecord || Object.hasOwn(snapshot, "mcpServers")) && typeof snapshot.hadPushary === "boolean" && (!snapshot.hadPushary || Object.hasOwn(snapshot, "pushary")) && (!Object.hasOwn(snapshot, "installedPushary") || asRecord3(snapshot.installedPushary)?.type === "http" && asRecord3(snapshot.installedPushary)?.url === PUSHARY_MCP_URL);
|
|
619
|
+
};
|
|
620
|
+
var captureClaudeMcpServer = (config, apiKey) => {
|
|
621
|
+
const mcpServers = asRecord3(config.mcpServers);
|
|
622
|
+
const hadMcpServers = Object.hasOwn(config, "mcpServers");
|
|
623
|
+
return {
|
|
624
|
+
hadMcpServers,
|
|
625
|
+
mcpServersWasRecord: Boolean(mcpServers),
|
|
626
|
+
...hadMcpServers && !mcpServers ? { mcpServers: structuredClone(config.mcpServers) } : {},
|
|
627
|
+
hadPushary: Boolean(mcpServers && Object.hasOwn(mcpServers, "pushary")),
|
|
628
|
+
...mcpServers && Object.hasOwn(mcpServers, "pushary") ? { pushary: structuredClone(mcpServers.pushary) } : {},
|
|
629
|
+
...apiKey ? { installedPushary: pusharyMcpServer(apiKey) } : {}
|
|
630
|
+
};
|
|
631
|
+
};
|
|
632
|
+
var updateClaudeMcpSnapshot = (snapshot, apiKey) => ({
|
|
633
|
+
...snapshot,
|
|
634
|
+
installedPushary: pusharyMcpServer(apiKey)
|
|
635
|
+
});
|
|
636
|
+
var restoreClaudeMcpServer = (config, snapshot) => {
|
|
637
|
+
const mcpServers = asRecord3(config.mcpServers);
|
|
638
|
+
const current = asRecord3(mcpServers?.pushary);
|
|
639
|
+
if (Object.hasOwn(snapshot, "installedPushary")) {
|
|
640
|
+
if (!isDeepStrictEqual(mcpServers?.pushary, snapshot.installedPushary)) return false;
|
|
641
|
+
} else if (current?.type !== "http" || current.url !== PUSHARY_MCP_URL) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
if (!snapshot.mcpServersWasRecord) {
|
|
645
|
+
if (Object.keys(mcpServers).length === 1) {
|
|
646
|
+
if (snapshot.hadMcpServers) config.mcpServers = structuredClone(snapshot.mcpServers);
|
|
647
|
+
else delete config.mcpServers;
|
|
648
|
+
} else {
|
|
649
|
+
delete mcpServers.pushary;
|
|
650
|
+
}
|
|
651
|
+
} else if (snapshot.hadPushary) {
|
|
652
|
+
mcpServers.pushary = structuredClone(snapshot.pushary);
|
|
653
|
+
} else {
|
|
654
|
+
delete mcpServers.pushary;
|
|
655
|
+
if (!snapshot.hadMcpServers && Object.keys(mcpServers).length === 0) delete config.mcpServers;
|
|
656
|
+
}
|
|
657
|
+
return true;
|
|
658
|
+
};
|
|
659
|
+
var removeClaudeMcpServers = (config) => {
|
|
660
|
+
let changed = false;
|
|
661
|
+
const removeFrom = (target) => {
|
|
662
|
+
const mcpServers = asRecord3(target.mcpServers);
|
|
663
|
+
if (!mcpServers?.pushary) return;
|
|
664
|
+
delete mcpServers.pushary;
|
|
665
|
+
if (Object.keys(mcpServers).length === 0) delete target.mcpServers;
|
|
666
|
+
changed = true;
|
|
667
|
+
};
|
|
668
|
+
removeFrom(config);
|
|
669
|
+
const projects = asRecord3(config.projects);
|
|
670
|
+
if (projects) {
|
|
671
|
+
for (const project of Object.values(projects)) {
|
|
672
|
+
const projectConfig = asRecord3(project);
|
|
673
|
+
if (projectConfig) removeFrom(projectConfig);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return changed;
|
|
677
|
+
};
|
|
678
|
+
var removePusharyToolPermissions = (settings) => {
|
|
679
|
+
const permissions = asRecord3(settings.permissions);
|
|
680
|
+
if (!permissions || !Array.isArray(permissions.allow)) return false;
|
|
681
|
+
const filtered = permissions.allow.filter((rule) => !isPusharyPermission(rule));
|
|
682
|
+
if (filtered.length === permissions.allow.length) return false;
|
|
683
|
+
if (filtered.length === 0) delete permissions.allow;
|
|
684
|
+
else permissions.allow = filtered;
|
|
685
|
+
if (Object.keys(permissions).length === 0) delete settings.permissions;
|
|
686
|
+
return true;
|
|
687
|
+
};
|
|
688
|
+
var addPusharyToolPermissions = (settings) => {
|
|
689
|
+
const permissions = ensureRecord2(settings, "permissions");
|
|
690
|
+
const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
|
|
691
|
+
const filtered = allow.filter((rule) => !isPusharyPermission(rule));
|
|
692
|
+
if (!filtered.includes(PUSHARY_PERMISSION_RULE)) {
|
|
693
|
+
filtered.push(PUSHARY_PERMISSION_RULE);
|
|
694
|
+
}
|
|
695
|
+
permissions.allow = filtered;
|
|
696
|
+
};
|
|
697
|
+
var addPusharyHooks = (settings, binDir, version = null) => {
|
|
698
|
+
const resolve2 = (name) => guardBinary(binDir ? join3(binDir, name) : name);
|
|
699
|
+
const hooks = ensureRecord2(settings, "hooks");
|
|
700
|
+
const supported = new Map(supportedClaudeEvents(version).map((hook) => [hook.event, hook]));
|
|
701
|
+
for (const known of CLAUDE_HOOK_EVENTS) {
|
|
702
|
+
const current = Array.isArray(hooks[known.event]) ? hooks[known.event] : [];
|
|
703
|
+
const definition = supported.get(known.event);
|
|
704
|
+
if (definition) {
|
|
705
|
+
hooks[known.event] = registerHookEntry(current, isPusharyHook, entry(definition, resolve2));
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
const others = current.filter((item) => !isPusharyHook(item));
|
|
709
|
+
if (others.length > 0) {
|
|
710
|
+
hooks[known.event] = others;
|
|
711
|
+
} else {
|
|
712
|
+
delete hooks[known.event];
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
var entry = (definition, resolve2) => ({
|
|
717
|
+
...definition.matcher ? { matcher: definition.matcher } : {},
|
|
718
|
+
hooks: [{
|
|
719
|
+
type: "command",
|
|
720
|
+
command: withHookHoldBudget(resolve2(definition.binary), holdBudgetOf(definition)),
|
|
721
|
+
timeout: definition.timeout
|
|
722
|
+
}]
|
|
723
|
+
});
|
|
724
|
+
var removePusharySettings = (settings, scope) => {
|
|
725
|
+
let changed = scope?.keepMcpServer === true ? false : removeClaudeMcpServers(settings);
|
|
726
|
+
if (scope?.keepMcpServer !== true && removePusharyToolPermissions(settings)) changed = true;
|
|
727
|
+
const hooks = asRecord3(settings.hooks);
|
|
728
|
+
if (hooks) {
|
|
729
|
+
for (const { event: key } of CLAUDE_HOOK_EVENTS) {
|
|
730
|
+
const entries = hooks[key];
|
|
731
|
+
if (!Array.isArray(entries)) continue;
|
|
732
|
+
const filtered = entries.filter((entry2) => !isPusharyHook(entry2, scope));
|
|
733
|
+
if (filtered.length !== entries.length) {
|
|
734
|
+
if (filtered.length === 0) {
|
|
735
|
+
delete hooks[key];
|
|
736
|
+
} else {
|
|
737
|
+
hooks[key] = filtered;
|
|
738
|
+
}
|
|
739
|
+
changed = true;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
743
|
+
}
|
|
744
|
+
return changed;
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
// src/instruction-file.ts
|
|
748
|
+
import { accessSync, constants, existsSync, readFileSync, statSync, writeFileSync, mkdirSync, rmSync } from "fs";
|
|
749
|
+
import { dirname } from "path";
|
|
750
|
+
var PUSHARY_BEGIN = "<!-- BEGIN PUSHARY (managed by @pushary/agent-hooks; do not edit this block) -->";
|
|
751
|
+
var PUSHARY_END = "<!-- END PUSHARY -->";
|
|
752
|
+
var instructionBody = (label) => `## Pushary notifications and approvals
|
|
753
|
+
|
|
754
|
+
You have Pushary MCP tools that reach the user on their phone. Use them proactively. Do not wait for the user to ask.
|
|
755
|
+
|
|
756
|
+
- When you need the user to make a decision or answer a clarifying question, call \`ask_user\` instead of guessing or stalling. Use type "confirm" for yes/no, "select" for a fixed set of options, and "input" for free text. Read \`answered\`, \`status\` and the returned handoff instead of assuming the tool blocked. Silence is not consent; choice/text answers do not authorize a separate action.
|
|
757
|
+
- Ask once, on one channel. If you also have a built-in way to prompt the user, use that or \`ask_user\` for a given question, never both. \`ask_user\` is the one that reaches the user when they are away from this machine, so prefer it. Raising both strands the user in whichever prompt you are not waiting on.
|
|
758
|
+
- Honor authorization already granted in this session. Ask before a risky or irreversible action outside that authorization, or when an enforced host policy requires it. Do not ask again for an already authorized step, and never bypass an enforced gate. When a Pushary hook result says the user approved a specific command after a denial, rerunning that exact command is the approved path and not a bypass of that gate.
|
|
759
|
+
- Call \`propose_scope\` only for an unresolved or requested file boundary. A ratified scope narrows what may proceed; it does not bypass other approval rules. Do not add a plan approval merely because work spans several files, and do not propose a new scope mid-run just to widen it. Let the installed gate request any scope expansion before the edit executes. Only file paths are checked, and only on tool calls that carry one, so put a boundary that is not a path (recipients, channels, spend, systems you will not open) in \`promises\` and never in \`allowedPaths\`. Read the \`enforces\` field that comes back: an empty array means nothing in that contract is checked automatically, and you must say so rather than report that a scope is in force. \`hookSeen: false\` means no hook has reported that sessionId, so the gate will never find the contract: fix the session id rather than proceeding as if a scope were in force.
|
|
760
|
+
- Before a task of more than two steps, break it into steps and find the forks. Settle by yourself every fork that a file, a command or a tool call can answer, then ask the rest as one numbered round with your recommended answer against each. A question in the terminal is free; a push is not. When the user is at the terminal, ask the whole round there. When the user is away, send only the fork that stops the run, and decide the rest on your recommendation.
|
|
761
|
+
- When you are blocked, stuck, or hit an error you cannot resolve on your own, call \`send_notification\` so the user knows, and call \`ask_user\` if you need a decision to continue.
|
|
762
|
+
- When meaningful work finishes while the user is away, or they requested an alert, call \`send_notification\` so the user knows it is done. Skip trivial completions and duplicate reports to someone already reading. Always pass \`context\` with a \`type\` of "task_complete", "error" or "info": it is what tells Pushary this is a task update, so the user's own setting for where updates land can route it.
|
|
763
|
+
- Confirm notifications can offer lock-screen actions; arbitrary choices and text open the app. The Mac notch handles personal account questions, not Partner customer enrollment.
|
|
764
|
+
- Use \`cancel_question\` to retract a pending question once it is no longer needed.
|
|
765
|
+
- When the user asks to be reminded later, call \`schedule_reminder\` with \`body\` and either \`inMinutes\` or \`at\` with an explicit UTC offset. Confirm the returned time, never guess their timezone, and do not use \`ask_user\` as a reminder. Pushary saves it and delivers after this agent exits. Call with no arguments to list pending reminders or \`cancelReminderId\` to cancel one before dispatch starts.
|
|
766
|
+
- Respect the user's Pushary delivery mode: it decides where an approval is surfaced (phone, terminal, or awareness only), not whether it is required. In Terminal mode do not expect a phone answer; the approval waits in the terminal. If the Pushary hook already gated an action, a single approval is enough, do not ask again.
|
|
767
|
+
|
|
768
|
+
Pass \`agentName\` as "${label} - <project folder name>" so the user knows which session is asking. If a question times out, follow \`handoffAction\` when present, otherwise \`nextAction\`: keep waiting with the same correlationId while pending. A poll ending is not question expiry. Cancel only for an explicit handoff or when the answer is no longer needed. If cancellation returns \`handoffAction: "stop"\`, stop. Otherwise, if cancellation returns false, poll once for 1 second and honor the answer that won the race.`;
|
|
769
|
+
var SHARED_AGENTS_MD_LABEL = "<your agent's name, such as Codex or Hermes>";
|
|
770
|
+
var CODEX_INSTRUCTIONS_LABEL = "Codex";
|
|
771
|
+
var GEMINI_INSTRUCTIONS_LABEL = "Gemini";
|
|
772
|
+
var OPENCODE_INSTRUCTIONS_LABEL = "OpenCode";
|
|
773
|
+
var renderAgentInstructions = (label) => instructionBody(label);
|
|
774
|
+
var KEYLESS_FALLBACK_LINE = "This repo uses Pushary phone approvals. Your teammates get agent questions on their lock screens; you do not have a key yet, so questions will fall back to the terminal. Get your own key (3-day trial): https://pushary.com/sign-up?utm_source=agents-md&utm_medium=teammate";
|
|
775
|
+
var renderProjectAgentInstructions = (label) => `${instructionBody(label)}
|
|
776
|
+
|
|
777
|
+
If this environment has no Pushary API key (no PUSHARY_API_KEY and no key in ~/.pushary/config.json), the tools above are unavailable in this session. In that case, treat the following as a statement of fact about this session and continue in the terminal: ${KEYLESS_FALLBACK_LINE}`;
|
|
778
|
+
var wrap = (body) => `${PUSHARY_BEGIN}
|
|
779
|
+
${body}
|
|
780
|
+
${PUSHARY_END}`;
|
|
781
|
+
var withInstructionBlockAppended = (existing, body) => {
|
|
782
|
+
const prefix = existing.trim() ? existing.replace(/\s*$/, "") + "\n\n" : "";
|
|
783
|
+
return prefix + wrap(body) + "\n";
|
|
784
|
+
};
|
|
785
|
+
var managedBlockRange = (text) => {
|
|
786
|
+
const start = text.indexOf(PUSHARY_BEGIN);
|
|
787
|
+
const end = text.indexOf(PUSHARY_END);
|
|
788
|
+
return start !== -1 && end > start ? { start, end: end + PUSHARY_END.length } : null;
|
|
789
|
+
};
|
|
790
|
+
var withInstructionBlockReplaced = (existing, body) => {
|
|
791
|
+
const range = managedBlockRange(existing);
|
|
792
|
+
return range ? existing.slice(0, range.start) + wrap(body) + existing.slice(range.end) : null;
|
|
793
|
+
};
|
|
794
|
+
var writeInstructionBlock = (filePath, body) => {
|
|
795
|
+
const dir = dirname(filePath);
|
|
796
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
797
|
+
let existing = "";
|
|
798
|
+
try {
|
|
799
|
+
existing = readFileSync(filePath, "utf-8");
|
|
800
|
+
} catch {
|
|
801
|
+
}
|
|
802
|
+
writeFileSync(filePath, withInstructionBlockReplaced(existing, body) ?? withInstructionBlockAppended(existing, body), "utf-8");
|
|
803
|
+
};
|
|
804
|
+
var occurrencesOf = (marker, text) => text.split(marker).length - 1;
|
|
805
|
+
var soleManagedBlock = (text) => {
|
|
806
|
+
const begins = occurrencesOf(PUSHARY_BEGIN, text);
|
|
807
|
+
const ends = occurrencesOf(PUSHARY_END, text);
|
|
808
|
+
if (begins === 0 && ends === 0) return { kind: "none" };
|
|
809
|
+
const range = managedBlockRange(text);
|
|
810
|
+
return begins === 1 && ends === 1 && range ? { kind: "one", range } : { kind: "ambiguous" };
|
|
811
|
+
};
|
|
812
|
+
var CRLF = "\r\n";
|
|
813
|
+
var withLineEndingsOf = (sample, block) => sample.includes(CRLF) ? block.replaceAll("\n", CRLF) : block;
|
|
814
|
+
var readUtf8 = (filePath) => {
|
|
815
|
+
try {
|
|
816
|
+
const bytes = readFileSync(filePath);
|
|
817
|
+
const text = bytes.toString("utf-8");
|
|
818
|
+
return Buffer.from(text, "utf-8").equals(bytes) ? { kind: "text", text } : { kind: "not_utf8" };
|
|
819
|
+
} catch {
|
|
820
|
+
return { kind: "unreadable" };
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
var isWritable = (filePath) => {
|
|
824
|
+
try {
|
|
825
|
+
accessSync(filePath, constants.W_OK);
|
|
826
|
+
return true;
|
|
827
|
+
} catch {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
var refreshInstructionBlock = (filePath, body) => {
|
|
832
|
+
if (!existsSync(filePath)) return "absent";
|
|
833
|
+
const read = readUtf8(filePath);
|
|
834
|
+
if (read.kind !== "text") return read.kind;
|
|
835
|
+
const block = soleManagedBlock(read.text);
|
|
836
|
+
if (block.kind === "none") return "absent";
|
|
837
|
+
if (block.kind === "ambiguous") return "ambiguous_markers";
|
|
838
|
+
const existing = read.text.slice(block.range.start, block.range.end);
|
|
839
|
+
const fresh = withLineEndingsOf(existing, wrap(body));
|
|
840
|
+
if (existing === fresh) return "current";
|
|
841
|
+
if (!isWritable(filePath)) return "read_only";
|
|
842
|
+
try {
|
|
843
|
+
const refreshed = read.text.slice(0, block.range.start) + fresh + read.text.slice(block.range.end);
|
|
844
|
+
writeFileAtomic(filePath, refreshed, statSync(filePath).mode & 511);
|
|
845
|
+
return "refreshed";
|
|
846
|
+
} catch {
|
|
847
|
+
return "write_failed";
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
var removeInstructionBlock = (filePath) => {
|
|
851
|
+
let existing = "";
|
|
852
|
+
try {
|
|
853
|
+
existing = readFileSync(filePath, "utf-8");
|
|
854
|
+
} catch {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
const start = existing.indexOf(PUSHARY_BEGIN);
|
|
858
|
+
const end = existing.indexOf(PUSHARY_END);
|
|
859
|
+
if (start === -1 || end === -1 || end < start) return false;
|
|
860
|
+
const remaining = (existing.slice(0, start) + existing.slice(end + PUSHARY_END.length)).trim();
|
|
861
|
+
if (remaining === "") {
|
|
862
|
+
rmSync(filePath, { force: true });
|
|
863
|
+
} else {
|
|
864
|
+
writeFileSync(filePath, remaining + "\n", "utf-8");
|
|
865
|
+
}
|
|
866
|
+
return true;
|
|
867
|
+
};
|
|
868
|
+
var hasInstructionBlock = (filePath) => {
|
|
869
|
+
try {
|
|
870
|
+
return readFileSync(filePath, "utf-8").includes(PUSHARY_BEGIN);
|
|
871
|
+
} catch {
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
// src/cline-config.ts
|
|
877
|
+
var CLINE_LABEL = "Cline";
|
|
878
|
+
var CLINE_SERVER_NAME = "pushary";
|
|
879
|
+
var CLINE_MCP_TIMEOUT_SECONDS = 90;
|
|
880
|
+
var CLINE_SETTINGS_FILE_NAME = "cline_mcp_settings.json";
|
|
881
|
+
var CLINE_RULES_FILE_NAME = "pushary.md";
|
|
882
|
+
var CLINE_DIRECTORY_NAME = ".cline";
|
|
883
|
+
var CLINE_DATA_DIRECTORY_NAME = "data";
|
|
884
|
+
var CLINE_SETTINGS_DIRECTORY_NAME = "settings";
|
|
885
|
+
var CLINE_RULES_DIRECTORY_NAME = "rules";
|
|
886
|
+
var CLINE_DIR_ENV = "CLINE_DIR";
|
|
887
|
+
var CLINE_DATA_DIR_ENV = "CLINE_DATA_DIR";
|
|
888
|
+
var CLINE_MCP_SETTINGS_PATH_ENV = "CLINE_MCP_SETTINGS_PATH";
|
|
889
|
+
var STREAMABLE_HTTP_TRANSPORT = "streamableHttp";
|
|
890
|
+
var STDIO_TRANSPORT = "stdio";
|
|
891
|
+
var AUTHORIZATION_HEADER = "Authorization";
|
|
892
|
+
var PUSHARY_KEY_BEARER_PREFIX = "Bearer pk_";
|
|
893
|
+
var TRANSPORT_FIELDS = /* @__PURE__ */ new Set(["transport", "type", "transportType", "url", "headers", "command", "args", "cwd", "env"]);
|
|
894
|
+
var CONNECTION_SWITCH_FIELD = "disabled";
|
|
895
|
+
var PUSHARY_MCP_HOSTS = /* @__PURE__ */ new Set(["pushary.com", "www.pushary.com"]);
|
|
896
|
+
var PUSHARY_MCP_PATH_PREFIX = "/api/mcp";
|
|
897
|
+
var CLINE_AUTO_APPROVE_STEP = 'In Cline, open Auto-approve under the chat box and tick "Use MCP servers", so Pushary calls run without waiting for a click in VS Code. That setting covers every MCP server in Cline, not only Pushary.';
|
|
898
|
+
var CLINE_RELOAD_STEP = "Reload VS Code (Developer: Reload Window) or restart the Cline CLI so Cline loads the Pushary server.";
|
|
899
|
+
var CLINE_TOOL_NOTE = `In Cline, these tools come from the MCP server named "${CLINE_SERVER_NAME}". Cline's own question tool waits in VS Code and never reaches the user's phone, so when the user may be away, ask through Pushary's \`ask_user\` instead. Cline cannot tell whether the user is watching VS Code; Pushary's delivery mode decides where a notification lands, so call \`send_notification\` with context type "task_complete" when a task of several steps finishes.`;
|
|
900
|
+
var CLINE_SETTINGS_REFUSALS = {
|
|
901
|
+
unparseable: "is not valid JSON, which Cline cannot read either",
|
|
902
|
+
"root-not-object": "is not a JSON object",
|
|
903
|
+
"servers-not-object": 'has an "mcpServers" value that is not an object'
|
|
904
|
+
};
|
|
905
|
+
var CLINE_SERVER_NAME_TAKEN = `already has a server named "${CLINE_SERVER_NAME}" that is not Pushary`;
|
|
906
|
+
var isAuthorizationHeader = ([name]) => name.toLowerCase() === AUTHORIZATION_HEADER.toLowerCase();
|
|
907
|
+
var asRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
908
|
+
var environmentValue = (environment, name) => {
|
|
909
|
+
const value = environment[name]?.trim();
|
|
910
|
+
return value ? value : void 0;
|
|
911
|
+
};
|
|
912
|
+
var clineDirectory = (location) => environmentValue(location.environment, CLINE_DIR_ENV) ?? join4(location.home, CLINE_DIRECTORY_NAME);
|
|
913
|
+
var clineDataDirectory = (location) => environmentValue(location.environment, CLINE_DATA_DIR_ENV) ?? join4(clineDirectory(location), CLINE_DATA_DIRECTORY_NAME);
|
|
914
|
+
var clineMcpSettingsPath = (location) => environmentValue(location.environment, CLINE_MCP_SETTINGS_PATH_ENV) ?? join4(clineDataDirectory(location), CLINE_SETTINGS_DIRECTORY_NAME, CLINE_SETTINGS_FILE_NAME);
|
|
915
|
+
var clineRulesFile = (location) => join4(clineDirectory(location), CLINE_RULES_DIRECTORY_NAME, CLINE_RULES_FILE_NAME);
|
|
916
|
+
var parsedJson = (text) => {
|
|
917
|
+
try {
|
|
918
|
+
return { ok: true, value: JSON.parse(text) };
|
|
919
|
+
} catch {
|
|
920
|
+
return { ok: false };
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
var settingsTextMentionsPushary = (text) => [...PUSHARY_MCP_HOSTS].some((host) => text.includes(host)) || text.includes(PUSHARY_KEY_BEARER_PREFIX);
|
|
924
|
+
var refusedSettings = (reason, text) => ({ kind: "refused", reason, mentionsPushary: settingsTextMentionsPushary(text) });
|
|
925
|
+
var readClineSettings = (text) => {
|
|
926
|
+
if (!text.trim()) return { kind: "settings", settings: {} };
|
|
927
|
+
const parsed = parsedJson(text);
|
|
928
|
+
if (!parsed.ok) return refusedSettings("unparseable", text);
|
|
929
|
+
const settings = asRecord4(parsed.value);
|
|
930
|
+
if (!settings) return refusedSettings("root-not-object", text);
|
|
931
|
+
if (settings.mcpServers !== void 0 && !asRecord4(settings.mcpServers)) return refusedSettings("servers-not-object", text);
|
|
932
|
+
return { kind: "settings", settings };
|
|
933
|
+
};
|
|
934
|
+
var serversOf = (settings) => asRecord4(settings.mcpServers) ?? {};
|
|
935
|
+
var transportOf = (entry2) => {
|
|
936
|
+
const record = asRecord4(entry2);
|
|
937
|
+
return asRecord4(record?.transport) ?? record;
|
|
938
|
+
};
|
|
939
|
+
var isPusharyMcpUrl = (value) => {
|
|
940
|
+
if (typeof value !== "string") return false;
|
|
941
|
+
try {
|
|
942
|
+
const url = new URL(value);
|
|
943
|
+
return PUSHARY_MCP_HOSTS.has(url.hostname) && (url.pathname === PUSHARY_MCP_PATH_PREFIX || url.pathname.startsWith(`${PUSHARY_MCP_PATH_PREFIX}/`));
|
|
944
|
+
} catch {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
var authorizationBearer = (apiKey) => `Bearer ${apiKey}`;
|
|
949
|
+
var clinePusharyEntry = (apiKey) => ({
|
|
950
|
+
transport: {
|
|
951
|
+
type: STREAMABLE_HTTP_TRANSPORT,
|
|
952
|
+
url: PUSHARY_MCP_URL,
|
|
953
|
+
headers: { [AUTHORIZATION_HEADER]: authorizationBearer(apiKey) }
|
|
954
|
+
},
|
|
955
|
+
timeout: CLINE_MCP_TIMEOUT_SECONDS
|
|
956
|
+
});
|
|
957
|
+
var commandArgumentsOf = (transport) => {
|
|
958
|
+
const args = transport?.args;
|
|
959
|
+
return Array.isArray(args) ? args : [];
|
|
960
|
+
};
|
|
961
|
+
var declaredTransportType = (transport) => transport?.type ?? transport?.transportType;
|
|
962
|
+
var clineEntryRunsCommand = (entry2) => {
|
|
963
|
+
const transport = transportOf(entry2);
|
|
964
|
+
const declared = declaredTransportType(transport);
|
|
965
|
+
return typeof transport?.command === "string" && (declared === void 0 || declared === STDIO_TRANSPORT);
|
|
966
|
+
};
|
|
967
|
+
var reachesPusharyThroughCommand = (entry2) => clineEntryRunsCommand(entry2) && commandArgumentsOf(transportOf(entry2)).some(isPusharyMcpUrl);
|
|
968
|
+
var isClinePusharyEntry = (entry2) => isPusharyMcpUrl(transportOf(entry2)?.url) || reachesPusharyThroughCommand(entry2);
|
|
969
|
+
var isKeptOnConnect = ([field]) => !TRANSPORT_FIELDS.has(field) && field !== CONNECTION_SWITCH_FIELD;
|
|
970
|
+
var isNotAuthorizationHeader = (header) => !isAuthorizationHeader(header);
|
|
971
|
+
var headersWithoutAuthorization = (headers) => Object.fromEntries(Object.entries(asRecord4(headers) ?? {}).filter(isNotAuthorizationHeader));
|
|
972
|
+
var connectedClinePusharyEntry = (existing, apiKey) => {
|
|
973
|
+
const ours = isClinePusharyEntry(existing) ? asRecord4(existing) ?? {} : {};
|
|
974
|
+
const fresh = clinePusharyEntry(apiKey);
|
|
975
|
+
return {
|
|
976
|
+
timeout: fresh.timeout,
|
|
977
|
+
...Object.fromEntries(Object.entries(ours).filter(isKeptOnConnect)),
|
|
978
|
+
transport: {
|
|
979
|
+
...fresh.transport,
|
|
980
|
+
headers: { ...headersWithoutAuthorization(transportOf(ours)?.headers), ...fresh.transport.headers }
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
};
|
|
984
|
+
var clinePusharyEntryOf = (settings) => serversOf(settings)[CLINE_SERVER_NAME];
|
|
985
|
+
var clineEntryAuthorization = (entry2) => {
|
|
986
|
+
const headers = asRecord4(transportOf(entry2)?.headers);
|
|
987
|
+
const value = headers ? Object.entries(headers).find(isAuthorizationHeader)?.[1] : void 0;
|
|
988
|
+
return typeof value === "string" ? value : void 0;
|
|
989
|
+
};
|
|
990
|
+
var clineEntryUsesKey = (entry2, apiKey) => isClinePusharyEntry(entry2) && clineEntryAuthorization(entry2) === authorizationBearer(apiKey);
|
|
991
|
+
var clineEntryHoldsLiteralKey = (entry2) => clineEntryAuthorization(entry2)?.startsWith(PUSHARY_KEY_BEARER_PREFIX) === true;
|
|
992
|
+
var clineEntrySignedIn = (entry2) => asRecord4(asRecord4(asRecord4(entry2)?.oauth)?.tokens) !== void 0;
|
|
993
|
+
var clineEntrySwitchedOff = (entry2) => asRecord4(entry2)?.[CONNECTION_SWITCH_FIELD] === true;
|
|
994
|
+
var clineServerNameTakenElsewhere = (settings) => {
|
|
995
|
+
const entry2 = serversOf(settings)[CLINE_SERVER_NAME];
|
|
996
|
+
return entry2 !== void 0 && !isClinePusharyEntry(entry2);
|
|
997
|
+
};
|
|
998
|
+
var withClinePusharyServer = (settings, apiKey) => {
|
|
999
|
+
const servers = serversOf(settings);
|
|
1000
|
+
return { ...settings, mcpServers: { ...servers, [CLINE_SERVER_NAME]: connectedClinePusharyEntry(servers[CLINE_SERVER_NAME], apiKey) } };
|
|
1001
|
+
};
|
|
1002
|
+
var withKeyHeader = (target, apiKey) => ({
|
|
1003
|
+
...target,
|
|
1004
|
+
headers: { ...headersWithoutAuthorization(target.headers), [AUTHORIZATION_HEADER]: authorizationBearer(apiKey) }
|
|
1005
|
+
});
|
|
1006
|
+
var withRefreshedClineKey = (settings, apiKey) => {
|
|
1007
|
+
const servers = serversOf(settings);
|
|
1008
|
+
const entry2 = asRecord4(servers[CLINE_SERVER_NAME]) ?? {};
|
|
1009
|
+
const nested = asRecord4(entry2.transport);
|
|
1010
|
+
const refreshed = nested ? { ...entry2, transport: withKeyHeader(nested, apiKey) } : withKeyHeader(entry2, apiKey);
|
|
1011
|
+
return { ...settings, mcpServers: { ...servers, [CLINE_SERVER_NAME]: refreshed } };
|
|
1012
|
+
};
|
|
1013
|
+
var withoutClinePusharyServer = (settings) => {
|
|
1014
|
+
const servers = serversOf(settings);
|
|
1015
|
+
if (!isClinePusharyEntry(servers[CLINE_SERVER_NAME])) return { settings, removed: false };
|
|
1016
|
+
const remaining = Object.fromEntries(Object.entries(servers).filter(([name]) => name !== CLINE_SERVER_NAME));
|
|
1017
|
+
return { settings: { ...settings, mcpServers: remaining }, removed: true };
|
|
1018
|
+
};
|
|
1019
|
+
var isOtherPusharyServer = ([name, entry2]) => name !== CLINE_SERVER_NAME && isClinePusharyEntry(entry2);
|
|
1020
|
+
var otherPusharyServerNames = (settings) => Object.entries(serversOf(settings)).filter(isOtherPusharyServer).map(([name]) => name);
|
|
1021
|
+
var clineSettingsText = (settings) => `${JSON.stringify(settings, null, 2)}
|
|
1022
|
+
`;
|
|
1023
|
+
var clineInstructionsBody = () => `${renderAgentInstructions(CLINE_LABEL)}
|
|
1024
|
+
|
|
1025
|
+
${CLINE_TOOL_NOTE}`;
|
|
1026
|
+
|
|
1027
|
+
// src/setup/paths.ts
|
|
1028
|
+
var claudeSettings = (environment = process.env) => join5(claudeConfigDir(homedir3(), environment), "settings.json");
|
|
1029
|
+
var claudeJson = (environment = process.env) => claudeUserConfigFile(homedir3(), environment);
|
|
1030
|
+
var claudeSkillDir = (environment = process.env) => join5(claudeConfigDir(homedir3(), environment), "skills", "pushary");
|
|
1031
|
+
var cursorPluginDir = () => join5(homedir3(), ".cursor", "plugins", "local", "pushary");
|
|
1032
|
+
var cursorUserHooks = () => join5(homedir3(), ".cursor", "hooks.json");
|
|
1033
|
+
var cursorPluginMcp = () => join5(cursorPluginDir(), "mcp.json");
|
|
1034
|
+
var cursorPluginManifest = () => join5(cursorPluginDir(), ".cursor-plugin", "plugin.json");
|
|
1035
|
+
var cursorPluginRule = () => join5(cursorPluginDir(), "rules", "pushary.mdc");
|
|
1036
|
+
var cursorUserMcp = () => join5(homedir3(), ".cursor", "mcp.json");
|
|
1037
|
+
var vscodePluginDir = () => join5(homedir3(), ".pushary", "plugins", "vscode");
|
|
1038
|
+
var vscodePluginMcp = () => join5(vscodePluginDir(), ".mcp.json");
|
|
1039
|
+
var claudeSettingsLocal = () => join5(claudeConfigDir(), "settings.local.json");
|
|
1040
|
+
var pusharyDir = () => join5(homedir3(), ".pushary");
|
|
1041
|
+
var claudeSetupDir = () => process.env.CLAUDE_CONFIG_DIR ? join5(pusharyDir(), "setup", "claude-profiles", createHash2("sha256").update(claudeUserConfigFile()).digest("hex").slice(0, 32)) : join5(pusharyDir(), "setup");
|
|
1042
|
+
var claudeMcpReceipt = () => join5(claudeSetupDir(), "claude-mcp.json");
|
|
1043
|
+
var bridgeAgentKey = () => join5(pusharyDir(), "bridge-auth", "agent-key.json");
|
|
1044
|
+
var autoUpdateState = () => join5(pusharyDir(), "auto-update.json");
|
|
1045
|
+
var autoUpdateLock = () => join5(pusharyDir(), "auto-update.lock");
|
|
1046
|
+
var serviceOwedBackMark = () => join5(pusharyDir(), "service-owed-back");
|
|
1047
|
+
var autoUpdateLog = () => join5(pusharyDir(), "logs", "auto-update.log");
|
|
1048
|
+
var clientReportClaims = () => join5(pusharyDir(), "client-reports");
|
|
1049
|
+
var claudeRegistrationReceipt = () => join5(claudeSetupDir(), "claude-registration.json");
|
|
1050
|
+
var hermesConfig = () => join5(homedir3(), ".hermes", "config.yaml");
|
|
1051
|
+
var hermesConfigReceipt = () => join5(pusharyDir(), "setup", "hermes-config.json");
|
|
1052
|
+
var fxQuestionRuleReceipt = () => join5(pusharyDir(), "setup", "fx-question-rule.json");
|
|
1053
|
+
var hookTakeoverReceipt = (agent) => join5(agent === "claude_code" ? claudeSetupDir() : join5(pusharyDir(), "setup"), `hook-takeover-${agent}.json`);
|
|
1054
|
+
var codexSkillDir2 = () => join5(codexHome(), "skills", "pushary");
|
|
1055
|
+
var codexAgentsMd2 = () => join5(codexHome(), "AGENTS.md");
|
|
1056
|
+
var geminiSettings = () => join5(homedir3(), ".gemini", "settings.json");
|
|
1057
|
+
var geminiMd = () => join5(homedir3(), ".gemini", "GEMINI.md");
|
|
1058
|
+
var pusharyConfigFile = () => join5(homedir3(), ".pushary", "config.json");
|
|
1059
|
+
var fxConfigDir = () => join5(homedir3(), ".fx");
|
|
1060
|
+
var fxMcpProfile = () => join5(fxConfigDir(), "mcp.json");
|
|
1061
|
+
var fxSettings = () => join5(fxConfigDir(), "settings.json");
|
|
1062
|
+
var fxAgentsMd = () => join5(fxConfigDir(), "AGENTS.md");
|
|
1063
|
+
var clineLocation = () => ({ home: homedir3(), environment: process.env });
|
|
1064
|
+
var clineMcpSettings = () => clineMcpSettingsPath(clineLocation());
|
|
1065
|
+
var clineRules = () => clineRulesFile(clineLocation());
|
|
1066
|
+
|
|
1067
|
+
export {
|
|
1068
|
+
claudeConfigDir,
|
|
1069
|
+
claudeUserConfigFile,
|
|
1070
|
+
registeredCommands,
|
|
1071
|
+
namesOurBinary,
|
|
1072
|
+
registrationHolderOf,
|
|
1073
|
+
HOOK_SHELL,
|
|
1074
|
+
holdBudgetOf,
|
|
1075
|
+
guardBinary,
|
|
1076
|
+
guardBinaryRefusing,
|
|
1077
|
+
guardNodeScript,
|
|
1078
|
+
guardCursorGate,
|
|
1079
|
+
guardedArgv,
|
|
1080
|
+
registerHookEntry,
|
|
1081
|
+
isProtectedByScope,
|
|
1082
|
+
canonicalJson,
|
|
1083
|
+
CODEX_HOOK_BINARY,
|
|
1084
|
+
codexHome,
|
|
1085
|
+
codexHomeFrom,
|
|
1086
|
+
codexConfigToml,
|
|
1087
|
+
codexHooksJsonIn,
|
|
1088
|
+
codexHooksJson,
|
|
1089
|
+
codexAgentsMd,
|
|
1090
|
+
codexSkillDir,
|
|
1091
|
+
CODEX_HOOK_EVENTS,
|
|
1092
|
+
CODEX_REQUIRED_APPROVAL_EVENTS,
|
|
1093
|
+
codexMcpMixesCommandWithHttp,
|
|
1094
|
+
CODEX_RESTART_AFTER_TRANSPORT_CHANGE,
|
|
1095
|
+
codexMcpStartsCommand,
|
|
1096
|
+
addCodexMcpServer,
|
|
1097
|
+
readCodexMcpAuth,
|
|
1098
|
+
isPusharyCodexCommand,
|
|
1099
|
+
codexRegistrationHolder,
|
|
1100
|
+
addCodexHooks,
|
|
1101
|
+
removeCodexHooks,
|
|
1102
|
+
hasCodexHooks,
|
|
1103
|
+
missingCodexHookEvents,
|
|
1104
|
+
codexHookPositions,
|
|
1105
|
+
codexVacatedIndexes,
|
|
1106
|
+
dropVacatedCodexHookTrust,
|
|
1107
|
+
captureCodexHookTrust,
|
|
1108
|
+
restoreCodexHookTrust,
|
|
1109
|
+
codexTrustGaps,
|
|
1110
|
+
removeCodexHookTrust,
|
|
1111
|
+
removeCodexSettings,
|
|
1112
|
+
addCodexHookTrust,
|
|
1113
|
+
POSTTOOLUSE_MIRROR_ONLY_TOOLS,
|
|
1114
|
+
IDLE_PROMPT_NOTIFICATION,
|
|
1115
|
+
NOTIFICATION_PUSH_TYPES,
|
|
1116
|
+
CLAUDE_HOOK_BINARY,
|
|
1117
|
+
CLAUDE_HOOK_EVENTS,
|
|
1118
|
+
supportedClaudeEvents,
|
|
1119
|
+
registrationSignatures,
|
|
1120
|
+
needsReregistration,
|
|
1121
|
+
PUSHARY_MCP_URL,
|
|
1122
|
+
isPusharyHook,
|
|
1123
|
+
addClaudeMcpServer,
|
|
1124
|
+
isClaudeMcpSnapshot,
|
|
1125
|
+
captureClaudeMcpServer,
|
|
1126
|
+
updateClaudeMcpSnapshot,
|
|
1127
|
+
restoreClaudeMcpServer,
|
|
1128
|
+
removeClaudeMcpServers,
|
|
1129
|
+
removePusharyToolPermissions,
|
|
1130
|
+
addPusharyToolPermissions,
|
|
1131
|
+
addPusharyHooks,
|
|
1132
|
+
removePusharySettings,
|
|
1133
|
+
SHARED_AGENTS_MD_LABEL,
|
|
1134
|
+
CODEX_INSTRUCTIONS_LABEL,
|
|
1135
|
+
GEMINI_INSTRUCTIONS_LABEL,
|
|
1136
|
+
OPENCODE_INSTRUCTIONS_LABEL,
|
|
1137
|
+
renderAgentInstructions,
|
|
1138
|
+
renderProjectAgentInstructions,
|
|
1139
|
+
writeInstructionBlock,
|
|
1140
|
+
refreshInstructionBlock,
|
|
1141
|
+
removeInstructionBlock,
|
|
1142
|
+
hasInstructionBlock,
|
|
1143
|
+
CLINE_LABEL,
|
|
1144
|
+
CLINE_AUTO_APPROVE_STEP,
|
|
1145
|
+
CLINE_RELOAD_STEP,
|
|
1146
|
+
CLINE_SETTINGS_REFUSALS,
|
|
1147
|
+
CLINE_SERVER_NAME_TAKEN,
|
|
1148
|
+
clineDataDirectory,
|
|
1149
|
+
readClineSettings,
|
|
1150
|
+
clineEntryRunsCommand,
|
|
1151
|
+
isClinePusharyEntry,
|
|
1152
|
+
clinePusharyEntryOf,
|
|
1153
|
+
clineEntryAuthorization,
|
|
1154
|
+
clineEntryUsesKey,
|
|
1155
|
+
clineEntryHoldsLiteralKey,
|
|
1156
|
+
clineEntrySignedIn,
|
|
1157
|
+
clineEntrySwitchedOff,
|
|
1158
|
+
clineServerNameTakenElsewhere,
|
|
1159
|
+
withClinePusharyServer,
|
|
1160
|
+
withRefreshedClineKey,
|
|
1161
|
+
withoutClinePusharyServer,
|
|
1162
|
+
otherPusharyServerNames,
|
|
1163
|
+
clineSettingsText,
|
|
1164
|
+
clineInstructionsBody,
|
|
1165
|
+
claudeSettings,
|
|
1166
|
+
claudeJson,
|
|
1167
|
+
claudeSkillDir,
|
|
1168
|
+
cursorPluginDir,
|
|
1169
|
+
cursorUserHooks,
|
|
1170
|
+
cursorPluginMcp,
|
|
1171
|
+
cursorPluginManifest,
|
|
1172
|
+
cursorPluginRule,
|
|
1173
|
+
cursorUserMcp,
|
|
1174
|
+
vscodePluginDir,
|
|
1175
|
+
vscodePluginMcp,
|
|
1176
|
+
claudeSettingsLocal,
|
|
1177
|
+
pusharyDir,
|
|
1178
|
+
claudeMcpReceipt,
|
|
1179
|
+
bridgeAgentKey,
|
|
1180
|
+
autoUpdateState,
|
|
1181
|
+
autoUpdateLock,
|
|
1182
|
+
serviceOwedBackMark,
|
|
1183
|
+
autoUpdateLog,
|
|
1184
|
+
clientReportClaims,
|
|
1185
|
+
claudeRegistrationReceipt,
|
|
1186
|
+
hermesConfig,
|
|
1187
|
+
hermesConfigReceipt,
|
|
1188
|
+
fxQuestionRuleReceipt,
|
|
1189
|
+
hookTakeoverReceipt,
|
|
1190
|
+
codexSkillDir2,
|
|
1191
|
+
codexAgentsMd2,
|
|
1192
|
+
geminiSettings,
|
|
1193
|
+
geminiMd,
|
|
1194
|
+
pusharyConfigFile,
|
|
1195
|
+
fxConfigDir,
|
|
1196
|
+
fxMcpProfile,
|
|
1197
|
+
fxSettings,
|
|
1198
|
+
fxAgentsMd,
|
|
1199
|
+
clineMcpSettings,
|
|
1200
|
+
clineRules
|
|
1201
|
+
};
|