jefrichat-mcp 0.16.0 → 0.18.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.
package/dist/http.js CHANGED
@@ -49466,6 +49466,7 @@ function setPrefs(patch) {
49466
49466
  }
49467
49467
  return { ...prefs };
49468
49468
  }
49469
+ var LOG_FILE = np.join(os2.homedir(), ".jefri", "notify.log");
49469
49470
 
49470
49471
  // src/autonomous.ts
49471
49472
  import fs3 from "node:fs";
@@ -49479,13 +49480,14 @@ var DEFAULTS2 = {
49479
49480
  replyMode: "mentions",
49480
49481
  persona: "",
49481
49482
  replyToBots: false,
49482
- ownerOnly: false,
49483
+ ownerOnly: true,
49484
+ // SECURITY: default to only the owner can direct an autonomous agent
49483
49485
  contextMessages: 12,
49484
49486
  promptTemplate: DEFAULT_TEMPLATE
49485
49487
  };
49486
49488
  var DIR = np2.join(os3.homedir(), ".jefri");
49487
49489
  var FILE = np2.join(DIR, "autonomous.json");
49488
- var LOG_FILE = np2.join(DIR, "autonomous.log");
49490
+ var LOG_FILE2 = np2.join(DIR, "autonomous.log");
49489
49491
  function loadCfg() {
49490
49492
  try {
49491
49493
  return { ...DEFAULTS2, ...JSON.parse(fs3.readFileSync(FILE, "utf8")) };
@@ -49628,7 +49630,23 @@ function waitFor(c, event, match, timeoutMs = 4e3) {
49628
49630
  }
49629
49631
  var ok = (text) => ({ content: [{ type: "text", text }] });
49630
49632
  var fail = (text) => ({ content: [{ type: "text", text }], isError: true });
49631
- var inboxWatermark = /* @__PURE__ */ new Map();
49633
+ var WATERMARK_FILE = np3.join(os4.homedir(), ".jefri", "inbox.json");
49634
+ var inboxWatermark = new Map(
49635
+ (() => {
49636
+ try {
49637
+ return Object.entries(JSON.parse(fs4.readFileSync(WATERMARK_FILE, "utf8")));
49638
+ } catch {
49639
+ return [];
49640
+ }
49641
+ })()
49642
+ );
49643
+ function saveWatermark() {
49644
+ try {
49645
+ fs4.mkdirSync(np3.dirname(WATERMARK_FILE), { recursive: true });
49646
+ fs4.writeFileSync(WATERMARK_FILE, JSON.stringify(Object.fromEntries(inboxWatermark)));
49647
+ } catch {
49648
+ }
49649
+ }
49632
49650
  function registerAcpTools(server, ctx) {
49633
49651
  const ENABLE_E2E = process.env.ACP_ENABLE_E2E === "true";
49634
49652
  const e2eServer = ENABLE_E2E ? server : { registerTool: () => void 0 };
@@ -50299,8 +50317,8 @@ ${cmd}`);
50299
50317
  server.registerTool(
50300
50318
  "jefri_inbox",
50301
50319
  {
50302
- title: "Check inbox",
50303
- description: "Return messages people/agents have sent you (DMs + group messages) since you last checked. Reads from the hub, so it works even if your client doesn't keep a live connection.",
50320
+ title: "Read unread messages",
50321
+ description: "Read your UNREAD / new messages \u2014 everything people and agents have sent you (DMs + group messages) since the last time you checked. Use this when asked to 'read my messages', 'check new messages', or 'catch up'. Each line is timestamped. Reads from the hub, so it works even without a live connection, and it remembers what you've already seen (persisted across restarts).",
50304
50322
  inputSchema: {}
50305
50323
  },
50306
50324
  async () => withClient(async (c) => {
@@ -50320,6 +50338,7 @@ ${cmd}`);
50320
50338
  if (!msgs.length) return ok("\u{1F4ED} No new messages.");
50321
50339
  msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : 1);
50322
50340
  inboxWatermark.set(me, msgs[msgs.length - 1].createdAt);
50341
+ saveWatermark();
50323
50342
  const groupNames = /* @__PURE__ */ new Map();
50324
50343
  if (msgs.some((m) => m.groupId)) {
50325
50344
  try {
package/dist/index.js CHANGED
@@ -25387,16 +25387,27 @@ function inQuietHours() {
25387
25387
  return from < to ? cur >= from && cur < to : cur >= from || cur < to;
25388
25388
  }
25389
25389
  var clean = (s) => (s || "").replace(/[\r\n\t]+/g, " ").replace(/[\x00-\x1F]/g, "").slice(0, 140).trim();
25390
+ var LOG_FILE = np.join(os2.homedir(), ".jefri", "notify.log");
25391
+ function logNotify(line) {
25392
+ try {
25393
+ fs2.mkdirSync(np.dirname(LOG_FILE), { recursive: true });
25394
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
25395
+ fs2.appendFileSync(LOG_FILE, `[${ts}] ${line}
25396
+ `);
25397
+ } catch {
25398
+ }
25399
+ }
25390
25400
  function notifyIncoming(m) {
25391
- if (OFF || !prefs.enabled) return;
25392
- if (m.isGroup ? !prefs.groups : !prefs.dms) return;
25393
- if (m.isGroup && prefs.onlyMentions && !m.isMention) return;
25394
- if (m.isGroup && m.groupId && prefs.mutedGroups.includes(m.groupId)) return;
25395
- if (prefs.mutedUsers.includes(m.sender)) return;
25396
- if (inQuietHours()) return;
25401
+ if (OFF || !prefs.enabled) return logNotify(`skip (disabled) from @${m.sender}`);
25402
+ if (m.isGroup ? !prefs.groups : !prefs.dms) return logNotify(`skip (dms/groups pref) from @${m.sender}`);
25403
+ if (m.isGroup && prefs.onlyMentions && !m.isMention) return logNotify(`skip (onlyMentions) from @${m.sender}`);
25404
+ if (m.isGroup && m.groupId && prefs.mutedGroups.includes(m.groupId)) return logNotify(`skip (muted group) from @${m.sender}`);
25405
+ if (prefs.mutedUsers.includes(m.sender)) return logNotify(`skip (muted user) @${m.sender}`);
25406
+ if (inQuietHours()) return logNotify(`skip (quiet hours) from @${m.sender}`);
25397
25407
  const sound = prefs.sound;
25398
25408
  const title = "Jefri Chat";
25399
25409
  const body = clean(m.body);
25410
+ logNotify(`fire on ${process.platform} for @${m.sender}: ${body.slice(0, 60)}`);
25400
25411
  if (sound) {
25401
25412
  try {
25402
25413
  process.stderr.write("\x07");
@@ -25420,9 +25431,21 @@ function notifyIncoming(m) {
25420
25431
  const xesc = (x) => x.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
25421
25432
  const audio = sound ? `<audio src="ms-winsoundevent:Notification.Default"/>` : `<audio silent="true"/>`;
25422
25433
  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();}`;
25434
+ 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();[Console]::Error.WriteLine('TOASTERR-fell-back-to-balloon: '+$_.Exception.Message);}`;
25424
25435
  const encoded = Buffer.from(ps, "utf16le").toString("base64");
25425
- cp.spawn("powershell", ["-NoProfile", "-WindowStyle", "Hidden", "-EncodedCommand", encoded], { stdio: "ignore", detached: true }).unref();
25436
+ const child = cp.spawn(
25437
+ "powershell",
25438
+ ["-NoProfile", "-WindowStyle", "Hidden", "-EncodedCommand", encoded],
25439
+ { stdio: ["ignore", "ignore", "pipe"] }
25440
+ );
25441
+ let errBuf = "";
25442
+ child.stderr?.on("data", (d) => errBuf += d.toString());
25443
+ child.on("error", (e) => logNotify(`win: powershell spawn FAILED: ${e.message}`));
25444
+ child.on(
25445
+ "close",
25446
+ (code) => logNotify(`win: powershell exit=${code}${errBuf.trim() ? ` stderr=${errBuf.trim().slice(0, 300)}` : " (no error output)"}`)
25447
+ );
25448
+ child.unref();
25426
25449
  } else {
25427
25450
  cp.spawn("notify-send", ["-a", "Jefri Chat", title, body], { stdio: "ignore", detached: true }).unref();
25428
25451
  }
@@ -25443,18 +25466,19 @@ var DEFAULTS2 = {
25443
25466
  replyMode: "mentions",
25444
25467
  persona: "",
25445
25468
  replyToBots: false,
25446
- ownerOnly: false,
25469
+ ownerOnly: true,
25470
+ // SECURITY: default to only the owner can direct an autonomous agent
25447
25471
  contextMessages: 12,
25448
25472
  promptTemplate: DEFAULT_TEMPLATE
25449
25473
  };
25450
25474
  var DIR = np2.join(os3.homedir(), ".jefri");
25451
25475
  var FILE = np2.join(DIR, "autonomous.json");
25452
- var LOG_FILE = np2.join(DIR, "autonomous.log");
25476
+ var LOG_FILE2 = np2.join(DIR, "autonomous.log");
25453
25477
  function logLine(line) {
25454
25478
  try {
25455
25479
  fs3.mkdirSync(DIR, { recursive: true });
25456
25480
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
25457
- fs3.appendFileSync(LOG_FILE, `[${ts}] ${line}
25481
+ fs3.appendFileSync(LOG_FILE2, `[${ts}] ${line}
25458
25482
  `);
25459
25483
  } catch {
25460
25484
  }
@@ -25497,10 +25521,12 @@ function brainArgv(brain, prompt) {
25497
25521
  var MAX_PROMPT = 12e3;
25498
25522
  var MAX_REPLY = 7900;
25499
25523
  var TIMEOUT_MS = 5 * 60 * 1e3;
25524
+ var FORCE_OWNER_ONLY = true;
25500
25525
  function shouldHandle(m) {
25501
25526
  if (!cfg.enabled) return false;
25502
25527
  const fromOwner = !!ownerName && m.sender === ownerName;
25503
- if (cfg.ownerOnly && !fromOwner) return false;
25528
+ const ownerOnly = cfg.ownerOnly || FORCE_OWNER_ONLY && !!ownerName;
25529
+ if (ownerOnly && !fromOwner) return false;
25504
25530
  if (m.senderIsBot && !cfg.replyToBots) return false;
25505
25531
  if (!m.isGroup) return true;
25506
25532
  if (cfg.replyMode === "all") return true;
@@ -25719,7 +25745,23 @@ function waitFor(c, event, match, timeoutMs = 4e3) {
25719
25745
  }
25720
25746
  var ok = (text) => ({ content: [{ type: "text", text }] });
25721
25747
  var fail = (text) => ({ content: [{ type: "text", text }], isError: true });
25722
- var inboxWatermark = /* @__PURE__ */ new Map();
25748
+ var WATERMARK_FILE = np3.join(os4.homedir(), ".jefri", "inbox.json");
25749
+ var inboxWatermark = new Map(
25750
+ (() => {
25751
+ try {
25752
+ return Object.entries(JSON.parse(fs4.readFileSync(WATERMARK_FILE, "utf8")));
25753
+ } catch {
25754
+ return [];
25755
+ }
25756
+ })()
25757
+ );
25758
+ function saveWatermark() {
25759
+ try {
25760
+ fs4.mkdirSync(np3.dirname(WATERMARK_FILE), { recursive: true });
25761
+ fs4.writeFileSync(WATERMARK_FILE, JSON.stringify(Object.fromEntries(inboxWatermark)));
25762
+ } catch {
25763
+ }
25764
+ }
25723
25765
  function registerAcpTools(server2, ctx) {
25724
25766
  const ENABLE_E2E = process.env.ACP_ENABLE_E2E === "true";
25725
25767
  const e2eServer = ENABLE_E2E ? server2 : { registerTool: () => void 0 };
@@ -26390,8 +26432,8 @@ ${cmd}`);
26390
26432
  server2.registerTool(
26391
26433
  "jefri_inbox",
26392
26434
  {
26393
- title: "Check inbox",
26394
- description: "Return messages people/agents have sent you (DMs + group messages) since you last checked. Reads from the hub, so it works even if your client doesn't keep a live connection.",
26435
+ title: "Read unread messages",
26436
+ description: "Read your UNREAD / new messages \u2014 everything people and agents have sent you (DMs + group messages) since the last time you checked. Use this when asked to 'read my messages', 'check new messages', or 'catch up'. Each line is timestamped. Reads from the hub, so it works even without a live connection, and it remembers what you've already seen (persisted across restarts).",
26395
26437
  inputSchema: {}
26396
26438
  },
26397
26439
  async () => withClient(async (c) => {
@@ -26411,6 +26453,7 @@ ${cmd}`);
26411
26453
  if (!msgs.length) return ok("\u{1F4ED} No new messages.");
26412
26454
  msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : 1);
26413
26455
  inboxWatermark.set(me, msgs[msgs.length - 1].createdAt);
26456
+ saveWatermark();
26414
26457
  const groupNames = /* @__PURE__ */ new Map();
26415
26458
  if (msgs.some((m) => m.groupId)) {
26416
26459
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.16.0",
3
+ "version": "0.18.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": {