jefrichat-mcp 0.12.0 → 0.15.0

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.
Files changed (3) hide show
  1. package/dist/http.js +37 -13
  2. package/dist/index.js +41 -14
  3. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -49526,6 +49526,18 @@ var MIME = {
49526
49526
  ".zip": "application/zip"
49527
49527
  };
49528
49528
  var mimeOf = (name) => MIME[np3.extname(name).toLowerCase()] ?? "application/octet-stream";
49529
+ function fmtTime(iso) {
49530
+ if (!iso) return "";
49531
+ const d = new Date(iso);
49532
+ if (isNaN(d.getTime())) return "";
49533
+ return d.toLocaleString(void 0, {
49534
+ month: "short",
49535
+ day: "numeric",
49536
+ hour: "2-digit",
49537
+ minute: "2-digit",
49538
+ hour12: false
49539
+ });
49540
+ }
49529
49541
  function screenshotDir() {
49530
49542
  try {
49531
49543
  const out = cp2.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
@@ -49644,22 +49656,30 @@ ${e?.message ?? e}`);
49644
49656
  "jefri_agents",
49645
49657
  {
49646
49658
  title: "List your connections",
49647
- description: "List the people and agents you're connected to (plus your own agents) \u2014 these are who you can message. To reach someone new, use jefri_search to find them, then jefri_connect to request a connection (their owner approves).",
49659
+ description: "List the people and agents you're connected to (plus your own agents) \u2014 these are who you can message, with a live \u{1F7E2} online / \u26AA offline indicator for each. NOTE: you can message someone even when they're \u26AA offline \u2014 the message is stored and delivered the moment they reconnect, so never refuse to send just because a recipient is offline. To reach someone new, use jefri_search to find them, then jefri_connect.",
49648
49660
  inputSchema: {}
49649
49661
  },
49650
49662
  async () => withClient(async (c) => {
49651
49663
  const all = await c.identities();
49652
- const lines = all.filter((i) => i.username !== c.identity?.username).map(
49653
- (i) => `\u2022 @${i.username} \u2014 ${i.displayName} [${i.type}] (${i.status})${i.owner ? ` \xB7 by @${i.owner}` : ""}${i.tags.length ? " \xB7 " + i.tags.join(", ") : ""}`
49654
- );
49655
- return ok(lines.length ? lines.join("\n") : "No other identities on the network yet.");
49664
+ const others = all.filter((i) => i.username !== c.identity?.username);
49665
+ const isOn = (i) => i.status !== "offline" && !!i.status;
49666
+ others.sort((a, b) => isOn(a) === isOn(b) ? a.username.localeCompare(b.username) : isOn(a) ? -1 : 1);
49667
+ const lines = others.map((i) => {
49668
+ const dot = isOn(i) ? "\u{1F7E2}" : "\u26AA";
49669
+ const live = isOn(i) ? i.status : "offline";
49670
+ return `${dot} @${i.username} \u2014 ${i.displayName} [${i.type}] (${live})${i.owner ? ` \xB7 by @${i.owner}` : ""}${i.tags.length ? " \xB7 " + i.tags.join(", ") : ""}`;
49671
+ });
49672
+ const onCount = others.filter(isOn).length;
49673
+ const header = others.length ? `${onCount} online / ${others.length} total:
49674
+ ` : "";
49675
+ return ok(lines.length ? header + lines.join("\n") : "No other identities on the network yet.");
49656
49676
  })
49657
49677
  );
49658
49678
  server.registerTool(
49659
49679
  "jefri_send",
49660
49680
  {
49661
49681
  title: "Send a message",
49662
- description: "Send a direct message to another Jefri Chat user or agent by username. Use jefri_agents first if you don't know the username.",
49682
+ description: "Send a direct message to another Jefri Chat user or agent by username. Works whether or not they're online \u2014 an offline recipient gets the message the moment they reconnect, so send it regardless of their status. Use jefri_agents first if you don't know the username.",
49663
49683
  inputSchema: {
49664
49684
  to: external_exports.string().describe("recipient username, e.g. claude_backend"),
49665
49685
  text: external_exports.string().describe("the message body")
@@ -50313,17 +50333,19 @@ ${cmd}`);
50313
50333
  return name ? ` (in group "${name}" \xB7 reply with jefri_send_group groupId="${m.groupId}")` : ` (in group id ${m.groupId})`;
50314
50334
  };
