oh-my-customcode 0.44.5 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -14207,7 +14207,94 @@ async function doctorCommand(options = {}) {
14207
14207
  }
14208
14208
 
14209
14209
  // src/cli/init.ts
14210
- import { join as join10 } from "node:path";
14210
+ import { join as join11 } from "node:path";
14211
+ // package.json
14212
+ var package_default = {
14213
+ name: "oh-my-customcode",
14214
+ workspaces: ["packages/*"],
14215
+ version: "0.45.0",
14216
+ description: "Batteries-included agent harness for Claude Code",
14217
+ type: "module",
14218
+ bin: {
14219
+ omcustom: "./dist/cli/index.js"
14220
+ },
14221
+ main: "./dist/index.js",
14222
+ types: "./dist/index.d.ts",
14223
+ exports: {
14224
+ ".": {
14225
+ import: "./dist/index.js",
14226
+ types: "./dist/index.d.ts"
14227
+ }
14228
+ },
14229
+ files: [
14230
+ "dist",
14231
+ "templates"
14232
+ ],
14233
+ publishConfig: {
14234
+ access: "public",
14235
+ registry: "https://registry.npmjs.org/"
14236
+ },
14237
+ scripts: {
14238
+ dev: "bun run src/cli/index.ts",
14239
+ build: "bun build src/cli/index.ts --outdir dist/cli --target node && bun build src/index.ts --outdir dist --target node",
14240
+ test: "bun test",
14241
+ "test:unit": "bun test tests/unit",
14242
+ "test:integration": "bun test tests/integration",
14243
+ "test:e2e": "bun test tests/e2e",
14244
+ "test:coverage": "bun test --coverage",
14245
+ lint: "biome check .",
14246
+ "lint:fix": "biome check --write .",
14247
+ format: "biome format --write .",
14248
+ typecheck: "tsc --noEmit",
14249
+ "docs:dev": "vitepress dev docs",
14250
+ "docs:build": "vitepress build docs",
14251
+ prepare: "sh scripts/setup-hooks.sh || true",
14252
+ "setup:hooks": "sh scripts/setup-hooks.sh",
14253
+ prepublishOnly: "bun run build && bun run test"
14254
+ },
14255
+ dependencies: {
14256
+ "@clack/prompts": "^1.1.0",
14257
+ commander: "^14.0.2",
14258
+ i18next: "^25.8.0",
14259
+ yaml: "^2.8.2"
14260
+ },
14261
+ devDependencies: {
14262
+ "@anthropic-ai/sdk": "^0.78.0",
14263
+ "@biomejs/biome": "^2.3.12",
14264
+ "@types/bun": "^1.3.6",
14265
+ "@types/js-yaml": "^4.0.9",
14266
+ "@types/nodemailer": "^7.0.9",
14267
+ "js-yaml": "^4.1.0",
14268
+ nodemailer: "^8.0.1",
14269
+ typescript: "^5.7.3",
14270
+ vitepress: "^1.6.4"
14271
+ },
14272
+ keywords: [
14273
+ "claude",
14274
+ "claude-code",
14275
+ "openai",
14276
+ "ai",
14277
+ "agent",
14278
+ "cli"
14279
+ ],
14280
+ author: "baekenough",
14281
+ license: "MIT",
14282
+ repository: {
14283
+ type: "git",
14284
+ url: "git+https://github.com/baekenough/oh-my-customcode.git"
14285
+ },
14286
+ bugs: {
14287
+ url: "https://github.com/baekenough/oh-my-customcode/issues"
14288
+ },
14289
+ homepage: "https://github.com/baekenough/oh-my-customcode#readme",
14290
+ engines: {
14291
+ node: ">=18.0.0"
14292
+ },
14293
+ overrides: {
14294
+ rollup: "^4.59.0",
14295
+ esbuild: "^0.25.0"
14296
+ }
14297
+ };
14211
14298
 
14212
14299
  // src/core/installer.ts
14213
14300
  init_fs();
