oh-my-customcode 0.65.2 → 0.66.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/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  **[한국어 문서 (Korean)](./README_ko.md)**
15
15
 
16
- 46 agents. 98 skills. 21 rules. One command.
16
+ 46 agents. 99 skills. 21 rules. One command.
17
17
 
18
18
  ```bash
19
19
  npm install -g oh-my-customcode && cd your-project && omcustom init
@@ -146,7 +146,7 @@ Each agent declares its tools, model, memory scope, and limitations in YAML fron
146
146
 
147
147
  ---
148
148
 
149
- ### Skills (98)
149
+ ### Skills (99)
150
150
 
151
151
  | Category | Count | Includes |
152
152
  |----------|-------|----------|
@@ -282,7 +282,7 @@ your-project/
282
282
  ├── CLAUDE.md # Entry point
283
283
  ├── .claude/
284
284
  │ ├── agents/ # 46 agent definitions
285
- │ ├── skills/ # 98 skill modules
285
+ │ ├── skills/ # 99 skill modules
286
286
  │ ├── rules/ # 21 governance rules (R000-R021)
287
287
  │ ├── hooks/ # 15 lifecycle hook scripts
288
288
  │ ├── schemas/ # Tool input validation schemas
package/dist/cli/index.js CHANGED
@@ -9325,7 +9325,7 @@ var init_package = __esm(() => {
9325
9325
  workspaces: [
9326
9326
  "packages/*"
9327
9327
  ],
9328
- version: "0.65.2",
9328
+ version: "0.66.0",
9329
9329
  description: "Batteries-included agent harness for Claude Code",
9330
9330
  type: "module",
9331
9331
  bin: {
package/dist/index.js CHANGED
@@ -1674,7 +1674,7 @@ var package_default = {
1674
1674
  workspaces: [
1675
1675
  "packages/*"
1676
1676
  ],
1677
- version: "0.65.2",
1677
+ version: "0.66.0",
1678
1678
  description: "Batteries-included agent harness for Claude Code",
1679
1679
  type: "module",
1680
1680
  bin: {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "0.65.2",
6
+ "version": "0.66.0",
7
7
  "description": "Batteries-included agent harness for Claude Code",
8
8
  "type": "module",
9
9
  "bin": {
@@ -21,6 +21,16 @@ if command -v codex >/dev/null 2>&1; then
21
21
  fi
22
22
  fi
23
23
 
24
+ # Check Gemini CLI availability
25
+ GEMINI_STATUS="unavailable"
26
+ if command -v gemini >/dev/null 2>&1; then
27
+ if [ -n "${GOOGLE_API_KEY:-}" ] || [ -n "${GEMINI_API_KEY:-}" ]; then
28
+ GEMINI_STATUS="available (authenticated)"
29
+ else
30
+ GEMINI_STATUS="installed (gcloud auth may be available)"
31
+ fi
32
+ fi
33
+
24
34
  # Check Agent Teams availability
25
35
  AGENT_TEAMS_STATUS="disabled"
26
36
  if [ "${CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS:-0}" = "1" ]; then
@@ -134,6 +144,7 @@ fi
134
144
  STATUS_FILE="/tmp/.claude-env-status-${PPID}"
135
145
  cat > "$STATUS_FILE" << ENVEOF
136
146
  codex=${CODEX_STATUS}
147
+ gemini=${GEMINI_STATUS}
137
148
  agent_teams=${AGENT_TEAMS_STATUS}
138
149
  git_branch=${CURRENT_BRANCH}
139
150
  claude_version=${CLAUDE_VERSION}
@@ -144,6 +155,7 @@ ENVEOF
144
155
 
145
156
  # Report to stderr (visible in conversation)
146
157
  echo " codex CLI: ${CODEX_STATUS}" >&2
158
+ echo " gemini CLI: ${GEMINI_STATUS}" >&2
147
159
  echo " Agent Teams: ${AGENT_TEAMS_STATUS}" >&2
148
160
  echo " Claude Code: v${CLAUDE_VERSION} (${COMPAT_STATUS})" >&2
149
161
  if [ "$COMPAT_STATUS" = "outdated" ]; then
@@ -64,12 +64,16 @@ Check if Agent Teams is available (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` or T
64
64
  ### Step 2: Codex-Exec Hybrid (Code Generation)
