@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.
@@ -5,8 +5,10 @@ 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
+ 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-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,13 @@
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
+ 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();
@@ -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,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
- } from "../chunk-VC6U3QGF.js";
14
+ npmErrorMessage,
15
+ renderAgentInstructions,
16
+ writeInstructionBlock
17
+ } from "../chunk-UIBTFZUD.js";
13
18
  import {
14
19
  isValidApiKey
15
- } from "../chunk-RZLVE57X.js";
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-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)