@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 +2 -2
- package/bin/fulcrum.js +3 -3
- package/package.json +1 -1
- package/server/index.js +42 -82
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
|
|
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
|
-
- **
|
|
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.
|
|
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.
|
|
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.
|
|
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
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
|
|
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
|
-
|
|
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
|
|
410109
|
-
if (
|
|
410110
|
-
|
|
410111
|
-
|
|
410112
|
-
|
|
410113
|
-
|
|
410114
|
-
|
|
410115
|
-
|
|
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.
|
|
466815
|
+
version: "2.7.0"
|
|
466856
466816
|
});
|
|
466857
466817
|
const client = new FulcrumClient(`http://localhost:${port}`);
|
|
466858
466818
|
registerTools(server, client);
|