@pushary/agent-hooks 0.18.2 → 0.19.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/data/cursor-plugin/scripts/pushary-gate.mjs +8 -1
- package/dist/bin/pushary-clean.js +15 -2
- package/dist/bin/pushary-codex-hook.js +3 -3
- package/dist/bin/pushary-codex.js +3 -3
- package/dist/bin/pushary-doctor.js +29 -2
- package/dist/bin/pushary-gemini-hook.d.ts +1 -0
- package/dist/bin/pushary-gemini-hook.js +246 -0
- package/dist/bin/pushary-hook.js +3 -3
- package/dist/bin/pushary-post-hook.js +3 -3
- package/dist/bin/pushary-prompt-hook.js +3 -3
- package/dist/bin/pushary-setup.js +55 -4
- package/dist/bin/pushary-stop-hook.js +3 -3
- package/dist/bin/pushary.js +1 -1
- package/dist/chunk-ACE77TKQ.js +328 -0
- package/dist/chunk-QY4L6XHN.js +384 -0
- package/dist/chunk-SDREQWNI.js +175 -0
- package/dist/chunk-USUCPCUC.js +156 -0
- package/dist/chunk-ZWBS3T7Q.js +244 -0
- package/dist/src/index.js +4 -4
- package/package.json +3 -2
|
@@ -324,7 +324,14 @@ const main = async () => {
|
|
|
324
324
|
diag('no input on stdin. Cursor did not pipe the command to this hook (a known Cursor issue on Windows). Handing off to Cursor\'s own prompt; the push approval cannot run without the command.')
|
|
325
325
|
return respond(ask())
|
|
326
326
|
}
|
|
327
|
-
|
|
327
|
+
// Windows: Cursor prepends a BOM/encoding prefix to the hook's stdin, so raw
|
|
328
|
+
// arrives as e.g. "���{...}" and a bare JSON.parse throws,
|
|
329
|
+
// silently dropping us to "ask" with no push (the script works when run
|
|
330
|
+
// manually because there is no BOM). The payload is always a JSON object, so
|
|
331
|
+
// parse from the first "{" — robust to a BOM or whatever prefix bytes Cursor
|
|
332
|
+
// emits. Verified on Windows + Cursor 3.8.11.
|
|
333
|
+
const jsonStart = raw.indexOf('{')
|
|
334
|
+
input = JSON.parse(jsonStart > 0 ? raw.slice(jsonStart) : raw)
|
|
328
335
|
} catch {
|
|
329
336
|
diag('stdin was not valid JSON (often empty or corrupted, a known Cursor Windows stdin issue). Handing off to Cursor\'s own prompt.')
|
|
330
337
|
return respond(ask())
|
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
} from "../chunk-5MA3CPZB.js";
|
|
6
6
|
import {
|
|
7
7
|
execNpm,
|
|
8
|
-
removeCodexHooks
|
|
9
|
-
|
|
8
|
+
removeCodexHooks,
|
|
9
|
+
removeGeminiSettings
|
|
10
|
+
} from "../chunk-ZWBS3T7Q.js";
|
|
10
11
|
|
|
11
12
|
// bin/pushary-clean.ts
|
|
12
13
|
import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
@@ -134,6 +135,18 @@ var main = async () => {
|
|
|
134
135
|
} else {
|
|
135
136
|
console.log(` ${skip} Codex hooks ${dim("(not found)")}`);
|
|
136
137
|
}
|
|
138
|
+
const geminiSettingsPath = join(homedir(), ".gemini", "settings.json");
|
|
139
|
+
const geminiSettings = readJson(geminiSettingsPath);
|
|
140
|
+
if (geminiSettings) {
|
|
141
|
+
if (removeGeminiSettings(geminiSettings)) {
|
|
142
|
+
writeJson(geminiSettingsPath, geminiSettings);
|
|
143
|
+
console.log(` ${check} Gemini CLI settings ${dim("(cleaned)")}`);
|
|
144
|
+
} else {
|
|
145
|
+
console.log(` ${skip} Gemini CLI settings ${dim("(no pushary entries)")}`);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
console.log(` ${skip} Gemini CLI settings ${dim("(not found)")}`);
|
|
149
|
+
}
|
|
137
150
|
for (const shellFile of SHELL_FILES) {
|
|
138
151
|
try {
|
|
139
152
|
const content = readFileSync(shellFile, "utf-8");
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
reportEvent,
|
|
13
13
|
toCodexWire,
|
|
14
14
|
toPolicyLookup
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-QY4L6XHN.js";
|
|
16
16
|
import {
|
|
17
17
|
DEFAULT_SESSION,
|
|
18
18
|
askUser,
|
|
@@ -25,8 +25,8 @@ import {
|
|
|
25
25
|
savePendingQuestion,
|
|
26
26
|
sendNotification,
|
|
27
27
|
waitForAnswer
|
|
28
|
-
} from "../chunk-
|
|
29
|
-
import "../chunk-
|
|
28
|
+
} from "../chunk-ACE77TKQ.js";
|
|
29
|
+
import "../chunk-USUCPCUC.js";
|
|
30
30
|
import "../chunk-DWED7BS3.js";
|
|
31
31
|
import {
|
|
32
32
|
getApiKey
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
reportEvent
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-QY4L6XHN.js";
|
|
5
5
|
import {
|
|
6
6
|
askUser,
|
|
7
7
|
getMachineId,
|
|
8
8
|
waitForAnswer
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-ACE77TKQ.js";
|
|
10
|
+
import "../chunk-USUCPCUC.js";
|
|
11
11
|
import "../chunk-DWED7BS3.js";
|
|
12
12
|
import {
|
|
13
13
|
getApiKey
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
GEMINI_HOOK_BINARY,
|
|
3
4
|
execNpm,
|
|
4
5
|
hasCodexHooks,
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
hasGeminiHooks,
|
|
7
|
+
missingCodexHookEvents,
|
|
8
|
+
missingGeminiHookEvents
|
|
9
|
+
} from "../chunk-ZWBS3T7Q.js";
|
|
7
10
|
import {
|
|
8
11
|
callMcpTool,
|
|
9
12
|
sendMcpRequest
|
|
@@ -187,6 +190,30 @@ var main = async () => {
|
|
|
187
190
|
const codexSkillPath = join(homedir(), ".codex", "skills", "pushary", "SKILL.md");
|
|
188
191
|
check(existsSync(codexSkillPath), "Codex: skill installed");
|
|
189
192
|
}
|
|
193
|
+
const geminiSettingsPath = join(homedir(), ".gemini", "settings.json");
|
|
194
|
+
const geminiSettings = readJson(geminiSettingsPath);
|
|
195
|
+
if (geminiSettings) {
|
|
196
|
+
const geminiServers = geminiSettings.mcpServers ?? {};
|
|
197
|
+
const geminiPushary = geminiServers.pushary;
|
|
198
|
+
check(!!geminiPushary, "Gemini CLI: MCP server configured");
|
|
199
|
+
if (geminiPushary) {
|
|
200
|
+
check(typeof geminiPushary.httpUrl === "string", "Gemini CLI: MCP transport", geminiPushary.httpUrl ? String(geminiPushary.httpUrl) : "missing \u2014 add httpUrl");
|
|
201
|
+
check(geminiPushary.trust === true, "Gemini CLI: tools auto-allowed", geminiPushary.trust === true ? "trust: true" : "missing \u2014 MCP calls will prompt for confirmation");
|
|
202
|
+
}
|
|
203
|
+
const geminiHooksInstalled = hasGeminiHooks(geminiSettings);
|
|
204
|
+
if (geminiHooksInstalled) {
|
|
205
|
+
const missingEvents = missingGeminiHookEvents(geminiSettings);
|
|
206
|
+
check(missingEvents.length === 0, "Gemini CLI: native hooks installed", missingEvents.length === 0 ? "all 5 events" : `missing ${missingEvents.join(", ")}, re-run setup`);
|
|
207
|
+
const geminiHooks = geminiSettings.hooks;
|
|
208
|
+
const hookCommand = extractHookCommand(geminiHooks?.BeforeTool, GEMINI_HOOK_BINARY);
|
|
209
|
+
if (hookCommand) {
|
|
210
|
+
const resolves = commandResolves(hookCommand);
|
|
211
|
+
check(resolves, "Gemini CLI: hook command resolves", resolves ? hookCommand : `not on PATH: ${hookCommand}`);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
check(false, "Gemini CLI: native hooks installed", "no Pushary hooks in ~/.gemini/settings.json \u2014 re-run setup");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
190
217
|
const cursorPluginDir = join(homedir(), ".cursor", "plugins", "local", "pushary");
|
|
191
218
|
if (existsSync(cursorPluginDir)) {
|
|
192
219
|
const cursorUserHooks = join(homedir(), ".cursor", "hooks.json");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
handlePostToolUse,
|
|
4
|
+
handleStop,
|
|
5
|
+
handleUserPrompt,
|
|
6
|
+
reportEvent
|
|
7
|
+
} from "../chunk-QY4L6XHN.js";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_SESSION,
|
|
10
|
+
askUser,
|
|
11
|
+
deriveToolTarget,
|
|
12
|
+
describeToolCall,
|
|
13
|
+
fetchModeState,
|
|
14
|
+
getMachineId,
|
|
15
|
+
getPolicy,
|
|
16
|
+
resolvePolicy,
|
|
17
|
+
savePendingQuestion,
|
|
18
|
+
sendNotification,
|
|
19
|
+
waitForAnswer
|
|
20
|
+
} from "../chunk-ACE77TKQ.js";
|
|
21
|
+
import "../chunk-USUCPCUC.js";
|
|
22
|
+
import "../chunk-DWED7BS3.js";
|
|
23
|
+
import {
|
|
24
|
+
getApiKey
|
|
25
|
+
} from "../chunk-NKXSILEW.js";
|
|
26
|
+
|
|
27
|
+
// bin/pushary-gemini-hook.ts
|
|
28
|
+
import { basename } from "path";
|
|
29
|
+
|
|
30
|
+
// src/gemini-adapter.ts
|
|
31
|
+
var GEMINI_AGENT = { type: "gemini_cli", label: "Gemini CLI" };
|
|
32
|
+
var GEMINI_TOOL_MAP = {
|
|
33
|
+
run_shell_command: "Bash",
|
|
34
|
+
write_file: "Write",
|
|
35
|
+
replace: "Edit",
|
|
36
|
+
read_file: "Read"
|
|
37
|
+
};
|
|
38
|
+
var firstString = (...values) => values.find((v) => typeof v === "string" && v.length > 0);
|
|
39
|
+
var toGeminiPolicyLookup = (toolName, toolInput) => {
|
|
40
|
+
const canonical = GEMINI_TOOL_MAP[toolName];
|
|
41
|
+
if (!canonical) return { tool: toolName, input: toolInput };
|
|
42
|
+
if (canonical === "Bash") {
|
|
43
|
+
const command = firstString(toolInput.command);
|
|
44
|
+
return { tool: "Bash", input: command ? { command } : {} };
|
|
45
|
+
}
|
|
46
|
+
const filePath = firstString(toolInput.file_path, toolInput.absolute_path, toolInput.path);
|
|
47
|
+
return { tool: canonical, input: filePath ? { file_path: filePath } : {} };
|
|
48
|
+
};
|
|
49
|
+
var geminiAllow = () => ({ kind: "allow" });
|
|
50
|
+
var geminiDeny = (reason) => ({ kind: "deny", reason });
|
|
51
|
+
var geminiPass = () => ({ kind: "pass" });
|
|
52
|
+
var toGeminiWire = (decision) => {
|
|
53
|
+
if (decision.kind === "allow") return { decision: "allow" };
|
|
54
|
+
if (decision.kind === "deny") return { decision: "deny", reason: decision.reason };
|
|
55
|
+
return null;
|
|
56
|
+
};
|
|
57
|
+
var geminiTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => {
|
|
58
|
+
if (timeoutAction === "approve") return geminiAllow();
|
|
59
|
+
if (timeoutAction === "deny") return geminiDeny(denyReason);
|
|
60
|
+
return geminiPass();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// bin/pushary-gemini-hook.ts
|
|
64
|
+
var KILL_REASON = "Stopped by user: this agent was halted from Pushary";
|
|
65
|
+
var MAX_WAIT_SECONDS = 170;
|
|
66
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
67
|
+
var denyReasonFrom = (value) => value && value !== "no" && value !== "yes" ? `Denied from your phone: ${value}` : "Denied via push notification";
|
|
68
|
+
var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
|
|
69
|
+
while (Date.now() < deadlineMs) {
|
|
70
|
+
const remaining = Math.min(Math.max(deadlineMs - Date.now(), 1e3), 3e4);
|
|
71
|
+
let answer;
|
|
72
|
+
try {
|
|
73
|
+
answer = await waitForAnswer(apiKey, correlationId, remaining);
|
|
74
|
+
} catch {
|
|
75
|
+
if (Date.now() + pollInterval >= deadlineMs) break;
|
|
76
|
+
await sleep(pollInterval);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (answer.answered) return answer;
|
|
80
|
+
if (Date.now() + pollInterval >= deadlineMs) break;
|
|
81
|
+
await sleep(pollInterval);
|
|
82
|
+
}
|
|
83
|
+
return { answered: false };
|
|
84
|
+
};
|
|
85
|
+
var agentNameFor = (input) => `${GEMINI_AGENT.label} - ${basename(input.cwd ?? process.cwd())}`;
|
|
86
|
+
var pushQuestion = async (apiKey, input, lookup, waitSeconds, pollInterval = 2e3) => {
|
|
87
|
+
const projectName = basename(input.cwd ?? process.cwd());
|
|
88
|
+
const description = describeToolCall(lookup.tool, lookup.input, "hook");
|
|
89
|
+
const result = await askUser(apiKey, {
|
|
90
|
+
question: `Allow ${description}?`,
|
|
91
|
+
type: "confirm",
|
|
92
|
+
context: `Agent wants to run this in ${projectName}`,
|
|
93
|
+
agentName: agentNameFor(input),
|
|
94
|
+
sessionId: input.session_id,
|
|
95
|
+
machineId: getMachineId(),
|
|
96
|
+
toolName: lookup.tool,
|
|
97
|
+
toolTarget: deriveToolTarget(lookup.tool, lookup.input)
|
|
98
|
+
});
|
|
99
|
+
const deadline = Date.now() + Math.min(waitSeconds, MAX_WAIT_SECONDS) * 1e3;
|
|
100
|
+
const answer = await pollForAnswer(apiKey, result.correlationId, deadline, pollInterval);
|
|
101
|
+
return { answer, correlationId: result.correlationId };
|
|
102
|
+
};
|
|
103
|
+
var notifyApprovalNeeded = async (apiKey, input, lookup) => {
|
|
104
|
+
try {
|
|
105
|
+
await sendNotification(apiKey, {
|
|
106
|
+
title: "Agent needs approval",
|
|
107
|
+
body: describeToolCall(lookup.tool, lookup.input, "hook"),
|
|
108
|
+
agentName: agentNameFor(input),
|
|
109
|
+
sessionId: input.session_id,
|
|
110
|
+
machineId: getMachineId()
|
|
111
|
+
});
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var handlePushOnly = async (apiKey, input, lookup, timeoutSeconds, timeoutAction) => {
|
|
116
|
+
let pushed;
|
|
117
|
+
try {
|
|
118
|
+
pushed = await pushQuestion(apiKey, input, lookup, timeoutSeconds);
|
|
119
|
+
} catch {
|
|
120
|
+
return geminiTimeoutDecision(timeoutAction, "Push notification failed, denying per policy");
|
|
121
|
+
}
|
|
122
|
+
const { answer, correlationId } = pushed;
|
|
123
|
+
if (answer.answered) {
|
|
124
|
+
return answer.value === "yes" ? geminiAllow() : geminiDeny(denyReasonFrom(answer.value));
|
|
125
|
+
}
|
|
126
|
+
savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
|
|
127
|
+
return geminiTimeoutDecision(timeoutAction);
|
|
128
|
+
};
|
|
129
|
+
var handlePushFirst = async (apiKey, input, lookup, pushFirstSeconds) => {
|
|
130
|
+
let pushed;
|
|
131
|
+
try {
|
|
132
|
+
pushed = await pushQuestion(apiKey, input, lookup, pushFirstSeconds, 1500);
|
|
133
|
+
} catch {
|
|
134
|
+
return geminiPass();
|
|
135
|
+
}
|
|
136
|
+
const { answer, correlationId } = pushed;
|
|
137
|
+
if (answer.answered) {
|
|
138
|
+
return answer.value === "yes" ? geminiAllow() : geminiDeny(denyReasonFrom(answer.value));
|
|
139
|
+
}
|
|
140
|
+
savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
|
|
141
|
+
return geminiPass();
|
|
142
|
+
};
|
|
143
|
+
var decideBeforeTool = async (input) => {
|
|
144
|
+
try {
|
|
145
|
+
const apiKey = getApiKey();
|
|
146
|
+
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
147
|
+
if (modeState.kill) return geminiDeny(KILL_REASON);
|
|
148
|
+
const lookup = toGeminiPolicyLookup(input.tool_name ?? "", input.tool_input ?? {});
|
|
149
|
+
const policy = await getPolicy(apiKey, modeState.policyVersion);
|
|
150
|
+
const toolPolicy = resolvePolicy(policy, lookup.tool, modeState.mode, lookup.input);
|
|
151
|
+
if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "approve") return geminiAllow();
|
|
152
|
+
if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "deny") {
|
|
153
|
+
return geminiDeny(`Denied by policy for ${toolPolicy.tool}`);
|
|
154
|
+
}
|
|
155
|
+
switch (toolPolicy.mode) {
|
|
156
|
+
case "push_only":
|
|
157
|
+
return handlePushOnly(apiKey, input, lookup, toolPolicy.timeoutSeconds, toolPolicy.timeoutAction);
|
|
158
|
+
case "terminal_only":
|
|
159
|
+
return geminiPass();
|
|
160
|
+
case "push_first":
|
|
161
|
+
return handlePushFirst(apiKey, input, lookup, toolPolicy.pushFirstSeconds);
|
|
162
|
+
case "notify_only":
|
|
163
|
+
await notifyApprovalNeeded(apiKey, input, lookup);
|
|
164
|
+
return geminiPass();
|
|
165
|
+
default:
|
|
166
|
+
return handlePushFirst(apiKey, input, lookup, toolPolicy.pushFirstSeconds);
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
return geminiPass();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var reportSessionStart = async (input) => {
|
|
173
|
+
try {
|
|
174
|
+
await reportEvent({
|
|
175
|
+
event: "session_begin",
|
|
176
|
+
agentType: GEMINI_AGENT.type,
|
|
177
|
+
agentName: agentNameFor(input),
|
|
178
|
+
action: "Session started",
|
|
179
|
+
sessionId: input.session_id
|
|
180
|
+
}, { maxAttempts: 1, timeoutMs: 5e3 });
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var toCanonicalResult = (toolResponse) => {
|
|
185
|
+
if (!toolResponse || typeof toolResponse !== "object" || Array.isArray(toolResponse)) return void 0;
|
|
186
|
+
const error = toolResponse.error;
|
|
187
|
+
return error ? { error } : {};
|
|
188
|
+
};
|
|
189
|
+
var emit = (wire) => {
|
|
190
|
+
if (wire) process.stdout.write(JSON.stringify(wire));
|
|
191
|
+
};
|
|
192
|
+
var main = async () => {
|
|
193
|
+
let rawInput = "";
|
|
194
|
+
for await (const chunk of process.stdin) {
|
|
195
|
+
rawInput += chunk;
|
|
196
|
+
}
|
|
197
|
+
if (!rawInput.trim()) {
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
let input;
|
|
201
|
+
try {
|
|
202
|
+
input = JSON.parse(rawInput);
|
|
203
|
+
} catch {
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
switch (input.hook_event_name) {
|
|
208
|
+
case "BeforeTool":
|
|
209
|
+
emit(toGeminiWire(await decideBeforeTool(input)));
|
|
210
|
+
break;
|
|
211
|
+
case "AfterTool": {
|
|
212
|
+
const lookup = toGeminiPolicyLookup(input.tool_name ?? "", input.tool_input ?? {});
|
|
213
|
+
await handlePostToolUse({
|
|
214
|
+
tool_name: lookup.tool,
|
|
215
|
+
tool_input: lookup.input,
|
|
216
|
+
tool_result: toCanonicalResult(input.tool_response),
|
|
217
|
+
cwd: input.cwd,
|
|
218
|
+
session_id: input.session_id,
|
|
219
|
+
transcript_path: input.transcript_path
|
|
220
|
+
}, GEMINI_AGENT);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case "BeforeAgent":
|
|
224
|
+
await handleUserPrompt({
|
|
225
|
+
prompt: input.prompt,
|
|
226
|
+
cwd: input.cwd,
|
|
227
|
+
session_id: input.session_id
|
|
228
|
+
}, GEMINI_AGENT);
|
|
229
|
+
break;
|
|
230
|
+
case "SessionEnd":
|
|
231
|
+
await handleStop({
|
|
232
|
+
cwd: input.cwd,
|
|
233
|
+
session_id: input.session_id,
|
|
234
|
+
transcript_path: input.transcript_path
|
|
235
|
+
}, GEMINI_AGENT);
|
|
236
|
+
break;
|
|
237
|
+
case "SessionStart":
|
|
238
|
+
await reportSessionStart(input);
|
|
239
|
+
break;
|
|
240
|
+
default:
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
main();
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePreToolUse
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-SDREQWNI.js";
|
|
5
|
+
import "../chunk-ACE77TKQ.js";
|
|
6
|
+
import "../chunk-USUCPCUC.js";
|
|
7
7
|
import "../chunk-DWED7BS3.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePostToolUse
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-QY4L6XHN.js";
|
|
5
|
+
import "../chunk-ACE77TKQ.js";
|
|
6
|
+
import "../chunk-USUCPCUC.js";
|
|
7
7
|
import "../chunk-DWED7BS3.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleUserPrompt
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-QY4L6XHN.js";
|
|
5
|
+
import "../chunk-ACE77TKQ.js";
|
|
6
|
+
import "../chunk-USUCPCUC.js";
|
|
7
7
|
import "../chunk-DWED7BS3.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
@@ -5,14 +5,17 @@ import {
|
|
|
5
5
|
addPusharyToolPermissions
|
|
6
6
|
} from "../chunk-5MA3CPZB.js";
|
|
7
7
|
import {
|
|
8
|
+
GEMINI_HOOK_BINARY,
|
|
8
9
|
addCodexHookTrust,
|
|
9
10
|
addCodexHooks,
|
|
11
|
+
addGeminiHooks,
|
|
12
|
+
addGeminiMcpServer,
|
|
10
13
|
execNpm,
|
|
11
14
|
npmErrorMessage
|
|
12
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-ZWBS3T7Q.js";
|
|
13
16
|
import {
|
|
14
17
|
isValidApiKey
|
|
15
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-USUCPCUC.js";
|
|
16
19
|
|
|
17
20
|
// bin/pushary-setup.ts
|
|
18
21
|
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, cpSync, rmSync, chmodSync } from "fs";
|
|
@@ -300,6 +303,7 @@ var CURSOR_USER_HOOKS = join(homedir(), ".cursor", "hooks.json");
|
|
|
300
303
|
var CLAUDE_SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
|
|
301
304
|
var CODEX_HOME = process.env.CODEX_HOME?.trim() || join(homedir(), ".codex");
|
|
302
305
|
var CODEX_SKILL_DIR = join(CODEX_HOME, "skills", "pushary");
|
|
306
|
+
var GEMINI_SETTINGS = join(homedir(), ".gemini", "settings.json");
|
|
303
307
|
var PUSHARY_CONFIG_DIR = join(homedir(), ".pushary");
|
|
304
308
|
var PUSHARY_CONFIG_FILE = join(PUSHARY_CONFIG_DIR, "config.json");
|
|
305
309
|
var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
|
|
@@ -720,6 +724,15 @@ var installCursorUserHooks = (gateScript) => {
|
|
|
720
724
|
hooks.beforeShellExecution = [...others, entry];
|
|
721
725
|
writeJson(CURSOR_USER_HOOKS, { ...userHooks, version: userHooks.version ?? 1, hooks });
|
|
722
726
|
};
|
|
727
|
+
var neutralizePluginGate = () => {
|
|
728
|
+
const path = join(CURSOR_PLUGIN_DIR, "hooks", "hooks.json");
|
|
729
|
+
if (!existsSync(path)) return;
|
|
730
|
+
const data = readJson(path);
|
|
731
|
+
if (data.hooks && "beforeShellExecution" in data.hooks) {
|
|
732
|
+
delete data.hooks.beforeShellExecution;
|
|
733
|
+
writeJson(path, { version: data.version ?? 1, hooks: data.hooks });
|
|
734
|
+
}
|
|
735
|
+
};
|
|
723
736
|
var setupCursor = async (apiKey) => {
|
|
724
737
|
console.log(`
|
|
725
738
|
${bold2("Setting up Cursor")}
|
|
@@ -745,13 +758,14 @@ var setupCursor = async (apiKey) => {
|
|
|
745
758
|
});
|
|
746
759
|
await spinner("Registering permission gate (~/.cursor/hooks.json)", async () => {
|
|
747
760
|
installCursorUserHooks(join(CURSOR_PLUGIN_DIR, "scripts", "pushary-gate.mjs"));
|
|
761
|
+
neutralizePluginGate();
|
|
748
762
|
});
|
|
749
763
|
console.log();
|
|
750
764
|
console.log(` ${dim2("What this configured:")}`);
|
|
751
765
|
console.log(` ${dim2("\u2022")} Plugin installed to ~/.cursor/plugins/local/pushary (MCP tools, rule, skill)`);
|
|
752
|
-
console.log(` ${dim2("\u2022")} Permission gate registered in ~/.cursor/hooks.json
|
|
766
|
+
console.log(` ${dim2("\u2022")} Permission gate registered once in ~/.cursor/hooks.json`);
|
|
753
767
|
console.log(` ${dim2("\u2022")} Risky shell commands route to push approval before they run`);
|
|
754
|
-
console.log(` ${dim2("\u2022")}
|
|
768
|
+
console.log(` ${dim2("\u2022")} Fully quit and reopen Cursor to load it (a Reload Window may not be enough)`);
|
|
755
769
|
};
|
|
756
770
|
var saveApiKey = async (apiKey) => {
|
|
757
771
|
await spinner("Saving your API key", async () => {
|
|
@@ -789,9 +803,44 @@ var setupCustom = async (_apiKey) => {
|
|
|
789
803
|
console.log();
|
|
790
804
|
console.log(` ${dim2("Full guide:")} ${cyan2("https://pushary.com/docs/agents/connect-any-agent")}`);
|
|
791
805
|
};
|
|
806
|
+
var setupGemini = async (apiKey) => {
|
|
807
|
+
console.log(`
|
|
808
|
+
${bold2("Setting up Gemini CLI")}
|
|
809
|
+
`);
|
|
810
|
+
if (!isInstalled("gemini")) {
|
|
811
|
+
console.log(` ${yellow2("!")} Gemini CLI not found. Skipping.`);
|
|
812
|
+
console.log(` ${dim2("Install Gemini CLI and re-run setup to configure.")}`);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
await installGlobally();
|
|
816
|
+
const settings = readJson(GEMINI_SETTINGS);
|
|
817
|
+
await spinner("Adding MCP server (~/.gemini/settings.json)", async () => {
|
|
818
|
+
addGeminiMcpServer(settings, apiKey);
|
|
819
|
+
});
|
|
820
|
+
await spinner("Adding hooks (BeforeTool, AfterTool, BeforeAgent, SessionStart, SessionEnd)", async () => {
|
|
821
|
+
let hookCommand = GEMINI_HOOK_BINARY;
|
|
822
|
+
try {
|
|
823
|
+
const candidate = join(execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim(), "bin", GEMINI_HOOK_BINARY);
|
|
824
|
+
if (existsSync(candidate)) hookCommand = candidate;
|
|
825
|
+
} catch {
|
|
826
|
+
}
|
|
827
|
+
addGeminiHooks(settings, hookCommand);
|
|
828
|
+
});
|
|
829
|
+
await spinner(`Writing ${GEMINI_SETTINGS}`, async () => {
|
|
830
|
+
writeJson(GEMINI_SETTINGS, settings);
|
|
831
|
+
});
|
|
832
|
+
console.log();
|
|
833
|
+
console.log(` ${dim2("What this configured:")}`);
|
|
834
|
+
console.log(` ${dim2("\u2022")} MCP server: Gemini can send notifications and ask questions`);
|
|
835
|
+
console.log(` ${dim2("\u2022")} Hooks: phone approvals, policy enforcement, kill switch, session tracking`);
|
|
836
|
+
console.log(` ${dim2("\u2022")} Gate covers ${bold2("run_shell_command")}, ${bold2("write_file")}, and ${bold2("replace")}`);
|
|
837
|
+
console.log(` ${dim2("\u2022")} Auto-allowed tools: Pushary MCP calls run without a confirmation prompt`);
|
|
838
|
+
console.log(` ${dim2("Restart Gemini CLI to load the new config.")}`);
|
|
839
|
+
};
|
|
792
840
|
var AGENT_SETUP = {
|
|
793
841
|
claude_code: setupClaudeCode,
|
|
794
842
|
codex: setupCodex,
|
|
843
|
+
gemini_cli: setupGemini,
|
|
795
844
|
hermes: setupHermes,
|
|
796
845
|
cursor: setupCursor,
|
|
797
846
|
custom: setupCustom
|
|
@@ -855,6 +904,7 @@ var main = async () => {
|
|
|
855
904
|
const detected = {
|
|
856
905
|
claude_code: isInstalled("claude"),
|
|
857
906
|
codex: isInstalled("codex"),
|
|
907
|
+
gemini_cli: isInstalled("gemini"),
|
|
858
908
|
hermes: isInstalled("hermes"),
|
|
859
909
|
cursor: isInstalled("cursor"),
|
|
860
910
|
custom: false
|
|
@@ -865,6 +915,7 @@ var main = async () => {
|
|
|
865
915
|
choices: [
|
|
866
916
|
{ name: `Claude Code ${dim2("MCP + hooks + auto-allowed tools")}`, value: "claude_code", checked: detected.claude_code },
|
|
867
917
|
{ name: `Codex ${dim2("MCP + native hooks + auto-allowed tools")}`, value: "codex", checked: detected.codex },
|
|
918
|
+
{ name: `Gemini CLI ${dim2("MCP + native hooks + auto-allowed tools")}`, value: "gemini_cli", checked: detected.gemini_cli },
|
|
868
919
|
{ name: `Hermes ${dim2("native plugin + auto-error notifications")}`, value: "hermes", checked: detected.hermes },
|
|
869
920
|
{ name: `Cursor ${dim2("plugin + permission gate")}`, value: "cursor", checked: detected.cursor },
|
|
870
921
|
{ name: `Other ${dim2("any MCP or HTTP agent (Windsurf, n8n, custom)")}`, value: "custom", checked: false }
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleStop
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-QY4L6XHN.js";
|
|
5
|
+
import "../chunk-ACE77TKQ.js";
|
|
6
|
+
import "../chunk-USUCPCUC.js";
|
|
7
7
|
import "../chunk-DWED7BS3.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
package/dist/bin/pushary.js
CHANGED
|
@@ -17,7 +17,7 @@ if (command === "setup") {
|
|
|
17
17
|
Pushary Agent Hooks
|
|
18
18
|
|
|
19
19
|
Commands:
|
|
20
|
-
setup Configure Claude Code, Codex, Hermes, or Cursor with Pushary
|
|
20
|
+
setup Configure Claude Code, Codex, Gemini CLI, Hermes, or Cursor with Pushary
|
|
21
21
|
doctor Verify your Pushary installation is working
|
|
22
22
|
clean Remove all Pushary configuration
|
|
23
23
|
mode Switch approval mode (push_only, push_first, terminal_only)
|