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.
Files changed (103) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +243 -3
  3. package/dist/adapters/claude-code/hook.d.ts +43 -0
  4. package/dist/adapters/claude-code/hook.js +101 -0
  5. package/dist/adapters/claude-code/hook.js.map +1 -0
  6. package/dist/adapters/claude-code/install.d.ts +23 -0
  7. package/dist/adapters/claude-code/install.js +86 -0
  8. package/dist/adapters/claude-code/install.js.map +1 -0
  9. package/dist/adapters/codex/hook.d.ts +7 -0
  10. package/dist/adapters/codex/hook.js +134 -0
  11. package/dist/adapters/codex/hook.js.map +1 -0
  12. package/dist/adapters/codex/install.d.ts +18 -0
  13. package/dist/adapters/codex/install.js +66 -0
  14. package/dist/adapters/codex/install.js.map +1 -0
  15. package/dist/adapters/cursor/hook.d.ts +25 -0
  16. package/dist/adapters/cursor/hook.js +228 -0
  17. package/dist/adapters/cursor/hook.js.map +1 -0
  18. package/dist/adapters/cursor/install.d.ts +10 -0
  19. package/dist/adapters/cursor/install.js +57 -0
  20. package/dist/adapters/cursor/install.js.map +1 -0
  21. package/dist/adapters/gemini/hook.d.ts +20 -0
  22. package/dist/adapters/gemini/hook.js +232 -0
  23. package/dist/adapters/gemini/hook.js.map +1 -0
  24. package/dist/adapters/gemini/install.d.ts +21 -0
  25. package/dist/adapters/gemini/install.js +101 -0
  26. package/dist/adapters/gemini/install.js.map +1 -0
  27. package/dist/adapters/hooks/pipeline.d.ts +69 -0
  28. package/dist/adapters/hooks/pipeline.js +131 -0
  29. package/dist/adapters/hooks/pipeline.js.map +1 -0
  30. package/dist/adapters/hooks/registry.d.ts +8 -0
  31. package/dist/adapters/hooks/registry.js +14 -0
  32. package/dist/adapters/hooks/registry.js.map +1 -0
  33. package/dist/adapters/mcp/gateway.d.ts +64 -0
  34. package/dist/adapters/mcp/gateway.js +205 -0
  35. package/dist/adapters/mcp/gateway.js.map +1 -0
  36. package/dist/adapters/mcp/run.d.ts +12 -0
  37. package/dist/adapters/mcp/run.js +86 -0
  38. package/dist/adapters/mcp/run.js.map +1 -0
  39. package/dist/adapters/openai-agents/index.d.ts +74 -0
  40. package/dist/adapters/openai-agents/index.js +139 -0
  41. package/dist/adapters/openai-agents/index.js.map +1 -0
  42. package/dist/cli/demo.d.ts +5 -0
  43. package/dist/cli/demo.js +130 -0
  44. package/dist/cli/demo.js.map +1 -0
  45. package/dist/cli/main.d.ts +2 -0
  46. package/dist/cli/main.js +746 -0
  47. package/dist/cli/main.js.map +1 -0
  48. package/dist/core/config.d.ts +50 -0
  49. package/dist/core/config.js +93 -0
  50. package/dist/core/config.js.map +1 -0
  51. package/dist/core/engine.d.ts +31 -0
  52. package/dist/core/engine.js +301 -0
  53. package/dist/core/engine.js.map +1 -0
  54. package/dist/core/ids.d.ts +5 -0
  55. package/dist/core/ids.js +36 -0
  56. package/dist/core/ids.js.map +1 -0
  57. package/dist/core/index.d.ts +40 -0
  58. package/dist/core/index.js +73 -0
  59. package/dist/core/index.js.map +1 -0
  60. package/dist/core/policy.d.ts +41 -0
  61. package/dist/core/policy.js +205 -0
  62. package/dist/core/policy.js.map +1 -0
  63. package/dist/core/receipts.d.ts +47 -0
  64. package/dist/core/receipts.js +137 -0
  65. package/dist/core/receipts.js.map +1 -0
  66. package/dist/core/report.d.ts +51 -0
  67. package/dist/core/report.js +125 -0
  68. package/dist/core/report.js.map +1 -0
  69. package/dist/core/shell.d.ts +105 -0
  70. package/dist/core/shell.js +921 -0
  71. package/dist/core/shell.js.map +1 -0
  72. package/dist/core/state.d.ts +52 -0
  73. package/dist/core/state.js +269 -0
  74. package/dist/core/state.js.map +1 -0
  75. package/dist/core/telemetry.d.ts +52 -0
  76. package/dist/core/telemetry.js +150 -0
  77. package/dist/core/telemetry.js.map +1 -0
  78. package/dist/core/tools.d.ts +5 -0
  79. package/dist/core/tools.js +59 -0
  80. package/dist/core/tools.js.map +1 -0
  81. package/dist/core/types.d.ts +213 -0
  82. package/dist/core/types.js +3 -0
  83. package/dist/core/types.js.map +1 -0
  84. package/dist/daemon/client.d.ts +34 -0
  85. package/dist/daemon/client.js +138 -0
  86. package/dist/daemon/client.js.map +1 -0
  87. package/dist/daemon/fast.d.ts +9 -0
  88. package/dist/daemon/fast.js +43 -0
  89. package/dist/daemon/fast.js.map +1 -0
  90. package/dist/daemon/server.d.ts +38 -0
  91. package/dist/daemon/server.js +321 -0
  92. package/dist/daemon/server.js.map +1 -0
  93. package/dist/daemon/service.d.ts +26 -0
  94. package/dist/daemon/service.js +171 -0
  95. package/dist/daemon/service.js.map +1 -0
  96. package/dist/viewer/server.d.ts +52 -0
  97. package/dist/viewer/server.js +184 -0
  98. package/dist/viewer/server.js.map +1 -0
  99. package/package.json +59 -7
  100. package/policies/README.md +58 -0
  101. package/policies/approve/default.cedar +72 -0
  102. package/policies/permit/default.cedar +48 -0
  103. package/policies/schema.cedarschema +104 -0
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Codex CLI hooks adapter (`yenop hook codex`).
3
+ *
4
+ * Codex's hooks are modelled on Claude Code's (learn.chatgpt.com/docs/hooks): hooks.json in ~/.codex or
5
+ * <repo>/.codex, a snake_case JSON event on stdin, a camelCase answer on stdout. PreToolUse fires for Bash,
6
+ * for file edits made through `apply_patch`, for MCP tools (`mcp__server__tool`) and other local tools.
7
+ *
8
+ * Three facts about Codex shape the security decisions here:
9
+ * - Codex has no "ask". A hook answering `permissionDecision: "ask"` is recorded as a FAILED hook and the call
10
+ * proceeds. So every ask becomes a deny with a message; "ask" is never emitted.
11
+ * - A deny is honoured two ways: exit code 2 with the reason on stderr (the documented block path), or the
12
+ * JSON deny on stdout. We do both, so whichever Codex reads, it blocks.
13
+ * - A hook that crashes, times out, or prints invalid JSON FAILS OPEN in Codex, and there is no failClosed
14
+ * option. That is Codex's design; Yenop cannot change it. What Yenop can do is be reliable (daemon fast
15
+ * path, in-process fallback), print nothing at all on allow (silence is unambiguous), and say so in docs.
16
+ *
17
+ * apply_patch touches several files in one call. The patch text names them (`*** Update File: path` and
18
+ * friends); every path is judged and the strictest verdict wins, through args.paths.
19
+ */
20
+ import { classifyTool } from "../../core/tools.js";
21
+ import { safeUser } from "../hooks/pipeline.js";
22
+ const PATCH_TOOL = /^(apply_patch|edit|write)$/i;
23
+ const PATCH_LINE = /^\*\*\* (?:Add|Update|Delete) File: (.+)$/gm;
24
+ const PATCH_MOVE = /^\*\*\* Move to: (.+)$/gm;
25
+ function str(v) {
26
+ return typeof v === "string" && v.length > 0 ? v : undefined;
27
+ }
28
+ function obj(v) {
29
+ return v && typeof v === "object" && !Array.isArray(v) ? v : {};
30
+ }
31
+ /** The files a patch touches, from its headers. Exported so a recorded apply_patch event can be checked against it. */
32
+ export function pathsInPatch(patch) {
33
+ const out = new Set();
34
+ for (const m of patch.matchAll(PATCH_LINE))
35
+ out.add(m[1].trim());
36
+ for (const m of patch.matchAll(PATCH_MOVE))
37
+ out.add(m[1].trim());
38
+ return [...out];
39
+ }
40
+ function request(e, tool, args) {
41
+ const req = {
42
+ runId: `codex:${str(e.session_id) ?? "unknown"}`,
43
+ principal: { runtime: "codex", agent: "main", user: safeUser() },
44
+ tool,
45
+ args,
46
+ };
47
+ const cwd = str(e.cwd);
48
+ if (cwd !== undefined)
49
+ req.cwd = cwd;
50
+ const pm = str(e.permission_mode);
51
+ if (pm !== undefined)
52
+ req.permissionMode = pm;
53
+ const id = str(e.tool_use_id);
54
+ if (id !== undefined)
55
+ req.callId = id;
56
+ return req;
57
+ }
58
+ export function parseCodex(raw) {
59
+ const e = JSON.parse(raw); // throws → deny
60
+ const event = str(e.hook_event_name);
61
+ if (!event)
62
+ throw new Error("codex hook event without hook_event_name");
63
+ const runId = `codex:${str(e.session_id) ?? "unknown"}`;
64
+ switch (event) {
65
+ case "PreToolUse": {
66
+ const name = str(e.tool_name);
67
+ if (name === undefined)
68
+ throw new Error("PreToolUse without tool_name");
69
+ const input = e.tool_input;
70
+ if (PATCH_TOOL.test(name)) {
71
+ // apply_patch: Codex (recorded 2026-09-20, v0.155) sends the patch text as tool_input.command; older or
72
+ // other shapes use a bare string, or `patch` / `input`. The files the patch names are the targets.
73
+ const o = obj(input);
74
+ const patch = typeof input === "string" ? input : (str(o["command"]) ?? str(o["patch"]) ?? str(o["input"]) ?? "");
75
+ const paths = patch ? pathsInPatch(patch) : [];
76
+ const single = str(o["file_path"]) ?? str(o["path"]);
77
+ const all = single ? [single, ...paths] : paths;
78
+ // A file edit whose targets cannot be determined must not slip past the path rules. Refuse it, loudly,
79
+ // rather than judge an edit of unknown files as harmless: that is exactly how the hook file got edited.
80
+ if (all.length === 0)
81
+ throw new Error(`apply_patch with no recognizable target files (input keys: ${Object.keys(o).join(",") || typeof input})`);
82
+ const tool = { name, kind: "write", readOnly: false };
83
+ const args = { ...o, patch: patch.slice(0, 2000), file_path: all[0], paths: all };
84
+ return { kind: "decision", event, askCapable: false, request: request(e, tool, args) };
85
+ }
86
+ return { kind: "decision", event, askCapable: false, request: request(e, classifyTool(name), obj(input)) };
87
+ }
88
+ case "PostToolUse": {
89
+ const callId = str(e.tool_use_id);
90
+ const tool = str(e.tool_name);
91
+ if (!callId || !tool)
92
+ return { kind: "ignore" };
93
+ const cwd = str(e.cwd);
94
+ return cwd !== undefined ? { kind: "outcome", runId, callId, tool, outcome: "ran", cwd } : { kind: "outcome", runId, callId, tool, outcome: "ran" };
95
+ }
96
+ case "SessionEnd": {
97
+ const cwd = str(e.cwd);
98
+ return cwd !== undefined ? { kind: "session-end", runId, cwd } : { kind: "session-end", runId };
99
+ }
100
+ default:
101
+ return { kind: "ignore" };
102
+ }
103
+ }
104
+ function denyBody(reason) {
105
+ return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } };
106
+ }
107
+ export function codexBody(effect, message, mode) {
108
+ if (mode === "observe" || effect === "allow")
109
+ return null; // silence: Codex proceeds, and nothing can be misread
110
+ if (effect === "deny")
111
+ return denyBody(`Yenop: ${message}`);
112
+ // ask: Codex cannot ask a person, and "ask" would make it proceed. Tighten, never loosen.
113
+ return denyBody(`Yenop: ${message} Codex cannot ask a person for this step, so it is blocked: do it yourself, or allow it in policy.`);
114
+ }
115
+ function reasonOf(body) {
116
+ const h = body["hookSpecificOutput"];
117
+ return h?.permissionDecisionReason ?? "blocked by Yenop";
118
+ }
119
+ export const codexTranslator = {
120
+ runtime: "codex",
121
+ parse: parseCodex,
122
+ body: (effect, message, mode) => codexBody(effect, message, mode),
123
+ result(body) {
124
+ if (body === null || Object.keys(body).length === 0)
125
+ return { stdout: "", exitCode: 0 };
126
+ // exit 2 + stderr is the block path Codex documents; the JSON on stdout serves anything that reads it instead
127
+ return { stdout: JSON.stringify(body), stderr: reasonOf(body), exitCode: 2 };
128
+ },
129
+ failure(reason) {
130
+ const text = `Yenop failed and refuses by default: ${reason}. Run "yenop status".`;
131
+ return { stdout: JSON.stringify(denyBody(text)), stderr: text, exitCode: 2 };
132
+ },
133
+ };
134
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../../src/adapters/codex/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export declare const CODEX_MARKER = "hook codex";
2
+ export declare const CODEX_DECISION_EVENT = "PreToolUse";
3
+ export declare const CODEX_REPORT_EVENTS: readonly ["PostToolUse", "SessionEnd"];
4
+ export declare function codexHooksPath(scope: "project" | "user", projectDir?: string): string;
5
+ export declare function codexDetected(projectDir: string): boolean;
6
+ export declare function installCodexHooks(path: string, command: string): {
7
+ changed: boolean;
8
+ path: string;
9
+ };
10
+ /**
11
+ * Codex decides per hook whether it is trusted and enabled, and writes that into ~/.codex/config.toml as
12
+ * [hooks.state."<file>:<event>:<i>:<j>"] with trusted_hash and an optional `enabled = false`. A hook that is
13
+ * untrusted or disabled never runs, and a disabled PreToolUse means Yenop is not enforcing on Codex at all,
14
+ * silently. This reads that state so `yenop status` can say so. Found in the first live Codex session, where
15
+ * PreToolUse was left disabled while the two report hooks ran.
16
+ */
17
+ export type CodexHookState = "active" | "disabled" | "untrusted" | "unknown";
18
+ export declare function codexHookState(hooksPath: string, event: "PreToolUse" | "PostToolUse" | "SessionEnd", configPath?: string): CodexHookState;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Install the Yenop hooks into a Codex hooks.json (project: <root>/.codex/hooks.json, user: ~/.codex/hooks.json).
3
+ * Same shape as Claude Code's settings: matcher groups, each with a list of command hooks. Idempotent: our
4
+ * entries are replaced, anyone else's are kept. Written atomically, since a half-written file would make Codex
5
+ * skip hooks entirely (it fails open).
6
+ */
7
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { homedir } from "node:os";
10
+ export const CODEX_MARKER = "hook codex";
11
+ export const CODEX_DECISION_EVENT = "PreToolUse";
12
+ export const CODEX_REPORT_EVENTS = ["PostToolUse", "SessionEnd"];
13
+ export function codexHooksPath(scope, projectDir) {
14
+ return scope === "user" ? join(homedir(), ".codex", "hooks.json") : join(projectDir ?? process.cwd(), ".codex", "hooks.json");
15
+ }
16
+ export function codexDetected(projectDir) {
17
+ return existsSync(join(homedir(), ".codex")) || existsSync(join(projectDir, ".codex"));
18
+ }
19
+ const isOurs = (h) => typeof h.command === "string" && h.command.trim().endsWith(CODEX_MARKER);
20
+ function writeAtomic(path, text) {
21
+ mkdirSync(dirname(path), { recursive: true });
22
+ const tmp = `${path}.${process.pid}.tmp`;
23
+ writeFileSync(tmp, text);
24
+ renameSync(tmp, path);
25
+ }
26
+ export function installCodexHooks(path, command) {
27
+ const file = existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
28
+ const before = JSON.stringify(file);
29
+ file.hooks ??= {};
30
+ const put = (event, hook) => {
31
+ const groups = (file.hooks[event] ??= []);
32
+ const mine = groups.find((g) => g.hooks?.some(isOurs));
33
+ if (mine)
34
+ mine.hooks = [...mine.hooks.filter((h) => !isOurs(h)), hook];
35
+ else
36
+ groups.push({ hooks: [hook] }); // no matcher: every tool
37
+ };
38
+ put(CODEX_DECISION_EVENT, { type: "command", command, timeout: 15, statusMessage: "Yenop is checking this action" });
39
+ put("PostToolUse", { type: "command", command, timeout: 5, async: true });
40
+ // Codex runs SessionEnd synchronously and clamps its timeout to 3 s; writing anything else earns a warning on every start
41
+ put("SessionEnd", { type: "command", command, timeout: 3 });
42
+ const after = JSON.stringify(file);
43
+ if (after === before)
44
+ return { changed: false, path };
45
+ writeAtomic(path, JSON.stringify(file, null, 2) + "\n");
46
+ return { changed: true, path };
47
+ }
48
+ export function codexHookState(hooksPath, event, configPath = join(homedir(), ".codex", "config.toml")) {
49
+ if (!existsSync(configPath))
50
+ return "unknown";
51
+ const snake = event.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
52
+ const key = `${hooksPath}:${snake}:`;
53
+ const text = readFileSync(configPath, "utf8");
54
+ // find the [hooks.state."<key>..."] table for this file+event and read its body
55
+ const re = new RegExp(`\\[hooks\\.state\\."${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^"]*"\\]([^[]*)`);
56
+ const m = re.exec(text);
57
+ if (!m)
58
+ return "untrusted";
59
+ const body = m[1] ?? "";
60
+ if (/^\s*enabled\s*=\s*false/m.test(body))
61
+ return "disabled";
62
+ if (/trusted_hash\s*=/.test(body))
63
+ return "active";
64
+ return "untrusted";
65
+ }
66
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/adapters/codex/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { Mode } from "../../core/config.js";
2
+ import { type Effect, type HookEvent, type HookTranslator } from "../hooks/pipeline.js";
3
+ /**
4
+ * Whether preToolUse may answer "allow" on an ask, leaving the ask to the specialised hook that follows and
5
+ * can show a person a prompt. Only for a kind whose specialised hook has been SEEN to fire after preToolUse
6
+ * allowed, in a live session; until then an ask in preToolUse is a deny, never an allow.
7
+ * - shell: proven 2026-09-23 with Cursor 3.21.18: preToolUse → beforeShellExecution → postToolUse, same call.
8
+ * - mcp: beforeMCPExecution is documented the same way but has not been seen live; stays strict.
9
+ */
10
+ export declare const DEFER_ASK_TO_SPECIALISED: Record<"shell" | "mcp", boolean>;
11
+ interface CursorBase {
12
+ conversation_id?: unknown;
13
+ generation_id?: unknown;
14
+ hook_event_name?: unknown;
15
+ workspace_roots?: unknown;
16
+ cwd?: unknown;
17
+ }
18
+ /** One id for one call, whichever hook carries it: preToolUse, the specialised hook, and the outcome agree. */
19
+ export declare function cursorCallId(base: CursorBase, kind: "shell" | "read" | "mcp", key: string): string;
20
+ export declare function parseCursor(raw: string): HookEvent;
21
+ export declare function cursorBody(effect: Effect, message: string, mode: Mode, ev: Extract<HookEvent, {
22
+ kind: "decision";
23
+ }>): Record<string, unknown>;
24
+ export declare const cursorTranslator: HookTranslator;
25
+ export {};
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Cursor hooks adapter (`yenop hook cursor`).
3
+ *
4
+ * Cursor runs a command before acting and reads a JSON answer. The contract (cursor.com/docs/hooks, and what
5
+ * Cursor 3.21 really sends, recorded 2026-09-23):
6
+ * - preToolUse {tool_name, tool_input, tool_use_id, cwd} → {permission: allow|deny, user_message}
7
+ * fires FIRST, for EVERY tool: Shell, Read, Write, Edit, Delete, Grep, Task, MCP:<tool>...
8
+ * - beforeShellExecution {command, cwd} → {permission: allow|ask|deny, user_message}
9
+ * - beforeMCPExecution {tool_name, tool_input (JSON string), mcp_server_name, url?, command?}
10
+ * → {permission: allow|ask|deny, user_message}
11
+ * - beforeReadFile {file_path, content, attachments} → {permission: allow|deny, user_message}
12
+ * - postToolUse / postToolUseFailure observe-only, carry tool_use_id
13
+ * - sessionEnd fire-and-forget
14
+ * Every event also carries conversation_id, generation_id, hook_event_name, workspace_roots, ...
15
+ *
16
+ * Security decisions baked in here:
17
+ * - A permission hook that prints invalid or empty JSON makes Cursor block (with failClosed), so for a decision
18
+ * we ALWAYS print a full answer, including in observe mode (where it is allow). Silence is never an answer.
19
+ * Found live: the first version ignored Shell in preToolUse, expecting beforeShellExecution to judge it, and
20
+ * Cursor blocked every command with "hook returned no output". Fail-closed held; the adapter was wrong.
21
+ * - preToolUse cannot ask. An ask there becomes a deny with a message saying why, never an allow. The
22
+ * specialised hooks that can ask fire only after preToolUse allowed, so deferring an ask to them is only
23
+ * safe once it is proven live that they fire; see DEFER_ASK_TO_SPECIALISED.
24
+ * - The same call reaches Yenop through preToolUse and then its specialised hook. Both derive the same call id
25
+ * from the conversation, generation and the command or path, so the second is a replay of the first: one
26
+ * decision, one receipt, and the outcome from postToolUse attaches to it.
27
+ * - Every tool name is judged. Known world-touching tools by what they do; a tool with no command, path or
28
+ * URL in its input is an internal tool (Task, Todo, ...) and judged as read-only rather than left silent.
29
+ * - tool_input on MCP events is a JSON string from the model's side of the fence: parsed defensively.
30
+ * - Malformed input throws; the pipeline turns that into a deny with exit 2. failClosed:true is set by the
31
+ * installer so a crash or timeout also blocks.
32
+ */
33
+ import { createHash } from "node:crypto";
34
+ import { classifyTool, mcpToolRef } from "../../core/tools.js";
35
+ import { safeUser } from "../hooks/pipeline.js";
36
+ /**
37
+ * Whether preToolUse may answer "allow" on an ask, leaving the ask to the specialised hook that follows and
38
+ * can show a person a prompt. Only for a kind whose specialised hook has been SEEN to fire after preToolUse
39
+ * allowed, in a live session; until then an ask in preToolUse is a deny, never an allow.
40
+ * - shell: proven 2026-09-23 with Cursor 3.21.18: preToolUse → beforeShellExecution → postToolUse, same call.
41
+ * - mcp: beforeMCPExecution is documented the same way but has not been seen live; stays strict.
42
+ */
43
+ export const DEFER_ASK_TO_SPECIALISED = { shell: true, mcp: false };
44
+ const MUTATING_TOOL = /(write|edit|create|delete|remove|replace|move|rename|patch|apply)/i;
45
+ const SHELL_TOOL = /^(shell|bash|terminal|run_terminal|run_terminal_cmd|runterminal)$/i;
46
+ const READ_TOOL = /^(read|read_file|readfile)$/i;
47
+ function str(v) {
48
+ return typeof v === "string" && v.length > 0 ? v : undefined;
49
+ }
50
+ function obj(v) {
51
+ return v && typeof v === "object" && !Array.isArray(v) ? v : {};
52
+ }
53
+ /** MCP tool_input arrives as a JSON string. A string that is not JSON becomes an empty object, never a crash. */
54
+ function jsonArgs(v) {
55
+ if (typeof v === "string") {
56
+ try {
57
+ return obj(JSON.parse(v));
58
+ }
59
+ catch {
60
+ return {};
61
+ }
62
+ }
63
+ return obj(v);
64
+ }
65
+ function firstRoot(v) {
66
+ return Array.isArray(v) && typeof v[0] === "string" ? v[0] : undefined;
67
+ }
68
+ function cwdOf(base, input) {
69
+ return (input && str(input["cwd"])) ?? str(base.cwd) ?? firstRoot(base.workspace_roots);
70
+ }
71
+ /** One id for one call, whichever hook carries it: preToolUse, the specialised hook, and the outcome agree. */
72
+ export function cursorCallId(base, kind, key) {
73
+ return "c_" + createHash("sha256").update(str(base.conversation_id) ?? "").update("\0").update(str(base.generation_id) ?? "").update("\0").update(kind).update("\0").update(key).digest("hex").slice(0, 32);
74
+ }
75
+ function request(base, tool, args, callId, cwd) {
76
+ const conv = str(base.conversation_id) ?? "unknown";
77
+ const req = { runId: `cursor:${conv}`, principal: { runtime: "cursor", agent: "main", user: safeUser() }, tool, args };
78
+ const at = cwd ?? cwdOf(base);
79
+ if (at !== undefined)
80
+ req.cwd = at;
81
+ if (callId !== undefined)
82
+ req.callId = callId;
83
+ return req;
84
+ }
85
+ /** preToolUse: what this tool is, from its name and what its input names. Never silent. */
86
+ function preToolUse(m, name, input, useId) {
87
+ const event = "preToolUse";
88
+ if (SHELL_TOOL.test(name)) {
89
+ const command = str(input["command"]);
90
+ if (command === undefined)
91
+ throw new Error("preToolUse Shell without command");
92
+ return { kind: "decision", event, askCapable: false, request: request(m, classifyTool("Bash"), { ...input, command }, cursorCallId(m, "shell", command), cwdOf(m, input)) };
93
+ }
94
+ const path = str(input["file_path"]) ?? str(input["path"]) ?? str(input["target_file"]);
95
+ if (READ_TOOL.test(name)) {
96
+ if (path === undefined)
97
+ throw new Error("preToolUse Read without file_path");
98
+ return { kind: "decision", event, askCapable: false, request: request(m, classifyTool("Read"), { ...input, file_path: path }, cursorCallId(m, "read", path)) };
99
+ }
100
+ const mcp = /^MCP:(.+)$/.exec(name);
101
+ if (mcp) {
102
+ const args = jsonArgs(input["args"] ?? input["arguments"] ?? input);
103
+ return { kind: "decision", event, askCapable: false, request: request(m, mcpToolRef(str(input["server"]) ?? str(input["mcp_server_name"]) ?? "mcp", mcp[1]), args, cursorCallId(m, "mcp", mcp[1] + JSON.stringify(args))) };
104
+ }
105
+ if (MUTATING_TOOL.test(name) && path !== undefined) {
106
+ const tool = { name, kind: "write", readOnly: false };
107
+ return { kind: "decision", event, askCapable: false, request: request(m, tool, { ...input, file_path: path }, useId) };
108
+ }
109
+ const url = str(input["url"]);
110
+ if (url !== undefined)
111
+ return { kind: "decision", event, askCapable: false, request: request(m, { name, kind: "web", readOnly: !MUTATING_TOOL.test(name) }, input, useId) };
112
+ // Grep, Glob, ListDir, Search, Task, Todo, ...: reads inside the workspace or Cursor's own bookkeeping.
113
+ const tool = { name, kind: "read", readOnly: true };
114
+ return { kind: "decision", event, askCapable: false, request: request(m, tool, path !== undefined ? { ...input, file_path: path } : input, useId) };
115
+ }
116
+ export function parseCursor(raw) {
117
+ const m = JSON.parse(raw); // throws → deny
118
+ const event = str(m.hook_event_name);
119
+ if (!event)
120
+ throw new Error("cursor hook event without hook_event_name");
121
+ switch (event) {
122
+ case "beforeShellExecution": {
123
+ const command = str(m["command"]);
124
+ if (command === undefined)
125
+ throw new Error("beforeShellExecution without command");
126
+ return { kind: "decision", event, askCapable: true, request: request(m, classifyTool("Bash"), { command }, cursorCallId(m, "shell", command)) };
127
+ }
128
+ case "beforeMCPExecution": {
129
+ const name = str(m["tool_name"]);
130
+ const server = str(m["mcp_server_name"]) ?? "mcp";
131
+ if (name === undefined)
132
+ throw new Error("beforeMCPExecution without tool_name");
133
+ const args = jsonArgs(m["tool_input"]);
134
+ return { kind: "decision", event, askCapable: true, request: request(m, mcpToolRef(server, name), args, cursorCallId(m, "mcp", name + JSON.stringify(args))) };
135
+ }
136
+ case "beforeReadFile": {
137
+ const file = str(m["file_path"]);
138
+ if (file === undefined)
139
+ throw new Error("beforeReadFile without file_path");
140
+ return { kind: "decision", event, askCapable: false, request: request(m, classifyTool("Read"), { file_path: file }, cursorCallId(m, "read", file)) };
141
+ }
142
+ case "preToolUse": {
143
+ const name = str(m["tool_name"]);
144
+ if (name === undefined)
145
+ throw new Error("preToolUse without tool_name");
146
+ return preToolUse(m, name, obj(m["tool_input"]), str(m["tool_use_id"]));
147
+ }
148
+ case "postToolUse":
149
+ case "postToolUseFailure": {
150
+ const useId = str(m["tool_use_id"]);
151
+ const tool = str(m["tool_name"]);
152
+ if (!useId || !tool)
153
+ return { kind: "ignore" };
154
+ const input = obj(m["tool_input"]);
155
+ const command = str(input["command"]);
156
+ const path = str(input["file_path"]) ?? str(input["path"]) ?? str(input["target_file"]);
157
+ const callId = SHELL_TOOL.test(tool) && command !== undefined ? cursorCallId(m, "shell", command) : READ_TOOL.test(tool) && path !== undefined ? cursorCallId(m, "read", path) : useId;
158
+ const conv = str(m.conversation_id) ?? "unknown";
159
+ const failed = event === "postToolUseFailure";
160
+ const denied = failed && str(m["failure_type"]) === "permission_denied";
161
+ const out = { kind: "outcome", runId: `cursor:${conv}`, callId, tool, outcome: denied ? "denied" : failed ? "failed" : "ran" };
162
+ const detail = str(m["error_message"]);
163
+ if (failed && detail)
164
+ out.detail = detail;
165
+ const cwd = cwdOf(m, input);
166
+ if (cwd !== undefined)
167
+ out.cwd = cwd;
168
+ return out;
169
+ }
170
+ case "afterShellExecution": {
171
+ // Fires after a shell command actually executed (to be confirmed live: Cursor's postToolUse also fires for
172
+ // a command the person skipped). Carries the command and, when known, its exit code.
173
+ const command = str(m["command"]) ?? str(obj(m["tool_input"])["command"]);
174
+ if (command === undefined)
175
+ return { kind: "ignore" };
176
+ const code = m["exit_code"] ?? m["exitCode"] ?? obj(m["result"])["exitCode"];
177
+ const failed = typeof code === "number" && code !== 0;
178
+ const conv = str(m.conversation_id) ?? "unknown";
179
+ const out = { kind: "outcome", runId: `cursor:${conv}`, callId: cursorCallId(m, "shell", command), tool: "Shell", outcome: failed ? "failed" : "ran" };
180
+ if (failed)
181
+ out.detail = `exit code ${code}`;
182
+ const cwd = cwdOf(m);
183
+ if (cwd !== undefined)
184
+ out.cwd = cwd;
185
+ return out;
186
+ }
187
+ case "sessionEnd": {
188
+ const end = { kind: "session-end", runId: `cursor:${str(m["session_id"]) ?? str(m.conversation_id) ?? "unknown"}` };
189
+ const cwd = cwdOf(m);
190
+ if (cwd !== undefined)
191
+ end.cwd = cwd;
192
+ return end;
193
+ }
194
+ default:
195
+ return { kind: "ignore" };
196
+ }
197
+ }
198
+ function answer(permission, message) {
199
+ return message ? { permission, user_message: message } : { permission };
200
+ }
201
+ export function cursorBody(effect, message, mode, ev) {
202
+ if (mode === "observe")
203
+ return answer("allow"); // recorded, never blocked; and Cursor needs a real answer
204
+ if (effect === "allow")
205
+ return answer("allow");
206
+ if (effect === "deny")
207
+ return answer("deny", `Yenop: ${message}`);
208
+ if (ev.askCapable)
209
+ return answer("ask", `Yenop: ${message}`);
210
+ const kind = ev.request.tool.kind;
211
+ if (ev.event === "preToolUse" && (kind === "shell" || kind === "mcp") && DEFER_ASK_TO_SPECIALISED[kind])
212
+ return answer("allow"); // the hook that can ask comes next, with the same call id
213
+ return answer("deny", `Yenop: ${message} Cursor cannot ask a person for this step, so it is blocked: do it yourself, or allow it in policy.`);
214
+ }
215
+ export const cursorTranslator = {
216
+ runtime: "cursor",
217
+ parse: parseCursor,
218
+ body: cursorBody,
219
+ result(body) {
220
+ if (body === null || Object.keys(body).length === 0)
221
+ return { stdout: "", exitCode: 0 };
222
+ return { stdout: JSON.stringify(body), exitCode: 0 };
223
+ },
224
+ failure(reason) {
225
+ return { stdout: JSON.stringify(answer("deny", `Yenop failed and refuses by default: ${reason}. Run "yenop status".`)), exitCode: 2 };
226
+ },
227
+ };
228
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../../src/adapters/cursor/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export declare const CURSOR_MARKER = "hook cursor";
2
+ export declare const CURSOR_DECISION_EVENTS: readonly ["beforeShellExecution", "beforeMCPExecution", "beforeReadFile", "preToolUse"];
3
+ export declare const CURSOR_REPORT_EVENTS: readonly ["postToolUse", "postToolUseFailure", "afterShellExecution", "sessionEnd"];
4
+ export declare function cursorHooksPath(scope: "project" | "user", projectDir?: string): string;
5
+ export declare function installCursorHooks(path: string, command: string): {
6
+ changed: boolean;
7
+ path: string;
8
+ };
9
+ /** Is Cursor present on this machine or in this project? Used by `yenop init` to decide what to hook. */
10
+ export declare function cursorDetected(projectDir: string): boolean;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Install the Yenop hooks into a Cursor hooks.json (project: <root>/.cursor/hooks.json, user: ~/.cursor/hooks.json).
3
+ *
4
+ * Decision events get failClosed:true so a crash or timeout of the hook blocks the action instead of letting it
5
+ * through; that is the only safe default for a guard. Report events are observe-only and left fail-open so a
6
+ * hiccup never stalls the agent for a record-keeping call. Idempotent: our entries are replaced, anyone else's
7
+ * are kept.
8
+ */
9
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
10
+ import { dirname, join } from "node:path";
11
+ import { homedir } from "node:os";
12
+ export const CURSOR_MARKER = "hook cursor";
13
+ export const CURSOR_DECISION_EVENTS = ["beforeShellExecution", "beforeMCPExecution", "beforeReadFile", "preToolUse"];
14
+ // afterShellExecution: seen live 2026-09-23 that Cursor reports a command the person SKIPPED as a normal
15
+ // postToolUse with empty output and exit code 0, so postToolUse alone cannot say whether a shell call ran.
16
+ export const CURSOR_REPORT_EVENTS = ["postToolUse", "postToolUseFailure", "afterShellExecution", "sessionEnd"];
17
+ export function cursorHooksPath(scope, projectDir) {
18
+ return scope === "user" ? join(homedir(), ".cursor", "hooks.json") : join(projectDir ?? process.cwd(), ".cursor", "hooks.json");
19
+ }
20
+ function readHooks(path) {
21
+ if (!existsSync(path))
22
+ return { version: 1, hooks: {} };
23
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
24
+ return { version: typeof parsed.version === "number" ? parsed.version : 1, hooks: parsed.hooks && typeof parsed.hooks === "object" ? parsed.hooks : {} };
25
+ }
26
+ function isOurs(e) {
27
+ return typeof e.command === "string" && e.command.endsWith(CURSOR_MARKER);
28
+ }
29
+ /** Write the file atomically so a crash mid-write never leaves Cursor with a half file (which would fail open). */
30
+ function writeAtomic(path, text) {
31
+ mkdirSync(dirname(path), { recursive: true });
32
+ const tmp = `${path}.${process.pid}.tmp`;
33
+ writeFileSync(tmp, text);
34
+ renameSync(tmp, path);
35
+ }
36
+ export function installCursorHooks(path, command) {
37
+ const file = readHooks(path);
38
+ const before = JSON.stringify(file);
39
+ for (const ev of CURSOR_DECISION_EVENTS) {
40
+ const rest = (file.hooks[ev] ?? []).filter((e) => !isOurs(e));
41
+ file.hooks[ev] = [...rest, { command, type: "command", timeout: 15, failClosed: true }];
42
+ }
43
+ for (const ev of CURSOR_REPORT_EVENTS) {
44
+ const rest = (file.hooks[ev] ?? []).filter((e) => !isOurs(e));
45
+ file.hooks[ev] = [...rest, { command, type: "command", timeout: 5 }];
46
+ }
47
+ const after = JSON.stringify(file);
48
+ if (after === before)
49
+ return { changed: false, path };
50
+ writeAtomic(path, JSON.stringify(file, null, 2) + "\n");
51
+ return { changed: true, path };
52
+ }
53
+ /** Is Cursor present on this machine or in this project? Used by `yenop init` to decide what to hook. */
54
+ export function cursorDetected(projectDir) {
55
+ return existsSync(join(homedir(), ".cursor")) || existsSync(join(projectDir, ".cursor"));
56
+ }
57
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/adapters/cursor/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Mode } from "../../core/config.js";
2
+ import { type Effect, type HookEvent, type HookTranslator } from "../hooks/pipeline.js";
3
+ interface GeminiBase {
4
+ session_id?: unknown;
5
+ cwd?: unknown;
6
+ hook_event_name?: unknown;
7
+ tool_name?: unknown;
8
+ tool_input?: unknown;
9
+ mcp_context?: unknown;
10
+ tool_response?: unknown;
11
+ }
12
+ export declare function runIdOf(m: GeminiBase): string;
13
+ /** Gemini sends no call id. Derive one from what identifies the call, so the AfterTool outcome finds its decision. */
14
+ export declare function geminiCallId(sessionId: string, tool: string, input: unknown): string;
15
+ /** The URLs named in a web_fetch prompt, external ones first, so the judged `url` is the one that leaves the machine. */
16
+ export declare function urlsInPrompt(prompt: string): string[];
17
+ export declare function parseGemini(raw: string): HookEvent;
18
+ export declare function geminiBody(effect: Effect, message: string, mode: Mode): Record<string, unknown> | null;
19
+ export declare const geminiTranslator: HookTranslator;
20
+ export {};