yenop 0.0.0 → 0.1.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/LICENSE.md +105 -0
- package/README.md +243 -3
- package/dist/adapters/claude-code/hook.d.ts +43 -0
- package/dist/adapters/claude-code/hook.js +101 -0
- package/dist/adapters/claude-code/hook.js.map +1 -0
- package/dist/adapters/claude-code/install.d.ts +23 -0
- package/dist/adapters/claude-code/install.js +86 -0
- package/dist/adapters/claude-code/install.js.map +1 -0
- package/dist/adapters/codex/hook.d.ts +7 -0
- package/dist/adapters/codex/hook.js +134 -0
- package/dist/adapters/codex/hook.js.map +1 -0
- package/dist/adapters/codex/install.d.ts +18 -0
- package/dist/adapters/codex/install.js +66 -0
- package/dist/adapters/codex/install.js.map +1 -0
- package/dist/adapters/cursor/hook.d.ts +25 -0
- package/dist/adapters/cursor/hook.js +228 -0
- package/dist/adapters/cursor/hook.js.map +1 -0
- package/dist/adapters/cursor/install.d.ts +10 -0
- package/dist/adapters/cursor/install.js +57 -0
- package/dist/adapters/cursor/install.js.map +1 -0
- package/dist/adapters/gemini/hook.d.ts +20 -0
- package/dist/adapters/gemini/hook.js +232 -0
- package/dist/adapters/gemini/hook.js.map +1 -0
- package/dist/adapters/gemini/install.d.ts +21 -0
- package/dist/adapters/gemini/install.js +101 -0
- package/dist/adapters/gemini/install.js.map +1 -0
- package/dist/adapters/hooks/pipeline.d.ts +69 -0
- package/dist/adapters/hooks/pipeline.js +131 -0
- package/dist/adapters/hooks/pipeline.js.map +1 -0
- package/dist/adapters/hooks/registry.d.ts +8 -0
- package/dist/adapters/hooks/registry.js +14 -0
- package/dist/adapters/hooks/registry.js.map +1 -0
- package/dist/adapters/mcp/gateway.d.ts +64 -0
- package/dist/adapters/mcp/gateway.js +205 -0
- package/dist/adapters/mcp/gateway.js.map +1 -0
- package/dist/adapters/mcp/run.d.ts +12 -0
- package/dist/adapters/mcp/run.js +86 -0
- package/dist/adapters/mcp/run.js.map +1 -0
- package/dist/adapters/openai-agents/index.d.ts +74 -0
- package/dist/adapters/openai-agents/index.js +139 -0
- package/dist/adapters/openai-agents/index.js.map +1 -0
- package/dist/cli/demo.d.ts +5 -0
- package/dist/cli/demo.js +130 -0
- package/dist/cli/demo.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +746 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/core/config.d.ts +50 -0
- package/dist/core/config.js +93 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/engine.d.ts +31 -0
- package/dist/core/engine.js +301 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/ids.d.ts +5 -0
- package/dist/core/ids.js +36 -0
- package/dist/core/ids.js.map +1 -0
- package/dist/core/index.d.ts +40 -0
- package/dist/core/index.js +73 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/policy.d.ts +41 -0
- package/dist/core/policy.js +205 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/receipts.d.ts +47 -0
- package/dist/core/receipts.js +137 -0
- package/dist/core/receipts.js.map +1 -0
- package/dist/core/report.d.ts +51 -0
- package/dist/core/report.js +125 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/shell.d.ts +105 -0
- package/dist/core/shell.js +921 -0
- package/dist/core/shell.js.map +1 -0
- package/dist/core/state.d.ts +52 -0
- package/dist/core/state.js +269 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/telemetry.d.ts +52 -0
- package/dist/core/telemetry.js +150 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/tools.d.ts +5 -0
- package/dist/core/tools.js +59 -0
- package/dist/core/tools.js.map +1 -0
- package/dist/core/types.d.ts +213 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/daemon/client.d.ts +34 -0
- package/dist/daemon/client.js +138 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/fast.d.ts +9 -0
- package/dist/daemon/fast.js +43 -0
- package/dist/daemon/fast.js.map +1 -0
- package/dist/daemon/server.d.ts +38 -0
- package/dist/daemon/server.js +321 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/service.d.ts +26 -0
- package/dist/daemon/service.js +171 -0
- package/dist/daemon/service.js.map +1 -0
- package/dist/viewer/server.d.ts +52 -0
- package/dist/viewer/server.js +184 -0
- package/dist/viewer/server.js.map +1 -0
- package/package.json +59 -7
- package/policies/README.md +58 -0
- package/policies/approve/default.cedar +72 -0
- package/policies/permit/default.cedar +48 -0
- package/policies/schema.cedarschema +104 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI hooks adapter (`yenop hook gemini`).
|
|
3
|
+
*
|
|
4
|
+
* Gemini CLI runs a command before and after every tool call and reads a JSON answer from stdout. The contract
|
|
5
|
+
* (geminicli.com/docs/hooks/reference, checked against @google/gemini-cli-core 0.60.0):
|
|
6
|
+
* - BeforeTool {session_id, cwd, tool_name, tool_input, mcp_context?} → {decision: allow|ask|deny, reason, systemMessage}
|
|
7
|
+
* - AfterTool {..., tool_response: {llmContent, returnDisplay, error?}} observe-only
|
|
8
|
+
* - SessionEnd {session_id, reason} fire-and-forget
|
|
9
|
+
* Every event also carries hook_event_name, transcript_path and timestamp.
|
|
10
|
+
*
|
|
11
|
+
* What Gemini does with the answer, from its source rather than its docs:
|
|
12
|
+
* - `ask` forces Gemini's own confirmation prompt, even in an auto-approve mode, and shows systemMessage.
|
|
13
|
+
* So Gemini can ask a person, like Claude Code and unlike Codex.
|
|
14
|
+
* - `deny` skips the tool; the model receives "Tool execution blocked: <reason>".
|
|
15
|
+
* - Exit code 0 with JSON is the answer it prefers. Exit 1 means "warning, proceed" (an allow!), any other
|
|
16
|
+
* non-zero exit is a deny. A hook that times out or cannot be spawned is logged and IGNORED: the runtime
|
|
17
|
+
* is fail-open, like Codex. Yenop therefore answers with exit 0 and JSON for every verdict, including a
|
|
18
|
+
* deny and its own failure, which is the one path that is honoured unambiguously; it never exits 1.
|
|
19
|
+
*
|
|
20
|
+
* Security decisions baked in here:
|
|
21
|
+
* - Every world-touching tool is mapped by name to what it can do; a name Yenop does not know is judged as
|
|
22
|
+
* an unknown tool (not read-only), never ignored.
|
|
23
|
+
* - Gemini gives no per-call id, so one is derived from session, tool and arguments; that is what lets the
|
|
24
|
+
* AfterTool outcome attach to the decision. Two identical calls inside the replay window get one verdict,
|
|
25
|
+
* which is the same verdict they would get anyway.
|
|
26
|
+
* - MCP tools arrive as `mcp_<server>_<tool>` after Gemini sanitises the name, which is ambiguous when names
|
|
27
|
+
* contain underscores. mcp_context carries the exact server and tool and is preferred; the name is the
|
|
28
|
+
* fallback, with the whole remainder treated as the tool.
|
|
29
|
+
* - run_shell_command may name a directory to run in; the decision is made relative to it, so a relative
|
|
30
|
+
* path in the command resolves the way the shell will resolve it.
|
|
31
|
+
* - web_fetch takes URLs inside a prose prompt. Every URL is extracted and the first external one is judged
|
|
32
|
+
* as the call's destination.
|
|
33
|
+
* - Malformed input throws; the pipeline turns that into a deny.
|
|
34
|
+
*/
|
|
35
|
+
import { createHash } from "node:crypto";
|
|
36
|
+
import { homedir } from "node:os";
|
|
37
|
+
import { join, resolve } from "node:path";
|
|
38
|
+
import { classifyTool, mcpToolRef } from "../../core/tools.js";
|
|
39
|
+
import { isInternalHost } from "../../core/shell.js";
|
|
40
|
+
import { safeUser } from "../hooks/pipeline.js";
|
|
41
|
+
/** Gemini's built-in tools (packages/core/src/tools/tool-names.ts) and what each can do. */
|
|
42
|
+
const BUILTIN = {
|
|
43
|
+
run_shell_command: { kind: "shell", readOnly: false },
|
|
44
|
+
read_file: { kind: "read", readOnly: true },
|
|
45
|
+
read_many_files: { kind: "read", readOnly: true },
|
|
46
|
+
list_directory: { kind: "read", readOnly: true },
|
|
47
|
+
glob: { kind: "read", readOnly: true },
|
|
48
|
+
grep_search: { kind: "read", readOnly: true },
|
|
49
|
+
search_file_content: { kind: "read", readOnly: true }, // legacy alias of grep_search
|
|
50
|
+
write_file: { kind: "write", readOnly: false },
|
|
51
|
+
replace: { kind: "write", readOnly: false },
|
|
52
|
+
save_memory: { kind: "write", readOnly: false },
|
|
53
|
+
web_fetch: { kind: "web", readOnly: true },
|
|
54
|
+
google_web_search: { kind: "web", readOnly: true },
|
|
55
|
+
read_mcp_resource: { kind: "mcp", readOnly: true },
|
|
56
|
+
// Tools that act inside Gemini itself, not on outside systems. Judged (cheap) rather than ignored.
|
|
57
|
+
write_todos: { kind: "read", readOnly: true },
|
|
58
|
+
ask_user: { kind: "read", readOnly: true },
|
|
59
|
+
activate_skill: { kind: "read", readOnly: true },
|
|
60
|
+
enter_plan_mode: { kind: "read", readOnly: true },
|
|
61
|
+
exit_plan_mode: { kind: "read", readOnly: true },
|
|
62
|
+
update_topic: { kind: "read", readOnly: true },
|
|
63
|
+
complete_task: { kind: "read", readOnly: true },
|
|
64
|
+
get_internal_docs: { kind: "read", readOnly: true },
|
|
65
|
+
list_mcp_resources: { kind: "read", readOnly: true },
|
|
66
|
+
invoke_agent: { kind: "read", readOnly: true },
|
|
67
|
+
tracker_create_task: { kind: "read", readOnly: true },
|
|
68
|
+
tracker_update_task: { kind: "read", readOnly: true },
|
|
69
|
+
tracker_get_task: { kind: "read", readOnly: true },
|
|
70
|
+
tracker_list_tasks: { kind: "read", readOnly: true },
|
|
71
|
+
tracker_add_dependency: { kind: "read", readOnly: true },
|
|
72
|
+
tracker_visualize: { kind: "read", readOnly: true },
|
|
73
|
+
};
|
|
74
|
+
function str(v) {
|
|
75
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
76
|
+
}
|
|
77
|
+
function obj(v) {
|
|
78
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
79
|
+
}
|
|
80
|
+
function strings(v) {
|
|
81
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
82
|
+
}
|
|
83
|
+
export function runIdOf(m) {
|
|
84
|
+
return `gemini:${str(m.session_id) ?? "unknown"}`;
|
|
85
|
+
}
|
|
86
|
+
/** Gemini sends no call id. Derive one from what identifies the call, so the AfterTool outcome finds its decision. */
|
|
87
|
+
export function geminiCallId(sessionId, tool, input) {
|
|
88
|
+
return "g_" + createHash("sha256").update(sessionId).update("\0").update(tool).update("\0").update(JSON.stringify(input ?? null)).digest("hex").slice(0, 32);
|
|
89
|
+
}
|
|
90
|
+
const URL_RE = /https?:\/\/[^\s<>"'`)\]]+/gi;
|
|
91
|
+
/** The URLs named in a web_fetch prompt, external ones first, so the judged `url` is the one that leaves the machine. */
|
|
92
|
+
export function urlsInPrompt(prompt) {
|
|
93
|
+
const found = (prompt.match(URL_RE) ?? []).map((u) => u.replace(/[.,;:!?]+$/, "")); // prose punctuation after a URL is not part of it
|
|
94
|
+
const external = [];
|
|
95
|
+
const internal = [];
|
|
96
|
+
for (const u of found) {
|
|
97
|
+
let host;
|
|
98
|
+
try {
|
|
99
|
+
host = new URL(u).hostname;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
(isInternalHost(host) ? internal : external).push(u);
|
|
105
|
+
}
|
|
106
|
+
return [...external, ...internal];
|
|
107
|
+
}
|
|
108
|
+
/** Map one Gemini tool call to what Yenop judges. */
|
|
109
|
+
function toolAndArgs(m, name, input) {
|
|
110
|
+
const ctx = obj(m.mcp_context);
|
|
111
|
+
const server = str(ctx["server_name"]);
|
|
112
|
+
const cwd = str(m.cwd);
|
|
113
|
+
if (server !== undefined)
|
|
114
|
+
return { tool: mcpToolRef(server, str(ctx["tool_name"]) ?? name), args: input, ...(cwd !== undefined ? { cwd } : {}) };
|
|
115
|
+
const known = BUILTIN[name];
|
|
116
|
+
if (known) {
|
|
117
|
+
const tool = { name, ...known };
|
|
118
|
+
switch (name) {
|
|
119
|
+
case "run_shell_command": {
|
|
120
|
+
const dir = str(input["dir_path"]);
|
|
121
|
+
const at = dir !== undefined && cwd !== undefined ? resolve(cwd, dir) : dir !== undefined ? resolve(dir) : cwd;
|
|
122
|
+
return { tool, args: input, ...(at !== undefined ? { cwd: at } : {}) };
|
|
123
|
+
}
|
|
124
|
+
case "read_many_files":
|
|
125
|
+
return { tool, args: { ...input, paths: strings(input["include"]) }, ...(cwd !== undefined ? { cwd } : {}) };
|
|
126
|
+
case "list_directory":
|
|
127
|
+
case "glob":
|
|
128
|
+
case "grep_search":
|
|
129
|
+
case "search_file_content": {
|
|
130
|
+
const dir = str(input["dir_path"]);
|
|
131
|
+
return { tool, args: dir !== undefined ? { ...input, path: dir } : input, ...(cwd !== undefined ? { cwd } : {}) };
|
|
132
|
+
}
|
|
133
|
+
case "save_memory":
|
|
134
|
+
// Persists into the user's global GEMINI.md: a write outside every project, and a persistence vector.
|
|
135
|
+
return { tool, args: { ...input, file_path: join(homedir(), ".gemini", "GEMINI.md"), content: input["fact"] ?? "" }, ...(cwd !== undefined ? { cwd } : {}) };
|
|
136
|
+
case "web_fetch": {
|
|
137
|
+
const urls = urlsInPrompt(str(input["prompt"]) ?? "");
|
|
138
|
+
return { tool, args: { ...input, urls, ...(urls[0] !== undefined ? { url: urls[0] } : {}) }, ...(cwd !== undefined ? { cwd } : {}) };
|
|
139
|
+
}
|
|
140
|
+
case "read_mcp_resource": {
|
|
141
|
+
const uri = str(input["uri"]);
|
|
142
|
+
let host = "unknown";
|
|
143
|
+
try {
|
|
144
|
+
if (uri)
|
|
145
|
+
host = new URL(uri).hostname || "unknown";
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
/* not a URL */
|
|
149
|
+
}
|
|
150
|
+
return { tool: { ...tool, server: host }, args: input, ...(cwd !== undefined ? { cwd } : {}) };
|
|
151
|
+
}
|
|
152
|
+
default:
|
|
153
|
+
return { tool, args: input, ...(cwd !== undefined ? { cwd } : {}) };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// An MCP tool without mcp_context: mcp_<server>_<tool>, sanitised. The split is a guess; the server is taken
|
|
157
|
+
// as the first segment and everything after it as the tool, which errs towards an unfamiliar (untrusted) server.
|
|
158
|
+
const mcp = /^mcp_([^_]+)_(.+)$/.exec(name);
|
|
159
|
+
if (mcp)
|
|
160
|
+
return { tool: mcpToolRef(mcp[1], mcp[2]), args: input, ...(cwd !== undefined ? { cwd } : {}) };
|
|
161
|
+
return { tool: classifyTool(name), args: input, ...(cwd !== undefined ? { cwd } : {}) };
|
|
162
|
+
}
|
|
163
|
+
export function parseGemini(raw) {
|
|
164
|
+
const m = JSON.parse(raw); // throws → deny
|
|
165
|
+
const event = str(m.hook_event_name);
|
|
166
|
+
if (!event)
|
|
167
|
+
throw new Error("gemini hook event without hook_event_name");
|
|
168
|
+
const session = str(m.session_id);
|
|
169
|
+
if (!session)
|
|
170
|
+
throw new Error("gemini hook event without session_id");
|
|
171
|
+
switch (event) {
|
|
172
|
+
case "BeforeTool": {
|
|
173
|
+
const name = str(m.tool_name);
|
|
174
|
+
if (name === undefined)
|
|
175
|
+
throw new Error("BeforeTool without tool_name");
|
|
176
|
+
const input = obj(m.tool_input);
|
|
177
|
+
const { tool, args, cwd } = toolAndArgs(m, name, input);
|
|
178
|
+
const request = { runId: runIdOf(m), principal: { runtime: "gemini", agent: "main", user: safeUser() }, tool, args, callId: geminiCallId(session, name, m.tool_input) };
|
|
179
|
+
if (cwd !== undefined)
|
|
180
|
+
request.cwd = cwd;
|
|
181
|
+
return { kind: "decision", event, askCapable: true, request };
|
|
182
|
+
}
|
|
183
|
+
case "AfterTool": {
|
|
184
|
+
const name = str(m.tool_name);
|
|
185
|
+
if (name === undefined)
|
|
186
|
+
return { kind: "ignore" };
|
|
187
|
+
const resp = obj(m.tool_response);
|
|
188
|
+
const failed = resp["error"] !== undefined && resp["error"] !== null;
|
|
189
|
+
const out = { kind: "outcome", runId: runIdOf(m), callId: geminiCallId(session, name, m.tool_input), tool: name, outcome: failed ? "failed" : "ran" };
|
|
190
|
+
const err = resp["error"];
|
|
191
|
+
const detail = typeof err === "string" ? err : err && typeof err === "object" ? str(err["message"]) : undefined;
|
|
192
|
+
if (failed && detail)
|
|
193
|
+
out.detail = detail;
|
|
194
|
+
const cwd = str(m.cwd);
|
|
195
|
+
if (cwd !== undefined)
|
|
196
|
+
out.cwd = cwd;
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
case "SessionEnd": {
|
|
200
|
+
const end = { kind: "session-end", runId: runIdOf(m) };
|
|
201
|
+
const cwd = str(m.cwd);
|
|
202
|
+
if (cwd !== undefined)
|
|
203
|
+
end.cwd = cwd;
|
|
204
|
+
return end;
|
|
205
|
+
}
|
|
206
|
+
default:
|
|
207
|
+
return { kind: "ignore" };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export function geminiBody(effect, message, mode) {
|
|
211
|
+
if (mode === "observe" || effect === "allow")
|
|
212
|
+
return null; // silence with exit 0 is an allow
|
|
213
|
+
if (effect === "deny")
|
|
214
|
+
return { decision: "deny", reason: `Yenop: ${message}` };
|
|
215
|
+
return { decision: "ask", reason: `Yenop: ${message}`, systemMessage: `Yenop: ${message}` };
|
|
216
|
+
}
|
|
217
|
+
export const geminiTranslator = {
|
|
218
|
+
runtime: "gemini",
|
|
219
|
+
parse: parseGemini,
|
|
220
|
+
body: (effect, message, mode) => geminiBody(effect, message, mode),
|
|
221
|
+
result(body) {
|
|
222
|
+
if (body === null || Object.keys(body).length === 0)
|
|
223
|
+
return { stdout: "", exitCode: 0 };
|
|
224
|
+
return { stdout: JSON.stringify(body), exitCode: 0 };
|
|
225
|
+
},
|
|
226
|
+
// Exit 0 on purpose: a JSON deny on exit 0 is honoured; a non-zero exit is a path Gemini may treat as a
|
|
227
|
+
// failed hook, and a failed hook is ignored.
|
|
228
|
+
failure(reason) {
|
|
229
|
+
return { stdout: JSON.stringify({ decision: "deny", reason: `Yenop failed and refuses by default: ${reason}. Run "yenop status".` }), exitCode: 0 };
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook.js","sourceRoot":"","sources":["../../../src/adapters/gemini/hook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const GEMINI_MARKER = "hook gemini";
|
|
2
|
+
export declare const GEMINI_DECISION_EVENT = "BeforeTool";
|
|
3
|
+
export declare const GEMINI_REPORT_EVENTS: readonly ["AfterTool", "SessionEnd"];
|
|
4
|
+
export declare const GEMINI_HOOK_NAME = "yenop";
|
|
5
|
+
export declare function geminiSettingsPath(scope: "project" | "user", projectDir?: string): string;
|
|
6
|
+
export declare function geminiDetected(projectDir: string): boolean;
|
|
7
|
+
export declare function installGeminiHooks(path: string, command: string): {
|
|
8
|
+
changed: boolean;
|
|
9
|
+
path: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Gemini keeps two switches that decide whether a project's hooks run at all, both under ~/.gemini:
|
|
13
|
+
* - trustedFolders.json: when folder trust is enabled, project hooks are skipped entirely in a folder that
|
|
14
|
+
* is not trusted ("Project hooks disabled because the folder is not trusted").
|
|
15
|
+
* - trusted_hooks.json: { "<project path>": ["<name>:<command>", ...] }; a project hook not listed there is
|
|
16
|
+
* shown to the person with a warning before it runs, and listed once acknowledged.
|
|
17
|
+
* `yenop status` reads both so "installed" is never mistaken for "enforcing". Found the hard way with Codex.
|
|
18
|
+
*/
|
|
19
|
+
export type GeminiTrust = "trusted" | "untrusted" | "unknown";
|
|
20
|
+
export declare function geminiFolderTrust(projectDir: string, file?: string): GeminiTrust;
|
|
21
|
+
export declare function geminiHookTrust(projectDir: string, command: string, file?: string): GeminiTrust;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install the Yenop hooks into a Gemini CLI settings.json (project: <root>/.gemini/settings.json, user:
|
|
3
|
+
* ~/.gemini/settings.json). Hooks live under the top-level "hooks" key: event → [{ matcher?, hooks: [...] }],
|
|
4
|
+
* timeouts in milliseconds. Idempotent: our entries are replaced, anyone else's are kept, every other setting
|
|
5
|
+
* in the file is untouched. Written atomically, since a half-written settings.json would make Gemini skip its
|
|
6
|
+
* hooks, and Gemini fails open.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
export const GEMINI_MARKER = "hook gemini";
|
|
12
|
+
export const GEMINI_DECISION_EVENT = "BeforeTool";
|
|
13
|
+
export const GEMINI_REPORT_EVENTS = ["AfterTool", "SessionEnd"];
|
|
14
|
+
export const GEMINI_HOOK_NAME = "yenop";
|
|
15
|
+
export function geminiSettingsPath(scope, projectDir) {
|
|
16
|
+
return scope === "user" ? join(homedir(), ".gemini", "settings.json") : join(projectDir ?? process.cwd(), ".gemini", "settings.json");
|
|
17
|
+
}
|
|
18
|
+
export function geminiDetected(projectDir) {
|
|
19
|
+
return existsSync(join(homedir(), ".gemini")) || existsSync(join(projectDir, ".gemini"));
|
|
20
|
+
}
|
|
21
|
+
const isOurs = (h) => typeof h.command === "string" && h.command.trim().endsWith(GEMINI_MARKER);
|
|
22
|
+
function writeAtomic(path, text) {
|
|
23
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
24
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
25
|
+
writeFileSync(tmp, text);
|
|
26
|
+
renameSync(tmp, path);
|
|
27
|
+
}
|
|
28
|
+
export function installGeminiHooks(path, command) {
|
|
29
|
+
const file = existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
|
|
30
|
+
const before = JSON.stringify(file);
|
|
31
|
+
file.hooks ??= {};
|
|
32
|
+
const put = (event, hook) => {
|
|
33
|
+
const groups = (file.hooks[event] ??= []);
|
|
34
|
+
const mine = groups.find((g) => g.hooks?.some(isOurs));
|
|
35
|
+
if (mine)
|
|
36
|
+
mine.hooks = [...mine.hooks.filter((h) => !isOurs(h)), hook];
|
|
37
|
+
else
|
|
38
|
+
groups.push({ hooks: [hook] }); // no matcher: every tool, built-in or MCP
|
|
39
|
+
};
|
|
40
|
+
// The hook's name is part of the key Gemini records when a person acknowledges it; keep it stable.
|
|
41
|
+
put(GEMINI_DECISION_EVENT, { type: "command", name: GEMINI_HOOK_NAME, command, timeout: 15000, description: "Yenop decides whether this action is allowed" });
|
|
42
|
+
put("AfterTool", { type: "command", name: `${GEMINI_HOOK_NAME}-outcome`, command, timeout: 5000, description: "Yenop records what became of the call" });
|
|
43
|
+
put("SessionEnd", { type: "command", name: `${GEMINI_HOOK_NAME}-session`, command, timeout: 3000, description: "Yenop closes the run" });
|
|
44
|
+
const after = JSON.stringify(file);
|
|
45
|
+
if (after === before)
|
|
46
|
+
return { changed: false, path };
|
|
47
|
+
writeAtomic(path, JSON.stringify(file, null, 2) + "\n");
|
|
48
|
+
return { changed: true, path };
|
|
49
|
+
}
|
|
50
|
+
function norm(p) {
|
|
51
|
+
const r = resolve(p);
|
|
52
|
+
return r.length > 1 && r.endsWith(sep) ? r.slice(0, -1) : r;
|
|
53
|
+
}
|
|
54
|
+
export function geminiFolderTrust(projectDir, file = join(homedir(), ".gemini", "trustedFolders.json")) {
|
|
55
|
+
if (!existsSync(file))
|
|
56
|
+
return "unknown";
|
|
57
|
+
let entries;
|
|
58
|
+
try {
|
|
59
|
+
entries = JSON.parse(readFileSync(file, "utf8"));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return "unknown";
|
|
63
|
+
}
|
|
64
|
+
const dir = norm(projectDir);
|
|
65
|
+
let verdict = "unknown";
|
|
66
|
+
for (const [p, v] of Object.entries(entries)) {
|
|
67
|
+
if (typeof v !== "string")
|
|
68
|
+
continue;
|
|
69
|
+
const at = v === "TRUST_PARENT" ? dirname(norm(p)) : norm(p);
|
|
70
|
+
const covers = dir === at || dir.startsWith(at + sep);
|
|
71
|
+
if (!covers)
|
|
72
|
+
continue;
|
|
73
|
+
if (v === "DO_NOT_TRUST") {
|
|
74
|
+
if (dir === norm(p))
|
|
75
|
+
return "untrusted";
|
|
76
|
+
}
|
|
77
|
+
else if (v === "TRUST_FOLDER" || v === "TRUST_PARENT")
|
|
78
|
+
verdict = "trusted";
|
|
79
|
+
}
|
|
80
|
+
return verdict;
|
|
81
|
+
}
|
|
82
|
+
export function geminiHookTrust(projectDir, command, file = join(homedir(), ".gemini", "trusted_hooks.json")) {
|
|
83
|
+
if (!existsSync(file))
|
|
84
|
+
return "unknown";
|
|
85
|
+
let entries;
|
|
86
|
+
try {
|
|
87
|
+
entries = JSON.parse(readFileSync(file, "utf8"));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return "unknown";
|
|
91
|
+
}
|
|
92
|
+
const dir = norm(projectDir);
|
|
93
|
+
const key = `${GEMINI_HOOK_NAME}:${command}`;
|
|
94
|
+
for (const [p, keys] of Object.entries(entries)) {
|
|
95
|
+
if (norm(p) !== dir || !Array.isArray(keys))
|
|
96
|
+
continue;
|
|
97
|
+
return keys.includes(key) ? "trusted" : "untrusted";
|
|
98
|
+
}
|
|
99
|
+
return "untrusted";
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/adapters/gemini/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { DecisionRequest, Outcome } from "../../core/types.js";
|
|
2
|
+
import type { Mode } from "../../core/config.js";
|
|
3
|
+
export type Effect = "allow" | "ask" | "deny";
|
|
4
|
+
export type HookEvent = {
|
|
5
|
+
kind: "decision";
|
|
6
|
+
request: DecisionRequest;
|
|
7
|
+
/** false when the runtime's hook can only allow or deny: an ask must then become a deny, never an allow. */
|
|
8
|
+
askCapable: boolean;
|
|
9
|
+
/** the runtime's own event name, for translators that answer differently per event */
|
|
10
|
+
event: string;
|
|
11
|
+
}
|
|
12
|
+
/** cwd on outcome and session-end selects the same project instance (tenant, policies) the decision used. */
|
|
13
|
+
| {
|
|
14
|
+
kind: "outcome";
|
|
15
|
+
runId: string;
|
|
16
|
+
callId: string;
|
|
17
|
+
tool: string;
|
|
18
|
+
outcome: Outcome;
|
|
19
|
+
detail?: string;
|
|
20
|
+
cwd?: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "session-end";
|
|
23
|
+
runId: string;
|
|
24
|
+
cwd?: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "ignore";
|
|
27
|
+
};
|
|
28
|
+
/** The project directory an event belongs to, whatever its kind. */
|
|
29
|
+
export declare function eventCwd(event: HookEvent): string | undefined;
|
|
30
|
+
export interface HookRunResult {
|
|
31
|
+
stdout: string;
|
|
32
|
+
exitCode: number;
|
|
33
|
+
/** Some runtimes read the reason for a block from stderr (Codex on exit 2). */
|
|
34
|
+
stderr?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface HookTranslator {
|
|
37
|
+
/** The runtime id, also the daemon route: /hooks/<runtime>. */
|
|
38
|
+
runtime: string;
|
|
39
|
+
/** Parse raw stdin. MUST throw on input it cannot make sense of; the pipeline turns that into a deny. */
|
|
40
|
+
parse(raw: string): HookEvent;
|
|
41
|
+
/** The runtime's answer for a decision. Always an object for a decision the runtime is waiting on. */
|
|
42
|
+
body(effect: Effect, message: string, mode: Mode, event: Extract<HookEvent, {
|
|
43
|
+
kind: "decision";
|
|
44
|
+
}>): Record<string, unknown> | null;
|
|
45
|
+
/** How to print a body: null or an empty object means print nothing. */
|
|
46
|
+
result(body: Record<string, unknown> | null): HookRunResult;
|
|
47
|
+
/** The answer when Yenop itself failed. MUST block. */
|
|
48
|
+
failure(reason: string): HookRunResult;
|
|
49
|
+
}
|
|
50
|
+
export declare function yenopHome(): string;
|
|
51
|
+
/**
|
|
52
|
+
* Decide one raw hook event for a runtime. Daemon first, in-process second. Throws only on a Yenop failure,
|
|
53
|
+
* which the caller renders with translator.failure() so the runtime blocks.
|
|
54
|
+
*/
|
|
55
|
+
export declare function runHookWith(t: HookTranslator, raw: string): Promise<HookRunResult>;
|
|
56
|
+
/** Apply one parsed event to an open Yenop and produce the runtime's body. Shared by the daemon route. */
|
|
57
|
+
export declare function decideEvent(yenop: {
|
|
58
|
+
decide: (r: DecisionRequest) => {
|
|
59
|
+
effect: Effect;
|
|
60
|
+
message: string;
|
|
61
|
+
};
|
|
62
|
+
recordOutcome: (runId: string, callId: string, tool: string, outcome: Outcome, detail?: string) => unknown;
|
|
63
|
+
endRun: (runId: string) => void;
|
|
64
|
+
config: {
|
|
65
|
+
mode: Mode;
|
|
66
|
+
};
|
|
67
|
+
}, t: HookTranslator, event: HookEvent): Record<string, unknown> | null;
|
|
68
|
+
export declare function readStdin(): Promise<string>;
|
|
69
|
+
export declare function safeUser(): string;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared hook pipeline. Every runtime that calls a command before acting (Claude Code, Cursor, Codex,
|
|
3
|
+
* Gemini CLI, ...) goes through this: one translator per runtime turns that runtime's event into a
|
|
4
|
+
* DecisionRequest and turns Yenop's decision back into the runtime's answer. The engine, the policies, the
|
|
5
|
+
* run state and the receipts never know which runtime asked.
|
|
6
|
+
*
|
|
7
|
+
* Security rules of this file, in order:
|
|
8
|
+
* - Fail closed. Input that cannot be parsed, or that a translator rejects, is a deny, never "no opinion".
|
|
9
|
+
* A permission hook that prints nothing when it should have judged is a bypass.
|
|
10
|
+
* - stdin is untrusted. Translators validate shape and never trust a field they did not check.
|
|
11
|
+
* - Nothing the agent can influence weakens enforcement: the only escape hatch is observe mode, which is
|
|
12
|
+
* operator configuration and is honoured exactly as in every other path.
|
|
13
|
+
*
|
|
14
|
+
* Performance: the fast path posts the raw event to the resident daemon over a raw socket (about a
|
|
15
|
+
* millisecond); the fallback decides in-process and then starts a daemon for next time. Both are shared, so
|
|
16
|
+
* a new runtime costs a translator and nothing else.
|
|
17
|
+
*/
|
|
18
|
+
import { homedir, userInfo } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
21
|
+
/** The project directory an event belongs to, whatever its kind. */
|
|
22
|
+
export function eventCwd(event) {
|
|
23
|
+
if (event.kind === "decision")
|
|
24
|
+
return event.request.cwd;
|
|
25
|
+
if (event.kind === "ignore")
|
|
26
|
+
return undefined;
|
|
27
|
+
return event.cwd;
|
|
28
|
+
}
|
|
29
|
+
export function yenopHome() {
|
|
30
|
+
return process.env["YENOP_HOME"] ?? join(homedir(), ".yenop");
|
|
31
|
+
}
|
|
32
|
+
function isEmpty(o) {
|
|
33
|
+
return o === null || Object.keys(o).length === 0;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decide one raw hook event for a runtime. Daemon first, in-process second. Throws only on a Yenop failure,
|
|
37
|
+
* which the caller renders with translator.failure() so the runtime blocks.
|
|
38
|
+
*/
|
|
39
|
+
export async function runHookWith(t, raw) {
|
|
40
|
+
const home = yenopHome();
|
|
41
|
+
const useDaemon = process.env["YENOP_NO_DAEMON"] !== "1";
|
|
42
|
+
recordRawEvent(t.runtime, raw);
|
|
43
|
+
if (useDaemon) {
|
|
44
|
+
const { readDaemonInfoFast, rawPost } = await import("../../daemon/fast.js");
|
|
45
|
+
const info = readDaemonInfoFast(home);
|
|
46
|
+
if (info) {
|
|
47
|
+
try {
|
|
48
|
+
const r = await rawPost(info, `/hooks/${t.runtime}`, raw, 1500);
|
|
49
|
+
if (r.status === 200) {
|
|
50
|
+
const body = JSON.parse(r.body);
|
|
51
|
+
return t.result(isEmpty(body) ? null : body);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* daemon not reachable: decide here */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const event = t.parse(raw); // throws on malformed input → caller fails closed
|
|
60
|
+
if (event.kind === "ignore")
|
|
61
|
+
return t.result(null);
|
|
62
|
+
const { openYenop } = await import("../../core/index.js");
|
|
63
|
+
const cwd = eventCwd(event);
|
|
64
|
+
const yenop = openYenop(cwd !== undefined ? { cwd } : {});
|
|
65
|
+
try {
|
|
66
|
+
return t.result(decideEvent(yenop, t, event));
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
yenop.close();
|
|
70
|
+
if (useDaemon) {
|
|
71
|
+
const { startDaemonDetached } = await import("../../daemon/client.js");
|
|
72
|
+
try {
|
|
73
|
+
startDaemonDetached(home);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* next call will try again */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Apply one parsed event to an open Yenop and produce the runtime's body. Shared by the daemon route. */
|
|
82
|
+
export function decideEvent(yenop, t, event) {
|
|
83
|
+
switch (event.kind) {
|
|
84
|
+
case "outcome":
|
|
85
|
+
yenop.recordOutcome(event.runId, event.callId, event.tool, event.outcome, event.detail);
|
|
86
|
+
return null;
|
|
87
|
+
case "session-end":
|
|
88
|
+
// the runtime told us the session ended: forget its run so a new session on the same id starts fresh
|
|
89
|
+
yenop.endRun(event.runId);
|
|
90
|
+
return null;
|
|
91
|
+
case "ignore":
|
|
92
|
+
return null;
|
|
93
|
+
case "decision": {
|
|
94
|
+
const d = yenop.decide(event.request);
|
|
95
|
+
return t.body(d.effect, d.message, yenop.config.mode, event);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Opt-in recorder for building test fixtures from what a runtime REALLY sends, rather than what its docs say.
|
|
101
|
+
* `YENOP_RECORD_HOOKS=<dir>` appends one line per event to <dir>/<runtime>.jsonl. Off by default. Recordings can
|
|
102
|
+
* contain file contents and secrets (Cursor's beforeReadFile carries the file), so they are reviewed and
|
|
103
|
+
* redacted before becoming fixtures; see docs/test-plan.md. Errors here never affect the decision.
|
|
104
|
+
*/
|
|
105
|
+
function recordRawEvent(runtime, raw) {
|
|
106
|
+
const dir = process.env["YENOP_RECORD_HOOKS"];
|
|
107
|
+
if (!dir)
|
|
108
|
+
return;
|
|
109
|
+
try {
|
|
110
|
+
mkdirSync(dir, { recursive: true });
|
|
111
|
+
appendFileSync(join(dir, `${runtime}.jsonl`), JSON.stringify({ runtime, ts: new Date().toISOString(), raw }) + "\n");
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* recording is a convenience; the decision must not depend on it */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export async function readStdin() {
|
|
118
|
+
const chunks = [];
|
|
119
|
+
for await (const c of process.stdin)
|
|
120
|
+
chunks.push(c);
|
|
121
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
122
|
+
}
|
|
123
|
+
export function safeUser() {
|
|
124
|
+
try {
|
|
125
|
+
return userInfo().username;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return "unknown";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../src/adapters/hooks/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtimes Yenop can hook. `yenop hook <runtime>` and the daemon's /hooks/<runtime> route both look here,
|
|
3
|
+
* so adding a runtime is one translator and one line. Loading is lazy on purpose: the command hook must stay
|
|
4
|
+
* close to bare Node startup, so it loads its own translator and nothing else.
|
|
5
|
+
*/
|
|
6
|
+
import type { HookTranslator } from "./pipeline.js";
|
|
7
|
+
export declare function hookTranslator(runtime: string): Promise<HookTranslator | undefined>;
|
|
8
|
+
export declare function hookRuntimes(): string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const LOADERS = {
|
|
2
|
+
"claude-code": async () => (await import("../claude-code/hook.js")).claudeCodeTranslator,
|
|
3
|
+
cursor: async () => (await import("../cursor/hook.js")).cursorTranslator,
|
|
4
|
+
codex: async () => (await import("../codex/hook.js")).codexTranslator,
|
|
5
|
+
gemini: async () => (await import("../gemini/hook.js")).geminiTranslator,
|
|
6
|
+
};
|
|
7
|
+
export async function hookTranslator(runtime) {
|
|
8
|
+
const load = Object.prototype.hasOwnProperty.call(LOADERS, runtime) ? LOADERS[runtime] : undefined;
|
|
9
|
+
return load ? load() : undefined;
|
|
10
|
+
}
|
|
11
|
+
export function hookRuntimes() {
|
|
12
|
+
return Object.keys(LOADERS);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/adapters/hooks/registry.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,GAAkD;IAC7D,aAAa,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC,oBAAoB;IACxF,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,gBAAgB;IACxE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,eAAe;IACrE,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,gBAAgB;CACzE,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAe;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnG,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACnC,CAAC;AACD,MAAM,UAAU,YAAY;IAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC9B,CAAC"}
|