oh-my-customcode 0.79.1 → 0.79.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -291,6 +291,159 @@ 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
+ cleanRegistry: () => cleanRegistry,
303
+ _setRegistryDirForTesting: () => _setRegistryDirForTesting
304
+ });
305
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
306
+ import { homedir } from "node:os";
307
+ import { basename as basename3, join as join6, resolve as resolve2 } from "node:path";
308
+ function _setRegistryDirForTesting(dir2) {
309
+ _registryDirOverride = dir2;
310
+ }
311
+ function registryDir() {
312
+ if (_registryDirOverride !== undefined)
313
+ return _registryDirOverride;
314
+ const home = process.env.HOME ?? homedir();
315
+ return join6(home, ".oh-my-customcode");
316
+ }
317
+ function registryPath() {
318
+ return join6(registryDir(), "projects.json");
319
+ }
320
+ async function readRegistryRaw() {
321
+ try {
322
+ const content = await readFile(registryPath(), "utf-8");
323
+ const parsed = JSON.parse(content);
324
+ if (typeof parsed === "object" && parsed !== null && "projects" in parsed && typeof parsed.projects === "object" && parsed.projects !== null) {
325
+ return parsed;
326
+ }
327
+ return { ...EMPTY_REGISTRY };
328
+ } catch {
329
+ return { ...EMPTY_REGISTRY };
330
+ }
331
+ }
332
+ async function writeRegistry(registry) {
333
+ const dir2 = registryDir();
334
+ await mkdir(dir2, { recursive: true });
335
+ await writeFile(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
336
+ }
337
+ async function readRegistry() {
338
+ return readRegistryRaw();
339
+ }
340
+ async function registerProject(projectPath, version) {
341
+ const normalizedPath = resolve2(projectPath);
342
+ const registry = await readRegistryRaw();
343
+ const existing = registry.projects[normalizedPath];
344
+ const now = new Date().toISOString();
345
+ registry.projects[normalizedPath] = {
346
+ version,
347
+ installedAt: existing?.installedAt ?? now,
348
+ updatedAt: now
349
+ };
350
+ await writeRegistry(registry);
351
+ }
352
+ async function unregisterProject(projectPath) {
353
+ const normalizedPath = resolve2(projectPath);
354
+ const registry = await readRegistryRaw();
355
+ if (!(normalizedPath in registry.projects))
356
+ return;
357
+ delete registry.projects[normalizedPath];
358
+ await writeRegistry(registry);
359
+ }
360
+ async function cleanRegistry() {
361
+ const { access: fsAccess } = await import("node:fs/promises");
362
+ const registry = await readRegistryRaw();
363
+ const paths = Object.keys(registry.projects);
364
+ let removed = 0;
365
+ for (const projectPath of paths) {
366
+ try {
367
+ await fsAccess(projectPath);
368
+ } catch {
369
+ delete registry.projects[projectPath];
370
+ removed++;
371
+ }
372
+ }
373
+ if (removed > 0) {
374
+ await writeRegistry(registry);
375
+ }
376
+ return removed;
377
+ }
378
+ async function parseLockFile(lockPath) {
379
+ try {
380
+ const content = await readFile(lockPath, "utf-8");
381
+ const lock = JSON.parse(content);
382
+ const version = typeof lock.version === "string" ? lock.version : typeof lock.templateVersion === "string" ? lock.templateVersion : "0.0.0";
383
+ const now = new Date().toISOString();
384
+ return {
385
+ version,
386
+ installedAt: typeof lock.installedAt === "string" ? lock.installedAt : now,
387
+ updatedAt: typeof lock.updatedAt === "string" ? lock.updatedAt : now
388
+ };
389
+ } catch {
390
+ return null;
391
+ }
392
+ }
393
+ async function migrateFromLockfiles(searchDirs) {
394
+ const { readdir: readdir3 } = await import("node:fs/promises");
395
+ const MAX_DEPTH = 3;
396
+ const discovered = new Map;
397
+ async function scan(dir2, depth) {
398
+ if (depth > MAX_DEPTH || discovered.has(dir2))
399
+ return;
400
+ let entries;
401
+ try {
402
+ entries = await readdir3(dir2, { withFileTypes: true });
403
+ } catch {
404
+ return;
405
+ }
406
+ const entry = await parseLockFile(join6(dir2, ".omcustom.lock.json"));
407
+ if (entry !== null) {
408
+ discovered.set(resolve2(dir2), entry);
409
+ return;
410
+ }
411
+ if (depth < MAX_DEPTH) {
412
+ const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SCAN_SKIP_DIRS.has(e.name));
413
+ await Promise.all(subdirs.map((sub) => scan(join6(dir2, sub.name), depth + 1)));
414
+ }
415
+ }
416
+ await Promise.all(searchDirs.map((dir2) => scan(dir2, 0).catch(() => {})));
417
+ if (discovered.size === 0)
418
+ return 0;
419
+ const registry = await readRegistryRaw();
420
+ let imported = 0;
421
+ for (const [projectPath, entry] of discovered) {
422
+ if (!(projectPath in registry.projects)) {
423
+ registry.projects[projectPath] = entry;
424
+ imported++;
425
+ }
426
+ }
427
+ if (imported > 0) {
428
+ await writeRegistry(registry);
429
+ }
430
+ return imported;
431
+ }
432
+ function registryToList(registry) {
433
+ return Object.entries(registry.projects).map(([path, entry]) => ({
434
+ name: basename3(path),
435
+ path,
436
+ version: entry.version,
437
+ installedAt: entry.installedAt,
438
+ updatedAt: entry.updatedAt
439
+ }));
440
+ }
441
+ var _registryDirOverride, EMPTY_REGISTRY, SCAN_SKIP_DIRS;
442
+ var init_registry = __esm(() => {
443
+ EMPTY_REGISTRY = { projects: {} };
444
+ SCAN_SKIP_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
445
+ });
446
+
294
447
  // src/core/config.ts
