@knowsuchagency/fulcrum 2.6.7 → 2.7.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/README.md CHANGED
@@ -170,7 +170,7 @@ Chat with the AI assistant from anywhere via your favorite messaging platform.
170
170
 
171
171
  | Platform | Auth Method |
172
172
  |----------|-------------|
173
- | **Email** | SMTP/IMAP credentials with sender allowlist |
173
+ | **Email** | SMTP/IMAP credentials, collects all emails, allowlist controls AI responses |
174
174
  | **WhatsApp** | QR code scan, monitors all messages, replies only to "Message yourself" |
175
175
  | **Discord** | Bot token from Developer Portal |
176
176
  | **Telegram** | Bot token from @BotFather |
@@ -178,7 +178,7 @@ Chat with the AI assistant from anywhere via your favorite messaging platform.
178
178
 
179
179
  - **Persistent sessions** — Conversation context maintained across messages
180
180
  - **Email threading** — Each email thread is a separate conversation
181
- - **Privacy-respecting** — WhatsApp sees all messages but only replies to you
181
+ - **Observe-first** — Email and WhatsApp collect all messages but only respond to authorized senders
182
182
  - **Commands** — `/reset` (new conversation), `/help`, `/status`
183
183
 
184
184
  Enable in Settings → Messaging and follow the setup instructions for each platform.
package/bin/fulcrum.js CHANGED
@@ -45830,7 +45830,7 @@ async function runMcpServer(urlOverride, portOverride) {
45830
45830
  const client = new FulcrumClient(urlOverride, portOverride);
45831
45831
  const server = new McpServer({
45832
45832
  name: "fulcrum",
45833
- version: "2.6.7"
45833
+ version: "2.7.0"
45834
45834
  });
45835
45835
  registerTools(server, client);
45836
45836
  const transport = new StdioServerTransport;
@@ -48179,7 +48179,7 @@ var marketplace_default = `{
48179
48179
  "name": "fulcrum",
48180
48180
  "source": "./",
48181
48181
  "description": "Task orchestration for Claude Code",
48182
- "version": "2.6.7",
48182
+ "version": "2.7.0",
48183
48183
  "skills": [
48184
48184
  "./skills/fulcrum"
48185
48185
  ],
@@ -49367,7 +49367,7 @@ function compareVersions(v1, v2) {
49367
49367
  var package_default = {
49368
49368
  name: "@knowsuchagency/fulcrum",
49369
49369
  private: true,
49370
- version: "2.6.7",
49370
+ version: "2.7.0",
49371
49371
  description: "Harness Attention. Orchestrate Agents. Ship.",
49372
49372
  license: "PolyForm-Perimeter-1.0.0",
49373
49373
  type: "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowsuchagency/fulcrum",
3
- "version": "2.6.7",
3
+ "version": "2.7.0",
4
4
  "description": "Harness Attention. Orchestrate Agents. Ship.",
5
5
  "license": "PolyForm-Perimeter-1.0.0",
6
6
  "repository": {
package/server/index.js CHANGED
@@ -71223,7 +71223,7 @@ You can read and modify all Fulcrum settings using the settings MCP tools. Setti
71223
71223
  - \`channels.email.smtp.*\` - SMTP server settings (host, port, secure, user, password)
71224
71224
  - \`channels.email.imap.*\` - IMAP server settings (host, port, secure, user, password)
71225
71225
  - \`channels.email.sendAs\` - Email address to send from
71226
- - \`channels.email.allowedSenders\` - Sender allowlist patterns
71226
+ - \`channels.email.allowedSenders\` - Sender allowlist (controls AI responses, not collection)
71227
71227
  - \`channels.slack.enabled\` - Enable/disable Slack channel
71228
71228
  - \`channels.slack.botToken\` - Slack bot token (xoxb-...) [SENSITIVE]
71229
71229
  - \`channels.slack.appToken\` - Slack app token (xapp-...) [SENSITIVE]
@@ -409928,43 +409928,6 @@ async function sendEmail(transporter, connectionId, fromAddress, recipientId, co
409928
409928
  return false;
409929
409929
  }
409930
409930
  }
409931
- async function sendUnauthorizedResponse(transporter, connectionId, fromAddress, headers, bcc) {
409932
- if (!headers.from)
409933
- return;
409934
- const response = `Sorry, I'm not able to respond to messages from your email address.
409935
-
409936
- If you believe this is an error, please contact the owner of this email address.`;
409937
- try {
409938
- const emailHeaders = {};
409939
- if (headers.messageId) {
409940
- emailHeaders["In-Reply-To"] = headers.messageId;
409941
- emailHeaders["References"] = headers.messageId;
409942
- }
409943
- let subject = "Unable to Process Your Request";
409944
- if (headers.subject) {
409945
- subject = headers.subject.startsWith("Re:") ? headers.subject : `Re: ${headers.subject}`;
409946
- }
409947
- await transporter.sendMail({
409948
- from: fromAddress,
409949
- to: headers.from,
409950
- bcc: bcc || undefined,
409951
- subject,
409952
- text: response,
409953
- html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6;"><p>${response.replace(/\n/g, "<br>")}</p></div>`,
409954
- headers: emailHeaders
409955
- });
409956
- log2.messaging.info("Sent unauthorized response", {
409957
- connectionId,
409958
- to: headers.from
409959
- });
409960
- } catch (err) {
409961
- log2.messaging.error("Failed to send unauthorized response", {
409962
- connectionId,
409963
- to: headers.from,
409964
- error: String(err)
409965
- });
409966
- }
409967
- }
409968
409931
  var init_email_sender = __esm(() => {
409969
409932
  init_nanoid();
409970
409933
  init_logger3();
@@ -409998,7 +409961,9 @@ class EmailChannel {
409998
409961
  user: this.credentials.imap.user,
409999
409962
  pass: this.credentials.imap.password
410000
409963
  },
410001
- logger: false
409964
+ logger: false,
409965
+ socketTimeout: 30000,
409966
+ greetingTimeout: 15000
410002
409967
  });
