@cleocode/adapters 2026.9.26 → 2026.10.2
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/index.js +1277 -261
- package/dist/index.js.map +3 -3
- package/dist/providers/claude-code/install.d.ts +6 -0
- package/dist/providers/claude-code/install.d.ts.map +1 -1
- package/dist/providers/claude-code/paths.d.ts +1 -78
- package/dist/providers/claude-code/paths.d.ts.map +1 -1
- package/dist/providers/codex/install.d.ts +8 -2
- package/dist/providers/codex/install.d.ts.map +1 -1
- package/dist/providers/kimi/install.d.ts +15 -0
- package/dist/providers/kimi/install.d.ts.map +1 -1
- package/dist/providers/opencode/install.d.ts +2 -0
- package/dist/providers/opencode/install.d.ts.map +1 -1
- package/dist/providers/shared/heavy-command-hook-install.d.ts +173 -0
- package/dist/providers/shared/heavy-command-hook-install.d.ts.map +1 -0
- package/dist/providers/shared/hook-config.d.ts +94 -0
- package/dist/providers/shared/hook-config.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/providers/claude-code/install.ts +29 -1
- package/src/providers/claude-code/paths.ts +9 -131
- package/src/providers/codex/install.ts +27 -3
- package/src/providers/kimi/install.ts +15 -0
- package/src/providers/opencode/install.ts +24 -1
- package/src/providers/shared/__tests__/heavy-command-hook-install.test.ts +621 -0
- package/src/providers/shared/heavy-command-hook-install.ts +562 -0
- package/src/providers/shared/hook-config.ts +145 -0
|
@@ -68,134 +68,12 @@ export function claudeSettingsPath(): string {
|
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
* @example
|
|
81
|
-
* ```typescript
|
|
82
|
-
* isPlainObject(settings.enabledPlugins);
|
|
83
|
-
* ```
|
|
84
|
-
*/
|
|
85
|
-
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
86
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Whether a settings.json hook entry carries a CLEO-owned command.
|
|
91
|
-
*
|
|
92
|
-
* @param entry - One element of a `hooks.<Event>` array
|
|
93
|
-
* @param commandIncludes - Extra substring the CLEO command must contain
|
|
94
|
-
* @returns `true` when one of the entry's commands has the `# cleo-hook` marker
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* ```typescript
|
|
98
|
-
* isCleoHookEntry({ hooks: [{ type: "command", command: "x # cleo-hook" }] }); // true
|
|
99
|
-
* ```
|
|
100
|
-
*/
|
|
101
|
-
export function isCleoHookEntry(entry: unknown, commandIncludes?: string): boolean {
|
|
102
|
-
if (!isPlainObject(entry) || !Array.isArray(entry.hooks)) return false;
|
|
103
|
-
return entry.hooks.some(
|
|
104
|
-
(hook) =>
|
|
105
|
-
isPlainObject(hook) &&
|
|
106
|
-
typeof hook.command === 'string' &&
|
|
107
|
-
hook.command.includes(CLEO_HOOK_MARKER) &&
|
|
108
|
-
(commandIncludes === undefined || hook.command.includes(commandIncludes)),
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Whether a `hooks.<Event>` value holds a CLEO-owned entry.
|
|
114
|
-
*
|
|
115
|
-
* @param entries - The event's value (non-arrays hold none)
|
|
116
|
-
* @param commandIncludes - Extra substring the CLEO command must contain
|
|
117
|
-
* @returns `true` when a CLEO entry is present
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* ```typescript
|
|
121
|
-
* hasCleoHook(hookMap(settings).Stop);
|
|
122
|
-
* ```
|
|
123
|
-
*/
|
|
124
|
-
export function hasCleoHook(entries: unknown, commandIncludes?: string): boolean {
|
|
125
|
-
return Array.isArray(entries) && entries.some((e) => isCleoHookEntry(e, commandIncludes));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Return the settings object's `hooks` map, creating an empty one if absent.
|
|
130
|
-
*
|
|
131
|
-
* @param settings - Parsed settings.json
|
|
132
|
-
* @returns The live `hooks` object (mutations apply to `settings`)
|
|
133
|
-
* @throws Error when `hooks` exists but is not an object — the caller's write
|
|
134
|
-
* is aborted and the file left untouched
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* ```typescript
|
|
138
|
-
* const hooks = hookMap(settings);
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
export function hookMap(settings: Record<string, unknown>): Record<string, unknown> {
|
|
142
|
-
const existing = settings.hooks;
|
|
143
|
-
if (existing === undefined) {
|
|
144
|
-
const created: Record<string, unknown> = {};
|
|
145
|
-
settings.hooks = created;
|
|
146
|
-
return created;
|
|
147
|
-
}
|
|
148
|
-
if (!isPlainObject(existing)) {
|
|
149
|
-
throw new Error('settings.json "hooks" is not an object; not modifying it');
|
|
150
|
-
}
|
|
151
|
-
return existing;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Append a hook entry to `hooks[event]`, preserving existing entries.
|
|
156
|
-
*
|
|
157
|
-
* @param hooks - The settings `hooks` map
|
|
158
|
-
* @param event - Native event name (e.g. `Stop`, `PreCompact`)
|
|
159
|
-
* @param entry - Entry to append
|
|
160
|
-
* @throws Error when `hooks[event]` exists but is not an array
|
|
161
|
-
*
|
|
162
|
-
* @example
|
|
163
|
-
* ```typescript
|
|
164
|
-
* appendHookEntry(hookMap(settings), "Stop", { matcher: "", hooks: [] });
|
|
165
|
-
* ```
|
|
166
|
-
*/
|
|
167
|
-
export function appendHookEntry(
|
|
168
|
-
hooks: Record<string, unknown>,
|
|
169
|
-
event: string,
|
|
170
|
-
entry: Record<string, unknown>,
|
|
171
|
-
): void {
|
|
172
|
-
const existing = hooks[event];
|
|
173
|
-
if (existing !== undefined && !Array.isArray(existing)) {
|
|
174
|
-
throw new Error(`settings.json "hooks.${event}" is not an array; not modifying it`);
|
|
175
|
-
}
|
|
176
|
-
hooks[event] = [...(existing ?? []), entry];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Remove every CLEO-owned entry from every event array in `hooks`.
|
|
181
|
-
*
|
|
182
|
-
* @param hooks - The settings `hooks` map
|
|
183
|
-
* @returns Whether anything was removed
|
|
184
|
-
*
|
|
185
|
-
* @example
|
|
186
|
-
* ```typescript
|
|
187
|
-
* const changed = removeCleoHookEntries(hookMap(settings));
|
|
188
|
-
* ```
|
|
189
|
-
*/
|
|
190
|
-
export function removeCleoHookEntries(hooks: Record<string, unknown>): boolean {
|
|
191
|
-
let changed = false;
|
|
192
|
-
for (const [event, entries] of Object.entries(hooks)) {
|
|
193
|
-
if (!Array.isArray(entries)) continue;
|
|
194
|
-
const kept = entries.filter((e) => !isCleoHookEntry(e));
|
|
195
|
-
if (kept.length !== entries.length) {
|
|
196
|
-
hooks[event] = kept;
|
|
197
|
-
changed = true;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return changed;
|
|
201
|
-
}
|
|
71
|
+
export {
|
|
72
|
+
appendHookEntry,
|
|
73
|
+
CLEO_HOOK_MARKER,
|
|
74
|
+
hasCleoHook,
|
|
75
|
+
hookMap,
|
|
76
|
+
isCleoHookEntry,
|
|
77
|
+
isPlainObject,
|
|
78
|
+
removeCleoHookEntries,
|
|
79
|
+
} from '../shared/hook-config.js';
|
|
@@ -3,15 +3,19 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles CLEO installation into Codex CLI environments:
|
|
5
5
|
* - Ensures AGENTS.md has CLEO @-references via CAAMP
|
|
6
|
+
* - Installs the heavy-command `PreToolUse` hook in the project's
|
|
7
|
+
* `.codex/hooks.json` (T12983)
|
|
6
8
|
*
|
|
7
9
|
* @task T162
|
|
8
10
|
* @task T9019
|
|
11
|
+
* @task T12983
|
|
9
12
|
* @epic T134
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
import { join } from 'node:path';
|
|
13
16
|
import { ensureProviderInstructionFile } from '@cleocode/caamp';
|
|
14
17
|
import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
|
|
18
|
+
import { isUserHomeDir, syncJsonHeavyCommandHook } from '../shared/heavy-command-hook-install.js';
|
|
15
19
|
import { getCleoTemplatesTildePath } from '../shared/paths.js';
|
|
16
20
|
|
|
17
21
|
/**
|
|
@@ -20,11 +24,14 @@ import { getCleoTemplatesTildePath } from '../shared/paths.js';
|
|
|
20
24
|
* Manages CLEO's integration with Codex CLI by:
|
|
21
25
|
* 1. Ensuring AGENTS.md contains @-references to CLEO instruction files
|
|
22
26
|
* (delegated to CAAMP's canonical {@link ensureProviderInstructionFile}).
|
|
27
|
+
* 2. Installing the heavy-command `PreToolUse` hook (matcher `Bash`) in the
|
|
28
|
+
* project's `.codex/hooks.json`, when `options.heavyCommandHook` is set.
|
|
29
|
+
* Codex asks the user to review and trust a new project hook (`/hooks`)
|
|
30
|
+
* before it runs it.
|
|
23
31
|
*
|
|
24
32
|
* @remarks
|
|
25
33
|
* Installation is idempotent — running install multiple times on the same
|
|
26
|
-
* project produces the same result.
|
|
27
|
-
* does not have an MCP or plugin registration mechanism.
|
|
34
|
+
* project produces the same result.
|
|
28
35
|
*
|
|
29
36
|
* @task T162
|
|
30
37
|
* @epic T134
|
|
@@ -53,8 +60,25 @@ export class CodexInstallProvider implements AdapterInstallProvider {
|
|
|
53
60
|
details.instructionFile = join(projectDir, result.instructFile);
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
// T12983: route heavy shell commands through `cleo run`.
|
|
64
|
+
let success = true;
|
|
65
|
+
if (options.heavyCommandHook !== undefined && isUserHomeDir(projectDir)) {
|
|
66
|
+
details.heavyCommandHook = 'skipped';
|
|
67
|
+
} else if (options.heavyCommandHook !== undefined) {
|
|
68
|
+
try {
|
|
69
|
+
details.heavyCommandHook = await syncJsonHeavyCommandHook(
|
|
70
|
+
join(projectDir, '.codex', 'hooks.json'),
|
|
71
|
+
'codex',
|
|
72
|
+
options.heavyCommandHook,
|
|
73
|
+
);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
details.settingsErrors = [err instanceof Error ? err.message : String(err)];
|
|
76
|
+
success = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
return {
|
|
57
|
-
success
|
|
81
|
+
success,
|
|
58
82
|
installedAt,
|
|
59
83
|
instructionFileUpdated,
|
|
60
84
|
details,
|
|
@@ -27,6 +27,21 @@ import { getCleoTemplatesTildePath } from '../shared/paths.js';
|
|
|
27
27
|
* project produces the same result. Only AGENTS.md is managed; Kimi does
|
|
28
28
|
* not have an MCP or plugin registration mechanism.
|
|
29
29
|
*
|
|
30
|
+
* The heavy-command hook (T12983) is NOT installed for Kimi: Kimi reads
|
|
31
|
+
* `[[hooks]]` only from the user-global `~/.kimi/config.toml` (no project
|
|
32
|
+
* config, and CLEO installs this hook per project only), and its
|
|
33
|
+
* `PreToolUse` answer cannot rewrite the command. `cleo hook heavy-command
|
|
34
|
+
* --provider kimi` speaks Kimi's protocol (deny with the governed command as
|
|
35
|
+
* the reason) for a manual entry:
|
|
36
|
+
*
|
|
37
|
+
* ```toml
|
|
38
|
+
* [[hooks]]
|
|
39
|
+
* event = "PreToolUse"
|
|
40
|
+
* matcher = "Shell"
|
|
41
|
+
* command = "cleo hook heavy-command --provider kimi"
|
|
42
|
+
* timeout = 20
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
30
45
|
* @task T163
|
|
31
46
|
* @epic T134
|
|
32
47
|
*/
|
|
@@ -4,16 +4,22 @@
|
|
|
4
4
|
* Handles CLEO installation into OpenCode environments:
|
|
5
5
|
* - Ensures AGENTS.md has CLEO @-references via CAAMP
|
|
6
6
|
* - Installs PreCompact hook shell shims + a JS plugin wrapper (T1013)
|
|
7
|
+
* - Installs the heavy-command `tool.execute.before` plugin (T12983)
|
|
7
8
|
*
|
|
8
9
|
* @task T5240
|
|
9
10
|
* @task T1013
|
|
10
11
|
* @task T9019
|
|
12
|
+
* @task T12983
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
16
|
import { join } from 'node:path';
|
|
15
17
|
import { ensureProviderInstructionFile } from '@cleocode/caamp';
|
|
16
18
|
import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
|
|
19
|
+
import {
|
|
20
|
+
isUserHomeDir,
|
|
21
|
+
syncOpencodeHeavyCommandPlugin,
|
|
22
|
+
} from '../shared/heavy-command-hook-install.js';
|
|
17
23
|
import {
|
|
18
24
|
type InstallHookTemplatesResult,
|
|
19
25
|
installProviderHookTemplates,
|
|
@@ -69,8 +75,25 @@ export class OpenCodeInstallProvider implements AdapterInstallProvider {
|
|
|
69
75
|
details.hookTemplates = hookResult;
|
|
70
76
|
}
|
|
71
77
|
|
|
78
|
+
// Step 3 (T12983): `.opencode/plugins/cleo-heavy-command.js` routes heavy
|
|
79
|
+
// shell commands through `cleo run`.
|
|
80
|
+
let success = true;
|
|
81
|
+
if (options.heavyCommandHook !== undefined && isUserHomeDir(projectDir)) {
|
|
82
|
+
details.heavyCommandHook = 'skipped';
|
|
83
|
+
} else if (options.heavyCommandHook !== undefined) {
|
|
84
|
+
try {
|
|
85
|
+
details.heavyCommandHook = syncOpencodeHeavyCommandPlugin(
|
|
86
|
+
projectDir,
|
|
87
|
+
options.heavyCommandHook,
|
|
88
|
+
);
|
|
89
|
+
} catch (err) {
|
|
90
|
+
details.settingsErrors = [err instanceof Error ? err.message : String(err)];
|
|
91
|
+
success = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
72
95
|
return {
|
|
73
|
-
success
|
|
96
|
+
success,
|
|
74
97
|
installedAt,
|
|
75
98
|
instructionFileUpdated,
|
|
76
99
|
details,
|