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/index.js CHANGED
@@ -291,6 +291,140 @@ function resolvePath(...paths) {
291
291
  }
292
292
  var init_fs = () => {};
293
293
 
294
+ // src/core/registry.ts
295
+ var exports_registry = {};
296
+ __export(exports_registry, {
297
+ unregisterProject: () => unregisterProject,
298
+ registryToList: () => registryToList,
299
+ registerProject: () => registerProject,
300
+ readRegistry: () => readRegistry,
301
+ migrateFromLockfiles: () => migrateFromLockfiles,
302
+ _setRegistryDirForTesting: () => _setRegistryDirForTesting
303
+ });
304
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
305
+ import { homedir } from "node:os";
306
+ import { basename as basename3, join as join6, resolve as resolve2 } from "node:path";
307
+ function _setRegistryDirForTesting(dir2) {
308
+ _registryDirOverride = dir2;
309
+ }
310
+ function registryDir() {
311
+ if (_registryDirOverride !== undefined)
312
+ return _registryDirOverride;
313
+ const home = process.env.HOME ?? homedir();
314
+ return join6(home, ".oh-my-customcode");
315
+ }
316
+ function registryPath() {
317
+ return join6(registryDir(), "projects.json");
318
+ }
319
+ async function readRegistryRaw() {
320
+ try {
321
+ const content = await readFile(registryPath(), "utf-8");
322
+ const parsed = JSON.parse(content);
323
+ if (typeof parsed === "object" && parsed !== null && "projects" in parsed && typeof parsed.projects === "object" && parsed.projects !== null) {
324
+ return parsed;
325
+ }
326
+ return { ...EMPTY_REGISTRY };
327
+ } catch {
328
+ return { ...EMPTY_REGISTRY };
329
+ }
330
+ }
331
+ async function writeRegistry(registry) {
332
+ const dir2 = registryDir();
333
+ await mkdir(dir2, { recursive: true });
334
+ await writeFile(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
335
+ }
336
+ async function readRegistry() {
337
+ return readRegistryRaw();
338
+ }
339
+ async function registerProject(projectPath, version) {
340
+ const normalizedPath = resolve2(projectPath);
341
+ const registry = await readRegistryRaw();
342
+ const existing = registry.projects[normalizedPath];
343
+ const now = new Date().toISOString();
344
+ registry.projects[normalizedPath] = {
345
+ version,
346
+ installedAt: existing?.installedAt ?? now,
347
+ updatedAt: now
348
+ };
349
+ await writeRegistry(registry);
350
+ }
351
+ async function unregisterProject(projectPath) {
352
+ const normalizedPath = resolve2(projectPath);
353
+ const registry = await readRegistryRaw();
354
+ if (!(normalizedPath in registry.projects))
355
+ return;
356
+ delete registry.projects[normalizedPath];
357
+ await writeRegistry(registry);
358
+ }
359
+ async function parseLockFile(lockPath) {
360
+ try {
361
+ const content = await readFile(lockPath, "utf-8");
362
+ const lock = JSON.parse(content);
363
+ const version = typeof lock.version === "string" ? lock.version : typeof lock.templateVersion === "string" ? lock.templateVersion : "0.0.0";
364
+ const now = new Date().toISOString();
365
+ return {
366
+ version,
367
+ installedAt: typeof lock.installedAt === "string" ? lock.installedAt : now,
368
+ updatedAt: typeof lock.updatedAt === "string" ? lock.updatedAt : now
369
+ };
370
+ } catch {
371
+ return null;
372
+ }
373
+ }
374
+ async function migrateFromLockfiles(searchDirs) {
375
+ const { readdir: readdir3 } = await import("node:fs/promises");
376
+ const MAX_DEPTH = 3;
377
+ const discovered = new Map;
378
+ async function scan(dir2, depth) {
379
+ if (depth > MAX_DEPTH || discovered.has(dir2))
380
+ return;
381
+ let entries;
382
+ try {
383
+ entries = await readdir3(dir2, { withFileTypes: true });
384
+ } catch {
385
+ return;
386
+ }
387
+ const entry = await parseLockFile(join6(dir2, ".omcustom.lock.json"));
388
+ if (entry !== null) {
389
+ discovered.set(resolve2(dir2), entry);
390
+ return;
391
+ }
392
+ if (depth < MAX_DEPTH) {
393
+ const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SCAN_SKIP_DIRS.has(e.name));
394
+ await Promise.all(subdirs.map((sub) => scan(join6(dir2, sub.name), depth + 1)));
395
+ }
396
+ }
397
+ await Promise.all(searchDirs.map((dir2) => scan(dir2, 0).catch(() => {})));
398
+ if (discovered.size === 0)
399
+ return 0;
400
+ const registry = await readRegistryRaw();
401
+ let imported = 0;
402
+ for (const [projectPath, entry] of discovered) {
403
+ if (!(projectPath in registry.projects)) {
404
+ registry.projects[projectPath] = entry;
405
+ imported++;
406
+ }
407
+ }
408
+ if (imported > 0) {
409
+ await writeRegistry(registry);
410
+ }
411
+ return imported;
412
+ }
413
+ function registryToList(registry) {
414
+ return Object.entries(registry.projects).map(([path, entry]) => ({
415
+ name: basename3(path),
416
+ path,
417
+ version: entry.version,
418
+ installedAt: entry.installedAt,
419
+ updatedAt: entry.updatedAt
420
+ }));
421
+ }
422
+ var _registryDirOverride, EMPTY_REGISTRY, SCAN_SKIP_DIRS;
423
+ var init_registry = __esm(() => {
424
+ EMPTY_REGISTRY = { projects: {} };
425
+ SCAN_SKIP_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
426
+ });
427
+
294
428
  // src/core/config.ts
