@knowsuchagency/fulcrum 2.7.1 → 2.8.2
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 +12 -0
- package/bin/fulcrum.js +95 -4
- package/dist/assets/{index-_cFoHsjb.js → index-BfBfuxBH.js} +165 -165
- package/dist/assets/index-TyeuSbkG.css +1 -0
- package/dist/index.html +2 -2
- package/drizzle/0052_agent_memory.sql +27 -0
- package/drizzle/0053_add_memory_source.sql +1 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +1 -1
- package/server/index.js +341 -47
- package/dist/assets/index-Bajq2D5F.css +0 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
CREATE TABLE `memories` (
|
|
2
|
+
`id` text PRIMARY KEY NOT NULL,
|
|
3
|
+
`content` text NOT NULL,
|
|
4
|
+
`tags` text,
|
|
5
|
+
`created_at` text NOT NULL,
|
|
6
|
+
`updated_at` text NOT NULL
|
|
7
|
+
);--> statement-breakpoint
|
|
8
|
+
CREATE VIRTUAL TABLE `memories_fts` USING fts5(
|
|
9
|
+
content,
|
|
10
|
+
tags,
|
|
11
|
+
content=memories,
|
|
12
|
+
content_rowid=rowid
|
|
13
|
+
);--> statement-breakpoint
|
|
14
|
+
CREATE TRIGGER memories_ai AFTER INSERT ON `memories` BEGIN
|
|
15
|
+
INSERT INTO memories_fts(rowid, content, tags)
|
|
16
|
+
VALUES (new.rowid, new.content, new.tags);
|
|
17
|
+
END;--> statement-breakpoint
|
|
18
|
+
CREATE TRIGGER memories_ad AFTER DELETE ON `memories` BEGIN
|
|
19
|
+
INSERT INTO memories_fts(memories_fts, rowid, content, tags)
|
|
20
|
+
VALUES ('delete', old.rowid, old.content, old.tags);
|
|
21
|
+
END;--> statement-breakpoint
|
|
22
|
+
CREATE TRIGGER memories_au AFTER UPDATE ON `memories` BEGIN
|
|
23
|
+
INSERT INTO memories_fts(memories_fts, rowid, content, tags)
|
|
24
|
+
VALUES ('delete', old.rowid, old.content, old.tags);
|
|
25
|
+
INSERT INTO memories_fts(rowid, content, tags)
|
|
26
|
+
VALUES (new.rowid, new.content, new.tags);
|
|
27
|
+
END;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE `memories` ADD `source` text;
|
|
@@ -365,6 +365,20 @@
|
|
|
365
365
|
"when": 1770548000000,
|
|
366
366
|
"tag": "0051_caldav_tables",
|
|
367
367
|
"breakpoints": true
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"idx": 52,
|
|
371
|
+
"version": "6",
|
|
372
|
+
"when": 1770634400000,
|
|
373
|
+
"tag": "0052_agent_memory",
|
|
374
|
+
"breakpoints": true
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"idx": 53,
|
|
378
|
+
"version": "6",
|
|
379
|
+
"when": 1770720800000,
|
|
380
|
+
"tag": "0053_add_memory_source",
|
|
381
|
+
"breakpoints": true
|
|
368
382
|
}
|
|
369
383
|
]
|
|
370
384
|
}
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -4368,6 +4368,7 @@ __export(exports_schema, {
|
|
|
4368
4368
|
projectAttachments: () => projectAttachments,
|
|
4369
4369
|
messagingSessionMappings: () => messagingSessionMappings,
|
|
4370
4370
|
messagingConnections: () => messagingConnections,
|
|
4371
|
+
memories: () => memories,
|
|
4371
4372
|
emailAuthorizedThreads: () => emailAuthorizedThreads,
|
|
4372
4373
|
deployments: () => deployments,
|
|
4373
4374
|
chatSessions: () => chatSessions,
|
|
@@ -4380,7 +4381,7 @@ __export(exports_schema, {
|
|
|
4380
4381
|
appServices: () => appServices,
|
|
4381
4382
|
actionableEvents: () => actionableEvents
|
|
4382
4383
|
});
|
|
4383
|
-
var tasks, taskRelationships, taskDependencies, taskLinks, taskAttachments, projectLinks, projectAttachments, terminalTabs, terminals, terminalViewState, repositories, apps, appServices, deployments, tunnels, projects, projectRepositories, tags, taskTags, projectTags, chatSessions, chatMessages, artifacts, systemMetrics, messagingConnections, messagingSessionMappings, emailAuthorizedThreads, channelMessages, actionableEvents, sweepRuns, caldavCalendars, caldavEvents;
|
|
4384
|
+
var tasks, taskRelationships, taskDependencies, taskLinks, taskAttachments, projectLinks, projectAttachments, terminalTabs, terminals, terminalViewState, repositories, apps, appServices, deployments, tunnels, projects, projectRepositories, tags, taskTags, projectTags, chatSessions, chatMessages, artifacts, systemMetrics, messagingConnections, messagingSessionMappings, emailAuthorizedThreads, channelMessages, actionableEvents, sweepRuns, caldavCalendars, caldavEvents, memories;
|
|
4384
4385
|
var init_schema = __esm(() => {
|
|
4385
4386
|
init_sqlite_core();
|
|
4386
4387
|
tasks = sqliteTable("tasks", {
|
|
@@ -4747,6 +4748,14 @@ var init_schema = __esm(() => {
|
|
|
4747
4748
|
createdAt: text("created_at").notNull(),
|
|
4748
4749
|
updatedAt: text("updated_at").notNull()
|
|
4749
4750
|
});
|
|
4751
|
+
memories = sqliteTable("memories", {
|
|
4752
|
+
id: text("id").primaryKey(),
|
|
4753
|
+
content: text("content").notNull(),
|
|
4754
|
+
tags: text("tags"),
|
|
4755
|
+
source: text("source"),
|
|
4756
|
+
createdAt: text("created_at").notNull(),
|
|
4757
|
+
updatedAt: text("updated_at").notNull()
|
|
4758
|
+
});
|
|
4750
4759
|
});
|
|
4751
4760
|
|
|
4752
4761
|
// server/lib/settings/types.ts
|
|
@@ -70943,7 +70952,13 @@ function getDataModel() {
|
|
|
70943
70952
|
- Calendars with display name, color, sync state
|
|
70944
70953
|
- Events with summary, start/end times, location, all-day flag
|
|
70945
70954
|
- Timezone-aware storage and display
|
|
70946
|
-
- Used to give the assistant schedule awareness for planning
|
|
70955
|
+
- Used to give the assistant schedule awareness for planning
|
|
70956
|
+
|
|
70957
|
+
**Memories** - Persistent agent knowledge store
|
|
70958
|
+
- Content with optional tags for categorization
|
|
70959
|
+
- SQLite FTS5 full-text search (boolean operators, phrase matching, prefix queries)
|
|
70960
|
+
- Used to remember facts, preferences, decisions, and patterns across conversations
|
|
70961
|
+
- Browsable via Monitoring > Memory tab in the UI`;
|
|
70947
70962
|
}
|
|
70948
70963
|
function getMcpToolCapabilities() {
|
|
70949
70964
|
return `## Available MCP Tools
|
|
@@ -71021,6 +71036,10 @@ You have access to Fulcrum's MCP tools. Use them proactively to help users.
|
|
|
71021
71036
|
- \`get_assistant_stats\` - Get event counts and last sweep times
|
|
71022
71037
|
- \`get_last_sweep\` - Check when last sweep ran
|
|
71023
71038
|
|
|
71039
|
+
**Memory Tools:**
|
|
71040
|
+
- \`memory_store\` - Store a piece of knowledge in persistent memory with optional tags
|
|
71041
|
+
- \`memory_search\` - Search memories using FTS5 full-text search (supports AND, OR, NOT, "phrases", prefix*)
|
|
71042
|
+
|
|
71024
71043
|
**Utilities:**
|
|
71025
71044
|
- \`list_tags\` - See all tags in use
|
|
71026
71045
|
- \`get_task_dependency_graph\` - Visualize task dependencies
|
|
@@ -71205,6 +71224,7 @@ You can read and modify all Fulcrum settings using the settings MCP tools. Setti
|
|
|
71205
71224
|
- \`assistant.model\` - Model tier: 'opus', 'sonnet', 'haiku'
|
|
71206
71225
|
- \`assistant.customInstructions\` - Custom system prompt additions
|
|
71207
71226
|
- \`assistant.documentsDir\` - Directory for assistant documents
|
|
71227
|
+
- \`assistant.observerModel\` - Model for observe-only messages (non-self WhatsApp, unauthorized emails), e.g., 'haiku' for cost savings
|
|
71208
71228
|
- \`assistant.ritualsEnabled\` - Enable/disable daily rituals (morning/evening briefings)
|
|
71209
71229
|
- \`assistant.morningRitual.time\` - Time for morning ritual (24h format, e.g., "09:00")
|
|
71210
71230
|
- \`assistant.morningRitual.prompt\` - Custom prompt for morning ritual
|
|
@@ -71322,12 +71342,14 @@ Fulcrum is your digital concierge - a personal command center where you track ev
|
|
|
71322
71342
|
- Deploying apps with Docker Compose
|
|
71323
71343
|
- Sending notifications to Slack, Discord, Pushover
|
|
71324
71344
|
- Calendar awareness via CalDAV sync (Google Calendar, etc.)
|
|
71345
|
+
- Persistent memory across conversations (store and search knowledge)
|
|
71325
71346
|
|
|
71326
71347
|
**Key tools available:**
|
|
71327
71348
|
- list_tasks, create_task, update_task, move_task
|
|
71328
71349
|
- list_projects, create_project
|
|
71329
71350
|
- execute_command (run any CLI command)
|
|
71330
71351
|
- send_notification
|
|
71352
|
+
- memory_store, memory_search (persistent knowledge across sessions)
|
|
71331
71353
|
- message (send to email/WhatsApp - concierge mode)
|
|
71332
71354
|
- create_actionable_event, list_actionable_events (track decisions - concierge mode)
|
|
71333
71355
|
|
|
@@ -463771,7 +463793,8 @@ var ToolCategorySchema = exports_external.enum([
|
|
|
463771
463793
|
"email",
|
|
463772
463794
|
"messaging",
|
|
463773
463795
|
"assistant",
|
|
463774
|
-
"caldav"
|
|
463796
|
+
"caldav",
|
|
463797
|
+
"memory"
|
|
463775
463798
|
]);
|
|
463776
463799
|
var AgentTypeSchema = exports_external.enum(["claude", "opencode"]);
|
|
463777
463800
|
|
|
@@ -464538,6 +464561,20 @@ var toolRegistry = [
|
|
|
464538
464561
|
category: "caldav",
|
|
464539
464562
|
keywords: ["calendar", "event", "delete", "remove", "cancel"],
|
|
464540
464563
|
defer_loading: true
|
|
464564
|
+
},
|
|
464565
|
+
{
|
|
464566
|
+
name: "memory_store",
|
|
464567
|
+
description: "Store a piece of knowledge in persistent memory",
|
|
464568
|
+
category: "memory",
|
|
464569
|
+
keywords: ["memory", "store", "save", "remember", "knowledge", "persist", "fact"],
|
|
464570
|
+
defer_loading: false
|
|
464571
|
+
},
|
|
464572
|
+
{
|
|
464573
|
+
name: "memory_search",
|
|
464574
|
+
description: "Search persistent memory using full-text search",
|
|
464575
|
+
category: "memory",
|
|
464576
|
+
keywords: ["memory", "search", "find", "recall", "knowledge", "retrieve", "remember"],
|
|
464577
|
+
defer_loading: false
|
|
464541
464578
|
}
|
|
464542
464579
|
];
|
|
464543
464580
|
function searchTools(query) {
|
|
@@ -466103,6 +466140,44 @@ var registerCaldavTools = (server, client) => {
|
|
|
466103
466140
|
});
|
|
466104
466141
|
};
|
|
466105
466142
|
|
|
466143
|
+
// shared/types.ts
|
|
466144
|
+
var MEMORY_SOURCES = [
|
|
466145
|
+
"channel:whatsapp",
|
|
466146
|
+
"channel:slack",
|
|
466147
|
+
"channel:discord",
|
|
466148
|
+
"channel:telegram",
|
|
466149
|
+
"channel:email",
|
|
466150
|
+
"conversation:assistant"
|
|
466151
|
+
];
|
|
466152
|
+
|
|
466153
|
+
// cli/src/mcp/tools/memory.ts
|
|
466154
|
+
var registerMemoryTools = (server, client) => {
|
|
466155
|
+
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.", {
|
|
466156
|
+
content: exports_external.string().describe("The memory content to store. Be specific and self-contained."),
|
|
466157
|
+
tags: exports_external.optional(exports_external.array(exports_external.string())).describe('Optional tags for categorization (e.g., ["preference", "architecture", "decision"])'),
|
|
466158
|
+
source: exports_external.optional(exports_external.enum(MEMORY_SOURCES)).describe('Where this memory originated (e.g., "channel:whatsapp", "conversation:assistant")')
|
|
466159
|
+
}, async ({ content, tags: tags2, source }) => {
|
|
466160
|
+
try {
|
|
466161
|
+
const result = await client.storeMemory({ content, tags: tags2, source });
|
|
466162
|
+
return formatSuccess(result);
|
|
466163
|
+
} catch (err) {
|
|
466164
|
+
return handleToolError(err);
|
|
466165
|
+
}
|
|
466166
|
+
});
|
|
466167
|
+
server.tool("memory_search", 'Search persistent memory using full-text search. Supports boolean operators (AND, OR, NOT), phrase matching ("quoted phrases"), and prefix matching (term*). Try different search terms or synonyms if initial results are insufficient.', {
|
|
466168
|
+
query: exports_external.string().describe('FTS5 search query. Supports: AND, OR, NOT operators, "quoted phrases", prefix* matching. Example: "user preference" OR settings'),
|
|
466169
|
+
tags: exports_external.optional(exports_external.array(exports_external.string())).describe("Optional tag filter - only return memories with at least one of these tags"),
|
|
466170
|
+
limit: exports_external.optional(exports_external.number()).describe("Maximum results to return (default: 20)")
|
|
466171
|
+
}, async ({ query, tags: tags2, limit: limit2 }) => {
|
|
466172
|
+
try {
|
|
466173
|
+
const results = await client.searchMemories({ query, tags: tags2, limit: limit2 });
|
|
466174
|
+
return formatSuccess(results);
|
|
466175
|
+
} catch (err) {
|
|
466176
|
+
return handleToolError(err);
|
|
466177
|
+
}
|
|
466178
|
+
});
|
|
466179
|
+
};
|
|
466180
|
+
|
|
466106
466181
|
// cli/src/mcp/tools/index.ts
|
|
466107
466182
|
function registerTools(server, client) {
|
|
466108
466183
|
registerCoreTools(server, client);
|
|
@@ -466118,6 +466193,7 @@ function registerTools(server, client) {
|
|
|
466118
466193
|
registerEmailTools(server, client);
|
|
466119
466194
|
registerAssistantEventTools(server, client);
|
|
466120
466195
|
registerCaldavTools(server, client);
|
|
466196
|
+
registerMemoryTools(server, client);
|
|
466121
466197
|
}
|
|
466122
466198
|
// cli/src/utils/server.ts
|
|
466123
466199
|
import { existsSync as existsSync27, readFileSync as readFileSync15, writeFileSync as writeFileSync12, mkdirSync as mkdirSync10, cpSync } from "fs";
|
|
@@ -466802,6 +466878,34 @@ class FulcrumClient {
|
|
|
466802
466878
|
method: "DELETE"
|
|
466803
466879
|
});
|
|
466804
466880
|
}
|
|
466881
|
+
async storeMemory(input) {
|
|
466882
|
+
return this.fetch("/api/memory", {
|
|
466883
|
+
method: "POST",
|
|
466884
|
+
body: JSON.stringify(input)
|
|
466885
|
+
});
|
|
466886
|
+
}
|
|
466887
|
+
async searchMemories(input) {
|
|
466888
|
+
const params = new URLSearchParams({ q: input.query });
|
|
466889
|
+
if (input.tags?.length)
|
|
466890
|
+
params.set("tags", input.tags.join(","));
|
|
466891
|
+
if (input.limit)
|
|
466892
|
+
params.set("limit", String(input.limit));
|
|
466893
|
+
return this.fetch(`/api/memory/search?${params.toString()}`);
|
|
466894
|
+
}
|
|
466895
|
+
async listMemories(input) {
|
|
466896
|
+
const params = new URLSearchParams;
|
|
466897
|
+
if (input?.tags?.length)
|
|
466898
|
+
params.set("tags", input.tags.join(","));
|
|
466899
|
+
if (input?.limit)
|
|
466900
|
+
params.set("limit", String(input.limit));
|
|
466901
|
+
if (input?.offset)
|
|
466902
|
+
params.set("offset", String(input.offset));
|
|
466903
|
+
const query = params.toString();
|
|
466904
|
+
return this.fetch(`/api/memory${query ? `?${query}` : ""}`);
|
|
466905
|
+
}
|
|
466906
|
+
async deleteMemory(id) {
|
|
466907
|
+
return this.fetch(`/api/memory/${id}`, { method: "DELETE" });
|
|
466908
|
+
}
|
|
466805
466909
|
async getAssistantStats() {
|
|
466806
466910
|
return this.fetch("/api/assistant/stats");
|
|
466807
466911
|
}
|
|
@@ -466818,7 +466922,7 @@ mcpRoutes.all("/", async (c) => {
|
|
|
466818
466922
|
});
|
|
466819
466923
|
const server = new McpServer({
|
|
466820
466924
|
name: "fulcrum",
|
|
466821
|
-
version: "2.
|
|
466925
|
+
version: "2.8.2"
|
|
466822
466926
|
});
|
|
466823
466927
|
const client = new FulcrumClient(`http://localhost:${port}`);
|
|
466824
466928
|
registerTools(server, client);
|
|
@@ -472623,6 +472727,195 @@ caldavRoutes.delete("/events/:id", async (c) => {
|
|
|
472623
472727
|
});
|
|
472624
472728
|
var caldav_default = caldavRoutes;
|
|
472625
472729
|
|
|
472730
|
+
// server/services/memory-service.ts
|
|
472731
|
+
init_db2();
|
|
472732
|
+
init_schema();
|
|
472733
|
+
init_drizzle_orm();
|
|
472734
|
+
init_nanoid();
|
|
472735
|
+
function parseTags(tagsJson) {
|
|
472736
|
+
if (!tagsJson)
|
|
472737
|
+
return null;
|
|
472738
|
+
try {
|
|
472739
|
+
return JSON.parse(tagsJson);
|
|
472740
|
+
} catch {
|
|
472741
|
+
return null;
|
|
472742
|
+
}
|
|
472743
|
+
}
|
|
472744
|
+
function toResult(row, rank) {
|
|
472745
|
+
return {
|
|
472746
|
+
id: row.id,
|
|
472747
|
+
content: row.content,
|
|
472748
|
+
tags: parseTags(row.tags),
|
|
472749
|
+
source: row.source,
|
|
472750
|
+
createdAt: row.createdAt,
|
|
472751
|
+
updatedAt: row.updatedAt,
|
|
472752
|
+
...rank !== undefined ? { rank } : {}
|
|
472753
|
+
};
|
|
472754
|
+
}
|
|
472755
|
+
async function storeMemory(input) {
|
|
472756
|
+
const now = new Date().toISOString();
|
|
472757
|
+
const id = nanoid();
|
|
472758
|
+
const tagsJson = input.tags?.length ? JSON.stringify(input.tags) : null;
|
|
472759
|
+
const [row] = await db2.insert(memories).values({
|
|
472760
|
+
id,
|
|
472761
|
+
content: input.content,
|
|
472762
|
+
tags: tagsJson,
|
|
472763
|
+
source: input.source ?? null,
|
|
472764
|
+
createdAt: now,
|
|
472765
|
+
updatedAt: now
|
|
472766
|
+
}).returning();
|
|
472767
|
+
return toResult(row);
|
|
472768
|
+
}
|
|
472769
|
+
async function searchMemories(input) {
|
|
472770
|
+
const limit2 = input.limit ?? 20;
|
|
472771
|
+
const ftsQuery = input.query;
|
|
472772
|
+
if (input.tags?.length) {
|
|
472773
|
+
const results2 = db2.all(sql`SELECT m.id, m.content, m.tags, m.source, m.created_at as "createdAt", m.updated_at as "updatedAt", bm25(memories_fts) as rank
|
|
472774
|
+
FROM memories_fts fts
|
|
472775
|
+
JOIN memories m ON m.rowid = fts.rowid
|
|
472776
|
+
WHERE memories_fts MATCH ${ftsQuery}
|
|
472777
|
+
AND EXISTS (
|
|
472778
|
+
SELECT 1 FROM json_each(m.tags) je
|
|
472779
|
+
WHERE je.value IN ${input.tags}
|
|
472780
|
+
)
|
|
472781
|
+
ORDER BY bm25(memories_fts)
|
|
472782
|
+
LIMIT ${limit2}`);
|
|
472783
|
+
return results2.map((r) => ({
|
|
472784
|
+
...r,
|
|
472785
|
+
tags: parseTags(r.tags)
|
|
472786
|
+
}));
|
|
472787
|
+
}
|
|
472788
|
+
const results = db2.all(sql`SELECT m.id, m.content, m.tags, m.source, m.created_at as "createdAt", m.updated_at as "updatedAt", bm25(memories_fts) as rank
|
|
472789
|
+
FROM memories_fts fts
|
|
472790
|
+
JOIN memories m ON m.rowid = fts.rowid
|
|
472791
|
+
WHERE memories_fts MATCH ${ftsQuery}
|
|
472792
|
+
ORDER BY bm25(memories_fts)
|
|
472793
|
+
LIMIT ${limit2}`);
|
|
472794
|
+
return results.map((r) => ({
|
|
472795
|
+
...r,
|
|
472796
|
+
tags: parseTags(r.tags)
|
|
472797
|
+
}));
|
|
472798
|
+
}
|
|
472799
|
+
async function updateMemory(id, input) {
|
|
472800
|
+
const now = new Date().toISOString();
|
|
472801
|
+
const updates = { updatedAt: now };
|
|
472802
|
+
if (input.content !== undefined)
|
|
472803
|
+
updates.content = input.content;
|
|
472804
|
+
if (input.tags !== undefined)
|
|
472805
|
+
updates.tags = input.tags?.length ? JSON.stringify(input.tags) : null;
|
|
472806
|
+
const [row] = await db2.update(memories).set({
|
|
472807
|
+
...input.content !== undefined ? { content: input.content } : {},
|
|
472808
|
+
...input.tags !== undefined ? { tags: input.tags?.length ? JSON.stringify(input.tags) : null } : {},
|
|
472809
|
+
...input.source !== undefined ? { source: input.source } : {},
|
|
472810
|
+
updatedAt: now
|
|
472811
|
+
}).where(eq(memories.id, id)).returning();
|
|
472812
|
+
if (!row)
|
|
472813
|
+
return null;
|
|
472814
|
+
return toResult(row);
|
|
472815
|
+
}
|
|
472816
|
+
async function deleteMemory(id) {
|
|
472817
|
+
const result = await db2.delete(memories).where(eq(memories.id, id)).returning();
|
|
472818
|
+
return result.length > 0;
|
|
472819
|
+
}
|
|
472820
|
+
async function listMemories(input = {}) {
|
|
472821
|
+
const limit2 = input.limit ?? 50;
|
|
472822
|
+
const offset = input.offset ?? 0;
|
|
472823
|
+
if (input.tags?.length) {
|
|
472824
|
+
const rows2 = db2.all(sql`SELECT m.id, m.content, m.tags, m.source, m.created_at as "createdAt", m.updated_at as "updatedAt"
|
|
472825
|
+
FROM memories m
|
|
472826
|
+
WHERE EXISTS (
|
|
472827
|
+
SELECT 1 FROM json_each(m.tags) je
|
|
472828
|
+
WHERE je.value IN ${input.tags}
|
|
472829
|
+
)
|
|
472830
|
+
ORDER BY m.created_at DESC
|
|
472831
|
+
LIMIT ${limit2} OFFSET ${offset}`);
|
|
472832
|
+
const [countResult2] = db2.all(sql`SELECT COUNT(*) as count FROM memories m
|
|
472833
|
+
WHERE EXISTS (
|
|
472834
|
+
SELECT 1 FROM json_each(m.tags) je
|
|
472835
|
+
WHERE je.value IN ${input.tags}
|
|
472836
|
+
)`);
|
|
472837
|
+
return {
|
|
472838
|
+
memories: rows2.map((r) => ({
|
|
472839
|
+
...r,
|
|
472840
|
+
tags: parseTags(r.tags)
|
|
472841
|
+
})),
|
|
472842
|
+
total: countResult2.count
|
|
472843
|
+
};
|
|
472844
|
+
}
|
|
472845
|
+
const rows = await db2.select().from(memories).orderBy(desc(memories.createdAt)).limit(limit2).offset(offset);
|
|
472846
|
+
const [countResult] = db2.all(sql`SELECT COUNT(*) as count FROM memories`);
|
|
472847
|
+
return {
|
|
472848
|
+
memories: rows.map((r) => toResult(r)),
|
|
472849
|
+
total: countResult.count
|
|
472850
|
+
};
|
|
472851
|
+
}
|
|
472852
|
+
|
|
472853
|
+
// server/routes/memory.ts
|
|
472854
|
+
var app25 = new Hono2;
|
|
472855
|
+
app25.post("/", async (c) => {
|
|
472856
|
+
const body = await c.req.json();
|
|
472857
|
+
if (!body.content?.trim()) {
|
|
472858
|
+
return c.json({ error: "content is required" }, 400);
|
|
472859
|
+
}
|
|
472860
|
+
const memory = await storeMemory({
|
|
472861
|
+
content: body.content.trim(),
|
|
472862
|
+
tags: body.tags,
|
|
472863
|
+
source: body.source
|
|
472864
|
+
});
|
|
472865
|
+
return c.json(memory, 201);
|
|
472866
|
+
});
|
|
472867
|
+
app25.get("/search", async (c) => {
|
|
472868
|
+
const query2 = c.req.query("q");
|
|
472869
|
+
if (!query2?.trim()) {
|
|
472870
|
+
return c.json({ error: "q query parameter is required" }, 400);
|
|
472871
|
+
}
|
|
472872
|
+
const tagsParam = c.req.query("tags");
|
|
472873
|
+
const tags2 = tagsParam ? tagsParam.split(",").map((t) => t.trim()).filter(Boolean) : undefined;
|
|
472874
|
+
const limitParam = c.req.query("limit");
|
|
472875
|
+
const limit2 = limitParam ? parseInt(limitParam, 10) : undefined;
|
|
472876
|
+
const results = await searchMemories({
|
|
472877
|
+
query: query2.trim(),
|
|
472878
|
+
tags: tags2,
|
|
472879
|
+
limit: limit2
|
|
472880
|
+
});
|
|
472881
|
+
return c.json(results);
|
|
472882
|
+
});
|
|
472883
|
+
app25.get("/", async (c) => {
|
|
472884
|
+
const tagsParam = c.req.query("tags");
|
|
472885
|
+
const tags2 = tagsParam ? tagsParam.split(",").map((t) => t.trim()).filter(Boolean) : undefined;
|
|
472886
|
+
const limitParam = c.req.query("limit");
|
|
472887
|
+
const limit2 = limitParam ? parseInt(limitParam, 10) : undefined;
|
|
472888
|
+
const offsetParam = c.req.query("offset");
|
|
472889
|
+
const offset = offsetParam ? parseInt(offsetParam, 10) : undefined;
|
|
472890
|
+
const result = await listMemories({ tags: tags2, limit: limit2, offset });
|
|
472891
|
+
return c.json(result);
|
|
472892
|
+
});
|
|
472893
|
+
app25.patch("/:id", async (c) => {
|
|
472894
|
+
const id = c.req.param("id");
|
|
472895
|
+
const body = await c.req.json();
|
|
472896
|
+
if (body.content !== undefined && !body.content.trim()) {
|
|
472897
|
+
return c.json({ error: "content cannot be empty" }, 400);
|
|
472898
|
+
}
|
|
472899
|
+
const updated = await updateMemory(id, {
|
|
472900
|
+
content: body.content?.trim(),
|
|
472901
|
+
tags: body.tags,
|
|
472902
|
+
source: body.source
|
|
472903
|
+
});
|
|
472904
|
+
if (!updated) {
|
|
472905
|
+
return c.json({ error: "Memory not found" }, 404);
|
|
472906
|
+
}
|
|
472907
|
+
return c.json(updated);
|
|
472908
|
+
});
|
|
472909
|
+
app25.delete("/:id", async (c) => {
|
|
472910
|
+
const id = c.req.param("id");
|
|
472911
|
+
const deleted = await deleteMemory(id);
|
|
472912
|
+
if (!deleted) {
|
|
472913
|
+
return c.json({ error: "Memory not found" }, 404);
|
|
472914
|
+
}
|
|
472915
|
+
return c.json({ success: true });
|
|
472916
|
+
});
|
|
472917
|
+
var memory_default = app25;
|
|
472918
|
+
|
|
472626
472919
|
// server/app.ts
|
|
472627
472920
|
init_logger3();
|
|
472628
472921
|
function getDistPath() {
|
|
@@ -472632,51 +472925,52 @@ function getDistPath() {
|
|
|
472632
472925
|
return join40(process.cwd(), "dist");
|
|
472633
472926
|
}
|
|
472634
472927
|
function createApp() {
|
|
472635
|
-
const
|
|
472636
|
-
|
|
472637
|
-
|
|
472928
|
+
const app26 = new Hono2;
|
|
472929
|
+
app26.use("*", logger());
|
|
472930
|
+
app26.use("*", cors({
|
|
472638
472931
|
origin: "*",
|
|
472639
472932
|
allowMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
|
|
472640
472933
|
allowHeaders: ["Content-Type", "mcp-session-id", "Last-Event-ID", "mcp-protocol-version"],
|
|
472641
472934
|
exposeHeaders: ["mcp-session-id", "mcp-protocol-version"]
|
|
472642
472935
|
}));
|
|
472643
|
-
|
|
472644
|
-
|
|
472645
|
-
|
|
472646
|
-
|
|
472647
|
-
|
|
472648
|
-
|
|
472649
|
-
|
|
472650
|
-
|
|
472651
|
-
|
|
472652
|
-
|
|
472653
|
-
|
|
472654
|
-
|
|
472655
|
-
|
|
472656
|
-
|
|
472657
|
-
|
|
472658
|
-
|
|
472659
|
-
|
|
472660
|
-
|
|
472661
|
-
|
|
472662
|
-
|
|
472663
|
-
|
|
472664
|
-
|
|
472665
|
-
|
|
472666
|
-
|
|
472667
|
-
|
|
472668
|
-
|
|
472669
|
-
|
|
472670
|
-
|
|
472671
|
-
|
|
472672
|
-
|
|
472936
|
+
app26.route("/health", health_default);
|
|
472937
|
+
app26.route("/api/tasks", tasks_default);
|
|
472938
|
+
app26.route("/api/git", git_default);
|
|
472939
|
+
app26.route("/api/fs", filesystem_default);
|
|
472940
|
+
app26.route("/api/config", config_default);
|
|
472941
|
+
app26.route("/api/uploads", uploads_default);
|
|
472942
|
+
app26.route("/api/worktrees", worktrees_default);
|
|
472943
|
+
app26.route("/api/terminal-view-state", terminal_view_state_default);
|
|
472944
|
+
app26.route("/api/repositories", repositories_default);
|
|
472945
|
+
app26.route("/api/copier", copier_default);
|
|
472946
|
+
app26.route("/api/github", github_default);
|
|
472947
|
+
app26.route("/api/monitoring", monitoringRoutes);
|
|
472948
|
+
app26.route("/api/system", system_default);
|
|
472949
|
+
app26.route("/api/exec", exec_default);
|
|
472950
|
+
app26.route("/api/apps", apps_default);
|
|
472951
|
+
app26.route("/api/compose", compose_default);
|
|
472952
|
+
app26.route("/api/deployment", deployment_default);
|
|
472953
|
+
app26.route("/api/jobs", jobs_default);
|
|
472954
|
+
app26.route("/api/opencode", opencode_default);
|
|
472955
|
+
app26.route("/api/projects", projects_default);
|
|
472956
|
+
app26.route("/api/task-dependencies", task_dependencies_default);
|
|
472957
|
+
app26.route("/api/tags", tags_default);
|
|
472958
|
+
app26.route("/api/version", version_default);
|
|
472959
|
+
app26.route("/mcp", mcp_default);
|
|
472960
|
+
app26.route("/api/chat", chat_default);
|
|
472961
|
+
app26.route("/api/assistant", assistant_default);
|
|
472962
|
+
app26.route("/api/messaging", messaging_default);
|
|
472963
|
+
app26.route("/api/backup", backup_default);
|
|
472964
|
+
app26.route("/api/caldav", caldav_default);
|
|
472965
|
+
app26.route("/api/memory", memory_default);
|
|
472966
|
+
app26.post("/api/logs", async (c) => {
|
|
472673
472967
|
const { entries } = await c.req.json();
|
|
472674
472968
|
for (const entry of entries) {
|
|
472675
472969
|
writeEntry(entry);
|
|
472676
472970
|
}
|
|
472677
472971
|
return c.json({ ok: true });
|
|
472678
472972
|
});
|
|
472679
|
-
|
|
472973
|
+
app26.post("/api/debug", async (c) => {
|
|
472680
472974
|
const body = await c.req.json();
|
|
472681
472975
|
const entry = {
|
|
472682
472976
|
ts: new Date().toISOString(),
|
|
@@ -472717,14 +473011,14 @@ function createApp() {
|
|
|
472717
473011
|
}
|
|
472718
473012
|
});
|
|
472719
473013
|
};
|
|
472720
|
-
|
|
473014
|
+
app26.get("/assets/*", async (c) => {
|
|
472721
473015
|
const assetPath = join40(distPath, c.req.path);
|
|
472722
473016
|
if (existsSync34(assetPath)) {
|
|
472723
473017
|
return serveFile(assetPath, true);
|
|
472724
473018
|
}
|
|
472725
473019
|
return c.notFound();
|
|
472726
473020
|
});
|
|
472727
|
-
|
|
473021
|
+
app26.get("/sounds/*", async (c) => {
|
|
472728
473022
|
const soundPath = join40(distPath, c.req.path);
|
|
472729
473023
|
if (existsSync34(soundPath)) {
|
|
472730
473024
|
return serveFile(soundPath);
|
|
@@ -472733,7 +473027,7 @@ function createApp() {
|
|
|
472733
473027
|
});
|
|
472734
473028
|
const staticFiles = ["fulcrum-icon.png", "fulcrum-logo.jpeg", "vite.svg", "logo.png", "goat.jpeg"];
|
|
472735
473029
|
for (const file2 of staticFiles) {
|
|
472736
|
-
|
|
473030
|
+
app26.get(`/${file2}`, async () => {
|
|
472737
473031
|
const filePath = join40(distPath, file2);
|
|
472738
473032
|
if (existsSync34(filePath)) {
|
|
472739
473033
|
return serveFile(filePath);
|
|
@@ -472741,7 +473035,7 @@ function createApp() {
|
|
|
472741
473035
|
return new Response("Not Found", { status: 404 });
|
|
472742
473036
|
});
|
|
472743
473037
|
}
|
|
472744
|
-
|
|
473038
|
+
app26.get("*", async (c, next) => {
|
|
472745
473039
|
const path15 = c.req.path;
|
|
472746
473040
|
if (path15.startsWith("/api/") || path15.startsWith("/ws/") || path15 === "/health") {
|
|
472747
473041
|
return next();
|
|
@@ -472752,7 +473046,7 @@ function createApp() {
|
|
|
472752
473046
|
});
|
|
472753
473047
|
});
|
|
472754
473048
|
}
|
|
472755
|
-
return
|
|
473049
|
+
return app26;
|
|
472756
473050
|
}
|
|
472757
473051
|
|
|
472758
473052
|
// server/index.ts
|
|
@@ -473109,11 +473403,11 @@ setBroadcastDestroyed((terminalId) => {
|
|
|
473109
473403
|
payload: { terminalId }
|
|
473110
473404
|
});
|
|
473111
473405
|
});
|
|
473112
|
-
var
|
|
473113
|
-
var { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app:
|
|
473114
|
-
|
|
473406
|
+
var app26 = createApp();
|
|
473407
|
+
var { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: app26 });
|
|
473408
|
+
app26.get("/ws/terminal", upgradeWebSocket(() => terminalWebSocketHandlers));
|
|
473115
473409
|
var server2 = serve({
|
|
473116
|
-
fetch:
|
|
473410
|
+
fetch: app26.fetch,
|
|
473117
473411
|
port: PORT,
|
|
473118
473412
|
hostname: HOST
|
|
473119
473413
|
}, (info) => {
|