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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/dist/bundle.js +285 -135
- package/dist/config.js +12 -0
- package/dist/extract/spreadsheet.js +33 -10
- package/dist/index.js +1 -1
- package/dist/sync.js +3 -2
- package/dist/timestamps.js +42 -0
- package/dist/tools/_shared.js +49 -2
- package/dist/tools/attachments.js +74 -8
- package/dist/tools/messages.js +93 -30
- package/mint.yaml +7 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/ofw/SKILL.md +1 -1
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,
|
|
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,
|
|
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,
|
|
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 ?? (
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
|
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
|
|
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)
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
32085
|
+
function resolveComponent(base, relative3, options, skipNormalization) {
|
|
32086
32086
|
const target = {};
|
|
32087
32087
|
if (!skipNormalization) {
|
|
32088
32088
|
base = parse3(serialize(base, options), options);
|
|
32089
|
-
|
|
32089
|
+
relative3 = parse3(serialize(relative3, options), options);
|
|
32090
32090
|
}
|
|
32091
32091
|
options = options || {};
|
|
32092
|
-
if (!options.tolerant &&
|
|
32093
|
-
target.scheme =
|
|
32094
|
-
target.userinfo =
|
|
32095
|
-
target.host =
|
|
32096
|
-
target.port =
|
|
32097
|
-
target.path = removeDotSegments(
|
|
32098
|
-
target.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 (
|
|
32101
|
-
target.userinfo =
|
|
32102
|
-
target.host =
|
|
32103
|
-
target.port =
|
|
32104
|
-
target.path = removeDotSegments(
|
|
32105
|
-
target.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 (!
|
|
32107
|
+
if (!relative3.path) {
|
|
32108
32108
|
target.path = base.path;
|
|
32109
|
-
if (
|
|
32109
|
+
if (relative3.query !== void 0) target.query = relative3.query;
|
|
32110
32110
|
else target.query = base.query;
|
|
32111
32111
|
} else {
|
|
32112
|
-
if (
|
|
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 = "/" +
|
|
32115
|
-
else if (!base.path) target.path =
|
|
32116
|
-
else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
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 =
|
|
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 =
|
|
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:
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
37702
|
+
return await new Promise((resolve6) => {
|
|
37703
37703
|
const waiter = () => {
|
|
37704
37704
|
clearTimeout(timer);
|
|
37705
|
-
|
|
37705
|
+
resolve6(true);
|
|
37706
37706
|
};
|
|
37707
37707
|
const timer = setTimeout(() => {
|
|
37708
37708
|
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
37709
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
41157
|
-
resolveOwnSession =
|
|
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((
|
|
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(() =>
|
|
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((
|
|
41607
|
+
await new Promise((resolve6, reject) => {
|
|
41608
41608
|
let timer;
|
|
41609
41609
|
const onOpen = () => {
|
|
41610
41610
|
cleanup();
|
|
41611
|
-
|
|
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((
|
|
41699
|
+
sessionPromise = new Promise((resolve6, reject) => {
|
|
41700
41700
|
resolveFirstReady = (st) => {
|
|
41701
41701
|
sessionPromiseSettled = true;
|
|
41702
|
-
|
|
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((
|
|
42853
|
-
this.pending.set(id,
|
|
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((
|
|
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
|
-
|
|
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
|
|
43025
|
+
const isAbsolute6 = path.startsWith("http://") || path.startsWith("https://");
|
|
43026
43026
|
let host;
|
|
43027
|
-
if (
|
|
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 =
|
|
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((
|
|
43302
|
-
this.pendingReadCookies.set(id,
|
|
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((
|
|
43361
|
-
this.pendingWriteCookies.set(id, { resolve:
|
|
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((
|
|
43432
|
-
this.pendingStorage.set(id, { resolve:
|
|
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((
|
|
43491
|
-
this.pendingCapture.set(id, { resolve:
|
|
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((
|
|
43539
|
-
this.pendingRedirect.set(id, { resolve:
|
|
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((
|
|
43635
|
-
this.pendingDownload.set(id, { resolve:
|
|
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((
|
|
43696
|
-
this.pendingIdb.set(id, { resolve:
|
|
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((
|
|
43736
|
-
this.pendingStorage.set(id, { resolve:
|
|
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((
|
|
43782
|
-
this.pendingDomList.set(id, { resolve:
|
|
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((
|
|
43828
|
-
this.pendingGraphql.set(id, { resolve:
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 ?? (
|
|
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 ?? (
|
|
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]
|
|
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 >
|
|
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
|
-
|
|
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
|
|
47226
|
-
const
|
|
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(
|
|
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(
|
|
47242
|
-
|
|
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:
|
|
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 ?? (
|
|
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
|
-
|
|
47891
|
-
|
|
47892
|
-
|
|
47893
|
-
|
|
47894
|
-
|
|
47895
|
-
|
|
47896
|
-
|
|
47897
|
-
|
|
47898
|
-
|
|
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 ?? (
|
|
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 ?? (
|
|
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("
|
|
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:
|
|
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("
|
|
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(
|
|
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
|
|
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 (
|
|
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.
|
|
49799
|
+
version: "2.19.3",
|
|
49650
49800
|
// x-release-please-version
|
|
49651
49801
|
deps: client,
|
|
49652
49802
|
tools: [
|