oh-my-customcode 0.44.6 → 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/README.md +5 -5
- package/dist/cli/index.js +337 -49
- package/package.json +1 -1
- package/templates/.claude/skills/ambiguity-gate/SKILL.md +94 -0
- package/templates/.claude/skills/omcustom-feedback/SKILL.md +137 -0
- package/templates/.claude/skills/sdd/SKILL.md +24 -0
- package/templates/.claude/skills/sdd-dev/SKILL.md +257 -0
- package/templates/.claude/skills/sdd-development/SKILL.md +24 -0
- package/templates/CLAUDE.md +4 -2
- package/templates/guides/git-worktree-workflow/README.md +134 -0
- package/templates/guides/skill-bundle-design/README.md +106 -0
- package/templates/manifest.json +3 -3
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
|
|
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
|
|
15351
|
+
import { join as join10 } from "node:path";
|
|
15071
15352
|
var DEFAULT_PORT = 4321;
|
|
15072
|
-
var PID_FILE =
|
|
15353
|
+
var PID_FILE = join10(process.env.HOME ?? "~", ".omcustom-serve.pid");
|
|
15073
15354
|
function findServeBuildDir(projectRoot) {
|
|
15074
|
-
const localBuild =
|
|
15075
|
-
if (existsSync2(
|
|
15355
|
+
const localBuild = join10(projectRoot, "packages", "serve", "build");
|
|
15356
|
+
if (existsSync2(join10(localBuild, "index.js")))
|
|
15076
15357
|
return localBuild;
|
|
15077
|
-
const npmBuild =
|
|
15078
|
-
if (existsSync2(
|
|
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", [
|
|
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 =
|
|
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
|
|
16415
|
+
return join11(targetDir, layout.entryFile);
|
|
16135
16416
|
}
|
|
16136
16417
|
if (PROVIDER_SUBDIR_COMPONENTS.has(component)) {
|
|
16137
16418
|
const layout = getProviderLayout();
|
|
16138
|
-
return
|
|
16419
|
+
return join11(targetDir, layout.rootDir, component);
|
|
16139
16420
|
}
|
|
16140
|
-
return
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
16431
|
-
const name =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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
|
|
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", [
|
|
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
|
|
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 =
|
|
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,
|
|
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 =
|
|
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,
|
|
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(
|
|
17823
|
+
const srcPath = resolveTemplatePath(join14(layout.rootDir, fileName));
|
|
17539
17824
|
if (!await fileExists(srcPath)) {
|
|
17540
17825
|
continue;
|
|
17541
17826
|
}
|
|
17542
|
-
const destPath =
|
|
17543
|
-
await ensureDirectory(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
17893
|
+
const srcPath = join14(targetDir, dir2);
|
|
17609
17894
|
if (await fileExists(srcPath)) {
|
|
17610
|
-
const destPath =
|
|
17895
|
+
const destPath = join14(backupDir, dir2);
|
|
17611
17896
|
await copyDirectory(srcPath, destPath, { overwrite: true });
|
|
17612
17897
|
}
|
|
17613
17898
|
}
|
|
17614
|
-
const entryPath =
|
|
17899
|
+
const entryPath = join14(targetDir, layout.entryFile);
|
|
17615
17900
|
if (await fileExists(entryPath)) {
|
|
17616
|
-
await fs3.copyFile(entryPath,
|
|
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 =
|
|
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;
|