ccc-notifier 0.4.0 → 0.6.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,19 @@ import {
8
8
  detectCodex
9
9
  } from "./chunk-HTYUYKFW.js";
10
10
  import {
11
+ loadPriceTable
12
+ } from "./chunk-6HTETN26.js";
13
+ import {
14
+ configFilePath,
11
15
  paths,
12
16
  readConfig
13
- } from "./chunk-26CISNOE.js";
17
+ } from "./chunk-OOAC5ULQ.js";
14
18
 
15
19
  // src/setup.ts
16
20
  import {
17
21
  copyFileSync as copyFileSync2,
18
22
  existsSync as existsSync2,
23
+ lstatSync,
19
24
  mkdirSync as mkdirSync2,
20
25
  readFileSync as readFileSync2,
21
26
  rmSync,
@@ -35,123 +40,201 @@ import {
35
40
  writeFileSync
36
41
  } from "fs";
37
42
  import { dirname, join } from "path";
38
- function isPlainObject(v) {
39
- return typeof v === "object" && v !== null && !Array.isArray(v);
43
+ var CODEX_HOOK_EVENTS = ["Stop", "UserPromptSubmit", "SubagentStart", "SubagentStop"];
44
+ var CODEX_HOOK_TIMEOUT_SECONDS = 20;
45
+ function isPlainObject(value) {
46
+ return typeof value === "object" && value !== null && !Array.isArray(value);
40
47
  }
41
48
  function codexHooksFile() {
42
49
  return join(codexHome(), "hooks.json");
43
50
  }
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, "/");
51
+ function normalizeExecutablePath(value) {
52
+ return process.platform === "win32" ? value.replace(/\\/g, "/") : value;
53
+ }
54
+ function codexHookCommand(nodePath, cliPath, event = "Stop") {
55
+ const node = normalizeExecutablePath(nodePath);
56
+ const cli = normalizeExecutablePath(cliPath);
57
+ return `"${node}" "${cli}" __ccc-notifier-codex-hook ${event}`;
58
+ }
59
+ function tokenize(command) {
60
+ const result = [];
61
+ const re = /\s*(?:"([^"]*)"|'([^']*)'|(\S+))/gy;
62
+ let offset = 0;
63
+ while (offset < command.length) {
64
+ re.lastIndex = offset;
65
+ const match = re.exec(command);
66
+ if (!match) return null;
67
+ result.push(match[1] ?? match[2] ?? match[3]);
68
+ offset = re.lastIndex;
50
69
  }
51
- return `"${node}" "${cli}" track --codex`;
70
+ return result;
52
71
  }
53
- function ourCodexStopEntry(command) {
54
- return { hooks: [{ type: "command", command }] };
72
+ function parseLegacyOwnedCommand(command) {
73
+ const tokens = tokenize(command);
74
+ if (tokens === null || tokens.length !== 4 || !isCccNotifierExecutablePair(tokens[0], tokens[1]) || tokens[2] !== "track" || tokens[3] !== "--codex") return null;
75
+ return {
76
+ nodePath: normalizeExecutablePath(tokens[0]),
77
+ cliPath: normalizeExecutablePath(tokens[1]),
78
+ event: "Stop"
79
+ };
55
80
  }
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
- );
81
+ function isCccNotifierExecutablePair(nodePath, cliPath) {
82
+ const node = normalizeExecutablePath(nodePath);
83
+ const cli = normalizeExecutablePath(cliPath);
84
+ const nodeAbsolute = node.startsWith("/") || /^[a-z]:\//i.test(node);
85
+ const cliAbsolute = cli.startsWith("/") || /^[a-z]:\//i.test(cli);
86
+ return nodeAbsolute && cliAbsolute && /(^|\/)node(?:\.exe)?$/i.test(node) && /\/(?:ccc-notifier\/dist|ccc-notifier-dist)\/cli\.js$/i.test(cli);
87
+ }
88
+ function parseOwnedCodexHookCommand(command) {
89
+ if (typeof command !== "string") return null;
90
+ const tokens = tokenize(command);
91
+ if (tokens === null || tokens.length !== 4 || tokens[2] !== "__ccc-notifier-codex-hook") return null;
92
+ if (!CODEX_HOOK_EVENTS.includes(tokens[3])) return null;
93
+ if (!isCccNotifierExecutablePair(tokens[0], tokens[1])) return null;
94
+ return {
95
+ nodePath: normalizeExecutablePath(tokens[0]),
96
+ cliPath: normalizeExecutablePath(tokens[1]),
97
+ event: tokens[3]
98
+ };
99
+ }
100
+ function parseConfiguredOwnedCodexHookCommand(command, event) {
101
+ const parsed = parseOwnedCodexHookCommand(command) ?? (typeof command === "string" ? parseLegacyOwnedCommand(command) : null);
102
+ return parsed !== null && (event === void 0 || parsed.event === event) ? parsed : null;
103
+ }
104
+ function isOwnedHandler(value, event) {
105
+ if (!isPlainObject(value) || value.type !== "command") return false;
106
+ return parseConfiguredOwnedCodexHookCommand(value.command, event) !== null;
107
+ }
108
+ function canonicalHandler(command) {
109
+ return { type: "command", command, timeout: CODEX_HOOK_TIMEOUT_SECONDS };
63
110
  }
