ccc-notifier 0.3.0 → 0.5.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.
@@ -8,14 +8,16 @@ import {
8
8
  detectCodex
9
9
  } from "./chunk-HTYUYKFW.js";
10
10
  import {
11
+ configFilePath,
11
12
  paths,
12
13
  readConfig
13
- } from "./chunk-ECADO26T.js";
14
+ } from "./chunk-5PH7PPD6.js";
14
15
 
15
16
  // src/setup.ts
16
17
  import {
17
18
  copyFileSync as copyFileSync2,
18
19
  existsSync as existsSync2,
20
+ lstatSync,
19
21
  mkdirSync as mkdirSync2,
20
22
  readFileSync as readFileSync2,
21
23
  rmSync,
@@ -35,123 +37,201 @@ import {
35
37
  writeFileSync
36
38
  } from "fs";
37
39
  import { dirname, join } from "path";
38
- function isPlainObject(v) {
39
- return typeof v === "object" && v !== null && !Array.isArray(v);
40
+ var CODEX_HOOK_EVENTS = ["Stop", "UserPromptSubmit", "SubagentStart", "SubagentStop"];
41
+ var CODEX_HOOK_TIMEOUT_SECONDS = 20;
42
+ function isPlainObject(value) {
43
+ return typeof value === "object" && value !== null && !Array.isArray(value);
40
44
  }
41
45
  function codexHooksFile() {
42
46
  return join(codexHome(), "hooks.json");
43
47
  }
44
- function codexHookCommand(nodePath, cliPath) {
45
- let node = nodePath;
46
- let cli = cliPath;
47
- if (process.platform === "win32") {
48
- node = node.replace(/\\/g, "/");
49
- cli = cli.replace(/\\/g, "/");
48
+ function normalizeExecutablePath(value) {
49
+ return process.platform === "win32" ? value.replace(/\\/g, "/") : value;
50
+ }
51
+ function codexHookCommand(nodePath, cliPath, event = "Stop") {
52
+ const node = normalizeExecutablePath(nodePath);
53
+ const cli = normalizeExecutablePath(cliPath);
54
+ return `"${node}" "${cli}" __ccc-notifier-codex-hook ${event}`;
55
+ }
56
+ function tokenize(command) {
57
+ const result = [];
58
+ const re = /\s*(?:"([^"]*)"|'([^']*)'|(\S+))/gy;
59
+ let offset = 0;
60
+ while (offset < command.length) {
61
+ re.lastIndex = offset;
62
+ const match = re.exec(command);
63
+ if (!match) return null;
64
+ result.push(match[1] ?? match[2] ?? match[3]);
65
+ offset = re.lastIndex;
50
66
  }
51
- return `"${node}" "${cli}" track --codex`;
67
+ return result;
52
68
  }
53
- function ourCodexStopEntry(command) {
54
- return { hooks: [{ type: "command", command }] };
69
+ function parseLegacyOwnedCommand(command) {
70
+ const tokens = tokenize(command);
71
+ if (tokens === null || tokens.length !== 4 || !isCccNotifierExecutablePair(tokens[0], tokens[1]) || tokens[2] !== "track" || tokens[3] !== "--codex") return null;
72
+ return {
73
+ nodePath: normalizeExecutablePath(tokens[0]),
74
+ cliPath: normalizeExecutablePath(tokens[1]),
75
+ event: "Stop"
76
+ };
55
77
  }
56
- function isOurStopEntry(entry) {
57
- if (!isPlainObject(entry)) return false;
58
- const hooks = entry.hooks;
59
- if (!Array.isArray(hooks)) return false;
60
- return hooks.some(
61
- (h) => isPlainObject(h) && typeof h.command === "string" && matchesMarker(h.command)
62
- );
78
+ function isCccNotifierExecutablePair(nodePath, cliPath) {
79
+ const node = normalizeExecutablePath(nodePath);
80
+ const cli = normalizeExecutablePath(cliPath);
81
+ const nodeAbsolute = node.startsWith("/") || /^[a-z]:\//i.test(node);
82
+ const cliAbsolute = cli.startsWith("/") || /^[a-z]:\//i.test(cli);
83
+ return nodeAbsolute && cliAbsolute && /(^|\/)node(?:\.exe)?$/i.test(node) && /\/(?:ccc-notifier\/dist|ccc-notifier-dist)\/cli\.js$/i.test(cli);
84
+ }
85
+ function parseOwnedCodexHookCommand(command) {
86
+ if (typeof command !== "string") return null;
87
+ const tokens = tokenize(command);
88
+ if (tokens === null || tokens.length !== 4 || tokens[2] !== "__ccc-notifier-codex-hook") return null;
89
+ if (!CODEX_HOOK_EVENTS.includes(tokens[3])) return null;
90
+ if (!isCccNotifierExecutablePair(tokens[0], tokens[1])) return null;
91
+ return {
92
+ nodePath: normalizeExecutablePath(tokens[0]),
93
+ cliPath: normalizeExecutablePath(tokens[1]),
94
+ event: tokens[3]
95
+ };
63
96
  }
64
- function buildManualSnippet(command) {
65
- return JSON.stringify(ourCodexStopEntry(command), null, 2);
97
+ function parseConfiguredOwnedCodexHookCommand(command, event) {
98
+ const parsed = parseOwnedCodexHookCommand(command) ?? (typeof command === "string" ? parseLegacyOwnedCommand(command) : null);
99
+ return parsed !== null && (event === void 0 || parsed.event === event) ? parsed : null;
100
+ }
101
+ function isOwnedHandler(value, event) {
102
+ if (!isPlainObject(value) || value.type !== "command") return false;
103
+ return parseConfiguredOwnedCodexHookCommand(value.command, event) !== null;
104
+ }
105
+ function canonicalHandler(command) {
106
+ return { type: "command", command, timeout: CODEX_HOOK_TIMEOUT_SECONDS };
107
+ }
108
+ function manualSnippet(nodePath, cliPath) {
109
+ const hooks = Object.fromEntries(
110
+ CODEX_HOOK_EVENTS.map((event) => [
111
+ event,
112
+ [{ hooks: [canonicalHandler(codexHookCommand(nodePath, cliPath, event))] }]
113
+ ])
114
+ );
115
+ return JSON.stringify({ hooks }, null, 2);
66
116
  }
67
- function backupHooks(path) {
117
+ function backup(path) {
68
118
  const backupPath = `${path}.bak-${Date.now()}`;
69
119
  copyFileSync(path, backupPath);
70
120
  return backupPath;
71
121
  }
72
- function writeHooks(path, obj) {
73
- writeFileSync(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
122
+ function write(path, value) {
123
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
124
+ `, "utf8");
125
+ }
126
+ function validateShape(root) {
127
+ if (root.hooks !== void 0 && !isPlainObject(root.hooks)) return null;
128
+ const hooks = root.hooks ?? {};
129
+ for (const event of CODEX_HOOK_EVENTS) {
130
+ if (hooks[event] !== void 0 && !Array.isArray(hooks[event])) return null;
131
+ const groups = hooks[event] ?? [];
132
+ for (const group of groups) {
133
+ if (!isPlainObject(group) || !Array.isArray(group.hooks)) return null;
134
+ }
135
+ }
136
+ return hooks;
74
137
  }
75
138
  function registerCodexHook(nodePath, cliPath) {
76
- const hooksFile = codexHooksFile();
77
- const command = codexHookCommand(nodePath, cliPath);
78
- if (!existsSync(hooksFile)) {
79
- mkdirSync(dirname(hooksFile), { recursive: true });
80
- writeHooks(hooksFile, { hooks: { Stop: [ourCodexStopEntry(command)] } });
139
+ const path = codexHooksFile();
140
+ if (!existsSync(path)) {
141
+ mkdirSync(dirname(path), { recursive: true });
142
+ const hooks2 = {};
143
+ for (const event of CODEX_HOOK_EVENTS) {
144
+ hooks2[event] = [{ hooks: [canonicalHandler(codexHookCommand(nodePath, cliPath, event))] }];
145
+ }
146
+ write(path, { hooks: hooks2 });
81
147
  return { status: "written", backupPath: null };
82
148
  }
83
- const raw = readFileSync(hooksFile, "utf8");
149
+ const raw = readFileSync(path, "utf8");
84
150
  let parsed;
85
151
  try {
86
152
  parsed = JSON.parse(raw);
87
153
  } catch {
88
- return { status: "manual", backupPath: null, manualSnippet: buildManualSnippet(command) };
154
+ return { status: "manual", backupPath: null, manualSnippet: manualSnippet(nodePath, cliPath) };
89
155
  }
90
156
  if (!isPlainObject(parsed)) {
91
- return { status: "manual", backupPath: null, manualSnippet: buildManualSnippet(command) };
157
+ return { status: "manual", backupPath: null, manualSnippet: manualSnippet(nodePath, cliPath) };
92
158
  }
93
- const obj = parsed;
94
- const hooksVal = obj.hooks;
95
- if (hooksVal !== void 0 && !isPlainObject(hooksVal)) {
96
- return { status: "manual", backupPath: null, manualSnippet: buildManualSnippet(command) };
159
+ const hooks = validateShape(parsed);
160
+ if (hooks === null) {
161
+ return { status: "manual", backupPath: null, manualSnippet: manualSnippet(nodePath, cliPath) };
97
162
  }
98
- const hooks = hooksVal ?? {};
99
- const stopVal = hooks.Stop;
100
- if (stopVal !== void 0 && !Array.isArray(stopVal)) {
101
- return { status: "manual", backupPath: null, manualSnippet: buildManualSnippet(command) };
102
- }
103
- const stop = stopVal ?? [];
104
- const idx = stop.findIndex(isOurStopEntry);
105
- if (idx >= 0) {
106
- const entry = stop[idx];
107
- const entryHooks = entry.hooks;
108
- const needsUpdate = entryHooks.some(
109
- (h) => isPlainObject(h) && typeof h.command === "string" && matchesMarker(h.command) && h.command !== command
110
- );
111
- if (!needsUpdate) return { status: "unchanged", backupPath: null };
112
- const backupPath2 = backupHooks(hooksFile);
113
- for (const h of entryHooks) {
114
- if (isPlainObject(h) && typeof h.command === "string" && matchesMarker(h.command)) {
115
- h.command = command;
163
+ let changed = false;
164
+ for (const event of CODEX_HOOK_EVENTS) {
165
+ const groups = hooks[event] ?? [];
166
+ const wanted = canonicalHandler(codexHookCommand(nodePath, cliPath, event));
167
+ let found = false;
168
+ const groupsToRemove = /* @__PURE__ */ new Set();
169
+ for (const group of groups) {
170
+ const handlers = group.hooks;
171
+ for (let i = 0; i < handlers.length; i++) {
172
+ if (!isOwnedHandler(handlers[i], event)) continue;
173
+ if (!found) {
174
+ found = true;
175
+ const current = handlers[i];
176
+ const needsUpdate = !isPlainObject(current) || current.type !== wanted.type || current.command !== wanted.command || current.timeout !== wanted.timeout;
177
+ if (needsUpdate) {
178
+ handlers[i] = isPlainObject(current) ? { ...current, ...wanted } : wanted;
179
+ changed = true;
180
+ }
181
+ } else {
182
+ handlers.splice(i--, 1);
183
+ changed = true;
184
+ if (handlers.length === 0) groupsToRemove.add(group);
185
+ }
116
186
  }
117
187
  }
118
- obj.hooks = hooks;
119
- hooks.Stop = stop;
120
- writeHooks(hooksFile, obj);
121
- return { status: "written", backupPath: backupPath2 };
188
+ if (!found) {
189
+ groups.push({ hooks: [wanted] });
190
+ changed = true;
191
+ }
192
+ const retainedGroups = groups.filter((group) => !groupsToRemove.has(group));
193
+ if (retainedGroups.length !== groups.length) changed = true;
194
+ hooks[event] = retainedGroups;
122
195
  }
123
- const backupPath = backupHooks(hooksFile);
124
- stop.push(ourCodexStopEntry(command));
125
- obj.hooks = hooks;
126
- hooks.Stop = stop;
127
- writeHooks(hooksFile, obj);
196
+ if (!changed) return { status: "unchanged", backupPath: null };
197
+ const backupPath = backup(path);
198
+ parsed.hooks = hooks;
199
+ write(path, parsed);
128
200
  return { status: "written", backupPath };
129
201
  }
130
202
  function removeCodexHook() {
131
- const hooksFile = codexHooksFile();
132
- if (!existsSync(hooksFile)) return { status: "unchanged", backupPath: null };
133
- const raw = readFileSync(hooksFile, "utf8");
203
+ const path = codexHooksFile();
204
+ if (!existsSync(path)) return { status: "unchanged", backupPath: null };
134
205
  let parsed;
135
206
  try {
136
- parsed = JSON.parse(raw);
207
+ parsed = JSON.parse(readFileSync(path, "utf8"));
137
208
  } catch {
138
209
  return { status: "unchanged", backupPath: null };
139
210
  }
140
211
  if (!isPlainObject(parsed)) return { status: "unchanged", backupPath: null };
141
- const obj = parsed;
142
- const hooks = isPlainObject(obj.hooks) ? obj.hooks : null;
143
- const stop = hooks && Array.isArray(hooks.Stop) ? hooks.Stop : null;
144
- if (!hooks || !stop || !stop.some(isOurStopEntry)) {
145
- return { status: "unchanged", backupPath: null };
146
- }
147
- const backupPath = backupHooks(hooksFile);
148
- const filtered = stop.filter((e) => !isOurStopEntry(e));
149
- if (filtered.length === 0) {
150
- delete hooks.Stop;
151
- } else {
152
- hooks.Stop = filtered;
212
+ const hooks = validateShape(parsed);
213
+ if (hooks === null) return { status: "unchanged", backupPath: null };
214
+ let changed = false;
215
+ for (const event of CODEX_HOOK_EVENTS) {
216
+ const original = hooks[event] ?? [];
217
+ const groups = [];
218
+ for (const group of original) {
219
+ const handlers = group.hooks;
220
+ const filtered = handlers.filter((handler) => !isOwnedHandler(handler, event));
221
+ const removedOwned = filtered.length !== handlers.length;
222
+ if (removedOwned) changed = true;
223
+ if (!removedOwned) groups.push(group);
224
+ else if (filtered.length > 0) groups.push({ ...group, hooks: filtered });
225
+ }
226
+ if (groups.length === 0) {
227
+ if (event in hooks) delete hooks[event];
228
+ } else {
229
+ hooks[event] = groups;
230
+ }
153
231
  }
154
- writeHooks(hooksFile, obj);
232
+ if (!changed) return { status: "unchanged", backupPath: null };
233
+ const backupPath = backup(path);
234
+ write(path, parsed);
155
235
  return { status: "written", backupPath };
156
236
  }
157
237
 
@@ -186,7 +266,7 @@ function buildHookCommand() {
186
266
  function matchesMarker(command) {
187
267
  return command.includes(HOOK_MARKER);
188
268
  }
189
- function isOurStopEntry2(entry) {
269
+ function isOurStopEntry(entry) {
190
270
  if (!isPlainObject2(entry)) return false;
191
271
  const hooks = entry.hooks;
192
272
  if (!Array.isArray(hooks)) return false;
@@ -246,10 +326,13 @@ function parseInitFlags(argv) {
246
326
  for (let i = 0; i < argv.length; i++) {
247
327
  const a = argv[i];
248
328
  if (a === "--yes" || a === "-y") flags.yes = true;
249
- else if (a === "--os-only") flags.osOnly = true;
250
- else if (a === "--slack-only") flags.slackOnly = true;
251
- else if (a === "--no-notify") flags.noNotify = true;
252
- else if (a === "--codex") flags.codex = true;
329
+ else if (a === "--os-only") {
330
+ flags.osOnly = true;
331
+ } else if (a === "--slack-only") {
332
+ flags.slackOnly = true;
333
+ } else if (a === "--no-notify") {
334
+ flags.noNotify = true;
335
+ } else if (a === "--codex") flags.codex = true;
253
336
  else if (a === "--no-codex") flags.noCodex = true;
254
337
  else if (a === "--slack-webhook" || a.startsWith("--slack-webhook=")) {
255
338
  flags.slackWebhook = takeValue(argv, i, "--slack-webhook");
@@ -267,6 +350,38 @@ function parseInitFlags(argv) {
267
350
  }
268
351
  return flags;
269
352
  }
353
+ function configPathEntryExists(path) {
354
+ try {
355
+ lstatSync(path);
356
+ return true;
357
+ } catch (err) {
358
+ if (isPlainObject2(err) && err.code === "ENOENT") return false;
359
+ return null;
360
+ }
361
+ }
362
+ function isExactCodexMigrationInvocation(argv, flags) {
363
+ return flags.yes && flags.codex && argv.every((arg) => arg === "--yes" || arg === "-y" || arg === "--codex");
364
+ }
365
+ function printCodexHookResult(codexResult) {
366
+ if (codexResult.status === "written") {
367
+ console.log(`Codex \u306B Stop/UserPromptSubmit/SubagentStart/SubagentStop hook \u3092\u767B\u9332\u3057\u307E\u3057\u305F: ${codexHooksFile()}`);
368
+ if (codexResult.backupPath) {
369
+ console.log(` \u30D0\u30C3\u30AF\u30A2\u30C3\u30D7: ${codexResult.backupPath}`);
370
+ }
371
+ console.log(
372
+ "\u6B21\u56DE codex \u8D77\u52D5\u6642\u306B\u300EHooks need review\u300F\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002\u300ETrust all and continue\u300F\u3092\u9078\u3076\u3068\u6709\u52B9\u306B\u306A\u308A\u307E\u3059(\u627F\u8A8D\u307E\u3067\u306F\u52D5\u304D\u307E\u305B\u3093)"
373
+ );
374
+ } else if (codexResult.status === "unchanged") {
375
+ console.log("Codex \u306E Stop/UserPromptSubmit/SubagentStart/SubagentStop hook \u306F\u767B\u9332\u6E08\u307F\u3067\u3059");
376
+ } else {
377
+ console.error(`Codex \u306E hooks.json \u3092\u81EA\u52D5\u7DE8\u96C6\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: ${codexHooksFile()}`);
378
+ console.error("\u5B89\u5168\u306E\u305F\u3081 hooks.json \u306E\u81EA\u52D5\u7DE8\u96C6\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F\u3002");
379
+ console.error("\u4EE5\u4E0B\u306E JSON \u306E4\u30A4\u30D9\u30F3\u30C8\u3092 hooks.json \u3078\u624B\u52D5\u3067\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044:");
380
+ console.error("");
381
+ console.error(codexResult.manualSnippet ?? "");
382
+ console.error("");
383
+ }
384
+ }
270
385
  function parseUninstallFlags(argv) {
271
386
  const flags = { yes: false, purge: false };
272
387
  for (const a of argv) {
@@ -300,7 +415,7 @@ function mergeSettings(sPath, command) {
300
415
  const hooks = obj.hooks;
301
416
  if (!Array.isArray(hooks.Stop)) hooks.Stop = [];
302
417
  const stop = hooks.Stop;
303
- const idx = stop.findIndex(isOurStopEntry2);
418
+ const idx = stop.findIndex(isOurStopEntry);
304
419
  if (idx >= 0) {
305
420
  const entry = stop[idx];
306
421
  const entryHooks = entry.hooks;
@@ -322,6 +437,33 @@ async function runInit(argv) {
322
437
  console.error("--codex \u3068 --no-codex \u306F\u540C\u6642\u306B\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093");
323
438
  return 1;
324
439
  }
440
+ const exactMigrationInvocation = isExactCodexMigrationInvocation(argv, flags);
441
+ const configEntryState = exactMigrationInvocation ? configPathEntryExists(configFilePath()) : false;
442
+ if (configEntryState === null) {
443
+ console.error(`config.json \u306E\u5B58\u5728\u3092\u5B89\u5168\u306B\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: ${configFilePath()}`);
444
+ console.error("\u5B89\u5168\u306E\u305F\u3081 Codex hook \u3092\u542B\u3080\u3059\u3079\u3066\u306E\u5909\u66F4\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F\u3002");
445
+ return 1;
446
+ }
447
+ const codexOnlyMigration = exactMigrationInvocation && configEntryState;
448
+ if (codexOnlyMigration) {
449
+ const codexResult2 = registerCodexHook(process.execPath, resolveCliPath());
450
+ printCodexHookResult(codexResult2);
451
+ if (codexResult2.status === "manual") {
452
+ console.error(
453
+ "Codex hook \u9650\u5B9A\u79FB\u884C\u3092\u5B8C\u4E86\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002config.json\u3001Claude settings\u3001\u901A\u77E5\u8A2D\u5B9A\u306F\u5909\u66F4\u3057\u3066\u3044\u307E\u305B\u3093\u3002"
454
+ );
455
+ return 1;
456
+ }
457
+ console.log(
458
+ "Codex hook \u306E\u307F\u3092\u78BA\u8A8D\u30FB\u66F4\u65B0\u3057\u307E\u3057\u305F\u3002config.json\u3001Claude settings\u3001\u901A\u77E5\u8A2D\u5B9A\u306F\u5909\u66F4\u305B\u305A\u3001\u30C6\u30B9\u30C8\u901A\u77E5\u3082\u9001\u4FE1\u3057\u3066\u3044\u307E\u305B\u3093\u3002"
459
+ );
460
+ if (codexResult2.status === "written") {
461
+ console.log(
462
+ "Codex \u3092\u518D\u8D77\u52D5\u3057\u3001/hooks \u3067 Stop / UserPromptSubmit / SubagentStart / SubagentStop \u3092\u4FE1\u983C\u6E08\u307F\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
463
+ );
464
+ }
465
+ return 0;
466
+ }
325
467
  let installCodex = false;
326
468
  const cfg = readConfig();
327
469
  const initialChannel = !cfg.notify.os && !cfg.notify.slack ? "none" : cfg.notify.slack ? cfg.notify.os ? "both" : "slack" : "os";
@@ -517,24 +659,7 @@ async function runInit(argv) {
517
659
  console.log("Claude Code \u3067\u4F55\u304B\u5B9F\u884C\u3059\u308B\u3068\u901A\u77E5\u304C\u5C4A\u304D\u307E\u3059\u3002\u78BA\u8A8D: npx ccc-notifier doctor");
518
660
  }
519
661
  if (codexResult) {
520
- if (codexResult.status === "written") {
521
- console.log(`Codex \u306B\u3082 Stop hook \u3092\u767B\u9332\u3057\u307E\u3057\u305F: ${codexHooksFile()}`);
522
- if (codexResult.backupPath) {
523
- console.log(` \u30D0\u30C3\u30AF\u30A2\u30C3\u30D7: ${codexResult.backupPath}`);
524
- }
525
- console.log(
526
- "\u6B21\u56DE codex \u8D77\u52D5\u6642\u306B\u300EHooks need review\u300F\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002\u300ETrust all and continue\u300F\u3092\u9078\u3076\u3068\u6709\u52B9\u306B\u306A\u308A\u307E\u3059(\u627F\u8A8D\u307E\u3067\u306F\u52D5\u304D\u307E\u305B\u3093)"
527
- );
528
- } else if (codexResult.status === "unchanged") {
529
- console.log("Codex \u306E Stop hook \u306F\u767B\u9332\u6E08\u307F\u3067\u3059");
530
- } else {
531
- console.error(`Codex \u306E hooks.json \u3092\u81EA\u52D5\u7DE8\u96C6\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: ${codexHooksFile()}`);
532
- console.error("\u5B89\u5168\u306E\u305F\u3081 hooks.json \u306E\u81EA\u52D5\u7DE8\u96C6\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F\u3002");
533
- console.error("\u4EE5\u4E0B\u306E JSON \u3092 hooks.json \u306E hooks.Stop \u914D\u5217\u306B\u624B\u52D5\u3067\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044:");
534
- console.error("");
535
- console.error(codexResult.manualSnippet ?? "");
536
- console.error("");
537
- }
662
+ printCodexHookResult(codexResult);
538
663
  }
539
664
  return 0;
540
665
  }
@@ -558,12 +683,12 @@ async function runUninstall(argv) {
558
683
  const obj = parsed;
559
684
  const hooks = isPlainObject2(obj.hooks) ? obj.hooks : null;
560
685
  const stop = hooks && Array.isArray(hooks.Stop) ? hooks.Stop : null;
561
- const hasMarker = stop ? stop.some(isOurStopEntry2) : false;
686
+ const hasMarker = stop ? stop.some(isOurStopEntry) : false;
562
687
  if (!hasMarker || !hooks || !stop) {
563
688
  console.log("\u767B\u9332\u306A\u3057: \u672C\u30C4\u30FC\u30EB\u306E Stop \u30D5\u30C3\u30AF\u306F\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002");
564
689
  } else {
565
690
  const backupPath = backupSettings(sPath);
566
- const filtered = stop.filter((e) => !isOurStopEntry2(e));
691
+ const filtered = stop.filter((e) => !isOurStopEntry(e));
567
692
  if (filtered.length === 0) {
568
693
  delete hooks.Stop;
569
694
  } else {
@@ -577,7 +702,7 @@ async function runUninstall(argv) {
577
702
  }
578
703
  const codexRemoval = removeCodexHook();
579
704
  if (codexRemoval.status === "written") {
580
- console.log("Codex \u306E Stop hook \u3092\u524A\u9664\u3057\u307E\u3057\u305F");
705
+ console.log("Codex \u306E Stop/UserPromptSubmit/SubagentStart/SubagentStop hook \u3092\u524A\u9664\u3057\u307E\u3057\u305F");
581
706
  }
582
707
  if (flags.purge) {
583
708
  const home = paths().home;
@@ -602,7 +727,9 @@ async function runUninstall(argv) {
602
727
  }
603
728
 
604
729
  export {
605
- codexHooksFile,
730
+ CODEX_HOOK_EVENTS,
731
+ CODEX_HOOK_TIMEOUT_SECONDS,
732
+ parseConfiguredOwnedCodexHookCommand,
606
733
  matchesMarker,
607
734
  runInit,
608
735
  runUninstall