295
429
  init_fs();
296
430
  import { join as join2 } from "node:path";
@@ -1813,14 +1947,14 @@ async function detectProvider(_options = {}) {
1813
1947
  };
1814
1948
  }
1815
1949
  // src/core/updater.ts
1816
- import { join as join6 } from "node:path";
1950
+ import { join as join7 } from "node:path";
1817
1951
  // package.json
1818
1952
  var package_default = {
1819
1953
  name: "oh-my-customcode",
1820
1954
  workspaces: [
1821
1955
  "packages/*"
1822
1956
  ],
1823
- version: "0.79.0",
1957
+ version: "0.79.2",
1824
1958
  description: "Batteries-included agent harness for Claude Code",
1825
1959
  type: "module",
1826
1960
  bin: {
@@ -4586,7 +4720,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
4586
4720
  }
4587
4721
  async function updateEntryDoc(targetDir, config, options) {
4588
4722
  const layout = getProviderLayout();
4589
- const entryPath = join6(targetDir, layout.entryFile);
4723
+ const entryPath = join7(targetDir, layout.entryFile);
4590
4724
  const templateName = getEntryTemplateName2(config.language);
4591
4725
  const templatePath = resolveTemplatePath(templateName);
4592
4726
  if (!await fileExists(templatePath)) {
@@ -4665,6 +4799,36 @@ function checkAndInstallRtkAfterUpdate() {
4665
4799
  }
4666
4800
  }
4667
4801
  }
4802
+ async function updateProjectRegistry(targetDir, newVersion) {
4803
+ try {
4804
+ const { registerProject: registerProject2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
4805
+ await registerProject2(targetDir, newVersion);
4806
+ } catch {}
4807
+ }
4808
+ async function regenerateLockfile(targetDir, result) {
4809
+ const lockfileResult = await generateAndWriteLockfileForDir(targetDir);
4810
+ if (lockfileResult.warning) {
4811
+ result.warnings.push(lockfileResult.warning);
4812
+ warn("update.lockfile_failed", { error: lockfileResult.warning });
4813
+ } else {
4814
+ debug("update.lockfile_regenerated", {
4815
+ files: String(lockfileResult.fileCount)
4816
+ });
4817
+ }
4818
+ }
4819
+ async function shouldSkipSelfUpdate(targetDir, result) {
4820
+ const targetPkgPath = join7(targetDir, "package.json");
4821
+ if (await fileExists(targetPkgPath)) {
4822
+ const targetPkg = await readJsonFile(targetPkgPath);
4823
+ if (targetPkg.name === "oh-my-customcode") {
4824
+ warn("update.self_update_skipped");
4825
+ result.success = true;
4826
+ result.warnings.push("Skipped: source project cannot update itself");
4827
+ return true;
4828
+ }
4829
+ }
4830
+ return false;
4831
+ }
4668
4832
  function checkAndInstallCodexAfterUpdate() {
4669
4833
  if (!isCodexInstalled()) {
4670
4834
  warn("update.codex_missing");
@@ -4687,15 +4851,8 @@ async function update(options) {
4687
4851
  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`;
4688
4852
  return result;
4689
4853
  }
4690
- const targetPkgPath = join6(options.targetDir, "package.json");
4691
- if (await fileExists(targetPkgPath)) {
4692
- const targetPkg = await readJsonFile(targetPkgPath);
4693
- if (targetPkg.name === "oh-my-customcode") {
4694
- warn("update.self_update_skipped");
4695
- result.success = true;
4696
- result.warnings.push("Skipped: source project cannot update itself");
4697
- return result;
4698
- }
4854
+ if (await shouldSkipSelfUpdate(options.targetDir, result)) {
4855
+ return result;
4699
4856
  }
4700
4857
  const updateCheck = await checkForUpdates(options.targetDir);
4701
4858
  result.newVersion = updateCheck.latestVersion;
@@ -4713,17 +4870,12 @@ async function update(options) {
4713
4870
  const components = options.components || getAllUpdateComponents();
4714
4871
  await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
4715
4872
  await runFullUpdatePostProcessing(options, result, config);
4716
- const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir);
4717
- if (lockfileResult.warning) {
4718
- result.warnings.push(lockfileResult.warning);
4719
- warn("update.lockfile_failed", { error: lockfileResult.warning });
4720
- } else {
4721
- debug("update.lockfile_regenerated", {
4722
- files: String(lockfileResult.fileCount)
4723
- });
4724
- }
4873
+ await regenerateLockfile(options.targetDir, result);
4725
4874
  checkAndInstallRtkAfterUpdate();
4726
4875
  checkAndInstallCodexAfterUpdate();
4876
+ if (result.success && !options.dryRun) {
4877
+ await updateProjectRegistry(options.targetDir, result.newVersion);
4878
+ }
4727
4879
  } catch (err) {
4728
4880
  const message = err instanceof Error ? err.message : String(err);
4729
4881
  result.error = message;
@@ -4757,8 +4909,8 @@ async function checkForUpdates(targetDir) {
4757
4909
  async function applyUpdates(targetDir, updates) {
4758
4910
  const fs = await import("node:fs/promises");
4759
4911
  for (const update2 of updates) {
4760
- const fullPath = join6(targetDir, update2.path);
4761
- await ensureDirectory(join6(fullPath, ".."));
4912
+ const fullPath = join7(targetDir, update2.path);
4913
+ await ensureDirectory(join7(fullPath, ".."));
4762
4914
  await fs.writeFile(fullPath, update2.content, "utf-8");
4763
4915
  debug("update.file_applied", { path: update2.path });
4764
4916
  }
@@ -4767,7 +4919,7 @@ async function preserveCustomizations(targetDir, customizations) {
4767
4919
  const preserved = new Map;
4768
4920
  const fs = await import("node:fs/promises");
4769
4921
  for (const filePath of customizations) {
4770
- const fullPath = join6(targetDir, filePath);
4922
+ const fullPath = join7(targetDir, filePath);
4771
4923
  if (await fileExists(fullPath)) {
4772
4924
  const content = await fs.readFile(fullPath, "utf-8");
4773
4925
  preserved.set(filePath, content);
@@ -4824,11 +4976,11 @@ async function collectProtectedSkipPaths(srcPath, destPath, componentPath, force
4824
4976
  const warnedPaths = [];
4825
4977
  const updatedPaths = [];
4826
4978
  for (const p of protectedRelative) {
4827
- const targetFilePath = join6(targetDir, componentPath, p);
4979
+ const targetFilePath = join7(targetDir, componentPath, p);
4828
4980
  const lockfileKey = `${componentPath}/${p}`.replace(/\\/g, "/");
4829
4981
  const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
4830
4982
  if (shouldSkip) {
4831
- skipPaths.push(path.relative(destPath, join6(destPath, p)));
4983
+ skipPaths.push(path.relative(destPath, join7(destPath, p)));
4832
4984
  warnedPaths.push(p);
4833
4985
  } else {
4834
4986
  updatedPaths.push(p);
@@ -4874,7 +5026,7 @@ async function updateComponent(targetDir, component, customizations, options, co
4874
5026
  const preservedFiles = [];
4875
5027
  const componentPath = getComponentPath2(component);
4876
5028
  const srcPath = resolveTemplatePath(componentPath);
4877
- const destPath = join6(targetDir, componentPath);
5029
+ const destPath = join7(targetDir, componentPath);
4878
5030
  const customComponents = config.customComponents || [];
4879
5031
  const skipPaths = [];
4880
5032
  if (customizations && !options.forceOverwriteAll) {
@@ -4916,7 +5068,7 @@ async function updateComponent(targetDir, component, customizations, options, co
4916
5068
  }
4917
5069
  skipPaths.push(...protectedSkipPaths);
4918
5070
  const path = await import("node:path");
4919
- const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath, join6(targetDir, p)));
5071
+ const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath, join7(targetDir, p)));
4920
5072
  const uniqueSkipPaths = [...new Set(normalizedSkipPaths)];
4921
5073
  await copyDirectory(srcPath, destPath, {
4922
5074
  overwrite: true,
@@ -4938,12 +5090,12 @@ async function syncRootLevelFiles(targetDir, options) {
4938
5090
  const layout = getProviderLayout();
4939
5091
  const synced = [];
4940
5092
  for (const fileName of ROOT_LEVEL_FILES) {
4941
- const srcPath = resolveTemplatePath(join6(layout.rootDir, fileName));
5093
+ const srcPath = resolveTemplatePath(join7(layout.rootDir, fileName));
4942
5094
  if (!await fileExists(srcPath)) {
4943
5095
  continue;
4944
5096
  }
4945
- const destPath = join6(targetDir, layout.rootDir, fileName);
4946
- await ensureDirectory(join6(destPath, ".."));
5097
+ const destPath = join7(targetDir, layout.rootDir, fileName);
5098
+ await ensureDirectory(join7(destPath, ".."));
4947
5099
  await fs.copyFile(srcPath, destPath);
4948
5100
  if (fileName.endsWith(".sh")) {
4949
5101
  await fs.chmod(destPath, 493);
@@ -4978,7 +5130,7 @@ async function removeDeprecatedFiles(targetDir, options) {
4978
5130
  });
4979
5131
  continue;
4980
5132
  }
4981
- const fullPath = join6(targetDir, entry.path);
5133
+ const fullPath = join7(targetDir, entry.path);
4982
5134
  if (await fileExists(fullPath)) {
4983
5135
  await fs.unlink(fullPath);
4984
5136
  removed.push(entry.path);
@@ -5019,7 +5171,7 @@ async function syncNamespaceInFile(targetFilePath, upstreamFilePath) {
5019
5171
  async function processNamespaceSyncEntry(entry, relPath, fullSrcPath, destPath, componentPath, lockfile) {
5020
5172
  if (!entry.isFile() || !entry.name.endsWith(".md"))
5021
5173
  return null;
5022
- const targetFilePath = join6(destPath, relPath);
5174
+ const targetFilePath = join7(destPath, relPath);
5023
5175
  const lockfileKey = `${componentPath}/${relPath}`.replace(/\\/g, "/");
5024
5176
  const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
5025
5177
  if (shouldSkip)
@@ -5034,7 +5186,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
5034
5186
  return [];
5035
5187
  const componentPath = getComponentPath2(component);
5036
5188
  const srcPath = resolveTemplatePath(componentPath);
5037
- const destPath = join6(targetDir, componentPath);
5189
+ const destPath = join7(targetDir, componentPath);
5038
5190
  const fs = await import("node:fs/promises");
5039
5191
  const synced = [];
5040
5192
  const queue = [{ dir: srcPath, relDir: "" }];
@@ -5048,7 +5200,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
5048
5200
  }
5049
5201
  for (const entry of entries) {
5050
5202
  const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
5051
- const fullSrcPath = join6(dir2, entry.name);
5203
+ const fullSrcPath = join7(dir2, entry.name);
5052
5204
  if (entry.isDirectory()) {
5053
5205
  queue.push({ dir: fullSrcPath, relDir: relPath });
5054
5206
  continue;
@@ -5071,26 +5223,26 @@ function getComponentPath2(component) {
5071
5223
  }
5072
5224
  async function backupInstallation(targetDir) {
5073
5225
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
5074
- const backupDir = join6(targetDir, `.omcustom-backup-${timestamp}`);
5226
+ const backupDir = join7(targetDir, `.omcustom-backup-${timestamp}`);
5075
5227
  const fs = await import("node:fs/promises");
5076
5228
  await ensureDirectory(backupDir);
5077
5229
  const layout = getProviderLayout();
5078
5230
  const dirsToBackup = [layout.rootDir, "guides"];
5079
5231
  for (const dir2 of dirsToBackup) {
5080
- const srcPath = join6(targetDir, dir2);
5232
+ const srcPath = join7(targetDir, dir2);
5081
5233
  if (await fileExists(srcPath)) {
5082
- const destPath = join6(backupDir, dir2);
5234
+ const destPath = join7(backupDir, dir2);
5083
5235
  await copyDirectory(srcPath, destPath, { overwrite: true });
5084
5236
  }
5085
5237
  }
5086
- const entryPath = join6(targetDir, layout.entryFile);
5238
+ const entryPath = join7(targetDir, layout.entryFile);
5087
5239
  if (await fileExists(entryPath)) {
5088
- await fs.copyFile(entryPath, join6(backupDir, layout.entryFile));
5240
+ await fs.copyFile(entryPath, join7(backupDir, layout.entryFile));
5089
5241
  }
5090
5242
  return backupDir;
5091
5243
  }
5092
5244
  async function loadCustomizationManifest(targetDir) {
5093
- const manifestPath = join6(targetDir, CUSTOMIZATION_MANIFEST_FILE);
5245
+ const manifestPath = join7(targetDir, CUSTOMIZATION_MANIFEST_FILE);
5094
5246
  if (await fileExists(manifestPath)) {
5095
5247
  return readJsonFile(manifestPath);
5096
5248
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "0.79.0",
6
+ "version": "0.79.2",
7
7
  "description": "Batteries-included agent harness for Claude Code",
8
8
  "type": "module",
9
9
  "bin": {
@@ -16,7 +16,7 @@ context: fork
16
16
  | Frontend | fe-vercel-agent, fe-vuejs-agent, fe-svelte-agent, fe-flutter-agent, fe-design-expert |
17
17
  | Backend | be-fastapi-expert, be-springboot-expert, be-go-backend-expert, be-nestjs-expert, be-express-expert, be-django-expert |
18
18
  | Tooling | tool-npm-expert, tool-optimizer, tool-bun-expert |
19
- | Database | db-supabase-expert, db-postgres-expert, db-redis-expert |
19
+ | Database | db-supabase-expert, db-postgres-expert, db-redis-expert, db-alembic-expert |
20
20
  | Architect | arch-documenter, arch-speckit-agent |
21
21
  | Security | sec-codeql-expert |
22
22
  | Infra | infra-docker-expert, infra-aws-expert |
@@ -37,6 +37,7 @@ context: fork
37
37
  | `.dart`, `pubspec.yaml` | fe-flutter-agent |
38
38
  | `.sql` (PG) | db-postgres-expert |
39
39
  | `.sql` (Supabase) | db-supabase-expert |
40
+ | `alembic.ini`, `alembic/versions/*.py` | db-alembic-expert |
40
41
  | `Dockerfile`, `*.dockerfile` | infra-docker-expert |
41
42
  | `*.tf`, `*.tfvars` | infra-aws-expert |
42
43
  | `*.yaml`, `*.yml` (CloudFormation) | infra-aws-expert |
@@ -67,6 +68,7 @@ context: fork
67
68
  | postgres, postgresql, psql, pg_stat | db-postgres-expert |
68
69
  | redis, cache, pub/sub, sorted set | db-redis-expert |
69
70
  | supabase, rls, edge function | db-supabase-expert |
71
+ | alembic, migration, db revision, db upgrade, db downgrade | db-alembic-expert |
70
72
  | docker, dockerfile, container, compose | infra-docker-expert |
71
73
  | aws, cloudformation, vpc, iam, s3, lambda, cdk, terraform | infra-aws-expert |
72
74
  | security, codeql, cve, vulnerability, sarif, sast, security audit | sec-codeql-expert |
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.79.0",
2
+ "version": "0.79.2",
3
3
  "lastUpdated": "2026-03-24T00:00:00.000Z",
4
4
  "components": [
5
5
  {