295
448
  init_fs();
296
449
  import { join as join2 } from "node:path";
@@ -1813,14 +1966,14 @@ async function detectProvider(_options = {}) {
1813
1966
  };
1814
1967
  }
1815
1968
  // src/core/updater.ts
1816
- import { join as join6 } from "node:path";
1969
+ import { join as join7 } from "node:path";
1817
1970
  // package.json
1818
1971
  var package_default = {
1819
1972
  name: "oh-my-customcode",
1820
1973
  workspaces: [
1821
1974
  "packages/*"
1822
1975
  ],
1823
- version: "0.79.1",
1976
+ version: "0.79.3",
1824
1977
  description: "Batteries-included agent harness for Claude Code",
1825
1978
  type: "module",
1826
1979
  bin: {
@@ -4586,7 +4739,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
4586
4739
  }
4587
4740
  async function updateEntryDoc(targetDir, config, options) {
4588
4741
  const layout = getProviderLayout();
4589
- const entryPath = join6(targetDir, layout.entryFile);
4742
+ const entryPath = join7(targetDir, layout.entryFile);
4590
4743
  const templateName = getEntryTemplateName2(config.language);
4591
4744
  const templatePath = resolveTemplatePath(templateName);
4592
4745
  if (!await fileExists(templatePath)) {
@@ -4665,6 +4818,36 @@ function checkAndInstallRtkAfterUpdate() {
4665
4818
  }
4666
4819
  }
4667
4820
  }
4821
+ async function updateProjectRegistry(targetDir, newVersion) {
4822
+ try {
4823
+ const { registerProject: registerProject2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
4824
+ await registerProject2(targetDir, newVersion);
4825
+ } catch {}
4826
+ }
4827
+ async function regenerateLockfile(targetDir, result) {
4828
+ const lockfileResult = await generateAndWriteLockfileForDir(targetDir);
4829
+ if (lockfileResult.warning) {
4830
+ result.warnings.push(lockfileResult.warning);
4831
+ warn("update.lockfile_failed", { error: lockfileResult.warning });
4832
+ } else {
4833
+ debug("update.lockfile_regenerated", {
4834
+ files: String(lockfileResult.fileCount)
4835
+ });
4836
+ }
4837
+ }
4838
+ async function shouldSkipSelfUpdate(targetDir, result) {
4839
+ const targetPkgPath = join7(targetDir, "package.json");
4840
+ if (await fileExists(targetPkgPath)) {
4841
+ const targetPkg = await readJsonFile(targetPkgPath);
4842
+ if (targetPkg.name === "oh-my-customcode") {
4843
+ warn("update.self_update_skipped");
4844
+ result.success = true;
4845
+ result.warnings.push("Skipped: source project cannot update itself");
4846
+ return true;
4847
+ }
4848
+ }
4849
+ return false;
4850
+ }
4668
4851
  function checkAndInstallCodexAfterUpdate() {
4669
4852
  if (!isCodexInstalled()) {
4670
4853
  warn("update.codex_missing");
@@ -4687,15 +4870,8 @@ async function update(options) {
4687
4870
  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
4871
  return result;
4689
4872
  }
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
- }
4873
+ if (await shouldSkipSelfUpdate(options.targetDir, result)) {
4874
+ return result;
4699
4875
  }
4700
4876
  const updateCheck = await checkForUpdates(options.targetDir);
4701
4877
  result.newVersion = updateCheck.latestVersion;
@@ -4713,17 +4889,12 @@ async function update(options) {
4713
4889
  const components = options.components || getAllUpdateComponents();
4714
4890
  await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
4715
4891
  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
- }
4892
+ await regenerateLockfile(options.targetDir, result);
4725
4893
  checkAndInstallRtkAfterUpdate();
4726
4894
  checkAndInstallCodexAfterUpdate();
4895
+ if (result.success && !options.dryRun) {
4896
+ await updateProjectRegistry(options.targetDir, result.newVersion);
4897
+ }
4727
4898
  } catch (err) {
4728
4899
  const message = err instanceof Error ? err.message : String(err);
4729
4900
  result.error = message;
@@ -4757,8 +4928,8 @@ async function checkForUpdates(targetDir) {
4757
4928
  async function applyUpdates(targetDir, updates) {
4758
4929
  const fs = await import("node:fs/promises");
4759
4930
  for (const update2 of updates) {
4760
- const fullPath = join6(targetDir, update2.path);
4761
- await ensureDirectory(join6(fullPath, ".."));
4931
+ const fullPath = join7(targetDir, update2.path);
4932
+ await ensureDirectory(join7(fullPath, ".."));
4762
4933
  await fs.writeFile(fullPath, update2.content, "utf-8");
4763
4934
  debug("update.file_applied", { path: update2.path });
4764
4935
  }
@@ -4767,7 +4938,7 @@ async function preserveCustomizations(targetDir, customizations) {
4767
4938
  const preserved = new Map;
4768
4939
  const fs = await import("node:fs/promises");
4769
4940
  for (const filePath of customizations) {
4770
- const fullPath = join6(targetDir, filePath);
4941
+ const fullPath = join7(targetDir, filePath);
4771
4942
  if (await fileExists(fullPath)) {
4772
4943
  const content = await fs.readFile(fullPath, "utf-8");
4773
4944
  preserved.set(filePath, content);
@@ -4824,11 +4995,11 @@ async function collectProtectedSkipPaths(srcPath, destPath, componentPath, force
4824
4995
  const warnedPaths = [];
4825
4996
  const updatedPaths = [];
4826
4997
  for (const p of protectedRelative) {
4827
- const targetFilePath = join6(targetDir, componentPath, p);
4998
+ const targetFilePath = join7(targetDir, componentPath, p);
4828
4999
  const lockfileKey = `${componentPath}/${p}`.replace(/\\/g, "/");
4829
5000
  const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
4830
5001
  if (shouldSkip) {
4831
- skipPaths.push(path.relative(destPath, join6(destPath, p)));
5002
+ skipPaths.push(path.relative(destPath, join7(destPath, p)));
4832
5003
  warnedPaths.push(p);
4833
5004
  } else {
4834
5005
  updatedPaths.push(p);
@@ -4874,7 +5045,7 @@ async function updateComponent(targetDir, component, customizations, options, co
4874
5045
  const preservedFiles = [];
4875
5046
  const componentPath = getComponentPath2(component);
4876
5047
  const srcPath = resolveTemplatePath(componentPath);
4877
- const destPath = join6(targetDir, componentPath);
5048
+ const destPath = join7(targetDir, componentPath);
4878
5049
  const customComponents = config.customComponents || [];
4879
5050
  const skipPaths = [];
4880
5051
  if (customizations && !options.forceOverwriteAll) {
@@ -4916,7 +5087,7 @@ async function updateComponent(targetDir, component, customizations, options, co
4916
5087
  }
4917
5088
  skipPaths.push(...protectedSkipPaths);
4918
5089
  const path = await import("node:path");
4919
- const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath, join6(targetDir, p)));
5090
+ const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath, join7(targetDir, p)));
4920
5091
  const uniqueSkipPaths = [...new Set(normalizedSkipPaths)];
4921
5092
  await copyDirectory(srcPath, destPath, {
4922
5093
  overwrite: true,
@@ -4938,12 +5109,12 @@ async function syncRootLevelFiles(targetDir, options) {
4938
5109
  const layout = getProviderLayout();
4939
5110
  const synced = [];
4940
5111
  for (const fileName of ROOT_LEVEL_FILES) {
4941
- const srcPath = resolveTemplatePath(join6(layout.rootDir, fileName));
5112
+ const srcPath = resolveTemplatePath(join7(layout.rootDir, fileName));
4942
5113
  if (!await fileExists(srcPath)) {
4943
5114
  continue;
4944
5115
  }
4945
- const destPath = join6(targetDir, layout.rootDir, fileName);
4946
- await ensureDirectory(join6(destPath, ".."));
5116
+ const destPath = join7(targetDir, layout.rootDir, fileName);
5117
+ await ensureDirectory(join7(destPath, ".."));
4947
5118
  await fs.copyFile(srcPath, destPath);
4948
5119
  if (fileName.endsWith(".sh")) {
4949
5120
  await fs.chmod(destPath, 493);
@@ -4978,7 +5149,7 @@ async function removeDeprecatedFiles(targetDir, options) {
4978
5149
  });
4979
5150
  continue;
4980
5151
  }
4981
- const fullPath = join6(targetDir, entry.path);
5152
+ const fullPath = join7(targetDir, entry.path);
4982
5153
  if (await fileExists(fullPath)) {
4983
5154
  await fs.unlink(fullPath);
4984
5155
  removed.push(entry.path);
@@ -5019,7 +5190,7 @@ async function syncNamespaceInFile(targetFilePath, upstreamFilePath) {
5019
5190
  async function processNamespaceSyncEntry(entry, relPath, fullSrcPath, destPath, componentPath, lockfile) {
5020
5191
  if (!entry.isFile() || !entry.name.endsWith(".md"))
5021
5192
  return null;
5022
- const targetFilePath = join6(destPath, relPath);
5193
+ const targetFilePath = join7(destPath, relPath);
5023
5194
  const lockfileKey = `${componentPath}/${relPath}`.replace(/\\/g, "/");
5024
5195
  const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
5025
5196
  if (shouldSkip)
@@ -5034,7 +5205,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
5034
5205
  return [];
5035
5206
  const componentPath = getComponentPath2(component);
5036
5207
  const srcPath = resolveTemplatePath(componentPath);
5037
- const destPath = join6(targetDir, componentPath);
5208
+ const destPath = join7(targetDir, componentPath);
5038
5209
  const fs = await import("node:fs/promises");
5039
5210
  const synced = [];
5040
5211
  const queue = [{ dir: srcPath, relDir: "" }];
@@ -5048,7 +5219,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
5048
5219
  }
5049
5220
  for (const entry of entries) {
5050
5221
  const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
5051
- const fullSrcPath = join6(dir2, entry.name);
5222
+ const fullSrcPath = join7(dir2, entry.name);
5052
5223
  if (entry.isDirectory()) {
5053
5224
  queue.push({ dir: fullSrcPath, relDir: relPath });
5054
5225
  continue;
@@ -5071,26 +5242,26 @@ function getComponentPath2(component) {
5071
5242
  }
5072
5243
  async function backupInstallation(targetDir) {
5073
5244
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
5074
- const backupDir = join6(targetDir, `.omcustom-backup-${timestamp}`);
5245
+ const backupDir = join7(targetDir, `.omcustom-backup-${timestamp}`);
5075
5246
  const fs = await import("node:fs/promises");
5076
5247
  await ensureDirectory(backupDir);
5077
5248
  const layout = getProviderLayout();
5078
5249
  const dirsToBackup = [layout.rootDir, "guides"];
5079
5250
  for (const dir2 of dirsToBackup) {
5080
- const srcPath = join6(targetDir, dir2);
5251
+ const srcPath = join7(targetDir, dir2);
5081
5252
  if (await fileExists(srcPath)) {
5082
- const destPath = join6(backupDir, dir2);
5253
+ const destPath = join7(backupDir, dir2);
5083
5254
  await copyDirectory(srcPath, destPath, { overwrite: true });
5084
5255
  }
5085
5256
  }
5086
- const entryPath = join6(targetDir, layout.entryFile);
5257
+ const entryPath = join7(targetDir, layout.entryFile);
5087
5258
  if (await fileExists(entryPath)) {
5088
- await fs.copyFile(entryPath, join6(backupDir, layout.entryFile));
5259
+ await fs.copyFile(entryPath, join7(backupDir, layout.entryFile));
5089
5260
  }
5090
5261
  return backupDir;
5091
5262
  }
5092
5263
  async function loadCustomizationManifest(targetDir) {
5093
- const manifestPath = join6(targetDir, CUSTOMIZATION_MANIFEST_FILE);
5264
+ const manifestPath = join7(targetDir, CUSTOMIZATION_MANIFEST_FILE);
5094
5265
  if (await fileExists(manifestPath)) {
5095
5266
  return readJsonFile(manifestPath);
5096
5267
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "0.79.1",
6
+ "version": "0.79.3",
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.1",
2
+ "version": "0.79.3",
3
3
  "lastUpdated": "2026-03-24T00:00:00.000Z",
4
4
  "components": [
5
5
  {