ed-mcp 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -10
- package/dist/index.js +1499 -282
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -103,7 +103,7 @@ var require_code = __commonJS({
|
|
|
103
103
|
}
|
|
104
104
|
exports._ = _;
|
|
105
105
|
var plus = new _Code("+");
|
|
106
|
-
function
|
|
106
|
+
function str6(strs, ...args) {
|
|
107
107
|
const expr = [safeStringify(strs[0])];
|
|
108
108
|
let i = 0;
|
|
109
109
|
while (i < args.length) {
|
|
@@ -114,7 +114,7 @@ var require_code = __commonJS({
|
|
|
114
114
|
optimize(expr);
|
|
115
115
|
return new _Code(expr);
|
|
116
116
|
}
|
|
117
|
-
exports.str =
|
|
117
|
+
exports.str = str6;
|
|
118
118
|
function addCodeArg(code, arg) {
|
|
119
119
|
if (arg instanceof _Code)
|
|
120
120
|
code.push(...arg._items);
|
|
@@ -157,7 +157,7 @@ var require_code = __commonJS({
|
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
159
|
function strConcat(c1, c2) {
|
|
160
|
-
return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 :
|
|
160
|
+
return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str6`${c1}${c2}`;
|
|
161
161
|
}
|
|
162
162
|
exports.strConcat = strConcat;
|
|
163
163
|
function interpolate(x) {
|
|
@@ -1119,22 +1119,22 @@ var require_util = __commonJS({
|
|
|
1119
1119
|
return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`;
|
|
1120
1120
|
}
|
|
1121
1121
|
exports.schemaRefOrVal = schemaRefOrVal;
|
|
1122
|
-
function unescapeFragment(
|
|
1123
|
-
return unescapeJsonPointer(decodeURIComponent(
|
|
1122
|
+
function unescapeFragment(str6) {
|
|
1123
|
+
return unescapeJsonPointer(decodeURIComponent(str6));
|
|
1124
1124
|
}
|
|
1125
1125
|
exports.unescapeFragment = unescapeFragment;
|
|
1126
|
-
function escapeFragment(
|
|
1127
|
-
return encodeURIComponent(escapeJsonPointer(
|
|
1126
|
+
function escapeFragment(str6) {
|
|
1127
|
+
return encodeURIComponent(escapeJsonPointer(str6));
|
|
1128
1128
|
}
|
|
1129
1129
|
exports.escapeFragment = escapeFragment;
|
|
1130
|
-
function escapeJsonPointer(
|
|
1131
|
-
if (typeof
|
|
1132
|
-
return `${
|
|
1133
|
-
return
|
|
1130
|
+
function escapeJsonPointer(str6) {
|
|
1131
|
+
if (typeof str6 == "number")
|
|
1132
|
+
return `${str6}`;
|
|
1133
|
+
return str6.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1134
1134
|
}
|
|
1135
1135
|
exports.escapeJsonPointer = escapeJsonPointer;
|
|
1136
|
-
function unescapeJsonPointer(
|
|
1137
|
-
return
|
|
1136
|
+
function unescapeJsonPointer(str6) {
|
|
1137
|
+
return str6.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
1138
1138
|
}
|
|
1139
1139
|
exports.unescapeJsonPointer = unescapeJsonPointer;
|
|
1140
1140
|
function eachItem(xs, f) {
|
|
@@ -2159,8 +2159,8 @@ var require_json_schema_traverse = __commonJS({
|
|
|
2159
2159
|
post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
|
|
2160
2160
|
}
|
|
2161
2161
|
}
|
|
2162
|
-
function escapeJsonPtr(
|
|
2163
|
-
return
|
|
2162
|
+
function escapeJsonPtr(str6) {
|
|
2163
|
+
return str6.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2164
2164
|
}
|
|
2165
2165
|
}
|
|
2166
2166
|
});
|
|
@@ -3257,10 +3257,10 @@ var require_utils = __commonJS({
|
|
|
3257
3257
|
isIPV6: true
|
|
3258
3258
|
};
|
|
3259
3259
|
}
|
|
3260
|
-
function findToken(
|
|
3260
|
+
function findToken(str6, token) {
|
|
3261
3261
|
let ind = 0;
|
|
3262
|
-
for (let i = 0; i <
|
|
3263
|
-
if (
|
|
3262
|
+
for (let i = 0; i < str6.length; i++) {
|
|
3263
|
+
if (str6[i] === token) ind++;
|
|
3264
3264
|
}
|
|
3265
3265
|
return ind;
|
|
3266
3266
|
}
|
|
@@ -4330,7 +4330,7 @@ var require_core = __commonJS({
|
|
|
4330
4330
|
var util_1 = require_util();
|
|
4331
4331
|
var $dataRefSchema = require_data();
|
|
4332
4332
|
var uri_1 = require_uri();
|
|
4333
|
-
var defaultRegExp = (
|
|
4333
|
+
var defaultRegExp = (str6, flags) => new RegExp(str6, flags);
|
|
4334
4334
|
defaultRegExp.code = "new RegExp";
|
|
4335
4335
|
var META_IGNORE_OPTIONS = ["removeAdditional", "useDefaults", "coerceTypes"];
|
|
4336
4336
|
var EXT_SCOPE_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -5125,16 +5125,16 @@ var require_ucs2length = __commonJS({
|
|
|
5125
5125
|
"node_modules/ajv/dist/runtime/ucs2length.js"(exports) {
|
|
5126
5126
|
"use strict";
|
|
5127
5127
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5128
|
-
function ucs2length(
|
|
5129
|
-
const len =
|
|
5128
|
+
function ucs2length(str6) {
|
|
5129
|
+
const len = str6.length;
|
|
5130
5130
|
let length = 0;
|
|
5131
5131
|
let pos = 0;
|
|
5132
5132
|
let value;
|
|
5133
5133
|
while (pos < len) {
|
|
5134
5134
|
length++;
|
|
5135
|
-
value =
|
|
5135
|
+
value = str6.charCodeAt(pos++);
|
|
5136
5136
|
if (value >= 55296 && value <= 56319 && pos < len) {
|
|
5137
|
-
value =
|
|
5137
|
+
value = str6.charCodeAt(pos);
|
|
5138
5138
|
if ((value & 64512) === 56320)
|
|
5139
5139
|
pos++;
|
|
5140
5140
|
}
|
|
@@ -7017,8 +7017,8 @@ var require_formats = __commonJS({
|
|
|
7017
7017
|
}
|
|
7018
7018
|
var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
|
|
7019
7019
|
var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
7020
|
-
function date5(
|
|
7021
|
-
const matches2 = DATE.exec(
|
|
7020
|
+
function date5(str6) {
|
|
7021
|
+
const matches2 = DATE.exec(str6);
|
|
7022
7022
|
if (!matches2)
|
|
7023
7023
|
return false;
|
|
7024
7024
|
const year = +matches2[1];
|
|
@@ -7037,8 +7037,8 @@ var require_formats = __commonJS({
|
|
|
7037
7037
|
}
|
|
7038
7038
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
7039
7039
|
function getTime(strictTimeZone) {
|
|
7040
|
-
return function time3(
|
|
7041
|
-
const matches2 = TIME.exec(
|
|
7040
|
+
return function time3(str6) {
|
|
7041
|
+
const matches2 = TIME.exec(str6);
|
|
7042
7042
|
if (!matches2)
|
|
7043
7043
|
return false;
|
|
7044
7044
|
const hr = +matches2[1];
|
|
@@ -7084,8 +7084,8 @@ var require_formats = __commonJS({
|
|
|
7084
7084
|
var DATE_TIME_SEPARATOR = /t|\s/i;
|
|
7085
7085
|
function getDateTime(strictTimeZone) {
|
|
7086
7086
|
const time3 = getTime(strictTimeZone);
|
|
7087
|
-
return function date_time(
|
|
7088
|
-
const dateTime =
|
|
7087
|
+
return function date_time(str6) {
|
|
7088
|
+
const dateTime = str6.split(DATE_TIME_SEPARATOR);
|
|
7089
7089
|
return dateTime.length === 2 && date5(dateTime[0]) && time3(dateTime[1]);
|
|
7090
7090
|
};
|
|
7091
7091
|
}
|
|
@@ -7110,13 +7110,13 @@ var require_formats = __commonJS({
|
|
|
7110
7110
|
}
|
|
7111
7111
|
var NOT_URI_FRAGMENT = /\/|:/;
|
|
7112
7112
|
var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
|
|
7113
|
-
function uri(
|
|
7114
|
-
return NOT_URI_FRAGMENT.test(
|
|
7113
|
+
function uri(str6) {
|
|
7114
|
+
return NOT_URI_FRAGMENT.test(str6) && URI.test(str6);
|
|
7115
7115
|
}
|
|
7116
7116
|
var BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
|
|
7117
|
-
function byte(
|
|
7117
|
+
function byte(str6) {
|
|
7118
7118
|
BYTE.lastIndex = 0;
|
|
7119
|
-
return BYTE.test(
|
|
7119
|
+
return BYTE.test(str6);
|
|
7120
7120
|
}
|
|
7121
7121
|
var MIN_INT32 = -(2 ** 31);
|
|
7122
7122
|
var MAX_INT32 = 2 ** 31 - 1;
|
|
@@ -7130,11 +7130,11 @@ var require_formats = __commonJS({
|
|
|
7130
7130
|
return true;
|
|
7131
7131
|
}
|
|
7132
7132
|
var Z_ANCHOR = /[^\\]\\Z/;
|
|
7133
|
-
function regex(
|
|
7134
|
-
if (Z_ANCHOR.test(
|
|
7133
|
+
function regex(str6) {
|
|
7134
|
+
if (Z_ANCHOR.test(str6))
|
|
7135
7135
|
return false;
|
|
7136
7136
|
try {
|
|
7137
|
-
new RegExp(
|
|
7137
|
+
new RegExp(str6);
|
|
7138
7138
|
return true;
|
|
7139
7139
|
} catch (e) {
|
|
7140
7140
|
return false;
|
|
@@ -8095,14 +8095,14 @@ function promiseAllObject(promisesObj) {
|
|
|
8095
8095
|
}
|
|
8096
8096
|
function randomString(length = 10) {
|
|
8097
8097
|
const chars = "abcdefghijklmnopqrstuvwxyz";
|
|
8098
|
-
let
|
|
8098
|
+
let str6 = "";
|
|
8099
8099
|
for (let i = 0; i < length; i++) {
|
|
8100
|
-
|
|
8100
|
+
str6 += chars[Math.floor(Math.random() * chars.length)];
|
|
8101
8101
|
}
|
|
8102
|
-
return
|
|
8102
|
+
return str6;
|
|
8103
8103
|
}
|
|
8104
|
-
function esc(
|
|
8105
|
-
return JSON.stringify(
|
|
8104
|
+
function esc(str6) {
|
|
8105
|
+
return JSON.stringify(str6);
|
|
8106
8106
|
}
|
|
8107
8107
|
function slugify(input2) {
|
|
8108
8108
|
return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -8216,8 +8216,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
|
|
|
8216
8216
|
"symbol",
|
|
8217
8217
|
"undefined"
|
|
8218
8218
|
]);
|
|
8219
|
-
function escapeRegex(
|
|
8220
|
-
return
|
|
8219
|
+
function escapeRegex(str6) {
|
|
8220
|
+
return str6.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8221
8221
|
}
|
|
8222
8222
|
function clone(inst, def, params) {
|
|
8223
8223
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
@@ -8483,13 +8483,13 @@ function getSizableOrigin(input2) {
|
|
|
8483
8483
|
return "unknown";
|
|
8484
8484
|
}
|
|
8485
8485
|
var highSurrogate = /[\uD800-\uDBFF]/;
|
|
8486
|
-
function codePointLength(
|
|
8487
|
-
const units =
|
|
8488
|
-
if (!highSurrogate.test(
|
|
8486
|
+
function codePointLength(str6) {
|
|
8487
|
+
const units = str6.length;
|
|
8488
|
+
if (!highSurrogate.test(str6))
|
|
8489
8489
|
return units;
|
|
8490
8490
|
let count = units;
|
|
8491
8491
|
for (let i = 0; i < units - 1; i++) {
|
|
8492
|
-
if ((
|
|
8492
|
+
if ((str6.charCodeAt(i) & 64512) === 55296 && (str6.charCodeAt(i + 1) & 64512) === 56320) {
|
|
8493
8493
|
count--;
|
|
8494
8494
|
i++;
|
|
8495
8495
|
}
|
|
@@ -28752,7 +28752,7 @@ async function canonical(p) {
|
|
|
28752
28752
|
}
|
|
28753
28753
|
|
|
28754
28754
|
// src/version.ts
|
|
28755
|
-
var VERSION = "0.
|
|
28755
|
+
var VERSION = "0.6.0";
|
|
28756
28756
|
|
|
28757
28757
|
// src/config.ts
|
|
28758
28758
|
var ConfigError = class extends Error {
|
|
@@ -28817,6 +28817,7 @@ function loadConfig(env2, dirs, platform = process.platform) {
|
|
|
28817
28817
|
legacyPlansEnv: get(env2, "ED_MCP_PLANS"),
|
|
28818
28818
|
plansMarkdownDir: path3.join(dataDir, "plans"),
|
|
28819
28819
|
prefsFile: path3.join(dataDir, "preferences.json"),
|
|
28820
|
+
activityFile: path3.join(dataDir, "activity.json"),
|
|
28820
28821
|
secretsFile: syncPassword ? path3.join(dataDir, "secrets.enc") : path3.join(dirs.config, "secrets.json"),
|
|
28821
28822
|
locksDir: path3.join(dirs.config, "locks"),
|
|
28822
28823
|
cacheDir: dirs.cache,
|
|
@@ -29754,8 +29755,8 @@ var Catalog = class {
|
|
|
29754
29755
|
).join(", ")}`
|
|
29755
29756
|
);
|
|
29756
29757
|
}
|
|
29757
|
-
const
|
|
29758
|
-
return { ...m, same_group:
|
|
29758
|
+
const same6 = m.trade_group ? this.d.materials.filter((x) => x.kind === m.kind && x.trade_group === m.trade_group).sort((a, b) => a.grade - b.grade) : [m];
|
|
29759
|
+
return { ...m, same_group: same6, trade_rules: TRADE_RULES };
|
|
29759
29760
|
}
|
|
29760
29761
|
/** Coriolis feature ranges, [min, max], for a blueprint id at a grade on a module group, or null. */
|
|
29761
29762
|
blueprintFeatures(blueprintId2, grade, grp) {
|
|
@@ -31701,6 +31702,333 @@ function buildState(events) {
|
|
|
31701
31702
|
};
|
|
31702
31703
|
}
|
|
31703
31704
|
|
|
31705
|
+
// src/journal/bodies.ts
|
|
31706
|
+
var BODY_EVENTS = [
|
|
31707
|
+
"Scan",
|
|
31708
|
+
"FSSDiscoveryScan",
|
|
31709
|
+
"FSSAllBodiesFound",
|
|
31710
|
+
"FSSBodySignals",
|
|
31711
|
+
"SAASignalsFound",
|
|
31712
|
+
"SAAScanComplete",
|
|
31713
|
+
"ScanOrganic",
|
|
31714
|
+
"SellOrganicData",
|
|
31715
|
+
"Died"
|
|
31716
|
+
];
|
|
31717
|
+
var num3 = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
31718
|
+
var str3 = (v) => typeof v === "string" && v !== "" ? v : null;
|
|
31719
|
+
var objs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "object" && x !== null) : [];
|
|
31720
|
+
var same = (a, b) => a !== null && a.toLowerCase() === b.toLowerCase();
|
|
31721
|
+
function systemAddress(events, system) {
|
|
31722
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
31723
|
+
const e = events[i];
|
|
31724
|
+
if (same(str3(e.StarSystem) ?? str3(e.SystemName), system)) {
|
|
31725
|
+
const address = num3(e.SystemAddress);
|
|
31726
|
+
if (address !== null) return address;
|
|
31727
|
+
}
|
|
31728
|
+
}
|
|
31729
|
+
return null;
|
|
31730
|
+
}
|
|
31731
|
+
function signalsOf(e) {
|
|
31732
|
+
return objs2(e.Signals).map((s) => ({
|
|
31733
|
+
type: str3(s.Type_Localised) ?? str3(s.Type) ?? "unknown",
|
|
31734
|
+
count: num3(s.Count) ?? 0
|
|
31735
|
+
}));
|
|
31736
|
+
}
|
|
31737
|
+
function bioCount(e) {
|
|
31738
|
+
return objs2(e.Signals).filter((s) => typeof s.Type === "string" && s.Type.endsWith("SignalType_Biological;")).reduce((n, s) => n + (num3(s.Count) ?? 0), 0);
|
|
31739
|
+
}
|
|
31740
|
+
function bodyNames(events, address) {
|
|
31741
|
+
const names = /* @__PURE__ */ new Map();
|
|
31742
|
+
for (const e of events) {
|
|
31743
|
+
if (num3(e.SystemAddress) !== address) continue;
|
|
31744
|
+
const id = num3(e.BodyID);
|
|
31745
|
+
const name = str3(e.BodyName);
|
|
31746
|
+
if (id !== null && name) names.set(id, name);
|
|
31747
|
+
}
|
|
31748
|
+
return names;
|
|
31749
|
+
}
|
|
31750
|
+
function bodyMatches(name, system, wanted) {
|
|
31751
|
+
if (!name) return false;
|
|
31752
|
+
if (same(name, wanted)) return true;
|
|
31753
|
+
return system !== null && same(name.slice(system.length).trim(), wanted);
|
|
31754
|
+
}
|
|
31755
|
+
function exobiology(events, address, opts = {}) {
|
|
31756
|
+
const names = bodyNames(events, address);
|
|
31757
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
31758
|
+
const body = (id) => {
|
|
31759
|
+
let b = bodies.get(id);
|
|
31760
|
+
if (!b) bodies.set(id, b = { bio: null, genera: /* @__PURE__ */ new Map(), species: /* @__PURE__ */ new Map() });
|
|
31761
|
+
return b;
|
|
31762
|
+
};
|
|
31763
|
+
let unsold = 0;
|
|
31764
|
+
let active2 = null;
|
|
31765
|
+
const drop = () => {
|
|
31766
|
+
if (active2?.species && !active2.species.analysed) {
|
|
31767
|
+
active2.species.samples = 0;
|
|
31768
|
+
active2.species.stage = "0/3";
|
|
31769
|
+
active2.species.abandoned = true;
|
|
31770
|
+
}
|
|
31771
|
+
active2 = null;
|
|
31772
|
+
};
|
|
31773
|
+
for (const e of events) {
|
|
31774
|
+
switch (e.event) {
|
|
31775
|
+
case "SellOrganicData":
|
|
31776
|
+
unsold = 0;
|
|
31777
|
+
break;
|
|
31778
|
+
case "Died":
|
|
31779
|
+
unsold = 0;
|
|
31780
|
+
drop();
|
|
31781
|
+
break;
|
|
31782
|
+
case "ScanOrganic": {
|
|
31783
|
+
if (e.ScanType === "Analyse") unsold++;
|
|
31784
|
+
const key = `${num3(e.SystemAddress)}|${num3(e.Body)}|${str3(e.Species)}`;
|
|
31785
|
+
if (e.ScanType === "Log" && active2 && active2.key !== key) drop();
|
|
31786
|
+
active2 = e.ScanType === "Analyse" ? null : { key, species: active2?.key === key ? active2.species : void 0 };
|
|
31787
|
+
break;
|
|
31788
|
+
}
|
|
31789
|
+
}
|
|
31790
|
+
if (num3(e.SystemAddress) !== address) continue;
|
|
31791
|
+
switch (e.event) {
|
|
31792
|
+
case "FSSBodySignals":
|
|
31793
|
+
case "SAASignalsFound": {
|
|
31794
|
+
const id = num3(e.BodyID);
|
|
31795
|
+
if (id === null) break;
|
|
31796
|
+
const count = bioCount(e);
|
|
31797
|
+
if (count > 0 || bodies.has(id)) body(id).bio = count;
|
|
31798
|
+
for (const g of objs2(e.Genuses)) {
|
|
31799
|
+
const key = str3(g.Genus);
|
|
31800
|
+
if (key) body(id).genera.set(key, str3(g.Genus_Localised) ?? key);
|
|
31801
|
+
}
|
|
31802
|
+
break;
|
|
31803
|
+
}
|
|
31804
|
+
case "ScanOrganic": {
|
|
31805
|
+
const id = num3(e.Body);
|
|
31806
|
+
const key = str3(e.Species);
|
|
31807
|
+
if (id === null || !key) break;
|
|
31808
|
+
const b = body(id);
|
|
31809
|
+
const genusKey = str3(e.Genus) ?? "";
|
|
31810
|
+
const s = b.species.get(key) ?? {
|
|
31811
|
+
genus: str3(e.Genus_Localised) ?? genusKey,
|
|
31812
|
+
genusKey,
|
|
31813
|
+
species: str3(e.Species_Localised) ?? key,
|
|
31814
|
+
variant: null,
|
|
31815
|
+
samples: 0,
|
|
31816
|
+
stage: "",
|
|
31817
|
+
analysed: false
|
|
31818
|
+
};
|
|
31819
|
+
s.variant = str3(e.Variant_Localised) ?? s.variant;
|
|
31820
|
+
if (e.ScanType === "Log") {
|
|
31821
|
+
if (!s.analysed) s.samples = 1;
|
|
31822
|
+
delete s.abandoned;
|
|
31823
|
+
} else if (e.ScanType === "Sample") s.samples = Math.min(3, Math.max(2, s.samples + 1));
|
|
31824
|
+
else if (e.ScanType === "Analyse") {
|
|
31825
|
+
s.samples = 3;
|
|
31826
|
+
s.analysed = true;
|
|
31827
|
+
}
|
|
31828
|
+
s.stage = `${s.samples}/3`;
|
|
31829
|
+
b.species.set(key, s);
|
|
31830
|
+
if (active2 && e.ScanType !== "Analyse") active2.species = s;
|
|
31831
|
+
if (genusKey && !b.genera.has(genusKey)) b.genera.set(genusKey, s.genus);
|
|
31832
|
+
break;
|
|
31833
|
+
}
|
|
31834
|
+
}
|
|
31835
|
+
}
|
|
31836
|
+
const out = [];
|
|
31837
|
+
for (const [id, b] of bodies) {
|
|
31838
|
+
const name = names.get(id) ?? `Body ${id}`;
|
|
31839
|
+
if (opts.body && !bodyMatches(name, opts.system ?? null, opts.body)) continue;
|
|
31840
|
+
const sampled = new Set(
|
|
31841
|
+
[...b.species.values()].filter((s) => !s.abandoned).map((s) => s.genusKey)
|
|
31842
|
+
);
|
|
31843
|
+
out.push({
|
|
31844
|
+
body: name,
|
|
31845
|
+
body_id: id,
|
|
31846
|
+
bio_signals: b.bio,
|
|
31847
|
+
genera: [...b.genera.values()],
|
|
31848
|
+
species: [...b.species.values()].map(({ genusKey: _, ...s }) => s),
|
|
31849
|
+
not_started: [...b.genera].filter(([k]) => !sampled.has(k)).map(([, v]) => v)
|
|
31850
|
+
});
|
|
31851
|
+
}
|
|
31852
|
+
out.sort((a, b) => a.body_id - b.body_id);
|
|
31853
|
+
return { bodies: out, unsold_analysed: unsold };
|
|
31854
|
+
}
|
|
31855
|
+
function exploration(events, address) {
|
|
31856
|
+
const discovery = {
|
|
31857
|
+
body_count: null,
|
|
31858
|
+
non_body_count: null,
|
|
31859
|
+
progress: null,
|
|
31860
|
+
all_bodies_found: false
|
|
31861
|
+
};
|
|
31862
|
+
const scans = /* @__PURE__ */ new Map();
|
|
31863
|
+
const mapped = /* @__PURE__ */ new Set();
|
|
31864
|
+
const signals = /* @__PURE__ */ new Map();
|
|
31865
|
+
for (const e of events) {
|
|
31866
|
+
if (num3(e.SystemAddress) !== address) continue;
|
|
31867
|
+
const id = num3(e.BodyID);
|
|
31868
|
+
switch (e.event) {
|
|
31869
|
+
case "FSSDiscoveryScan":
|
|
31870
|
+
discovery.body_count = num3(e.BodyCount) ?? discovery.body_count;
|
|
31871
|
+
discovery.non_body_count = num3(e.NonBodyCount) ?? discovery.non_body_count;
|
|
31872
|
+
discovery.progress = num3(e.Progress) ?? discovery.progress;
|
|
31873
|
+
break;
|
|
31874
|
+
case "FSSAllBodiesFound":
|
|
31875
|
+
discovery.all_bodies_found = true;
|
|
31876
|
+
discovery.body_count = num3(e.Count) ?? discovery.body_count;
|
|
31877
|
+
break;
|
|
31878
|
+
case "Scan":
|
|
31879
|
+
if (id !== null) scans.set(id, e);
|
|
31880
|
+
break;
|
|
31881
|
+
case "SAAScanComplete":
|
|
31882
|
+
if (id !== null) mapped.add(id);
|
|
31883
|
+
break;
|
|
31884
|
+
case "FSSBodySignals":
|
|
31885
|
+
case "SAASignalsFound":
|
|
31886
|
+
if (id !== null) signals.set(id, signalsOf(e));
|
|
31887
|
+
break;
|
|
31888
|
+
}
|
|
31889
|
+
}
|
|
31890
|
+
const bodies = [...scans].map(([id, s]) => {
|
|
31891
|
+
const distance2 = num3(s.DistanceFromArrivalLS);
|
|
31892
|
+
return {
|
|
31893
|
+
body: str3(s.BodyName) ?? `Body ${id}`,
|
|
31894
|
+
body_id: id,
|
|
31895
|
+
// Belt clusters have neither a planet class nor a star type.
|
|
31896
|
+
type: str3(s.PlanetClass) ?? (str3(s.StarType) ? `${s.StarType} star` : null),
|
|
31897
|
+
distance_ls: distance2 === null ? null : Math.round(distance2),
|
|
31898
|
+
landable: s.Landable === true,
|
|
31899
|
+
terraformable: s.TerraformState === "Terraformable",
|
|
31900
|
+
atmosphere: str3(s.Atmosphere),
|
|
31901
|
+
rings: objs2(s.Rings).map((r) => ({
|
|
31902
|
+
name: str3(r.Name) ?? "unknown",
|
|
31903
|
+
class: str3(r.RingClass)?.replace(/^eRingClass_/, "") ?? null
|
|
31904
|
+
})),
|
|
31905
|
+
was_discovered: typeof s.WasDiscovered === "boolean" ? s.WasDiscovered : null,
|
|
31906
|
+
was_mapped: typeof s.WasMapped === "boolean" ? s.WasMapped : null,
|
|
31907
|
+
mapped_by_you: mapped.has(id),
|
|
31908
|
+
signals: signals.get(id) ?? []
|
|
31909
|
+
};
|
|
31910
|
+
});
|
|
31911
|
+
bodies.sort((a, b) => (a.distance_ls ?? Infinity) - (b.distance_ls ?? Infinity));
|
|
31912
|
+
return { discovery, scanned: bodies.length, bodies };
|
|
31913
|
+
}
|
|
31914
|
+
|
|
31915
|
+
// src/journal/events.ts
|
|
31916
|
+
var MAX_ARRAY = 20;
|
|
31917
|
+
function truncate(value) {
|
|
31918
|
+
if (Array.isArray(value)) {
|
|
31919
|
+
const kept = value.slice(0, MAX_ARRAY).map(truncate);
|
|
31920
|
+
if (value.length > MAX_ARRAY) kept.push(`(${value.length - MAX_ARRAY} more not shown)`);
|
|
31921
|
+
return kept;
|
|
31922
|
+
}
|
|
31923
|
+
if (value && typeof value === "object") {
|
|
31924
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, truncate(v)]));
|
|
31925
|
+
}
|
|
31926
|
+
return value;
|
|
31927
|
+
}
|
|
31928
|
+
var LOCATION_EVENTS = /* @__PURE__ */ new Set([
|
|
31929
|
+
"Location",
|
|
31930
|
+
"FSDJump",
|
|
31931
|
+
"CarrierJump",
|
|
31932
|
+
"Docked"
|
|
31933
|
+
]);
|
|
31934
|
+
function systemAt(locations, timestamp) {
|
|
31935
|
+
for (let i = locations.length - 1; i >= 0; i--) {
|
|
31936
|
+
const e = locations[i];
|
|
31937
|
+
if (e.timestamp <= timestamp && typeof e.StarSystem === "string") return e.StarSystem;
|
|
31938
|
+
}
|
|
31939
|
+
return null;
|
|
31940
|
+
}
|
|
31941
|
+
function matchFile(events, filter, start = null) {
|
|
31942
|
+
const names = filter.events?.length ? new Set(filter.events.map((n) => n.toLowerCase())) : void 0;
|
|
31943
|
+
const system = filter.system?.toLowerCase();
|
|
31944
|
+
let current = start?.toLowerCase() ?? null;
|
|
31945
|
+
const out = [];
|
|
31946
|
+
for (const e of events) {
|
|
31947
|
+
if (LOCATION_EVENTS.has(e.event) && typeof e.StarSystem === "string") {
|
|
31948
|
+
current = e.StarSystem.toLowerCase();
|
|
31949
|
+
}
|
|
31950
|
+
if (names && !names.has(e.event.toLowerCase())) continue;
|
|
31951
|
+
if (filter.since !== void 0 && !(Date.parse(e.timestamp) >= filter.since)) continue;
|
|
31952
|
+
if (system !== void 0) {
|
|
31953
|
+
const own2 = e.event === "StartJump" ? void 0 : e.StarSystem ?? e.SystemName;
|
|
31954
|
+
const at = typeof own2 === "string" ? own2.toLowerCase() : current;
|
|
31955
|
+
if (at !== system) continue;
|
|
31956
|
+
}
|
|
31957
|
+
out.push(e);
|
|
31958
|
+
}
|
|
31959
|
+
return out;
|
|
31960
|
+
}
|
|
31961
|
+
|
|
31962
|
+
// src/journal/massacre.ts
|
|
31963
|
+
var MASSACRE_EVENTS = [
|
|
31964
|
+
"Bounty",
|
|
31965
|
+
"FactionKillBond",
|
|
31966
|
+
"MissionRedirected"
|
|
31967
|
+
];
|
|
31968
|
+
var same2 = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
31969
|
+
function massacre(missions, events) {
|
|
31970
|
+
const tracked = missions.filter((m) => (m.kill_count ?? 0) > 0 && m.target_faction && m.accepted).map((m) => ({
|
|
31971
|
+
mission: m,
|
|
31972
|
+
out: {
|
|
31973
|
+
id: m.id,
|
|
31974
|
+
name: m.name,
|
|
31975
|
+
accepted: m.accepted,
|
|
31976
|
+
expires: m.expires,
|
|
31977
|
+
reward: m.reward,
|
|
31978
|
+
kills_required: m.kill_count,
|
|
31979
|
+
kills_counted: 0,
|
|
31980
|
+
kills_left: m.kill_count,
|
|
31981
|
+
done: false
|
|
31982
|
+
}
|
|
31983
|
+
})).sort((a, b) => a.out.accepted.localeCompare(b.out.accepted) || a.out.id - b.out.id);
|
|
31984
|
+
const uncounted = missions.filter((m) => m.details_missing && /massacre/i.test(m.name));
|
|
31985
|
+
const byId = new Map(tracked.map((t) => [t.out.id, t]));
|
|
31986
|
+
for (const e of events) {
|
|
31987
|
+
if (e.event === "MissionRedirected") {
|
|
31988
|
+
const t = byId.get(typeof e.MissionID === "number" ? e.MissionID : -1);
|
|
31989
|
+
if (t?.mission.status === "complete") t.out.done = true;
|
|
31990
|
+
continue;
|
|
31991
|
+
}
|
|
31992
|
+
if (e.event !== "Bounty" && e.event !== "FactionKillBond") continue;
|
|
31993
|
+
const victim = typeof e.VictimFaction === "string" ? e.VictimFaction : null;
|
|
31994
|
+
if (!victim) continue;
|
|
31995
|
+
const credited = /* @__PURE__ */ new Set();
|
|
31996
|
+
for (const { mission, out } of tracked) {
|
|
31997
|
+
const giver = mission.faction ?? "";
|
|
31998
|
+
if (credited.has(giver) || out.done || out.accepted > e.timestamp) continue;
|
|
31999
|
+
if (!same2(mission.target_faction, victim)) continue;
|
|
32000
|
+
out.kills_counted++;
|
|
32001
|
+
credited.add(giver);
|
|
32002
|
+
if (out.kills_counted >= out.kills_required) out.done = true;
|
|
32003
|
+
}
|
|
32004
|
+
}
|
|
32005
|
+
const groups = /* @__PURE__ */ new Map();
|
|
32006
|
+
for (const { mission, out } of tracked) {
|
|
32007
|
+
if (mission.status === "complete") out.done = true;
|
|
32008
|
+
out.kills_left = out.done ? 0 : out.kills_required - out.kills_counted;
|
|
32009
|
+
const target = mission.target_faction;
|
|
32010
|
+
const giver = mission.faction ?? "unknown";
|
|
32011
|
+
const byGiver = groups.get(target) ?? /* @__PURE__ */ new Map();
|
|
32012
|
+
groups.set(target, byGiver);
|
|
32013
|
+
byGiver.set(giver, [...byGiver.get(giver) ?? [], out]);
|
|
32014
|
+
}
|
|
32015
|
+
return {
|
|
32016
|
+
groups: [...groups].map(([target, byGiver]) => {
|
|
32017
|
+
const factions = [...byGiver].map(([faction, list2]) => ({
|
|
32018
|
+
faction,
|
|
32019
|
+
kills_left: list2.reduce((n, m) => n + m.kills_left, 0),
|
|
32020
|
+
missions: list2
|
|
32021
|
+
}));
|
|
32022
|
+
return {
|
|
32023
|
+
target_faction: target,
|
|
32024
|
+
kills_left: Math.max(0, ...factions.map((f) => f.kills_left)),
|
|
32025
|
+
factions
|
|
32026
|
+
};
|
|
32027
|
+
}),
|
|
32028
|
+
uncounted
|
|
32029
|
+
};
|
|
32030
|
+
}
|
|
32031
|
+
|
|
31704
32032
|
// src/journal/service.ts
|
|
31705
32033
|
function errText(err) {
|
|
31706
32034
|
return err.code ?? err.message ?? String(err);
|
|
@@ -31712,6 +32040,12 @@ function staleWarning(minutes2) {
|
|
|
31712
32040
|
var STATE_WINDOW_MS = 14 * 864e5;
|
|
31713
32041
|
var FRESH_MS = 864e5;
|
|
31714
32042
|
var MEMO_MS = 1e3;
|
|
32043
|
+
var RAW_CACHE_FILES = 2;
|
|
32044
|
+
var KEPT_EVENTS = /* @__PURE__ */ new Set([
|
|
32045
|
+
...STATE_EVENTS,
|
|
32046
|
+
...BODY_EVENTS,
|
|
32047
|
+
...MASSACRE_EVENTS
|
|
32048
|
+
]);
|
|
31715
32049
|
var STATION_FILES = {
|
|
31716
32050
|
market: "Market.json",
|
|
31717
32051
|
outfitting: "Outfitting.json",
|
|
@@ -31720,11 +32054,12 @@ var STATION_FILES = {
|
|
|
31720
32054
|
var JournalService = class {
|
|
31721
32055
|
constructor(dir, opts = {}) {
|
|
31722
32056
|
this.dir = dir;
|
|
31723
|
-
this.reader = opts.reader ?? new JournalReader({ keep:
|
|
32057
|
+
this.reader = opts.reader ?? new JournalReader({ keep: KEPT_EVENTS });
|
|
31724
32058
|
this.now = opts.now ?? Date.now;
|
|
31725
32059
|
}
|
|
31726
32060
|
dir;
|
|
31727
32061
|
reader;
|
|
32062
|
+
rawReader = new JournalReader();
|
|
31728
32063
|
now;
|
|
31729
32064
|
// Timed with performance.now so an injected test clock cannot freeze it.
|
|
31730
32065
|
memo;
|
|
@@ -31850,7 +32185,37 @@ var JournalService = class {
|
|
|
31850
32185
|
const ageMinutes = Number.isNaN(age) ? null : Math.max(0, Math.round(age / 6e4));
|
|
31851
32186
|
if (ageMinutes !== null && age > STALE_MS) warnings.push(staleWarning(ageMinutes));
|
|
31852
32187
|
const loadout = events.findLast((e) => e.event === "Loadout") ?? null;
|
|
31853
|
-
return { ...state, age_minutes: ageMinutes, warnings, loadout };
|
|
32188
|
+
return { ...state, age_minutes: ageMinutes, warnings, loadout, events };
|
|
32189
|
+
}
|
|
32190
|
+
/**
|
|
32191
|
+
* Every recorded event that matches, newest first, from the state window. Files are read newest
|
|
32192
|
+
* first and reading stops once the limit is reached or the files are older than since.
|
|
32193
|
+
*/
|
|
32194
|
+
async recentEvents(filter, limit) {
|
|
32195
|
+
const found = await this.files(STATE_WINDOW_MS);
|
|
32196
|
+
if (!("files" in found)) throw new UserError(`${found.missing}. Run journal_status for help.`);
|
|
32197
|
+
if (found.error) throw new UserError(`${found.error}. Run journal_status for help.`);
|
|
32198
|
+
const warnings = [];
|
|
32199
|
+
const out = [];
|
|
32200
|
+
const locations = filter.system ? (await this.state()).events.filter((e) => LOCATION_EVENTS.has(e.event)) : [];
|
|
32201
|
+
for (const file2 of [...found.files].reverse()) {
|
|
32202
|
+
if (out.length >= limit) break;
|
|
32203
|
+
let events;
|
|
32204
|
+
try {
|
|
32205
|
+
events = await this.rawReader.read(file2);
|
|
32206
|
+
} catch (err) {
|
|
32207
|
+
warnings.push(`Cannot read ${file2.name}: ${errText(err)}`);
|
|
32208
|
+
continue;
|
|
32209
|
+
}
|
|
32210
|
+
const first = events[0];
|
|
32211
|
+
const start = first && filter.system ? systemAt(locations, first.timestamp) : null;
|
|
32212
|
+
const matched = matchFile(events, filter, start);
|
|
32213
|
+
for (let i = matched.length - 1; i >= 0 && out.length < limit; i--) out.push(matched[i]);
|
|
32214
|
+
const last = events.at(-1);
|
|
32215
|
+
if (filter.since !== void 0 && last && Date.parse(last.timestamp) < filter.since) break;
|
|
32216
|
+
}
|
|
32217
|
+
this.rawReader.retain(found.files.slice(-RAW_CACHE_FILES).map((f) => f.path));
|
|
32218
|
+
return { events: out, warnings };
|
|
31854
32219
|
}
|
|
31855
32220
|
// Newer game builds keep the cargo list here instead of in the Cargo event. Syncthing can
|
|
31856
32221
|
// deliver the logs before this file, so a file older than the newest Cargo event is ignored.
|
|
@@ -31894,16 +32259,16 @@ var JournalService = class {
|
|
|
31894
32259
|
const f = await this.stationFile("market");
|
|
31895
32260
|
if (!f) return null;
|
|
31896
32261
|
const items = Array.isArray(f.data.Items) ? f.data.Items : [];
|
|
31897
|
-
const
|
|
32262
|
+
const num7 = (v) => typeof v === "number" ? v : 0;
|
|
31898
32263
|
const rows = [];
|
|
31899
32264
|
for (const i of items) {
|
|
31900
32265
|
const name = i.Name_Localised ?? i.Name;
|
|
31901
32266
|
if (typeof name !== "string") continue;
|
|
31902
32267
|
rows.push({
|
|
31903
32268
|
commodity: name,
|
|
31904
|
-
buy_price:
|
|
31905
|
-
sell_price:
|
|
31906
|
-
supply:
|
|
32269
|
+
buy_price: num7(i.BuyPrice),
|
|
32270
|
+
sell_price: num7(i.SellPrice),
|
|
32271
|
+
supply: num7(i.Stock),
|
|
31907
32272
|
demand: marketDemand(i)
|
|
31908
32273
|
});
|
|
31909
32274
|
}
|
|
@@ -32176,7 +32541,10 @@ function mapSystem(raw) {
|
|
|
32176
32541
|
name: s.name,
|
|
32177
32542
|
type: s.type ?? null,
|
|
32178
32543
|
pad: stationPad(s),
|
|
32179
|
-
arrival_ls: s.distance_to_arrival ?? null
|
|
32544
|
+
arrival_ls: s.distance_to_arrival ?? null,
|
|
32545
|
+
// System search gives service names; station search gives { name } objects.
|
|
32546
|
+
services: (s.services ?? []).map((x2) => typeof x2 === "string" ? x2 : x2.name),
|
|
32547
|
+
owner: s.controlling_minor_faction ?? null
|
|
32180
32548
|
})),
|
|
32181
32549
|
stars: (raw.bodies ?? []).filter((b) => b.type === "Star").map((b) => ({ name: b.name, arrival_ls: b.distance_to_arrival ?? 0 }))
|
|
32182
32550
|
};
|
|
@@ -32326,6 +32694,7 @@ var SpanshClient = class {
|
|
|
32326
32694
|
this.systemCache = opts.systemCache ?? cache;
|
|
32327
32695
|
this.marketCache = opts.marketCache ?? cache;
|
|
32328
32696
|
this.sweepCache = opts.sweepCache ?? new SweepCache();
|
|
32697
|
+
this.warCache = opts.warCache ?? cache;
|
|
32329
32698
|
}
|
|
32330
32699
|
http;
|
|
32331
32700
|
cache;
|
|
@@ -32337,11 +32706,13 @@ var SpanshClient = class {
|
|
|
32337
32706
|
systemCache;
|
|
32338
32707
|
marketCache;
|
|
32339
32708
|
sweepCache;
|
|
32709
|
+
warCache;
|
|
32340
32710
|
// Job ids by route form, so a call after a timeout collects the job instead of resubmitting.
|
|
32341
32711
|
routeJobs = /* @__PURE__ */ new Map();
|
|
32342
32712
|
tradeJobs = /* @__PURE__ */ new Map();
|
|
32343
32713
|
tradeSubmits = /* @__PURE__ */ new Map();
|
|
32344
32714
|
sweeps = /* @__PURE__ */ new Map();
|
|
32715
|
+
warSearches = /* @__PURE__ */ new Map();
|
|
32345
32716
|
gate = Promise.resolve();
|
|
32346
32717
|
lastStart = 0;
|
|
32347
32718
|
async resolveShip(name) {
|
|
@@ -32522,6 +32893,85 @@ var SpanshClient = class {
|
|
|
32522
32893
|
this.sweepCache.savePartial({ key, centre, coveredLy, systems, total });
|
|
32523
32894
|
return { centre, systems, total, partial: partial2, coveredLy };
|
|
32524
32895
|
}
|
|
32896
|
+
/** Systems within radiusLy of near with a faction at war and a Frontline Solutions station. */
|
|
32897
|
+
async frontlineWarSystems(near, radiusLy, opts = {}) {
|
|
32898
|
+
const centre = (await this.system(near, 0)).coords;
|
|
32899
|
+
const key = `wars:${near.toLowerCase()}:${radiusLy}`;
|
|
32900
|
+
const hit = this.warCache.get(key);
|
|
32901
|
+
if (hit) return { centre, systems: hit, partial: false };
|
|
32902
|
+
let running = this.warSearches.get(key);
|
|
32903
|
+
if (!running) {
|
|
32904
|
+
running = this.warPages(near, radiusLy, centre, key, opts).finally(
|
|
32905
|
+
() => this.warSearches.delete(key)
|
|
32906
|
+
);
|
|
32907
|
+
this.warSearches.set(key, running);
|
|
32908
|
+
}
|
|
32909
|
+
return running;
|
|
32910
|
+
}
|
|
32911
|
+
async warPages(near, radiusLy, centre, key, opts) {
|
|
32912
|
+
const systems = [];
|
|
32913
|
+
const seen = /* @__PURE__ */ new Set();
|
|
32914
|
+
let partial2 = false;
|
|
32915
|
+
for (let page2 = 0; ; page2++) {
|
|
32916
|
+
if (page2 > 0 && opts.deadline !== void 0 && this.now() >= opts.deadline) {
|
|
32917
|
+
partial2 = true;
|
|
32918
|
+
break;
|
|
32919
|
+
}
|
|
32920
|
+
const body = {
|
|
32921
|
+
filters: {
|
|
32922
|
+
distance: { min: 0, max: radiusLy },
|
|
32923
|
+
minor_faction_presences: [{ state: { value: ["War", "Civil War"] } }],
|
|
32924
|
+
stations: [{ services: { value: ["Frontline Solutions"] } }]
|
|
32925
|
+
},
|
|
32926
|
+
sort: [{ distance: { direction: "asc" } }],
|
|
32927
|
+
size: SWEEP_PAGE_SIZE,
|
|
32928
|
+
page: page2,
|
|
32929
|
+
reference_system: near
|
|
32930
|
+
};
|
|
32931
|
+
let res;
|
|
32932
|
+
try {
|
|
32933
|
+
res = await this.post("/systems/search", JSON.stringify(body), "application/json");
|
|
32934
|
+
} catch (err) {
|
|
32935
|
+
if (err instanceof SpanshError && err.status === 400) {
|
|
32936
|
+
throw new UserError(
|
|
32937
|
+
`Spansh rejected the search (${err.detail}). Check that "${near}" is an exact system name (spansh_system).`
|
|
32938
|
+
);
|
|
32939
|
+
}
|
|
32940
|
+
throw err;
|
|
32941
|
+
}
|
|
32942
|
+
const results = res.results ?? [];
|
|
32943
|
+
for (const raw of results) {
|
|
32944
|
+
const s = mapSystem(raw);
|
|
32945
|
+
if (s && !seen.has(s.id64)) {
|
|
32946
|
+
seen.add(s.id64);
|
|
32947
|
+
systems.push(s);
|
|
32948
|
+
}
|
|
32949
|
+
}
|
|
32950
|
+
if (results.length < SWEEP_PAGE_SIZE) break;
|
|
32951
|
+
}
|
|
32952
|
+
if (!partial2) this.warCache.set(key, systems);
|
|
32953
|
+
return { centre, systems, partial: partial2 };
|
|
32954
|
+
}
|
|
32955
|
+
/** The bodies of a system, with the stations and settlements on each. Bodies rarely change. */
|
|
32956
|
+
async systemBodies(id64) {
|
|
32957
|
+
return await this.systemCache.getOrLoad(
|
|
32958
|
+
`bodies:${id64}`,
|
|
32959
|
+
async () => {
|
|
32960
|
+
const raw = await this.get(`/dump/${id64}`);
|
|
32961
|
+
const bodies = raw.system?.bodies ?? raw.bodies ?? [];
|
|
32962
|
+
return bodies.map((b) => ({
|
|
32963
|
+
name: b.name,
|
|
32964
|
+
bodyId: b.bodyId,
|
|
32965
|
+
subType: b.subType ?? null,
|
|
32966
|
+
arrival_ls: b.distanceToArrival ?? 0,
|
|
32967
|
+
parentPlanetId: (b.parents ?? []).map((p) => p.Planet).find((x) => x !== void 0) ?? null,
|
|
32968
|
+
stations: (b.stations ?? []).map((s) => s.name)
|
|
32969
|
+
}));
|
|
32970
|
+
},
|
|
32971
|
+
// An empty list is probably a dump that was not ready yet; fetch it again next time.
|
|
32972
|
+
{ keep: (b) => b.length > 0 }
|
|
32973
|
+
);
|
|
32974
|
+
}
|
|
32525
32975
|
async route(q, opts = {}) {
|
|
32526
32976
|
const form = new URLSearchParams({
|
|
32527
32977
|
from: q.from,
|
|
@@ -32923,10 +33373,8 @@ function mapRoute(r) {
|
|
|
32923
33373
|
};
|
|
32924
33374
|
}
|
|
32925
33375
|
|
|
32926
|
-
// src/
|
|
32927
|
-
import {
|
|
32928
|
-
import { lstat as lstat2, mkdir as mkdir3, rename as rename3, rm, writeFile as writeFile3 } from "fs/promises";
|
|
32929
|
-
import path8 from "path";
|
|
33376
|
+
// src/activity/store.ts
|
|
33377
|
+
import { lstat as lstat2, mkdir as mkdir3, unlink as unlink2 } from "fs/promises";
|
|
32930
33378
|
|
|
32931
33379
|
// src/fs/lock.ts
|
|
32932
33380
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
@@ -33090,6 +33538,276 @@ async function failure2(err, file2) {
|
|
|
33090
33538
|
return { kind: "unsafe", reason: `not readable (${code ?? "unknown error"})` };
|
|
33091
33539
|
}
|
|
33092
33540
|
|
|
33541
|
+
// src/plans/schema.ts
|
|
33542
|
+
var ItemKind = external_exports.enum([
|
|
33543
|
+
"buy",
|
|
33544
|
+
"engineer",
|
|
33545
|
+
"experimental",
|
|
33546
|
+
"unlock_engineer",
|
|
33547
|
+
"gather",
|
|
33548
|
+
"trade",
|
|
33549
|
+
"refit"
|
|
33550
|
+
]);
|
|
33551
|
+
var ItemStatus = external_exports.enum(["todo", "in_progress", "done", "skipped"]);
|
|
33552
|
+
var PlanItemSchema = external_exports.object({
|
|
33553
|
+
id: external_exports.string().min(1),
|
|
33554
|
+
kind: ItemKind,
|
|
33555
|
+
description: external_exports.string().min(1),
|
|
33556
|
+
status: ItemStatus.default("todo"),
|
|
33557
|
+
station: external_exports.string().optional(),
|
|
33558
|
+
engineer: external_exports.string().optional(),
|
|
33559
|
+
materials: external_exports.array(external_exports.object({ name: external_exports.string().min(1), count: external_exports.number().int().positive() })).optional(),
|
|
33560
|
+
notes: external_exports.string().optional()
|
|
33561
|
+
});
|
|
33562
|
+
var StopKind = external_exports.enum([
|
|
33563
|
+
"shop",
|
|
33564
|
+
"trader",
|
|
33565
|
+
"engineer",
|
|
33566
|
+
"exobiology",
|
|
33567
|
+
"exploration",
|
|
33568
|
+
"combat",
|
|
33569
|
+
"mission",
|
|
33570
|
+
"hand_in",
|
|
33571
|
+
"trade",
|
|
33572
|
+
"mining",
|
|
33573
|
+
"fuel",
|
|
33574
|
+
"other"
|
|
33575
|
+
]);
|
|
33576
|
+
var StopStatus = external_exports.enum(["todo", "done", "skipped"]);
|
|
33577
|
+
var StopSchema = external_exports.object({
|
|
33578
|
+
system: external_exports.string().min(1),
|
|
33579
|
+
// Older files can hold station "", which named nothing; it reads as no station.
|
|
33580
|
+
station: external_exports.string().transform((v) => v === "" ? void 0 : v).optional(),
|
|
33581
|
+
body: external_exports.string().min(1).optional(),
|
|
33582
|
+
purpose: external_exports.string().min(1),
|
|
33583
|
+
label: external_exports.string().min(1).optional(),
|
|
33584
|
+
kind: StopKind.optional(),
|
|
33585
|
+
after: external_exports.array(external_exports.string().min(1)).optional(),
|
|
33586
|
+
status: StopStatus.default("todo"),
|
|
33587
|
+
notes: external_exports.string().optional()
|
|
33588
|
+
});
|
|
33589
|
+
var PlanRouteSchema = external_exports.object({
|
|
33590
|
+
computed_at: external_exports.string(),
|
|
33591
|
+
from: external_exports.string(),
|
|
33592
|
+
stops: external_exports.array(external_exports.string()),
|
|
33593
|
+
total_min: external_exports.number(),
|
|
33594
|
+
plotted: external_exports.boolean(),
|
|
33595
|
+
// For each entry of stops, the indexes in the plan's stops that it visits. Names alone do not
|
|
33596
|
+
// map back: an engineer stop takes the engineer's name, and repeated names get -2, -3.
|
|
33597
|
+
// Optional, so routes saved before it load.
|
|
33598
|
+
stop_indexes: external_exports.array(external_exports.array(external_exports.number().int().min(0))).optional()
|
|
33599
|
+
});
|
|
33600
|
+
var PlanId = external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be lowercase letters, digits and dashes");
|
|
33601
|
+
var PlanInputSchema = external_exports.object({
|
|
33602
|
+
id: PlanId,
|
|
33603
|
+
name: external_exports.string().min(1),
|
|
33604
|
+
target_build: external_exports.unknown().optional(),
|
|
33605
|
+
items: external_exports.array(PlanItemSchema).default([]),
|
|
33606
|
+
stops: external_exports.array(StopSchema).default([]),
|
|
33607
|
+
route: PlanRouteSchema.optional(),
|
|
33608
|
+
notes: external_exports.string().optional()
|
|
33609
|
+
});
|
|
33610
|
+
var PlanSchema = PlanInputSchema.extend({
|
|
33611
|
+
created_at: external_exports.string(),
|
|
33612
|
+
updated_at: external_exports.string()
|
|
33613
|
+
});
|
|
33614
|
+
var PlanFileSchema = external_exports.object({
|
|
33615
|
+
version: external_exports.literal(1),
|
|
33616
|
+
plans: external_exports.array(PlanSchema)
|
|
33617
|
+
});
|
|
33618
|
+
|
|
33619
|
+
// src/activity/schema.ts
|
|
33620
|
+
var ActivityKind = external_exports.enum([
|
|
33621
|
+
"exobiology",
|
|
33622
|
+
"exploration",
|
|
33623
|
+
"massacre",
|
|
33624
|
+
"missions",
|
|
33625
|
+
"hauling",
|
|
33626
|
+
"trading",
|
|
33627
|
+
"mining",
|
|
33628
|
+
"engineering",
|
|
33629
|
+
"combat",
|
|
33630
|
+
"other"
|
|
33631
|
+
]);
|
|
33632
|
+
var MAX_SUMMARY = 500;
|
|
33633
|
+
var ActivityInputSchema = external_exports.object({
|
|
33634
|
+
kind: ActivityKind,
|
|
33635
|
+
summary: external_exports.string().trim().min(1).max(MAX_SUMMARY),
|
|
33636
|
+
// Bounded like the other fields, so the note cannot outgrow the size limit for data files.
|
|
33637
|
+
plan_id: PlanId.optional()
|
|
33638
|
+
});
|
|
33639
|
+
var ActivitySchema = ActivityInputSchema.extend({
|
|
33640
|
+
updated_at: external_exports.string()
|
|
33641
|
+
});
|
|
33642
|
+
var ActivityFileSchema = ActivitySchema.extend({ version: external_exports.literal(1) });
|
|
33643
|
+
|
|
33644
|
+
// src/activity/store.ts
|
|
33645
|
+
var LOCK_TIMEOUT_MS = 5e3;
|
|
33646
|
+
var LOCKED = "Activity file is locked by another process; try again";
|
|
33647
|
+
var ActivityStore = class {
|
|
33648
|
+
constructor(file2, now = () => /* @__PURE__ */ new Date(), opts = {}) {
|
|
33649
|
+
this.file = file2;
|
|
33650
|
+
this.now = now;
|
|
33651
|
+
this.lockTimeoutMs = opts.lockTimeoutMs ?? LOCK_TIMEOUT_MS;
|
|
33652
|
+
this.lockDir = opts.lockDir;
|
|
33653
|
+
this.guard = opts.guard;
|
|
33654
|
+
}
|
|
33655
|
+
file;
|
|
33656
|
+
now;
|
|
33657
|
+
queue = Promise.resolve();
|
|
33658
|
+
lockTimeoutMs;
|
|
33659
|
+
lockDir;
|
|
33660
|
+
guard;
|
|
33661
|
+
/** The saved note, or null when there is none. */
|
|
33662
|
+
async get() {
|
|
33663
|
+
await this.guard?.();
|
|
33664
|
+
const read2 = await readRegularText(this.file, MAX_DATA_FILE);
|
|
33665
|
+
if (read2.kind === "missing") return null;
|
|
33666
|
+
if (read2.kind === "unsafe") {
|
|
33667
|
+
throw new UserError(`Activity file ${this.file} is invalid: ${read2.reason}`);
|
|
33668
|
+
}
|
|
33669
|
+
let json3;
|
|
33670
|
+
try {
|
|
33671
|
+
json3 = JSON.parse(read2.text);
|
|
33672
|
+
} catch {
|
|
33673
|
+
throw new UserError(`Activity file ${this.file} is invalid: not JSON`);
|
|
33674
|
+
}
|
|
33675
|
+
const r = ActivityFileSchema.safeParse(json3);
|
|
33676
|
+
if (!r.success) {
|
|
33677
|
+
throw new UserError(`Activity file ${this.file} is invalid: ${external_exports.prettifyError(r.error)}`);
|
|
33678
|
+
}
|
|
33679
|
+
const { kind: kind2, summary, plan_id, updated_at } = r.data;
|
|
33680
|
+
return { kind: kind2, summary, ...plan_id === void 0 ? {} : { plan_id }, updated_at };
|
|
33681
|
+
}
|
|
33682
|
+
/** Replaces the note. */
|
|
33683
|
+
set(input2) {
|
|
33684
|
+
return this.serial(async () => {
|
|
33685
|
+
const parsed = ActivityInputSchema.safeParse(input2);
|
|
33686
|
+
if (!parsed.success) {
|
|
33687
|
+
throw new UserError(`Invalid activity: ${external_exports.prettifyError(parsed.error)}`);
|
|
33688
|
+
}
|
|
33689
|
+
const activity = { ...parsed.data, updated_at: this.now().toISOString() };
|
|
33690
|
+
await this.guard?.();
|
|
33691
|
+
return lockedWrite(
|
|
33692
|
+
this.file,
|
|
33693
|
+
{ timeoutMs: this.lockTimeoutMs, lockDir: this.lockDir, lockedMessage: LOCKED },
|
|
33694
|
+
async () => ({
|
|
33695
|
+
content: JSON.stringify({ version: 1, ...activity }, null, 2),
|
|
33696
|
+
result: activity
|
|
33697
|
+
})
|
|
33698
|
+
);
|
|
33699
|
+
});
|
|
33700
|
+
}
|
|
33701
|
+
/** Deletes the note. Returns false when there was none. */
|
|
33702
|
+
clear() {
|
|
33703
|
+
return this.serial(async () => {
|
|
33704
|
+
await this.guard?.();
|
|
33705
|
+
const missing = await lstat2(this.file).then(
|
|
33706
|
+
() => false,
|
|
33707
|
+
(err) => {
|
|
33708
|
+
if (err.code === "ENOENT") return true;
|
|
33709
|
+
throw err;
|
|
33710
|
+
}
|
|
33711
|
+
);
|
|
33712
|
+
if (missing) return false;
|
|
33713
|
+
if (this.lockDir) await mkdir3(this.lockDir, { recursive: true });
|
|
33714
|
+
const release = await acquireLock(this.file, this.lockTimeoutMs, LOCKED, this.lockDir);
|
|
33715
|
+
try {
|
|
33716
|
+
await unlink2(this.file);
|
|
33717
|
+
return true;
|
|
33718
|
+
} catch (err) {
|
|
33719
|
+
const code = err.code;
|
|
33720
|
+
if (code === "ENOENT") return false;
|
|
33721
|
+
if (code === "EPERM" || code === "EACCES" || code === "EBUSY") {
|
|
33722
|
+
throw new UserError("Activity file is in use by another program; try again");
|
|
33723
|
+
}
|
|
33724
|
+
throw err;
|
|
33725
|
+
} finally {
|
|
33726
|
+
await release();
|
|
33727
|
+
}
|
|
33728
|
+
});
|
|
33729
|
+
}
|
|
33730
|
+
// One change at a time in this process; the lock file covers other processes.
|
|
33731
|
+
serial(fn) {
|
|
33732
|
+
const run = this.queue.then(fn);
|
|
33733
|
+
this.queue = run.catch(() => void 0);
|
|
33734
|
+
return run;
|
|
33735
|
+
}
|
|
33736
|
+
};
|
|
33737
|
+
|
|
33738
|
+
// src/plans/store.ts
|
|
33739
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
33740
|
+
import { lstat as lstat3, mkdir as mkdir4, rename as rename3, rm, writeFile as writeFile3 } from "fs/promises";
|
|
33741
|
+
import path8 from "path";
|
|
33742
|
+
|
|
33743
|
+
// src/plans/stops.ts
|
|
33744
|
+
var lc = (s) => s.toLowerCase();
|
|
33745
|
+
function stopName(s) {
|
|
33746
|
+
return s.label ?? s.station ?? s.body ?? s.system;
|
|
33747
|
+
}
|
|
33748
|
+
function namesOf(s) {
|
|
33749
|
+
return [s.label, s.station, s.body, s.system].flatMap((n) => n ? [lc(n)] : []);
|
|
33750
|
+
}
|
|
33751
|
+
function visitOrder(plan) {
|
|
33752
|
+
const all = plan.stops.map((_, i) => i);
|
|
33753
|
+
const route = plan.route;
|
|
33754
|
+
if (!route) return all;
|
|
33755
|
+
const used = /* @__PURE__ */ new Set();
|
|
33756
|
+
const order = [];
|
|
33757
|
+
const take = (i) => {
|
|
33758
|
+
if (i === void 0 || used.has(i) || i >= plan.stops.length) return false;
|
|
33759
|
+
used.add(i);
|
|
33760
|
+
order.push(i);
|
|
33761
|
+
return true;
|
|
33762
|
+
};
|
|
33763
|
+
if (route.stop_indexes && route.stop_indexes.length === route.stops.length) {
|
|
33764
|
+
for (const entry2 of route.stop_indexes) for (const i of entry2) take(i);
|
|
33765
|
+
} else {
|
|
33766
|
+
const primary = plan.stops.map((s) => lc(stopName(s)));
|
|
33767
|
+
for (const raw of route.stops) {
|
|
33768
|
+
const name = lc(raw);
|
|
33769
|
+
if (take(all.find((j) => !used.has(j) && primary[j] === name))) continue;
|
|
33770
|
+
const m = /^(.*)-(\d+)$/.exec(name);
|
|
33771
|
+
if (m && take(all.filter((j) => primary[j] === m[1])[Number(m[2]) - 1])) continue;
|
|
33772
|
+
take(all.find((j) => !used.has(j) && namesOf(plan.stops[j]).includes(name)));
|
|
33773
|
+
}
|
|
33774
|
+
}
|
|
33775
|
+
return [...order, ...all.filter((i) => !used.has(i))];
|
|
33776
|
+
}
|
|
33777
|
+
function nextStop(plan) {
|
|
33778
|
+
const i = visitOrder(plan).find((j) => plan.stops[j].status === "todo");
|
|
33779
|
+
return i === void 0 ? null : { index: i, ...plan.stops[i] };
|
|
33780
|
+
}
|
|
33781
|
+
function findStop(plan, ref) {
|
|
33782
|
+
if (ref.label === void 0 && ref.index === void 0) {
|
|
33783
|
+
throw new UserError("Give the stop's label or its index");
|
|
33784
|
+
}
|
|
33785
|
+
if (ref.label !== void 0 && ref.index !== void 0) {
|
|
33786
|
+
throw new UserError("Give the stop's label or its index, not both");
|
|
33787
|
+
}
|
|
33788
|
+
if (plan.stops.length === 0) throw new UserError(`Plan "${plan.id}" has no stops.`);
|
|
33789
|
+
if (ref.index !== void 0) {
|
|
33790
|
+
if (!Number.isInteger(ref.index) || ref.index < 0 || ref.index >= plan.stops.length) {
|
|
33791
|
+
throw new UserError(
|
|
33792
|
+
`Plan "${plan.id}" has no stop ${ref.index}; indexes run from 0 to ${plan.stops.length - 1}`
|
|
33793
|
+
);
|
|
33794
|
+
}
|
|
33795
|
+
return ref.index;
|
|
33796
|
+
}
|
|
33797
|
+
const want = lc(ref.label);
|
|
33798
|
+
const byLabel = plan.stops.flatMap((s, i) => s.label && lc(s.label) === want ? [i] : []);
|
|
33799
|
+
const byName = plan.stops.flatMap((s, i) => lc(stopName(s)) === want ? [i] : []);
|
|
33800
|
+
const hits = byLabel.length ? byLabel : byName.length ? byName : plan.stops.flatMap((s, i) => namesOf(s).includes(want) ? [i] : []);
|
|
33801
|
+
if (hits.length === 1) return hits[0];
|
|
33802
|
+
if (hits.length > 1) {
|
|
33803
|
+
throw new UserError(
|
|
33804
|
+
`${hits.length} stops in plan "${plan.id}" match "${ref.label}"; give the index instead (${hits.join(", ")})`
|
|
33805
|
+
);
|
|
33806
|
+
}
|
|
33807
|
+
const known = plan.stops.map((s, i) => `${i}: ${stopName(s)}`).join(", ");
|
|
33808
|
+
throw new UserError(`No stop "${ref.label}" in plan "${plan.id}". Stops: ${known}`);
|
|
33809
|
+
}
|
|
33810
|
+
|
|
33093
33811
|
// src/plans/markdown.ts
|
|
33094
33812
|
var box = {
|
|
33095
33813
|
todo: "[ ]",
|
|
@@ -33097,6 +33815,7 @@ var box = {
|
|
|
33097
33815
|
done: "[x]",
|
|
33098
33816
|
skipped: "[-]"
|
|
33099
33817
|
};
|
|
33818
|
+
var stopBox = { todo: "[ ]", done: "[x]", skipped: "[-]" };
|
|
33100
33819
|
function planToMarkdown(plan) {
|
|
33101
33820
|
const done = plan.items.filter((i) => i.status === "done" || i.status === "skipped").length;
|
|
33102
33821
|
const out = [
|
|
@@ -33125,8 +33844,13 @@ function planToMarkdown(plan) {
|
|
|
33125
33844
|
}
|
|
33126
33845
|
if (plan.stops.length) {
|
|
33127
33846
|
out.push("## Stops", "");
|
|
33847
|
+
const next = nextStop(plan)?.index;
|
|
33128
33848
|
plan.stops.forEach((s, n) => {
|
|
33129
|
-
|
|
33849
|
+
const place2 = [s.system, s.body, s.station].filter(Boolean).join(" \u2014 ");
|
|
33850
|
+
const kind2 = s.kind ? `**${s.kind}** ` : "";
|
|
33851
|
+
const mark = n === next ? " \u2190 next" : "";
|
|
33852
|
+
out.push(`${n + 1}. ${stopBox[s.status]} ${kind2}${place2}: ${s.purpose}${mark}`);
|
|
33853
|
+
if (s.notes) out.push(` - Notes: ${s.notes}`);
|
|
33130
33854
|
});
|
|
33131
33855
|
out.push("");
|
|
33132
33856
|
}
|
|
@@ -33144,63 +33868,8 @@ function planToMarkdown(plan) {
|
|
|
33144
33868
|
return out.join("\n");
|
|
33145
33869
|
}
|
|
33146
33870
|
|
|
33147
|
-
// src/plans/schema.ts
|
|
33148
|
-
var ItemKind = external_exports.enum([
|
|
33149
|
-
"buy",
|
|
33150
|
-
"engineer",
|
|
33151
|
-
"experimental",
|
|
33152
|
-
"unlock_engineer",
|
|
33153
|
-
"gather",
|
|
33154
|
-
"trade",
|
|
33155
|
-
"refit"
|
|
33156
|
-
]);
|
|
33157
|
-
var ItemStatus = external_exports.enum(["todo", "in_progress", "done", "skipped"]);
|
|
33158
|
-
var PlanItemSchema = external_exports.object({
|
|
33159
|
-
id: external_exports.string().min(1),
|
|
33160
|
-
kind: ItemKind,
|
|
33161
|
-
description: external_exports.string().min(1),
|
|
33162
|
-
status: ItemStatus.default("todo"),
|
|
33163
|
-
station: external_exports.string().optional(),
|
|
33164
|
-
engineer: external_exports.string().optional(),
|
|
33165
|
-
materials: external_exports.array(external_exports.object({ name: external_exports.string().min(1), count: external_exports.number().int().positive() })).optional(),
|
|
33166
|
-
notes: external_exports.string().optional()
|
|
33167
|
-
});
|
|
33168
|
-
var StopKind = external_exports.enum(["shop", "trader", "engineer", "other"]);
|
|
33169
|
-
var StopSchema = external_exports.object({
|
|
33170
|
-
system: external_exports.string().min(1),
|
|
33171
|
-
station: external_exports.string().optional(),
|
|
33172
|
-
purpose: external_exports.string().min(1),
|
|
33173
|
-
label: external_exports.string().min(1).optional(),
|
|
33174
|
-
kind: StopKind.optional(),
|
|
33175
|
-
after: external_exports.array(external_exports.string().min(1)).optional()
|
|
33176
|
-
});
|
|
33177
|
-
var PlanRouteSchema = external_exports.object({
|
|
33178
|
-
computed_at: external_exports.string(),
|
|
33179
|
-
from: external_exports.string(),
|
|
33180
|
-
stops: external_exports.array(external_exports.string()),
|
|
33181
|
-
total_min: external_exports.number(),
|
|
33182
|
-
plotted: external_exports.boolean()
|
|
33183
|
-
});
|
|
33184
|
-
var PlanInputSchema = external_exports.object({
|
|
33185
|
-
id: external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be lowercase letters, digits and dashes"),
|
|
33186
|
-
name: external_exports.string().min(1),
|
|
33187
|
-
target_build: external_exports.unknown().optional(),
|
|
33188
|
-
items: external_exports.array(PlanItemSchema).default([]),
|
|
33189
|
-
stops: external_exports.array(StopSchema).default([]),
|
|
33190
|
-
route: PlanRouteSchema.optional(),
|
|
33191
|
-
notes: external_exports.string().optional()
|
|
33192
|
-
});
|
|
33193
|
-
var PlanSchema = PlanInputSchema.extend({
|
|
33194
|
-
created_at: external_exports.string(),
|
|
33195
|
-
updated_at: external_exports.string()
|
|
33196
|
-
});
|
|
33197
|
-
var PlanFileSchema = external_exports.object({
|
|
33198
|
-
version: external_exports.literal(1),
|
|
33199
|
-
plans: external_exports.array(PlanSchema)
|
|
33200
|
-
});
|
|
33201
|
-
|
|
33202
33871
|
// src/plans/store.ts
|
|
33203
|
-
var
|
|
33872
|
+
var LOCK_TIMEOUT_MS2 = 5e3;
|
|
33204
33873
|
var PlanExistsError = class extends UserError {
|
|
33205
33874
|
constructor(id) {
|
|
33206
33875
|
super(`Plan ${id} exists; pass overwrite: true to replace it`);
|
|
@@ -33210,7 +33879,7 @@ var PlanStore = class {
|
|
|
33210
33879
|
constructor(file2, now = () => /* @__PURE__ */ new Date(), opts = {}) {
|
|
33211
33880
|
this.file = file2;
|
|
33212
33881
|
this.now = now;
|
|
33213
|
-
this.lockTimeoutMs = opts.lockTimeoutMs ??
|
|
33882
|
+
this.lockTimeoutMs = opts.lockTimeoutMs ?? LOCK_TIMEOUT_MS2;
|
|
33214
33883
|
this.lockDir = opts.lockDir;
|
|
33215
33884
|
this.guard = opts.guard;
|
|
33216
33885
|
this.markdownDir = opts.markdownDir;
|
|
@@ -33230,6 +33899,7 @@ var PlanStore = class {
|
|
|
33230
33899
|
name: p.name,
|
|
33231
33900
|
items: p.items.length,
|
|
33232
33901
|
done: p.items.filter((i) => i.status === "done" || i.status === "skipped").length,
|
|
33902
|
+
next_stop: nextStop(p),
|
|
33233
33903
|
updated_at: p.updated_at
|
|
33234
33904
|
}));
|
|
33235
33905
|
}
|
|
@@ -33247,11 +33917,16 @@ var PlanStore = class {
|
|
|
33247
33917
|
if (idx >= 0 && opts.overwrite === false) {
|
|
33248
33918
|
throw new PlanExistsError(parsed.data.id);
|
|
33249
33919
|
}
|
|
33250
|
-
const
|
|
33920
|
+
const old = idx >= 0 ? data.plans[idx] : void 0;
|
|
33921
|
+
const plan = cleanRoute({
|
|
33251
33922
|
...parsed.data,
|
|
33252
|
-
created_at:
|
|
33923
|
+
created_at: old ? old.created_at : stamp,
|
|
33253
33924
|
updated_at: stamp
|
|
33254
|
-
};
|
|
33925
|
+
});
|
|
33926
|
+
const route = plan.route;
|
|
33927
|
+
if (route?.stop_indexes && old?.route?.computed_at === route.computed_at && !sameStops(old.stops, plan.stops)) {
|
|
33928
|
+
delete route.stop_indexes;
|
|
33929
|
+
}
|
|
33255
33930
|
if (idx >= 0) data.plans[idx] = plan;
|
|
33256
33931
|
else data.plans.push(plan);
|
|
33257
33932
|
return plan;
|
|
@@ -33269,10 +33944,30 @@ var PlanStore = class {
|
|
|
33269
33944
|
return plan;
|
|
33270
33945
|
});
|
|
33271
33946
|
}
|
|
33272
|
-
|
|
33947
|
+
updateStop(planId, ref, patch) {
|
|
33948
|
+
return this.mutate((data) => {
|
|
33949
|
+
const plan = data.plans.find((p) => p.id === planId);
|
|
33950
|
+
if (!plan) throw new UserError(`Plan "${planId}" not found`);
|
|
33951
|
+
const stop = plan.stops[findStop(plan, ref)];
|
|
33952
|
+
if (patch.status !== void 0) stop.status = patch.status;
|
|
33953
|
+
if (patch.notes !== void 0) stop.notes = patch.notes;
|
|
33954
|
+
plan.updated_at = this.now().toISOString();
|
|
33955
|
+
return plan;
|
|
33956
|
+
});
|
|
33957
|
+
}
|
|
33958
|
+
/**
|
|
33959
|
+
* Saves a computed route. With `stops`, the stops the route was computed from, it refuses when
|
|
33960
|
+
* the plan's stops have changed since, because the route's stop_indexes would point at others.
|
|
33961
|
+
*/
|
|
33962
|
+
setRoute(planId, route, opts = {}) {
|
|
33273
33963
|
return this.mutate((data) => {
|
|
33274
33964
|
const plan = data.plans.find((p) => p.id === planId);
|
|
33275
33965
|
if (!plan) throw new UserError(`Plan "${planId}" not found`);
|
|
33966
|
+
if (opts.stops && !sameStops(opts.stops, plan.stops)) {
|
|
33967
|
+
throw new UserError(
|
|
33968
|
+
`The stops of plan "${planId}" changed while the route was computed; run route_stops again to save it`
|
|
33969
|
+
);
|
|
33970
|
+
}
|
|
33276
33971
|
plan.route = route;
|
|
33277
33972
|
plan.updated_at = this.now().toISOString();
|
|
33278
33973
|
return plan;
|
|
@@ -33303,6 +33998,7 @@ var PlanStore = class {
|
|
|
33303
33998
|
if (!parsed.success) {
|
|
33304
33999
|
throw new UserError(`Plan file ${this.file} is invalid: ${external_exports.prettifyError(parsed.error)}`);
|
|
33305
34000
|
}
|
|
34001
|
+
parsed.data.plans.forEach(cleanRoute);
|
|
33306
34002
|
return parsed.data;
|
|
33307
34003
|
}
|
|
33308
34004
|
// Read-modify-write runs one at a time so concurrent tool calls cannot lose updates.
|
|
@@ -33354,7 +34050,7 @@ var PlanStore = class {
|
|
|
33354
34050
|
if (!dir) return null;
|
|
33355
34051
|
let st;
|
|
33356
34052
|
try {
|
|
33357
|
-
st = await
|
|
34053
|
+
st = await lstat3(dir);
|
|
33358
34054
|
} catch {
|
|
33359
34055
|
return null;
|
|
33360
34056
|
}
|
|
@@ -33367,7 +34063,7 @@ var PlanStore = class {
|
|
|
33367
34063
|
async writeMarkdown(data, before) {
|
|
33368
34064
|
const dir = this.markdownDir;
|
|
33369
34065
|
try {
|
|
33370
|
-
await
|
|
34066
|
+
await mkdir4(dir, { recursive: true });
|
|
33371
34067
|
const problem = await this.directoryProblem();
|
|
33372
34068
|
if (problem) {
|
|
33373
34069
|
console.error(problem);
|
|
@@ -33390,7 +34086,7 @@ var PlanStore = class {
|
|
|
33390
34086
|
const n = `${id}.md`;
|
|
33391
34087
|
if (keep.has(n) || !PRUNABLE.test(n)) continue;
|
|
33392
34088
|
const file2 = path8.join(dir, n);
|
|
33393
|
-
const st = await
|
|
34089
|
+
const st = await lstat3(file2).catch(() => null);
|
|
33394
34090
|
if (st?.isFile()) await rm(file2, { force: true });
|
|
33395
34091
|
}
|
|
33396
34092
|
this.lastCopyError = null;
|
|
@@ -33400,6 +34096,20 @@ var PlanStore = class {
|
|
|
33400
34096
|
}
|
|
33401
34097
|
}
|
|
33402
34098
|
};
|
|
34099
|
+
function cleanRoute(plan) {
|
|
34100
|
+
const route = plan.route;
|
|
34101
|
+
if (!route?.stop_indexes) return plan;
|
|
34102
|
+
if (route.stop_indexes.length !== route.stops.length) {
|
|
34103
|
+
delete route.stop_indexes;
|
|
34104
|
+
} else {
|
|
34105
|
+
route.stop_indexes = route.stop_indexes.map((e) => e.filter((i) => i < plan.stops.length));
|
|
34106
|
+
}
|
|
34107
|
+
return plan;
|
|
34108
|
+
}
|
|
34109
|
+
var stopKey = (s) => JSON.stringify([s.system, s.station, s.body, s.label, s.purpose]);
|
|
34110
|
+
function sameStops(a, b) {
|
|
34111
|
+
return a.length === b.length && a.every((s, i) => stopKey(s) === stopKey(b[i]));
|
|
34112
|
+
}
|
|
33403
34113
|
var PRUNABLE = /^[a-z0-9][a-z0-9-]{0,63}\.md$/;
|
|
33404
34114
|
|
|
33405
34115
|
// src/prefs/schema.ts
|
|
@@ -33522,11 +34232,11 @@ function applyPatch(base2, patch) {
|
|
|
33522
34232
|
}
|
|
33523
34233
|
|
|
33524
34234
|
// src/prefs/store.ts
|
|
33525
|
-
var
|
|
34235
|
+
var LOCK_TIMEOUT_MS3 = 5e3;
|
|
33526
34236
|
var PreferencesStore = class {
|
|
33527
34237
|
constructor(file2, opts = {}) {
|
|
33528
34238
|
this.file = file2;
|
|
33529
|
-
this.lockTimeoutMs = opts.lockTimeoutMs ??
|
|
34239
|
+
this.lockTimeoutMs = opts.lockTimeoutMs ?? LOCK_TIMEOUT_MS3;
|
|
33530
34240
|
this.lockDir = opts.lockDir;
|
|
33531
34241
|
this.guard = opts.guard;
|
|
33532
34242
|
}
|
|
@@ -33587,7 +34297,7 @@ var PreferencesStore = class {
|
|
|
33587
34297
|
};
|
|
33588
34298
|
|
|
33589
34299
|
// src/ships/data.ts
|
|
33590
|
-
import { mkdir as
|
|
34300
|
+
import { mkdir as mkdir5, readFile as readFile5, rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
33591
34301
|
import path9 from "path";
|
|
33592
34302
|
import { gunzipSync } from "zlib";
|
|
33593
34303
|
|
|
@@ -33789,7 +34499,7 @@ var ships_default = {
|
|
|
33789
34499
|
|
|
33790
34500
|
// src/ships/tar.ts
|
|
33791
34501
|
var BLOCK = 512;
|
|
33792
|
-
function
|
|
34502
|
+
function str4(buf, start, len) {
|
|
33793
34503
|
const field = buf.subarray(start, start + len);
|
|
33794
34504
|
const end = field.indexOf(0);
|
|
33795
34505
|
return field.subarray(0, end < 0 ? len : end).toString("utf8");
|
|
@@ -33817,7 +34527,7 @@ function readTar(buf) {
|
|
|
33817
34527
|
while (pos + BLOCK <= buf.length) {
|
|
33818
34528
|
const h = buf.subarray(pos, pos + BLOCK);
|
|
33819
34529
|
if (h.every((b) => b === 0)) break;
|
|
33820
|
-
const size = Number.parseInt(
|
|
34530
|
+
const size = Number.parseInt(str4(h, 124, 12).trim() || "0", 8);
|
|
33821
34531
|
const type = String.fromCharCode(h[156] ?? 0);
|
|
33822
34532
|
const start = pos + BLOCK;
|
|
33823
34533
|
if (start + size > buf.length) throw new Error("Truncated tar archive");
|
|
@@ -33831,9 +34541,9 @@ function readTar(buf) {
|
|
|
33831
34541
|
comment = paxFields(data).comment ?? comment;
|
|
33832
34542
|
continue;
|
|
33833
34543
|
}
|
|
33834
|
-
let path15 =
|
|
33835
|
-
if (
|
|
33836
|
-
const prefix =
|
|
34544
|
+
let path15 = str4(h, 0, 100);
|
|
34545
|
+
if (str4(h, 257, 6) === "ustar") {
|
|
34546
|
+
const prefix = str4(h, 345, 155);
|
|
33837
34547
|
if (prefix) path15 = `${prefix}/${path15}`;
|
|
33838
34548
|
}
|
|
33839
34549
|
if (nextPath !== null) path15 = nextPath;
|
|
@@ -34115,7 +34825,7 @@ var ShipDataService = class {
|
|
|
34115
34825
|
}
|
|
34116
34826
|
}
|
|
34117
34827
|
async save(snapshot) {
|
|
34118
|
-
await
|
|
34828
|
+
await mkdir5(path9.dirname(this.file), { recursive: true });
|
|
34119
34829
|
const tmp = `${this.file}.${process.pid}.tmp`;
|
|
34120
34830
|
await writeFile4(tmp, JSON.stringify(snapshot));
|
|
34121
34831
|
await rename4(tmp, this.file);
|
|
@@ -34124,7 +34834,7 @@ var ShipDataService = class {
|
|
|
34124
34834
|
var message = (err) => err instanceof Error ? err.message : String(err);
|
|
34125
34835
|
|
|
34126
34836
|
// src/settings/secrets.ts
|
|
34127
|
-
import { lstat as
|
|
34837
|
+
import { lstat as lstat4, mkdir as mkdir6, rm as rm2 } from "fs/promises";
|
|
34128
34838
|
import path10 from "path";
|
|
34129
34839
|
|
|
34130
34840
|
// src/settings/crypto.ts
|
|
@@ -34227,7 +34937,7 @@ var SecretsSchema = external_exports.strictObject({
|
|
|
34227
34937
|
});
|
|
34228
34938
|
var WrongPasswordError = class extends SecretsError {
|
|
34229
34939
|
};
|
|
34230
|
-
var
|
|
34940
|
+
var LOCK_TIMEOUT_MS4 = 5e3;
|
|
34231
34941
|
var SecretStore = class _SecretStore {
|
|
34232
34942
|
constructor(file2, opts = {}) {
|
|
34233
34943
|
this.file = file2;
|
|
@@ -34261,7 +34971,7 @@ var SecretStore = class _SecretStore {
|
|
|
34261
34971
|
await lockedWrite(
|
|
34262
34972
|
this.file,
|
|
34263
34973
|
{
|
|
34264
|
-
timeoutMs: this.opts.lockTimeoutMs ??
|
|
34974
|
+
timeoutMs: this.opts.lockTimeoutMs ?? LOCK_TIMEOUT_MS4,
|
|
34265
34975
|
lockedMessage: "Secrets file is locked by another process; try again",
|
|
34266
34976
|
lockDir: this.opts.lockDir,
|
|
34267
34977
|
mode: 384
|
|
@@ -34290,11 +35000,11 @@ var SecretStore = class _SecretStore {
|
|
|
34290
35000
|
reset() {
|
|
34291
35001
|
const run = this.queue.then(async () => {
|
|
34292
35002
|
await this.opts.guard?.();
|
|
34293
|
-
await
|
|
34294
|
-
if (this.opts.lockDir) await
|
|
35003
|
+
await mkdir6(path10.dirname(this.file), { recursive: true });
|
|
35004
|
+
if (this.opts.lockDir) await mkdir6(this.opts.lockDir, { recursive: true });
|
|
34295
35005
|
const release = await acquireLock(
|
|
34296
35006
|
this.file,
|
|
34297
|
-
this.opts.lockTimeoutMs ??
|
|
35007
|
+
this.opts.lockTimeoutMs ?? LOCK_TIMEOUT_MS4,
|
|
34298
35008
|
"Secrets file is locked by another process; try again",
|
|
34299
35009
|
this.opts.lockDir
|
|
34300
35010
|
);
|
|
@@ -34330,7 +35040,7 @@ var SecretStore = class _SecretStore {
|
|
|
34330
35040
|
async checkFile() {
|
|
34331
35041
|
let st;
|
|
34332
35042
|
try {
|
|
34333
|
-
st = await
|
|
35043
|
+
st = await lstat4(this.file);
|
|
34334
35044
|
} catch (err) {
|
|
34335
35045
|
const code = err.code;
|
|
34336
35046
|
if (code === "ENOENT") return null;
|
|
@@ -34388,7 +35098,7 @@ var SecretStore = class _SecretStore {
|
|
|
34388
35098
|
return lockedWrite(
|
|
34389
35099
|
this.file,
|
|
34390
35100
|
{
|
|
34391
|
-
timeoutMs: this.opts.lockTimeoutMs ??
|
|
35101
|
+
timeoutMs: this.opts.lockTimeoutMs ?? LOCK_TIMEOUT_MS4,
|
|
34392
35102
|
lockedMessage: "Secrets file is locked by another process; try again",
|
|
34393
35103
|
lockDir: this.opts.lockDir,
|
|
34394
35104
|
mode: 384
|
|
@@ -34866,7 +35576,7 @@ function buildDeps(config2, opts = {}) {
|
|
|
34866
35576
|
markdownDir: config2.plansMarkdownDir
|
|
34867
35577
|
});
|
|
34868
35578
|
const legacyPlans = config2.legacyPlansEnv ? `ED_MCP_PLANS is no longer used. Plans are now in ${config2.plansFile}. Move your old plans file there, or use plan_import, then remove ED_MCP_PLANS.` : void 0;
|
|
34869
|
-
const syncWarnings = sync ? () => sync.warnings([config2.plansFile, config2.prefsFile, config2.secretsFile]) : void 0;
|
|
35579
|
+
const syncWarnings = sync ? () => sync.warnings([config2.plansFile, config2.prefsFile, config2.activityFile, config2.secretsFile]) : void 0;
|
|
34870
35580
|
const inaraCache = new TtlCache("inara", 6e4);
|
|
34871
35581
|
caches.register(inaraCache);
|
|
34872
35582
|
const inara = new InaraClient(http, () => settings.inara(), inaraCache);
|
|
@@ -34881,10 +35591,13 @@ function buildDeps(config2, opts = {}) {
|
|
|
34881
35591
|
caches.register(spanshMarketCache);
|
|
34882
35592
|
const spanshSweepCache = new SweepCache();
|
|
34883
35593
|
caches.register(spanshSweepCache);
|
|
35594
|
+
const spanshWarCache = new TtlCache("spansh-wars", 6e5);
|
|
35595
|
+
caches.register(spanshWarCache);
|
|
34884
35596
|
const spansh = new SpanshClient(http, spanshCache, {
|
|
34885
35597
|
systemCache: spanshSystemCache,
|
|
34886
35598
|
marketCache: spanshMarketCache,
|
|
34887
|
-
sweepCache: spanshSweepCache
|
|
35599
|
+
sweepCache: spanshSweepCache,
|
|
35600
|
+
warCache: spanshWarCache
|
|
34888
35601
|
});
|
|
34889
35602
|
const capiHttp = new HttpClient({ userAgent: `EDCD-EdMcp-${VERSION}`, fetchFn: opts.fetchFn });
|
|
34890
35603
|
const capiCache = new TtlCache("capi", 6e4);
|
|
@@ -34930,6 +35643,10 @@ function buildDeps(config2, opts = {}) {
|
|
|
34930
35643
|
lockDir: lockFor(config2.prefsFile),
|
|
34931
35644
|
guard: guardFor(config2.prefsFile)
|
|
34932
35645
|
}),
|
|
35646
|
+
activity: new ActivityStore(config2.activityFile, void 0, {
|
|
35647
|
+
lockDir: lockFor(config2.activityFile),
|
|
35648
|
+
guard: guardFor(config2.activityFile)
|
|
35649
|
+
}),
|
|
34933
35650
|
sync,
|
|
34934
35651
|
syncWarnings,
|
|
34935
35652
|
versionCheck: new VersionCheck({ http, cacheDir: config2.cacheDir, current: VERSION }),
|
|
@@ -42982,6 +43699,124 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
42982
43699
|
}
|
|
42983
43700
|
};
|
|
42984
43701
|
|
|
43702
|
+
// src/tools/result.ts
|
|
43703
|
+
function ok(value) {
|
|
43704
|
+
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
43705
|
+
}
|
|
43706
|
+
function fail(message2) {
|
|
43707
|
+
return { isError: true, content: [{ type: "text", text: message2 }] };
|
|
43708
|
+
}
|
|
43709
|
+
function wrap(fn) {
|
|
43710
|
+
return async (args) => {
|
|
43711
|
+
try {
|
|
43712
|
+
return ok(await fn(args));
|
|
43713
|
+
} catch (err) {
|
|
43714
|
+
if (err instanceof UserError) return fail(err.message);
|
|
43715
|
+
console.error(err);
|
|
43716
|
+
return fail(`Internal error: ${err instanceof Error ? err.message : String(err)}`);
|
|
43717
|
+
}
|
|
43718
|
+
};
|
|
43719
|
+
}
|
|
43720
|
+
function wrapWarn(fn, warnings) {
|
|
43721
|
+
const inner = wrap(fn);
|
|
43722
|
+
return async (args) => {
|
|
43723
|
+
const result = await inner(args);
|
|
43724
|
+
const list2 = warnings ? await warnings().catch(() => []) : [];
|
|
43725
|
+
if (!list2.length) return result;
|
|
43726
|
+
return {
|
|
43727
|
+
...result,
|
|
43728
|
+
content: [
|
|
43729
|
+
...result.content,
|
|
43730
|
+
{ type: "text", text: `Warnings:
|
|
43731
|
+
${list2.map((w) => `- ${w}`).join("\n")}` }
|
|
43732
|
+
]
|
|
43733
|
+
};
|
|
43734
|
+
};
|
|
43735
|
+
}
|
|
43736
|
+
function reason(err) {
|
|
43737
|
+
if (err instanceof UserError) return err.message;
|
|
43738
|
+
console.error(err);
|
|
43739
|
+
return err instanceof Error ? err.message : String(err);
|
|
43740
|
+
}
|
|
43741
|
+
|
|
43742
|
+
// src/tools/activity.ts
|
|
43743
|
+
var NO_NOTE = "No activity is saved. Ask the player what they are doing, then save it.";
|
|
43744
|
+
function registerActivityTools(server, deps) {
|
|
43745
|
+
const now = deps.now ?? Date.now;
|
|
43746
|
+
const location = async () => {
|
|
43747
|
+
if (!deps.journal) return { location: null, location_note: "The journal is not available." };
|
|
43748
|
+
try {
|
|
43749
|
+
const s = await deps.journal.state();
|
|
43750
|
+
return {
|
|
43751
|
+
location: {
|
|
43752
|
+
system: s.location.system,
|
|
43753
|
+
station: s.location.station,
|
|
43754
|
+
docked: s.location.docked,
|
|
43755
|
+
as_of: s.as_of,
|
|
43756
|
+
age_minutes: s.age_minutes,
|
|
43757
|
+
warnings: s.warnings
|
|
43758
|
+
}
|
|
43759
|
+
};
|
|
43760
|
+
} catch (err) {
|
|
43761
|
+
return { location: null, location_note: `The journal could not be read: ${reason(err)}` };
|
|
43762
|
+
}
|
|
43763
|
+
};
|
|
43764
|
+
server.registerTool(
|
|
43765
|
+
"activity_get",
|
|
43766
|
+
{
|
|
43767
|
+
annotations: {
|
|
43768
|
+
title: "Get Activity",
|
|
43769
|
+
readOnlyHint: true,
|
|
43770
|
+
destructiveHint: false,
|
|
43771
|
+
idempotentHint: true,
|
|
43772
|
+
openWorldHint: false
|
|
43773
|
+
},
|
|
43774
|
+
description: "What the commander was last doing: a short saved note (kind, summary, optional plan_id), its age, and the commander's current location from the journal. Call it at the start of a conversation. When the location or age says the player has moved on, ask before relying on the note."
|
|
43775
|
+
},
|
|
43776
|
+
wrapWarn(async () => {
|
|
43777
|
+
const [activity, here] = await Promise.all([deps.activity.get(), location()]);
|
|
43778
|
+
if (!activity) return { activity: null, note: NO_NOTE, ...here };
|
|
43779
|
+
const saved = Date.parse(activity.updated_at);
|
|
43780
|
+
const age = Number.isNaN(saved) ? null : Math.max(0, Math.round((now() - saved) / 6e4));
|
|
43781
|
+
return { activity, age_minutes: age, ...here };
|
|
43782
|
+
}, deps.warnings)
|
|
43783
|
+
);
|
|
43784
|
+
server.registerTool(
|
|
43785
|
+
"activity_set",
|
|
43786
|
+
{
|
|
43787
|
+
annotations: {
|
|
43788
|
+
title: "Set Activity",
|
|
43789
|
+
readOnlyHint: false,
|
|
43790
|
+
destructiveHint: true,
|
|
43791
|
+
idempotentHint: true,
|
|
43792
|
+
openWorldHint: false
|
|
43793
|
+
},
|
|
43794
|
+
description: `Replace the saved note of what the commander is doing, so the next conversation can pick it up. kind and summary (at most ${MAX_SUMMARY} characters: where, the goal, and progress) are required; plan_id names the active route or build plan. Update it when the player starts something new or the goal changes. clear: true deletes the note.`,
|
|
43795
|
+
inputSchema: {
|
|
43796
|
+
kind: ActivityKind.optional(),
|
|
43797
|
+
summary: external_exports.string().max(MAX_SUMMARY).optional(),
|
|
43798
|
+
plan_id: PlanId.optional(),
|
|
43799
|
+
clear: external_exports.boolean().optional()
|
|
43800
|
+
}
|
|
43801
|
+
},
|
|
43802
|
+
wrapWarn(
|
|
43803
|
+
async (a) => {
|
|
43804
|
+
if (a.clear) {
|
|
43805
|
+
if (a.kind !== void 0 || a.summary !== void 0 || a.plan_id !== void 0) {
|
|
43806
|
+
throw new UserError("Give clear: true alone, or kind and summary without clear");
|
|
43807
|
+
}
|
|
43808
|
+
return { cleared: await deps.activity.clear() };
|
|
43809
|
+
}
|
|
43810
|
+
if (a.kind === void 0 || a.summary === void 0) {
|
|
43811
|
+
throw new UserError("Give kind and summary, or clear: true");
|
|
43812
|
+
}
|
|
43813
|
+
return deps.activity.set({ kind: a.kind, summary: a.summary, plan_id: a.plan_id });
|
|
43814
|
+
},
|
|
43815
|
+
deps.warnings
|
|
43816
|
+
)
|
|
43817
|
+
);
|
|
43818
|
+
}
|
|
43819
|
+
|
|
42985
43820
|
// src/ships/constants.ts
|
|
42986
43821
|
var MODULE_GROUP_NAMES = {
|
|
42987
43822
|
pp: "Power Plant",
|
|
@@ -43718,8 +44553,8 @@ var family = (symbol2) => (symbol2 ?? "").toLowerCase().replace(/size\d+|class\d
|
|
|
43718
44553
|
function pick2(candidates, current) {
|
|
43719
44554
|
let list2 = candidates;
|
|
43720
44555
|
if (current?.module.symbol) {
|
|
43721
|
-
const
|
|
43722
|
-
if (
|
|
44556
|
+
const same6 = list2.filter((m) => family(m.symbol) === family(current.module.symbol));
|
|
44557
|
+
if (same6.length) list2 = same6;
|
|
43723
44558
|
}
|
|
43724
44559
|
const plain = list2.filter((m) => m.name === void 0 && m.preEngineered === void 0);
|
|
43725
44560
|
return (plain.length ? plain : list2)[0] ?? null;
|
|
@@ -43944,8 +44779,8 @@ function toSlef(build) {
|
|
|
43944
44779
|
|
|
43945
44780
|
// src/ships/calc/core.ts
|
|
43946
44781
|
var CARGO_HATCH_MW = 0.6;
|
|
43947
|
-
var
|
|
43948
|
-
var prop = (b, name) =>
|
|
44782
|
+
var num4 = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
44783
|
+
var prop = (b, name) => num4(b.ship.properties[name]);
|
|
43949
44784
|
var find = (b, grp) => b.modules.find((m) => m.module.grp === grp) ?? null;
|
|
43950
44785
|
function scale(m, feature) {
|
|
43951
44786
|
const v = m.engineering?.modifiers[feature];
|
|
@@ -43954,17 +44789,17 @@ function scale(m, feature) {
|
|
|
43954
44789
|
function moduleStat(m, feature) {
|
|
43955
44790
|
const mods = m.engineering?.modifiers ?? {};
|
|
43956
44791
|
if ((feature === "minmass" || feature === "maxmass") && mods[feature] === void 0) {
|
|
43957
|
-
const base2 =
|
|
44792
|
+
const base2 = num4(m.module[feature]);
|
|
43958
44793
|
const optmass = scale(m, "optmass");
|
|
43959
44794
|
if (SHIELD_GENERATORS.has(m.module.grp) && feature === "maxmass" && optmass < 1) return base2;
|
|
43960
44795
|
return base2 * optmass;
|
|
43961
44796
|
}
|
|
43962
44797
|
const mul = /^(min|opt|max)mul(.*)$/.exec(feature);
|
|
43963
44798
|
if (mul) {
|
|
43964
|
-
const base2 =
|
|
44799
|
+
const base2 = num4(m.module[feature]) || num4(m.module[`${mul[1]}mul`]);
|
|
43965
44800
|
return base2 * scale(m, "optmul");
|
|
43966
44801
|
}
|
|
43967
|
-
return
|
|
44802
|
+
return num4(modifiedValue(m.module, mods, feature));
|
|
43968
44803
|
}
|
|
43969
44804
|
var sum = (b, feature, round2 = (v) => v) => b.modules.reduce((s, m) => s + round2(moduleStat(m, feature)), 0);
|
|
43970
44805
|
function massFuel(b) {
|
|
@@ -44101,8 +44936,8 @@ var BROKEN_DELAY_S = 16;
|
|
|
44101
44936
|
var HRPS = /* @__PURE__ */ new Set(["hr", "ghrp", "mahr"]);
|
|
44102
44937
|
var MRPS = /* @__PURE__ */ new Set(["mrp", "gmrp"]);
|
|
44103
44938
|
var RESISTANCES = { explosive: "explres", kinetic: "kinres", thermal: "thermres" };
|
|
44104
|
-
var
|
|
44105
|
-
var prop2 = (b, name) =>
|
|
44939
|
+
var num5 = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
44940
|
+
var prop2 = (b, name) => num5(b.ship.properties[name]);
|
|
44106
44941
|
var active = (b, grps) => b.modules.filter((m) => grps.has(m.module.grp) && online(b, m));
|
|
44107
44942
|
function pipRecharge(rate, pips) {
|
|
44108
44943
|
return rate * Math.pow(pips / 4, 1.1);
|
|
@@ -44276,46 +45111,6 @@ function weapons(b) {
|
|
|
44276
45111
|
};
|
|
44277
45112
|
}
|
|
44278
45113
|
|
|
44279
|
-
// src/tools/result.ts
|
|
44280
|
-
function ok(value) {
|
|
44281
|
-
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
44282
|
-
}
|
|
44283
|
-
function fail(message2) {
|
|
44284
|
-
return { isError: true, content: [{ type: "text", text: message2 }] };
|
|
44285
|
-
}
|
|
44286
|
-
function wrap(fn) {
|
|
44287
|
-
return async (args) => {
|
|
44288
|
-
try {
|
|
44289
|
-
return ok(await fn(args));
|
|
44290
|
-
} catch (err) {
|
|
44291
|
-
if (err instanceof UserError) return fail(err.message);
|
|
44292
|
-
console.error(err);
|
|
44293
|
-
return fail(`Internal error: ${err instanceof Error ? err.message : String(err)}`);
|
|
44294
|
-
}
|
|
44295
|
-
};
|
|
44296
|
-
}
|
|
44297
|
-
function wrapWarn(fn, warnings) {
|
|
44298
|
-
const inner = wrap(fn);
|
|
44299
|
-
return async (args) => {
|
|
44300
|
-
const result = await inner(args);
|
|
44301
|
-
const list2 = warnings ? await warnings().catch(() => []) : [];
|
|
44302
|
-
if (!list2.length) return result;
|
|
44303
|
-
return {
|
|
44304
|
-
...result,
|
|
44305
|
-
content: [
|
|
44306
|
-
...result.content,
|
|
44307
|
-
{ type: "text", text: `Warnings:
|
|
44308
|
-
${list2.map((w) => `- ${w}`).join("\n")}` }
|
|
44309
|
-
]
|
|
44310
|
-
};
|
|
44311
|
-
};
|
|
44312
|
-
}
|
|
44313
|
-
function reason(err) {
|
|
44314
|
-
if (err instanceof UserError) return err.message;
|
|
44315
|
-
console.error(err);
|
|
44316
|
-
return err instanceof Error ? err.message : String(err);
|
|
44317
|
-
}
|
|
44318
|
-
|
|
44319
45114
|
// src/tools/build.ts
|
|
44320
45115
|
var SCO_NOTE = "Jump range uses the drive's normal values, not supercruise overcharge.";
|
|
44321
45116
|
var SlefInput = external_exports.union([external_exports.string(), external_exports.array(external_exports.unknown()), external_exports.record(external_exports.string(), external_exports.unknown())]);
|
|
@@ -45534,7 +46329,7 @@ async function locate(deps) {
|
|
|
45534
46329
|
// src/routes/legs.ts
|
|
45535
46330
|
var STATION_LIMIT = 500;
|
|
45536
46331
|
var PLANETARY = /planetary|surface|settlement/i;
|
|
45537
|
-
var
|
|
46332
|
+
var same3 = (a, b) => a !== null && b !== null && a.toLowerCase() === b.toLowerCase();
|
|
45538
46333
|
function createPlacer(spansh, notes) {
|
|
45539
46334
|
const systems = /* @__PURE__ */ new Map();
|
|
45540
46335
|
const lookup2 = (name) => {
|
|
@@ -45546,7 +46341,7 @@ function createPlacer(spansh, notes) {
|
|
|
45546
46341
|
return {
|
|
45547
46342
|
async place(system, station) {
|
|
45548
46343
|
const info = await lookup2(system);
|
|
45549
|
-
const hit = station ? info.stations.find((s) =>
|
|
46344
|
+
const hit = station ? info.stations.find((s) => same3(s.name, station)) : void 0;
|
|
45550
46345
|
if (station && !hit) {
|
|
45551
46346
|
notes.add(`Station ${station} not found in ${info.name}; assumed 1000 ls from arrival.`);
|
|
45552
46347
|
}
|
|
@@ -45560,7 +46355,7 @@ function createPlacer(spansh, notes) {
|
|
|
45560
46355
|
},
|
|
45561
46356
|
async placeEngineer(e) {
|
|
45562
46357
|
const info = await lookup2(e.system);
|
|
45563
|
-
const hit = info.stations.find((s) =>
|
|
46358
|
+
const hit = info.stations.find((s) => same3(s.name, e.base));
|
|
45564
46359
|
if (!hit) {
|
|
45565
46360
|
notes.add(
|
|
45566
46361
|
`${e.name}'s base ${e.base} is not in Spansh; assumed a planetary base 1000 ls from arrival.`
|
|
@@ -45577,10 +46372,10 @@ function createPlacer(spansh, notes) {
|
|
|
45577
46372
|
};
|
|
45578
46373
|
}
|
|
45579
46374
|
function legBetween(from, to, travel) {
|
|
45580
|
-
if (
|
|
46375
|
+
if (same3(from.system, to.system) && same3(from.station, to.station)) {
|
|
45581
46376
|
return { jumps: 0, seconds: 0, distanceLy: 0 };
|
|
45582
46377
|
}
|
|
45583
|
-
const distanceLy2 =
|
|
46378
|
+
const distanceLy2 = same3(from.system, to.system) ? 0 : Math.hypot(
|
|
45584
46379
|
from.coords.x - to.coords.x,
|
|
45585
46380
|
from.coords.y - to.coords.y,
|
|
45586
46381
|
from.coords.z - to.coords.z
|
|
@@ -45967,6 +46762,13 @@ function registerMissionTools(server, deps) {
|
|
|
45967
46762
|
// src/tools/journal.ts
|
|
45968
46763
|
var FRESHNESS_TEXT = "as_of is the time of the newest journal event and age_minutes its age; warnings flag stale data (over 30 minutes old) and unreadable journal files.";
|
|
45969
46764
|
var NAMES_MISSING = 'Material names are not available until the game data catalog is downloaded. Call any catalog tool (for example catalog_material) or cache_refresh with source "catalog".';
|
|
46765
|
+
var MASSACRE_NOTE = "Kill counts are an estimate from the journal. Kills by wing mates, kills before a mission was accepted elsewhere and the game's own rules can differ; the mission panel in the game is authoritative.";
|
|
46766
|
+
var READ_ONLY = {
|
|
46767
|
+
readOnlyHint: true,
|
|
46768
|
+
destructiveHint: false,
|
|
46769
|
+
idempotentHint: true,
|
|
46770
|
+
openWorldHint: false
|
|
46771
|
+
};
|
|
45970
46772
|
var freshness = (s) => ({
|
|
45971
46773
|
as_of: s.as_of,
|
|
45972
46774
|
age_minutes: s.age_minutes,
|
|
@@ -46086,7 +46888,104 @@ function registerJournalTools(server, deps) {
|
|
|
46086
46888
|
return { engineers: s.engineers, ...freshness(s) };
|
|
46087
46889
|
})
|
|
46088
46890
|
);
|
|
46891
|
+
server.registerTool(
|
|
46892
|
+
"journal_exobiology",
|
|
46893
|
+
{
|
|
46894
|
+
annotations: { title: "Journal Exobiology", ...READ_ONLY },
|
|
46895
|
+
description: 'Exobiology progress in a system from the local game journal (last 14 days). For each body with biological signals: bio_signals (the count from the FSS or surface scan), genera found by the surface scan, each species sampled with samples taken (stage 0/3 to 3/3, analysed once complete; abandoned when another species was started first, which drops an unfinished sample, or after a death), and not_started genera with no sample yet. unsold_analysed counts species analysed since the last sale of organic data (or death), in any system. The journal has no sample positions. system defaults to the current system; body is a full body name or the part after the system name, such as "A 4". ' + FRESHNESS_TEXT,
|
|
46896
|
+
inputSchema: {
|
|
46897
|
+
system: external_exports.string().optional().describe("System name; default the current system"),
|
|
46898
|
+
body: external_exports.string().optional().describe('Body name, or the part after the system ("A 4")')
|
|
46899
|
+
}
|
|
46900
|
+
},
|
|
46901
|
+
wrap(async (a) => {
|
|
46902
|
+
const s = await deps.journal.state();
|
|
46903
|
+
const system = systemOf(a.system, s.location.system);
|
|
46904
|
+
const address = systemAddress(s.events, system);
|
|
46905
|
+
const r = exobiology(s.events, address ?? -1, { system, body: a.body });
|
|
46906
|
+
return {
|
|
46907
|
+
system,
|
|
46908
|
+
...r,
|
|
46909
|
+
...address === null ? { note: notVisited(system) } : {},
|
|
46910
|
+
...freshness(s)
|
|
46911
|
+
};
|
|
46912
|
+
})
|
|
46913
|
+
);
|
|
46914
|
+
server.registerTool(
|
|
46915
|
+
"journal_exploration",
|
|
46916
|
+
{
|
|
46917
|
+
annotations: { title: "Journal Exploration", ...READ_ONLY },
|
|
46918
|
+
description: "Exploration scans of a system from the local game journal (last 14 days). discovery has the FSS discovery scan body count and progress, and all_bodies_found once the FSS is complete. bodies lists each scanned body, nearest first: type, distance_ls from the arrival star, landable, terraformable, atmosphere, rings, was_discovered and was_mapped (by someone before you), mapped_by_you (surface scan complete) and signals. Values are not estimated. system defaults to the current system. " + FRESHNESS_TEXT,
|
|
46919
|
+
inputSchema: {
|
|
46920
|
+
system: external_exports.string().optional().describe("System name; default the current system")
|
|
46921
|
+
}
|
|
46922
|
+
},
|
|
46923
|
+
wrap(async (a) => {
|
|
46924
|
+
const s = await deps.journal.state();
|
|
46925
|
+
const system = systemOf(a.system, s.location.system);
|
|
46926
|
+
const address = systemAddress(s.events, system);
|
|
46927
|
+
return {
|
|
46928
|
+
system,
|
|
46929
|
+
...exploration(s.events, address ?? -1),
|
|
46930
|
+
...address === null ? { note: notVisited(system) } : {},
|
|
46931
|
+
...freshness(s)
|
|
46932
|
+
};
|
|
46933
|
+
})
|
|
46934
|
+
);
|
|
46935
|
+
server.registerTool(
|
|
46936
|
+
"journal_massacre",
|
|
46937
|
+
{
|
|
46938
|
+
annotations: { title: "Journal Massacre Progress", ...READ_ONLY },
|
|
46939
|
+
description: "Progress of active massacre missions from the local game journal, grouped by target faction and then by giving faction. Each mission has kills_required, kills_counted (from bounties and combat bonds against the target since it was accepted) and kills_left. A kill counts toward the oldest open mission of each giving faction, as the game stacks them; a mission redirected back to its giver is done. A target's kills_left is the most any one giver still needs. The counts are an estimate. uncounted lists massacre missions accepted before the 14-day journal window. " + FRESHNESS_TEXT
|
|
46940
|
+
},
|
|
46941
|
+
wrap(async () => {
|
|
46942
|
+
const s = await deps.journal.state();
|
|
46943
|
+
const r = massacre(s.missions, s.events);
|
|
46944
|
+
return {
|
|
46945
|
+
targets: r.groups,
|
|
46946
|
+
uncounted: r.uncounted.map((m) => ({ id: m.id, name: m.name, expires: m.expires })),
|
|
46947
|
+
note: MASSACRE_NOTE,
|
|
46948
|
+
...freshness(s)
|
|
46949
|
+
};
|
|
46950
|
+
})
|
|
46951
|
+
);
|
|
46952
|
+
server.registerTool(
|
|
46953
|
+
"journal_events",
|
|
46954
|
+
{
|
|
46955
|
+
annotations: { title: "Journal Events", ...READ_ONLY },
|
|
46956
|
+
description: "Raw events from the local game journal (last 14 days), newest first, as the game wrote them. Use it for anything the other journal tools do not cover, instead of reading the journal files. events limits the event names (for example Bounty or Docked); since is an ISO time; system keeps events in that system. limit defaults to 50 (at most 200). Arrays longer than 20 entries are cut, with a last entry that says how many were left out. " + FRESHNESS_TEXT,
|
|
46957
|
+
inputSchema: {
|
|
46958
|
+
events: external_exports.array(external_exports.string()).optional().describe("Event names, such as Bounty or Docked"),
|
|
46959
|
+
since: external_exports.string().optional().describe("Only events at or after this ISO time"),
|
|
46960
|
+
system: external_exports.string().optional().describe("Only events in this system"),
|
|
46961
|
+
limit: external_exports.number().int().min(1).max(200).default(50)
|
|
46962
|
+
}
|
|
46963
|
+
},
|
|
46964
|
+
wrap(async (a) => {
|
|
46965
|
+
const since = a.since === void 0 ? void 0 : Date.parse(a.since);
|
|
46966
|
+
if (since !== void 0 && Number.isNaN(since)) {
|
|
46967
|
+
throw new UserError(`since is not a time: ${a.since}. Use an ISO time.`);
|
|
46968
|
+
}
|
|
46969
|
+
const s = await deps.journal.state();
|
|
46970
|
+
const r = await deps.journal.recentEvents(
|
|
46971
|
+
{ events: a.events, since, system: a.system },
|
|
46972
|
+
a.limit
|
|
46973
|
+
);
|
|
46974
|
+
return {
|
|
46975
|
+
events: r.events.map(truncate),
|
|
46976
|
+
...freshness({ ...s, warnings: [...s.warnings, ...r.warnings] })
|
|
46977
|
+
};
|
|
46978
|
+
})
|
|
46979
|
+
);
|
|
46980
|
+
}
|
|
46981
|
+
function systemOf(given, current) {
|
|
46982
|
+
const system = given?.trim() || current;
|
|
46983
|
+
if (!system) {
|
|
46984
|
+
throw new UserError("The journal has no current system. Give a system name.");
|
|
46985
|
+
}
|
|
46986
|
+
return system;
|
|
46089
46987
|
}
|
|
46988
|
+
var notVisited = (system) => `The journal has no events in ${system} in the last 14 days. Check the system name.`;
|
|
46090
46989
|
|
|
46091
46990
|
// src/trade/loops.ts
|
|
46092
46991
|
function bestCargo(from, to, cargo, capital) {
|
|
@@ -46243,7 +47142,7 @@ var NO_LOOPS = "No profitable loop found. Try a larger radius, another centre, o
|
|
|
46243
47142
|
var PERMIT_SPARES = 30;
|
|
46244
47143
|
var PERMIT_EXTRA_MS = 8e3;
|
|
46245
47144
|
var Station = external_exports.object({ system: external_exports.string(), station: external_exports.string() });
|
|
46246
|
-
var
|
|
47145
|
+
var same4 = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
46247
47146
|
var tenth = (n) => Math.round(n * 10) / 10;
|
|
46248
47147
|
var minutes = (seconds) => tenth(seconds / 60);
|
|
46249
47148
|
var placeOf = (s) => ({
|
|
@@ -46448,11 +47347,11 @@ function registerLoopTools(server, deps) {
|
|
|
46448
47347
|
if (here) {
|
|
46449
47348
|
const name = centre.station;
|
|
46450
47349
|
let startMarket = found.stations.find(
|
|
46451
|
-
(st) =>
|
|
47350
|
+
(st) => same4(st.name, name) && same4(st.system, centre.system)
|
|
46452
47351
|
);
|
|
46453
47352
|
if (!startMarket) {
|
|
46454
47353
|
const info = await spansh.system(centre.system, STATION_LIMIT);
|
|
46455
|
-
const entry2 = info.stations.find((st) =>
|
|
47354
|
+
const entry2 = info.stations.find((st) => same4(st.name, name));
|
|
46456
47355
|
if (entry2?.market_id == null) {
|
|
46457
47356
|
throw new UserError(`Spansh has no market for ${name} in ${info.name}.`);
|
|
46458
47357
|
}
|
|
@@ -46641,7 +47540,7 @@ function registerLoopTools(server, deps) {
|
|
|
46641
47540
|
);
|
|
46642
47541
|
async function includeMarket(inc) {
|
|
46643
47542
|
const info = await spansh.system(inc.system, STATION_LIMIT);
|
|
46644
|
-
const id = info.stations.find((st) =>
|
|
47543
|
+
const id = info.stations.find((st) => same4(st.name, inc.station))?.market_id;
|
|
46645
47544
|
if (id == null) return null;
|
|
46646
47545
|
return spansh.stationMarket(id).catch((err) => {
|
|
46647
47546
|
if (err instanceof UserError) return null;
|
|
@@ -46650,6 +47549,116 @@ function registerLoopTools(server, deps) {
|
|
|
46650
47549
|
}
|
|
46651
47550
|
}
|
|
46652
47551
|
|
|
47552
|
+
// src/systems/groundcz.ts
|
|
47553
|
+
var AT_WAR = /* @__PURE__ */ new Set(["war", "civil war"]);
|
|
47554
|
+
var FRONTLINE = "Frontline Solutions";
|
|
47555
|
+
var PAD_RANK2 = { S: 1, M: 2, L: 3 };
|
|
47556
|
+
var padOk2 = (pad, min3) => !min3 || (PAD_RANK2[pad ?? ""] ?? 0) >= PAD_RANK2[min3];
|
|
47557
|
+
var smallestGap = (s) => s.settlements[0]?.gap_ls ?? Infinity;
|
|
47558
|
+
function groundCzTargets(systems, centre, opts) {
|
|
47559
|
+
const leftOut = { stale: 0, no_warring_settlements: 0, no_station_near: 0, pad_too_small: 0 };
|
|
47560
|
+
const targets = [];
|
|
47561
|
+
for (const system of systems) {
|
|
47562
|
+
const updated = system.updated_at ? Date.parse(system.updated_at) : NaN;
|
|
47563
|
+
const ageHours2 = Number.isNaN(updated) ? Infinity : (opts.now - updated) / 36e5;
|
|
47564
|
+
if (ageHours2 > opts.maxAgeHours) {
|
|
47565
|
+
leftOut.stale++;
|
|
47566
|
+
continue;
|
|
47567
|
+
}
|
|
47568
|
+
const warring = system.factions.flatMap((f) => {
|
|
47569
|
+
const state = f.active_states.find((s) => AT_WAR.has(s.toLowerCase()));
|
|
47570
|
+
return state ? [{ name: f.name, state }] : [];
|
|
47571
|
+
});
|
|
47572
|
+
const sides = new Set(warring.map((w) => w.name));
|
|
47573
|
+
const settlements = system.stations.filter(
|
|
47574
|
+
(s) => s.type === "Settlement" && s.owner !== null && sides.has(s.owner) && s.arrival_ls !== null
|
|
47575
|
+
);
|
|
47576
|
+
if (!settlements.length) {
|
|
47577
|
+
leftOut.no_warring_settlements++;
|
|
47578
|
+
continue;
|
|
47579
|
+
}
|
|
47580
|
+
const warringSettlements = {};
|
|
47581
|
+
for (const s of settlements)
|
|
47582
|
+
warringSettlements[s.owner] = (warringSettlements[s.owner] ?? 0) + 1;
|
|
47583
|
+
const stations = system.stations.filter((s) => s.services.includes(FRONTLINE) && s.arrival_ls !== null).map((s) => {
|
|
47584
|
+
const planetary = PLANETARY.test(s.type ?? "");
|
|
47585
|
+
const near = settlements.map((x) => ({
|
|
47586
|
+
name: x.name,
|
|
47587
|
+
owner: x.owner,
|
|
47588
|
+
arrival_ls: x.arrival_ls,
|
|
47589
|
+
gap_ls: Math.abs(x.arrival_ls - s.arrival_ls)
|
|
47590
|
+
})).filter((x) => x.gap_ls <= opts.maxGapLs).sort((a, b) => a.gap_ls - b.gap_ls);
|
|
47591
|
+
return {
|
|
47592
|
+
name: s.name,
|
|
47593
|
+
type: s.type,
|
|
47594
|
+
pad: s.pad,
|
|
47595
|
+
arrival_ls: s.arrival_ls,
|
|
47596
|
+
planetary,
|
|
47597
|
+
dock_s: opts.dockSeconds(s.arrival_ls, planetary),
|
|
47598
|
+
settlements: near
|
|
47599
|
+
};
|
|
47600
|
+
}).filter((s) => s.settlements.length > 0);
|
|
47601
|
+
const fit = stations.filter((s) => padOk2(s.pad, opts.minPad)).sort(
|
|
47602
|
+
(a, b) => a.dock_s - b.dock_s || b.settlements.length - a.settlements.length || smallestGap(a) - smallestGap(b)
|
|
47603
|
+
);
|
|
47604
|
+
if (!stations.length) {
|
|
47605
|
+
leftOut.no_station_near++;
|
|
47606
|
+
continue;
|
|
47607
|
+
}
|
|
47608
|
+
if (!fit.length) {
|
|
47609
|
+
leftOut.pad_too_small++;
|
|
47610
|
+
continue;
|
|
47611
|
+
}
|
|
47612
|
+
targets.push({
|
|
47613
|
+
system,
|
|
47614
|
+
distanceLy: distanceLy(system.coords, centre),
|
|
47615
|
+
ageHours: ageHours2,
|
|
47616
|
+
warring,
|
|
47617
|
+
best: fit[0],
|
|
47618
|
+
others: fit.slice(1),
|
|
47619
|
+
warringSettlements
|
|
47620
|
+
});
|
|
47621
|
+
}
|
|
47622
|
+
targets.sort(
|
|
47623
|
+
(a, b) => Math.floor(a.ageHours) - Math.floor(b.ageHours) || b.best.settlements.length - a.best.settlements.length || smallestGap(a.best) - smallestGap(b.best) || a.distanceLy - b.distanceLy
|
|
47624
|
+
);
|
|
47625
|
+
return { targets, leftOut };
|
|
47626
|
+
}
|
|
47627
|
+
var isStar = (b) => /Star$/.test(b.subType ?? "");
|
|
47628
|
+
function czDetail(best, bodies, maxGapLs) {
|
|
47629
|
+
const bodyOf = (station) => bodies.find((b) => b.stations.includes(station)) ?? null;
|
|
47630
|
+
const settlementBodies = {};
|
|
47631
|
+
for (const s of best.settlements) {
|
|
47632
|
+
const b = bodyOf(s.name);
|
|
47633
|
+
if (b) settlementBodies[s.name] = b.name;
|
|
47634
|
+
}
|
|
47635
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
47636
|
+
for (const name of Object.values(settlementBodies)) counts2.set(name, (counts2.get(name) ?? 0) + 1);
|
|
47637
|
+
const body_family = bodies.filter((b) => counts2.has(b.name)).map((b) => ({ name: b.name, subType: b.subType, settlements: counts2.get(b.name) }));
|
|
47638
|
+
const onBody = (b) => !!b && counts2.has(b.name);
|
|
47639
|
+
if (best.planetary) {
|
|
47640
|
+
const own2 = bodyOf(best.name);
|
|
47641
|
+
return {
|
|
47642
|
+
drop: onBody(own2) ? "same_body" : "gap_only",
|
|
47643
|
+
near_body: own2?.name ?? null,
|
|
47644
|
+
settlementBodies,
|
|
47645
|
+
body_family
|
|
47646
|
+
};
|
|
47647
|
+
}
|
|
47648
|
+
const near = bodies.filter((b) => !isStar(b) && Math.abs(b.arrival_ls - best.arrival_ls) <= maxGapLs).sort(
|
|
47649
|
+
(a, b) => Math.abs(a.arrival_ls - best.arrival_ls) - Math.abs(b.arrival_ls - best.arrival_ls)
|
|
47650
|
+
)[0];
|
|
47651
|
+
if (!near) return { drop: "gap_only", near_body: null, settlementBodies, body_family };
|
|
47652
|
+
const root = near.parentPlanetId ?? near.bodyId;
|
|
47653
|
+
const family2 = bodies.filter((b) => b.bodyId === root || b.parentPlanetId === root);
|
|
47654
|
+
return {
|
|
47655
|
+
drop: family2.some(onBody) ? "orbital" : "gap_only",
|
|
47656
|
+
near_body: near.name,
|
|
47657
|
+
settlementBodies,
|
|
47658
|
+
body_family
|
|
47659
|
+
};
|
|
47660
|
+
}
|
|
47661
|
+
|
|
46653
47662
|
// src/systems/massacre.ts
|
|
46654
47663
|
var SOURCE_RANGE_LY = 10;
|
|
46655
47664
|
var NO_MASSACRES = /* @__PURE__ */ new Set(["war", "civil war", "election"]);
|
|
@@ -46985,6 +47994,121 @@ function registerSystemTools(server, deps) {
|
|
|
46985
47994
|
}
|
|
46986
47995
|
)
|
|
46987
47996
|
);
|
|
47997
|
+
server.registerTool(
|
|
47998
|
+
"ground_cz_targets",
|
|
47999
|
+
{
|
|
48000
|
+
annotations: {
|
|
48001
|
+
title: "Find Ground Conflict Zones",
|
|
48002
|
+
readOnlyHint: true,
|
|
48003
|
+
destructiveHint: false,
|
|
48004
|
+
idempotentHint: true,
|
|
48005
|
+
openWorldHint: true
|
|
48006
|
+
},
|
|
48007
|
+
description: "Find likely ground (on-foot) conflict zones near you: systems with a faction at War or Civil War, settlements owned by the warring factions, and a Frontline Solutions station close to those settlements (within max_gap_ls of distance from the arrival star, so on the same planet or its moons) that meets the minimum pad; left_out.pad_too_small counts systems where only smaller-pad stations are close. Data older than max_age_hours (default 6) is left out: wars often end at the daily tick. The best station is the quickest to dock at (a planetary landing counts against it), then the one with most warring settlements nearby. For the top detail_count results, body detail says whether the station is a planetary port on the settlements' planet (drop: same_body) or an orbital station next to that planet and its moons (drop: orbital). drop: gap_only means the station and settlements are at about the same distance from the arrival star, but the body data could not confirm the same planet or its moons: check each settlement's body. drop: null (with near_body and body_family null, and no body on settlements) means no body detail was fetched: the result is beyond detail_count, the time limit was reached, or the fetch failed. Zones are likely, not confirmed. near defaults to the journal location.",
|
|
48008
|
+
inputSchema: {
|
|
48009
|
+
near: external_exports.string().optional().describe("Exact reference system name"),
|
|
48010
|
+
radius_ly: Radius,
|
|
48011
|
+
max_age_hours: external_exports.number().min(1).max(168).default(6),
|
|
48012
|
+
max_gap_ls: external_exports.number().min(0.1).max(20).default(1),
|
|
48013
|
+
detail_count: external_exports.number().int().min(0).max(15).default(8),
|
|
48014
|
+
limit: external_exports.number().int().min(1).max(100).default(20),
|
|
48015
|
+
preferences: PrefsPatchSchema.optional(),
|
|
48016
|
+
use_preferences: external_exports.boolean().default(true)
|
|
48017
|
+
}
|
|
48018
|
+
},
|
|
48019
|
+
wrap(
|
|
48020
|
+
async (a) => {
|
|
48021
|
+
const notes = [];
|
|
48022
|
+
const near = await resolveNear(a.near, notes);
|
|
48023
|
+
const rules = await resolveSearchRules(deps, a.preferences, a.use_preferences);
|
|
48024
|
+
const deadline = now() + TIME_LIMIT_MS2;
|
|
48025
|
+
const found = await deps.spansh.frontlineWarSystems(near, a.radius_ly, { deadline });
|
|
48026
|
+
const { targets, leftOut } = groundCzTargets(found.systems, found.centre, {
|
|
48027
|
+
now: now(),
|
|
48028
|
+
maxAgeHours: a.max_age_hours,
|
|
48029
|
+
maxGapLs: a.max_gap_ls,
|
|
48030
|
+
minPad: rules.pad.size,
|
|
48031
|
+
dockSeconds: (arrivalLs, planetary) => legSeconds({ distanceLy: 0, arrivalLs, planetary, stationKnown: true }, rules.travel).seconds
|
|
48032
|
+
});
|
|
48033
|
+
const shown = targets.slice(0, a.limit);
|
|
48034
|
+
const details = /* @__PURE__ */ new Map();
|
|
48035
|
+
let timedOut = 0;
|
|
48036
|
+
for (const t of shown.slice(0, a.detail_count)) {
|
|
48037
|
+
if (now() >= deadline) {
|
|
48038
|
+
timedOut++;
|
|
48039
|
+
continue;
|
|
48040
|
+
}
|
|
48041
|
+
try {
|
|
48042
|
+
const bodies = await deps.spansh.systemBodies(t.system.id64);
|
|
48043
|
+
if (!bodies.length)
|
|
48044
|
+
notes.push(`No body detail for ${t.system.name}: Spansh returned no bodies.`);
|
|
48045
|
+
else details.set(t.system.id64, czDetail(t.best, bodies, a.max_gap_ls));
|
|
48046
|
+
} catch (err) {
|
|
48047
|
+
notes.push(
|
|
48048
|
+
`No body detail for ${t.system.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
48049
|
+
);
|
|
48050
|
+
}
|
|
48051
|
+
}
|
|
48052
|
+
notes.push(
|
|
48053
|
+
"These are likely ground conflict zones, not confirmed: Spansh does not see live conflict zone signals, and a war can end at the daily tick."
|
|
48054
|
+
);
|
|
48055
|
+
if (timedOut) {
|
|
48056
|
+
notes.push(
|
|
48057
|
+
`${timedOut} ${timedOut === 1 ? "result is" : "results are"} without body detail: the time limit was reached. Call again to fill them in.`
|
|
48058
|
+
);
|
|
48059
|
+
}
|
|
48060
|
+
if (found.partial) {
|
|
48061
|
+
notes.push("Spansh did not return every system in time; results may be missing.");
|
|
48062
|
+
}
|
|
48063
|
+
const minutes2 = (s) => Math.round(s / 6) / 10;
|
|
48064
|
+
return {
|
|
48065
|
+
near,
|
|
48066
|
+
searched: found.systems.length,
|
|
48067
|
+
left_out: leftOut,
|
|
48068
|
+
partial: found.partial,
|
|
48069
|
+
travel: describeRules(rules).travel,
|
|
48070
|
+
targets: shown.map((t) => {
|
|
48071
|
+
const d = details.get(t.system.id64) ?? null;
|
|
48072
|
+
const l = leg(t.distanceLy, rules.travel);
|
|
48073
|
+
return {
|
|
48074
|
+
system: t.system.name,
|
|
48075
|
+
distance_ly: Math.round(t.distanceLy * 100) / 100,
|
|
48076
|
+
jumps: l.jumps,
|
|
48077
|
+
travel_min: minutes2(l.seconds),
|
|
48078
|
+
age_hours: Math.round(t.ageHours * 10) / 10,
|
|
48079
|
+
updated_at: t.system.updated_at,
|
|
48080
|
+
warring_factions: t.warring,
|
|
48081
|
+
best_station: {
|
|
48082
|
+
name: t.best.name,
|
|
48083
|
+
type: t.best.type,
|
|
48084
|
+
pad: t.best.pad,
|
|
48085
|
+
arrival_ls: Math.round(t.best.arrival_ls * 100) / 100,
|
|
48086
|
+
dock_min: minutes2(t.best.dock_s),
|
|
48087
|
+
drop: d?.drop ?? null,
|
|
48088
|
+
near_body: d?.near_body ?? null,
|
|
48089
|
+
settlements: t.best.settlements.map((s) => ({
|
|
48090
|
+
name: s.name,
|
|
48091
|
+
owner: s.owner,
|
|
48092
|
+
gap_ls: Math.round(s.gap_ls * 1e3) / 1e3,
|
|
48093
|
+
...d ? { body: d.settlementBodies[s.name] ?? null } : {}
|
|
48094
|
+
}))
|
|
48095
|
+
},
|
|
48096
|
+
other_frontline_stations: t.others.map((s) => ({
|
|
48097
|
+
name: s.name,
|
|
48098
|
+
type: s.type,
|
|
48099
|
+
pad: s.pad,
|
|
48100
|
+
arrival_ls: Math.round(s.arrival_ls * 100) / 100,
|
|
48101
|
+
settlements: s.settlements.length
|
|
48102
|
+
})),
|
|
48103
|
+
warring_settlements: t.warringSettlements,
|
|
48104
|
+
body_family: d?.body_family ?? null
|
|
48105
|
+
};
|
|
48106
|
+
}),
|
|
48107
|
+
notes
|
|
48108
|
+
};
|
|
48109
|
+
}
|
|
48110
|
+
)
|
|
48111
|
+
);
|
|
46988
48112
|
}
|
|
46989
48113
|
|
|
46990
48114
|
// src/tools/inara.ts
|
|
@@ -47057,8 +48181,8 @@ function registerEdsmTools(server, deps) {
|
|
|
47057
48181
|
}
|
|
47058
48182
|
|
|
47059
48183
|
// src/routes/pool.ts
|
|
47060
|
-
var
|
|
47061
|
-
var
|
|
48184
|
+
var lc2 = (s) => s.toLowerCase();
|
|
48185
|
+
var namesOf2 = (s) => [s.label, ...s.aliases];
|
|
47062
48186
|
function joinPurposes(a, b) {
|
|
47063
48187
|
const parts = [a, b].flatMap((p) => p ? p.split("; ") : []);
|
|
47064
48188
|
return parts.length ? [...new Set(parts)].join("; ") : null;
|
|
@@ -47067,8 +48191,8 @@ function buildPool(a, catalog) {
|
|
|
47067
48191
|
const used = /* @__PURE__ */ new Set();
|
|
47068
48192
|
const unique = (label) => {
|
|
47069
48193
|
let candidate = label;
|
|
47070
|
-
for (let n = 2; used.has(
|
|
47071
|
-
used.add(
|
|
48194
|
+
for (let n = 2; used.has(lc2(candidate)); n++) candidate = `${label}-${n}`;
|
|
48195
|
+
used.add(lc2(candidate));
|
|
47072
48196
|
return candidate;
|
|
47073
48197
|
};
|
|
47074
48198
|
const resolveEngineer = (name) => {
|
|
@@ -47087,13 +48211,16 @@ function buildPool(a, catalog) {
|
|
|
47087
48211
|
);
|
|
47088
48212
|
}
|
|
47089
48213
|
const station = input2.station ?? (input2.system ? void 0 : engineer2?.base);
|
|
47090
|
-
const key = engineer2 ? `${
|
|
47091
|
-
const name = input2.label ?? engineer2?.name ?? input2.station ?? system;
|
|
48214
|
+
const key = engineer2 ? `${lc2(engineer2.system)}|${lc2(engineer2.base)}` : `${lc2(system)}|${lc2(station ?? "")}${!station && input2.body ? `|${lc2(input2.body)}` : ""}`;
|
|
48215
|
+
const name = input2.label ?? engineer2?.name ?? input2.station ?? input2.body ?? system;
|
|
47092
48216
|
const hit = byKey.get(key);
|
|
47093
48217
|
if (hit) {
|
|
47094
|
-
if (!
|
|
48218
|
+
if (!namesOf2(hit).some((n) => lc2(n) === lc2(name))) hit.aliases.push(unique(name));
|
|
47095
48219
|
if (input2.after) hit.after = [.../* @__PURE__ */ new Set([...hit.after ?? [], ...input2.after])];
|
|
47096
48220
|
hit.purpose = joinPurposes(hit.purpose, over.purpose ?? null);
|
|
48221
|
+
hit.notes = joinPurposes(hit.notes, over.notes ?? null);
|
|
48222
|
+
hit.body ??= input2.body ?? null;
|
|
48223
|
+
if (over.planStop !== void 0) hit.planStops.push(over.planStop);
|
|
47097
48224
|
hit.engineer ??= engineer2;
|
|
47098
48225
|
if (engineer2 || (over.kind ?? input2.kind) === "engineer") hit.kind = "engineer";
|
|
47099
48226
|
return hit;
|
|
@@ -47104,34 +48231,43 @@ function buildPool(a, catalog) {
|
|
|
47104
48231
|
aliases: [],
|
|
47105
48232
|
system,
|
|
47106
48233
|
station: station ?? null,
|
|
48234
|
+
body: input2.body ?? null,
|
|
47107
48235
|
engineer: engineer2,
|
|
47108
48236
|
kind: over.kind ?? input2.kind ?? (engineer2 ? "engineer" : "other"),
|
|
47109
48237
|
purpose: over.purpose ?? null,
|
|
47110
|
-
|
|
48238
|
+
notes: over.notes ?? null,
|
|
48239
|
+
after: input2.after,
|
|
48240
|
+
planStops: over.planStop === void 0 ? [] : [over.planStop]
|
|
47111
48241
|
};
|
|
47112
48242
|
byKey.set(key, stop);
|
|
47113
48243
|
return stop;
|
|
47114
48244
|
};
|
|
47115
48245
|
const entries = a.stops ?? [];
|
|
47116
48246
|
const candidates = [];
|
|
48247
|
+
const finished = /* @__PURE__ */ new Map();
|
|
47117
48248
|
if (a.plan) {
|
|
48249
|
+
const planStops = a.plan.stops.filter((s) => s.status !== "done" && s.status !== "skipped");
|
|
48250
|
+
for (const s of a.plan.stops) {
|
|
48251
|
+
if (s.status !== "done" && s.status !== "skipped") continue;
|
|
48252
|
+
for (const n of [s.label, s.station, s.body, s.system]) if (n) finished.set(lc2(n), n);
|
|
48253
|
+
}
|
|
47118
48254
|
const named = /* @__PURE__ */ new Set();
|
|
47119
|
-
for (const s of
|
|
48255
|
+
for (const s of planStops) if (s.kind === "engineer" && s.label) named.add(lc2(s.label));
|
|
47120
48256
|
for (const e of entries) {
|
|
47121
48257
|
if ("one_of" in e) continue;
|
|
47122
|
-
if (e.engineer) named.add(
|
|
47123
|
-
if (e.kind === "engineer" && e.label) named.add(
|
|
48258
|
+
if (e.engineer) named.add(lc2(e.engineer));
|
|
48259
|
+
if (e.kind === "engineer" && e.label) named.add(lc2(e.label));
|
|
47124
48260
|
}
|
|
47125
48261
|
const byEngineer = /* @__PURE__ */ new Map();
|
|
47126
48262
|
for (const item of a.plan.items) {
|
|
47127
48263
|
if (!item.engineer || item.status === "done" || item.status === "skipped") continue;
|
|
47128
|
-
const entry2 = byEngineer.get(
|
|
48264
|
+
const entry2 = byEngineer.get(lc2(item.engineer)) ?? {
|
|
47129
48265
|
id: item.id,
|
|
47130
48266
|
name: item.engineer,
|
|
47131
48267
|
descriptions: []
|
|
47132
48268
|
};
|
|
47133
48269
|
entry2.descriptions.push(item.description);
|
|
47134
|
-
byEngineer.set(
|
|
48270
|
+
byEngineer.set(lc2(item.engineer), entry2);
|
|
47135
48271
|
}
|
|
47136
48272
|
const baseFor = /* @__PURE__ */ new Map();
|
|
47137
48273
|
if (catalog) {
|
|
@@ -47142,26 +48278,27 @@ function buildPool(a, catalog) {
|
|
|
47142
48278
|
} catch {
|
|
47143
48279
|
continue;
|
|
47144
48280
|
}
|
|
47145
|
-
const here =
|
|
47146
|
-
(s) => s.kind === "engineer" && !s.station && !baseFor.has(s) &&
|
|
48281
|
+
const here = planStops.filter(
|
|
48282
|
+
(s) => s.kind === "engineer" && !s.station && !baseFor.has(s) && lc2(s.system ?? "") === lc2(e.system)
|
|
47147
48283
|
);
|
|
47148
|
-
const mentions = (s) =>
|
|
48284
|
+
const mentions = (s) => lc2(`${s.label ?? ""} ${s.purpose ?? ""}`).includes(lc2(e.name));
|
|
47149
48285
|
const match = here.find(mentions) ?? (named.has(key) ? void 0 : here[0]);
|
|
47150
48286
|
if (match) baseFor.set(match, { base: e.base, engineer: e.name });
|
|
47151
48287
|
}
|
|
47152
48288
|
}
|
|
47153
|
-
for (const s of
|
|
48289
|
+
for (const s of planStops) {
|
|
47154
48290
|
candidates.push({
|
|
47155
48291
|
fixed: make(
|
|
47156
48292
|
{
|
|
47157
48293
|
label: s.label,
|
|
47158
48294
|
system: s.system,
|
|
47159
48295
|
station: s.station ?? baseFor.get(s)?.base,
|
|
48296
|
+
body: s.body,
|
|
47160
48297
|
engineer: baseFor.get(s)?.engineer,
|
|
47161
48298
|
kind: s.kind,
|
|
47162
48299
|
after: s.after
|
|
47163
48300
|
},
|
|
47164
|
-
{ purpose: s.purpose }
|
|
48301
|
+
{ purpose: s.purpose, notes: s.notes, planStop: a.plan.stops.indexOf(s) }
|
|
47165
48302
|
)
|
|
47166
48303
|
});
|
|
47167
48304
|
}
|
|
@@ -47193,26 +48330,30 @@ function buildPool(a, catalog) {
|
|
|
47193
48330
|
const notes = [];
|
|
47194
48331
|
const known = /* @__PURE__ */ new Map();
|
|
47195
48332
|
for (const c of candidates) {
|
|
47196
|
-
if ("group" in c) known.set(
|
|
48333
|
+
if ("group" in c) known.set(lc2(c.group), c.group);
|
|
47197
48334
|
for (const s of "fixed" in c ? [c.fixed] : c.options) {
|
|
47198
|
-
for (const n of
|
|
48335
|
+
for (const n of namesOf2(s)) known.set(lc2(n), n);
|
|
47199
48336
|
}
|
|
47200
48337
|
}
|
|
47201
48338
|
for (const l of [...a.only ?? [], ...a.skip ?? []]) {
|
|
47202
|
-
if (!known.has(
|
|
48339
|
+
if (!known.has(lc2(l)) && finished.has(lc2(l))) {
|
|
48340
|
+
notes.push(`"${l}" is done or skipped in the plan, so the route leaves it out.`);
|
|
48341
|
+
continue;
|
|
48342
|
+
}
|
|
48343
|
+
if (!known.has(lc2(l))) {
|
|
47203
48344
|
throw new UserError(`Unknown stop label "${l}". Known: ${[...known.values()].join(", ")}`);
|
|
47204
48345
|
}
|
|
47205
48346
|
}
|
|
47206
|
-
const only = a.only ? new Set(a.only.map(
|
|
47207
|
-
const skip = new Set((a.skip ?? []).map(
|
|
48347
|
+
const only = a.only ? new Set(a.only.map(lc2)) : null;
|
|
48348
|
+
const skip = new Set((a.skip ?? []).map(lc2));
|
|
47208
48349
|
const keep = (s, viaGroup) => {
|
|
47209
|
-
const names =
|
|
48350
|
+
const names = namesOf2(s).map(lc2);
|
|
47210
48351
|
return (!only || viaGroup || names.some((n) => only.has(n))) && !names.some((n) => skip.has(n));
|
|
47211
48352
|
};
|
|
47212
48353
|
const kept = candidates.map((c) => {
|
|
47213
48354
|
if ("fixed" in c) return keep(c.fixed, false) ? [c.fixed] : [];
|
|
47214
|
-
if (skip.has(
|
|
47215
|
-
const viaGroup = !!only?.has(
|
|
48355
|
+
if (skip.has(lc2(c.group))) return [];
|
|
48356
|
+
const viaGroup = !!only?.has(lc2(c.group));
|
|
47216
48357
|
return [...new Set(c.options.filter((o) => keep(o, viaGroup)))];
|
|
47217
48358
|
});
|
|
47218
48359
|
const fixedStops = new Set(
|
|
@@ -47230,8 +48371,8 @@ function buildPool(a, catalog) {
|
|
|
47230
48371
|
}
|
|
47231
48372
|
const met = options.length > 1 ? options.find((o) => fixedStops.has(o)) : void 0;
|
|
47232
48373
|
groupLabels.set(
|
|
47233
|
-
|
|
47234
|
-
(met ? [met] : options).map((o) =>
|
|
48374
|
+
lc2(c.group),
|
|
48375
|
+
(met ? [met] : options).map((o) => lc2(o.label))
|
|
47235
48376
|
);
|
|
47236
48377
|
if (options.length === 0) {
|
|
47237
48378
|
notes.push(`Group "${c.group}" has no stops left after only/skip; dropped.`);
|
|
@@ -47248,17 +48389,17 @@ function buildPool(a, catalog) {
|
|
|
47248
48389
|
});
|
|
47249
48390
|
if (stops.length === 0 && groups.length === 0) throw new UserError("No stops to route.");
|
|
47250
48391
|
const all = [.../* @__PURE__ */ new Set([...stops, ...groups.flatMap((g) => g.options)])];
|
|
47251
|
-
const present = new Map(all.flatMap((s) =>
|
|
48392
|
+
const present = new Map(all.flatMap((s) => namesOf2(s).map((n) => [lc2(n), lc2(s.label)])));
|
|
47252
48393
|
const rulesApplied = [];
|
|
47253
48394
|
const after = /* @__PURE__ */ new Map();
|
|
47254
48395
|
const planFallback = !!a.plan && a.planRules;
|
|
47255
48396
|
const fallbackTargets = all.filter((s) => s.kind === "shop" || s.kind === "trader");
|
|
47256
48397
|
let fallbackUsed = false;
|
|
47257
48398
|
for (const s of all) {
|
|
47258
|
-
const key =
|
|
48399
|
+
const key = lc2(s.label);
|
|
47259
48400
|
if (s.after === void 0) {
|
|
47260
48401
|
if (!planFallback || s.kind !== "engineer") continue;
|
|
47261
|
-
const targets = fallbackTargets.filter((t) => t !== s).map((t) =>
|
|
48402
|
+
const targets = fallbackTargets.filter((t) => t !== s).map((t) => lc2(t.label));
|
|
47262
48403
|
if (targets.length === 0) continue;
|
|
47263
48404
|
after.set(key, targets);
|
|
47264
48405
|
fallbackUsed = true;
|
|
@@ -47267,8 +48408,8 @@ function buildPool(a, catalog) {
|
|
|
47267
48408
|
const resolved = [];
|
|
47268
48409
|
const named = [];
|
|
47269
48410
|
for (const l of s.after) {
|
|
47270
|
-
const main2 = present.get(
|
|
47271
|
-
const expanded = groupLabels.get(
|
|
48411
|
+
const main2 = present.get(lc2(l));
|
|
48412
|
+
const expanded = groupLabels.get(lc2(l)) ?? (main2 ? [main2] : []);
|
|
47272
48413
|
const targets = expanded.filter((t) => t !== key);
|
|
47273
48414
|
if (targets.length === 0) {
|
|
47274
48415
|
notes.push(`Rule "${s.label} after ${l}" ignored: ${l} is not in this route.`);
|
|
@@ -47341,16 +48482,17 @@ function solveRoute(input2) {
|
|
|
47341
48482
|
|
|
47342
48483
|
// src/tools/routes.ts
|
|
47343
48484
|
var min2 = (s) => Math.round(s / 6) / 10;
|
|
47344
|
-
var
|
|
48485
|
+
var lc3 = (s) => s.toLowerCase();
|
|
47345
48486
|
var LONG_LEG_LY = 200;
|
|
47346
48487
|
var PLOT_ROUNDS = 2;
|
|
47347
48488
|
var PLOT_DIFF = 0.2;
|
|
47348
48489
|
var PLOT_PENDING_NOTE = "Spansh is still plotting; call again to finish.";
|
|
47349
48490
|
var PLOT_BUDGET_MS = 4e4;
|
|
47350
48491
|
var StopInputSchema = external_exports.object({
|
|
47351
|
-
label: external_exports.string().min(1).optional().describe("Name for rules and output; default the engineer, station or system"),
|
|
48492
|
+
label: external_exports.string().min(1).optional().describe("Name for rules and output; default the engineer, station, body or system"),
|
|
47352
48493
|
system: external_exports.string().optional(),
|
|
47353
48494
|
station: external_exports.string().optional(),
|
|
48495
|
+
body: external_exports.string().min(1).optional().describe("Planet or moon, for a stop with no station"),
|
|
47354
48496
|
engineer: external_exports.string().optional().describe("Places the stop from the engineer catalog"),
|
|
47355
48497
|
kind: StopKind.optional(),
|
|
47356
48498
|
after: external_exports.array(external_exports.string()).optional().describe("Labels of stops that must come before this one")
|
|
@@ -47374,7 +48516,7 @@ function registerRouteTools(server, deps) {
|
|
|
47374
48516
|
idempotentHint: true,
|
|
47375
48517
|
openWorldHint: true
|
|
47376
48518
|
},
|
|
47377
|
-
description: "Order the stops
|
|
48519
|
+
description: "Order the stops of a plan (plan_id) or a list (stops): shops, material traders and engineers for a build, or the systems, bodies and stations of a route. Plan stops marked done or skipped are left out. Uses Spansh coordinates and the travel-time model. For each engineer stop, set `after` to the shops and traders that supply what that engineer needs (the module to buy, the materials to trade). Give `after: []` to an engineer who needs nothing from the other stops, so they can come first. Without your rules, a plan puts every engineer after every shop and trader. Use `one_of` for alternatives, for example two engineers who both do the work; the tool tries every combination (at most 16) and keeps the fastest. `only` and `skip` choose stops from the plan, so one plan can serve several trips. One place (the same engineer, or the same system and station) can be an option in several groups; it is visited once, and each stop's `meets` lists the groups it covers. Leg times are estimates from straight-line jumps; pass plot: true to plot the chosen route with Spansh.",
|
|
47378
48520
|
inputSchema: {
|
|
47379
48521
|
plan_id: external_exports.string().optional(),
|
|
47380
48522
|
stops: external_exports.array(StopEntrySchema).optional(),
|
|
@@ -47409,9 +48551,9 @@ function registerRouteTools(server, deps) {
|
|
|
47409
48551
|
countCombinations(pool.groups.map((g) => g.options.length));
|
|
47410
48552
|
const all = [.../* @__PURE__ */ new Set([...pool.stops, ...pool.groups.flatMap((g) => g.options)])];
|
|
47411
48553
|
const names = (s) => [s.label, ...s.aliases];
|
|
47412
|
-
const labelIndex = new Map(all.map((s, i) => [
|
|
48554
|
+
const labelIndex = new Map(all.map((s, i) => [lc3(s.label), i]));
|
|
47413
48555
|
const graph = all.map(
|
|
47414
|
-
(s) => (pool.after.get(
|
|
48556
|
+
(s) => (pool.after.get(lc3(s.label)) ?? []).flatMap((l) => labelIndex.get(l) ?? [])
|
|
47415
48557
|
);
|
|
47416
48558
|
const cycle = findCycle(graph);
|
|
47417
48559
|
if (cycle) {
|
|
@@ -47449,7 +48591,7 @@ function registerRouteTools(server, deps) {
|
|
|
47449
48591
|
const seen = nodeOfStop.get(s);
|
|
47450
48592
|
if (seen !== void 0) return seen;
|
|
47451
48593
|
const p = await placeStop(s);
|
|
47452
|
-
const key2 = p.station ? `${
|
|
48594
|
+
const key2 = p.station ? `${lc3(p.system)}|${lc3(p.station)}` : null;
|
|
47453
48595
|
const hit = merge2 && key2 ? byStation.get(key2) : void 0;
|
|
47454
48596
|
let idx;
|
|
47455
48597
|
if (hit !== void 0) {
|
|
@@ -47458,6 +48600,9 @@ function registerRouteTools(server, deps) {
|
|
|
47458
48600
|
node2.engineer ??= s.engineer?.name ?? null;
|
|
47459
48601
|
if (s.kind === "engineer" || s.engineer) node2.kind = "engineer";
|
|
47460
48602
|
node2.purpose = joinPurposes(node2.purpose, s.purpose);
|
|
48603
|
+
node2.notes = joinPurposes(node2.notes, s.notes);
|
|
48604
|
+
node2.body ??= s.body;
|
|
48605
|
+
node2.planStops.push(...s.planStops);
|
|
47461
48606
|
idx = hit;
|
|
47462
48607
|
} else {
|
|
47463
48608
|
nodes.push({
|
|
@@ -47465,12 +48610,15 @@ function registerRouteTools(server, deps) {
|
|
|
47465
48610
|
place: p,
|
|
47466
48611
|
engineer: s.engineer?.name ?? null,
|
|
47467
48612
|
kind: s.kind,
|
|
47468
|
-
purpose: s.purpose
|
|
48613
|
+
purpose: s.purpose,
|
|
48614
|
+
body: s.body,
|
|
48615
|
+
notes: s.notes,
|
|
48616
|
+
planStops: [...s.planStops]
|
|
47469
48617
|
});
|
|
47470
48618
|
idx = nodes.length - 1;
|
|
47471
48619
|
if (merge2 && key2) byStation.set(key2, idx);
|
|
47472
48620
|
}
|
|
47473
|
-
nodeOf.set(
|
|
48621
|
+
nodeOf.set(lc3(s.label), idx);
|
|
47474
48622
|
nodeOfStop.set(s, idx);
|
|
47475
48623
|
return idx;
|
|
47476
48624
|
};
|
|
@@ -47533,7 +48681,7 @@ function registerRouteTools(server, deps) {
|
|
|
47533
48681
|
...returnPlace ? [returnPlace] : []
|
|
47534
48682
|
];
|
|
47535
48683
|
const leg = (from, to) => legBetween(places[from], places[to], travel);
|
|
47536
|
-
const sameSystem = (f, t) =>
|
|
48684
|
+
const sameSystem = (f, t) => lc3(places[f].system) === lc3(places[t].system);
|
|
47537
48685
|
const plottedJumps = /* @__PURE__ */ new Map();
|
|
47538
48686
|
const unfinished = /* @__PURE__ */ new Set();
|
|
47539
48687
|
const failed = /* @__PURE__ */ new Map();
|
|
@@ -47627,6 +48775,8 @@ function registerRouteTools(server, deps) {
|
|
|
47627
48775
|
engineer: node2?.engineer ?? null,
|
|
47628
48776
|
kind: node2 ? node2.kind : "other",
|
|
47629
48777
|
purpose: node2 ? node2.purpose : null,
|
|
48778
|
+
...node2?.body ? { body: node2.body } : {},
|
|
48779
|
+
...node2?.notes ? { notes: node2.notes } : {},
|
|
47630
48780
|
meets: allChosen.filter((c) => c.option === t).map((c) => c.group),
|
|
47631
48781
|
jumps: plottedJumps.get(key(f, t)) ?? l.jumps,
|
|
47632
48782
|
leg_min: min2(seconds),
|
|
@@ -47662,18 +48812,26 @@ function registerRouteTools(server, deps) {
|
|
|
47662
48812
|
let saved = false;
|
|
47663
48813
|
if (a.save_to_plan && a.plan_id) {
|
|
47664
48814
|
try {
|
|
47665
|
-
await deps.plans.setRoute(
|
|
47666
|
-
|
|
47667
|
-
|
|
47668
|
-
|
|
47669
|
-
|
|
47670
|
-
|
|
47671
|
-
|
|
47672
|
-
|
|
47673
|
-
|
|
47674
|
-
|
|
47675
|
-
|
|
47676
|
-
|
|
48815
|
+
await deps.plans.setRoute(
|
|
48816
|
+
a.plan_id,
|
|
48817
|
+
{
|
|
48818
|
+
computed_at: new Date(now()).toISOString(),
|
|
48819
|
+
from: startPlace.system,
|
|
48820
|
+
stops: [
|
|
48821
|
+
...solved.order.map(optionLabel),
|
|
48822
|
+
...returnPlace ? [
|
|
48823
|
+
`return to ${returnPlace.system}${returnPlace.station ? ` \u2014 ${returnPlace.station}` : ""}`
|
|
48824
|
+
] : []
|
|
48825
|
+
],
|
|
48826
|
+
total_min: total,
|
|
48827
|
+
plotted: a.plot && allPlotted,
|
|
48828
|
+
stop_indexes: [
|
|
48829
|
+
...solved.order.map((i) => [...new Set(nodes[i].planStops)]),
|
|
48830
|
+
...returnPlace ? [[]] : []
|
|
48831
|
+
]
|
|
48832
|
+
},
|
|
48833
|
+
{ stops: plan?.stops }
|
|
48834
|
+
);
|
|
47677
48835
|
saved = true;
|
|
47678
48836
|
} catch (err) {
|
|
47679
48837
|
notes.add(`The route was not saved to plan "${a.plan_id}": ${reason(err)}`);
|
|
@@ -47703,7 +48861,7 @@ function registerRouteTools(server, deps) {
|
|
|
47703
48861
|
|
|
47704
48862
|
// src/tools/plans.ts
|
|
47705
48863
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
47706
|
-
import { mkdir as
|
|
48864
|
+
import { mkdir as mkdir7, readFile as readFile7, rename as rename5, stat as stat7, unlink as unlink3, writeFile as writeFile5 } from "fs/promises";
|
|
47707
48865
|
import path13 from "path";
|
|
47708
48866
|
var MAX_ECHOED_PLAN = 100 * 1024;
|
|
47709
48867
|
function registerPlanTools(server, deps) {
|
|
@@ -47743,7 +48901,7 @@ ${tail}`);
|
|
|
47743
48901
|
idempotentHint: true,
|
|
47744
48902
|
openWorldHint: false
|
|
47745
48903
|
},
|
|
47746
|
-
description: "List saved
|
|
48904
|
+
description: "List saved plans with item counts, progress and next_stop (the first stop still to do). A plan is a ship build or a route, or both."
|
|
47747
48905
|
},
|
|
47748
48906
|
wrapWarn(async () => plans.list(), warnings)
|
|
47749
48907
|
);
|
|
@@ -47757,10 +48915,13 @@ ${tail}`);
|
|
|
47757
48915
|
idempotentHint: true,
|
|
47758
48916
|
openWorldHint: false
|
|
47759
48917
|
},
|
|
47760
|
-
description: "Get a saved plan by id.",
|
|
48918
|
+
description: "Get a saved plan (a build or a route) by id. next_stop is the first stop still to do, in the saved route's order when there is one, else in the order of stops.",
|
|
47761
48919
|
inputSchema: { id: external_exports.string() }
|
|
47762
48920
|
},
|
|
47763
|
-
wrapWarn(async ({ id }) =>
|
|
48921
|
+
wrapWarn(async ({ id }) => {
|
|
48922
|
+
const plan = await plans.get(id);
|
|
48923
|
+
return { ...plan, next_stop: nextStop(plan) };
|
|
48924
|
+
}, warnings)
|
|
47764
48925
|
);
|
|
47765
48926
|
server.registerTool(
|
|
47766
48927
|
"plan_upsert",
|
|
@@ -47772,7 +48933,7 @@ ${tail}`);
|
|
|
47772
48933
|
idempotentHint: true,
|
|
47773
48934
|
openWorldHint: false
|
|
47774
48935
|
},
|
|
47775
|
-
description: "Create or replace a build
|
|
48936
|
+
description: "Create or replace a plan. A plan is a ship build, a route, or both: for example system X, planet Y for bios, then planet Z, then station A to hand in. Items are things to buy, engineer, unlock, gather or trade, plus refits (moving modules between slots, storing modules, or a fitting decision). Stops are the places to visit, each with a system and optionally a station or body (planet or moon), a label, a kind, a status (todo, done, skipped), notes and after rules. Without a saved route, the order of stops is the visiting order; route is the order route_stops saved. Replacing keeps created_at.",
|
|
47776
48937
|
inputSchema: { plan: PlanInputSchema }
|
|
47777
48938
|
},
|
|
47778
48939
|
wrapWarn(
|
|
@@ -47821,6 +48982,54 @@ ${tail}`);
|
|
|
47821
48982
|
warnings
|
|
47822
48983
|
)
|
|
47823
48984
|
);
|
|
48985
|
+
server.registerTool(
|
|
48986
|
+
"plan_update_stop",
|
|
48987
|
+
{
|
|
48988
|
+
annotations: {
|
|
48989
|
+
title: "Update Plan Stop",
|
|
48990
|
+
readOnlyHint: false,
|
|
48991
|
+
destructiveHint: false,
|
|
48992
|
+
idempotentHint: true,
|
|
48993
|
+
openWorldHint: false
|
|
48994
|
+
},
|
|
48995
|
+
description: "Set the status (todo, done, skipped) and/or notes of one stop in a plan. Name the stop by label (its label, else its station, body or system) or by index in the plan's stops, from 0; give exactly one. Mark a stop done when the player has finished there; returns the plan with the new next_stop.",
|
|
48996
|
+
inputSchema: {
|
|
48997
|
+
plan_id: external_exports.string(),
|
|
48998
|
+
label: external_exports.string().min(1).optional(),
|
|
48999
|
+
index: external_exports.number().int().min(0).optional(),
|
|
49000
|
+
status: StopStatus.optional(),
|
|
49001
|
+
notes: external_exports.string().optional()
|
|
49002
|
+
}
|
|
49003
|
+
},
|
|
49004
|
+
wrapWarn(
|
|
49005
|
+
async (a) => {
|
|
49006
|
+
if (a.label === void 0 === (a.index === void 0)) {
|
|
49007
|
+
throw new UserError("Give exactly one of label or index to name the stop");
|
|
49008
|
+
}
|
|
49009
|
+
if (a.status === void 0 && a.notes === void 0) {
|
|
49010
|
+
throw new UserError("Give status or notes to update");
|
|
49011
|
+
}
|
|
49012
|
+
const which = a.label !== void 0 ? `"${a.label}"` : String(a.index);
|
|
49013
|
+
try {
|
|
49014
|
+
const plan = await plans.updateStop(
|
|
49015
|
+
a.plan_id,
|
|
49016
|
+
{ label: a.label, index: a.index },
|
|
49017
|
+
{ status: a.status, notes: a.notes }
|
|
49018
|
+
);
|
|
49019
|
+
return { ...plan, next_stop: nextStop(plan) };
|
|
49020
|
+
} catch (err) {
|
|
49021
|
+
const changes = [
|
|
49022
|
+
...a.status !== void 0 ? [`status: ${a.status}`] : [],
|
|
49023
|
+
...a.notes !== void 0 ? ["notes changed"] : []
|
|
49024
|
+
].join(", ");
|
|
49025
|
+
throw new UserError(
|
|
49026
|
+
`Stop ${which} in plan "${a.plan_id}" was not updated (${changes}). ${reason(err)}`
|
|
49027
|
+
);
|
|
49028
|
+
}
|
|
49029
|
+
},
|
|
49030
|
+
warnings
|
|
49031
|
+
)
|
|
49032
|
+
);
|
|
47824
49033
|
server.registerTool(
|
|
47825
49034
|
"plan_delete",
|
|
47826
49035
|
{
|
|
@@ -47871,13 +49080,13 @@ ${tail}`);
|
|
|
47871
49080
|
if (!a.overwrite && await exists(target)) {
|
|
47872
49081
|
throw new UserError(`${target} exists; pass overwrite: true to replace it`);
|
|
47873
49082
|
}
|
|
47874
|
-
await
|
|
49083
|
+
await mkdir7(path13.dirname(target), { recursive: true });
|
|
47875
49084
|
const tmp = `${target}.${process.pid}.${randomUUID2()}.tmp`;
|
|
47876
49085
|
try {
|
|
47877
49086
|
await writeFile5(tmp, text);
|
|
47878
49087
|
await rename5(tmp, target);
|
|
47879
49088
|
} catch (err) {
|
|
47880
|
-
await
|
|
49089
|
+
await unlink3(tmp).catch(() => void 0);
|
|
47881
49090
|
throw err;
|
|
47882
49091
|
}
|
|
47883
49092
|
});
|
|
@@ -47985,35 +49194,35 @@ var TOP = 5;
|
|
|
47985
49194
|
var NO_CATALOG = 'Module names need the game data catalog. Call any catalog tool (for example catalog_module) or cache_refresh with source "catalog".';
|
|
47986
49195
|
var cap = (s) => s[0].toUpperCase() + s.slice(1);
|
|
47987
49196
|
var titleCase = (symbol2) => symbol2.split("_").filter(Boolean).map(cap).join(" ");
|
|
47988
|
-
var
|
|
47989
|
-
var
|
|
49197
|
+
var str5 = (v) => typeof v === "string" ? v : void 0;
|
|
49198
|
+
var num6 = (v) => typeof v === "number" ? v : 0;
|
|
47990
49199
|
var list = (v) => Array.isArray(v) ? v : [];
|
|
47991
|
-
var
|
|
49200
|
+
var same5 = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
47992
49201
|
var byText = (a, b) => a.localeCompare(b);
|
|
47993
49202
|
function vsMean(price, mean) {
|
|
47994
49203
|
return mean > 0 ? Math.round((price - mean) / mean * 100) : null;
|
|
47995
49204
|
}
|
|
47996
49205
|
function marketRows(file2, commodity) {
|
|
47997
49206
|
const rows = list(file2.data.Items).map((i) => ({
|
|
47998
|
-
name:
|
|
47999
|
-
category:
|
|
49207
|
+
name: str5(i.Name_Localised) ?? str5(i.Name) ?? "?",
|
|
49208
|
+
category: str5(i.Category_Localised) ?? str5(i.Category) ?? "",
|
|
48000
49209
|
i
|
|
48001
49210
|
})).filter((r) => !commodity || r.name.toLowerCase().includes(commodity.toLowerCase())).sort((a, b) => byText(a.category, b.category) || byText(a.name, b.name));
|
|
48002
|
-
const sells = rows.filter((r) =>
|
|
49211
|
+
const sells = rows.filter((r) => num6(r.i.Stock) > 0 && num6(r.i.BuyPrice) > 0).map((r) => ({
|
|
48003
49212
|
name: r.name,
|
|
48004
49213
|
category: r.category,
|
|
48005
|
-
buy_price:
|
|
48006
|
-
stock:
|
|
48007
|
-
mean_price:
|
|
48008
|
-
vs_mean_pct: vsMean(
|
|
49214
|
+
buy_price: num6(r.i.BuyPrice),
|
|
49215
|
+
stock: num6(r.i.Stock),
|
|
49216
|
+
mean_price: num6(r.i.MeanPrice),
|
|
49217
|
+
vs_mean_pct: vsMean(num6(r.i.BuyPrice), num6(r.i.MeanPrice))
|
|
48009
49218
|
}));
|
|
48010
49219
|
const buys = rows.filter((r) => marketDemand(r.i) > 0).map((r) => ({
|
|
48011
49220
|
name: r.name,
|
|
48012
49221
|
category: r.category,
|
|
48013
|
-
sell_price:
|
|
49222
|
+
sell_price: num6(r.i.SellPrice),
|
|
48014
49223
|
demand: marketDemand(r.i),
|
|
48015
|
-
mean_price:
|
|
48016
|
-
vs_mean_pct: vsMean(
|
|
49224
|
+
mean_price: num6(r.i.MeanPrice),
|
|
49225
|
+
vs_mean_pct: vsMean(num6(r.i.SellPrice), num6(r.i.MeanPrice))
|
|
48017
49226
|
}));
|
|
48018
49227
|
return { sells, buys };
|
|
48019
49228
|
}
|
|
@@ -48025,7 +49234,7 @@ async function modules(file2, catalog, notes) {
|
|
|
48025
49234
|
const cat = await catalog?.peek().catch(() => null);
|
|
48026
49235
|
if (!cat) notes.push(NO_CATALOG);
|
|
48027
49236
|
return list(file2.data.Items).flatMap((i) => {
|
|
48028
|
-
const symbol2 =
|
|
49237
|
+
const symbol2 = str5(i.Name);
|
|
48029
49238
|
if (!symbol2) return [];
|
|
48030
49239
|
const found = cat?.moduleBySymbol(symbol2);
|
|
48031
49240
|
return [
|
|
@@ -48035,7 +49244,7 @@ async function modules(file2, catalog, notes) {
|
|
|
48035
49244
|
class: found?.class ?? null,
|
|
48036
49245
|
rating: found?.rating ?? null,
|
|
48037
49246
|
group: found?.group ?? null,
|
|
48038
|
-
price:
|
|
49247
|
+
price: num6(i.BuyPrice)
|
|
48039
49248
|
}
|
|
48040
49249
|
];
|
|
48041
49250
|
});
|
|
@@ -48064,8 +49273,8 @@ function groupModules(mods) {
|
|
|
48064
49273
|
}
|
|
48065
49274
|
function ships(file2) {
|
|
48066
49275
|
return list(file2.data.PriceList).map((s) => ({
|
|
48067
|
-
name:
|
|
48068
|
-
price:
|
|
49276
|
+
name: str5(s.ShipType_Localised) ?? titleCase(str5(s.ShipType) ?? "?"),
|
|
49277
|
+
price: num6(s.ShipPrice)
|
|
48069
49278
|
})).sort((a, b) => a.price - b.price);
|
|
48070
49279
|
}
|
|
48071
49280
|
function registerStationTools(server, deps) {
|
|
@@ -48109,7 +49318,7 @@ function registerStationTools(server, deps) {
|
|
|
48109
49318
|
notes.push(`No ${kind2} data. Open the ${kind2} screen at a station first.`);
|
|
48110
49319
|
continue;
|
|
48111
49320
|
}
|
|
48112
|
-
if (docked && !
|
|
49321
|
+
if (docked && !same5(file2.station, docked)) {
|
|
48113
49322
|
notes.push(
|
|
48114
49323
|
`${label} last opened at ${file2.station} (${file2.system}), not the station where you are docked.`
|
|
48115
49324
|
);
|
|
@@ -48757,6 +49966,7 @@ function registerSettingsTools(server, deps) {
|
|
|
48757
49966
|
const files = (await existing(localDir, [
|
|
48758
49967
|
"plans.json",
|
|
48759
49968
|
"preferences.json",
|
|
49969
|
+
"activity.json",
|
|
48760
49970
|
"secrets.json",
|
|
48761
49971
|
"secrets.enc"
|
|
48762
49972
|
])).filter((n) => path14.resolve(localDir, n) !== own2);
|
|
@@ -48805,6 +50015,13 @@ function createServer3(deps) {
|
|
|
48805
50015
|
warnings: deps.planWarnings ?? deps.syncWarnings
|
|
48806
50016
|
});
|
|
48807
50017
|
if (deps.prefs) registerPrefsTools(server, { prefs: deps.prefs, warnings: deps.syncWarnings });
|
|
50018
|
+
if (deps.activity) {
|
|
50019
|
+
registerActivityTools(server, {
|
|
50020
|
+
activity: deps.activity,
|
|
50021
|
+
journal: deps.journal,
|
|
50022
|
+
warnings: deps.syncWarnings
|
|
50023
|
+
});
|
|
50024
|
+
}
|
|
48808
50025
|
if (deps.settingsPage && deps.settings && deps.secrets) {
|
|
48809
50026
|
registerSettingsTools(server, {
|
|
48810
50027
|
page: deps.settingsPage,
|