@edgemodders/edgedeck-mcp 0.27.3 → 0.28.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/README.md +5 -4
- package/bin/edgedeck.mjs +9 -4
- package/dist/core.js +197 -24
- package/dist/mcp.js +218 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,19 +23,20 @@ Tools:
|
|
|
23
23
|
| `edgedeck_list` | Every mod: on or off, version, kinds, status, size; the frameworks line after a game patch |
|
|
24
24
|
| `edgedeck_mod` | One mod in detail: files, versions, Nexus info |
|
|
25
25
|
| `edgedeck_add` | Add a zip, 7z, rar, single mod file or folder to the library |
|
|
26
|
-
| `edgedeck_install_project` | Add or refresh a REDcode or WolvenKit project's packed output |
|
|
26
|
+
| `edgedeck_install_project` | Add or refresh a REDcode or WolvenKit project's packed output; `sameName` (`follow` or `version`) settles a mod of the same name from another folder |
|
|
27
27
|
| `edgedeck_turn` | Turn a mod on or off |
|
|
28
28
|
| `edgedeck_move` | Move a mod up or down; higher wins a conflict |
|
|
29
|
+
| `edgedeck_rename` | A new name for a mod in EDGEdeck, for all its versions; empty puts the first name back |
|
|
29
30
|
| `edgedeck_remove` | Remove a mod from the library |
|
|
30
31
|
| `edgedeck_pending` | What the next deploy would add, replace, put back or remove, and why |
|
|
31
|
-
| `edgedeck_deploy` | Put the active loadout in the game (refuses while the game runs) |
|
|
32
|
+
| `edgedeck_deploy` | Put the active loadout in the game (refuses while the game runs); `mod` deploys that mod's changes only |
|
|
32
33
|
| `edgedeck_undo` | Take everything EDGEdeck deployed out again, putting backups back |
|
|
33
34
|
| `edgedeck_changes` | Deployed files changed or missing in the game folder since |
|
|
34
35
|
| `edgedeck_conflicts` | Files two enabled mods both have, and who wins |
|
|
35
36
|
| `edgedeck_loadout` | Save, switch to (load) or list loadouts |
|
|
36
37
|
| `edgedeck_vortex` | List and import the mods Vortex has for the game (Vortex is not changed) |
|
|
37
38
|
|
|
38
|
-
Nothing touches the game folder until `edgedeck_deploy` or `edgedeck_undo`; the server tells the assistant to ask the player first.
|
|
39
|
+
Nothing touches the game folder until `edgedeck_deploy` or `edgedeck_undo`; the server tells the assistant to ask the player first. These two and `edgedeck_remove` also carry `_meta: { "anthropic/requiresUserInteraction": true }`, so Claude Code asks you before every call, in every permission mode, auto mode included.
|
|
39
40
|
|
|
40
41
|
## The command line
|
|
41
42
|
|
|
@@ -43,7 +44,7 @@ Nothing touches the game folder until `edgedeck_deploy` or `edgedeck_undo`; the
|
|
|
43
44
|
npx -p @edgemodders/edgedeck-mcp edgedeck list
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
or, installed with `npm i -g @edgemodders/edgedeck-mcp`, just `edgedeck list`. Commands: `list`, `mod`, `add`, `install-project`, `on`, `off`, `up`, `down`, `remove`, `pending`, `deploy`, `undo`, `changes`, `conflicts`, `loadout save|load|list`, `vortex list|import`, `mcp`. Options `--library`, `--game`, and `--json` for machine output.
|
|
47
|
+
or, installed with `npm i -g @edgemodders/edgedeck-mcp`, just `edgedeck list`. Commands: `list`, `mod`, `add`, `install-project`, `on`, `off`, `up`, `down`, `rename`, `remove`, `pending`, `deploy`, `undo`, `changes`, `conflicts`, `loadout save|load|list`, `vortex list|import`, `mcp`. Options `--library`, `--game`, and `--json` for machine output.
|
|
47
48
|
|
|
48
49
|
## More
|
|
49
50
|
|
package/bin/edgedeck.mjs
CHANGED
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
// edgedeck list [--size] what is in the library, its size and status (--size: largest first)
|
|
6
6
|
// edgedeck mod <id> one mod in full: files, sizes, source, Nexus data, conflicts
|
|
7
7
|
// edgedeck add <file|folder> [--name N] [--option FOLDER]
|
|
8
|
-
// edgedeck install-project <.cpmodproj>
|
|
8
|
+
// edgedeck install-project <.cpmodproj> [--follow | --keep-both]
|
|
9
|
+
// add a REDcode or WolvenKit project's packed output, or refresh it, and turn it on;
|
|
10
|
+
// beside a mod of the same name: --follow makes that mod follow this project,
|
|
11
|
+
// --keep-both adds the project as another version of it, on in its place
|
|
9
12
|
// edgedeck on <id> | off <id> turn a mod on or off
|
|
10
13
|
// edgedeck up <id> | down <id> change the order (first wins conflicts)
|
|
14
|
+
// edgedeck rename <id> [name...] a new name in EDGEdeck, for all its versions (no name: the first one back)
|
|
11
15
|
// edgedeck remove <id>
|
|
12
|
-
// edgedeck deploy [--keep PATH...] [--restore PATH...]
|
|
16
|
+
// edgedeck deploy [--keep PATH...] [--restore PATH...] [--mod ID] --mod: that mod's changes only
|
|
13
17
|
// edgedeck undo
|
|
14
18
|
// edgedeck changes files changed in the game folder since the deploy
|
|
15
19
|
// edgedeck pending what the next deploy adds, replaces, puts back or removes, and why
|
|
@@ -66,13 +70,14 @@ if (cmd === "mcp") {
|
|
|
66
70
|
case "list": out(await v.list(s, !!flags.size)); break;
|
|
67
71
|
case "mod": out(v.mod(s, need(rest[0], "a mod id"))); break;
|
|
68
72
|
case "add": out(await v.add(s, need(rest[0], "a file or folder"), flags.name, flags.option)); break;
|
|
69
|
-
case "install-project": out(v.installProject(s, need(rest[0], "a .cpmodproj file"))); break;
|
|
73
|
+
case "install-project": out(v.installProject(s, need(rest[0], "a .cpmodproj file"), flags.follow ? "follow" : flags["keep-both"] ? "version" : undefined)); break;
|
|
70
74
|
case "on":
|
|
71
75
|
case "off": out(v.turn(s, need(rest[0], "a mod id"), cmd === "on")); break;
|
|
72
76
|
case "up":
|
|
73
77
|
case "down": out(v.move(s, need(rest[0], "a mod id"), cmd)); break;
|
|
78
|
+
case "rename": out(v.rename(s, need(rest[0], "a mod id"), rest.slice(1).join(" "))); break;
|
|
74
79
|
case "remove": out(v.remove(s, need(rest[0], "a mod id"))); break;
|
|
75
|
-
case "deploy": out(await v.deploy(s, flags.keep ?? [], flags.restore ?? [])); break;
|
|
80
|
+
case "deploy": out(await v.deploy(s, flags.keep ?? [], flags.restore ?? [], typeof flags.mod === "string" ? flags.mod : undefined)); break;
|
|
76
81
|
case "undo": out(v.undo(s)); break;
|
|
77
82
|
case "changes": out(v.changes(s)); break;
|
|
78
83
|
case "pending": out(v.pending(s)); break;
|
package/dist/core.js
CHANGED
|
@@ -3739,6 +3739,7 @@ __export(core_exports, {
|
|
|
3739
3739
|
deploy: () => deploy,
|
|
3740
3740
|
deployRedmod: () => deployRedmod,
|
|
3741
3741
|
deployReportsFolder: () => deployReportsFolder,
|
|
3742
|
+
deployedRedmodPackages: () => deployedRedmodPackages,
|
|
3742
3743
|
describeHandler: () => describeHandler,
|
|
3743
3744
|
detectChanges: () => detectChanges,
|
|
3744
3745
|
dotnet10Root: () => dotnet10Root,
|
|
@@ -3784,6 +3785,7 @@ __export(core_exports, {
|
|
|
3784
3785
|
isGameFolder: () => isGameFolder,
|
|
3785
3786
|
keepDownload: () => keepDownload,
|
|
3786
3787
|
latestBridgeRelease: () => latestBridgeRelease,
|
|
3788
|
+
launchTarget: () => launchTarget,
|
|
3787
3789
|
libraryFolder: () => libraryFolder,
|
|
3788
3790
|
librarySize: () => librarySize,
|
|
3789
3791
|
listLoadouts: () => listLoadouts,
|
|
@@ -3792,8 +3794,10 @@ __export(core_exports, {
|
|
|
3792
3794
|
loadoutNameProblem: () => loadoutNameProblem,
|
|
3793
3795
|
loadoutState: () => loadoutState,
|
|
3794
3796
|
loadoutsFolder: () => loadoutsFolder,
|
|
3797
|
+
lostFiles: () => lostFiles,
|
|
3795
3798
|
makeActive: () => makeActive,
|
|
3796
3799
|
md5Of: () => md5Of,
|
|
3800
|
+
modDeployPaths: () => modDeployPaths,
|
|
3797
3801
|
modFolder: () => modFolder,
|
|
3798
3802
|
modPageUrl: () => modPageUrl,
|
|
3799
3803
|
modSize: () => modSize,
|
|
@@ -3817,6 +3821,7 @@ __export(core_exports, {
|
|
|
3817
3821
|
placeAbove: () => placeAbove,
|
|
3818
3822
|
placeNextTo: () => placeNextTo,
|
|
3819
3823
|
plan: () => plan,
|
|
3824
|
+
projectMod: () => projectMod,
|
|
3820
3825
|
projectName: () => projectName,
|
|
3821
3826
|
putInSection: () => putInSection,
|
|
3822
3827
|
readLoadout: () => readLoadout,
|
|
@@ -3828,8 +3833,10 @@ __export(core_exports, {
|
|
|
3828
3833
|
registerNxmHandler: () => registerNxmHandler,
|
|
3829
3834
|
removeMod: () => removeMod,
|
|
3830
3835
|
removeOlderBridges: () => removeOlderBridges,
|
|
3836
|
+
renameFamily: () => renameFamily,
|
|
3831
3837
|
renameLoadout: () => renameLoadout,
|
|
3832
3838
|
renameSection: () => renameSection,
|
|
3839
|
+
sameNameMod: () => sameNameMod,
|
|
3833
3840
|
saveLibrary: () => saveLibrary,
|
|
3834
3841
|
saveLoadout: () => saveLoadout,
|
|
3835
3842
|
saveMod: () => saveMod,
|
|
@@ -3847,6 +3854,7 @@ __export(core_exports, {
|
|
|
3847
3854
|
steamLibraries: () => steamLibraries,
|
|
3848
3855
|
syncActiveLoadout: () => syncActiveLoadout,
|
|
3849
3856
|
syncProjects: () => syncProjects,
|
|
3857
|
+
takeFamilyName: () => takeFamilyName,
|
|
3850
3858
|
tokenUser: () => tokenUser,
|
|
3851
3859
|
undo: () => undo,
|
|
3852
3860
|
unpackOrCopy: () => unpackOrCopy,
|
|
@@ -4467,6 +4475,11 @@ function gameRunning() {
|
|
|
4467
4475
|
return false;
|
|
4468
4476
|
}
|
|
4469
4477
|
}
|
|
4478
|
+
function launchTarget(gamePath, how) {
|
|
4479
|
+
const steam = how === "steam" || how === "auto" && /[\\/]steamapps[\\/]common[\\/]/i.test(gamePath);
|
|
4480
|
+
if (steam) return { kind: "steam", url: "steam://run/1091500//-modded/" };
|
|
4481
|
+
return { kind: "exe", exe: gameExe(gamePath), args: ["-modded"] };
|
|
4482
|
+
}
|
|
4470
4483
|
function isGameFolder(gamePath) {
|
|
4471
4484
|
return !!gamePath && fs6.existsSync(gameExe(gamePath));
|
|
4472
4485
|
}
|
|
@@ -4757,6 +4770,18 @@ function detectChanges(gamePath) {
|
|
|
4757
4770
|
}
|
|
4758
4771
|
return changes;
|
|
4759
4772
|
}
|
|
4773
|
+
function lostFiles(p) {
|
|
4774
|
+
const out = /* @__PURE__ */ new Map();
|
|
4775
|
+
for (const c of p.conflicts) {
|
|
4776
|
+
for (const loser of c.losers) {
|
|
4777
|
+
const k = `${loser}\0${c.winner}`;
|
|
4778
|
+
const l = out.get(k) ?? { mod: loser, files: 0, winner: c.winner };
|
|
4779
|
+
l.files++;
|
|
4780
|
+
out.set(k, l);
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
return [...out.values()];
|
|
4784
|
+
}
|
|
4760
4785
|
function modsTouched(result) {
|
|
4761
4786
|
return [...new Set(result.changes.filter((c) => c.kind !== "removed" && c.mod !== Generated).map((c) => c.mod))];
|
|
4762
4787
|
}
|
|
@@ -4849,28 +4874,46 @@ function cleanDirs(gamePath, manifest) {
|
|
|
4849
4874
|
}
|
|
4850
4875
|
manifest.dirs = manifest.dirs.filter((d) => fs8.existsSync(path7.join(gamePath, ...d.split("/"))));
|
|
4851
4876
|
}
|
|
4852
|
-
function
|
|
4877
|
+
function modDeployPaths(gamePath, p, id) {
|
|
4878
|
+
const paths = new Set((readManifest(gamePath)?.files ?? []).filter((e) => e.mod === id).map((e) => key(e.path)));
|
|
4879
|
+
for (const d of p.desired) if (d.mod?.id === id) paths.add(key(d.rel));
|
|
4880
|
+
return paths;
|
|
4881
|
+
}
|
|
4882
|
+
function modlistFor(lib, files) {
|
|
4883
|
+
const rank = new Map(lib.mods.map((m, i) => [m.id, i]));
|
|
4884
|
+
const archives = files.filter((e) => e.mod !== Generated && e.path.toLowerCase().startsWith("archive/pc/mod/") && e.path.toLowerCase().endsWith(".archive")).sort((a, b) => (rank.get(a.mod) ?? 1e9) - (rank.get(b.mod) ?? 1e9) || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
4885
|
+
return archives.length ? archives.map((e) => path7.posix.basename(e.path)).join("\r\n") + "\r\n" : "";
|
|
4886
|
+
}
|
|
4887
|
+
function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress, gameVersion2, only) {
|
|
4853
4888
|
if (vortexManages(gamePath)) throw new Error("Vortex manages this game folder. Purge in Vortex first (Mods > Purge), then deploy with EDGEdeck.");
|
|
4854
4889
|
if (!isGameFolder(gamePath)) throw new Error(`${gamePath} does not look like the Cyberpunk 2077 folder (no bin\\x64\\Cyberpunk2077.exe).`);
|
|
4855
4890
|
const p = plan(lib, gamePath);
|
|
4856
4891
|
const manifest = readManifest(gamePath) ?? { version: 1, library: lib.root, gamePath, method: p.method, time: "", files: [], dirs: [] };
|
|
4857
4892
|
manifest.library = lib.root;
|
|
4858
|
-
const result = { linked: 0, removed: 0, kept: 0, backedUp: 0, method: manifest.method, skipped: [], changes: [], modsInGame: 0 };
|
|
4893
|
+
const result = { linked: 0, removed: 0, kept: 0, backedUp: 0, method: manifest.method, skipped: [], changes: [], modsInGame: 0, lost: lostFiles(p) };
|
|
4859
4894
|
const entries = new Map(manifest.files.map((e) => [key(e.path), e]));
|
|
4895
|
+
const scope = only ? modDeployPaths(gamePath, p, only) : void 0;
|
|
4896
|
+
const inScope = (rel) => !scope || scope.has(key(rel));
|
|
4897
|
+
const stale = p.stale.filter((e) => inScope(e.path) && !(scope && e.mod === Generated));
|
|
4898
|
+
const add = p.add.filter((d) => inScope(d.rel) && !(scope && d.content !== void 0));
|
|
4899
|
+
if (scope) {
|
|
4900
|
+
const waiting = new Set([...p.stale.map((e) => e.path), ...p.add.map((d) => d.rel)].filter((r) => !scope.has(key(r)) && key(r) !== key(ModListPath)).map(key));
|
|
4901
|
+
result.waiting = waiting.size;
|
|
4902
|
+
}
|
|
4860
4903
|
for (const [rel, decision] of decisions) {
|
|
4861
4904
|
const d = p.desired.find((x) => key(x.rel) === key(rel));
|
|
4862
4905
|
if (decision === "keep" && d?.mod) {
|
|
4863
4906
|
fs8.copyFileSync(gameFile(gamePath, rel), d.src);
|
|
4864
4907
|
}
|
|
4865
4908
|
}
|
|
4866
|
-
const total =
|
|
4909
|
+
const total = stale.length + add.length;
|
|
4867
4910
|
let done = 0;
|
|
4868
4911
|
const save = () => {
|
|
4869
4912
|
manifest.time = (/* @__PURE__ */ new Date()).toISOString();
|
|
4870
4913
|
if (gameVersion2) manifest.gameVersion = gameVersion2;
|
|
4871
4914
|
writeManifest(gamePath, manifest);
|
|
4872
4915
|
};
|
|
4873
|
-
|
|
4916
|
+
const takeOut = (entry) => {
|
|
4874
4917
|
const decided = decisions.get(entry.path);
|
|
4875
4918
|
if (removeDeployed(lib, gamePath, manifest, entry, decided !== void 0, result.skipped)) {
|
|
4876
4919
|
entries.delete(key(entry.path));
|
|
@@ -4878,10 +4921,8 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
|
|
|
4878
4921
|
result.changes.push({ path: entry.path, mod: entry.mod, kind: "removed", ...entry.backup ? { backup: true } : {} });
|
|
4879
4922
|
}
|
|
4880
4923
|
manifest.files = [...entries.values()];
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
if (p.stale.length) cleanDirs(gamePath, manifest);
|
|
4884
|
-
for (const d of p.add) {
|
|
4924
|
+
};
|
|
4925
|
+
const putIn = (d) => {
|
|
4885
4926
|
const target = gameFile(gamePath, d.rel);
|
|
4886
4927
|
const existing = entries.get(key(d.rel));
|
|
4887
4928
|
let backup = existing?.backup ?? false;
|
|
@@ -4889,7 +4930,7 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
|
|
|
4889
4930
|
const state = stillOurs(existing, manifest.method, gamePath);
|
|
4890
4931
|
if (state === "changed" && !decisions.has(d.rel)) {
|
|
4891
4932
|
result.skipped.push(d.rel);
|
|
4892
|
-
|
|
4933
|
+
return;
|
|
4893
4934
|
}
|
|
4894
4935
|
fs8.rmSync(target, { force: true });
|
|
4895
4936
|
} else if (fs8.existsSync(target)) {
|
|
@@ -4915,10 +4956,29 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
|
|
|
4915
4956
|
manifest.files = [...entries.values()];
|
|
4916
4957
|
result.linked++;
|
|
4917
4958
|
result.changes.push({ path: d.rel, mod: entry.mod, kind: existing ? "replaced" : "added", ...backup && !existing ? { backup: true } : {} });
|
|
4959
|
+
};
|
|
4960
|
+
for (const entry of stale) {
|
|
4961
|
+
takeOut(entry);
|
|
4962
|
+
progress?.(++done, total);
|
|
4963
|
+
}
|
|
4964
|
+
if (stale.length) cleanDirs(gamePath, manifest);
|
|
4965
|
+
for (const d of add) {
|
|
4966
|
+
putIn(d);
|
|
4918
4967
|
progress?.(++done, total);
|
|
4919
4968
|
if (done % 200 === 0) save();
|
|
4920
4969
|
}
|
|
4921
|
-
|
|
4970
|
+
if (scope) {
|
|
4971
|
+
const content = modlistFor(lib, manifest.files);
|
|
4972
|
+
const e = entries.get(key(ModListPath));
|
|
4973
|
+
if (!content && e) {
|
|
4974
|
+
takeOut(e);
|
|
4975
|
+
cleanDirs(gamePath, manifest);
|
|
4976
|
+
} else if (content && (!e || generatedDiffers(gameFile(gamePath, ModListPath), content))) {
|
|
4977
|
+
putIn({ rel: ModListPath, mod: void 0, src: "", content });
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
result.kept = (scope ? p.desired.filter((d) => scope.has(key(d.rel))).length : p.desired.length) - result.linked - result.skipped.length;
|
|
4981
|
+
if (scope) result.kept = Math.max(0, result.kept);
|
|
4922
4982
|
result.modsInGame = new Set(manifest.files.map((e) => e.mod).filter((m) => m !== Generated)).size;
|
|
4923
4983
|
save();
|
|
4924
4984
|
return result;
|
|
@@ -4992,6 +5052,7 @@ function rednodePackageId(mod) {
|
|
|
4992
5052
|
return void 0;
|
|
4993
5053
|
}
|
|
4994
5054
|
function familyKey(mod) {
|
|
5055
|
+
if (mod.family) return mod.family;
|
|
4995
5056
|
if (mod.source.modId) return `nexus:${mod.source.modId}`;
|
|
4996
5057
|
const pkg = rednodePackageId(mod);
|
|
4997
5058
|
if (pkg) return `rednode:${pkg}`;
|
|
@@ -5044,6 +5105,7 @@ function settleNewVersion(lib, mod) {
|
|
|
5044
5105
|
const fam = familyOf(lib, mod);
|
|
5045
5106
|
const others = fam.mods.filter((m) => m.id !== mod.id);
|
|
5046
5107
|
if (!others.length) return {};
|
|
5108
|
+
takeFamilyName(lib, mod, others);
|
|
5047
5109
|
const previous = others.find((m) => m.enabled) ?? [...others].sort(newestFirst)[0];
|
|
5048
5110
|
if (mod.version && previous.version && versionNewer(previous.version, mod.version)) {
|
|
5049
5111
|
if (mod.enabled) {
|
|
@@ -5066,6 +5128,31 @@ function settleNewVersion(lib, mod) {
|
|
|
5066
5128
|
}
|
|
5067
5129
|
return { replaced: previous };
|
|
5068
5130
|
}
|
|
5131
|
+
function renameFamily(lib, mod, name) {
|
|
5132
|
+
const fam = familyOf(lib, mod);
|
|
5133
|
+
const to = name.trim();
|
|
5134
|
+
for (const m of fam.mods) {
|
|
5135
|
+
m.family = fam.key;
|
|
5136
|
+
if (!to) {
|
|
5137
|
+
if (m.renamedFrom !== void 0) m.name = m.renamedFrom;
|
|
5138
|
+
delete m.renamedFrom;
|
|
5139
|
+
} else if (m.name !== to) {
|
|
5140
|
+
m.renamedFrom ??= m.name;
|
|
5141
|
+
m.name = to;
|
|
5142
|
+
if (m.renamedFrom === to) delete m.renamedFrom;
|
|
5143
|
+
}
|
|
5144
|
+
saveMod(lib, m);
|
|
5145
|
+
}
|
|
5146
|
+
return familyOf(lib, mod);
|
|
5147
|
+
}
|
|
5148
|
+
function takeFamilyName(lib, mod, others) {
|
|
5149
|
+
const renamed = others.find((m) => m.renamedFrom !== void 0);
|
|
5150
|
+
if (!renamed || mod.name === renamed.name) return;
|
|
5151
|
+
mod.family = familyKey(renamed);
|
|
5152
|
+
mod.renamedFrom = mod.name;
|
|
5153
|
+
mod.name = renamed.name;
|
|
5154
|
+
saveMod(lib, mod);
|
|
5155
|
+
}
|
|
5069
5156
|
function familiesWithTwoOn(lib) {
|
|
5070
5157
|
return families(lib).filter((f) => f.mods.filter((m) => m.enabled).length > 1);
|
|
5071
5158
|
}
|
|
@@ -5671,11 +5758,48 @@ function syncProjects(lib) {
|
|
|
5671
5758
|
}
|
|
5672
5759
|
return refreshed;
|
|
5673
5760
|
}
|
|
5674
|
-
function
|
|
5761
|
+
function projectMod(lib, projectFile) {
|
|
5762
|
+
return lib.mods.find((m) => m.source.type === "project" && m.source.path?.toLowerCase() === projectFile.toLowerCase());
|
|
5763
|
+
}
|
|
5764
|
+
function sameNameMod(lib, projectFile) {
|
|
5765
|
+
if (projectMod(lib, projectFile)) return void 0;
|
|
5766
|
+
const name = baseName(projectName(projectFile));
|
|
5767
|
+
const match = name ? lib.mods.find((m) => baseName(m.name) === name || m.renamedFrom !== void 0 && baseName(m.renamedFrom) === name) : void 0;
|
|
5768
|
+
return match && familyOf(lib, match).active;
|
|
5769
|
+
}
|
|
5770
|
+
function devVersion(lib, family) {
|
|
5771
|
+
const taken = new Set(lib.mods.filter((m) => familyKey(m) === family).map((m) => m.version.toLowerCase()));
|
|
5772
|
+
if (!taken.has("dev")) return "dev";
|
|
5773
|
+
let n = 2;
|
|
5774
|
+
while (taken.has(`dev ${n}`)) n++;
|
|
5775
|
+
return `dev ${n}`;
|
|
5776
|
+
}
|
|
5777
|
+
function installProject(lib, projectFile, beside) {
|
|
5675
5778
|
const c = classifyProject(projectFile);
|
|
5676
5779
|
if (!c.placements.length) throw new Error(`${projectName(projectFile)} has no packed output yet. Pack the project first (packed/ next to the project file).`);
|
|
5677
|
-
|
|
5780
|
+
let existing = projectMod(lib, projectFile);
|
|
5781
|
+
if (!existing && beside?.how === "follow") {
|
|
5782
|
+
existing = beside.mod;
|
|
5783
|
+
existing.family = familyKey(existing);
|
|
5784
|
+
existing.source = { type: "project", path: projectFile };
|
|
5785
|
+
}
|
|
5678
5786
|
if (!existing) {
|
|
5787
|
+
if (beside?.how === "version") {
|
|
5788
|
+
const family = familyKey(beside.mod);
|
|
5789
|
+
const previous = familyOf(lib, beside.mod);
|
|
5790
|
+
const mod2 = addMod(lib, c.placements, { name: projectName(projectFile), version: devVersion(lib, family), source: { type: "project", path: projectFile } });
|
|
5791
|
+
mod2.family = family;
|
|
5792
|
+
takeFamilyName(lib, mod2, previous.mods);
|
|
5793
|
+
placeAbove(lib, mod2, previous.active);
|
|
5794
|
+
for (const m of previous.mods) {
|
|
5795
|
+
if (m.enabled) {
|
|
5796
|
+
m.enabled = false;
|
|
5797
|
+
saveMod(lib, m);
|
|
5798
|
+
}
|
|
5799
|
+
}
|
|
5800
|
+
saveMod(lib, mod2);
|
|
5801
|
+
return { mod: mod2, added: true, beside };
|
|
5802
|
+
}
|
|
5679
5803
|
const mod = addMod(lib, c.placements, { name: projectName(projectFile), version: "dev", source: { type: "project", path: projectFile } });
|
|
5680
5804
|
return { mod, added: true };
|
|
5681
5805
|
}
|
|
@@ -5689,9 +5813,10 @@ function installProject(lib, projectFile) {
|
|
|
5689
5813
|
for (const f of existing.files) if (!keep.has(f)) fs10.rmSync(sourcePath(lib, existing, f), { force: true });
|
|
5690
5814
|
existing.files = [...keep].sort();
|
|
5691
5815
|
existing.kinds = c.kinds;
|
|
5816
|
+
activateVersion(lib, existing);
|
|
5692
5817
|
existing.enabled = true;
|
|
5693
5818
|
saveMod(lib, existing);
|
|
5694
|
-
return { mod: existing, added: false };
|
|
5819
|
+
return { mod: existing, added: false, ...beside ? { beside } : {} };
|
|
5695
5820
|
}
|
|
5696
5821
|
|
|
5697
5822
|
// src/redmod.ts
|
|
@@ -5714,8 +5839,18 @@ function enabledRedmodPackages(lib) {
|
|
|
5714
5839
|
}
|
|
5715
5840
|
return names;
|
|
5716
5841
|
}
|
|
5717
|
-
|
|
5718
|
-
const
|
|
5842
|
+
function deployedRedmodPackages(lib, gamePath) {
|
|
5843
|
+
const inGame = new Set((readManifest(gamePath)?.files ?? []).map((e) => /^mods\/([^/]+)\//i.exec(e.path)?.[1]).filter((n) => !!n));
|
|
5844
|
+
const names = [];
|
|
5845
|
+
for (const mod of lib.mods) {
|
|
5846
|
+
for (const f of mod.files) {
|
|
5847
|
+
const m = /^mods\/([^/]+)\//i.exec(f);
|
|
5848
|
+
if (m && inGame.has(m[1]) && !names.includes(m[1])) names.push(m[1]);
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
return names;
|
|
5852
|
+
}
|
|
5853
|
+
async function deployRedmod(lib, gamePath, log, packages = enabledRedmodPackages(lib)) {
|
|
5719
5854
|
const exe = redModExe(gamePath);
|
|
5720
5855
|
if (!packages.length) return { ran: false, packages, output: "no REDmod packages enabled" };
|
|
5721
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` };
|
|
@@ -6045,6 +6180,13 @@ function importFromVortex(lib, mods) {
|
|
|
6045
6180
|
}
|
|
6046
6181
|
|
|
6047
6182
|
// src/verbs.ts
|
|
6183
|
+
function lostProjectsText(lib, lost) {
|
|
6184
|
+
const lines = lost.flatMap((l) => {
|
|
6185
|
+
const mod = lib.mods.find((m) => m.id === l.mod);
|
|
6186
|
+
return mod?.source.type === "project" ? [`the game still has ${l.winner} for ${l.files} file(s), not the project ${l.mod} (move ${l.mod} up to let it win)`] : [];
|
|
6187
|
+
});
|
|
6188
|
+
return lines.length ? `; ${lines.join("; ")}` : "";
|
|
6189
|
+
}
|
|
6048
6190
|
var DefaultGame = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Cyberpunk 2077";
|
|
6049
6191
|
function openSession(opts) {
|
|
6050
6192
|
const libraryRoot = opts.library ?? defaultLibraryPath();
|
|
@@ -6139,12 +6281,20 @@ ${mod.files.join("\n")}`;
|
|
|
6139
6281
|
}
|
|
6140
6282
|
},
|
|
6141
6283
|
/** Adds a REDcode or WolvenKit project's packed output, or refreshes the mod that follows it, and turns it on. */
|
|
6142
|
-
|
|
6284
|
+
/**
|
|
6285
|
+
* `sameName` answers for a mod of the same name that does not follow this project: follow (it
|
|
6286
|
+
* follows this project from now on) or version (the project is another version of it, on in its
|
|
6287
|
+
* place). Without it, the project is a mod of its own and the text says what it sits beside.
|
|
6288
|
+
*/
|
|
6289
|
+
installProject(s, projectFile, sameName) {
|
|
6143
6290
|
if (!fs14.existsSync(projectFile)) throw new Error(`${projectFile} does not exist`);
|
|
6144
|
-
const
|
|
6291
|
+
const other = sameNameMod(s.lib, projectFile);
|
|
6292
|
+
const before = other && `${other.id}${other.source.type === "project" && other.source.path ? ` (${path13.dirname(other.source.path)})` : ""}`;
|
|
6293
|
+
const { mod, added, beside } = installProject(s.lib, projectFile, other && sameName ? { mod: other, how: sameName } : void 0);
|
|
6294
|
+
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)` : "";
|
|
6145
6295
|
return {
|
|
6146
|
-
data: { id: mod.id, name: mod.name, version: mod.version, kinds: mod.kinds, files: mod.files.length, added },
|
|
6147
|
-
text: `${added ? "added" : "refreshed"} ${mod.id} from its project (${mod.files.length} files, ${mod.kinds.join(", ")}); it is on, deploy to put it in the game`
|
|
6296
|
+
data: { id: mod.id, name: mod.name, version: mod.version, kinds: mod.kinds, files: mod.files.length, added, sameName: other?.id, beside: beside?.how },
|
|
6297
|
+
text: `${added ? "added" : "refreshed"} ${mod.id} from its project (${mod.files.length} files, ${mod.kinds.join(", ")}); it is on, deploy to put it in the game${note}`
|
|
6148
6298
|
};
|
|
6149
6299
|
},
|
|
6150
6300
|
turn(s, id, on) {
|
|
@@ -6158,22 +6308,37 @@ ${mod.files.join("\n")}`;
|
|
|
6158
6308
|
moveMod(s.lib, mod, direction === "up" ? -1 : 1);
|
|
6159
6309
|
return { data: { order: s.lib.info.order }, text: s.lib.info.order.join("\n") };
|
|
6160
6310
|
},
|
|
6311
|
+
/** A new name in EDGEdeck for a mod and all its versions; an empty name puts the first name back. */
|
|
6312
|
+
rename(s, id, name) {
|
|
6313
|
+
const mod = find(s, id);
|
|
6314
|
+
const before = familyOf(s.lib, mod).name;
|
|
6315
|
+
const fam = renameFamily(s.lib, mod, name);
|
|
6316
|
+
return {
|
|
6317
|
+
data: { id: mod.id, name: fam.name, versions: fam.mods.map((m) => m.id) },
|
|
6318
|
+
text: `${before} is now ${fam.name}${fam.mods.length > 1 ? ` (${fam.mods.length} versions)` : ""}; its files and the game do not change`
|
|
6319
|
+
};
|
|
6320
|
+
},
|
|
6161
6321
|
remove(s, id) {
|
|
6162
6322
|
const mod = find(s, id);
|
|
6163
6323
|
removeMod(s.lib, mod);
|
|
6164
6324
|
return { data: { removed: mod.id }, text: `removed ${mod.id} from the library; its deployed files leave on the next deploy` };
|
|
6165
6325
|
},
|
|
6166
|
-
/**
|
|
6167
|
-
|
|
6326
|
+
/**
|
|
6327
|
+
* Deploys. A file changed outside EDGEdeck is skipped unless named in keep (import it) or restore
|
|
6328
|
+
* (overwrite it). With `only` (a mod id), deploys that mod's changes and leaves the rest waiting.
|
|
6329
|
+
*/
|
|
6330
|
+
async deploy(s, keep = [], restore = [], only) {
|
|
6168
6331
|
gameReady(s);
|
|
6169
6332
|
if (gameRunning()) throw new Error("Cyberpunk 2077 is running; it keeps mod files open. Quit the game, then deploy");
|
|
6333
|
+
if (only) find(s, only);
|
|
6170
6334
|
const decisions = /* @__PURE__ */ new Map();
|
|
6171
6335
|
for (const p of keep) decisions.set(p, "keep");
|
|
6172
6336
|
for (const p of restore) decisions.set(p, "restore");
|
|
6173
|
-
const result = deploy(s.lib, s.game, decisions, void 0, await gameVersion(s.game));
|
|
6174
|
-
const
|
|
6337
|
+
const result = deploy(s.lib, s.game, decisions, void 0, await gameVersion(s.game), only);
|
|
6338
|
+
const packages = only ? result.changes.some((c) => c.path.toLowerCase().startsWith("mods/")) ? deployedRedmodPackages(s.lib, s.game) : [] : enabledRedmodPackages(s.lib);
|
|
6339
|
+
const redmod = packages.length ? await deployRedmod(s.lib, s.game, void 0, packages) : void 0;
|
|
6175
6340
|
const report = writeDeployReport(s.lib, s.game, result);
|
|
6176
|
-
const text = `deployed (${result.method}): ${result.linked} added for ${modsTouched(result).length} mod(s), ${result.removed} removed, ${result.kept} unchanged; ${result.modsInGame} mod(s) in the game; report ${report}${result.skipped.length ? `; skipped (changed in game folder, answer with keep or restore): ${result.skipped.join(", ")}` : ""}${redmod ? `; REDmod: ${redmod.ran ? `${redmod.packages.length} prepared` : redmod.output}` : ""}`;
|
|
6341
|
+
const text = `deployed${only ? ` ${only} only` : ""} (${result.method}): ${result.linked} added for ${modsTouched(result).length} mod(s), ${result.removed} removed, ${result.kept} unchanged; ${result.modsInGame} mod(s) in the game; report ${report}${result.skipped.length ? `; skipped (changed in game folder, answer with keep or restore): ${result.skipped.join(", ")}` : ""}${redmod ? `; REDmod: ${redmod.ran ? `${redmod.packages.length} prepared` : redmod.output}` : ""}${result.waiting ? `; ${result.waiting} file(s) of other mods' changes still waiting` : ""}${lostProjectsText(s.lib, result.lost)}`;
|
|
6177
6342
|
return { data: { ...result, redmod, report }, text };
|
|
6178
6343
|
},
|
|
6179
6344
|
undo(s) {
|
|
@@ -6271,6 +6436,7 @@ ${mod.files.join("\n")}`;
|
|
|
6271
6436
|
deploy,
|
|
6272
6437
|
deployRedmod,
|
|
6273
6438
|
deployReportsFolder,
|
|
6439
|
+
deployedRedmodPackages,
|
|
6274
6440
|
describeHandler,
|
|
6275
6441
|
detectChanges,
|
|
6276
6442
|
dotnet10Root,
|
|
@@ -6316,6 +6482,7 @@ ${mod.files.join("\n")}`;
|
|
|
6316
6482
|
isGameFolder,
|
|
6317
6483
|
keepDownload,
|
|
6318
6484
|
latestBridgeRelease,
|
|
6485
|
+
launchTarget,
|
|
6319
6486
|
libraryFolder,
|
|
6320
6487
|
librarySize,
|
|
6321
6488
|
listLoadouts,
|
|
@@ -6324,8 +6491,10 @@ ${mod.files.join("\n")}`;
|
|
|
6324
6491
|
loadoutNameProblem,
|
|
6325
6492
|
loadoutState,
|
|
6326
6493
|
loadoutsFolder,
|
|
6494
|
+
lostFiles,
|
|
6327
6495
|
makeActive,
|
|
6328
6496
|
md5Of,
|
|
6497
|
+
modDeployPaths,
|
|
6329
6498
|
modFolder,
|
|
6330
6499
|
modPageUrl,
|
|
6331
6500
|
modSize,
|
|
@@ -6349,6 +6518,7 @@ ${mod.files.join("\n")}`;
|
|
|
6349
6518
|
placeAbove,
|
|
6350
6519
|
placeNextTo,
|
|
6351
6520
|
plan,
|
|
6521
|
+
projectMod,
|
|
6352
6522
|
projectName,
|
|
6353
6523
|
putInSection,
|
|
6354
6524
|
readLoadout,
|
|
@@ -6360,8 +6530,10 @@ ${mod.files.join("\n")}`;
|
|
|
6360
6530
|
registerNxmHandler,
|
|
6361
6531
|
removeMod,
|
|
6362
6532
|
removeOlderBridges,
|
|
6533
|
+
renameFamily,
|
|
6363
6534
|
renameLoadout,
|
|
6364
6535
|
renameSection,
|
|
6536
|
+
sameNameMod,
|
|
6365
6537
|
saveLibrary,
|
|
6366
6538
|
saveLoadout,
|
|
6367
6539
|
saveMod,
|
|
@@ -6379,6 +6551,7 @@ ${mod.files.join("\n")}`;
|
|
|
6379
6551
|
steamLibraries,
|
|
6380
6552
|
syncActiveLoadout,
|
|
6381
6553
|
syncProjects,
|
|
6554
|
+
takeFamilyName,
|
|
6382
6555
|
tokenUser,
|
|
6383
6556
|
undo,
|
|
6384
6557
|
unpackOrCopy,
|
package/dist/mcp.js
CHANGED
|
@@ -36932,6 +36932,7 @@ function rednodePackageId(mod) {
|
|
|
36932
36932
|
return void 0;
|
|
36933
36933
|
}
|
|
36934
36934
|
function familyKey(mod) {
|
|
36935
|
+
if (mod.family) return mod.family;
|
|
36935
36936
|
if (mod.source.modId) return `nexus:${mod.source.modId}`;
|
|
36936
36937
|
const pkg = rednodePackageId(mod);
|
|
36937
36938
|
if (pkg) return `rednode:${pkg}`;
|
|
@@ -36963,10 +36964,25 @@ function families(lib) {
|
|
|
36963
36964
|
function familyOf(lib, mod) {
|
|
36964
36965
|
return families(lib).find((f) => f.key === familyKey(mod)) ?? { key: familyKey(mod), name: mod.name, nexusId: mod.source.modId, mods: [mod], active: mod, on: mod.enabled };
|
|
36965
36966
|
}
|
|
36967
|
+
function activateVersion(lib, chosen) {
|
|
36968
|
+
const fam = familyOf(lib, chosen);
|
|
36969
|
+
const previous = fam.active;
|
|
36970
|
+
const on = fam.on;
|
|
36971
|
+
if (previous.id !== chosen.id) placeAbove(lib, chosen, previous);
|
|
36972
|
+
for (const m of fam.mods) {
|
|
36973
|
+
const want = m.id === chosen.id ? on : false;
|
|
36974
|
+
if (m.enabled !== want) {
|
|
36975
|
+
m.enabled = want;
|
|
36976
|
+
saveMod(lib, m);
|
|
36977
|
+
}
|
|
36978
|
+
}
|
|
36979
|
+
return { previous, on };
|
|
36980
|
+
}
|
|
36966
36981
|
function settleNewVersion(lib, mod) {
|
|
36967
36982
|
const fam = familyOf(lib, mod);
|
|
36968
36983
|
const others = fam.mods.filter((m) => m.id !== mod.id);
|
|
36969
36984
|
if (!others.length) return {};
|
|
36985
|
+
takeFamilyName(lib, mod, others);
|
|
36970
36986
|
const previous = others.find((m) => m.enabled) ?? [...others].sort(newestFirst)[0];
|
|
36971
36987
|
if (mod.version && previous.version && versionNewer(previous.version, mod.version)) {
|
|
36972
36988
|
if (mod.enabled) {
|
|
@@ -36989,6 +37005,31 @@ function settleNewVersion(lib, mod) {
|
|
|
36989
37005
|
}
|
|
36990
37006
|
return { replaced: previous };
|
|
36991
37007
|
}
|
|
37008
|
+
function renameFamily(lib, mod, name) {
|
|
37009
|
+
const fam = familyOf(lib, mod);
|
|
37010
|
+
const to = name.trim();
|
|
37011
|
+
for (const m of fam.mods) {
|
|
37012
|
+
m.family = fam.key;
|
|
37013
|
+
if (!to) {
|
|
37014
|
+
if (m.renamedFrom !== void 0) m.name = m.renamedFrom;
|
|
37015
|
+
delete m.renamedFrom;
|
|
37016
|
+
} else if (m.name !== to) {
|
|
37017
|
+
m.renamedFrom ??= m.name;
|
|
37018
|
+
m.name = to;
|
|
37019
|
+
if (m.renamedFrom === to) delete m.renamedFrom;
|
|
37020
|
+
}
|
|
37021
|
+
saveMod(lib, m);
|
|
37022
|
+
}
|
|
37023
|
+
return familyOf(lib, mod);
|
|
37024
|
+
}
|
|
37025
|
+
function takeFamilyName(lib, mod, others) {
|
|
37026
|
+
const renamed = others.find((m) => m.renamedFrom !== void 0);
|
|
37027
|
+
if (!renamed || mod.name === renamed.name) return;
|
|
37028
|
+
mod.family = familyKey(renamed);
|
|
37029
|
+
mod.renamedFrom = mod.name;
|
|
37030
|
+
mod.name = renamed.name;
|
|
37031
|
+
saveMod(lib, mod);
|
|
37032
|
+
}
|
|
36992
37033
|
|
|
36993
37034
|
// src/deploy.ts
|
|
36994
37035
|
var fs4 = __toESM(require("node:fs"));
|
|
@@ -37178,6 +37219,18 @@ function detectChanges(gamePath) {
|
|
|
37178
37219
|
}
|
|
37179
37220
|
return changes;
|
|
37180
37221
|
}
|
|
37222
|
+
function lostFiles(p) {
|
|
37223
|
+
const out = /* @__PURE__ */ new Map();
|
|
37224
|
+
for (const c of p.conflicts) {
|
|
37225
|
+
for (const loser of c.losers) {
|
|
37226
|
+
const k = `${loser}\0${c.winner}`;
|
|
37227
|
+
const l = out.get(k) ?? { mod: loser, files: 0, winner: c.winner };
|
|
37228
|
+
l.files++;
|
|
37229
|
+
out.set(k, l);
|
|
37230
|
+
}
|
|
37231
|
+
}
|
|
37232
|
+
return [...out.values()];
|
|
37233
|
+
}
|
|
37181
37234
|
function modsTouched(result) {
|
|
37182
37235
|
return [...new Set(result.changes.filter((c) => c.kind !== "removed" && c.mod !== Generated).map((c) => c.mod))];
|
|
37183
37236
|
}
|
|
@@ -37267,28 +37320,46 @@ function cleanDirs(gamePath, manifest) {
|
|
|
37267
37320
|
}
|
|
37268
37321
|
manifest.dirs = manifest.dirs.filter((d) => fs4.existsSync(path4.join(gamePath, ...d.split("/"))));
|
|
37269
37322
|
}
|
|
37270
|
-
function
|
|
37323
|
+
function modDeployPaths(gamePath, p, id) {
|
|
37324
|
+
const paths = new Set((readManifest(gamePath)?.files ?? []).filter((e) => e.mod === id).map((e) => key(e.path)));
|
|
37325
|
+
for (const d of p.desired) if (d.mod?.id === id) paths.add(key(d.rel));
|
|
37326
|
+
return paths;
|
|
37327
|
+
}
|
|
37328
|
+
function modlistFor(lib, files) {
|
|
37329
|
+
const rank = new Map(lib.mods.map((m, i) => [m.id, i]));
|
|
37330
|
+
const archives = files.filter((e) => e.mod !== Generated && e.path.toLowerCase().startsWith("archive/pc/mod/") && e.path.toLowerCase().endsWith(".archive")).sort((a, b) => (rank.get(a.mod) ?? 1e9) - (rank.get(b.mod) ?? 1e9) || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
37331
|
+
return archives.length ? archives.map((e) => path4.posix.basename(e.path)).join("\r\n") + "\r\n" : "";
|
|
37332
|
+
}
|
|
37333
|
+
function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress, gameVersion2, only) {
|
|
37271
37334
|
if (vortexManages(gamePath)) throw new Error("Vortex manages this game folder. Purge in Vortex first (Mods > Purge), then deploy with EDGEdeck.");
|
|
37272
37335
|
if (!isGameFolder(gamePath)) throw new Error(`${gamePath} does not look like the Cyberpunk 2077 folder (no bin\\x64\\Cyberpunk2077.exe).`);
|
|
37273
37336
|
const p = plan(lib, gamePath);
|
|
37274
37337
|
const manifest = readManifest(gamePath) ?? { version: 1, library: lib.root, gamePath, method: p.method, time: "", files: [], dirs: [] };
|
|
37275
37338
|
manifest.library = lib.root;
|
|
37276
|
-
const result = { linked: 0, removed: 0, kept: 0, backedUp: 0, method: manifest.method, skipped: [], changes: [], modsInGame: 0 };
|
|
37339
|
+
const result = { linked: 0, removed: 0, kept: 0, backedUp: 0, method: manifest.method, skipped: [], changes: [], modsInGame: 0, lost: lostFiles(p) };
|
|
37277
37340
|
const entries = new Map(manifest.files.map((e) => [key(e.path), e]));
|
|
37341
|
+
const scope = only ? modDeployPaths(gamePath, p, only) : void 0;
|
|
37342
|
+
const inScope = (rel) => !scope || scope.has(key(rel));
|
|
37343
|
+
const stale = p.stale.filter((e) => inScope(e.path) && !(scope && e.mod === Generated));
|
|
37344
|
+
const add = p.add.filter((d) => inScope(d.rel) && !(scope && d.content !== void 0));
|
|
37345
|
+
if (scope) {
|
|
37346
|
+
const waiting = new Set([...p.stale.map((e) => e.path), ...p.add.map((d) => d.rel)].filter((r) => !scope.has(key(r)) && key(r) !== key(ModListPath)).map(key));
|
|
37347
|
+
result.waiting = waiting.size;
|
|
37348
|
+
}
|
|
37278
37349
|
for (const [rel, decision] of decisions) {
|
|
37279
37350
|
const d = p.desired.find((x) => key(x.rel) === key(rel));
|
|
37280
37351
|
if (decision === "keep" && d?.mod) {
|
|
37281
37352
|
fs4.copyFileSync(gameFile(gamePath, rel), d.src);
|
|
37282
37353
|
}
|
|
37283
37354
|
}
|
|
37284
|
-
const total =
|
|
37355
|
+
const total = stale.length + add.length;
|
|
37285
37356
|
let done = 0;
|
|
37286
37357
|
const save = () => {
|
|
37287
37358
|
manifest.time = (/* @__PURE__ */ new Date()).toISOString();
|
|
37288
37359
|
if (gameVersion2) manifest.gameVersion = gameVersion2;
|
|
37289
37360
|
writeManifest(gamePath, manifest);
|
|
37290
37361
|
};
|
|
37291
|
-
|
|
37362
|
+
const takeOut = (entry) => {
|
|
37292
37363
|
const decided = decisions.get(entry.path);
|
|
37293
37364
|
if (removeDeployed(lib, gamePath, manifest, entry, decided !== void 0, result.skipped)) {
|
|
37294
37365
|
entries.delete(key(entry.path));
|
|
@@ -37296,10 +37367,8 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
|
|
|
37296
37367
|
result.changes.push({ path: entry.path, mod: entry.mod, kind: "removed", ...entry.backup ? { backup: true } : {} });
|
|
37297
37368
|
}
|
|
37298
37369
|
manifest.files = [...entries.values()];
|
|
37299
|
-
|
|
37300
|
-
|
|
37301
|
-
if (p.stale.length) cleanDirs(gamePath, manifest);
|
|
37302
|
-
for (const d of p.add) {
|
|
37370
|
+
};
|
|
37371
|
+
const putIn = (d) => {
|
|
37303
37372
|
const target = gameFile(gamePath, d.rel);
|
|
37304
37373
|
const existing = entries.get(key(d.rel));
|
|
37305
37374
|
let backup = existing?.backup ?? false;
|
|
@@ -37307,7 +37376,7 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
|
|
|
37307
37376
|
const state = stillOurs(existing, manifest.method, gamePath);
|
|
37308
37377
|
if (state === "changed" && !decisions.has(d.rel)) {
|
|
37309
37378
|
result.skipped.push(d.rel);
|
|
37310
|
-
|
|
37379
|
+
return;
|
|
37311
37380
|
}
|
|
37312
37381
|
fs4.rmSync(target, { force: true });
|
|
37313
37382
|
} else if (fs4.existsSync(target)) {
|
|
@@ -37333,10 +37402,29 @@ function deploy(lib, gamePath, decisions = /* @__PURE__ */ new Map(), progress,
|
|
|
37333
37402
|
manifest.files = [...entries.values()];
|
|
37334
37403
|
result.linked++;
|
|
37335
37404
|
result.changes.push({ path: d.rel, mod: entry.mod, kind: existing ? "replaced" : "added", ...backup && !existing ? { backup: true } : {} });
|
|
37405
|
+
};
|
|
37406
|
+
for (const entry of stale) {
|
|
37407
|
+
takeOut(entry);
|
|
37408
|
+
progress?.(++done, total);
|
|
37409
|
+
}
|
|
37410
|
+
if (stale.length) cleanDirs(gamePath, manifest);
|
|
37411
|
+
for (const d of add) {
|
|
37412
|
+
putIn(d);
|
|
37336
37413
|
progress?.(++done, total);
|
|
37337
37414
|
if (done % 200 === 0) save();
|
|
37338
37415
|
}
|
|
37339
|
-
|
|
37416
|
+
if (scope) {
|
|
37417
|
+
const content = modlistFor(lib, manifest.files);
|
|
37418
|
+
const e = entries.get(key(ModListPath));
|
|
37419
|
+
if (!content && e) {
|
|
37420
|
+
takeOut(e);
|
|
37421
|
+
cleanDirs(gamePath, manifest);
|
|
37422
|
+
} else if (content && (!e || generatedDiffers(gameFile(gamePath, ModListPath), content))) {
|
|
37423
|
+
putIn({ rel: ModListPath, mod: void 0, src: "", content });
|
|
37424
|
+
}
|
|
37425
|
+
}
|
|
37426
|
+
result.kept = (scope ? p.desired.filter((d) => scope.has(key(d.rel))).length : p.desired.length) - result.linked - result.skipped.length;
|
|
37427
|
+
if (scope) result.kept = Math.max(0, result.kept);
|
|
37340
37428
|
result.modsInGame = new Set(manifest.files.map((e) => e.mod).filter((m) => m !== Generated)).size;
|
|
37341
37429
|
save();
|
|
37342
37430
|
return result;
|
|
@@ -37620,11 +37708,48 @@ function classifyProject(projectFile) {
|
|
|
37620
37708
|
if (!fs7.existsSync(packed)) return { placements: [], kinds: [], unplaced: [], ignored: [] };
|
|
37621
37709
|
return classify(packed, projectName(projectFile));
|
|
37622
37710
|
}
|
|
37623
|
-
function
|
|
37711
|
+
function projectMod(lib, projectFile) {
|
|
37712
|
+
return lib.mods.find((m) => m.source.type === "project" && m.source.path?.toLowerCase() === projectFile.toLowerCase());
|
|
37713
|
+
}
|
|
37714
|
+
function sameNameMod(lib, projectFile) {
|
|
37715
|
+
if (projectMod(lib, projectFile)) return void 0;
|
|
37716
|
+
const name = baseName(projectName(projectFile));
|
|
37717
|
+
const match = name ? lib.mods.find((m) => baseName(m.name) === name || m.renamedFrom !== void 0 && baseName(m.renamedFrom) === name) : void 0;
|
|
37718
|
+
return match && familyOf(lib, match).active;
|
|
37719
|
+
}
|
|
37720
|
+
function devVersion(lib, family) {
|
|
37721
|
+
const taken = new Set(lib.mods.filter((m) => familyKey(m) === family).map((m) => m.version.toLowerCase()));
|
|
37722
|
+
if (!taken.has("dev")) return "dev";
|
|
37723
|
+
let n = 2;
|
|
37724
|
+
while (taken.has(`dev ${n}`)) n++;
|
|
37725
|
+
return `dev ${n}`;
|
|
37726
|
+
}
|
|
37727
|
+
function installProject(lib, projectFile, beside) {
|
|
37624
37728
|
const c = classifyProject(projectFile);
|
|
37625
37729
|
if (!c.placements.length) throw new Error(`${projectName(projectFile)} has no packed output yet. Pack the project first (packed/ next to the project file).`);
|
|
37626
|
-
|
|
37730
|
+
let existing = projectMod(lib, projectFile);
|
|
37731
|
+
if (!existing && beside?.how === "follow") {
|
|
37732
|
+
existing = beside.mod;
|
|
37733
|
+
existing.family = familyKey(existing);
|
|
37734
|
+
existing.source = { type: "project", path: projectFile };
|
|
37735
|
+
}
|
|
37627
37736
|
if (!existing) {
|
|
37737
|
+
if (beside?.how === "version") {
|
|
37738
|
+
const family = familyKey(beside.mod);
|
|
37739
|
+
const previous = familyOf(lib, beside.mod);
|
|
37740
|
+
const mod2 = addMod(lib, c.placements, { name: projectName(projectFile), version: devVersion(lib, family), source: { type: "project", path: projectFile } });
|
|
37741
|
+
mod2.family = family;
|
|
37742
|
+
takeFamilyName(lib, mod2, previous.mods);
|
|
37743
|
+
placeAbove(lib, mod2, previous.active);
|
|
37744
|
+
for (const m of previous.mods) {
|
|
37745
|
+
if (m.enabled) {
|
|
37746
|
+
m.enabled = false;
|
|
37747
|
+
saveMod(lib, m);
|
|
37748
|
+
}
|
|
37749
|
+
}
|
|
37750
|
+
saveMod(lib, mod2);
|
|
37751
|
+
return { mod: mod2, added: true, beside };
|
|
37752
|
+
}
|
|
37628
37753
|
const mod = addMod(lib, c.placements, { name: projectName(projectFile), version: "dev", source: { type: "project", path: projectFile } });
|
|
37629
37754
|
return { mod, added: true };
|
|
37630
37755
|
}
|
|
@@ -37638,9 +37763,10 @@ function installProject(lib, projectFile) {
|
|
|
37638
37763
|
for (const f of existing.files) if (!keep.has(f)) fs7.rmSync(sourcePath(lib, existing, f), { force: true });
|
|
37639
37764
|
existing.files = [...keep].sort();
|
|
37640
37765
|
existing.kinds = c.kinds;
|
|
37766
|
+
activateVersion(lib, existing);
|
|
37641
37767
|
existing.enabled = true;
|
|
37642
37768
|
saveMod(lib, existing);
|
|
37643
|
-
return { mod: existing, added: false };
|
|
37769
|
+
return { mod: existing, added: false, ...beside ? { beside } : {} };
|
|
37644
37770
|
}
|
|
37645
37771
|
|
|
37646
37772
|
// src/redmod.ts
|
|
@@ -37663,8 +37789,18 @@ function enabledRedmodPackages(lib) {
|
|
|
37663
37789
|
}
|
|
37664
37790
|
return names;
|
|
37665
37791
|
}
|
|
37666
|
-
|
|
37667
|
-
const
|
|
37792
|
+
function deployedRedmodPackages(lib, gamePath) {
|
|
37793
|
+
const inGame = new Set((readManifest(gamePath)?.files ?? []).map((e) => /^mods\/([^/]+)\//i.exec(e.path)?.[1]).filter((n) => !!n));
|
|
37794
|
+
const names = [];
|
|
37795
|
+
for (const mod of lib.mods) {
|
|
37796
|
+
for (const f of mod.files) {
|
|
37797
|
+
const m = /^mods\/([^/]+)\//i.exec(f);
|
|
37798
|
+
if (m && inGame.has(m[1]) && !names.includes(m[1])) names.push(m[1]);
|
|
37799
|
+
}
|
|
37800
|
+
}
|
|
37801
|
+
return names;
|
|
37802
|
+
}
|
|
37803
|
+
async function deployRedmod(lib, gamePath, log, packages = enabledRedmodPackages(lib)) {
|
|
37668
37804
|
const exe = redModExe(gamePath);
|
|
37669
37805
|
if (!packages.length) return { ran: false, packages, output: "no REDmod packages enabled" };
|
|
37670
37806
|
if (!fs8.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` };
|
|
@@ -37879,6 +38015,13 @@ function importFromVortex(lib, mods) {
|
|
|
37879
38015
|
}
|
|
37880
38016
|
|
|
37881
38017
|
// src/verbs.ts
|
|
38018
|
+
function lostProjectsText(lib, lost) {
|
|
38019
|
+
const lines = lost.flatMap((l) => {
|
|
38020
|
+
const mod = lib.mods.find((m) => m.id === l.mod);
|
|
38021
|
+
return mod?.source.type === "project" ? [`the game still has ${l.winner} for ${l.files} file(s), not the project ${l.mod} (move ${l.mod} up to let it win)`] : [];
|
|
38022
|
+
});
|
|
38023
|
+
return lines.length ? `; ${lines.join("; ")}` : "";
|
|
38024
|
+
}
|
|
37882
38025
|
var DefaultGame = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Cyberpunk 2077";
|
|
37883
38026
|
function openSession(opts) {
|
|
37884
38027
|
const libraryRoot = opts.library ?? defaultLibraryPath();
|
|
@@ -37973,12 +38116,20 @@ ${mod.files.join("\n")}`;
|
|
|
37973
38116
|
}
|
|
37974
38117
|
},
|
|
37975
38118
|
/** Adds a REDcode or WolvenKit project's packed output, or refreshes the mod that follows it, and turns it on. */
|
|
37976
|
-
|
|
38119
|
+
/**
|
|
38120
|
+
* `sameName` answers for a mod of the same name that does not follow this project: follow (it
|
|
38121
|
+
* follows this project from now on) or version (the project is another version of it, on in its
|
|
38122
|
+
* place). Without it, the project is a mod of its own and the text says what it sits beside.
|
|
38123
|
+
*/
|
|
38124
|
+
installProject(s, projectFile, sameName) {
|
|
37977
38125
|
if (!fs11.existsSync(projectFile)) throw new Error(`${projectFile} does not exist`);
|
|
37978
|
-
const
|
|
38126
|
+
const other = sameNameMod(s.lib, projectFile);
|
|
38127
|
+
const before = other && `${other.id}${other.source.type === "project" && other.source.path ? ` (${path11.dirname(other.source.path)})` : ""}`;
|
|
38128
|
+
const { mod, added, beside } = installProject(s.lib, projectFile, other && sameName ? { mod: other, how: sameName } : void 0);
|
|
38129
|
+
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)` : "";
|
|
37979
38130
|
return {
|
|
37980
|
-
data: { id: mod.id, name: mod.name, version: mod.version, kinds: mod.kinds, files: mod.files.length, added },
|
|
37981
|
-
text: `${added ? "added" : "refreshed"} ${mod.id} from its project (${mod.files.length} files, ${mod.kinds.join(", ")}); it is on, deploy to put it in the game`
|
|
38131
|
+
data: { id: mod.id, name: mod.name, version: mod.version, kinds: mod.kinds, files: mod.files.length, added, sameName: other?.id, beside: beside?.how },
|
|
38132
|
+
text: `${added ? "added" : "refreshed"} ${mod.id} from its project (${mod.files.length} files, ${mod.kinds.join(", ")}); it is on, deploy to put it in the game${note}`
|
|
37982
38133
|
};
|
|
37983
38134
|
},
|
|
37984
38135
|
turn(s, id, on) {
|
|
@@ -37992,22 +38143,37 @@ ${mod.files.join("\n")}`;
|
|
|
37992
38143
|
moveMod(s.lib, mod, direction === "up" ? -1 : 1);
|
|
37993
38144
|
return { data: { order: s.lib.info.order }, text: s.lib.info.order.join("\n") };
|
|
37994
38145
|
},
|
|
38146
|
+
/** A new name in EDGEdeck for a mod and all its versions; an empty name puts the first name back. */
|
|
38147
|
+
rename(s, id, name) {
|
|
38148
|
+
const mod = find(s, id);
|
|
38149
|
+
const before = familyOf(s.lib, mod).name;
|
|
38150
|
+
const fam = renameFamily(s.lib, mod, name);
|
|
38151
|
+
return {
|
|
38152
|
+
data: { id: mod.id, name: fam.name, versions: fam.mods.map((m) => m.id) },
|
|
38153
|
+
text: `${before} is now ${fam.name}${fam.mods.length > 1 ? ` (${fam.mods.length} versions)` : ""}; its files and the game do not change`
|
|
38154
|
+
};
|
|
38155
|
+
},
|
|
37995
38156
|
remove(s, id) {
|
|
37996
38157
|
const mod = find(s, id);
|
|
37997
38158
|
removeMod(s.lib, mod);
|
|
37998
38159
|
return { data: { removed: mod.id }, text: `removed ${mod.id} from the library; its deployed files leave on the next deploy` };
|
|
37999
38160
|
},
|
|
38000
|
-
/**
|
|
38001
|
-
|
|
38161
|
+
/**
|
|
38162
|
+
* Deploys. A file changed outside EDGEdeck is skipped unless named in keep (import it) or restore
|
|
38163
|
+
* (overwrite it). With `only` (a mod id), deploys that mod's changes and leaves the rest waiting.
|
|
38164
|
+
*/
|
|
38165
|
+
async deploy(s, keep = [], restore = [], only) {
|
|
38002
38166
|
gameReady(s);
|
|
38003
38167
|
if (gameRunning()) throw new Error("Cyberpunk 2077 is running; it keeps mod files open. Quit the game, then deploy");
|
|
38168
|
+
if (only) find(s, only);
|
|
38004
38169
|
const decisions = /* @__PURE__ */ new Map();
|
|
38005
38170
|
for (const p of keep) decisions.set(p, "keep");
|
|
38006
38171
|
for (const p of restore) decisions.set(p, "restore");
|
|
38007
|
-
const result = deploy(s.lib, s.game, decisions, void 0, await gameVersion(s.game));
|
|
38008
|
-
const
|
|
38172
|
+
const result = deploy(s.lib, s.game, decisions, void 0, await gameVersion(s.game), only);
|
|
38173
|
+
const packages = only ? result.changes.some((c) => c.path.toLowerCase().startsWith("mods/")) ? deployedRedmodPackages(s.lib, s.game) : [] : enabledRedmodPackages(s.lib);
|
|
38174
|
+
const redmod = packages.length ? await deployRedmod(s.lib, s.game, void 0, packages) : void 0;
|
|
38009
38175
|
const report = writeDeployReport(s.lib, s.game, result);
|
|
38010
|
-
const text = `deployed (${result.method}): ${result.linked} added for ${modsTouched(result).length} mod(s), ${result.removed} removed, ${result.kept} unchanged; ${result.modsInGame} mod(s) in the game; report ${report}${result.skipped.length ? `; skipped (changed in game folder, answer with keep or restore): ${result.skipped.join(", ")}` : ""}${redmod ? `; REDmod: ${redmod.ran ? `${redmod.packages.length} prepared` : redmod.output}` : ""}`;
|
|
38176
|
+
const text = `deployed${only ? ` ${only} only` : ""} (${result.method}): ${result.linked} added for ${modsTouched(result).length} mod(s), ${result.removed} removed, ${result.kept} unchanged; ${result.modsInGame} mod(s) in the game; report ${report}${result.skipped.length ? `; skipped (changed in game folder, answer with keep or restore): ${result.skipped.join(", ")}` : ""}${redmod ? `; REDmod: ${redmod.ran ? `${redmod.packages.length} prepared` : redmod.output}` : ""}${result.waiting ? `; ${result.waiting} file(s) of other mods' changes still waiting` : ""}${lostProjectsText(s.lib, result.lost)}`;
|
|
38011
38177
|
return { data: { ...result, redmod, report }, text };
|
|
38012
38178
|
},
|
|
38013
38179
|
undo(s) {
|
|
@@ -38093,6 +38259,7 @@ ${JSON.stringify(v.data, null, 2)}` }] };
|
|
|
38093
38259
|
};
|
|
38094
38260
|
}
|
|
38095
38261
|
var session = () => openSession(options);
|
|
38262
|
+
var AskFirst = { "anthropic/requiresUserInteraction": true };
|
|
38096
38263
|
var server = new McpServer({ name: "edgedeck", version: version2 }, { instructions: Instructions });
|
|
38097
38264
|
server.registerTool(
|
|
38098
38265
|
"edgedeck_list",
|
|
@@ -38127,10 +38294,13 @@ server.registerTool(
|
|
|
38127
38294
|
"edgedeck_install_project",
|
|
38128
38295
|
{
|
|
38129
38296
|
title: "Install a project",
|
|
38130
|
-
description: "Takes a REDcode or WolvenKit project's packed/ folder into the library: adds it as version dev the first time, and on later calls replaces the files of the mod that follows the project. The mod is turned on; deploy to put it in the game. Pack the project first.",
|
|
38131
|
-
inputSchema: {
|
|
38297
|
+
description: "Takes a REDcode or WolvenKit project's packed/ folder into the library: adds it as version dev the first time, and on later calls replaces the files of the mod that follows the project. The mod is turned on; deploy to put it in the game. Pack the project first. When the library has a mod of the same name that does not follow this project (another project folder, or a download), the result says so; ask the player and call again with sameName.",
|
|
38298
|
+
inputSchema: {
|
|
38299
|
+
project: external_exports.string().describe("Full path of the .cpmodproj file"),
|
|
38300
|
+
sameName: external_exports.enum(["follow", "version"]).optional().describe("For a mod of the same name: follow = it follows this project from now on (no second mod); version = the project becomes another version of it, on in its place")
|
|
38301
|
+
}
|
|
38132
38302
|
},
|
|
38133
|
-
({ project }) => run4(() => verbs.installProject(session(), project))()
|
|
38303
|
+
({ project, sameName }) => run4(() => verbs.installProject(session(), project, sameName))()
|
|
38134
38304
|
);
|
|
38135
38305
|
server.registerTool(
|
|
38136
38306
|
"edgedeck_turn",
|
|
@@ -38151,13 +38321,23 @@ server.registerTool(
|
|
|
38151
38321
|
},
|
|
38152
38322
|
({ id, direction }) => run4(() => verbs.move(session(), id, direction))()
|
|
38153
38323
|
);
|
|
38324
|
+
server.registerTool(
|
|
38325
|
+
"edgedeck_rename",
|
|
38326
|
+
{
|
|
38327
|
+
title: "Rename a mod",
|
|
38328
|
+
description: "Gives a mod a new name in EDGEdeck, for all its versions; its files and the game do not change. New versions of it take the name too. An empty name puts the first name back.",
|
|
38329
|
+
inputSchema: { id: external_exports.string(), name: external_exports.string().describe("The new name, or empty for the first one") }
|
|
38330
|
+
},
|
|
38331
|
+
({ id, name }) => run4(() => verbs.rename(session(), id, name))()
|
|
38332
|
+
);
|
|
38154
38333
|
server.registerTool(
|
|
38155
38334
|
"edgedeck_remove",
|
|
38156
38335
|
{
|
|
38157
38336
|
title: "Remove a mod",
|
|
38158
38337
|
description: "Deletes a mod from the library. Its deployed files leave the game on the next deploy. The kept download, if any, stays.",
|
|
38159
38338
|
inputSchema: { id: external_exports.string() },
|
|
38160
|
-
annotations: { destructiveHint: true }
|
|
38339
|
+
annotations: { destructiveHint: true },
|
|
38340
|
+
_meta: AskFirst
|
|
38161
38341
|
},
|
|
38162
38342
|
({ id }) => run4(() => verbs.remove(session(), id))()
|
|
38163
38343
|
);
|
|
@@ -38175,17 +38355,23 @@ server.registerTool(
|
|
|
38175
38355
|
{
|
|
38176
38356
|
title: "Deploy",
|
|
38177
38357
|
description: "Hardlinks (or copies, on another drive) the files of every mod that is on into the game folder, writes the archive load order, prepares REDmod packages and records a manifest. A file changed in the game folder since the last deploy is skipped and named; call again with keep (import the changed file into the library) or restore (put the mod's file back). Refuses while Vortex manages the game folder.",
|
|
38178
|
-
inputSchema: {
|
|
38179
|
-
|
|
38358
|
+
inputSchema: {
|
|
38359
|
+
keep: external_exports.array(external_exports.string()).optional().describe("Game-folder paths whose changed file to keep"),
|
|
38360
|
+
restore: external_exports.array(external_exports.string()).optional().describe("Game-folder paths to overwrite with the mod's file"),
|
|
38361
|
+
mod: external_exports.string().optional().describe("A mod id: deploy only that mod's changes and leave other mods' changes waiting")
|
|
38362
|
+
},
|
|
38363
|
+
annotations: { destructiveHint: true },
|
|
38364
|
+
_meta: AskFirst
|
|
38180
38365
|
},
|
|
38181
|
-
({ keep, restore }) => run4(() => verbs.deploy(session(), keep, restore))()
|
|
38366
|
+
({ keep, restore, mod }) => run4(() => verbs.deploy(session(), keep, restore, mod))()
|
|
38182
38367
|
);
|
|
38183
38368
|
server.registerTool(
|
|
38184
38369
|
"edgedeck_undo",
|
|
38185
38370
|
{
|
|
38186
38371
|
title: "Undo deploy",
|
|
38187
38372
|
description: "Removes every file EDGEdeck deployed, and nothing else, and puts backed-up files back.",
|
|
38188
|
-
annotations: { destructiveHint: true }
|
|
38373
|
+
annotations: { destructiveHint: true },
|
|
38374
|
+
_meta: AskFirst
|
|
38189
38375
|
},
|
|
38190
38376
|
run4(() => verbs.undo(session()))
|
|
38191
38377
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edgemodders/edgedeck-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.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",
|