65
65
  For **new pipeline code**, **DAG scaffolding**, or **SQL model generation**:
66
66
 
67
- 1. Check `/tmp/.claude-env-status-*` for codex availability
67
+ 1. Check `/tmp/.claude-env-status-*` for codex and gemini availability
68
68
  2. If codex available AND task involves new file creation → automatically delegate to `/codex-exec` for scaffolding:
69
69
  - Display: `[Codex Hybrid] Delegating to codex-exec...`
70
70
  - codex-exec generates initial code (strength: fast generation)
71
71
  - Selected DE expert reviews and refines codex output (strength: reasoning, quality)
72
- 3. If codex unavailable display `[Codex] Unavailable proceeding with {expert} directly` and use DE expert directly
72
+ 3. If codex unavailable but gemini available delegate to `/gemini-exec` for scaffolding:
73
+ - Display: `[Gemini Hybrid] Delegating to gemini-exec...`
74
+ - gemini-exec generates initial code
75
+ - Selected DE expert reviews and refines output
76
+ 4. If neither available → display `[External CLI] Unavailable — proceeding with {expert} directly` and use DE expert directly
73
77
 
74
78
  **Suitable**: New DAG files, dbt model scaffolding, SQL template generation
75
79
  **Unsuitable**: Existing pipeline modification, architecture decisions, data quality analysis
