oh-my-customcode 0.17.0 → 0.18.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/dist/cli/index.js CHANGED
@@ -12805,6 +12805,11 @@ async function listFiles(dir2, options = {}) {
12805
12805
  }
12806
12806
  return files;
12807
12807
  }
12808
+ async function copyFile(src, dest) {
12809
+ const fs = await import("node:fs/promises");
12810
+ await ensureDirectory(dirname2(dest));
12811
+ await fs.copyFile(src, dest);
12812
+ }
12808
12813
  function matchesPattern(filename, pattern) {
12809
12814
  const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
12810
12815
  const regex = new RegExp(`^${regexPattern}$`);
@@ -13984,6 +13989,53 @@ async function installSingleComponent(targetDir, component, options, result) {
13984
13989
  result.warnings.push(`Failed to install ${component}: ${message}`);
13985
13990
  }
13986
13991
  }
13992
+ async function installStatusline(targetDir, options, _result) {
13993
+ const layout = getProviderLayout();
13994
+ const srcPath = resolveTemplatePath(join4(layout.rootDir, "statusline.sh"));
13995
+ const destPath = join4(targetDir, layout.rootDir, "statusline.sh");
13996
+ if (!await fileExists(srcPath)) {
13997
+ debug("install.statusline_not_found", { path: srcPath });
13998
+ return;
13999
+ }
14000
+ if (await fileExists(destPath)) {
14001
+ if (!options.force && !options.backup) {
14002
+ debug("install.statusline_skipped", { reason: "exists" });
14003
+ return;
14004
+ }
14005
+ }
14006
+ await copyFile(srcPath, destPath);
14007
+ const fs2 = await import("node:fs/promises");
14008
+ await fs2.chmod(destPath, 493);
14009
+ debug("install.statusline_installed", {});
14010
+ }
14011
+ async function installSettingsLocal(targetDir, result) {
14012
+ const layout = getProviderLayout();
14013
+ const settingsPath = join4(targetDir, layout.rootDir, "settings.local.json");
14014
+ const statusLineConfig = {
14015
+ statusLine: {
14016
+ type: "command",
14017
+ command: ".claude/statusline.sh",
14018
+ padding: 0
14019
+ }
14020
+ };
14021
+ if (await fileExists(settingsPath)) {
14022
+ try {
14023
+ const existing = await readJsonFile(settingsPath);
14024
+ if (!existing.statusLine) {
14025
+ existing.statusLine = statusLineConfig.statusLine;
14026
+ await writeJsonFile(settingsPath, existing);
14027
+ debug("install.settings_local_merged", {});
14028
+ } else {
14029
+ debug("install.settings_local_skipped", { reason: "statusLine exists" });
14030
+ }
14031
+ } catch {
14032
+ result.warnings.push("Failed to parse existing settings.local.json, skipping statusLine config");
14033
+ }
14034
+ return;
14035
+ }
14036
+ await writeJsonFile(settingsPath, statusLineConfig);
14037
+ debug("install.settings_local_created", {});
14038
+ }
13987
14039
  async function installEntryDocWithTracking(targetDir, options, result) {
13988
14040
  const language = options.language ?? DEFAULT_LANGUAGE2;
13989
14041
  const overwrite = !!(options.force || options.backup);
@@ -14010,6 +14062,8 @@ async function install(options) {
14010
14062
  await checkAndWarnExisting(options.targetDir, !!options.force, !!options.backup, result);
14011
14063
  await verifyTemplateDirectory();
14012
14064
  await installAllComponents(options.targetDir, options, result);
14065
+ await installStatusline(options.targetDir, options, result);
14066
+ await installSettingsLocal(options.targetDir, result);
14013
14067
  await installEntryDocWithTracking(options.targetDir, options, result);
14014
14068
  await updateInstallConfig(options.targetDir, options, result.installedComponents);
14015
14069
  result.success = true;
package/dist/index.js CHANGED
@@ -163,6 +163,11 @@ function resolveTemplatePath(relativePath) {
163
163
  const packageRoot = getPackageRoot();
164
164
  return join(packageRoot, "templates", relativePath);
165
165
  }
166
+ async function copyFile(src, dest) {
167
+ const fs = await import("node:fs/promises");
168
+ await ensureDirectory(dirname(dest));
169
+ await fs.copyFile(src, dest);
170
+ }
166
171
  function matchesPattern(filename, pattern) {
167
172
  const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
168
173
  const regex = new RegExp(`^${regexPattern}$`);
@@ -846,6 +851,53 @@ async function installSingleComponent(targetDir, component, options, result) {
846
851
  result.warnings.push(`Failed to install ${component}: ${message}`);
847
852
  }
848
853
  }
854
+ async function installStatusline(targetDir, options, _result) {
855
+ const layout = getProviderLayout();
856
+ const srcPath = resolveTemplatePath(join3(layout.rootDir, "statusline.sh"));
857
+ const destPath = join3(targetDir, layout.rootDir, "statusline.sh");
858
+ if (!await fileExists(srcPath)) {
859
+ debug("install.statusline_not_found", { path: srcPath });
860
+ return;
861
+ }
862
+ if (await fileExists(destPath)) {
863
+ if (!options.force && !options.backup) {
864
+ debug("install.statusline_skipped", { reason: "exists" });
865
+ return;
866
+ }
867
+ }
868
+ await copyFile(srcPath, destPath);
869
+ const fs = await import("node:fs/promises");
870
+ await fs.chmod(destPath, 493);
871
+ debug("install.statusline_installed", {});
872
+ }
873
+ async function installSettingsLocal(targetDir, result) {
874
+ const layout = getProviderLayout();
875
+ const settingsPath = join3(targetDir, layout.rootDir, "settings.local.json");
876
+ const statusLineConfig = {
877
+ statusLine: {
878
+ type: "command",
879
+ command: ".claude/statusline.sh",
880
+ padding: 0
881
+ }
882
+ };
883
+ if (await fileExists(settingsPath)) {
884
+ try {
885
+ const existing = await readJsonFile(settingsPath);
886
+ if (!existing.statusLine) {
887
+ existing.statusLine = statusLineConfig.statusLine;
888
+ await writeJsonFile(settingsPath, existing);
889
+ debug("install.settings_local_merged", {});
890
+ } else {
891
+ debug("install.settings_local_skipped", { reason: "statusLine exists" });
892
+ }
893
+ } catch {
894
+ result.warnings.push("Failed to parse existing settings.local.json, skipping statusLine config");
895
+ }
896
+ return;
897
+ }
898
+ await writeJsonFile(settingsPath, statusLineConfig);
899
+ debug("install.settings_local_created", {});
900
+ }
849
901
  async function installEntryDocWithTracking(targetDir, options, result) {
850
902
  const language = options.language ?? DEFAULT_LANGUAGE;
851
903
  const overwrite = !!(options.force || options.backup);
@@ -872,6 +924,8 @@ async function install(options) {
872
924
  await checkAndWarnExisting(options.targetDir, !!options.force, !!options.backup, result);
873
925
  await verifyTemplateDirectory();
874
926
  await installAllComponents(options.targetDir, options, result);
927
+ await installStatusline(options.targetDir, options, result);
928
+ await installSettingsLocal(options.targetDir, result);
875
929
  await installEntryDocWithTracking(options.targetDir, options, result);
876
930
  await updateInstallConfig(options.targetDir, options, result.installedComponents);
877
931
  result.success = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-customcode",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Batteries-included agent harness for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: codex-exec
3
3
  description: Execute OpenAI Codex CLI prompts and return results
4
- argument-hint: "<prompt> [--json] [--output <path>] [--model <name>] [--timeout <ms>]"
4
+ argument-hint: "<prompt> [--json] [--output <path>] [--model <name>] [--timeout <ms>] [--effort <level>]"
5
5
  ---
6
6
 
7
7
  # Codex Exec Skill
@@ -18,6 +18,10 @@ Execute OpenAI Codex CLI prompts in non-interactive mode and return structured r
18
18
  --timeout <ms> Execution timeout (default: 120000, max: 600000)
19
19
  --full-auto Enable auto-approval mode (codex -a full-auto)
20
20
  --working-dir Working directory for Codex execution
21
+ --effort <level> Set reasoning effort level (minimal, low, medium, high, xhigh)
22
+ Maps to Codex CLI's model_reasoning_effort config
23
+ Default: uses Codex CLI's configured default
24
+ Recommended: xhigh for research/analysis tasks
21
25
  ```
22
26
 
23
27
  ## Workflow
@@ -147,3 +151,27 @@ Orchestrator delegates generation task
147
151
  → Reviewer validates quality
148
152
  → Iterate if needed
149
153
  ```
154
+
155
+ ## Research Workflow
156
+
157
+ When the orchestrator detects a research/information gathering request:
158
+
159
+ 1. **Check Codex availability**: Verify `codex` binary and `OPENAI_API_KEY`
160
+ 2. **If available**: Execute with xhigh reasoning effort for thorough research
161
+ 3. **If unavailable**: Fall back to Claude's WebFetch/WebSearch
162
+
163
+ ### Research Command Pattern
164
+
165
+ ```
166
+ /codex-exec "Research and analyze: {topic}. Provide structured findings with sources." --effort xhigh --full-auto --json
167
+ ```
168
+
169
+ ### Effort Level Guide
170
+
171
+ | Level | Use Case | Speed | Depth |
172
+ |-------|----------|-------|-------|
173
+ | minimal | Quick lookups | Fastest | Surface |
174
+ | low | Simple queries | Fast | Basic |
175
+ | medium | General tasks | Balanced | Standard |
176
+ | high | Complex analysis | Slower | Deep |
177
+ | xhigh | Research & investigation | Slowest | Maximum |
@@ -51,6 +51,7 @@ function parseArgs() {
51
51
  timeout: DEFAULT_TIMEOUT_MS,
52
52
  fullAuto: false,
53
53
  workingDir: null,
54
+ effort: null,
54
55
  };
55
56
 
56
57
  for (let i = 2; i < process.argv.length; i++) {
@@ -91,6 +92,12 @@ function parseArgs() {
91
92
  args.workingDir = process.argv[++i];
92
93
  }
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;
94
101
  }
95
102
  }
96
103
 
@@ -160,6 +167,16 @@ function buildCommand(options) {
160
167
  args.push('-C', options.workingDir);
161
168
  }
162
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
+
163
180
  // Add prompt as last argument
164
181
  args.push(options.prompt);
165
182
 
@@ -228,3 +228,65 @@ intent_detection:
228
228
  max_alternatives: 3
229
229
  korean_support: true
230
230
  ```
231
+
232
+ ## Research Intent Routing
233
+
234
+ When a research/information gathering intent is detected:
235
+
236
+ ### Detection Keywords
237
+
238
+ ```yaml
239
+ # Korean
240
+ korean:
241
+ - "조사" → research
242
+ - "검색" → search
243
+ - "리서치" → research
244
+ - "탐색" → explore
245
+ - "찾아" → look up
246
+ - "알아봐" → find out
247
+ - "자료" → gather materials
248
+ - "정보 수집" → gather information
249
+
250
+ # English
251
+ english:
252
+ - "research"
253
+ - "investigate"
254
+ - "search for"
255
+ - "look up"
256
+ - "gather information"
257
+ ```
258
+
259
+ ### Routing Logic
260
+
261
+ ```
262
+ Research intent detected (confidence >= 70%)
263
+
264
+ Check Codex CLI availability
265
+ ├─ Available (codex binary + OPENAI_API_KEY)
266
+ │ → Use codex-exec skill with --effort xhigh
267
+ │ → Prompt: "Research and analyze: {user_request}"
268
+ │ → Returns: structured findings for orchestrator
269
+ └─ Unavailable
270
+ → Fall back to Claude's WebFetch/WebSearch
271
+ → Orchestrator handles directly or via general-purpose agent
272
+ ```
273
+
274
+ ### Confidence Scoring
275
+
276
+ | Factor | Weight | Example |
277
+ |--------|--------|---------|
278
+ | Research keyword match | +40 | "조사해줘", "research" |
279
+ | Action verb match | +30 | "찾아", "investigate" |
280
+ | URL/topic present | +20 | specific URL or topic mentioned |
281
+ | Context (previous research) | +10 | follow-up research request |
282
+
283
+ ### Output Format
284
+
285
+ ```
286
+ [Intent Detected]
287
+ ├── Input: "{user input}"
288
+ ├── Workflow: research-workflow
289
+ ├── Confidence: {percentage}%
290
+ ├── Method: codex-exec (xhigh) | WebFetch fallback
291
+ └── Reason: {explanation}
292
+ ```
@@ -288,6 +288,42 @@ agents:
288
288
  supported_actions: [save, recall, remember]
289
289
  base_confidence: 40
290
290
 
291
+ # ---------------------------------------------------------------------------
292
+ # Research / Information Gathering (skill-based, not agent)
293
+ # ---------------------------------------------------------------------------
294
+ research-workflow:
295
+ keywords:
296
+ korean:
297
+ - 조사
298
+ - 검색
299
+ - 리서치
300
+ - 탐색
301
+ - 찾아
302
+ - 알아봐
303
+ - 자료
304
+ - 정보 수집
305
+ - 웹에서
306
+ english:
307
+ - research
308
+ - investigate
309
+ - search for
310
+ - look up
311
+ - gather information
312
+ - web fetch
313
+ - find out
314
+ - explore topic
315
+ file_patterns: []
316
+ supported_actions:
317
+ - search
318
+ - fetch
319
+ - investigate
320
+ - research
321
+ - analyze
322
+ - gather
323
+ base_confidence: 50
324
+ action_weight: 30
325
+ routing_note: "Uses codex-exec skill with xhigh effort when available, falls back to WebFetch/WebSearch"
326
+
291
327
  # Managers (continued)
292
328
  mgr-gitnerd:
293
329
  keywords:
@@ -4,7 +4,7 @@
4
4
  # Reads JSON from stdin (Claude Code statusline API, ~300ms intervals)
5
5
  # and outputs a formatted status line, e.g.:
6
6
  #
7
- # Opus | my-project | develop | CTX:42% | $0.05
7
+ # $0.05 | my-project | develop | PR #160 | CTX:42%
8
8
  #
9
9
  # JSON input structure:
10
10
  # {
@@ -71,6 +71,7 @@ IFS=$'\t' read -r model_name project_dir ctx_pct ctx_size cost_usd <<< "$(
71
71
 
72
72
  # ---------------------------------------------------------------------------
73
73
  # 5. Model display name + color (bash 3.2 compatible case pattern matching)
74
+ # Model detection (kept for internal reference, not displayed in statusline)
74
75
  # ---------------------------------------------------------------------------
75
76
  case "$model_name" in
76
77
  *[Oo]pus*) model_display="Opus"; model_color="${COLOR_OPUS}" ;;
@@ -79,6 +80,30 @@ case "$model_name" in
79
80
  *) model_display="$model_name"; model_color="${COLOR_RESET}" ;;
80
81
  esac
81
82
 
83
+ # ---------------------------------------------------------------------------
84
+ # 5b. Cost display — format and colorize session API cost
85
+ # ---------------------------------------------------------------------------
86
+ # Ensure cost_usd is a valid number (fallback to 0)
87
+ if [[ -z "$cost_usd" ]] || ! printf '%f' "$cost_usd" >/dev/null 2>&1; then
88
+ cost_usd="0"
89
+ fi
90
+
91
+ cost_display=$(printf '$%.2f' "$cost_usd")
92
+
93
+ # Color by cost threshold (cents for integer comparison)
94
+ cost_cents=$(printf '%.0f' "$(echo "$cost_usd * 100" | bc 2>/dev/null || echo 0)")
95
+ if ! [[ "$cost_cents" =~ ^[0-9]+$ ]]; then
96
+ cost_cents=0
97
+ fi
98
+
99
+ if [[ "$cost_cents" -ge 500 ]]; then
100
+ cost_color="${COLOR_CTX_CRIT}" # Red (>= $5.00)
101
+ elif [[ "$cost_cents" -ge 100 ]]; then
102
+ cost_color="${COLOR_CTX_WARN}" # Yellow ($1.00 - $4.99)
103
+ else
104
+ cost_color="${COLOR_CTX_OK}" # Green (< $1.00)
105
+ fi
106
+
82
107
  # ---------------------------------------------------------------------------
83
108
  # 6. Project name — basename of workspace current_dir
84
109
  # ---------------------------------------------------------------------------
@@ -108,7 +133,85 @@ if [[ -f "$git_head_file" ]]; then
108
133
  fi
109
134
 
110
135
  # ---------------------------------------------------------------------------
111
- # 8. Context percentage with color
136
+ # 7b. Branch URL for OSC 8 clickable link
137
+ # ---------------------------------------------------------------------------
138
+ branch_url=""
139
+ if [[ -n "$git_branch" && -n "$project_dir" ]]; then
140
+ # Get remote URL from git config
141
+ git_config="${project_dir}/.git/config"
142
+ if [[ -f "$git_config" ]]; then
143
+ # Extract remote origin URL from git config (no subprocess)
144
+ remote_url=""
145
+ in_remote_origin=false
146
+ while IFS= read -r line; do
147
+ case "$line" in
148
+ '[remote "origin"]')
149
+ in_remote_origin=true
150
+ ;;
151
+ '['*)
152
+ in_remote_origin=false
153
+ ;;
154
+ *)
155
+ if $in_remote_origin; then
156
+ case "$line" in
157
+ *url\ =*)
158
+ remote_url="${line#*url = }"
159
+ ;;
160
+ esac
161
+ fi
162
+ ;;
163
+ esac
164
+ done < "$git_config"
165
+
166
+ # Convert remote URL to HTTPS browse URL
167
+ if [[ -n "$remote_url" ]]; then
168
+ case "$remote_url" in
169
+ git@github.com:*)
170
+ # git@github.com:owner/repo.git → https://github.com/owner/repo
171
+ repo_path="${remote_url#git@github.com:}"
172
+ repo_path="${repo_path%.git}"
173
+ branch_url="https://github.com/${repo_path}/tree/${git_branch}"
174
+ ;;
175
+ https://github.com/*)
176
+ # https://github.com/owner/repo.git → https://github.com/owner/repo
177
+ repo_path="${remote_url#https://github.com/}"
178
+ repo_path="${repo_path%.git}"
179
+ branch_url="https://github.com/${repo_path}/tree/${git_branch}"
180
+ ;;
181
+ esac
182
+ fi
183
+ fi
184
+ fi
185
+
186
+ # ---------------------------------------------------------------------------
187
+ # 8. PR number — cached by branch to avoid gh call on every refresh
188
+ # ---------------------------------------------------------------------------
189
+ pr_display=""
190
+ if [[ -n "$git_branch" ]] && command -v gh >/dev/null 2>&1; then
191
+ cache_file="/tmp/statusline-pr-${project_name}"
192
+ cached_branch=""
193
+ cached_pr=""
194
+
195
+ if [[ -f "$cache_file" ]]; then
196
+ IFS=$'\t' read -r cached_branch cached_pr < "$cache_file"
197
+ fi
198
+
199
+ if [[ "$cached_branch" == "$git_branch" ]]; then
200
+ # Cache hit — use cached PR number
201
+ pr_number="$cached_pr"
202
+ else
203
+ # Cache miss — query gh and update cache
204
+ pr_number="$(gh pr view --json number -q .number 2>/dev/null || echo "")"
205
+ printf '%s\t%s\n' "$git_branch" "$pr_number" > "$cache_file"
206
+ fi
207
+
208
+ if [[ -n "$pr_number" ]]; then
209
+ pr_display="PR #${pr_number}"
210
+ fi
211
+ fi
212
+
213
+ # ---------------------------------------------------------------------------
214
+ # 9. Context percentage with color
112
215
  # ---------------------------------------------------------------------------
113
216
  # ctx_pct may arrive as a float (e.g. 42.5); truncate to integer for comparison
114
217
  ctx_int="${ctx_pct%%.*}"
@@ -127,26 +230,34 @@ fi
127
230
 
128
231
  ctx_display="CTX:${ctx_int}%"
129
232
 
130
- # ---------------------------------------------------------------------------
131
- # 9. Cost formatting — always two decimal places
132
- # ---------------------------------------------------------------------------
133
- cost_display="$(printf '$%.2f' "$cost_usd")"
134
-
135
233
  # ---------------------------------------------------------------------------
136
234
  # 10. Assemble and output the status line
137
235
  # ---------------------------------------------------------------------------
138
- # Build segments; omit git branch segment when unavailable
236
+ # Format branch with optional OSC 8 hyperlink
237
+ if [[ -n "$branch_url" && -n "${COLOR_RESET}" ]]; then
238
+ # OSC 8 hyperlink: ESC]8;;URL BEL visible-text ESC]8;; BEL
239
+ branch_display=$'\033]8;;'"${branch_url}"$'\a'"${git_branch}"$'\033]8;;\a'
240
+ else
241
+ branch_display="$git_branch"
242
+ fi
243
+
244
+ # Build the PR segment (with separator) if present
245
+ pr_segment=""
246
+ if [[ -n "$pr_display" ]]; then
247
+ pr_segment=" | ${pr_display}"
248
+ fi
249
+
139
250
  if [[ -n "$git_branch" ]]; then
140
- printf "${model_color}%s${COLOR_RESET} | %s | %s | ${ctx_color}%s${COLOR_RESET} | %s\n" \
141
- "$model_display" \
251
+ printf "${cost_color}%s${COLOR_RESET} | %s | %s%s | ${ctx_color}%s${COLOR_RESET}\n" \
252
+ "$cost_display" \
142
253
  "$project_name" \
143
- "$git_branch" \
144
- "$ctx_display" \
145
- "$cost_display"
254
+ "$branch_display" \
255
+ "$pr_segment" \
256
+ "$ctx_display"
146
257
  else
147
- printf "${model_color}%s${COLOR_RESET} | %s | ${ctx_color}%s${COLOR_RESET} | %s\n" \
148
- "$model_display" \
258
+ printf "${cost_color}%s${COLOR_RESET} | %s%s | ${ctx_color}%s${COLOR_RESET}\n" \
259
+ "$cost_display" \
149
260
  "$project_name" \
150
- "$ctx_display" \
151
- "$cost_display"
261
+ "$pr_segment" \
262
+ "$ctx_display"
152
263
  fi