@casys/mcp-erpnext 3.1.0-beta.7 → 3.1.0-beta.9

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
@@ -1784,14 +1784,14 @@ var require_code3 = __commonJS({
1784
1784
  }
1785
1785
  exports.callValidateCode = callValidateCode;
1786
1786
  var newRegExp = (0, codegen_1._)`new RegExp`;
1787
- function usePattern({ gen, it: { opts } }, pattern) {
1787
+ function usePattern({ gen, it: { opts } }, pattern2) {
1788
1788
  const u = opts.unicodeRegExp ? "u" : "";
1789
1789
  const { regExp } = opts.code;
1790
- const rx = regExp(pattern, u);
1790
+ const rx = regExp(pattern2, u);
1791
1791
  return gen.scopeValue("pattern", {
1792
1792
  key: rx.toString(),
1793
1793
  ref: rx,
1794
- code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${pattern}, ${u})`
1794
+ code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${pattern2}, ${u})`
1795
1795
  });
1796
1796
  }
1797
1797
  exports.usePattern = usePattern;
@@ -6901,7 +6901,7 @@ var require_discriminator2 = __commonJS({
6901
6901
  error: error2,
6902
6902
  code(cxt) {
6903
6903
  const { gen, data, schema, parentSchema, it } = cxt;
6904
- const { oneOf } = parentSchema;
6904
+ const { oneOf: oneOf2 } = parentSchema;
6905
6905
  if (!it.opts.discriminator) {
6906
6906
  throw new Error("discriminator: requires discriminator option");
6907
6907
  }
@@ -6910,7 +6910,7 @@ var require_discriminator2 = __commonJS({
6910
6910
  throw new Error("discriminator: requires propertyName");
6911
6911
  if (schema.mapping)
6912
6912
  throw new Error("discriminator: mapping is not supported");
6913
- if (!oneOf)
6913
+ if (!oneOf2)
6914
6914
  throw new Error("discriminator: requires oneOf keyword");
6915
6915
  const valid = gen.let("valid", false);
6916
6916
  const tag2 = gen.const("tag", (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(tagName)}`);
@@ -6938,8 +6938,8 @@ var require_discriminator2 = __commonJS({
6938
6938
  const oneOfMapping = {};
6939
6939
  const topRequired = hasRequired(parentSchema);
6940
6940
  let tagRequired = true;
6941
- for (let i = 0; i < oneOf.length; i++) {
6942
- let sch = oneOf[i];
6941
+ for (let i = 0; i < oneOf2.length; i++) {
6942
+ let sch = oneOf2[i];
6943
6943
  if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) {
6944
6944
  const ref = sch.$ref;
6945
6945
  sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref);
@@ -9021,9 +9021,9 @@ ${indent}`) + "'";
9021
9021
  return quotedString(value, ctx);
9022
9022
  }
9023
9023
  const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? " " : "");
9024
- const literal2 = blockQuote === "literal" ? true : blockQuote === "folded" || type === Scalar.Scalar.BLOCK_FOLDED ? false : type === Scalar.Scalar.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, lineWidth, indent.length);
9024
+ const literal3 = blockQuote === "literal" ? true : blockQuote === "folded" || type === Scalar.Scalar.BLOCK_FOLDED ? false : type === Scalar.Scalar.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, lineWidth, indent.length);
9025
9025
  if (!value)
9026
- return literal2 ? "|\n" : ">\n";
9026
+ return literal3 ? "|\n" : ">\n";
9027
9027
  let chomp;
9028
9028
  let endStart;
9029
9029
  for (endStart = value.length; endStart > 0; --endStart) {
@@ -9072,7 +9072,7 @@ ${indent}`) + "'";
9072
9072
  if (onComment)
9073
9073
  onComment();
9074
9074
  }
9075
- if (!literal2) {
9075
+ if (!literal3) {
9076
9076
  const foldedValue = value.replace(/\n+/g, "\n$&").replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, "$1$2").replace(/\n+/g, `$&${indent}`);
9077
9077
  let literalFallback = false;
9078
9078
  const foldOptions = getFoldOptions(ctx, true);
@@ -16815,8 +16815,8 @@ var $ZodCheckUpperCase = /* @__PURE__ */ $constructor("$ZodCheckUpperCase", (ins
16815
16815
  var $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst, def) => {
16816
16816
  $ZodCheck.init(inst, def);
16817
16817
  const escapedRegex = escapeRegex(def.includes);
16818
- const pattern = new RegExp(typeof def.position === "number" ? `^.{${def.position},}${escapedRegex}` : escapedRegex);
16819
- def.pattern = pattern;
16818
+ const pattern2 = new RegExp(typeof def.position === "number" ? `^.{${def.position},}${escapedRegex}` : escapedRegex);
16819
+ def.pattern = pattern2;
16820
16820
  inst._zod.check = (payload) => {
16821
16821
  if (payload.value.includes(def.includes, def.position))
16822
16822
  return;
@@ -16833,8 +16833,8 @@ var $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst,
16833
16833
  });
16834
16834
  var $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (inst, def) => {
16835
16835
  $ZodCheck.init(inst, def);
16836
- const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);
16837
- def.pattern ?? (def.pattern = pattern);
16836
+ const pattern2 = new RegExp(`^${escapeRegex(def.prefix)}.*`);
16837
+ def.pattern ?? (def.pattern = pattern2);
16838
16838
  inst._zod.check = (payload) => {
16839
16839
  if (payload.value.startsWith(def.prefix))
16840
16840
  return;
@@ -16851,8 +16851,8 @@ var $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (i
16851
16851
  });
16852
16852
  var $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst, def) => {
16853
16853
  $ZodCheck.init(inst, def);
16854
- const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);
16855
- def.pattern ?? (def.pattern = pattern);
16854
+ const pattern2 = new RegExp(`.*${escapeRegex(def.suffix)}$`);
16855
+ def.pattern ?? (def.pattern = pattern2);
16856
16856
  inst._zod.check = (payload) => {
16857
16857
  if (payload.value.endsWith(def.suffix))
16858
16858
  return;
@@ -18369,8 +18369,8 @@ var $ZodOptional = /* @__PURE__ */ $constructor("$ZodOptional", (inst, def) => {
18369
18369
  return values ? /* @__PURE__ */ new Set([...values, void 0]) : void 0;
18370
18370
  });
18371
18371
  defineLazyInternal(inst, "pattern", (zod) => {
18372
- const pattern = zod.def.innerType._zod.pattern;
18373
- return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0;
18372
+ const pattern2 = zod.def.innerType._zod.pattern;
18373
+ return pattern2 ? new RegExp(`^(${cleanRegex(pattern2.source)})?$`) : void 0;
18374
18374
  });
18375
18375
  inst._zod.parse = (payload, ctx) => {
18376
18376
  if (payload.value === void 0) {
@@ -18397,8 +18397,8 @@ var $ZodNullable = /* @__PURE__ */ $constructor("$ZodNullable", (inst, def) => {
18397
18397
  defineLazyInternal(inst, "optin", (zod) => zod.def.innerType._zod.optin);
18398
18398
  defineLazyInternal(inst, "optout", (zod) => zod.def.innerType._zod.optout);
18399
18399
  defineLazyInternal(inst, "pattern", (zod) => {
18400
- const pattern = zod.def.innerType._zod.pattern;
18401
- return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;
18400
+ const pattern2 = zod.def.innerType._zod.pattern;
18401
+ return pattern2 ? new RegExp(`^(${cleanRegex(pattern2.source)}|null)$`) : void 0;
18402
18402
  });
18403
18403
  defineLazyInternal(inst, "values", (zod) => {
18404
18404
  return zod.def.innerType._zod.values ? /* @__PURE__ */ new Set([...zod.def.innerType._zod.values, null]) : void 0;
@@ -19492,12 +19492,12 @@ function _length(length, params) {
19492
19492
  });
19493
19493
  }
19494
19494
  // @__NO_SIDE_EFFECTS__
19495
- function _regex(pattern, params) {
19495
+ function _regex(pattern2, params) {
19496
19496
  return new $ZodCheckRegex({
19497
19497
  check: "string_format",
19498
19498
  format: "regex",
19499
19499
  ...normalizeParams(params),
19500
- pattern
19500
+ pattern: pattern2
19501
19501
  });
19502
19502
  }
19503
19503
  // @__NO_SIDE_EFFECTS__
@@ -20183,9 +20183,9 @@ var addDivisor = (agg, value) => {
20183
20183
  if (!agg.multipleOf.includes(value))
20184
20184
  agg.multipleOf.push(value);
20185
20185
  };
20186
- var addPattern = (agg, pattern) => {
20186
+ var addPattern = (agg, pattern2) => {
20187
20187
  agg.patterns ?? (agg.patterns = /* @__PURE__ */ new Set());
20188
- agg.patterns.add(pattern);
20188
+ agg.patterns.add(pattern2);
20189
20189
  };
20190
20190
  var intersectMime = (agg, mime) => {
20191
20191
  agg.mime = agg.mime ? agg.mime.filter((m) => mime.includes(m)) : [...mime];
@@ -20250,8 +20250,8 @@ function aggregateChecks(schema) {
20250
20250
  }
20251
20251
  if (bag.mime)
20252
20252
  intersectMime(agg, bag.mime);
20253
- for (const pattern of bag.patterns ?? [])
20254
- addPattern(agg, pattern);
20253
+ for (const pattern2 of bag.patterns ?? [])
20254
+ addPattern(agg, pattern2);
20255
20255
  return agg;
20256
20256
  }
20257
20257
  var formatMap = {
@@ -20433,11 +20433,11 @@ var nanProcessor = (schema, ctx, json, params) => {
20433
20433
  };
20434
20434
  var templateLiteralProcessor = (schema, _ctx, json, _params) => {
20435
20435
  const _json = json;
20436
- const pattern = schema._zod.pattern;
20437
- if (!pattern)
20436
+ const pattern2 = schema._zod.pattern;
20437
+ if (!pattern2)
20438
20438
  throw new Error("Pattern not found in template literal");
20439
20439
  _json.type = "string";
20440
- _json.pattern = pattern.source;
20440
+ _json.pattern = pattern2.source;
20441
20441
  };
20442
20442
  var fileProcessor = (schema, _ctx, json, _params) => {
20443
20443
  const _json = json;
@@ -20704,8 +20704,8 @@ function rewriteKeyNames(ctx) {
20704
20704
  bySchema.set(entry.schema, entry);
20705
20705
  }
20706
20706
  const rewrites = /* @__PURE__ */ new Map();
20707
- for (const record2 of pendingRecords.get(ctx) ?? []) {
20708
- const seen = ctx.seen.get(record2);
20707
+ for (const record3 of pendingRecords.get(ctx) ?? []) {
20708
+ const seen = ctx.seen.get(record3);
20709
20709
  const names = (seen?.def ?? seen?.schema)?.propertyNames;
20710
20710
  if (!names || names === true || rewrites.has(names))
20711
20711
  continue;
@@ -20735,8 +20735,8 @@ var recordProcessor = (schema, ctx, _json, params) => {
20735
20735
  path: [...params.path, "patternProperties", "*"]
20736
20736
  });
20737
20737
  json.patternProperties = {};
20738
- for (const pattern of patterns) {
20739
- assignProp(json.patternProperties, exactPattern(pattern).source, valueSchema);
20738
+ for (const pattern2 of patterns) {
20739
+ assignProp(json.patternProperties, exactPattern(pattern2).source, valueSchema);
20740
20740
  }
20741
20741
  } else {
20742
20742
  if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
@@ -26265,8 +26265,8 @@ function zodEmittedPattern(schema) {
26265
26265
  return typeof jsonSchema.pattern === "string" ? jsonSchema.pattern : void 0;
26266
26266
  }
26267
26267
  var DATETIME_FRACTION_DIGITS = /\\\.\\d\{(\d+)\}/;
26268
- function datetimeReferenceSchemas(pattern) {
26269
- const fractionDigits = DATETIME_FRACTION_DIGITS.exec(pattern);
26268
+ function datetimeReferenceSchemas(pattern2) {
26269
+ const fractionDigits = DATETIME_FRACTION_DIGITS.exec(pattern2);
26270
26270
  const precisions = [
26271
26271
  void 0,
26272
26272
  -1,
@@ -26279,7 +26279,7 @@ function datetimeReferenceSchemas(pattern) {
26279
26279
  precision
26280
26280
  }))));
26281
26281
  }
26282
- function referencePatternsForFormat(format, pattern) {
26282
+ function referencePatternsForFormat(format, pattern2) {
26283
26283
  let referenceSchemas;
26284
26284
  switch (format) {
26285
26285
  case "email":
@@ -26292,14 +26292,14 @@ function referencePatternsForFormat(format, pattern) {
26292
26292
  referenceSchemas = [iso_exports.date()];
26293
26293
  break;
26294
26294
  case "date-time":
26295
- referenceSchemas = datetimeReferenceSchemas(pattern);
26295
+ referenceSchemas = datetimeReferenceSchemas(pattern2);
26296
26296
  break;
26297
26297
  }
26298
26298
  return new Set(referenceSchemas.map((schema) => zodEmittedPattern(schema)).filter((emitted) => emitted !== void 0));
26299
26299
  }
26300
- function isLibraryFormatPattern(format, pattern, vendor) {
26300
+ function isLibraryFormatPattern(format, pattern2, vendor) {
26301
26301
  if (vendor !== "zod") return true;
26302
- return referencePatternsForFormat(format, pattern).has(pattern);
26302
+ return referencePatternsForFormat(format, pattern2).has(pattern2);
26303
26303
  }
26304
26304
  function promptArgumentsFromStandardSchema(schema) {
26305
26305
  const jsonSchema = standardSchemaToJsonSchema(schema, "input");
@@ -29237,14 +29237,14 @@ var require_code = /* @__PURE__ */ __commonJSMin(((exports) => {
29237
29237
  }
29238
29238
  exports.callValidateCode = callValidateCode;
29239
29239
  const newRegExp = (0, codegen_1._)`new RegExp`;
29240
- function usePattern({ gen, it: { opts } }, pattern) {
29240
+ function usePattern({ gen, it: { opts } }, pattern2) {
29241
29241
  const u = opts.unicodeRegExp ? "u" : "";
29242
29242
  const { regExp } = opts.code;
29243
- const rx = regExp(pattern, u);
29243
+ const rx = regExp(pattern2, u);
29244
29244
  return gen.scopeValue("pattern", {
29245
29245
  key: rx.toString(),
29246
29246
  ref: rx,
29247
- code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${pattern}, ${u})`
29247
+ code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${pattern2}, ${u})`
29248
29248
  });
29249
29249
  }
29250
29250
  exports.usePattern = usePattern;
@@ -32907,12 +32907,12 @@ var require_discriminator = /* @__PURE__ */ __commonJSMin(((exports) => {
32907
32907
  },
32908
32908
  code(cxt) {
32909
32909
  const { gen, data, schema, parentSchema, it } = cxt;
32910
- const { oneOf } = parentSchema;
32910
+ const { oneOf: oneOf2 } = parentSchema;
32911
32911
  if (!it.opts.discriminator) throw new Error("discriminator: requires discriminator option");
32912
32912
  const tagName = schema.propertyName;
32913
32913
  if (typeof tagName != "string") throw new Error("discriminator: requires propertyName");
32914
32914
  if (schema.mapping) throw new Error("discriminator: mapping is not supported");
32915
- if (!oneOf) throw new Error("discriminator: requires oneOf keyword");
32915
+ if (!oneOf2) throw new Error("discriminator: requires oneOf keyword");
32916
32916
  const valid = gen.let("valid", false);
32917
32917
  const tag2 = gen.const("tag", (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(tagName)}`);
32918
32918
  gen.if((0, codegen_1._)`typeof ${tag2} == "string"`, () => validateMapping(), () => cxt.error(false, {
@@ -32950,8 +32950,8 @@ var require_discriminator = /* @__PURE__ */ __commonJSMin(((exports) => {
32950
32950
  const oneOfMapping = {};
32951
32951
  const topRequired = hasRequired(parentSchema);
32952
32952
  let tagRequired = true;
32953
- for (let i = 0; i < oneOf.length; i++) {
32954
- let sch = oneOf[i];
32953
+ for (let i = 0; i < oneOf2.length; i++) {
32954
+ let sch = oneOf2[i];
32955
32955
  if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) {
32956
32956
  const ref = sch.$ref;
32957
32957
  sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref);
@@ -39730,12 +39730,12 @@ var Node = class _Node {
39730
39730
  let node2 = this;
39731
39731
  for (let i = 0, len = tokens.length; i < len; i++) {
39732
39732
  const token = tokens[i];
39733
- const pattern = token.length === 1 ? token === "*" ? i === len - 1 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : null : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
39733
+ const pattern2 = token.length === 1 ? token === "*" ? i === len - 1 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : null : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
39734
39734
  let nextNode;
39735
- if (pattern) {
39736
- const name = pattern[1];
39737
- let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
39738
- if (name && pattern[2]) {
39735
+ if (pattern2) {
39736
+ const name = pattern2[1];
39737
+ let regexpStr = pattern2[2] || LABEL_REG_EXP_STR;
39738
+ if (name && pattern2[2]) {
39739
39739
  if (regexpStr === ".*") {
39740
39740
  throw PATH_ERROR;
39741
39741
  }
@@ -40059,17 +40059,17 @@ var Node2 = class _Node2 {
40059
40059
  let i = 0;
40060
40060
  for (const p of parts) {
40061
40061
  const nextP = parts[++i];
40062
- const pattern = getPattern(p, nextP) || (nextP === void 0 && p && p.indexOf("*") === p.length - 1 ? p : null);
40063
- const isParam = Array.isArray(pattern);
40064
- const key = isParam ? pattern[0] : pattern || p;
40062
+ const pattern2 = getPattern(p, nextP) || (nextP === void 0 && p && p.indexOf("*") === p.length - 1 ? p : null);
40063
+ const isParam = Array.isArray(pattern2);
40064
+ const key = isParam ? pattern2[0] : pattern2 || p;
40065
40065
  const child = curNode.#children[key] ||= new _Node2();
40066
- if (pattern && !child.#pattern) {
40067
- child.#pattern = pattern;
40066
+ if (pattern2 && !child.#pattern) {
40067
+ child.#pattern = pattern2;
40068
40068
  curNode.#patterns.push(child);
40069
40069
  }
40070
40070
  curNode = child;
40071
40071
  if (isParam) {
40072
- possibleKeys.add(pattern[1]);
40072
+ possibleKeys.add(pattern2[1]);
40073
40073
  }
40074
40074
  }
40075
40075
  curNode.#methods.push({
@@ -40122,19 +40122,19 @@ var Node2 = class _Node2 {
40122
40122
  }
40123
40123
  }
40124
40124
  for (const child of node2.#patterns) {
40125
- const pattern = child.#pattern;
40125
+ const pattern2 = child.#pattern;
40126
40126
  const params = node2.#params === emptyParams ? {} : { ...node2.#params };
40127
- if (typeof pattern === "string") {
40128
- if (pattern === "*" || part.startsWith(pattern.slice(0, -1))) {
40127
+ if (typeof pattern2 === "string") {
40128
+ if (pattern2 === "*" || part.startsWith(pattern2.slice(0, -1))) {
40129
40129
  this.#pushHandlerSets(handlerSets, child, method, node2.#params);
40130
- if (pattern === "*") {
40130
+ if (pattern2 === "*") {
40131
40131
  child.#params = params;
40132
40132
  tempNodes.push(child);
40133
40133
  }
40134
40134
  }
40135
40135
  continue;
40136
40136
  }
40137
- const [, name, matcher] = pattern;
40137
+ const [, name, matcher] = pattern2;
40138
40138
  if (!part && matcher === true) {
40139
40139
  continue;
40140
40140
  }
@@ -48158,6 +48158,7 @@ var CHART_META = viewer("chart-viewer");
48158
48158
  var KANBAN_META = viewer("kanban-viewer");
48159
48159
  var KPI_META = viewer("kpi-viewer");
48160
48160
  var FUNNEL_META = viewer("funnel-viewer");
48161
+ var BUY_EVIDENCE_META = viewer("buy-evidence-viewer");
48161
48162
 
48162
48163
  // src/runtime.ts
48163
48164
  var isDeno2 = typeof globalThis.Deno?.version?.deno === "string";
@@ -48387,6 +48388,24 @@ function decodeBase64File(contentBase64, maxBytes) {
48387
48388
  }
48388
48389
  return Uint8Array.from(binary, (character) => character.charCodeAt(0));
48389
48390
  }
48391
+ function normalizeFrappeSiteUrl(baseUrl) {
48392
+ if (typeof baseUrl !== "string" || baseUrl.trim() === "") {
48393
+ throw new Error(
48394
+ "[FrappeClient] baseUrl is required to derive site identity"
48395
+ );
48396
+ }
48397
+ let url2;
48398
+ try {
48399
+ url2 = new URL(baseUrl);
48400
+ } catch {
48401
+ throw new Error("[FrappeClient] baseUrl must be an absolute URL");
48402
+ }
48403
+ if (url2.protocol !== "http:" && url2.protocol !== "https:") {
48404
+ throw new Error("[FrappeClient] baseUrl must be http or https");
48405
+ }
48406
+ const path = url2.pathname.replace(/\/+$/, "");
48407
+ return `${url2.protocol}//${url2.host}${path}`;
48408
+ }
48390
48409
  var FrappeAPIError = class extends Error {
48391
48410
  /**
48392
48411
  * @param message - Human-readable error description
@@ -48473,6 +48492,17 @@ var FrappeClient = class {
48473
48492
  this.retryMethods = config2.retryMethods ?? DEFAULT_RETRY_METHODS;
48474
48493
  this.cache = config2.cache ?? new MemoryCache();
48475
48494
  }
48495
+ /**
48496
+ * Normalized site URL of this configured client: origin + site path, with
48497
+ * credentials, query, and fragment stripped, and without a trailing slash.
48498
+ *
48499
+ * This is the preimage of the Buy capture `sourceInstance.siteId` SHA-256.
48500
+ * It is not an ERP database UUID and must not be copied onto the wire when
48501
+ * the opaque site identifier is sufficient.
48502
+ */
48503
+ normalizedSiteUrl() {
48504
+ return normalizeFrappeSiteUrl(this.baseUrl);
48505
+ }
48476
48506
  // ── Private HTTP helpers ────────────────────────────────────────────────────
48477
48507
  buildHeaders(includeJsonContentType = true) {
48478
48508
  const headers = {
@@ -49154,10 +49184,10 @@ function roundedTotalFallbackWarning(original, patched) {
49154
49184
 
49155
49185
  // src/tools/sales.ts
49156
49186
  function viewerDocument(value, doctype, name) {
49157
- const record2 = typeof value === "object" && value !== null ? value : {};
49187
+ const record3 = typeof value === "object" && value !== null ? value : {};
49158
49188
  return {
49159
- ...record2,
49160
- ...typeof record2.name === "string" || !name ? {} : { name },
49189
+ ...record3,
49190
+ ...typeof record3.name === "string" || !name ? {} : { name },
49161
49191
  doctype
49162
49192
  };
49163
49193
  }
@@ -51665,8 +51695,8 @@ async function applyAssignment(doctype, name, assignment, ctx, failureContext) {
51665
51695
  throw new Error(`${failureContext}: ${reason}`, { cause: error2 });
51666
51696
  }
51667
51697
  const todos = Array.isArray(nativeResult) ? nativeResult.map((todo) => {
51668
- const record2 = todo;
51669
- return { owner: record2.owner, name: record2.name };
51698
+ const record3 = todo;
51699
+ return { owner: record3.owner, name: record3.name };
51670
51700
  }) : [];
51671
51701
  return { notify_user: true, assignees: assignment.assignees, todos };
51672
51702
  }
@@ -51683,8 +51713,8 @@ async function removeAssignment(doctype, name, assignee, ctx, failureContext) {
51683
51713
  throw new Error(`${failureContext}: ${reason}`, { cause: error2 });
51684
51714
  }
51685
51715
  const todos = Array.isArray(nativeResult) ? nativeResult.map((todo) => {
51686
- const record2 = todo;
51687
- return { owner: record2.owner, name: record2.name };
51716
+ const record3 = todo;
51717
+ return { owner: record3.owner, name: record3.name };
51688
51718
  }) : [];
51689
51719
  return { removed: assignee, remaining: todos };
51690
51720
  }
@@ -54973,11 +55003,11 @@ var setupTools = [
54973
55003
  filters.push(["enabled", "=", 1]);
54974
55004
  }
54975
55005
  if (input.search) {
54976
- const literal2 = input.search.replace(
55006
+ const literal3 = input.search.replace(
54977
55007
  /[\\%_]/g,
54978
55008
  (match2) => `\\${match2}`
54979
55009
  );
54980
- filters.push(["full_name", "like", `%${literal2}%`]);
55010
+ filters.push(["full_name", "like", `%${literal3}%`]);
54981
55011
  }
54982
55012
  const docs = await ctx.client.list("User", {
54983
55013
  fields: ["name", "full_name", "enabled"],
@@ -57437,6 +57467,1349 @@ var kanbanTools = [
57437
57467
  }
57438
57468
  ];
57439
57469
 
57470
+ // src/buy/identities.ts
57471
+ var BUY_SOURCE_CAPTURE_SCHEMA = "io.casys.mcp-erpnext.buy-source-capture/1.0";
57472
+ var BUY_RECORDED_RESULT_SCHEMA = "io.casys.mcp-erpnext.buy-recorded-result/1.0";
57473
+ var BUY_RECORDED_SESSION_SCHEMA = "io.casys.mcp-erpnext.buy-recorded-session/1.0";
57474
+ var BUY_EVIDENCE_VIEWER_URI = "ui://mcp-erpnext/buy-evidence-viewer";
57475
+ var BUY_VIEW_APP_MANIFEST_URI = "ui://mcp-erpnext/buy-evidence-manifest";
57476
+ var BUY_VIEW_APP_ID = "io.casys.mcp-erpnext.buy-evidence";
57477
+ var BUY_VIEW_APP_TITLE = "ERPNext Buy Evidence";
57478
+ var BUY_VIEW_APP_VERSION = "3.1.0-beta.9";
57479
+ var BUY_SOURCE_INSTANCE_KIND = "erpnext-site";
57480
+ var BUY_CAPTURE_DOCTYPE = [
57481
+ "Item",
57482
+ "BOM",
57483
+ "Item Price",
57484
+ "Supplier Quotation",
57485
+ "Supplier",
57486
+ "Price List",
57487
+ "UOM",
57488
+ "Currency Exchange"
57489
+ ];
57490
+ var BUY_CAPTURE_MAX_DOCUMENTS = 32;
57491
+ var BUY_CAPTURE_MAX_CHILD_ROWS = 200;
57492
+ var BUY_CAPTURE_MAX_CANONICAL_BYTES = 262144;
57493
+ var BUY_CAPTURE_MAX_NAME_LENGTH = 140;
57494
+ var BUY_DOCTYPE_SOURCE_CATEGORY = {
57495
+ Item: "item",
57496
+ BOM: "bom",
57497
+ "Item Price": "catalogue-price",
57498
+ "Supplier Quotation": "supplier-quotation",
57499
+ Supplier: "supplier",
57500
+ "Price List": "price-list",
57501
+ UOM: "uom",
57502
+ "Currency Exchange": "currency-exchange"
57503
+ };
57504
+ var BUY_CONSISTENCY_KIND = "repeated-read";
57505
+ var BUY_CONSISTENCY_READS = 2;
57506
+
57507
+ // src/buy/errors.ts
57508
+ var BuyCaptureError = class extends Error {
57509
+ code;
57510
+ retryable;
57511
+ context;
57512
+ recovery;
57513
+ constructor(code, message2, options) {
57514
+ super(message2);
57515
+ this.name = "BuyCaptureError";
57516
+ this.code = code;
57517
+ this.retryable = options.retryable === true;
57518
+ this.context = Object.freeze({ ...options.context ?? {} });
57519
+ this.recovery = options.recovery;
57520
+ }
57521
+ };
57522
+
57523
+ // src/buy/json.ts
57524
+ var FINGERPRINT = /^sha256:[a-f0-9]{64}$/;
57525
+ var ISO_MILLISECONDS = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/;
57526
+ var CALENDAR_DATE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
57527
+ var FRAPPE_DATETIME = /^[0-9]{4}-[0-9]{2}-[0-9]{2}[ T][0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?$/;
57528
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
57529
+ "api_key",
57530
+ "api_secret",
57531
+ "authorization",
57532
+ "baseUrl",
57533
+ "command",
57534
+ "commands",
57535
+ "cookie",
57536
+ "credentials",
57537
+ "endpoint",
57538
+ "password",
57539
+ "source_path",
57540
+ "sourcePath",
57541
+ "stagedPath"
57542
+ ]);
57543
+ function record2(value, name) {
57544
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
57545
+ throw new TypeError(`${name} must be an object.`);
57546
+ }
57547
+ return value;
57548
+ }
57549
+ function exactKeys(value, keys, name) {
57550
+ const ownKeys = Reflect.ownKeys(value);
57551
+ if (ownKeys.some((key) => typeof key !== "string")) {
57552
+ throw new TypeError(`${name} contains missing or unsupported fields.`);
57553
+ }
57554
+ for (const key of ownKeys) {
57555
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
57556
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
57557
+ throw new TypeError(`${name} contains missing or unsupported fields.`);
57558
+ }
57559
+ }
57560
+ const actual = ownKeys.toSorted();
57561
+ const expected = [...keys].toSorted();
57562
+ if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
57563
+ throw new TypeError(`${name} contains missing or unsupported fields.`);
57564
+ }
57565
+ }
57566
+ function exactRecord(value, keys, name) {
57567
+ const root = record2(value, name);
57568
+ exactKeys(root, keys, name);
57569
+ rejectForbiddenKeys(root, name);
57570
+ return root;
57571
+ }
57572
+ function denseArray(value, name) {
57573
+ if (!Array.isArray(value)) throw new TypeError(`${name} must be an array.`);
57574
+ const allowed = /* @__PURE__ */ new Set(["length"]);
57575
+ for (let index = 0; index < value.length; index += 1) {
57576
+ allowed.add(String(index));
57577
+ }
57578
+ const ownKeys = Reflect.ownKeys(value);
57579
+ if (ownKeys.length !== allowed.size || ownKeys.some((key) => typeof key !== "string" || !allowed.has(key))) {
57580
+ throw new TypeError(`${name} must be a dense, unadorned array.`);
57581
+ }
57582
+ for (let index = 0; index < value.length; index += 1) {
57583
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
57584
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
57585
+ throw new TypeError(`${name} must be a dense, unadorned array.`);
57586
+ }
57587
+ }
57588
+ return value;
57589
+ }
57590
+ function literal2(value, expected, name) {
57591
+ if (value !== expected) throw new TypeError(`${name} must be ${expected}.`);
57592
+ return expected;
57593
+ }
57594
+ function oneOf(value, allowed, name) {
57595
+ if (typeof value !== "string" || !allowed.includes(value)) {
57596
+ throw new TypeError(`${name} must be one of ${allowed.join(", ")}.`);
57597
+ }
57598
+ return value;
57599
+ }
57600
+ function nonEmpty(value, name) {
57601
+ if (typeof value !== "string" || value.trim() === "") {
57602
+ throw new TypeError(`${name} must be a non-empty string.`);
57603
+ }
57604
+ if (value.trim() !== value) {
57605
+ throw new TypeError(`${name} must not contain whitespace padding.`);
57606
+ }
57607
+ return value;
57608
+ }
57609
+ function pattern(value, expected, name) {
57610
+ if (typeof value !== "string" || !expected.test(value)) {
57611
+ throw new TypeError(`${name} has an invalid format.`);
57612
+ }
57613
+ return value;
57614
+ }
57615
+ function fingerprint(value, name) {
57616
+ return pattern(value, FINGERPRINT, name);
57617
+ }
57618
+ function canonicalTimestamp(value, name) {
57619
+ const timestamp = pattern(value, ISO_MILLISECONDS, name);
57620
+ try {
57621
+ if (new Date(timestamp).toISOString() !== timestamp) {
57622
+ throw new TypeError(`${name} is not canonical UTC.`);
57623
+ }
57624
+ } catch {
57625
+ throw new TypeError(`${name} is not canonical UTC.`);
57626
+ }
57627
+ return timestamp;
57628
+ }
57629
+ function calendarDate(value, name) {
57630
+ const date4 = pattern(value, CALENDAR_DATE, name);
57631
+ const parsed = /* @__PURE__ */ new Date(`${date4}T00:00:00.000Z`);
57632
+ if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== date4) {
57633
+ throw new TypeError(`${name} is not a real calendar date.`);
57634
+ }
57635
+ return date4;
57636
+ }
57637
+ function frappeDatetime(value, name) {
57638
+ const datetime3 = pattern(value, FRAPPE_DATETIME, name);
57639
+ calendarDate(datetime3.slice(0, 10), name);
57640
+ const [hour, minute, second] = datetime3.slice(11, 19).split(":").map(Number);
57641
+ if (hour > 23 || minute > 59 || second > 59) {
57642
+ throw new TypeError(`${name} is not a real Frappe datetime.`);
57643
+ }
57644
+ return datetime3;
57645
+ }
57646
+ function boundedArray(value, max, name) {
57647
+ const items = denseArray(value, name);
57648
+ if (items.length > max) {
57649
+ throw new TypeError(`${name} exceeds the ${max}-entry bound.`);
57650
+ }
57651
+ return items;
57652
+ }
57653
+ function rejectForbiddenKeys(value, name) {
57654
+ if (Array.isArray(value)) {
57655
+ const items = denseArray(value, name);
57656
+ for (let index = 0; index < items.length; index += 1) {
57657
+ rejectForbiddenKeys(items[index], `${name}[${index}]`);
57658
+ }
57659
+ return;
57660
+ }
57661
+ if (typeof value !== "object" || value === null) return;
57662
+ const root = record2(value, name);
57663
+ for (const key of Object.keys(root)) {
57664
+ if (FORBIDDEN_KEYS.has(key)) {
57665
+ throw new TypeError(
57666
+ `${name}.${key} is a private credential, path, or command field and must not appear in the Buy contract.`
57667
+ );
57668
+ }
57669
+ rejectForbiddenKeys(root[key], `${name}.${key}`);
57670
+ }
57671
+ }
57672
+ function canonicalJson2(value, name) {
57673
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
57674
+ return JSON.stringify(value);
57675
+ }
57676
+ if (typeof value === "number") {
57677
+ if (!Number.isFinite(value)) {
57678
+ throw new TypeError(`${name} must contain only finite JSON numbers.`);
57679
+ }
57680
+ return JSON.stringify(value);
57681
+ }
57682
+ if (Array.isArray(value)) {
57683
+ const items = denseArray(value, name);
57684
+ return `[${items.map((item, index) => canonicalJson2(item, `${name}[${index}]`)).join(",")}]`;
57685
+ }
57686
+ const root = record2(value, name);
57687
+ const prototype = Object.getPrototypeOf(root);
57688
+ if (prototype !== Object.prototype && prototype !== null) {
57689
+ throw new TypeError(`${name} must contain only plain JSON objects.`);
57690
+ }
57691
+ const ownKeys = Reflect.ownKeys(root);
57692
+ if (ownKeys.some((key) => typeof key !== "string")) {
57693
+ throw new TypeError(`${name} must contain only string-keyed JSON objects.`);
57694
+ }
57695
+ const keys = ownKeys.toSorted();
57696
+ const fields = keys.map((key) => {
57697
+ const descriptor = Object.getOwnPropertyDescriptor(root, key);
57698
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
57699
+ throw new TypeError(`${name}.${key} must be an enumerable JSON value.`);
57700
+ }
57701
+ if (descriptor.value === void 0) {
57702
+ throw new TypeError(`${name}.${key} must not be undefined.`);
57703
+ }
57704
+ return `${JSON.stringify(key)}:${canonicalJson2(descriptor.value, `${name}.${key}`)}`;
57705
+ });
57706
+ return `{${fields.join(",")}}`;
57707
+ }
57708
+ function utf8ByteCount(text) {
57709
+ return new TextEncoder().encode(text).byteLength;
57710
+ }
57711
+ async function sha256HexOfUtf8(text) {
57712
+ const bytes = new TextEncoder().encode(text);
57713
+ const digestBytes = new Uint8Array(
57714
+ await crypto.subtle.digest("SHA-256", bytes)
57715
+ );
57716
+ return Array.from(digestBytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
57717
+ }
57718
+ async function sha256FingerprintOfUtf8(text) {
57719
+ return `sha256:${await sha256HexOfUtf8(text)}`;
57720
+ }
57721
+
57722
+ // src/buy/projection.ts
57723
+ var DECIMAL = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
57724
+ var CURRENCY = /^[A-Z]{3}$/;
57725
+ var SENSITIVE_FIELDS = /* @__PURE__ */ new Set([
57726
+ "api_key",
57727
+ "api_secret",
57728
+ "password",
57729
+ "email_id",
57730
+ "email",
57731
+ "mobile_no",
57732
+ "phone",
57733
+ "cell_number"
57734
+ ]);
57735
+ var ITEM_FIELDS = [
57736
+ { key: "item_code", kind: "string", required: true },
57737
+ { key: "item_name", kind: "string" },
57738
+ { key: "item_group", kind: "string" },
57739
+ { key: "stock_uom", kind: "string" },
57740
+ { key: "is_purchase_item", kind: "boolean" },
57741
+ { key: "is_stock_item", kind: "boolean" },
57742
+ { key: "disabled", kind: "boolean" }
57743
+ ];
57744
+ var BOM_FIELDS = [
57745
+ { key: "item", kind: "string", required: true },
57746
+ { key: "item_name", kind: "string" },
57747
+ { key: "quantity", kind: "decimal", required: true },
57748
+ { key: "uom", kind: "string", required: true },
57749
+ { key: "is_active", kind: "boolean" },
57750
+ { key: "is_default", kind: "boolean" },
57751
+ { key: "company", kind: "string" },
57752
+ { key: "currency", kind: "currency" },
57753
+ { key: "with_operations", kind: "boolean" }
57754
+ ];
57755
+ var BOM_ITEM_FIELDS = [
57756
+ { key: "item_code", kind: "string", required: true },
57757
+ { key: "item_name", kind: "string" },
57758
+ { key: "qty", kind: "decimal", required: true },
57759
+ { key: "uom", kind: "string", required: true },
57760
+ { key: "rate", kind: "decimal" },
57761
+ { key: "amount", kind: "decimal" },
57762
+ { key: "sourced_by_supplier", kind: "boolean" }
57763
+ ];
57764
+ var BOM_OPERATION_FIELDS = [
57765
+ { key: "operation", kind: "string", required: true },
57766
+ { key: "time_in_mins", kind: "decimal" },
57767
+ { key: "hour_rate", kind: "decimal" }
57768
+ ];
57769
+ var ITEM_PRICE_FIELDS = [
57770
+ { key: "item_code", kind: "string", required: true },
57771
+ { key: "item_name", kind: "string" },
57772
+ { key: "price_list", kind: "string" },
57773
+ { key: "price_list_rate", kind: "decimal", required: true },
57774
+ { key: "currency", kind: "currency", required: true },
57775
+ { key: "uom", kind: "string" },
57776
+ { key: "min_qty", kind: "decimal" },
57777
+ { key: "valid_from", kind: "date" },
57778
+ { key: "valid_upto", kind: "date" },
57779
+ { key: "supplier", kind: "string" },
57780
+ { key: "buying", kind: "boolean" },
57781
+ { key: "selling", kind: "boolean" }
57782
+ ];
57783
+ var SUPPLIER_QUOTATION_FIELDS = [
57784
+ { key: "supplier", kind: "string", required: true },
57785
+ { key: "supplier_name", kind: "string" },
57786
+ { key: "transaction_date", kind: "date", required: true },
57787
+ { key: "valid_till", kind: "date" },
57788
+ { key: "currency", kind: "currency", required: true },
57789
+ { key: "conversion_rate", kind: "decimal" },
57790
+ { key: "status", kind: "string" },
57791
+ { key: "company", kind: "string" },
57792
+ { key: "price_list", kind: "string" }
57793
+ ];
57794
+ var SUPPLIER_QUOTATION_ITEM_FIELDS = [
57795
+ { key: "item_code", kind: "string", required: true },
57796
+ { key: "item_name", kind: "string" },
57797
+ { key: "qty", kind: "decimal", required: true },
57798
+ { key: "uom", kind: "string", required: true },
57799
+ { key: "rate", kind: "decimal" },
57800
+ { key: "amount", kind: "decimal" },
57801
+ { key: "price_list_rate", kind: "decimal" },
57802
+ { key: "discount_percentage", kind: "decimal" },
57803
+ { key: "supplier_part_no", kind: "string" }
57804
+ ];
57805
+ var SUPPLIER_FIELDS = [
57806
+ { key: "supplier_name", kind: "string", required: true },
57807
+ { key: "supplier_group", kind: "string" },
57808
+ { key: "supplier_type", kind: "string" },
57809
+ { key: "country", kind: "string" },
57810
+ { key: "default_currency", kind: "currency" },
57811
+ { key: "disabled", kind: "boolean" }
57812
+ ];
57813
+ var PRICE_LIST_FIELDS = [
57814
+ { key: "currency", kind: "currency", required: true },
57815
+ { key: "enabled", kind: "boolean" },
57816
+ { key: "buying", kind: "boolean" },
57817
+ { key: "selling", kind: "boolean" },
57818
+ { key: "price_not_uom_dependent", kind: "boolean" }
57819
+ ];
57820
+ var UOM_FIELDS = [
57821
+ { key: "uom_name", kind: "string" },
57822
+ { key: "must_be_whole_number", kind: "boolean" }
57823
+ ];
57824
+ var CURRENCY_EXCHANGE_FIELDS = [
57825
+ { key: "from_currency", kind: "currency", required: true },
57826
+ { key: "to_currency", kind: "currency", required: true },
57827
+ { key: "exchange_rate", kind: "decimal", required: true },
57828
+ { key: "date", kind: "date", required: true }
57829
+ ];
57830
+ var BUY_DOCTYPE_CHILD_TABLES = {
57831
+ BOM: [
57832
+ { table: "items", fields: BOM_ITEM_FIELDS },
57833
+ { table: "operations", fields: BOM_OPERATION_FIELDS }
57834
+ ],
57835
+ "Supplier Quotation": [
57836
+ { table: "items", fields: SUPPLIER_QUOTATION_ITEM_FIELDS }
57837
+ ]
57838
+ };
57839
+ var BUY_DOCTYPE_FIELD_SPECS = {
57840
+ Item: ITEM_FIELDS,
57841
+ BOM: BOM_FIELDS,
57842
+ "Item Price": ITEM_PRICE_FIELDS,
57843
+ "Supplier Quotation": SUPPLIER_QUOTATION_FIELDS,
57844
+ Supplier: SUPPLIER_FIELDS,
57845
+ "Price List": PRICE_LIST_FIELDS,
57846
+ UOM: UOM_FIELDS,
57847
+ "Currency Exchange": CURRENCY_EXCHANGE_FIELDS
57848
+ };
57849
+ function isBuyCaptureDoctype(value) {
57850
+ return BUY_CAPTURE_DOCTYPE.includes(value);
57851
+ }
57852
+ async function projectBuyDocument(doctype, raw2) {
57853
+ const name = requiredName(raw2.name, `${doctype}.name`);
57854
+ const modified = requiredModified(raw2.modified, `${doctype}.modified`);
57855
+ const fields = projectFields(
57856
+ raw2,
57857
+ BUY_DOCTYPE_FIELD_SPECS[doctype],
57858
+ `${doctype}`
57859
+ );
57860
+ const children = projectChildren(doctype, raw2);
57861
+ const docstatus = optionalDocstatus(raw2.docstatus, `${doctype}.docstatus`);
57862
+ const status = optionalStatus(raw2.status, `${doctype}.status`);
57863
+ const identity = {
57864
+ doctype,
57865
+ name,
57866
+ modified,
57867
+ ...docstatus !== void 0 ? { docstatus } : {},
57868
+ ...status !== void 0 ? { status } : {},
57869
+ sourceCategory: BUY_DOCTYPE_SOURCE_CATEGORY[doctype],
57870
+ fields,
57871
+ ...children !== void 0 ? { children } : {}
57872
+ };
57873
+ const fingerprint2 = await sha256FingerprintOfUtf8(
57874
+ canonicalJson2(identity, `${doctype}:${name} projection`)
57875
+ );
57876
+ return { ...identity, fingerprint: fingerprint2 };
57877
+ }
57878
+ function projectChildren(doctype, raw2) {
57879
+ const specs = BUY_DOCTYPE_CHILD_TABLES[doctype];
57880
+ if (!specs) return void 0;
57881
+ const tables = [];
57882
+ for (const spec of specs) {
57883
+ const value = raw2[spec.table];
57884
+ if (value === void 0 || value === null) continue;
57885
+ if (!Array.isArray(value)) {
57886
+ throw new BuyCaptureError(
57887
+ "BUY_CAPTURE_INVALID_PROJECTION",
57888
+ `${doctype}.${spec.table} must be a child table array.`,
57889
+ {
57890
+ recovery: "Capture only closed child tables with exact row identities.",
57891
+ context: { doctype, table: spec.table }
57892
+ }
57893
+ );
57894
+ }
57895
+ if (value.length > BUY_CAPTURE_MAX_CHILD_ROWS) {
57896
+ throw new BuyCaptureError(
57897
+ "BUY_CAPTURE_BOUNDS_EXCEEDED",
57898
+ `${doctype}.${spec.table} exceeds the ${BUY_CAPTURE_MAX_CHILD_ROWS}-row bound.`,
57899
+ {
57900
+ recovery: "Narrow the capture to fewer child rows.",
57901
+ context: {
57902
+ doctype,
57903
+ table: spec.table,
57904
+ limit: BUY_CAPTURE_MAX_CHILD_ROWS,
57905
+ actual: value.length
57906
+ }
57907
+ }
57908
+ );
57909
+ }
57910
+ if (value.length === 0) continue;
57911
+ const rows = value.map(
57912
+ (row, index) => projectChildRow(
57913
+ row,
57914
+ spec.fields,
57915
+ `${doctype}.${spec.table}[${index}]`
57916
+ )
57917
+ );
57918
+ tables.push({ table: spec.table, rows });
57919
+ }
57920
+ return tables.length > 0 ? tables : void 0;
57921
+ }
57922
+ function projectChildRow(row, specs, path) {
57923
+ if (typeof row !== "object" || row === null || Array.isArray(row)) {
57924
+ throw new BuyCaptureError(
57925
+ "BUY_CAPTURE_INVALID_PROJECTION",
57926
+ `${path} must be a child row object.`,
57927
+ {
57928
+ recovery: "Child rows must keep their exact Frappe name identity.",
57929
+ context: { path }
57930
+ }
57931
+ );
57932
+ }
57933
+ const record3 = row;
57934
+ const name = requiredName(record3.name, `${path}.name`);
57935
+ const idx = optionalIdx(record3.idx, `${path}.idx`);
57936
+ return {
57937
+ name,
57938
+ ...idx !== void 0 ? { idx } : {},
57939
+ fields: projectFields(record3, specs, path)
57940
+ };
57941
+ }
57942
+ function projectFields(raw2, specs, path) {
57943
+ for (const key of Object.keys(raw2)) {
57944
+ if (SENSITIVE_FIELDS.has(key)) {
57945
+ continue;
57946
+ }
57947
+ }
57948
+ const fields = {};
57949
+ for (const spec of specs) {
57950
+ const value = projectField(raw2[spec.key], spec, `${path}.${spec.key}`);
57951
+ if (value !== void 0) fields[spec.key] = value;
57952
+ }
57953
+ return fields;
57954
+ }
57955
+ function projectField(value, spec, path) {
57956
+ if (value === void 0 || value === null || value === "") {
57957
+ if (spec.required) {
57958
+ throw new BuyCaptureError(
57959
+ "BUY_CAPTURE_INVALID_PROJECTION",
57960
+ `${path} is required on the closed commercial projection.`,
57961
+ {
57962
+ recovery: "The ERP document is missing a field later consumed as cost evidence.",
57963
+ context: { path, field: spec.key }
57964
+ }
57965
+ );
57966
+ }
57967
+ return void 0;
57968
+ }
57969
+ switch (spec.kind) {
57970
+ case "string":
57971
+ return requiredString(value, path);
57972
+ case "decimal":
57973
+ return requiredDecimal(value, path);
57974
+ case "boolean":
57975
+ return requiredBoolean(value, path);
57976
+ case "currency":
57977
+ return requiredCurrency(value, path);
57978
+ case "date":
57979
+ return requiredDate(value, path);
57980
+ case "integer":
57981
+ return requiredInteger(value, path);
57982
+ }
57983
+ }
57984
+ function requiredName(value, path) {
57985
+ const name = requiredString(value, path);
57986
+ if (name.length > BUY_CAPTURE_MAX_NAME_LENGTH) {
57987
+ throw new BuyCaptureError(
57988
+ "BUY_CAPTURE_BOUNDS_EXCEEDED",
57989
+ `${path} exceeds the ${BUY_CAPTURE_MAX_NAME_LENGTH}-character bound.`,
57990
+ {
57991
+ recovery: "Use the exact ERP document name; do not pass a description.",
57992
+ context: {
57993
+ path,
57994
+ limit: BUY_CAPTURE_MAX_NAME_LENGTH,
57995
+ actual: name.length
57996
+ }
57997
+ }
57998
+ );
57999
+ }
58000
+ return name;
58001
+ }
58002
+ function requiredModified(value, path) {
58003
+ try {
58004
+ return frappeDatetime(value, path);
58005
+ } catch {
58006
+ throw new BuyCaptureError(
58007
+ "BUY_CAPTURE_INVALID_PROJECTION",
58008
+ `${path} is not a Frappe datetime.`,
58009
+ {
58010
+ recovery: "Capture the document's exact modified timestamp.",
58011
+ context: { path }
58012
+ }
58013
+ );
58014
+ }
58015
+ }
58016
+ function requiredString(value, path) {
58017
+ if (typeof value !== "string" || value.trim() === "" || value.trim() !== value) {
58018
+ throw new BuyCaptureError(
58019
+ "BUY_CAPTURE_INVALID_PROJECTION",
58020
+ `${path} must be a non-empty unpadded string.`,
58021
+ {
58022
+ recovery: "Use the exact ERP string; do not invent a label.",
58023
+ context: { path }
58024
+ }
58025
+ );
58026
+ }
58027
+ return value;
58028
+ }
58029
+ function requiredDecimal(value, path) {
58030
+ if (typeof value === "number") {
58031
+ if (!Number.isFinite(value)) {
58032
+ throw new BuyCaptureError(
58033
+ "BUY_CAPTURE_INVALID_PROJECTION",
58034
+ `${path} must be a finite decimal.`,
58035
+ {
58036
+ recovery: "Refuse non-finite commercial amounts rather than coerce them.",
58037
+ context: { path }
58038
+ }
58039
+ );
58040
+ }
58041
+ return JSON.stringify(value);
58042
+ }
58043
+ if (typeof value === "string") {
58044
+ if (!DECIMAL.test(value)) {
58045
+ throw new BuyCaptureError(
58046
+ "BUY_CAPTURE_INVALID_PROJECTION",
58047
+ `${path} must be a canonical decimal string.`,
58048
+ {
58049
+ recovery: "Keep the ERP decimal; do not fill absence with zero.",
58050
+ context: { path }
58051
+ }
58052
+ );
58053
+ }
58054
+ return value;
58055
+ }
58056
+ throw new BuyCaptureError(
58057
+ "BUY_CAPTURE_INVALID_PROJECTION",
58058
+ `${path} must be a decimal.`,
58059
+ {
58060
+ recovery: "Keep the ERP decimal; do not fill absence with zero.",
58061
+ context: { path }
58062
+ }
58063
+ );
58064
+ }
58065
+ function requiredBoolean(value, path) {
58066
+ if (value === true || value === 1) return true;
58067
+ if (value === false || value === 0) return false;
58068
+ throw new BuyCaptureError(
58069
+ "BUY_CAPTURE_INVALID_PROJECTION",
58070
+ `${path} must be a boolean or Frappe 0/1.`,
58071
+ {
58072
+ recovery: "Do not coerce missing flags to false.",
58073
+ context: { path }
58074
+ }
58075
+ );
58076
+ }
58077
+ function requiredCurrency(value, path) {
58078
+ const code = requiredString(value, path);
58079
+ if (!CURRENCY.test(code)) {
58080
+ throw new BuyCaptureError(
58081
+ "BUY_CAPTURE_INVALID_PROJECTION",
58082
+ `${path} must be a 3-letter currency code.`,
58083
+ {
58084
+ recovery: "Capture the document currency; do not guess a conversion.",
58085
+ context: { path }
58086
+ }
58087
+ );
58088
+ }
58089
+ return code;
58090
+ }
58091
+ function requiredDate(value, path) {
58092
+ try {
58093
+ return calendarDate(value, path);
58094
+ } catch {
58095
+ throw new BuyCaptureError(
58096
+ "BUY_CAPTURE_INVALID_PROJECTION",
58097
+ `${path} must be a real YYYY-MM-DD date.`,
58098
+ {
58099
+ recovery: "Keep the ERP calendar date; do not localize it.",
58100
+ context: { path }
58101
+ }
58102
+ );
58103
+ }
58104
+ }
58105
+ function requiredInteger(value, path) {
58106
+ if (!Number.isSafeInteger(value)) {
58107
+ throw new BuyCaptureError(
58108
+ "BUY_CAPTURE_INVALID_PROJECTION",
58109
+ `${path} must be a safe integer.`,
58110
+ { recovery: "Keep the ERP integer identity.", context: { path } }
58111
+ );
58112
+ }
58113
+ return value;
58114
+ }
58115
+ function optionalDocstatus(value, path) {
58116
+ if (value === void 0 || value === null || value === "") return void 0;
58117
+ if (value !== 0 && value !== 1 && value !== 2) {
58118
+ throw new BuyCaptureError(
58119
+ "BUY_CAPTURE_INVALID_PROJECTION",
58120
+ `${path} must be 0, 1, or 2.`,
58121
+ { recovery: "Keep the ERP docstatus.", context: { path } }
58122
+ );
58123
+ }
58124
+ return value;
58125
+ }
58126
+ function optionalStatus(value, path) {
58127
+ if (value === void 0 || value === null || value === "") return void 0;
58128
+ return requiredString(value, path);
58129
+ }
58130
+ function optionalIdx(value, path) {
58131
+ if (value === void 0 || value === null || value === "") return void 0;
58132
+ return requiredInteger(value, path);
58133
+ }
58134
+ function parseClosedWireFields(value, specs, path) {
58135
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
58136
+ throw new TypeError(`${path} must be an object.`);
58137
+ }
58138
+ const root = value;
58139
+ const allowed = new Map(specs.map((spec) => [spec.key, spec]));
58140
+ for (const key of Object.keys(root)) {
58141
+ if (!allowed.has(key)) {
58142
+ throw new TypeError(`${path}.${key} is not a closed commercial field.`);
58143
+ }
58144
+ }
58145
+ const fields = {};
58146
+ for (const spec of specs) {
58147
+ const raw2 = root[spec.key];
58148
+ if (raw2 === void 0 || raw2 === null || raw2 === "") {
58149
+ if (spec.required) {
58150
+ throw new TypeError(`${path}.${spec.key} is required.`);
58151
+ }
58152
+ continue;
58153
+ }
58154
+ fields[spec.key] = parseWireValue(raw2, spec, `${path}.${spec.key}`);
58155
+ }
58156
+ return fields;
58157
+ }
58158
+ function parseClosedWireChildren(doctype, value, path) {
58159
+ if (value === void 0) return void 0;
58160
+ if (!Array.isArray(value)) {
58161
+ throw new TypeError(`${path} must be an array.`);
58162
+ }
58163
+ const specs = BUY_DOCTYPE_CHILD_TABLES[doctype] ?? [];
58164
+ if (specs.length === 0) {
58165
+ throw new TypeError(`${path} is not admitted for ${doctype}.`);
58166
+ }
58167
+ if (value.length === 0) {
58168
+ throw new TypeError(`${path} must be omitted when empty.`);
58169
+ }
58170
+ const known = new Map(specs.map((spec) => [spec.table, spec]));
58171
+ const seenTables = /* @__PURE__ */ new Set();
58172
+ return value.map((item, index) => {
58173
+ const tablePath = `${path}[${index}]`;
58174
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
58175
+ throw new TypeError(`${tablePath} must be an object.`);
58176
+ }
58177
+ const table2 = item;
58178
+ const keys = Object.keys(table2).toSorted();
58179
+ if (keys.length !== 2 || keys[0] !== "rows" || keys[1] !== "table") {
58180
+ throw new TypeError(
58181
+ `${tablePath} contains missing or unsupported fields.`
58182
+ );
58183
+ }
58184
+ if (typeof table2.table !== "string" || !known.has(table2.table)) {
58185
+ throw new TypeError(
58186
+ `${tablePath}.table is not a closed child table for ${doctype}.`
58187
+ );
58188
+ }
58189
+ if (seenTables.has(table2.table)) {
58190
+ throw new TypeError(`${tablePath}.table duplicates ${table2.table}.`);
58191
+ }
58192
+ seenTables.add(table2.table);
58193
+ const spec = known.get(table2.table);
58194
+ if (!Array.isArray(table2.rows)) {
58195
+ throw new TypeError(`${tablePath}.rows must be an array.`);
58196
+ }
58197
+ if (table2.rows.length > BUY_CAPTURE_MAX_CHILD_ROWS) {
58198
+ throw new TypeError(
58199
+ `${tablePath}.rows exceeds the ${BUY_CAPTURE_MAX_CHILD_ROWS}-row bound.`
58200
+ );
58201
+ }
58202
+ const seenRows = /* @__PURE__ */ new Set();
58203
+ const rows = table2.rows.map((row, rowIndex) => {
58204
+ const rowPath = `${tablePath}.rows[${rowIndex}]`;
58205
+ const parsed = parseClosedWireRow(row, spec.fields, rowPath);
58206
+ if (seenRows.has(parsed.name)) {
58207
+ throw new TypeError(`${rowPath} duplicates child row ${parsed.name}.`);
58208
+ }
58209
+ seenRows.add(parsed.name);
58210
+ return parsed;
58211
+ });
58212
+ return { table: spec.table, rows };
58213
+ });
58214
+ }
58215
+ function parseClosedWireRow(value, specs, path) {
58216
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
58217
+ throw new TypeError(`${path} must be an object.`);
58218
+ }
58219
+ const root = value;
58220
+ const keys = Object.keys(root).toSorted();
58221
+ const allowed = root.idx === void 0 ? ["fields", "name"] : ["fields", "idx", "name"];
58222
+ if (keys.length !== allowed.length || keys.some((key, index) => key !== allowed[index])) {
58223
+ throw new TypeError(`${path} contains missing or unsupported fields.`);
58224
+ }
58225
+ const name = requiredWireName(root.name, `${path}.name`);
58226
+ return {
58227
+ name,
58228
+ ...root.idx === void 0 ? {} : {
58229
+ idx: requiredWireIdx(root.idx, `${path}.idx`)
58230
+ },
58231
+ fields: parseClosedWireFields(root.fields, specs, `${path}.fields`)
58232
+ };
58233
+ }
58234
+ function parseWireValue(value, spec, path) {
58235
+ switch (spec.kind) {
58236
+ case "string":
58237
+ if (typeof value !== "string" || value.trim() === "" || value.trim() !== value) {
58238
+ throw new TypeError(`${path} must be a non-empty unpadded string.`);
58239
+ }
58240
+ return value;
58241
+ case "decimal":
58242
+ if (typeof value !== "string" || !DECIMAL.test(value)) {
58243
+ throw new TypeError(`${path} must be a canonical decimal string.`);
58244
+ }
58245
+ return value;
58246
+ case "boolean":
58247
+ if (value !== true && value !== false) {
58248
+ throw new TypeError(`${path} must be a boolean.`);
58249
+ }
58250
+ return value;
58251
+ case "currency":
58252
+ if (typeof value !== "string" || !CURRENCY.test(value)) {
58253
+ throw new TypeError(`${path} must be a 3-letter currency code.`);
58254
+ }
58255
+ return value;
58256
+ case "date":
58257
+ return calendarDate(value, path);
58258
+ case "integer":
58259
+ if (!Number.isSafeInteger(value)) {
58260
+ throw new TypeError(`${path} must be a safe integer.`);
58261
+ }
58262
+ return value;
58263
+ }
58264
+ }
58265
+ function requiredWireName(value, path) {
58266
+ if (typeof value !== "string" || value.trim() === "" || value.trim() !== value) {
58267
+ throw new TypeError(`${path} must be a non-empty unpadded string.`);
58268
+ }
58269
+ if (value.length > BUY_CAPTURE_MAX_NAME_LENGTH) {
58270
+ throw new TypeError(
58271
+ `${path} exceeds the ${BUY_CAPTURE_MAX_NAME_LENGTH}-character bound.`
58272
+ );
58273
+ }
58274
+ return value;
58275
+ }
58276
+ function requiredWireIdx(value, path) {
58277
+ if (!Number.isSafeInteger(value) || value < 0) {
58278
+ throw new TypeError(`${path} must be a non-negative integer.`);
58279
+ }
58280
+ return value;
58281
+ }
58282
+
58283
+ // src/buy/site.ts
58284
+ async function sourceInstanceFromClient(client) {
58285
+ const normalized = client.normalizedSiteUrl();
58286
+ return {
58287
+ kind: BUY_SOURCE_INSTANCE_KIND,
58288
+ siteId: await sha256FingerprintOfUtf8(normalized)
58289
+ };
58290
+ }
58291
+
58292
+ // src/buy/capture.ts
58293
+ var CALLER_AUTHORITY_KEYS = [
58294
+ "sourceInstance",
58295
+ "capturedAt",
58296
+ "fingerprint",
58297
+ "canonicalText",
58298
+ "byteCount",
58299
+ "endpoint",
58300
+ "uri",
58301
+ "credentials",
58302
+ "baseUrl",
58303
+ "siteId",
58304
+ "attestedTimestamp",
58305
+ "digest"
58306
+ ];
58307
+ function parseBuyCaptureInput(value) {
58308
+ const root = asRecord(value, "buy capture input");
58309
+ for (const key of CALLER_AUTHORITY_KEYS) {
58310
+ if (key in root) {
58311
+ throw new BuyCaptureError(
58312
+ "BUY_CAPTURE_CALLER_AUTHORITY_REJECTED",
58313
+ `buy capture input must not include caller-supplied ${key}.`,
58314
+ {
58315
+ recovery: "Omit sourceInstance, capturedAt, fingerprints, endpoints, and credentials; the configured FrappeClient and server clock own those fields.",
58316
+ context: { key }
58317
+ }
58318
+ );
58319
+ }
58320
+ }
58321
+ const keys = Object.keys(root);
58322
+ if (keys.length !== 1 || keys[0] !== "documents") {
58323
+ throw new BuyCaptureError(
58324
+ "BUY_CAPTURE_INVALID_INPUT",
58325
+ "buy capture input must contain only documents.",
58326
+ {
58327
+ recovery: "Pass a bounded list of exact {doctype, name, expectedModified?} references.",
58328
+ context: { keys }
58329
+ }
58330
+ );
58331
+ }
58332
+ if (!Array.isArray(root.documents)) {
58333
+ throw new BuyCaptureError(
58334
+ "BUY_CAPTURE_INVALID_INPUT",
58335
+ "buy capture input.documents must be an array.",
58336
+ {
58337
+ recovery: "Pass a dense list of exact document references."
58338
+ }
58339
+ );
58340
+ }
58341
+ if (root.documents.length === 0) {
58342
+ throw new BuyCaptureError(
58343
+ "BUY_CAPTURE_INVALID_INPUT",
58344
+ "buy capture input.documents must not be empty.",
58345
+ {
58346
+ recovery: "Name the exact documents to capture."
58347
+ }
58348
+ );
58349
+ }
58350
+ if (root.documents.length > BUY_CAPTURE_MAX_DOCUMENTS) {
58351
+ throw new BuyCaptureError(
58352
+ "BUY_CAPTURE_BOUNDS_EXCEEDED",
58353
+ `buy capture input.documents exceeds the ${BUY_CAPTURE_MAX_DOCUMENTS}-document bound.`,
58354
+ {
58355
+ recovery: "Narrow the capture; silent pagination is not allowed.",
58356
+ context: {
58357
+ limit: BUY_CAPTURE_MAX_DOCUMENTS,
58358
+ actual: root.documents.length
58359
+ }
58360
+ }
58361
+ );
58362
+ }
58363
+ const seen = /* @__PURE__ */ new Set();
58364
+ const documents = root.documents.map(
58365
+ (item, index) => parseDocumentRef(item, index, seen)
58366
+ );
58367
+ return { documents };
58368
+ }
58369
+ async function parseBuySourceCapture(value) {
58370
+ const root = exactRecord(value, [
58371
+ "schemaVersion",
58372
+ "sourceInstance",
58373
+ "capturedAt",
58374
+ "documents",
58375
+ "consistency"
58376
+ ], "buy source capture");
58377
+ literal2(
58378
+ root.schemaVersion,
58379
+ BUY_SOURCE_CAPTURE_SCHEMA,
58380
+ "buy source capture.schemaVersion"
58381
+ );
58382
+ const source = exactRecord(
58383
+ root.sourceInstance,
58384
+ ["kind", "siteId"],
58385
+ "buy source capture.sourceInstance"
58386
+ );
58387
+ literal2(
58388
+ source.kind,
58389
+ BUY_SOURCE_INSTANCE_KIND,
58390
+ "buy source capture.sourceInstance.kind"
58391
+ );
58392
+ const documents = [];
58393
+ const rawDocuments = boundedArray(
58394
+ root.documents,
58395
+ BUY_CAPTURE_MAX_DOCUMENTS,
58396
+ "buy source capture.documents"
58397
+ );
58398
+ if (rawDocuments.length === 0) {
58399
+ throw new TypeError("buy source capture.documents must not be empty.");
58400
+ }
58401
+ const seenDocuments = /* @__PURE__ */ new Set();
58402
+ for (let index = 0; index < rawDocuments.length; index += 1) {
58403
+ const parsed = await parseCapturedDocument(rawDocuments[index], index);
58404
+ const id = `${parsed.doctype}:${parsed.name}`;
58405
+ if (seenDocuments.has(id)) {
58406
+ throw new TypeError(
58407
+ `buy source capture.documents[${index}] duplicates ${id}.`
58408
+ );
58409
+ }
58410
+ seenDocuments.add(id);
58411
+ documents.push(parsed);
58412
+ }
58413
+ const consistency = exactRecord(
58414
+ root.consistency,
58415
+ ["kind", "reads", "consistent"],
58416
+ "buy source capture.consistency"
58417
+ );
58418
+ literal2(
58419
+ consistency.kind,
58420
+ BUY_CONSISTENCY_KIND,
58421
+ "buy source capture.consistency.kind"
58422
+ );
58423
+ if (consistency.reads !== BUY_CONSISTENCY_READS) {
58424
+ throw new TypeError(
58425
+ "buy source capture.consistency.reads must be 2."
58426
+ );
58427
+ }
58428
+ if (consistency.consistent !== true) {
58429
+ throw new TypeError(
58430
+ "buy source capture.consistency.consistent must be true; inconsistent captures are not a usable payload."
58431
+ );
58432
+ }
58433
+ return {
58434
+ schemaVersion: BUY_SOURCE_CAPTURE_SCHEMA,
58435
+ sourceInstance: {
58436
+ kind: BUY_SOURCE_INSTANCE_KIND,
58437
+ siteId: fingerprint(
58438
+ source.siteId,
58439
+ "buy source capture.sourceInstance.siteId"
58440
+ )
58441
+ },
58442
+ capturedAt: canonicalTimestamp(
58443
+ root.capturedAt,
58444
+ "buy source capture.capturedAt"
58445
+ ),
58446
+ documents,
58447
+ consistency: {
58448
+ kind: BUY_CONSISTENCY_KIND,
58449
+ reads: BUY_CONSISTENCY_READS,
58450
+ consistent: true
58451
+ }
58452
+ };
58453
+ }
58454
+ async function sealBuySourceCapture(capture) {
58455
+ const parsed = await parseBuySourceCapture(capture);
58456
+ const canonicalText = canonicalJson2(parsed, "buy source capture");
58457
+ const byteCount = utf8ByteCount(canonicalText);
58458
+ if (byteCount > BUY_CAPTURE_MAX_CANONICAL_BYTES) {
58459
+ throw new BuyCaptureError(
58460
+ "BUY_CAPTURE_BOUNDS_EXCEEDED",
58461
+ `buy source capture exceeds the ${BUY_CAPTURE_MAX_CANONICAL_BYTES}-byte bound.`,
58462
+ {
58463
+ recovery: "Narrow the capture; silent pagination is not allowed.",
58464
+ context: { limit: BUY_CAPTURE_MAX_CANONICAL_BYTES, actual: byteCount }
58465
+ }
58466
+ );
58467
+ }
58468
+ return {
58469
+ schemaVersion: BUY_SOURCE_CAPTURE_SCHEMA,
58470
+ capture: parsed,
58471
+ canonicalText,
58472
+ fingerprint: await sha256FingerprintOfUtf8(canonicalText),
58473
+ byteCount
58474
+ };
58475
+ }
58476
+ async function runBuyCapture(input, deps) {
58477
+ const parsedInput = parseBuyCaptureInput(input);
58478
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
58479
+ const capturedAt = canonicalTimestamp(
58480
+ now().toISOString(),
58481
+ "buy source capture.capturedAt"
58482
+ );
58483
+ const sourceInstance = await sourceInstanceFromClient(deps.client);
58484
+ const first = await readAll(parsedInput.documents, deps.client);
58485
+ const documents = await Promise.all(
58486
+ first.map((row) => projectBuyDocument(row.doctype, row.doc))
58487
+ );
58488
+ for (let index = 0; index < parsedInput.documents.length; index += 1) {
58489
+ const expected = parsedInput.documents[index].expectedModified;
58490
+ if (expected !== void 0 && documents[index].modified !== expected) {
58491
+ throw new BuyCaptureError(
58492
+ "BUY_CAPTURE_EXPECTED_MODIFIED_MISMATCH",
58493
+ `Document ${documents[index].doctype}:${documents[index].name} modified does not match expectedModified.`,
58494
+ {
58495
+ recovery: "Re-resolve the exact document revision before capturing.",
58496
+ context: {
58497
+ doctype: documents[index].doctype,
58498
+ name: documents[index].name,
58499
+ modified: documents[index].modified,
58500
+ expectedModified: expected
58501
+ }
58502
+ }
58503
+ );
58504
+ }
58505
+ }
58506
+ const second = await readAll(parsedInput.documents, deps.client);
58507
+ const reread = await Promise.all(
58508
+ second.map((row) => projectBuyDocument(row.doctype, row.doc))
58509
+ );
58510
+ for (let index = 0; index < documents.length; index += 1) {
58511
+ const a = documents[index];
58512
+ const b = reread[index];
58513
+ if (a.modified !== b.modified || a.fingerprint !== b.fingerprint) {
58514
+ throw new BuyCaptureError(
58515
+ "BUY_CAPTURE_INCONSISTENT",
58516
+ `Document ${a.doctype}:${a.name} changed between the two skipCache reads.`,
58517
+ {
58518
+ retryable: true,
58519
+ recovery: "Retry the capture. Repeated-read consistency failed; this is not a transactional snapshot.",
58520
+ context: {
58521
+ doctype: a.doctype,
58522
+ name: a.name,
58523
+ firstModified: a.modified,
58524
+ secondModified: b.modified,
58525
+ firstFingerprint: a.fingerprint,
58526
+ secondFingerprint: b.fingerprint
58527
+ }
58528
+ }
58529
+ );
58530
+ }
58531
+ }
58532
+ return await sealBuySourceCapture({
58533
+ schemaVersion: BUY_SOURCE_CAPTURE_SCHEMA,
58534
+ sourceInstance,
58535
+ capturedAt,
58536
+ documents,
58537
+ consistency: {
58538
+ kind: BUY_CONSISTENCY_KIND,
58539
+ reads: BUY_CONSISTENCY_READS,
58540
+ consistent: true
58541
+ }
58542
+ });
58543
+ }
58544
+ async function readAll(refs, client) {
58545
+ const docs = [];
58546
+ for (const ref of refs) {
58547
+ let doc;
58548
+ try {
58549
+ doc = await client.get(ref.doctype, ref.name, { skipCache: true });
58550
+ } catch (error2) {
58551
+ if (error2 instanceof FrappeAPIError && error2.status === 404) {
58552
+ throw new BuyCaptureError(
58553
+ "BUY_CAPTURE_MISSING_SOURCE",
58554
+ `Document ${ref.doctype}:${ref.name} was not found.`,
58555
+ {
58556
+ recovery: "Capture only documents that currently exist on the configured site.",
58557
+ context: { doctype: ref.doctype, name: ref.name }
58558
+ }
58559
+ );
58560
+ }
58561
+ throw error2;
58562
+ }
58563
+ assertExactReturnedIdentity(ref, doc);
58564
+ docs.push({ doctype: ref.doctype, doc });
58565
+ }
58566
+ return docs;
58567
+ }
58568
+ function assertExactReturnedIdentity(ref, doc) {
58569
+ if (doc.name !== ref.name) {
58570
+ throw new BuyCaptureError(
58571
+ "BUY_CAPTURE_IDENTITY_MISMATCH",
58572
+ `Requested ${ref.doctype}:${ref.name} but the client returned name ${String(doc.name)}.`,
58573
+ {
58574
+ recovery: "Refuse the response; do not relabel a different ERP document as the requested identity.",
58575
+ context: {
58576
+ requestedDoctype: ref.doctype,
58577
+ requestedName: ref.name,
58578
+ returnedName: doc.name
58579
+ }
58580
+ }
58581
+ );
58582
+ }
58583
+ if (doc.doctype !== void 0 && doc.doctype !== null && doc.doctype !== "" && doc.doctype !== ref.doctype) {
58584
+ throw new BuyCaptureError(
58585
+ "BUY_CAPTURE_IDENTITY_MISMATCH",
58586
+ `Requested ${ref.doctype}:${ref.name} but the client returned doctype ${String(doc.doctype)}.`,
58587
+ {
58588
+ recovery: "Refuse the response; do not relabel a different ERP document as the requested identity.",
58589
+ context: {
58590
+ requestedDoctype: ref.doctype,
58591
+ requestedName: ref.name,
58592
+ returnedDoctype: doc.doctype
58593
+ }
58594
+ }
58595
+ );
58596
+ }
58597
+ }
58598
+ function parseDocumentRef(value, index, seen) {
58599
+ const path = `buy capture input.documents[${index}]`;
58600
+ const root = asRecord(value, path);
58601
+ const keys = Object.keys(root).toSorted();
58602
+ const allowed = root.expectedModified === void 0 ? ["doctype", "name"] : ["doctype", "expectedModified", "name"];
58603
+ if (keys.length !== allowed.length || keys.some((key, keyIndex) => key !== allowed[keyIndex])) {
58604
+ throw new BuyCaptureError(
58605
+ "BUY_CAPTURE_INVALID_INPUT",
58606
+ `${path} must contain only doctype, name, and optional expectedModified.`,
58607
+ {
58608
+ recovery: "Do not pass endpoints, sourceInstance, or extra ERP fields.",
58609
+ context: { keys }
58610
+ }
58611
+ );
58612
+ }
58613
+ if (typeof root.doctype !== "string" || !isBuyCaptureDoctype(root.doctype)) {
58614
+ throw new BuyCaptureError(
58615
+ "BUY_CAPTURE_UNSUPPORTED_DOCTYPE",
58616
+ `${path}.doctype is not a closed Buy capture DocType.`,
58617
+ {
58618
+ recovery: `Use one of ${BUY_CAPTURE_DOCTYPE.join(", ")}. Generic doctypes cannot masquerade as a price.`,
58619
+ context: { doctype: root.doctype }
58620
+ }
58621
+ );
58622
+ }
58623
+ if (typeof root.name !== "string" || root.name.trim() === "" || root.name.trim() !== root.name) {
58624
+ throw new BuyCaptureError(
58625
+ "BUY_CAPTURE_INVALID_INPUT",
58626
+ `${path}.name must be the exact ERP document name.`,
58627
+ { recovery: "Pass the exact document name, not a label." }
58628
+ );
58629
+ }
58630
+ if (root.name.length > BUY_CAPTURE_MAX_NAME_LENGTH) {
58631
+ throw new BuyCaptureError(
58632
+ "BUY_CAPTURE_BOUNDS_EXCEEDED",
58633
+ `${path}.name exceeds the ${BUY_CAPTURE_MAX_NAME_LENGTH}-character bound.`,
58634
+ {
58635
+ recovery: "Use the exact ERP document name.",
58636
+ context: {
58637
+ limit: BUY_CAPTURE_MAX_NAME_LENGTH,
58638
+ actual: root.name.length
58639
+ }
58640
+ }
58641
+ );
58642
+ }
58643
+ const id = `${root.doctype}:${root.name}`;
58644
+ if (seen.has(id)) {
58645
+ throw new BuyCaptureError(
58646
+ "BUY_CAPTURE_INVALID_INPUT",
58647
+ `${path} duplicates ${id}.`,
58648
+ { recovery: "Each exact document reference may appear once." }
58649
+ );
58650
+ }
58651
+ seen.add(id);
58652
+ const expectedModified = root.expectedModified === void 0 ? void 0 : parseExpectedModified(root.expectedModified, `${path}.expectedModified`);
58653
+ return {
58654
+ doctype: root.doctype,
58655
+ name: root.name,
58656
+ ...expectedModified !== void 0 ? { expectedModified } : {}
58657
+ };
58658
+ }
58659
+ function parseExpectedModified(value, path) {
58660
+ if (typeof value !== "string" || value.trim() === "" || value.trim() !== value) {
58661
+ throw new BuyCaptureError(
58662
+ "BUY_CAPTURE_INVALID_INPUT",
58663
+ `${path} must be the exact Frappe modified timestamp.`,
58664
+ { recovery: "Pass the document's modified value, not a caller digest." }
58665
+ );
58666
+ }
58667
+ try {
58668
+ frappeDatetime(value, path);
58669
+ } catch {
58670
+ throw new BuyCaptureError(
58671
+ "BUY_CAPTURE_INVALID_INPUT",
58672
+ `${path} is not a Frappe datetime.`,
58673
+ { recovery: "Pass the document's modified value." }
58674
+ );
58675
+ }
58676
+ return value;
58677
+ }
58678
+ async function parseCapturedDocument(value, index) {
58679
+ const path = `buy source capture.documents[${index}]`;
58680
+ const root = record2(value, path);
58681
+ const keys = Object.keys(root).toSorted();
58682
+ const optional2 = /* @__PURE__ */ new Set(["docstatus", "status", "children"]);
58683
+ const required2 = [
58684
+ "doctype",
58685
+ "fields",
58686
+ "fingerprint",
58687
+ "modified",
58688
+ "name",
58689
+ "sourceCategory"
58690
+ ];
58691
+ const actualRequired = keys.filter((key) => !optional2.has(key)).toSorted();
58692
+ if (actualRequired.length !== required2.length || actualRequired.some((key, keyIndex) => key !== required2[keyIndex])) {
58693
+ throw new TypeError(`${path} contains missing or unsupported fields.`);
58694
+ }
58695
+ const extra = keys.filter(
58696
+ (key) => !required2.includes(key) && !optional2.has(key)
58697
+ );
58698
+ if (extra.length > 0) {
58699
+ throw new TypeError(`${path} contains missing or unsupported fields.`);
58700
+ }
58701
+ const doctype = oneOf(
58702
+ root.doctype,
58703
+ BUY_CAPTURE_DOCTYPE,
58704
+ `${path}.doctype`
58705
+ );
58706
+ const document = {
58707
+ doctype,
58708
+ name: nonEmpty(root.name, `${path}.name`),
58709
+ modified: frappeDatetime(root.modified, `${path}.modified`),
58710
+ ...root.docstatus === void 0 ? {} : {
58711
+ docstatus: parseDocstatus(root.docstatus, `${path}.docstatus`)
58712
+ },
58713
+ ...root.status === void 0 ? {} : { status: nonEmpty(root.status, `${path}.status`) },
58714
+ sourceCategory: nonEmpty(
58715
+ root.sourceCategory,
58716
+ `${path}.sourceCategory`
58717
+ ),
58718
+ fields: parseClosedWireFields(
58719
+ root.fields,
58720
+ BUY_DOCTYPE_FIELD_SPECS[doctype],
58721
+ `${path}.fields`
58722
+ ),
58723
+ ...root.children === void 0 ? {} : {
58724
+ children: parseClosedWireChildren(
58725
+ doctype,
58726
+ root.children,
58727
+ `${path}.children`
58728
+ )
58729
+ },
58730
+ fingerprint: fingerprint(root.fingerprint, `${path}.fingerprint`)
58731
+ };
58732
+ if (document.sourceCategory !== BUY_DOCTYPE_SOURCE_CATEGORY[doctype]) {
58733
+ throw new TypeError(
58734
+ `${path}.sourceCategory must match the closed DocType; a generic document cannot masquerade as a price.`
58735
+ );
58736
+ }
58737
+ const { fingerprint: _ignored, ...identity } = document;
58738
+ const expectedFingerprint = await sha256FingerprintOfUtf8(
58739
+ canonicalJson2(identity, `${path} identity`)
58740
+ );
58741
+ if (document.fingerprint !== expectedFingerprint) {
58742
+ throw new TypeError(
58743
+ `${path}.fingerprint must be SHA-256 of the document projection without fingerprint.`
58744
+ );
58745
+ }
58746
+ return document;
58747
+ }
58748
+ function parseDocstatus(value, path) {
58749
+ if (value !== 0 && value !== 1 && value !== 2) {
58750
+ throw new TypeError(`${path} must be 0, 1, or 2.`);
58751
+ }
58752
+ return value;
58753
+ }
58754
+ function asRecord(value, name) {
58755
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
58756
+ throw new BuyCaptureError(
58757
+ "BUY_CAPTURE_INVALID_INPUT",
58758
+ `${name} must be an object.`,
58759
+ { recovery: "Pass a JSON object with exact keys." }
58760
+ );
58761
+ }
58762
+ return value;
58763
+ }
58764
+
58765
+ // src/tools/buy.ts
58766
+ var buyTools = [
58767
+ {
58768
+ name: "erpnext_buy_capture",
58769
+ annotations: { readOnlyHint: true },
58770
+ description: "Read-only bounded capture of exact ERPNext commercial documents (Item, BOM, Item Price, Supplier Quotation, Supplier, Price List, UOM, Currency Exchange). Two skipCache reads must agree on modified and the closed projection fingerprint. Returns canonical JSON + SHA-256; does not store CAS, create BOM/RFQ/PO, compute a cost total, or qualify a live ERP. Not a registered purchase capability.",
58771
+ category: "buy",
58772
+ inputSchema: {
58773
+ type: "object",
58774
+ additionalProperties: false,
58775
+ required: ["documents"],
58776
+ properties: {
58777
+ documents: {
58778
+ type: "array",
58779
+ minItems: 1,
58780
+ maxItems: BUY_CAPTURE_MAX_DOCUMENTS,
58781
+ description: "Exact document references. Do not pass sourceInstance, URL, credentials, capturedAt, or a caller digest.",
58782
+ items: {
58783
+ type: "object",
58784
+ additionalProperties: false,
58785
+ required: ["doctype", "name"],
58786
+ properties: {
58787
+ doctype: {
58788
+ type: "string",
58789
+ enum: [...BUY_CAPTURE_DOCTYPE],
58790
+ description: "Closed Buy capture DocType"
58791
+ },
58792
+ name: {
58793
+ type: "string",
58794
+ minLength: 1,
58795
+ maxLength: 140,
58796
+ description: "Exact ERP document name"
58797
+ },
58798
+ expectedModified: {
58799
+ type: "string",
58800
+ description: "Optional exact Frappe modified timestamp to match"
58801
+ }
58802
+ }
58803
+ }
58804
+ }
58805
+ }
58806
+ },
58807
+ handler: async (input, ctx) => {
58808
+ return await runBuyCapture(input, { client: ctx.client });
58809
+ }
58810
+ }
58811
+ ];
58812
+
57440
58813
  // src/tools/mod.ts
57441
58814
  var toolsByCategory = {
57442
58815
  sales: salesTools,
@@ -57452,7 +58825,8 @@ var toolsByCategory = {
57452
58825
  operations: operationsTools,
57453
58826
  setup: setupTools,
57454
58827
  analytics: analyticsTools,
57455
- kanban: kanbanTools
58828
+ kanban: kanbanTools,
58829
+ buy: buyTools
57456
58830
  };
57457
58831
  var allTools = [
57458
58832
  ...salesTools,
@@ -57468,7 +58842,8 @@ var allTools = [
57468
58842
  ...operationsTools,
57469
58843
  ...setupTools,
57470
58844
  ...analyticsTools,
57471
- ...kanbanTools
58845
+ ...kanbanTools,
58846
+ ...buyTools
57472
58847
  ];
57473
58848
  function getToolsByCategory(category) {
57474
58849
  return toolsByCategory[category] ?? [];
@@ -58846,7 +60221,8 @@ var UI_VIEWERS = [
58846
60221
  "chart-viewer",
58847
60222
  "kpi-viewer",
58848
60223
  "funnel-viewer",
58849
- "kanban-viewer"
60224
+ "kanban-viewer",
60225
+ "buy-evidence-viewer"
58850
60226
  ];
58851
60227
  var VIEWER_RESOURCE_META = {
58852
60228
  ui: {
@@ -58860,6 +60236,110 @@ function withViewerResourceMeta(resource) {
58860
60236
  return { ...resource, _meta: VIEWER_RESOURCE_META };
58861
60237
  }
58862
60238
 
60239
+ // src/buy/manifest.ts
60240
+ var VIEW_APP_MANIFEST_SCHEMA = "io.casys.mcp.view-app-manifest/1.0";
60241
+ var VIEWER_SESSION_APPLY_ACTION = "viewer.session.apply";
60242
+ var BUY_VIEW_APP_MANIFEST = {
60243
+ schemaVersion: VIEW_APP_MANIFEST_SCHEMA,
60244
+ app: {
60245
+ id: BUY_VIEW_APP_ID,
60246
+ title: BUY_VIEW_APP_TITLE,
60247
+ version: BUY_VIEW_APP_VERSION
60248
+ },
60249
+ resources: [{
60250
+ uri: BUY_EVIDENCE_VIEWER_URI,
60251
+ ownership: "whole-view",
60252
+ resultSchemas: [BUY_RECORDED_RESULT_SCHEMA],
60253
+ acceptedActions: [VIEWER_SESSION_APPLY_ACTION],
60254
+ sessionSchemas: [BUY_RECORDED_SESSION_SCHEMA]
60255
+ }]
60256
+ };
60257
+ var BUY_VIEW_APP_MANIFEST_JSON = `${JSON.stringify(BUY_VIEW_APP_MANIFEST)}
60258
+ `;
60259
+ function parseBuyViewAppManifest(value) {
60260
+ const root = exactRecord(
60261
+ value,
60262
+ ["schemaVersion", "app", "resources"],
60263
+ "Buy View App manifest"
60264
+ );
60265
+ literal2(
60266
+ root.schemaVersion,
60267
+ VIEW_APP_MANIFEST_SCHEMA,
60268
+ "Buy View App manifest.schemaVersion"
60269
+ );
60270
+ const app = exactRecord(
60271
+ root.app,
60272
+ ["id", "title", "version"],
60273
+ "Buy View App manifest.app"
60274
+ );
60275
+ literal2(app.id, BUY_VIEW_APP_ID, "Buy View App manifest.app.id");
60276
+ literal2(app.title, BUY_VIEW_APP_TITLE, "Buy View App manifest.app.title");
60277
+ literal2(
60278
+ app.version,
60279
+ BUY_VIEW_APP_VERSION,
60280
+ "Buy View App manifest.app.version"
60281
+ );
60282
+ const resources = denseArray(
60283
+ root.resources,
60284
+ "Buy View App manifest.resources"
60285
+ );
60286
+ if (resources.length !== 1) {
60287
+ throw new TypeError(
60288
+ "Buy View App manifest.resources must contain exactly one whole view."
60289
+ );
60290
+ }
60291
+ const resource = exactRecord(
60292
+ resources[0],
60293
+ [
60294
+ "uri",
60295
+ "ownership",
60296
+ "resultSchemas",
60297
+ "acceptedActions",
60298
+ "sessionSchemas"
60299
+ ],
60300
+ "Buy View App manifest.resources[0]"
60301
+ );
60302
+ literal2(
60303
+ resource.uri,
60304
+ BUY_EVIDENCE_VIEWER_URI,
60305
+ "Buy View App manifest.resources[0].uri"
60306
+ );
60307
+ literal2(
60308
+ resource.ownership,
60309
+ "whole-view",
60310
+ "Buy View App manifest.resources[0].ownership"
60311
+ );
60312
+ const resultSchemas = denseArray(
60313
+ resource.resultSchemas,
60314
+ "Buy View App manifest.resources[0].resultSchemas"
60315
+ );
60316
+ if (resultSchemas.length !== 1 || resultSchemas[0] !== BUY_RECORDED_RESULT_SCHEMA) {
60317
+ throw new TypeError(
60318
+ "Buy View App manifest result schemas must be io.casys.mcp-erpnext.buy-recorded-result/1.0 exactly."
60319
+ );
60320
+ }
60321
+ const acceptedActions = denseArray(
60322
+ resource.acceptedActions,
60323
+ "Buy View App manifest.resources[0].acceptedActions"
60324
+ );
60325
+ if (acceptedActions.length !== 1 || acceptedActions[0] !== VIEWER_SESSION_APPLY_ACTION) {
60326
+ throw new TypeError(
60327
+ "Buy View App manifest must accept viewer.session.apply exactly."
60328
+ );
60329
+ }
60330
+ const sessionSchemas = denseArray(
60331
+ resource.sessionSchemas,
60332
+ "Buy View App manifest.resources[0].sessionSchemas"
60333
+ );
60334
+ if (sessionSchemas.length !== 1 || sessionSchemas[0] !== BUY_RECORDED_SESSION_SCHEMA) {
60335
+ throw new TypeError(
60336
+ "Buy View App manifest must bind buy-recorded-session/1.0 exactly."
60337
+ );
60338
+ }
60339
+ return BUY_VIEW_APP_MANIFEST;
60340
+ }
60341
+ parseBuyViewAppManifest(BUY_VIEW_APP_MANIFEST);
60342
+
58863
60343
  // src/ui/viewer-resource-paths.ts
58864
60344
  function fileUrlToPath2(url2) {
58865
60345
  const decodedPath = decodeURIComponent(url2.pathname);
@@ -59091,6 +60571,28 @@ function loadMrtrConfig() {
59091
60571
 
59092
60572
  // server.ts
59093
60573
  var DEFAULT_HTTP_PORT = 3012;
60574
+ function registerBuyViewAppManifest(server) {
60575
+ const bytes = new TextEncoder().encode(BUY_VIEW_APP_MANIFEST_JSON);
60576
+ server.registerResource(
60577
+ {
60578
+ uri: BUY_VIEW_APP_MANIFEST_URI,
60579
+ name: "ERPNext Buy Evidence App manifest",
60580
+ description: `Exact ${BUY_VIEW_APP_MANIFEST.app.id}@${BUY_VIEW_APP_MANIFEST.app.version} whole-view and recorded-session contract.`,
60581
+ mimeType: "application/json",
60582
+ size: bytes.byteLength
60583
+ },
60584
+ (requested) => {
60585
+ if (requested.toString() !== BUY_VIEW_APP_MANIFEST_URI) {
60586
+ throw new Error("Requested URI does not match the Buy App manifest.");
60587
+ }
60588
+ return {
60589
+ uri: BUY_VIEW_APP_MANIFEST_URI,
60590
+ mimeType: "application/json",
60591
+ text: BUY_VIEW_APP_MANIFEST_JSON
60592
+ };
60593
+ }
60594
+ );
60595
+ }
59094
60596
  async function main() {
59095
60597
  const args = getArgs3();
59096
60598
  if (args.includes("--inspect")) {
@@ -59120,7 +60622,7 @@ async function main() {
59120
60622
  );
59121
60623
  const server = new McpApp({
59122
60624
  name: "mcp-erpnext",
59123
- version: "3.1.0-beta.7",
60625
+ version: "3.1.0-beta.9",
59124
60626
  transport: "stateless",
59125
60627
  cache: {
59126
60628
  ttlMs: 36e5,
@@ -59141,6 +60643,7 @@ async function main() {
59141
60643
  const mcpTools = toolsClient.toMCPFormat();
59142
60644
  const handlers = toolsClient.buildHandlersMap();
59143
60645
  server.registerTools(mcpTools, handlers);
60646
+ registerBuyViewAppManifest(server);
59144
60647
  for (const viewerName of UI_VIEWERS) {
59145
60648
  const distPath = resolveViewerDistPath2(
59146
60649
  import.meta.url,