@cleocode/adapters 2026.3.72 → 2026.3.73
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.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1227 -101
- package/dist/index.js.map +4 -4
- package/dist/providers/claude-code/adapter.d.ts.map +1 -1
- package/dist/providers/claude-code/adapter.js +16 -5
- package/dist/providers/claude-code/adapter.js.map +1 -1
- package/dist/providers/claude-code/hooks.d.ts +89 -25
- package/dist/providers/claude-code/hooks.d.ts.map +1 -1
- package/dist/providers/claude-code/hooks.js +230 -28
- package/dist/providers/claude-code/hooks.js.map +1 -1
- package/dist/providers/codex/adapter.d.ts +70 -0
- package/dist/providers/codex/adapter.d.ts.map +1 -0
- package/dist/providers/codex/adapter.js +134 -0
- package/dist/providers/codex/adapter.js.map +1 -0
- package/dist/providers/codex/hooks.d.ts +85 -0
- package/dist/providers/codex/hooks.d.ts.map +1 -0
- package/dist/providers/codex/hooks.js +155 -0
- package/dist/providers/codex/hooks.js.map +1 -0
- package/dist/providers/codex/index.d.ts +22 -0
- package/dist/providers/codex/index.d.ts.map +1 -0
- package/dist/providers/codex/index.js +24 -0
- package/dist/providers/codex/index.js.map +1 -0
- package/dist/providers/codex/install.d.ts +74 -0
- package/dist/providers/codex/install.d.ts.map +1 -0
- package/dist/providers/codex/install.js +183 -0
- package/dist/providers/codex/install.js.map +1 -0
- package/dist/providers/cursor/adapter.d.ts.map +1 -1
- package/dist/providers/cursor/adapter.js +16 -2
- package/dist/providers/cursor/adapter.js.map +1 -1
- package/dist/providers/cursor/hooks.d.ts +102 -17
- package/dist/providers/cursor/hooks.d.ts.map +1 -1
- package/dist/providers/cursor/hooks.js +164 -18
- package/dist/providers/cursor/hooks.js.map +1 -1
- package/dist/providers/gemini-cli/adapter.d.ts +70 -0
- package/dist/providers/gemini-cli/adapter.d.ts.map +1 -0
- package/dist/providers/gemini-cli/adapter.js +145 -0
- package/dist/providers/gemini-cli/adapter.js.map +1 -0
- package/dist/providers/gemini-cli/hooks.d.ts +92 -0
- package/dist/providers/gemini-cli/hooks.d.ts.map +1 -0
- package/dist/providers/gemini-cli/hooks.js +169 -0
- package/dist/providers/gemini-cli/hooks.js.map +1 -0
- package/dist/providers/gemini-cli/index.d.ts +22 -0
- package/dist/providers/gemini-cli/index.d.ts.map +1 -0
- package/dist/providers/gemini-cli/index.js +24 -0
- package/dist/providers/gemini-cli/index.js.map +1 -0
- package/dist/providers/gemini-cli/install.d.ts +74 -0
- package/dist/providers/gemini-cli/install.d.ts.map +1 -0
- package/dist/providers/gemini-cli/install.js +183 -0
- package/dist/providers/gemini-cli/install.js.map +1 -0
- package/dist/providers/kimi/adapter.d.ts +72 -0
- package/dist/providers/kimi/adapter.d.ts.map +1 -0
- package/dist/providers/kimi/adapter.js +133 -0
- package/dist/providers/kimi/adapter.js.map +1 -0
- package/dist/providers/kimi/hooks.d.ts +64 -0
- package/dist/providers/kimi/hooks.d.ts.map +1 -0
- package/dist/providers/kimi/hooks.js +73 -0
- package/dist/providers/kimi/hooks.js.map +1 -0
- package/dist/providers/kimi/index.d.ts +22 -0
- package/dist/providers/kimi/index.d.ts.map +1 -0
- package/dist/providers/kimi/index.js +24 -0
- package/dist/providers/kimi/index.js.map +1 -0
- package/dist/providers/kimi/install.d.ts +80 -0
- package/dist/providers/kimi/install.d.ts.map +1 -0
- package/dist/providers/kimi/install.js +189 -0
- package/dist/providers/kimi/install.js.map +1 -0
- package/dist/providers/opencode/adapter.d.ts.map +1 -1
- package/dist/providers/opencode/adapter.js +13 -6
- package/dist/providers/opencode/adapter.js.map +1 -1
- package/dist/providers/opencode/hooks.d.ts +89 -28
- package/dist/providers/opencode/hooks.d.ts.map +1 -1
- package/dist/providers/opencode/hooks.js +145 -37
- package/dist/providers/opencode/hooks.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +18 -0
- package/src/providers/claude-code/adapter.ts +16 -5
- package/src/providers/claude-code/hooks.ts +154 -30
- package/src/providers/codex/adapter.ts +154 -0
- package/src/providers/codex/hooks.ts +163 -0
- package/src/providers/codex/index.ts +27 -0
- package/src/providers/codex/install.ts +203 -0
- package/src/providers/codex/manifest.json +28 -0
- package/src/providers/cursor/adapter.ts +16 -2
- package/src/providers/cursor/hooks.ts +167 -18
- package/src/providers/gemini-cli/adapter.ts +165 -0
- package/src/providers/gemini-cli/hooks.ts +177 -0
- package/src/providers/gemini-cli/index.ts +27 -0
- package/src/providers/gemini-cli/install.ts +203 -0
- package/src/providers/gemini-cli/manifest.json +35 -0
- package/src/providers/kimi/adapter.ts +153 -0
- package/src/providers/kimi/hooks.ts +80 -0
- package/src/providers/kimi/index.ts +27 -0
- package/src/providers/kimi/install.ts +209 -0
- package/src/providers/kimi/manifest.json +24 -0
- package/src/providers/opencode/adapter.ts +13 -6
- package/src/providers/opencode/hooks.ts +146 -37
|
@@ -1,43 +1,116 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cursor Hook Provider
|
|
3
3
|
*
|
|
4
|
-
* Cursor
|
|
5
|
-
*
|
|
6
|
-
* as unsupported. It exists to satisfy the AdapterHookProvider contract
|
|
7
|
-
* so the adapter can be used uniformly by the AdapterManager.
|
|
4
|
+
* Maps Cursor's native hook events to CAAMP canonical hook events.
|
|
5
|
+
* Cursor supports 10 of 16 canonical events through its config-based hook system.
|
|
8
6
|
*
|
|
9
|
-
*
|
|
7
|
+
* Event translation uses CAAMP normalizer APIs:
|
|
8
|
+
* - `toCanonical(nativeName, 'cursor')` for runtime event name resolution
|
|
9
|
+
* - `getSupportedEvents('cursor')` to enumerate supported canonical events
|
|
10
|
+
* - `getProviderHookProfile('cursor')` for the full provider profile
|
|
11
|
+
*
|
|
12
|
+
* Cursor uses camelCase native event names (e.g. `sessionStart`, `preToolUse`).
|
|
13
|
+
* Hooks are configured via `.cursor/hooks.json`. Supported handler types:
|
|
14
|
+
* command, prompt.
|
|
15
|
+
*
|
|
16
|
+
* Unsupported events: PermissionRequest, PreModel, PostModel, PostCompact,
|
|
17
|
+
* Notification, ConfigChange.
|
|
18
|
+
*
|
|
19
|
+
* @task T165
|
|
20
|
+
* @epic T134
|
|
10
21
|
*/
|
|
11
22
|
|
|
12
23
|
import type { AdapterHookProvider } from '@cleocode/contracts';
|
|
13
24
|
|
|
25
|
+
/** CAAMP provider identifier for Cursor. */
|
|
26
|
+
const PROVIDER_ID = 'cursor' as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fallback map from Cursor native event names to CAAMP canonical names.
|
|
30
|
+
*
|
|
31
|
+
* Derived from `getProviderHookProfile('cursor').mappings` in CAAMP 1.9.1.
|
|
32
|
+
* Covers all 10 supported events. PermissionRequest, PreModel, PostModel,
|
|
33
|
+
* PostCompact, Notification, and ConfigChange are not supported by Cursor.
|
|
34
|
+
*
|
|
35
|
+
* Cursor uses camelCase names while CAAMP canonical names are PascalCase.
|
|
36
|
+
*/
|
|
37
|
+
const CURSOR_EVENT_MAP: Record<string, string> = {
|
|
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
|
+
};
|
|
59
|
+
|
|
14
60
|
/**
|
|
15
|
-
* Hook provider for Cursor
|
|
61
|
+
* Hook provider for Cursor.
|
|
62
|
+
*
|
|
63
|
+
* Cursor registers hooks via its config system at `.cursor/hooks.json`.
|
|
64
|
+
* Supported handler types: command, prompt.
|
|
16
65
|
*
|
|
17
|
-
* Cursor
|
|
18
|
-
*
|
|
66
|
+
* CAAMP 1.9.1 reveals Cursor supports 10 of 16 canonical events. Previously
|
|
67
|
+
* this provider was a no-op stub. It now provides full event mapping and CAAMP
|
|
68
|
+
* normalizer integration.
|
|
69
|
+
*
|
|
70
|
+
* Event mapping is based on `getProviderHookProfile('cursor')` from CAAMP 1.9.1.
|
|
71
|
+
* Async accessors (`getSupportedCanonicalEvents`, `getProviderProfile`) call
|
|
72
|
+
* CAAMP directly when available.
|
|
73
|
+
*
|
|
74
|
+
* Since hooks are registered through the config system (managed by the install
|
|
75
|
+
* provider), `registerNativeHooks` and `unregisterNativeHooks` track registration
|
|
76
|
+
* state without performing filesystem operations.
|
|
77
|
+
*
|
|
78
|
+
* @task T165
|
|
79
|
+
* @epic T134
|
|
19
80
|
*/
|
|
20
81
|
export class CursorHookProvider implements AdapterHookProvider {
|
|
21
82
|
private registered = false;
|
|
22
83
|
|
|
23
84
|
/**
|
|
24
|
-
* Map a
|
|
85
|
+
* Map a Cursor native event name to a CAAMP canonical hook event name.
|
|
25
86
|
*
|
|
26
|
-
*
|
|
87
|
+
* Looks up the native event name in the map derived from
|
|
88
|
+
* `getProviderHookProfile('cursor').mappings` (CAAMP 1.9.1). Cursor uses
|
|
89
|
+
* camelCase names (e.g. "preToolUse", "sessionStart").
|
|
27
90
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
91
|
+
* Returns null for unsupported events (PermissionRequest, PreModel,
|
|
92
|
+
* PostModel, PostCompact, Notification, ConfigChange).
|
|
93
|
+
*
|
|
94
|
+
* @param providerEvent - Cursor native event name (e.g. "preToolUse", "sessionStart")
|
|
95
|
+
* @returns CAAMP canonical event name, or null if unmapped
|
|
96
|
+
* @task T165
|
|
30
97
|
*/
|
|
31
|
-
mapProviderEvent(
|
|
32
|
-
return null;
|
|
98
|
+
mapProviderEvent(providerEvent: string): string | null {
|
|
99
|
+
return CURSOR_EVENT_MAP[providerEvent] ?? null;
|
|
33
100
|
}
|
|
34
101
|
|
|
35
102
|
/**
|
|
36
103
|
* Register native hooks for a project.
|
|
37
104
|
*
|
|
38
|
-
*
|
|
105
|
+
* For Cursor, hooks are registered via the config system
|
|
106
|
+
* (`.cursor/hooks.json`), managed by the install provider.
|
|
107
|
+
* This method marks hooks as registered without performing filesystem operations.
|
|
108
|
+
*
|
|
109
|
+
* Iterating supported events is handled at install time using
|
|
110
|
+
* `getSupportedCanonicalEvents()` to enumerate all 10 supported hooks.
|
|
39
111
|
*
|
|
40
|
-
* @param _projectDir -
|
|
112
|
+
* @param _projectDir - Project directory (unused; Cursor config manages registration)
|
|
113
|
+
* @task T165
|
|
41
114
|
*/
|
|
42
115
|
async registerNativeHooks(_projectDir: string): Promise<void> {
|
|
43
116
|
this.registered = true;
|
|
@@ -46,16 +119,92 @@ export class CursorHookProvider implements AdapterHookProvider {
|
|
|
46
119
|
/**
|
|
47
120
|
* Unregister native hooks.
|
|
48
121
|
*
|
|
49
|
-
*
|
|
122
|
+
* For Cursor, this is a no-op since hooks are managed through the config
|
|
123
|
+
* system. Unregistration happens via the install provider's uninstall method.
|
|
124
|
+
*
|
|
125
|
+
* @task T165
|
|
50
126
|
*/
|
|
51
127
|
async unregisterNativeHooks(): Promise<void> {
|
|
52
128
|
this.registered = false;
|
|
53
129
|
}
|
|
54
130
|
|
|
55
131
|
/**
|
|
56
|
-
* Check whether hooks have been registered
|
|
132
|
+
* Check whether hooks have been registered via `registerNativeHooks`.
|
|
57
133
|
*/
|
|
58
134
|
isRegistered(): boolean {
|
|
59
135
|
return this.registered;
|
|
60
136
|
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get the native→canonical event mapping for introspection and debugging.
|
|
140
|
+
*
|
|
141
|
+
* Returns the map derived from `getProviderHookProfile('cursor').mappings`
|
|
142
|
+
* (CAAMP 1.9.1). Use `getSupportedCanonicalEvents()` to enumerate canonical
|
|
143
|
+
* names via live CAAMP APIs.
|
|
144
|
+
*
|
|
145
|
+
* @returns Immutable record of native event name → canonical event name
|
|
146
|
+
*/
|
|
147
|
+
getEventMap(): Readonly<Record<string, string>> {
|
|
148
|
+
return { ...CURSOR_EVENT_MAP };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Enumerate supported canonical events via CAAMP's `getSupportedEvents()`.
|
|
153
|
+
*
|
|
154
|
+
* Calls `getSupportedEvents('cursor')` from the CAAMP normalizer to get the
|
|
155
|
+
* authoritative list. Cursor supports 10 of 16 canonical events. Falls back
|
|
156
|
+
* to the values of the static event map when CAAMP is unavailable at runtime.
|
|
157
|
+
*
|
|
158
|
+
* @returns Array of CAAMP canonical event names supported by Cursor
|
|
159
|
+
* @task T165
|
|
160
|
+
*/
|
|
161
|
+
async getSupportedCanonicalEvents(): Promise<string[]> {
|
|
162
|
+
try {
|
|
163
|
+
const { getSupportedEvents } = await import('@cleocode/caamp');
|
|
164
|
+
return getSupportedEvents(PROVIDER_ID) as string[];
|
|
165
|
+
} catch {
|
|
166
|
+
return [...new Set(Object.values(CURSOR_EVENT_MAP))];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Retrieve the full provider hook profile from CAAMP.
|
|
172
|
+
*
|
|
173
|
+
* Calls `getProviderHookProfile('cursor')` from the CAAMP normalizer to
|
|
174
|
+
* get the complete profile: hook system type (`config`), config path
|
|
175
|
+
* (`.cursor/hooks.json`), handler types (command, prompt), and all event
|
|
176
|
+
* mappings. Returns null when CAAMP is unavailable at runtime.
|
|
177
|
+
*
|
|
178
|
+
* @returns Provider hook profile or null if CAAMP is unavailable
|
|
179
|
+
* @task T165
|
|
180
|
+
*/
|
|
181
|
+
async getProviderProfile(): Promise<unknown | null> {
|
|
182
|
+
try {
|
|
183
|
+
const { getProviderHookProfile } = await import('@cleocode/caamp');
|
|
184
|
+
return getProviderHookProfile(PROVIDER_ID) ?? null;
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Translate a CAAMP canonical event to its Cursor native name via CAAMP.
|
|
192
|
+
*
|
|
193
|
+
* Calls `toNative(canonical, 'cursor')` from the CAAMP normalizer.
|
|
194
|
+
* Returns null for unsupported events or when CAAMP is unavailable.
|
|
195
|
+
*
|
|
196
|
+
* @param canonical - CAAMP canonical event name (e.g. "PreToolUse")
|
|
197
|
+
* @returns Cursor native event name (e.g. "preToolUse") or null
|
|
198
|
+
* @task T165
|
|
199
|
+
*/
|
|
200
|
+
async toNativeEvent(canonical: string): Promise<string | null> {
|
|
201
|
+
try {
|
|
202
|
+
const { toNative } = await import('@cleocode/caamp');
|
|
203
|
+
return toNative(canonical as Parameters<typeof toNative>[0], PROVIDER_ID);
|
|
204
|
+
} catch {
|
|
205
|
+
// Invert the static map as fallback
|
|
206
|
+
const entry = Object.entries(CURSOR_EVENT_MAP).find(([, v]) => v === canonical);
|
|
207
|
+
return entry?.[0] ?? null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
61
210
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI Adapter
|
|
3
|
+
*
|
|
4
|
+
* Main CLEOProviderAdapter implementation for Google Gemini CLI.
|
|
5
|
+
* Provides hooks and install capabilities for CLEO integration.
|
|
6
|
+
*
|
|
7
|
+
* @task T161
|
|
8
|
+
* @epic T134
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { exec } from 'node:child_process';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { promisify } from 'node:util';
|
|
16
|
+
import type {
|
|
17
|
+
AdapterCapabilities,
|
|
18
|
+
AdapterHealthStatus,
|
|
19
|
+
CLEOProviderAdapter,
|
|
20
|
+
} from '@cleocode/contracts';
|
|
21
|
+
import { GeminiCliHookProvider } from './hooks.js';
|
|
22
|
+
import { GeminiCliInstallProvider } from './install.js';
|
|
23
|
+
|
|
24
|
+
const execAsync = promisify(exec);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* CLEO provider adapter for Google Gemini CLI.
|
|
28
|
+
*
|
|
29
|
+
* Bridges CLEO's adapter system with Gemini CLI's native capabilities:
|
|
30
|
+
* - Hooks: Maps Gemini CLI events (SessionStart, PreToolUse, etc.) to CAAMP events
|
|
31
|
+
* - Install: Registers MCP server in ~/.gemini/settings.json and ensures AGENTS.md references
|
|
32
|
+
*
|
|
33
|
+
* @task T161
|
|
34
|
+
* @epic T134
|
|
35
|
+
*/
|
|
36
|
+
export class GeminiCliAdapter implements CLEOProviderAdapter {
|
|
37
|
+
readonly id = 'gemini-cli';
|
|
38
|
+
readonly name = 'Gemini CLI';
|
|
39
|
+
readonly version = '1.0.0';
|
|
40
|
+
|
|
41
|
+
capabilities: AdapterCapabilities = {
|
|
42
|
+
supportsHooks: true,
|
|
43
|
+
supportedHookEvents: [
|
|
44
|
+
'SessionStart',
|
|
45
|
+
'SessionEnd',
|
|
46
|
+
'BeforeAgent',
|
|
47
|
+
'AfterAgent',
|
|
48
|
+
'BeforeTool',
|
|
49
|
+
'AfterTool',
|
|
50
|
+
'BeforeModel',
|
|
51
|
+
'AfterModel',
|
|
52
|
+
'PreCompress',
|
|
53
|
+
'Notification',
|
|
54
|
+
],
|
|
55
|
+
supportsSpawn: false,
|
|
56
|
+
supportsInstall: true,
|
|
57
|
+
supportsMcp: true,
|
|
58
|
+
supportsInstructionFiles: false,
|
|
59
|
+
supportsContextMonitor: false,
|
|
60
|
+
supportsStatusline: false,
|
|
61
|
+
supportsProviderPaths: false,
|
|
62
|
+
supportsTransport: false,
|
|
63
|
+
supportsTaskSync: false,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
hooks: GeminiCliHookProvider;
|
|
67
|
+
install: GeminiCliInstallProvider;
|
|
68
|
+
|
|
69
|
+
private projectDir: string | null = null;
|
|
70
|
+
private initialized = false;
|
|
71
|
+
|
|
72
|
+
constructor() {
|
|
73
|
+
this.hooks = new GeminiCliHookProvider();
|
|
74
|
+
this.install = new GeminiCliInstallProvider();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Initialize the adapter for a given project directory.
|
|
79
|
+
*
|
|
80
|
+
* @param projectDir - Root directory of the project
|
|
81
|
+
* @task T161
|
|
82
|
+
*/
|
|
83
|
+
async initialize(projectDir: string): Promise<void> {
|
|
84
|
+
this.projectDir = projectDir;
|
|
85
|
+
this.initialized = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Dispose the adapter and clean up resources.
|
|
90
|
+
*
|
|
91
|
+
* Unregisters hooks and releases any tracked state.
|
|
92
|
+
* @task T161
|
|
93
|
+
*/
|
|
94
|
+
async dispose(): Promise<void> {
|
|
95
|
+
if (this.hooks.isRegistered()) {
|
|
96
|
+
await this.hooks.unregisterNativeHooks();
|
|
97
|
+
}
|
|
98
|
+
this.initialized = false;
|
|
99
|
+
this.projectDir = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Run a health check to verify Gemini CLI is accessible.
|
|
104
|
+
*
|
|
105
|
+
* Checks:
|
|
106
|
+
* 1. Adapter has been initialized
|
|
107
|
+
* 2. Gemini CLI binary is available in PATH
|
|
108
|
+
* 3. ~/.gemini/ configuration directory exists
|
|
109
|
+
*
|
|
110
|
+
* @returns Health status with details about each check
|
|
111
|
+
* @task T161
|
|
112
|
+
*/
|
|
113
|
+
async healthCheck(): Promise<AdapterHealthStatus> {
|
|
114
|
+
const details: Record<string, unknown> = {};
|
|
115
|
+
|
|
116
|
+
if (!this.initialized) {
|
|
117
|
+
return {
|
|
118
|
+
healthy: false,
|
|
119
|
+
provider: this.id,
|
|
120
|
+
details: { error: 'Adapter not initialized' },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check Gemini CLI availability
|
|
125
|
+
let cliAvailable = false;
|
|
126
|
+
try {
|
|
127
|
+
const { stdout } = await execAsync('which gemini');
|
|
128
|
+
cliAvailable = stdout.trim().length > 0;
|
|
129
|
+
details.cliPath = stdout.trim();
|
|
130
|
+
} catch {
|
|
131
|
+
details.cliAvailable = false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check for Gemini CLI config directory
|
|
135
|
+
const geminiConfigDir = join(homedir(), '.gemini');
|
|
136
|
+
const configExists = existsSync(geminiConfigDir);
|
|
137
|
+
details.configDirExists = configExists;
|
|
138
|
+
|
|
139
|
+
// Healthy if CLI is available (primary requirement)
|
|
140
|
+
const healthy = cliAvailable;
|
|
141
|
+
details.cliAvailable = cliAvailable;
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
healthy,
|
|
145
|
+
provider: this.id,
|
|
146
|
+
details,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check whether the adapter has been initialized.
|
|
152
|
+
* @task T161
|
|
153
|
+
*/
|
|
154
|
+
isInitialized(): boolean {
|
|
155
|
+
return this.initialized;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get the project directory this adapter was initialized with.
|
|
160
|
+
* @task T161
|
|
161
|
+
*/
|
|
162
|
+
getProjectDir(): string | null {
|
|
163
|
+
return this.projectDir;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI Hook Provider
|
|
3
|
+
*
|
|
4
|
+
* Maps Gemini CLI's native hook events to CAAMP canonical hook events.
|
|
5
|
+
* Gemini CLI supports 11 canonical events through its hook system.
|
|
6
|
+
*
|
|
7
|
+
* Gemini CLI event mapping:
|
|
8
|
+
* - SessionStart -> SessionStart
|
|
9
|
+
* - SessionEnd -> SessionEnd
|
|
10
|
+
* - PromptSubmit -> BeforeAgent
|
|
11
|
+
* - ResponseComplete -> AfterAgent
|
|
12
|
+
* - PreToolUse -> BeforeTool
|
|
13
|
+
* - PostToolUse -> AfterTool
|
|
14
|
+
* - PreModel -> BeforeModel
|
|
15
|
+
* - PostModel -> AfterModel
|
|
16
|
+
* - PreCompact -> PreCompress
|
|
17
|
+
* - Notification -> Notification
|
|
18
|
+
*
|
|
19
|
+
* @task T161
|
|
20
|
+
* @epic T134
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import type { AdapterHookProvider } from '@cleocode/contracts';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Mapping from Gemini CLI native event names to CAAMP canonical event names.
|
|
29
|
+
*/
|
|
30
|
+
const GEMINI_CLI_EVENT_MAP: Record<string, string> = {
|
|
31
|
+
SessionStart: 'SessionStart',
|
|
32
|
+
SessionEnd: 'SessionEnd',
|
|
33
|
+
PromptSubmit: 'BeforeAgent',
|
|
34
|
+
ResponseComplete: 'AfterAgent',
|
|
35
|
+
PreToolUse: 'BeforeTool',
|
|
36
|
+
PostToolUse: 'AfterTool',
|
|
37
|
+
PreModel: 'BeforeModel',
|
|
38
|
+
PostModel: 'AfterModel',
|
|
39
|
+
PreCompact: 'PreCompress',
|
|
40
|
+
Notification: 'Notification',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hook provider for Gemini CLI.
|
|
45
|
+
*
|
|
46
|
+
* Gemini CLI registers hooks via its configuration system at
|
|
47
|
+
* ~/.gemini/. Hook handlers are shell scripts or commands that
|
|
48
|
+
* execute when the corresponding event fires.
|
|
49
|
+
*
|
|
50
|
+
* Since hooks are registered through the config system (managed by
|
|
51
|
+
* the install provider), registerNativeHooks and unregisterNativeHooks
|
|
52
|
+
* track registration state without performing filesystem operations.
|
|
53
|
+
*
|
|
54
|
+
* @task T161
|
|
55
|
+
* @epic T134
|
|
56
|
+
*/
|
|
57
|
+
export class GeminiCliHookProvider implements AdapterHookProvider {
|
|
58
|
+
private registered = false;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Map a Gemini CLI native event name to a CAAMP hook event name.
|
|
62
|
+
*
|
|
63
|
+
* @param providerEvent - Gemini CLI event name (e.g. "SessionStart", "PreToolUse")
|
|
64
|
+
* @returns CAAMP event name or null if unmapped
|
|
65
|
+
* @task T161
|
|
66
|
+
*/
|
|
67
|
+
mapProviderEvent(providerEvent: string): string | null {
|
|
68
|
+
return GEMINI_CLI_EVENT_MAP[providerEvent] ?? null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Register native hooks for a project.
|
|
73
|
+
*
|
|
74
|
+
* For Gemini CLI, hooks are registered via the config system
|
|
75
|
+
* (~/.gemini/), which is handled by the install provider.
|
|
76
|
+
* This method marks hooks as registered without performing
|
|
77
|
+
* filesystem operations.
|
|
78
|
+
*
|
|
79
|
+
* @param _projectDir - Project directory (unused; hooks are global)
|
|
80
|
+
* @task T161
|
|
81
|
+
*/
|
|
82
|
+
async registerNativeHooks(_projectDir: string): Promise<void> {
|
|
83
|
+
this.registered = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Unregister native hooks.
|
|
88
|
+
*
|
|
89
|
+
* For Gemini CLI, this is a no-op since hooks are managed through
|
|
90
|
+
* the config system. Unregistration happens via the install
|
|
91
|
+
* provider's uninstall method.
|
|
92
|
+
* @task T161
|
|
93
|
+
*/
|
|
94
|
+
async unregisterNativeHooks(): Promise<void> {
|
|
95
|
+
this.registered = false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check whether hooks have been registered via registerNativeHooks.
|
|
100
|
+
* @task T161
|
|
101
|
+
*/
|
|
102
|
+
isRegistered(): boolean {
|
|
103
|
+
return this.registered;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the full event mapping for introspection/debugging.
|
|
108
|
+
* @task T161
|
|
109
|
+
*/
|
|
110
|
+
getEventMap(): Readonly<Record<string, string>> {
|
|
111
|
+
return { ...GEMINI_CLI_EVENT_MAP };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Extract a plain-text transcript from Gemini CLI session data.
|
|
116
|
+
*
|
|
117
|
+
* Reads the most recent session file under ~/.gemini/ and extracts
|
|
118
|
+
* turn text into a flat string for brain observation extraction.
|
|
119
|
+
*
|
|
120
|
+
* Returns null when no session data is found or on any read error.
|
|
121
|
+
*
|
|
122
|
+
* @param _sessionId - CLEO session ID (unused; reads the most recent file)
|
|
123
|
+
* @param _projectDir - Project directory (unused; Gemini CLI uses global paths)
|
|
124
|
+
* @task T161 @epic T134
|
|
125
|
+
*/
|
|
126
|
+
async getTranscript(_sessionId: string, _projectDir: string): Promise<string | null> {
|
|
127
|
+
try {
|
|
128
|
+
const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? '/root';
|
|
129
|
+
const geminiDir = join(homeDir, '.gemini');
|
|
130
|
+
|
|
131
|
+
let allFiles: string[] = [];
|
|
132
|
+
try {
|
|
133
|
+
const entries = await readdir(geminiDir, { withFileTypes: true });
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
if (!entry.isFile()) continue;
|
|
136
|
+
const name = entry.name;
|
|
137
|
+
if (name.endsWith('.json') || name.endsWith('.jsonl')) {
|
|
138
|
+
allFiles.push(join(geminiDir, name));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (allFiles.length === 0) return null;
|
|
146
|
+
|
|
147
|
+
// Sort descending by filename (timestamps in filenames sort naturally)
|
|
148
|
+
allFiles = allFiles.sort((a, b) => b.localeCompare(a));
|
|
149
|
+
const mostRecent = allFiles[0];
|
|
150
|
+
if (!mostRecent) return null;
|
|
151
|
+
|
|
152
|
+
const raw = await readFile(mostRecent, 'utf-8');
|
|
153
|
+
const turns: string[] = [];
|
|
154
|
+
|
|
155
|
+
// Support both JSONL (one JSON per line) and JSON array formats
|
|
156
|
+
const lines = raw.split('\n').filter((l) => l.trim());
|
|
157
|
+
for (const line of lines) {
|
|
158
|
+
try {
|
|
159
|
+
const entry = JSON.parse(line) as Record<string, unknown>;
|
|
160
|
+
const role = entry.role as string | undefined;
|
|
161
|
+
const content = entry.content;
|
|
162
|
+
if (role === 'assistant' && typeof content === 'string') {
|
|
163
|
+
turns.push(`assistant: ${content}`);
|
|
164
|
+
} else if (role === 'user' && typeof content === 'string') {
|
|
165
|
+
turns.push(`user: ${content}`);
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
// Skip malformed lines
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return turns.length > 0 ? turns.join('\n') : null;
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI provider adapter.
|
|
3
|
+
*
|
|
4
|
+
* CLEO provider adapter for Google Gemini CLI.
|
|
5
|
+
* Default export is the adapter class for dynamic loading by AdapterManager.
|
|
6
|
+
*
|
|
7
|
+
* @task T161
|
|
8
|
+
* @epic T134
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { GeminiCliAdapter } from './adapter.js';
|
|
12
|
+
|
|
13
|
+
export { GeminiCliAdapter } from './adapter.js';
|
|
14
|
+
export { GeminiCliHookProvider } from './hooks.js';
|
|
15
|
+
export { GeminiCliInstallProvider } from './install.js';
|
|
16
|
+
|
|
17
|
+
export default GeminiCliAdapter;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Factory function for creating adapter instances.
|
|
21
|
+
* Used by AdapterManager's dynamic import fallback.
|
|
22
|
+
*
|
|
23
|
+
* @task T161
|
|
24
|
+
*/
|
|
25
|
+
export function createAdapter(): GeminiCliAdapter {
|
|
26
|
+
return new GeminiCliAdapter();
|
|
27
|
+
}
|