@modwrench/workbench 0.0.1

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.
Files changed (121) hide show
  1. package/data/conflicts/README.md +39 -0
  2. package/data/conflicts/fallout4.json +1 -0
  3. package/data/conflicts/lethalcompany.json +1 -0
  4. package/data/conflicts/skyrimspecialedition.json +1 -0
  5. package/dist/conflicts/community.d.ts +3 -0
  6. package/dist/conflicts/community.d.ts.map +1 -0
  7. package/dist/conflicts/community.js +96 -0
  8. package/dist/conflicts/community.js.map +1 -0
  9. package/dist/conflicts/index.d.ts +6 -0
  10. package/dist/conflicts/index.d.ts.map +1 -0
  11. package/dist/conflicts/index.js +114 -0
  12. package/dist/conflicts/index.js.map +1 -0
  13. package/dist/conflicts/loot.d.ts +19 -0
  14. package/dist/conflicts/loot.d.ts.map +1 -0
  15. package/dist/conflicts/loot.js +148 -0
  16. package/dist/conflicts/loot.js.map +1 -0
  17. package/dist/conflicts/types.d.ts +31 -0
  18. package/dist/conflicts/types.d.ts.map +1 -0
  19. package/dist/conflicts/types.js +6 -0
  20. package/dist/conflicts/types.js.map +1 -0
  21. package/dist/crashlog/bepinex.d.ts +3 -0
  22. package/dist/crashlog/bepinex.d.ts.map +1 -0
  23. package/dist/crashlog/bepinex.js +105 -0
  24. package/dist/crashlog/bepinex.js.map +1 -0
  25. package/dist/crashlog/crashlogger-sse.d.ts +3 -0
  26. package/dist/crashlog/crashlogger-sse.d.ts.map +1 -0
  27. package/dist/crashlog/crashlogger-sse.js +226 -0
  28. package/dist/crashlog/crashlogger-sse.js.map +1 -0
  29. package/dist/crashlog/detect.d.ts +3 -0
  30. package/dist/crashlog/detect.d.ts.map +1 -0
  31. package/dist/crashlog/detect.js +44 -0
  32. package/dist/crashlog/detect.js.map +1 -0
  33. package/dist/crashlog/index.d.ts +15 -0
  34. package/dist/crashlog/index.d.ts.map +1 -0
  35. package/dist/crashlog/index.js +65 -0
  36. package/dist/crashlog/index.js.map +1 -0
  37. package/dist/crashlog/minecraft.d.ts +3 -0
  38. package/dist/crashlog/minecraft.d.ts.map +1 -0
  39. package/dist/crashlog/minecraft.js +145 -0
  40. package/dist/crashlog/minecraft.js.map +1 -0
  41. package/dist/crashlog/netscriptframework.d.ts +3 -0
  42. package/dist/crashlog/netscriptframework.d.ts.map +1 -0
  43. package/dist/crashlog/netscriptframework.js +109 -0
  44. package/dist/crashlog/netscriptframework.js.map +1 -0
  45. package/dist/crashlog/types.d.ts +35 -0
  46. package/dist/crashlog/types.d.ts.map +1 -0
  47. package/dist/crashlog/types.js +9 -0
  48. package/dist/crashlog/types.js.map +1 -0
  49. package/dist/detect/environment.d.ts +35 -0
  50. package/dist/detect/environment.d.ts.map +1 -0
  51. package/dist/detect/environment.js +65 -0
  52. package/dist/detect/environment.js.map +1 -0
  53. package/dist/detect/games.d.ts +21 -0
  54. package/dist/detect/games.d.ts.map +1 -0
  55. package/dist/detect/games.js +159 -0
  56. package/dist/detect/games.js.map +1 -0
  57. package/dist/detect/loader.d.ts +12 -0
  58. package/dist/detect/loader.d.ts.map +1 -0
  59. package/dist/detect/loader.js +20 -0
  60. package/dist/detect/loader.js.map +1 -0
  61. package/dist/detect/manager.d.ts +26 -0
  62. package/dist/detect/manager.d.ts.map +1 -0
  63. package/dist/detect/manager.js +157 -0
  64. package/dist/detect/manager.js.map +1 -0
  65. package/dist/detect/os.d.ts +21 -0
  66. package/dist/detect/os.d.ts.map +1 -0
  67. package/dist/detect/os.js +58 -0
  68. package/dist/detect/os.js.map +1 -0
  69. package/dist/detect/steam.d.ts +32 -0
  70. package/dist/detect/steam.d.ts.map +1 -0
  71. package/dist/detect/steam.js +155 -0
  72. package/dist/detect/steam.js.map +1 -0
  73. package/dist/detect/vdf.d.ts +12 -0
  74. package/dist/detect/vdf.d.ts.map +1 -0
  75. package/dist/detect/vdf.js +112 -0
  76. package/dist/detect/vdf.js.map +1 -0
  77. package/dist/index.d.ts +3 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +27 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/loadorder/index.d.ts +20 -0
  82. package/dist/loadorder/index.d.ts.map +1 -0
  83. package/dist/loadorder/index.js +76 -0
  84. package/dist/loadorder/index.js.map +1 -0
  85. package/dist/loadorder/mo2.d.ts +21 -0
  86. package/dist/loadorder/mo2.d.ts.map +1 -0
  87. package/dist/loadorder/mo2.js +201 -0
  88. package/dist/loadorder/mo2.js.map +1 -0
  89. package/dist/loadorder/r2modman.d.ts +12 -0
  90. package/dist/loadorder/r2modman.d.ts.map +1 -0
  91. package/dist/loadorder/r2modman.js +115 -0
  92. package/dist/loadorder/r2modman.js.map +1 -0
  93. package/dist/loadorder/types.d.ts +30 -0
  94. package/dist/loadorder/types.d.ts.map +1 -0
  95. package/dist/loadorder/types.js +5 -0
  96. package/dist/loadorder/types.js.map +1 -0
  97. package/dist/loadorder/vortex.d.ts +8 -0
  98. package/dist/loadorder/vortex.d.ts.map +1 -0
  99. package/dist/loadorder/vortex.js +77 -0
  100. package/dist/loadorder/vortex.js.map +1 -0
  101. package/dist/metadata/clients.d.ts +11 -0
  102. package/dist/metadata/clients.d.ts.map +1 -0
  103. package/dist/metadata/clients.js +90 -0
  104. package/dist/metadata/clients.js.map +1 -0
  105. package/dist/metadata/index.d.ts +3 -0
  106. package/dist/metadata/index.d.ts.map +1 -0
  107. package/dist/metadata/index.js +149 -0
  108. package/dist/metadata/index.js.map +1 -0
  109. package/dist/metadata/normalize.d.ts +43 -0
  110. package/dist/metadata/normalize.d.ts.map +1 -0
  111. package/dist/metadata/normalize.js +88 -0
  112. package/dist/metadata/normalize.js.map +1 -0
  113. package/dist/metadata/types.d.ts +57 -0
  114. package/dist/metadata/types.d.ts.map +1 -0
  115. package/dist/metadata/types.js +9 -0
  116. package/dist/metadata/types.js.map +1 -0
  117. package/dist/register.d.ts +13 -0
  118. package/dist/register.d.ts.map +1 -0
  119. package/dist/register.js +209 -0
  120. package/dist/register.js.map +1 -0
  121. package/package.json +58 -0
