ocx 1.0.14 → 1.0.15
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 +300 -11
- package/dist/index.js.map +10 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8492,7 +8492,7 @@ var openCodeNameSchema = exports_external.string().min(1, "Name cannot be empty"
|
|
|
8492
8492
|
});
|
|
8493
8493
|
var namespaceSchema = openCodeNameSchema;
|
|
8494
8494
|
var qualifiedComponentSchema = exports_external.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*\/[a-z0-9]+(-[a-z0-9]+)*$/, {
|
|
8495
|
-
message: 'Must be in format "namespace/component" (e.g., "kdco/
|
|
8495
|
+
message: 'Must be in format "namespace/component" (e.g., "kdco/researcher"). Both parts must be lowercase alphanumeric with hyphens.'
|
|
8496
8496
|
});
|
|
8497
8497
|
function parseQualifiedComponent(ref) {
|
|
8498
8498
|
if (!ref.includes("/")) {
|
|
@@ -8734,7 +8734,7 @@ class IntegrityError extends OCXError {
|
|
|
8734
8734
|
` + ` Found: ${found}
|
|
8735
8735
|
|
|
8736
8736
|
` + `The registry content has changed since this component was locked.
|
|
8737
|
-
` + `
|
|
8737
|
+
` + `Use 'ocx update ${component}' to intentionally update this component.`;
|
|
8738
8738
|
super(message, "INTEGRITY_ERROR", EXIT_CODES.INTEGRITY);
|
|
8739
8739
|
this.name = "IntegrityError";
|
|
8740
8740
|
}
|
|
@@ -8773,23 +8773,31 @@ async function fetchRegistryIndex(baseUrl) {
|
|
|
8773
8773
|
});
|
|
8774
8774
|
}
|
|
8775
8775
|
async function fetchComponent(baseUrl, name) {
|
|
8776
|
+
const result = await fetchComponentVersion(baseUrl, name);
|
|
8777
|
+
return result.manifest;
|
|
8778
|
+
}
|
|
8779
|
+
async function fetchComponentVersion(baseUrl, name, version) {
|
|
8776
8780
|
const url = `${baseUrl.replace(/\/$/, "")}/components/${name}.json`;
|
|
8777
|
-
return fetchWithCache(url
|
|
8781
|
+
return fetchWithCache(`${url}#v=${version ?? "latest"}`, (data) => {
|
|
8778
8782
|
const packumentResult = packumentSchema.safeParse(data);
|
|
8779
8783
|
if (!packumentResult.success) {
|
|
8780
8784
|
throw new ValidationError(`Invalid packument format for "${name}": ${packumentResult.error.message}`);
|
|
8781
8785
|
}
|
|
8782
8786
|
const packument = packumentResult.data;
|
|
8783
|
-
const
|
|
8784
|
-
const manifest = packument.versions[
|
|
8787
|
+
const resolvedVersion = version ?? packument["dist-tags"].latest;
|
|
8788
|
+
const manifest = packument.versions[resolvedVersion];
|
|
8785
8789
|
if (!manifest) {
|
|
8786
|
-
|
|
8790
|
+
if (version) {
|
|
8791
|
+
const availableVersions = Object.keys(packument.versions).join(", ");
|
|
8792
|
+
throw new ValidationError(`Component "${name}" has no version "${version}". Available: ${availableVersions}`);
|
|
8793
|
+
}
|
|
8794
|
+
throw new ValidationError(`Component "${name}" has no manifest for latest version ${resolvedVersion}`);
|
|
8787
8795
|
}
|
|
8788
8796
|
const manifestResult = componentManifestSchema.safeParse(manifest);
|
|
8789
8797
|
if (!manifestResult.success) {
|
|
8790
|
-
throw new ValidationError(`Invalid component manifest for "${name}@${
|
|
8798
|
+
throw new ValidationError(`Invalid component manifest for "${name}@${resolvedVersion}": ${manifestResult.error.message}`);
|
|
8791
8799
|
}
|
|
8792
|
-
return manifestResult.data;
|
|
8800
|
+
return { manifest: manifestResult.data, version: resolvedVersion };
|
|
8793
8801
|
});
|
|
8794
8802
|
}
|
|
8795
8803
|
async function fetchFileContent(baseUrl, componentName, filePath) {
|
|
@@ -10272,7 +10280,8 @@ var installedComponentSchema = exports_external.object({
|
|
|
10272
10280
|
version: exports_external.string(),
|
|
10273
10281
|
hash: exports_external.string(),
|
|
10274
10282
|
files: exports_external.array(exports_external.string()),
|
|
10275
|
-
installedAt: exports_external.string()
|
|
10283
|
+
installedAt: exports_external.string(),
|
|
10284
|
+
updatedAt: exports_external.string().optional()
|
|
10276
10285
|
});
|
|
10277
10286
|
var ocxLockSchema = exports_external.object({
|
|
10278
10287
|
lockVersion: exports_external.literal(1),
|
|
@@ -13635,12 +13644,292 @@ function registerSearchCommand(program2) {
|
|
|
13635
13644
|
}
|
|
13636
13645
|
});
|
|
13637
13646
|
}
|
|
13647
|
+
|
|
13648
|
+
// src/commands/update.ts
|
|
13649
|
+
import { createHash as createHash2 } from "crypto";
|
|
13650
|
+
import { existsSync as existsSync3 } from "fs";
|
|
13651
|
+
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
13652
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
13653
|
+
function registerUpdateCommand(program2) {
|
|
13654
|
+
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) => {
|
|
13655
|
+
try {
|
|
13656
|
+
await runUpdate(components, options2);
|
|
13657
|
+
} catch (error) {
|
|
13658
|
+
handleError(error, { json: options2.json });
|
|
13659
|
+
}
|
|
13660
|
+
});
|
|
13661
|
+
}
|
|
13662
|
+
async function runUpdate(componentNames, options2) {
|
|
13663
|
+
const cwd = options2.cwd ?? process.cwd();
|
|
13664
|
+
const lockPath = join5(cwd, "ocx.lock");
|
|
13665
|
+
const config = await readOcxConfig(cwd);
|
|
13666
|
+
if (!config) {
|
|
13667
|
+
throw new ConfigError("No ocx.jsonc found. Run 'ocx init' first.");
|
|
13668
|
+
}
|
|
13669
|
+
const lock = await readOcxLock(cwd);
|
|
13670
|
+
if (!lock || Object.keys(lock.installed).length === 0) {
|
|
13671
|
+
throw new ConfigError("Nothing installed yet. Run 'ocx add <component>' first.");
|
|
13672
|
+
}
|
|
13673
|
+
const hasComponents = componentNames.length > 0;
|
|
13674
|
+
const hasAll = options2.all === true;
|
|
13675
|
+
const hasRegistry = options2.registry !== undefined;
|
|
13676
|
+
if (!hasComponents && !hasAll && !hasRegistry) {
|
|
13677
|
+
throw new ValidationError(`Specify components, use --all, or use --registry <name>.
|
|
13678
|
+
|
|
13679
|
+
` + `Examples:
|
|
13680
|
+
` + ` ocx update kdco/agents # Update specific component
|
|
13681
|
+
` + ` ocx update --all # Update all installed components
|
|
13682
|
+
` + " ocx update --registry kdco # Update all from a registry");
|
|
13683
|
+
}
|
|
13684
|
+
if (hasAll && hasComponents) {
|
|
13685
|
+
throw new ValidationError(`Cannot specify components with --all.
|
|
13686
|
+
` + "Use either 'ocx update --all' or 'ocx update <components>'.");
|
|
13687
|
+
}
|
|
13688
|
+
if (hasRegistry && hasComponents) {
|
|
13689
|
+
throw new ValidationError(`Cannot specify components with --registry.
|
|
13690
|
+
` + "Use either 'ocx update --registry <name>' or 'ocx update <components>'.");
|
|
13691
|
+
}
|
|
13692
|
+
if (hasAll && hasRegistry) {
|
|
13693
|
+
throw new ValidationError(`Cannot use --all with --registry.
|
|
13694
|
+
` + "Use either 'ocx update --all' or 'ocx update --registry <name>'.");
|
|
13695
|
+
}
|
|
13696
|
+
const parsedComponents = componentNames.map(parseComponentSpec);
|
|
13697
|
+
for (const spec of parsedComponents) {
|
|
13698
|
+
if (spec.version !== undefined && spec.version === "") {
|
|
13699
|
+
throw new ValidationError(`Invalid version specifier in '${spec.component}@'.` + `
|
|
13700
|
+
Version cannot be empty. Use 'kdco/agents@1.2.0' or omit the version for latest.`);
|
|
13701
|
+
}
|
|
13702
|
+
}
|
|
13703
|
+
const componentsToUpdate = resolveComponentsToUpdate(lock, parsedComponents, options2);
|
|
13704
|
+
if (componentsToUpdate.length === 0) {
|
|
13705
|
+
if (hasRegistry) {
|
|
13706
|
+
throw new NotFoundError(`No installed components from registry '${options2.registry}'.`);
|
|
13707
|
+
}
|
|
13708
|
+
throw new NotFoundError("No matching components found to update.");
|
|
13709
|
+
}
|
|
13710
|
+
const spin = options2.quiet ? null : createSpinner({ text: "Checking for updates..." });
|
|
13711
|
+
spin?.start();
|
|
13712
|
+
const results = [];
|
|
13713
|
+
const updates = [];
|
|
13714
|
+
try {
|
|
13715
|
+
for (const spec of componentsToUpdate) {
|
|
13716
|
+
const qualifiedName = spec.component;
|
|
13717
|
+
const lockEntry = lock.installed[qualifiedName];
|
|
13718
|
+
if (!lockEntry) {
|
|
13719
|
+
throw new NotFoundError(`Component '${qualifiedName}' not found in lock file.`);
|
|
13720
|
+
}
|
|
13721
|
+
const { namespace, component: componentName } = parseQualifiedComponent(qualifiedName);
|
|
13722
|
+
const regConfig = config.registries[namespace];
|
|
13723
|
+
if (!regConfig) {
|
|
13724
|
+
throw new ConfigError(`Registry '${namespace}' not configured. Component '${qualifiedName}' cannot be updated.`);
|
|
13725
|
+
}
|
|
13726
|
+
const fetchResult = await fetchComponentVersion(regConfig.url, componentName, spec.version);
|
|
13727
|
+
const manifest = fetchResult.manifest;
|
|
13728
|
+
const version = fetchResult.version;
|
|
13729
|
+
const normalizedManifest = normalizeComponentManifest(manifest);
|
|
13730
|
+
const files = [];
|
|
13731
|
+
for (const file of normalizedManifest.files) {
|
|
13732
|
+
const content2 = await fetchFileContent(regConfig.url, componentName, file.path);
|
|
13733
|
+
files.push({ path: file.path, content: Buffer.from(content2) });
|
|
13734
|
+
}
|
|
13735
|
+
const newHash = await hashBundle2(files);
|
|
13736
|
+
if (newHash === lockEntry.hash) {
|
|
13737
|
+
results.push({
|
|
13738
|
+
qualifiedName,
|
|
13739
|
+
oldVersion: lockEntry.version,
|
|
13740
|
+
newVersion: version,
|
|
13741
|
+
status: "up-to-date"
|
|
13742
|
+
});
|
|
13743
|
+
} else if (options2.dryRun) {
|
|
13744
|
+
results.push({
|
|
13745
|
+
qualifiedName,
|
|
13746
|
+
oldVersion: lockEntry.version,
|
|
13747
|
+
newVersion: version,
|
|
13748
|
+
status: "would-update"
|
|
13749
|
+
});
|
|
13750
|
+
} else {
|
|
13751
|
+
results.push({
|
|
13752
|
+
qualifiedName,
|
|
13753
|
+
oldVersion: lockEntry.version,
|
|
13754
|
+
newVersion: version,
|
|
13755
|
+
status: "updated"
|
|
13756
|
+
});
|
|
13757
|
+
updates.push({
|
|
13758
|
+
qualifiedName,
|
|
13759
|
+
component: normalizedManifest,
|
|
13760
|
+
files,
|
|
13761
|
+
newHash,
|
|
13762
|
+
newVersion: version,
|
|
13763
|
+
baseUrl: regConfig.url
|
|
13764
|
+
});
|
|
13765
|
+
}
|
|
13766
|
+
}
|
|
13767
|
+
spin?.succeed(`Checked ${componentsToUpdate.length} component(s)`);
|
|
13768
|
+
if (options2.dryRun) {
|
|
13769
|
+
outputDryRun(results, options2);
|
|
13770
|
+
return;
|
|
13771
|
+
}
|
|
13772
|
+
if (updates.length === 0) {
|
|
13773
|
+
if (!options2.quiet && !options2.json) {
|
|
13774
|
+
logger.info("");
|
|
13775
|
+
logger.success("All components are up to date.");
|
|
13776
|
+
}
|
|
13777
|
+
if (options2.json) {
|
|
13778
|
+
console.log(JSON.stringify({ success: true, updated: [], upToDate: results }, null, 2));
|
|
13779
|
+
}
|
|
13780
|
+
return;
|
|
13781
|
+
}
|
|
13782
|
+
const installSpin = options2.quiet ? null : createSpinner({ text: "Updating components..." });
|
|
13783
|
+
installSpin?.start();
|
|
13784
|
+
for (const update of updates) {
|
|
13785
|
+
for (const file of update.files) {
|
|
13786
|
+
const fileObj = update.component.files.find((f) => f.path === file.path);
|
|
13787
|
+
if (!fileObj)
|
|
13788
|
+
continue;
|
|
13789
|
+
const targetPath = join5(cwd, fileObj.target);
|
|
13790
|
+
const targetDir = dirname3(targetPath);
|
|
13791
|
+
if (!existsSync3(targetDir)) {
|
|
13792
|
+
await mkdir4(targetDir, { recursive: true });
|
|
13793
|
+
}
|
|
13794
|
+
await writeFile3(targetPath, file.content);
|
|
13795
|
+
if (options2.verbose) {
|
|
13796
|
+
logger.info(` \u2713 Updated ${fileObj.target}`);
|
|
13797
|
+
}
|
|
13798
|
+
}
|
|
13799
|
+
const existingEntry = lock.installed[update.qualifiedName];
|
|
13800
|
+
if (!existingEntry) {
|
|
13801
|
+
throw new NotFoundError(`Component '${update.qualifiedName}' not found in lock file.`);
|
|
13802
|
+
}
|
|
13803
|
+
lock.installed[update.qualifiedName] = {
|
|
13804
|
+
registry: existingEntry.registry,
|
|
13805
|
+
version: update.newVersion,
|
|
13806
|
+
hash: update.newHash,
|
|
13807
|
+
files: existingEntry.files,
|
|
13808
|
+
installedAt: existingEntry.installedAt,
|
|
13809
|
+
updatedAt: new Date().toISOString()
|
|
13810
|
+
};
|
|
13811
|
+
}
|
|
13812
|
+
installSpin?.succeed(`Updated ${updates.length} component(s)`);
|
|
13813
|
+
await writeFile3(lockPath, JSON.stringify(lock, null, 2), "utf-8");
|
|
13814
|
+
if (options2.json) {
|
|
13815
|
+
console.log(JSON.stringify({
|
|
13816
|
+
success: true,
|
|
13817
|
+
updated: results.filter((r2) => r2.status === "updated"),
|
|
13818
|
+
upToDate: results.filter((r2) => r2.status === "up-to-date")
|
|
13819
|
+
}, null, 2));
|
|
13820
|
+
} else if (!options2.quiet) {
|
|
13821
|
+
logger.info("");
|
|
13822
|
+
for (const result of results) {
|
|
13823
|
+
if (result.status === "updated") {
|
|
13824
|
+
logger.info(` \u2713 ${result.qualifiedName} (${result.oldVersion} \u2192 ${result.newVersion})`);
|
|
13825
|
+
} else if (result.status === "up-to-date" && options2.verbose) {
|
|
13826
|
+
logger.info(` \u25CB ${result.qualifiedName} (already up to date)`);
|
|
13827
|
+
}
|
|
13828
|
+
}
|
|
13829
|
+
logger.info("");
|
|
13830
|
+
logger.success(`Done! Updated ${updates.length} component(s).`);
|
|
13831
|
+
}
|
|
13832
|
+
} catch (error) {
|
|
13833
|
+
spin?.fail("Failed to check for updates");
|
|
13834
|
+
throw error;
|
|
13835
|
+
}
|
|
13836
|
+
}
|
|
13837
|
+
function parseComponentSpec(spec) {
|
|
13838
|
+
const atIndex = spec.lastIndexOf("@");
|
|
13839
|
+
if (atIndex <= 0) {
|
|
13840
|
+
return { component: spec };
|
|
13841
|
+
}
|
|
13842
|
+
return {
|
|
13843
|
+
component: spec.slice(0, atIndex),
|
|
13844
|
+
version: spec.slice(atIndex + 1)
|
|
13845
|
+
};
|
|
13846
|
+
}
|
|
13847
|
+
function resolveComponentsToUpdate(lock, parsedComponents, options2) {
|
|
13848
|
+
const installedComponents = Object.keys(lock.installed);
|
|
13849
|
+
if (options2.all) {
|
|
13850
|
+
return installedComponents.map((c) => ({ component: c }));
|
|
13851
|
+
}
|
|
13852
|
+
if (options2.registry) {
|
|
13853
|
+
return installedComponents.filter((name) => {
|
|
13854
|
+
const entry = lock.installed[name];
|
|
13855
|
+
return entry?.registry === options2.registry;
|
|
13856
|
+
}).map((c) => ({ component: c }));
|
|
13857
|
+
}
|
|
13858
|
+
const result = [];
|
|
13859
|
+
for (const spec of parsedComponents) {
|
|
13860
|
+
const name = spec.component;
|
|
13861
|
+
if (!name.includes("/")) {
|
|
13862
|
+
const suggestions = installedComponents.filter((installed) => installed.endsWith(`/${name}`));
|
|
13863
|
+
if (suggestions.length === 1) {
|
|
13864
|
+
throw new ValidationError(`Ambiguous component '${name}'. Did you mean '${suggestions[0]}'?`);
|
|
13865
|
+
}
|
|
13866
|
+
if (suggestions.length > 1) {
|
|
13867
|
+
throw new ValidationError(`Ambiguous component '${name}'. Found in multiple registries:
|
|
13868
|
+
` + suggestions.map((s) => ` - ${s}`).join(`
|
|
13869
|
+
`) + `
|
|
13870
|
+
|
|
13871
|
+
Please use a fully qualified name (registry/component).`);
|
|
13872
|
+
}
|
|
13873
|
+
throw new ValidationError(`Component '${name}' must include a registry prefix (e.g., 'kdco/${name}').`);
|
|
13874
|
+
}
|
|
13875
|
+
if (!lock.installed[name]) {
|
|
13876
|
+
throw new NotFoundError(`Component '${name}' is not installed.
|
|
13877
|
+
Run 'ocx add ${name}' to install it first.`);
|
|
13878
|
+
}
|
|
13879
|
+
result.push(spec);
|
|
13880
|
+
}
|
|
13881
|
+
return result;
|
|
13882
|
+
}
|
|
13883
|
+
function outputDryRun(results, options2) {
|
|
13884
|
+
const wouldUpdate = results.filter((r2) => r2.status === "would-update");
|
|
13885
|
+
const upToDate = results.filter((r2) => r2.status === "up-to-date");
|
|
13886
|
+
if (options2.json) {
|
|
13887
|
+
console.log(JSON.stringify({ dryRun: true, wouldUpdate, upToDate }, null, 2));
|
|
13888
|
+
return;
|
|
13889
|
+
}
|
|
13890
|
+
if (!options2.quiet) {
|
|
13891
|
+
logger.info("");
|
|
13892
|
+
if (wouldUpdate.length > 0) {
|
|
13893
|
+
logger.info("Would update:");
|
|
13894
|
+
for (const result of wouldUpdate) {
|
|
13895
|
+
logger.info(` ${result.qualifiedName} (${result.oldVersion} \u2192 ${result.newVersion})`);
|
|
13896
|
+
}
|
|
13897
|
+
}
|
|
13898
|
+
if (upToDate.length > 0 && options2.verbose) {
|
|
13899
|
+
logger.info("");
|
|
13900
|
+
logger.info("Already up to date:");
|
|
13901
|
+
for (const result of upToDate) {
|
|
13902
|
+
logger.info(` ${result.qualifiedName}`);
|
|
13903
|
+
}
|
|
13904
|
+
}
|
|
13905
|
+
if (wouldUpdate.length > 0) {
|
|
13906
|
+
logger.info("");
|
|
13907
|
+
logger.info("Run without --dry-run to apply changes.");
|
|
13908
|
+
} else {
|
|
13909
|
+
logger.info("All components are up to date.");
|
|
13910
|
+
}
|
|
13911
|
+
}
|
|
13912
|
+
}
|
|
13913
|
+
async function hashContent2(content2) {
|
|
13914
|
+
return createHash2("sha256").update(content2).digest("hex");
|
|
13915
|
+
}
|
|
13916
|
+
async function hashBundle2(files) {
|
|
13917
|
+
const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
|
|
13918
|
+
const manifestParts = [];
|
|
13919
|
+
for (const file of sorted) {
|
|
13920
|
+
const hash = await hashContent2(file.content);
|
|
13921
|
+
manifestParts.push(`${file.path}:${hash}`);
|
|
13922
|
+
}
|
|
13923
|
+
return hashContent2(manifestParts.join(`
|
|
13924
|
+
`));
|
|
13925
|
+
}
|
|
13638
13926
|
// src/index.ts
|
|
13639
|
-
var version = "1.0.
|
|
13927
|
+
var version = "1.0.15";
|
|
13640
13928
|
async function main2() {
|
|
13641
13929
|
const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
|
|
13642
13930
|
registerInitCommand(program2);
|
|
13643
13931
|
registerAddCommand(program2);
|
|
13932
|
+
registerUpdateCommand(program2);
|
|
13644
13933
|
registerDiffCommand(program2);
|
|
13645
13934
|
registerSearchCommand(program2);
|
|
13646
13935
|
registerRegistryCommand(program2);
|
|
@@ -13661,4 +13950,4 @@ export {
|
|
|
13661
13950
|
buildRegistry
|
|
13662
13951
|
};
|
|
13663
13952
|
|
|
13664
|
-
//# debugId=
|
|
13953
|
+
//# debugId=0C10B0E363D77B8C64756E2164756E21
|