oh-my-customcode 0.158.0 → 0.159.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.
@@ -1,430 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * codex-wrapper.js
5
- *
6
- * Node.js wrapper for OpenAI Codex CLI (non-interactive execution).
7
- * Executes codex in ephemeral mode with structured JSON output.
8
- *
9
- * Usage:
10
- * node codex-wrapper.js --prompt "your prompt" [options]
11
- *
12
- * Options:
13
- * --prompt <text> Required: prompt to execute
14
- * --json Enable JSON Lines output from codex
15
- * --output <path> Save final message to file
16
- * --model <name> Specify model (default: o3)
17
- * --timeout <ms> Execution timeout in milliseconds (default: 120000, max: 600000)
18
- * --full-auto Use full-auto approval mode (default: -a never)
19
- * --working-dir <dir> Set working directory for execution
20
- *
21
- * Output (JSON to stdout):
22
- * Success: { "success": true, "output": "...", "duration_ms": 1234, ... }
23
- * Failure: { "success": false, "error": "...", "stderr": "...", ... }
24
- *
25
- * Exit codes:
26
- * 0 = success
27
- * 1 = execution error
28
- * 2 = validation error (missing binary/auth)
29
- */
30
-
31
- const { spawn, execFileSync } = require('child_process');
32
- const fs = require('fs');
33
- const path = require('path');
34
- const os = require('os');
35
-
36
- // Configuration
37
- const DEFAULT_TIMEOUT_MS = 120000; // 2 minutes
38
- const MAX_TIMEOUT_MS = 600000; // 10 minutes
39
- const KILL_GRACE_PERIOD_MS = 5000; // 5 seconds for graceful shutdown
40
-
41
- /**
42
- * Parse command line arguments
43
- * @returns {Object} Parsed arguments
44
- */
45
- function parseArgs() {
46
- const args = {
47
- prompt: null,
48
- json: false,
49
- output: null,
50
- model: null,
51
- timeout: DEFAULT_TIMEOUT_MS,
52
- fullAuto: false,
53
- workingDir: null,
54
- effort: null,
55
- };
56
-
57
- for (let i = 2; i < process.argv.length; i++) {
58
- const arg = process.argv[i];
59
-
60
- switch (arg) {
61
- case '--prompt':
62
- if (i + 1 < process.argv.length) {
63
- args.prompt = process.argv[++i];
64
- }
65
- break;
66
- case '--json':
67
- args.json = true;
68
- break;
69
- case '--output':
70
- if (i + 1 < process.argv.length) {
71
- args.output = process.argv[++i];
72
- }
73
- break;
74
- case '--model':
75
- if (i + 1 < process.argv.length) {
76
- args.model = process.argv[++i];
77
- }
78
- break;
79
- case '--timeout':
80
- if (i + 1 < process.argv.length) {
81
- const timeoutValue = parseInt(process.argv[++i], 10);
82
- if (!isNaN(timeoutValue)) {
83
- args.timeout = Math.min(timeoutValue, MAX_TIMEOUT_MS);
84
- }
85
- }
86
- break;
87
- case '--full-auto':
88
- args.fullAuto = true;
89
- break;
90
- case '--working-dir':
91
- if (i + 1 < process.argv.length) {
92
- args.workingDir = process.argv[++i];
93
- }
94
- break;
95
- case '--effort':
96
- case '--reasoning-effort':
97
- if (i + 1 < process.argv.length) {
98
- args.effort = process.argv[++i];
99
- }
100
- break;
101
- }
102
- }
103
-
104
- return args;
105
- }
106
-
107
- /**
108
- * Validate environment for codex execution
109
- * @returns {Object} Validation result { valid: boolean, errors: string[] }
110
- */
111
- function validateEnvironment() {
112
- const errors = [];
113
-
114
- // Check for codex binary
115
- try {
116
- execFileSync('which', ['codex'], { stdio: 'pipe' });
117
- } catch (error) {
118
- // Try common installation paths
119
- const commonPaths = [
120
- '/usr/local/bin/codex',
121
- path.join(os.homedir(), '.local', 'bin', 'codex'),
122
- path.join(os.homedir(), 'bin', 'codex'),
123
- ];
124
-
125
- const codexExists = commonPaths.some(p => fs.existsSync(p));
126
- if (!codexExists) {
127
- errors.push('codex binary not found in PATH or common locations');
128
- }
129
- }
130
-
131
- // Note: OPENAI_API_KEY is optional if codex has its own stored auth (via `codex auth`)
132
- if (!process.env.OPENAI_API_KEY) {
133
- console.error('[codex-wrapper] Note: OPENAI_API_KEY not set, relying on codex built-in auth');
134
- }
135
-
136
- return {
137
- valid: errors.length === 0,
138
- errors,
139
- };
140
- }
141
-
142
- /**
143
- * Build codex command array
144
- * @param {Object} options - Command options
145
- * @returns {Object} Command structure { binary: string, args: string[] }
146
- */
147
- function buildCommand(options) {
148
- const args = ['exec', '--ephemeral'];
149
-
150
- // Approval mode (default: normal, --full-auto: automatic execution)
151
- if (options.fullAuto) {
152
- args.push('--full-auto');
153
- }
154
-
155
- // JSON output
156
- if (options.json) {
157
- args.push('--json');
158
- }
159
-
160
- // Model selection
161
- if (options.model) {
162
- args.push('--model', options.model);
163
- }
164
-
165
- // Working directory
166
- if (options.workingDir) {
167
- args.push('-C', options.workingDir);
168
- }
169
-
170
- // Reasoning effort (maps to -c model_reasoning_effort="value")
171
- if (options.effort) {
172
- const validEfforts = ['minimal', 'low', 'medium', 'high', 'xhigh'];
173
- if (validEfforts.includes(options.effort)) {
174
- args.push('-c', `model_reasoning_effort="${options.effort}"`);
175
- } else {
176
- process.stderr.write(`Warning: Invalid effort level "${options.effort}". Valid: ${validEfforts.join(', ')}\n`);
177
- }
178
- }
179
-
180
- // Add prompt as last argument
181
- args.push(options.prompt);
182
-
183
- return {
184
- binary: 'codex',
185
- args,
186
- };
187
- }
188
-
189
- /**
190
- * Execute codex command
191
- * @param {string} binary - Binary to execute
192
- * @param {string[]} args - Command arguments
193
- * @param {number} timeout - Timeout in milliseconds
194
- * @param {string|null} workingDir - Working directory
195
- * @returns {Promise<Object>} Execution result
196
- */
197
- function executeCodex(binary, args, timeout, workingDir = null) {
198
- return new Promise((resolve) => {
199
- const startTime = Date.now();
200
- let stdout = '';
201
- let stderr = '';
202
- let timedOut = false;
203
-
204
- const spawnOptions = {
205
- cwd: workingDir || process.cwd(),
206
- env: process.env,
207
- };
208
-
209
- const child = spawn(binary, args, spawnOptions);
210
-
211
- // Collect output
212
- child.stdout.on('data', (data) => {
213
- stdout += data.toString();
214
- });
215
-
216
- child.stderr.on('data', (data) => {
217
- stderr += data.toString();
218
- });
219
-
220
- // Set timeout
221
- const timeoutHandle = setTimeout(() => {
222
- timedOut = true;
223
- console.error('[codex-wrapper] Timeout reached, terminating process...', { file: 'stderr' });
224
-
225
- // Graceful termination attempt
226
- child.kill('SIGTERM');
227
-
228
- // Force kill after grace period
229
- setTimeout(() => {
230
- if (!child.killed) {
231
- console.error('[codex-wrapper] Force killing process...', { file: 'stderr' });
232
- child.kill('SIGKILL');
233
- }
234
- }, KILL_GRACE_PERIOD_MS);
235
- }, timeout);
236
-
237
- // Handle process exit
238
- child.on('close', (exitCode) => {
239
- clearTimeout(timeoutHandle);
240
- const durationMs = Date.now() - startTime;
241
-
242
- resolve({
243
- exitCode: exitCode !== null ? exitCode : 1,
244
- stdout,
245
- stderr,
246
- timedOut,
247
- durationMs,
248
- });
249
- });
250
-
251
- // Handle spawn errors
252
- child.on('error', (error) => {
253
- clearTimeout(timeoutHandle);
254
- const durationMs = Date.now() - startTime;
255
-
256
- resolve({
257
- exitCode: 1,
258
- stdout,
259
- stderr: stderr + '\nSpawn error: ' + error.message,
260
- timedOut: false,
261
- durationMs,
262
- });
263
- });
264
- });
265
- }
266
-
267
- /**
268
- * Parse JSON Lines output from codex
269
- * @param {string} output - Raw output string
270
- * @returns {Object} Parsed result { events: object[], finalMessage: string|null, parseErrors: string[] }
271
- */
272
- function parseJsonLines(output) {
273
- const lines = output.split('\n').filter(line => line.trim().length > 0);
274
- const events = [];
275
- const parseErrors = [];
276
- let finalMessage = null;
277
-
278
- for (const line of lines) {
279
- try {
280
- const event = JSON.parse(line);
281
- events.push(event);
282
-
283
- // Codex CLI v0.99.0 format: item.completed events with agent_message type
284
- if (event.type === 'item.completed' && event.item) {
285
- if (event.item.type === 'agent_message' && event.item.text) {
286
- finalMessage = event.item.text;
287
- }
288
- }
289
- // Look for assistant message in various event structures (fallback for future API changes)
290
- else if (event.type === 'assistant_message' && event.content) {
291
- finalMessage = event.content;
292
- } else if (event.message && event.message.role === 'assistant') {
293
- finalMessage = event.message.content || event.message.text;
294
- } else if (event.role === 'assistant' && event.content) {
295
- finalMessage = event.content;
296
- }
297
- } catch (error) {
298
- parseErrors.push(`Failed to parse line: ${error.message}`);
299
- }
300
- }
301
-
302
- return {
303
- events,
304
- finalMessage,
305
- parseErrors,
306
- };
307
- }
308
-
309
- /**
310
- * Main execution function
311
- */
312
- async function main() {
313
- const args = parseArgs();
314
-
315
- // Validate required arguments
316
- if (!args.prompt) {
317
- const result = {
318
- success: false,
319
- error: 'Missing required argument: --prompt',
320
- exit_code: 2,
321
- };
322
- console.log(JSON.stringify(result, null, 2));
323
- process.exit(2);
324
- }
325
-
326
- // Validate environment
327
- const validation = validateEnvironment();
328
- if (!validation.valid) {
329
- const result = {
330
- success: false,
331
- error: 'Environment validation failed',
332
- validation_errors: validation.errors,
333
- exit_code: 2,
334
- };
335
- console.log(JSON.stringify(result, null, 2));
336
- process.exit(2);
337
- }
338
-
339
- console.error(`[codex-wrapper] Executing codex with timeout: ${args.timeout}ms`);
340
- if (args.workingDir) {
341
- console.error(`[codex-wrapper] Working directory: ${args.workingDir}`);
342
- }
343
-
344
- // Build command
345
- const command = buildCommand(args);
346
- console.error(`[codex-wrapper] Command: ${command.binary} ${command.args.join(' ')}`);
347
-
348
- // Execute
349
- const execResult = await executeCodex(
350
- command.binary,
351
- command.args,
352
- args.timeout,
353
- args.workingDir
354
- );
355
-
356
- // Process result
357
- let output = null;
358
- let eventsCount = 0;
359
-
360
- if (args.json && execResult.stdout) {
361
- const parsed = parseJsonLines(execResult.stdout);
362
- eventsCount = parsed.events.length;
363
- output = parsed.finalMessage;
364
-
365
- if (parsed.parseErrors.length > 0) {
366
- console.error('[codex-wrapper] JSON parse errors:', parsed.parseErrors.join('; '));
367
- }
368
- } else {
369
- output = execResult.stdout.trim();
370
- }
371
-
372
- // Determine success
373
- const success = execResult.exitCode === 0 && !execResult.timedOut;
374
-
375
- // Build result object
376
- const result = {
377
- success,
378
- duration_ms: execResult.durationMs,
379
- exit_code: execResult.exitCode,
380
- };
381
-
382
- if (success) {
383
- result.output = output || execResult.stdout;
384
- result.model = args.model || 'o3';
385
- if (args.json) {
386
- result.events_count = eventsCount;
387
- }
388
- } else {
389
- if (execResult.timedOut) {
390
- result.error = `Execution timed out after ${args.timeout}ms`;
391
- } else {
392
- result.error = 'Execution failed';
393
- }
394
- if (execResult.stderr) {
395
- result.stderr = execResult.stderr.trim();
396
- }
397
- }
398
-
399
- // Write output file if requested
400
- if (args.output && output) {
401
- try {
402
- const outputDir = path.dirname(args.output);
403
- if (!fs.existsSync(outputDir)) {
404
- fs.mkdirSync(outputDir, { recursive: true });
405
- }
406
- fs.writeFileSync(args.output, output, 'utf-8');
407
- console.error(`[codex-wrapper] Output written to: ${args.output}`);
408
- } catch (error) {
409
- console.error(`[codex-wrapper] Failed to write output file: ${error.message}`);
410
- result.output_file_error = error.message;
411
- }
412
- }
413
-
414
- // Output JSON result to stdout
415
- console.log(JSON.stringify(result, null, 2));
416
-
417
- process.exit(result.exit_code);
418
- }
419
-
420
- // Run
421
- main().catch(error => {
422
- const result = {
423
- success: false,
424
- error: 'Unexpected error: ' + error.message,
425
- stack: error.stack,
426
- exit_code: 1,
427
- };
428
- console.log(JSON.stringify(result, null, 2));
429
- process.exit(1);
430
- });
@@ -1,215 +0,0 @@
1
- ---
2
- name: gemini-exec
3
- description: Execute Gemini CLI prompts and return results
4
- scope: core
5
- argument-hint: "<prompt> [--json] [--stream-json] [--output <path>] [--model <name>] [--timeout <ms>] [--sandbox] [--plan]"
6
- user-invocable: true
7
- ---
8
-
9
- # Gemini Exec Skill
10
-
11
- Execute Google Gemini CLI prompts in non-interactive mode and return structured results. Enables Claude + Gemini hybrid workflows.
12
-
13
- ## Options
14
-
15
- ```
16
- <prompt> Required. The prompt to send to Gemini CLI
17
- --json Return structured JSON output (-o json)
18
- --stream-json Return streaming JSON events (-o stream-json)
19
- --output <path> Save response to file
20
- --model <name> Model override (default: Gemini CLI default)
21
- --timeout <ms> Execution timeout (default: 120000, max: 600000)
22
- --yolo Enable auto-approval mode (gemini -y)
23
- --sandbox Run in sandbox mode (gemini -s)
24
- --plan Use plan approval mode (--approval-mode plan)
25
- --working-dir Working directory for Gemini execution
26
- ```
27
-
28
- ## Workflow
29
-
30
- ```
31
- 1. Pre-checks
32
- - Verify `gemini` binary is installed (which gemini)
33
- - Verify authentication (GOOGLE_API_KEY, GEMINI_API_KEY, or gcloud auth)
34
- 2. Build command
35
- - Base: gemini -p "<prompt>"
36
- - Apply options: -o json, -m <model>, -y, -s, --approval-mode plan
37
- 3. Execute
38
- - Run via Bash tool with timeout (default 2min, max 10min)
39
- - Or use helper script: node .claude/skills/gemini-exec/scripts/gemini-wrapper.cjs
40
- 4. Parse output
41
- - Text mode: return raw stdout
42
- - JSON mode: parse single JSON object, extract response field
43
- - Stream-JSON mode: parse event stream, extract final assistant message
44
- 5. Report results
45
- - Format output with execution metadata
46
- ```
47
-
48
- ## Safety Defaults
49
-
50
- - `-p` flag: Non-interactive prompt mode (no session persistence)
51
- - Default mode: Normal approval (Gemini prompts for confirmation)
52
- - Override with `--yolo` only when explicitly requested
53
- - Sandbox mode (`-s`) available for isolated execution
54
-
55
- ## Output Format
56
-
57
- ### Success (Text Mode)
58
- ```
59
- [Gemini Exec] Completed
60
-
61
- Model: (default)
62
- Duration: 23.4s
63
- Working Dir: /path/to/project
64
-
65
- --- Output ---
66
- {gemini response text}
67
- ```
68
-
69
- ### Success (JSON Mode)
70
- ```
71
- [Gemini Exec] Completed (JSON)
72
-
73
- Model: (default)
74
- Duration: 23.4s
75
-
76
- --- Response ---
77
- {extracted response from JSON}
78
-
79
- --- Stats ---
80
- {token usage and other stats}
81
- ```
82
-
83
- ### Success (Stream-JSON Mode)
84
- ```
85
- [Gemini Exec] Completed (Stream-JSON)
86
-
87
- Model: (default)
88
- Duration: 23.4s
89
- Events: 12
90
-
91
- --- Final Message ---
92
- {extracted final assistant message}
93
- ```
94
-
95
- ### Failure
96
- ```
97
- [Gemini Exec] Failed
98
-
99
- Error: {error_message}
100
- Exit Code: {code}
101
- Suggested Fix: {suggestion}
102
- ```
103
-
104
- ## Helper Script
105
-
106
- For complex executions, use the wrapper script:
107
- ```bash
108
- node .claude/skills/gemini-exec/scripts/gemini-wrapper.cjs --prompt "your prompt" [options]
109
- ```
110
-
111
- The wrapper provides:
112
- - Environment validation (binary + auth checks)
113
- - Safe command construction
114
- - JSON and stream-JSON parsing with response extraction
115
- - Structured JSON output
116
- - Timeout handling with graceful termination
117
-
118
- ## Examples
119
-
120
- ```bash
121
- # Simple text prompt
122
- gemini-exec "explain what this project does"
123
-
124
- # JSON output with model override
125
- gemini-exec "list all TODO items" --json --model gemini-2.5-pro
126
-
127
- # Stream-JSON for detailed event tracking
128
- gemini-exec "analyze the codebase" --stream-json
129
-
130
- # Save output to file
131
- gemini-exec "generate a README" --output ./README.md
132
-
133
- # Sandbox mode with auto-approval
134
- gemini-exec "fix the failing tests" --yolo --sandbox
135
-
136
- # Plan mode for careful execution
137
- gemini-exec "refactor the auth module" --plan
138
-
139
- # Specify working directory
140
- gemini-exec "analyze the codebase" --working-dir /path/to/project
141
- ```
142
-
143
- ## Integration
144
-
145
- Works with the orchestrator pattern:
146
- - Main conversation delegates Gemini execution via this skill
147
- - Results are returned to the main conversation for further processing
148
- - Can be chained with other skills (e.g., dev-review after Gemini generates code)
149
-
150
- ## Availability Check
151
-
152
- gemini-exec requires the Gemini CLI binary to be installed and authenticated. The skill is only usable when:
153
-
154
- 1. `gemini` binary is found in PATH (`which gemini` succeeds)
155
- 2. Authentication is valid (GOOGLE_API_KEY, GEMINI_API_KEY set, or gcloud auth active)
156
-
157
- If either check fails, this skill cannot be used. Fall back to Claude agents for the task.
158
-
159
- > **Note**: This skill is invoked via `/gemini-exec` command, delegated by the orchestrator, or suggested by routing skills when gemini is available. The intent-detection system can trigger it for research and code generation hybrid workflows.
160
-
161
- ## Agent Teams Integration
162
-
163
- When used within Agent Teams (requires explicit invocation):
164
-
165
- 1. **As delegated task**: orchestrator explicitly delegates gemini-exec for code generation
166
- 2. **Hybrid workflow**: Claude team member analyzes → orchestrator invokes gemini-exec → Claude reviews
167
- 3. **Iteration**: Team messaging enables review-fix cycles between Claude and Gemini outputs
168
-
169
- ```
170
- Orchestrator delegates generation task
171
- → /gemini-exec invoked explicitly
172
- → Output returned to orchestrator
173
- → Reviewer validates quality
174
- → Iterate if needed
175
- ```
176
-
177
- ## Research Workflow
178
-
179
- When the orchestrator or intent-detection detects a research request:
180
-
181
- 1. **Check Gemini availability**: Verify `gemini` binary and auth
182
- 2. **If available**: Execute prompt for research
183
- 3. **If unavailable**: Fall back to Claude's WebFetch/WebSearch
184
-
185
- ### Research Command Pattern
186
- ```
187
- /gemini-exec "Research and analyze: {topic}. Provide structured findings with sources." --json
188
- ```
189
-
190
- ## Code Generation Workflow
191
-
192
- When routing skills detect a code generation task and gemini is available:
193
-
194
- 1. **Check availability**: Verify gemini CLI via `/tmp/.claude-env-status-*`
195
- 2. **If available + new file creation**: Suggest hybrid workflow
196
- 3. **Hybrid pattern**:
197
- - gemini-exec generates initial code (fast, broad generation)
198
- - Claude expert reviews for quality, patterns, best practices
199
- - Iterate if needed
200
-
201
- ### Suitable Tasks
202
- - New file scaffolding
203
- - Boilerplate generation
204
- - Test stub creation
205
- - Documentation generation
206
-
207
- ### Unsuitable Tasks
208
- - Modifying existing code (Claude expert better at understanding context)
209
- - Architecture decisions (requires reasoning, not generation)
210
- - Bug fixes (requires deep code understanding)
211
-
212
- ### Code Generation Command Pattern
213
- ```
214
- /gemini-exec "Generate {description} following {framework} best practices" --yolo
215
- ```