mellos-mapping 0.20.2 → 0.22.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 +125 -88
- package/README.zh-CN.md +122 -84
- package/dist/hook-session-start.mjs +25 -19
- package/dist/mmap.mjs +302 -170
- package/dist/preview.mjs +1418 -0
- package/dist/server.mjs +1198 -400
- package/dist/store-paths.mjs +40 -21
- package/dist/terminal-worker.mjs +3188 -0
- package/dist/watch.mjs +523 -280
- package/dist/web/TERMINAL-LICENSES.txt +70 -0
- package/dist/web/app.css +967 -0
- package/dist/web/app.js +1356 -0
- package/dist/web/index.html +9 -0
- package/dist/web/terminal.css +9 -0
- package/dist/web/terminal.html +7 -0
- package/dist/web/terminal.js +9293 -0
- package/dist/web/xterm.css +285 -0
- package/dist/web.mjs +5535 -0
- package/docs/codex.md +183 -0
- package/lib/domain/text.d.ts +9 -0
- package/lib/domain/text.js +43 -0
- package/lib/domain/types.js +10 -1
- package/lib/preview/index.d.ts +3 -0
- package/lib/preview/index.js +3 -0
- package/lib/preview/markdown.d.ts +8 -0
- package/lib/preview/markdown.js +54 -0
- package/lib/preview/presentation.d.ts +6 -0
- package/lib/preview/presentation.js +14 -0
- package/lib/preview/publisher.d.ts +23 -0
- package/lib/preview/publisher.js +143 -0
- package/lib/preview/svg.d.ts +3 -0
- package/lib/preview/svg.js +74 -0
- package/lib/preview/text.d.ts +4 -0
- package/lib/preview/text.js +13 -0
- package/lib/render/canvas.d.ts +1 -1
- package/lib/render/canvas.js +4 -2
- package/lib/render/draw.js +9 -6
- package/lib/render/render.d.ts +6 -0
- package/lib/render/render.js +50 -18
- package/lib/render/width.js +3 -1
- package/lib/store/atomic.d.ts +18 -0
- package/lib/store/atomic.js +86 -0
- package/lib/store/channels.d.ts +40 -0
- package/lib/store/channels.js +135 -0
- package/lib/store/format.js +3 -1
- package/lib/store/json-text.d.ts +9 -0
- package/lib/store/json-text.js +16 -0
- package/lib/store/maps.d.ts +12 -0
- package/lib/store/maps.js +42 -0
- package/lib/store/migration.d.ts +12 -0
- package/lib/store/migration.js +46 -0
- package/lib/store/pages.d.ts +46 -0
- package/lib/store/pages.js +89 -0
- package/lib/store/policy.d.ts +74 -0
- package/lib/store/policy.js +144 -0
- package/lib/store/store.d.ts +9 -256
- package/lib/store/store.js +10 -694
- package/lib/store/viewers.d.ts +81 -0
- package/lib/store/viewers.js +186 -0
- package/package.json +25 -6
- package/scripts/codex-cli.mjs +42 -0
- package/scripts/codex-register.mjs +27 -94
- package/scripts/mmap.mjs +26 -16
- package/scripts/open-pane.mjs +37 -18
- package/scripts/pane-core.mjs +57 -220
- package/scripts/terminal-session.mjs +137 -0
- package/scripts/tmux-session.mjs +90 -0
- package/scripts/watcher-command.mjs +16 -0
package/dist/server.mjs
CHANGED
|
@@ -2981,7 +2981,7 @@ var require_compile = __commonJS({
|
|
|
2981
2981
|
const schOrFunc = root.refs[ref];
|
|
2982
2982
|
if (schOrFunc)
|
|
2983
2983
|
return schOrFunc;
|
|
2984
|
-
let _sch =
|
|
2984
|
+
let _sch = resolve2.call(this, root, ref);
|
|
2985
2985
|
if (_sch === void 0) {
|
|
2986
2986
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
2987
2987
|
const { schemaId } = this.opts;
|
|
@@ -3008,7 +3008,7 @@ var require_compile = __commonJS({
|
|
|
3008
3008
|
function sameSchemaEnv(s1, s2) {
|
|
3009
3009
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3010
3010
|
}
|
|
3011
|
-
function
|
|
3011
|
+
function resolve2(root, ref) {
|
|
3012
3012
|
let sch;
|
|
3013
3013
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3014
3014
|
ref = sch;
|
|
@@ -3106,9 +3106,28 @@ var require_utils = __commonJS({
|
|
|
3106
3106
|
"use strict";
|
|
3107
3107
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3108
3108
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3109
|
+
var isPort = RegExp.prototype.test.bind(/^\d*$/u);
|
|
3109
3110
|
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3110
3111
|
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3111
|
-
var isPathCharacter = RegExp.prototype.test.bind(/^[
|
|
3112
|
+
var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
|
|
3113
|
+
var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
|
|
3114
|
+
var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
|
|
3115
|
+
var BYTE_HEX = new Array(256);
|
|
3116
|
+
{
|
|
3117
|
+
const HEX_DIGITS = "0123456789ABCDEF";
|
|
3118
|
+
for (let i = 0; i < 256; i++) {
|
|
3119
|
+
BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
function percentEncodeNonAscii(cp) {
|
|
3123
|
+
if (cp < 2048) {
|
|
3124
|
+
return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
|
|
3125
|
+
}
|
|
3126
|
+
if (cp < 65536) {
|
|
3127
|
+
return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
|
|
3128
|
+
}
|
|
3129
|
+
return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
|
|
3130
|
+
}
|
|
3112
3131
|
function stringArrayToHexStripped(input) {
|
|
3113
3132
|
let acc = "";
|
|
3114
3133
|
let code = 0;
|
|
@@ -3133,91 +3152,105 @@ var require_utils = __commonJS({
|
|
|
3133
3152
|
}
|
|
3134
3153
|
return acc;
|
|
3135
3154
|
}
|
|
3155
|
+
var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
|
|
3156
|
+
var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
|
|
3157
|
+
var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
|
|
3136
3158
|
var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
|
|
3137
|
-
function
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
if (hex !== "") {
|
|
3145
|
-
address.push(hex);
|
|
3146
|
-
} else {
|
|
3147
|
-
output.error = true;
|
|
3148
|
-
return false;
|
|
3159
|
+
function isZoneIdentifier(zone) {
|
|
3160
|
+
if (zone.length === 0) return false;
|
|
3161
|
+
for (let i = 0; i < zone.length; i++) {
|
|
3162
|
+
if (isZoneCharacter(zone[i])) continue;
|
|
3163
|
+
if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
|
|
3164
|
+
i += 2;
|
|
3165
|
+
continue;
|
|
3149
3166
|
}
|
|
3150
|
-
|
|
3167
|
+
return false;
|
|
3151
3168
|
}
|
|
3152
3169
|
return true;
|
|
3153
3170
|
}
|
|
3154
|
-
function
|
|
3155
|
-
let
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
let
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
}
|
|
3167
|
-
if (cursor === ":") {
|
|
3168
|
-
if (endipv6Encountered === true) {
|
|
3169
|
-
endIpv6 = true;
|
|
3170
|
-
}
|
|
3171
|
-
if (!consume(buffer, address, output)) {
|
|
3172
|
-
break;
|
|
3173
|
-
}
|
|
3174
|
-
if (++tokenCount > 7) {
|
|
3175
|
-
output.error = true;
|
|
3176
|
-
break;
|
|
3177
|
-
}
|
|
3178
|
-
if (i > 0 && input[i - 1] === ":") {
|
|
3179
|
-
endipv6Encountered = true;
|
|
3180
|
-
}
|
|
3181
|
-
address.push(":");
|
|
3182
|
-
continue;
|
|
3183
|
-
} else if (cursor === "%") {
|
|
3184
|
-
if (!consume(buffer, address, output)) {
|
|
3185
|
-
break;
|
|
3171
|
+
function compressIPv6ZeroRun(hextets) {
|
|
3172
|
+
let bestStart = -1;
|
|
3173
|
+
let bestLength = 0;
|
|
3174
|
+
let runStart = -1;
|
|
3175
|
+
let runLength = 0;
|
|
3176
|
+
for (let i = 0; i < hextets.length; i++) {
|
|
3177
|
+
if (hextets[i] === "0") {
|
|
3178
|
+
if (runStart === -1) runStart = i;
|
|
3179
|
+
runLength++;
|
|
3180
|
+
if (runLength > bestLength) {
|
|
3181
|
+
bestLength = runLength;
|
|
3182
|
+
bestStart = runStart;
|
|
3186
3183
|
}
|
|
3187
|
-
consume = consumeIsZone;
|
|
3188
3184
|
} else {
|
|
3189
|
-
|
|
3185
|
+
runStart = -1;
|
|
3186
|
+
runLength = 0;
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
if (bestLength < 2) return hextets.join(":");
|
|
3190
|
+
const head = hextets.slice(0, bestStart).join(":");
|
|
3191
|
+
const tail = hextets.slice(bestStart + bestLength).join(":");
|
|
3192
|
+
return head + "::" + tail;
|
|
3193
|
+
}
|
|
3194
|
+
function normalizeIPv6Address(input) {
|
|
3195
|
+
const compression = input.indexOf("::");
|
|
3196
|
+
if (compression !== -1 && input.indexOf("::", compression + 1) !== -1) return void 0;
|
|
3197
|
+
const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
|
|
3198
|
+
const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
|
|
3199
|
+
if (compression !== -1) {
|
|
3200
|
+
if (left.length === 1 && left[0] === "") left.length = 0;
|
|
3201
|
+
if (right.length === 1 && right[0] === "") right.length = 0;
|
|
3202
|
+
}
|
|
3203
|
+
const parts = left.concat(right);
|
|
3204
|
+
let hextetCount = 0;
|
|
3205
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3206
|
+
const part = parts[i];
|
|
3207
|
+
if (part === "") return void 0;
|
|
3208
|
+
if (part.indexOf(".") !== -1) {
|
|
3209
|
+
if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part)) return void 0;
|
|
3210
|
+
hextetCount += 2;
|
|
3190
3211
|
continue;
|
|
3191
3212
|
}
|
|
3213
|
+
if (!isHextet(part)) return void 0;
|
|
3214
|
+
parts[i] = parseInt(part, 16).toString(16);
|
|
3215
|
+
hextetCount++;
|
|
3192
3216
|
}
|
|
3193
|
-
if (
|
|
3194
|
-
if (
|
|
3195
|
-
|
|
3196
|
-
} else if (endIpv6) {
|
|
3197
|
-
address.push(buffer.join(""));
|
|
3198
|
-
} else {
|
|
3199
|
-
address.push(stringArrayToHexStripped(buffer));
|
|
3200
|
-
}
|
|
3217
|
+
if (compression === -1) {
|
|
3218
|
+
if (hextetCount !== 8) return void 0;
|
|
3219
|
+
return compressIPv6ZeroRun(parts);
|
|
3201
3220
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3221
|
+
if (hextetCount >= 8) return void 0;
|
|
3222
|
+
const expanded = parts.slice(0, left.length);
|
|
3223
|
+
for (let i = hextetCount; i < 8; i++) expanded.push("0");
|
|
3224
|
+
for (let i = left.length; i < parts.length; i++) expanded.push(parts[i]);
|
|
3225
|
+
return compressIPv6ZeroRun(expanded);
|
|
3204
3226
|
}
|
|
3205
3227
|
function normalizeIPv6(host) {
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
}
|
|
3209
|
-
|
|
3210
|
-
if (
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3228
|
+
const bracketed = host[0] === "[" && host[host.length - 1] === "]";
|
|
3229
|
+
const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
|
|
3230
|
+
if (hasBracket && !bracketed) return { host, isIPV6: false, error: true };
|
|
3231
|
+
let input = bracketed ? host.slice(1, -1) : host;
|
|
3232
|
+
if (bracketed && isIPvFuture(input)) {
|
|
3233
|
+
input = input.toLowerCase();
|
|
3234
|
+
return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
|
|
3235
|
+
}
|
|
3236
|
+
if (findToken(input, ":") < 2) {
|
|
3237
|
+
return { host, isIPV6: false, error: bracketed };
|
|
3238
|
+
}
|
|
3239
|
+
let zoneIdentifier = "";
|
|
3240
|
+
const zoneSeparator = input.indexOf("%");
|
|
3241
|
+
if (zoneSeparator !== -1) {
|
|
3242
|
+
const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
|
|
3243
|
+
zoneIdentifier = input.slice(zoneSeparator + separatorLength);
|
|
3244
|
+
if (!isZoneIdentifier(zoneIdentifier)) return { host, isIPV6: false, error: true };
|
|
3245
|
+
input = input.slice(0, zoneSeparator);
|
|
3246
|
+
}
|
|
3247
|
+
const address = normalizeIPv6Address(input);
|
|
3248
|
+
if (address === void 0) return { host, isIPV6: false, error: true };
|
|
3249
|
+
return {
|
|
3250
|
+
host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
|
|
3251
|
+
escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
|
|
3252
|
+
isIPV6: true
|
|
3253
|
+
};
|
|
3221
3254
|
}
|
|
3222
3255
|
function findToken(str, token) {
|
|
3223
3256
|
let ind = 0;
|
|
@@ -3336,7 +3369,8 @@ var require_utils = __commonJS({
|
|
|
3336
3369
|
function normalizePathEncoding(input) {
|
|
3337
3370
|
let output = "";
|
|
3338
3371
|
for (let i = 0; i < input.length; i++) {
|
|
3339
|
-
|
|
3372
|
+
const ch = input[i];
|
|
3373
|
+
if (ch === "%" && i + 2 < input.length) {
|
|
3340
3374
|
const hex = input.slice(i + 1, i + 3);
|
|
3341
3375
|
if (isHexPair(hex)) {
|
|
3342
3376
|
const normalizedHex = hex.toUpperCase();
|
|
@@ -3350,10 +3384,152 @@ var require_utils = __commonJS({
|
|
|
3350
3384
|
continue;
|
|
3351
3385
|
}
|
|
3352
3386
|
}
|
|
3353
|
-
if (isPathCharacter(
|
|
3354
|
-
output +=
|
|
3387
|
+
if (isPathCharacter(ch)) {
|
|
3388
|
+
output += ch;
|
|
3355
3389
|
} else {
|
|
3356
|
-
|
|
3390
|
+
const code = input.charCodeAt(i);
|
|
3391
|
+
if (code < 128) {
|
|
3392
|
+
output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
|
|
3393
|
+
} else if (code < 55296 || code > 57343) {
|
|
3394
|
+
output += percentEncodeNonAscii(code);
|
|
3395
|
+
} else if (code <= 56319 && i + 1 < input.length) {
|
|
3396
|
+
const low = input.charCodeAt(i + 1);
|
|
3397
|
+
if (low >= 56320 && low <= 57343) {
|
|
3398
|
+
output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
|
|
3399
|
+
i++;
|
|
3400
|
+
} else {
|
|
3401
|
+
output += percentEncodeNonAscii(65533);
|
|
3402
|
+
}
|
|
3403
|
+
} else {
|
|
3404
|
+
output += percentEncodeNonAscii(65533);
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
return output;
|
|
3409
|
+
}
|
|
3410
|
+
function serializePathEncoding(input, pathNoScheme = false) {
|
|
3411
|
+
let output = "";
|
|
3412
|
+
let firstSegment = pathNoScheme && input[0] !== "/";
|
|
3413
|
+
for (let i = 0; i < input.length; i++) {
|
|
3414
|
+
const ch = input[i];
|
|
3415
|
+
if (ch === "%" && i + 2 < input.length) {
|
|
3416
|
+
const hex = input.slice(i + 1, i + 3);
|
|
3417
|
+
if (isHexPair(hex)) {
|
|
3418
|
+
output += "%" + hex.toUpperCase();
|
|
3419
|
+
i += 2;
|
|
3420
|
+
continue;
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
if (ch === "/") {
|
|
3424
|
+
firstSegment = false;
|
|
3425
|
+
}
|
|
3426
|
+
if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
|
|
3427
|
+
output += ch;
|
|
3428
|
+
} else {
|
|
3429
|
+
const code = input.charCodeAt(i);
|
|
3430
|
+
if (code < 128) {
|
|
3431
|
+
output += BYTE_HEX[code];
|
|
3432
|
+
} else if (code < 55296 || code > 57343) {
|
|
3433
|
+
output += percentEncodeNonAscii(code);
|
|
3434
|
+
} else if (code <= 56319 && i + 1 < input.length) {
|
|
3435
|
+
const low = input.charCodeAt(i + 1);
|
|
3436
|
+
if (low >= 56320 && low <= 57343) {
|
|
3437
|
+
output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
|
|
3438
|
+
i++;
|
|
3439
|
+
} else {
|
|
3440
|
+
output += percentEncodeNonAscii(65533);
|
|
3441
|
+
}
|
|
3442
|
+
} else {
|
|
3443
|
+
output += percentEncodeNonAscii(65533);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
return output;
|
|
3448
|
+
}
|
|
3449
|
+
function encodeComponent(input, isAllowed) {
|
|
3450
|
+
let output = "";
|
|
3451
|
+
for (let i = 0; i < input.length; i++) {
|
|
3452
|
+
const ch = input[i];
|
|
3453
|
+
if (ch === "%" && i + 2 < input.length) {
|
|
3454
|
+
const hex = input.slice(i + 1, i + 3);
|
|
3455
|
+
if (isHexPair(hex)) {
|
|
3456
|
+
output += "%" + hex.toUpperCase();
|
|
3457
|
+
i += 2;
|
|
3458
|
+
continue;
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
if (isAllowed(ch)) {
|
|
3462
|
+
output += ch;
|
|
3463
|
+
} else {
|
|
3464
|
+
const code = input.charCodeAt(i);
|
|
3465
|
+
if (code < 128) {
|
|
3466
|
+
output += BYTE_HEX[code];
|
|
3467
|
+
} else if (code < 55296 || code > 57343) {
|
|
3468
|
+
output += percentEncodeNonAscii(code);
|
|
3469
|
+
} else if (code <= 56319 && i + 1 < input.length) {
|
|
3470
|
+
const low = input.charCodeAt(i + 1);
|
|
3471
|
+
if (low >= 56320 && low <= 57343) {
|
|
3472
|
+
output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
|
|
3473
|
+
i++;
|
|
3474
|
+
} else {
|
|
3475
|
+
output += percentEncodeNonAscii(65533);
|
|
3476
|
+
}
|
|
3477
|
+
} else {
|
|
3478
|
+
output += percentEncodeNonAscii(65533);
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
return output;
|
|
3483
|
+
}
|
|
3484
|
+
function encodeUserinfo(input) {
|
|
3485
|
+
return encodeComponent(input, isUserinfoCharacter);
|
|
3486
|
+
}
|
|
3487
|
+
function encodeQuery(input) {
|
|
3488
|
+
return encodeComponent(input, isQueryFragmentCharacter);
|
|
3489
|
+
}
|
|
3490
|
+
function encodeFragment(input) {
|
|
3491
|
+
return encodeComponent(input, isQueryFragmentCharacter);
|
|
3492
|
+
}
|
|
3493
|
+
function isEscapeSafe(cp) {
|
|
3494
|
+
return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
|
|
3495
|
+
}
|
|
3496
|
+
function normalizeQueryFragmentEncoding(input) {
|
|
3497
|
+
let output = "";
|
|
3498
|
+
for (let i = 0; i < input.length; i++) {
|
|
3499
|
+
const ch = input[i];
|
|
3500
|
+
if (ch === "%" && i + 2 < input.length) {
|
|
3501
|
+
const hex = input.slice(i + 1, i + 3);
|
|
3502
|
+
if (isHexPair(hex)) {
|
|
3503
|
+
const normalizedHex = hex.toUpperCase();
|
|
3504
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3505
|
+
if (isUnreserved(decoded)) {
|
|
3506
|
+
output += decoded;
|
|
3507
|
+
} else {
|
|
3508
|
+
output += "%" + normalizedHex;
|
|
3509
|
+
}
|
|
3510
|
+
i += 2;
|
|
3511
|
+
continue;
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
if (isQueryFragmentCharacter(ch)) {
|
|
3515
|
+
output += ch;
|
|
3516
|
+
} else {
|
|
3517
|
+
const code = input.charCodeAt(i);
|
|
3518
|
+
if (code < 128) {
|
|
3519
|
+
output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
|
|
3520
|
+
} else if (code < 55296 || code > 57343) {
|
|
3521
|
+
output += percentEncodeNonAscii(code);
|
|
3522
|
+
} else if (code <= 56319 && i + 1 < input.length) {
|
|
3523
|
+
const low = input.charCodeAt(i + 1);
|
|
3524
|
+
if (low >= 56320 && low <= 57343) {
|
|
3525
|
+
output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
|
|
3526
|
+
i++;
|
|
3527
|
+
} else {
|
|
3528
|
+
output += percentEncodeNonAscii(65533);
|
|
3529
|
+
}
|
|
3530
|
+
} else {
|
|
3531
|
+
output += percentEncodeNonAscii(65533);
|
|
3532
|
+
}
|
|
3357
3533
|
}
|
|
3358
3534
|
}
|
|
3359
3535
|
return output;
|
|
@@ -3376,14 +3552,18 @@ var require_utils = __commonJS({
|
|
|
3376
3552
|
function recomposeAuthority(component) {
|
|
3377
3553
|
const uriTokens = [];
|
|
3378
3554
|
if (component.userinfo !== void 0) {
|
|
3379
|
-
uriTokens.push(component.userinfo);
|
|
3555
|
+
uriTokens.push(encodeUserinfo(component.userinfo));
|
|
3380
3556
|
uriTokens.push("@");
|
|
3381
3557
|
}
|
|
3382
3558
|
if (component.host !== void 0) {
|
|
3383
|
-
let host =
|
|
3559
|
+
let host = component.host;
|
|
3384
3560
|
if (!isIPv4(host)) {
|
|
3385
|
-
|
|
3386
|
-
if (ipV6res.isIPV6
|
|
3561
|
+
let ipV6res = normalizeIPv6(host);
|
|
3562
|
+
if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
|
|
3563
|
+
host = normalizePercentEncoding(host, true);
|
|
3564
|
+
ipV6res = normalizeIPv6(host);
|
|
3565
|
+
}
|
|
3566
|
+
if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
|
|
3387
3567
|
host = `[${ipV6res.escapedHost}]`;
|
|
3388
3568
|
} else {
|
|
3389
3569
|
host = reescapeHostDelimiters(host, false);
|
|
@@ -3392,8 +3572,12 @@ var require_utils = __commonJS({
|
|
|
3392
3572
|
uriTokens.push(host);
|
|
3393
3573
|
}
|
|
3394
3574
|
if (typeof component.port === "number" || typeof component.port === "string") {
|
|
3575
|
+
const port = String(component.port);
|
|
3576
|
+
if (!isPort(port)) {
|
|
3577
|
+
throw new TypeError("URI port is malformed.");
|
|
3578
|
+
}
|
|
3395
3579
|
uriTokens.push(":");
|
|
3396
|
-
uriTokens.push(
|
|
3580
|
+
uriTokens.push(port);
|
|
3397
3581
|
}
|
|
3398
3582
|
return uriTokens.length ? uriTokens.join("") : void 0;
|
|
3399
3583
|
}
|
|
@@ -3403,6 +3587,11 @@ var require_utils = __commonJS({
|
|
|
3403
3587
|
reescapeHostDelimiters,
|
|
3404
3588
|
normalizePercentEncoding,
|
|
3405
3589
|
normalizePathEncoding,
|
|
3590
|
+
serializePathEncoding,
|
|
3591
|
+
normalizeQueryFragmentEncoding,
|
|
3592
|
+
encodeUserinfo,
|
|
3593
|
+
encodeQuery,
|
|
3594
|
+
encodeFragment,
|
|
3406
3595
|
escapePreservingEscapes,
|
|
3407
3596
|
removeDotSegments,
|
|
3408
3597
|
isIPv4,
|
|
@@ -3418,7 +3607,7 @@ var require_schemes = __commonJS({
|
|
|
3418
3607
|
"node_modules/fast-uri/lib/schemes.js"(exports, module) {
|
|
3419
3608
|
"use strict";
|
|
3420
3609
|
var { isUUID } = require_utils();
|
|
3421
|
-
var URN_REG =
|
|
3610
|
+
var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
|
|
3422
3611
|
var supportedSchemeNames = (
|
|
3423
3612
|
/** @type {const} */
|
|
3424
3613
|
[
|
|
@@ -3479,9 +3668,10 @@ var require_schemes = __commonJS({
|
|
|
3479
3668
|
wsComponent.secure = void 0;
|
|
3480
3669
|
}
|
|
3481
3670
|
if (wsComponent.resourceName) {
|
|
3482
|
-
const
|
|
3671
|
+
const queryIndex = wsComponent.resourceName.indexOf("?");
|
|
3672
|
+
const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
|
|
3483
3673
|
wsComponent.path = path && path !== "/" ? path : void 0;
|
|
3484
|
-
wsComponent.query =
|
|
3674
|
+
wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
|
|
3485
3675
|
wsComponent.resourceName = void 0;
|
|
3486
3676
|
}
|
|
3487
3677
|
wsComponent.fragment = void 0;
|
|
@@ -3493,7 +3683,7 @@ var require_schemes = __commonJS({
|
|
|
3493
3683
|
return urnComponent;
|
|
3494
3684
|
}
|
|
3495
3685
|
const matches = urnComponent.path.match(URN_REG);
|
|
3496
|
-
if (matches) {
|
|
3686
|
+
if (matches && matches[0] === urnComponent.path) {
|
|
3497
3687
|
const scheme = options.scheme || urnComponent.scheme || "urn";
|
|
3498
3688
|
urnComponent.nid = matches[1].toLowerCase();
|
|
3499
3689
|
urnComponent.nss = matches[2];
|
|
@@ -3627,8 +3817,17 @@ var require_schemes = __commonJS({
|
|
|
3627
3817
|
var require_fast_uri = __commonJS({
|
|
3628
3818
|
"node_modules/fast-uri/index.js"(exports, module) {
|
|
3629
3819
|
"use strict";
|
|
3630
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding,
|
|
3820
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3631
3821
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3822
|
+
var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
|
|
3823
|
+
var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
|
|
3824
|
+
function decodeValidScheme(scheme) {
|
|
3825
|
+
const decodedScheme = unescape(String(scheme));
|
|
3826
|
+
if (!VALID_SCHEME.test(decodedScheme)) {
|
|
3827
|
+
throw new TypeError(MALFORMED_SCHEME_ERROR);
|
|
3828
|
+
}
|
|
3829
|
+
return decodedScheme;
|
|
3830
|
+
}
|
|
3632
3831
|
function normalize(uri, options) {
|
|
3633
3832
|
if (typeof uri === "string") {
|
|
3634
3833
|
uri = /** @type {T} */
|
|
@@ -3639,14 +3838,36 @@ var require_fast_uri = __commonJS({
|
|
|
3639
3838
|
}
|
|
3640
3839
|
return uri;
|
|
3641
3840
|
}
|
|
3642
|
-
function
|
|
3841
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
3643
3842
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3644
|
-
const {
|
|
3645
|
-
|
|
3646
|
-
|
|
3843
|
+
const {
|
|
3844
|
+
parsed: baseParsed,
|
|
3845
|
+
malformedAuthorityOrPort: baseMalformed,
|
|
3846
|
+
malformedPercentEncoding: baseMalformedPercentEncoding,
|
|
3847
|
+
malformedSchemeSpecific: baseMalformedSchemeSpecific,
|
|
3848
|
+
malformedHost: baseMalformedHost,
|
|
3849
|
+
malformedScheme: baseMalformedScheme
|
|
3850
|
+
} = parseWithStatus(baseURI, schemelessOptions);
|
|
3851
|
+
const {
|
|
3852
|
+
parsed: relativeParsed,
|
|
3853
|
+
malformedAuthorityOrPort: relativeMalformed,
|
|
3854
|
+
malformedPercentEncoding: relativeMalformedPercentEncoding,
|
|
3855
|
+
malformedSchemeSpecific: relativeMalformedSchemeSpecific,
|
|
3856
|
+
malformedHost: relativeMalformedHost,
|
|
3857
|
+
malformedScheme: relativeMalformedScheme
|
|
3858
|
+
} = parseWithStatus(relativeURI, schemelessOptions);
|
|
3859
|
+
if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
|
|
3647
3860
|
throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
|
|
3648
3861
|
}
|
|
3649
3862
|
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
3863
|
+
const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
|
|
3864
|
+
const resolvedHost = resolved.host;
|
|
3865
|
+
const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
|
|
3866
|
+
canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
|
|
3867
|
+
const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !new RegExp("\\P{ASCII}", "u").test(resolvedHost);
|
|
3868
|
+
if (resolved.error && !encodedASCIIHost) {
|
|
3869
|
+
throw new Error(resolved.error);
|
|
3870
|
+
}
|
|
3650
3871
|
schemelessOptions.skipEscape = true;
|
|
3651
3872
|
return serialize(resolved, schemelessOptions);
|
|
3652
3873
|
}
|
|
@@ -3706,7 +3927,7 @@ var require_fast_uri = __commonJS({
|
|
|
3706
3927
|
function equal(uriA, uriB, options) {
|
|
3707
3928
|
const normalizedA = normalizeComparableURI(uriA, options);
|
|
3708
3929
|
const normalizedB = normalizeComparableURI(uriB, options);
|
|
3709
|
-
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA
|
|
3930
|
+
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA === normalizedB;
|
|
3710
3931
|
}
|
|
3711
3932
|
function serialize(cmpts, opts) {
|
|
3712
3933
|
const component = {
|
|
@@ -3727,19 +3948,22 @@ var require_fast_uri = __commonJS({
|
|
|
3727
3948
|
};
|
|
3728
3949
|
const options = Object.assign({}, opts);
|
|
3729
3950
|
const uriTokens = [];
|
|
3951
|
+
if (component.scheme) {
|
|
3952
|
+
component.scheme = decodeValidScheme(component.scheme);
|
|
3953
|
+
}
|
|
3730
3954
|
const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
|
|
3731
3955
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
3956
|
+
const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
|
|
3957
|
+
const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
|
|
3732
3958
|
if (component.path !== void 0) {
|
|
3733
3959
|
if (!options.skipEscape) {
|
|
3734
|
-
component.path =
|
|
3735
|
-
if (component.scheme !== void 0) {
|
|
3736
|
-
component.path = component.path.split("%3A").join(":");
|
|
3737
|
-
}
|
|
3960
|
+
component.path = serializePathEncoding(component.path, pathNoScheme);
|
|
3738
3961
|
} else {
|
|
3739
3962
|
component.path = normalizePercentEncoding(component.path);
|
|
3740
3963
|
}
|
|
3741
3964
|
}
|
|
3742
3965
|
if (options.reference !== "suffix" && component.scheme) {
|
|
3966
|
+
component.scheme = decodeValidScheme(component.scheme);
|
|
3743
3967
|
uriTokens.push(component.scheme, ":");
|
|
3744
3968
|
}
|
|
3745
3969
|
const authority = recomposeAuthority(component);
|
|
@@ -3757,16 +3981,19 @@ var require_fast_uri = __commonJS({
|
|
|
3757
3981
|
if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
|
|
3758
3982
|
s = removeDotSegments(s);
|
|
3759
3983
|
}
|
|
3984
|
+
if (pathNoScheme) {
|
|
3985
|
+
s = serializePathEncoding(s, true);
|
|
3986
|
+
}
|
|
3760
3987
|
if (authority === void 0 && s[0] === "/" && s[1] === "/") {
|
|
3761
3988
|
s = "/%2F" + s.slice(2);
|
|
3762
3989
|
}
|
|
3763
3990
|
uriTokens.push(s);
|
|
3764
3991
|
}
|
|
3765
3992
|
if (component.query !== void 0) {
|
|
3766
|
-
uriTokens.push("?", component.query);
|
|
3993
|
+
uriTokens.push("?", encodeQuery(component.query));
|
|
3767
3994
|
}
|
|
3768
3995
|
if (component.fragment !== void 0) {
|
|
3769
|
-
uriTokens.push("#", component.fragment);
|
|
3996
|
+
uriTokens.push("#", encodeFragment(component.fragment));
|
|
3770
3997
|
}
|
|
3771
3998
|
return uriTokens.join("");
|
|
3772
3999
|
}
|
|
@@ -3782,6 +4009,35 @@ var require_fast_uri = __commonJS({
|
|
|
3782
4009
|
}
|
|
3783
4010
|
return void 0;
|
|
3784
4011
|
}
|
|
4012
|
+
function hasMalformedPercentEncoding(component) {
|
|
4013
|
+
if (component === void 0) return false;
|
|
4014
|
+
let percent = component.indexOf("%");
|
|
4015
|
+
while (percent !== -1) {
|
|
4016
|
+
if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
|
|
4017
|
+
return true;
|
|
4018
|
+
}
|
|
4019
|
+
percent = component.indexOf("%", percent + 3);
|
|
4020
|
+
}
|
|
4021
|
+
return false;
|
|
4022
|
+
}
|
|
4023
|
+
function isIPLiteral(host) {
|
|
4024
|
+
return host[0] === "[" && host[host.length - 1] === "]";
|
|
4025
|
+
}
|
|
4026
|
+
function hasMalformedComponentPercentEncoding(matches) {
|
|
4027
|
+
const host = matches[4];
|
|
4028
|
+
return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
|
|
4029
|
+
}
|
|
4030
|
+
function canonicalizeHost(parsed, options, schemeHandler, isIP) {
|
|
4031
|
+
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
4032
|
+
try {
|
|
4033
|
+
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
4034
|
+
} catch (e) {
|
|
4035
|
+
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
4036
|
+
return true;
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
return false;
|
|
4040
|
+
}
|
|
3785
4041
|
function parseWithStatus(uri, opts) {
|
|
3786
4042
|
const options = Object.assign({}, opts);
|
|
3787
4043
|
const parsed = {
|
|
@@ -3794,6 +4050,11 @@ var require_fast_uri = __commonJS({
|
|
|
3794
4050
|
fragment: void 0
|
|
3795
4051
|
};
|
|
3796
4052
|
let malformedAuthorityOrPort = false;
|
|
4053
|
+
let malformedPercentEncoding = false;
|
|
4054
|
+
let malformedSchemeSpecific = false;
|
|
4055
|
+
let malformedHost = false;
|
|
4056
|
+
let malformedIPLiteral = false;
|
|
4057
|
+
let malformedScheme = false;
|
|
3797
4058
|
let isIP = false;
|
|
3798
4059
|
if (options.reference === "suffix") {
|
|
3799
4060
|
if (options.scheme) {
|
|
@@ -3830,6 +4091,19 @@ var require_fast_uri = __commonJS({
|
|
|
3830
4091
|
parsed.path = matches[6] || "";
|
|
3831
4092
|
parsed.query = matches[7];
|
|
3832
4093
|
parsed.fragment = matches[8];
|
|
4094
|
+
if (parsed.scheme !== void 0) {
|
|
4095
|
+
const decodedScheme = unescape(parsed.scheme);
|
|
4096
|
+
if (VALID_SCHEME.test(decodedScheme)) {
|
|
4097
|
+
parsed.scheme = decodedScheme.toLowerCase();
|
|
4098
|
+
} else {
|
|
4099
|
+
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
|
|
4100
|
+
malformedScheme = true;
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
|
|
4104
|
+
if (malformedPercentEncoding) {
|
|
4105
|
+
parsed.error = parsed.error || "URI contains malformed percent-encoding.";
|
|
4106
|
+
}
|
|
3833
4107
|
if (isNaN(parsed.port)) {
|
|
3834
4108
|
parsed.port = matches[5];
|
|
3835
4109
|
}
|
|
@@ -3841,9 +4115,16 @@ var require_fast_uri = __commonJS({
|
|
|
3841
4115
|
if (parsed.host) {
|
|
3842
4116
|
const ipv4result = isIPv4(parsed.host);
|
|
3843
4117
|
if (ipv4result === false) {
|
|
4118
|
+
const bracketedIPLiteral = isIPLiteral(parsed.host);
|
|
4119
|
+
const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
|
|
3844
4120
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
3845
|
-
|
|
3846
|
-
|
|
4121
|
+
isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
|
|
4122
|
+
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
|
|
4123
|
+
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
|
|
4124
|
+
if (malformedIPLiteral) {
|
|
4125
|
+
parsed.error = parsed.error || "URI host is malformed.";
|
|
4126
|
+
malformedAuthorityOrPort = true;
|
|
4127
|
+
}
|
|
3847
4128
|
} else {
|
|
3848
4129
|
isIP = true;
|
|
3849
4130
|
}
|
|
@@ -3861,42 +4142,36 @@ var require_fast_uri = __commonJS({
|
|
|
3861
4142
|
parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
|
|
3862
4143
|
}
|
|
3863
4144
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
|
|
3864
|
-
if (!
|
|
3865
|
-
|
|
3866
|
-
try {
|
|
3867
|
-
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
3868
|
-
} catch (e) {
|
|
3869
|
-
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
3870
|
-
}
|
|
3871
|
-
}
|
|
4145
|
+
if (!malformedIPLiteral) {
|
|
4146
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
|
|
3872
4147
|
}
|
|
3873
4148
|
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
3874
4149
|
if (uri.indexOf("%") !== -1) {
|
|
3875
|
-
if (parsed.
|
|
3876
|
-
parsed.
|
|
3877
|
-
|
|
3878
|
-
if (parsed.host !== void 0) {
|
|
3879
|
-
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
|
|
4150
|
+
if (parsed.host !== void 0 && !malformedIPLiteral) {
|
|
4151
|
+
const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4152
|
+
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
3880
4153
|
}
|
|
3881
4154
|
}
|
|
3882
4155
|
if (parsed.path) {
|
|
3883
4156
|
parsed.path = normalizePathEncoding(parsed.path);
|
|
3884
4157
|
}
|
|
4158
|
+
if (parsed.query) {
|
|
4159
|
+
parsed.query = normalizeQueryFragmentEncoding(parsed.query);
|
|
4160
|
+
}
|
|
3885
4161
|
if (parsed.fragment) {
|
|
3886
|
-
|
|
3887
|
-
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
3888
|
-
} catch {
|
|
3889
|
-
parsed.error = parsed.error || "URI malformed";
|
|
3890
|
-
}
|
|
4162
|
+
parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
|
|
3891
4163
|
}
|
|
3892
4164
|
}
|
|
3893
4165
|
if (schemeHandler && schemeHandler.parse) {
|
|
3894
4166
|
schemeHandler.parse(parsed, options);
|
|
4167
|
+
if (schemeHandler === SCHEMES.urn && parsed.nid === void 0) {
|
|
4168
|
+
malformedSchemeSpecific = true;
|
|
4169
|
+
}
|
|
3895
4170
|
}
|
|
3896
4171
|
} else {
|
|
3897
4172
|
parsed.error = parsed.error || "URI can not be parsed.";
|
|
3898
4173
|
}
|
|
3899
|
-
return { parsed, malformedAuthorityOrPort };
|
|
4174
|
+
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
|
|
3900
4175
|
}
|
|
3901
4176
|
function parse3(uri, opts) {
|
|
3902
4177
|
return parseWithStatus(uri, opts).parsed;
|
|
@@ -3905,25 +4180,33 @@ var require_fast_uri = __commonJS({
|
|
|
3905
4180
|
return normalizeStringWithStatus(uri, opts).normalized;
|
|
3906
4181
|
}
|
|
3907
4182
|
function normalizeStringWithStatus(uri, opts) {
|
|
3908
|
-
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
|
|
4183
|
+
const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
|
|
3909
4184
|
return {
|
|
3910
|
-
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
3911
|
-
malformedAuthorityOrPort
|
|
4185
|
+
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
|
|
4186
|
+
malformedAuthorityOrPort,
|
|
4187
|
+
malformedPercentEncoding,
|
|
4188
|
+
malformedSchemeSpecific,
|
|
4189
|
+
malformedHost,
|
|
4190
|
+
malformedScheme
|
|
3912
4191
|
};
|
|
3913
4192
|
}
|
|
3914
4193
|
function normalizeComparableURI(uri, opts) {
|
|
3915
|
-
if (typeof uri
|
|
3916
|
-
|
|
3917
|
-
return malformedAuthorityOrPort ? void 0 : normalized;
|
|
4194
|
+
if (typeof uri !== "string" && typeof uri !== "object") {
|
|
4195
|
+
return void 0;
|
|
3918
4196
|
}
|
|
3919
|
-
|
|
3920
|
-
|
|
4197
|
+
let value;
|
|
4198
|
+
try {
|
|
4199
|
+
value = typeof uri === "string" ? uri : serialize(uri, opts);
|
|
4200
|
+
} catch {
|
|
4201
|
+
return void 0;
|
|
3921
4202
|
}
|
|
4203
|
+
const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
|
|
4204
|
+
return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? void 0 : normalized;
|
|
3922
4205
|
}
|
|
3923
4206
|
var fastUri = {
|
|
3924
4207
|
SCHEMES,
|
|
3925
4208
|
normalize,
|
|
3926
|
-
resolve,
|
|
4209
|
+
resolve: resolve2,
|
|
3927
4210
|
resolveComponent,
|
|
3928
4211
|
equal,
|
|
3929
4212
|
serialize,
|
|
@@ -6913,11 +7196,10 @@ var require_dist = __commonJS({
|
|
|
6913
7196
|
});
|
|
6914
7197
|
|
|
6915
7198
|
// src/server/server.ts
|
|
6916
|
-
import {
|
|
6917
|
-
import { existsSync as existsSync2, realpathSync } from "node:fs";
|
|
7199
|
+
import { existsSync as existsSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
6918
7200
|
import { homedir } from "node:os";
|
|
6919
|
-
import {
|
|
6920
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7201
|
+
import { join as join8 } from "node:path";
|
|
7202
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
6921
7203
|
|
|
6922
7204
|
// node_modules/zod/v3/external.js
|
|
6923
7205
|
var external_exports = {};
|
|
@@ -19029,7 +19311,7 @@ var Protocol = class {
|
|
|
19029
19311
|
return;
|
|
19030
19312
|
}
|
|
19031
19313
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
19032
|
-
await new Promise((
|
|
19314
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
19033
19315
|
options?.signal?.throwIfAborted();
|
|
19034
19316
|
}
|
|
19035
19317
|
} catch (error2) {
|
|
@@ -19046,7 +19328,7 @@ var Protocol = class {
|
|
|
19046
19328
|
*/
|
|
19047
19329
|
request(request, resultSchema, options) {
|
|
19048
19330
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
19049
|
-
return new Promise((
|
|
19331
|
+
return new Promise((resolve2, reject) => {
|
|
19050
19332
|
const earlyReject = (error2) => {
|
|
19051
19333
|
reject(error2);
|
|
19052
19334
|
};
|
|
@@ -19124,7 +19406,7 @@ var Protocol = class {
|
|
|
19124
19406
|
if (!parseResult.success) {
|
|
19125
19407
|
reject(parseResult.error);
|
|
19126
19408
|
} else {
|
|
19127
|
-
|
|
19409
|
+
resolve2(parseResult.data);
|
|
19128
19410
|
}
|
|
19129
19411
|
} catch (error2) {
|
|
19130
19412
|
reject(error2);
|
|
@@ -19385,12 +19667,12 @@ var Protocol = class {
|
|
|
19385
19667
|
}
|
|
19386
19668
|
} catch {
|
|
19387
19669
|
}
|
|
19388
|
-
return new Promise((
|
|
19670
|
+
return new Promise((resolve2, reject) => {
|
|
19389
19671
|
if (signal.aborted) {
|
|
19390
19672
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
19391
19673
|
return;
|
|
19392
19674
|
}
|
|
19393
|
-
const timeoutId = setTimeout(
|
|
19675
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
19394
19676
|
signal.addEventListener("abort", () => {
|
|
19395
19677
|
clearTimeout(timeoutId);
|
|
19396
19678
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -20481,7 +20763,7 @@ var McpServer = class {
|
|
|
20481
20763
|
let task = createTaskResult.task;
|
|
20482
20764
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
20483
20765
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
20484
|
-
await new Promise((
|
|
20766
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
20485
20767
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
20486
20768
|
if (!updatedTask) {
|
|
20487
20769
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -21145,12 +21427,12 @@ var StdioServerTransport = class {
|
|
|
21145
21427
|
this.onclose?.();
|
|
21146
21428
|
}
|
|
21147
21429
|
send(message) {
|
|
21148
|
-
return new Promise((
|
|
21430
|
+
return new Promise((resolve2) => {
|
|
21149
21431
|
const json = serializeMessage(message);
|
|
21150
21432
|
if (this._stdout.write(json)) {
|
|
21151
|
-
|
|
21433
|
+
resolve2();
|
|
21152
21434
|
} else {
|
|
21153
|
-
this._stdout.once("drain",
|
|
21435
|
+
this._stdout.once("drain", resolve2);
|
|
21154
21436
|
}
|
|
21155
21437
|
});
|
|
21156
21438
|
}
|
|
@@ -21237,7 +21519,7 @@ function describeMapError(e) {
|
|
|
21237
21519
|
case "layer-holds-group":
|
|
21238
21520
|
return `layer "${e.id}" still holds group "${e.occupant}"; remove its groups (removeGroup) first`;
|
|
21239
21521
|
case "edge-not-downward":
|
|
21240
|
-
return `edge ${e.from} (rank ${e.fromRank}) -> ${e.to} (rank ${e.toRank}) is not strictly downward;
|
|
21522
|
+
return `edge ${e.from} (rank ${e.fromRank}) -> ${e.to} (rank ${e.toRank}) is not strictly downward; ` + (e.fromRank === e.toRank ? `same-band siblings may not depend on each other \u2014 either "${e.to}" is really a lower concept (declare it on a lower band) or "${e.from}" and "${e.to}" are one node (merge them)` : `"${e.from}" would USE "${e.to}" from a lower band \u2014 reverse the edge if "${e.to}" is the user, otherwise move or re-rank so "${e.from}" sits above "${e.to}"`);
|
|
21241
21523
|
}
|
|
21242
21524
|
}
|
|
21243
21525
|
|
|
@@ -21587,6 +21869,41 @@ function flipForSequence(map) {
|
|
|
21587
21869
|
};
|
|
21588
21870
|
}
|
|
21589
21871
|
|
|
21872
|
+
// src/domain/text.ts
|
|
21873
|
+
var NO_CONTROLS = /^[^\u0000-\u001f\u007f-\u009f]*$/;
|
|
21874
|
+
var NO_CONTROLS_TEXT = "one line of text; control characters (ESC, newline, tab) are not allowed";
|
|
21875
|
+
var NO_CONTROLS_BUT_BREAKS = /^[^\u0000-\u0008\u000b-\u001f\u007f-\u009f]*$/;
|
|
21876
|
+
var NO_CONTROLS_BUT_BREAKS_TEXT = "text with optional newlines (\\n) and tabs; other control characters (ESC, BEL, CR) are not allowed";
|
|
21877
|
+
function mapTextError(map) {
|
|
21878
|
+
const check2 = (field, value, multiline = false) => value === void 0 || (multiline ? NO_CONTROLS_BUT_BREAKS : NO_CONTROLS).test(value) ? void 0 : `${field}: ${multiline ? NO_CONTROLS_BUT_BREAKS_TEXT : NO_CONTROLS_TEXT}`;
|
|
21879
|
+
let error2 = check2("title", map.title);
|
|
21880
|
+
if (error2) return error2;
|
|
21881
|
+
for (const [i, layer] of map.layers.entries()) {
|
|
21882
|
+
error2 = check2(`layers[${i}].name`, layer.name);
|
|
21883
|
+
if (error2) return error2;
|
|
21884
|
+
}
|
|
21885
|
+
for (const name of ["lanes", "groups"]) {
|
|
21886
|
+
for (const [i, item] of map[name].entries()) {
|
|
21887
|
+
error2 = check2(`${name}[${i}].label`, item.label);
|
|
21888
|
+
if (error2) return error2;
|
|
21889
|
+
}
|
|
21890
|
+
}
|
|
21891
|
+
for (const [i, node] of map.nodes.entries()) {
|
|
21892
|
+
for (const name of ["label", "evidence", "detail"]) {
|
|
21893
|
+
error2 = check2(`nodes[${i}].${name}`, node[name], name !== "label");
|
|
21894
|
+
if (error2) return error2;
|
|
21895
|
+
}
|
|
21896
|
+
}
|
|
21897
|
+
for (const [i, edge] of map.edges.entries()) {
|
|
21898
|
+
error2 = check2(`edges[${i}].label`, edge.label);
|
|
21899
|
+
if (error2) return error2;
|
|
21900
|
+
}
|
|
21901
|
+
return void 0;
|
|
21902
|
+
}
|
|
21903
|
+
function terminalText(text2, multiline = false) {
|
|
21904
|
+
return text2.replace(multiline ? /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g : /[\u0000-\u001f\u007f-\u009f]/g, "?");
|
|
21905
|
+
}
|
|
21906
|
+
|
|
21590
21907
|
// src/render/width.ts
|
|
21591
21908
|
var WIDE_RANGES = [
|
|
21592
21909
|
[4352, 4447],
|
|
@@ -21681,6 +21998,7 @@ function displayWidth(text2) {
|
|
|
21681
21998
|
return w;
|
|
21682
21999
|
}
|
|
21683
22000
|
function fitWidth(s, width) {
|
|
22001
|
+
s = terminalText(s);
|
|
21684
22002
|
if (displayWidth(s) <= width) return s;
|
|
21685
22003
|
let out = "";
|
|
21686
22004
|
let w = 0;
|
|
@@ -21696,7 +22014,7 @@ function wrapWidth(s, width) {
|
|
|
21696
22014
|
const lines = [];
|
|
21697
22015
|
let line2 = "";
|
|
21698
22016
|
let w = 0;
|
|
21699
|
-
for (const ch of s.replace(/\r/g, "")) {
|
|
22017
|
+
for (const ch of terminalText(s.replace(/\r/g, "").replace(/\t/g, " "), true)) {
|
|
21700
22018
|
if (ch === "\n") {
|
|
21701
22019
|
lines.push(line2);
|
|
21702
22020
|
line2 = "";
|
|
@@ -21725,7 +22043,9 @@ var SGR = {
|
|
|
21725
22043
|
greenDim: "32;2",
|
|
21726
22044
|
// done, but nothing behind the claim: green, not fully lit
|
|
21727
22045
|
red: "31",
|
|
21728
|
-
faint: "90"
|
|
22046
|
+
faint: "90",
|
|
22047
|
+
focus: "97"
|
|
22048
|
+
// bright foreground without changing font weight
|
|
21729
22049
|
};
|
|
21730
22050
|
var ANSI_RESET = "\x1B[0m";
|
|
21731
22051
|
var UP = 1;
|
|
@@ -21786,7 +22106,7 @@ var Canvas = class {
|
|
|
21786
22106
|
/** Write literal text starting at (x, y). Returns the column just past it. */
|
|
21787
22107
|
text(x, y, s, style, bold = false) {
|
|
21788
22108
|
let cx = x;
|
|
21789
|
-
for (const ch of s) {
|
|
22109
|
+
for (const ch of terminalText(s)) {
|
|
21790
22110
|
const w = charWidth(ch.codePointAt(0));
|
|
21791
22111
|
if (w === 0) {
|
|
21792
22112
|
const base = this.cell(Math.max(0, cx - 1), y);
|
|
@@ -21845,7 +22165,7 @@ var Canvas = class {
|
|
|
21845
22165
|
} else if (charWidth(ch.codePointAt(0)) === 2 && x + 1 >= vp.x + vp.width) {
|
|
21846
22166
|
ch = " ";
|
|
21847
22167
|
}
|
|
21848
|
-
const params = ch === " " ? "" : isWire ? c.bright ?
|
|
22168
|
+
const params = ch === " " ? "" : isWire ? c.bright ? SGR.focus : SGR.faint : [SGR[c.style], c.bold ? "1" : ""].filter(Boolean).join(";");
|
|
21849
22169
|
if (opts.color && params !== open) {
|
|
21850
22170
|
line2 += (open !== "" ? ANSI_RESET : "") + (params !== "" ? `\x1B[${params}m` : "");
|
|
21851
22171
|
open = params;
|
|
@@ -22191,6 +22511,7 @@ function drawBands(canvas, columns, rows, wiredWidth, totalWidth) {
|
|
|
22191
22511
|
function drawBox(canvas, box, opts, neutral, face, focused = false) {
|
|
22192
22512
|
const { node, x, y, w } = box;
|
|
22193
22513
|
const skin = neutral ? neutralSkin(opts.unicode) : skinFor(face, opts.unicode);
|
|
22514
|
+
const borderStyle = focused ? "focus" : skin.style;
|
|
22194
22515
|
const slotGlyph = neutral ? neutralGlyph(node, opts.unicode) : glyphFor(face, opts);
|
|
22195
22516
|
if (box.borderless) {
|
|
22196
22517
|
canvas.text(x + 1, y, slotGlyph, skin.style, true);
|
|
@@ -22198,18 +22519,18 @@ function drawBox(canvas, box, opts, neutral, face, focused = false) {
|
|
|
22198
22519
|
}
|
|
22199
22520
|
const inner = w - 2;
|
|
22200
22521
|
const pad = box.pad === 1 ? " " : "";
|
|
22201
|
-
canvas.text(x, y, skin.corners[0] + skin.h.repeat(inner) + skin.corners[1],
|
|
22202
|
-
canvas.text(x, y + 1, skin.v,
|
|
22522
|
+
canvas.text(x, y, skin.corners[0] + skin.h.repeat(inner) + skin.corners[1], borderStyle);
|
|
22523
|
+
canvas.text(x, y + 1, skin.v, borderStyle);
|
|
22203
22524
|
canvas.text(x + 1, y + 1, `${pad}${slotGlyph} ${box.label}${pad}`, skin.style, true);
|
|
22204
|
-
canvas.text(x + w - 1, y + 1, skin.v,
|
|
22525
|
+
canvas.text(x + w - 1, y + 1, skin.v, borderStyle);
|
|
22205
22526
|
for (let i = 0; i < box.extra.length; i++) {
|
|
22206
22527
|
const row = box.extra[i];
|
|
22207
22528
|
const yy = y + 2 + i;
|
|
22208
|
-
canvas.text(x, yy, skin.v,
|
|
22529
|
+
canvas.text(x, yy, skin.v, borderStyle);
|
|
22209
22530
|
canvas.text(x + 1, yy, row.text, row.style);
|
|
22210
|
-
canvas.text(x + w - 1, yy, skin.v,
|
|
22531
|
+
canvas.text(x + w - 1, yy, skin.v, borderStyle);
|
|
22211
22532
|
}
|
|
22212
|
-
canvas.text(x, y + box.h - 1, skin.corners[2] + skin.h.repeat(inner) + skin.corners[3],
|
|
22533
|
+
canvas.text(x, y + box.h - 1, skin.corners[2] + skin.h.repeat(inner) + skin.corners[3], borderStyle);
|
|
22213
22534
|
}
|
|
22214
22535
|
function drawEdges(canvas, edges, rows, opts) {
|
|
22215
22536
|
for (const edge of edges) {
|
|
@@ -22388,29 +22709,43 @@ function layoutRows(columns, geo, gapRowCount, hasTitle, hasLanes) {
|
|
|
22388
22709
|
|
|
22389
22710
|
// src/render/render.ts
|
|
22390
22711
|
function renderMap(map, opts) {
|
|
22391
|
-
const built =
|
|
22712
|
+
const built = paint(prepareScene(map, opts), opts);
|
|
22392
22713
|
return built.canvas.emit(opts);
|
|
22393
22714
|
}
|
|
22394
|
-
function
|
|
22715
|
+
function prepareScene(map, opts) {
|
|
22395
22716
|
const oriented = flipForSequence(map);
|
|
22396
22717
|
const plainGeo = zoomGeometry(opts.zoom ?? ZOOM_DEFAULT);
|
|
22397
22718
|
const aggregated = plainGeo.mode === "constellation" ? aggregateMap(oriented) : void 0;
|
|
22398
22719
|
const drawn = aggregated ?? oriented;
|
|
22399
|
-
|
|
22720
|
+
const unverified = unverifiedDoneIds(oriented, drawn);
|
|
22721
|
+
if (drawn.layers.length === 0) return { map: drawn, unverified };
|
|
22722
|
+
return { map: drawn, unverified, geometry: prepareGeometry(drawn, opts, aggregated !== void 0 ? AGGREGATE_GEO : plainGeo) };
|
|
22400
22723
|
}
|
|
22401
|
-
function
|
|
22402
|
-
const canvas = new Canvas();
|
|
22403
|
-
if (map.layers.length === 0) {
|
|
22404
|
-
canvas.text(0, 0, map.title ?? "mellos mapping", "none", true);
|
|
22405
|
-
canvas.text(0, 2, "(empty map \u2014 declare layers and nodes to begin)", "dim");
|
|
22406
|
-
return { canvas, hits: [] };
|
|
22407
|
-
}
|
|
22724
|
+
function prepareGeometry(map, opts, geo) {
|
|
22408
22725
|
const neutral = isNeutralKind(map);
|
|
22409
22726
|
const columns = layoutColumns(map, geo, opts.unicode, neutral);
|
|
22410
22727
|
const routing = routeEdges(map, columns);
|
|
22411
22728
|
const rows = layoutRows(columns, geo, routing.gapRowCount, map.title !== void 0, map.lanes.length > 0);
|
|
22412
22729
|
const wiredWidth = routing.fallbackCount > 0 ? columns.contentWidth + 2 + routing.fallbackCount * 2 : columns.contentWidth;
|
|
22413
22730
|
const totalWidth = wiredWidth + Math.max(...columns.bandLabel.map(displayWidth));
|
|
22731
|
+
const hits = [...rows.boxOf.values()].map((b) => ({
|
|
22732
|
+
id: b.node.id,
|
|
22733
|
+
x: b.x,
|
|
22734
|
+
y: b.y,
|
|
22735
|
+
w: b.w,
|
|
22736
|
+
h: b.h
|
|
22737
|
+
}));
|
|
22738
|
+
return { neutral, columns, routing, rows, wiredWidth, totalWidth, hits };
|
|
22739
|
+
}
|
|
22740
|
+
function paint(scene, opts) {
|
|
22741
|
+
const { map, unverified, geometry } = scene;
|
|
22742
|
+
const canvas = new Canvas();
|
|
22743
|
+
if (geometry === void 0) {
|
|
22744
|
+
canvas.text(0, 0, map.title ?? "mellos mapping", "none", true);
|
|
22745
|
+
canvas.text(0, 2, "(empty map \u2014 declare layers and nodes to begin)", "dim");
|
|
22746
|
+
return { canvas, hits: [] };
|
|
22747
|
+
}
|
|
22748
|
+
const { neutral, columns, routing, rows, wiredWidth, totalWidth, hits } = geometry;
|
|
22414
22749
|
if (map.title !== void 0) drawTitle(canvas, map.title);
|
|
22415
22750
|
drawLaneHeaders(canvas, map, columns, rows);
|
|
22416
22751
|
drawBands(canvas, columns, rows, wiredWidth, totalWidth);
|
|
@@ -22421,20 +22756,9 @@ function paint(map, opts, geo, unverified) {
|
|
|
22421
22756
|
}
|
|
22422
22757
|
drawEdges(canvas, routing.edges, rows, opts);
|
|
22423
22758
|
drawLegend(canvas, map, opts, rows.legendY, neutral, unverified.size > 0);
|
|
22424
|
-
const hits = [...rows.boxOf.values()].map((b) => ({
|
|
22425
|
-
id: b.node.id,
|
|
22426
|
-
x: b.x,
|
|
22427
|
-
y: b.y,
|
|
22428
|
-
w: b.w,
|
|
22429
|
-
h: b.h
|
|
22430
|
-
}));
|
|
22431
22759
|
return { canvas, hits };
|
|
22432
22760
|
}
|
|
22433
22761
|
|
|
22434
|
-
// src/store/store.ts
|
|
22435
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
22436
|
-
import { basename, dirname, join } from "node:path";
|
|
22437
|
-
|
|
22438
22762
|
// src/store/format.ts
|
|
22439
22763
|
var STATE_FILE_VERSION = 1;
|
|
22440
22764
|
function makePageId(raw) {
|
|
@@ -22645,7 +22969,8 @@ function parseMap(raw, path) {
|
|
|
22645
22969
|
if (!linked.ok) return err({ kind: "invariant-violation", path, violation: linked.error });
|
|
22646
22970
|
map = linked.value;
|
|
22647
22971
|
}
|
|
22648
|
-
|
|
22972
|
+
const textError = mapTextError(map);
|
|
22973
|
+
return textError ? err({ kind: "bad-shape", path, detail: textError }) : ok(map);
|
|
22649
22974
|
}
|
|
22650
22975
|
function serializeMap(map) {
|
|
22651
22976
|
const body = {
|
|
@@ -22661,7 +22986,9 @@ function serializeMap(map) {
|
|
|
22661
22986
|
return JSON.stringify(body, null, 2) + "\n";
|
|
22662
22987
|
}
|
|
22663
22988
|
|
|
22664
|
-
// src/store/
|
|
22989
|
+
// src/store/atomic.ts
|
|
22990
|
+
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
22991
|
+
import { dirname } from "node:path";
|
|
22665
22992
|
var RENAME_MAX_ATTEMPTS = 10;
|
|
22666
22993
|
var RENAME_BACKOFF_STEP_MS = 10;
|
|
22667
22994
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOENT"]);
|
|
@@ -22678,6 +23005,9 @@ function errnoOf(e) {
|
|
|
22678
23005
|
return e.code ?? e.message;
|
|
22679
23006
|
}
|
|
22680
23007
|
function writeFileAtomic(path, contents) {
|
|
23008
|
+
return writeAtomic(path, contents, RENAME_MAX_ATTEMPTS);
|
|
23009
|
+
}
|
|
23010
|
+
function writeAtomic(path, contents, maxAttempts) {
|
|
22681
23011
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
22682
23012
|
try {
|
|
22683
23013
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -22693,7 +23023,7 @@ function writeFileAtomic(path, contents) {
|
|
|
22693
23023
|
return ok(void 0);
|
|
22694
23024
|
} catch (e) {
|
|
22695
23025
|
const code = errnoOf(e);
|
|
22696
|
-
if (!TRANSIENT_RENAME_CODES.has(code) || attempt >=
|
|
23026
|
+
if (!TRANSIENT_RENAME_CODES.has(code) || attempt >= maxAttempts) {
|
|
22697
23027
|
discardTemp(tmp);
|
|
22698
23028
|
return err({ kind: "save-failed", path, detail: `${code} after ${attempt} attempt(s)` });
|
|
22699
23029
|
}
|
|
@@ -22702,17 +23032,15 @@ function writeFileAtomic(path, contents) {
|
|
|
22702
23032
|
}
|
|
22703
23033
|
}
|
|
22704
23034
|
}
|
|
22705
|
-
|
|
22706
|
-
|
|
22707
|
-
}
|
|
22708
|
-
|
|
22709
|
-
return text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
|
|
22710
|
-
}
|
|
23035
|
+
|
|
23036
|
+
// src/store/pages.ts
|
|
23037
|
+
import { existsSync, readdirSync, rmSync as rmSync2 } from "node:fs";
|
|
23038
|
+
import { basename, dirname as dirname2, join } from "node:path";
|
|
22711
23039
|
var STORE_DIR_NAME = ".mellos";
|
|
22712
23040
|
var STATE_FILE_RELATIVE_PATH = join(STORE_DIR_NAME, "map.json");
|
|
22713
23041
|
var PAGES_DIR_NAME = "pages";
|
|
22714
23042
|
function pageFilePath(defaultFile, page2) {
|
|
22715
|
-
return page2 === void 0 ? defaultFile : join(
|
|
23043
|
+
return page2 === void 0 ? defaultFile : join(dirname2(defaultFile), PAGES_DIR_NAME, `${page2}.json`);
|
|
22716
23044
|
}
|
|
22717
23045
|
function pageIdOfFile(defaultFile, path) {
|
|
22718
23046
|
if (path === defaultFile) return void 0;
|
|
@@ -22724,28 +23052,40 @@ function listPageFiles(defaultFile) {
|
|
|
22724
23052
|
if (existsSync(defaultFile)) out.push(defaultFile);
|
|
22725
23053
|
let entries = [];
|
|
22726
23054
|
try {
|
|
22727
|
-
entries = readdirSync(join(
|
|
23055
|
+
entries = readdirSync(join(dirname2(defaultFile), PAGES_DIR_NAME));
|
|
22728
23056
|
} catch {
|
|
22729
23057
|
}
|
|
22730
23058
|
for (const e of entries.sort()) {
|
|
22731
|
-
if (e.endsWith(".json")) out.push(join(
|
|
23059
|
+
if (e.endsWith(".json")) out.push(join(dirname2(defaultFile), PAGES_DIR_NAME, e));
|
|
22732
23060
|
}
|
|
22733
23061
|
return out;
|
|
22734
23062
|
}
|
|
22735
23063
|
function deletePageFile(path) {
|
|
22736
23064
|
try {
|
|
22737
|
-
|
|
23065
|
+
rmSync2(path, { force: true });
|
|
22738
23066
|
return ok(void 0);
|
|
22739
23067
|
} catch (e) {
|
|
22740
23068
|
return err({ kind: "delete-failed", path, detail: errnoOf(e) });
|
|
22741
23069
|
}
|
|
22742
23070
|
}
|
|
23071
|
+
|
|
23072
|
+
// src/store/json-text.ts
|
|
23073
|
+
function isRecord2(v) {
|
|
23074
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
23075
|
+
}
|
|
23076
|
+
function stripBom(text2) {
|
|
23077
|
+
return text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
|
|
23078
|
+
}
|
|
23079
|
+
|
|
23080
|
+
// src/store/viewers.ts
|
|
23081
|
+
import { readdirSync as readdirSync2, readFileSync, statSync, rmSync as rmSync3 } from "node:fs";
|
|
23082
|
+
import { dirname as dirname3, join as join2 } from "node:path";
|
|
22743
23083
|
var VIEWERS_DIR_NAME = "viewers";
|
|
22744
23084
|
var VIEWER_FILE_VERSION = 1;
|
|
22745
23085
|
var VIEWER_STALE_MS = 5e3;
|
|
22746
23086
|
var VIEWER_SWEEP_MS = 6e4;
|
|
22747
23087
|
function viewersDirPath(defaultFile) {
|
|
22748
|
-
return
|
|
23088
|
+
return join2(dirname3(defaultFile), VIEWERS_DIR_NAME);
|
|
22749
23089
|
}
|
|
22750
23090
|
function viewerPidOf(fileName) {
|
|
22751
23091
|
const m = /^(\d+)\.json$/.exec(fileName);
|
|
@@ -22762,17 +23102,20 @@ function parseViewerReport(raw) {
|
|
|
22762
23102
|
if (parsed["version"] !== VIEWER_FILE_VERSION) return void 0;
|
|
22763
23103
|
const follow = parsed["follow"];
|
|
22764
23104
|
if (typeof follow !== "boolean") return void 0;
|
|
23105
|
+
const owner = parsed["owner"];
|
|
23106
|
+
if (owner !== void 0 && (typeof owner !== "string" || !makePageId(owner).ok)) return void 0;
|
|
23107
|
+
const binding = owner === void 0 ? {} : { owner };
|
|
22765
23108
|
const page2 = parsed["page"];
|
|
22766
|
-
if (page2 === null || page2 === void 0) return { page: void 0, follow };
|
|
23109
|
+
if (page2 === null || page2 === void 0) return { page: void 0, follow, ...binding };
|
|
22767
23110
|
if (typeof page2 !== "string") return void 0;
|
|
22768
23111
|
const id2 = makePageId(page2);
|
|
22769
|
-
return id2.ok ? { page: id2.value, follow } : void 0;
|
|
23112
|
+
return id2.ok ? { page: id2.value, follow, ...binding } : void 0;
|
|
22770
23113
|
}
|
|
22771
23114
|
function readLiveViewers(defaultFile, nowMs) {
|
|
22772
23115
|
const dir = viewersDirPath(defaultFile);
|
|
22773
23116
|
let names;
|
|
22774
23117
|
try {
|
|
22775
|
-
names =
|
|
23118
|
+
names = readdirSync2(dir);
|
|
22776
23119
|
} catch {
|
|
22777
23120
|
return [];
|
|
22778
23121
|
}
|
|
@@ -22780,13 +23123,13 @@ function readLiveViewers(defaultFile, nowMs) {
|
|
|
22780
23123
|
for (const name of names) {
|
|
22781
23124
|
const pid = viewerPidOf(name);
|
|
22782
23125
|
if (pid === void 0) continue;
|
|
22783
|
-
const path =
|
|
23126
|
+
const path = join2(dir, name);
|
|
22784
23127
|
let raw;
|
|
22785
23128
|
let ageMs;
|
|
22786
23129
|
try {
|
|
22787
23130
|
ageMs = Math.max(0, nowMs - statSync(path).mtimeMs);
|
|
22788
23131
|
if (ageMs > VIEWER_SWEEP_MS) {
|
|
22789
|
-
|
|
23132
|
+
rmSync3(path, { force: true });
|
|
22790
23133
|
continue;
|
|
22791
23134
|
}
|
|
22792
23135
|
if (ageMs > VIEWER_STALE_MS) continue;
|
|
@@ -22799,13 +23142,17 @@ function readLiveViewers(defaultFile, nowMs) {
|
|
|
22799
23142
|
}
|
|
22800
23143
|
return live.sort((a, b) => a.ageMs - b.ageMs || a.pid - b.pid);
|
|
22801
23144
|
}
|
|
23145
|
+
|
|
23146
|
+
// src/store/policy.ts
|
|
23147
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
23148
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
22802
23149
|
var CONFIG_FILE_NAME = "config.json";
|
|
22803
23150
|
var CONFIG_FILE_VERSION = 1;
|
|
22804
23151
|
function configFilePath(defaultFile) {
|
|
22805
|
-
return
|
|
23152
|
+
return join3(dirname4(defaultFile), CONFIG_FILE_NAME);
|
|
22806
23153
|
}
|
|
22807
23154
|
function userConfigFilePath(userBase) {
|
|
22808
|
-
return
|
|
23155
|
+
return join3(userBase, STORE_DIR_NAME, CONFIG_FILE_NAME);
|
|
22809
23156
|
}
|
|
22810
23157
|
var MAPPING_POLICIES = ["always", "complex", "on-request"];
|
|
22811
23158
|
function makeMappingPolicy(raw) {
|
|
@@ -22824,7 +23171,7 @@ function describeMappingPolicy(policy) {
|
|
|
22824
23171
|
function loadMappingPolicy(path) {
|
|
22825
23172
|
let text2;
|
|
22826
23173
|
try {
|
|
22827
|
-
text2 =
|
|
23174
|
+
text2 = readFileSync2(path, "utf8");
|
|
22828
23175
|
} catch (e) {
|
|
22829
23176
|
if (e.code === "ENOENT") return ok(void 0);
|
|
22830
23177
|
throw e;
|
|
@@ -22859,27 +23206,34 @@ function effectiveMappingPolicy(projectConfigFile, userConfigFile) {
|
|
|
22859
23206
|
const source = project.value !== void 0 ? "project" : user.value !== void 0 ? "user" : void 0;
|
|
22860
23207
|
return ok({ project: project.value, user: user.value, effective, source });
|
|
22861
23208
|
}
|
|
22862
|
-
|
|
23209
|
+
|
|
23210
|
+
// src/store/migration.ts
|
|
23211
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, renameSync as renameSync2 } from "node:fs";
|
|
23212
|
+
import { dirname as dirname5, join as join4 } from "node:path";
|
|
23213
|
+
var LEGACY_STATE_FILE_RELATIVE_PATH = join4(".claude", "mellos-mapping.json");
|
|
22863
23214
|
var LEGACY_PAGES_DIR_NAME = "mellos-mapping.pages";
|
|
22864
23215
|
var LEGACY_CONFIG_FILE_NAME = "mellos-mapping.config.json";
|
|
22865
23216
|
function migrateLegacyStore(defaultFile) {
|
|
22866
|
-
const projectRoot =
|
|
22867
|
-
const legacyDefault =
|
|
22868
|
-
const legacyPages =
|
|
22869
|
-
const legacyConfig =
|
|
22870
|
-
const hasLegacy =
|
|
22871
|
-
const hasCurrent =
|
|
23217
|
+
const projectRoot = dirname5(dirname5(defaultFile));
|
|
23218
|
+
const legacyDefault = join4(projectRoot, LEGACY_STATE_FILE_RELATIVE_PATH);
|
|
23219
|
+
const legacyPages = join4(dirname5(legacyDefault), LEGACY_PAGES_DIR_NAME);
|
|
23220
|
+
const legacyConfig = join4(dirname5(legacyDefault), LEGACY_CONFIG_FILE_NAME);
|
|
23221
|
+
const hasLegacy = existsSync2(legacyDefault) || existsSync2(legacyPages) || existsSync2(legacyConfig);
|
|
23222
|
+
const hasCurrent = existsSync2(defaultFile) || existsSync2(join4(dirname5(defaultFile), PAGES_DIR_NAME)) || existsSync2(configFilePath(defaultFile));
|
|
22872
23223
|
if (!hasLegacy || hasCurrent) return false;
|
|
22873
|
-
|
|
22874
|
-
if (
|
|
22875
|
-
if (
|
|
22876
|
-
if (
|
|
23224
|
+
mkdirSync2(dirname5(defaultFile), { recursive: true });
|
|
23225
|
+
if (existsSync2(legacyDefault)) renameSync2(legacyDefault, defaultFile);
|
|
23226
|
+
if (existsSync2(legacyPages)) renameSync2(legacyPages, join4(dirname5(defaultFile), PAGES_DIR_NAME));
|
|
23227
|
+
if (existsSync2(legacyConfig)) renameSync2(legacyConfig, configFilePath(defaultFile));
|
|
22877
23228
|
return true;
|
|
22878
23229
|
}
|
|
23230
|
+
|
|
23231
|
+
// src/store/maps.ts
|
|
23232
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
22879
23233
|
function loadMapFile(path) {
|
|
22880
23234
|
let text2;
|
|
22881
23235
|
try {
|
|
22882
|
-
text2 =
|
|
23236
|
+
text2 = readFileSync3(path, "utf8");
|
|
22883
23237
|
} catch (e) {
|
|
22884
23238
|
const code = e.code;
|
|
22885
23239
|
if (code === "ENOENT") return err({ kind: "not-found", path });
|
|
@@ -22894,6 +23248,8 @@ function loadMapFile(path) {
|
|
|
22894
23248
|
return parseMap(raw, path);
|
|
22895
23249
|
}
|
|
22896
23250
|
function saveMapFile(path, map) {
|
|
23251
|
+
const textError = mapTextError(map);
|
|
23252
|
+
if (textError) return err({ kind: "save-failed", path, detail: textError });
|
|
22897
23253
|
return writeFileAtomic(path, serializeMap(map));
|
|
22898
23254
|
}
|
|
22899
23255
|
|
|
@@ -23143,9 +23499,359 @@ function summarize(map) {
|
|
|
23143
23499
|
return `map now: ${map.layers.length} layer(s), ${map.nodes.length} node(s)` + (statusPart ? ` [${statusPart}]` : "") + (map.groups.length > 0 ? `, ${map.groups.length} group(s)` : "") + (map.lanes.length > 0 ? `, ${map.lanes.length} lane(s)` : "") + `, ${map.edges.length} edge(s)` + (map.kind !== void 0 && map.kind !== "dev" ? ` (${map.kind})` : "");
|
|
23144
23500
|
}
|
|
23145
23501
|
|
|
23146
|
-
// src/
|
|
23147
|
-
|
|
23148
|
-
|
|
23502
|
+
// src/preview/publisher.ts
|
|
23503
|
+
import { createHash } from "node:crypto";
|
|
23504
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync3, realpathSync, rmdirSync } from "node:fs";
|
|
23505
|
+
import { dirname as dirname6, join as join5, resolve } from "node:path";
|
|
23506
|
+
|
|
23507
|
+
// src/preview/presentation.ts
|
|
23508
|
+
var LABELS = { planned: "\u5F85\u5F00\u53D1", "in-progress": "\u5F00\u53D1\u4E2D", done: "\u5DF2\u9A8C\u8BC1", regressed: "\u51FA\u73B0\u56DE\u5F52" };
|
|
23509
|
+
function isVerified(node) {
|
|
23510
|
+
return node.status === "done" && node.evidence !== void 0;
|
|
23511
|
+
}
|
|
23512
|
+
function statusText(node) {
|
|
23513
|
+
return node.status === "done" && !isVerified(node) ? `${unverifiedDoneGlyph(true)} \u5B8C\u6210\u4F46\u7F3A\u5C11\u8BC1\u636E` : `${statusGlyph(node.status, true)} ${LABELS[node.status]}`;
|
|
23514
|
+
}
|
|
23515
|
+
function documentName(page2) {
|
|
23516
|
+
return page2 === void 0 ? "map.md" : `page-${page2}.md`;
|
|
23517
|
+
}
|
|
23518
|
+
|
|
23519
|
+
// src/preview/text.ts
|
|
23520
|
+
function xml(text2) {
|
|
23521
|
+
return text2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ufffe\uffff]/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
23522
|
+
}
|
|
23523
|
+
function markdown(text2) {
|
|
23524
|
+
return text2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/([\\`*_[\]{}()#+.!|~-])/g, "\\$1").replace(/\r?\n/g, " \n");
|
|
23525
|
+
}
|
|
23526
|
+
function cell(text2) {
|
|
23527
|
+
return markdown(text2).replace(/ \n/g, "<br>");
|
|
23528
|
+
}
|
|
23529
|
+
|
|
23530
|
+
// src/preview/markdown.ts
|
|
23531
|
+
function renderMapMarkdown(map, image, pages) {
|
|
23532
|
+
const neutral = isNeutralKind(map);
|
|
23533
|
+
const rows = [
|
|
23534
|
+
`# ${markdown(map.title ?? "\u6885\u52D2\u65AF\u5730\u56FE")}`,
|
|
23535
|
+
"",
|
|
23536
|
+
"[\u6240\u6709\u5730\u56FE](index.md)",
|
|
23537
|
+
"",
|
|
23538
|
+
"> \u81EA\u52A8\u751F\u6210\u7684\u5730\u56FE\u9884\u89C8\uFF1B\u4FEE\u6539\u5730\u56FE\u6570\u636E\u540E\u91CD\u65B0\u751F\u6210\u3002",
|
|
23539
|
+
""
|
|
23540
|
+
];
|
|
23541
|
+
if (!neutral) {
|
|
23542
|
+
const active = map.nodes.filter((n) => n.status === "in-progress");
|
|
23543
|
+
rows.push(
|
|
23544
|
+
`**\u5F53\u524D\uFF1A** ${active.length ? active.map((n) => markdown(n.label)).join("\u3001") : "\u6682\u65E0\u8FDB\u884C\u4E2D\u7684\u6A21\u5757"}`,
|
|
23545
|
+
"",
|
|
23546
|
+
`\u5DF2\u9A8C\u8BC1 **${map.nodes.filter(isVerified).length} / ${map.nodes.length}**\u3000\uFF5C\u3000\u56DE\u5F52 **${map.nodes.filter((n) => n.status === "regressed").length}**`,
|
|
23547
|
+
""
|
|
23548
|
+
);
|
|
23549
|
+
}
|
|
23550
|
+
rows.push("## \u5206\u5C42\u4F9D\u8D56", "", ``, "");
|
|
23551
|
+
if (!neutral) rows.push("\xB7 \u5F85\u5F00\u53D1\u3000\u283F \u5F00\u53D1\u4E2D\u3000\u25A0 \u5DF2\u9A8C\u8BC1\u3000\u2717 \u51FA\u73B0\u56DE\u5F52\u3000\u25A1 \u5B8C\u6210\u4F46\u7F3A\u5C11\u8BC1\u636E", "");
|
|
23552
|
+
rows.push(map.kind === "sequence" ? "\u65F6\u95F4\u4ECE\u4E0A\u5411\u4E0B\u63A8\u8FDB\uFF1B\u7BAD\u5934\u4FDD\u7559\u5730\u56FE\u4E2D\u7684\u4F9D\u8D56\u65B9\u5411\u3002" : "\u7BAD\u5934\u7531\u4F7F\u7528\u65B9\u6307\u5411\u5B83\u4F9D\u8D56\u7684\u6A21\u5757\uFF1B\u57FA\u7840\u5C42\u4F4D\u4E8E\u4E0B\u65B9\u3002", "", "## \u6A21\u5757\u8BE6\u60C5", "");
|
|
23553
|
+
const nodes = new Map(map.nodes.map((n) => [n.id, n]));
|
|
23554
|
+
const layers = [...map.layers].sort((a, b) => map.kind === "sequence" ? a.rank - b.rank : b.rank - a.rank);
|
|
23555
|
+
for (const layer of layers) {
|
|
23556
|
+
rows.push(`### ${markdown(layer.name)}`, "");
|
|
23557
|
+
const members = map.nodes.filter((n) => n.layer === layer.id);
|
|
23558
|
+
if (!members.length) rows.push("\u5C1A\u672A\u58F0\u660E\u6A21\u5757\u3002", "");
|
|
23559
|
+
for (const node of members) {
|
|
23560
|
+
rows.push(`#### ${markdown(node.label)}${neutral ? "" : `\u3000${statusText(node)}`}`, "");
|
|
23561
|
+
if (node.detail !== void 0) rows.push(markdown(node.detail), "");
|
|
23562
|
+
const used = map.edges.filter((e) => e.from === node.id);
|
|
23563
|
+
rows.push(`**\u4F9D\u8D56\uFF1A** ${used.length ? used.map((e) => `${markdown(nodes.get(e.to).label)}${e.label !== void 0 ? `\uFF08${markdown(e.label)}\uFF09` : ""}`).join("\u3001") : "\u65E0"}`, "");
|
|
23564
|
+
const meta = [
|
|
23565
|
+
node.group === void 0 ? void 0 : map.groups.find((g) => g.id === node.group)?.label,
|
|
23566
|
+
node.lane === void 0 ? void 0 : map.lanes.find((l) => l.id === node.lane)?.label,
|
|
23567
|
+
node.kind
|
|
23568
|
+
].filter((v) => v !== void 0);
|
|
23569
|
+
if (meta.length) rows.push(`**\u5F52\u5C5E / \u7C7B\u578B\uFF1A** ${meta.map(markdown).join(" \xB7 ")}`, "");
|
|
23570
|
+
if (node.evidence !== void 0) rows.push(`**\u9A8C\u8BC1\u8BB0\u5F55\uFF1A** ${markdown(node.evidence)}`, "");
|
|
23571
|
+
if (node.submap !== void 0) rows.push(pages.some((p) => p.page === node.submap) ? `[\u6253\u5F00\u5B50\u56FE\uFF1A${markdown(node.submap)}](${documentName(node.submap)})` : `\u5B50\u56FE\u5C1A\u672A\u521B\u5EFA\uFF1A${markdown(node.submap)}`, "");
|
|
23572
|
+
}
|
|
23573
|
+
}
|
|
23574
|
+
if (!neutral) {
|
|
23575
|
+
rows.push("## \u9A8C\u8BC1\u8BB0\u5F55", "", "| \u6A21\u5757 | \u72B6\u6001 | \u6700\u8FD1\u8BC1\u636E |", "| --- | --- | --- |");
|
|
23576
|
+
for (const node of map.nodes) rows.push(`| ${cell(node.label)} | ${statusText(node)} | ${node.evidence === void 0 ? "\u5C1A\u672A\u8BB0\u5F55" : cell(node.evidence)} |`);
|
|
23577
|
+
rows.push("");
|
|
23578
|
+
}
|
|
23579
|
+
rows.push("---", "", "\u9759\u6001\u6587\u6863\uFF1A\u66F4\u65B0\u65F6\u91CD\u65B0\u751F\u6210\u5730\u56FE\u56FE\u7247\u4E0E\u6587\u5B57\u3002\u56FE\u4E2D\u8282\u70B9\u4E0D\u652F\u6301\u62D6\u62FD\u3001\u60AC\u505C\u5C55\u5F00\u6216\u52A8\u753B\u3002", "");
|
|
23580
|
+
return rows.join("\n");
|
|
23581
|
+
}
|
|
23582
|
+
function renderPreviewIndex(pages) {
|
|
23583
|
+
return [
|
|
23584
|
+
"# \u6885\u52D2\u65AF\u5730\u56FE \xB7 \u9875\u9762\u76EE\u5F55",
|
|
23585
|
+
"",
|
|
23586
|
+
...pages.map((p) => `- [${markdown(p.map.title ?? p.page ?? "\u9ED8\u8BA4\u5730\u56FE")}](${documentName(p.page)})`),
|
|
23587
|
+
"",
|
|
23588
|
+
"\u5730\u56FE\u9884\u89C8\u7531\u9879\u76EE\u5185\u7684\u5730\u56FE\u6570\u636E\u751F\u6210\u3002",
|
|
23589
|
+
""
|
|
23590
|
+
].join("\n");
|
|
23591
|
+
}
|
|
23592
|
+
|
|
23593
|
+
// src/preview/svg.ts
|
|
23594
|
+
var X = 8;
|
|
23595
|
+
var Y = 26;
|
|
23596
|
+
var TOP = 20;
|
|
23597
|
+
var PALETTES = {
|
|
23598
|
+
planned: ["#f5f7fa", "#98a3b2", "#566477"],
|
|
23599
|
+
"in-progress": ["#fff4d9", "#c48c24", "#805910"],
|
|
23600
|
+
done: ["#e9f5ee", "#67a883", "#286247"],
|
|
23601
|
+
regressed: ["#fdecec", "#cc7575", "#923d3d"],
|
|
23602
|
+
unverified: ["#fff6e8", "#b49a77", "#785e3e"],
|
|
23603
|
+
neutral: ["#f2f5f9", "#a1adbc", "#364558"]
|
|
23604
|
+
};
|
|
23605
|
+
function renderMapSvg(map) {
|
|
23606
|
+
const neutral = isNeutralKind(map);
|
|
23607
|
+
const originals = new Map(map.nodes.map((n) => [n.id, n]));
|
|
23608
|
+
const oriented = flipForSequence(map);
|
|
23609
|
+
const shaped = { ...oriented, nodes: oriented.nodes.map((n) => {
|
|
23610
|
+
const label = fitWidth(n.label, 32);
|
|
23611
|
+
return { ...n, label: label + " ".repeat(Math.max(0, 20 - displayWidth(label))) };
|
|
23612
|
+
}) };
|
|
23613
|
+
const geo = { ...zoomGeometry(0), boxGap: 6 };
|
|
23614
|
+
const columns = layoutColumns(shaped, geo, true, neutral);
|
|
23615
|
+
const routing = routeEdges(shaped, columns);
|
|
23616
|
+
const rows = layoutRows(columns, geo, routing.gapRowCount, false, map.lanes.length > 0);
|
|
23617
|
+
const width = Math.max(440, (columns.contentWidth + 4 + routing.fallbackCount * 2) * X);
|
|
23618
|
+
const height = Math.max(100, TOP + rows.legendY * Y);
|
|
23619
|
+
const parts = [
|
|
23620
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-labelledby="map-title map-desc">`,
|
|
23621
|
+
`<title id="map-title">${xml(map.title ?? "\u6885\u52D2\u65AF\u5730\u56FE")}</title>`,
|
|
23622
|
+
`<desc id="map-desc">${map.nodes.length} \u4E2A\u8282\u70B9\uFF0C${map.edges.length} \u6761\u4F9D\u8D56\u3002\u5B8C\u6574\u8BF4\u660E\u4E0E\u9A8C\u8BC1\u8BB0\u5F55\u5728\u5730\u56FE\u6587\u6863\u4E2D\u3002</desc>`,
|
|
23623
|
+
'<defs><marker id="arrow" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M0 0 L8 4 L0 8 Z" fill="#8995a5"/></marker></defs>',
|
|
23624
|
+
`<rect width="${width}" height="${height}" fill="#ffffff"/>`,
|
|
23625
|
+
'<g font-family="Segoe UI, Microsoft YaHei, Noto Sans CJK SC, sans-serif">'
|
|
23626
|
+
];
|
|
23627
|
+
if (map.nodes.length === 0) parts.push('<text x="20" y="52" font-size="14" fill="#657286">\u5C1A\u672A\u58F0\u660E\u6A21\u5757\uFF0C\u7B49\u5F85\u5730\u56FE\u66F4\u65B0\u3002</text>');
|
|
23628
|
+
columns.bands.forEach((band, i) => {
|
|
23629
|
+
const top = TOP + rows.barY[i] * Y;
|
|
23630
|
+
const boxes = rows.bandBoxes[i];
|
|
23631
|
+
const bottom = boxes.reduce((max, b) => Math.max(max, TOP + (b.y + b.h - 1) * Y), top + 60);
|
|
23632
|
+
parts.push(`<rect x="8" y="${top - 10}" width="${width - 16}" height="${bottom - top + 26}" rx="5" fill="#fafbfc"/>`);
|
|
23633
|
+
parts.push(`<text x="16" y="${top + 5}" font-size="12" fill="#6a7687">${xml(fitWidth(band.name, Math.floor((width - 40) / X)))}</text>`);
|
|
23634
|
+
});
|
|
23635
|
+
if (rows.laneHeaderY !== void 0) map.lanes.forEach((lane, i) => {
|
|
23636
|
+
const region = columns.lanes[i];
|
|
23637
|
+
parts.push(`<text x="${(region.x + region.w / 2) * X}" y="${TOP + rows.laneHeaderY * Y + 5}" text-anchor="middle" font-size="12" fill="#566477">${xml(fitWidth(lane.label, region.w))}</text>`);
|
|
23638
|
+
});
|
|
23639
|
+
for (const edge of routing.edges) {
|
|
23640
|
+
const points = edgePolyline(edge, rows).map(([x, y]) => `${x * X},${TOP + y * Y}`).join(" ");
|
|
23641
|
+
parts.push(`<polyline points="${points}" fill="none" stroke="#8995a5" stroke-width="1.4" marker-end="url(#arrow)"/>`);
|
|
23642
|
+
}
|
|
23643
|
+
for (const box of rows.boxOf.values()) {
|
|
23644
|
+
const node = originals.get(box.node.id);
|
|
23645
|
+
const palette = neutral ? PALETTES.neutral : node.status === "done" && !isVerified(node) ? PALETTES.unverified : PALETTES[node.status];
|
|
23646
|
+
const x = box.x * X, y = TOP + box.y * Y, w = (box.w - 1) * X, h = (box.h - 1) * Y;
|
|
23647
|
+
const dash = !neutral && node.status === "planned" ? ' stroke-dasharray="5 4"' : "";
|
|
23648
|
+
parts.push(`<g data-node="${xml(node.id)}"><title>${xml(node.label)}${neutral ? "" : ` \xB7 ${xml(statusText(node))}`}</title>`);
|
|
23649
|
+
parts.push(`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="5" fill="${palette[0]}" stroke="${palette[1]}" stroke-width="1.5"${dash}/>`);
|
|
23650
|
+
parts.push(`<text x="${x + w / 2}" y="${y + 21}" text-anchor="middle" font-size="14" fill="${palette[2]}">${xml(box.label.trim())}</text>`);
|
|
23651
|
+
const secondary = neutral ? node.kind ?? node.id : statusText(node);
|
|
23652
|
+
parts.push(`<text x="${x + w / 2}" y="${y + 40}" text-anchor="middle" font-size="11" fill="${palette[2]}">${xml(fitWidth(secondary, box.w - 4))}</text></g>`);
|
|
23653
|
+
}
|
|
23654
|
+
parts.push("</g></svg>");
|
|
23655
|
+
return parts.join("\n");
|
|
23656
|
+
}
|
|
23657
|
+
|
|
23658
|
+
// src/preview/publisher.ts
|
|
23659
|
+
var PREVIEW_DIR_NAME = "previews";
|
|
23660
|
+
var ENABLED = ".enabled";
|
|
23661
|
+
var PUBLISH_LOCK = ".publish-lock";
|
|
23662
|
+
function previewDirectory(defaultFile) {
|
|
23663
|
+
return join5(dirname6(defaultFile), PREVIEW_DIR_NAME);
|
|
23664
|
+
}
|
|
23665
|
+
function previewFile(defaultFile, page2) {
|
|
23666
|
+
if (page2 !== void 0 && !ID_RULE.test(page2)) throw new Error("Invalid preview page id.");
|
|
23667
|
+
return join5(previewDirectory(defaultFile), documentName(page2));
|
|
23668
|
+
}
|
|
23669
|
+
function save(path, contents) {
|
|
23670
|
+
try {
|
|
23671
|
+
if (readFileSync4(path, "utf8") === contents) return;
|
|
23672
|
+
} catch (error2) {
|
|
23673
|
+
if (error2.code !== "ENOENT") throw error2;
|
|
23674
|
+
}
|
|
23675
|
+
const result = writeFileAtomic(path, contents);
|
|
23676
|
+
if (!result.ok) throw new Error(describeStoreError(result.error));
|
|
23677
|
+
}
|
|
23678
|
+
function ownedDirectory(path) {
|
|
23679
|
+
mkdirSync3(path, { recursive: true });
|
|
23680
|
+
const expected = join5(realpathSync(dirname6(path)), path.slice(dirname6(path).length + 1));
|
|
23681
|
+
const actual = realpathSync(path);
|
|
23682
|
+
if (process.platform === "win32" ? actual.toLowerCase() !== expected.toLowerCase() : actual !== expected) {
|
|
23683
|
+
throw new Error(`Preview directory redirects outside its parent: ${path}`);
|
|
23684
|
+
}
|
|
23685
|
+
}
|
|
23686
|
+
function acquireLock(directory) {
|
|
23687
|
+
const path = join5(directory, PUBLISH_LOCK);
|
|
23688
|
+
const deadline = Date.now() + 2e3;
|
|
23689
|
+
while (true) {
|
|
23690
|
+
try {
|
|
23691
|
+
mkdirSync3(path);
|
|
23692
|
+
return () => rmdirSync(path);
|
|
23693
|
+
} catch (error2) {
|
|
23694
|
+
if (error2.code !== "EEXIST") throw error2;
|
|
23695
|
+
if (Date.now() >= deadline) throw new Error(`Preview export is busy or was interrupted. Retry; if no exporter is running, remove the stale lock directory: ${path}`);
|
|
23696
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
23697
|
+
}
|
|
23698
|
+
}
|
|
23699
|
+
}
|
|
23700
|
+
function createPreviewPublisher(defaultFile) {
|
|
23701
|
+
const directory = previewDirectory(defaultFile);
|
|
23702
|
+
const enabledFile = join5(directory, ENABLED);
|
|
23703
|
+
const enabled = () => existsSync3(enabledFile);
|
|
23704
|
+
const refresh = (page2) => {
|
|
23705
|
+
try {
|
|
23706
|
+
const path = previewFile(defaultFile, page2);
|
|
23707
|
+
ownedDirectory(directory);
|
|
23708
|
+
const release = acquireLock(directory);
|
|
23709
|
+
try {
|
|
23710
|
+
const pages = [];
|
|
23711
|
+
for (const source of listPageFiles(defaultFile)) {
|
|
23712
|
+
const slug = pageIdOfFile(defaultFile, source);
|
|
23713
|
+
if (slug !== void 0 && !ID_RULE.test(slug)) throw new Error(`Invalid map page filename: ${source}`);
|
|
23714
|
+
const loaded = loadMapFile(source);
|
|
23715
|
+
if (!loaded.ok) throw new Error(describeStoreError(loaded.error));
|
|
23716
|
+
pages.push({ page: slug, map: loaded.value });
|
|
23717
|
+
}
|
|
23718
|
+
if (page2 !== void 0 && !pages.some((p) => p.page === page2)) return err(`No map page named "${page2}".`);
|
|
23719
|
+
if (page2 === void 0 && !pages.some((p) => p.page === void 0)) pages.unshift({ page: void 0, map: EMPTY_MAP });
|
|
23720
|
+
const images = join5(directory, "images");
|
|
23721
|
+
ownedDirectory(images);
|
|
23722
|
+
const present = /* @__PURE__ */ new Set();
|
|
23723
|
+
for (const item of pages) {
|
|
23724
|
+
const svg = renderMapSvg(item.map);
|
|
23725
|
+
const digest = createHash("sha256").update(svg).digest("hex");
|
|
23726
|
+
const image = `images/${digest}.svg`;
|
|
23727
|
+
save(join5(images, `${digest}.svg`), svg);
|
|
23728
|
+
const filename = documentName(item.page);
|
|
23729
|
+
save(join5(directory, filename), renderMapMarkdown(item.map, image, pages));
|
|
23730
|
+
present.add(filename);
|
|
23731
|
+
}
|
|
23732
|
+
for (const filename of readdirSync3(directory)) {
|
|
23733
|
+
if (/^(map|page-[a-z0-9][a-z0-9-]{0,63})\.md$/.test(filename) && !present.has(filename)) {
|
|
23734
|
+
save(join5(directory, filename), "# \u5730\u56FE\u5DF2\u5220\u9664\n\n\u6B64\u9875\u9762\u5DF2\u4E0D\u5728\u9879\u76EE\u5730\u56FE\u4E2D\u3002\n\n[\u8FD4\u56DE\u5730\u56FE\u76EE\u5F55](index.md)\n");
|
|
23735
|
+
}
|
|
23736
|
+
}
|
|
23737
|
+
const index = join5(directory, "index.md");
|
|
23738
|
+
save(index, renderPreviewIndex(pages));
|
|
23739
|
+
return ok({ path: resolve(path), index: resolve(index), pages: pages.length });
|
|
23740
|
+
} finally {
|
|
23741
|
+
release();
|
|
23742
|
+
}
|
|
23743
|
+
} catch (error2) {
|
|
23744
|
+
return err(error2 instanceof Error ? error2.message : String(error2));
|
|
23745
|
+
}
|
|
23746
|
+
};
|
|
23747
|
+
const activate = (page2) => {
|
|
23748
|
+
const published = refresh(page2);
|
|
23749
|
+
if (!published.ok) return published;
|
|
23750
|
+
try {
|
|
23751
|
+
save(enabledFile, "Markdown preview updates are enabled for this project.\n");
|
|
23752
|
+
} catch (error2) {
|
|
23753
|
+
return err(error2 instanceof Error ? error2.message : String(error2));
|
|
23754
|
+
}
|
|
23755
|
+
return published;
|
|
23756
|
+
};
|
|
23757
|
+
return { enabled, refresh, activate };
|
|
23758
|
+
}
|
|
23759
|
+
|
|
23760
|
+
// src/web/launcher.ts
|
|
23761
|
+
import { spawn } from "node:child_process";
|
|
23762
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
23763
|
+
import { dirname as dirname8, join as join6 } from "node:path";
|
|
23764
|
+
|
|
23765
|
+
// src/web/source.ts
|
|
23766
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
23767
|
+
import { statSync as statSync2 } from "node:fs";
|
|
23768
|
+
import { basename as basename2, dirname as dirname7 } from "node:path";
|
|
23769
|
+
function readWebSnapshot(defaultFile) {
|
|
23770
|
+
const pages = listPageFiles(defaultFile).map((file) => {
|
|
23771
|
+
const id2 = pageIdOfFile(defaultFile, file) ?? "";
|
|
23772
|
+
const title = id2 || "\u9ED8\u8BA4\u5730\u56FE";
|
|
23773
|
+
try {
|
|
23774
|
+
if (id2 !== "" && !ID_RULE.test(id2)) throw new Error("Invalid page filename");
|
|
23775
|
+
const modified = statSync2(file).mtimeMs;
|
|
23776
|
+
const result = loadMapFile(file);
|
|
23777
|
+
return result.ok ? { id: id2, title: result.value.title ?? title, modified, map: result.value } : { id: id2, title, modified, error: describeStoreError(result.error) };
|
|
23778
|
+
} catch (error2) {
|
|
23779
|
+
return { id: id2, title, modified: 0, error: String(error2) };
|
|
23780
|
+
}
|
|
23781
|
+
});
|
|
23782
|
+
if (pages.length === 0) pages.push({ id: "", title: "\u7B49\u5F85\u7B2C\u4E00\u5F20\u5730\u56FE", modified: 0, map: EMPTY_MAP });
|
|
23783
|
+
const value = { project: basename2(dirname7(dirname7(defaultFile))), pages };
|
|
23784
|
+
return { revision: createHash2("sha256").update(JSON.stringify(value)).digest("hex"), value };
|
|
23785
|
+
}
|
|
23786
|
+
|
|
23787
|
+
// src/web/launcher.ts
|
|
23788
|
+
var webRuntimeFile = (defaultFile) => join6(dirname8(defaultFile), "web", "server.json");
|
|
23789
|
+
async function runningWebUrl(defaultFile) {
|
|
23790
|
+
try {
|
|
23791
|
+
const info = JSON.parse(readFileSync5(webRuntimeFile(defaultFile), "utf8"));
|
|
23792
|
+
if (!Number.isInteger(info.port) || info.port < 1 || info.port > 65535 || !/^[a-f0-9]{48}$/.test(info.token)) return void 0;
|
|
23793
|
+
const url = `http://127.0.0.1:${info.port}/${info.token}/`;
|
|
23794
|
+
const response = await fetch(`${url}api/health`, { signal: AbortSignal.timeout(700) });
|
|
23795
|
+
if (response.ok && (await response.json()).file === defaultFile) return url;
|
|
23796
|
+
} catch {
|
|
23797
|
+
}
|
|
23798
|
+
return void 0;
|
|
23799
|
+
}
|
|
23800
|
+
async function openWebPreview(defaultFile, entry, page2, terminal = false) {
|
|
23801
|
+
if (page2 !== void 0 && (!ID_RULE.test(page2) || !readWebSnapshot(defaultFile).value.pages.some((p) => p.id === page2))) throw new Error(`No map page named "${page2}".`);
|
|
23802
|
+
let url = await runningWebUrl(defaultFile);
|
|
23803
|
+
if (url && terminal) {
|
|
23804
|
+
const health = await fetch(`${url}api/health`, { signal: AbortSignal.timeout(2e3) });
|
|
23805
|
+
const info = await health.json();
|
|
23806
|
+
if (!info.surfaces?.includes("web-terminal")) {
|
|
23807
|
+
await fetch(`${url}api/stop`, { method: "POST", signal: AbortSignal.timeout(2e3) });
|
|
23808
|
+
const deadline = Date.now() + 3e3;
|
|
23809
|
+
while (await runningWebUrl(defaultFile)) {
|
|
23810
|
+
if (Date.now() > deadline) throw new Error("Old web viewer is still stopping. Retry opening the terminal.");
|
|
23811
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
23812
|
+
}
|
|
23813
|
+
url = void 0;
|
|
23814
|
+
}
|
|
23815
|
+
}
|
|
23816
|
+
if (!url) {
|
|
23817
|
+
if (!existsSync4(entry)) throw new Error(`Web runtime missing: ${entry}. Run npm run build or reinstall the plugin.`);
|
|
23818
|
+
const child = spawn(process.execPath, [entry, "--serve", defaultFile], { detached: true, windowsHide: true, stdio: "ignore" });
|
|
23819
|
+
let failure;
|
|
23820
|
+
child.on("error", (error2) => {
|
|
23821
|
+
failure = error2;
|
|
23822
|
+
});
|
|
23823
|
+
child.unref();
|
|
23824
|
+
const deadline = Date.now() + 8e3;
|
|
23825
|
+
while (!url && Date.now() < deadline) {
|
|
23826
|
+
if (failure) throw failure;
|
|
23827
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
23828
|
+
url = await runningWebUrl(defaultFile);
|
|
23829
|
+
}
|
|
23830
|
+
if (!url) throw new Error("Web preview did not start. Run the web CLI directly to inspect the error.");
|
|
23831
|
+
}
|
|
23832
|
+
const query = new URLSearchParams();
|
|
23833
|
+
if (terminal) query.set("view", "terminal");
|
|
23834
|
+
if (page2 !== void 0) query.set("page", page2);
|
|
23835
|
+
return query.size ? `${url}?${query}` : url;
|
|
23836
|
+
}
|
|
23837
|
+
|
|
23838
|
+
// src/server/terminal-handoff.ts
|
|
23839
|
+
function terminalHandoff(node, watcher, stateFile, page2) {
|
|
23840
|
+
const args = [watcher, "--file", stateFile, ...page2 ? ["--page", page2] : []];
|
|
23841
|
+
const tokens = [node, ...args];
|
|
23842
|
+
if (tokens.some((token) => /[\r\n\0]/.test(token))) throw new Error("Terminal paths must fit on one line.");
|
|
23843
|
+
const powershell = (token) => `'${token.replaceAll("'", "''")}'`;
|
|
23844
|
+
const posix = (token) => `'${token.replaceAll("'", `'"'"'`)}'`;
|
|
23845
|
+
return {
|
|
23846
|
+
command: node,
|
|
23847
|
+
args,
|
|
23848
|
+
powershell: `& ${tokens.map(powershell).join(" ")}`,
|
|
23849
|
+
posix: tokens.map(posix).join(" "),
|
|
23850
|
+
hostOpen: { placement: "right", target: { type: "terminal" } }
|
|
23851
|
+
};
|
|
23852
|
+
}
|
|
23853
|
+
|
|
23854
|
+
// src/server/tool-definitions.ts
|
|
23149
23855
|
var TITLE_MAX = 120;
|
|
23150
23856
|
var LABEL_MAX = 60;
|
|
23151
23857
|
var DETAIL_MAX = 600;
|
|
@@ -23181,10 +23887,6 @@ function edgeEnds() {
|
|
|
23181
23887
|
to: id("the node being used (must live on a strictly lower layer)")
|
|
23182
23888
|
};
|
|
23183
23889
|
}
|
|
23184
|
-
var NO_CONTROLS = /^[^\u0000-\u001f\u007f-\u009f]*$/;
|
|
23185
|
-
var NO_CONTROLS_TEXT = "one line of text; control characters (ESC, newline, tab) are not allowed";
|
|
23186
|
-
var NO_CONTROLS_BUT_BREAKS = /^[^\u0000-\u0008\u000b-\u001f\u007f-\u009f]*$/;
|
|
23187
|
-
var NO_CONTROLS_BUT_BREAKS_TEXT = "text with optional newlines (\\n) and tabs; other control characters (ESC, BEL, CR) are not allowed";
|
|
23188
23890
|
function line(max, description) {
|
|
23189
23891
|
return external_exports.string().min(1).max(max).regex(NO_CONTROLS, NO_CONTROLS_TEXT).describe(description);
|
|
23190
23892
|
}
|
|
@@ -23194,6 +23896,160 @@ function note(max, description) {
|
|
|
23194
23896
|
function closed(shape) {
|
|
23195
23897
|
return external_exports.object(shape).strict();
|
|
23196
23898
|
}
|
|
23899
|
+
function declareTool() {
|
|
23900
|
+
return {
|
|
23901
|
+
title: "Declare map structure",
|
|
23902
|
+
description: "Grow the Mellos map: set the title and diagram kind, add layer bands, lanes and groups (labeled subsystems within ONE band \u2014 declare them when a single band grows crowded, roughly five or more nodes in that band; a group must be a strict subset of its band, and a map spread thin across many bands needs none), add nodes, add dependency edges. Declare the whole ghost design up front, then grow it as understanding deepens. Edges must point strictly downward (a node may only use nodes on lower layers); the batch is all-or-nothing. The title lives here and only here: pass it again to replace it, or null to remove it. Revising what already exists (moving, renaming, relabeling, clearing) is mmap_update.",
|
|
23903
|
+
inputSchema: closed({
|
|
23904
|
+
page: page(),
|
|
23905
|
+
title: line(TITLE_MAX, "map title, e.g. the feature being built; null removes it").nullable().optional(),
|
|
23906
|
+
kind: mapKind().optional(),
|
|
23907
|
+
lanes: external_exports.array(
|
|
23908
|
+
closed({
|
|
23909
|
+
id: id("stable kebab-case identifier of the lane"),
|
|
23910
|
+
label: line(LABEL_MAX, "column name, e.g. a sequence participant")
|
|
23911
|
+
})
|
|
23912
|
+
).optional().describe("vertical columns crossing all bands; declaration order = left-to-right"),
|
|
23913
|
+
layers: external_exports.array(
|
|
23914
|
+
closed({
|
|
23915
|
+
id: id("stable kebab-case identifier of the band"),
|
|
23916
|
+
name: line(LABEL_MAX, "display name of the band"),
|
|
23917
|
+
rank: rank()
|
|
23918
|
+
})
|
|
23919
|
+
).optional(),
|
|
23920
|
+
groups: external_exports.array(
|
|
23921
|
+
closed({
|
|
23922
|
+
id: id("stable kebab-case identifier of the group"),
|
|
23923
|
+
label: line(LABEL_MAX, "subsystem name shown at the far zoom"),
|
|
23924
|
+
layer: id("band this group clusters; members must live on the same band")
|
|
23925
|
+
})
|
|
23926
|
+
).optional(),
|
|
23927
|
+
nodes: external_exports.array(
|
|
23928
|
+
closed({
|
|
23929
|
+
id: id("stable kebab-case identifier of the node"),
|
|
23930
|
+
label: line(LABEL_MAX, "display label inside the box"),
|
|
23931
|
+
layer: id("id of the band this node lives in"),
|
|
23932
|
+
status: status("defaults to planned").optional(),
|
|
23933
|
+
evidence: line(
|
|
23934
|
+
EVIDENCE_MAX,
|
|
23935
|
+
"how an already-verified node was verified; for regressed: what broke. Declaring a node straight to done needs it as much as updating one does."
|
|
23936
|
+
).optional(),
|
|
23937
|
+
detail: note(
|
|
23938
|
+
DETAIL_MAX,
|
|
23939
|
+
"design notes shown in the pane detail panel: responsibility, contract, key decisions"
|
|
23940
|
+
).optional(),
|
|
23941
|
+
group: id("same-band group this node belongs to").optional(),
|
|
23942
|
+
kind: nodeKind().optional(),
|
|
23943
|
+
lane: id("lane (column) this node belongs to").optional(),
|
|
23944
|
+
submap: id(
|
|
23945
|
+
"page slug of this node's child map \u2014 the pane badges the node \u229E and double-click dives in. Declare the child page separately. Create a sub-map only when the node's internals genuinely deserve their own picture; most nodes need none."
|
|
23946
|
+
).optional()
|
|
23947
|
+
})
|
|
23948
|
+
).optional(),
|
|
23949
|
+
edges: external_exports.array(
|
|
23950
|
+
closed({
|
|
23951
|
+
...edgeEnds(),
|
|
23952
|
+
label: line(EDGE_LABEL_MAX, "what flows along the edge").optional()
|
|
23953
|
+
})
|
|
23954
|
+
).optional()
|
|
23955
|
+
})
|
|
23956
|
+
};
|
|
23957
|
+
}
|
|
23958
|
+
function updateTool() {
|
|
23959
|
+
return {
|
|
23960
|
+
title: "Record progress and revise the map",
|
|
23961
|
+
description: "The revision tool, all-or-nothing. Record progress on nodes: in-progress when starting a node (the pane spins), done with evidence when its verification passes, regressed with evidence when a done node breaks. Revise what the ghost design got wrong: move a node to another band, join or leave a group or lane, rename a band (or re-rank it, which reorders the whole map), relabel a group or a lane. Every clearable field takes null to empty it \u2014 that is how a field is cleared, never an empty string. Bands, groups and lanes are applied before the node updates, and within one node update `layer` moves the node before its other fields. The map is a ledger: report honestly, it never blocks you.",
|
|
23962
|
+
inputSchema: closed({
|
|
23963
|
+
page: page(),
|
|
23964
|
+
updates: external_exports.array(
|
|
23965
|
+
closed({
|
|
23966
|
+
id: id("id of the node to update"),
|
|
23967
|
+
status: status("the status to record").optional(),
|
|
23968
|
+
label: line(LABEL_MAX, "new display label inside the box").optional(),
|
|
23969
|
+
evidence: line(EVIDENCE_MAX, "for done: how it was verified; for regressed: what broke; null clears it").nullable().optional(),
|
|
23970
|
+
detail: note(
|
|
23971
|
+
DETAIL_MAX,
|
|
23972
|
+
"design notes shown in the pane detail panel: responsibility, contract, key decisions; null clears them"
|
|
23973
|
+
).nullable().optional(),
|
|
23974
|
+
layer: id(
|
|
23975
|
+
"move the node to this band; applied before this item's other fields, so a node can move and join a group on the new band in one item. Every edge touching it must still point strictly downward, and a grouped node may only move to its group's band."
|
|
23976
|
+
).optional(),
|
|
23977
|
+
group: id("join this same-band group; null leaves the current group").nullable().optional(),
|
|
23978
|
+
kind: nodeKind().nullable().optional(),
|
|
23979
|
+
lane: id("join this lane; null leaves the current lane").nullable().optional(),
|
|
23980
|
+
submap: id("link a child map page by slug; null unlinks it").nullable().optional()
|
|
23981
|
+
})
|
|
23982
|
+
).min(1).optional(),
|
|
23983
|
+
layers: external_exports.array(
|
|
23984
|
+
closed({
|
|
23985
|
+
id: id("id of the band to revise"),
|
|
23986
|
+
name: line(LABEL_MAX, "new display name of the band").optional(),
|
|
23987
|
+
rank: rank().optional()
|
|
23988
|
+
})
|
|
23989
|
+
).min(1).optional().describe("rename and/or re-rank existing bands; an item must carry a name, a rank, or both"),
|
|
23990
|
+
groups: external_exports.array(closed({ id: id("id of the group to relabel"), label: line(LABEL_MAX, "new subsystem name") })).min(1).optional().describe("relabel existing groups; membership and band are untouched"),
|
|
23991
|
+
lanes: external_exports.array(closed({ id: id("id of the lane to relabel"), label: line(LABEL_MAX, "new column name") })).min(1).optional().describe("relabel existing lanes; order and membership are untouched")
|
|
23992
|
+
})
|
|
23993
|
+
};
|
|
23994
|
+
}
|
|
23995
|
+
function removeTool() {
|
|
23996
|
+
return {
|
|
23997
|
+
title: "Revise the map",
|
|
23998
|
+
description: 'Remove edges, nodes, groups and empty layer bands (in that order, all-or-nothing). Removing a node also removes every edge touching it; removing a group merely ungroups its members. Use when the ghost design turns out wrong \u2014 the map is a hypothesis, revising it is honest work. `pages` is the other scale: it DELETES whole page files, so a finished effort can be cleaned up instead of accumulating tabs forever. A bare `{pages: ["slug"]}` with no other field is the normal form; combined with map edits, the edits are applied first and the pages are deleted after. The deletion is permanent and cannot be undone, so delete only pages whose effort is over \u2014 and only ever with the user behind it. An unknown slug is refused with the project\'s real page list (naming a page that does not exist is a typo, not a request). The default page has no slug and is not deletable here. A node elsewhere still pointing at a deleted page with `submap` stays legal \u2014 a submap reference has no existence invariant \u2014 but it has nowhere to dive until the page comes back.',
|
|
23999
|
+
inputSchema: closed({
|
|
24000
|
+
page: page(),
|
|
24001
|
+
edges: external_exports.array(closed(edgeEnds())).optional(),
|
|
24002
|
+
nodes: external_exports.array(id("id of the node to remove, with every edge touching it")).optional(),
|
|
24003
|
+
groups: external_exports.array(id("id of the group to remove; members stay, merely ungrouped")).optional(),
|
|
24004
|
+
lanes: external_exports.array(id("id of the lane to remove; members stay, merely off-lane")).optional(),
|
|
24005
|
+
layers: external_exports.array(id("id of the band to remove; it must hold no nodes and no groups")).optional(),
|
|
24006
|
+
pages: external_exports.array(
|
|
24007
|
+
id(
|
|
24008
|
+
"slug of a page whose WHOLE map file is deleted \u2014 the page and everything drawn on it. Not the page this same call targets with `page`."
|
|
24009
|
+
)
|
|
24010
|
+
).optional().describe("pages to delete entirely, after this call's map edits; permanent")
|
|
24011
|
+
})
|
|
24012
|
+
};
|
|
24013
|
+
}
|
|
24014
|
+
function setupTool() {
|
|
24015
|
+
return {
|
|
24016
|
+
title: "Configure when maps open",
|
|
24017
|
+
description: 'Get or set the mapping policy \u2014 WHEN the assistant opens a Mellos map. Call with no arguments to read it: the reply names the policy chosen for the USER (every project), the one this PROJECT overrides it with if any, and which of them is in effect. If it reports "not set", ask the USER to choose (never pick for them): always = ' + describeMappingPolicy("always") + "; complex = " + describeMappingPolicy("complex") + "; on-request = " + describeMappingPolicy("on-request") + '. Then call again with their choice to persist it. It defaults to user scope, which is the normal one \u2014 the question is about how someone works, so it is asked once ever, not once per repository. Pass scope: "project" only when the user wants THIS project to differ from that habit. The policy guides you; it never blocks the tools, and an explicit user request for a map always wins.',
|
|
24018
|
+
inputSchema: closed({
|
|
24019
|
+
policy: external_exports.enum(MAPPING_POLICIES).optional().describe("the user's choice to persist; omit to read the current policy"),
|
|
24020
|
+
scope: external_exports.enum(POLICY_SCOPES).optional().describe(
|
|
24021
|
+
'where to record the choice: "user" (default) applies to every project this user opens; "project" overrides that for this project alone. Ignored when reading.'
|
|
24022
|
+
)
|
|
24023
|
+
})
|
|
24024
|
+
};
|
|
24025
|
+
}
|
|
24026
|
+
function viewTool() {
|
|
24027
|
+
return {
|
|
24028
|
+
title: "View the current map",
|
|
24029
|
+
description: "Render the current Mellos map as monochrome text \u2014 the same picture the split-pane watcher shows live. Use it to check the map state or to show it inline in conversation. Every response ends with a `pages:` line naming the pages this project actually has and which one you are looking at, so this is also how you discover whether a map exists at all and under which slugs \u2014 never probe the files.",
|
|
24030
|
+
inputSchema: closed({
|
|
24031
|
+
page: page(),
|
|
24032
|
+
zoom: external_exports.number().int().min(ZOOM_MIN).max(ZOOM_MAX).optional().describe("zoom ladder: 1 = detail (notes unfold), 0 = standard (default), -1..-3 = scaled down, -4 = overview glyphs")
|
|
24033
|
+
})
|
|
24034
|
+
};
|
|
24035
|
+
}
|
|
24036
|
+
function openTool() {
|
|
24037
|
+
return {
|
|
24038
|
+
title: "Open the map pane",
|
|
24039
|
+
description: 'For automatic display beside the current ChatGPT desktop conversation in Codex mode, use surface: "web-terminal", then call open_in_codex with the returned browser hostOpen object. No paste or Computer Use is needed. For the native host terminal, use surface: "codex-terminal": prepare absolute watcher commands for the current project, then ask the host to open its right terminal. This does not launch the watcher or type into that terminal. Agent exec PTYs cannot be attached using their numeric session ids. For a document panel, use surface: "markdown": generate MD + SVG files, enable automatic preview updates after successful map writes, then use the HOST file-opening tool to display the returned absolute Markdown path on the right of the current conversation. Generated does not mean visible: this server cannot open or observe the desktop side panel. For interactive maps, choose surface: "web": start or reuse a project-local web viewer and pass the returned URL to the host browser-opening tool. Markdown and terminal remain available. The default surface is "terminal", preserving the terminal workflow. Put the live map on the user\'s screen: a terminal pane beside this conversation that redraws on every write. Call it whenever a result says `pane: CLOSED` \u2014 and do NOT ask permission first, because a user who has set a mapping policy has already said they want to see the map. With a pane already open this RETARGETS it to `page` instead of opening a second one, so it is also how you show the user a particular page when they ask for one. It never closes a pane: taking the map off the screen belongs to the user (the `q` key in the pane, or typing `mmap` in a terminal). The reply says whether a pane actually reported itself in afterwards, not merely that a command was run. Automatic terminal opening supports Windows Terminal and tmux on Linux/macOS. If opening fails, relay the reason and copyable command; retry only after the environment changes or the user asks.',
|
|
24040
|
+
inputSchema: closed({
|
|
24041
|
+
surface: external_exports.enum(["terminal", "codex-terminal", "markdown", "web", "web-terminal"]).optional().describe("web-terminal = automatically started mmap terminal in a local browser page; codex-terminal = prepare a command for the desktop host terminal; web = local browser viewer; markdown = MD/SVG; terminal = Windows Terminal or tmux launcher (default)"),
|
|
24042
|
+
page: id(
|
|
24043
|
+
"page to show first \u2014 the page THIS effort lives on, the same slug you pass to the other tools. Omit only for the default page: without it a fresh pane opens on whichever page was written last, which after a gap is rarely the one under discussion."
|
|
24044
|
+
).optional(),
|
|
24045
|
+
window: external_exports.boolean().optional().describe(
|
|
24046
|
+
`open the map in its own "mellos-mapping" window (a new tmux window on Linux/macOS) instead of splitting this conversation's window. Pass it only when the user asked for the map separate (a second monitor, a small screen); the split is the default because the map is meant to sit beside what it describes.`
|
|
24047
|
+
)
|
|
24048
|
+
})
|
|
24049
|
+
};
|
|
24050
|
+
}
|
|
24051
|
+
|
|
24052
|
+
// src/server/presence.ts
|
|
23197
24053
|
var DEFAULT_PAGE_NAME = "(default)";
|
|
23198
24054
|
var DEFAULT_PAGE_ABSENT = "(default: absent)";
|
|
23199
24055
|
function pagesLine(stateFile, shown) {
|
|
@@ -23206,26 +24062,49 @@ function pagesLine(stateFile, shown) {
|
|
|
23206
24062
|
function pageName(page2) {
|
|
23207
24063
|
return page2 ?? DEFAULT_PAGE_NAME;
|
|
23208
24064
|
}
|
|
23209
|
-
function paneLine(stateFile, touched) {
|
|
24065
|
+
function paneLine(stateFile, touched, openFailure) {
|
|
23210
24066
|
const viewers = readLiveViewers(stateFile, Date.now());
|
|
23211
24067
|
if (viewers.length === 0) {
|
|
24068
|
+
if (openFailure !== void 0) return `pane: CLOSED \u2014 automatic opening previously failed. Do not retry mmap_open until the terminal environment changes or the user asks to retry. The map is saved; mmap_view remains available inline. Last failure: ${openFailure}`;
|
|
23212
24069
|
return `pane: CLOSED \u2014 nobody is seeing this map. Open it with mmap_open {page: ${touched === void 0 ? "(omit for the default page)" : `"${touched}"`}} and do not ask first: a user with a mapping policy has already said they want the picture.`;
|
|
23213
24070
|
}
|
|
23214
|
-
if (viewers.some((v) => v.page === touched)) return "pane:
|
|
24071
|
+
if (viewers.some((v) => v.page === touched)) return "pane: running on this page \u2014 a heartbeat confirms the process, not terminal visibility.";
|
|
23215
24072
|
const elsewhere = [...new Set(viewers.map((v) => pageName(v.page)))].join(", ");
|
|
23216
24073
|
if (viewers.some((v) => v.follow)) {
|
|
23217
24074
|
return `pane: open on ${elsewhere}, auto-follow on \u2014 it lands on this page within a second.`;
|
|
23218
24075
|
}
|
|
23219
24076
|
return `pane: open on ${elsewhere}, auto-follow OFF \u2014 the user pinned that page, so this change is NOT on their screen. Tell them rather than switching it behind them; mmap_open {page} retargets the pane if they want it moved.`;
|
|
23220
24077
|
}
|
|
23221
|
-
|
|
24078
|
+
|
|
24079
|
+
// src/server/map-service.ts
|
|
24080
|
+
function loadOrEmpty(file) {
|
|
24081
|
+
const loaded = loadMapFile(file);
|
|
24082
|
+
if (loaded.ok) return loaded;
|
|
24083
|
+
return loaded.error.kind === "not-found" ? { ok: true, value: EMPTY_MAP } : { ok: false, error: describeStoreError(loaded.error) };
|
|
24084
|
+
}
|
|
24085
|
+
function mutateMap(file, apply) {
|
|
24086
|
+
const current = loadOrEmpty(file);
|
|
24087
|
+
if (!current.ok) return { ok: false, error: { kind: "load", detail: current.error } };
|
|
24088
|
+
const applied = apply(current.value);
|
|
24089
|
+
if (!applied.ok) return { ok: false, error: { kind: "refused", detail: applied.error } };
|
|
24090
|
+
const saved = saveMapFile(file, applied.value);
|
|
24091
|
+
return saved.ok ? applied : { ok: false, error: { kind: "save", error: saved.error } };
|
|
24092
|
+
}
|
|
24093
|
+
|
|
24094
|
+
// src/server/pane-launcher.ts
|
|
24095
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
24096
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
24097
|
+
import { dirname as dirname9, join as join7 } from "node:path";
|
|
24098
|
+
import { fileURLToPath } from "node:url";
|
|
24099
|
+
var pageName2 = (page2) => page2 ?? "(default)";
|
|
24100
|
+
var LAUNCH_TIMEOUT_MS = 6e4;
|
|
23222
24101
|
var PANE_REPORT_TIMEOUT_MS = 8e3;
|
|
23223
24102
|
var PANE_REPORT_POLL_MS = 250;
|
|
23224
24103
|
function launcherPath(moduleUrl) {
|
|
23225
|
-
return
|
|
24104
|
+
return join7(dirname9(dirname9(fileURLToPath(moduleUrl))), "scripts", "open-pane.mjs");
|
|
23226
24105
|
}
|
|
23227
24106
|
function projectDirOf(stateFile) {
|
|
23228
|
-
return
|
|
24107
|
+
return dirname9(dirname9(stateFile));
|
|
23229
24108
|
}
|
|
23230
24109
|
function launcherArgs(projectDir, page2, window) {
|
|
23231
24110
|
const args = [projectDir];
|
|
@@ -23234,8 +24113,8 @@ function launcherArgs(projectDir, page2, window) {
|
|
|
23234
24113
|
return args;
|
|
23235
24114
|
}
|
|
23236
24115
|
function runLauncher(script, args) {
|
|
23237
|
-
return new Promise((
|
|
23238
|
-
const child =
|
|
24116
|
+
return new Promise((resolve2) => {
|
|
24117
|
+
const child = spawn2(process.execPath, [script, ...args], {
|
|
23239
24118
|
windowsHide: true,
|
|
23240
24119
|
stdio: ["ignore", "pipe", "pipe"]
|
|
23241
24120
|
});
|
|
@@ -23248,70 +24127,94 @@ function runLauncher(script, args) {
|
|
|
23248
24127
|
const abandon = setTimeout(() => child.kill(), LAUNCH_TIMEOUT_MS);
|
|
23249
24128
|
child.on("error", (e) => {
|
|
23250
24129
|
clearTimeout(abandon);
|
|
23251
|
-
|
|
24130
|
+
resolve2({ ok: false, output: e.message });
|
|
23252
24131
|
});
|
|
23253
24132
|
child.on("close", (code) => {
|
|
23254
24133
|
clearTimeout(abandon);
|
|
23255
|
-
|
|
24134
|
+
resolve2({ ok: code === 0, output: output.trim() });
|
|
23256
24135
|
});
|
|
23257
24136
|
});
|
|
23258
24137
|
}
|
|
24138
|
+
function launchPane(args) {
|
|
24139
|
+
const script = launcherPath(import.meta.url);
|
|
24140
|
+
if (!existsSync5(script)) return Promise.resolve({
|
|
24141
|
+
ok: false,
|
|
24142
|
+
output: `the launcher is missing at ${script}. This install is incomplete \u2014 reinstall the plugin (a source checkout needs "npm run build").`
|
|
24143
|
+
});
|
|
24144
|
+
return runLauncher(script, args);
|
|
24145
|
+
}
|
|
23259
24146
|
function paneShows(viewers, page2) {
|
|
23260
24147
|
return page2 === void 0 ? viewers.length > 0 : viewers.some((v) => v.page === page2);
|
|
23261
24148
|
}
|
|
23262
|
-
async function awaitPane(stateFile, page2, deadlineMs) {
|
|
24149
|
+
async function awaitPane(stateFile, page2, deadlineMs, pid) {
|
|
23263
24150
|
for (; ; ) {
|
|
23264
|
-
const viewers = readLiveViewers(stateFile, Date.now());
|
|
24151
|
+
const viewers = readLiveViewers(stateFile, Date.now()).filter((viewer) => pid === void 0 || viewer.pid === pid);
|
|
23265
24152
|
if (paneShows(viewers, page2)) return viewers;
|
|
23266
24153
|
if (Date.now() >= deadlineMs) return viewers;
|
|
23267
24154
|
await new Promise((r) => setTimeout(r, PANE_REPORT_POLL_MS));
|
|
23268
24155
|
}
|
|
23269
24156
|
}
|
|
23270
24157
|
function openOutcome(run, viewers, page2) {
|
|
24158
|
+
const pid = launcherViewerPid(run);
|
|
24159
|
+
viewers = viewers.filter((viewer) => pid === void 0 || viewer.pid === pid);
|
|
23271
24160
|
if (!run.ok) {
|
|
23272
24161
|
return `could not open the pane: ${run.output === "" ? "the launcher failed without saying why" : run.output}
|
|
23273
|
-
Relay
|
|
24162
|
+
Relay the launcher reason and any copyable fallback command. Do not retry mmap_open until the terminal environment changes or the user asks to retry. A failed default split is not permission to open a separate window.`;
|
|
23274
24163
|
}
|
|
23275
24164
|
if (paneShows(viewers, page2)) {
|
|
23276
|
-
|
|
24165
|
+
const visible = /^MMAP_PANE [^\r\n]*\bvisibility=visible(?:\s|$)/m.test(run.output);
|
|
24166
|
+
return `pane: running and reporting ${pageName2(page2)} \u2014 ${visible ? "the launcher verified its tmux window is active and its pane is visible in the attached session." : "terminal visibility is not confirmed by the process heartbeat."}
|
|
23277
24167
|
${run.output}`;
|
|
23278
24168
|
}
|
|
23279
24169
|
if (viewers.length > 0) {
|
|
23280
|
-
const elsewhere = [...new Set(viewers.map((v) =>
|
|
23281
|
-
return `pane: open, but it reports ${elsewhere} rather than ${
|
|
24170
|
+
const elsewhere = [...new Set(viewers.map((v) => pageName2(v.page)))].join(", ");
|
|
24171
|
+
return `pane: open, but it reports ${elsewhere} rather than ${pageName2(page2)}. With auto-follow on it lands there on your next write; with follow off the user is holding that page on purpose.
|
|
23282
24172
|
` + run.output;
|
|
23283
24173
|
}
|
|
23284
24174
|
return `the launcher succeeded but no pane has reported in within ${PANE_REPORT_TIMEOUT_MS / 1e3}s. It may still be starting; the \`pane:\` line on your next write says whether it made it.
|
|
23285
24175
|
` + run.output;
|
|
23286
24176
|
}
|
|
24177
|
+
function launcherViewerPid(run) {
|
|
24178
|
+
const raw = /^MMAP_PANE [^\r\n]*\bpid=([1-9]\d*)(?:\s|$)/m.exec(run.output)?.[1];
|
|
24179
|
+
return raw === void 0 ? void 0 : Number(raw);
|
|
24180
|
+
}
|
|
24181
|
+
|
|
24182
|
+
// src/server/server.ts
|
|
24183
|
+
var SERVER_NAME = "mellos-mapping";
|
|
24184
|
+
var SERVER_VERSION = "0.22.1";
|
|
23287
24185
|
function text(s, isError = false) {
|
|
23288
24186
|
return { content: [{ type: "text", text: s }], ...isError ? { isError: true } : {} };
|
|
23289
24187
|
}
|
|
23290
24188
|
function saveFailed(error2) {
|
|
23291
24189
|
return text(`save failed, nothing changed (retry): ${describeStoreError(error2)}`, true);
|
|
23292
24190
|
}
|
|
23293
|
-
function
|
|
23294
|
-
const loaded = loadMapFile(stateFile);
|
|
23295
|
-
if (loaded.ok) return { ok: true, value: loaded.value };
|
|
23296
|
-
if (loaded.error.kind === "not-found") return { ok: true, value: EMPTY_MAP };
|
|
23297
|
-
return { ok: false, error: describeStoreError(loaded.error) };
|
|
23298
|
-
}
|
|
23299
|
-
function buildServer(stateFile, userConfigFile) {
|
|
24191
|
+
function buildServer(stateFile, userConfigFile, launch = launchPane) {
|
|
23300
24192
|
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
|
24193
|
+
let paneOpenFailure;
|
|
24194
|
+
const currentPaneLine = (page2) => paneLine(stateFile, page2, paneOpenFailure);
|
|
23301
24195
|
const projectConfigFile = configFilePath(stateFile);
|
|
24196
|
+
const previews = createPreviewPublisher(stateFile);
|
|
24197
|
+
const refreshPreview = (page2) => {
|
|
24198
|
+
if (!previews.enabled()) return "";
|
|
24199
|
+
const published = previews.refresh(page2);
|
|
24200
|
+
return published.ok ? `
|
|
24201
|
+
preview: updated
|
|
24202
|
+
markdown: ${published.value.path}
|
|
24203
|
+
File generated; desktop visibility is not tracked.` : `
|
|
24204
|
+
preview: STALE \u2014 map changes were saved, but preview generation failed: ${published.error}. Retry mmap_open {surface: "markdown"}; do not repeat the map mutation.`;
|
|
24205
|
+
};
|
|
23302
24206
|
const fileOf = (page2) => pageFilePath(stateFile, page2);
|
|
23303
24207
|
const mutate = (page2, apply) => {
|
|
23304
|
-
const
|
|
23305
|
-
|
|
23306
|
-
|
|
23307
|
-
|
|
23308
|
-
|
|
23309
|
-
|
|
23310
|
-
|
|
23311
|
-
return text(summarize(applied.value) + (page2 !== void 0 ? ` [page: ${page2}]` : ""));
|
|
24208
|
+
const result = mutateMap(fileOf(page2), apply);
|
|
24209
|
+
if (!result.ok) {
|
|
24210
|
+
const failure = result.error;
|
|
24211
|
+
if (failure.kind === "save") return saveFailed(failure.error);
|
|
24212
|
+
return text(failure.kind === "refused" ? `refused (nothing changed): ${failure.detail}` : failure.detail, true);
|
|
24213
|
+
}
|
|
24214
|
+
return text(summarize(result.value) + (page2 !== void 0 ? ` [page: ${page2}]` : "") + refreshPreview(page2));
|
|
23312
24215
|
};
|
|
23313
|
-
const withPane = (result, page2) => result.isError === true ? result : text(`${result.content[0]?.text ?? ""}
|
|
23314
|
-
${
|
|
24216
|
+
const withPane = (result, page2) => result.isError === true || previews.enabled() ? result : text(`${result.content[0]?.text ?? ""}
|
|
24217
|
+
${existsSync6(webRuntimeFile(stateFile)) ? 'web: configured \u2014 the browser reads project map updates. Use mmap_open {surface: "web", page} to open or reconnect; desktop visibility is not tracked.' : currentPaneLine(page2)}`);
|
|
23315
24218
|
const knownPages = () => listPageFiles(stateFile).map((f) => pageIdOfFile(stateFile, f)).filter((p) => p !== void 0);
|
|
23316
24219
|
const refusePageDeletion = (pages, target) => {
|
|
23317
24220
|
if (target !== void 0 && pages.includes(target)) {
|
|
@@ -23338,7 +24241,7 @@ ${paneLine(stateFile, page2)}`);
|
|
|
23338
24241
|
if (removed.ok) deleted.push(p);
|
|
23339
24242
|
else failed.push(`${p} (${describeStoreError(removed.error)})`);
|
|
23340
24243
|
}
|
|
23341
|
-
const gone = `deleted page(s): ${deleted.length > 0 ? deleted.join(", ") : "(none)"}
|
|
24244
|
+
const gone = `deleted page(s): ${deleted.length > 0 ? deleted.join(", ") : "(none)"}` + (deleted.length ? refreshPreview(void 0) : "");
|
|
23342
24245
|
if (failed.length === 0) return text(`${summary}${gone}`);
|
|
23343
24246
|
return text(
|
|
23344
24247
|
`${summary}${gone}; could NOT delete: ${failed.join("; ")}. Deleting files is not a transaction: what is named deleted above is gone for good, and only the failures are worth retrying.`,
|
|
@@ -23354,63 +24257,7 @@ note: ${describeStoreError(scopes.error)} \u2014 fix it or rerun setup (mmap_set
|
|
|
23354
24257
|
};
|
|
23355
24258
|
server.registerTool(
|
|
23356
24259
|
"mmap_declare",
|
|
23357
|
-
|
|
23358
|
-
title: "Declare map structure",
|
|
23359
|
-
description: "Grow the Mellos map: set the title and diagram kind, add layer bands, lanes and groups (labeled subsystems within ONE band \u2014 declare them when a single band grows crowded, roughly five or more nodes in that band; a group must be a strict subset of its band, and a map spread thin across many bands needs none), add nodes, add dependency edges. Declare the whole ghost design up front, then grow it as understanding deepens. Edges must point strictly downward (a node may only use nodes on lower layers); the batch is all-or-nothing. The title lives here and only here: pass it again to replace it, or null to remove it. Revising what already exists (moving, renaming, relabeling, clearing) is mmap_update.",
|
|
23360
|
-
inputSchema: closed({
|
|
23361
|
-
page: page(),
|
|
23362
|
-
title: line(TITLE_MAX, "map title, e.g. the feature being built; null removes it").nullable().optional(),
|
|
23363
|
-
kind: mapKind().optional(),
|
|
23364
|
-
lanes: external_exports.array(
|
|
23365
|
-
closed({
|
|
23366
|
-
id: id("stable kebab-case identifier of the lane"),
|
|
23367
|
-
label: line(LABEL_MAX, "column name, e.g. a sequence participant")
|
|
23368
|
-
})
|
|
23369
|
-
).optional().describe("vertical columns crossing all bands; declaration order = left-to-right"),
|
|
23370
|
-
layers: external_exports.array(
|
|
23371
|
-
closed({
|
|
23372
|
-
id: id("stable kebab-case identifier of the band"),
|
|
23373
|
-
name: line(LABEL_MAX, "display name of the band"),
|
|
23374
|
-
rank: rank()
|
|
23375
|
-
})
|
|
23376
|
-
).optional(),
|
|
23377
|
-
groups: external_exports.array(
|
|
23378
|
-
closed({
|
|
23379
|
-
id: id("stable kebab-case identifier of the group"),
|
|
23380
|
-
label: line(LABEL_MAX, "subsystem name shown at the far zoom"),
|
|
23381
|
-
layer: id("band this group clusters; members must live on the same band")
|
|
23382
|
-
})
|
|
23383
|
-
).optional(),
|
|
23384
|
-
nodes: external_exports.array(
|
|
23385
|
-
closed({
|
|
23386
|
-
id: id("stable kebab-case identifier of the node"),
|
|
23387
|
-
label: line(LABEL_MAX, "display label inside the box"),
|
|
23388
|
-
layer: id("id of the band this node lives in"),
|
|
23389
|
-
status: status("defaults to planned").optional(),
|
|
23390
|
-
evidence: line(
|
|
23391
|
-
EVIDENCE_MAX,
|
|
23392
|
-
"how an already-verified node was verified; for regressed: what broke. Declaring a node straight to done needs it as much as updating one does."
|
|
23393
|
-
).optional(),
|
|
23394
|
-
detail: note(
|
|
23395
|
-
DETAIL_MAX,
|
|
23396
|
-
"design notes shown in the pane detail panel: responsibility, contract, key decisions"
|
|
23397
|
-
).optional(),
|
|
23398
|
-
group: id("same-band group this node belongs to").optional(),
|
|
23399
|
-
kind: nodeKind().optional(),
|
|
23400
|
-
lane: id("lane (column) this node belongs to").optional(),
|
|
23401
|
-
submap: id(
|
|
23402
|
-
"page slug of this node's child map \u2014 the pane badges the node \u229E and double-click dives in. Declare the child page separately. Create a sub-map only when the node's internals genuinely deserve their own picture; most nodes need none."
|
|
23403
|
-
).optional()
|
|
23404
|
-
})
|
|
23405
|
-
).optional(),
|
|
23406
|
-
edges: external_exports.array(
|
|
23407
|
-
closed({
|
|
23408
|
-
...edgeEnds(),
|
|
23409
|
-
label: line(EDGE_LABEL_MAX, "what flows along the edge").optional()
|
|
23410
|
-
})
|
|
23411
|
-
).optional()
|
|
23412
|
-
})
|
|
23413
|
-
},
|
|
24260
|
+
declareTool(),
|
|
23414
24261
|
(input) => {
|
|
23415
24262
|
const result = withPane(mutate(input.page, (map) => applyDeclare(map, input)), input.page);
|
|
23416
24263
|
if (result.isError === true) return result;
|
|
@@ -23420,62 +24267,12 @@ note: ${describeStoreError(scopes.error)} \u2014 fix it or rerun setup (mmap_set
|
|
|
23420
24267
|
);
|
|
23421
24268
|
server.registerTool(
|
|
23422
24269
|
"mmap_update",
|
|
23423
|
-
|
|
23424
|
-
title: "Record progress and revise the map",
|
|
23425
|
-
description: "The revision tool, all-or-nothing. Record progress on nodes: in-progress when starting a node (the pane spins), done with evidence when its verification passes, regressed with evidence when a done node breaks. Revise what the ghost design got wrong: move a node to another band, join or leave a group or lane, rename a band (or re-rank it, which reorders the whole map), relabel a group or a lane. Every clearable field takes null to empty it \u2014 that is how a field is cleared, never an empty string. Bands, groups and lanes are applied before the node updates, and within one node update `layer` moves the node before its other fields. The map is a ledger: report honestly, it never blocks you.",
|
|
23426
|
-
inputSchema: closed({
|
|
23427
|
-
page: page(),
|
|
23428
|
-
updates: external_exports.array(
|
|
23429
|
-
closed({
|
|
23430
|
-
id: id("id of the node to update"),
|
|
23431
|
-
status: status("the status to record").optional(),
|
|
23432
|
-
label: line(LABEL_MAX, "new display label inside the box").optional(),
|
|
23433
|
-
evidence: line(EVIDENCE_MAX, "for done: how it was verified; for regressed: what broke; null clears it").nullable().optional(),
|
|
23434
|
-
detail: note(
|
|
23435
|
-
DETAIL_MAX,
|
|
23436
|
-
"design notes shown in the pane detail panel: responsibility, contract, key decisions; null clears them"
|
|
23437
|
-
).nullable().optional(),
|
|
23438
|
-
layer: id(
|
|
23439
|
-
"move the node to this band; applied before this item's other fields, so a node can move and join a group on the new band in one item. Every edge touching it must still point strictly downward, and a grouped node may only move to its group's band."
|
|
23440
|
-
).optional(),
|
|
23441
|
-
group: id("join this same-band group; null leaves the current group").nullable().optional(),
|
|
23442
|
-
kind: nodeKind().nullable().optional(),
|
|
23443
|
-
lane: id("join this lane; null leaves the current lane").nullable().optional(),
|
|
23444
|
-
submap: id("link a child map page by slug; null unlinks it").nullable().optional()
|
|
23445
|
-
})
|
|
23446
|
-
).min(1).optional(),
|
|
23447
|
-
layers: external_exports.array(
|
|
23448
|
-
closed({
|
|
23449
|
-
id: id("id of the band to revise"),
|
|
23450
|
-
name: line(LABEL_MAX, "new display name of the band").optional(),
|
|
23451
|
-
rank: rank().optional()
|
|
23452
|
-
})
|
|
23453
|
-
).min(1).optional().describe("rename and/or re-rank existing bands; an item must carry a name, a rank, or both"),
|
|
23454
|
-
groups: external_exports.array(closed({ id: id("id of the group to relabel"), label: line(LABEL_MAX, "new subsystem name") })).min(1).optional().describe("relabel existing groups; membership and band are untouched"),
|
|
23455
|
-
lanes: external_exports.array(closed({ id: id("id of the lane to relabel"), label: line(LABEL_MAX, "new column name") })).min(1).optional().describe("relabel existing lanes; order and membership are untouched")
|
|
23456
|
-
})
|
|
23457
|
-
},
|
|
24270
|
+
updateTool(),
|
|
23458
24271
|
(input) => withPane(mutate(input.page, (map) => applyUpdate(map, input)), input.page)
|
|
23459
24272
|
);
|
|
23460
24273
|
server.registerTool(
|
|
23461
24274
|
"mmap_remove",
|
|
23462
|
-
|
|
23463
|
-
title: "Revise the map",
|
|
23464
|
-
description: 'Remove edges, nodes, groups and empty layer bands (in that order, all-or-nothing). Removing a node also removes every edge touching it; removing a group merely ungroups its members. Use when the ghost design turns out wrong \u2014 the map is a hypothesis, revising it is honest work. `pages` is the other scale: it DELETES whole page files, so a finished effort can be cleaned up instead of accumulating tabs forever. A bare `{pages: ["slug"]}` with no other field is the normal form; combined with map edits, the edits are applied first and the pages are deleted after. The deletion is permanent and cannot be undone, so delete only pages whose effort is over \u2014 and only ever with the user behind it. An unknown slug is refused with the project\'s real page list (naming a page that does not exist is a typo, not a request). The default page has no slug and is not deletable here. A node elsewhere still pointing at a deleted page with `submap` stays legal \u2014 a submap reference has no existence invariant \u2014 but it has nowhere to dive until the page comes back.',
|
|
23465
|
-
inputSchema: closed({
|
|
23466
|
-
page: page(),
|
|
23467
|
-
edges: external_exports.array(closed(edgeEnds())).optional(),
|
|
23468
|
-
nodes: external_exports.array(id("id of the node to remove, with every edge touching it")).optional(),
|
|
23469
|
-
groups: external_exports.array(id("id of the group to remove; members stay, merely ungrouped")).optional(),
|
|
23470
|
-
lanes: external_exports.array(id("id of the lane to remove; members stay, merely off-lane")).optional(),
|
|
23471
|
-
layers: external_exports.array(id("id of the band to remove; it must hold no nodes and no groups")).optional(),
|
|
23472
|
-
pages: external_exports.array(
|
|
23473
|
-
id(
|
|
23474
|
-
"slug of a page whose WHOLE map file is deleted \u2014 the page and everything drawn on it. Not the page this same call targets with `page`."
|
|
23475
|
-
)
|
|
23476
|
-
).optional().describe("pages to delete entirely, after this call's map edits; permanent")
|
|
23477
|
-
})
|
|
23478
|
-
},
|
|
24275
|
+
removeTool(),
|
|
23479
24276
|
(input) => {
|
|
23480
24277
|
if (input.pages === void 0) return withPane(mutate(input.page, (map) => applyRemove(map, input)), input.page);
|
|
23481
24278
|
const refusal = refusePageDeletion(input.pages, input.page);
|
|
@@ -23493,16 +24290,7 @@ note: ${describeStoreError(scopes.error)} \u2014 fix it or rerun setup (mmap_set
|
|
|
23493
24290
|
);
|
|
23494
24291
|
server.registerTool(
|
|
23495
24292
|
"mmap_setup",
|
|
23496
|
-
|
|
23497
|
-
title: "Configure when maps open",
|
|
23498
|
-
description: 'Get or set the mapping policy \u2014 WHEN the assistant opens a Mellos map. Call with no arguments to read it: the reply names the policy chosen for the USER (every project), the one this PROJECT overrides it with if any, and which of them is in effect. If it reports "not set", ask the USER to choose (never pick for them): always = ' + describeMappingPolicy("always") + "; complex = " + describeMappingPolicy("complex") + "; on-request = " + describeMappingPolicy("on-request") + '. Then call again with their choice to persist it. It defaults to user scope, which is the normal one \u2014 the question is about how someone works, so it is asked once ever, not once per repository. Pass scope: "project" only when the user wants THIS project to differ from that habit. The policy guides you; it never blocks the tools, and an explicit user request for a map always wins.',
|
|
23499
|
-
inputSchema: closed({
|
|
23500
|
-
policy: external_exports.enum(MAPPING_POLICIES).optional().describe("the user's choice to persist; omit to read the current policy"),
|
|
23501
|
-
scope: external_exports.enum(POLICY_SCOPES).optional().describe(
|
|
23502
|
-
'where to record the choice: "user" (default) applies to every project this user opens; "project" overrides that for this project alone. Ignored when reading.'
|
|
23503
|
-
)
|
|
23504
|
-
})
|
|
23505
|
-
},
|
|
24293
|
+
setupTool(),
|
|
23506
24294
|
(input) => {
|
|
23507
24295
|
const scope = input.scope ?? "user";
|
|
23508
24296
|
const configFile = scope === "project" ? projectConfigFile : userConfigFile;
|
|
@@ -23530,56 +24318,66 @@ note: ${describeStoreError(scopes.error)} \u2014 fix it or rerun setup (mmap_set
|
|
|
23530
24318
|
);
|
|
23531
24319
|
server.registerTool(
|
|
23532
24320
|
"mmap_view",
|
|
23533
|
-
|
|
23534
|
-
title: "View the current map",
|
|
23535
|
-
description: "Render the current Mellos map as monochrome text \u2014 the same picture the split-pane watcher shows live. Use it to check the map state or to show it inline in conversation. Every response ends with a `pages:` line naming the pages this project actually has and which one you are looking at, so this is also how you discover whether a map exists at all and under which slugs \u2014 never probe the files.",
|
|
23536
|
-
inputSchema: closed({
|
|
23537
|
-
page: page(),
|
|
23538
|
-
zoom: external_exports.number().int().min(ZOOM_MIN).max(ZOOM_MAX).optional().describe("zoom ladder: 1 = detail (notes unfold), 0 = standard (default), -1..-3 = scaled down, -4 = overview glyphs")
|
|
23539
|
-
})
|
|
23540
|
-
},
|
|
24321
|
+
viewTool(),
|
|
23541
24322
|
(input) => {
|
|
23542
24323
|
const current = loadOrEmpty(fileOf(input.page));
|
|
23543
24324
|
if (!current.ok) return text(current.error, true);
|
|
23544
24325
|
const zoom = clampZoom(input.zoom ?? 0);
|
|
23545
24326
|
const picture = renderMap(current.value, { color: false, unicode: true, spinnerFrame: 0, zoom }).join("\n");
|
|
24327
|
+
const surface = previews.enabled() ? `markdown: ${previewFile(stateFile, input.page)}
|
|
24328
|
+
Use mmap_open {surface: "markdown", page} to regenerate. Desktop visibility is not tracked.` : existsSync6(webRuntimeFile(stateFile)) ? 'web: configured \u2014 use mmap_open {surface: "web", page} to open or reconnect. Desktop visibility is not tracked.' : currentPaneLine(input.page);
|
|
23546
24329
|
return text(`${picture}
|
|
23547
24330
|
${pagesLine(stateFile, input.page)}
|
|
23548
|
-
${
|
|
24331
|
+
${surface}`);
|
|
23549
24332
|
}
|
|
23550
24333
|
);
|
|
23551
24334
|
server.registerTool(
|
|
23552
24335
|
"mmap_open",
|
|
23553
|
-
|
|
23554
|
-
title: "Open the map pane",
|
|
23555
|
-
description: "Put the live map on the user's screen: a terminal pane beside this conversation that redraws on every write. Call it whenever a result says `pane: CLOSED` \u2014 and do NOT ask permission first, because a user who has set a mapping policy has already said they want to see the map. With a pane already open this RETARGETS it to `page` instead of opening a second one, so it is also how you show the user a particular page when they ask for one. It never closes a pane: taking the map off the screen belongs to the user (the `q` key in the pane, or typing `mmap` in a terminal). The reply says whether a pane actually reported itself in afterwards, not merely that a command was run. Windows Terminal is the supported route; anywhere else it says so and you relay the manual command from the README.",
|
|
23556
|
-
inputSchema: closed({
|
|
23557
|
-
page: id(
|
|
23558
|
-
"page to show first \u2014 the page THIS effort lives on, the same slug you pass to the other tools. Omit only for the default page: without it a fresh pane opens on whichever page was written last, which after a gap is rarely the one under discussion."
|
|
23559
|
-
).optional(),
|
|
23560
|
-
window: external_exports.boolean().optional().describe(
|
|
23561
|
-
`open the map in its own "mellos-mapping" window instead of splitting this conversation's window. Pass it only when the user asked for the map separate (a second monitor, a small screen); the split is the default because the map is meant to sit beside what it describes.`
|
|
23562
|
-
)
|
|
23563
|
-
})
|
|
23564
|
-
},
|
|
24336
|
+
openTool(),
|
|
23565
24337
|
async (input) => {
|
|
23566
|
-
|
|
23567
|
-
|
|
23568
|
-
|
|
23569
|
-
`
|
|
23570
|
-
|
|
23571
|
-
);
|
|
24338
|
+
if (input.surface === "codex-terminal") {
|
|
24339
|
+
if (input.window === true) return text("codex-terminal uses the current conversation panel; window: true is not supported.", true);
|
|
24340
|
+
if (input.page && !listPageFiles(stateFile).some((file) => pageIdOfFile(stateFile, file) === input.page)) {
|
|
24341
|
+
return text(`Unknown page: ${input.page}. ${pagesLine(stateFile, input.page)}`, true);
|
|
24342
|
+
}
|
|
24343
|
+
const handoff = terminalHandoff(process.execPath, fileURLToPath2(new URL("./watch.mjs", import.meta.url)), stateFile, input.page);
|
|
24344
|
+
return text("terminal: ready-to-start\n" + JSON.stringify(handoff, null, 2) + "\nUse open_in_codex with hostOpen in the CURRENT conversation, without a threadId. If a supported host tool can run commands in that user terminal, use it. Otherwise give the user the command for their shell to paste once. An exec_command session_id belongs to the agent PTY, not this terminal. queued is not visible, and opened is not running. Use read_thread_terminal to confirm the map title and controls after startup. Preserve a page the user pinned.");
|
|
24345
|
+
}
|
|
24346
|
+
if (input.surface === "web" || input.surface === "web-terminal") {
|
|
24347
|
+
if (input.window === true) return text('surface: "web" cannot be combined with window: true. Open the returned URL using the desktop host.', true);
|
|
24348
|
+
try {
|
|
24349
|
+
const url = await openWebPreview(stateFile, fileURLToPath2(new URL("./web.mjs", import.meta.url)), input.page, input.surface === "web-terminal");
|
|
24350
|
+
return text(`surface: ${input.surface}
|
|
24351
|
+
hostOpen: ${JSON.stringify({ placement: "right", target: { type: "browser", url } })}
|
|
24352
|
+
preview: ready
|
|
24353
|
+
web: ${url}
|
|
24354
|
+
Open this URL in the current conversation's right browser panel using the host tool. The viewer refreshes from project maps while open. Existing Markdown previews remain enabled. Desktop visibility is not confirmed by this tool.`);
|
|
24355
|
+
} catch (error2) {
|
|
24356
|
+
return text(`Could not open web preview: ${String(error2)}`, true);
|
|
24357
|
+
}
|
|
24358
|
+
}
|
|
24359
|
+
if (input.surface === "markdown") {
|
|
24360
|
+
if (input.window === true) return text('surface: "markdown" cannot be combined with window: true. Open the returned file using the desktop host.', true);
|
|
24361
|
+
const published = previews.activate(input.page);
|
|
24362
|
+
if (!published.ok) return text(`Could not generate Markdown preview: ${published.error}`, true);
|
|
24363
|
+
return text(`preview: ready
|
|
24364
|
+
markdown: ${published.value.path}
|
|
24365
|
+
index: ${published.value.index}
|
|
24366
|
+
Automatic preview updates are enabled for this project. Open the Markdown file in the current conversation's right file panel using the host tool. No terminal was launched. Visibility and automatic file-viewer refresh are not confirmed by this tool.`);
|
|
23572
24367
|
}
|
|
23573
|
-
const run = await
|
|
23574
|
-
const viewers = run.ok ? await awaitPane(stateFile, input.page, Date.now() + PANE_REPORT_TIMEOUT_MS) : [];
|
|
23575
|
-
|
|
24368
|
+
const run = await launch(launcherArgs(projectDirOf(stateFile), input.page, input.window === true));
|
|
24369
|
+
const viewers = run.ok ? await awaitPane(stateFile, input.page, Date.now() + PANE_REPORT_TIMEOUT_MS, launcherViewerPid(run)) : [];
|
|
24370
|
+
const outcome = openOutcome(run, viewers, input.page);
|
|
24371
|
+
const failed = !run.ok || !paneShows(viewers, input.page);
|
|
24372
|
+
paneOpenFailure = failed ? outcome : void 0;
|
|
24373
|
+
return text(outcome, failed);
|
|
23576
24374
|
}
|
|
23577
24375
|
);
|
|
23578
24376
|
return server;
|
|
23579
24377
|
}
|
|
23580
24378
|
function resolveStateFile(env, cwd) {
|
|
23581
24379
|
const projectDir = env["MELLOS_MAPPING_CWD"] ?? env["CLAUDE_PROJECT_DIR"] ?? cwd;
|
|
23582
|
-
return
|
|
24380
|
+
return join8(projectDir, STATE_FILE_RELATIVE_PATH);
|
|
23583
24381
|
}
|
|
23584
24382
|
function resolveUserConfigFile(home) {
|
|
23585
24383
|
return userConfigFilePath(home);
|
|
@@ -23594,7 +24392,7 @@ async function main() {
|
|
|
23594
24392
|
function launchedAsEntry(argv1, moduleUrl) {
|
|
23595
24393
|
if (argv1 === void 0) return false;
|
|
23596
24394
|
try {
|
|
23597
|
-
return
|
|
24395
|
+
return realpathSync2(argv1) === realpathSync2(fileURLToPath2(moduleUrl));
|
|
23598
24396
|
} catch {
|
|
23599
24397
|
return pathToFileURL(argv1).href === moduleUrl;
|
|
23600
24398
|
}
|