jefrichat-mcp 0.49.5 → 0.49.6
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/http.js +82 -2
- package/dist/index.js +82 -2
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -63555,11 +63555,91 @@ ${fp}`);
|
|
|
63555
63555
|
return ok3(`Sent private E2E group file.`);
|
|
63556
63556
|
})
|
|
63557
63557
|
);
|
|
63558
|
+
if (!ctx.local) server2.registerTool(
|
|
63559
|
+
"jefri_upload_link",
|
|
63560
|
+
{
|
|
63561
|
+
title: "Get an upload link (any file up to 100 MB, full quality)",
|
|
63562
|
+
description: "Get a ONE-TAP UPLOAD LINK to send files or images \u2014 up to 5 PER LINK via `fileNames`, up to 100 MB each, FULL quality, no base64, no compression, no path needed. ALWAYS PREFER THIS for images and for files in your sandbox or on the user's device \u2014 the ONE exception: files \u2264256KB (already held OR readable in your sandbox \u2014 PDFs, CSVs, code, small images) go straight through jefri_send_file's dataUrl automatically, no link needed. One cheap call, then hand the user the link \u2014 they open it (phone works), pick the file, and it delivers to the recipient automatically. Give `to` (a username) or `groupId`, plus the `fileName` the delivery should carry. The link is single-use and expires in ~15 minutes; mint a fresh one any time. If the file lives in YOUR sandbox, present it to the user for download first so they can pick it at the link. NEVER stream file bytes through plain output (base64/pixel data) \u2014 that burns the user's tokens. The ONE sanctioned carrier is a \u2264256KB `dataUrl` TOOL ARGUMENT via jefri_send_file (near the cap it may still strain output limits \u2014 then use this link); anything larger only ever travels as this link.",
|
|
63563
|
+
inputSchema: {
|
|
63564
|
+
to: external_exports.string().optional().describe("recipient username (or use groupId)"),
|
|
63565
|
+
groupId: external_exports.string().optional().describe("group id (from jefri_groups) instead of `to`"),
|
|
63566
|
+
fileName: external_exports.string().optional().describe("the name the delivered file should carry, e.g. photo.png (single file)"),
|
|
63567
|
+
fileNames: external_exports.array(external_exports.string()).optional().describe('for a MULTI-file link (2\u20135): the delivery names in order, e.g. ["foto-1.jpg","foto-2.jpg"] \u2014 one link, the user picks them all'),
|
|
63568
|
+
caption: external_exports.string().optional().describe("optional text caption delivered with the file")
|
|
63569
|
+
}
|
|
63570
|
+
},
|
|
63571
|
+
async (args, extra) => withClient(async (c) => {
|
|
63572
|
+
const to = typeof args.to === "string" && args.to.trim() ? args.to.trim() : void 0;
|
|
63573
|
+
const groupId = typeof args.groupId === "string" && args.groupId.trim() ? args.groupId.trim() : void 0;
|
|
63574
|
+
if (!to && !groupId) return fail("give `to` (a username) or `groupId`");
|
|
63575
|
+
if (to && groupId) return fail("give either `to` or `groupId`, not both");
|
|
63576
|
+
const names = Array.isArray(args.fileNames) ? args.fileNames.map((n) => String(n ?? "").trim()).filter(Boolean).slice(0, 5) : [];
|
|
63577
|
+
const fileName = String(args.fileName ?? "").trim();
|
|
63578
|
+
if (!fileName && names.length === 0) return fail("give `fileName` (one file) or `fileNames` (2\u20135 files) \u2014 deliveries carry these names");
|
|
63579
|
+
if (Array.isArray(args.fileNames) && args.fileNames.length > 5)
|
|
63580
|
+
return fail("a link carries at most 5 files \u2014 mint another link for more");
|
|
63581
|
+
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
63582
|
+
const gc = new AbortController();
|
|
63583
|
+
const onHostAbort = () => gc.abort();
|
|
63584
|
+
const gKiller = setTimeout(() => gc.abort(), MINT_LINK_DEADLINE_MS);
|
|
63585
|
+
const signal = extra?.signal;
|
|
63586
|
+
if (signal) {
|
|
63587
|
+
if (signal.aborted) {
|
|
63588
|
+
clearTimeout(gKiller);
|
|
63589
|
+
return fail("cancelled");
|
|
63590
|
+
}
|
|
63591
|
+
signal.addEventListener("abort", onHostAbort, { once: true });
|
|
63592
|
+
}
|
|
63593
|
+
try {
|
|
63594
|
+
const g = await fetch(`${hub}/api/files/grant`, {
|
|
63595
|
+
method: "POST",
|
|
63596
|
+
headers: { authorization: `Bearer ${c.token}`, "content-type": "application/json" },
|
|
63597
|
+
body: JSON.stringify({ ...to ? { to } : { groupId }, ...names.length ? { fileNames: names } : { fileName }, ...args.caption ? { caption: args.caption } : {} }),
|
|
63598
|
+
signal: gc.signal
|
|
63599
|
+
});
|
|
63600
|
+
if (!g.ok) {
|
|
63601
|
+
try {
|
|
63602
|
+
await g.body?.cancel();
|
|
63603
|
+
} catch {
|
|
63604
|
+
}
|
|
63605
|
+
return fail(`the upload-link service answered ${g.status} \u2014 try again shortly. Real fallbacks while it's down: a public URL \u2192 jefri_send_file with fileUrl; otherwise present/download the file and DRAG it into the Jefri Chat web app.`);
|
|
63606
|
+
}
|
|
63607
|
+
const body = await g.json().catch(() => ({}));
|
|
63608
|
+
const parsedUrl = (() => {
|
|
63609
|
+
const raw = names.length ? body.batchUrl : body.uploadUrl;
|
|
63610
|
+
if (typeof raw !== "string") return null;
|
|
63611
|
+
try {
|
|
63612
|
+
const u = new URL(raw);
|
|
63613
|
+
return (u.protocol === "http:" || u.protocol === "https:") && u.host ? raw : null;
|
|
63614
|
+
} catch {
|
|
63615
|
+
return null;
|
|
63616
|
+
}
|
|
63617
|
+
})();
|
|
63618
|
+
if (!parsedUrl || typeof body.expiresInMs !== "number" || !Number.isFinite(body.expiresInMs) || body.expiresInMs <= 0)
|
|
63619
|
+
return fail("the upload-link service answered malformed data \u2014 try again shortly. Real fallbacks: a public URL \u2192 jefri_send_file with fileUrl; otherwise present/download the file and DRAG it into the Jefri Chat web app.");
|
|
63620
|
+
const mins = Math.round(body.expiresInMs / 6e4);
|
|
63621
|
+
const where = to ? `@${safeHandle(to)}` : "the group";
|
|
63622
|
+
const delivers = names.length ? `${names.length} files: ${names.map((n) => `\u{1F4CE} ${n}`).join(", ")}` : `\u{1F4CE} ${fileName}`;
|
|
63623
|
+
return ok3(
|
|
63624
|
+
`\u{1F517} One-tap upload link for ${where} (delivers ${delivers}; valid ~${mins} min, single-use):
|
|
63625
|
+
${parsedUrl}
|
|
63626
|
+
|
|
63627
|
+
SAY THIS to the user (adapt their language): "Here's your upload link \u2014 open it and pick the ${names.length ? "files" : "file"}; ${names.length ? "they deliver" : "it delivers"} to ${where} automatically at full quality: ${parsedUrl}" If the file is in YOUR sandbox, present it for download first so they can pick the downloaded copy. Do NOT open the link yourself and do NOT prepare or stream any bytes \u2014 your part is done.`
|
|
63628
|
+
);
|
|
63629
|
+
} catch (e) {
|
|
63630
|
+
return fail(`could not mint the upload link: ${e?.message ?? e} \u2014 try again shortly. Real fallbacks: a public URL \u2192 jefri_send_file with fileUrl; otherwise present/download the file and DRAG it into the Jefri Chat web app.`);
|
|
63631
|
+
} finally {
|
|
63632
|
+
gc.abort();
|
|
63633
|
+
clearTimeout(gKiller);
|
|
63634
|
+
if (signal) signal.removeEventListener("abort", onHostAbort);
|
|
63635
|
+
}
|
|
63636
|
+
})
|
|
63637
|
+
);
|
|
63558
63638
|
server2.registerTool(
|
|
63559
63639
|
"jefri_send_file",
|
|
63560
63640
|
{
|
|
63561
63641
|
title: "Send a file, PDF, or image",
|
|
63562
|
-
description: "Send a file to
|
|
63642
|
+
description: ctx.local ? "Send a file to a Jefri Chat user/agent \u2014 PDF, image, video, any document. On this LOCAL connector, `path` reads the file off disk and sends it DIRECTLY (up to 100 MB) \u2014 always prefer `path`; never read a file into base64 yourself. `dataUrl` + `fileName` is only for small (\u2264256KB) content you generated in-conversation, and `fileUrl` fetches a public URL. Optional `caption` always." : "Send a file to a Jefri Chat user/agent. THE RULE: a file \u2264256KB (check its size first \u2014 PDFs, CSVs, code, small images alike), whether already held as base64 or sitting in your sandbox, \u2192 read it and send NOW via `dataUrl` + `fileName`, automatically, no user step. ANYTHING over 256KB, or that would need TRANSFORMING (compressing, converting, resizing) \u2192 STOP and call jefri_upload_link instead \u2014 never transform. For a file on the USER'S DEVICE, CALL THIS TOOL FIRST with the file's `path` \u2014 even a path this connector cannot read: it answers a one-tap upload LINK the user opens to deliver the file at full quality. NEVER pre-process first: no compressing, no splitting base64 into chunks, no reading/retyping base64 through a viewer \u2014 that wastes many minutes and usually fails; the LINK route needs none of it. Inputs by where the file lives: (1) user's machine \u2192 `path` (also yields a runnable upload command). (2) public URL \u2192 `fileUrl` (connector fetches, \u226425MB). (3) YOUR OWN sandbox (cloud-sandboxed assistant, no egress): up to 100 MB \u2192 `path` for the LINK (present the file for the user to download and pick at the link); \u2264256KB (held OR read from your sandbox) \u2192 `dataUrl` + `fileName`, automatically. Large images: the LINK, full quality \u2014 do not offer to compress. Optional `caption` always.",
|
|
63563
63643
|
inputSchema: {
|
|
63564
63644
|
to: external_exports.string().describe("recipient username"),
|
|
63565
63645
|
path: external_exports.string().optional().describe("local file path, e.g. ~/Downloads/report.pdf"),
|
|
@@ -64340,7 +64420,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
64340
64420
|
"jefri_send_group_file",
|
|
64341
64421
|
{
|
|
64342
64422
|
title: "Send a file to a group",
|
|
64343
|
-
description: "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox).
|
|
64423
|
+
description: ctx.local ? "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). On this LOCAL connector, `path` sends the file DIRECTLY off disk (up to 100 MB) \u2014 always prefer `path`; `dataUrl` only for small generated content; `fileUrl` for a public URL." : "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). For a file on the USER'S DEVICE, CALL FIRST with its `path` \u2014 even one this connector cannot read: you get a one-tap upload LINK for the user. NEVER TRANSFORM (no compressing/splitting/retyping base64 through a viewer). user's device \u2192 `path`; public URL \u2192 `fileUrl`; your OWN sandbox: \u2264256KB (any type) \u2192 read it, send via `dataUrl` + `fileName` automatically; larger \u2192 `path` for the LINK (up to 100 MB).",
|
|
64344
64424
|
inputSchema: {
|
|
64345
64425
|
groupId: external_exports.string().describe("the group's id (from jefri_groups or jefri_inbox)"),
|
|
64346
64426
|
path: external_exports.string().optional().describe("local file path, e.g. ~/Downloads/report.pdf"),
|
package/dist/index.js
CHANGED
|
@@ -43278,11 +43278,91 @@ ${fp}`);
|
|
|
43278
43278
|
return ok3(`Sent private E2E group file.`);
|
|
43279
43279
|
})
|
|
43280
43280
|
);
|
|
43281
|
+
if (!ctx.local) server2.registerTool(
|
|
43282
|
+
"jefri_upload_link",
|
|
43283
|
+
{
|
|
43284
|
+
title: "Get an upload link (any file up to 100 MB, full quality)",
|
|
43285
|
+
description: "Get a ONE-TAP UPLOAD LINK to send files or images \u2014 up to 5 PER LINK via `fileNames`, up to 100 MB each, FULL quality, no base64, no compression, no path needed. ALWAYS PREFER THIS for images and for files in your sandbox or on the user's device \u2014 the ONE exception: files \u2264256KB (already held OR readable in your sandbox \u2014 PDFs, CSVs, code, small images) go straight through jefri_send_file's dataUrl automatically, no link needed. One cheap call, then hand the user the link \u2014 they open it (phone works), pick the file, and it delivers to the recipient automatically. Give `to` (a username) or `groupId`, plus the `fileName` the delivery should carry. The link is single-use and expires in ~15 minutes; mint a fresh one any time. If the file lives in YOUR sandbox, present it to the user for download first so they can pick it at the link. NEVER stream file bytes through plain output (base64/pixel data) \u2014 that burns the user's tokens. The ONE sanctioned carrier is a \u2264256KB `dataUrl` TOOL ARGUMENT via jefri_send_file (near the cap it may still strain output limits \u2014 then use this link); anything larger only ever travels as this link.",
|
|
43286
|
+
inputSchema: {
|
|
43287
|
+
to: external_exports.string().optional().describe("recipient username (or use groupId)"),
|
|
43288
|
+
groupId: external_exports.string().optional().describe("group id (from jefri_groups) instead of `to`"),
|
|
43289
|
+
fileName: external_exports.string().optional().describe("the name the delivered file should carry, e.g. photo.png (single file)"),
|
|
43290
|
+
fileNames: external_exports.array(external_exports.string()).optional().describe('for a MULTI-file link (2\u20135): the delivery names in order, e.g. ["foto-1.jpg","foto-2.jpg"] \u2014 one link, the user picks them all'),
|
|
43291
|
+
caption: external_exports.string().optional().describe("optional text caption delivered with the file")
|
|
43292
|
+
}
|
|
43293
|
+
},
|
|
43294
|
+
async (args, extra) => withClient(async (c2) => {
|
|
43295
|
+
const to = typeof args.to === "string" && args.to.trim() ? args.to.trim() : void 0;
|
|
43296
|
+
const groupId = typeof args.groupId === "string" && args.groupId.trim() ? args.groupId.trim() : void 0;
|
|
43297
|
+
if (!to && !groupId) return fail("give `to` (a username) or `groupId`");
|
|
43298
|
+
if (to && groupId) return fail("give either `to` or `groupId`, not both");
|
|
43299
|
+
const names = Array.isArray(args.fileNames) ? args.fileNames.map((n) => String(n ?? "").trim()).filter(Boolean).slice(0, 5) : [];
|
|
43300
|
+
const fileName = String(args.fileName ?? "").trim();
|
|
43301
|
+
if (!fileName && names.length === 0) return fail("give `fileName` (one file) or `fileNames` (2\u20135 files) \u2014 deliveries carry these names");
|
|
43302
|
+
if (Array.isArray(args.fileNames) && args.fileNames.length > 5)
|
|
43303
|
+
return fail("a link carries at most 5 files \u2014 mint another link for more");
|
|
43304
|
+
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
43305
|
+
const gc = new AbortController();
|
|
43306
|
+
const onHostAbort = () => gc.abort();
|
|
43307
|
+
const gKiller = setTimeout(() => gc.abort(), MINT_LINK_DEADLINE_MS);
|
|
43308
|
+
const signal = extra?.signal;
|
|
43309
|
+
if (signal) {
|
|
43310
|
+
if (signal.aborted) {
|
|
43311
|
+
clearTimeout(gKiller);
|
|
43312
|
+
return fail("cancelled");
|
|
43313
|
+
}
|
|
43314
|
+
signal.addEventListener("abort", onHostAbort, { once: true });
|
|
43315
|
+
}
|
|
43316
|
+
try {
|
|
43317
|
+
const g = await fetch(`${hub}/api/files/grant`, {
|
|
43318
|
+
method: "POST",
|
|
43319
|
+
headers: { authorization: `Bearer ${c2.token}`, "content-type": "application/json" },
|
|
43320
|
+
body: JSON.stringify({ ...to ? { to } : { groupId }, ...names.length ? { fileNames: names } : { fileName }, ...args.caption ? { caption: args.caption } : {} }),
|
|
43321
|
+
signal: gc.signal
|
|
43322
|
+
});
|
|
43323
|
+
if (!g.ok) {
|
|
43324
|
+
try {
|
|
43325
|
+
await g.body?.cancel();
|
|
43326
|
+
} catch {
|
|
43327
|
+
}
|
|
43328
|
+
return fail(`the upload-link service answered ${g.status} \u2014 try again shortly. Real fallbacks while it's down: a public URL \u2192 jefri_send_file with fileUrl; otherwise present/download the file and DRAG it into the Jefri Chat web app.`);
|
|
43329
|
+
}
|
|
43330
|
+
const body = await g.json().catch(() => ({}));
|
|
43331
|
+
const parsedUrl = (() => {
|
|
43332
|
+
const raw = names.length ? body.batchUrl : body.uploadUrl;
|
|
43333
|
+
if (typeof raw !== "string") return null;
|
|
43334
|
+
try {
|
|
43335
|
+
const u = new URL(raw);
|
|
43336
|
+
return (u.protocol === "http:" || u.protocol === "https:") && u.host ? raw : null;
|
|
43337
|
+
} catch {
|
|
43338
|
+
return null;
|
|
43339
|
+
}
|
|
43340
|
+
})();
|
|
43341
|
+
if (!parsedUrl || typeof body.expiresInMs !== "number" || !Number.isFinite(body.expiresInMs) || body.expiresInMs <= 0)
|
|
43342
|
+
return fail("the upload-link service answered malformed data \u2014 try again shortly. Real fallbacks: a public URL \u2192 jefri_send_file with fileUrl; otherwise present/download the file and DRAG it into the Jefri Chat web app.");
|
|
43343
|
+
const mins = Math.round(body.expiresInMs / 6e4);
|
|
43344
|
+
const where = to ? `@${safeHandle(to)}` : "the group";
|
|
43345
|
+
const delivers = names.length ? `${names.length} files: ${names.map((n) => `\u{1F4CE} ${n}`).join(", ")}` : `\u{1F4CE} ${fileName}`;
|
|
43346
|
+
return ok3(
|
|
43347
|
+
`\u{1F517} One-tap upload link for ${where} (delivers ${delivers}; valid ~${mins} min, single-use):
|
|
43348
|
+
${parsedUrl}
|
|
43349
|
+
|
|
43350
|
+
SAY THIS to the user (adapt their language): "Here's your upload link \u2014 open it and pick the ${names.length ? "files" : "file"}; ${names.length ? "they deliver" : "it delivers"} to ${where} automatically at full quality: ${parsedUrl}" If the file is in YOUR sandbox, present it for download first so they can pick the downloaded copy. Do NOT open the link yourself and do NOT prepare or stream any bytes \u2014 your part is done.`
|
|
43351
|
+
);
|
|
43352
|
+
} catch (e) {
|
|
43353
|
+
return fail(`could not mint the upload link: ${e?.message ?? e} \u2014 try again shortly. Real fallbacks: a public URL \u2192 jefri_send_file with fileUrl; otherwise present/download the file and DRAG it into the Jefri Chat web app.`);
|
|
43354
|
+
} finally {
|
|
43355
|
+
gc.abort();
|
|
43356
|
+
clearTimeout(gKiller);
|
|
43357
|
+
if (signal) signal.removeEventListener("abort", onHostAbort);
|
|
43358
|
+
}
|
|
43359
|
+
})
|
|
43360
|
+
);
|
|
43281
43361
|
server2.registerTool(
|
|
43282
43362
|
"jefri_send_file",
|
|
43283
43363
|
{
|
|
43284
43364
|
title: "Send a file, PDF, or image",
|
|
43285
|
-
description: "Send a file to
|
|
43365
|
+
description: ctx.local ? "Send a file to a Jefri Chat user/agent \u2014 PDF, image, video, any document. On this LOCAL connector, `path` reads the file off disk and sends it DIRECTLY (up to 100 MB) \u2014 always prefer `path`; never read a file into base64 yourself. `dataUrl` + `fileName` is only for small (\u2264256KB) content you generated in-conversation, and `fileUrl` fetches a public URL. Optional `caption` always." : "Send a file to a Jefri Chat user/agent. THE RULE: a file \u2264256KB (check its size first \u2014 PDFs, CSVs, code, small images alike), whether already held as base64 or sitting in your sandbox, \u2192 read it and send NOW via `dataUrl` + `fileName`, automatically, no user step. ANYTHING over 256KB, or that would need TRANSFORMING (compressing, converting, resizing) \u2192 STOP and call jefri_upload_link instead \u2014 never transform. For a file on the USER'S DEVICE, CALL THIS TOOL FIRST with the file's `path` \u2014 even a path this connector cannot read: it answers a one-tap upload LINK the user opens to deliver the file at full quality. NEVER pre-process first: no compressing, no splitting base64 into chunks, no reading/retyping base64 through a viewer \u2014 that wastes many minutes and usually fails; the LINK route needs none of it. Inputs by where the file lives: (1) user's machine \u2192 `path` (also yields a runnable upload command). (2) public URL \u2192 `fileUrl` (connector fetches, \u226425MB). (3) YOUR OWN sandbox (cloud-sandboxed assistant, no egress): up to 100 MB \u2192 `path` for the LINK (present the file for the user to download and pick at the link); \u2264256KB (held OR read from your sandbox) \u2192 `dataUrl` + `fileName`, automatically. Large images: the LINK, full quality \u2014 do not offer to compress. Optional `caption` always.",
|
|
43286
43366
|
inputSchema: {
|
|
43287
43367
|
to: external_exports.string().describe("recipient username"),
|
|
43288
43368
|
path: external_exports.string().optional().describe("local file path, e.g. ~/Downloads/report.pdf"),
|
|
@@ -44063,7 +44143,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
44063
44143
|
"jefri_send_group_file",
|
|
44064
44144
|
{
|
|
44065
44145
|
title: "Send a file to a group",
|
|
44066
|
-
description: "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox).
|
|
44146
|
+
description: ctx.local ? "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). On this LOCAL connector, `path` sends the file DIRECTLY off disk (up to 100 MB) \u2014 always prefer `path`; `dataUrl` only for small generated content; `fileUrl` for a public URL." : "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). For a file on the USER'S DEVICE, CALL FIRST with its `path` \u2014 even one this connector cannot read: you get a one-tap upload LINK for the user. NEVER TRANSFORM (no compressing/splitting/retyping base64 through a viewer). user's device \u2192 `path`; public URL \u2192 `fileUrl`; your OWN sandbox: \u2264256KB (any type) \u2192 read it, send via `dataUrl` + `fileName` automatically; larger \u2192 `path` for the LINK (up to 100 MB).",
|
|
44067
44147
|
inputSchema: {
|
|
44068
44148
|
groupId: external_exports.string().describe("the group's id (from jefri_groups or jefri_inbox)"),
|
|
44069
44149
|
path: external_exports.string().optional().describe("local file path, e.g. ~/Downloads/report.pdf"),
|
package/package.json
CHANGED