ocx 1.0.7 → 1.0.9

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",
@@ -13072,7 +13165,7 @@ var mcpServerObjectSchema = exports_external.object({
13072
13165
  }, {
13073
13166
  message: "Remote MCP servers require 'url', local servers require 'command'"
13074
13167
  });
13075
- var mcpServerSchema = exports_external.union([exports_external.string().url(), mcpServerObjectSchema]);
13168
+ var mcpServerRefSchema = exports_external.union([exports_external.string().url(), mcpServerObjectSchema]);
13076
13169
  var componentFileObjectSchema = exports_external.object({
13077
13170
  path: exports_external.string().min(1, "File path cannot be empty"),
13078
13171
  target: targetPathSchema
@@ -13098,10 +13191,10 @@ 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
- mcpServers: exports_external.record(mcpServerSchema).optional(),
13197
+ mcpServers: exports_external.record(mcpServerRefSchema).optional(),
13105
13198
  mcpScope: exports_external.enum(["agent", "global"]).default("agent"),
13106
13199
  opencode: opencodeConfigSchema.optional(),
13107
13200
  disabledTools: exports_external.array(exports_external.string()).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,14 +14841,15 @@ 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
- var opencodeMcpSchema = exports_external.record(mcpServerSchema);
14852
+ var opencodeMcpSchema = exports_external.record(mcpServerRefSchema);
14814
14853
  var opencodeAgentSchema = exports_external.object({
14815
14854
  disable: exports_external.boolean().optional(),
14816
14855
  tools: exports_external.record(exports_external.boolean()).optional(),
@@ -14906,12 +14945,21 @@ function applyMcpServers(content, config2, mcpServers) {
14906
14945
  if (server.type === "local" && server.command) {
14907
14946
  serverConfig.command = server.command;
14908
14947
  }
14948
+ if (server.args) {
14949
+ serverConfig.args = server.args;
14950
+ }
14909
14951
  if (server.headers) {
14910
14952
  serverConfig.headers = server.headers;
14911
14953
  }
14954
+ if (server.oauth !== undefined) {
14955
+ serverConfig.oauth = server.oauth;
14956
+ }
14912
14957
  if (server.enabled !== undefined) {
14913
14958
  serverConfig.enabled = server.enabled;
14914
14959
  }
14960
+ if (server.environment) {
14961
+ serverConfig.environment = server.environment;
14962
+ }
14915
14963
  const edits = modify(updatedContent, ["mcp", name], serverConfig, JSONC_OPTIONS);
14916
14964
  updatedContent = applyEdits(updatedContent, edits);
14917
14965
  added.push(name);
@@ -16667,17 +16715,31 @@ async function runAdd(componentNames, options3) {
16667
16715
  files.push({ path: file.path, content: Buffer.from(content) });
16668
16716
  }
16669
16717
  const computedHash = await hashBundle(files);
16670
- const existingEntry = lock.installed[component.name];
16718
+ const existingEntry = lock.installed[component.qualifiedName];
16671
16719
  if (existingEntry && existingEntry.hash !== computedHash) {
16672
- throw new IntegrityError(component.name, existingEntry.hash, computedHash);
16720
+ throw new IntegrityError(component.qualifiedName, existingEntry.hash, computedHash);
16721
+ }
16722
+ for (const file of component.files) {
16723
+ const targetPath = join2(cwd, file.target);
16724
+ if (existsSync2(targetPath)) {
16725
+ const conflictingComponent = findComponentByFile(lock, file.target);
16726
+ if (conflictingComponent && conflictingComponent !== component.qualifiedName) {
16727
+ throw new ConflictError(`File conflict: ${file.target} already exists (installed by '${conflictingComponent}').
16728
+
16729
+ ` + `To resolve:
16730
+ ` + ` 1. Remove existing: rm ${file.target}
16731
+ ` + ` 2. Or rename it manually and update references
16732
+ ` + ` 3. Then run: ocx add ${component.qualifiedName}`);
16733
+ }
16734
+ }
16673
16735
  }
16674
16736
  await installComponent(component, files, cwd);
16675
16737
  const index = await fetchRegistryIndex(component.baseUrl);
16676
- lock.installed[component.name] = {
16738
+ lock.installed[component.qualifiedName] = {
16677
16739
  registry: component.registryName,
16678
16740
  version: index.version,
16679
16741
  hash: computedHash,
16680
- target: getTargetPath(component),
16742
+ files: component.files.map((f2) => f2.target),
16681
16743
  installedAt: new Date().toISOString()
16682
16744
  };
16683
16745
  }
@@ -16763,10 +16825,6 @@ async function installComponent(component, files, cwd) {
16763
16825
  await writeFile(targetPath, file.content);
16764
16826
  }
16765
16827
  }
16766
- function getTargetPath(component) {
16767
- const typeDir = component.type.replace(/^ocx:/, "");
16768
- return component.files[0]?.target ?? `.opencode/${typeDir}/${component.name}`;
16769
- }
16770
16828
  async function hashContent(content) {
16771
16829
  return createHash("sha256").update(content).digest("hex");
16772
16830
  }
@@ -16822,6 +16880,14 @@ async function installNpmDependencies(dependencies, devDependencies, cwd, option
16822
16880
  await run(parseNi, [...devDependencies, "-D"], { cwd });
16823
16881
  }
16824
16882
  }
16883
+ function findComponentByFile(lock, filePath) {
16884
+ for (const [qualifiedName, entry] of Object.entries(lock.installed)) {
16885
+ if (entry.files.includes(filePath)) {
16886
+ return qualifiedName;
16887
+ }
16888
+ }
16889
+ return null;
16890
+ }
16825
16891
 
16826
16892
  // src/commands/build.ts
16827
16893
  import { mkdir as mkdir2 } from "fs/promises";
@@ -16896,7 +16962,7 @@ function registerBuildCommand(program2) {
16896
16962
  }
16897
16963
  const index = {
16898
16964
  name: registry.name,
16899
- prefix: registry.prefix,
16965
+ namespace: registry.namespace,
16900
16966
  version: registry.version,
16901
16967
  author: registry.author,
16902
16968
  components: registry.components.map((c2) => ({
@@ -17764,7 +17830,11 @@ function registerDiffCommand(program2) {
17764
17830
  }
17765
17831
  continue;
17766
17832
  }
17767
- const localPath = `${options3.cwd}/${installed.target}`;
17833
+ if (!installed.files || installed.files.length === 0) {
17834
+ logger.warn(`No files recorded for component '${name}'`);
17835
+ continue;
17836
+ }
17837
+ const localPath = `${options3.cwd}/${installed.files[0]}`;
17768
17838
  const localFile = Bun.file(localPath);
17769
17839
  if (!await localFile.exists()) {
17770
17840
  results.push({ name, hasChanges: true, diff: "Local file missing" });
@@ -17777,14 +17847,16 @@ function registerDiffCommand(program2) {
17777
17847
  continue;
17778
17848
  }
17779
17849
  try {
17780
- const upstream = await fetchComponent(registryConfig.url, name);
17850
+ const parsed = parseQualifiedComponent(name);
17851
+ const componentName = parsed.component;
17852
+ const upstream = await fetchComponent(registryConfig.url, componentName);
17781
17853
  const rawUpstreamFile = upstream.files[0];
17782
17854
  if (!rawUpstreamFile) {
17783
17855
  results.push({ name, hasChanges: false });
17784
17856
  continue;
17785
17857
  }
17786
17858
  const upstreamFile = normalizeFile(rawUpstreamFile);
17787
- const upstreamContent = await fetchFileContent(registryConfig.url, name, upstreamFile.path);
17859
+ const upstreamContent = await fetchFileContent(registryConfig.url, componentName, upstreamFile.path);
17788
17860
  if (localContent === upstreamContent) {
17789
17861
  results.push({ name, hasChanges: false });
17790
17862
  } else {
@@ -18032,7 +18104,7 @@ function registerSearchCommand(program2) {
18032
18104
  }
18033
18105
  for (const comp of index.components) {
18034
18106
  allComponents.push({
18035
- name: comp.name,
18107
+ name: `${index.namespace}/${comp.name}`,
18036
18108
  description: comp.description,
18037
18109
  type: comp.type,
18038
18110
  registry: registryName
@@ -18076,7 +18148,7 @@ function registerSearchCommand(program2) {
18076
18148
  }
18077
18149
 
18078
18150
  // src/index.ts
18079
- var version2 = "1.0.7";
18151
+ var version2 = "1.0.9";
18080
18152
  async function main2() {
18081
18153
  const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version2);
18082
18154
  registerInitCommand(program2);
@@ -18091,4 +18163,4 @@ main2().catch((err) => {
18091
18163
  handleError(err);
18092
18164
  });
18093
18165
 
18094
- //# debugId=787522CA1BFEEB9464756E2164756E21
18166
+ //# debugId=8F8057E520B12DC364756E2164756E21