gemini-executor 0.1.0
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/LICENSE +21 -0
- package/README.md +274 -0
- package/agents/gemini-executor/README.md +315 -0
- package/agents/gemini-executor/__tests__/command.test.ts +362 -0
- package/agents/gemini-executor/__tests__/integration.test.ts +486 -0
- package/agents/gemini-executor/__tests__/security.test.ts +257 -0
- package/agents/gemini-executor/__tests__/validation.test.ts +373 -0
- package/agents/gemini-executor/index.ts +309 -0
- package/dist/agents/gemini-executor/index.d.ts +77 -0
- package/dist/agents/gemini-executor/index.d.ts.map +1 -0
- package/dist/agents/gemini-executor/index.js +249 -0
- package/dist/agents/gemini-executor/index.js.map +1 -0
- package/docs/architecture.md +261 -0
- package/package.json +58 -0
- package/skills/gemini/skill.md +362 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Executor SubAgent
|
|
3
|
+
*
|
|
4
|
+
* This is a specialized agent that executes Gemini CLI commands and returns results.
|
|
5
|
+
* It is invoked programmatically by Claude via the Task tool.
|
|
6
|
+
*
|
|
7
|
+
* Design Pattern: Universal SubAgent
|
|
8
|
+
* - Handles all Gemini CLI interactions
|
|
9
|
+
* - Provides error handling and retry logic
|
|
10
|
+
* - Sanitizes inputs for security
|
|
11
|
+
* - Isolates context from main conversation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { exec } from 'child_process';
|
|
15
|
+
import { promisify } from 'util';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
|
|
19
|
+
const execAsync = promisify(exec);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configuration for Gemini CLI execution
|
|
23
|
+
*/
|
|
24
|
+
interface GeminiConfig {
|
|
25
|
+
/** Path to Gemini CLI executable */
|
|
26
|
+
cliPath: string;
|
|
27
|
+
/** Default model to use */
|
|
28
|
+
defaultModel: string;
|
|
29
|
+
/** Maximum retries on failure */
|
|
30
|
+
maxRetries: number;
|
|
31
|
+
/** Timeout in milliseconds */
|
|
32
|
+
timeout: number;
|
|
33
|
+
/** Enable YOLO mode (auto-confirm) */
|
|
34
|
+
yolo: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for executing Gemini CLI
|
|
39
|
+
*/
|
|
40
|
+
interface ExecutionOptions {
|
|
41
|
+
/** Query or prompt to send to Gemini */
|
|
42
|
+
query: string;
|
|
43
|
+
/** Specific model to use (overrides default) */
|
|
44
|
+
model?: string;
|
|
45
|
+
/** Output format: 'text', 'json', or 'stream-json' */
|
|
46
|
+
outputFormat?: 'text' | 'json' | 'stream-json';
|
|
47
|
+
/** File paths to include in the prompt */
|
|
48
|
+
files?: string[];
|
|
49
|
+
/** Working directory for file references */
|
|
50
|
+
workingDir?: string;
|
|
51
|
+
/** Enable interactive mode */
|
|
52
|
+
interactive?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Result from Gemini CLI execution
|
|
57
|
+
*/
|
|
58
|
+
interface ExecutionResult {
|
|
59
|
+
/** Success status */
|
|
60
|
+
success: boolean;
|
|
61
|
+
/** Output from Gemini */
|
|
62
|
+
output?: string;
|
|
63
|
+
/** Error message if failed */
|
|
64
|
+
error?: string;
|
|
65
|
+
/** Execution metadata */
|
|
66
|
+
metadata: {
|
|
67
|
+
model: string;
|
|
68
|
+
retries: number;
|
|
69
|
+
duration: number;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Default configuration
|
|
75
|
+
*/
|
|
76
|
+
const DEFAULT_CONFIG: GeminiConfig = {
|
|
77
|
+
cliPath: '/opt/homebrew/bin/gemini',
|
|
78
|
+
defaultModel: 'gemini-2.0-flash',
|
|
79
|
+
maxRetries: 3,
|
|
80
|
+
timeout: 120000, // 2 minutes
|
|
81
|
+
yolo: true
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Security: Sensitive file patterns to detect
|
|
86
|
+
*/
|
|
87
|
+
const SENSITIVE_FILE_PATTERNS = [
|
|
88
|
+
/\.env$/,
|
|
89
|
+
/\.env\./,
|
|
90
|
+
/credentials\.json$/,
|
|
91
|
+
/secrets\.yaml$/,
|
|
92
|
+
/\.key$/,
|
|
93
|
+
/\.pem$/,
|
|
94
|
+
/id_rsa$/,
|
|
95
|
+
/\.ssh\//
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if a file path contains sensitive information
|
|
100
|
+
*/
|
|
101
|
+
function isSensitiveFile(filePath: string): boolean {
|
|
102
|
+
return SENSITIVE_FILE_PATTERNS.some(pattern => pattern.test(filePath));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Sanitize user input to prevent command injection
|
|
107
|
+
*/
|
|
108
|
+
function sanitizeInput(input: string): string {
|
|
109
|
+
// Remove or escape dangerous characters
|
|
110
|
+
return input
|
|
111
|
+
.replace(/[;&|`$()]/g, '')
|
|
112
|
+
.replace(/\\/g, '\\\\')
|
|
113
|
+
.replace(/"/g, '\\"');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Validate file paths to prevent directory traversal
|
|
118
|
+
*/
|
|
119
|
+
function validateFilePath(filePath: string, workingDir?: string): string {
|
|
120
|
+
// Resolve to absolute path
|
|
121
|
+
const absolutePath = path.resolve(workingDir || process.cwd(), filePath);
|
|
122
|
+
|
|
123
|
+
// Check for directory traversal attempts
|
|
124
|
+
if (absolutePath.includes('..')) {
|
|
125
|
+
throw new Error(`Invalid file path: directory traversal detected in ${filePath}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check if file exists
|
|
129
|
+
if (!fs.existsSync(absolutePath)) {
|
|
130
|
+
throw new Error(`File not found: ${absolutePath}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check for sensitive files
|
|
134
|
+
if (isSensitiveFile(absolutePath)) {
|
|
135
|
+
console.warn(`⚠️ Warning: Potentially sensitive file detected: ${absolutePath}`);
|
|
136
|
+
// In production, you might want to require explicit confirmation
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return absolutePath;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build Gemini CLI command
|
|
144
|
+
*/
|
|
145
|
+
function buildCommand(options: ExecutionOptions, config: GeminiConfig): string {
|
|
146
|
+
const parts: string[] = [config.cliPath];
|
|
147
|
+
|
|
148
|
+
// Add model flag if specified
|
|
149
|
+
if (options.model || config.defaultModel) {
|
|
150
|
+
parts.push('-m', options.model || config.defaultModel);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Add output format flag
|
|
154
|
+
if (options.outputFormat && options.outputFormat !== 'text') {
|
|
155
|
+
parts.push('-o', options.outputFormat);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Add YOLO flag for non-interactive mode
|
|
159
|
+
if (config.yolo && !options.interactive) {
|
|
160
|
+
parts.push('-y');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add interactive flag if requested
|
|
164
|
+
if (options.interactive) {
|
|
165
|
+
parts.push('-i');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Add file references in the prompt (not as CLI args)
|
|
169
|
+
let query = sanitizeInput(options.query);
|
|
170
|
+
if (options.files && options.files.length > 0) {
|
|
171
|
+
const fileList = options.files
|
|
172
|
+
.map(f => validateFilePath(f, options.workingDir))
|
|
173
|
+
.join(', ');
|
|
174
|
+
query = `${query}\n\nFiles to analyze: ${fileList}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add the query (properly quoted)
|
|
178
|
+
parts.push(`"${query}"`);
|
|
179
|
+
|
|
180
|
+
return parts.join(' ');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Execute Gemini CLI with retry logic
|
|
185
|
+
*/
|
|
186
|
+
async function executeWithRetry(
|
|
187
|
+
command: string,
|
|
188
|
+
config: GeminiConfig,
|
|
189
|
+
model: string,
|
|
190
|
+
attempt: number = 1
|
|
191
|
+
): Promise<ExecutionResult> {
|
|
192
|
+
const startTime = Date.now();
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
196
|
+
timeout: config.timeout,
|
|
197
|
+
maxBuffer: 10 * 1024 * 1024 // 10MB buffer for large outputs
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const duration = Date.now() - startTime;
|
|
201
|
+
|
|
202
|
+
if (stderr && !stdout) {
|
|
203
|
+
throw new Error(stderr);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
output: stdout?.trim() || '',
|
|
209
|
+
metadata: {
|
|
210
|
+
model: model,
|
|
211
|
+
retries: attempt - 1,
|
|
212
|
+
duration
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
} catch (error: any) {
|
|
216
|
+
const duration = Date.now() - startTime;
|
|
217
|
+
|
|
218
|
+
// Check if we should retry
|
|
219
|
+
if (attempt < config.maxRetries) {
|
|
220
|
+
console.log(`Retry attempt ${attempt}/${config.maxRetries} after error: ${error.message}`);
|
|
221
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
|
|
222
|
+
return executeWithRetry(command, config, model, attempt + 1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
success: false,
|
|
227
|
+
error: error.message || 'Unknown error occurred',
|
|
228
|
+
metadata: {
|
|
229
|
+
model: model,
|
|
230
|
+
retries: attempt - 1,
|
|
231
|
+
duration
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Main execution function for Gemini CLI
|
|
239
|
+
*
|
|
240
|
+
* This is the entry point called by Claude Code's Task tool.
|
|
241
|
+
*
|
|
242
|
+
* @param options Execution options
|
|
243
|
+
* @param config Optional configuration overrides
|
|
244
|
+
* @returns Execution result
|
|
245
|
+
*/
|
|
246
|
+
export async function execute(
|
|
247
|
+
options: ExecutionOptions,
|
|
248
|
+
config: Partial<GeminiConfig> = {}
|
|
249
|
+
): Promise<ExecutionResult> {
|
|
250
|
+
const finalConfig: GeminiConfig = { ...DEFAULT_CONFIG, ...config };
|
|
251
|
+
|
|
252
|
+
// Validate inputs
|
|
253
|
+
if (!options.query || options.query.trim().length === 0) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
error: 'Query cannot be empty',
|
|
257
|
+
metadata: {
|
|
258
|
+
model: finalConfig.defaultModel,
|
|
259
|
+
retries: 0,
|
|
260
|
+
duration: 0
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Build command
|
|
266
|
+
let command: string;
|
|
267
|
+
try {
|
|
268
|
+
command = buildCommand(options, finalConfig);
|
|
269
|
+
} catch (error: any) {
|
|
270
|
+
return {
|
|
271
|
+
success: false,
|
|
272
|
+
error: `Failed to build command: ${error.message}`,
|
|
273
|
+
metadata: {
|
|
274
|
+
model: finalConfig.defaultModel,
|
|
275
|
+
retries: 0,
|
|
276
|
+
duration: 0
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log(`Executing Gemini CLI: ${command.substring(0, 100)}...`);
|
|
282
|
+
|
|
283
|
+
// Determine the actual model being used
|
|
284
|
+
const actualModel = options.model || finalConfig.defaultModel;
|
|
285
|
+
|
|
286
|
+
// Execute with retry logic
|
|
287
|
+
return executeWithRetry(command, finalConfig, actualModel);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Check if Gemini CLI is installed and accessible
|
|
292
|
+
*/
|
|
293
|
+
export async function checkGeminiCLI(config: Partial<GeminiConfig> = {}): Promise<boolean> {
|
|
294
|
+
const finalConfig: GeminiConfig = { ...DEFAULT_CONFIG, ...config };
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
await execAsync(`${finalConfig.cliPath} --version`);
|
|
298
|
+
return true;
|
|
299
|
+
} catch {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Export types for external use
|
|
305
|
+
export type {
|
|
306
|
+
GeminiConfig,
|
|
307
|
+
ExecutionOptions,
|
|
308
|
+
ExecutionResult
|
|
309
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Executor SubAgent
|
|
3
|
+
*
|
|
4
|
+
* This is a specialized agent that executes Gemini CLI commands and returns results.
|
|
5
|
+
* It is invoked programmatically by Claude via the Task tool.
|
|
6
|
+
*
|
|
7
|
+
* Design Pattern: Universal SubAgent
|
|
8
|
+
* - Handles all Gemini CLI interactions
|
|
9
|
+
* - Provides error handling and retry logic
|
|
10
|
+
* - Sanitizes inputs for security
|
|
11
|
+
* - Isolates context from main conversation
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for Gemini CLI execution
|
|
15
|
+
*/
|
|
16
|
+
interface GeminiConfig {
|
|
17
|
+
/** Path to Gemini CLI executable */
|
|
18
|
+
cliPath: string;
|
|
19
|
+
/** Default model to use */
|
|
20
|
+
defaultModel: string;
|
|
21
|
+
/** Maximum retries on failure */
|
|
22
|
+
maxRetries: number;
|
|
23
|
+
/** Timeout in milliseconds */
|
|
24
|
+
timeout: number;
|
|
25
|
+
/** Enable YOLO mode (auto-confirm) */
|
|
26
|
+
yolo: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Options for executing Gemini CLI
|
|
30
|
+
*/
|
|
31
|
+
interface ExecutionOptions {
|
|
32
|
+
/** Query or prompt to send to Gemini */
|
|
33
|
+
query: string;
|
|
34
|
+
/** Specific model to use (overrides default) */
|
|
35
|
+
model?: string;
|
|
36
|
+
/** Output format: 'text', 'json', or 'stream-json' */
|
|
37
|
+
outputFormat?: 'text' | 'json' | 'stream-json';
|
|
38
|
+
/** File paths to include in the prompt */
|
|
39
|
+
files?: string[];
|
|
40
|
+
/** Working directory for file references */
|
|
41
|
+
workingDir?: string;
|
|
42
|
+
/** Enable interactive mode */
|
|
43
|
+
interactive?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Result from Gemini CLI execution
|
|
47
|
+
*/
|
|
48
|
+
interface ExecutionResult {
|
|
49
|
+
/** Success status */
|
|
50
|
+
success: boolean;
|
|
51
|
+
/** Output from Gemini */
|
|
52
|
+
output?: string;
|
|
53
|
+
/** Error message if failed */
|
|
54
|
+
error?: string;
|
|
55
|
+
/** Execution metadata */
|
|
56
|
+
metadata: {
|
|
57
|
+
model: string;
|
|
58
|
+
retries: number;
|
|
59
|
+
duration: number;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Main execution function for Gemini CLI
|
|
64
|
+
*
|
|
65
|
+
* This is the entry point called by Claude Code's Task tool.
|
|
66
|
+
*
|
|
67
|
+
* @param options Execution options
|
|
68
|
+
* @param config Optional configuration overrides
|
|
69
|
+
* @returns Execution result
|
|
70
|
+
*/
|
|
71
|
+
export declare function execute(options: ExecutionOptions, config?: Partial<GeminiConfig>): Promise<ExecutionResult>;
|
|
72
|
+
/**
|
|
73
|
+
* Check if Gemini CLI is installed and accessible
|
|
74
|
+
*/
|
|
75
|
+
export declare function checkGeminiCLI(config?: Partial<GeminiConfig>): Promise<boolean>;
|
|
76
|
+
export type { GeminiConfig, ExecutionOptions, ExecutionResult };
|
|
77
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../agents/gemini-executor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH;;GAEG;AACH,UAAU,YAAY;IACpB,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,UAAU,gBAAgB;IACxB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;IAC/C,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,yBAAyB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,QAAQ,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAsKD;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,gBAAgB,EACzB,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM,GACjC,OAAO,CAAC,eAAe,CAAC,CAuC1B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASzF;AAGD,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EAChB,CAAC"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gemini Executor SubAgent
|
|
4
|
+
*
|
|
5
|
+
* This is a specialized agent that executes Gemini CLI commands and returns results.
|
|
6
|
+
* It is invoked programmatically by Claude via the Task tool.
|
|
7
|
+
*
|
|
8
|
+
* Design Pattern: Universal SubAgent
|
|
9
|
+
* - Handles all Gemini CLI interactions
|
|
10
|
+
* - Provides error handling and retry logic
|
|
11
|
+
* - Sanitizes inputs for security
|
|
12
|
+
* - Isolates context from main conversation
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.execute = execute;
|
|
49
|
+
exports.checkGeminiCLI = checkGeminiCLI;
|
|
50
|
+
const child_process_1 = require("child_process");
|
|
51
|
+
const util_1 = require("util");
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
55
|
+
/**
|
|
56
|
+
* Default configuration
|
|
57
|
+
*/
|
|
58
|
+
const DEFAULT_CONFIG = {
|
|
59
|
+
cliPath: '/opt/homebrew/bin/gemini',
|
|
60
|
+
defaultModel: 'gemini-2.0-flash',
|
|
61
|
+
maxRetries: 3,
|
|
62
|
+
timeout: 120000, // 2 minutes
|
|
63
|
+
yolo: true
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Security: Sensitive file patterns to detect
|
|
67
|
+
*/
|
|
68
|
+
const SENSITIVE_FILE_PATTERNS = [
|
|
69
|
+
/\.env$/,
|
|
70
|
+
/\.env\./,
|
|
71
|
+
/credentials\.json$/,
|
|
72
|
+
/secrets\.yaml$/,
|
|
73
|
+
/\.key$/,
|
|
74
|
+
/\.pem$/,
|
|
75
|
+
/id_rsa$/,
|
|
76
|
+
/\.ssh\//
|
|
77
|
+
];
|
|
78
|
+
/**
|
|
79
|
+
* Check if a file path contains sensitive information
|
|
80
|
+
*/
|
|
81
|
+
function isSensitiveFile(filePath) {
|
|
82
|
+
return SENSITIVE_FILE_PATTERNS.some(pattern => pattern.test(filePath));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Sanitize user input to prevent command injection
|
|
86
|
+
*/
|
|
87
|
+
function sanitizeInput(input) {
|
|
88
|
+
// Remove or escape dangerous characters
|
|
89
|
+
return input
|
|
90
|
+
.replace(/[;&|`$()]/g, '')
|
|
91
|
+
.replace(/\\/g, '\\\\')
|
|
92
|
+
.replace(/"/g, '\\"');
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validate file paths to prevent directory traversal
|
|
96
|
+
*/
|
|
97
|
+
function validateFilePath(filePath, workingDir) {
|
|
98
|
+
// Resolve to absolute path
|
|
99
|
+
const absolutePath = path.resolve(workingDir || process.cwd(), filePath);
|
|
100
|
+
// Check for directory traversal attempts
|
|
101
|
+
if (absolutePath.includes('..')) {
|
|
102
|
+
throw new Error(`Invalid file path: directory traversal detected in ${filePath}`);
|
|
103
|
+
}
|
|
104
|
+
// Check if file exists
|
|
105
|
+
if (!fs.existsSync(absolutePath)) {
|
|
106
|
+
throw new Error(`File not found: ${absolutePath}`);
|
|
107
|
+
}
|
|
108
|
+
// Check for sensitive files
|
|
109
|
+
if (isSensitiveFile(absolutePath)) {
|
|
110
|
+
console.warn(`⚠️ Warning: Potentially sensitive file detected: ${absolutePath}`);
|
|
111
|
+
// In production, you might want to require explicit confirmation
|
|
112
|
+
}
|
|
113
|
+
return absolutePath;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build Gemini CLI command
|
|
117
|
+
*/
|
|
118
|
+
function buildCommand(options, config) {
|
|
119
|
+
const parts = [config.cliPath];
|
|
120
|
+
// Add model flag if specified
|
|
121
|
+
if (options.model || config.defaultModel) {
|
|
122
|
+
parts.push('-m', options.model || config.defaultModel);
|
|
123
|
+
}
|
|
124
|
+
// Add output format flag
|
|
125
|
+
if (options.outputFormat && options.outputFormat !== 'text') {
|
|
126
|
+
parts.push('-o', options.outputFormat);
|
|
127
|
+
}
|
|
128
|
+
// Add YOLO flag for non-interactive mode
|
|
129
|
+
if (config.yolo && !options.interactive) {
|
|
130
|
+
parts.push('-y');
|
|
131
|
+
}
|
|
132
|
+
// Add interactive flag if requested
|
|
133
|
+
if (options.interactive) {
|
|
134
|
+
parts.push('-i');
|
|
135
|
+
}
|
|
136
|
+
// Add file references in the prompt (not as CLI args)
|
|
137
|
+
let query = sanitizeInput(options.query);
|
|
138
|
+
if (options.files && options.files.length > 0) {
|
|
139
|
+
const fileList = options.files
|
|
140
|
+
.map(f => validateFilePath(f, options.workingDir))
|
|
141
|
+
.join(', ');
|
|
142
|
+
query = `${query}\n\nFiles to analyze: ${fileList}`;
|
|
143
|
+
}
|
|
144
|
+
// Add the query (properly quoted)
|
|
145
|
+
parts.push(`"${query}"`);
|
|
146
|
+
return parts.join(' ');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Execute Gemini CLI with retry logic
|
|
150
|
+
*/
|
|
151
|
+
async function executeWithRetry(command, config, model, attempt = 1) {
|
|
152
|
+
const startTime = Date.now();
|
|
153
|
+
try {
|
|
154
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
155
|
+
timeout: config.timeout,
|
|
156
|
+
maxBuffer: 10 * 1024 * 1024 // 10MB buffer for large outputs
|
|
157
|
+
});
|
|
158
|
+
const duration = Date.now() - startTime;
|
|
159
|
+
if (stderr && !stdout) {
|
|
160
|
+
throw new Error(stderr);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
success: true,
|
|
164
|
+
output: stdout?.trim() || '',
|
|
165
|
+
metadata: {
|
|
166
|
+
model: model,
|
|
167
|
+
retries: attempt - 1,
|
|
168
|
+
duration
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const duration = Date.now() - startTime;
|
|
174
|
+
// Check if we should retry
|
|
175
|
+
if (attempt < config.maxRetries) {
|
|
176
|
+
console.log(`Retry attempt ${attempt}/${config.maxRetries} after error: ${error.message}`);
|
|
177
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
|
|
178
|
+
return executeWithRetry(command, config, model, attempt + 1);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
error: error.message || 'Unknown error occurred',
|
|
183
|
+
metadata: {
|
|
184
|
+
model: model,
|
|
185
|
+
retries: attempt - 1,
|
|
186
|
+
duration
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Main execution function for Gemini CLI
|
|
193
|
+
*
|
|
194
|
+
* This is the entry point called by Claude Code's Task tool.
|
|
195
|
+
*
|
|
196
|
+
* @param options Execution options
|
|
197
|
+
* @param config Optional configuration overrides
|
|
198
|
+
* @returns Execution result
|
|
199
|
+
*/
|
|
200
|
+
async function execute(options, config = {}) {
|
|
201
|
+
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
202
|
+
// Validate inputs
|
|
203
|
+
if (!options.query || options.query.trim().length === 0) {
|
|
204
|
+
return {
|
|
205
|
+
success: false,
|
|
206
|
+
error: 'Query cannot be empty',
|
|
207
|
+
metadata: {
|
|
208
|
+
model: finalConfig.defaultModel,
|
|
209
|
+
retries: 0,
|
|
210
|
+
duration: 0
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Build command
|
|
215
|
+
let command;
|
|
216
|
+
try {
|
|
217
|
+
command = buildCommand(options, finalConfig);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error: `Failed to build command: ${error.message}`,
|
|
223
|
+
metadata: {
|
|
224
|
+
model: finalConfig.defaultModel,
|
|
225
|
+
retries: 0,
|
|
226
|
+
duration: 0
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
console.log(`Executing Gemini CLI: ${command.substring(0, 100)}...`);
|
|
231
|
+
// Determine the actual model being used
|
|
232
|
+
const actualModel = options.model || finalConfig.defaultModel;
|
|
233
|
+
// Execute with retry logic
|
|
234
|
+
return executeWithRetry(command, finalConfig, actualModel);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Check if Gemini CLI is installed and accessible
|
|
238
|
+
*/
|
|
239
|
+
async function checkGeminiCLI(config = {}) {
|
|
240
|
+
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
241
|
+
try {
|
|
242
|
+
await execAsync(`${finalConfig.cliPath} --version`);
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../agents/gemini-executor/index.ts"],"names":[],"mappings":"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"}
|