oh-my-customcode 0.79.0 → 0.79.2

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
@@ -2140,6 +2140,234 @@ var require_commander = __commonJS((exports) => {
2140
2140
  exports.InvalidOptionArgumentError = InvalidArgumentError;
2141
2141
  });
2142
2142
 
2143
+ // src/core/registry.ts
2144
+ var exports_registry = {};
2145
+ __export(exports_registry, {
2146
+ unregisterProject: () => unregisterProject,
2147
+ registryToList: () => registryToList,
2148
+ registerProject: () => registerProject,
2149
+ readRegistry: () => readRegistry,
2150
+ migrateFromLockfiles: () => migrateFromLockfiles,
2151
+ _setRegistryDirForTesting: () => _setRegistryDirForTesting
2152
+ });
2153
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2154
+ import { homedir } from "node:os";
2155
+ import { basename, join, resolve } from "node:path";
2156
+ function _setRegistryDirForTesting(dir) {
2157
+ _registryDirOverride = dir;
2158
+ }
2159
+ function registryDir() {
2160
+ if (_registryDirOverride !== undefined)
2161
+ return _registryDirOverride;
2162
+ const home = process.env.HOME ?? homedir();
2163
+ return join(home, ".oh-my-customcode");
2164
+ }
2165
+ function registryPath() {
2166
+ return join(registryDir(), "projects.json");
2167
+ }
2168
+ async function readRegistryRaw() {
2169
+ try {
2170
+ const content = await readFile(registryPath(), "utf-8");
2171
+ const parsed = JSON.parse(content);
2172
+ if (typeof parsed === "object" && parsed !== null && "projects" in parsed && typeof parsed.projects === "object" && parsed.projects !== null) {
2173
+ return parsed;
2174
+ }
2175
+ return { ...EMPTY_REGISTRY };
2176
+ } catch {
2177
+ return { ...EMPTY_REGISTRY };
2178
+ }
2179
+ }
2180
+ async function writeRegistry(registry) {
2181
+ const dir = registryDir();
2182
+ await mkdir(dir, { recursive: true });
2183
+ await writeFile(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
2184
+ }
2185
+ async function readRegistry() {
2186
+ return readRegistryRaw();
2187
+ }
2188
+ async function registerProject(projectPath, version) {
2189
+ const normalizedPath = resolve(projectPath);
2190
+ const registry = await readRegistryRaw();
2191
+ const existing = registry.projects[normalizedPath];
2192
+ const now = new Date().toISOString();
2193
+ registry.projects[normalizedPath] = {
2194
+ version,
2195
+ installedAt: existing?.installedAt ?? now,
2196
+ updatedAt: now
2197
+ };
2198
+ await writeRegistry(registry);
2199
+ }
2200
+ async function unregisterProject(projectPath) {
2201
+ const normalizedPath = resolve(projectPath);
2202
+ const registry = await readRegistryRaw();
2203
+ if (!(normalizedPath in registry.projects))
2204
+ return;
2205
+ delete registry.projects[normalizedPath];
2206
+ await writeRegistry(registry);
2207
+ }
2208
+ async function parseLockFile(lockPath) {
2209
+ try {
2210
+ const content = await readFile(lockPath, "utf-8");
2211
+ const lock = JSON.parse(content);
2212
+ const version = typeof lock.version === "string" ? lock.version : typeof lock.templateVersion === "string" ? lock.templateVersion : "0.0.0";
2213
+ const now = new Date().toISOString();
2214
+ return {
2215
+ version,
2216
+ installedAt: typeof lock.installedAt === "string" ? lock.installedAt : now,
2217
+ updatedAt: typeof lock.updatedAt === "string" ? lock.updatedAt : now
2218
+ };
2219
+ } catch {
2220
+ return null;
2221
+ }
2222
+ }
2223
+ async function migrateFromLockfiles(searchDirs) {
2224
+ const { readdir } = await import("node:fs/promises");
2225
+ const MAX_DEPTH = 3;
2226
+ const discovered = new Map;
2227
+ async function scan(dir, depth) {
2228
+ if (depth > MAX_DEPTH || discovered.has(dir))
2229
+ return;
2230
+ let entries;
2231
+ try {
2232
+ entries = await readdir(dir, { withFileTypes: true });
2233
+ } catch {
2234
+ return;
2235
+ }
2236
+ const entry = await parseLockFile(join(dir, ".omcustom.lock.json"));
2237
+ if (entry !== null) {
2238
+ discovered.set(resolve(dir), entry);
2239
+ return;
2240
+ }
2241
+ if (depth < MAX_DEPTH) {
2242
+ const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SCAN_SKIP_DIRS.has(e.name));
2243
+ await Promise.all(subdirs.map((sub) => scan(join(dir, sub.name), depth + 1)));
2244
+ }
2245
+ }
2246
+ await Promise.all(searchDirs.map((dir) => scan(dir, 0).catch(() => {})));
2247
+ if (discovered.size === 0)
2248
+ return 0;
2249
+ const registry = await readRegistryRaw();
2250
+ let imported = 0;
2251
+ for (const [projectPath, entry] of discovered) {
2252
+ if (!(projectPath in registry.projects)) {
2253
+ registry.projects[projectPath] = entry;
2254
+ imported++;
2255
+ }
2256
+ }
2257
+ if (imported > 0) {
2258
+ await writeRegistry(registry);
2259
+ }
2260
+ return imported;
2261
+ }
2262
+ function registryToList(registry) {
2263
+ return Object.entries(registry.projects).map(([path, entry]) => ({
2264
+ name: basename(path),
2265
+ path,
2266
+ version: entry.version,
2267
+ installedAt: entry.installedAt,
2268
+ updatedAt: entry.updatedAt
2269
+ }));
2270
+ }
2271
+ var _registryDirOverride, EMPTY_REGISTRY, SCAN_SKIP_DIRS;
2272
+ var init_registry = __esm(() => {
2273
+ EMPTY_REGISTRY = { projects: {} };
2274
+ SCAN_SKIP_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
2275
+ });
2276
+
2277
+ // package.json
2278
+ var package_default;
2279
+ var init_package = __esm(() => {
2280
+ package_default = {
2281
+ name: "oh-my-customcode",
2282
+ workspaces: [
2283
+ "packages/*"
2284
+ ],
2285
+ version: "0.79.2",
2286
+ description: "Batteries-included agent harness for Claude Code",
2287
+ type: "module",
2288
+ bin: {
2289
+ omcustom: "./dist/cli/index.js"
2290
+ },
2291
+ main: "./dist/index.js",
2292
+ types: "./dist/index.d.ts",
2293
+ exports: {
2294
+ ".": {
2295
+ import: "./dist/index.js",
2296
+ types: "./dist/index.d.ts"
2297
+ }
2298
+ },
2299
+ files: [
2300
+ "dist",
2301
+ "templates"
2302
+ ],
2303
+ publishConfig: {
2304
+ access: "public",
2305
+ registry: "https://registry.npmjs.org/"
2306
+ },
2307
+ scripts: {
2308
+ dev: "bun run src/cli/index.ts",
2309
+ build: "bun build src/cli/index.ts --outdir dist/cli --target node && bun build src/index.ts --outdir dist --target node && bun run scripts/sync-source-lockfile.ts",
2310
+ test: "bun test tests/ packages/eval-core/",
2311
+ "test:unit": "bun test tests/unit",
2312
+ "test:integration": "bun test tests/integration",
2313
+ "test:e2e": "bun test tests/e2e",
2314
+ "test:coverage": "bun test --coverage",
2315
+ lint: "biome check src/ tests/ scripts/",
2316
+ "lint:fix": "biome check --write src/ tests/ scripts/",
2317
+ format: "biome format --write src/ tests/ scripts/",
2318
+ typecheck: "tsc --noEmit",
2319
+ "docs:dev": "vitepress dev docs",
2320
+ "docs:build": "vitepress build docs",
2321
+ prepare: "sh scripts/setup-hooks.sh || true",
2322
+ "setup:hooks": "sh scripts/setup-hooks.sh",
2323
+ prepublishOnly: "bun run build && bun run test"
2324
+ },
2325
+ dependencies: {
2326
+ "@clack/prompts": "^1.1.0",
2327
+ "@inquirer/prompts": "^8.3.2",
2328
+ commander: "^14.0.2",
2329
+ i18next: "^26.0.2",
2330
+ yaml: "^2.8.2"
2331
+ },
2332
+ devDependencies: {
2333
+ "@anthropic-ai/sdk": "^0.82.0",
2334
+ "@biomejs/biome": "^2.3.12",
2335
+ "@types/bun": "^1.3.6",
2336
+ "@types/js-yaml": "^4.0.9",
2337
+ "@types/nodemailer": "^8.0.0",
2338
+ "js-yaml": "^4.1.0",
2339
+ nodemailer: "^8.0.1",
2340
+ typescript: "^6.0.2",
2341
+ vitepress: "^1.6.4"
2342
+ },
2343
+ keywords: [
2344
+ "claude",
2345
+ "claude-code",
2346
+ "openai",
2347
+ "ai",
2348
+ "agent",
2349
+ "cli"
2350
+ ],
2351
+ author: "baekenough",
2352
+ license: "MIT",
2353
+ repository: {
2354
+ type: "git",
2355
+ url: "git+https://github.com/baekenough/oh-my-customcode.git"
2356
+ },
2357
+ bugs: {
2358
+ url: "https://github.com/baekenough/oh-my-customcode/issues"
2359
+ },
2360
+ homepage: "https://github.com/baekenough/oh-my-customcode#readme",
2361
+ engines: {
2362
+ node: ">=18.0.0"
2363
+ },
2364
+ overrides: {
2365
+ rollup: "^4.59.0",
2366
+ esbuild: "^0.25.0"
2367
+ }
2368
+ };
2369
+ });
2370
+
2143
2371
  // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/identity.js
