ocx 1.4.2 → 1.4.3
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 +1027 -324
- package/dist/index.js.map +20 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3949,7 +3949,7 @@ var require_fuzzysort = __commonJS((exports, module) => {
|
|
|
3949
3949
|
results.total = resultsLen + limitedCount;
|
|
3950
3950
|
return results;
|
|
3951
3951
|
};
|
|
3952
|
-
var
|
|
3952
|
+
var highlight2 = (result, open = "<b>", close = "</b>") => {
|
|
3953
3953
|
var callback = typeof open === "function" ? open : undefined;
|
|
3954
3954
|
var target = result.target;
|
|
3955
3955
|
var targetLen = target.length;
|
|
@@ -4019,7 +4019,7 @@ var require_fuzzysort = __commonJS((exports, module) => {
|
|
|
4019
4019
|
return this._indexes = indexes;
|
|
4020
4020
|
}
|
|
4021
4021
|
["highlight"](open, close) {
|
|
4022
|
-
return
|
|
4022
|
+
return highlight2(this, open, close);
|
|
4023
4023
|
}
|
|
4024
4024
|
get ["score"]() {
|
|
4025
4025
|
return normalizeScore(this._score);
|
|
@@ -4510,13 +4510,13 @@ var {
|
|
|
4510
4510
|
// src/commands/add.ts
|
|
4511
4511
|
import { createHash } from "crypto";
|
|
4512
4512
|
import { existsSync as existsSync5 } from "fs";
|
|
4513
|
-
import { mkdir as
|
|
4514
|
-
import { dirname, join as
|
|
4513
|
+
import { mkdir as mkdir5, writeFile } from "fs/promises";
|
|
4514
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
4515
4515
|
|
|
4516
4516
|
// src/schemas/config.ts
|
|
4517
4517
|
import { existsSync } from "fs";
|
|
4518
4518
|
import { mkdir } from "fs/promises";
|
|
4519
|
-
import
|
|
4519
|
+
import path2 from "path";
|
|
4520
4520
|
|
|
4521
4521
|
// ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
4522
4522
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -9815,7 +9815,7 @@ var coerce = {
|
|
|
9815
9815
|
};
|
|
9816
9816
|
var NEVER = INVALID;
|
|
9817
9817
|
// src/schemas/registry.ts
|
|
9818
|
-
import { isAbsolute, normalize } from "path";
|
|
9818
|
+
import { isAbsolute as isAbsolute2, normalize } from "path";
|
|
9819
9819
|
|
|
9820
9820
|
// src/utils/errors.ts
|
|
9821
9821
|
var EXIT_CODES = {
|
|
@@ -9847,9 +9847,15 @@ class NotFoundError extends OCXError {
|
|
|
9847
9847
|
}
|
|
9848
9848
|
|
|
9849
9849
|
class NetworkError extends OCXError {
|
|
9850
|
-
|
|
9850
|
+
url;
|
|
9851
|
+
status;
|
|
9852
|
+
statusText;
|
|
9853
|
+
constructor(message, options) {
|
|
9851
9854
|
super(message, "NETWORK_ERROR", EXIT_CODES.NETWORK);
|
|
9852
9855
|
this.name = "NetworkError";
|
|
9856
|
+
this.url = options?.url;
|
|
9857
|
+
this.status = options?.status;
|
|
9858
|
+
this.statusText = options?.statusText;
|
|
9853
9859
|
}
|
|
9854
9860
|
}
|
|
9855
9861
|
|
|
@@ -9875,6 +9881,9 @@ class ConflictError extends OCXError {
|
|
|
9875
9881
|
}
|
|
9876
9882
|
|
|
9877
9883
|
class IntegrityError extends OCXError {
|
|
9884
|
+
component;
|
|
9885
|
+
expected;
|
|
9886
|
+
found;
|
|
9878
9887
|
constructor(component, expected, found) {
|
|
9879
9888
|
const message = `Integrity verification failed for "${component}"
|
|
9880
9889
|
` + ` Expected: ${expected}
|
|
@@ -9883,6 +9892,9 @@ class IntegrityError extends OCXError {
|
|
|
9883
9892
|
` + `The registry content has changed since this component was locked.
|
|
9884
9893
|
` + `Use 'ocx update ${component}' to intentionally update this component.`;
|
|
9885
9894
|
super(message, "INTEGRITY_ERROR", EXIT_CODES.INTEGRITY);
|
|
9895
|
+
this.component = component;
|
|
9896
|
+
this.expected = expected;
|
|
9897
|
+
this.found = found;
|
|
9886
9898
|
this.name = "IntegrityError";
|
|
9887
9899
|
}
|
|
9888
9900
|
}
|
|
@@ -9902,15 +9914,19 @@ class OcxConfigError extends OCXError {
|
|
|
9902
9914
|
}
|
|
9903
9915
|
|
|
9904
9916
|
class ProfileNotFoundError extends OCXError {
|
|
9905
|
-
|
|
9906
|
-
|
|
9917
|
+
profile;
|
|
9918
|
+
constructor(profile) {
|
|
9919
|
+
super(`Profile "${profile}" not found`, "NOT_FOUND", EXIT_CODES.NOT_FOUND);
|
|
9920
|
+
this.profile = profile;
|
|
9907
9921
|
this.name = "ProfileNotFoundError";
|
|
9908
9922
|
}
|
|
9909
9923
|
}
|
|
9910
9924
|
|
|
9911
9925
|
class ProfileExistsError extends OCXError {
|
|
9912
|
-
|
|
9913
|
-
|
|
9926
|
+
profile;
|
|
9927
|
+
constructor(profile) {
|
|
9928
|
+
super(`Profile "${profile}" already exists. Use --force to overwrite.`, "CONFLICT", EXIT_CODES.CONFLICT);
|
|
9929
|
+
this.profile = profile;
|
|
9914
9930
|
this.name = "ProfileExistsError";
|
|
9915
9931
|
}
|
|
9916
9932
|
}
|
|
@@ -9937,8 +9953,12 @@ class RegistryExistsError extends OCXError {
|
|
|
9937
9953
|
}
|
|
9938
9954
|
|
|
9939
9955
|
class InvalidProfileNameError extends OCXError {
|
|
9940
|
-
|
|
9941
|
-
|
|
9956
|
+
profile;
|
|
9957
|
+
reason;
|
|
9958
|
+
constructor(profile, reason) {
|
|
9959
|
+
super(`Invalid profile name "${profile}": ${reason}`, "VALIDATION_ERROR", EXIT_CODES.GENERAL);
|
|
9960
|
+
this.profile = profile;
|
|
9961
|
+
this.reason = reason;
|
|
9942
9962
|
this.name = "InvalidProfileNameError";
|
|
9943
9963
|
}
|
|
9944
9964
|
}
|
|
@@ -9950,6 +9970,68 @@ class ProfilesNotInitializedError extends OCXError {
|
|
|
9950
9970
|
}
|
|
9951
9971
|
}
|
|
9952
9972
|
|
|
9973
|
+
// src/utils/path-security.ts
|
|
9974
|
+
import * as path from "path";
|
|
9975
|
+
|
|
9976
|
+
class PathValidationError extends Error {
|
|
9977
|
+
attemptedPath;
|
|
9978
|
+
reason;
|
|
9979
|
+
constructor(message, attemptedPath, reason) {
|
|
9980
|
+
super(message);
|
|
9981
|
+
this.attemptedPath = attemptedPath;
|
|
9982
|
+
this.reason = reason;
|
|
9983
|
+
this.name = "PathValidationError";
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
var WINDOWS_RESERVED = new Set([
|
|
9987
|
+
"CON",
|
|
9988
|
+
"PRN",
|
|
9989
|
+
"AUX",
|
|
9990
|
+
"NUL",
|
|
9991
|
+
"COM1",
|
|
9992
|
+
"COM2",
|
|
9993
|
+
"COM3",
|
|
9994
|
+
"COM4",
|
|
9995
|
+
"COM5",
|
|
9996
|
+
"COM6",
|
|
9997
|
+
"COM7",
|
|
9998
|
+
"COM8",
|
|
9999
|
+
"COM9",
|
|
10000
|
+
"LPT1",
|
|
10001
|
+
"LPT2",
|
|
10002
|
+
"LPT3",
|
|
10003
|
+
"LPT4",
|
|
10004
|
+
"LPT5",
|
|
10005
|
+
"LPT6",
|
|
10006
|
+
"LPT7",
|
|
10007
|
+
"LPT8",
|
|
10008
|
+
"LPT9"
|
|
10009
|
+
]);
|
|
10010
|
+
function validatePath(basePath, userPath) {
|
|
10011
|
+
if (userPath.includes("\x00")) {
|
|
10012
|
+
throw new PathValidationError("Path contains null bytes", userPath, "null_byte");
|
|
10013
|
+
}
|
|
10014
|
+
if (path.isAbsolute(userPath) || path.win32.isAbsolute(userPath)) {
|
|
10015
|
+
throw new PathValidationError("Path must be relative", userPath, "absolute_path");
|
|
10016
|
+
}
|
|
10017
|
+
if (/^[a-zA-Z]:/.test(userPath) || userPath.startsWith("\\\\")) {
|
|
10018
|
+
throw new PathValidationError("Path contains Windows absolute", userPath, "windows_absolute");
|
|
10019
|
+
}
|
|
10020
|
+
const baseName = path.basename(userPath).toUpperCase().split(".")[0] ?? "";
|
|
10021
|
+
if (WINDOWS_RESERVED.has(baseName)) {
|
|
10022
|
+
throw new PathValidationError("Path uses Windows reserved name", userPath, "windows_reserved");
|
|
10023
|
+
}
|
|
10024
|
+
const normalized = userPath.normalize("NFC");
|
|
10025
|
+
const unified = normalized.replace(/\\/g, "/");
|
|
10026
|
+
const resolvedBase = path.resolve(basePath);
|
|
10027
|
+
const resolvedCombined = path.resolve(resolvedBase, unified);
|
|
10028
|
+
const relativePath = path.relative(resolvedBase, resolvedCombined);
|
|
10029
|
+
if (relativePath.startsWith("../") || relativePath.startsWith("..\\") || relativePath === ".." || path.isAbsolute(relativePath)) {
|
|
10030
|
+
throw new PathValidationError("Path escapes base directory", userPath, "path_traversal");
|
|
10031
|
+
}
|
|
10032
|
+
return resolvedCombined;
|
|
10033
|
+
}
|
|
10034
|
+
|
|
9953
10035
|
// src/schemas/registry.ts
|
|
9954
10036
|
var npmSpecifierSchema = exports_external.string().refine((val) => val.startsWith("npm:"), {
|
|
9955
10037
|
message: 'npm specifier must start with "npm:" prefix'
|
|
@@ -9999,11 +10081,11 @@ var componentTypeSchema = exports_external.enum([
|
|
|
9999
10081
|
"ocx:bundle",
|
|
10000
10082
|
"ocx:profile"
|
|
10001
10083
|
]);
|
|
10002
|
-
var
|
|
10003
|
-
var targetPathSchema = exports_external.string().refine((
|
|
10084
|
+
var PROFILE_RESERVED_TARGETS = new Set(["ocx.lock", ".opencode"]);
|
|
10085
|
+
var targetPathSchema = exports_external.string().refine((path2) => path2.startsWith(".opencode/"), {
|
|
10004
10086
|
message: 'Target path must start with ".opencode/"'
|
|
10005
|
-
}).refine((
|
|
10006
|
-
const parts =
|
|
10087
|
+
}).refine((path2) => {
|
|
10088
|
+
const parts = path2.split("/");
|
|
10007
10089
|
const dir = parts[1];
|
|
10008
10090
|
if (!dir)
|
|
10009
10091
|
return false;
|
|
@@ -10132,7 +10214,7 @@ var componentManifestSchema = exports_external.object({
|
|
|
10132
10214
|
opencode: opencodeConfigSchema.optional()
|
|
10133
10215
|
});
|
|
10134
10216
|
function validateSafePath(filePath) {
|
|
10135
|
-
if (
|
|
10217
|
+
if (isAbsolute2(filePath)) {
|
|
10136
10218
|
throw new ValidationError(`Invalid path: "${filePath}" - absolute paths not allowed`);
|
|
10137
10219
|
}
|
|
10138
10220
|
if (filePath.startsWith("~")) {
|
|
@@ -10149,17 +10231,18 @@ function inferTargetPath(sourcePath) {
|
|
|
10149
10231
|
function validateFileTarget(target, componentType) {
|
|
10150
10232
|
const isProfile = componentType === "ocx:profile";
|
|
10151
10233
|
if (isProfile) {
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
if (!isProfileFile && !isOpencodeTarget) {
|
|
10155
|
-
throw new ValidationError(`Invalid profile target: "${target}". ` + `Must be a profile file (ocx.jsonc, opencode.jsonc, AGENTS.md) or start with ".opencode/"`);
|
|
10234
|
+
if (PROFILE_RESERVED_TARGETS.has(target)) {
|
|
10235
|
+
throw new ValidationError(`Target "${target}" is reserved for installer use`);
|
|
10156
10236
|
}
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10237
|
+
try {
|
|
10238
|
+
validatePath("/dummy/base", target);
|
|
10239
|
+
} catch (error) {
|
|
10240
|
+
if (error instanceof PathValidationError) {
|
|
10241
|
+
throw new ValidationError(`Invalid profile target "${target}": ${error.message}`);
|
|
10161
10242
|
}
|
|
10243
|
+
throw error;
|
|
10162
10244
|
}
|
|
10245
|
+
return;
|
|
10163
10246
|
} else {
|
|
10164
10247
|
const parseResult = targetPathSchema.safeParse(target);
|
|
10165
10248
|
if (!parseResult.success) {
|
|
@@ -10291,8 +10374,8 @@ var CONFIG_FILE = "ocx.jsonc";
|
|
|
10291
10374
|
var LOCK_FILE = "ocx.lock";
|
|
10292
10375
|
var LOCAL_CONFIG_DIR = ".opencode";
|
|
10293
10376
|
function findOcxConfig(cwd) {
|
|
10294
|
-
const dotOpencodePath =
|
|
10295
|
-
const rootPath =
|
|
10377
|
+
const dotOpencodePath = path2.join(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE);
|
|
10378
|
+
const rootPath = path2.join(cwd, CONFIG_FILE);
|
|
10296
10379
|
const dotOpencodeExists = existsSync(dotOpencodePath);
|
|
10297
10380
|
const rootExists = existsSync(rootPath);
|
|
10298
10381
|
if (dotOpencodeExists && rootExists) {
|
|
@@ -10307,8 +10390,8 @@ function findOcxConfig(cwd) {
|
|
|
10307
10390
|
return { path: dotOpencodePath, exists: false };
|
|
10308
10391
|
}
|
|
10309
10392
|
function findOcxLock(cwd, options) {
|
|
10310
|
-
const dotOpencodePath =
|
|
10311
|
-
const rootPath =
|
|
10393
|
+
const dotOpencodePath = path2.join(cwd, LOCAL_CONFIG_DIR, LOCK_FILE);
|
|
10394
|
+
const rootPath = path2.join(cwd, LOCK_FILE);
|
|
10312
10395
|
if (options?.isFlattened) {
|
|
10313
10396
|
if (existsSync(rootPath)) {
|
|
10314
10397
|
return { path: rootPath, exists: true };
|
|
@@ -10339,8 +10422,8 @@ async function readOcxConfig(cwd) {
|
|
|
10339
10422
|
}
|
|
10340
10423
|
}
|
|
10341
10424
|
async function writeOcxConfig(cwd, config, existingPath) {
|
|
10342
|
-
const configPath = existingPath ??
|
|
10343
|
-
await mkdir(
|
|
10425
|
+
const configPath = existingPath ?? path2.join(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE);
|
|
10426
|
+
await mkdir(path2.dirname(configPath), { recursive: true });
|
|
10344
10427
|
const content = JSON.stringify(config, null, 2);
|
|
10345
10428
|
await Bun.write(configPath, content);
|
|
10346
10429
|
}
|
|
@@ -10355,8 +10438,8 @@ async function readOcxLock(cwd, options) {
|
|
|
10355
10438
|
return ocxLockSchema.parse(json);
|
|
10356
10439
|
}
|
|
10357
10440
|
async function writeOcxLock(cwd, lock, existingPath) {
|
|
10358
|
-
const lockPath = existingPath ??
|
|
10359
|
-
await mkdir(
|
|
10441
|
+
const lockPath = existingPath ?? path2.join(cwd, LOCAL_CONFIG_DIR, LOCK_FILE);
|
|
10442
|
+
await mkdir(path2.dirname(lockPath), { recursive: true });
|
|
10360
10443
|
const content = JSON.stringify(lock, null, 2);
|
|
10361
10444
|
await Bun.write(lockPath, content);
|
|
10362
10445
|
}
|
|
@@ -10364,10 +10447,10 @@ async function writeOcxLock(cwd, lock, existingPath) {
|
|
|
10364
10447
|
// src/utils/paths.ts
|
|
10365
10448
|
import { stat } from "fs/promises";
|
|
10366
10449
|
import { homedir } from "os";
|
|
10367
|
-
import { isAbsolute as
|
|
10450
|
+
import { isAbsolute as isAbsolute3, join } from "path";
|
|
10368
10451
|
function getGlobalConfigPath() {
|
|
10369
10452
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
10370
|
-
const base = xdg &&
|
|
10453
|
+
const base = xdg && isAbsolute3(xdg) ? xdg : join(homedir(), ".config");
|
|
10371
10454
|
return join(base, "opencode");
|
|
10372
10455
|
}
|
|
10373
10456
|
async function globalDirectoryExists() {
|
|
@@ -10436,19 +10519,19 @@ class GlobalConfigProvider {
|
|
|
10436
10519
|
|
|
10437
10520
|
// src/config/resolver.ts
|
|
10438
10521
|
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
10439
|
-
import { join as join2, relative } from "path";
|
|
10522
|
+
import { join as join2, relative as relative2 } from "path";
|
|
10440
10523
|
var {Glob: Glob2 } = globalThis.Bun;
|
|
10441
10524
|
|
|
10442
10525
|
// src/profile/manager.ts
|
|
10443
|
-
import { mkdir as mkdir2, readdir, rm, stat as stat2 } from "fs/promises";
|
|
10526
|
+
import { mkdir as mkdir2, readdir, rename as rename2, rm, stat as stat2 } from "fs/promises";
|
|
10444
10527
|
|
|
10445
10528
|
// src/schemas/ocx.ts
|
|
10446
10529
|
var {Glob } = globalThis.Bun;
|
|
10447
10530
|
|
|
10448
10531
|
// src/utils/path-helpers.ts
|
|
10449
|
-
import
|
|
10532
|
+
import path3 from "path";
|
|
10450
10533
|
function isAbsolutePath(p) {
|
|
10451
|
-
return
|
|
10534
|
+
return path3.posix.isAbsolute(p) || path3.win32.isAbsolute(p);
|
|
10452
10535
|
}
|
|
10453
10536
|
|
|
10454
10537
|
// src/schemas/common.ts
|
|
@@ -10470,7 +10553,6 @@ var profileOcxConfigSchema = exports_external.object({
|
|
|
10470
10553
|
componentPath: safeRelativePathSchema.optional(),
|
|
10471
10554
|
renameWindow: exports_external.boolean().default(true).describe("Set terminal/tmux window name when launching OpenCode"),
|
|
10472
10555
|
exclude: exports_external.array(globPatternSchema).default([
|
|
10473
|
-
"**/AGENTS.md",
|
|
10474
10556
|
"**/CLAUDE.md",
|
|
10475
10557
|
"**/CONTEXT.md",
|
|
10476
10558
|
"**/.opencode/**",
|
|
@@ -10498,38 +10580,38 @@ async function atomicWrite(filePath, data) {
|
|
|
10498
10580
|
// src/profile/paths.ts
|
|
10499
10581
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
10500
10582
|
import { homedir as homedir2 } from "os";
|
|
10501
|
-
import
|
|
10583
|
+
import path4 from "path";
|
|
10502
10584
|
var OCX_CONFIG_FILE = "ocx.jsonc";
|
|
10503
10585
|
var OPENCODE_CONFIG_FILE = "opencode.jsonc";
|
|
10504
10586
|
var LOCAL_CONFIG_DIR2 = ".opencode";
|
|
10505
10587
|
function getProfilesDir() {
|
|
10506
|
-
const base = process.env.XDG_CONFIG_HOME ||
|
|
10507
|
-
return
|
|
10588
|
+
const base = process.env.XDG_CONFIG_HOME || path4.join(homedir2(), ".config");
|
|
10589
|
+
return path4.join(base, "opencode", "profiles");
|
|
10508
10590
|
}
|
|
10509
10591
|
function getProfileDir(name) {
|
|
10510
|
-
return
|
|
10592
|
+
return path4.join(getProfilesDir(), name);
|
|
10511
10593
|
}
|
|
10512
10594
|
function getProfileOcxConfig(name) {
|
|
10513
|
-
return
|
|
10595
|
+
return path4.join(getProfileDir(name), "ocx.jsonc");
|
|
10514
10596
|
}
|
|
10515
10597
|
function getProfileOpencodeConfig(name) {
|
|
10516
|
-
return
|
|
10598
|
+
return path4.join(getProfileDir(name), "opencode.jsonc");
|
|
10517
10599
|
}
|
|
10518
10600
|
function getProfileAgents(name) {
|
|
10519
|
-
return
|
|
10601
|
+
return path4.join(getProfileDir(name), "AGENTS.md");
|
|
10520
10602
|
}
|
|
10521
10603
|
function findLocalConfigDir(cwd) {
|
|
10522
10604
|
let currentDir = cwd;
|
|
10523
10605
|
while (true) {
|
|
10524
|
-
const configDir =
|
|
10606
|
+
const configDir = path4.join(currentDir, LOCAL_CONFIG_DIR2);
|
|
10525
10607
|
if (existsSync2(configDir) && statSync(configDir).isDirectory()) {
|
|
10526
10608
|
return configDir;
|
|
10527
10609
|
}
|
|
10528
|
-
const gitDir =
|
|
10610
|
+
const gitDir = path4.join(currentDir, ".git");
|
|
10529
10611
|
if (existsSync2(gitDir)) {
|
|
10530
10612
|
return null;
|
|
10531
10613
|
}
|
|
10532
|
-
const parentDir =
|
|
10614
|
+
const parentDir = path4.dirname(currentDir);
|
|
10533
10615
|
if (parentDir === currentDir) {
|
|
10534
10616
|
return null;
|
|
10535
10617
|
}
|
|
@@ -10537,8 +10619,8 @@ function findLocalConfigDir(cwd) {
|
|
|
10537
10619
|
}
|
|
10538
10620
|
}
|
|
10539
10621
|
function getGlobalConfig() {
|
|
10540
|
-
const base = process.env.XDG_CONFIG_HOME ||
|
|
10541
|
-
return
|
|
10622
|
+
const base = process.env.XDG_CONFIG_HOME || path4.join(homedir2(), ".config");
|
|
10623
|
+
return path4.join(base, "opencode", "ocx.jsonc");
|
|
10542
10624
|
}
|
|
10543
10625
|
|
|
10544
10626
|
// src/profile/schema.ts
|
|
@@ -10556,7 +10638,6 @@ var DEFAULT_OCX_CONFIG = {
|
|
|
10556
10638
|
registries: {},
|
|
10557
10639
|
renameWindow: true,
|
|
10558
10640
|
exclude: [
|
|
10559
|
-
"**/AGENTS.md",
|
|
10560
10641
|
"**/CLAUDE.md",
|
|
10561
10642
|
"**/CONTEXT.md",
|
|
10562
10643
|
"**/.opencode/**",
|
|
@@ -10565,6 +10646,21 @@ var DEFAULT_OCX_CONFIG = {
|
|
|
10565
10646
|
],
|
|
10566
10647
|
include: []
|
|
10567
10648
|
};
|
|
10649
|
+
var DEFAULT_OCX_CONFIG_TEMPLATE = `{
|
|
10650
|
+
"$schema": "https://ocx.kdco.dev/schemas/ocx.json",
|
|
10651
|
+
"registries": {},
|
|
10652
|
+
"renameWindow": true,
|
|
10653
|
+
"exclude": [
|
|
10654
|
+
// "**/AGENTS.md",
|
|
10655
|
+
"**/CLAUDE.md",
|
|
10656
|
+
"**/CONTEXT.md",
|
|
10657
|
+
"**/.opencode/**",
|
|
10658
|
+
"**/opencode.jsonc",
|
|
10659
|
+
"**/opencode.json"
|
|
10660
|
+
],
|
|
10661
|
+
"include": []
|
|
10662
|
+
}
|
|
10663
|
+
`;
|
|
10568
10664
|
|
|
10569
10665
|
class ProfileManager {
|
|
10570
10666
|
profilesDir;
|
|
@@ -10650,7 +10746,7 @@ class ProfileManager {
|
|
|
10650
10746
|
const ocxPath = getProfileOcxConfig(name);
|
|
10651
10747
|
const ocxFile = Bun.file(ocxPath);
|
|
10652
10748
|
if (!await ocxFile.exists()) {
|
|
10653
|
-
await
|
|
10749
|
+
await Bun.write(ocxPath, DEFAULT_OCX_CONFIG_TEMPLATE, { mode: 384 });
|
|
10654
10750
|
}
|
|
10655
10751
|
const opencodePath = getProfileOpencodeConfig(name);
|
|
10656
10752
|
const opencodeFile = Bun.file(opencodePath);
|
|
@@ -10679,6 +10775,44 @@ class ProfileManager {
|
|
|
10679
10775
|
const dir = getProfileDir(name);
|
|
10680
10776
|
await rm(dir, { recursive: true });
|
|
10681
10777
|
}
|
|
10778
|
+
async move(oldName, newName) {
|
|
10779
|
+
const oldResult = profileNameSchema.safeParse(oldName);
|
|
10780
|
+
if (!oldResult.success) {
|
|
10781
|
+
throw new InvalidProfileNameError(oldName, oldResult.error.errors[0]?.message ?? "Invalid name");
|
|
10782
|
+
}
|
|
10783
|
+
const newResult = profileNameSchema.safeParse(newName);
|
|
10784
|
+
if (!newResult.success) {
|
|
10785
|
+
throw new InvalidProfileNameError(newName, newResult.error.errors[0]?.message ?? "Invalid name");
|
|
10786
|
+
}
|
|
10787
|
+
await this.ensureInitialized();
|
|
10788
|
+
if (!await this.exists(oldName)) {
|
|
10789
|
+
throw new ProfileNotFoundError(oldName);
|
|
10790
|
+
}
|
|
10791
|
+
if (oldName === newName) {
|
|
10792
|
+
return { warnActiveProfile: false };
|
|
10793
|
+
}
|
|
10794
|
+
if (await this.exists(newName)) {
|
|
10795
|
+
throw new ConflictError(`Cannot move: profile "${newName}" already exists. Remove it first with 'ocx p rm ${newName}'.`);
|
|
10796
|
+
}
|
|
10797
|
+
const warnActiveProfile = process.env.OCX_PROFILE === oldName;
|
|
10798
|
+
const oldDir = getProfileDir(oldName);
|
|
10799
|
+
const newDir = getProfileDir(newName);
|
|
10800
|
+
try {
|
|
10801
|
+
await rename2(oldDir, newDir);
|
|
10802
|
+
} catch (error) {
|
|
10803
|
+
if (error instanceof Error && "code" in error) {
|
|
10804
|
+
const code = error.code;
|
|
10805
|
+
if (code === "EEXIST" || code === "ENOTEMPTY") {
|
|
10806
|
+
throw new ConflictError(`Cannot move: profile "${newName}" already exists. Remove it first with 'ocx p rm ${newName}'.`);
|
|
10807
|
+
}
|
|
10808
|
+
if (code === "ENOENT") {
|
|
10809
|
+
throw new ProfileNotFoundError(oldName);
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
10812
|
+
throw error;
|
|
10813
|
+
}
|
|
10814
|
+
return { warnActiveProfile };
|
|
10815
|
+
}
|
|
10682
10816
|
async resolveProfile(override) {
|
|
10683
10817
|
if (override) {
|
|
10684
10818
|
if (!await this.exists(override)) {
|
|
@@ -10728,7 +10862,7 @@ function discoverInstructionFiles(projectDir, gitRoot) {
|
|
|
10728
10862
|
for (const filename of INSTRUCTION_FILES) {
|
|
10729
10863
|
const filePath = join2(currentDir, filename);
|
|
10730
10864
|
if (existsSync3(filePath) && statSync2(filePath).isFile()) {
|
|
10731
|
-
const relativePath =
|
|
10865
|
+
const relativePath = relative2(root, filePath);
|
|
10732
10866
|
discovered.push(relativePath);
|
|
10733
10867
|
}
|
|
10734
10868
|
}
|
|
@@ -10889,7 +11023,7 @@ class ConfigResolver {
|
|
|
10889
11023
|
return true;
|
|
10890
11024
|
const gitRoot = detectGitRoot(this.cwd);
|
|
10891
11025
|
const root = gitRoot ?? this.cwd;
|
|
10892
|
-
const relativePath =
|
|
11026
|
+
const relativePath = relative2(root, this.localConfigDir);
|
|
10893
11027
|
const exclude = this.profile.ocx.exclude ?? [];
|
|
10894
11028
|
const include = this.profile.ocx.include ?? [];
|
|
10895
11029
|
for (const pattern of include) {
|
|
@@ -10982,7 +11116,7 @@ class ConfigResolver {
|
|
|
10982
11116
|
// package.json
|
|
10983
11117
|
var package_default = {
|
|
10984
11118
|
name: "ocx",
|
|
10985
|
-
version: "1.4.
|
|
11119
|
+
version: "1.4.3",
|
|
10986
11120
|
description: "OCX CLI - ShadCN-style registry for OpenCode extensions. Install agents, plugins, skills, and MCP servers.",
|
|
10987
11121
|
author: "kdcokenny",
|
|
10988
11122
|
license: "MIT",
|
|
@@ -11061,14 +11195,28 @@ async function fetchWithCache(url, parse3) {
|
|
|
11061
11195
|
return cached;
|
|
11062
11196
|
}
|
|
11063
11197
|
const promise = (async () => {
|
|
11064
|
-
|
|
11198
|
+
let response;
|
|
11199
|
+
try {
|
|
11200
|
+
response = await fetch(url);
|
|
11201
|
+
} catch (error) {
|
|
11202
|
+
throw new NetworkError(`Network request failed for ${url}: ${error instanceof Error ? error.message : String(error)}`, { url });
|
|
11203
|
+
}
|
|
11065
11204
|
if (!response.ok) {
|
|
11066
11205
|
if (response.status === 404) {
|
|
11067
11206
|
throw new NotFoundError(`Not found: ${url}`);
|
|
11068
11207
|
}
|
|
11069
|
-
throw new NetworkError(`Failed to fetch ${url}: ${response.status} ${response.statusText}
|
|
11208
|
+
throw new NetworkError(`Failed to fetch ${url}: ${response.status} ${response.statusText}`, {
|
|
11209
|
+
url,
|
|
11210
|
+
status: response.status,
|
|
11211
|
+
statusText: response.statusText
|
|
11212
|
+
});
|
|
11213
|
+
}
|
|
11214
|
+
let data;
|
|
11215
|
+
try {
|
|
11216
|
+
data = await response.json();
|
|
11217
|
+
} catch (error) {
|
|
11218
|
+
throw new NetworkError(`Invalid JSON response from ${url}: ${error instanceof Error ? error.message : String(error)}`, { url });
|
|
11070
11219
|
}
|
|
11071
|
-
const data = await response.json();
|
|
11072
11220
|
return parse3(data);
|
|
11073
11221
|
})();
|
|
11074
11222
|
cache.set(url, promise);
|
|
@@ -11115,9 +11263,14 @@ async function fetchComponentVersion(baseUrl, name, version) {
|
|
|
11115
11263
|
}
|
|
11116
11264
|
async function fetchFileContent(baseUrl, componentName, filePath) {
|
|
11117
11265
|
const url = `${baseUrl.replace(/\/$/, "")}/components/${componentName}/${filePath}`;
|
|
11118
|
-
|
|
11266
|
+
let response;
|
|
11267
|
+
try {
|
|
11268
|
+
response = await fetch(url);
|
|
11269
|
+
} catch (error) {
|
|
11270
|
+
throw new NetworkError(`Network request failed for ${url}: ${error instanceof Error ? error.message : String(error)}`, { url });
|
|
11271
|
+
}
|
|
11119
11272
|
if (!response.ok) {
|
|
11120
|
-
throw new NetworkError(`Failed to fetch file ${filePath} for ${componentName}: ${response.status} ${response.statusText}
|
|
11273
|
+
throw new NetworkError(`Failed to fetch file ${filePath} for ${componentName} from ${url}: ${response.status} ${response.statusText}`, { url, status: response.status, statusText: response.statusText });
|
|
11121
11274
|
}
|
|
11122
11275
|
return response.text();
|
|
11123
11276
|
}
|
|
@@ -11192,13 +11345,13 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11192
11345
|
const npmDeps = new Set;
|
|
11193
11346
|
const npmDevDeps = new Set;
|
|
11194
11347
|
let opencode = {};
|
|
11195
|
-
async function
|
|
11348
|
+
async function resolve2(componentNamespace, componentName, path5 = []) {
|
|
11196
11349
|
const qualifiedName = createQualifiedComponent(componentNamespace, componentName);
|
|
11197
11350
|
if (resolved.has(qualifiedName)) {
|
|
11198
11351
|
return;
|
|
11199
11352
|
}
|
|
11200
11353
|
if (visiting.has(qualifiedName)) {
|
|
11201
|
-
const cycle = [...
|
|
11354
|
+
const cycle = [...path5, qualifiedName].join(" \u2192 ");
|
|
11202
11355
|
throw new ValidationError(`Circular dependency detected: ${cycle}`);
|
|
11203
11356
|
}
|
|
11204
11357
|
visiting.add(qualifiedName);
|
|
@@ -11209,12 +11362,21 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11209
11362
|
let component;
|
|
11210
11363
|
try {
|
|
11211
11364
|
component = await fetchComponent(regConfig.url, componentName);
|
|
11212
|
-
} catch (
|
|
11213
|
-
|
|
11365
|
+
} catch (err) {
|
|
11366
|
+
if (err instanceof NetworkError) {
|
|
11367
|
+
throw err;
|
|
11368
|
+
}
|
|
11369
|
+
if (err instanceof NotFoundError) {
|
|
11370
|
+
throw new NotFoundError(`Component '${componentName}' not found in registry '${componentNamespace}'.`);
|
|
11371
|
+
}
|
|
11372
|
+
if (err instanceof OCXError) {
|
|
11373
|
+
throw err;
|
|
11374
|
+
}
|
|
11375
|
+
throw new NetworkError(`Failed to fetch component '${componentName}' from registry '${componentNamespace}': ${err instanceof Error ? err.message : String(err)}`, { url: regConfig.url });
|
|
11214
11376
|
}
|
|
11215
11377
|
for (const dep of component.dependencies) {
|
|
11216
11378
|
const depRef = parseComponentRef(dep, componentNamespace);
|
|
11217
|
-
await
|
|
11379
|
+
await resolve2(depRef.namespace, depRef.component, [...path5, qualifiedName]);
|
|
11218
11380
|
}
|
|
11219
11381
|
const normalizedComponent = normalizeComponentManifest(component);
|
|
11220
11382
|
resolved.set(qualifiedName, {
|
|
@@ -11241,7 +11403,7 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11241
11403
|
}
|
|
11242
11404
|
for (const name of componentNames) {
|
|
11243
11405
|
const ref = parseComponentRef(name);
|
|
11244
|
-
await
|
|
11406
|
+
await resolve2(ref.namespace, ref.component);
|
|
11245
11407
|
}
|
|
11246
11408
|
const components = Array.from(resolved.values());
|
|
11247
11409
|
const installOrder = Array.from(resolved.keys());
|
|
@@ -11258,17 +11420,17 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11258
11420
|
import { existsSync as existsSync4 } from "fs";
|
|
11259
11421
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
11260
11422
|
import { homedir as homedir3 } from "os";
|
|
11261
|
-
import
|
|
11423
|
+
import path5 from "path";
|
|
11262
11424
|
var LOCAL_CONFIG_DIR3 = ".opencode";
|
|
11263
11425
|
function isGlobalConfigPath(cwd) {
|
|
11264
|
-
const base = process.env.XDG_CONFIG_HOME ||
|
|
11265
|
-
const globalConfigDir =
|
|
11266
|
-
const resolvedCwd =
|
|
11426
|
+
const base = process.env.XDG_CONFIG_HOME || path5.join(homedir3(), ".config");
|
|
11427
|
+
const globalConfigDir = path5.resolve(base, "opencode");
|
|
11428
|
+
const resolvedCwd = path5.resolve(cwd);
|
|
11267
11429
|
if (resolvedCwd === globalConfigDir) {
|
|
11268
11430
|
return true;
|
|
11269
11431
|
}
|
|
11270
|
-
const
|
|
11271
|
-
return
|
|
11432
|
+
const relative3 = path5.relative(globalConfigDir, resolvedCwd);
|
|
11433
|
+
return relative3 !== "" && !relative3.startsWith("..") && !path5.isAbsolute(relative3);
|
|
11272
11434
|
}
|
|
11273
11435
|
var JSONC_OPTIONS = {
|
|
11274
11436
|
formattingOptions: {
|
|
@@ -11285,8 +11447,8 @@ var OPENCODE_CONFIG_TEMPLATE = `{
|
|
|
11285
11447
|
`;
|
|
11286
11448
|
function findOpencodeConfig(cwd) {
|
|
11287
11449
|
if (isGlobalConfigPath(cwd)) {
|
|
11288
|
-
const rootJsonc2 =
|
|
11289
|
-
const rootJson2 =
|
|
11450
|
+
const rootJsonc2 = path5.join(cwd, "opencode.jsonc");
|
|
11451
|
+
const rootJson2 = path5.join(cwd, "opencode.json");
|
|
11290
11452
|
if (existsSync4(rootJsonc2)) {
|
|
11291
11453
|
return { path: rootJsonc2, exists: true };
|
|
11292
11454
|
}
|
|
@@ -11295,16 +11457,16 @@ function findOpencodeConfig(cwd) {
|
|
|
11295
11457
|
}
|
|
11296
11458
|
return { path: rootJsonc2, exists: false };
|
|
11297
11459
|
}
|
|
11298
|
-
const dotOpencodeJsonc =
|
|
11299
|
-
const dotOpencodeJson =
|
|
11460
|
+
const dotOpencodeJsonc = path5.join(cwd, LOCAL_CONFIG_DIR3, "opencode.jsonc");
|
|
11461
|
+
const dotOpencodeJson = path5.join(cwd, LOCAL_CONFIG_DIR3, "opencode.json");
|
|
11300
11462
|
if (existsSync4(dotOpencodeJsonc)) {
|
|
11301
11463
|
return { path: dotOpencodeJsonc, exists: true };
|
|
11302
11464
|
}
|
|
11303
11465
|
if (existsSync4(dotOpencodeJson)) {
|
|
11304
11466
|
return { path: dotOpencodeJson, exists: true };
|
|
11305
11467
|
}
|
|
11306
|
-
const rootJsonc =
|
|
11307
|
-
const rootJson =
|
|
11468
|
+
const rootJsonc = path5.join(cwd, "opencode.jsonc");
|
|
11469
|
+
const rootJson = path5.join(cwd, "opencode.json");
|
|
11308
11470
|
if (existsSync4(rootJsonc)) {
|
|
11309
11471
|
return { path: rootJsonc, exists: true };
|
|
11310
11472
|
}
|
|
@@ -11318,7 +11480,7 @@ async function ensureOpencodeConfig(cwd) {
|
|
|
11318
11480
|
if (exists) {
|
|
11319
11481
|
return { path: configPath, created: false };
|
|
11320
11482
|
}
|
|
11321
|
-
await mkdir3(
|
|
11483
|
+
await mkdir3(path5.dirname(configPath), { recursive: true });
|
|
11322
11484
|
await Bun.write(configPath, OPENCODE_CONFIG_TEMPLATE);
|
|
11323
11485
|
return { path: configPath, created: true };
|
|
11324
11486
|
}
|
|
@@ -11335,13 +11497,13 @@ async function readOpencodeJsonConfig(cwd) {
|
|
|
11335
11497
|
path: configPath
|
|
11336
11498
|
};
|
|
11337
11499
|
}
|
|
11338
|
-
async function writeOpencodeJsonConfig(
|
|
11339
|
-
await Bun.write(
|
|
11500
|
+
async function writeOpencodeJsonConfig(path6, content) {
|
|
11501
|
+
await Bun.write(path6, content);
|
|
11340
11502
|
}
|
|
11341
|
-
function getValueAtPath(content,
|
|
11503
|
+
function getValueAtPath(content, path6) {
|
|
11342
11504
|
const parsed = parse2(content, [], { allowTrailingComma: true });
|
|
11343
11505
|
let current = parsed;
|
|
11344
|
-
for (const segment of
|
|
11506
|
+
for (const segment of path6) {
|
|
11345
11507
|
if (current === null || current === undefined)
|
|
11346
11508
|
return;
|
|
11347
11509
|
if (typeof current !== "object")
|
|
@@ -11350,27 +11512,27 @@ function getValueAtPath(content, path5) {
|
|
|
11350
11512
|
}
|
|
11351
11513
|
return current;
|
|
11352
11514
|
}
|
|
11353
|
-
function applyValueAtPath(content,
|
|
11515
|
+
function applyValueAtPath(content, path6, value) {
|
|
11354
11516
|
if (value === null || value === undefined) {
|
|
11355
11517
|
return content;
|
|
11356
11518
|
}
|
|
11357
11519
|
if (typeof value === "object" && !Array.isArray(value)) {
|
|
11358
|
-
const existingValue = getValueAtPath(content,
|
|
11520
|
+
const existingValue = getValueAtPath(content, path6);
|
|
11359
11521
|
if (existingValue !== undefined && (existingValue === null || typeof existingValue !== "object")) {
|
|
11360
|
-
const edits2 = modify(content,
|
|
11522
|
+
const edits2 = modify(content, path6, value, JSONC_OPTIONS);
|
|
11361
11523
|
return applyEdits(content, edits2);
|
|
11362
11524
|
}
|
|
11363
11525
|
let updatedContent = content;
|
|
11364
11526
|
for (const [key, val] of Object.entries(value)) {
|
|
11365
|
-
updatedContent = applyValueAtPath(updatedContent, [...
|
|
11527
|
+
updatedContent = applyValueAtPath(updatedContent, [...path6, key], val);
|
|
11366
11528
|
}
|
|
11367
11529
|
return updatedContent;
|
|
11368
11530
|
}
|
|
11369
11531
|
if (Array.isArray(value)) {
|
|
11370
|
-
const edits2 = modify(content,
|
|
11532
|
+
const edits2 = modify(content, path6, value, JSONC_OPTIONS);
|
|
11371
11533
|
return applyEdits(content, edits2);
|
|
11372
11534
|
}
|
|
11373
|
-
const edits = modify(content,
|
|
11535
|
+
const edits = modify(content, path6, value, JSONC_OPTIONS);
|
|
11374
11536
|
return applyEdits(content, edits);
|
|
11375
11537
|
}
|
|
11376
11538
|
async function updateOpencodeJsonConfig(cwd, opencode) {
|
|
@@ -11384,8 +11546,8 @@ async function updateOpencodeJsonConfig(cwd, opencode) {
|
|
|
11384
11546
|
} else {
|
|
11385
11547
|
const config = { $schema: "https://opencode.ai/config.json" };
|
|
11386
11548
|
content = JSON.stringify(config, null, "\t");
|
|
11387
|
-
configPath = isGlobalConfigPath(cwd) ?
|
|
11388
|
-
await mkdir3(
|
|
11549
|
+
configPath = isGlobalConfigPath(cwd) ? path5.join(cwd, "opencode.jsonc") : path5.join(cwd, LOCAL_CONFIG_DIR3, "opencode.jsonc");
|
|
11550
|
+
await mkdir3(path5.dirname(configPath), { recursive: true });
|
|
11389
11551
|
created = true;
|
|
11390
11552
|
}
|
|
11391
11553
|
const originalContent = content;
|
|
@@ -11427,7 +11589,7 @@ function parseEnvBool(value, defaultValue) {
|
|
|
11427
11589
|
return defaultValue;
|
|
11428
11590
|
}
|
|
11429
11591
|
// src/utils/git-context.ts
|
|
11430
|
-
import { basename, resolve } from "path";
|
|
11592
|
+
import { basename as basename2, resolve as resolve2 } from "path";
|
|
11431
11593
|
function getGitEnv() {
|
|
11432
11594
|
const { GIT_DIR: _, GIT_WORK_TREE: __, ...cleanEnv } = process.env;
|
|
11433
11595
|
return cleanEnv;
|
|
@@ -11493,12 +11655,100 @@ async function getRepoName(cwd) {
|
|
|
11493
11655
|
if (!rootPath) {
|
|
11494
11656
|
return null;
|
|
11495
11657
|
}
|
|
11496
|
-
return
|
|
11658
|
+
return basename2(rootPath);
|
|
11497
11659
|
}
|
|
11498
11660
|
async function getGitInfo(cwd) {
|
|
11499
11661
|
const [repoName, branch] = await Promise.all([getRepoName(cwd), getBranch(cwd)]);
|
|
11500
11662
|
return { repoName, branch };
|
|
11501
11663
|
}
|
|
11664
|
+
// src/lib/build-registry.ts
|
|
11665
|
+
import { mkdir as mkdir4 } from "fs/promises";
|
|
11666
|
+
import { dirname, join as join3 } from "path";
|
|
11667
|
+
class BuildRegistryError extends Error {
|
|
11668
|
+
errors;
|
|
11669
|
+
constructor(message, errors2 = []) {
|
|
11670
|
+
super(message);
|
|
11671
|
+
this.errors = errors2;
|
|
11672
|
+
this.name = "BuildRegistryError";
|
|
11673
|
+
}
|
|
11674
|
+
}
|
|
11675
|
+
async function buildRegistry(options) {
|
|
11676
|
+
const { source: sourcePath, out: outPath } = options;
|
|
11677
|
+
const jsoncFile = Bun.file(join3(sourcePath, "registry.jsonc"));
|
|
11678
|
+
const jsonFile = Bun.file(join3(sourcePath, "registry.json"));
|
|
11679
|
+
const jsoncExists = await jsoncFile.exists();
|
|
11680
|
+
const jsonExists = await jsonFile.exists();
|
|
11681
|
+
if (!jsoncExists && !jsonExists) {
|
|
11682
|
+
throw new BuildRegistryError("No registry.jsonc or registry.json found in source directory");
|
|
11683
|
+
}
|
|
11684
|
+
const registryFile = jsoncExists ? jsoncFile : jsonFile;
|
|
11685
|
+
const content = await registryFile.text();
|
|
11686
|
+
const registryData = parse2(content, [], { allowTrailingComma: true });
|
|
11687
|
+
const parseResult = registrySchema.safeParse(registryData);
|
|
11688
|
+
if (!parseResult.success) {
|
|
11689
|
+
const errors2 = parseResult.error.errors.map((e3) => `${e3.path.join(".")}: ${e3.message}`);
|
|
11690
|
+
throw new BuildRegistryError("Registry validation failed", errors2);
|
|
11691
|
+
}
|
|
11692
|
+
const registry = parseResult.data;
|
|
11693
|
+
const validationErrors = [];
|
|
11694
|
+
const componentsDir = join3(outPath, "components");
|
|
11695
|
+
await mkdir4(componentsDir, { recursive: true });
|
|
11696
|
+
for (const component of registry.components) {
|
|
11697
|
+
const packument = {
|
|
11698
|
+
name: component.name,
|
|
11699
|
+
versions: {
|
|
11700
|
+
[registry.version]: component
|
|
11701
|
+
},
|
|
11702
|
+
"dist-tags": {
|
|
11703
|
+
latest: registry.version
|
|
11704
|
+
}
|
|
11705
|
+
};
|
|
11706
|
+
const packumentPath = join3(componentsDir, `${component.name}.json`);
|
|
11707
|
+
await Bun.write(packumentPath, JSON.stringify(packument, null, 2));
|
|
11708
|
+
for (const rawFile of component.files) {
|
|
11709
|
+
const file = normalizeFile(rawFile, component.type);
|
|
11710
|
+
const sourceFilePath = join3(sourcePath, "files", file.path);
|
|
11711
|
+
const destFilePath = join3(componentsDir, component.name, file.path);
|
|
11712
|
+
const destFileDir = dirname(destFilePath);
|
|
11713
|
+
if (!await Bun.file(sourceFilePath).exists()) {
|
|
11714
|
+
validationErrors.push(`${component.name}: Source file not found at ${sourceFilePath}`);
|
|
11715
|
+
continue;
|
|
11716
|
+
}
|
|
11717
|
+
await mkdir4(destFileDir, { recursive: true });
|
|
11718
|
+
const sourceFile = Bun.file(sourceFilePath);
|
|
11719
|
+
await Bun.write(destFilePath, sourceFile);
|
|
11720
|
+
}
|
|
11721
|
+
}
|
|
11722
|
+
if (validationErrors.length > 0) {
|
|
11723
|
+
throw new BuildRegistryError(`Build failed with ${validationErrors.length} errors`, validationErrors);
|
|
11724
|
+
}
|
|
11725
|
+
const index = {
|
|
11726
|
+
name: registry.name,
|
|
11727
|
+
namespace: registry.namespace,
|
|
11728
|
+
version: registry.version,
|
|
11729
|
+
author: registry.author,
|
|
11730
|
+
...registry.opencode && { opencode: registry.opencode },
|
|
11731
|
+
...registry.ocx && { ocx: registry.ocx },
|
|
11732
|
+
components: registry.components.map((c) => ({
|
|
11733
|
+
name: c.name,
|
|
11734
|
+
type: c.type,
|
|
11735
|
+
description: c.description
|
|
11736
|
+
}))
|
|
11737
|
+
};
|
|
11738
|
+
await Bun.write(join3(outPath, "index.json"), JSON.stringify(index, null, 2));
|
|
11739
|
+
const wellKnownDir = join3(outPath, ".well-known");
|
|
11740
|
+
await mkdir4(wellKnownDir, { recursive: true });
|
|
11741
|
+
const discovery = { registry: "/index.json" };
|
|
11742
|
+
await Bun.write(join3(wellKnownDir, "ocx.json"), JSON.stringify(discovery, null, 2));
|
|
11743
|
+
return {
|
|
11744
|
+
name: registry.name,
|
|
11745
|
+
namespace: registry.namespace,
|
|
11746
|
+
version: registry.version,
|
|
11747
|
+
componentsCount: registry.components.length,
|
|
11748
|
+
outputPath: outPath
|
|
11749
|
+
};
|
|
11750
|
+
}
|
|
11751
|
+
|
|
11502
11752
|
// ../../node_modules/.bun/kleur@4.1.5/node_modules/kleur/index.mjs
|
|
11503
11753
|
var FORCE_COLOR;
|
|
11504
11754
|
var NODE_DISABLE_COLORS;
|
|
@@ -11636,6 +11886,15 @@ var logger = {
|
|
|
11636
11886
|
console.log("");
|
|
11637
11887
|
}
|
|
11638
11888
|
};
|
|
11889
|
+
var highlight = {
|
|
11890
|
+
component: (text) => kleur_default.cyan(text),
|
|
11891
|
+
path: (text) => kleur_default.green(text),
|
|
11892
|
+
command: (text) => kleur_default.yellow(text),
|
|
11893
|
+
url: (text) => kleur_default.blue().underline(text),
|
|
11894
|
+
error: (text) => kleur_default.red(text),
|
|
11895
|
+
dim: (text) => kleur_default.gray(text),
|
|
11896
|
+
bold: (text) => kleur_default.bold(text)
|
|
11897
|
+
};
|
|
11639
11898
|
|
|
11640
11899
|
// src/utils/handle-error.ts
|
|
11641
11900
|
function handleError(error, options2 = {}) {
|
|
@@ -11651,8 +11910,8 @@ function handleError(error, options2 = {}) {
|
|
|
11651
11910
|
if (error instanceof ZodError) {
|
|
11652
11911
|
logger.error("Validation failed:");
|
|
11653
11912
|
for (const issue of error.issues) {
|
|
11654
|
-
const
|
|
11655
|
-
logger.error(` ${
|
|
11913
|
+
const path6 = issue.path.join(".");
|
|
11914
|
+
logger.error(` ${path6}: ${issue.message}`);
|
|
11656
11915
|
}
|
|
11657
11916
|
process.exit(EXIT_CODES.CONFIG);
|
|
11658
11917
|
}
|
|
@@ -11695,6 +11954,110 @@ function formatErrorAsJson(error) {
|
|
|
11695
11954
|
}
|
|
11696
11955
|
};
|
|
11697
11956
|
}
|
|
11957
|
+
if (error instanceof IntegrityError) {
|
|
11958
|
+
return {
|
|
11959
|
+
success: false,
|
|
11960
|
+
error: {
|
|
11961
|
+
code: error.code,
|
|
11962
|
+
message: error.message,
|
|
11963
|
+
details: {
|
|
11964
|
+
component: error.component,
|
|
11965
|
+
expected: error.expected,
|
|
11966
|
+
found: error.found
|
|
11967
|
+
}
|
|
11968
|
+
},
|
|
11969
|
+
exitCode: error.exitCode,
|
|
11970
|
+
meta: {
|
|
11971
|
+
timestamp: new Date().toISOString()
|
|
11972
|
+
}
|
|
11973
|
+
};
|
|
11974
|
+
}
|
|
11975
|
+
if (error instanceof NetworkError) {
|
|
11976
|
+
const details = {};
|
|
11977
|
+
if (error.url)
|
|
11978
|
+
details.url = error.url;
|
|
11979
|
+
if (error.status !== undefined)
|
|
11980
|
+
details.status = error.status;
|
|
11981
|
+
if (error.statusText)
|
|
11982
|
+
details.statusText = error.statusText;
|
|
11983
|
+
return {
|
|
11984
|
+
success: false,
|
|
11985
|
+
error: {
|
|
11986
|
+
code: error.code,
|
|
11987
|
+
message: error.message,
|
|
11988
|
+
...Object.keys(details).length > 0 && { details }
|
|
11989
|
+
},
|
|
11990
|
+
exitCode: error.exitCode,
|
|
11991
|
+
meta: {
|
|
11992
|
+
timestamp: new Date().toISOString()
|
|
11993
|
+
}
|
|
11994
|
+
};
|
|
11995
|
+
}
|
|
11996
|
+
if (error instanceof ProfileNotFoundError) {
|
|
11997
|
+
return {
|
|
11998
|
+
success: false,
|
|
11999
|
+
error: {
|
|
12000
|
+
code: error.code,
|
|
12001
|
+
message: error.message,
|
|
12002
|
+
details: {
|
|
12003
|
+
profile: error.profile
|
|
12004
|
+
}
|
|
12005
|
+
},
|
|
12006
|
+
exitCode: error.exitCode,
|
|
12007
|
+
meta: {
|
|
12008
|
+
timestamp: new Date().toISOString()
|
|
12009
|
+
}
|
|
12010
|
+
};
|
|
12011
|
+
}
|
|
12012
|
+
if (error instanceof ProfileExistsError) {
|
|
12013
|
+
return {
|
|
12014
|
+
success: false,
|
|
12015
|
+
error: {
|
|
12016
|
+
code: error.code,
|
|
12017
|
+
message: error.message,
|
|
12018
|
+
details: {
|
|
12019
|
+
profile: error.profile
|
|
12020
|
+
}
|
|
12021
|
+
},
|
|
12022
|
+
exitCode: error.exitCode,
|
|
12023
|
+
meta: {
|
|
12024
|
+
timestamp: new Date().toISOString()
|
|
12025
|
+
}
|
|
12026
|
+
};
|
|
12027
|
+
}
|
|
12028
|
+
if (error instanceof InvalidProfileNameError) {
|
|
12029
|
+
return {
|
|
12030
|
+
success: false,
|
|
12031
|
+
error: {
|
|
12032
|
+
code: error.code,
|
|
12033
|
+
message: error.message,
|
|
12034
|
+
details: {
|
|
12035
|
+
profile: error.profile,
|
|
12036
|
+
reason: error.reason
|
|
12037
|
+
}
|
|
12038
|
+
},
|
|
12039
|
+
exitCode: error.exitCode,
|
|
12040
|
+
meta: {
|
|
12041
|
+
timestamp: new Date().toISOString()
|
|
12042
|
+
}
|
|
12043
|
+
};
|
|
12044
|
+
}
|
|
12045
|
+
if (error instanceof BuildRegistryError) {
|
|
12046
|
+
return {
|
|
12047
|
+
success: false,
|
|
12048
|
+
error: {
|
|
12049
|
+
code: "BUILD_ERROR",
|
|
12050
|
+
message: error.message,
|
|
12051
|
+
details: {
|
|
12052
|
+
errors: error.errors
|
|
12053
|
+
}
|
|
12054
|
+
},
|
|
12055
|
+
exitCode: EXIT_CODES.GENERAL,
|
|
12056
|
+
meta: {
|
|
12057
|
+
timestamp: new Date().toISOString()
|
|
12058
|
+
}
|
|
12059
|
+
};
|
|
12060
|
+
}
|
|
11698
12061
|
if (error instanceof OCXError) {
|
|
11699
12062
|
return {
|
|
11700
12063
|
success: false,
|
|
@@ -11713,7 +12076,14 @@ function formatErrorAsJson(error) {
|
|
|
11713
12076
|
success: false,
|
|
11714
12077
|
error: {
|
|
11715
12078
|
code: "VALIDATION_ERROR",
|
|
11716
|
-
message: error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
|
|
12079
|
+
message: error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "),
|
|
12080
|
+
details: {
|
|
12081
|
+
issues: error.issues.map((i) => ({
|
|
12082
|
+
path: i.path.join("."),
|
|
12083
|
+
message: i.message,
|
|
12084
|
+
code: i.code
|
|
12085
|
+
}))
|
|
12086
|
+
}
|
|
11717
12087
|
},
|
|
11718
12088
|
exitCode: EXIT_CODES.CONFIG,
|
|
11719
12089
|
meta: {
|
|
@@ -11738,15 +12108,15 @@ function outputJson(data) {
|
|
|
11738
12108
|
console.log(JSON.stringify(data, null, 2));
|
|
11739
12109
|
}
|
|
11740
12110
|
// src/utils/path-safety.ts
|
|
11741
|
-
import
|
|
12111
|
+
import path6 from "path";
|
|
11742
12112
|
function isPathInside(childPath, parentPath) {
|
|
11743
|
-
const resolvedChild =
|
|
11744
|
-
const resolvedParent =
|
|
12113
|
+
const resolvedChild = path6.resolve(childPath);
|
|
12114
|
+
const resolvedParent = path6.resolve(parentPath);
|
|
11745
12115
|
if (resolvedChild === resolvedParent) {
|
|
11746
12116
|
return true;
|
|
11747
12117
|
}
|
|
11748
|
-
const
|
|
11749
|
-
return !!
|
|
12118
|
+
const relative3 = path6.relative(resolvedParent, resolvedChild);
|
|
12119
|
+
return !!relative3 && !relative3.startsWith("..") && !isAbsolutePath(relative3);
|
|
11750
12120
|
}
|
|
11751
12121
|
function assertPathInside(childPath, parentPath) {
|
|
11752
12122
|
if (!isPathInside(childPath, parentPath)) {
|
|
@@ -13507,7 +13877,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13507
13877
|
}
|
|
13508
13878
|
const computedHash = await hashBundle(files);
|
|
13509
13879
|
for (const file of component.files) {
|
|
13510
|
-
const targetPath =
|
|
13880
|
+
const targetPath = join4(cwd, resolveTargetPath(file.target, isFlattened));
|
|
13511
13881
|
assertPathInside(targetPath, cwd);
|
|
13512
13882
|
}
|
|
13513
13883
|
const existingEntry = lock.installed[component.qualifiedName];
|
|
@@ -13517,7 +13887,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13517
13887
|
}
|
|
13518
13888
|
for (const file of component.files) {
|
|
13519
13889
|
const resolvedTarget = resolveTargetPath(file.target, isFlattened);
|
|
13520
|
-
const targetPath =
|
|
13890
|
+
const targetPath = join4(cwd, resolvedTarget);
|
|
13521
13891
|
if (existsSync5(targetPath)) {
|
|
13522
13892
|
const conflictingComponent = findComponentByFile(lock, resolvedTarget);
|
|
13523
13893
|
if (conflictingComponent && conflictingComponent !== component.qualifiedName) {
|
|
@@ -13541,7 +13911,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13541
13911
|
if (!componentFile)
|
|
13542
13912
|
continue;
|
|
13543
13913
|
const resolvedTarget = resolveTargetPath(componentFile.target, isFlattened);
|
|
13544
|
-
const targetPath =
|
|
13914
|
+
const targetPath = join4(cwd, resolvedTarget);
|
|
13545
13915
|
if (existsSync5(targetPath)) {
|
|
13546
13916
|
const existingContent = await Bun.file(targetPath).text();
|
|
13547
13917
|
const incomingContent = file.content.toString("utf-8");
|
|
@@ -13606,7 +13976,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13606
13976
|
}
|
|
13607
13977
|
const hasNpmDeps = resolved.npmDependencies.length > 0;
|
|
13608
13978
|
const hasNpmDevDeps = resolved.npmDevDependencies.length > 0;
|
|
13609
|
-
const packageJsonPath = options2.global || options2.profile ?
|
|
13979
|
+
const packageJsonPath = options2.global || options2.profile ? join4(cwd, "package.json") : join4(cwd, ".opencode/package.json");
|
|
13610
13980
|
if (hasNpmDeps || hasNpmDevDeps) {
|
|
13611
13981
|
const npmSpin = options2.quiet ? null : createSpinner({ text: `Updating ${packageJsonPath}...` });
|
|
13612
13982
|
npmSpin?.start();
|
|
@@ -13648,8 +14018,8 @@ async function installComponent(component, files, cwd, options2) {
|
|
|
13648
14018
|
if (!componentFile)
|
|
13649
14019
|
continue;
|
|
13650
14020
|
const resolvedTarget = resolveTargetPath(componentFile.target, !!options2.isFlattened);
|
|
13651
|
-
const targetPath =
|
|
13652
|
-
const targetDir =
|
|
14021
|
+
const targetPath = join4(cwd, resolvedTarget);
|
|
14022
|
+
const targetDir = dirname2(targetPath);
|
|
13653
14023
|
if (existsSync5(targetPath)) {
|
|
13654
14024
|
const existingContent = await Bun.file(targetPath).text();
|
|
13655
14025
|
const incomingContent = file.content.toString("utf-8");
|
|
@@ -13662,7 +14032,7 @@ async function installComponent(component, files, cwd, options2) {
|
|
|
13662
14032
|
result.written.push(resolvedTarget);
|
|
13663
14033
|
}
|
|
13664
14034
|
if (!existsSync5(targetDir)) {
|
|
13665
|
-
await
|
|
14035
|
+
await mkdir5(targetDir, { recursive: true });
|
|
13666
14036
|
}
|
|
13667
14037
|
await writeFile(targetPath, file.content);
|
|
13668
14038
|
}
|
|
@@ -13738,7 +14108,7 @@ function mergeDevDependencies(existing, newDeps) {
|
|
|
13738
14108
|
return { ...existing, devDependencies: merged };
|
|
13739
14109
|
}
|
|
13740
14110
|
async function readOpencodePackageJson(opencodeDir) {
|
|
13741
|
-
const pkgPath =
|
|
14111
|
+
const pkgPath = join4(opencodeDir, "package.json");
|
|
13742
14112
|
if (!existsSync5(pkgPath)) {
|
|
13743
14113
|
return { ...DEFAULT_PACKAGE_JSON };
|
|
13744
14114
|
}
|
|
@@ -13751,7 +14121,7 @@ async function readOpencodePackageJson(opencodeDir) {
|
|
|
13751
14121
|
}
|
|
13752
14122
|
}
|
|
13753
14123
|
async function ensureManifestFilesAreTracked(opencodeDir) {
|
|
13754
|
-
const gitignorePath =
|
|
14124
|
+
const gitignorePath = join4(opencodeDir, ".gitignore");
|
|
13755
14125
|
const filesToTrack = new Set(["package.json", "bun.lock"]);
|
|
13756
14126
|
const requiredIgnores = ["node_modules"];
|
|
13757
14127
|
let lines = [];
|
|
@@ -13774,12 +14144,12 @@ async function updateOpencodeDevDependencies(cwd, npmDeps, npmDevDeps, options2
|
|
|
13774
14144
|
const allDepSpecs = [...npmDeps, ...npmDevDeps];
|
|
13775
14145
|
if (allDepSpecs.length === 0)
|
|
13776
14146
|
return;
|
|
13777
|
-
const packageDir = options2.isFlattened ? cwd :
|
|
13778
|
-
await
|
|
14147
|
+
const packageDir = options2.isFlattened ? cwd : join4(cwd, ".opencode");
|
|
14148
|
+
await mkdir5(packageDir, { recursive: true });
|
|
13779
14149
|
const parsedDeps = allDepSpecs.map(parseNpmDependency);
|
|
13780
14150
|
const existing = await readOpencodePackageJson(packageDir);
|
|
13781
14151
|
const updated = mergeDevDependencies(existing, parsedDeps);
|
|
13782
|
-
await Bun.write(
|
|
14152
|
+
await Bun.write(join4(packageDir, "package.json"), `${JSON.stringify(updated, null, 2)}
|
|
13783
14153
|
`);
|
|
13784
14154
|
if (!options2.isFlattened) {
|
|
13785
14155
|
await ensureManifestFilesAreTracked(packageDir);
|
|
@@ -13795,101 +14165,11 @@ function findComponentByFile(lock, filePath) {
|
|
|
13795
14165
|
}
|
|
13796
14166
|
|
|
13797
14167
|
// src/commands/build.ts
|
|
13798
|
-
import { join as join5, relative as
|
|
13799
|
-
|
|
13800
|
-
// src/lib/build-registry.ts
|
|
13801
|
-
import { mkdir as mkdir5 } from "fs/promises";
|
|
13802
|
-
import { dirname as dirname2, join as join4 } from "path";
|
|
13803
|
-
class BuildRegistryError extends Error {
|
|
13804
|
-
errors;
|
|
13805
|
-
constructor(message, errors3 = []) {
|
|
13806
|
-
super(message);
|
|
13807
|
-
this.errors = errors3;
|
|
13808
|
-
this.name = "BuildRegistryError";
|
|
13809
|
-
}
|
|
13810
|
-
}
|
|
13811
|
-
async function buildRegistry(options2) {
|
|
13812
|
-
const { source: sourcePath, out: outPath } = options2;
|
|
13813
|
-
const jsoncFile = Bun.file(join4(sourcePath, "registry.jsonc"));
|
|
13814
|
-
const jsonFile = Bun.file(join4(sourcePath, "registry.json"));
|
|
13815
|
-
const jsoncExists = await jsoncFile.exists();
|
|
13816
|
-
const jsonExists = await jsonFile.exists();
|
|
13817
|
-
if (!jsoncExists && !jsonExists) {
|
|
13818
|
-
throw new BuildRegistryError("No registry.jsonc or registry.json found in source directory");
|
|
13819
|
-
}
|
|
13820
|
-
const registryFile = jsoncExists ? jsoncFile : jsonFile;
|
|
13821
|
-
const content2 = await registryFile.text();
|
|
13822
|
-
const registryData = parse2(content2, [], { allowTrailingComma: true });
|
|
13823
|
-
const parseResult = registrySchema.safeParse(registryData);
|
|
13824
|
-
if (!parseResult.success) {
|
|
13825
|
-
const errors3 = parseResult.error.errors.map((e3) => `${e3.path.join(".")}: ${e3.message}`);
|
|
13826
|
-
throw new BuildRegistryError("Registry validation failed", errors3);
|
|
13827
|
-
}
|
|
13828
|
-
const registry = parseResult.data;
|
|
13829
|
-
const validationErrors = [];
|
|
13830
|
-
const componentsDir = join4(outPath, "components");
|
|
13831
|
-
await mkdir5(componentsDir, { recursive: true });
|
|
13832
|
-
for (const component of registry.components) {
|
|
13833
|
-
const packument = {
|
|
13834
|
-
name: component.name,
|
|
13835
|
-
versions: {
|
|
13836
|
-
[registry.version]: component
|
|
13837
|
-
},
|
|
13838
|
-
"dist-tags": {
|
|
13839
|
-
latest: registry.version
|
|
13840
|
-
}
|
|
13841
|
-
};
|
|
13842
|
-
const packumentPath = join4(componentsDir, `${component.name}.json`);
|
|
13843
|
-
await Bun.write(packumentPath, JSON.stringify(packument, null, 2));
|
|
13844
|
-
for (const rawFile of component.files) {
|
|
13845
|
-
const file = normalizeFile(rawFile, component.type);
|
|
13846
|
-
const sourceFilePath = join4(sourcePath, "files", file.path);
|
|
13847
|
-
const destFilePath = join4(componentsDir, component.name, file.path);
|
|
13848
|
-
const destFileDir = dirname2(destFilePath);
|
|
13849
|
-
if (!await Bun.file(sourceFilePath).exists()) {
|
|
13850
|
-
validationErrors.push(`${component.name}: Source file not found at ${sourceFilePath}`);
|
|
13851
|
-
continue;
|
|
13852
|
-
}
|
|
13853
|
-
await mkdir5(destFileDir, { recursive: true });
|
|
13854
|
-
const sourceFile = Bun.file(sourceFilePath);
|
|
13855
|
-
await Bun.write(destFilePath, sourceFile);
|
|
13856
|
-
}
|
|
13857
|
-
}
|
|
13858
|
-
if (validationErrors.length > 0) {
|
|
13859
|
-
throw new BuildRegistryError(`Build failed with ${validationErrors.length} errors`, validationErrors);
|
|
13860
|
-
}
|
|
13861
|
-
const index = {
|
|
13862
|
-
name: registry.name,
|
|
13863
|
-
namespace: registry.namespace,
|
|
13864
|
-
version: registry.version,
|
|
13865
|
-
author: registry.author,
|
|
13866
|
-
...registry.opencode && { opencode: registry.opencode },
|
|
13867
|
-
...registry.ocx && { ocx: registry.ocx },
|
|
13868
|
-
components: registry.components.map((c) => ({
|
|
13869
|
-
name: c.name,
|
|
13870
|
-
type: c.type,
|
|
13871
|
-
description: c.description
|
|
13872
|
-
}))
|
|
13873
|
-
};
|
|
13874
|
-
await Bun.write(join4(outPath, "index.json"), JSON.stringify(index, null, 2));
|
|
13875
|
-
const wellKnownDir = join4(outPath, ".well-known");
|
|
13876
|
-
await mkdir5(wellKnownDir, { recursive: true });
|
|
13877
|
-
const discovery = { registry: "/index.json" };
|
|
13878
|
-
await Bun.write(join4(wellKnownDir, "ocx.json"), JSON.stringify(discovery, null, 2));
|
|
13879
|
-
return {
|
|
13880
|
-
name: registry.name,
|
|
13881
|
-
namespace: registry.namespace,
|
|
13882
|
-
version: registry.version,
|
|
13883
|
-
componentsCount: registry.components.length,
|
|
13884
|
-
outputPath: outPath
|
|
13885
|
-
};
|
|
13886
|
-
}
|
|
13887
|
-
|
|
13888
|
-
// src/commands/build.ts
|
|
14168
|
+
import { join as join5, relative as relative3 } from "path";
|
|
13889
14169
|
function registerBuildCommand(program2) {
|
|
13890
|
-
program2.command("build").description("Build a registry from source (for registry authors)").argument("[path]", "Registry source directory", ".").option("--out <dir>", "Output directory", "./dist").option("--cwd <path>", "Working directory", process.cwd()).option("--json", "Output as JSON", false).option("-q, --quiet", "Suppress output", false).action(async (
|
|
14170
|
+
program2.command("build").description("Build a registry from source (for registry authors)").argument("[path]", "Registry source directory", ".").option("--out <dir>", "Output directory", "./dist").option("--cwd <path>", "Working directory", process.cwd()).option("--json", "Output as JSON", false).option("-q, --quiet", "Suppress output", false).action(async (path7, options2) => {
|
|
13891
14171
|
try {
|
|
13892
|
-
const sourcePath = join5(options2.cwd,
|
|
14172
|
+
const sourcePath = join5(options2.cwd, path7);
|
|
13893
14173
|
const outPath = join5(options2.cwd, options2.out);
|
|
13894
14174
|
const spinner2 = createSpinner({
|
|
13895
14175
|
text: "Building registry...",
|
|
@@ -13902,7 +14182,7 @@ function registerBuildCommand(program2) {
|
|
|
13902
14182
|
out: outPath
|
|
13903
14183
|
});
|
|
13904
14184
|
if (!options2.json) {
|
|
13905
|
-
const msg = `Built ${result.componentsCount} components to ${
|
|
14185
|
+
const msg = `Built ${result.componentsCount} components to ${relative3(options2.cwd, outPath)}`;
|
|
13906
14186
|
spinner2.succeed(msg);
|
|
13907
14187
|
if (!process.stdout.isTTY) {
|
|
13908
14188
|
logger.success(`Built ${result.componentsCount} components`);
|
|
@@ -14204,16 +14484,16 @@ class Diff {
|
|
|
14204
14484
|
}
|
|
14205
14485
|
}
|
|
14206
14486
|
}
|
|
14207
|
-
addToPath(
|
|
14208
|
-
const last =
|
|
14487
|
+
addToPath(path7, added, removed, oldPosInc, options2) {
|
|
14488
|
+
const last = path7.lastComponent;
|
|
14209
14489
|
if (last && !options2.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
14210
14490
|
return {
|
|
14211
|
-
oldPos:
|
|
14491
|
+
oldPos: path7.oldPos + oldPosInc,
|
|
14212
14492
|
lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
|
|
14213
14493
|
};
|
|
14214
14494
|
} else {
|
|
14215
14495
|
return {
|
|
14216
|
-
oldPos:
|
|
14496
|
+
oldPos: path7.oldPos + oldPosInc,
|
|
14217
14497
|
lastComponent: { count: 1, added, removed, previousComponent: last }
|
|
14218
14498
|
};
|
|
14219
14499
|
}
|
|
@@ -15030,7 +15310,7 @@ import {
|
|
|
15030
15310
|
rmSync,
|
|
15031
15311
|
unlinkSync
|
|
15032
15312
|
} from "fs";
|
|
15033
|
-
import
|
|
15313
|
+
import path7 from "path";
|
|
15034
15314
|
var GHOST_CONFIG_FILE = "ghost.jsonc";
|
|
15035
15315
|
var BACKUP_EXT = ".bak";
|
|
15036
15316
|
var CURRENT_SYMLINK = "current";
|
|
@@ -15068,8 +15348,8 @@ function planMigration() {
|
|
|
15068
15348
|
if (!entry.isDirectory() || entry.name === CURRENT_SYMLINK)
|
|
15069
15349
|
continue;
|
|
15070
15350
|
const profileName = entry.name;
|
|
15071
|
-
const ghostConfig =
|
|
15072
|
-
const ocxConfig =
|
|
15351
|
+
const ghostConfig = path7.join(profilesDir, profileName, GHOST_CONFIG_FILE);
|
|
15352
|
+
const ocxConfig = path7.join(profilesDir, profileName, OCX_CONFIG_FILE);
|
|
15073
15353
|
if (!existsSync7(ghostConfig))
|
|
15074
15354
|
continue;
|
|
15075
15355
|
if (existsSync7(ocxConfig)) {
|
|
@@ -15099,13 +15379,13 @@ function planMigration() {
|
|
|
15099
15379
|
if (!entry.isDirectory() || entry.name === CURRENT_SYMLINK)
|
|
15100
15380
|
continue;
|
|
15101
15381
|
const profileName = entry.name;
|
|
15102
|
-
const profileDir =
|
|
15103
|
-
const dotOpencode =
|
|
15382
|
+
const profileDir = path7.join(profilesDir, profileName);
|
|
15383
|
+
const dotOpencode = path7.join(profileDir, ".opencode");
|
|
15104
15384
|
if (!existsSync7(dotOpencode))
|
|
15105
15385
|
continue;
|
|
15106
15386
|
for (const dir of FLATTEN_DIRS) {
|
|
15107
|
-
const source =
|
|
15108
|
-
const destination =
|
|
15387
|
+
const source = path7.join(dotOpencode, dir);
|
|
15388
|
+
const destination = path7.join(profileDir, dir);
|
|
15109
15389
|
if (!existsSync7(source))
|
|
15110
15390
|
continue;
|
|
15111
15391
|
try {
|
|
@@ -15144,7 +15424,7 @@ function planMigration() {
|
|
|
15144
15424
|
});
|
|
15145
15425
|
}
|
|
15146
15426
|
}
|
|
15147
|
-
const currentPath =
|
|
15427
|
+
const currentPath = path7.join(profilesDir, CURRENT_SYMLINK);
|
|
15148
15428
|
if (existsSync7(currentPath)) {
|
|
15149
15429
|
try {
|
|
15150
15430
|
const stat3 = lstatSync(currentPath);
|
|
@@ -15203,21 +15483,21 @@ function executeMigration(plan) {
|
|
|
15203
15483
|
} catch {}
|
|
15204
15484
|
}
|
|
15205
15485
|
} catch (error) {
|
|
15206
|
-
for (const
|
|
15486
|
+
for (const rename3 of completedRenames) {
|
|
15207
15487
|
try {
|
|
15208
|
-
if (existsSync7(
|
|
15209
|
-
if (
|
|
15210
|
-
rmSync(
|
|
15488
|
+
if (existsSync7(rename3.destination)) {
|
|
15489
|
+
if (rename3.isDir) {
|
|
15490
|
+
rmSync(rename3.destination, { recursive: true, force: true });
|
|
15211
15491
|
} else {
|
|
15212
|
-
unlinkSync(
|
|
15492
|
+
unlinkSync(rename3.destination);
|
|
15213
15493
|
}
|
|
15214
15494
|
}
|
|
15215
|
-
mkdirSync(
|
|
15216
|
-
if (existsSync7(
|
|
15217
|
-
if (
|
|
15218
|
-
cpSync(
|
|
15495
|
+
mkdirSync(path7.dirname(rename3.source), { recursive: true });
|
|
15496
|
+
if (existsSync7(rename3.backup)) {
|
|
15497
|
+
if (rename3.isDir) {
|
|
15498
|
+
cpSync(rename3.backup, rename3.source, { recursive: true });
|
|
15219
15499
|
} else {
|
|
15220
|
-
copyFileSync(
|
|
15500
|
+
copyFileSync(rename3.backup, rename3.source);
|
|
15221
15501
|
}
|
|
15222
15502
|
}
|
|
15223
15503
|
} catch {}
|
|
@@ -15238,8 +15518,8 @@ function executeMigration(plan) {
|
|
|
15238
15518
|
try {
|
|
15239
15519
|
const processedProfiles = new Set(plan.profiles.filter((a) => a.type === "move-dir").map((a) => a.profileName));
|
|
15240
15520
|
for (const profileName of processedProfiles) {
|
|
15241
|
-
const profileDir =
|
|
15242
|
-
const dotOpencode =
|
|
15521
|
+
const profileDir = path7.join(getProfilesDir(), profileName);
|
|
15522
|
+
const dotOpencode = path7.join(profileDir, ".opencode");
|
|
15243
15523
|
if (existsSync7(dotOpencode)) {
|
|
15244
15524
|
const remaining = readdirSync(dotOpencode);
|
|
15245
15525
|
if (remaining.length === 0) {
|
|
@@ -15265,7 +15545,7 @@ function printPlan(plan, dryRun) {
|
|
|
15265
15545
|
if (action.type === "rename") {
|
|
15266
15546
|
console.log(` \u2713 ${action.profileName}: ${GHOST_CONFIG_FILE} \u2192 ${OCX_CONFIG_FILE}`);
|
|
15267
15547
|
} else {
|
|
15268
|
-
const dirName =
|
|
15548
|
+
const dirName = path7.basename(action.source);
|
|
15269
15549
|
console.log(` \u2713 ${action.profileName}: .opencode/${dirName}/ \u2192 ${dirName}/`);
|
|
15270
15550
|
}
|
|
15271
15551
|
}
|
|
@@ -15525,7 +15805,7 @@ async function copyDir(src, dest) {
|
|
|
15525
15805
|
}
|
|
15526
15806
|
function getReleaseTag() {
|
|
15527
15807
|
if (false) {}
|
|
15528
|
-
return `v${"1.4.
|
|
15808
|
+
return `v${"1.4.3"}`;
|
|
15529
15809
|
}
|
|
15530
15810
|
function getTemplateUrl(version) {
|
|
15531
15811
|
const ref = version === "main" ? "heads/main" : `tags/${version}`;
|
|
@@ -15566,6 +15846,9 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
|
|
|
15566
15846
|
await rm2(tempDir, { recursive: true, force: true });
|
|
15567
15847
|
}
|
|
15568
15848
|
}
|
|
15849
|
+
function toTitleCase(str) {
|
|
15850
|
+
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
15851
|
+
}
|
|
15569
15852
|
async function replacePlaceholders(dir, values) {
|
|
15570
15853
|
const filesToProcess = [
|
|
15571
15854
|
"registry.jsonc",
|
|
@@ -15580,16 +15863,17 @@ async function replacePlaceholders(dir, values) {
|
|
|
15580
15863
|
continue;
|
|
15581
15864
|
let content2 = await readFile(filePath).then((b) => b.toString());
|
|
15582
15865
|
content2 = content2.replace(/my-registry/g, values.namespace);
|
|
15866
|
+
content2 = content2.replace(/My Registry/g, toTitleCase(values.namespace));
|
|
15583
15867
|
content2 = content2.replace(/Your Name/g, values.author);
|
|
15584
15868
|
await writeFile2(filePath, content2);
|
|
15585
15869
|
}
|
|
15586
15870
|
}
|
|
15587
15871
|
|
|
15588
15872
|
// src/commands/opencode.ts
|
|
15589
|
-
import { resolve as
|
|
15873
|
+
import { resolve as resolve3 } from "path";
|
|
15590
15874
|
|
|
15591
15875
|
// src/utils/terminal-title.ts
|
|
15592
|
-
import
|
|
15876
|
+
import path8 from "path";
|
|
15593
15877
|
var MAX_BRANCH_LENGTH = 20;
|
|
15594
15878
|
var titleSaved = false;
|
|
15595
15879
|
function isInsideTmux() {
|
|
@@ -15632,7 +15916,7 @@ function restoreTerminalTitle() {
|
|
|
15632
15916
|
titleSaved = false;
|
|
15633
15917
|
}
|
|
15634
15918
|
function formatTerminalName(cwd, profileName, gitInfo) {
|
|
15635
|
-
const repoName = gitInfo.repoName ??
|
|
15919
|
+
const repoName = gitInfo.repoName ?? path8.basename(cwd);
|
|
15636
15920
|
if (!gitInfo.branch) {
|
|
15637
15921
|
return `ocx[${profileName}]:${repoName}`;
|
|
15638
15922
|
}
|
|
@@ -15654,16 +15938,16 @@ function buildOpenCodeEnv(opts) {
|
|
|
15654
15938
|
};
|
|
15655
15939
|
}
|
|
15656
15940
|
function registerOpencodeCommand(program2) {
|
|
15657
|
-
program2.command("opencode [path]").alias("oc").description("Launch OpenCode with resolved configuration").option("-p, --profile <name>", "Use specific profile").option("--no-rename", "Disable terminal/tmux window renaming").addOption(sharedOptions.quiet()).addOption(sharedOptions.json()).allowUnknownOption().allowExcessArguments(true).action(async (
|
|
15941
|
+
program2.command("opencode [path]").alias("oc").description("Launch OpenCode with resolved configuration").option("-p, --profile <name>", "Use specific profile").option("--no-rename", "Disable terminal/tmux window renaming").addOption(sharedOptions.quiet()).addOption(sharedOptions.json()).allowUnknownOption().allowExcessArguments(true).action(async (path9, options2, command) => {
|
|
15658
15942
|
try {
|
|
15659
|
-
await runOpencode(
|
|
15943
|
+
await runOpencode(path9, command.args, options2);
|
|
15660
15944
|
} catch (error) {
|
|
15661
15945
|
handleError(error, { json: options2.json });
|
|
15662
15946
|
}
|
|
15663
15947
|
});
|
|
15664
15948
|
}
|
|
15665
15949
|
async function runOpencode(pathArg, args, options2) {
|
|
15666
|
-
const projectDir = pathArg ?
|
|
15950
|
+
const projectDir = pathArg ? resolve3(pathArg) : process.cwd();
|
|
15667
15951
|
const resolver = await ConfigResolver.create(projectDir, { profile: options2.profile });
|
|
15668
15952
|
const config = resolver.resolve();
|
|
15669
15953
|
const profile = resolver.getProfile();
|
|
@@ -15748,12 +16032,8 @@ async function runOpencode(pathArg, args, options2) {
|
|
|
15748
16032
|
// src/commands/profile/install-from-registry.ts
|
|
15749
16033
|
import { createHash as createHash2 } from "crypto";
|
|
15750
16034
|
import { existsSync as existsSync9 } from "fs";
|
|
15751
|
-
import { mkdir as mkdir8, mkdtemp, rename as
|
|
16035
|
+
import { mkdir as mkdir8, mkdtemp, rename as rename3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
15752
16036
|
import { dirname as dirname4, join as join8 } from "path";
|
|
15753
|
-
var PROFILE_FILE_TARGETS = new Set(["ocx.jsonc", "opencode.jsonc", "AGENTS.md"]);
|
|
15754
|
-
function isProfileFile(target) {
|
|
15755
|
-
return PROFILE_FILE_TARGETS.has(target);
|
|
15756
|
-
}
|
|
15757
16037
|
function hashContent2(content2) {
|
|
15758
16038
|
return createHash2("sha256").update(content2).digest("hex");
|
|
15759
16039
|
}
|
|
@@ -15805,7 +16085,7 @@ Use --force to overwrite.`);
|
|
|
15805
16085
|
const filesSpin = quiet ? null : createSpinner({ text: "Downloading profile files..." });
|
|
15806
16086
|
filesSpin?.start();
|
|
15807
16087
|
const profileFiles = [];
|
|
15808
|
-
const
|
|
16088
|
+
const embeddedFiles = [];
|
|
15809
16089
|
for (const file of normalized.files) {
|
|
15810
16090
|
const content2 = await fetchFileContent(registryUrl, component, file.path);
|
|
15811
16091
|
const fileEntry = {
|
|
@@ -15813,10 +16093,10 @@ Use --force to overwrite.`);
|
|
|
15813
16093
|
target: file.target,
|
|
15814
16094
|
content: Buffer.from(content2)
|
|
15815
16095
|
};
|
|
15816
|
-
if (
|
|
15817
|
-
|
|
16096
|
+
if (file.target.startsWith(".opencode/")) {
|
|
16097
|
+
embeddedFiles.push(fileEntry);
|
|
15818
16098
|
} else {
|
|
15819
|
-
|
|
16099
|
+
profileFiles.push(fileEntry);
|
|
15820
16100
|
}
|
|
15821
16101
|
}
|
|
15822
16102
|
filesSpin?.succeed(`Downloaded ${normalized.files.length} files`);
|
|
@@ -15870,7 +16150,7 @@ Use --force to overwrite.`);
|
|
|
15870
16150
|
}
|
|
15871
16151
|
await writeFile3(targetPath, file.content);
|
|
15872
16152
|
}
|
|
15873
|
-
for (const file of
|
|
16153
|
+
for (const file of embeddedFiles) {
|
|
15874
16154
|
const target = file.target.startsWith(".opencode/") ? file.target.slice(".opencode/".length) : file.target;
|
|
15875
16155
|
const targetPath = join8(stagingOpencodeDir, target);
|
|
15876
16156
|
const targetDir = dirname4(targetPath);
|
|
@@ -15879,7 +16159,7 @@ Use --force to overwrite.`);
|
|
|
15879
16159
|
}
|
|
15880
16160
|
await writeFile3(targetPath, file.content);
|
|
15881
16161
|
}
|
|
15882
|
-
writeSpin?.succeed(`Wrote ${profileFiles.length +
|
|
16162
|
+
writeSpin?.succeed(`Wrote ${profileFiles.length + embeddedFiles.length} profile files`);
|
|
15883
16163
|
if (dependencyBundles.length > 0) {
|
|
15884
16164
|
const depWriteSpin = quiet ? null : createSpinner({ text: "Writing dependency files..." });
|
|
15885
16165
|
depWriteSpin?.start();
|
|
@@ -15928,16 +16208,16 @@ Use --force to overwrite.`);
|
|
|
15928
16208
|
}
|
|
15929
16209
|
if (profileExists && force) {
|
|
15930
16210
|
const backupDir = `${profileDir}.backup-${Date.now()}`;
|
|
15931
|
-
await
|
|
16211
|
+
await rename3(profileDir, backupDir);
|
|
15932
16212
|
try {
|
|
15933
|
-
await
|
|
16213
|
+
await rename3(stagingDir, profileDir);
|
|
15934
16214
|
} catch (err) {
|
|
15935
|
-
await
|
|
16215
|
+
await rename3(backupDir, profileDir);
|
|
15936
16216
|
throw err;
|
|
15937
16217
|
}
|
|
15938
16218
|
await rm3(backupDir, { recursive: true, force: true });
|
|
15939
16219
|
} else {
|
|
15940
|
-
await
|
|
16220
|
+
await rename3(stagingDir, profileDir);
|
|
15941
16221
|
}
|
|
15942
16222
|
moveSpin?.succeed("Installation complete");
|
|
15943
16223
|
if (!quiet) {
|
|
@@ -16122,6 +16402,25 @@ async function runProfileList(options2) {
|
|
|
16122
16402
|
}
|
|
16123
16403
|
}
|
|
16124
16404
|
|
|
16405
|
+
// src/commands/profile/move.ts
|
|
16406
|
+
function registerProfileMoveCommand(parent) {
|
|
16407
|
+
parent.command("move <old-name> <new-name>").alias("mv").description("Move (rename) a profile").action(async (oldName, newName) => {
|
|
16408
|
+
try {
|
|
16409
|
+
await runProfileMove(oldName, newName);
|
|
16410
|
+
} catch (error) {
|
|
16411
|
+
handleError(error);
|
|
16412
|
+
}
|
|
16413
|
+
});
|
|
16414
|
+
}
|
|
16415
|
+
async function runProfileMove(oldName, newName) {
|
|
16416
|
+
const manager = await ProfileManager.requireInitialized();
|
|
16417
|
+
const { warnActiveProfile } = await manager.move(oldName, newName);
|
|
16418
|
+
if (warnActiveProfile) {
|
|
16419
|
+
logger.warn(`Moving active profile. Update OCX_PROFILE env var to "${newName}".`);
|
|
16420
|
+
}
|
|
16421
|
+
logger.success(`Moved profile "${oldName}" \u2192 "${newName}"`);
|
|
16422
|
+
}
|
|
16423
|
+
|
|
16125
16424
|
// src/commands/profile/remove.ts
|
|
16126
16425
|
function registerProfileRemoveCommand(parent) {
|
|
16127
16426
|
parent.command("remove <name>").alias("rm").description("Delete a global profile").action(async (name) => {
|
|
@@ -16180,6 +16479,7 @@ function registerProfileCommand(program2) {
|
|
|
16180
16479
|
registerProfileListCommand(profile);
|
|
16181
16480
|
registerProfileAddCommand(profile);
|
|
16182
16481
|
registerProfileRemoveCommand(profile);
|
|
16482
|
+
registerProfileMoveCommand(profile);
|
|
16183
16483
|
registerProfileShowCommand(profile);
|
|
16184
16484
|
}
|
|
16185
16485
|
|
|
@@ -16517,9 +16817,460 @@ async function runSearchCore(query, options2, provider) {
|
|
|
16517
16817
|
}
|
|
16518
16818
|
}
|
|
16519
16819
|
|
|
16820
|
+
// src/commands/self/uninstall.ts
|
|
16821
|
+
import { existsSync as existsSync11, lstatSync as lstatSync2, readdirSync as readdirSync2, realpathSync, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
16822
|
+
import { homedir as homedir4 } from "os";
|
|
16823
|
+
import path9 from "path";
|
|
16824
|
+
|
|
16825
|
+
// src/self-update/detect-method.ts
|
|
16826
|
+
function parseInstallMethod(input) {
|
|
16827
|
+
const VALID_METHODS = ["curl", "npm", "yarn", "pnpm", "bun"];
|
|
16828
|
+
const method = VALID_METHODS.find((m) => m === input);
|
|
16829
|
+
if (!method) {
|
|
16830
|
+
throw new SelfUpdateError(`Invalid install method: "${input}"
|
|
16831
|
+
Valid methods: ${VALID_METHODS.join(", ")}`);
|
|
16832
|
+
}
|
|
16833
|
+
return method;
|
|
16834
|
+
}
|
|
16835
|
+
var isCompiledBinary = () => Bun.main.startsWith("/$bunfs/");
|
|
16836
|
+
var isTempExecution = (path9) => path9.includes("/_npx/") || path9.includes("/.cache/bunx/") || path9.includes("/.pnpm/_temp/");
|
|
16837
|
+
var isYarnGlobalInstall = (path9) => path9.includes("/.yarn/global") || path9.includes("/.config/yarn/global");
|
|
16838
|
+
var isPnpmGlobalInstall = (path9) => path9.includes("/.pnpm/") || path9.includes("/pnpm/global");
|
|
16839
|
+
var isBunGlobalInstall = (path9) => path9.includes("/.bun/bin") || path9.includes("/.bun/install/global");
|
|
16840
|
+
var isNpmGlobalInstall = (path9) => path9.includes("/.npm/") || path9.includes("/node_modules/");
|
|
16841
|
+
function detectInstallMethod() {
|
|
16842
|
+
if (isCompiledBinary()) {
|
|
16843
|
+
return "curl";
|
|
16844
|
+
}
|
|
16845
|
+
const scriptPath = process.argv[1] ?? "";
|
|
16846
|
+
if (isTempExecution(scriptPath))
|
|
16847
|
+
return "unknown";
|
|
16848
|
+
if (isYarnGlobalInstall(scriptPath))
|
|
16849
|
+
return "yarn";
|
|
16850
|
+
if (isPnpmGlobalInstall(scriptPath))
|
|
16851
|
+
return "pnpm";
|
|
16852
|
+
if (isBunGlobalInstall(scriptPath))
|
|
16853
|
+
return "bun";
|
|
16854
|
+
if (isNpmGlobalInstall(scriptPath))
|
|
16855
|
+
return "npm";
|
|
16856
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
16857
|
+
if (userAgent.includes("yarn"))
|
|
16858
|
+
return "yarn";
|
|
16859
|
+
if (userAgent.includes("pnpm"))
|
|
16860
|
+
return "pnpm";
|
|
16861
|
+
if (userAgent.includes("bun"))
|
|
16862
|
+
return "bun";
|
|
16863
|
+
if (userAgent.includes("npm"))
|
|
16864
|
+
return "npm";
|
|
16865
|
+
return "unknown";
|
|
16866
|
+
}
|
|
16867
|
+
function getExecutablePath() {
|
|
16868
|
+
if (typeof Bun !== "undefined" && Bun.main.startsWith("/$bunfs/")) {
|
|
16869
|
+
return process.execPath;
|
|
16870
|
+
}
|
|
16871
|
+
return process.argv[1] ?? process.execPath;
|
|
16872
|
+
}
|
|
16873
|
+
|
|
16874
|
+
// src/commands/self/uninstall.ts
|
|
16875
|
+
var UNINSTALL_EXIT_CODES = {
|
|
16876
|
+
SUCCESS: 0,
|
|
16877
|
+
ERROR: 1,
|
|
16878
|
+
SAFETY_ERROR: 2
|
|
16879
|
+
};
|
|
16880
|
+
function isNodeError(err) {
|
|
16881
|
+
return err instanceof Error && "code" in err;
|
|
16882
|
+
}
|
|
16883
|
+
function tildify(absolutePath) {
|
|
16884
|
+
const home = homedir4();
|
|
16885
|
+
if (!home)
|
|
16886
|
+
return absolutePath;
|
|
16887
|
+
if (absolutePath === home)
|
|
16888
|
+
return "~";
|
|
16889
|
+
if (absolutePath.startsWith(home + path9.sep)) {
|
|
16890
|
+
return `~${absolutePath.slice(home.length)}`;
|
|
16891
|
+
}
|
|
16892
|
+
return absolutePath;
|
|
16893
|
+
}
|
|
16894
|
+
function getRelativePathIfContained(parent, child) {
|
|
16895
|
+
const normalizedParent = path9.normalize(parent);
|
|
16896
|
+
const normalizedChild = path9.normalize(child);
|
|
16897
|
+
const relative4 = path9.relative(normalizedParent, normalizedChild);
|
|
16898
|
+
if (relative4.startsWith("..") || path9.isAbsolute(relative4)) {
|
|
16899
|
+
return null;
|
|
16900
|
+
}
|
|
16901
|
+
return relative4;
|
|
16902
|
+
}
|
|
16903
|
+
function isLexicallyInside(root, target) {
|
|
16904
|
+
return getRelativePathIfContained(root, target) !== null;
|
|
16905
|
+
}
|
|
16906
|
+
function isRealpathInside(root, target) {
|
|
16907
|
+
if (!existsSync11(target)) {
|
|
16908
|
+
return { contained: true };
|
|
16909
|
+
}
|
|
16910
|
+
try {
|
|
16911
|
+
const realRoot = realpathSync(root);
|
|
16912
|
+
const realTarget = realpathSync(target);
|
|
16913
|
+
return { contained: getRelativePathIfContained(realRoot, realTarget) !== null };
|
|
16914
|
+
} catch (err) {
|
|
16915
|
+
if (isNodeError(err) && (err.code === "EACCES" || err.code === "EPERM")) {
|
|
16916
|
+
return { contained: false, error: "permission" };
|
|
16917
|
+
}
|
|
16918
|
+
return { contained: false, error: "io" };
|
|
16919
|
+
}
|
|
16920
|
+
}
|
|
16921
|
+
function validateRootDirectory(rootPath) {
|
|
16922
|
+
try {
|
|
16923
|
+
const stats = lstatSync2(rootPath);
|
|
16924
|
+
if (stats.isSymbolicLink()) {
|
|
16925
|
+
return { valid: false, reason: "symlink" };
|
|
16926
|
+
}
|
|
16927
|
+
if (!stats.isDirectory()) {
|
|
16928
|
+
return { valid: false, reason: "not-directory" };
|
|
16929
|
+
}
|
|
16930
|
+
return { valid: true };
|
|
16931
|
+
} catch (err) {
|
|
16932
|
+
if (isNodeError(err)) {
|
|
16933
|
+
if (err.code === "ENOENT") {
|
|
16934
|
+
return { valid: false, reason: "not-found" };
|
|
16935
|
+
}
|
|
16936
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
16937
|
+
return { valid: false, reason: "permission" };
|
|
16938
|
+
}
|
|
16939
|
+
}
|
|
16940
|
+
return { valid: false, reason: "permission" };
|
|
16941
|
+
}
|
|
16942
|
+
}
|
|
16943
|
+
function getPathKind(targetPath) {
|
|
16944
|
+
if (!existsSync11(targetPath)) {
|
|
16945
|
+
return "missing";
|
|
16946
|
+
}
|
|
16947
|
+
try {
|
|
16948
|
+
const stats = lstatSync2(targetPath);
|
|
16949
|
+
if (stats.isSymbolicLink()) {
|
|
16950
|
+
return "symlink";
|
|
16951
|
+
}
|
|
16952
|
+
if (stats.isDirectory()) {
|
|
16953
|
+
return "directory";
|
|
16954
|
+
}
|
|
16955
|
+
return "file";
|
|
16956
|
+
} catch {
|
|
16957
|
+
return "missing";
|
|
16958
|
+
}
|
|
16959
|
+
}
|
|
16960
|
+
function isDirectoryEmpty(dirPath) {
|
|
16961
|
+
if (!existsSync11(dirPath)) {
|
|
16962
|
+
return true;
|
|
16963
|
+
}
|
|
16964
|
+
try {
|
|
16965
|
+
const entries = readdirSync2(dirPath);
|
|
16966
|
+
return entries.length === 0;
|
|
16967
|
+
} catch {
|
|
16968
|
+
return false;
|
|
16969
|
+
}
|
|
16970
|
+
}
|
|
16971
|
+
function classifyTargetSafety(target) {
|
|
16972
|
+
if (target.kind === "missing") {
|
|
16973
|
+
return "safe";
|
|
16974
|
+
}
|
|
16975
|
+
if (target.kind === "symlink") {
|
|
16976
|
+
return isLexicallyInside(target.rootPath, target.absolutePath) ? "safe" : "forbidden";
|
|
16977
|
+
}
|
|
16978
|
+
const result = isRealpathInside(target.rootPath, target.absolutePath);
|
|
16979
|
+
if (result.error) {
|
|
16980
|
+
return "error";
|
|
16981
|
+
}
|
|
16982
|
+
return result.contained ? "safe" : "forbidden";
|
|
16983
|
+
}
|
|
16984
|
+
function isPackageManaged(method) {
|
|
16985
|
+
return method === "npm" || method === "pnpm" || method === "bun" || method === "yarn";
|
|
16986
|
+
}
|
|
16987
|
+
function getPackageManagerCommand(method) {
|
|
16988
|
+
switch (method) {
|
|
16989
|
+
case "npm":
|
|
16990
|
+
return "npm uninstall -g ocx";
|
|
16991
|
+
case "pnpm":
|
|
16992
|
+
return "pnpm remove -g ocx";
|
|
16993
|
+
case "bun":
|
|
16994
|
+
return "bun remove -g ocx";
|
|
16995
|
+
case "yarn":
|
|
16996
|
+
return "yarn global remove ocx";
|
|
16997
|
+
default:
|
|
16998
|
+
return "npm uninstall -g ocx";
|
|
16999
|
+
}
|
|
17000
|
+
}
|
|
17001
|
+
function getGlobalConfigRoot() {
|
|
17002
|
+
const base = process.env.XDG_CONFIG_HOME || path9.join(homedir4(), ".config");
|
|
17003
|
+
return path9.join(base, "opencode");
|
|
17004
|
+
}
|
|
17005
|
+
function buildConfigTargets() {
|
|
17006
|
+
const rootPath = getGlobalConfigRoot();
|
|
17007
|
+
const targets = [];
|
|
17008
|
+
const profilesDir = getProfilesDir();
|
|
17009
|
+
const profilesRelative = getRelativePathIfContained(rootPath, profilesDir);
|
|
17010
|
+
if (profilesRelative) {
|
|
17011
|
+
const kind = getPathKind(profilesDir);
|
|
17012
|
+
targets.push({
|
|
17013
|
+
rootPath,
|
|
17014
|
+
relativePath: profilesRelative,
|
|
17015
|
+
absolutePath: profilesDir,
|
|
17016
|
+
displayPath: tildify(profilesDir),
|
|
17017
|
+
kind,
|
|
17018
|
+
deleteIfEmpty: false,
|
|
17019
|
+
safetyStatus: classifyTargetSafety({ rootPath, absolutePath: profilesDir, kind })
|
|
17020
|
+
});
|
|
17021
|
+
}
|
|
17022
|
+
const globalConfig = getGlobalConfig();
|
|
17023
|
+
const configRelative = getRelativePathIfContained(rootPath, globalConfig);
|
|
17024
|
+
if (configRelative) {
|
|
17025
|
+
const kind = getPathKind(globalConfig);
|
|
17026
|
+
targets.push({
|
|
17027
|
+
rootPath,
|
|
17028
|
+
relativePath: configRelative,
|
|
17029
|
+
absolutePath: globalConfig,
|
|
17030
|
+
displayPath: tildify(globalConfig),
|
|
17031
|
+
kind,
|
|
17032
|
+
deleteIfEmpty: false,
|
|
17033
|
+
safetyStatus: classifyTargetSafety({ rootPath, absolutePath: globalConfig, kind })
|
|
17034
|
+
});
|
|
17035
|
+
}
|
|
17036
|
+
const rootKind = getPathKind(rootPath);
|
|
17037
|
+
targets.push({
|
|
17038
|
+
rootPath,
|
|
17039
|
+
relativePath: ".",
|
|
17040
|
+
absolutePath: rootPath,
|
|
17041
|
+
displayPath: tildify(rootPath),
|
|
17042
|
+
kind: rootKind,
|
|
17043
|
+
deleteIfEmpty: true,
|
|
17044
|
+
safetyStatus: rootKind === "missing" ? "safe" : "safe"
|
|
17045
|
+
});
|
|
17046
|
+
return targets;
|
|
17047
|
+
}
|
|
17048
|
+
function buildBinaryTarget() {
|
|
17049
|
+
const method = detectInstallMethod();
|
|
17050
|
+
if (isPackageManaged(method)) {
|
|
17051
|
+
return null;
|
|
17052
|
+
}
|
|
17053
|
+
if (method === "curl") {
|
|
17054
|
+
const binaryPath = getExecutablePath();
|
|
17055
|
+
const kind = getPathKind(binaryPath);
|
|
17056
|
+
const parentDir = path9.dirname(binaryPath);
|
|
17057
|
+
return {
|
|
17058
|
+
rootPath: parentDir,
|
|
17059
|
+
relativePath: path9.basename(binaryPath),
|
|
17060
|
+
absolutePath: binaryPath,
|
|
17061
|
+
displayPath: tildify(binaryPath),
|
|
17062
|
+
kind,
|
|
17063
|
+
deleteIfEmpty: false,
|
|
17064
|
+
safetyStatus: kind === "missing" ? "safe" : "safe"
|
|
17065
|
+
};
|
|
17066
|
+
}
|
|
17067
|
+
return null;
|
|
17068
|
+
}
|
|
17069
|
+
function executeRemoval(target) {
|
|
17070
|
+
if (target.kind === "missing") {
|
|
17071
|
+
return { target, success: true, skipped: true, reason: "not found" };
|
|
17072
|
+
}
|
|
17073
|
+
if (target.safetyStatus === "forbidden") {
|
|
17074
|
+
return {
|
|
17075
|
+
target,
|
|
17076
|
+
success: false,
|
|
17077
|
+
skipped: true,
|
|
17078
|
+
reason: "containment violation",
|
|
17079
|
+
error: new Error("Target escapes containment boundary")
|
|
17080
|
+
};
|
|
17081
|
+
}
|
|
17082
|
+
if (target.deleteIfEmpty && target.kind === "directory") {
|
|
17083
|
+
if (!isDirectoryEmpty(target.absolutePath)) {
|
|
17084
|
+
return { target, success: true, skipped: true, reason: "not empty" };
|
|
17085
|
+
}
|
|
17086
|
+
}
|
|
17087
|
+
try {
|
|
17088
|
+
if (target.kind === "directory") {
|
|
17089
|
+
rmSync2(target.absolutePath, { recursive: true, force: true });
|
|
17090
|
+
} else {
|
|
17091
|
+
unlinkSync2(target.absolutePath);
|
|
17092
|
+
}
|
|
17093
|
+
return { target, success: true, skipped: false };
|
|
17094
|
+
} catch (err) {
|
|
17095
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
17096
|
+
const reason = isNodeError(err) && (err.code === "EACCES" || err.code === "EPERM") ? "permission denied" : undefined;
|
|
17097
|
+
return { target, success: false, skipped: false, reason, error };
|
|
17098
|
+
}
|
|
17099
|
+
}
|
|
17100
|
+
function executeRemovals(targets) {
|
|
17101
|
+
return targets.map(executeRemoval);
|
|
17102
|
+
}
|
|
17103
|
+
function removeBinary(binaryPath) {
|
|
17104
|
+
const target = {
|
|
17105
|
+
rootPath: path9.dirname(binaryPath),
|
|
17106
|
+
relativePath: path9.basename(binaryPath),
|
|
17107
|
+
absolutePath: binaryPath,
|
|
17108
|
+
displayPath: tildify(binaryPath),
|
|
17109
|
+
kind: getPathKind(binaryPath),
|
|
17110
|
+
deleteIfEmpty: false,
|
|
17111
|
+
safetyStatus: "safe"
|
|
17112
|
+
};
|
|
17113
|
+
if (target.kind === "missing") {
|
|
17114
|
+
return { target, success: true, skipped: true, reason: "not found" };
|
|
17115
|
+
}
|
|
17116
|
+
if (process.platform === "win32") {
|
|
17117
|
+
logger.info(`To complete uninstall, manually delete: ${target.displayPath}`);
|
|
17118
|
+
return { target, success: true, skipped: true };
|
|
17119
|
+
}
|
|
17120
|
+
try {
|
|
17121
|
+
unlinkSync2(binaryPath);
|
|
17122
|
+
return { target, success: true, skipped: false };
|
|
17123
|
+
} catch (error) {
|
|
17124
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
17125
|
+
return { target, success: false, skipped: false, reason: "permission denied", error: err };
|
|
17126
|
+
}
|
|
17127
|
+
}
|
|
17128
|
+
function printDryRun(configTargets, binaryTarget, installMethod) {
|
|
17129
|
+
logger.info(`Dry run - the following would be removed:
|
|
17130
|
+
`);
|
|
17131
|
+
const existingConfigTargets = configTargets.filter((t2) => t2.kind !== "missing");
|
|
17132
|
+
for (const target of existingConfigTargets) {
|
|
17133
|
+
const kindLabel = target.kind === "directory" ? "[dir] " : "[file]";
|
|
17134
|
+
const emptyNote = target.deleteIfEmpty ? " (if empty)" : "";
|
|
17135
|
+
logger.log(` ${kindLabel} ${highlight.path(target.displayPath)}${emptyNote}`);
|
|
17136
|
+
}
|
|
17137
|
+
if (binaryTarget && binaryTarget.kind !== "missing") {
|
|
17138
|
+
logger.log(` [bin] ${highlight.path(binaryTarget.displayPath)}`);
|
|
17139
|
+
}
|
|
17140
|
+
if (isPackageManaged(installMethod)) {
|
|
17141
|
+
logger.log("");
|
|
17142
|
+
logger.info(`Binary managed by ${installMethod}. Run:`);
|
|
17143
|
+
logger.log(` ${highlight.command(getPackageManagerCommand(installMethod))}`);
|
|
17144
|
+
}
|
|
17145
|
+
if (existingConfigTargets.length === 0 && (!binaryTarget || binaryTarget.kind === "missing")) {
|
|
17146
|
+
logger.info("Nothing to remove.");
|
|
17147
|
+
}
|
|
17148
|
+
}
|
|
17149
|
+
function printRemovalPlan(configTargets, binaryTarget) {
|
|
17150
|
+
const existingConfigTargets = configTargets.filter((t2) => t2.kind !== "missing");
|
|
17151
|
+
if (existingConfigTargets.length > 0 || binaryTarget && binaryTarget.kind !== "missing") {
|
|
17152
|
+
logger.info("Removing OCX files...");
|
|
17153
|
+
}
|
|
17154
|
+
}
|
|
17155
|
+
function printResults(results, binaryResult, installMethod) {
|
|
17156
|
+
logger.break();
|
|
17157
|
+
for (const result of results) {
|
|
17158
|
+
if (result.skipped) {
|
|
17159
|
+
if (result.reason === "not found") {
|
|
17160
|
+
continue;
|
|
17161
|
+
}
|
|
17162
|
+
if (result.reason === "not empty") {
|
|
17163
|
+
logger.info(`Kept ${highlight.path(result.target.displayPath)} (not empty)`);
|
|
17164
|
+
continue;
|
|
17165
|
+
}
|
|
17166
|
+
if (result.reason === "permission denied") {
|
|
17167
|
+
logger.warn(`Skipped ${highlight.path(result.target.displayPath)} (permission denied)`);
|
|
17168
|
+
continue;
|
|
17169
|
+
}
|
|
17170
|
+
if (result.reason === "containment violation") {
|
|
17171
|
+
logger.warn(`Skipped ${highlight.path(result.target.displayPath)} (containment violation)`);
|
|
17172
|
+
continue;
|
|
17173
|
+
}
|
|
17174
|
+
}
|
|
17175
|
+
if (result.success) {
|
|
17176
|
+
logger.success(`Removed ${highlight.path(result.target.displayPath)}`);
|
|
17177
|
+
} else {
|
|
17178
|
+
logger.error(`Failed to remove ${result.target.displayPath}: ${result.error?.message}`);
|
|
17179
|
+
}
|
|
17180
|
+
}
|
|
17181
|
+
if (binaryResult) {
|
|
17182
|
+
if (binaryResult.skipped && binaryResult.reason === "not found") {} else if (binaryResult.success && !binaryResult.skipped) {
|
|
17183
|
+
logger.success(`Removed binary ${highlight.path(binaryResult.target.displayPath)}`);
|
|
17184
|
+
} else if (!binaryResult.success) {
|
|
17185
|
+
logger.error(`Failed to remove binary ${binaryResult.target.displayPath}: ${binaryResult.error?.message}`);
|
|
17186
|
+
}
|
|
17187
|
+
}
|
|
17188
|
+
if (isPackageManaged(installMethod)) {
|
|
17189
|
+
logger.break();
|
|
17190
|
+
logger.info(`Binary is managed by ${installMethod}. To complete uninstall, run:`);
|
|
17191
|
+
logger.log(` ${highlight.command(getPackageManagerCommand(installMethod))}`);
|
|
17192
|
+
}
|
|
17193
|
+
}
|
|
17194
|
+
function printNothingToRemove() {
|
|
17195
|
+
logger.info("Nothing to remove. OCX is not installed globally.");
|
|
17196
|
+
}
|
|
17197
|
+
async function runUninstall(options2) {
|
|
17198
|
+
const rootPath = getGlobalConfigRoot();
|
|
17199
|
+
const rootValidation = validateRootDirectory(rootPath);
|
|
17200
|
+
if (!rootValidation.valid) {
|
|
17201
|
+
switch (rootValidation.reason) {
|
|
17202
|
+
case "not-found":
|
|
17203
|
+
break;
|
|
17204
|
+
case "symlink":
|
|
17205
|
+
logger.error("Safety error: Global config root is a symlink. Aborting.");
|
|
17206
|
+
process.exit(UNINSTALL_EXIT_CODES.SAFETY_ERROR);
|
|
17207
|
+
break;
|
|
17208
|
+
case "not-directory":
|
|
17209
|
+
logger.error("Safety error: Global config root is not a directory. Aborting.");
|
|
17210
|
+
process.exit(UNINSTALL_EXIT_CODES.SAFETY_ERROR);
|
|
17211
|
+
break;
|
|
17212
|
+
case "permission":
|
|
17213
|
+
logger.error("Error: Cannot access global config root (permission denied).");
|
|
17214
|
+
process.exit(UNINSTALL_EXIT_CODES.ERROR);
|
|
17215
|
+
break;
|
|
17216
|
+
}
|
|
17217
|
+
}
|
|
17218
|
+
const configTargets = buildConfigTargets();
|
|
17219
|
+
const forbiddenTargets = configTargets.filter((t2) => t2.safetyStatus === "forbidden");
|
|
17220
|
+
if (forbiddenTargets.length > 0) {
|
|
17221
|
+
logger.error("Safety error: Target escapes containment boundary:");
|
|
17222
|
+
for (const target of forbiddenTargets) {
|
|
17223
|
+
logger.error(` ${target.displayPath}`);
|
|
17224
|
+
}
|
|
17225
|
+
process.exit(UNINSTALL_EXIT_CODES.SAFETY_ERROR);
|
|
17226
|
+
}
|
|
17227
|
+
const errorTargets = configTargets.filter((t2) => t2.safetyStatus === "error");
|
|
17228
|
+
if (errorTargets.length > 0) {
|
|
17229
|
+
logger.error("Error: Cannot verify containment for targets (permission/IO error):");
|
|
17230
|
+
for (const target of errorTargets) {
|
|
17231
|
+
logger.error(` ${target.displayPath}`);
|
|
17232
|
+
}
|
|
17233
|
+
process.exit(UNINSTALL_EXIT_CODES.ERROR);
|
|
17234
|
+
}
|
|
17235
|
+
const installMethod = detectInstallMethod();
|
|
17236
|
+
const binaryTarget = buildBinaryTarget();
|
|
17237
|
+
const existingConfigTargets = configTargets.filter((t2) => t2.kind !== "missing");
|
|
17238
|
+
const hasBinary = binaryTarget && binaryTarget.kind !== "missing";
|
|
17239
|
+
const hasPackageManager = isPackageManaged(installMethod);
|
|
17240
|
+
if (existingConfigTargets.length === 0 && !hasBinary && !hasPackageManager) {
|
|
17241
|
+
printNothingToRemove();
|
|
17242
|
+
process.exit(UNINSTALL_EXIT_CODES.SUCCESS);
|
|
17243
|
+
}
|
|
17244
|
+
if (options2.dryRun) {
|
|
17245
|
+
printDryRun(configTargets, binaryTarget, installMethod);
|
|
17246
|
+
process.exit(UNINSTALL_EXIT_CODES.SUCCESS);
|
|
17247
|
+
}
|
|
17248
|
+
printRemovalPlan(configTargets, binaryTarget);
|
|
17249
|
+
const configResults = executeRemovals(configTargets);
|
|
17250
|
+
let binaryResult = null;
|
|
17251
|
+
if (binaryTarget) {
|
|
17252
|
+
binaryResult = removeBinary(binaryTarget.absolutePath);
|
|
17253
|
+
}
|
|
17254
|
+
printResults(configResults, binaryResult, installMethod);
|
|
17255
|
+
const hasFailures = configResults.some((r2) => !r2.success && !r2.skipped);
|
|
17256
|
+
const binaryFailed = binaryResult && !binaryResult.success && !binaryResult.skipped;
|
|
17257
|
+
if (hasFailures || binaryFailed) {
|
|
17258
|
+
process.exit(UNINSTALL_EXIT_CODES.ERROR);
|
|
17259
|
+
}
|
|
17260
|
+
if (isPackageManaged(installMethod)) {
|
|
17261
|
+
process.exit(UNINSTALL_EXIT_CODES.ERROR);
|
|
17262
|
+
}
|
|
17263
|
+
process.exit(UNINSTALL_EXIT_CODES.SUCCESS);
|
|
17264
|
+
}
|
|
17265
|
+
function registerSelfUninstallCommand(parent) {
|
|
17266
|
+
parent.command("uninstall").description("Remove OCX global configuration and binary").option("--dry-run", "Preview what would be removed").action(wrapAction(async (options2) => {
|
|
17267
|
+
await runUninstall(options2);
|
|
17268
|
+
}));
|
|
17269
|
+
}
|
|
17270
|
+
|
|
16520
17271
|
// src/self-update/version-provider.ts
|
|
16521
17272
|
class BuildTimeVersionProvider {
|
|
16522
|
-
version = "1.4.
|
|
17273
|
+
version = "1.4.3";
|
|
16523
17274
|
}
|
|
16524
17275
|
var defaultVersionProvider = new BuildTimeVersionProvider;
|
|
16525
17276
|
|
|
@@ -16582,57 +17333,8 @@ async function checkForUpdate(versionProvider, timeoutMs = VERSION_CHECK_TIMEOUT
|
|
|
16582
17333
|
}
|
|
16583
17334
|
}
|
|
16584
17335
|
|
|
16585
|
-
// src/self-update/detect-method.ts
|
|
16586
|
-
function parseInstallMethod(input) {
|
|
16587
|
-
const VALID_METHODS = ["curl", "npm", "yarn", "pnpm", "bun"];
|
|
16588
|
-
const method = VALID_METHODS.find((m) => m === input);
|
|
16589
|
-
if (!method) {
|
|
16590
|
-
throw new SelfUpdateError(`Invalid install method: "${input}"
|
|
16591
|
-
Valid methods: ${VALID_METHODS.join(", ")}`);
|
|
16592
|
-
}
|
|
16593
|
-
return method;
|
|
16594
|
-
}
|
|
16595
|
-
var isCompiledBinary = () => Bun.main.startsWith("/$bunfs/");
|
|
16596
|
-
var isTempExecution = (path8) => path8.includes("/_npx/") || path8.includes("/.cache/bunx/") || path8.includes("/.pnpm/_temp/");
|
|
16597
|
-
var isYarnGlobalInstall = (path8) => path8.includes("/.yarn/global") || path8.includes("/.config/yarn/global");
|
|
16598
|
-
var isPnpmGlobalInstall = (path8) => path8.includes("/.pnpm/") || path8.includes("/pnpm/global");
|
|
16599
|
-
var isBunGlobalInstall = (path8) => path8.includes("/.bun/bin") || path8.includes("/.bun/install/global");
|
|
16600
|
-
var isNpmGlobalInstall = (path8) => path8.includes("/.npm/") || path8.includes("/node_modules/");
|
|
16601
|
-
function detectInstallMethod() {
|
|
16602
|
-
if (isCompiledBinary()) {
|
|
16603
|
-
return "curl";
|
|
16604
|
-
}
|
|
16605
|
-
const scriptPath = process.argv[1] ?? "";
|
|
16606
|
-
if (isTempExecution(scriptPath))
|
|
16607
|
-
return "unknown";
|
|
16608
|
-
if (isYarnGlobalInstall(scriptPath))
|
|
16609
|
-
return "yarn";
|
|
16610
|
-
if (isPnpmGlobalInstall(scriptPath))
|
|
16611
|
-
return "pnpm";
|
|
16612
|
-
if (isBunGlobalInstall(scriptPath))
|
|
16613
|
-
return "bun";
|
|
16614
|
-
if (isNpmGlobalInstall(scriptPath))
|
|
16615
|
-
return "npm";
|
|
16616
|
-
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
16617
|
-
if (userAgent.includes("yarn"))
|
|
16618
|
-
return "yarn";
|
|
16619
|
-
if (userAgent.includes("pnpm"))
|
|
16620
|
-
return "pnpm";
|
|
16621
|
-
if (userAgent.includes("bun"))
|
|
16622
|
-
return "bun";
|
|
16623
|
-
if (userAgent.includes("npm"))
|
|
16624
|
-
return "npm";
|
|
16625
|
-
return "unknown";
|
|
16626
|
-
}
|
|
16627
|
-
function getExecutablePath() {
|
|
16628
|
-
if (typeof Bun !== "undefined" && Bun.main.startsWith("/$bunfs/")) {
|
|
16629
|
-
return process.execPath;
|
|
16630
|
-
}
|
|
16631
|
-
return process.argv[1] ?? process.execPath;
|
|
16632
|
-
}
|
|
16633
|
-
|
|
16634
17336
|
// src/self-update/download.ts
|
|
16635
|
-
import { chmodSync, existsSync as
|
|
17337
|
+
import { chmodSync, existsSync as existsSync12, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
16636
17338
|
var GITHUB_REPO2 = "kdcokenny/ocx";
|
|
16637
17339
|
var DEFAULT_DOWNLOAD_BASE_URL = `https://github.com/${GITHUB_REPO2}/releases/download`;
|
|
16638
17340
|
var PLATFORM_MAP = {
|
|
@@ -16710,8 +17412,8 @@ async function downloadToTemp(version) {
|
|
|
16710
17412
|
try {
|
|
16711
17413
|
chmodSync(tempPath, 493);
|
|
16712
17414
|
} catch (error) {
|
|
16713
|
-
if (
|
|
16714
|
-
|
|
17415
|
+
if (existsSync12(tempPath)) {
|
|
17416
|
+
unlinkSync3(tempPath);
|
|
16715
17417
|
}
|
|
16716
17418
|
throw new SelfUpdateError(`Failed to set permissions: ${error instanceof Error ? error.message : String(error)}`);
|
|
16717
17419
|
}
|
|
@@ -16720,31 +17422,31 @@ async function downloadToTemp(version) {
|
|
|
16720
17422
|
function atomicReplace(tempPath, execPath) {
|
|
16721
17423
|
const backupPath = `${execPath}.backup`;
|
|
16722
17424
|
try {
|
|
16723
|
-
if (
|
|
17425
|
+
if (existsSync12(execPath)) {
|
|
16724
17426
|
renameSync2(execPath, backupPath);
|
|
16725
17427
|
}
|
|
16726
17428
|
renameSync2(tempPath, execPath);
|
|
16727
|
-
if (
|
|
16728
|
-
|
|
17429
|
+
if (existsSync12(backupPath)) {
|
|
17430
|
+
unlinkSync3(backupPath);
|
|
16729
17431
|
}
|
|
16730
17432
|
} catch (error) {
|
|
16731
|
-
if (
|
|
17433
|
+
if (existsSync12(backupPath) && !existsSync12(execPath)) {
|
|
16732
17434
|
try {
|
|
16733
17435
|
renameSync2(backupPath, execPath);
|
|
16734
17436
|
} catch {}
|
|
16735
17437
|
}
|
|
16736
|
-
if (
|
|
17438
|
+
if (existsSync12(tempPath)) {
|
|
16737
17439
|
try {
|
|
16738
|
-
|
|
17440
|
+
unlinkSync3(tempPath);
|
|
16739
17441
|
} catch {}
|
|
16740
17442
|
}
|
|
16741
17443
|
throw new SelfUpdateError(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
16742
17444
|
}
|
|
16743
17445
|
}
|
|
16744
17446
|
function cleanupTempFile(tempPath) {
|
|
16745
|
-
if (
|
|
17447
|
+
if (existsSync12(tempPath)) {
|
|
16746
17448
|
try {
|
|
16747
|
-
|
|
17449
|
+
unlinkSync3(tempPath);
|
|
16748
17450
|
} catch {}
|
|
16749
17451
|
}
|
|
16750
17452
|
}
|
|
@@ -16913,12 +17615,13 @@ function registerSelfUpdateCommand(parent) {
|
|
|
16913
17615
|
// src/commands/self/index.ts
|
|
16914
17616
|
function registerSelfCommand(program2) {
|
|
16915
17617
|
const self = program2.command("self").description("Manage the OCX CLI");
|
|
17618
|
+
registerSelfUninstallCommand(self);
|
|
16916
17619
|
registerSelfUpdateCommand(self);
|
|
16917
17620
|
}
|
|
16918
17621
|
|
|
16919
17622
|
// src/commands/update.ts
|
|
16920
17623
|
import { createHash as createHash4 } from "crypto";
|
|
16921
|
-
import { existsSync as
|
|
17624
|
+
import { existsSync as existsSync13 } from "fs";
|
|
16922
17625
|
import { mkdir as mkdir9, writeFile as writeFile4 } from "fs/promises";
|
|
16923
17626
|
import { dirname as dirname6, join as join10 } from "path";
|
|
16924
17627
|
function registerUpdateCommand(program2) {
|
|
@@ -17060,7 +17763,7 @@ Version cannot be empty. Use 'kdco/agents@1.2.0' or omit the version for latest.
|
|
|
17060
17763
|
continue;
|
|
17061
17764
|
const targetPath = join10(provider.cwd, fileObj.target);
|
|
17062
17765
|
const targetDir = dirname6(targetPath);
|
|
17063
|
-
if (!
|
|
17766
|
+
if (!existsSync13(targetDir)) {
|
|
17064
17767
|
await mkdir9(targetDir, { recursive: true });
|
|
17065
17768
|
}
|
|
17066
17769
|
await writeFile4(targetPath, file.content);
|
|
@@ -17209,8 +17912,8 @@ function shouldCheckForUpdate() {
|
|
|
17209
17912
|
return true;
|
|
17210
17913
|
}
|
|
17211
17914
|
function registerUpdateCheckHook(program2) {
|
|
17212
|
-
program2.hook("postAction", async (
|
|
17213
|
-
if (
|
|
17915
|
+
program2.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
17916
|
+
if (actionCommand.name() === "update" && actionCommand.parent?.name() === "self") {
|
|
17214
17917
|
return;
|
|
17215
17918
|
}
|
|
17216
17919
|
if (!shouldCheckForUpdate())
|
|
@@ -17224,7 +17927,7 @@ function registerUpdateCheckHook(program2) {
|
|
|
17224
17927
|
});
|
|
17225
17928
|
}
|
|
17226
17929
|
// src/index.ts
|
|
17227
|
-
var version = "1.4.
|
|
17930
|
+
var version = "1.4.3";
|
|
17228
17931
|
async function main2() {
|
|
17229
17932
|
const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
|
|
17230
17933
|
registerInitCommand(program2);
|
|
@@ -17256,4 +17959,4 @@ export {
|
|
|
17256
17959
|
buildRegistry
|
|
17257
17960
|
};
|
|
17258
17961
|
|
|
17259
|
-
//# debugId=
|
|
17962
|
+
//# debugId=947205F975538A0764756E2164756E21
|