agent-switchboard 0.4.12 → 0.4.16

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.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Codex hook distribution: writes hooks.json and copies bundle files.
3
+ *
4
+ * Codex reads hooks from ~/.codex/hooks.json (global) and
5
+ * <project>/.codex/hooks.json (project scope). Unlike Claude Code which
6
+ * embeds hooks inside settings.json, Codex uses a dedicated file.
7
+ *
8
+ * Codex only supports: command handlers, 5 event types, no ${HOOK_DIR}
9
+ * runtime expansion. ASB must filter unsupported entries and rewrite
10
+ * bundle paths to absolute paths before writing.
11
+ */
12
+ import type { ConfigScope } from '../config/scope.js';
13
+ import type { DistributionResult } from '../library/distribute.js';
14
+ import type { BundleDistributionResult } from '../library/distribute-bundle.js';
15
+ import type { HookEntry } from './library.js';
16
+ type CodexPlatform = 'codex';
17
+ export interface CodexHookDistributeOptions {
18
+ scope?: ConfigScope;
19
+ selected: readonly HookEntry[];
20
+ dryRun?: boolean;
21
+ projectMode?: 'managed' | 'exclusive' | 'none';
22
+ }
23
+ export declare function distributeCodexHooks(options: CodexHookDistributeOptions): {
24
+ results: Array<DistributionResult<CodexPlatform> | BundleDistributionResult<CodexPlatform>>;
25
+ };
26
+ export {};
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Codex hook distribution: writes hooks.json and copies bundle files.
3
+ *
4
+ * Codex reads hooks from ~/.codex/hooks.json (global) and
5
+ * <project>/.codex/hooks.json (project scope). Unlike Claude Code which
6
+ * embeds hooks inside settings.json, Codex uses a dedicated file.
7
+ *
8
+ * Codex only supports: command handlers, 5 event types, no ${HOOK_DIR}
9
+ * runtime expansion. ASB must filter unsupported entries and rewrite
10
+ * bundle paths to absolute paths before writing.
11
+ */
12
+ import fs from 'node:fs';
13
+ import os from 'node:os';
14
+ import path from 'node:path';
15
+ import { ensureTrustEntry } from '../agents/codex.js';
16
+ import { getCodexConfigPath, getCodexDir, getCodexHooksJsonPath, getProjectCodexDir, getProjectCodexHooksJsonPath, } from '../config/paths.js';
17
+ import { distributeBundle } from '../library/distribute-bundle.js';
18
+ import { ensureParentDir, rmDirRecursive } from '../library/fs.js';
19
+ import { listHookBundleFiles } from './library.js';
20
+ import { HOOK_DIR_PLACEHOLDER } from './schema.js';
21
+ const CODEX_SUPPORTED_EVENTS = new Set([
22
+ 'PreToolUse',
23
+ 'PostToolUse',
24
+ 'SessionStart',
25
+ 'UserPromptSubmit',
26
+ 'Stop',
27
+ ]);
28
+ const CODEX_SUPPORTED_HANDLER_TYPES = new Set(['command']);
29
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional literal placeholder
30
+ const CLAUDE_PLUGIN_ROOT_HOOKS_PREFIX = '${CLAUDE_PLUGIN_ROOT}/hooks';
31
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional literal placeholder
32
+ const CLAUDE_PLUGIN_ROOT_HOOKS_PREFIX_WINDOWS = '${CLAUDE_PLUGIN_ROOT}\\hooks';
33
+ const ASB_MANAGED_KEY = '_asb_managed_hooks';
34
+ const ASB_HOOKS_SUBDIR = 'asb';
35
+ // ---------------------------------------------------------------------------
36
+ // Path helpers
37
+ // ---------------------------------------------------------------------------
38
+ function resolveHooksJsonPath(scope) {
39
+ const projectRoot = scope?.project?.trim();
40
+ if (projectRoot && projectRoot.length > 0) {
41
+ return getProjectCodexHooksJsonPath(projectRoot);
42
+ }
43
+ return getCodexHooksJsonPath();
44
+ }
45
+ function resolveHooksBundleParentDir(scope) {
46
+ const projectRoot = scope?.project?.trim();
47
+ if (projectRoot && projectRoot.length > 0) {
48
+ return path.join(getProjectCodexDir(projectRoot), 'hooks', ASB_HOOKS_SUBDIR);
49
+ }
50
+ return path.join(getCodexDir(), 'hooks', ASB_HOOKS_SUBDIR);
51
+ }
52
+ function resolveHookBundleTargetDir(entry, scope) {
53
+ return path.join(resolveHooksBundleParentDir(scope), entry.id);
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Portable path helpers
57
+ // ---------------------------------------------------------------------------
58
+ function preferHomeVar(command) {
59
+ const home = os.homedir();
60
+ if (!command.includes(home))
61
+ return command;
62
+ return command.replaceAll(home, '$HOME');
63
+ }
64
+ function filterForCodex(entries) {
65
+ const result = [];
66
+ for (const entry of entries) {
67
+ const filteredHooks = {};
68
+ for (const [event, groups] of Object.entries(entry.hooks)) {
69
+ if (!CODEX_SUPPORTED_EVENTS.has(event))
70
+ continue;
71
+ const filteredGroups = [];
72
+ for (const group of groups) {
73
+ const filteredHandlers = (group.hooks ?? []).filter((h) => CODEX_SUPPORTED_HANDLER_TYPES.has(h.type ?? ''));
74
+ if (filteredHandlers.length > 0) {
75
+ filteredGroups.push({ ...group, hooks: filteredHandlers });
76
+ }
77
+ }
78
+ if (filteredGroups.length > 0) {
79
+ filteredHooks[event] = filteredGroups;
80
+ }
81
+ }
82
+ if (Object.keys(filteredHooks).length > 0) {
83
+ result.push({ entry, hooks: filteredHooks });
84
+ }
85
+ }
86
+ return result;
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // hooks.json I/O
90
+ // ---------------------------------------------------------------------------
91
+ function readHooksJson(filePath) {
92
+ try {
93
+ if (fs.existsSync(filePath)) {
94
+ return {
95
+ ok: true,
96
+ data: JSON.parse(fs.readFileSync(filePath, 'utf-8')),
97
+ };
98
+ }
99
+ }
100
+ catch (err) {
101
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
102
+ }
103
+ return { ok: true, data: {} };
104
+ }
105
+ function writeHooksJson(filePath, data) {
106
+ ensureParentDir(filePath);
107
+ fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf-8');
108
+ }
109
+ // ---------------------------------------------------------------------------
110
+ // Config merge
111
+ // ---------------------------------------------------------------------------
112
+ function rewriteHookDir(hooks, distributedDir) {
113
+ const result = {};
114
+ for (const [event, groups] of Object.entries(hooks)) {
115
+ result[event] = groups.map((group) => ({
116
+ ...group,
117
+ hooks: (group.hooks ?? []).map((handler) => {
118
+ if (typeof handler.command !== 'string')
119
+ return handler;
120
+ return {
121
+ ...handler,
122
+ command: preferHomeVar(handler.command
123
+ .replaceAll(HOOK_DIR_PLACEHOLDER, distributedDir)
124
+ .replaceAll(CLAUDE_PLUGIN_ROOT_HOOKS_PREFIX, distributedDir)
125
+ .replaceAll(CLAUDE_PLUGIN_ROOT_HOOKS_PREFIX_WINDOWS, distributedDir)),
126
+ };
127
+ }),
128
+ }));
129
+ }
130
+ return result;
131
+ }
132
+ function mergeHooksIntoFile(fileData, filteredEntries, scope) {
133
+ const existingHooks = (fileData.hooks ?? {});
134
+ // Remove previously ASB-managed matcher groups
135
+ const cleanedHooks = {};
136
+ for (const [event, groups] of Object.entries(existingHooks)) {
137
+ const kept = groups.filter((g) => g._asb_source !== true);
138
+ if (kept.length > 0)
139
+ cleanedHooks[event] = kept;
140
+ }
141
+ // Merge filtered entries
142
+ for (const { entry, hooks } of filteredEntries) {
143
+ const resolvedHooks = entry.isBundle
144
+ ? rewriteHookDir(hooks, resolveHookBundleTargetDir(entry, scope))
145
+ : hooks;
146
+ for (const [event, groups] of Object.entries(resolvedHooks)) {
147
+ if (!cleanedHooks[event])
148
+ cleanedHooks[event] = [];
149
+ for (const group of groups) {
150
+ cleanedHooks[event].push({ ...group, _asb_source: true });
151
+ }
152
+ }
153
+ }
154
+ fileData.hooks = cleanedHooks;
155
+ fileData[ASB_MANAGED_KEY] = filteredEntries.map((f) => f.entry.id);
156
+ }
157
+ // ---------------------------------------------------------------------------
158
+ // Orphan cleanup for bundles
159
+ // ---------------------------------------------------------------------------
160
+ function cleanOrphanBundleDirs(activeIds, scope, dryRun) {
161
+ const parentDir = resolveHooksBundleParentDir(scope);
162
+ const results = [];
163
+ if (!fs.existsSync(parentDir))
164
+ return results;
165
+ try {
166
+ const entries = fs.readdirSync(parentDir, { withFileTypes: true });
167
+ for (const entry of entries) {
168
+ if (!entry.isDirectory())
169
+ continue;
170
+ if (activeIds.has(entry.name))
171
+ continue;
172
+ const dirPath = path.join(parentDir, entry.name);
173
+ try {
174
+ if (!dryRun)
175
+ rmDirRecursive(dirPath);
176
+ results.push({
177
+ platform: 'codex',
178
+ targetDir: dirPath,
179
+ status: 'deleted',
180
+ reason: 'orphan',
181
+ entryId: entry.name,
182
+ });
183
+ }
184
+ catch (error) {
185
+ const msg = error instanceof Error ? error.message : String(error);
186
+ results.push({
187
+ platform: 'codex',
188
+ targetDir: dirPath,
189
+ status: 'error',
190
+ error: `Failed to delete orphan: ${msg}`,
191
+ entryId: entry.name,
192
+ });
193
+ }
194
+ }
195
+ }
196
+ catch {
197
+ // Ignore directory read errors
198
+ }
199
+ return results;
200
+ }
201
+ // ---------------------------------------------------------------------------
202
+ // Codex prerequisites: feature flag + project trust
203
+ // ---------------------------------------------------------------------------
204
+ function ensureCodexHooksFeature(results) {
205
+ const configPath = getCodexConfigPath();
206
+ if (!fs.existsSync(configPath))
207
+ return;
208
+ const content = fs.readFileSync(configPath, 'utf-8');
209
+ if (content.includes('codex_hooks') && content.includes('true'))
210
+ return;
211
+ // Feature not explicitly enabled - add warning result
212
+ results.push({
213
+ platform: 'codex',
214
+ filePath: configPath,
215
+ status: 'written',
216
+ reason: 'codex_hooks feature flag may not be enabled in config.toml',
217
+ });
218
+ }
219
+ function ensureProjectTrust(projectRoot, results) {
220
+ const globalPath = getCodexConfigPath();
221
+ const globalContent = fs.existsSync(globalPath) ? fs.readFileSync(globalPath, 'utf-8') : '';
222
+ const trustResult = ensureTrustEntry(globalContent, projectRoot);
223
+ if (trustResult.warning) {
224
+ results.push({
225
+ platform: 'codex',
226
+ filePath: globalPath,
227
+ status: 'skipped',
228
+ reason: trustResult.warning,
229
+ });
230
+ }
231
+ if (trustResult.changed) {
232
+ ensureParentDir(globalPath);
233
+ fs.writeFileSync(globalPath, trustResult.content, 'utf-8');
234
+ results.push({
235
+ platform: 'codex',
236
+ filePath: globalPath,
237
+ status: 'written',
238
+ reason: 'added project trust entry',
239
+ });
240
+ }
241
+ }
242
+ export function distributeCodexHooks(options) {
243
+ const { scope, selected, dryRun = false, projectMode } = options;
244
+ if (scope?.project && projectMode === 'none') {
245
+ return { results: [] };
246
+ }
247
+ const results = [];
248
+ // Ensure Codex hooks feature flag is enabled
249
+ if (!dryRun) {
250
+ ensureCodexHooksFeature(results);
251
+ }
252
+ // Ensure project trust for project-scoped distribution
253
+ if (!dryRun && scope?.project) {
254
+ ensureProjectTrust(scope.project, results);
255
+ }
256
+ // Filter entries for Codex compatibility
257
+ const filteredEntries = filterForCodex(selected);
258
+ // Pre-validate hooks.json before making any filesystem changes
259
+ const hooksJsonPath = resolveHooksJsonPath(scope);
260
+ const fileResult = readHooksJson(hooksJsonPath);
261
+ if (!fileResult.ok) {
262
+ results.push({
263
+ platform: 'codex',
264
+ filePath: hooksJsonPath,
265
+ status: 'error',
266
+ error: `Cannot read hooks.json, aborting merge: ${fileResult.error}`,
267
+ });
268
+ return { results };
269
+ }
270
+ const fileData = fileResult.data;
271
+ // Validate hooks field shape: must be a Record<string, array> or absent
272
+ if (fileData.hooks !== undefined) {
273
+ if (typeof fileData.hooks !== 'object' ||
274
+ fileData.hooks === null ||
275
+ Array.isArray(fileData.hooks)) {
276
+ results.push({
277
+ platform: 'codex',
278
+ filePath: hooksJsonPath,
279
+ status: 'error',
280
+ error: 'hooks.json has invalid shape: "hooks" must be an object',
281
+ });
282
+ return { results };
283
+ }
284
+ for (const [event, groups] of Object.entries(fileData.hooks)) {
285
+ if (!Array.isArray(groups)) {
286
+ results.push({
287
+ platform: 'codex',
288
+ filePath: hooksJsonPath,
289
+ status: 'error',
290
+ error: `hooks.json has invalid shape: "hooks.${event}" must be an array`,
291
+ });
292
+ return { results };
293
+ }
294
+ }
295
+ }
296
+ // Phase 1: Copy bundle files (only after validation passes)
297
+ const bundleEntries = filteredEntries.filter((f) => f.entry.isBundle).map((f) => f.entry);
298
+ if (bundleEntries.length > 0) {
299
+ const bundleOutcome = distributeBundle({
300
+ section: 'hooks',
301
+ selected: bundleEntries,
302
+ platforms: ['codex'],
303
+ resolveTargetDir: (_p, entry) => resolveHookBundleTargetDir(entry, scope),
304
+ listFiles: listHookBundleFiles,
305
+ getId: (entry) => entry.id,
306
+ scope,
307
+ dryRun,
308
+ });
309
+ results.push(...bundleOutcome.results);
310
+ }
311
+ // Clean up orphan bundle directories
312
+ const activeBundleIds = new Set(bundleEntries.map((e) => e.id));
313
+ results.push(...cleanOrphanBundleDirs(activeBundleIds, scope, dryRun));
314
+ // Phase 2: Merge hook configs into hooks.json
315
+ const previouslyManaged = (fileData[ASB_MANAGED_KEY] ?? []);
316
+ // Check for existing ASB groups
317
+ const existingHooks = (fileData.hooks ?? {});
318
+ const hasAsbGroups = Object.values(existingHooks).some((groups) => groups.some((g) => g._asb_source === true));
319
+ if (filteredEntries.length === 0 && previouslyManaged.length === 0 && !hasAsbGroups) {
320
+ return { results };
321
+ }
322
+ const before = JSON.stringify(fileData);
323
+ mergeHooksIntoFile(fileData, filteredEntries, scope);
324
+ // Clean up empty state: if no hooks remain, remove the file entirely
325
+ const mergedHooks = fileData.hooks;
326
+ const totalGroups = Object.values(mergedHooks).reduce((sum, groups) => sum + groups.length, 0);
327
+ const hasNoHooks = totalGroups === 0;
328
+ if (hasNoHooks && filteredEntries.length === 0) {
329
+ // Remove ASB tracking keys
330
+ delete fileData[ASB_MANAGED_KEY];
331
+ // If file has only hooks (now empty) and ASB keys, consider deleting
332
+ const remainingKeys = Object.keys(fileData).filter((k) => k !== 'hooks');
333
+ if (remainingKeys.length === 0 && fs.existsSync(hooksJsonPath) && !dryRun) {
334
+ fs.unlinkSync(hooksJsonPath);
335
+ results.push({
336
+ platform: 'codex',
337
+ filePath: hooksJsonPath,
338
+ status: 'deleted',
339
+ reason: 'no hooks remain',
340
+ });
341
+ return { results };
342
+ }
343
+ }
344
+ const after = JSON.stringify(fileData);
345
+ if (before === after) {
346
+ results.push({
347
+ platform: 'codex',
348
+ filePath: hooksJsonPath,
349
+ status: 'skipped',
350
+ reason: 'up-to-date',
351
+ });
352
+ }
353
+ else if (dryRun) {
354
+ const reason = filteredEntries.length === 0 ? 'hooks cleared' : `${filteredEntries.length} hook(s) merged`;
355
+ results.push({ platform: 'codex', filePath: hooksJsonPath, status: 'written', reason });
356
+ }
357
+ else {
358
+ try {
359
+ writeHooksJson(hooksJsonPath, fileData);
360
+ const reason = filteredEntries.length === 0 ? 'hooks cleared' : `${filteredEntries.length} hook(s) merged`;
361
+ results.push({ platform: 'codex', filePath: hooksJsonPath, status: 'written', reason });
362
+ }
363
+ catch (error) {
364
+ const msg = error instanceof Error ? error.message : String(error);
365
+ results.push({ platform: 'codex', filePath: hooksJsonPath, status: 'error', error: msg });
366
+ }
367
+ }
368
+ return { results };
369
+ }
370
+ //# sourceMappingURL=codex-distribute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-distribute.js","sourceRoot":"","sources":["../../src/hooks/codex-distribute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAInD,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,YAAY;IACZ,aAAa;IACb,cAAc;IACd,kBAAkB;IAClB,MAAM;CACP,CAAC,CAAC;AAEH,MAAM,6BAA6B,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAE3D,wFAAwF;AACxF,MAAM,+BAA+B,GAAG,6BAA6B,CAAC;AACtE,wFAAwF;AACxF,MAAM,uCAAuC,GAAG,8BAA8B,CAAC;AAE/E,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAC7C,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,SAAS,oBAAoB,CAAC,KAAmB;IAC/C,MAAM,WAAW,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,4BAA4B,CAAC,WAAW,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,qBAAqB,EAAE,CAAC;AACjC,CAAC;AAED,SAAS,2BAA2B,CAAC,KAAmB;IACtD,MAAM,WAAW,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,0BAA0B,CAAC,KAAgB,EAAE,KAAmB;IACvE,OAAO,IAAI,CAAC,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,SAAS,aAAa,CAAC,OAAe;IACpC,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAC1B,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IAC5C,OAAO,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC;AAWD,SAAS,cAAc,CAAC,OAA6B;IACnD,MAAM,MAAM,GAAwB,EAAE,CAAC;IAEvC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,aAAa,GAA8B,EAAE,CAAC;QAEpD,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1D,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YAEjD,MAAM,cAAc,GAAc,EAAE,CAAC;YACrC,KAAK,MAAM,KAAK,IAAI,MAAqD,EAAE,CAAC;gBAC1E,MAAM,gBAAgB,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACxD,6BAA6B,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAChD,CAAC;gBACF,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAChC,cAAc,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBAC7D,CAAC;YACH,CAAC;YAED,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,aAAa,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC;YACxC,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,SAAS,aAAa,CACpB,QAAgB;IAEhB,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAA4B;aAChF,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAChF,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAChC,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB,EAAE,IAA6B;IACrE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC1B,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5E,CAAC;AAED,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,SAAS,cAAc,CACrB,KAAgC,EAChC,cAAsB;IAEtB,MAAM,MAAM,GAA8B,EAAE,CAAC;IAE7C,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC,KAAK,CAAC,GAAI,MAAyD,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACzF,GAAG,KAAK;YACR,KAAK,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBACzC,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;oBAAE,OAAO,OAAO,CAAC;gBACxD,OAAO;oBACL,GAAG,OAAO;oBACV,OAAO,EAAE,aAAa,CACpB,OAAO,CAAC,OAAO;yBACZ,UAAU,CAAC,oBAAoB,EAAE,cAAc,CAAC;yBAChD,UAAU,CAAC,+BAA+B,EAAE,cAAc,CAAC;yBAC3D,UAAU,CAAC,uCAAuC,EAAE,cAAc,CAAC,CACvE;iBACF,CAAC;YACJ,CAAC,CAAC;SACH,CAAC,CAAC,CAAC;IACN,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,kBAAkB,CACzB,QAAiC,EACjC,eAAoC,EACpC,KAAmB;IAEnB,MAAM,aAAa,GAAG,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAA8B,CAAC;IAE1E,+CAA+C;IAC/C,MAAM,YAAY,GAA8B,EAAE,CAAC;IACnD,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QAC5D,MAAM,IAAI,GAAI,MAAyC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC;QAC9F,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAClD,CAAC;IAED,yBAAyB;IACzB,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,eAAe,EAAE,CAAC;QAC/C,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ;YAClC,CAAC,CAAC,cAAc,CAAC,KAAK,EAAE,0BAA0B,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACjE,CAAC,CAAC,KAAK,CAAC;QAEV,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;YAC5D,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;gBAAE,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;YACnD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,YAAY,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,GAAI,KAAiC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;YACzF,CAAC;QACH,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,KAAK,GAAG,YAAY,CAAC;IAC9B,QAAQ,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACrE,CAAC;AAED,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E,SAAS,qBAAqB,CAC5B,SAAsB,EACtB,KAAmB,EACnB,MAAgB;IAEhB,MAAM,SAAS,GAAG,2BAA2B,CAAC,KAAK,CAAC,CAAC;IACrD,MAAM,OAAO,GAAmD,EAAE,CAAC;IAEnE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,OAAO,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;gBAAE,SAAS;YACnC,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YAExC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACjD,IAAI,CAAC;gBACH,IAAI,CAAC,MAAM;oBAAE,cAAc,CAAC,OAAO,CAAC,CAAC;gBACrC,OAAO,CAAC,IAAI,CAAC;oBACX,QAAQ,EAAE,OAAO;oBACjB,SAAS,EAAE,OAAO;oBAClB,MAAM,EAAE,SAAS;oBACjB,MAAM,EAAE,QAAQ;oBAChB,OAAO,EAAE,KAAK,CAAC,IAAI;iBACpB,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACnE,OAAO,CAAC,IAAI,CAAC;oBACX,QAAQ,EAAE,OAAO;oBACjB,SAAS,EAAE,OAAO;oBAClB,MAAM,EAAE,OAAO;oBACf,KAAK,EAAE,4BAA4B,GAAG,EAAE;oBACxC,OAAO,EAAE,KAAK,CAAC,IAAI;iBACpB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+BAA+B;IACjC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8EAA8E;AAC9E,oDAAoD;AACpD,8EAA8E;AAE9E,SAAS,uBAAuB,CAC9B,OAA2F;IAE3F,MAAM,UAAU,GAAG,kBAAkB,EAAE,CAAC;IACxC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO;IAEvC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACrD,IAAI,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO;IAExE,sDAAsD;IACtD,OAAO,CAAC,IAAI,CAAC;QACX,QAAQ,EAAE,OAAO;QACjB,QAAQ,EAAE,UAAU;QACpB,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,4DAA4D;KACrE,CAAC,CAAC;AACL,CAAC;AAED,SAAS,kBAAkB,CACzB,WAAmB,EACnB,OAA2F;IAE3F,MAAM,UAAU,GAAG,kBAAkB,EAAE,CAAC;IACxC,MAAM,aAAa,GAAG,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5F,MAAM,WAAW,GAAG,gBAAgB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAEjE,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC;YACX,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,UAAU;YACpB,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,WAAW,CAAC,OAAO;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;QACxB,eAAe,CAAC,UAAU,CAAC,CAAC;QAC5B,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC;YACX,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,UAAU;YACpB,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,2BAA2B;SACpC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAaD,MAAM,UAAU,oBAAoB,CAAC,OAAmC;IAGtE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAEjE,IAAI,KAAK,EAAE,OAAO,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;QAC7C,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,MAAM,OAAO,GAET,EAAE,CAAC;IAEP,6CAA6C;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,uDAAuD;IACvD,IAAI,CAAC,MAAM,IAAI,KAAK,EAAE,OAAO,EAAE,CAAC;QAC9B,kBAAkB,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,yCAAyC;IACzC,MAAM,eAAe,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAEjD,+DAA+D;IAC/D,MAAM,aAAa,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;IAEhD,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC;YACX,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,aAAa;YACvB,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,2CAA2C,UAAU,CAAC,KAAK,EAAE;SACrE,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,CAAC;IACrB,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC;IAEjC,wEAAwE;IACxE,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACjC,IACE,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ;YAClC,QAAQ,CAAC,KAAK,KAAK,IAAI;YACvB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAC7B,CAAC;YACD,OAAO,CAAC,IAAI,CAAC;gBACX,QAAQ,EAAE,OAAO;gBACjB,QAAQ,EAAE,aAAa;gBACvB,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,yDAAyD;aACjE,CAAC,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,CAAC;QACrB,CAAC;QACD,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAgC,CAAC,EAAE,CAAC;YACxF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,OAAO,CAAC,IAAI,CAAC;oBACX,QAAQ,EAAE,OAAO;oBACjB,QAAQ,EAAE,aAAa;oBACvB,MAAM,EAAE,OAAO;oBACf,KAAK,EAAE,wCAAwC,KAAK,oBAAoB;iBACzE,CAAC,CAAC;gBACH,OAAO,EAAE,OAAO,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IAED,4DAA4D;IAC5D,MAAM,aAAa,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1F,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,aAAa,GAAG,gBAAgB,CAA2B;YAC/D,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,aAAa;YACvB,SAAS,EAAE,CAAC,OAAO,CAAC;YACpB,gBAAgB,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,0BAA0B,CAAC,KAAK,EAAE,KAAK,CAAC;YACzE,SAAS,EAAE,mBAAmB;YAC9B,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE;YAC1B,KAAK;YACL,MAAM;SACP,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,qCAAqC;IACrC,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,GAAG,qBAAqB,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IAEvE,8CAA8C;IAC9C,MAAM,iBAAiB,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,EAAE,CAAa,CAAC;IAExE,gCAAgC;IAChC,MAAM,aAAa,GAAG,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAA8B,CAAC;IAC1E,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAC/D,MAAyC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,IAAI,CAAC,CAC/E,CAAC;IAEF,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QACpF,OAAO,EAAE,OAAO,EAAE,CAAC;IACrB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACxC,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;IAErD,qEAAqE;IACrE,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAkC,CAAC;IAChE,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/F,MAAM,UAAU,GAAG,WAAW,KAAK,CAAC,CAAC;IAErC,IAAI,UAAU,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,2BAA2B;QAC3B,OAAO,QAAQ,CAAC,eAAe,CAAC,CAAC;QACjC,qEAAqE;QACrE,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC;QACzE,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1E,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC;gBACX,QAAQ,EAAE,OAAO;gBACjB,QAAQ,EAAE,aAAa;gBACvB,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,iBAAiB;aAC1B,CAAC,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEvC,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,aAAa;YACvB,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,YAAY;SACrB,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,MAAM,EAAE,CAAC;QAClB,MAAM,MAAM,GACV,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,MAAM,iBAAiB,CAAC;QAC9F,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1F,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,cAAc,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;YACxC,MAAM,MAAM,GACV,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,MAAM,iBAAiB,CAAC;YAC9F,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1F,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnE,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC"}
@@ -14,14 +14,14 @@
14
14
  import type { ConfigScope } from '../config/scope.js';
15
15
  import type { DistributionResult } from '../library/distribute.js';
16
16
  import type { BundleDistributionResult } from '../library/distribute-bundle.js';
17
- export type HookPlatform = 'claude-code';
17
+ export type HookPlatform = 'claude-code' | 'codex';
18
18
  export interface HookDistributionOutcome {
19
19
  results: Array<DistributionResult<HookPlatform> | BundleDistributionResult<HookPlatform>>;
20
20
  }
21
21
  /**
22
- * Distribute hooks to Claude Code:
22
+ * Distribute hooks to active targets (Claude Code, Codex, etc.):
23
23
  * 1. Copy bundle files for bundle-type hooks
24
- * 2. Merge all active hook configs into settings.json
24
+ * 2. Merge all active hook configs into each target's config
25
25
  * 3. Clean up orphan bundle directories
26
26
  */
27
27
  export declare function distributeHooks(scope?: ConfigScope, activeAppIds?: string[], assumeInstalled?: ReadonlySet<string>, options?: {
@@ -19,6 +19,7 @@ import { distributeBundle } from '../library/distribute-bundle.js';
19
19
  import { ensureParentDir, rmDirRecursive } from '../library/fs.js';
20
20
  import { loadLibraryStateSectionForApplication } from '../library/state.js';
21
21
  import { getTargetById } from '../targets/registry.js';
22
+ import { distributeCodexHooks } from './codex-distribute.js';
22
23
  import { listHookBundleFiles, loadHookLibrary } from './library.js';
23
24
  import { HOOK_DIR_PLACEHOLDER } from './schema.js';
24
25
  const ASB_MANAGED_KEY = '_asb_managed_hooks';
@@ -191,10 +192,20 @@ function cleanOrphanBundleDirs(activeIds, scope, dryRun) {
191
192
  // ---------------------------------------------------------------------------
192
193
  // Main distribution entry point
193
194
  // ---------------------------------------------------------------------------
195
+ // ---------------------------------------------------------------------------
196
+ // Target reachability check
197
+ // ---------------------------------------------------------------------------
198
+ function isTargetReachable(platformId, _activeAppIds, assumeInstalled) {
199
+ const target = getTargetById(platformId);
200
+ if (target?.isInstalled?.() === false && !assumeInstalled?.has(platformId)) {
201
+ return false;
202
+ }
203
+ return true;
204
+ }
194
205
  /**
195
- * Distribute hooks to Claude Code:
206
+ * Distribute hooks to active targets (Claude Code, Codex, etc.):
196
207
  * 1. Copy bundle files for bundle-type hooks
197
- * 2. Merge all active hook configs into settings.json
208
+ * 2. Merge all active hook configs into each target's config
198
209
  * 3. Clean up orphan bundle directories
199
210
  */
200
211
  export function distributeHooks(scope, activeAppIds, assumeInstalled, options) {
@@ -202,49 +213,80 @@ export function distributeHooks(scope, activeAppIds, assumeInstalled, options) {
202
213
  return { results: [] };
203
214
  }
204
215
  const results = [];
205
- const platform = 'claude-code';
206
- // Skip if claude-code is not installed (unless assumed installed)
207
- const target = getTargetById(platform);
208
- if (target?.isInstalled?.() === false && !assumeInstalled?.has(platform)) {
216
+ const dryRun = options?.dryRun === true;
217
+ // Check if any hook-capable target is reachable before loading library
218
+ const claudeReachable = isTargetReachable('claude-code', activeAppIds, assumeInstalled);
219
+ const codexReachable = isTargetReachable('codex', activeAppIds, assumeInstalled);
220
+ if (!claudeReachable && !codexReachable) {
209
221
  return { results };
210
222
  }
211
- // When activeAppIds is set and doesn't include claude-code, treat as inactive:
212
- // use empty selection so that cleanup/unmerge of previously injected hooks can run.
213
- const isActive = !activeAppIds || activeAppIds.includes(platform);
223
+ // Load all hook entries once (shared across targets)
214
224
  const allEntries = loadHookLibrary(scope);
215
225
  const byId = new Map(allEntries.map((e) => [e.id, e]));
216
- const state = loadLibraryStateSectionForApplication('hooks', 'claude-code', scope);
226
+ // --- Claude Code distribution ---
227
+ const claudeResults = distributeClaude({
228
+ scope,
229
+ activeAppIds,
230
+ assumeInstalled,
231
+ allEntries,
232
+ byId,
233
+ dryRun,
234
+ projectMode: options?.projectMode,
235
+ });
236
+ results.push(...claudeResults);
237
+ // --- Codex distribution ---
238
+ const codexResults = distributeCodex({
239
+ scope,
240
+ activeAppIds,
241
+ assumeInstalled,
242
+ allEntries,
243
+ byId,
244
+ dryRun,
245
+ projectMode: options?.projectMode,
246
+ });
247
+ results.push(...codexResults);
248
+ return { results };
249
+ }
250
+ function distributeClaude(ctx) {
251
+ const platform = 'claude-code';
252
+ const results = [];
253
+ // Skip if claude-code is not installed (unless assumed installed)
254
+ const target = getTargetById(platform);
255
+ if (target?.isInstalled?.() === false && !ctx.assumeInstalled?.has(platform)) {
256
+ return results;
257
+ }
258
+ // When activeAppIds is set and doesn't include claude-code, treat as inactive
259
+ const isActive = !ctx.activeAppIds || ctx.activeAppIds.includes(platform);
260
+ const state = loadLibraryStateSectionForApplication('hooks', 'claude-code', ctx.scope);
217
261
  const selected = [];
218
262
  if (isActive) {
219
263
  for (const id of state.enabled) {
220
- const e = byId.get(id);
264
+ const e = ctx.byId.get(id);
221
265
  if (e)
222
266
  selected.push(e);
223
267
  }
224
268
  }
225
269
  // Phase 1: Copy bundle files for bundle-type hooks
226
- const dryRun = options?.dryRun === true;
227
270
  const bundleEntries = selected.filter((e) => e.isBundle);
228
271
  if (bundleEntries.length > 0) {
229
272
  const bundleOutcome = distributeBundle({
230
273
  section: 'hooks',
231
274
  selected: bundleEntries,
232
275
  platforms: [platform],
233
- resolveTargetDir: (_p, entry) => resolveHookBundleTargetDir(_p, entry, scope),
276
+ resolveTargetDir: (_p, entry) => resolveHookBundleTargetDir(_p, entry, ctx.scope),
234
277
  listFiles: listHookBundleFiles,
235
278
  getId: (entry) => entry.id,
236
- scope,
237
- dryRun,
279
+ scope: ctx.scope,
280
+ dryRun: ctx.dryRun,
238
281
  });
239
282
  results.push(...bundleOutcome.results);
240
283
  }
241
284
  // Clean up orphan bundle directories
242
285
  const activeBundleIds = new Set(bundleEntries.map((e) => e.id));
243
- results.push(...cleanOrphanBundleDirs(activeBundleIds, scope, dryRun));
286
+ results.push(...cleanOrphanBundleDirs(activeBundleIds, ctx.scope, ctx.dryRun));
244
287
  // Phase 2: Merge hook configs into settings.json
245
- const settingsPath = resolveSettingsPath(scope);
288
+ const settingsPath = resolveSettingsPath(ctx.scope);
246
289
  const settingsResult = readSettingsJson(settingsPath);
247
- // Abort hooks config merge if settings file is unreadable (prevents data loss)
248
290
  if (!settingsResult.ok) {
249
291
  results.push({
250
292
  platform,
@@ -252,23 +294,22 @@ export function distributeHooks(scope, activeAppIds, assumeInstalled, options) {
252
294
  status: 'error',
253
295
  error: `Cannot read settings.json, aborting hooks merge: ${settingsResult.error}`,
254
296
  });
255
- return { results };
297
+ return results;
256
298
  }
257
299
  const settings = settingsResult.data;
258
300
  const previouslyManaged = (settings[ASB_MANAGED_KEY] ?? []);
259
- // Also check for legacy ASB hooks (missing _asb_source marker)
260
301
  const existingHooks = (settings.hooks ?? {});
261
- const hasLegacyAsb = Object.values(existingHooks).some((groups) => groups.some((g) => isLegacyAsbGroup(g, scope)));
302
+ const hasLegacyAsb = Object.values(existingHooks).some((groups) => groups.some((g) => isLegacyAsbGroup(g, ctx.scope)));
262
303
  if (selected.length === 0 && previouslyManaged.length === 0 && !hasLegacyAsb) {
263
- return { results };
304
+ return results;
264
305
  }
265
306
  const before = JSON.stringify(settings);
266
- mergeHooksIntoSettings(settings, selected, scope);
307
+ mergeHooksIntoSettings(settings, selected, ctx.scope);
267
308
  const after = JSON.stringify(settings);
268
309
  if (before === after) {
269
310
  results.push({ platform, filePath: settingsPath, status: 'skipped', reason: 'up-to-date' });
270
311
  }
271
- else if (dryRun) {
312
+ else if (ctx.dryRun) {
272
313
  const reason = selected.length === 0 ? 'hooks cleared' : `${selected.length} hook(s) merged`;
273
314
  results.push({ platform, filePath: settingsPath, status: 'written', reason });
274
315
  }
@@ -283,6 +324,34 @@ export function distributeHooks(scope, activeAppIds, assumeInstalled, options) {
283
324
  results.push({ platform, filePath: settingsPath, status: 'error', error: msg });
284
325
  }
285
326
  }
286
- return { results };
327
+ return results;
328
+ }
329
+ // ---------------------------------------------------------------------------
330
+ // Codex distribution (delegates to codex-distribute module)
331
+ // ---------------------------------------------------------------------------
332
+ function distributeCodex(ctx) {
333
+ const platform = 'codex';
334
+ // Skip if codex is not installed (unless assumed installed)
335
+ const target = getTargetById(platform);
336
+ if (target?.isInstalled?.() === false && !ctx.assumeInstalled?.has(platform)) {
337
+ return [];
338
+ }
339
+ const isActive = !ctx.activeAppIds || ctx.activeAppIds.includes(platform);
340
+ const state = loadLibraryStateSectionForApplication('hooks', 'codex', ctx.scope);
341
+ const selected = [];
342
+ if (isActive) {
343
+ for (const id of state.enabled) {
344
+ const e = ctx.byId.get(id);
345
+ if (e)
346
+ selected.push(e);
347
+ }
348
+ }
349
+ const outcome = distributeCodexHooks({
350
+ scope: ctx.scope,
351
+ selected,
352
+ dryRun: ctx.dryRun,
353
+ projectMode: ctx.projectMode,
354
+ });
355
+ return outcome.results;
287
356
  }
288
357
  //# sourceMappingURL=distribution.js.map