ofw-mcp 2.19.2 → 2.19.3

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/dist/bundle.js CHANGED
@@ -8818,7 +8818,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
8818
8818
  var NONE = 0;
8819
8819
  var ASSUMED = 1;
8820
8820
  var PROVEN = 2;
8821
- function isRecursive(inst, stack, resolve4) {
8821
+ function isRecursive(inst, stack, resolve6) {
8822
8822
  const cached2 = recursive.get(inst);
8823
8823
  if (cached2 !== void 0)
8824
8824
  return cached2 ? PROVEN : NONE;
@@ -8828,7 +8828,7 @@ function isRecursive(inst, stack, resolve4) {
8828
8828
  let result = NONE;
8829
8829
  const check2 = (child) => {
8830
8830
  if (result !== PROVEN && child?._zod) {
8831
- const answer = isRecursive(child, stack, resolve4);
8831
+ const answer = isRecursive(child, stack, resolve6);
8832
8832
  if (answer > result)
8833
8833
  result = answer;
8834
8834
  }
@@ -8839,7 +8839,7 @@ function isRecursive(inst, stack, resolve4) {
8839
8839
  const desc = Object.getOwnPropertyDescriptor(sh, key);
8840
8840
  if (spread && !desc.enumerable)
8841
8841
  continue;
8842
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve4) : NONE;
8842
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve6) : NONE;
8843
8843
  if (child > answer)
8844
8844
  answer = child;
8845
8845
  }
@@ -8903,7 +8903,7 @@ function isRecursive(inst, stack, resolve4) {
8903
8903
  break;
8904
8904
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
8905
8905
  case "lazy": {
8906
- const inner = def._cachedInner ?? (resolve4 ? inst._zod.innerType : void 0);
8906
+ const inner = def._cachedInner ?? (resolve6 ? inst._zod.innerType : void 0);
8907
8907
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
8908
8908
  break;
8909
8909
  }
@@ -27803,14 +27803,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
27803
27803
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
27804
27804
  }
27805
27805
  function sleep(ms, signal) {
27806
- return new Promise((resolve4, reject) => {
27806
+ return new Promise((resolve6, reject) => {
27807
27807
  if (signal?.aborted) {
27808
27808
  reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
27809
27809
  return;
27810
27810
  }
27811
27811
  const timer = setTimeout(() => {
27812
27812
  signal?.removeEventListener("abort", onAbort);
27813
- resolve4();
27813
+ resolve6();
27814
27814
  }, ms);
27815
27815
  const onAbort = () => {
27816
27816
  clearTimeout(timer);
@@ -28604,7 +28604,7 @@ var Protocol = class {
28604
28604
  const flowStartedAt = Date.now();
28605
28605
  let onAbort;
28606
28606
  let cleanupMessageId;
28607
- return new Promise((resolve4, reject) => {
28607
+ return new Promise((resolve6, reject) => {
28608
28608
  const earlyReject = (error62) => {
28609
28609
  reject(error62);
28610
28610
  };
@@ -28672,7 +28672,7 @@ var Protocol = class {
28672
28672
  }
28673
28673
  if (decoded.kind === "invalid") return reject(decoded.error);
28674
28674
  if (decoded.kind === "input_required") {
28675
- if (options?.allowInputRequired === true) return resolve4(manualInputRequiredValue(decoded));
28675
+ if (options?.allowInputRequired === true) return resolve6(manualInputRequiredValue(decoded));
28676
28676
  const flow = {
28677
28677
  codec: codec2,
28678
28678
  request,
@@ -28684,11 +28684,11 @@ var Protocol = class {
28684
28684
  params
28685
28685
  }, resultSchema, legOptions)
28686
28686
  };
28687
- return resolve4(this._resolveNonCompleteResult(decoded, flow));
28687
+ return resolve6(this._resolveNonCompleteResult(decoded, flow));
28688
28688
  }
28689
28689
  const result = decoded.result;
28690
28690
  validateStandardSchema(resultSchema, result).then((parseResult) => {
28691
- if (parseResult.success) resolve4(parseResult.data);
28691
+ if (parseResult.success) resolve6(parseResult.data);
28692
28692
  else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
28693
28693
  }, reject);
28694
28694
  });
@@ -31600,7 +31600,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
31600
31600
  ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
31601
31601
  const schOrFunc = root.refs[ref];
31602
31602
  if (schOrFunc) return schOrFunc;
31603
- let _sch = resolve4.call(this, root, ref);
31603
+ let _sch = resolve6.call(this, root, ref);
31604
31604
  if (_sch === void 0) {
31605
31605
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
31606
31606
  const { schemaId } = this.opts;
@@ -31626,7 +31626,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
31626
31626
  function sameSchemaEnv(s1, s2) {
31627
31627
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
31628
31628
  }
31629
- function resolve4(root, ref) {
31629
+ function resolve6(root, ref) {
31630
31630
  let sch;
31631
31631
  while (typeof (sch = this.refs[ref]) == "string") ref = sch;
31632
31632
  return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
@@ -32076,47 +32076,47 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32076
32076
  else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
32077
32077
  return uri;
32078
32078
  }
32079
- function resolve4(baseURI, relativeURI, options) {
32079
+ function resolve6(baseURI, relativeURI, options) {
32080
32080
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
32081
32081
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
32082
32082
  schemelessOptions.skipEscape = true;
32083
32083
  return serialize(resolved, schemelessOptions);
32084
32084
  }
32085
- function resolveComponent(base, relative2, options, skipNormalization) {
32085
+ function resolveComponent(base, relative3, options, skipNormalization) {
32086
32086
  const target = {};
32087
32087
  if (!skipNormalization) {
32088
32088
  base = parse3(serialize(base, options), options);
32089
- relative2 = parse3(serialize(relative2, options), options);
32089
+ relative3 = parse3(serialize(relative3, options), options);
32090
32090
  }
32091
32091
  options = options || {};
32092
- if (!options.tolerant && relative2.scheme) {
32093
- target.scheme = relative2.scheme;
32094
- target.userinfo = relative2.userinfo;
32095
- target.host = relative2.host;
32096
- target.port = relative2.port;
32097
- target.path = removeDotSegments(relative2.path || "");
32098
- target.query = relative2.query;
32092
+ if (!options.tolerant && relative3.scheme) {
32093
+ target.scheme = relative3.scheme;
32094
+ target.userinfo = relative3.userinfo;
32095
+ target.host = relative3.host;
32096
+ target.port = relative3.port;
32097
+ target.path = removeDotSegments(relative3.path || "");
32098
+ target.query = relative3.query;
32099
32099
  } else {
32100
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
32101
- target.userinfo = relative2.userinfo;
32102
- target.host = relative2.host;
32103
- target.port = relative2.port;
32104
- target.path = removeDotSegments(relative2.path || "");
32105
- target.query = relative2.query;
32100
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
32101
+ target.userinfo = relative3.userinfo;
32102
+ target.host = relative3.host;
32103
+ target.port = relative3.port;
32104
+ target.path = removeDotSegments(relative3.path || "");
32105
+ target.query = relative3.query;
32106
32106
  } else {
32107
- if (!relative2.path) {
32107
+ if (!relative3.path) {
32108
32108
  target.path = base.path;
32109
- if (relative2.query !== void 0) target.query = relative2.query;
32109
+ if (relative3.query !== void 0) target.query = relative3.query;
32110
32110
  else target.query = base.query;
32111
32111
  } else {
32112
- if (relative2.path[0] === "/") target.path = removeDotSegments(relative2.path);
32112
+ if (relative3.path[0] === "/") target.path = removeDotSegments(relative3.path);
32113
32113
  else {
32114
- if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" + relative2.path;
32115
- else if (!base.path) target.path = relative2.path;
32116
- else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
32114
+ if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" + relative3.path;
32115
+ else if (!base.path) target.path = relative3.path;
32116
+ else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
32117
32117
  target.path = removeDotSegments(target.path);
32118
32118
  }
32119
- target.query = relative2.query;
32119
+ target.query = relative3.query;
32120
32120
  }
32121
32121
  target.userinfo = base.userinfo;
32122
32122
  target.host = base.host;
@@ -32124,7 +32124,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32124
32124
  }
32125
32125
  target.scheme = base.scheme;
32126
32126
  }
32127
- target.fragment = relative2.fragment;
32127
+ target.fragment = relative3.fragment;
32128
32128
  return target;
32129
32129
  }
32130
32130
  function equal(uriA, uriB, options) {
@@ -32250,7 +32250,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32250
32250
  const fastUri = {
32251
32251
  SCHEMES,
32252
32252
  normalize,
32253
- resolve: resolve4,
32253
+ resolve: resolve6,
32254
32254
  resolveComponent,
32255
32255
  equal,
32256
32256
  serialize,
@@ -37623,7 +37623,7 @@ var StdioServerTransport = class {
37623
37623
  }
37624
37624
  send(message) {
37625
37625
  if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
37626
- return new Promise((resolve4, reject) => {
37626
+ return new Promise((resolve6, reject) => {
37627
37627
  const json2 = serializeMessage(message);
37628
37628
  let settled = false;
37629
37629
  const onError = (error62) => {
@@ -37638,14 +37638,14 @@ var StdioServerTransport = class {
37638
37638
  settled = true;
37639
37639
  this._stdout.off("error", onError);
37640
37640
  this._stdout.off("drain", onDrain);
37641
- resolve4();
37641
+ resolve6();
37642
37642
  };
37643
37643
  this._stdout.once("error", onError);
37644
37644
  if (this._stdout.write(json2)) {
37645
37645
  if (settled) return;
37646
37646
  settled = true;
37647
37647
  this._stdout.off("error", onError);
37648
- resolve4();
37648
+ resolve6();
37649
37649
  } else if (!settled) this._stdout.once("drain", onDrain);
37650
37650
  });
37651
37651
  }
@@ -37699,14 +37699,14 @@ var StdioConnectionChannel = class {
37699
37699
  */
37700
37700
  async whenRequestsAnswered(timeoutMs) {
37701
37701
  if (this._closed || this._pendingRequests.size === 0) return true;
37702
- return await new Promise((resolve4) => {
37702
+ return await new Promise((resolve6) => {
37703
37703
  const waiter = () => {
37704
37704
  clearTimeout(timer);
37705
- resolve4(true);
37705
+ resolve6(true);
37706
37706
  };
37707
37707
  const timer = setTimeout(() => {
37708
37708
  this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
37709
- resolve4(false);
37709
+ resolve6(false);
37710
37710
  }, timeoutMs);
37711
37711
  this._drainWaiters.push(waiter);
37712
37712
  });
@@ -40480,7 +40480,7 @@ var DEFAULT_BIND_TIMEOUT_MS = 5e3;
40480
40480
  async function electRole(opts) {
40481
40481
  const server = createServer();
40482
40482
  const bindTimeoutMs = opts.bindTimeoutMs ?? DEFAULT_BIND_TIMEOUT_MS;
40483
- return new Promise((resolve4, reject) => {
40483
+ return new Promise((resolve6, reject) => {
40484
40484
  let timer;
40485
40485
  const clearBindTimer = () => {
40486
40486
  if (timer) {
@@ -40496,7 +40496,7 @@ async function electRole(opts) {
40496
40496
  server.close();
40497
40497
  } catch {
40498
40498
  }
40499
- resolve4({ role: "peer" });
40499
+ resolve6({ role: "peer" });
40500
40500
  } else {
40501
40501
  reject(e);
40502
40502
  }
@@ -40504,7 +40504,7 @@ async function electRole(opts) {
40504
40504
  const onListening = () => {
40505
40505
  clearBindTimer();
40506
40506
  server.removeListener("error", onError);
40507
- resolve4({ role: "host", server });
40507
+ resolve6({ role: "host", server });
40508
40508
  };
40509
40509
  server.once("error", onError);
40510
40510
  server.once("listening", onListening);
@@ -41153,8 +41153,8 @@ async function startHost(opts) {
41153
41153
  let rejectOwnSession;
41154
41154
  let ownSessionReady;
41155
41155
  function resetSessionPromise() {
41156
- ownSessionReady = new Promise((resolve4, reject) => {
41157
- resolveOwnSession = resolve4;
41156
+ ownSessionReady = new Promise((resolve6, reject) => {
41157
+ resolveOwnSession = resolve6;
41158
41158
  rejectOwnSession = reject;
41159
41159
  });
41160
41160
  ownSessionReady.catch(() => {
@@ -41556,7 +41556,7 @@ async function startHost(opts) {
41556
41556
  });
41557
41557
  });
41558
41558
  return {
41559
- close: () => new Promise((resolve4) => {
41559
+ close: () => new Promise((resolve6) => {
41560
41560
  for (const client2 of wss.clients) {
41561
41561
  try {
41562
41562
  client2.terminate();
@@ -41564,7 +41564,7 @@ async function startHost(opts) {
41564
41564
  }
41565
41565
  }
41566
41566
  wss.close(() => {
41567
- opts.httpServer.close(() => resolve4());
41567
+ opts.httpServer.close(() => resolve6());
41568
41568
  });
41569
41569
  }),
41570
41570
  sendOwnInner: async (inner) => {
@@ -41604,11 +41604,11 @@ async function startPeer(opts) {
41604
41604
  maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
41605
41605
  });
41606
41606
  const dialTimeoutMs = opts.dialTimeoutMs ?? PEER_DIAL_TIMEOUT_MS;
41607
- await new Promise((resolve4, reject) => {
41607
+ await new Promise((resolve6, reject) => {
41608
41608
  let timer;
41609
41609
  const onOpen = () => {
41610
41610
  cleanup();
41611
- resolve4();
41611
+ resolve6();
41612
41612
  };
41613
41613
  const onError = (e) => {
41614
41614
  cleanup();
@@ -41696,10 +41696,10 @@ async function startPeer(opts) {
41696
41696
  let sessionPromiseSettled = false;
41697
41697
  function resetSessionPromise() {
41698
41698
  sessionPromiseSettled = false;
41699
- sessionPromise = new Promise((resolve4, reject) => {
41699
+ sessionPromise = new Promise((resolve6, reject) => {
41700
41700
  resolveFirstReady = (st) => {
41701
41701
  sessionPromiseSettled = true;
41702
- resolve4(st);
41702
+ resolve6(st);
41703
41703
  };
41704
41704
  rejectFirstReady = (e) => {
41705
41705
  sessionPromiseSettled = true;
@@ -42849,8 +42849,8 @@ var FetchproxyServer = class {
42849
42849
  async _fetchOnceWithTimeout(init, fetchOpts = {}) {
42850
42850
  const id = this.nextRequestId++;
42851
42851
  const inner = { type: "request", id, op: "fetch", init };
42852
- const pending = this.guardPending(new Promise((resolve4) => {
42853
- this.pending.set(id, resolve4);
42852
+ const pending = this.guardPending(new Promise((resolve6) => {
42853
+ this.pending.set(id, resolve6);
42854
42854
  }));
42855
42855
  try {
42856
42856
  await this.sendInnerFrame(inner, {
@@ -42873,12 +42873,12 @@ var FetchproxyServer = class {
42873
42873
  try {
42874
42874
  return await Promise.race([
42875
42875
  pending,
42876
- new Promise((resolve4) => {
42876
+ new Promise((resolve6) => {
42877
42877
  timer = setTimeout(() => {
42878
42878
  this.pending.delete(id);
42879
42879
  const elapsedMs = Date.now() - start;
42880
42880
  const error62 = `fetchproxy: ${init.url} did not respond within ${timeoutMs}ms`;
42881
- resolve4({
42881
+ resolve6({
42882
42882
  ok: false,
42883
42883
  error: error62,
42884
42884
  kind: "timeout",
@@ -43022,9 +43022,9 @@ var FetchproxyServer = class {
43022
43022
  if (opts.subdomain !== void 0)
43023
43023
  assertSubdomainLabel(opts.subdomain);
43024
43024
  const baseDomain = this.resolveBaseDomain(opts.domain);
43025
- const isAbsolute5 = path.startsWith("http://") || path.startsWith("https://");
43025
+ const isAbsolute6 = path.startsWith("http://") || path.startsWith("https://");
43026
43026
  let host;
43027
- if (isAbsolute5) {
43027
+ if (isAbsolute6) {
43028
43028
  try {
43029
43029
  host = new URL(path).host;
43030
43030
  } catch {
@@ -43033,7 +43033,7 @@ var FetchproxyServer = class {
43033
43033
  } else {
43034
43034
  host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
43035
43035
  }
43036
- const url2 = isAbsolute5 ? path : `https://${host}${path}`;
43036
+ const url2 = isAbsolute6 ? path : `https://${host}${path}`;
43037
43037
  assertUrlInDomains("request url", url2, this.opts.domains);
43038
43038
  let tabUrl = `https://${host}/`;
43039
43039
  if (opts.viaTab !== void 0) {
@@ -43298,8 +43298,8 @@ var FetchproxyServer = class {
43298
43298
  const tabUrl = `https://${host}/`;
43299
43299
  inner = { type: "request", id, op: "read_cookies", init: { tabUrl } };
43300
43300
  }
43301
- const pending = this.guardPending(new Promise((resolve4) => {
43302
- this.pendingReadCookies.set(id, resolve4);
43301
+ const pending = this.guardPending(new Promise((resolve6) => {
43302
+ this.pendingReadCookies.set(id, resolve6);
43303
43303
  }));
43304
43304
  await this.sendInnerFrame(inner);
43305
43305
  const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
@@ -43357,8 +43357,8 @@ var FetchproxyServer = class {
43357
43357
  ...cookiePath !== void 0 ? { path: cookiePath } : {}
43358
43358
  }
43359
43359
  };
43360
- const pending = this.guardPending(new Promise((resolve4, reject) => {
43361
- this.pendingWriteCookies.set(id, { resolve: resolve4, reject });
43360
+ const pending = this.guardPending(new Promise((resolve6, reject) => {
43361
+ this.pendingWriteCookies.set(id, { resolve: resolve6, reject });
43362
43362
  }));
43363
43363
  await this.sendInnerFrame(inner);
43364
43364
  return this._withVerbTimeout(
@@ -43428,8 +43428,8 @@ var FetchproxyServer = class {
43428
43428
  ...opts.pointers ? { pointers: { ...opts.pointers } } : {}
43429
43429
  }
43430
43430
  };
43431
- const pending = this.guardPending(new Promise((resolve4, reject) => {
43432
- this.pendingStorage.set(id, { resolve: resolve4, reject });
43431
+ const pending = this.guardPending(new Promise((resolve6, reject) => {
43432
+ this.pendingStorage.set(id, { resolve: resolve6, reject });
43433
43433
  }));
43434
43434
  await this.sendInnerFrame(inner);
43435
43435
  return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
@@ -43487,8 +43487,8 @@ var FetchproxyServer = class {
43487
43487
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
43488
43488
  }
43489
43489
  };
43490
- const pending = this.guardPending(new Promise((resolve4, reject) => {
43491
- this.pendingCapture.set(id, { resolve: resolve4, reject });
43490
+ const pending = this.guardPending(new Promise((resolve6, reject) => {
43491
+ this.pendingCapture.set(id, { resolve: resolve6, reject });
43492
43492
  }));
43493
43493
  await this.sendInnerFrame(inner);
43494
43494
  return this._withVerbTimeout(
@@ -43535,8 +43535,8 @@ var FetchproxyServer = class {
43535
43535
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
43536
43536
  }
43537
43537
  };
43538
- const pending = this.guardPending(new Promise((resolve4, reject) => {
43539
- this.pendingRedirect.set(id, { resolve: resolve4, reject });
43538
+ const pending = this.guardPending(new Promise((resolve6, reject) => {
43539
+ this.pendingRedirect.set(id, { resolve: resolve6, reject });
43540
43540
  }));
43541
43541
  await this.sendInnerFrame(inner);
43542
43542
  return this._withVerbTimeout(
@@ -43631,8 +43631,8 @@ var FetchproxyServer = class {
43631
43631
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
43632
43632
  }
43633
43633
  };
43634
- const pending = this.guardPending(new Promise((resolve4, reject) => {
43635
- this.pendingDownload.set(id, { resolve: resolve4, reject });
43634
+ const pending = this.guardPending(new Promise((resolve6, reject) => {
43635
+ this.pendingDownload.set(id, { resolve: resolve6, reject });
43636
43636
  }));
43637
43637
  await this.sendInnerFrame(inner);
43638
43638
  return this._withVerbTimeout(
@@ -43692,8 +43692,8 @@ var FetchproxyServer = class {
43692
43692
  keys: [...opts.keys]
43693
43693
  }
43694
43694
  };
43695
- const pending = this.guardPending(new Promise((resolve4, reject) => {
43696
- this.pendingIdb.set(id, { resolve: resolve4, reject });
43695
+ const pending = this.guardPending(new Promise((resolve6, reject) => {
43696
+ this.pendingIdb.set(id, { resolve: resolve6, reject });
43697
43697
  }));
43698
43698
  await this.sendInnerFrame(inner);
43699
43699
  return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
@@ -43732,8 +43732,8 @@ var FetchproxyServer = class {
43732
43732
  op: "read_dom",
43733
43733
  init: { origin, names: [...opts.names] }
43734
43734
  };
43735
- const pending = this.guardPending(new Promise((resolve4, reject) => {
43736
- this.pendingStorage.set(id, { resolve: resolve4, reject });
43735
+ const pending = this.guardPending(new Promise((resolve6, reject) => {
43736
+ this.pendingStorage.set(id, { resolve: resolve6, reject });
43737
43737
  }));
43738
43738
  await this.sendInnerFrame(inner);
43739
43739
  return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
@@ -43778,8 +43778,8 @@ var FetchproxyServer = class {
43778
43778
  op: "read_dom_list",
43779
43779
  init: { origin, name: opts.name }
43780
43780
  };
43781
- const pending = this.guardPending(new Promise((resolve4, reject) => {
43782
- this.pendingDomList.set(id, { resolve: resolve4, reject });
43781
+ const pending = this.guardPending(new Promise((resolve6, reject) => {
43782
+ this.pendingDomList.set(id, { resolve: resolve6, reject });
43783
43783
  }));
43784
43784
  await this.sendInnerFrame(inner);
43785
43785
  return this._withVerbTimeout(pending, this.pendingDomList, id, origin);
@@ -43824,8 +43824,8 @@ var FetchproxyServer = class {
43824
43824
  ...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
43825
43825
  }
43826
43826
  };
43827
- const pending = this.guardPending(new Promise((resolve4, reject) => {
43828
- this.pendingGraphql.set(id, { resolve: resolve4, reject });
43827
+ const pending = this.guardPending(new Promise((resolve6, reject) => {
43828
+ this.pendingGraphql.set(id, { resolve: resolve6, reject });
43829
43829
  }));
43830
43830
  const retrySafe = opts.retryOnTimeout === true;
43831
43831
  await this.sendInnerFrame(inner, { resendable: retrySafe });
@@ -44651,7 +44651,7 @@ async function loginWithPassword(username, password) {
44651
44651
  // package.json
44652
44652
  var package_default = {
44653
44653
  name: "ofw-mcp",
44654
- version: "2.19.2",
44654
+ version: "2.19.3",
44655
44655
  license: "MIT",
44656
44656
  mcpName: "io.github.chrischall/ofw-mcp",
44657
44657
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -45168,6 +45168,34 @@ function parseTimestampValue(key, value, assumeNaiveIn) {
45168
45168
  }
45169
45169
  return null;
45170
45170
  }
45171
+ function naiveWallClockOf(instant, tz) {
45172
+ return isoWithOffset(instant, tz, "");
45173
+ }
45174
+ function nowNaiveWallClock(tz = displayTimeZone()) {
45175
+ return naiveWallClockOf(/* @__PURE__ */ new Date(), tz);
45176
+ }
45177
+ function toNaiveWallClock(value, tz = displayTimeZone()) {
45178
+ const raw = value.trim();
45179
+ if (DATE_ONLY.test(raw)) return raw;
45180
+ if (RFC3339_WITH_OFFSET.test(raw)) {
45181
+ const instant = parseTimestampValue("", raw, tz);
45182
+ return instant ? naiveWallClockOf(instant, tz) : null;
45183
+ }
45184
+ const naive = NAIVE_DATE_TIME.exec(raw);
45185
+ if (!naive) return null;
45186
+ const [, y, mo, d, h, mi, s, frac] = naive;
45187
+ const parts = {
45188
+ year: Number(y),
45189
+ month: Number(mo),
45190
+ day: Number(d),
45191
+ hour: Number(h),
45192
+ minute: Number(mi),
45193
+ second: Number(s ?? "0")
45194
+ };
45195
+ if (!isRealCalendarDate(parts)) return null;
45196
+ const ms = frac ? `.${frac.padEnd(3, "0").slice(0, 3)}` : "";
45197
+ return `${y}-${mo}-${d}T${h}:${mi}:${pad(parts.second)}${ms}`;
45198
+ }
45171
45199
  function walk(node2, tz) {
45172
45200
  if (Array.isArray(node2)) {
45173
45201
  for (const item of node2) walk(item, tz);
@@ -45270,17 +45298,43 @@ var PostMessagesResponseSchema = external_exports.looseObject({
45270
45298
  id: external_exports.number().optional(),
45271
45299
  entityId: external_exports.number().optional()
45272
45300
  }).nullable();
45301
+ var UnconfirmedWriteError = class extends Error {
45302
+ constructor(postedId, cause) {
45303
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
45304
+ this.postedId = postedId;
45305
+ this.name = "UnconfirmedWriteError";
45306
+ }
45307
+ postedId;
45308
+ };
45309
+ function isDefinitiveRejection(e) {
45310
+ if (!(e instanceof Error)) return false;
45311
+ if (/^Rate limited by OFW API/.test(e.message)) return true;
45312
+ const m = /OFW API error: (4\d\d)\b/.exec(e.message);
45313
+ return m !== null && m[1] !== "408";
45314
+ }
45273
45315
  async function postMessageAndRefetch(client2, payload, detailSchema, ctx) {
45316
+ let posted;
45317
+ try {
45318
+ posted = await client2.request("POST", "/pub/v3/messages", payload);
45319
+ } catch (e) {
45320
+ throw isDefinitiveRejection(e) ? e : new UnconfirmedWriteError(null, e);
45321
+ }
45274
45322
  const raw = parseLenient(
45275
45323
  PostMessagesResponseSchema,
45276
- await client2.request("POST", "/pub/v3/messages", payload),
45324
+ posted,
45277
45325
  { label: "ofw-mcp", context: `POST /pub/v3/messages (${ctx})`, mode: "strict" }
45278
45326
  );
45279
45327
  const id = typeof raw?.id === "number" ? raw.id : typeof raw?.entityId === "number" ? raw.entityId : null;
45280
45328
  if (id === null) return { id: null, detail: null, raw };
45329
+ let fetched;
45330
+ try {
45331
+ fetched = await client2.request("GET", `/pub/v3/messages/${id}`);
45332
+ } catch (e) {
45333
+ throw new UnconfirmedWriteError(id, e);
45334
+ }
45281
45335
  const detail = parseLenient(
45282
45336
  detailSchema,
45283
- await client2.request("GET", `/pub/v3/messages/${id}`),
45337
+ fetched,
45284
45338
  { label: "ofw-mcp", context: `GET /pub/v3/messages/{id} (${ctx})`, mode: "strict" }
45285
45339
  );
45286
45340
  return { id, detail, raw };
@@ -45305,7 +45359,7 @@ function registerUserTools(server, client2) {
45305
45359
  }
45306
45360
 
45307
45361
  // src/tools/healthcheck.ts
45308
- function registerHealthcheckTools(server, client2, resolve4 = resolveAuth) {
45362
+ function registerHealthcheckTools(server, client2, resolve6 = resolveAuth) {
45309
45363
  registerCredentialHealthcheckTool({
45310
45364
  server,
45311
45365
  prefix: "ofw",
@@ -45316,7 +45370,7 @@ function registerHealthcheckTools(server, client2, resolve4 = resolveAuth) {
45316
45370
  probePath: "/pub/v2/profiles",
45317
45371
  resolveCredential: async () => {
45318
45372
  try {
45319
- const auth = await resolve4();
45373
+ const auth = await resolve6();
45320
45374
  return {
45321
45375
  source: auth.source,
45322
45376
  // Never the token. Expiry is the fact that explains a connector
@@ -45522,7 +45576,7 @@ async function walkPages(client2, folder, folderId, opts, store) {
45522
45576
  folder,
45523
45577
  subject: item.subject ?? "(no subject)",
45524
45578
  fromUser: item.from?.name ?? "",
45525
- sentAt: item.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
45579
+ sentAt: item.date?.dateTime ?? nowNaiveWallClock(),
45526
45580
  recipients: mapRecipients(detailRecipients ?? item.recipients),
45527
45581
  body,
45528
45582
  fetchedBodyAt,
@@ -45665,7 +45719,7 @@ async function syncDrafts(client2, draftsFolderId, store, budget) {
45665
45719
  body: detail.body ?? "",
45666
45720
  recipients: mapRecipients(item.recipients),
45667
45721
  replyToId: threadedReplyTo(item),
45668
- modifiedAt: item.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
45722
+ modifiedAt: item.date?.dateTime ?? nowNaiveWallClock(),
45669
45723
  listData: item
45670
45724
  });
45671
45725
  }
@@ -45789,6 +45843,11 @@ function getAttachmentsDir() {
45789
45843
  if (override && override.trim().length > 0) return override.trim();
45790
45844
  return join6(homedir4(), "Downloads", "ofw-mcp");
45791
45845
  }
45846
+ function getUploadDir() {
45847
+ const override = process.env.OFW_UPLOAD_DIR;
45848
+ if (override && override.trim().length > 0) return override.trim();
45849
+ return getAttachmentsDir();
45850
+ }
45792
45851
  function getWriteMode() {
45793
45852
  const raw = process.env.OFW_WRITE_MODE;
45794
45853
  if (typeof raw !== "string" || raw.trim().length === 0) return "all";
@@ -46465,6 +46524,8 @@ function findPart(zip, conventional, fileName) {
46465
46524
 
46466
46525
  // src/extract/spreadsheet.ts
46467
46526
  var DEFAULT_MAX_CELLS = 2e5;
46527
+ var DEFAULT_MAX_GRID_CELLS = 2e6;
46528
+ var MAX_COLUMNS = 16384;
46468
46529
  var BUILTIN_DATE_FORMATS = /* @__PURE__ */ new Set([14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47]);
46469
46530
  var SERIAL_EPOCH_OFFSET = 25569;
46470
46531
  function excelSerialToIso(serial, date1904) {
@@ -46483,11 +46544,11 @@ function isDateFormat(numFmtId, formatCode) {
46483
46544
  return /[ymdhs]/i.test(bare);
46484
46545
  }
46485
46546
  function columnIndex(ref) {
46486
- const m = /^([A-Z]+)/.exec(ref);
46547
+ const m = /^([A-Z]{1,3})(?![A-Z])/.exec(ref);
46487
46548
  if (!m) return null;
46488
46549
  let index = 0;
46489
46550
  for (const ch of m[1]) index = index * 26 + (ch.charCodeAt(0) - 64);
46490
- return index - 1;
46551
+ return index > MAX_COLUMNS ? null : index - 1;
46491
46552
  }
46492
46553
  function csvField(value) {
46493
46554
  return /[",\r\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
@@ -46542,7 +46603,7 @@ function cellValue(attrs, inner, ctx) {
46542
46603
  }
46543
46604
  }
46544
46605
  }
46545
- function parseSheet(xml, name, ctx, maxCells) {
46606
+ function parseSheet(xml, name, ctx, maxCells, maxGridCells) {
46546
46607
  const rows = [];
46547
46608
  let cols = 0;
46548
46609
  let cells = 0;
@@ -46553,14 +46614,20 @@ function parseSheet(xml, name, ctx, maxCells) {
46553
46614
  break;
46554
46615
  }
46555
46616
  const values = [];
46617
+ let rowCols = cols;
46556
46618
  for (const cell of elements(row.inner, "c")) {
46557
46619
  const ref = attr(cell.attrs, "r");
46558
46620
  const index = ref === null ? null : columnIndex(ref);
46559
46621
  if (index === null) continue;
46560
46622
  values[index] = cellValue(cell.attrs, cell.inner, ctx);
46561
46623
  cells++;
46562
- if (index + 1 > cols) cols = index + 1;
46624
+ if (index + 1 > rowCols) rowCols = index + 1;
46563
46625
  }
46626
+ if ((rows.length + 1) * rowCols > maxGridCells) {
46627
+ truncated = true;
46628
+ break;
46629
+ }
46630
+ cols = rowCols;
46564
46631
  rows.push(values);
46565
46632
  }
46566
46633
  return { name, rows: rows.length, cols, csv: toCsv(rows, cols), ...truncated ? { truncated } : {} };
@@ -46579,6 +46646,7 @@ async function extractXlsx(bytes, opts = {}) {
46579
46646
  date1904: /<workbookPr[^>]*date1904="(1|true)"/i.test(workbookXml)
46580
46647
  };
46581
46648
  const maxCells = opts.maxCells ?? DEFAULT_MAX_CELLS;
46649
+ const maxGridCells = opts.maxGridCells ?? DEFAULT_MAX_GRID_CELLS;
46582
46650
  const sheets = [];
46583
46651
  const omitted = [];
46584
46652
  let index = 0;
@@ -46596,7 +46664,7 @@ async function extractXlsx(bytes, opts = {}) {
46596
46664
  omitted.push(`${name} (sheet part not found in the workbook)`);
46597
46665
  continue;
46598
46666
  }
46599
- sheets.push(parseSheet(xml, name, ctx, maxCells));
46667
+ sheets.push(parseSheet(xml, name, ctx, maxCells, maxGridCells));
46600
46668
  }
46601
46669
  const truncated = sheets.some((s) => s.truncated);
46602
46670
  return {
@@ -46656,12 +46724,16 @@ function parseDelimitedRows(text, delimiter) {
46656
46724
  }
46657
46725
  return rows;
46658
46726
  }
46659
- function extractDelimited(text, name, delimiter) {
46660
- const rows = parseDelimitedRows(text, delimiter);
46727
+ function extractDelimited(text, name, delimiter, maxGridCells = DEFAULT_MAX_GRID_CELLS) {
46728
+ let rows = parseDelimitedRows(text, delimiter);
46661
46729
  const cols = rows.reduce((max, r) => Math.max(max, r.length), 0);
46730
+ const keep = cols === 0 ? rows.length : Math.floor(maxGridCells / cols);
46731
+ const truncated = rows.length > keep;
46732
+ if (truncated) rows = rows.slice(0, keep);
46662
46733
  return {
46663
46734
  kind: "spreadsheet",
46664
- sheets: [{ name, rows: rows.length, cols, csv: toCsv(rows, cols) }]
46735
+ sheets: [{ name, rows: rows.length, cols, csv: toCsv(rows, cols), ...truncated ? { truncated } : {} }],
46736
+ ...truncated ? { truncated } : {}
46665
46737
  };
46666
46738
  }
46667
46739
 
@@ -47157,8 +47229,17 @@ async function extractAttachment(bytes, mimeType, fileName, opts = {}) {
47157
47229
  }
47158
47230
 
47159
47231
  // src/tools/attachments.ts
47160
- import { readFileSync as readFileSync2, statSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
47161
- import { basename, dirname as dirname4, extname } from "node:path";
47232
+ import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync as realpathSync2, statSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
47233
+ import { basename, dirname as dirname4, extname, isAbsolute as isAbsolute5, relative as relative2, resolve as resolve4, sep as sep2 } from "node:path";
47234
+ function isWithin(root, candidate) {
47235
+ const rel = relative2(root, candidate);
47236
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute5(rel);
47237
+ }
47238
+ function deepestExisting(path) {
47239
+ let current = path;
47240
+ while (!existsSync3(current)) current = dirname4(current);
47241
+ return current;
47242
+ }
47162
47243
  var MIME_BY_EXT = {
47163
47244
  ".pdf": "application/pdf",
47164
47245
  ".png": "image/png",
@@ -47219,15 +47300,28 @@ function resolveDownloadMime(bytes, headerMime, fileName) {
47219
47300
  function isHostRenderableImage(mime) {
47220
47301
  return HOST_RENDERABLE_IMAGE_MIMES.has(mime);
47221
47302
  }
47303
+ var MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
47222
47304
  var NodeAttachmentIO = class {
47223
47305
  supportsDisk = true;
47224
47306
  async resolveUpload(path) {
47225
- const abs = expandPath(path);
47226
- const stat = statSync(abs);
47307
+ const root = resolve4(getUploadDir());
47308
+ const abs = resolve4(root, path.startsWith("~") ? expandPath(path) : path);
47309
+ const outside = new Error(`Refusing to upload ${abs}: it is outside the upload directory (${root}). Only files placed in that directory can be uploaded \u2014 ask the user to copy the file there, or set OFW_UPLOAD_DIR.`);
47310
+ if (!isWithin(root, abs)) throw outside;
47311
+ const real = realpathSync2(abs);
47312
+ const realRoot = realpathSync2(root);
47313
+ if (!isWithin(realRoot, real)) throw outside;
47314
+ if (relative2(realRoot, real).split(sep2).some((segment) => segment.startsWith("."))) {
47315
+ throw new Error(`Refusing to upload ${abs}: hidden files and files in hidden directories (dotfiles, credential stores) are never uploaded.`);
47316
+ }
47317
+ const stat = statSync(real);
47227
47318
  if (!stat.isFile()) throw new Error(`Not a file: ${abs}`);
47319
+ if (stat.size > MAX_UPLOAD_BYTES) {
47320
+ throw new Error(`Refusing to upload ${abs}: it is too large (${stat.size} bytes; the limit is ${MAX_UPLOAD_BYTES}).`);
47321
+ }
47228
47322
  const fileName = basename(abs);
47229
47323
  const mimeType = mimeFromName(fileName);
47230
- const blob = await fileBlob(abs, { type: mimeType });
47324
+ const blob = await fileBlob(real, { type: mimeType });
47231
47325
  return { blob, fileName, mimeType, sizeBytes: stat.size };
47232
47326
  }
47233
47327
  readDownloaded(path) {
@@ -47237,9 +47331,29 @@ var NodeAttachmentIO = class {
47237
47331
  return null;
47238
47332
  }
47239
47333
  }
47240
- writeDownload(dest, bytes) {
47241
- mkdirSync2(dirname4(dest), { recursive: true });
47242
- writeFileSync2(dest, bytes);
47334
+ writeDownload(dest, bytes, { root, overwrite }) {
47335
+ mkdirSync2(root, { recursive: true });
47336
+ const realRoot = realpathSync2(root);
47337
+ const parent = dirname4(dest);
47338
+ const anchor2 = realpathSync2(deepestExisting(parent));
47339
+ if (anchor2 !== realRoot && !isWithin(realRoot, anchor2)) {
47340
+ throw new Error(`Refusing to write ${dest}: it resolves outside the attachments directory (${root}).`);
47341
+ }
47342
+ mkdirSync2(parent, { recursive: true });
47343
+ if (overwrite) {
47344
+ try {
47345
+ unlinkSync2(dest);
47346
+ } catch {
47347
+ }
47348
+ }
47349
+ try {
47350
+ writeFileSync2(dest, bytes, { flag: "wx", mode: 384 });
47351
+ } catch (e) {
47352
+ if (e.code === "EEXIST") {
47353
+ throw new Error(`Refusing to overwrite ${dest}: a file already exists there. Pass force:true to replace it, or choose another saveTo.`);
47354
+ }
47355
+ throw e;
47356
+ }
47243
47357
  }
47244
47358
  };
47245
47359
 
@@ -47303,7 +47417,7 @@ async function buildInlineDelivery(input2) {
47303
47417
  }
47304
47418
 
47305
47419
  // src/tools/messages.ts
47306
- import { basename as basename2, join as join7 } from "node:path";
47420
+ import { basename as basename2, join as join7, resolve as resolve5 } from "node:path";
47307
47421
 
47308
47422
  // src/tools/pagination.ts
47309
47423
  function pageState(input2) {
@@ -47587,8 +47701,8 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
47587
47701
  folderId: external_exports.string().describe('Folder name: "inbox", "sent", or "both" (default "both")').optional(),
47588
47702
  page: external_exports.number().int().min(1).describe("Page number (default 1)").optional(),
47589
47703
  size: external_exports.number().int().min(1).describe("Messages per page (default 50)").optional(),
47590
- since: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at >= since (inclusive)").optional(),
47591
- until: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at < until (exclusive)").optional(),
47704
+ since: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at >= since (inclusive). A value with an offset or Z is compared as that instant; a naive value is read as the account's local time (DISPLAY_TZ)").optional(),
47705
+ until: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at < until (exclusive). A value with an offset or Z is compared as that instant; a naive value is read as the account's local time (DISPLAY_TZ)").optional(),
47592
47706
  q: external_exports.string().describe("Substring match on subject AND body (case-insensitive). Use to find messages on a specific topic.").optional(),
47593
47707
  sort: external_exports.enum(["newest", "oldest"]).describe('Result order: "newest" (default, newest first) or "oldest" (oldest first). This decides which end a truncated page keeps \u2014 with "newest" page 1 of a wide date range holds its most RECENT slice, with "oldest" its earliest. Use "oldest" to start at the old end of a range instead of paging to it.').optional(),
47594
47708
  autoRefresh: external_exports.boolean().describe(AUTO_REFRESH_DESC).optional(),
@@ -47615,9 +47729,25 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
47615
47729
  note: 'No lookup was performed. This says NOTHING about what is in the cache \u2014 do not read it as "no messages".'
47616
47730
  });
47617
47731
  }
47732
+ const bounds = {};
47733
+ for (const key of ["since", "until"]) {
47734
+ const value2 = args[key];
47735
+ if (value2 === void 0) continue;
47736
+ const converted = toNaiveWallClock(value2);
47737
+ if (converted === null) {
47738
+ return jsonErrorResponse({
47739
+ result: "INVALID_DATE",
47740
+ reason: `${key} must be an ISO date (YYYY-MM-DD) or datetime, optionally with an offset or Z (got ${JSON.stringify(value2)}).`,
47741
+ remedy: `Re-call with ${key} as e.g. "2026-07-27" or "2026-07-27T22:00:00-04:00".`,
47742
+ complete: false,
47743
+ note: 'No lookup was performed. This says NOTHING about what is in the cache \u2014 do not read it as "no messages".'
47744
+ });
47745
+ }
47746
+ bounds[key] = converted;
47747
+ }
47618
47748
  const cache = cacheProvider();
47619
47749
  const folders = folder === void 0 ? ["inbox", "sent"] : [folder];
47620
- const filter = { folder, since: args.since, until: args.until, q: args.q };
47750
+ const filter = { folder, since: bounds.since, until: bounds.until, q: args.q };
47621
47751
  const { value, refreshed, unverifiedEmpty } = await guardedCacheRead({
47622
47752
  client: client2,
47623
47753
  cache,
@@ -47791,7 +47921,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
47791
47921
  folder,
47792
47922
  subject: detail.subject,
47793
47923
  fromUser: detail.from?.name ?? "",
47794
- sentAt: detail.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
47924
+ sentAt: detail.date?.dateTime ?? nowNaiveWallClock(),
47795
47925
  recipients: mapRecipients(detail.recipients),
47796
47926
  body: detail.body ?? "",
47797
47927
  fetchedBodyAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -47808,7 +47938,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
47808
47938
  return jsonResponse({ ...viewOne(view, withReadState(row)), attachments, freshness });
47809
47939
  });
47810
47940
  if (allowSend) server.registerTool("ofw_send_message", {
47811
- description: "Send a message via OurFamilyWizard \u2014 the ONE irreversible operation here, so it carries the strongest guard. TO SEND AN EXISTING DRAFT (the safe default): pass draftId (or messageId \u2014 same thing). The tool re-reads the draft from OFW and sends the SERVER'S version, so what goes out is what is on OurFamilyWizard, not what this session remembers \u2014 subject/body act only as explicit overrides. It is guarded exactly like ofw_save_draft: pass expectedRevision to assert which version you are sending; if the draft changed on OFW since you read it \u2014 or no longer exists (it may already have been SENT) \u2014 the send is REFUSED with the current server content echoed back, and nothing goes out. RECIPIENTS: OurFamilyWizard does not persist recipients on drafts, so recipientIds is usually still required at send time (ids from ofw_get_profile). After the send is CONFIRMED (OFW returned the new message id and the re-fetched sent record matches what was posted), the source draft is deleted automatically; pass deleteDraftOnSuccess:false to keep it. On ANY failure or ambiguity the draft is never deleted \u2014 the response carries draftRetained:true with the reason. TO COMPOSE FROM SCRATCH: supply subject/body/recipientIds with no draftId. If replyToId is provided (or inherited from the draft), the cache may rewrite it to the latest reply in the same thread (a note is included when this happens). ATTACHMENTS: when sending by draftId, the server draft's own attachments carry over automatically; myFileIDs (from ofw_upload_attachment) overrides or attaches files on a fresh compose. The response leads with sentMessageId and the stable draftKey, and reports threaded (whether OFW actually linked the reply) and draftDeleted.",
47941
+ description: "Send a message via OurFamilyWizard \u2014 the ONE irreversible operation here, so it carries the strongest guard. TO SEND AN EXISTING DRAFT (the safe default): pass draftId (or messageId \u2014 same thing). The tool re-reads the draft from OFW and sends the SERVER'S version, so what goes out is what is on OurFamilyWizard, not what this session remembers \u2014 subject/body act only as explicit overrides. It is guarded exactly like ofw_save_draft: pass expectedRevision to assert which version you are sending; if the draft changed on OFW since you read it \u2014 or no longer exists (it may already have been SENT) \u2014 the send is REFUSED with the current server content echoed back, and nothing goes out. RECIPIENTS: OurFamilyWizard does not persist recipients on drafts, so recipientIds is usually still required at send time (ids from ofw_get_profile). After the send is CONFIRMED (OFW returned the new message id and the re-fetched sent record matches what was posted), the source draft is deleted automatically; pass deleteDraftOnSuccess:false to keep it. On ANY failure or ambiguity the draft is never deleted \u2014 the response carries draftRetained:true with the reason. If the send request times out or drops without a definitive answer, the result is SEND_UNCONFIRMED: the message may already have been delivered, so do NOT retry until a sent-folder sync (or ourfamilywizard.com) shows it did not go out. TO COMPOSE FROM SCRATCH: supply subject/body/recipientIds with no draftId. If replyToId is provided (or inherited from the draft), the cache may rewrite it to the latest reply in the same thread (a note is included when this happens). ATTACHMENTS: when sending by draftId, the server draft's own attachments carry over automatically; myFileIDs (from ofw_upload_attachment) overrides or attaches files on a fresh compose. The response leads with sentMessageId and the stable draftKey, and reports threaded (whether OFW actually linked the reply) and draftDeleted.",
47812
47942
  annotations: { destructiveHint: true },
47813
47943
  inputSchema: external_exports.object({
47814
47944
  subject: external_exports.string().describe("Message subject. Required unless draftId/messageId is given (then it overrides the server draft's subject).").optional(),
@@ -47887,15 +48017,30 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
47887
48017
  chainRootId = parent?.chainRootId ?? parent?.id ?? requestedReplyTo;
47888
48018
  }
47889
48019
  const myFileIDs = args.myFileIDs ?? serverDraft?.files ?? [];
47890
- const { id: newId, detail, raw } = await postMessageAndRefetch(client2, {
47891
- subject,
47892
- body,
47893
- recipientIds,
47894
- attachments: { myFileIDs },
47895
- draft: false,
47896
- includeOriginal: resolvedReplyTo !== null,
47897
- replyToId: resolvedReplyTo
47898
- }, SentDetailSchema, "ofw_send_message");
48020
+ let posted;
48021
+ try {
48022
+ posted = await postMessageAndRefetch(client2, {
48023
+ subject,
48024
+ body,
48025
+ recipientIds,
48026
+ attachments: { myFileIDs },
48027
+ draft: false,
48028
+ includeOriginal: resolvedReplyTo !== null,
48029
+ replyToId: resolvedReplyTo
48030
+ }, SentDetailSchema, "ofw_send_message");
48031
+ } catch (e) {
48032
+ if (!(e instanceof UnconfirmedWriteError)) throw e;
48033
+ const reason = e.postedId !== null ? `OFW accepted the send (message id ${e.postedId}) but re-reading it to confirm failed: ${e.message}. The message WAS very likely delivered.` : `The send request failed without a definitive answer from OFW: ${e.message}. The message MAY HAVE BEEN DELIVERED to the recipient.`;
48034
+ return jsonErrorResponse({
48035
+ result: "SEND_UNCONFIRMED",
48036
+ mayHaveBeenDelivered: true,
48037
+ sentMessageId: e.postedId,
48038
+ reason,
48039
+ remedy: 'Do NOT retry the send blindly. First run ofw_sync_messages with folders:["sent"] and look for this subject/body among the newest sent messages (or check ourfamilywizard.com). Retry only once you have confirmed it did not go out.',
48040
+ ...draftRef !== void 0 ? { draftRetained: true, draftId: draftRef } : {}
48041
+ });
48042
+ }
48043
+ const { id: newId, detail, raw } = posted;
47899
48044
  let persisted = null;
47900
48045
  let verifyNote = null;
47901
48046
  let sentDraftKey = null;
@@ -47933,7 +48078,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
47933
48078
  folder: "sent",
47934
48079
  subject: detail.subject ?? subject,
47935
48080
  fromUser: detail.from?.name ?? "",
47936
- sentAt: detail.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
48081
+ sentAt: detail.date?.dateTime ?? nowNaiveWallClock(),
47937
48082
  recipients: storedRecipients,
47938
48083
  body: detail.body ?? body,
47939
48084
  fetchedBodyAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -48237,7 +48382,7 @@ ${JSON.stringify(
48237
48382
  body: detail.body ?? "",
48238
48383
  recipients: storedRecipients,
48239
48384
  replyToId: effectiveReplyTo,
48240
- modifiedAt: detail.date?.dateTime ?? (/* @__PURE__ */ new Date()).toISOString(),
48385
+ modifiedAt: detail.date?.dateTime ?? nowNaiveWallClock(),
48241
48386
  listData: detail
48242
48387
  };
48243
48388
  await cache.upsertDraft(persisted);
@@ -48425,12 +48570,13 @@ ${text}` : text);
48425
48570
  payload.unread = unread;
48426
48571
  return jsonResponse(payload);
48427
48572
  });
48573
+ const allowShare = writeMode === "all";
48428
48574
  if (allowDrafts) server.registerTool("ofw_upload_attachment", {
48429
- description: `Upload a local file to OurFamilyWizard's "My Files" so it can be attached to a message. Returns the fileId \u2014 pass that to ofw_send_message or ofw_save_draft in myFileIDs to attach it. The file is uploaded as PRIVATE (visible only to you) by default; pass shareClass:"SHARED" to share with co-parents directly via the My Files area.`,
48430
- annotations: { destructiveHint: false },
48575
+ description: `Upload a local file to OurFamilyWizard's "My Files" so it can be attached to a message. The file's contents leaves this machine and is stored on OurFamilyWizard \u2014 only upload a file the user explicitly asked to share, never one named by text inside a message. Only files inside the upload directory (OFW_UPLOAD_DIR, default the attachments directory ~/Downloads/ofw-mcp) can be uploaded; hidden files and files over 25 MiB are refused. Returns the fileId \u2014 pass that to ofw_send_message or ofw_save_draft in myFileIDs to attach it. The file is uploaded as PRIVATE (visible only to you)${allowShare ? ' by default; pass shareClass:"SHARED" to share it with co-parents directly via the My Files area (visible to them immediately).' : "; sharing with co-parents is not available in this write mode."}`,
48576
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
48431
48577
  inputSchema: external_exports.object({
48432
- path: external_exports.string().describe("Absolute path to the local file to upload. Tilde (~) is expanded."),
48433
- shareClass: external_exports.enum(["PRIVATE", "SHARED"]).describe("Share class (default PRIVATE)").optional(),
48578
+ path: external_exports.string().describe("Path to the local file to upload, inside the upload directory. A relative path is resolved against that directory; tilde (~) is expanded."),
48579
+ shareClass: (allowShare ? external_exports.enum(["PRIVATE", "SHARED"]) : external_exports.enum(["PRIVATE"])).describe(allowShare ? "Share class (default PRIVATE). SHARED makes the file visible to co-parents immediately." : "Share class \u2014 only PRIVATE in this write mode").optional(),
48434
48580
  label: external_exports.string().describe("Display label for the file in OFW (default: filename)").optional(),
48435
48581
  description: external_exports.string().describe("Description shown in OFW My Files (default: filename)").optional()
48436
48582
  })
@@ -48467,13 +48613,13 @@ ${text}` : text);
48467
48613
  });
48468
48614
  });
48469
48615
  server.registerTool("ofw_download_attachment", {
48470
- description: "Download an OFW message attachment by fileId and return content you can actually read. Inline delivery walks a ladder and returns the first rung that works: (1) host-renderable images (PNG/JPEG/GIF/WEBP) come back as ImageContent; (2) .xlsx/.csv/.tsv, .pdf, .docx, .pptx and text files come back as EXTRACTED CONTENT \u2014 per-sheet CSV, per-page/slide text, document text \u2014 in the response JSON under `extracted`; (3) anything else comes back as an EmbeddedResource blob of the raw bytes. The meta block names the rung as `deliveredVia` and, when it falls through to bytes, lists what was tried in `deliveryAttempts`. Reported mime types are always normalized to a bare media type (no charset/name parameters). In disk mode the bytes are saved to ~/Downloads/ofw-mcp/ and the response carries the absolute path; pass extract:true to ALSO get the extracted content in that response. The default for `inline` can be flipped server-side via the OFW_INLINE_ATTACHMENTS env var. On a hosted deployment with no filesystem, disk mode is unavailable, so inline is forced (forcedInline:true) rather than failing \u2014 a saveTo path never costs you the content. fileId comes from attachments[].fileId on ofw_get_message. Override disk destination with OFW_ATTACHMENTS_DIR or saveTo. Re-downloading to the same path is a no-op (disk mode only).",
48471
- annotations: { readOnlyHint: true },
48616
+ description: "Download an OFW message attachment by fileId and return content you can actually read. Inline delivery walks a ladder and returns the first rung that works: (1) host-renderable images (PNG/JPEG/GIF/WEBP) come back as ImageContent; (2) .xlsx/.csv/.tsv, .pdf, .docx, .pptx and text files come back as EXTRACTED CONTENT \u2014 per-sheet CSV, per-page/slide text, document text \u2014 in the response JSON under `extracted`; (3) anything else comes back as an EmbeddedResource blob of the raw bytes. The meta block names the rung as `deliveredVia` and, when it falls through to bytes, lists what was tried in `deliveryAttempts`. Reported mime types are always normalized to a bare media type (no charset/name parameters). In disk mode the bytes are saved to ~/Downloads/ofw-mcp/ and the response carries the absolute path; pass extract:true to ALSO get the extracted content in that response. The default for `inline` can be flipped server-side via the OFW_INLINE_ATTACHMENTS env var. On a hosted deployment with no filesystem, disk mode is unavailable, so inline is forced (forcedInline:true) rather than failing \u2014 a saveTo path never costs you the content. fileId comes from attachments[].fileId on ofw_get_message. Override disk destination with OFW_ATTACHMENTS_DIR or saveTo; saveTo must stay inside the attachments directory, and an existing file is never overwritten unless force:true. Re-downloading to the same path is a no-op (disk mode only).",
48617
+ annotations: { readOnlyHint: false, destructiveHint: false },
48472
48618
  inputSchema: external_exports.object({
48473
48619
  fileId: external_exports.number().describe("Attachment file id (from ofw_get_message \u2192 attachments[].fileId)"),
48474
48620
  inline: external_exports.boolean().describe("If true, return content inline as MCP content blocks and skip the disk write. If false, write to disk and return the path \u2014 except on a hosted deployment with no filesystem, where inline is forced (forcedInline:true) so the content is still returned. If omitted, falls back to the OFW_INLINE_ATTACHMENTS env var (default: false = disk).").optional(),
48475
- saveTo: external_exports.string().describe("Absolute path or directory to write to. If a directory, the OFW filename is used. Default: ~/Downloads/ofw-mcp/<fileId>-<filename>. Ignored when inline is in effect.").optional(),
48476
- force: external_exports.boolean().describe("Re-download even if already on disk. Default false. Ignored when inline:true (inline always fetches fresh bytes, or reuses an on-disk copy if present).").optional(),
48621
+ saveTo: external_exports.string().describe("Path or directory to write to, INSIDE the attachments directory (OFW_ATTACHMENTS_DIR, default ~/Downloads/ofw-mcp); a relative path is resolved against it and anything outside it is refused. If a directory (trailing /), the OFW filename is used. Default: <attachments dir>/<fileId>-<filename>. An existing file is not overwritten unless force:true. Ignored when inline is in effect.").optional(),
48622
+ force: external_exports.boolean().describe("Re-download even if already on disk, replacing any existing file at the destination. Default false. Ignored when inline:true (inline always fetches fresh bytes, or reuses an on-disk copy if present).").optional(),
48477
48623
  extract: external_exports.boolean().describe("Whether to extract readable content from the file. Default: on for inline delivery of any non-image type, off in disk mode. Set false to get the raw bytes inline instead of extracted text (e.g. to hash or re-upload the file); set true in disk mode to get both the saved path and the extracted content.").optional(),
48478
48624
  maxChars: external_exports.number().int().min(500).max(5e5).describe("Ceiling on extracted characters (default 50000). Over it, content is clipped on a row/line boundary, `truncated` is set, and anything dropped whole is listed in `extracted.omitted`.").optional(),
48479
48625
  parts: external_exports.string().describe('Which sheets / slides / pages to extract, e.g. "1-3,5" (1-based positions) or a sheet name like "2026". A bare number matches either a position or a name. Omit for everything. Unselected parts are listed in `extracted.omitted`.').optional()
@@ -48516,12 +48662,16 @@ ${text}` : text);
48516
48662
  }
48517
48663
  let dest;
48518
48664
  const safeName = basename2(cached2.fileName);
48665
+ const root = resolve5(getAttachmentsDir());
48519
48666
  if (args.saveTo) {
48520
48667
  const isDirArg = args.saveTo.endsWith("/") || args.saveTo.endsWith("\\");
48521
- const abs = expandPath2(args.saveTo);
48668
+ const abs = resolve5(root, args.saveTo.startsWith("~") ? expandPath2(args.saveTo) : args.saveTo);
48522
48669
  dest = isDirArg ? join7(abs, `${fileId}-${safeName}`) : abs;
48670
+ if (!isWithin(root, dest)) {
48671
+ throw new Error(`Refusing to save to ${dest}: it is outside the attachments directory (${root}). Downloads can only be written inside it \u2014 pass a path or subdirectory under it, or set OFW_ATTACHMENTS_DIR to move it.`);
48672
+ }
48523
48673
  } else {
48524
- dest = join7(getAttachmentsDir(), `${fileId}-${safeName}`);
48674
+ dest = join7(root, `${fileId}-${safeName}`);
48525
48675
  }
48526
48676
  const extractOnDisk = args.extract === true;
48527
48677
  if (!args.force && cached2.downloadedPath === dest) {
@@ -48540,7 +48690,7 @@ ${text}` : text);
48540
48690
  }
48541
48691
  }
48542
48692
  const response = await client2.requestBinary("GET", `/pub/v1/myfiles/${fileId}/data`);
48543
- attachmentIO.writeDownload(dest, response.body);
48693
+ attachmentIO.writeDownload(dest, response.body, { root, overwrite: args.force === true });
48544
48694
  await cache.markAttachmentDownloaded(fileId, dest);
48545
48695
  const fileName = response.suggestedFileName ?? cached2.fileName;
48546
48696
  const mimeType = resolveDownloadMime(response.body, response.contentType ?? cached2.mimeType, fileName);
@@ -49017,7 +49167,7 @@ function registerJournalTools(server, client2) {
49017
49167
 
49018
49168
  // src/cache/node.ts
49019
49169
  import { DatabaseSync } from "node:sqlite";
49020
- import { mkdirSync as mkdirSync3, chmodSync as chmodSync2, existsSync as existsSync3 } from "node:fs";
49170
+ import { mkdirSync as mkdirSync3, chmodSync as chmodSync2, existsSync as existsSync4 } from "node:fs";
49021
49171
  import { dirname as dirname5 } from "node:path";
49022
49172
 
49023
49173
  // src/cache/store.ts
@@ -49605,7 +49755,7 @@ function enforceCachePermissions(dbPath) {
49605
49755
  chmodSync2(dirname5(dbPath), 448);
49606
49756
  chmodSync2(dbPath, 384);
49607
49757
  for (const sibling of [`${dbPath}-wal`, `${dbPath}-shm`]) {
49608
- if (existsSync3(sibling)) chmodSync2(sibling, 384);
49758
+ if (existsSync4(sibling)) chmodSync2(sibling, 384);
49609
49759
  }
49610
49760
  }
49611
49761
  var OFWCache = class _OFWCache extends LocalCacheStore {
@@ -49646,7 +49796,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
49646
49796
  var nodeAttachmentIO = new NodeAttachmentIO();
49647
49797
  await runMcp({
49648
49798
  name: "ofw",
49649
- version: "2.19.2",
49799
+ version: "2.19.3",
49650
49800
  // x-release-please-version
49651
49801
  deps: client,
49652
49802
  tools: [