ocx 1.0.21 → 1.1.1

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
@@ -9864,6 +9864,18 @@ function isAbsolutePath(p) {
9864
9864
  var safeRelativePathSchema = exports_external.string().refine((val) => !val.includes("\x00"), "Path cannot contain null bytes").refine((val) => !val.split(/[/\\]/).some((seg) => seg === ".."), "Path cannot contain '..'").refine((val) => !isAbsolutePath(val), "Path must be relative, not absolute");
9865
9865
 
9866
9866
  // src/schemas/registry.ts
9867
+ var npmSpecifierSchema = exports_external.string().refine((val) => val.startsWith("npm:"), {
9868
+ message: 'npm specifier must start with "npm:" prefix'
9869
+ }).refine((val) => {
9870
+ const remainder = val.slice(4);
9871
+ if (!remainder)
9872
+ return false;
9873
+ if (remainder.includes("..") || remainder.includes("/./"))
9874
+ return false;
9875
+ return true;
9876
+ }, {
9877
+ message: "Invalid npm specifier format"
9878
+ });
9867
9879
  var openCodeNameSchema = exports_external.string().min(1, "Name cannot be empty").max(64, "Name cannot exceed 64 characters").regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, {
9868
9880
  message: "Must be lowercase alphanumeric with single hyphen separators (e.g., 'my-component', 'my-plugin'). Cannot start/end with hyphen or have consecutive hyphens."
9869
9881
  });