50315
50335
  const lines = await Promise.all(msgs.map(async (m) => {
50336
+ const ts = fmtTime(m.createdAt);
50337
+ const at = ts ? `[${ts}] ` : "";
50316
50338
  if (m.encryptionMode === "private_e2e" && m.encryptedPayload) {
50317
50339
  try {
50318
50340
  const plain = await c.decryptPrivatePayload(m.encryptedPayload);
50319
50341
  const body2 = plain.kind === "file" ? `\u{1F512}\u{1F4CE} private file: ${plain.fileName ?? m.fileName}` : `\u{1F512} ${plain.content ?? ""}`;
50320
- return `@${m.senderUsername}${where(m)}: ${body2}`;
50342
+ return `${at}@${m.senderUsername}${where(m)}: ${body2}`;
50321
50343
  } catch {
50322
- return `@${m.senderUsername}${where(m)}: \u{1F512} Private message unavailable on this device`;
50344
+ return `${at}@${m.senderUsername}${where(m)}: \u{1F512} Private message unavailable on this device`;
50323
50345
  }
50324
50346
  }
50325
50347
  const body = m.kind === "file" ? `\u{1F4CE} sent a file: ${m.fileName} \u2014 to save it call jefri_download_file(from: "${m.senderUsername}"${m.fileName ? `, fileName: "${m.fileName}"` : ""})` : m.content;
50326
- return `@${m.senderUsername}${where(m)}: ${body}`;
50348
+ return `${at}@${m.senderUsername}${where(m)}: ${body}`;
50327
50349
  }));
50328
50350
  return ok(`\u{1F4E8} ${lines.length} message(s):
50329
50351
  ` + lines.join("\n"));
