oh-my-customcode 0.9.1 → 0.9.3

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
@@ -16,7 +16,7 @@ Like oh-my-zsh transformed shell customization, oh-my-customcode makes personali
16
16
 
17
17
  | Feature | Description |
18
18
  |---------|-------------|
19
- | **Batteries Included** | 42 agents, 51 skills, 22 guides, 18 rules, 1 hook, 4 contexts - ready to use out of the box |
19
+ | **Batteries Included** | 42 agents, 52 skills, 22 guides, 18 rules, 1 hook, 1 context - ready to use out of the box |
20
20
  | **Sub-Agent Model** | Supports hierarchical agent orchestration with specialized roles |
21
21
  | **Dead Simple Customization** | Create a folder + markdown file = new agent or skill |
22
22
  | **Mix and Match** | Use built-in components, create your own, or combine both |
@@ -47,6 +47,11 @@ oh-my-customcode can operate in both Claude-native and Codex-native modes. The C
47
47
  4. Project markers (`AGENTS.md`/`.codex` vs `CLAUDE.md`/`.claude`)
48
48
  5. Default: `claude`
49
49
 
50
+ **Claude Mode** generates `.claude/` directory with `CLAUDE.md` entry point.
51
+ **Codex Mode** generates `.codex/` directory with `AGENTS.md` entry point.
52
+
53
+ Both modes install the same agents, skills, guides, and rules — only the directory layout and entry file differ.
54
+
50
55
  ## Customization First
51
56
 
52
57
  This is what oh-my-customcode is all about. **Making Claude Code yours.**