2144
2372
  var require_identity = __commonJS((exports) => {
2145
2373
  var ALIAS = Symbol.for("yaml.alias");
@@ -9068,7 +9296,7 @@ __export(exports_fs, {
9068
9296
  copyDirectory: () => copyDirectory,
9069
9297
  calculateChecksum: () => calculateChecksum
9070
9298
  });
9071
- import { dirname as dirname2, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
9299
+ import { dirname as dirname2, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "node:path";
9072
9300
  import { fileURLToPath } from "node:url";
9073
9301
  function validatePreserveFilePath(filePath, projectRoot) {
9074
9302
  if (!filePath || filePath.trim() === "") {
@@ -9083,7 +9311,7 @@ function validatePreserveFilePath(filePath, projectRoot) {
9083
9311
  reason: "Absolute paths are not allowed"
9084
9312
  };
9085
9313
  }
9086
- const resolvedPath = resolve(projectRoot, filePath);
9314
+ const resolvedPath = resolve2(projectRoot, filePath);
9087
9315
  const relativePath = relative(projectRoot, resolvedPath);
9088
9316
  if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
9089
9317
  return {
@@ -9229,11 +9457,11 @@ async function remove(path) {
9229
9457
  function getPackageRoot() {
9230
9458
  const currentFile = fileURLToPath(import.meta.url);
9231
9459
  const currentDir = dirname2(currentFile);
9232
- return resolve(currentDir, "..", "..");
9460
+ return resolve2(currentDir, "..", "..");
9233
9461
  }
9234
9462
  function resolveTemplatePath(relativePath) {
9235
9463
  const packageRoot = getPackageRoot();
9236
- return join2(packageRoot, "templates", relativePath);
9464
+ return join3(packageRoot, "templates", relativePath);
9237
9465
  }
9238
9466
  async function listFiles(dir2, options = {}) {
9239
9467
  const fs = await import("node:fs/promises");
@@ -9313,104 +9541,10 @@ function isAbsolutePath(inputPath) {
9313
9541
  return isAbsolute(inputPath);
9314
9542
  }
9315
9543
  function resolvePath(...paths) {
9316
- return resolve(...paths);
9544
+ return resolve2(...paths);
9317
9545
  }
9318
9546
  var init_fs = () => {};
9319
9547
 
9320
- // package.json
9321
- var package_default;
9322
- var init_package = __esm(() => {
9323
- package_default = {
9324
- name: "oh-my-customcode",
9325
- workspaces: [
9326
- "packages/*"
9327
- ],
9328
- version: "0.79.0",
9329
- description: "Batteries-included agent harness for Claude Code",
9330
- type: "module",
9331
- bin: {
9332
- omcustom: "./dist/cli/index.js"
9333
- },
9334
- main: "./dist/index.js",
9335
- types: "./dist/index.d.ts",
9336
- exports: {
9337
- ".": {
9338
- import: "./dist/index.js",
9339
- types: "./dist/index.d.ts"
9340
- }
9341
- },
9342
- files: [
9343
- "dist",
9344
- "templates"
9345
- ],
9346
- publishConfig: {
9347
- access: "public",
9348
- registry: "https://registry.npmjs.org/"
9349
- },
9350
- scripts: {
9351
- dev: "bun run src/cli/index.ts",
9352
- build: "bun build src/cli/index.ts --outdir dist/cli --target node && bun build src/index.ts --outdir dist --target node && bun run scripts/sync-source-lockfile.ts",
9353
- test: "bun test tests/ packages/eval-core/",
9354
- "test:unit": "bun test tests/unit",
9355
- "test:integration": "bun test tests/integration",
9356
- "test:e2e": "bun test tests/e2e",
9357
- "test:coverage": "bun test --coverage",
9358
- lint: "biome check src/ tests/ scripts/",
9359
- "lint:fix": "biome check --write src/ tests/ scripts/",
9360
- format: "biome format --write src/ tests/ scripts/",
9361
- typecheck: "tsc --noEmit",
9362
- "docs:dev": "vitepress dev docs",
9363
- "docs:build": "vitepress build docs",
9364
- prepare: "sh scripts/setup-hooks.sh || true",
9365
- "setup:hooks": "sh scripts/setup-hooks.sh",
9366
- prepublishOnly: "bun run build && bun run test"
9367
- },
9368
- dependencies: {
9369
- "@clack/prompts": "^1.1.0",
9370
- "@inquirer/prompts": "^8.3.2",
9371
- commander: "^14.0.2",
9372
- i18next: "^26.0.2",
9373
- yaml: "^2.8.2"
9374
- },
9375
- devDependencies: {
9376
- "@anthropic-ai/sdk": "^0.82.0",
9377
- "@biomejs/biome": "^2.3.12",
9378
- "@types/bun": "^1.3.6",
9379
- "@types/js-yaml": "^4.0.9",
9380
- "@types/nodemailer": "^8.0.0",
9381
- "js-yaml": "^4.1.0",
9382
- nodemailer: "^8.0.1",
9383
- typescript: "^6.0.2",
9384
- vitepress: "^1.6.4"
9385
- },
9386
- keywords: [
9387
- "claude",
9388
- "claude-code",
9389
- "openai",
9390
- "ai",
9391
- "agent",
9392
- "cli"
9393
- ],
9394
- author: "baekenough",
9395
- license: "MIT",
9396
- repository: {
9397
- type: "git",
9398
- url: "git+https://github.com/baekenough/oh-my-customcode.git"
9399
- },
9400
- bugs: {
9401
- url: "https://github.com/baekenough/oh-my-customcode/issues"
9402
- },
9403
- homepage: "https://github.com/baekenough/oh-my-customcode#readme",
9404
- engines: {
9405
- node: ">=18.0.0"
9406
- },
9407
- overrides: {
9408
- rollup: "^4.59.0",
9409
- esbuild: "^0.25.0"
9410
- }
9411
- };
9412
- });
9413
-
9414
9548
  // src/cli/projects.ts
9415
9549
  var exports_projects = {};
9416
9550
  __export(exports_projects, {
@@ -9420,10 +9554,10 @@ __export(exports_projects, {
9420
9554
  findProjects: () => findProjects,
9421
9555
  default: () => projects_default
9422
9556
  });
9423
- import { homedir as homedir2 } from "node:os";
9424
- import { basename as basename3, dirname as dirname3, join as join9 } from "node:path";
9557
+ import { homedir as homedir3 } from "node:os";
9558
+ import { basename as basename4, join as join10, sep as sep2 } from "node:path";
9425
9559
  async function readLockFile(projectDir) {
9426
- const lockFilePath = join9(projectDir, ".omcustom.lock.json");
9560
+ const lockFilePath = join10(projectDir, ".omcustom.lock.json");
9427
9561
  try {
9428
9562
  const fs2 = await import("node:fs/promises");
9429
9563
  const content = await fs2.readFile(lockFilePath, "utf-8");
@@ -9432,20 +9566,6 @@ async function readLockFile(projectDir) {
9432
9566
  return null;
9433
9567
  }
9434
9568
  }
9435
- async function hasOmcustomMarkers(dir2) {
9436
- const fs2 = await import("node:fs/promises");
9437
- const agentsDir = join9(dir2, ".claude", "agents");
9438
- const skillsDir = join9(dir2, ".claude", "skills");
9439
- try {
9440
- const [agentsStat, skillsStat] = await Promise.allSettled([
9441
- fs2.stat(agentsDir),
9442
- fs2.stat(skillsDir)
9443
- ]);
9444
- return agentsStat.status === "fulfilled" && agentsStat.value.isDirectory() && skillsStat.status === "fulfilled" && skillsStat.value.isDirectory();
9445
- } catch {
9446
- return false;
9447
- }
9448
- }
9449
9569
  function computeStatus(version, currentVersion) {
9450
9570
  if (!version)
9451
9571
  return "unknown";
@@ -9461,48 +9581,6 @@ function computeStatus(version, currentVersion) {
9461
9581
  }
9462
9582
  return "latest";
9463
9583
  }
9464
- async function searchDirectory(dir2, depth, results, currentVersion, seen) {
9465
- if (depth > MAX_SEARCH_DEPTH || seen.has(dir2))
9466
- return;
9467
- seen.add(dir2);
9468
- const fs2 = await import("node:fs/promises");
9469
- let entries;
9470
- try {
9471
- entries = await fs2.readdir(dir2, { withFileTypes: true });
9472
- } catch {
9473
- return;
9474
- }
9475
- const lockFile = await readLockFile(dir2);
9476
- if (lockFile) {
9477
- const version = lockFile.version || lockFile.templateVersion || null;
9478
- results.push({
9479
- name: basename3(dir2),
9480
- path: dir2,
9481
- version,
9482
- installedAt: lockFile.installedAt || null,
9483
- updatedAt: lockFile.updatedAt || null,
9484
- status: computeStatus(version, currentVersion),
9485
- detectionMethod: "lockfile"
9486
- });
9487
- return;
9488
- }
9489
- if (await hasOmcustomMarkers(dir2)) {
9490
- results.push({
9491
- name: basename3(dir2),
9492
- path: dir2,
9493
- version: null,
9494
- installedAt: null,
9495
- updatedAt: null,
9496
- status: "unknown",
9497
- detectionMethod: "directory"
9498
- });
9499
- return;
9500
- }
9501
- if (depth < MAX_SEARCH_DEPTH) {
9502
- const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist" && e.name !== "build" && e.name !== ".git");
9503
- await Promise.all(subdirs.map((subdir) => searchDirectory(join9(dir2, subdir.name), depth + 1, results, currentVersion, seen)));
9504
- }
9505
- }
9506
9584
  async function getTemplateVersion() {
9507
9585
  const manifestPath = resolveTemplatePath("manifest.json");
9508
9586
  if (await fileExists(manifestPath)) {
@@ -9513,13 +9591,74 @@ async function getTemplateVersion() {
9513
9591
  }
9514
9592
  async function findProjects(options = {}) {
9515
9593
  const currentVersion = await getTemplateVersion();
9516
- const home = homedir2();
9594
+ const registry = await readRegistry();
9595
+ if (Object.keys(registry.projects).length === 0) {
9596
+ const fallbackResults = await _findProjectsFromLockfiles(options, currentVersion);
9597
+ if (fallbackResults.length === 0 && !options.paths) {
9598
+ process.stderr.write(" No projects in registry. Run `omcustom projects --migrate` to import existing projects.\n");
9599
+ }
9600
+ return fallbackResults;
9601
+ }
9602
+ const results = [];
9603
+ for (const [projectPath, entry] of Object.entries(registry.projects)) {
9604
+ if (options.paths && options.paths.length > 0) {
9605
+ const matchesPath = options.paths.some((searchPath) => projectPath === searchPath || projectPath.startsWith(searchPath + sep2));
9606
+ if (!matchesPath)
9607
+ continue;
9608
+ }
9609
+ results.push({
9610
+ name: basename4(projectPath),
9611
+ path: projectPath,
9612
+ version: entry.version || null,
9613
+ installedAt: entry.installedAt || null,
9614
+ updatedAt: entry.updatedAt || null,
9615
+ status: computeStatus(entry.version || null, currentVersion),
9616
+ detectionMethod: "registry"
9617
+ });
9618
+ }
9619
+ return results.sort((a, b) => {
9620
+ if (a.status === "latest" && b.status !== "latest")
9621
+ return -1;
9622
+ if (a.status !== "latest" && b.status === "latest")
9623
+ return 1;
9624
+ return a.name.localeCompare(b.name);
9625
+ });
9626
+ }
9627
+ async function _findProjectsFromLockfiles(options, currentVersion) {
9628
+ const { dirname: dirname3 } = await import("node:path");
9629
+ const fs2 = await import("node:fs/promises");
9517
9630
  const seen = new Set;
9518
9631
  const results = [];
9519
- const searchPaths = [];
9520
- for (const dir2 of DEFAULT_SEARCH_DIRS) {
9521
- searchPaths.push(join9(home, dir2));
9632
+ async function scanDir(dir2, depth) {
9633
+ if (depth > 3 || seen.has(dir2))
9634
+ return;
9635
+ seen.add(dir2);
9636
+ let entries;
9637
+ try {
9638
+ entries = await fs2.readdir(dir2, { withFileTypes: true });
9639
+ } catch {
9640
+ return;
9641
+ }
9642
+ const lockFile = await readLockFile(dir2);
9643
+ if (lockFile) {
9644
+ const version = lockFile.version || lockFile.templateVersion || null;
9645
+ results.push({
9646
+ name: basename4(dir2),
9647
+ path: dir2,
9648
+ version,
9649
+ installedAt: lockFile.installedAt || null,
9650
+ updatedAt: lockFile.updatedAt || null,
9651
+ status: computeStatus(version, currentVersion),
9652
+ detectionMethod: "lockfile"
9653
+ });
9654
+ return;
9655
+ }
9656
+ if (depth < 3) {
9657
+ const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist" && e.name !== "build" && e.name !== ".git");
9658
+ await Promise.all(subdirs.map((sub) => scanDir(join10(dir2, sub.name), depth + 1).catch(() => {})));
9659
+ }
9522
9660
  }
9661
+ const searchPaths = options.paths ? [...options.paths] : [];
9523
9662
  if (!options.paths) {
9524
9663
  const cwd = process.cwd();
9525
9664
  if (!searchPaths.includes(cwd))
@@ -9528,10 +9667,7 @@ async function findProjects(options = {}) {
9528
9667
  if (parent !== cwd && !searchPaths.includes(parent))
9529
9668
  searchPaths.push(parent);
9530
9669
  }
9531
- if (options.paths) {
9532
- searchPaths.push(...options.paths);
9533
- }
9534
- await Promise.all(searchPaths.map((searchPath) => searchDirectory(searchPath, 0, results, currentVersion, seen).catch(() => {})));
9670
+ await Promise.all(searchPaths.map((p) => scanDir(p, 0).catch(() => {})));
9535
9671
  return results.sort((a, b) => {
9536
9672
  if (a.status === "latest" && b.status !== "latest")
9537
9673
  return -1;
@@ -9542,7 +9678,7 @@ async function findProjects(options = {}) {
9542
9678
  }
9543
9679
  async function writeLockFile(projectDir, version, existing) {
9544
9680
  const fs2 = await import("node:fs/promises");
9545
- const lockFilePath = join9(projectDir, ".omcustom.lock.json");
9681
+ const lockFilePath = join10(projectDir, ".omcustom.lock.json");
9546
9682
  const now = new Date().toISOString();
9547
9683
  const merged = {
9548
9684
  ...existing || {},
@@ -9556,8 +9692,7 @@ function formatProjectsTable(projects, currentVersion) {
9556
9692
  if (projects.length === 0) {
9557
9693
  console.log(`
9558
9694
  oh-my-customcode가 적용된 프로젝트를 찾을 수 없습니다.`);
9559
- console.log(` 검색 경로: ~/workspace, ~/projects, ~/dev, ~/src, ~/code, ~/repos, ~/work, (현재 디렉토리 및 부모)
9560
- `);
9695
+ console.log(" 레지스트리가 비어 있습니다. `omcustom projects --migrate`를 실행하여 기존 프로젝트를 가져오세요.\n");
9561
9696
  return;
9562
9697
  }
9563
9698
  const nameWidth = Math.max(20, ...projects.map((p) => p.name.length));
@@ -9589,7 +9724,7 @@ function formatProjectsTable(projects, currentVersion) {
9589
9724
  `);
9590
9725
  }
9591
9726
  function shortenPath(path2) {
9592
- const home = homedir2();
9727
+ const home = homedir3();
9593
9728
  if (path2.startsWith(home)) {
9594
9729
  return `~${path2.slice(home.length)}`;
9595
9730
  }
@@ -9606,9 +9741,34 @@ oh-my-customcode 적용 프로젝트 (${projects.length}개):`);
9606
9741
  console.log(`
9607
9742
  현재 설치 버전: v${currentVersion}`);
9608
9743
  }
9744
+ async function runMigration(options) {
9745
+ const { migrateFromLockfiles: migrateFromLockfiles2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
9746
+ const { homedir: _homedir } = await import("node:os");
9747
+ const DEFAULT_SEARCH_DIRS = ["workspace", "projects", "dev", "src", "code", "repos", "work"];
9748
+ const home = _homedir();
9749
+ const searchDirs = [...DEFAULT_SEARCH_DIRS.map((d) => join10(home, d)), ...options.paths ?? []];
9750
+ const cwd = process.cwd();
9751
+ if (!searchDirs.includes(cwd))
9752
+ searchDirs.push(cwd);
9753
+ console.log(" 레지스트리 마이그레이션 시작...");
9754
+ try {
9755
+ const imported = await migrateFromLockfiles2(searchDirs);
9756
+ console.log(` 마이그레이션 완료: ${imported}개 프로젝트가 레지스트리에 추가되었습니다.`);
9757
+ return null;
9758
+ } catch (error2) {
9759
+ return error2 instanceof Error ? error2.message : String(error2);
9760
+ }
9761
+ }
9609
9762
  async function projectsCommand(options = {}) {
9610
9763
  const currentVersion = await getTemplateVersion();
9611
9764
  const format = options.format || "table";
9765
+ if (options.migrate) {
9766
+ const migrationError = await runMigration(options);
9767
+ if (migrationError) {
9768
+ console.error(" 마이그레이션 실패:", migrationError);
9769
+ return { success: false, projects: [], currentVersion, errors: [migrationError] };
9770
+ }
9771
+ }
9612
9772
  console.log(" oh-my-customcode 적용 프로젝트를 검색 중...");
9613
9773
  try {
9614
9774
  const projects = await findProjects(options);
@@ -9626,11 +9786,11 @@ async function projectsCommand(options = {}) {
9626
9786
  return { success: false, projects: [], currentVersion, errors: [errorMessage] };
9627
9787
  }
9628
9788
  }
9629
- var DEFAULT_SEARCH_DIRS, MAX_SEARCH_DEPTH = 3, projects_default;
9789
+ var projects_default;
9630
9790
  var init_projects = __esm(() => {
9631
9791
  init_package();
9792
+ init_registry();
9632
9793
  init_fs();
9633
- DEFAULT_SEARCH_DIRS = ["workspace", "projects", "dev", "src", "code", "repos", "work"];
9634
9794
  projects_default = projectsCommand;
9635
9795
  });
9636
9796
 
@@ -11185,13 +11345,13 @@ var PromisePolyfill;
11185
11345
  var init_promise_polyfill = __esm(() => {
11186
11346
  PromisePolyfill = class PromisePolyfill extends Promise {
11187
11347
  static withResolver() {
11188
- let resolve3;
11348
+ let resolve4;
11189
11349
  let reject;
11190
11350
  const promise = new Promise((res, rej) => {
11191
- resolve3 = res;
11351
+ resolve4 = res;
11192
11352
  reject = rej;
11193
11353
  });
11194
- return { promise, resolve: resolve3, reject };
11354
+ return { promise, resolve: resolve4, reject };
11195
11355
  }
11196
11356
  };
11197
11357
  });
@@ -11229,7 +11389,7 @@ function createPrompt(view) {
11229
11389
  output
11230
11390
  });
11231
11391
  const screen = new ScreenManager(rl);
11232
- const { promise, resolve: resolve3, reject } = PromisePolyfill.withResolver();
11392
+ const { promise, resolve: resolve4, reject } = PromisePolyfill.withResolver();
11233
11393
  const cancel = () => reject(new CancelPromptError);
11234
11394
  if (signal) {
11235
11395
  const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
@@ -11257,7 +11417,7 @@ function createPrompt(view) {
11257
11417
  cycle(() => {
11258
11418
  try {
11259
11419
  const nextView = view(config, (value) => {
11260
- setImmediate(() => resolve3(value));
11420
+ setImmediate(() => resolve4(value));
11261
11421
  });
11262
11422
  if (nextView === undefined) {
11263
11423
  const callerFilename = callSites[1]?.getFileName();
@@ -17122,7 +17282,7 @@ var require_lib2 = __commonJS((exports) => {
17122
17282
  return matches;
17123
17283
  };
17124
17284
  exports.analyse = analyse;
17125
- var detectFile = (filepath, opts = {}) => new Promise((resolve3, reject) => {
17285
+ var detectFile = (filepath, opts = {}) => new Promise((resolve4, reject) => {
17126
17286
  let fd;
17127
17287
  const fs3 = (0, node_1.default)();
17128
17288
  const handler = (err, buffer) => {
@@ -17132,7 +17292,7 @@ var require_lib2 = __commonJS((exports) => {
17132
17292
  if (err) {
17133
17293
  reject(err);
17134
17294
  } else if (buffer) {
17135
- resolve3((0, exports.detect)(buffer));
17295
+ resolve4((0, exports.detect)(buffer));
17136
17296
  } else {
17137
17297
  reject(new Error("No error and no buffer received"));
17138
17298
  }
@@ -22259,11 +22419,15 @@ function formatPreflightWarnings(result) {
22259
22419
  `);
22260
22420
  }
22261
22421
 
22422
+ // src/cli/index.ts
22423
+ init_registry();
22424
+
22262
22425
  // src/core/self-update.ts
22426
+ init_package();
22263
22427
  import { execSync as execSync2, spawnSync } from "node:child_process";
22264
22428
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
22265
- import { homedir } from "node:os";
22266
- import { dirname, join } from "node:path";
22429
+ import { homedir as homedir2 } from "node:os";
22430
+ import { dirname, join as join2 } from "node:path";
22267
22431
  import { createInterface } from "node:readline/promises";
22268
22432
 
22269
22433
  // node_modules/.bun/i18next@26.0.2+8e24a2f921b8d7be/node_modules/i18next/dist/esm/i18next.js
@@ -22271,8 +22435,8 @@ var isString = (obj) => typeof obj === "string";
22271
22435
  var defer = () => {
22272
22436
  let res;
22273
22437
  let rej;
22274
- const promise = new Promise((resolve, reject) => {
22275
- res = resolve;
22438
+ const promise = new Promise((resolve2, reject) => {
22439
+ res = resolve2;
22276
22440
  rej = reject;
22277
22441
  });
22278
22442
  promise.resolve = res;
@@ -24828,6 +24992,8 @@ var en_default = {
24828
24992
  interactiveUpdating: "Updating selected projects...",
24829
24993
  projectLatestSuffix: "(latest)",
24830
24994
  newVersionAvailable: "[Info] oh-my-customcode v{{latest}} available (current: v{{current}}). Run 'npm i -g oh-my-customcode' to upgrade.",
24995
+ selfUpdateDone: "✓ oh-my-customcode updated ({{from}} → {{to}})",
24996
+ selfUpdateFailed: "⚠ Self-update check failed — continuing with external updates",
24831
24997
  rtkMissing: "[RTK] RTK is not installed. Attempting installation...",
24832
24998
  rtkInstalled: "[RTK] ✓ RTK installed — 60-90% token savings activated",
24833
24999
  codexMissing: "[Codex] Codex CLI is not installed. Attempting installation...",
@@ -25241,6 +25407,8 @@ var ko_default = {
25241
25407
  interactiveUpdating: "선택된 프로젝트 업데이트 중...",
25242
25408
  projectLatestSuffix: "(최신)",
25243
25409
  newVersionAvailable: "[정보] oh-my-customcode v{{latest}} 사용 가능 (현재: v{{current}}). 'npm i -g oh-my-customcode'를 실행하여 업그레이드하세요.",
25410
+ selfUpdateDone: "✓ oh-my-customcode 업데이트 완료 ({{from}} → {{to}})",
25411
+ selfUpdateFailed: "⚠ 자체 업데이트 확인 실패 — 외부 업데이트를 계속 진행합니다",
25244
25412
  rtkMissing: "[RTK] RTK가 설치되지 않았습니다. 설치를 시도합니다...",
25245
25413
  rtkInstalled: "[RTK] ✓ RTK 설치 완료 — 토큰 60-90% 절감 활성화",
25246
25414
  codexMissing: "[Codex] Codex CLI가 설치되지 않았습니다. 설치를 시도합니다...",
@@ -25551,7 +25719,7 @@ var i18n = {
25551
25719
  // src/core/self-update.ts
25552
25720
  var DEFAULT_PACKAGE_NAME = "oh-my-customcode";
25553
25721
  var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
25554
- var DEFAULT_CACHE_PATH = join(homedir(), ".oh-my-customcode", "self-update-cache.json");
25722
+ var DEFAULT_CACHE_PATH = join2(homedir2(), ".oh-my-customcode", "self-update-cache.json");
25555
25723
  function normalizeVersion(version) {
25556
25724
  return version.trim().replace(/^v/i, "").split("-")[0] || "";
25557
25725
  }
@@ -25688,6 +25856,70 @@ function runGlobalUpdate(packageName, latestVersion) {
25688
25856
  printContinueCurrentVersion();
25689
25857
  }
25690
25858
  }
25859
+ function shouldSkipEnvironmentUpdate(argv, env) {
25860
+ if (isNpxInvocation(argv, env))
25861
+ return true;
25862
+ if (env.CI === "true" || env.GITHUB_ACTIONS === "true")
25863
+ return true;
25864
+ if (env.OMCUSTOM_SKIP_SELF_UPDATE === "true")
25865
+ return true;
25866
+ return false;
25867
+ }
25868
+ function installGlobalPackage(packageName, version, silent) {
25869
+ try {
25870
+ execSync2(`npm install -g ${packageName}@${version}`, {
25871
+ stdio: silent ? "pipe" : "inherit",
25872
+ timeout: 60000
25873
+ });
25874
+ return true;
25875
+ } catch {
25876
+ return false;
25877
+ }
25878
+ }
25879
+ function executeSelfUpdate(options = {}) {
25880
+ const packageName = options.packageName || DEFAULT_PACKAGE_NAME;
25881
+ const argv = options.argv || process.argv;
25882
+ const env = options.env || process.env;
25883
+ const currentVersion = normalizeVersion(options.currentVersion || package_default.version || "");
25884
+ const noUpdate = {
25885
+ updated: false,
25886
+ fromVersion: currentVersion,
25887
+ toVersion: currentVersion
25888
+ };
25889
+ if (shouldSkipEnvironmentUpdate(argv, env)) {
25890
+ return noUpdate;
25891
+ }
25892
+ const checkOptions = {
25893
+ currentVersion,
25894
+ packageName,
25895
+ cachePath: options.cachePath,
25896
+ cacheTtlMs: options.cacheTtlMs,
25897
+ fetchLatestVersion: options.fetchLatestVersion,
25898
+ now: options.now,
25899
+ argv,
25900
+ env
25901
+ };
25902
+ const result = checkSelfUpdate(checkOptions);
25903
+ if (!result.checked || !result.updateAvailable || !result.latestVersion) {
25904
+ return noUpdate;
25905
+ }
25906
+ const latestVersion = result.latestVersion;
25907
+ if (!options.silent) {
25908
+ console.log(i18n.t("cli.selfUpdate.updatingGlobal", { version: latestVersion }));
25909
+ }
25910
+ const installed = installGlobalPackage(packageName, latestVersion, options.silent ?? false);
25911
+ if (installed) {
25912
+ if (!options.silent) {
25913
+ console.log(i18n.t("cli.selfUpdate.updated", { version: latestVersion }));
25914
+ printContinuationSpacing();
25915
+ }
25916
+ return { updated: true, fromVersion: currentVersion, toVersion: latestVersion };
25917
+ }
25918
+ if (!options.silent) {
25919
+ console.warn(i18n.t("cli.selfUpdate.failed", { error: "npm install failed" }));
25920
+ }
25921
+ return noUpdate;
25922
+ }
25691
25923
  function checkSelfUpdate(options) {
25692
25924
  const packageName = options.packageName || DEFAULT_PACKAGE_NAME;
25693
25925
  const cachePath = options.cachePath || DEFAULT_CACHE_PATH;
@@ -26145,7 +26377,7 @@ function installCodex(deps = defaultDeps) {
26145
26377
 
26146
26378
  // src/core/config.ts
26147
26379
  init_fs();
26148
- import { join as join3 } from "node:path";
26380
+ import { join as join4 } from "node:path";
26149
26381
  var CONFIG_FILE = ".omcustomrc.json";
26150
26382
  var CURRENT_CONFIG_VERSION = 1;
26151
26383
  function getDefaultConfig() {
@@ -26185,7 +26417,7 @@ function getDefaultPreferences() {
26185
26417
  };
26186
26418
  }
26187
26419
  function getConfigPath(targetDir) {
26188
- return join3(targetDir, CONFIG_FILE);
26420
+ return join4(targetDir, CONFIG_FILE);
26189
26421
  }
26190
26422
  async function loadConfig(targetDir) {
26191
26423
  const configPath = getConfigPath(targetDir);
@@ -26282,14 +26514,14 @@ function migrateConfig(config) {
26282
26514
 
26283
26515
  // src/core/doctor-framework.ts
26284
26516
  init_fs();
26285
- import { readFile } from "node:fs/promises";
26286
- import { join as join4 } from "node:path";
26517
+ import { readFile as readFile2 } from "node:fs/promises";
26518
+ import { join as join5 } from "node:path";
26287
26519
  async function getInstalledVersion(targetDir) {
26288
- const rcPath = join4(targetDir, ".omcustomrc.json");
26520
+ const rcPath = join5(targetDir, ".omcustomrc.json");
26289
26521
  if (!await fileExists(rcPath))
26290
26522
  return null;
26291
26523
  try {
26292
- const content = JSON.parse(await readFile(rcPath, "utf-8"));
26524
+ const content = JSON.parse(await readFile2(rcPath, "utf-8"));
26293
26525
  return content.version ?? null;
26294
26526
  } catch {
26295
26527
  return null;
@@ -26357,7 +26589,7 @@ init_fs();
26357
26589
  import { createHash } from "node:crypto";
26358
26590
  import { createReadStream } from "node:fs";
26359
26591
  import { readdir, stat } from "node:fs/promises";
26360
- import { join as join5, relative as relative2 } from "node:path";
26592
+ import { join as join6, relative as relative2 } from "node:path";
26361
26593
  var LOCKFILE_NAME = ".omcustom.lock.json";
26362
26594
  var LOCKFILE_VERSION = 1;
26363
26595
  var LOCKFILE_COMPONENTS = [
@@ -26371,7 +26603,7 @@ var LOCKFILE_COMPONENTS = [
26371
26603
  ];
26372
26604
  var COMPONENT_PATHS = LOCKFILE_COMPONENTS.map((component) => [getComponentPath(component), component]);
26373
26605
  function computeFileHash(filePath) {
26374
- return new Promise((resolve2, reject) => {
26606
+ return new Promise((resolve3, reject) => {
26375
26607
  const hash = createHash("sha256");
26376
26608
  const stream = createReadStream(filePath);
26377
26609
  stream.on("error", (err) => {
@@ -26381,12 +26613,12 @@ function computeFileHash(filePath) {
26381
26613
  hash.update(chunk);
26382
26614
  });
26383
26615
  stream.on("end", () => {
26384
- resolve2(hash.digest("hex"));
26616
+ resolve3(hash.digest("hex"));
26385
26617
  });
26386
26618
  });
26387
26619
  }
26388
26620
  async function readLockfile(targetDir) {
26389
- const lockfilePath = join5(targetDir, LOCKFILE_NAME);
26621
+ const lockfilePath = join6(targetDir, LOCKFILE_NAME);
26390
26622
  const exists2 = await fileExists(lockfilePath);
26391
26623
  if (!exists2) {
26392
26624
  debug("lockfile.not_found", { path: lockfilePath });
@@ -26410,7 +26642,7 @@ async function readLockfile(targetDir) {
26410
26642
  }
26411
26643
  }
26412
26644
  async function writeLockfile(targetDir, lockfile) {
26413
- const lockfilePath = join5(targetDir, LOCKFILE_NAME);
26645
+ const lockfilePath = join6(targetDir, LOCKFILE_NAME);
26414
26646
  await writeJsonFile(lockfilePath, lockfile);
26415
26647
  debug("lockfile.written", { path: lockfilePath });
26416
26648
  }
@@ -26435,7 +26667,7 @@ async function collectFiles(dir2, projectRoot, isTopLevel) {
26435
26667
  if (isTopLevel && entry.startsWith(".") && entry !== ".claude") {
26436
26668
  continue;
26437
26669
  }
26438
- const fullPath = join5(dir2, entry);
26670
+ const fullPath = join6(dir2, entry);
26439
26671
  let fileStat;
26440
26672
  try {
26441
26673
  fileStat = await stat(fullPath);
@@ -26453,7 +26685,7 @@ async function collectFiles(dir2, projectRoot, isTopLevel) {
26453
26685
  }
26454
26686
  async function generateLockfile(targetDir, generatorVersion, templateVersion) {
26455
26687
  const files = {};
26456
- const componentRoots = COMPONENT_PATHS.map(([prefix]) => join5(targetDir, prefix));
26688
+ const componentRoots = COMPONENT_PATHS.map(([prefix]) => join6(targetDir, prefix));
26457
26689
  for (const componentRoot of componentRoots) {
26458
26690
  const exists2 = await fileExists(componentRoot);
26459
26691
  if (!exists2) {
@@ -26493,8 +26725,8 @@ async function generateLockfile(targetDir, generatorVersion, templateVersion) {
26493
26725
  async function generateAndWriteLockfileForDir(targetDir) {
26494
26726
  try {
26495
26727
  const packageRoot = getPackageRoot();
26496
- const manifest = await readJsonFile(join5(packageRoot, "templates", "manifest.json"));
26497
- const { version: generatorVersion } = await readJsonFile(join5(packageRoot, "package.json"));
26728
+ const manifest = await readJsonFile(join6(packageRoot, "templates", "manifest.json"));
26729
+ const { version: generatorVersion } = await readJsonFile(join6(packageRoot, "package.json"));
26498
26730
  const lockfile = await generateLockfile(targetDir, generatorVersion, manifest.version);
26499
26731
  await writeLockfile(targetDir, lockfile);
26500
26732
  return { fileCount: Object.keys(lockfile.files).length };
@@ -27244,7 +27476,7 @@ async function doctorCommand(options = {}) {
27244
27476
 
27245
27477
  // src/cli/init.ts
27246
27478
  init_package();
27247
- import { join as join11 } from "node:path";
27479
+ import { join as join12 } from "node:path";
27248
27480
 
27249
27481
  // src/core/installer.ts
27250
27482
  init_fs();
@@ -27255,18 +27487,18 @@ import {
27255
27487
  rename,
27256
27488
  stat as stat2
27257
27489
  } from "node:fs/promises";
27258
- import { basename as basename2, join as join7 } from "node:path";
27490
+ import { basename as basename3, join as join8 } from "node:path";
27259
27491
 
27260
27492
  // src/core/file-preservation.ts
27261
27493
  init_fs();
27262
- import { basename, join as join6 } from "node:path";
27494
+ import { basename as basename2, join as join7 } from "node:path";
27263
27495
  var DEFAULT_CRITICAL_FILES = ["settings.json", "settings.local.json"];
27264
27496
  var DEFAULT_CRITICAL_DIRECTORIES = ["agent-memory", "agent-memory-local"];
27265
27497
  var PROTECTED_FRAMEWORK_FILES = ["CLAUDE.md", "AGENTS.md"];
27266
27498
  var PROTECTED_RULE_PATTERNS = ["rules/MUST-*.md"];
27267
27499
  function isProtectedFile(relativePath) {
27268
- const basename2 = relativePath.split("/").pop() ?? "";
27269
- if (PROTECTED_FRAMEWORK_FILES.includes(basename2)) {
27500
+ const basename3 = relativePath.split("/").pop() ?? "";
27501
+ if (PROTECTED_FRAMEWORK_FILES.includes(basename3)) {
27270
27502
  return true;
27271
27503
  }
27272
27504
  for (const pattern of PROTECTED_RULE_PATTERNS) {
@@ -27282,8 +27514,8 @@ function matchesGlobPattern(filePath, pattern) {
27282
27514
  return regex.test(filePath);
27283
27515
  }
27284
27516
  async function extractSingleFile(fileName, rootDir, tempDir, result) {
27285
- const srcPath = join6(rootDir, fileName);
27286
- const destPath = join6(tempDir, fileName);
27517
+ const srcPath = join7(rootDir, fileName);
27518
+ const destPath = join7(tempDir, fileName);
27287
27519
  try {
27288
27520
  if (await fileExists(srcPath)) {
27289
27521
  await copyFile(srcPath, destPath);
@@ -27297,8 +27529,8 @@ async function extractSingleFile(fileName, rootDir, tempDir, result) {
27297
27529
  }
27298
27530
  }
27299
27531
  async function extractSingleDir(dirName, rootDir, tempDir, result) {
27300
- const srcPath = join6(rootDir, dirName);
27301
- const destPath = join6(tempDir, dirName);
27532
+ const srcPath = join7(rootDir, dirName);
27533
+ const destPath = join7(tempDir, dirName);
27302
27534
  try {
27303
27535
  if (await fileExists(srcPath)) {
27304
27536
  await copyDirectory(srcPath, destPath, { overwrite: true, preserveTimestamps: true });
@@ -27335,8 +27567,8 @@ async function restoreCriticalFiles(rootDir, preservation) {
27335
27567
  failures: []
27336
27568
  };
27337
27569
  for (const fileName of preservation.extractedFiles) {
27338
- const preservedPath = join6(preservation.tempDir, fileName);
27339
- const targetPath = join6(rootDir, fileName);
27570
+ const preservedPath = join7(preservation.tempDir, fileName);
27571
+ const targetPath = join7(rootDir, fileName);
27340
27572
  try {
27341
27573
  if (fileName.endsWith(".json")) {
27342
27574
  await mergeJsonFile(preservedPath, targetPath);
@@ -27352,8 +27584,8 @@ async function restoreCriticalFiles(rootDir, preservation) {
27352
27584
  }
27353
27585
  }
27354
27586
  for (const dirName of preservation.extractedDirs) {
27355
- const preservedPath = join6(preservation.tempDir, dirName);
27356
- const targetPath = join6(rootDir, dirName);
27587
+ const preservedPath = join7(preservation.tempDir, dirName);
27588
+ const targetPath = join7(rootDir, dirName);
27357
27589
  try {
27358
27590
  await copyDirectory(preservedPath, targetPath, {
27359
27591
  overwrite: false,
@@ -27375,10 +27607,10 @@ async function mergeJsonFile(preservedPath, targetPath) {
27375
27607
  const targetData = await readJsonFile(targetPath);
27376
27608
  const merged = deepMerge(targetData, preservedData);
27377
27609
  await writeJsonFile(targetPath, merged);
27378
- debug("preserve.merged_json", { file: basename(targetPath) });
27610
+ debug("preserve.merged_json", { file: basename2(targetPath) });
27379
27611
  } else {
27380
27612
  await copyFile(preservedPath, targetPath);
27381
- debug("preserve.copied_json", { file: basename(targetPath) });
27613
+ debug("preserve.copied_json", { file: basename2(targetPath) });
27382
27614
  }
27383
27615
  }
27384
27616
  function deepMerge(target, source) {
@@ -27684,7 +27916,7 @@ function shouldInstallAgent(agentDomain, filterDomain) {
27684
27916
  var DEFAULT_LANGUAGE2 = "en";
27685
27917
  function getTemplateDir() {
27686
27918
  const packageRoot = getPackageRoot();
27687
- return join7(packageRoot, "templates");
27919
+ return join8(packageRoot, "templates");
27688
27920
  }
27689
27921
  function createInstallResult(targetDir) {
27690
27922
  return {
@@ -27706,7 +27938,7 @@ async function handleBackup(targetDir, shouldBackup, result) {
27706
27938
  if (!shouldBackup)
27707
27939
  return null;
27708
27940
  const layout = getProviderLayout();
27709
- const rootDir = join7(targetDir, layout.rootDir);
27941
+ const rootDir = join8(targetDir, layout.rootDir);
27710
27942
  let preservation = null;
27711
27943
  if (await fileExists(rootDir)) {
27712
27944
  const { createTempDir: createTempDir2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
@@ -27763,8 +27995,8 @@ async function installSingleComponent(targetDir, component, options, result) {
27763
27995
  }
27764
27996
  async function installStatusline(targetDir, options, _result) {
27765
27997
  const layout = getProviderLayout();
27766
- const srcPath = resolveTemplatePath(join7(layout.rootDir, "statusline.sh"));
27767
- const destPath = join7(targetDir, layout.rootDir, "statusline.sh");
27998
+ const srcPath = resolveTemplatePath(join8(layout.rootDir, "statusline.sh"));
27999
+ const destPath = join8(targetDir, layout.rootDir, "statusline.sh");
27768
28000
  if (!await fileExists(srcPath)) {
27769
28001
  debug("install.statusline_not_found", { path: srcPath });
27770
28002
  return;
@@ -27782,7 +28014,7 @@ async function installStatusline(targetDir, options, _result) {
27782
28014
  }
27783
28015
  async function installSettingsLocal(targetDir, result) {
27784
28016
  const layout = getProviderLayout();
27785
- const settingsPath = join7(targetDir, layout.rootDir, "settings.local.json");
28017
+ const settingsPath = join8(targetDir, layout.rootDir, "settings.local.json");
27786
28018
  const statusLineConfig = {
27787
28019
  statusLine: {
27788
28020
  type: "command",
@@ -27868,7 +28100,7 @@ async function install(options) {
27868
28100
  await installEntryDocWithTracking(options.targetDir, options, result);
27869
28101
  if (preservation) {
27870
28102
  const layout = getProviderLayout();
27871
- const rootDir = join7(options.targetDir, layout.rootDir);
28103
+ const rootDir = join8(options.targetDir, layout.rootDir);
27872
28104
  const restoration = await restoreCriticalFiles(rootDir, preservation);
27873
28105
  if (restoration.restoredFiles.length > 0 || restoration.restoredDirs.length > 0) {
27874
28106
  info("install.restored", {
@@ -27905,7 +28137,7 @@ async function install(options) {
27905
28137
  async function getTemplateManifest() {
27906
28138
  const packageRoot = getPackageRoot();
27907
28139
  const layout = getProviderLayout();
27908
- const manifestPath = join7(packageRoot, "templates", layout.manifestFile);
28140
+ const manifestPath = join8(packageRoot, "templates", layout.manifestFile);
27909
28141
  if (await fileExists(manifestPath)) {
27910
28142
  return readJsonFile(manifestPath);
27911
28143
  }
@@ -27928,10 +28160,10 @@ async function installSkillsWithScopeFilter(srcPath, destPath, options) {
27928
28160
  await ensureDirectory(destPath);
27929
28161
  const entries = await readdir2(srcPath);
27930
28162
  for (const entry of entries) {
27931
- const entrySrcPath = join7(srcPath, entry);
28163
+ const entrySrcPath = join8(srcPath, entry);
27932
28164
  if (!(await stat2(entrySrcPath)).isDirectory())
27933
28165
  continue;
27934
- const skillMdPath = join7(entrySrcPath, "SKILL.md");
28166
+ const skillMdPath = join8(entrySrcPath, "SKILL.md");
27935
28167
  if (await fileExists(skillMdPath)) {
27936
28168
  const content = await fsReadFile(skillMdPath, "utf-8");
27937
28169
  const scope = getSkillScope(content);
@@ -27940,7 +28172,7 @@ async function installSkillsWithScopeFilter(srcPath, destPath, options) {
27940
28172
  continue;
27941
28173
  }
27942
28174
  }
27943
- await copyDirectory(entrySrcPath, join7(destPath, entry), {
28175
+ await copyDirectory(entrySrcPath, join8(destPath, entry), {
27944
28176
  overwrite: !!(options.force || options.backup),
27945
28177
  preserveSymlinks: true,
27946
28178
  preserveTimestamps: true
@@ -27951,10 +28183,10 @@ async function installAgentsWithDomainFilter(srcPath, destPath, options) {
27951
28183
  await ensureDirectory(destPath);
27952
28184
  const entries = await readdir2(srcPath);
27953
28185
  for (const entry of entries) {
27954
- const entrySrcPath = join7(srcPath, entry);
28186
+ const entrySrcPath = join8(srcPath, entry);
27955
28187
  const entryStat = await stat2(entrySrcPath);
27956
28188
  if (entryStat.isDirectory()) {
27957
- await copyDirectory(entrySrcPath, join7(destPath, entry), {
28189
+ await copyDirectory(entrySrcPath, join8(destPath, entry), {
27958
28190
  overwrite: !!(options.force || options.backup),
27959
28191
  preserveSymlinks: true,
27960
28192
  preserveTimestamps: true
@@ -27971,7 +28203,7 @@ async function installAgentsWithDomainFilter(srcPath, destPath, options) {
27971
28203
  continue;
27972
28204
  }
27973
28205
  }
27974
- await copyFile(entrySrcPath, join7(destPath, entry));
28206
+ await copyFile(entrySrcPath, join8(destPath, entry));
27975
28207
  }
27976
28208
  }
27977
28209
  async function installComponent(targetDir, component, options) {
@@ -27979,7 +28211,7 @@ async function installComponent(targetDir, component, options) {
27979
28211
  return false;
27980
28212
  }
27981
28213
  const templatePath = getComponentPath(component);
27982
- const destPath = join7(targetDir, templatePath);
28214
+ const destPath = join8(targetDir, templatePath);
27983
28215
  const destExists = await fileExists(destPath);
27984
28216
  if (destExists && !options.force && !options.backup) {
27985
28217
  debug("install.component_skipped", { component });
@@ -28013,7 +28245,7 @@ async function installEntryDoc(targetDir, language, overwrite = false) {
28013
28245
  const layout = getProviderLayout();
28014
28246
  const templateFile = getEntryTemplateName(language);
28015
28247
  const srcPath = resolveTemplatePath(templateFile);
28016
- const destPath = join7(targetDir, layout.entryFile);
28248
+ const destPath = join8(targetDir, layout.entryFile);
28017
28249
  if (!await fileExists(srcPath)) {
28018
28250
  warn("install.entry_md_not_found", { language, path: srcPath, entry: layout.entryFile });
28019
28251
  return false;
@@ -28033,8 +28265,8 @@ async function installEntryDoc(targetDir, language, overwrite = false) {
28033
28265
  return true;
28034
28266
  }
28035
28267
  async function backupExisting(sourcePath, backupDir) {
28036
- const name = basename2(sourcePath);
28037
- const backupPath = join7(backupDir, name);
28268
+ const name = basename3(sourcePath);
28269
+ const backupPath = join8(backupDir, name);
28038
28270
  await rename(sourcePath, backupPath);
28039
28271
  return backupPath;
28040
28272
  }
@@ -28043,7 +28275,7 @@ async function checkExistingPaths(targetDir) {
28043
28275
  const pathsToCheck = [layout.entryFile, layout.rootDir, "guides"];
28044
28276
  const existingPaths = [];
28045
28277
  for (const relativePath of pathsToCheck) {
28046
- const fullPath = join7(targetDir, relativePath);
28278
+ const fullPath = join8(targetDir, relativePath);
28047
28279
  if (await fileExists(fullPath)) {
28048
28280
  existingPaths.push(relativePath);
28049
28281
  }
@@ -28057,11 +28289,11 @@ async function backupExistingInstallation(targetDir) {
28057
28289
  return [];
28058
28290
  }
28059
28291
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
28060
- const backupDir = join7(targetDir, `${layout.backupDirPrefix}${timestamp}`);
28292
+ const backupDir = join8(targetDir, `${layout.backupDirPrefix}${timestamp}`);
28061
28293
  await ensureDirectory(backupDir);
28062
28294
  const backedUpPaths = [];
28063
28295
  for (const relativePath of existingPaths) {
28064
- const fullPath = join7(targetDir, relativePath);
28296
+ const fullPath = join8(targetDir, relativePath);
28065
28297
  try {
28066
28298
  const backupPath = await backupExisting(fullPath, backupDir);
28067
28299
  backedUpPaths.push(backupPath);
@@ -28077,13 +28309,13 @@ async function backupExistingInstallation(targetDir) {
28077
28309
  // src/core/mcp-config.ts
28078
28310
  init_fs();
28079
28311
  import { execSync as execSync5 } from "node:child_process";
28080
- import { writeFile } from "node:fs/promises";
28081
- import { join as join8 } from "node:path";
28312
+ import { writeFile as writeFile2 } from "node:fs/promises";
28313
+ import { join as join9 } from "node:path";
28082
28314
  async function generateMCPConfig(targetDir) {
28083
28315
  const layout = getProviderLayout();
28084
- const mcpConfigPath = join8(targetDir, ".mcp.json");
28085
- const ontologyDir = join8(layout.rootDir, "ontology");
28086
- const ontologyExists = await fileExists(join8(targetDir, ontologyDir));
28316
+ const mcpConfigPath = join9(targetDir, ".mcp.json");
28317
+ const ontologyDir = join9(layout.rootDir, "ontology");
28318
+ const ontologyExists = await fileExists(join9(targetDir, ontologyDir));
28087
28319
  if (!ontologyExists) {
28088
28320
  return;
28089
28321
  }
@@ -28118,21 +28350,21 @@ async function generateMCPConfig(targetDir) {
28118
28350
  const existingConfigPath = mcpConfigPath;
28119
28351
  if (await fileExists(existingConfigPath)) {
28120
28352
  try {
28121
- const { readFile: readFile2 } = await import("node:fs/promises");
28122
- const existingContent = await readFile2(existingConfigPath, "utf-8");
28353
+ const { readFile: readFile3 } = await import("node:fs/promises");
28354
+ const existingContent = await readFile3(existingConfigPath, "utf-8");
28123
28355
  const existing = JSON.parse(existingContent);
28124
28356
  if (!existing.mcpServers?.["ontology-rag"]) {
28125
28357
  existing.mcpServers = existing.mcpServers || {};
28126
28358
  existing.mcpServers["ontology-rag"] = config.mcpServers["ontology-rag"];
28127
- await writeFile(mcpConfigPath, `${JSON.stringify(existing, null, 2)}
28359
+ await writeFile2(mcpConfigPath, `${JSON.stringify(existing, null, 2)}
28128
28360
  `);
28129
28361
  }
28130
28362
  } catch {
28131
- await writeFile(mcpConfigPath, `${JSON.stringify(config, null, 2)}
28363
+ await writeFile2(mcpConfigPath, `${JSON.stringify(config, null, 2)}
28132
28364
  `);
28133
28365
  }
28134
28366
  } else {
28135
- await writeFile(mcpConfigPath, `${JSON.stringify(config, null, 2)}
28367
+ await writeFile2(mcpConfigPath, `${JSON.stringify(config, null, 2)}
28136
28368
  `);
28137
28369
  }
28138
28370
  info("ontology-rag MCP server configured successfully");
@@ -28146,16 +28378,20 @@ async function checkUvAvailable() {
28146
28378
  }
28147
28379
  }
28148
28380
 
28381
+ // src/cli/init.ts
28382
+ init_registry();
28383
+
28149
28384
  // src/core/snapshot.ts
28150
28385
  init_package();
28151
28386
  init_projects();
28152
28387
  import { existsSync as existsSync2 } from "node:fs";
28153
28388
  import { copyFile as copyFile2, cp } from "node:fs/promises";
28154
- import { join as join10 } from "node:path";
28389
+ import { join as join11 } from "node:path";
28155
28390
  init_fs();
28391
+ init_registry();
28156
28392
  async function checkExistingInstallation(targetDir) {
28157
28393
  const layout = getProviderLayout();
28158
- const rootDir = join10(targetDir, layout.rootDir);
28394
+ const rootDir = join11(targetDir, layout.rootDir);
28159
28395
  return fileExists(rootDir);
28160
28396
  }
28161
28397
  async function installFromSnapshot(targetDir, snapshotPath, options) {
@@ -28167,7 +28403,7 @@ async function installFromSnapshot(targetDir, snapshotPath, options) {
28167
28403
  };
28168
28404
  }
28169
28405
  const layout = getProviderLayout();
28170
- const snapshotClaude = join10(snapshotPath, layout.rootDir);
28406
+ const snapshotClaude = join11(snapshotPath, layout.rootDir);
28171
28407
  if (!existsSync2(snapshotClaude)) {
28172
28408
  return {
28173
28409
  success: false,
@@ -28181,29 +28417,32 @@ async function installFromSnapshot(targetDir, snapshotPath, options) {
28181
28417
  if (exists2 && !options.force) {
28182
28418
  console.log(i18n.t("cli.init.exists", { rootDir: layout.rootDir }));
28183
28419
  console.log(i18n.t("cli.init.backing_up"));
28184
- const backupDir = join10(targetDir, `.claude-backup-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, -1)}`);
28185
- await cp(join10(targetDir, layout.rootDir), backupDir, { recursive: true });
28420
+ const backupDir = join11(targetDir, `.claude-backup-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, -1)}`);
28421
+ await cp(join11(targetDir, layout.rootDir), backupDir, { recursive: true });
28186
28422
  console.log(` Backed up to: ${backupDir}`);
28187
28423
  }
28188
- await cp(snapshotClaude, join10(targetDir, layout.rootDir), {
28424
+ await cp(snapshotClaude, join11(targetDir, layout.rootDir), {
28189
28425
  recursive: true,
28190
28426
  force: true
28191
28427
  });
28192
- const snapshotGuides = join10(snapshotPath, "guides");
28428
+ const snapshotGuides = join11(snapshotPath, "guides");
28193
28429
  if (existsSync2(snapshotGuides)) {
28194
- await cp(snapshotGuides, join10(targetDir, "guides"), {
28430
+ await cp(snapshotGuides, join11(targetDir, "guides"), {
28195
28431
  recursive: true,
28196
28432
  force: true
28197
28433
  });
28198
28434
  }
28199
- const snapshotEntry = join10(snapshotPath, layout.entryFile);
28435
+ const snapshotEntry = join11(snapshotPath, layout.entryFile);
28200
28436
  if (existsSync2(snapshotEntry)) {
28201
- await copyFile2(snapshotEntry, join10(targetDir, layout.entryFile));
28437
+ await copyFile2(snapshotEntry, join11(targetDir, layout.entryFile));
28202
28438
  }
28203
28439
  try {
28204
28440
  const existing = await readLockFile(targetDir);
28205
28441
  await writeLockFile(targetDir, package_default.version, existing);
28206
28442
  } catch {}
28443
+ try {
28444
+ await registerProject(targetDir, package_default.version);
28445
+ } catch {}
28207
28446
  console.log(i18n.t("cli.init.success"));
28208
28447
  console.log(`
28209
28448
  Installed from snapshot: ${snapshotPath}`);
@@ -29203,7 +29442,7 @@ async function runInitWizard(options) {
29203
29442
  // src/cli/init.ts
29204
29443
  async function checkExistingInstallation2(targetDir) {
29205
29444
  const layout = getProviderLayout();
29206
- const rootDir = join11(targetDir, layout.rootDir);
29445
+ const rootDir = join12(targetDir, layout.rootDir);
29207
29446
  return fileExists(rootDir);
29208
29447
  }
29209
29448
  var PROVIDER_SUBDIR_COMPONENTS = new Set([
@@ -29217,13 +29456,13 @@ var PROVIDER_SUBDIR_COMPONENTS = new Set([
29217
29456
  function componentToPath(targetDir, component) {
29218
29457
  if (component === "entry-md") {
29219
29458
  const layout = getProviderLayout();
29220
- return join11(targetDir, layout.entryFile);
29459
+ return join12(targetDir, layout.entryFile);
29221
29460
  }
29222
29461
  if (PROVIDER_SUBDIR_COMPONENTS.has(component)) {
29223
29462
  const layout = getProviderLayout();
29224
- return join11(targetDir, layout.rootDir, component);
29463
+ return join12(targetDir, layout.rootDir, component);
29225
29464
  }
29226
- return join11(targetDir, component);
29465
+ return join12(targetDir, component);
29227
29466
  }
29228
29467
  function buildInstalledPaths(targetDir, components) {
29229
29468
  return components.map((component) => componentToPath(targetDir, component));
@@ -29323,6 +29562,9 @@ async function initCommand(options) {
29323
29562
  const existing = await readLockFile(targetDir);
29324
29563
  await writeLockFile(targetDir, package_default.version, existing);
29325
29564
  } catch {}
29565
+ try {
29566
+ await registerProject(targetDir, package_default.version);
29567
+ } catch {}
29326
29568
  console.log("");
29327
29569
  console.log("Required plugins (install manually):");
29328
29570
  console.log(" /plugin marketplace add obra/superpowers-marketplace");
@@ -29345,7 +29587,7 @@ async function initCommand(options) {
29345
29587
  }
29346
29588
 
29347
29589
  // src/cli/list.ts
29348
- import { basename as basename4, dirname as dirname4, join as join12, relative as relative3 } from "node:path";
29590
+ import { basename as basename5, dirname as dirname3, join as join13, relative as relative3 } from "node:path";
29349
29591
  init_fs();
29350
29592
  var ALLOWED_TOP_LEVEL_KEYS = new Set(["name", "type", "description", "version", "category"]);
29351
29593
  function parseKeyValue(line) {
@@ -29392,7 +29634,7 @@ function parseYamlMetadata(content) {
29392
29634
  return result;
29393
29635
  }
29394
29636
  function extractAgentTypeFromFilename(filename) {
29395
- const name = basename4(filename, ".md");
29637
+ const name = basename5(filename, ".md");
29396
29638
  const prefixMap = {
29397
29639
  lang: "language",
29398
29640
  be: "backend",
@@ -29410,17 +29652,17 @@ function extractAgentTypeFromFilename(filename) {
29410
29652
  return prefixMap[prefix] || "unknown";
29411
29653
  }
29412
29654
  function extractSkillCategoryFromPath(skillPath, baseDir, rootDir) {
29413
- const relativePath = relative3(join12(baseDir, rootDir, "skills"), skillPath);
29655
+ const relativePath = relative3(join13(baseDir, rootDir, "skills"), skillPath);
29414
29656
  const parts = relativePath.split("/").filter(Boolean);
29415
29657
  return parts[0] || "unknown";
29416
29658
  }
29417
29659
  function extractGuideCategoryFromPath(guidePath, baseDir) {
29418
- const relativePath = relative3(join12(baseDir, "guides"), guidePath);
29660
+ const relativePath = relative3(join13(baseDir, "guides"), guidePath);
29419
29661
  const parts = relativePath.split("/").filter(Boolean);
29420
29662
  return parts[0] || "unknown";
29421
29663
  }
29422
29664
  function extractRulePriorityFromFilename(filename) {
29423
- const name = basename4(filename, ".md");
29665
+ const name = basename5(filename, ".md");
29424
29666
  const parts = name.split("-");
29425
29667
  return parts[0] || "unknown";
29426
29668
  }
@@ -29509,7 +29751,7 @@ async function tryExtractMarkdownDescription(mdPath, options = {}) {
29509
29751
  }
29510
29752
  }
29511
29753
  async function getAgents(targetDir, rootDir = ".claude", config) {
29512
- const agentsDir = join12(targetDir, rootDir, "agents");
29754
+ const agentsDir = join13(targetDir, rootDir, "agents");
29513
29755
  if (!await fileExists(agentsDir))
29514
29756
  return [];
29515
29757
  try {
@@ -29518,8 +29760,8 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
29518
29760
  const customAgentPaths = new Set(customComponents.filter((c) => c.type === "agent").map((c) => c.path));
29519
29761
  const agentMdFiles = await listFiles(agentsDir, { recursive: false, pattern: "*.md" });
29520
29762
  const agents = await Promise.all(agentMdFiles.map(async (agentMdPath) => {
29521
- const filename = basename4(agentMdPath);
29522
- const name = basename4(filename, ".md");
29763
+ const filename = basename5(agentMdPath);
29764
+ const name = basename5(filename, ".md");
29523
29765
  const description = await tryExtractMarkdownDescription(agentMdPath);
29524
29766
  const relativePath = relative3(targetDir, agentMdPath);
29525
29767
  return {
@@ -29537,7 +29779,7 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
29537
29779
  }
29538
29780
  }
29539
29781
  async function getSkills(targetDir, rootDir = ".claude", config) {
29540
- const skillsDir = join12(targetDir, rootDir, "skills");
29782
+ const skillsDir = join13(targetDir, rootDir, "skills");
29541
29783
  if (!await fileExists(skillsDir))
29542
29784
  return [];
29543
29785
  try {
@@ -29546,12 +29788,12 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
29546
29788
  const customSkillPaths = new Set(customComponents.filter((c) => c.type === "skill").map((c) => c.path));
29547
29789
  const skillMdFiles = await listFiles(skillsDir, { recursive: true, pattern: "SKILL.md" });
29548
29790
  const skills = await Promise.all(skillMdFiles.map(async (skillMdPath) => {
29549
- const skillDir = dirname4(skillMdPath);
29550
- const indexYamlPath = join12(skillDir, "index.yaml");
29791
+ const skillDir = dirname3(skillMdPath);
29792
+ const indexYamlPath = join13(skillDir, "index.yaml");
29551
29793
  const { description, version } = await tryReadIndexYamlMetadata(indexYamlPath);
29552
29794
  const relativePath = relative3(targetDir, skillDir);
29553
29795
  return {
29554
- name: basename4(skillDir),
29796
+ name: basename5(skillDir),
29555
29797
  type: "skill",
29556
29798
  category: extractSkillCategoryFromPath(skillDir, targetDir, rootDir),
29557
29799
  path: relativePath,
@@ -29566,7 +29808,7 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
29566
29808
  }
29567
29809
  }
29568
29810
  async function getGuides(targetDir, config) {
29569
- const guidesDir = join12(targetDir, "guides");
29811
+ const guidesDir = join13(targetDir, "guides");
29570
29812
  if (!await fileExists(guidesDir))
29571
29813
  return [];
29572
29814
  try {
@@ -29578,7 +29820,7 @@ async function getGuides(targetDir, config) {
29578
29820
  const description = await tryExtractMarkdownDescription(guideMdPath, { maxLength: 100 });
29579
29821
  const relativePath = relative3(targetDir, guideMdPath);
29580
29822
  return {
29581
- name: basename4(guideMdPath, ".md"),
29823
+ name: basename5(guideMdPath, ".md"),
29582
29824
  type: "guide",
29583
29825
  category: extractGuideCategoryFromPath(guideMdPath, targetDir),
29584
29826
  path: relativePath,
@@ -29593,7 +29835,7 @@ async function getGuides(targetDir, config) {
29593
29835
  }
29594
29836
  var RULE_PRIORITY_ORDER = { MUST: 0, SHOULD: 1, MAY: 2 };
29595
29837
  async function getRules(targetDir, rootDir = ".claude", config) {
29596
- const rulesDir = join12(targetDir, rootDir, "rules");
29838
+ const rulesDir = join13(targetDir, rootDir, "rules");
29597
29839
  if (!await fileExists(rulesDir))
29598
29840
  return [];
29599
29841
  try {
@@ -29602,13 +29844,13 @@ async function getRules(targetDir, rootDir = ".claude", config) {
29602
29844
  const customRulePaths = new Set(customComponents.filter((c) => c.type === "rule").map((c) => c.path));
29603
29845
  const ruleMdFiles = await listFiles(rulesDir, { recursive: false, pattern: "*.md" });
29604
29846
  const rules = await Promise.all(ruleMdFiles.map(async (ruleMdPath) => {
29605
- const filename = basename4(ruleMdPath);
29847
+ const filename = basename5(ruleMdPath);
29606
29848
  const description = await tryExtractMarkdownDescription(ruleMdPath, {
29607
29849
  cleanFormatting: true
29608
29850
  });
29609
29851
  const relativePath = relative3(targetDir, ruleMdPath);
29610
29852
  return {
29611
- name: basename4(ruleMdPath, ".md"),
29853
+ name: basename5(ruleMdPath, ".md"),
29612
29854
  type: extractRulePriorityFromFilename(filename),
29613
29855
  path: relativePath,
29614
29856
  description,
@@ -29665,7 +29907,7 @@ function formatAsJson(components) {
29665
29907
  console.log(JSON.stringify(components, null, 2));
29666
29908
  }
29667
29909
  async function getHooks(targetDir, rootDir = ".claude") {
29668
- const hooksDir = join12(targetDir, rootDir, "hooks");
29910
+ const hooksDir = join13(targetDir, rootDir, "hooks");
29669
29911
  if (!await fileExists(hooksDir))
29670
29912
  return [];
29671
29913
  try {
@@ -29674,7 +29916,7 @@ async function getHooks(targetDir, rootDir = ".claude") {
29674
29916
  const hookYamls = await listFiles(hooksDir, { recursive: true, pattern: "*.yaml" });
29675
29917
  const allFiles = [...hookFiles, ...hookConfigs, ...hookYamls];
29676
29918
  return allFiles.map((hookPath) => ({
29677
- name: basename4(hookPath),
29919
+ name: basename5(hookPath),
29678
29920
  type: "hook",
29679
29921
  path: relative3(targetDir, hookPath)
29680
29922
  })).sort((a, b) => a.name.localeCompare(b.name));
@@ -29683,7 +29925,7 @@ async function getHooks(targetDir, rootDir = ".claude") {
29683
29925
  }
29684
29926
  }
29685
29927
  async function getContexts(targetDir, rootDir = ".claude") {
29686
- const contextsDir = join12(targetDir, rootDir, "contexts");
29928
+ const contextsDir = join13(targetDir, rootDir, "contexts");
29687
29929
  if (!await fileExists(contextsDir))
29688
29930
  return [];
29689
29931
  try {
@@ -29694,7 +29936,7 @@ async function getContexts(targetDir, rootDir = ".claude") {
29694
29936
  const ext = ctxPath.endsWith(".md") ? ".md" : ".yaml";
29695
29937
  const description = ext === ".md" ? await tryExtractMarkdownDescription(ctxPath, { maxLength: 100 }) : undefined;
29696
29938
  return {
29697
- name: basename4(ctxPath, ext),
29939
+ name: basename5(ctxPath, ext),
29698
29940
  type: "context",
29699
29941
  path: relative3(targetDir, ctxPath),
29700
29942
  description
@@ -30076,29 +30318,29 @@ async function securityCommand(_options = {}) {
30076
30318
 
30077
30319
  // src/cli/serve-commands.ts
30078
30320
  import { spawnSync as spawnSync2 } from "node:child_process";
30079
- import { join as join14 } from "node:path";
30321
+ import { join as join15 } from "node:path";
30080
30322
 
30081
30323
  // src/cli/serve.ts
30082
30324
  import { spawn } from "node:child_process";
30083
30325
  import { existsSync as existsSync3 } from "node:fs";
30084
- import { readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
30085
- import { join as join13 } from "node:path";
30326
+ import { readFile as readFile3, unlink, writeFile as writeFile3 } from "node:fs/promises";
30327
+ import { join as join14 } from "node:path";
30086
30328
  var DEFAULT_PORT = 4321;
30087
- var PID_FILE = join13(process.env.HOME ?? "~", ".omcustom-serve.pid");
30329
+ var PID_FILE = join14(process.env.HOME ?? "~", ".omcustom-serve.pid");
30088
30330
  function findServeBuildDir(projectRoot, options) {
30089
- const localBuild = join13(projectRoot, "packages", "serve", "build");
30090
- if (existsSync3(join13(localBuild, "index.js")))
30331
+ const localBuild = join14(projectRoot, "packages", "serve", "build");
30332
+ if (existsSync3(join14(localBuild, "index.js")))
30091
30333
  return localBuild;
30092
30334
  if (options?.skipNpmFallback !== true) {
30093
- const npmBuild = join13(import.meta.dirname, "..", "..", "packages", "serve", "build");
30094
- if (existsSync3(join13(npmBuild, "index.js")))
30335
+ const npmBuild = join14(import.meta.dirname, "..", "..", "packages", "serve", "build");
30336
+ if (existsSync3(join14(npmBuild, "index.js")))
30095
30337
  return npmBuild;
30096
30338
  }
30097
30339
  return null;
30098
30340
  }
30099
30341
  async function isServeRunning() {
30100
30342
  try {
30101
- const raw = await readFile2(PID_FILE, "utf-8");
30343
+ const raw = await readFile3(PID_FILE, "utf-8");
30102
30344
  const pid = Number(raw.trim());
30103
30345
  if (!Number.isFinite(pid) || pid <= 0) {
30104
30346
  await cleanupPidFile();
@@ -30119,7 +30361,7 @@ async function startServeBackground(projectRoot, port = DEFAULT_PORT, buildDirOp
30119
30361
  if (buildDir === null) {
30120
30362
  return;
30121
30363
  }
30122
- const child = spawn("node", [join13(buildDir, "index.js")], {
30364
+ const child = spawn("node", [join14(buildDir, "index.js")], {
30123
30365
  env: {
30124
30366
  ...process.env,
30125
30367
  OMCUSTOM_PORT: String(port),
@@ -30132,12 +30374,12 @@ async function startServeBackground(projectRoot, port = DEFAULT_PORT, buildDirOp
30132
30374
  });
30133
30375
  child.unref();
30134
30376
  if (child.pid !== undefined) {
30135
- await writeFile2(PID_FILE, String(child.pid), "utf-8");
30377
+ await writeFile3(PID_FILE, String(child.pid), "utf-8");
30136
30378
  }
30137
30379
  }
30138
30380
  async function stopServe() {
30139
30381
  try {
30140
- const raw = await readFile2(PID_FILE, "utf-8");
30382
+ const raw = await readFile3(PID_FILE, "utf-8");
30141
30383
  const pid = Number(raw.trim());
30142
30384
  if (!Number.isFinite(pid) || pid <= 0) {
30143
30385
  await cleanupPidFile();
@@ -30196,7 +30438,7 @@ function runForeground(projectRoot, port, buildDirOpts) {
30196
30438
  process.exit(1);
30197
30439
  }
30198
30440
  console.log(`Web UI: http://localhost:${port}`);
30199
- spawnSync2("node", [join14(buildDir, "index.js")], {
30441
+ spawnSync2("node", [join15(buildDir, "index.js")], {
30200
30442
  env: {
30201
30443
  ...process.env,
30202
30444
  OMCUSTOM_PORT: String(port),
@@ -30209,18 +30451,18 @@ function runForeground(projectRoot, port, buildDirOpts) {
30209
30451
  }
30210
30452
 
30211
30453
  // src/cli/sync.ts
30212
- import { resolve as resolve2 } from "node:path";
30454
+ import { resolve as resolve3 } from "node:path";
30213
30455
 
30214
30456
  // src/core/sync.ts
30215
30457
  init_fs();
30216
30458
  import { existsSync as existsSync4 } from "node:fs";
30217
- import { cp as cp2, mkdir } from "node:fs/promises";
30218
- import { join as join15 } from "node:path";
30459
+ import { cp as cp2, mkdir as mkdir2 } from "node:fs/promises";
30460
+ import { join as join16 } from "node:path";
30219
30461
  async function loadVersions() {
30220
30462
  try {
30221
30463
  const packageRoot = getPackageRoot();
30222
- const manifest = await readJsonFile(join15(packageRoot, "templates", "manifest.json"));
30223
- const pkg = await readJsonFile(join15(packageRoot, "package.json"));
30464
+ const manifest = await readJsonFile(join16(packageRoot, "templates", "manifest.json"));
30465
+ const pkg = await readJsonFile(join16(packageRoot, "package.json"));
30224
30466
  return { generatorVersion: pkg.version, templateVersion: manifest.version };
30225
30467
  } catch {
30226
30468
  return { generatorVersion: "0.0.0", templateVersion: "0.0.0" };
@@ -30290,7 +30532,7 @@ async function countFiles(dir2) {
30290
30532
  return 0;
30291
30533
  }
30292
30534
  for (const entry of entries) {
30293
- const full = join15(current, entry);
30535
+ const full = join16(current, entry);
30294
30536
  try {
30295
30537
  const s = await stat3(full);
30296
30538
  if (s.isDirectory()) {
@@ -30305,19 +30547,19 @@ async function countFiles(dir2) {
30305
30547
  return walk(dir2);
30306
30548
  }
30307
30549
  async function exportSnapshot(targetDir, outputPath) {
30308
- const claudeDir = join15(targetDir, ".claude");
30309
- const guidesDir = join15(targetDir, "guides");
30550
+ const claudeDir = join16(targetDir, ".claude");
30551
+ const guidesDir = join16(targetDir, "guides");
30310
30552
  if (!existsSync4(claudeDir)) {
30311
30553
  return { success: false, exportPath: outputPath, fileCount: 0 };
30312
30554
  }
30313
- await mkdir(outputPath, { recursive: true });
30314
- const destClaude = join15(outputPath, ".claude");
30555
+ await mkdir2(outputPath, { recursive: true });
30556
+ const destClaude = join16(outputPath, ".claude");
30315
30557
  await cp2(claudeDir, destClaude, {
30316
30558
  recursive: true,
30317
30559
  filter: isExportable
30318
30560
  });
30319
30561
  if (existsSync4(guidesDir)) {
30320
- await cp2(guidesDir, join15(outputPath, "guides"), { recursive: true });
30562
+ await cp2(guidesDir, join16(outputPath, "guides"), { recursive: true });
30321
30563
  }
30322
30564
  const lockfile = await generateCurrentLockfile(targetDir);
30323
30565
  if (lockfile) {
@@ -30329,7 +30571,7 @@ async function exportSnapshot(targetDir, outputPath) {
30329
30571
 
30330
30572
  // src/cli/sync.ts
30331
30573
  async function runExport(targetDir, outputPath) {
30332
- const result = await exportSnapshot(targetDir, resolve2(outputPath));
30574
+ const result = await exportSnapshot(targetDir, resolve3(outputPath));
30333
30575
  if (!result.success) {
30334
30576
  console.error(`
30335
30577
  Export failed — no .claude/ directory found in current project.`);
@@ -30386,7 +30628,7 @@ Summary: ${result.unchanged} unchanged, ${result.modified.length} modified, ${re
30386
30628
  }
30387
30629
  function syncCommand(program2) {
30388
30630
  program2.command("sync").description(i18n.t("cli.sync.description")).option("--check", "Compare current state against lockfile (default behavior)").option("--reference <path>", "Compare against an external snapshot instead of the lockfile").option("--export <path>", "Export current .claude/ state as a reusable snapshot").action(async (options) => {
30389
- const targetDir = resolve2(".");
30631
+ const targetDir = resolve3(".");
30390
30632
  if (options.export) {
30391
30633
  await runExport(targetDir, options.export);
30392
30634
  return;
@@ -30400,7 +30642,7 @@ init_package();
30400
30642
 
30401
30643
  // src/core/updater.ts
30402
30644
  init_package();
30403
- import { join as join16 } from "node:path";
30645
+ import { join as join17 } from "node:path";
30404
30646
  init_fs();
30405
30647
 
30406
30648
  // src/core/entry-merger.ts
@@ -30655,7 +30897,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
30655
30897
  }
30656
30898
  async function updateEntryDoc(targetDir, config, options) {
30657
30899
  const layout = getProviderLayout();
30658
- const entryPath = join16(targetDir, layout.entryFile);
30900
+ const entryPath = join17(targetDir, layout.entryFile);
30659
30901
  const templateName = getEntryTemplateName2(config.language);
30660
30902
  const templatePath = resolveTemplatePath(templateName);
30661
30903
  if (!await fileExists(templatePath)) {
@@ -30734,6 +30976,36 @@ function checkAndInstallRtkAfterUpdate() {
30734
30976
  }
30735
30977
  }
30736
30978
  }
30979
+ async function updateProjectRegistry(targetDir, newVersion) {
30980
+ try {
30981
+ const { registerProject: registerProject2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
30982
+ await registerProject2(targetDir, newVersion);
30983
+ } catch {}
30984
+ }
30985
+ async function regenerateLockfile(targetDir, result) {
30986
+ const lockfileResult = await generateAndWriteLockfileForDir(targetDir);
30987
+ if (lockfileResult.warning) {
30988
+ result.warnings.push(lockfileResult.warning);
30989
+ warn("update.lockfile_failed", { error: lockfileResult.warning });
30990
+ } else {
30991
+ debug("update.lockfile_regenerated", {
30992
+ files: String(lockfileResult.fileCount)
30993
+ });
30994
+ }
30995
+ }
30996
+ async function shouldSkipSelfUpdate2(targetDir, result) {
30997
+ const targetPkgPath = join17(targetDir, "package.json");
30998
+ if (await fileExists(targetPkgPath)) {
30999
+ const targetPkg = await readJsonFile(targetPkgPath);
31000
+ if (targetPkg.name === "oh-my-customcode") {
31001
+ warn("update.self_update_skipped");
31002
+ result.success = true;
31003
+ result.warnings.push("Skipped: source project cannot update itself");
31004
+ return true;
31005
+ }
31006
+ }
31007
+ return false;
31008
+ }
30737
31009
  function checkAndInstallCodexAfterUpdate() {
30738
31010
  if (!isCodexInstalled()) {
30739
31011
  warn("update.codex_missing");
@@ -30756,15 +31028,8 @@ async function update(options) {
30756
31028
  result.error = `Downgrade prevented: project has v${result.previousVersion} but CLI is v${cliVersion}. Update the CLI first: npm install -g oh-my-customcode@latest`;
30757
31029
  return result;
30758
31030
  }
30759
- const targetPkgPath = join16(options.targetDir, "package.json");
30760
- if (await fileExists(targetPkgPath)) {
30761
- const targetPkg = await readJsonFile(targetPkgPath);
30762
- if (targetPkg.name === "oh-my-customcode") {
30763
- warn("update.self_update_skipped");
30764
- result.success = true;
30765
- result.warnings.push("Skipped: source project cannot update itself");
30766
- return result;
30767
- }
31031
+ if (await shouldSkipSelfUpdate2(options.targetDir, result)) {
31032
+ return result;
30768
31033
  }
30769
31034
  const updateCheck = await checkForUpdates(options.targetDir);
30770
31035
  result.newVersion = updateCheck.latestVersion;
@@ -30782,17 +31047,12 @@ async function update(options) {
30782
31047
  const components = options.components || getAllUpdateComponents();
30783
31048
  await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
30784
31049
  await runFullUpdatePostProcessing(options, result, config);
30785
- const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir);
30786
- if (lockfileResult.warning) {
30787
- result.warnings.push(lockfileResult.warning);
30788
- warn("update.lockfile_failed", { error: lockfileResult.warning });
30789
- } else {
30790
- debug("update.lockfile_regenerated", {
30791
- files: String(lockfileResult.fileCount)
30792
- });
30793
- }
31050
+ await regenerateLockfile(options.targetDir, result);
30794
31051
  checkAndInstallRtkAfterUpdate();
30795
31052
  checkAndInstallCodexAfterUpdate();
31053
+ if (result.success && !options.dryRun) {
31054
+ await updateProjectRegistry(options.targetDir, result.newVersion);
31055
+ }
30796
31056
  } catch (err) {
30797
31057
  const message = err instanceof Error ? err.message : String(err);
30798
31058
  result.error = message;
@@ -30872,11 +31132,11 @@ async function collectProtectedSkipPaths(srcPath, destPath, componentPath, force
30872
31132
  const warnedPaths = [];
30873
31133
  const updatedPaths = [];
30874
31134
  for (const p of protectedRelative) {
30875
- const targetFilePath = join16(targetDir, componentPath, p);
31135
+ const targetFilePath = join17(targetDir, componentPath, p);
30876
31136
  const lockfileKey = `${componentPath}/${p}`.replace(/\\/g, "/");
30877
31137
  const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
30878
31138
  if (shouldSkip) {
30879
- skipPaths.push(path3.relative(destPath, join16(destPath, p)));
31139
+ skipPaths.push(path3.relative(destPath, join17(destPath, p)));
30880
31140
  warnedPaths.push(p);
30881
31141
  } else {
30882
31142
  updatedPaths.push(p);
@@ -30922,7 +31182,7 @@ async function updateComponent(targetDir, component, customizations, options, co
30922
31182
  const preservedFiles = [];
30923
31183
  const componentPath = getComponentPath2(component);
30924
31184
  const srcPath = resolveTemplatePath(componentPath);
30925
- const destPath = join16(targetDir, componentPath);
31185
+ const destPath = join17(targetDir, componentPath);
30926
31186
  const customComponents = config.customComponents || [];
30927
31187
  const skipPaths = [];
30928
31188
  if (customizations && !options.forceOverwriteAll) {
@@ -30964,7 +31224,7 @@ async function updateComponent(targetDir, component, customizations, options, co
30964
31224
  }
30965
31225
  skipPaths.push(...protectedSkipPaths);
30966
31226
  const path3 = await import("node:path");
30967
- const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join16(targetDir, p)));
31227
+ const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join17(targetDir, p)));
30968
31228
  const uniqueSkipPaths = [...new Set(normalizedSkipPaths)];
30969
31229
  await copyDirectory(srcPath, destPath, {
30970
31230
  overwrite: true,
@@ -30986,12 +31246,12 @@ async function syncRootLevelFiles(targetDir, options) {
30986
31246
  const layout = getProviderLayout();
30987
31247
  const synced = [];
30988
31248
  for (const fileName of ROOT_LEVEL_FILES) {
30989
- const srcPath = resolveTemplatePath(join16(layout.rootDir, fileName));
31249
+ const srcPath = resolveTemplatePath(join17(layout.rootDir, fileName));
30990
31250
  if (!await fileExists(srcPath)) {
30991
31251
  continue;
30992
31252
  }
30993
- const destPath = join16(targetDir, layout.rootDir, fileName);
30994
- await ensureDirectory(join16(destPath, ".."));
31253
+ const destPath = join17(targetDir, layout.rootDir, fileName);
31254
+ await ensureDirectory(join17(destPath, ".."));
30995
31255
  await fs3.copyFile(srcPath, destPath);
30996
31256
  if (fileName.endsWith(".sh")) {
30997
31257
  await fs3.chmod(destPath, 493);
@@ -31026,7 +31286,7 @@ async function removeDeprecatedFiles(targetDir, options) {
31026
31286
  });
31027
31287
  continue;
31028
31288
  }
31029
- const fullPath = join16(targetDir, entry.path);
31289
+ const fullPath = join17(targetDir, entry.path);
31030
31290
  if (await fileExists(fullPath)) {
31031
31291
  await fs3.unlink(fullPath);
31032
31292
  removed.push(entry.path);
@@ -31067,7 +31327,7 @@ async function syncNamespaceInFile(targetFilePath, upstreamFilePath) {
31067
31327
  async function processNamespaceSyncEntry(entry, relPath, fullSrcPath, destPath, componentPath, lockfile) {
31068
31328
  if (!entry.isFile() || !entry.name.endsWith(".md"))
31069
31329
  return null;
31070
- const targetFilePath = join16(destPath, relPath);
31330
+ const targetFilePath = join17(destPath, relPath);
31071
31331
  const lockfileKey = `${componentPath}/${relPath}`.replace(/\\/g, "/");
31072
31332
  const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
31073
31333
  if (shouldSkip)
@@ -31082,7 +31342,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
31082
31342
  return [];
31083
31343
  const componentPath = getComponentPath2(component);
31084
31344
  const srcPath = resolveTemplatePath(componentPath);
31085
- const destPath = join16(targetDir, componentPath);
31345
+ const destPath = join17(targetDir, componentPath);
31086
31346
  const fs3 = await import("node:fs/promises");
31087
31347
  const synced = [];
31088
31348
  const queue = [{ dir: srcPath, relDir: "" }];
@@ -31096,7 +31356,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
31096
31356
  }
31097
31357
  for (const entry of entries) {
31098
31358
  const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
31099
- const fullSrcPath = join16(dir2, entry.name);
31359
+ const fullSrcPath = join17(dir2, entry.name);
31100
31360
  if (entry.isDirectory()) {
31101
31361
  queue.push({ dir: fullSrcPath, relDir: relPath });
31102
31362
  continue;
@@ -31119,26 +31379,26 @@ function getComponentPath2(component) {
31119
31379
  }
31120
31380
  async function backupInstallation(targetDir) {
31121
31381
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
31122
- const backupDir = join16(targetDir, `.omcustom-backup-${timestamp}`);
31382
+ const backupDir = join17(targetDir, `.omcustom-backup-${timestamp}`);
31123
31383
  const fs3 = await import("node:fs/promises");
31124
31384
  await ensureDirectory(backupDir);
31125
31385
  const layout = getProviderLayout();
31126
31386
  const dirsToBackup = [layout.rootDir, "guides"];
31127
31387
  for (const dir2 of dirsToBackup) {
31128
- const srcPath = join16(targetDir, dir2);
31388
+ const srcPath = join17(targetDir, dir2);
31129
31389
  if (await fileExists(srcPath)) {
31130
- const destPath = join16(backupDir, dir2);
31390
+ const destPath = join17(backupDir, dir2);
31131
31391
  await copyDirectory(srcPath, destPath, { overwrite: true });
31132
31392
  }
31133
31393
  }
31134
- const entryPath = join16(targetDir, layout.entryFile);
31394
+ const entryPath = join17(targetDir, layout.entryFile);
31135
31395
  if (await fileExists(entryPath)) {
31136
- await fs3.copyFile(entryPath, join16(backupDir, layout.entryFile));
31396
+ await fs3.copyFile(entryPath, join17(backupDir, layout.entryFile));
31137
31397
  }
31138
31398
  return backupDir;
31139
31399
  }
31140
31400
  async function loadCustomizationManifest(targetDir) {
31141
- const manifestPath = join16(targetDir, CUSTOMIZATION_MANIFEST_FILE);
31401
+ const manifestPath = join17(targetDir, CUSTOMIZATION_MANIFEST_FILE);
31142
31402
  if (await fileExists(manifestPath)) {
31143
31403
  return readJsonFile(manifestPath);
31144
31404
  }
@@ -31158,7 +31418,21 @@ async function checkCliVersion(checkFn) {
31158
31418
  } catch {}
31159
31419
  }
31160
31420
  async function updateCommand(options = {}, cliVersionCheck = checkSelfUpdate) {
31161
- await checkCliVersion(cliVersionCheck);
31421
+ if (!options.skipSelf) {
31422
+ try {
31423
+ const selfUpdateResult = executeSelfUpdate();
31424
+ if (selfUpdateResult.updated) {
31425
+ console.log(i18n.t("cli.update.selfUpdateDone", {
31426
+ from: selfUpdateResult.fromVersion,
31427
+ to: selfUpdateResult.toVersion
31428
+ }));
31429
+ }
31430
+ } catch {
31431
+ console.warn(i18n.t("cli.update.selfUpdateFailed"));
31432
+ }
31433
+ } else {
31434
+ await checkCliVersion(cliVersionCheck);
31435
+ }
31162
31436
  try {
31163
31437
  if (options.all) {
31164
31438
  await updateAllProjects(options);
@@ -31382,7 +31656,7 @@ function createProgram() {
31382
31656
  program2.command("init").description(i18n.t("cli.init.description")).option("-l, --lang <language>", i18n.t("cli.init.langOption")).option("--domain <domain>", "Install only agents/skills for specific domain (backend, frontend, data-engineering, devops)").option("--yes", "Skip interactive wizard, use defaults").option("--from-snapshot <path>", "Install from a pre-configured team snapshot directory").action(async (options) => {
31383
31657
  await initCommand(options);
31384
31658
  });
31385
- 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("--force-overwrite-all", i18n.t("cli.update.forceOverwriteAllOption")).option("--hard", i18n.t("cli.update.hardOption")).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("--all", i18n.t("cli.update.allOption")).action(async (options) => {
31659
+ 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("--force-overwrite-all", i18n.t("cli.update.forceOverwriteAllOption")).option("--hard", i18n.t("cli.update.hardOption")).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("--all", i18n.t("cli.update.allOption")).option("--skip-self", "Skip self-update of oh-my-customcode package").action(async (options) => {
31386
31660
  await updateCommand(options);
31387
31661
  });
31388
31662
  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").action(async (type, options) => {
@@ -31423,8 +31697,23 @@ function createProgram() {
31423
31697
  console.warn(i18n.t("cli.web.deprecated.serveStop"));
31424
31698
  await serveStopCommand();
31425
31699
  });
31426
- program2.command("projects").description("List all projects on this machine where oh-my-customcode is installed").option("-f, --format <format>", "Output format: table, json, or simple", "table").option("--path <dir>", "Additional search directory (can be specified multiple times)", (val, prev) => [...prev, val], []).action(async (options) => {
31427
- await projectsCommand({ format: options.format, paths: options.path });
31700
+ program2.command("projects").description("List all projects on this machine where oh-my-customcode is installed").option("-f, --format <format>", "Output format: table, json, or simple", "table").option("--path <dir>", "Additional search directory (can be specified multiple times)", (val, prev) => [...prev, val], []).option("--migrate", "Scan for existing projects with lock files and import them into the registry").action(async (options) => {
31701
+ await projectsCommand({
31702
+ format: options.format,
31703
+ paths: options.path,
31704
+ migrate: options.migrate
31705
+ });
31706
+ });
31707
+ program2.command("unregister [path]").description("Remove a project from the local registry").action(async (projectPath) => {
31708
+ const targetPath = projectPath ?? process.cwd();
31709
+ try {
31710
+ await unregisterProject(targetPath);
31711
+ console.log(` 프로젝트가 레지스트리에서 제거되었습니다: ${targetPath}`);
31712
+ } catch (error2) {
31713
+ const msg = error2 instanceof Error ? error2.message : String(error2);
31714
+ console.error(` 레지스트리 제거 실패: ${msg}`);
31715
+ process.exitCode = 1;
31716
+ }
31428
31717
  });
31429
31718
  program2.hook("preAction", async (thisCommand, actionCommand) => {
31430
31719
  const opts = thisCommand.optsWithGlobals();