ocx 1.2.1 → 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.1",
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,289 +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);
14403
- try {
14404
- await stat2(profilesDir);
14405
- return false;
14406
- } catch {
14407
- return true;
14408
- }
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
- }
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
- }
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
+ };
14461
14837
 
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) => {
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;
14465
14885
  try {
14466
- await runMigrate(options2);
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
+ }
14467
14925
  } catch (error) {
14468
- handleError(error);
14926
+ this.destroy(error);
14927
+ } finally {
14928
+ this.reading = false;
14469
14929
  }
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;
14481
14930
  }
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}`);
14931
+ async _exploreDir(path5, depth) {
14932
+ let files;
14933
+ try {
14934
+ files = await readdir2(path5, this._rdOptions);
14935
+ } catch (error) {
14936
+ this._onError(error);
14512
14937
  }
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")}`);
14938
+ return { files, depth, path: path5 };
14519
14939
  }
14520
- }
14521
-
14522
- // src/commands/ghost/opencode.ts
14523
- import { renameSync, rmSync } from "fs";
14524
- import { copyFile as copyFilePromise } from "fs/promises";
14525
- import path7 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);
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;
14540
14950
  }
14541
- if (stop === current)
14542
- break;
14543
- const parent = dirname3(current);
14544
- if (parent === current)
14545
- break;
14546
- current = parent;
14951
+ return entry;
14547
14952
  }
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
- }
14953
+ _onError(err) {
14954
+ if (isNormalFlowError(err) && !this.destroyed) {
14955
+ this.emit("warn", err);
14956
+ } else {
14957
+ this.destroy(err);
14559
14958
  }
14560
- if (stop === current)
14561
- break;
14562
- const parent = dirname3(current);
14563
- if (parent === current)
14564
- break;
14565
- current = parent;
14566
14959
  }
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);
14960
+ async _getEntryType(entry) {
14961
+ if (!entry && this._statsProp in entry) {
14962
+ return "";
14574
14963
  }
14575
- }
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);
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
+ }
14580
14990
  }
14581
14991
  }
14582
- for await (const dir of up({ targets: CONFIG_DIRS, start, stop })) {
14583
- excluded.add(dir);
14992
+ _includeAsFile(entry) {
14993
+ const stats = entry && entry[this._statsProp];
14994
+ return stats && this._wantsEverything && !stats.isDirectory();
14584
14995
  }
14585
- return excluded;
14586
14996
  }
