@jcyamacho/agent-memory 0.0.12 → 0.0.13

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.
Files changed (2) hide show
  1. package/dist/index.js +376 -389
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12464,7 +12464,7 @@ class StdioServerTransport {
12464
12464
  }
12465
12465
  }
12466
12466
  // package.json
12467
- var version2 = "0.0.12";
12467
+ var version2 = "0.0.13";
12468
12468
 
12469
12469
  // src/config.ts
12470
12470
  import { homedir } from "node:os";
@@ -12472,7 +12472,7 @@ import { join } from "node:path";
12472
12472
  import { parseArgs } from "node:util";
12473
12473
  var AGENT_MEMORY_DB_PATH_ENV = "AGENT_MEMORY_DB_PATH";
12474
12474
  var DEFAULT_UI_PORT = 6580;
12475
- var resolveConfig = (environment = process.env, argv = process.argv.slice(2)) => {
12475
+ function resolveConfig(environment = process.env, argv = process.argv.slice(2)) {
12476
12476
  const { values } = parseArgs({
12477
12477
  args: argv,
12478
12478
  options: {
@@ -12486,7 +12486,7 @@ var resolveConfig = (environment = process.env, argv = process.argv.slice(2)) =>
12486
12486
  uiMode: Boolean(values.ui),
12487
12487
  uiPort: Number(values.port) || DEFAULT_UI_PORT
12488
12488
  };
12489
- };
12489
+ }
12490
12490
 
12491
12491
  // node_modules/zod/v3/helpers/util.js
12492
12492
  var util;
@@ -19926,8 +19926,8 @@ class PersistenceError extends MemoryError {
19926
19926
  }
19927
19927
  }
19928
19928
 
19929
- // src/tools/shared.ts
19930
- var toMcpError = (error2) => {
19929
+ // src/mcp/tools/shared.ts
19930
+ function toMcpError(error2) {
19931
19931
  if (error2 instanceof McpError) {
19932
19932
  return error2;
19933
19933
  }
@@ -19939,9 +19939,9 @@ var toMcpError = (error2) => {
19939
19939
  }
19940
19940
  const message = error2 instanceof Error ? error2.message : "Unknown server error.";
19941
19941
  return new McpError(ErrorCode.InternalError, message);
19942
- };
19942
+ }
19943
19943
  var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
19944
- var parseOptionalDate = (value, fieldName) => {
19944
+ function parseOptionalDate(value, fieldName) {
19945
19945
  if (!value) {
19946
19946
  return;
19947
19947
  }
@@ -19950,19 +19950,19 @@ var parseOptionalDate = (value, fieldName) => {
19950
19950
  throw new MemoryError("VALIDATION_ERROR", `${fieldName} must be a valid ISO 8601 datetime.`);
19951
19951
  }
19952
19952
  return date4;
19953
- };
19953
+ }
19954
19954
 
19955
- // src/tools/forget.ts
19955
+ // src/mcp/tools/forget.ts
19956
19956
  var forgetInputSchema = {
19957
19957
  id: string2().describe("The id of the memory to delete. Use the id returned by a previous recall result.")
19958
19958
  };
19959
- var registerForgetTool = (server, memoryService) => {
19959
+ function registerForgetTool(server, memory) {
19960
19960
  server.registerTool("forget", {
19961
19961
  description: "Permanently delete a memory that is wrong, obsolete, or no longer relevant. Pass the memory id from a previous recall result.",
19962
19962
  inputSchema: forgetInputSchema
19963
19963
  }, async ({ id }) => {
19964
19964
  try {
19965
- await memoryService.forget({ id });
19965
+ await memory.delete({ id });
19966
19966
  return {
19967
19967
  content: [{ type: "text", text: `<memory id="${id.trim()}" deleted="true" />` }]
19968
19968
  };
@@ -19970,56 +19970,108 @@ var registerForgetTool = (server, memoryService) => {
19970
19970
  throw toMcpError(error2);
19971
19971
  }
19972
19972
  });
19973
- };
19974
-
19975
- // src/memory-service.ts
19976
- import { randomUUID } from "node:crypto";
19973
+ }
19977
19974
 
19978
19975
  // src/memory.ts
19979
19976
  var toNormalizedScore = (value) => value;
19980
19977
 
19981
- // src/memory-service.ts
19982
- var DEFAULT_LIMIT = 15;
19983
- var MAX_LIMIT = 50;
19984
- var RECALL_CANDIDATE_LIMIT_MULTIPLIER = 3;
19978
+ // src/ranking.ts
19985
19979
  var RETRIEVAL_SCORE_WEIGHT = 8;
19986
19980
  var WORKSPACE_MATCH_WEIGHT = 4;
19987
19981
  var RECENCY_WEIGHT = 1;
19988
19982
  var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
19989
19983
  var GLOBAL_WORKSPACE_SCORE = 0.5;
19990
19984
  var SIBLING_WORKSPACE_SCORE = 0.25;
19985
+ function rerankSearchResults(results, workspace) {
19986
+ if (results.length <= 1) {
19987
+ return results;
19988
+ }
19989
+ const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
19990
+ const updatedAtTimes = results.map((result) => result.updatedAt.getTime());
19991
+ const minUpdatedAt = Math.min(...updatedAtTimes);
19992
+ const maxUpdatedAt = Math.max(...updatedAtTimes);
19993
+ return results.map((result) => {
19994
+ const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
19995
+ const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
19996
+ const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
19997
+ return {
19998
+ ...result,
19999
+ score: toNormalizedScore(combinedScore)
20000
+ };
20001
+ }).sort((a, b) => b.score - a.score);
20002
+ }
20003
+ function normalizeWorkspacePath(value) {
20004
+ return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
20005
+ }
20006
+ function computeWorkspaceScore(memoryWs, queryWs) {
20007
+ if (!queryWs) {
20008
+ return 0;
20009
+ }
20010
+ if (!memoryWs) {
20011
+ return GLOBAL_WORKSPACE_SCORE;
20012
+ }
20013
+ const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
20014
+ if (normalizedMemoryWs === queryWs) {
20015
+ return 1;
20016
+ }
20017
+ const queryLastSlashIndex = queryWs.lastIndexOf("/");
20018
+ const memoryLastSlashIndex = normalizedMemoryWs.lastIndexOf("/");
20019
+ if (queryLastSlashIndex <= 0 || memoryLastSlashIndex <= 0) {
20020
+ return 0;
20021
+ }
20022
+ const queryParent = queryWs.slice(0, queryLastSlashIndex);
20023
+ const memoryParent = normalizedMemoryWs.slice(0, memoryLastSlashIndex);
20024
+ return memoryParent === queryParent ? SIBLING_WORKSPACE_SCORE : 0;
20025
+ }
20026
+
20027
+ // src/memory-service.ts
20028
+ var DEFAULT_RECALL_LIMIT = 15;
20029
+ var MAX_RECALL_LIMIT = 50;
20030
+ var RECALL_CANDIDATE_LIMIT_MULTIPLIER = 3;
20031
+ var DEFAULT_LIST_LIMIT = 15;
20032
+ var MAX_LIST_LIMIT = 100;
19991
20033
 
19992
20034
  class MemoryService {
19993
20035
  repository;
19994
20036
  constructor(repository) {
19995
20037
  this.repository = repository;
19996
20038
  }
19997
- async save(input) {
20039
+ async create(input) {
19998
20040
  const content = input.content.trim();
19999
20041
  if (!content) {
20000
20042
  throw new ValidationError("Memory content is required.");
20001
20043
  }
20002
- const now = new Date;
20003
- const memory = {
20004
- id: randomUUID(),
20044
+ return this.repository.create({
20005
20045
  content,
20006
- workspace: normalizeOptionalString(input.workspace),
20007
- createdAt: now,
20008
- updatedAt: now
20009
- };
20010
- return this.repository.save(memory);
20046
+ workspace: normalizeOptionalString(input.workspace)
20047
+ });
20011
20048
  }
20012
- async revise(input) {
20049
+ async update(input) {
20013
20050
  const content = input.content.trim();
20014
20051
  if (!content)
20015
20052
  throw new ValidationError("Memory content is required.");
20016
- return this.repository.update(input.id, content);
20053
+ return this.repository.update({ id: input.id, content });
20017
20054
  }
20018
- async forget(input) {
20055
+ async delete(input) {
20019
20056
  const id = input.id.trim();
20020
20057
  if (!id)
20021
20058
  throw new ValidationError("Memory id is required.");
20022
- return this.repository.delete(id);
20059
+ return this.repository.delete({ id });
20060
+ }
20061
+ async get(id) {
20062
+ return this.repository.get(id);
20063
+ }
20064
+ async list(input) {
20065
+ const workspace = normalizeOptionalString(input.workspace);
20066
+ return this.repository.list({
20067
+ workspace,
20068
+ workspaceIsNull: workspace ? false : Boolean(input.workspaceIsNull),
20069
+ offset: normalizeOffset(input.offset),
20070
+ limit: normalizeListLimit(input.limit)
20071
+ });
20072
+ }
20073
+ async listWorkspaces() {
20074
+ return this.repository.listWorkspaces();
20023
20075
  }
20024
20076
  async search(input) {
20025
20077
  const terms = normalizeTerms(input.terms);
@@ -20035,19 +20087,27 @@ class MemoryService {
20035
20087
  updatedBefore: input.updatedBefore
20036
20088
  };
20037
20089
  const results = await this.repository.search(normalizedQuery);
20038
- const reranked = rerankSearchResults(results, workspace);
20039
- return reranked.sort((a, b) => b.score - a.score).slice(0, requestedLimit);
20090
+ return rerankSearchResults(results, workspace).slice(0, requestedLimit);
20040
20091
  }
20041
20092
  }
20042
20093
  function normalizeLimit(value) {
20043
20094
  if (value === undefined) {
20044
- return DEFAULT_LIMIT;
20095
+ return DEFAULT_RECALL_LIMIT;
20045
20096
  }
20046
- if (!Number.isInteger(value) || value < 1 || value > MAX_LIMIT) {
20047
- throw new ValidationError(`Limit must be an integer between 1 and ${MAX_LIMIT}.`);
20097
+ if (!Number.isInteger(value) || value < 1 || value > MAX_RECALL_LIMIT) {
20098
+ throw new ValidationError(`Limit must be an integer between 1 and ${MAX_RECALL_LIMIT}.`);
20048
20099
  }
20049
20100
  return value;
20050
20101
  }
20102
+ function normalizeOffset(value) {
20103
+ return Number.isInteger(value) && value && value > 0 ? value : 0;
20104
+ }
20105
+ function normalizeListLimit(value) {
20106
+ if (!Number.isInteger(value) || value === undefined) {
20107
+ return DEFAULT_LIST_LIMIT;
20108
+ }
20109
+ return Math.min(Math.max(value, 1), MAX_LIST_LIMIT);
20110
+ }
20051
20111
  function normalizeOptionalString(value) {
20052
20112
  const trimmed = value?.trim();
20053
20113
  return trimmed ? trimmed : undefined;
@@ -20056,70 +20116,29 @@ function normalizeTerms(terms) {
20056
20116
  const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
20057
20117
  return [...new Set(normalizedTerms)];
20058
20118
  }
20059
- function normalizeWorkspacePath(value) {
20060
- return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
20061
- }
20062
- function computeWorkspaceScore(memoryWs, queryWs) {
20063
- if (!queryWs) {
20064
- return 0;
20065
- }
20066
- if (!memoryWs) {
20067
- return GLOBAL_WORKSPACE_SCORE;
20068
- }
20069
- const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
20070
- if (normalizedMemoryWs === queryWs) {
20071
- return 1;
20072
- }
20073
- const queryLastSlashIndex = queryWs.lastIndexOf("/");
20074
- const memoryLastSlashIndex = normalizedMemoryWs.lastIndexOf("/");
20075
- if (queryLastSlashIndex <= 0 || memoryLastSlashIndex <= 0) {
20076
- return 0;
20077
- }
20078
- const queryParent = queryWs.slice(0, queryLastSlashIndex);
20079
- const memoryParent = normalizedMemoryWs.slice(0, memoryLastSlashIndex);
20080
- return memoryParent === queryParent ? SIBLING_WORKSPACE_SCORE : 0;
20081
- }
20082
- function rerankSearchResults(results, workspace) {
20083
- if (results.length <= 1) {
20084
- return results;
20085
- }
20086
- const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
20087
- const updatedAtTimes = results.map((result) => result.updatedAt.getTime());
20088
- const minUpdatedAt = Math.min(...updatedAtTimes);
20089
- const maxUpdatedAt = Math.max(...updatedAtTimes);
20090
- return results.map((result) => {
20091
- const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
20092
- const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
20093
- const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
20094
- return {
20095
- ...result,
20096
- score: toNormalizedScore(combinedScore)
20097
- };
20098
- });
20099
- }
20100
20119
 
20101
- // src/tools/recall.ts
20120
+ // src/mcp/tools/recall.ts
20102
20121
  var recallInputSchema = {
20103
20122
  terms: array(string2()).min(1).describe("Search terms used to find relevant memories. Pass 2-5 short, distinctive items as separate array entries. Be specific: instead of 'preferences' or 'context', name the actual topic -- e.g. 'error handling', 'commit format', 'testing strategy'. Do not repeat the project or workspace name here -- use the workspace parameter for project scoping. Avoid full sentences."),
20104
- limit: number2().int().min(1).max(MAX_LIMIT).optional().describe("Maximum number of matches to return. Keep this small when you only need the strongest hits."),
20123
+ limit: number2().int().min(1).max(MAX_RECALL_LIMIT).optional().describe("Maximum number of matches to return. Keep this small when you only need the strongest hits."),
20105
20124
  workspace: string2().optional().describe("Always pass the current working directory. Biases ranking toward the active project while still allowing cross-workspace matches. Memories saved without a workspace are treated as global and rank between matching and non-matching results."),
20106
20125
  updated_after: string2().optional().describe("Only return memories updated at or after this ISO 8601 timestamp. Use it when you need to narrow recall to newer context."),
20107
20126
  updated_before: string2().optional().describe("Only return memories updated at or before this ISO 8601 timestamp. Use it when you need to narrow recall to older context.")
20108
20127
  };
20109
- var toMemoryXml = (r) => {
20128
+ function toMemoryXml(r) {
20110
20129
  const workspace = r.workspace ? ` workspace="${escapeXml(r.workspace)}"` : "";
20111
20130
  const content = escapeXml(r.content);
20112
20131
  return `<memory id="${r.id}" score="${r.score}"${workspace} updated_at="${r.updatedAt.toISOString()}">
20113
20132
  ${content}
20114
20133
  </memory>`;
20115
- };
20116
- var registerRecallTool = (server, memoryService) => {
20134
+ }
20135
+ function registerRecallTool(server, memory) {
20117
20136
  server.registerTool("recall", {
20118
20137
  description: "Search memories for prior decisions, corrections, and context that cannot be derived from code or git history. Call at the start of every conversation and again mid-task when you are about to make a design choice, pick a convention, or handle an edge case that the user may have guided before. Always pass workspace.",
20119
20138
  inputSchema: recallInputSchema
20120
20139
  }, async ({ terms, limit, workspace, updated_after, updated_before }) => {
20121
20140
  try {
20122
- const results = await memoryService.search({
20141
+ const results = await memory.search({
20123
20142
  terms,
20124
20143
  limit,
20125
20144
  workspace,
@@ -20137,49 +20156,49 @@ ${results.map(toMemoryXml).join(`
20137
20156
  throw toMcpError(error2);
20138
20157
  }
20139
20158
  });
20140
- };
20159
+ }
20141
20160
 
20142
- // src/tools/remember.ts
20161
+ // src/mcp/tools/remember.ts
20143
20162
  var rememberInputSchema = {
20144
20163
  content: string2().describe("The fact, preference, decision, or context to remember. Use a single self-contained sentence or short note. One fact per memory."),
20145
20164
  workspace: string2().optional().describe("Always pass the current working directory to scope this memory to a project. Omit only when the memory applies across all projects (global preference).")
20146
20165
  };
20147
- var registerRememberTool = (server, memoryService) => {
20166
+ function registerRememberTool(server, memory) {
20148
20167
  server.registerTool("remember", {
20149
20168
  description: "Save durable context for later recall. Use this when the user corrects your approach, states a preference, a key decision or convention is established, or you learn project context not obvious from the code. Store one concise fact per memory. Do not store secrets, ephemeral task state, or information already in the codebase.",
20150
20169
  inputSchema: rememberInputSchema
20151
20170
  }, async ({ content, workspace }) => {
20152
20171
  try {
20153
- const memory = await memoryService.save({
20172
+ const savedMemory = await memory.create({
20154
20173
  content,
20155
20174
  workspace
20156
20175
  });
20157
20176
  return {
20158
- content: [{ type: "text", text: `<memory id="${memory.id}" />` }]
20177
+ content: [{ type: "text", text: `<memory id="${savedMemory.id}" />` }]
20159
20178
  };
20160
20179
  } catch (error2) {
20161
20180
  throw toMcpError(error2);
20162
20181
  }
20163
20182
  });
20164
- };
20183
+ }
20165
20184
 
20166
- // src/tools/revise.ts
20185
+ // src/mcp/tools/revise.ts
20167
20186
  var reviseInputSchema = {
20168
20187
  id: string2().describe("The id of the memory to update. Use the id returned by a previous recall result."),
20169
20188
  content: string2().describe("The replacement content for the memory. Use a single self-contained sentence or short note. One fact per memory.")
20170
20189
  };
20171
- var registerReviseTool = (server, memoryService) => {
20190
+ function registerReviseTool(server, memory) {
20172
20191
  server.registerTool("revise", {
20173
20192
  description: "Update the content of an existing memory. Use when a previously saved memory is outdated or inaccurate and needs correction rather than deletion. Pass the memory id from a previous recall result.",
20174
20193
  inputSchema: reviseInputSchema
20175
20194
  }, async ({ id, content }) => {
20176
20195
  try {
20177
- const memory = await memoryService.revise({ id, content });
20196
+ const revisedMemory = await memory.update({ id, content });
20178
20197
  return {
20179
20198
  content: [
20180
20199
  {
20181
20200
  type: "text",
20182
- text: `<memory id="${memory.id}" updated_at="${memory.updatedAt.toISOString()}" />`
20201
+ text: `<memory id="${revisedMemory.id}" updated_at="${revisedMemory.updatedAt.toISOString()}" />`
20183
20202
  }
20184
20203
  ]
20185
20204
  };
@@ -20187,9 +20206,9 @@ var registerReviseTool = (server, memoryService) => {
20187
20206
  throw toMcpError(error2);
20188
20207
  }
20189
20208
  });
20190
- };
20209
+ }
20191
20210
 
20192
- // src/mcp-server.ts
20211
+ // src/mcp/server.ts
20193
20212
  var SERVER_INSTRUCTIONS = [
20194
20213
  "Stores decisions, corrections, and context that cannot be derived from code or git history.",
20195
20214
  "Use `recall` at the start of every conversation and again mid-task before making design choices or picking conventions the user may have guided before.",
@@ -20200,19 +20219,19 @@ var SERVER_INSTRUCTIONS = [
20200
20219
  "Always pass workspace (the current working directory) to scope results to the active project.",
20201
20220
  "Omit workspace only when saving a memory that applies across all projects."
20202
20221
  ].join(" ");
20203
- var createMcpServer = (memoryService, version3) => {
20222
+ function createMcpServer(memory, version3) {
20204
20223
  const server = new McpServer({
20205
20224
  name: "agent-memory",
20206
20225
  version: version3
20207
20226
  }, {
20208
20227
  instructions: SERVER_INSTRUCTIONS
20209
20228
  });
20210
- registerRememberTool(server, memoryService);
20211
- registerRecallTool(server, memoryService);
20212
- registerReviseTool(server, memoryService);
20213
- registerForgetTool(server, memoryService);
20229
+ registerRememberTool(server, memory);
20230
+ registerRecallTool(server, memory);
20231
+ registerReviseTool(server, memory);
20232
+ registerForgetTool(server, memory);
20214
20233
  return server;
20215
- };
20234
+ }
20216
20235
 
20217
20236
  // src/sqlite-db.ts
20218
20237
  import { mkdirSync } from "node:fs";
@@ -20252,7 +20271,7 @@ var MEMORY_SCHEMA = `
20252
20271
  INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
20253
20272
  END;
20254
20273
  `;
20255
- var openMemoryDatabase = (databasePath) => {
20274
+ function openMemoryDatabase(databasePath) {
20256
20275
  try {
20257
20276
  mkdirSync(dirname(databasePath), { recursive: true });
20258
20277
  const database = new Database(databasePath);
@@ -20263,8 +20282,8 @@ var openMemoryDatabase = (databasePath) => {
20263
20282
  cause: error2
20264
20283
  });
20265
20284
  }
20266
- };
20267
- var initializeMemoryDatabase = (database) => {
20285
+ }
20286
+ function initializeMemoryDatabase(database) {
20268
20287
  if (database.pragma) {
20269
20288
  database.pragma("journal_mode = WAL");
20270
20289
  database.pragma("synchronous = NORMAL");
@@ -20277,13 +20296,17 @@ var initializeMemoryDatabase = (database) => {
20277
20296
  database.exec("PRAGMA busy_timeout = 5000");
20278
20297
  }
20279
20298
  database.exec(MEMORY_SCHEMA);
20280
- };
20299
+ }
20300
+
20301
+ // src/sqlite-memory-repository.ts
20302
+ import { randomUUID } from "node:crypto";
20303
+ var DEFAULT_SEARCH_LIMIT = 15;
20304
+ var DEFAULT_LIST_LIMIT2 = 15;
20281
20305
 
20282
- // src/sqlite-repository.ts
20283
20306
  class SqliteMemoryRepository {
20284
20307
  database;
20285
20308
  insertStatement;
20286
- findByIdStatement;
20309
+ getStatement;
20287
20310
  updateStatement;
20288
20311
  deleteStatement;
20289
20312
  listWorkspacesStatement;
@@ -20304,13 +20327,21 @@ class SqliteMemoryRepository {
20304
20327
  ?
20305
20328
  )
20306
20329
  `);
20307
- this.findByIdStatement = database.prepare("SELECT id, content, workspace, created_at, updated_at FROM memories WHERE id = ?");
20330
+ this.getStatement = database.prepare("SELECT id, content, workspace, created_at, updated_at FROM memories WHERE id = ?");
20308
20331
  this.updateStatement = database.prepare("UPDATE memories SET content = ?, updated_at = ? WHERE id = ?");
20309
20332
  this.deleteStatement = database.prepare("DELETE FROM memories WHERE id = ?");
20310
20333
  this.listWorkspacesStatement = database.prepare("SELECT DISTINCT workspace FROM memories WHERE workspace IS NOT NULL ORDER BY workspace");
20311
20334
  }
20312
- async save(memory) {
20335
+ async create(input) {
20313
20336
  try {
20337
+ const now = new Date;
20338
+ const memory = {
20339
+ id: randomUUID(),
20340
+ content: input.content,
20341
+ workspace: input.workspace,
20342
+ createdAt: now,
20343
+ updatedAt: now
20344
+ };
20314
20345
  this.insertStatement.run(memory.id, memory.content, memory.workspace, memory.createdAt.getTime(), memory.updatedAt.getTime());
20315
20346
  return memory;
20316
20347
  } catch (error2) {
@@ -20320,6 +20351,7 @@ class SqliteMemoryRepository {
20320
20351
  async search(query) {
20321
20352
  try {
20322
20353
  const whereParams = [toFtsQuery(query.terms)];
20354
+ const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
20323
20355
  const whereClauses = ["memories_fts MATCH ?"];
20324
20356
  if (query.updatedAfter) {
20325
20357
  whereClauses.push("m.updated_at >= ?");
@@ -20329,7 +20361,7 @@ class SqliteMemoryRepository {
20329
20361
  whereClauses.push("m.updated_at <= ?");
20330
20362
  whereParams.push(query.updatedBefore.getTime());
20331
20363
  }
20332
- const params = [...whereParams, query.limit];
20364
+ const params = [...whereParams, limit];
20333
20365
  const statement = this.database.prepare(`
20334
20366
  SELECT
20335
20367
  m.id,
@@ -20356,19 +20388,21 @@ class SqliteMemoryRepository {
20356
20388
  });
20357
20389
  }
20358
20390
  }
20359
- async findById(id) {
20391
+ async get(id) {
20360
20392
  try {
20361
- const rows = this.findByIdStatement.all(id);
20393
+ const rows = this.getStatement.all(id);
20362
20394
  const row = rows[0];
20363
20395
  return row ? toMemoryRecord(row) : undefined;
20364
20396
  } catch (error2) {
20365
20397
  throw new PersistenceError("Failed to find memory.", { cause: error2 });
20366
20398
  }
20367
20399
  }
20368
- async findAll(options) {
20400
+ async list(options) {
20369
20401
  try {
20370
20402
  const whereClauses = [];
20371
20403
  const params = [];
20404
+ const offset = options.offset ?? 0;
20405
+ const limit = options.limit ?? DEFAULT_LIST_LIMIT2;
20372
20406
  if (options.workspace) {
20373
20407
  whereClauses.push("workspace = ?");
20374
20408
  params.push(options.workspace);
@@ -20376,8 +20410,8 @@ class SqliteMemoryRepository {
20376
20410
  whereClauses.push("workspace IS NULL");
20377
20411
  }
20378
20412
  const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
20379
- const limit = options.limit + 1;
20380
- params.push(limit, options.offset);
20413
+ const queryLimit = limit + 1;
20414
+ params.push(queryLimit, offset);
20381
20415
  const statement = this.database.prepare(`
20382
20416
  SELECT id, content, workspace, created_at, updated_at
20383
20417
  FROM memories
@@ -20386,39 +20420,39 @@ class SqliteMemoryRepository {
20386
20420
  LIMIT ? OFFSET ?
20387
20421
  `);
20388
20422
  const rows = statement.all(...params);
20389
- const hasMore = rows.length > options.limit;
20390
- const items = (hasMore ? rows.slice(0, options.limit) : rows).map(toMemoryRecord);
20423
+ const hasMore = rows.length > limit;
20424
+ const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryRecord);
20391
20425
  return { items, hasMore };
20392
20426
  } catch (error2) {
20393
20427
  throw new PersistenceError("Failed to list memories.", { cause: error2 });
20394
20428
  }
20395
20429
  }
20396
- async update(id, content) {
20430
+ async update(input) {
20397
20431
  let result;
20398
20432
  try {
20399
20433
  const now = Date.now();
20400
- result = this.updateStatement.run(content, now, id);
20434
+ result = this.updateStatement.run(input.content, now, input.id);
20401
20435
  } catch (error2) {
20402
20436
  throw new PersistenceError("Failed to update memory.", { cause: error2 });
20403
20437
  }
20404
20438
  if (result.changes === 0) {
20405
- throw new NotFoundError(`Memory not found: ${id}`);
20439
+ throw new NotFoundError(`Memory not found: ${input.id}`);
20406
20440
  }
20407
- const memory = await this.findById(id);
20441
+ const memory = await this.get(input.id);
20408
20442
  if (!memory) {
20409
- throw new NotFoundError(`Memory not found after update: ${id}`);
20443
+ throw new NotFoundError(`Memory not found after update: ${input.id}`);
20410
20444
  }
20411
20445
  return memory;
20412
20446
  }
20413
- async delete(id) {
20447
+ async delete(input) {
20414
20448
  let result;
20415
20449
  try {
20416
- result = this.deleteStatement.run(id);
20450
+ result = this.deleteStatement.run(input.id);
20417
20451
  } catch (error2) {
20418
20452
  throw new PersistenceError("Failed to delete memory.", { cause: error2 });
20419
20453
  }
20420
20454
  if (result.changes === 0) {
20421
- throw new NotFoundError(`Memory not found: ${id}`);
20455
+ throw new NotFoundError(`Memory not found: ${input.id}`);
20422
20456
  }
20423
20457
  }
20424
20458
  async listWorkspaces() {
@@ -20438,16 +20472,13 @@ var toMemoryRecord = (row) => ({
20438
20472
  updatedAt: new Date(row.updated_at)
20439
20473
  });
20440
20474
  var toFtsQuery = (terms) => terms.map(toFtsTerm).join(" OR ");
20441
- var toFtsTerm = (term) => {
20475
+ function toFtsTerm(term) {
20442
20476
  const escaped = term.replaceAll('"', '""');
20443
20477
  if (term.includes(" ")) {
20444
20478
  return `"${escaped}"`;
20445
20479
  }
20446
20480
  return `"${escaped}"*`;
20447
- };
20448
-
20449
- // src/ui/server.tsx
20450
- import { randomUUID as randomUUID2 } from "node:crypto";
20481
+ }
20451
20482
 
20452
20483
  // node_modules/@hono/node-server/dist/index.mjs
20453
20484
  import { createServer as createServerHTTP } from "http";
@@ -23441,182 +23472,188 @@ function jsxDEV(tag, props, key) {
23441
23472
  }
23442
23473
 
23443
23474
  // src/ui/components/create-form.tsx
23444
- var CreateForm = ({ workspace }) => /* @__PURE__ */ jsxDEV("div", {
23445
- class: "form-card",
23446
- children: /* @__PURE__ */ jsxDEV("form", {
23447
- method: "post",
23448
- action: "/memories",
23449
- children: [
23450
- /* @__PURE__ */ jsxDEV("div", {
23451
- class: "field",
23452
- children: [
23453
- /* @__PURE__ */ jsxDEV("label", {
23454
- for: "new-content",
23455
- children: "Content"
23456
- }, undefined, false, undefined, this),
23457
- /* @__PURE__ */ jsxDEV("textarea", {
23458
- id: "new-content",
23459
- name: "content",
23460
- placeholder: "Fact, preference, decision...",
23461
- required: true
23462
- }, undefined, false, undefined, this)
23463
- ]
23464
- }, undefined, true, undefined, this),
23465
- /* @__PURE__ */ jsxDEV("div", {
23466
- class: "field",
23467
- children: [
23468
- /* @__PURE__ */ jsxDEV("label", {
23469
- for: "new-workspace",
23470
- children: "Workspace (optional)"
23471
- }, undefined, false, undefined, this),
23472
- /* @__PURE__ */ jsxDEV("input", {
23473
- id: "new-workspace",
23474
- name: "workspace",
23475
- type: "text",
23476
- placeholder: "/path/to/project",
23477
- value: workspace && workspace !== NO_WORKSPACE_FILTER ? workspace : ""
23478
- }, undefined, false, undefined, this)
23479
- ]
23480
- }, undefined, true, undefined, this),
23481
- /* @__PURE__ */ jsxDEV("div", {
23482
- class: "form-actions",
23483
- children: /* @__PURE__ */ jsxDEV("button", {
23484
- type: "submit",
23485
- class: "primary",
23486
- children: "Save"
23487
- }, undefined, false, undefined, this)
23488
- }, undefined, false, undefined, this)
23489
- ]
23490
- }, undefined, true, undefined, this)
23491
- }, undefined, false, undefined, this);
23492
-
23493
- // src/ui/components/memory-card.tsx
23494
- var MemoryCard = ({ memory, editing, showWorkspace, returnUrl }) => /* @__PURE__ */ jsxDEV("div", {
23495
- class: "card",
23496
- children: [
23497
- /* @__PURE__ */ jsxDEV("div", {
23498
- class: "card-header",
23499
- children: /* @__PURE__ */ jsxDEV("div", {
23500
- class: "card-meta",
23501
- children: [
23502
- showWorkspace && memory.workspace && /* @__PURE__ */ jsxDEV(Fragment, {
23503
- children: [
23504
- /* @__PURE__ */ jsxDEV("span", {
23505
- class: "badge",
23506
- children: memory.workspace
23507
- }, undefined, false, undefined, this),
23508
- " "
23509
- ]
23510
- }, undefined, true, undefined, this),
23511
- memory.updatedAt.toLocaleString()
23512
- ]
23513
- }, undefined, true, undefined, this)
23514
- }, undefined, false, undefined, this),
23515
- editing ? /* @__PURE__ */ jsxDEV("form", {
23475
+ function CreateForm({ workspace }) {
23476
+ return /* @__PURE__ */ jsxDEV("div", {
23477
+ class: "form-card",
23478
+ children: /* @__PURE__ */ jsxDEV("form", {
23516
23479
  method: "post",
23517
- action: `/memories/${encodeURIComponent(memory.id)}/update`,
23480
+ action: "/memories",
23518
23481
  children: [
23519
- /* @__PURE__ */ jsxDEV("input", {
23520
- type: "hidden",
23521
- name: "returnUrl",
23522
- value: returnUrl
23523
- }, undefined, false, undefined, this),
23524
- /* @__PURE__ */ jsxDEV("textarea", {
23525
- name: "content",
23526
- required: true,
23527
- children: memory.content
23528
- }, undefined, false, undefined, this),
23529
23482
  /* @__PURE__ */ jsxDEV("div", {
23530
- class: "card-actions",
23483
+ class: "field",
23531
23484
  children: [
23532
- /* @__PURE__ */ jsxDEV("button", {
23533
- type: "submit",
23534
- class: "primary",
23535
- children: "Save"
23485
+ /* @__PURE__ */ jsxDEV("label", {
23486
+ for: "new-content",
23487
+ children: "Content"
23536
23488
  }, undefined, false, undefined, this),
23537
- /* @__PURE__ */ jsxDEV("a", {
23538
- href: returnUrl,
23539
- class: "btn",
23540
- children: "Cancel"
23489
+ /* @__PURE__ */ jsxDEV("textarea", {
23490
+ id: "new-content",
23491
+ name: "content",
23492
+ placeholder: "Fact, preference, decision...",
23493
+ required: true
23541
23494
  }, undefined, false, undefined, this)
23542
23495
  ]
23543
- }, undefined, true, undefined, this)
23544
- ]
23545
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
23546
- children: [
23496
+ }, undefined, true, undefined, this),
23547
23497
  /* @__PURE__ */ jsxDEV("div", {
23548
- class: "card-content",
23549
- children: memory.content
23550
- }, undefined, false, undefined, this),
23551
- /* @__PURE__ */ jsxDEV("div", {
23552
- class: "card-actions",
23498
+ class: "field",
23553
23499
  children: [
23554
- /* @__PURE__ */ jsxDEV("a", {
23555
- href: `${returnUrl}${returnUrl.includes("?") ? "&" : "?"}edit=${encodeURIComponent(memory.id)}`,
23556
- class: "btn",
23557
- children: "Edit"
23500
+ /* @__PURE__ */ jsxDEV("label", {
23501
+ for: "new-workspace",
23502
+ children: "Workspace (optional)"
23558
23503
  }, undefined, false, undefined, this),
23559
- /* @__PURE__ */ jsxDEV("form", {
23560
- method: "post",
23561
- action: `/memories/${encodeURIComponent(memory.id)}/delete`,
23562
- onsubmit: "return confirm('Delete this memory?')",
23504
+ /* @__PURE__ */ jsxDEV("input", {
23505
+ id: "new-workspace",
23506
+ name: "workspace",
23507
+ type: "text",
23508
+ placeholder: "/path/to/project",
23509
+ value: workspace && workspace !== NO_WORKSPACE_FILTER ? workspace : ""
23510
+ }, undefined, false, undefined, this)
23511
+ ]
23512
+ }, undefined, true, undefined, this),
23513
+ /* @__PURE__ */ jsxDEV("div", {
23514
+ class: "form-actions",
23515
+ children: /* @__PURE__ */ jsxDEV("button", {
23516
+ type: "submit",
23517
+ class: "primary",
23518
+ children: "Save"
23519
+ }, undefined, false, undefined, this)
23520
+ }, undefined, false, undefined, this)
23521
+ ]
23522
+ }, undefined, true, undefined, this)
23523
+ }, undefined, false, undefined, this);
23524
+ }
23525
+
23526
+ // src/ui/components/memory-card.tsx
23527
+ function MemoryCard({ memory, editing, showWorkspace, returnUrl }) {
23528
+ return /* @__PURE__ */ jsxDEV("div", {
23529
+ class: "card",
23530
+ children: [
23531
+ /* @__PURE__ */ jsxDEV("div", {
23532
+ class: "card-header",
23533
+ children: /* @__PURE__ */ jsxDEV("div", {
23534
+ class: "card-meta",
23535
+ children: [
23536
+ showWorkspace && memory.workspace && /* @__PURE__ */ jsxDEV(Fragment, {
23563
23537
  children: [
23564
- /* @__PURE__ */ jsxDEV("input", {
23565
- type: "hidden",
23566
- name: "returnUrl",
23567
- value: returnUrl
23538
+ /* @__PURE__ */ jsxDEV("span", {
23539
+ class: "badge",
23540
+ children: memory.workspace
23568
23541
  }, undefined, false, undefined, this),
23569
- /* @__PURE__ */ jsxDEV("button", {
23570
- type: "submit",
23571
- class: "danger",
23572
- children: "Delete"
23573
- }, undefined, false, undefined, this)
23542
+ " "
23574
23543
  ]
23575
- }, undefined, true, undefined, this)
23544
+ }, undefined, true, undefined, this),
23545
+ memory.updatedAt.toLocaleString()
23576
23546
  ]
23577
23547
  }, undefined, true, undefined, this)
23578
- ]
23579
- }, undefined, true, undefined, this)
23580
- ]
23581
- }, undefined, true, undefined, this);
23548
+ }, undefined, false, undefined, this),
23549
+ editing ? /* @__PURE__ */ jsxDEV("form", {
23550
+ method: "post",
23551
+ action: `/memories/${encodeURIComponent(memory.id)}/update`,
23552
+ children: [
23553
+ /* @__PURE__ */ jsxDEV("input", {
23554
+ type: "hidden",
23555
+ name: "returnUrl",
23556
+ value: returnUrl
23557
+ }, undefined, false, undefined, this),
23558
+ /* @__PURE__ */ jsxDEV("textarea", {
23559
+ name: "content",
23560
+ required: true,
23561
+ children: memory.content
23562
+ }, undefined, false, undefined, this),
23563
+ /* @__PURE__ */ jsxDEV("div", {
23564
+ class: "card-actions",
23565
+ children: [
23566
+ /* @__PURE__ */ jsxDEV("button", {
23567
+ type: "submit",
23568
+ class: "primary",
23569
+ children: "Save"
23570
+ }, undefined, false, undefined, this),
23571
+ /* @__PURE__ */ jsxDEV("a", {
23572
+ href: returnUrl,
23573
+ class: "btn",
23574
+ children: "Cancel"
23575
+ }, undefined, false, undefined, this)
23576
+ ]
23577
+ }, undefined, true, undefined, this)
23578
+ ]
23579
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
23580
+ children: [
23581
+ /* @__PURE__ */ jsxDEV("div", {
23582
+ class: "card-content",
23583
+ children: memory.content
23584
+ }, undefined, false, undefined, this),
23585
+ /* @__PURE__ */ jsxDEV("div", {
23586
+ class: "card-actions",
23587
+ children: [
23588
+ /* @__PURE__ */ jsxDEV("a", {
23589
+ href: `${returnUrl}${returnUrl.includes("?") ? "&" : "?"}edit=${encodeURIComponent(memory.id)}`,
23590
+ class: "btn",
23591
+ children: "Edit"
23592
+ }, undefined, false, undefined, this),
23593
+ /* @__PURE__ */ jsxDEV("form", {
23594
+ method: "post",
23595
+ action: `/memories/${encodeURIComponent(memory.id)}/delete`,
23596
+ onsubmit: "return confirm('Delete this memory?')",
23597
+ children: [
23598
+ /* @__PURE__ */ jsxDEV("input", {
23599
+ type: "hidden",
23600
+ name: "returnUrl",
23601
+ value: returnUrl
23602
+ }, undefined, false, undefined, this),
23603
+ /* @__PURE__ */ jsxDEV("button", {
23604
+ type: "submit",
23605
+ class: "danger",
23606
+ children: "Delete"
23607
+ }, undefined, false, undefined, this)
23608
+ ]
23609
+ }, undefined, true, undefined, this)
23610
+ ]
23611
+ }, undefined, true, undefined, this)
23612
+ ]
23613
+ }, undefined, true, undefined, this)
23614
+ ]
23615
+ }, undefined, true, undefined, this);
23616
+ }
23582
23617
 
23583
23618
  // src/ui/components/sidebar.tsx
23584
- var Sidebar = ({ workspaces, selected }) => /* @__PURE__ */ jsxDEV("nav", {
23585
- class: "sidebar",
23586
- children: [
23587
- /* @__PURE__ */ jsxDEV("h2", {
23588
- children: "Workspaces"
23589
- }, undefined, false, undefined, this),
23590
- /* @__PURE__ */ jsxDEV("a", {
23591
- href: "/",
23592
- class: `sidebar-item all-item${selected === null ? " active" : ""}`,
23593
- children: "All"
23594
- }, undefined, false, undefined, this),
23595
- /* @__PURE__ */ jsxDEV("a", {
23596
- href: `/?workspace=${NO_WORKSPACE_FILTER}`,
23597
- class: `sidebar-item no-ws-item${selected === NO_WORKSPACE_FILTER ? " active" : ""}`,
23598
- children: "No workspace"
23599
- }, undefined, false, undefined, this),
23600
- workspaces.map((ws) => /* @__PURE__ */ jsxDEV("a", {
23601
- href: `/?workspace=${encodeURIComponent(ws)}`,
23602
- class: `sidebar-item ws-item${selected === ws ? " active" : ""}`,
23603
- title: ws,
23604
- children: /* @__PURE__ */ jsxDEV("span", {
23605
- children: ws.replace(/\/$/, "")
23606
- }, undefined, false, undefined, this)
23607
- }, undefined, false, undefined, this))
23608
- ]
23609
- }, undefined, true, undefined, this);
23619
+ function Sidebar({ workspaces, selected }) {
23620
+ return /* @__PURE__ */ jsxDEV("nav", {
23621
+ class: "sidebar",
23622
+ children: [
23623
+ /* @__PURE__ */ jsxDEV("h2", {
23624
+ children: "Workspaces"
23625
+ }, undefined, false, undefined, this),
23626
+ /* @__PURE__ */ jsxDEV("a", {
23627
+ href: "/",
23628
+ class: `sidebar-item all-item${selected === null ? " active" : ""}`,
23629
+ children: "All"
23630
+ }, undefined, false, undefined, this),
23631
+ /* @__PURE__ */ jsxDEV("a", {
23632
+ href: `/?workspace=${NO_WORKSPACE_FILTER}`,
23633
+ class: `sidebar-item no-ws-item${selected === NO_WORKSPACE_FILTER ? " active" : ""}`,
23634
+ children: "No workspace"
23635
+ }, undefined, false, undefined, this),
23636
+ workspaces.map((ws) => /* @__PURE__ */ jsxDEV("a", {
23637
+ href: `/?workspace=${encodeURIComponent(ws)}`,
23638
+ class: `sidebar-item ws-item${selected === ws ? " active" : ""}`,
23639
+ title: ws,
23640
+ children: /* @__PURE__ */ jsxDEV("span", {
23641
+ children: ws.replace(/\/$/, "")
23642
+ }, undefined, false, undefined, this)
23643
+ }, undefined, false, undefined, this))
23644
+ ]
23645
+ }, undefined, true, undefined, this);
23646
+ }
23610
23647
 
23611
23648
  // src/ui/components/page.tsx
23612
- var buildUrl = (base, overrides) => {
23649
+ function buildUrl(base, overrides) {
23613
23650
  const url = new URL(base, "http://localhost");
23614
23651
  for (const [key, value] of Object.entries(overrides)) {
23615
23652
  url.searchParams.set(key, value);
23616
23653
  }
23617
23654
  return `${url.pathname}${url.search}`;
23618
- };
23619
- var Page = ({
23655
+ }
23656
+ function Page({
23620
23657
  memories,
23621
23658
  workspaces,
23622
23659
  selectedWorkspace,
@@ -23624,7 +23661,7 @@ var Page = ({
23624
23661
  currentPage,
23625
23662
  hasMore,
23626
23663
  showCreate
23627
- }) => {
23664
+ }) {
23628
23665
  const params = new URLSearchParams;
23629
23666
  if (selectedWorkspace)
23630
23667
  params.set("workspace", selectedWorkspace);
@@ -23743,27 +23780,26 @@ var Page = ({
23743
23780
  }, undefined, false, undefined, this)
23744
23781
  ]
23745
23782
  }, undefined, true, undefined, this);
23746
- };
23783
+ }
23747
23784
 
23748
- // src/ui/server.tsx
23749
- var DEFAULT_LIST_LIMIT = 15;
23750
- var MAX_LIST_LIMIT = 100;
23751
- var startWebServer = (repository, options) => {
23785
+ // src/ui/routes/page-routes.tsx
23786
+ var DEFAULT_LIST_LIMIT3 = 15;
23787
+ function createPageRoutes(memory) {
23752
23788
  const app = new Hono2;
23753
- app.get("/", async (c) => {
23789
+ async function renderPage(c) {
23754
23790
  const workspace = c.req.query("workspace") ?? null;
23755
23791
  const pageNum = Math.max(Number(c.req.query("page")) || 1, 1);
23756
23792
  const editingId = c.req.query("edit") ?? null;
23757
23793
  const showCreate = c.req.query("create") === "1";
23758
23794
  const isNoWorkspace = workspace === NO_WORKSPACE_FILTER;
23759
23795
  const wsFilter = workspace && !isNoWorkspace ? workspace : undefined;
23760
- const page = await repository.findAll({
23796
+ const page = await memory.list({
23761
23797
  workspace: wsFilter,
23762
23798
  workspaceIsNull: isNoWorkspace,
23763
- offset: (pageNum - 1) * DEFAULT_LIST_LIMIT,
23764
- limit: DEFAULT_LIST_LIMIT
23799
+ offset: (pageNum - 1) * DEFAULT_LIST_LIMIT3,
23800
+ limit: DEFAULT_LIST_LIMIT3
23765
23801
  });
23766
- const workspaces = await repository.listWorkspaces();
23802
+ const workspaces = await memory.listWorkspaces();
23767
23803
  return c.html(/* @__PURE__ */ jsxDEV(Page, {
23768
23804
  memories: page.items,
23769
23805
  workspaces,
@@ -23773,132 +23809,83 @@ var startWebServer = (repository, options) => {
23773
23809
  hasMore: page.hasMore,
23774
23810
  showCreate
23775
23811
  }, undefined, false, undefined, this));
23776
- });
23777
- app.post("/memories", async (c) => {
23812
+ }
23813
+ async function createMemory(c) {
23778
23814
  const form2 = await c.req.parseBody();
23779
- const content = typeof form2.content === "string" ? form2.content.trim() : "";
23780
- const workspace = typeof form2.workspace === "string" ? form2.workspace.trim() || undefined : undefined;
23781
- if (content) {
23782
- const now = new Date;
23783
- await repository.save({ id: randomUUID2(), content, workspace, createdAt: now, updatedAt: now });
23815
+ const content = typeof form2.content === "string" ? form2.content : "";
23816
+ const workspace = typeof form2.workspace === "string" ? form2.workspace : undefined;
23817
+ try {
23818
+ await memory.create({ content, workspace });
23819
+ } catch (error2) {
23820
+ if (!(error2 instanceof ValidationError)) {
23821
+ throw error2;
23822
+ }
23784
23823
  }
23785
- const wsParam = workspace ? `/?workspace=${encodeURIComponent(workspace)}` : "/";
23824
+ const wsParam = workspace?.trim() ? `/?workspace=${encodeURIComponent(workspace.trim())}` : "/";
23786
23825
  return c.redirect(wsParam);
23787
- });
23788
- app.post("/memories/:id/update", async (c) => {
23826
+ }
23827
+ async function updateMemory(c) {
23789
23828
  const form2 = await c.req.parseBody();
23790
- const content = typeof form2.content === "string" ? form2.content.trim() : "";
23829
+ const content = typeof form2.content === "string" ? form2.content : "";
23791
23830
  const returnUrl = safeReturnUrl(form2.returnUrl);
23792
- if (content) {
23793
- try {
23794
- await repository.update(c.req.param("id"), content);
23795
- } catch (error2) {
23796
- if (!(error2 instanceof NotFoundError))
23797
- throw error2;
23798
- }
23831
+ const id = c.req.param("id") ?? "";
23832
+ try {
23833
+ await memory.update({ id, content });
23834
+ } catch (error2) {
23835
+ if (!(error2 instanceof NotFoundError) && !(error2 instanceof ValidationError))
23836
+ throw error2;
23799
23837
  }
23800
23838
  return c.redirect(returnUrl);
23801
- });
23802
- app.post("/memories/:id/delete", async (c) => {
23839
+ }
23840
+ async function deleteMemory(c) {
23803
23841
  const form2 = await c.req.parseBody();
23804
23842
  const returnUrl = safeReturnUrl(form2.returnUrl);
23843
+ const id = c.req.param("id") ?? "";
23805
23844
  try {
23806
- await repository.delete(c.req.param("id"));
23845
+ await memory.delete({ id });
23807
23846
  } catch (error2) {
23808
23847
  if (!(error2 instanceof NotFoundError))
23809
23848
  throw error2;
23810
23849
  }
23811
23850
  return c.redirect(returnUrl);
23812
- });
23813
- app.get("/api/workspaces", async (c) => {
23814
- const workspaces = await repository.listWorkspaces();
23815
- return c.json({ workspaces });
23816
- });
23817
- app.get("/api/memories", async (c) => {
23818
- const workspace = c.req.query("workspace");
23819
- const limitParam = c.req.query("limit");
23820
- const limit = Math.min(Math.max(Number(limitParam) || DEFAULT_LIST_LIMIT, 1), MAX_LIST_LIMIT);
23821
- const offset = Math.max(Number(c.req.query("offset")) || 0, 0);
23822
- const page = await repository.findAll({ workspace, offset, limit });
23823
- return c.json({ items: page.items.map(toMemoryJson), hasMore: page.hasMore });
23824
- });
23825
- app.get("/api/memories/:id", async (c) => {
23826
- const memory = await repository.findById(c.req.param("id"));
23827
- if (!memory)
23828
- return c.json({ error: "Memory not found." }, 404);
23829
- return c.json(toMemoryJson(memory));
23830
- });
23831
- app.post("/api/memories", async (c) => {
23832
- const body = await c.req.json().catch(() => null);
23833
- if (!body)
23834
- return c.json({ error: "Invalid JSON." }, 400);
23835
- const content = typeof body.content === "string" ? body.content.trim() : "";
23836
- if (!content)
23837
- return c.json({ error: "Content is required." }, 400);
23838
- const workspace = typeof body.workspace === "string" ? body.workspace.trim() || undefined : undefined;
23839
- const now = new Date;
23840
- const memory = await repository.save({ id: randomUUID2(), content, workspace, createdAt: now, updatedAt: now });
23841
- return c.json(toMemoryJson(memory), 201);
23842
- });
23843
- app.patch("/api/memories/:id", async (c) => {
23844
- const body = await c.req.json().catch(() => null);
23845
- if (!body)
23846
- return c.json({ error: "Invalid JSON." }, 400);
23847
- const content = typeof body.content === "string" ? body.content.trim() : "";
23848
- if (!content)
23849
- return c.json({ error: "Content is required." }, 400);
23850
- try {
23851
- const updated = await repository.update(c.req.param("id"), content);
23852
- return c.json(toMemoryJson(updated));
23853
- } catch (error2) {
23854
- if (error2 instanceof NotFoundError)
23855
- return c.json({ error: "Memory not found." }, 404);
23856
- throw error2;
23857
- }
23858
- });
23859
- app.delete("/api/memories/:id", async (c) => {
23860
- try {
23861
- await repository.delete(c.req.param("id"));
23862
- return c.body(null, 204);
23863
- } catch (error2) {
23864
- if (error2 instanceof NotFoundError)
23865
- return c.json({ error: "Memory not found." }, 404);
23866
- throw error2;
23867
- }
23868
- });
23869
- return serve({ fetch: app.fetch, port: options.port });
23870
- };
23871
- var safeReturnUrl = (value) => {
23851
+ }
23852
+ app.get("/", renderPage);
23853
+ app.post("/memories", createMemory);
23854
+ app.post("/memories/:id/update", updateMemory);
23855
+ app.post("/memories/:id/delete", deleteMemory);
23856
+ return app;
23857
+ }
23858
+ function safeReturnUrl(value) {
23872
23859
  if (typeof value === "string" && value.startsWith("/"))
23873
23860
  return value;
23874
23861
  return "/";
23875
- };
23876
- var toMemoryJson = (memory) => ({
23877
- id: memory.id,
23878
- content: memory.content,
23879
- workspace: memory.workspace ?? null,
23880
- created_at: memory.createdAt.toISOString(),
23881
- updated_at: memory.updatedAt.toISOString()
23882
- });
23862
+ }
23863
+
23864
+ // src/ui/server.tsx
23865
+ function startWebServer(memory, options) {
23866
+ const app = new Hono2;
23867
+ app.route("/", createPageRoutes(memory));
23868
+ return serve({ fetch: app.fetch, port: options.port });
23869
+ }
23883
23870
 
23884
23871
  // src/index.ts
23885
23872
  var config2 = resolveConfig();
23886
23873
  var database = openMemoryDatabase(config2.databasePath);
23887
23874
  var repository = new SqliteMemoryRepository(database);
23875
+ var memoryService = new MemoryService(repository);
23888
23876
  if (config2.uiMode) {
23889
- const server = startWebServer(repository, { port: config2.uiPort });
23890
- const addr = server.address();
23891
- const port = typeof addr === "object" && addr ? addr.port : config2.uiPort;
23892
- console.log(`agent-memory UI running at http://localhost:${port}`);
23893
- const shutdown = () => {
23877
+ let shutdown = function() {
23894
23878
  server.close();
23895
23879
  database.close();
23896
23880
  process.exit(0);
23897
23881
  };
23882
+ const server = startWebServer(memoryService, { port: config2.uiPort });
23883
+ const addr = server.address();
23884
+ const port = typeof addr === "object" && addr ? addr.port : config2.uiPort;
23885
+ console.log(`agent-memory UI running at http://localhost:${port}`);
23898
23886
  process.on("SIGINT", shutdown);
23899
23887
  process.on("SIGTERM", shutdown);
23900
23888
  } else {
23901
- const memoryService = new MemoryService(repository);
23902
23889
  const server = createMcpServer(memoryService, version2);
23903
23890
  const transport = new StdioServerTransport;
23904
23891
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jcyamacho/agent-memory",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.12",
4
+ "version": "0.0.13",
5
5
  "bin": {
6
6
  "agent-memory": "dist/index.js"
7
7
  },