ocx 1.0.7 → 1.0.8

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
@@ -13026,9 +13026,102 @@ var coerce = {
13026
13026
  date: (arg) => ZodDate.create({ ...arg, coerce: true })
13027
13027
  };
13028
13028
  var NEVER = INVALID;
13029
+ // src/utils/errors.ts
13030
+ var EXIT_CODES = {
13031
+ SUCCESS: 0,
13032
+ GENERAL: 1,
13033
+ NOT_FOUND: 66,
13034
+ NETWORK: 69,
13035
+ CONFIG: 78,
13036
+ INTEGRITY: 1
13037
+ };
13038
+
13039
+ class OCXError extends Error {
13040
+ code;
13041
+ exitCode;
13042
+ constructor(message, code, exitCode = EXIT_CODES.GENERAL) {
13043
+ super(message);
13044
+ this.code = code;
13045
+ this.exitCode = exitCode;
13046
+ this.name = "OCXError";
13047
+ }
13048
+ }
13049
+
13050
+ class NotFoundError extends OCXError {
13051
+ constructor(message) {
13052
+ super(message, "NOT_FOUND", EXIT_CODES.NOT_FOUND);
13053
+ this.name = "NotFoundError";
13054
+ }
13055
+ }
13056
+
13057
+ class NetworkError extends OCXError {
13058
+ constructor(message) {
13059
+ super(message, "NETWORK_ERROR", EXIT_CODES.NETWORK);
13060
+ this.name = "NetworkError";
13061
+ }
13062
+ }
13063
+
13064
+ class ConfigError extends OCXError {
13065
+ constructor(message) {
13066
+ super(message, "CONFIG_ERROR", EXIT_CODES.CONFIG);
13067
+ this.name = "ConfigError";
13068
+ }
13069
+ }
13070
+
13071
+ class ValidationError extends OCXError {
13072
+ constructor(message) {
13073
+ super(message, "VALIDATION_ERROR", EXIT_CODES.GENERAL);
13074
+ this.name = "ValidationError";
13075
+ }
13076
+ }
13077
+
13078
+ class ConflictError extends OCXError {
13079
+ constructor(message) {
13080
+ super(message, "CONFLICT", EXIT_CODES.GENERAL);
13081
+ this.name = "ConflictError";
13082
+ }
13083
+ }
13084
+
13085
+ class IntegrityError extends OCXError {
13086
+ constructor(component, expected, found) {
13087
+ const message = `Integrity verification failed for "${component}"
13088
+ ` + ` Expected: ${expected}
13089
+ ` + ` Found: ${found}
13090
+
13091
+ ` + `The registry content has changed since this component was locked.
13092
+ ` + `This could indicate tampering or an unauthorized update.`;
13093
+ super(message, "INTEGRITY_ERROR", EXIT_CODES.INTEGRITY);
13094
+ this.name = "IntegrityError";
13095
+ }
13096
+ }
13097
+
13029
13098
  // src/schemas/registry.ts
13030
13099
  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]+)*$/, {
13031
- message: "Must be lowercase alphanumeric with single hyphen separators (e.g., 'my-component', 'kdco-plan'). Cannot start/end with hyphen or have consecutive hyphens."
13100
+ message: "Must be lowercase alphanumeric with single hyphen separators (e.g., 'my-component', 'my-plugin'). Cannot start/end with hyphen or have consecutive hyphens."
13101
+ });
13102
+ var namespaceSchema = openCodeNameSchema;
13103
+ var qualifiedComponentSchema = exports_external.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*\/[a-z0-9]+(-[a-z0-9]+)*$/, {
13104
+ message: 'Must be in format "namespace/component" (e.g., "kdco/librarian"). Both parts must be lowercase alphanumeric with hyphens.'
13105
+ });
13106
+ function parseQualifiedComponent(ref) {
13107
+ if (!ref.includes("/")) {
13108
+ throw new ValidationError(`Invalid component reference: "${ref}". Use format: namespace/component`);
13109
+ }
13110
+ const [namespace, component] = ref.split("/");
13111
+ if (!namespace || !component) {
13112
+ throw new ValidationError(`Invalid component reference: "${ref}". Both namespace and component are required.`);
13113
+ }
13114
+ return { namespace, component };
13115
+ }
13116
+ function createQualifiedComponent(namespace, component) {
13117
+ return `${namespace}/${component}`;
13118
+ }
13119
+ var dependencyRefSchema = exports_external.string().refine((dep) => {
13120
+ const barePattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
13121
+ const qualifiedPattern = /^[a-z0-9]+(-[a-z0-9]+)*\/[a-z0-9]+(-[a-z0-9]+)*$/;
13122
+ return barePattern.test(dep) || qualifiedPattern.test(dep);
13123
+ }, {
13124
+ message: 'Dependency must be either a bare name (e.g., "utils") or qualified (e.g., "acme/utils")'
13032
13125
  });
