@pushary/agent-hooks 0.18.3 → 0.20.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/dist/bin/pushary-clean.js +35 -2
- package/dist/bin/pushary-codex-hook.js +3 -3
- package/dist/bin/pushary-codex.js +3 -3
- package/dist/bin/pushary-doctor.js +36 -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 +56 -3
- 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-UIBTFZUD.js +314 -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
|
@@ -5,8 +5,10 @@ import {
|
|
|
5
5
|
} from "../chunk-5MA3CPZB.js";
|
|
6
6
|
import {
|
|
7
7
|
execNpm,
|
|
8
|
-
removeCodexHooks
|
|
9
|
-
|
|
8
|
+
removeCodexHooks,
|
|
9
|
+
removeGeminiSettings,
|
|
10
|
+
removeInstructionBlock
|
|
11
|
+
} from "../chunk-UIBTFZUD.js";
|
|
10
12
|
|
|
11
13
|
// bin/pushary-clean.ts
|
|
12
14
|
import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
@@ -134,6 +136,37 @@ var main = async () => {
|
|
|
134
136
|
} else {
|
|
135
137
|
console.log(` ${skip} Codex hooks ${dim("(not found)")}`);
|
|
136
138
|
}
|
|
139
|
+
const codexAgentsMd = join(homedir(), ".codex", "AGENTS.md");
|
|
140
|
+
if (removeInstructionBlock(codexAgentsMd)) {
|
|
141
|
+
console.log(` ${check} Codex AGENTS.md ${dim("(removed Pushary block)")}`);
|
|
142
|
+
} else {
|
|
143
|
+
console.log(` ${skip} Codex AGENTS.md ${dim("(no pushary block)")}`);
|
|
144
|
+
}
|
|
145
|
+
const codexSkillDir = join(homedir(), ".codex", "skills", "pushary");
|
|
146
|
+
if (existsSync(codexSkillDir)) {
|
|
147
|
+
rmSync(codexSkillDir, { recursive: true });
|
|
148
|
+
console.log(` ${check} Codex skill directory ${dim("(removed)")}`);
|
|
149
|
+
} else {
|
|
150
|
+
console.log(` ${skip} Codex skill directory ${dim("(not found)")}`);
|
|
151
|
+
}
|
|
152
|
+
const geminiSettingsPath = join(homedir(), ".gemini", "settings.json");
|
|
153
|
+
const geminiSettings = readJson(geminiSettingsPath);
|
|
154
|
+
if (geminiSettings) {
|
|
155
|
+
if (removeGeminiSettings(geminiSettings)) {
|
|
156
|
+
writeJson(geminiSettingsPath, geminiSettings);
|
|
157
|
+
console.log(` ${check} Gemini CLI settings ${dim("(cleaned)")}`);
|
|
158
|
+
} else {
|
|
159
|
+
console.log(` ${skip} Gemini CLI settings ${dim("(no pushary entries)")}`);
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
console.log(` ${skip} Gemini CLI settings ${dim("(not found)")}`);
|
|
163
|
+
}
|
|
164
|
+
const geminiMd = join(homedir(), ".gemini", "GEMINI.md");
|
|
165
|
+
if (removeInstructionBlock(geminiMd)) {
|
|
166
|
+
console.log(` ${check} Gemini GEMINI.md ${dim("(removed Pushary block)")}`);
|
|
167
|
+
} else {
|
|
168
|
+
console.log(` ${skip} Gemini GEMINI.md ${dim("(no pushary block)")}`);
|
|
169
|
+
}
|
|
137
170
|
for (const shellFile of SHELL_FILES) {
|
|
138
171
|
try {
|
|
139
172
|
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,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
GEMINI_HOOK_BINARY,
|
|
3
4
|
execNpm,
|
|
4
5
|
hasCodexHooks,
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
hasGeminiHooks,
|
|
7
|
+
hasInstructionBlock,
|
|
8
|
+
missingCodexHookEvents,
|
|
9
|
+
missingGeminiHookEvents
|
|
10
|
+
} from "../chunk-UIBTFZUD.js";
|
|
7
11
|
import {
|
|
8
12
|
callMcpTool,
|
|
9
13
|
sendMcpRequest
|
|
@@ -186,6 +190,36 @@ var main = async () => {
|
|
|
186
190
|
}
|
|
187
191
|
const codexSkillPath = join(homedir(), ".codex", "skills", "pushary", "SKILL.md");
|
|
188
192
|
check(existsSync(codexSkillPath), "Codex: skill installed");
|
|
193
|
+
const codexAgentsMd = join(homedir(), ".codex", "AGENTS.md");
|
|
194
|
+
const hasCodexInstructions = hasInstructionBlock(codexAgentsMd);
|
|
195
|
+
check(hasCodexInstructions, "Codex: proactive instructions (AGENTS.md)", hasCodexInstructions ? codexAgentsMd : "missing \u2014 re-run setup so Codex asks via push, not the terminal");
|
|
196
|
+
}
|
|
197
|
+
const geminiSettingsPath = join(homedir(), ".gemini", "settings.json");
|
|
198
|
+
const geminiSettings = readJson(geminiSettingsPath);
|
|
199
|
+
if (geminiSettings) {
|
|
200
|
+
const geminiServers = geminiSettings.mcpServers ?? {};
|
|
201
|
+
const geminiPushary = geminiServers.pushary;
|
|
202
|
+
check(!!geminiPushary, "Gemini CLI: MCP server configured");
|
|
203
|
+
if (geminiPushary) {
|
|
204
|
+
check(typeof geminiPushary.httpUrl === "string", "Gemini CLI: MCP transport", geminiPushary.httpUrl ? String(geminiPushary.httpUrl) : "missing \u2014 add httpUrl");
|
|
205
|
+
check(geminiPushary.trust === true, "Gemini CLI: tools auto-allowed", geminiPushary.trust === true ? "trust: true" : "missing \u2014 MCP calls will prompt for confirmation");
|
|
206
|
+
}
|
|
207
|
+
const geminiHooksInstalled = hasGeminiHooks(geminiSettings);
|
|
208
|
+
if (geminiHooksInstalled) {
|
|
209
|
+
const missingEvents = missingGeminiHookEvents(geminiSettings);
|
|
210
|
+
check(missingEvents.length === 0, "Gemini CLI: native hooks installed", missingEvents.length === 0 ? "all 5 events" : `missing ${missingEvents.join(", ")}, re-run setup`);
|
|
211
|
+
const geminiHooks = geminiSettings.hooks;
|
|
212
|
+
const hookCommand = extractHookCommand(geminiHooks?.BeforeTool, GEMINI_HOOK_BINARY);
|
|
213
|
+
if (hookCommand) {
|
|
214
|
+
const resolves = commandResolves(hookCommand);
|
|
215
|
+
check(resolves, "Gemini CLI: hook command resolves", resolves ? hookCommand : `not on PATH: ${hookCommand}`);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
check(false, "Gemini CLI: native hooks installed", "no Pushary hooks in ~/.gemini/settings.json \u2014 re-run setup");
|
|
219
|
+
}
|
|
220
|
+
const geminiMd = join(homedir(), ".gemini", "GEMINI.md");
|
|
221
|
+
const hasGeminiInstructions = hasInstructionBlock(geminiMd);
|
|
222
|
+
check(hasGeminiInstructions, "Gemini CLI: proactive instructions (GEMINI.md)", hasGeminiInstructions ? geminiMd : "missing \u2014 re-run setup so Gemini asks via push, not the terminal");
|
|
189
223
|
}
|
|
190
224
|
const cursorPluginDir = join(homedir(), ".cursor", "plugins", "local", "pushary");
|
|
191
225
|
if (existsSync(cursorPluginDir)) {
|
|
@@ -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,19 @@ 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
|
-
npmErrorMessage
|
|
12
|
-
|
|
14
|
+
npmErrorMessage,
|
|
15
|
+
renderAgentInstructions,
|
|
16
|
+
writeInstructionBlock
|
|
17
|
+
} from "../chunk-UIBTFZUD.js";
|
|
13
18
|
import {
|
|
14
19
|
isValidApiKey
|
|
15
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-USUCPCUC.js";
|
|
16
21
|
|
|
17
22
|
// bin/pushary-setup.ts
|
|
18
23
|
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, cpSync, rmSync, chmodSync } from "fs";
|
|
@@ -300,6 +305,9 @@ var CURSOR_USER_HOOKS = join(homedir(), ".cursor", "hooks.json");
|
|
|
300
305
|
var CLAUDE_SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
|
|
301
306
|
var CODEX_HOME = process.env.CODEX_HOME?.trim() || join(homedir(), ".codex");
|
|
302
307
|
var CODEX_SKILL_DIR = join(CODEX_HOME, "skills", "pushary");
|
|
308
|
+
var CODEX_AGENTS_MD = join(CODEX_HOME, "AGENTS.md");
|
|
309
|
+
var GEMINI_SETTINGS = join(homedir(), ".gemini", "settings.json");
|
|
310
|
+
var GEMINI_MD = join(homedir(), ".gemini", "GEMINI.md");
|
|
303
311
|
var PUSHARY_CONFIG_DIR = join(homedir(), ".pushary");
|
|
304
312
|
var PUSHARY_CONFIG_FILE = join(PUSHARY_CONFIG_DIR, "config.json");
|
|
305
313
|
var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
|
|
@@ -663,9 +671,13 @@ var setupCodex = async (_apiKey) => {
|
|
|
663
671
|
});
|
|
664
672
|
}
|
|
665
673
|
await installSkillToDir(CODEX_SKILL_DIR, "Installing Pushary skill");
|
|
674
|
+
await spinner("Teaching Codex to ask via push (~/.codex/AGENTS.md)", async () => {
|
|
675
|
+
writeInstructionBlock(CODEX_AGENTS_MD, renderAgentInstructions("Codex"));
|
|
676
|
+
});
|
|
666
677
|
console.log();
|
|
667
678
|
console.log(` ${dim2("What this configured:")}`);
|
|
668
679
|
console.log(` ${dim2("\u2022")} MCP server: Codex can send notifications and ask questions`);
|
|
680
|
+
console.log(` ${dim2("\u2022")} Proactive instructions: ~/.codex/AGENTS.md tells Codex to ask via push, not the terminal`);
|
|
669
681
|
console.log(` ${dim2("\u2022")} Auto-allowed tools: no permission prompts for Pushary MCP calls`);
|
|
670
682
|
if (hooksSupported) {
|
|
671
683
|
console.log(` ${dim2("\u2022")} Native hooks: phone approvals, policy enforcement, kill switch, session tracking`);
|
|
@@ -799,9 +811,48 @@ var setupCustom = async (_apiKey) => {
|
|
|
799
811
|
console.log();
|
|
800
812
|
console.log(` ${dim2("Full guide:")} ${cyan2("https://pushary.com/docs/agents/connect-any-agent")}`);
|
|
801
813
|
};
|
|
814
|
+
var setupGemini = async (apiKey) => {
|
|
815
|
+
console.log(`
|
|
816
|
+
${bold2("Setting up Gemini CLI")}
|
|
817
|
+
`);
|
|
818
|
+
if (!isInstalled("gemini")) {
|
|
819
|
+
console.log(` ${yellow2("!")} Gemini CLI not found. Skipping.`);
|
|
820
|
+
console.log(` ${dim2("Install Gemini CLI and re-run setup to configure.")}`);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
await installGlobally();
|
|
824
|
+
const settings = readJson(GEMINI_SETTINGS);
|
|
825
|
+
await spinner("Adding MCP server (~/.gemini/settings.json)", async () => {
|
|
826
|
+
addGeminiMcpServer(settings, apiKey);
|
|
827
|
+
});
|
|
828
|
+
await spinner("Adding hooks (BeforeTool, AfterTool, BeforeAgent, SessionStart, SessionEnd)", async () => {
|
|
829
|
+
let hookCommand = GEMINI_HOOK_BINARY;
|
|
830
|
+
try {
|
|
831
|
+
const candidate = join(execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim(), "bin", GEMINI_HOOK_BINARY);
|
|
832
|
+
if (existsSync(candidate)) hookCommand = candidate;
|
|
833
|
+
} catch {
|
|
834
|
+
}
|
|
835
|
+
addGeminiHooks(settings, hookCommand);
|
|
836
|
+
});
|
|
837
|
+
await spinner(`Writing ${GEMINI_SETTINGS}`, async () => {
|
|
838
|
+
writeJson(GEMINI_SETTINGS, settings);
|
|
839
|
+
});
|
|
840
|
+
await spinner("Teaching Gemini to ask via push (~/.gemini/GEMINI.md)", async () => {
|
|
841
|
+
writeInstructionBlock(GEMINI_MD, renderAgentInstructions("Gemini"));
|
|
842
|
+
});
|
|
843
|
+
console.log();
|
|
844
|
+
console.log(` ${dim2("What this configured:")}`);
|
|
845
|
+
console.log(` ${dim2("\u2022")} MCP server: Gemini can send notifications and ask questions`);
|
|
846
|
+
console.log(` ${dim2("\u2022")} Proactive instructions: ~/.gemini/GEMINI.md tells Gemini to ask via push, not the terminal`);
|
|
847
|
+
console.log(` ${dim2("\u2022")} Hooks: phone approvals, policy enforcement, kill switch, session tracking`);
|
|
848
|
+
console.log(` ${dim2("\u2022")} Gate covers ${bold2("run_shell_command")}, ${bold2("write_file")}, and ${bold2("replace")}`);
|
|
849
|
+
console.log(` ${dim2("\u2022")} Auto-allowed tools: Pushary MCP calls run without a confirmation prompt`);
|
|
850
|
+
console.log(` ${dim2("Restart Gemini CLI to load the new config.")}`);
|
|
851
|
+
};
|
|
802
852
|
var AGENT_SETUP = {
|
|
803
853
|
claude_code: setupClaudeCode,
|
|
804
854
|
codex: setupCodex,
|
|
855
|
+
gemini_cli: setupGemini,
|
|
805
856
|
hermes: setupHermes,
|
|
806
857
|
cursor: setupCursor,
|
|
807
858
|
custom: setupCustom
|
|
@@ -865,6 +916,7 @@ var main = async () => {
|
|
|
865
916
|
const detected = {
|
|
866
917
|
claude_code: isInstalled("claude"),
|
|
867
918
|
codex: isInstalled("codex"),
|
|
919
|
+
gemini_cli: isInstalled("gemini"),
|
|
868
920
|
hermes: isInstalled("hermes"),
|
|
869
921
|
cursor: isInstalled("cursor"),
|
|
870
922
|
custom: false
|
|
@@ -875,6 +927,7 @@ var main = async () => {
|
|
|
875
927
|
choices: [
|
|
876
928
|
{ name: `Claude Code ${dim2("MCP + hooks + auto-allowed tools")}`, value: "claude_code", checked: detected.claude_code },
|
|
877
929
|
{ name: `Codex ${dim2("MCP + native hooks + auto-allowed tools")}`, value: "codex", checked: detected.codex },
|
|
930
|
+
{ name: `Gemini CLI ${dim2("MCP + native hooks + auto-allowed tools")}`, value: "gemini_cli", checked: detected.gemini_cli },
|
|
878
931
|
{ name: `Hermes ${dim2("native plugin + auto-error notifications")}`, value: "hermes", checked: detected.hermes },
|
|
879
932
|
{ name: `Cursor ${dim2("plugin + permission gate")}`, value: "cursor", checked: detected.cursor },
|
|
880
933
|
{ 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)
|