ed-mcp 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -1
- package/dist/index.js +678 -60
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -8,7 +8,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
8
8
|
var __getProtoOf = Object.getPrototypeOf;
|
|
9
9
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
10
|
var __commonJS = (cb, mod) => function __require() {
|
|
11
|
-
|
|
11
|
+
try {
|
|
12
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw mod = 0, e;
|
|
15
|
+
}
|
|
12
16
|
};
|
|
13
17
|
var __export = (target, all) => {
|
|
14
18
|
for (var name in all)
|
|
@@ -3335,6 +3339,37 @@ var require_utils = __commonJS({
|
|
|
3335
3339
|
}
|
|
3336
3340
|
return output3.join("");
|
|
3337
3341
|
}
|
|
3342
|
+
function removeDotSegmentsRelative(path15) {
|
|
3343
|
+
if (path15 === "" || path15[0] === "/") {
|
|
3344
|
+
return removeDotSegments(path15);
|
|
3345
|
+
}
|
|
3346
|
+
const segments = path15.split("/");
|
|
3347
|
+
const last = segments.length - 1;
|
|
3348
|
+
const output3 = [];
|
|
3349
|
+
let trailingDirectory = false;
|
|
3350
|
+
for (let i = 0; i <= last; i++) {
|
|
3351
|
+
const segment = segments[i];
|
|
3352
|
+
if (segment === "." || segment === "..") {
|
|
3353
|
+
if (segment === "..") {
|
|
3354
|
+
if (output3.length !== 0 && output3[output3.length - 1] !== "..") {
|
|
3355
|
+
output3.pop();
|
|
3356
|
+
} else {
|
|
3357
|
+
output3.push("..");
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
trailingDirectory = i === last;
|
|
3361
|
+
} else {
|
|
3362
|
+
output3.push(segment);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
if (trailingDirectory && output3[output3.length - 1] !== "..") {
|
|
3366
|
+
output3.push("");
|
|
3367
|
+
}
|
|
3368
|
+
if (output3.length === 0 || output3[0] === "" || output3[0].includes(":")) {
|
|
3369
|
+
output3.unshift(".");
|
|
3370
|
+
}
|
|
3371
|
+
return output3.join("/");
|
|
3372
|
+
}
|
|
3338
3373
|
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3339
3374
|
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3340
3375
|
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
@@ -3595,6 +3630,7 @@ var require_utils = __commonJS({
|
|
|
3595
3630
|
encodeFragment,
|
|
3596
3631
|
escapePreservingEscapes,
|
|
3597
3632
|
removeDotSegments,
|
|
3633
|
+
removeDotSegmentsRelative,
|
|
3598
3634
|
isIPv4,
|
|
3599
3635
|
isUUID,
|
|
3600
3636
|
normalizeIPv6,
|
|
@@ -3818,16 +3854,18 @@ var require_schemes = __commonJS({
|
|
|
3818
3854
|
var require_fast_uri = __commonJS({
|
|
3819
3855
|
"node_modules/fast-uri/index.js"(exports, module) {
|
|
3820
3856
|
"use strict";
|
|
3821
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3857
|
+
var { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3822
3858
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3823
3859
|
var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
|
|
3824
3860
|
var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3861
|
+
var MALFORMED_HOST_ERROR = "URI host is malformed.";
|
|
3862
|
+
var FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
|
|
3863
|
+
function validateScheme(scheme) {
|
|
3864
|
+
const stringScheme = String(scheme);
|
|
3865
|
+
if (!VALID_SCHEME.test(stringScheme)) {
|
|
3828
3866
|
throw new TypeError(MALFORMED_SCHEME_ERROR);
|
|
3829
3867
|
}
|
|
3830
|
-
return
|
|
3868
|
+
return stringScheme;
|
|
3831
3869
|
}
|
|
3832
3870
|
function normalize(uri, options) {
|
|
3833
3871
|
if (typeof uri === "string") {
|
|
@@ -3864,6 +3902,9 @@ var require_fast_uri = __commonJS({
|
|
|
3864
3902
|
const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
|
|
3865
3903
|
const resolvedHost = resolved.host;
|
|
3866
3904
|
const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
|
|
3905
|
+
if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
|
|
3906
|
+
throw new Error(MALFORMED_HOST_ERROR);
|
|
3907
|
+
}
|
|
3867
3908
|
canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
|
|
3868
3909
|
const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !new RegExp("\\P{ASCII}", "u").test(resolvedHost);
|
|
3869
3910
|
if (resolved.error && !encodedASCIIHost) {
|
|
@@ -3905,14 +3946,15 @@ var require_fast_uri = __commonJS({
|
|
|
3905
3946
|
if (relative.path[0] === "/") {
|
|
3906
3947
|
target.path = removeDotSegments(relative.path);
|
|
3907
3948
|
} else {
|
|
3908
|
-
|
|
3949
|
+
const baseHasAuthority = base2.userinfo !== void 0 || base2.host !== void 0 || base2.port !== void 0;
|
|
3950
|
+
if (baseHasAuthority && !base2.path) {
|
|
3909
3951
|
target.path = "/" + relative.path;
|
|
3910
3952
|
} else if (!base2.path) {
|
|
3911
3953
|
target.path = relative.path;
|
|
3912
3954
|
} else {
|
|
3913
3955
|
target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative.path;
|
|
3914
3956
|
}
|
|
3915
|
-
target.path = removeDotSegments(target.path);
|
|
3957
|
+
target.path = base2.scheme === void 0 && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
|
|
3916
3958
|
}
|
|
3917
3959
|
target.query = relative.query;
|
|
3918
3960
|
}
|
|
@@ -3950,12 +3992,13 @@ var require_fast_uri = __commonJS({
|
|
|
3950
3992
|
const options = Object.assign({}, opts);
|
|
3951
3993
|
const uriTokens = [];
|
|
3952
3994
|
if (component.scheme) {
|
|
3953
|
-
component.scheme =
|
|
3995
|
+
component.scheme = validateScheme(component.scheme);
|
|
3954
3996
|
}
|
|
3955
3997
|
const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
|
|
3956
3998
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
3957
3999
|
const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
|
|
3958
|
-
const
|
|
4000
|
+
const schemelessReference = (component.scheme === void 0 || options.reference === "suffix") && !hasAuthority;
|
|
4001
|
+
const pathNoScheme = !options.skipEscape && schemelessReference;
|
|
3959
4002
|
if (component.path !== void 0) {
|
|
3960
4003
|
if (!options.skipEscape) {
|
|
3961
4004
|
component.path = serializePathEncoding(component.path, pathNoScheme);
|
|
@@ -3964,7 +4007,7 @@ var require_fast_uri = __commonJS({
|
|
|
3964
4007
|
}
|
|
3965
4008
|
}
|
|
3966
4009
|
if (options.reference !== "suffix" && component.scheme) {
|
|
3967
|
-
component.scheme =
|
|
4010
|
+
component.scheme = validateScheme(component.scheme);
|
|
3968
4011
|
uriTokens.push(component.scheme, ":");
|
|
3969
4012
|
}
|
|
3970
4013
|
const authority = recomposeAuthority(component);
|
|
@@ -3980,9 +4023,9 @@ var require_fast_uri = __commonJS({
|
|
|
3980
4023
|
if (component.path !== void 0) {
|
|
3981
4024
|
let s = component.path;
|
|
3982
4025
|
if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
|
|
3983
|
-
s = removeDotSegments(s);
|
|
4026
|
+
s = schemelessReference && s[0] !== "/" ? removeDotSegmentsRelative(s) : removeDotSegments(s);
|
|
3984
4027
|
}
|
|
3985
|
-
if (
|
|
4028
|
+
if (schemelessReference) {
|
|
3986
4029
|
s = serializePathEncoding(s, true);
|
|
3987
4030
|
}
|
|
3988
4031
|
if (authority === void 0 && s[0] === "/" && s[1] === "/") {
|
|
@@ -4029,16 +4072,34 @@ var require_fast_uri = __commonJS({
|
|
|
4029
4072
|
return hasMalformedPercentEncoding(matches2[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches2[6]) || hasMalformedPercentEncoding(matches2[7]) || hasMalformedPercentEncoding(matches2[8]);
|
|
4030
4073
|
}
|
|
4031
4074
|
function canonicalizeHost(parsed, options, schemeHandler, isIP) {
|
|
4075
|
+
if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
|
|
4076
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
4077
|
+
return true;
|
|
4078
|
+
}
|
|
4032
4079
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
4080
|
+
let url2;
|
|
4033
4081
|
try {
|
|
4034
|
-
|
|
4082
|
+
url2 = new URL("http://" + parsed.host);
|
|
4035
4083
|
} catch (e) {
|
|
4036
4084
|
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
4037
4085
|
return true;
|
|
4038
4086
|
}
|
|
4087
|
+
if (!isHostOnlyURL(url2)) {
|
|
4088
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
4089
|
+
return true;
|
|
4090
|
+
}
|
|
4091
|
+
parsed.host = url2.hostname;
|
|
4039
4092
|
}
|
|
4040
4093
|
return false;
|
|
4041
4094
|
}
|
|
4095
|
+
function hasForbiddenDomainHost(parsed, options, schemeHandler, isIP) {
|
|
4096
|
+
return Boolean(
|
|
4097
|
+
parsed.host && isIP === false && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host)
|
|
4098
|
+
);
|
|
4099
|
+
}
|
|
4100
|
+
function isHostOnlyURL(url2) {
|
|
4101
|
+
return url2.username === "" && url2.password === "" && url2.port === "" && url2.pathname === "/" && url2.search === "" && url2.hash === "" && url2.hostname !== "";
|
|
4102
|
+
}
|
|
4042
4103
|
function parseWithStatus(uri, opts) {
|
|
4043
4104
|
const options = Object.assign({}, opts);
|
|
4044
4105
|
const parsed = {
|
|
@@ -4093,9 +4154,8 @@ var require_fast_uri = __commonJS({
|
|
|
4093
4154
|
parsed.query = matches2[7];
|
|
4094
4155
|
parsed.fragment = matches2[8];
|
|
4095
4156
|
if (parsed.scheme !== void 0) {
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
parsed.scheme = decodedScheme.toLowerCase();
|
|
4157
|
+
if (VALID_SCHEME.test(parsed.scheme)) {
|
|
4158
|
+
parsed.scheme = parsed.scheme.toLowerCase();
|
|
4099
4159
|
} else {
|
|
4100
4160
|
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
|
|
4101
4161
|
malformedScheme = true;
|
|
@@ -4123,7 +4183,7 @@ var require_fast_uri = __commonJS({
|
|
|
4123
4183
|
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
|
|
4124
4184
|
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
|
|
4125
4185
|
if (malformedIPLiteral) {
|
|
4126
|
-
parsed.error = parsed.error ||
|
|
4186
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
4127
4187
|
malformedAuthorityOrPort = true;
|
|
4128
4188
|
}
|
|
4129
4189
|
} else {
|
|
@@ -4151,7 +4211,7 @@ var require_fast_uri = __commonJS({
|
|
|
4151
4211
|
if (!isIP) {
|
|
4152
4212
|
host = normalizePercentEncoding(host.toLowerCase());
|
|
4153
4213
|
}
|
|
4154
|
-
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4214
|
+
parsed.host = isIP || options.scheme !== "null" ? reescapeHostDelimiters(host, isIP) : host;
|
|
4155
4215
|
}
|
|
4156
4216
|
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4157
4217
|
if (parsed.path) {
|
|
@@ -9803,7 +9863,7 @@ ${content.join("\n")}
|
|
|
9803
9863
|
var version = {
|
|
9804
9864
|
major: 4,
|
|
9805
9865
|
minor: 6,
|
|
9806
|
-
patch:
|
|
9866
|
+
patch: 7
|
|
9807
9867
|
};
|
|
9808
9868
|
|
|
9809
9869
|
// node_modules/zod/v4/core/schemas.js
|
|
@@ -28692,7 +28752,7 @@ async function canonical(p) {
|
|
|
28692
28752
|
}
|
|
28693
28753
|
|
|
28694
28754
|
// src/version.ts
|
|
28695
|
-
var VERSION = "0.
|
|
28755
|
+
var VERSION = "0.5.1";
|
|
28696
28756
|
|
|
28697
28757
|
// src/config.ts
|
|
28698
28758
|
var ConfigError = class extends Error {
|
|
@@ -32080,25 +32140,159 @@ function parseSpanshJson(text) {
|
|
|
32080
32140
|
return JSON.parse(text.replace(BIG_ID, '$1"$2"'));
|
|
32081
32141
|
}
|
|
32082
32142
|
|
|
32143
|
+
// src/systems/sweep.ts
|
|
32144
|
+
function stationPad(s) {
|
|
32145
|
+
if (s.has_large_pad || (s.large_pads ?? 0) > 0) return "L";
|
|
32146
|
+
if ((s.medium_pads ?? 0) > 0) return "M";
|
|
32147
|
+
if ((s.small_pads ?? 0) > 0) return "S";
|
|
32148
|
+
return null;
|
|
32149
|
+
}
|
|
32150
|
+
var skipStation = (type) => type === "Drake-Class Carrier" || /Construction Depot/.test(type ?? "");
|
|
32151
|
+
function mapSystem(raw) {
|
|
32152
|
+
const { x, y, z: z2 } = raw;
|
|
32153
|
+
if (x == null || y == null || z2 == null) return null;
|
|
32154
|
+
return {
|
|
32155
|
+
name: raw.name,
|
|
32156
|
+
id64: String(raw.id64),
|
|
32157
|
+
coords: { x, y, z: z2 },
|
|
32158
|
+
government: raw.government ?? null,
|
|
32159
|
+
allegiance: raw.allegiance ?? null,
|
|
32160
|
+
security: raw.security ?? null,
|
|
32161
|
+
controlling_power: raw.controlling_power ?? null,
|
|
32162
|
+
powers: raw.power ?? [],
|
|
32163
|
+
power_state: raw.power_state ?? null,
|
|
32164
|
+
updated_at: raw.updated_at ?? null,
|
|
32165
|
+
factions: (raw.minor_faction_presences ?? []).map((f) => ({
|
|
32166
|
+
name: f.name,
|
|
32167
|
+
government: f.government ?? null,
|
|
32168
|
+
allegiance: f.allegiance ?? null,
|
|
32169
|
+
influence: f.influence ?? null,
|
|
32170
|
+
controlling: f.name === raw.controlling_minor_faction,
|
|
32171
|
+
// Spansh leaves out active_states when a faction has one state.
|
|
32172
|
+
active_states: f.active_states ?? (f.state && f.state !== "None" ? [f.state] : []),
|
|
32173
|
+
pending_states: f.pending_states ?? []
|
|
32174
|
+
})),
|
|
32175
|
+
stations: (raw.stations ?? []).filter((s) => !skipStation(s.type)).map((s) => ({
|
|
32176
|
+
name: s.name,
|
|
32177
|
+
type: s.type ?? null,
|
|
32178
|
+
pad: stationPad(s),
|
|
32179
|
+
arrival_ls: s.distance_to_arrival ?? null
|
|
32180
|
+
})),
|
|
32181
|
+
stars: (raw.bodies ?? []).filter((b) => b.type === "Star").map((b) => ({ name: b.name, arrival_ls: b.distance_to_arrival ?? 0 }))
|
|
32182
|
+
};
|
|
32183
|
+
}
|
|
32184
|
+
function distanceLy(a, b) {
|
|
32185
|
+
return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
|
|
32186
|
+
}
|
|
32187
|
+
var isAnarchy = (f) => f.government === "Anarchy";
|
|
32188
|
+
|
|
32189
|
+
// src/systems/sweep-cache.ts
|
|
32190
|
+
var SweepCache = class {
|
|
32191
|
+
constructor(ttlMs = 36e5, now = Date.now, maxSweeps = 4) {
|
|
32192
|
+
this.ttlMs = ttlMs;
|
|
32193
|
+
this.now = now;
|
|
32194
|
+
this.maxSweeps = maxSweeps;
|
|
32195
|
+
}
|
|
32196
|
+
ttlMs;
|
|
32197
|
+
now;
|
|
32198
|
+
maxSweeps;
|
|
32199
|
+
name = "spansh-sweeps";
|
|
32200
|
+
sweeps = [];
|
|
32201
|
+
find(centre, radiusLy) {
|
|
32202
|
+
this.dropExpired();
|
|
32203
|
+
const hit = this.sweeps.find(
|
|
32204
|
+
(s) => distanceLy(s.centre, centre) + radiusLy <= s.coveredLy + 1e-9
|
|
32205
|
+
);
|
|
32206
|
+
return hit?.systems.filter((s) => distanceLy(s.coords, centre) <= radiusLy);
|
|
32207
|
+
}
|
|
32208
|
+
add(centre, radiusLy, systems, key) {
|
|
32209
|
+
const storedAt = key ? this.sweeps.find((s) => s.resume?.key === key)?.storedAt : void 0;
|
|
32210
|
+
if (key) this.sweeps = this.sweeps.filter((s) => s.resume?.key !== key);
|
|
32211
|
+
this.push({ centre, coveredLy: radiusLy, systems, storedAt: storedAt ?? this.now() });
|
|
32212
|
+
}
|
|
32213
|
+
savePartial(p) {
|
|
32214
|
+
const storedAt = this.sweeps.find((s) => s.resume?.key === p.key)?.storedAt ?? this.now();
|
|
32215
|
+
this.sweeps = this.sweeps.filter((s) => s.resume?.key !== p.key);
|
|
32216
|
+
this.push({
|
|
32217
|
+
centre: p.centre,
|
|
32218
|
+
coveredLy: p.coveredLy,
|
|
32219
|
+
systems: p.systems,
|
|
32220
|
+
storedAt,
|
|
32221
|
+
resume: p
|
|
32222
|
+
});
|
|
32223
|
+
}
|
|
32224
|
+
partial(key) {
|
|
32225
|
+
this.dropExpired();
|
|
32226
|
+
return this.sweeps.find((s) => s.resume?.key === key)?.resume;
|
|
32227
|
+
}
|
|
32228
|
+
push(s) {
|
|
32229
|
+
this.sweeps.push(s);
|
|
32230
|
+
while (this.sweeps.length > this.maxSweeps) this.sweeps.shift();
|
|
32231
|
+
}
|
|
32232
|
+
clear() {
|
|
32233
|
+
const n = this.sweeps.length;
|
|
32234
|
+
this.sweeps = [];
|
|
32235
|
+
return n;
|
|
32236
|
+
}
|
|
32237
|
+
stats() {
|
|
32238
|
+
this.dropExpired();
|
|
32239
|
+
const oldest = this.sweeps.length ? Math.min(...this.sweeps.map((s) => s.storedAt)) : void 0;
|
|
32240
|
+
return {
|
|
32241
|
+
name: this.name,
|
|
32242
|
+
entries: this.sweeps.length,
|
|
32243
|
+
oldestAgeSeconds: oldest === void 0 ? null : Math.round((this.now() - oldest) / 1e3)
|
|
32244
|
+
};
|
|
32245
|
+
}
|
|
32246
|
+
dropExpired() {
|
|
32247
|
+
const cutoff = this.now() - this.ttlMs;
|
|
32248
|
+
this.sweeps = this.sweeps.filter((s) => s.storedAt > cutoff);
|
|
32249
|
+
}
|
|
32250
|
+
};
|
|
32251
|
+
|
|
32083
32252
|
// src/spansh/client.ts
|
|
32084
|
-
var
|
|
32253
|
+
var TRADER_FILTERS = {
|
|
32085
32254
|
material_trader_raw: { material_trader: { value: ["Raw"] } },
|
|
32086
32255
|
material_trader_manufactured: { material_trader: { value: ["Manufactured"] } },
|
|
32087
32256
|
material_trader_encoded: { material_trader: { value: ["Encoded"] } },
|
|
32088
32257
|
tech_broker_human: { technology_broker: { value: ["Human"] } },
|
|
32089
|
-
tech_broker_guardian: { technology_broker: { value: ["Guardian"] } }
|
|
32090
|
-
|
|
32091
|
-
|
|
32258
|
+
tech_broker_guardian: { technology_broker: { value: ["Guardian"] } }
|
|
32259
|
+
};
|
|
32260
|
+
var NAMED_SERVICES = {
|
|
32261
|
+
interstellar_factors: "Interstellar Factors Contact",
|
|
32262
|
+
black_market: "Black Market",
|
|
32263
|
+
universal_cartographics: "Universal Cartographics",
|
|
32264
|
+
vista_genomics: "Vista Genomics",
|
|
32265
|
+
search_and_rescue: "Search and Rescue",
|
|
32266
|
+
refuel: "Refuel",
|
|
32267
|
+
repair: "Repair",
|
|
32268
|
+
restock: "Restock",
|
|
32269
|
+
shipyard: "Shipyard",
|
|
32270
|
+
outfitting: "Outfitting",
|
|
32271
|
+
market: "Market",
|
|
32272
|
+
apex_interstellar: "Apex Interstellar",
|
|
32273
|
+
frontline_solutions: "Frontline Solutions",
|
|
32274
|
+
pioneer_supplies: "Pioneer Supplies",
|
|
32275
|
+
bartender: "Bartender",
|
|
32276
|
+
refinery_contact: "Refinery Contact",
|
|
32277
|
+
redemption_office: "Redemption Office",
|
|
32278
|
+
crew_lounge: "Crew Lounge",
|
|
32279
|
+
squadron_bank: "Squadron Bank",
|
|
32280
|
+
construction_services: "Construction Services",
|
|
32281
|
+
powerplay: "Powerplay"
|
|
32282
|
+
};
|
|
32283
|
+
var STATION_SERVICES = [
|
|
32284
|
+
...Object.keys(TRADER_FILTERS),
|
|
32285
|
+
...Object.keys(NAMED_SERVICES)
|
|
32286
|
+
];
|
|
32287
|
+
function serviceFilter(s) {
|
|
32288
|
+
if (Object.hasOwn(TRADER_FILTERS, s)) return TRADER_FILTERS[s];
|
|
32289
|
+
return { services: [{ name: { value: [NAMED_SERVICES[s]] } }] };
|
|
32290
|
+
}
|
|
32092
32291
|
var COUNT_CAP = 1e4;
|
|
32093
32292
|
var shipKey = (s) => s.toLowerCase().replace(/[\s-]/g, "");
|
|
32094
|
-
function pad(s) {
|
|
32095
|
-
if (s.has_large_pad || (s.large_pads ?? 0) > 0) return "L";
|
|
32096
|
-
if ((s.medium_pads ?? 0) > 0) return "M";
|
|
32097
|
-
if ((s.small_pads ?? 0) > 0) return "S";
|
|
32098
|
-
return null;
|
|
32099
|
-
}
|
|
32100
32293
|
var MAX_JOBS = 100;
|
|
32101
32294
|
var SEARCH_PAGE_SIZE = 50;
|
|
32295
|
+
var SWEEP_PAGE_SIZE = 500;
|
|
32102
32296
|
var CARRIER_TYPE = "Drake-Class Carrier";
|
|
32103
32297
|
var NON_CARRIER_TYPES = [
|
|
32104
32298
|
"Asteroid base",
|
|
@@ -32131,6 +32325,7 @@ var SpanshClient = class {
|
|
|
32131
32325
|
this.now = opts.now ?? Date.now;
|
|
32132
32326
|
this.systemCache = opts.systemCache ?? cache;
|
|
32133
32327
|
this.marketCache = opts.marketCache ?? cache;
|
|
32328
|
+
this.sweepCache = opts.sweepCache ?? new SweepCache();
|
|
32134
32329
|
}
|
|
32135
32330
|
http;
|
|
32136
32331
|
cache;
|
|
@@ -32141,10 +32336,12 @@ var SpanshClient = class {
|
|
|
32141
32336
|
now;
|
|
32142
32337
|
systemCache;
|
|
32143
32338
|
marketCache;
|
|
32339
|
+
sweepCache;
|
|
32144
32340
|
// Job ids by route form, so a call after a timeout collects the job instead of resubmitting.
|
|
32145
32341
|
routeJobs = /* @__PURE__ */ new Map();
|
|
32146
32342
|
tradeJobs = /* @__PURE__ */ new Map();
|
|
32147
32343
|
tradeSubmits = /* @__PURE__ */ new Map();
|
|
32344
|
+
sweeps = /* @__PURE__ */ new Map();
|
|
32148
32345
|
gate = Promise.resolve();
|
|
32149
32346
|
lastStart = 0;
|
|
32150
32347
|
async resolveShip(name) {
|
|
@@ -32169,7 +32366,7 @@ var SpanshClient = class {
|
|
|
32169
32366
|
shipName = await this.resolveShip(q.ship);
|
|
32170
32367
|
filters.ships = [{ name: { value: [shipName] } }];
|
|
32171
32368
|
} else if (q.service) {
|
|
32172
|
-
Object.assign(filters,
|
|
32369
|
+
Object.assign(filters, serviceFilter(q.service));
|
|
32173
32370
|
}
|
|
32174
32371
|
if (q.largePad) filters.has_large_pad = { value: true };
|
|
32175
32372
|
if (q.maxDistanceLy !== void 0) filters.distance = { min: 0, max: q.maxDistanceLy };
|
|
@@ -32236,7 +32433,7 @@ var SpanshClient = class {
|
|
|
32236
32433
|
name: s.name,
|
|
32237
32434
|
type: s.type ?? null,
|
|
32238
32435
|
arrival_ls: s.distance_to_arrival ?? null,
|
|
32239
|
-
pad:
|
|
32436
|
+
pad: stationPad(s),
|
|
32240
32437
|
market: s.has_market ?? false,
|
|
32241
32438
|
market_id: s.market_id ?? null,
|
|
32242
32439
|
outfitting: s.has_outfitting ?? false,
|
|
@@ -32253,6 +32450,78 @@ var SpanshClient = class {
|
|
|
32253
32450
|
);
|
|
32254
32451
|
return detail.record.needs_permit ?? null;
|
|
32255
32452
|
}
|
|
32453
|
+
/** Populated systems within radiusLy of near. Stops paging at opts.deadline; a partial result is cached and the next call resumes it. */
|
|
32454
|
+
async populatedSystems(near, radiusLy, opts = {}) {
|
|
32455
|
+
const centre = (await this.system(near, 0)).coords;
|
|
32456
|
+
const cached2 = this.sweepCache.find(centre, radiusLy);
|
|
32457
|
+
if (cached2)
|
|
32458
|
+
return { centre, systems: cached2, total: cached2.length, partial: false, coveredLy: radiusLy };
|
|
32459
|
+
const key = `${near.toLowerCase()}:${radiusLy}`;
|
|
32460
|
+
let running = this.sweeps.get(key);
|
|
32461
|
+
if (!running) {
|
|
32462
|
+
running = this.sweepPages(near, radiusLy, centre, key, opts).finally(
|
|
32463
|
+
() => this.sweeps.delete(key)
|
|
32464
|
+
);
|
|
32465
|
+
this.sweeps.set(key, running);
|
|
32466
|
+
}
|
|
32467
|
+
return running;
|
|
32468
|
+
}
|
|
32469
|
+
async sweepPages(near, radiusLy, centre, key, opts) {
|
|
32470
|
+
const resume = this.sweepCache.partial(key);
|
|
32471
|
+
const systems = resume ? [...resume.systems] : [];
|
|
32472
|
+
const seen = new Set(systems.map((s) => s.id64));
|
|
32473
|
+
let total = resume?.total ?? 0;
|
|
32474
|
+
let partial2 = false;
|
|
32475
|
+
for (let page2 = 0; ; page2++) {
|
|
32476
|
+
if (page2 > 0 && opts.deadline !== void 0 && this.now() >= opts.deadline) {
|
|
32477
|
+
partial2 = true;
|
|
32478
|
+
break;
|
|
32479
|
+
}
|
|
32480
|
+
const body = {
|
|
32481
|
+
filters: {
|
|
32482
|
+
// Resuming by distance, not page: Spansh's list can shift between calls an hour apart.
|
|
32483
|
+
distance: { min: resume?.coveredLy ?? 0, max: radiusLy },
|
|
32484
|
+
population: { comparison: "<=>", value: [1, 1e11] }
|
|
32485
|
+
},
|
|
32486
|
+
sort: [{ distance: { direction: "asc" } }],
|
|
32487
|
+
size: SWEEP_PAGE_SIZE,
|
|
32488
|
+
page: page2,
|
|
32489
|
+
reference_system: near
|
|
32490
|
+
};
|
|
32491
|
+
let res;
|
|
32492
|
+
try {
|
|
32493
|
+
res = await this.post("/systems/search", JSON.stringify(body), "application/json");
|
|
32494
|
+
} catch (err) {
|
|
32495
|
+
if (err instanceof SpanshError && err.status === 400) {
|
|
32496
|
+
throw new UserError(
|
|
32497
|
+
`Spansh rejected the search (${err.detail}). Check that "${near}" is an exact system name (spansh_system).`
|
|
32498
|
+
);
|
|
32499
|
+
}
|
|
32500
|
+
throw err;
|
|
32501
|
+
}
|
|
32502
|
+
if (!resume) total = res.count;
|
|
32503
|
+
const results = res.results ?? [];
|
|
32504
|
+
for (const raw of results) {
|
|
32505
|
+
const s = mapSystem(raw);
|
|
32506
|
+
if (s && !seen.has(s.id64)) {
|
|
32507
|
+
seen.add(s.id64);
|
|
32508
|
+
systems.push(s);
|
|
32509
|
+
}
|
|
32510
|
+
}
|
|
32511
|
+
if (results.length < SWEEP_PAGE_SIZE) break;
|
|
32512
|
+
}
|
|
32513
|
+
if (!partial2) {
|
|
32514
|
+
this.sweepCache.add(centre, radiusLy, systems, key);
|
|
32515
|
+
return { centre, systems, total, partial: partial2, coveredLy: radiusLy };
|
|
32516
|
+
}
|
|
32517
|
+
const last = systems[systems.length - 1];
|
|
32518
|
+
const coveredLy = Math.max(
|
|
32519
|
+
resume?.coveredLy ?? 0,
|
|
32520
|
+
last ? distanceLy(last.coords, centre) - 0.01 : 0
|
|
32521
|
+
);
|
|
32522
|
+
this.sweepCache.savePartial({ key, centre, coveredLy, systems, total });
|
|
32523
|
+
return { centre, systems, total, partial: partial2, coveredLy };
|
|
32524
|
+
}
|
|
32256
32525
|
async route(q, opts = {}) {
|
|
32257
32526
|
const form = new URLSearchParams({
|
|
32258
32527
|
from: q.from,
|
|
@@ -32548,7 +32817,7 @@ var SpanshClient = class {
|
|
|
32548
32817
|
distance_ly: s.distance ?? null,
|
|
32549
32818
|
arrival_ls: s.distance_to_arrival ?? null,
|
|
32550
32819
|
coords: { x, y, z: z2 },
|
|
32551
|
-
pad:
|
|
32820
|
+
pad: stationPad(s),
|
|
32552
32821
|
type: s.type ?? null,
|
|
32553
32822
|
planetary: s.is_planetary ?? false,
|
|
32554
32823
|
market_age_hours: Number.isNaN(updated) ? null : Math.round((now - updated) / 36e5 * 10) / 10,
|
|
@@ -32579,7 +32848,7 @@ var SpanshClient = class {
|
|
|
32579
32848
|
distance_ly: s.distance ?? 0,
|
|
32580
32849
|
arrival_ls: s.distance_to_arrival ?? null,
|
|
32581
32850
|
type: s.type ?? null,
|
|
32582
|
-
pad:
|
|
32851
|
+
pad: stationPad(s),
|
|
32583
32852
|
planetary: s.is_planetary ?? null,
|
|
32584
32853
|
price,
|
|
32585
32854
|
market_updated_at: s.market_updated_at ?? null,
|
|
@@ -34610,9 +34879,12 @@ function buildDeps(config2, opts = {}) {
|
|
|
34610
34879
|
caches.register(spanshSystemCache);
|
|
34611
34880
|
const spanshMarketCache = new TtlCache("spansh-markets", 6e5);
|
|
34612
34881
|
caches.register(spanshMarketCache);
|
|
34882
|
+
const spanshSweepCache = new SweepCache();
|
|
34883
|
+
caches.register(spanshSweepCache);
|
|
34613
34884
|
const spansh = new SpanshClient(http, spanshCache, {
|
|
34614
34885
|
systemCache: spanshSystemCache,
|
|
34615
|
-
marketCache: spanshMarketCache
|
|
34886
|
+
marketCache: spanshMarketCache,
|
|
34887
|
+
sweepCache: spanshSweepCache
|
|
34616
34888
|
});
|
|
34617
34889
|
const capiHttp = new HttpClient({ userAgent: `EDCD-EdMcp-${VERSION}`, fetchFn: opts.fetchFn });
|
|
34618
34890
|
const capiCache = new TtlCache("capi", 6e4);
|
|
@@ -41918,6 +42190,15 @@ var McpServer = class {
|
|
|
41918
42190
|
this._promptHandlersInitialized = false;
|
|
41919
42191
|
this.server = new Server(serverInfo, options);
|
|
41920
42192
|
this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
|
|
42193
|
+
if (options?.capabilities?.tools) {
|
|
42194
|
+
this.setToolRequestHandlers();
|
|
42195
|
+
}
|
|
42196
|
+
if (options?.capabilities?.resources) {
|
|
42197
|
+
this.setResourceRequestHandlers();
|
|
42198
|
+
}
|
|
42199
|
+
if (options?.capabilities?.prompts) {
|
|
42200
|
+
this.setPromptRequestHandlers();
|
|
42201
|
+
}
|
|
41921
42202
|
}
|
|
41922
42203
|
/**
|
|
41923
42204
|
* Access experimental features.
|
|
@@ -43245,10 +43526,10 @@ function slotInfo(hull, slot, item) {
|
|
|
43245
43526
|
return { slot, kind: kind2, size };
|
|
43246
43527
|
}
|
|
43247
43528
|
function bulkheadFromItem(hull, item) {
|
|
43248
|
-
const
|
|
43529
|
+
const lower2 = item.toLowerCase();
|
|
43249
43530
|
const offset = hull.bulkheads.length > 5 ? 1 : 0;
|
|
43250
|
-
if (
|
|
43251
|
-
const i = BULKHEAD_SUFFIXES.findIndex((s) =>
|
|
43531
|
+
if (lower2.endsWith("_armour_grade1_default")) return hull.bulkheads[0] ?? null;
|
|
43532
|
+
const i = BULKHEAD_SUFFIXES.findIndex((s) => lower2.endsWith(`_armour_${s}`));
|
|
43252
43533
|
return i < 0 ? null : hull.bulkheads[i + offset] ?? null;
|
|
43253
43534
|
}
|
|
43254
43535
|
function bulkheadItem(hull, module) {
|
|
@@ -44712,13 +44993,13 @@ function resolveRules(stored, override, usePreferences, auto, jumpAuto = { ly: n
|
|
|
44712
44993
|
const prefs = applyPatch(usePreferences ? stored : OFF_PREFS, override ?? {});
|
|
44713
44994
|
const p = prefs.stations.min_pad;
|
|
44714
44995
|
const fromRequest = override?.stations?.min_pad !== void 0;
|
|
44715
|
-
const
|
|
44996
|
+
const pad = p === "auto" ? auto : p === "none" ? { size: null, source: fromRequest ? "request" : "none" } : { size: p, source: fromRequest ? "request" : "preferences" };
|
|
44716
44997
|
const range = prefs.travel.jump_range_ly;
|
|
44717
44998
|
const fromRequestRange = override?.travel?.jump_range_ly !== void 0;
|
|
44718
44999
|
const { ly, source } = range !== "auto" ? { ly: range, source: fromRequestRange ? "request" : "preferences" } : jumpAuto.ly ? { ly: jumpAuto.ly, source: jumpAuto.source } : { ly: 30, source: "auto: default 30 LY" };
|
|
44719
45000
|
return {
|
|
44720
45001
|
prefs,
|
|
44721
|
-
pad
|
|
45002
|
+
pad,
|
|
44722
45003
|
travel: { ...prefs.travel, jump_range_ly: ly },
|
|
44723
45004
|
jumpRangeSource: source
|
|
44724
45005
|
};
|
|
@@ -45092,11 +45373,11 @@ function union2(a, b) {
|
|
|
45092
45373
|
async function resolveSearchRules(deps, preferences, usePreferences, largePad) {
|
|
45093
45374
|
const stored = deps.prefs && usePreferences ? await deps.prefs.get() : DEFAULT_PREFS;
|
|
45094
45375
|
const override = largePad ? { ...preferences, stations: { ...preferences?.stations, min_pad: "L" } } : preferences ?? {};
|
|
45095
|
-
let
|
|
45376
|
+
let pad = { size: null, source: "none" };
|
|
45096
45377
|
let jump2;
|
|
45097
|
-
const first = resolveRules(stored, override, usePreferences,
|
|
45378
|
+
const first = resolveRules(stored, override, usePreferences, pad);
|
|
45098
45379
|
if (first.prefs.stations.min_pad === "auto") {
|
|
45099
|
-
|
|
45380
|
+
pad = await autoPad(deps.currentShip ?? (async () => null));
|
|
45100
45381
|
}
|
|
45101
45382
|
if (first.prefs.travel.jump_range_ly === "auto") {
|
|
45102
45383
|
const max = await deps.jumpRange?.().catch(() => null);
|
|
@@ -45108,7 +45389,7 @@ async function resolveSearchRules(deps, preferences, usePreferences, largePad) {
|
|
|
45108
45389
|
};
|
|
45109
45390
|
}
|
|
45110
45391
|
}
|
|
45111
|
-
return resolveRules(stored, override, usePreferences,
|
|
45392
|
+
return resolveRules(stored, override, usePreferences, pad, jump2);
|
|
45112
45393
|
}
|
|
45113
45394
|
|
|
45114
45395
|
// src/tools/materials.ts
|
|
@@ -45299,7 +45580,7 @@ function legBetween(from, to, travel) {
|
|
|
45299
45580
|
if (same(from.system, to.system) && same(from.station, to.station)) {
|
|
45300
45581
|
return { jumps: 0, seconds: 0, distanceLy: 0 };
|
|
45301
45582
|
}
|
|
45302
|
-
const
|
|
45583
|
+
const distanceLy2 = same(from.system, to.system) ? 0 : Math.hypot(
|
|
45303
45584
|
from.coords.x - to.coords.x,
|
|
45304
45585
|
from.coords.y - to.coords.y,
|
|
45305
45586
|
from.coords.z - to.coords.z
|
|
@@ -45307,14 +45588,14 @@ function legBetween(from, to, travel) {
|
|
|
45307
45588
|
return {
|
|
45308
45589
|
...legSeconds(
|
|
45309
45590
|
{
|
|
45310
|
-
distanceLy,
|
|
45591
|
+
distanceLy: distanceLy2,
|
|
45311
45592
|
arrivalLs: to.arrivalLs,
|
|
45312
45593
|
planetary: to.planetary,
|
|
45313
45594
|
stationKnown: to.station !== null
|
|
45314
45595
|
},
|
|
45315
45596
|
travel
|
|
45316
45597
|
),
|
|
45317
|
-
distanceLy
|
|
45598
|
+
distanceLy: distanceLy2
|
|
45318
45599
|
};
|
|
45319
45600
|
}
|
|
45320
45601
|
|
|
@@ -46369,6 +46650,343 @@ function registerLoopTools(server, deps) {
|
|
|
46369
46650
|
}
|
|
46370
46651
|
}
|
|
46371
46652
|
|
|
46653
|
+
// src/systems/massacre.ts
|
|
46654
|
+
var SOURCE_RANGE_LY = 10;
|
|
46655
|
+
var NO_MASSACRES = /* @__PURE__ */ new Set(["war", "civil war", "election"]);
|
|
46656
|
+
function massacreTargets(systems, centre, opts) {
|
|
46657
|
+
const pirates = systems.flatMap(
|
|
46658
|
+
(s) => s.factions.filter(isAnarchy).map((f) => ({ faction: f.name, system: s }))
|
|
46659
|
+
);
|
|
46660
|
+
const piratesNear = /* @__PURE__ */ new Map();
|
|
46661
|
+
const piratesInRange = (s) => {
|
|
46662
|
+
let list2 = piratesNear.get(s.id64);
|
|
46663
|
+
if (!list2) {
|
|
46664
|
+
list2 = pirates.filter((p) => p.system.id64 !== s.id64).filter((p) => distanceLy(p.system.coords, s.coords) <= SOURCE_RANGE_LY).map((p) => ({ faction: p.faction, system: p.system.name }));
|
|
46665
|
+
piratesNear.set(s.id64, list2);
|
|
46666
|
+
}
|
|
46667
|
+
return list2;
|
|
46668
|
+
};
|
|
46669
|
+
const candidates = systems.filter((s) => s.stations.length > 0);
|
|
46670
|
+
const targets = [];
|
|
46671
|
+
let leftOutSpread = 0;
|
|
46672
|
+
for (const p of pirates) {
|
|
46673
|
+
const distance2 = distanceLy(p.system.coords, centre);
|
|
46674
|
+
if (distance2 > opts.radiusLy) continue;
|
|
46675
|
+
const spreadLs = Math.max(0, ...p.system.stars.map((s) => s.arrival_ls));
|
|
46676
|
+
if (spreadLs > opts.maxStarLs) {
|
|
46677
|
+
leftOutSpread++;
|
|
46678
|
+
continue;
|
|
46679
|
+
}
|
|
46680
|
+
const sources = [];
|
|
46681
|
+
const excluded = [];
|
|
46682
|
+
for (const s of candidates) {
|
|
46683
|
+
if (s.id64 === p.system.id64) continue;
|
|
46684
|
+
const toTarget = distanceLy(s.coords, p.system.coords);
|
|
46685
|
+
if (toTarget > SOURCE_RANGE_LY) continue;
|
|
46686
|
+
const blocked = s.factions.flatMap(
|
|
46687
|
+
(f) => f.active_states.filter((x) => NO_MASSACRES.has(x.toLowerCase())).map((state) => ({ system: s.name, faction: f.name, state, updated_at: s.updated_at }))
|
|
46688
|
+
);
|
|
46689
|
+
if (blocked.length) {
|
|
46690
|
+
excluded.push(...blocked);
|
|
46691
|
+
continue;
|
|
46692
|
+
}
|
|
46693
|
+
const others = piratesInRange(s).filter((x) => x.faction !== p.faction);
|
|
46694
|
+
sources.push({
|
|
46695
|
+
system: s,
|
|
46696
|
+
distanceToTargetLy: toTarget,
|
|
46697
|
+
primary: others.length === 0,
|
|
46698
|
+
otherPirates: others
|
|
46699
|
+
});
|
|
46700
|
+
}
|
|
46701
|
+
const primary = sources.filter((s) => s.primary);
|
|
46702
|
+
if (!primary.length) continue;
|
|
46703
|
+
const givers = new Set(
|
|
46704
|
+
primary.flatMap((s) => s.system.factions.filter((f) => !isAnarchy(f)).map((f) => f.name))
|
|
46705
|
+
);
|
|
46706
|
+
if (givers.size === 0) continue;
|
|
46707
|
+
sources.sort(
|
|
46708
|
+
(a, b) => Number(b.primary) - Number(a.primary) || a.distanceToTargetLy - b.distanceToTargetLy
|
|
46709
|
+
);
|
|
46710
|
+
targets.push({
|
|
46711
|
+
faction: p.faction,
|
|
46712
|
+
system: p.system,
|
|
46713
|
+
distanceLy: distance2,
|
|
46714
|
+
spreadLs,
|
|
46715
|
+
stackSize: givers.size,
|
|
46716
|
+
sources,
|
|
46717
|
+
excluded
|
|
46718
|
+
});
|
|
46719
|
+
}
|
|
46720
|
+
targets.sort(
|
|
46721
|
+
(a, b) => b.stackSize - a.stackSize || a.spreadLs - b.spreadLs || a.distanceLy - b.distanceLy
|
|
46722
|
+
);
|
|
46723
|
+
return { targets, leftOutSpread };
|
|
46724
|
+
}
|
|
46725
|
+
|
|
46726
|
+
// src/systems/state.ts
|
|
46727
|
+
var lower = (xs) => xs?.length ? new Set(xs.map((x) => x.toLowerCase())) : null;
|
|
46728
|
+
var has = (set2, v) => !set2 || v !== null && set2.has(v.toLowerCase());
|
|
46729
|
+
function hasFilter(q) {
|
|
46730
|
+
return [q.states, q.government, q.allegiance, q.power, q.security].some((x) => x?.length);
|
|
46731
|
+
}
|
|
46732
|
+
function systemsByState(systems, centre, radiusLy, q) {
|
|
46733
|
+
const states = lower(q.states);
|
|
46734
|
+
const government = lower(q.government);
|
|
46735
|
+
const allegiance = lower(q.allegiance);
|
|
46736
|
+
const power2 = lower(q.power);
|
|
46737
|
+
const security = lower(q.security);
|
|
46738
|
+
const out = [];
|
|
46739
|
+
for (const s of systems) {
|
|
46740
|
+
const d = distanceLy(s.coords, centre);
|
|
46741
|
+
if (d > radiusLy) continue;
|
|
46742
|
+
if (!has(government, s.government) || !has(allegiance, s.allegiance)) continue;
|
|
46743
|
+
if (!has(power2, s.controlling_power) || !has(security, s.security)) continue;
|
|
46744
|
+
let factions = [];
|
|
46745
|
+
if (states) {
|
|
46746
|
+
factions = s.factions.filter(
|
|
46747
|
+
(f) => (!q.controllingOnly || f.controlling) && [...f.active_states, ...q.includePending ? f.pending_states : []].some(
|
|
46748
|
+
(x) => states.has(x.toLowerCase())
|
|
46749
|
+
)
|
|
46750
|
+
);
|
|
46751
|
+
if (!factions.length) continue;
|
|
46752
|
+
}
|
|
46753
|
+
out.push({ system: s, distanceLy: d, factions });
|
|
46754
|
+
}
|
|
46755
|
+
return out.sort((a, b) => a.distanceLy - b.distanceLy);
|
|
46756
|
+
}
|
|
46757
|
+
|
|
46758
|
+
// src/tools/systems.ts
|
|
46759
|
+
var TIME_LIMIT_MS2 = 2e4;
|
|
46760
|
+
var MASSACRE_MARGIN_LY = 2 * SOURCE_RANGE_LY;
|
|
46761
|
+
var partialNote = (coveredLy) => `Spansh returned systems out to ${Math.floor(coveredLy * 10) / 10} ly within the time limit. Call again to continue the search.`;
|
|
46762
|
+
var STALE_HOURS = 72;
|
|
46763
|
+
var FAR_STAR_LS = 1e3;
|
|
46764
|
+
var Radius = external_exports.number().min(1).max(50).default(50);
|
|
46765
|
+
var Limit = external_exports.number().int().min(1).max(100).default(50);
|
|
46766
|
+
var Values = external_exports.array(external_exports.string()).optional();
|
|
46767
|
+
function registerSystemTools(server, deps) {
|
|
46768
|
+
const now = deps.now ?? Date.now;
|
|
46769
|
+
const resolveNear = async (near, notes) => {
|
|
46770
|
+
if (near) return near;
|
|
46771
|
+
if (deps.journal) {
|
|
46772
|
+
try {
|
|
46773
|
+
const system = (await deps.journal.state())?.location.system;
|
|
46774
|
+
if (system) return system;
|
|
46775
|
+
} catch (err) {
|
|
46776
|
+
notes.push(`Journal unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
46777
|
+
}
|
|
46778
|
+
}
|
|
46779
|
+
const why = notes.length ? ` (${notes.join(" ")})` : "";
|
|
46780
|
+
throw new UserError(`Give near, or set up the journal to use the current system${why}.`);
|
|
46781
|
+
};
|
|
46782
|
+
const ageHours2 = (s) => s.updated_at ? Math.round((now() - Date.parse(s.updated_at)) / 36e5 * 10) / 10 : null;
|
|
46783
|
+
const leg = (ly, t) => legSeconds({ distanceLy: ly, arrivalLs: null, planetary: false, stationKnown: false }, t);
|
|
46784
|
+
const nearestWith = (s, ok2) => {
|
|
46785
|
+
const ls = s.stations.filter((x) => ok2(x.pad)).map((x) => x.arrival_ls ?? Infinity);
|
|
46786
|
+
const min3 = Math.min(...ls);
|
|
46787
|
+
return Number.isFinite(min3) ? Math.round(min3) : null;
|
|
46788
|
+
};
|
|
46789
|
+
const pads = (s) => ({
|
|
46790
|
+
L: nearestWith(s, (p) => p === "L"),
|
|
46791
|
+
M: nearestWith(s, (p) => p === "L" || p === "M"),
|
|
46792
|
+
S: nearestWith(s, (p) => p !== null)
|
|
46793
|
+
});
|
|
46794
|
+
server.registerTool(
|
|
46795
|
+
"systems_by_state",
|
|
46796
|
+
{
|
|
46797
|
+
annotations: {
|
|
46798
|
+
title: "Systems by Faction State",
|
|
46799
|
+
readOnlyHint: true,
|
|
46800
|
+
destructiveHint: false,
|
|
46801
|
+
idempotentHint: true,
|
|
46802
|
+
openWorldHint: true
|
|
46803
|
+
},
|
|
46804
|
+
description: "Find the nearest populated systems with a faction in a given state (e.g. War, Civil War, Election, Boom, Outbreak), or by government, allegiance, controlling power or security. near defaults to the journal location. War and Civil War results say conflict_zones: likely (settlements are not checked). Faction states come from player visits: check age_hours. Uses one Spansh search of all populated systems in radius_ly, cached for an hour; the first call can take 10 s.",
|
|
46805
|
+
inputSchema: {
|
|
46806
|
+
near: external_exports.string().optional().describe("Exact reference system name"),
|
|
46807
|
+
radius_ly: Radius,
|
|
46808
|
+
state: Values.describe("Faction states to match, e.g. ['War', 'Civil War']"),
|
|
46809
|
+
include_pending: external_exports.boolean().default(false),
|
|
46810
|
+
controlling_only: external_exports.boolean().default(false),
|
|
46811
|
+
government: Values,
|
|
46812
|
+
allegiance: Values,
|
|
46813
|
+
power: Values.describe("Controlling power"),
|
|
46814
|
+
security: Values,
|
|
46815
|
+
limit: Limit
|
|
46816
|
+
}
|
|
46817
|
+
},
|
|
46818
|
+
wrap(
|
|
46819
|
+
async (a) => {
|
|
46820
|
+
const q = {
|
|
46821
|
+
states: a.state,
|
|
46822
|
+
includePending: a.include_pending,
|
|
46823
|
+
controllingOnly: a.controlling_only,
|
|
46824
|
+
government: a.government,
|
|
46825
|
+
allegiance: a.allegiance,
|
|
46826
|
+
power: a.power,
|
|
46827
|
+
security: a.security
|
|
46828
|
+
};
|
|
46829
|
+
if (!hasFilter(q)) {
|
|
46830
|
+
throw new UserError(
|
|
46831
|
+
"Give at least one of state, government, allegiance, power or security."
|
|
46832
|
+
);
|
|
46833
|
+
}
|
|
46834
|
+
if (a.controlling_only && !a.state?.length) {
|
|
46835
|
+
throw new UserError(
|
|
46836
|
+
"controlling_only needs state: it limits the state match to the controlling faction."
|
|
46837
|
+
);
|
|
46838
|
+
}
|
|
46839
|
+
const notes = [];
|
|
46840
|
+
const near = await resolveNear(a.near, notes);
|
|
46841
|
+
const rules = await resolveSearchRules(deps, void 0, true);
|
|
46842
|
+
const sweep = await deps.spansh.populatedSystems(near, a.radius_ly, {
|
|
46843
|
+
deadline: now() + TIME_LIMIT_MS2
|
|
46844
|
+
});
|
|
46845
|
+
const matches2 = systemsByState(
|
|
46846
|
+
sweep.systems,
|
|
46847
|
+
sweep.centre,
|
|
46848
|
+
Math.min(a.radius_ly, sweep.coveredLy),
|
|
46849
|
+
q
|
|
46850
|
+
);
|
|
46851
|
+
const shown = matches2.slice(0, a.limit);
|
|
46852
|
+
const stale = shown.filter((m) => (ageHours2(m.system) ?? 0) > STALE_HOURS).length;
|
|
46853
|
+
if (stale) notes.push(`${stale} results have data more than ${STALE_HOURS / 24} days old.`);
|
|
46854
|
+
if (sweep.partial) {
|
|
46855
|
+
notes.push(partialNote(sweep.coveredLy));
|
|
46856
|
+
}
|
|
46857
|
+
return {
|
|
46858
|
+
near,
|
|
46859
|
+
searched: sweep.systems.length,
|
|
46860
|
+
travel: describeRules(rules).travel,
|
|
46861
|
+
total_matches: matches2.length,
|
|
46862
|
+
partial: sweep.partial,
|
|
46863
|
+
...sweep.partial ? { covered_ly: Math.floor(sweep.coveredLy * 10) / 10 } : {},
|
|
46864
|
+
systems: shown.map((m) => {
|
|
46865
|
+
const s = m.system;
|
|
46866
|
+
const war = m.factions.some(
|
|
46867
|
+
(f) => f.active_states.some((x) => /^(war|civil war)$/i.test(x))
|
|
46868
|
+
);
|
|
46869
|
+
const nearest3 = pads(s);
|
|
46870
|
+
const bestPad = nearest3.L !== null ? "L" : nearest3.M !== null ? "M" : nearest3.S !== null ? "S" : null;
|
|
46871
|
+
return {
|
|
46872
|
+
system: s.name,
|
|
46873
|
+
distance_ly: Math.round(m.distanceLy * 100) / 100,
|
|
46874
|
+
jumps: leg(m.distanceLy, rules.travel).jumps,
|
|
46875
|
+
matching_factions: m.factions.map((f) => ({
|
|
46876
|
+
name: f.name,
|
|
46877
|
+
controlling: f.controlling,
|
|
46878
|
+
influence: f.influence,
|
|
46879
|
+
active_states: f.active_states,
|
|
46880
|
+
pending_states: f.pending_states
|
|
46881
|
+
})),
|
|
46882
|
+
government: s.government,
|
|
46883
|
+
allegiance: s.allegiance,
|
|
46884
|
+
security: s.security,
|
|
46885
|
+
controlling_power: s.controlling_power,
|
|
46886
|
+
power_state: s.power_state,
|
|
46887
|
+
best_pad: bestPad,
|
|
46888
|
+
nearest_station_ls: bestPad ? nearest3[bestPad] : null,
|
|
46889
|
+
updated_at: s.updated_at,
|
|
46890
|
+
age_hours: ageHours2(s),
|
|
46891
|
+
...war ? { conflict_zones: "likely" } : {}
|
|
46892
|
+
};
|
|
46893
|
+
}),
|
|
46894
|
+
notes
|
|
46895
|
+
};
|
|
46896
|
+
}
|
|
46897
|
+
)
|
|
46898
|
+
);
|
|
46899
|
+
server.registerTool(
|
|
46900
|
+
"massacre_targets",
|
|
46901
|
+
{
|
|
46902
|
+
annotations: {
|
|
46903
|
+
title: "Find Massacre Targets",
|
|
46904
|
+
readOnlyHint: true,
|
|
46905
|
+
destructiveHint: false,
|
|
46906
|
+
idempotentHint: true,
|
|
46907
|
+
openWorldHint: true
|
|
46908
|
+
},
|
|
46909
|
+
description: "Find pirate (Anarchy) factions near you with good sources of stackable pirate massacre missions, as Inara's 'nearest massacre missions' does. Sources are populated systems within 10 ly of the target; a primary source has no other pirate faction in other systems within 10 ly (its own pirate faction does not count, as missions send you to another system), so all its massacre missions are for this target. Sources with a faction in War, Civil War or Election are excluded and listed. stack_size is the number of distinct giving factions in the primary sources. Targets whose farthest star is beyond max_star_ls (default 10,000) are left out, because fights there can be far from the arrival star. Ranked by stack_size, then spread_ls, then distance. Results are candidates: mission boards are not published. Pads are listed, not filtered. near defaults to the journal location.",
|
|
46910
|
+
inputSchema: {
|
|
46911
|
+
near: external_exports.string().optional().describe("Exact reference system name"),
|
|
46912
|
+
radius_ly: Radius.describe("Targets within this distance"),
|
|
46913
|
+
max_star_ls: external_exports.number().min(0).default(1e4),
|
|
46914
|
+
limit: Limit,
|
|
46915
|
+
preferences: PrefsPatchSchema.optional(),
|
|
46916
|
+
use_preferences: external_exports.boolean().default(true)
|
|
46917
|
+
}
|
|
46918
|
+
},
|
|
46919
|
+
wrap(
|
|
46920
|
+
async (a) => {
|
|
46921
|
+
const notes = [];
|
|
46922
|
+
const near = await resolveNear(a.near, notes);
|
|
46923
|
+
const rules = await resolveSearchRules(deps, a.preferences, a.use_preferences);
|
|
46924
|
+
const sweep = await deps.spansh.populatedSystems(near, a.radius_ly + MASSACRE_MARGIN_LY, {
|
|
46925
|
+
deadline: now() + TIME_LIMIT_MS2
|
|
46926
|
+
});
|
|
46927
|
+
const { targets, leftOutSpread } = massacreTargets(sweep.systems, sweep.centre, {
|
|
46928
|
+
// Past covered_ly - margin a source could have a pirate neighbour that was not fetched.
|
|
46929
|
+
// A negative radius lists nothing, not even a target at distance 0.
|
|
46930
|
+
radiusLy: sweep.partial ? Math.min(a.radius_ly, sweep.coveredLy - MASSACRE_MARGIN_LY) : a.radius_ly,
|
|
46931
|
+
maxStarLs: a.max_star_ls
|
|
46932
|
+
});
|
|
46933
|
+
notes.push(
|
|
46934
|
+
"These are candidates: whether the mission boards have massacre missions now depends on recent player activity."
|
|
46935
|
+
);
|
|
46936
|
+
if (leftOutSpread) {
|
|
46937
|
+
notes.push(
|
|
46938
|
+
`${leftOutSpread} targets left out: a star beyond ${a.max_star_ls} ls. Raise max_star_ls to see them.`
|
|
46939
|
+
);
|
|
46940
|
+
}
|
|
46941
|
+
if (sweep.partial) {
|
|
46942
|
+
notes.push(partialNote(sweep.coveredLy));
|
|
46943
|
+
}
|
|
46944
|
+
return {
|
|
46945
|
+
near,
|
|
46946
|
+
searched: sweep.systems.length,
|
|
46947
|
+
left_out_spread: leftOutSpread,
|
|
46948
|
+
partial: sweep.partial,
|
|
46949
|
+
...sweep.partial ? { covered_ly: Math.floor(sweep.coveredLy * 10) / 10 } : {},
|
|
46950
|
+
travel: describeRules(rules).travel,
|
|
46951
|
+
targets: targets.slice(0, a.limit).map((t) => ({
|
|
46952
|
+
target_faction: t.faction,
|
|
46953
|
+
target_system: t.system.name,
|
|
46954
|
+
distance_ly: Math.round(t.distanceLy * 100) / 100,
|
|
46955
|
+
travel_min: Math.round(leg(t.distanceLy, rules.travel).seconds / 6) / 10,
|
|
46956
|
+
stack_size: t.stackSize,
|
|
46957
|
+
spread_ls: Math.round(t.spreadLs),
|
|
46958
|
+
far_stars: t.system.stars.filter((s) => s.arrival_ls > FAR_STAR_LS).map((s) => ({ name: s.name, arrival_ls: Math.round(s.arrival_ls) })),
|
|
46959
|
+
sources: t.sources.map((s) => {
|
|
46960
|
+
const counts2 = { federation: 0, empire: 0, alliance: 0, independent: 0 };
|
|
46961
|
+
for (const f of s.system.factions) {
|
|
46962
|
+
const k = f.allegiance?.toLowerCase();
|
|
46963
|
+
if (!isAnarchy(f) && k && k in counts2) counts2[k]++;
|
|
46964
|
+
}
|
|
46965
|
+
return {
|
|
46966
|
+
system: s.system.name,
|
|
46967
|
+
primary: s.primary,
|
|
46968
|
+
other_pirates: s.otherPirates,
|
|
46969
|
+
distance_to_target_ly: Math.round(s.distanceToTargetLy * 100) / 100,
|
|
46970
|
+
allegiance_counts: counts2,
|
|
46971
|
+
pads: pads(s.system),
|
|
46972
|
+
factions: s.system.factions.map((f) => ({
|
|
46973
|
+
name: f.name,
|
|
46974
|
+
allegiance: f.allegiance,
|
|
46975
|
+
active_states: f.active_states
|
|
46976
|
+
})),
|
|
46977
|
+
updated_at: s.system.updated_at,
|
|
46978
|
+
age_hours: ageHours2(s.system)
|
|
46979
|
+
};
|
|
46980
|
+
}),
|
|
46981
|
+
excluded: t.excluded
|
|
46982
|
+
})),
|
|
46983
|
+
notes
|
|
46984
|
+
};
|
|
46985
|
+
}
|
|
46986
|
+
)
|
|
46987
|
+
);
|
|
46988
|
+
}
|
|
46989
|
+
|
|
46372
46990
|
// src/tools/inara.ts
|
|
46373
46991
|
function registerInaraTools(server, deps) {
|
|
46374
46992
|
server.registerTool(
|
|
@@ -47541,15 +48159,8 @@ function registerStationTools(server, deps) {
|
|
|
47541
48159
|
}
|
|
47542
48160
|
|
|
47543
48161
|
// src/tools/spansh.ts
|
|
47544
|
-
var Service = external_exports.enum(
|
|
47545
|
-
|
|
47546
|
-
"material_trader_manufactured",
|
|
47547
|
-
"material_trader_encoded",
|
|
47548
|
-
"tech_broker_human",
|
|
47549
|
-
"tech_broker_guardian",
|
|
47550
|
-
"interstellar_factors"
|
|
47551
|
-
]);
|
|
47552
|
-
var Limit = external_exports.number().int().min(1).max(50).default(10);
|
|
48162
|
+
var Service = external_exports.enum(STATION_SERVICES);
|
|
48163
|
+
var Limit2 = external_exports.number().int().min(1).max(50).default(10);
|
|
47553
48164
|
function registerSpanshTools(server, deps) {
|
|
47554
48165
|
const { spansh } = deps;
|
|
47555
48166
|
const now = deps.now ?? Date.now;
|
|
@@ -47563,7 +48174,7 @@ function registerSpanshTools(server, deps) {
|
|
|
47563
48174
|
idempotentHint: true,
|
|
47564
48175
|
openWorldHint: true
|
|
47565
48176
|
},
|
|
47566
|
-
description: "Find the best stations near a system, ranked by travel time and cost, that sell a module or ship, or offer a service
|
|
48177
|
+
description: "Find the best stations near a system, ranked by travel time and cost, that sell a module or ship, or offer a service: material traders, tech brokers, interstellar factors, black market, Universal Cartographics, Vista Genomics, Search and Rescue, refuel, repair, restock, shipyard, outfitting, market and more (see the service enum). Give exactly one of module, ship, service. Module names are generic (e.g. 'Frame Shift Drive') with class 1-8 and rating A-I; give class and rating to compare prices. Applies the commander's stored travel preferences (arrival distance, station types, pad size from the current ship, travel-time model); override any of them for this call with `preferences` (same shape as prefs_set's patch) or ignore them with use_preferences=false. Returns total (Spansh matches before preferences; 10000 means 10000 or more), matched (matches after preferences among the stations fetched: the nearest 50, or up to 100 when the arrival limit was relaxed). Stations are ranked by estimated travel time (jumps plus supercruise, docking and planetary time) and, for purchases, by effective cost = price + travel time \xD7 the commander's credits_per_hour. Each station has jumps, travel_s, travel_min, arrival_assumed, effective_cost and discount_pct (versus the median price); recommended explains the trade-off. Stations in permit-locked systems are left out unless permit_systems is allow, which keeps them with needs_permit: true. Also returns any relaxed rules, the applied preferences and notes. Cached 5 min.",
|
|
47567
48178
|
inputSchema: {
|
|
47568
48179
|
near: external_exports.string().describe("Exact reference system name"),
|
|
47569
48180
|
module: external_exports.object({
|
|
@@ -47577,7 +48188,7 @@ function registerSpanshTools(server, deps) {
|
|
|
47577
48188
|
max_distance_ly: external_exports.number().positive().optional(),
|
|
47578
48189
|
preferences: PrefsPatchSchema.optional(),
|
|
47579
48190
|
use_preferences: external_exports.boolean().default(true),
|
|
47580
|
-
limit:
|
|
48191
|
+
limit: Limit2
|
|
47581
48192
|
}
|
|
47582
48193
|
},
|
|
47583
48194
|
wrap(
|
|
@@ -47611,7 +48222,7 @@ function registerSpanshTools(server, deps) {
|
|
|
47611
48222
|
openWorldHint: true
|
|
47612
48223
|
},
|
|
47613
48224
|
description: "Look up a system by exact name: coordinates, allegiance, permit, and its stations sorted by distance from arrival (limit applies to stations). Cached 24 h.",
|
|
47614
|
-
inputSchema: { name: external_exports.string(), limit:
|
|
48225
|
+
inputSchema: { name: external_exports.string(), limit: Limit2 }
|
|
47615
48226
|
},
|
|
47616
48227
|
wrap(async (a) => spansh.system(a.name, a.limit))
|
|
47617
48228
|
);
|
|
@@ -48230,6 +48841,13 @@ function createServer3(deps) {
|
|
|
48230
48841
|
currentShip: deps.currentShip,
|
|
48231
48842
|
jumpRange: deps.jumpRange
|
|
48232
48843
|
});
|
|
48844
|
+
registerSystemTools(server, {
|
|
48845
|
+
spansh: deps.spansh,
|
|
48846
|
+
journal: deps.journal,
|
|
48847
|
+
prefs: deps.prefs,
|
|
48848
|
+
currentShip: deps.currentShip,
|
|
48849
|
+
jumpRange: deps.jumpRange
|
|
48850
|
+
});
|
|
48233
48851
|
}
|
|
48234
48852
|
if (deps.catalog && deps.spansh) {
|
|
48235
48853
|
registerMaterialsTools(server, {
|