ocx 1.4.2 → 1.4.3

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