@cleocode/adapters 2026.10.8 → 2026.10.9
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.
- package/dist/heavy-command-hook.js +13 -12
- package/dist/heavy-command-hook.js.map +1 -1
- package/dist/index.js +60 -133
- package/dist/index.js.map +2 -2
- package/dist/project-hook-delivery.d.ts +6 -0
- package/dist/project-hook-delivery.d.ts.map +1 -0
- package/dist/project-hook-delivery.js +527 -0
- package/dist/project-hook-delivery.js.map +6 -0
- package/dist/project-hook-native.d.ts +7 -0
- package/dist/project-hook-native.d.ts.map +1 -0
- package/dist/project-hook-native.js +84 -0
- package/dist/project-hook-native.js.map +6 -0
- package/dist/providers/claude-code/hooks.d.ts.map +1 -1
- package/dist/providers/codex/hooks.d.ts +5 -4
- package/dist/providers/codex/hooks.d.ts.map +1 -1
- package/dist/providers/cursor/hooks.d.ts.map +1 -1
- package/dist/providers/gemini-cli/hooks.d.ts.map +1 -1
- package/dist/providers/opencode/hooks.d.ts.map +1 -1
- package/dist/providers/pi/hooks.d.ts.map +1 -1
- package/dist/providers/shared/heavy-command-hook-install.d.ts +2 -1
- package/dist/providers/shared/heavy-command-hook-install.d.ts.map +1 -1
- package/package.json +14 -5
- package/src/__tests__/project-hook-delivery.test.ts +319 -0
- package/src/project-hook-delivery.ts +532 -0
- package/src/project-hook-native.ts +128 -0
- package/src/providers/claude-code/hooks.ts +2 -30
- package/src/providers/codex/hooks.ts +7 -9
- package/src/providers/cursor/hooks.ts +2 -22
- package/src/providers/gemini-cli/hooks.ts +2 -12
- package/src/providers/opencode/hooks.ts +2 -22
- package/src/providers/pi/hooks.ts +2 -26
- package/src/providers/shared/heavy-command-hook-install.ts +21 -10
|
@@ -20,6 +20,7 @@ import { existsSync } from 'node:fs';
|
|
|
20
20
|
import { readdir, readFile } from 'node:fs/promises';
|
|
21
21
|
import { join } from 'node:path';
|
|
22
22
|
import { updateJsonConfigFile } from '@cleocode/caamp';
|
|
23
|
+
import { PROVIDER_NATIVE_EVENT_MAPS } from '@cleocode/caamp/hooks';
|
|
23
24
|
import type { AdapterHookProvider } from '@cleocode/contracts';
|
|
24
25
|
import { excludeLocalSettingsFromGit } from '../shared/heavy-command-hook-install.js';
|
|
25
26
|
import { appendHookEntry, hookMap, isPlainObject, projectClaudeSettingsPath } from './paths.js';
|
|
@@ -114,36 +115,7 @@ const PROVIDER_ID = 'claude-code' as const;
|
|
|
114
115
|
* Used as fallback when CAAMP runtime is unavailable, and as the synchronous
|
|
115
116
|
* implementation of `mapProviderEvent()`.
|
|
116
117
|
*/
|
|
117
|
-
const CLAUDE_CODE_EVENT_MAP
|
|
118
|
-
// CAAMP: toNative('SessionStart', 'claude-code') = 'SessionStart'
|
|
119
|
-
SessionStart: 'SessionStart',
|
|
120
|
-
// CAAMP: toNative('SessionEnd', 'claude-code') = 'SessionEnd'
|
|
121
|
-
SessionEnd: 'SessionEnd',
|
|
122
|
-
// CAAMP: toNative('PromptSubmit', 'claude-code') = 'UserPromptSubmit'
|
|
123
|
-
UserPromptSubmit: 'PromptSubmit',
|
|
124
|
-
// CAAMP: toNative('ResponseComplete', 'claude-code') = 'Stop'
|
|
125
|
-
Stop: 'ResponseComplete',
|
|
126
|
-
// CAAMP: toNative('PreToolUse', 'claude-code') = 'PreToolUse'
|
|
127
|
-
PreToolUse: 'PreToolUse',
|
|
128
|
-
// CAAMP: toNative('PostToolUse', 'claude-code') = 'PostToolUse'
|
|
129
|
-
PostToolUse: 'PostToolUse',
|
|
130
|
-
// CAAMP: toNative('PostToolUseFailure','claude-code') = 'PostToolUseFailure'
|
|
131
|
-
PostToolUseFailure: 'PostToolUseFailure',
|
|
132
|
-
// CAAMP: toNative('PermissionRequest', 'claude-code') = 'PermissionRequest'
|
|
133
|
-
PermissionRequest: 'PermissionRequest',
|
|
134
|
-
// CAAMP: toNative('SubagentStart', 'claude-code') = 'SubagentStart'
|
|
135
|
-
SubagentStart: 'SubagentStart',
|
|
136
|
-
// CAAMP: toNative('SubagentStop', 'claude-code') = 'SubagentStop'
|
|
137
|
-
SubagentStop: 'SubagentStop',
|
|
138
|
-
// CAAMP: toNative('PreCompact', 'claude-code') = 'PreCompact'
|
|
139
|
-
PreCompact: 'PreCompact',
|
|
140
|
-
// CAAMP: toNative('PostCompact', 'claude-code') = 'PostCompact'
|
|
141
|
-
PostCompact: 'PostCompact',
|
|
142
|
-
// CAAMP: toNative('Notification', 'claude-code') = 'Notification'
|
|
143
|
-
Notification: 'Notification',
|
|
144
|
-
// CAAMP: toNative('ConfigChange', 'claude-code') = 'ConfigChange'
|
|
145
|
-
ConfigChange: 'ConfigChange',
|
|
146
|
-
};
|
|
118
|
+
const CLAUDE_CODE_EVENT_MAP = PROVIDER_NATIVE_EVENT_MAPS['claude-code'] ?? {};
|
|
147
119
|
|
|
148
120
|
/**
|
|
149
121
|
* Hook provider for Claude Code.
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Codex CLI Hook Provider
|
|
3
3
|
*
|
|
4
4
|
* Maps Codex CLI's native hook events to CAAMP canonical hook events.
|
|
5
|
-
*
|
|
5
|
+
* Capabilities and fallback maps are generated from the CAAMP registry.
|
|
6
6
|
*
|
|
7
7
|
* Codex CLI event mapping:
|
|
8
8
|
* - SessionStart -> SessionStart
|
|
9
9
|
* - PromptSubmit -> UserPromptSubmit
|
|
10
10
|
* - ResponseComplete -> Stop
|
|
11
|
+
* - PreToolUse -> PreToolUse
|
|
11
12
|
*
|
|
12
13
|
* @task T162
|
|
13
14
|
* @epic T134
|
|
@@ -15,23 +16,20 @@
|
|
|
15
16
|
|
|
16
17
|
import { homedir } from 'node:os';
|
|
17
18
|
import { join } from 'node:path';
|
|
19
|
+
import { PROVIDER_NATIVE_EVENT_MAPS } from '@cleocode/caamp/hooks';
|
|
18
20
|
import type { AdapterHookProvider } from '@cleocode/contracts';
|
|
19
21
|
import { readLatestTranscript } from '../shared/transcript-reader.js';
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Mapping from Codex CLI native event names to CAAMP canonical event names.
|
|
23
25
|
*/
|
|
24
|
-
const CODEX_EVENT_MAP
|
|
25
|
-
SessionStart: 'SessionStart',
|
|
26
|
-
PromptSubmit: 'UserPromptSubmit',
|
|
27
|
-
ResponseComplete: 'Stop',
|
|
28
|
-
};
|
|
26
|
+
const CODEX_EVENT_MAP = PROVIDER_NATIVE_EVENT_MAPS['codex'] ?? {};
|
|
29
27
|
|
|
30
28
|
/**
|
|
31
29
|
* Hook provider for Codex CLI.
|
|
32
30
|
*
|
|
33
31
|
* Codex CLI registers hooks via its configuration system at
|
|
34
|
-
*
|
|
32
|
+
* the project-local .codex/hooks.json. Hook handlers are shell commands or script paths that
|
|
35
33
|
* execute when the corresponding event fires.
|
|
36
34
|
*
|
|
37
35
|
* Since hooks are registered through the config system (managed by
|
|
@@ -39,9 +37,9 @@ const CODEX_EVENT_MAP: Record<string, string> = {
|
|
|
39
37
|
* track registration state without performing filesystem operations.
|
|
40
38
|
*
|
|
41
39
|
* @remarks
|
|
42
|
-
* Codex
|
|
40
|
+
* Codex supports PreToolUse; version certification remains separate from installation.
|
|
43
41
|
* Registration state is tracked in-memory because Codex CLI manages
|
|
44
|
-
* hooks through its own configuration system
|
|
42
|
+
* hooks through its own configuration system. Native project and hook trust are separate.
|
|
45
43
|
*
|
|
46
44
|
* @task T162
|
|
47
45
|
* @epic T134
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* @epic T134
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { PROVIDER_NATIVE_EVENT_MAPS } from '@cleocode/caamp/hooks';
|
|
23
24
|
import type { AdapterHookProvider } from '@cleocode/contracts';
|
|
24
25
|
|
|
25
26
|
/** CAAMP provider identifier for Cursor. */
|
|
@@ -34,28 +35,7 @@ const PROVIDER_ID = 'cursor' as const;
|
|
|
34
35
|
*
|
|
35
36
|
* Cursor uses camelCase names while CAAMP canonical names are PascalCase.
|
|
36
37
|
*/
|
|
37
|
-
const CURSOR_EVENT_MAP
|
|
38
|
-
// CAAMP: toNative('SessionStart', 'cursor') = 'sessionStart'
|
|
39
|
-
sessionStart: 'SessionStart',
|
|
40
|
-
// CAAMP: toNative('SessionEnd', 'cursor') = 'sessionEnd'
|
|
41
|
-
sessionEnd: 'SessionEnd',
|
|
42
|
-
// CAAMP: toNative('PromptSubmit', 'cursor') = 'beforeSubmitPrompt'
|
|
43
|
-
beforeSubmitPrompt: 'PromptSubmit',
|
|
44
|
-
// CAAMP: toNative('ResponseComplete', 'cursor') = 'stop'
|
|
45
|
-
stop: 'ResponseComplete',
|
|
46
|
-
// CAAMP: toNative('PreToolUse', 'cursor') = 'preToolUse'
|
|
47
|
-
preToolUse: 'PreToolUse',
|
|
48
|
-
// CAAMP: toNative('PostToolUse', 'cursor') = 'postToolUse'
|
|
49
|
-
postToolUse: 'PostToolUse',
|
|
50
|
-
// CAAMP: toNative('PostToolUseFailure', 'cursor') = 'postToolUseFailure'
|
|
51
|
-
postToolUseFailure: 'PostToolUseFailure',
|
|
52
|
-
// CAAMP: toNative('SubagentStart', 'cursor') = 'subagentStart'
|
|
53
|
-
subagentStart: 'SubagentStart',
|
|
54
|
-
// CAAMP: toNative('SubagentStop', 'cursor') = 'subagentStop'
|
|
55
|
-
subagentStop: 'SubagentStop',
|
|
56
|
-
// CAAMP: toNative('PreCompact', 'cursor') = 'preCompact'
|
|
57
|
-
preCompact: 'PreCompact',
|
|
58
|
-
};
|
|
38
|
+
const CURSOR_EVENT_MAP = PROVIDER_NATIVE_EVENT_MAPS['cursor'] ?? {};
|
|
59
39
|
|
|
60
40
|
/**
|
|
61
41
|
* Hook provider for Cursor.
|
|
@@ -22,24 +22,14 @@
|
|
|
22
22
|
|
|
23
23
|
import { homedir } from 'node:os';
|
|
24
24
|
import { join } from 'node:path';
|
|
25
|
+
import { PROVIDER_NATIVE_EVENT_MAPS } from '@cleocode/caamp/hooks';
|
|
25
26
|
import type { AdapterHookProvider } from '@cleocode/contracts';
|
|
26
27
|
import { readLatestTranscript } from '../shared/transcript-reader.js';
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Mapping from Gemini CLI native event names to CAAMP canonical event names.
|
|
30
31
|
*/
|
|
31
|
-
const GEMINI_CLI_EVENT_MAP
|
|
32
|
-
SessionStart: 'SessionStart',
|
|
33
|
-
SessionEnd: 'SessionEnd',
|
|
34
|
-
PromptSubmit: 'BeforeAgent',
|
|
35
|
-
ResponseComplete: 'AfterAgent',
|
|
36
|
-
PreToolUse: 'BeforeTool',
|
|
37
|
-
PostToolUse: 'AfterTool',
|
|
38
|
-
PreModel: 'BeforeModel',
|
|
39
|
-
PostModel: 'AfterModel',
|
|
40
|
-
PreCompact: 'PreCompress',
|
|
41
|
-
Notification: 'Notification',
|
|
42
|
-
};
|
|
32
|
+
const GEMINI_CLI_EVENT_MAP = PROVIDER_NATIVE_EVENT_MAPS['gemini-cli'] ?? {};
|
|
43
33
|
|
|
44
34
|
/**
|
|
45
35
|
* Hook provider for Gemini CLI.
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* @epic T134
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import { PROVIDER_NATIVE_EVENT_MAPS } from '@cleocode/caamp/hooks';
|
|
22
23
|
import type { AdapterHookProvider } from '@cleocode/contracts';
|
|
23
24
|
|
|
24
25
|
/** CAAMP provider identifier for OpenCode. */
|
|
@@ -35,28 +36,7 @@ const PROVIDER_ID = 'opencode' as const;
|
|
|
35
36
|
* OpenCode uses dot-delimited and event-prefixed names (e.g. "event:session.created")
|
|
36
37
|
* while CAAMP canonical names are PascalCase (e.g. "SessionStart").
|
|
37
38
|
*/
|
|
38
|
-
const OPENCODE_EVENT_MAP
|
|
39
|
-
// CAAMP: toNative('SessionStart', 'opencode') = 'event:session.created'
|
|
40
|
-
'event:session.created': 'SessionStart',
|
|
41
|
-
// CAAMP: toNative('SessionEnd', 'opencode') = 'event:session.deleted'
|
|
42
|
-
'event:session.deleted': 'SessionEnd',
|
|
43
|
-
// CAAMP: toNative('PromptSubmit', 'opencode') = 'chat.message'
|
|
44
|
-
'chat.message': 'PromptSubmit',
|
|
45
|
-
// CAAMP: toNative('ResponseComplete', 'opencode') = 'event:session.idle'
|
|
46
|
-
'event:session.idle': 'ResponseComplete',
|
|
47
|
-
// CAAMP: toNative('PreToolUse', 'opencode') = 'tool.execute.before'
|
|
48
|
-
'tool.execute.before': 'PreToolUse',
|
|
49
|
-
// CAAMP: toNative('PostToolUse', 'opencode') = 'tool.execute.after'
|
|
50
|
-
'tool.execute.after': 'PostToolUse',
|
|
51
|
-
// CAAMP: toNative('PermissionRequest', 'opencode') = 'permission.ask'
|
|
52
|
-
'permission.ask': 'PermissionRequest',
|
|
53
|
-
// CAAMP: toNative('PreModel', 'opencode') = 'chat.params'
|
|
54
|
-
'chat.params': 'PreModel',
|
|
55
|
-
// CAAMP: toNative('PreCompact', 'opencode') = 'experimental.session.compacting'
|
|
56
|
-
'experimental.session.compacting': 'PreCompact',
|
|
57
|
-
// CAAMP: toNative('PostCompact', 'opencode') = 'event:session.compacted'
|
|
58
|
-
'event:session.compacted': 'PostCompact',
|
|
59
|
-
};
|
|
39
|
+
const OPENCODE_EVENT_MAP = PROVIDER_NATIVE_EVENT_MAPS['opencode'] ?? {};
|
|
60
40
|
|
|
61
41
|
/**
|
|
62
42
|
* Hook provider for OpenCode.
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
* @task T553
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
+
import { PROVIDER_NATIVE_EVENT_MAPS } from '@cleocode/caamp/hooks';
|
|
35
36
|
import type { AdapterHookProvider } from '@cleocode/contracts';
|
|
36
37
|
|
|
37
38
|
/** CAAMP provider identifier for Pi. */
|
|
@@ -48,32 +49,7 @@ const PROVIDER_ID = 'pi' as const;
|
|
|
48
49
|
* Used as fallback when CAAMP runtime is unavailable, and as the
|
|
49
50
|
* synchronous implementation of `mapProviderEvent()`.
|
|
50
51
|
*/
|
|
51
|
-
const PI_EVENT_MAP
|
|
52
|
-
// piEventCatalog: session_start → SessionStart
|
|
53
|
-
session_start: 'SessionStart',
|
|
54
|
-
// piEventCatalog: session_shutdown → SessionEnd
|
|
55
|
-
session_shutdown: 'SessionEnd',
|
|
56
|
-
// piEventCatalog: input → PromptSubmit
|
|
57
|
-
input: 'PromptSubmit',
|
|
58
|
-
// piEventCatalog: turn_end → Notification (assistant turn complete)
|
|
59
|
-
turn_end: 'Notification',
|
|
60
|
-
// piEventCatalog: tool_call → PreToolUse
|
|
61
|
-
tool_call: 'PreToolUse',
|
|
62
|
-
// piEventCatalog: tool_execution_start → PreToolUse (duplicate path)
|
|
63
|
-
tool_execution_start: 'PreToolUse',
|
|
64
|
-
// piEventCatalog: tool_result → PostToolUse
|
|
65
|
-
tool_result: 'PostToolUse',
|
|
66
|
-
// piEventCatalog: tool_execution_end → PostToolUse (duplicate path)
|
|
67
|
-
tool_execution_end: 'PostToolUse',
|
|
68
|
-
// piEventCatalog: before_agent_start → SubagentStart
|
|
69
|
-
before_agent_start: 'SubagentStart',
|
|
70
|
-
// piEventCatalog: agent_end → SubagentStop
|
|
71
|
-
agent_end: 'SubagentStop',
|
|
72
|
-
// piEventCatalog: before_provider_request → PreModel
|
|
73
|
-
before_provider_request: 'PreModel',
|
|
74
|
-
// piEventCatalog: context → PreCompact (context assembly is the pre-compaction proxy for Pi)
|
|
75
|
-
context: 'PreCompact',
|
|
76
|
-
};
|
|
52
|
+
const PI_EVENT_MAP = PROVIDER_NATIVE_EVENT_MAPS['pi'] ?? {};
|
|
77
53
|
|
|
78
54
|
/**
|
|
79
55
|
* Hook provider for Pi coding agent.
|
|
@@ -526,12 +526,17 @@ export const CODEX_HOOKS_FILE = '.codex/hooks.json';
|
|
|
526
526
|
export const OPENCODE_PLUGIN_FILE = `.opencode/plugins/${OPENCODE_HEAVY_COMMAND_PLUGIN}`;
|
|
527
527
|
|
|
528
528
|
/** Run git in `projectDir`; its trimmed stdout, or `null` on any failure. */
|
|
529
|
-
function gitOutput(
|
|
529
|
+
function gitOutput(
|
|
530
|
+
projectDir: string,
|
|
531
|
+
args: readonly string[],
|
|
532
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
533
|
+
): string | null {
|
|
530
534
|
try {
|
|
531
535
|
return execFileSync('git', ['-C', projectDir, ...args], {
|
|
532
536
|
encoding: 'utf-8',
|
|
533
537
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
534
538
|
timeout: 5000,
|
|
539
|
+
env,
|
|
535
540
|
}).trim();
|
|
536
541
|
} catch {
|
|
537
542
|
return null;
|
|
@@ -539,16 +544,20 @@ function gitOutput(projectDir: string, args: readonly string[]): string | null {
|
|
|
539
544
|
}
|
|
540
545
|
|
|
541
546
|
/** The repository's `info/exclude` path for `projectDir`, or `null` outside a work tree. */
|
|
542
|
-
function excludeFile(projectDir: string): string | null {
|
|
543
|
-
if (gitOutput(projectDir, ['rev-parse', '--is-inside-work-tree']) !== 'true') return null;
|
|
544
|
-
const path = gitOutput(projectDir, ['rev-parse', '--git-path', 'info/exclude']);
|
|
547
|
+
function excludeFile(projectDir: string, env: NodeJS.ProcessEnv = process.env): string | null {
|
|
548
|
+
if (gitOutput(projectDir, ['rev-parse', '--is-inside-work-tree'], env) !== 'true') return null;
|
|
549
|
+
const path = gitOutput(projectDir, ['rev-parse', '--git-path', 'info/exclude'], env);
|
|
545
550
|
if (path === null || path === '') return null;
|
|
546
551
|
return isAbsolute(path) ? path : join(projectDir, path);
|
|
547
552
|
}
|
|
548
553
|
|
|
549
554
|
/** The `info/exclude` line for a project file (relative to the repo root). */
|
|
550
|
-
function excludeLine(
|
|
551
|
-
|
|
555
|
+
function excludeLine(
|
|
556
|
+
projectDir: string,
|
|
557
|
+
relPath: string,
|
|
558
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
559
|
+
): string {
|
|
560
|
+
const prefix = gitOutput(projectDir, ['rev-parse', '--show-prefix'], env) ?? '';
|
|
552
561
|
return `/${prefix}${relPath}`;
|
|
553
562
|
}
|
|
554
563
|
|
|
@@ -563,18 +572,20 @@ function excludeLine(projectDir: string, relPath: string): string {
|
|
|
563
572
|
* @param projectDir - the project root.
|
|
564
573
|
* @param relPath - the file, relative to the project (forward slashes).
|
|
565
574
|
* @param marker - the block's first line.
|
|
575
|
+
* @param env - Git discovery environment; existing callers retain the process environment.
|
|
566
576
|
* @returns whether a block was added.
|
|
567
577
|
*/
|
|
568
578
|
export function excludeHookFileFromGit(
|
|
569
579
|
projectDir: string,
|
|
570
580
|
relPath: string,
|
|
571
581
|
marker: string = HOOK_FILE_EXCLUDE_MARKER,
|
|
582
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
572
583
|
): boolean {
|
|
573
|
-
const exclude = excludeFile(projectDir);
|
|
584
|
+
const exclude = excludeFile(projectDir, env);
|
|
574
585
|
if (exclude === null) return false;
|
|
575
|
-
if (gitOutput(projectDir, ['check-ignore', relPath]) !== null) return false;
|
|
576
|
-
if (gitOutput(projectDir, ['ls-files', '--error-unmatch', relPath]) !== null) return false;
|
|
577
|
-
const line = excludeLine(projectDir, relPath);
|
|
586
|
+
if (gitOutput(projectDir, ['check-ignore', relPath], env) !== null) return false;
|
|
587
|
+
if (gitOutput(projectDir, ['ls-files', '--error-unmatch', relPath], env) !== null) return false;
|
|
588
|
+
const line = excludeLine(projectDir, relPath, env);
|
|
578
589
|
const text = existsSync(exclude) ? readFileSync(exclude, 'utf-8') : '';
|
|
579
590
|
if (text.split('\n').includes(line)) return false;
|
|
580
591
|
const block = `${marker}\n${line}\n`;
|