410003
409968
  client3.on("error", (err) => {
410004
409969
  log2.messaging.error("IMAP client error", {
@@ -410084,8 +410049,9 @@ class EmailChannel {
410084
410049
  async pollForNewEmails() {
410085
410050
  if (this.isShuttingDown || !this.credentials)
410086
410051
  return;
410052
+ let client3 = null;
410087
410053
  try {
410088
- const client3 = this.createImapClient();
410054
+ client3 = this.createImapClient();
410089
410055
  await client3.connect();
410090
410056
  const lock = await client3.getMailboxLock("INBOX");
410091
410057
  try {
@@ -410105,33 +410071,38 @@ class EmailChannel {
410105
410071
  if (headers.from.toLowerCase() === fromAddress) {
410106
410072
  continue;
410107
410073
  }
410108
- const authResult = await checkAuthorization(this.connectionId, headers, this.credentials?.allowedSenders || [], this.credentials?.smtp.user.toLowerCase());
410109
- if (!authResult.authorized) {
410110
- const automatedCheck = isAutomatedEmail(headers);
410111
- if (automatedCheck.isAutomated) {
410112
- log2.messaging.info("Email skipped - automated sender", {
410113
- connectionId: this.connectionId,
410114
- from: headers.from,
410115
- subject: headers.subject,
410116
- reason: automatedCheck.reason
410117
- });
410118
- } else {
410119
- log2.messaging.info("Email rejected - not authorized", {
410120
- connectionId: this.connectionId,
410121
- from: headers.from,
410122
- subject: headers.subject,
410123
- reason: authResult.reason
410124
- });
410125
- if (this.transporter) {
410126
- await sendUnauthorizedResponse(this.transporter, this.connectionId, this.getFromAddress(), headers, this.credentials?.bcc);
410127
- }
410128
- }
410129
- await client3.messageFlagsAdd({ uid: message.uid }, ["\\Seen"]);
410074
+ const automatedCheck = isAutomatedEmail(headers);
410075
+ if (automatedCheck.isAutomated) {
410076
+ log2.messaging.info("Email skipped - automated sender", {
410077
+ connectionId: this.connectionId,
410078
+ from: headers.from,
410079
+ subject: headers.subject,
410080
+ reason: automatedCheck.reason
410081
+ });
410130
410082
  continue;
410131
410083
  }
410132
410084
  const content = await parseEmailContent(message.source, this.connectionId);
410133
410085
  if (!content)
410134
410086
  continue;
410087
+ const authResult = await checkAuthorization(this.connectionId, headers, this.credentials?.allowedSenders || [], this.credentials?.smtp.user.toLowerCase());
410088
+ if (headers.messageId) {
410089
+ storeEmail({
410090
+ connectionId: this.connectionId,
410091
+ messageId: headers.messageId,
410092
+ threadId: authResult.threadId,
410093
+ inReplyTo: headers.inReplyTo ?? undefined,
410094
+ references: headers.references.length > 0 ? headers.references : undefined,
410095
+ direction: "incoming",
410096
+ fromAddress: headers.from || "unknown",
410097
+ fromName: headers.fromName ?? undefined,
410098
+ toAddresses: headers.to.length > 0 ? headers.to : undefined,
410099
+ ccAddresses: headers.cc.length > 0 ? headers.cc : undefined,
410100
+ subject: headers.subject ?? undefined,
410101
+ textContent: content,
410102
+ emailDate: headers.date ?? undefined,
410103
+ imapUid: message.uid
410104
+ });
410105
+ }
410135
410106
  const incomingMessage = {
410136
410107
  channelType: "email",
410137
410108
  connectionId: this.connectionId,
@@ -410144,7 +410115,8 @@ class EmailChannel {
410144
410115
  inReplyTo: headers.inReplyTo,
410145
410116
  references: headers.references,
410146
410117
  subject: headers.subject,
410147
- threadId: authResult.threadId
410118
+ threadId: authResult.threadId,
410119
+ observeOnly: !authResult.authorized
410148
410120
  }
410149
410121
  };
410150
410122
  log2.messaging.info("Email received", {
@@ -410153,26 +410125,9 @@ class EmailChannel {
410153
410125
  subject: headers.subject,
410154
410126
  contentLength: content.length,
410155
410127
  threadId: authResult.threadId,
410128
+ authorized: authResult.authorized,
410156
410129
  authorizedBy: authResult.authorizedBy
410157
410130
  });
410158
- if (headers.messageId) {
410159
- storeEmail({
410160
- connectionId: this.connectionId,
410161
- messageId: headers.messageId,
410162
- threadId: authResult.threadId,
410163
- inReplyTo: headers.inReplyTo ?? undefined,
410164
- references: headers.references.length > 0 ? headers.references : undefined,
410165
- direction: "incoming",
410166
- fromAddress: headers.from || "unknown",
410167
- fromName: headers.fromName ?? undefined,
410168
- toAddresses: headers.to.length > 0 ? headers.to : undefined,
410169
- ccAddresses: headers.cc.length > 0 ? headers.cc : undefined,
410170
- subject: headers.subject ?? undefined,
410171
- textContent: content,
410172
- emailDate: headers.date ?? undefined,
410173
- imapUid: message.uid
410174
- });
410175
- }
410176
410131
  try {
410177
410132
  await this.events?.onMessage(incomingMessage);
410178
410133
  } catch (err) {
@@ -410194,6 +410149,11 @@ class EmailChannel {
410194
410149
  if (String(err).includes("AUTHENTICATIONFAILED") || String(err).includes("LOGIN")) {
410195
410150
  this.updateStatus("credentials_required");
410196
410151
  }
410152
+ if (client3) {
410153
+ try {
410154
+ await client3.logout();
410155
+ } catch {}
410156
+ }
410197
410157
  }
410198
410158
  }
410199
410159
  async shutdown() {
@@ -466852,7 +466812,7 @@ mcpRoutes.all("/", async (c) => {
466852
466812
  });
466853
466813
  const server = new McpServer({
466854
466814
  name: "fulcrum",
466855
- version: "2.6.7"
466815
+ version: "2.7.0"
466856
466816
  });
466857
466817
  const client = new FulcrumClient(`http://localhost:${port}`);
466858
466818
  registerTools(server, client);