@vibe-commit/capture 0.1.0 → 0.2.1

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 (59) hide show
  1. package/dist/agent_hooks.d.ts +133 -0
  2. package/dist/agent_hooks.js +228 -0
  3. package/dist/agent_hooks.js.map +1 -0
  4. package/dist/agent_install.d.ts +79 -0
  5. package/dist/agent_install.js +108 -0
  6. package/dist/agent_install.js.map +1 -0
  7. package/dist/agents/claude_code.js +12 -1
  8. package/dist/agents/claude_code.js.map +1 -1
  9. package/dist/agents/codex_cli.js +21 -1
  10. package/dist/agents/codex_cli.js.map +1 -1
  11. package/dist/agents/cursor.js +32 -6
  12. package/dist/agents/cursor.js.map +1 -1
  13. package/dist/agents/registry.d.ts +39 -1
  14. package/dist/agents/registry.js +47 -1
  15. package/dist/agents/registry.js.map +1 -1
  16. package/dist/agents/types.d.ts +76 -0
  17. package/dist/agents/types.js.map +1 -1
  18. package/dist/commands/auth.d.ts +66 -0
  19. package/dist/commands/auth.js +119 -0
  20. package/dist/commands/auth.js.map +1 -0
  21. package/dist/commands/connect.js +83 -1
  22. package/dist/commands/connect.js.map +1 -1
  23. package/dist/commands/status.js +18 -12
  24. package/dist/commands/status.js.map +1 -1
  25. package/dist/copy/index.d.ts +1 -1
  26. package/dist/copy/index.js +1 -1
  27. package/dist/copy/index.js.map +1 -1
  28. package/dist/copy/strings.d.ts +102 -4
  29. package/dist/copy/strings.js +114 -4
  30. package/dist/copy/strings.js.map +1 -1
  31. package/dist/credential.d.ts +46 -0
  32. package/dist/credential.js +59 -1
  33. package/dist/credential.js.map +1 -1
  34. package/dist/hooks/entry.d.ts +12 -1
  35. package/dist/hooks/entry.js +157 -12
  36. package/dist/hooks/entry.js.map +1 -1
  37. package/dist/hooks/fire_lock.d.ts +90 -0
  38. package/dist/hooks/fire_lock.js +192 -0
  39. package/dist/hooks/fire_lock.js.map +1 -0
  40. package/dist/hooks/post_commit.js +8 -2
  41. package/dist/hooks/post_commit.js.map +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.js +53 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/install.d.ts +8 -9
  46. package/dist/install.js +61 -6
  47. package/dist/install.js.map +1 -1
  48. package/dist/paths.d.ts +24 -0
  49. package/dist/paths.js +38 -0
  50. package/dist/paths.js.map +1 -1
  51. package/dist/spool.d.ts +83 -1
  52. package/dist/spool.js +196 -6
  53. package/dist/spool.js.map +1 -1
  54. package/dist/term.d.ts +9 -0
  55. package/dist/term.js +14 -0
  56. package/dist/term.js.map +1 -1
  57. package/dist/version.d.ts +1 -1
  58. package/dist/version.js +1 -1
  59. package/package.json +2 -2
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Registering our hook in an AGENT's own config — `CR-195`, D205.
3
+ *
4
+ * ⛔ **THIS IS NOT `install.ts`'s CHAINING, AND THE DIFFERENCE IS THE CONTAINER,
5
+ * NOT THE RULE.** The rule is the same one D154 settled for git hooks — *never
6
+ * clobber; whatever was there keeps working*. The MECHANISM cannot be the same:
7
+ *
8
+ * - `.git/hooks/post-commit` is a **single-slot executable file**. One path,
9
+ * one script, no way for two owners to coexist — so `writeHook` renames the
10
+ * incumbent aside and invokes it explicitly. That is the only move available.
11
+ * - `~/.claude/settings.json` is a **multi-slot document**, and `hooks.Stop`
12
+ * is an ARRAY that already expresses coexistence natively.
13
+ *
14
+ * ⛔ **So renaming here would be catastrophic rather than conservative.** That
15
+ * file holds the user's model, permissions, enabled plugins, marketplaces and
16
+ * theme; moving it aside to write our own would delete all of it, and on a
17
+ * machine with an existing `Stop` hook it would fire on the very first
18
+ * `connect`. The faithful translation of *never clobber* into this container is
19
+ * **append into the array and leave every other byte alone**.
20
+ *
21
+ * ## The three rules, and what each one is defending against
22
+ *
23
+ * 1. **APPEND, never replace.** A foreign entry is never removed, rewritten or
24
+ * reordered. Ours goes last.
25
+ * 2. **IDEMPOTENT via a marker.** JSON has nowhere to put a comment, so ours
26
+ * carries `_vibecommit`. Without it, `connect` run twice appends twice and
27
+ * the hook fires twice per event — two processes racing on one session
28
+ * state file. ⚠ The marker field is TOLERATED by all three readers, and
29
+ * that is measured rather than assumed: Codex parses and clamps with the
30
+ * field present on the inner and outer object alike, and Cursor's validator
31
+ * checks only known members (`HWu`/`jWu`/`$Wu` in its bundle).
32
+ * 3. ⛔ **REFUSE on unparseable, never overwrite.** A file we could not read is
33
+ * a file whose contents we cannot preserve. Overwriting it would be the
34
+ * destructive outcome wearing a success message — the exact defect class
35
+ * this whole unit exists to delete. Fail-closed, the same direction as
36
+ * `install.ts`'s `core.hooksPath` refusal.
37
+ *
38
+ * ## ⛔ TWO DOCUMENT SHAPES, MEASURED — they are NOT interchangeable
39
+ *
40
+ * The brief recorded all three agents as "identical to Claude Code". That is
41
+ * true of the STDIN payload and false of the CONFIG document:
42
+ *
43
+ * | | `claude` shape | `cursor` shape |
44
+ * |---|---|---|
45
+ * | used by | Claude Code, Codex CLI | Cursor |
46
+ * | top-level `version` | absent | ⛔ REQUIRED positive integer |
47
+ * | entry | `[{ hooks: [{ type, command, timeout }] }]` | ⛔ flat `[{ command, timeout }]` |
48
+ * | unknown event key | ignored | ⛔ validation ERROR |
49
+ *
50
+ * Cursor's is read out of its shipped bundle (`parseAndValidateHooksConfig` →
51
+ * `M6s` → `zWu` → `HWu`), which rejects a document with no numeric `version`
52
+ * outright. Codex's is MEASURED: a probe wrote the `claude` shape to a throwaway
53
+ * `CODEX_HOME` and Codex parsed it, recognised the event and clamped the
54
+ * timeout — which it could only do having read the inner entry.
55
+ *
56
+ * ⚠ **Writing the wrong shape does not error.** It produces a file that parses,
57
+ * registers nothing, and reports success — the same matches-nothing failure as
58
+ * an unrecognised event name, relocated to the document level.
59
+ */
60
+ /**
61
+ * What marks an entry as ours.
62
+ *
63
+ * ⚠ **A FIELD, not a substring of the command.** Matching on the command text
64
+ * would claim a user's own hook that happens to mention this product, and would
65
+ * lose track of ours the moment the binary path changed — which is exactly when
66
+ * idempotence matters, because that is a re-`connect`.
67
+ */
68
+ export declare const AGENT_HOOK_MARKER = "vibecommit-capture-hook v1";
69
+ /** Which document shape an agent's hook config uses. See the module docblock. */
70
+ export type HookDocShape = "claude" | "cursor";
71
+ /** One event we want registered, already in the agent's own vocabulary. */
72
+ export interface HookRegistration {
73
+ /** The agent's OWN spelling — `SessionEnd` or `sessionEnd`, never canonical. */
74
+ readonly event: string;
75
+ readonly command: string;
76
+ /** ⛔ SECONDS. Every one of the three configs is in seconds; our budgets are ms. */
77
+ readonly timeoutSec: number;
78
+ }
79
+ export type MergeOutcome = {
80
+ readonly kind: "merged";
81
+ readonly text: string;
82
+ /** Events where ours was newly appended. */
83
+ readonly added: readonly string[];
84
+ /** Events where ours was already present and was refreshed. */
85
+ readonly updated: readonly string[];
86
+ /** Foreign entries left untouched beside ours, across all events. */
87
+ readonly foreign: number;
88
+ }
89
+ /** ⛔ The document exists and could not be read. NOTHING is written. */
90
+ | {
91
+ readonly kind: "refused";
92
+ readonly why: string;
93
+ };
94
+ /**
95
+ * ⛔ **THE WHOLE MERGE, AS A PURE FUNCTION ON TEXT.**
96
+ *
97
+ * `raw` is the file's current contents, or `null` when it does not exist. No fs,
98
+ * no `home`, no clock — so the semantics that matter can be driven by a test
99
+ * without a temp directory, and the writer below is left with nothing but I/O.
100
+ * That split is deliberate: every rule in the module docblock is a property of
101
+ * this function, and none of them needs a real `~/.claude` to check.
102
+ */
103
+ export declare function mergeHookDocument(raw: string | null, shape: HookDocShape, registrations: readonly HookRegistration[]): MergeOutcome;
104
+ export type AgentHookInstall = {
105
+ readonly kind: "installed";
106
+ readonly path: string;
107
+ readonly events: readonly string[];
108
+ } | {
109
+ readonly kind: "already";
110
+ readonly path: string;
111
+ readonly events: readonly string[];
112
+ } | {
113
+ readonly kind: "refused";
114
+ readonly path: string;
115
+ readonly why: string;
116
+ } | {
117
+ readonly kind: "failed";
118
+ readonly path: string;
119
+ readonly why: string;
120
+ };
121
+ /**
122
+ * Merge our registrations into `path` and write it back.
123
+ *
124
+ * ⚠ **Written through a temp file in the same directory, then renamed.** A
125
+ * partial write here is the user's settings truncated, and `rename(2)` within a
126
+ * directory is atomic — so a crash mid-write leaves the original intact rather
127
+ * than a half-document that the next run would then REFUSE to touch, locking
128
+ * the user out of their own config.
129
+ *
130
+ * ⚠ The file's existing mode is preserved when there is one. A settings file
131
+ * the user tightened to `0600` must not be widened by us having rewritten it.
132
+ */
133
+ export declare function writeAgentHooks(path: string, shape: HookDocShape, registrations: readonly HookRegistration[]): AgentHookInstall;
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Registering our hook in an AGENT's own config — `CR-195`, D205.
3
+ *
4
+ * ⛔ **THIS IS NOT `install.ts`'s CHAINING, AND THE DIFFERENCE IS THE CONTAINER,
5
+ * NOT THE RULE.** The rule is the same one D154 settled for git hooks — *never
6
+ * clobber; whatever was there keeps working*. The MECHANISM cannot be the same:
7
+ *
8
+ * - `.git/hooks/post-commit` is a **single-slot executable file**. One path,
9
+ * one script, no way for two owners to coexist — so `writeHook` renames the
10
+ * incumbent aside and invokes it explicitly. That is the only move available.
11
+ * - `~/.claude/settings.json` is a **multi-slot document**, and `hooks.Stop`
12
+ * is an ARRAY that already expresses coexistence natively.
13
+ *
14
+ * ⛔ **So renaming here would be catastrophic rather than conservative.** That
15
+ * file holds the user's model, permissions, enabled plugins, marketplaces and
16
+ * theme; moving it aside to write our own would delete all of it, and on a
17
+ * machine with an existing `Stop` hook it would fire on the very first
18
+ * `connect`. The faithful translation of *never clobber* into this container is
19
+ * **append into the array and leave every other byte alone**.
20
+ *
21
+ * ## The three rules, and what each one is defending against
22
+ *
23
+ * 1. **APPEND, never replace.** A foreign entry is never removed, rewritten or
24
+ * reordered. Ours goes last.
25
+ * 2. **IDEMPOTENT via a marker.** JSON has nowhere to put a comment, so ours
26
+ * carries `_vibecommit`. Without it, `connect` run twice appends twice and
27
+ * the hook fires twice per event — two processes racing on one session
28
+ * state file. ⚠ The marker field is TOLERATED by all three readers, and
29
+ * that is measured rather than assumed: Codex parses and clamps with the
30
+ * field present on the inner and outer object alike, and Cursor's validator
31
+ * checks only known members (`HWu`/`jWu`/`$Wu` in its bundle).
32
+ * 3. ⛔ **REFUSE on unparseable, never overwrite.** A file we could not read is
33
+ * a file whose contents we cannot preserve. Overwriting it would be the
34
+ * destructive outcome wearing a success message — the exact defect class
35
+ * this whole unit exists to delete. Fail-closed, the same direction as
36
+ * `install.ts`'s `core.hooksPath` refusal.
37
+ *
38
+ * ## ⛔ TWO DOCUMENT SHAPES, MEASURED — they are NOT interchangeable
39
+ *
40
+ * The brief recorded all three agents as "identical to Claude Code". That is
41
+ * true of the STDIN payload and false of the CONFIG document:
42
+ *
43
+ * | | `claude` shape | `cursor` shape |
44
+ * |---|---|---|
45
+ * | used by | Claude Code, Codex CLI | Cursor |
46
+ * | top-level `version` | absent | ⛔ REQUIRED positive integer |
47
+ * | entry | `[{ hooks: [{ type, command, timeout }] }]` | ⛔ flat `[{ command, timeout }]` |
48
+ * | unknown event key | ignored | ⛔ validation ERROR |
49
+ *
50
+ * Cursor's is read out of its shipped bundle (`parseAndValidateHooksConfig` →
51
+ * `M6s` → `zWu` → `HWu`), which rejects a document with no numeric `version`
52
+ * outright. Codex's is MEASURED: a probe wrote the `claude` shape to a throwaway
53
+ * `CODEX_HOME` and Codex parsed it, recognised the event and clamped the
54
+ * timeout — which it could only do having read the inner entry.
55
+ *
56
+ * ⚠ **Writing the wrong shape does not error.** It produces a file that parses,
57
+ * registers nothing, and reports success — the same matches-nothing failure as
58
+ * an unrecognised event name, relocated to the document level.
59
+ */
60
+ import { chmodSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
61
+ import { dirname, join } from "node:path";
62
+ /**
63
+ * What marks an entry as ours.
64
+ *
65
+ * ⚠ **A FIELD, not a substring of the command.** Matching on the command text
66
+ * would claim a user's own hook that happens to mention this product, and would
67
+ * lose track of ours the moment the binary path changed — which is exactly when
68
+ * idempotence matters, because that is a re-`connect`.
69
+ */
70
+ export const AGENT_HOOK_MARKER = "vibecommit-capture-hook v1";
71
+ /** The field carrying it. Leading underscore: a hint that it is not the agent's. */
72
+ const MARKER_KEY = "_vibecommit";
73
+ /** The `version` we mint when CREATING a `cursor` document. */
74
+ const CURSOR_CONFIG_VERSION = 1;
75
+ /**
76
+ * ⛔ **THE WHOLE MERGE, AS A PURE FUNCTION ON TEXT.**
77
+ *
78
+ * `raw` is the file's current contents, or `null` when it does not exist. No fs,
79
+ * no `home`, no clock — so the semantics that matter can be driven by a test
80
+ * without a temp directory, and the writer below is left with nothing but I/O.
81
+ * That split is deliberate: every rule in the module docblock is a property of
82
+ * this function, and none of them needs a real `~/.claude` to check.
83
+ */
84
+ export function mergeHookDocument(raw, shape, registrations) {
85
+ let doc;
86
+ if (raw === null || raw.trim() === "") {
87
+ doc = {};
88
+ }
89
+ else {
90
+ let parsed;
91
+ try {
92
+ parsed = JSON.parse(raw);
93
+ }
94
+ catch (error) {
95
+ // ⛔ RULE 3. We hold the user's settings in our hands here; a parse we do
96
+ // not understand is not permission to replace them.
97
+ return {
98
+ kind: "refused",
99
+ why: error instanceof Error ? error.message : "not valid JSON",
100
+ };
101
+ }
102
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
103
+ return { kind: "refused", why: "the document is not a JSON object" };
104
+ }
105
+ doc = { ...parsed };
106
+ }
107
+ // ⛔ MINTED ONLY ON CREATE. An existing `version` is the user's (or another
108
+ // tool's) and is left exactly as found — bumping someone else's schema version
109
+ // is the same class of act as renaming their settings file, in a smaller hat.
110
+ if (shape === "cursor" && doc.version === undefined) {
111
+ doc.version = CURSOR_CONFIG_VERSION;
112
+ }
113
+ const existingHooks = doc.hooks;
114
+ if (existingHooks !== undefined) {
115
+ if (existingHooks === null || typeof existingHooks !== "object" || Array.isArray(existingHooks)) {
116
+ // Present and not an object: we cannot merge into it without destroying it.
117
+ return { kind: "refused", why: "`hooks` is present but is not an object" };
118
+ }
119
+ }
120
+ const hooks = {
121
+ ...(existingHooks ?? {}),
122
+ };
123
+ const added = [];
124
+ const updated = [];
125
+ let foreign = 0;
126
+ for (const registration of registrations) {
127
+ const current = hooks[registration.event];
128
+ if (current !== undefined && !Array.isArray(current)) {
129
+ return {
130
+ kind: "refused",
131
+ why: `\`hooks.${registration.event}\` is present but is not an array`,
132
+ };
133
+ }
134
+ const entries = [...(current ?? [])];
135
+ const ours = entryFor(shape, registration);
136
+ const at = entries.findIndex(isOurs);
137
+ if (at >= 0) {
138
+ // ⛔ REFRESH IN PLACE, and the position is kept. The binary path moves on
139
+ // every reinstall, so this arm is the ordinary one on a re-`connect` — and
140
+ // appending instead would grow the array without bound.
141
+ entries[at] = ours;
142
+ updated.push(registration.event);
143
+ }
144
+ else {
145
+ entries.push(ours);
146
+ added.push(registration.event);
147
+ }
148
+ foreign += entries.filter((entry) => !isOurs(entry)).length;
149
+ hooks[registration.event] = entries;
150
+ }
151
+ doc.hooks = hooks;
152
+ return { kind: "merged", text: `${JSON.stringify(doc, null, 2)}\n`, added, updated, foreign };
153
+ }
154
+ /** Ours? ⛔ By the marker alone — never by the command, which changes. */
155
+ function isOurs(entry) {
156
+ return (entry !== null &&
157
+ typeof entry === "object" &&
158
+ !Array.isArray(entry) &&
159
+ entry[MARKER_KEY] === AGENT_HOOK_MARKER);
160
+ }
161
+ /** Our entry, in the shape that agent's reader actually validates. */
162
+ function entryFor(shape, registration) {
163
+ if (shape === "cursor") {
164
+ // ⛔ FLAT. `zWu` validates `hooks[event]` as an array of hook SCRIPTS, and
165
+ // `HWu` requires a `command` on each — so a nested `{ hooks: [...] }` object
166
+ // is rejected outright for having none.
167
+ return {
168
+ [MARKER_KEY]: AGENT_HOOK_MARKER,
169
+ command: registration.command,
170
+ timeout: registration.timeoutSec,
171
+ };
172
+ }
173
+ // ⛔ NESTED, and `matcher` is deliberately omitted rather than set to `"*"` —
174
+ // these three events carry no tool name to match on, and the plugin's own
175
+ // `hooks.json` omits it too.
176
+ return {
177
+ [MARKER_KEY]: AGENT_HOOK_MARKER,
178
+ hooks: [
179
+ { type: "command", command: registration.command, timeout: registration.timeoutSec },
180
+ ],
181
+ };
182
+ }
183
+ /**
184
+ * Merge our registrations into `path` and write it back.
185
+ *
186
+ * ⚠ **Written through a temp file in the same directory, then renamed.** A
187
+ * partial write here is the user's settings truncated, and `rename(2)` within a
188
+ * directory is atomic — so a crash mid-write leaves the original intact rather
189
+ * than a half-document that the next run would then REFUSE to touch, locking
190
+ * the user out of their own config.
191
+ *
192
+ * ⚠ The file's existing mode is preserved when there is one. A settings file
193
+ * the user tightened to `0600` must not be widened by us having rewritten it.
194
+ */
195
+ export function writeAgentHooks(path, shape, registrations) {
196
+ let raw = null;
197
+ let mode;
198
+ try {
199
+ raw = readFileSync(path, "utf8");
200
+ mode = statSync(path).mode & 0o777;
201
+ }
202
+ catch {
203
+ // Absent is the ordinary case on a first install; anything else surfaces on
204
+ // the write below rather than being guessed at here.
205
+ raw = null;
206
+ }
207
+ const merged = mergeHookDocument(raw, shape, registrations);
208
+ if (merged.kind === "refused")
209
+ return { kind: "refused", path, why: merged.why };
210
+ const temp = join(dirname(path), `.${AGENT_HOOK_MARKER.replace(/\W+/g, "-")}.tmp`);
211
+ try {
212
+ mkdirSync(dirname(path), { recursive: true });
213
+ writeFileSync(temp, merged.text, { mode: mode ?? 0o600 });
214
+ if (mode !== undefined)
215
+ chmodSync(temp, mode);
216
+ renameSync(temp, path);
217
+ }
218
+ catch (error) {
219
+ return { kind: "failed", path, why: error instanceof Error ? error.message : "write failed" };
220
+ }
221
+ const events = registrations.map((registration) => registration.event);
222
+ // "already" is not "nothing happened" — the command was still refreshed. It is
223
+ // the answer to *did this connect ADD anything*, which is what a user reads.
224
+ return merged.added.length === 0
225
+ ? { kind: "already", path, events }
226
+ : { kind: "installed", path, events };
227
+ }
228
+ //# sourceMappingURL=agent_hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent_hooks.js","sourceRoot":"","sources":["../src/agent_hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Registering capture with every agent on this machine — `CR-195`/U4b, D205.
3
+ *
4
+ * ⛔ **THIS IS THE FILE `/install` HAS BEEN DESCRIBING SINCE BEFORE IT EXISTED.**
5
+ * `app/install/page.tsx` told users that `vibecommit connect` *"wires Claude Code
6
+ * hooks on Stop, PreCompact and SessionEnd"*. It did not: `connect` wrote GIT
7
+ * hooks only, and nothing in `src/` ever wrote `settings.json`. The three events
8
+ * were registered in exactly one place — `claude-plugin/hooks/hooks.json` — so
9
+ * the npm path gave a user no session capture at all, silently, while the page
10
+ * said otherwise.
11
+ *
12
+ * ## What is written, and what decides it
13
+ *
14
+ * Everything below is DERIVED from the registry rather than restated here:
15
+ *
16
+ * - **which file** — `dialect.hookConfig.path(home, env)`, honouring
17
+ * `CLAUDE_CONFIG_DIR` and `CODEX_HOME` because those products honour them;
18
+ * - **which document shape** — `dialect.hookConfig.shape`, two shapes across
19
+ * three agents (Cursor's is its own);
20
+ * - **which event names** — `dialect.eventNames`, camelCase for Cursor;
21
+ * - **which timeouts** — `dialect.events[event].registeredTimeoutMs`, so the
22
+ * number written into an agent's config and the number this client checks
23
+ * its own watchdog against cannot drift apart.
24
+ *
25
+ * ⚠ That last one matters more than it looks. `EventBudget` requires
26
+ * `clientBudgetMs < registeredTimeoutMs`, and until this file existed the
27
+ * "registered" half was a MIRROR of a JSON file hand-maintained in another
28
+ * repository — `test/agent-registry.test.ts` says so, and says a drift in it is
29
+ * invisible from here. For the three files this writes, the mirror becomes the
30
+ * source.
31
+ *
32
+ * ## ⛔ Only agents that are actually here
33
+ *
34
+ * A config is written only when the directory that would contain it already
35
+ * exists. Creating `~/.cursor/hooks.json` on a machine with no Cursor is not
36
+ * harmless: it is a file the user never asked for, in a tool they do not run,
37
+ * that a future Cursor install would silently inherit. ⚠ The directory — never
38
+ * the config file — is the evidence, because the file's absence is the ordinary
39
+ * first-install case and is exactly what this function is for.
40
+ */
41
+ import { type AgentHookInstall, type HookRegistration } from "./agent_hooks.js";
42
+ import { type AgentId, type TranscriptDialect } from "./agents/types.js";
43
+ /** What happened for one agent. `null` install means the agent is not here. */
44
+ export interface AgentHookOutcome {
45
+ readonly agentId: AgentId;
46
+ readonly path: string;
47
+ readonly install: AgentHookInstall | null;
48
+ }
49
+ /**
50
+ * The command an agent's config invokes.
51
+ *
52
+ * ⛔ **`hook` LEADS, AND THE FLAG NEVER DOES.** `invocationMode` returns `"hook"`
53
+ * only when `argv[0] === "hook"`, so a registered command whose first argument
54
+ * is `--agent=` runs the binary INTERACTIVELY — inside a hook, printing to a
55
+ * stream the agent reads back into the user's turn. `registry.ts` names this as
56
+ * the trap the wave was most likely to ship, and `test/agent-registry.test.ts`
57
+ * holds both arms.
58
+ *
59
+ * ⚠ **The flag is passed explicitly for every agent, Claude Code included**,
60
+ * even though `DEFAULT_AGENT_ID` makes it redundant there. A config that names
61
+ * its own agent is self-describing, and — since Cursor reads Claude Code's
62
+ * settings and maps them onto its own events — it is the only thing that tells
63
+ * the two invocations apart when both fire.
64
+ */
65
+ export declare function hookCommand(binPath: string, agentId: AgentId): string;
66
+ /** Every event this client registers, in one agent's own vocabulary. */
67
+ export declare function registrationsFor(dialect: TranscriptDialect, binPath: string): readonly HookRegistration[];
68
+ /** Is this agent on the machine at all? The DIRECTORY, never the config file. */
69
+ export declare function agentPresent(dialect: TranscriptDialect, home: string, env: NodeJS.ProcessEnv): boolean;
70
+ /**
71
+ * Register capture with every agent present on this machine.
72
+ *
73
+ * ⚠ **Never throws and never partially aborts.** Each agent is independent: a
74
+ * refusal to overwrite one unparseable `settings.json` must not stop the other
75
+ * two being wired, and the caller reports all three. That is the same shape
76
+ * `installCaptureHooks` uses for its two git hooks and the same reason —
77
+ * a per-target verdict is the only honest answer when the targets are unrelated.
78
+ */
79
+ export declare function installAgentHooks(home: string, env: NodeJS.ProcessEnv, binPath: string): readonly AgentHookOutcome[];
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Registering capture with every agent on this machine — `CR-195`/U4b, D205.
3
+ *
4
+ * ⛔ **THIS IS THE FILE `/install` HAS BEEN DESCRIBING SINCE BEFORE IT EXISTED.**
5
+ * `app/install/page.tsx` told users that `vibecommit connect` *"wires Claude Code
6
+ * hooks on Stop, PreCompact and SessionEnd"*. It did not: `connect` wrote GIT
7
+ * hooks only, and nothing in `src/` ever wrote `settings.json`. The three events
8
+ * were registered in exactly one place — `claude-plugin/hooks/hooks.json` — so
9
+ * the npm path gave a user no session capture at all, silently, while the page
10
+ * said otherwise.
11
+ *
12
+ * ## What is written, and what decides it
13
+ *
14
+ * Everything below is DERIVED from the registry rather than restated here:
15
+ *
16
+ * - **which file** — `dialect.hookConfig.path(home, env)`, honouring
17
+ * `CLAUDE_CONFIG_DIR` and `CODEX_HOME` because those products honour them;
18
+ * - **which document shape** — `dialect.hookConfig.shape`, two shapes across
19
+ * three agents (Cursor's is its own);
20
+ * - **which event names** — `dialect.eventNames`, camelCase for Cursor;
21
+ * - **which timeouts** — `dialect.events[event].registeredTimeoutMs`, so the
22
+ * number written into an agent's config and the number this client checks
23
+ * its own watchdog against cannot drift apart.
24
+ *
25
+ * ⚠ That last one matters more than it looks. `EventBudget` requires
26
+ * `clientBudgetMs < registeredTimeoutMs`, and until this file existed the
27
+ * "registered" half was a MIRROR of a JSON file hand-maintained in another
28
+ * repository — `test/agent-registry.test.ts` says so, and says a drift in it is
29
+ * invisible from here. For the three files this writes, the mirror becomes the
30
+ * source.
31
+ *
32
+ * ## ⛔ Only agents that are actually here
33
+ *
34
+ * A config is written only when the directory that would contain it already
35
+ * exists. Creating `~/.cursor/hooks.json` on a machine with no Cursor is not
36
+ * harmless: it is a file the user never asked for, in a tool they do not run,
37
+ * that a future Cursor install would silently inherit. ⚠ The directory — never
38
+ * the config file — is the evidence, because the file's absence is the ordinary
39
+ * first-install case and is exactly what this function is for.
40
+ */
41
+ import { existsSync } from "node:fs";
42
+ import { dirname } from "node:path";
43
+ import { writeAgentHooks } from "./agent_hooks.js";
44
+ import { DIALECTS } from "./agents/registry.js";
45
+ import { HOOK_EVENTS } from "./agents/types.js";
46
+ /**
47
+ * The command an agent's config invokes.
48
+ *
49
+ * ⛔ **`hook` LEADS, AND THE FLAG NEVER DOES.** `invocationMode` returns `"hook"`
50
+ * only when `argv[0] === "hook"`, so a registered command whose first argument
51
+ * is `--agent=` runs the binary INTERACTIVELY — inside a hook, printing to a
52
+ * stream the agent reads back into the user's turn. `registry.ts` names this as
53
+ * the trap the wave was most likely to ship, and `test/agent-registry.test.ts`
54
+ * holds both arms.
55
+ *
56
+ * ⚠ **The flag is passed explicitly for every agent, Claude Code included**,
57
+ * even though `DEFAULT_AGENT_ID` makes it redundant there. A config that names
58
+ * its own agent is self-describing, and — since Cursor reads Claude Code's
59
+ * settings and maps them onto its own events — it is the only thing that tells
60
+ * the two invocations apart when both fire.
61
+ */
62
+ export function hookCommand(binPath, agentId) {
63
+ return `${quote(binPath)} hook --agent=${agentId}`;
64
+ }
65
+ /** Double-quote for the shells these configs are read by. */
66
+ function quote(value) {
67
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
68
+ }
69
+ /** Every event this client registers, in one agent's own vocabulary. */
70
+ export function registrationsFor(dialect, binPath) {
71
+ return HOOK_EVENTS.map((event) => ({
72
+ event: dialect.eventNames[event],
73
+ command: hookCommand(binPath, dialect.id),
74
+ // ⛔ SECONDS, from the registry's milliseconds. Every one of the three
75
+ // configs takes seconds; `EventBudget` is in milliseconds because the
76
+ // client's own watchdog is. For Codex this lands `SessionEnd` on 3 s, which
77
+ // is exactly the cap Codex clamps to whatever we ask for — measured, so the
78
+ // number we write is the number that takes effect.
79
+ timeoutSec: Math.max(1, Math.round(dialect.events[event].registeredTimeoutMs / 1000)),
80
+ }));
81
+ }
82
+ /** Is this agent on the machine at all? The DIRECTORY, never the config file. */
83
+ export function agentPresent(dialect, home, env) {
84
+ return existsSync(dirname(dialect.hookConfig.path(home, env)));
85
+ }
86
+ /**
87
+ * Register capture with every agent present on this machine.
88
+ *
89
+ * ⚠ **Never throws and never partially aborts.** Each agent is independent: a
90
+ * refusal to overwrite one unparseable `settings.json` must not stop the other
91
+ * two being wired, and the caller reports all three. That is the same shape
92
+ * `installCaptureHooks` uses for its two git hooks and the same reason —
93
+ * a per-target verdict is the only honest answer when the targets are unrelated.
94
+ */
95
+ export function installAgentHooks(home, env, binPath) {
96
+ return DIALECTS.map((dialect) => {
97
+ const path = dialect.hookConfig.path(home, env);
98
+ if (!agentPresent(dialect, home, env)) {
99
+ return { agentId: dialect.id, path, install: null };
100
+ }
101
+ return {
102
+ agentId: dialect.id,
103
+ path,
104
+ install: writeAgentHooks(path, dialect.hookConfig.shape, registrationsFor(dialect, binPath)),
105
+ };
106
+ });
107
+ }
108
+ //# sourceMappingURL=agent_install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent_install.js","sourceRoot":"","sources":["../src/agent_install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,eAAe,EAAgD,MAAM,kBAAkB,CAAC;AACjG,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAwC,MAAM,mBAAmB,CAAC;AAStF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,OAAgB;IAC3D,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,iBAAiB,OAAO,EAAE,CAAC;AACrD,CAAC;AAED,6DAA6D;AAC7D,SAAS,KAAK,CAAC,KAAa;IAC1B,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC;AACpD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,gBAAgB,CAC9B,OAA0B,EAC1B,OAAe;IAEf,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACjC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC;QAChC,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;QACzC,sEAAsE;QACtE,sEAAsE;QACtE,4EAA4E;QAC5E,4EAA4E;QAC5E,mDAAmD;QACnD,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;KACtF,CAAC,CAAC,CAAC;AACN,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,OAA0B,EAAE,IAAY,EAAE,GAAsB;IAC3F,OAAO,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAY,EACZ,GAAsB,EACtB,OAAe;IAEf,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChD,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YACtC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACtD,CAAC;QACD,OAAO;YACL,OAAO,EAAE,OAAO,CAAC,EAAE;YACnB,IAAI;YACJ,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;SAC7F,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { readdirSync, statSync } from "node:fs";
11
11
  import { basename, join } from "node:path";
12
- import { isInside, isTranscriptFile, transcriptRoot } from "../paths.js";
12
+ import { claudeSettingsPath, isInside, isTranscriptFile, transcriptRoot } from "../paths.js";
13
13
  /**
14
14
  * The client-side backstop, per event.
15
15
  *
@@ -138,6 +138,17 @@ export const CLAUDE_CODE = {
138
138
  // `subagents/` directory, so every file here is already a transcript; there is
139
139
  // no interior shape to require, unlike Cursor's.
140
140
  admitsFile: isTranscriptFile,
141
+ // ⛔ IDENTITY, and it is the reference the other two are measured AGAINST —
142
+ // `HOOK_EVENTS` is spelled the way Claude Code spells it, because this is the
143
+ // agent the client was built for. Declared explicitly rather than defaulted so
144
+ // that a dialect added later must ANSWER the question; a default would let a
145
+ // camelCase agent inherit Claude's spellings and match nothing (`CR-195`).
146
+ eventNames: { Stop: "Stop", PreCompact: "PreCompact", SessionEnd: "SessionEnd" },
147
+ // ⛔ `~/.claude/settings.json`, honouring `CLAUDE_CONFIG_DIR`. ⚠ This is the
148
+ // file the PLUGIN path does not need — the plugin ships its own `hooks.json`
149
+ // — and the npm path had no writer at all, which is the defect `/install`
150
+ // has been advertising past since it first claimed `connect` wires hooks.
151
+ hookConfig: { path: claudeSettingsPath, shape: "claude" },
141
152
  events: {
142
153
  Stop: {
143
154
  registeredTimeoutMs: CLAUDE_REGISTERED_STOP_MS,
@@ -1 +1 @@
1
- {"version":3,"file":"claude_code.js","sourceRoot":"","sources":["../../src/agents/claude_code.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude_code.js","sourceRoot":"","sources":["../../src/agents/claude_code.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7F;;;;;;;;;GASG;AACH,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAEtC;;;;;;;;;;;;;GAaG;AACH,MAAM,yBAAyB,GAAG,KAAK,CAAC;AACxC,MAAM,+BAA+B,GAAG,KAAK,CAAC;AAC9C,MAAM,gCAAgC,GAAG,MAAM,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,SAAS,uBAAuB,CAC9B,IAAY,EACZ,GAAsB,EACtB,UAAkB;IAElB,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAEzD,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,IAAI,GAA4E,IAAI,CAAC;IACzF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,SAAS;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS;QACpC,IAAI,IAAI,CAAC;QACT,IAAI,CAAC;YACH,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,SAAS;QAChD,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/C,IAAI,GAAG;gBACL,IAAI;gBACJ,SAAS,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;gBACpC,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,OAAO;aACpB,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;AAChG,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,sBAAsB,CAAC,GAAsB;IACpD,MAAM,GAAG,GAAG,GAAG,CAAC,sBAAsB,CAAC;IACvC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACzB,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAsB;IAC5C,EAAE,EAAE,aAAa;IACjB,SAAS,EAAE,QAAQ;IACnB,cAAc;IACd,8EAA8E;IAC9E,8EAA8E;IAC9E,+EAA+E;IAC/E,iDAAiD;IACjD,UAAU,EAAE,gBAAgB;IAC5B,2EAA2E;IAC3E,8EAA8E;IAC9E,+EAA+E;IAC/E,6EAA6E;IAC7E,2EAA2E;IAC3E,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE;IAChF,4EAA4E;IAC5E,6EAA6E;IAC7E,0EAA0E;IAC1E,0EAA0E;IAC1E,UAAU,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,QAAQ,EAAE;IACzD,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,mBAAmB,EAAE,yBAAyB;YAC9C,cAAc,EAAE,uBAAuB;SACxC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,+BAA+B;YACpD,cAAc,EAAE,uBAAuB;SACxC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,gCAAgC;YACrD,cAAc,EAAE,uBAAuB;SACxC;KACF;IACD,4EAA4E;IAC5E,uBAAuB;IACvB,oBAAoB,EAAE,UAAU;IAChC,SAAS,EAAE,aAAa;IACxB,gBAAgB,EAAE,sBAAsB;IACxC,uBAAuB;CACxB,CAAC"}
@@ -11,7 +11,7 @@
11
11
  * measured by this task.** They are recorded as the brief stated them so a later
12
12
  * reader can check them against the product rather than against this file.
13
13
  */
14
- import { codexTranscriptRoot, isTranscriptFile } from "../paths.js";
14
+ import { codexHooksPath, codexTranscriptRoot, isTranscriptFile } from "../paths.js";
15
15
  /**
16
16
  * `SessionEnd` is the tight one — 3 s registered, 2.2 s client.
17
17
  *
@@ -33,6 +33,26 @@ export const CODEX_CLI = {
33
33
  // shape to require here either. ⚠ CARRIED FROM THE BRIEF like the numbers
34
34
  // above: this task did not re-walk a Codex tree to confirm it.
35
35
  admitsFile: isTranscriptFile,
36
+ // ⛔ **IDENTITY — MEASURED, and it is the one member on this dialect that is
37
+ // no longer carried from the brief** (`CR-195`, D205). Read out of the native
38
+ // binary's embedded JSON schema, where every `hook_event_name` is a `const`:
39
+ // `PermissionRequest PostCompact PostToolUse PreCompact PreToolUse SessionEnd
40
+ // SessionStart Stop SubagentStart SubagentStop UserPromptSubmit`. All three of
41
+ // ours are present and spelled exactly as Claude Code spells them.
42
+ //
43
+ // ⚠ **The binary ALSO carries a snake_case enum** (`session_end`,
44
+ // `pre_compact`, …) beside a TOML-serialisation error string, and taking it
45
+ // for the config vocabulary was the near-miss of this unit. A differential run
46
+ // against three throwaway `CODEX_HOME`s settled it: `SessionEnd` is recognised
47
+ // and says so by clamping, while `session_end` and a bogus key produce NO
48
+ // diagnostic — a file that parses, registers nothing, and reports success.
49
+ eventNames: { Stop: "Stop", PreCompact: "PreCompact", SessionEnd: "SessionEnd" },
50
+ // ⛔ `~/.codex/hooks.json`, honouring `CODEX_HOME` — MEASURED, since every
51
+ // probe in this unit drove a real `codex` through a throwaway `CODEX_HOME`
52
+ // and it read the `hooks.json` there. The NESTED shape, also measured: a
53
+ // document in this form was parsed, its event recognised and its timeout
54
+ // clamped, which Codex can only do having read the inner entry.
55
+ hookConfig: { path: codexHooksPath, shape: "claude" },
36
56
  events: {
37
57
  Stop: {
38
58
  registeredTimeoutMs: CODEX_TURN_REGISTERED_MS,
@@ -1 +1 @@
1
- {"version":3,"file":"codex_cli.js","sourceRoot":"","sources":["../../src/agents/codex_cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpE;;;;;;GAMG;AACH,MAAM,+BAA+B,GAAG,KAAK,CAAC;AAC9C,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAE1C,2EAA2E;AAC3E,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAEnC,MAAM,CAAC,MAAM,SAAS,GAAsB;IAC1C,EAAE,EAAE,WAAW;IACf,SAAS,EAAE,QAAQ;IACnB,cAAc,EAAE,mBAAmB;IACnC,yEAAyE;IACzE,4EAA4E;IAC5E,0EAA0E;IAC1E,+DAA+D;IAC/D,UAAU,EAAE,gBAAgB;IAC5B,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,mBAAmB,EAAE,wBAAwB;YAC7C,cAAc,EAAE,oBAAoB;SACrC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,wBAAwB;YAC7C,cAAc,EAAE,oBAAoB;SACrC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,+BAA+B;YACpD,cAAc,EAAE,2BAA2B;SAC5C;KACF;IACD,+EAA+E;IAC/E,6EAA6E;IAC7E,oEAAoE;IACpE,oBAAoB,EAAE,UAAU;IAChC,SAAS,EAAE,WAAW;IACtB,8EAA8E;IAC9E,0EAA0E;IAC1E,8EAA8E;IAC9E,6EAA6E;IAC7E,+EAA+E;IAC/E,8BAA8B;IAC9B,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI;IAC5B,4EAA4E;IAC5E,wEAAwE;IACxE,6EAA6E;IAC7E,oDAAoD;CACrD,CAAC"}
1
+ {"version":3,"file":"codex_cli.js","sourceRoot":"","sources":["../../src/agents/codex_cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpF;;;;;;GAMG;AACH,MAAM,+BAA+B,GAAG,KAAK,CAAC;AAC9C,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAE1C,2EAA2E;AAC3E,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAEnC,MAAM,CAAC,MAAM,SAAS,GAAsB;IAC1C,EAAE,EAAE,WAAW;IACf,SAAS,EAAE,QAAQ;IACnB,cAAc,EAAE,mBAAmB;IACnC,yEAAyE;IACzE,4EAA4E;IAC5E,0EAA0E;IAC1E,+DAA+D;IAC/D,UAAU,EAAE,gBAAgB;IAC5B,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,8EAA8E;IAC9E,+EAA+E;IAC/E,mEAAmE;IACnE,EAAE;IACF,kEAAkE;IAClE,4EAA4E;IAC5E,+EAA+E;IAC/E,+EAA+E;IAC/E,0EAA0E;IAC1E,2EAA2E;IAC3E,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE;IAChF,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,yEAAyE;IACzE,gEAAgE;IAChE,UAAU,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE;IACrD,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,mBAAmB,EAAE,wBAAwB;YAC7C,cAAc,EAAE,oBAAoB;SACrC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,wBAAwB;YAC7C,cAAc,EAAE,oBAAoB;SACrC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,+BAA+B;YACpD,cAAc,EAAE,2BAA2B;SAC5C;KACF;IACD,+EAA+E;IAC/E,6EAA6E;IAC7E,oEAAoE;IACpE,oBAAoB,EAAE,UAAU;IAChC,SAAS,EAAE,WAAW;IACtB,8EAA8E;IAC9E,0EAA0E;IAC1E,8EAA8E;IAC9E,6EAA6E;IAC7E,+EAA+E;IAC/E,8BAA8B;IAC9B,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI;IAC5B,4EAA4E;IAC5E,wEAAwE;IACxE,6EAA6E;IAC7E,oDAAoD;CACrD,CAAC"}