@@ -99,12 +99,16 @@ Check if Agent Teams is available (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` or T
99
99
  ### Step 2: Codex-Exec Hybrid (Implementation Tasks)
100
100
  For **new file creation**, **boilerplate**, or **test code generation**:
101
101
 
102
- 1. Check `/tmp/.claude-env-status-*` for codex availability
102
+ 1. Check `/tmp/.claude-env-status-*` for codex and gemini availability
103
103
  2. If codex available AND task involves new file creation → automatically delegate to `/codex-exec` for scaffolding:
104
104
  - Display: `[Codex Hybrid] Delegating to codex-exec...`
105
105
  - codex-exec generates initial code (strength: fast generation)
106
106
  - Selected Claude expert reviews and refines codex output (strength: reasoning, quality)
107
- 3. If codex unavailable display `[Codex] Unavailable proceeding with {expert} directly` and use Claude expert directly
107
+ 3. If codex unavailable but gemini available delegate to `/gemini-exec` for scaffolding:
108
+ - Display: `[Gemini Hybrid] Delegating to gemini-exec...`
109
+ - gemini-exec generates initial code
110
+ - Selected Claude expert reviews and refines output
111
+ 4. If neither available → display `[External CLI] Unavailable — proceeding with {expert} directly` and use Claude expert directly
108
112
 
109
113
  **Suitable**: New file creation, boilerplate, scaffolding, test code
110
114
  **Unsuitable**: Existing code modification, architecture decisions, bug fixes
@@ -0,0 +1,215 @@
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
+ ```
@@ -0,0 +1,485 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * gemini-wrapper.cjs
5
+ *
6
+ * Node.js wrapper for Google Gemini CLI (non-interactive execution).
7
+ * Executes gemini in prompt mode with structured JSON output.
8
+ *
9
+ * Usage:
10
+ * node gemini-wrapper.cjs --prompt "your prompt" [options]
11
+ *
12
+ * Options:
13
+ * --prompt <text> Required: prompt to execute
14
+ * --json Enable JSON output from gemini (-o json)
15
+ * --stream-json Enable stream-JSON output (-o stream-json)
16
+ * --output <path> Save response to file
17
+ * --model <name> Specify model (default: gemini CLI default)
18
+ * --timeout <ms> Execution timeout in milliseconds (default: 120000, max: 600000)
19
+ * --yolo Use yolo approval mode (auto-approve all actions)
20
+ * --sandbox Run in sandbox mode
21
+ * --plan Use plan approval mode
22
+ * --working-dir <dir> Set working directory for execution
23
+ *
24
+ * Output (JSON to stdout):
25
+ * Success: { "success": true, "output": "...", "duration_ms": 1234, ... }
26
+ * Failure: { "success": false, "error": "...", "stderr": "...", ... }
27
+ *
28
+ * Exit codes:
29
+ * 0 = success
30
+ * 1 = execution error
31
+ * 2 = validation error (missing binary/auth)
32
+ */
33
+
34
+ const { spawn, execFileSync } = require('child_process');
35
+ const fs = require('fs');
36
+ const path = require('path');
37
+ const os = require('os');
38
+
39
+ // Configuration
40
+ const DEFAULT_TIMEOUT_MS = 120000; // 2 minutes
41
+ const MAX_TIMEOUT_MS = 600000; // 10 minutes
42
+ const KILL_GRACE_PERIOD_MS = 5000; // 5 seconds for graceful shutdown
43
+
44
+ /**
45
+ * Parse command line arguments
46
+ * @returns {Object} Parsed arguments
47
+ */
48
+ function parseArgs() {
49
+ const args = {
50
+ prompt: null,
51
+ json: false,
52
+ streamJson: false,
53
+ output: null,
54
+ model: null,
55
+ timeout: DEFAULT_TIMEOUT_MS,
56
+ yolo: false,
57
+ sandbox: false,
58
+ plan: false,
59
+ workingDir: null,
60
+ };
61
+
62
+ for (let i = 2; i < process.argv.length; i++) {
63
+ const arg = process.argv[i];
64
+
65
+ switch (arg) {
66
+ case '--prompt':
67
+ if (i + 1 < process.argv.length) {
68
+ args.prompt = process.argv[++i];
69
+ }
70
+ break;
71
+ case '--json':
72
+ args.json = true;
73
+ break;
74
+ case '--stream-json':
75
+ args.streamJson = true;
76
+ break;
77
+ case '--output':
78
+ if (i + 1 < process.argv.length) {
79
+ args.output = process.argv[++i];
80
+ }
81
+ break;
82
+ case '--model':
83
+ if (i + 1 < process.argv.length) {
84
+ args.model = process.argv[++i];
85
+ }
86
+ break;
87
+ case '--timeout':
88
+ if (i + 1 < process.argv.length) {
89
+ const timeoutValue = parseInt(process.argv[++i], 10);
90
+ if (!isNaN(timeoutValue)) {
91
+ args.timeout = Math.min(timeoutValue, MAX_TIMEOUT_MS);
92
+ }
93
+ }
94
+ break;
95
+ case '--yolo':
96
+ args.yolo = true;
97
+ break;
98
+ case '--sandbox':
99
+ args.sandbox = true;
100
+ break;
101
+ case '--plan':
102
+ args.plan = true;
103
+ break;
104
+ case '--working-dir':
105
+ if (i + 1 < process.argv.length) {
106
+ args.workingDir = process.argv[++i];
107
+ }
108
+ break;
109
+ }
110
+ }
111
+
112
+ return args;
113
+ }
114
+
115
+ /**
116
+ * Validate environment for gemini execution
117
+ * @returns {Object} Validation result { valid: boolean, errors: string[] }
118
+ */
119
+ function validateEnvironment() {
120
+ const errors = [];
121
+
122
+ // Check for gemini binary
123
+ try {
124
+ execFileSync('which', ['gemini'], { stdio: 'pipe' });
125
+ } catch (error) {
126
+ // Try common installation paths
127
+ const commonPaths = [
128
+ '/usr/local/bin/gemini',
129
+ path.join(os.homedir(), '.local', 'bin', 'gemini'),
130
+ path.join(os.homedir(), 'bin', 'gemini'),
131
+ path.join(os.homedir(), '.npm-global', 'bin', 'gemini'),
132
+ ];
133
+
134
+ const geminiExists = commonPaths.some(p => fs.existsSync(p));
135
+ if (!geminiExists) {
136
+ errors.push('gemini binary not found in PATH or common locations');
137
+ }
138
+ }
139
+
140
+ // Check authentication (multiple methods supported)
141
+ const hasGoogleApiKey = !!process.env.GOOGLE_API_KEY;
142
+ const hasGeminiApiKey = !!process.env.GEMINI_API_KEY;
143
+
144
+ if (!hasGoogleApiKey && !hasGeminiApiKey) {
145
+ console.error('[gemini-wrapper] Note: GOOGLE_API_KEY/GEMINI_API_KEY not set, relying on gcloud auth');
146
+ }
147
+
148
+ return {
149
+ valid: errors.length === 0,
150
+ errors,
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Build gemini command array
156
+ * @param {Object} options - Command options
157
+ * @returns {Object} Command structure { binary: string, args: string[] }
158
+ */
159
+ function buildCommand(options) {
160
+ const args = [];
161
+
162
+ // Prompt mode (non-interactive, ephemeral)
163
+ args.push('-p', options.prompt);
164
+
165
+ // Output format
166
+ if (options.streamJson) {
167
+ args.push('-o', 'stream-json');
168
+ } else if (options.json) {
169
+ args.push('-o', 'json');
170
+ }
171
+
172
+ // Model selection
173
+ if (options.model) {
174
+ args.push('-m', options.model);
175
+ }
176
+
177
+ // Approval mode
178
+ if (options.yolo) {
179
+ args.push('-y');
180
+ } else if (options.plan) {
181
+ args.push('--approval-mode', 'plan');
182
+ }
183
+
184
+ // Sandbox mode
185
+ if (options.sandbox) {
186
+ args.push('-s');
187
+ }
188
+
189
+ return {
190
+ binary: 'gemini',
191
+ args,
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Execute gemini command
197
+ * @param {string} binary - Binary to execute
198
+ * @param {string[]} args - Command arguments
199
+ * @param {number} timeout - Timeout in milliseconds
200
+ * @param {string|null} workingDir - Working directory
201
+ * @returns {Promise<Object>} Execution result
202
+ */
203
+ function executeGemini(binary, args, timeout, workingDir = null) {
204
+ return new Promise((resolve) => {
205
+ const startTime = Date.now();
206
+ let stdout = '';
207
+ let stderr = '';
208
+ let timedOut = false;
209
+
210
+ const spawnOptions = {
211
+ cwd: workingDir || process.cwd(),
212
+ env: process.env,
213
+ };
214
+
215
+ const child = spawn(binary, args, spawnOptions);
216
+
217
+ // Collect output
218
+ child.stdout.on('data', (data) => {
219
+ stdout += data.toString();
220
+ });
221
+
222
+ child.stderr.on('data', (data) => {
223
+ stderr += data.toString();
224
+ });
225
+
226
+ // Set timeout
227
+ const timeoutHandle = setTimeout(() => {
228
+ timedOut = true;
229
+ console.error('[gemini-wrapper] Timeout reached, terminating process...');
230
+
231
+ // Graceful termination attempt
232
+ child.kill('SIGTERM');
233
+
234
+ // Force kill after grace period
235
+ setTimeout(() => {
236
+ if (!child.killed) {
237
+ console.error('[gemini-wrapper] Force killing process...');
238
+ child.kill('SIGKILL');
239
+ }
240
+ }, KILL_GRACE_PERIOD_MS);
241
+ }, timeout);
242
+
243
+ // Handle process exit
244
+ child.on('close', (exitCode) => {
245
+ clearTimeout(timeoutHandle);
246
+ const durationMs = Date.now() - startTime;
247
+
248
+ resolve({
249
+ exitCode: exitCode !== null ? exitCode : 1,
250
+ stdout,
251
+ stderr,
252
+ timedOut,
253
+ durationMs,
254
+ });
255
+ });
256
+
257
+ // Handle spawn errors
258
+ child.on('error', (error) => {
259
+ clearTimeout(timeoutHandle);
260
+ const durationMs = Date.now() - startTime;
261
+
262
+ resolve({
263
+ exitCode: 1,
264
+ stdout,
265
+ stderr: stderr + '\nSpawn error: ' + error.message,
266
+ timedOut: false,
267
+ durationMs,
268
+ });
269
+ });
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Parse JSON output from gemini (-o json)
275
+ * Gemini JSON output is a single JSON object: { session_id, response, stats }
276
+ * @param {string} output - Raw output string
277
+ * @returns {Object} Parsed result { response: string|null, stats: object|null, parseError: string|null }
278
+ */
279
+ function parseJson(output) {
280
+ try {
281
+ const data = JSON.parse(output.trim());
282
+ return {
283
+ response: data.response || null,
284
+ stats: data.stats || null,
285
+ sessionId: data.session_id || null,
286
+ parseError: null,
287
+ };
288
+ } catch (error) {
289
+ return {
290
+ response: null,
291
+ stats: null,
292
+ sessionId: null,
293
+ parseError: `Failed to parse JSON: ${error.message}`,
294
+ };
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Parse stream-JSON output from gemini (-o stream-json)
300
+ * Stream format: newline-delimited JSON events
301
+ * { type: "init", ... }
302
+ * { type: "message", role: "user"|"assistant", content: "..." }
303
+ * { type: "result", stats: {...} }
304
+ * @param {string} output - Raw output string
305
+ * @returns {Object} Parsed result { events: object[], finalMessage: string|null, stats: object|null, parseErrors: string[] }
306
+ */
307
+ function parseStreamJson(output) {
308
+ const lines = output.split('\n').filter(line => line.trim().length > 0);
309
+ const events = [];
310
+ const parseErrors = [];
311
+ let finalMessage = null;
312
+ let stats = null;
313
+
314
+ for (const line of lines) {
315
+ try {
316
+ const event = JSON.parse(line);
317
+ events.push(event);
318
+
319
+ // Extract final assistant message
320
+ if (event.type === 'message' && event.role === 'assistant') {
321
+ finalMessage = event.content || event.text || finalMessage;
322
+ }
323
+
324
+ // Extract stats from result event
325
+ if (event.type === 'result') {
326
+ stats = event.stats || null;
327
+ if (event.response) {
328
+ finalMessage = event.response;
329
+ }
330
+ }
331
+
332
+ // Fallback: look for common response patterns
333
+ if (!finalMessage && event.content && event.role === 'model') {
334
+ finalMessage = event.content;
335
+ }
336
+ } catch (error) {
337
+ parseErrors.push(`Failed to parse line: ${error.message}`);
338
+ }
339
+ }
340
+
341
+ return {
342
+ events,
343
+ finalMessage,
344
+ stats,
345
+ parseErrors,
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Main execution function
351
+ */
352
+ async function main() {
353
+ const args = parseArgs();
354
+
355
+ // Validate required arguments
356
+ if (!args.prompt) {
357
+ const result = {
358
+ success: false,
359
+ error: 'Missing required argument: --prompt',
360
+ exit_code: 2,
361
+ };
362
+ console.log(JSON.stringify(result, null, 2));
363
+ process.exit(2);
364
+ }
365
+
366
+ // Validate environment
367
+ const validation = validateEnvironment();
368
+ if (!validation.valid) {
369
+ const result = {
370
+ success: false,
371
+ error: 'Environment validation failed',
372
+ validation_errors: validation.errors,
373
+ exit_code: 2,
374
+ };
375
+ console.log(JSON.stringify(result, null, 2));
376
+ process.exit(2);
377
+ }
378
+
379
+ console.error(`[gemini-wrapper] Executing gemini with timeout: ${args.timeout}ms`);
380
+ if (args.workingDir) {
381
+ console.error(`[gemini-wrapper] Working directory: ${args.workingDir}`);
382
+ }
383
+
384
+ // Build command
385
+ const command = buildCommand(args);
386
+ console.error(`[gemini-wrapper] Command: ${command.binary} ${command.args.join(' ')}`);
387
+
388
+ // Execute
389
+ const execResult = await executeGemini(
390
+ command.binary,
391
+ command.args,
392
+ args.timeout,
393
+ args.workingDir
394
+ );
395
+
396
+ // Process result
397
+ let output = null;
398
+ let eventsCount = 0;
399
+ let stats = null;
400
+
401
+ if (args.streamJson && execResult.stdout) {
402
+ const parsed = parseStreamJson(execResult.stdout);
403
+ eventsCount = parsed.events.length;
404
+ output = parsed.finalMessage;
405
+ stats = parsed.stats;
406
+
407
+ if (parsed.parseErrors.length > 0) {
408
+ console.error('[gemini-wrapper] Stream-JSON parse errors:', parsed.parseErrors.join('; '));
409
+ }
410
+ } else if (args.json && execResult.stdout) {
411
+ const parsed = parseJson(execResult.stdout);
412
+ output = parsed.response;
413
+ stats = parsed.stats;
414
+
415
+ if (parsed.parseError) {
416
+ console.error('[gemini-wrapper] JSON parse error:', parsed.parseError);
417
+ // Fallback to raw output
418
+ output = execResult.stdout.trim();
419
+ }
420
+ } else {
421
+ output = execResult.stdout.trim();
422
+ }
423
+
424
+ // Determine success
425
+ const success = execResult.exitCode === 0 && !execResult.timedOut;
426
+
427
+ // Build result object
428
+ const result = {
429
+ success,
430
+ duration_ms: execResult.durationMs,
431
+ exit_code: execResult.exitCode,
432
+ };
433
+
434
+ if (success) {
435
+ result.output = output || execResult.stdout;
436
+ result.model = args.model || '(default)';
437
+ if (args.streamJson) {
438
+ result.events_count = eventsCount;
439
+ }
440
+ if (stats) {
441
+ result.stats = stats;
442
+ }
443
+ } else {
444
+ if (execResult.timedOut) {
445
+ result.error = `Execution timed out after ${args.timeout}ms`;
446
+ } else {
447
+ result.error = 'Execution failed';
448
+ }
449
+ if (execResult.stderr) {
450
+ result.stderr = execResult.stderr.trim();
451
+ }
452
+ }
453
+
454
+ // Write output file if requested
455
+ if (args.output && output) {
456
+ try {
457
+ const outputDir = path.dirname(args.output);
458
+ if (!fs.existsSync(outputDir)) {
459
+ fs.mkdirSync(outputDir, { recursive: true });
460
+ }
461
+ fs.writeFileSync(args.output, output, 'utf-8');
462
+ console.error(`[gemini-wrapper] Output written to: ${args.output}`);
463
+ } catch (error) {
464
+ console.error(`[gemini-wrapper] Failed to write output file: ${error.message}`);
465
+ result.output_file_error = error.message;
466
+ }
467
+ }
468
+
469
+ // Output JSON result to stdout
470
+ console.log(JSON.stringify(result, null, 2));
471
+
472
+ process.exit(result.exit_code);
473
+ }
474
+
475
+ // Run
476
+ main().catch(error => {
477
+ const result = {
478
+ success: false,
479
+ error: 'Unexpected error: ' + error.message,
480
+ stack: error.stack,
481
+ exit_code: 1,
482
+ };
483
+ console.log(JSON.stringify(result, null, 2));
484
+ process.exit(1);
485
+ });
@@ -110,6 +110,14 @@ agents:
110
110
  supported_actions: [review, create, fix, refactor, test]
111
111
  base_confidence: 40
112
112
 
113
+ fe-design-expert:
114
+ keywords:
115
+ korean: [디자인, 타이포그래피, 색상, 모션, UX라이팅, 디자인시스템, 디자인리뷰]
116
+ english: [design, typography, color, motion, "ux writing", "design system", "design review", impeccable, "design audit", "ai slop"]
117
+ file_patterns: ["*.css", "*.scss", "*.less", "tailwind.config.*"]
118
+ supported_actions: [review, audit, critique, polish, normalize]
119
+ base_confidence: 40
120
+
113
121
  # SW Engineers - Backend
114
122
  be-fastapi-expert:
115
123
  keywords:
@@ -330,7 +338,7 @@ agents:
330
338
  - gather
331
339
  base_confidence: 50
332
340
  action_weight: 30
333
- routing_rule: "MUST use codex-exec --effort xhigh when codex available, fallback to WebFetch/WebSearch"
341
+ routing_rule: "MUST use codex-exec --effort xhigh when codex available, or gemini-exec when gemini available, fallback to WebFetch/WebSearch"
334
342
 
335
343
  # ---------------------------------------------------------------------------
336
344
  # Code Generation (hybrid workflow, skill-based)
@@ -359,7 +367,7 @@ agents:
359
367
  - scaffold
360
368
  base_confidence: 30
361
369
  action_weight: 25
362
- routing_rule: "Suggest codex-exec hybrid when codex available and task is new file creation"
370
+ routing_rule: "Suggest codex-exec hybrid when codex available, or gemini-exec hybrid when gemini available, for new file creation tasks"
363
371
 
364
372
  # Managers (continued)
365
373
  mgr-gitnerd:
@@ -411,3 +419,12 @@ agents:
411
419
  file_patterns: []
412
420
  supported_actions: [manage, create, coordinate]
413
421
  base_confidence: 40
422
+
423
+ professor-triage:
424
+ keywords:
425
+ korean: [트리아지, 이슈분석, 교차분석, 프로페서]
426
+ english: [triage, cross-analyze, professor, issue-analysis]
427
+ file_patterns: []
428
+ actions: [review, analyze]
429
+ base_confidence: 85
430
+ routing_rule: "MUST use professor-triage skill for cross-analyzing GitHub issues with omc_issue_analyzer comments"
@@ -1,3 +1,4 @@
1
+ <!-- omcustom:start -->
1
2
  # AI 에이전트 시스템
2
3
 
3
4
  oh-my-customcode로 구동됩니다.
@@ -118,6 +119,7 @@ oh-my-customcode로 구동됩니다.
118
119
  | `/omcustom-release-notes` | 릴리즈 노트 생성 (git 히스토리 기반) |
119
120
  | `/omcustom-feedback` | 사용자 피드백을 GitHub Issue로 등록 |
120
121
  | `/codex-exec` | Codex CLI 프롬프트 실행 |
122
+ | `/gemini-exec` | Gemini CLI 프롬프트 실행 |
121
123
  | `/optimize-analyze` | 번들 및 성능 분석 |
122
124
  | `/optimize-bundle` | 번들 크기 최적화 |
123
125
  | `/optimize-report` | 최적화 리포트 생성 |
@@ -125,11 +127,19 @@ oh-my-customcode로 구동됩니다.
125
127
  | `/scout` | 외부 URL 분석 및 프로젝트 적합성 평가 |
126
128
  | `/deep-plan` | 연구 검증 기반 계획 수립 (research → plan → verify) |
127
129
  | `/deep-verify` | 다중 관점 릴리즈 품질 검증 |
130
+ | `/professor-triage` | 이슈 교차 분석 트리아지 (omc_issue_analyzer 댓글 기반) |
131
+ | `/release-plan` | verify-done 이슈 릴리즈 유닛 계획 생성 |
132
+ | `/omcustom:workflow` | YAML 워크플로우 실행 (예: /omcustom:workflow auto-dev) |
133
+ | `/omcustom:workflow:resume` | 중단된 워크플로우 재개 |
128
134
  | `/omcustom:sauron-watch` | 전체 R017 검증 |
135
+ | `/sdd-dev` | Spec-Driven Development 워크플로우 (sdd/ 폴더 기반) |
129
136
  | `/structured-dev-cycle` | 6단계 구조적 개발 사이클 (Plan → Verify → Implement → Verify → Compound → Done) |
130
137
  | `/omcustom:loop` | 백그라운드 에이전트 자동 계속 실행 |
131
138
  | `/omcustom:lists` | 모든 사용 가능한 커맨드 표시 |
132
139
  | `/omcustom:status` | 시스템 상태 표시 |
140
+ | `/omcustom-web` | 내장 Web UI 제어 및 검사 |
141
+ | `/skills-sh-search` | skills.sh 마켓플레이스 스킬 검색 및 설치 |
142
+ | `/vercel-deploy` | Vercel 배포 자동화 |
133
143
  | `/omcustom:help` | 도움말 표시 |
134
144
 
135
145
  ## 프로젝트 구조
@@ -139,7 +149,7 @@ project/
139
149
  +-- CLAUDE.md # 진입점
140
150
  +-- .claude/
141
151
  | +-- agents/ # 서브에이전트 정의 (46 파일)
142
- | +-- skills/ # 스킬 (98 디렉토리)
152
+ | +-- skills/ # 스킬 (99 디렉토리)
143
153
  | +-- rules/ # 전역 규칙 (R000-R021)
144
154
  | +-- hooks/ # 훅 스크립트 (보안, 검증, HUD)
145
155
  | +-- contexts/ # 컨텍스트 파일 (ecomode)
@@ -278,3 +288,5 @@ claude-mem setup
278
288
  ```
279
289
 
280
290
  <!-- omcustom:git-workflow -->
291
+
292
+ <!-- omcustom:end -->
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.65.2",
2
+ "version": "0.66.0",
3
3
  "lastUpdated": "2026-03-24T00:00:00.000Z",
4
4
  "components": [
5
5
  {
@@ -18,7 +18,7 @@
18
18
  "name": "skills",
19
19
  "path": ".claude/skills",
20
20
  "description": "Reusable skill modules (includes slash commands)",
21
- "files": 98
21
+ "files": 99
22
22
  },
23
23
  {
24
24
  "name": "guides",