@@ -50444,19 +50466,21 @@ ${cmd}`
50444
50466
  const msgs = (res?.messages ?? []).slice(-(limit ?? 20));
50445
50467
  if (!msgs.length) return ok(`No messages yet in ${label}.`);
50446
50468
  const lines = await Promise.all(msgs.map(async (m) => {
50469
+ const ts = fmtTime(m.createdAt);
50470
+ const at = ts ? `[${ts}] ` : "";
50447
50471
  if (m.encryptionMode === "private_e2e" && m.encryptedPayload) {
50448
50472
  try {
50449
50473
  const plain = await c.decryptPrivatePayload(m.encryptedPayload);
50450
- return `${m.senderUsername}: ${plain.kind === "file" ? `\u{1F512}\u{1F4CE} ${plain.fileName ?? m.fileName}` : `\u{1F512} ${plain.content ?? ""}`}`;
50474
+ return `${at}${m.senderUsername}: ${plain.kind === "file" ? `\u{1F512}\u{1F4CE} ${plain.fileName ?? m.fileName}` : `\u{1F512} ${plain.content ?? ""}`}`;
50451
50475
  } catch {
50452
- return `${m.senderUsername}: \u{1F512} Private message unavailable on this device`;
50476
+ return `${at}${m.senderUsername}: \u{1F512} Private message unavailable on this device`;
50453
50477
  }
50454
50478
  }
50455
50479
  if (m.kind === "file") {
50456
50480
  const dl = groupId ? `jefri_download_file(groupId: "${groupId}", fileName: "${m.fileName}")` : `jefri_download_file(from: "${m.senderUsername}", fileName: "${m.fileName}")`;
50457
- return `${m.senderUsername}: \u{1F4CE} ${m.fileName} \u2014 to save it call ${dl}`;
50481
+ return `${at}${m.senderUsername}: \u{1F4CE} ${m.fileName} \u2014 to save it call ${dl}`;
50458
50482
  }
50459
- return `${m.senderUsername}: ${m.content}`;
50483
+ return `${at}${m.senderUsername}: ${m.content}`;
50460
50484
  }));
50461
50485
  return ok(lines.join("\n"));
50462
50486
  })
package/dist/index.js CHANGED
@@ -25417,7 +25417,10 @@ function notifyIncoming(m) {
25417
25417
  }
25418
25418
  } else if (process.platform === "win32") {
25419
25419
  const esc2 = (x) => x.replace(/'/g, "''");
25420
- const ps = `Add-Type -AssemblyName System.Windows.Forms;$n=New-Object System.Windows.Forms.NotifyIcon;$n.Icon=[System.Drawing.SystemIcons]::Information;$n.BalloonTipTitle='${esc2(title)}';$n.BalloonTipText='${esc2(body)}';$n.Visible=$true;$n.ShowBalloonTip(6000);Start-Sleep -Milliseconds 6500;$n.Dispose()`;
25420
+ const xesc = (x) => x.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
25421
+ const audio = sound ? `<audio src="ms-winsoundevent:Notification.Default"/>` : `<audio silent="true"/>`;
25422
+ const xml = `<toast><visual><binding template="ToastGeneric"><text>${xesc(title)}</text><text>${xesc(body)}</text></binding></visual>${audio}</toast>`;
25423
+ const ps = `$ErrorActionPreference='Stop';$t='${esc2(title)}';$b='${esc2(body)}';try{[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null;[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] | Out-Null;$d=New-Object Windows.Data.Xml.Dom.XmlDocument;$d.LoadXml('${xml}');$toast=New-Object Windows.UI.Notifications.ToastNotification -ArgumentList $d;[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe').Show($toast);Start-Sleep -Milliseconds 400;}catch{Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$n=New-Object System.Windows.Forms.NotifyIcon;$n.Icon=[System.Drawing.SystemIcons]::Information;$n.BalloonTipTitle=$t;$n.BalloonTipText=$b;$n.Visible=$true;$n.ShowBalloonTip(6000);Start-Sleep -Milliseconds 6500;$n.Dispose();}`;
25421
25424
  cp.spawn("powershell", ["-NoProfile", "-WindowStyle", "Hidden", "-Command", ps], { stdio: "ignore", detached: true }).unref();
25422
25425
  } else {
25423
25426
  cp.spawn("notify-send", ["-a", "Jefri Chat", title, body], { stdio: "ignore", detached: true }).unref();
@@ -25613,6 +25616,18 @@ var MIME = {
25613
25616
  ".zip": "application/zip"
25614
25617
  };
25615
25618
  var mimeOf = (name) => MIME[np3.extname(name).toLowerCase()] ?? "application/octet-stream";
25619
+ function fmtTime(iso) {
25620
+ if (!iso) return "";
25621
+ const d = new Date(iso);
25622
+ if (isNaN(d.getTime())) return "";
25623
+ return d.toLocaleString(void 0, {
25624
+ month: "short",
25625
+ day: "numeric",
25626
+ hour: "2-digit",
25627
+ minute: "2-digit",
25628
+ hour12: false
25629
+ });
25630
+ }
25616
25631
  function screenshotDir() {
25617
25632
  try {
25618
25633
  const out = cp3.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
@@ -25731,22 +25746,30 @@ ${e?.message ?? e}`);
25731
25746
  "jefri_agents",
25732
25747
  {
25733
25748
  title: "List your connections",
25734
- description: "List the people and agents you're connected to (plus your own agents) \u2014 these are who you can message. To reach someone new, use jefri_search to find them, then jefri_connect to request a connection (their owner approves).",
25749
+ description: "List the people and agents you're connected to (plus your own agents) \u2014 these are who you can message, with a live \u{1F7E2} online / \u26AA offline indicator for each. NOTE: you can message someone even when they're \u26AA offline \u2014 the message is stored and delivered the moment they reconnect, so never refuse to send just because a recipient is offline. To reach someone new, use jefri_search to find them, then jefri_connect.",
25735
25750
  inputSchema: {}
25736
25751
  },
25737
25752
  async () => withClient(async (c) => {
25738
25753
  const all = await c.identities();
25739
- const lines = all.filter((i) => i.username !== c.identity?.username).map(
25740
- (i) => `\u2022 @${i.username} \u2014 ${i.displayName} [${i.type}] (${i.status})${i.owner ? ` \xB7 by @${i.owner}` : ""}${i.tags.length ? " \xB7 " + i.tags.join(", ") : ""}`
25741
- );
25742
- return ok(lines.length ? lines.join("\n") : "No other identities on the network yet.");
25754
+ const others = all.filter((i) => i.username !== c.identity?.username);
25755
+ const isOn = (i) => i.status !== "offline" && !!i.status;
25756
+ others.sort((a, b) => isOn(a) === isOn(b) ? a.username.localeCompare(b.username) : isOn(a) ? -1 : 1);
25757
+ const lines = others.map((i) => {
25758
+ const dot = isOn(i) ? "\u{1F7E2}" : "\u26AA";
25759
+ const live = isOn(i) ? i.status : "offline";
25760
+ return `${dot} @${i.username} \u2014 ${i.displayName} [${i.type}] (${live})${i.owner ? ` \xB7 by @${i.owner}` : ""}${i.tags.length ? " \xB7 " + i.tags.join(", ") : ""}`;
25761
+ });
25762
+ const onCount = others.filter(isOn).length;
25763
+ const header = others.length ? `${onCount} online / ${others.length} total:
25764
+ ` : "";
25765
+ return ok(lines.length ? header + lines.join("\n") : "No other identities on the network yet.");
25743
25766
  })
25744
25767
  );
25745
25768
  server2.registerTool(
25746
25769
  "jefri_send",
25747
25770
  {
25748
25771
  title: "Send a message",
25749
- description: "Send a direct message to another Jefri Chat user or agent by username. Use jefri_agents first if you don't know the username.",
25772
+ description: "Send a direct message to another Jefri Chat user or agent by username. Works whether or not they're online \u2014 an offline recipient gets the message the moment they reconnect, so send it regardless of their status. Use jefri_agents first if you don't know the username.",
25750
25773
  inputSchema: {
25751
25774
  to: external_exports.string().describe("recipient username, e.g. claude_backend"),
25752
25775
  text: external_exports.string().describe("the message body")
@@ -26400,17 +26423,19 @@ ${cmd}`);
26400
26423
  return name ? ` (in group "${name}" \xB7 reply with jefri_send_group groupId="${m.groupId}")` : ` (in group id ${m.groupId})`;
26401
26424
  };
26402
26425
  const lines = await Promise.all(msgs.map(async (m) => {
26426
+ const ts = fmtTime(m.createdAt);
26427
+ const at = ts ? `[${ts}] ` : "";
26403
26428
  if (m.encryptionMode === "private_e2e" && m.encryptedPayload) {
26404
26429
  try {
26405
26430
  const plain = await c.decryptPrivatePayload(m.encryptedPayload);
26406
26431
  const body2 = plain.kind === "file" ? `\u{1F512}\u{1F4CE} private file: ${plain.fileName ?? m.fileName}` : `\u{1F512} ${plain.content ?? ""}`;
26407
- return `@${m.senderUsername}${where(m)}: ${body2}`;
26432
+ return `${at}@${m.senderUsername}${where(m)}: ${body2}`;
26408
26433
  } catch {
26409
- return `@${m.senderUsername}${where(m)}: \u{1F512} Private message unavailable on this device`;
26434
+ return `${at}@${m.senderUsername}${where(m)}: \u{1F512} Private message unavailable on this device`;
26410
26435
  }
26411
26436
  }
26412
26437
  const body = m.kind === "file" ? `\u{1F4CE} sent a file: ${m.fileName} \u2014 to save it call jefri_download_file(from: "${m.senderUsername}"${m.fileName ? `, fileName: "${m.fileName}"` : ""})` : m.content;
26413
- return `@${m.senderUsername}${where(m)}: ${body}`;
26438
+ return `${at}@${m.senderUsername}${where(m)}: ${body}`;
26414
26439
  }));
26415
26440
  return ok(`\u{1F4E8} ${lines.length} message(s):
26416
26441
  ` + lines.join("\n"));
@@ -26531,19 +26556,21 @@ ${cmd}`
26531
26556
  const msgs = (res?.messages ?? []).slice(-(limit ?? 20));
26532
26557
  if (!msgs.length) return ok(`No messages yet in ${label}.`);
26533
26558
  const lines = await Promise.all(msgs.map(async (m) => {
26559
+ const ts = fmtTime(m.createdAt);
26560
+ const at = ts ? `[${ts}] ` : "";
26534
26561
  if (m.encryptionMode === "private_e2e" && m.encryptedPayload) {
26535
26562
  try {
26536
26563
  const plain = await c.decryptPrivatePayload(m.encryptedPayload);
26537
- return `${m.senderUsername}: ${plain.kind === "file" ? `\u{1F512}\u{1F4CE} ${plain.fileName ?? m.fileName}` : `\u{1F512} ${plain.content ?? ""}`}`;
26564
+ return `${at}${m.senderUsername}: ${plain.kind === "file" ? `\u{1F512}\u{1F4CE} ${plain.fileName ?? m.fileName}` : `\u{1F512} ${plain.content ?? ""}`}`;
26538
26565
  } catch {
26539
- return `${m.senderUsername}: \u{1F512} Private message unavailable on this device`;
26566
+ return `${at}${m.senderUsername}: \u{1F512} Private message unavailable on this device`;
26540
26567
  }
26541
26568
  }
26542
26569
  if (m.kind === "file") {
26543
26570
  const dl = groupId ? `jefri_download_file(groupId: "${groupId}", fileName: "${m.fileName}")` : `jefri_download_file(from: "${m.senderUsername}", fileName: "${m.fileName}")`;
26544
- return `${m.senderUsername}: \u{1F4CE} ${m.fileName} \u2014 to save it call ${dl}`;
26571
+ return `${at}${m.senderUsername}: \u{1F4CE} ${m.fileName} \u2014 to save it call ${dl}`;
26545
26572
  }
26546
- return `${m.senderUsername}: ${m.content}`;
26573
+ return `${at}${m.senderUsername}: ${m.content}`;
26547
26574
  }));
26548
26575
  return ok(lines.join("\n"));
26549
26576
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.12.0",
3
+ "version": "0.15.0",
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": {