@knowsuchagency/fulcrum 2.8.2 → 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 +32 -153
- package/dist/assets/{index-TyeuSbkG.css → index-Lj1lLJpw.css} +1 -1
- package/dist/assets/{index-BfBfuxBH.js → index-dMSLbxmc.js} +166 -165
- package/dist/index.html +2 -2
- package/drizzle/0054_remove_actionable_events.sql +1 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/server/index.js +242 -541
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
|
|
|
@@ -45884,7 +45763,7 @@ function registerTools(server, client) {
|
|
|
45884
45763
|
registerSettingsTools(server, client);
|
|
45885
45764
|
registerBackupTools(server, client);
|
|
45886
45765
|
registerEmailTools(server, client);
|
|
45887
|
-
|
|
45766
|
+
registerAssistantTools(server, client);
|
|
45888
45767
|
registerCaldavTools(server, client);
|
|
45889
45768
|
registerMemoryTools(server, client);
|
|
45890
45769
|
}
|
|
@@ -45900,7 +45779,7 @@ var init_tools = __esm(() => {
|
|
|
45900
45779
|
init_settings();
|
|
45901
45780
|
init_backup();
|
|
45902
45781
|
init_email();
|
|
45903
|
-
|
|
45782
|
+
init_assistant();
|
|
45904
45783
|
init_caldav();
|
|
45905
45784
|
init_memory();
|
|
45906
45785
|
init_types4();
|
|
@@ -45921,7 +45800,7 @@ async function runMcpServer(urlOverride, portOverride) {
|
|
|
45921
45800
|
const client = new FulcrumClient(urlOverride, portOverride);
|
|
45922
45801
|
const server = new McpServer({
|
|
45923
45802
|
name: "fulcrum",
|
|
45924
|
-
version: "2.
|
|
45803
|
+
version: "2.9.0"
|
|
45925
45804
|
});
|
|
45926
45805
|
registerTools(server, client);
|
|
45927
45806
|
const transport = new StdioServerTransport;
|
|
@@ -48270,7 +48149,7 @@ var marketplace_default = `{
|
|
|
48270
48149
|
"name": "fulcrum",
|
|
48271
48150
|
"source": "./",
|
|
48272
48151
|
"description": "Task orchestration for Claude Code",
|
|
48273
|
-
"version": "2.
|
|
48152
|
+
"version": "2.9.0",
|
|
48274
48153
|
"skills": [
|
|
48275
48154
|
"./skills/fulcrum"
|
|
48276
48155
|
],
|
|
@@ -49458,7 +49337,7 @@ function compareVersions(v1, v2) {
|
|
|
49458
49337
|
var package_default = {
|
|
49459
49338
|
name: "@knowsuchagency/fulcrum",
|
|
49460
49339
|
private: true,
|
|
49461
|
-
version: "2.
|
|
49340
|
+
version: "2.9.0",
|
|
49462
49341
|
description: "Harness Attention. Orchestrate Agents. Ship.",
|
|
49463
49342
|
license: "PolyForm-Perimeter-1.0.0",
|
|
49464
49343
|
type: "module",
|