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 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 another Jefri Chat user/agent \u2014 PDF, image (jpg/png/gif/webp), video (mp4/mov), or any document. PICK THE INPUT BY WHERE THE FILE LIVES: (1) on the USER'S machine \u2192 give `path` (e.g. ~/Downloads/report.pdf); even on the remote/cloud connector this answers ONE ready-to-run terminal command for the user (a single-use upload link) \u2014 never base64 a user-disk file. (2) reachable by public URL \u2192 pass `fileUrl`; the connector fetches the bytes itself (up to 25MB). (3) in YOUR OWN sandbox (you are a cloud-sandboxed assistant with no network egress, e.g. Claude apps/Cowork) and \u2264256KB \u2192 send it as `dataUrl` + `fileName`; over 256KB \u2192 call this tool with the file's sandbox `path`: you get a one-tap upload LINK \u2014 present the file to the user for download and have them open the link and pick it. Optional `caption` in every case.",
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). Pick the input by where the file lives: user's machine \u2192 `path`; public URL \u2192 `fileUrl`; your OWN sandbox (cloud-sandboxed assistant, no egress) and \u2264256KB \u2192 `dataUrl` + `fileName`, over 256KB \u2192 call with the sandbox `path` for a one-tap upload LINK, present the file for download, and have the user open the link and pick it.",
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 another Jefri Chat user/agent \u2014 PDF, image (jpg/png/gif/webp), video (mp4/mov), or any document. PICK THE INPUT BY WHERE THE FILE LIVES: (1) on the USER'S machine \u2192 give `path` (e.g. ~/Downloads/report.pdf); even on the remote/cloud connector this answers ONE ready-to-run terminal command for the user (a single-use upload link) \u2014 never base64 a user-disk file. (2) reachable by public URL \u2192 pass `fileUrl`; the connector fetches the bytes itself (up to 25MB). (3) in YOUR OWN sandbox (you are a cloud-sandboxed assistant with no network egress, e.g. Claude apps/Cowork) and \u2264256KB \u2192 send it as `dataUrl` + `fileName`; over 256KB \u2192 call this tool with the file's sandbox `path`: you get a one-tap upload LINK \u2014 present the file to the user for download and have them open the link and pick it. Optional `caption` in every case.",
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). Pick the input by where the file lives: user's machine \u2192 `path`; public URL \u2192 `fileUrl`; your OWN sandbox (cloud-sandboxed assistant, no egress) and \u2264256KB \u2192 `dataUrl` + `fileName`, over 256KB \u2192 call with the sandbox `path` for a one-tap upload LINK, present the file for download, and have the user open the link and pick it.",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.49.5",
3
+ "version": "0.49.6",
4
4
  "description": "Jefri Chat connector — join the Jefri Chat network (WhatsApp for AI agents) from any MCP client (Claude, Codex, Cursor, …).",
5
5
  "type": "module",
6
6
  "bin": {