@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,157 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { pathExists } from "./os.js";
5
+ // Per-OS candidate locations for each manager's state directory. We scan and
6
+ // return the first that exists — none of these are required, and modders
7
+ // running unusual setups can still use the workbench tools that don't depend
8
+ // on manager state.
9
+ function vortexCandidates() {
10
+ const home = homedir();
11
+ if (process.platform === "win32") {
12
+ return [
13
+ process.env.APPDATA ? join(process.env.APPDATA, "Vortex") : "",
14
+ ].filter(Boolean);
15
+ }
16
+ // Vortex on Linux is unofficial / Wine-only. We check the standard Wine
17
+ // prefix layout as a best effort but don't expect to find much.
18
+ return [join(home, ".config", "Vortex")];
19
+ }
20
+ function mo2Candidates() {
21
+ if (process.platform !== "win32")
22
+ return [];
23
+ // Modern MO2 installs portably; the launcher lives under LocalAppData. The
24
+ // "instances" subdir holds per-game configs.
25
+ return [
26
+ process.env.LOCALAPPDATA
27
+ ? join(process.env.LOCALAPPDATA, "ModOrganizer")
28
+ : "",
29
+ ].filter(Boolean);
30
+ }
31
+ function r2modmanCandidates() {
32
+ const home = homedir();
33
+ switch (process.platform) {
34
+ case "win32":
35
+ return [
36
+ process.env.APPDATA
37
+ ? join(process.env.APPDATA, "r2modmanPlus-local")
38
+ : "",
39
+ ].filter(Boolean);
40
+ case "darwin":
41
+ return [
42
+ join(home, "Library", "Application Support", "r2modmanPlus-local"),
43
+ ];
44
+ default:
45
+ return [
46
+ join(home, ".config", "r2modmanPlus-local"),
47
+ // Flatpak install
48
+ join(home, ".var", "app", "com.kalindudc.r2modmanPlus", "config", "r2modmanPlus-local"),
49
+ ];
50
+ }
51
+ }
52
+ function curseforgeCandidates() {
53
+ if (process.platform !== "win32")
54
+ return [];
55
+ return [
56
+ process.env.LOCALAPPDATA
57
+ ? join(process.env.LOCALAPPDATA, "CurseForge")
58
+ : "",
59
+ ].filter(Boolean);
60
+ }
61
+ function findFirstExisting(paths) {
62
+ for (const p of paths) {
63
+ if (p && pathExists(p))
64
+ return p;
65
+ }
66
+ return null;
67
+ }
68
+ /**
69
+ * Detect every mod manager whose state directory is present on this system.
70
+ * Each manager is reported once, with the data path the workbench should read
71
+ * if it ever needs to interrogate state (load order, profiles, etc.).
72
+ *
73
+ * Note: presence ≠ active. A user may have Vortex installed but use MO2 daily.
74
+ * Per-game inference (inferManagerForGame) makes the best guess we can.
75
+ */
76
+ export function detectInstalledManagers() {
77
+ const out = [];
78
+ const vortex = findFirstExisting(vortexCandidates());
79
+ if (vortex) {
80
+ out.push({ name: "vortex", dataPath: vortex });
81
+ }
82
+ const mo2 = findFirstExisting(mo2Candidates());
83
+ if (mo2) {
84
+ // List instance subdirs as a clue for which games MO2 might manage.
85
+ const instances = [];
86
+ try {
87
+ for (const entry of readdirSync(mo2, { withFileTypes: true })) {
88
+ if (entry.isDirectory())
89
+ instances.push(entry.name);
90
+ }
91
+ }
92
+ catch {
93
+ // Ignore unreadable.
94
+ }
95
+ out.push({
96
+ name: "mo2",
97
+ dataPath: mo2,
98
+ managedGameIds: instances,
99
+ });
100
+ }
101
+ const r2 = findFirstExisting(r2modmanCandidates());
102
+ if (r2) {
103
+ const games = [];
104
+ try {
105
+ for (const entry of readdirSync(r2, { withFileTypes: true })) {
106
+ if (entry.isDirectory())
107
+ games.push(entry.name);
108
+ }
109
+ }
110
+ catch {
111
+ // Ignore unreadable.
112
+ }
113
+ out.push({ name: "r2modman", dataPath: r2, managedGameIds: games });
114
+ }
115
+ const curse = findFirstExisting(curseforgeCandidates());
116
+ if (curse) {
117
+ out.push({ name: "curseforge", dataPath: curse });
118
+ }
119
+ return out;
120
+ }
121
+ /**
122
+ * Heuristic guess at which detected manager is most likely managing a given
123
+ * game. Family-based: bethesda games skew Vortex/MO2, unity-coop skews
124
+ * r2modman, sims/minecraft skews CurseForge. Returns null if no detected
125
+ * manager fits the game.
126
+ */
127
+ export function inferManagerForGame(game, managers) {
128
+ const names = new Set(managers.map((m) => m.name));
129
+ switch (game.family) {
130
+ case "bethesda": {
131
+ // Prefer MO2 if its instance list mentions this game, else Vortex.
132
+ const mo2 = managers.find((m) => m.name === "mo2");
133
+ if (mo2?.managedGameIds?.some((i) => i.toLowerCase().includes(game.gameId.slice(0, 6)))) {
134
+ return "mo2";
135
+ }
136
+ if (names.has("vortex"))
137
+ return "vortex";
138
+ if (names.has("mo2"))
139
+ return "mo2";
140
+ return null;
141
+ }
142
+ case "unity-coop":
143
+ if (names.has("r2modman"))
144
+ return "r2modman";
145
+ if (names.has("thunderstore-mm"))
146
+ return "thunderstore-mm";
147
+ return null;
148
+ case "minecraft":
149
+ case "sims":
150
+ if (names.has("curseforge"))
151
+ return "curseforge";
152
+ return null;
153
+ default:
154
+ return null;
155
+ }
156
+ }
157
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/detect/manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export type OsName = "windows" | "macos" | "linux";
2
+ export declare function detectOs(): OsName;
3
+ /**
4
+ * Detect SteamOS / Steam Deck by reading /etc/os-release. Returns false on
5
+ * non-Linux systems without touching the filesystem.
6
+ *
7
+ * Source: Valve's Steam Deck images set ID=steamos in os-release. Holo (the
8
+ * SteamOS 3 base) keeps the same ID, so this catches both bare metal and
9
+ * cloned/recovery installs.
10
+ */
11
+ export declare function detectSteamDeck(): boolean;
12
+ /**
13
+ * Detect whether we're running inside Steam Deck's Game Mode (Gamescope
14
+ * compositor) vs Desktop Mode. Game Mode can't run mod managers cleanly, so
15
+ * this is worth surfacing to the user.
16
+ */
17
+ export declare function detectGameMode(): boolean;
18
+ /** Whether a path exists and is readable. Wraps existsSync with a try/catch
19
+ * because on Windows existsSync can throw on certain reparse points. */
20
+ export declare function pathExists(p: string): boolean;
21
+ //# sourceMappingURL=os.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"os.d.ts","sourceRoot":"","sources":["../../src/detect/os.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnD,wBAAgB,QAAQ,IAAI,MAAM,CASjC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAazC;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAIxC;AAED;wEACwE;AACxE,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAM7C"}
@@ -0,0 +1,58 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ export function detectOs() {
3
+ switch (process.platform) {
4
+ case "win32":
5
+ return "windows";
6
+ case "darwin":
7
+ return "macos";
8
+ default:
9
+ return "linux";
10
+ }
11
+ }
12
+ /**
13
+ * Detect SteamOS / Steam Deck by reading /etc/os-release. Returns false on
14
+ * non-Linux systems without touching the filesystem.
15
+ *
16
+ * Source: Valve's Steam Deck images set ID=steamos in os-release. Holo (the
17
+ * SteamOS 3 base) keeps the same ID, so this catches both bare metal and
18
+ * cloned/recovery installs.
19
+ */
20
+ export function detectSteamDeck() {
21
+ if (process.platform !== "linux")
22
+ return false;
23
+ try {
24
+ const content = readFileSync("/etc/os-release", "utf8");
25
+ const idLine = content
26
+ .split("\n")
27
+ .find((line) => line.startsWith("ID="));
28
+ if (!idLine)
29
+ return false;
30
+ const id = idLine.slice(3).replace(/"/g, "").trim().toLowerCase();
31
+ return id === "steamos" || id === "holo";
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ /**
38
+ * Detect whether we're running inside Steam Deck's Game Mode (Gamescope
39
+ * compositor) vs Desktop Mode. Game Mode can't run mod managers cleanly, so
40
+ * this is worth surfacing to the user.
41
+ */
42
+ export function detectGameMode() {
43
+ if (process.platform !== "linux")
44
+ return false;
45
+ // Gamescope sets these env vars in Game Mode sessions.
46
+ return !!(process.env.GAMESCOPE_WAYLAND_DISPLAY || process.env.SteamDeck);
47
+ }
48
+ /** Whether a path exists and is readable. Wraps existsSync with a try/catch
49
+ * because on Windows existsSync can throw on certain reparse points. */
50
+ export function pathExists(p) {
51
+ try {
52
+ return existsSync(p);
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
58
+ //# sourceMappingURL=os.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"os.js","sourceRoot":"","sources":["../../src/detect/os.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAInD,MAAM,UAAU,QAAQ;IACtB,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,OAAO,CAAC;QACjB;YACE,OAAO,OAAO,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,OAAO;aACnB,KAAK,CAAC,IAAI,CAAC;aACX,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC1B,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAClE,OAAO,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,MAAM,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAC/C,uDAAuD;IACvD,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC5E,CAAC;AAED;wEACwE;AACxE,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,32 @@
1
+ export declare function findSteamRoot(): string | null;
2
+ /**
3
+ * Resolve all Steam library folders configured for this install. Steam keeps
4
+ * a list of these in steamapps/libraryfolders.vdf so games on a second drive
5
+ * are reachable from the same client.
6
+ */
7
+ export declare function findSteamLibraries(steamRoot: string): string[];
8
+ export type InstalledApp = {
9
+ appId: string;
10
+ name: string;
11
+ installDir: string;
12
+ libraryPath: string;
13
+ };
14
+ /**
15
+ * Find an installed Steam app by its numeric appid. Walks every configured
16
+ * library until it finds a matching appmanifest_<appid>.acf. Returns null if
17
+ * the app isn't installed anywhere.
18
+ */
19
+ export declare function findInstalledApp(libraryPaths: string[], appId: string): InstalledApp | null;
20
+ /**
21
+ * List Proton tools available to the Steam install — both bundled and any
22
+ * user-installed (GE-Proton, etc.) under compatibilitytools.d. Only relevant
23
+ * on Linux; returns empty array on other platforms.
24
+ */
25
+ export declare function listProtonTools(steamRoot: string): string[];
26
+ /**
27
+ * Look up the Proton version used for a specific game by reading the
28
+ * compatdata/<appid> entry. Returns null if no compat prefix exists (game is
29
+ * running natively or hasn't been launched yet).
30
+ */
31
+ export declare function detectProtonForApp(steamRoot: string, appId: string): string | null;
32
+ //# sourceMappingURL=steam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steam.d.ts","sourceRoot":"","sources":["../../src/detect/steam.ts"],"names":[],"mappings":"AAuCA,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAO7C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAuB9D;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EAAE,EACtB,KAAK,EAAE,MAAM,GACZ,YAAY,GAAG,IAAI,CAoBrB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAgB3D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAcf"}
@@ -0,0 +1,155 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { parseVdf } from "./vdf.js";
5
+ import { pathExists } from "./os.js";
6
+ /**
7
+ * Candidate Steam install roots for the current OS. We try defaults rather
8
+ * than touching the Windows registry — covers 99% of installs and keeps the
9
+ * workbench dependency-free. Power users with a non-default Steam root can
10
+ * override via STEAM_ROOT env var.
11
+ */
12
+ function steamRootCandidates() {
13
+ const override = process.env.STEAM_ROOT;
14
+ if (override)
15
+ return [override];
16
+ const home = homedir();
17
+ switch (process.platform) {
18
+ case "win32":
19
+ return [
20
+ "C:\\Program Files (x86)\\Steam",
21
+ "C:\\Program Files\\Steam",
22
+ process.env.ProgramFiles ? join(process.env.ProgramFiles, "Steam") : "",
23
+ process.env["ProgramFiles(x86)"]
24
+ ? join(process.env["ProgramFiles(x86)"], "Steam")
25
+ : "",
26
+ ].filter(Boolean);
27
+ case "darwin":
28
+ return [join(home, "Library", "Application Support", "Steam")];
29
+ default:
30
+ return [
31
+ join(home, ".steam", "steam"),
32
+ join(home, ".local", "share", "Steam"),
33
+ // Flatpak Steam
34
+ join(home, ".var", "app", "com.valvesoftware.Steam", "data", "Steam"),
35
+ ];
36
+ }
37
+ }
38
+ export function findSteamRoot() {
39
+ for (const candidate of steamRootCandidates()) {
40
+ if (pathExists(candidate) && pathExists(join(candidate, "steamapps"))) {
41
+ return candidate;
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ /**
47
+ * Resolve all Steam library folders configured for this install. Steam keeps
48
+ * a list of these in steamapps/libraryfolders.vdf so games on a second drive
49
+ * are reachable from the same client.
50
+ */
51
+ export function findSteamLibraries(steamRoot) {
52
+ const libs = [join(steamRoot, "steamapps")];
53
+ const vdfPath = join(steamRoot, "steamapps", "libraryfolders.vdf");
54
+ if (!pathExists(vdfPath))
55
+ return libs;
56
+ let parsed;
57
+ try {
58
+ parsed = parseVdf(readFileSync(vdfPath, "utf8"));
59
+ }
60
+ catch {
61
+ return libs;
62
+ }
63
+ if (!parsed)
64
+ return libs;
65
+ for (const value of Object.values(parsed)) {
66
+ if (typeof value !== "object")
67
+ continue;
68
+ const path = value["path"];
69
+ if (typeof path !== "string")
70
+ continue;
71
+ const steamapps = join(path, "steamapps");
72
+ if (pathExists(steamapps) && !libs.includes(steamapps)) {
73
+ libs.push(steamapps);
74
+ }
75
+ }
76
+ return libs;
77
+ }
78
+ /**
79
+ * Find an installed Steam app by its numeric appid. Walks every configured
80
+ * library until it finds a matching appmanifest_<appid>.acf. Returns null if
81
+ * the app isn't installed anywhere.
82
+ */
83
+ export function findInstalledApp(libraryPaths, appId) {
84
+ for (const lib of libraryPaths) {
85
+ const manifestPath = join(lib, `appmanifest_${appId}.acf`);
86
+ if (!pathExists(manifestPath))
87
+ continue;
88
+ let parsed;
89
+ try {
90
+ parsed = parseVdf(readFileSync(manifestPath, "utf8"));
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ if (!parsed)
96
+ continue;
97
+ const name = typeof parsed["name"] === "string" ? parsed["name"] : appId;
98
+ const installDirName = typeof parsed["installdir"] === "string" ? parsed["installdir"] : null;
99
+ if (!installDirName)
100
+ continue;
101
+ const installDir = join(lib, "common", installDirName);
102
+ if (!pathExists(installDir))
103
+ continue;
104
+ return { appId, name, installDir, libraryPath: lib };
105
+ }
106
+ return null;
107
+ }
108
+ /**
109
+ * List Proton tools available to the Steam install — both bundled and any
110
+ * user-installed (GE-Proton, etc.) under compatibilitytools.d. Only relevant
111
+ * on Linux; returns empty array on other platforms.
112
+ */
113
+ export function listProtonTools(steamRoot) {
114
+ if (process.platform !== "linux")
115
+ return [];
116
+ const out = [];
117
+ const compatDir = join(steamRoot, "compatibilitytools.d");
118
+ if (pathExists(compatDir)) {
119
+ try {
120
+ for (const entry of readdirSync(compatDir)) {
121
+ if (entry.startsWith("Proton") || entry.startsWith("GE-Proton")) {
122
+ out.push(entry);
123
+ }
124
+ }
125
+ }
126
+ catch {
127
+ // Ignore unreadable directory.
128
+ }
129
+ }
130
+ return out;
131
+ }
132
+ /**
133
+ * Look up the Proton version used for a specific game by reading the
134
+ * compatdata/<appid> entry. Returns null if no compat prefix exists (game is
135
+ * running natively or hasn't been launched yet).
136
+ */
137
+ export function detectProtonForApp(steamRoot, appId) {
138
+ if (process.platform !== "linux")
139
+ return null;
140
+ const compatdataAppDir = join(steamRoot, "steamapps", "compatdata", appId);
141
+ if (!pathExists(compatdataAppDir))
142
+ return null;
143
+ // version.txt inside the prefix names the Proton build that created it.
144
+ const versionFile = join(compatdataAppDir, "version");
145
+ if (pathExists(versionFile)) {
146
+ try {
147
+ return readFileSync(versionFile, "utf8").trim();
148
+ }
149
+ catch {
150
+ // Fall through.
151
+ }
152
+ }
153
+ return "unknown";
154
+ }
155
+ //# sourceMappingURL=steam.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steam.js","sourceRoot":"","sources":["../../src/detect/steam.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export type VdfValue = string | VdfObject;
2
+ export type VdfObject = {
3
+ [k: string]: VdfValue;
4
+ };
5
+ /**
6
+ * Parse a VDF document. Returns the top-level object (after the outer wrapper
7
+ * key, which Valve always wraps everything in — e.g. `libraryfolders { ... }`).
8
+ * Returns null on parse failure rather than throwing — caller decides whether
9
+ * a missing manifest is fatal or just "skip this library."
10
+ */
11
+ export declare function parseVdf(text: string): VdfObject | null;
12
+ //# sourceMappingURL=vdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vdf.d.ts","sourceRoot":"","sources":["../../src/detect/vdf.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAC1C,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,CAAC;AAwElD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAYvD"}
@@ -0,0 +1,112 @@
1
+ // Minimal parser for Valve KeyValues (VDF / .acf) format. Steam stores its
2
+ // library catalog and per-app manifests in this format. We only need the
3
+ // subset that lets us pull installed-app metadata, so we hand-roll a small
4
+ // recursive descent parser rather than pulling in a dependency.
5
+ //
6
+ // Spec we support:
7
+ // - "key" "value" → string field
8
+ // - "key" { ... } → nested object
9
+ // - // line comments → ignored
10
+ // - Escaped quotes (\") inside values
11
+ // Anything weirder, we'll punt to null and the caller can fall back.
12
+ class Cursor {
13
+ src;
14
+ pos = 0;
15
+ constructor(src) {
16
+ this.src = src;
17
+ }
18
+ peek() {
19
+ return this.src[this.pos];
20
+ }
21
+ next() {
22
+ return this.src[this.pos++];
23
+ }
24
+ eof() {
25
+ return this.pos >= this.src.length;
26
+ }
27
+ }
28
+ function skipWhitespace(c) {
29
+ while (!c.eof()) {
30
+ const ch = c.peek();
31
+ if (ch === " " || ch === "\t" || ch === "\r" || ch === "\n") {
32
+ c.next();
33
+ continue;
34
+ }
35
+ // Line comment
36
+ if (ch === "/" && c.src[c.pos + 1] === "/") {
37
+ while (!c.eof() && c.peek() !== "\n")
38
+ c.next();
39
+ continue;
40
+ }
41
+ break;
42
+ }
43
+ }
44
+ function readQuotedString(c) {
45
+ if (c.next() !== '"')
46
+ throw new Error("expected opening quote");
47
+ let out = "";
48
+ while (!c.eof()) {
49
+ const ch = c.next();
50
+ if (ch === '"')
51
+ return out;
52
+ if (ch === "\\") {
53
+ const esc = c.next();
54
+ if (esc === "n")
55
+ out += "\n";
56
+ else if (esc === "t")
57
+ out += "\t";
58
+ else
59
+ out += esc ?? "";
60
+ continue;
61
+ }
62
+ out += ch;
63
+ }
64
+ throw new Error("unterminated string");
65
+ }
66
+ function readObject(c) {
67
+ if (c.next() !== "{")
68
+ throw new Error("expected {");
69
+ const obj = {};
70
+ while (true) {
71
+ skipWhitespace(c);
72
+ if (c.peek() === "}") {
73
+ c.next();
74
+ return obj;
75
+ }
76
+ if (c.eof())
77
+ throw new Error("unterminated object");
78
+ const key = readQuotedString(c);
79
+ skipWhitespace(c);
80
+ if (c.peek() === "{") {
81
+ obj[key] = readObject(c);
82
+ }
83
+ else if (c.peek() === '"') {
84
+ obj[key] = readQuotedString(c);
85
+ }
86
+ else {
87
+ throw new Error(`unexpected token after key "${key}"`);
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Parse a VDF document. Returns the top-level object (after the outer wrapper
93
+ * key, which Valve always wraps everything in — e.g. `libraryfolders { ... }`).
94
+ * Returns null on parse failure rather than throwing — caller decides whether
95
+ * a missing manifest is fatal or just "skip this library."
96
+ */
97
+ export function parseVdf(text) {
98
+ try {
99
+ const c = new Cursor(text);
100
+ skipWhitespace(c);
101
+ if (c.eof())
102
+ return null;
103
+ // Outer key
104
+ readQuotedString(c);
105
+ skipWhitespace(c);
106
+ return readObject(c);
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ //# sourceMappingURL=vdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vdf.js","sourceRoot":"","sources":["../../src/detect/vdf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { log } from "@modwrench/core";
5
+ import { registerWorkbenchTools } from "./register.js";
6
+ // ─── MCP server boot ──────────────────────────────────────────────────────────
7
+ // Standalone bin for running just the workbench tools in isolation. The
8
+ // @modwrench/cli meta-server composes this with the platform packages, but
9
+ // users who only want local-filesystem tools (no Nexus/mod.io credentials)
10
+ // can run this one directly.
11
+ const server = new McpServer({
12
+ name: "modwrench-workbench",
13
+ version: "0.0.1",
14
+ });
15
+ const { toolCount } = registerWorkbenchTools(server);
16
+ async function main() {
17
+ const transport = new StdioServerTransport();
18
+ await server.connect(transport);
19
+ log("info", "modwrench-workbench.started", { tools: toolCount });
20
+ }
21
+ main().catch((err) => {
22
+ log("error", "modwrench-workbench.fatal", {
23
+ message: err instanceof Error ? err.message : String(err),
24
+ });
25
+ process.exit(1);
26
+ });
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEvD,iFAAiF;AACjF,wEAAwE;AACxE,2EAA2E;AAC3E,2EAA2E;AAC3E,6BAA6B;AAE7B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,qBAAqB;IAC3B,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,MAAM,EAAE,SAAS,EAAE,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAErD,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,GAAG,CAAC,MAAM,EAAE,6BAA6B,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,GAAG,CAAC,OAAO,EAAE,2BAA2B,EAAE;QACxC,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;KAC1D,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { GameDef } from "../detect/games.js";
2
+ import { type ManagerName } from "../detect/manager.js";
3
+ import type { LoadOrderResult } from "./types.js";
4
+ export type ReadLoadOrderInput = {
5
+ gameId: string;
6
+ modManager?: ManagerName | "auto";
7
+ profileName?: string;
8
+ instancePath?: string;
9
+ };
10
+ export type ReadLoadOrderError = {
11
+ ok: false;
12
+ reason: string;
13
+ game?: GameDef;
14
+ attemptedManagers: ManagerName[];
15
+ };
16
+ export type ReadLoadOrderSuccess = LoadOrderResult & {
17
+ ok: true;
18
+ };
19
+ export declare function readLoadOrder(input: ReadLoadOrderInput): ReadLoadOrderSuccess | ReadLoadOrderError;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/loadorder/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iBAAiB,EAAE,WAAW,EAAE,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC;AAElE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,kBAAkB,GACxB,oBAAoB,GAAG,kBAAkB,CAuE3C"}