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.
Files changed (68) hide show
  1. package/README.md +125 -88
  2. package/README.zh-CN.md +122 -84
  3. package/dist/hook-session-start.mjs +25 -19
  4. package/dist/mmap.mjs +302 -170
  5. package/dist/preview.mjs +1418 -0
  6. package/dist/server.mjs +1198 -400
  7. package/dist/store-paths.mjs +40 -21
  8. package/dist/terminal-worker.mjs +3188 -0
  9. package/dist/watch.mjs +523 -280
  10. package/dist/web/TERMINAL-LICENSES.txt +70 -0
  11. package/dist/web/app.css +967 -0
  12. package/dist/web/app.js +1356 -0
  13. package/dist/web/index.html +9 -0
  14. package/dist/web/terminal.css +9 -0
  15. package/dist/web/terminal.html +7 -0
  16. package/dist/web/terminal.js +9293 -0
  17. package/dist/web/xterm.css +285 -0
  18. package/dist/web.mjs +5535 -0
  19. package/docs/codex.md +183 -0
  20. package/lib/domain/text.d.ts +9 -0
  21. package/lib/domain/text.js +43 -0
  22. package/lib/domain/types.js +10 -1
  23. package/lib/preview/index.d.ts +3 -0
  24. package/lib/preview/index.js +3 -0
  25. package/lib/preview/markdown.d.ts +8 -0
  26. package/lib/preview/markdown.js +54 -0
  27. package/lib/preview/presentation.d.ts +6 -0
  28. package/lib/preview/presentation.js +14 -0
  29. package/lib/preview/publisher.d.ts +23 -0
  30. package/lib/preview/publisher.js +143 -0
  31. package/lib/preview/svg.d.ts +3 -0
  32. package/lib/preview/svg.js +74 -0
  33. package/lib/preview/text.d.ts +4 -0
  34. package/lib/preview/text.js +13 -0
  35. package/lib/render/canvas.d.ts +1 -1
  36. package/lib/render/canvas.js +4 -2
  37. package/lib/render/draw.js +9 -6
  38. package/lib/render/render.d.ts +6 -0
  39. package/lib/render/render.js +50 -18
  40. package/lib/render/width.js +3 -1
  41. package/lib/store/atomic.d.ts +18 -0
  42. package/lib/store/atomic.js +86 -0
  43. package/lib/store/channels.d.ts +40 -0
  44. package/lib/store/channels.js +135 -0
  45. package/lib/store/format.js +3 -1
  46. package/lib/store/json-text.d.ts +9 -0
  47. package/lib/store/json-text.js +16 -0
  48. package/lib/store/maps.d.ts +12 -0
  49. package/lib/store/maps.js +42 -0
  50. package/lib/store/migration.d.ts +12 -0
  51. package/lib/store/migration.js +46 -0
  52. package/lib/store/pages.d.ts +46 -0
  53. package/lib/store/pages.js +89 -0
  54. package/lib/store/policy.d.ts +74 -0
  55. package/lib/store/policy.js +144 -0
  56. package/lib/store/store.d.ts +9 -256
  57. package/lib/store/store.js +10 -694
  58. package/lib/store/viewers.d.ts +81 -0
  59. package/lib/store/viewers.js +186 -0
  60. package/package.json +25 -6
  61. package/scripts/codex-cli.mjs +42 -0
  62. package/scripts/codex-register.mjs +27 -94
  63. package/scripts/mmap.mjs +26 -16
  64. package/scripts/open-pane.mjs +37 -18
  65. package/scripts/pane-core.mjs +57 -220
  66. package/scripts/terminal-session.mjs +137 -0
  67. package/scripts/tmux-session.mjs +90 -0
  68. 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 = resolve.call(this, root, ref);
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 resolve(root, ref) {
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(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
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 consumeIsZone(buffer) {
3138
- buffer.length = 0;
3139
- return true;
3140
- }
3141
- function consumeHextets(buffer, address, output) {
3142
- if (buffer.length) {
3143
- const hex = stringArrayToHexStripped(buffer);
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
- buffer.length = 0;
3167
+ return false;
3151
3168
  }
3152
3169
  return true;
3153
3170
  }
3154
- function getIPV6(input) {
3155
- let tokenCount = 0;
3156
- const output = { error: false, address: "", zone: "" };
3157
- const address = [];
3158
- const buffer = [];
3159
- let endipv6Encountered = false;
3160
- let endIpv6 = false;
3161
- let consume = consumeHextets;
3162
- for (let i = 0; i < input.length; i++) {
3163
- const cursor = input[i];
3164
- if (cursor === "[" || cursor === "]") {
3165
- continue;
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
- buffer.push(cursor);
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 (buffer.length) {
3194
- if (consume === consumeIsZone) {
3195
- output.zone = buffer.join("");
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
- output.address = address.join("");
3203
- return output;
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
- if (findToken(host, ":") < 2) {
3207
- return { host, isIPV6: false };
3208
- }
3209
- const ipv62 = getIPV6(host);
3210
- if (!ipv62.error) {
3211
- let newHost = ipv62.address;
3212
- let escapedHost = ipv62.address;
3213
- if (ipv62.zone) {
3214
- newHost += "%" + ipv62.zone;
3215
- escapedHost += "%25" + ipv62.zone;
3216
- }
3217
- return { host: newHost, isIPV6: true, escapedHost };
3218
- } else {
3219
- return { host, isIPV6: false };
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
- if (input[i] === "%" && i + 2 < input.length) {
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(input[i])) {
3354
- output += input[i];
3387
+ if (isPathCharacter(ch)) {
3388
+ output += ch;
3355
3389
  } else {
3356
- output += escape(input[i]);
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 = unescape(component.host);
3559
+ let host = component.host;
3384
3560
  if (!isIPv4(host)) {
3385
- const ipV6res = normalizeIPv6(host);
3386
- if (ipV6res.isIPV6 === true) {
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(String(component.port));
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 = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
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 [path, query] = wsComponent.resourceName.split("?");
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 = 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, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
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 resolve(baseURI, relativeURI, options) {
3841
+ function resolve2(baseURI, relativeURI, options) {
3643
3842
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3644
- const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3645
- const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3646
- if (baseMalformed || relativeMalformed) {
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.toLowerCase() === normalizedB.toLowerCase();
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 = escapePreservingEscapes(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
- parsed.host = ipv6result.host.toLowerCase();
3846
- isIP = ipv6result.isIPV6;
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 (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
3865
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
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.scheme !== void 0) {
3876
- parsed.scheme = unescape(parsed.scheme);
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
- try {
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 === "string") {
3916
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
3917
- return malformedAuthorityOrPort ? void 0 : normalized;
4194
+ if (typeof uri !== "string" && typeof uri !== "object") {
4195
+ return void 0;
3918
4196
  }
3919
- if (typeof uri === "object") {
3920
- return serialize(uri, opts);
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 { spawn } from "node:child_process";
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 { dirname as dirname2, join as join2 } from "node:path";
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((resolve) => setTimeout(resolve, pollInterval));
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((resolve, reject) => {
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
- resolve(parseResult.data);
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((resolve, reject) => {
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(resolve, interval);
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((resolve) => setTimeout(resolve, pollInterval));
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((resolve) => {
21430
+ return new Promise((resolve2) => {
21149
21431
  const json = serializeMessage(message);
21150
21432
  if (this._stdout.write(json)) {
21151
- resolve();
21433
+ resolve2();
21152
21434
  } else {
21153
- this._stdout.once("drain", resolve);
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; dependencies may only point to a lower layer`;
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 ? "1" : SGR.faint : [SGR[c.style], c.bold ? "1" : ""].filter(Boolean).join(";");
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], skin.style, focused);
22202
- canvas.text(x, y + 1, skin.v, skin.style, focused);
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, skin.style, focused);
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, skin.style, focused);
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, skin.style, focused);
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], skin.style, focused);
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 = buildCanvas(map, opts);
22712
+ const built = paint(prepareScene(map, opts), opts);
22392
22713
  return built.canvas.emit(opts);
22393
22714
  }
22394
- function buildCanvas(map, opts) {
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
- return paint(drawn, opts, aggregated !== void 0 ? AGGREGATE_GEO : plainGeo, unverifiedDoneIds(oriented, drawn));
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 paint(map, opts, geo, unverified) {
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
- return ok(map);
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/store.ts
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 >= RENAME_MAX_ATTEMPTS) {
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
- function isRecord2(v) {
22706
- return typeof v === "object" && v !== null && !Array.isArray(v);
22707
- }
22708
- function stripBom(text2) {
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(dirname(defaultFile), PAGES_DIR_NAME, `${page2}.json`);
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(dirname(defaultFile), PAGES_DIR_NAME));
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(dirname(defaultFile), PAGES_DIR_NAME, e));
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
- rmSync(path, { force: true });
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 join(dirname(defaultFile), VIEWERS_DIR_NAME);
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 = readdirSync(dir);
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 = join(dir, name);
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
- rmSync(path, { force: true });
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 join(dirname(defaultFile), CONFIG_FILE_NAME);
23152
+ return join3(dirname4(defaultFile), CONFIG_FILE_NAME);
22806
23153
  }
22807
23154
  function userConfigFilePath(userBase) {
22808
- return join(userBase, STORE_DIR_NAME, CONFIG_FILE_NAME);
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 = readFileSync(path, "utf8");
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
- var LEGACY_STATE_FILE_RELATIVE_PATH = join(".claude", "mellos-mapping.json");
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 = dirname(dirname(defaultFile));
22867
- const legacyDefault = join(projectRoot, LEGACY_STATE_FILE_RELATIVE_PATH);
22868
- const legacyPages = join(dirname(legacyDefault), LEGACY_PAGES_DIR_NAME);
22869
- const legacyConfig = join(dirname(legacyDefault), LEGACY_CONFIG_FILE_NAME);
22870
- const hasLegacy = existsSync(legacyDefault) || existsSync(legacyPages) || existsSync(legacyConfig);
22871
- const hasCurrent = existsSync(defaultFile) || existsSync(join(dirname(defaultFile), PAGES_DIR_NAME)) || existsSync(configFilePath(defaultFile));
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
- mkdirSync(dirname(defaultFile), { recursive: true });
22874
- if (existsSync(legacyDefault)) renameSync(legacyDefault, defaultFile);
22875
- if (existsSync(legacyPages)) renameSync(legacyPages, join(dirname(defaultFile), PAGES_DIR_NAME));
22876
- if (existsSync(legacyConfig)) renameSync(legacyConfig, configFilePath(defaultFile));
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 = readFileSync(path, "utf8");
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/server/server.ts
23147
- var SERVER_NAME = "mellos-mapping";
23148
- var SERVER_VERSION = "0.20.2";
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
23522
+ }
23523
+ function markdown(text2) {
23524
+ return text2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").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", "", `![\u5206\u5C42\u4F9D\u8D56\u5730\u56FE](${image})`, "");
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: open on this page \u2014 the user is seeing this.";
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
- var LAUNCH_TIMEOUT_MS = 45e3;
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 join2(dirname2(dirname2(fileURLToPath(moduleUrl))), "scripts", "open-pane.mjs");
24104
+ return join7(dirname9(dirname9(fileURLToPath(moduleUrl))), "scripts", "open-pane.mjs");
23226
24105
  }
23227
24106
  function projectDirOf(stateFile) {
23228
- return dirname2(dirname2(stateFile));
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((resolve) => {
23238
- const child = spawn(process.execPath, [script, ...args], {
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
- resolve({ ok: false, output: e.message });
24130
+ resolve2({ ok: false, output: e.message });
23252
24131
  });
23253
24132
  child.on("close", (code) => {
23254
24133
  clearTimeout(abandon);
23255
- resolve({ ok: code === 0, output: output.trim() });
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 this to the user \u2014 on a machine without Windows Terminal the map is opened by running the watcher in any second terminal or tmux split (see the plugin README).`;
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
- return `pane: open and showing ${pageName(page2)} \u2014 the user can see the map now.
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) => pageName(v.page)))].join(", ");
23281
- return `pane: open, but it reports ${elsewhere} rather than ${pageName(page2)}. With auto-follow on it lands there on your next write; with follow off the user is holding that page on purpose.
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 loadOrEmpty(stateFile) {
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 file = fileOf(page2);
23305
- const current = loadOrEmpty(file);
23306
- if (!current.ok) return text(current.error, true);
23307
- const applied = apply(current.value);
23308
- if (!applied.ok) return text(`refused (nothing changed): ${applied.error}`, true);
23309
- const saved = saveMapFile(file, applied.value);
23310
- if (!saved.ok) return saveFailed(saved.error);
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
- ${paneLine(stateFile, page2)}`);
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
- ${paneLine(stateFile, input.page)}`);
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
- const script = launcherPath(import.meta.url);
23567
- if (!existsSync2(script)) {
23568
- return text(
23569
- `cannot open the pane: the launcher is missing at ${script}. This install is incomplete \u2014 tell the user to reinstall the plugin (a source checkout needs "npm run build").`,
23570
- true
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 runLauncher(script, launcherArgs(projectDirOf(stateFile), input.page, input.window === true));
23574
- const viewers = run.ok ? await awaitPane(stateFile, input.page, Date.now() + PANE_REPORT_TIMEOUT_MS) : [];
23575
- return text(openOutcome(run, viewers, input.page), !run.ok);
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 join2(projectDir, STATE_FILE_RELATIVE_PATH);
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 realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
24395
+ return realpathSync2(argv1) === realpathSync2(fileURLToPath2(moduleUrl));
23598
24396
  } catch {
23599
24397
  return pathToFileURL(argv1).href === moduleUrl;
23600
24398
  }