@@ -15063,19 +15150,213 @@ async function checkUvAvailable() {
15063
15150
  // src/cli/init.ts
15064
15151
  init_fs();
15065
15152
 
15153
+ // src/cli/projects.ts
15154
+ import { homedir as homedir2 } from "node:os";
15155
+ import { basename as basename3, join as join9 } from "node:path";
15156
+ var DEFAULT_SEARCH_DIRS = ["workspace", "projects", "dev", "src", "code", "repos", "work"];
15157
+ var MAX_SEARCH_DEPTH = 3;
15158
+ async function readLockFile(projectDir) {
15159
+ const lockFilePath = join9(projectDir, ".omcustom.lock.json");
15160
+ try {
15161
+ const fs2 = await import("node:fs/promises");
15162
+ const content = await fs2.readFile(lockFilePath, "utf-8");
15163
+ return JSON.parse(content);
15164
+ } catch {
15165
+ return null;
15166
+ }
15167
+ }
15168
+ async function hasOmcustomMarkers(dir2) {
15169
+ const fs2 = await import("node:fs/promises");
15170
+ const agentsDir = join9(dir2, ".claude", "agents");
15171
+ const skillsDir = join9(dir2, ".claude", "skills");
15172
+ try {
15173
+ const [agentsStat, skillsStat] = await Promise.allSettled([
15174
+ fs2.stat(agentsDir),
15175
+ fs2.stat(skillsDir)
15176
+ ]);
15177
+ return agentsStat.status === "fulfilled" && agentsStat.value.isDirectory() && skillsStat.status === "fulfilled" && skillsStat.value.isDirectory();
15178
+ } catch {
15179
+ return false;
15180
+ }
15181
+ }
15182
+ function computeStatus(version, currentVersion) {
15183
+ if (!version)
15184
+ return "unknown";
15185
+ const normalizedInstalled = version.replace(/^v/, "");
15186
+ const normalizedCurrent = currentVersion.replace(/^v/, "");
15187
+ if (normalizedInstalled === normalizedCurrent)
15188
+ return "latest";
15189
+ const parseVersion = (v) => v.split(".").map((n) => parseInt(n, 10) || 0);
15190
+ const [aMaj, aMin, aPatch] = parseVersion(normalizedInstalled);
15191
+ const [bMaj, bMin, bPatch] = parseVersion(normalizedCurrent);
15192
+ if (aMaj < bMaj || aMaj === bMaj && aMin < bMin || aMaj === bMaj && aMin === bMin && aPatch < bPatch) {
15193
+ return "outdated";
15194
+ }
15195
+ return "latest";
15196
+ }
15197
+ async function searchDirectory(dir2, depth, results, currentVersion, seen) {
15198
+ if (depth > MAX_SEARCH_DEPTH || seen.has(dir2))
15199
+ return;
15200
+ seen.add(dir2);
15201
+ const fs2 = await import("node:fs/promises");
15202
+ let entries;
15203
+ try {
15204
+ entries = await fs2.readdir(dir2, { withFileTypes: true });
15205
+ } catch {
15206
+ return;
15207
+ }
15208
+ const lockFile = await readLockFile(dir2);
15209
+ if (lockFile) {
15210
+ const version = lockFile.version || lockFile.templateVersion || null;
15211
+ results.push({
15212
+ name: basename3(dir2),
15213
+ path: dir2,
15214
+ version,
15215
+ installedAt: lockFile.installedAt || null,
15216
+ updatedAt: lockFile.updatedAt || null,
15217
+ status: computeStatus(version, currentVersion),
15218
+ detectionMethod: "lockfile"
15219
+ });
15220
+ return;
15221
+ }
15222
+ if (await hasOmcustomMarkers(dir2)) {
15223
+ results.push({
15224
+ name: basename3(dir2),
15225
+ path: dir2,
15226
+ version: null,
15227
+ installedAt: null,
15228
+ updatedAt: null,
15229
+ status: "unknown",
15230
+ detectionMethod: "directory"
15231
+ });
15232
+ return;
15233
+ }
15234
+ if (depth < MAX_SEARCH_DEPTH) {
15235
+ const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist" && e.name !== "build" && e.name !== ".git");
15236
+ await Promise.all(subdirs.map((subdir) => searchDirectory(join9(dir2, subdir.name), depth + 1, results, currentVersion, seen)));
15237
+ }
15238
+ }
15239
+ async function findProjects(options = {}) {
15240
+ const currentVersion = package_default.version;
15241
+ const home = homedir2();
15242
+ const seen = new Set;
15243
+ const results = [];
15244
+ const searchPaths = [];
15245
+ for (const dir2 of DEFAULT_SEARCH_DIRS) {
15246
+ searchPaths.push(join9(home, dir2));
15247
+ }
15248
+ if (options.paths) {
15249
+ searchPaths.push(...options.paths);
15250
+ }
15251
+ await Promise.all(searchPaths.map((searchPath) => searchDirectory(searchPath, 0, results, currentVersion, seen).catch(() => {})));
15252
+ return results.sort((a, b) => {
15253
+ if (a.status === "latest" && b.status !== "latest")
15254
+ return -1;
15255
+ if (a.status !== "latest" && b.status === "latest")
15256
+ return 1;
15257
+ return a.name.localeCompare(b.name);
15258
+ });
15259
+ }
15260
+ async function writeLockFile(projectDir, version, existing) {
15261
+ const fs2 = await import("node:fs/promises");
15262
+ const lockFilePath = join9(projectDir, ".omcustom.lock.json");
15263
+ const now = new Date().toISOString();
15264
+ const merged = {
15265
+ ...existing || {},
15266
+ version,
15267
+ installedAt: existing?.installedAt || now,
15268
+ updatedAt: now
15269
+ };
15270
+ await fs2.writeFile(lockFilePath, JSON.stringify(merged, null, 2), "utf-8");
15271
+ }
15272
+ function formatProjectsTable(projects, currentVersion) {
15273
+ if (projects.length === 0) {
15274
+ console.log(`
15275
+ oh-my-customcode가 적용된 프로젝트를 찾을 수 없습니다.`);
15276
+ console.log(` 검색 경로: ~/workspace, ~/projects, ~/dev, ~/src, ~/code
15277
+ `);
15278
+ return;
15279
+ }
15280
+ const nameWidth = Math.max(20, ...projects.map((p) => p.name.length));
15281
+ const pathWidth = Math.max(35, ...projects.map((p) => shortenPath(p.path).length));
15282
+ const versionWidth = 10;
15283
+ console.log(`
15284
+ oh-my-customcode 적용 프로젝트 목록:
15285
+ `);
15286
+ const nameHeader = "Project".padEnd(nameWidth);
15287
+ const pathHeader = "Path".padEnd(pathWidth);
15288
+ const versionHeader = "Version".padEnd(versionWidth);
15289
+ const statusHeader = "Status";
15290
+ console.log(` ${nameHeader} ${pathHeader} ${versionHeader} ${statusHeader}`);
15291
+ console.log(` ${"─".repeat(nameWidth)} ${"─".repeat(pathWidth)} ${"─".repeat(versionWidth)} ${"─".repeat(12)}`);
15292
+ for (const project of projects) {
15293
+ const name = project.name.padEnd(nameWidth);
15294
+ const path2 = shortenPath(project.path).padEnd(pathWidth);
15295
+ const version = (project.version || "unknown").padEnd(versionWidth);
15296
+ const statusIcon = project.status === "latest" ? "✓ latest" : project.status === "outdated" ? "⚠ outdated" : "? unknown";
15297
+ console.log(` ${name} ${path2} ${version} ${statusIcon}`);
15298
+ }
15299
+ console.log(` ${"─".repeat(nameWidth + pathWidth + versionWidth + 20)}`);
15300
+ const latestCount = projects.filter((p) => p.status === "latest").length;
15301
+ const outdatedCount = projects.filter((p) => p.status === "outdated").length;
15302
+ const unknownCount = projects.filter((p) => p.status === "unknown").length;
15303
+ console.log(`
15304
+ Total: ${projects.length} projects (${latestCount} latest, ${outdatedCount} outdated, ${unknownCount} unknown)`);
15305
+ console.log(` Latest version: v${currentVersion}
15306
+ `);
15307
+ }
15308
+ function shortenPath(path2) {
15309
+ const home = homedir2();
15310
+ if (path2.startsWith(home)) {
15311
+ return `~${path2.slice(home.length)}`;
15312
+ }
15313
+ return path2;
15314
+ }
15315
+ function formatProjectsSimple(projects, currentVersion) {
15316
+ console.log(`
15317
+ oh-my-customcode 적용 프로젝트 (${projects.length}개):`);
15318
+ for (const project of projects) {
15319
+ const version = project.version ? `v${project.version}` : "unknown";
15320
+ const status = project.status === "latest" ? "✓" : project.status === "outdated" ? "⚠" : "?";
15321
+ console.log(` ${status} ${project.name} [${version}] — ${shortenPath(project.path)}`);
15322
+ }
15323
+ console.log(`
15324
+ 현재 설치 버전: v${currentVersion}`);
15325
+ }
15326
+ async function projectsCommand(options = {}) {
15327
+ const currentVersion = package_default.version;
15328
+ const format = options.format || "table";
15329
+ console.log(" oh-my-customcode 적용 프로젝트를 검색 중...");
15330
+ try {
15331
+ const projects = await findProjects(options);
15332
+ if (format === "json") {
15333
+ console.log(JSON.stringify({ currentVersion, projects }, null, 2));
15334
+ } else if (format === "simple") {
15335
+ formatProjectsSimple(projects, currentVersion);
15336
+ } else {
15337
+ formatProjectsTable(projects, currentVersion);
15338
+ }
15339
+ return { success: true, projects, currentVersion };
15340
+ } catch (error2) {
15341
+ const errorMessage = error2 instanceof Error ? error2.message : String(error2);
15342
+ console.error(" 프로젝트 검색 실패:", errorMessage);
15343
+ return { success: false, projects: [], currentVersion, errors: [errorMessage] };
15344
+ }
15345
+ }
15346
+
15066
15347
  // src/cli/serve.ts
15067
15348
  import { spawn } from "node:child_process";
15068
15349
  import { existsSync as existsSync2 } from "node:fs";
15069
15350
  import { readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
15070
- import { join as join9 } from "node:path";
15351
+ import { join as join10 } from "node:path";
15071
15352
  var DEFAULT_PORT = 4321;
15072
- var PID_FILE = join9(process.env.HOME ?? "~", ".omcustom-serve.pid");
15353
+ var PID_FILE = join10(process.env.HOME ?? "~", ".omcustom-serve.pid");
15073
15354
  function findServeBuildDir(projectRoot) {
15074
- const localBuild = join9(projectRoot, "packages", "serve", "build");
15075
- if (existsSync2(join9(localBuild, "index.js")))
15355
+ const localBuild = join10(projectRoot, "packages", "serve", "build");
15356
+ if (existsSync2(join10(localBuild, "index.js")))
15076
15357
  return localBuild;
15077
- const npmBuild = join9(import.meta.dirname, "..", "..", "packages", "serve", "build");
15078
- if (existsSync2(join9(npmBuild, "index.js")))
15358
+ const npmBuild = join10(import.meta.dirname, "..", "..", "packages", "serve", "build");
15359
+ if (existsSync2(join10(npmBuild, "index.js")))
15079
15360
  return npmBuild;
15080
15361
  return null;
15081
15362
  }
@@ -15102,7 +15383,7 @@ async function startServeBackground(projectRoot, port = DEFAULT_PORT) {
15102
15383
  if (buildDir === null) {
15103
15384
  return;
15104
15385
  }
15105
- const child = spawn("node", [join9(buildDir, "index.js")], {
15386
+ const child = spawn("node", [join10(buildDir, "index.js")], {
15106
15387
  env: {
15107
15388
  ...process.env,
15108
15389
  OMCUSTOM_PORT: String(port),
@@ -16117,7 +16398,7 @@ async function runInitWizard(options) {
16117
16398
  // src/cli/init.ts
16118
16399
  async function checkExistingInstallation(targetDir) {
16119
16400
  const layout = getProviderLayout();
16120
- const rootDir = join10(targetDir, layout.rootDir);
16401
+ const rootDir = join11(targetDir, layout.rootDir);
16121
16402
  return fileExists(rootDir);
16122
16403
  }
16123
16404
  var PROVIDER_SUBDIR_COMPONENTS = new Set([
@@ -16131,13 +16412,13 @@ var PROVIDER_SUBDIR_COMPONENTS = new Set([
16131
16412
  function componentToPath(targetDir, component) {
16132
16413
  if (component === "entry-md") {
16133
16414
  const layout = getProviderLayout();
16134
- return join10(targetDir, layout.entryFile);
16415
+ return join11(targetDir, layout.entryFile);
16135
16416
  }
16136
16417
  if (PROVIDER_SUBDIR_COMPONENTS.has(component)) {
16137
16418
  const layout = getProviderLayout();
16138
- return join10(targetDir, layout.rootDir, component);
16419
+ return join11(targetDir, layout.rootDir, component);
16139
16420
  }
16140
- return join10(targetDir, component);
16421
+ return join11(targetDir, component);
16141
16422
  }
16142
16423
  function buildInstalledPaths(targetDir, components) {
16143
16424
  return components.map((component) => componentToPath(targetDir, component));
@@ -16230,6 +16511,10 @@ async function initCommand(options) {
16230
16511
  logInstallResultInfo(installResult);
16231
16512
  logSuccessDetails(installedPaths, installResult.skippedComponents);
16232
16513
  await setupMcpConfig(targetDir);
16514
+ try {
16515
+ const existing = await readLockFile(targetDir);
16516
+ await writeLockFile(targetDir, package_default.version, existing);
16517
+ } catch {}
16233
16518
  console.log("");
16234
16519
  console.log("Required plugins (install manually):");
16235
16520
  console.log(" /plugin marketplace add obra/superpowers-marketplace");
@@ -16254,7 +16539,7 @@ async function initCommand(options) {
16254
16539
  }
16255
16540
 
16256
16541
  // src/cli/list.ts
16257
- import { basename as basename3, dirname as dirname3, join as join11, relative as relative3 } from "node:path";
16542
+ import { basename as basename4, dirname as dirname3, join as join12, relative as relative3 } from "node:path";
16258
16543
  init_fs();
16259
16544
  var ALLOWED_TOP_LEVEL_KEYS = new Set(["name", "type", "description", "version", "category"]);
16260
16545
  function parseKeyValue(line) {
@@ -16301,7 +16586,7 @@ function parseYamlMetadata(content) {
16301
16586
  return result;
16302
16587
  }
16303
16588
  function extractAgentTypeFromFilename(filename) {
16304
- const name = basename3(filename, ".md");
16589
+ const name = basename4(filename, ".md");
16305
16590
  const prefixMap = {
16306
16591
  lang: "language",
16307
16592
  be: "backend",
@@ -16319,17 +16604,17 @@ function extractAgentTypeFromFilename(filename) {
16319
16604
  return prefixMap[prefix] || "unknown";
16320
16605
  }
16321
16606
  function extractSkillCategoryFromPath(skillPath, baseDir, rootDir) {
16322
- const relativePath = relative3(join11(baseDir, rootDir, "skills"), skillPath);
16607
+ const relativePath = relative3(join12(baseDir, rootDir, "skills"), skillPath);
16323
16608
  const parts = relativePath.split("/").filter(Boolean);
16324
16609
  return parts[0] || "unknown";
16325
16610
  }
16326
16611
  function extractGuideCategoryFromPath(guidePath, baseDir) {
16327
- const relativePath = relative3(join11(baseDir, "guides"), guidePath);
16612
+ const relativePath = relative3(join12(baseDir, "guides"), guidePath);
16328
16613
  const parts = relativePath.split("/").filter(Boolean);
16329
16614
  return parts[0] || "unknown";
16330
16615
  }
16331
16616
  function extractRulePriorityFromFilename(filename) {
16332
- const name = basename3(filename, ".md");
16617
+ const name = basename4(filename, ".md");
16333
16618
  const parts = name.split("-");
16334
16619
  return parts[0] || "unknown";
16335
16620
  }
@@ -16418,7 +16703,7 @@ async function tryExtractMarkdownDescription(mdPath, options = {}) {
16418
16703
  }
16419
16704
  }
16420
16705
  async function getAgents(targetDir, rootDir = ".claude", config) {
16421
- const agentsDir = join11(targetDir, rootDir, "agents");
16706
+ const agentsDir = join12(targetDir, rootDir, "agents");
16422
16707
  if (!await fileExists(agentsDir))
16423
16708
  return [];
16424
16709
  try {
@@ -16427,8 +16712,8 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
16427
16712
  const customAgentPaths = new Set(customComponents.filter((c) => c.type === "agent").map((c) => c.path));
16428
16713
  const agentMdFiles = await listFiles(agentsDir, { recursive: false, pattern: "*.md" });
16429
16714
  const agents = await Promise.all(agentMdFiles.map(async (agentMdPath) => {
16430
- const filename = basename3(agentMdPath);
16431
- const name = basename3(filename, ".md");
16715
+ const filename = basename4(agentMdPath);
16716
+ const name = basename4(filename, ".md");
16432
16717
  const description = await tryExtractMarkdownDescription(agentMdPath);
16433
16718
  const relativePath = relative3(targetDir, agentMdPath);
16434
16719
  return {
@@ -16446,7 +16731,7 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
16446
16731
  }
16447
16732
  }
16448
16733
  async function getSkills(targetDir, rootDir = ".claude", config) {
16449
- const skillsDir = join11(targetDir, rootDir, "skills");
16734
+ const skillsDir = join12(targetDir, rootDir, "skills");
16450
16735
  if (!await fileExists(skillsDir))
16451
16736
  return [];
16452
16737
  try {
@@ -16456,11 +16741,11 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
16456
16741
  const skillMdFiles = await listFiles(skillsDir, { recursive: true, pattern: "SKILL.md" });
16457
16742
  const skills = await Promise.all(skillMdFiles.map(async (skillMdPath) => {
16458
16743
  const skillDir = dirname3(skillMdPath);
16459
- const indexYamlPath = join11(skillDir, "index.yaml");
16744
+ const indexYamlPath = join12(skillDir, "index.yaml");
16460
16745
  const { description, version } = await tryReadIndexYamlMetadata(indexYamlPath);
16461
16746
  const relativePath = relative3(targetDir, skillDir);
16462
16747
  return {
16463
- name: basename3(skillDir),
16748
+ name: basename4(skillDir),
16464
16749
  type: "skill",
16465
16750
  category: extractSkillCategoryFromPath(skillDir, targetDir, rootDir),
16466
16751
  path: relativePath,
@@ -16475,7 +16760,7 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
16475
16760
  }
16476
16761
  }
16477
16762
  async function getGuides(targetDir, config) {
16478
- const guidesDir = join11(targetDir, "guides");
16763
+ const guidesDir = join12(targetDir, "guides");
16479
16764
  if (!await fileExists(guidesDir))
16480
16765
  return [];
16481
16766
  try {
@@ -16487,7 +16772,7 @@ async function getGuides(targetDir, config) {
16487
16772
  const description = await tryExtractMarkdownDescription(guideMdPath, { maxLength: 100 });
16488
16773
  const relativePath = relative3(targetDir, guideMdPath);
16489
16774
  return {
16490
- name: basename3(guideMdPath, ".md"),
16775
+ name: basename4(guideMdPath, ".md"),
16491
16776
  type: "guide",
16492
16777
  category: extractGuideCategoryFromPath(guideMdPath, targetDir),
16493
16778
  path: relativePath,
@@ -16502,7 +16787,7 @@ async function getGuides(targetDir, config) {
16502
16787
  }
16503
16788
  var RULE_PRIORITY_ORDER = { MUST: 0, SHOULD: 1, MAY: 2 };
16504
16789
  async function getRules(targetDir, rootDir = ".claude", config) {
16505
- const rulesDir = join11(targetDir, rootDir, "rules");
16790
+ const rulesDir = join12(targetDir, rootDir, "rules");
16506
16791
  if (!await fileExists(rulesDir))
16507
16792
  return [];
16508
16793
  try {
@@ -16511,13 +16796,13 @@ async function getRules(targetDir, rootDir = ".claude", config) {
16511
16796
  const customRulePaths = new Set(customComponents.filter((c) => c.type === "rule").map((c) => c.path));
16512
16797
  const ruleMdFiles = await listFiles(rulesDir, { recursive: false, pattern: "*.md" });
16513
16798
  const rules = await Promise.all(ruleMdFiles.map(async (ruleMdPath) => {
16514
- const filename = basename3(ruleMdPath);
16799
+ const filename = basename4(ruleMdPath);
16515
16800
  const description = await tryExtractMarkdownDescription(ruleMdPath, {
16516
16801
  cleanFormatting: true
16517
16802
  });
16518
16803
  const relativePath = relative3(targetDir, ruleMdPath);
16519
16804
  return {
16520
- name: basename3(ruleMdPath, ".md"),
16805
+ name: basename4(ruleMdPath, ".md"),
16521
16806
  type: extractRulePriorityFromFilename(filename),
16522
16807
  path: relativePath,
16523
16808
  description,
@@ -16574,7 +16859,7 @@ function formatAsJson(components) {
16574
16859
  console.log(JSON.stringify(components, null, 2));
16575
16860
  }
16576
16861
  async function getHooks(targetDir, rootDir = ".claude") {
16577
- const hooksDir = join11(targetDir, rootDir, "hooks");
16862
+ const hooksDir = join12(targetDir, rootDir, "hooks");
16578
16863
  if (!await fileExists(hooksDir))
16579
16864
  return [];
16580
16865
  try {
@@ -16583,7 +16868,7 @@ async function getHooks(targetDir, rootDir = ".claude") {
16583
16868
  const hookYamls = await listFiles(hooksDir, { recursive: true, pattern: "*.yaml" });
16584
16869
  const allFiles = [...hookFiles, ...hookConfigs, ...hookYamls];
16585
16870
  return allFiles.map((hookPath) => ({
16586
- name: basename3(hookPath),
16871
+ name: basename4(hookPath),
16587
16872
  type: "hook",
16588
16873
  path: relative3(targetDir, hookPath)
16589
16874
  })).sort((a, b) => a.name.localeCompare(b.name));
@@ -16592,7 +16877,7 @@ async function getHooks(targetDir, rootDir = ".claude") {
16592
16877
  }
16593
16878
  }
16594
16879
  async function getContexts(targetDir, rootDir = ".claude") {
16595
- const contextsDir = join11(targetDir, rootDir, "contexts");
16880
+ const contextsDir = join12(targetDir, rootDir, "contexts");
16596
16881
  if (!await fileExists(contextsDir))
16597
16882
  return [];
16598
16883
  try {
@@ -16603,7 +16888,7 @@ async function getContexts(targetDir, rootDir = ".claude") {
16603
16888
  const ext = ctxPath.endsWith(".md") ? ".md" : ".yaml";
16604
16889
  const description = ext === ".md" ? await tryExtractMarkdownDescription(ctxPath, { maxLength: 100 }) : undefined;
16605
16890
  return {
16606
- name: basename3(ctxPath, ext),
16891
+ name: basename4(ctxPath, ext),
16607
16892
  type: "context",
16608
16893
  path: relative3(targetDir, ctxPath),
16609
16894
  description
@@ -16982,7 +17267,7 @@ async function securityCommand(_options = {}) {
16982
17267
 
16983
17268
  // src/cli/serve-commands.ts
16984
17269
  import { execFile, spawnSync as spawnSync2 } from "node:child_process";
16985
- import { join as join12 } from "node:path";
17270
+ import { join as join13 } from "node:path";
16986
17271
  async function serveCommand(options) {
16987
17272
  const port = options.port !== undefined ? Number(options.port) : DEFAULT_PORT;
16988
17273
  if (!Number.isFinite(port) || port < 1 || port > 65535) {
@@ -17021,7 +17306,7 @@ function runForeground(projectRoot, port) {
17021
17306
  process.exit(1);
17022
17307
  }
17023
17308
  console.log(`Web UI: http://127.0.0.1:${port}`);
17024
- spawnSync2("node", [join12(buildDir, "index.js")], {
17309
+ spawnSync2("node", [join13(buildDir, "index.js")], {
17025
17310
  env: {
17026
17311
  ...process.env,
17027
17312
  OMCUSTOM_PORT: String(port),
@@ -17046,7 +17331,7 @@ function openBrowser(port) {
17046
17331
 
17047
17332
  // src/core/updater.ts
17048
17333
  init_fs();
17049
- import { join as join13 } from "node:path";
17334
+ import { join as join14 } from "node:path";
17050
17335
 
17051
17336
  // src/core/entry-merger.ts
17052
17337
  var MANAGED_START = "<!-- omcustom:start -->";
@@ -17295,7 +17580,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
17295
17580
  }
17296
17581
  async function updateEntryDoc(targetDir, config, options) {
17297
17582
  const layout = getProviderLayout();
17298
- const entryPath = join13(targetDir, layout.entryFile);
17583
+ const entryPath = join14(targetDir, layout.entryFile);
17299
17584
  const templateName = getEntryTemplateName2(config.language);
17300
17585
  const templatePath = resolveTemplatePath(templateName);
17301
17586
  if (!await fileExists(templatePath)) {
@@ -17441,7 +17726,7 @@ async function collectProtectedSkipPaths(srcPath, destPath, componentPath, force
17441
17726
  }
17442
17727
  const protectedRelative = await findProtectedFilesInDir(srcPath, componentPath);
17443
17728
  const path3 = await import("node:path");
17444
- const skipPaths = protectedRelative.map((p) => path3.relative(destPath, join13(destPath, p)));
17729
+ const skipPaths = protectedRelative.map((p) => path3.relative(destPath, join14(destPath, p)));
17445
17730
  return { skipPaths, warnedPaths: protectedRelative };
17446
17731
  }
17447
17732
  function isEntryProtected(relPath, componentRelativePrefix) {
@@ -17482,7 +17767,7 @@ async function updateComponent(targetDir, component, customizations, options, co
17482
17767
  const preservedFiles = [];
17483
17768
  const componentPath = getComponentPath2(component);
17484
17769
  const srcPath = resolveTemplatePath(componentPath);
17485
- const destPath = join13(targetDir, componentPath);
17770
+ const destPath = join14(targetDir, componentPath);
17486
17771
  const customComponents = config.customComponents || [];
17487
17772
  const skipPaths = [];
17488
17773
  if (customizations && !options.forceOverwriteAll) {
@@ -17513,7 +17798,7 @@ async function updateComponent(targetDir, component, customizations, options, co
17513
17798
  }
17514
17799
  skipPaths.push(...protectedSkipPaths);
17515
17800
  const path3 = await import("node:path");
17516
- const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join13(targetDir, p)));
17801
+ const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join14(targetDir, p)));
17517
17802
  const uniqueSkipPaths = [...new Set(normalizedSkipPaths)];
17518
17803
  await copyDirectory(srcPath, destPath, {
17519
17804
  overwrite: true,
@@ -17535,12 +17820,12 @@ async function syncRootLevelFiles(targetDir, options) {
17535
17820
  const layout = getProviderLayout();
17536
17821
  const synced = [];
17537
17822
  for (const fileName of ROOT_LEVEL_FILES) {
17538
- const srcPath = resolveTemplatePath(join13(layout.rootDir, fileName));
17823
+ const srcPath = resolveTemplatePath(join14(layout.rootDir, fileName));
17539
17824
  if (!await fileExists(srcPath)) {
17540
17825
  continue;
17541
17826
  }
17542
- const destPath = join13(targetDir, layout.rootDir, fileName);
17543
- await ensureDirectory(join13(destPath, ".."));
17827
+ const destPath = join14(targetDir, layout.rootDir, fileName);
17828
+ await ensureDirectory(join14(destPath, ".."));
17544
17829
  await fs3.copyFile(srcPath, destPath);
17545
17830
  if (fileName.endsWith(".sh")) {
17546
17831
  await fs3.chmod(destPath, 493);
@@ -17575,7 +17860,7 @@ async function removeDeprecatedFiles(targetDir, options) {
17575
17860
  });
17576
17861
  continue;
17577
17862
  }
17578
- const fullPath = join13(targetDir, entry.path);
17863
+ const fullPath = join14(targetDir, entry.path);
17579
17864
  if (await fileExists(fullPath)) {
17580
17865
  await fs3.unlink(fullPath);
17581
17866
  removed.push(entry.path);
@@ -17599,26 +17884,26 @@ function getComponentPath2(component) {
17599
17884
  }
17600
17885
  async function backupInstallation(targetDir) {
17601
17886
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
17602
- const backupDir = join13(targetDir, `.omcustom-backup-${timestamp}`);
17887
+ const backupDir = join14(targetDir, `.omcustom-backup-${timestamp}`);
17603
17888
  const fs3 = await import("node:fs/promises");
17604
17889
  await ensureDirectory(backupDir);
17605
17890
  const layout = getProviderLayout();
17606
17891
  const dirsToBackup = [layout.rootDir, "guides"];
17607
17892
  for (const dir2 of dirsToBackup) {
17608
- const srcPath = join13(targetDir, dir2);
17893
+ const srcPath = join14(targetDir, dir2);
17609
17894
  if (await fileExists(srcPath)) {
17610
- const destPath = join13(backupDir, dir2);
17895
+ const destPath = join14(backupDir, dir2);
17611
17896
  await copyDirectory(srcPath, destPath, { overwrite: true });
17612
17897
  }
17613
17898
  }
17614
- const entryPath = join13(targetDir, layout.entryFile);
17899
+ const entryPath = join14(targetDir, layout.entryFile);
17615
17900
  if (await fileExists(entryPath)) {
17616
- await fs3.copyFile(entryPath, join13(backupDir, layout.entryFile));
17901
+ await fs3.copyFile(entryPath, join14(backupDir, layout.entryFile));
17617
17902
  }
17618
17903
  return backupDir;
17619
17904
  }
17620
17905
  async function loadCustomizationManifest(targetDir) {
17621
- const manifestPath = join13(targetDir, CUSTOMIZATION_MANIFEST_FILE);
17906
+ const manifestPath = join14(targetDir, CUSTOMIZATION_MANIFEST_FILE);
17622
17907
  if (await fileExists(manifestPath)) {
17623
17908
  return readJsonFile(manifestPath);
17624
17909
  }
@@ -17734,6 +18019,9 @@ function createProgram() {
17734
18019
  program2.command("serve-stop").description("Stop the web UI server").action(async () => {
17735
18020
  await serveStopCommand();
17736
18021
  });
18022
+ 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) => {
18023
+ await projectsCommand({ format: options.format, paths: options.path });
18024
+ });
17737
18025
  program2.hook("preAction", async (thisCommand, actionCommand) => {
17738
18026
  const opts = thisCommand.optsWithGlobals();
17739
18027
  const skipCheck = opts.skipVersionCheck || false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oh-my-customcode",
3
3
  "workspaces": ["packages/*"],
4
- "version": "0.44.5",
4
+ "version": "0.45.0",
5
5
  "description": "Batteries-included agent harness for Claude Code",
6
6
  "type": "module",
7
7
  "bin": {