64
- function buildManualSnippet(command) {
65
- return JSON.stringify(ourCodexStopEntry(command), null, 2);
111
+ function manualSnippet(nodePath, cliPath) {
112
+ const hooks = Object.fromEntries(
113
+ CODEX_HOOK_EVENTS.map((event) => [
114
+ event,
115
+ [{ hooks: [canonicalHandler(codexHookCommand(nodePath, cliPath, event))] }]
116
+ ])
117
+ );
118
+ return JSON.stringify({ hooks }, null, 2);
66
119
  }
67
- function backupHooks(path) {
120
+ function backup(path) {
68
121
  const backupPath = `${path}.bak-${Date.now()}`;
69
122
  copyFileSync(path, backupPath);
70
123
  return backupPath;
71
124
  }
72
- function writeHooks(path, obj) {
73
- writeFileSync(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
125
+ function write(path, value) {
126
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
127
+ `, "utf8");
128
+ }
129
+ function validateShape(root) {
130
+ if (root.hooks !== void 0 && !isPlainObject(root.hooks)) return null;
131
+ const hooks = root.hooks ?? {};
132
+ for (const event of CODEX_HOOK_EVENTS) {
133
+ if (hooks[event] !== void 0 && !Array.isArray(hooks[event])) return null;
134
+ const groups = hooks[event] ?? [];
135
+ for (const group of groups) {
136
+ if (!isPlainObject(group) || !Array.isArray(group.hooks)) return null;
137
+ }
138
+ }
139
+ return hooks;
74
140
  }
75
141
  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)] } });
142
+ const path = codexHooksFile();
143
+ if (!existsSync(path)) {
144
+ mkdirSync(dirname(path), { recursive: true });
145
+ const hooks2 = {};
146
+ for (const event of CODEX_HOOK_EVENTS) {
147
+ hooks2[event] = [{ hooks: [canonicalHandler(codexHookCommand(nodePath, cliPath, event))] }];
148
+ }
149
+ write(path, { hooks: hooks2 });
81
150
  return { status: "written", backupPath: null };
82
151
  }
83
- const raw = readFileSync(hooksFile, "utf8");
152
+ const raw = readFileSync(path, "utf8");
84
153
  let parsed;
85
154
  try {
86
155
  parsed = JSON.parse(raw);
87
156
  } catch {
88
- return { status: "manual", backupPath: null, manualSnippet: buildManualSnippet(command) };
157
+ return { status: "manual", backupPath: null, manualSnippet: manualSnippet(nodePath, cliPath) };
89
158
  }
90
159
  if (!isPlainObject(parsed)) {
91
- return { status: "manual", backupPath: null, manualSnippet: buildManualSnippet(command) };
160
+ return { status: "manual", backupPath: null, manualSnippet: manualSnippet(nodePath, cliPath) };
92
161
  }
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) };
97
- }
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) };
162
+ const hooks = validateShape(parsed);
163
+ if (hooks === null) {
164
+ return { status: "manual", backupPath: null, manualSnippet: manualSnippet(nodePath, cliPath) };
102
165
  }
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;
166
+ let changed = false;
167
+ for (const event of CODEX_HOOK_EVENTS) {
168
+ const groups = hooks[event] ?? [];
169
+ const wanted = canonicalHandler(codexHookCommand(nodePath, cliPath, event));
170
+ let found = false;
171
+ const groupsToRemove = /* @__PURE__ */ new Set();
172
+ for (const group of groups) {
173
+ const handlers = group.hooks;
174
+ for (let i = 0; i < handlers.length; i++) {
175
+ if (!isOwnedHandler(handlers[i], event)) continue;
176
+ if (!found) {
177
+ found = true;
178
+ const current = handlers[i];
179
+ const needsUpdate = !isPlainObject(current) || current.type !== wanted.type || current.command !== wanted.command || current.timeout !== wanted.timeout;
180
+ if (needsUpdate) {
181
+ handlers[i] = isPlainObject(current) ? { ...current, ...wanted } : wanted;
182
+ changed = true;
183
+ }
184
+ } else {
185
+ handlers.splice(i--, 1);
186
+ changed = true;
187
+ if (handlers.length === 0) groupsToRemove.add(group);
188
+ }
116
189
  }
117
190
  }
118
- obj.hooks = hooks;
119
- hooks.Stop = stop;
120
- writeHooks(hooksFile, obj);
121
- return { status: "written", backupPath: backupPath2 };
191
+ if (!found) {
192
+ groups.push({ hooks: [wanted] });
193
+ changed = true;
194
+ }
195
+ const retainedGroups = groups.filter((group) => !groupsToRemove.has(group));
196
+ if (retainedGroups.length !== groups.length) changed = true;
197
+ hooks[event] = retainedGroups;
122
198
  }
123
- const backupPath = backupHooks(hooksFile);
124
- stop.push(ourCodexStopEntry(command));
125
- obj.hooks = hooks;
126
- hooks.Stop = stop;
127
- writeHooks(hooksFile, obj);
199
+ if (!changed) return { status: "unchanged", backupPath: null };
200
+ const backupPath = backup(path);
201
+ parsed.hooks = hooks;
202
+ write(path, parsed);
128
203
  return { status: "written", backupPath };
129
204
  }
130
205
  function removeCodexHook() {
131
- const hooksFile = codexHooksFile();
132
- if (!existsSync(hooksFile)) return { status: "unchanged", backupPath: null };
133
- const raw = readFileSync(hooksFile, "utf8");
206
+ const path = codexHooksFile();
207
+ if (!existsSync(path)) return { status: "unchanged", backupPath: null };
134
208
  let parsed;
135
209
  try {
136
- parsed = JSON.parse(raw);
210
+ parsed = JSON.parse(readFileSync(path, "utf8"));
137
211
  } catch {
138
212
  return { status: "unchanged", backupPath: null };
139
213
  }
140
214
  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;
215
+ const hooks = validateShape(parsed);
216
+ if (hooks === null) return { status: "unchanged", backupPath: null };
217
+ let changed = false;
218
+ for (const event of CODEX_HOOK_EVENTS) {
219
+ const original = hooks[event] ?? [];
220
+ const groups = [];
221
+ for (const group of original) {
222
+ const handlers = group.hooks;
223
+ const filtered = handlers.filter((handler) => !isOwnedHandler(handler, event));
224
+ const removedOwned = filtered.length !== handlers.length;
225
+ if (removedOwned) changed = true;
226
+ if (!removedOwned) groups.push(group);
227
+ else if (filtered.length > 0) groups.push({ ...group, hooks: filtered });
228
+ }
229
+ if (groups.length === 0) {
230
+ if (event in hooks) delete hooks[event];
231
+ } else {
232
+ hooks[event] = groups;
233
+ }
153
234
  }
154
- writeHooks(hooksFile, obj);
235
+ if (!changed) return { status: "unchanged", backupPath: null };
236
+ const backupPath = backup(path);
237
+ write(path, parsed);
155
238
  return { status: "written", backupPath };
156
239
  }
157
240
 
@@ -186,7 +269,7 @@ function buildHookCommand() {
186
269
  function matchesMarker(command) {
187
270
  return command.includes(HOOK_MARKER);
188
271
  }
189
- function isOurStopEntry2(entry) {
272
+ function isOurStopEntry(entry) {
190
273
  if (!isPlainObject2(entry)) return false;
191
274
  const hooks = entry.hooks;
192
275
  if (!Array.isArray(hooks)) return false;
@@ -223,8 +306,8 @@ function makeTestRecord() {
223
306
  sidechainTokens: null,
224
307
  apiCalls: 1,
225
308
  costUSD: 0.01,
226
- costJPY: 1.5,
227
- fxRate: 150,
309
+ costJPY: 1.6,
310
+ fxRate: 160,
228
311
  fxSource: "fixed",
229
312
  prompt: "\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5B8C\u4E86\u30C6\u30B9\u30C8"
230
313
  };
@@ -246,10 +329,13 @@ function parseInitFlags(argv) {
246
329
  for (let i = 0; i < argv.length; i++) {
247
330
  const a = argv[i];
248
331
  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;
332
+ else if (a === "--os-only") {
333
+ flags.osOnly = true;
334
+ } else if (a === "--slack-only") {
335
+ flags.slackOnly = true;
336
+ } else if (a === "--no-notify") {
337
+ flags.noNotify = true;
338
+ } else if (a === "--codex") flags.codex = true;
253
339
  else if (a === "--no-codex") flags.noCodex = true;
254
340
  else if (a === "--slack-webhook" || a.startsWith("--slack-webhook=")) {
255
341
  flags.slackWebhook = takeValue(argv, i, "--slack-webhook");
@@ -267,6 +353,38 @@ function parseInitFlags(argv) {
267
353
  }
268
354
  return flags;
269
355
  }
356
+ function configPathEntryExists(path) {
357
+ try {
358
+ lstatSync(path);
359
+ return true;
360
+ } catch (err) {
361
+ if (isPlainObject2(err) && err.code === "ENOENT") return false;
362
+ return null;
363
+ }
364
+ }
365
+ function isExactCodexMigrationInvocation(argv, flags) {
366
+ return flags.yes && flags.codex && argv.every((arg) => arg === "--yes" || arg === "-y" || arg === "--codex");
367
+ }
368
+ function printCodexHookResult(codexResult) {
369
+ if (codexResult.status === "written") {
370
+ console.log(`Codex \u306B Stop/UserPromptSubmit/SubagentStart/SubagentStop hook \u3092\u767B\u9332\u3057\u307E\u3057\u305F: ${codexHooksFile()}`);
371
+ if (codexResult.backupPath) {
372
+ console.log(` \u30D0\u30C3\u30AF\u30A2\u30C3\u30D7: ${codexResult.backupPath}`);
373
+ }
374
+ console.log(
375
+ "\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)"
376
+ );
377
+ } else if (codexResult.status === "unchanged") {
378
+ console.log("Codex \u306E Stop/UserPromptSubmit/SubagentStart/SubagentStop hook \u306F\u767B\u9332\u6E08\u307F\u3067\u3059");
379
+ } else {
380
+ console.error(`Codex \u306E hooks.json \u3092\u81EA\u52D5\u7DE8\u96C6\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: ${codexHooksFile()}`);
381
+ console.error("\u5B89\u5168\u306E\u305F\u3081 hooks.json \u306E\u81EA\u52D5\u7DE8\u96C6\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F\u3002");
382
+ 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:");
383
+ console.error("");
384
+ console.error(codexResult.manualSnippet ?? "");
385
+ console.error("");
386
+ }
387
+ }
270
388
  function parseUninstallFlags(argv) {
271
389
  const flags = { yes: false, purge: false };
272
390
  for (const a of argv) {
@@ -300,7 +418,7 @@ function mergeSettings(sPath, command) {
300
418
  const hooks = obj.hooks;
301
419
  if (!Array.isArray(hooks.Stop)) hooks.Stop = [];
302
420
  const stop = hooks.Stop;
303
- const idx = stop.findIndex(isOurStopEntry2);
421
+ const idx = stop.findIndex(isOurStopEntry);
304
422
  if (idx >= 0) {
305
423
  const entry = stop[idx];
306
424
  const entryHooks = entry.hooks;
@@ -322,6 +440,33 @@ async function runInit(argv) {
322
440
  console.error("--codex \u3068 --no-codex \u306F\u540C\u6642\u306B\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093");
323
441
  return 1;
324
442
  }
443
+ const exactMigrationInvocation = isExactCodexMigrationInvocation(argv, flags);
444
+ const configEntryState = exactMigrationInvocation ? configPathEntryExists(configFilePath()) : false;
445
+ if (configEntryState === null) {
446
+ console.error(`config.json \u306E\u5B58\u5728\u3092\u5B89\u5168\u306B\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: ${configFilePath()}`);
447
+ 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");
448
+ return 1;
449
+ }
450
+ const codexOnlyMigration = exactMigrationInvocation && configEntryState;
451
+ if (codexOnlyMigration) {
452
+ const codexResult2 = registerCodexHook(process.execPath, resolveCliPath());
453
+ printCodexHookResult(codexResult2);
454
+ if (codexResult2.status === "manual") {
455
+ console.error(
456
+ "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"
457
+ );
458
+ return 1;
459
+ }
460
+ console.log(
461
+ "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"
462
+ );
463
+ if (codexResult2.status === "written") {
464
+ console.log(
465
+ "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"
466
+ );
467
+ }
468
+ return 0;
469
+ }
325
470
  let installCodex = false;
326
471
  const cfg = readConfig();
327
472
  const initialChannel = !cfg.notify.os && !cfg.notify.slack ? "none" : cfg.notify.slack ? cfg.notify.os ? "both" : "slack" : "os";
@@ -493,6 +638,7 @@ async function runInit(argv) {
493
638
  if (installCodex) {
494
639
  codexResult = registerCodexHook(process.execPath, resolveCliPath());
495
640
  }
641
+ await loadPriceTable(cccn.cacheDir, { offline: false });
496
642
  const notifyDisabled = !cfg.notify.os && !cfg.notify.slack;
497
643
  if (notifyDisabled) {
498
644
  console.log("\u30C6\u30B9\u30C8\u901A\u77E5: \u901A\u77E5\u306A\u3057\u30E2\u30FC\u30C9\u306E\u305F\u3081\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F");
@@ -517,24 +663,7 @@ async function runInit(argv) {
517
663
  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
664
  }
519
665
  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
- }
666
+ printCodexHookResult(codexResult);
538
667
  }
539
668
  return 0;
540
669
  }
@@ -558,12 +687,12 @@ async function runUninstall(argv) {
558
687
  const obj = parsed;
559
688
  const hooks = isPlainObject2(obj.hooks) ? obj.hooks : null;
560
689
  const stop = hooks && Array.isArray(hooks.Stop) ? hooks.Stop : null;
561
- const hasMarker = stop ? stop.some(isOurStopEntry2) : false;
690
+ const hasMarker = stop ? stop.some(isOurStopEntry) : false;
562
691
  if (!hasMarker || !hooks || !stop) {
563
692
  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
693
  } else {
565
694
  const backupPath = backupSettings(sPath);
566
- const filtered = stop.filter((e) => !isOurStopEntry2(e));
695
+ const filtered = stop.filter((e) => !isOurStopEntry(e));
567
696
  if (filtered.length === 0) {
568
697
  delete hooks.Stop;
569
698
  } else {
@@ -577,7 +706,7 @@ async function runUninstall(argv) {
577
706
  }
578
707
  const codexRemoval = removeCodexHook();
579
708
  if (codexRemoval.status === "written") {
580
- console.log("Codex \u306E Stop hook \u3092\u524A\u9664\u3057\u307E\u3057\u305F");
709
+ console.log("Codex \u306E Stop/UserPromptSubmit/SubagentStart/SubagentStop hook \u3092\u524A\u9664\u3057\u307E\u3057\u305F");
581
710
  }
582
711
  if (flags.purge) {
583
712
  const home = paths().home;
@@ -602,7 +731,9 @@ async function runUninstall(argv) {
602
731
  }
603
732
 
604
733
  export {
605
- codexHooksFile,
734
+ CODEX_HOOK_EVENTS,
735
+ CODEX_HOOK_TIMEOUT_SECONDS,
736
+ parseConfiguredOwnedCodexHookCommand,
606
737
  matchesMarker,
607
738
  runInit,
608
739
  runUninstall