@@ -0,0 +1,76 @@
1
+ import { findGameById } from "../detect/games.js";
2
+ import { detectInstalledManagers, inferManagerForGame, } from "../detect/manager.js";
3
+ import { readMo2LoadOrder } from "./mo2.js";
4
+ import { readR2modmanLoadOrder } from "./r2modman.js";
5
+ import { readVortexLoadOrder } from "./vortex.js";
6
+ export function readLoadOrder(input) {
7
+ const game = findGameById(input.gameId);
8
+ if (!game) {
9
+ return {
10
+ ok: false,
11
+ reason: `Unknown gameId "${input.gameId}". Known IDs are listed in ` +
12
+ "@modwrench/workbench's KNOWN_GAMES catalogue. Use mw_detect_environment " +
13
+ "to see detected games on this machine.",
14
+ attemptedManagers: [],
15
+ };
16
+ }
17
+ // Build the list of managers to try, in priority order.
18
+ let order;
19
+ if (input.modManager && input.modManager !== "auto") {
20
+ order = [input.modManager];
21
+ }
22
+ else {
23
+ const managers = detectInstalledManagers();
24
+ const inferred = inferManagerForGame(game, managers);
25
+ // Fall back across the rest in family-sensible order.
26
+ const familyOrder = game.family === "bethesda"
27
+ ? ["mo2", "vortex"]
28
+ : game.family === "unity-coop"
29
+ ? ["r2modman", "thunderstore-mm"]
30
+ : [];
31
+ const seen = new Set();
32
+ order = [];
33
+ for (const m of [inferred, ...familyOrder]) {
34
+ if (m && !seen.has(m)) {
35
+ seen.add(m);
36
+ order.push(m);
37
+ }
38
+ }
39
+ }
40
+ const attempted = [];
41
+ for (const manager of order) {
42
+ attempted.push(manager);
43
+ let result = null;
44
+ if (manager === "mo2") {
45
+ result = readMo2LoadOrder(game, {
46
+ instancePath: input.instancePath,
47
+ profileName: input.profileName,
48
+ });
49
+ }
50
+ else if (manager === "r2modman") {
51
+ result = readR2modmanLoadOrder(game, {
52
+ profileName: input.profileName,
53
+ });
54
+ }
55
+ else if (manager === "vortex") {
56
+ result = readVortexLoadOrder(game);
57
+ }
58
+ if (result)
59
+ return { ok: true, ...result };
60
+ }
61
+ return {
62
+ ok: false,
63
+ game,
64
+ attemptedManagers: attempted,
65
+ reason: attempted.length === 0
66
+ ? "No mod manager could be inferred for this game's family. " +
67
+ "Specify modManager explicitly or install one of: " +
68
+ (game.family === "bethesda"
69
+ ? "MO2, Vortex"
70
+ : game.family === "unity-coop"
71
+ ? "r2modman, Thunderstore Mod Manager"
72
+ : "a supported manager")
73
+ : `Tried ${attempted.join(", ")} but no readable state was found for ${game.displayName}.`,
74
+ };
75
+ }
76
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/loadorder/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { GameDef } from "../detect/games.js";
2
+ import type { LoadOrderResult } from "./types.js";
3
+ /**
4
+ * Find the MO2 instance directory for a given game. Walks the default
5
+ * instances root, reads each ModOrganizer.ini, and matches gameName against
6
+ * the game's known MO2 name aliases.
7
+ *
8
+ * Returns null if MO2 isn't installed or no matching instance is found.
9
+ */
10
+ export declare function findMo2InstanceForGame(game: GameDef, explicitPath?: string): string | null;
11
+ /**
12
+ * Read the load order for a game's active MO2 instance + profile. For
13
+ * Bethesda games, the primary mods[] entries are .esp/.esl/.esm plugins from
14
+ * plugins.txt (in load order). The mod-folder view from modlist.txt is
15
+ * returned as modFolders[] so the LLM can map plugin → mod when needed.
16
+ */
17
+ export declare function readMo2LoadOrder(game: GameDef, opts: {
18
+ instancePath?: string;
19
+ profileName?: string;
20
+ }): LoadOrderResult | null;
21
+ //# sourceMappingURL=mo2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mo2.d.ts","sourceRoot":"","sources":["../../src/loadorder/mo2.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,YAAY,CAAC;AAgEhE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,OAAO,EACb,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,GAAG,IAAI,CA4Bf;AAwCD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD,eAAe,GAAG,IAAI,CAmDxB"}
@@ -0,0 +1,201 @@
1
+ import { readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { pathExists } from "../detect/os.js";
5
+ // ─── Minimal INI parser ───────────────────────────────────────────────────────
6
+ // MO2 stores instance metadata in ModOrganizer.ini. The file is small and
7
+ // well-formed Qt-style INI; we only need a few keys, so a tiny parser keeps us
8
+ // dep-free.
9
+ function parseIni(text) {
10
+ const out = {};
11
+ let section = "_default";
12
+ out[section] = {};
13
+ for (const rawLine of text.split(/\r?\n/)) {
14
+ const line = rawLine.trim();
15
+ if (!line || line.startsWith(";") || line.startsWith("#"))
16
+ continue;
17
+ if (line.startsWith("[") && line.endsWith("]")) {
18
+ section = line.slice(1, -1);
19
+ out[section] ??= {};
20
+ continue;
21
+ }
22
+ const eq = line.indexOf("=");
23
+ if (eq === -1)
24
+ continue;
25
+ const key = line.slice(0, eq).trim();
26
+ let value = line.slice(eq + 1).trim();
27
+ // Qt's @ByteArray(...) wrapper appears around some values. Unwrap it.
28
+ const qtMatch = value.match(/^@ByteArray\((.*)\)$/);
29
+ if (qtMatch)
30
+ value = qtMatch[1] ?? value;
31
+ (out[section] ??= {})[key] = value;
32
+ }
33
+ return out;
34
+ }
35
+ // ─── Instance discovery ───────────────────────────────────────────────────────
36
+ function defaultMo2InstancesRoot() {
37
+ if (process.platform !== "win32")
38
+ return null;
39
+ const local = process.env.LOCALAPPDATA;
40
+ if (!local)
41
+ return null;
42
+ return join(local, "ModOrganizer");
43
+ }
44
+ function readInstanceGameName(instancePath) {
45
+ const iniPath = join(instancePath, "ModOrganizer.ini");
46
+ if (!pathExists(iniPath))
47
+ return null;
48
+ try {
49
+ const ini = parseIni(readFileSync(iniPath, "utf8"));
50
+ const general = ini["General"] ?? {};
51
+ return general["gameName"] ?? null;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ function readInstanceActiveProfile(instancePath) {
58
+ const iniPath = join(instancePath, "ModOrganizer.ini");
59
+ if (!pathExists(iniPath))
60
+ return null;
61
+ try {
62
+ const ini = parseIni(readFileSync(iniPath, "utf8"));
63
+ const general = ini["General"] ?? {};
64
+ return general["selected_profile"] ?? general["profile"] ?? null;
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ /**
71
+ * Find the MO2 instance directory for a given game. Walks the default
72
+ * instances root, reads each ModOrganizer.ini, and matches gameName against
73
+ * the game's known MO2 name aliases.
74
+ *
75
+ * Returns null if MO2 isn't installed or no matching instance is found.
76
+ */
77
+ export function findMo2InstanceForGame(game, explicitPath) {
78
+ if (explicitPath) {
79
+ return pathExists(join(explicitPath, "ModOrganizer.ini"))
80
+ ? explicitPath
81
+ : null;
82
+ }
83
+ const root = defaultMo2InstancesRoot();
84
+ if (!root || !pathExists(root))
85
+ return null;
86
+ const aliases = (game.mo2GameNames ?? []).map((n) => n.toLowerCase());
87
+ if (aliases.length === 0)
88
+ return null;
89
+ let candidates;
90
+ try {
91
+ candidates = readdirSync(root).map((name) => join(root, name));
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ for (const candidate of candidates) {
97
+ try {
98
+ if (!statSync(candidate).isDirectory())
99
+ continue;
100
+ }
101
+ catch {
102
+ continue;
103
+ }
104
+ const gameName = readInstanceGameName(candidate);
105
+ if (!gameName)
106
+ continue;
107
+ if (aliases.includes(gameName.toLowerCase()))
108
+ return candidate;
109
+ }
110
+ return null;
111
+ }
112
+ function parseModlist(text) {
113
+ // modlist.txt is LIFO: top line is highest-priority. We reverse so index 0
114
+ // is the lowest-priority mod and indices grow with priority — easier to
115
+ // reason about as "load order index."
116
+ const lines = text.split(/\r?\n/);
117
+ const entries = [];
118
+ for (const raw of lines) {
119
+ const line = raw.trim();
120
+ if (!line || line.startsWith("#"))
121
+ continue;
122
+ const prefix = line[0];
123
+ const name = line.slice(1);
124
+ // Separators (---xxx_separator) and backup markers can be skipped — they
125
+ // aren't real mods. MO2 marks them with name ending "_separator".
126
+ if (name.endsWith("_separator"))
127
+ continue;
128
+ if (prefix === "+")
129
+ entries.push({ name, enabled: true });
130
+ else if (prefix === "-")
131
+ entries.push({ name, enabled: false });
132
+ // Other prefixes (*) are markers we don't care about.
133
+ }
134
+ return entries.reverse();
135
+ }
136
+ function parsePlugins(text) {
137
+ const out = [];
138
+ for (const raw of text.split(/\r?\n/)) {
139
+ const line = raw.trim();
140
+ if (!line || line.startsWith("#"))
141
+ continue;
142
+ if (line.startsWith("*"))
143
+ out.push({ name: line.slice(1), enabled: true });
144
+ else
145
+ out.push({ name: line, enabled: false });
146
+ }
147
+ return out;
148
+ }
149
+ /**
150
+ * Read the load order for a game's active MO2 instance + profile. For
151
+ * Bethesda games, the primary mods[] entries are .esp/.esl/.esm plugins from
152
+ * plugins.txt (in load order). The mod-folder view from modlist.txt is
153
+ * returned as modFolders[] so the LLM can map plugin → mod when needed.
154
+ */
155
+ export function readMo2LoadOrder(game, opts) {
156
+ const instancePath = findMo2InstanceForGame(game, opts.instancePath);
157
+ if (!instancePath)
158
+ return null;
159
+ const profile = opts.profileName ?? readInstanceActiveProfile(instancePath) ?? "Default";
160
+ const profileDir = join(instancePath, "profiles", profile);
161
+ if (!pathExists(profileDir))
162
+ return null;
163
+ const modlistPath = join(profileDir, "modlist.txt");
164
+ const pluginsPath = join(profileDir, "plugins.txt");
165
+ let modFolders = [];
166
+ if (pathExists(modlistPath)) {
167
+ const entries = parseModlist(readFileSync(modlistPath, "utf8"));
168
+ modFolders = entries.map((e, i) => ({
169
+ name: e.name,
170
+ enabled: e.enabled,
171
+ modlistIndex: i,
172
+ }));
173
+ }
174
+ const mods = [];
175
+ if (pathExists(pluginsPath)) {
176
+ const plugins = parsePlugins(readFileSync(pluginsPath, "utf8"));
177
+ for (let i = 0; i < plugins.length; i++) {
178
+ const p = plugins[i];
179
+ mods.push({
180
+ name: p.name,
181
+ enabled: p.enabled,
182
+ loadOrderIndex: i,
183
+ pluginFile: p.name,
184
+ });
185
+ }
186
+ }
187
+ const totalCount = mods.length || modFolders.length;
188
+ const enabledCount = mods.length > 0
189
+ ? mods.filter((m) => m.enabled).length
190
+ : modFolders.filter((f) => f.enabled).length;
191
+ return {
192
+ modManager: "mo2",
193
+ profile,
194
+ sourcePath: profileDir,
195
+ mods,
196
+ modFolders,
197
+ enabledCount,
198
+ totalCount,
199
+ };
200
+ }
201
+ //# sourceMappingURL=mo2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mo2.js","sourceRoot":"","sources":["../../src/loadorder/mo2.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { GameDef } from "../detect/games.js";
2
+ import type { LoadOrderResult } from "./types.js";
3
+ /**
4
+ * Read the load order for a game's r2modman profile. r2modman profiles live
5
+ * at <root>/<gameFolder>/profiles/<profile>/mods.yml. Active profile isn't
6
+ * recorded in disk state (it's a UI session value), so we default to the
7
+ * first profile alphabetically (typically "Default") unless one is named.
8
+ */
9
+ export declare function readR2modmanLoadOrder(game: GameDef, opts: {
10
+ profileName?: string;
11
+ }): LoadOrderResult | null;
12
+ //# sourceMappingURL=r2modman.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2modman.d.ts","sourceRoot":"","sources":["../../src/loadorder/r2modman.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,YAAY,CAAC;AA+DhE;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,OAAO,EACb,IAAI,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,eAAe,GAAG,IAAI,CA+DxB"}
@@ -0,0 +1,115 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { load as yamlLoad } from "js-yaml";
5
+ import { pathExists } from "../detect/os.js";
6
+ function r2modmanRootCandidates() {
7
+ const home = homedir();
8
+ switch (process.platform) {
9
+ case "win32":
10
+ return [
11
+ process.env.APPDATA
12
+ ? join(process.env.APPDATA, "r2modmanPlus-local")
13
+ : "",
14
+ ].filter(Boolean);
15
+ case "darwin":
16
+ return [
17
+ join(home, "Library", "Application Support", "r2modmanPlus-local"),
18
+ ];
19
+ default:
20
+ return [
21
+ join(home, ".config", "r2modmanPlus-local"),
22
+ join(home, ".var", "app", "com.kalindudc.r2modmanPlus", "config", "r2modmanPlus-local"),
23
+ ];
24
+ }
25
+ }
26
+ function findR2modmanRoot() {
27
+ for (const candidate of r2modmanRootCandidates()) {
28
+ if (pathExists(candidate))
29
+ return candidate;
30
+ }
31
+ return null;
32
+ }
33
+ function formatVersion(v) {
34
+ if (typeof v === "string")
35
+ return v;
36
+ if (v && typeof v === "object") {
37
+ const parts = [v.Major ?? 0, v.Minor ?? 0, v.Patch ?? 0];
38
+ return parts.join(".");
39
+ }
40
+ return undefined;
41
+ }
42
+ /**
43
+ * Read the load order for a game's r2modman profile. r2modman profiles live
44
+ * at <root>/<gameFolder>/profiles/<profile>/mods.yml. Active profile isn't
45
+ * recorded in disk state (it's a UI session value), so we default to the
46
+ * first profile alphabetically (typically "Default") unless one is named.
47
+ */
48
+ export function readR2modmanLoadOrder(game, opts) {
49
+ const folder = game.r2modmanFolder;
50
+ if (!folder)
51
+ return null;
52
+ const root = findR2modmanRoot();
53
+ if (!root)
54
+ return null;
55
+ const gameDir = join(root, folder);
56
+ const profilesDir = join(gameDir, "profiles");
57
+ if (!pathExists(profilesDir))
58
+ return null;
59
+ let profile = opts.profileName;
60
+ if (!profile) {
61
+ try {
62
+ const profiles = readdirSync(profilesDir).sort();
63
+ profile = profiles[0];
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ if (!profile)
70
+ return null;
71
+ const profileDir = join(profilesDir, profile);
72
+ const manifestPath = join(profileDir, "mods.yml");
73
+ if (!pathExists(manifestPath))
74
+ return null;
75
+ let entries = [];
76
+ try {
77
+ const parsed = yamlLoad(readFileSync(manifestPath, "utf8"));
78
+ if (Array.isArray(parsed))
79
+ entries = parsed;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ const mods = entries.map((e, i) => {
85
+ const author = e.AuthorName;
86
+ const displayName = e.DisplayName ?? e.Name ?? "(unnamed)";
87
+ // Thunderstore's canonical mod id is "Author-ModName". Reconstruct it
88
+ // from the manifest fields when possible — this is what downstream tools
89
+ // need to resolve attribution.
90
+ const sourceModId = author && e.Name ? `${author}-${e.Name}` : e.Name ?? undefined;
91
+ const mod = {
92
+ name: displayName,
93
+ enabled: e.Enabled ?? true,
94
+ loadOrderIndex: i,
95
+ sourcePlatform: "thunderstore",
96
+ };
97
+ const version = formatVersion(e.Version);
98
+ if (version)
99
+ mod.version = version;
100
+ if (author)
101
+ mod.author = author;
102
+ if (sourceModId)
103
+ mod.sourceModId = sourceModId;
104
+ return mod;
105
+ });
106
+ return {
107
+ modManager: "r2modman",
108
+ profile,
109
+ sourcePath: profileDir,
110
+ mods,
111
+ enabledCount: mods.filter((m) => m.enabled).length,
112
+ totalCount: mods.length,
113
+ };
114
+ }
115
+ //# sourceMappingURL=r2modman.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2modman.js","sourceRoot":"","sources":["../../src/loadorder/r2modman.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ export type SourcePlatform = "nexus" | "modio" | "thunderstore" | "curseforge" | "unknown";
2
+ export type LoadOrderMod = {
3
+ name: string;
4
+ enabled: boolean | null;
5
+ loadOrderIndex?: number;
6
+ pluginFile?: string;
7
+ version?: string;
8
+ sourcePlatform?: SourcePlatform;
9
+ sourceModId?: string;
10
+ author?: string;
11
+ installedAt?: string;
12
+ };
13
+ export type LoadOrderResult = {
14
+ modManager: "vortex" | "mo2" | "r2modman";
15
+ profile: string;
16
+ /** Absolute path of the manager-specific state directory we read from. */
17
+ sourcePath: string;
18
+ mods: LoadOrderMod[];
19
+ /** Bethesda mod-folder list (MO2 only). Null for Unity / Vortex. */
20
+ modFolders?: Array<{
21
+ name: string;
22
+ enabled: boolean;
23
+ modlistIndex: number;
24
+ }>;
25
+ enabledCount: number;
26
+ totalCount: number;
27
+ /** Honest about limitations — Vortex's LevelDB state can't be parsed yet. */
28
+ warning?: string;
29
+ };
30
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/loadorder/types.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,OAAO,GACP,cAAc,GACd,YAAY,GACZ,SAAS,CAAC;AAEd,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,QAAQ,GAAG,KAAK,GAAG,UAAU,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,oEAAoE;IACpE,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC"}
@@ -0,0 +1,5 @@
1
+ // Normalized cross-manager load-order shape. Each parser returns this so
2
+ // downstream tools (mw_check_known_conflicts, mw_query_mod_metadata, etc.)
3
+ // don't care whether the user is on Vortex / MO2 / r2modman.
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/loadorder/types.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,2EAA2E;AAC3E,6DAA6D"}
@@ -0,0 +1,8 @@
1
+ import type { GameDef } from "../detect/games.js";
2
+ import type { LoadOrderResult } from "./types.js";
3
+ /**
4
+ * Best-effort enumeration of installed mod folders for a game under Vortex.
5
+ * Returns null if Vortex isn't installed or the game has no Vortex data dir.
6
+ */
7
+ export declare function readVortexLoadOrder(game: GameDef): LoadOrderResult | null;
8
+ //# sourceMappingURL=vortex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vortex.d.ts","sourceRoot":"","sources":["../../src/loadorder/vortex.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,YAAY,CAAC;AAqBhE;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CAkDzE"}
@@ -0,0 +1,77 @@
1
+ import { readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { pathExists } from "../detect/os.js";
5
+ // Vortex stores its canonical state in a LevelDB store at
6
+ // %APPDATA%/Vortex/state.v2/ — not directly parseable without a LevelDB
7
+ // library (which would add ~5MB native deps). Until we add that path, this
8
+ // reader does a best-effort directory scan of the per-game mods folder:
9
+ //
10
+ // %APPDATA%/Vortex/<gameId>/mods/<mod-folder>/
11
+ //
12
+ // We return mod folder names with enabled=null and a warning so the LLM
13
+ // knows enable-state and load order are unavailable through this view.
14
+ function vortexRoot() {
15
+ if (process.platform === "win32") {
16
+ return process.env.APPDATA ? join(process.env.APPDATA, "Vortex") : null;
17
+ }
18
+ // Vortex on Linux is Wine-only and uncommon; check the standard config
19
+ // location as a courtesy.
20
+ return join(homedir(), ".config", "Vortex");
21
+ }
22
+ /**
23
+ * Best-effort enumeration of installed mod folders for a game under Vortex.
24
+ * Returns null if Vortex isn't installed or the game has no Vortex data dir.
25
+ */
26
+ export function readVortexLoadOrder(game) {
27
+ const root = vortexRoot();
28
+ if (!root || !pathExists(root))
29
+ return null;
30
+ const gameDir = join(root, game.gameId);
31
+ const modsDir = join(gameDir, "mods");
32
+ if (!pathExists(modsDir))
33
+ return null;
34
+ let entries;
35
+ try {
36
+ entries = readdirSync(modsDir);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ const mods = [];
42
+ for (const entry of entries) {
43
+ const fullPath = join(modsDir, entry);
44
+ try {
45
+ if (!statSync(fullPath).isDirectory())
46
+ continue;
47
+ }
48
+ catch {
49
+ continue;
50
+ }
51
+ // Vortex names mod folders like "ModName-12345-1-0-1700000000". The
52
+ // numeric run at the end is typically <nexus_mod_id>-<version>-<unixts>.
53
+ // We try a soft parse, but it's best-effort only.
54
+ const nexusIdMatch = entry.match(/-(\d{2,7})-/);
55
+ const mod = {
56
+ name: entry,
57
+ enabled: null,
58
+ };
59
+ if (nexusIdMatch) {
60
+ mod.sourcePlatform = "nexus";
61
+ mod.sourceModId = nexusIdMatch[1];
62
+ }
63
+ mods.push(mod);
64
+ }
65
+ return {
66
+ modManager: "vortex",
67
+ profile: "(unknown)",
68
+ sourcePath: modsDir,
69
+ mods,
70
+ enabledCount: 0,
71
+ totalCount: mods.length,
72
+ warning: "Vortex stores enable-state and load order in a LevelDB store (state.v2). " +
73
+ "ModWrench currently lists installed mod folders only — enable/disable " +
74
+ "state is not visible. For full state visibility on Bethesda games, use MO2.",
75
+ };
76
+ }
77
+ //# sourceMappingURL=vortex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vortex.js","sourceRoot":"","sources":["../../src/loadorder/vortex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,0DAA0D;AAC1D,wEAAwE;AACxE,2EAA2E;AAC3E,wEAAwE;AACxE,EAAE;AACF,iDAAiD;AACjD,EAAE;AACF,wEAAwE;AACxE,uEAAuE;AAEvE,SAAS,UAAU;IACjB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1E,CAAC;IACD,uEAAuE;IACvE,0BAA0B;IAC1B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtC,IAAI,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE;gBAAE,SAAS;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,oEAAoE;QACpE,yEAAyE;QACzE,kDAAkD;QAClD,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAChD,MAAM,GAAG,GAAiB;YACxB,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,IAAI;SACd,CAAC;QACF,IAAI,YAAY,EAAE,CAAC;YACjB,GAAG,CAAC,cAAc,GAAG,OAAO,CAAC;YAC7B,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IAED,OAAO;QACL,UAAU,EAAE,QAAQ;QACpB,OAAO,EAAE,WAAW;QACpB,UAAU,EAAE,OAAO;QACnB,IAAI;QACJ,YAAY,EAAE,CAAC;QACf,UAAU,EAAE,IAAI,CAAC,MAAM;QACvB,OAAO,EACL,2EAA2E;YAC3E,wEAAwE;YACxE,6EAA6E;KAChF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ export type NexusClient = {
2
+ baseUrl: string;
3
+ request<T>(path: string): Promise<T>;
4
+ };
5
+ export declare function tryCreateNexusClient(): NexusClient | null;
6
+ export type ModioClient = {
7
+ baseUrl: string;
8
+ request<T>(path: string, query?: Record<string, string | number | undefined>): Promise<T>;
9
+ };
10
+ export declare function tryCreateModioClient(): ModioClient | null;
11
+ //# sourceMappingURL=clients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../../src/metadata/clients.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACtC,CAAC;AAEF,wBAAgB,oBAAoB,IAAI,WAAW,GAAG,IAAI,CA8BzD;AAID,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,CAAC,EACP,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,GAClD,OAAO,CAAC,CAAC,CAAC,CAAC;CACf,CAAC;AAEF,wBAAgB,oBAAoB,IAAI,WAAW,GAAG,IAAI,CA8CzD"}