@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.
@@ -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
- input = JSON.parse(raw)
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
- } from "../chunk-VC6U3QGF.js";
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-Y633ZIJF.js";
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-2BE2IPMO.js";
29
- import "../chunk-RZLVE57X.js";
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-Y633ZIJF.js";
4
+ } from "../chunk-QY4L6XHN.js";
5
5
  import {
6
6
  askUser,
7
7
  getMachineId,
8
8
  waitForAnswer
9
- } from "../chunk-2BE2IPMO.js";
10
- import "../chunk-RZLVE57X.js";
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
- missingCodexHookEvents
6
- } from "../chunk-VC6U3QGF.js";
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();
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-DKZVKEOW.js";
5
- import "../chunk-2BE2IPMO.js";
6
- import "../chunk-RZLVE57X.js";
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-Y633ZIJF.js";
5
- import "../chunk-2BE2IPMO.js";
6
- import "../chunk-RZLVE57X.js";
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-Y633ZIJF.js";
5
- import "../chunk-2BE2IPMO.js";
6
- import "../chunk-RZLVE57X.js";
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-VC6U3QGF.js";
15
+ } from "../chunk-ZWBS3T7Q.js";
13
16
  import {
14
17
  isValidApiKey
15
- } from "../chunk-RZLVE57X.js";
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 \u2014 where Cursor reads hooks`);
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")} Restart Cursor (or run Developer: Reload Window) to load it`);
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-Y633ZIJF.js";
5
- import "../chunk-2BE2IPMO.js";
6
- import "../chunk-RZLVE57X.js";
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
 
@@ -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)