ocx 1.3.0 → 1.3.2
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 +1084 -807
- package/dist/index.js.map +15 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10501,7 +10501,8 @@ var ghostConfigSchema = exports_external.object({
|
|
|
10501
10501
|
"opencode.json"
|
|
10502
10502
|
]).describe("Glob patterns to exclude from the symlink farm"),
|
|
10503
10503
|
include: exports_external.array(globPatternSchema).default([]).describe("Glob patterns to re-include from excluded set (for power users)"),
|
|
10504
|
-
renameWindow: exports_external.boolean().default(true).describe("Set terminal/tmux window name when launching OpenCode")
|
|
10504
|
+
renameWindow: exports_external.boolean().default(true).describe("Set terminal/tmux window name when launching OpenCode"),
|
|
10505
|
+
maxFiles: exports_external.number().int().min(0, "maxFiles must be non-negative").default(1e4).describe("Maximum files to process (0 = unlimited)")
|
|
10505
10506
|
});
|
|
10506
10507
|
|
|
10507
10508
|
// src/utils/errors.ts
|
|
@@ -10511,7 +10512,8 @@ var EXIT_CODES = {
|
|
|
10511
10512
|
NOT_FOUND: 66,
|
|
10512
10513
|
NETWORK: 69,
|
|
10513
10514
|
CONFIG: 78,
|
|
10514
|
-
INTEGRITY: 1
|
|
10515
|
+
INTEGRITY: 1,
|
|
10516
|
+
FILE_LIMIT: 74
|
|
10515
10517
|
};
|
|
10516
10518
|
|
|
10517
10519
|
class OCXError extends Error {
|
|
@@ -10586,6 +10588,17 @@ class GhostConfigError extends OCXError {
|
|
|
10586
10588
|
}
|
|
10587
10589
|
}
|
|
10588
10590
|
|
|
10591
|
+
class FileLimitExceededError extends OCXError {
|
|
10592
|
+
count;
|
|
10593
|
+
limit;
|
|
10594
|
+
constructor(count, limit) {
|
|
10595
|
+
super(`File limit exceeded: found ${count} entries (limit: ${limit}). ` + `To fix: add exclude patterns to ghost.jsonc, or set "maxFiles" to a higher value (0 = unlimited).`, "FILE_LIMIT", EXIT_CODES.FILE_LIMIT);
|
|
10596
|
+
this.count = count;
|
|
10597
|
+
this.limit = limit;
|
|
10598
|
+
this.name = "FileLimitExceededError";
|
|
10599
|
+
}
|
|
10600
|
+
}
|
|
10601
|
+
|
|
10589
10602
|
class ProfileNotFoundError extends OCXError {
|
|
10590
10603
|
constructor(name) {
|
|
10591
10604
|
super(`Profile "${name}" not found`, "NOT_FOUND", EXIT_CODES.NOT_FOUND);
|
|
@@ -10686,7 +10699,8 @@ var DEFAULT_GHOST_CONFIG = {
|
|
|
10686
10699
|
"opencode.json"
|
|
10687
10700
|
],
|
|
10688
10701
|
include: [],
|
|
10689
|
-
renameWindow: true
|
|
10702
|
+
renameWindow: true,
|
|
10703
|
+
maxFiles: 1e4
|
|
10690
10704
|
};
|
|
10691
10705
|
|
|
10692
10706
|
class ProfileManager {
|
|
@@ -10880,7 +10894,7 @@ class GhostConfigProvider {
|
|
|
10880
10894
|
// package.json
|
|
10881
10895
|
var package_default = {
|
|
10882
10896
|
name: "ocx",
|
|
10883
|
-
version: "1.3.
|
|
10897
|
+
version: "1.3.2",
|
|
10884
10898
|
description: "OCX CLI - ShadCN-style registry for OpenCode extensions. Install agents, plugins, skills, and MCP servers.",
|
|
10885
10899
|
author: "kdcokenny",
|
|
10886
10900
|
license: "MIT",
|
|
@@ -11020,13 +11034,13 @@ async function fetchFileContent(baseUrl, componentName, filePath) {
|
|
|
11020
11034
|
return response.text();
|
|
11021
11035
|
}
|
|
11022
11036
|
|
|
11023
|
-
// ../../node_modules/.bun/remeda@2.33.
|
|
11037
|
+
// ../../node_modules/.bun/remeda@2.33.1/node_modules/remeda/dist/lazyDataLastImpl-DtF3cihj.js
|
|
11024
11038
|
function e(e2, t, n) {
|
|
11025
11039
|
let r = (n2) => e2(n2, ...t);
|
|
11026
11040
|
return n === undefined ? r : Object.assign(r, { lazy: n, lazyArgs: t });
|
|
11027
11041
|
}
|
|
11028
11042
|
|
|
11029
|
-
// ../../node_modules/.bun/remeda@2.33.
|
|
11043
|
+
// ../../node_modules/.bun/remeda@2.33.1/node_modules/remeda/dist/purry-GjwKKIlp.js
|
|
11030
11044
|
function t(t2, n, r) {
|
|
11031
11045
|
let i = t2.length - n.length;
|
|
11032
11046
|
if (i === 0)
|
|
@@ -11036,7 +11050,7 @@ function t(t2, n, r) {
|
|
|
11036
11050
|
throw Error(`Wrong number of arguments`);
|
|
11037
11051
|
}
|
|
11038
11052
|
|
|
11039
|
-
// ../../node_modules/.bun/remeda@2.33.
|
|
11053
|
+
// ../../node_modules/.bun/remeda@2.33.1/node_modules/remeda/dist/isPlainObject.js
|
|
11040
11054
|
function e2(e3) {
|
|
11041
11055
|
if (typeof e3 != `object` || !e3)
|
|
11042
11056
|
return false;
|
|
@@ -11044,7 +11058,7 @@ function e2(e3) {
|
|
|
11044
11058
|
return t2 === null || t2 === Object.prototype;
|
|
11045
11059
|
}
|
|
11046
11060
|
|
|
11047
|
-
// ../../node_modules/.bun/remeda@2.33.
|
|
11061
|
+
// ../../node_modules/.bun/remeda@2.33.1/node_modules/remeda/dist/mergeDeep.js
|
|
11048
11062
|
function n(...t2) {
|
|
11049
11063
|
return t(r, t2);
|
|
11050
11064
|
}
|
|
@@ -13770,7 +13784,7 @@ function registerBuildCommand(program2) {
|
|
|
13770
13784
|
});
|
|
13771
13785
|
}
|
|
13772
13786
|
|
|
13773
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
13787
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/diff/base.js
|
|
13774
13788
|
class Diff {
|
|
13775
13789
|
diff(oldStr, newStr, options2 = {}) {
|
|
13776
13790
|
let callback;
|
|
@@ -13970,12 +13984,12 @@ class Diff {
|
|
|
13970
13984
|
}
|
|
13971
13985
|
}
|
|
13972
13986
|
|
|
13973
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
13987
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/diff/character.js
|
|
13974
13988
|
class CharacterDiff extends Diff {
|
|
13975
13989
|
}
|
|
13976
13990
|
var characterDiff = new CharacterDiff;
|
|
13977
13991
|
|
|
13978
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
13992
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/util/string.js
|
|
13979
13993
|
function longestCommonPrefix(str1, str2) {
|
|
13980
13994
|
let i;
|
|
13981
13995
|
for (i = 0;i < str1.length && i < str2.length; i++) {
|
|
@@ -14071,8 +14085,8 @@ function leadingWs(string) {
|
|
|
14071
14085
|
return match ? match[0] : "";
|
|
14072
14086
|
}
|
|
14073
14087
|
|
|
14074
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
14075
|
-
var extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{
|
|
14088
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/diff/word.js
|
|
14089
|
+
var extendedWordChars = "a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}";
|
|
14076
14090
|
var tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, "ug");
|
|
14077
14091
|
|
|
14078
14092
|
class WordDiff extends Diff {
|
|
@@ -14090,7 +14104,15 @@ class WordDiff extends Diff {
|
|
|
14090
14104
|
if (segmenter2.resolvedOptions().granularity != "word") {
|
|
14091
14105
|
throw new Error('The segmenter passed must have a granularity of "word"');
|
|
14092
14106
|
}
|
|
14093
|
-
parts =
|
|
14107
|
+
parts = [];
|
|
14108
|
+
for (const segmentObj of Array.from(segmenter2.segment(value))) {
|
|
14109
|
+
const segment = segmentObj.segment;
|
|
14110
|
+
if (parts.length && /\s/.test(parts[parts.length - 1]) && /\s/.test(segment)) {
|
|
14111
|
+
parts[parts.length - 1] += segment;
|
|
14112
|
+
} else {
|
|
14113
|
+
parts.push(segment);
|
|
14114
|
+
}
|
|
14115
|
+
}
|
|
14094
14116
|
} else {
|
|
14095
14117
|
parts = value.match(tokenizeIncludingWhitespace) || [];
|
|
14096
14118
|
}
|
|
@@ -14209,7 +14231,7 @@ class WordsWithSpaceDiff extends Diff {
|
|
|
14209
14231
|
}
|
|
14210
14232
|
var wordsWithSpaceDiff = new WordsWithSpaceDiff;
|
|
14211
14233
|
|
|
14212
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
14234
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/diff/line.js
|
|
14213
14235
|
class LineDiff extends Diff {
|
|
14214
14236
|
constructor() {
|
|
14215
14237
|
super(...arguments);
|
|
@@ -14262,7 +14284,7 @@ function tokenize(value, options2) {
|
|
|
14262
14284
|
return retLines;
|
|
14263
14285
|
}
|
|
14264
14286
|
|
|
14265
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
14287
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/diff/sentence.js
|
|
14266
14288
|
function isSentenceEndPunct(char) {
|
|
14267
14289
|
return char == "." || char == "!" || char == "?";
|
|
14268
14290
|
}
|
|
@@ -14292,7 +14314,7 @@ class SentenceDiff extends Diff {
|
|
|
14292
14314
|
}
|
|
14293
14315
|
var sentenceDiff = new SentenceDiff;
|
|
14294
14316
|
|
|
14295
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
14317
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/diff/css.js
|
|
14296
14318
|
class CssDiff extends Diff {
|
|
14297
14319
|
tokenize(value) {
|
|
14298
14320
|
return value.split(/([{}:;,]|\s+)/);
|
|
@@ -14300,7 +14322,7 @@ class CssDiff extends Diff {
|
|
|
14300
14322
|
}
|
|
14301
14323
|
var cssDiff = new CssDiff;
|
|
14302
14324
|
|
|
14303
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
14325
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/diff/json.js
|
|
14304
14326
|
class JsonDiff extends Diff {
|
|
14305
14327
|
constructor() {
|
|
14306
14328
|
super(...arguments);
|
|
@@ -14369,7 +14391,7 @@ function canonicalize(obj, stack, replacementStack, replacer, key) {
|
|
|
14369
14391
|
return canonicalizedObj;
|
|
14370
14392
|
}
|
|
14371
14393
|
|
|
14372
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
14394
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/diff/array.js
|
|
14373
14395
|
class ArrayDiff extends Diff {
|
|
14374
14396
|
tokenize(value) {
|
|
14375
14397
|
return value.slice();
|
|
@@ -14383,7 +14405,12 @@ class ArrayDiff extends Diff {
|
|
|
14383
14405
|
}
|
|
14384
14406
|
var arrayDiff = new ArrayDiff;
|
|
14385
14407
|
|
|
14386
|
-
// ../../node_modules/.bun/diff@8.0.
|
|
14408
|
+
// ../../node_modules/.bun/diff@8.0.3/node_modules/diff/libesm/patch/create.js
|
|
14409
|
+
var INCLUDE_HEADERS = {
|
|
14410
|
+
includeIndex: true,
|
|
14411
|
+
includeUnderline: true,
|
|
14412
|
+
includeFileHeaders: true
|
|
14413
|
+
};
|
|
14387
14414
|
function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options2) {
|
|
14388
14415
|
let optionsObj;
|
|
14389
14416
|
if (!options2) {
|
|
@@ -14491,18 +14518,28 @@ function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, ne
|
|
|
14491
14518
|
};
|
|
14492
14519
|
}
|
|
14493
14520
|
}
|
|
14494
|
-
function formatPatch(patch) {
|
|
14521
|
+
function formatPatch(patch, headerOptions) {
|
|
14522
|
+
if (!headerOptions) {
|
|
14523
|
+
headerOptions = INCLUDE_HEADERS;
|
|
14524
|
+
}
|
|
14495
14525
|
if (Array.isArray(patch)) {
|
|
14496
|
-
|
|
14526
|
+
if (patch.length > 1 && !headerOptions.includeFileHeaders) {
|
|
14527
|
+
throw new Error("Cannot omit file headers on a multi-file patch. " + "(The result would be unparseable; how would a tool trying to apply " + "the patch know which changes are to which file?)");
|
|
14528
|
+
}
|
|
14529
|
+
return patch.map((p) => formatPatch(p, headerOptions)).join(`
|
|
14497
14530
|
`);
|
|
14498
14531
|
}
|
|
14499
14532
|
const ret = [];
|
|
14500
|
-
if (patch.oldFileName == patch.newFileName) {
|
|
14533
|
+
if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName) {
|
|
14501
14534
|
ret.push("Index: " + patch.oldFileName);
|
|
14502
14535
|
}
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14536
|
+
if (headerOptions.includeUnderline) {
|
|
14537
|
+
ret.push("===================================================================");
|
|
14538
|
+
}
|
|
14539
|
+
if (headerOptions.includeFileHeaders) {
|
|
14540
|
+
ret.push("--- " + patch.oldFileName + (typeof patch.oldHeader === "undefined" ? "" : "\t" + patch.oldHeader));
|
|
14541
|
+
ret.push("+++ " + patch.newFileName + (typeof patch.newHeader === "undefined" ? "" : "\t" + patch.newHeader));
|
|
14542
|
+
}
|
|
14506
14543
|
for (let i = 0;i < patch.hunks.length; i++) {
|
|
14507
14544
|
const hunk = patch.hunks[i];
|
|
14508
14545
|
if (hunk.oldLines === 0) {
|
|
@@ -14529,14 +14566,14 @@ function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader
|
|
|
14529
14566
|
if (!patchObj) {
|
|
14530
14567
|
return;
|
|
14531
14568
|
}
|
|
14532
|
-
return formatPatch(patchObj);
|
|
14569
|
+
return formatPatch(patchObj, options2 === null || options2 === undefined ? undefined : options2.headerOptions);
|
|
14533
14570
|
} else {
|
|
14534
14571
|
const { callback } = options2;
|
|
14535
14572
|
structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, Object.assign(Object.assign({}, options2), { callback: (patchObj) => {
|
|
14536
14573
|
if (!patchObj) {
|
|
14537
14574
|
callback(undefined);
|
|
14538
14575
|
} else {
|
|
14539
|
-
callback(formatPatch(patchObj));
|
|
14576
|
+
callback(formatPatch(patchObj, options2.headerOptions));
|
|
14540
14577
|
}
|
|
14541
14578
|
} }));
|
|
14542
14579
|
}
|
|
@@ -14762,7 +14799,7 @@ async function runGhostInit(options2) {
|
|
|
14762
14799
|
}
|
|
14763
14800
|
|
|
14764
14801
|
// src/commands/ghost/opencode.ts
|
|
14765
|
-
import { renameSync, rmSync } from "fs";
|
|
14802
|
+
import { existsSync as existsSync4, renameSync, rmSync, statSync as statSync2 } from "fs";
|
|
14766
14803
|
import { copyFile, mkdir as mkdir5, readdir as readdir5 } from "fs/promises";
|
|
14767
14804
|
import path7 from "path";
|
|
14768
14805
|
var {Glob: Glob3 } = globalThis.Bun;
|
|
@@ -16448,7 +16485,12 @@ function createFileSync(tempDir, projectDir, options2) {
|
|
|
16448
16485
|
stabilityThreshold: 200,
|
|
16449
16486
|
pollInterval: 50
|
|
16450
16487
|
},
|
|
16451
|
-
ignored: (filePath) =>
|
|
16488
|
+
ignored: (filePath) => {
|
|
16489
|
+
const relativePath = normalizePath2(relative4(tempDir, filePath));
|
|
16490
|
+
if (options2?.overlayFiles?.has(relativePath))
|
|
16491
|
+
return true;
|
|
16492
|
+
return isExcluded(relativePath, gitignore);
|
|
16493
|
+
}
|
|
16452
16494
|
});
|
|
16453
16495
|
watcher.on("error", (err) => {
|
|
16454
16496
|
failures.push({ path: "<watcher>", error: err });
|
|
@@ -16534,10 +16576,12 @@ function createFileSync(tempDir, projectDir, options2) {
|
|
|
16534
16576
|
}
|
|
16535
16577
|
|
|
16536
16578
|
// src/utils/symlink-farm.ts
|
|
16579
|
+
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
16537
16580
|
import { randomBytes } from "crypto";
|
|
16581
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync, statSync } from "fs";
|
|
16538
16582
|
import { mkdir as mkdir4, readdir as readdir4, rename as rename2, rm as rm2, stat as stat5, symlink as symlink2 } from "fs/promises";
|
|
16539
|
-
import { tmpdir } from "os";
|
|
16540
|
-
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join7, relative as relative5 } from "path";
|
|
16583
|
+
import { homedir as homedir2, tmpdir } from "os";
|
|
16584
|
+
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join7, normalize as normalize2, posix, relative as relative5, sep } from "path";
|
|
16541
16585
|
|
|
16542
16586
|
// src/utils/pattern-filter.ts
|
|
16543
16587
|
import path5 from "path";
|
|
@@ -16647,10 +16691,25 @@ async function createSymlinkFarm(sourceDir, options2) {
|
|
|
16647
16691
|
const tempDir = join7(tmpdir(), `${GHOST_DIR_PREFIX}${suffix}`);
|
|
16648
16692
|
await Bun.write(join7(tempDir, GHOST_MARKER_FILE), "");
|
|
16649
16693
|
try {
|
|
16694
|
+
const projectDir = options2?.projectDir ?? sourceDir;
|
|
16695
|
+
const gitIgnore = await loadGitIgnoreStack(projectDir);
|
|
16650
16696
|
const matcher = createPathMatcher(options2?.includePatterns ?? [], options2?.excludePatterns ?? []);
|
|
16651
|
-
const
|
|
16652
|
-
|
|
16653
|
-
|
|
16697
|
+
const maxFiles = options2?.maxFiles ?? 1e4;
|
|
16698
|
+
const state = { count: 0, plan: [] };
|
|
16699
|
+
const plan = await computeSymlinkPlan(sourceDir, sourceDir, matcher, state, maxFiles, gitIgnore, projectDir);
|
|
16700
|
+
const symlinkRoots = new Set;
|
|
16701
|
+
await executeSymlinkPlan(plan, sourceDir, tempDir, "", symlinkRoots);
|
|
16702
|
+
for (const op of state.plan) {
|
|
16703
|
+
const relPath = normalizePath3(relative5(sourceDir, op.source));
|
|
16704
|
+
const targetPath = join7(tempDir, relPath);
|
|
16705
|
+
const parentDir = dirname6(targetPath);
|
|
16706
|
+
if (parentDir !== tempDir) {
|
|
16707
|
+
await mkdir4(parentDir, { recursive: true });
|
|
16708
|
+
}
|
|
16709
|
+
await symlink2(op.source, targetPath);
|
|
16710
|
+
symlinkRoots.add(relPath);
|
|
16711
|
+
}
|
|
16712
|
+
return { tempDir, symlinkRoots };
|
|
16654
16713
|
} catch (error) {
|
|
16655
16714
|
await rm2(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
16656
16715
|
throw error;
|
|
@@ -16707,7 +16766,7 @@ async function cleanupOrphanedGhostDirs(tempBase = tmpdir()) {
|
|
|
16707
16766
|
}
|
|
16708
16767
|
return cleanedCount;
|
|
16709
16768
|
}
|
|
16710
|
-
async function computeSymlinkPlan(sourceDir, projectRoot, matcher) {
|
|
16769
|
+
async function computeSymlinkPlan(sourceDir, projectRoot, matcher, state, maxFiles, gitIgnore, projectDir) {
|
|
16711
16770
|
if (!isAbsolute2(sourceDir)) {
|
|
16712
16771
|
throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
|
|
16713
16772
|
}
|
|
@@ -16716,32 +16775,79 @@ async function computeSymlinkPlan(sourceDir, projectRoot, matcher) {
|
|
|
16716
16775
|
files: [],
|
|
16717
16776
|
partialDirs: new Map
|
|
16718
16777
|
};
|
|
16778
|
+
let resolvedSource;
|
|
16779
|
+
let resolvedProject;
|
|
16780
|
+
try {
|
|
16781
|
+
resolvedSource = realpathSync(sourceDir);
|
|
16782
|
+
resolvedProject = realpathSync(projectDir);
|
|
16783
|
+
} catch {
|
|
16784
|
+
resolvedSource = sourceDir;
|
|
16785
|
+
resolvedProject = projectDir;
|
|
16786
|
+
}
|
|
16787
|
+
const rel = normalize2(relative5(resolvedProject, resolvedSource));
|
|
16788
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute2(rel)) {
|
|
16789
|
+
return plan;
|
|
16790
|
+
}
|
|
16791
|
+
const relativeDirPath = normalizePath3(relative5(resolvedProject, resolvedSource));
|
|
16792
|
+
const nestedGitignorePath = join7(sourceDir, ".gitignore");
|
|
16793
|
+
if (gitIgnore && existsSync3(nestedGitignorePath)) {
|
|
16794
|
+
try {
|
|
16795
|
+
addScopedRules(gitIgnore, readFileSync2(nestedGitignorePath, "utf-8"), relativeDirPath);
|
|
16796
|
+
} catch {}
|
|
16797
|
+
}
|
|
16719
16798
|
const entries = await readdir4(sourceDir, { withFileTypes: true });
|
|
16720
16799
|
for (const entry of entries) {
|
|
16800
|
+
if (entry.name === ".git")
|
|
16801
|
+
continue;
|
|
16721
16802
|
const sourcePath = join7(sourceDir, entry.name);
|
|
16722
|
-
const relativePath =
|
|
16723
|
-
const
|
|
16803
|
+
const relativePath = normalizePath3(relative5(projectDir, sourcePath));
|
|
16804
|
+
const checkPath = entry.isDirectory() ? `${relativePath}/` : relativePath;
|
|
16805
|
+
const isGitignored = gitIgnore?.ignores(checkPath) ?? false;
|
|
16806
|
+
const matcherRelativePath = normalizeForMatching(sourcePath, projectRoot);
|
|
16807
|
+
const disposition = matcher.getDisposition(matcherRelativePath);
|
|
16724
16808
|
if (disposition.type === "excluded") {
|
|
16725
16809
|
continue;
|
|
16726
16810
|
}
|
|
16727
|
-
if (
|
|
16728
|
-
if (
|
|
16811
|
+
if (entry.isDirectory()) {
|
|
16812
|
+
if (isGitignored) {
|
|
16813
|
+
state.plan.push({ type: "directory", source: sourcePath });
|
|
16814
|
+
state.count += 1;
|
|
16815
|
+
if (maxFiles > 0 && state.count > maxFiles) {
|
|
16816
|
+
throw new FileLimitExceededError(state.count, maxFiles);
|
|
16817
|
+
}
|
|
16818
|
+
continue;
|
|
16819
|
+
}
|
|
16820
|
+
if (disposition.type === "included") {
|
|
16729
16821
|
plan.wholeDirs.push(entry.name);
|
|
16730
|
-
|
|
16731
|
-
|
|
16822
|
+
state.count += 1;
|
|
16823
|
+
if (maxFiles > 0 && state.count > maxFiles) {
|
|
16824
|
+
throw new FileLimitExceededError(state.count, maxFiles);
|
|
16825
|
+
}
|
|
16826
|
+
continue;
|
|
16732
16827
|
}
|
|
16733
|
-
|
|
16734
|
-
}
|
|
16735
|
-
if (entry.isDirectory()) {
|
|
16736
|
-
const nestedPlan = await computeSymlinkPlan(sourcePath, projectRoot, matcher);
|
|
16828
|
+
const nestedPlan = await computeSymlinkPlan(sourcePath, projectRoot, matcher, state, maxFiles, gitIgnore, projectDir);
|
|
16737
16829
|
plan.partialDirs.set(entry.name, nestedPlan);
|
|
16738
16830
|
} else {
|
|
16831
|
+
if (isGitignored)
|
|
16832
|
+
continue;
|
|
16833
|
+
if (disposition.type === "included") {
|
|
16834
|
+
plan.files.push(entry.name);
|
|
16835
|
+
state.count += 1;
|
|
16836
|
+
if (maxFiles > 0 && state.count > maxFiles) {
|
|
16837
|
+
throw new FileLimitExceededError(state.count, maxFiles);
|
|
16838
|
+
}
|
|
16839
|
+
continue;
|
|
16840
|
+
}
|
|
16739
16841
|
plan.files.push(entry.name);
|
|
16842
|
+
state.count += 1;
|
|
16843
|
+
if (maxFiles > 0 && state.count > maxFiles) {
|
|
16844
|
+
throw new FileLimitExceededError(state.count, maxFiles);
|
|
16845
|
+
}
|
|
16740
16846
|
}
|
|
16741
16847
|
}
|
|
16742
16848
|
return plan;
|
|
16743
16849
|
}
|
|
16744
|
-
async function executeSymlinkPlan(plan, sourceRoot, targetRoot) {
|
|
16850
|
+
async function executeSymlinkPlan(plan, sourceRoot, targetRoot, relativePath = "", symlinkRoots) {
|
|
16745
16851
|
if (!isAbsolute2(sourceRoot)) {
|
|
16746
16852
|
throw new Error(`sourceRoot must be an absolute path, got: ${sourceRoot}`);
|
|
16747
16853
|
}
|
|
@@ -16752,18 +16858,99 @@ async function executeSymlinkPlan(plan, sourceRoot, targetRoot) {
|
|
|
16752
16858
|
const sourcePath = join7(sourceRoot, dirName);
|
|
16753
16859
|
const targetPath = join7(targetRoot, dirName);
|
|
16754
16860
|
await symlink2(sourcePath, targetPath);
|
|
16861
|
+
symlinkRoots?.add(relativePath ? `${relativePath}/${dirName}` : dirName);
|
|
16755
16862
|
}
|
|
16756
16863
|
for (const fileName of plan.files) {
|
|
16757
16864
|
const sourcePath = join7(sourceRoot, fileName);
|
|
16758
16865
|
const targetPath = join7(targetRoot, fileName);
|
|
16759
16866
|
await symlink2(sourcePath, targetPath);
|
|
16867
|
+
symlinkRoots?.add(relativePath ? `${relativePath}/${fileName}` : fileName);
|
|
16760
16868
|
}
|
|
16761
16869
|
for (const [dirName, nestedPlan] of plan.partialDirs) {
|
|
16762
16870
|
const sourcePath = join7(sourceRoot, dirName);
|
|
16763
16871
|
const targetPath = join7(targetRoot, dirName);
|
|
16764
16872
|
await mkdir4(targetPath, { recursive: true });
|
|
16765
|
-
|
|
16873
|
+
const nestedRelativePath = relativePath ? `${relativePath}/${dirName}` : dirName;
|
|
16874
|
+
await executeSymlinkPlan(nestedPlan, sourcePath, targetPath, nestedRelativePath, symlinkRoots);
|
|
16875
|
+
}
|
|
16876
|
+
}
|
|
16877
|
+
function normalizePath3(p) {
|
|
16878
|
+
return p.replace(/\\/g, "/").replace(/\/$/, "");
|
|
16879
|
+
}
|
|
16880
|
+
function getGitDir(projectDir) {
|
|
16881
|
+
const gitPath = join7(projectDir, ".git");
|
|
16882
|
+
try {
|
|
16883
|
+
const stats = statSync(gitPath);
|
|
16884
|
+
if (stats.isDirectory())
|
|
16885
|
+
return gitPath;
|
|
16886
|
+
if (stats.isFile()) {
|
|
16887
|
+
const content2 = readFileSync2(gitPath, "utf-8").trim();
|
|
16888
|
+
const match = content2.match(/^gitdir:\s*(.+)$/);
|
|
16889
|
+
if (match?.[1]) {
|
|
16890
|
+
const gitdir = match[1];
|
|
16891
|
+
return isAbsolute2(gitdir) ? gitdir : join7(projectDir, gitdir);
|
|
16892
|
+
}
|
|
16893
|
+
}
|
|
16894
|
+
} catch {}
|
|
16895
|
+
return null;
|
|
16896
|
+
}
|
|
16897
|
+
function getGlobalGitignore() {
|
|
16898
|
+
const gitconfigPath = join7(homedir2(), ".gitconfig");
|
|
16899
|
+
try {
|
|
16900
|
+
const content2 = readFileSync2(gitconfigPath, "utf-8");
|
|
16901
|
+
const match = content2.match(/excludesfile\s*=\s*(.+)/i);
|
|
16902
|
+
if (match?.[1]) {
|
|
16903
|
+
const p = match[1].trim().replace(/^~/, homedir2());
|
|
16904
|
+
if (existsSync3(p))
|
|
16905
|
+
return p;
|
|
16906
|
+
}
|
|
16907
|
+
} catch {}
|
|
16908
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join7(homedir2(), ".config");
|
|
16909
|
+
const xdgIgnore = join7(xdgConfig, "git", "ignore");
|
|
16910
|
+
return existsSync3(xdgIgnore) ? xdgIgnore : null;
|
|
16911
|
+
}
|
|
16912
|
+
function addScopedRules(ig, content2, subdir) {
|
|
16913
|
+
for (const line of content2.split(/\r?\n/)) {
|
|
16914
|
+
const trimmed = line.trim();
|
|
16915
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
16916
|
+
continue;
|
|
16917
|
+
let pattern = trimmed;
|
|
16918
|
+
let isNegation = false;
|
|
16919
|
+
if (pattern.startsWith("!")) {
|
|
16920
|
+
isNegation = true;
|
|
16921
|
+
pattern = pattern.slice(1);
|
|
16922
|
+
}
|
|
16923
|
+
if (pattern.startsWith("/")) {
|
|
16924
|
+
pattern = pattern.slice(1);
|
|
16925
|
+
}
|
|
16926
|
+
const scoped = subdir ? posix.join(subdir, pattern) : pattern;
|
|
16927
|
+
ig.add(isNegation ? `!${scoped}` : scoped);
|
|
16928
|
+
}
|
|
16929
|
+
}
|
|
16930
|
+
async function loadGitIgnoreStack(projectDir) {
|
|
16931
|
+
const gitDir = getGitDir(projectDir);
|
|
16932
|
+
if (!gitDir)
|
|
16933
|
+
return null;
|
|
16934
|
+
const ig = import_ignore2.default();
|
|
16935
|
+
const globalPath = getGlobalGitignore();
|
|
16936
|
+
if (globalPath) {
|
|
16937
|
+
try {
|
|
16938
|
+
addScopedRules(ig, readFileSync2(globalPath, "utf-8"), "");
|
|
16939
|
+
} catch {}
|
|
16766
16940
|
}
|
|
16941
|
+
const excludePath = join7(gitDir, "info", "exclude");
|
|
16942
|
+
try {
|
|
16943
|
+
if (existsSync3(excludePath)) {
|
|
16944
|
+
addScopedRules(ig, readFileSync2(excludePath, "utf-8"), "");
|
|
16945
|
+
}
|
|
16946
|
+
} catch {}
|
|
16947
|
+
const rootGitignore = join7(projectDir, ".gitignore");
|
|
16948
|
+
try {
|
|
16949
|
+
if (existsSync3(rootGitignore)) {
|
|
16950
|
+
addScopedRules(ig, readFileSync2(rootGitignore, "utf-8"), "");
|
|
16951
|
+
}
|
|
16952
|
+
} catch {}
|
|
16953
|
+
return ig;
|
|
16767
16954
|
}
|
|
16768
16955
|
|
|
16769
16956
|
// src/utils/terminal-title.ts
|
|
@@ -16819,11 +17006,77 @@ function formatTerminalName(cwd, profileName, gitInfo) {
|
|
|
16819
17006
|
}
|
|
16820
17007
|
|
|
16821
17008
|
// src/commands/ghost/opencode.ts
|
|
17009
|
+
var PROFILE_OVERLAY_ALLOWED = [
|
|
17010
|
+
"opencode.jsonc",
|
|
17011
|
+
"opencode.json",
|
|
17012
|
+
"opencode.yaml",
|
|
17013
|
+
"AGENTS.md",
|
|
17014
|
+
"CLAUDE.md",
|
|
17015
|
+
"CONTEXT.md",
|
|
17016
|
+
/^\.opencode(\/|$)/
|
|
17017
|
+
];
|
|
17018
|
+
var defaultFs = {
|
|
17019
|
+
existsSync: existsSync4,
|
|
17020
|
+
statSync: statSync2
|
|
17021
|
+
};
|
|
17022
|
+
function resolveProjectPath(args, cwd, fs = defaultFs) {
|
|
17023
|
+
if (args.length === 0) {
|
|
17024
|
+
return { projectDir: cwd, remainingArgs: [], explicitPath: null };
|
|
17025
|
+
}
|
|
17026
|
+
const firstArg = args[0];
|
|
17027
|
+
if (firstArg === undefined) {
|
|
17028
|
+
return { projectDir: cwd, remainingArgs: [], explicitPath: null };
|
|
17029
|
+
}
|
|
17030
|
+
let pathArg;
|
|
17031
|
+
let remainingArgs;
|
|
17032
|
+
if (firstArg === "--") {
|
|
17033
|
+
const secondArg = args[1];
|
|
17034
|
+
if (secondArg === undefined) {
|
|
17035
|
+
return { projectDir: cwd, remainingArgs: [], explicitPath: null };
|
|
17036
|
+
}
|
|
17037
|
+
pathArg = secondArg;
|
|
17038
|
+
remainingArgs = args.slice(2);
|
|
17039
|
+
} else {
|
|
17040
|
+
const potentialPath = isAbsolutePath(firstArg) ? firstArg : path7.resolve(cwd, firstArg);
|
|
17041
|
+
if (!fs.existsSync(potentialPath)) {
|
|
17042
|
+
return { projectDir: cwd, remainingArgs: args, explicitPath: null };
|
|
17043
|
+
}
|
|
17044
|
+
const stat7 = fs.statSync(potentialPath);
|
|
17045
|
+
if (!stat7.isDirectory()) {
|
|
17046
|
+
return { projectDir: cwd, remainingArgs: args, explicitPath: null };
|
|
17047
|
+
}
|
|
17048
|
+
pathArg = firstArg;
|
|
17049
|
+
remainingArgs = args.slice(1);
|
|
17050
|
+
}
|
|
17051
|
+
const projectDir = isAbsolutePath(pathArg) ? pathArg : path7.resolve(cwd, pathArg);
|
|
17052
|
+
if (!fs.existsSync(projectDir)) {
|
|
17053
|
+
throw new NotFoundError(`Project path does not exist: ${pathArg}`);
|
|
17054
|
+
}
|
|
17055
|
+
const stat6 = fs.statSync(projectDir);
|
|
17056
|
+
if (!stat6.isDirectory()) {
|
|
17057
|
+
throw new ValidationError(`Project path is not a directory: ${pathArg}`);
|
|
17058
|
+
}
|
|
17059
|
+
return {
|
|
17060
|
+
projectDir,
|
|
17061
|
+
remainingArgs,
|
|
17062
|
+
explicitPath: pathArg
|
|
17063
|
+
};
|
|
17064
|
+
}
|
|
17065
|
+
function isAllowedOverlayFile(relativePath) {
|
|
17066
|
+
return PROFILE_OVERLAY_ALLOWED.some((pattern) => typeof pattern === "string" ? relativePath === pattern : pattern.test(relativePath));
|
|
17067
|
+
}
|
|
17068
|
+
function isWithinSymlinkRoot(relativePath, symlinkRoots) {
|
|
17069
|
+
for (const root of symlinkRoots) {
|
|
17070
|
+
if (relativePath === root || relativePath.startsWith(`${root}/`)) {
|
|
17071
|
+
return true;
|
|
17072
|
+
}
|
|
17073
|
+
}
|
|
17074
|
+
return false;
|
|
17075
|
+
}
|
|
16822
17076
|
function registerGhostOpenCodeCommand(parent) {
|
|
16823
|
-
parent.command("opencode").description("Launch OpenCode with ghost mode configuration").option("-p, --profile <name>", "Use specific profile").option("--no-rename", "Disable terminal/tmux window renaming").addOption(sharedOptions.json()).addOption(sharedOptions.quiet()).allowUnknownOption().allowExcessArguments(true).action(async (options2, command) => {
|
|
17077
|
+
parent.command("opencode").description("Launch OpenCode with ghost mode configuration (first arg can be project path)").option("-p, --profile <name>", "Use specific profile").option("--no-rename", "Disable terminal/tmux window renaming").addOption(sharedOptions.json()).addOption(sharedOptions.quiet()).allowUnknownOption().allowExcessArguments(true).action(async (options2, command) => {
|
|
16824
17078
|
try {
|
|
16825
|
-
|
|
16826
|
-
await runGhostOpenCode(args, options2);
|
|
17079
|
+
await runGhostOpenCode(command.args, options2);
|
|
16827
17080
|
} catch (error) {
|
|
16828
17081
|
handleError(error, { json: options2.json });
|
|
16829
17082
|
}
|
|
@@ -16844,17 +17097,21 @@ async function runGhostOpenCode(args, options2) {
|
|
|
16844
17097
|
if (!hasOpencodeConfig && !options2.quiet) {
|
|
16845
17098
|
logger.warn(`No opencode.jsonc found at ${profileOpencodePath}. Create one to customize OpenCode settings.`);
|
|
16846
17099
|
}
|
|
16847
|
-
const
|
|
16848
|
-
|
|
17100
|
+
const { projectDir, remainingArgs, explicitPath } = resolveProjectPath(args, process.cwd());
|
|
17101
|
+
if (explicitPath && !options2.quiet) {
|
|
17102
|
+
logger.info(`Using project directory: ${projectDir}`);
|
|
17103
|
+
}
|
|
17104
|
+
const gitContext = await detectGitRepo(projectDir);
|
|
16849
17105
|
const ghostConfig = profile.ghost;
|
|
16850
|
-
const tempDir = await createSymlinkFarm(
|
|
17106
|
+
const { tempDir, symlinkRoots } = await createSymlinkFarm(projectDir, {
|
|
16851
17107
|
includePatterns: ghostConfig.include,
|
|
16852
|
-
excludePatterns: ghostConfig.exclude
|
|
17108
|
+
excludePatterns: ghostConfig.exclude,
|
|
17109
|
+
maxFiles: ghostConfig.maxFiles
|
|
16853
17110
|
});
|
|
16854
|
-
const overlayFiles = await injectProfileOverlay(tempDir, profileDir, ghostConfig.include);
|
|
17111
|
+
const overlayFiles = await injectProfileOverlay(tempDir, profileDir, ghostConfig.include, symlinkRoots);
|
|
16855
17112
|
let cleanupDone = false;
|
|
16856
17113
|
let fileSync;
|
|
16857
|
-
fileSync = createFileSync(tempDir,
|
|
17114
|
+
fileSync = createFileSync(tempDir, projectDir, { overlayFiles });
|
|
16858
17115
|
const performCleanup = async () => {
|
|
16859
17116
|
if (cleanupDone)
|
|
16860
17117
|
return;
|
|
@@ -16885,11 +17142,11 @@ async function runGhostOpenCode(args, options2) {
|
|
|
16885
17142
|
process.on("SIGTERM", sigtermHandler);
|
|
16886
17143
|
if (shouldRename) {
|
|
16887
17144
|
saveTerminalTitle();
|
|
16888
|
-
const gitInfo = await getGitInfo(
|
|
16889
|
-
setTerminalName(formatTerminalName(
|
|
17145
|
+
const gitInfo = await getGitInfo(projectDir);
|
|
17146
|
+
setTerminalName(formatTerminalName(projectDir, profileName, gitInfo));
|
|
16890
17147
|
}
|
|
16891
17148
|
proc = Bun.spawn({
|
|
16892
|
-
cmd: ["opencode", ...
|
|
17149
|
+
cmd: ["opencode", ...remainingArgs],
|
|
16893
17150
|
cwd: tempDir,
|
|
16894
17151
|
env: {
|
|
16895
17152
|
...process.env,
|
|
@@ -16942,7 +17199,7 @@ function userExplicitlyIncluded(relativePath, compiledPatterns) {
|
|
|
16942
17199
|
return false;
|
|
16943
17200
|
return compiledPatterns.some((glob) => glob.match(relativePath));
|
|
16944
17201
|
}
|
|
16945
|
-
async function injectProfileOverlay(tempDir, profileDir, includePatterns) {
|
|
17202
|
+
async function injectProfileOverlay(tempDir, profileDir, includePatterns, symlinkRoots) {
|
|
16946
17203
|
const entries = await readdir5(profileDir, { withFileTypes: true, recursive: true });
|
|
16947
17204
|
const injectedFiles = new Set;
|
|
16948
17205
|
const compiledIncludePatterns = includePatterns.map((p) => new Glob3(p));
|
|
@@ -16952,11 +17209,17 @@ async function injectProfileOverlay(tempDir, profileDir, includePatterns) {
|
|
|
16952
17209
|
continue;
|
|
16953
17210
|
if (relativePath === ".gitignore")
|
|
16954
17211
|
continue;
|
|
17212
|
+
if (!isAllowedOverlayFile(relativePath))
|
|
17213
|
+
continue;
|
|
17214
|
+
if (isWithinSymlinkRoot(relativePath, symlinkRoots))
|
|
17215
|
+
continue;
|
|
16955
17216
|
if (userExplicitlyIncluded(relativePath, compiledIncludePatterns))
|
|
16956
17217
|
continue;
|
|
16957
17218
|
if (entry.isDirectory())
|
|
16958
17219
|
continue;
|
|
16959
17220
|
const destPath = path7.join(tempDir, relativePath);
|
|
17221
|
+
if (existsSync4(destPath))
|
|
17222
|
+
continue;
|
|
16960
17223
|
await mkdir5(path7.dirname(destPath), { recursive: true });
|
|
16961
17224
|
await copyFile(path7.join(entry.parentPath, entry.name), destPath);
|
|
16962
17225
|
injectedFiles.add(normalizePath2(relativePath));
|
|
@@ -17488,850 +17751,864 @@ function registerGhostSearchCommand(parent) {
|
|
|
17488
17751
|
});
|
|
17489
17752
|
}
|
|
17490
17753
|
|
|
17491
|
-
// src/commands/
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
registerGhostSearchCommand(ghost);
|
|
17499
|
-
registerGhostOpenCodeCommand(ghost);
|
|
17500
|
-
registerGhostProfileCommand(ghost);
|
|
17501
|
-
}
|
|
17502
|
-
|
|
17503
|
-
// src/commands/init.ts
|
|
17504
|
-
import { existsSync as existsSync3 } from "fs";
|
|
17505
|
-
import { cp, mkdir as mkdir6, readdir as readdir6, readFile, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
17506
|
-
import { join as join8 } from "path";
|
|
17507
|
-
var TEMPLATE_REPO = "kdcokenny/ocx";
|
|
17508
|
-
var TEMPLATE_PATH = "examples/registry-starter";
|
|
17509
|
-
function registerInitCommand(program2) {
|
|
17510
|
-
program2.command("init [directory]").description("Initialize OCX configuration in your project").option("--cwd <path>", "Working directory", process.cwd()).option("-q, --quiet", "Suppress output").option("-v, --verbose", "Verbose output").option("--json", "Output as JSON").option("--registry", "Scaffold a new OCX registry project").option("--namespace <name>", "Registry namespace (e.g., my-org)").option("--author <name>", "Author name for the registry").option("--canary", "Use canary (main branch) instead of latest release").option("--local <path>", "Use local template directory instead of fetching").option("-f, --force", "Overwrite existing files (registry mode only)").action(async (directory, options2) => {
|
|
17754
|
+
// src/commands/update.ts
|
|
17755
|
+
import { createHash as createHash2 } from "crypto";
|
|
17756
|
+
import { existsSync as existsSync5 } from "fs";
|
|
17757
|
+
import { mkdir as mkdir6, writeFile as writeFile2 } from "fs/promises";
|
|
17758
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
17759
|
+
function registerUpdateCommand(program2) {
|
|
17760
|
+
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) => {
|
|
17511
17761
|
try {
|
|
17512
|
-
|
|
17513
|
-
await runInitRegistry(directory, options2);
|
|
17514
|
-
} else {
|
|
17515
|
-
await runInit(options2);
|
|
17516
|
-
}
|
|
17762
|
+
await runUpdate(components, options2);
|
|
17517
17763
|
} catch (error) {
|
|
17518
17764
|
handleError(error, { json: options2.json });
|
|
17519
17765
|
}
|
|
17520
17766
|
});
|
|
17521
17767
|
}
|
|
17522
|
-
async function
|
|
17768
|
+
async function runUpdate(componentNames, options2) {
|
|
17523
17769
|
const cwd = options2.cwd ?? process.cwd();
|
|
17524
|
-
const
|
|
17525
|
-
|
|
17526
|
-
|
|
17770
|
+
const provider = await LocalConfigProvider.create(cwd);
|
|
17771
|
+
await runUpdateCore(componentNames, options2, provider);
|
|
17772
|
+
}
|
|
17773
|
+
async function runUpdateCore(componentNames, options2, provider) {
|
|
17774
|
+
const lockPath = join8(provider.cwd, "ocx.lock");
|
|
17775
|
+
const registries = provider.getRegistries();
|
|
17776
|
+
const lock = await readOcxLock(provider.cwd);
|
|
17777
|
+
if (!lock || Object.keys(lock.installed).length === 0) {
|
|
17778
|
+
throw new ValidationError("Nothing installed yet. Run 'ocx add <component>' first.");
|
|
17779
|
+
}
|
|
17780
|
+
const hasComponents = componentNames.length > 0;
|
|
17781
|
+
const hasAll = options2.all === true;
|
|
17782
|
+
const hasRegistry = options2.registry !== undefined;
|
|
17783
|
+
if (!hasComponents && !hasAll && !hasRegistry) {
|
|
17784
|
+
throw new ValidationError(`Specify components, use --all, or use --registry <name>.
|
|
17527
17785
|
|
|
17528
|
-
` + `
|
|
17529
|
-
` + `
|
|
17786
|
+
` + `Examples:
|
|
17787
|
+
` + ` ocx update kdco/agents # Update specific component
|
|
17788
|
+
` + ` ocx update --all # Update all installed components
|
|
17789
|
+
` + " ocx update --registry kdco # Update all from a registry");
|
|
17530
17790
|
}
|
|
17531
|
-
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
const rawConfig = {
|
|
17535
|
-
$schema: OCX_SCHEMA_URL,
|
|
17536
|
-
registries: {}
|
|
17537
|
-
};
|
|
17538
|
-
const config = ocxConfigSchema.parse(rawConfig);
|
|
17539
|
-
const content2 = JSON.stringify(config, null, 2);
|
|
17540
|
-
await writeFile2(configPath, content2, "utf-8");
|
|
17541
|
-
const opencodeResult = await ensureOpencodeConfig(cwd);
|
|
17542
|
-
spin?.succeed("Initialized OCX configuration");
|
|
17543
|
-
if (options2.json) {
|
|
17544
|
-
console.log(JSON.stringify({
|
|
17545
|
-
success: true,
|
|
17546
|
-
path: configPath,
|
|
17547
|
-
opencodePath: opencodeResult.path,
|
|
17548
|
-
opencodeCreated: opencodeResult.created
|
|
17549
|
-
}));
|
|
17550
|
-
} else if (!options2.quiet) {
|
|
17551
|
-
logger.info(`Created ${configPath}`);
|
|
17552
|
-
if (opencodeResult.created) {
|
|
17553
|
-
logger.info(`Created ${opencodeResult.path}`);
|
|
17554
|
-
}
|
|
17555
|
-
logger.info("");
|
|
17556
|
-
logger.info("Next steps:");
|
|
17557
|
-
logger.info(" 1. Add a registry: ocx registry add <url>");
|
|
17558
|
-
logger.info(" 2. Install components: ocx add <component>");
|
|
17559
|
-
}
|
|
17560
|
-
} catch (error) {
|
|
17561
|
-
spin?.fail("Failed to initialize");
|
|
17562
|
-
throw error;
|
|
17791
|
+
if (hasAll && hasComponents) {
|
|
17792
|
+
throw new ValidationError(`Cannot specify components with --all.
|
|
17793
|
+
` + "Use either 'ocx update --all' or 'ocx update <components>'.");
|
|
17563
17794
|
}
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
|
|
17567
|
-
const namespace = options2.namespace ?? "my-registry";
|
|
17568
|
-
const author = options2.author ?? "Your Name";
|
|
17569
|
-
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(namespace)) {
|
|
17570
|
-
throw new ValidationError("Invalid namespace format: must start with letter/number, use hyphens only between segments (e.g., 'my-registry')");
|
|
17795
|
+
if (hasRegistry && hasComponents) {
|
|
17796
|
+
throw new ValidationError(`Cannot specify components with --registry.
|
|
17797
|
+
` + "Use either 'ocx update --registry <name>' or 'ocx update <components>'.");
|
|
17571
17798
|
}
|
|
17572
|
-
|
|
17573
|
-
|
|
17574
|
-
|
|
17575
|
-
throw new ConflictError("Directory is not empty. Use --force to overwrite existing files.");
|
|
17799
|
+
if (hasAll && hasRegistry) {
|
|
17800
|
+
throw new ValidationError(`Cannot use --all with --registry.
|
|
17801
|
+
` + "Use either 'ocx update --all' or 'ocx update --registry <name>'.");
|
|
17576
17802
|
}
|
|
17577
|
-
const
|
|
17578
|
-
|
|
17579
|
-
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
if (options2.local) {
|
|
17583
|
-
await mkdir6(cwd, { recursive: true });
|
|
17584
|
-
await copyDir(options2.local, cwd);
|
|
17585
|
-
} else {
|
|
17586
|
-
const version = options2.canary ? "main" : await getLatestVersion();
|
|
17587
|
-
await fetchAndExtractTemplate(cwd, version, options2.verbose);
|
|
17588
|
-
}
|
|
17589
|
-
if (spin)
|
|
17590
|
-
spin.text = "Configuring project...";
|
|
17591
|
-
await replacePlaceholders(cwd, { namespace, author });
|
|
17592
|
-
spin?.succeed(`Created registry: ${namespace}`);
|
|
17593
|
-
if (options2.json) {
|
|
17594
|
-
console.log(JSON.stringify({ success: true, namespace, path: cwd }));
|
|
17595
|
-
} else if (!options2.quiet) {
|
|
17596
|
-
logger.info("");
|
|
17597
|
-
logger.info("Next steps:");
|
|
17598
|
-
logger.info(" 1. bun install");
|
|
17599
|
-
logger.info(" 2. Edit registry.jsonc with your components");
|
|
17600
|
-
logger.info(" 3. bun run build");
|
|
17601
|
-
logger.info("");
|
|
17602
|
-
logger.info("Deploy to:");
|
|
17603
|
-
logger.info(" Cloudflare: bunx wrangler deploy");
|
|
17604
|
-
logger.info(" Vercel: vercel");
|
|
17605
|
-
logger.info(" Netlify: netlify deploy");
|
|
17803
|
+
const parsedComponents = componentNames.map(parseComponentSpec);
|
|
17804
|
+
for (const spec of parsedComponents) {
|
|
17805
|
+
if (spec.version !== undefined && spec.version === "") {
|
|
17806
|
+
throw new ValidationError(`Invalid version specifier in '${spec.component}@'.` + `
|
|
17807
|
+
Version cannot be empty. Use 'kdco/agents@1.2.0' or omit the version for latest.`);
|
|
17606
17808
|
}
|
|
17607
|
-
} catch (error) {
|
|
17608
|
-
spin?.fail("Failed to scaffold registry");
|
|
17609
|
-
throw error;
|
|
17610
|
-
}
|
|
17611
|
-
}
|
|
17612
|
-
async function copyDir(src, dest) {
|
|
17613
|
-
await cp(src, dest, { recursive: true });
|
|
17614
|
-
}
|
|
17615
|
-
async function getLatestVersion() {
|
|
17616
|
-
const pkgPath = new URL("../../package.json", import.meta.url);
|
|
17617
|
-
const pkgContent = await readFile(pkgPath);
|
|
17618
|
-
const pkg = JSON.parse(pkgContent.toString());
|
|
17619
|
-
return `v${pkg.version}`;
|
|
17620
|
-
}
|
|
17621
|
-
async function fetchAndExtractTemplate(destDir, version, verbose) {
|
|
17622
|
-
const ref = version === "main" ? "heads/main" : `tags/${version}`;
|
|
17623
|
-
const tarballUrl = `https://github.com/${TEMPLATE_REPO}/archive/refs/${ref}.tar.gz`;
|
|
17624
|
-
if (verbose) {
|
|
17625
|
-
logger.info(`Fetching ${tarballUrl}`);
|
|
17626
17809
|
}
|
|
17627
|
-
const
|
|
17628
|
-
if (
|
|
17629
|
-
|
|
17810
|
+
const componentsToUpdate = resolveComponentsToUpdate(lock, parsedComponents, options2);
|
|
17811
|
+
if (componentsToUpdate.length === 0) {
|
|
17812
|
+
if (hasRegistry) {
|
|
17813
|
+
throw new NotFoundError(`No installed components from registry '${options2.registry}'.`);
|
|
17814
|
+
}
|
|
17815
|
+
throw new NotFoundError("No matching components found to update.");
|
|
17630
17816
|
}
|
|
17631
|
-
const
|
|
17632
|
-
|
|
17817
|
+
const spin = options2.quiet ? null : createSpinner({ text: "Checking for updates..." });
|
|
17818
|
+
spin?.start();
|
|
17819
|
+
const results = [];
|
|
17820
|
+
const updates = [];
|
|
17633
17821
|
try {
|
|
17634
|
-
const
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
|
|
17649
|
-
|
|
17822
|
+
for (const spec of componentsToUpdate) {
|
|
17823
|
+
const qualifiedName = spec.component;
|
|
17824
|
+
const lockEntry = lock.installed[qualifiedName];
|
|
17825
|
+
if (!lockEntry) {
|
|
17826
|
+
throw new NotFoundError(`Component '${qualifiedName}' not found in lock file.`);
|
|
17827
|
+
}
|
|
17828
|
+
const { namespace, component: componentName } = parseQualifiedComponent(qualifiedName);
|
|
17829
|
+
const regConfig = registries[namespace];
|
|
17830
|
+
if (!regConfig) {
|
|
17831
|
+
throw new ConfigError(`Registry '${namespace}' not configured. Component '${qualifiedName}' cannot be updated.`);
|
|
17832
|
+
}
|
|
17833
|
+
const fetchResult = await fetchComponentVersion(regConfig.url, componentName, spec.version);
|
|
17834
|
+
const manifest = fetchResult.manifest;
|
|
17835
|
+
const version = fetchResult.version;
|
|
17836
|
+
const normalizedManifest = normalizeComponentManifest(manifest);
|
|
17837
|
+
const files = [];
|
|
17838
|
+
for (const file of normalizedManifest.files) {
|
|
17839
|
+
const content2 = await fetchFileContent(regConfig.url, componentName, file.path);
|
|
17840
|
+
files.push({ path: file.path, content: Buffer.from(content2) });
|
|
17841
|
+
}
|
|
17842
|
+
const newHash = await hashBundle2(files);
|
|
17843
|
+
if (newHash === lockEntry.hash) {
|
|
17844
|
+
results.push({
|
|
17845
|
+
qualifiedName,
|
|
17846
|
+
oldVersion: lockEntry.version,
|
|
17847
|
+
newVersion: version,
|
|
17848
|
+
status: "up-to-date"
|
|
17849
|
+
});
|
|
17850
|
+
} else if (options2.dryRun) {
|
|
17851
|
+
results.push({
|
|
17852
|
+
qualifiedName,
|
|
17853
|
+
oldVersion: lockEntry.version,
|
|
17854
|
+
newVersion: version,
|
|
17855
|
+
status: "would-update"
|
|
17856
|
+
});
|
|
17857
|
+
} else {
|
|
17858
|
+
results.push({
|
|
17859
|
+
qualifiedName,
|
|
17860
|
+
oldVersion: lockEntry.version,
|
|
17861
|
+
newVersion: version,
|
|
17862
|
+
status: "updated"
|
|
17863
|
+
});
|
|
17864
|
+
updates.push({
|
|
17865
|
+
qualifiedName,
|
|
17866
|
+
component: normalizedManifest,
|
|
17867
|
+
files,
|
|
17868
|
+
newHash,
|
|
17869
|
+
newVersion: version,
|
|
17870
|
+
baseUrl: regConfig.url
|
|
17871
|
+
});
|
|
17872
|
+
}
|
|
17650
17873
|
}
|
|
17651
|
-
|
|
17652
|
-
|
|
17653
|
-
|
|
17654
|
-
|
|
17655
|
-
}
|
|
17656
|
-
}
|
|
17657
|
-
async function replacePlaceholders(dir, values) {
|
|
17658
|
-
const filesToProcess = [
|
|
17659
|
-
"registry.jsonc",
|
|
17660
|
-
"package.json",
|
|
17661
|
-
"wrangler.jsonc",
|
|
17662
|
-
"README.md",
|
|
17663
|
-
"AGENTS.md"
|
|
17664
|
-
];
|
|
17665
|
-
for (const file of filesToProcess) {
|
|
17666
|
-
const filePath = join8(dir, file);
|
|
17667
|
-
if (!existsSync3(filePath))
|
|
17668
|
-
continue;
|
|
17669
|
-
let content2 = await readFile(filePath).then((b) => b.toString());
|
|
17670
|
-
content2 = content2.replace(/my-registry/g, values.namespace);
|
|
17671
|
-
content2 = content2.replace(/Your Name/g, values.author);
|
|
17672
|
-
await writeFile2(filePath, content2);
|
|
17673
|
-
}
|
|
17674
|
-
}
|
|
17675
|
-
|
|
17676
|
-
// src/self-update/version-provider.ts
|
|
17677
|
-
class BuildTimeVersionProvider {
|
|
17678
|
-
version = "1.3.0";
|
|
17679
|
-
}
|
|
17680
|
-
var defaultVersionProvider = new BuildTimeVersionProvider;
|
|
17681
|
-
|
|
17682
|
-
// src/self-update/check.ts
|
|
17683
|
-
var VERSION_CHECK_TIMEOUT_MS = 200;
|
|
17684
|
-
var PACKAGE_NAME = "ocx";
|
|
17685
|
-
function parseVersion2(v) {
|
|
17686
|
-
const [main2 = ""] = v.split("-");
|
|
17687
|
-
const parts = main2.split(".");
|
|
17688
|
-
const major = parseInt(parts[0] ?? "", 10);
|
|
17689
|
-
const minor = parseInt(parts[1] ?? "", 10);
|
|
17690
|
-
const patch = parseInt(parts[2] ?? "", 10);
|
|
17691
|
-
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) {
|
|
17692
|
-
return null;
|
|
17693
|
-
}
|
|
17694
|
-
return { major, minor, patch };
|
|
17695
|
-
}
|
|
17696
|
-
function compareSemver2(a, b) {
|
|
17697
|
-
const vA = parseVersion2(a);
|
|
17698
|
-
const vB = parseVersion2(b);
|
|
17699
|
-
if (!vA || !vB) {
|
|
17700
|
-
return null;
|
|
17701
|
-
}
|
|
17702
|
-
if (vA.major !== vB.major)
|
|
17703
|
-
return vA.major - vB.major;
|
|
17704
|
-
if (vA.minor !== vB.minor)
|
|
17705
|
-
return vA.minor - vB.minor;
|
|
17706
|
-
return vA.patch - vB.patch;
|
|
17707
|
-
}
|
|
17708
|
-
async function checkForUpdate(versionProvider) {
|
|
17709
|
-
const provider = versionProvider ?? defaultVersionProvider;
|
|
17710
|
-
const current = provider.version || "0.0.0-dev";
|
|
17711
|
-
if (current === "0.0.0-dev") {
|
|
17712
|
-
return null;
|
|
17713
|
-
}
|
|
17714
|
-
try {
|
|
17715
|
-
const result = await fetchPackageVersion(PACKAGE_NAME, undefined, AbortSignal.timeout(VERSION_CHECK_TIMEOUT_MS));
|
|
17716
|
-
const latest = result.version;
|
|
17717
|
-
const comparison = compareSemver2(latest, current);
|
|
17718
|
-
if (comparison === null) {
|
|
17719
|
-
return null;
|
|
17874
|
+
spin?.succeed(`Checked ${componentsToUpdate.length} component(s)`);
|
|
17875
|
+
if (options2.dryRun) {
|
|
17876
|
+
outputDryRun(results, options2);
|
|
17877
|
+
return;
|
|
17720
17878
|
}
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
|
|
17725
|
-
|
|
17726
|
-
|
|
17727
|
-
|
|
17879
|
+
if (updates.length === 0) {
|
|
17880
|
+
if (!options2.quiet && !options2.json) {
|
|
17881
|
+
logger.info("");
|
|
17882
|
+
logger.success("All components are up to date.");
|
|
17883
|
+
}
|
|
17884
|
+
if (options2.json) {
|
|
17885
|
+
console.log(JSON.stringify({ success: true, updated: [], upToDate: results }, null, 2));
|
|
17886
|
+
}
|
|
17887
|
+
return;
|
|
17888
|
+
}
|
|
17889
|
+
const installSpin = options2.quiet ? null : createSpinner({ text: "Updating components..." });
|
|
17890
|
+
installSpin?.start();
|
|
17891
|
+
for (const update of updates) {
|
|
17892
|
+
for (const file of update.files) {
|
|
17893
|
+
const fileObj = update.component.files.find((f) => f.path === file.path);
|
|
17894
|
+
if (!fileObj)
|
|
17895
|
+
continue;
|
|
17896
|
+
const targetPath = join8(provider.cwd, fileObj.target);
|
|
17897
|
+
const targetDir = dirname7(targetPath);
|
|
17898
|
+
if (!existsSync5(targetDir)) {
|
|
17899
|
+
await mkdir6(targetDir, { recursive: true });
|
|
17900
|
+
}
|
|
17901
|
+
await writeFile2(targetPath, file.content);
|
|
17902
|
+
if (options2.verbose) {
|
|
17903
|
+
logger.info(` \u2713 Updated ${fileObj.target}`);
|
|
17904
|
+
}
|
|
17905
|
+
}
|
|
17906
|
+
const existingEntry = lock.installed[update.qualifiedName];
|
|
17907
|
+
if (!existingEntry) {
|
|
17908
|
+
throw new NotFoundError(`Component '${update.qualifiedName}' not found in lock file.`);
|
|
17909
|
+
}
|
|
17910
|
+
lock.installed[update.qualifiedName] = {
|
|
17911
|
+
registry: existingEntry.registry,
|
|
17912
|
+
version: update.newVersion,
|
|
17913
|
+
hash: update.newHash,
|
|
17914
|
+
files: existingEntry.files,
|
|
17915
|
+
installedAt: existingEntry.installedAt,
|
|
17916
|
+
updatedAt: new Date().toISOString()
|
|
17917
|
+
};
|
|
17918
|
+
}
|
|
17919
|
+
installSpin?.succeed(`Updated ${updates.length} component(s)`);
|
|
17920
|
+
await writeFile2(lockPath, JSON.stringify(lock, null, 2), "utf-8");
|
|
17921
|
+
if (options2.json) {
|
|
17922
|
+
console.log(JSON.stringify({
|
|
17923
|
+
success: true,
|
|
17924
|
+
updated: results.filter((r2) => r2.status === "updated"),
|
|
17925
|
+
upToDate: results.filter((r2) => r2.status === "up-to-date")
|
|
17926
|
+
}, null, 2));
|
|
17927
|
+
} else if (!options2.quiet) {
|
|
17928
|
+
logger.info("");
|
|
17929
|
+
for (const result of results) {
|
|
17930
|
+
if (result.status === "updated") {
|
|
17931
|
+
logger.info(` \u2713 ${result.qualifiedName} (${result.oldVersion} \u2192 ${result.newVersion})`);
|
|
17932
|
+
} else if (result.status === "up-to-date" && options2.verbose) {
|
|
17933
|
+
logger.info(` \u25CB ${result.qualifiedName} (already up to date)`);
|
|
17934
|
+
}
|
|
17935
|
+
}
|
|
17936
|
+
logger.info("");
|
|
17937
|
+
logger.success(`Done! Updated ${updates.length} component(s).`);
|
|
17938
|
+
}
|
|
17939
|
+
} catch (error) {
|
|
17940
|
+
spin?.fail("Failed to check for updates");
|
|
17941
|
+
throw error;
|
|
17728
17942
|
}
|
|
17729
17943
|
}
|
|
17730
|
-
|
|
17731
|
-
|
|
17732
|
-
|
|
17733
|
-
|
|
17734
|
-
const method = VALID_METHODS.find((m) => m === input);
|
|
17735
|
-
if (!method) {
|
|
17736
|
-
throw new SelfUpdateError(`Invalid install method: "${input}"
|
|
17737
|
-
Valid methods: ${VALID_METHODS.join(", ")}`);
|
|
17944
|
+
function parseComponentSpec(spec) {
|
|
17945
|
+
const atIndex = spec.lastIndexOf("@");
|
|
17946
|
+
if (atIndex <= 0) {
|
|
17947
|
+
return { component: spec };
|
|
17738
17948
|
}
|
|
17739
|
-
return
|
|
17949
|
+
return {
|
|
17950
|
+
component: spec.slice(0, atIndex),
|
|
17951
|
+
version: spec.slice(atIndex + 1)
|
|
17952
|
+
};
|
|
17740
17953
|
}
|
|
17741
|
-
|
|
17742
|
-
|
|
17743
|
-
|
|
17744
|
-
|
|
17745
|
-
var isBunGlobalInstall = (path8) => path8.includes("/.bun/bin") || path8.includes("/.bun/install/global");
|
|
17746
|
-
var isNpmGlobalInstall = (path8) => path8.includes("/.npm/") || path8.includes("/node_modules/");
|
|
17747
|
-
function detectInstallMethod() {
|
|
17748
|
-
if (isCompiledBinary()) {
|
|
17749
|
-
return "curl";
|
|
17954
|
+
function resolveComponentsToUpdate(lock, parsedComponents, options2) {
|
|
17955
|
+
const installedComponents = Object.keys(lock.installed);
|
|
17956
|
+
if (options2.all) {
|
|
17957
|
+
return installedComponents.map((c) => ({ component: c }));
|
|
17750
17958
|
}
|
|
17751
|
-
|
|
17752
|
-
|
|
17753
|
-
|
|
17754
|
-
|
|
17755
|
-
|
|
17756
|
-
if (isPnpmGlobalInstall(scriptPath))
|
|
17757
|
-
return "pnpm";
|
|
17758
|
-
if (isBunGlobalInstall(scriptPath))
|
|
17759
|
-
return "bun";
|
|
17760
|
-
if (isNpmGlobalInstall(scriptPath))
|
|
17761
|
-
return "npm";
|
|
17762
|
-
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
17763
|
-
if (userAgent.includes("yarn"))
|
|
17764
|
-
return "yarn";
|
|
17765
|
-
if (userAgent.includes("pnpm"))
|
|
17766
|
-
return "pnpm";
|
|
17767
|
-
if (userAgent.includes("bun"))
|
|
17768
|
-
return "bun";
|
|
17769
|
-
if (userAgent.includes("npm"))
|
|
17770
|
-
return "npm";
|
|
17771
|
-
return "unknown";
|
|
17772
|
-
}
|
|
17773
|
-
function getExecutablePath() {
|
|
17774
|
-
if (typeof Bun !== "undefined" && Bun.main.startsWith("/$bunfs/")) {
|
|
17775
|
-
return process.execPath;
|
|
17959
|
+
if (options2.registry) {
|
|
17960
|
+
return installedComponents.filter((name) => {
|
|
17961
|
+
const entry = lock.installed[name];
|
|
17962
|
+
return entry?.registry === options2.registry;
|
|
17963
|
+
}).map((c) => ({ component: c }));
|
|
17776
17964
|
}
|
|
17777
|
-
|
|
17778
|
-
|
|
17965
|
+
const result = [];
|
|
17966
|
+
for (const spec of parsedComponents) {
|
|
17967
|
+
const name = spec.component;
|
|
17968
|
+
if (!name.includes("/")) {
|
|
17969
|
+
const suggestions = installedComponents.filter((installed) => installed.endsWith(`/${name}`));
|
|
17970
|
+
if (suggestions.length === 1) {
|
|
17971
|
+
throw new ValidationError(`Ambiguous component '${name}'. Did you mean '${suggestions[0]}'?`);
|
|
17972
|
+
}
|
|
17973
|
+
if (suggestions.length > 1) {
|
|
17974
|
+
throw new ValidationError(`Ambiguous component '${name}'. Found in multiple registries:
|
|
17975
|
+
` + suggestions.map((s) => ` - ${s}`).join(`
|
|
17976
|
+
`) + `
|
|
17779
17977
|
|
|
17780
|
-
|
|
17781
|
-
import { chmodSync, existsSync as existsSync4, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
17782
|
-
var GITHUB_REPO2 = "kdcokenny/ocx";
|
|
17783
|
-
var DEFAULT_DOWNLOAD_BASE_URL = `https://github.com/${GITHUB_REPO2}/releases/download`;
|
|
17784
|
-
var PLATFORM_MAP = {
|
|
17785
|
-
"arm64-darwin": "ocx-darwin-arm64",
|
|
17786
|
-
"x64-darwin": "ocx-darwin-x64",
|
|
17787
|
-
"arm64-linux": "ocx-linux-arm64",
|
|
17788
|
-
"x64-linux": "ocx-linux-x64",
|
|
17789
|
-
"x64-win32": "ocx-windows-x64.exe"
|
|
17790
|
-
};
|
|
17791
|
-
function getDownloadBaseUrl() {
|
|
17792
|
-
const envUrl = process.env.OCX_DOWNLOAD_URL;
|
|
17793
|
-
if (envUrl) {
|
|
17794
|
-
return envUrl.replace(/\/+$/, "");
|
|
17795
|
-
}
|
|
17796
|
-
return DEFAULT_DOWNLOAD_BASE_URL;
|
|
17797
|
-
}
|
|
17798
|
-
function getDownloadUrl(version) {
|
|
17799
|
-
const platform = `${process.arch}-${process.platform}`;
|
|
17800
|
-
const target = PLATFORM_MAP[platform];
|
|
17801
|
-
if (!target) {
|
|
17802
|
-
const supported = Object.keys(PLATFORM_MAP).join(", ");
|
|
17803
|
-
throw new SelfUpdateError(`Unsupported platform: ${platform}
|
|
17804
|
-
` + `Supported platforms: ${supported}`);
|
|
17805
|
-
}
|
|
17806
|
-
const baseUrl = getDownloadBaseUrl();
|
|
17807
|
-
return `${baseUrl}/v${version}/${target}`;
|
|
17808
|
-
}
|
|
17809
|
-
async function downloadWithProgress(url, dest) {
|
|
17810
|
-
const spin = createSpinner({ text: "Downloading update..." });
|
|
17811
|
-
spin.start();
|
|
17812
|
-
let response;
|
|
17813
|
-
try {
|
|
17814
|
-
response = await fetch(url, { redirect: "follow" });
|
|
17815
|
-
} catch (error) {
|
|
17816
|
-
spin.fail("Download failed");
|
|
17817
|
-
throw new SelfUpdateError(`Network error: ${error instanceof Error ? error.message : String(error)}`);
|
|
17818
|
-
}
|
|
17819
|
-
if (!response.ok) {
|
|
17820
|
-
spin.fail("Download failed");
|
|
17821
|
-
throw new SelfUpdateError(`Failed to download: HTTP ${response.status} ${response.statusText}`);
|
|
17822
|
-
}
|
|
17823
|
-
if (!response.body) {
|
|
17824
|
-
spin.fail("Download failed");
|
|
17825
|
-
throw new SelfUpdateError("Download failed: Empty response body");
|
|
17826
|
-
}
|
|
17827
|
-
const reader = response.body.getReader();
|
|
17828
|
-
const writer = Bun.file(dest).writer();
|
|
17829
|
-
const total = Number(response.headers.get("content-length") || 0);
|
|
17830
|
-
let received = 0;
|
|
17831
|
-
try {
|
|
17832
|
-
while (true) {
|
|
17833
|
-
const { done, value } = await reader.read();
|
|
17834
|
-
if (done)
|
|
17835
|
-
break;
|
|
17836
|
-
await writer.write(value);
|
|
17837
|
-
received += value.length;
|
|
17838
|
-
if (total > 0) {
|
|
17839
|
-
const pct = Math.round(received / total * 100);
|
|
17840
|
-
spin.text = `Downloading... ${pct}%`;
|
|
17978
|
+
Please use a fully qualified name (registry/component).`);
|
|
17841
17979
|
}
|
|
17980
|
+
throw new ValidationError(`Component '${name}' must include a registry prefix (e.g., 'kdco/${name}').`);
|
|
17842
17981
|
}
|
|
17843
|
-
|
|
17844
|
-
|
|
17845
|
-
|
|
17846
|
-
spin.fail("Download failed");
|
|
17847
|
-
await writer.end();
|
|
17848
|
-
throw new SelfUpdateError(`Download interrupted: ${error instanceof Error ? error.message : String(error)}`);
|
|
17849
|
-
}
|
|
17850
|
-
}
|
|
17851
|
-
async function downloadToTemp(version) {
|
|
17852
|
-
const execPath = getExecutablePath();
|
|
17853
|
-
const tempPath = `${execPath}.new.${Date.now()}`;
|
|
17854
|
-
const url = getDownloadUrl(version);
|
|
17855
|
-
await downloadWithProgress(url, tempPath);
|
|
17856
|
-
try {
|
|
17857
|
-
chmodSync(tempPath, 493);
|
|
17858
|
-
} catch (error) {
|
|
17859
|
-
if (existsSync4(tempPath)) {
|
|
17860
|
-
unlinkSync2(tempPath);
|
|
17982
|
+
if (!lock.installed[name]) {
|
|
17983
|
+
throw new NotFoundError(`Component '${name}' is not installed.
|
|
17984
|
+
Run 'ocx add ${name}' to install it first.`);
|
|
17861
17985
|
}
|
|
17862
|
-
|
|
17986
|
+
result.push(spec);
|
|
17863
17987
|
}
|
|
17864
|
-
return
|
|
17988
|
+
return result;
|
|
17865
17989
|
}
|
|
17866
|
-
function
|
|
17867
|
-
const
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
|
|
17871
|
-
|
|
17872
|
-
|
|
17873
|
-
|
|
17874
|
-
|
|
17990
|
+
function outputDryRun(results, options2) {
|
|
17991
|
+
const wouldUpdate = results.filter((r2) => r2.status === "would-update");
|
|
17992
|
+
const upToDate = results.filter((r2) => r2.status === "up-to-date");
|
|
17993
|
+
if (options2.json) {
|
|
17994
|
+
console.log(JSON.stringify({ dryRun: true, wouldUpdate, upToDate }, null, 2));
|
|
17995
|
+
return;
|
|
17996
|
+
}
|
|
17997
|
+
if (!options2.quiet) {
|
|
17998
|
+
logger.info("");
|
|
17999
|
+
if (wouldUpdate.length > 0) {
|
|
18000
|
+
logger.info("Would update:");
|
|
18001
|
+
for (const result of wouldUpdate) {
|
|
18002
|
+
logger.info(` ${result.qualifiedName} (${result.oldVersion} \u2192 ${result.newVersion})`);
|
|
18003
|
+
}
|
|
17875
18004
|
}
|
|
17876
|
-
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
18005
|
+
if (upToDate.length > 0 && options2.verbose) {
|
|
18006
|
+
logger.info("");
|
|
18007
|
+
logger.info("Already up to date:");
|
|
18008
|
+
for (const result of upToDate) {
|
|
18009
|
+
logger.info(` ${result.qualifiedName}`);
|
|
18010
|
+
}
|
|
17881
18011
|
}
|
|
17882
|
-
if (
|
|
17883
|
-
|
|
17884
|
-
|
|
17885
|
-
|
|
18012
|
+
if (wouldUpdate.length > 0) {
|
|
18013
|
+
logger.info("");
|
|
18014
|
+
logger.info("Run without --dry-run to apply changes.");
|
|
18015
|
+
} else {
|
|
18016
|
+
logger.info("All components are up to date.");
|
|
17886
18017
|
}
|
|
17887
|
-
throw new SelfUpdateError(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
17888
18018
|
}
|
|
17889
18019
|
}
|
|
17890
|
-
function
|
|
17891
|
-
|
|
17892
|
-
|
|
17893
|
-
|
|
17894
|
-
|
|
18020
|
+
async function hashContent2(content2) {
|
|
18021
|
+
return createHash2("sha256").update(content2).digest("hex");
|
|
18022
|
+
}
|
|
18023
|
+
async function hashBundle2(files) {
|
|
18024
|
+
const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
|
|
18025
|
+
const manifestParts = [];
|
|
18026
|
+
for (const file of sorted) {
|
|
18027
|
+
const hash = await hashContent2(file.content);
|
|
18028
|
+
manifestParts.push(`${file.path}:${hash}`);
|
|
17895
18029
|
}
|
|
18030
|
+
return hashContent2(manifestParts.join(`
|
|
18031
|
+
`));
|
|
17896
18032
|
}
|
|
17897
18033
|
|
|
17898
|
-
// src/
|
|
17899
|
-
function
|
|
17900
|
-
|
|
17901
|
-
|
|
17902
|
-
|
|
17903
|
-
|
|
17904
|
-
}
|
|
17905
|
-
|
|
17906
|
-
|
|
18034
|
+
// src/commands/ghost/update.ts
|
|
18035
|
+
function registerGhostUpdateCommand(parent) {
|
|
18036
|
+
parent.command("update [components...]").description("Update installed components (use @version suffix to pin)").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) => {
|
|
18037
|
+
try {
|
|
18038
|
+
const provider = await GhostConfigProvider.create(options2.cwd ?? process.cwd());
|
|
18039
|
+
await runUpdateCore(components, options2, provider);
|
|
18040
|
+
} catch (error) {
|
|
18041
|
+
handleError(error, { json: options2.json });
|
|
18042
|
+
}
|
|
18043
|
+
});
|
|
17907
18044
|
}
|
|
17908
|
-
|
|
17909
|
-
|
|
18045
|
+
|
|
18046
|
+
// src/commands/ghost/index.ts
|
|
18047
|
+
function registerGhostCommand(program2) {
|
|
18048
|
+
const ghost = program2.command("ghost").alias("g").description("Ghost mode - work without local config files");
|
|
18049
|
+
registerGhostInitCommand(ghost);
|
|
18050
|
+
registerGhostConfigCommand(ghost);
|
|
18051
|
+
registerGhostRegistryCommand(ghost);
|
|
18052
|
+
registerGhostAddCommand(ghost);
|
|
18053
|
+
registerGhostSearchCommand(ghost);
|
|
18054
|
+
registerGhostOpenCodeCommand(ghost);
|
|
18055
|
+
registerGhostProfileCommand(ghost);
|
|
18056
|
+
registerGhostUpdateCommand(ghost);
|
|
17910
18057
|
}
|
|
17911
18058
|
|
|
17912
|
-
// src/
|
|
17913
|
-
import {
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
18059
|
+
// src/commands/init.ts
|
|
18060
|
+
import { existsSync as existsSync6 } from "fs";
|
|
18061
|
+
import { cp, mkdir as mkdir7, readdir as readdir6, readFile, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
18062
|
+
import { join as join9 } from "path";
|
|
18063
|
+
var TEMPLATE_REPO = "kdcokenny/ocx";
|
|
18064
|
+
var TEMPLATE_PATH = "examples/registry-starter";
|
|
18065
|
+
function registerInitCommand(program2) {
|
|
18066
|
+
program2.command("init [directory]").description("Initialize OCX configuration in your project").option("--cwd <path>", "Working directory", process.cwd()).option("-q, --quiet", "Suppress output").option("-v, --verbose", "Verbose output").option("--json", "Output as JSON").option("--registry", "Scaffold a new OCX registry project").option("--namespace <name>", "Registry namespace (e.g., my-org)").option("--author <name>", "Author name for the registry").option("--canary", "Use canary (main branch) instead of latest release").option("--local <path>", "Use local template directory instead of fetching").option("-f, --force", "Overwrite existing files (registry mode only)").action(async (directory, options2) => {
|
|
18067
|
+
try {
|
|
18068
|
+
if (options2.registry) {
|
|
18069
|
+
await runInitRegistry(directory, options2);
|
|
18070
|
+
} else {
|
|
18071
|
+
await runInit(options2);
|
|
18072
|
+
}
|
|
18073
|
+
} catch (error) {
|
|
18074
|
+
handleError(error, { json: options2.json });
|
|
17922
18075
|
}
|
|
17923
|
-
}
|
|
17924
|
-
return checksums;
|
|
17925
|
-
}
|
|
17926
|
-
function hashContent2(content2) {
|
|
17927
|
-
return createHash2("sha256").update(content2).digest("hex");
|
|
17928
|
-
}
|
|
17929
|
-
async function fetchChecksums(version) {
|
|
17930
|
-
const url = `https://github.com/${GITHUB_REPO3}/releases/download/v${version}/SHA256SUMS.txt`;
|
|
17931
|
-
const response = await fetch(url);
|
|
17932
|
-
if (!response.ok) {
|
|
17933
|
-
throw new SelfUpdateError(`Failed to fetch checksums: ${response.status}`);
|
|
17934
|
-
}
|
|
17935
|
-
const content2 = await response.text();
|
|
17936
|
-
return parseSha256Sums(content2);
|
|
17937
|
-
}
|
|
17938
|
-
async function verifyChecksum(filePath, expectedHash, filename) {
|
|
17939
|
-
const file = Bun.file(filePath);
|
|
17940
|
-
const content2 = await file.arrayBuffer();
|
|
17941
|
-
const actualHash = hashContent2(Buffer.from(content2));
|
|
17942
|
-
if (actualHash !== expectedHash) {
|
|
17943
|
-
throw new IntegrityError(filename, expectedHash, actualHash);
|
|
17944
|
-
}
|
|
18076
|
+
});
|
|
17945
18077
|
}
|
|
18078
|
+
async function runInit(options2) {
|
|
18079
|
+
const cwd = options2.cwd ?? process.cwd();
|
|
18080
|
+
const configPath = join9(cwd, "ocx.jsonc");
|
|
18081
|
+
if (existsSync6(configPath)) {
|
|
18082
|
+
throw new ConflictError(`ocx.jsonc already exists at ${configPath}
|
|
17946
18083
|
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
async function updateCommand(options2) {
|
|
17950
|
-
const method = options2.method ? parseInstallMethod(options2.method) : detectInstallMethod();
|
|
17951
|
-
const result = await checkForUpdate();
|
|
17952
|
-
if (!result) {
|
|
17953
|
-
throw new SelfUpdateError("Failed to check for updates");
|
|
17954
|
-
}
|
|
17955
|
-
const { current, latest, updateAvailable } = result;
|
|
17956
|
-
if (!updateAvailable && !options2.force) {
|
|
17957
|
-
notifyUpToDate(current);
|
|
17958
|
-
return;
|
|
18084
|
+
` + `To reset, delete the config and run init again:
|
|
18085
|
+
` + ` rm ${configPath} && ocx init`);
|
|
17959
18086
|
}
|
|
17960
|
-
const
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
|
|
17971
|
-
|
|
18087
|
+
const spin = options2.quiet ? null : createSpinner({ text: "Initializing OCX..." });
|
|
18088
|
+
spin?.start();
|
|
18089
|
+
try {
|
|
18090
|
+
const rawConfig = {
|
|
18091
|
+
$schema: OCX_SCHEMA_URL,
|
|
18092
|
+
registries: {}
|
|
18093
|
+
};
|
|
18094
|
+
const config = ocxConfigSchema.parse(rawConfig);
|
|
18095
|
+
const content2 = JSON.stringify(config, null, 2);
|
|
18096
|
+
await writeFile3(configPath, content2, "utf-8");
|
|
18097
|
+
const opencodeResult = await ensureOpencodeConfig(cwd);
|
|
18098
|
+
spin?.succeed("Initialized OCX configuration");
|
|
18099
|
+
if (options2.json) {
|
|
18100
|
+
console.log(JSON.stringify({
|
|
18101
|
+
success: true,
|
|
18102
|
+
path: configPath,
|
|
18103
|
+
opencodePath: opencodeResult.path,
|
|
18104
|
+
opencodeCreated: opencodeResult.created
|
|
18105
|
+
}));
|
|
18106
|
+
} else if (!options2.quiet) {
|
|
18107
|
+
logger.info(`Created ${configPath}`);
|
|
18108
|
+
if (opencodeResult.created) {
|
|
18109
|
+
logger.info(`Created ${opencodeResult.path}`);
|
|
18110
|
+
}
|
|
18111
|
+
logger.info("");
|
|
18112
|
+
logger.info("Next steps:");
|
|
18113
|
+
logger.info(" 1. Add a registry: ocx registry add <url>");
|
|
18114
|
+
logger.info(" 2. Install components: ocx add <component>");
|
|
17972
18115
|
}
|
|
18116
|
+
} catch (error) {
|
|
18117
|
+
spin?.fail("Failed to initialize");
|
|
18118
|
+
throw error;
|
|
17973
18119
|
}
|
|
17974
18120
|
}
|
|
17975
|
-
async function
|
|
17976
|
-
const
|
|
17977
|
-
const
|
|
17978
|
-
|
|
17979
|
-
|
|
18121
|
+
async function runInitRegistry(directory, options2) {
|
|
18122
|
+
const cwd = directory ?? options2.cwd ?? process.cwd();
|
|
18123
|
+
const namespace = options2.namespace ?? "my-registry";
|
|
18124
|
+
const author = options2.author ?? "Your Name";
|
|
18125
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(namespace)) {
|
|
18126
|
+
throw new ValidationError("Invalid namespace format: must start with letter/number, use hyphens only between segments (e.g., 'my-registry')");
|
|
17980
18127
|
}
|
|
17981
|
-
const
|
|
17982
|
-
const
|
|
17983
|
-
if (!
|
|
17984
|
-
throw new
|
|
18128
|
+
const existingFiles = await readdir6(cwd).catch(() => []);
|
|
18129
|
+
const hasVisibleFiles = existingFiles.some((f) => !f.startsWith("."));
|
|
18130
|
+
if (hasVisibleFiles && !options2.force) {
|
|
18131
|
+
throw new ConflictError("Directory is not empty. Use --force to overwrite existing files.");
|
|
17985
18132
|
}
|
|
17986
|
-
const
|
|
18133
|
+
const spin = options2.quiet ? null : createSpinner({ text: "Scaffolding registry..." });
|
|
18134
|
+
spin?.start();
|
|
17987
18135
|
try {
|
|
17988
|
-
|
|
18136
|
+
if (spin)
|
|
18137
|
+
spin.text = options2.local ? "Copying template..." : "Fetching template...";
|
|
18138
|
+
if (options2.local) {
|
|
18139
|
+
await mkdir7(cwd, { recursive: true });
|
|
18140
|
+
await copyDir(options2.local, cwd);
|
|
18141
|
+
} else {
|
|
18142
|
+
const version = options2.canary ? "main" : await getLatestVersion();
|
|
18143
|
+
await fetchAndExtractTemplate(cwd, version, options2.verbose);
|
|
18144
|
+
}
|
|
18145
|
+
if (spin)
|
|
18146
|
+
spin.text = "Configuring project...";
|
|
18147
|
+
await replacePlaceholders(cwd, { namespace, author });
|
|
18148
|
+
spin?.succeed(`Created registry: ${namespace}`);
|
|
18149
|
+
if (options2.json) {
|
|
18150
|
+
console.log(JSON.stringify({ success: true, namespace, path: cwd }));
|
|
18151
|
+
} else if (!options2.quiet) {
|
|
18152
|
+
logger.info("");
|
|
18153
|
+
logger.info("Next steps:");
|
|
18154
|
+
logger.info(" 1. bun install");
|
|
18155
|
+
logger.info(" 2. Edit registry.jsonc with your components");
|
|
18156
|
+
logger.info(" 3. bun run build");
|
|
18157
|
+
logger.info("");
|
|
18158
|
+
logger.info("Deploy to:");
|
|
18159
|
+
logger.info(" Cloudflare: bunx wrangler deploy");
|
|
18160
|
+
logger.info(" Vercel: vercel");
|
|
18161
|
+
logger.info(" Netlify: netlify deploy");
|
|
18162
|
+
}
|
|
17989
18163
|
} catch (error) {
|
|
17990
|
-
|
|
18164
|
+
spin?.fail("Failed to scaffold registry");
|
|
17991
18165
|
throw error;
|
|
17992
18166
|
}
|
|
17993
|
-
atomicReplace(tempPath, execPath);
|
|
17994
|
-
notifyUpdated(current, targetVersion);
|
|
17995
18167
|
}
|
|
17996
|
-
async function
|
|
17997
|
-
|
|
17998
|
-
const exitCode = await proc.exited;
|
|
17999
|
-
if (exitCode !== 0) {
|
|
18000
|
-
const stderr = await new Response(proc.stderr).text();
|
|
18001
|
-
throw new SelfUpdateError(`Package manager command failed: ${stderr.trim()}`);
|
|
18002
|
-
}
|
|
18168
|
+
async function copyDir(src, dest) {
|
|
18169
|
+
await cp(src, dest, { recursive: true });
|
|
18003
18170
|
}
|
|
18004
|
-
async function
|
|
18005
|
-
|
|
18006
|
-
|
|
18171
|
+
async function getLatestVersion() {
|
|
18172
|
+
const pkgPath = new URL("../../package.json", import.meta.url);
|
|
18173
|
+
const pkgContent = await readFile(pkgPath);
|
|
18174
|
+
const pkg = JSON.parse(pkgContent.toString());
|
|
18175
|
+
return `v${pkg.version}`;
|
|
18176
|
+
}
|
|
18177
|
+
async function fetchAndExtractTemplate(destDir, version, verbose) {
|
|
18178
|
+
const ref = version === "main" ? "heads/main" : `tags/${version}`;
|
|
18179
|
+
const tarballUrl = `https://github.com/${TEMPLATE_REPO}/archive/refs/${ref}.tar.gz`;
|
|
18180
|
+
if (verbose) {
|
|
18181
|
+
logger.info(`Fetching ${tarballUrl}`);
|
|
18007
18182
|
}
|
|
18008
|
-
const
|
|
18009
|
-
|
|
18183
|
+
const response = await fetch(tarballUrl);
|
|
18184
|
+
if (!response.ok || !response.body) {
|
|
18185
|
+
throw new NetworkError(`Failed to fetch template from ${tarballUrl}: ${response.statusText}`);
|
|
18186
|
+
}
|
|
18187
|
+
const tempDir = join9(destDir, ".ocx-temp");
|
|
18188
|
+
await mkdir7(tempDir, { recursive: true });
|
|
18010
18189
|
try {
|
|
18011
|
-
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
|
|
18022
|
-
break;
|
|
18023
|
-
}
|
|
18024
|
-
case "bun": {
|
|
18025
|
-
await runPackageManager(["bun", "install", "-g", `ocx@${targetVersion}`]);
|
|
18026
|
-
break;
|
|
18027
|
-
}
|
|
18028
|
-
case "unknown": {
|
|
18029
|
-
throw new SelfUpdateError(`Could not detect install method. Update manually with one of:
|
|
18030
|
-
` + ` npm install -g ocx@latest
|
|
18031
|
-
` + ` pnpm install -g ocx@latest
|
|
18032
|
-
` + " bun install -g ocx@latest");
|
|
18033
|
-
}
|
|
18190
|
+
const tarPath = join9(tempDir, "template.tar.gz");
|
|
18191
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
18192
|
+
await writeFile3(tarPath, Buffer.from(arrayBuffer));
|
|
18193
|
+
const proc = Bun.spawn(["tar", "-xzf", tarPath, "-C", tempDir], {
|
|
18194
|
+
stdout: "ignore",
|
|
18195
|
+
stderr: "pipe"
|
|
18196
|
+
});
|
|
18197
|
+
const exitCode = await proc.exited;
|
|
18198
|
+
if (exitCode !== 0) {
|
|
18199
|
+
const stderr = await new Response(proc.stderr).text();
|
|
18200
|
+
throw new Error(`Failed to extract template: ${stderr}`);
|
|
18034
18201
|
}
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
|
|
18038
|
-
|
|
18039
|
-
spin.fail(`Update failed`);
|
|
18040
|
-
throw error;
|
|
18202
|
+
const extractedDirs = await readdir6(tempDir);
|
|
18203
|
+
const extractedDir = extractedDirs.find((d) => d.startsWith("ocx-"));
|
|
18204
|
+
if (!extractedDir) {
|
|
18205
|
+
throw new Error("Failed to find extracted template directory");
|
|
18041
18206
|
}
|
|
18042
|
-
|
|
18043
|
-
|
|
18207
|
+
const templateDir = join9(tempDir, extractedDir, TEMPLATE_PATH);
|
|
18208
|
+
await copyDir(templateDir, destDir);
|
|
18209
|
+
} finally {
|
|
18210
|
+
await rm3(tempDir, { recursive: true, force: true });
|
|
18044
18211
|
}
|
|
18045
18212
|
}
|
|
18046
|
-
function
|
|
18047
|
-
|
|
18048
|
-
|
|
18049
|
-
|
|
18213
|
+
async function replacePlaceholders(dir, values) {
|
|
18214
|
+
const filesToProcess = [
|
|
18215
|
+
"registry.jsonc",
|
|
18216
|
+
"package.json",
|
|
18217
|
+
"wrangler.jsonc",
|
|
18218
|
+
"README.md",
|
|
18219
|
+
"AGENTS.md"
|
|
18220
|
+
];
|
|
18221
|
+
for (const file of filesToProcess) {
|
|
18222
|
+
const filePath = join9(dir, file);
|
|
18223
|
+
if (!existsSync6(filePath))
|
|
18224
|
+
continue;
|
|
18225
|
+
let content2 = await readFile(filePath).then((b) => b.toString());
|
|
18226
|
+
content2 = content2.replace(/my-registry/g, values.namespace);
|
|
18227
|
+
content2 = content2.replace(/Your Name/g, values.author);
|
|
18228
|
+
await writeFile3(filePath, content2);
|
|
18229
|
+
}
|
|
18050
18230
|
}
|
|
18051
18231
|
|
|
18052
|
-
// src/
|
|
18053
|
-
|
|
18054
|
-
|
|
18055
|
-
|
|
18232
|
+
// src/self-update/version-provider.ts
|
|
18233
|
+
class BuildTimeVersionProvider {
|
|
18234
|
+
version = "1.3.2";
|
|
18235
|
+
}
|
|
18236
|
+
var defaultVersionProvider = new BuildTimeVersionProvider;
|
|
18237
|
+
|
|
18238
|
+
// src/self-update/check.ts
|
|
18239
|
+
var VERSION_CHECK_TIMEOUT_MS = 200;
|
|
18240
|
+
var PACKAGE_NAME = "ocx";
|
|
18241
|
+
function parseVersion2(v) {
|
|
18242
|
+
const [main2 = ""] = v.split("-");
|
|
18243
|
+
const parts = main2.split(".");
|
|
18244
|
+
const major = parseInt(parts[0] ?? "", 10);
|
|
18245
|
+
const minor = parseInt(parts[1] ?? "", 10);
|
|
18246
|
+
const patch = parseInt(parts[2] ?? "", 10);
|
|
18247
|
+
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) {
|
|
18248
|
+
return null;
|
|
18249
|
+
}
|
|
18250
|
+
return { major, minor, patch };
|
|
18251
|
+
}
|
|
18252
|
+
function compareSemver2(a, b) {
|
|
18253
|
+
const vA = parseVersion2(a);
|
|
18254
|
+
const vB = parseVersion2(b);
|
|
18255
|
+
if (!vA || !vB) {
|
|
18256
|
+
return null;
|
|
18257
|
+
}
|
|
18258
|
+
if (vA.major !== vB.major)
|
|
18259
|
+
return vA.major - vB.major;
|
|
18260
|
+
if (vA.minor !== vB.minor)
|
|
18261
|
+
return vA.minor - vB.minor;
|
|
18262
|
+
return vA.patch - vB.patch;
|
|
18263
|
+
}
|
|
18264
|
+
async function checkForUpdate(versionProvider) {
|
|
18265
|
+
const provider = versionProvider ?? defaultVersionProvider;
|
|
18266
|
+
const current = provider.version || "0.0.0-dev";
|
|
18267
|
+
if (current === "0.0.0-dev") {
|
|
18268
|
+
return null;
|
|
18269
|
+
}
|
|
18270
|
+
try {
|
|
18271
|
+
const result = await fetchPackageVersion(PACKAGE_NAME, undefined, AbortSignal.timeout(VERSION_CHECK_TIMEOUT_MS));
|
|
18272
|
+
const latest = result.version;
|
|
18273
|
+
const comparison = compareSemver2(latest, current);
|
|
18274
|
+
if (comparison === null) {
|
|
18275
|
+
return null;
|
|
18276
|
+
}
|
|
18277
|
+
return {
|
|
18278
|
+
current,
|
|
18279
|
+
latest,
|
|
18280
|
+
updateAvailable: comparison > 0
|
|
18281
|
+
};
|
|
18282
|
+
} catch {
|
|
18283
|
+
return null;
|
|
18284
|
+
}
|
|
18056
18285
|
}
|
|
18057
18286
|
|
|
18058
|
-
// src/
|
|
18059
|
-
|
|
18060
|
-
|
|
18061
|
-
|
|
18062
|
-
|
|
18063
|
-
|
|
18064
|
-
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
} catch (error) {
|
|
18068
|
-
handleError(error, { json: options2.json });
|
|
18069
|
-
}
|
|
18070
|
-
});
|
|
18287
|
+
// src/self-update/detect-method.ts
|
|
18288
|
+
function parseInstallMethod(input) {
|
|
18289
|
+
const VALID_METHODS = ["curl", "npm", "yarn", "pnpm", "bun"];
|
|
18290
|
+
const method = VALID_METHODS.find((m) => m === input);
|
|
18291
|
+
if (!method) {
|
|
18292
|
+
throw new SelfUpdateError(`Invalid install method: "${input}"
|
|
18293
|
+
Valid methods: ${VALID_METHODS.join(", ")}`);
|
|
18294
|
+
}
|
|
18295
|
+
return method;
|
|
18071
18296
|
}
|
|
18072
|
-
|
|
18073
|
-
|
|
18074
|
-
|
|
18075
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18297
|
+
var isCompiledBinary = () => Bun.main.startsWith("/$bunfs/");
|
|
18298
|
+
var isTempExecution = (path8) => path8.includes("/_npx/") || path8.includes("/.cache/bunx/") || path8.includes("/.pnpm/_temp/");
|
|
18299
|
+
var isYarnGlobalInstall = (path8) => path8.includes("/.yarn/global") || path8.includes("/.config/yarn/global");
|
|
18300
|
+
var isPnpmGlobalInstall = (path8) => path8.includes("/.pnpm/") || path8.includes("/pnpm/global");
|
|
18301
|
+
var isBunGlobalInstall = (path8) => path8.includes("/.bun/bin") || path8.includes("/.bun/install/global");
|
|
18302
|
+
var isNpmGlobalInstall = (path8) => path8.includes("/.npm/") || path8.includes("/node_modules/");
|
|
18303
|
+
function detectInstallMethod() {
|
|
18304
|
+
if (isCompiledBinary()) {
|
|
18305
|
+
return "curl";
|
|
18078
18306
|
}
|
|
18079
|
-
const
|
|
18080
|
-
if (
|
|
18081
|
-
|
|
18307
|
+
const scriptPath = process.argv[1] ?? "";
|
|
18308
|
+
if (isTempExecution(scriptPath))
|
|
18309
|
+
return "unknown";
|
|
18310
|
+
if (isYarnGlobalInstall(scriptPath))
|
|
18311
|
+
return "yarn";
|
|
18312
|
+
if (isPnpmGlobalInstall(scriptPath))
|
|
18313
|
+
return "pnpm";
|
|
18314
|
+
if (isBunGlobalInstall(scriptPath))
|
|
18315
|
+
return "bun";
|
|
18316
|
+
if (isNpmGlobalInstall(scriptPath))
|
|
18317
|
+
return "npm";
|
|
18318
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
18319
|
+
if (userAgent.includes("yarn"))
|
|
18320
|
+
return "yarn";
|
|
18321
|
+
if (userAgent.includes("pnpm"))
|
|
18322
|
+
return "pnpm";
|
|
18323
|
+
if (userAgent.includes("bun"))
|
|
18324
|
+
return "bun";
|
|
18325
|
+
if (userAgent.includes("npm"))
|
|
18326
|
+
return "npm";
|
|
18327
|
+
return "unknown";
|
|
18328
|
+
}
|
|
18329
|
+
function getExecutablePath() {
|
|
18330
|
+
if (typeof Bun !== "undefined" && Bun.main.startsWith("/$bunfs/")) {
|
|
18331
|
+
return process.execPath;
|
|
18082
18332
|
}
|
|
18083
|
-
|
|
18084
|
-
|
|
18085
|
-
const hasRegistry = options2.registry !== undefined;
|
|
18086
|
-
if (!hasComponents && !hasAll && !hasRegistry) {
|
|
18087
|
-
throw new ValidationError(`Specify components, use --all, or use --registry <name>.
|
|
18333
|
+
return process.argv[1] ?? process.execPath;
|
|
18334
|
+
}
|
|
18088
18335
|
|
|
18089
|
-
|
|
18090
|
-
|
|
18091
|
-
|
|
18092
|
-
|
|
18093
|
-
|
|
18094
|
-
|
|
18095
|
-
|
|
18096
|
-
|
|
18336
|
+
// src/self-update/download.ts
|
|
18337
|
+
import { chmodSync, existsSync as existsSync7, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
18338
|
+
var GITHUB_REPO2 = "kdcokenny/ocx";
|
|
18339
|
+
var DEFAULT_DOWNLOAD_BASE_URL = `https://github.com/${GITHUB_REPO2}/releases/download`;
|
|
18340
|
+
var PLATFORM_MAP = {
|
|
18341
|
+
"arm64-darwin": "ocx-darwin-arm64",
|
|
18342
|
+
"x64-darwin": "ocx-darwin-x64",
|
|
18343
|
+
"arm64-linux": "ocx-linux-arm64",
|
|
18344
|
+
"x64-linux": "ocx-linux-x64",
|
|
18345
|
+
"x64-win32": "ocx-windows-x64.exe"
|
|
18346
|
+
};
|
|
18347
|
+
function getDownloadBaseUrl() {
|
|
18348
|
+
const envUrl = process.env.OCX_DOWNLOAD_URL;
|
|
18349
|
+
if (envUrl) {
|
|
18350
|
+
return envUrl.replace(/\/+$/, "");
|
|
18097
18351
|
}
|
|
18098
|
-
|
|
18099
|
-
|
|
18100
|
-
|
|
18352
|
+
return DEFAULT_DOWNLOAD_BASE_URL;
|
|
18353
|
+
}
|
|
18354
|
+
function getDownloadUrl(version) {
|
|
18355
|
+
const platform = `${process.arch}-${process.platform}`;
|
|
18356
|
+
const target = PLATFORM_MAP[platform];
|
|
18357
|
+
if (!target) {
|
|
18358
|
+
const supported = Object.keys(PLATFORM_MAP).join(", ");
|
|
18359
|
+
throw new SelfUpdateError(`Unsupported platform: ${platform}
|
|
18360
|
+
` + `Supported platforms: ${supported}`);
|
|
18101
18361
|
}
|
|
18102
|
-
|
|
18103
|
-
|
|
18104
|
-
|
|
18362
|
+
const baseUrl = getDownloadBaseUrl();
|
|
18363
|
+
return `${baseUrl}/v${version}/${target}`;
|
|
18364
|
+
}
|
|
18365
|
+
async function downloadWithProgress(url, dest) {
|
|
18366
|
+
const spin = createSpinner({ text: "Downloading update..." });
|
|
18367
|
+
spin.start();
|
|
18368
|
+
let response;
|
|
18369
|
+
try {
|
|
18370
|
+
response = await fetch(url, { redirect: "follow" });
|
|
18371
|
+
} catch (error) {
|
|
18372
|
+
spin.fail("Download failed");
|
|
18373
|
+
throw new SelfUpdateError(`Network error: ${error instanceof Error ? error.message : String(error)}`);
|
|
18105
18374
|
}
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18109
|
-
throw new ValidationError(`Invalid version specifier in '${spec.component}@'.` + `
|
|
18110
|
-
Version cannot be empty. Use 'kdco/agents@1.2.0' or omit the version for latest.`);
|
|
18111
|
-
}
|
|
18375
|
+
if (!response.ok) {
|
|
18376
|
+
spin.fail("Download failed");
|
|
18377
|
+
throw new SelfUpdateError(`Failed to download: HTTP ${response.status} ${response.statusText}`);
|
|
18112
18378
|
}
|
|
18113
|
-
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
throw new NotFoundError(`No installed components from registry '${options2.registry}'.`);
|
|
18117
|
-
}
|
|
18118
|
-
throw new NotFoundError("No matching components found to update.");
|
|
18379
|
+
if (!response.body) {
|
|
18380
|
+
spin.fail("Download failed");
|
|
18381
|
+
throw new SelfUpdateError("Download failed: Empty response body");
|
|
18119
18382
|
}
|
|
18120
|
-
const
|
|
18121
|
-
|
|
18122
|
-
const
|
|
18123
|
-
|
|
18383
|
+
const reader = response.body.getReader();
|
|
18384
|
+
const writer = Bun.file(dest).writer();
|
|
18385
|
+
const total = Number(response.headers.get("content-length") || 0);
|
|
18386
|
+
let received = 0;
|
|
18124
18387
|
try {
|
|
18125
|
-
|
|
18126
|
-
const
|
|
18127
|
-
|
|
18128
|
-
|
|
18129
|
-
|
|
18130
|
-
|
|
18131
|
-
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
throw new ConfigError(`Registry '${namespace}' not configured. Component '${qualifiedName}' cannot be updated.`);
|
|
18135
|
-
}
|
|
18136
|
-
const fetchResult = await fetchComponentVersion(regConfig.url, componentName, spec.version);
|
|
18137
|
-
const manifest = fetchResult.manifest;
|
|
18138
|
-
const version = fetchResult.version;
|
|
18139
|
-
const normalizedManifest = normalizeComponentManifest(manifest);
|
|
18140
|
-
const files = [];
|
|
18141
|
-
for (const file of normalizedManifest.files) {
|
|
18142
|
-
const content2 = await fetchFileContent(regConfig.url, componentName, file.path);
|
|
18143
|
-
files.push({ path: file.path, content: Buffer.from(content2) });
|
|
18144
|
-
}
|
|
18145
|
-
const newHash = await hashBundle2(files);
|
|
18146
|
-
if (newHash === lockEntry.hash) {
|
|
18147
|
-
results.push({
|
|
18148
|
-
qualifiedName,
|
|
18149
|
-
oldVersion: lockEntry.version,
|
|
18150
|
-
newVersion: version,
|
|
18151
|
-
status: "up-to-date"
|
|
18152
|
-
});
|
|
18153
|
-
} else if (options2.dryRun) {
|
|
18154
|
-
results.push({
|
|
18155
|
-
qualifiedName,
|
|
18156
|
-
oldVersion: lockEntry.version,
|
|
18157
|
-
newVersion: version,
|
|
18158
|
-
status: "would-update"
|
|
18159
|
-
});
|
|
18160
|
-
} else {
|
|
18161
|
-
results.push({
|
|
18162
|
-
qualifiedName,
|
|
18163
|
-
oldVersion: lockEntry.version,
|
|
18164
|
-
newVersion: version,
|
|
18165
|
-
status: "updated"
|
|
18166
|
-
});
|
|
18167
|
-
updates.push({
|
|
18168
|
-
qualifiedName,
|
|
18169
|
-
component: normalizedManifest,
|
|
18170
|
-
files,
|
|
18171
|
-
newHash,
|
|
18172
|
-
newVersion: version,
|
|
18173
|
-
baseUrl: regConfig.url
|
|
18174
|
-
});
|
|
18175
|
-
}
|
|
18176
|
-
}
|
|
18177
|
-
spin?.succeed(`Checked ${componentsToUpdate.length} component(s)`);
|
|
18178
|
-
if (options2.dryRun) {
|
|
18179
|
-
outputDryRun(results, options2);
|
|
18180
|
-
return;
|
|
18181
|
-
}
|
|
18182
|
-
if (updates.length === 0) {
|
|
18183
|
-
if (!options2.quiet && !options2.json) {
|
|
18184
|
-
logger.info("");
|
|
18185
|
-
logger.success("All components are up to date.");
|
|
18186
|
-
}
|
|
18187
|
-
if (options2.json) {
|
|
18188
|
-
console.log(JSON.stringify({ success: true, updated: [], upToDate: results }, null, 2));
|
|
18388
|
+
while (true) {
|
|
18389
|
+
const { done, value } = await reader.read();
|
|
18390
|
+
if (done)
|
|
18391
|
+
break;
|
|
18392
|
+
await writer.write(value);
|
|
18393
|
+
received += value.length;
|
|
18394
|
+
if (total > 0) {
|
|
18395
|
+
const pct = Math.round(received / total * 100);
|
|
18396
|
+
spin.text = `Downloading... ${pct}%`;
|
|
18189
18397
|
}
|
|
18190
|
-
return;
|
|
18191
18398
|
}
|
|
18192
|
-
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
|
|
18197
|
-
|
|
18198
|
-
|
|
18199
|
-
|
|
18200
|
-
|
|
18201
|
-
|
|
18202
|
-
|
|
18203
|
-
|
|
18204
|
-
|
|
18205
|
-
|
|
18206
|
-
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
18210
|
-
if (!existingEntry) {
|
|
18211
|
-
throw new NotFoundError(`Component '${update.qualifiedName}' not found in lock file.`);
|
|
18212
|
-
}
|
|
18213
|
-
lock.installed[update.qualifiedName] = {
|
|
18214
|
-
registry: existingEntry.registry,
|
|
18215
|
-
version: update.newVersion,
|
|
18216
|
-
hash: update.newHash,
|
|
18217
|
-
files: existingEntry.files,
|
|
18218
|
-
installedAt: existingEntry.installedAt,
|
|
18219
|
-
updatedAt: new Date().toISOString()
|
|
18220
|
-
};
|
|
18399
|
+
await writer.end();
|
|
18400
|
+
spin.succeed("Download complete");
|
|
18401
|
+
} catch (error) {
|
|
18402
|
+
spin.fail("Download failed");
|
|
18403
|
+
await writer.end();
|
|
18404
|
+
throw new SelfUpdateError(`Download interrupted: ${error instanceof Error ? error.message : String(error)}`);
|
|
18405
|
+
}
|
|
18406
|
+
}
|
|
18407
|
+
async function downloadToTemp(version) {
|
|
18408
|
+
const execPath = getExecutablePath();
|
|
18409
|
+
const tempPath = `${execPath}.new.${Date.now()}`;
|
|
18410
|
+
const url = getDownloadUrl(version);
|
|
18411
|
+
await downloadWithProgress(url, tempPath);
|
|
18412
|
+
try {
|
|
18413
|
+
chmodSync(tempPath, 493);
|
|
18414
|
+
} catch (error) {
|
|
18415
|
+
if (existsSync7(tempPath)) {
|
|
18416
|
+
unlinkSync2(tempPath);
|
|
18221
18417
|
}
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
|
|
18226
|
-
|
|
18227
|
-
|
|
18228
|
-
|
|
18229
|
-
|
|
18230
|
-
|
|
18231
|
-
|
|
18232
|
-
|
|
18233
|
-
|
|
18234
|
-
|
|
18235
|
-
} else if (result.status === "up-to-date" && options2.verbose) {
|
|
18236
|
-
logger.info(` \u25CB ${result.qualifiedName} (already up to date)`);
|
|
18237
|
-
}
|
|
18238
|
-
}
|
|
18239
|
-
logger.info("");
|
|
18240
|
-
logger.success(`Done! Updated ${updates.length} component(s).`);
|
|
18418
|
+
throw new SelfUpdateError(`Failed to set permissions: ${error instanceof Error ? error.message : String(error)}`);
|
|
18419
|
+
}
|
|
18420
|
+
return { tempPath, execPath };
|
|
18421
|
+
}
|
|
18422
|
+
function atomicReplace(tempPath, execPath) {
|
|
18423
|
+
const backupPath = `${execPath}.backup`;
|
|
18424
|
+
try {
|
|
18425
|
+
if (existsSync7(execPath)) {
|
|
18426
|
+
renameSync2(execPath, backupPath);
|
|
18427
|
+
}
|
|
18428
|
+
renameSync2(tempPath, execPath);
|
|
18429
|
+
if (existsSync7(backupPath)) {
|
|
18430
|
+
unlinkSync2(backupPath);
|
|
18241
18431
|
}
|
|
18242
18432
|
} catch (error) {
|
|
18243
|
-
|
|
18244
|
-
|
|
18433
|
+
if (existsSync7(backupPath) && !existsSync7(execPath)) {
|
|
18434
|
+
try {
|
|
18435
|
+
renameSync2(backupPath, execPath);
|
|
18436
|
+
} catch {}
|
|
18437
|
+
}
|
|
18438
|
+
if (existsSync7(tempPath)) {
|
|
18439
|
+
try {
|
|
18440
|
+
unlinkSync2(tempPath);
|
|
18441
|
+
} catch {}
|
|
18442
|
+
}
|
|
18443
|
+
throw new SelfUpdateError(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
18245
18444
|
}
|
|
18246
18445
|
}
|
|
18247
|
-
function
|
|
18248
|
-
|
|
18249
|
-
|
|
18250
|
-
|
|
18446
|
+
function cleanupTempFile(tempPath) {
|
|
18447
|
+
if (existsSync7(tempPath)) {
|
|
18448
|
+
try {
|
|
18449
|
+
unlinkSync2(tempPath);
|
|
18450
|
+
} catch {}
|
|
18251
18451
|
}
|
|
18252
|
-
return {
|
|
18253
|
-
component: spec.slice(0, atIndex),
|
|
18254
|
-
version: spec.slice(atIndex + 1)
|
|
18255
|
-
};
|
|
18256
18452
|
}
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
|
|
18260
|
-
|
|
18453
|
+
|
|
18454
|
+
// src/self-update/notify.ts
|
|
18455
|
+
function notifyUpdate(current, latest) {
|
|
18456
|
+
if (!process.stdout.isTTY)
|
|
18457
|
+
return;
|
|
18458
|
+
console.error(`${kleur_default.cyan("info")}: update available - ${kleur_default.green(latest)} (current: ${kleur_default.dim(current)})`);
|
|
18459
|
+
console.error(` run ${kleur_default.cyan("`ocx self update`")} to upgrade`);
|
|
18460
|
+
}
|
|
18461
|
+
function notifyUpToDate(version) {
|
|
18462
|
+
console.error(`${kleur_default.cyan("info")}: ocx unchanged - ${kleur_default.dim(version)}`);
|
|
18463
|
+
}
|
|
18464
|
+
function notifyUpdated(from, to) {
|
|
18465
|
+
console.error(` ${kleur_default.green("ocx updated")} - ${to} (from ${from})`);
|
|
18466
|
+
}
|
|
18467
|
+
|
|
18468
|
+
// src/self-update/verify.ts
|
|
18469
|
+
import { createHash as createHash3 } from "crypto";
|
|
18470
|
+
var GITHUB_REPO3 = "kdcokenny/ocx";
|
|
18471
|
+
function parseSha256Sums(content2) {
|
|
18472
|
+
const checksums = new Map;
|
|
18473
|
+
for (const line of content2.split(`
|
|
18474
|
+
`)) {
|
|
18475
|
+
const match = line.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
|
|
18476
|
+
if (match?.[1] && match[2]) {
|
|
18477
|
+
checksums.set(match[2].trim(), match[1].toLowerCase());
|
|
18478
|
+
}
|
|
18261
18479
|
}
|
|
18262
|
-
|
|
18263
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
|
|
18480
|
+
return checksums;
|
|
18481
|
+
}
|
|
18482
|
+
function hashContent3(content2) {
|
|
18483
|
+
return createHash3("sha256").update(content2).digest("hex");
|
|
18484
|
+
}
|
|
18485
|
+
async function fetchChecksums(version) {
|
|
18486
|
+
const url = `https://github.com/${GITHUB_REPO3}/releases/download/v${version}/SHA256SUMS.txt`;
|
|
18487
|
+
const response = await fetch(url);
|
|
18488
|
+
if (!response.ok) {
|
|
18489
|
+
throw new SelfUpdateError(`Failed to fetch checksums: ${response.status}`);
|
|
18267
18490
|
}
|
|
18268
|
-
const
|
|
18269
|
-
|
|
18270
|
-
|
|
18271
|
-
|
|
18272
|
-
|
|
18273
|
-
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
`) + `
|
|
18491
|
+
const content2 = await response.text();
|
|
18492
|
+
return parseSha256Sums(content2);
|
|
18493
|
+
}
|
|
18494
|
+
async function verifyChecksum(filePath, expectedHash, filename) {
|
|
18495
|
+
const file = Bun.file(filePath);
|
|
18496
|
+
const content2 = await file.arrayBuffer();
|
|
18497
|
+
const actualHash = hashContent3(Buffer.from(content2));
|
|
18498
|
+
if (actualHash !== expectedHash) {
|
|
18499
|
+
throw new IntegrityError(filename, expectedHash, actualHash);
|
|
18500
|
+
}
|
|
18501
|
+
}
|
|
18280
18502
|
|
|
18281
|
-
|
|
18282
|
-
|
|
18283
|
-
|
|
18503
|
+
// src/commands/self/update.ts
|
|
18504
|
+
var SEMVER_PATTERN = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
18505
|
+
async function updateCommand(options2) {
|
|
18506
|
+
const method = options2.method ? parseInstallMethod(options2.method) : detectInstallMethod();
|
|
18507
|
+
const result = await checkForUpdate();
|
|
18508
|
+
if (!result) {
|
|
18509
|
+
throw new SelfUpdateError("Failed to check for updates");
|
|
18510
|
+
}
|
|
18511
|
+
const { current, latest, updateAvailable } = result;
|
|
18512
|
+
if (!updateAvailable && !options2.force) {
|
|
18513
|
+
notifyUpToDate(current);
|
|
18514
|
+
return;
|
|
18515
|
+
}
|
|
18516
|
+
const targetVersion = latest;
|
|
18517
|
+
switch (method) {
|
|
18518
|
+
case "curl": {
|
|
18519
|
+
await updateViaCurl(current, targetVersion);
|
|
18520
|
+
break;
|
|
18284
18521
|
}
|
|
18285
|
-
|
|
18286
|
-
|
|
18287
|
-
|
|
18522
|
+
case "npm":
|
|
18523
|
+
case "pnpm":
|
|
18524
|
+
case "bun":
|
|
18525
|
+
case "unknown": {
|
|
18526
|
+
await updateViaPackageManager(method, current, targetVersion);
|
|
18527
|
+
break;
|
|
18288
18528
|
}
|
|
18289
|
-
result.push(spec);
|
|
18290
18529
|
}
|
|
18291
|
-
return result;
|
|
18292
18530
|
}
|
|
18293
|
-
function
|
|
18294
|
-
const
|
|
18295
|
-
const
|
|
18296
|
-
if (
|
|
18297
|
-
|
|
18298
|
-
return;
|
|
18531
|
+
async function updateViaCurl(current, targetVersion) {
|
|
18532
|
+
const url = getDownloadUrl(targetVersion);
|
|
18533
|
+
const filename = url.split("/").pop();
|
|
18534
|
+
if (!filename) {
|
|
18535
|
+
throw new SelfUpdateError("Failed to determine binary filename from download URL");
|
|
18299
18536
|
}
|
|
18300
|
-
|
|
18301
|
-
|
|
18302
|
-
|
|
18303
|
-
|
|
18304
|
-
|
|
18305
|
-
|
|
18537
|
+
const checksums = await fetchChecksums(targetVersion);
|
|
18538
|
+
const expectedHash = checksums.get(filename);
|
|
18539
|
+
if (!expectedHash) {
|
|
18540
|
+
throw new SelfUpdateError(`Security error: No checksum found for ${filename}. Update aborted.`);
|
|
18541
|
+
}
|
|
18542
|
+
const { tempPath, execPath } = await downloadToTemp(targetVersion);
|
|
18543
|
+
try {
|
|
18544
|
+
await verifyChecksum(tempPath, expectedHash, filename);
|
|
18545
|
+
} catch (error) {
|
|
18546
|
+
cleanupTempFile(tempPath);
|
|
18547
|
+
throw error;
|
|
18548
|
+
}
|
|
18549
|
+
atomicReplace(tempPath, execPath);
|
|
18550
|
+
notifyUpdated(current, targetVersion);
|
|
18551
|
+
}
|
|
18552
|
+
async function runPackageManager(cmd) {
|
|
18553
|
+
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
18554
|
+
const exitCode = await proc.exited;
|
|
18555
|
+
if (exitCode !== 0) {
|
|
18556
|
+
const stderr = await new Response(proc.stderr).text();
|
|
18557
|
+
throw new SelfUpdateError(`Package manager command failed: ${stderr.trim()}`);
|
|
18558
|
+
}
|
|
18559
|
+
}
|
|
18560
|
+
async function updateViaPackageManager(method, current, targetVersion) {
|
|
18561
|
+
if (!SEMVER_PATTERN.test(targetVersion)) {
|
|
18562
|
+
throw new SelfUpdateError(`Invalid version format: ${targetVersion}`);
|
|
18563
|
+
}
|
|
18564
|
+
const spin = createSpinner({ text: `Updating via ${method}...` });
|
|
18565
|
+
spin.start();
|
|
18566
|
+
try {
|
|
18567
|
+
switch (method) {
|
|
18568
|
+
case "npm": {
|
|
18569
|
+
await runPackageManager(["npm", "install", "-g", `ocx@${targetVersion}`]);
|
|
18570
|
+
break;
|
|
18306
18571
|
}
|
|
18307
|
-
|
|
18308
|
-
|
|
18309
|
-
|
|
18310
|
-
|
|
18311
|
-
|
|
18312
|
-
|
|
18572
|
+
case "yarn": {
|
|
18573
|
+
await runPackageManager(["yarn", "global", "add", `ocx@${targetVersion}`]);
|
|
18574
|
+
break;
|
|
18575
|
+
}
|
|
18576
|
+
case "pnpm": {
|
|
18577
|
+
await runPackageManager(["pnpm", "install", "-g", `ocx@${targetVersion}`]);
|
|
18578
|
+
break;
|
|
18579
|
+
}
|
|
18580
|
+
case "bun": {
|
|
18581
|
+
await runPackageManager(["bun", "install", "-g", `ocx@${targetVersion}`]);
|
|
18582
|
+
break;
|
|
18583
|
+
}
|
|
18584
|
+
case "unknown": {
|
|
18585
|
+
throw new SelfUpdateError(`Could not detect install method. Update manually with one of:
|
|
18586
|
+
` + ` npm install -g ocx@latest
|
|
18587
|
+
` + ` pnpm install -g ocx@latest
|
|
18588
|
+
` + " bun install -g ocx@latest");
|
|
18313
18589
|
}
|
|
18314
18590
|
}
|
|
18315
|
-
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
|
|
18319
|
-
|
|
18591
|
+
spin.succeed(`Updated via ${method}`);
|
|
18592
|
+
notifyUpdated(current, targetVersion);
|
|
18593
|
+
} catch (error) {
|
|
18594
|
+
if (error instanceof SelfUpdateError) {
|
|
18595
|
+
spin.fail(`Update failed`);
|
|
18596
|
+
throw error;
|
|
18320
18597
|
}
|
|
18598
|
+
spin.fail(`Update failed`);
|
|
18599
|
+
throw new SelfUpdateError(`Failed to run ${method}: ${error instanceof Error ? error.message : String(error)}`);
|
|
18321
18600
|
}
|
|
18322
18601
|
}
|
|
18323
|
-
|
|
18324
|
-
|
|
18602
|
+
function registerSelfUpdateCommand(parent) {
|
|
18603
|
+
parent.command("update").description("Update OCX to the latest version").option("-f, --force", "Reinstall even if already up to date").option("-m, --method <method>", "Override install method detection (curl|npm|pnpm|bun)").action(wrapAction(async (options2) => {
|
|
18604
|
+
await updateCommand(options2);
|
|
18605
|
+
}));
|
|
18325
18606
|
}
|
|
18326
|
-
|
|
18327
|
-
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
18331
|
-
manifestParts.push(`${file.path}:${hash}`);
|
|
18332
|
-
}
|
|
18333
|
-
return hashContent3(manifestParts.join(`
|
|
18334
|
-
`));
|
|
18607
|
+
|
|
18608
|
+
// src/commands/self/index.ts
|
|
18609
|
+
function registerSelfCommand(program2) {
|
|
18610
|
+
const self = program2.command("self").description("Manage the OCX CLI");
|
|
18611
|
+
registerSelfUpdateCommand(self);
|
|
18335
18612
|
}
|
|
18336
18613
|
|
|
18337
18614
|
// src/self-update/index.ts
|
|
@@ -18362,7 +18639,7 @@ function registerUpdateCheckHook(program2) {
|
|
|
18362
18639
|
});
|
|
18363
18640
|
}
|
|
18364
18641
|
// src/index.ts
|
|
18365
|
-
var version = "1.3.
|
|
18642
|
+
var version = "1.3.2";
|
|
18366
18643
|
async function main2() {
|
|
18367
18644
|
const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
|
|
18368
18645
|
registerInitCommand(program2);
|
|
@@ -18391,4 +18668,4 @@ export {
|
|
|
18391
18668
|
buildRegistry
|
|
18392
18669
|
};
|
|
18393
18670
|
|
|
18394
|
-
//# debugId=
|
|
18671
|
+
//# debugId=B7C7102D18EED5CE64756E2164756E21
|