@edgemodders/redcode-mcp 0.4.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ What changed in each redcode-mcp release. The release notes on GitHub come from these sections; changes are grouped as **New**, **Changed**, **Fixed** and **Other**, and older sections keep the form they had.
4
+
5
+ ## Unreleased
6
+
7
+ ## 0.5.0 (2026-11-10)
8
+
9
+ For EDGEfab and EDGEhack: `library_add`, and image imports with a texture setup. `texture` needs REDbridge 0.12.0; the rest work as in 0.4.0.
10
+
11
+ **New**
12
+
13
+ - `library_add`: a game file or a project's file, with the files it needs, as one asset item in the EDGE Library (REDcode issue #19). With no Library yet or no folder given, it answers what to ask the modder. `gamePathFolders` is the Use the game path choice. Works with REDbridge 0.10.0 or later.
14
+ - `project_import` takes `texture`, an image's `.xbm` setup (a preset, a texture group, or its fields), and answers the setup read back. Needs REDbridge 0.12.0.
15
+
3
16
  ## 0.4.0 (2026-10-27)
4
17
 
5
18
  The new tool needs REDbridge 0.11.0, the bridge for game 2.5; the rest work as in 0.3.0.
package/README.md CHANGED
@@ -53,7 +53,8 @@ To build from source, run `npm install` and `npm run mcp:build` in the [REDcode
53
53
  | `texture_to_png`, `texture_file_to_png` | Decode an `.xbm` to PNG |
54
54
  | `hash_fnv1a64`, `hash_resolve` | Hash a string; look a hash up |
55
55
  | `archive_open_mod`, `archive_extract_from_mod` | List the resources in a mod's `.archive` on disk; extract them, by game path or hash. No game needed |
56
- | `project_export`, `project_import`, `project_pack` | WolvenKit's exporter (a mesh to glTF, a texture to an image, a `.wem` to Ogg) and importer between `source/archive` and `source/raw`; pack a folder into `<modName>.archive` |
56
+ | `project_export`, `project_import`, `project_pack` | WolvenKit's exporter (a mesh to glTF, a texture to an image, a `.wem` to Ogg) and importer between `source/archive` and `source/raw`; pack a folder into `<modName>.archive`. An image import takes `texture`, the `.xbm`'s setup (a preset, a texture group or its fields), and answers the setup read back (bridge 0.12.0) |
57
+ | `library_add` | A game file or a project's file, with the files it needs, as one asset item in the EDGE Library. With no Library yet or no folder given, it answers what to ask the modder |
57
58
  | `loc_languages`, `loc_search`, `loc_get` | The game's on-screen texts (LocKeys) per language, base game and Phantom Liberty, read offline. Search by LocKey, secondary key or words of the text; get by LocKey or secondary key. With `projectDir`, the project's own onscreens entries win. Bridge 0.10.0 |
58
59
  | `tweakdb_types`, `tweakdb_records`, `tweakdb_record`, `tweakdb_flat`, `tweakdb_search`, `tweakdb_names` | TweakDB as the game ships it, read offline from `tweakdb_ep1.bin` (the first call loads it, about 12 s). Records by type (subtypes included) or prefix with the flats you name, one record with every flat, one flat, a search by path words, and ids to paths. Record links come back as paths, LocKeys as `{ key, text }`. Bridge 0.8.0 |
59
60
  | `mesh_items` | The TweakDB item records whose look shows a mesh, each with its display name, the mesh appearance it shows, the `.app` look and the root entity on the way. Covers items that pick their look by `appearanceName`, such as clothing. Bridge 0.11.0 |
@@ -1,4 +1,4 @@
1
- @edgemodders/redcode-mcp 0.4.0 bundles the following packages into dist/server.cjs. Their notices follow.
1
+ @edgemodders/redcode-mcp 0.5.0 bundles the following packages into dist/server.cjs. Their notices follow.
2
2
 
3
3
  ------------------------------------------------------------------------------
4
4
 
package/dist/server.cjs CHANGED
@@ -2236,8 +2236,8 @@ var require_resolve = __commonJS({
2236
2236
  }
2237
2237
  return count;
2238
2238
  }
2239
- function getFullPath(resolver, id = "", normalize) {
2240
- if (normalize !== false)
2239
+ function getFullPath(resolver, id = "", normalize2) {
2240
+ if (normalize2 !== false)
2241
2241
  id = normalizeId(id);
2242
2242
  const p = resolver.parse(id);
2243
2243
  return _getFullPath(resolver, p);
@@ -2985,7 +2985,7 @@ var require_compile = __commonJS({
2985
2985
  const schOrFunc = root.refs[ref];
2986
2986
  if (schOrFunc)
2987
2987
  return schOrFunc;
2988
- let _sch = resolve2.call(this, root, ref);
2988
+ let _sch = resolve4.call(this, root, ref);
2989
2989
  if (_sch === void 0) {
2990
2990
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2991
2991
  const { schemaId } = this.opts;
@@ -3012,7 +3012,7 @@ var require_compile = __commonJS({
3012
3012
  function sameSchemaEnv(s1, s2) {
3013
3013
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3014
3014
  }
3015
- function resolve2(root, ref) {
3015
+ function resolve4(root, ref) {
3016
3016
  let sch;
3017
3017
  while (typeof (sch = this.refs[ref]) == "string")
3018
3018
  ref = sch;
@@ -3263,8 +3263,8 @@ var require_utils = __commonJS({
3263
3263
  }
3264
3264
  return ind;
3265
3265
  }
3266
- function removeDotSegments(path4) {
3267
- let input = path4;
3266
+ function removeDotSegments(path6) {
3267
+ let input = path6;
3268
3268
  const output = [];
3269
3269
  let nextSlash = -1;
3270
3270
  let len = 0;
@@ -3673,8 +3673,8 @@ var require_schemes = __commonJS({
3673
3673
  }
3674
3674
  if (wsComponent.resourceName) {
3675
3675
  const queryIndex = wsComponent.resourceName.indexOf("?");
3676
- const path4 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3677
- wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
3676
+ const path6 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3677
+ wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
3678
3678
  wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
3679
3679
  wsComponent.resourceName = void 0;
3680
3680
  }
@@ -3832,7 +3832,7 @@ var require_fast_uri = __commonJS({
3832
3832
  }
3833
3833
  return decodedScheme;
3834
3834
  }
3835
- function normalize(uri, options) {
3835
+ function normalize2(uri, options) {
3836
3836
  if (typeof uri === "string") {
3837
3837
  uri = /** @type {T} */
3838
3838
  normalizeString(uri, options);
@@ -3842,7 +3842,7 @@ var require_fast_uri = __commonJS({
3842
3842
  }
3843
3843
  return uri;
3844
3844
  }
3845
- function resolve2(baseURI, relativeURI, options) {
3845
+ function resolve4(baseURI, relativeURI, options) {
3846
3846
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3847
3847
  const {
3848
3848
  parsed: baseParsed,
@@ -3875,49 +3875,49 @@ var require_fast_uri = __commonJS({
3875
3875
  schemelessOptions.skipEscape = true;
3876
3876
  return serialize(resolved, schemelessOptions);
3877
3877
  }
3878
- function resolveComponent(base, relative2, options, skipNormalization) {
3878
+ function resolveComponent(base, relative3, options, skipNormalization) {
3879
3879
  const target = {};
3880
3880
  if (!skipNormalization) {
3881
3881
  base = parse3(serialize(base, options), options);
3882
- relative2 = parse3(serialize(relative2, options), options);
3882
+ relative3 = parse3(serialize(relative3, options), options);
3883
3883
  }
3884
3884
  options = options || {};
3885
- if (!options.tolerant && relative2.scheme) {
3886
- target.scheme = relative2.scheme;
3887
- target.userinfo = relative2.userinfo;
3888
- target.host = relative2.host;
3889
- target.port = relative2.port;
3890
- target.path = removeDotSegments(relative2.path || "");
3891
- target.query = relative2.query;
3885
+ if (!options.tolerant && relative3.scheme) {
3886
+ target.scheme = relative3.scheme;
3887
+ target.userinfo = relative3.userinfo;
3888
+ target.host = relative3.host;
3889
+ target.port = relative3.port;
3890
+ target.path = removeDotSegments(relative3.path || "");
3891
+ target.query = relative3.query;
3892
3892
  } else {
3893
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
3894
- target.userinfo = relative2.userinfo;
3895
- target.host = relative2.host;
3896
- target.port = relative2.port;
3897
- target.path = removeDotSegments(relative2.path || "");
3898
- target.query = relative2.query;
3893
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
3894
+ target.userinfo = relative3.userinfo;
3895
+ target.host = relative3.host;
3896
+ target.port = relative3.port;
3897
+ target.path = removeDotSegments(relative3.path || "");
3898
+ target.query = relative3.query;
3899
3899
  } else {
3900
- if (!relative2.path) {
3900
+ if (!relative3.path) {
3901
3901
  target.path = base.path;
3902
- if (relative2.query !== void 0) {
3903
- target.query = relative2.query;
3902
+ if (relative3.query !== void 0) {
3903
+ target.query = relative3.query;
3904
3904
  } else {
3905
3905
  target.query = base.query;
3906
3906
  }
3907
3907
  } else {
3908
- if (relative2.path[0] === "/") {
3909
- target.path = removeDotSegments(relative2.path);
3908
+ if (relative3.path[0] === "/") {
3909
+ target.path = removeDotSegments(relative3.path);
3910
3910
  } else {
3911
3911
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3912
- target.path = "/" + relative2.path;
3912
+ target.path = "/" + relative3.path;
3913
3913
  } else if (!base.path) {
3914
- target.path = relative2.path;
3914
+ target.path = relative3.path;
3915
3915
  } else {
3916
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
3916
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
3917
3917
  }
3918
3918
  target.path = removeDotSegments(target.path);
3919
3919
  }
3920
- target.query = relative2.query;
3920
+ target.query = relative3.query;
3921
3921
  }
3922
3922
  target.userinfo = base.userinfo;
3923
3923
  target.host = base.host;
@@ -3925,7 +3925,7 @@ var require_fast_uri = __commonJS({
3925
3925
  }
3926
3926
  target.scheme = base.scheme;
3927
3927
  }
3928
- target.fragment = relative2.fragment;
3928
+ target.fragment = relative3.fragment;
3929
3929
  return target;
3930
3930
  }
3931
3931
  function equal(uriA, uriB, options) {
@@ -4210,8 +4210,8 @@ var require_fast_uri = __commonJS({
4210
4210
  }
4211
4211
  var fastUri = {
4212
4212
  SCHEMES,
4213
- normalize,
4214
- resolve: resolve2,
4213
+ normalize: normalize2,
4214
+ resolve: resolve4,
4215
4215
  resolveComponent,
4216
4216
  equal,
4217
4217
  serialize,
@@ -7187,12 +7187,12 @@ var require_dist = __commonJS({
7187
7187
  throw new Error(`Unknown format "${name}"`);
7188
7188
  return f;
7189
7189
  };
7190
- function addFormats(ajv, list, fs4, exportName) {
7190
+ function addFormats(ajv, list, fs6, exportName) {
7191
7191
  var _a;
7192
7192
  var _b;
7193
7193
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
7194
7194
  for (const f of list)
7195
- ajv.addFormat(f, fs4[f]);
7195
+ ajv.addFormat(f, fs6[f]);
7196
7196
  }
7197
7197
  module2.exports = exports2 = formatsPlugin;
7198
7198
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -8416,8 +8416,8 @@ var require_semaphore = __commonJS({
8416
8416
  this._waiting = [];
8417
8417
  }
8418
8418
  lock(thunk) {
8419
- return new Promise((resolve2, reject) => {
8420
- this._waiting.push({ thunk, resolve: resolve2, reject });
8419
+ return new Promise((resolve4, reject) => {
8420
+ this._waiting.push({ thunk, resolve: resolve4, reject });
8421
8421
  this.runNext();
8422
8422
  });
8423
8423
  }
@@ -10043,9 +10043,9 @@ ${JSON.stringify(message, null, 4)}`);
10043
10043
  if (typeof cancellationStrategy.sender.enableCancellation === "function") {
10044
10044
  cancellationStrategy.sender.enableCancellation(requestMessage);
10045
10045
  }
10046
- return new Promise(async (resolve2, reject) => {
10046
+ return new Promise(async (resolve4, reject) => {
10047
10047
  const resolveWithCleanup = (r) => {
10048
- resolve2(r);
10048
+ resolve4(r);
10049
10049
  cancellationStrategy.sender.cleanup(id);
10050
10050
  disposable?.dispose();
10051
10051
  };
@@ -10474,10 +10474,10 @@ var require_ril = __commonJS({
10474
10474
  return api_1.Disposable.create(() => this.stream.off("end", listener));
10475
10475
  }
10476
10476
  write(data, encoding) {
10477
- return new Promise((resolve2, reject) => {
10477
+ return new Promise((resolve4, reject) => {
10478
10478
  const callback = (error2) => {
10479
10479
  if (error2 === void 0 || error2 === null) {
10480
- resolve2();
10480
+ resolve4();
10481
10481
  } else {
10482
10482
  reject(error2);
10483
10483
  }
@@ -10613,9 +10613,9 @@ var require_main = __commonJS({
10613
10613
  exports2.createMessageConnection = createMessageConnection2;
10614
10614
  var ril_1 = __importDefault(require_ril());
10615
10615
  ril_1.default.install();
10616
- var path4 = __importStar(require("path"));
10617
- var os2 = __importStar(require("os"));
10618
- var fs4 = __importStar(require("fs"));
10616
+ var path6 = __importStar(require("path"));
10617
+ var os3 = __importStar(require("os"));
10618
+ var fs6 = __importStar(require("fs"));
10619
10619
  var crypto_1 = require("crypto");
10620
10620
  var net_1 = require("net");
10621
10621
  var api_1 = require_api();
@@ -10756,7 +10756,7 @@ var require_main = __commonJS({
10756
10756
  }
10757
10757
  let randomLength = 32;
10758
10758
  const fixedLength = "/lsp-.sock".length;
10759
- const tmpDir = fs4.realpathSync(XDG_RUNTIME_DIR ?? os2.tmpdir());
10759
+ const tmpDir = fs6.realpathSync(XDG_RUNTIME_DIR ?? os3.tmpdir());
10760
10760
  const limit = safeIpcPathLengths.get(process.platform);
10761
10761
  if (limit !== void 0) {
10762
10762
  randomLength = Math.min(limit - tmpDir.length - fixedLength, randomLength);
@@ -10765,14 +10765,14 @@ var require_main = __commonJS({
10765
10765
  throw new Error(`Unable to generate a random pipe name with ${randomLength} characters.`);
10766
10766
  }
10767
10767
  const randomSuffix = (0, crypto_1.randomBytes)(Math.floor(randomLength / 2)).toString("hex");
10768
- return path4.join(tmpDir, `lsp-${randomSuffix}.sock`);
10768
+ return path6.join(tmpDir, `lsp-${randomSuffix}.sock`);
10769
10769
  }
10770
10770
  function createClientPipeTransport(pipeName, encoding = "utf-8") {
10771
10771
  let connectResolve;
10772
- const connected = new Promise((resolve2, _reject) => {
10773
- connectResolve = resolve2;
10772
+ const connected = new Promise((resolve4, _reject) => {
10773
+ connectResolve = resolve4;
10774
10774
  });
10775
- return new Promise((resolve2, reject) => {
10775
+ return new Promise((resolve4, reject) => {
10776
10776
  const server2 = (0, net_1.createServer)((socket) => {
10777
10777
  server2.close();
10778
10778
  connectResolve([
@@ -10783,7 +10783,7 @@ var require_main = __commonJS({
10783
10783
  server2.on("error", reject);
10784
10784
  server2.listen(pipeName, () => {
10785
10785
  server2.removeListener("error", reject);
10786
- resolve2({
10786
+ resolve4({
10787
10787
  onConnected: () => {
10788
10788
  return connected;
10789
10789
  }
@@ -10800,10 +10800,10 @@ var require_main = __commonJS({
10800
10800
  }
10801
10801
  function createClientSocketTransport(port, encoding = "utf-8") {
10802
10802
  let connectResolve;
10803
- const connected = new Promise((resolve2, _reject) => {
10804
- connectResolve = resolve2;
10803
+ const connected = new Promise((resolve4, _reject) => {
10804
+ connectResolve = resolve4;
10805
10805
  });
10806
- return new Promise((resolve2, reject) => {
10806
+ return new Promise((resolve4, reject) => {
10807
10807
  const server2 = (0, net_1.createServer)((socket) => {
10808
10808
  server2.close();
10809
10809
  connectResolve([
@@ -10820,7 +10820,7 @@ var require_main = __commonJS({
10820
10820
  return;
10821
10821
  }
10822
10822
  const boundPort = address.port;
10823
- resolve2({
10823
+ resolve4({
10824
10824
  port: () => boundPort,
10825
10825
  onConnected: () => {
10826
10826
  return connected;
@@ -10859,9 +10859,9 @@ var require_main = __commonJS({
10859
10859
  });
10860
10860
 
10861
10861
  // src/server.ts
10862
- var fs3 = __toESM(require("node:fs"));
10863
- var os = __toESM(require("node:os"));
10864
- var path3 = __toESM(require("node:path"));
10862
+ var fs5 = __toESM(require("node:fs"));
10863
+ var os2 = __toESM(require("node:os"));
10864
+ var path5 = __toESM(require("node:path"));
10865
10865
 
10866
10866
  // ../../node_modules/zod/v3/external.js
10867
10867
  var external_exports = {};
@@ -11341,8 +11341,8 @@ function getErrorMap() {
11341
11341
 
11342
11342
  // ../../node_modules/zod/v3/helpers/parseUtil.js
11343
11343
  var makeIssue = (params) => {
11344
- const { data, path: path4, errorMaps, issueData } = params;
11345
- const fullPath = [...path4, ...issueData.path || []];
11344
+ const { data, path: path6, errorMaps, issueData } = params;
11345
+ const fullPath = [...path6, ...issueData.path || []];
11346
11346
  const fullIssue = {
11347
11347
  ...issueData,
11348
11348
  path: fullPath
@@ -11458,11 +11458,11 @@ var errorUtil;
11458
11458
 
11459
11459
  // ../../node_modules/zod/v3/types.js
11460
11460
  var ParseInputLazyPath = class {
11461
- constructor(parent, value, path4, key) {
11461
+ constructor(parent, value, path6, key) {
11462
11462
  this._cachedPath = [];
11463
11463
  this.parent = parent;
11464
11464
  this.data = value;
11465
- this._path = path4;
11465
+ this._path = path6;
11466
11466
  this._key = key;
11467
11467
  }
11468
11468
  get path() {
@@ -15099,10 +15099,10 @@ function assignProp(target, prop, value) {
15099
15099
  configurable: true
15100
15100
  });
15101
15101
  }
15102
- function getElementAtPath(obj, path4) {
15103
- if (!path4)
15102
+ function getElementAtPath(obj, path6) {
15103
+ if (!path6)
15104
15104
  return obj;
15105
- return path4.reduce((acc, key) => acc?.[key], obj);
15105
+ return path6.reduce((acc, key) => acc?.[key], obj);
15106
15106
  }
15107
15107
  function promiseAllObject(promisesObj) {
15108
15108
  const keys = Object.keys(promisesObj);
@@ -15422,11 +15422,11 @@ function aborted(x, startIndex = 0) {
15422
15422
  }
15423
15423
  return false;
15424
15424
  }
15425
- function prefixIssues(path4, issues) {
15425
+ function prefixIssues(path6, issues) {
15426
15426
  return issues.map((iss) => {
15427
15427
  var _a;
15428
15428
  (_a = iss).path ?? (_a.path = []);
15429
- iss.path.unshift(path4);
15429
+ iss.path.unshift(path6);
15430
15430
  return iss;
15431
15431
  });
15432
15432
  }
@@ -18837,11 +18837,11 @@ function normalizeObjectSchema(schema) {
18837
18837
  }
18838
18838
  return void 0;
18839
18839
  }
18840
- function getDotPath(path4) {
18841
- if (path4.length === 0) {
18840
+ function getDotPath(path6) {
18841
+ if (path6.length === 0) {
18842
18842
  return "object root";
18843
18843
  }
18844
- return path4.reduce((acc, seg, index) => {
18844
+ return path6.reduce((acc, seg, index) => {
18845
18845
  if (index === 0) {
18846
18846
  return String(seg);
18847
18847
  }
@@ -22992,7 +22992,7 @@ var Protocol = class {
22992
22992
  return;
22993
22993
  }
22994
22994
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
22995
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
22995
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
22996
22996
  options?.signal?.throwIfAborted();
22997
22997
  }
22998
22998
  } catch (error2) {
@@ -23009,7 +23009,7 @@ var Protocol = class {
23009
23009
  */
23010
23010
  request(request, resultSchema, options) {
23011
23011
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
23012
- return new Promise((resolve2, reject) => {
23012
+ return new Promise((resolve4, reject) => {
23013
23013
  const earlyReject = (error2) => {
23014
23014
  reject(error2);
23015
23015
  };
@@ -23087,7 +23087,7 @@ var Protocol = class {
23087
23087
  if (!parseResult.success) {
23088
23088
  reject(parseResult.error);
23089
23089
  } else {
23090
- resolve2(parseResult.data);
23090
+ resolve4(parseResult.data);
23091
23091
  }
23092
23092
  } catch (error2) {
23093
23093
  reject(error2);
@@ -23349,12 +23349,12 @@ var Protocol = class {
23349
23349
  }
23350
23350
  } catch {
23351
23351
  }
23352
- return new Promise((resolve2, reject) => {
23352
+ return new Promise((resolve4, reject) => {
23353
23353
  if (signal.aborted) {
23354
23354
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
23355
23355
  return;
23356
23356
  }
23357
- const timeoutId = setTimeout(resolve2, interval);
23357
+ const timeoutId = setTimeout(resolve4, interval);
23358
23358
  signal.addEventListener("abort", () => {
23359
23359
  clearTimeout(timeoutId);
23360
23360
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -24485,7 +24485,7 @@ var McpServer = class {
24485
24485
  let task = createTaskResult.task;
24486
24486
  const pollInterval = task.pollInterval ?? 5e3;
24487
24487
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
24488
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
24488
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
24489
24489
  const updatedTask = await extra.taskStore.getTask(taskId);
24490
24490
  if (!updatedTask) {
24491
24491
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -25149,12 +25149,12 @@ var StdioServerTransport = class {
25149
25149
  this.onclose?.();
25150
25150
  }
25151
25151
  send(message) {
25152
- return new Promise((resolve2) => {
25152
+ return new Promise((resolve4) => {
25153
25153
  const json = serializeMessage(message);
25154
25154
  if (this._stdout.write(json)) {
25155
- resolve2();
25155
+ resolve4();
25156
25156
  } else {
25157
- this._stdout.once("drain", resolve2);
25157
+ this._stdout.once("drain", resolve4);
25158
25158
  }
25159
25159
  });
25160
25160
  }
@@ -25167,8 +25167,44 @@ var path2 = __toESM(require("node:path"));
25167
25167
  // ../../extensions/redcode/src/exportCache.ts
25168
25168
  var fs = __toESM(require("node:fs"));
25169
25169
  var path = __toESM(require("node:path"));
25170
+ function gameStamp(gameDir) {
25171
+ if (!gameDir) return "no game";
25172
+ try {
25173
+ const s = fs.statSync(path.join(gameDir, "bin", "x64", "Cyberpunk2077.exe"));
25174
+ return `${path.resolve(gameDir).toLowerCase()}|${s.size}|${Math.round(s.mtimeMs)}`;
25175
+ } catch {
25176
+ return `${path.resolve(gameDir).toLowerCase()}|no exe`;
25177
+ }
25178
+ }
25179
+ function checkGameCache(cacheRoot, stamp) {
25180
+ const dir = path.join(cacheRoot, "game");
25181
+ const file = path.join(dir, "stamp.txt");
25182
+ let old;
25183
+ try {
25184
+ old = fs.readFileSync(file, "utf8").trim();
25185
+ } catch {
25186
+ }
25187
+ if (old === stamp) return stamp;
25188
+ fs.rmSync(dir, { recursive: true, force: true });
25189
+ fs.mkdirSync(dir, { recursive: true });
25190
+ fs.writeFileSync(file, `${stamp}
25191
+ `);
25192
+ return stamp;
25193
+ }
25194
+ var checkedStamps = /* @__PURE__ */ new Map();
25195
+ async function gameCacheReady(bridge2, cacheRoot) {
25196
+ const info = await bridge2.info();
25197
+ if (!info.gameDir) return void 0;
25198
+ const stamp = gameStamp(info.gameDir);
25199
+ if (checkedStamps.get(cacheRoot) !== stamp) {
25200
+ checkGameCache(cacheRoot, stamp);
25201
+ checkedStamps.set(cacheRoot, stamp);
25202
+ }
25203
+ return stamp;
25204
+ }
25170
25205
  async function sourceFile(bridge2, cacheRoot, source) {
25171
25206
  if (source.gamePath) {
25207
+ await gameCacheReady(bridge2, cacheRoot);
25172
25208
  const archiveDir = path.join(cacheRoot, "game", "archive");
25173
25209
  const file2 = path.join(archiveDir, ...source.gamePath.split("\\"));
25174
25210
  if (!fs.existsSync(file2)) await bridge2.extract(source.gamePath, file2);
@@ -25345,11 +25381,11 @@ var BridgeClient = class extends import_node_events.EventEmitter {
25345
25381
  if (!child || child.exitCode !== null) {
25346
25382
  return Promise.resolve();
25347
25383
  }
25348
- return new Promise((resolve2) => {
25349
- const timer = setTimeout(resolve2, timeoutMs);
25384
+ return new Promise((resolve4) => {
25385
+ const timer = setTimeout(resolve4, timeoutMs);
25350
25386
  child.once("exit", () => {
25351
25387
  clearTimeout(timer);
25352
- resolve2();
25388
+ resolve4();
25353
25389
  });
25354
25390
  });
25355
25391
  }
@@ -25376,17 +25412,17 @@ var BridgeClient = class extends import_node_events.EventEmitter {
25376
25412
  return this.call("archive.search", { query, limit });
25377
25413
  }
25378
25414
  /** One level of the archive folder tree. An empty path is the root. */
25379
- tree(path4 = "") {
25380
- return this.call("archive.tree", { path: path4 });
25415
+ tree(path6 = "") {
25416
+ return this.call("archive.tree", { path: path6 });
25381
25417
  }
25382
- extract(path4, outFile) {
25383
- return this.call("archive.extract", { path: path4, outFile });
25418
+ extract(path6, outFile) {
25419
+ return this.call("archive.extract", { path: path6, outFile });
25384
25420
  }
25385
25421
  extractMany(items) {
25386
25422
  return this.call("archive.extractMany", { items });
25387
25423
  }
25388
- toJson(path4, outFile) {
25389
- return this.call("cr2w.toJson", outFile ? { path: path4, outFile } : { path: path4 });
25424
+ toJson(path6, outFile) {
25425
+ return this.call("cr2w.toJson", outFile ? { path: path6, outFile } : { path: path6 });
25390
25426
  }
25391
25427
  /** Converts a CR2W file on disk, for example one in a project, to JSON. */
25392
25428
  fileToJson(file, outFile) {
@@ -25395,8 +25431,8 @@ var BridgeClient = class extends import_node_events.EventEmitter {
25395
25431
  fromJson(outFile, source) {
25396
25432
  return this.call("cr2w.fromJson", { outFile, ...source });
25397
25433
  }
25398
- toPng(path4, outFile) {
25399
- return this.call("texture.toPng", { path: path4, outFile });
25434
+ toPng(path6, outFile) {
25435
+ return this.call("texture.toPng", { path: path6, outFile });
25400
25436
  }
25401
25437
  /** Converts a texture file on disk, for example one in a project, to PNG. Needs REDbridge 0.3.0. */
25402
25438
  fileToPng(file, outFile) {
@@ -25415,16 +25451,16 @@ var BridgeClient = class extends import_node_events.EventEmitter {
25415
25451
  return this.call("archive.extractFromMod", { archivePath, items });
25416
25452
  }
25417
25453
  /** Lists the resources inside a mod archive on disk. No game needed. Bridge 0.4.0. */
25418
- openMod(path4) {
25419
- return this.call("archive.openMod", { path: path4 });
25454
+ openMod(path6) {
25455
+ return this.call("archive.openMod", { path: path6 });
25420
25456
  }
25421
25457
  /** Exports a file under archiveDir into rawDir at the same relative path, through WolvenKit's exporter. Bridge 0.7.0. */
25422
25458
  exportFile(file, archiveDir, rawDir, options) {
25423
25459
  return this.call("project.export", { file, archiveDir, rawDir, options: options ?? null });
25424
25460
  }
25425
25461
  /** Imports a raw file under rawDir back into archiveDir, through WolvenKit's importer. keep: an existing file keeps its settings. Bridge 0.7.0. */
25426
- importFile(file, rawDir, archiveDir, keep = true, template) {
25427
- return this.call("project.import", { file, rawDir, archiveDir, keep, template: template ?? null });
25462
+ importFile(file, rawDir, archiveDir, keep = true, template, texture) {
25463
+ return this.call("project.import", { file, rawDir, archiveDir, keep, template: template ?? null, ...texture !== void 0 ? { texture } : {} });
25428
25464
  }
25429
25465
  /** The sound event names WolvenKit knows, with their Wwise hashes, which name the groups in an .opusinfo. Bridge 0.7.2. */
25430
25466
  soundEvents() {
@@ -25441,11 +25477,11 @@ var BridgeClient = class extends import_node_events.EventEmitter {
25441
25477
  tweakRecords(query) {
25442
25478
  return this.call("tweakdb.records", { ...query });
25443
25479
  }
25444
- tweakRecord(path4, resolveLocKeys = false, language = "en-us") {
25445
- return this.call("tweakdb.record", { path: path4, resolveLocKeys, language });
25480
+ tweakRecord(path6, resolveLocKeys = false, language = "en-us") {
25481
+ return this.call("tweakdb.record", { path: path6, resolveLocKeys, language });
25446
25482
  }
25447
- tweakFlat(path4, resolveLocKeys = false, language = "en-us") {
25448
- return this.call("tweakdb.flat", { path: path4, resolveLocKeys, language });
25483
+ tweakFlat(path6, resolveLocKeys = false, language = "en-us") {
25484
+ return this.call("tweakdb.flat", { path: path6, resolveLocKeys, language });
25449
25485
  }
25450
25486
  tweakSearch(text, type, limit = 200) {
25451
25487
  return this.call("tweakdb.search", { text, type: type ?? null, limit });
@@ -25458,8 +25494,8 @@ var BridgeClient = class extends import_node_events.EventEmitter {
25458
25494
  * The item records whose look shows a mesh (a game path), and every .app look that shows it. With
25459
25495
  * appearance, only the looks that show that mesh appearance. Loads TweakDB on the first call. Bridge 0.11.0.
25460
25496
  */
25461
- meshItems(path4, appearance, language = "en-us") {
25462
- return this.call("mesh.items", { path: path4, appearance: appearance ?? null, language });
25497
+ meshItems(path6, appearance, language = "en-us") {
25498
+ return this.call("mesh.items", { path: path6, appearance: appearance ?? null, language });
25463
25499
  }
25464
25500
  // ---- on-screen texts (bridge 0.10.0) ------------------------------------------------
25465
25501
  /** The languages with on-screen texts in the game, and whether the base game and Phantom Liberty have them. */
@@ -25497,16 +25533,16 @@ var BridgeClient = class extends import_node_events.EventEmitter {
25497
25533
  return this.call("cr2w.documents");
25498
25534
  }
25499
25535
  /** One node by dotted path from the root chunk; an empty path is the root. Arrays page from offset. */
25500
- getNode(doc, path4 = "", offset = 0, limit = 200) {
25501
- return this.call("cr2w.getNode", { doc, path: path4, offset, limit });
25536
+ getNode(doc, path6 = "", offset = 0, limit = 200) {
25537
+ return this.call("cr2w.getNode", { doc, path: path6, offset, limit });
25502
25538
  }
25503
25539
  /** Several nodes in one call; a path that fails gives a node of kind "error" whose type holds the message. */
25504
25540
  getNodes(doc, paths, limit = 200) {
25505
25541
  return this.call("cr2w.getNodes", { doc, paths, limit });
25506
25542
  }
25507
25543
  /** Leaves whose text or property name contains the query, below a path. Total counts all; hits holds the first limit. */
25508
- searchDocument(doc, query, path4 = "", limit = 200) {
25509
- return this.call("cr2w.search", { doc, query, path: path4, limit });
25544
+ searchDocument(doc, query, path6 = "", limit = 200) {
25545
+ return this.call("cr2w.search", { doc, query, path: path6, limit });
25510
25546
  }
25511
25547
  /** Applies the operations atomically and returns the inverse, for undo. */
25512
25548
  patch(doc, ops) {
@@ -25532,8 +25568,359 @@ var BridgeClient = class extends import_node_events.EventEmitter {
25532
25568
  }
25533
25569
  };
25534
25570
 
25571
+ // ../../extensions/redcode/src/library.ts
25572
+ var import_node_child_process2 = require("node:child_process");
25573
+ var import_node_crypto = require("node:crypto");
25574
+ var fs3 = __toESM(require("node:fs"));
25575
+ var os = __toESM(require("node:os"));
25576
+ var path3 = __toESM(require("node:path"));
25577
+ function libraryConfigFile() {
25578
+ return path3.join(process.env.APPDATA ?? path3.join(os.homedir(), "AppData", "Roaming"), "EDGE", "library.json");
25579
+ }
25580
+ function readLibraryRoot(file = libraryConfigFile()) {
25581
+ try {
25582
+ const root = JSON.parse(fs3.readFileSync(file, "utf8")).root;
25583
+ return typeof root === "string" && root.trim() ? root.trim() : void 0;
25584
+ } catch {
25585
+ return void 0;
25586
+ }
25587
+ }
25588
+ function writeLibraryRoot(root, file = libraryConfigFile()) {
25589
+ root = path3.resolve(root);
25590
+ fs3.mkdirSync(root, { recursive: true });
25591
+ let data = {};
25592
+ try {
25593
+ data = JSON.parse(fs3.readFileSync(file, "utf8"));
25594
+ } catch {
25595
+ }
25596
+ fs3.mkdirSync(path3.dirname(file), { recursive: true });
25597
+ fs3.writeFileSync(file, JSON.stringify({ ...data, root }, null, 2));
25598
+ return root;
25599
+ }
25600
+ function documentsFolder() {
25601
+ const fallback = path3.join(os.homedir(), "Documents");
25602
+ if (process.platform !== "win32") return fallback;
25603
+ try {
25604
+ const out = (0, import_node_child_process2.execFileSync)("reg", ["query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", "/v", "Personal"], { encoding: "utf8", windowsHide: true, timeout: 5e3 });
25605
+ const m = /Personal\s+REG_(?:EXPAND_)?SZ\s+(.+)/.exec(out);
25606
+ if (!m) return fallback;
25607
+ return m[1].trim().replace(/%([^%]+)%/g, (all, name) => process.env[name] ?? all);
25608
+ } catch {
25609
+ return fallback;
25610
+ }
25611
+ }
25612
+ function suggestedLibraryRoot() {
25613
+ return path3.join(documentsFolder(), "EDGE Library");
25614
+ }
25615
+ var ItemEnding = /\.(asset|bake)$/i;
25616
+ function libraryFolders(root, cap = 2e3) {
25617
+ const found = [];
25618
+ const walk = (dir, rel, depth) => {
25619
+ let entries;
25620
+ try {
25621
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
25622
+ } catch {
25623
+ return;
25624
+ }
25625
+ for (const e of entries) {
25626
+ if (found.length >= cap) return;
25627
+ if (!e.isDirectory() || e.name.startsWith(".") || ItemEnding.test(e.name)) continue;
25628
+ const child = rel ? `${rel}/${e.name}` : e.name;
25629
+ found.push(child);
25630
+ if (depth < 16) walk(path3.join(dir, e.name), child, depth + 1);
25631
+ }
25632
+ };
25633
+ walk(root, "", 0);
25634
+ return found.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
25635
+ }
25636
+ function safeFolder(folder) {
25637
+ const parts = (folder ?? "").split(/[\\/]+/).filter((p) => p !== "");
25638
+ for (const p of parts) {
25639
+ if (p === "." || p === "..") throw new Error(`A Library folder cannot go through "${p}": ${folder}`);
25640
+ if (/[<>:"|?*\u0000-\u001f]/.test(p) || /[. ]$/.test(p)) throw new Error(`Not a folder name: "${p}"`);
25641
+ if (ItemEnding.test(p)) throw new Error(`"${p}" is a Library item, not a folder.`);
25642
+ }
25643
+ return parts.join("/");
25644
+ }
25645
+ function isInside(child, parent) {
25646
+ const rel = path3.relative(parent, child);
25647
+ return rel === "" || !rel.startsWith("..") && !path3.isAbsolute(rel);
25648
+ }
25649
+ var versions = /* @__PURE__ */ new Map();
25650
+ function gameVersionOf(gameDir) {
25651
+ if (!gameDir) return Promise.resolve(void 0);
25652
+ let v = versions.get(gameDir);
25653
+ if (!v) {
25654
+ const exe = path3.join(gameDir, "bin", "x64", "Cyberpunk2077.exe");
25655
+ v = new Promise((resolve4) => {
25656
+ (0, import_node_child_process2.execFile)(
25657
+ "powershell.exe",
25658
+ ["-NoProfile", "-NonInteractive", "-Command", `(Get-Item -LiteralPath '${exe.replace(/'/g, "''")}').VersionInfo.ProductVersion`],
25659
+ { windowsHide: true, timeout: 15e3 },
25660
+ (e, out) => resolve4(e ? void 0 : out.trim() || void 0)
25661
+ );
25662
+ });
25663
+ versions.set(gameDir, v);
25664
+ }
25665
+ return v;
25666
+ }
25667
+ var TakenTypes = /* @__PURE__ */ new Set(["mesh", "ent", "app", "mi", "mlsetup", "mlmask", "xbm"]);
25668
+ var ReadTypes = /* @__PURE__ */ new Set(["mesh", "ent", "app", "mi", "mlsetup"]);
25669
+ var SharedFolders = /^(engine|(base|ep1)\\(surfaces|materials|fx))\\/i;
25670
+ var typeOf = (p) => path3.win32.extname(p).slice(1).toLowerCase();
25671
+ function assetTakes(gamePath) {
25672
+ return TakenTypes.has(typeOf(gamePath)) && !SharedFolders.test(gamePath);
25673
+ }
25674
+ function safeGamePath(p) {
25675
+ const parts = p.split(/[\\/]+/);
25676
+ if (!parts.length || parts.some((s) => s === "" || s === "." || s === ".." || /[<>:"|?*\u0000-\u001f]/.test(s))) return void 0;
25677
+ return parts.join("\\");
25678
+ }
25679
+ function referencesIn(json) {
25680
+ const named = /* @__PURE__ */ new Set();
25681
+ let unnamed = 0;
25682
+ const walk = (v) => {
25683
+ if (Array.isArray(v)) {
25684
+ for (const x of v) walk(x);
25685
+ } else if (v && typeof v === "object") {
25686
+ const o = v;
25687
+ if (o.$type === "ResourcePath") {
25688
+ if (o.$storage === "string" && typeof o.$value === "string" && o.$value) named.add(o.$value);
25689
+ else if (o.$value && o.$value !== "0") unnamed++;
25690
+ return;
25691
+ }
25692
+ for (const k of Object.keys(o)) walk(o[k]);
25693
+ }
25694
+ };
25695
+ walk(json);
25696
+ return { named, unnamed };
25697
+ }
25698
+ function kindOf(gamePath) {
25699
+ const kind = typeOf(gamePath);
25700
+ if (!/^[a-z0-9_]+$/.test(kind)) throw new Error(`${gamePath} has no file type, so it cannot be a Library asset.`);
25701
+ return kind;
25702
+ }
25703
+ var projectFile = (dir, gamePath) => path3.join(dir, "source", "archive", ...gamePath.split("\\"));
25704
+ async function planAsset(b, source) {
25705
+ const main2 = safeGamePath(source.path);
25706
+ if (!main2) throw new Error(`Not a game path: ${source.path}`);
25707
+ kindOf(main2);
25708
+ const plan = { main: main2, take: [], stay: [], unread: [], badPaths: [], unnamed: 0 };
25709
+ const seen = /* @__PURE__ */ new Set();
25710
+ const queue = [main2];
25711
+ while (queue.length) {
25712
+ const p = queue.shift();
25713
+ const key = p.toLowerCase();
25714
+ if (seen.has(key)) continue;
25715
+ seen.add(key);
25716
+ if (p !== main2 && !assetTakes(p)) {
25717
+ plan.stay.push(p);
25718
+ continue;
25719
+ }
25720
+ plan.take.push(p);
25721
+ if (!ReadTypes.has(typeOf(p))) continue;
25722
+ let text;
25723
+ try {
25724
+ const own = source.project && projectFile(source.project.dir, p);
25725
+ text = (own && fs3.existsSync(own) ? await b.fileToJson(own) : await b.toJson(p)).json;
25726
+ } catch (e) {
25727
+ if (p === main2) throw e;
25728
+ plan.unread.push(p);
25729
+ continue;
25730
+ }
25731
+ if (text === null) {
25732
+ plan.unread.push(p);
25733
+ continue;
25734
+ }
25735
+ const refs = referencesIn(JSON.parse(text));
25736
+ plan.unnamed += refs.unnamed;
25737
+ for (const r of refs.named) {
25738
+ const safe = safeGamePath(r);
25739
+ if (safe) queue.push(safe);
25740
+ else plan.badPaths.push(r);
25741
+ }
25742
+ }
25743
+ plan.stay.sort();
25744
+ return plan;
25745
+ }
25746
+ var ItemExists = class extends Error {
25747
+ constructor(item) {
25748
+ super(`${path3.basename(item)} is already in the Library: ${item}`);
25749
+ this.item = item;
25750
+ }
25751
+ item;
25752
+ };
25753
+ function itemFolder(root, folder, main2, gamePathFolders) {
25754
+ const gameFolders = gamePathFolders ? main2.split("\\").slice(0, -1) : [];
25755
+ return path3.join(root, ...safeFolder(folder).split("/").filter(Boolean), ...gameFolders, itemName(main2));
25756
+ }
25757
+ var stemOf = (main2) => path3.win32.basename(main2, path3.win32.extname(main2));
25758
+ function itemName(main2) {
25759
+ return `${stemOf(main2).toLowerCase()}.${kindOf(main2)}.asset`;
25760
+ }
25761
+ function freeFolder(wanted) {
25762
+ const parent = path3.dirname(wanted);
25763
+ const m = /^(.*)(\.[^.]+\.asset)$/.exec(path3.basename(wanted));
25764
+ for (let n = 2; ; n++) {
25765
+ const next = path3.join(parent, `${m[1]}_${n}${m[2]}`);
25766
+ if (!fs3.existsSync(next)) return next;
25767
+ }
25768
+ }
25769
+ function readAssetJson(item) {
25770
+ try {
25771
+ return JSON.parse(fs3.readFileSync(path3.join(item, "asset.json"), "utf8"));
25772
+ } catch {
25773
+ return {};
25774
+ }
25775
+ }
25776
+ function filesUnder(dir) {
25777
+ return fs3.readdirSync(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? filesUnder(path3.join(dir, e.name)) : [path3.join(dir, e.name)]);
25778
+ }
25779
+ async function renameFolder(from, to, waits = [50, 100, 200, 400, 800, 1600, 3200, 6400]) {
25780
+ for (let i = 0; ; i++) {
25781
+ try {
25782
+ fs3.renameSync(from, to);
25783
+ return;
25784
+ } catch (e) {
25785
+ const code = e.code;
25786
+ if (code !== "EPERM" && code !== "EACCES" && code !== "EBUSY") throw e;
25787
+ if (i >= waits.length) throw new Error(`Could not move ${from} to ${to}: something has a file in it open, such as an antivirus or the search indexer. Try again in a moment. (${code})`);
25788
+ await new Promise((resolve4) => setTimeout(resolve4, waits[i]));
25789
+ }
25790
+ }
25791
+ }
25792
+ async function writeAsset(b, source, plan, opts) {
25793
+ const root = path3.resolve(opts.root);
25794
+ if (opts.gameDir && isInside(root, opts.gameDir)) throw new Error(`The Library must not be inside the game's folder: ${root}`);
25795
+ let item = itemFolder(root, opts.folder, plan.main, opts.gamePathFolders);
25796
+ const had = fs3.existsSync(item);
25797
+ if (had && (opts.ifExists ?? "stop") === "stop") throw new ItemExists(item);
25798
+ if (had && opts.ifExists === "another") item = freeFolder(item);
25799
+ const replacing = had && opts.ifExists === "replace" ? readAssetJson(item) : void 0;
25800
+ const parent = path3.dirname(item);
25801
+ fs3.mkdirSync(parent, { recursive: true });
25802
+ const tag = (0, import_node_crypto.randomBytes)(6).toString("hex");
25803
+ const building = path3.join(parent, `.redcode-add-${tag}`);
25804
+ const archive = path3.join(building, "source", "archive");
25805
+ try {
25806
+ fs3.mkdirSync(archive, { recursive: true });
25807
+ const out = (p) => path3.join(archive, ...p.split("\\"));
25808
+ const fromGame = [];
25809
+ let files = 0;
25810
+ let bytes = 0;
25811
+ for (const p of plan.take) {
25812
+ const own = source.project && projectFile(source.project.dir, p);
25813
+ if (own && fs3.existsSync(own)) {
25814
+ fs3.mkdirSync(path3.dirname(out(p)), { recursive: true });
25815
+ fs3.copyFileSync(own, out(p));
25816
+ files++;
25817
+ bytes += fs3.statSync(own).size;
25818
+ } else {
25819
+ fromGame.push(p);
25820
+ }
25821
+ }
25822
+ let missing = [];
25823
+ if (fromGame.length) {
25824
+ const got = await b.extractMany(fromGame.map((p) => ({ path: p, outFile: out(p) })));
25825
+ files += got.extracted;
25826
+ bytes += got.bytes;
25827
+ missing = got.failed;
25828
+ }
25829
+ if (missing.some((m) => m.toLowerCase() === plan.main.toLowerCase())) throw new Error(`Could not get ${plan.main} from the game.`);
25830
+ const stem = stemOf(plan.main);
25831
+ const inGame = !source.project || !fs3.existsSync(projectFile(source.project.dir, plan.main)) || await existsInGame(b, plan.main);
25832
+ const entry = { tool: opts.tool, did: replacing ? source.project ? `Replaced from project ${source.project.name}` : "Replaced from the game" : source.project ? `Added from project ${source.project.name}` : "Added from the game", date: (/* @__PURE__ */ new Date()).toISOString() };
25833
+ const asset = {
25834
+ format: 1,
25835
+ id: replacing?.id ?? (0, import_node_crypto.randomBytes)(8).toString("hex"),
25836
+ name: replacing?.name ?? stem,
25837
+ kind: kindOf(plan.main),
25838
+ main: plan.main,
25839
+ version: (replacing?.version ?? 0) + 1,
25840
+ from: source.project ? { project: source.project.name, path: plan.main } : { game: plan.main, gameVersion: opts.gameVersion ?? null },
25841
+ // The asset's own terms, in the shape EDGEfab's assets have when they are not the game's.
25842
+ license: inGame ? "game" : { source: "own", note: "From a REDcode project: record its source and terms before releasing it." },
25843
+ history: [...replacing?.history ?? [], entry]
25844
+ };
25845
+ fs3.writeFileSync(path3.join(building, "asset.json"), JSON.stringify(asset, null, 2) + "\n");
25846
+ const longPaths = filesUnder(building).filter((f) => item.length + f.length - building.length > 259).length;
25847
+ if (replacing) {
25848
+ const old = path3.join(parent, `.redcode-old-${tag}`);
25849
+ await renameFolder(item, old);
25850
+ try {
25851
+ await renameFolder(building, item);
25852
+ } catch (e) {
25853
+ await renameFolder(old, item);
25854
+ throw e;
25855
+ }
25856
+ fs3.rmSync(old, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
25857
+ } else {
25858
+ await renameFolder(building, item);
25859
+ }
25860
+ const relative3 = path3.relative(root, item).split(path3.sep).join("/");
25861
+ return { item, relative: relative3, id: asset.id, kind: asset.kind, main: plan.main, version: asset.version, files, bytes, stay: plan.stay, missing, unread: plan.unread, badPaths: plan.badPaths, unnamed: plan.unnamed, longPaths };
25862
+ } finally {
25863
+ fs3.rmSync(building, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
25864
+ }
25865
+ }
25866
+ async function existsInGame(b, gamePath) {
25867
+ return b.toJson(gamePath).then(
25868
+ () => true,
25869
+ () => false
25870
+ );
25871
+ }
25872
+
25873
+ // ../../extensions/redcode/src/project.ts
25874
+ var fs4 = __toESM(require("node:fs"));
25875
+ var path4 = __toESM(require("node:path"));
25876
+ var ProjectFolders = {
25877
+ archive: path4.join("source", "archive"),
25878
+ raw: path4.join("source", "raw"),
25879
+ resources: path4.join("source", "resources"),
25880
+ /** REDmod custom sounds, named in source/resources/info.json. Made by the tool that writes the first one, not by a new project. */
25881
+ customSounds: path4.join("source", "customSounds"),
25882
+ /** EDGEbuilder's scenes (<name>.scene.json, REDcode issue #2). Made by EDGEbuilder; not packed. */
25883
+ scenes: path4.join("source", "scenes"),
25884
+ packed: "packed"
25885
+ };
25886
+ function findProject(dir) {
25887
+ let entries;
25888
+ try {
25889
+ entries = fs4.readdirSync(dir);
25890
+ } catch {
25891
+ return void 0;
25892
+ }
25893
+ const file = entries.find((e) => e.toLowerCase().endsWith(".cpmodproj"));
25894
+ return file ? readProject(path4.join(dir, file)) : void 0;
25895
+ }
25896
+ function readProject(file) {
25897
+ const xml = fs4.readFileSync(file, "utf8");
25898
+ const get = (tag) => {
25899
+ const m = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
25900
+ return m ? unescapeXml(m[1].trim()) : "";
25901
+ };
25902
+ const name = get("Name") || path4.basename(file, path4.extname(file));
25903
+ return {
25904
+ file,
25905
+ dir: path4.dirname(file),
25906
+ name,
25907
+ modName: get("ModName") || name,
25908
+ author: get("Author"),
25909
+ email: get("Email"),
25910
+ description: get("Description"),
25911
+ version: get("Version") || "1.0.0"
25912
+ };
25913
+ }
25914
+ function gamePathOf(project, file) {
25915
+ const root = path4.join(project.dir, ProjectFolders.archive) + path4.sep;
25916
+ return file.startsWith(root) ? file.slice(root.length).split(path4.sep).join("\\") : void 0;
25917
+ }
25918
+ function unescapeXml(s) {
25919
+ return s.replace(/&quot;/g, '"').replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&");
25920
+ }
25921
+
25535
25922
  // src/server.ts
25536
- var Version = "0.4.0";
25923
+ var Version = "0.5.0";
25537
25924
  function arg(name) {
25538
25925
  const i = process.argv.indexOf(name);
25539
25926
  return i >= 0 ? process.argv[i + 1] || void 0 : void 0;
@@ -25542,11 +25929,11 @@ function env(name) {
25542
25929
  return process.env[name]?.trim() || void 0;
25543
25930
  }
25544
25931
  function vscodeSetting(key) {
25545
- const base = process.platform === "win32" ? process.env.APPDATA : process.platform === "darwin" ? path3.join(process.env.HOME ?? "", "Library/Application Support") : path3.join(process.env.HOME ?? "", ".config");
25932
+ const base = process.platform === "win32" ? process.env.APPDATA : process.platform === "darwin" ? path5.join(process.env.HOME ?? "", "Library/Application Support") : path5.join(process.env.HOME ?? "", ".config");
25546
25933
  if (!base) return void 0;
25547
- const file = path3.join(base, "Code", "User", "settings.json");
25934
+ const file = path5.join(base, "Code", "User", "settings.json");
25548
25935
  try {
25549
- const text = fs3.readFileSync(file, "utf8");
25936
+ const text = fs5.readFileSync(file, "utf8");
25550
25937
  const match = text.match(new RegExp(`"${key.replace(/\./g, "\\.")}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`));
25551
25938
  return match ? JSON.parse(`"${match[1]}"`) || void 0 : void 0;
25552
25939
  } catch {
@@ -25555,8 +25942,8 @@ function vscodeSetting(key) {
25555
25942
  }
25556
25943
  function resolveBridgeExe() {
25557
25944
  let exe = arg("--bridge") ?? env("REDBRIDGE_EXE") ?? vscodeSetting("redcode.bridgePath") ?? "REDbridge.exe";
25558
- if (exe && fs3.existsSync(exe) && fs3.statSync(exe).isDirectory()) {
25559
- exe = path3.join(exe, "REDbridge.exe");
25945
+ if (exe && fs5.existsSync(exe) && fs5.statSync(exe).isDirectory()) {
25946
+ exe = path5.join(exe, "REDbridge.exe");
25560
25947
  }
25561
25948
  return exe;
25562
25949
  }
@@ -25688,8 +26075,47 @@ tool("project_import", "Import a raw file under a project's source/raw back into
25688
26075
  rawDir: external_exports.string().describe("The project's source/raw folder."),
25689
26076
  archiveDir: external_exports.string().describe("The project's source/archive folder."),
25690
26077
  keep: external_exports.boolean().optional().describe("Keep the replaced file's settings; default true."),
25691
- template: external_exports.string().optional().describe("For a GLB with no mesh to import over: a game path (base\\...mesh) or a .mesh file to start the new mesh from.")
25692
- }, (a, b) => b.importFile(a.file, a.rawDir, a.archiveDir, a.keep ?? true, a.template));
26078
+ template: external_exports.string().optional().describe("For a GLB with no mesh to import over: a game path (base\\...mesh) or a .mesh file to start the new mesh from."),
26079
+ texture: external_exports.union([
26080
+ external_exports.string(),
26081
+ external_exports.object({
26082
+ group: external_exports.string().optional(),
26083
+ compression: external_exports.string().optional(),
26084
+ rawFormat: external_exports.string().optional(),
26085
+ isGamma: external_exports.boolean().optional(),
26086
+ hasMipchain: external_exports.boolean().optional(),
26087
+ isStreamable: external_exports.boolean().optional()
26088
+ })
26089
+ ]).optional().describe("For an image import: the .xbm's setup. A preset (color, normal, grayscale), a texture group name (TEXG_Generic_UI), or { group, compression, rawFormat, isGamma, hasMipchain, isStreamable } with the game's enum names, the rest from the group's defaults. Wins over the existing texture's setup; the answer's texture is the setup read back. Bridge 0.12.0.")
26090
+ }, (a, b) => b.importFile(a.file, a.rawDir, a.archiveDir, a.keep ?? true, a.template, a.texture));
26091
+ tool("library_add", "Add a game file or a project's file to the EDGE Library, the modder's store shared by every EDGE tool, as one asset item: <name>.<kind>.asset with asset.json and source/archive holding the file and the files it needs (a mesh's materials and textures; an entity's appearances and meshes), at their game paths. Game-wide files (base materials, templates, surfaces, effects, rigs) stay in the game and are referred to. Returns the item's folder, id, version, the files copied, those that stay, and those it names that are not in the game (the game's own files name some such). Writes outside the game, in the modder's Library only. With no Library yet, it answers where one would go: ask the modder, then call again with root. With no folder, it answers the modder's folders: ask which, or pass the folder, since the folders are theirs and tools never pick one on their own.", {
26092
+ path: external_exports.string().describe("A game path (base\\...\\comfy_rocker.mesh), or the full path of a file under a WolvenKit project's source/archive, whose project copies then win over the game's."),
26093
+ folder: external_exports.string().optional().describe("A folder under the Library's root, with /, for example Production/Meshes; made if missing. An empty string is the root itself."),
26094
+ gamePathFolders: external_exports.boolean().optional().describe("Puts the item in its game path's folders under the folder, each made if missing: <folder>/base/.../comfy_rocker.mesh.asset, so the Library browses like the game's files. The modder's choice."),
26095
+ root: external_exports.string().optional().describe("Only when there is no Library yet: where to make it, written to %APPDATA%\\EDGE\\library.json for every EDGE tool. Ask the modder first."),
26096
+ ifExists: external_exports.enum(["stop", "replace", "another"]).optional().describe("When the item is there already: stop (default), another (a second item beside it), or replace (a new copy, version raised, id kept; anything changed in the item since is lost, so ask the modder first)."),
26097
+ allowLarge: external_exports.boolean().optional().describe("Copy more than 500 files. An entity's walk can reach that; check with the modder first.")
26098
+ }, async (a, b) => {
26099
+ const known = readLibraryRoot();
26100
+ if (a.root && known) throw new Error(`The EDGE Library is already at ${known}. Leave root out; moving it is Relocate, in EDGEbuilder.`);
26101
+ if (!a.root && !known) return { needsRoot: true, suggested: suggestedLibraryRoot(), note: "No EDGE Library yet. Ask the modder where it should go, then call again with root." };
26102
+ const info = await b.info();
26103
+ if (a.root && info.gameDir && isInside(path5.resolve(a.root), info.gameDir)) throw new Error("The EDGE Library cannot be inside the game's folder.");
26104
+ const root = known ?? writeLibraryRoot(a.root);
26105
+ if (a.folder === void 0) return { needsFolder: true, root, folders: libraryFolders(root), note: "Ask the modder which folder, or for a new one, then call again with folder (an empty string is the root)." };
26106
+ const source = { path: a.path };
26107
+ if (path5.isAbsolute(a.path)) {
26108
+ const m = /^(.*?)[\\/]source[\\/]archive[\\/]/i.exec(a.path);
26109
+ const project = m ? findProject(m[1]) : void 0;
26110
+ const gamePath = project && gamePathOf(project, path5.normalize(a.path));
26111
+ if (!project || !gamePath) throw new Error(`${a.path} is not under a WolvenKit project's source/archive.`);
26112
+ source.path = gamePath;
26113
+ source.project = { name: project.name, dir: project.dir };
26114
+ }
26115
+ const plan = await planAsset(b, source);
26116
+ if (plan.take.length > 500 && !a.allowLarge) throw new Error(`${plan.main} needs ${plan.take.length} files. Check with the modder, then pass allowLarge.`);
26117
+ return writeAsset(b, source, plan, { root, folder: a.folder, gamePathFolders: a.gamePathFolders, ifExists: a.ifExists, tool: `redcode-mcp ${Version}`, gameVersion: await gameVersionOf(info.gameDir), gameDir: info.gameDir });
26118
+ });
25693
26119
  tool("project_pack", "Pack a folder (a project's source/archive) into <modName>.archive in outDir through WolvenKit's packer. Returns the archive path, size, file count and warnings for paths with capital letters or spaces. Bridge 0.6.1.", {
25694
26120
  dir: external_exports.string().describe("The folder to pack, laid out as game paths (base/..., ep1/...)."),
25695
26121
  outDir: external_exports.string().describe("Where the archive goes, usually packed/archive/pc/mod."),
@@ -25750,13 +26176,13 @@ tool("loc_get", `The texts for LocKeys, by number or secondary key, with or with
25750
26176
  language: external_exports.string().optional(),
25751
26177
  projectDir: external_exports.string().optional()
25752
26178
  }, (a, b) => b.locGet(a.keys, a.language ?? "en-us", a.projectDir));
25753
- var soundCache = path3.join(os.tmpdir(), "redcode-mcp", "sounds");
26179
+ var soundCache = path5.join(os2.tmpdir(), "redcode-mcp", "sounds");
25754
26180
  var soundIndex;
25755
26181
  async function gameSounds(b) {
25756
26182
  if (!soundIndex) {
25757
- const file = path3.join(soundCache, "sfx_container.opusinfo");
25758
- if (!fs3.existsSync(file)) await b.extract(GameSoundIndex, file);
25759
- soundIndex = parseOpusInfo(fs3.readFileSync(file));
26183
+ const file = path5.join(soundCache, "sfx_container.opusinfo");
26184
+ if (!fs5.existsSync(file)) await b.extract(GameSoundIndex, file);
26185
+ soundIndex = parseOpusInfo(fs5.readFileSync(file));
25760
26186
  }
25761
26187
  return soundIndex;
25762
26188
  }
@@ -25796,12 +26222,12 @@ tool("sound_extract", "Write game sound effects as .opus files (Ogg Opus, 48 kHz
25796
26222
  } else {
25797
26223
  throw new Error("event or sounds is required");
25798
26224
  }
25799
- fs3.mkdirSync(a.outDir, { recursive: true });
26225
+ fs5.mkdirSync(a.outDir, { recursive: true });
25800
26226
  const written = [];
25801
26227
  for (const i of picks) {
25802
26228
  const bytes = await opusSound(b, soundCache, { gamePath: GameSoundIndex }, index, i);
25803
- const file = path3.join(a.outDir, `${index.hashes[i]}.opus`);
25804
- fs3.writeFileSync(file, bytes);
26229
+ const file = path5.join(a.outDir, `${index.hashes[i]}.opus`);
26230
+ fs5.writeFileSync(file, bytes);
25805
26231
  const f = audioFormat(bytes);
25806
26232
  written.push({ file, bytes: bytes.length, channels: f.channels });
25807
26233
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgemodders/redcode-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Cyberpunk 2077 modding tools for AI assistants over MCP: the game archives, CR2W files as JSON and node-by-node edits, textures, TweakDB, game sounds, export, import and pack, through REDbridge and WolvenKit's engine.",
5
5
  "license": "MIT",
6
6
  "author": "Edgemodders",