ocx 1.4.2 → 1.4.4
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 +1069 -434
- package/dist/index.js.map +22 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3949,7 +3949,7 @@ var require_fuzzysort = __commonJS((exports, module) => {
|
|
|
3949
3949
|
results.total = resultsLen + limitedCount;
|
|
3950
3950
|
return results;
|
|
3951
3951
|
};
|
|
3952
|
-
var
|
|
3952
|
+
var highlight2 = (result, open = "<b>", close = "</b>") => {
|
|
3953
3953
|
var callback = typeof open === "function" ? open : undefined;
|
|
3954
3954
|
var target = result.target;
|
|
3955
3955
|
var targetLen = target.length;
|
|
@@ -4019,7 +4019,7 @@ var require_fuzzysort = __commonJS((exports, module) => {
|
|
|
4019
4019
|
return this._indexes = indexes;
|
|
4020
4020
|
}
|
|
4021
4021
|
["highlight"](open, close) {
|
|
4022
|
-
return
|
|
4022
|
+
return highlight2(this, open, close);
|
|
4023
4023
|
}
|
|
4024
4024
|
get ["score"]() {
|
|
4025
4025
|
return normalizeScore(this._score);
|
|
@@ -4510,13 +4510,13 @@ var {
|
|
|
4510
4510
|
// src/commands/add.ts
|
|
4511
4511
|
import { createHash } from "crypto";
|
|
4512
4512
|
import { existsSync as existsSync5 } from "fs";
|
|
4513
|
-
import { mkdir as
|
|
4514
|
-
import { dirname, join as
|
|
4513
|
+
import { mkdir as mkdir5, writeFile } from "fs/promises";
|
|
4514
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
4515
4515
|
|
|
4516
4516
|
// src/schemas/config.ts
|
|
4517
4517
|
import { existsSync } from "fs";
|
|
4518
4518
|
import { mkdir } from "fs/promises";
|
|
4519
|
-
import
|
|
4519
|
+
import path2 from "path";
|
|
4520
4520
|
|
|
4521
4521
|
// ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
4522
4522
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -9815,7 +9815,7 @@ var coerce = {
|
|
|
9815
9815
|
};
|
|
9816
9816
|
var NEVER = INVALID;
|
|
9817
9817
|
// src/schemas/registry.ts
|
|
9818
|
-
import { isAbsolute, normalize } from "path";
|
|
9818
|
+
import { isAbsolute as isAbsolute2, normalize } from "path";
|
|
9819
9819
|
|
|
9820
9820
|
// src/utils/errors.ts
|
|
9821
9821
|
var EXIT_CODES = {
|
|
@@ -9847,9 +9847,15 @@ class NotFoundError extends OCXError {
|
|
|
9847
9847
|
}
|
|
9848
9848
|
|
|
9849
9849
|
class NetworkError extends OCXError {
|
|
9850
|
-
|
|
9850
|
+
url;
|
|
9851
|
+
status;
|
|
9852
|
+
statusText;
|
|
9853
|
+
constructor(message, options) {
|
|
9851
9854
|
super(message, "NETWORK_ERROR", EXIT_CODES.NETWORK);
|
|
9852
9855
|
this.name = "NetworkError";
|
|
9856
|
+
this.url = options?.url;
|
|
9857
|
+
this.status = options?.status;
|
|
9858
|
+
this.statusText = options?.statusText;
|
|
9853
9859
|
}
|
|
9854
9860
|
}
|
|
9855
9861
|
|
|
@@ -9875,6 +9881,9 @@ class ConflictError extends OCXError {
|
|
|
9875
9881
|
}
|
|
9876
9882
|
|
|
9877
9883
|
class IntegrityError extends OCXError {
|
|
9884
|
+
component;
|
|
9885
|
+
expected;
|
|
9886
|
+
found;
|
|
9878
9887
|
constructor(component, expected, found) {
|
|
9879
9888
|
const message = `Integrity verification failed for "${component}"
|
|
9880
9889
|
` + ` Expected: ${expected}
|
|
@@ -9883,6 +9892,9 @@ class IntegrityError extends OCXError {
|
|
|
9883
9892
|
` + `The registry content has changed since this component was locked.
|
|
9884
9893
|
` + `Use 'ocx update ${component}' to intentionally update this component.`;
|
|
9885
9894
|
super(message, "INTEGRITY_ERROR", EXIT_CODES.INTEGRITY);
|
|
9895
|
+
this.component = component;
|
|
9896
|
+
this.expected = expected;
|
|
9897
|
+
this.found = found;
|
|
9886
9898
|
this.name = "IntegrityError";
|
|
9887
9899
|
}
|
|
9888
9900
|
}
|
|
@@ -9902,15 +9914,20 @@ class OcxConfigError extends OCXError {
|
|
|
9902
9914
|
}
|
|
9903
9915
|
|
|
9904
9916
|
class ProfileNotFoundError extends OCXError {
|
|
9905
|
-
|
|
9906
|
-
|
|
9917
|
+
profile;
|
|
9918
|
+
constructor(profile) {
|
|
9919
|
+
super(`Profile "${profile}" not found`, "NOT_FOUND", EXIT_CODES.NOT_FOUND);
|
|
9920
|
+
this.profile = profile;
|
|
9907
9921
|
this.name = "ProfileNotFoundError";
|
|
9908
9922
|
}
|
|
9909
9923
|
}
|
|
9910
9924
|
|
|
9911
9925
|
class ProfileExistsError extends OCXError {
|
|
9912
|
-
|
|
9913
|
-
|
|
9926
|
+
profile;
|
|
9927
|
+
constructor(profile, hint) {
|
|
9928
|
+
const message = hint ? `Profile "${profile}" already exists. ${hint}` : `Profile "${profile}" already exists.`;
|
|
9929
|
+
super(message, "CONFLICT", EXIT_CODES.CONFLICT);
|
|
9930
|
+
this.profile = profile;
|
|
9914
9931
|
this.name = "ProfileExistsError";
|
|
9915
9932
|
}
|
|
9916
9933
|
}
|
|
@@ -9937,8 +9954,12 @@ class RegistryExistsError extends OCXError {
|
|
|
9937
9954
|
}
|
|
9938
9955
|
|
|
9939
9956
|
class InvalidProfileNameError extends OCXError {
|
|
9940
|
-
|
|
9941
|
-
|
|
9957
|
+
profile;
|
|
9958
|
+
reason;
|
|
9959
|
+
constructor(profile, reason) {
|
|
9960
|
+
super(`Invalid profile name "${profile}": ${reason}`, "VALIDATION_ERROR", EXIT_CODES.GENERAL);
|
|
9961
|
+
this.profile = profile;
|
|
9962
|
+
this.reason = reason;
|
|
9942
9963
|
this.name = "InvalidProfileNameError";
|
|
9943
9964
|
}
|
|
9944
9965
|
}
|
|
@@ -9950,6 +9971,68 @@ class ProfilesNotInitializedError extends OCXError {
|
|
|
9950
9971
|
}
|
|
9951
9972
|
}
|
|
9952
9973
|
|
|
9974
|
+
// src/utils/path-security.ts
|
|
9975
|
+
import * as path from "path";
|
|
9976
|
+
|
|
9977
|
+
class PathValidationError extends Error {
|
|
9978
|
+
attemptedPath;
|
|
9979
|
+
reason;
|
|
9980
|
+
constructor(message, attemptedPath, reason) {
|
|
9981
|
+
super(message);
|
|
9982
|
+
this.attemptedPath = attemptedPath;
|
|
9983
|
+
this.reason = reason;
|
|
9984
|
+
this.name = "PathValidationError";
|
|
9985
|
+
}
|
|
9986
|
+
}
|
|
9987
|
+
var WINDOWS_RESERVED = new Set([
|
|
9988
|
+
"CON",
|
|
9989
|
+
"PRN",
|
|
9990
|
+
"AUX",
|
|
9991
|
+
"NUL",
|
|
9992
|
+
"COM1",
|
|
9993
|
+
"COM2",
|
|
9994
|
+
"COM3",
|
|
9995
|
+
"COM4",
|
|
9996
|
+
"COM5",
|
|
9997
|
+
"COM6",
|
|
9998
|
+
"COM7",
|
|
9999
|
+
"COM8",
|
|
10000
|
+
"COM9",
|
|
10001
|
+
"LPT1",
|
|
10002
|
+
"LPT2",
|
|
10003
|
+
"LPT3",
|
|
10004
|
+
"LPT4",
|
|
10005
|
+
"LPT5",
|
|
10006
|
+
"LPT6",
|
|
10007
|
+
"LPT7",
|
|
10008
|
+
"LPT8",
|
|
10009
|
+
"LPT9"
|
|
10010
|
+
]);
|
|
10011
|
+
function validatePath(basePath, userPath) {
|
|
10012
|
+
if (userPath.includes("\x00")) {
|
|
10013
|
+
throw new PathValidationError("Path contains null bytes", userPath, "null_byte");
|
|
10014
|
+
}
|
|
10015
|
+
if (path.isAbsolute(userPath) || path.win32.isAbsolute(userPath)) {
|
|
10016
|
+
throw new PathValidationError("Path must be relative", userPath, "absolute_path");
|
|
10017
|
+
}
|
|
10018
|
+
if (/^[a-zA-Z]:/.test(userPath) || userPath.startsWith("\\\\")) {
|
|
10019
|
+
throw new PathValidationError("Path contains Windows absolute", userPath, "windows_absolute");
|
|
10020
|
+
}
|
|
10021
|
+
const baseName = path.basename(userPath).toUpperCase().split(".")[0] ?? "";
|
|
10022
|
+
if (WINDOWS_RESERVED.has(baseName)) {
|
|
10023
|
+
throw new PathValidationError("Path uses Windows reserved name", userPath, "windows_reserved");
|
|
10024
|
+
}
|
|
10025
|
+
const normalized = userPath.normalize("NFC");
|
|
10026
|
+
const unified = normalized.replace(/\\/g, "/");
|
|
10027
|
+
const resolvedBase = path.resolve(basePath);
|
|
10028
|
+
const resolvedCombined = path.resolve(resolvedBase, unified);
|
|
10029
|
+
const relativePath = path.relative(resolvedBase, resolvedCombined);
|
|
10030
|
+
if (relativePath.startsWith("../") || relativePath.startsWith("..\\") || relativePath === ".." || path.isAbsolute(relativePath)) {
|
|
10031
|
+
throw new PathValidationError("Path escapes base directory", userPath, "path_traversal");
|
|
10032
|
+
}
|
|
10033
|
+
return resolvedCombined;
|
|
10034
|
+
}
|
|
10035
|
+
|
|
9953
10036
|
// src/schemas/registry.ts
|
|
9954
10037
|
var npmSpecifierSchema = exports_external.string().refine((val) => val.startsWith("npm:"), {
|
|
9955
10038
|
message: 'npm specifier must start with "npm:" prefix'
|
|
@@ -9999,11 +10082,11 @@ var componentTypeSchema = exports_external.enum([
|
|
|
9999
10082
|
"ocx:bundle",
|
|
10000
10083
|
"ocx:profile"
|
|
10001
10084
|
]);
|
|
10002
|
-
var
|
|
10003
|
-
var targetPathSchema = exports_external.string().refine((
|
|
10085
|
+
var PROFILE_RESERVED_TARGETS = new Set(["ocx.lock", ".opencode"]);
|
|
10086
|
+
var targetPathSchema = exports_external.string().refine((path2) => path2.startsWith(".opencode/"), {
|
|
10004
10087
|
message: 'Target path must start with ".opencode/"'
|
|
10005
|
-
}).refine((
|
|
10006
|
-
const parts =
|
|
10088
|
+
}).refine((path2) => {
|
|
10089
|
+
const parts = path2.split("/");
|
|
10007
10090
|
const dir = parts[1];
|
|
10008
10091
|
if (!dir)
|
|
10009
10092
|
return false;
|
|
@@ -10132,7 +10215,7 @@ var componentManifestSchema = exports_external.object({
|
|
|
10132
10215
|
opencode: opencodeConfigSchema.optional()
|
|
10133
10216
|
});
|
|
10134
10217
|
function validateSafePath(filePath) {
|
|
10135
|
-
if (
|
|
10218
|
+
if (isAbsolute2(filePath)) {
|
|
10136
10219
|
throw new ValidationError(`Invalid path: "${filePath}" - absolute paths not allowed`);
|
|
10137
10220
|
}
|
|
10138
10221
|
if (filePath.startsWith("~")) {
|
|
@@ -10149,17 +10232,18 @@ function inferTargetPath(sourcePath) {
|
|
|
10149
10232
|
function validateFileTarget(target, componentType) {
|
|
10150
10233
|
const isProfile = componentType === "ocx:profile";
|
|
10151
10234
|
if (isProfile) {
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
if (!isProfileFile && !isOpencodeTarget) {
|
|
10155
|
-
throw new ValidationError(`Invalid profile target: "${target}". ` + `Must be a profile file (ocx.jsonc, opencode.jsonc, AGENTS.md) or start with ".opencode/"`);
|
|
10235
|
+
if (PROFILE_RESERVED_TARGETS.has(target)) {
|
|
10236
|
+
throw new ValidationError(`Target "${target}" is reserved for installer use`);
|
|
10156
10237
|
}
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10238
|
+
try {
|
|
10239
|
+
validatePath("/dummy/base", target);
|
|
10240
|
+
} catch (error) {
|
|
10241
|
+
if (error instanceof PathValidationError) {
|
|
10242
|
+
throw new ValidationError(`Invalid profile target "${target}": ${error.message}`);
|
|
10161
10243
|
}
|
|
10244
|
+
throw error;
|
|
10162
10245
|
}
|
|
10246
|
+
return;
|
|
10163
10247
|
} else {
|
|
10164
10248
|
const parseResult = targetPathSchema.safeParse(target);
|
|
10165
10249
|
if (!parseResult.success) {
|
|
@@ -10283,16 +10367,24 @@ var installedComponentSchema = exports_external.object({
|
|
|
10283
10367
|
installedAt: exports_external.string(),
|
|
10284
10368
|
updatedAt: exports_external.string().optional()
|
|
10285
10369
|
});
|
|
10370
|
+
var installedFromSchema = exports_external.object({
|
|
10371
|
+
registry: exports_external.string(),
|
|
10372
|
+
component: exports_external.string(),
|
|
10373
|
+
version: exports_external.string().optional(),
|
|
10374
|
+
hash: exports_external.string(),
|
|
10375
|
+
installedAt: exports_external.string()
|
|
10376
|
+
});
|
|
10286
10377
|
var ocxLockSchema = exports_external.object({
|
|
10287
10378
|
lockVersion: exports_external.literal(1),
|
|
10379
|
+
installedFrom: installedFromSchema.optional(),
|
|
10288
10380
|
installed: exports_external.record(qualifiedComponentSchema, installedComponentSchema).default({})
|
|
10289
10381
|
});
|
|
10290
10382
|
var CONFIG_FILE = "ocx.jsonc";
|
|
10291
10383
|
var LOCK_FILE = "ocx.lock";
|
|
10292
10384
|
var LOCAL_CONFIG_DIR = ".opencode";
|
|
10293
10385
|
function findOcxConfig(cwd) {
|
|
10294
|
-
const dotOpencodePath =
|
|
10295
|
-
const rootPath =
|
|
10386
|
+
const dotOpencodePath = path2.join(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE);
|
|
10387
|
+
const rootPath = path2.join(cwd, CONFIG_FILE);
|
|
10296
10388
|
const dotOpencodeExists = existsSync(dotOpencodePath);
|
|
10297
10389
|
const rootExists = existsSync(rootPath);
|
|
10298
10390
|
if (dotOpencodeExists && rootExists) {
|
|
@@ -10307,8 +10399,8 @@ function findOcxConfig(cwd) {
|
|
|
10307
10399
|
return { path: dotOpencodePath, exists: false };
|
|
10308
10400
|
}
|
|
10309
10401
|
function findOcxLock(cwd, options) {
|
|
10310
|
-
const dotOpencodePath =
|
|
10311
|
-
const rootPath =
|
|
10402
|
+
const dotOpencodePath = path2.join(cwd, LOCAL_CONFIG_DIR, LOCK_FILE);
|
|
10403
|
+
const rootPath = path2.join(cwd, LOCK_FILE);
|
|
10312
10404
|
if (options?.isFlattened) {
|
|
10313
10405
|
if (existsSync(rootPath)) {
|
|
10314
10406
|
return { path: rootPath, exists: true };
|
|
@@ -10339,8 +10431,8 @@ async function readOcxConfig(cwd) {
|
|
|
10339
10431
|
}
|
|
10340
10432
|
}
|
|
10341
10433
|
async function writeOcxConfig(cwd, config, existingPath) {
|
|
10342
|
-
const configPath = existingPath ??
|
|
10343
|
-
await mkdir(
|
|
10434
|
+
const configPath = existingPath ?? path2.join(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE);
|
|
10435
|
+
await mkdir(path2.dirname(configPath), { recursive: true });
|
|
10344
10436
|
const content = JSON.stringify(config, null, 2);
|
|
10345
10437
|
await Bun.write(configPath, content);
|
|
10346
10438
|
}
|
|
@@ -10355,8 +10447,8 @@ async function readOcxLock(cwd, options) {
|
|
|
10355
10447
|
return ocxLockSchema.parse(json);
|
|
10356
10448
|
}
|
|
10357
10449
|
async function writeOcxLock(cwd, lock, existingPath) {
|
|
10358
|
-
const lockPath = existingPath ??
|
|
10359
|
-
await mkdir(
|
|
10450
|
+
const lockPath = existingPath ?? path2.join(cwd, LOCAL_CONFIG_DIR, LOCK_FILE);
|
|
10451
|
+
await mkdir(path2.dirname(lockPath), { recursive: true });
|
|
10360
10452
|
const content = JSON.stringify(lock, null, 2);
|
|
10361
10453
|
await Bun.write(lockPath, content);
|
|
10362
10454
|
}
|
|
@@ -10364,10 +10456,10 @@ async function writeOcxLock(cwd, lock, existingPath) {
|
|
|
10364
10456
|
// src/utils/paths.ts
|
|
10365
10457
|
import { stat } from "fs/promises";
|
|
10366
10458
|
import { homedir } from "os";
|
|
10367
|
-
import { isAbsolute as
|
|
10459
|
+
import { isAbsolute as isAbsolute3, join } from "path";
|
|
10368
10460
|
function getGlobalConfigPath() {
|
|
10369
10461
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
10370
|
-
const base = xdg &&
|
|
10462
|
+
const base = xdg && isAbsolute3(xdg) ? xdg : join(homedir(), ".config");
|
|
10371
10463
|
return join(base, "opencode");
|
|
10372
10464
|
}
|
|
10373
10465
|
async function globalDirectoryExists() {
|
|
@@ -10436,19 +10528,19 @@ class GlobalConfigProvider {
|
|
|
10436
10528
|
|
|
10437
10529
|
// src/config/resolver.ts
|
|
10438
10530
|
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
10439
|
-
import { join as join2, relative } from "path";
|
|
10531
|
+
import { join as join2, relative as relative2 } from "path";
|
|
10440
10532
|
var {Glob: Glob2 } = globalThis.Bun;
|
|
10441
10533
|
|
|
10442
10534
|
// src/profile/manager.ts
|
|
10443
|
-
import { mkdir as mkdir2, readdir, rm, stat as stat2 } from "fs/promises";
|
|
10535
|
+
import { mkdir as mkdir2, readdir, rename as rename2, rm, stat as stat2 } from "fs/promises";
|
|
10444
10536
|
|
|
10445
10537
|
// src/schemas/ocx.ts
|
|
10446
10538
|
var {Glob } = globalThis.Bun;
|
|
10447
10539
|
|
|
10448
10540
|
// src/utils/path-helpers.ts
|
|
10449
|
-
import
|
|
10541
|
+
import path3 from "path";
|
|
10450
10542
|
function isAbsolutePath(p) {
|
|
10451
|
-
return
|
|
10543
|
+
return path3.posix.isAbsolute(p) || path3.win32.isAbsolute(p);
|
|
10452
10544
|
}
|
|
10453
10545
|
|
|
10454
10546
|
// src/schemas/common.ts
|
|
@@ -10470,7 +10562,6 @@ var profileOcxConfigSchema = exports_external.object({
|
|
|
10470
10562
|
componentPath: safeRelativePathSchema.optional(),
|
|
10471
10563
|
renameWindow: exports_external.boolean().default(true).describe("Set terminal/tmux window name when launching OpenCode"),
|
|
10472
10564
|
exclude: exports_external.array(globPatternSchema).default([
|
|
10473
|
-
"**/AGENTS.md",
|
|
10474
10565
|
"**/CLAUDE.md",
|
|
10475
10566
|
"**/CONTEXT.md",
|
|
10476
10567
|
"**/.opencode/**",
|
|
@@ -10498,38 +10589,38 @@ async function atomicWrite(filePath, data) {
|
|
|
10498
10589
|
// src/profile/paths.ts
|
|
10499
10590
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
10500
10591
|
import { homedir as homedir2 } from "os";
|
|
10501
|
-
import
|
|
10592
|
+
import path4 from "path";
|
|
10502
10593
|
var OCX_CONFIG_FILE = "ocx.jsonc";
|
|
10503
10594
|
var OPENCODE_CONFIG_FILE = "opencode.jsonc";
|
|
10504
10595
|
var LOCAL_CONFIG_DIR2 = ".opencode";
|
|
10505
10596
|
function getProfilesDir() {
|
|
10506
|
-
const base = process.env.XDG_CONFIG_HOME ||
|
|
10507
|
-
return
|
|
10597
|
+
const base = process.env.XDG_CONFIG_HOME || path4.join(homedir2(), ".config");
|
|
10598
|
+
return path4.join(base, "opencode", "profiles");
|
|
10508
10599
|
}
|
|
10509
10600
|
function getProfileDir(name) {
|
|
10510
|
-
return
|
|
10601
|
+
return path4.join(getProfilesDir(), name);
|
|
10511
10602
|
}
|
|
10512
10603
|
function getProfileOcxConfig(name) {
|
|
10513
|
-
return
|
|
10604
|
+
return path4.join(getProfileDir(name), "ocx.jsonc");
|
|
10514
10605
|
}
|
|
10515
10606
|
function getProfileOpencodeConfig(name) {
|
|
10516
|
-
return
|
|
10607
|
+
return path4.join(getProfileDir(name), "opencode.jsonc");
|
|
10517
10608
|
}
|
|
10518
10609
|
function getProfileAgents(name) {
|
|
10519
|
-
return
|
|
10610
|
+
return path4.join(getProfileDir(name), "AGENTS.md");
|
|
10520
10611
|
}
|
|
10521
10612
|
function findLocalConfigDir(cwd) {
|
|
10522
10613
|
let currentDir = cwd;
|
|
10523
10614
|
while (true) {
|
|
10524
|
-
const configDir =
|
|
10615
|
+
const configDir = path4.join(currentDir, LOCAL_CONFIG_DIR2);
|
|
10525
10616
|
if (existsSync2(configDir) && statSync(configDir).isDirectory()) {
|
|
10526
10617
|
return configDir;
|
|
10527
10618
|
}
|
|
10528
|
-
const gitDir =
|
|
10619
|
+
const gitDir = path4.join(currentDir, ".git");
|
|
10529
10620
|
if (existsSync2(gitDir)) {
|
|
10530
10621
|
return null;
|
|
10531
10622
|
}
|
|
10532
|
-
const parentDir =
|
|
10623
|
+
const parentDir = path4.dirname(currentDir);
|
|
10533
10624
|
if (parentDir === currentDir) {
|
|
10534
10625
|
return null;
|
|
10535
10626
|
}
|
|
@@ -10537,8 +10628,8 @@ function findLocalConfigDir(cwd) {
|
|
|
10537
10628
|
}
|
|
10538
10629
|
}
|
|
10539
10630
|
function getGlobalConfig() {
|
|
10540
|
-
const base = process.env.XDG_CONFIG_HOME ||
|
|
10541
|
-
return
|
|
10631
|
+
const base = process.env.XDG_CONFIG_HOME || path4.join(homedir2(), ".config");
|
|
10632
|
+
return path4.join(base, "opencode", "ocx.jsonc");
|
|
10542
10633
|
}
|
|
10543
10634
|
|
|
10544
10635
|
// src/profile/schema.ts
|
|
@@ -10556,7 +10647,6 @@ var DEFAULT_OCX_CONFIG = {
|
|
|
10556
10647
|
registries: {},
|
|
10557
10648
|
renameWindow: true,
|
|
10558
10649
|
exclude: [
|
|
10559
|
-
"**/AGENTS.md",
|
|
10560
10650
|
"**/CLAUDE.md",
|
|
10561
10651
|
"**/CONTEXT.md",
|
|
10562
10652
|
"**/.opencode/**",
|
|
@@ -10565,6 +10655,21 @@ var DEFAULT_OCX_CONFIG = {
|
|
|
10565
10655
|
],
|
|
10566
10656
|
include: []
|
|
10567
10657
|
};
|
|
10658
|
+
var DEFAULT_OCX_CONFIG_TEMPLATE = `{
|
|
10659
|
+
"$schema": "https://ocx.kdco.dev/schemas/ocx.json",
|
|
10660
|
+
"registries": {},
|
|
10661
|
+
"renameWindow": true,
|
|
10662
|
+
"exclude": [
|
|
10663
|
+
// "**/AGENTS.md",
|
|
10664
|
+
"**/CLAUDE.md",
|
|
10665
|
+
"**/CONTEXT.md",
|
|
10666
|
+
"**/.opencode/**",
|
|
10667
|
+
"**/opencode.jsonc",
|
|
10668
|
+
"**/opencode.json"
|
|
10669
|
+
],
|
|
10670
|
+
"include": []
|
|
10671
|
+
}
|
|
10672
|
+
`;
|
|
10568
10673
|
|
|
10569
10674
|
class ProfileManager {
|
|
10570
10675
|
profilesDir;
|
|
@@ -10650,7 +10755,7 @@ class ProfileManager {
|
|
|
10650
10755
|
const ocxPath = getProfileOcxConfig(name);
|
|
10651
10756
|
const ocxFile = Bun.file(ocxPath);
|
|
10652
10757
|
if (!await ocxFile.exists()) {
|
|
10653
|
-
await
|
|
10758
|
+
await Bun.write(ocxPath, DEFAULT_OCX_CONFIG_TEMPLATE, { mode: 384 });
|
|
10654
10759
|
}
|
|
10655
10760
|
const opencodePath = getProfileOpencodeConfig(name);
|
|
10656
10761
|
const opencodeFile = Bun.file(opencodePath);
|
|
@@ -10679,6 +10784,44 @@ class ProfileManager {
|
|
|
10679
10784
|
const dir = getProfileDir(name);
|
|
10680
10785
|
await rm(dir, { recursive: true });
|
|
10681
10786
|
}
|
|
10787
|
+
async move(oldName, newName) {
|
|
10788
|
+
const oldResult = profileNameSchema.safeParse(oldName);
|
|
10789
|
+
if (!oldResult.success) {
|
|
10790
|
+
throw new InvalidProfileNameError(oldName, oldResult.error.errors[0]?.message ?? "Invalid name");
|
|
10791
|
+
}
|
|
10792
|
+
const newResult = profileNameSchema.safeParse(newName);
|
|
10793
|
+
if (!newResult.success) {
|
|
10794
|
+
throw new InvalidProfileNameError(newName, newResult.error.errors[0]?.message ?? "Invalid name");
|
|
10795
|
+
}
|
|
10796
|
+
await this.ensureInitialized();
|
|
10797
|
+
if (!await this.exists(oldName)) {
|
|
10798
|
+
throw new ProfileNotFoundError(oldName);
|
|
10799
|
+
}
|
|
10800
|
+
if (oldName === newName) {
|
|
10801
|
+
return { warnActiveProfile: false };
|
|
10802
|
+
}
|
|
10803
|
+
if (await this.exists(newName)) {
|
|
10804
|
+
throw new ConflictError(`Cannot move: profile "${newName}" already exists. Remove it first with 'ocx p rm ${newName}'.`);
|
|
10805
|
+
}
|
|
10806
|
+
const warnActiveProfile = process.env.OCX_PROFILE === oldName;
|
|
10807
|
+
const oldDir = getProfileDir(oldName);
|
|
10808
|
+
const newDir = getProfileDir(newName);
|
|
10809
|
+
try {
|
|
10810
|
+
await rename2(oldDir, newDir);
|
|
10811
|
+
} catch (error) {
|
|
10812
|
+
if (error instanceof Error && "code" in error) {
|
|
10813
|
+
const code = error.code;
|
|
10814
|
+
if (code === "EEXIST" || code === "ENOTEMPTY") {
|
|
10815
|
+
throw new ConflictError(`Cannot move: profile "${newName}" already exists. Remove it first with 'ocx p rm ${newName}'.`);
|
|
10816
|
+
}
|
|
10817
|
+
if (code === "ENOENT") {
|
|
10818
|
+
throw new ProfileNotFoundError(oldName);
|
|
10819
|
+
}
|
|
10820
|
+
}
|
|
10821
|
+
throw error;
|
|
10822
|
+
}
|
|
10823
|
+
return { warnActiveProfile };
|
|
10824
|
+
}
|
|
10682
10825
|
async resolveProfile(override) {
|
|
10683
10826
|
if (override) {
|
|
10684
10827
|
if (!await this.exists(override)) {
|
|
@@ -10728,7 +10871,7 @@ function discoverInstructionFiles(projectDir, gitRoot) {
|
|
|
10728
10871
|
for (const filename of INSTRUCTION_FILES) {
|
|
10729
10872
|
const filePath = join2(currentDir, filename);
|
|
10730
10873
|
if (existsSync3(filePath) && statSync2(filePath).isFile()) {
|
|
10731
|
-
const relativePath =
|
|
10874
|
+
const relativePath = relative2(root, filePath);
|
|
10732
10875
|
discovered.push(relativePath);
|
|
10733
10876
|
}
|
|
10734
10877
|
}
|
|
@@ -10889,7 +11032,7 @@ class ConfigResolver {
|
|
|
10889
11032
|
return true;
|
|
10890
11033
|
const gitRoot = detectGitRoot(this.cwd);
|
|
10891
11034
|
const root = gitRoot ?? this.cwd;
|
|
10892
|
-
const relativePath =
|
|
11035
|
+
const relativePath = relative2(root, this.localConfigDir);
|
|
10893
11036
|
const exclude = this.profile.ocx.exclude ?? [];
|
|
10894
11037
|
const include = this.profile.ocx.include ?? [];
|
|
10895
11038
|
for (const pattern of include) {
|
|
@@ -10982,7 +11125,7 @@ class ConfigResolver {
|
|
|
10982
11125
|
// package.json
|
|
10983
11126
|
var package_default = {
|
|
10984
11127
|
name: "ocx",
|
|
10985
|
-
version: "1.4.
|
|
11128
|
+
version: "1.4.4",
|
|
10986
11129
|
description: "OCX CLI - ShadCN-style registry for OpenCode extensions. Install agents, plugins, skills, and MCP servers.",
|
|
10987
11130
|
author: "kdcokenny",
|
|
10988
11131
|
license: "MIT",
|
|
@@ -11061,14 +11204,28 @@ async function fetchWithCache(url, parse3) {
|
|
|
11061
11204
|
return cached;
|
|
11062
11205
|
}
|
|
11063
11206
|
const promise = (async () => {
|
|
11064
|
-
|
|
11207
|
+
let response;
|
|
11208
|
+
try {
|
|
11209
|
+
response = await fetch(url);
|
|
11210
|
+
} catch (error) {
|
|
11211
|
+
throw new NetworkError(`Network request failed for ${url}: ${error instanceof Error ? error.message : String(error)}`, { url });
|
|
11212
|
+
}
|
|
11065
11213
|
if (!response.ok) {
|
|
11066
11214
|
if (response.status === 404) {
|
|
11067
11215
|
throw new NotFoundError(`Not found: ${url}`);
|
|
11068
11216
|
}
|
|
11069
|
-
throw new NetworkError(`Failed to fetch ${url}: ${response.status} ${response.statusText}
|
|
11217
|
+
throw new NetworkError(`Failed to fetch ${url}: ${response.status} ${response.statusText}`, {
|
|
11218
|
+
url,
|
|
11219
|
+
status: response.status,
|
|
11220
|
+
statusText: response.statusText
|
|
11221
|
+
});
|
|
11222
|
+
}
|
|
11223
|
+
let data;
|
|
11224
|
+
try {
|
|
11225
|
+
data = await response.json();
|
|
11226
|
+
} catch (error) {
|
|
11227
|
+
throw new NetworkError(`Invalid JSON response from ${url}: ${error instanceof Error ? error.message : String(error)}`, { url });
|
|
11070
11228
|
}
|
|
11071
|
-
const data = await response.json();
|
|
11072
11229
|
return parse3(data);
|
|
11073
11230
|
})();
|
|
11074
11231
|
cache.set(url, promise);
|
|
@@ -11115,9 +11272,14 @@ async function fetchComponentVersion(baseUrl, name, version) {
|
|
|
11115
11272
|
}
|
|
11116
11273
|
async function fetchFileContent(baseUrl, componentName, filePath) {
|
|
11117
11274
|
const url = `${baseUrl.replace(/\/$/, "")}/components/${componentName}/${filePath}`;
|
|
11118
|
-
|
|
11275
|
+
let response;
|
|
11276
|
+
try {
|
|
11277
|
+
response = await fetch(url);
|
|
11278
|
+
} catch (error) {
|
|
11279
|
+
throw new NetworkError(`Network request failed for ${url}: ${error instanceof Error ? error.message : String(error)}`, { url });
|
|
11280
|
+
}
|
|
11119
11281
|
if (!response.ok) {
|
|
11120
|
-
throw new NetworkError(`Failed to fetch file ${filePath} for ${componentName}: ${response.status} ${response.statusText}
|
|
11282
|
+
throw new NetworkError(`Failed to fetch file ${filePath} for ${componentName} from ${url}: ${response.status} ${response.statusText}`, { url, status: response.status, statusText: response.statusText });
|
|
11121
11283
|
}
|
|
11122
11284
|
return response.text();
|
|
11123
11285
|
}
|
|
@@ -11192,13 +11354,13 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11192
11354
|
const npmDeps = new Set;
|
|
11193
11355
|
const npmDevDeps = new Set;
|
|
11194
11356
|
let opencode = {};
|
|
11195
|
-
async function
|
|
11357
|
+
async function resolve2(componentNamespace, componentName, path5 = []) {
|
|
11196
11358
|
const qualifiedName = createQualifiedComponent(componentNamespace, componentName);
|
|
11197
11359
|
if (resolved.has(qualifiedName)) {
|
|
11198
11360
|
return;
|
|
11199
11361
|
}
|
|
11200
11362
|
if (visiting.has(qualifiedName)) {
|
|
11201
|
-
const cycle = [...
|
|
11363
|
+
const cycle = [...path5, qualifiedName].join(" \u2192 ");
|
|
11202
11364
|
throw new ValidationError(`Circular dependency detected: ${cycle}`);
|
|
11203
11365
|
}
|
|
11204
11366
|
visiting.add(qualifiedName);
|
|
@@ -11209,12 +11371,21 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11209
11371
|
let component;
|
|
11210
11372
|
try {
|
|
11211
11373
|
component = await fetchComponent(regConfig.url, componentName);
|
|
11212
|
-
} catch (
|
|
11213
|
-
|
|
11374
|
+
} catch (err) {
|
|
11375
|
+
if (err instanceof NetworkError) {
|
|
11376
|
+
throw err;
|
|
11377
|
+
}
|
|
11378
|
+
if (err instanceof NotFoundError) {
|
|
11379
|
+
throw new NotFoundError(`Component '${componentName}' not found in registry '${componentNamespace}'.`);
|
|
11380
|
+
}
|
|
11381
|
+
if (err instanceof OCXError) {
|
|
11382
|
+
throw err;
|
|
11383
|
+
}
|
|
11384
|
+
throw new NetworkError(`Failed to fetch component '${componentName}' from registry '${componentNamespace}': ${err instanceof Error ? err.message : String(err)}`, { url: regConfig.url });
|
|
11214
11385
|
}
|
|
11215
11386
|
for (const dep of component.dependencies) {
|
|
11216
11387
|
const depRef = parseComponentRef(dep, componentNamespace);
|
|
11217
|
-
await
|
|
11388
|
+
await resolve2(depRef.namespace, depRef.component, [...path5, qualifiedName]);
|
|
11218
11389
|
}
|
|
11219
11390
|
const normalizedComponent = normalizeComponentManifest(component);
|
|
11220
11391
|
resolved.set(qualifiedName, {
|
|
@@ -11241,7 +11412,7 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11241
11412
|
}
|
|
11242
11413
|
for (const name of componentNames) {
|
|
11243
11414
|
const ref = parseComponentRef(name);
|
|
11244
|
-
await
|
|
11415
|
+
await resolve2(ref.namespace, ref.component);
|
|
11245
11416
|
}
|
|
11246
11417
|
const components = Array.from(resolved.values());
|
|
11247
11418
|
const installOrder = Array.from(resolved.keys());
|
|
@@ -11258,17 +11429,17 @@ async function resolveDependencies(registries, componentNames) {
|
|
|
11258
11429
|
import { existsSync as existsSync4 } from "fs";
|
|
11259
11430
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
11260
11431
|
import { homedir as homedir3 } from "os";
|
|
11261
|
-
import
|
|
11432
|
+
import path5 from "path";
|
|
11262
11433
|
var LOCAL_CONFIG_DIR3 = ".opencode";
|
|
11263
11434
|
function isGlobalConfigPath(cwd) {
|
|
11264
|
-
const base = process.env.XDG_CONFIG_HOME ||
|
|
11265
|
-
const globalConfigDir =
|
|
11266
|
-
const resolvedCwd =
|
|
11435
|
+
const base = process.env.XDG_CONFIG_HOME || path5.join(homedir3(), ".config");
|
|
11436
|
+
const globalConfigDir = path5.resolve(base, "opencode");
|
|
11437
|
+
const resolvedCwd = path5.resolve(cwd);
|
|
11267
11438
|
if (resolvedCwd === globalConfigDir) {
|
|
11268
11439
|
return true;
|
|
11269
11440
|
}
|
|
11270
|
-
const
|
|
11271
|
-
return
|
|
11441
|
+
const relative3 = path5.relative(globalConfigDir, resolvedCwd);
|
|
11442
|
+
return relative3 !== "" && !relative3.startsWith("..") && !path5.isAbsolute(relative3);
|
|
11272
11443
|
}
|
|
11273
11444
|
var JSONC_OPTIONS = {
|
|
11274
11445
|
formattingOptions: {
|
|
@@ -11285,8 +11456,8 @@ var OPENCODE_CONFIG_TEMPLATE = `{
|
|
|
11285
11456
|
`;
|
|
11286
11457
|
function findOpencodeConfig(cwd) {
|
|
11287
11458
|
if (isGlobalConfigPath(cwd)) {
|
|
11288
|
-
const rootJsonc2 =
|
|
11289
|
-
const rootJson2 =
|
|
11459
|
+
const rootJsonc2 = path5.join(cwd, "opencode.jsonc");
|
|
11460
|
+
const rootJson2 = path5.join(cwd, "opencode.json");
|
|
11290
11461
|
if (existsSync4(rootJsonc2)) {
|
|
11291
11462
|
return { path: rootJsonc2, exists: true };
|
|
11292
11463
|
}
|
|
@@ -11295,16 +11466,16 @@ function findOpencodeConfig(cwd) {
|
|
|
11295
11466
|
}
|
|
11296
11467
|
return { path: rootJsonc2, exists: false };
|
|
11297
11468
|
}
|
|
11298
|
-
const dotOpencodeJsonc =
|
|
11299
|
-
const dotOpencodeJson =
|
|
11469
|
+
const dotOpencodeJsonc = path5.join(cwd, LOCAL_CONFIG_DIR3, "opencode.jsonc");
|
|
11470
|
+
const dotOpencodeJson = path5.join(cwd, LOCAL_CONFIG_DIR3, "opencode.json");
|
|
11300
11471
|
if (existsSync4(dotOpencodeJsonc)) {
|
|
11301
11472
|
return { path: dotOpencodeJsonc, exists: true };
|
|
11302
11473
|
}
|
|
11303
11474
|
if (existsSync4(dotOpencodeJson)) {
|
|
11304
11475
|
return { path: dotOpencodeJson, exists: true };
|
|
11305
11476
|
}
|
|
11306
|
-
const rootJsonc =
|
|
11307
|
-
const rootJson =
|
|
11477
|
+
const rootJsonc = path5.join(cwd, "opencode.jsonc");
|
|
11478
|
+
const rootJson = path5.join(cwd, "opencode.json");
|
|
11308
11479
|
if (existsSync4(rootJsonc)) {
|
|
11309
11480
|
return { path: rootJsonc, exists: true };
|
|
11310
11481
|
}
|
|
@@ -11318,7 +11489,7 @@ async function ensureOpencodeConfig(cwd) {
|
|
|
11318
11489
|
if (exists) {
|
|
11319
11490
|
return { path: configPath, created: false };
|
|
11320
11491
|
}
|
|
11321
|
-
await mkdir3(
|
|
11492
|
+
await mkdir3(path5.dirname(configPath), { recursive: true });
|
|
11322
11493
|
await Bun.write(configPath, OPENCODE_CONFIG_TEMPLATE);
|
|
11323
11494
|
return { path: configPath, created: true };
|
|
11324
11495
|
}
|
|
@@ -11335,13 +11506,13 @@ async function readOpencodeJsonConfig(cwd) {
|
|
|
11335
11506
|
path: configPath
|
|
11336
11507
|
};
|
|
11337
11508
|
}
|
|
11338
|
-
async function writeOpencodeJsonConfig(
|
|
11339
|
-
await Bun.write(
|
|
11509
|
+
async function writeOpencodeJsonConfig(path6, content) {
|
|
11510
|
+
await Bun.write(path6, content);
|
|
11340
11511
|
}
|
|
11341
|
-
function getValueAtPath(content,
|
|
11512
|
+
function getValueAtPath(content, path6) {
|
|
11342
11513
|
const parsed = parse2(content, [], { allowTrailingComma: true });
|
|
11343
11514
|
let current = parsed;
|
|
11344
|
-
for (const segment of
|
|
11515
|
+
for (const segment of path6) {
|
|
11345
11516
|
if (current === null || current === undefined)
|
|
11346
11517
|
return;
|
|
11347
11518
|
if (typeof current !== "object")
|
|
@@ -11350,27 +11521,27 @@ function getValueAtPath(content, path5) {
|
|
|
11350
11521
|
}
|
|
11351
11522
|
return current;
|
|
11352
11523
|
}
|
|
11353
|
-
function applyValueAtPath(content,
|
|
11524
|
+
function applyValueAtPath(content, path6, value) {
|
|
11354
11525
|
if (value === null || value === undefined) {
|
|
11355
11526
|
return content;
|
|
11356
11527
|
}
|
|
11357
11528
|
if (typeof value === "object" && !Array.isArray(value)) {
|
|
11358
|
-
const existingValue = getValueAtPath(content,
|
|
11529
|
+
const existingValue = getValueAtPath(content, path6);
|
|
11359
11530
|
if (existingValue !== undefined && (existingValue === null || typeof existingValue !== "object")) {
|
|
11360
|
-
const edits2 = modify(content,
|
|
11531
|
+
const edits2 = modify(content, path6, value, JSONC_OPTIONS);
|
|
11361
11532
|
return applyEdits(content, edits2);
|
|
11362
11533
|
}
|
|
11363
11534
|
let updatedContent = content;
|
|
11364
11535
|
for (const [key, val] of Object.entries(value)) {
|
|
11365
|
-
updatedContent = applyValueAtPath(updatedContent, [...
|
|
11536
|
+
updatedContent = applyValueAtPath(updatedContent, [...path6, key], val);
|
|
11366
11537
|
}
|
|
11367
11538
|
return updatedContent;
|
|
11368
11539
|
}
|
|
11369
11540
|
if (Array.isArray(value)) {
|
|
11370
|
-
const edits2 = modify(content,
|
|
11541
|
+
const edits2 = modify(content, path6, value, JSONC_OPTIONS);
|
|
11371
11542
|
return applyEdits(content, edits2);
|
|
11372
11543
|
}
|
|
11373
|
-
const edits = modify(content,
|
|
11544
|
+
const edits = modify(content, path6, value, JSONC_OPTIONS);
|
|
11374
11545
|
return applyEdits(content, edits);
|
|
11375
11546
|
}
|
|
11376
11547
|
async function updateOpencodeJsonConfig(cwd, opencode) {
|
|
@@ -11384,8 +11555,8 @@ async function updateOpencodeJsonConfig(cwd, opencode) {
|
|
|
11384
11555
|
} else {
|
|
11385
11556
|
const config = { $schema: "https://opencode.ai/config.json" };
|
|
11386
11557
|
content = JSON.stringify(config, null, "\t");
|
|
11387
|
-
configPath = isGlobalConfigPath(cwd) ?
|
|
11388
|
-
await mkdir3(
|
|
11558
|
+
configPath = isGlobalConfigPath(cwd) ? path5.join(cwd, "opencode.jsonc") : path5.join(cwd, LOCAL_CONFIG_DIR3, "opencode.jsonc");
|
|
11559
|
+
await mkdir3(path5.dirname(configPath), { recursive: true });
|
|
11389
11560
|
created = true;
|
|
11390
11561
|
}
|
|
11391
11562
|
const originalContent = content;
|
|
@@ -11427,7 +11598,7 @@ function parseEnvBool(value, defaultValue) {
|
|
|
11427
11598
|
return defaultValue;
|
|
11428
11599
|
}
|
|
11429
11600
|
// src/utils/git-context.ts
|
|
11430
|
-
import { basename, resolve } from "path";
|
|
11601
|
+
import { basename as basename2, resolve as resolve2 } from "path";
|
|
11431
11602
|
function getGitEnv() {
|
|
11432
11603
|
const { GIT_DIR: _, GIT_WORK_TREE: __, ...cleanEnv } = process.env;
|
|
11433
11604
|
return cleanEnv;
|
|
@@ -11493,12 +11664,100 @@ async function getRepoName(cwd) {
|
|
|
11493
11664
|
if (!rootPath) {
|
|
11494
11665
|
return null;
|
|
11495
11666
|
}
|
|
11496
|
-
return
|
|
11667
|
+
return basename2(rootPath);
|
|
11497
11668
|
}
|
|
11498
11669
|
async function getGitInfo(cwd) {
|
|
11499
11670
|
const [repoName, branch] = await Promise.all([getRepoName(cwd), getBranch(cwd)]);
|
|
11500
11671
|
return { repoName, branch };
|
|
11501
11672
|
}
|
|
11673
|
+
// src/lib/build-registry.ts
|
|
11674
|
+
import { mkdir as mkdir4 } from "fs/promises";
|
|
11675
|
+
import { dirname, join as join3 } from "path";
|
|
11676
|
+
class BuildRegistryError extends Error {
|
|
11677
|
+
errors;
|
|
11678
|
+
constructor(message, errors2 = []) {
|
|
11679
|
+
super(message);
|
|
11680
|
+
this.errors = errors2;
|
|
11681
|
+
this.name = "BuildRegistryError";
|
|
11682
|
+
}
|
|
11683
|
+
}
|
|
11684
|
+
async function buildRegistry(options) {
|
|
11685
|
+
const { source: sourcePath, out: outPath } = options;
|
|
11686
|
+
const jsoncFile = Bun.file(join3(sourcePath, "registry.jsonc"));
|
|
11687
|
+
const jsonFile = Bun.file(join3(sourcePath, "registry.json"));
|
|
11688
|
+
const jsoncExists = await jsoncFile.exists();
|
|
11689
|
+
const jsonExists = await jsonFile.exists();
|
|
11690
|
+
if (!jsoncExists && !jsonExists) {
|
|
11691
|
+
throw new BuildRegistryError("No registry.jsonc or registry.json found in source directory");
|
|
11692
|
+
}
|
|
11693
|
+
const registryFile = jsoncExists ? jsoncFile : jsonFile;
|
|
11694
|
+
const content = await registryFile.text();
|
|
11695
|
+
const registryData = parse2(content, [], { allowTrailingComma: true });
|
|
11696
|
+
const parseResult = registrySchema.safeParse(registryData);
|
|
11697
|
+
if (!parseResult.success) {
|
|
11698
|
+
const errors2 = parseResult.error.errors.map((e3) => `${e3.path.join(".")}: ${e3.message}`);
|
|
11699
|
+
throw new BuildRegistryError("Registry validation failed", errors2);
|
|
11700
|
+
}
|
|
11701
|
+
const registry = parseResult.data;
|
|
11702
|
+
const validationErrors = [];
|
|
11703
|
+
const componentsDir = join3(outPath, "components");
|
|
11704
|
+
await mkdir4(componentsDir, { recursive: true });
|
|
11705
|
+
for (const component of registry.components) {
|
|
11706
|
+
const packument = {
|
|
11707
|
+
name: component.name,
|
|
11708
|
+
versions: {
|
|
11709
|
+
[registry.version]: component
|
|
11710
|
+
},
|
|
11711
|
+
"dist-tags": {
|
|
11712
|
+
latest: registry.version
|
|
11713
|
+
}
|
|
11714
|
+
};
|
|
11715
|
+
const packumentPath = join3(componentsDir, `${component.name}.json`);
|
|
11716
|
+
await Bun.write(packumentPath, JSON.stringify(packument, null, 2));
|
|
11717
|
+
for (const rawFile of component.files) {
|
|
11718
|
+
const file = normalizeFile(rawFile, component.type);
|
|
11719
|
+
const sourceFilePath = join3(sourcePath, "files", file.path);
|
|
11720
|
+
const destFilePath = join3(componentsDir, component.name, file.path);
|
|
11721
|
+
const destFileDir = dirname(destFilePath);
|
|
11722
|
+
if (!await Bun.file(sourceFilePath).exists()) {
|
|
11723
|
+
validationErrors.push(`${component.name}: Source file not found at ${sourceFilePath}`);
|
|
11724
|
+
continue;
|
|
11725
|
+
}
|
|
11726
|
+
await mkdir4(destFileDir, { recursive: true });
|
|
11727
|
+
const sourceFile = Bun.file(sourceFilePath);
|
|
11728
|
+
await Bun.write(destFilePath, sourceFile);
|
|
11729
|
+
}
|
|
11730
|
+
}
|
|
11731
|
+
if (validationErrors.length > 0) {
|
|
11732
|
+
throw new BuildRegistryError(`Build failed with ${validationErrors.length} errors`, validationErrors);
|
|
11733
|
+
}
|
|
11734
|
+
const index = {
|
|
11735
|
+
name: registry.name,
|
|
11736
|
+
namespace: registry.namespace,
|
|
11737
|
+
version: registry.version,
|
|
11738
|
+
author: registry.author,
|
|
11739
|
+
...registry.opencode && { opencode: registry.opencode },
|
|
11740
|
+
...registry.ocx && { ocx: registry.ocx },
|
|
11741
|
+
components: registry.components.map((c) => ({
|
|
11742
|
+
name: c.name,
|
|
11743
|
+
type: c.type,
|
|
11744
|
+
description: c.description
|
|
11745
|
+
}))
|
|
11746
|
+
};
|
|
11747
|
+
await Bun.write(join3(outPath, "index.json"), JSON.stringify(index, null, 2));
|
|
11748
|
+
const wellKnownDir = join3(outPath, ".well-known");
|
|
11749
|
+
await mkdir4(wellKnownDir, { recursive: true });
|
|
11750
|
+
const discovery = { registry: "/index.json" };
|
|
11751
|
+
await Bun.write(join3(wellKnownDir, "ocx.json"), JSON.stringify(discovery, null, 2));
|
|
11752
|
+
return {
|
|
11753
|
+
name: registry.name,
|
|
11754
|
+
namespace: registry.namespace,
|
|
11755
|
+
version: registry.version,
|
|
11756
|
+
componentsCount: registry.components.length,
|
|
11757
|
+
outputPath: outPath
|
|
11758
|
+
};
|
|
11759
|
+
}
|
|
11760
|
+
|
|
11502
11761
|
// ../../node_modules/.bun/kleur@4.1.5/node_modules/kleur/index.mjs
|
|
11503
11762
|
var FORCE_COLOR;
|
|
11504
11763
|
var NODE_DISABLE_COLORS;
|
|
@@ -11636,6 +11895,15 @@ var logger = {
|
|
|
11636
11895
|
console.log("");
|
|
11637
11896
|
}
|
|
11638
11897
|
};
|
|
11898
|
+
var highlight = {
|
|
11899
|
+
component: (text) => kleur_default.cyan(text),
|
|
11900
|
+
path: (text) => kleur_default.green(text),
|
|
11901
|
+
command: (text) => kleur_default.yellow(text),
|
|
11902
|
+
url: (text) => kleur_default.blue().underline(text),
|
|
11903
|
+
error: (text) => kleur_default.red(text),
|
|
11904
|
+
dim: (text) => kleur_default.gray(text),
|
|
11905
|
+
bold: (text) => kleur_default.bold(text)
|
|
11906
|
+
};
|
|
11639
11907
|
|
|
11640
11908
|
// src/utils/handle-error.ts
|
|
11641
11909
|
function handleError(error, options2 = {}) {
|
|
@@ -11651,8 +11919,8 @@ function handleError(error, options2 = {}) {
|
|
|
11651
11919
|
if (error instanceof ZodError) {
|
|
11652
11920
|
logger.error("Validation failed:");
|
|
11653
11921
|
for (const issue of error.issues) {
|
|
11654
|
-
const
|
|
11655
|
-
logger.error(` ${
|
|
11922
|
+
const path6 = issue.path.join(".");
|
|
11923
|
+
logger.error(` ${path6}: ${issue.message}`);
|
|
11656
11924
|
}
|
|
11657
11925
|
process.exit(EXIT_CODES.CONFIG);
|
|
11658
11926
|
}
|
|
@@ -11695,6 +11963,110 @@ function formatErrorAsJson(error) {
|
|
|
11695
11963
|
}
|
|
11696
11964
|
};
|
|
11697
11965
|
}
|
|
11966
|
+
if (error instanceof IntegrityError) {
|
|
11967
|
+
return {
|
|
11968
|
+
success: false,
|
|
11969
|
+
error: {
|
|
11970
|
+
code: error.code,
|
|
11971
|
+
message: error.message,
|
|
11972
|
+
details: {
|
|
11973
|
+
component: error.component,
|
|
11974
|
+
expected: error.expected,
|
|
11975
|
+
found: error.found
|
|
11976
|
+
}
|
|
11977
|
+
},
|
|
11978
|
+
exitCode: error.exitCode,
|
|
11979
|
+
meta: {
|
|
11980
|
+
timestamp: new Date().toISOString()
|
|
11981
|
+
}
|
|
11982
|
+
};
|
|
11983
|
+
}
|
|
11984
|
+
if (error instanceof NetworkError) {
|
|
11985
|
+
const details = {};
|
|
11986
|
+
if (error.url)
|
|
11987
|
+
details.url = error.url;
|
|
11988
|
+
if (error.status !== undefined)
|
|
11989
|
+
details.status = error.status;
|
|
11990
|
+
if (error.statusText)
|
|
11991
|
+
details.statusText = error.statusText;
|
|
11992
|
+
return {
|
|
11993
|
+
success: false,
|
|
11994
|
+
error: {
|
|
11995
|
+
code: error.code,
|
|
11996
|
+
message: error.message,
|
|
11997
|
+
...Object.keys(details).length > 0 && { details }
|
|
11998
|
+
},
|
|
11999
|
+
exitCode: error.exitCode,
|
|
12000
|
+
meta: {
|
|
12001
|
+
timestamp: new Date().toISOString()
|
|
12002
|
+
}
|
|
12003
|
+
};
|
|
12004
|
+
}
|
|
12005
|
+
if (error instanceof ProfileNotFoundError) {
|
|
12006
|
+
return {
|
|
12007
|
+
success: false,
|
|
12008
|
+
error: {
|
|
12009
|
+
code: error.code,
|
|
12010
|
+
message: error.message,
|
|
12011
|
+
details: {
|
|
12012
|
+
profile: error.profile
|
|
12013
|
+
}
|
|
12014
|
+
},
|
|
12015
|
+
exitCode: error.exitCode,
|
|
12016
|
+
meta: {
|
|
12017
|
+
timestamp: new Date().toISOString()
|
|
12018
|
+
}
|
|
12019
|
+
};
|
|
12020
|
+
}
|
|
12021
|
+
if (error instanceof ProfileExistsError) {
|
|
12022
|
+
return {
|
|
12023
|
+
success: false,
|
|
12024
|
+
error: {
|
|
12025
|
+
code: error.code,
|
|
12026
|
+
message: error.message,
|
|
12027
|
+
details: {
|
|
12028
|
+
profile: error.profile
|
|
12029
|
+
}
|
|
12030
|
+
},
|
|
12031
|
+
exitCode: error.exitCode,
|
|
12032
|
+
meta: {
|
|
12033
|
+
timestamp: new Date().toISOString()
|
|
12034
|
+
}
|
|
12035
|
+
};
|
|
12036
|
+
}
|
|
12037
|
+
if (error instanceof InvalidProfileNameError) {
|
|
12038
|
+
return {
|
|
12039
|
+
success: false,
|
|
12040
|
+
error: {
|
|
12041
|
+
code: error.code,
|
|
12042
|
+
message: error.message,
|
|
12043
|
+
details: {
|
|
12044
|
+
profile: error.profile,
|
|
12045
|
+
reason: error.reason
|
|
12046
|
+
}
|
|
12047
|
+
},
|
|
12048
|
+
exitCode: error.exitCode,
|
|
12049
|
+
meta: {
|
|
12050
|
+
timestamp: new Date().toISOString()
|
|
12051
|
+
}
|
|
12052
|
+
};
|
|
12053
|
+
}
|
|
12054
|
+
if (error instanceof BuildRegistryError) {
|
|
12055
|
+
return {
|
|
12056
|
+
success: false,
|
|
12057
|
+
error: {
|
|
12058
|
+
code: "BUILD_ERROR",
|
|
12059
|
+
message: error.message,
|
|
12060
|
+
details: {
|
|
12061
|
+
errors: error.errors
|
|
12062
|
+
}
|
|
12063
|
+
},
|
|
12064
|
+
exitCode: EXIT_CODES.GENERAL,
|
|
12065
|
+
meta: {
|
|
12066
|
+
timestamp: new Date().toISOString()
|
|
12067
|
+
}
|
|
12068
|
+
};
|
|
12069
|
+
}
|
|
11698
12070
|
if (error instanceof OCXError) {
|
|
11699
12071
|
return {
|
|
11700
12072
|
success: false,
|
|
@@ -11713,7 +12085,14 @@ function formatErrorAsJson(error) {
|
|
|
11713
12085
|
success: false,
|
|
11714
12086
|
error: {
|
|
11715
12087
|
code: "VALIDATION_ERROR",
|
|
11716
|
-
message: error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
|
|
12088
|
+
message: error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "),
|
|
12089
|
+
details: {
|
|
12090
|
+
issues: error.issues.map((i) => ({
|
|
12091
|
+
path: i.path.join("."),
|
|
12092
|
+
message: i.message,
|
|
12093
|
+
code: i.code
|
|
12094
|
+
}))
|
|
12095
|
+
}
|
|
11717
12096
|
},
|
|
11718
12097
|
exitCode: EXIT_CODES.CONFIG,
|
|
11719
12098
|
meta: {
|
|
@@ -11738,15 +12117,15 @@ function outputJson(data) {
|
|
|
11738
12117
|
console.log(JSON.stringify(data, null, 2));
|
|
11739
12118
|
}
|
|
11740
12119
|
// src/utils/path-safety.ts
|
|
11741
|
-
import
|
|
12120
|
+
import path6 from "path";
|
|
11742
12121
|
function isPathInside(childPath, parentPath) {
|
|
11743
|
-
const resolvedChild =
|
|
11744
|
-
const resolvedParent =
|
|
12122
|
+
const resolvedChild = path6.resolve(childPath);
|
|
12123
|
+
const resolvedParent = path6.resolve(parentPath);
|
|
11745
12124
|
if (resolvedChild === resolvedParent) {
|
|
11746
12125
|
return true;
|
|
11747
12126
|
}
|
|
11748
|
-
const
|
|
11749
|
-
return !!
|
|
12127
|
+
const relative3 = path6.relative(resolvedParent, resolvedChild);
|
|
12128
|
+
return !!relative3 && !relative3.startsWith("..") && !isAbsolutePath(relative3);
|
|
11750
12129
|
}
|
|
11751
12130
|
function assertPathInside(childPath, parentPath) {
|
|
11752
12131
|
if (!isPathInside(childPath, parentPath)) {
|
|
@@ -13507,7 +13886,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13507
13886
|
}
|
|
13508
13887
|
const computedHash = await hashBundle(files);
|
|
13509
13888
|
for (const file of component.files) {
|
|
13510
|
-
const targetPath =
|
|
13889
|
+
const targetPath = join4(cwd, resolveTargetPath(file.target, isFlattened));
|
|
13511
13890
|
assertPathInside(targetPath, cwd);
|
|
13512
13891
|
}
|
|
13513
13892
|
const existingEntry = lock.installed[component.qualifiedName];
|
|
@@ -13517,7 +13896,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13517
13896
|
}
|
|
13518
13897
|
for (const file of component.files) {
|
|
13519
13898
|
const resolvedTarget = resolveTargetPath(file.target, isFlattened);
|
|
13520
|
-
const targetPath =
|
|
13899
|
+
const targetPath = join4(cwd, resolvedTarget);
|
|
13521
13900
|
if (existsSync5(targetPath)) {
|
|
13522
13901
|
const conflictingComponent = findComponentByFile(lock, resolvedTarget);
|
|
13523
13902
|
if (conflictingComponent && conflictingComponent !== component.qualifiedName) {
|
|
@@ -13541,7 +13920,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13541
13920
|
if (!componentFile)
|
|
13542
13921
|
continue;
|
|
13543
13922
|
const resolvedTarget = resolveTargetPath(componentFile.target, isFlattened);
|
|
13544
|
-
const targetPath =
|
|
13923
|
+
const targetPath = join4(cwd, resolvedTarget);
|
|
13545
13924
|
if (existsSync5(targetPath)) {
|
|
13546
13925
|
const existingContent = await Bun.file(targetPath).text();
|
|
13547
13926
|
const incomingContent = file.content.toString("utf-8");
|
|
@@ -13606,7 +13985,7 @@ async function runRegistryAddCore(componentNames, options2, provider) {
|
|
|
13606
13985
|
}
|
|
13607
13986
|
const hasNpmDeps = resolved.npmDependencies.length > 0;
|
|
13608
13987
|
const hasNpmDevDeps = resolved.npmDevDependencies.length > 0;
|
|
13609
|
-
const packageJsonPath = options2.global || options2.profile ?
|
|
13988
|
+
const packageJsonPath = options2.global || options2.profile ? join4(cwd, "package.json") : join4(cwd, ".opencode/package.json");
|
|
13610
13989
|
if (hasNpmDeps || hasNpmDevDeps) {
|
|
13611
13990
|
const npmSpin = options2.quiet ? null : createSpinner({ text: `Updating ${packageJsonPath}...` });
|
|
13612
13991
|
npmSpin?.start();
|
|
@@ -13648,8 +14027,8 @@ async function installComponent(component, files, cwd, options2) {
|
|
|
13648
14027
|
if (!componentFile)
|
|
13649
14028
|
continue;
|
|
13650
14029
|
const resolvedTarget = resolveTargetPath(componentFile.target, !!options2.isFlattened);
|
|
13651
|
-
const targetPath =
|
|
13652
|
-
const targetDir =
|
|
14030
|
+
const targetPath = join4(cwd, resolvedTarget);
|
|
14031
|
+
const targetDir = dirname2(targetPath);
|
|
13653
14032
|
if (existsSync5(targetPath)) {
|
|
13654
14033
|
const existingContent = await Bun.file(targetPath).text();
|
|
13655
14034
|
const incomingContent = file.content.toString("utf-8");
|
|
@@ -13662,7 +14041,7 @@ async function installComponent(component, files, cwd, options2) {
|
|
|
13662
14041
|
result.written.push(resolvedTarget);
|
|
13663
14042
|
}
|
|
13664
14043
|
if (!existsSync5(targetDir)) {
|
|
13665
|
-
await
|
|
14044
|
+
await mkdir5(targetDir, { recursive: true });
|
|
13666
14045
|
}
|
|
13667
14046
|
await writeFile(targetPath, file.content);
|
|
13668
14047
|
}
|
|
@@ -13738,7 +14117,7 @@ function mergeDevDependencies(existing, newDeps) {
|
|
|
13738
14117
|
return { ...existing, devDependencies: merged };
|
|
13739
14118
|
}
|
|
13740
14119
|
async function readOpencodePackageJson(opencodeDir) {
|
|
13741
|
-
const pkgPath =
|
|
14120
|
+
const pkgPath = join4(opencodeDir, "package.json");
|
|
13742
14121
|
if (!existsSync5(pkgPath)) {
|
|
13743
14122
|
return { ...DEFAULT_PACKAGE_JSON };
|
|
13744
14123
|
}
|
|
@@ -13751,7 +14130,7 @@ async function readOpencodePackageJson(opencodeDir) {
|
|
|
13751
14130
|
}
|
|
13752
14131
|
}
|
|
13753
14132
|
async function ensureManifestFilesAreTracked(opencodeDir) {
|
|
13754
|
-
const gitignorePath =
|
|
14133
|
+
const gitignorePath = join4(opencodeDir, ".gitignore");
|
|
13755
14134
|
const filesToTrack = new Set(["package.json", "bun.lock"]);
|
|
13756
14135
|
const requiredIgnores = ["node_modules"];
|
|
13757
14136
|
let lines = [];
|
|
@@ -13774,12 +14153,12 @@ async function updateOpencodeDevDependencies(cwd, npmDeps, npmDevDeps, options2
|
|
|
13774
14153
|
const allDepSpecs = [...npmDeps, ...npmDevDeps];
|
|
13775
14154
|
if (allDepSpecs.length === 0)
|
|
13776
14155
|
return;
|
|
13777
|
-
const packageDir = options2.isFlattened ? cwd :
|
|
13778
|
-
await
|
|
14156
|
+
const packageDir = options2.isFlattened ? cwd : join4(cwd, ".opencode");
|
|
14157
|
+
await mkdir5(packageDir, { recursive: true });
|
|
13779
14158
|
const parsedDeps = allDepSpecs.map(parseNpmDependency);
|
|
13780
14159
|
const existing = await readOpencodePackageJson(packageDir);
|
|
13781
14160
|
const updated = mergeDevDependencies(existing, parsedDeps);
|
|
13782
|
-
await Bun.write(
|
|
14161
|
+
await Bun.write(join4(packageDir, "package.json"), `${JSON.stringify(updated, null, 2)}
|
|
13783
14162
|
`);
|
|
13784
14163
|
if (!options2.isFlattened) {
|
|
13785
14164
|
await ensureManifestFilesAreTracked(packageDir);
|
|
@@ -13795,101 +14174,11 @@ function findComponentByFile(lock, filePath) {
|
|
|
13795
14174
|
}
|
|
13796
14175
|
|
|
13797
14176
|
// src/commands/build.ts
|
|
13798
|
-
import { join as join5, relative as
|
|
13799
|
-
|
|
13800
|
-
// src/lib/build-registry.ts
|
|
13801
|
-
import { mkdir as mkdir5 } from "fs/promises";
|
|
13802
|
-
import { dirname as dirname2, join as join4 } from "path";
|
|
13803
|
-
class BuildRegistryError extends Error {
|
|
13804
|
-
errors;
|
|
13805
|
-
constructor(message, errors3 = []) {
|
|
13806
|
-
super(message);
|
|
13807
|
-
this.errors = errors3;
|
|
13808
|
-
this.name = "BuildRegistryError";
|
|
13809
|
-
}
|
|
13810
|
-
}
|
|
13811
|
-
async function buildRegistry(options2) {
|
|
13812
|
-
const { source: sourcePath, out: outPath } = options2;
|
|
13813
|
-
const jsoncFile = Bun.file(join4(sourcePath, "registry.jsonc"));
|
|
13814
|
-
const jsonFile = Bun.file(join4(sourcePath, "registry.json"));
|
|
13815
|
-
const jsoncExists = await jsoncFile.exists();
|
|
13816
|
-
const jsonExists = await jsonFile.exists();
|
|
13817
|
-
if (!jsoncExists && !jsonExists) {
|
|
13818
|
-
throw new BuildRegistryError("No registry.jsonc or registry.json found in source directory");
|
|
13819
|
-
}
|
|
13820
|
-
const registryFile = jsoncExists ? jsoncFile : jsonFile;
|
|
13821
|
-
const content2 = await registryFile.text();
|
|
13822
|
-
const registryData = parse2(content2, [], { allowTrailingComma: true });
|
|
13823
|
-
const parseResult = registrySchema.safeParse(registryData);
|
|
13824
|
-
if (!parseResult.success) {
|
|
13825
|
-
const errors3 = parseResult.error.errors.map((e3) => `${e3.path.join(".")}: ${e3.message}`);
|
|
13826
|
-
throw new BuildRegistryError("Registry validation failed", errors3);
|
|
13827
|
-
}
|
|
13828
|
-
const registry = parseResult.data;
|
|
13829
|
-
const validationErrors = [];
|
|
13830
|
-
const componentsDir = join4(outPath, "components");
|
|
13831
|
-
await mkdir5(componentsDir, { recursive: true });
|
|
13832
|
-
for (const component of registry.components) {
|
|
13833
|
-
const packument = {
|
|
13834
|
-
name: component.name,
|
|
13835
|
-
versions: {
|
|
13836
|
-
[registry.version]: component
|
|
13837
|
-
},
|
|
13838
|
-
"dist-tags": {
|
|
13839
|
-
latest: registry.version
|
|
13840
|
-
}
|
|
13841
|
-
};
|
|
13842
|
-
const packumentPath = join4(componentsDir, `${component.name}.json`);
|
|
13843
|
-
await Bun.write(packumentPath, JSON.stringify(packument, null, 2));
|
|
13844
|
-
for (const rawFile of component.files) {
|
|
13845
|
-
const file = normalizeFile(rawFile, component.type);
|
|
13846
|
-
const sourceFilePath = join4(sourcePath, "files", file.path);
|
|
13847
|
-
const destFilePath = join4(componentsDir, component.name, file.path);
|
|
13848
|
-
const destFileDir = dirname2(destFilePath);
|
|
13849
|
-
if (!await Bun.file(sourceFilePath).exists()) {
|
|
13850
|
-
validationErrors.push(`${component.name}: Source file not found at ${sourceFilePath}`);
|
|
13851
|
-
continue;
|
|
13852
|
-
}
|
|
13853
|
-
await mkdir5(destFileDir, { recursive: true });
|
|
13854
|
-
const sourceFile = Bun.file(sourceFilePath);
|
|
13855
|
-
await Bun.write(destFilePath, sourceFile);
|
|
13856
|
-
}
|
|
13857
|
-
}
|
|
13858
|
-
if (validationErrors.length > 0) {
|
|
13859
|
-
throw new BuildRegistryError(`Build failed with ${validationErrors.length} errors`, validationErrors);
|
|
13860
|
-
}
|
|
13861
|
-
const index = {
|
|
13862
|
-
name: registry.name,
|
|
13863
|
-
namespace: registry.namespace,
|
|
13864
|
-
version: registry.version,
|
|
13865
|
-
author: registry.author,
|
|
13866
|
-
...registry.opencode && { opencode: registry.opencode },
|
|
13867
|
-
...registry.ocx && { ocx: registry.ocx },
|
|
13868
|
-
components: registry.components.map((c) => ({
|
|
13869
|
-
name: c.name,
|
|
13870
|
-
type: c.type,
|
|
13871
|
-
description: c.description
|
|
13872
|
-
}))
|
|
13873
|
-
};
|
|
13874
|
-
await Bun.write(join4(outPath, "index.json"), JSON.stringify(index, null, 2));
|
|
13875
|
-
const wellKnownDir = join4(outPath, ".well-known");
|
|
13876
|
-
await mkdir5(wellKnownDir, { recursive: true });
|
|
13877
|
-
const discovery = { registry: "/index.json" };
|
|
13878
|
-
await Bun.write(join4(wellKnownDir, "ocx.json"), JSON.stringify(discovery, null, 2));
|
|
13879
|
-
return {
|
|
13880
|
-
name: registry.name,
|
|
13881
|
-
namespace: registry.namespace,
|
|
13882
|
-
version: registry.version,
|
|
13883
|
-
componentsCount: registry.components.length,
|
|
13884
|
-
outputPath: outPath
|
|
13885
|
-
};
|
|
13886
|
-
}
|
|
13887
|
-
|
|
13888
|
-
// src/commands/build.ts
|
|
14177
|
+
import { join as join5, relative as relative3 } from "path";
|
|
13889
14178
|
function registerBuildCommand(program2) {
|
|
13890
|
-
program2.command("build").description("Build a registry from source (for registry authors)").argument("[path]", "Registry source directory", ".").option("--out <dir>", "Output directory", "./dist").option("--cwd <path>", "Working directory", process.cwd()).option("--json", "Output as JSON", false).option("-q, --quiet", "Suppress output", false).action(async (
|
|
14179
|
+
program2.command("build").description("Build a registry from source (for registry authors)").argument("[path]", "Registry source directory", ".").option("--out <dir>", "Output directory", "./dist").option("--cwd <path>", "Working directory", process.cwd()).option("--json", "Output as JSON", false).option("-q, --quiet", "Suppress output", false).action(async (path7, options2) => {
|
|
13891
14180
|
try {
|
|
13892
|
-
const sourcePath = join5(options2.cwd,
|
|
14181
|
+
const sourcePath = join5(options2.cwd, path7);
|
|
13893
14182
|
const outPath = join5(options2.cwd, options2.out);
|
|
13894
14183
|
const spinner2 = createSpinner({
|
|
13895
14184
|
text: "Building registry...",
|
|
@@ -13902,7 +14191,7 @@ function registerBuildCommand(program2) {
|
|
|
13902
14191
|
out: outPath
|
|
13903
14192
|
});
|
|
13904
14193
|
if (!options2.json) {
|
|
13905
|
-
const msg = `Built ${result.componentsCount} components to ${
|
|
14194
|
+
const msg = `Built ${result.componentsCount} components to ${relative3(options2.cwd, outPath)}`;
|
|
13906
14195
|
spinner2.succeed(msg);
|
|
13907
14196
|
if (!process.stdout.isTTY) {
|
|
13908
14197
|
logger.success(`Built ${result.componentsCount} components`);
|
|
@@ -14204,16 +14493,16 @@ class Diff {
|
|
|
14204
14493
|
}
|
|
14205
14494
|
}
|
|
14206
14495
|
}
|
|
14207
|
-
addToPath(
|
|
14208
|
-
const last =
|
|
14496
|
+
addToPath(path7, added, removed, oldPosInc, options2) {
|
|
14497
|
+
const last = path7.lastComponent;
|
|
14209
14498
|
if (last && !options2.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
14210
14499
|
return {
|
|
14211
|
-
oldPos:
|
|
14500
|
+
oldPos: path7.oldPos + oldPosInc,
|
|
14212
14501
|
lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
|
|
14213
14502
|
};
|
|
14214
14503
|
} else {
|
|
14215
14504
|
return {
|
|
14216
|
-
oldPos:
|
|
14505
|
+
oldPos: path7.oldPos + oldPosInc,
|
|
14217
14506
|
lastComponent: { count: 1, added, removed, previousComponent: last }
|
|
14218
14507
|
};
|
|
14219
14508
|
}
|
|
@@ -15030,7 +15319,7 @@ import {
|
|
|
15030
15319
|
rmSync,
|
|
15031
15320
|
unlinkSync
|
|
15032
15321
|
} from "fs";
|
|
15033
|
-
import
|
|
15322
|
+
import path7 from "path";
|
|
15034
15323
|
var GHOST_CONFIG_FILE = "ghost.jsonc";
|
|
15035
15324
|
var BACKUP_EXT = ".bak";
|
|
15036
15325
|
var CURRENT_SYMLINK = "current";
|
|
@@ -15068,8 +15357,8 @@ function planMigration() {
|
|
|
15068
15357
|
if (!entry.isDirectory() || entry.name === CURRENT_SYMLINK)
|
|
15069
15358
|
continue;
|
|
15070
15359
|
const profileName = entry.name;
|
|
15071
|
-
const ghostConfig =
|
|
15072
|
-
const ocxConfig =
|
|
15360
|
+
const ghostConfig = path7.join(profilesDir, profileName, GHOST_CONFIG_FILE);
|
|
15361
|
+
const ocxConfig = path7.join(profilesDir, profileName, OCX_CONFIG_FILE);
|
|
15073
15362
|
if (!existsSync7(ghostConfig))
|
|
15074
15363
|
continue;
|
|
15075
15364
|
if (existsSync7(ocxConfig)) {
|
|
@@ -15099,13 +15388,13 @@ function planMigration() {
|
|
|
15099
15388
|
if (!entry.isDirectory() || entry.name === CURRENT_SYMLINK)
|
|
15100
15389
|
continue;
|
|
15101
15390
|
const profileName = entry.name;
|
|
15102
|
-
const profileDir =
|
|
15103
|
-
const dotOpencode =
|
|
15391
|
+
const profileDir = path7.join(profilesDir, profileName);
|
|
15392
|
+
const dotOpencode = path7.join(profileDir, ".opencode");
|
|
15104
15393
|
if (!existsSync7(dotOpencode))
|
|
15105
15394
|
continue;
|
|
15106
15395
|
for (const dir of FLATTEN_DIRS) {
|
|
15107
|
-
const source =
|
|
15108
|
-
const destination =
|
|
15396
|
+
const source = path7.join(dotOpencode, dir);
|
|
15397
|
+
const destination = path7.join(profileDir, dir);
|
|
15109
15398
|
if (!existsSync7(source))
|
|
15110
15399
|
continue;
|
|
15111
15400
|
try {
|
|
@@ -15144,7 +15433,7 @@ function planMigration() {
|
|
|
15144
15433
|
});
|
|
15145
15434
|
}
|
|
15146
15435
|
}
|
|
15147
|
-
const currentPath =
|
|
15436
|
+
const currentPath = path7.join(profilesDir, CURRENT_SYMLINK);
|
|
15148
15437
|
if (existsSync7(currentPath)) {
|
|
15149
15438
|
try {
|
|
15150
15439
|
const stat3 = lstatSync(currentPath);
|
|
@@ -15203,21 +15492,21 @@ function executeMigration(plan) {
|
|
|
15203
15492
|
} catch {}
|
|
15204
15493
|
}
|
|
15205
15494
|
} catch (error) {
|
|
15206
|
-
for (const
|
|
15495
|
+
for (const rename3 of completedRenames) {
|
|
15207
15496
|
try {
|
|
15208
|
-
if (existsSync7(
|
|
15209
|
-
if (
|
|
15210
|
-
rmSync(
|
|
15497
|
+
if (existsSync7(rename3.destination)) {
|
|
15498
|
+
if (rename3.isDir) {
|
|
15499
|
+
rmSync(rename3.destination, { recursive: true, force: true });
|
|
15211
15500
|
} else {
|
|
15212
|
-
unlinkSync(
|
|
15501
|
+
unlinkSync(rename3.destination);
|
|
15213
15502
|
}
|
|
15214
15503
|
}
|
|
15215
|
-
mkdirSync(
|
|
15216
|
-
if (existsSync7(
|
|
15217
|
-
if (
|
|
15218
|
-
cpSync(
|
|
15504
|
+
mkdirSync(path7.dirname(rename3.source), { recursive: true });
|
|
15505
|
+
if (existsSync7(rename3.backup)) {
|
|
15506
|
+
if (rename3.isDir) {
|
|
15507
|
+
cpSync(rename3.backup, rename3.source, { recursive: true });
|
|
15219
15508
|
} else {
|
|
15220
|
-
copyFileSync(
|
|
15509
|
+
copyFileSync(rename3.backup, rename3.source);
|
|
15221
15510
|
}
|
|
15222
15511
|
}
|
|
15223
15512
|
} catch {}
|
|
@@ -15238,8 +15527,8 @@ function executeMigration(plan) {
|
|
|
15238
15527
|
try {
|
|
15239
15528
|
const processedProfiles = new Set(plan.profiles.filter((a) => a.type === "move-dir").map((a) => a.profileName));
|
|
15240
15529
|
for (const profileName of processedProfiles) {
|
|
15241
|
-
const profileDir =
|
|
15242
|
-
const dotOpencode =
|
|
15530
|
+
const profileDir = path7.join(getProfilesDir(), profileName);
|
|
15531
|
+
const dotOpencode = path7.join(profileDir, ".opencode");
|
|
15243
15532
|
if (existsSync7(dotOpencode)) {
|
|
15244
15533
|
const remaining = readdirSync(dotOpencode);
|
|
15245
15534
|
if (remaining.length === 0) {
|
|
@@ -15265,7 +15554,7 @@ function printPlan(plan, dryRun) {
|
|
|
15265
15554
|
if (action.type === "rename") {
|
|
15266
15555
|
console.log(` \u2713 ${action.profileName}: ${GHOST_CONFIG_FILE} \u2192 ${OCX_CONFIG_FILE}`);
|
|
15267
15556
|
} else {
|
|
15268
|
-
const dirName =
|
|
15557
|
+
const dirName = path7.basename(action.source);
|
|
15269
15558
|
console.log(` \u2713 ${action.profileName}: .opencode/${dirName}/ \u2192 ${dirName}/`);
|
|
15270
15559
|
}
|
|
15271
15560
|
}
|
|
@@ -15525,7 +15814,7 @@ async function copyDir(src, dest) {
|
|
|
15525
15814
|
}
|
|
15526
15815
|
function getReleaseTag() {
|
|
15527
15816
|
if (false) {}
|
|
15528
|
-
return `v${"1.4.
|
|
15817
|
+
return `v${"1.4.4"}`;
|
|
15529
15818
|
}
|
|
15530
15819
|
function getTemplateUrl(version) {
|
|
15531
15820
|
const ref = version === "main" ? "heads/main" : `tags/${version}`;
|
|
@@ -15566,6 +15855,9 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
|
|
|
15566
15855
|
await rm2(tempDir, { recursive: true, force: true });
|
|
15567
15856
|
}
|
|
15568
15857
|
}
|
|
15858
|
+
function toTitleCase(str) {
|
|
15859
|
+
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
15860
|
+
}
|
|
15569
15861
|
async function replacePlaceholders(dir, values) {
|
|
15570
15862
|
const filesToProcess = [
|
|
15571
15863
|
"registry.jsonc",
|
|
@@ -15580,16 +15872,17 @@ async function replacePlaceholders(dir, values) {
|
|
|
15580
15872
|
continue;
|
|
15581
15873
|
let content2 = await readFile(filePath).then((b) => b.toString());
|
|
15582
15874
|
content2 = content2.replace(/my-registry/g, values.namespace);
|
|
15875
|
+
content2 = content2.replace(/My Registry/g, toTitleCase(values.namespace));
|
|
15583
15876
|
content2 = content2.replace(/Your Name/g, values.author);
|
|
15584
15877
|
await writeFile2(filePath, content2);
|
|
15585
15878
|
}
|
|
15586
15879
|
}
|
|
15587
15880
|
|
|
15588
15881
|
// src/commands/opencode.ts
|
|
15589
|
-
import { resolve as
|
|
15882
|
+
import { resolve as resolve3 } from "path";
|
|
15590
15883
|
|
|
15591
15884
|
// src/utils/terminal-title.ts
|
|
15592
|
-
import
|
|
15885
|
+
import path8 from "path";
|
|
15593
15886
|
var MAX_BRANCH_LENGTH = 20;
|
|
15594
15887
|
var titleSaved = false;
|
|
15595
15888
|
function isInsideTmux() {
|
|
@@ -15632,7 +15925,7 @@ function restoreTerminalTitle() {
|
|
|
15632
15925
|
titleSaved = false;
|
|
15633
15926
|
}
|
|
15634
15927
|
function formatTerminalName(cwd, profileName, gitInfo) {
|
|
15635
|
-
const repoName = gitInfo.repoName ??
|
|
15928
|
+
const repoName = gitInfo.repoName ?? path8.basename(cwd);
|
|
15636
15929
|
if (!gitInfo.branch) {
|
|
15637
15930
|
return `ocx[${profileName}]:${repoName}`;
|
|
15638
15931
|
}
|
|
@@ -15654,16 +15947,16 @@ function buildOpenCodeEnv(opts) {
|
|
|
15654
15947
|
};
|
|
15655
15948
|
}
|
|
15656
15949
|
function registerOpencodeCommand(program2) {
|
|
15657
|
-
program2.command("opencode [path]").alias("oc").description("Launch OpenCode with resolved configuration").option("-p, --profile <name>", "Use specific profile").option("--no-rename", "Disable terminal/tmux window renaming").addOption(sharedOptions.quiet()).addOption(sharedOptions.json()).allowUnknownOption().allowExcessArguments(true).action(async (
|
|
15950
|
+
program2.command("opencode [path]").alias("oc").description("Launch OpenCode with resolved configuration").option("-p, --profile <name>", "Use specific profile").option("--no-rename", "Disable terminal/tmux window renaming").addOption(sharedOptions.quiet()).addOption(sharedOptions.json()).allowUnknownOption().allowExcessArguments(true).action(async (path9, options2, command) => {
|
|
15658
15951
|
try {
|
|
15659
|
-
await runOpencode(
|
|
15952
|
+
await runOpencode(path9, command.args, options2);
|
|
15660
15953
|
} catch (error) {
|
|
15661
15954
|
handleError(error, { json: options2.json });
|
|
15662
15955
|
}
|
|
15663
15956
|
});
|
|
15664
15957
|
}
|
|
15665
15958
|
async function runOpencode(pathArg, args, options2) {
|
|
15666
|
-
const projectDir = pathArg ?
|
|
15959
|
+
const projectDir = pathArg ? resolve3(pathArg) : process.cwd();
|
|
15667
15960
|
const resolver = await ConfigResolver.create(projectDir, { profile: options2.profile });
|
|
15668
15961
|
const config = resolver.resolve();
|
|
15669
15962
|
const profile = resolver.getProfile();
|
|
@@ -15748,12 +16041,8 @@ async function runOpencode(pathArg, args, options2) {
|
|
|
15748
16041
|
// src/commands/profile/install-from-registry.ts
|
|
15749
16042
|
import { createHash as createHash2 } from "crypto";
|
|
15750
16043
|
import { existsSync as existsSync9 } from "fs";
|
|
15751
|
-
import { mkdir as mkdir8, mkdtemp, rename as
|
|
16044
|
+
import { mkdir as mkdir8, mkdtemp, rename as rename3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
15752
16045
|
import { dirname as dirname4, join as join8 } from "path";
|
|
15753
|
-
var PROFILE_FILE_TARGETS = new Set(["ocx.jsonc", "opencode.jsonc", "AGENTS.md"]);
|
|
15754
|
-
function isProfileFile(target) {
|
|
15755
|
-
return PROFILE_FILE_TARGETS.has(target);
|
|
15756
|
-
}
|
|
15757
16046
|
function hashContent2(content2) {
|
|
15758
16047
|
return createHash2("sha256").update(content2).digest("hex");
|
|
15759
16048
|
}
|
|
@@ -15768,7 +16057,7 @@ function hashBundle2(files) {
|
|
|
15768
16057
|
`));
|
|
15769
16058
|
}
|
|
15770
16059
|
async function installProfileFromRegistry(options2) {
|
|
15771
|
-
const { namespace, component, profileName,
|
|
16060
|
+
const { namespace, component, profileName, registryUrl, registries, quiet } = options2;
|
|
15772
16061
|
const parseResult = profileNameSchema.safeParse(profileName);
|
|
15773
16062
|
if (!parseResult.success) {
|
|
15774
16063
|
throw new ValidationError(`Invalid profile name: "${profileName}". ` + `Profile names must start with a letter and contain only alphanumeric characters, dots, underscores, or hyphens.`);
|
|
@@ -15776,9 +16065,8 @@ async function installProfileFromRegistry(options2) {
|
|
|
15776
16065
|
const profileDir = getProfileDir(profileName);
|
|
15777
16066
|
const qualifiedName = `${namespace}/${component}`;
|
|
15778
16067
|
const profileExists = existsSync9(profileDir);
|
|
15779
|
-
if (profileExists
|
|
15780
|
-
throw new ConflictError(`Profile "${profileName}" already exists.
|
|
15781
|
-
Use --force to overwrite.`);
|
|
16068
|
+
if (profileExists) {
|
|
16069
|
+
throw new ConflictError(`Profile "${profileName}" already exists. Remove it first with 'ocx profile rm ${profileName}'.`);
|
|
15782
16070
|
}
|
|
15783
16071
|
const fetchSpin = quiet ? null : createSpinner({ text: `Fetching ${qualifiedName}...` });
|
|
15784
16072
|
fetchSpin?.start();
|
|
@@ -15805,7 +16093,7 @@ Use --force to overwrite.`);
|
|
|
15805
16093
|
const filesSpin = quiet ? null : createSpinner({ text: "Downloading profile files..." });
|
|
15806
16094
|
filesSpin?.start();
|
|
15807
16095
|
const profileFiles = [];
|
|
15808
|
-
const
|
|
16096
|
+
const embeddedFiles = [];
|
|
15809
16097
|
for (const file of normalized.files) {
|
|
15810
16098
|
const content2 = await fetchFileContent(registryUrl, component, file.path);
|
|
15811
16099
|
const fileEntry = {
|
|
@@ -15813,53 +16101,17 @@ Use --force to overwrite.`);
|
|
|
15813
16101
|
target: file.target,
|
|
15814
16102
|
content: Buffer.from(content2)
|
|
15815
16103
|
};
|
|
15816
|
-
if (
|
|
15817
|
-
|
|
16104
|
+
if (file.target.startsWith(".opencode/")) {
|
|
16105
|
+
embeddedFiles.push(fileEntry);
|
|
15818
16106
|
} else {
|
|
15819
|
-
|
|
16107
|
+
profileFiles.push(fileEntry);
|
|
15820
16108
|
}
|
|
15821
16109
|
}
|
|
15822
16110
|
filesSpin?.succeed(`Downloaded ${normalized.files.length} files`);
|
|
15823
|
-
let resolvedDeps = null;
|
|
15824
|
-
const dependencyBundles = [];
|
|
15825
|
-
if (manifest.dependencies.length > 0) {
|
|
15826
|
-
const depsSpin = quiet ? null : createSpinner({ text: "Resolving dependencies..." });
|
|
15827
|
-
depsSpin?.start();
|
|
15828
|
-
try {
|
|
15829
|
-
const depRefs = manifest.dependencies.map((dep) => dep.includes("/") ? dep : `${namespace}/${dep}`);
|
|
15830
|
-
resolvedDeps = await resolveDependencies(registries, depRefs);
|
|
15831
|
-
for (const depComponent of resolvedDeps.components) {
|
|
15832
|
-
const files = [];
|
|
15833
|
-
for (const file of depComponent.files) {
|
|
15834
|
-
const content2 = await fetchFileContent(depComponent.baseUrl, depComponent.name, file.path);
|
|
15835
|
-
const resolvedTarget = resolveTargetPath(file.target, true);
|
|
15836
|
-
files.push({
|
|
15837
|
-
path: file.path,
|
|
15838
|
-
target: resolvedTarget,
|
|
15839
|
-
content: Buffer.from(content2)
|
|
15840
|
-
});
|
|
15841
|
-
}
|
|
15842
|
-
const registryIndex = await fetchRegistryIndex(depComponent.baseUrl);
|
|
15843
|
-
dependencyBundles.push({
|
|
15844
|
-
qualifiedName: depComponent.qualifiedName,
|
|
15845
|
-
registryName: depComponent.registryName,
|
|
15846
|
-
files,
|
|
15847
|
-
hash: hashBundle2(files),
|
|
15848
|
-
version: registryIndex.version
|
|
15849
|
-
});
|
|
15850
|
-
}
|
|
15851
|
-
depsSpin?.succeed(`Resolved ${resolvedDeps.components.length} dependencies`);
|
|
15852
|
-
} catch (error) {
|
|
15853
|
-
depsSpin?.fail("Failed to resolve dependencies");
|
|
15854
|
-
throw error;
|
|
15855
|
-
}
|
|
15856
|
-
}
|
|
15857
16111
|
const profilesDir = getProfilesDir();
|
|
15858
16112
|
await mkdir8(profilesDir, { recursive: true, mode: 448 });
|
|
15859
16113
|
const stagingDir = await mkdtemp(join8(profilesDir, ".staging-"));
|
|
15860
|
-
const stagingOpencodeDir = join8(stagingDir, ".opencode");
|
|
15861
16114
|
try {
|
|
15862
|
-
await mkdir8(stagingOpencodeDir, { recursive: true, mode: 448 });
|
|
15863
16115
|
const writeSpin = quiet ? null : createSpinner({ text: "Writing profile files..." });
|
|
15864
16116
|
writeSpin?.start();
|
|
15865
16117
|
for (const file of profileFiles) {
|
|
@@ -15870,37 +16122,20 @@ Use --force to overwrite.`);
|
|
|
15870
16122
|
}
|
|
15871
16123
|
await writeFile3(targetPath, file.content);
|
|
15872
16124
|
}
|
|
15873
|
-
for (const file of
|
|
16125
|
+
for (const file of embeddedFiles) {
|
|
15874
16126
|
const target = file.target.startsWith(".opencode/") ? file.target.slice(".opencode/".length) : file.target;
|
|
15875
|
-
const targetPath = join8(
|
|
16127
|
+
const targetPath = join8(stagingDir, target);
|
|
15876
16128
|
const targetDir = dirname4(targetPath);
|
|
15877
16129
|
if (!existsSync9(targetDir)) {
|
|
15878
16130
|
await mkdir8(targetDir, { recursive: true });
|
|
15879
16131
|
}
|
|
15880
16132
|
await writeFile3(targetPath, file.content);
|
|
15881
16133
|
}
|
|
15882
|
-
writeSpin?.succeed(`Wrote ${profileFiles.length +
|
|
15883
|
-
if (dependencyBundles.length > 0) {
|
|
15884
|
-
const depWriteSpin = quiet ? null : createSpinner({ text: "Writing dependency files..." });
|
|
15885
|
-
depWriteSpin?.start();
|
|
15886
|
-
let depFileCount = 0;
|
|
15887
|
-
for (const bundle of dependencyBundles) {
|
|
15888
|
-
for (const file of bundle.files) {
|
|
15889
|
-
const targetPath = join8(stagingOpencodeDir, file.target);
|
|
15890
|
-
const targetDir = dirname4(targetPath);
|
|
15891
|
-
if (!existsSync9(targetDir)) {
|
|
15892
|
-
await mkdir8(targetDir, { recursive: true });
|
|
15893
|
-
}
|
|
15894
|
-
await writeFile3(targetPath, file.content);
|
|
15895
|
-
depFileCount++;
|
|
15896
|
-
}
|
|
15897
|
-
}
|
|
15898
|
-
depWriteSpin?.succeed(`Wrote ${depFileCount} dependency files`);
|
|
15899
|
-
}
|
|
16134
|
+
writeSpin?.succeed(`Wrote ${profileFiles.length + embeddedFiles.length} profile files`);
|
|
15900
16135
|
const profileHash = hashBundle2(profileFiles.map((f) => ({ path: f.path, content: f.content })));
|
|
15901
16136
|
const registryIndex = await fetchRegistryIndex(registryUrl);
|
|
15902
16137
|
const lock = {
|
|
15903
|
-
|
|
16138
|
+
lockVersion: 1,
|
|
15904
16139
|
installedFrom: {
|
|
15905
16140
|
registry: namespace,
|
|
15906
16141
|
component,
|
|
@@ -15910,36 +16145,32 @@ Use --force to overwrite.`);
|
|
|
15910
16145
|
},
|
|
15911
16146
|
installed: {}
|
|
15912
16147
|
};
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
version: bundle.version,
|
|
15917
|
-
hash: bundle.hash,
|
|
15918
|
-
files: bundle.files.map((f) => f.target),
|
|
15919
|
-
installedAt: new Date().toISOString()
|
|
15920
|
-
};
|
|
15921
|
-
}
|
|
15922
|
-
await writeFile3(join8(stagingDir, "ocx.lock"), JSON.stringify(lock, null, "\t"));
|
|
15923
|
-
const moveSpin = quiet ? null : createSpinner({ text: "Finalizing installation..." });
|
|
15924
|
-
moveSpin?.start();
|
|
16148
|
+
await writeOcxLock(stagingDir, lock, join8(stagingDir, "ocx.lock"));
|
|
16149
|
+
const renameSpin = quiet ? null : createSpinner({ text: "Moving to profile directory..." });
|
|
16150
|
+
renameSpin?.start();
|
|
15925
16151
|
const profilesDir2 = dirname4(profileDir);
|
|
15926
16152
|
if (!existsSync9(profilesDir2)) {
|
|
15927
16153
|
await mkdir8(profilesDir2, { recursive: true, mode: 448 });
|
|
15928
16154
|
}
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
16155
|
+
await rename3(stagingDir, profileDir);
|
|
16156
|
+
renameSpin?.succeed("Profile installed");
|
|
16157
|
+
if (manifest.dependencies.length > 0) {
|
|
16158
|
+
const depsSpin = quiet ? null : createSpinner({ text: "Installing dependencies..." });
|
|
16159
|
+
depsSpin?.start();
|
|
15932
16160
|
try {
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
16161
|
+
const depRefs = manifest.dependencies.map((dep) => dep.includes("/") ? dep : `${namespace}/${dep}`);
|
|
16162
|
+
const provider = {
|
|
16163
|
+
cwd: profileDir,
|
|
16164
|
+
getRegistries: () => registries,
|
|
16165
|
+
getComponentPath: () => ""
|
|
16166
|
+
};
|
|
16167
|
+
await runAddCore(depRefs, { profile: profileName }, provider);
|
|
16168
|
+
depsSpin?.succeed(`Installed ${manifest.dependencies.length} dependencies`);
|
|
16169
|
+
} catch (error) {
|
|
16170
|
+
depsSpin?.fail("Failed to install dependencies");
|
|
16171
|
+
throw error;
|
|
15937
16172
|
}
|
|
15938
|
-
await rm3(backupDir, { recursive: true, force: true });
|
|
15939
|
-
} else {
|
|
15940
|
-
await rename2(stagingDir, profileDir);
|
|
15941
16173
|
}
|
|
15942
|
-
moveSpin?.succeed("Installation complete");
|
|
15943
16174
|
if (!quiet) {
|
|
15944
16175
|
logger.info("");
|
|
15945
16176
|
logger.success(`Installed profile "${profileName}" from ${qualifiedName}`);
|
|
@@ -15948,12 +16179,9 @@ Use --force to overwrite.`);
|
|
|
15948
16179
|
for (const file of profileFiles) {
|
|
15949
16180
|
logger.info(` ${file.target}`);
|
|
15950
16181
|
}
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
logger.info(
|
|
15954
|
-
for (const bundle of dependencyBundles) {
|
|
15955
|
-
logger.info(` ${bundle.qualifiedName}`);
|
|
15956
|
-
}
|
|
16182
|
+
for (const file of embeddedFiles) {
|
|
16183
|
+
const target = file.target.startsWith(".opencode/") ? file.target.slice(".opencode/".length) : file.target;
|
|
16184
|
+
logger.info(` ${target}`);
|
|
15957
16185
|
}
|
|
15958
16186
|
}
|
|
15959
16187
|
} catch (error) {
|
|
@@ -15972,9 +16200,6 @@ function parseFromOption(from) {
|
|
|
15972
16200
|
throw new ValidationError("--from value cannot be empty");
|
|
15973
16201
|
}
|
|
15974
16202
|
const trimmed = from.trim();
|
|
15975
|
-
if (trimmed.startsWith("./") || trimmed.startsWith("~/") || trimmed.startsWith("/")) {
|
|
15976
|
-
return { type: "local-path", path: trimmed };
|
|
15977
|
-
}
|
|
15978
16203
|
const slashCount = (trimmed.match(/\//g) || []).length;
|
|
15979
16204
|
if (slashCount === 1) {
|
|
15980
16205
|
const [namespace, component] = trimmed.split("/").map((s) => s.trim());
|
|
@@ -16022,12 +16247,11 @@ async function requireGlobalRegistry(namespace) {
|
|
|
16022
16247
|
return { config: globalConfig, registryUrl: registry.url };
|
|
16023
16248
|
}
|
|
16024
16249
|
function registerProfileAddCommand(parent) {
|
|
16025
|
-
parent.command("add <name>").description("Create a new profile, clone from existing, or install from registry").option("--from <source>", "Clone from existing profile or install from registry (e.g., kdco/minimal)").
|
|
16250
|
+
parent.command("add <name>").description("Create a new profile, clone from existing, or install from registry").option("--from <source>", "Clone from existing profile or install from registry (e.g., kdco/minimal)").addHelpText("after", `
|
|
16026
16251
|
Examples:
|
|
16027
16252
|
$ ocx profile add work # Create empty profile
|
|
16028
16253
|
$ ocx profile add work --from dev # Clone from existing profile
|
|
16029
16254
|
$ ocx profile add work --from kdco/minimal # Install from registry
|
|
16030
|
-
$ ocx profile add work --from kdco/minimal --force # Overwrite existing
|
|
16031
16255
|
`).action(async (name, options2) => {
|
|
16032
16256
|
try {
|
|
16033
16257
|
await runProfileAdd(name, options2);
|
|
@@ -16038,28 +16262,15 @@ Examples:
|
|
|
16038
16262
|
}
|
|
16039
16263
|
async function runProfileAdd(name, options2) {
|
|
16040
16264
|
const manager = await ProfileManager.requireInitialized();
|
|
16041
|
-
const profileExists = await manager.exists(name);
|
|
16042
|
-
if (profileExists && !options2.force) {
|
|
16043
|
-
logger.error(`\u2717 Profile "${name}" already exists`);
|
|
16044
|
-
logger.error("");
|
|
16045
|
-
logger.error("Use --force to overwrite the existing profile.");
|
|
16046
|
-
throw new ProfileExistsError(name);
|
|
16047
|
-
}
|
|
16048
16265
|
if (!options2.from) {
|
|
16049
|
-
await createEmptyProfile(manager, name
|
|
16266
|
+
await createEmptyProfile(manager, name);
|
|
16050
16267
|
return;
|
|
16051
16268
|
}
|
|
16052
16269
|
const fromInput = parseFromOption(options2.from);
|
|
16053
16270
|
switch (fromInput.type) {
|
|
16054
16271
|
case "local-profile":
|
|
16055
|
-
await cloneFromLocalProfile(manager, name, fromInput.name
|
|
16272
|
+
await cloneFromLocalProfile(manager, name, fromInput.name);
|
|
16056
16273
|
break;
|
|
16057
|
-
case "local-path":
|
|
16058
|
-
throw new ValidationError(`Local path installation is not yet implemented: "${fromInput.path}"
|
|
16059
|
-
|
|
16060
|
-
` + `Currently supported sources:
|
|
16061
|
-
` + ` - Existing profile: --from <profile-name>
|
|
16062
|
-
` + ` - Registry: --from <namespace>/<component>`);
|
|
16063
16274
|
case "registry": {
|
|
16064
16275
|
const { config: globalConfig, registryUrl } = await requireGlobalRegistry(fromInput.namespace);
|
|
16065
16276
|
const registries = {};
|
|
@@ -16070,7 +16281,6 @@ async function runProfileAdd(name, options2) {
|
|
|
16070
16281
|
namespace: fromInput.namespace,
|
|
16071
16282
|
component: fromInput.component,
|
|
16072
16283
|
profileName: name,
|
|
16073
|
-
force: options2.force,
|
|
16074
16284
|
registryUrl,
|
|
16075
16285
|
registries
|
|
16076
16286
|
});
|
|
@@ -16078,18 +16288,20 @@ async function runProfileAdd(name, options2) {
|
|
|
16078
16288
|
}
|
|
16079
16289
|
}
|
|
16080
16290
|
}
|
|
16081
|
-
async function createEmptyProfile(manager, name
|
|
16291
|
+
async function createEmptyProfile(manager, name) {
|
|
16292
|
+
const exists = await manager.exists(name);
|
|
16082
16293
|
if (exists) {
|
|
16083
|
-
|
|
16294
|
+
throw new ProfileExistsError(name, `Remove it first with 'ocx profile rm ${name}'.`);
|
|
16084
16295
|
}
|
|
16085
16296
|
await manager.add(name);
|
|
16086
16297
|
logger.success(`Created profile "${name}"`);
|
|
16087
16298
|
}
|
|
16088
|
-
async function cloneFromLocalProfile(manager, name, sourceName
|
|
16089
|
-
const
|
|
16299
|
+
async function cloneFromLocalProfile(manager, name, sourceName) {
|
|
16300
|
+
const exists = await manager.exists(name);
|
|
16090
16301
|
if (exists) {
|
|
16091
|
-
|
|
16302
|
+
throw new ProfileExistsError(name, `Remove it first with 'ocx profile rm ${name}'.`);
|
|
16092
16303
|
}
|
|
16304
|
+
const source = await manager.get(sourceName);
|
|
16093
16305
|
await manager.add(name);
|
|
16094
16306
|
await atomicWrite(getProfileOcxConfig(name), source.ocx);
|
|
16095
16307
|
logger.success(`Created profile "${name}" (cloned from "${sourceName}")`);
|
|
@@ -16122,6 +16334,25 @@ async function runProfileList(options2) {
|
|
|
16122
16334
|
}
|
|
16123
16335
|
}
|
|
16124
16336
|
|
|
16337
|
+
// src/commands/profile/move.ts
|
|
16338
|
+
function registerProfileMoveCommand(parent) {
|
|
16339
|
+
parent.command("move <old-name> <new-name>").alias("mv").description("Move (rename) a profile").action(async (oldName, newName) => {
|
|
16340
|
+
try {
|
|
16341
|
+
await runProfileMove(oldName, newName);
|
|
16342
|
+
} catch (error) {
|
|
16343
|
+
handleError(error);
|
|
16344
|
+
}
|
|
16345
|
+
});
|
|
16346
|
+
}
|
|
16347
|
+
async function runProfileMove(oldName, newName) {
|
|
16348
|
+
const manager = await ProfileManager.requireInitialized();
|
|
16349
|
+
const { warnActiveProfile } = await manager.move(oldName, newName);
|
|
16350
|
+
if (warnActiveProfile) {
|
|
16351
|
+
logger.warn(`Moving active profile. Update OCX_PROFILE env var to "${newName}".`);
|
|
16352
|
+
}
|
|
16353
|
+
logger.success(`Moved profile "${oldName}" \u2192 "${newName}"`);
|
|
16354
|
+
}
|
|
16355
|
+
|
|
16125
16356
|
// src/commands/profile/remove.ts
|
|
16126
16357
|
function registerProfileRemoveCommand(parent) {
|
|
16127
16358
|
parent.command("remove <name>").alias("rm").description("Delete a global profile").action(async (name) => {
|
|
@@ -16180,6 +16411,7 @@ function registerProfileCommand(program2) {
|
|
|
16180
16411
|
registerProfileListCommand(profile);
|
|
16181
16412
|
registerProfileAddCommand(profile);
|
|
16182
16413
|
registerProfileRemoveCommand(profile);
|
|
16414
|
+
registerProfileMoveCommand(profile);
|
|
16183
16415
|
registerProfileShowCommand(profile);
|
|
16184
16416
|
}
|
|
16185
16417
|
|
|
@@ -16517,9 +16749,460 @@ async function runSearchCore(query, options2, provider) {
|
|
|
16517
16749
|
}
|
|
16518
16750
|
}
|
|
16519
16751
|
|
|
16752
|
+
// src/commands/self/uninstall.ts
|
|
16753
|
+
import { existsSync as existsSync11, lstatSync as lstatSync2, readdirSync as readdirSync2, realpathSync, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
16754
|
+
import { homedir as homedir4 } from "os";
|
|
16755
|
+
import path9 from "path";
|
|
16756
|
+
|
|
16757
|
+
// src/self-update/detect-method.ts
|
|
16758
|
+
function parseInstallMethod(input) {
|
|
16759
|
+
const VALID_METHODS = ["curl", "npm", "yarn", "pnpm", "bun"];
|
|
16760
|
+
const method = VALID_METHODS.find((m) => m === input);
|
|
16761
|
+
if (!method) {
|
|
16762
|
+
throw new SelfUpdateError(`Invalid install method: "${input}"
|
|
16763
|
+
Valid methods: ${VALID_METHODS.join(", ")}`);
|
|
16764
|
+
}
|
|
16765
|
+
return method;
|
|
16766
|
+
}
|
|
16767
|
+
var isCompiledBinary = () => Bun.main.startsWith("/$bunfs/");
|
|
16768
|
+
var isTempExecution = (path9) => path9.includes("/_npx/") || path9.includes("/.cache/bunx/") || path9.includes("/.pnpm/_temp/");
|
|
16769
|
+
var isYarnGlobalInstall = (path9) => path9.includes("/.yarn/global") || path9.includes("/.config/yarn/global");
|
|
16770
|
+
var isPnpmGlobalInstall = (path9) => path9.includes("/.pnpm/") || path9.includes("/pnpm/global");
|
|
16771
|
+
var isBunGlobalInstall = (path9) => path9.includes("/.bun/bin") || path9.includes("/.bun/install/global");
|
|
16772
|
+
var isNpmGlobalInstall = (path9) => path9.includes("/.npm/") || path9.includes("/node_modules/");
|
|
16773
|
+
function detectInstallMethod() {
|
|
16774
|
+
if (isCompiledBinary()) {
|
|
16775
|
+
return "curl";
|
|
16776
|
+
}
|
|
16777
|
+
const scriptPath = process.argv[1] ?? "";
|
|
16778
|
+
if (isTempExecution(scriptPath))
|
|
16779
|
+
return "unknown";
|
|
16780
|
+
if (isYarnGlobalInstall(scriptPath))
|
|
16781
|
+
return "yarn";
|
|
16782
|
+
if (isPnpmGlobalInstall(scriptPath))
|
|
16783
|
+
return "pnpm";
|
|
16784
|
+
if (isBunGlobalInstall(scriptPath))
|
|
16785
|
+
return "bun";
|
|
16786
|
+
if (isNpmGlobalInstall(scriptPath))
|
|
16787
|
+
return "npm";
|
|
16788
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
16789
|
+
if (userAgent.includes("yarn"))
|
|
16790
|
+
return "yarn";
|
|
16791
|
+
if (userAgent.includes("pnpm"))
|
|
16792
|
+
return "pnpm";
|
|
16793
|
+
if (userAgent.includes("bun"))
|
|
16794
|
+
return "bun";
|
|
16795
|
+
if (userAgent.includes("npm"))
|
|
16796
|
+
return "npm";
|
|
16797
|
+
return "unknown";
|
|
16798
|
+
}
|
|
16799
|
+
function getExecutablePath() {
|
|
16800
|
+
if (typeof Bun !== "undefined" && Bun.main.startsWith("/$bunfs/")) {
|
|
16801
|
+
return process.execPath;
|
|
16802
|
+
}
|
|
16803
|
+
return process.argv[1] ?? process.execPath;
|
|
16804
|
+
}
|
|
16805
|
+
|
|
16806
|
+
// src/commands/self/uninstall.ts
|
|
16807
|
+
var UNINSTALL_EXIT_CODES = {
|
|
16808
|
+
SUCCESS: 0,
|
|
16809
|
+
ERROR: 1,
|
|
16810
|
+
SAFETY_ERROR: 2
|
|
16811
|
+
};
|
|
16812
|
+
function isNodeError(err) {
|
|
16813
|
+
return err instanceof Error && "code" in err;
|
|
16814
|
+
}
|
|
16815
|
+
function tildify(absolutePath) {
|
|
16816
|
+
const home = homedir4();
|
|
16817
|
+
if (!home)
|
|
16818
|
+
return absolutePath;
|
|
16819
|
+
if (absolutePath === home)
|
|
16820
|
+
return "~";
|
|
16821
|
+
if (absolutePath.startsWith(home + path9.sep)) {
|
|
16822
|
+
return `~${absolutePath.slice(home.length)}`;
|
|
16823
|
+
}
|
|
16824
|
+
return absolutePath;
|
|
16825
|
+
}
|
|
16826
|
+
function getRelativePathIfContained(parent, child) {
|
|
16827
|
+
const normalizedParent = path9.normalize(parent);
|
|
16828
|
+
const normalizedChild = path9.normalize(child);
|
|
16829
|
+
const relative4 = path9.relative(normalizedParent, normalizedChild);
|
|
16830
|
+
if (relative4.startsWith("..") || path9.isAbsolute(relative4)) {
|
|
16831
|
+
return null;
|
|
16832
|
+
}
|
|
16833
|
+
return relative4;
|
|
16834
|
+
}
|
|
16835
|
+
function isLexicallyInside(root, target) {
|
|
16836
|
+
return getRelativePathIfContained(root, target) !== null;
|
|
16837
|
+
}
|
|
16838
|
+
function isRealpathInside(root, target) {
|
|
16839
|
+
if (!existsSync11(target)) {
|
|
16840
|
+
return { contained: true };
|
|
16841
|
+
}
|
|
16842
|
+
try {
|
|
16843
|
+
const realRoot = realpathSync(root);
|
|
16844
|
+
const realTarget = realpathSync(target);
|
|
16845
|
+
return { contained: getRelativePathIfContained(realRoot, realTarget) !== null };
|
|
16846
|
+
} catch (err) {
|
|
16847
|
+
if (isNodeError(err) && (err.code === "EACCES" || err.code === "EPERM")) {
|
|
16848
|
+
return { contained: false, error: "permission" };
|
|
16849
|
+
}
|
|
16850
|
+
return { contained: false, error: "io" };
|
|
16851
|
+
}
|
|
16852
|
+
}
|
|
16853
|
+
function validateRootDirectory(rootPath) {
|
|
16854
|
+
try {
|
|
16855
|
+
const stats = lstatSync2(rootPath);
|
|
16856
|
+
if (stats.isSymbolicLink()) {
|
|
16857
|
+
return { valid: false, reason: "symlink" };
|
|
16858
|
+
}
|
|
16859
|
+
if (!stats.isDirectory()) {
|
|
16860
|
+
return { valid: false, reason: "not-directory" };
|
|
16861
|
+
}
|
|
16862
|
+
return { valid: true };
|
|
16863
|
+
} catch (err) {
|
|
16864
|
+
if (isNodeError(err)) {
|
|
16865
|
+
if (err.code === "ENOENT") {
|
|
16866
|
+
return { valid: false, reason: "not-found" };
|
|
16867
|
+
}
|
|
16868
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
16869
|
+
return { valid: false, reason: "permission" };
|
|
16870
|
+
}
|
|
16871
|
+
}
|
|
16872
|
+
return { valid: false, reason: "permission" };
|
|
16873
|
+
}
|
|
16874
|
+
}
|
|
16875
|
+
function getPathKind(targetPath) {
|
|
16876
|
+
if (!existsSync11(targetPath)) {
|
|
16877
|
+
return "missing";
|
|
16878
|
+
}
|
|
16879
|
+
try {
|
|
16880
|
+
const stats = lstatSync2(targetPath);
|
|
16881
|
+
if (stats.isSymbolicLink()) {
|
|
16882
|
+
return "symlink";
|
|
16883
|
+
}
|
|
16884
|
+
if (stats.isDirectory()) {
|
|
16885
|
+
return "directory";
|
|
16886
|
+
}
|
|
16887
|
+
return "file";
|
|
16888
|
+
} catch {
|
|
16889
|
+
return "missing";
|
|
16890
|
+
}
|
|
16891
|
+
}
|
|
16892
|
+
function isDirectoryEmpty(dirPath) {
|
|
16893
|
+
if (!existsSync11(dirPath)) {
|
|
16894
|
+
return true;
|
|
16895
|
+
}
|
|
16896
|
+
try {
|
|
16897
|
+
const entries = readdirSync2(dirPath);
|
|
16898
|
+
return entries.length === 0;
|
|
16899
|
+
} catch {
|
|
16900
|
+
return false;
|
|
16901
|
+
}
|
|
16902
|
+
}
|
|
16903
|
+
function classifyTargetSafety(target) {
|
|
16904
|
+
if (target.kind === "missing") {
|
|
16905
|
+
return "safe";
|
|
16906
|
+
}
|
|
16907
|
+
if (target.kind === "symlink") {
|
|
16908
|
+
return isLexicallyInside(target.rootPath, target.absolutePath) ? "safe" : "forbidden";
|
|
16909
|
+
}
|
|
16910
|
+
const result = isRealpathInside(target.rootPath, target.absolutePath);
|
|
16911
|
+
if (result.error) {
|
|
16912
|
+
return "error";
|
|
16913
|
+
}
|
|
16914
|
+
return result.contained ? "safe" : "forbidden";
|
|
16915
|
+
}
|
|
16916
|
+
function isPackageManaged(method) {
|
|
16917
|
+
return method === "npm" || method === "pnpm" || method === "bun" || method === "yarn";
|
|
16918
|
+
}
|
|
16919
|
+
function getPackageManagerCommand(method) {
|
|
16920
|
+
switch (method) {
|
|
16921
|
+
case "npm":
|
|
16922
|
+
return "npm uninstall -g ocx";
|
|
16923
|
+
case "pnpm":
|
|
16924
|
+
return "pnpm remove -g ocx";
|
|
16925
|
+
case "bun":
|
|
16926
|
+
return "bun remove -g ocx";
|
|
16927
|
+
case "yarn":
|
|
16928
|
+
return "yarn global remove ocx";
|
|
16929
|
+
default:
|
|
16930
|
+
return "npm uninstall -g ocx";
|
|
16931
|
+
}
|
|
16932
|
+
}
|
|
16933
|
+
function getGlobalConfigRoot() {
|
|
16934
|
+
const base = process.env.XDG_CONFIG_HOME || path9.join(homedir4(), ".config");
|
|
16935
|
+
return path9.join(base, "opencode");
|
|
16936
|
+
}
|
|
16937
|
+
function buildConfigTargets() {
|
|
16938
|
+
const rootPath = getGlobalConfigRoot();
|
|
16939
|
+
const targets = [];
|
|
16940
|
+
const profilesDir = getProfilesDir();
|
|
16941
|
+
const profilesRelative = getRelativePathIfContained(rootPath, profilesDir);
|
|
16942
|
+
if (profilesRelative) {
|
|
16943
|
+
const kind = getPathKind(profilesDir);
|
|
16944
|
+
targets.push({
|
|
16945
|
+
rootPath,
|
|
16946
|
+
relativePath: profilesRelative,
|
|
16947
|
+
absolutePath: profilesDir,
|
|
16948
|
+
displayPath: tildify(profilesDir),
|
|
16949
|
+
kind,
|
|
16950
|
+
deleteIfEmpty: false,
|
|
16951
|
+
safetyStatus: classifyTargetSafety({ rootPath, absolutePath: profilesDir, kind })
|
|
16952
|
+
});
|
|
16953
|
+
}
|
|
16954
|
+
const globalConfig = getGlobalConfig();
|
|
16955
|
+
const configRelative = getRelativePathIfContained(rootPath, globalConfig);
|
|
16956
|
+
if (configRelative) {
|
|
16957
|
+
const kind = getPathKind(globalConfig);
|
|
16958
|
+
targets.push({
|
|
16959
|
+
rootPath,
|
|
16960
|
+
relativePath: configRelative,
|
|
16961
|
+
absolutePath: globalConfig,
|
|
16962
|
+
displayPath: tildify(globalConfig),
|
|
16963
|
+
kind,
|
|
16964
|
+
deleteIfEmpty: false,
|
|
16965
|
+
safetyStatus: classifyTargetSafety({ rootPath, absolutePath: globalConfig, kind })
|
|
16966
|
+
});
|
|
16967
|
+
}
|
|
16968
|
+
const rootKind = getPathKind(rootPath);
|
|
16969
|
+
targets.push({
|
|
16970
|
+
rootPath,
|
|
16971
|
+
relativePath: ".",
|
|
16972
|
+
absolutePath: rootPath,
|
|
16973
|
+
displayPath: tildify(rootPath),
|
|
16974
|
+
kind: rootKind,
|
|
16975
|
+
deleteIfEmpty: true,
|
|
16976
|
+
safetyStatus: rootKind === "missing" ? "safe" : "safe"
|
|
16977
|
+
});
|
|
16978
|
+
return targets;
|
|
16979
|
+
}
|
|
16980
|
+
function buildBinaryTarget() {
|
|
16981
|
+
const method = detectInstallMethod();
|
|
16982
|
+
if (isPackageManaged(method)) {
|
|
16983
|
+
return null;
|
|
16984
|
+
}
|
|
16985
|
+
if (method === "curl") {
|
|
16986
|
+
const binaryPath = getExecutablePath();
|
|
16987
|
+
const kind = getPathKind(binaryPath);
|
|
16988
|
+
const parentDir = path9.dirname(binaryPath);
|
|
16989
|
+
return {
|
|
16990
|
+
rootPath: parentDir,
|
|
16991
|
+
relativePath: path9.basename(binaryPath),
|
|
16992
|
+
absolutePath: binaryPath,
|
|
16993
|
+
displayPath: tildify(binaryPath),
|
|
16994
|
+
kind,
|
|
16995
|
+
deleteIfEmpty: false,
|
|
16996
|
+
safetyStatus: kind === "missing" ? "safe" : "safe"
|
|
16997
|
+
};
|
|
16998
|
+
}
|
|
16999
|
+
return null;
|
|
17000
|
+
}
|
|
17001
|
+
function executeRemoval(target) {
|
|
17002
|
+
if (target.kind === "missing") {
|
|
17003
|
+
return { target, success: true, skipped: true, reason: "not found" };
|
|
17004
|
+
}
|
|
17005
|
+
if (target.safetyStatus === "forbidden") {
|
|
17006
|
+
return {
|
|
17007
|
+
target,
|
|
17008
|
+
success: false,
|
|
17009
|
+
skipped: true,
|
|
17010
|
+
reason: "containment violation",
|
|
17011
|
+
error: new Error("Target escapes containment boundary")
|
|
17012
|
+
};
|
|
17013
|
+
}
|
|
17014
|
+
if (target.deleteIfEmpty && target.kind === "directory") {
|
|
17015
|
+
if (!isDirectoryEmpty(target.absolutePath)) {
|
|
17016
|
+
return { target, success: true, skipped: true, reason: "not empty" };
|
|
17017
|
+
}
|
|
17018
|
+
}
|
|
17019
|
+
try {
|
|
17020
|
+
if (target.kind === "directory") {
|
|
17021
|
+
rmSync2(target.absolutePath, { recursive: true, force: true });
|
|
17022
|
+
} else {
|
|
17023
|
+
unlinkSync2(target.absolutePath);
|
|
17024
|
+
}
|
|
17025
|
+
return { target, success: true, skipped: false };
|
|
17026
|
+
} catch (err) {
|
|
17027
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
17028
|
+
const reason = isNodeError(err) && (err.code === "EACCES" || err.code === "EPERM") ? "permission denied" : undefined;
|
|
17029
|
+
return { target, success: false, skipped: false, reason, error };
|
|
17030
|
+
}
|
|
17031
|
+
}
|
|
17032
|
+
function executeRemovals(targets) {
|
|
17033
|
+
return targets.map(executeRemoval);
|
|
17034
|
+
}
|
|
17035
|
+
function removeBinary(binaryPath) {
|
|
17036
|
+
const target = {
|
|
17037
|
+
rootPath: path9.dirname(binaryPath),
|
|
17038
|
+
relativePath: path9.basename(binaryPath),
|
|
17039
|
+
absolutePath: binaryPath,
|
|
17040
|
+
displayPath: tildify(binaryPath),
|
|
17041
|
+
kind: getPathKind(binaryPath),
|
|
17042
|
+
deleteIfEmpty: false,
|
|
17043
|
+
safetyStatus: "safe"
|
|
17044
|
+
};
|
|
17045
|
+
if (target.kind === "missing") {
|
|
17046
|
+
return { target, success: true, skipped: true, reason: "not found" };
|
|
17047
|
+
}
|
|
17048
|
+
if (process.platform === "win32") {
|
|
17049
|
+
logger.info(`To complete uninstall, manually delete: ${target.displayPath}`);
|
|
17050
|
+
return { target, success: true, skipped: true };
|
|
17051
|
+
}
|
|
17052
|
+
try {
|
|
17053
|
+
unlinkSync2(binaryPath);
|
|
17054
|
+
return { target, success: true, skipped: false };
|
|
17055
|
+
} catch (error) {
|
|
17056
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
17057
|
+
return { target, success: false, skipped: false, reason: "permission denied", error: err };
|
|
17058
|
+
}
|
|
17059
|
+
}
|
|
17060
|
+
function printDryRun(configTargets, binaryTarget, installMethod) {
|
|
17061
|
+
logger.info(`Dry run - the following would be removed:
|
|
17062
|
+
`);
|
|
17063
|
+
const existingConfigTargets = configTargets.filter((t2) => t2.kind !== "missing");
|
|
17064
|
+
for (const target of existingConfigTargets) {
|
|
17065
|
+
const kindLabel = target.kind === "directory" ? "[dir] " : "[file]";
|
|
17066
|
+
const emptyNote = target.deleteIfEmpty ? " (if empty)" : "";
|
|
17067
|
+
logger.log(` ${kindLabel} ${highlight.path(target.displayPath)}${emptyNote}`);
|
|
17068
|
+
}
|
|
17069
|
+
if (binaryTarget && binaryTarget.kind !== "missing") {
|
|
17070
|
+
logger.log(` [bin] ${highlight.path(binaryTarget.displayPath)}`);
|
|
17071
|
+
}
|
|
17072
|
+
if (isPackageManaged(installMethod)) {
|
|
17073
|
+
logger.log("");
|
|
17074
|
+
logger.info(`Binary managed by ${installMethod}. Run:`);
|
|
17075
|
+
logger.log(` ${highlight.command(getPackageManagerCommand(installMethod))}`);
|
|
17076
|
+
}
|
|
17077
|
+
if (existingConfigTargets.length === 0 && (!binaryTarget || binaryTarget.kind === "missing")) {
|
|
17078
|
+
logger.info("Nothing to remove.");
|
|
17079
|
+
}
|
|
17080
|
+
}
|
|
17081
|
+
function printRemovalPlan(configTargets, binaryTarget) {
|
|
17082
|
+
const existingConfigTargets = configTargets.filter((t2) => t2.kind !== "missing");
|
|
17083
|
+
if (existingConfigTargets.length > 0 || binaryTarget && binaryTarget.kind !== "missing") {
|
|
17084
|
+
logger.info("Removing OCX files...");
|
|
17085
|
+
}
|
|
17086
|
+
}
|
|
17087
|
+
function printResults(results, binaryResult, installMethod) {
|
|
17088
|
+
logger.break();
|
|
17089
|
+
for (const result of results) {
|
|
17090
|
+
if (result.skipped) {
|
|
17091
|
+
if (result.reason === "not found") {
|
|
17092
|
+
continue;
|
|
17093
|
+
}
|
|
17094
|
+
if (result.reason === "not empty") {
|
|
17095
|
+
logger.info(`Kept ${highlight.path(result.target.displayPath)} (not empty)`);
|
|
17096
|
+
continue;
|
|
17097
|
+
}
|
|
17098
|
+
if (result.reason === "permission denied") {
|
|
17099
|
+
logger.warn(`Skipped ${highlight.path(result.target.displayPath)} (permission denied)`);
|
|
17100
|
+
continue;
|
|
17101
|
+
}
|
|
17102
|
+
if (result.reason === "containment violation") {
|
|
17103
|
+
logger.warn(`Skipped ${highlight.path(result.target.displayPath)} (containment violation)`);
|
|
17104
|
+
continue;
|
|
17105
|
+
}
|
|
17106
|
+
}
|
|
17107
|
+
if (result.success) {
|
|
17108
|
+
logger.success(`Removed ${highlight.path(result.target.displayPath)}`);
|
|
17109
|
+
} else {
|
|
17110
|
+
logger.error(`Failed to remove ${result.target.displayPath}: ${result.error?.message}`);
|
|
17111
|
+
}
|
|
17112
|
+
}
|
|
17113
|
+
if (binaryResult) {
|
|
17114
|
+
if (binaryResult.skipped && binaryResult.reason === "not found") {} else if (binaryResult.success && !binaryResult.skipped) {
|
|
17115
|
+
logger.success(`Removed binary ${highlight.path(binaryResult.target.displayPath)}`);
|
|
17116
|
+
} else if (!binaryResult.success) {
|
|
17117
|
+
logger.error(`Failed to remove binary ${binaryResult.target.displayPath}: ${binaryResult.error?.message}`);
|
|
17118
|
+
}
|
|
17119
|
+
}
|
|
17120
|
+
if (isPackageManaged(installMethod)) {
|
|
17121
|
+
logger.break();
|
|
17122
|
+
logger.info(`Binary is managed by ${installMethod}. To complete uninstall, run:`);
|
|
17123
|
+
logger.log(` ${highlight.command(getPackageManagerCommand(installMethod))}`);
|
|
17124
|
+
}
|
|
17125
|
+
}
|
|
17126
|
+
function printNothingToRemove() {
|
|
17127
|
+
logger.info("Nothing to remove. OCX is not installed globally.");
|
|
17128
|
+
}
|
|
17129
|
+
async function runUninstall(options2) {
|
|
17130
|
+
const rootPath = getGlobalConfigRoot();
|
|
17131
|
+
const rootValidation = validateRootDirectory(rootPath);
|
|
17132
|
+
if (!rootValidation.valid) {
|
|
17133
|
+
switch (rootValidation.reason) {
|
|
17134
|
+
case "not-found":
|
|
17135
|
+
break;
|
|
17136
|
+
case "symlink":
|
|
17137
|
+
logger.error("Safety error: Global config root is a symlink. Aborting.");
|
|
17138
|
+
process.exit(UNINSTALL_EXIT_CODES.SAFETY_ERROR);
|
|
17139
|
+
break;
|
|
17140
|
+
case "not-directory":
|
|
17141
|
+
logger.error("Safety error: Global config root is not a directory. Aborting.");
|
|
17142
|
+
process.exit(UNINSTALL_EXIT_CODES.SAFETY_ERROR);
|
|
17143
|
+
break;
|
|
17144
|
+
case "permission":
|
|
17145
|
+
logger.error("Error: Cannot access global config root (permission denied).");
|
|
17146
|
+
process.exit(UNINSTALL_EXIT_CODES.ERROR);
|
|
17147
|
+
break;
|
|
17148
|
+
}
|
|
17149
|
+
}
|
|
17150
|
+
const configTargets = buildConfigTargets();
|
|
17151
|
+
const forbiddenTargets = configTargets.filter((t2) => t2.safetyStatus === "forbidden");
|
|
17152
|
+
if (forbiddenTargets.length > 0) {
|
|
17153
|
+
logger.error("Safety error: Target escapes containment boundary:");
|
|
17154
|
+
for (const target of forbiddenTargets) {
|
|
17155
|
+
logger.error(` ${target.displayPath}`);
|
|
17156
|
+
}
|
|
17157
|
+
process.exit(UNINSTALL_EXIT_CODES.SAFETY_ERROR);
|
|
17158
|
+
}
|
|
17159
|
+
const errorTargets = configTargets.filter((t2) => t2.safetyStatus === "error");
|
|
17160
|
+
if (errorTargets.length > 0) {
|
|
17161
|
+
logger.error("Error: Cannot verify containment for targets (permission/IO error):");
|
|
17162
|
+
for (const target of errorTargets) {
|
|
17163
|
+
logger.error(` ${target.displayPath}`);
|
|
17164
|
+
}
|
|
17165
|
+
process.exit(UNINSTALL_EXIT_CODES.ERROR);
|
|
17166
|
+
}
|
|
17167
|
+
const installMethod = detectInstallMethod();
|
|
17168
|
+
const binaryTarget = buildBinaryTarget();
|
|
17169
|
+
const existingConfigTargets = configTargets.filter((t2) => t2.kind !== "missing");
|
|
17170
|
+
const hasBinary = binaryTarget && binaryTarget.kind !== "missing";
|
|
17171
|
+
const hasPackageManager = isPackageManaged(installMethod);
|
|
17172
|
+
if (existingConfigTargets.length === 0 && !hasBinary && !hasPackageManager) {
|
|
17173
|
+
printNothingToRemove();
|
|
17174
|
+
process.exit(UNINSTALL_EXIT_CODES.SUCCESS);
|
|
17175
|
+
}
|
|
17176
|
+
if (options2.dryRun) {
|
|
17177
|
+
printDryRun(configTargets, binaryTarget, installMethod);
|
|
17178
|
+
process.exit(UNINSTALL_EXIT_CODES.SUCCESS);
|
|
17179
|
+
}
|
|
17180
|
+
printRemovalPlan(configTargets, binaryTarget);
|
|
17181
|
+
const configResults = executeRemovals(configTargets);
|
|
17182
|
+
let binaryResult = null;
|
|
17183
|
+
if (binaryTarget) {
|
|
17184
|
+
binaryResult = removeBinary(binaryTarget.absolutePath);
|
|
17185
|
+
}
|
|
17186
|
+
printResults(configResults, binaryResult, installMethod);
|
|
17187
|
+
const hasFailures = configResults.some((r2) => !r2.success && !r2.skipped);
|
|
17188
|
+
const binaryFailed = binaryResult && !binaryResult.success && !binaryResult.skipped;
|
|
17189
|
+
if (hasFailures || binaryFailed) {
|
|
17190
|
+
process.exit(UNINSTALL_EXIT_CODES.ERROR);
|
|
17191
|
+
}
|
|
17192
|
+
if (isPackageManaged(installMethod)) {
|
|
17193
|
+
process.exit(UNINSTALL_EXIT_CODES.ERROR);
|
|
17194
|
+
}
|
|
17195
|
+
process.exit(UNINSTALL_EXIT_CODES.SUCCESS);
|
|
17196
|
+
}
|
|
17197
|
+
function registerSelfUninstallCommand(parent) {
|
|
17198
|
+
parent.command("uninstall").description("Remove OCX global configuration and binary").option("--dry-run", "Preview what would be removed").action(wrapAction(async (options2) => {
|
|
17199
|
+
await runUninstall(options2);
|
|
17200
|
+
}));
|
|
17201
|
+
}
|
|
17202
|
+
|
|
16520
17203
|
// src/self-update/version-provider.ts
|
|
16521
17204
|
class BuildTimeVersionProvider {
|
|
16522
|
-
version = "1.4.
|
|
17205
|
+
version = "1.4.4";
|
|
16523
17206
|
}
|
|
16524
17207
|
var defaultVersionProvider = new BuildTimeVersionProvider;
|
|
16525
17208
|
|
|
@@ -16582,57 +17265,8 @@ async function checkForUpdate(versionProvider, timeoutMs = VERSION_CHECK_TIMEOUT
|
|
|
16582
17265
|
}
|
|
16583
17266
|
}
|
|
16584
17267
|
|
|
16585
|
-
// src/self-update/detect-method.ts
|
|
16586
|
-
function parseInstallMethod(input) {
|
|
16587
|
-
const VALID_METHODS = ["curl", "npm", "yarn", "pnpm", "bun"];
|
|
16588
|
-
const method = VALID_METHODS.find((m) => m === input);
|
|
16589
|
-
if (!method) {
|
|
16590
|
-
throw new SelfUpdateError(`Invalid install method: "${input}"
|
|
16591
|
-
Valid methods: ${VALID_METHODS.join(", ")}`);
|
|
16592
|
-
}
|
|
16593
|
-
return method;
|
|
16594
|
-
}
|
|
16595
|
-
var isCompiledBinary = () => Bun.main.startsWith("/$bunfs/");
|
|
16596
|
-
var isTempExecution = (path8) => path8.includes("/_npx/") || path8.includes("/.cache/bunx/") || path8.includes("/.pnpm/_temp/");
|
|
16597
|
-
var isYarnGlobalInstall = (path8) => path8.includes("/.yarn/global") || path8.includes("/.config/yarn/global");
|
|
16598
|
-
var isPnpmGlobalInstall = (path8) => path8.includes("/.pnpm/") || path8.includes("/pnpm/global");
|
|
16599
|
-
var isBunGlobalInstall = (path8) => path8.includes("/.bun/bin") || path8.includes("/.bun/install/global");
|
|
16600
|
-
var isNpmGlobalInstall = (path8) => path8.includes("/.npm/") || path8.includes("/node_modules/");
|
|
16601
|
-
function detectInstallMethod() {
|
|
16602
|
-
if (isCompiledBinary()) {
|
|
16603
|
-
return "curl";
|
|
16604
|
-
}
|
|
16605
|
-
const scriptPath = process.argv[1] ?? "";
|
|
16606
|
-
if (isTempExecution(scriptPath))
|
|
16607
|
-
return "unknown";
|
|
16608
|
-
if (isYarnGlobalInstall(scriptPath))
|
|
16609
|
-
return "yarn";
|
|
16610
|
-
if (isPnpmGlobalInstall(scriptPath))
|
|
16611
|
-
return "pnpm";
|
|
16612
|
-
if (isBunGlobalInstall(scriptPath))
|
|
16613
|
-
return "bun";
|
|
16614
|
-
if (isNpmGlobalInstall(scriptPath))
|
|
16615
|
-
return "npm";
|
|
16616
|
-
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
16617
|
-
if (userAgent.includes("yarn"))
|
|
16618
|
-
return "yarn";
|
|
16619
|
-
if (userAgent.includes("pnpm"))
|
|
16620
|
-
return "pnpm";
|
|
16621
|
-
if (userAgent.includes("bun"))
|
|
16622
|
-
return "bun";
|
|
16623
|
-
if (userAgent.includes("npm"))
|
|
16624
|
-
return "npm";
|
|
16625
|
-
return "unknown";
|
|
16626
|
-
}
|
|
16627
|
-
function getExecutablePath() {
|
|
16628
|
-
if (typeof Bun !== "undefined" && Bun.main.startsWith("/$bunfs/")) {
|
|
16629
|
-
return process.execPath;
|
|
16630
|
-
}
|
|
16631
|
-
return process.argv[1] ?? process.execPath;
|
|
16632
|
-
}
|
|
16633
|
-
|
|
16634
17268
|
// src/self-update/download.ts
|
|
16635
|
-
import { chmodSync, existsSync as
|
|
17269
|
+
import { chmodSync, existsSync as existsSync12, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
16636
17270
|
var GITHUB_REPO2 = "kdcokenny/ocx";
|
|
16637
17271
|
var DEFAULT_DOWNLOAD_BASE_URL = `https://github.com/${GITHUB_REPO2}/releases/download`;
|
|
16638
17272
|
var PLATFORM_MAP = {
|
|
@@ -16710,8 +17344,8 @@ async function downloadToTemp(version) {
|
|
|
16710
17344
|
try {
|
|
16711
17345
|
chmodSync(tempPath, 493);
|
|
16712
17346
|
} catch (error) {
|
|
16713
|
-
if (
|
|
16714
|
-
|
|
17347
|
+
if (existsSync12(tempPath)) {
|
|
17348
|
+
unlinkSync3(tempPath);
|
|
16715
17349
|
}
|
|
16716
17350
|
throw new SelfUpdateError(`Failed to set permissions: ${error instanceof Error ? error.message : String(error)}`);
|
|
16717
17351
|
}
|
|
@@ -16720,31 +17354,31 @@ async function downloadToTemp(version) {
|
|
|
16720
17354
|
function atomicReplace(tempPath, execPath) {
|
|
16721
17355
|
const backupPath = `${execPath}.backup`;
|
|
16722
17356
|
try {
|
|
16723
|
-
if (
|
|
17357
|
+
if (existsSync12(execPath)) {
|
|
16724
17358
|
renameSync2(execPath, backupPath);
|
|
16725
17359
|
}
|
|
16726
17360
|
renameSync2(tempPath, execPath);
|
|
16727
|
-
if (
|
|
16728
|
-
|
|
17361
|
+
if (existsSync12(backupPath)) {
|
|
17362
|
+
unlinkSync3(backupPath);
|
|
16729
17363
|
}
|
|
16730
17364
|
} catch (error) {
|
|
16731
|
-
if (
|
|
17365
|
+
if (existsSync12(backupPath) && !existsSync12(execPath)) {
|
|
16732
17366
|
try {
|
|
16733
17367
|
renameSync2(backupPath, execPath);
|
|
16734
17368
|
} catch {}
|
|
16735
17369
|
}
|
|
16736
|
-
if (
|
|
17370
|
+
if (existsSync12(tempPath)) {
|
|
16737
17371
|
try {
|
|
16738
|
-
|
|
17372
|
+
unlinkSync3(tempPath);
|
|
16739
17373
|
} catch {}
|
|
16740
17374
|
}
|
|
16741
17375
|
throw new SelfUpdateError(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
16742
17376
|
}
|
|
16743
17377
|
}
|
|
16744
17378
|
function cleanupTempFile(tempPath) {
|
|
16745
|
-
if (
|
|
17379
|
+
if (existsSync12(tempPath)) {
|
|
16746
17380
|
try {
|
|
16747
|
-
|
|
17381
|
+
unlinkSync3(tempPath);
|
|
16748
17382
|
} catch {}
|
|
16749
17383
|
}
|
|
16750
17384
|
}
|
|
@@ -16913,12 +17547,13 @@ function registerSelfUpdateCommand(parent) {
|
|
|
16913
17547
|
// src/commands/self/index.ts
|
|
16914
17548
|
function registerSelfCommand(program2) {
|
|
16915
17549
|
const self = program2.command("self").description("Manage the OCX CLI");
|
|
17550
|
+
registerSelfUninstallCommand(self);
|
|
16916
17551
|
registerSelfUpdateCommand(self);
|
|
16917
17552
|
}
|
|
16918
17553
|
|
|
16919
17554
|
// src/commands/update.ts
|
|
16920
17555
|
import { createHash as createHash4 } from "crypto";
|
|
16921
|
-
import { existsSync as
|
|
17556
|
+
import { existsSync as existsSync13 } from "fs";
|
|
16922
17557
|
import { mkdir as mkdir9, writeFile as writeFile4 } from "fs/promises";
|
|
16923
17558
|
import { dirname as dirname6, join as join10 } from "path";
|
|
16924
17559
|
function registerUpdateCommand(program2) {
|
|
@@ -17060,7 +17695,7 @@ Version cannot be empty. Use 'kdco/agents@1.2.0' or omit the version for latest.
|
|
|
17060
17695
|
continue;
|
|
17061
17696
|
const targetPath = join10(provider.cwd, fileObj.target);
|
|
17062
17697
|
const targetDir = dirname6(targetPath);
|
|
17063
|
-
if (!
|
|
17698
|
+
if (!existsSync13(targetDir)) {
|
|
17064
17699
|
await mkdir9(targetDir, { recursive: true });
|
|
17065
17700
|
}
|
|
17066
17701
|
await writeFile4(targetPath, file.content);
|
|
@@ -17209,8 +17844,8 @@ function shouldCheckForUpdate() {
|
|
|
17209
17844
|
return true;
|
|
17210
17845
|
}
|
|
17211
17846
|
function registerUpdateCheckHook(program2) {
|
|
17212
|
-
program2.hook("postAction", async (
|
|
17213
|
-
if (
|
|
17847
|
+
program2.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
17848
|
+
if (actionCommand.name() === "update" && actionCommand.parent?.name() === "self") {
|
|
17214
17849
|
return;
|
|
17215
17850
|
}
|
|
17216
17851
|
if (!shouldCheckForUpdate())
|
|
@@ -17224,7 +17859,7 @@ function registerUpdateCheckHook(program2) {
|
|
|
17224
17859
|
});
|
|
17225
17860
|
}
|
|
17226
17861
|
// src/index.ts
|
|
17227
|
-
var version = "1.4.
|
|
17862
|
+
var version = "1.4.4";
|
|
17228
17863
|
async function main2() {
|
|
17229
17864
|
const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
|
|
17230
17865
|
registerInitCommand(program2);
|
|
@@ -17256,4 +17891,4 @@ export {
|
|
|
17256
17891
|
buildRegistry
|
|
17257
17892
|
};
|
|
17258
17893
|
|
|
17259
|
-
//# debugId=
|
|
17894
|
+
//# debugId=C29AE4337E16008A64756E2164756E21
|