14587
-
14588
- // src/utils/pattern-filter.ts
14589
- var {Glob: Glob2 } = globalThis.Bun;
14590
- function matchesAnyGlob(filePath, globs) {
14591
- return globs.some((g) => g.match(filePath));
14592
- }
14593
- function filterExcludedPaths(excludedPaths, includePatterns, excludePatterns) {
14594
- if (!includePatterns || includePatterns.length === 0) {
14595
- return new Set(excludedPaths);
14596
- }
14597
- const includeGlobs = includePatterns.map((p) => new Glob2(p));
14598
- const excludeGlobs = excludePatterns?.map((p) => new Glob2(p)) ?? [];
14599
- const filteredExclusions = new Set;
14600
- for (const path6 of excludedPaths) {
14601
- const matchesInclude = matchesAnyGlob(path6, includeGlobs);
14602
- const matchesExclude = matchesAnyGlob(path6, excludeGlobs);
14603
- if (matchesInclude && !matchesExclude) {
14604
- continue;
14605
- }
14606
- filteredExclusions.add(path6);
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(", ")}`);
14607
15009
  }
14608
- return filteredExclusions;
15010
+ options2.root = root;
15011
+ return new ReaddirpStream(options2);
14609
15012
  }
14610
15013
 
14611
- // src/utils/symlink-farm.ts
14612
- import { randomBytes } from "crypto";
14613
- import { mkdir as mkdir4, readdir as readdir3, rename as rename3, rm as rm2, stat as stat3, symlink as symlink2 } from "fs/promises";
14614
- import { tmpdir } from "os";
14615
- import { dirname as dirname4, isAbsolute, join as join5, relative as relative2 } from "path";
14616
- var STALE_SESSION_THRESHOLD_MS = 24 * 60 * 60 * 1000;
14617
- var REMOVING_THRESHOLD_MS = 60 * 60 * 1000;
14618
- var GHOST_DIR_PREFIX = "ocx-ghost-";
14619
- var REMOVING_SUFFIX = "-removing";
14620
- var GHOST_MARKER_FILE = ".ocx-ghost-marker";
14621
- async function createSymlinkFarm(sourceDir, excludePaths) {
14622
- if (!isAbsolute(sourceDir)) {
14623
- throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
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);
14624
15316
  }
14625
- const suffix = randomBytes(4).toString("hex");
14626
- const tempDir = join5(tmpdir(), `${GHOST_DIR_PREFIX}${suffix}`);
14627
- await Bun.write(join5(tempDir, GHOST_MARKER_FILE), "");
14628
- try {
14629
- const entries = await readdir3(sourceDir, { withFileTypes: true });
14630
- for (const entry of entries) {
14631
- const sourcePath = join5(sourceDir, entry.name);
14632
- if (excludePaths.has(sourcePath))
14633
- continue;
14634
- const targetPath = join5(tempDir, entry.name);
14635
- await symlink2(sourcePath, targetPath);
15317
+ };
15318
+ var addAndConvert = (main2, prop, item) => {
15319
+ let container = main2[prop];
15320
+ if (!(container instanceof Set)) {
15321
+ main2[prop] = container = new Set([container]);
15322
+ }
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];
15331
+ }
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];
15339
+ }
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));
14636
15349
  }
14637
- return tempDir;
15350
+ };
15351
+ try {
15352
+ return fs_watch(path5, {
15353
+ persistent: options2.persistent
15354
+ }, handleEvent);
14638
15355
  } catch (error) {
14639
- await rm2(tempDir, { recursive: true, force: true }).catch(() => {});
14640
- throw error;
15356
+ errHandler(error);
15357
+ return;
14641
15358
  }
14642
15359
  }
14643
- async function injectGhostFiles(tempDir, sourceDir, injectPaths) {
14644
- if (!isAbsolute(tempDir)) {
14645
- throw new Error(`tempDir must be an absolute path, got: ${tempDir}`);
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);
14646
15377
  }
14647
- if (!isAbsolute(sourceDir)) {
14648
- throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
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);
14649
15449
  }
14650
- for (const injectPath of injectPaths) {
14651
- const relativePath = relative2(sourceDir, injectPath);
14652
- if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
14653
- throw new Error(`injectPath must be within sourceDir: ${injectPath}`);
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);
14654
15458
  }
14655
- const targetPath = join5(tempDir, relativePath);
14656
- const parentDir = dirname4(targetPath);
14657
- if (parentDir !== tempDir) {
14658
- await mkdir4(parentDir, { recursive: true });
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
+ });
14659
15495
  }
14660
- try {
14661
- await symlink2(injectPath, targetPath);
14662
- } catch (err) {
14663
- if (err.code !== "EEXIST") {
14664
- throw new Error(`Failed to inject ${injectPath} \u2192 ${targetPath}: ${err.message}`);
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;
14665
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);
14666
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 });
16504
+ } catch (err) {
16505
+ if (err.code !== "EEXIST") {
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 };
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: [] };
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;
14667
16657
  }
14668
16658
  }
14669
16659
  async function cleanupSymlinkFarm(tempDir) {
14670
16660
  const removingPath = `${tempDir}${REMOVING_SUFFIX}`;
14671
16661
  try {
14672
- await rename3(tempDir, removingPath);
16662
+ await rename2(tempDir, removingPath);
14673
16663
  } catch {
14674
16664
  return;
14675
16665
  }
@@ -14677,24 +16667,24 @@ async function cleanupSymlinkFarm(tempDir) {
14677
16667
  }
14678
16668
  async function cleanupOrphanedGhostDirs(tempBase = tmpdir()) {
14679
16669
  let cleanedCount = 0;
14680
- if (!isAbsolute(tempBase)) {
16670
+ if (!isAbsolute2(tempBase)) {
14681
16671
  throw new Error(`tempBase must be an absolute path, got: ${tempBase}`);
14682
16672
  }
14683
16673
  let dirNames;
14684
16674
  try {
14685
- dirNames = await readdir3(tempBase);
16675
+ dirNames = await readdir4(tempBase);
14686
16676
  } catch {
14687
16677
  return 0;
14688
16678
  }
14689
16679
  for (const dirName of dirNames) {
14690
- const dirPath = join5(tempBase, dirName);
16680
+ const dirPath = join7(tempBase, dirName);
14691
16681
  const isRemovingDir = dirName.endsWith(REMOVING_SUFFIX);
14692
16682
  const isGhostDir = dirName.startsWith(GHOST_DIR_PREFIX) && !isRemovingDir;
14693
16683
  if (!isRemovingDir && !isGhostDir)
14694
16684
  continue;
14695
16685
  let stats;
14696
16686
  try {
14697
- stats = await stat3(dirPath);
16687
+ stats = await stat5(dirPath);
14698
16688
  } catch {
14699
16689
  continue;
14700
16690
  }
@@ -14707,7 +16697,7 @@ async function cleanupOrphanedGhostDirs(tempBase = tmpdir()) {
14707
16697
  try {
14708
16698
  if (isGhostDir) {
14709
16699
  const removingPath = `${dirPath}${REMOVING_SUFFIX}`;
14710
- await rename3(dirPath, removingPath);
16700
+ await rename2(dirPath, removingPath);
14711
16701
  await rm2(removingPath, { recursive: true, force: true });
14712
16702
  } else {
14713
16703
  await rm2(dirPath, { recursive: true, force: true });
@@ -14717,10 +16707,69 @@ async function cleanupOrphanedGhostDirs(tempBase = tmpdir()) {
14717
16707
  }
14718
16708
  return cleanedCount;
14719
16709
  }
16710
+ async function computeSymlinkPlan(sourceDir, projectRoot, matcher) {
16711
+ if (!isAbsolute2(sourceDir)) {
16712
+ throw new Error(`sourceDir must be an absolute path, got: ${sourceDir}`);
16713
+ }
16714
+ const plan = {
16715
+ wholeDirs: [],
16716
+ files: [],
16717
+ partialDirs: new Map
16718
+ };
16719
+ const entries = await readdir4(sourceDir, { withFileTypes: true });
16720
+ for (const entry of entries) {
16721
+ const sourcePath = join7(sourceDir, entry.name);
16722
+ const relativePath = normalizeForMatching(sourcePath, projectRoot);
16723
+ const disposition = matcher.getDisposition(relativePath);
16724
+ if (disposition.type === "excluded") {
16725
+ continue;
16726
+ }
16727
+ if (disposition.type === "included") {
16728
+ if (entry.isDirectory()) {
16729
+ plan.wholeDirs.push(entry.name);
16730
+ } else {
16731
+ plan.files.push(entry.name);
16732
+ }
16733
+ continue;
16734
+ }
16735
+ if (entry.isDirectory()) {
16736
+ const nestedPlan = await computeSymlinkPlan(sourcePath, projectRoot, matcher);
16737
+ plan.partialDirs.set(entry.name, nestedPlan);
16738
+ } else {
16739
+ plan.files.push(entry.name);
16740
+ }
16741
+ }
16742
+ return plan;
16743
+ }
16744
+ async function executeSymlinkPlan(plan, sourceRoot, targetRoot) {
16745
+ if (!isAbsolute2(sourceRoot)) {
16746
+ throw new Error(`sourceRoot must be an absolute path, got: ${sourceRoot}`);
16747
+ }
16748
+ if (!isAbsolute2(targetRoot)) {
16749
+ throw new Error(`targetRoot must be an absolute path, got: ${targetRoot}`);
16750
+ }
16751
+ for (const dirName of plan.wholeDirs) {
16752
+ const sourcePath = join7(sourceRoot, dirName);
16753
+ const targetPath = join7(targetRoot, dirName);
16754
+ await symlink2(sourcePath, targetPath);
16755
+ }
16756
+ for (const fileName of plan.files) {
16757
+ const sourcePath = join7(sourceRoot, fileName);
16758
+ const targetPath = join7(targetRoot, fileName);
16759
+ await symlink2(sourcePath, targetPath);
16760
+ }
16761
+ for (const [dirName, nestedPlan] of plan.partialDirs) {
16762
+ const sourcePath = join7(sourceRoot, dirName);
16763
+ const targetPath = join7(targetRoot, dirName);
16764
+ await mkdir4(targetPath, { recursive: true });
16765
+ await executeSymlinkPlan(nestedPlan, sourcePath, targetPath);
16766
+ }
16767
+ }
14720
16768
 
14721
16769
  // src/utils/terminal-title.ts
14722
16770
  import path6 from "path";
14723
16771
  var MAX_BRANCH_LENGTH = 20;
16772
+ var titleSaved = false;
14724
16773
  function isInsideTmux() {
14725
16774
  return Boolean(process.env.TMUX);
14726
16775
  }
@@ -14741,6 +16790,25 @@ function setTerminalName(name) {
14741
16790
  setTmuxWindowName(name);
14742
16791
  setTerminalTitle(name);
14743
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
+ }
14744
16812
  function formatTerminalName(cwd, profileName, gitInfo) {
14745
16813
  const repoName = gitInfo.repoName ?? path6.basename(cwd);
14746
16814
  if (!gitInfo.branch) {
@@ -14752,7 +16820,7 @@ function formatTerminalName(cwd, profileName, gitInfo) {
14752
16820
 
14753
16821
  // src/commands/ghost/opencode.ts
14754
16822
  function registerGhostOpenCodeCommand(parent) {
14755
- 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) => {
14756
16824
  try {
14757
16825
  const args = command.args;
14758
16826
  await runGhostOpenCode(args, options2);
@@ -14766,11 +16834,6 @@ async function runGhostOpenCode(args, options2) {
14766
16834
  if (!await manager.isInitialized()) {
14767
16835
  throw new ProfilesNotInitializedError;
14768
16836
  }
14769
- if (!options2.quiet && await needsMigration()) {
14770
- console.log("Notice: Found legacy config at ~/.config/ocx/");
14771
- console.log(`Run 'ocx ghost migrate' to upgrade to the new profiles system.
14772
- `);
14773
- }
14774
16837
  await cleanupOrphanedGhostDirs();
14775
16838
  const profileName = await manager.getCurrent(options2.profile);
14776
16839
  const profile = await manager.get(profileName);
@@ -14783,26 +16846,29 @@ async function runGhostOpenCode(args, options2) {
14783
16846
  }
14784
16847
  const cwd = process.cwd();
14785
16848
  const gitContext = await detectGitRepo(cwd);
14786
- const gitRoot = gitContext?.workTree ?? cwd;
14787
- const discoveredPaths = await discoverProjectFiles(cwd, gitRoot);
14788
16849
  const ghostConfig = profile.ghost;
14789
- const excludePaths = filterExcludedPaths(discoveredPaths, ghostConfig.include, ghostConfig.exclude);
14790
- const tempDir = await createSymlinkFarm(cwd, excludePaths);
14791
- const ghostFiles = await discoverProjectFiles(profileDir, profileDir);
14792
- await injectGhostFiles(tempDir, profileDir, ghostFiles);
14793
- if (profile.hasAgents) {
14794
- const agentsPath = getProfileAgents(profileName);
14795
- const destAgentsPath = path7.join(tempDir, "AGENTS.md");
14796
- await copyFilePromise(agentsPath, destAgentsPath);
14797
- }
16850
+ const tempDir = await createSymlinkFarm(cwd, {
16851
+ includePatterns: ghostConfig.include,
16852
+ excludePatterns: ghostConfig.exclude
16853
+ });
16854
+ const overlayFiles = await injectProfileOverlay(tempDir, profileDir, ghostConfig.include);
14798
16855
  let cleanupDone = false;
16856
+ let fileSync;
16857
+ fileSync = createFileSync(tempDir, cwd, { overlayFiles });
14799
16858
  const performCleanup = async () => {
14800
16859
  if (cleanupDone)
14801
16860
  return;
14802
16861
  cleanupDone = true;
14803
16862
  await cleanupSymlinkFarm(tempDir);
14804
16863
  };
16864
+ const shouldRename = options2.rename !== false && ghostConfig.renameWindow !== false;
14805
16865
  const exitHandler = () => {
16866
+ if (shouldRename) {
16867
+ restoreTerminalTitle();
16868
+ }
16869
+ if (fileSync) {
16870
+ fileSync.close().catch(() => {});
16871
+ }
14806
16872
  if (!cleanupDone && tempDir) {
14807
16873
  try {
14808
16874
  const removingPath = `${tempDir}${REMOVING_SUFFIX}`;
@@ -14817,8 +16883,11 @@ async function runGhostOpenCode(args, options2) {
14817
16883
  const sigtermHandler = () => proc?.kill("SIGTERM");
14818
16884
  process.on("SIGINT", sigintHandler);
14819
16885
  process.on("SIGTERM", sigtermHandler);
14820
- const gitInfo = await getGitInfo(cwd);
14821
- setTerminalName(formatTerminalName(cwd, profileName, gitInfo));
16886
+ if (shouldRename) {
16887
+ saveTerminalTitle();
16888
+ const gitInfo = await getGitInfo(cwd);
16889
+ setTerminalName(formatTerminalName(cwd, profileName, gitInfo));
16890
+ }
14822
16891
  proc = Bun.spawn({
14823
16892
  cmd: ["opencode", ...args],
14824
16893
  cwd: tempDir,
@@ -14838,14 +16907,62 @@ async function runGhostOpenCode(args, options2) {
14838
16907
  });
14839
16908
  try {
14840
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();
14841
16928
  process.exit(exitCode);
14842
- } finally {
16929
+ } catch (error) {
14843
16930
  process.off("SIGINT", sigintHandler);
14844
16931
  process.off("SIGTERM", sigtermHandler);
14845
16932
  process.off("exit", exitHandler);
16933
+ if (fileSync) {
16934
+ await fileSync.close();
16935
+ }
14846
16936
  await performCleanup();
16937
+ throw error;
14847
16938
  }
14848
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
+ }
14849
16966
 
14850
16967
  // src/commands/ghost/profile/add.ts
14851
16968
  function registerProfileAddCommand(parent) {
@@ -15381,13 +17498,12 @@ function registerGhostCommand(program2) {
15381
17498
  registerGhostSearchCommand(ghost);
15382
17499
  registerGhostOpenCodeCommand(ghost);
15383
17500
  registerGhostProfileCommand(ghost);
15384
- registerGhostMigrateCommand(ghost);
15385
17501
  }
15386
17502
 
15387
17503
  // src/commands/init.ts
15388
- import { existsSync as existsSync2 } from "fs";
15389
- import { cp, mkdir as mkdir5, readdir as readdir4, readFile, rm as rm3, writeFile as writeFile2 } from "fs/promises";
15390
- 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";
15391
17507
  var TEMPLATE_REPO = "kdcokenny/ocx";
15392
17508
  var TEMPLATE_PATH = "examples/registry-starter";
15393
17509
  function registerInitCommand(program2) {
@@ -15405,8 +17521,8 @@ function registerInitCommand(program2) {
15405
17521
  }
15406
17522
  async function runInit(options2) {
15407
17523
  const cwd = options2.cwd ?? process.cwd();
15408
- const configPath = join6(cwd, "ocx.jsonc");
15409
- if (existsSync2(configPath)) {
17524
+ const configPath = join8(cwd, "ocx.jsonc");
17525
+ if (existsSync3(configPath)) {
15410
17526
  throw new ConflictError(`ocx.jsonc already exists at ${configPath}
15411
17527
 
15412
17528
  ` + `To reset, delete the config and run init again:
@@ -15453,7 +17569,7 @@ async function runInitRegistry(directory, options2) {
15453
17569
  if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(namespace)) {
15454
17570
  throw new ValidationError("Invalid namespace format: must start with letter/number, use hyphens only between segments (e.g., 'my-registry')");
15455
17571
  }
15456
- const existingFiles = await readdir4(cwd).catch(() => []);
17572
+ const existingFiles = await readdir6(cwd).catch(() => []);
15457
17573
  const hasVisibleFiles = existingFiles.some((f) => !f.startsWith("."));
15458
17574
  if (hasVisibleFiles && !options2.force) {
15459
17575
  throw new ConflictError("Directory is not empty. Use --force to overwrite existing files.");
@@ -15464,7 +17580,7 @@ async function runInitRegistry(directory, options2) {
15464
17580
  if (spin)
15465
17581
  spin.text = options2.local ? "Copying template..." : "Fetching template...";
15466
17582
  if (options2.local) {
15467
- await mkdir5(cwd, { recursive: true });
17583
+ await mkdir6(cwd, { recursive: true });
15468
17584
  await copyDir(options2.local, cwd);
15469
17585
  } else {
15470
17586
  const version = options2.canary ? "main" : await getLatestVersion();
@@ -15480,7 +17596,7 @@ async function runInitRegistry(directory, options2) {
15480
17596
  logger.info("");
15481
17597
  logger.info("Next steps:");
15482
17598
  logger.info(" 1. bun install");
15483
- logger.info(" 2. Edit registry.json with your components");
17599
+ logger.info(" 2. Edit registry.jsonc with your components");
15484
17600
  logger.info(" 3. bun run build");
15485
17601
  logger.info("");
15486
17602
  logger.info("Deploy to:");
@@ -15512,10 +17628,10 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
15512
17628
  if (!response.ok || !response.body) {
15513
17629
  throw new NetworkError(`Failed to fetch template from ${tarballUrl}: ${response.statusText}`);
15514
17630
  }
15515
- const tempDir = join6(destDir, ".ocx-temp");
15516
- await mkdir5(tempDir, { recursive: true });
17631
+ const tempDir = join8(destDir, ".ocx-temp");
17632
+ await mkdir6(tempDir, { recursive: true });
15517
17633
  try {
15518
- const tarPath = join6(tempDir, "template.tar.gz");
17634
+ const tarPath = join8(tempDir, "template.tar.gz");
15519
17635
  const arrayBuffer = await response.arrayBuffer();
15520
17636
  await writeFile2(tarPath, Buffer.from(arrayBuffer));
15521
17637
  const proc = Bun.spawn(["tar", "-xzf", tarPath, "-C", tempDir], {
@@ -15527,12 +17643,12 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
15527
17643
  const stderr = await new Response(proc.stderr).text();
15528
17644
  throw new Error(`Failed to extract template: ${stderr}`);
15529
17645
  }
15530
- const extractedDirs = await readdir4(tempDir);
17646
+ const extractedDirs = await readdir6(tempDir);
15531
17647
  const extractedDir = extractedDirs.find((d) => d.startsWith("ocx-"));
15532
17648
  if (!extractedDir) {
15533
17649
  throw new Error("Failed to find extracted template directory");
15534
17650
  }
15535
- const templateDir = join6(tempDir, extractedDir, TEMPLATE_PATH);
17651
+ const templateDir = join8(tempDir, extractedDir, TEMPLATE_PATH);
15536
17652
  await copyDir(templateDir, destDir);
15537
17653
  } finally {
15538
17654
  await rm3(tempDir, { recursive: true, force: true });
@@ -15540,15 +17656,15 @@ async function fetchAndExtractTemplate(destDir, version, verbose) {
15540
17656
  }
15541
17657
  async function replacePlaceholders(dir, values) {
15542
17658
  const filesToProcess = [
15543
- "registry.json",
17659
+ "registry.jsonc",
15544
17660
  "package.json",
15545
17661
  "wrangler.jsonc",
15546
17662
  "README.md",
15547
17663
  "AGENTS.md"
15548
17664
  ];
15549
17665
  for (const file of filesToProcess) {
15550
- const filePath = join6(dir, file);
15551
- if (!existsSync2(filePath))
17666
+ const filePath = join8(dir, file);
17667
+ if (!existsSync3(filePath))
15552
17668
  continue;
15553
17669
  let content2 = await readFile(filePath).then((b) => b.toString());
15554
17670
  content2 = content2.replace(/my-registry/g, values.namespace);
@@ -15557,11 +17673,393 @@ async function replacePlaceholders(dir, values) {
15557
17673
  }
15558
17674
  }
15559
17675
 
15560
- // 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
15561
17913
  import { createHash as createHash2 } from "crypto";
15562
- import { existsSync as existsSync3 } from "fs";
15563
- import { mkdir as mkdir6, writeFile as writeFile3 } from "fs/promises";
15564
- 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";
15565
18063
  function registerUpdateCommand(program2) {
15566
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) => {
15567
18065
  try {
@@ -15573,7 +18071,7 @@ function registerUpdateCommand(program2) {
15573
18071
  }
15574
18072
  async function runUpdate(componentNames, options2) {
15575
18073
  const cwd = options2.cwd ?? process.cwd();
15576
- const lockPath = join7(cwd, "ocx.lock");
18074
+ const lockPath = join9(cwd, "ocx.lock");
15577
18075
  const config = await readOcxConfig(cwd);
15578
18076
  if (!config) {
15579
18077
  throw new ConfigError("No ocx.jsonc found. Run 'ocx init' first.");
@@ -15698,10 +18196,10 @@ Version cannot be empty. Use 'kdco/agents@1.2.0' or omit the version for latest.
15698
18196
  const fileObj = update.component.files.find((f) => f.path === file.path);
15699
18197
  if (!fileObj)
15700
18198
  continue;
15701
- const targetPath = join7(cwd, fileObj.target);
15702
- const targetDir = dirname5(targetPath);
15703
- if (!existsSync3(targetDir)) {
15704
- 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 });
15705
18203
  }
15706
18204
  await writeFile3(targetPath, file.content);
15707
18205
  if (options2.verbose) {
@@ -15822,21 +18320,49 @@ function outputDryRun(results, options2) {
15822
18320
  }
15823
18321
  }
15824
18322
  }
15825
- async function hashContent2(content2) {
15826
- return createHash2("sha256").update(content2).digest("hex");
18323
+ async function hashContent3(content2) {
18324
+ return createHash3("sha256").update(content2).digest("hex");
15827
18325
  }
15828
18326
  async function hashBundle2(files) {
15829
18327
  const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
15830
18328
  const manifestParts = [];
15831
18329
  for (const file of sorted) {
15832
- const hash = await hashContent2(file.content);
18330
+ const hash = await hashContent3(file.content);
15833
18331
  manifestParts.push(`${file.path}:${hash}`);
15834
18332
  }
15835
- return hashContent2(manifestParts.join(`
18333
+ return hashContent3(manifestParts.join(`
15836
18334
  `));
15837
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
+ }
15838
18364
  // src/index.ts
15839
- var version = "1.2.1";
18365
+ var version = "1.3.0";
15840
18366
  async function main2() {
15841
18367
  const program2 = new Command().name("ocx").description("OpenCode Extensions - Install agents, skills, plugins, and commands").version(version);
15842
18368
  registerInitCommand(program2);
@@ -15847,6 +18373,8 @@ async function main2() {
15847
18373
  registerRegistryCommand(program2);
15848
18374
  registerBuildCommand(program2);
15849
18375
  registerGhostCommand(program2);
18376
+ registerSelfCommand(program2);
18377
+ registerUpdateCheckHook(program2);
15850
18378
  await program2.parseAsync(process.argv);
15851
18379
  }
15852
18380
  if (import.meta.main) {
@@ -15863,4 +18391,4 @@ export {
15863
18391
  buildRegistry
15864
18392
  };
15865
18393
 
15866
- //# debugId=FF19E8C89476382D64756E2164756E21
18394
+ //# debugId=FE5CBBF0E272CBE164756E2164756E21