@edgemodders/edgedeck-mcp 0.28.0 → 0.29.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/bin/edgedeck.mjs CHANGED
@@ -23,7 +23,7 @@
23
23
  // edgedeck mcp serve the same verbs as MCP tools over stdio
24
24
  //
25
25
  // Options: --library <folder> (default %USERPROFILE%\EDGEdeck), --game <folder> (default: the
26
- // library's remembered game, else the Steam folder), --json.
26
+ // library's remembered game, else the game found through Steam, GOG or Epic), --json.
27
27
 
28
28
  import { createRequire } from "node:module";
29
29
  import * as fs from "node:fs";
package/dist/core.js CHANGED
@@ -1250,8 +1250,8 @@ var require_semaphore = __commonJS({
1250
1250
  this._waiting = [];
1251
1251
  }
1252
1252
  lock(thunk) {
1253
- return new Promise((resolve3, reject) => {
1254
- this._waiting.push({ thunk, resolve: resolve3, reject });
1253
+ return new Promise((resolve5, reject) => {
1254
+ this._waiting.push({ thunk, resolve: resolve5, reject });
1255
1255
  this.runNext();
1256
1256
  });
1257
1257
  }
@@ -2877,9 +2877,9 @@ ${JSON.stringify(message, null, 4)}`);
2877
2877
  if (typeof cancellationStrategy.sender.enableCancellation === "function") {
2878
2878
  cancellationStrategy.sender.enableCancellation(requestMessage);
2879
2879
  }
2880
- return new Promise(async (resolve3, reject) => {
2880
+ return new Promise(async (resolve5, reject) => {
2881
2881
  const resolveWithCleanup = (r) => {
2882
- resolve3(r);
2882
+ resolve5(r);
2883
2883
  cancellationStrategy.sender.cleanup(id);
2884
2884
  disposable?.dispose();
2885
2885
  };
@@ -3308,10 +3308,10 @@ var require_ril = __commonJS({
3308
3308
  return api_1.Disposable.create(() => this.stream.off("end", listener));
3309
3309
  }
3310
3310
  write(data, encoding) {
3311
- return new Promise((resolve3, reject) => {
3311
+ return new Promise((resolve5, reject) => {
3312
3312
  const callback = (error) => {
3313
3313
  if (error === void 0 || error === null) {
3314
- resolve3();
3314
+ resolve5();
3315
3315
  } else {
3316
3316
  reject(error);
3317
3317
  }
@@ -3447,9 +3447,9 @@ var require_main = __commonJS({
3447
3447
  exports2.createMessageConnection = createMessageConnection2;
3448
3448
  var ril_1 = __importDefault(require_ril());
3449
3449
  ril_1.default.install();
3450
- var path14 = __importStar(require("path"));
3450
+ var path15 = __importStar(require("path"));
3451
3451
  var os4 = __importStar(require("os"));
3452
- var fs15 = __importStar(require("fs"));
3452
+ var fs16 = __importStar(require("fs"));
3453
3453
  var crypto_1 = require("crypto");
3454
3454
  var net_1 = require("net");
3455
3455
  var api_1 = require_api();
@@ -3590,7 +3590,7 @@ var require_main = __commonJS({
3590
3590
  }
3591
3591
  let randomLength = 32;
3592
3592
  const fixedLength = "/lsp-.sock".length;
3593
- const tmpDir = fs15.realpathSync(XDG_RUNTIME_DIR ?? os4.tmpdir());
3593
+ const tmpDir = fs16.realpathSync(XDG_RUNTIME_DIR ?? os4.tmpdir());
3594
3594
  const limit = safeIpcPathLengths.get(process.platform);
3595
3595
  if (limit !== void 0) {
3596
3596
  randomLength = Math.min(limit - tmpDir.length - fixedLength, randomLength);
@@ -3599,14 +3599,14 @@ var require_main = __commonJS({
3599
3599
  throw new Error(`Unable to generate a random pipe name with ${randomLength} characters.`);
3600
3600
  }
3601
3601
  const randomSuffix = (0, crypto_1.randomBytes)(Math.floor(randomLength / 2)).toString("hex");
3602
- return path14.join(tmpDir, `lsp-${randomSuffix}.sock`);
3602
+ return path15.join(tmpDir, `lsp-${randomSuffix}.sock`);
3603
3603
  }
3604
3604
  function createClientPipeTransport(pipeName, encoding = "utf-8") {
3605
3605
  let connectResolve;
3606
- const connected = new Promise((resolve3, _reject) => {
3607
- connectResolve = resolve3;
3606
+ const connected = new Promise((resolve5, _reject) => {
3607
+ connectResolve = resolve5;
3608
3608
  });
3609
- return new Promise((resolve3, reject) => {
3609
+ return new Promise((resolve5, reject) => {
3610
3610
  const server = (0, net_1.createServer)((socket) => {
3611
3611
  server.close();
3612
3612
  connectResolve([
@@ -3617,7 +3617,7 @@ var require_main = __commonJS({
3617
3617
  server.on("error", reject);
3618
3618
  server.listen(pipeName, () => {
3619
3619
  server.removeListener("error", reject);
3620
- resolve3({
3620
+ resolve5({
3621
3621
  onConnected: () => {
3622
3622
  return connected;
3623
3623
  }
@@ -3634,10 +3634,10 @@ var require_main = __commonJS({
3634
3634
  }
3635
3635
  function createClientSocketTransport(port, encoding = "utf-8") {
3636
3636
  let connectResolve;
3637
- const connected = new Promise((resolve3, _reject) => {
3638
- connectResolve = resolve3;
3637
+ const connected = new Promise((resolve5, _reject) => {
3638
+ connectResolve = resolve5;
3639
3639
  });
3640
- return new Promise((resolve3, reject) => {
3640
+ return new Promise((resolve5, reject) => {
3641
3641
  const server = (0, net_1.createServer)((socket) => {
3642
3642
  server.close();
3643
3643
  connectResolve([
@@ -3654,7 +3654,7 @@ var require_main = __commonJS({
3654
3654
  return;
3655
3655
  }
3656
3656
  const boundPort = address.port;
3657
- resolve3({
3657
+ resolve5({
3658
3658
  port: () => boundPort,
3659
3659
  onConnected: () => {
3660
3660
  return connected;
@@ -3700,6 +3700,7 @@ __export(core_exports, {
3700
3700
  BridgeClient: () => BridgeClient,
3701
3701
  BridgeRepo: () => BridgeRepo,
3702
3702
  DefaultLoadout: () => DefaultLoadout,
3703
+ FrameworkSources: () => FrameworkSources,
3703
3704
  FrameworksSection: () => FrameworksSection,
3704
3705
  KnownModNames: () => KnownModNames,
3705
3706
  ManifestName: () => ManifestName,
@@ -3737,6 +3738,7 @@ __export(core_exports, {
3737
3738
  deleteLoadout: () => deleteLoadout,
3738
3739
  deleteSection: () => deleteSection,
3739
3740
  deploy: () => deploy,
3741
+ deployMethod: () => deployMethod,
3740
3742
  deployRedmod: () => deployRedmod,
3741
3743
  deployReportsFolder: () => deployReportsFolder,
3742
3744
  deployedRedmodPackages: () => deployedRedmodPackages,
@@ -3768,12 +3770,15 @@ __export(core_exports, {
3768
3770
  formatBytes: () => formatBytes,
3769
3771
  forwarderCommand: () => forwarderCommand,
3770
3772
  frameworkDetail: () => frameworkDetail,
3773
+ frameworkFamily: () => frameworkFamily,
3774
+ frameworkStates: () => frameworkStates,
3771
3775
  frameworkSummary: () => frameworkSummary,
3772
3776
  gameExe: () => gameExe,
3773
3777
  gameRunning: () => gameRunning,
3774
3778
  gameVersion: () => gameVersion,
3775
3779
  guessName: () => guessName,
3776
3780
  hasWebSocket: () => hasWebSocket,
3781
+ holdsLibrary: () => holdsLibrary,
3777
3782
  idFor: () => idFor,
3778
3783
  importFromVortex: () => importFromVortex,
3779
3784
  importLoadout: () => importLoadout,
@@ -3785,6 +3790,7 @@ __export(core_exports, {
3785
3790
  isGameFolder: () => isGameFolder,
3786
3791
  keepDownload: () => keepDownload,
3787
3792
  latestBridgeRelease: () => latestBridgeRelease,
3793
+ latestFrameworkRelease: () => latestFrameworkRelease,
3788
3794
  launchTarget: () => launchTarget,
3789
3795
  libraryFolder: () => libraryFolder,
3790
3796
  librarySize: () => librarySize,
@@ -3797,6 +3803,7 @@ __export(core_exports, {
3797
3803
  lostFiles: () => lostFiles,
3798
3804
  makeActive: () => makeActive,
3799
3805
  md5Of: () => md5Of,
3806
+ missingFrameworks: () => missingFrameworks,
3800
3807
  modDeployPaths: () => modDeployPaths,
3801
3808
  modFolder: () => modFolder,
3802
3809
  modPageUrl: () => modPageUrl,
@@ -3805,8 +3812,10 @@ __export(core_exports, {
3805
3812
  modsRemoved: () => modsRemoved,
3806
3813
  modsTouched: () => modsTouched,
3807
3814
  moveInSection: () => moveInSection,
3815
+ moveLibrary: () => moveLibrary,
3808
3816
  moveMod: () => moveMod,
3809
3817
  moveSection: () => moveSection,
3818
+ moveTarget: () => moveTarget,
3810
3819
  needsRefresh: () => needsRefresh,
3811
3820
  newLoadout: () => newLoadout,
3812
3821
  newestFile: () => newestFile,
@@ -3836,6 +3845,7 @@ __export(core_exports, {
3836
3845
  renameFamily: () => renameFamily,
3837
3846
  renameLoadout: () => renameLoadout,
3838
3847
  renameSection: () => renameSection,
3848
+ sameDrive: () => sameDrive,
3839
3849
  sameNameMod: () => sameNameMod,
3840
3850
  saveLibrary: () => saveLibrary,
3841
3851
  saveLoadout: () => saveLoadout,
@@ -4039,8 +4049,8 @@ var NexusClient = class {
4039
4049
  get usesToken() {
4040
4050
  return "bearer" in this.auth;
4041
4051
  }
4042
- async get(path14, retried = false) {
4043
- const res = await fetch(`${this.options.api ?? NexusApi}${path14}`, {
4052
+ async get(path15, retried = false) {
4053
+ const res = await fetch(`${this.options.api ?? NexusApi}${path15}`, {
4044
4054
  headers: {
4045
4055
  ..."bearer" in this.auth ? { authorization: `Bearer ${this.auth.bearer}` } : { apikey: this.auth.apiKey },
4046
4056
  accept: "application/json",
@@ -4057,7 +4067,7 @@ var NexusClient = class {
4057
4067
  const bearer = await this.options.refresh();
4058
4068
  if (bearer) {
4059
4069
  this.auth = { bearer };
4060
- return this.get(path14, true);
4070
+ return this.get(path15, true);
4061
4071
  }
4062
4072
  }
4063
4073
  if (!res.ok) {
@@ -4163,11 +4173,11 @@ async function installDotnetRuntime(log) {
4163
4173
  try {
4164
4174
  await download(DotnetInstallScript, script);
4165
4175
  log(`Installing the .NET 10 runtime into ${root}`);
4166
- await new Promise((resolve3, reject) => {
4176
+ await new Promise((resolve5, reject) => {
4167
4177
  (0, import_node_child_process3.execFile)("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script, "-Channel", "10.0", "-Runtime", "dotnet", "-InstallDir", root, "-NoPath"], { windowsHide: true, maxBuffer: 1 << 24 }, (err, stdout, stderr) => {
4168
4178
  for (const line of `${stdout}
4169
4179
  ${stderr}`.split(/\r?\n/)) if (line.trim()) log(` ${line.trim()}`);
4170
- err ? reject(new Error(`dotnet-install failed: ${err.message}`)) : resolve3();
4180
+ err ? reject(new Error(`dotnet-install failed: ${err.message}`)) : resolve5();
4171
4181
  });
4172
4182
  });
4173
4183
  } finally {
@@ -4699,6 +4709,21 @@ function canHardlink(libraryRoot, gamePath) {
4699
4709
  return false;
4700
4710
  }
4701
4711
  }
4712
+ var hardlinkable = /* @__PURE__ */ new Map();
4713
+ function deployMethod(libraryRoot, gamePath, current) {
4714
+ try {
4715
+ if (fs8.statSync(libraryRoot).dev !== fs8.statSync(gamePath).dev) return "copy";
4716
+ } catch {
4717
+ return current ?? "copy";
4718
+ }
4719
+ if (current === "link") return "link";
4720
+ const k = `${path7.resolve(libraryRoot).toLowerCase()}|${path7.resolve(gamePath).toLowerCase()}`;
4721
+ if (!hardlinkable.has(k)) hardlinkable.set(k, canHardlink(libraryRoot, gamePath));
4722
+ return hardlinkable.get(k) ? "link" : "copy";
4723
+ }
4724
+ function otherMethod(entry, method) {
4725
+ return method === "link" !== !!entry.ino;
4726
+ }
4702
4727
  function sourceReplaced(entry, method, d) {
4703
4728
  if (d.content !== void 0) return false;
4704
4729
  const src = identity(d.src);
@@ -4706,10 +4731,10 @@ function sourceReplaced(entry, method, d) {
4706
4731
  if (method === "link" && entry.ino) return src.ino !== entry.ino;
4707
4732
  return src.size !== entry.size || Math.abs(src.mtimeMs - entry.mtimeMs) >= 2e3;
4708
4733
  }
4709
- function stillOurs(entry, method, gamePath) {
4734
+ function stillOurs(entry, _method, gamePath) {
4710
4735
  const id = identity(gameFile(gamePath, entry.path));
4711
4736
  if (!id) return "missing";
4712
- if (method === "link" && entry.ino) {
4737
+ if (entry.ino) {
4713
4738
  if (id.ino !== entry.ino) return "changed";
4714
4739
  if (id.nlink < 2 && entry.mod !== Generated) return "sourceMissing";
4715
4740
  return "ok";
@@ -4725,7 +4750,7 @@ function generatedDiffers(file, content) {
4725
4750
  }
4726
4751
  function plan(lib, gamePath) {
4727
4752
  const manifest = readManifest(gamePath);
4728
- const method = manifest?.method ?? (canHardlink(lib.root, gamePath) ? "link" : "copy");
4753
+ const method = deployMethod(lib.root, gamePath, manifest?.method);
4729
4754
  const desired = /* @__PURE__ */ new Map();
4730
4755
  const conflicts = /* @__PURE__ */ new Map();
4731
4756
  for (const mod of lib.mods) {
@@ -4756,6 +4781,7 @@ function plan(lib, gamePath) {
4756
4781
  if (!e || (d.mod?.id ?? Generated) !== e.mod) return true;
4757
4782
  if (d.content !== void 0 && generatedDiffers(gameFile(gamePath, d.rel), d.content)) return true;
4758
4783
  if (stillOurs(e, method, gamePath) !== "ok") return true;
4784
+ if (d.content === void 0 && otherMethod(e, method)) return true;
4759
4785
  return sourceReplaced(e, method, d);
4760
4786
  });
4761
4787
  return { method, desired: [...desired.values()], conflicts: [...conflicts.values()], stale, add, vortex: vortexManages(gamePath) };
@@ -4890,6 +4916,7 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
4890
4916
  const p = plan(lib, gamePath);
4891
4917
  const manifest = readManifest(gamePath) ?? { version: 1, library: lib.root, gamePath, method: p.method, time: "", files: [], dirs: [] };
4892
4918
  manifest.library = lib.root;
4919
+ manifest.method = p.method;
4893
4920
  const result = { linked: 0, removed: 0, kept: 0, backedUp: 0, method: manifest.method, skipped: [], changes: [], modsInGame: 0, lost: lostFiles(p) };
4894
4921
  const entries = new Map(manifest.files.map((e) => [key(e.path), e]));
4895
4922
  const scope = only ? modDeployPaths(gamePath, p, only) : void 0;
@@ -5016,6 +5043,7 @@ function pendingChanges(lib, gamePath, p) {
5016
5043
  const st = stillOurs(e, p.method, gamePath);
5017
5044
  if (st === "missing") out.push({ path: d.rel, mod: id, kind: "restore", detail: "missing from the game folder" });
5018
5045
  else if (st === "changed") out.push({ path: d.rel, mod: id, kind: "restore", detail: "changed in the game folder; deploy asks whether to keep it" });
5046
+ else if (d.content === void 0 && otherMethod(e, p.method)) out.push({ path: d.rel, mod: id, kind: "relink", detail: p.method === "link" ? "the library is on the game's drive now: a hardlink instead of a copy" : "the library is on another drive now: a copy instead of a hardlink" });
5019
5047
  else out.push({ path: d.rel, mod: id, kind: "relink", detail: d.content !== void 0 ? "the generated content changed (load order)" : "the library file was replaced (new version or rebuild)" });
5020
5048
  }
5021
5049
  }
@@ -5192,28 +5220,165 @@ function frameworkDetail(c) {
5192
5220
  return lines;
5193
5221
  }
5194
5222
 
5195
- // src/loadouts.ts
5223
+ // src/frameworkGet.ts
5224
+ var FrameworkSources = [
5225
+ { name: "RED4ext", nexusId: 2380, repo: "WopsS/RED4ext", asset: /^red4ext-[\d.]+\.zip$/i, signature: "red4ext/RED4ext.dll" },
5226
+ { name: "Cyber Engine Tweaks", nexusId: 107, repo: "maximegmd/CyberEngineTweaks", asset: /^cet_[\d_]+\.zip$/i, signature: "bin/x64/plugins/cyber_engine_tweaks.asi" },
5227
+ { name: "redscript", nexusId: 1511, repo: "jac3km4/redscript", asset: /^redscript-v?[\d.]+-windows\.zip$/i, signature: "engine/tools/scc.exe" },
5228
+ { name: "ArchiveXL", nexusId: 4198, repo: "psiberx/cp2077-archive-xl", asset: /^ArchiveXL-[\d.]+\.zip$/i, signature: "red4ext/plugins/ArchiveXL/ArchiveXL.dll" },
5229
+ { name: "TweakXL", nexusId: 4197, repo: "psiberx/cp2077-tweak-xl", asset: /^TweakXL-[\d.]+\.zip$/i, signature: "red4ext/plugins/TweakXL/TweakXL.dll" },
5230
+ { name: "Codeware", nexusId: 7780, repo: "psiberx/cp2077-codeware", asset: /^Codeware-[\d.]+\.zip$/i, signature: "red4ext/plugins/Codeware/Codeware.dll" }
5231
+ ];
5232
+ function frameworkFamily(lib, f) {
5233
+ const sig = f.signature.toLowerCase();
5234
+ return families(lib).find((fam) => fam.nexusId === f.nexusId || fam.mods.some((m) => m.files.some((x) => x.toLowerCase() === sig)));
5235
+ }
5236
+ function missingFrameworks(lib) {
5237
+ return FrameworkSources.filter((f) => !frameworkFamily(lib, f));
5238
+ }
5239
+ async function latestFrameworkRelease(f, fetchImpl = fetch, api = "https://api.github.com") {
5240
+ const headers = { Accept: "application/vnd.github+json", "User-Agent": "EDGEdeck" };
5241
+ if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
5242
+ const res = await fetchImpl(`${api}/repos/${f.repo}/releases/latest`, { headers });
5243
+ if (!res.ok) throw new Error(res.status === 403 ? "GitHub's limit for calls without an account was reached; try again within the hour." : `GitHub answered ${res.status}.`);
5244
+ const rel = await res.json();
5245
+ const asset = rel.assets.find((a) => f.asset.test(a.name));
5246
+ if (!asset) throw new Error(`release ${rel.tag_name} has no file named like ${f.asset.source}.`);
5247
+ return { version: rel.tag_name.replace(/^v/i, ""), tag: rel.tag_name, assetName: asset.name, url: asset.browser_download_url, size: asset.size, page: rel.html_url };
5248
+ }
5249
+ async function frameworkStates(lib, fetchImpl, api) {
5250
+ return Promise.all(
5251
+ FrameworkSources.map(async (source) => {
5252
+ const family = frameworkFamily(lib, source);
5253
+ try {
5254
+ const release = await latestFrameworkRelease(source, fetchImpl, api);
5255
+ const newest2 = family?.mods[0]?.version ?? "";
5256
+ return { source, family, release, wanted: !family || !!newest2 && versionNewer(release.version, newest2) };
5257
+ } catch (err) {
5258
+ return { source, family, error: err instanceof Error ? err.message : String(err), wanted: !family };
5259
+ }
5260
+ })
5261
+ );
5262
+ }
5263
+
5264
+ // src/libraryMove.ts
5196
5265
  var fs9 = __toESM(require("node:fs"));
5197
5266
  var path8 = __toESM(require("node:path"));
5267
+ function holdsLibrary(dir) {
5268
+ return fs9.existsSync(path8.join(dir, "library.json")) || fs9.existsSync(path8.join(dir, "library"));
5269
+ }
5270
+ function moveTarget(picked) {
5271
+ if (!fs9.existsSync(picked)) return picked;
5272
+ return fs9.readdirSync(picked).length ? path8.join(picked, "EDGEdeck") : picked;
5273
+ }
5274
+ function volume(p) {
5275
+ let dir = path8.resolve(p);
5276
+ for (; ; ) {
5277
+ try {
5278
+ return fs9.statSync(dir).dev;
5279
+ } catch {
5280
+ const up = path8.dirname(dir);
5281
+ if (up === dir) return void 0;
5282
+ dir = up;
5283
+ }
5284
+ }
5285
+ }
5286
+ function sameDrive(a, b) {
5287
+ const va = volume(a);
5288
+ const vb = volume(b);
5289
+ return va !== void 0 && va === vb;
5290
+ }
5291
+ function inside(child, parent) {
5292
+ const rel = path8.relative(parent, child);
5293
+ return !!rel && !rel.startsWith("..") && !path8.isAbsolute(rel);
5294
+ }
5295
+ function listFiles(root) {
5296
+ const out = [];
5297
+ const walk2 = (dir) => {
5298
+ for (const e of fs9.readdirSync(dir, { withFileTypes: true })) {
5299
+ const abs = path8.join(dir, e.name);
5300
+ if (e.isDirectory()) walk2(abs);
5301
+ else if (e.isFile()) out.push({ rel: path8.relative(root, abs), size: fs9.statSync(abs).size });
5302
+ }
5303
+ };
5304
+ walk2(root);
5305
+ return out;
5306
+ }
5307
+ async function moveLibrary(from, to, progress, cancelled) {
5308
+ const src = path8.resolve(from);
5309
+ const dest = path8.resolve(to);
5310
+ if (src.toLowerCase() === dest.toLowerCase()) throw new Error(`The library is already in ${dest}.`);
5311
+ if (inside(dest, src)) throw new Error(`${dest} is inside the library itself; pick a folder outside it.`);
5312
+ if (!holdsLibrary(src)) throw new Error(`${src} does not hold a library.`);
5313
+ const existed = fs9.existsSync(dest);
5314
+ if (existed && fs9.readdirSync(dest).length) throw new Error(`${dest} is not empty.`);
5315
+ if (sameDrive(src, dest)) {
5316
+ const files2 = listFiles(src);
5317
+ fs9.mkdirSync(path8.dirname(dest), { recursive: true });
5318
+ if (existed) fs9.rmdirSync(dest);
5319
+ fs9.renameSync(src, dest);
5320
+ return { how: "rename", files: files2.length, bytes: files2.reduce((n, f) => n + f.size, 0), leftBehind: [] };
5321
+ }
5322
+ const files = listFiles(src);
5323
+ const totalBytes = files.reduce((n, f) => n + f.size, 0);
5324
+ const done = { files: 0, totalFiles: files.length, bytes: 0, totalBytes };
5325
+ try {
5326
+ fs9.mkdirSync(dest, { recursive: true });
5327
+ for (const f of files) {
5328
+ if (cancelled?.()) throw new Error("Cancelled: the library stays where it was.");
5329
+ const a = path8.join(src, f.rel);
5330
+ const b = path8.join(dest, f.rel);
5331
+ fs9.mkdirSync(path8.dirname(b), { recursive: true });
5332
+ await fs9.promises.copyFile(a, b);
5333
+ const st = fs9.statSync(a);
5334
+ fs9.utimesSync(b, st.atime, st.mtime);
5335
+ if (fs9.statSync(b).size !== f.size) throw new Error(`${f.rel} did not copy whole.`);
5336
+ done.files++;
5337
+ done.bytes += f.size;
5338
+ progress?.({ ...done });
5339
+ }
5340
+ } catch (err) {
5341
+ fs9.rmSync(dest, { recursive: true, force: true });
5342
+ if (existed) fs9.mkdirSync(dest, { recursive: true });
5343
+ throw err;
5344
+ }
5345
+ const leftBehind = [];
5346
+ for (const f of files) {
5347
+ try {
5348
+ fs9.rmSync(path8.join(src, f.rel), { force: true });
5349
+ } catch {
5350
+ leftBehind.push(path8.join(src, f.rel));
5351
+ }
5352
+ }
5353
+ try {
5354
+ fs9.rmSync(src, { recursive: true, force: true });
5355
+ } catch {
5356
+ }
5357
+ return { how: "copy", files: files.length, bytes: totalBytes, leftBehind };
5358
+ }
5359
+
5360
+ // src/loadouts.ts
5361
+ var fs10 = __toESM(require("node:fs"));
5362
+ var path9 = __toESM(require("node:path"));
5198
5363
  var DefaultLoadout = "Default";
5199
5364
  function loadoutsFolder(root) {
5200
- return path8.join(root, "loadouts");
5365
+ return path9.join(root, "loadouts");
5201
5366
  }
5202
5367
  function slug(name) {
5203
5368
  return name.replace(/[<>:"/\\|?*]+/g, "-").trim() || "loadout";
5204
5369
  }
5205
5370
  function readLoadout(file) {
5206
- return JSON.parse(fs9.readFileSync(file, "utf8"));
5371
+ return JSON.parse(fs10.readFileSync(file, "utf8"));
5207
5372
  }
5208
5373
  function listLoadouts(root) {
5209
5374
  const dir = loadoutsFolder(root);
5210
- if (!fs9.existsSync(dir)) return [];
5375
+ if (!fs10.existsSync(dir)) return [];
5211
5376
  const out = [];
5212
- for (const f of fs9.readdirSync(dir)) {
5377
+ for (const f of fs10.readdirSync(dir)) {
5213
5378
  if (!f.toLowerCase().endsWith(".json")) continue;
5214
5379
  try {
5215
- const l = readLoadout(path8.join(dir, f));
5216
- out.push({ name: l.name, file: path8.join(dir, f), saved: l.saved, mods: l.mods.length, on: l.mods.filter((m) => m.enabled).length });
5380
+ const l = readLoadout(path9.join(dir, f));
5381
+ out.push({ name: l.name, file: path9.join(dir, f), saved: l.saved, mods: l.mods.length, on: l.mods.filter((m) => m.enabled).length });
5217
5382
  } catch {
5218
5383
  }
5219
5384
  }
@@ -5242,13 +5407,13 @@ function entryFor(f, mod, enabled) {
5242
5407
  return { family: f.key, id: mod.id, name: f.name, version: mod.version, enabled, nexusModId: f.nexusId };
5243
5408
  }
5244
5409
  function writeLoadout(root, loadout, file) {
5245
- fs9.mkdirSync(loadoutsFolder(root), { recursive: true });
5410
+ fs10.mkdirSync(loadoutsFolder(root), { recursive: true });
5246
5411
  let target = file;
5247
5412
  if (!target) {
5248
- target = path8.join(loadoutsFolder(root), `${slug(loadout.name)}.json`);
5249
- for (let n = 2; fs9.existsSync(target); n++) target = path8.join(loadoutsFolder(root), `${slug(loadout.name)}-${n}.json`);
5413
+ target = path9.join(loadoutsFolder(root), `${slug(loadout.name)}.json`);
5414
+ for (let n = 2; fs10.existsSync(target); n++) target = path9.join(loadoutsFolder(root), `${slug(loadout.name)}-${n}.json`);
5250
5415
  }
5251
- fs9.writeFileSync(target, JSON.stringify(loadout, null, 2) + "\n");
5416
+ fs10.writeFileSync(target, JSON.stringify(loadout, null, 2) + "\n");
5252
5417
  return target;
5253
5418
  }
5254
5419
  function saveLoadout(lib, name, gameVersion2) {
@@ -5352,7 +5517,7 @@ function renameLoadout(lib, from, to) {
5352
5517
  if (!info) throw new Error(`There is no loadout called ${from}.`);
5353
5518
  const loadout = readLoadout(info.file);
5354
5519
  loadout.name = to.trim();
5355
- fs9.rmSync(info.file);
5520
+ fs10.rmSync(info.file);
5356
5521
  writeLoadout(lib.root, loadout);
5357
5522
  if (lib.info.activeLoadout === from) {
5358
5523
  lib.info.activeLoadout = to.trim();
@@ -5362,18 +5527,18 @@ function renameLoadout(lib, from, to) {
5362
5527
  function deleteLoadout(lib, name) {
5363
5528
  if (lib.info.activeLoadout === name) throw new Error(`${name} is the active loadout; make another one active first.`);
5364
5529
  const info = findLoadout(lib.root, name);
5365
- if (info) fs9.rmSync(info.file);
5530
+ if (info) fs10.rmSync(info.file);
5366
5531
  }
5367
5532
  function exportLoadout(lib, name, dest, gameVersion2) {
5368
5533
  if (lib.info.activeLoadout === name) syncActiveLoadout(lib, gameVersion2);
5369
5534
  const info = findLoadout(lib.root, name);
5370
5535
  if (!info) throw new Error(`There is no loadout called ${name}.`);
5371
- fs9.copyFileSync(info.file, dest);
5536
+ fs10.copyFileSync(info.file, dest);
5372
5537
  }
5373
5538
  function importLoadout(lib, file) {
5374
5539
  const loadout = readLoadout(file);
5375
- if (!Array.isArray(loadout.mods)) throw new Error(`${path8.basename(file)} is not a EDGEdeck loadout.`);
5376
- let name = (loadout.name || path8.basename(file, ".json")).trim();
5540
+ if (!Array.isArray(loadout.mods)) throw new Error(`${path9.basename(file)} is not a EDGEdeck loadout.`);
5541
+ let name = (loadout.name || path9.basename(file, ".json")).trim();
5377
5542
  for (let n = 2; findLoadout(lib.root, name); n++) name = `${(loadout.name || "Loadout").trim()} (${n})`;
5378
5543
  writeLoadout(lib.root, { ...loadout, name, saved: (/* @__PURE__ */ new Date()).toISOString() });
5379
5544
  return name;
@@ -5398,7 +5563,7 @@ function signIn(options) {
5398
5563
  let done = false;
5399
5564
  let attempts = 0;
5400
5565
  let socket;
5401
- return new Promise((resolve3, reject) => {
5566
+ return new Promise((resolve5, reject) => {
5402
5567
  const finish = (err, key2) => {
5403
5568
  if (done) return;
5404
5569
  done = true;
@@ -5408,7 +5573,7 @@ function signIn(options) {
5408
5573
  socket?.close();
5409
5574
  } catch {
5410
5575
  }
5411
- if (key2) resolve3(key2);
5576
+ if (key2) resolve5(key2);
5412
5577
  else reject(err ?? new Error("Nexus sign-in failed"));
5413
5578
  };
5414
5579
  const onAbort = () => finish(new Error("Sign-in cancelled"));
@@ -5539,7 +5704,7 @@ function signInWithOAuth(options) {
5539
5704
  const { verifier, challenge } = pkce();
5540
5705
  const state = crypto3.randomUUID();
5541
5706
  let done = false;
5542
- return new Promise((resolve3, reject) => {
5707
+ return new Promise((resolve5, reject) => {
5543
5708
  const server = http.createServer();
5544
5709
  const finish = (err, tokens) => {
5545
5710
  if (done) return;
@@ -5547,7 +5712,7 @@ function signInWithOAuth(options) {
5547
5712
  clearTimeout(timer);
5548
5713
  options.signal?.removeEventListener("abort", onAbort);
5549
5714
  server.close();
5550
- if (tokens) resolve3(tokens);
5715
+ if (tokens) resolve5(tokens);
5551
5716
  else reject(err ?? new Error("Nexus sign-in failed"));
5552
5717
  };
5553
5718
  const onAbort = () => finish(new Error("Sign-in cancelled"));
@@ -5668,10 +5833,10 @@ function describeHandler(command) {
5668
5833
  }
5669
5834
 
5670
5835
  // src/project.ts
5671
- var fs10 = __toESM(require("node:fs"));
5672
- var path9 = __toESM(require("node:path"));
5836
+ var fs11 = __toESM(require("node:fs"));
5837
+ var path10 = __toESM(require("node:path"));
5673
5838
  function addToProject(lib, mod, files, projectFile, replace = /* @__PURE__ */ new Set()) {
5674
- const resources = path9.join(path9.dirname(projectFile), "source", "resources");
5839
+ const resources = path10.join(path10.dirname(projectFile), "source", "resources");
5675
5840
  const out = { copied: [], same: [], kept: [], archives: [] };
5676
5841
  for (const f of files) {
5677
5842
  if (f.toLowerCase().endsWith(".archive")) {
@@ -5679,10 +5844,10 @@ function addToProject(lib, mod, files, projectFile, replace = /* @__PURE__ */ ne
5679
5844
  continue;
5680
5845
  }
5681
5846
  const src = sourcePath(lib, mod, f);
5682
- const rel = path9.join("source", "resources", ...f.split("/"));
5683
- const dest = path9.join(resources, ...f.split("/"));
5684
- if (fs10.existsSync(dest)) {
5685
- if (fs10.readFileSync(dest).equals(fs10.readFileSync(src))) {
5847
+ const rel = path10.join("source", "resources", ...f.split("/"));
5848
+ const dest = path10.join(resources, ...f.split("/"));
5849
+ if (fs11.existsSync(dest)) {
5850
+ if (fs11.readFileSync(dest).equals(fs11.readFileSync(src))) {
5686
5851
  out.same.push(rel);
5687
5852
  continue;
5688
5853
  }
@@ -5691,39 +5856,39 @@ function addToProject(lib, mod, files, projectFile, replace = /* @__PURE__ */ ne
5691
5856
  continue;
5692
5857
  }
5693
5858
  }
5694
- fs10.mkdirSync(path9.dirname(dest), { recursive: true });
5695
- fs10.copyFileSync(src, dest);
5859
+ fs11.mkdirSync(path10.dirname(dest), { recursive: true });
5860
+ fs11.copyFileSync(src, dest);
5696
5861
  out.copied.push(rel);
5697
5862
  }
5698
5863
  return out;
5699
5864
  }
5700
5865
  function findProjectFile(target) {
5701
- if (fs10.existsSync(target) && fs10.statSync(target).isFile()) return target.toLowerCase().endsWith(".cpmodproj") ? target : void 0;
5702
- const files = fs10.existsSync(target) ? fs10.readdirSync(target).filter((f) => f.toLowerCase().endsWith(".cpmodproj")) : [];
5703
- return files.length ? path9.join(target, files[0]) : void 0;
5866
+ if (fs11.existsSync(target) && fs11.statSync(target).isFile()) return target.toLowerCase().endsWith(".cpmodproj") ? target : void 0;
5867
+ const files = fs11.existsSync(target) ? fs11.readdirSync(target).filter((f) => f.toLowerCase().endsWith(".cpmodproj")) : [];
5868
+ return files.length ? path10.join(target, files[0]) : void 0;
5704
5869
  }
5705
5870
  function projectName(projectFile) {
5706
5871
  try {
5707
- const xml = fs10.readFileSync(projectFile, "utf8");
5872
+ const xml = fs11.readFileSync(projectFile, "utf8");
5708
5873
  const m = /<ModName>\s*([^<]+?)\s*<\//.exec(xml) ?? /<Name>\s*([^<]+?)\s*<\//.exec(xml);
5709
5874
  if (m) return m[1];
5710
5875
  } catch {
5711
5876
  }
5712
- return path9.basename(projectFile, path9.extname(projectFile));
5877
+ return path10.basename(projectFile, path10.extname(projectFile));
5713
5878
  }
5714
5879
  function packedFolder(projectFile) {
5715
- return path9.join(path9.dirname(projectFile), "packed");
5880
+ return path10.join(path10.dirname(projectFile), "packed");
5716
5881
  }
5717
5882
  function classifyProject(projectFile) {
5718
5883
  const packed = packedFolder(projectFile);
5719
- if (!fs10.existsSync(packed)) return { placements: [], kinds: [], unplaced: [], ignored: [] };
5884
+ if (!fs11.existsSync(packed)) return { placements: [], kinds: [], unplaced: [], ignored: [] };
5720
5885
  return classify(packed, projectName(projectFile));
5721
5886
  }
5722
5887
  function newest(dir) {
5723
5888
  let t = 0;
5724
- for (const e of fs10.readdirSync(dir, { withFileTypes: true })) {
5725
- const p = path9.join(dir, e.name);
5726
- t = Math.max(t, e.isDirectory() ? newest(p) : fs10.statSync(p).mtimeMs);
5889
+ for (const e of fs11.readdirSync(dir, { withFileTypes: true })) {
5890
+ const p = path10.join(dir, e.name);
5891
+ t = Math.max(t, e.isDirectory() ? newest(p) : fs11.statSync(p).mtimeMs);
5727
5892
  }
5728
5893
  return t;
5729
5894
  }
@@ -5732,10 +5897,10 @@ function syncProjects(lib) {
5732
5897
  for (const mod of lib.mods) {
5733
5898
  if (mod.source.type !== "project" || !mod.source.path) continue;
5734
5899
  const packed = packedFolder(mod.source.path);
5735
- if (!fs10.existsSync(packed)) continue;
5900
+ if (!fs11.existsSync(packed)) continue;
5736
5901
  const libNewest = mod.files.reduce((t, f) => {
5737
5902
  try {
5738
- return Math.max(t, fs10.statSync(sourcePath(lib, mod, f)).mtimeMs);
5903
+ return Math.max(t, fs11.statSync(sourcePath(lib, mod, f)).mtimeMs);
5739
5904
  } catch {
5740
5905
  return t;
5741
5906
  }
@@ -5746,11 +5911,11 @@ function syncProjects(lib) {
5746
5911
  const keep = /* @__PURE__ */ new Set();
5747
5912
  for (const p of c.placements) {
5748
5913
  const dest = sourcePath(lib, mod, p.to);
5749
- fs10.mkdirSync(path9.dirname(dest), { recursive: true });
5750
- fs10.copyFileSync(p.from, dest);
5914
+ fs11.mkdirSync(path10.dirname(dest), { recursive: true });
5915
+ fs11.copyFileSync(p.from, dest);
5751
5916
  keep.add(p.to);
5752
5917
  }
5753
- for (const f of mod.files) if (!keep.has(f)) fs10.rmSync(sourcePath(lib, mod, f), { force: true });
5918
+ for (const f of mod.files) if (!keep.has(f)) fs11.rmSync(sourcePath(lib, mod, f), { force: true });
5754
5919
  mod.files = [...keep].sort();
5755
5920
  mod.kinds = c.kinds;
5756
5921
  saveMod(lib, mod);
@@ -5806,11 +5971,11 @@ function installProject(lib, projectFile, beside) {
5806
5971
  const keep = /* @__PURE__ */ new Set();
5807
5972
  for (const p of c.placements) {
5808
5973
  const dest = sourcePath(lib, existing, p.to);
5809
- fs10.mkdirSync(path9.dirname(dest), { recursive: true });
5810
- fs10.copyFileSync(p.from, dest);
5974
+ fs11.mkdirSync(path10.dirname(dest), { recursive: true });
5975
+ fs11.copyFileSync(p.from, dest);
5811
5976
  keep.add(p.to);
5812
5977
  }
5813
- for (const f of existing.files) if (!keep.has(f)) fs10.rmSync(sourcePath(lib, existing, f), { force: true });
5978
+ for (const f of existing.files) if (!keep.has(f)) fs11.rmSync(sourcePath(lib, existing, f), { force: true });
5814
5979
  existing.files = [...keep].sort();
5815
5980
  existing.kinds = c.kinds;
5816
5981
  activateVersion(lib, existing);
@@ -5821,12 +5986,12 @@ function installProject(lib, projectFile, beside) {
5821
5986
 
5822
5987
  // src/redmod.ts
5823
5988
  var import_node_child_process6 = require("node:child_process");
5824
- var fs11 = __toESM(require("node:fs"));
5825
- var path10 = __toESM(require("node:path"));
5989
+ var fs12 = __toESM(require("node:fs"));
5990
+ var path11 = __toESM(require("node:path"));
5826
5991
  var import_node_util4 = require("node:util");
5827
5992
  var run4 = (0, import_node_util4.promisify)(import_node_child_process6.execFile);
5828
5993
  function redModExe(gamePath) {
5829
- return path10.join(gamePath, "tools", "redmod", "bin", "redMod.exe");
5994
+ return path11.join(gamePath, "tools", "redmod", "bin", "redMod.exe");
5830
5995
  }
5831
5996
  function enabledRedmodPackages(lib) {
5832
5997
  const names = [];
@@ -5853,13 +6018,13 @@ function deployedRedmodPackages(lib, gamePath) {
5853
6018
  async function deployRedmod(lib, gamePath, log, packages = enabledRedmodPackages(lib)) {
5854
6019
  const exe = redModExe(gamePath);
5855
6020
  if (!packages.length) return { ran: false, packages, output: "no REDmod packages enabled" };
5856
- if (!fs11.existsSync(exe)) return { ran: false, packages, output: `redMod.exe not found at ${exe}; the REDmod tools are a free DLC on the game's store page` };
5857
- const listFile = path10.join(lib.root, "redmod-modlist.txt");
5858
- fs11.writeFileSync(listFile, packages.join("\r\n") + "\r\n");
5859
- const args = ["deploy", `-root=${gamePath}`, `-modlist=${listFile}`, `-rttiSchemaPath=${path10.join(gamePath, "tools", "redmod", "metadata.json")}`];
6021
+ if (!fs12.existsSync(exe)) return { ran: false, packages, output: `redMod.exe not found at ${exe}; the REDmod tools are a free DLC on the game's store page` };
6022
+ const listFile = path11.join(lib.root, "redmod-modlist.txt");
6023
+ fs12.writeFileSync(listFile, packages.join("\r\n") + "\r\n");
6024
+ const args = ["deploy", `-root=${gamePath}`, `-modlist=${listFile}`, `-rttiSchemaPath=${path11.join(gamePath, "tools", "redmod", "metadata.json")}`];
5860
6025
  log?.(`redMod.exe ${args.join(" ")}`);
5861
6026
  try {
5862
- const { stdout, stderr } = await run4(exe, args, { cwd: path10.dirname(exe), windowsHide: true, maxBuffer: 1 << 26 });
6027
+ const { stdout, stderr } = await run4(exe, args, { cwd: path11.dirname(exe), windowsHide: true, maxBuffer: 1 << 26 });
5863
6028
  const output = (stdout + stderr).trim();
5864
6029
  for (const line of output.split(/\r?\n/)) if (line.trim()) log?.(` ${line}`);
5865
6030
  return { ran: true, packages, output };
@@ -5977,8 +6142,8 @@ function setSectionOn(lib, name, on) {
5977
6142
  }
5978
6143
 
5979
6144
  // src/sizes.ts
5980
- var fs12 = __toESM(require("node:fs"));
5981
- var path11 = __toESM(require("node:path"));
6145
+ var fs13 = __toESM(require("node:fs"));
6146
+ var path12 = __toESM(require("node:path"));
5982
6147
  var cache = /* @__PURE__ */ new Map();
5983
6148
  function modSize(lib, mod) {
5984
6149
  const stamp = `${mod.files.length}:${mod.added}:${lib.root}`;
@@ -5991,7 +6156,7 @@ function modSize(lib, mod) {
5991
6156
  for (const f of mod.files) {
5992
6157
  let n = 0;
5993
6158
  try {
5994
- const st = fs12.statSync(path11.join(folder, ...f.split("/")));
6159
+ const st = fs13.statSync(path12.join(folder, ...f.split("/")));
5995
6160
  n = st.size;
5996
6161
  mtimes.set(f, st.mtimeMs);
5997
6162
  } catch {
@@ -6010,10 +6175,10 @@ function forgetSizes(modId) {
6010
6175
  function folderSize(dir) {
6011
6176
  let total = 0;
6012
6177
  try {
6013
- for (const e of fs12.readdirSync(dir, { withFileTypes: true })) {
6014
- const p = path11.join(dir, e.name);
6178
+ for (const e of fs13.readdirSync(dir, { withFileTypes: true })) {
6179
+ const p = path12.join(dir, e.name);
6015
6180
  if (e.isDirectory()) total += folderSize(p);
6016
- else if (e.isFile()) total += fs12.statSync(p).size;
6181
+ else if (e.isFile()) total += fs13.statSync(p).size;
6017
6182
  }
6018
6183
  } catch {
6019
6184
  }
@@ -6034,20 +6199,20 @@ function formatBytes(n) {
6034
6199
  }
6035
6200
 
6036
6201
  // src/verbs.ts
6037
- var fs14 = __toESM(require("node:fs"));
6202
+ var fs15 = __toESM(require("node:fs"));
6038
6203
  var os3 = __toESM(require("node:os"));
6039
- var path13 = __toESM(require("node:path"));
6204
+ var path14 = __toESM(require("node:path"));
6040
6205
 
6041
6206
  // src/vortex.ts
6042
- var fs13 = __toESM(require("node:fs"));
6043
- var path12 = __toESM(require("node:path"));
6207
+ var fs14 = __toESM(require("node:fs"));
6208
+ var path13 = __toESM(require("node:path"));
6044
6209
  function vortexStagingPath() {
6045
- return path12.join(process.env.APPDATA ?? path12.join(process.env.USERPROFILE ?? "", "AppData", "Roaming"), "Vortex", "cyberpunk2077", "mods");
6210
+ return path13.join(process.env.APPDATA ?? path13.join(process.env.USERPROFILE ?? "", "AppData", "Roaming"), "Vortex", "cyberpunk2077", "mods");
6046
6211
  }
6047
6212
  function manifestStaging(gamePath) {
6048
6213
  if (!gamePath) return void 0;
6049
6214
  try {
6050
- const m = JSON.parse(fs13.readFileSync(path12.join(gamePath, "vortex.deployment.json"), "utf8"));
6215
+ const m = JSON.parse(fs14.readFileSync(path13.join(gamePath, "vortex.deployment.json"), "utf8"));
6051
6216
  return m.stagingPath || void 0;
6052
6217
  } catch {
6053
6218
  return void 0;
@@ -6055,18 +6220,18 @@ function manifestStaging(gamePath) {
6055
6220
  }
6056
6221
  function driveRoots(gamePath) {
6057
6222
  if (process.platform !== "win32") return [];
6058
- const first = gamePath ? path12.parse(path12.resolve(gamePath)).root.toUpperCase() : "";
6223
+ const first = gamePath ? path13.parse(path13.resolve(gamePath)).root.toUpperCase() : "";
6059
6224
  const roots = first ? [first] : [];
6060
6225
  for (let c = 67; c <= 90; c++) {
6061
6226
  const r = `${String.fromCharCode(c)}:\\`;
6062
- if (r !== first && fs13.existsSync(r)) roots.push(r);
6227
+ if (r !== first && fs14.existsSync(r)) roots.push(r);
6063
6228
  }
6064
6229
  return roots;
6065
6230
  }
6066
6231
  function vortexStagingCandidates(gamePath, setting) {
6067
6232
  const list = [setting, manifestStaging(gamePath), vortexStagingPath()];
6068
6233
  for (const root of driveRoots(gamePath)) {
6069
- list.push(path12.join(root, "Vortex Mods", "cyberpunk2077"), path12.join(root, "VortexMods", "cyberpunk2077"), path12.join(root, "Vortex", "cyberpunk2077", "mods"), path12.join(root, "Vortex", "mods", "cyberpunk2077"));
6234
+ list.push(path13.join(root, "Vortex Mods", "cyberpunk2077"), path13.join(root, "VortexMods", "cyberpunk2077"), path13.join(root, "Vortex", "cyberpunk2077", "mods"), path13.join(root, "Vortex", "mods", "cyberpunk2077"));
6070
6235
  }
6071
6236
  const seen = /* @__PURE__ */ new Set();
6072
6237
  return list.filter((p) => !!p && !seen.has(p.toLowerCase()) && !!seen.add(p.toLowerCase()));
@@ -6074,7 +6239,7 @@ function vortexStagingCandidates(gamePath, setting) {
6074
6239
  function findVortexStaging(gamePath, setting) {
6075
6240
  for (const dir of vortexStagingCandidates(gamePath, setting)) {
6076
6241
  try {
6077
- if (fs13.readdirSync(dir, { withFileTypes: true }).some((e) => e.isDirectory() && !e.name.startsWith("__"))) return dir;
6242
+ if (fs14.readdirSync(dir, { withFileTypes: true }).some((e) => e.isDirectory() && !e.name.startsWith("__"))) return dir;
6078
6243
  } catch {
6079
6244
  }
6080
6245
  }
@@ -6082,24 +6247,24 @@ function findVortexStaging(gamePath, setting) {
6082
6247
  }
6083
6248
  function countFiles(dir) {
6084
6249
  let n = 0;
6085
- for (const e of fs13.readdirSync(dir, { withFileTypes: true })) {
6086
- if (e.isDirectory()) n += countFiles(path12.join(dir, e.name));
6250
+ for (const e of fs14.readdirSync(dir, { withFileTypes: true })) {
6251
+ if (e.isDirectory()) n += countFiles(path13.join(dir, e.name));
6087
6252
  else n++;
6088
6253
  }
6089
6254
  return n;
6090
6255
  }
6091
6256
  function guessName(folder) {
6092
6257
  const sub = (...parts) => {
6093
- const d = path12.join(folder, ...parts);
6094
- if (!fs13.existsSync(d)) return void 0;
6095
- const names = fs13.readdirSync(d, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
6258
+ const d = path13.join(folder, ...parts);
6259
+ if (!fs14.existsSync(d)) return void 0;
6260
+ const names = fs14.readdirSync(d, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
6096
6261
  return names.length === 1 ? names[0] : void 0;
6097
6262
  };
6098
6263
  const named = sub("r6", "scripts") ?? sub("bin", "x64", "plugins", "cyber_engine_tweaks", "mods") ?? sub("red4ext", "plugins") ?? sub("r6", "tweaks") ?? sub("mods");
6099
6264
  if (named) return named;
6100
- const archives = path12.join(folder, "archive", "pc", "mod");
6101
- if (fs13.existsSync(archives)) {
6102
- const a = fs13.readdirSync(archives).find((f) => f.toLowerCase().endsWith(".archive"));
6265
+ const archives = path13.join(folder, "archive", "pc", "mod");
6266
+ if (fs14.existsSync(archives)) {
6267
+ const a = fs14.readdirSync(archives).find((f) => f.toLowerCase().endsWith(".archive"));
6103
6268
  if (a) return a.replace(/\.archive$/i, "").replace(/^[#!_0-9]+/, "") || a;
6104
6269
  }
6105
6270
  return void 0;
@@ -6109,21 +6274,21 @@ function listVortexMods(gamePath, staging) {
6109
6274
  if (!dir) return [];
6110
6275
  staging = dir;
6111
6276
  const deployed = /* @__PURE__ */ new Set();
6112
- const manifest = path12.join(gamePath, "vortex.deployment.json");
6113
- if (fs13.existsSync(manifest)) {
6277
+ const manifest = path13.join(gamePath, "vortex.deployment.json");
6278
+ if (fs14.existsSync(manifest)) {
6114
6279
  try {
6115
- const m = JSON.parse(fs13.readFileSync(manifest, "utf8"));
6280
+ const m = JSON.parse(fs14.readFileSync(manifest, "utf8"));
6116
6281
  for (const f of m.files ?? []) deployed.add(f.source);
6117
6282
  } catch {
6118
6283
  }
6119
6284
  }
6120
6285
  const mods = [];
6121
- for (const entry of fs13.readdirSync(staging, { withFileTypes: true })) {
6286
+ for (const entry of fs14.readdirSync(staging, { withFileTypes: true })) {
6122
6287
  if (!entry.isDirectory() || entry.name.startsWith("__")) continue;
6123
- const folder = path12.join(staging, entry.name);
6288
+ const folder = path13.join(staging, entry.name);
6124
6289
  const files = countFiles(folder);
6125
6290
  if (files === 0) continue;
6126
- if (fs13.existsSync(path12.join(folder, "collection.json")) && files === 1) continue;
6291
+ if (fs14.existsSync(path13.join(folder, "collection.json")) && files === 1) continue;
6127
6292
  const m = /^(.*?)-(\d+)-([\d-]+)-(\d{10})$/.exec(entry.name);
6128
6293
  const guid = /^[0-9a-f]{2}_[0-9a-f]{2}_[0-9a-f]{2}_[0-9a-f-]{36}$/i.test(entry.name);
6129
6294
  mods.push({
@@ -6211,7 +6376,7 @@ var verbs = {
6211
6376
  /** Every mod with its status and size, the library totals and the frameworks line. */
6212
6377
  async list(s, largestFirst = false) {
6213
6378
  const { lib, game } = s;
6214
- const p = fs14.existsSync(game) && isGameFolder(game) ? plan(lib, game) : void 0;
6379
+ const p = fs15.existsSync(game) && isGameFolder(game) ? plan(lib, game) : void 0;
6215
6380
  const manifest = p ? readManifest(game) : void 0;
6216
6381
  const changes = p ? detectChanges(game) : [];
6217
6382
  const rows = lib.mods.map((m) => ({ id: m.id, name: m.name, version: m.version, enabled: m.enabled, kinds: m.kinds, files: m.files.length, bytes: modSize(lib, m).total, status: p ? modStatus(lib, game, m, p, manifest, changes) : "" }));
@@ -6253,14 +6418,14 @@ ${mod.files.join("\n")}`;
6253
6418
  },
6254
6419
  /** Adds a zip, 7z, rar, a single mod file or a folder. An archive with options needs the option's label. */
6255
6420
  async add(s, source, name, option) {
6256
- if (!fs14.existsSync(source)) throw new Error(`${source} does not exist`);
6257
- const isFile = fs14.statSync(source).isFile();
6258
- const modName = name ?? path13.basename(source, isFile ? path13.extname(source) : "");
6421
+ if (!fs15.existsSync(source)) throw new Error(`${source} does not exist`);
6422
+ const isFile = fs15.statSync(source).isFile();
6423
+ const modName = name ?? path14.basename(source, isFile ? path14.extname(source) : "");
6259
6424
  let folder = source;
6260
6425
  let temp;
6261
6426
  try {
6262
6427
  if (isFile) {
6263
- temp = fs14.mkdtempSync(path13.join(os3.tmpdir(), "edgedeck-verb-"));
6428
+ temp = fs15.mkdtempSync(path14.join(os3.tmpdir(), "edgedeck-verb-"));
6264
6429
  await unpackOrCopy(source, temp, s.sevenZa);
6265
6430
  folder = temp;
6266
6431
  }
@@ -6277,7 +6442,7 @@ ${mod.files.join("\n")}`;
6277
6442
  const note = settled.replaced ? `; a new version of ${settled.replaced.name} ${settled.replaced.version}, which is off now` : settled.older ? `; older than ${settled.older.version}, so it is off` : "";
6278
6443
  return { data: { added: { id: mod.id, name: mod.name, version: mod.version, kinds: mod.kinds, files: mod.files.length, enabled: mod.enabled }, replaced: settled.replaced?.id, unplaced: c.unplaced }, text: `added ${mod.id} (${mod.files.length} files, ${mod.kinds.join(", ")})${note}; ${mod.enabled ? "it is on, " : ""}deploy to put it in the game${c.unplaced.length ? `; not placed: ${c.unplaced.join(", ")}` : ""}` };
6279
6444
  } finally {
6280
- if (temp) fs14.rmSync(temp, { recursive: true, force: true });
6445
+ if (temp) fs15.rmSync(temp, { recursive: true, force: true });
6281
6446
  }
6282
6447
  },
6283
6448
  /** Adds a REDcode or WolvenKit project's packed output, or refreshes the mod that follows it, and turns it on. */
@@ -6287,9 +6452,9 @@ ${mod.files.join("\n")}`;
6287
6452
  * place). Without it, the project is a mod of its own and the text says what it sits beside.
6288
6453
  */
6289
6454
  installProject(s, projectFile, sameName) {
6290
- if (!fs14.existsSync(projectFile)) throw new Error(`${projectFile} does not exist`);
6455
+ if (!fs15.existsSync(projectFile)) throw new Error(`${projectFile} does not exist`);
6291
6456
  const other = sameNameMod(s.lib, projectFile);
6292
- const before = other && `${other.id}${other.source.type === "project" && other.source.path ? ` (${path13.dirname(other.source.path)})` : ""}`;
6457
+ const before = other && `${other.id}${other.source.type === "project" && other.source.path ? ` (${path14.dirname(other.source.path)})` : ""}`;
6293
6458
  const { mod, added, beside } = installProject(s.lib, projectFile, other && sameName ? { mod: other, how: sameName } : void 0);
6294
6459
  const note = beside?.how === "follow" ? `; ${before} follows this project now` : beside ? `; another version of ${before}, on in its place` : other ? `; note: the library also has ${before} under the same name, a separate mod; the one higher in the list wins the files they share (call again with sameName follow or version to settle it)` : "";
6295
6460
  return {
@@ -6373,7 +6538,7 @@ ${mod.files.join("\n")}`;
6373
6538
  }
6374
6539
  if (action === "load") {
6375
6540
  if (!name) throw new Error("loadout load needs a name or file");
6376
- const target = fs14.existsSync(name) && name.toLowerCase().endsWith(".json") ? importLoadout(s.lib, name) : name;
6541
+ const target = fs15.existsSync(name) && name.toLowerCase().endsWith(".json") ? importLoadout(s.lib, name) : name;
6377
6542
  const r = makeActive(s.lib, target);
6378
6543
  return { data: { ...r, active: target }, text: `loadout ${target} active: ${r.on} on, ${r.off} off${r.missing.length ? `; not in the library: ${r.missing.join(", ")}` : ""}; deploy to apply` };
6379
6544
  }
@@ -6397,6 +6562,7 @@ ${mod.files.join("\n")}`;
6397
6562
  BridgeClient,
6398
6563
  BridgeRepo,
6399
6564
  DefaultLoadout,
6565
+ FrameworkSources,
6400
6566
  FrameworksSection,
6401
6567
  KnownModNames,
6402
6568
  ManifestName,
@@ -6434,6 +6600,7 @@ ${mod.files.join("\n")}`;
6434
6600
  deleteLoadout,
6435
6601
  deleteSection,
6436
6602
  deploy,
6603
+ deployMethod,
6437
6604
  deployRedmod,
6438
6605
  deployReportsFolder,
6439
6606
  deployedRedmodPackages,
@@ -6465,12 +6632,15 @@ ${mod.files.join("\n")}`;
6465
6632
  formatBytes,
6466
6633
  forwarderCommand,
6467
6634
  frameworkDetail,
6635
+ frameworkFamily,
6636
+ frameworkStates,
6468
6637
  frameworkSummary,
6469
6638
  gameExe,
6470
6639
  gameRunning,
6471
6640
  gameVersion,
6472
6641
  guessName,
6473
6642
  hasWebSocket,
6643
+ holdsLibrary,
6474
6644
  idFor,
6475
6645
  importFromVortex,
6476
6646
  importLoadout,
@@ -6482,6 +6652,7 @@ ${mod.files.join("\n")}`;
6482
6652
  isGameFolder,
6483
6653
  keepDownload,
6484
6654
  latestBridgeRelease,
6655
+ latestFrameworkRelease,
6485
6656
  launchTarget,
6486
6657
  libraryFolder,
6487
6658
  librarySize,
@@ -6494,6 +6665,7 @@ ${mod.files.join("\n")}`;
6494
6665
  lostFiles,
6495
6666
  makeActive,
6496
6667
  md5Of,
6668
+ missingFrameworks,
6497
6669
  modDeployPaths,
6498
6670
  modFolder,
6499
6671
  modPageUrl,
@@ -6502,8 +6674,10 @@ ${mod.files.join("\n")}`;
6502
6674
  modsRemoved,
6503
6675
  modsTouched,
6504
6676
  moveInSection,
6677
+ moveLibrary,
6505
6678
  moveMod,
6506
6679
  moveSection,
6680
+ moveTarget,
6507
6681
  needsRefresh,
6508
6682
  newLoadout,
6509
6683
  newestFile,
@@ -6533,6 +6707,7 @@ ${mod.files.join("\n")}`;
6533
6707
  renameFamily,
6534
6708
  renameLoadout,
6535
6709
  renameSection,
6710
+ sameDrive,
6536
6711
  sameNameMod,
6537
6712
  saveLibrary,
6538
6713
  saveLoadout,
package/dist/mcp.js CHANGED
@@ -2984,7 +2984,7 @@ var require_compile = __commonJS({
2984
2984
  const schOrFunc = root.refs[ref];
2985
2985
  if (schOrFunc)
2986
2986
  return schOrFunc;
2987
- let _sch = resolve4.call(this, root, ref);
2987
+ let _sch = resolve5.call(this, root, ref);
2988
2988
  if (_sch === void 0) {
2989
2989
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
2990
2990
  const { schemaId } = this.opts;
@@ -3011,7 +3011,7 @@ var require_compile = __commonJS({
3011
3011
  function sameSchemaEnv(s1, s2) {
3012
3012
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3013
3013
  }
3014
- function resolve4(root, ref) {
3014
+ function resolve5(root, ref) {
3015
3015
  let sch;
3016
3016
  while (typeof (sch = this.refs[ref]) == "string")
3017
3017
  ref = sch;
@@ -3841,7 +3841,7 @@ var require_fast_uri = __commonJS({
3841
3841
  }
3842
3842
  return uri;
3843
3843
  }
3844
- function resolve4(baseURI, relativeURI, options2) {
3844
+ function resolve5(baseURI, relativeURI, options2) {
3845
3845
  const schemelessOptions = options2 ? Object.assign({ scheme: "null" }, options2) : { scheme: "null" };
3846
3846
  const {
3847
3847
  parsed: baseParsed,
@@ -4210,7 +4210,7 @@ var require_fast_uri = __commonJS({
4210
4210
  var fastUri = {
4211
4211
  SCHEMES,
4212
4212
  normalize,
4213
- resolve: resolve4,
4213
+ resolve: resolve5,
4214
4214
  resolveComponent,
4215
4215
  equal,
4216
4216
  serialize,
@@ -15901,7 +15901,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
15901
15901
  var NONE = 0;
15902
15902
  var ASSUMED = 1;
15903
15903
  var PROVEN = 2;
15904
- function isRecursive(inst, stack, resolve4) {
15904
+ function isRecursive(inst, stack, resolve5) {
15905
15905
  const cached2 = recursive.get(inst);
15906
15906
  if (cached2 !== void 0)
15907
15907
  return cached2 ? PROVEN : NONE;
@@ -15911,7 +15911,7 @@ function isRecursive(inst, stack, resolve4) {
15911
15911
  let result = NONE;
15912
15912
  const check2 = (child) => {
15913
15913
  if (result !== PROVEN && child?._zod) {
15914
- const answer = isRecursive(child, stack, resolve4);
15914
+ const answer = isRecursive(child, stack, resolve5);
15915
15915
  if (answer > result)
15916
15916
  result = answer;
15917
15917
  }
@@ -15922,7 +15922,7 @@ function isRecursive(inst, stack, resolve4) {
15922
15922
  const desc = Object.getOwnPropertyDescriptor(sh, key2);
15923
15923
  if (spread && !desc.enumerable)
15924
15924
  continue;
15925
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve4) : NONE;
15925
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve5) : NONE;
15926
15926
  if (child > answer)
15927
15927
  answer = child;
15928
15928
  }
@@ -15986,7 +15986,7 @@ function isRecursive(inst, stack, resolve4) {
15986
15986
  break;
15987
15987
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
15988
15988
  case "lazy": {
15989
- const inner = def._cachedInner ?? (resolve4 ? inst._zod.innerType : void 0);
15989
+ const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
15990
15990
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
15991
15991
  break;
15992
15992
  }
@@ -34424,7 +34424,7 @@ var Protocol = class {
34424
34424
  return;
34425
34425
  }
34426
34426
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
34427
- await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
34427
+ await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
34428
34428
  options2?.signal?.throwIfAborted();
34429
34429
  }
34430
34430
  } catch (error62) {
@@ -34441,7 +34441,7 @@ var Protocol = class {
34441
34441
  */
34442
34442
  request(request, resultSchema, options2) {
34443
34443
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options2 ?? {};
34444
- return new Promise((resolve4, reject) => {
34444
+ return new Promise((resolve5, reject) => {
34445
34445
  const earlyReject = (error62) => {
34446
34446
  reject(error62);
34447
34447
  };
@@ -34519,7 +34519,7 @@ var Protocol = class {
34519
34519
  if (!parseResult.success) {
34520
34520
  reject(parseResult.error);
34521
34521
  } else {
34522
- resolve4(parseResult.data);
34522
+ resolve5(parseResult.data);
34523
34523
  }
34524
34524
  } catch (error62) {
34525
34525
  reject(error62);
@@ -34781,12 +34781,12 @@ var Protocol = class {
34781
34781
  }
34782
34782
  } catch {
34783
34783
  }
34784
- return new Promise((resolve4, reject) => {
34784
+ return new Promise((resolve5, reject) => {
34785
34785
  if (signal.aborted) {
34786
34786
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
34787
34787
  return;
34788
34788
  }
34789
- const timeoutId = setTimeout(resolve4, interval);
34789
+ const timeoutId = setTimeout(resolve5, interval);
34790
34790
  signal.addEventListener("abort", () => {
34791
34791
  clearTimeout(timeoutId);
34792
34792
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -35917,7 +35917,7 @@ var McpServer = class {
35917
35917
  let task = createTaskResult.task;
35918
35918
  const pollInterval = task.pollInterval ?? 5e3;
35919
35919
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
35920
- await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
35920
+ await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
35921
35921
  const updatedTask = await extra.taskStore.getTask(taskId);
35922
35922
  if (!updatedTask) {
35923
35923
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -36581,12 +36581,12 @@ var StdioServerTransport = class {
36581
36581
  this.onclose?.();
36582
36582
  }
36583
36583
  send(message) {
36584
- return new Promise((resolve4) => {
36584
+ return new Promise((resolve5) => {
36585
36585
  const json2 = serializeMessage(message);
36586
36586
  if (this._stdout.write(json2)) {
36587
- resolve4();
36587
+ resolve5();
36588
36588
  } else {
36589
- this._stdout.once("drain", resolve4);
36589
+ this._stdout.once("drain", resolve5);
36590
36590
  }
36591
36591
  });
36592
36592
  }
@@ -37148,6 +37148,21 @@ function canHardlink(libraryRoot, gamePath) {
37148
37148
  return false;
37149
37149
  }
37150
37150
  }
37151
+ var hardlinkable = /* @__PURE__ */ new Map();
37152
+ function deployMethod(libraryRoot, gamePath, current) {
37153
+ try {
37154
+ if (fs4.statSync(libraryRoot).dev !== fs4.statSync(gamePath).dev) return "copy";
37155
+ } catch {
37156
+ return current ?? "copy";
37157
+ }
37158
+ if (current === "link") return "link";
37159
+ const k = `${path4.resolve(libraryRoot).toLowerCase()}|${path4.resolve(gamePath).toLowerCase()}`;
37160
+ if (!hardlinkable.has(k)) hardlinkable.set(k, canHardlink(libraryRoot, gamePath));
37161
+ return hardlinkable.get(k) ? "link" : "copy";
37162
+ }
37163
+ function otherMethod(entry, method) {
37164
+ return method === "link" !== !!entry.ino;
37165
+ }
37151
37166
  function sourceReplaced(entry, method, d) {
37152
37167
  if (d.content !== void 0) return false;
37153
37168
  const src = identity(d.src);
@@ -37155,10 +37170,10 @@ function sourceReplaced(entry, method, d) {
37155
37170
  if (method === "link" && entry.ino) return src.ino !== entry.ino;
37156
37171
  return src.size !== entry.size || Math.abs(src.mtimeMs - entry.mtimeMs) >= 2e3;
37157
37172
  }
37158
- function stillOurs(entry, method, gamePath) {
37173
+ function stillOurs(entry, _method, gamePath) {
37159
37174
  const id = identity(gameFile(gamePath, entry.path));
37160
37175
  if (!id) return "missing";
37161
- if (method === "link" && entry.ino) {
37176
+ if (entry.ino) {
37162
37177
  if (id.ino !== entry.ino) return "changed";
37163
37178
  if (id.nlink < 2 && entry.mod !== Generated) return "sourceMissing";
37164
37179
  return "ok";
@@ -37174,7 +37189,7 @@ function generatedDiffers(file2, content) {
37174
37189
  }
37175
37190
  function plan(lib, gamePath) {
37176
37191
  const manifest = readManifest(gamePath);
37177
- const method = manifest?.method ?? (canHardlink(lib.root, gamePath) ? "link" : "copy");
37192
+ const method = deployMethod(lib.root, gamePath, manifest?.method);
37178
37193
  const desired = /* @__PURE__ */ new Map();
37179
37194
  const conflicts = /* @__PURE__ */ new Map();
37180
37195
  for (const mod of lib.mods) {
@@ -37205,6 +37220,7 @@ function plan(lib, gamePath) {
37205
37220
  if (!e || (d.mod?.id ?? Generated) !== e.mod) return true;
37206
37221
  if (d.content !== void 0 && generatedDiffers(gameFile(gamePath, d.rel), d.content)) return true;
37207
37222
  if (stillOurs(e, method, gamePath) !== "ok") return true;
37223
+ if (d.content === void 0 && otherMethod(e, method)) return true;
37208
37224
  return sourceReplaced(e, method, d);
37209
37225
  });
37210
37226
  return { method, desired: [...desired.values()], conflicts: [...conflicts.values()], stale, add, vortex: vortexManages(gamePath) };
@@ -37336,6 +37352,7 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
37336
37352
  const p = plan(lib, gamePath);
37337
37353
  const manifest = readManifest(gamePath) ?? { version: 1, library: lib.root, gamePath, method: p.method, time: "", files: [], dirs: [] };
37338
37354
  manifest.library = lib.root;
37355
+ manifest.method = p.method;
37339
37356
  const result = { linked: 0, removed: 0, kept: 0, backedUp: 0, method: manifest.method, skipped: [], changes: [], modsInGame: 0, lost: lostFiles(p) };
37340
37357
  const entries = new Map(manifest.files.map((e) => [key(e.path), e]));
37341
37358
  const scope = only ? modDeployPaths(gamePath, p, only) : void 0;
@@ -37462,6 +37479,7 @@ function pendingChanges(lib, gamePath, p) {
37462
37479
  const st = stillOurs(e, p.method, gamePath);
37463
37480
  if (st === "missing") out.push({ path: d.rel, mod: id, kind: "restore", detail: "missing from the game folder" });
37464
37481
  else if (st === "changed") out.push({ path: d.rel, mod: id, kind: "restore", detail: "changed in the game folder; deploy asks whether to keep it" });
37482
+ else if (d.content === void 0 && otherMethod(e, p.method)) out.push({ path: d.rel, mod: id, kind: "relink", detail: p.method === "link" ? "the library is on the game's drive now: a hardlink instead of a copy" : "the library is on another drive now: a copy instead of a hardlink" });
37465
37483
  else out.push({ path: d.rel, mod: id, kind: "relink", detail: d.content !== void 0 ? "the generated content changed (load order)" : "the library file was replaced (new version or rebuild)" });
37466
37484
  }
37467
37485
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgemodders/edgedeck-mcp",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "EDGEdeck's MCP server and command line: manage Cyberpunk 2077 mods (add, turn on or off, order, deploy, undo, conflicts, loadouts) from an AI assistant or a script.",
5
5
  "license": "MIT",
6
6
  "author": "Edgemodders",