@@ -129,7 +134,7 @@ Claude Code selects the appropriate model and parallelizes independent tasks (up
129
134
  | **QA** | 3 | qa-planner, qa-writer, qa-engineer |
130
135
  | **Total** | **42** | |
131
136
 
132
- ### Skills (51)
137
+ ### Skills (52)
133
138
 
134
139
  Includes slash commands and capabilities:
135
140
 
@@ -165,9 +170,9 @@ Comprehensive reference documentation covering:
165
170
 
166
171
  Event-driven automation for Claude Code lifecycle events (PreToolUse, PostToolUse, etc.).
167
172
 
168
- ### Contexts (4)
173
+ ### Contexts (1)
169
174
 
170
- Shared context files for cross-agent knowledge and mode configurations.
175
+ Shared context file for cross-agent knowledge and mode configurations.
171
176
 
172
177
  ---
173
178
 
@@ -183,6 +188,13 @@ Shared context files for cross-agent knowledge and mode configurations.
183
188
  | `omcustom doctor` | Verify installation health |
184
189
  | `omcustom doctor --fix` | Auto-fix common issues |
185
190
 
191
+ **Global Options:**
192
+ | Option | Description |
193
+ |--------|-------------|
194
+ | `--skip-version-check` | Skip CLI tool version pre-flight check |
195
+ | `-v, --version` | Show version number |
196
+ | `-h, --help` | Show help |
197
+
186
198
  ---
187
199
 
188
200
  ## Project Structure
@@ -191,25 +203,21 @@ After `omcustom init`:
191
203
 
192
204
  ```
193
205
  your-project/
194
- ├── CLAUDE.md # Entry point for Claude
195
- └── .claude/
206
+ ├── CLAUDE.md # Entry point for Claude (or AGENTS.md for Codex)
207
+ └── .claude/ # (or .codex/)
196
208
  ├── rules/ # Behavior rules (18 total)
197
209
  ├── hooks/ # Event hooks (1 total)
198
- ├── contexts/ # Context files (4 total)
199
- ├── agents/ # All agents (flat structure, 42 total)
200
- │ ├── lang-golang-expert/
201
- │ ├── be-fastapi-expert/
202
- │ ├── de-airflow-expert/
203
- ├── mgr-creator/
210
+ ├── contexts/ # Context files (1 total)
211
+ ├── agents/ # Agent definitions (42 flat .md files)
212
+ │ ├── lang-golang-expert.md
213
+ │ ├── be-fastapi-expert.md
214
+ │ ├── mgr-creator.md
215
+ └── ...
216
+ ├── skills/ # Skill modules (52 directories, each with SKILL.md)
217
+ │ ├── go-best-practices/
218
+ │ ├── react-best-practices/
219
+ │ ├── secretary-routing/
204
220
  │ └── ...
205
- ├── skills/ # All skills (51 total, includes slash commands)
206
- │ ├── development/
207
- │ ├── backend/
208
- │ ├── data-engineering/
209
- │ ├── database/
210
- │ ├── infrastructure/
211
- │ ├── system/
212
- │ └── orchestration/
213
221
  └── guides/ # Reference docs (22 total)
214
222
  ```
215
223
 
@@ -227,7 +235,7 @@ bun run build # Build for production
227
235
  ### Requirements
228
236
 
229
237
  - Node.js >= 18.0.0
230
- - Claude Code CLI
238
+ - Claude Code CLI or OpenAI Codex CLI
231
239
 
232
240
  ---
233
241
 
package/dist/cli/index.js CHANGED
@@ -9033,6 +9033,214 @@ var {
9033
9033
  Help
9034
9034
  } = import__.default;
9035
9035
 
9036
+ // src/core/preflight.ts
9037
+ import { execSync } from "node:child_process";
9038
+ function isCI() {
9039
+ const ciEnvVars = ["CI", "GITHUB_ACTIONS", "OMCUSTOM_SKIP_PREFLIGHT"];
9040
+ return ciEnvVars.some((envVar) => process.env[envVar] === "true");
9041
+ }
9042
+ function hasHomebrew() {
9043
+ try {
9044
+ execSync("which brew", { encoding: "utf-8", stdio: "pipe" });
9045
+ return true;
9046
+ } catch {
9047
+ return false;
9048
+ }
9049
+ }
9050
+ function getToolInfoFromBrew(toolName) {
9051
+ const tool = {
9052
+ name: toolName,
9053
+ installed: false,
9054
+ currentVersion: null,
9055
+ latestVersion: null,
9056
+ updateAvailable: false,
9057
+ installMethod: "homebrew"
9058
+ };
9059
+ try {
9060
+ const infoOutput = execSync(`brew info --json=v2 ${toolName}`, {
9061
+ encoding: "utf-8",
9062
+ stdio: "pipe",
9063
+ timeout: 3000
9064
+ });
9065
+ const info = JSON.parse(infoOutput);
9066
+ if (info.casks && info.casks.length > 0) {
9067
+ const cask = info.casks[0];
9068
+ tool.latestVersion = cask.version;
9069
+ tool.currentVersion = cask.installed || null;
9070
+ tool.installed = cask.installed !== null;
9071
+ }
9072
+ if (!tool.installed && info.formulae && info.formulae.length > 0) {
9073
+ const formula = info.formulae[0];
9074
+ tool.latestVersion = formula.versions.stable;
9075
+ if (formula.installed && formula.installed.length > 0) {
9076
+ tool.currentVersion = formula.installed[0].version;
9077
+ tool.installed = true;
9078
+ }
9079
+ }
9080
+ if (tool.installed && tool.currentVersion && tool.latestVersion) {
9081
+ tool.updateAvailable = tool.currentVersion !== tool.latestVersion;
9082
+ }
9083
+ } catch {}
9084
+ return tool;
9085
+ }
9086
+ function getToolInfoFromNpm(toolName) {
9087
+ const tool = {
9088
+ name: toolName,
9089
+ installed: false,
9090
+ currentVersion: null,
9091
+ latestVersion: null,
9092
+ updateAvailable: false,
9093
+ installMethod: "npm"
9094
+ };
9095
+ try {
9096
+ const versionOutput = execSync(`npx ${toolName} --version`, {
9097
+ encoding: "utf-8",
9098
+ stdio: "pipe",
9099
+ timeout: 3000
9100
+ });
9101
+ const version = versionOutput.trim();
9102
+ if (version) {
9103
+ tool.installed = true;
9104
+ tool.currentVersion = version;
9105
+ }
9106
+ } catch {}
9107
+ return tool;
9108
+ }
9109
+ function getToolInfo(toolName) {
9110
+ if (hasHomebrew()) {
9111
+ const brewTool = getToolInfoFromBrew(toolName);
9112
+ if (brewTool.installed) {
9113
+ return brewTool;
9114
+ }
9115
+ }
9116
+ const npmTool = getToolInfoFromNpm(toolName);
9117
+ if (npmTool.installed) {
9118
+ return npmTool;
9119
+ }
9120
+ return {
9121
+ name: toolName,
9122
+ installed: false,
9123
+ currentVersion: null,
9124
+ latestVersion: null,
9125
+ updateAvailable: false,
9126
+ installMethod: "unknown"
9127
+ };
9128
+ }
9129
+ function checkOutdated(tools) {
9130
+ if (!hasHomebrew())
9131
+ return;
9132
+ try {
9133
+ const toolNames = tools.map((t) => t.name).join(" ");
9134
+ const outdatedOutput = execSync(`brew outdated --json=v2 ${toolNames}`, {
9135
+ encoding: "utf-8",
9136
+ stdio: "pipe",
9137
+ timeout: 3000
9138
+ });
9139
+ const outdated = JSON.parse(outdatedOutput);
9140
+ const outdatedCasks = outdated.casks || [];
9141
+ const outdatedFormulae = outdated.formulae || [];
9142
+ for (const tool of tools) {
9143
+ const outdatedCask = outdatedCasks.find((c) => c.name === tool.name);
9144
+ const outdatedFormula = outdatedFormulae.find((f) => f.name === tool.name);
9145
+ if (outdatedCask) {
9146
+ tool.latestVersion = outdatedCask.current_version;
9147
+ tool.updateAvailable = true;
9148
+ } else if (outdatedFormula) {
9149
+ tool.latestVersion = outdatedFormula.current_version;
9150
+ tool.updateAvailable = true;
9151
+ }
9152
+ }
9153
+ } catch {}
9154
+ }
9155
+ async function runPreflightCheck(options = {}) {
9156
+ const { skip = false, tools: toolNames = ["claude-code", "codex"], timeout = 5000 } = options;
9157
+ if (skip) {
9158
+ return {
9159
+ tools: [],
9160
+ hasUpdates: false,
9161
+ warnings: [],
9162
+ skipped: true,
9163
+ skipReason: "Skipped by --skip-version-check flag"
9164
+ };
9165
+ }
9166
+ if (isCI()) {
9167
+ return {
9168
+ tools: [],
9169
+ hasUpdates: false,
9170
+ warnings: [],
9171
+ skipped: true,
9172
+ skipReason: "CI environment detected"
9173
+ };
9174
+ }
9175
+ if (!hasHomebrew()) {
9176
+ return {
9177
+ tools: [],
9178
+ hasUpdates: false,
9179
+ warnings: ["Homebrew not found, skipping version check"],
9180
+ skipped: true,
9181
+ skipReason: "Homebrew not available"
9182
+ };
9183
+ }
9184
+ return new Promise((resolve) => {
9185
+ const timeoutId = setTimeout(() => {
9186
+ resolve({
9187
+ tools: [],
9188
+ hasUpdates: false,
9189
+ warnings: ["Version check timed out"],
9190
+ skipped: true,
9191
+ skipReason: "Timeout"
9192
+ });
9193
+ }, timeout);
9194
+ try {
9195
+ const tools = [];
9196
+ for (const toolName of toolNames) {
9197
+ const tool = getToolInfo(toolName);
9198
+ tools.push(tool);
9199
+ }
9200
+ checkOutdated(tools);
9201
+ const hasUpdates = tools.some((t) => t.updateAvailable);
9202
+ const warnings = [];
9203
+ clearTimeout(timeoutId);
9204
+ resolve({
9205
+ tools,
9206
+ hasUpdates,
9207
+ warnings,
9208
+ skipped: false
9209
+ });
9210
+ } catch (error) {
9211
+ clearTimeout(timeoutId);
9212
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
9213
+ resolve({
9214
+ tools: [],
9215
+ hasUpdates: false,
9216
+ warnings: [`Pre-flight check failed: ${errorMessage}`],
9217
+ skipped: true,
9218
+ skipReason: "Error during check"
9219
+ });
9220
+ }
9221
+ });
9222
+ }
9223
+ function formatPreflightWarnings(result) {
9224
+ if (!result.hasUpdates) {
9225
+ return "";
9226
+ }
9227
+ const lines = [];
9228
+ const updatesAvailable = result.tools.filter((t) => t.updateAvailable);
9229
+ if (updatesAvailable.length === 1) {
9230
+ const tool = updatesAvailable[0];
9231
+ lines.push(`⚠ ${tool.name} ${tool.latestVersion} is available (current: ${tool.currentVersion})`);
9232
+ lines.push(` Run: brew upgrade ${tool.name}`);
9233
+ } else if (updatesAvailable.length > 1) {
9234
+ lines.push("Run the following to upgrade:");
9235
+ for (const tool of updatesAvailable) {
9236
+ lines.push(` brew upgrade ${tool.name} # ${tool.latestVersion} available (current: ${tool.currentVersion})`);
9237
+ }
9238
+ }
9239
+ lines.push(" Use --skip-version-check to skip this check");
9240
+ return lines.join(`
9241
+ `);
9242
+ }
9243
+
9036
9244
  // node_modules/i18next/dist/esm/i18next.js
9037
9245
  var isString = (obj) => typeof obj === "string";
9038
9246
  var defer = () => {
@@ -11461,6 +11669,17 @@ var en_default = {
11461
11669
  error: {
11462
11670
  unexpected: "An unexpected error occurred:"
11463
11671
  },
11672
+ preflight: {
11673
+ checking: "Checking CLI tool versions...",
11674
+ updateAvailable: "⚠ {{name}} {{latest}} is available (current: {{current}})",
11675
+ upgradeCommand: " Run: brew upgrade {{name}}",
11676
+ multipleUpdates: "Run the following to upgrade:",
11677
+ skipFlag: " Use --skip-version-check to skip this check",
11678
+ skippedCi: "Skipping version check (CI environment detected)",
11679
+ skippedFlag: "Skipping version check (--skip-version-check)",
11680
+ homebrewNotFound: "Homebrew not found, skipping version check",
11681
+ timeout: "Version check timed out, continuing..."
11682
+ },
11464
11683
  init: {
11465
11684
  description: "Initialize oh-my-customcode in the current directory",
11466
11685
  langOption: "Language for templates (en or ko)",
@@ -11503,7 +11722,25 @@ var en_default = {
11503
11722
  latestVersion: "Latest version: {{version}}",
11504
11723
  changelog: "Changelog",
11505
11724
  promptUpdate: "Do you want to update?",
11506
- skipped: "Update skipped"
11725
+ skipped: "Update skipped",
11726
+ dryRunOption: "Show what would be updated without making changes",
11727
+ forceOption: "Force update even if already at latest version",
11728
+ backupOption: "Create backup before updating",
11729
+ agentsOption: "Update only agents",
11730
+ skillsOption: "Update only skills",
11731
+ rulesOption: "Update only rules",
11732
+ guidesOption: "Update only guides",
11733
+ hooksOption: "Update only hooks",
11734
+ contextsOption: "Update only contexts",
11735
+ providerOption: "Provider to update (auto, claude, codex)",
11736
+ dryRunHeader: "Dry run - no changes will be made:",
11737
+ componentUpdated: "✓ {{component}} updated",
11738
+ componentSkipped: "- {{component}} skipped (no changes)",
11739
+ componentFailed: "✗ {{component}} failed: {{error}}",
11740
+ preservedFiles: "Preserved {{count}} user-customized files",
11741
+ backupCreated: "Backup created at {{path}}",
11742
+ summary: "Update complete: {{updated}} updated, {{skipped}} skipped",
11743
+ summaryFailed: "Update failed: {{error}}"
11507
11744
  },
11508
11745
  list: {
11509
11746
  description: "List installed components",
@@ -11715,6 +11952,17 @@ var ko_default = {
11715
11952
  error: {
11716
11953
  unexpected: "예기치 않은 오류가 발생했습니다:"
11717
11954
  },
11955
+ preflight: {
11956
+ checking: "CLI 도구 버전 확인 중...",
11957
+ updateAvailable: "⚠ {{name}} {{latest}} 사용 가능 (현재: {{current}})",
11958
+ upgradeCommand: " 실행: brew upgrade {{name}}",
11959
+ multipleUpdates: "다음을 실행하여 업그레이드하세요:",
11960
+ skipFlag: " --skip-version-check 옵션으로 이 검사를 건너뛸 수 있습니다",
11961
+ skippedCi: "버전 확인 건너뜀 (CI 환경 감지)",
11962
+ skippedFlag: "버전 확인 건너뜀 (--skip-version-check)",
11963
+ homebrewNotFound: "Homebrew를 찾을 수 없어 버전 확인을 건너뜁니다",
11964
+ timeout: "버전 확인 시간 초과, 계속 진행합니다..."
11965
+ },
11718
11966
  init: {
11719
11967
  description: "현재 디렉토리에 oh-my-customcode 초기화",
11720
11968
  langOption: "템플릿 언어 (en 또는 ko)",
@@ -11757,7 +12005,25 @@ var ko_default = {
11757
12005
  latestVersion: "최신 버전: {{version}}",
11758
12006
  changelog: "변경 내역",
11759
12007
  promptUpdate: "업데이트하시겠습니까?",
11760
- skipped: "업데이트 건너뜀"
12008
+ skipped: "업데이트 건너뜀",
12009
+ dryRunOption: "변경 없이 업데이트할 내용 표시",
12010
+ forceOption: "이미 최신 버전이어도 강제 업데이트",
12011
+ backupOption: "업데이트 전 백업 생성",
12012
+ agentsOption: "에이전트만 업데이트",
12013
+ skillsOption: "스킬만 업데이트",
12014
+ rulesOption: "규칙만 업데이트",
12015
+ guidesOption: "가이드만 업데이트",
12016
+ hooksOption: "훅만 업데이트",
12017
+ contextsOption: "컨텍스트만 업데이트",
12018
+ providerOption: "업데이트할 제공자 (auto, claude, codex)",
12019
+ dryRunHeader: "드라이 런 - 변경사항 없음:",
12020
+ componentUpdated: "✓ {{component}} 업데이트됨",
12021
+ componentSkipped: "- {{component}} 건너뜀 (변경사항 없음)",
12022
+ componentFailed: "✗ {{component}} 실패: {{error}}",
12023
+ preservedFiles: "사용자 커스터마이징 파일 {{count}}개 보존됨",
12024
+ backupCreated: "백업 생성됨: {{path}}",
12025
+ summary: "업데이트 완료: {{updated}}개 업데이트, {{skipped}}개 건너뜀",
12026
+ summaryFailed: "업데이트 실패: {{error}}"
11761
12027
  },
11762
12028
  list: {
11763
12029
  description: "설치된 컴포넌트 목록 표시",
@@ -13795,73 +14061,279 @@ async function listCommand(type = "all", options = {}) {
13795
14061
  }
13796
14062
  }
13797
14063
 
13798
- // src/cli/update.ts
13799
- async function getCurrentVersion() {
13800
- return null;
14064
+ // src/core/updater.ts
14065
+ import { join as join7 } from "node:path";
14066
+ var CUSTOMIZATION_MANIFEST_FILE = ".omcustom-customizations.json";
14067
+ function createUpdateResult() {
14068
+ return {
14069
+ success: false,
14070
+ updatedComponents: [],
14071
+ skippedComponents: [],
14072
+ preservedFiles: [],
14073
+ backedUpPaths: [],
14074
+ previousVersion: "",
14075
+ newVersion: "",
14076
+ warnings: []
14077
+ };
14078
+ }
14079
+ async function handleBackupIfRequested(targetDir, provider, backup, result) {
14080
+ if (!backup)
14081
+ return;
14082
+ const backupPath = await backupInstallation(targetDir, provider);
14083
+ result.backedUpPaths.push(backupPath);
14084
+ info("update.backup_created", { path: backupPath });
14085
+ }
14086
+ async function processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result) {
14087
+ const componentUpdate = updateCheck.updatableComponents.find((c) => c.name === component);
14088
+ if (!componentUpdate && !options.force) {
14089
+ result.skippedComponents.push(component);
14090
+ return;
14091
+ }
14092
+ if (options.dryRun) {
14093
+ debug("update.dry_run", { component });
14094
+ result.updatedComponents.push(component);
14095
+ return;
14096
+ }
14097
+ try {
14098
+ const preserved = await updateComponent(targetDir, provider, component, customizations, options);
14099
+ result.updatedComponents.push(component);
14100
+ result.preservedFiles.push(...preserved);
14101
+ } catch (err) {
14102
+ const message = err instanceof Error ? err.message : String(err);
14103
+ result.warnings.push(`Failed to update ${component}: ${message}`);
14104
+ result.skippedComponents.push(component);
14105
+ }
14106
+ }
14107
+ async function updateAllComponents(targetDir, provider, components, updateCheck, customizations, options, result) {
14108
+ for (const component of components) {
14109
+ await processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result);
14110
+ }
13801
14111
  }
13802
- async function getLatestVersion() {
14112
+ async function update(options) {
14113
+ const result = createUpdateResult();
14114
+ try {
14115
+ info("update.start", { targetDir: options.targetDir });
14116
+ const config = await loadConfig(options.targetDir);
14117
+ const provider = options.provider ?? (config.provider === "codex" ? "codex" : "claude");
14118
+ result.previousVersion = config.version;
14119
+ const updateCheck = await checkForUpdates(options.targetDir, provider);
14120
+ result.newVersion = updateCheck.latestVersion;
14121
+ if (!updateCheck.hasUpdates && !options.force) {
14122
+ info("update.no_updates");
14123
+ result.success = true;
14124
+ result.skippedComponents = options.components || getAllUpdateComponents();
14125
+ return result;
14126
+ }
14127
+ await handleBackupIfRequested(options.targetDir, provider, !!options.backup, result);
14128
+ const customizations = options.preserveCustomizations !== false ? await loadCustomizationManifest(options.targetDir) : null;
14129
+ const components = options.components || getAllUpdateComponents();
14130
+ await updateAllComponents(options.targetDir, provider, components, updateCheck, customizations, options, result);
14131
+ config.version = result.newVersion;
14132
+ config.lastUpdated = new Date().toISOString();
14133
+ await saveConfig(options.targetDir, config);
14134
+ result.success = true;
14135
+ success("update.success", { from: result.previousVersion, to: result.newVersion });
14136
+ } catch (err) {
14137
+ const message = err instanceof Error ? err.message : String(err);
14138
+ result.error = message;
14139
+ error("update.failed", { error: message });
14140
+ }
14141
+ return result;
14142
+ }
14143
+ async function checkForUpdates(targetDir, provider = "claude") {
14144
+ const config = await loadConfig(targetDir);
14145
+ const currentVersion = config.version;
14146
+ const latestVersion = await getLatestVersion(provider);
14147
+ const updatableComponents = [];
14148
+ for (const component of getAllUpdateComponents()) {
14149
+ const hasUpdate = await componentHasUpdate(targetDir, provider, component, config);
14150
+ if (hasUpdate) {
14151
+ updatableComponents.push({
14152
+ name: component,
14153
+ currentVersion: config.componentVersions?.[component] || "0.0.0",
14154
+ latestVersion
14155
+ });
14156
+ }
14157
+ }
13803
14158
  return {
13804
- version: "0.0.0",
13805
- lastUpdated: new Date().toISOString(),
13806
- source: "https://github.com/baekenough/oh-my-customcode"
14159
+ hasUpdates: updatableComponents.length > 0 || currentVersion !== latestVersion,
14160
+ currentVersion,
14161
+ latestVersion,
14162
+ updatableComponents,
14163
+ checkedAt: new Date().toISOString()
13807
14164
  };
13808
14165
  }
13809
- async function checkUpdateAvailable() {
13810
- const current = await getCurrentVersion();
13811
- const latest = await getLatestVersion();
13812
- if (!current) {
14166
+ async function preserveCustomizations(targetDir, customizations) {
14167
+ const preserved = new Map;
14168
+ const fs2 = await import("node:fs/promises");
14169
+ for (const filePath of customizations) {
14170
+ const fullPath = join7(targetDir, filePath);
14171
+ if (await fileExists(fullPath)) {
14172
+ const content = await fs2.readFile(fullPath, "utf-8");
14173
+ preserved.set(filePath, content);
14174
+ }
14175
+ }
14176
+ return preserved;
14177
+ }
14178
+ function getAllUpdateComponents() {
14179
+ return ["rules", "agents", "skills", "guides", "hooks", "contexts"];
14180
+ }
14181
+ async function getLatestVersion(provider) {
14182
+ const layout = getProviderLayout(provider);
14183
+ const manifestPath = resolveTemplatePath(layout.manifestFile);
14184
+ if (await fileExists(manifestPath)) {
14185
+ const manifest = await readJsonFile(manifestPath);
14186
+ return manifest.version;
14187
+ }
14188
+ return "0.0.0";
14189
+ }
14190
+ async function componentHasUpdate(_targetDir, provider, component, config) {
14191
+ const installedVersion = config.componentVersions?.[component];
14192
+ if (!installedVersion) {
13813
14193
  return true;
13814
14194
  }
13815
- return current.version !== latest.version;
14195
+ const latestVersion = await getLatestVersion(provider);
14196
+ return installedVersion !== latestVersion;
14197
+ }
14198
+ async function updateComponent(targetDir, provider, component, customizations, options) {
14199
+ const preservedFiles = [];
14200
+ const componentPath = getComponentPath2(provider, component);
14201
+ const srcPath = resolveTemplatePath(componentPath);
14202
+ const destPath = join7(targetDir, componentPath);
14203
+ if (customizations && options.preserveCustomizations !== false) {
14204
+ const toPreserve = customizations.preserveFiles.filter((f) => f.startsWith(componentPath));
14205
+ if (toPreserve.length > 0) {
14206
+ const preserved = await preserveCustomizations(targetDir, toPreserve);
14207
+ preservedFiles.push(...preserved.keys());
14208
+ await copyDirectory(srcPath, destPath, { overwrite: true });
14209
+ const fs2 = await import("node:fs/promises");
14210
+ for (const [path2, content] of preserved) {
14211
+ await fs2.writeFile(join7(targetDir, path2), content, "utf-8");
14212
+ }
14213
+ } else {
14214
+ await copyDirectory(srcPath, destPath, { overwrite: true });
14215
+ }
14216
+ } else {
14217
+ await copyDirectory(srcPath, destPath, { overwrite: true });
14218
+ }
14219
+ debug("update.component_updated", { component });
14220
+ return preservedFiles;
14221
+ }
14222
+ function getComponentPath2(provider, component) {
14223
+ const layout = getProviderLayout(provider);
14224
+ if (component === "guides") {
14225
+ return "guides";
14226
+ }
14227
+ return `${layout.rootDir}/${component}`;
13816
14228
  }
13817
- async function applyUpdates(_options) {
13818
- const updatedComponents = [];
13819
- return updatedComponents;
14229
+ async function backupInstallation(targetDir, provider) {
14230
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
14231
+ const backupDir = join7(targetDir, `.omcustom-backup-${timestamp}`);
14232
+ const fs2 = await import("node:fs/promises");
14233
+ await ensureDirectory(backupDir);
14234
+ const layout = getProviderLayout(provider);
14235
+ const dirsToBackup = [layout.rootDir, "guides"];
14236
+ for (const dir2 of dirsToBackup) {
14237
+ const srcPath = join7(targetDir, dir2);
14238
+ if (await fileExists(srcPath)) {
14239
+ const destPath = join7(backupDir, dir2);
14240
+ await copyDirectory(srcPath, destPath, { overwrite: true });
14241
+ }
14242
+ }
14243
+ const entryPath = join7(targetDir, layout.entryFile);
14244
+ if (await fileExists(entryPath)) {
14245
+ await fs2.copyFile(entryPath, join7(backupDir, layout.entryFile));
14246
+ }
14247
+ return backupDir;
13820
14248
  }
14249
+ async function loadCustomizationManifest(targetDir) {
14250
+ const manifestPath = join7(targetDir, CUSTOMIZATION_MANIFEST_FILE);
14251
+ if (await fileExists(manifestPath)) {
14252
+ return readJsonFile(manifestPath);
14253
+ }
14254
+ return null;
14255
+ }
14256
+
14257
+ // src/cli/update.ts
13821
14258
  async function updateCommand(options = {}) {
13822
- console.log(i18n.t("cli.update.checking"));
13823
14259
  try {
13824
- const currentVersion = await getCurrentVersion();
13825
- const latestVersion = await getLatestVersion();
13826
- if (!currentVersion) {
13827
- console.log(i18n.t("cli.update.notInstalled"));
13828
- return {
13829
- success: false,
13830
- message: i18n.t("cli.update.notInstalled"),
13831
- errors: [i18n.t("cli.update.runInitFirst")]
13832
- };
13833
- }
13834
- const updateAvailable = await checkUpdateAvailable();
13835
- if (!updateAvailable && !options.force) {
13836
- console.log(i18n.t("cli.update.alreadyLatest"));
13837
- return {
13838
- success: true,
13839
- message: i18n.t("cli.update.alreadyLatest"),
13840
- currentVersion: currentVersion.version,
13841
- newVersion: latestVersion.version
13842
- };
14260
+ const targetDir = process.cwd();
14261
+ const detection = await detectProvider({
14262
+ targetDir,
14263
+ override: options.provider
14264
+ });
14265
+ const provider = detection.provider;
14266
+ const components = buildComponentsList(options);
14267
+ if (options.dryRun) {
14268
+ console.log(i18n.t("cli.update.dryRunHeader"));
13843
14269
  }
13844
- console.log(i18n.t("cli.update.updating", {
13845
- from: currentVersion.version,
13846
- to: latestVersion.version
13847
- }));
13848
- const updatedComponents = await applyUpdates(options);
13849
- console.log(i18n.t("cli.update.success"));
13850
- return {
13851
- success: true,
13852
- message: i18n.t("cli.update.success"),
13853
- updatedComponents,
13854
- currentVersion: currentVersion.version,
13855
- newVersion: latestVersion.version
14270
+ const updateOptions = {
14271
+ targetDir,
14272
+ provider,
14273
+ components,
14274
+ force: options.force,
14275
+ preserveCustomizations: true,
14276
+ dryRun: options.dryRun,
14277
+ backup: options.backup
13856
14278
  };
14279
+ const result = await update(updateOptions);
14280
+ printUpdateResults(result);
14281
+ if (!result.success) {
14282
+ process.exit(1);
14283
+ }
13857
14284
  } catch (error2) {
13858
14285
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
13859
- console.error(i18n.t("cli.update.failed"), errorMessage);
13860
- return {
13861
- success: false,
13862
- message: i18n.t("cli.update.failed"),
13863
- errors: [errorMessage]
13864
- };
14286
+ console.error(i18n.t("cli.update.summaryFailed", { error: errorMessage }));
14287
+ process.exit(1);
14288
+ }
14289
+ }
14290
+ function buildComponentsList(options) {
14291
+ const components = [];
14292
+ if (options.agents) {
14293
+ components.push("agents");
14294
+ }
14295
+ if (options.skills) {
14296
+ components.push("skills");
14297
+ }
14298
+ if (options.rules) {
14299
+ components.push("rules");
14300
+ }
14301
+ if (options.guides) {
14302
+ components.push("guides");
14303
+ }
14304
+ if (options.hooks) {
14305
+ components.push("hooks");
14306
+ }
14307
+ if (options.contexts) {
14308
+ components.push("contexts");
14309
+ }
14310
+ return components.length > 0 ? components : undefined;
14311
+ }
14312
+ function printUpdateResults(result) {
14313
+ for (const component of result.updatedComponents) {
14314
+ console.log(i18n.t("cli.update.componentUpdated", { component }));
14315
+ }
14316
+ for (const component of result.skippedComponents) {
14317
+ console.log(i18n.t("cli.update.componentSkipped", { component }));
14318
+ }
14319
+ if (result.preservedFiles.length > 0) {
14320
+ console.log(i18n.t("cli.update.preservedFiles", { count: result.preservedFiles.length }));
14321
+ }
14322
+ if (result.backedUpPaths.length > 0) {
14323
+ for (const path2 of result.backedUpPaths) {
14324
+ console.log(i18n.t("cli.update.backupCreated", { path: path2 }));
14325
+ }
14326
+ }
14327
+ for (const warning of result.warnings) {
14328
+ console.warn(warning);
14329
+ }
14330
+ if (result.success) {
14331
+ console.log(i18n.t("cli.update.summary", {
14332
+ updated: result.updatedComponents.length,
14333
+ skipped: result.skippedComponents.length
14334
+ }));
14335
+ } else if (result.error) {
14336
+ console.error(i18n.t("cli.update.summaryFailed", { error: result.error }));
13865
14337
  }
13866
14338
  }
13867
14339
 
@@ -13870,12 +14342,12 @@ var require2 = createRequire2(import.meta.url);
13870
14342
  var packageJson = require2("../../package.json");
13871
14343
  function createProgram() {
13872
14344
  const program2 = new Command;
13873
- program2.name("omcustom").description(i18n.t("cli.description")).version(packageJson.version, "-v, --version", i18n.t("cli.versionOption"));
14345
+ program2.name("omcustom").description(i18n.t("cli.description")).version(packageJson.version, "-v, --version", i18n.t("cli.versionOption")).option("--skip-version-check", "Skip CLI version pre-flight check");
13874
14346
  program2.command("init").description(i18n.t("cli.init.description")).option("-l, --lang <language>", i18n.t("cli.init.langOption"), "en").option("-p, --provider <provider>", i18n.t("cli.init.providerOption"), "auto").action(async (options) => {
13875
14347
  await initCommand(options);
13876
14348
  });
13877
- program2.command("update").description(i18n.t("cli.update.description")).action(async () => {
13878
- await updateCommand();
14349
+ program2.command("update").description(i18n.t("cli.update.description")).option("--dry-run", i18n.t("cli.update.dryRunOption")).option("--force", i18n.t("cli.update.forceOption")).option("--backup", i18n.t("cli.update.backupOption")).option("--agents", i18n.t("cli.update.agentsOption")).option("--skills", i18n.t("cli.update.skillsOption")).option("--rules", i18n.t("cli.update.rulesOption")).option("--guides", i18n.t("cli.update.guidesOption")).option("--hooks", i18n.t("cli.update.hooksOption")).option("--contexts", i18n.t("cli.update.contextsOption")).option("-p, --provider <provider>", i18n.t("cli.update.providerOption"), "auto").action(async (options) => {
14350
+ await updateCommand(options);
13879
14351
  });
13880
14352
  program2.command("list").description(i18n.t("cli.list.description")).argument("[type]", i18n.t("cli.list.typeArgument"), "all").option("-f, --format <format>", "Output format: table, json, or simple", "table").option("--verbose", "Show detailed information").option("-p, --provider <provider>", i18n.t("cli.list.providerOption"), "auto").action(async (type, options) => {
13881
14353
  await listCommand(type, {
@@ -13887,6 +14359,16 @@ function createProgram() {
13887
14359
  program2.command("doctor").description(i18n.t("cli.doctor.description")).option("--fix", i18n.t("cli.doctor.fixOption")).option("-p, --provider <provider>", i18n.t("cli.doctor.providerOption"), "auto").action(async (options) => {
13888
14360
  await doctorCommand(options);
13889
14361
  });
14362
+ program2.hook("preAction", async (thisCommand) => {
14363
+ const opts = thisCommand.optsWithGlobals();
14364
+ const skipCheck = opts.skipVersionCheck || false;
14365
+ const result = await runPreflightCheck({ skip: skipCheck });
14366
+ if (result.hasUpdates) {
14367
+ const warnings = formatPreflightWarnings(result);
14368
+ console.warn(warnings);
14369
+ console.warn("");
14370
+ }
14371
+ });
13890
14372
  return program2;
13891
14373
  }
13892
14374
  async function main() {
package/dist/index.js CHANGED
@@ -1,20 +1,4 @@
1
1
  import { createRequire } from "node:module";
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __toESM = (mod, isNodeMode, target) => {
8
- target = mod != null ? __create(__getProtoOf(mod)) : {};
9
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
- for (let key of __getOwnPropNames(mod))
11
- if (!__hasOwnProp.call(to, key))
12
- __defProp(to, key, {
13
- get: () => mod[key],
14
- enumerable: true
15
- });
16
- return to;
17
- };
18
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
3
 
20
4
  // src/core/config.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-customcode",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "Batteries-included agent harness for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- 4e8d4e056e9633819fc41f927e1c18a6bc757636abbac85552a767664df242f4