@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.
Files changed (96) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1227 -101
  4. package/dist/index.js.map +4 -4
  5. package/dist/providers/claude-code/adapter.d.ts.map +1 -1
  6. package/dist/providers/claude-code/adapter.js +16 -5
  7. package/dist/providers/claude-code/adapter.js.map +1 -1
  8. package/dist/providers/claude-code/hooks.d.ts +89 -25
  9. package/dist/providers/claude-code/hooks.d.ts.map +1 -1
  10. package/dist/providers/claude-code/hooks.js +230 -28
  11. package/dist/providers/claude-code/hooks.js.map +1 -1
  12. package/dist/providers/codex/adapter.d.ts +70 -0
  13. package/dist/providers/codex/adapter.d.ts.map +1 -0
  14. package/dist/providers/codex/adapter.js +134 -0
  15. package/dist/providers/codex/adapter.js.map +1 -0
  16. package/dist/providers/codex/hooks.d.ts +85 -0
  17. package/dist/providers/codex/hooks.d.ts.map +1 -0
  18. package/dist/providers/codex/hooks.js +155 -0
  19. package/dist/providers/codex/hooks.js.map +1 -0
  20. package/dist/providers/codex/index.d.ts +22 -0
  21. package/dist/providers/codex/index.d.ts.map +1 -0
  22. package/dist/providers/codex/index.js +24 -0
  23. package/dist/providers/codex/index.js.map +1 -0
  24. package/dist/providers/codex/install.d.ts +74 -0
  25. package/dist/providers/codex/install.d.ts.map +1 -0
  26. package/dist/providers/codex/install.js +183 -0
  27. package/dist/providers/codex/install.js.map +1 -0
  28. package/dist/providers/cursor/adapter.d.ts.map +1 -1
  29. package/dist/providers/cursor/adapter.js +16 -2
  30. package/dist/providers/cursor/adapter.js.map +1 -1
  31. package/dist/providers/cursor/hooks.d.ts +102 -17
  32. package/dist/providers/cursor/hooks.d.ts.map +1 -1
  33. package/dist/providers/cursor/hooks.js +164 -18
  34. package/dist/providers/cursor/hooks.js.map +1 -1
  35. package/dist/providers/gemini-cli/adapter.d.ts +70 -0
  36. package/dist/providers/gemini-cli/adapter.d.ts.map +1 -0
  37. package/dist/providers/gemini-cli/adapter.js +145 -0
  38. package/dist/providers/gemini-cli/adapter.js.map +1 -0
  39. package/dist/providers/gemini-cli/hooks.d.ts +92 -0
  40. package/dist/providers/gemini-cli/hooks.d.ts.map +1 -0
  41. package/dist/providers/gemini-cli/hooks.js +169 -0
  42. package/dist/providers/gemini-cli/hooks.js.map +1 -0
  43. package/dist/providers/gemini-cli/index.d.ts +22 -0
  44. package/dist/providers/gemini-cli/index.d.ts.map +1 -0
  45. package/dist/providers/gemini-cli/index.js +24 -0
  46. package/dist/providers/gemini-cli/index.js.map +1 -0
  47. package/dist/providers/gemini-cli/install.d.ts +74 -0
  48. package/dist/providers/gemini-cli/install.d.ts.map +1 -0
  49. package/dist/providers/gemini-cli/install.js +183 -0
  50. package/dist/providers/gemini-cli/install.js.map +1 -0
  51. package/dist/providers/kimi/adapter.d.ts +72 -0
  52. package/dist/providers/kimi/adapter.d.ts.map +1 -0
  53. package/dist/providers/kimi/adapter.js +133 -0
  54. package/dist/providers/kimi/adapter.js.map +1 -0
  55. package/dist/providers/kimi/hooks.d.ts +64 -0
  56. package/dist/providers/kimi/hooks.d.ts.map +1 -0
  57. package/dist/providers/kimi/hooks.js +73 -0
  58. package/dist/providers/kimi/hooks.js.map +1 -0
  59. package/dist/providers/kimi/index.d.ts +22 -0
  60. package/dist/providers/kimi/index.d.ts.map +1 -0
  61. package/dist/providers/kimi/index.js +24 -0
  62. package/dist/providers/kimi/index.js.map +1 -0
  63. package/dist/providers/kimi/install.d.ts +80 -0
  64. package/dist/providers/kimi/install.d.ts.map +1 -0
  65. package/dist/providers/kimi/install.js +189 -0
  66. package/dist/providers/kimi/install.js.map +1 -0
  67. package/dist/providers/opencode/adapter.d.ts.map +1 -1
  68. package/dist/providers/opencode/adapter.js +13 -6
  69. package/dist/providers/opencode/adapter.js.map +1 -1
  70. package/dist/providers/opencode/hooks.d.ts +89 -28
  71. package/dist/providers/opencode/hooks.d.ts.map +1 -1
  72. package/dist/providers/opencode/hooks.js +145 -37
  73. package/dist/providers/opencode/hooks.js.map +1 -1
  74. package/package.json +3 -2
  75. package/src/index.ts +18 -0
  76. package/src/providers/claude-code/adapter.ts +16 -5
  77. package/src/providers/claude-code/hooks.ts +154 -30
  78. package/src/providers/codex/adapter.ts +154 -0
  79. package/src/providers/codex/hooks.ts +163 -0
  80. package/src/providers/codex/index.ts +27 -0
  81. package/src/providers/codex/install.ts +203 -0
  82. package/src/providers/codex/manifest.json +28 -0
  83. package/src/providers/cursor/adapter.ts +16 -2
  84. package/src/providers/cursor/hooks.ts +167 -18
  85. package/src/providers/gemini-cli/adapter.ts +165 -0
  86. package/src/providers/gemini-cli/hooks.ts +177 -0
  87. package/src/providers/gemini-cli/index.ts +27 -0
  88. package/src/providers/gemini-cli/install.ts +203 -0
  89. package/src/providers/gemini-cli/manifest.json +35 -0
  90. package/src/providers/kimi/adapter.ts +153 -0
  91. package/src/providers/kimi/hooks.ts +80 -0
  92. package/src/providers/kimi/index.ts +27 -0
  93. package/src/providers/kimi/install.ts +209 -0
  94. package/src/providers/kimi/manifest.json +24 -0
  95. package/src/providers/opencode/adapter.ts +13 -6
  96. package/src/providers/opencode/hooks.ts +146 -37
@@ -1,43 +1,116 @@
1
1
  /**
2
2
  * Cursor Hook Provider
3
3
  *
4
- * Cursor does not have a native hook/event system for external tools.
5
- * This provider returns null for all event mappings and marks hooks
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
- * @task T5240
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 (stub).
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 lacks a hook-based lifecycle event system. All mapping
18
- * operations return null. Registration is a no-op.
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 provider event name to a CAAMP hook event name.
85
+ * Map a Cursor native event name to a CAAMP canonical hook event name.
25
86
  *
26
- * Always returns null since Cursor does not emit hook events.
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
- * @param _providerEvent - Ignored; Cursor has no hook events
29
- * @returns null (no mapping available)
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(_providerEvent: string): string | null {
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
- * No-op for Cursor since it has no hook system.
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 - Ignored
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
- * No-op for Cursor since it has no hook system.
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
+ }