oh-my-customcode 0.42.3 → 0.43.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
@@ -9376,7 +9376,9 @@ var require_src = __commonJS((exports, module) => {
9376
9376
  });
9377
9377
 
9378
9378
  // src/cli/index.ts
9379
+ import { existsSync as existsSync3 } from "node:fs";
9379
9380
  import { createRequire as createRequire2 } from "node:module";
9381
+ import { join as join14 } from "node:path";
9380
9382
 
9381
9383
  // node_modules/.bun/commander@14.0.2/node_modules/commander/esm.mjs
9382
9384
  var import__ = __toESM(require_commander(), 1);
@@ -14207,7 +14209,7 @@ async function doctorCommand(options = {}) {
14207
14209
  }
14208
14210
 
14209
14211
  // src/cli/init.ts
14210
- import { join as join9 } from "node:path";
14212
+ import { join as join10 } from "node:path";
14211
14213
 
14212
14214
  // src/core/installer.ts
14213
14215
  init_fs();
@@ -15063,6 +15065,82 @@ async function checkUvAvailable() {
15063
15065
  // src/cli/init.ts
15064
15066
  init_fs();
15065
15067
 
15068
+ // src/cli/serve.ts
15069
+ import { spawn } from "node:child_process";
15070
+ import { existsSync as existsSync2 } from "node:fs";
15071
+ import { readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
15072
+ import { join as join9 } from "node:path";
15073
+ var DEFAULT_PORT = 4321;
15074
+ var PID_FILE = join9(process.env.HOME ?? "~", ".omcustom-serve.pid");
15075
+ function findServeBuildDir(projectRoot) {
15076
+ const localBuild = join9(projectRoot, "packages", "serve", "build");
15077
+ if (existsSync2(join9(localBuild, "index.js")))
15078
+ return localBuild;
15079
+ const npmBuild = join9(import.meta.dirname, "..", "..", "packages", "serve", "build");
15080
+ if (existsSync2(join9(npmBuild, "index.js")))
15081
+ return npmBuild;
15082
+ return null;
15083
+ }
15084
+ async function isServeRunning() {
15085
+ try {
15086
+ const raw = await readFile2(PID_FILE, "utf-8");
15087
+ const pid = Number(raw.trim());
15088
+ if (!Number.isFinite(pid) || pid <= 0) {
15089
+ await cleanupPidFile();
15090
+ return false;
15091
+ }
15092
+ process.kill(pid, 0);
15093
+ return true;
15094
+ } catch {
15095
+ await cleanupPidFile();
15096
+ return false;
15097
+ }
15098
+ }
15099
+ async function startServeBackground(projectRoot, port = DEFAULT_PORT) {
15100
+ if (await isServeRunning()) {
15101
+ return;
15102
+ }
15103
+ const buildDir = findServeBuildDir(projectRoot);
15104
+ if (buildDir === null) {
15105
+ return;
15106
+ }
15107
+ const child = spawn("node", [join9(buildDir, "index.js")], {
15108
+ env: {
15109
+ ...process.env,
15110
+ PORT: String(port),
15111
+ HOST: "127.0.0.1",
15112
+ OMCUSTOM_PROJECT_ROOT: projectRoot
15113
+ },
15114
+ stdio: "ignore",
15115
+ detached: true
15116
+ });
15117
+ child.unref();
15118
+ if (child.pid !== undefined) {
15119
+ await writeFile2(PID_FILE, String(child.pid), "utf-8");
15120
+ }
15121
+ }
15122
+ async function stopServe() {
15123
+ try {
15124
+ const raw = await readFile2(PID_FILE, "utf-8");
15125
+ const pid = Number(raw.trim());
15126
+ if (!Number.isFinite(pid) || pid <= 0) {
15127
+ await cleanupPidFile();
15128
+ return false;
15129
+ }
15130
+ process.kill(pid, "SIGTERM");
15131
+ await cleanupPidFile();
15132
+ return true;
15133
+ } catch {
15134
+ await cleanupPidFile();
15135
+ return false;
15136
+ }
15137
+ }
15138
+ async function cleanupPidFile() {
15139
+ try {
15140
+ await unlink(PID_FILE);
15141
+ } catch {}
15142
+ }
15143
+
15066
15144
  // node_modules/.bun/@clack+core@1.1.0/node_modules/@clack/core/dist/index.mjs
15067
15145
  import { styleText as D } from "node:util";
15068
15146
  import { stdout as R, stdin as q } from "node:process";
@@ -16040,7 +16118,7 @@ async function runInitWizard(options) {
16040
16118
  // src/cli/init.ts
16041
16119
  async function checkExistingInstallation(targetDir) {
16042
16120
  const layout = getProviderLayout();
16043
- const rootDir = join9(targetDir, layout.rootDir);
16121
+ const rootDir = join10(targetDir, layout.rootDir);
16044
16122
  return fileExists(rootDir);
16045
16123
  }
16046
16124
  var PROVIDER_SUBDIR_COMPONENTS = new Set([
@@ -16054,13 +16132,13 @@ var PROVIDER_SUBDIR_COMPONENTS = new Set([
16054
16132
  function componentToPath(targetDir, component) {
16055
16133
  if (component === "entry-md") {
16056
16134
  const layout = getProviderLayout();
16057
- return join9(targetDir, layout.entryFile);
16135
+ return join10(targetDir, layout.entryFile);
16058
16136
  }
16059
16137
  if (PROVIDER_SUBDIR_COMPONENTS.has(component)) {
16060
16138
  const layout = getProviderLayout();
16061
- return join9(targetDir, layout.rootDir, component);
16139
+ return join10(targetDir, layout.rootDir, component);
16062
16140
  }
16063
- return join9(targetDir, component);
16141
+ return join10(targetDir, component);
16064
16142
  }
16065
16143
  function buildInstalledPaths(targetDir, components) {
16066
16144
  return components.map((component) => componentToPath(targetDir, component));
@@ -16162,6 +16240,8 @@ async function initCommand(options) {
16162
16240
  console.log(" /plugin install context7");
16163
16241
  console.log("");
16164
16242
  console.log('See CLAUDE.md "외부 의존성" section for details.');
16243
+ await startServeBackground(targetDir).catch(() => {});
16244
+ console.log(`Web UI: http://127.0.0.1:${DEFAULT_PORT}`);
16165
16245
  return {
16166
16246
  success: true,
16167
16247
  message: i18n.t("cli.init.success"),
@@ -16175,7 +16255,7 @@ async function initCommand(options) {
16175
16255
  }
16176
16256
 
16177
16257
  // src/cli/list.ts
16178
- import { basename as basename3, dirname as dirname3, join as join10, relative as relative3 } from "node:path";
16258
+ import { basename as basename3, dirname as dirname3, join as join11, relative as relative3 } from "node:path";
16179
16259
  init_fs();
16180
16260
  var ALLOWED_TOP_LEVEL_KEYS = new Set(["name", "type", "description", "version", "category"]);
16181
16261
  function parseKeyValue(line) {
@@ -16240,12 +16320,12 @@ function extractAgentTypeFromFilename(filename) {
16240
16320
  return prefixMap[prefix] || "unknown";
16241
16321
  }
16242
16322
  function extractSkillCategoryFromPath(skillPath, baseDir, rootDir) {
16243
- const relativePath = relative3(join10(baseDir, rootDir, "skills"), skillPath);
16323
+ const relativePath = relative3(join11(baseDir, rootDir, "skills"), skillPath);
16244
16324
  const parts = relativePath.split("/").filter(Boolean);
16245
16325
  return parts[0] || "unknown";
16246
16326
  }
16247
16327
  function extractGuideCategoryFromPath(guidePath, baseDir) {
16248
- const relativePath = relative3(join10(baseDir, "guides"), guidePath);
16328
+ const relativePath = relative3(join11(baseDir, "guides"), guidePath);
16249
16329
  const parts = relativePath.split("/").filter(Boolean);
16250
16330
  return parts[0] || "unknown";
16251
16331
  }
@@ -16339,7 +16419,7 @@ async function tryExtractMarkdownDescription(mdPath, options = {}) {
16339
16419
  }
16340
16420
  }
16341
16421
  async function getAgents(targetDir, rootDir = ".claude", config) {
16342
- const agentsDir = join10(targetDir, rootDir, "agents");
16422
+ const agentsDir = join11(targetDir, rootDir, "agents");
16343
16423
  if (!await fileExists(agentsDir))
16344
16424
  return [];
16345
16425
  try {
@@ -16367,7 +16447,7 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
16367
16447
  }
16368
16448
  }
16369
16449
  async function getSkills(targetDir, rootDir = ".claude", config) {
16370
- const skillsDir = join10(targetDir, rootDir, "skills");
16450
+ const skillsDir = join11(targetDir, rootDir, "skills");
16371
16451
  if (!await fileExists(skillsDir))
16372
16452
  return [];
16373
16453
  try {
@@ -16377,7 +16457,7 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
16377
16457
  const skillMdFiles = await listFiles(skillsDir, { recursive: true, pattern: "SKILL.md" });
16378
16458
  const skills = await Promise.all(skillMdFiles.map(async (skillMdPath) => {
16379
16459
  const skillDir = dirname3(skillMdPath);
16380
- const indexYamlPath = join10(skillDir, "index.yaml");
16460
+ const indexYamlPath = join11(skillDir, "index.yaml");
16381
16461
  const { description, version } = await tryReadIndexYamlMetadata(indexYamlPath);
16382
16462
  const relativePath = relative3(targetDir, skillDir);
16383
16463
  return {
@@ -16396,7 +16476,7 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
16396
16476
  }
16397
16477
  }
16398
16478
  async function getGuides(targetDir, config) {
16399
- const guidesDir = join10(targetDir, "guides");
16479
+ const guidesDir = join11(targetDir, "guides");
16400
16480
  if (!await fileExists(guidesDir))
16401
16481
  return [];
16402
16482
  try {
@@ -16423,7 +16503,7 @@ async function getGuides(targetDir, config) {
16423
16503
  }
16424
16504
  var RULE_PRIORITY_ORDER = { MUST: 0, SHOULD: 1, MAY: 2 };
16425
16505
  async function getRules(targetDir, rootDir = ".claude", config) {
16426
- const rulesDir = join10(targetDir, rootDir, "rules");
16506
+ const rulesDir = join11(targetDir, rootDir, "rules");
16427
16507
  if (!await fileExists(rulesDir))
16428
16508
  return [];
16429
16509
  try {
@@ -16495,7 +16575,7 @@ function formatAsJson(components) {
16495
16575
  console.log(JSON.stringify(components, null, 2));
16496
16576
  }
16497
16577
  async function getHooks(targetDir, rootDir = ".claude") {
16498
- const hooksDir = join10(targetDir, rootDir, "hooks");
16578
+ const hooksDir = join11(targetDir, rootDir, "hooks");
16499
16579
  if (!await fileExists(hooksDir))
16500
16580
  return [];
16501
16581
  try {
@@ -16513,7 +16593,7 @@ async function getHooks(targetDir, rootDir = ".claude") {
16513
16593
  }
16514
16594
  }
16515
16595
  async function getContexts(targetDir, rootDir = ".claude") {
16516
- const contextsDir = join10(targetDir, rootDir, "contexts");
16596
+ const contextsDir = join11(targetDir, rootDir, "contexts");
16517
16597
  if (!await fileExists(contextsDir))
16518
16598
  return [];
16519
16599
  try {
@@ -16901,9 +16981,72 @@ async function securityCommand(_options = {}) {
16901
16981
  };
16902
16982
  }
16903
16983
 
16984
+ // src/cli/serve-commands.ts
16985
+ import { execFile, spawnSync as spawnSync2 } from "node:child_process";
16986
+ import { join as join12 } from "node:path";
16987
+ async function serveCommand(options) {
16988
+ const port = options.port !== undefined ? Number(options.port) : DEFAULT_PORT;
16989
+ if (!Number.isFinite(port) || port < 1 || port > 65535) {
16990
+ console.error(`Invalid port: ${options.port}`);
16991
+ process.exit(1);
16992
+ }
16993
+ const cwd = process.cwd();
16994
+ if (options.foreground === true) {
16995
+ runForeground(cwd, port);
16996
+ return;
16997
+ }
16998
+ await startServeBackground(cwd, port);
16999
+ const running = await isServeRunning();
17000
+ if (running) {
17001
+ console.log(`Web UI started: http://127.0.0.1:${port}`);
17002
+ if (options.open === true) {
17003
+ openBrowser(port);
17004
+ }
17005
+ } else {
17006
+ console.error("Failed to start Web UI server");
17007
+ process.exit(1);
17008
+ }
17009
+ }
17010
+ async function serveStopCommand() {
17011
+ const stopped = await stopServe();
17012
+ if (stopped) {
17013
+ console.log("Web UI server stopped");
17014
+ } else {
17015
+ console.log("Web UI server is not running");
17016
+ }
17017
+ }
17018
+ function runForeground(projectRoot, port) {
17019
+ const buildDir = findServeBuildDir(projectRoot);
17020
+ if (buildDir === null) {
17021
+ console.error("Web UI build not found. Run: cd packages/serve && bun run build");
17022
+ process.exit(1);
17023
+ }
17024
+ console.log(`Web UI: http://127.0.0.1:${port}`);
17025
+ spawnSync2("node", [join12(buildDir, "index.js")], {
17026
+ env: {
17027
+ ...process.env,
17028
+ PORT: String(port),
17029
+ HOST: "127.0.0.1",
17030
+ OMCUSTOM_PROJECT_ROOT: projectRoot
17031
+ },
17032
+ stdio: "inherit"
17033
+ });
17034
+ }
17035
+ function openBrowser(port) {
17036
+ const url = `http://127.0.0.1:${port}`;
17037
+ const platform = process.platform;
17038
+ if (platform === "darwin") {
17039
+ execFile("open", [url], () => {});
17040
+ } else if (platform === "win32") {
17041
+ execFile("cmd", ["/c", "start", url], () => {});
17042
+ } else {
17043
+ execFile("xdg-open", [url], () => {});
17044
+ }
17045
+ }
17046
+
16904
17047
  // src/core/updater.ts
16905
17048
  init_fs();
16906
- import { join as join11 } from "node:path";
17049
+ import { join as join13 } from "node:path";
16907
17050
 
16908
17051
  // src/core/entry-merger.ts
16909
17052
  var MANAGED_START = "<!-- omcustom:start -->";
@@ -17152,7 +17295,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
17152
17295
  }
17153
17296
  async function updateEntryDoc(targetDir, config, options) {
17154
17297
  const layout = getProviderLayout();
17155
- const entryPath = join11(targetDir, layout.entryFile);
17298
+ const entryPath = join13(targetDir, layout.entryFile);
17156
17299
  const templateName = getEntryTemplateName2(config.language);
17157
17300
  const templatePath = resolveTemplatePath(templateName);
17158
17301
  if (!await fileExists(templatePath)) {
@@ -17298,7 +17441,7 @@ async function collectProtectedSkipPaths(srcPath, destPath, componentPath, force
17298
17441
  }
17299
17442
  const protectedRelative = await findProtectedFilesInDir(srcPath, componentPath);
17300
17443
  const path3 = await import("node:path");
17301
- const skipPaths = protectedRelative.map((p) => path3.relative(destPath, join11(destPath, p)));
17444
+ const skipPaths = protectedRelative.map((p) => path3.relative(destPath, join13(destPath, p)));
17302
17445
  return { skipPaths, warnedPaths: protectedRelative };
17303
17446
  }
17304
17447
  function isEntryProtected(relPath, componentRelativePrefix) {
@@ -17339,7 +17482,7 @@ async function updateComponent(targetDir, component, customizations, options, co
17339
17482
  const preservedFiles = [];
17340
17483
  const componentPath = getComponentPath2(component);
17341
17484
  const srcPath = resolveTemplatePath(componentPath);
17342
- const destPath = join11(targetDir, componentPath);
17485
+ const destPath = join13(targetDir, componentPath);
17343
17486
  const customComponents = config.customComponents || [];
17344
17487
  const skipPaths = [];
17345
17488
  if (customizations && !options.forceOverwriteAll) {
@@ -17370,7 +17513,7 @@ async function updateComponent(targetDir, component, customizations, options, co
17370
17513
  }
17371
17514
  skipPaths.push(...protectedSkipPaths);
17372
17515
  const path3 = await import("node:path");
17373
- const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join11(targetDir, p)));
17516
+ const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join13(targetDir, p)));
17374
17517
  const uniqueSkipPaths = [...new Set(normalizedSkipPaths)];
17375
17518
  await copyDirectory(srcPath, destPath, {
17376
17519
  overwrite: true,
@@ -17392,12 +17535,12 @@ async function syncRootLevelFiles(targetDir, options) {
17392
17535
  const layout = getProviderLayout();
17393
17536
  const synced = [];
17394
17537
  for (const fileName of ROOT_LEVEL_FILES) {
17395
- const srcPath = resolveTemplatePath(join11(layout.rootDir, fileName));
17538
+ const srcPath = resolveTemplatePath(join13(layout.rootDir, fileName));
17396
17539
  if (!await fileExists(srcPath)) {
17397
17540
  continue;
17398
17541
  }
17399
- const destPath = join11(targetDir, layout.rootDir, fileName);
17400
- await ensureDirectory(join11(destPath, ".."));
17542
+ const destPath = join13(targetDir, layout.rootDir, fileName);
17543
+ await ensureDirectory(join13(destPath, ".."));
17401
17544
  await fs3.copyFile(srcPath, destPath);
17402
17545
  if (fileName.endsWith(".sh")) {
17403
17546
  await fs3.chmod(destPath, 493);
@@ -17432,7 +17575,7 @@ async function removeDeprecatedFiles(targetDir, options) {
17432
17575
  });
17433
17576
  continue;
17434
17577
  }
17435
- const fullPath = join11(targetDir, entry.path);
17578
+ const fullPath = join13(targetDir, entry.path);
17436
17579
  if (await fileExists(fullPath)) {
17437
17580
  await fs3.unlink(fullPath);
17438
17581
  removed.push(entry.path);
@@ -17456,26 +17599,26 @@ function getComponentPath2(component) {
17456
17599
  }
17457
17600
  async function backupInstallation(targetDir) {
17458
17601
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
17459
- const backupDir = join11(targetDir, `.omcustom-backup-${timestamp}`);
17602
+ const backupDir = join13(targetDir, `.omcustom-backup-${timestamp}`);
17460
17603
  const fs3 = await import("node:fs/promises");
17461
17604
  await ensureDirectory(backupDir);
17462
17605
  const layout = getProviderLayout();
17463
17606
  const dirsToBackup = [layout.rootDir, "guides"];
17464
17607
  for (const dir2 of dirsToBackup) {
17465
- const srcPath = join11(targetDir, dir2);
17608
+ const srcPath = join13(targetDir, dir2);
17466
17609
  if (await fileExists(srcPath)) {
17467
- const destPath = join11(backupDir, dir2);
17610
+ const destPath = join13(backupDir, dir2);
17468
17611
  await copyDirectory(srcPath, destPath, { overwrite: true });
17469
17612
  }
17470
17613
  }
17471
- const entryPath = join11(targetDir, layout.entryFile);
17614
+ const entryPath = join13(targetDir, layout.entryFile);
17472
17615
  if (await fileExists(entryPath)) {
17473
- await fs3.copyFile(entryPath, join11(backupDir, layout.entryFile));
17616
+ await fs3.copyFile(entryPath, join13(backupDir, layout.entryFile));
17474
17617
  }
17475
17618
  return backupDir;
17476
17619
  }
17477
17620
  async function loadCustomizationManifest(targetDir) {
17478
- const manifestPath = join11(targetDir, CUSTOMIZATION_MANIFEST_FILE);
17621
+ const manifestPath = join13(targetDir, CUSTOMIZATION_MANIFEST_FILE);
17479
17622
  if (await fileExists(manifestPath)) {
17480
17623
  return readJsonFile(manifestPath);
17481
17624
  }
@@ -17585,6 +17728,12 @@ function createProgram() {
17585
17728
  const result = await securityCommand(options);
17586
17729
  process.exitCode = result.success ? 0 : 1;
17587
17730
  });
17731
+ program2.command("serve").description("Start the web UI server").option("-p, --port <port>", "Port number", "4321").option("--open", "Open browser automatically").option("--foreground", "Run in foreground (not detached)").action(async (options) => {
17732
+ await serveCommand(options);
17733
+ });
17734
+ program2.command("serve-stop").description("Stop the web UI server").action(async () => {
17735
+ await serveStopCommand();
17736
+ });
17588
17737
  program2.hook("preAction", async (thisCommand, actionCommand) => {
17589
17738
  const opts = thisCommand.optsWithGlobals();
17590
17739
  const skipCheck = opts.skipVersionCheck || false;
@@ -17600,6 +17749,13 @@ function createProgram() {
17600
17749
  console.warn(warnings);
17601
17750
  console.warn("");
17602
17751
  }
17752
+ const commandName = actionCommand.name();
17753
+ if (commandName !== "serve-stop" && commandName !== "serve") {
17754
+ const cwd = process.cwd();
17755
+ if (existsSync3(join14(cwd, ".claude"))) {
17756
+ startServeBackground(cwd).catch(() => {});
17757
+ }
17758
+ }
17603
17759
  });
17604
17760
  return program2;
17605
17761
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oh-my-customcode",
3
3
  "workspaces": ["packages/*"],
4
- "version": "0.42.3",
4
+ "version": "0.43.0",
5
5
  "description": "Batteries-included agent harness for Claude Code",
6
6
  "type": "module",
7
7
  "bin": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.42.3",
2
+ "version": "0.43.0",
3
3
  "lastUpdated": "2026-03-16T00:00:00.000Z",
4
4
  "components": [
5
5
  {