@knowsuchagency/fulcrum 2.8.1 → 2.9.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 +5 -5
- package/bin/fulcrum.js +50 -156
- package/dist/assets/{index-TyeuSbkG.css → index-Lj1lLJpw.css} +1 -1
- package/dist/assets/{index-COqMlavt.js → index-dMSLbxmc.js} +166 -165
- package/dist/index.html +2 -2
- package/drizzle/0053_add_memory_source.sql +1 -0
- package/drizzle/0054_remove_actionable_events.sql +1 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +1 -1
- package/server/index.js +267 -549
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Fulcrum doesn't replace your tools—it gives you leverage over them. You config
|
|
|
15
15
|
**Six pillars:**
|
|
16
16
|
|
|
17
17
|
- **Claude Code Ecosystem** — Deep integration with Claude Code. Connect via messaging channels, manage tasks, deploy from one dashboard.
|
|
18
|
-
- **Proactive AI Concierge** — Your assistant monitors messages,
|
|
18
|
+
- **Proactive AI Concierge** — Your assistant monitors messages, stores important observations as memories, creates daily plans, and sends morning/evening briefings automatically.
|
|
19
19
|
- **Work From Anywhere** — Run Fulcrum on a remote server. Close your laptop, agents keep working.
|
|
20
20
|
- **Project Management** — Tasks with dependencies, due dates, labels, and attachments. Visual kanban boards.
|
|
21
21
|
- **Production Deployment** — Docker Compose with automatic Traefik routing and Cloudflare DNS/tunnels.
|
|
@@ -42,13 +42,13 @@ Fulcrum's AI assistant doesn't just respond—it actively monitors and manages y
|
|
|
42
42
|
|
|
43
43
|
When messages arrive via Email, WhatsApp, Discord, Telegram, or Slack:
|
|
44
44
|
|
|
45
|
-
- **Actionable requests** (deadlines, meetings, tasks) →
|
|
45
|
+
- **Actionable requests** (deadlines, meetings, tasks) → Stored as memories with `actionable` tag, optionally creates tasks
|
|
46
46
|
- **Casual conversations** → Responded to naturally, no tracking overhead
|
|
47
47
|
- **Spam/newsletters** → Silently ignored
|
|
48
48
|
|
|
49
|
-
###
|
|
49
|
+
### Persistent Memory
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Important observations are stored as **memories** with tags like `actionable` or `monitoring`—searchable via full-text search, with recency-weighted results so recent items surface first.
|
|
52
52
|
|
|
53
53
|
### Daily Planning
|
|
54
54
|
|
|
@@ -247,7 +247,7 @@ Both plugins include an MCP server with 60+ tools:
|
|
|
247
247
|
| **Backup & Restore** | Snapshot database and settings; auto-safety-backup on restore |
|
|
248
248
|
| **Settings** | View and update configuration; manage notification channels |
|
|
249
249
|
| **Memory** | Store and search persistent knowledge with FTS5 full-text search |
|
|
250
|
-
| **Assistant
|
|
250
|
+
| **Assistant** | Send messages via channels; query sweep history |
|
|
251
251
|
|
|
252
252
|
Use `search_tools` to discover available tools by keyword or category.
|
|
253
253
|
|
package/bin/fulcrum.js
CHANGED
|
@@ -1197,6 +1197,13 @@ class FulcrumClient {
|
|
|
1197
1197
|
method: "DELETE"
|
|
1198
1198
|
});
|
|
1199
1199
|
}
|
|
1200
|
+
async deleteTag(tagName) {
|
|
1201
|
+
const allTags = await this.fetch("/api/tags");
|
|
1202
|
+
const tag = allTags.find((t2) => t2.name === tagName);
|
|
1203
|
+
if (!tag)
|
|
1204
|
+
throw new ApiError(404, "Tag not found");
|
|
1205
|
+
return this.fetch(`/api/tags/${tag.id}`, { method: "DELETE" });
|
|
1206
|
+
}
|
|
1200
1207
|
async setTaskDueDate(taskId, dueDate) {
|
|
1201
1208
|
return this.fetch(`/api/tasks/${taskId}/due-date`, {
|
|
1202
1209
|
method: "PATCH",
|
|
@@ -1487,39 +1494,6 @@ class FulcrumClient {
|
|
|
1487
1494
|
body: JSON.stringify({ uids, limit })
|
|
1488
1495
|
});
|
|
1489
1496
|
}
|
|
1490
|
-
async listActionableEvents(options) {
|
|
1491
|
-
const params = new URLSearchParams;
|
|
1492
|
-
if (options?.status)
|
|
1493
|
-
params.set("status", options.status);
|
|
1494
|
-
if (options?.channel)
|
|
1495
|
-
params.set("channel", options.channel);
|
|
1496
|
-
if (options?.limit)
|
|
1497
|
-
params.set("limit", String(options.limit));
|
|
1498
|
-
if (options?.offset)
|
|
1499
|
-
params.set("offset", String(options.offset));
|
|
1500
|
-
const query = params.toString();
|
|
1501
|
-
return this.fetch(`/api/assistant/events${query ? `?${query}` : ""}`);
|
|
1502
|
-
}
|
|
1503
|
-
async getActionableEvent(id) {
|
|
1504
|
-
return this.fetch(`/api/assistant/events/${id}`);
|
|
1505
|
-
}
|
|
1506
|
-
async createActionableEvent(data) {
|
|
1507
|
-
return this.fetch("/api/assistant/events", {
|
|
1508
|
-
method: "POST",
|
|
1509
|
-
body: JSON.stringify(data)
|
|
1510
|
-
});
|
|
1511
|
-
}
|
|
1512
|
-
async updateActionableEvent(id, updates) {
|
|
1513
|
-
return this.fetch(`/api/assistant/events/${id}`, {
|
|
1514
|
-
method: "PATCH",
|
|
1515
|
-
body: JSON.stringify(updates)
|
|
1516
|
-
});
|
|
1517
|
-
}
|
|
1518
|
-
async deleteActionableEvent(id) {
|
|
1519
|
-
return this.fetch(`/api/assistant/events/${id}`, {
|
|
1520
|
-
method: "DELETE"
|
|
1521
|
-
});
|
|
1522
|
-
}
|
|
1523
1497
|
async listSweepRuns(options) {
|
|
1524
1498
|
const params = new URLSearchParams;
|
|
1525
1499
|
if (options?.type)
|
|
@@ -1611,9 +1585,6 @@ class FulcrumClient {
|
|
|
1611
1585
|
async deleteMemory(id) {
|
|
1612
1586
|
return this.fetch(`/api/memory/${id}`, { method: "DELETE" });
|
|
1613
1587
|
}
|
|
1614
|
-
async getAssistantStats() {
|
|
1615
|
-
return this.fetch("/api/assistant/stats");
|
|
1616
|
-
}
|
|
1617
1588
|
}
|
|
1618
1589
|
var init_client = __esm(() => {
|
|
1619
1590
|
init_server();
|
|
@@ -43951,6 +43922,13 @@ var init_registry = __esm(() => {
|
|
|
43951
43922
|
keywords: ["tags", "categories", "filter", "search", "discover"],
|
|
43952
43923
|
defer_loading: false
|
|
43953
43924
|
},
|
|
43925
|
+
{
|
|
43926
|
+
name: "delete_tag",
|
|
43927
|
+
description: "Delete a tag from the database and all its associations",
|
|
43928
|
+
category: "tasks",
|
|
43929
|
+
keywords: ["tag", "delete", "remove", "cleanup"],
|
|
43930
|
+
defer_loading: false
|
|
43931
|
+
},
|
|
43954
43932
|
{
|
|
43955
43933
|
name: "list_task_attachments",
|
|
43956
43934
|
description: "List all file attachments for a task",
|
|
@@ -44112,41 +44090,6 @@ var init_registry = __esm(() => {
|
|
|
44112
44090
|
keywords: ["message", "send", "reply", "email", "whatsapp", "communicate", "respond"],
|
|
44113
44091
|
defer_loading: false
|
|
44114
44092
|
},
|
|
44115
|
-
{
|
|
44116
|
-
name: "create_actionable_event",
|
|
44117
|
-
description: "Create an actionable event to track something noticed",
|
|
44118
|
-
category: "assistant",
|
|
44119
|
-
keywords: ["event", "actionable", "track", "remember", "log", "decision", "memory"],
|
|
44120
|
-
defer_loading: false
|
|
44121
|
-
},
|
|
44122
|
-
{
|
|
44123
|
-
name: "list_actionable_events",
|
|
44124
|
-
description: "List actionable events by status or channel",
|
|
44125
|
-
category: "assistant",
|
|
44126
|
-
keywords: ["event", "actionable", "list", "pending", "review", "memory"],
|
|
44127
|
-
defer_loading: false
|
|
44128
|
-
},
|
|
44129
|
-
{
|
|
44130
|
-
name: "get_actionable_event",
|
|
44131
|
-
description: "Get details of a specific actionable event",
|
|
44132
|
-
category: "assistant",
|
|
44133
|
-
keywords: ["event", "actionable", "get", "details", "history"],
|
|
44134
|
-
defer_loading: false
|
|
44135
|
-
},
|
|
44136
|
-
{
|
|
44137
|
-
name: "update_actionable_event",
|
|
44138
|
-
description: "Update an actionable event status or link to a task",
|
|
44139
|
-
category: "assistant",
|
|
44140
|
-
keywords: ["event", "actionable", "update", "status", "link", "task", "log"],
|
|
44141
|
-
defer_loading: false
|
|
44142
|
-
},
|
|
44143
|
-
{
|
|
44144
|
-
name: "get_assistant_stats",
|
|
44145
|
-
description: "Get assistant statistics: event counts by status and last sweep times",
|
|
44146
|
-
category: "assistant",
|
|
44147
|
-
keywords: ["assistant", "stats", "statistics", "events", "sweep", "summary"],
|
|
44148
|
-
defer_loading: false
|
|
44149
|
-
},
|
|
44150
44093
|
{
|
|
44151
44094
|
name: "get_last_sweep",
|
|
44152
44095
|
description: "Get information about the last sweep run of a type",
|
|
@@ -44683,6 +44626,16 @@ var registerTaskTools = (server, client) => {
|
|
|
44683
44626
|
return handleToolError(err);
|
|
44684
44627
|
}
|
|
44685
44628
|
});
|
|
44629
|
+
server.tool("delete_tag", "Delete a tag from the database. This removes the tag and all its associations with tasks and projects.", {
|
|
44630
|
+
tag: exports_external.string().describe("The exact name of the tag to delete")
|
|
44631
|
+
}, async ({ tag }) => {
|
|
44632
|
+
try {
|
|
44633
|
+
await client.deleteTag(tag);
|
|
44634
|
+
return formatSuccess({ deleted: tag });
|
|
44635
|
+
} catch (err) {
|
|
44636
|
+
return handleToolError(err);
|
|
44637
|
+
}
|
|
44638
|
+
});
|
|
44686
44639
|
server.tool("list_tasks_by_due_date", "List tasks within a date range based on due date", {
|
|
44687
44640
|
startDate: exports_external.optional(exports_external.string()).describe("Start date (YYYY-MM-DD), inclusive"),
|
|
44688
44641
|
endDate: exports_external.optional(exports_external.string()).describe("End date (YYYY-MM-DD), inclusive"),
|
|
@@ -45615,8 +45568,8 @@ var init_email = __esm(() => {
|
|
|
45615
45568
|
init_utils();
|
|
45616
45569
|
});
|
|
45617
45570
|
|
|
45618
|
-
// cli/src/mcp/tools/assistant
|
|
45619
|
-
var
|
|
45571
|
+
// cli/src/mcp/tools/assistant.ts
|
|
45572
|
+
var ChannelSchema, registerAssistantTools = (server, client) => {
|
|
45620
45573
|
server.tool("message", "Send a message to a messaging channel (email, WhatsApp, etc.). Use this to reply to messages or send proactive communications.", {
|
|
45621
45574
|
channel: ChannelSchema.describe("Target channel: email, whatsapp, discord, telegram, slack, or all"),
|
|
45622
45575
|
to: exports_external.string().describe("Recipient (email address, phone number, or channel ID)"),
|
|
@@ -45639,79 +45592,6 @@ var ActionableEventStatusSchema, ChannelSchema, registerAssistantEventTools = (s
|
|
|
45639
45592
|
return handleToolError(err);
|
|
45640
45593
|
}
|
|
45641
45594
|
});
|
|
45642
|
-
server.tool("create_actionable_event", "Create an actionable event to track something noticed (message, request, etc.). Use this to log decisions and maintain memory of things that need attention.", {
|
|
45643
|
-
source_channel: ChannelSchema.exclude(["all"]).describe("Channel where the event originated"),
|
|
45644
|
-
source_id: exports_external.string().describe("ID of the source message/email"),
|
|
45645
|
-
source_metadata: exports_external.optional(exports_external.record(exports_external.string(), exports_external.any())).describe("Additional context (sender, subject, etc.)"),
|
|
45646
|
-
summary: exports_external.optional(exports_external.string()).describe("AI-generated description of what this event is about"),
|
|
45647
|
-
status: exports_external.optional(ActionableEventStatusSchema).describe("Event status (default: pending)"),
|
|
45648
|
-
linked_task_id: exports_external.optional(exports_external.string()).describe("Fulcrum task ID if this event relates to a task")
|
|
45649
|
-
}, async ({ source_channel, source_id, source_metadata, summary, status, linked_task_id }) => {
|
|
45650
|
-
try {
|
|
45651
|
-
const result = await client.createActionableEvent({
|
|
45652
|
-
sourceChannel: source_channel,
|
|
45653
|
-
sourceId: source_id,
|
|
45654
|
-
sourceMetadata: source_metadata,
|
|
45655
|
-
summary,
|
|
45656
|
-
status,
|
|
45657
|
-
linkedTaskId: linked_task_id
|
|
45658
|
-
});
|
|
45659
|
-
return formatSuccess(result);
|
|
45660
|
-
} catch (err) {
|
|
45661
|
-
return handleToolError(err);
|
|
45662
|
-
}
|
|
45663
|
-
});
|
|
45664
|
-
server.tool("list_actionable_events", "List actionable events. Use this to review what needs attention, check for patterns, or avoid creating duplicates.", {
|
|
45665
|
-
status: exports_external.optional(ActionableEventStatusSchema).describe("Filter by status"),
|
|
45666
|
-
source_channel: exports_external.optional(exports_external.string()).describe("Filter by source channel"),
|
|
45667
|
-
limit: exports_external.optional(exports_external.number()).describe("Maximum events to return (default: 50)")
|
|
45668
|
-
}, async ({ status, source_channel, limit }) => {
|
|
45669
|
-
try {
|
|
45670
|
-
const result = await client.listActionableEvents({
|
|
45671
|
-
status,
|
|
45672
|
-
channel: source_channel,
|
|
45673
|
-
limit: limit ?? 50
|
|
45674
|
-
});
|
|
45675
|
-
return formatSuccess(result);
|
|
45676
|
-
} catch (err) {
|
|
45677
|
-
return handleToolError(err);
|
|
45678
|
-
}
|
|
45679
|
-
});
|
|
45680
|
-
server.tool("get_actionable_event", "Get details of a specific actionable event including its action log and linked task.", {
|
|
45681
|
-
id: exports_external.string().describe("Event ID")
|
|
45682
|
-
}, async ({ id }) => {
|
|
45683
|
-
try {
|
|
45684
|
-
const result = await client.getActionableEvent(id);
|
|
45685
|
-
return formatSuccess(result);
|
|
45686
|
-
} catch (err) {
|
|
45687
|
-
return handleToolError(err);
|
|
45688
|
-
}
|
|
45689
|
-
});
|
|
45690
|
-
server.tool("update_actionable_event", "Update an actionable event status, link it to a task, or add to its action log. Use action_log_entry to record decisions/actions taken.", {
|
|
45691
|
-
id: exports_external.string().describe("Event ID"),
|
|
45692
|
-
status: exports_external.optional(ActionableEventStatusSchema).describe("New status"),
|
|
45693
|
-
linked_task_id: exports_external.optional(exports_external.string()).describe("Link to a Fulcrum task (use empty string to unlink)"),
|
|
45694
|
-
action_log_entry: exports_external.optional(exports_external.string()).describe('Action to log (e.g., "Replied with project update")')
|
|
45695
|
-
}, async ({ id, status, linked_task_id, action_log_entry }) => {
|
|
45696
|
-
try {
|
|
45697
|
-
const result = await client.updateActionableEvent(id, {
|
|
45698
|
-
status,
|
|
45699
|
-
linkedTaskId: linked_task_id === "" ? null : linked_task_id,
|
|
45700
|
-
actionLogEntry: action_log_entry
|
|
45701
|
-
});
|
|
45702
|
-
return formatSuccess(result);
|
|
45703
|
-
} catch (err) {
|
|
45704
|
-
return handleToolError(err);
|
|
45705
|
-
}
|
|
45706
|
-
});
|
|
45707
|
-
server.tool("get_assistant_stats", "Get assistant statistics: event counts by status and last sweep times. Useful for understanding workload and activity.", {}, async () => {
|
|
45708
|
-
try {
|
|
45709
|
-
const result = await client.getAssistantStats();
|
|
45710
|
-
return formatSuccess(result);
|
|
45711
|
-
} catch (err) {
|
|
45712
|
-
return handleToolError(err);
|
|
45713
|
-
}
|
|
45714
|
-
});
|
|
45715
45595
|
server.tool("get_last_sweep", "Get information about the last sweep run of a specific type. Useful for context about what was last reviewed.", {
|
|
45716
45596
|
type: exports_external.enum(["hourly", "morning_ritual", "evening_ritual"]).describe("Type of sweep")
|
|
45717
45597
|
}, async ({ type }) => {
|
|
@@ -45723,10 +45603,9 @@ var ActionableEventStatusSchema, ChannelSchema, registerAssistantEventTools = (s
|
|
|
45723
45603
|
}
|
|
45724
45604
|
});
|
|
45725
45605
|
};
|
|
45726
|
-
var
|
|
45606
|
+
var init_assistant = __esm(() => {
|
|
45727
45607
|
init_zod2();
|
|
45728
45608
|
init_utils();
|
|
45729
|
-
ActionableEventStatusSchema = exports_external.enum(["pending", "acted_upon", "dismissed", "monitoring"]);
|
|
45730
45609
|
ChannelSchema = exports_external.enum(["email", "whatsapp", "discord", "telegram", "slack", "all"]);
|
|
45731
45610
|
});
|
|
45732
45611
|
|
|
@@ -45825,14 +45704,28 @@ var init_caldav = __esm(() => {
|
|
|
45825
45704
|
init_utils();
|
|
45826
45705
|
});
|
|
45827
45706
|
|
|
45707
|
+
// shared/types.ts
|
|
45708
|
+
var MEMORY_SOURCES;
|
|
45709
|
+
var init_types5 = __esm(() => {
|
|
45710
|
+
MEMORY_SOURCES = [
|
|
45711
|
+
"channel:whatsapp",
|
|
45712
|
+
"channel:slack",
|
|
45713
|
+
"channel:discord",
|
|
45714
|
+
"channel:telegram",
|
|
45715
|
+
"channel:email",
|
|
45716
|
+
"conversation:assistant"
|
|
45717
|
+
];
|
|
45718
|
+
});
|
|
45719
|
+
|
|
45828
45720
|
// cli/src/mcp/tools/memory.ts
|
|
45829
45721
|
var registerMemoryTools = (server, client) => {
|
|
45830
45722
|
server.tool("memory_store", "Store a piece of knowledge in persistent memory. Use this to remember facts, preferences, decisions, patterns, or any information that should persist across conversations.", {
|
|
45831
45723
|
content: exports_external.string().describe("The memory content to store. Be specific and self-contained."),
|
|
45832
|
-
tags: exports_external.optional(exports_external.array(exports_external.string())).describe('Optional tags for categorization (e.g., ["preference", "architecture", "decision"])')
|
|
45833
|
-
|
|
45724
|
+
tags: exports_external.optional(exports_external.array(exports_external.string())).describe('Optional tags for categorization (e.g., ["preference", "architecture", "decision"])'),
|
|
45725
|
+
source: exports_external.optional(exports_external.enum(MEMORY_SOURCES)).describe('Where this memory originated (e.g., "channel:whatsapp", "conversation:assistant")')
|
|
45726
|
+
}, async ({ content, tags, source }) => {
|
|
45834
45727
|
try {
|
|
45835
|
-
const result = await client.storeMemory({ content, tags });
|
|
45728
|
+
const result = await client.storeMemory({ content, tags, source });
|
|
45836
45729
|
return formatSuccess(result);
|
|
45837
45730
|
} catch (err) {
|
|
45838
45731
|
return handleToolError(err);
|
|
@@ -45854,6 +45747,7 @@ var registerMemoryTools = (server, client) => {
|
|
|
45854
45747
|
var init_memory = __esm(() => {
|
|
45855
45748
|
init_zod2();
|
|
45856
45749
|
init_utils();
|
|
45750
|
+
init_types5();
|
|
45857
45751
|
});
|
|
45858
45752
|
|
|
45859
45753
|
// cli/src/mcp/tools/index.ts
|
|
@@ -45869,7 +45763,7 @@ function registerTools(server, client) {
|
|
|
45869
45763
|
registerSettingsTools(server, client);
|
|
45870
45764
|
registerBackupTools(server, client);
|
|
45871
45765
|
registerEmailTools(server, client);
|
|
45872
|
-
|
|
45766
|
+
registerAssistantTools(server, client);
|
|
45873
45767
|
registerCaldavTools(server, client);
|
|
45874
45768
|
registerMemoryTools(server, client);
|
|
45875
45769
|
}
|
|
@@ -45885,7 +45779,7 @@ var init_tools = __esm(() => {
|
|
|
45885
45779
|
init_settings();
|
|
45886
45780
|
init_backup();
|
|
45887
45781
|
init_email();
|
|
45888
|
-
|
|
45782
|
+
init_assistant();
|
|
45889
45783
|
init_caldav();
|
|
45890
45784
|
init_memory();
|
|
45891
45785
|
init_types4();
|
|
@@ -45906,7 +45800,7 @@ async function runMcpServer(urlOverride, portOverride) {
|
|
|
45906
45800
|
const client = new FulcrumClient(urlOverride, portOverride);
|
|
45907
45801
|
const server = new McpServer({
|
|
45908
45802
|
name: "fulcrum",
|
|
45909
|
-
version: "2.
|
|
45803
|
+
version: "2.9.0"
|
|
45910
45804
|
});
|
|
45911
45805
|
registerTools(server, client);
|
|
45912
45806
|
const transport = new StdioServerTransport;
|
|
@@ -48255,7 +48149,7 @@ var marketplace_default = `{
|
|
|
48255
48149
|
"name": "fulcrum",
|
|
48256
48150
|
"source": "./",
|
|
48257
48151
|
"description": "Task orchestration for Claude Code",
|
|
48258
|
-
"version": "2.
|
|
48152
|
+
"version": "2.9.0",
|
|
48259
48153
|
"skills": [
|
|
48260
48154
|
"./skills/fulcrum"
|
|
48261
48155
|
],
|
|
@@ -49443,7 +49337,7 @@ function compareVersions(v1, v2) {
|
|
|
49443
49337
|
var package_default = {
|
|
49444
49338
|
name: "@knowsuchagency/fulcrum",
|
|
49445
49339
|
private: true,
|
|
49446
|
-
version: "2.
|
|
49340
|
+
version: "2.9.0",
|
|
49447
49341
|
description: "Harness Attention. Orchestrate Agents. Ship.",
|
|
49448
49342
|
license: "PolyForm-Perimeter-1.0.0",
|
|
49449
49343
|
type: "module",
|