13033
13126
  var componentTypeSchema = exports_external.enum([
13034
13127
  "ocx:agent",
@@ -13098,7 +13191,7 @@ var componentManifestSchema = exports_external.object({
13098
13191
  type: componentTypeSchema,
13099
13192
  description: exports_external.string().min(1).max(1024),
13100
13193
  files: exports_external.array(componentFileSchema),
13101
- dependencies: exports_external.array(openCodeNameSchema).default([]),
13194
+ dependencies: exports_external.array(dependencyRefSchema).default([]),
13102
13195
  npmDependencies: exports_external.array(exports_external.string()).optional(),
13103
13196
  npmDevDependencies: exports_external.array(exports_external.string()).optional(),
13104
13197
  mcpServers: exports_external.record(mcpServerSchema).optional(),
@@ -13141,28 +13234,24 @@ function normalizeComponentManifest(manifest) {
13141
13234
  var semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/;
13142
13235
  var registrySchema = exports_external.object({
13143
13236
  name: exports_external.string().min(1, "Registry name cannot be empty"),
13144
- prefix: openCodeNameSchema,
13237
+ namespace: namespaceSchema,
13145
13238
  version: exports_external.string().regex(semverRegex, {
13146
13239
  message: "Version must be valid semver (e.g., '1.0.0', '2.1.0-beta.1')"
13147
13240
  }),
13148
13241
  author: exports_external.string().min(1, "Author cannot be empty"),
13149
13242
  components: exports_external.array(componentManifestSchema)
13150
- }).refine((data) => {
13151
- return data.components.every((c2) => c2.name.startsWith(`${data.prefix}-`));
13152
- }, {
13153
- message: "All component names must start with the registry prefix"
13154
13243
  }).refine((data) => {
13155
13244
  const componentNames = new Set(data.components.map((c2) => c2.name));
13156
13245
  for (const component of data.components) {
13157
13246
  for (const dep of component.dependencies) {
13158
- if (!componentNames.has(dep)) {
13247
+ if (!dep.includes("/") && !componentNames.has(dep)) {
13159
13248
  return false;
13160
13249
  }
13161
13250
  }
13162
13251
  }
13163
13252
  return true;
13164
13253
  }, {
13165
- message: "All dependencies must reference components that exist in the registry"
13254
+ message: "Bare dependencies must reference components that exist in the registry. Use qualified references (e.g., 'other-registry/component') for cross-namespace dependencies."
13166
13255
  });
13167
13256
  var packumentSchema = exports_external.object({
13168
13257
  name: openCodeNameSchema,
@@ -13173,7 +13262,7 @@ var packumentSchema = exports_external.object({
13173
13262
  });
13174
13263
  var registryIndexSchema = exports_external.object({
13175
13264
  name: exports_external.string(),
13176
- prefix: openCodeNameSchema,
13265
+ namespace: namespaceSchema,
13177
13266
  version: exports_external.string(),
13178
13267
  author: exports_external.string(),
13179
13268
  components: exports_external.array(exports_external.object({
@@ -13183,67 +13272,6 @@ var registryIndexSchema = exports_external.object({
13183
13272
  }))
13184
13273
  });
13185
13274
 
13186
- // src/utils/errors.ts
13187
- var EXIT_CODES = {
13188
- SUCCESS: 0,
13189
- GENERAL: 1,
13190
- NOT_FOUND: 66,
13191
- NETWORK: 69,
13192
- CONFIG: 78,
13193
- INTEGRITY: 1
13194
- };
13195
-
13196
- class OCXError extends Error {
13197
- code;
13198
- exitCode;
13199
- constructor(message, code, exitCode = EXIT_CODES.GENERAL) {
13200
- super(message);
13201
- this.code = code;
13202
- this.exitCode = exitCode;
13203
- this.name = "OCXError";
13204
- }
13205
- }
13206
-
13207
- class NotFoundError extends OCXError {
13208
- constructor(message) {
13209
- super(message, "NOT_FOUND", EXIT_CODES.NOT_FOUND);
13210
- this.name = "NotFoundError";
13211
- }
13212
- }
13213
-
13214
- class NetworkError extends OCXError {
13215
- constructor(message) {
13216
- super(message, "NETWORK_ERROR", EXIT_CODES.NETWORK);
13217
- this.name = "NetworkError";
13218
- }
13219
- }
13220
-
13221
- class ConfigError extends OCXError {
13222
- constructor(message) {
13223
- super(message, "CONFIG_ERROR", EXIT_CODES.CONFIG);
13224
- this.name = "ConfigError";
13225
- }
13226
- }
13227
-
13228
- class ValidationError extends OCXError {
13229
- constructor(message) {
13230
- super(message, "VALIDATION_ERROR", EXIT_CODES.GENERAL);
13231
- this.name = "ValidationError";
13232
- }
13233
- }
13234
- class IntegrityError extends OCXError {
13235
- constructor(component, expected, found) {
13236
- const message = `Integrity verification failed for "${component}"
13237
- ` + ` Expected: ${expected}
13238
- ` + ` Found: ${found}
13239
-
13240
- ` + `The registry content has changed since this component was locked.
13241
- ` + `This could indicate tampering or an unauthorized update.`;
13242
- super(message, "INTEGRITY_ERROR", EXIT_CODES.INTEGRITY);
13243
- this.name = "IntegrityError";
13244
- }
13245
- }
13246
-
13247
13275
  // src/registry/fetcher.ts
13248
13276
  var cache = new Map;
13249
13277
  async function fetchWithCache(url, parse) {
@@ -13348,6 +13376,15 @@ function r2(e4, n3) {
13348
13376
  }
13349
13377
 
13350
13378
  // src/registry/resolver.ts
13379
+ function parseComponentRef(ref, defaultNamespace) {
13380
+ if (ref.includes("/")) {
13381
+ return parseQualifiedComponent(ref);
13382
+ }
13383
+ if (defaultNamespace) {
13384
+ return { namespace: defaultNamespace, component: ref };
13385
+ }
13386
+ throw new ValidationError(`Component '${ref}' must include a namespace (e.g., 'kdco/${ref}')`);
13387
+ }
13351
13388
  async function resolveDependencies(registries, componentNames) {
13352
13389
  const resolved = new Map;
13353
13390
  const visiting = new Set;
@@ -13359,39 +13396,39 @@ async function resolveDependencies(registries, componentNames) {
13359
13396
  const plugins = new Set;
13360
13397
  const agentConfigs = {};
13361
13398
  const instructionsSet = new Set;
13362
- async function resolve2(name, path3 = []) {
13363
- if (resolved.has(name)) {
13399
+ async function resolve2(componentNamespace, componentName, path3 = []) {
13400
+ const qualifiedName = createQualifiedComponent(componentNamespace, componentName);
13401
+ if (resolved.has(qualifiedName)) {
13364
13402
  return;
13365
13403
  }
13366
- if (visiting.has(name)) {
13367
- const cycle = [...path3, name].join(" \u2192 ");
13404
+ if (visiting.has(qualifiedName)) {
13405
+ const cycle = [...path3, qualifiedName].join(" \u2192 ");
13368
13406
  throw new ValidationError(`Circular dependency detected: ${cycle}`);
13369
13407
  }
13370
- visiting.add(name);
13371
- let component = null;
13372
- let foundRegistry = null;
13373
- const registryEntries = Object.entries(registries);
13374
- for (const [regName, regConfig] of registryEntries) {
13375
- try {
13376
- const manifest = await fetchComponent(regConfig.url, name);
13377
- component = manifest;
13378
- foundRegistry = { name: regName, url: regConfig.url };
13379
- break;
13380
- } catch (_err) {}
13408
+ visiting.add(qualifiedName);
13409
+ const regConfig = registries[componentNamespace];
13410
+ if (!regConfig) {
13411
+ throw new ConfigError(`Registry '${componentNamespace}' not configured. Add it to ocx.jsonc registries.`);
13381
13412
  }
13382
- if (!component || !foundRegistry) {
13383
- throw new OCXError(`Component '${name}' not found in any configured registry.`, "NOT_FOUND");
13413
+ let component;
13414
+ try {
13415
+ component = await fetchComponent(regConfig.url, componentName);
13416
+ } catch (_err) {
13417
+ throw new OCXError(`Component '${componentName}' not found in registry '${componentNamespace}'.`, "NOT_FOUND");
13384
13418
  }
13385
13419
  for (const dep of component.dependencies) {
13386
- await resolve2(dep, [...path3, name]);
13420
+ const depRef = parseComponentRef(dep, componentNamespace);
13421
+ await resolve2(depRef.namespace, depRef.component, [...path3, qualifiedName]);
13387
13422
  }
13388
13423
  const normalizedComponent = normalizeComponentManifest(component);
13389
- resolved.set(name, {
13424
+ resolved.set(qualifiedName, {
13390
13425
  ...normalizedComponent,
13391
- registryName: foundRegistry.name,
13392
- baseUrl: foundRegistry.url
13426
+ namespace: componentNamespace,
13427
+ registryName: componentNamespace,
13428
+ baseUrl: regConfig.url,
13429
+ qualifiedName
13393
13430
  });
13394
- visiting.delete(name);
13431
+ visiting.delete(qualifiedName);
13395
13432
  if (normalizedComponent.mcpServers) {
13396
13433
  const serverNames = [];
13397
13434
  for (const [serverName, config2] of Object.entries(normalizedComponent.mcpServers)) {
@@ -13447,7 +13484,8 @@ async function resolveDependencies(registries, componentNames) {
13447
13484
  }
13448
13485
  }
13449
13486
  for (const name of componentNames) {
13450
- await resolve2(name);
13487
+ const ref = parseComponentRef(name);
13488
+ await resolve2(ref.namespace, ref.component);
13451
13489
  }
13452
13490
  const components = Array.from(resolved.values());
13453
13491
  const installOrder = Array.from(resolved.keys());
@@ -14803,12 +14841,13 @@ var installedComponentSchema = exports_external.object({
14803
14841
  registry: exports_external.string(),
14804
14842
  version: exports_external.string(),
14805
14843
  hash: exports_external.string(),
14806
- target: exports_external.string(),
14807
- installedAt: exports_external.string()
14844
+ files: exports_external.array(exports_external.string()),
14845
+ installedAt: exports_external.string(),
14846
+ alias: exports_external.string().optional()
14808
14847
  });
14809
14848
  var ocxLockSchema = exports_external.object({
14810
14849
  lockVersion: exports_external.literal(1),
14811
- installed: exports_external.record(installedComponentSchema).default({})
14850
+ installed: exports_external.record(qualifiedComponentSchema, installedComponentSchema).default({})
14812
14851
  });
14813
14852
  var opencodeMcpSchema = exports_external.record(mcpServerSchema);
14814
14853
  var opencodeAgentSchema = exports_external.object({
@@ -16667,17 +16706,31 @@ async function runAdd(componentNames, options3) {
16667
16706
  files.push({ path: file.path, content: Buffer.from(content) });
16668
16707
  }
16669
16708
  const computedHash = await hashBundle(files);
16670
- const existingEntry = lock.installed[component.name];
16709
+ const existingEntry = lock.installed[component.qualifiedName];
16671
16710
  if (existingEntry && existingEntry.hash !== computedHash) {
16672
- throw new IntegrityError(component.name, existingEntry.hash, computedHash);
16711
+ throw new IntegrityError(component.qualifiedName, existingEntry.hash, computedHash);
16712
+ }
16713
+ for (const file of component.files) {
16714
+ const targetPath = join2(cwd, file.target);
16715
+ if (existsSync2(targetPath)) {
16716
+ const conflictingComponent = findComponentByFile(lock, file.target);
16717
+ if (conflictingComponent && conflictingComponent !== component.qualifiedName) {
16718
+ throw new ConflictError(`File conflict: ${file.target} already exists (installed by '${conflictingComponent}').
16719
+
16720
+ ` + `To resolve:
16721
+ ` + ` 1. Remove existing: rm ${file.target}
16722
+ ` + ` 2. Or rename it manually and update references
16723
+ ` + ` 3. Then run: ocx add ${component.qualifiedName}`);
16724
+ }
16725
+ }
16673
16726
  }
16674
16727
  await installComponent(component, files, cwd);
16675
16728
  const index = await fetchRegistryIndex(component.baseUrl);
16676
- lock.installed[component.name] = {
16729
+ lock.installed[component.qualifiedName] = {
16677
16730
  registry: component.registryName,
16678
16731
  version: index.version,
16679
16732
  hash: computedHash,
16680
- target: getTargetPath(component),
16733
+ files: component.files.map((f2) => f2.target),
16681
16734
  installedAt: new Date().toISOString()
16682
16735
  };
16683
16736
  }
@@ -16763,10 +16816,6 @@ async function installComponent(component, files, cwd) {
16763
16816
  await writeFile(targetPath, file.content);
16764
16817
  }
16765
16818
  }
16766
- function getTargetPath(component) {
16767
- const typeDir = component.type.replace(/^ocx:/, "");
16768
- return component.files[0]?.target ?? `.opencode/${typeDir}/${component.name}`;
16769
- }
16770
16819
  async function hashContent(content) {
16771
16820
  return createHash("sha256").update(content).digest("hex");
16772
16821
  }
@@ -16822,6 +16871,14 @@ async function installNpmDependencies(dependencies, devDependencies, cwd, option
16822
16871
  await run(parseNi, [...devDependencies, "-D"], { cwd });
16823
16872
  }
16824
16873
  }
16874
+ function findComponentByFile(lock, filePath) {
16875
+ for (const [qualifiedName, entry] of Object.entries(lock.installed)) {
16876
+ if (entry.files.includes(filePath)) {
16877
+ return qualifiedName;
16878
+ }
16879
+ }
16880
+ return null;
16881
+ }
16825
16882
 
16826
16883
  // src/commands/build.ts
16827
16884
  import { mkdir as mkdir2 } from "fs/promises";
@@ -16896,7 +16953,7 @@ function registerBuildCommand(program2) {
16896
16953
  }
16897
16954
  const index = {
16898
16955
  name: registry.name,
16899
- prefix: registry.prefix,
16956
+ namespace: registry.namespace,
16900
16957
  version: registry.version,
16901
16958
  author: registry.author,
16902
16959
  components: registry.components.map((c2) => ({
@@ -17764,7 +17821,11 @@ function registerDiffCommand(program2) {
17764
17821
  }
17765
17822
  continue;
17766
17823
  }
17767
- const localPath = `${options3.cwd}/${installed.target}`;
17824
+ if (!installed.files || installed.files.length === 0) {
17825
+ logger.warn(`No files recorded for component '${name}'`);
17826
+ continue;
17827
+ }
17828
+ const localPath = `${options3.cwd}/${installed.files[0]}`;
17768
17829
  const localFile = Bun.file(localPath);
17769
17830
  if (!await localFile.exists()) {
17770
17831
  results.push({ name, hasChanges: true, diff: "Local file missing" });
@@ -17777,14 +17838,16 @@ function registerDiffCommand(program2) {
17777
17838
  continue;
17778
17839
  }
17779
17840
  try {
17780
- const upstream = await fetchComponent(registryConfig.url, name);
17841
+ const parsed = parseQualifiedComponent(name);
17842
+ const componentName = parsed.component;
17843
+ const upstream = await fetchComponent(registryConfig.url, componentName);
17781
17844
  const rawUpstreamFile = upstream.files[0];
17782
17845
  if (!rawUpstreamFile) {
17783
17846
  results.push({ name, hasChanges: false });
17784
17847
  continue;
17785
17848
  }
17786
17849
  const upstreamFile = normalizeFile(rawUpstreamFile);
17787
- const upstreamContent = await fetchFileContent(registryConfig.url, name, upstreamFile.path);
17850
+ const upstreamContent = await fetchFileContent(registryConfig.url, componentName, upstreamFile.path);
17788
17851
  if (localContent === upstreamContent) {
17789
17852
  results.push({ name, hasChanges: false });
17790
17853
  } else {
@@ -18032,7 +18095,7 @@ function registerSearchCommand(program2) {
18032
18095
  }
18033
18096
  for (const comp of index.components) {
18034
18097
  allComponents.push({
18035
- name: comp.name,
18098
+ name: `${index.namespace}/${comp.name}`,
18036
18099
  description: comp.description,
18037
18100
  type: comp.type,
18038
18101
  registry: registryName
@@ -18076,7 +18139,7 @@ function registerSearchCommand(program2) {
18076
18139
  }
18077
18140
 
18078
18141
  // src/index.ts
18079
- var version2 = "1.0.7";
18142
+ var version2 = "1.0.8";
18080
18143
  async function main2() {
18081
18144
  const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version2);
18082
18145
  registerInitCommand(program2);
@@ -18091,4 +18154,4 @@ main2().catch((err) => {
18091
18154
  handleError(err);
18092
18155
  });
18093
18156
 
18094
- //# debugId=787522CA1BFEEB9464756E2164756E21
18157
+ //# debugId=4504091B3609ACE764756E2164756E21