ocx 1.4.1 → 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 +1344 -493
- package/dist/index.js.map +28 -26
- 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 = {
|
|
@@ -9824,7 +9824,8 @@ var EXIT_CODES = {
|
|
|
9824
9824
|
NOT_FOUND: 66,
|
|
9825
9825
|
NETWORK: 69,
|
|
9826
9826
|
CONFIG: 78,
|
|
9827
|
-
INTEGRITY: 1
|
|
9827
|
+
INTEGRITY: 1,
|
|
9828
|
+
CONFLICT: 6
|
|
9828
9829
|
};
|
|
9829
9830
|
|
|
9830
9831
|
class OCXError extends Error {
|
|
@@ -9846,9 +9847,15 @@ class NotFoundError extends OCXError {
|
|
|
9846
9847
|
}
|
|
9847
9848
|
|
|
9848
9849
|
class NetworkError extends OCXError {
|
|
9849
|
-
|
|
9850
|
+
url;
|
|
9851
|
+
status;
|
|
9852
|
+
statusText;
|
|
9853
|
+
constructor(message, options) {
|
|
9850
9854
|
super(message, "NETWORK_ERROR", EXIT_CODES.NETWORK);
|
|
9851
9855
|
this.name = "NetworkError";
|
|
9856
|
+
this.url = options?.url;
|
|
9857
|
+
this.status = options?.status;
|
|
9858
|
+
this.statusText = options?.statusText;
|
|
9852
9859
|
}
|
|
9853
9860
|
}
|
|
9854
9861
|
|
|
@@ -9868,12 +9875,15 @@ class ValidationError extends OCXError {
|
|
|
9868
9875
|
|
|
9869
9876
|
class ConflictError extends OCXError {
|
|
9870
9877
|
constructor(message) {
|
|
9871
|
-
super(message, "CONFLICT", EXIT_CODES.
|
|
9878
|
+
super(message, "CONFLICT", EXIT_CODES.CONFLICT);
|
|
9872
9879
|
this.name = "ConflictError";
|
|
9873
9880
|
}
|
|
9874
9881
|
}
|
|
9875
9882
|
|
|
9876
9883
|
class IntegrityError extends OCXError {
|
|
9884
|
+
component;
|
|
9885
|
+
expected;
|
|
9886
|
+
found;
|
|
9877
9887
|
constructor(component, expected, found) {
|
|
9878
9888
|
const message = `Integrity verification failed for "${component}"
|
|
9879
9889
|
` + ` Expected: ${expected}
|
|
@@ -9882,6 +9892,9 @@ class IntegrityError extends OCXError {
|
|
|
9882
9892
|
` + `The registry content has changed since this component was locked.
|
|
9883
9893
|
` + `Use 'ocx update ${component}' to intentionally update this component.`;
|
|
9884
9894
|
super(message, "INTEGRITY_ERROR", EXIT_CODES.INTEGRITY);
|
|
9895
|
+
this.component = component;
|
|
9896
|
+
this.expected = expected;
|
|
9897
|
+
this.found = found;
|
|
9885
9898
|
this.name = "IntegrityError";
|
|
9886
9899
|
}
|
|
9887
9900
|
}
|
|
@@ -9901,33 +9914,124 @@ class OcxConfigError extends OCXError {
|
|
|
9901
9914
|
}
|
|
9902
9915
|
|
|
9903
9916
|
class ProfileNotFoundError extends OCXError {
|
|
9904
|
-
|
|
9905
|
-
|
|
9917
|
+
profile;
|
|
9918
|
+
constructor(profile) {
|
|
9919
|
+
super(`Profile "${profile}" not found`, "NOT_FOUND", EXIT_CODES.NOT_FOUND);
|
|
9920
|
+
this.profile = profile;
|
|
9906
9921
|
this.name = "ProfileNotFoundError";
|
|
9907
9922
|
}
|
|
9908
9923
|
}
|
|
9909
9924
|
|
|
9910
9925
|
class ProfileExistsError extends OCXError {
|
|
9911
|
-
|
|
9912
|
-
|
|
9926
|
+
profile;
|
|
9927
|
+
constructor(profile) {
|
|
9928
|
+
super(`Profile "${profile}" already exists. Use --force to overwrite.`, "CONFLICT", EXIT_CODES.CONFLICT);
|
|
9929
|
+
this.profile = profile;
|
|
9913
9930
|
this.name = "ProfileExistsError";
|
|
9914
9931
|
}
|
|
9915
9932
|
}
|
|
9916
9933
|
|
|
9934
|
+
class RegistryExistsError extends OCXError {
|
|
9935
|
+
registryName;
|
|
9936
|
+
existingUrl;
|
|
9937
|
+
newUrl;
|
|
9938
|
+
targetLabel;
|
|
9939
|
+
constructor(registryName, existingUrl, newUrl, targetLabel) {
|
|
9940
|
+
const target = targetLabel ? ` in ${targetLabel}` : "";
|
|
9941
|
+
const message = `Registry "${registryName}" already exists${target}.
|
|
9942
|
+
` + ` Current: ${existingUrl}
|
|
9943
|
+
` + ` New: ${newUrl}
|
|
9944
|
+
|
|
9945
|
+
` + `Use --force to overwrite.`;
|
|
9946
|
+
super(message, "CONFLICT", EXIT_CODES.CONFLICT);
|
|
9947
|
+
this.registryName = registryName;
|
|
9948
|
+
this.existingUrl = existingUrl;
|
|
9949
|
+
this.newUrl = newUrl;
|
|
9950
|
+
this.targetLabel = targetLabel;
|
|
9951
|
+
this.name = "RegistryExistsError";
|
|
9952
|
+
}
|
|
9953
|
+
}
|
|
9954
|
+
|
|
9917
9955
|
class InvalidProfileNameError extends OCXError {
|
|
9918
|
-
|
|
9919
|
-
|
|
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;
|
|
9920
9962
|
this.name = "InvalidProfileNameError";
|
|
9921
9963
|
}
|
|
9922
9964
|
}
|
|
9923
9965
|
|
|
9924
9966
|
class ProfilesNotInitializedError extends OCXError {
|
|
9925
9967
|
constructor() {
|
|
9926
|
-
super("Profiles not initialized. Run 'ocx
|
|
9968
|
+
super("Profiles not initialized. Run 'ocx init --global' first.", "NOT_FOUND", EXIT_CODES.NOT_FOUND);
|
|
9927
9969
|
this.name = "ProfilesNotInitializedError";
|
|
9928
9970
|
}
|
|
9929
9971
|
}
|
|
9930
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
|
+
|
|
9931
10035
|
// src/schemas/registry.ts
|
|
9932
10036
|
var npmSpecifierSchema = exports_external.string().refine((val) => val.startsWith("npm:"), {
|
|
9933
10037
|
message: 'npm specifier must start with "npm:" prefix'
|
|
@@ -9977,11 +10081,11 @@ var componentTypeSchema = exports_external.enum([
|
|
|
9977
10081
|
"ocx:bundle",
|
|
9978
10082
|
"ocx:profile"
|
|
9979
10083
|
]);
|
|
9980
|
-
var
|
|
9981
|
-
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/"), {
|
|
9982
10086
|
message: 'Target path must start with ".opencode/"'
|
|
9983
|
-
}).refine((
|
|
9984
|
-
const parts =
|
|
10087
|
+
}).refine((path2) => {
|
|
10088
|
+
const parts = path2.split("/");
|
|
9985
10089
|
const dir = parts[1];
|
|
9986
10090
|
if (!dir)
|
|
9987
10091
|
return false;
|
|
@@ -10110,7 +10214,7 @@ var componentManifestSchema = exports_external.object({
|
|
|
10110
10214
|
opencode: opencodeConfigSchema.optional()
|
|
10111
10215
|
});
|
|
10112
10216
|
function validateSafePath(filePath) {
|
|
10113
|
-
if (
|
|
10217
|
+
if (isAbsolute2(filePath)) {
|
|
10114
10218
|
throw new ValidationError(`Invalid path: "${filePath}" - absolute paths not allowed`);
|
|
10115
10219
|
}
|
|
10116
10220
|
if (filePath.startsWith("~")) {
|
|
@@ -10127,17 +10231,18 @@ function inferTargetPath(sourcePath) {
|
|
|
10127
10231
|
function validateFileTarget(target, componentType) {
|
|
10128
10232
|
const isProfile = componentType === "ocx:profile";
|
|
10129
10233
|
if (isProfile) {
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
if (!isProfileFile && !isOpencodeTarget) {
|
|
10133
|
-
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`);
|
|
10134
10236
|
}
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
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}`);
|
|
10139
10242
|
}
|
|
10243
|
+
throw error;
|
|
10140
10244
|
}
|
|
10245
|
+
return;
|
|
10141
10246
|
} else {
|
|
10142
10247
|
const parseResult = targetPathSchema.safeParse(target);
|
|
10143
10248
|
if (!parseResult.success) {
|
|
@@ -10269,8 +10374,8 @@ var CONFIG_FILE = "ocx.jsonc";
|
|
|
10269
10374
|
var LOCK_FILE = "ocx.lock";
|
|
10270
10375
|
var LOCAL_CONFIG_DIR = ".opencode";
|
|
10271
10376
|
function findOcxConfig(cwd) {
|
|
10272
|
-
const dotOpencodePath =
|
|
10273
|
-
const rootPath =
|
|
10377
|
+
const dotOpencodePath = path2.join(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE);
|
|
10378
|
+
const rootPath = path2.join(cwd, CONFIG_FILE);
|
|
10274
10379
|
const dotOpencodeExists = existsSync(dotOpencodePath);
|
|
10275
10380
|
const rootExists = existsSync(rootPath);
|
|
10276
10381
|
if (dotOpencodeExists && rootExists) {
|
|
@@ -10285,8 +10390,8 @@ function findOcxConfig(cwd) {
|
|
|
10285
10390
|
return { path: dotOpencodePath, exists: false };
|
|
10286
10391
|
}
|
|
10287
10392
|
function findOcxLock(cwd, options) {
|
|
10288
|
-
const dotOpencodePath =
|
|
10289
|
-
const rootPath =
|
|
10393
|
+
const dotOpencodePath = path2.join(cwd, LOCAL_CONFIG_DIR, LOCK_FILE);
|
|
10394
|
+
const rootPath = path2.join(cwd, LOCK_FILE);
|
|
10290
10395
|
if (options?.isFlattened) {
|
|
10291
10396
|
if (existsSync(rootPath)) {
|
|
10292
10397
|
return { path: rootPath, exists: true };
|
|
@@ -10317,8 +10422,8 @@ async function readOcxConfig(cwd) {
|
|
|
10317
10422
|
}
|
|
10318
10423
|
}
|
|
10319
10424
|
async function writeOcxConfig(cwd, config, existingPath) {
|
|
10320
|
-
const configPath = existingPath ??
|
|
10321
|
-
await mkdir(
|
|
10425
|
+
const configPath = existingPath ?? path2.join(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE);
|
|
10426
|
+
await mkdir(path2.dirname(configPath), { recursive: true });
|
|
10322
10427
|
const content = JSON.stringify(config, null, 2);
|
|
10323
10428
|
await Bun.write(configPath, content);
|
|
10324
10429
|
}
|
|
@@ -10333,8 +10438,8 @@ async function readOcxLock(cwd, options) {
|
|
|
10333
10438
|
return ocxLockSchema.parse(json);
|
|
10334
10439
|
}
|
|
10335
10440
|
async function writeOcxLock(cwd, lock, existingPath) {
|
|
10336
|
-
const lockPath = existingPath ??
|
|
10337
|
-
await mkdir(
|
|
10441
|
+
const lockPath = existingPath ?? path2.join(cwd, LOCAL_CONFIG_DIR, LOCK_FILE);
|
|
10442
|
+
await mkdir(path2.dirname(lockPath), { recursive: true });
|
|
10338
10443
|
const content = JSON.stringify(lock, null, 2);
|
|
10339
10444
|
await Bun.write(lockPath, content);
|
|
10340
10445
|
}
|
|
@@ -10342,10 +10447,10 @@ async function writeOcxLock(cwd, lock, existingPath) {
|
|
|
10342
10447
|
// src/utils/paths.ts
|
|
10343
10448
|
import { stat } from "fs/promises";
|
|
10344
10449
|
import { homedir } from "os";
|
|
10345
|
-
import { isAbsolute as
|
|
10450
|
+
import { isAbsolute as isAbsolute3, join } from "path";
|
|
10346
10451
|
function getGlobalConfigPath() {
|
|
10347
10452
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
10348
|
-
const base = xdg &&
|
|
10453
|
+
const base = xdg && isAbsolute3(xdg) ? xdg : join(homedir(), ".config");
|
|
10349
10454
|
return join(base, "opencode");
|
|
10350
10455
|
}
|
|
10351
10456
|
async function globalDirectoryExists() {
|
|
@@ -10399,7 +10504,7 @@ class GlobalConfigProvider {
|
|
|
10399
10504
|
static async requireInitialized() {
|
|
10400
10505
|
const basePath = getGlobalConfigPath();
|
|
10401
10506
|
if (!await globalDirectoryExists()) {
|
|
10402
|
-
throw new ConfigError("Global config not found. Run '
|
|
10507
|
+
throw new ConfigError("Global config not found. Run 'ocx init --global' first.");
|
|
10403
10508
|
}
|
|
10404
10509
|
const config = await readOcxConfig(basePath);
|
|
10405
10510
|
return new GlobalConfigProvider(basePath, config);
|
|
@@ -10414,19 +10519,19 @@ class GlobalConfigProvider {
|
|
|
10414
10519
|
|
|
10415
10520
|
// src/config/resolver.ts
|
|
10416
10521
|
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
10417
|
-
import { join as join2, relative } from "path";
|
|
10522
|
+
import { join as join2, relative as relative2 } from "path";
|
|
10418
10523
|
var {Glob: Glob2 } = globalThis.Bun;
|
|
10419
10524
|
|
|
10420
10525
|
// src/profile/manager.ts
|
|
10421
|
-
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";
|
|
10422
10527
|
|
|
10423
10528
|
// src/schemas/ocx.ts
|
|
10424
10529
|
var {Glob } = globalThis.Bun;
|
|
10425
10530
|
|
|
10426
10531
|
// src/utils/path-helpers.ts
|
|
10427
|
-
import
|
|
10532
|
+
import path3 from "path";
|
|
10428
10533
|
function isAbsolutePath(p) {
|
|
10429
|
-
return
|
|
10534
|
+
return path3.posix.isAbsolute(p) || path3.win32.isAbsolute(p);
|
|
10430
10535
|
}
|
|
10431
10536
|
|
|
10432
10537
|
// src/schemas/common.ts
|
|
@@ -10448,7 +10553,6 @@ var profileOcxConfigSchema = exports_external.object({
|
|
|
10448
10553
|
componentPath: safeRelativePathSchema.optional(),
|
|
10449
10554
|
renameWindow: exports_external.boolean().default(true).describe("Set terminal/tmux window name when launching OpenCode"),
|
|
10450
10555
|
exclude: exports_external.array(globPatternSchema).default([
|
|
10451
|
-
"**/AGENTS.md",
|
|
10452
10556
|
"**/CLAUDE.md",
|
|
10453
10557
|
"**/CONTEXT.md",
|
|
10454
10558
|
"**/.opencode/**",
|
|
@@ -10476,38 +10580,38 @@ async function atomicWrite(filePath, data) {
|
|
|
10476
10580
|
// src/profile/paths.ts
|
|
10477
10581
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
10478
10582
|
import { homedir as homedir2 } from "os";
|
|
10479
|
-
import
|
|
10583
|
+
import path4 from "path";
|
|
10480
10584
|
var OCX_CONFIG_FILE = "ocx.jsonc";
|
|
10481
10585
|
var OPENCODE_CONFIG_FILE = "opencode.jsonc";
|
|
10482
10586
|
var LOCAL_CONFIG_DIR2 = ".opencode";
|
|
10483
10587
|
function getProfilesDir() {
|
|
10484
|
-
const base = process.env.XDG_CONFIG_HOME ||
|
|
10485
|
-
return
|
|
10588
|
+
const base = process.env.XDG_CONFIG_HOME || path4.join(homedir2(), ".config");
|
|
10589
|
+
return path4.join(base, "opencode", "profiles");
|
|
10486
10590
|
}
|
|
10487
10591
|
function getProfileDir(name) {
|
|
10488
|
-
return
|
|
10592
|
+
return path4.join(getProfilesDir(), name);
|
|
10489
10593
|
}
|
|
10490
10594
|
function getProfileOcxConfig(name) {
|
|
10491
|
-
return
|
|
10595
|
+
return path4.join(getProfileDir(name), "ocx.jsonc");
|
|
10492
10596
|
}
|
|
10493
10597
|
function getProfileOpencodeConfig(name) {
|
|
10494
|
-
return
|
|
10598
|
+
return path4.join(getProfileDir(name), "opencode.jsonc");
|
|
10495
10599
|
}
|
|
10496
10600
|
function getProfileAgents(name) {
|
|
10497
|
-
return
|
|
10601
|
+
return path4.join(getProfileDir(name), "AGENTS.md");
|
|
10498
10602
|
}
|
|
10499
10603
|
function findLocalConfigDir(cwd) {
|
|
10500
10604
|
let currentDir = cwd;
|
|
10501
10605
|
while (true) {
|
|
10502
|
-
const configDir =
|
|
10606
|
+
const configDir = path4.join(currentDir, LOCAL_CONFIG_DIR2);
|
|
10503
10607
|
if (existsSync2(configDir) && statSync(configDir).isDirectory()) {
|
|
10504
10608
|
return configDir;
|
|
10505
10609
|
}
|
|
10506
|
-
const gitDir =
|
|
10610
|
+
const gitDir = path4.join(currentDir, ".git");
|
|
10507
10611
|
if (existsSync2(gitDir)) {
|
|
10508
10612
|
return null;
|
|
10509
10613
|
}
|
|
10510
|
-
const parentDir =
|
|
10614
|
+
const parentDir = path4.dirname(currentDir);
|
|
10511
10615
|
if (parentDir === currentDir) {
|
|
10512
10616
|
return null;
|
|
10513
10617
|
}
|
|
@@ -10515,8 +10619,8 @@ function findLocalConfigDir(cwd) {
|
|
|
10515
10619
|
}
|
|
10516
10620
|
}
|
|
10517
10621
|
function getGlobalConfig() {
|
|
10518
|
-
const base = process.env.XDG_CONFIG_HOME ||
|
|
10519
|
-
return
|
|
10622
|
+
const base = process.env.XDG_CONFIG_HOME || path4.join(homedir2(), ".config");
|
|
10623
|
+
return path4.join(base, "opencode", "ocx.jsonc");
|
|
10520
10624
|
}
|
|
10521
10625
|
|
|
10522
10626
|
// src/profile/schema.ts
|
|
@@ -10534,7 +10638,6 @@ var DEFAULT_OCX_CONFIG = {
|
|
|
10534
10638
|
registries: {},
|
|
10535
10639
|
renameWindow: true,
|
|
10536
10640
|
exclude: [
|
|
10537
|
-
"**/AGENTS.md",
|
|
10538
10641
|
"**/CLAUDE.md",
|
|
10539
10642
|
"**/CONTEXT.md",
|
|
10540
10643
|
"**/.opencode/**",
|
|
@@ -10543,6 +10646,21 @@ var DEFAULT_OCX_CONFIG = {
|
|
|
10543
10646
|
],
|
|
10544
10647
|
include: []
|
|
10545
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
|
+
`;
|
|
10546
10664
|
|
|
10547
10665
|
class ProfileManager {
|
|
10548
10666
|
profilesDir;
|
|
@@ -10626,7 +10744,25 @@ class ProfileManager {
|
|
|
10626
10744
|
const dir = getProfileDir(name);
|
|
10627
10745
|
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
10628
10746
|
const ocxPath = getProfileOcxConfig(name);
|
|
10629
|
-
|
|
10747
|
+
const ocxFile = Bun.file(ocxPath);
|
|
10748
|
+
if (!await ocxFile.exists()) {
|
|
10749
|
+
await Bun.write(ocxPath, DEFAULT_OCX_CONFIG_TEMPLATE, { mode: 384 });
|
|
10750
|
+
}
|
|
10751
|
+
const opencodePath = getProfileOpencodeConfig(name);
|
|
10752
|
+
const opencodeFile = Bun.file(opencodePath);
|
|
10753
|
+
if (!await opencodeFile.exists()) {
|
|
10754
|
+
await atomicWrite(opencodePath, {});
|
|
10755
|
+
}
|
|
10756
|
+
const agentsPath = getProfileAgents(name);
|
|
10757
|
+
const agentsFile = Bun.file(agentsPath);
|
|
10758
|
+
if (!await agentsFile.exists()) {
|
|
10759
|
+
const agentsContent = `# Profile Instructions
|
|
10760
|
+
|
|
10761
|
+
<!-- Add your custom instructions for this profile here -->
|
|
10762
|
+
<!-- These will be included when running \`ocx opencode -p ${name}\` -->
|
|
10763
|
+
`;
|
|
10764
|
+
await Bun.write(agentsPath, agentsContent, { mode: 384 });
|
|
10765
|
+
}
|
|
10630
10766
|
}
|
|
10631
10767
|
async remove(name) {
|
|
10632
10768
|
if (!await this.exists(name)) {
|
|
@@ -10639,6 +10775,44 @@ class ProfileManager {
|
|
|
10639
10775
|
const dir = getProfileDir(name);
|
|
10640
10776
|
await rm(dir, { recursive: true });
|
|
10641
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
|
+
}
|
|
10642
10816
|
async resolveProfile(override) {
|
|
10643
10817
|
if (override) {
|
|
10644
10818
|
if (!await this.exists(override)) {
|
|
@@ -10688,7 +10862,7 @@ function discoverInstructionFiles(projectDir, gitRoot) {
|
|
|
10688
10862
|
for (const filename of INSTRUCTION_FILES) {
|
|
10689
10863
|
const filePath = join2(currentDir, filename);
|
|
10690
10864
|
if (existsSync3(filePath) && statSync2(filePath).isFile()) {
|
|
10691
|
-
const relativePath =
|
|
10865
|
+
const relativePath = relative2(root, filePath);
|
|
10692
10866
|
discovered.push(relativePath);
|
|
10693
10867
|
}
|
|
10694
10868
|
}
|
|
@@ -10765,11 +10939,13 @@ class ConfigResolver {
|
|
|
10765
10939
|
}
|
|
10766
10940
|
}
|
|
10767
10941
|
const shouldLoadLocal = this.shouldLoadLocalConfig();
|
|
10768
|
-
if (shouldLoadLocal && this.localConfigDir) {
|
|
10942
|
+
if (!this.profile && shouldLoadLocal && this.localConfigDir) {
|
|
10769
10943
|
const localOcxConfig = this.loadLocalOcxConfig();
|
|
10770
10944
|
if (localOcxConfig) {
|
|
10771
|
-
registries =
|
|
10945
|
+
registries = localOcxConfig.registries;
|
|
10772
10946
|
}
|
|
10947
|
+
}
|
|
10948
|
+
if (shouldLoadLocal && this.localConfigDir) {
|
|
10773
10949
|
const localOpencodeConfig = this.loadLocalOpencodeConfig();
|
|
10774
10950
|
if (localOpencodeConfig) {
|
|
10775
10951
|
opencode = this.deepMerge(opencode, localOpencodeConfig);
|
|
@@ -10810,7 +10986,7 @@ class ConfigResolver {
|
|
|
10810
10986
|
}
|
|
10811
10987
|
}
|
|
10812
10988
|
const shouldLoadLocal = this.shouldLoadLocalConfig();
|
|
10813
|
-
if (shouldLoadLocal && this.localConfigDir) {
|
|
10989
|
+
if (!this.profile && shouldLoadLocal && this.localConfigDir) {
|
|
10814
10990
|
const localOcxConfig = this.loadLocalOcxConfig();
|
|
10815
10991
|
if (localOcxConfig) {
|
|
10816
10992
|
const localOcxPath = join2(this.localConfigDir, OCX_CONFIG_FILE);
|
|
@@ -10819,6 +10995,8 @@ class ConfigResolver {
|
|
|
10819
10995
|
origins.set(`registries.${key}`, { path: localOcxPath, source: "local-config" });
|
|
10820
10996
|
}
|
|
10821
10997
|
}
|
|
10998
|
+
}
|
|
10999
|
+
if (shouldLoadLocal && this.localConfigDir) {
|
|
10822
11000
|
const localOpencodeConfig = this.loadLocalOpencodeConfig();
|
|
10823
11001
|
if (localOpencodeConfig) {
|
|
10824
11002
|
opencode = this.deepMerge(opencode, localOpencodeConfig);
|
|
@@ -10845,7 +11023,7 @@ class ConfigResolver {
|
|
|
10845
11023
|
return true;
|
|
10846
11024
|
const gitRoot = detectGitRoot(this.cwd);
|
|
10847
11025
|
const root = gitRoot ?? this.cwd;
|
|
10848
|
-
const relativePath =
|
|
11026
|
+
const relativePath = relative2(root, this.localConfigDir);
|
|
10849
11027
|
const exclude = this.profile.ocx.exclude ?? [];
|
|
10850
11028
|
const include = this.profile.ocx.include ?? [];
|
|
10851
11029
|
for (const pattern of include) {
|
|
@@ -10938,7 +11116,7 @@ class ConfigResolver {
|
|
|
10938
11116
|
// package.json
|
|
10939
11117
|
var package_default = {
|
|
10940
11118
|
name: "ocx",
|
|
10941
|
-
version: "1.4.
|
|
11119
|
+
version: "1.4.3",
|
|
10942
11120
|
description: "OCX CLI - ShadCN-style registry for OpenCode extensions. Install agents, plugins, skills, and MCP servers.",
|
|
10943
11121
|
author: "kdcokenny",
|
|
10944
11122
|
license: "MIT",
|
|
@@ -11017,14 +11195,28 @@ async function fetchWithCache(url, parse3) {
|
|
|
11017
11195
|
return cached;
|
|
11018
11196
|
}
|
|
11019
11197
|
const promise = (async () => {
|
|
11020
|
-
|
|
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
|
+
}
|
|
11021
11204
|
if (!response.ok) {
|
|
11022
11205
|
if (response.status === 404) {
|
|
11023
11206
|
throw new NotFoundError(`Not found: ${url}`);
|
|
11024
11207
|
}
|
|
11025
|
-
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 });
|
|
11026
11219
|
}
|
|
11027
|
-
const data = await response.json();
|
|
11028
11220
|
return parse3(data);
|
|
11029
11221
|
})();
|
|
11030
11222
|
cache.set(url, promise);
|
|
@@ -11071,9 +11263,14 @@ async function fetchComponentVersion(baseUrl, name, version) {
|
|
|
11071
11263
|
}
|
|
11072
11264
|
async function fetchFileContent(baseUrl, componentName, filePath) {
|
|
11073
11265
|
const url = `${baseUrl.replace(/\/$/, "")}/components/${componentName}/${filePath}`;
|
|
11074
|
-
|
|
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
|
+
}
|
|
11075
11272
|
if (!response.ok) {
|
|
11076
|
-
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 });
|
|
11077
11274
|
}
|
|
11078
11275
|
return response.text();
|
|
11079
11276
|
}
|
|
@@ -11148,13 +11345,13 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11148
11345
|
const npmDeps = new Set;
|
|
11149
11346
|
const npmDevDeps = new Set;
|
|
11150
11347
|
let opencode = {};
|
|
11151
|
-
async function
|
|
11348
|
+
async function resolve2(componentNamespace, componentName, path5 = []) {
|
|
11152
11349
|
const qualifiedName = createQualifiedComponent(componentNamespace, componentName);
|
|
11153
11350
|
if (resolved.has(qualifiedName)) {
|
|
11154
11351
|
return;
|
|
11155
11352
|
}
|
|
11156
11353
|
if (visiting.has(qualifiedName)) {
|
|
11157
|
-
const cycle = [...
|
|
11354
|
+
const cycle = [...path5, qualifiedName].join(" \u2192 ");
|
|
11158
11355
|
throw new ValidationError(`Circular dependency detected: ${cycle}`);
|
|
11159
11356
|
}
|
|
11160
11357
|
visiting.add(qualifiedName);
|
|
@@ -11165,12 +11362,21 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11165
11362
|
let component;
|
|
11166
11363
|
try {
|
|
11167
11364
|
component = await fetchComponent(regConfig.url, componentName);
|
|
11168
|
-
} catch (
|
|
11169
|
-
|
|
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 });
|
|
11170
11376
|
}
|
|
11171
11377
|
for (const dep of component.dependencies) {
|
|
11172
11378
|
const depRef = parseComponentRef(dep, componentNamespace);
|
|
11173
|
-
await
|
|
11379
|
+
await resolve2(depRef.namespace, depRef.component, [...path5, qualifiedName]);
|
|
11174
11380
|
}
|
|
11175
11381
|
const normalizedComponent = normalizeComponentManifest(component);
|
|
11176
11382
|
resolved.set(qualifiedName, {
|
|
@@ -11197,7 +11403,7 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11197
11403
|
}
|
|
11198
11404
|
for (const name of componentNames) {
|
|
11199
11405
|
const ref = parseComponentRef(name);
|
|
11200
|
-
await
|
|
11406
|
+
await resolve2(ref.namespace, ref.component);
|
|
11201
11407
|
}
|
|
11202
11408
|
const components = Array.from(resolved.values());
|
|
11203
11409
|
const installOrder = Array.from(resolved.keys());
|
|
@@ -11214,17 +11420,17 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11214
11420
|
import { existsSync as existsSync4 } from "fs";
|
|
11215
11421
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
11216
11422
|
import { homedir as homedir3 } from "os";
|
|
11217
|
-
import
|
|
11423
|
+
import path5 from "path";
|
|
11218
11424
|
var LOCAL_CONFIG_DIR3 = ".opencode";
|
|
11219
11425
|
function isGlobalConfigPath(cwd) {
|
|
11220
|
-
const base = process.env.XDG_CONFIG_HOME ||
|
|
11221
|
-
const globalConfigDir =
|
|
11222
|
-
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);
|
|
11223
11429
|
if (resolvedCwd === globalConfigDir) {
|
|
11224
11430
|
return true;
|
|
11225
11431
|
}
|
|
11226
|
-
const
|
|
11227
|
-
return
|
|
11432
|
+
const relative3 = path5.relative(globalConfigDir, resolvedCwd);
|
|
11433
|
+
return relative3 !== "" && !relative3.startsWith("..") && !path5.isAbsolute(relative3);
|
|
11228
11434
|
}
|
|
11229
11435
|
var JSONC_OPTIONS = {
|
|
11230
11436
|
formattingOptions: {
|
|
@@ -11241,8 +11447,8 @@ var OPENCODE_CONFIG_TEMPLATE = `{
|
|
|
11241
11447
|
`;
|
|
11242
11448
|
function findOpencodeConfig(cwd) {
|
|
11243
11449
|
if (isGlobalConfigPath(cwd)) {
|
|
11244
|
-
const rootJsonc2 =
|
|
11245
|
-
const rootJson2 =
|
|
11450
|
+
const rootJsonc2 = path5.join(cwd, "opencode.jsonc");
|
|
11451
|
+
const rootJson2 = path5.join(cwd, "opencode.json");
|
|
11246
11452
|
if (existsSync4(rootJsonc2)) {
|
|
11247
11453
|
return { path: rootJsonc2, exists: true };
|
|
11248
11454
|
}
|
|
@@ -11251,16 +11457,16 @@ function findOpencodeConfig(cwd) {
|
|
|
11251
11457
|
}
|
|
11252
11458
|
return { path: rootJsonc2, exists: false };
|
|
11253
11459
|
}
|
|
11254
|
-
const dotOpencodeJsonc =
|
|
11255
|
-
const dotOpencodeJson =
|
|
11460
|
+
const dotOpencodeJsonc = path5.join(cwd, LOCAL_CONFIG_DIR3, "opencode.jsonc");
|
|
11461
|
+
const dotOpencodeJson = path5.join(cwd, LOCAL_CONFIG_DIR3, "opencode.json");
|
|
11256
11462
|
if (existsSync4(dotOpencodeJsonc)) {
|
|
11257
11463
|
return { path: dotOpencodeJsonc, exists: true };
|
|
11258
11464
|
}
|
|
11259
11465
|
if (existsSync4(dotOpencodeJson)) {
|
|
11260
11466
|
return { path: dotOpencodeJson, exists: true };
|
|
11261
11467
|
}
|
|
11262
|
-
const rootJsonc =
|
|
11263
|
-
const rootJson =
|
|
11468
|
+
const rootJsonc = path5.join(cwd, "opencode.jsonc");
|
|
11469
|
+
const rootJson = path5.join(cwd, "opencode.json");
|
|
11264
11470
|
if (existsSync4(rootJsonc)) {
|
|
11265
11471
|
return { path: rootJsonc, exists: true };
|
|
11266
11472
|
}
|
|
@@ -11274,7 +11480,7 @@ async function ensureOpencodeConfig(cwd) {
|
|
|
11274
11480
|
if (exists) {
|
|
11275
11481
|
return { path: configPath, created: false };
|
|
11276
11482
|
}
|
|
11277
|
-
await mkdir3(
|
|
11483
|
+
await mkdir3(path5.dirname(configPath), { recursive: true });
|
|
11278
11484
|
await Bun.write(configPath, OPENCODE_CONFIG_TEMPLATE);
|
|
11279
11485
|
return { path: configPath, created: true };
|
|
11280
11486
|
}
|
|
@@ -11291,13 +11497,13 @@ async function readOpencodeJsonConfig(cwd) {
|
|
|
11291
11497
|
path: configPath
|
|
11292
11498
|
};
|
|
11293
11499
|
}
|
|
11294
|
-
async function writeOpencodeJsonConfig(
|
|
11295
|
-
await Bun.write(
|
|
11500
|
+
async function writeOpencodeJsonConfig(path6, content) {
|
|
11501
|
+
await Bun.write(path6, content);
|
|
11296
11502
|
}
|
|
11297
|
-
function getValueAtPath(content,
|
|
11503
|
+
function getValueAtPath(content, path6) {
|
|
11298
11504
|
const parsed = parse2(content, [], { allowTrailingComma: true });
|
|
11299
11505
|
let current = parsed;
|
|
11300
|
-
for (const segment of
|
|
11506
|
+
for (const segment of path6) {
|
|
11301
11507
|
if (current === null || current === undefined)
|
|
11302
11508
|
return;
|
|
11303
11509
|
if (typeof current !== "object")
|
|
@@ -11306,27 +11512,27 @@ function getValueAtPath(content, path5) {
|
|
|
11306
11512
|
}
|
|
11307
11513
|
return current;
|
|
11308
11514
|
}
|
|
11309
|
-
function applyValueAtPath(content,
|
|
11515
|
+
function applyValueAtPath(content, path6, value) {
|
|
11310
11516
|
if (value === null || value === undefined) {
|
|
11311
11517
|
return content;
|
|
11312
11518
|
}
|
|
11313
11519
|
if (typeof value === "object" && !Array.isArray(value)) {
|
|
11314
|
-
const existingValue = getValueAtPath(content,
|
|
11520
|
+
const existingValue = getValueAtPath(content, path6);
|
|
11315
11521
|
if (existingValue !== undefined && (existingValue === null || typeof existingValue !== "object")) {
|
|
11316
|
-
const edits2 = modify(content,
|
|
11522
|
+
const edits2 = modify(content, path6, value, JSONC_OPTIONS);
|
|
11317
11523
|
return applyEdits(content, edits2);
|
|
11318
11524
|
}
|
|
11319
11525
|
let updatedContent = content;
|
|
11320
11526
|
for (const [key, val] of Object.entries(value)) {
|
|
11321
|
-
updatedContent = applyValueAtPath(updatedContent, [...
|
|
11527
|
+
updatedContent = applyValueAtPath(updatedContent, [...path6, key], val);
|
|
11322
11528
|
}
|
|
11323
11529
|
return updatedContent;
|
|
11324
11530
|
}
|
|
11325
11531
|
if (Array.isArray(value)) {
|
|
11326
|
-
const edits2 = modify(content,
|
|
11532
|
+
const edits2 = modify(content, path6, value, JSONC_OPTIONS);
|
|
11327
11533
|
return applyEdits(content, edits2);
|
|
11328
11534
|
}
|
|
11329
|
-
const edits = modify(content,
|
|
11535
|
+
const edits = modify(content, path6, value, JSONC_OPTIONS);
|
|
11330
11536
|
return applyEdits(content, edits);
|
|
11331
11537
|
}
|
|
11332
11538
|
async function updateOpencodeJsonConfig(cwd, opencode) {
|
|
@@ -11340,8 +11546,8 @@ async function updateOpencodeJsonConfig(cwd, opencode) {
|
|
|
11340
11546
|
} else {
|
|
11341
11547
|
const config = { $schema: "https://opencode.ai/config.json" };
|
|
11342
11548
|
content = JSON.stringify(config, null, "\t");
|
|
11343
|
-
configPath = isGlobalConfigPath(cwd) ?
|
|
11344
|
-
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 });
|
|
11345
11551
|
created = true;
|
|
11346
11552
|
}
|
|
11347
11553
|
const originalContent = content;
|
|
@@ -11383,7 +11589,7 @@ function parseEnvBool(value, defaultValue) {
|
|
|
11383
11589
|
return defaultValue;
|
|
11384
11590
|
}
|
|
11385
11591
|
// src/utils/git-context.ts
|
|
11386
|
-
import { basename, resolve } from "path";
|
|
11592
|
+
import { basename as basename2, resolve as resolve2 } from "path";
|
|
11387
11593
|
function getGitEnv() {
|
|
11388
11594
|
const { GIT_DIR: _, GIT_WORK_TREE: __, ...cleanEnv } = process.env;
|
|
11389
11595
|
return cleanEnv;
|
|
@@ -11449,12 +11655,100 @@ async function getRepoName(cwd) {
|
|
|
11449
11655
|
if (!rootPath) {
|
|
11450
11656
|
return null;
|
|
11451
11657
|
}
|
|
11452
|
-
return
|
|
11658
|
+
return basename2(rootPath);
|
|
11453
11659
|
}
|
|
11454
11660
|
async function getGitInfo(cwd) {
|
|
11455
11661
|
const [repoName, branch] = await Promise.all([getRepoName(cwd), getBranch(cwd)]);
|
|
11456
11662
|
return { repoName, branch };
|
|
11457
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
|
+
|
|
11458
11752
|
// ../../node_modules/.bun/kleur@4.1.5/node_modules/kleur/index.mjs
|
|
11459
11753
|
var FORCE_COLOR;
|
|
11460
11754
|
var NODE_DISABLE_COLORS;
|
|
@@ -11592,6 +11886,15 @@ var logger = {
|
|
|
11592
11886
|
console.log("");
|
|
11593
11887
|
}
|
|
11594
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
|
+
};
|
|
11595
11898
|
|
|
11596
11899
|
// src/utils/handle-error.ts
|
|
11597
11900
|
function handleError(error, options2 = {}) {
|
|
@@ -11607,8 +11910,8 @@ function handleError(error, options2 = {}) {
|
|
|
11607
11910
|
if (error instanceof ZodError) {
|
|
11608
11911
|
logger.error("Validation failed:");
|
|
11609
11912
|
for (const issue of error.issues) {
|
|
11610
|
-
const
|
|
11611
|
-
logger.error(` ${
|
|
11913
|
+
const path6 = issue.path.join(".");
|
|
11914
|
+
logger.error(` ${path6}: ${issue.message}`);
|
|
11612
11915
|
}
|
|
11613
11916
|
process.exit(EXIT_CODES.CONFIG);
|
|
11614
11917
|
}
|
|
@@ -11632,6 +11935,129 @@ function wrapAction(action) {
|
|
|
11632
11935
|
};
|
|
11633
11936
|
}
|
|
11634
11937
|
function formatErrorAsJson(error) {
|
|
11938
|
+
if (error instanceof RegistryExistsError) {
|
|
11939
|
+
return {
|
|
11940
|
+
success: false,
|
|
11941
|
+
error: {
|
|
11942
|
+
code: error.code,
|
|
11943
|
+
message: error.message,
|
|
11944
|
+
details: {
|
|
11945
|
+
registryName: error.registryName,
|
|
11946
|
+
existingUrl: error.existingUrl,
|
|
11947
|
+
newUrl: error.newUrl,
|
|
11948
|
+
...error.targetLabel && { targetLabel: error.targetLabel }
|
|
11949
|
+
}
|
|
11950
|
+
},
|
|
11951
|
+
exitCode: error.exitCode,
|
|
11952
|
+
meta: {
|
|
11953
|
+
timestamp: new Date().toISOString()
|
|
11954
|
+
}
|
|
11955
|
+
};
|
|
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
|
+
}
|
|
11635
12061
|
if (error instanceof OCXError) {
|
|
11636
12062
|
return {
|
|
11637
12063
|
success: false,
|
|
@@ -11650,7 +12076,14 @@ function formatErrorAsJson(error) {
|
|
|
11650
12076
|
success: false,
|
|
11651
12077
|
error: {
|
|
11652
12078
|
code: "VALIDATION_ERROR",
|
|
11653
|
-
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
|
+
}
|
|
11654
12087
|
},
|
|
11655
12088
|
exitCode: EXIT_CODES.CONFIG,
|
|
11656
12089
|
meta: {
|
|
@@ -11675,15 +12108,15 @@ function outputJson(data) {
|
|
|
11675
12108
|
console.log(JSON.stringify(data, null, 2));
|
|
11676
12109
|
}
|
|
11677
12110
|
// src/utils/path-safety.ts
|
|
11678
|
-
import
|
|
12111
|
+
import path6 from "path";
|
|
11679
12112
|
function isPathInside(childPath, parentPath) {
|
|
11680
|
-
const resolvedChild =
|
|
11681
|
-
const resolvedParent =
|
|
12113
|
+
const resolvedChild = path6.resolve(childPath);
|
|
12114
|
+
const resolvedParent = path6.resolve(parentPath);
|
|
11682
12115
|
if (resolvedChild === resolvedParent) {
|
|
11683
12116
|
return true;
|
|
11684
12117
|
}
|
|
11685
|
-
const
|
|
11686
|
-
return !!
|
|
12118
|
+
const relative3 = path6.relative(resolvedParent, resolvedChild);
|
|
12119
|
+
return !!relative3 && !relative3.startsWith("..") && !isAbsolutePath(relative3);
|
|
11687
12120
|
}
|
|
11688
12121
|
function assertPathInside(childPath, parentPath) {
|
|
11689
12122
|
if (!isPathInside(childPath, parentPath)) {
|
|
@@ -11695,6 +12128,7 @@ var sharedOptions = {
|
|
|
11695
12128
|
cwd: () => new Option("--cwd <path>", "Working directory").default(process.cwd()),
|
|
11696
12129
|
quiet: () => new Option("-q, --quiet", "Suppress output"),
|
|
11697
12130
|
json: () => new Option("--json", "Output as JSON"),
|
|
12131
|
+
profile: () => new Option("-p, --profile <name>", "Target a specific profile's config"),
|
|
11698
12132
|
force: () => new Option("-f, --force", "Skip confirmation prompts"),
|
|
11699
12133
|
verbose: () => new Option("-v, --verbose", "Verbose output"),
|
|
11700
12134
|
global: new Option("-g, --global", "Install to global OpenCode config (~/.config/opencode)")
|
|
@@ -11711,6 +12145,20 @@ function addVerboseOption(cmd) {
|
|
|
11711
12145
|
function addGlobalOption(cmd) {
|
|
11712
12146
|
return cmd.addOption(sharedOptions.global);
|
|
11713
12147
|
}
|
|
12148
|
+
function addProfileOption(cmd) {
|
|
12149
|
+
return cmd.addOption(sharedOptions.profile());
|
|
12150
|
+
}
|
|
12151
|
+
function validateProfileName(name) {
|
|
12152
|
+
if (!name || name.length === 0) {
|
|
12153
|
+
throw new InvalidProfileNameError(name, "cannot be empty");
|
|
12154
|
+
}
|
|
12155
|
+
if (name.length > 32) {
|
|
12156
|
+
throw new InvalidProfileNameError(name, "must be 32 characters or less");
|
|
12157
|
+
}
|
|
12158
|
+
if (!/^[a-zA-Z][a-zA-Z0-9._-]*$/.test(name)) {
|
|
12159
|
+
throw new InvalidProfileNameError(name, "must start with a letter and contain only alphanumeric characters, dots, underscores, or hyphens");
|
|
12160
|
+
}
|
|
12161
|
+
}
|
|
11714
12162
|
// ../../node_modules/.bun/ora@8.2.0/node_modules/ora/index.js
|
|
11715
12163
|
import process9 from "process";
|
|
11716
12164
|
|
|
@@ -13429,7 +13877,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13429
13877
|
}
|
|
13430
13878
|
const computedHash = await hashBundle(files);
|
|
13431
13879
|
for (const file of component.files) {
|
|
13432
|
-
const targetPath =
|
|
13880
|
+
const targetPath = join4(cwd, resolveTargetPath(file.target, isFlattened));
|
|
13433
13881
|
assertPathInside(targetPath, cwd);
|
|
13434
13882
|
}
|
|
13435
13883
|
const existingEntry = lock.installed[component.qualifiedName];
|
|
@@ -13439,7 +13887,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13439
13887
|
}
|
|
13440
13888
|
for (const file of component.files) {
|
|
13441
13889
|
const resolvedTarget = resolveTargetPath(file.target, isFlattened);
|
|
13442
|
-
const targetPath =
|
|
13890
|
+
const targetPath = join4(cwd, resolvedTarget);
|
|
13443
13891
|
if (existsSync5(targetPath)) {
|
|
13444
13892
|
const conflictingComponent = findComponentByFile(lock, resolvedTarget);
|
|
13445
13893
|
if (conflictingComponent && conflictingComponent !== component.qualifiedName) {
|
|
@@ -13463,7 +13911,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13463
13911
|
if (!componentFile)
|
|
13464
13912
|
continue;
|
|
13465
13913
|
const resolvedTarget = resolveTargetPath(componentFile.target, isFlattened);
|
|
13466
|
-
const targetPath =
|
|
13914
|
+
const targetPath = join4(cwd, resolvedTarget);
|
|
13467
13915
|
if (existsSync5(targetPath)) {
|
|
13468
13916
|
const existingContent = await Bun.file(targetPath).text();
|
|
13469
13917
|
const incomingContent = file.content.toString("utf-8");
|
|
@@ -13528,7 +13976,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13528
13976
|
}
|
|
13529
13977
|
const hasNpmDeps = resolved.npmDependencies.length > 0;
|
|
13530
13978
|
const hasNpmDevDeps = resolved.npmDevDependencies.length > 0;
|
|
13531
|
-
const packageJsonPath = options2.global || options2.profile ?
|
|
13979
|
+
const packageJsonPath = options2.global || options2.profile ? join4(cwd, "package.json") : join4(cwd, ".opencode/package.json");
|
|
13532
13980
|
if (hasNpmDeps || hasNpmDevDeps) {
|
|
13533
13981
|
const npmSpin = options2.quiet ? null : createSpinner({ text: `Updating ${packageJsonPath}...` });
|
|
13534
13982
|
npmSpin?.start();
|
|
@@ -13570,8 +14018,8 @@ async function installComponent(component, files, cwd, options2) {
|
|
|
13570
14018
|
if (!componentFile)
|
|
13571
14019
|
continue;
|
|
13572
14020
|
const resolvedTarget = resolveTargetPath(componentFile.target, !!options2.isFlattened);
|
|
13573
|
-
const targetPath =
|
|
13574
|
-
const targetDir =
|
|
14021
|
+
const targetPath = join4(cwd, resolvedTarget);
|
|
14022
|
+
const targetDir = dirname2(targetPath);
|
|
13575
14023
|
if (existsSync5(targetPath)) {
|
|
13576
14024
|
const existingContent = await Bun.file(targetPath).text();
|
|
13577
14025
|
const incomingContent = file.content.toString("utf-8");
|
|
@@ -13584,7 +14032,7 @@ async function installComponent(component, files, cwd, options2) {
|
|
|
13584
14032
|
result.written.push(resolvedTarget);
|
|
13585
14033
|
}
|
|
13586
14034
|
if (!existsSync5(targetDir)) {
|
|
13587
|
-
await
|
|
14035
|
+
await mkdir5(targetDir, { recursive: true });
|
|
13588
14036
|
}
|
|
13589
14037
|
await writeFile(targetPath, file.content);
|
|
13590
14038
|
}
|
|
@@ -13660,7 +14108,7 @@ function mergeDevDependencies(existing, newDeps) {
|
|
|
13660
14108
|
return { ...existing, devDependencies: merged };
|
|
13661
14109
|
}
|
|
13662
14110
|
async function readOpencodePackageJson(opencodeDir) {
|
|
13663
|
-
const pkgPath =
|
|
14111
|
+
const pkgPath = join4(opencodeDir, "package.json");
|
|
13664
14112
|
if (!existsSync5(pkgPath)) {
|
|
13665
14113
|
return { ...DEFAULT_PACKAGE_JSON };
|
|
13666
14114
|
}
|
|
@@ -13673,7 +14121,7 @@ async function readOpencodePackageJson(opencodeDir) {
|
|
|
13673
14121
|
}
|
|
13674
14122
|
}
|
|
13675
14123
|
async function ensureManifestFilesAreTracked(opencodeDir) {
|
|
13676
|
-
const gitignorePath =
|
|
14124
|
+
const gitignorePath = join4(opencodeDir, ".gitignore");
|
|
13677
14125
|
const filesToTrack = new Set(["package.json", "bun.lock"]);
|
|
13678
14126
|
const requiredIgnores = ["node_modules"];
|
|
13679
14127
|
let lines = [];
|
|
@@ -13696,12 +14144,12 @@ async function updateOpencodeDevDependencies(cwd, npmDeps, npmDevDeps, options2
|
|
|
13696
14144
|
const allDepSpecs = [...npmDeps, ...npmDevDeps];
|
|
13697
14145
|
if (allDepSpecs.length === 0)
|
|
13698
14146
|
return;
|
|
13699
|
-
const packageDir = options2.isFlattened ? cwd :
|
|
13700
|
-
await
|
|
14147
|
+
const packageDir = options2.isFlattened ? cwd : join4(cwd, ".opencode");
|
|
14148
|
+
await mkdir5(packageDir, { recursive: true });
|
|
13701
14149
|
const parsedDeps = allDepSpecs.map(parseNpmDependency);
|
|
13702
14150
|
const existing = await readOpencodePackageJson(packageDir);
|
|
13703
14151
|
const updated = mergeDevDependencies(existing, parsedDeps);
|
|
13704
|
-
await Bun.write(
|
|
14152
|
+
await Bun.write(join4(packageDir, "package.json"), `${JSON.stringify(updated, null, 2)}
|
|
13705
14153
|
`);
|
|
13706
14154
|
if (!options2.isFlattened) {
|
|
13707
14155
|
await ensureManifestFilesAreTracked(packageDir);
|
|
@@ -13717,101 +14165,11 @@ function findComponentByFile(lock, filePath) {
|
|
|
13717
14165
|
}
|
|
13718
14166
|
|
|
13719
14167
|
// src/commands/build.ts
|
|
13720
|
-
import { join as join5, relative as
|
|
13721
|
-
|
|
13722
|
-
// src/lib/build-registry.ts
|
|
13723
|
-
import { mkdir as mkdir5 } from "fs/promises";
|
|
13724
|
-
import { dirname as dirname2, join as join4 } from "path";
|
|
13725
|
-
class BuildRegistryError extends Error {
|
|
13726
|
-
errors;
|
|
13727
|
-
constructor(message, errors3 = []) {
|
|
13728
|
-
super(message);
|
|
13729
|
-
this.errors = errors3;
|
|
13730
|
-
this.name = "BuildRegistryError";
|
|
13731
|
-
}
|
|
13732
|
-
}
|
|
13733
|
-
async function buildRegistry(options2) {
|
|
13734
|
-
const { source: sourcePath, out: outPath } = options2;
|
|
13735
|
-
const jsoncFile = Bun.file(join4(sourcePath, "registry.jsonc"));
|
|
13736
|
-
const jsonFile = Bun.file(join4(sourcePath, "registry.json"));
|
|
13737
|
-
const jsoncExists = await jsoncFile.exists();
|
|
13738
|
-
const jsonExists = await jsonFile.exists();
|
|
13739
|
-
if (!jsoncExists && !jsonExists) {
|
|
13740
|
-
throw new BuildRegistryError("No registry.jsonc or registry.json found in source directory");
|
|
13741
|
-
}
|
|
13742
|
-
const registryFile = jsoncExists ? jsoncFile : jsonFile;
|
|
13743
|
-
const content2 = await registryFile.text();
|
|
13744
|
-
const registryData = parse2(content2, [], { allowTrailingComma: true });
|
|
13745
|
-
const parseResult = registrySchema.safeParse(registryData);
|
|
13746
|
-
if (!parseResult.success) {
|
|
13747
|
-
const errors3 = parseResult.error.errors.map((e3) => `${e3.path.join(".")}: ${e3.message}`);
|
|
13748
|
-
throw new BuildRegistryError("Registry validation failed", errors3);
|
|
13749
|
-
}
|
|
13750
|
-
const registry = parseResult.data;
|
|
13751
|
-
const validationErrors = [];
|
|
13752
|
-
const componentsDir = join4(outPath, "components");
|
|
13753
|
-
await mkdir5(componentsDir, { recursive: true });
|
|
13754
|
-
for (const component of registry.components) {
|
|
13755
|
-
const packument = {
|
|
13756
|
-
name: component.name,
|
|
13757
|
-
versions: {
|
|
13758
|
-
[registry.version]: component
|
|
13759
|
-
},
|
|
13760
|
-
"dist-tags": {
|
|
13761
|
-
latest: registry.version
|
|
13762
|
-
}
|
|
13763
|
-
};
|
|
13764
|
-
const packumentPath = join4(componentsDir, `${component.name}.json`);
|
|
13765
|
-
await Bun.write(packumentPath, JSON.stringify(packument, null, 2));
|
|
13766
|
-
for (const rawFile of component.files) {
|
|
13767
|
-
const file = normalizeFile(rawFile, component.type);
|
|
13768
|
-
const sourceFilePath = join4(sourcePath, "files", file.path);
|
|
13769
|
-
const destFilePath = join4(componentsDir, component.name, file.path);
|
|
13770
|
-
const destFileDir = dirname2(destFilePath);
|
|
13771
|
-
if (!await Bun.file(sourceFilePath).exists()) {
|
|
13772
|
-
validationErrors.push(`${component.name}: Source file not found at ${sourceFilePath}`);
|
|
13773
|
-
continue;
|
|
13774
|
-
}
|
|
13775
|
-
await mkdir5(destFileDir, { recursive: true });
|
|
13776
|
-
const sourceFile = Bun.file(sourceFilePath);
|
|
13777
|
-
await Bun.write(destFilePath, sourceFile);
|
|
13778
|
-
}
|
|
13779
|
-
}
|
|
13780
|
-
if (validationErrors.length > 0) {
|
|
13781
|
-
throw new BuildRegistryError(`Build failed with ${validationErrors.length} errors`, validationErrors);
|
|
13782
|
-
}
|
|
13783
|
-
const index = {
|
|
13784
|
-
name: registry.name,
|
|
13785
|
-
namespace: registry.namespace,
|
|
13786
|
-
version: registry.version,
|
|
13787
|
-
author: registry.author,
|
|
13788
|
-
...registry.opencode && { opencode: registry.opencode },
|
|
13789
|
-
...registry.ocx && { ocx: registry.ocx },
|
|
13790
|
-
components: registry.components.map((c) => ({
|
|
13791
|
-
name: c.name,
|
|
13792
|
-
type: c.type,
|
|
13793
|
-
description: c.description
|
|
13794
|
-
}))
|
|
13795
|
-
};
|
|
13796
|
-
await Bun.write(join4(outPath, "index.json"), JSON.stringify(index, null, 2));
|
|
13797
|
-
const wellKnownDir = join4(outPath, ".well-known");
|
|
13798
|
-
await mkdir5(wellKnownDir, { recursive: true });
|
|
13799
|
-
const discovery = { registry: "/index.json" };
|
|
13800
|
-
await Bun.write(join4(wellKnownDir, "ocx.json"), JSON.stringify(discovery, null, 2));
|
|
13801
|
-
return {
|
|
13802
|
-
name: registry.name,
|
|
13803
|
-
namespace: registry.namespace,
|
|
13804
|
-
version: registry.version,
|
|
13805
|
-
componentsCount: registry.components.length,
|
|
13806
|
-
outputPath: outPath
|
|
13807
|
-
};
|
|
13808
|
-
}
|
|
13809
|
-
|
|
13810
|
-
// src/commands/build.ts
|
|
14168
|
+
import { join as join5, relative as relative3 } from "path";
|
|
13811
14169
|
function registerBuildCommand(program2) {
|
|
13812
|
-
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) => {
|
|
13813
14171
|
try {
|
|
13814
|
-
const sourcePath = join5(options2.cwd,
|
|
14172
|
+
const sourcePath = join5(options2.cwd, path7);
|
|
13815
14173
|
const outPath = join5(options2.cwd, options2.out);
|
|
13816
14174
|
const spinner2 = createSpinner({
|
|
13817
14175
|
text: "Building registry...",
|
|
@@ -13824,7 +14182,7 @@ function registerBuildCommand(program2) {
|
|
|
13824
14182
|
out: outPath
|
|
13825
14183
|
});
|
|
13826
14184
|
if (!options2.json) {
|
|
13827
|
-
const msg = `Built ${result.componentsCount} components to ${
|
|
14185
|
+
const msg = `Built ${result.componentsCount} components to ${relative3(options2.cwd, outPath)}`;
|
|
13828
14186
|
spinner2.succeed(msg);
|
|
13829
14187
|
if (!process.stdout.isTTY) {
|
|
13830
14188
|
logger.success(`Built ${result.componentsCount} components`);
|
|
@@ -13861,7 +14219,7 @@ import { existsSync as existsSync6 } from "fs";
|
|
|
13861
14219
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
13862
14220
|
import { join as join6 } from "path";
|
|
13863
14221
|
function registerConfigEditCommand(parent) {
|
|
13864
|
-
parent.command("edit").description("Open configuration file in editor").option("-g, --global", "Edit global ocx.jsonc").action(async (options2) => {
|
|
14222
|
+
parent.command("edit").description("Open configuration file in editor").option("-g, --global", "Edit global ocx.jsonc").option("-p, --profile <name>", "Edit specific profile's config").action(async (options2) => {
|
|
13865
14223
|
try {
|
|
13866
14224
|
await runConfigEdit(options2);
|
|
13867
14225
|
} catch (error) {
|
|
@@ -13870,12 +14228,26 @@ function registerConfigEditCommand(parent) {
|
|
|
13870
14228
|
});
|
|
13871
14229
|
}
|
|
13872
14230
|
async function runConfigEdit(options2) {
|
|
14231
|
+
if (options2.global && options2.profile) {
|
|
14232
|
+
throw new ValidationError("Cannot use both --global and --profile flags");
|
|
14233
|
+
}
|
|
13873
14234
|
let configPath;
|
|
13874
|
-
if (options2.
|
|
14235
|
+
if (options2.profile) {
|
|
14236
|
+
const parseResult = profileNameSchema.safeParse(options2.profile);
|
|
14237
|
+
if (!parseResult.success) {
|
|
14238
|
+
throw new ValidationError(`Invalid profile name "${options2.profile}": ${parseResult.error.errors[0]?.message ?? "Invalid name"}`);
|
|
14239
|
+
}
|
|
14240
|
+
await ProfileManager.requireInitialized();
|
|
14241
|
+
const manager = ProfileManager.create();
|
|
14242
|
+
if (!await manager.exists(options2.profile)) {
|
|
14243
|
+
throw new ProfileNotFoundError(options2.profile);
|
|
14244
|
+
}
|
|
14245
|
+
configPath = getProfileOcxConfig(options2.profile);
|
|
14246
|
+
} else if (options2.global) {
|
|
13875
14247
|
configPath = getGlobalConfig();
|
|
13876
14248
|
if (!existsSync6(configPath)) {
|
|
13877
14249
|
throw new ConfigError(`Global config not found at ${configPath}.
|
|
13878
|
-
|
|
14250
|
+
Run 'ocx init --global' first.`);
|
|
13879
14251
|
}
|
|
13880
14252
|
} else {
|
|
13881
14253
|
const localConfigDir = findLocalConfigDir(process.cwd());
|
|
@@ -14112,16 +14484,16 @@ class Diff {
|
|
|
14112
14484
|
}
|
|
14113
14485
|
}
|
|
14114
14486
|
}
|
|
14115
|
-
addToPath(
|
|
14116
|
-
const last =
|
|
14487
|
+
addToPath(path7, added, removed, oldPosInc, options2) {
|
|
14488
|
+
const last = path7.lastComponent;
|
|
14117
14489
|
if (last && !options2.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
14118
14490
|
return {
|
|
14119
|
-
oldPos:
|
|
14491
|
+
oldPos: path7.oldPos + oldPosInc,
|
|
14120
14492
|
lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
|
|
14121
14493
|
};
|
|
14122
14494
|
} else {
|
|
14123
14495
|
return {
|
|
14124
|
-
oldPos:
|
|
14496
|
+
oldPos: path7.oldPos + oldPosInc,
|
|
14125
14497
|
lastComponent: { count: 1, added, removed, previousComponent: last }
|
|
14126
14498
|
};
|
|
14127
14499
|
}
|
|
@@ -14938,7 +15310,7 @@ import {
|
|
|
14938
15310
|
rmSync,
|
|
14939
15311
|
unlinkSync
|
|
14940
15312
|
} from "fs";
|
|
14941
|
-
import
|
|
15313
|
+
import path7 from "path";
|
|
14942
15314
|
var GHOST_CONFIG_FILE = "ghost.jsonc";
|
|
14943
15315
|
var BACKUP_EXT = ".bak";
|
|
14944
15316
|
var CURRENT_SYMLINK = "current";
|
|
@@ -14976,8 +15348,8 @@ function planMigration() {
|
|
|
14976
15348
|
if (!entry.isDirectory() || entry.name === CURRENT_SYMLINK)
|
|
14977
15349
|
continue;
|
|
14978
15350
|
const profileName = entry.name;
|
|
14979
|
-
const ghostConfig =
|
|
14980
|
-
const ocxConfig =
|
|
15351
|
+
const ghostConfig = path7.join(profilesDir, profileName, GHOST_CONFIG_FILE);
|
|
15352
|
+
const ocxConfig = path7.join(profilesDir, profileName, OCX_CONFIG_FILE);
|
|
14981
15353
|
if (!existsSync7(ghostConfig))
|
|
14982
15354
|
continue;
|
|
14983
15355
|
if (existsSync7(ocxConfig)) {
|
|
@@ -15007,13 +15379,13 @@ function planMigration() {
|
|
|
15007
15379
|
if (!entry.isDirectory() || entry.name === CURRENT_SYMLINK)
|
|
15008
15380
|
continue;
|
|
15009
15381
|
const profileName = entry.name;
|
|
15010
|
-
const profileDir =
|
|
15011
|
-
const dotOpencode =
|
|
15382
|
+
const profileDir = path7.join(profilesDir, profileName);
|
|
15383
|
+
const dotOpencode = path7.join(profileDir, ".opencode");
|
|
15012
15384
|
if (!existsSync7(dotOpencode))
|
|
15013
15385
|
continue;
|
|
15014
15386
|
for (const dir of FLATTEN_DIRS) {
|
|
15015
|
-
const source =
|
|
15016
|
-
const destination =
|
|
15387
|
+
const source = path7.join(dotOpencode, dir);
|
|
15388
|
+
const destination = path7.join(profileDir, dir);
|
|
15017
15389
|
if (!existsSync7(source))
|
|
15018
15390
|
continue;
|
|
15019
15391
|
try {
|
|
@@ -15052,7 +15424,7 @@ function planMigration() {
|
|
|
15052
15424
|
});
|
|
15053
15425
|
}
|
|
15054
15426
|
}
|
|
15055
|
-
const currentPath =
|
|
15427
|
+
const currentPath = path7.join(profilesDir, CURRENT_SYMLINK);
|
|
15056
15428
|
if (existsSync7(currentPath)) {
|
|
15057
15429
|
try {
|
|
15058
15430
|
const stat3 = lstatSync(currentPath);
|
|
@@ -15111,21 +15483,21 @@ function executeMigration(plan) {
|
|
|
15111
15483
|
} catch {}
|
|
15112
15484
|
}
|
|
15113
15485
|
} catch (error) {
|
|
15114
|
-
for (const
|
|
15486
|
+
for (const rename3 of completedRenames) {
|
|
15115
15487
|
try {
|
|
15116
|
-
if (existsSync7(
|
|
15117
|
-
if (
|
|
15118
|
-
rmSync(
|
|
15488
|
+
if (existsSync7(rename3.destination)) {
|
|
15489
|
+
if (rename3.isDir) {
|
|
15490
|
+
rmSync(rename3.destination, { recursive: true, force: true });
|
|
15119
15491
|
} else {
|
|
15120
|
-
unlinkSync(
|
|
15492
|
+
unlinkSync(rename3.destination);
|
|
15121
15493
|
}
|
|
15122
15494
|
}
|
|
15123
|
-
mkdirSync(
|
|
15124
|
-
if (existsSync7(
|
|
15125
|
-
if (
|
|
15126
|
-
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 });
|
|
15127
15499
|
} else {
|
|
15128
|
-
copyFileSync(
|
|
15500
|
+
copyFileSync(rename3.backup, rename3.source);
|
|
15129
15501
|
}
|
|
15130
15502
|
}
|
|
15131
15503
|
} catch {}
|
|
@@ -15146,8 +15518,8 @@ function executeMigration(plan) {
|
|
|
15146
15518
|
try {
|
|
15147
15519
|
const processedProfiles = new Set(plan.profiles.filter((a) => a.type === "move-dir").map((a) => a.profileName));
|
|
15148
15520
|
for (const profileName of processedProfiles) {
|
|
15149
|
-
const profileDir =
|
|
15150
|
-
const dotOpencode =
|
|
15521
|
+
const profileDir = path7.join(getProfilesDir(), profileName);
|
|
15522
|
+
const dotOpencode = path7.join(profileDir, ".opencode");
|
|
15151
15523
|
if (existsSync7(dotOpencode)) {
|
|
15152
15524
|
const remaining = readdirSync(dotOpencode);
|
|
15153
15525
|
if (remaining.length === 0) {
|
|
@@ -15173,7 +15545,7 @@ function printPlan(plan, dryRun) {
|
|
|
15173
15545
|
if (action.type === "rename") {
|
|
15174
15546
|
console.log(` \u2713 ${action.profileName}: ${GHOST_CONFIG_FILE} \u2192 ${OCX_CONFIG_FILE}`);
|
|
15175
15547
|
} else {
|
|
15176
|
-
const dirName =
|
|
15548
|
+
const dirName = path7.basename(action.source);
|
|
15177
15549
|
console.log(` \u2713 ${action.profileName}: .opencode/${dirName}/ \u2192 ${dirName}/`);
|
|
15178
15550
|
}
|
|
15179
15551
|
}
|
|
@@ -15291,34 +15663,90 @@ async function runInit(options2) {
|
|
|
15291
15663
|
}
|
|
15292
15664
|
}
|
|
15293
15665
|
async function runInitGlobal(options2) {
|
|
15294
|
-
const manager = ProfileManager.create();
|
|
15295
|
-
if (await manager.isInitialized()) {
|
|
15296
|
-
const profilesDir = getProfilesDir();
|
|
15297
|
-
throw new ProfileExistsError(`Global profiles already initialized at ${profilesDir}`);
|
|
15298
|
-
}
|
|
15299
15666
|
const spin = options2.quiet ? null : createSpinner({ text: "Initializing global profiles..." });
|
|
15300
15667
|
spin?.start();
|
|
15301
15668
|
try {
|
|
15302
|
-
|
|
15669
|
+
const created = [];
|
|
15670
|
+
const existed = [];
|
|
15671
|
+
const globalConfigPath = getGlobalConfig();
|
|
15672
|
+
if (existsSync8(globalConfigPath)) {
|
|
15673
|
+
existed.push("globalConfig");
|
|
15674
|
+
} else {
|
|
15675
|
+
await mkdir7(dirname3(globalConfigPath), { recursive: true, mode: 448 });
|
|
15676
|
+
await atomicWrite(globalConfigPath, {
|
|
15677
|
+
$schema: OCX_SCHEMA_URL,
|
|
15678
|
+
registries: {}
|
|
15679
|
+
});
|
|
15680
|
+
created.push("globalConfig");
|
|
15681
|
+
}
|
|
15303
15682
|
const profilesDir = getProfilesDir();
|
|
15304
|
-
|
|
15683
|
+
if (!existsSync8(profilesDir)) {
|
|
15684
|
+
await mkdir7(profilesDir, { recursive: true, mode: 448 });
|
|
15685
|
+
}
|
|
15686
|
+
const profileDir = getProfileDir("default");
|
|
15687
|
+
if (!existsSync8(profileDir)) {
|
|
15688
|
+
await mkdir7(profileDir, { recursive: true, mode: 448 });
|
|
15689
|
+
}
|
|
15690
|
+
const ocxPath = getProfileOcxConfig("default");
|
|
15691
|
+
if (existsSync8(ocxPath)) {
|
|
15692
|
+
existed.push("profileOcx");
|
|
15693
|
+
} else {
|
|
15694
|
+
await atomicWrite(ocxPath, DEFAULT_OCX_CONFIG);
|
|
15695
|
+
created.push("profileOcx");
|
|
15696
|
+
}
|
|
15697
|
+
const opencodePath = getProfileOpencodeConfig("default");
|
|
15698
|
+
if (existsSync8(opencodePath)) {
|
|
15699
|
+
existed.push("profileOpencode");
|
|
15700
|
+
} else {
|
|
15701
|
+
await atomicWrite(opencodePath, {});
|
|
15702
|
+
created.push("profileOpencode");
|
|
15703
|
+
}
|
|
15704
|
+
const agentsPath = getProfileAgents("default");
|
|
15705
|
+
if (existsSync8(agentsPath)) {
|
|
15706
|
+
existed.push("profileAgents");
|
|
15707
|
+
} else {
|
|
15708
|
+
const agentsContent = `# Profile Instructions
|
|
15709
|
+
|
|
15710
|
+
<!-- Add your custom instructions for this profile here -->
|
|
15711
|
+
<!-- These will be included when running \`ocx opencode -p default\` -->
|
|
15712
|
+
`;
|
|
15713
|
+
await Bun.write(agentsPath, agentsContent, { mode: 384 });
|
|
15714
|
+
created.push("profileAgents");
|
|
15715
|
+
}
|
|
15305
15716
|
spin?.succeed("Initialized global profiles");
|
|
15306
15717
|
if (options2.json) {
|
|
15307
15718
|
console.log(JSON.stringify({
|
|
15308
15719
|
success: true,
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
|
|
15720
|
+
files: {
|
|
15721
|
+
globalConfig: globalConfigPath,
|
|
15722
|
+
profileOcx: ocxPath,
|
|
15723
|
+
profileOpencode: opencodePath,
|
|
15724
|
+
profileAgents: agentsPath
|
|
15725
|
+
},
|
|
15726
|
+
created,
|
|
15727
|
+
existed
|
|
15312
15728
|
}));
|
|
15313
15729
|
} else if (!options2.quiet) {
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15730
|
+
if (created.length > 0) {
|
|
15731
|
+
for (const key of created) {
|
|
15732
|
+
if (key === "globalConfig")
|
|
15733
|
+
logger.info(`Created global config: ${globalConfigPath}`);
|
|
15734
|
+
if (key === "profileOcx")
|
|
15735
|
+
logger.info(`Created profile config: ${ocxPath}`);
|
|
15736
|
+
if (key === "profileOpencode")
|
|
15737
|
+
logger.info(`Created profile opencode config: ${opencodePath}`);
|
|
15738
|
+
if (key === "profileAgents")
|
|
15739
|
+
logger.info(`Created profile instructions: ${agentsPath}`);
|
|
15740
|
+
}
|
|
15741
|
+
logger.info("");
|
|
15742
|
+
logger.info("Next steps:");
|
|
15743
|
+
logger.info(" 1. Edit your profile config: ocx config edit -p default");
|
|
15744
|
+
logger.info(" 2. Add registries: ocx registry add <url> --name <name> --global");
|
|
15745
|
+
logger.info(" 3. Launch OpenCode: ocx opencode");
|
|
15746
|
+
logger.info(" 4. Create more profiles: ocx profile add <name>");
|
|
15747
|
+
} else {
|
|
15748
|
+
logger.info("Global profiles already initialized (all files exist)");
|
|
15749
|
+
}
|
|
15322
15750
|
}
|
|
15323
15751
|
} catch (error) {
|
|
15324
15752
|
spin?.fail("Failed to initialize");
|
|
@@ -15346,7 +15774,7 @@ async function runInitRegistry(directory, options2) {
|
|
|
15346
15774
|
await mkdir7(cwd, { recursive: true });
|
|
15347
15775
|
await copyDir(options2.local, cwd);
|
|
15348
15776
|
} else {
|
|
15349
|
-
const version = options2.canary ? "main" :
|
|
15777
|
+
const version = options2.canary ? "main" : getReleaseTag();
|
|
15350
15778
|
await fetchAndExtractTemplate(cwd, version, options2.verbose);
|
|
15351
15779
|
}
|
|
15352
15780
|
if (spin)
|
|
@@ -15375,15 +15803,16 @@ async function runInitRegistry(directory, options2) {
|
|
|
15375
15803
|
async function copyDir(src, dest) {
|
|
15376
15804
|
await cp(src, dest, { recursive: true });
|
|
15377
15805
|
}
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
const pkg = JSON.parse(pkgContent.toString());
|
|
15382
|
-
return `v${pkg.version}`;
|
|
15806
|
+
function getReleaseTag() {
|
|
15807
|
+
if (false) {}
|
|
15808
|
+
return `v${"1.4.3"}`;
|
|
15383
15809
|
}
|
|
15384
|
-
|
|
15810
|
+
function getTemplateUrl(version) {
|
|
15385
15811
|
const ref = version === "main" ? "heads/main" : `tags/${version}`;
|
|
15386
|
-
|
|
15812
|
+
return `https://github.com/${TEMPLATE_REPO}/archive/refs/${ref}.tar.gz`;
|
|
15813
|
+
}
|
|
15814
|
+
async function fetchAndExtractTemplate(destDir, version, verbose) {
|
|
15815
|
+
const tarballUrl = getTemplateUrl(version);
|
|
15387
15816
|
if (verbose) {
|
|
15388
15817
|
logger.info(`Fetching ${tarballUrl}`);
|
|
15389
15818
|
}
|
|
@@ -15417,6 +15846,9 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
|
|
|
15417
15846
|
await rm2(tempDir, { recursive: true, force: true });
|
|
15418
15847
|
}
|
|
15419
15848
|
}
|
|
15849
|
+
function toTitleCase(str) {
|
|
15850
|
+
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
15851
|
+
}
|
|
15420
15852
|
async function replacePlaceholders(dir, values) {
|
|
15421
15853
|
const filesToProcess = [
|
|
15422
15854
|
"registry.jsonc",
|
|
@@ -15431,16 +15863,17 @@ async function replacePlaceholders(dir, values) {
|
|
|
15431
15863
|
continue;
|
|
15432
15864
|
let content2 = await readFile(filePath).then((b) => b.toString());
|
|
15433
15865
|
content2 = content2.replace(/my-registry/g, values.namespace);
|
|
15866
|
+
content2 = content2.replace(/My Registry/g, toTitleCase(values.namespace));
|
|
15434
15867
|
content2 = content2.replace(/Your Name/g, values.author);
|
|
15435
15868
|
await writeFile2(filePath, content2);
|
|
15436
15869
|
}
|
|
15437
15870
|
}
|
|
15438
15871
|
|
|
15439
15872
|
// src/commands/opencode.ts
|
|
15440
|
-
import { resolve as
|
|
15873
|
+
import { resolve as resolve3 } from "path";
|
|
15441
15874
|
|
|
15442
15875
|
// src/utils/terminal-title.ts
|
|
15443
|
-
import
|
|
15876
|
+
import path8 from "path";
|
|
15444
15877
|
var MAX_BRANCH_LENGTH = 20;
|
|
15445
15878
|
var titleSaved = false;
|
|
15446
15879
|
function isInsideTmux() {
|
|
@@ -15483,7 +15916,7 @@ function restoreTerminalTitle() {
|
|
|
15483
15916
|
titleSaved = false;
|
|
15484
15917
|
}
|
|
15485
15918
|
function formatTerminalName(cwd, profileName, gitInfo) {
|
|
15486
|
-
const repoName = gitInfo.repoName ??
|
|
15919
|
+
const repoName = gitInfo.repoName ?? path8.basename(cwd);
|
|
15487
15920
|
if (!gitInfo.branch) {
|
|
15488
15921
|
return `ocx[${profileName}]:${repoName}`;
|
|
15489
15922
|
}
|
|
@@ -15492,17 +15925,29 @@ function formatTerminalName(cwd, profileName, gitInfo) {
|
|
|
15492
15925
|
}
|
|
15493
15926
|
|
|
15494
15927
|
// src/commands/opencode.ts
|
|
15928
|
+
function resolveOpenCodeBinary(opts) {
|
|
15929
|
+
return opts.configBin ?? opts.envBin ?? "opencode";
|
|
15930
|
+
}
|
|
15931
|
+
function buildOpenCodeEnv(opts) {
|
|
15932
|
+
return {
|
|
15933
|
+
...opts.baseEnv,
|
|
15934
|
+
...opts.disableProjectConfig && { OPENCODE_DISABLE_PROJECT_CONFIG: "true" },
|
|
15935
|
+
...opts.profileDir && { OPENCODE_CONFIG_DIR: opts.profileDir },
|
|
15936
|
+
...opts.mergedConfig && { OPENCODE_CONFIG_CONTENT: JSON.stringify(opts.mergedConfig) },
|
|
15937
|
+
...opts.profileName && { OCX_PROFILE: opts.profileName }
|
|
15938
|
+
};
|
|
15939
|
+
}
|
|
15495
15940
|
function registerOpencodeCommand(program2) {
|
|
15496
|
-
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) => {
|
|
15497
15942
|
try {
|
|
15498
|
-
await runOpencode(
|
|
15943
|
+
await runOpencode(path9, command.args, options2);
|
|
15499
15944
|
} catch (error) {
|
|
15500
15945
|
handleError(error, { json: options2.json });
|
|
15501
15946
|
}
|
|
15502
15947
|
});
|
|
15503
15948
|
}
|
|
15504
15949
|
async function runOpencode(pathArg, args, options2) {
|
|
15505
|
-
const projectDir = pathArg ?
|
|
15950
|
+
const projectDir = pathArg ? resolve3(pathArg) : process.cwd();
|
|
15506
15951
|
const resolver = await ConfigResolver.create(projectDir, { profile: options2.profile });
|
|
15507
15952
|
const config = resolver.resolve();
|
|
15508
15953
|
const profile = resolver.getProfile();
|
|
@@ -15546,17 +15991,20 @@ async function runOpencode(pathArg, args, options2) {
|
|
|
15546
15991
|
const gitInfo = await getGitInfo(projectDir);
|
|
15547
15992
|
setTerminalName(formatTerminalName(projectDir, config.profileName ?? "default", gitInfo));
|
|
15548
15993
|
}
|
|
15549
|
-
const bin =
|
|
15994
|
+
const bin = resolveOpenCodeBinary({
|
|
15995
|
+
configBin: ocxConfig?.bin,
|
|
15996
|
+
envBin: process.env.OPENCODE_BIN
|
|
15997
|
+
});
|
|
15550
15998
|
proc = Bun.spawn({
|
|
15551
15999
|
cmd: [bin, ...args],
|
|
15552
16000
|
cwd: projectDir,
|
|
15553
|
-
env: {
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
},
|
|
16001
|
+
env: buildOpenCodeEnv({
|
|
16002
|
+
baseEnv: process.env,
|
|
16003
|
+
profileDir,
|
|
16004
|
+
profileName: config.profileName ?? undefined,
|
|
16005
|
+
mergedConfig: configToPass,
|
|
16006
|
+
disableProjectConfig: true
|
|
16007
|
+
}),
|
|
15560
16008
|
stdin: "inherit",
|
|
15561
16009
|
stdout: "inherit",
|
|
15562
16010
|
stderr: "inherit"
|
|
@@ -15584,12 +16032,8 @@ async function runOpencode(pathArg, args, options2) {
|
|
|
15584
16032
|
// src/commands/profile/install-from-registry.ts
|
|
15585
16033
|
import { createHash as createHash2 } from "crypto";
|
|
15586
16034
|
import { existsSync as existsSync9 } from "fs";
|
|
15587
|
-
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";
|
|
15588
16036
|
import { dirname as dirname4, join as join8 } from "path";
|
|
15589
|
-
var PROFILE_FILE_TARGETS = new Set(["ocx.jsonc", "opencode.jsonc", "AGENTS.md"]);
|
|
15590
|
-
function isProfileFile(target) {
|
|
15591
|
-
return PROFILE_FILE_TARGETS.has(target);
|
|
15592
|
-
}
|
|
15593
16037
|
function hashContent2(content2) {
|
|
15594
16038
|
return createHash2("sha256").update(content2).digest("hex");
|
|
15595
16039
|
}
|
|
@@ -15641,7 +16085,7 @@ Use --force to overwrite.`);
|
|
|
15641
16085
|
const filesSpin = quiet ? null : createSpinner({ text: "Downloading profile files..." });
|
|
15642
16086
|
filesSpin?.start();
|
|
15643
16087
|
const profileFiles = [];
|
|
15644
|
-
const
|
|
16088
|
+
const embeddedFiles = [];
|
|
15645
16089
|
for (const file of normalized.files) {
|
|
15646
16090
|
const content2 = await fetchFileContent(registryUrl, component, file.path);
|
|
15647
16091
|
const fileEntry = {
|
|
@@ -15649,10 +16093,10 @@ Use --force to overwrite.`);
|
|
|
15649
16093
|
target: file.target,
|
|
15650
16094
|
content: Buffer.from(content2)
|
|
15651
16095
|
};
|
|
15652
|
-
if (
|
|
15653
|
-
|
|
16096
|
+
if (file.target.startsWith(".opencode/")) {
|
|
16097
|
+
embeddedFiles.push(fileEntry);
|
|
15654
16098
|
} else {
|
|
15655
|
-
|
|
16099
|
+
profileFiles.push(fileEntry);
|
|
15656
16100
|
}
|
|
15657
16101
|
}
|
|
15658
16102
|
filesSpin?.succeed(`Downloaded ${normalized.files.length} files`);
|
|
@@ -15706,7 +16150,7 @@ Use --force to overwrite.`);
|
|
|
15706
16150
|
}
|
|
15707
16151
|
await writeFile3(targetPath, file.content);
|
|
15708
16152
|
}
|
|
15709
|
-
for (const file of
|
|
16153
|
+
for (const file of embeddedFiles) {
|
|
15710
16154
|
const target = file.target.startsWith(".opencode/") ? file.target.slice(".opencode/".length) : file.target;
|
|
15711
16155
|
const targetPath = join8(stagingOpencodeDir, target);
|
|
15712
16156
|
const targetDir = dirname4(targetPath);
|
|
@@ -15715,7 +16159,7 @@ Use --force to overwrite.`);
|
|
|
15715
16159
|
}
|
|
15716
16160
|
await writeFile3(targetPath, file.content);
|
|
15717
16161
|
}
|
|
15718
|
-
writeSpin?.succeed(`Wrote ${profileFiles.length +
|
|
16162
|
+
writeSpin?.succeed(`Wrote ${profileFiles.length + embeddedFiles.length} profile files`);
|
|
15719
16163
|
if (dependencyBundles.length > 0) {
|
|
15720
16164
|
const depWriteSpin = quiet ? null : createSpinner({ text: "Writing dependency files..." });
|
|
15721
16165
|
depWriteSpin?.start();
|
|
@@ -15764,16 +16208,16 @@ Use --force to overwrite.`);
|
|
|
15764
16208
|
}
|
|
15765
16209
|
if (profileExists && force) {
|
|
15766
16210
|
const backupDir = `${profileDir}.backup-${Date.now()}`;
|
|
15767
|
-
await
|
|
16211
|
+
await rename3(profileDir, backupDir);
|
|
15768
16212
|
try {
|
|
15769
|
-
await
|
|
16213
|
+
await rename3(stagingDir, profileDir);
|
|
15770
16214
|
} catch (err) {
|
|
15771
|
-
await
|
|
16215
|
+
await rename3(backupDir, profileDir);
|
|
15772
16216
|
throw err;
|
|
15773
16217
|
}
|
|
15774
16218
|
await rm3(backupDir, { recursive: true, force: true });
|
|
15775
16219
|
} else {
|
|
15776
|
-
await
|
|
16220
|
+
await rename3(stagingDir, profileDir);
|
|
15777
16221
|
}
|
|
15778
16222
|
moveSpin?.succeed("Installation complete");
|
|
15779
16223
|
if (!quiet) {
|
|
@@ -15846,14 +16290,14 @@ async function requireGlobalRegistry(namespace) {
|
|
|
15846
16290
|
throw new ConfigError(`Registry "${namespace}" is not configured globally.
|
|
15847
16291
|
|
|
15848
16292
|
` + `Profile installation requires global registry configuration.
|
|
15849
|
-
` + `Run: ocx registry add ${namespace}
|
|
16293
|
+
` + `Run: ocx registry add <url> --name ${namespace} --global`);
|
|
15850
16294
|
}
|
|
15851
16295
|
const registry = globalConfig.registries[namespace];
|
|
15852
16296
|
if (!registry) {
|
|
15853
16297
|
throw new ConfigError(`Registry "${namespace}" is not configured globally.
|
|
15854
16298
|
|
|
15855
16299
|
` + `Profile installation requires global registry configuration.
|
|
15856
|
-
` + `Run: ocx registry add ${namespace}
|
|
16300
|
+
` + `Run: ocx registry add <url> --name ${namespace} --global`);
|
|
15857
16301
|
}
|
|
15858
16302
|
return { config: globalConfig, registryUrl: registry.url };
|
|
15859
16303
|
}
|
|
@@ -15931,33 +16375,6 @@ async function cloneFromLocalProfile(manager, name, sourceName, exists) {
|
|
|
15931
16375
|
logger.success(`Created profile "${name}" (cloned from "${sourceName}")`);
|
|
15932
16376
|
}
|
|
15933
16377
|
|
|
15934
|
-
// src/commands/profile/config.ts
|
|
15935
|
-
function registerProfileConfigCommand(parent) {
|
|
15936
|
-
parent.command("config [name]").description("Open profile ocx.jsonc in editor").action(async (name) => {
|
|
15937
|
-
try {
|
|
15938
|
-
await runProfileConfig(name);
|
|
15939
|
-
} catch (error) {
|
|
15940
|
-
handleError(error);
|
|
15941
|
-
}
|
|
15942
|
-
});
|
|
15943
|
-
}
|
|
15944
|
-
async function runProfileConfig(name) {
|
|
15945
|
-
const manager = await ProfileManager.requireInitialized();
|
|
15946
|
-
const profileName = name ?? await manager.resolveProfile();
|
|
15947
|
-
await manager.get(profileName);
|
|
15948
|
-
const configPath = getProfileOcxConfig(profileName);
|
|
15949
|
-
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
15950
|
-
const proc = Bun.spawn([editor, configPath], {
|
|
15951
|
-
stdin: "inherit",
|
|
15952
|
-
stdout: "inherit",
|
|
15953
|
-
stderr: "inherit"
|
|
15954
|
-
});
|
|
15955
|
-
const exitCode = await proc.exited;
|
|
15956
|
-
if (exitCode !== 0) {
|
|
15957
|
-
throw new Error(`Editor exited with code ${exitCode}`);
|
|
15958
|
-
}
|
|
15959
|
-
}
|
|
15960
|
-
|
|
15961
16378
|
// src/commands/profile/list.ts
|
|
15962
16379
|
function registerProfileListCommand(parent) {
|
|
15963
16380
|
parent.command("list").alias("ls").description("List all global profiles").addOption(sharedOptions.json()).action(async (options2) => {
|
|
@@ -15985,6 +16402,25 @@ async function runProfileList(options2) {
|
|
|
15985
16402
|
}
|
|
15986
16403
|
}
|
|
15987
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
|
+
|
|
15988
16424
|
// src/commands/profile/remove.ts
|
|
15989
16425
|
function registerProfileRemoveCommand(parent) {
|
|
15990
16426
|
parent.command("remove <name>").alias("rm").description("Delete a global profile").action(async (name) => {
|
|
@@ -16043,23 +16479,45 @@ function registerProfileCommand(program2) {
|
|
|
16043
16479
|
registerProfileListCommand(profile);
|
|
16044
16480
|
registerProfileAddCommand(profile);
|
|
16045
16481
|
registerProfileRemoveCommand(profile);
|
|
16482
|
+
registerProfileMoveCommand(profile);
|
|
16046
16483
|
registerProfileShowCommand(profile);
|
|
16047
|
-
registerProfileConfigCommand(profile);
|
|
16048
16484
|
}
|
|
16049
16485
|
|
|
16050
16486
|
// src/commands/registry.ts
|
|
16487
|
+
import { existsSync as existsSync10 } from "fs";
|
|
16488
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
16051
16489
|
async function runRegistryAddCore2(url, options2, callbacks) {
|
|
16052
16490
|
if (callbacks.isLocked?.()) {
|
|
16053
16491
|
throw new Error("Registries are locked. Cannot add.");
|
|
16054
16492
|
}
|
|
16055
|
-
const
|
|
16493
|
+
const trimmedUrl = url.trim();
|
|
16494
|
+
if (!trimmedUrl) {
|
|
16495
|
+
throw new ValidationError("Registry URL is required");
|
|
16496
|
+
}
|
|
16497
|
+
let derivedName;
|
|
16498
|
+
try {
|
|
16499
|
+
const parsed = new URL(trimmedUrl);
|
|
16500
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
16501
|
+
throw new ValidationError(`Invalid registry URL: ${trimmedUrl} (must use http or https)`);
|
|
16502
|
+
}
|
|
16503
|
+
derivedName = options2.name || parsed.hostname.replace(/\./g, "-");
|
|
16504
|
+
} catch (error) {
|
|
16505
|
+
if (error instanceof ValidationError)
|
|
16506
|
+
throw error;
|
|
16507
|
+
throw new ValidationError(`Invalid registry URL: ${trimmedUrl}`);
|
|
16508
|
+
}
|
|
16509
|
+
const name = derivedName;
|
|
16056
16510
|
const registries = callbacks.getRegistries();
|
|
16511
|
+
const existingRegistry = registries[name];
|
|
16512
|
+
if (existingRegistry && !options2.force) {
|
|
16513
|
+
throw new RegistryExistsError(name, existingRegistry.url, trimmedUrl);
|
|
16514
|
+
}
|
|
16057
16515
|
const isUpdate = name in registries;
|
|
16058
16516
|
await callbacks.setRegistry(name, {
|
|
16059
|
-
url,
|
|
16517
|
+
url: trimmedUrl,
|
|
16060
16518
|
version: options2.version
|
|
16061
16519
|
});
|
|
16062
|
-
return { name, url, updated: isUpdate };
|
|
16520
|
+
return { name, url: trimmedUrl, updated: isUpdate };
|
|
16063
16521
|
}
|
|
16064
16522
|
async function runRegistryRemoveCore(name, callbacks) {
|
|
16065
16523
|
if (callbacks.isLocked?.()) {
|
|
@@ -16082,43 +16540,69 @@ function runRegistryListCore(callbacks) {
|
|
|
16082
16540
|
}));
|
|
16083
16541
|
return { registries: list, locked };
|
|
16084
16542
|
}
|
|
16543
|
+
async function resolveRegistryTarget(options2, command, cwd) {
|
|
16544
|
+
const cwdExplicitlyProvided = command.getOptionValueSource("cwd") === "cli";
|
|
16545
|
+
if (options2.global && options2.profile) {
|
|
16546
|
+
throw new ValidationError("Cannot use both --global and --profile flags");
|
|
16547
|
+
}
|
|
16548
|
+
if (cwdExplicitlyProvided && options2.profile) {
|
|
16549
|
+
throw new ValidationError("Cannot use both --cwd and --profile flags");
|
|
16550
|
+
}
|
|
16551
|
+
if (options2.global && cwdExplicitlyProvided) {
|
|
16552
|
+
throw new ValidationError("Cannot use both --global and --cwd flags");
|
|
16553
|
+
}
|
|
16554
|
+
if (options2.profile) {
|
|
16555
|
+
validateProfileName(options2.profile);
|
|
16556
|
+
const manager = await ProfileManager.requireInitialized();
|
|
16557
|
+
if (!await manager.exists(options2.profile)) {
|
|
16558
|
+
throw new ProfileNotFoundError(options2.profile);
|
|
16559
|
+
}
|
|
16560
|
+
const configPath = getProfileOcxConfig(options2.profile);
|
|
16561
|
+
if (!existsSync10(configPath)) {
|
|
16562
|
+
throw new OcxConfigError(`Profile '${options2.profile}' has no ocx.jsonc. Run 'ocx profile config ${options2.profile}' to create it.`);
|
|
16563
|
+
}
|
|
16564
|
+
return {
|
|
16565
|
+
scope: "profile",
|
|
16566
|
+
configPath,
|
|
16567
|
+
configDir: dirname5(configPath),
|
|
16568
|
+
targetLabel: `profile '${options2.profile}' config`
|
|
16569
|
+
};
|
|
16570
|
+
}
|
|
16571
|
+
if (options2.global) {
|
|
16572
|
+
const configDir = getGlobalConfigPath();
|
|
16573
|
+
return {
|
|
16574
|
+
scope: "global",
|
|
16575
|
+
configPath: join9(configDir, "ocx.jsonc"),
|
|
16576
|
+
configDir,
|
|
16577
|
+
targetLabel: "global config"
|
|
16578
|
+
};
|
|
16579
|
+
}
|
|
16580
|
+
const found = findOcxConfig(cwd);
|
|
16581
|
+
return {
|
|
16582
|
+
scope: "local",
|
|
16583
|
+
configPath: found.path,
|
|
16584
|
+
configDir: found.exists ? dirname5(found.path) : join9(cwd, ".opencode"),
|
|
16585
|
+
targetLabel: "local config"
|
|
16586
|
+
};
|
|
16587
|
+
}
|
|
16085
16588
|
function registerRegistryCommand(program2) {
|
|
16086
16589
|
const registry = program2.command("registry").description("Manage registries");
|
|
16087
|
-
const addCmd = registry.command("add").description("Add a registry").argument("<url>", "Registry URL").option("--name <name>", "Registry alias (defaults to hostname)").option("--version <version>", "Pin to specific version");
|
|
16590
|
+
const addCmd = registry.command("add").description("Add a registry").argument("<url>", "Registry URL").option("--name <name>", "Registry alias (defaults to hostname)").option("--version <version>", "Pin to specific version").option("-f, --force", "Overwrite existing registry");
|
|
16088
16591
|
addGlobalOption(addCmd);
|
|
16592
|
+
addProfileOption(addCmd);
|
|
16089
16593
|
addCommonOptions(addCmd);
|
|
16090
16594
|
addCmd.action(async (url, options2, command) => {
|
|
16595
|
+
let target;
|
|
16091
16596
|
try {
|
|
16092
|
-
const
|
|
16093
|
-
|
|
16094
|
-
|
|
16597
|
+
const cwd = options2.cwd ?? process.cwd();
|
|
16598
|
+
target = await resolveRegistryTarget(options2, command, cwd);
|
|
16599
|
+
const { configDir, configPath } = target;
|
|
16600
|
+
const config = await readOcxConfig(configDir);
|
|
16601
|
+
if (!config) {
|
|
16602
|
+
const initHint = target.scope === "global" ? "Run 'ocx init --global' first." : target.scope === "profile" ? `Run 'ocx profile config ${options2.profile}' to create it.` : "Run 'ocx init' first.";
|
|
16603
|
+
logger.error(`${target.targetLabel} not found. ${initHint}`);
|
|
16095
16604
|
process.exit(1);
|
|
16096
16605
|
}
|
|
16097
|
-
let configDir;
|
|
16098
|
-
let configPath;
|
|
16099
|
-
const config = await (async () => {
|
|
16100
|
-
if (options2.global) {
|
|
16101
|
-
configDir = getGlobalConfigPath();
|
|
16102
|
-
const found = findOcxConfig(configDir);
|
|
16103
|
-
configPath = found.path;
|
|
16104
|
-
const cfg = await readOcxConfig(configDir);
|
|
16105
|
-
if (!cfg) {
|
|
16106
|
-
logger.error("Global config not found. Run 'opencode' once to initialize.");
|
|
16107
|
-
process.exit(1);
|
|
16108
|
-
}
|
|
16109
|
-
return cfg;
|
|
16110
|
-
} else {
|
|
16111
|
-
configDir = options2.cwd ?? process.cwd();
|
|
16112
|
-
const found = findOcxConfig(configDir);
|
|
16113
|
-
configPath = found.path;
|
|
16114
|
-
const cfg = await readOcxConfig(configDir);
|
|
16115
|
-
if (!cfg) {
|
|
16116
|
-
logger.error("No ocx.jsonc found. Run 'ocx init' first.");
|
|
16117
|
-
process.exit(1);
|
|
16118
|
-
}
|
|
16119
|
-
return cfg;
|
|
16120
|
-
}
|
|
16121
|
-
})();
|
|
16122
16606
|
const result = await runRegistryAddCore2(url, options2, {
|
|
16123
16607
|
getRegistries: () => config.registries,
|
|
16124
16608
|
isLocked: () => config.lockRegistries ?? false,
|
|
@@ -16130,65 +16614,46 @@ function registerRegistryCommand(program2) {
|
|
|
16130
16614
|
if (options2.json) {
|
|
16131
16615
|
outputJson({ success: true, data: result });
|
|
16132
16616
|
} else if (!options2.quiet) {
|
|
16133
|
-
const location = options2.global ? "global config" : "local config";
|
|
16134
16617
|
if (result.updated) {
|
|
16135
|
-
logger.success(`Updated registry in ${
|
|
16618
|
+
logger.success(`Updated registry in ${target.targetLabel}: ${result.name} -> ${result.url}`);
|
|
16136
16619
|
} else {
|
|
16137
|
-
logger.success(`Added registry to ${
|
|
16620
|
+
logger.success(`Added registry to ${target.targetLabel}: ${result.name} -> ${result.url}`);
|
|
16138
16621
|
}
|
|
16139
16622
|
}
|
|
16140
16623
|
} catch (error) {
|
|
16624
|
+
if (error instanceof RegistryExistsError && !error.targetLabel) {
|
|
16625
|
+
const enrichedError = new RegistryExistsError(error.registryName, error.existingUrl, error.newUrl, target?.targetLabel ?? "config");
|
|
16626
|
+
handleError(enrichedError, { json: options2.json });
|
|
16627
|
+
}
|
|
16141
16628
|
handleError(error, { json: options2.json });
|
|
16142
16629
|
}
|
|
16143
16630
|
});
|
|
16144
16631
|
const removeCmd = registry.command("remove").description("Remove a registry").argument("<name>", "Registry name");
|
|
16145
16632
|
addGlobalOption(removeCmd);
|
|
16633
|
+
addProfileOption(removeCmd);
|
|
16146
16634
|
addCommonOptions(removeCmd);
|
|
16147
16635
|
removeCmd.action(async (name, options2, command) => {
|
|
16148
16636
|
try {
|
|
16149
|
-
const
|
|
16150
|
-
|
|
16151
|
-
|
|
16637
|
+
const cwd = options2.cwd ?? process.cwd();
|
|
16638
|
+
const target = await resolveRegistryTarget(options2, command, cwd);
|
|
16639
|
+
const config = await readOcxConfig(target.configDir);
|
|
16640
|
+
if (!config) {
|
|
16641
|
+
const initHint = target.scope === "global" ? "Run 'ocx init --global' first." : target.scope === "profile" ? `Run 'ocx profile config ${options2.profile}' to create it.` : "Run 'ocx init' first.";
|
|
16642
|
+
logger.error(`${target.targetLabel} not found. ${initHint}`);
|
|
16152
16643
|
process.exit(1);
|
|
16153
16644
|
}
|
|
16154
|
-
let configDir;
|
|
16155
|
-
let configPath;
|
|
16156
|
-
const config = await (async () => {
|
|
16157
|
-
if (options2.global) {
|
|
16158
|
-
configDir = getGlobalConfigPath();
|
|
16159
|
-
const found = findOcxConfig(configDir);
|
|
16160
|
-
configPath = found.path;
|
|
16161
|
-
const cfg = await readOcxConfig(configDir);
|
|
16162
|
-
if (!cfg) {
|
|
16163
|
-
logger.error("Global config not found. Run 'opencode' once to initialize.");
|
|
16164
|
-
process.exit(1);
|
|
16165
|
-
}
|
|
16166
|
-
return cfg;
|
|
16167
|
-
} else {
|
|
16168
|
-
configDir = options2.cwd ?? process.cwd();
|
|
16169
|
-
const found = findOcxConfig(configDir);
|
|
16170
|
-
configPath = found.path;
|
|
16171
|
-
const cfg = await readOcxConfig(configDir);
|
|
16172
|
-
if (!cfg) {
|
|
16173
|
-
logger.error("No ocx.jsonc found. Run 'ocx init' first.");
|
|
16174
|
-
process.exit(1);
|
|
16175
|
-
}
|
|
16176
|
-
return cfg;
|
|
16177
|
-
}
|
|
16178
|
-
})();
|
|
16179
16645
|
const result = await runRegistryRemoveCore(name, {
|
|
16180
16646
|
getRegistries: () => config.registries,
|
|
16181
16647
|
isLocked: () => config.lockRegistries ?? false,
|
|
16182
16648
|
removeRegistry: async (regName) => {
|
|
16183
16649
|
delete config.registries[regName];
|
|
16184
|
-
await writeOcxConfig(configDir, config, configPath);
|
|
16650
|
+
await writeOcxConfig(target.configDir, config, target.configPath);
|
|
16185
16651
|
}
|
|
16186
16652
|
});
|
|
16187
16653
|
if (options2.json) {
|
|
16188
16654
|
outputJson({ success: true, data: result });
|
|
16189
16655
|
} else if (!options2.quiet) {
|
|
16190
|
-
|
|
16191
|
-
logger.success(`Removed registry from ${location}: ${result.removed}`);
|
|
16656
|
+
logger.success(`Removed registry from ${target.targetLabel}: ${result.removed}`);
|
|
16192
16657
|
}
|
|
16193
16658
|
} catch (error) {
|
|
16194
16659
|
handleError(error, { json: options2.json });
|
|
@@ -16196,36 +16661,18 @@ function registerRegistryCommand(program2) {
|
|
|
16196
16661
|
});
|
|
16197
16662
|
const listCmd = registry.command("list").description("List configured registries");
|
|
16198
16663
|
addGlobalOption(listCmd);
|
|
16664
|
+
addProfileOption(listCmd);
|
|
16199
16665
|
addCommonOptions(listCmd);
|
|
16200
16666
|
listCmd.action(async (options2, command) => {
|
|
16201
16667
|
try {
|
|
16202
|
-
const
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
16208
|
-
const config = await (async () => {
|
|
16209
|
-
if (options2.global) {
|
|
16210
|
-
configDir = getGlobalConfigPath();
|
|
16211
|
-
const cfg = await readOcxConfig(configDir);
|
|
16212
|
-
if (!cfg) {
|
|
16213
|
-
logger.warn("Global config not found. Run 'opencode' once to initialize.");
|
|
16214
|
-
return null;
|
|
16215
|
-
}
|
|
16216
|
-
return cfg;
|
|
16217
|
-
} else {
|
|
16218
|
-
configDir = options2.cwd ?? process.cwd();
|
|
16219
|
-
const cfg = await readOcxConfig(configDir);
|
|
16220
|
-
if (!cfg) {
|
|
16221
|
-
logger.warn("No ocx.jsonc found. Run 'ocx init' first.");
|
|
16222
|
-
return null;
|
|
16223
|
-
}
|
|
16224
|
-
return cfg;
|
|
16225
|
-
}
|
|
16226
|
-
})();
|
|
16227
|
-
if (!config)
|
|
16668
|
+
const cwd = options2.cwd ?? process.cwd();
|
|
16669
|
+
const target = await resolveRegistryTarget(options2, command, cwd);
|
|
16670
|
+
const config = await readOcxConfig(target.configDir);
|
|
16671
|
+
if (!config) {
|
|
16672
|
+
const initHint = target.scope === "global" ? "Run 'ocx init --global' first." : target.scope === "profile" ? `Run 'ocx profile config ${options2.profile}' to create it.` : "Run 'ocx init' first.";
|
|
16673
|
+
logger.warn(`${target.targetLabel} not found. ${initHint}`);
|
|
16228
16674
|
return;
|
|
16675
|
+
}
|
|
16229
16676
|
const result = runRegistryListCore({
|
|
16230
16677
|
getRegistries: () => config.registries,
|
|
16231
16678
|
isLocked: () => config.lockRegistries ?? false
|
|
@@ -16236,7 +16683,8 @@ function registerRegistryCommand(program2) {
|
|
|
16236
16683
|
if (result.registries.length === 0) {
|
|
16237
16684
|
logger.info("No registries configured.");
|
|
16238
16685
|
} else {
|
|
16239
|
-
|
|
16686
|
+
const scopeLabel = target.scope === "global" ? " (global)" : target.scope === "profile" ? ` (profile '${options2.profile}')` : "";
|
|
16687
|
+
logger.info(`Configured registries${scopeLabel}${result.locked ? kleur_default.yellow(" (locked)") : ""}:`);
|
|
16240
16688
|
for (const reg of result.registries) {
|
|
16241
16689
|
console.log(` ${kleur_default.cyan(reg.name)}: ${reg.url} ${kleur_default.dim(`(${reg.version})`)}`);
|
|
16242
16690
|
}
|
|
@@ -16369,9 +16817,460 @@ async function runSearchCore(query, options2, provider) {
|
|
|
16369
16817
|
}
|
|
16370
16818
|
}
|
|
16371
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
|
+
|
|
16372
17271
|
// src/self-update/version-provider.ts
|
|
16373
17272
|
class BuildTimeVersionProvider {
|
|
16374
|
-
version = "1.4.
|
|
17273
|
+
version = "1.4.3";
|
|
16375
17274
|
}
|
|
16376
17275
|
var defaultVersionProvider = new BuildTimeVersionProvider;
|
|
16377
17276
|
|
|
@@ -16434,57 +17333,8 @@ async function checkForUpdate(versionProvider, timeoutMs = VERSION_CHECK_TIMEOUT
|
|
|
16434
17333
|
}
|
|
16435
17334
|
}
|
|
16436
17335
|
|
|
16437
|
-
// src/self-update/detect-method.ts
|
|
16438
|
-
function parseInstallMethod(input) {
|
|
16439
|
-
const VALID_METHODS = ["curl", "npm", "yarn", "pnpm", "bun"];
|
|
16440
|
-
const method = VALID_METHODS.find((m) => m === input);
|
|
16441
|
-
if (!method) {
|
|
16442
|
-
throw new SelfUpdateError(`Invalid install method: "${input}"
|
|
16443
|
-
Valid methods: ${VALID_METHODS.join(", ")}`);
|
|
16444
|
-
}
|
|
16445
|
-
return method;
|
|
16446
|
-
}
|
|
16447
|
-
var isCompiledBinary = () => Bun.main.startsWith("/$bunfs/");
|
|
16448
|
-
var isTempExecution = (path8) => path8.includes("/_npx/") || path8.includes("/.cache/bunx/") || path8.includes("/.pnpm/_temp/");
|
|
16449
|
-
var isYarnGlobalInstall = (path8) => path8.includes("/.yarn/global") || path8.includes("/.config/yarn/global");
|
|
16450
|
-
var isPnpmGlobalInstall = (path8) => path8.includes("/.pnpm/") || path8.includes("/pnpm/global");
|
|
16451
|
-
var isBunGlobalInstall = (path8) => path8.includes("/.bun/bin") || path8.includes("/.bun/install/global");
|
|
16452
|
-
var isNpmGlobalInstall = (path8) => path8.includes("/.npm/") || path8.includes("/node_modules/");
|
|
16453
|
-
function detectInstallMethod() {
|
|
16454
|
-
if (isCompiledBinary()) {
|
|
16455
|
-
return "curl";
|
|
16456
|
-
}
|
|
16457
|
-
const scriptPath = process.argv[1] ?? "";
|
|
16458
|
-
if (isTempExecution(scriptPath))
|
|
16459
|
-
return "unknown";
|
|
16460
|
-
if (isYarnGlobalInstall(scriptPath))
|
|
16461
|
-
return "yarn";
|
|
16462
|
-
if (isPnpmGlobalInstall(scriptPath))
|
|
16463
|
-
return "pnpm";
|
|
16464
|
-
if (isBunGlobalInstall(scriptPath))
|
|
16465
|
-
return "bun";
|
|
16466
|
-
if (isNpmGlobalInstall(scriptPath))
|
|
16467
|
-
return "npm";
|
|
16468
|
-
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
16469
|
-
if (userAgent.includes("yarn"))
|
|
16470
|
-
return "yarn";
|
|
16471
|
-
if (userAgent.includes("pnpm"))
|
|
16472
|
-
return "pnpm";
|
|
16473
|
-
if (userAgent.includes("bun"))
|
|
16474
|
-
return "bun";
|
|
16475
|
-
if (userAgent.includes("npm"))
|
|
16476
|
-
return "npm";
|
|
16477
|
-
return "unknown";
|
|
16478
|
-
}
|
|
16479
|
-
function getExecutablePath() {
|
|
16480
|
-
if (typeof Bun !== "undefined" && Bun.main.startsWith("/$bunfs/")) {
|
|
16481
|
-
return process.execPath;
|
|
16482
|
-
}
|
|
16483
|
-
return process.argv[1] ?? process.execPath;
|
|
16484
|
-
}
|
|
16485
|
-
|
|
16486
17336
|
// src/self-update/download.ts
|
|
16487
|
-
import { chmodSync, existsSync as
|
|
17337
|
+
import { chmodSync, existsSync as existsSync12, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
16488
17338
|
var GITHUB_REPO2 = "kdcokenny/ocx";
|
|
16489
17339
|
var DEFAULT_DOWNLOAD_BASE_URL = `https://github.com/${GITHUB_REPO2}/releases/download`;
|
|
16490
17340
|
var PLATFORM_MAP = {
|
|
@@ -16562,8 +17412,8 @@ async function downloadToTemp(version) {
|
|
|
16562
17412
|
try {
|
|
16563
17413
|
chmodSync(tempPath, 493);
|
|
16564
17414
|
} catch (error) {
|
|
16565
|
-
if (
|
|
16566
|
-
|
|
17415
|
+
if (existsSync12(tempPath)) {
|
|
17416
|
+
unlinkSync3(tempPath);
|
|
16567
17417
|
}
|
|
16568
17418
|
throw new SelfUpdateError(`Failed to set permissions: ${error instanceof Error ? error.message : String(error)}`);
|
|
16569
17419
|
}
|
|
@@ -16572,31 +17422,31 @@ async function downloadToTemp(version) {
|
|
|
16572
17422
|
function atomicReplace(tempPath, execPath) {
|
|
16573
17423
|
const backupPath = `${execPath}.backup`;
|
|
16574
17424
|
try {
|
|
16575
|
-
if (
|
|
17425
|
+
if (existsSync12(execPath)) {
|
|
16576
17426
|
renameSync2(execPath, backupPath);
|
|
16577
17427
|
}
|
|
16578
17428
|
renameSync2(tempPath, execPath);
|
|
16579
|
-
if (
|
|
16580
|
-
|
|
17429
|
+
if (existsSync12(backupPath)) {
|
|
17430
|
+
unlinkSync3(backupPath);
|
|
16581
17431
|
}
|
|
16582
17432
|
} catch (error) {
|
|
16583
|
-
if (
|
|
17433
|
+
if (existsSync12(backupPath) && !existsSync12(execPath)) {
|
|
16584
17434
|
try {
|
|
16585
17435
|
renameSync2(backupPath, execPath);
|
|
16586
17436
|
} catch {}
|
|
16587
17437
|
}
|
|
16588
|
-
if (
|
|
17438
|
+
if (existsSync12(tempPath)) {
|
|
16589
17439
|
try {
|
|
16590
|
-
|
|
17440
|
+
unlinkSync3(tempPath);
|
|
16591
17441
|
} catch {}
|
|
16592
17442
|
}
|
|
16593
17443
|
throw new SelfUpdateError(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
16594
17444
|
}
|
|
16595
17445
|
}
|
|
16596
17446
|
function cleanupTempFile(tempPath) {
|
|
16597
|
-
if (
|
|
17447
|
+
if (existsSync12(tempPath)) {
|
|
16598
17448
|
try {
|
|
16599
|
-
|
|
17449
|
+
unlinkSync3(tempPath);
|
|
16600
17450
|
} catch {}
|
|
16601
17451
|
}
|
|
16602
17452
|
}
|
|
@@ -16765,14 +17615,15 @@ function registerSelfUpdateCommand(parent) {
|
|
|
16765
17615
|
// src/commands/self/index.ts
|
|
16766
17616
|
function registerSelfCommand(program2) {
|
|
16767
17617
|
const self = program2.command("self").description("Manage the OCX CLI");
|
|
17618
|
+
registerSelfUninstallCommand(self);
|
|
16768
17619
|
registerSelfUpdateCommand(self);
|
|
16769
17620
|
}
|
|
16770
17621
|
|
|
16771
17622
|
// src/commands/update.ts
|
|
16772
17623
|
import { createHash as createHash4 } from "crypto";
|
|
16773
|
-
import { existsSync as
|
|
17624
|
+
import { existsSync as existsSync13 } from "fs";
|
|
16774
17625
|
import { mkdir as mkdir9, writeFile as writeFile4 } from "fs/promises";
|
|
16775
|
-
import { dirname as
|
|
17626
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
16776
17627
|
function registerUpdateCommand(program2) {
|
|
16777
17628
|
program2.command("update [components...]").description("Update installed components (use @version suffix to pin, e.g., kdco/agents@1.2.0)").option("--all", "Update all installed components").option("--registry <name>", "Update all components from a specific registry").option("--dry-run", "Preview changes without applying").option("--cwd <path>", "Working directory", process.cwd()).option("-q, --quiet", "Suppress output").option("-v, --verbose", "Verbose output").option("--json", "Output as JSON").action(async (components, options2) => {
|
|
16778
17629
|
try {
|
|
@@ -16910,9 +17761,9 @@ Version cannot be empty. Use 'kdco/agents@1.2.0' or omit the version for latest.
|
|
|
16910
17761
|
const fileObj = update.component.files.find((f) => f.path === file.path);
|
|
16911
17762
|
if (!fileObj)
|
|
16912
17763
|
continue;
|
|
16913
|
-
const targetPath =
|
|
16914
|
-
const targetDir =
|
|
16915
|
-
if (!
|
|
17764
|
+
const targetPath = join10(provider.cwd, fileObj.target);
|
|
17765
|
+
const targetDir = dirname6(targetPath);
|
|
17766
|
+
if (!existsSync13(targetDir)) {
|
|
16916
17767
|
await mkdir9(targetDir, { recursive: true });
|
|
16917
17768
|
}
|
|
16918
17769
|
await writeFile4(targetPath, file.content);
|
|
@@ -17061,8 +17912,8 @@ function shouldCheckForUpdate() {
|
|
|
17061
17912
|
return true;
|
|
17062
17913
|
}
|
|
17063
17914
|
function registerUpdateCheckHook(program2) {
|
|
17064
|
-
program2.hook("postAction", async (
|
|
17065
|
-
if (
|
|
17915
|
+
program2.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
17916
|
+
if (actionCommand.name() === "update" && actionCommand.parent?.name() === "self") {
|
|
17066
17917
|
return;
|
|
17067
17918
|
}
|
|
17068
17919
|
if (!shouldCheckForUpdate())
|
|
@@ -17076,7 +17927,7 @@ function registerUpdateCheckHook(program2) {
|
|
|
17076
17927
|
});
|
|
17077
17928
|
}
|
|
17078
17929
|
// src/index.ts
|
|
17079
|
-
var version = "1.4.
|
|
17930
|
+
var version = "1.4.3";
|
|
17080
17931
|
async function main2() {
|
|
17081
17932
|
const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
|
|
17082
17933
|
registerInitCommand(program2);
|
|
@@ -17108,4 +17959,4 @@ export {
|
|
|
17108
17959
|
buildRegistry
|
|
17109
17960
|
};
|
|
17110
17961
|
|
|
17111
|
-
//# debugId=
|
|
17962
|
+
//# debugId=947205F975538A0764756E2164756E21
|