@casys/mcp-erpnext 3.0.2 → 3.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mcp-erpnext.mjs CHANGED
@@ -3117,7 +3117,25 @@ var require_utils2 = __commonJS({
3117
3117
  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);
3118
3118
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3119
3119
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3120
- var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3120
+ var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
3121
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
3122
+ var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
3123
+ var BYTE_HEX = new Array(256);
3124
+ {
3125
+ const HEX_DIGITS = "0123456789ABCDEF";
3126
+ for (let i = 0; i < 256; i++) {
3127
+ BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
3128
+ }
3129
+ }
3130
+ function percentEncodeNonAscii(cp) {
3131
+ if (cp < 2048) {
3132
+ return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
3133
+ }
3134
+ if (cp < 65536) {
3135
+ return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3136
+ }
3137
+ return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3138
+ }
3121
3139
  function stringArrayToHexStripped(input) {
3122
3140
  let acc = "";
3123
3141
  let code = 0;
@@ -3142,91 +3160,105 @@ var require_utils2 = __commonJS({
3142
3160
  }
3143
3161
  return acc;
3144
3162
  }
3163
+ var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
3164
+ var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
3165
+ var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
3145
3166
  var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
3146
- function consumeIsZone(buffer) {
3147
- buffer.length = 0;
3148
- return true;
3149
- }
3150
- function consumeHextets(buffer, address, output) {
3151
- if (buffer.length) {
3152
- const hex = stringArrayToHexStripped(buffer);
3153
- if (hex !== "") {
3154
- address.push(hex);
3155
- } else {
3156
- output.error = true;
3157
- return false;
3167
+ function isZoneIdentifier(zone) {
3168
+ if (zone.length === 0) return false;
3169
+ for (let i = 0; i < zone.length; i++) {
3170
+ if (isZoneCharacter(zone[i])) continue;
3171
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
3172
+ i += 2;
3173
+ continue;
3158
3174
  }
3159
- buffer.length = 0;
3175
+ return false;
3160
3176
  }
3161
3177
  return true;
3162
3178
  }
3163
- function getIPV6(input) {
3164
- let tokenCount = 0;
3165
- const output = { error: false, address: "", zone: "" };
3166
- const address = [];
3167
- const buffer = [];
3168
- let endipv6Encountered = false;
3169
- let endIpv6 = false;
3170
- let consume = consumeHextets;
3171
- for (let i = 0; i < input.length; i++) {
3172
- const cursor = input[i];
3173
- if (cursor === "[" || cursor === "]") {
3174
- continue;
3175
- }
3176
- if (cursor === ":") {
3177
- if (endipv6Encountered === true) {
3178
- endIpv6 = true;
3179
+ function compressIPv6ZeroRun(hextets) {
3180
+ let bestStart = -1;
3181
+ let bestLength = 0;
3182
+ let runStart = -1;
3183
+ let runLength = 0;
3184
+ for (let i = 0; i < hextets.length; i++) {
3185
+ if (hextets[i] === "0") {
3186
+ if (runStart === -1) runStart = i;
3187
+ runLength++;
3188
+ if (runLength > bestLength) {
3189
+ bestLength = runLength;
3190
+ bestStart = runStart;
3179
3191
  }
3180
- if (!consume(buffer, address, output)) {
3181
- break;
3182
- }
3183
- if (++tokenCount > 7) {
3184
- output.error = true;
3185
- break;
3186
- }
3187
- if (i > 0 && input[i - 1] === ":") {
3188
- endipv6Encountered = true;
3189
- }
3190
- address.push(":");
3191
- continue;
3192
- } else if (cursor === "%") {
3193
- if (!consume(buffer, address, output)) {
3194
- break;
3195
- }
3196
- consume = consumeIsZone;
3197
3192
  } else {
3198
- buffer.push(cursor);
3193
+ runStart = -1;
3194
+ runLength = 0;
3195
+ }
3196
+ }
3197
+ if (bestLength < 2) return hextets.join(":");
3198
+ const head = hextets.slice(0, bestStart).join(":");
3199
+ const tail = hextets.slice(bestStart + bestLength).join(":");
3200
+ return head + "::" + tail;
3201
+ }
3202
+ function normalizeIPv6Address(input) {
3203
+ const compression = input.indexOf("::");
3204
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1) return void 0;
3205
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
3206
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
3207
+ if (compression !== -1) {
3208
+ if (left.length === 1 && left[0] === "") left.length = 0;
3209
+ if (right.length === 1 && right[0] === "") right.length = 0;
3210
+ }
3211
+ const parts = left.concat(right);
3212
+ let hextetCount = 0;
3213
+ for (let i = 0; i < parts.length; i++) {
3214
+ const part = parts[i];
3215
+ if (part === "") return void 0;
3216
+ if (part.indexOf(".") !== -1) {
3217
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part)) return void 0;
3218
+ hextetCount += 2;
3199
3219
  continue;
3200
3220
  }
3221
+ if (!isHextet(part)) return void 0;
3222
+ parts[i] = parseInt(part, 16).toString(16);
3223
+ hextetCount++;
3201
3224
  }
3202
- if (buffer.length) {
3203
- if (consume === consumeIsZone) {
3204
- output.zone = buffer.join("");
3205
- } else if (endIpv6) {
3206
- address.push(buffer.join(""));
3207
- } else {
3208
- address.push(stringArrayToHexStripped(buffer));
3209
- }
3225
+ if (compression === -1) {
3226
+ if (hextetCount !== 8) return void 0;
3227
+ return compressIPv6ZeroRun(parts);
3210
3228
  }
3211
- output.address = address.join("");
3212
- return output;
3229
+ if (hextetCount >= 8) return void 0;
3230
+ const expanded = parts.slice(0, left.length);
3231
+ for (let i = hextetCount; i < 8; i++) expanded.push("0");
3232
+ for (let i = left.length; i < parts.length; i++) expanded.push(parts[i]);
3233
+ return compressIPv6ZeroRun(expanded);
3213
3234
  }
3214
3235
  function normalizeIPv6(host) {
3215
- if (findToken(host, ":") < 2) {
3216
- return { host, isIPV6: false };
3217
- }
3218
- const ipv62 = getIPV6(host);
3219
- if (!ipv62.error) {
3220
- let newHost = ipv62.address;
3221
- let escapedHost = ipv62.address;
3222
- if (ipv62.zone) {
3223
- newHost += "%" + ipv62.zone;
3224
- escapedHost += "%25" + ipv62.zone;
3225
- }
3226
- return { host: newHost, isIPV6: true, escapedHost };
3227
- } else {
3228
- return { host, isIPV6: false };
3229
- }
3236
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
3237
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
3238
+ if (hasBracket && !bracketed) return { host, isIPV6: false, error: true };
3239
+ let input = bracketed ? host.slice(1, -1) : host;
3240
+ if (bracketed && isIPvFuture(input)) {
3241
+ input = input.toLowerCase();
3242
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
3243
+ }
3244
+ if (findToken(input, ":") < 2) {
3245
+ return { host, isIPV6: false, error: bracketed };
3246
+ }
3247
+ let zoneIdentifier = "";
3248
+ const zoneSeparator = input.indexOf("%");
3249
+ if (zoneSeparator !== -1) {
3250
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
3251
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
3252
+ if (!isZoneIdentifier(zoneIdentifier)) return { host, isIPV6: false, error: true };
3253
+ input = input.slice(0, zoneSeparator);
3254
+ }
3255
+ const address = normalizeIPv6Address(input);
3256
+ if (address === void 0) return { host, isIPV6: false, error: true };
3257
+ return {
3258
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
3259
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
3260
+ isIPV6: true
3261
+ };
3230
3262
  }
3231
3263
  function findToken(str, token) {
3232
3264
  let ind = 0;
@@ -3345,7 +3377,8 @@ var require_utils2 = __commonJS({
3345
3377
  function normalizePathEncoding(input) {
3346
3378
  let output = "";
3347
3379
  for (let i = 0; i < input.length; i++) {
3348
- if (input[i] === "%" && i + 2 < input.length) {
3380
+ const ch = input[i];
3381
+ if (ch === "%" && i + 2 < input.length) {
3349
3382
  const hex = input.slice(i + 1, i + 3);
3350
3383
  if (isHexPair(hex)) {
3351
3384
  const normalizedHex = hex.toUpperCase();
@@ -3359,10 +3392,152 @@ var require_utils2 = __commonJS({
3359
3392
  continue;
3360
3393
  }
3361
3394
  }
3362
- if (isPathCharacter(input[i])) {
3363
- output += input[i];
3395
+ if (isPathCharacter(ch)) {
3396
+ output += ch;
3397
+ } else {
3398
+ const code = input.charCodeAt(i);
3399
+ if (code < 128) {
3400
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3401
+ } else if (code < 55296 || code > 57343) {
3402
+ output += percentEncodeNonAscii(code);
3403
+ } else if (code <= 56319 && i + 1 < input.length) {
3404
+ const low = input.charCodeAt(i + 1);
3405
+ if (low >= 56320 && low <= 57343) {
3406
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3407
+ i++;
3408
+ } else {
3409
+ output += percentEncodeNonAscii(65533);
3410
+ }
3411
+ } else {
3412
+ output += percentEncodeNonAscii(65533);
3413
+ }
3414
+ }
3415
+ }
3416
+ return output;
3417
+ }
3418
+ function serializePathEncoding(input, pathNoScheme = false) {
3419
+ let output = "";
3420
+ let firstSegment = pathNoScheme && input[0] !== "/";
3421
+ for (let i = 0; i < input.length; i++) {
3422
+ const ch = input[i];
3423
+ if (ch === "%" && i + 2 < input.length) {
3424
+ const hex = input.slice(i + 1, i + 3);
3425
+ if (isHexPair(hex)) {
3426
+ output += "%" + hex.toUpperCase();
3427
+ i += 2;
3428
+ continue;
3429
+ }
3430
+ }
3431
+ if (ch === "/") {
3432
+ firstSegment = false;
3433
+ }
3434
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
3435
+ output += ch;
3364
3436
  } else {
3365
- output += escape(input[i]);
3437
+ const code = input.charCodeAt(i);
3438
+ if (code < 128) {
3439
+ output += BYTE_HEX[code];
3440
+ } else if (code < 55296 || code > 57343) {
3441
+ output += percentEncodeNonAscii(code);
3442
+ } else if (code <= 56319 && i + 1 < input.length) {
3443
+ const low = input.charCodeAt(i + 1);
3444
+ if (low >= 56320 && low <= 57343) {
3445
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3446
+ i++;
3447
+ } else {
3448
+ output += percentEncodeNonAscii(65533);
3449
+ }
3450
+ } else {
3451
+ output += percentEncodeNonAscii(65533);
3452
+ }
3453
+ }
3454
+ }
3455
+ return output;
3456
+ }
3457
+ function encodeComponent(input, isAllowed) {
3458
+ let output = "";
3459
+ for (let i = 0; i < input.length; i++) {
3460
+ const ch = input[i];
3461
+ if (ch === "%" && i + 2 < input.length) {
3462
+ const hex = input.slice(i + 1, i + 3);
3463
+ if (isHexPair(hex)) {
3464
+ output += "%" + hex.toUpperCase();
3465
+ i += 2;
3466
+ continue;
3467
+ }
3468
+ }
3469
+ if (isAllowed(ch)) {
3470
+ output += ch;
3471
+ } else {
3472
+ const code = input.charCodeAt(i);
3473
+ if (code < 128) {
3474
+ output += BYTE_HEX[code];
3475
+ } else if (code < 55296 || code > 57343) {
3476
+ output += percentEncodeNonAscii(code);
3477
+ } else if (code <= 56319 && i + 1 < input.length) {
3478
+ const low = input.charCodeAt(i + 1);
3479
+ if (low >= 56320 && low <= 57343) {
3480
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3481
+ i++;
3482
+ } else {
3483
+ output += percentEncodeNonAscii(65533);
3484
+ }
3485
+ } else {
3486
+ output += percentEncodeNonAscii(65533);
3487
+ }
3488
+ }
3489
+ }
3490
+ return output;
3491
+ }
3492
+ function encodeUserinfo(input) {
3493
+ return encodeComponent(input, isUserinfoCharacter);
3494
+ }
3495
+ function encodeQuery(input) {
3496
+ return encodeComponent(input, isQueryFragmentCharacter);
3497
+ }
3498
+ function encodeFragment(input) {
3499
+ return encodeComponent(input, isQueryFragmentCharacter);
3500
+ }
3501
+ function isEscapeSafe(cp) {
3502
+ 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;
3503
+ }
3504
+ function normalizeQueryFragmentEncoding(input) {
3505
+ let output = "";
3506
+ for (let i = 0; i < input.length; i++) {
3507
+ const ch = input[i];
3508
+ if (ch === "%" && i + 2 < input.length) {
3509
+ const hex = input.slice(i + 1, i + 3);
3510
+ if (isHexPair(hex)) {
3511
+ const normalizedHex = hex.toUpperCase();
3512
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3513
+ if (isUnreserved(decoded)) {
3514
+ output += decoded;
3515
+ } else {
3516
+ output += "%" + normalizedHex;
3517
+ }
3518
+ i += 2;
3519
+ continue;
3520
+ }
3521
+ }
3522
+ if (isQueryFragmentCharacter(ch)) {
3523
+ output += ch;
3524
+ } else {
3525
+ const code = input.charCodeAt(i);
3526
+ if (code < 128) {
3527
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3528
+ } else if (code < 55296 || code > 57343) {
3529
+ output += percentEncodeNonAscii(code);
3530
+ } else if (code <= 56319 && i + 1 < input.length) {
3531
+ const low = input.charCodeAt(i + 1);
3532
+ if (low >= 56320 && low <= 57343) {
3533
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3534
+ i++;
3535
+ } else {
3536
+ output += percentEncodeNonAscii(65533);
3537
+ }
3538
+ } else {
3539
+ output += percentEncodeNonAscii(65533);
3540
+ }
3366
3541
  }
3367
3542
  }
3368
3543
  return output;
@@ -3385,14 +3560,18 @@ var require_utils2 = __commonJS({
3385
3560
  function recomposeAuthority(component) {
3386
3561
  const uriTokens = [];
3387
3562
  if (component.userinfo !== void 0) {
3388
- uriTokens.push(component.userinfo);
3563
+ uriTokens.push(encodeUserinfo(component.userinfo));
3389
3564
  uriTokens.push("@");
3390
3565
  }
3391
3566
  if (component.host !== void 0) {
3392
- let host = unescape(component.host);
3567
+ let host = component.host;
3393
3568
  if (!isIPv4(host)) {
3394
- const ipV6res = normalizeIPv6(host);
3395
- if (ipV6res.isIPV6 === true) {
3569
+ let ipV6res = normalizeIPv6(host);
3570
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
3571
+ host = normalizePercentEncoding(host, true);
3572
+ ipV6res = normalizeIPv6(host);
3573
+ }
3574
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
3396
3575
  host = `[${ipV6res.escapedHost}]`;
3397
3576
  } else {
3398
3577
  host = reescapeHostDelimiters(host, false);
@@ -3412,6 +3591,11 @@ var require_utils2 = __commonJS({
3412
3591
  reescapeHostDelimiters,
3413
3592
  normalizePercentEncoding,
3414
3593
  normalizePathEncoding,
3594
+ serializePathEncoding,
3595
+ normalizeQueryFragmentEncoding,
3596
+ encodeUserinfo,
3597
+ encodeQuery,
3598
+ encodeFragment,
3415
3599
  escapePreservingEscapes,
3416
3600
  removeDotSegments,
3417
3601
  isIPv4,
@@ -3427,7 +3611,7 @@ var require_schemes2 = __commonJS({
3427
3611
  "node_modules/fast-uri/lib/schemes.js"(exports, module) {
3428
3612
  "use strict";
3429
3613
  var { isUUID } = require_utils2();
3430
- var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
3614
+ var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
3431
3615
  var supportedSchemeNames = (
3432
3616
  /** @type {const} */
3433
3617
  [
@@ -3488,9 +3672,10 @@ var require_schemes2 = __commonJS({
3488
3672
  wsComponent.secure = void 0;
3489
3673
  }
3490
3674
  if (wsComponent.resourceName) {
3491
- const [path, query] = wsComponent.resourceName.split("?");
3675
+ const queryIndex = wsComponent.resourceName.indexOf("?");
3676
+ const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3492
3677
  wsComponent.path = path && path !== "/" ? path : void 0;
3493
- wsComponent.query = query;
3678
+ wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
3494
3679
  wsComponent.resourceName = void 0;
3495
3680
  }
3496
3681
  wsComponent.fragment = void 0;
@@ -3502,7 +3687,7 @@ var require_schemes2 = __commonJS({
3502
3687
  return urnComponent;
3503
3688
  }
3504
3689
  const matches = urnComponent.path.match(URN_REG);
3505
- if (matches) {
3690
+ if (matches && matches[0] === urnComponent.path) {
3506
3691
  const scheme = options.scheme || urnComponent.scheme || "urn";
3507
3692
  urnComponent.nid = matches[1].toLowerCase();
3508
3693
  urnComponent.nss = matches[2];
@@ -3636,8 +3821,17 @@ var require_schemes2 = __commonJS({
3636
3821
  var require_fast_uri2 = __commonJS({
3637
3822
  "node_modules/fast-uri/index.js"(exports, module) {
3638
3823
  "use strict";
3639
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils2();
3824
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils2();
3640
3825
  var { SCHEMES, getSchemeHandler } = require_schemes2();
3826
+ var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
3827
+ var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
3828
+ function decodeValidScheme(scheme) {
3829
+ const decodedScheme = unescape(String(scheme));
3830
+ if (!VALID_SCHEME.test(decodedScheme)) {
3831
+ throw new TypeError(MALFORMED_SCHEME_ERROR);
3832
+ }
3833
+ return decodedScheme;
3834
+ }
3641
3835
  function normalize2(uri, options) {
3642
3836
  if (typeof uri === "string") {
3643
3837
  uri = /** @type {T} */
@@ -3650,12 +3844,34 @@ var require_fast_uri2 = __commonJS({
3650
3844
  }
3651
3845
  function resolve(baseURI, relativeURI, options) {
3652
3846
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3653
- const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3654
- const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3655
- if (baseMalformed || relativeMalformed) {
3847
+ const {
3848
+ parsed: baseParsed,
3849
+ malformedAuthorityOrPort: baseMalformed,
3850
+ malformedPercentEncoding: baseMalformedPercentEncoding,
3851
+ malformedSchemeSpecific: baseMalformedSchemeSpecific,
3852
+ malformedHost: baseMalformedHost,
3853
+ malformedScheme: baseMalformedScheme
3854
+ } = parseWithStatus(baseURI, schemelessOptions);
3855
+ const {
3856
+ parsed: relativeParsed,
3857
+ malformedAuthorityOrPort: relativeMalformed,
3858
+ malformedPercentEncoding: relativeMalformedPercentEncoding,
3859
+ malformedSchemeSpecific: relativeMalformedSchemeSpecific,
3860
+ malformedHost: relativeMalformedHost,
3861
+ malformedScheme: relativeMalformedScheme
3862
+ } = parseWithStatus(relativeURI, schemelessOptions);
3863
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
3656
3864
  throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3657
3865
  }
3658
3866
  const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
3867
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
3868
+ const resolvedHost = resolved.host;
3869
+ const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
3870
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
3871
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !new RegExp("\\P{ASCII}", "u").test(resolvedHost);
3872
+ if (resolved.error && !encodedASCIIHost) {
3873
+ throw new Error(resolved.error);
3874
+ }
3659
3875
  schemelessOptions.skipEscape = true;
3660
3876
  return serialize(resolved, schemelessOptions);
3661
3877
  }
@@ -3715,7 +3931,7 @@ var require_fast_uri2 = __commonJS({
3715
3931
  function equal(uriA, uriB, options) {
3716
3932
  const normalizedA = normalizeComparableURI(uriA, options);
3717
3933
  const normalizedB = normalizeComparableURI(uriB, options);
3718
- return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
3934
+ return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA === normalizedB;
3719
3935
  }
3720
3936
  function serialize(cmpts, opts) {
3721
3937
  const component = {
@@ -3736,19 +3952,22 @@ var require_fast_uri2 = __commonJS({
3736
3952
  };
3737
3953
  const options = Object.assign({}, opts);
3738
3954
  const uriTokens = [];
3955
+ if (component.scheme) {
3956
+ component.scheme = decodeValidScheme(component.scheme);
3957
+ }
3739
3958
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
3740
3959
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
3960
+ const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
3961
+ const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
3741
3962
  if (component.path !== void 0) {
3742
3963
  if (!options.skipEscape) {
3743
- component.path = escapePreservingEscapes(component.path);
3744
- if (component.scheme !== void 0) {
3745
- component.path = component.path.split("%3A").join(":");
3746
- }
3964
+ component.path = serializePathEncoding(component.path, pathNoScheme);
3747
3965
  } else {
3748
3966
  component.path = normalizePercentEncoding(component.path);
3749
3967
  }
3750
3968
  }
3751
3969
  if (options.reference !== "suffix" && component.scheme) {
3970
+ component.scheme = decodeValidScheme(component.scheme);
3752
3971
  uriTokens.push(component.scheme, ":");
3753
3972
  }
3754
3973
  const authority = recomposeAuthority(component);
@@ -3766,16 +3985,19 @@ var require_fast_uri2 = __commonJS({
3766
3985
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
3767
3986
  s = removeDotSegments(s);
3768
3987
  }
3988
+ if (pathNoScheme) {
3989
+ s = serializePathEncoding(s, true);
3990
+ }
3769
3991
  if (authority === void 0 && s[0] === "/" && s[1] === "/") {
3770
3992
  s = "/%2F" + s.slice(2);
3771
3993
  }
3772
3994
  uriTokens.push(s);
3773
3995
  }
3774
3996
  if (component.query !== void 0) {
3775
- uriTokens.push("?", component.query);
3997
+ uriTokens.push("?", encodeQuery(component.query));
3776
3998
  }
3777
3999
  if (component.fragment !== void 0) {
3778
- uriTokens.push("#", component.fragment);
4000
+ uriTokens.push("#", encodeFragment(component.fragment));
3779
4001
  }
3780
4002
  return uriTokens.join("");
3781
4003
  }
@@ -3791,6 +4013,32 @@ var require_fast_uri2 = __commonJS({
3791
4013
  }
3792
4014
  return void 0;
3793
4015
  }
4016
+ function hasMalformedPercentEncoding(component) {
4017
+ if (component === void 0) return false;
4018
+ let percent = component.indexOf("%");
4019
+ while (percent !== -1) {
4020
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
4021
+ return true;
4022
+ }
4023
+ percent = component.indexOf("%", percent + 3);
4024
+ }
4025
+ return false;
4026
+ }
4027
+ function hasMalformedComponentPercentEncoding(matches) {
4028
+ const host = matches[4];
4029
+ return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !(host[0] === "[" && host[host.length - 1] === "]") && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
4030
+ }
4031
+ function canonicalizeHost(parsed, options, schemeHandler, isIP) {
4032
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && parsed.host[0] !== "[" && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
4033
+ try {
4034
+ parsed.host = new URL("http://" + parsed.host).hostname;
4035
+ } catch (e) {
4036
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
4037
+ return true;
4038
+ }
4039
+ }
4040
+ return false;
4041
+ }
3794
4042
  function parseWithStatus(uri, opts) {
3795
4043
  const options = Object.assign({}, opts);
3796
4044
  const parsed = {
@@ -3803,6 +4051,11 @@ var require_fast_uri2 = __commonJS({
3803
4051
  fragment: void 0
3804
4052
  };
3805
4053
  let malformedAuthorityOrPort = false;
4054
+ let malformedPercentEncoding = false;
4055
+ let malformedSchemeSpecific = false;
4056
+ let malformedHost = false;
4057
+ let malformedIPLiteral = false;
4058
+ let malformedScheme = false;
3806
4059
  let isIP = false;
3807
4060
  if (options.reference === "suffix") {
3808
4061
  if (options.scheme) {
@@ -3839,6 +4092,19 @@ var require_fast_uri2 = __commonJS({
3839
4092
  parsed.path = matches[6] || "";
3840
4093
  parsed.query = matches[7];
3841
4094
  parsed.fragment = matches[8];
4095
+ if (parsed.scheme !== void 0) {
4096
+ const decodedScheme = unescape(parsed.scheme);
4097
+ if (VALID_SCHEME.test(decodedScheme)) {
4098
+ parsed.scheme = decodedScheme.toLowerCase();
4099
+ } else {
4100
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
4101
+ malformedScheme = true;
4102
+ }
4103
+ }
4104
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
4105
+ if (malformedPercentEncoding) {
4106
+ parsed.error = parsed.error || "URI contains malformed percent-encoding.";
4107
+ }
3842
4108
  if (isNaN(parsed.port)) {
3843
4109
  parsed.port = matches[5];
3844
4110
  }
@@ -3850,9 +4116,15 @@ var require_fast_uri2 = __commonJS({
3850
4116
  if (parsed.host) {
3851
4117
  const ipv4result = isIPv4(parsed.host);
3852
4118
  if (ipv4result === false) {
4119
+ const bracketedIPLiteral = parsed.host[0] === "[" && parsed.host[parsed.host.length - 1] === "]";
3853
4120
  const ipv6result = normalizeIPv6(parsed.host);
3854
- parsed.host = ipv6result.host.toLowerCase();
3855
- isIP = ipv6result.isIPV6;
4121
+ isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
4122
+ malformedIPLiteral = 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
+ }
3856
4128
  } else {
3857
4129
  isIP = true;
3858
4130
  }
@@ -3870,42 +4142,34 @@ var require_fast_uri2 = __commonJS({
3870
4142
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
3871
4143
  }
3872
4144
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
3873
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
3874
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
3875
- try {
3876
- parsed.host = new URL("http://" + parsed.host).hostname;
3877
- } catch (e) {
3878
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3879
- }
3880
- }
3881
- }
4145
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
3882
4146
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
3883
4147
  if (uri.indexOf("%") !== -1) {
3884
- if (parsed.scheme !== void 0) {
3885
- parsed.scheme = unescape(parsed.scheme);
3886
- }
3887
- if (parsed.host !== void 0) {
3888
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
4148
+ if (parsed.host !== void 0 && !malformedIPLiteral) {
4149
+ const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4150
+ parsed.host = reescapeHostDelimiters(host, isIP);
3889
4151
  }
3890
4152
  }
3891
4153
  if (parsed.path) {
3892
4154
  parsed.path = normalizePathEncoding(parsed.path);
3893
4155
  }
4156
+ if (parsed.query) {
4157
+ parsed.query = normalizeQueryFragmentEncoding(parsed.query);
4158
+ }
3894
4159
  if (parsed.fragment) {
3895
- try {
3896
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3897
- } catch {
3898
- parsed.error = parsed.error || "URI malformed";
3899
- }
4160
+ parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
3900
4161
  }
3901
4162
  }
3902
4163
  if (schemeHandler && schemeHandler.parse) {
3903
4164
  schemeHandler.parse(parsed, options);
4165
+ if (schemeHandler === SCHEMES.urn && parsed.nid === void 0) {
4166
+ malformedSchemeSpecific = true;
4167
+ }
3904
4168
  }
3905
4169
  } else {
3906
4170
  parsed.error = parsed.error || "URI can not be parsed.";
3907
4171
  }
3908
- return { parsed, malformedAuthorityOrPort };
4172
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
3909
4173
  }
3910
4174
  function parse3(uri, opts) {
3911
4175
  return parseWithStatus(uri, opts).parsed;
@@ -3914,20 +4178,28 @@ var require_fast_uri2 = __commonJS({
3914
4178
  return normalizeStringWithStatus(uri, opts).normalized;
3915
4179
  }
3916
4180
  function normalizeStringWithStatus(uri, opts) {
3917
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
4181
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
3918
4182
  return {
3919
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
3920
- malformedAuthorityOrPort
4183
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
4184
+ malformedAuthorityOrPort,
4185
+ malformedPercentEncoding,
4186
+ malformedSchemeSpecific,
4187
+ malformedHost,
4188
+ malformedScheme
3921
4189
  };
3922
4190
  }
3923
4191
  function normalizeComparableURI(uri, opts) {
3924
- if (typeof uri === "string") {
3925
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
3926
- return malformedAuthorityOrPort ? void 0 : normalized;
4192
+ if (typeof uri !== "string" && typeof uri !== "object") {
4193
+ return void 0;
3927
4194
  }
3928
- if (typeof uri === "object") {
3929
- return serialize(uri, opts);
4195
+ let value;
4196
+ try {
4197
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
4198
+ } catch {
4199
+ return void 0;
3930
4200
  }
4201
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
4202
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? void 0 : normalized;
3931
4203
  }
3932
4204
  var fastUri = {
3933
4205
  SCHEMES,
@@ -37072,13 +37344,13 @@ var HonoRequest = class {
37072
37344
  return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
37073
37345
  }
37074
37346
  #getDecodedParam(key) {
37075
- const paramKey = this.#matchResult[0][this.routeIndex][1][key];
37347
+ const paramKey = this.#matchResult[0][this.routeIndex]?.[1][key];
37076
37348
  const param = this.#getParamValue(paramKey);
37077
37349
  return param && tryDecodeURIComponent(param);
37078
37350
  }
37079
37351
  #getAllDecodedParams() {
37080
37352
  const decoded = {};
37081
- const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
37353
+ const keys = Object.keys(this.#matchResult[0][this.routeIndex]?.[1] ?? {});
37082
37354
  for (const key of keys) {
37083
37355
  const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
37084
37356
  if (value !== void 0) {
@@ -38165,6 +38437,9 @@ var Hono = class _Hono {
38165
38437
  };
38166
38438
  };
38167
38439
 
38440
+ // node_modules/hono/dist/router/utils.js
38441
+ var createNullObject = () => /* @__PURE__ */ Object.create(null);
38442
+
38168
38443
  // node_modules/hono/dist/router/reg-exp-router/matcher.js
38169
38444
  var emptyParam = [];
38170
38445
  function match(method, path) {
@@ -38215,7 +38490,7 @@ var Node = class _Node {
38215
38490
  // handler index of a dynamic path, or -1 for a static path terminal
38216
38491
  #index;
38217
38492
  #varIndex;
38218
- #children = /* @__PURE__ */ Object.create(null);
38493
+ #children = createNullObject();
38219
38494
  insert(tokens, index, paramMap, context2, isStatic) {
38220
38495
  let node = this;
38221
38496
  for (let i = 0, len = tokens.length; i < len; i++) {
@@ -38299,7 +38574,7 @@ var Trie = class {
38299
38574
  #root = new Node();
38300
38575
  #index = 0;
38301
38576
  // dynamic path -> [handler index, param assoc]; static paths are not registered
38302
- paths = /* @__PURE__ */ Object.create(null);
38577
+ paths = createNullObject();
38303
38578
  insert(path, isStatic) {
38304
38579
  if (isStatic) {
38305
38580
  this.#root.insert(path.split(""), 0, [], this.#context, true);
@@ -38358,22 +38633,16 @@ var Trie = class {
38358
38633
  };
38359
38634
 
38360
38635
  // node_modules/hono/dist/router/reg-exp-router/router.js
38361
- var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
38636
+ var wildcardRegExpCache = createNullObject();
38362
38637
  function buildWildcardRegExp(path) {
38363
38638
  return wildcardRegExpCache[path] ??= new RegExp(
38364
- path === "*" ? "" : `^${path.replace(
38365
- /\/\*$|([.\\+*[^\]$()])/g,
38366
- (_, metaChar) => metaChar ? `\\${metaChar}` : "(?:|/.*)"
38639
+ `^${path.replace(
38640
+ /\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g,
38641
+ (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ONLY_WILDCARD_REG_EXP_STR : `/:${LABEL_REG_EXP_STR}`
38367
38642
  )}$`
38368
38643
  );
38369
38644
  }
38370
- function clearWildcardRegExpCache() {
38371
- wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
38372
- }
38373
38645
  function findMiddleware(middleware, path) {
38374
- if (!middleware) {
38375
- return void 0;
38376
- }
38377
38646
  for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
38378
38647
  if (buildWildcardRegExp(k).test(path)) {
38379
38648
  return [...middleware[k]];
@@ -38387,8 +38656,8 @@ var RegExpRouter = class {
38387
38656
  #routes;
38388
38657
  #tries;
38389
38658
  constructor() {
38390
- this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
38391
- this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
38659
+ this.#middleware = { [METHOD_NAME_ALL]: createNullObject() };
38660
+ this.#routes = { [METHOD_NAME_ALL]: createNullObject() };
38392
38661
  this.#tries = { [METHOD_NAME_ALL]: new Trie() };
38393
38662
  }
38394
38663
  #insertPath(method, path) {
@@ -38401,117 +38670,86 @@ var RegExpRouter = class {
38401
38670
  add(method, path, handler) {
38402
38671
  const middleware = this.#middleware;
38403
38672
  const routes = this.#routes;
38404
- if (!middleware || !routes) {
38673
+ if (!middleware) {
38405
38674
  throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
38406
38675
  }
38407
38676
  if (!middleware[method]) {
38408
38677
  this.#tries[method] = new Trie();
38409
- [middleware, routes].forEach((handlerMap) => {
38410
- handlerMap[method] = /* @__PURE__ */ Object.create(null);
38411
- Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p) => {
38678
+ for (const handlerMap of [middleware, routes]) {
38679
+ handlerMap[method] = createNullObject();
38680
+ for (const p in handlerMap[METHOD_NAME_ALL]) {
38412
38681
  handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]];
38413
38682
  this.#insertPath(method, p);
38414
- });
38415
- });
38683
+ }
38684
+ }
38416
38685
  }
38417
38686
  if (path === "/*") {
38418
38687
  path = "*";
38419
38688
  }
38420
- const paramCount = (path.match(/\/:/g) || []).length;
38689
+ const methods = method === METHOD_NAME_ALL ? Object.keys(middleware) : [method];
38421
38690
  if (/\*$/.test(path)) {
38422
38691
  const re2 = buildWildcardRegExp(path);
38423
- Object.keys(middleware).forEach((m) => {
38424
- if ((method === METHOD_NAME_ALL || method === m) && !middleware[m][path]) {
38692
+ for (const m of methods) {
38693
+ if (!middleware[m][path]) {
38425
38694
  this.#insertPath(m, path);
38426
38695
  middleware[m][path] = findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
38427
38696
  }
38428
- });
38429
- Object.keys(middleware).forEach((m) => {
38430
- if (method === METHOD_NAME_ALL || method === m) {
38431
- Object.keys(middleware[m]).forEach((p) => {
38432
- re2.test(p) && middleware[m][p].push([handler, paramCount]);
38433
- });
38434
- }
38435
- });
38436
- Object.keys(routes).forEach((m) => {
38437
- if (method === METHOD_NAME_ALL || method === m) {
38438
- Object.keys(routes[m]).forEach(
38439
- (p) => re2.test(p) && routes[m][p].push([handler, paramCount])
38440
- );
38697
+ }
38698
+ for (const handlerMap of [middleware, routes]) {
38699
+ for (const m of methods) {
38700
+ for (const p in handlerMap[m]) {
38701
+ re2.test(p) && handlerMap[m][p].push([handler, path]);
38702
+ }
38441
38703
  }
38442
- });
38704
+ }
38443
38705
  return;
38444
38706
  }
38445
38707
  const paths = checkOptionalParameter(path) || [path];
38446
- for (let i = 0, len = paths.length; i < len; i++) {
38447
- const path2 = paths[i];
38448
- Object.keys(routes).forEach((m) => {
38449
- if (method === METHOD_NAME_ALL || method === m) {
38450
- if (!routes[m][path2]) {
38451
- this.#insertPath(m, path2);
38452
- routes[m][path2] = [
38453
- ...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || []
38454
- ];
38455
- }
38456
- routes[m][path2].push([handler, paramCount - len + i + 1]);
38708
+ for (const path2 of paths) {
38709
+ for (const m of methods) {
38710
+ if (!routes[m][path2]) {
38711
+ this.#insertPath(m, path2);
38712
+ routes[m][path2] = findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || [];
38457
38713
  }
38458
- });
38714
+ routes[m][path2].push([handler, path2]);
38715
+ }
38459
38716
  }
38460
38717
  }
38461
38718
  match = match;
38462
38719
  buildAllMatchers() {
38463
- const matchers = /* @__PURE__ */ Object.create(null);
38464
- Object.keys(this.#routes).concat(Object.keys(this.#middleware)).forEach((method) => {
38465
- matchers[method] ||= this.#buildMatcher(method);
38466
- });
38720
+ const matchers = createNullObject();
38721
+ for (const method of Object.keys(this.#routes)) {
38722
+ matchers[method] = this.#buildMatcher(method);
38723
+ }
38467
38724
  this.#middleware = this.#routes = this.#tries = void 0;
38468
- clearWildcardRegExpCache();
38725
+ wildcardRegExpCache = createNullObject();
38469
38726
  return matchers;
38470
38727
  }
38471
38728
  #buildMatcher(method) {
38472
38729
  const middleware = this.#middleware[method];
38473
38730
  const routes = this.#routes[method];
38474
38731
  const trie = this.#tries[method];
38475
- const staticMap = /* @__PURE__ */ Object.create(null);
38732
+ const staticMap = createNullObject();
38476
38733
  const handlerData = [];
38477
- [middleware, routes].forEach((r) => {
38734
+ const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
38735
+ for (const r of [middleware, routes]) {
38478
38736
  for (const path in r) {
38479
38737
  const handlers = r[path];
38480
38738
  const pathData = trie.paths[path];
38481
38739
  if (!pathData) {
38482
- staticMap[path] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
38483
- continue;
38484
- }
38485
- const paramAssoc = pathData[1];
38486
- handlerData[pathData[0]] = handlers.map(([h, paramCount]) => {
38487
- const paramIndexMap = /* @__PURE__ */ Object.create(null);
38488
- paramCount -= 1;
38489
- for (; paramCount >= 0; paramCount--) {
38490
- const [key, value] = paramAssoc[paramCount];
38491
- paramIndexMap[key] = value;
38492
- }
38493
- return [h, paramIndexMap];
38494
- });
38495
- }
38496
- });
38497
- const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
38498
- for (let i = 0, len = handlerData.length; i < len; i++) {
38499
- for (let j = 0, len2 = handlerData[i].length; j < len2; j++) {
38500
- const map = handlerData[i][j]?.[1];
38501
- if (!map) {
38740
+ staticMap[path] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
38502
38741
  continue;
38503
38742
  }
38504
- const keys = Object.keys(map);
38505
- for (let k = 0, len3 = keys.length; k < len3; k++) {
38506
- map[keys[k]] = paramReplacementMap[map[keys[k]]];
38507
- }
38743
+ handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [
38744
+ h,
38745
+ trie.paths[handlerPath][1].reduceRight((map, [key], i) => {
38746
+ map[key] = paramReplacementMap[pathData[1][i][1]];
38747
+ return map;
38748
+ }, createNullObject())
38749
+ ]);
38508
38750
  }
38509
38751
  }
38510
- const handlerMap = [];
38511
- for (const i in indexReplacementMap) {
38512
- handlerMap[i] = handlerData[indexReplacementMap[i]];
38513
- }
38514
- return [regexp, handlerMap, staticMap];
38752
+ return [regexp, indexReplacementMap.map((i) => handlerData[i]), staticMap];
38515
38753
  }
38516
38754
  };
38517
38755
 
@@ -38571,11 +38809,11 @@ var SmartRouter = class {
38571
38809
  };
38572
38810
 
38573
38811
  // node_modules/hono/dist/router/trie-router/node.js
38574
- var emptyParams = /* @__PURE__ */ Object.create(null);
38812
+ var emptyParams = createNullObject();
38575
38813
  var order = 0;
38576
38814
  var Node2 = class _Node2 {
38577
38815
  #methods = [];
38578
- #children = /* @__PURE__ */ Object.create(null);
38816
+ #children = createNullObject();
38579
38817
  #patterns = [];
38580
38818
  #pattern;
38581
38819
  #params = emptyParams;
@@ -38612,7 +38850,7 @@ var Node2 = class _Node2 {
38612
38850
  const m = node.#methods[i];
38613
38851
  const handlerSet = m[method] || m[METHOD_NAME_ALL];
38614
38852
  if (handlerSet) {
38615
- handlerSet.params = /* @__PURE__ */ Object.create(null);
38853
+ handlerSet.params = createNullObject();
38616
38854
  handlerSets.push(handlerSet);
38617
38855
  for (let i2 = 0, len2 = handlerSet.possibleKeys.length; i2 < len2; i2++) {
38618
38856
  const key = handlerSet.possibleKeys[i2];
@@ -40550,33 +40788,11 @@ function isObject2(input) {
40550
40788
  return false;
40551
40789
  }
40552
40790
  const prototype = Object.getPrototypeOf(input);
40553
- if (prototype === null) {
40554
- return true;
40555
- }
40556
- let proto = prototype;
40557
- while (Object.getPrototypeOf(proto) !== null) {
40558
- proto = Object.getPrototypeOf(proto);
40559
- }
40560
- return prototype === proto;
40791
+ return prototype === null || Object.getPrototypeOf(prototype) === null;
40561
40792
  }
40562
- function isDisjoint(...headers) {
40563
- const parameters = /* @__PURE__ */ new Set();
40564
- for (const header of headers) {
40565
- if (!header)
40566
- continue;
40567
- for (const parameter of Object.keys(header)) {
40568
- if (parameters.has(parameter)) {
40569
- return false;
40570
- }
40571
- parameters.add(parameter);
40572
- }
40573
- }
40574
- return true;
40793
+ function isJwkSet(input) {
40794
+ return isObject2(input) && Array.isArray(input.keys) && Array.from(input.keys).every(isObject2);
40575
40795
  }
40576
- var isJWK = (key) => isObject2(key) && typeof key.kty === "string";
40577
- var isPrivateJWK = (key) => key.kty !== "oct" && (key.kty === "AKP" && typeof key.priv === "string" || typeof key.d === "string");
40578
- var isPublicJWK = (key) => key.kty !== "oct" && key.d === void 0 && key.priv === void 0;
40579
- var isSecretJWK = (key) => key.kty === "oct" && typeof key.k === "string";
40580
40796
 
40581
40797
  // node_modules/jose/dist/webapi/lib/helpers.js
40582
40798
  var unprotected = Symbol();
@@ -40625,6 +40841,26 @@ async function jwkToKey(entry, jwk) {
40625
40841
  return crypto.subtle.importKey("jwk", keyData, algorithm, jwk.ext ?? !isPrivate, jwk.key_ops ?? entry.usages[isPrivate ? 1 : 0]);
40626
40842
  }
40627
40843
 
40844
+ // node_modules/jose/dist/webapi/lib/jwk_metadata.js
40845
+ function snapshotJwk(jwk) {
40846
+ return { __proto__: null, ...jwk };
40847
+ }
40848
+ function normalizeJwk(jwk) {
40849
+ const normalized = snapshotJwk(jwk);
40850
+ if (normalized.ext !== void 0 && typeof normalized.ext !== "boolean") {
40851
+ throw new TypeError('"ext" (Extractable) Parameter must be a boolean');
40852
+ }
40853
+ if (normalized.key_ops !== void 0) {
40854
+ const value = normalized.key_ops;
40855
+ const keyOps = Array.isArray(value) ? [...value] : void 0;
40856
+ if (!keyOps || keyOps.some((operation) => typeof operation !== "string") || new Set(keyOps).size !== keyOps.length) {
40857
+ throw new TypeError('"key_ops" (Key Operations) Parameter must be an array of unique strings');
40858
+ }
40859
+ normalized.key_ops = keyOps;
40860
+ }
40861
+ return normalized;
40862
+ }
40863
+
40628
40864
  // node_modules/jose/dist/webapi/lib/key.js
40629
40865
  var tag = (key) => key[Symbol.toStringTag];
40630
40866
  var jwkMatchesOp = (entry, key, usage) => {
@@ -40650,12 +40886,17 @@ function checkKeyType(entry, key, usage) {
40650
40886
  const privateKey = usage === "decrypt" || usage === "sign";
40651
40887
  if (secret && key instanceof Uint8Array)
40652
40888
  return [BYTES, key];
40653
- if (isJWK(key)) {
40654
- if (secret ? !isSecretJWK(key) : !(privateKey ? isPrivateJWK(key) : isPublicJWK(key))) {
40889
+ if (isObject2(key)) {
40890
+ const normalized = normalizeJwk(key);
40891
+ if (typeof normalized.kty !== "string") {
40892
+ throw new TypeError(secret ? withAlg(alg, key, "CryptoKey", "KeyObject", "JSON Web Key", "Uint8Array") : withAlg(alg, key, "CryptoKey", "KeyObject", "JSON Web Key"));
40893
+ }
40894
+ const valid = secret ? normalized.kty === "oct" && typeof normalized.k === "string" : normalized.kty !== "oct" && (privateKey ? normalized.kty === "AKP" && typeof normalized.priv === "string" || typeof normalized.d === "string" : normalized.d === void 0 && normalized.priv === void 0);
40895
+ if (!valid) {
40655
40896
  throw new TypeError(secret ? `JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present` : `JSON Web Key for this operation must be a ${privateKey ? "private" : "public"} JWK`);
40656
40897
  }
40657
- jwkMatchesOp(entry, key, usage);
40658
- return [JWK, key];
40898
+ jwkMatchesOp(entry, normalized, usage);
40899
+ return [JWK, key, normalized];
40659
40900
  }
40660
40901
  if (!isKeyLike(key)) {
40661
40902
  throw new TypeError(secret ? withAlg(alg, key, "CryptoKey", "KeyObject", "JSON Web Key", "Uint8Array") : withAlg(alg, key, "CryptoKey", "KeyObject", "JSON Web Key"));
@@ -40694,7 +40935,7 @@ function cached2(key, alg, value) {
40694
40935
  if (entry) {
40695
40936
  entry[alg] = value;
40696
40937
  } else {
40697
- cache.set(key, { __proto__: null, [alg]: value });
40938
+ cache.set(key, { [alg]: value });
40698
40939
  }
40699
40940
  }
40700
40941
  return value ?? entry?.[alg];
@@ -40719,8 +40960,9 @@ async function prepareKey(entry, key, usage) {
40719
40960
  return tagged[1];
40720
40961
  case JWK: {
40721
40962
  const key2 = tagged[1];
40722
- if (key2.k) {
40723
- return decode2(key2.k);
40963
+ const normalized = tagged[2];
40964
+ if (normalized.kty === "oct") {
40965
+ return decode2(normalized.k);
40724
40966
  }
40725
40967
  if (!Object.isFrozen(key2)) {
40726
40968
  const { key_ops } = key2;
@@ -40728,7 +40970,7 @@ async function prepareKey(entry, key, usage) {
40728
40970
  Object.freeze(key_ops);
40729
40971
  Object.freeze(key2);
40730
40972
  }
40731
- return handleJWK(key2, key2, entry);
40973
+ return handleJWK(key2, normalized, entry);
40732
40974
  }
40733
40975
  case KEYOBJECT: {
40734
40976
  const keyObject = tagged[1];
@@ -40787,6 +41029,16 @@ function validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader,
40787
41029
  }
40788
41030
  return protectedHeader.crit;
40789
41031
  }
41032
+ function validateB64(protectedHeader, extensions) {
41033
+ if (extensions.includes("b64")) {
41034
+ const b64 = protectedHeader.b64;
41035
+ if (typeof b64 !== "boolean") {
41036
+ throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean');
41037
+ }
41038
+ return b64;
41039
+ }
41040
+ return true;
41041
+ }
40790
41042
 
40791
41043
  // node_modules/jose/dist/webapi/lib/signing.js
40792
41044
  async function getSigKey(entry, key, usage) {
@@ -40886,64 +41138,42 @@ function jwsAlgorithm(alg) {
40886
41138
  function prepareVerify(options) {
40887
41139
  return [options && validateAlgorithms("algorithms", options.algorithms), options?.crit];
40888
41140
  }
40889
- async function verifySignature(jws, shared, key) {
40890
- const { protected: encodedProtected, header, payload: inputPayload } = jws;
40891
- let parsedProt = {};
40892
- if (encodedProtected) {
40893
- parsedProt = parseJoseHeader(encodedProtected, JWSInvalid, "JWS Protected Header is invalid");
40894
- }
40895
- let joseHeader;
40896
- if (header !== void 0) {
40897
- if (!isDisjoint(parsedProt, header)) {
40898
- throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");
40899
- }
40900
- joseHeader = { ...parsedProt, ...header };
40901
- } else {
40902
- joseHeader = parsedProt;
40903
- }
40904
- const extensions = validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], parsedProt, joseHeader);
40905
- let b64 = true;
40906
- if (extensions.includes("b64")) {
40907
- b64 = parsedProt.b64;
40908
- if (typeof b64 !== "boolean") {
40909
- throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean');
40910
- }
40911
- }
40912
- const { alg } = joseHeader;
41141
+ function parseProtectedHeader(encodedProtected, parsedProtected = encodedProtected === void 0 ? {} : parseJoseHeader(encodedProtected, JWSInvalid, "JWS Protected Header is invalid")) {
41142
+ return parsedProtected;
41143
+ }
41144
+ function validateJwsHeaders(parsedProt, joseHeader, shared) {
41145
+ const b64 = validateB64(parsedProt, validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], parsedProt, joseHeader));
41146
+ const alg = joseHeader.alg;
40913
41147
  if (typeof alg !== "string" || !alg) {
40914
41148
  throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
40915
41149
  }
40916
41150
  if (shared[0] && !shared[0].has(alg)) {
40917
41151
  throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
40918
41152
  }
40919
- if (b64) {
40920
- if (typeof inputPayload !== "string") {
40921
- throw new JWSInvalid("JWS Payload must be a string");
40922
- }
40923
- } else if (typeof inputPayload !== "string" && !(inputPayload instanceof Uint8Array)) {
40924
- throw new JWSInvalid("JWS Payload must be a string or an Uint8Array instance");
41153
+ return [b64, alg];
41154
+ }
41155
+ function encodeCompactUnencodedPayload(payload) {
41156
+ try {
41157
+ return encode2(payload);
41158
+ } catch {
41159
+ throw new JWSInvalid("JWS Compact Serialization payload must use only ASCII characters");
40925
41160
  }
41161
+ }
41162
+ async function verifyPrepared(jws, shared, key, encodedProtected, parsedProt, alg, signingPayload) {
40926
41163
  let resolvedKey = false;
40927
41164
  if (typeof key === "function") {
40928
41165
  key = await key(parsedProt, jws);
40929
41166
  resolvedKey = true;
40930
41167
  }
41168
+ const b64 = typeof signingPayload === "string";
40931
41169
  const entry = jwsAlgorithm(alg);
40932
- const data = concat(encodedProtected !== void 0 ? encode2(encodedProtected) : new Uint8Array(), encode2("."), typeof inputPayload === "string" ? b64 ? shared[2] ??= encodeBase64url(inputPayload, "payload", JWSInvalid) : encoder.encode(inputPayload) : inputPayload);
41170
+ const data = concat(encodedProtected !== void 0 ? encode2(encodedProtected) : new Uint8Array(), encode2("."), b64 ? shared[2] ??= encodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload);
40933
41171
  const signature = decodeBase64url(jws.signature, "signature", JWSInvalid);
40934
41172
  const k = await prepareKey(entry, key, "verify");
40935
- const verified = await verify(entry, k, signature, data);
40936
- if (!verified) {
41173
+ if (!await verify(entry, k, signature, data)) {
40937
41174
  throw new JWSSignatureVerificationFailed();
40938
41175
  }
40939
- let payload;
40940
- if (b64) {
40941
- payload = decodeBase64url(inputPayload, "payload", JWSInvalid);
40942
- } else if (typeof inputPayload === "string") {
40943
- payload = encoder.encode(inputPayload);
40944
- } else {
40945
- payload = inputPayload;
40946
- }
41176
+ const payload = b64 ? decodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload;
40947
41177
  return [payload, parsedProt, b64, k, resolvedKey];
40948
41178
  }
40949
41179
  async function verifyCompact(jws, shared, key) {
@@ -40957,7 +41187,11 @@ async function verifyCompact(jws, shared, key) {
40957
41187
  if (length !== 3) {
40958
41188
  throw new JWSInvalid("Invalid Compact JWS");
40959
41189
  }
40960
- return verifySignature({ payload, protected: protectedHeader, signature }, shared, key);
41190
+ const compactJws = { payload, protected: protectedHeader, signature };
41191
+ const parsedProt = parseProtectedHeader(protectedHeader);
41192
+ const [b64, alg] = validateJwsHeaders(parsedProt, parsedProt, shared);
41193
+ const signingPayload = b64 ? payload : encodeCompactUnencodedPayload(payload);
41194
+ return verifyPrepared(compactJws, shared, key, protectedHeader, parsedProt, alg, signingPayload);
40961
41195
  }
40962
41196
 
40963
41197
  // node_modules/jose/dist/webapi/lib/jwt_claims_set.js
@@ -40972,13 +41206,22 @@ var multipliers = {
40972
41206
  };
40973
41207
  var REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i;
40974
41208
  var checkFailed = "check_failed";
41209
+ function invalidDuration() {
41210
+ throw new TypeError("Invalid time period format");
41211
+ }
40975
41212
  function secs(str) {
41213
+ if (typeof str !== "string") {
41214
+ invalidDuration();
41215
+ }
40976
41216
  const matched = REGEX.exec(str);
40977
41217
  if (!matched || matched[4] && matched[1]) {
40978
- throw new TypeError("Invalid time period format");
41218
+ invalidDuration();
40979
41219
  }
40980
41220
  const value = parseFloat(matched[2]);
40981
41221
  const numericDate = Math.round(value * multipliers[matched[3][0].toLowerCase()]);
41222
+ if (!Number.isFinite(numericDate)) {
41223
+ invalidDuration();
41224
+ }
40982
41225
  if (matched[1] === "-" || matched[4] === "ago") {
40983
41226
  return -numericDate;
40984
41227
  }
@@ -40991,10 +41234,8 @@ function validateInput(label, input) {
40991
41234
  return input;
40992
41235
  }
40993
41236
  var normalizeTyp = (value) => {
40994
- if (value.includes("/")) {
40995
- return value.toLowerCase();
40996
- }
40997
- return `application/${value.toLowerCase()}`;
41237
+ const normalized = value.toLowerCase();
41238
+ return value.includes("/") ? normalized : `application/${normalized}`;
40998
41239
  };
40999
41240
  var checkAudiencePresence = (audPayload, audOption) => {
41000
41241
  if (typeof audPayload === "string") {
@@ -41027,7 +41268,7 @@ function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
41027
41268
  throw new JWTInvalid("JWT Claims Set must be a top-level JSON object");
41028
41269
  }
41029
41270
  const { typ } = options;
41030
- if (typ && (typeof protectedHeader.typ !== "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) {
41271
+ if (typ !== void 0 && (typeof protectedHeader.typ !== "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) {
41031
41272
  throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, "typ", checkFailed);
41032
41273
  }
41033
41274
  const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options;
@@ -41066,7 +41307,7 @@ function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
41066
41307
  }
41067
41308
  validateInput("clockTolerance option", tolerance);
41068
41309
  const { currentDate } = options;
41069
- const now = validateInput("currentDate option", epoch(currentDate || /* @__PURE__ */ new Date()));
41310
+ const now = validateInput("currentDate option", epoch(currentDate === void 0 ? /* @__PURE__ */ new Date() : currentDate));
41070
41311
  const iat = validateNumericDate(payload, "iat", maxTokenAge !== void 0);
41071
41312
  const nbf = validateNumericDate(payload, "nbf");
41072
41313
  if (nbf !== void 0) {
@@ -41082,11 +41323,11 @@ function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
41082
41323
  }
41083
41324
  if (maxTokenAge !== void 0) {
41084
41325
  const age = now - iat;
41085
- const max = typeof maxTokenAge === "number" ? maxTokenAge : secs(maxTokenAge);
41326
+ const max = validateInput("maxTokenAge option", typeof maxTokenAge === "number" ? maxTokenAge : secs(maxTokenAge));
41086
41327
  if (age - tolerance > max) {
41087
41328
  throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, "iat", checkFailed);
41088
41329
  }
41089
- if (age < 0 - tolerance) {
41330
+ if (age < -tolerance) {
41090
41331
  throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, "iat", checkFailed);
41091
41332
  }
41092
41333
  }
@@ -41108,74 +41349,61 @@ async function jwtVerify(jwt, key, options) {
41108
41349
  }
41109
41350
 
41110
41351
  // node_modules/jose/dist/webapi/jwks/local.js
41111
- function signatureAlgorithm(alg) {
41112
- const entry = typeof alg === "string" ? JWS[alg] : void 0;
41113
- if (!entry || entry.secret) {
41114
- throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
41115
- }
41116
- return entry;
41352
+ function isUsableJWK(jwk, entry, alg, kid) {
41353
+ const { kty, key_ops, ext, kid: jwkKid, alg: jwkAlg, use, crv } = snapshotJwk(jwk);
41354
+ const keyOps = Array.isArray(key_ops) ? [...key_ops] : key_ops;
41355
+ return (ext === void 0 || typeof ext === "boolean") && (keyOps === void 0 || Array.isArray(keyOps) && keyOps.every((operation, index) => typeof operation === "string" && keyOps.indexOf(operation) === index) && keyOps.includes("verify")) && entry.kty.includes(kty) && (kid === void 0 || typeof kid === "string" && kid === jwkKid) && (jwkAlg === void 0 ? kty !== "AKP" : alg === jwkAlg) && (use === void 0 || use === "sig") && (!entry.crv || crv === entry.crv);
41117
41356
  }
41118
- function isJWKSLike(jwks) {
41119
- if (!jwks || typeof jwks !== "object") {
41120
- return false;
41357
+ async function importWithAlgCache(cache2, jwk, entry) {
41358
+ const cached3 = cache2.get(jwk) || cache2.set(jwk, {}).get(jwk);
41359
+ const { alg } = entry;
41360
+ if (cached3[alg] === void 0) {
41361
+ const key = await jwkToKey(entry, { ...jwk, alg, ext: true });
41362
+ if (key.type !== "public") {
41363
+ throw new JWKSInvalid("JSON Web Key Set members must be public keys");
41364
+ }
41365
+ cached3[alg] = key;
41121
41366
  }
41122
- const { keys } = jwks;
41123
- return Array.isArray(keys) && keys.every(isObject2);
41367
+ return cached3[alg];
41124
41368
  }
41125
- var LocalJWKSetImpl = class {
41126
- #jwks;
41127
- #cached = /* @__PURE__ */ new WeakMap();
41128
- constructor(jwks) {
41129
- if (!isJWKSLike(jwks)) {
41130
- throw new JWKSInvalid("JSON Web Key Set malformed");
41131
- }
41132
- this.#jwks = structuredClone(jwks);
41369
+ function createLocalJWKSet(jwks) {
41370
+ let snapshot;
41371
+ try {
41372
+ snapshot = structuredClone(jwks);
41373
+ } catch {
41133
41374
  }
41134
- jwks() {
41135
- return this.#jwks;
41375
+ if (!isJwkSet(snapshot)) {
41376
+ throw new JWKSInvalid("JSON Web Key Set malformed");
41136
41377
  }
41137
- async getKey(protectedHeader, token) {
41378
+ const cached3 = /* @__PURE__ */ new WeakMap();
41379
+ const localJWKSet = async (protectedHeader, token) => {
41138
41380
  const { alg, kid } = { ...protectedHeader, ...token?.header };
41139
- const entry = signatureAlgorithm(alg);
41140
- const candidates = this.#jwks.keys.filter((jwk2) => entry.kty.includes(jwk2.kty) && (typeof kid !== "string" || kid === jwk2.kid) && (!(typeof jwk2.alg === "string" || jwk2.kty === "AKP") || alg === jwk2.alg) && (typeof jwk2.use !== "string" || jwk2.use === "sig") && (!Array.isArray(jwk2.key_ops) || jwk2.key_ops.includes("verify")) && (!entry.crv || jwk2.crv === entry.crv));
41381
+ const entry = typeof alg === "string" ? JWS[alg] : void 0;
41382
+ if (!entry || entry.secret) {
41383
+ throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
41384
+ }
41385
+ const candidates = snapshot.keys.filter((jwk2) => isUsableJWK(jwk2, entry, alg, kid));
41141
41386
  const { 0: jwk, length } = candidates;
41142
- if (length === 0) {
41387
+ if (!length) {
41143
41388
  throw new JWKSNoMatchingKey();
41144
41389
  }
41145
41390
  if (length !== 1) {
41146
41391
  const error2 = new JWKSMultipleMatchingKeys();
41147
- const _cached = this.#cached;
41148
41392
  error2[Symbol.asyncIterator] = async function* () {
41149
41393
  for (const jwk2 of candidates) {
41150
41394
  try {
41151
- yield await importWithAlgCache(_cached, jwk2, entry);
41395
+ yield await importWithAlgCache(cached3, jwk2, entry);
41152
41396
  } catch {
41153
41397
  }
41154
41398
  }
41155
41399
  };
41156
41400
  throw error2;
41157
41401
  }
41158
- return importWithAlgCache(this.#cached, jwk, entry);
41159
- }
41160
- };
41161
- async function importWithAlgCache(cache2, jwk, entry) {
41162
- const cached3 = cache2.get(jwk) || cache2.set(jwk, { __proto__: null }).get(jwk);
41163
- if (cached3[entry.alg] === void 0) {
41164
- const key = await jwkToKey(entry, { ...jwk, alg: entry.alg, ext: true });
41165
- if (key.type !== "public") {
41166
- throw new JWKSInvalid("JSON Web Key Set members must be public keys");
41167
- }
41168
- cached3[entry.alg] = key;
41169
- }
41170
- return cached3[entry.alg];
41171
- }
41172
- function createLocalJWKSet(jwks) {
41173
- const set = new LocalJWKSetImpl(jwks);
41174
- const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
41175
- Object.defineProperty(localJWKSet, "jwks", {
41176
- value: () => structuredClone(set.jwks())
41402
+ return importWithAlgCache(cached3, jwk, entry);
41403
+ };
41404
+ return Object.defineProperty(localJWKSet, "jwks", {
41405
+ value: () => structuredClone(snapshot)
41177
41406
  });
41178
- return localJWKSet;
41179
41407
  }
41180
41408
 
41181
41409
  // node_modules/jose/dist/webapi/jwks/remote.js
@@ -41185,7 +41413,7 @@ function isCloudflareWorkers() {
41185
41413
  var USER_AGENT;
41186
41414
  if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
41187
41415
  const NAME = "jose";
41188
- const VERSION2 = "v6.2.9";
41416
+ const VERSION2 = "v6.2.10";
41189
41417
  USER_AGENT = `${NAME}/${VERSION2}`;
41190
41418
  }
41191
41419
  var customFetch = Symbol();
@@ -41211,130 +41439,112 @@ async function fetchJwks(url2, headers, signal, fetchImpl = fetch) {
41211
41439
  }
41212
41440
  }
41213
41441
  var jwksCache = Symbol();
41214
- function isFreshJwksCache(input, cacheMaxAge) {
41215
- if (typeof input !== "object" || input === null) {
41216
- return false;
41217
- }
41218
- if (!("uat" in input) || typeof input.uat !== "number" || Date.now() - input.uat >= cacheMaxAge) {
41219
- return false;
41220
- }
41221
- if (!("jwks" in input) || !isObject2(input.jwks) || !Array.isArray(input.jwks.keys) || !Array.prototype.every.call(input.jwks.keys, isObject2)) {
41222
- return false;
41442
+ function isFreshFor(timestamp, duration3) {
41443
+ return Number.isFinite(timestamp) && Date.now() < timestamp + duration3;
41444
+ }
41445
+ function validateDuration(value, fallback, option) {
41446
+ if (Number.isNaN(value)) {
41447
+ throw new TypeError(`"${option}" option must not be NaN`);
41223
41448
  }
41224
- return true;
41449
+ return typeof value === "number" ? value : fallback;
41225
41450
  }
41226
- var RemoteJWKSetImpl = class {
41227
- #url;
41228
- #timeoutDuration;
41229
- #cooldownDuration;
41230
- #cacheMaxAge;
41231
- #jwksTimestamp;
41232
- #pendingFetch;
41233
- #headers;
41234
- #customFetch;
41235
- #local;
41236
- #cache;
41237
- constructor(url2, options) {
41238
- if (!(url2 instanceof URL)) {
41239
- throw new TypeError("url must be an instance of URL");
41240
- }
41241
- this.#url = new URL(url2.href);
41242
- const opts = options ?? {};
41243
- this.#timeoutDuration = typeof opts.timeoutDuration === "number" ? opts.timeoutDuration : 5e3;
41244
- this.#cooldownDuration = typeof opts.cooldownDuration === "number" ? opts.cooldownDuration : 3e4;
41245
- this.#cacheMaxAge = typeof opts.cacheMaxAge === "number" ? opts.cacheMaxAge : 6e5;
41246
- this.#headers = new Headers(opts.headers);
41247
- if (USER_AGENT && !this.#headers.has("User-Agent")) {
41248
- this.#headers.set("User-Agent", USER_AGENT);
41249
- }
41250
- if (!this.#headers.has("accept")) {
41251
- this.#headers.set("accept", "application/json");
41252
- this.#headers.append("accept", "application/jwk-set+json");
41253
- }
41254
- this.#customFetch = opts[customFetch];
41255
- const cache2 = opts[jwksCache];
41256
- if (cache2 !== void 0) {
41257
- this.#cache = cache2;
41258
- if (isFreshJwksCache(cache2, this.#cacheMaxAge)) {
41259
- this.#jwksTimestamp = this.#cache.uat;
41260
- this.#local = createLocalJWKSet(this.#cache.jwks);
41261
- }
41262
- }
41263
- }
41264
- pendingFetch() {
41265
- return !!this.#pendingFetch;
41266
- }
41267
- #validFor(duration3) {
41268
- return typeof this.#jwksTimestamp === "number" && Date.now() < this.#jwksTimestamp + duration3;
41269
- }
41270
- coolingDown() {
41271
- return this.#validFor(this.#cooldownDuration);
41272
- }
41273
- fresh() {
41274
- return this.#validFor(this.#cacheMaxAge);
41275
- }
41276
- jwks() {
41277
- return this.#local?.jwks();
41278
- }
41279
- async getKey(protectedHeader, token) {
41280
- if (!this.#local || !this.fresh()) {
41281
- await this.reload();
41451
+ function createRemoteJWKSet(url2, options) {
41452
+ if (!(url2 instanceof URL)) {
41453
+ throw new TypeError("url must be an instance of URL");
41454
+ }
41455
+ const href = new URL(url2.href).href;
41456
+ const opts = options ?? {};
41457
+ const timeoutOption = opts.timeoutDuration;
41458
+ if (typeof timeoutOption === "number" && (!Number.isInteger(timeoutOption) || timeoutOption < 0)) {
41459
+ throw new TypeError('"timeoutDuration" option must be a non-negative integer');
41460
+ }
41461
+ const timeoutDuration = typeof timeoutOption === "number" ? timeoutOption : 5e3;
41462
+ const cooldownDuration = validateDuration(opts.cooldownDuration, 3e4, "cooldownDuration");
41463
+ const cacheMaxAge = validateDuration(opts.cacheMaxAge, 6e5, "cacheMaxAge");
41464
+ const headers = new Headers(opts.headers);
41465
+ if (USER_AGENT && !headers.has("User-Agent")) {
41466
+ headers.set("User-Agent", USER_AGENT);
41467
+ }
41468
+ if (!headers.has("accept")) {
41469
+ headers.set("accept", "application/json, application/jwk-set+json");
41470
+ }
41471
+ const fetchImpl = opts[customFetch];
41472
+ const cache2 = opts[jwksCache];
41473
+ let jwksTimestamp;
41474
+ let pendingFetch;
41475
+ let reloadSequence = 0;
41476
+ let appliedSequence = 0;
41477
+ let local;
41478
+ if (cache2 && typeof cache2 === "object") {
41479
+ const { uat, jwks } = cache2;
41480
+ if (isFreshFor(uat, cacheMaxAge) && isJwkSet(jwks)) {
41481
+ jwksTimestamp = uat;
41482
+ local = createLocalJWKSet(jwks);
41483
+ }
41484
+ }
41485
+ const reload = async () => {
41486
+ if (pendingFetch && isCloudflareWorkers()) {
41487
+ pendingFetch = void 0;
41488
+ }
41489
+ if (!pendingFetch) {
41490
+ const sequence = ++reloadSequence;
41491
+ const current = pendingFetch = fetchJwks(href, headers, AbortSignal.timeout(timeoutDuration), fetchImpl).then((json) => {
41492
+ const next = createLocalJWKSet(json);
41493
+ if (sequence <= appliedSequence) {
41494
+ return;
41495
+ }
41496
+ local = next;
41497
+ const updatedAt = Date.now();
41498
+ if (cache2) {
41499
+ cache2.uat = updatedAt;
41500
+ cache2.jwks = json;
41501
+ }
41502
+ jwksTimestamp = updatedAt;
41503
+ appliedSequence = sequence;
41504
+ }).finally(() => {
41505
+ if (pendingFetch === current) {
41506
+ pendingFetch = void 0;
41507
+ }
41508
+ });
41509
+ }
41510
+ await pendingFetch;
41511
+ };
41512
+ const remoteJWKSet = async (protectedHeader, token) => {
41513
+ if (!local || !isFreshFor(jwksTimestamp, cacheMaxAge)) {
41514
+ await reload();
41282
41515
  }
41283
41516
  try {
41284
- return await this.#local(protectedHeader, token);
41517
+ return await local(protectedHeader, token);
41285
41518
  } catch (err) {
41286
- if (err instanceof JWKSNoMatchingKey) {
41287
- if (this.coolingDown() === false) {
41288
- await this.reload();
41289
- return this.#local(protectedHeader, token);
41290
- }
41519
+ if (err instanceof JWKSNoMatchingKey && !isFreshFor(jwksTimestamp, cooldownDuration)) {
41520
+ await reload();
41521
+ return local(protectedHeader, token);
41291
41522
  }
41292
41523
  throw err;
41293
41524
  }
41294
- }
41295
- async reload() {
41296
- if (this.#pendingFetch && isCloudflareWorkers()) {
41297
- this.#pendingFetch = void 0;
41298
- }
41299
- this.#pendingFetch ||= fetchJwks(this.#url.href, this.#headers, AbortSignal.timeout(this.#timeoutDuration), this.#customFetch).then((json) => {
41300
- this.#local = createLocalJWKSet(json);
41301
- if (this.#cache) {
41302
- this.#cache.uat = Date.now();
41303
- this.#cache.jwks = json;
41304
- }
41305
- this.#jwksTimestamp = Date.now();
41306
- }).finally(() => {
41307
- this.#pendingFetch = void 0;
41308
- });
41309
- await this.#pendingFetch;
41310
- }
41311
- };
41312
- function createRemoteJWKSet(url2, options) {
41313
- const set = new RemoteJWKSetImpl(url2, options);
41314
- const remoteJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
41315
- Object.defineProperties(remoteJWKSet, {
41525
+ };
41526
+ return Object.defineProperties(remoteJWKSet, {
41316
41527
  coolingDown: {
41317
- get: () => set.coolingDown(),
41528
+ get: () => isFreshFor(jwksTimestamp, cooldownDuration),
41318
41529
  enumerable: true
41319
41530
  },
41320
41531
  fresh: {
41321
- get: () => set.fresh(),
41532
+ get: () => isFreshFor(jwksTimestamp, cacheMaxAge),
41322
41533
  enumerable: true
41323
41534
  },
41324
41535
  reload: {
41325
- value: () => set.reload(),
41536
+ value: reload,
41326
41537
  enumerable: true
41327
41538
  },
41328
41539
  reloading: {
41329
- get: () => set.pendingFetch(),
41540
+ get: () => !!pendingFetch,
41330
41541
  enumerable: true
41331
41542
  },
41332
41543
  jwks: {
41333
- value: () => set.jwks(),
41544
+ value: () => local?.jwks(),
41334
41545
  enumerable: true
41335
41546
  }
41336
41547
  });
41337
- return remoteJWKSet;
41338
41548
  }
41339
41549
 
41340
41550
  // node_modules/@casys/mcp-server/src/auth/provider.ts
@@ -46975,6 +47185,7 @@ var viewer = (name) => ({
46975
47185
  ui: { resourceUri: `ui://mcp-erpnext/${name}` }
46976
47186
  });
46977
47187
  var DOCLIST_META = viewer("doclist-viewer");
47188
+ var DOC_META = viewer("doc-viewer");
46978
47189
  var INVOICE_META = viewer("invoice-viewer");
46979
47190
  var STOCK_META = viewer("stock-viewer");
46980
47191
  var CHART_META = viewer("chart-viewer");
@@ -47074,6 +47285,115 @@ function stableStringify(value) {
47074
47285
  var DEFAULT_RETRY_STATUSES = [408, 429, 502, 503, 504];
47075
47286
  var DEFAULT_RETRY_METHODS = ["GET"];
47076
47287
  var DEFAULT_MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
47288
+ var DEFAULT_MAX_DOWNLOAD_BYTES = 10 * 1024 * 1024;
47289
+ var MAX_ERROR_BODY_BYTES = 64 * 1024;
47290
+ var ResponseBodyLimitError = class extends Error {
47291
+ constructor(label, size, limit) {
47292
+ super(
47293
+ `[FrappeClient] ${label} size ${size} bytes exceeds the ${limit}-byte limit`
47294
+ );
47295
+ this.name = "ResponseBodyLimitError";
47296
+ }
47297
+ };
47298
+ async function readResponseBytes(response, maxBytes, label) {
47299
+ const declaredLength = response.headers.get("content-length")?.trim();
47300
+ if (declaredLength && /^\d+$/.test(declaredLength)) {
47301
+ const size = Number(declaredLength);
47302
+ if (size > maxBytes) {
47303
+ try {
47304
+ await response.body?.cancel();
47305
+ } catch {
47306
+ }
47307
+ throw new ResponseBodyLimitError(label, size, maxBytes);
47308
+ }
47309
+ }
47310
+ if (!response.body) return new Uint8Array();
47311
+ const reader = response.body.getReader();
47312
+ const chunks = [];
47313
+ let total = 0;
47314
+ try {
47315
+ while (true) {
47316
+ const { done, value } = await reader.read();
47317
+ if (done) break;
47318
+ total += value.byteLength;
47319
+ if (total > maxBytes) {
47320
+ try {
47321
+ await reader.cancel();
47322
+ } catch {
47323
+ }
47324
+ throw new ResponseBodyLimitError(label, total, maxBytes);
47325
+ }
47326
+ chunks.push(value);
47327
+ }
47328
+ } finally {
47329
+ reader.releaseLock();
47330
+ }
47331
+ const bytes = new Uint8Array(total);
47332
+ let offset = 0;
47333
+ for (const chunk of chunks) {
47334
+ bytes.set(chunk, offset);
47335
+ offset += chunk.byteLength;
47336
+ }
47337
+ return bytes;
47338
+ }
47339
+ function isUnsafeControl(character) {
47340
+ const codePoint = character.codePointAt(0) ?? 0;
47341
+ return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
47342
+ }
47343
+ function validateLocalFileUrl(fileUrl, isPrivate) {
47344
+ if (typeof fileUrl !== "string" || fileUrl.length === 0) {
47345
+ throw new Error("[FrappeClient] File.file_url must be a non-empty string");
47346
+ }
47347
+ if (fileUrl !== fileUrl.trim()) {
47348
+ throw new Error(
47349
+ "[FrappeClient] File.file_url must not contain whitespace padding"
47350
+ );
47351
+ }
47352
+ const expectedPrefix = isPrivate ? "/private/files/" : "/files/";
47353
+ let decoded = fileUrl;
47354
+ for (let depth = 0; depth < 5; depth++) {
47355
+ if (Array.from(decoded).some(isUnsafeControl) || decoded.includes("\\") || decoded.includes("?") || decoded.includes("#")) {
47356
+ throw new Error(
47357
+ "[FrappeClient] File.file_url must be a local Frappe file path without query, fragment, NUL, or backslash"
47358
+ );
47359
+ }
47360
+ if (!decoded.startsWith(expectedPrefix)) {
47361
+ throw new Error(
47362
+ `[FrappeClient] File.file_url does not match is_private=${isPrivate ? 1 : 0}`
47363
+ );
47364
+ }
47365
+ const fileSegment = decoded.slice(expectedPrefix.length);
47366
+ if (!fileSegment || fileSegment === "." || fileSegment === ".." || fileSegment.includes("/")) {
47367
+ throw new Error(
47368
+ "[FrappeClient] File.file_url must contain exactly one safe file segment"
47369
+ );
47370
+ }
47371
+ let next;
47372
+ try {
47373
+ next = decodeURIComponent(decoded);
47374
+ } catch {
47375
+ throw new Error(
47376
+ "[FrappeClient] File.file_url contains malformed percent encoding"
47377
+ );
47378
+ }
47379
+ if (next === decoded) return fileUrl;
47380
+ decoded = next;
47381
+ }
47382
+ throw new Error(
47383
+ "[FrappeClient] File.file_url contains excessive nested percent encoding"
47384
+ );
47385
+ }
47386
+ function sanitizeDownloadFileName(raw2, fileId) {
47387
+ const fallbackId = fileId.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80) || "file";
47388
+ if (typeof raw2 !== "string") return `erpnext-${fallbackId}`;
47389
+ const normalized = Array.from(raw2.normalize("NFC")).filter((character) => !isUnsafeControl(character)).join("").replace(/[\\/:*?"<>|]/g, "_").trim().replace(/[. ]+$/g, "");
47390
+ const bounded = Array.from(normalized).slice(0, 255).join("");
47391
+ return !bounded || bounded === "." || bounded === ".." ? `erpnext-${fallbackId}` : bounded;
47392
+ }
47393
+ function normalizeMimeType(raw2) {
47394
+ const candidate = (raw2 ?? "").split(";", 1)[0].trim().toLowerCase();
47395
+ return /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i.test(candidate) && candidate !== "application/unknown" ? candidate : "application/octet-stream";
47396
+ }
47077
47397
  function decodeBase64File(contentBase64, maxBytes) {
47078
47398
  if (contentBase64.length === 0) {
47079
47399
  throw new Error("[FrappeClient] File content must not be empty");
@@ -47159,6 +47479,7 @@ var FrappeClient = class {
47159
47479
  authHeader;
47160
47480
  timeoutMs;
47161
47481
  maxUploadBytes;
47482
+ maxDownloadBytes;
47162
47483
  retries;
47163
47484
  retryStatuses;
47164
47485
  retryBackoffMs;
@@ -47174,6 +47495,12 @@ var FrappeClient = class {
47174
47495
  "[FrappeClient] maxUploadBytes must be a positive integer"
47175
47496
  );
47176
47497
  }
47498
+ this.maxDownloadBytes = config2.maxDownloadBytes ?? DEFAULT_MAX_DOWNLOAD_BYTES;
47499
+ if (!Number.isInteger(this.maxDownloadBytes) || this.maxDownloadBytes <= 0) {
47500
+ throw new Error(
47501
+ "[FrappeClient] maxDownloadBytes must be a positive integer"
47502
+ );
47503
+ }
47177
47504
  this.retries = config2.retries ?? 3;
47178
47505
  this.retryStatuses = config2.retryStatuses ?? DEFAULT_RETRY_STATUSES;
47179
47506
  this.retryBackoffMs = config2.retryBackoffMs ?? 200;
@@ -47288,6 +47615,152 @@ var FrappeClient = class {
47288
47615
  }
47289
47616
  return responseBody;
47290
47617
  }
47618
+ buildSameOriginUrl(path) {
47619
+ if (!path.startsWith("/")) {
47620
+ throw new Error("[FrappeClient] Download path must be root-relative");
47621
+ }
47622
+ let base;
47623
+ let target;
47624
+ try {
47625
+ base = new URL(this.baseUrl);
47626
+ target = new URL(`${this.baseUrl}${path}`);
47627
+ } catch {
47628
+ throw new Error("[FrappeClient] baseUrl must be a valid absolute URL");
47629
+ }
47630
+ if (target.origin !== base.origin) {
47631
+ throw new Error("[FrappeClient] Refusing a cross-origin download");
47632
+ }
47633
+ return target.href;
47634
+ }
47635
+ async requestBinary(path) {
47636
+ let lastError;
47637
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
47638
+ try {
47639
+ return await this.requestBinaryOnce(path);
47640
+ } catch (err) {
47641
+ lastError = err;
47642
+ if (attempt === this.retries || !this.isRetryable(err, "GET")) {
47643
+ throw err;
47644
+ }
47645
+ const delay = this.computeBackoff(attempt, err);
47646
+ if (delay > 0) {
47647
+ await new Promise((resolve) => setTimeout(resolve, delay));
47648
+ }
47649
+ }
47650
+ }
47651
+ throw lastError;
47652
+ }
47653
+ async requestBinaryOnce(path) {
47654
+ const url2 = this.buildSameOriginUrl(path);
47655
+ const controller = new AbortController();
47656
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
47657
+ let response;
47658
+ try {
47659
+ response = await fetch(url2, {
47660
+ method: "GET",
47661
+ headers: {
47662
+ "Authorization": this.authHeader,
47663
+ "Accept": "*/*",
47664
+ "Cache-Control": "no-store"
47665
+ },
47666
+ cache: "no-store",
47667
+ redirect: "manual",
47668
+ signal: controller.signal
47669
+ });
47670
+ } catch (err) {
47671
+ clearTimeout(timer);
47672
+ if (err instanceof Error && err.name === "AbortError") {
47673
+ throw new FrappeAPIError(
47674
+ `Request timed out after ${this.timeoutMs}ms: GET ${path}`,
47675
+ 408,
47676
+ null
47677
+ );
47678
+ }
47679
+ throw new FrappeAPIError(
47680
+ `Network error on GET ${path}: ${err.message}`,
47681
+ 0,
47682
+ null
47683
+ );
47684
+ }
47685
+ try {
47686
+ if (response.redirected || response.type === "opaqueredirect" || response.status >= 300 && response.status < 400) {
47687
+ try {
47688
+ await response.body?.cancel();
47689
+ } catch {
47690
+ }
47691
+ throw new FrappeAPIError(
47692
+ `GET ${path} refused an HTTP redirect`,
47693
+ response.status,
47694
+ null
47695
+ );
47696
+ }
47697
+ if (!response.ok) {
47698
+ let rawText;
47699
+ try {
47700
+ const errorBytes = await readResponseBytes(
47701
+ response,
47702
+ MAX_ERROR_BODY_BYTES,
47703
+ "Error response body"
47704
+ );
47705
+ rawText = new TextDecoder().decode(errorBytes);
47706
+ } catch (err) {
47707
+ if (!(err instanceof ResponseBodyLimitError)) throw err;
47708
+ rawText = err.message;
47709
+ }
47710
+ const contentType = response.headers.get("content-type") ?? "";
47711
+ let responseBody = rawText;
47712
+ if (contentType.includes("application/json") && rawText.length > 0) {
47713
+ try {
47714
+ responseBody = JSON.parse(rawText);
47715
+ } catch {
47716
+ responseBody = rawText;
47717
+ }
47718
+ }
47719
+ let message2 = response.statusText;
47720
+ if (typeof responseBody === "object" && responseBody !== null) {
47721
+ const body = responseBody;
47722
+ const baseMessage = body.message ?? body.exc_type ?? response.statusText;
47723
+ const serverDetails = extractServerMessages(body._server_messages);
47724
+ message2 = serverDetails ? `${baseMessage}: ${serverDetails}` : baseMessage;
47725
+ } else if (typeof responseBody === "string" && responseBody.length > 0) {
47726
+ message2 = responseBody.slice(0, 200);
47727
+ }
47728
+ throw new FrappeAPIError(
47729
+ `GET ${path} failed: ${message2}`,
47730
+ response.status,
47731
+ responseBody,
47732
+ parseRetryAfter(response.headers.get("retry-after"))
47733
+ );
47734
+ }
47735
+ const bytes = await readResponseBytes(
47736
+ response,
47737
+ this.maxDownloadBytes,
47738
+ "Downloaded file"
47739
+ );
47740
+ return {
47741
+ bytes,
47742
+ mimeType: normalizeMimeType(response.headers.get("content-type"))
47743
+ };
47744
+ } catch (err) {
47745
+ if (err instanceof FrappeAPIError || err instanceof ResponseBodyLimitError) {
47746
+ throw err;
47747
+ }
47748
+ if (err instanceof Error && err.name === "AbortError") {
47749
+ throw new FrappeAPIError(
47750
+ `Request timed out after ${this.timeoutMs}ms: GET ${path}`,
47751
+ 408,
47752
+ null
47753
+ );
47754
+ }
47755
+ throw new FrappeAPIError(
47756
+ `Network error while reading GET ${path}: ${err.message}`,
47757
+ 0,
47758
+ null
47759
+ );
47760
+ } finally {
47761
+ clearTimeout(timer);
47762
+ }
47763
+ }
47291
47764
  // ── Resource CRUD ───────────────────────────────────────────────────────────
47292
47765
  /**
47293
47766
  * List documents of a DocType.
@@ -47409,6 +47882,73 @@ var FrappeClient = class {
47409
47882
  );
47410
47883
  this.invalidate(doctype, name);
47411
47884
  }
47885
+ /**
47886
+ * Download one File row through Frappe's authenticated download handler.
47887
+ *
47888
+ * The caller supplies the expected attachment owner as a confused-deputy
47889
+ * guard. The File row is fetched fresh, its local path is validated, and the
47890
+ * response stream is stopped as soon as it exceeds `maxDownloadBytes`.
47891
+ */
47892
+ async downloadFile(input) {
47893
+ const fileId = input.fileId.trim();
47894
+ const attachedToDoctype = input.attachedToDoctype.trim();
47895
+ const attachedToName = input.attachedToName.trim();
47896
+ if (!fileId) {
47897
+ throw new Error("[FrappeClient] fileId must not be empty");
47898
+ }
47899
+ if (!attachedToDoctype) {
47900
+ throw new Error("[FrappeClient] attachedToDoctype must not be empty");
47901
+ }
47902
+ if (!attachedToName) {
47903
+ throw new Error("[FrappeClient] attachedToName must not be empty");
47904
+ }
47905
+ const metadata = await this.request(
47906
+ "GET",
47907
+ `/api/resource/File/${encodeURIComponent(fileId)}`
47908
+ );
47909
+ const file = metadata.data;
47910
+ if (file.name !== fileId) {
47911
+ throw new Error("[FrappeClient] File response identity mismatch");
47912
+ }
47913
+ if (file.attached_to_doctype !== attachedToDoctype || file.attached_to_name !== attachedToName) {
47914
+ throw new Error(
47915
+ "[FrappeClient] File is not attached to the requested document"
47916
+ );
47917
+ }
47918
+ if (file.is_private !== 0 && file.is_private !== 1) {
47919
+ throw new Error("[FrappeClient] File.is_private must be 0 or 1");
47920
+ }
47921
+ const fileUrl = validateLocalFileUrl(
47922
+ file.file_url,
47923
+ file.is_private === 1
47924
+ );
47925
+ const fileSize = file.file_size;
47926
+ if (fileSize !== void 0 && fileSize !== null) {
47927
+ if (typeof fileSize !== "number" || !Number.isInteger(fileSize) || fileSize < 0) {
47928
+ throw new Error(
47929
+ "[FrappeClient] File.file_size must be a non-negative integer"
47930
+ );
47931
+ }
47932
+ if (fileSize > this.maxDownloadBytes) {
47933
+ throw new ResponseBodyLimitError(
47934
+ "File metadata",
47935
+ fileSize,
47936
+ this.maxDownloadBytes
47937
+ );
47938
+ }
47939
+ }
47940
+ const params = new URLSearchParams({ file_url: fileUrl });
47941
+ const download = await this.requestBinary(
47942
+ `/api/method/frappe.handler.download_file?${params.toString()}`
47943
+ );
47944
+ return {
47945
+ fileId,
47946
+ fileName: sanitizeDownloadFileName(file.file_name, fileId),
47947
+ mimeType: download.mimeType,
47948
+ bytes: download.bytes,
47949
+ isPrivate: file.is_private === 1
47950
+ };
47951
+ }
47412
47952
  /**
47413
47953
  * Upload file bytes and attach the native File document to another document.
47414
47954
  * POST /api/method/upload_file
@@ -47479,6 +48019,7 @@ function getFrappeClient() {
47479
48019
  const apiKey = env6("ERPNEXT_API_KEY");
47480
48020
  const apiSecret = env6("ERPNEXT_API_SECRET");
47481
48021
  const maxUploadBytesRaw = env6("ERPNEXT_MAX_UPLOAD_BYTES");
48022
+ const maxDownloadBytesRaw = env6("ERPNEXT_MAX_DOWNLOAD_BYTES");
47482
48023
  if (!url2) {
47483
48024
  throw new Error(
47484
48025
  "[lib/erpnext] ERPNEXT_URL is required. Set it to your ERPNext instance URL, e.g. http://localhost:8000"
@@ -47494,7 +48035,8 @@ function getFrappeClient() {
47494
48035
  apiKey,
47495
48036
  apiSecret,
47496
48037
  cache: getCache(),
47497
- maxUploadBytes: maxUploadBytesRaw?.trim() ? Number(maxUploadBytesRaw) : void 0
48038
+ maxUploadBytes: maxUploadBytesRaw?.trim() ? Number(maxUploadBytesRaw) : void 0,
48039
+ maxDownloadBytes: maxDownloadBytesRaw?.trim() ? Number(maxDownloadBytesRaw) : void 0
47498
48040
  });
47499
48041
  return _client;
47500
48042
  }
@@ -47626,6 +48168,14 @@ function roundedTotalFallbackWarning(original, patched) {
47626
48168
  }
47627
48169
 
47628
48170
  // src/tools/sales.ts
48171
+ function viewerDocument(value, doctype, name) {
48172
+ const record2 = typeof value === "object" && value !== null ? value : {};
48173
+ return {
48174
+ ...record2,
48175
+ ...typeof record2.name === "string" || !name ? {} : { name },
48176
+ doctype
48177
+ };
48178
+ }
47629
48179
  function mapLineItems(items, options) {
47630
48180
  if (!Array.isArray(items) || items.length === 0) {
47631
48181
  throw new Error(
@@ -47711,6 +48261,7 @@ var salesTools = [
47711
48261
  {
47712
48262
  name: "erpnext_customer_get",
47713
48263
  annotations: { readOnlyHint: true },
48264
+ _meta: DOC_META,
47714
48265
  description: "Get a single ERPNext customer by name (ID). Returns all fields including contact details.",
47715
48266
  category: "sales",
47716
48267
  inputSchema: {
@@ -47725,7 +48276,7 @@ var salesTools = [
47725
48276
  throw new Error("[erpnext_customer_get] 'name' is required");
47726
48277
  }
47727
48278
  const doc = await ctx.client.get("Customer", input.name);
47728
- return { data: doc };
48279
+ return { data: { ...doc, doctype: "Customer" } };
47729
48280
  }
47730
48281
  },
47731
48282
  {
@@ -47906,7 +48457,7 @@ var salesTools = [
47906
48457
  throw new Error("[erpnext_sales_order_get] 'name' is required");
47907
48458
  }
47908
48459
  const doc = await ctx.client.get("Sales Order", input.name);
47909
- return { data: doc };
48460
+ return { data: viewerDocument(doc, "Sales Order", input.name) };
47910
48461
  }
47911
48462
  },
47912
48463
  {
@@ -47987,8 +48538,12 @@ var salesTools = [
47987
48538
  }
47988
48539
  const doc = await ctx.client.create("Sales Order", data);
47989
48540
  return {
47990
- data: doc,
47991
- message: `Sales Order ${doc.name} created successfully`
48541
+ data: viewerDocument(doc, "Sales Order"),
48542
+ message: `Sales Order ${doc.name} created successfully`,
48543
+ refreshRequest: {
48544
+ toolName: "erpnext_sales_order_get",
48545
+ arguments: { name: doc.name }
48546
+ }
47992
48547
  };
47993
48548
  }
47994
48549
  },
@@ -48078,8 +48633,16 @@ var salesTools = [
48078
48633
  ctx.client.invalidate("Sales Order", input.name);
48079
48634
  const warnings = roundedTotalFallbackWarning(docWithDoctype, patchedDoc);
48080
48635
  return {
48081
- data: result,
48636
+ data: viewerDocument(
48637
+ result,
48638
+ "Sales Order",
48639
+ input.name
48640
+ ),
48082
48641
  message: `Sales Order ${input.name} submitted successfully`,
48642
+ refreshRequest: {
48643
+ toolName: "erpnext_sales_order_get",
48644
+ arguments: { name: input.name }
48645
+ },
48083
48646
  ...warnings.length > 0 ? { warnings } : {}
48084
48647
  };
48085
48648
  }
@@ -48205,7 +48768,11 @@ var salesTools = [
48205
48768
  }
48206
48769
  const doc = await ctx.client.get("Sales Invoice", input.name);
48207
48770
  return {
48208
- data: doc,
48771
+ data: viewerDocument(
48772
+ doc,
48773
+ "Sales Invoice",
48774
+ input.name
48775
+ ),
48209
48776
  _meta: INVOICE_META
48210
48777
  };
48211
48778
  }
@@ -48291,9 +48858,13 @@ var salesTools = [
48291
48858
  }
48292
48859
  const doc = await ctx.client.create("Sales Invoice", data);
48293
48860
  return {
48294
- data: doc,
48861
+ data: viewerDocument(doc, "Sales Invoice"),
48295
48862
  message: `Sales Invoice ${doc.name} created successfully`,
48296
- _meta: INVOICE_META
48863
+ _meta: INVOICE_META,
48864
+ refreshRequest: {
48865
+ toolName: "erpnext_sales_invoice_get",
48866
+ arguments: { name: doc.name }
48867
+ }
48297
48868
  };
48298
48869
  }
48299
48870
  },
@@ -48327,9 +48898,17 @@ var salesTools = [
48327
48898
  ctx.client.invalidate("Sales Invoice", input.name);
48328
48899
  const warnings = roundedTotalFallbackWarning(docWithDoctype, patchedDoc);
48329
48900
  return {
48330
- data: result,
48901
+ data: viewerDocument(
48902
+ result,
48903
+ "Sales Invoice",
48904
+ input.name
48905
+ ),
48331
48906
  message: `Sales Invoice ${input.name} submitted successfully`,
48332
48907
  _meta: INVOICE_META,
48908
+ refreshRequest: {
48909
+ toolName: "erpnext_sales_invoice_get",
48910
+ arguments: { name: input.name }
48911
+ },
48333
48912
  ...warnings.length > 0 ? { warnings } : {}
48334
48913
  };
48335
48914
  }
@@ -48435,7 +49014,7 @@ var salesTools = [
48435
49014
  throw new Error("[erpnext_quotation_get] 'name' is required");
48436
49015
  }
48437
49016
  const doc = await ctx.client.get("Quotation", input.name);
48438
- return { data: doc };
49017
+ return { data: viewerDocument(doc, "Quotation", input.name) };
48439
49018
  }
48440
49019
  },
48441
49020
  {
@@ -48529,8 +49108,12 @@ var salesTools = [
48529
49108
  }
48530
49109
  const doc = await ctx.client.create("Quotation", data);
48531
49110
  return {
48532
- data: doc,
48533
- message: `Quotation ${doc.name} created successfully`
49111
+ data: viewerDocument(doc, "Quotation"),
49112
+ message: `Quotation ${doc.name} created successfully`,
49113
+ refreshRequest: {
49114
+ toolName: "erpnext_quotation_get",
49115
+ arguments: { name: doc.name }
49116
+ }
48534
49117
  };
48535
49118
  }
48536
49119
  }
@@ -48601,6 +49184,7 @@ var inventoryTools = [
48601
49184
  {
48602
49185
  name: "erpnext_item_get",
48603
49186
  annotations: { readOnlyHint: true },
49187
+ _meta: DOC_META,
48604
49188
  description: "Get a single ERPNext Item by name/item_code. Returns all fields including pricing and stock details.",
48605
49189
  category: "inventory",
48606
49190
  inputSchema: {
@@ -48615,7 +49199,7 @@ var inventoryTools = [
48615
49199
  throw new Error("[erpnext_item_get] 'name' is required");
48616
49200
  }
48617
49201
  const doc = await ctx.client.get("Item", input.name);
48618
- return { data: doc };
49202
+ return { data: { ...doc, doctype: "Item" } };
48619
49203
  }
48620
49204
  },
48621
49205
  {
@@ -48816,12 +49400,16 @@ var inventoryTools = [
48816
49400
  name: "erpnext_stock_entry_list",
48817
49401
  annotations: { readOnlyHint: true },
48818
49402
  _meta: DOCLIST_META,
48819
- description: "List Stock Entries (material transfers, receipts, issues). Fields: name, stock_entry_type, posting_date, from_warehouse, to_warehouse, total_amount. Filterable by stock_entry_type, date range.",
49403
+ description: "List Stock Entries (material transfers, receipts, issues). Fields: name, stock_entry_type, posting_date, from_warehouse, to_warehouse, total_amount. Filterable by item_code, stock_entry_type, date range.",
48820
49404
  category: "inventory",
48821
49405
  inputSchema: {
48822
49406
  type: "object",
48823
49407
  properties: {
48824
49408
  limit: { type: "number", description: "Max results (default 20)" },
49409
+ item_code: {
49410
+ type: "string",
49411
+ description: "Exact item code present in a Stock Entry line"
49412
+ },
48825
49413
  stock_entry_type: {
48826
49414
  type: "string",
48827
49415
  description: "Filter by type (Material Issue, Material Receipt, Material Transfer, etc.)"
@@ -48836,6 +49424,14 @@ var inventoryTools = [
48836
49424
  handler: async (input, ctx) => {
48837
49425
  const limit = input.limit ?? 20;
48838
49426
  const filters = [];
49427
+ if (input.item_code) {
49428
+ filters.push([
49429
+ "Stock Entry Detail",
49430
+ "item_code",
49431
+ "=",
49432
+ input.item_code
49433
+ ]);
49434
+ }
48839
49435
  if (input.stock_entry_type) {
48840
49436
  filters.push([
48841
49437
  "stock_entry_type",
@@ -48873,6 +49469,7 @@ var inventoryTools = [
48873
49469
  {
48874
49470
  name: "erpnext_stock_entry_get",
48875
49471
  annotations: { readOnlyHint: true },
49472
+ _meta: DOC_META,
48876
49473
  description: "Get a single Stock Entry by name. Returns full document with item details.",
48877
49474
  category: "inventory",
48878
49475
  inputSchema: {
@@ -48890,7 +49487,7 @@ var inventoryTools = [
48890
49487
  throw new Error("[erpnext_stock_entry_get] 'name' is required");
48891
49488
  }
48892
49489
  const doc = await ctx.client.get("Stock Entry", input.name);
48893
- return { data: doc };
49490
+ return { data: { ...doc, doctype: "Stock Entry" } };
48894
49491
  }
48895
49492
  },
48896
49493
  {
@@ -49085,6 +49682,7 @@ var accountingTools = [
49085
49682
  {
49086
49683
  name: "erpnext_journal_entry_get",
49087
49684
  annotations: { readOnlyHint: true },
49685
+ _meta: DOC_META,
49088
49686
  description: "Get a single Journal Entry by name (e.g. JV-00001). Returns full document with accounts.",
49089
49687
  category: "accounting",
49090
49688
  inputSchema: {
@@ -49102,7 +49700,7 @@ var accountingTools = [
49102
49700
  throw new Error("[erpnext_journal_entry_get] 'name' is required");
49103
49701
  }
49104
49702
  const doc = await ctx.client.get("Journal Entry", input.name);
49105
- return { data: doc };
49703
+ return { data: { ...doc, doctype: "Journal Entry" } };
49106
49704
  }
49107
49705
  },
49108
49706
  // ── Payment Entries ───────────────────────────────────────────────────────
@@ -49190,6 +49788,7 @@ var accountingTools = [
49190
49788
  {
49191
49789
  name: "erpnext_payment_entry_get",
49192
49790
  annotations: { readOnlyHint: true },
49791
+ _meta: DOC_META,
49193
49792
  description: "Get a single Payment Entry by name (e.g. PE-00001). Returns full document including references.",
49194
49793
  category: "accounting",
49195
49794
  inputSchema: {
@@ -49207,7 +49806,7 @@ var accountingTools = [
49207
49806
  throw new Error("[erpnext_payment_entry_get] 'name' is required");
49208
49807
  }
49209
49808
  const doc = await ctx.client.get("Payment Entry", input.name);
49210
- return { data: doc };
49809
+ return { data: { ...doc, doctype: "Payment Entry" } };
49211
49810
  }
49212
49811
  },
49213
49812
  {
@@ -49333,6 +49932,7 @@ var hrTools = [
49333
49932
  {
49334
49933
  name: "erpnext_employee_get",
49335
49934
  annotations: { readOnlyHint: true },
49935
+ _meta: DOC_META,
49336
49936
  description: "Get a single Employee by name/ID (e.g. HR-EMP-00001). Returns all fields.",
49337
49937
  category: "hr",
49338
49938
  inputSchema: {
@@ -49357,7 +49957,7 @@ var hrTools = [
49357
49957
  { allowPartialMatch: false, inputPath: "name" }
49358
49958
  );
49359
49959
  const doc = await ctx.client.get("Employee", employeeId);
49360
- return { data: doc };
49960
+ return { data: { ...doc, doctype: "Employee" } };
49361
49961
  }
49362
49962
  },
49363
49963
  // ── Attendance ────────────────────────────────────────────────────────────
@@ -49508,6 +50108,7 @@ var hrTools = [
49508
50108
  {
49509
50109
  name: "erpnext_leave_application_get",
49510
50110
  annotations: { readOnlyHint: true },
50111
+ _meta: DOC_META,
49511
50112
  description: "Get a single Leave Application by name. Returns full document.",
49512
50113
  category: "hr",
49513
50114
  inputSchema: {
@@ -49525,7 +50126,7 @@ var hrTools = [
49525
50126
  "Leave Application",
49526
50127
  input.name
49527
50128
  );
49528
- return { data: doc };
50129
+ return { data: { ...doc, doctype: "Leave Application" } };
49529
50130
  }
49530
50131
  },
49531
50132
  {
@@ -49671,6 +50272,7 @@ var hrTools = [
49671
50272
  {
49672
50273
  name: "erpnext_salary_slip_get",
49673
50274
  annotations: { readOnlyHint: true },
50275
+ _meta: DOC_META,
49674
50276
  description: "Get a single Salary Slip by name/ID. Returns all fields including earnings and deductions.",
49675
50277
  category: "hr",
49676
50278
  inputSchema: {
@@ -49688,7 +50290,7 @@ var hrTools = [
49688
50290
  throw new Error("[erpnext_salary_slip_get] 'name' is required");
49689
50291
  }
49690
50292
  const doc = await ctx.client.get("Salary Slip", input.name);
49691
- return { data: doc };
50293
+ return { data: { ...doc, doctype: "Salary Slip" } };
49692
50294
  }
49693
50295
  },
49694
50296
  // ── Payroll Entries ───────────────────────────────────────────────────────
@@ -50179,6 +50781,7 @@ var projectTools = [
50179
50781
  {
50180
50782
  name: "erpnext_project_get",
50181
50783
  annotations: { readOnlyHint: true },
50784
+ _meta: DOC_META,
50182
50785
  description: "Get a single Project by name. Returns full document including tasks summary.",
50183
50786
  category: "project",
50184
50787
  inputSchema: {
@@ -50193,7 +50796,7 @@ var projectTools = [
50193
50796
  throw new Error("[erpnext_project_get] 'name' is required");
50194
50797
  }
50195
50798
  const doc = await ctx.client.get("Project", input.name);
50196
- return { data: doc };
50799
+ return { data: { ...doc, doctype: "Project" } };
50197
50800
  }
50198
50801
  },
50199
50802
  // ── Tasks ─────────────────────────────────────────────────────────────────
@@ -50358,6 +50961,7 @@ var projectTools = [
50358
50961
  {
50359
50962
  name: "erpnext_task_get",
50360
50963
  annotations: { readOnlyHint: true },
50964
+ _meta: DOC_META,
50361
50965
  description: "Get a single Task by name. Returns full document including description and dependencies.",
50362
50966
  category: "project",
50363
50967
  inputSchema: {
@@ -50372,7 +50976,7 @@ var projectTools = [
50372
50976
  throw new Error("[erpnext_task_get] 'name' is required");
50373
50977
  }
50374
50978
  const doc = await ctx.client.get("Task", input.name);
50375
- return { data: doc };
50979
+ return { data: { ...doc, doctype: "Task" } };
50376
50980
  }
50377
50981
  },
50378
50982
  {
@@ -50548,6 +51152,7 @@ var projectTools = [
50548
51152
  {
50549
51153
  name: "erpnext_timesheet_get",
50550
51154
  annotations: { readOnlyHint: true },
51155
+ _meta: DOC_META,
50551
51156
  description: "Get a single Timesheet by name. Returns full document with time log details.",
50552
51157
  category: "project",
50553
51158
  inputSchema: {
@@ -50562,7 +51167,7 @@ var projectTools = [
50562
51167
  throw new Error("[erpnext_timesheet_get] 'name' is required");
50563
51168
  }
50564
51169
  const doc = await ctx.client.get("Timesheet", input.name);
50565
- return { data: doc };
51170
+ return { data: { ...doc, doctype: "Timesheet" } };
50566
51171
  }
50567
51172
  },
50568
51173
  {
@@ -50684,6 +51289,7 @@ var purchasingTools = [
50684
51289
  {
50685
51290
  name: "erpnext_supplier_get",
50686
51291
  annotations: { readOnlyHint: true },
51292
+ _meta: DOC_META,
50687
51293
  description: "Get a single ERPNext supplier by name (ID). Returns all fields including contact details.",
50688
51294
  category: "purchasing",
50689
51295
  inputSchema: {
@@ -50698,7 +51304,7 @@ var purchasingTools = [
50698
51304
  throw new Error("[erpnext_supplier_get] 'name' is required");
50699
51305
  }
50700
51306
  const doc = await ctx.client.get("Supplier", input.name);
50701
- return { data: doc };
51307
+ return { data: { ...doc, doctype: "Supplier" } };
50702
51308
  }
50703
51309
  },
50704
51310
  {
@@ -50827,6 +51433,7 @@ var purchasingTools = [
50827
51433
  {
50828
51434
  name: "erpnext_purchase_order_get",
50829
51435
  annotations: { readOnlyHint: true },
51436
+ _meta: DOC_META,
50830
51437
  description: "Get a single Purchase Order by name (e.g. PO-00001). Returns full document with line items.",
50831
51438
  category: "purchasing",
50832
51439
  inputSchema: {
@@ -50844,7 +51451,7 @@ var purchasingTools = [
50844
51451
  throw new Error("[erpnext_purchase_order_get] 'name' is required");
50845
51452
  }
50846
51453
  const doc = await ctx.client.get("Purchase Order", input.name);
50847
- return { data: doc };
51454
+ return { data: { ...doc, doctype: "Purchase Order" } };
50848
51455
  }
50849
51456
  },
50850
51457
  {
@@ -50995,6 +51602,7 @@ var purchasingTools = [
50995
51602
  {
50996
51603
  name: "erpnext_purchase_invoice_get",
50997
51604
  annotations: { readOnlyHint: true },
51605
+ _meta: DOC_META,
50998
51606
  description: "Get a single Purchase Invoice by name (e.g. PINV-00001). Returns full document with line items.",
50999
51607
  category: "purchasing",
51000
51608
  inputSchema: {
@@ -51015,7 +51623,7 @@ var purchasingTools = [
51015
51623
  "Purchase Invoice",
51016
51624
  input.name
51017
51625
  );
51018
- return { data: doc };
51626
+ return { data: { ...doc, doctype: "Purchase Invoice" } };
51019
51627
  }
51020
51628
  },
51021
51629
  // ── Purchase Receipts ─────────────────────────────────────────────────────
@@ -51089,6 +51697,7 @@ var purchasingTools = [
51089
51697
  {
51090
51698
  name: "erpnext_purchase_receipt_get",
51091
51699
  annotations: { readOnlyHint: true },
51700
+ _meta: DOC_META,
51092
51701
  description: "Get a single Purchase Receipt by name (e.g. MAT-PRE-00001). Returns full document with received items.",
51093
51702
  category: "purchasing",
51094
51703
  inputSchema: {
@@ -51109,7 +51718,7 @@ var purchasingTools = [
51109
51718
  "Purchase Receipt",
51110
51719
  input.name
51111
51720
  );
51112
- return { data: doc };
51721
+ return { data: { ...doc, doctype: "Purchase Receipt" } };
51113
51722
  }
51114
51723
  },
51115
51724
  // ── Supplier Quotations ───────────────────────────────────────────────────
@@ -51254,6 +51863,7 @@ var deliveryTools = [
51254
51863
  {
51255
51864
  name: "erpnext_delivery_note_get",
51256
51865
  annotations: { readOnlyHint: true },
51866
+ _meta: DOC_META,
51257
51867
  description: "Get a single Delivery Note by name (e.g. MAT-DN-00001). Returns full document with delivered items.",
51258
51868
  category: "delivery",
51259
51869
  inputSchema: {
@@ -51271,7 +51881,7 @@ var deliveryTools = [
51271
51881
  throw new Error("[erpnext_delivery_note_get] 'name' is required");
51272
51882
  }
51273
51883
  const doc = await ctx.client.get("Delivery Note", input.name);
51274
- return { data: doc };
51884
+ return { data: { ...doc, doctype: "Delivery Note" } };
51275
51885
  }
51276
51886
  },
51277
51887
  {
@@ -51404,6 +52014,7 @@ var deliveryTools = [
51404
52014
  {
51405
52015
  name: "erpnext_shipment_get",
51406
52016
  annotations: { readOnlyHint: true },
52017
+ _meta: DOC_META,
51407
52018
  description: "Get a single Shipment by name. Returns full shipment details including parcels.",
51408
52019
  category: "delivery",
51409
52020
  inputSchema: {
@@ -51418,7 +52029,7 @@ var deliveryTools = [
51418
52029
  throw new Error("[erpnext_shipment_get] 'name' is required");
51419
52030
  }
51420
52031
  const doc = await ctx.client.get("Shipment", input.name);
51421
- return { data: doc };
52032
+ return { data: { ...doc, doctype: "Shipment" } };
51422
52033
  }
51423
52034
  }
51424
52035
  ];
@@ -51498,6 +52109,7 @@ var manufacturingTools = [
51498
52109
  {
51499
52110
  name: "erpnext_bom_get",
51500
52111
  annotations: { readOnlyHint: true },
52112
+ _meta: DOC_META,
51501
52113
  description: "Get a single BOM by name (e.g. BOM-ITEM-00001). Returns full document with raw materials and operations.",
51502
52114
  category: "manufacturing",
51503
52115
  inputSchema: {
@@ -51512,7 +52124,7 @@ var manufacturingTools = [
51512
52124
  throw new Error("[erpnext_bom_get] 'name' is required");
51513
52125
  }
51514
52126
  const doc = await ctx.client.get("BOM", input.name);
51515
- return { data: doc };
52127
+ return { data: { ...doc, doctype: "BOM" } };
51516
52128
  }
51517
52129
  },
51518
52130
  // ── Work Orders ───────────────────────────────────────────────────────────
@@ -51590,6 +52202,7 @@ var manufacturingTools = [
51590
52202
  {
51591
52203
  name: "erpnext_work_order_get",
51592
52204
  annotations: { readOnlyHint: true },
52205
+ _meta: DOC_META,
51593
52206
  description: "Get a single Work Order by name (e.g. MFG-WO-00001). Returns full document with operations and materials.",
51594
52207
  category: "manufacturing",
51595
52208
  inputSchema: {
@@ -51607,7 +52220,7 @@ var manufacturingTools = [
51607
52220
  throw new Error("[erpnext_work_order_get] 'name' is required");
51608
52221
  }
51609
52222
  const doc = await ctx.client.get("Work Order", input.name);
51610
- return { data: doc };
52223
+ return { data: { ...doc, doctype: "Work Order" } };
51611
52224
  }
51612
52225
  },
51613
52226
  {
@@ -51723,6 +52336,7 @@ var manufacturingTools = [
51723
52336
  {
51724
52337
  name: "erpnext_job_card_get",
51725
52338
  annotations: { readOnlyHint: true },
52339
+ _meta: DOC_META,
51726
52340
  description: "Get a single Job Card by name. Returns full document with time logs and material transfers.",
51727
52341
  category: "manufacturing",
51728
52342
  inputSchema: {
@@ -51737,7 +52351,7 @@ var manufacturingTools = [
51737
52351
  throw new Error("[erpnext_job_card_get] 'name' is required");
51738
52352
  }
51739
52353
  const doc = await ctx.client.get("Job Card", input.name);
51740
- return { data: doc };
52354
+ return { data: { ...doc, doctype: "Job Card" } };
51741
52355
  }
51742
52356
  }
51743
52357
  ];
@@ -51799,6 +52413,7 @@ var crmTools = [
51799
52413
  {
51800
52414
  name: "erpnext_lead_get",
51801
52415
  annotations: { readOnlyHint: true },
52416
+ _meta: DOC_META,
51802
52417
  description: "Get a single CRM Lead by name. Returns all lead details including contact info.",
51803
52418
  category: "crm",
51804
52419
  inputSchema: {
@@ -51813,7 +52428,7 @@ var crmTools = [
51813
52428
  throw new Error("[erpnext_lead_get] 'name' is required");
51814
52429
  }
51815
52430
  const doc = await ctx.client.get("Lead", input.name);
51816
- return { data: doc };
52431
+ return { data: { ...doc, doctype: "Lead" } };
51817
52432
  }
51818
52433
  },
51819
52434
  {
@@ -51945,6 +52560,7 @@ var crmTools = [
51945
52560
  {
51946
52561
  name: "erpnext_opportunity_get",
51947
52562
  annotations: { readOnlyHint: true },
52563
+ _meta: DOC_META,
51948
52564
  description: "Get a single CRM Opportunity by name. Returns full details including items and competitors.",
51949
52565
  category: "crm",
51950
52566
  inputSchema: {
@@ -51959,7 +52575,7 @@ var crmTools = [
51959
52575
  throw new Error("[erpnext_opportunity_get] 'name' is required");
51960
52576
  }
51961
52577
  const doc = await ctx.client.get("Opportunity", input.name);
51962
- return { data: doc };
52578
+ return { data: { ...doc, doctype: "Opportunity" } };
51963
52579
  }
51964
52580
  },
51965
52581
  // ── Contacts ──────────────────────────────────────────────────────────────
@@ -52014,6 +52630,7 @@ var crmTools = [
52014
52630
  {
52015
52631
  name: "erpnext_contact_get",
52016
52632
  annotations: { readOnlyHint: true },
52633
+ _meta: DOC_META,
52017
52634
  description: "Get a single Contact by name. Returns all contact details.",
52018
52635
  category: "crm",
52019
52636
  inputSchema: {
@@ -52028,7 +52645,7 @@ var crmTools = [
52028
52645
  throw new Error("[erpnext_contact_get] 'name' is required");
52029
52646
  }
52030
52647
  const doc = await ctx.client.get("Contact", input.name);
52031
- return { data: doc };
52648
+ return { data: { ...doc, doctype: "Contact" } };
52032
52649
  }
52033
52650
  },
52034
52651
  // ── Campaigns ─────────────────────────────────────────────────────────────
@@ -52178,6 +52795,7 @@ var assetsTools = [
52178
52795
  {
52179
52796
  name: "erpnext_asset_get",
52180
52797
  annotations: { readOnlyHint: true },
52798
+ _meta: DOC_META,
52181
52799
  description: "Get a single Asset by name. Returns full details including depreciation schedule and maintenance logs.",
52182
52800
  category: "assets",
52183
52801
  inputSchema: {
@@ -52192,7 +52810,7 @@ var assetsTools = [
52192
52810
  throw new Error("[erpnext_asset_get] 'name' is required");
52193
52811
  }
52194
52812
  const doc = await ctx.client.get("Asset", input.name);
52195
- return { data: doc };
52813
+ return { data: { ...doc, doctype: "Asset" } };
52196
52814
  }
52197
52815
  },
52198
52816
  {
@@ -52328,6 +52946,7 @@ var assetsTools = [
52328
52946
  {
52329
52947
  name: "erpnext_asset_movement_get",
52330
52948
  annotations: { readOnlyHint: true },
52949
+ _meta: DOC_META,
52331
52950
  description: "Get a single Asset Movement by name. Returns full details including assets moved.",
52332
52951
  category: "assets",
52333
52952
  inputSchema: {
@@ -52342,7 +52961,7 @@ var assetsTools = [
52342
52961
  throw new Error("[erpnext_asset_movement_get] 'name' is required");
52343
52962
  }
52344
52963
  const doc = await ctx.client.get("Asset Movement", input.name);
52345
- return { data: doc };
52964
+ return { data: { ...doc, doctype: "Asset Movement" } };
52346
52965
  }
52347
52966
  },
52348
52967
  // ── Asset Maintenance ─────────────────────────────────────────────────────
@@ -52399,6 +53018,7 @@ var assetsTools = [
52399
53018
  {
52400
53019
  name: "erpnext_asset_maintenance_get",
52401
53020
  annotations: { readOnlyHint: true },
53021
+ _meta: DOC_META,
52402
53022
  description: "Get a single Asset Maintenance record by name. Returns full details including maintenance tasks.",
52403
53023
  category: "assets",
52404
53024
  inputSchema: {
@@ -52416,7 +53036,7 @@ var assetsTools = [
52416
53036
  "Asset Maintenance",
52417
53037
  input.name
52418
53038
  );
52419
- return { data: doc };
53039
+ return { data: { ...doc, doctype: "Asset Maintenance" } };
52420
53040
  }
52421
53041
  },
52422
53042
  // ── Asset Categories ──────────────────────────────────────────────────────
@@ -52451,8 +53071,161 @@ var assetsTools = [
52451
53071
  ];
52452
53072
 
52453
53073
  // src/tools/operations.ts
53074
+ function bytesToBase64(bytes) {
53075
+ const chunkSize = 32 * 1024;
53076
+ let binary = "";
53077
+ for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
53078
+ binary += String.fromCharCode(
53079
+ ...bytes.subarray(offset, Math.min(offset + chunkSize, bytes.byteLength))
53080
+ );
53081
+ }
53082
+ return btoa(binary);
53083
+ }
52454
53084
  var operationsTools = [
52455
53085
  // ── File Attachments ───────────────────────────────────────────────────────
53086
+ {
53087
+ name: "erpnext_file_list",
53088
+ annotations: { readOnlyHint: true },
53089
+ description: "List the files attached to an ERPNext document. Returns name, size, privacy and URL for each attachment. Pairs with erpnext_file_upload: that one attaches, this one reads back.",
53090
+ category: "operations",
53091
+ inputSchema: {
53092
+ type: "object",
53093
+ properties: {
53094
+ attached_to_doctype: {
53095
+ type: "string",
53096
+ description: "DocType of the document whose attachments to list.",
53097
+ minLength: 1
53098
+ },
53099
+ attached_to_name: {
53100
+ type: "string",
53101
+ description: "Name/ID of the document whose attachments to list.",
53102
+ minLength: 1
53103
+ },
53104
+ limit: {
53105
+ type: "number",
53106
+ description: "Maximum number of files to return. Defaults to 50.",
53107
+ minimum: 1,
53108
+ maximum: 500
53109
+ }
53110
+ },
53111
+ required: ["attached_to_doctype", "attached_to_name"]
53112
+ },
53113
+ handler: async (input, ctx) => {
53114
+ for (const field of ["attached_to_doctype", "attached_to_name"]) {
53115
+ if (typeof input[field] !== "string" || !input[field].trim()) {
53116
+ throw new Error(
53117
+ `[erpnext_file_list] '${field}' must be a non-empty string`
53118
+ );
53119
+ }
53120
+ }
53121
+ if (input.limit !== void 0 && (typeof input.limit !== "number" || !Number.isInteger(input.limit) || input.limit < 1 || input.limit > 500)) {
53122
+ throw new Error(
53123
+ "[erpnext_file_list] 'limit' must be an integer between 1 and 500"
53124
+ );
53125
+ }
53126
+ const files = await ctx.client.list("File", {
53127
+ fields: [
53128
+ "name",
53129
+ "file_name",
53130
+ "file_url",
53131
+ "file_size",
53132
+ "is_private",
53133
+ "attached_to_field",
53134
+ "creation",
53135
+ "modified",
53136
+ "owner"
53137
+ ],
53138
+ filters: [
53139
+ ["attached_to_doctype", "=", input.attached_to_doctype],
53140
+ ["attached_to_name", "=", input.attached_to_name]
53141
+ ],
53142
+ order_by: "creation desc",
53143
+ limit: input.limit ?? 50
53144
+ });
53145
+ return {
53146
+ count: files.length,
53147
+ data: files.map((file) => ({
53148
+ name: file.name,
53149
+ file_name: file.file_name,
53150
+ file_url: file.file_url,
53151
+ file_size: file.file_size ?? null,
53152
+ is_private: file.is_private === 1,
53153
+ attached_to_field: file.attached_to_field ?? null,
53154
+ creation: file.creation,
53155
+ modified: file.modified,
53156
+ owner: file.owner
53157
+ }))
53158
+ };
53159
+ }
53160
+ },
53161
+ {
53162
+ name: "erpnext_file_download",
53163
+ annotations: { readOnlyHint: true },
53164
+ _meta: {
53165
+ ui: {
53166
+ resourceUri: DOC_META.ui.resourceUri,
53167
+ visibility: ["app"]
53168
+ }
53169
+ },
53170
+ description: "Download one ERPNext attachment for the document viewer. The tool accepts a File ID, verifies its document attachment, and returns one embedded binary resource.",
53171
+ category: "operations",
53172
+ inputSchema: {
53173
+ type: "object",
53174
+ properties: {
53175
+ file_id: {
53176
+ type: "string",
53177
+ description: "Native ERPNext File.name identifier, never a URL.",
53178
+ minLength: 1
53179
+ },
53180
+ attached_to_doctype: {
53181
+ type: "string",
53182
+ description: "Expected parent document DocType.",
53183
+ minLength: 1
53184
+ },
53185
+ attached_to_name: {
53186
+ type: "string",
53187
+ description: "Expected parent document name/ID.",
53188
+ minLength: 1
53189
+ }
53190
+ },
53191
+ required: ["file_id", "attached_to_doctype", "attached_to_name"],
53192
+ additionalProperties: false
53193
+ },
53194
+ handler: async (input, ctx) => {
53195
+ for (const field of [
53196
+ "file_id",
53197
+ "attached_to_doctype",
53198
+ "attached_to_name"
53199
+ ]) {
53200
+ if (typeof input[field] !== "string" || !input[field].trim()) {
53201
+ throw new Error(
53202
+ `[erpnext_file_download] '${field}' must be a non-empty string`
53203
+ );
53204
+ }
53205
+ }
53206
+ const file = await ctx.client.downloadFile({
53207
+ fileId: input.file_id.trim(),
53208
+ attachedToDoctype: input.attached_to_doctype.trim(),
53209
+ attachedToName: input.attached_to_name.trim()
53210
+ });
53211
+ return {
53212
+ content: [
53213
+ {
53214
+ type: "text",
53215
+ text: `Prepared ${file.fileName} for download (${file.bytes.byteLength} bytes).`
53216
+ },
53217
+ {
53218
+ type: "resource",
53219
+ resource: {
53220
+ uri: `file:///${encodeURIComponent(file.fileName)}`,
53221
+ mimeType: file.mimeType,
53222
+ blob: bytesToBase64(file.bytes)
53223
+ }
53224
+ }
53225
+ ]
53226
+ };
53227
+ }
53228
+ },
52456
53229
  {
52457
53230
  name: "erpnext_file_upload",
52458
53231
  annotations: { destructiveHint: true },
@@ -52754,6 +53527,7 @@ var operationsTools = [
52754
53527
  {
52755
53528
  name: "erpnext_doc_get",
52756
53529
  annotations: { readOnlyHint: true },
53530
+ _meta: DOC_META,
52757
53531
  description: "Get any ERPNext document by DocType and name. Useful for DocTypes not covered by dedicated tools. Returns the full document with all fields.",
52758
53532
  category: "operations",
52759
53533
  inputSchema: {
@@ -52771,17 +53545,18 @@ var operationsTools = [
52771
53545
  required: ["doctype", "name"]
52772
53546
  },
52773
53547
  handler: async (input, ctx) => {
52774
- if (!input.doctype) {
53548
+ if (typeof input.doctype !== "string" || !input.doctype.trim()) {
52775
53549
  throw new Error("[erpnext_doc_get] 'doctype' is required");
52776
53550
  }
52777
- if (!input.name) {
53551
+ if (typeof input.name !== "string" || !input.name.trim()) {
52778
53552
  throw new Error("[erpnext_doc_get] 'name' is required");
52779
53553
  }
53554
+ const doctype = input.doctype.trim();
52780
53555
  const doc = await ctx.client.get(
52781
- input.doctype,
52782
- input.name
53556
+ doctype,
53557
+ input.name.trim()
52783
53558
  );
52784
- return { data: doc };
53559
+ return { data: { ...doc, doctype } };
52785
53560
  }
52786
53561
  },
52787
53562
  // ── Generic List ──────────────────────────────────────────────────────────
@@ -52805,10 +53580,52 @@ var operationsTools = [
52805
53580
  },
52806
53581
  filters: {
52807
53582
  type: "array",
52808
- description: 'Frappe filters as array of [fieldname, operator, value] tuples. Example: [["status","=","Open"],["company","=","Acme"]]',
53583
+ description: 'Frappe filters as array of [fieldname, operator, value] tuples, or [child doctype, fieldname, operator, value] to filter on a child table. Values may be strings, numbers, booleans, null, or string/number arrays for in/not in. Example: [["status","=","Open"],["company","=","Acme"]]',
52809
53584
  items: {
52810
53585
  type: "array",
52811
- items: { type: "string" }
53586
+ anyOf: [
53587
+ {
53588
+ prefixItems: [
53589
+ { type: "string", minLength: 1 },
53590
+ { type: "string", minLength: 1 },
53591
+ {
53592
+ oneOf: [
53593
+ { type: ["string", "number", "boolean", "null"] },
53594
+ {
53595
+ type: "array",
53596
+ items: { type: ["string", "number"] }
53597
+ }
53598
+ ]
53599
+ }
53600
+ ],
53601
+ minItems: 3,
53602
+ maxItems: 3
53603
+ },
53604
+ {
53605
+ prefixItems: [
53606
+ { type: "string", minLength: 1 },
53607
+ { type: "string", minLength: 1 },
53608
+ { type: "string", minLength: 1 },
53609
+ {
53610
+ oneOf: [
53611
+ { type: ["string", "number", "boolean", "null"] },
53612
+ {
53613
+ type: "array",
53614
+ items: { type: ["string", "number"] }
53615
+ }
53616
+ ]
53617
+ }
53618
+ ],
53619
+ minItems: 4,
53620
+ maxItems: 4
53621
+ },
53622
+ {
53623
+ // 3.0.x advertised arbitrary string arrays. Keep accepting
53624
+ // that legacy surface while describing real 3/4-part Frappe
53625
+ // tuples precisely for modern clients.
53626
+ items: { type: "string" }
53627
+ }
53628
+ ]
52812
53629
  }
52813
53630
  },
52814
53631
  limit: { type: "number", description: "Max results (default 20)" },
@@ -53215,7 +54032,6 @@ var analyticsTools = [
53215
54032
  const invoices2 = await ctx.client.list("Sales Invoice", {
53216
54033
  fields: ["name", "status", "grand_total"],
53217
54034
  filters: [["docstatus", "!=", 2]],
53218
- // exclude cancelled
53219
54035
  limit: 500,
53220
54036
  order_by: "modified desc"
53221
54037
  });
@@ -53551,6 +54367,7 @@ var analyticsTools = [
53551
54367
  color: "#fbbf24",
53552
54368
  type: "line",
53553
54369
  yAxisId: "right",
54370
+ unit: "orders",
53554
54371
  showDots: true
53555
54372
  }
53556
54373
  ],
@@ -54006,7 +54823,9 @@ var analyticsTools = [
54006
54823
  category: "analytics",
54007
54824
  inputSchema: { type: "object", properties: {} },
54008
54825
  handler: async (_input, ctx) => {
54009
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
54826
+ const now = /* @__PURE__ */ new Date();
54827
+ const pad = (n) => String(n).padStart(2, "0");
54828
+ const today = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
54010
54829
  const invoices = await ctx.client.list("Sales Invoice", {
54011
54830
  fields: ["outstanding_amount", "due_date"],
54012
54831
  filters: [
@@ -55616,46 +56435,190 @@ var DOCTYPE_GET_TOOLS = {
55616
56435
  };
55617
56436
  var DOCTYPE_SEND_MESSAGE_HINTS = {
55618
56437
  "Customer": [
55619
- { label: "Orders", message: "Show sales orders for customer {id}" },
55620
- { label: "Invoices", message: "Show sales invoices for customer {id}" }
56438
+ {
56439
+ key: "orders",
56440
+ label: "Orders",
56441
+ message: "Show sales orders for customer {id}",
56442
+ tool: "erpnext_sales_order_list",
56443
+ args: { customer: "{id}", limit: 20 }
56444
+ },
56445
+ {
56446
+ key: "invoices",
56447
+ label: "Invoices",
56448
+ message: "Show sales invoices for customer {id}",
56449
+ tool: "erpnext_sales_invoice_list",
56450
+ args: { customer: "{id}", limit: 20 }
56451
+ }
55621
56452
  ],
55622
56453
  "Sales Order": [
55623
- { label: "Invoice", message: "Show invoices linked to sales order {id}" },
55624
- { label: "Delivery", message: "Show delivery notes for sales order {id}" }
56454
+ {
56455
+ key: "invoice",
56456
+ label: "Invoice",
56457
+ message: "Show invoices linked to sales order {id}",
56458
+ tool: "erpnext_doc_list",
56459
+ args: {
56460
+ doctype: "Sales Invoice",
56461
+ fields: ["name", "customer", "posting_date", "status", "grand_total"],
56462
+ filters: [["Sales Invoice Item", "sales_order", "=", "{id}"]],
56463
+ limit: 20
56464
+ }
56465
+ },
56466
+ {
56467
+ key: "delivery",
56468
+ label: "Delivery",
56469
+ message: "Show delivery notes for sales order {id}",
56470
+ tool: "erpnext_doc_list",
56471
+ args: {
56472
+ doctype: "Delivery Note",
56473
+ fields: ["name", "customer", "posting_date", "status"],
56474
+ filters: [["Delivery Note Item", "against_sales_order", "=", "{id}"]],
56475
+ limit: 20
56476
+ }
56477
+ }
55625
56478
  ],
55626
56479
  "Sales Invoice": [
55627
- { label: "Payments", message: "Show payment entries for invoice {id}" }
56480
+ {
56481
+ key: "payments",
56482
+ label: "Payments",
56483
+ message: "Show payment entries for invoice {id}",
56484
+ tool: "erpnext_doc_list",
56485
+ args: {
56486
+ doctype: "Payment Entry",
56487
+ fields: [
56488
+ "name",
56489
+ "posting_date",
56490
+ "paid_amount",
56491
+ "mode_of_payment",
56492
+ "docstatus"
56493
+ ],
56494
+ filters: [["Payment Entry Reference", "reference_name", "=", "{id}"]],
56495
+ limit: 20
56496
+ }
56497
+ }
55628
56498
  ],
55629
56499
  "Item": [
55630
- { label: "Stock", message: "Show stock balance for item {id}" },
55631
- { label: "Orders", message: "Show sales orders containing item {id}" }
56500
+ {
56501
+ key: "stock",
56502
+ label: "Stock",
56503
+ message: "Show stock balance for item {id}",
56504
+ tool: "erpnext_stock_balance",
56505
+ args: { item_code: "{id}" }
56506
+ },
56507
+ {
56508
+ key: "orders",
56509
+ label: "Orders",
56510
+ message: "Show sales orders containing item {id}",
56511
+ tool: "erpnext_doc_list",
56512
+ args: {
56513
+ doctype: "Sales Order",
56514
+ fields: ["name", "customer", "transaction_date", "status"],
56515
+ filters: [["Sales Order Item", "item_code", "=", "{id}"]],
56516
+ limit: 20
56517
+ }
56518
+ }
55632
56519
  ],
55633
56520
  "Supplier": [
55634
- { label: "PO", message: "Show purchase orders for supplier {id}" },
55635
- { label: "Invoices", message: "Show purchase invoices for supplier {id}" }
56521
+ {
56522
+ key: "po",
56523
+ label: "PO",
56524
+ message: "Show purchase orders for supplier {id}",
56525
+ tool: "erpnext_purchase_order_list",
56526
+ args: { supplier: "{id}", limit: 20 }
56527
+ },
56528
+ {
56529
+ key: "invoices",
56530
+ label: "Invoices",
56531
+ message: "Show purchase invoices for supplier {id}",
56532
+ tool: "erpnext_purchase_invoice_list",
56533
+ args: { supplier: "{id}", limit: 20 }
56534
+ }
55636
56535
  ],
55637
56536
  "Purchase Order": [
55638
- { label: "Receipt", message: "Show purchase receipts for order {id}" },
55639
- { label: "Invoice", message: "Show purchase invoices for order {id}" }
56537
+ {
56538
+ key: "receipt",
56539
+ label: "Receipt",
56540
+ message: "Show purchase receipts for order {id}",
56541
+ tool: "erpnext_doc_list",
56542
+ args: {
56543
+ doctype: "Purchase Receipt",
56544
+ fields: ["name", "supplier", "posting_date", "status"],
56545
+ filters: [["Purchase Receipt Item", "purchase_order", "=", "{id}"]],
56546
+ limit: 20
56547
+ }
56548
+ },
56549
+ {
56550
+ key: "invoice",
56551
+ label: "Invoice",
56552
+ message: "Show purchase invoices for order {id}",
56553
+ tool: "erpnext_doc_list",
56554
+ args: {
56555
+ doctype: "Purchase Invoice",
56556
+ fields: ["name", "supplier", "posting_date", "status", "grand_total"],
56557
+ filters: [["Purchase Invoice Item", "purchase_order", "=", "{id}"]],
56558
+ limit: 20
56559
+ }
56560
+ }
55640
56561
  ],
55641
56562
  "Employee": [
55642
- { label: "Attendance", message: "Show attendance for employee {id}" },
55643
- { label: "Leaves", message: "Show leave applications for employee {id}" }
56563
+ {
56564
+ key: "attendance",
56565
+ label: "Attendance",
56566
+ message: "Show attendance for employee {id}",
56567
+ tool: "erpnext_attendance_list",
56568
+ args: { employee: "{id}", limit: 20 }
56569
+ },
56570
+ {
56571
+ key: "leaves",
56572
+ label: "Leaves",
56573
+ message: "Show leave applications for employee {id}",
56574
+ tool: "erpnext_leave_application_list",
56575
+ args: { employee: "{id}", limit: 20 }
56576
+ }
55644
56577
  ],
55645
56578
  "Project": [
55646
- { label: "Tasks", message: "Show tasks for project {id}" },
55647
- { label: "Timesheets", message: "Show timesheets for project {id}" }
56579
+ {
56580
+ key: "tasks",
56581
+ label: "Tasks",
56582
+ message: "Show tasks for project {id}",
56583
+ tool: "erpnext_task_list",
56584
+ args: { project: "{id}", limit: 20 }
56585
+ },
56586
+ {
56587
+ key: "timesheets",
56588
+ label: "Timesheets",
56589
+ message: "Show timesheets for project {id}",
56590
+ tool: "erpnext_timesheet_list",
56591
+ args: { project: "{id}", limit: 20 }
56592
+ }
55648
56593
  ],
55649
56594
  "Task": [
55650
- { label: "Timesheets", message: "Show timesheets for task {id}" }
56595
+ {
56596
+ key: "timesheets",
56597
+ label: "Timesheets",
56598
+ message: "Show timesheets for task {id}",
56599
+ tool: "erpnext_doc_list",
56600
+ args: {
56601
+ doctype: "Timesheet",
56602
+ fields: ["name", "employee", "start_date", "total_hours", "status"],
56603
+ filters: [["Timesheet Detail", "task", "=", "{id}"]],
56604
+ limit: 20
56605
+ }
56606
+ }
55651
56607
  ],
55652
56608
  "Lead": [
55653
- { label: "Opportunities", message: "Show opportunities for lead {id}" }
56609
+ {
56610
+ key: "opportunities",
56611
+ label: "Opportunities",
56612
+ message: "Show opportunities for lead {id}",
56613
+ tool: "erpnext_opportunity_list",
56614
+ // Le handler exige opportunity_from avec party_name.
56615
+ args: { opportunity_from: "Lead", party_name: "{id}", limit: 20 }
56616
+ }
55654
56617
  ]
55655
56618
  };
55656
56619
  var KPI_DRILL_DOWN = {
55657
56620
  "erpnext_kpi_revenue": {
55658
- _drillDown: "Show all sales invoices for this month",
56621
+ _drillDown: "Show all sales orders for this month",
55659
56622
  _trendDrillDown: "Show revenue trend chart for the last 12 months"
55660
56623
  },
55661
56624
  "erpnext_kpi_outstanding": {
@@ -55667,7 +56630,7 @@ var KPI_DRILL_DOWN = {
55667
56630
  _trendDrillDown: "Show order breakdown chart for the last 6 months"
55668
56631
  },
55669
56632
  "erpnext_kpi_gross_margin": {
55670
- _drillDown: "Show gross profit breakdown by item",
56633
+ _drillDown: "Show the non-cancelled Sales Order Items and Bin valuation rates used to estimate this gross margin",
55671
56634
  _trendDrillDown: "Show profit and loss chart for the last 12 months"
55672
56635
  },
55673
56636
  "erpnext_kpi_overdue": {
@@ -55678,12 +56641,521 @@ var KPI_DRILL_DOWN = {
55678
56641
  var CHART_DRILL_DOWN = {
55679
56642
  "erpnext_sales_chart": "Show sales invoices for {label}",
55680
56643
  "erpnext_stock_chart": "Show stock balance for item {label}",
55681
- "erpnext_revenue_trend": "Show sales invoices for month {label}",
56644
+ "erpnext_revenue_trend": "Show sales orders for month {label}",
55682
56645
  "erpnext_order_breakdown": "Show sales orders for {label}",
55683
- "erpnext_ar_aging": "Show overdue sales invoices in the {label} aging bucket",
56646
+ "erpnext_ar_aging": "Show outstanding sales invoices for customer {label}",
55684
56647
  "erpnext_gross_profit": "Show gross profit details for {label}",
55685
- "erpnext_profit_loss": "Show accounting details for month {label}"
56648
+ "erpnext_profit_loss": "Show submitted sales and purchase orders for month {label}"
56649
+ };
56650
+ var INVOICE_ITEM_HINTS = [
56651
+ {
56652
+ key: "item",
56653
+ label: "Item",
56654
+ message: "Show item {item}",
56655
+ tool: "erpnext_item_get",
56656
+ args: { name: "{item}" },
56657
+ kind: "record"
56658
+ },
56659
+ {
56660
+ key: "stock",
56661
+ label: "Stock",
56662
+ message: "Show stock balance for item {item}",
56663
+ tool: "erpnext_stock_balance",
56664
+ args: { item_code: "{item}", limit: 50 },
56665
+ kind: "list"
56666
+ }
56667
+ ];
56668
+ var INVOICE_HINTS = {
56669
+ "Sales Invoice": [
56670
+ {
56671
+ key: "payments",
56672
+ label: "Payments",
56673
+ message: "Show payment entries for invoice {id}",
56674
+ tool: "erpnext_doc_list",
56675
+ args: {
56676
+ doctype: "Payment Entry",
56677
+ fields: [
56678
+ "name",
56679
+ "posting_date",
56680
+ "paid_amount",
56681
+ "mode_of_payment",
56682
+ "docstatus"
56683
+ ],
56684
+ filters: [["Payment Entry Reference", "reference_name", "=", "{id}"]],
56685
+ limit: 20
56686
+ },
56687
+ kind: "list"
56688
+ },
56689
+ {
56690
+ key: "customer",
56691
+ label: "Customer",
56692
+ message: "Show customer {party}",
56693
+ tool: "erpnext_customer_get",
56694
+ args: { name: "{party}" },
56695
+ kind: "record"
56696
+ },
56697
+ ...INVOICE_ITEM_HINTS
56698
+ ],
56699
+ "Purchase Invoice": [
56700
+ {
56701
+ key: "payments",
56702
+ label: "Payments",
56703
+ message: "Show payment entries for invoice {id}",
56704
+ tool: "erpnext_doc_list",
56705
+ args: {
56706
+ doctype: "Payment Entry",
56707
+ fields: [
56708
+ "name",
56709
+ "posting_date",
56710
+ "paid_amount",
56711
+ "mode_of_payment",
56712
+ "docstatus"
56713
+ ],
56714
+ filters: [["Payment Entry Reference", "reference_name", "=", "{id}"]],
56715
+ limit: 20
56716
+ },
56717
+ kind: "list"
56718
+ },
56719
+ {
56720
+ key: "supplier",
56721
+ label: "Supplier",
56722
+ message: "Show supplier {party}",
56723
+ tool: "erpnext_supplier_get",
56724
+ args: { name: "{party}" },
56725
+ kind: "record"
56726
+ },
56727
+ ...INVOICE_ITEM_HINTS
56728
+ ]
56729
+ };
56730
+ var STOCK_HINTS = [
56731
+ {
56732
+ key: "item",
56733
+ label: "Item",
56734
+ message: "Show item {id}",
56735
+ tool: "erpnext_item_get",
56736
+ args: { name: "{id}" },
56737
+ kind: "record"
56738
+ },
56739
+ {
56740
+ key: "movements",
56741
+ label: "Stock entries",
56742
+ message: "Show stock entries for item {id}",
56743
+ tool: "erpnext_doc_list",
56744
+ args: {
56745
+ doctype: "Stock Entry",
56746
+ fields: ["name", "posting_date", "stock_entry_type", "docstatus"],
56747
+ filters: [["Stock Entry Detail", "item_code", "=", "{id}"]],
56748
+ limit: 20
56749
+ },
56750
+ kind: "list"
56751
+ },
56752
+ {
56753
+ key: "warehouse",
56754
+ label: "Warehouse stock",
56755
+ message: "Show stock chart for warehouse {warehouse}",
56756
+ tool: "erpnext_stock_chart",
56757
+ args: { warehouse: "{warehouse}", limit: 10 },
56758
+ kind: "chart"
56759
+ }
56760
+ ];
56761
+ function monthRange(now) {
56762
+ const y = now.getFullYear();
56763
+ const m = now.getMonth();
56764
+ const pad = (n) => String(n).padStart(2, "0");
56765
+ const last = new Date(y, m + 1, 0).getDate();
56766
+ return {
56767
+ from: `${y}-${pad(m + 1)}-01`,
56768
+ to: `${y}-${pad(m + 1)}-${pad(last)}`,
56769
+ today: `${y}-${pad(m + 1)}-${pad(now.getDate())}`
56770
+ };
56771
+ }
56772
+ var INVOICE_LIST_FIELDS = [
56773
+ "name",
56774
+ "customer",
56775
+ "posting_date",
56776
+ "due_date",
56777
+ "status",
56778
+ "outstanding_amount"
56779
+ ];
56780
+ var KPI_JUMPS = {
56781
+ "erpnext_kpi_revenue": (r) => ({
56782
+ number: {
56783
+ key: "sales_orders_month",
56784
+ label: "Sales orders this month",
56785
+ tool: "erpnext_doc_list",
56786
+ args: {
56787
+ doctype: "Sales Order",
56788
+ fields: [
56789
+ "name",
56790
+ "customer",
56791
+ "transaction_date",
56792
+ "status",
56793
+ "grand_total"
56794
+ ],
56795
+ filters: [
56796
+ ["transaction_date", ">=", r.from],
56797
+ ["transaction_date", "<=", r.to],
56798
+ // comme le KPI : les commandes annulées ne comptent pas
56799
+ ["docstatus", "<", 2]
56800
+ ],
56801
+ limit: 20
56802
+ },
56803
+ kind: "list"
56804
+ },
56805
+ trend: {
56806
+ key: "revenue_trend",
56807
+ label: "Revenue trend",
56808
+ tool: "erpnext_revenue_trend",
56809
+ args: { months: 12 },
56810
+ kind: "chart"
56811
+ }
56812
+ }),
56813
+ "erpnext_kpi_outstanding": () => ({
56814
+ number: {
56815
+ key: "unpaid_invoices",
56816
+ label: "Unpaid invoices",
56817
+ tool: "erpnext_doc_list",
56818
+ args: {
56819
+ doctype: "Sales Invoice",
56820
+ fields: INVOICE_LIST_FIELDS,
56821
+ filters: [["outstanding_amount", ">", 0], ["docstatus", "=", 1]],
56822
+ limit: 20
56823
+ },
56824
+ kind: "list"
56825
+ },
56826
+ trend: {
56827
+ key: "receivables_aging",
56828
+ label: "Receivables aging",
56829
+ tool: "erpnext_ar_aging",
56830
+ args: {},
56831
+ kind: "chart"
56832
+ }
56833
+ }),
56834
+ "erpnext_kpi_orders": (r) => ({
56835
+ number: {
56836
+ key: "sales_orders_month",
56837
+ label: "Sales orders this month",
56838
+ tool: "erpnext_doc_list",
56839
+ args: {
56840
+ doctype: "Sales Order",
56841
+ fields: [
56842
+ "name",
56843
+ "customer",
56844
+ "transaction_date",
56845
+ "status",
56846
+ "grand_total"
56847
+ ],
56848
+ filters: [
56849
+ ["transaction_date", ">=", r.from],
56850
+ ["transaction_date", "<=", r.to],
56851
+ // comme le KPI : les commandes annulées ne comptent pas
56852
+ ["docstatus", "<", 2]
56853
+ ],
56854
+ limit: 20
56855
+ },
56856
+ kind: "list"
56857
+ },
56858
+ trend: {
56859
+ key: "order_breakdown",
56860
+ label: "Order breakdown",
56861
+ tool: "erpnext_order_breakdown",
56862
+ args: {},
56863
+ kind: "chart"
56864
+ }
56865
+ }),
56866
+ "erpnext_kpi_gross_margin": () => ({
56867
+ trend: {
56868
+ key: "profit_loss",
56869
+ label: "Profit and loss",
56870
+ tool: "erpnext_profit_loss",
56871
+ args: { months: 12 },
56872
+ kind: "chart"
56873
+ }
56874
+ }),
56875
+ "erpnext_kpi_overdue": (r) => ({
56876
+ number: {
56877
+ key: "overdue_invoices",
56878
+ label: "Overdue invoices",
56879
+ tool: "erpnext_doc_list",
56880
+ args: {
56881
+ doctype: "Sales Invoice",
56882
+ fields: INVOICE_LIST_FIELDS,
56883
+ filters: [
56884
+ ["due_date", "<", r.today],
56885
+ ["outstanding_amount", ">", 0],
56886
+ ["docstatus", "=", 1]
56887
+ ],
56888
+ limit: 20
56889
+ },
56890
+ kind: "list"
56891
+ },
56892
+ trend: {
56893
+ key: "receivables_aging",
56894
+ label: "Receivables aging",
56895
+ tool: "erpnext_ar_aging",
56896
+ args: {},
56897
+ kind: "chart"
56898
+ }
56899
+ })
56900
+ };
56901
+ var FUNNEL_LIST_FIELDS = {
56902
+ "Leads": [
56903
+ "name",
56904
+ "lead_name",
56905
+ "company_name",
56906
+ "status",
56907
+ "lead_owner",
56908
+ "creation"
56909
+ ],
56910
+ "Opportunities": [
56911
+ "name",
56912
+ "opportunity_from",
56913
+ "party_name",
56914
+ "status",
56915
+ "opportunity_amount",
56916
+ "currency",
56917
+ "probability",
56918
+ "opportunity_owner",
56919
+ "transaction_date"
56920
+ ],
56921
+ "Quotations": [
56922
+ "name",
56923
+ "party_name",
56924
+ "transaction_date",
56925
+ "status",
56926
+ "grand_total"
56927
+ ],
56928
+ "Orders": [
56929
+ "name",
56930
+ "customer",
56931
+ "transaction_date",
56932
+ "status",
56933
+ "grand_total"
56934
+ ]
56935
+ };
56936
+ var FUNNEL_STAGE_JUMPS = {
56937
+ "Leads": {
56938
+ key: "leads",
56939
+ label: "Leads",
56940
+ tool: "erpnext_lead_list",
56941
+ args: { limit: 20 },
56942
+ kind: "list"
56943
+ },
56944
+ "Opportunities": {
56945
+ key: "opportunities",
56946
+ label: "Opportunities",
56947
+ tool: "erpnext_opportunity_list",
56948
+ args: { limit: 20 },
56949
+ kind: "list"
56950
+ },
56951
+ "Quotations": {
56952
+ key: "quotations",
56953
+ label: "Quotations",
56954
+ tool: "erpnext_doc_list",
56955
+ args: {
56956
+ doctype: "Quotation",
56957
+ fields: FUNNEL_LIST_FIELDS.Quotations,
56958
+ filters: [["docstatus", "!=", 2]],
56959
+ limit: 20
56960
+ },
56961
+ kind: "list"
56962
+ },
56963
+ "Orders": {
56964
+ key: "sales_orders",
56965
+ label: "Sales orders",
56966
+ tool: "erpnext_doc_list",
56967
+ args: {
56968
+ doctype: "Sales Order",
56969
+ fields: FUNNEL_LIST_FIELDS.Orders,
56970
+ filters: [["docstatus", "!=", 2]],
56971
+ limit: 20
56972
+ },
56973
+ kind: "list"
56974
+ }
55686
56975
  };
56976
+ function funnelPeriodStart(period, now) {
56977
+ const year = now.getFullYear();
56978
+ const pad = (value) => String(value).padStart(2, "0");
56979
+ if (period === "this_month") {
56980
+ return `${year}-${pad(now.getMonth() + 1)}-01`;
56981
+ }
56982
+ if (period === "this_quarter") {
56983
+ const firstMonth = Math.floor(now.getMonth() / 3) * 3 + 1;
56984
+ return `${year}-${pad(firstMonth)}-01`;
56985
+ }
56986
+ if (period === "this_year") return `${year}-01-01`;
56987
+ return void 0;
56988
+ }
56989
+ function funnelStageJumps(args, now) {
56990
+ const since = funnelPeriodStart(args.period, now);
56991
+ if (!since) return FUNNEL_STAGE_JUMPS;
56992
+ const scopedJump = (stage, doctype, dateField) => {
56993
+ const base = FUNNEL_STAGE_JUMPS[stage];
56994
+ const baseFilters = Array.isArray(base.args.filters) ? base.args.filters : [];
56995
+ return {
56996
+ ...base,
56997
+ tool: "erpnext_doc_list",
56998
+ args: {
56999
+ doctype,
57000
+ fields: FUNNEL_LIST_FIELDS[stage],
57001
+ filters: [[dateField, ">=", since], ...baseFilters],
57002
+ limit: 20
57003
+ }
57004
+ };
57005
+ };
57006
+ return {
57007
+ "Leads": scopedJump("Leads", "Lead", "creation"),
57008
+ "Opportunities": scopedJump(
57009
+ "Opportunities",
57010
+ "Opportunity",
57011
+ "transaction_date"
57012
+ ),
57013
+ "Quotations": scopedJump(
57014
+ "Quotations",
57015
+ "Quotation",
57016
+ "transaction_date"
57017
+ ),
57018
+ "Orders": scopedJump("Orders", "Sales Order", "transaction_date")
57019
+ };
57020
+ }
57021
+ var SALES_ORDER_LIST_FIELDS = [
57022
+ "name",
57023
+ "customer",
57024
+ "transaction_date",
57025
+ "status",
57026
+ "grand_total"
57027
+ ];
57028
+ var PURCHASE_ORDER_LIST_FIELDS = [
57029
+ "name",
57030
+ "supplier",
57031
+ "transaction_date",
57032
+ "status",
57033
+ "grand_total"
57034
+ ];
57035
+ function monthLabel(d) {
57036
+ return `${d.toLocaleString("en", { month: "short" })} ${d.getFullYear().toString().slice(2)}`;
57037
+ }
57038
+ function monthBuckets(monthsBack, now) {
57039
+ const buckets = [];
57040
+ for (let m = 0; m < monthsBack; m++) {
57041
+ const d = new Date(
57042
+ now.getFullYear(),
57043
+ now.getMonth() - monthsBack + 1 + m,
57044
+ 1
57045
+ );
57046
+ buckets.push({ label: monthLabel(d), range: monthRange(d) });
57047
+ }
57048
+ return buckets;
57049
+ }
57050
+ var docList = (label, doctype, fields, filters) => ({
57051
+ label,
57052
+ tool: "erpnext_doc_list",
57053
+ args: { doctype, fields, filters, limit: 20 },
57054
+ kind: "list"
57055
+ });
57056
+ function chartPointJumps(toolName, args, labels, now) {
57057
+ const jumps = {};
57058
+ const byLabel = (make) => {
57059
+ for (const label of labels) {
57060
+ if (label === "Unknown") continue;
57061
+ jumps[label] = make(label);
57062
+ }
57063
+ };
57064
+ switch (toolName) {
57065
+ case "erpnext_revenue_trend": {
57066
+ const months = Number(args.months ?? 6);
57067
+ for (const { label, range } of monthBuckets(months, now)) {
57068
+ if (!labels.includes(label)) continue;
57069
+ jumps[label] = docList(label, "Sales Order", SALES_ORDER_LIST_FIELDS, [
57070
+ ["transaction_date", ">=", range.from],
57071
+ ["transaction_date", "<=", range.to],
57072
+ ["docstatus", "<", 2]
57073
+ ]);
57074
+ }
57075
+ break;
57076
+ }
57077
+ case "erpnext_sales_chart": {
57078
+ const groupBy = String(args.group_by ?? "customer");
57079
+ byLabel((label) => {
57080
+ const filters = [
57081
+ groupBy === "item" ? ["Sales Invoice Item", "item_name", "=", label] : groupBy === "status" ? ["status", "=", label] : ["customer_name", "=", label]
57082
+ ];
57083
+ if (groupBy === "status") {
57084
+ filters.push(["docstatus", "!=", 2]);
57085
+ } else if (groupBy === "item" || args.include_drafts !== true) {
57086
+ filters.push(["docstatus", "=", 1]);
57087
+ }
57088
+ return docList(label, "Sales Invoice", INVOICE_LIST_FIELDS, filters);
57089
+ });
57090
+ break;
57091
+ }
57092
+ case "erpnext_order_breakdown":
57093
+ case "erpnext_revenue_vs_orders":
57094
+ byLabel(
57095
+ (label) => docList(label, "Sales Order", SALES_ORDER_LIST_FIELDS, [
57096
+ ["customer_name", "=", label],
57097
+ ["docstatus", "<", 2]
57098
+ ])
57099
+ );
57100
+ break;
57101
+ case "erpnext_ar_aging":
57102
+ case "erpnext_profit_loss":
57103
+ return void 0;
57104
+ case "erpnext_gross_profit": {
57105
+ const groupBy = String(args.group_by ?? "item");
57106
+ byLabel(
57107
+ (label) => docList(label, "Sales Invoice", INVOICE_LIST_FIELDS, [
57108
+ groupBy === "customer" ? ["customer_name", "=", label] : ["Sales Invoice Item", "item_name", "=", label],
57109
+ ["docstatus", "=", 1]
57110
+ ])
57111
+ );
57112
+ break;
57113
+ }
57114
+ case "erpnext_stock_chart":
57115
+ byLabel((label) => ({
57116
+ label,
57117
+ tool: "erpnext_stock_balance",
57118
+ args: {
57119
+ item_code: label,
57120
+ ...typeof args.warehouse === "string" ? { warehouse: args.warehouse } : {},
57121
+ limit: 50
57122
+ },
57123
+ kind: "list"
57124
+ }));
57125
+ break;
57126
+ default:
57127
+ return void 0;
57128
+ }
57129
+ return Object.keys(jumps).length > 0 ? jumps : void 0;
57130
+ }
57131
+ function chartSeriesPointJumps(toolName, args, labels, now) {
57132
+ if (toolName !== "erpnext_profit_loss") return void 0;
57133
+ const jumps = {};
57134
+ const months = Number(args.months ?? 6);
57135
+ for (const { label, range } of monthBuckets(months, now)) {
57136
+ if (!labels.includes(label)) continue;
57137
+ const filters = [
57138
+ ["transaction_date", ">=", range.from],
57139
+ ["transaction_date", "<=", range.to],
57140
+ ["docstatus", "=", 1]
57141
+ ];
57142
+ jumps[label] = {
57143
+ "Income": docList(
57144
+ `${label} \xB7 Income`,
57145
+ "Sales Order",
57146
+ SALES_ORDER_LIST_FIELDS,
57147
+ filters
57148
+ ),
57149
+ "Expenses": docList(
57150
+ `${label} \xB7 Expenses`,
57151
+ "Purchase Order",
57152
+ PURCHASE_ORDER_LIST_FIELDS,
57153
+ filters
57154
+ )
57155
+ };
57156
+ }
57157
+ return Object.keys(jumps).length > 0 ? jumps : void 0;
57158
+ }
55687
57159
  function isChartViewer(result) {
55688
57160
  const uri = result._meta?.ui?.resourceUri;
55689
57161
  return uri === "ui://mcp-erpnext/chart-viewer";
@@ -55692,6 +57164,28 @@ function isKpiViewer(result) {
55692
57164
  const uri = result._meta?.ui?.resourceUri;
55693
57165
  return uri === "ui://mcp-erpnext/kpi-viewer";
55694
57166
  }
57167
+ function isFunnelViewer(result) {
57168
+ const uri = result._meta?.ui?.resourceUri;
57169
+ return uri === "ui://mcp-erpnext/funnel-viewer";
57170
+ }
57171
+ function resultDoctype(result) {
57172
+ if (typeof result.doctype === "string") return result.doctype;
57173
+ const data = result.data;
57174
+ if (isRecord7(data) && typeof data.doctype === "string") return data.doctype;
57175
+ return void 0;
57176
+ }
57177
+ function isInvoiceViewer(result) {
57178
+ const uri = result._meta?.ui?.resourceUri;
57179
+ return uri === "ui://mcp-erpnext/invoice-viewer";
57180
+ }
57181
+ function isStockViewer(result) {
57182
+ const uri = result._meta?.ui?.resourceUri;
57183
+ return uri === "ui://mcp-erpnext/stock-viewer";
57184
+ }
57185
+ function isKanbanViewer(result) {
57186
+ const uri = result._meta?.ui?.resourceUri;
57187
+ return uri === "ui://mcp-erpnext/kanban-viewer";
57188
+ }
55695
57189
  function isDoclistResult(result) {
55696
57190
  return typeof result.doctype === "string" && Array.isArray(result.data);
55697
57191
  }
@@ -55699,7 +57193,193 @@ function isDoclistViewer(result) {
55699
57193
  const uri = result._meta?.ui?.resourceUri;
55700
57194
  return uri === "ui://mcp-erpnext/doclist-viewer";
55701
57195
  }
55702
- function withUiRefreshRequest(result, toolName, args) {
57196
+ function isDocViewer(result) {
57197
+ const uri = result._meta?.ui?.resourceUri;
57198
+ return uri === "ui://mcp-erpnext/doc-viewer";
57199
+ }
57200
+ var VIEWER_TOOL_CANDIDATES = {
57201
+ "ui://mcp-erpnext/invoice-viewer": [
57202
+ "erpnext_item_get",
57203
+ "erpnext_stock_balance",
57204
+ "erpnext_file_list",
57205
+ "erpnext_file_upload",
57206
+ "erpnext_file_download"
57207
+ ],
57208
+ "ui://mcp-erpnext/doclist-viewer": [],
57209
+ "ui://mcp-erpnext/doc-viewer": [
57210
+ "erpnext_file_list",
57211
+ "erpnext_file_upload",
57212
+ "erpnext_file_download"
57213
+ ],
57214
+ "ui://mcp-erpnext/kanban-viewer": [
57215
+ "erpnext_doc_get",
57216
+ "erpnext_doc_update",
57217
+ "erpnext_user_list",
57218
+ "erpnext_doc_assign",
57219
+ "erpnext_doc_unassign"
57220
+ ],
57221
+ "ui://mcp-erpnext/stock-viewer": [
57222
+ "erpnext_item_get",
57223
+ "erpnext_stock_entry_list"
57224
+ ]
57225
+ };
57226
+ var SUBMITTABLE_VIEWER_DOCTYPES = /* @__PURE__ */ new Set([
57227
+ "Quotation",
57228
+ "Sales Order",
57229
+ "Delivery Note",
57230
+ "Sales Invoice",
57231
+ "Purchase Order",
57232
+ "Purchase Receipt",
57233
+ "Purchase Invoice",
57234
+ "Stock Entry",
57235
+ "Journal Entry",
57236
+ "Payment Entry",
57237
+ "Timesheet",
57238
+ "Leave Application",
57239
+ "Salary Slip",
57240
+ "BOM",
57241
+ "Work Order",
57242
+ "Job Card",
57243
+ "Asset",
57244
+ "Asset Movement",
57245
+ "Shipment"
57246
+ ]);
57247
+ var INVOICE_DEDICATED_MUTATION_TOOLS = {
57248
+ "Sales Order": [
57249
+ "erpnext_sales_order_submit",
57250
+ "erpnext_sales_order_cancel"
57251
+ ],
57252
+ "Sales Invoice": ["erpnext_sales_invoice_submit"]
57253
+ };
57254
+ function addDoctypeMutationCandidates(target, uri, doctype) {
57255
+ if (!doctype || !SUBMITTABLE_VIEWER_DOCTYPES.has(doctype)) return;
57256
+ if (uri === "ui://mcp-erpnext/invoice-viewer" || uri === "ui://mcp-erpnext/doclist-viewer" || uri === "ui://mcp-erpnext/doc-viewer") {
57257
+ target.add("erpnext_doc_submit");
57258
+ target.add("erpnext_doc_cancel");
57259
+ }
57260
+ if (uri === "ui://mcp-erpnext/invoice-viewer") {
57261
+ for (const tool of INVOICE_DEDICATED_MUTATION_TOOLS[doctype] ?? []) {
57262
+ target.add(tool);
57263
+ }
57264
+ }
57265
+ }
57266
+ function addJumpTools(target, jumps) {
57267
+ if (!jumps) return;
57268
+ for (const jump of Object.values(jumps)) target.add(jump.tool);
57269
+ }
57270
+ function addSeriesJumpTools(target, jumps) {
57271
+ if (!jumps) return;
57272
+ for (const seriesJumps of Object.values(jumps)) {
57273
+ addJumpTools(target, seriesJumps);
57274
+ }
57275
+ }
57276
+ function availableViewerToolNames(result, availableToolNames) {
57277
+ const candidates = /* @__PURE__ */ new Set();
57278
+ const uri = result._meta?.ui?.resourceUri;
57279
+ for (const name of uri ? VIEWER_TOOL_CANDIDATES[uri] ?? [] : []) {
57280
+ candidates.add(name);
57281
+ }
57282
+ addDoctypeMutationCandidates(candidates, uri, resultDoctype(result));
57283
+ if (result.refreshRequest) candidates.add(result.refreshRequest.toolName);
57284
+ if (result._rowAction) candidates.add(result._rowAction.toolName);
57285
+ for (const hint of result._sendMessageHints ?? []) {
57286
+ if (hint.tool) candidates.add(hint.tool);
57287
+ }
57288
+ addJumpTools(
57289
+ candidates,
57290
+ result._jumps ? Object.fromEntries(
57291
+ Object.entries(result._jumps).filter(
57292
+ (entry) => entry[1] !== void 0
57293
+ )
57294
+ ) : void 0
57295
+ );
57296
+ addJumpTools(candidates, result._stageJumps);
57297
+ addJumpTools(candidates, result._pointJumps);
57298
+ addSeriesJumpTools(candidates, result._seriesPointJumps);
57299
+ if (typeof result.moveToolName === "string") {
57300
+ candidates.add(result.moveToolName);
57301
+ }
57302
+ return [...candidates].filter((name) => availableToolNames.has(name)).sort();
57303
+ }
57304
+ function filterJumpMap(jumps, availableToolNames) {
57305
+ const available = Object.fromEntries(
57306
+ Object.entries(jumps).filter(
57307
+ ([, jump]) => availableToolNames.has(jump.tool)
57308
+ )
57309
+ );
57310
+ return Object.keys(available).length > 0 ? available : void 0;
57311
+ }
57312
+ function filterSeriesJumpMap(jumps, availableToolNames) {
57313
+ const available = {};
57314
+ for (const [label, seriesJumps] of Object.entries(jumps)) {
57315
+ const filtered = filterJumpMap(seriesJumps, availableToolNames);
57316
+ if (filtered) available[label] = filtered;
57317
+ }
57318
+ return Object.keys(available).length > 0 ? available : void 0;
57319
+ }
57320
+ function filterNavJumpsByAvailableTools(result, availableToolNames) {
57321
+ if (!availableToolNames) return result;
57322
+ const filtered = { ...result };
57323
+ if (result._jumps) {
57324
+ const jumps = filterJumpMap(
57325
+ Object.fromEntries(
57326
+ Object.entries(result._jumps).filter(
57327
+ (entry) => entry[1] !== void 0
57328
+ )
57329
+ ),
57330
+ availableToolNames
57331
+ );
57332
+ if (jumps) filtered._jumps = jumps;
57333
+ else delete filtered._jumps;
57334
+ }
57335
+ if (result._stageJumps) {
57336
+ const jumps = filterJumpMap(result._stageJumps, availableToolNames);
57337
+ if (jumps) filtered._stageJumps = jumps;
57338
+ else delete filtered._stageJumps;
57339
+ }
57340
+ if (result._pointJumps) {
57341
+ const jumps = filterJumpMap(result._pointJumps, availableToolNames);
57342
+ if (jumps) filtered._pointJumps = jumps;
57343
+ else delete filtered._pointJumps;
57344
+ }
57345
+ if (result._seriesPointJumps) {
57346
+ const jumps = filterSeriesJumpMap(
57347
+ result._seriesPointJumps,
57348
+ availableToolNames
57349
+ );
57350
+ if (jumps) filtered._seriesPointJumps = jumps;
57351
+ else delete filtered._seriesPointJumps;
57352
+ }
57353
+ if (result._rowAction && !availableToolNames.has(result._rowAction.toolName)) {
57354
+ const doctype = resultDoctype(result);
57355
+ if (doctype && availableToolNames.has("erpnext_doc_get")) {
57356
+ filtered._rowAction = {
57357
+ toolName: "erpnext_doc_get",
57358
+ idField: "name",
57359
+ argName: "name",
57360
+ extraArgs: { doctype }
57361
+ };
57362
+ } else {
57363
+ delete filtered._rowAction;
57364
+ }
57365
+ }
57366
+ if (result._sendMessageHints) {
57367
+ filtered._sendMessageHints = result._sendMessageHints.map((hint) => {
57368
+ if (!hint.tool || availableToolNames.has(hint.tool)) return hint;
57369
+ return { key: hint.key, label: hint.label, message: hint.message };
57370
+ });
57371
+ }
57372
+ filtered._availableTools = availableViewerToolNames(
57373
+ filtered,
57374
+ availableToolNames
57375
+ );
57376
+ return filtered;
57377
+ }
57378
+ function withViewerToolCapabilities(result, availableToolNames) {
57379
+ if (!isRecord7(result) || !hasUiResource(result)) return result;
57380
+ return filterNavJumpsByAvailableTools(result, availableToolNames);
57381
+ }
57382
+ function withUiRefreshRequest(result, toolName, args, now = /* @__PURE__ */ new Date(), availableToolNames) {
55703
57383
  if (!isRecord7(result) || !hasUiResource(result)) {
55704
57384
  return result;
55705
57385
  }
@@ -55749,10 +57429,92 @@ function withUiRefreshRequest(result, toolName, args) {
55749
57429
  }
55750
57430
  }
55751
57431
  }
55752
- return enriched;
57432
+ if (isKpiViewer(enriched) && !enriched._jumps) {
57433
+ const jumps = KPI_JUMPS[toolName];
57434
+ if (jumps) enriched._jumps = jumps(monthRange(now));
57435
+ }
57436
+ if (isFunnelViewer(enriched) && !enriched._stageJumps) {
57437
+ enriched._stageJumps = funnelStageJumps(args, now);
57438
+ }
57439
+ if (isChartViewer(enriched) && Array.isArray(enriched.labels)) {
57440
+ const labels = enriched.labels.map(String);
57441
+ if (!enriched._pointJumps) {
57442
+ const jumps = chartPointJumps(toolName, args, labels, now);
57443
+ if (jumps) enriched._pointJumps = jumps;
57444
+ }
57445
+ if (!enriched._seriesPointJumps) {
57446
+ const jumps = chartSeriesPointJumps(toolName, args, labels, now);
57447
+ if (jumps) enriched._seriesPointJumps = jumps;
57448
+ }
57449
+ }
57450
+ if (!enriched._sendMessageHints) {
57451
+ const doctype = resultDoctype(enriched);
57452
+ if (isInvoiceViewer(enriched) && doctype) {
57453
+ const hints = INVOICE_HINTS[doctype];
57454
+ if (hints) enriched._sendMessageHints = hints;
57455
+ } else if (isStockViewer(enriched)) {
57456
+ enriched._sendMessageHints = STOCK_HINTS;
57457
+ } else if ((isKanbanViewer(enriched) || isDocViewer(enriched)) && doctype) {
57458
+ const hints = DOCTYPE_SEND_MESSAGE_HINTS[doctype];
57459
+ if (hints) enriched._sendMessageHints = hints;
57460
+ }
57461
+ }
57462
+ return filterNavJumpsByAvailableTools(enriched, availableToolNames);
55753
57463
  }
55754
57464
 
55755
57465
  // src/client.ts
57466
+ function isRecord8(value) {
57467
+ return typeof value === "object" && value !== null && !Array.isArray(value);
57468
+ }
57469
+ function isSingleEmbeddedBlobResult(value) {
57470
+ if (!isRecord8(value) || !Array.isArray(value.content)) return false;
57471
+ if (Object.keys(value).some((key) => key !== "content")) return false;
57472
+ if (value.content.length !== 2) return false;
57473
+ const [summary, embedded] = value.content;
57474
+ if (!isRecord8(summary) || summary.type !== "text" || typeof summary.text !== "string") {
57475
+ return false;
57476
+ }
57477
+ if (!isRecord8(embedded) || embedded.type !== "resource") return false;
57478
+ if (!isRecord8(embedded.resource)) return false;
57479
+ const resource = embedded.resource;
57480
+ return typeof resource.uri === "string" && resource.uri.startsWith("file:///") && typeof resource.mimeType === "string" && typeof resource.blob === "string";
57481
+ }
57482
+ function withSafeUiRefresh(result, tool, args, availableToolNames) {
57483
+ const readOnly = tool.annotations?.readOnlyHint === true;
57484
+ if (!isRecord8(result)) {
57485
+ return result;
57486
+ }
57487
+ const resultMeta = isRecord8(result._meta) ? result._meta : {};
57488
+ const declaredUi = tool._meta?.ui;
57489
+ const viewerResult = declaredUi && !isRecord8(resultMeta.ui) ? {
57490
+ ...result,
57491
+ _meta: { ...resultMeta, ...tool._meta, ui: declaredUi }
57492
+ } : result;
57493
+ const request = isRecord8(viewerResult.refreshRequest) ? viewerResult.refreshRequest : null;
57494
+ const target = request && typeof request.toolName === "string" ? getToolByName(request.toolName) : void 0;
57495
+ const hasSafeExplicitRefresh = request !== null && typeof request.toolName === "string" && availableToolNames.has(request.toolName) && isRecord8(request.arguments) && (request.toolName === tool.name ? readOnly : target?.annotations?.readOnlyHint === true);
57496
+ if (hasSafeExplicitRefresh) {
57497
+ return withUiRefreshRequest(
57498
+ viewerResult,
57499
+ tool.name,
57500
+ args,
57501
+ /* @__PURE__ */ new Date(),
57502
+ availableToolNames
57503
+ );
57504
+ }
57505
+ let sanitized = viewerResult;
57506
+ if ("refreshRequest" in viewerResult) {
57507
+ sanitized = { ...viewerResult };
57508
+ delete sanitized.refreshRequest;
57509
+ }
57510
+ return readOnly ? withUiRefreshRequest(
57511
+ sanitized,
57512
+ tool.name,
57513
+ args,
57514
+ /* @__PURE__ */ new Date(),
57515
+ availableToolNames
57516
+ ) : withViewerToolCapabilities(sanitized, availableToolNames);
57517
+ }
55756
57518
  var ErpNextToolsClient = class {
55757
57519
  tools;
55758
57520
  enableLinkDisambiguation;
@@ -55793,6 +57555,7 @@ var ErpNextToolsClient = class {
55793
57555
  */
55794
57556
  buildHandlersMap() {
55795
57557
  const handlers = /* @__PURE__ */ new Map();
57558
+ const availableToolNames = new Set(this.tools.map((tool) => tool.name));
55796
57559
  for (const tool of this.tools) {
55797
57560
  const toolMeta = tool._meta;
55798
57561
  handlers.set(tool.name, async (args, mcpContext) => {
@@ -55815,10 +57578,14 @@ var ErpNextToolsClient = class {
55815
57578
  if (execution.result !== null && typeof execution.result === "object" && !Array.isArray(execution.result) && execution.result.resultType === "input_required") {
55816
57579
  return execution.result;
55817
57580
  }
55818
- const result = withUiRefreshRequest(
57581
+ if (tool.name === "erpnext_file_download" && isSingleEmbeddedBlobResult(execution.result)) {
57582
+ return execution.result;
57583
+ }
57584
+ const result = withSafeUiRefresh(
55819
57585
  execution.result,
55820
- tool.name,
55821
- execution.args
57586
+ tool,
57587
+ execution.args,
57588
+ availableToolNames
55822
57589
  );
55823
57590
  const r = result !== null && typeof result === "object" && !Array.isArray(result) ? result : null;
55824
57591
  const resultUi = r?._meta && typeof r._meta === "object" && r._meta.ui;
@@ -55845,7 +57612,15 @@ var ErpNextToolsClient = class {
55845
57612
  }
55846
57613
  const client = getFrappeClient();
55847
57614
  const result = await tool.handler(args, { client });
55848
- return withUiRefreshRequest(result, tool.name, args);
57615
+ if (tool.name === "erpnext_file_download" && isSingleEmbeddedBlobResult(result)) {
57616
+ return result;
57617
+ }
57618
+ return withSafeUiRefresh(
57619
+ result,
57620
+ tool,
57621
+ args,
57622
+ new Set(this.tools.map((candidate) => candidate.name))
57623
+ );
55849
57624
  }
55850
57625
  /** Get tool count */
55851
57626
  get count() {
@@ -55859,6 +57634,7 @@ var UI_VIEWERS = [
55859
57634
  "invoice-viewer",
55860
57635
  "stock-viewer",
55861
57636
  "doclist-viewer",
57637
+ "doc-viewer",
55862
57638
  "chart-viewer",
55863
57639
  "kpi-viewer",
55864
57640
  "funnel-viewer",
@@ -56102,7 +57878,7 @@ async function main() {
56102
57878
  );
56103
57879
  const server = new McpApp({
56104
57880
  name: "mcp-erpnext",
56105
- version: "3.0.2",
57881
+ version: "3.1.0-beta.2",
56106
57882
  transport: "stateless",
56107
57883
  cache: {
56108
57884
  ttlMs: 36e5,