ocx 1.0.20 → 1.1.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/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.20",
10435
+ version: "1.1.0",
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",
@@ -10691,6 +10703,7 @@ async function resolveDependencies(registries, componentNames) {
10691
10703
  }
10692
10704
 
10693
10705
  // src/updaters/update-opencode-config.ts
10706
+ import path3 from "path";
10694
10707
  var JSONC_OPTIONS = {
10695
10708
  formattingOptions: {
10696
10709
  tabSize: 2,
@@ -10699,9 +10712,28 @@ var JSONC_OPTIONS = {
10699
10712
  `
10700
10713
  }
10701
10714
  };
10715
+ var OPENCODE_CONFIG_TEMPLATE = `{
10716
+ "$schema": "https://opencode.ai/config.json"
10717
+ // Add MCP servers, tools, plugins here
10718
+ }
10719
+ `;
10720
+ async function ensureOpencodeConfig(cwd) {
10721
+ const jsoncPath = path3.join(cwd, "opencode.jsonc");
10722
+ const jsonPath = path3.join(cwd, "opencode.json");
10723
+ const jsoncFile = Bun.file(jsoncPath);
10724
+ if (await jsoncFile.exists()) {
10725
+ return { path: jsoncPath, created: false };
10726
+ }
10727
+ const jsonFile = Bun.file(jsonPath);
10728
+ if (await jsonFile.exists()) {
10729
+ return { path: jsonPath, created: false };
10730
+ }
10731
+ await Bun.write(jsoncPath, OPENCODE_CONFIG_TEMPLATE);
10732
+ return { path: jsoncPath, created: true };
10733
+ }
10702
10734
  async function readOpencodeJsonConfig(cwd) {
10703
- const jsonPath = `${cwd}/opencode.json`;
10704
- const jsoncPath = `${cwd}/opencode.jsonc`;
10735
+ const jsonPath = path3.join(cwd, "opencode.json");
10736
+ const jsoncPath = path3.join(cwd, "opencode.jsonc");
10705
10737
  for (const configPath of [jsoncPath, jsonPath]) {
10706
10738
  const file = Bun.file(configPath);
10707
10739
  if (await file.exists()) {
@@ -10715,13 +10747,13 @@ async function readOpencodeJsonConfig(cwd) {
10715
10747
  }
10716
10748
  return null;
10717
10749
  }
10718
- async function writeOpencodeJsonConfig(path3, content) {
10719
- await Bun.write(path3, content);
10750
+ async function writeOpencodeJsonConfig(path4, content) {
10751
+ await Bun.write(path4, content);
10720
10752
  }
10721
- function getValueAtPath(content, path3) {
10753
+ function getValueAtPath(content, path4) {
10722
10754
  const parsed = parse2(content, [], { allowTrailingComma: true });
10723
10755
  let current = parsed;
10724
- for (const segment of path3) {
10756
+ for (const segment of path4) {
10725
10757
  if (current === null || current === undefined)
10726
10758
  return;
10727
10759
  if (typeof current !== "object")
@@ -10730,27 +10762,27 @@ function getValueAtPath(content, path3) {
10730
10762
  }
10731
10763
  return current;
10732
10764
  }
10733
- function applyValueAtPath(content, path3, value) {
10765
+ function applyValueAtPath(content, path4, value) {
10734
10766
  if (value === null || value === undefined) {
10735
10767
  return content;
10736
10768
  }
10737
10769
  if (typeof value === "object" && !Array.isArray(value)) {
10738
- const existingValue = getValueAtPath(content, path3);
10770
+ const existingValue = getValueAtPath(content, path4);
10739
10771
  if (existingValue !== undefined && (existingValue === null || typeof existingValue !== "object")) {
10740
- const edits2 = modify(content, path3, value, JSONC_OPTIONS);
10772
+ const edits2 = modify(content, path4, value, JSONC_OPTIONS);
10741
10773
  return applyEdits(content, edits2);
10742
10774
  }
10743
10775
  let updatedContent = content;
10744
10776
  for (const [key, val] of Object.entries(value)) {
10745
- updatedContent = applyValueAtPath(updatedContent, [...path3, key], val);
10777
+ updatedContent = applyValueAtPath(updatedContent, [...path4, key], val);
10746
10778
  }
10747
10779
  return updatedContent;
10748
10780
  }
10749
10781
  if (Array.isArray(value)) {
10750
- const edits2 = modify(content, path3, value, JSONC_OPTIONS);
10782
+ const edits2 = modify(content, path4, value, JSONC_OPTIONS);
10751
10783
  return applyEdits(content, edits2);
10752
10784
  }
10753
- const edits = modify(content, path3, value, JSONC_OPTIONS);
10785
+ const edits = modify(content, path4, value, JSONC_OPTIONS);
10754
10786
  return applyEdits(content, edits);
10755
10787
  }
10756
10788
  async function updateOpencodeJsonConfig(cwd, opencode) {
@@ -10764,7 +10796,7 @@ async function updateOpencodeJsonConfig(cwd, opencode) {
10764
10796
  } else {
10765
10797
  const config = { $schema: "https://opencode.ai/config.json" };
10766
10798
  content = JSON.stringify(config, null, "\t");
10767
- configPath = `${cwd}/opencode.jsonc`;
10799
+ configPath = path3.join(cwd, "opencode.jsonc");
10768
10800
  created = true;
10769
10801
  }
10770
10802
  const originalContent = content;
@@ -10983,8 +11015,8 @@ function handleError(error, options2 = {}) {
10983
11015
  if (error instanceof ZodError) {
10984
11016
  logger.error("Validation failed:");
10985
11017
  for (const issue of error.issues) {
10986
- const path3 = issue.path.join(".");
10987
- logger.error(` ${path3}: ${issue.message}`);
11018
+ const path4 = issue.path.join(".");
11019
+ logger.error(` ${path4}: ${issue.message}`);
10988
11020
  }
10989
11021
  process.exit(EXIT_CODES.CONFIG);
10990
11022
  }
@@ -11042,14 +11074,14 @@ function outputJson(data) {
11042
11074
  console.log(JSON.stringify(data, null, 2));
11043
11075
  }
11044
11076
  // src/utils/path-safety.ts
11045
- import path3 from "path";
11077
+ import path4 from "path";
11046
11078
  function isPathInside(childPath, parentPath) {
11047
- const resolvedChild = path3.resolve(childPath);
11048
- const resolvedParent = path3.resolve(parentPath);
11079
+ const resolvedChild = path4.resolve(childPath);
11080
+ const resolvedParent = path4.resolve(parentPath);
11049
11081
  if (resolvedChild === resolvedParent) {
11050
11082
  return true;
11051
11083
  }
11052
- const relative = path3.relative(resolvedParent, resolvedChild);
11084
+ const relative = path4.relative(resolvedParent, resolvedChild);
11053
11085
  return !!relative && !relative.startsWith("..") && !isAbsolutePath(relative);
11054
11086
  }
11055
11087
  function assertPathInside(childPath, parentPath) {
@@ -11062,14 +11094,14 @@ var sharedOptions = {
11062
11094
  cwd: () => new Option("--cwd <path>", "Working directory").default(process.cwd()),
11063
11095
  quiet: () => new Option("-q, --quiet", "Suppress output"),
11064
11096
  json: () => new Option("--json", "Output as JSON"),
11065
- yes: () => new Option("-y, --yes", "Skip confirmation prompts"),
11097
+ force: () => new Option("-f, --force", "Skip confirmation prompts"),
11066
11098
  verbose: () => new Option("-v, --verbose", "Verbose output")
11067
11099
  };
11068
11100
  function addCommonOptions(cmd) {
11069
11101
  return cmd.addOption(sharedOptions.cwd()).addOption(sharedOptions.quiet()).addOption(sharedOptions.json());
11070
11102
  }
11071
- function addConfirmationOptions(cmd) {
11072
- return cmd.addOption(sharedOptions.yes());
11103
+ function addForceOption(cmd) {
11104
+ return cmd.addOption(sharedOptions.force());
11073
11105
  }
11074
11106
  function addVerboseOption(cmd) {
11075
11107
  return cmd.addOption(sharedOptions.verbose());
@@ -12466,11 +12498,154 @@ function warnCompatIssues(registryName, issues) {
12466
12498
  logger.log(formatCompatWarning(registryName, issue));
12467
12499
  }
12468
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
+
12469
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
+ }
12470
12642
  function registerAddCommand(program2) {
12471
- 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)");
12472
12647
  addCommonOptions(cmd);
12473
- addConfirmationOptions(cmd);
12648
+ addForceOption(cmd);
12474
12649
  addVerboseOption(cmd);
12475
12650
  cmd.action(async (components, options2) => {
12476
12651
  try {
@@ -12482,6 +12657,104 @@ function registerAddCommand(program2) {
12482
12657
  });
12483
12658
  }
12484
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) {
12485
12758
  const cwd = provider.cwd;
12486
12759
  const lockPath = join2(cwd, "ocx.lock");
12487
12760
  const registries = provider.getRegistries();
@@ -12575,7 +12848,7 @@ async function runAddCore(componentNames, options2, provider) {
12575
12848
  if (existsSync(targetPath)) {
12576
12849
  const existingContent = await Bun.file(targetPath).text();
12577
12850
  const incomingContent = file.content.toString("utf-8");
12578
- if (!isContentIdentical(existingContent, incomingContent) && !options2.yes) {
12851
+ if (!isContentIdentical(existingContent, incomingContent) && !options2.force) {
12579
12852
  allConflicts.push(componentFile.target);
12580
12853
  }
12581
12854
  }
@@ -12589,14 +12862,14 @@ async function runAddCore(componentNames, options2, provider) {
12589
12862
  }
12590
12863
  logger.error("");
12591
12864
  logger.error("These files have been modified since installation.");
12592
- logger.error("Use --yes to overwrite, or review the changes first.");
12593
- 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.`);
12594
12867
  }
12595
12868
  const installSpin = options2.quiet ? null : createSpinner({ text: "Installing components..." });
12596
12869
  installSpin?.start();
12597
12870
  for (const { component, files, computedHash } of componentBundles) {
12598
12871
  const installResult = await installComponent(component, files, cwd, {
12599
- yes: options2.yes,
12872
+ force: options2.force,
12600
12873
  verbose: options2.verbose
12601
12874
  });
12602
12875
  if (options2.verbose) {
@@ -12912,9 +13185,9 @@ async function buildRegistry(options2) {
12912
13185
 
12913
13186
  // src/commands/build.ts
12914
13187
  function registerBuildCommand(program2) {
12915
- program2.command("build").description("Build a registry from source (for registry authors)").argument("[path]", "Registry source directory", ".").option("--out <dir>", "Output directory", "./dist").option("--cwd <path>", "Working directory", process.cwd()).option("--json", "Output as JSON", false).option("-q, --quiet", "Suppress output", false).action(async (path4, options2) => {
13188
+ program2.command("build").description("Build a registry from source (for registry authors)").argument("[path]", "Registry source directory", ".").option("--out <dir>", "Output directory", "./dist").option("--cwd <path>", "Working directory", process.cwd()).option("--json", "Output as JSON", false).option("-q, --quiet", "Suppress output", false).action(async (path5, options2) => {
12916
13189
  try {
12917
- const sourcePath = join4(options2.cwd, path4);
13190
+ const sourcePath = join4(options2.cwd, path5);
12918
13191
  const outPath = join4(options2.cwd, options2.out);
12919
13192
  const spinner2 = createSpinner({
12920
13193
  text: "Building registry...",
@@ -13059,16 +13332,16 @@ class Diff {
13059
13332
  }
13060
13333
  }
13061
13334
  }
13062
- addToPath(path4, added, removed, oldPosInc, options2) {
13063
- const last = path4.lastComponent;
13335
+ addToPath(path5, added, removed, oldPosInc, options2) {
13336
+ const last = path5.lastComponent;
13064
13337
  if (last && !options2.oneChangePerToken && last.added === added && last.removed === removed) {
13065
13338
  return {
13066
- oldPos: path4.oldPos + oldPosInc,
13339
+ oldPos: path5.oldPos + oldPosInc,
13067
13340
  lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
13068
13341
  };
13069
13342
  } else {
13070
13343
  return {
13071
- oldPos: path4.oldPos + oldPosInc,
13344
+ oldPos: path5.oldPos + oldPosInc,
13072
13345
  lastComponent: { count: 1, added, removed, previousComponent: last }
13073
13346
  };
13074
13347
  }
@@ -13851,9 +14124,9 @@ Diff for ${res.name}:`));
13851
14124
 
13852
14125
  // src/commands/ghost/add.ts
13853
14126
  function registerGhostAddCommand(parent) {
13854
- 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");
13855
14128
  addCommonOptions(cmd);
13856
- addConfirmationOptions(cmd);
14129
+ addForceOption(cmd);
13857
14130
  addVerboseOption(cmd);
13858
14131
  cmd.action(async (components, options2) => {
13859
14132
  try {
@@ -13946,13 +14219,22 @@ async function runGhostInit(options2) {
13946
14219
  }
13947
14220
  throw err;
13948
14221
  }
14222
+ const opencodeResult = await ensureOpencodeConfig(configDir);
13949
14223
  if (options2.json) {
13950
- console.log(JSON.stringify({ success: true, path: configPath }));
14224
+ console.log(JSON.stringify({
14225
+ success: true,
14226
+ path: configPath,
14227
+ opencodePath: opencodeResult.path,
14228
+ opencodeCreated: opencodeResult.created
14229
+ }));
13951
14230
  return;
13952
14231
  }
13953
14232
  if (!options2.quiet) {
13954
14233
  logger.success("Ghost mode initialized");
13955
14234
  logger.info(`Created ${configPath}`);
14235
+ if (opencodeResult.created) {
14236
+ logger.info(`Created ${opencodeResult.path}`);
14237
+ }
13956
14238
  logger.info("");
13957
14239
  logger.info("Next steps:");
13958
14240
  logger.info(" 1. Edit your config: ocx ghost config");
@@ -14009,14 +14291,14 @@ async function discoverProjectFiles(start, stop) {
14009
14291
  const excluded = new Set;
14010
14292
  for (const file of CONFIG_FILES) {
14011
14293
  const found = await findUp(file, start, stop);
14012
- for (const path4 of found) {
14013
- excluded.add(path4);
14294
+ for (const path5 of found) {
14295
+ excluded.add(path5);
14014
14296
  }
14015
14297
  }
14016
14298
  for (const file of RULE_FILES) {
14017
14299
  const found = await findUp(file, start, stop);
14018
- for (const path4 of found) {
14019
- excluded.add(path4);
14300
+ for (const path5 of found) {
14301
+ excluded.add(path5);
14020
14302
  }
14021
14303
  }
14022
14304
  for await (const dir of up({ targets: CONFIG_DIRS, start, stop })) {
@@ -14037,13 +14319,13 @@ function filterExcludedPaths(excludedPaths, includePatterns, excludePatterns) {
14037
14319
  const includeGlobs = includePatterns.map((p) => new Glob2(p));
14038
14320
  const excludeGlobs = excludePatterns?.map((p) => new Glob2(p)) ?? [];
14039
14321
  const filteredExclusions = new Set;
14040
- for (const path4 of excludedPaths) {
14041
- const matchesInclude = matchesAnyGlob(path4, includeGlobs);
14042
- const matchesExclude = matchesAnyGlob(path4, excludeGlobs);
14322
+ for (const path5 of excludedPaths) {
14323
+ const matchesInclude = matchesAnyGlob(path5, includeGlobs);
14324
+ const matchesExclude = matchesAnyGlob(path5, excludeGlobs);
14043
14325
  if (matchesInclude && !matchesExclude) {
14044
14326
  continue;
14045
14327
  }
14046
- filteredExclusions.add(path4);
14328
+ filteredExclusions.add(path5);
14047
14329
  }
14048
14330
  return filteredExclusions;
14049
14331
  }
@@ -14151,7 +14433,7 @@ async function runGhostOpenCode(args, options2) {
14151
14433
  const openCodeConfig = await loadGhostOpencodeConfig();
14152
14434
  const ghostConfigDir = getGhostConfigDir();
14153
14435
  if (Object.keys(openCodeConfig).length === 0 && !options2.quiet) {
14154
- logger.warn(`No opencode.jsonc found at ${getGhostOpencodeConfigPath()}. Run 'ocx ghost add' first.`);
14436
+ logger.warn(`No opencode.jsonc found at ${getGhostOpencodeConfigPath()}. Run 'ocx ghost init' first.`);
14155
14437
  }
14156
14438
  const cwd = process.cwd();
14157
14439
  const gitContext = await detectGitRepo(cwd);
@@ -14210,7 +14492,7 @@ async function runGhostOpenCode(args, options2) {
14210
14492
  }
14211
14493
 
14212
14494
  // src/commands/registry.ts
14213
- async function runRegistryAddCore(url, options2, callbacks) {
14495
+ async function runRegistryAddCore2(url, options2, callbacks) {
14214
14496
  if (callbacks.isLocked?.()) {
14215
14497
  throw new Error("Registries are locked. Cannot add.");
14216
14498
  }
@@ -14256,7 +14538,7 @@ function registerRegistryCommand(program2) {
14256
14538
  logger.error("No ocx.jsonc found. Run 'ocx init' first.");
14257
14539
  process.exit(1);
14258
14540
  }
14259
- const result = await runRegistryAddCore(url, options2, {
14541
+ const result = await runRegistryAddCore2(url, options2, {
14260
14542
  getRegistries: () => config.registries,
14261
14543
  isLocked: () => config.lockRegistries ?? false,
14262
14544
  setRegistry: async (name, regConfig) => {
@@ -14351,7 +14633,7 @@ function registerGhostRegistryCommand(parent) {
14351
14633
  try {
14352
14634
  await ensureGhostInitialized();
14353
14635
  const config = await loadGhostConfig();
14354
- const result = await runRegistryAddCore(url, options2, {
14636
+ const result = await runRegistryAddCore2(url, options2, {
14355
14637
  getRegistries: () => config.registries,
14356
14638
  setRegistry: async (name, regConfig) => {
14357
14639
  config.registries[name] = regConfig;
@@ -14613,14 +14895,20 @@ async function runInit(options2) {
14613
14895
  const config = ocxConfigSchema.parse(rawConfig);
14614
14896
  const content2 = JSON.stringify(config, null, 2);
14615
14897
  await writeFile3(configPath, content2, "utf-8");
14616
- if (!options2.quiet && !options2.json) {
14617
- logger.success("Initialized OCX configuration");
14618
- }
14898
+ const opencodeResult = await ensureOpencodeConfig(cwd);
14619
14899
  spin?.succeed("Initialized OCX configuration");
14620
14900
  if (options2.json) {
14621
- console.log(JSON.stringify({ success: true, path: configPath }));
14901
+ console.log(JSON.stringify({
14902
+ success: true,
14903
+ path: configPath,
14904
+ opencodePath: opencodeResult.path,
14905
+ opencodeCreated: opencodeResult.created
14906
+ }));
14622
14907
  } else if (!options2.quiet) {
14623
14908
  logger.info(`Created ${configPath}`);
14909
+ if (opencodeResult.created) {
14910
+ logger.info(`Created ${opencodeResult.path}`);
14911
+ }
14624
14912
  logger.info("");
14625
14913
  logger.info("Next steps:");
14626
14914
  logger.info(" 1. Add a registry: ocx registry add <url>");
@@ -15021,7 +15309,7 @@ async function hashBundle2(files) {
15021
15309
  `));
15022
15310
  }
15023
15311
  // src/index.ts
15024
- var version = "1.0.20";
15312
+ var version = "1.1.0";
15025
15313
  async function main2() {
15026
15314
  const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
15027
15315
  registerInitCommand(program2);
@@ -15048,4 +15336,4 @@ export {
15048
15336
  buildRegistry
15049
15337
  };
15050
15338
 
15051
- //# debugId=88A0B09CD885AC2664756E2164756E21
15339
+ //# debugId=46A2A0DCEF36C6A464756E2164756E21