ocx 1.2.2 → 1.3.0

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
@@ -3761,6 +3761,333 @@ var require_cli_spinners = __commonJS((exports, module) => {
3761
3761
  module.exports = spinners;
3762
3762
  });
3763
3763
 
3764
+ // ../../node_modules/.bun/ignore@7.0.5/node_modules/ignore/index.js
3765
+ var require_ignore = __commonJS((exports, module) => {
3766
+ function makeArray(subject) {
3767
+ return Array.isArray(subject) ? subject : [subject];
3768
+ }
3769
+ var UNDEFINED = undefined;
3770
+ var EMPTY = "";
3771
+ var SPACE = " ";
3772
+ var ESCAPE = "\\";
3773
+ var REGEX_TEST_BLANK_LINE = /^\s+$/;
3774
+ var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
3775
+ var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
3776
+ var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
3777
+ var REGEX_SPLITALL_CRLF = /\r?\n/g;
3778
+ var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
3779
+ var REGEX_TEST_TRAILING_SLASH = /\/$/;
3780
+ var SLASH2 = "/";
3781
+ var TMP_KEY_IGNORE = "node-ignore";
3782
+ if (typeof Symbol !== "undefined") {
3783
+ TMP_KEY_IGNORE = Symbol.for("node-ignore");
3784
+ }
3785
+ var KEY_IGNORE = TMP_KEY_IGNORE;
3786
+ var define2 = (object, key, value) => {
3787
+ Object.defineProperty(object, key, { value });
3788
+ return value;
3789
+ };
3790
+ var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
3791
+ var RETURN_FALSE = () => false;
3792
+ var sanitizeRange = (range) => range.replace(REGEX_REGEXP_RANGE, (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY);
3793
+ var cleanRangeBackSlash = (slashes) => {
3794
+ const { length } = slashes;
3795
+ return slashes.slice(0, length - length % 2);
3796
+ };
3797
+ var REPLACERS = [
3798
+ [
3799
+ /^\uFEFF/,
3800
+ () => EMPTY
3801
+ ],
3802
+ [
3803
+ /((?:\\\\)*?)(\\?\s+)$/,
3804
+ (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
3805
+ ],
3806
+ [
3807
+ /(\\+?)\s/g,
3808
+ (_, m1) => {
3809
+ const { length } = m1;
3810
+ return m1.slice(0, length - length % 2) + SPACE;
3811
+ }
3812
+ ],
3813
+ [
3814
+ /[\\$.|*+(){^]/g,
3815
+ (match) => `\\${match}`
3816
+ ],
3817
+ [
3818
+ /(?!\\)\?/g,
3819
+ () => "[^/]"
3820
+ ],
3821
+ [
3822
+ /^\//,
3823
+ () => "^"
3824
+ ],
3825
+ [
3826
+ /\//g,
3827
+ () => "\\/"
3828
+ ],
3829
+ [
3830
+ /^\^*\\\*\\\*\\\//,
3831
+ () => "^(?:.*\\/)?"
3832
+ ],
3833
+ [
3834
+ /^(?=[^^])/,
3835
+ function startingReplacer() {
3836
+ return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
3837
+ }
3838
+ ],
3839
+ [
3840
+ /\\\/\\\*\\\*(?=\\\/|$)/g,
3841
+ (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
3842
+ ],
3843
+ [
3844
+ /(^|[^\\]+)(\\\*)+(?=.+)/g,
3845
+ (_, p1, p2) => {
3846
+ const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
3847
+ return p1 + unescaped;
3848
+ }
3849
+ ],
3850
+ [
3851
+ /\\\\\\(?=[$.|*+(){^])/g,
3852
+ () => ESCAPE
3853
+ ],
3854
+ [
3855
+ /\\\\/g,
3856
+ () => ESCAPE
3857
+ ],
3858
+ [
3859
+ /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
3860
+ (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
3861
+ ],
3862
+ [
3863
+ /(?:[^*])$/,
3864
+ (match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
3865
+ ]
3866
+ ];
3867
+ var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
3868
+ var MODE_IGNORE = "regex";
3869
+ var MODE_CHECK_IGNORE = "checkRegex";
3870
+ var UNDERSCORE = "_";
3871
+ var TRAILING_WILD_CARD_REPLACERS = {
3872
+ [MODE_IGNORE](_, p1) {
3873
+ const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
3874
+ return `${prefix}(?=$|\\/$)`;
3875
+ },
3876
+ [MODE_CHECK_IGNORE](_, p1) {
3877
+ const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
3878
+ return `${prefix}(?=$|\\/$)`;
3879
+ }
3880
+ };
3881
+ var makeRegexPrefix = (pattern) => REPLACERS.reduce((prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)), pattern);
3882
+ var isString = (subject) => typeof subject === "string";
3883
+ var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
3884
+ var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
3885
+
3886
+ class IgnoreRule {
3887
+ constructor(pattern, mark, body, ignoreCase, negative, prefix) {
3888
+ this.pattern = pattern;
3889
+ this.mark = mark;
3890
+ this.negative = negative;
3891
+ define2(this, "body", body);
3892
+ define2(this, "ignoreCase", ignoreCase);
3893
+ define2(this, "regexPrefix", prefix);
3894
+ }
3895
+ get regex() {
3896
+ const key = UNDERSCORE + MODE_IGNORE;
3897
+ if (this[key]) {
3898
+ return this[key];
3899
+ }
3900
+ return this._make(MODE_IGNORE, key);
3901
+ }
3902
+ get checkRegex() {
3903
+ const key = UNDERSCORE + MODE_CHECK_IGNORE;
3904
+ if (this[key]) {
3905
+ return this[key];
3906
+ }
3907
+ return this._make(MODE_CHECK_IGNORE, key);
3908
+ }
3909
+ _make(mode, key) {
3910
+ const str = this.regexPrefix.replace(REGEX_REPLACE_TRAILING_WILDCARD, TRAILING_WILD_CARD_REPLACERS[mode]);
3911
+ const regex2 = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
3912
+ return define2(this, key, regex2);
3913
+ }
3914
+ }
3915
+ var createRule = ({
3916
+ pattern,
3917
+ mark
3918
+ }, ignoreCase) => {
3919
+ let negative = false;
3920
+ let body = pattern;
3921
+ if (body.indexOf("!") === 0) {
3922
+ negative = true;
3923
+ body = body.substr(1);
3924
+ }
3925
+ body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
3926
+ const regexPrefix = makeRegexPrefix(body);
3927
+ return new IgnoreRule(pattern, mark, body, ignoreCase, negative, regexPrefix);
3928
+ };
3929
+
3930
+ class RuleManager {
3931
+ constructor(ignoreCase) {
3932
+ this._ignoreCase = ignoreCase;
3933
+ this._rules = [];
3934
+ }
3935
+ _add(pattern) {
3936
+ if (pattern && pattern[KEY_IGNORE]) {
3937
+ this._rules = this._rules.concat(pattern._rules._rules);
3938
+ this._added = true;
3939
+ return;
3940
+ }
3941
+ if (isString(pattern)) {
3942
+ pattern = {
3943
+ pattern
3944
+ };
3945
+ }
3946
+ if (checkPattern(pattern.pattern)) {
3947
+ const rule = createRule(pattern, this._ignoreCase);
3948
+ this._added = true;
3949
+ this._rules.push(rule);
3950
+ }
3951
+ }
3952
+ add(pattern) {
3953
+ this._added = false;
3954
+ makeArray(isString(pattern) ? splitPattern(pattern) : pattern).forEach(this._add, this);
3955
+ return this._added;
3956
+ }
3957
+ test(path5, checkUnignored, mode) {
3958
+ let ignored = false;
3959
+ let unignored = false;
3960
+ let matchedRule;
3961
+ this._rules.forEach((rule) => {
3962
+ const { negative } = rule;
3963
+ if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
3964
+ return;
3965
+ }
3966
+ const matched = rule[mode].test(path5);
3967
+ if (!matched) {
3968
+ return;
3969
+ }
3970
+ ignored = !negative;
3971
+ unignored = negative;
3972
+ matchedRule = negative ? UNDEFINED : rule;
3973
+ });
3974
+ const ret = {
3975
+ ignored,
3976
+ unignored
3977
+ };
3978
+ if (matchedRule) {
3979
+ ret.rule = matchedRule;
3980
+ }
3981
+ return ret;
3982
+ }
3983
+ }
3984
+ var throwError = (message, Ctor) => {
3985
+ throw new Ctor(message);
3986
+ };
3987
+ var checkPath = (path5, originalPath, doThrow) => {
3988
+ if (!isString(path5)) {
3989
+ return doThrow(`path must be a string, but got \`${originalPath}\``, TypeError);
3990
+ }
3991
+ if (!path5) {
3992
+ return doThrow(`path must not be empty`, TypeError);
3993
+ }
3994
+ if (checkPath.isNotRelative(path5)) {
3995
+ const r2 = "`path.relative()`d";
3996
+ return doThrow(`path should be a ${r2} string, but got "${originalPath}"`, RangeError);
3997
+ }
3998
+ return true;
3999
+ };
4000
+ var isNotRelative = (path5) => REGEX_TEST_INVALID_PATH.test(path5);
4001
+ checkPath.isNotRelative = isNotRelative;
4002
+ checkPath.convert = (p) => p;
4003
+
4004
+ class Ignore {
4005
+ constructor({
4006
+ ignorecase = true,
4007
+ ignoreCase = ignorecase,
4008
+ allowRelativePaths = false
4009
+ } = {}) {
4010
+ define2(this, KEY_IGNORE, true);
4011
+ this._rules = new RuleManager(ignoreCase);
4012
+ this._strictPathCheck = !allowRelativePaths;
4013
+ this._initCache();
4014
+ }
4015
+ _initCache() {
4016
+ this._ignoreCache = Object.create(null);
4017
+ this._testCache = Object.create(null);
4018
+ }
4019
+ add(pattern) {
4020
+ if (this._rules.add(pattern)) {
4021
+ this._initCache();
4022
+ }
4023
+ return this;
4024
+ }
4025
+ addPattern(pattern) {
4026
+ return this.add(pattern);
4027
+ }
4028
+ _test(originalPath, cache2, checkUnignored, slices) {
4029
+ const path5 = originalPath && checkPath.convert(originalPath);
4030
+ checkPath(path5, originalPath, this._strictPathCheck ? throwError : RETURN_FALSE);
4031
+ return this._t(path5, cache2, checkUnignored, slices);
4032
+ }
4033
+ checkIgnore(path5) {
4034
+ if (!REGEX_TEST_TRAILING_SLASH.test(path5)) {
4035
+ return this.test(path5);
4036
+ }
4037
+ const slices = path5.split(SLASH2).filter(Boolean);
4038
+ slices.pop();
4039
+ if (slices.length) {
4040
+ const parent = this._t(slices.join(SLASH2) + SLASH2, this._testCache, true, slices);
4041
+ if (parent.ignored) {
4042
+ return parent;
4043
+ }
4044
+ }
4045
+ return this._rules.test(path5, false, MODE_CHECK_IGNORE);
4046
+ }
4047
+ _t(path5, cache2, checkUnignored, slices) {
4048
+ if (path5 in cache2) {
4049
+ return cache2[path5];
4050
+ }
4051
+ if (!slices) {
4052
+ slices = path5.split(SLASH2).filter(Boolean);
4053
+ }
4054
+ slices.pop();
4055
+ if (!slices.length) {
4056
+ return cache2[path5] = this._rules.test(path5, checkUnignored, MODE_IGNORE);
4057
+ }
4058
+ const parent = this._t(slices.join(SLASH2) + SLASH2, cache2, checkUnignored, slices);
4059
+ return cache2[path5] = parent.ignored ? parent : this._rules.test(path5, checkUnignored, MODE_IGNORE);
4060
+ }
4061
+ ignores(path5) {
4062
+ return this._test(path5, this._ignoreCache, false).ignored;
4063
+ }
4064
+ createFilter() {
4065
+ return (path5) => !this.ignores(path5);
4066
+ }
4067
+ filter(paths) {
4068
+ return makeArray(paths).filter(this.createFilter());
4069
+ }
4070
+ test(path5) {
4071
+ return this._test(path5, this._testCache, true);
4072
+ }
4073
+ }
4074
+ var factory = (options2) => new Ignore(options2);
4075
+ var isPathValid = (path5) => checkPath(path5 && checkPath.convert(path5), path5, RETURN_FALSE);
4076
+ var setupWindows = () => {
4077
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
4078
+ checkPath.convert = makePosix;
4079
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
4080
+ checkPath.isNotRelative = (path5) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path5) || isNotRelative(path5);
4081
+ };
4082
+ if (typeof process !== "undefined" && process.platform === "win32") {
4083
+ setupWindows();
4084
+ }
4085
+ module.exports = factory;
4086
+ factory.default = factory;
4087
+ module.exports.isPathValid = isPathValid;
4088
+ define2(module.exports, Symbol.for("setupWindows"), setupWindows);
4089
+ });
4090
+
3764
4091
  // ../../node_modules/.bun/fuzzysort@3.1.0/node_modules/fuzzysort/fuzzysort.js
3765
4092
  var require_fuzzysort = __commonJS((exports, module) => {
3766
4093
  ((root, UMD) => {
@@ -3949,8 +4276,8 @@ var require_fuzzysort = __commonJS((exports, module) => {
3949
4276
  results.total = resultsLen + limitedCount;
3950
4277
  return results;
3951
4278
  };
3952
- var highlight = (result, open = "<b>", close = "</b>") => {
3953
- var callback = typeof open === "function" ? open : undefined;
4279
+ var highlight = (result, open2 = "<b>", close = "</b>") => {
4280
+ var callback = typeof open2 === "function" ? open2 : undefined;
3954
4281
  var target = result.target;
3955
4282
  var targetLen = target.length;
3956
4283
  var indexes = result.indexes;
@@ -3969,7 +4296,7 @@ var require_fuzzysort = __commonJS((exports, module) => {
3969
4296
  parts.push(highlighted);
3970
4297
  highlighted = "";
3971
4298
  } else {
3972
- highlighted += open;
4299
+ highlighted += open2;
3973
4300
  }
3974
4301
  }
3975
4302
  if (indexesI === indexes.length) {
@@ -4018,8 +4345,8 @@ var require_fuzzysort = __commonJS((exports, module) => {
4018
4345
  set ["indexes"](indexes) {
4019
4346
  return this._indexes = indexes;
4020
4347
  }
4021
- ["highlight"](open, close) {
4022
- return highlight(this, open, close);
4348
+ ["highlight"](open2, close) {
4349
+ return highlight(this, open2, close);
4023
4350
  }
4024
4351
  get ["score"]() {
4025
4352
  return normalizeScore(this._score);
@@ -10165,8 +10492,16 @@ var ghostConfigSchema = exports_external.object({
10165
10492
  $schema: exports_external.string().optional(),
10166
10493
  registries: exports_external.record(registryConfigSchema).default({}),
10167
10494
  componentPath: safeRelativePathSchema.optional(),
10168
- include: exports_external.array(globPatternSchema).optional(),
10169
- exclude: exports_external.array(globPatternSchema).optional()
10495
+ exclude: exports_external.array(globPatternSchema).default([
10496
+ "**/AGENTS.md",
10497
+ "**/CLAUDE.md",
10498
+ "**/CONTEXT.md",
10499
+ ".opencode",
10500
+ "opencode.jsonc",
10501
+ "opencode.json"
10502
+ ]).describe("Glob patterns to exclude from the symlink farm"),
10503
+ include: exports_external.array(globPatternSchema).default([]).describe("Glob patterns to re-include from excluded set (for power users)"),
10504
+ renameWindow: exports_external.boolean().default(true).describe("Set terminal/tmux window name when launching OpenCode")
10170
10505
  });
10171
10506
 
10172
10507
  // src/utils/errors.ts
@@ -10237,6 +10572,13 @@ class IntegrityError extends OCXError {
10237
10572
  this.name = "IntegrityError";
10238
10573
  }
10239
10574
  }
10575
+
10576
+ class SelfUpdateError extends OCXError {
10577
+ constructor(message) {
10578
+ super(message, "UPDATE_ERROR", EXIT_CODES.GENERAL);
10579
+ this.name = "SelfUpdateError";
10580
+ }
10581
+ }
10240
10582
  class GhostConfigError extends OCXError {
10241
10583
  constructor(message) {
10242
10584
  super(message, "CONFIG_ERROR", EXIT_CODES.CONFIG);
@@ -10334,7 +10676,17 @@ var profileSchema = exports_external.object({
10334
10676
  // src/profile/manager.ts
10335
10677
  var DEFAULT_GHOST_CONFIG = {
10336
10678
  $schema: "https://ocx.kdco.dev/schemas/ghost.json",
10337
- registries: {}
10679
+ registries: {},
10680
+ exclude: [
10681
+ "**/AGENTS.md",
10682
+ "**/CLAUDE.md",
10683
+ "**/CONTEXT.md",
10684
+ ".opencode",
10685
+ "opencode.jsonc",
10686
+ "opencode.json"
10687
+ ],
10688
+ include: [],
10689
+ renameWindow: true
10338
10690
  };
10339
10691
 
10340
10692
  class ProfileManager {
@@ -10528,7 +10880,7 @@ class GhostConfigProvider {
10528
10880
  // package.json
10529
10881
  var package_default = {
10530
10882
  name: "ocx",
10531
- version: "1.2.2",
10883
+ version: "1.3.0",
10532
10884
  description: "OCX CLI - ShadCN-style registry for OpenCode extensions. Install agents, plugins, skills, and MCP servers.",
10533
10885
  author: "kdcokenny",
10534
10886
  license: "MIT",
@@ -10576,9 +10928,11 @@ var package_default = {
10576
10928
  test: "bun test"
10577
10929
  },
10578
10930
  dependencies: {
10931
+ chokidar: "^5.0.0",
10579
10932
  commander: "^14.0.0",
10580
10933
  diff: "^8.0.0",
10581
10934
  fuzzysort: "^3.1.0",
10935
+ ignore: "^7.0.5",
10582
10936
  "jsonc-parser": "3.3.1",
10583
10937
  kleur: "^4.1.5",
10584
10938
  ora: "^8.2.0",
@@ -10920,6 +11274,19 @@ function isContentIdentical(existing, incoming) {
10920
11274
  var isCI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.JENKINS_URL || process.env.BUILDKITE);
10921
11275
  var isTTY = Boolean(process.stdout.isTTY && !isCI);
10922
11276
  var supportsColor = Boolean(isTTY && process.env.FORCE_COLOR !== "0" && process.env.NO_COLOR === undefined);
11277
+ function parseEnvBool(value, defaultValue) {
11278
+ if (value == null || value === "") {
11279
+ return defaultValue;
11280
+ }
11281
+ const normalized = value.trim().toLowerCase();
11282
+ if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on") {
11283
+ return true;
11284
+ }
11285
+ if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "off") {
11286
+ return false;
11287
+ }
11288
+ return defaultValue;
11289
+ }
10923
11290
  // src/utils/git-context.ts
10924
11291
  import { basename, resolve } from "path";
10925
11292
  function getGitEnv() {
@@ -11194,6 +11561,15 @@ function handleError(error, options2 = {}) {
11194
11561
  }
11195
11562
  process.exit(EXIT_CODES.GENERAL);
11196
11563
  }
11564
+ function wrapAction(action) {
11565
+ return async (...args) => {
11566
+ try {
11567
+ await action(...args);
11568
+ } catch (error) {
11569
+ handleError(error);
11570
+ }
11571
+ };
11572
+ }
11197
11573
  function formatErrorAsJson(error) {
11198
11574
  if (error instanceof OCXError) {
11199
11575
  return {
@@ -12714,20 +13090,18 @@ function parseNpmSpecifier(specifier) {
12714
13090
  function isNpmSpecifier(input) {
12715
13091
  return input.trim().startsWith("npm:");
12716
13092
  }
12717
- async function validateNpmPackage(packageName) {
13093
+ async function validateNpmPackage(packageName, signal) {
12718
13094
  validateNpmPackageName(packageName);
12719
13095
  const encodedName = packageName.startsWith("@") ? `@${encodeURIComponent(packageName.slice(1))}` : encodeURIComponent(packageName);
12720
13096
  const url = `${NPM_REGISTRY_BASE}/${encodedName}`;
12721
13097
  try {
12722
- const controller = new AbortController;
12723
- const timeoutId = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT_MS);
13098
+ const fetchSignal = signal ?? AbortSignal.timeout(NPM_FETCH_TIMEOUT_MS);
12724
13099
  const response = await fetch(url, {
12725
- signal: controller.signal,
13100
+ signal: fetchSignal,
12726
13101
  headers: {
12727
13102
  Accept: "application/json"
12728
13103
  }
12729
13104
  });
12730
- clearTimeout(timeoutId);
12731
13105
  if (response.status === 404) {
12732
13106
  throw new NotFoundError(`npm package \`${packageName}\` not found on registry`);
12733
13107
  }
@@ -12740,8 +13114,8 @@ async function validateNpmPackage(packageName) {
12740
13114
  if (error instanceof NotFoundError || error instanceof NetworkError) {
12741
13115
  throw error;
12742
13116
  }
12743
- if (error instanceof Error && error.name === "AbortError") {
12744
- throw new NetworkError(`Request to npm registry timed out after ${NPM_FETCH_TIMEOUT_MS / 1000}s for package \`${packageName}\``);
13117
+ if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) {
13118
+ throw new NetworkError(`Request to npm registry timed out for package \`${packageName}\``);
12745
13119
  }
12746
13120
  const message = error instanceof Error ? error.message : String(error);
12747
13121
  throw new NetworkError(`Failed to fetch npm package \`${packageName}\`: ${message}`);
@@ -12765,8 +13139,8 @@ function validateOpenCodePlugin(packageJson) {
12765
13139
  }
12766
13140
  return { valid: true, warnings };
12767
13141
  }
12768
- async function fetchPackageVersion(packageName, version) {
12769
- const metadata = await validateNpmPackage(packageName);
13142
+ async function fetchPackageVersion(packageName, version, signal) {
13143
+ const metadata = await validateNpmPackage(packageName, signal);
12770
13144
  const resolvedVersion = version ?? metadata["dist-tags"].latest;
12771
13145
  const versionData = metadata.versions[resolvedVersion];
12772
13146
  if (!versionData) {
@@ -14387,317 +14761,1905 @@ async function runGhostInit(options2) {
14387
14761
  }
14388
14762
  }
14389
14763
 
14390
- // src/profile/migrate.ts
14391
- import { chmod, readdir as readdir2, rename as rename2, stat as stat2 } from "fs/promises";
14392
- import { homedir as homedir2 } from "os";
14393
- import path5 from "path";
14394
- function getLegacyConfigDir() {
14395
- const base = process.env.XDG_CONFIG_HOME || path5.join(homedir2(), ".config");
14396
- return path5.join(base, "ocx");
14397
- }
14398
- async function needsMigration() {
14399
- const legacyDir = getLegacyConfigDir();
14400
- const profilesDir = getProfilesDir();
14401
- try {
14402
- await stat2(legacyDir);
14764
+ // src/commands/ghost/opencode.ts
14765
+ import { renameSync, rmSync } from "fs";
14766
+ import { copyFile, mkdir as mkdir5, readdir as readdir5 } from "fs/promises";
14767
+ import path7 from "path";
14768
+ var {Glob: Glob3 } = globalThis.Bun;
14769
+
14770
+ // src/utils/file-sync.ts
14771
+ import { existsSync as existsSync2, lstatSync, mkdirSync, readFileSync, rmdirSync, unlinkSync } from "fs";
14772
+ import { dirname as dirname5, join as join6, relative as relative4 } from "path";
14773
+
14774
+ // ../../node_modules/.bun/chokidar@5.0.0/node_modules/chokidar/index.js
14775
+ import { EventEmitter } from "events";
14776
+ import { stat as statcb, Stats } from "fs";
14777
+ import { readdir as readdir3, stat as stat4 } from "fs/promises";
14778
+ import * as sp2 from "path";
14779
+
14780
+ // ../../node_modules/.bun/readdirp@5.0.0/node_modules/readdirp/index.js
14781
+ import { lstat, readdir as readdir2, realpath, stat as stat2 } from "fs/promises";
14782
+ import { join as pjoin, relative as prelative, resolve as presolve, sep as psep } from "path";
14783
+ import { Readable } from "stream";
14784
+ var EntryTypes = {
14785
+ FILE_TYPE: "files",
14786
+ DIR_TYPE: "directories",
14787
+ FILE_DIR_TYPE: "files_directories",
14788
+ EVERYTHING_TYPE: "all"
14789
+ };
14790
+ var defaultOptions = {
14791
+ root: ".",
14792
+ fileFilter: (_entryInfo) => true,
14793
+ directoryFilter: (_entryInfo) => true,
14794
+ type: EntryTypes.FILE_TYPE,
14795
+ lstat: false,
14796
+ depth: 2147483648,
14797
+ alwaysStat: false,
14798
+ highWaterMark: 4096
14799
+ };
14800
+ Object.freeze(defaultOptions);
14801
+ var RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
14802
+ var NORMAL_FLOW_ERRORS = new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
14803
+ var ALL_TYPES = [
14804
+ EntryTypes.DIR_TYPE,
14805
+ EntryTypes.EVERYTHING_TYPE,
14806
+ EntryTypes.FILE_DIR_TYPE,
14807
+ EntryTypes.FILE_TYPE
14808
+ ];
14809
+ var DIR_TYPES = new Set([
14810
+ EntryTypes.DIR_TYPE,
14811
+ EntryTypes.EVERYTHING_TYPE,
14812
+ EntryTypes.FILE_DIR_TYPE
14813
+ ]);
14814
+ var FILE_TYPES = new Set([
14815
+ EntryTypes.EVERYTHING_TYPE,
14816
+ EntryTypes.FILE_DIR_TYPE,
14817
+ EntryTypes.FILE_TYPE
14818
+ ]);
14819
+ var isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
14820
+ var wantBigintFsStats = process.platform === "win32";
14821
+ var emptyFn = (_entryInfo) => true;
14822
+ var normalizeFilter = (filter) => {
14823
+ if (filter === undefined)
14824
+ return emptyFn;
14825
+ if (typeof filter === "function")
14826
+ return filter;
14827
+ if (typeof filter === "string") {
14828
+ const fl = filter.trim();
14829
+ return (entry) => entry.basename === fl;
14830
+ }
14831
+ if (Array.isArray(filter)) {
14832
+ const trItems = filter.map((item) => item.trim());
14833
+ return (entry) => trItems.some((f) => entry.basename === f);
14834
+ }
14835
+ return emptyFn;
14836
+ };
14837
+
14838
+ class ReaddirpStream extends Readable {
14839
+ parents;
14840
+ reading;
14841
+ parent;
14842
+ _stat;
14843
+ _maxDepth;
14844
+ _wantsDir;
14845
+ _wantsFile;
14846
+ _wantsEverything;
14847
+ _root;
14848
+ _isDirent;
14849
+ _statsProp;
14850
+ _rdOptions;
14851
+ _fileFilter;
14852
+ _directoryFilter;
14853
+ constructor(options2 = {}) {
14854
+ super({
14855
+ objectMode: true,
14856
+ autoDestroy: true,
14857
+ highWaterMark: options2.highWaterMark
14858
+ });
14859
+ const opts = { ...defaultOptions, ...options2 };
14860
+ const { root, type } = opts;
14861
+ this._fileFilter = normalizeFilter(opts.fileFilter);
14862
+ this._directoryFilter = normalizeFilter(opts.directoryFilter);
14863
+ const statMethod = opts.lstat ? lstat : stat2;
14864
+ if (wantBigintFsStats) {
14865
+ this._stat = (path5) => statMethod(path5, { bigint: true });
14866
+ } else {
14867
+ this._stat = statMethod;
14868
+ }
14869
+ this._maxDepth = opts.depth != null && Number.isSafeInteger(opts.depth) ? opts.depth : defaultOptions.depth;
14870
+ this._wantsDir = type ? DIR_TYPES.has(type) : false;
14871
+ this._wantsFile = type ? FILE_TYPES.has(type) : false;
14872
+ this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
14873
+ this._root = presolve(root);
14874
+ this._isDirent = !opts.alwaysStat;
14875
+ this._statsProp = this._isDirent ? "dirent" : "stats";
14876
+ this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
14877
+ this.parents = [this._exploreDir(root, 1)];
14878
+ this.reading = false;
14879
+ this.parent = undefined;
14880
+ }
14881
+ async _read(batch) {
14882
+ if (this.reading)
14883
+ return;
14884
+ this.reading = true;
14403
14885
  try {
14404
- await stat2(profilesDir);
14405
- return false;
14406
- } catch {
14407
- return true;
14886
+ while (!this.destroyed && batch > 0) {
14887
+ const par = this.parent;
14888
+ const fil = par && par.files;
14889
+ if (fil && fil.length > 0) {
14890
+ const { path: path5, depth } = par;
14891
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path5));
14892
+ const awaited = await Promise.all(slice);
14893
+ for (const entry of awaited) {
14894
+ if (!entry)
14895
+ continue;
14896
+ if (this.destroyed)
14897
+ return;
14898
+ const entryType = await this._getEntryType(entry);
14899
+ if (entryType === "directory" && this._directoryFilter(entry)) {
14900
+ if (depth <= this._maxDepth) {
14901
+ this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
14902
+ }
14903
+ if (this._wantsDir) {
14904
+ this.push(entry);
14905
+ batch--;
14906
+ }
14907
+ } else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
14908
+ if (this._wantsFile) {
14909
+ this.push(entry);
14910
+ batch--;
14911
+ }
14912
+ }
14913
+ }
14914
+ } else {
14915
+ const parent = this.parents.pop();
14916
+ if (!parent) {
14917
+ this.push(null);
14918
+ break;
14919
+ }
14920
+ this.parent = await parent;
14921
+ if (this.destroyed)
14922
+ return;
14923
+ }
14924
+ }
14925
+ } catch (error) {
14926
+ this.destroy(error);
14927
+ } finally {
14928
+ this.reading = false;
14408
14929
  }
14409
- } catch {
14410
- return false;
14411
- }
14412
- }
14413
- async function migrate(dryRun = false) {
14414
- const result = {
14415
- success: false,
14416
- migratedFiles: [],
14417
- backupPath: null,
14418
- errors: []
14419
- };
14420
- const legacyDir = getLegacyConfigDir();
14421
- const profilesDir = getProfilesDir();
14422
- try {
14423
- await stat2(legacyDir);
14424
- } catch {
14425
- result.errors.push(`No legacy config found at ${legacyDir}`);
14426
- return result;
14427
- }
14428
- try {
14429
- await stat2(profilesDir);
14430
- result.errors.push(`Profiles directory already exists at ${profilesDir}`);
14431
- return result;
14432
- } catch {}
14433
- const legacyFiles = await readdir2(legacyDir);
14434
- const filesToMigrate = legacyFiles.filter((f) => f === "ghost.jsonc" || f === "opencode.jsonc" || f === "AGENTS.md");
14435
- if (filesToMigrate.length === 0) {
14436
- result.errors.push("No migratable files found in legacy config");
14437
- return result;
14438
- }
14439
- if (dryRun) {
14440
- result.migratedFiles = filesToMigrate.map((f) => path5.join(legacyDir, f));
14441
- result.backupPath = `${legacyDir}.bak`;
14442
- result.success = true;
14443
- return result;
14444
14930
  }
14445
- const manager = ProfileManager.create();
14446
- await manager.initialize();
14447
- const defaultProfileDir = getProfileDir("default");
14448
- for (const file of filesToMigrate) {
14449
- const srcPath = path5.join(legacyDir, file);
14450
- const destPath = path5.join(defaultProfileDir, file);
14451
- await Bun.write(destPath, Bun.file(srcPath));
14452
- await chmod(destPath, 384);
14453
- result.migratedFiles.push(file);
14454
- }
14455
- const backupPath = `${legacyDir}.bak`;
14456
- await rename2(legacyDir, backupPath);
14457
- result.backupPath = backupPath;
14458
- result.success = true;
14459
- return result;
14460
- }
14461
-
14462
- // src/commands/ghost/migrate.ts
14463
- function registerGhostMigrateCommand(parent) {
14464
- parent.command("migrate").description("Migrate from legacy ~/.config/ocx/ to new profiles system").option("--dry-run", "Preview changes without making them").action(async (options2) => {
14931
+ async _exploreDir(path5, depth) {
14932
+ let files;
14465
14933
  try {
14466
- await runMigrate(options2);
14934
+ files = await readdir2(path5, this._rdOptions);
14467
14935
  } catch (error) {
14468
- handleError(error);
14936
+ this._onError(error);
14469
14937
  }
14470
- });
14471
- }
14472
- async function runMigrate(options2) {
14473
- const needsMigrationResult = await needsMigration();
14474
- if (!needsMigrationResult) {
14475
- const legacyDir = getLegacyConfigDir();
14476
- const profilesDir = getProfilesDir();
14477
- console.log("No migration needed.");
14478
- console.log(` Legacy config: ${legacyDir} (not found)`);
14479
- console.log(` Profiles: ${profilesDir}`);
14480
- return;
14938
+ return { files, depth, path: path5 };
14481
14939
  }
14482
- if (options2.dryRun) {
14483
- console.log(`Dry run - no changes will be made.
14484
- `);
14485
- }
14486
- const result = await migrate(options2.dryRun);
14487
- if (!result.success) {
14488
- console.error("Migration failed:");
14489
- for (const error of result.errors) {
14490
- console.error(` - ${error}`);
14491
- }
14492
- process.exit(1);
14493
- }
14494
- if (options2.dryRun) {
14495
- console.log("Migration preview:");
14496
- console.log(`
14497
- Files to migrate to default profile:`);
14498
- for (const file of result.migratedFiles) {
14499
- console.log(` ${file}`);
14500
- }
14501
- console.log(`
14502
- Legacy config will be renamed to:`);
14503
- console.log(` ${result.backupPath}`);
14504
- console.log(`
14505
- Run without --dry-run to perform migration.`);
14506
- } else {
14507
- console.log("Migration complete!");
14508
- console.log(`
14509
- Migrated to default profile:`);
14510
- for (const file of result.migratedFiles) {
14511
- console.log(` ${file}`);
14940
+ async _formatEntry(dirent, path5) {
14941
+ let entry;
14942
+ const basename2 = this._isDirent ? dirent.name : dirent;
14943
+ try {
14944
+ const fullPath = presolve(pjoin(path5, basename2));
14945
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename2 };
14946
+ entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
14947
+ } catch (err) {
14948
+ this._onError(err);
14949
+ return;
14512
14950
  }
14513
- console.log(`
14514
- Legacy config backed up to:`);
14515
- console.log(` ${result.backupPath}`);
14516
- console.log(`
14517
- Profile location:`);
14518
- console.log(` ${getProfileDir("default")}`);
14951
+ return entry;
14519
14952
  }
14520
- }
14521
-
14522
- // src/commands/ghost/opencode.ts
14523
- import { renameSync, rmSync } from "fs";
14524
- import { copyFile as copyFilePromise } from "fs/promises";
14525
- import path8 from "path";
14526
-
14527
- // src/utils/opencode-discovery.ts
14528
- import { exists } from "fs/promises";
14529
- import { dirname as dirname3, join as join4 } from "path";
14530
- var CONFIG_FILES = ["opencode.jsonc", "opencode.json"];
14531
- var RULE_FILES = ["AGENTS.md", "CLAUDE.md", "CONTEXT.md"];
14532
- var CONFIG_DIRS = [".opencode"];
14533
- async function findUp(target, start, stop) {
14534
- let current = start;
14535
- const result = [];
14536
- while (true) {
14537
- const search = join4(current, target);
14538
- if (await exists(search).catch(() => false)) {
14539
- result.push(search);
14953
+ _onError(err) {
14954
+ if (isNormalFlowError(err) && !this.destroyed) {
14955
+ this.emit("warn", err);
14956
+ } else {
14957
+ this.destroy(err);
14540
14958
  }
14541
- if (stop === current)
14542
- break;
14543
- const parent = dirname3(current);
14544
- if (parent === current)
14545
- break;
14546
- current = parent;
14547
14959
  }
14548
- return result;
14549
- }
14550
- async function* up(options2) {
14551
- const { targets, start, stop } = options2;
14552
- let current = start;
14553
- while (true) {
14554
- for (const target of targets) {
14555
- const search = join4(current, target);
14556
- if (await exists(search).catch(() => false)) {
14557
- yield search;
14558
- }
14960
+ async _getEntryType(entry) {
14961
+ if (!entry && this._statsProp in entry) {
14962
+ return "";
14559
14963
  }
14560
- if (stop === current)
14561
- break;
14562
- const parent = dirname3(current);
14563
- if (parent === current)
14564
- break;
14565
- current = parent;
14566
- }
14567
- }
14568
- async function discoverProjectFiles(start, stop) {
14569
- const excluded = new Set;
14570
- for (const file of CONFIG_FILES) {
14571
- const found = await findUp(file, start, stop);
14572
- for (const path6 of found) {
14573
- excluded.add(path6);
14964
+ const stats = entry[this._statsProp];
14965
+ if (stats.isFile())
14966
+ return "file";
14967
+ if (stats.isDirectory())
14968
+ return "directory";
14969
+ if (stats && stats.isSymbolicLink()) {
14970
+ const full = entry.fullPath;
14971
+ try {
14972
+ const entryRealPath = await realpath(full);
14973
+ const entryRealPathStats = await lstat(entryRealPath);
14974
+ if (entryRealPathStats.isFile()) {
14975
+ return "file";
14976
+ }
14977
+ if (entryRealPathStats.isDirectory()) {
14978
+ const len = entryRealPath.length;
14979
+ if (full.startsWith(entryRealPath) && full.substr(len, 1) === psep) {
14980
+ const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
14981
+ recursiveError.code = RECURSIVE_ERROR_CODE;
14982
+ return this._onError(recursiveError);
14983
+ }
14984
+ return "directory";
14985
+ }
14986
+ } catch (error) {
14987
+ this._onError(error);
14988
+ return "";
14989
+ }
14574
14990
  }
14575
14991
  }
14576
- for (const file of RULE_FILES) {
14577
- const found = await findUp(file, start, stop);
14578
- for (const path6 of found) {
14579
- excluded.add(path6);
14580
- }
14992
+ _includeAsFile(entry) {
14993
+ const stats = entry && entry[this._statsProp];
14994
+ return stats && this._wantsEverything && !stats.isDirectory();
14581
14995
  }
14582
- for await (const dir of up({ targets: CONFIG_DIRS, start, stop })) {
14583
- excluded.add(dir);
14996
+ }
14997
+ function readdirp(root, options2 = {}) {
14998
+ let type = options2.entryType || options2.type;
14999
+ if (type === "both")
15000
+ type = EntryTypes.FILE_DIR_TYPE;
15001
+ if (type)
15002
+ options2.type = type;
15003
+ if (!root) {
15004
+ throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
15005
+ } else if (typeof root !== "string") {
15006
+ throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
15007
+ } else if (type && !ALL_TYPES.includes(type)) {
15008
+ throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
14584
15009
  }
14585
- return excluded;
15010
+ options2.root = root;
15011
+ return new ReaddirpStream(options2);
14586
15012
  }
14587
15013
 
14588
- // src/utils/symlink-farm.ts
14589
- import { randomBytes } from "crypto";
14590
- import { mkdir as mkdir4, readdir as readdir3, rename as rename3, rm as rm2, stat as stat3, symlink as symlink2 } from "fs/promises";
14591
- import { tmpdir } from "os";
14592
- import { dirname as dirname4, isAbsolute, join as join5, relative as relative2 } from "path";
14593
-
14594
- // src/utils/pattern-filter.ts
14595
- import path6 from "path";
14596
- var {Glob: Glob2 } = globalThis.Bun;
14597
- function normalizeForMatching(absolutePath, projectRoot) {
14598
- const relativePath = path6.relative(projectRoot, absolutePath);
14599
- return relativePath.split(path6.sep).join("/").replace(/^\.\//, "");
14600
- }
14601
- class PathMatcher {
14602
- includeGlobs;
14603
- excludeGlobs;
14604
- includePatterns;
14605
- constructor(includePatterns = [], excludePatterns = []) {
14606
- this.includePatterns = includePatterns;
14607
- this.includeGlobs = includePatterns.map((p) => ({ pattern: p, glob: new Glob2(p) }));
14608
- this.excludeGlobs = excludePatterns.map((p) => ({ pattern: p, glob: new Glob2(p) }));
14609
- }
14610
- getDisposition(relativePath) {
14611
- if (this.excludeGlobs.some((g) => g.glob.match(relativePath))) {
14612
- return { type: "excluded" };
14613
- }
14614
- if (this.includeGlobs.some((g) => g.glob.match(relativePath))) {
14615
- return { type: "included" };
14616
- }
14617
- const patternsInsideDir = this.includePatterns.filter((pattern) => {
14618
- if (pattern.startsWith(`${relativePath}/`))
14619
- return true;
14620
- if (pattern.startsWith("**/"))
14621
- return true;
14622
- return false;
14623
- });
14624
- if (patternsInsideDir.length > 0) {
14625
- return { type: "partial", patterns: patternsInsideDir };
14626
- }
14627
- if (this.includePatterns.length > 0) {
14628
- return { type: "excluded" };
14629
- }
14630
- return { type: "included" };
14631
- }
14632
- targetsInside(dirPath) {
14633
- const normalizedDir = dirPath.endsWith("/") ? dirPath : `${dirPath}/`;
14634
- return this.includePatterns.some((p) => p.startsWith(normalizedDir));
15014
+ // ../../node_modules/.bun/chokidar@5.0.0/node_modules/chokidar/handler.js
15015
+ import { watch as fs_watch, unwatchFile, watchFile } from "fs";
15016
+ import { realpath as fsrealpath, lstat as lstat2, open, stat as stat3 } from "fs/promises";
15017
+ import { type as osType } from "os";
15018
+ import * as sp from "path";
15019
+ var STR_DATA = "data";
15020
+ var STR_END = "end";
15021
+ var STR_CLOSE = "close";
15022
+ var EMPTY_FN = () => {};
15023
+ var pl = process.platform;
15024
+ var isWindows = pl === "win32";
15025
+ var isMacos = pl === "darwin";
15026
+ var isLinux = pl === "linux";
15027
+ var isFreeBSD = pl === "freebsd";
15028
+ var isIBMi = osType() === "OS400";
15029
+ var EVENTS = {
15030
+ ALL: "all",
15031
+ READY: "ready",
15032
+ ADD: "add",
15033
+ CHANGE: "change",
15034
+ ADD_DIR: "addDir",
15035
+ UNLINK: "unlink",
15036
+ UNLINK_DIR: "unlinkDir",
15037
+ RAW: "raw",
15038
+ ERROR: "error"
15039
+ };
15040
+ var EV = EVENTS;
15041
+ var THROTTLE_MODE_WATCH = "watch";
15042
+ var statMethods = { lstat: lstat2, stat: stat3 };
15043
+ var KEY_LISTENERS = "listeners";
15044
+ var KEY_ERR = "errHandlers";
15045
+ var KEY_RAW = "rawEmitters";
15046
+ var HANDLER_KEYS = [KEY_LISTENERS, KEY_ERR, KEY_RAW];
15047
+ var binaryExtensions = new Set([
15048
+ "3dm",
15049
+ "3ds",
15050
+ "3g2",
15051
+ "3gp",
15052
+ "7z",
15053
+ "a",
15054
+ "aac",
15055
+ "adp",
15056
+ "afdesign",
15057
+ "afphoto",
15058
+ "afpub",
15059
+ "ai",
15060
+ "aif",
15061
+ "aiff",
15062
+ "alz",
15063
+ "ape",
15064
+ "apk",
15065
+ "appimage",
15066
+ "ar",
15067
+ "arj",
15068
+ "asf",
15069
+ "au",
15070
+ "avi",
15071
+ "bak",
15072
+ "baml",
15073
+ "bh",
15074
+ "bin",
15075
+ "bk",
15076
+ "bmp",
15077
+ "btif",
15078
+ "bz2",
15079
+ "bzip2",
15080
+ "cab",
15081
+ "caf",
15082
+ "cgm",
15083
+ "class",
15084
+ "cmx",
15085
+ "cpio",
15086
+ "cr2",
15087
+ "cur",
15088
+ "dat",
15089
+ "dcm",
15090
+ "deb",
15091
+ "dex",
15092
+ "djvu",
15093
+ "dll",
15094
+ "dmg",
15095
+ "dng",
15096
+ "doc",
15097
+ "docm",
15098
+ "docx",
15099
+ "dot",
15100
+ "dotm",
15101
+ "dra",
15102
+ "DS_Store",
15103
+ "dsk",
15104
+ "dts",
15105
+ "dtshd",
15106
+ "dvb",
15107
+ "dwg",
15108
+ "dxf",
15109
+ "ecelp4800",
15110
+ "ecelp7470",
15111
+ "ecelp9600",
15112
+ "egg",
15113
+ "eol",
15114
+ "eot",
15115
+ "epub",
15116
+ "exe",
15117
+ "f4v",
15118
+ "fbs",
15119
+ "fh",
15120
+ "fla",
15121
+ "flac",
15122
+ "flatpak",
15123
+ "fli",
15124
+ "flv",
15125
+ "fpx",
15126
+ "fst",
15127
+ "fvt",
15128
+ "g3",
15129
+ "gh",
15130
+ "gif",
15131
+ "graffle",
15132
+ "gz",
15133
+ "gzip",
15134
+ "h261",
15135
+ "h263",
15136
+ "h264",
15137
+ "icns",
15138
+ "ico",
15139
+ "ief",
15140
+ "img",
15141
+ "ipa",
15142
+ "iso",
15143
+ "jar",
15144
+ "jpeg",
15145
+ "jpg",
15146
+ "jpgv",
15147
+ "jpm",
15148
+ "jxr",
15149
+ "key",
15150
+ "ktx",
15151
+ "lha",
15152
+ "lib",
15153
+ "lvp",
15154
+ "lz",
15155
+ "lzh",
15156
+ "lzma",
15157
+ "lzo",
15158
+ "m3u",
15159
+ "m4a",
15160
+ "m4v",
15161
+ "mar",
15162
+ "mdi",
15163
+ "mht",
15164
+ "mid",
15165
+ "midi",
15166
+ "mj2",
15167
+ "mka",
15168
+ "mkv",
15169
+ "mmr",
15170
+ "mng",
15171
+ "mobi",
15172
+ "mov",
15173
+ "movie",
15174
+ "mp3",
15175
+ "mp4",
15176
+ "mp4a",
15177
+ "mpeg",
15178
+ "mpg",
15179
+ "mpga",
15180
+ "mxu",
15181
+ "nef",
15182
+ "npx",
15183
+ "numbers",
15184
+ "nupkg",
15185
+ "o",
15186
+ "odp",
15187
+ "ods",
15188
+ "odt",
15189
+ "oga",
15190
+ "ogg",
15191
+ "ogv",
15192
+ "otf",
15193
+ "ott",
15194
+ "pages",
15195
+ "pbm",
15196
+ "pcx",
15197
+ "pdb",
15198
+ "pdf",
15199
+ "pea",
15200
+ "pgm",
15201
+ "pic",
15202
+ "png",
15203
+ "pnm",
15204
+ "pot",
15205
+ "potm",
15206
+ "potx",
15207
+ "ppa",
15208
+ "ppam",
15209
+ "ppm",
15210
+ "pps",
15211
+ "ppsm",
15212
+ "ppsx",
15213
+ "ppt",
15214
+ "pptm",
15215
+ "pptx",
15216
+ "psd",
15217
+ "pya",
15218
+ "pyc",
15219
+ "pyo",
15220
+ "pyv",
15221
+ "qt",
15222
+ "rar",
15223
+ "ras",
15224
+ "raw",
15225
+ "resources",
15226
+ "rgb",
15227
+ "rip",
15228
+ "rlc",
15229
+ "rmf",
15230
+ "rmvb",
15231
+ "rpm",
15232
+ "rtf",
15233
+ "rz",
15234
+ "s3m",
15235
+ "s7z",
15236
+ "scpt",
15237
+ "sgi",
15238
+ "shar",
15239
+ "snap",
15240
+ "sil",
15241
+ "sketch",
15242
+ "slk",
15243
+ "smv",
15244
+ "snk",
15245
+ "so",
15246
+ "stl",
15247
+ "suo",
15248
+ "sub",
15249
+ "swf",
15250
+ "tar",
15251
+ "tbz",
15252
+ "tbz2",
15253
+ "tga",
15254
+ "tgz",
15255
+ "thmx",
15256
+ "tif",
15257
+ "tiff",
15258
+ "tlz",
15259
+ "ttc",
15260
+ "ttf",
15261
+ "txz",
15262
+ "udf",
15263
+ "uvh",
15264
+ "uvi",
15265
+ "uvm",
15266
+ "uvp",
15267
+ "uvs",
15268
+ "uvu",
15269
+ "viv",
15270
+ "vob",
15271
+ "war",
15272
+ "wav",
15273
+ "wax",
15274
+ "wbmp",
15275
+ "wdp",
15276
+ "weba",
15277
+ "webm",
15278
+ "webp",
15279
+ "whl",
15280
+ "wim",
15281
+ "wm",
15282
+ "wma",
15283
+ "wmv",
15284
+ "wmx",
15285
+ "woff",
15286
+ "woff2",
15287
+ "wrm",
15288
+ "wvx",
15289
+ "xbm",
15290
+ "xif",
15291
+ "xla",
15292
+ "xlam",
15293
+ "xls",
15294
+ "xlsb",
15295
+ "xlsm",
15296
+ "xlsx",
15297
+ "xlt",
15298
+ "xltm",
15299
+ "xltx",
15300
+ "xm",
15301
+ "xmind",
15302
+ "xpi",
15303
+ "xpm",
15304
+ "xwd",
15305
+ "xz",
15306
+ "z",
15307
+ "zip",
15308
+ "zipx"
15309
+ ]);
15310
+ var isBinaryPath = (filePath) => binaryExtensions.has(sp.extname(filePath).slice(1).toLowerCase());
15311
+ var foreach = (val, fn) => {
15312
+ if (val instanceof Set) {
15313
+ val.forEach(fn);
15314
+ } else {
15315
+ fn(val);
14635
15316
  }
14636
- getInnerPatterns(dirPath) {
14637
- const normalizedDir = dirPath.endsWith("/") ? dirPath : `${dirPath}/`;
14638
- return this.includePatterns.filter((p) => p.startsWith(normalizedDir));
15317
+ };
15318
+ var addAndConvert = (main2, prop, item) => {
15319
+ let container = main2[prop];
15320
+ if (!(container instanceof Set)) {
15321
+ main2[prop] = container = new Set([container]);
14639
15322
  }
14640
- hasIncludePatterns() {
14641
- return this.includePatterns.length > 0;
15323
+ container.add(item);
15324
+ };
15325
+ var clearItem = (cont) => (key) => {
15326
+ const set = cont[key];
15327
+ if (set instanceof Set) {
15328
+ set.clear();
15329
+ } else {
15330
+ delete cont[key];
14642
15331
  }
14643
- }
14644
- function createPathMatcher(includePatterns = [], excludePatterns = []) {
14645
- return new PathMatcher(includePatterns, excludePatterns);
14646
- }
14647
-
14648
- // src/utils/symlink-farm.ts
14649
- var STALE_SESSION_THRESHOLD_MS = 24 * 60 * 60 * 1000;
14650
- var REMOVING_THRESHOLD_MS = 60 * 60 * 1000;
14651
- var GHOST_DIR_PREFIX = "ocx-ghost-";
14652
- var REMOVING_SUFFIX = "-removing";
14653
- var GHOST_MARKER_FILE = ".ocx-ghost-marker";
14654
- async function createSymlinkFarm(sourceDir, excludePaths, options2) {
14655
- if (!isAbsolute(sourceDir)) {
14656
- throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
15332
+ };
15333
+ var delFromSet = (main2, prop, item) => {
15334
+ const container = main2[prop];
15335
+ if (container instanceof Set) {
15336
+ container.delete(item);
15337
+ } else if (container === item) {
15338
+ delete main2[prop];
14657
15339
  }
14658
- const suffix = randomBytes(4).toString("hex");
14659
- const tempDir = join5(tmpdir(), `${GHOST_DIR_PREFIX}${suffix}`);
14660
- await Bun.write(join5(tempDir, GHOST_MARKER_FILE), "");
15340
+ };
15341
+ var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
15342
+ var FsWatchInstances = new Map;
15343
+ function createFsWatchInstance(path5, options2, listener, errHandler, emitRaw) {
15344
+ const handleEvent = (rawEvent, evPath) => {
15345
+ listener(path5);
15346
+ emitRaw(rawEvent, evPath, { watchedPath: path5 });
15347
+ if (evPath && path5 !== evPath) {
15348
+ fsWatchBroadcast(sp.resolve(path5, evPath), KEY_LISTENERS, sp.join(path5, evPath));
15349
+ }
15350
+ };
14661
15351
  try {
14662
- const matcher = createPathMatcher(options2?.includePatterns ?? [], options2?.excludePatterns ?? []);
14663
- const plan = await computeSymlinkPlan(sourceDir, sourceDir, excludePaths, matcher);
14664
- await executeSymlinkPlan(plan, sourceDir, tempDir);
14665
- return tempDir;
15352
+ return fs_watch(path5, {
15353
+ persistent: options2.persistent
15354
+ }, handleEvent);
14666
15355
  } catch (error) {
14667
- await rm2(tempDir, { recursive: true, force: true }).catch(() => {});
14668
- throw error;
15356
+ errHandler(error);
15357
+ return;
14669
15358
  }
14670
15359
  }
14671
- async function injectGhostFiles(tempDir, sourceDir, injectPaths) {
14672
- if (!isAbsolute(tempDir)) {
14673
- throw new Error(`tempDir must be an absolute path, got: ${tempDir}`);
14674
- }
14675
- if (!isAbsolute(sourceDir)) {
14676
- throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
15360
+ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
15361
+ const cont = FsWatchInstances.get(fullPath);
15362
+ if (!cont)
15363
+ return;
15364
+ foreach(cont[listenerType], (listener) => {
15365
+ listener(val1, val2, val3);
15366
+ });
15367
+ };
15368
+ var setFsWatchListener = (path5, fullPath, options2, handlers) => {
15369
+ const { listener, errHandler, rawEmitter } = handlers;
15370
+ let cont = FsWatchInstances.get(fullPath);
15371
+ let watcher;
15372
+ if (!options2.persistent) {
15373
+ watcher = createFsWatchInstance(path5, options2, listener, errHandler, rawEmitter);
15374
+ if (!watcher)
15375
+ return;
15376
+ return watcher.close.bind(watcher);
14677
15377
  }
14678
- for (const injectPath of injectPaths) {
14679
- const relativePath = relative2(sourceDir, injectPath);
14680
- if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
14681
- throw new Error(`injectPath must be within sourceDir: ${injectPath}`);
14682
- }
14683
- const targetPath = join5(tempDir, relativePath);
14684
- const parentDir = dirname4(targetPath);
14685
- if (parentDir !== tempDir) {
14686
- await mkdir4(parentDir, { recursive: true });
14687
- }
14688
- try {
14689
- await symlink2(injectPath, targetPath);
15378
+ if (cont) {
15379
+ addAndConvert(cont, KEY_LISTENERS, listener);
15380
+ addAndConvert(cont, KEY_ERR, errHandler);
15381
+ addAndConvert(cont, KEY_RAW, rawEmitter);
15382
+ } else {
15383
+ watcher = createFsWatchInstance(path5, options2, fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), errHandler, fsWatchBroadcast.bind(null, fullPath, KEY_RAW));
15384
+ if (!watcher)
15385
+ return;
15386
+ watcher.on(EV.ERROR, async (error) => {
15387
+ const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
15388
+ if (cont)
15389
+ cont.watcherUnusable = true;
15390
+ if (isWindows && error.code === "EPERM") {
15391
+ try {
15392
+ const fd = await open(path5, "r");
15393
+ await fd.close();
15394
+ broadcastErr(error);
15395
+ } catch (err) {}
15396
+ } else {
15397
+ broadcastErr(error);
15398
+ }
15399
+ });
15400
+ cont = {
15401
+ listeners: listener,
15402
+ errHandlers: errHandler,
15403
+ rawEmitters: rawEmitter,
15404
+ watcher
15405
+ };
15406
+ FsWatchInstances.set(fullPath, cont);
15407
+ }
15408
+ return () => {
15409
+ delFromSet(cont, KEY_LISTENERS, listener);
15410
+ delFromSet(cont, KEY_ERR, errHandler);
15411
+ delFromSet(cont, KEY_RAW, rawEmitter);
15412
+ if (isEmptySet(cont.listeners)) {
15413
+ cont.watcher.close();
15414
+ FsWatchInstances.delete(fullPath);
15415
+ HANDLER_KEYS.forEach(clearItem(cont));
15416
+ cont.watcher = undefined;
15417
+ Object.freeze(cont);
15418
+ }
15419
+ };
15420
+ };
15421
+ var FsWatchFileInstances = new Map;
15422
+ var setFsWatchFileListener = (path5, fullPath, options2, handlers) => {
15423
+ const { listener, rawEmitter } = handlers;
15424
+ let cont = FsWatchFileInstances.get(fullPath);
15425
+ const copts = cont && cont.options;
15426
+ if (copts && (copts.persistent < options2.persistent || copts.interval > options2.interval)) {
15427
+ unwatchFile(fullPath);
15428
+ cont = undefined;
15429
+ }
15430
+ if (cont) {
15431
+ addAndConvert(cont, KEY_LISTENERS, listener);
15432
+ addAndConvert(cont, KEY_RAW, rawEmitter);
15433
+ } else {
15434
+ cont = {
15435
+ listeners: listener,
15436
+ rawEmitters: rawEmitter,
15437
+ options: options2,
15438
+ watcher: watchFile(fullPath, options2, (curr, prev) => {
15439
+ foreach(cont.rawEmitters, (rawEmitter2) => {
15440
+ rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
15441
+ });
15442
+ const currmtime = curr.mtimeMs;
15443
+ if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
15444
+ foreach(cont.listeners, (listener2) => listener2(path5, curr));
15445
+ }
15446
+ })
15447
+ };
15448
+ FsWatchFileInstances.set(fullPath, cont);
15449
+ }
15450
+ return () => {
15451
+ delFromSet(cont, KEY_LISTENERS, listener);
15452
+ delFromSet(cont, KEY_RAW, rawEmitter);
15453
+ if (isEmptySet(cont.listeners)) {
15454
+ FsWatchFileInstances.delete(fullPath);
15455
+ unwatchFile(fullPath);
15456
+ cont.options = cont.watcher = undefined;
15457
+ Object.freeze(cont);
15458
+ }
15459
+ };
15460
+ };
15461
+
15462
+ class NodeFsHandler {
15463
+ fsw;
15464
+ _boundHandleError;
15465
+ constructor(fsW) {
15466
+ this.fsw = fsW;
15467
+ this._boundHandleError = (error) => fsW._handleError(error);
15468
+ }
15469
+ _watchWithNodeFs(path5, listener) {
15470
+ const opts = this.fsw.options;
15471
+ const directory = sp.dirname(path5);
15472
+ const basename3 = sp.basename(path5);
15473
+ const parent = this.fsw._getWatchedDir(directory);
15474
+ parent.add(basename3);
15475
+ const absolutePath = sp.resolve(path5);
15476
+ const options2 = {
15477
+ persistent: opts.persistent
15478
+ };
15479
+ if (!listener)
15480
+ listener = EMPTY_FN;
15481
+ let closer;
15482
+ if (opts.usePolling) {
15483
+ const enableBin = opts.interval !== opts.binaryInterval;
15484
+ options2.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
15485
+ closer = setFsWatchFileListener(path5, absolutePath, options2, {
15486
+ listener,
15487
+ rawEmitter: this.fsw._emitRaw
15488
+ });
15489
+ } else {
15490
+ closer = setFsWatchListener(path5, absolutePath, options2, {
15491
+ listener,
15492
+ errHandler: this._boundHandleError,
15493
+ rawEmitter: this.fsw._emitRaw
15494
+ });
15495
+ }
15496
+ return closer;
15497
+ }
15498
+ _handleFile(file, stats, initialAdd) {
15499
+ if (this.fsw.closed) {
15500
+ return;
15501
+ }
15502
+ const dirname4 = sp.dirname(file);
15503
+ const basename3 = sp.basename(file);
15504
+ const parent = this.fsw._getWatchedDir(dirname4);
15505
+ let prevStats = stats;
15506
+ if (parent.has(basename3))
15507
+ return;
15508
+ const listener = async (path5, newStats) => {
15509
+ if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
15510
+ return;
15511
+ if (!newStats || newStats.mtimeMs === 0) {
15512
+ try {
15513
+ const newStats2 = await stat3(file);
15514
+ if (this.fsw.closed)
15515
+ return;
15516
+ const at = newStats2.atimeMs;
15517
+ const mt = newStats2.mtimeMs;
15518
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) {
15519
+ this.fsw._emit(EV.CHANGE, file, newStats2);
15520
+ }
15521
+ if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
15522
+ this.fsw._closeFile(path5);
15523
+ prevStats = newStats2;
15524
+ const closer2 = this._watchWithNodeFs(file, listener);
15525
+ if (closer2)
15526
+ this.fsw._addPathCloser(path5, closer2);
15527
+ } else {
15528
+ prevStats = newStats2;
15529
+ }
15530
+ } catch (error) {
15531
+ this.fsw._remove(dirname4, basename3);
15532
+ }
15533
+ } else if (parent.has(basename3)) {
15534
+ const at = newStats.atimeMs;
15535
+ const mt = newStats.mtimeMs;
15536
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) {
15537
+ this.fsw._emit(EV.CHANGE, file, newStats);
15538
+ }
15539
+ prevStats = newStats;
15540
+ }
15541
+ };
15542
+ const closer = this._watchWithNodeFs(file, listener);
15543
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
15544
+ if (!this.fsw._throttle(EV.ADD, file, 0))
15545
+ return;
15546
+ this.fsw._emit(EV.ADD, file, stats);
15547
+ }
15548
+ return closer;
15549
+ }
15550
+ async _handleSymlink(entry, directory, path5, item) {
15551
+ if (this.fsw.closed) {
15552
+ return;
15553
+ }
15554
+ const full = entry.fullPath;
15555
+ const dir = this.fsw._getWatchedDir(directory);
15556
+ if (!this.fsw.options.followSymlinks) {
15557
+ this.fsw._incrReadyCount();
15558
+ let linkPath;
15559
+ try {
15560
+ linkPath = await fsrealpath(path5);
15561
+ } catch (e3) {
15562
+ this.fsw._emitReady();
15563
+ return true;
15564
+ }
15565
+ if (this.fsw.closed)
15566
+ return;
15567
+ if (dir.has(item)) {
15568
+ if (this.fsw._symlinkPaths.get(full) !== linkPath) {
15569
+ this.fsw._symlinkPaths.set(full, linkPath);
15570
+ this.fsw._emit(EV.CHANGE, path5, entry.stats);
15571
+ }
15572
+ } else {
15573
+ dir.add(item);
15574
+ this.fsw._symlinkPaths.set(full, linkPath);
15575
+ this.fsw._emit(EV.ADD, path5, entry.stats);
15576
+ }
15577
+ this.fsw._emitReady();
15578
+ return true;
15579
+ }
15580
+ if (this.fsw._symlinkPaths.has(full)) {
15581
+ return true;
15582
+ }
15583
+ this.fsw._symlinkPaths.set(full, true);
15584
+ }
15585
+ _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
15586
+ directory = sp.join(directory, "");
15587
+ const throttleKey = target ? `${directory}:${target}` : directory;
15588
+ throttler = this.fsw._throttle("readdir", throttleKey, 1000);
15589
+ if (!throttler)
15590
+ return;
15591
+ const previous = this.fsw._getWatchedDir(wh.path);
15592
+ const current = new Set;
15593
+ let stream = this.fsw._readdirp(directory, {
15594
+ fileFilter: (entry) => wh.filterPath(entry),
15595
+ directoryFilter: (entry) => wh.filterDir(entry)
15596
+ });
15597
+ if (!stream)
15598
+ return;
15599
+ stream.on(STR_DATA, async (entry) => {
15600
+ if (this.fsw.closed) {
15601
+ stream = undefined;
15602
+ return;
15603
+ }
15604
+ const item = entry.path;
15605
+ let path5 = sp.join(directory, item);
15606
+ current.add(item);
15607
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path5, item)) {
15608
+ return;
15609
+ }
15610
+ if (this.fsw.closed) {
15611
+ stream = undefined;
15612
+ return;
15613
+ }
15614
+ if (item === target || !target && !previous.has(item)) {
15615
+ this.fsw._incrReadyCount();
15616
+ path5 = sp.join(dir, sp.relative(dir, path5));
15617
+ this._addToNodeFs(path5, initialAdd, wh, depth + 1);
15618
+ }
15619
+ }).on(EV.ERROR, this._boundHandleError);
15620
+ return new Promise((resolve3, reject) => {
15621
+ if (!stream)
15622
+ return reject();
15623
+ stream.once(STR_END, () => {
15624
+ if (this.fsw.closed) {
15625
+ stream = undefined;
15626
+ return;
15627
+ }
15628
+ const wasThrottled = throttler ? throttler.clear() : false;
15629
+ resolve3(undefined);
15630
+ previous.getChildren().filter((item) => {
15631
+ return item !== directory && !current.has(item);
15632
+ }).forEach((item) => {
15633
+ this.fsw._remove(directory, item);
15634
+ });
15635
+ stream = undefined;
15636
+ if (wasThrottled)
15637
+ this._handleRead(directory, false, wh, target, dir, depth, throttler);
15638
+ });
15639
+ });
15640
+ }
15641
+ async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath2) {
15642
+ const parentDir = this.fsw._getWatchedDir(sp.dirname(dir));
15643
+ const tracked = parentDir.has(sp.basename(dir));
15644
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
15645
+ this.fsw._emit(EV.ADD_DIR, dir, stats);
15646
+ }
15647
+ parentDir.add(sp.basename(dir));
15648
+ this.fsw._getWatchedDir(dir);
15649
+ let throttler;
15650
+ let closer;
15651
+ const oDepth = this.fsw.options.depth;
15652
+ if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath2)) {
15653
+ if (!target) {
15654
+ await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
15655
+ if (this.fsw.closed)
15656
+ return;
15657
+ }
15658
+ closer = this._watchWithNodeFs(dir, (dirPath, stats2) => {
15659
+ if (stats2 && stats2.mtimeMs === 0)
15660
+ return;
15661
+ this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
15662
+ });
15663
+ }
15664
+ return closer;
15665
+ }
15666
+ async _addToNodeFs(path5, initialAdd, priorWh, depth, target) {
15667
+ const ready = this.fsw._emitReady;
15668
+ if (this.fsw._isIgnored(path5) || this.fsw.closed) {
15669
+ ready();
15670
+ return false;
15671
+ }
15672
+ const wh = this.fsw._getWatchHelpers(path5);
15673
+ if (priorWh) {
15674
+ wh.filterPath = (entry) => priorWh.filterPath(entry);
15675
+ wh.filterDir = (entry) => priorWh.filterDir(entry);
15676
+ }
15677
+ try {
15678
+ const stats = await statMethods[wh.statMethod](wh.watchPath);
15679
+ if (this.fsw.closed)
15680
+ return;
15681
+ if (this.fsw._isIgnored(wh.watchPath, stats)) {
15682
+ ready();
15683
+ return false;
15684
+ }
15685
+ const follow = this.fsw.options.followSymlinks;
15686
+ let closer;
15687
+ if (stats.isDirectory()) {
15688
+ const absPath = sp.resolve(path5);
15689
+ const targetPath = follow ? await fsrealpath(path5) : path5;
15690
+ if (this.fsw.closed)
15691
+ return;
15692
+ closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
15693
+ if (this.fsw.closed)
15694
+ return;
15695
+ if (absPath !== targetPath && targetPath !== undefined) {
15696
+ this.fsw._symlinkPaths.set(absPath, targetPath);
15697
+ }
15698
+ } else if (stats.isSymbolicLink()) {
15699
+ const targetPath = follow ? await fsrealpath(path5) : path5;
15700
+ if (this.fsw.closed)
15701
+ return;
15702
+ const parent = sp.dirname(wh.watchPath);
15703
+ this.fsw._getWatchedDir(parent).add(wh.watchPath);
15704
+ this.fsw._emit(EV.ADD, wh.watchPath, stats);
15705
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path5, wh, targetPath);
15706
+ if (this.fsw.closed)
15707
+ return;
15708
+ if (targetPath !== undefined) {
15709
+ this.fsw._symlinkPaths.set(sp.resolve(path5), targetPath);
15710
+ }
15711
+ } else {
15712
+ closer = this._handleFile(wh.watchPath, stats, initialAdd);
15713
+ }
15714
+ ready();
15715
+ if (closer)
15716
+ this.fsw._addPathCloser(path5, closer);
15717
+ return false;
15718
+ } catch (error) {
15719
+ if (this.fsw._handleError(error)) {
15720
+ ready();
15721
+ return path5;
15722
+ }
15723
+ }
15724
+ }
15725
+ }
15726
+
15727
+ // ../../node_modules/.bun/chokidar@5.0.0/node_modules/chokidar/index.js
15728
+ /*! chokidar - MIT License (c) 2012 Paul Miller (paulmillr.com) */
15729
+ var SLASH = "/";
15730
+ var SLASH_SLASH = "//";
15731
+ var ONE_DOT = ".";
15732
+ var TWO_DOTS = "..";
15733
+ var STRING_TYPE = "string";
15734
+ var BACK_SLASH_RE = /\\/g;
15735
+ var DOUBLE_SLASH_RE = /\/\//g;
15736
+ var DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
15737
+ var REPLACER_RE = /^\.[/\\]/;
15738
+ function arrify(item) {
15739
+ return Array.isArray(item) ? item : [item];
15740
+ }
15741
+ var isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
15742
+ function createPattern(matcher) {
15743
+ if (typeof matcher === "function")
15744
+ return matcher;
15745
+ if (typeof matcher === "string")
15746
+ return (string) => matcher === string;
15747
+ if (matcher instanceof RegExp)
15748
+ return (string) => matcher.test(string);
15749
+ if (typeof matcher === "object" && matcher !== null) {
15750
+ return (string) => {
15751
+ if (matcher.path === string)
15752
+ return true;
15753
+ if (matcher.recursive) {
15754
+ const relative4 = sp2.relative(matcher.path, string);
15755
+ if (!relative4) {
15756
+ return false;
15757
+ }
15758
+ return !relative4.startsWith("..") && !sp2.isAbsolute(relative4);
15759
+ }
15760
+ return false;
15761
+ };
15762
+ }
15763
+ return () => false;
15764
+ }
15765
+ function normalizePath(path5) {
15766
+ if (typeof path5 !== "string")
15767
+ throw new Error("string expected");
15768
+ path5 = sp2.normalize(path5);
15769
+ path5 = path5.replace(/\\/g, "/");
15770
+ let prepend = false;
15771
+ if (path5.startsWith("//"))
15772
+ prepend = true;
15773
+ path5 = path5.replace(DOUBLE_SLASH_RE, "/");
15774
+ if (prepend)
15775
+ path5 = "/" + path5;
15776
+ return path5;
15777
+ }
15778
+ function matchPatterns(patterns, testString, stats) {
15779
+ const path5 = normalizePath(testString);
15780
+ for (let index = 0;index < patterns.length; index++) {
15781
+ const pattern = patterns[index];
15782
+ if (pattern(path5, stats)) {
15783
+ return true;
15784
+ }
15785
+ }
15786
+ return false;
15787
+ }
15788
+ function anymatch(matchers, testString) {
15789
+ if (matchers == null) {
15790
+ throw new TypeError("anymatch: specify first argument");
15791
+ }
15792
+ const matchersArray = arrify(matchers);
15793
+ const patterns = matchersArray.map((matcher) => createPattern(matcher));
15794
+ if (testString == null) {
15795
+ return (testString2, stats) => {
15796
+ return matchPatterns(patterns, testString2, stats);
15797
+ };
15798
+ }
15799
+ return matchPatterns(patterns, testString);
15800
+ }
15801
+ var unifyPaths = (paths_) => {
15802
+ const paths = arrify(paths_).flat();
15803
+ if (!paths.every((p) => typeof p === STRING_TYPE)) {
15804
+ throw new TypeError(`Non-string provided as watch path: ${paths}`);
15805
+ }
15806
+ return paths.map(normalizePathToUnix);
15807
+ };
15808
+ var toUnix = (string) => {
15809
+ let str = string.replace(BACK_SLASH_RE, SLASH);
15810
+ let prepend = false;
15811
+ if (str.startsWith(SLASH_SLASH)) {
15812
+ prepend = true;
15813
+ }
15814
+ str = str.replace(DOUBLE_SLASH_RE, SLASH);
15815
+ if (prepend) {
15816
+ str = SLASH + str;
15817
+ }
15818
+ return str;
15819
+ };
15820
+ var normalizePathToUnix = (path5) => toUnix(sp2.normalize(toUnix(path5)));
15821
+ var normalizeIgnored = (cwd = "") => (path5) => {
15822
+ if (typeof path5 === "string") {
15823
+ return normalizePathToUnix(sp2.isAbsolute(path5) ? path5 : sp2.join(cwd, path5));
15824
+ } else {
15825
+ return path5;
15826
+ }
15827
+ };
15828
+ var getAbsolutePath = (path5, cwd) => {
15829
+ if (sp2.isAbsolute(path5)) {
15830
+ return path5;
15831
+ }
15832
+ return sp2.join(cwd, path5);
15833
+ };
15834
+ var EMPTY_SET = Object.freeze(new Set);
15835
+
15836
+ class DirEntry {
15837
+ path;
15838
+ _removeWatcher;
15839
+ items;
15840
+ constructor(dir, removeWatcher) {
15841
+ this.path = dir;
15842
+ this._removeWatcher = removeWatcher;
15843
+ this.items = new Set;
15844
+ }
15845
+ add(item) {
15846
+ const { items } = this;
15847
+ if (!items)
15848
+ return;
15849
+ if (item !== ONE_DOT && item !== TWO_DOTS)
15850
+ items.add(item);
15851
+ }
15852
+ async remove(item) {
15853
+ const { items } = this;
15854
+ if (!items)
15855
+ return;
15856
+ items.delete(item);
15857
+ if (items.size > 0)
15858
+ return;
15859
+ const dir = this.path;
15860
+ try {
15861
+ await readdir3(dir);
15862
+ } catch (err) {
15863
+ if (this._removeWatcher) {
15864
+ this._removeWatcher(sp2.dirname(dir), sp2.basename(dir));
15865
+ }
15866
+ }
15867
+ }
15868
+ has(item) {
15869
+ const { items } = this;
15870
+ if (!items)
15871
+ return;
15872
+ return items.has(item);
15873
+ }
15874
+ getChildren() {
15875
+ const { items } = this;
15876
+ if (!items)
15877
+ return [];
15878
+ return [...items.values()];
15879
+ }
15880
+ dispose() {
15881
+ this.items.clear();
15882
+ this.path = "";
15883
+ this._removeWatcher = EMPTY_FN;
15884
+ this.items = EMPTY_SET;
15885
+ Object.freeze(this);
15886
+ }
15887
+ }
15888
+ var STAT_METHOD_F = "stat";
15889
+ var STAT_METHOD_L = "lstat";
15890
+
15891
+ class WatchHelper {
15892
+ fsw;
15893
+ path;
15894
+ watchPath;
15895
+ fullWatchPath;
15896
+ dirParts;
15897
+ followSymlinks;
15898
+ statMethod;
15899
+ constructor(path5, follow, fsw) {
15900
+ this.fsw = fsw;
15901
+ const watchPath = path5;
15902
+ this.path = path5 = path5.replace(REPLACER_RE, "");
15903
+ this.watchPath = watchPath;
15904
+ this.fullWatchPath = sp2.resolve(watchPath);
15905
+ this.dirParts = [];
15906
+ this.dirParts.forEach((parts) => {
15907
+ if (parts.length > 1)
15908
+ parts.pop();
15909
+ });
15910
+ this.followSymlinks = follow;
15911
+ this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
15912
+ }
15913
+ entryPath(entry) {
15914
+ return sp2.join(this.watchPath, sp2.relative(this.watchPath, entry.fullPath));
15915
+ }
15916
+ filterPath(entry) {
15917
+ const { stats } = entry;
15918
+ if (stats && stats.isSymbolicLink())
15919
+ return this.filterDir(entry);
15920
+ const resolvedPath = this.entryPath(entry);
15921
+ return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
15922
+ }
15923
+ filterDir(entry) {
15924
+ return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
15925
+ }
15926
+ }
15927
+
15928
+ class FSWatcher extends EventEmitter {
15929
+ closed;
15930
+ options;
15931
+ _closers;
15932
+ _ignoredPaths;
15933
+ _throttled;
15934
+ _streams;
15935
+ _symlinkPaths;
15936
+ _watched;
15937
+ _pendingWrites;
15938
+ _pendingUnlinks;
15939
+ _readyCount;
15940
+ _emitReady;
15941
+ _closePromise;
15942
+ _userIgnored;
15943
+ _readyEmitted;
15944
+ _emitRaw;
15945
+ _boundRemove;
15946
+ _nodeFsHandler;
15947
+ constructor(_opts = {}) {
15948
+ super();
15949
+ this.closed = false;
15950
+ this._closers = new Map;
15951
+ this._ignoredPaths = new Set;
15952
+ this._throttled = new Map;
15953
+ this._streams = new Set;
15954
+ this._symlinkPaths = new Map;
15955
+ this._watched = new Map;
15956
+ this._pendingWrites = new Map;
15957
+ this._pendingUnlinks = new Map;
15958
+ this._readyCount = 0;
15959
+ this._readyEmitted = false;
15960
+ const awf = _opts.awaitWriteFinish;
15961
+ const DEF_AWF = { stabilityThreshold: 2000, pollInterval: 100 };
15962
+ const opts = {
15963
+ persistent: true,
15964
+ ignoreInitial: false,
15965
+ ignorePermissionErrors: false,
15966
+ interval: 100,
15967
+ binaryInterval: 300,
15968
+ followSymlinks: true,
15969
+ usePolling: false,
15970
+ atomic: true,
15971
+ ..._opts,
15972
+ ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
15973
+ awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? { ...DEF_AWF, ...awf } : false
15974
+ };
15975
+ if (isIBMi)
15976
+ opts.usePolling = true;
15977
+ if (opts.atomic === undefined)
15978
+ opts.atomic = !opts.usePolling;
15979
+ const envPoll = process.env.CHOKIDAR_USEPOLLING;
15980
+ if (envPoll !== undefined) {
15981
+ const envLower = envPoll.toLowerCase();
15982
+ if (envLower === "false" || envLower === "0")
15983
+ opts.usePolling = false;
15984
+ else if (envLower === "true" || envLower === "1")
15985
+ opts.usePolling = true;
15986
+ else
15987
+ opts.usePolling = !!envLower;
15988
+ }
15989
+ const envInterval = process.env.CHOKIDAR_INTERVAL;
15990
+ if (envInterval)
15991
+ opts.interval = Number.parseInt(envInterval, 10);
15992
+ let readyCalls = 0;
15993
+ this._emitReady = () => {
15994
+ readyCalls++;
15995
+ if (readyCalls >= this._readyCount) {
15996
+ this._emitReady = EMPTY_FN;
15997
+ this._readyEmitted = true;
15998
+ process.nextTick(() => this.emit(EVENTS.READY));
15999
+ }
16000
+ };
16001
+ this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
16002
+ this._boundRemove = this._remove.bind(this);
16003
+ this.options = opts;
16004
+ this._nodeFsHandler = new NodeFsHandler(this);
16005
+ Object.freeze(opts);
16006
+ }
16007
+ _addIgnoredPath(matcher) {
16008
+ if (isMatcherObject(matcher)) {
16009
+ for (const ignored of this._ignoredPaths) {
16010
+ if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) {
16011
+ return;
16012
+ }
16013
+ }
16014
+ }
16015
+ this._ignoredPaths.add(matcher);
16016
+ }
16017
+ _removeIgnoredPath(matcher) {
16018
+ this._ignoredPaths.delete(matcher);
16019
+ if (typeof matcher === "string") {
16020
+ for (const ignored of this._ignoredPaths) {
16021
+ if (isMatcherObject(ignored) && ignored.path === matcher) {
16022
+ this._ignoredPaths.delete(ignored);
16023
+ }
16024
+ }
16025
+ }
16026
+ }
16027
+ add(paths_, _origAdd, _internal) {
16028
+ const { cwd } = this.options;
16029
+ this.closed = false;
16030
+ this._closePromise = undefined;
16031
+ let paths = unifyPaths(paths_);
16032
+ if (cwd) {
16033
+ paths = paths.map((path5) => {
16034
+ const absPath = getAbsolutePath(path5, cwd);
16035
+ return absPath;
16036
+ });
16037
+ }
16038
+ paths.forEach((path5) => {
16039
+ this._removeIgnoredPath(path5);
16040
+ });
16041
+ this._userIgnored = undefined;
16042
+ if (!this._readyCount)
16043
+ this._readyCount = 0;
16044
+ this._readyCount += paths.length;
16045
+ Promise.all(paths.map(async (path5) => {
16046
+ const res = await this._nodeFsHandler._addToNodeFs(path5, !_internal, undefined, 0, _origAdd);
16047
+ if (res)
16048
+ this._emitReady();
16049
+ return res;
16050
+ })).then((results) => {
16051
+ if (this.closed)
16052
+ return;
16053
+ results.forEach((item) => {
16054
+ if (item)
16055
+ this.add(sp2.dirname(item), sp2.basename(_origAdd || item));
16056
+ });
16057
+ });
16058
+ return this;
16059
+ }
16060
+ unwatch(paths_) {
16061
+ if (this.closed)
16062
+ return this;
16063
+ const paths = unifyPaths(paths_);
16064
+ const { cwd } = this.options;
16065
+ paths.forEach((path5) => {
16066
+ if (!sp2.isAbsolute(path5) && !this._closers.has(path5)) {
16067
+ if (cwd)
16068
+ path5 = sp2.join(cwd, path5);
16069
+ path5 = sp2.resolve(path5);
16070
+ }
16071
+ this._closePath(path5);
16072
+ this._addIgnoredPath(path5);
16073
+ if (this._watched.has(path5)) {
16074
+ this._addIgnoredPath({
16075
+ path: path5,
16076
+ recursive: true
16077
+ });
16078
+ }
16079
+ this._userIgnored = undefined;
16080
+ });
16081
+ return this;
16082
+ }
16083
+ close() {
16084
+ if (this._closePromise) {
16085
+ return this._closePromise;
16086
+ }
16087
+ this.closed = true;
16088
+ this.removeAllListeners();
16089
+ const closers = [];
16090
+ this._closers.forEach((closerList) => closerList.forEach((closer) => {
16091
+ const promise = closer();
16092
+ if (promise instanceof Promise)
16093
+ closers.push(promise);
16094
+ }));
16095
+ this._streams.forEach((stream) => stream.destroy());
16096
+ this._userIgnored = undefined;
16097
+ this._readyCount = 0;
16098
+ this._readyEmitted = false;
16099
+ this._watched.forEach((dirent) => dirent.dispose());
16100
+ this._closers.clear();
16101
+ this._watched.clear();
16102
+ this._streams.clear();
16103
+ this._symlinkPaths.clear();
16104
+ this._throttled.clear();
16105
+ this._closePromise = closers.length ? Promise.all(closers).then(() => {
16106
+ return;
16107
+ }) : Promise.resolve();
16108
+ return this._closePromise;
16109
+ }
16110
+ getWatched() {
16111
+ const watchList = {};
16112
+ this._watched.forEach((entry, dir) => {
16113
+ const key = this.options.cwd ? sp2.relative(this.options.cwd, dir) : dir;
16114
+ const index = key || ONE_DOT;
16115
+ watchList[index] = entry.getChildren().sort();
16116
+ });
16117
+ return watchList;
16118
+ }
16119
+ emitWithAll(event, args) {
16120
+ this.emit(event, ...args);
16121
+ if (event !== EVENTS.ERROR)
16122
+ this.emit(EVENTS.ALL, event, ...args);
16123
+ }
16124
+ async _emit(event, path5, stats) {
16125
+ if (this.closed)
16126
+ return;
16127
+ const opts = this.options;
16128
+ if (isWindows)
16129
+ path5 = sp2.normalize(path5);
16130
+ if (opts.cwd)
16131
+ path5 = sp2.relative(opts.cwd, path5);
16132
+ const args = [path5];
16133
+ if (stats != null)
16134
+ args.push(stats);
16135
+ const awf = opts.awaitWriteFinish;
16136
+ let pw;
16137
+ if (awf && (pw = this._pendingWrites.get(path5))) {
16138
+ pw.lastChange = new Date;
16139
+ return this;
16140
+ }
16141
+ if (opts.atomic) {
16142
+ if (event === EVENTS.UNLINK) {
16143
+ this._pendingUnlinks.set(path5, [event, ...args]);
16144
+ setTimeout(() => {
16145
+ this._pendingUnlinks.forEach((entry, path6) => {
16146
+ this.emit(...entry);
16147
+ this.emit(EVENTS.ALL, ...entry);
16148
+ this._pendingUnlinks.delete(path6);
16149
+ });
16150
+ }, typeof opts.atomic === "number" ? opts.atomic : 100);
16151
+ return this;
16152
+ }
16153
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path5)) {
16154
+ event = EVENTS.CHANGE;
16155
+ this._pendingUnlinks.delete(path5);
16156
+ }
16157
+ }
16158
+ if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
16159
+ const awfEmit = (err, stats2) => {
16160
+ if (err) {
16161
+ event = EVENTS.ERROR;
16162
+ args[0] = err;
16163
+ this.emitWithAll(event, args);
16164
+ } else if (stats2) {
16165
+ if (args.length > 1) {
16166
+ args[1] = stats2;
16167
+ } else {
16168
+ args.push(stats2);
16169
+ }
16170
+ this.emitWithAll(event, args);
16171
+ }
16172
+ };
16173
+ this._awaitWriteFinish(path5, awf.stabilityThreshold, event, awfEmit);
16174
+ return this;
16175
+ }
16176
+ if (event === EVENTS.CHANGE) {
16177
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path5, 50);
16178
+ if (isThrottled)
16179
+ return this;
16180
+ }
16181
+ if (opts.alwaysStat && stats === undefined && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
16182
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path5) : path5;
16183
+ let stats2;
16184
+ try {
16185
+ stats2 = await stat4(fullPath);
16186
+ } catch (err) {}
16187
+ if (!stats2 || this.closed)
16188
+ return;
16189
+ args.push(stats2);
16190
+ }
16191
+ this.emitWithAll(event, args);
16192
+ return this;
16193
+ }
16194
+ _handleError(error) {
16195
+ const code = error && error.code;
16196
+ if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) {
16197
+ this.emit(EVENTS.ERROR, error);
16198
+ }
16199
+ return error || this.closed;
16200
+ }
16201
+ _throttle(actionType, path5, timeout) {
16202
+ if (!this._throttled.has(actionType)) {
16203
+ this._throttled.set(actionType, new Map);
16204
+ }
16205
+ const action = this._throttled.get(actionType);
16206
+ if (!action)
16207
+ throw new Error("invalid throttle");
16208
+ const actionPath = action.get(path5);
16209
+ if (actionPath) {
16210
+ actionPath.count++;
16211
+ return false;
16212
+ }
16213
+ let timeoutObject;
16214
+ const clear = () => {
16215
+ const item = action.get(path5);
16216
+ const count = item ? item.count : 0;
16217
+ action.delete(path5);
16218
+ clearTimeout(timeoutObject);
16219
+ if (item)
16220
+ clearTimeout(item.timeoutObject);
16221
+ return count;
16222
+ };
16223
+ timeoutObject = setTimeout(clear, timeout);
16224
+ const thr = { timeoutObject, clear, count: 0 };
16225
+ action.set(path5, thr);
16226
+ return thr;
16227
+ }
16228
+ _incrReadyCount() {
16229
+ return this._readyCount++;
16230
+ }
16231
+ _awaitWriteFinish(path5, threshold, event, awfEmit) {
16232
+ const awf = this.options.awaitWriteFinish;
16233
+ if (typeof awf !== "object")
16234
+ return;
16235
+ const pollInterval = awf.pollInterval;
16236
+ let timeoutHandler;
16237
+ let fullPath = path5;
16238
+ if (this.options.cwd && !sp2.isAbsolute(path5)) {
16239
+ fullPath = sp2.join(this.options.cwd, path5);
16240
+ }
16241
+ const now = new Date;
16242
+ const writes = this._pendingWrites;
16243
+ function awaitWriteFinishFn(prevStat) {
16244
+ statcb(fullPath, (err, curStat) => {
16245
+ if (err || !writes.has(path5)) {
16246
+ if (err && err.code !== "ENOENT")
16247
+ awfEmit(err);
16248
+ return;
16249
+ }
16250
+ const now2 = Number(new Date);
16251
+ if (prevStat && curStat.size !== prevStat.size) {
16252
+ writes.get(path5).lastChange = now2;
16253
+ }
16254
+ const pw = writes.get(path5);
16255
+ const df = now2 - pw.lastChange;
16256
+ if (df >= threshold) {
16257
+ writes.delete(path5);
16258
+ awfEmit(undefined, curStat);
16259
+ } else {
16260
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
16261
+ }
16262
+ });
16263
+ }
16264
+ if (!writes.has(path5)) {
16265
+ writes.set(path5, {
16266
+ lastChange: now,
16267
+ cancelWait: () => {
16268
+ writes.delete(path5);
16269
+ clearTimeout(timeoutHandler);
16270
+ return event;
16271
+ }
16272
+ });
16273
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
16274
+ }
16275
+ }
16276
+ _isIgnored(path5, stats) {
16277
+ if (this.options.atomic && DOT_RE.test(path5))
16278
+ return true;
16279
+ if (!this._userIgnored) {
16280
+ const { cwd } = this.options;
16281
+ const ign = this.options.ignored;
16282
+ const ignored = (ign || []).map(normalizeIgnored(cwd));
16283
+ const ignoredPaths = [...this._ignoredPaths];
16284
+ const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
16285
+ this._userIgnored = anymatch(list, undefined);
16286
+ }
16287
+ return this._userIgnored(path5, stats);
16288
+ }
16289
+ _isntIgnored(path5, stat5) {
16290
+ return !this._isIgnored(path5, stat5);
16291
+ }
16292
+ _getWatchHelpers(path5) {
16293
+ return new WatchHelper(path5, this.options.followSymlinks, this);
16294
+ }
16295
+ _getWatchedDir(directory) {
16296
+ const dir = sp2.resolve(directory);
16297
+ if (!this._watched.has(dir))
16298
+ this._watched.set(dir, new DirEntry(dir, this._boundRemove));
16299
+ return this._watched.get(dir);
16300
+ }
16301
+ _hasReadPermissions(stats) {
16302
+ if (this.options.ignorePermissionErrors)
16303
+ return true;
16304
+ return Boolean(Number(stats.mode) & 256);
16305
+ }
16306
+ _remove(directory, item, isDirectory) {
16307
+ const path5 = sp2.join(directory, item);
16308
+ const fullPath = sp2.resolve(path5);
16309
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path5) || this._watched.has(fullPath);
16310
+ if (!this._throttle("remove", path5, 100))
16311
+ return;
16312
+ if (!isDirectory && this._watched.size === 1) {
16313
+ this.add(directory, item, true);
16314
+ }
16315
+ const wp = this._getWatchedDir(path5);
16316
+ const nestedDirectoryChildren = wp.getChildren();
16317
+ nestedDirectoryChildren.forEach((nested) => this._remove(path5, nested));
16318
+ const parent = this._getWatchedDir(directory);
16319
+ const wasTracked = parent.has(item);
16320
+ parent.remove(item);
16321
+ if (this._symlinkPaths.has(fullPath)) {
16322
+ this._symlinkPaths.delete(fullPath);
16323
+ }
16324
+ let relPath = path5;
16325
+ if (this.options.cwd)
16326
+ relPath = sp2.relative(this.options.cwd, path5);
16327
+ if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
16328
+ const event = this._pendingWrites.get(relPath).cancelWait();
16329
+ if (event === EVENTS.ADD)
16330
+ return;
16331
+ }
16332
+ this._watched.delete(path5);
16333
+ this._watched.delete(fullPath);
16334
+ const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
16335
+ if (wasTracked && !this._isIgnored(path5))
16336
+ this._emit(eventName, path5);
16337
+ this._closePath(path5);
16338
+ }
16339
+ _closePath(path5) {
16340
+ this._closeFile(path5);
16341
+ const dir = sp2.dirname(path5);
16342
+ this._getWatchedDir(dir).remove(sp2.basename(path5));
16343
+ }
16344
+ _closeFile(path5) {
16345
+ const closers = this._closers.get(path5);
16346
+ if (!closers)
16347
+ return;
16348
+ closers.forEach((closer) => closer());
16349
+ this._closers.delete(path5);
16350
+ }
16351
+ _addPathCloser(path5, closer) {
16352
+ if (!closer)
16353
+ return;
16354
+ let list = this._closers.get(path5);
16355
+ if (!list) {
16356
+ list = [];
16357
+ this._closers.set(path5, list);
16358
+ }
16359
+ list.push(closer);
16360
+ }
16361
+ _readdirp(root, opts) {
16362
+ if (this.closed)
16363
+ return;
16364
+ const options2 = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
16365
+ let stream = readdirp(root, options2);
16366
+ this._streams.add(stream);
16367
+ stream.once(STR_CLOSE, () => {
16368
+ stream = undefined;
16369
+ });
16370
+ stream.once(STR_END, () => {
16371
+ if (stream) {
16372
+ this._streams.delete(stream);
16373
+ stream = undefined;
16374
+ }
16375
+ });
16376
+ return stream;
16377
+ }
16378
+ }
16379
+ function watch(paths, options2 = {}) {
16380
+ const watcher = new FSWatcher(options2);
16381
+ watcher.add(paths);
16382
+ return watcher;
16383
+ }
16384
+ var chokidar_default = { watch, FSWatcher };
16385
+
16386
+ // src/utils/file-sync.ts
16387
+ var import_ignore = __toESM(require_ignore(), 1);
16388
+ var OS_JUNK = [/\.DS_Store$/, /Thumbs\.db$/];
16389
+ function loadGitignore(projectDir) {
16390
+ const ig = import_ignore.default();
16391
+ const gitignorePath = join6(projectDir, ".gitignore");
16392
+ try {
16393
+ if (existsSync2(gitignorePath)) {
16394
+ ig.add(readFileSync(gitignorePath, "utf8"));
16395
+ }
16396
+ } catch (err) {
16397
+ const code = err.code;
16398
+ if (code !== "ENOENT") {
16399
+ console.warn(`Warning: Could not read .gitignore: ${err.message}`);
16400
+ }
16401
+ }
16402
+ return ig;
16403
+ }
16404
+ function normalizePath2(p) {
16405
+ return p.replace(/\\/g, "/").replace(/\/$/, "");
16406
+ }
16407
+ function isSymlink(filePath) {
16408
+ try {
16409
+ return lstatSync(filePath).isSymbolicLink();
16410
+ } catch (err) {
16411
+ if (err.code === "ENOENT")
16412
+ return false;
16413
+ throw err;
16414
+ }
16415
+ }
16416
+ function isExcluded(relativePath, gitignore) {
16417
+ if (!relativePath)
16418
+ return false;
16419
+ if (OS_JUNK.some((p) => p.test(relativePath)))
16420
+ return true;
16421
+ if (gitignore.ignores(relativePath))
16422
+ return true;
16423
+ return false;
16424
+ }
16425
+ async function syncFile(src, dest) {
16426
+ mkdirSync(dirname5(dest), { recursive: true });
16427
+ await Bun.write(dest, Bun.file(src));
16428
+ }
16429
+ function syncDelete(dest) {
16430
+ try {
16431
+ unlinkSync(dest);
16432
+ } catch (err) {
16433
+ if (err.code !== "ENOENT")
16434
+ throw err;
16435
+ }
16436
+ }
16437
+ function createFileSync(tempDir, projectDir, options2) {
16438
+ const failures = [];
16439
+ const syncedFiles = new Set;
16440
+ const gitignore = loadGitignore(projectDir);
16441
+ const isCI2 = process.env.CI === "true";
16442
+ const watcher = chokidar_default.watch(tempDir, {
16443
+ followSymlinks: false,
16444
+ ignoreInitial: true,
16445
+ usePolling: isCI2,
16446
+ interval: 100,
16447
+ awaitWriteFinish: {
16448
+ stabilityThreshold: 200,
16449
+ pollInterval: 50
16450
+ },
16451
+ ignored: (filePath) => isExcluded(relative4(tempDir, filePath), gitignore)
16452
+ });
16453
+ watcher.on("error", (err) => {
16454
+ failures.push({ path: "<watcher>", error: err });
16455
+ });
16456
+ const handleAdd = async (filePath) => {
16457
+ if (isSymlink(filePath))
16458
+ return;
16459
+ const relativePath = relative4(tempDir, filePath);
16460
+ const normalizedPath = normalizePath2(relativePath);
16461
+ if (options2?.overlayFiles?.has(normalizedPath))
16462
+ return;
16463
+ const destPath = join6(projectDir, relativePath);
16464
+ try {
16465
+ await syncFile(filePath, destPath);
16466
+ syncedFiles.add(normalizedPath);
16467
+ } catch (err) {
16468
+ failures.push({ path: relativePath, error: err });
16469
+ }
16470
+ };
16471
+ const handleChange = async (filePath) => {
16472
+ if (isSymlink(filePath))
16473
+ return;
16474
+ const relativePath = relative4(tempDir, filePath);
16475
+ const normalizedPath = normalizePath2(relativePath);
16476
+ if (options2?.overlayFiles?.has(normalizedPath))
16477
+ return;
16478
+ const destPath = join6(projectDir, relativePath);
16479
+ try {
16480
+ await syncFile(filePath, destPath);
16481
+ } catch (err) {
16482
+ failures.push({ path: relativePath, error: err });
16483
+ }
16484
+ };
16485
+ const handleUnlink = async (filePath) => {
16486
+ const relativePath = relative4(tempDir, filePath);
16487
+ const normalizedPath = normalizePath2(relativePath);
16488
+ if (!syncedFiles.has(normalizedPath))
16489
+ return;
16490
+ const destPath = join6(projectDir, relativePath);
16491
+ try {
16492
+ syncDelete(destPath);
16493
+ syncedFiles.delete(normalizedPath);
16494
+ } catch (err) {
16495
+ failures.push({ path: relativePath, error: err });
16496
+ }
16497
+ };
16498
+ const handleAddDir = (dirPath) => {
16499
+ if (isSymlink(dirPath))
16500
+ return;
16501
+ const relativePath = relative4(tempDir, dirPath);
16502
+ try {
16503
+ mkdirSync(join6(projectDir, relativePath), { recursive: true });
14690
16504
  } catch (err) {
14691
16505
  if (err.code !== "EEXIST") {
14692
- throw new Error(`Failed to inject ${injectPath} \u2192 ${targetPath}: ${err.message}`);
16506
+ failures.push({ path: relativePath, error: err });
16507
+ }
16508
+ }
16509
+ };
16510
+ const handleUnlinkDir = (dirPath) => {
16511
+ const relativePath = relative4(tempDir, dirPath);
16512
+ const destPath = join6(projectDir, relativePath);
16513
+ try {
16514
+ rmdirSync(destPath);
16515
+ } catch (err) {
16516
+ const code = err.code;
16517
+ if (code !== "ENOENT" && code !== "ENOTEMPTY") {
16518
+ failures.push({ path: relativePath, error: err });
16519
+ }
16520
+ }
16521
+ };
16522
+ watcher.on("add", handleAdd);
16523
+ watcher.on("change", handleChange);
16524
+ watcher.on("unlink", handleUnlink);
16525
+ watcher.on("addDir", handleAddDir);
16526
+ watcher.on("unlinkDir", handleUnlinkDir);
16527
+ return {
16528
+ close: async () => {
16529
+ await watcher.close();
16530
+ },
16531
+ getFailures: () => [...failures],
16532
+ getSyncCount: () => syncedFiles.size
16533
+ };
16534
+ }
16535
+
16536
+ // src/utils/symlink-farm.ts
16537
+ import { randomBytes } from "crypto";
16538
+ import { mkdir as mkdir4, readdir as readdir4, rename as rename2, rm as rm2, stat as stat5, symlink as symlink2 } from "fs/promises";
16539
+ import { tmpdir } from "os";
16540
+ import { dirname as dirname6, isAbsolute as isAbsolute2, join as join7, relative as relative5 } from "path";
16541
+
16542
+ // src/utils/pattern-filter.ts
16543
+ import path5 from "path";
16544
+ var {Glob: Glob2 } = globalThis.Bun;
16545
+ function normalizeForMatching(absolutePath, projectRoot) {
16546
+ const relativePath = path5.relative(projectRoot, absolutePath);
16547
+ return relativePath.split(path5.sep).join("/").replace(/^\.\//, "");
16548
+ }
16549
+ class PathMatcher {
16550
+ includeGlobs;
16551
+ excludeGlobs;
16552
+ includePatterns;
16553
+ excludePatterns;
16554
+ constructor(includePatterns = [], excludePatterns = []) {
16555
+ this.includePatterns = includePatterns;
16556
+ this.excludePatterns = excludePatterns;
16557
+ this.includeGlobs = includePatterns.map((p) => ({ pattern: p, glob: new Glob2(p) }));
16558
+ this.excludeGlobs = excludePatterns.map((p) => ({ pattern: p, glob: new Glob2(p) }));
16559
+ }
16560
+ getDisposition(relativePath) {
16561
+ const matchesExclude = this.excludeGlobs.some((g) => g.glob.match(relativePath));
16562
+ const matchesInclude = this.includeGlobs.some((g) => g.glob.match(relativePath));
16563
+ if (this.includePatterns.length > 0 && this.excludePatterns.length > 0) {
16564
+ if (matchesInclude) {
16565
+ return { type: "included" };
16566
+ }
16567
+ if (matchesExclude) {
16568
+ return { type: "excluded" };
16569
+ }
16570
+ const hasIncludesInside = this.includePatterns.some((pattern) => {
16571
+ if (pattern.startsWith(`${relativePath}/`))
16572
+ return true;
16573
+ if (pattern.startsWith("**/"))
16574
+ return true;
16575
+ return false;
16576
+ });
16577
+ const hasExcludesInside2 = this.excludePatterns.some((pattern) => {
16578
+ if (pattern.startsWith(`${relativePath}/`))
16579
+ return true;
16580
+ if (pattern.startsWith("**/"))
16581
+ return true;
16582
+ return false;
16583
+ });
16584
+ if (hasIncludesInside || hasExcludesInside2) {
16585
+ return { type: "partial", patterns: this.includePatterns };
14693
16586
  }
16587
+ return { type: "included" };
16588
+ }
16589
+ if (this.includePatterns.length > 0) {
16590
+ if (matchesInclude) {
16591
+ return { type: "included" };
16592
+ }
16593
+ const hasIncludesInside = this.includePatterns.some((pattern) => {
16594
+ if (pattern.startsWith(`${relativePath}/`))
16595
+ return true;
16596
+ if (pattern.startsWith("**/"))
16597
+ return true;
16598
+ return false;
16599
+ });
16600
+ if (hasIncludesInside) {
16601
+ return { type: "partial", patterns: this.includePatterns };
16602
+ }
16603
+ return { type: "excluded" };
16604
+ }
16605
+ if (matchesExclude) {
16606
+ return { type: "excluded" };
16607
+ }
16608
+ const hasExcludesInside = this.excludePatterns.some((pattern) => {
16609
+ if (pattern.startsWith(`${relativePath}/`))
16610
+ return true;
16611
+ if (pattern.startsWith("**/"))
16612
+ return true;
16613
+ return false;
16614
+ });
16615
+ if (hasExcludesInside) {
16616
+ return { type: "partial", patterns: [] };
14694
16617
  }
16618
+ return { type: "included" };
16619
+ }
16620
+ targetsInside(dirPath) {
16621
+ const normalizedDir = dirPath.endsWith("/") ? dirPath : `${dirPath}/`;
16622
+ return this.includePatterns.some((p) => p.startsWith(normalizedDir));
16623
+ }
16624
+ getInnerPatterns(dirPath) {
16625
+ const normalizedDir = dirPath.endsWith("/") ? dirPath : `${dirPath}/`;
16626
+ return this.includePatterns.filter((p) => p.startsWith(normalizedDir));
16627
+ }
16628
+ hasIncludePatterns() {
16629
+ return this.includePatterns.length > 0;
16630
+ }
16631
+ }
16632
+ function createPathMatcher(includePatterns = [], excludePatterns = []) {
16633
+ return new PathMatcher(includePatterns, excludePatterns);
16634
+ }
16635
+
16636
+ // src/utils/symlink-farm.ts
16637
+ var STALE_SESSION_THRESHOLD_MS = 24 * 60 * 60 * 1000;
16638
+ var REMOVING_THRESHOLD_MS = 60 * 60 * 1000;
16639
+ var GHOST_DIR_PREFIX = "ocx-ghost-";
16640
+ var REMOVING_SUFFIX = "-removing";
16641
+ var GHOST_MARKER_FILE = ".ocx-ghost-marker";
16642
+ async function createSymlinkFarm(sourceDir, options2) {
16643
+ if (!isAbsolute2(sourceDir)) {
16644
+ throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
16645
+ }
16646
+ const suffix = randomBytes(4).toString("hex");
16647
+ const tempDir = join7(tmpdir(), `${GHOST_DIR_PREFIX}${suffix}`);
16648
+ await Bun.write(join7(tempDir, GHOST_MARKER_FILE), "");
16649
+ try {
16650
+ const matcher = createPathMatcher(options2?.includePatterns ?? [], options2?.excludePatterns ?? []);
16651
+ const plan = await computeSymlinkPlan(sourceDir, sourceDir, matcher);
16652
+ await executeSymlinkPlan(plan, sourceDir, tempDir);
16653
+ return tempDir;
16654
+ } catch (error) {
16655
+ await rm2(tempDir, { recursive: true, force: true }).catch(() => {});
16656
+ throw error;
14695
16657
  }
14696
16658
  }
14697
16659
  async function cleanupSymlinkFarm(tempDir) {
14698
16660
  const removingPath = `${tempDir}${REMOVING_SUFFIX}`;
14699
16661
  try {
14700
- await rename3(tempDir, removingPath);
16662
+ await rename2(tempDir, removingPath);
14701
16663
  } catch {
14702
16664
  return;
14703
16665
  }
@@ -14705,24 +16667,24 @@ async function cleanupSymlinkFarm(tempDir) {
14705
16667
  }
14706
16668
  async function cleanupOrphanedGhostDirs(tempBase = tmpdir()) {
14707
16669
  let cleanedCount = 0;
14708
- if (!isAbsolute(tempBase)) {
16670
+ if (!isAbsolute2(tempBase)) {
14709
16671
  throw new Error(`tempBase must be an absolute path, got: ${tempBase}`);
14710
16672
  }
14711
16673
  let dirNames;
14712
16674
  try {
14713
- dirNames = await readdir3(tempBase);
16675
+ dirNames = await readdir4(tempBase);
14714
16676
  } catch {
14715
16677
  return 0;
14716
16678
  }
14717
16679
  for (const dirName of dirNames) {
14718
- const dirPath = join5(tempBase, dirName);
16680
+ const dirPath = join7(tempBase, dirName);
14719
16681
  const isRemovingDir = dirName.endsWith(REMOVING_SUFFIX);
14720
16682
  const isGhostDir = dirName.startsWith(GHOST_DIR_PREFIX) && !isRemovingDir;
14721
16683
  if (!isRemovingDir && !isGhostDir)
14722
16684
  continue;
14723
16685
  let stats;
14724
16686
  try {
14725
- stats = await stat3(dirPath);
16687
+ stats = await stat5(dirPath);
14726
16688
  } catch {
14727
16689
  continue;
14728
16690
  }
@@ -14735,7 +16697,7 @@ async function cleanupOrphanedGhostDirs(tempBase = tmpdir()) {
14735
16697
  try {
14736
16698
  if (isGhostDir) {
14737
16699
  const removingPath = `${dirPath}${REMOVING_SUFFIX}`;
14738
- await rename3(dirPath, removingPath);
16700
+ await rename2(dirPath, removingPath);
14739
16701
  await rm2(removingPath, { recursive: true, force: true });
14740
16702
  } else {
14741
16703
  await rm2(dirPath, { recursive: true, force: true });
@@ -14745,21 +16707,8 @@ async function cleanupOrphanedGhostDirs(tempBase = tmpdir()) {
14745
16707
  }
14746
16708
  return cleanedCount;
14747
16709
  }
14748
- async function handleExcludedEntry(isDirectory, disposition, computeNestedPlan) {
14749
- if (disposition.type === "excluded") {
14750
- return { action: "skip" };
14751
- }
14752
- if (disposition.type === "included") {
14753
- return { action: "includeWhole" };
14754
- }
14755
- if (isDirectory) {
14756
- const nestedPlan = await computeNestedPlan();
14757
- return { action: "partial", nestedPlan };
14758
- }
14759
- return { action: "skip" };
14760
- }
14761
- async function computeSymlinkPlan(sourceDir, projectRoot, excludedPaths, matcher, insideExcludedTree = false) {
14762
- if (!isAbsolute(sourceDir)) {
16710
+ async function computeSymlinkPlan(sourceDir, projectRoot, matcher) {
16711
+ if (!isAbsolute2(sourceDir)) {
14763
16712
  throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
14764
16713
  }
14765
16714
  const plan = {
@@ -14767,49 +16716,25 @@ async function computeSymlinkPlan(sourceDir, projectRoot, excludedPaths, matcher
14767
16716
  files: [],
14768
16717
  partialDirs: new Map
14769
16718
  };
14770
- const entries = await readdir3(sourceDir, { withFileTypes: true });
16719
+ const entries = await readdir4(sourceDir, { withFileTypes: true });
14771
16720
  for (const entry of entries) {
14772
- const sourcePath = join5(sourceDir, entry.name);
16721
+ const sourcePath = join7(sourceDir, entry.name);
14773
16722
  const relativePath = normalizeForMatching(sourcePath, projectRoot);
14774
- if (insideExcludedTree) {
14775
- const disposition = matcher.getDisposition(relativePath);
14776
- const result = await handleExcludedEntry(entry.isDirectory(), disposition, () => computeSymlinkPlan(sourcePath, projectRoot, excludedPaths, matcher, true));
14777
- if (result.action === "skip") {
14778
- continue;
14779
- }
14780
- if (result.action === "includeWhole") {
14781
- if (entry.isDirectory()) {
14782
- plan.wholeDirs.push(entry.name);
14783
- } else {
14784
- plan.files.push(entry.name);
14785
- }
14786
- continue;
14787
- }
14788
- plan.partialDirs.set(entry.name, result.nestedPlan);
16723
+ const disposition = matcher.getDisposition(relativePath);
16724
+ if (disposition.type === "excluded") {
14789
16725
  continue;
14790
16726
  }
14791
- if (excludedPaths.has(sourcePath)) {
14792
- if (!matcher.hasIncludePatterns()) {
14793
- continue;
14794
- }
14795
- const disposition = matcher.getDisposition(relativePath);
14796
- const result = await handleExcludedEntry(entry.isDirectory(), disposition, () => computeSymlinkPlan(sourcePath, projectRoot, excludedPaths, matcher, true));
14797
- if (result.action === "skip") {
14798
- continue;
14799
- }
14800
- if (result.action === "includeWhole") {
14801
- if (entry.isDirectory()) {
14802
- plan.wholeDirs.push(entry.name);
14803
- } else {
14804
- plan.files.push(entry.name);
14805
- }
14806
- continue;
16727
+ if (disposition.type === "included") {
16728
+ if (entry.isDirectory()) {
16729
+ plan.wholeDirs.push(entry.name);
16730
+ } else {
16731
+ plan.files.push(entry.name);
14807
16732
  }
14808
- plan.partialDirs.set(entry.name, result.nestedPlan);
14809
16733
  continue;
14810
16734
  }
14811
16735
  if (entry.isDirectory()) {
14812
- plan.wholeDirs.push(entry.name);
16736
+ const nestedPlan = await computeSymlinkPlan(sourcePath, projectRoot, matcher);
16737
+ plan.partialDirs.set(entry.name, nestedPlan);
14813
16738
  } else {
14814
16739
  plan.files.push(entry.name);
14815
16740
  }
@@ -14817,33 +16742,34 @@ async function computeSymlinkPlan(sourceDir, projectRoot, excludedPaths, matcher
14817
16742
  return plan;
14818
16743
  }
14819
16744
  async function executeSymlinkPlan(plan, sourceRoot, targetRoot) {
14820
- if (!isAbsolute(sourceRoot)) {
16745
+ if (!isAbsolute2(sourceRoot)) {
14821
16746
  throw new Error(`sourceRoot must be an absolute path, got: ${sourceRoot}`);
14822
16747
  }
14823
- if (!isAbsolute(targetRoot)) {
16748
+ if (!isAbsolute2(targetRoot)) {
14824
16749
  throw new Error(`targetRoot must be an absolute path, got: ${targetRoot}`);
14825
16750
  }
14826
16751
  for (const dirName of plan.wholeDirs) {
14827
- const sourcePath = join5(sourceRoot, dirName);
14828
- const targetPath = join5(targetRoot, dirName);
16752
+ const sourcePath = join7(sourceRoot, dirName);
16753
+ const targetPath = join7(targetRoot, dirName);
14829
16754
  await symlink2(sourcePath, targetPath);
14830
16755
  }
14831
16756
  for (const fileName of plan.files) {
14832
- const sourcePath = join5(sourceRoot, fileName);
14833
- const targetPath = join5(targetRoot, fileName);
16757
+ const sourcePath = join7(sourceRoot, fileName);
16758
+ const targetPath = join7(targetRoot, fileName);
14834
16759
  await symlink2(sourcePath, targetPath);
14835
16760
  }
14836
16761
  for (const [dirName, nestedPlan] of plan.partialDirs) {
14837
- const sourcePath = join5(sourceRoot, dirName);
14838
- const targetPath = join5(targetRoot, dirName);
16762
+ const sourcePath = join7(sourceRoot, dirName);
16763
+ const targetPath = join7(targetRoot, dirName);
14839
16764
  await mkdir4(targetPath, { recursive: true });
14840
16765
  await executeSymlinkPlan(nestedPlan, sourcePath, targetPath);
14841
16766
  }
14842
16767
  }
14843
16768
 
14844
16769
  // src/utils/terminal-title.ts
14845
- import path7 from "path";
16770
+ import path6 from "path";
14846
16771
  var MAX_BRANCH_LENGTH = 20;
16772
+ var titleSaved = false;
14847
16773
  function isInsideTmux() {
14848
16774
  return Boolean(process.env.TMUX);
14849
16775
  }
@@ -14864,8 +16790,27 @@ function setTerminalName(name) {
14864
16790
  setTmuxWindowName(name);
14865
16791
  setTerminalTitle(name);
14866
16792
  }
16793
+ function saveTerminalTitle() {
16794
+ if (titleSaved || !isTTY) {
16795
+ return;
16796
+ }
16797
+ process.stdout.write("\x1B[22;2t");
16798
+ titleSaved = true;
16799
+ }
16800
+ function restoreTerminalTitle() {
16801
+ if (!titleSaved) {
16802
+ return;
16803
+ }
16804
+ if (isInsideTmux()) {
16805
+ Bun.spawnSync(["tmux", "set-window-option", "automatic-rename", "on"]);
16806
+ }
16807
+ if (isTTY) {
16808
+ process.stdout.write("\x1B[23;2t");
16809
+ }
16810
+ titleSaved = false;
16811
+ }
14867
16812
  function formatTerminalName(cwd, profileName, gitInfo) {
14868
- const repoName = gitInfo.repoName ?? path7.basename(cwd);
16813
+ const repoName = gitInfo.repoName ?? path6.basename(cwd);
14869
16814
  if (!gitInfo.branch) {
14870
16815
  return `ghost[${profileName}]:${repoName}`;
14871
16816
  }
@@ -14875,7 +16820,7 @@ function formatTerminalName(cwd, profileName, gitInfo) {
14875
16820
 
14876
16821
  // src/commands/ghost/opencode.ts
14877
16822
  function registerGhostOpenCodeCommand(parent) {
14878
- parent.command("opencode").description("Launch OpenCode with ghost mode configuration").option("-p, --profile <name>", "Use specific profile").addOption(sharedOptions.json()).addOption(sharedOptions.quiet()).allowUnknownOption().allowExcessArguments(true).action(async (options2, command) => {
16823
+ parent.command("opencode").description("Launch OpenCode with ghost mode configuration").option("-p, --profile <name>", "Use specific profile").option("--no-rename", "Disable terminal/tmux window renaming").addOption(sharedOptions.json()).addOption(sharedOptions.quiet()).allowUnknownOption().allowExcessArguments(true).action(async (options2, command) => {
14879
16824
  try {
14880
16825
  const args = command.args;
14881
16826
  await runGhostOpenCode(args, options2);
@@ -14889,11 +16834,6 @@ async function runGhostOpenCode(args, options2) {
14889
16834
  if (!await manager.isInitialized()) {
14890
16835
  throw new ProfilesNotInitializedError;
14891
16836
  }
14892
- if (!options2.quiet && await needsMigration()) {
14893
- console.log("Notice: Found legacy config at ~/.config/ocx/");
14894
- console.log(`Run 'ocx ghost migrate' to upgrade to the new profiles system.
14895
- `);
14896
- }
14897
16837
  await cleanupOrphanedGhostDirs();
14898
16838
  const profileName = await manager.getCurrent(options2.profile);
14899
16839
  const profile = await manager.get(profileName);
@@ -14906,28 +16846,29 @@ async function runGhostOpenCode(args, options2) {
14906
16846
  }
14907
16847
  const cwd = process.cwd();
14908
16848
  const gitContext = await detectGitRepo(cwd);
14909
- const gitRoot = gitContext?.workTree ?? cwd;
14910
- const discoveredPaths = await discoverProjectFiles(cwd, gitRoot);
14911
16849
  const ghostConfig = profile.ghost;
14912
- const tempDir = await createSymlinkFarm(cwd, discoveredPaths, {
16850
+ const tempDir = await createSymlinkFarm(cwd, {
14913
16851
  includePatterns: ghostConfig.include,
14914
16852
  excludePatterns: ghostConfig.exclude
14915
16853
  });
14916
- const ghostFiles = await discoverProjectFiles(profileDir, profileDir);
14917
- await injectGhostFiles(tempDir, profileDir, ghostFiles);
14918
- if (profile.hasAgents) {
14919
- const agentsPath = getProfileAgents(profileName);
14920
- const destAgentsPath = path8.join(tempDir, "AGENTS.md");
14921
- await copyFilePromise(agentsPath, destAgentsPath);
14922
- }
16854
+ const overlayFiles = await injectProfileOverlay(tempDir, profileDir, ghostConfig.include);
14923
16855
  let cleanupDone = false;
16856
+ let fileSync;
16857
+ fileSync = createFileSync(tempDir, cwd, { overlayFiles });
14924
16858
  const performCleanup = async () => {
14925
16859
  if (cleanupDone)
14926
16860
  return;
14927
16861
  cleanupDone = true;
14928
16862
  await cleanupSymlinkFarm(tempDir);
14929
16863
  };
16864
+ const shouldRename = options2.rename !== false && ghostConfig.renameWindow !== false;
14930
16865
  const exitHandler = () => {
16866
+ if (shouldRename) {
16867
+ restoreTerminalTitle();
16868
+ }
16869
+ if (fileSync) {
16870
+ fileSync.close().catch(() => {});
16871
+ }
14931
16872
  if (!cleanupDone && tempDir) {
14932
16873
  try {
14933
16874
  const removingPath = `${tempDir}${REMOVING_SUFFIX}`;
@@ -14942,8 +16883,11 @@ async function runGhostOpenCode(args, options2) {
14942
16883
  const sigtermHandler = () => proc?.kill("SIGTERM");
14943
16884
  process.on("SIGINT", sigintHandler);
14944
16885
  process.on("SIGTERM", sigtermHandler);
14945
- const gitInfo = await getGitInfo(cwd);
14946
- setTerminalName(formatTerminalName(cwd, profileName, gitInfo));
16886
+ if (shouldRename) {
16887
+ saveTerminalTitle();
16888
+ const gitInfo = await getGitInfo(cwd);
16889
+ setTerminalName(formatTerminalName(cwd, profileName, gitInfo));
16890
+ }
14947
16891
  proc = Bun.spawn({
14948
16892
  cmd: ["opencode", ...args],
14949
16893
  cwd: tempDir,
@@ -14963,14 +16907,62 @@ async function runGhostOpenCode(args, options2) {
14963
16907
  });
14964
16908
  try {
14965
16909
  const exitCode = await proc.exited;
16910
+ process.off("SIGINT", sigintHandler);
16911
+ process.off("SIGTERM", sigtermHandler);
16912
+ process.off("exit", exitHandler);
16913
+ if (fileSync) {
16914
+ await fileSync.close();
16915
+ const syncCount = fileSync.getSyncCount();
16916
+ const failures = fileSync.getFailures();
16917
+ if (syncCount > 0 && !options2.quiet) {
16918
+ logger.info(`Synced ${syncCount} new files to project`);
16919
+ }
16920
+ if (failures.length > 0) {
16921
+ logger.warn(`${failures.length} files failed to sync`);
16922
+ for (const f of failures) {
16923
+ logger.debug(` ${f.path}: ${f.error.message}`);
16924
+ }
16925
+ }
16926
+ }
16927
+ await performCleanup();
14966
16928
  process.exit(exitCode);
14967
- } finally {
16929
+ } catch (error) {
14968
16930
  process.off("SIGINT", sigintHandler);
14969
16931
  process.off("SIGTERM", sigtermHandler);
14970
16932
  process.off("exit", exitHandler);
16933
+ if (fileSync) {
16934
+ await fileSync.close();
16935
+ }
14971
16936
  await performCleanup();
16937
+ throw error;
14972
16938
  }
14973
16939
  }
16940
+ function userExplicitlyIncluded(relativePath, compiledPatterns) {
16941
+ if (compiledPatterns.length === 0)
16942
+ return false;
16943
+ return compiledPatterns.some((glob) => glob.match(relativePath));
16944
+ }
16945
+ async function injectProfileOverlay(tempDir, profileDir, includePatterns) {
16946
+ const entries = await readdir5(profileDir, { withFileTypes: true, recursive: true });
16947
+ const injectedFiles = new Set;
16948
+ const compiledIncludePatterns = includePatterns.map((p) => new Glob3(p));
16949
+ for (const entry of entries) {
16950
+ const relativePath = path7.relative(profileDir, path7.join(entry.parentPath, entry.name));
16951
+ if (relativePath === "ghost.jsonc")
16952
+ continue;
16953
+ if (relativePath === ".gitignore")
16954
+ continue;
16955
+ if (userExplicitlyIncluded(relativePath, compiledIncludePatterns))
16956
+ continue;
16957
+ if (entry.isDirectory())
16958
+ continue;
16959
+ const destPath = path7.join(tempDir, relativePath);
16960
+ await mkdir5(path7.dirname(destPath), { recursive: true });
16961
+ await copyFile(path7.join(entry.parentPath, entry.name), destPath);
16962
+ injectedFiles.add(normalizePath2(relativePath));
16963
+ }
16964
+ return new Set(injectedFiles);
16965
+ }
14974
16966
 
14975
16967
  // src/commands/ghost/profile/add.ts
14976
16968
  function registerProfileAddCommand(parent) {
@@ -15506,13 +17498,12 @@ function registerGhostCommand(program2) {
15506
17498
  registerGhostSearchCommand(ghost);
15507
17499
  registerGhostOpenCodeCommand(ghost);
15508
17500
  registerGhostProfileCommand(ghost);
15509
- registerGhostMigrateCommand(ghost);
15510
17501
  }
15511
17502
 
15512
17503
  // src/commands/init.ts
15513
- import { existsSync as existsSync2 } from "fs";
15514
- import { cp, mkdir as mkdir5, readdir as readdir4, readFile, rm as rm3, writeFile as writeFile2 } from "fs/promises";
15515
- import { join as join6 } from "path";
17504
+ import { existsSync as existsSync3 } from "fs";
17505
+ import { cp, mkdir as mkdir6, readdir as readdir6, readFile, rm as rm3, writeFile as writeFile2 } from "fs/promises";
17506
+ import { join as join8 } from "path";
15516
17507
  var TEMPLATE_REPO = "kdcokenny/ocx";
15517
17508
  var TEMPLATE_PATH = "examples/registry-starter";
15518
17509
  function registerInitCommand(program2) {
@@ -15530,8 +17521,8 @@ function registerInitCommand(program2) {
15530
17521
  }
15531
17522
  async function runInit(options2) {
15532
17523
  const cwd = options2.cwd ?? process.cwd();
15533
- const configPath = join6(cwd, "ocx.jsonc");
15534
- if (existsSync2(configPath)) {
17524
+ const configPath = join8(cwd, "ocx.jsonc");
17525
+ if (existsSync3(configPath)) {
15535
17526
  throw new ConflictError(`ocx.jsonc already exists at ${configPath}
15536
17527
 
15537
17528
  ` + `To reset, delete the config and run init again:
@@ -15578,7 +17569,7 @@ async function runInitRegistry(directory, options2) {
15578
17569
  if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(namespace)) {
15579
17570
  throw new ValidationError("Invalid namespace format: must start with letter/number, use hyphens only between segments (e.g., 'my-registry')");
15580
17571
  }
15581
- const existingFiles = await readdir4(cwd).catch(() => []);
17572
+ const existingFiles = await readdir6(cwd).catch(() => []);
15582
17573
  const hasVisibleFiles = existingFiles.some((f) => !f.startsWith("."));
15583
17574
  if (hasVisibleFiles && !options2.force) {
15584
17575
  throw new ConflictError("Directory is not empty. Use --force to overwrite existing files.");
@@ -15589,7 +17580,7 @@ async function runInitRegistry(directory, options2) {
15589
17580
  if (spin)
15590
17581
  spin.text = options2.local ? "Copying template..." : "Fetching template...";
15591
17582
  if (options2.local) {
15592
- await mkdir5(cwd, { recursive: true });
17583
+ await mkdir6(cwd, { recursive: true });
15593
17584
  await copyDir(options2.local, cwd);
15594
17585
  } else {
15595
17586
  const version = options2.canary ? "main" : await getLatestVersion();
@@ -15605,7 +17596,7 @@ async function runInitRegistry(directory, options2) {
15605
17596
  logger.info("");
15606
17597
  logger.info("Next steps:");
15607
17598
  logger.info(" 1. bun install");
15608
- logger.info(" 2. Edit registry.json with your components");
17599
+ logger.info(" 2. Edit registry.jsonc with your components");
15609
17600
  logger.info(" 3. bun run build");
15610
17601
  logger.info("");
15611
17602
  logger.info("Deploy to:");
@@ -15637,10 +17628,10 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
15637
17628
  if (!response.ok || !response.body) {
15638
17629
  throw new NetworkError(`Failed to fetch template from ${tarballUrl}: ${response.statusText}`);
15639
17630
  }
15640
- const tempDir = join6(destDir, ".ocx-temp");
15641
- await mkdir5(tempDir, { recursive: true });
17631
+ const tempDir = join8(destDir, ".ocx-temp");
17632
+ await mkdir6(tempDir, { recursive: true });
15642
17633
  try {
15643
- const tarPath = join6(tempDir, "template.tar.gz");
17634
+ const tarPath = join8(tempDir, "template.tar.gz");
15644
17635
  const arrayBuffer = await response.arrayBuffer();
15645
17636
  await writeFile2(tarPath, Buffer.from(arrayBuffer));
15646
17637
  const proc = Bun.spawn(["tar", "-xzf", tarPath, "-C", tempDir], {
@@ -15652,12 +17643,12 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
15652
17643
  const stderr = await new Response(proc.stderr).text();
15653
17644
  throw new Error(`Failed to extract template: ${stderr}`);
15654
17645
  }
15655
- const extractedDirs = await readdir4(tempDir);
17646
+ const extractedDirs = await readdir6(tempDir);
15656
17647
  const extractedDir = extractedDirs.find((d) => d.startsWith("ocx-"));
15657
17648
  if (!extractedDir) {
15658
17649
  throw new Error("Failed to find extracted template directory");
15659
17650
  }
15660
- const templateDir = join6(tempDir, extractedDir, TEMPLATE_PATH);
17651
+ const templateDir = join8(tempDir, extractedDir, TEMPLATE_PATH);
15661
17652
  await copyDir(templateDir, destDir);
15662
17653
  } finally {
15663
17654
  await rm3(tempDir, { recursive: true, force: true });
@@ -15665,15 +17656,15 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
15665
17656
  }
15666
17657
  async function replacePlaceholders(dir, values) {
15667
17658
  const filesToProcess = [
15668
- "registry.json",
17659
+ "registry.jsonc",
15669
17660
  "package.json",
15670
17661
  "wrangler.jsonc",
15671
17662
  "README.md",
15672
17663
  "AGENTS.md"
15673
17664
  ];
15674
17665
  for (const file of filesToProcess) {
15675
- const filePath = join6(dir, file);
15676
- if (!existsSync2(filePath))
17666
+ const filePath = join8(dir, file);
17667
+ if (!existsSync3(filePath))
15677
17668
  continue;
15678
17669
  let content2 = await readFile(filePath).then((b) => b.toString());
15679
17670
  content2 = content2.replace(/my-registry/g, values.namespace);
@@ -15682,11 +17673,393 @@ async function replacePlaceholders(dir, values) {
15682
17673
  }
15683
17674
  }
15684
17675
 
15685
- // src/commands/update.ts
17676
+ // src/self-update/version-provider.ts
17677
+ class BuildTimeVersionProvider {
17678
+ version = "1.3.0";
17679
+ }
17680
+ var defaultVersionProvider = new BuildTimeVersionProvider;
17681
+
17682
+ // src/self-update/check.ts
17683
+ var VERSION_CHECK_TIMEOUT_MS = 200;
17684
+ var PACKAGE_NAME = "ocx";
17685
+ function parseVersion2(v) {
17686
+ const [main2 = ""] = v.split("-");
17687
+ const parts = main2.split(".");
17688
+ const major = parseInt(parts[0] ?? "", 10);
17689
+ const minor = parseInt(parts[1] ?? "", 10);
17690
+ const patch = parseInt(parts[2] ?? "", 10);
17691
+ if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) {
17692
+ return null;
17693
+ }
17694
+ return { major, minor, patch };
17695
+ }
17696
+ function compareSemver2(a, b) {
17697
+ const vA = parseVersion2(a);
17698
+ const vB = parseVersion2(b);
17699
+ if (!vA || !vB) {
17700
+ return null;
17701
+ }
17702
+ if (vA.major !== vB.major)
17703
+ return vA.major - vB.major;
17704
+ if (vA.minor !== vB.minor)
17705
+ return vA.minor - vB.minor;
17706
+ return vA.patch - vB.patch;
17707
+ }
17708
+ async function checkForUpdate(versionProvider) {
17709
+ const provider = versionProvider ?? defaultVersionProvider;
17710
+ const current = provider.version || "0.0.0-dev";
17711
+ if (current === "0.0.0-dev") {
17712
+ return null;
17713
+ }
17714
+ try {
17715
+ const result = await fetchPackageVersion(PACKAGE_NAME, undefined, AbortSignal.timeout(VERSION_CHECK_TIMEOUT_MS));
17716
+ const latest = result.version;
17717
+ const comparison = compareSemver2(latest, current);
17718
+ if (comparison === null) {
17719
+ return null;
17720
+ }
17721
+ return {
17722
+ current,
17723
+ latest,
17724
+ updateAvailable: comparison > 0
17725
+ };
17726
+ } catch {
17727
+ return null;
17728
+ }
17729
+ }
17730
+
17731
+ // src/self-update/detect-method.ts
17732
+ function parseInstallMethod(input) {
17733
+ const VALID_METHODS = ["curl", "npm", "yarn", "pnpm", "bun"];
17734
+ const method = VALID_METHODS.find((m) => m === input);
17735
+ if (!method) {
17736
+ throw new SelfUpdateError(`Invalid install method: "${input}"
17737
+ Valid methods: ${VALID_METHODS.join(", ")}`);
17738
+ }
17739
+ return method;
17740
+ }
17741
+ var isCompiledBinary = () => Bun.main.startsWith("/$bunfs/");
17742
+ var isTempExecution = (path8) => path8.includes("/_npx/") || path8.includes("/.cache/bunx/") || path8.includes("/.pnpm/_temp/");
17743
+ var isYarnGlobalInstall = (path8) => path8.includes("/.yarn/global") || path8.includes("/.config/yarn/global");
17744
+ var isPnpmGlobalInstall = (path8) => path8.includes("/.pnpm/") || path8.includes("/pnpm/global");
17745
+ var isBunGlobalInstall = (path8) => path8.includes("/.bun/bin") || path8.includes("/.bun/install/global");
17746
+ var isNpmGlobalInstall = (path8) => path8.includes("/.npm/") || path8.includes("/node_modules/");
17747
+ function detectInstallMethod() {
17748
+ if (isCompiledBinary()) {
17749
+ return "curl";
17750
+ }
17751
+ const scriptPath = process.argv[1] ?? "";
17752
+ if (isTempExecution(scriptPath))
17753
+ return "unknown";
17754
+ if (isYarnGlobalInstall(scriptPath))
17755
+ return "yarn";
17756
+ if (isPnpmGlobalInstall(scriptPath))
17757
+ return "pnpm";
17758
+ if (isBunGlobalInstall(scriptPath))
17759
+ return "bun";
17760
+ if (isNpmGlobalInstall(scriptPath))
17761
+ return "npm";
17762
+ const userAgent = process.env.npm_config_user_agent ?? "";
17763
+ if (userAgent.includes("yarn"))
17764
+ return "yarn";
17765
+ if (userAgent.includes("pnpm"))
17766
+ return "pnpm";
17767
+ if (userAgent.includes("bun"))
17768
+ return "bun";
17769
+ if (userAgent.includes("npm"))
17770
+ return "npm";
17771
+ return "unknown";
17772
+ }
17773
+ function getExecutablePath() {
17774
+ if (typeof Bun !== "undefined" && Bun.main.startsWith("/$bunfs/")) {
17775
+ return process.execPath;
17776
+ }
17777
+ return process.argv[1] ?? process.execPath;
17778
+ }
17779
+
17780
+ // src/self-update/download.ts
17781
+ import { chmodSync, existsSync as existsSync4, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "fs";
17782
+ var GITHUB_REPO2 = "kdcokenny/ocx";
17783
+ var DEFAULT_DOWNLOAD_BASE_URL = `https://github.com/${GITHUB_REPO2}/releases/download`;
17784
+ var PLATFORM_MAP = {
17785
+ "arm64-darwin": "ocx-darwin-arm64",
17786
+ "x64-darwin": "ocx-darwin-x64",
17787
+ "arm64-linux": "ocx-linux-arm64",
17788
+ "x64-linux": "ocx-linux-x64",
17789
+ "x64-win32": "ocx-windows-x64.exe"
17790
+ };
17791
+ function getDownloadBaseUrl() {
17792
+ const envUrl = process.env.OCX_DOWNLOAD_URL;
17793
+ if (envUrl) {
17794
+ return envUrl.replace(/\/+$/, "");
17795
+ }
17796
+ return DEFAULT_DOWNLOAD_BASE_URL;
17797
+ }
17798
+ function getDownloadUrl(version) {
17799
+ const platform = `${process.arch}-${process.platform}`;
17800
+ const target = PLATFORM_MAP[platform];
17801
+ if (!target) {
17802
+ const supported = Object.keys(PLATFORM_MAP).join(", ");
17803
+ throw new SelfUpdateError(`Unsupported platform: ${platform}
17804
+ ` + `Supported platforms: ${supported}`);
17805
+ }
17806
+ const baseUrl = getDownloadBaseUrl();
17807
+ return `${baseUrl}/v${version}/${target}`;
17808
+ }
17809
+ async function downloadWithProgress(url, dest) {
17810
+ const spin = createSpinner({ text: "Downloading update..." });
17811
+ spin.start();
17812
+ let response;
17813
+ try {
17814
+ response = await fetch(url, { redirect: "follow" });
17815
+ } catch (error) {
17816
+ spin.fail("Download failed");
17817
+ throw new SelfUpdateError(`Network error: ${error instanceof Error ? error.message : String(error)}`);
17818
+ }
17819
+ if (!response.ok) {
17820
+ spin.fail("Download failed");
17821
+ throw new SelfUpdateError(`Failed to download: HTTP ${response.status} ${response.statusText}`);
17822
+ }
17823
+ if (!response.body) {
17824
+ spin.fail("Download failed");
17825
+ throw new SelfUpdateError("Download failed: Empty response body");
17826
+ }
17827
+ const reader = response.body.getReader();
17828
+ const writer = Bun.file(dest).writer();
17829
+ const total = Number(response.headers.get("content-length") || 0);
17830
+ let received = 0;
17831
+ try {
17832
+ while (true) {
17833
+ const { done, value } = await reader.read();
17834
+ if (done)
17835
+ break;
17836
+ await writer.write(value);
17837
+ received += value.length;
17838
+ if (total > 0) {
17839
+ const pct = Math.round(received / total * 100);
17840
+ spin.text = `Downloading... ${pct}%`;
17841
+ }
17842
+ }
17843
+ await writer.end();
17844
+ spin.succeed("Download complete");
17845
+ } catch (error) {
17846
+ spin.fail("Download failed");
17847
+ await writer.end();
17848
+ throw new SelfUpdateError(`Download interrupted: ${error instanceof Error ? error.message : String(error)}`);
17849
+ }
17850
+ }
17851
+ async function downloadToTemp(version) {
17852
+ const execPath = getExecutablePath();
17853
+ const tempPath = `${execPath}.new.${Date.now()}`;
17854
+ const url = getDownloadUrl(version);
17855
+ await downloadWithProgress(url, tempPath);
17856
+ try {
17857
+ chmodSync(tempPath, 493);
17858
+ } catch (error) {
17859
+ if (existsSync4(tempPath)) {
17860
+ unlinkSync2(tempPath);
17861
+ }
17862
+ throw new SelfUpdateError(`Failed to set permissions: ${error instanceof Error ? error.message : String(error)}`);
17863
+ }
17864
+ return { tempPath, execPath };
17865
+ }
17866
+ function atomicReplace(tempPath, execPath) {
17867
+ const backupPath = `${execPath}.backup`;
17868
+ try {
17869
+ if (existsSync4(execPath)) {
17870
+ renameSync2(execPath, backupPath);
17871
+ }
17872
+ renameSync2(tempPath, execPath);
17873
+ if (existsSync4(backupPath)) {
17874
+ unlinkSync2(backupPath);
17875
+ }
17876
+ } catch (error) {
17877
+ if (existsSync4(backupPath) && !existsSync4(execPath)) {
17878
+ try {
17879
+ renameSync2(backupPath, execPath);
17880
+ } catch {}
17881
+ }
17882
+ if (existsSync4(tempPath)) {
17883
+ try {
17884
+ unlinkSync2(tempPath);
17885
+ } catch {}
17886
+ }
17887
+ throw new SelfUpdateError(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
17888
+ }
17889
+ }
17890
+ function cleanupTempFile(tempPath) {
17891
+ if (existsSync4(tempPath)) {
17892
+ try {
17893
+ unlinkSync2(tempPath);
17894
+ } catch {}
17895
+ }
17896
+ }
17897
+
17898
+ // src/self-update/notify.ts
17899
+ function notifyUpdate(current, latest) {
17900
+ if (!process.stdout.isTTY)
17901
+ return;
17902
+ console.error(`${kleur_default.cyan("info")}: update available - ${kleur_default.green(latest)} (current: ${kleur_default.dim(current)})`);
17903
+ console.error(` run ${kleur_default.cyan("`ocx self update`")} to upgrade`);
17904
+ }
17905
+ function notifyUpToDate(version) {
17906
+ console.error(`${kleur_default.cyan("info")}: ocx unchanged - ${kleur_default.dim(version)}`);
17907
+ }
17908
+ function notifyUpdated(from, to) {
17909
+ console.error(` ${kleur_default.green("ocx updated")} - ${to} (from ${from})`);
17910
+ }
17911
+
17912
+ // src/self-update/verify.ts
15686
17913
  import { createHash as createHash2 } from "crypto";
15687
- import { existsSync as existsSync3 } from "fs";
15688
- import { mkdir as mkdir6, writeFile as writeFile3 } from "fs/promises";
15689
- import { dirname as dirname5, join as join7 } from "path";
17914
+ var GITHUB_REPO3 = "kdcokenny/ocx";
17915
+ function parseSha256Sums(content2) {
17916
+ const checksums = new Map;
17917
+ for (const line of content2.split(`
17918
+ `)) {
17919
+ const match = line.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
17920
+ if (match?.[1] && match[2]) {
17921
+ checksums.set(match[2].trim(), match[1].toLowerCase());
17922
+ }
17923
+ }
17924
+ return checksums;
17925
+ }
17926
+ function hashContent2(content2) {
17927
+ return createHash2("sha256").update(content2).digest("hex");
17928
+ }
17929
+ async function fetchChecksums(version) {
17930
+ const url = `https://github.com/${GITHUB_REPO3}/releases/download/v${version}/SHA256SUMS.txt`;
17931
+ const response = await fetch(url);
17932
+ if (!response.ok) {
17933
+ throw new SelfUpdateError(`Failed to fetch checksums: ${response.status}`);
17934
+ }
17935
+ const content2 = await response.text();
17936
+ return parseSha256Sums(content2);
17937
+ }
17938
+ async function verifyChecksum(filePath, expectedHash, filename) {
17939
+ const file = Bun.file(filePath);
17940
+ const content2 = await file.arrayBuffer();
17941
+ const actualHash = hashContent2(Buffer.from(content2));
17942
+ if (actualHash !== expectedHash) {
17943
+ throw new IntegrityError(filename, expectedHash, actualHash);
17944
+ }
17945
+ }
17946
+
17947
+ // src/commands/self/update.ts
17948
+ var SEMVER_PATTERN = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
17949
+ async function updateCommand(options2) {
17950
+ const method = options2.method ? parseInstallMethod(options2.method) : detectInstallMethod();
17951
+ const result = await checkForUpdate();
17952
+ if (!result) {
17953
+ throw new SelfUpdateError("Failed to check for updates");
17954
+ }
17955
+ const { current, latest, updateAvailable } = result;
17956
+ if (!updateAvailable && !options2.force) {
17957
+ notifyUpToDate(current);
17958
+ return;
17959
+ }
17960
+ const targetVersion = latest;
17961
+ switch (method) {
17962
+ case "curl": {
17963
+ await updateViaCurl(current, targetVersion);
17964
+ break;
17965
+ }
17966
+ case "npm":
17967
+ case "pnpm":
17968
+ case "bun":
17969
+ case "unknown": {
17970
+ await updateViaPackageManager(method, current, targetVersion);
17971
+ break;
17972
+ }
17973
+ }
17974
+ }
17975
+ async function updateViaCurl(current, targetVersion) {
17976
+ const url = getDownloadUrl(targetVersion);
17977
+ const filename = url.split("/").pop();
17978
+ if (!filename) {
17979
+ throw new SelfUpdateError("Failed to determine binary filename from download URL");
17980
+ }
17981
+ const checksums = await fetchChecksums(targetVersion);
17982
+ const expectedHash = checksums.get(filename);
17983
+ if (!expectedHash) {
17984
+ throw new SelfUpdateError(`Security error: No checksum found for ${filename}. Update aborted.`);
17985
+ }
17986
+ const { tempPath, execPath } = await downloadToTemp(targetVersion);
17987
+ try {
17988
+ await verifyChecksum(tempPath, expectedHash, filename);
17989
+ } catch (error) {
17990
+ cleanupTempFile(tempPath);
17991
+ throw error;
17992
+ }
17993
+ atomicReplace(tempPath, execPath);
17994
+ notifyUpdated(current, targetVersion);
17995
+ }
17996
+ async function runPackageManager(cmd) {
17997
+ const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
17998
+ const exitCode = await proc.exited;
17999
+ if (exitCode !== 0) {
18000
+ const stderr = await new Response(proc.stderr).text();
18001
+ throw new SelfUpdateError(`Package manager command failed: ${stderr.trim()}`);
18002
+ }
18003
+ }
18004
+ async function updateViaPackageManager(method, current, targetVersion) {
18005
+ if (!SEMVER_PATTERN.test(targetVersion)) {
18006
+ throw new SelfUpdateError(`Invalid version format: ${targetVersion}`);
18007
+ }
18008
+ const spin = createSpinner({ text: `Updating via ${method}...` });
18009
+ spin.start();
18010
+ try {
18011
+ switch (method) {
18012
+ case "npm": {
18013
+ await runPackageManager(["npm", "install", "-g", `ocx@${targetVersion}`]);
18014
+ break;
18015
+ }
18016
+ case "yarn": {
18017
+ await runPackageManager(["yarn", "global", "add", `ocx@${targetVersion}`]);
18018
+ break;
18019
+ }
18020
+ case "pnpm": {
18021
+ await runPackageManager(["pnpm", "install", "-g", `ocx@${targetVersion}`]);
18022
+ break;
18023
+ }
18024
+ case "bun": {
18025
+ await runPackageManager(["bun", "install", "-g", `ocx@${targetVersion}`]);
18026
+ break;
18027
+ }
18028
+ case "unknown": {
18029
+ throw new SelfUpdateError(`Could not detect install method. Update manually with one of:
18030
+ ` + ` npm install -g ocx@latest
18031
+ ` + ` pnpm install -g ocx@latest
18032
+ ` + " bun install -g ocx@latest");
18033
+ }
18034
+ }
18035
+ spin.succeed(`Updated via ${method}`);
18036
+ notifyUpdated(current, targetVersion);
18037
+ } catch (error) {
18038
+ if (error instanceof SelfUpdateError) {
18039
+ spin.fail(`Update failed`);
18040
+ throw error;
18041
+ }
18042
+ spin.fail(`Update failed`);
18043
+ throw new SelfUpdateError(`Failed to run ${method}: ${error instanceof Error ? error.message : String(error)}`);
18044
+ }
18045
+ }
18046
+ function registerSelfUpdateCommand(parent) {
18047
+ parent.command("update").description("Update OCX to the latest version").option("-f, --force", "Reinstall even if already up to date").option("-m, --method <method>", "Override install method detection (curl|npm|pnpm|bun)").action(wrapAction(async (options2) => {
18048
+ await updateCommand(options2);
18049
+ }));
18050
+ }
18051
+
18052
+ // src/commands/self/index.ts
18053
+ function registerSelfCommand(program2) {
18054
+ const self = program2.command("self").description("Manage the OCX CLI");
18055
+ registerSelfUpdateCommand(self);
18056
+ }
18057
+
18058
+ // src/commands/update.ts
18059
+ import { createHash as createHash3 } from "crypto";
18060
+ import { existsSync as existsSync5 } from "fs";
18061
+ import { mkdir as mkdir7, writeFile as writeFile3 } from "fs/promises";
18062
+ import { dirname as dirname7, join as join9 } from "path";
15690
18063
  function registerUpdateCommand(program2) {
15691
18064
  program2.command("update [components...]").description("Update installed components (use @version suffix to pin, e.g., kdco/agents@1.2.0)").option("--all", "Update all installed components").option("--registry <name>", "Update all components from a specific registry").option("--dry-run", "Preview changes without applying").option("--cwd <path>", "Working directory", process.cwd()).option("-q, --quiet", "Suppress output").option("-v, --verbose", "Verbose output").option("--json", "Output as JSON").action(async (components, options2) => {
15692
18065
  try {
@@ -15698,7 +18071,7 @@ function registerUpdateCommand(program2) {
15698
18071
  }
15699
18072
  async function runUpdate(componentNames, options2) {
15700
18073
  const cwd = options2.cwd ?? process.cwd();
15701
- const lockPath = join7(cwd, "ocx.lock");
18074
+ const lockPath = join9(cwd, "ocx.lock");
15702
18075
  const config = await readOcxConfig(cwd);
15703
18076
  if (!config) {
15704
18077
  throw new ConfigError("No ocx.jsonc found. Run 'ocx init' first.");
@@ -15823,10 +18196,10 @@ Version cannot be empty. Use 'kdco/agents@1.2.0' or omit the version for latest.
15823
18196
  const fileObj = update.component.files.find((f) => f.path === file.path);
15824
18197
  if (!fileObj)
15825
18198
  continue;
15826
- const targetPath = join7(cwd, fileObj.target);
15827
- const targetDir = dirname5(targetPath);
15828
- if (!existsSync3(targetDir)) {
15829
- await mkdir6(targetDir, { recursive: true });
18199
+ const targetPath = join9(cwd, fileObj.target);
18200
+ const targetDir = dirname7(targetPath);
18201
+ if (!existsSync5(targetDir)) {
18202
+ await mkdir7(targetDir, { recursive: true });
15830
18203
  }
15831
18204
  await writeFile3(targetPath, file.content);
15832
18205
  if (options2.verbose) {
@@ -15947,21 +18320,49 @@ function outputDryRun(results, options2) {
15947
18320
  }
15948
18321
  }
15949
18322
  }
15950
- async function hashContent2(content2) {
15951
- return createHash2("sha256").update(content2).digest("hex");
18323
+ async function hashContent3(content2) {
18324
+ return createHash3("sha256").update(content2).digest("hex");
15952
18325
  }
15953
18326
  async function hashBundle2(files) {
15954
18327
  const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
15955
18328
  const manifestParts = [];
15956
18329
  for (const file of sorted) {
15957
- const hash = await hashContent2(file.content);
18330
+ const hash = await hashContent3(file.content);
15958
18331
  manifestParts.push(`${file.path}:${hash}`);
15959
18332
  }
15960
- return hashContent2(manifestParts.join(`
18333
+ return hashContent3(manifestParts.join(`
15961
18334
  `));
15962
18335
  }
18336
+
18337
+ // src/self-update/index.ts
18338
+ function shouldCheckForUpdate() {
18339
+ if (process.env.OCX_SELF_UPDATE === "off")
18340
+ return false;
18341
+ if (parseEnvBool(process.env.OCX_NO_UPDATE_CHECK, false))
18342
+ return false;
18343
+ if (process.env.CI)
18344
+ return false;
18345
+ if (!process.stdout.isTTY)
18346
+ return false;
18347
+ return true;
18348
+ }
18349
+ function registerUpdateCheckHook(program2) {
18350
+ program2.hook("postAction", async (thisCommand) => {
18351
+ if (thisCommand.name() === "update" && thisCommand.parent?.name() === "self") {
18352
+ return;
18353
+ }
18354
+ if (!shouldCheckForUpdate())
18355
+ return;
18356
+ try {
18357
+ const result = await checkForUpdate();
18358
+ if (result?.updateAvailable) {
18359
+ notifyUpdate(result.current, result.latest);
18360
+ }
18361
+ } catch {}
18362
+ });
18363
+ }
15963
18364
  // src/index.ts
15964
- var version = "1.2.2";
18365
+ var version = "1.3.0";
15965
18366
  async function main2() {
15966
18367
  const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
15967
18368
  registerInitCommand(program2);
@@ -15972,6 +18373,8 @@ async function main2() {
15972
18373
  registerRegistryCommand(program2);
15973
18374
  registerBuildCommand(program2);
15974
18375
  registerGhostCommand(program2);
18376
+ registerSelfCommand(program2);
18377
+ registerUpdateCheckHook(program2);
15975
18378
  await program2.parseAsync(process.argv);
15976
18379
  }
15977
18380
  if (import.meta.main) {
@@ -15988,4 +18391,4 @@ export {
15988
18391
  buildRegistry
15989
18392
  };
15990
18393
 
15991
- //# debugId=DCBED805AED28C6C64756E2164756E21
18394
+ //# debugId=FE5CBBF0E272CBE164756E2164756E21