@@ -10420,7 +10432,7 @@ class GhostConfigProvider {
10420
10432
  // package.json
10421
10433
  var package_default = {
10422
10434
  name: "ocx",
10423
- version: "1.0.21",
10435
+ version: "1.1.1",
10424
10436
  description: "OCX CLI - ShadCN-style registry for OpenCode extensions. Install agents, plugins, skills, and MCP servers.",
10425
10437
  author: "kdcokenny",
10426
10438
  license: "MIT",
@@ -11082,14 +11094,14 @@ var sharedOptions = {
11082
11094
  cwd: () => new Option("--cwd <path>", "Working directory").default(process.cwd()),
11083
11095
  quiet: () => new Option("-q, --quiet", "Suppress output"),
11084
11096
  json: () => new Option("--json", "Output as JSON"),
11085
- yes: () => new Option("-y, --yes", "Skip confirmation prompts"),
11097
+ force: () => new Option("-f, --force", "Skip confirmation prompts"),
11086
11098
  verbose: () => new Option("-v, --verbose", "Verbose output")
11087
11099
  };
11088
11100
  function addCommonOptions(cmd) {
11089
11101
  return cmd.addOption(sharedOptions.cwd()).addOption(sharedOptions.quiet()).addOption(sharedOptions.json());
11090
11102
  }
11091
- function addConfirmationOptions(cmd) {
11092
- return cmd.addOption(sharedOptions.yes());
11103
+ function addForceOption(cmd) {
11104
+ return cmd.addOption(sharedOptions.force());
11093
11105
  }
11094
11106
  function addVerboseOption(cmd) {
11095
11107
  return cmd.addOption(sharedOptions.verbose());
@@ -12486,11 +12498,154 @@ function warnCompatIssues(registryName, issues) {
12486
12498
  logger.log(formatCompatWarning(registryName, issue));
12487
12499
  }
12488
12500
  }
12501
+ // src/utils/npm-registry.ts
12502
+ var NPM_REGISTRY_BASE = "https://registry.npmjs.org";
12503
+ var NPM_FETCH_TIMEOUT_MS = 30000;
12504
+ var NPM_NAME_REGEX = /^(?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/;
12505
+ var MAX_NAME_LENGTH = 214;
12506
+ function validateNpmPackageName(name) {
12507
+ if (!name) {
12508
+ throw new ValidationError("npm package name cannot be empty");
12509
+ }
12510
+ if (name.length > MAX_NAME_LENGTH) {
12511
+ throw new ValidationError(`npm package name exceeds maximum length of ${MAX_NAME_LENGTH} characters: \`${name}\``);
12512
+ }
12513
+ if (name.includes("..") || name.includes("/./") || name.startsWith("./")) {
12514
+ throw new ValidationError(`Invalid npm package name - path traversal detected: \`${name}\``);
12515
+ }
12516
+ if (!NPM_NAME_REGEX.test(name)) {
12517
+ throw new ValidationError(`Invalid npm package name: \`${name}\`. ` + "Must be lowercase, start with alphanumeric, and contain only letters, numbers, hyphens, dots, or underscores.");
12518
+ }
12519
+ }
12520
+ function parseNpmSpecifier(specifier) {
12521
+ if (!specifier?.trim()) {
12522
+ throw new ValidationError("npm specifier cannot be empty");
12523
+ }
12524
+ const trimmed = specifier.trim();
12525
+ if (!trimmed.startsWith("npm:")) {
12526
+ throw new ValidationError(`Invalid npm specifier: \`${specifier}\`. Must start with \`npm:\` prefix.`);
12527
+ }
12528
+ const remainder = trimmed.slice(4);
12529
+ if (!remainder) {
12530
+ throw new ValidationError(`Invalid npm specifier: \`${specifier}\`. Package name is required.`);
12531
+ }
12532
+ const lastAt = remainder.lastIndexOf("@");
12533
+ let name;
12534
+ let version;
12535
+ if (lastAt > 0) {
12536
+ const beforeAt = remainder.slice(0, lastAt);
12537
+ const afterAt = remainder.slice(lastAt + 1);
12538
+ if (beforeAt.includes("/") || !beforeAt.startsWith("@")) {
12539
+ name = beforeAt;
12540
+ version = afterAt || undefined;
12541
+ } else {
12542
+ throw new ValidationError(`Invalid npm specifier: \`${specifier}\`. Scoped packages must have format @scope/pkg.`);
12543
+ }
12544
+ } else {
12545
+ name = remainder;
12546
+ }
12547
+ validateNpmPackageName(name);
12548
+ return { type: "npm", name, version };
12549
+ }
12550
+ function isNpmSpecifier(input) {
12551
+ return input.trim().startsWith("npm:");
12552
+ }
12553
+ async function validateNpmPackage(packageName) {
12554
+ validateNpmPackageName(packageName);
12555
+ const encodedName = packageName.startsWith("@") ? `@${encodeURIComponent(packageName.slice(1))}` : encodeURIComponent(packageName);
12556
+ const url = `${NPM_REGISTRY_BASE}/${encodedName}`;
12557
+ try {
12558
+ const controller = new AbortController;
12559
+ const timeoutId = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT_MS);
12560
+ const response = await fetch(url, {
12561
+ signal: controller.signal,
12562
+ headers: {
12563
+ Accept: "application/json"
12564
+ }
12565
+ });
12566
+ clearTimeout(timeoutId);
12567
+ if (response.status === 404) {
12568
+ throw new NotFoundError(`npm package \`${packageName}\` not found on registry`);
12569
+ }
12570
+ if (!response.ok) {
12571
+ throw new NetworkError(`Failed to fetch npm package \`${packageName}\`: HTTP ${response.status} ${response.statusText}`);
12572
+ }
12573
+ const data = await response.json();
12574
+ return data;
12575
+ } catch (error) {
12576
+ if (error instanceof NotFoundError || error instanceof NetworkError) {
12577
+ throw error;
12578
+ }
12579
+ if (error instanceof Error && error.name === "AbortError") {
12580
+ throw new NetworkError(`Request to npm registry timed out after ${NPM_FETCH_TIMEOUT_MS / 1000}s for package \`${packageName}\``);
12581
+ }
12582
+ const message = error instanceof Error ? error.message : String(error);
12583
+ throw new NetworkError(`Failed to fetch npm package \`${packageName}\`: ${message}`);
12584
+ }
12585
+ }
12586
+ function formatPluginEntry(name, version) {
12587
+ return version ? `${name}@${version}` : name;
12588
+ }
12589
+ function validateOpenCodePlugin(packageJson) {
12590
+ const warnings = [];
12591
+ if (packageJson.type !== "module") {
12592
+ throw new ValidationError(`Package \`${packageJson.name}\` is not an ESM module (missing "type": "module" in package.json)`);
12593
+ }
12594
+ const hasMain = Boolean(packageJson.main);
12595
+ const hasExports = packageJson.exports !== undefined;
12596
+ if (!hasMain && !hasExports) {
12597
+ throw new ValidationError(`Package \`${packageJson.name}\` has no entry point (missing "main" or "exports")`);
12598
+ }
12599
+ if (!packageJson.name.includes("opencode")) {
12600
+ warnings.push(`Package name \`${packageJson.name}\` doesn't contain "opencode" - this may not be an OpenCode plugin`);
12601
+ }
12602
+ return { valid: true, warnings };
12603
+ }
12604
+ async function fetchPackageVersion(packageName, version) {
12605
+ const metadata = await validateNpmPackage(packageName);
12606
+ const resolvedVersion = version ?? metadata["dist-tags"].latest;
12607
+ const versionData = metadata.versions[resolvedVersion];
12608
+ if (!versionData) {
12609
+ throw new NotFoundError(`Version \`${resolvedVersion}\` not found for npm package \`${packageName}\``);
12610
+ }
12611
+ return versionData;
12612
+ }
12613
+ function extractPackageName(pluginEntry) {
12614
+ const trimmed = pluginEntry.trim();
12615
+ const lastAt = trimmed.lastIndexOf("@");
12616
+ if (lastAt <= 0) {
12617
+ return trimmed;
12618
+ }
12619
+ const beforeAt = trimmed.slice(0, lastAt);
12620
+ if (beforeAt.includes("/") || !beforeAt.startsWith("@")) {
12621
+ return beforeAt;
12622
+ }
12623
+ return trimmed;
12624
+ }
12625
+
12489
12626
  // src/commands/add.ts
12627
+ function parseAddInput(input) {
12628
+ if (!input?.trim()) {
12629
+ throw new ValidationError("Component name cannot be empty");
12630
+ }
12631
+ const trimmed = input.trim();
12632
+ if (isNpmSpecifier(trimmed)) {
12633
+ const parsed = parseNpmSpecifier(trimmed);
12634
+ return { type: "npm", name: parsed.name, version: parsed.version };
12635
+ }
12636
+ if (trimmed.includes("/")) {
12637
+ const { namespace, component } = parseQualifiedComponent(trimmed);
12638
+ return { type: "registry", namespace, component };
12639
+ }
12640
+ return { type: "registry", namespace: "", component: trimmed };
12641
+ }
12490
12642
  function registerAddCommand(program2) {
12491
- const cmd = program2.command("add").description("Add components to your project").argument("<components...>", "Components to install").option("--dry-run", "Show what would be installed without making changes").option("--skip-compat-check", "Skip version compatibility checks");
12643
+ const cmd = program2.command("add").description(`Add components or npm plugins to your project.
12644
+
12645
+ ` + ` Registry components: ocx add namespace/component
12646
+ ` + " npm plugins: ocx add npm:package-name[@version]").argument("<components...>", "Components to install (namespace/component or npm:package[@version])").option("--dry-run", "Show what would be installed without making changes").option("--skip-compat-check", "Skip version compatibility checks").option("--trust", "Skip npm plugin validation (for packages that don't follow conventions)");
12492
12647
  addCommonOptions(cmd);
12493
- addConfirmationOptions(cmd);
12648
+ addForceOption(cmd);
12494
12649
  addVerboseOption(cmd);
12495
12650
  cmd.action(async (components, options2) => {
12496
12651
  try {
@@ -12502,6 +12657,104 @@ function registerAddCommand(program2) {
12502
12657
  });
12503
12658
  }
12504
12659
  async function runAddCore(componentNames, options2, provider) {
12660
+ const cwd = provider.cwd;
12661
+ const parsedInputs = componentNames.map(parseAddInput);
12662
+ const npmInputs = parsedInputs.filter((i) => i.type === "npm");
12663
+ const registryInputs = parsedInputs.filter((i) => i.type === "registry");
12664
+ if (npmInputs.length > 0) {
12665
+ await handleNpmPlugins(npmInputs, options2, cwd);
12666
+ }
12667
+ if (registryInputs.length > 0) {
12668
+ const registryComponentNames = registryInputs.map((i) => i.namespace ? `${i.namespace}/${i.component}` : i.component);
12669
+ await runRegistryAddCore(registryComponentNames, options2, provider);
12670
+ }
12671
+ }
12672
+ async function handleNpmPlugins(inputs, options2, cwd) {
12673
+ const spin = options2.quiet ? null : createSpinner({ text: "Validating npm packages..." });
12674
+ spin?.start();
12675
+ try {
12676
+ const allWarnings = [];
12677
+ for (const input of inputs) {
12678
+ await validateNpmPackage(input.name);
12679
+ if (!options2.trust) {
12680
+ try {
12681
+ const versionData = await fetchPackageVersion(input.name, input.version);
12682
+ const result = validateOpenCodePlugin(versionData);
12683
+ allWarnings.push(...result.warnings);
12684
+ } catch (error) {
12685
+ if (error instanceof ValidationError) {
12686
+ spin?.fail("Plugin validation failed");
12687
+ throw new ValidationError(`${error.message}
12688
+ ` + `hint OpenCode plugins must be ESM modules with an entry point
12689
+ ` + `hint Use \`--trust\` to add anyway`);
12690
+ }
12691
+ throw error;
12692
+ }
12693
+ }
12694
+ }
12695
+ spin?.succeed(`Validated ${inputs.length} npm package(s)`);
12696
+ if (allWarnings.length > 0 && !options2.quiet) {
12697
+ logger.info("");
12698
+ for (const warning of allWarnings) {
12699
+ logger.warn(warning);
12700
+ }
12701
+ }
12702
+ const existingConfig = await readOpencodeJsonConfig(cwd);
12703
+ const existingPlugins = existingConfig?.config.plugin ?? [];
12704
+ const existingPluginMap = new Map;
12705
+ for (const plugin of existingPlugins) {
12706
+ const name = extractPackageName(plugin);
12707
+ existingPluginMap.set(name, plugin);
12708
+ }
12709
+ const pluginsToAdd = [];
12710
+ const conflicts = [];
12711
+ for (const input of inputs) {
12712
+ const existingEntry = existingPluginMap.get(input.name);
12713
+ if (existingEntry) {
12714
+ if (!options2.force) {
12715
+ conflicts.push(input.name);
12716
+ } else {
12717
+ existingPluginMap.set(input.name, formatPluginEntry(input.name, input.version));
12718
+ }
12719
+ } else {
12720
+ pluginsToAdd.push(formatPluginEntry(input.name, input.version));
12721
+ }
12722
+ }
12723
+ if (conflicts.length > 0) {
12724
+ throw new ConflictError(`Plugin(s) already exist in opencode.json: ${conflicts.join(", ")}.
12725
+ ` + "Use --force to replace existing entries.");
12726
+ }
12727
+ const finalPlugins = [...existingPluginMap.values(), ...pluginsToAdd];
12728
+ if (options2.dryRun) {
12729
+ logger.info("");
12730
+ logger.info("Dry run - no changes made");
12731
+ logger.info("");
12732
+ logger.info("Would add npm plugins:");
12733
+ for (const input of inputs) {
12734
+ logger.info(` ${formatPluginEntry(input.name, input.version)}`);
12735
+ }
12736
+ return;
12737
+ }
12738
+ await updateOpencodeJsonConfig(cwd, { plugin: finalPlugins });
12739
+ if (!options2.quiet) {
12740
+ logger.info("");
12741
+ logger.success(`Added ${inputs.length} npm plugin(s) to opencode.json`);
12742
+ for (const input of inputs) {
12743
+ logger.info(` \u2713 ${formatPluginEntry(input.name, input.version)}`);
12744
+ }
12745
+ }
12746
+ if (options2.json) {
12747
+ console.log(JSON.stringify({
12748
+ success: true,
12749
+ plugins: inputs.map((i) => formatPluginEntry(i.name, i.version))
12750
+ }, null, 2));
12751
+ }
12752
+ } catch (error) {
12753
+ spin?.fail("Failed to add npm plugins");
12754
+ throw error;
12755
+ }
12756
+ }
12757
+ async function runRegistryAddCore(componentNames, options2, provider) {
12505
12758
  const cwd = provider.cwd;
12506
12759
  const lockPath = join2(cwd, "ocx.lock");
12507
12760
  const registries = provider.getRegistries();
@@ -12595,7 +12848,7 @@ async function runAddCore(componentNames, options2, provider) {
12595
12848
  if (existsSync(targetPath)) {
12596
12849
  const existingContent = await Bun.file(targetPath).text();
12597
12850
  const incomingContent = file.content.toString("utf-8");
12598
- if (!isContentIdentical(existingContent, incomingContent) && !options2.yes) {
12851
+ if (!isContentIdentical(existingContent, incomingContent) && !options2.force) {
12599
12852
  allConflicts.push(componentFile.target);
12600
12853
  }
12601
12854
  }
@@ -12609,14 +12862,14 @@ async function runAddCore(componentNames, options2, provider) {
12609
12862
  }
12610
12863
  logger.error("");
12611
12864
  logger.error("These files have been modified since installation.");
12612
- logger.error("Use --yes to overwrite, or review the changes first.");
12613
- throw new ConflictError(`${allConflicts.length} file(s) have conflicts. Use --yes to overwrite.`);
12865
+ logger.error("Use --force to overwrite, or review the changes first.");
12866
+ throw new ConflictError(`${allConflicts.length} file(s) have conflicts. Use --force to overwrite.`);
12614
12867
  }
12615
12868
  const installSpin = options2.quiet ? null : createSpinner({ text: "Installing components..." });
12616
12869
  installSpin?.start();
12617
12870
  for (const { component, files, computedHash } of componentBundles) {
12618
12871
  const installResult = await installComponent(component, files, cwd, {
12619
- yes: options2.yes,
12872
+ force: options2.force,
12620
12873
  verbose: options2.verbose
12621
12874
  });
12622
12875
  if (options2.verbose) {
@@ -13871,9 +14124,9 @@ Diff for ${res.name}:`));
13871
14124
 
13872
14125
  // src/commands/ghost/add.ts
13873
14126
  function registerGhostAddCommand(parent) {
13874
- const cmd = parent.command("add").description("Add components using ghost mode registries").argument("<components...>", "Components to install").option("--dry-run", "Show what would be installed without making changes").option("--skip-compat-check", "Skip version compatibility checks");
14127
+ const cmd = parent.command("add").description("Add components using ghost mode registries").argument("<components...>", "Components to install").option("--dry-run", "Show what would be installed without making changes").option("--skip-compat-check", "Skip version compatibility checks").option("--trust", "Skip npm plugin validation");
13875
14128
  addCommonOptions(cmd);
13876
- addConfirmationOptions(cmd);
14129
+ addForceOption(cmd);
13877
14130
  addVerboseOption(cmd);
13878
14131
  cmd.action(async (components, options2) => {
13879
14132
  try {
@@ -13931,12 +14184,9 @@ var DEFAULT_GHOST_CONFIG = `{
13931
14184
  // This config is used when running commands with \`ocx ghost\` or \`ocx g\`
13932
14185
  // Note: OpenCode settings go in ~/.config/ocx/opencode.jsonc (see: ocx ghost opencode --edit)
13933
14186
 
13934
- // Component registries to use
13935
- "registries": {
13936
- "default": {
13937
- "url": "https://registry.opencode.ai"
13938
- }
13939
- },
14187
+ // Component registries - add your registries here
14188
+ // Example: "myregistry": { "url": "https://example.com/registry" }
14189
+ "registries": {},
13940
14190
 
13941
14191
  // Where to install components (relative to project root)
13942
14192
  "componentPath": "src/components"
@@ -14079,9 +14329,9 @@ function filterExcludedPaths(excludedPaths, includePatterns, excludePatterns) {
14079
14329
 
14080
14330
  // src/utils/symlink-farm.ts
14081
14331
  import { randomBytes } from "crypto";
14082
- import { readdir, rename, rm, stat, symlink } from "fs/promises";
14332
+ import { mkdir as mkdir5, readdir, rename, rm, stat, symlink } from "fs/promises";
14083
14333
  import { tmpdir } from "os";
14084
- import { isAbsolute, join as join6 } from "path";
14334
+ import { dirname as dirname5, isAbsolute, join as join6, relative as relative2 } from "path";
14085
14335
  var STALE_SESSION_THRESHOLD_MS = 24 * 60 * 60 * 1000;
14086
14336
  var REMOVING_THRESHOLD_MS = 60 * 60 * 1000;
14087
14337
  var GHOST_DIR_PREFIX = "ocx-ghost-";
@@ -14109,6 +14359,32 @@ async function createSymlinkFarm(sourceDir, excludePaths) {
14109
14359
  throw error;
14110
14360
  }
14111
14361
  }
14362
+ async function injectGhostFiles(tempDir, sourceDir, injectPaths) {
14363
+ if (!isAbsolute(tempDir)) {
14364
+ throw new Error(`tempDir must be an absolute path, got: ${tempDir}`);
14365
+ }
14366
+ if (!isAbsolute(sourceDir)) {
14367
+ throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
14368
+ }
14369
+ for (const injectPath of injectPaths) {
14370
+ const relativePath = relative2(sourceDir, injectPath);
14371
+ if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
14372
+ throw new Error(`injectPath must be within sourceDir: ${injectPath}`);
14373
+ }
14374
+ const targetPath = join6(tempDir, relativePath);
14375
+ const parentDir = dirname5(targetPath);
14376
+ if (parentDir !== tempDir) {
14377
+ await mkdir5(parentDir, { recursive: true });
14378
+ }
14379
+ try {
14380
+ await symlink(injectPath, targetPath);
14381
+ } catch (err) {
14382
+ if (err.code !== "EEXIST") {
14383
+ throw new Error(`Failed to inject ${injectPath} \u2192 ${targetPath}: ${err.message}`);
14384
+ }
14385
+ }
14386
+ }
14387
+ }
14112
14388
  async function cleanupSymlinkFarm(tempDir) {
14113
14389
  const removingPath = `${tempDir}${REMOVING_SUFFIX}`;
14114
14390
  try {
@@ -14189,6 +14465,8 @@ async function runGhostOpenCode(args, options2) {
14189
14465
  const ghostConfig = await loadGhostConfig();
14190
14466
  const excludePaths = filterExcludedPaths(discoveredPaths, ghostConfig.include, ghostConfig.exclude);
14191
14467
  const tempDir = await createSymlinkFarm(cwd, excludePaths);
14468
+ const ghostFiles = await discoverProjectFiles(ghostConfigDir, ghostConfigDir);
14469
+ await injectGhostFiles(tempDir, ghostConfigDir, ghostFiles);
14192
14470
  let cleanupDone = false;
14193
14471
  const performCleanup = async () => {
14194
14472
  if (cleanupDone)
@@ -14239,7 +14517,7 @@ async function runGhostOpenCode(args, options2) {
14239
14517
  }
14240
14518
 
14241
14519
  // src/commands/registry.ts
14242
- async function runRegistryAddCore(url, options2, callbacks) {
14520
+ async function runRegistryAddCore2(url, options2, callbacks) {
14243
14521
  if (callbacks.isLocked?.()) {
14244
14522
  throw new Error("Registries are locked. Cannot add.");
14245
14523
  }
@@ -14285,7 +14563,7 @@ function registerRegistryCommand(program2) {
14285
14563
  logger.error("No ocx.jsonc found. Run 'ocx init' first.");
14286
14564
  process.exit(1);
14287
14565
  }
14288
- const result = await runRegistryAddCore(url, options2, {
14566
+ const result = await runRegistryAddCore2(url, options2, {
14289
14567
  getRegistries: () => config.registries,
14290
14568
  isLocked: () => config.lockRegistries ?? false,
14291
14569
  setRegistry: async (name, regConfig) => {
@@ -14380,7 +14658,7 @@ function registerGhostRegistryCommand(parent) {
14380
14658
  try {
14381
14659
  await ensureGhostInitialized();
14382
14660
  const config = await loadGhostConfig();
14383
- const result = await runRegistryAddCore(url, options2, {
14661
+ const result = await runRegistryAddCore2(url, options2, {
14384
14662
  getRegistries: () => config.registries,
14385
14663
  setRegistry: async (name, regConfig) => {
14386
14664
  config.registries[name] = regConfig;
@@ -14606,7 +14884,7 @@ function registerGhostCommand(program2) {
14606
14884
 
14607
14885
  // src/commands/init.ts
14608
14886
  import { existsSync as existsSync2 } from "fs";
14609
- import { cp, mkdir as mkdir5, readdir as readdir2, readFile, rm as rm2, writeFile as writeFile3 } from "fs/promises";
14887
+ import { cp, mkdir as mkdir6, readdir as readdir2, readFile, rm as rm2, writeFile as writeFile3 } from "fs/promises";
14610
14888
  import { join as join7 } from "path";
14611
14889
  var TEMPLATE_REPO = "kdcokenny/ocx";
14612
14890
  var TEMPLATE_PATH = "examples/registry-starter";
@@ -14684,7 +14962,7 @@ async function runInitRegistry(directory, options2) {
14684
14962
  if (spin)
14685
14963
  spin.text = options2.local ? "Copying template..." : "Fetching template...";
14686
14964
  if (options2.local) {
14687
- await mkdir5(cwd, { recursive: true });
14965
+ await mkdir6(cwd, { recursive: true });
14688
14966
  await copyDir(options2.local, cwd);
14689
14967
  } else {
14690
14968
  const version = options2.canary ? "main" : await getLatestVersion();
@@ -14733,7 +15011,7 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
14733
15011
  throw new NetworkError(`Failed to fetch template from ${tarballUrl}: ${response.statusText}`);
14734
15012
  }
14735
15013
  const tempDir = join7(destDir, ".ocx-temp");
14736
- await mkdir5(tempDir, { recursive: true });
15014
+ await mkdir6(tempDir, { recursive: true });
14737
15015
  try {
14738
15016
  const tarPath = join7(tempDir, "template.tar.gz");
14739
15017
  const arrayBuffer = await response.arrayBuffer();
@@ -14780,8 +15058,8 @@ async function replacePlaceholders(dir, values) {
14780
15058
  // src/commands/update.ts
14781
15059
  import { createHash as createHash2 } from "crypto";
14782
15060
  import { existsSync as existsSync3 } from "fs";
14783
- import { mkdir as mkdir6, writeFile as writeFile4 } from "fs/promises";
14784
- import { dirname as dirname5, join as join8 } from "path";
15061
+ import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
15062
+ import { dirname as dirname6, join as join8 } from "path";
14785
15063
  function registerUpdateCommand(program2) {
14786
15064
  program2.command("update [components...]").description("Update installed components (use @version suffix to pin, e.g., kdco/agents@1.2.0)").option("--all", "Update all installed components").option("--registry <name>", "Update all components from a specific registry").option("--dry-run", "Preview changes without applying").option("--cwd <path>", "Working directory", process.cwd()).option("-q, --quiet", "Suppress output").option("-v, --verbose", "Verbose output").option("--json", "Output as JSON").action(async (components, options2) => {
14787
15065
  try {
@@ -14919,9 +15197,9 @@ Version cannot be empty. Use 'kdco/agents@1.2.0' or omit the version for latest.
14919
15197
  if (!fileObj)
14920
15198
  continue;
14921
15199
  const targetPath = join8(cwd, fileObj.target);
14922
- const targetDir = dirname5(targetPath);
15200
+ const targetDir = dirname6(targetPath);
14923
15201
  if (!existsSync3(targetDir)) {
14924
- await mkdir6(targetDir, { recursive: true });
15202
+ await mkdir7(targetDir, { recursive: true });
14925
15203
  }
14926
15204
  await writeFile4(targetPath, file.content);
14927
15205
  if (options2.verbose) {
@@ -15056,7 +15334,7 @@ async function hashBundle2(files) {
15056
15334
  `));
15057
15335
  }
15058
15336
  // src/index.ts
15059
- var version = "1.0.21";
15337
+ var version = "1.1.1";
15060
15338
  async function main2() {
15061
15339
  const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
15062
15340
  registerInitCommand(program2);
@@ -15083,4 +15361,4 @@ export {
15083
15361
  buildRegistry
15084
15362
  };
15085
15363
 
15086
- //# debugId=4A28ED9CEDFE469F64756E2164756E21
15364
+ //# debugId=66554E2EE245482F64756E2164756E21