jefrichat-mcp 0.12.0 → 0.13.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 +39 -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,8 @@ 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 silent = sound ? "" : `$a=$tpl.CreateElement('audio');$a.SetAttribute('silent','true');$tpl.DocumentElement.AppendChild($a)|Out-Null;`;
25421
+ const ps = `$t='${esc2(title)}';$b='${esc2(body)}';try{$null=[Windows.UI.Notifications.ToastNotificationManager,Windows.UI.Notifications,ContentType=WindowsRuntime];$tpl=[Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02);$x=$tpl.GetElementsByTagName('text');$x.Item(0).AppendChild($tpl.CreateTextNode($t))|Out-Null;$x.Item(1).AppendChild($tpl.CreateTextNode($b))|Out-Null;` + silent + `$toast=[Windows.UI.Notifications.ToastNotification]::new($tpl);[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe').Show($toast);}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
25422
  cp.spawn("powershell", ["-NoProfile", "-WindowStyle", "Hidden", "-Command", ps], { stdio: "ignore", detached: true }).unref();
25422
25423
  } else {
25423
25424
  cp.spawn("notify-send", ["-a", "Jefri Chat", title, body], { stdio: "ignore", detached: true }).unref();
@@ -25613,6 +25614,18 @@ var MIME = {
25613
25614
  ".zip": "application/zip"
25614
25615
  };
25615
25616
  var mimeOf = (name) => MIME[np3.extname(name).toLowerCase()] ?? "application/octet-stream";
25617
+ function fmtTime(iso) {
25618
+ if (!iso) return "";
25619
+ const d = new Date(iso);
25620
+ if (isNaN(d.getTime())) return "";
25621
+ return d.toLocaleString(void 0, {
25622
+ month: "short",
25623
+ day: "numeric",
25624
+ hour: "2-digit",
25625
+ minute: "2-digit",
25626
+ hour12: false
25627
+ });
25628
+ }
25616
25629
  function screenshotDir() {
25617
25630
  try {
25618
25631
  const out = cp3.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
@@ -25731,22 +25744,30 @@ ${e?.message ?? e}`);
25731
25744
  "jefri_agents",
25732
25745
  {
25733
25746
  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).",
25747
+ 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
25748
  inputSchema: {}
25736
25749
  },
25737
25750
  async () => withClient(async (c) => {
25738
25751
  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.");
25752
+ const others = all.filter((i) => i.username !== c.identity?.username);
25753
+ const isOn = (i) => i.status !== "offline" && !!i.status;
25754
+ others.sort((a, b) => isOn(a) === isOn(b) ? a.username.localeCompare(b.username) : isOn(a) ? -1 : 1);
25755
+ const lines = others.map((i) => {
25756
+ const dot = isOn(i) ? "\u{1F7E2}" : "\u26AA";
25757
+ const live = isOn(i) ? i.status : "offline";
25758
+ return `${dot} @${i.username} \u2014 ${i.displayName} [${i.type}] (${live})${i.owner ? ` \xB7 by @${i.owner}` : ""}${i.tags.length ? " \xB7 " + i.tags.join(", ") : ""}`;
25759
+ });
25760
+ const onCount = others.filter(isOn).length;
25761
+ const header = others.length ? `${onCount} online / ${others.length} total:
25762
+ ` : "";
25763
+ return ok(lines.length ? header + lines.join("\n") : "No other identities on the network yet.");
25743
25764
  })
25744
25765
  );
25745
25766
  server2.registerTool(
25746
25767
  "jefri_send",
25747
25768
  {
25748
25769
  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.",
25770
+ 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
25771
  inputSchema: {
25751
25772
  to: external_exports.string().describe("recipient username, e.g. claude_backend"),
25752
25773
  text: external_exports.string().describe("the message body")
@@ -26400,17 +26421,19 @@ ${cmd}`);
26400
26421
  return name ? ` (in group "${name}" \xB7 reply with jefri_send_group groupId="${m.groupId}")` : ` (in group id ${m.groupId})`;
26401
26422
  };
26402
26423
  const lines = await Promise.all(msgs.map(async (m) => {
26424
+ const ts = fmtTime(m.createdAt);
26425
+ const at = ts ? `[${ts}] ` : "";
26403
26426
  if (m.encryptionMode === "private_e2e" && m.encryptedPayload) {
26404
26427
  try {
26405
26428
  const plain = await c.decryptPrivatePayload(m.encryptedPayload);
26406
26429
  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}`;
26430
+ return `${at}@${m.senderUsername}${where(m)}: ${body2}`;
26408
26431
  } catch {
26409
- return `@${m.senderUsername}${where(m)}: \u{1F512} Private message unavailable on this device`;
26432
+ return `${at}@${m.senderUsername}${where(m)}: \u{1F512} Private message unavailable on this device`;
26410
26433
  }
26411
26434
  }
26412
26435
  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}`;
26436
+ return `${at}@${m.senderUsername}${where(m)}: ${body}`;
26414
26437
  }));
26415
26438
  return ok(`\u{1F4E8} ${lines.length} message(s):
26416
26439
  ` + lines.join("\n"));
@@ -26531,19 +26554,21 @@ ${cmd}`
26531
26554
  const msgs = (res?.messages ?? []).slice(-(limit ?? 20));
26532
26555
  if (!msgs.length) return ok(`No messages yet in ${label}.`);
26533
26556
  const lines = await Promise.all(msgs.map(async (m) => {
26557
+ const ts = fmtTime(m.createdAt);
26558
+ const at = ts ? `[${ts}] ` : "";
26534
26559
  if (m.encryptionMode === "private_e2e" && m.encryptedPayload) {
26535
26560
  try {
26536
26561
  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 ?? ""}`}`;
26562
+ return `${at}${m.senderUsername}: ${plain.kind === "file" ? `\u{1F512}\u{1F4CE} ${plain.fileName ?? m.fileName}` : `\u{1F512} ${plain.content ?? ""}`}`;
26538
26563
  } catch {
26539
- return `${m.senderUsername}: \u{1F512} Private message unavailable on this device`;
26564
+ return `${at}${m.senderUsername}: \u{1F512} Private message unavailable on this device`;
26540
26565
  }
26541
26566
  }
26542
26567
  if (m.kind === "file") {
26543
26568
  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}`;
26569
+ return `${at}${m.senderUsername}: \u{1F4CE} ${m.fileName} \u2014 to save it call ${dl}`;
26545
26570
  }
26546
- return `${m.senderUsername}: ${m.content}`;
26571
+ return `${at}${m.senderUsername}: ${m.content}`;
26547
26572
  }));
26548
26573
  return ok(lines.join("\n"));
26549
26574
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.12.0",
3
+ "version": "0.13.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": {