@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 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, tracks actionable events, creates daily plans, and sends morning/evening briefings automatically.
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) → Tracked as events, optionally creates 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
- ### Actionable Events
49
+ ### Persistent Memory
50
50
 
51
- Every important message becomes an **actionable event**—searchable, linkable to tasks, with a full decision audit trail. Your assistant remembers what it noticed and what it decided.
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 Events** | Track actionable events; query decision history |
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-events.ts
45619
- var ActionableEventStatusSchema, ChannelSchema, registerAssistantEventTools = (server, client) => {
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 init_assistant_events = __esm(() => {
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
- registerAssistantEventTools(server, client);
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
- init_assistant_events();
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.8.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.8.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.8.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",