@pcircle/memesh 4.5.1 → 4.6.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.
Files changed (169) hide show
  1. package/.claude-plugin/marketplace.json +5 -3
  2. package/.claude-plugin/plugin.json +6 -4
  3. package/AGENTS.md +95 -0
  4. package/README.de.md +129 -35
  5. package/README.md +161 -34
  6. package/README.zh-TW.md +130 -35
  7. package/dashboard/dist/index.html +10 -10
  8. package/dist/cli/view-live.js +3 -3
  9. package/dist/core/auto-tagger.d.ts.map +1 -1
  10. package/dist/core/auto-tagger.js +4 -9
  11. package/dist/core/auto-tagger.js.map +1 -1
  12. package/dist/core/briefing.d.ts +8 -0
  13. package/dist/core/briefing.d.ts.map +1 -0
  14. package/dist/core/briefing.js +91 -0
  15. package/dist/core/briefing.js.map +1 -0
  16. package/dist/core/capture-flag.d.ts +5 -0
  17. package/dist/core/capture-flag.d.ts.map +1 -0
  18. package/dist/core/capture-flag.js +10 -0
  19. package/dist/core/capture-flag.js.map +1 -0
  20. package/dist/core/conflict-candidates.d.ts +20 -0
  21. package/dist/core/conflict-candidates.d.ts.map +1 -0
  22. package/dist/core/conflict-candidates.js +79 -0
  23. package/dist/core/conflict-candidates.js.map +1 -0
  24. package/dist/core/conflict-judge.d.ts +47 -0
  25. package/dist/core/conflict-judge.d.ts.map +1 -0
  26. package/dist/core/conflict-judge.js +189 -0
  27. package/dist/core/conflict-judge.js.map +1 -0
  28. package/dist/core/digest-validator.d.ts.map +1 -1
  29. package/dist/core/digest-validator.js +3 -5
  30. package/dist/core/digest-validator.js.map +1 -1
  31. package/dist/core/doctor.d.ts +2 -0
  32. package/dist/core/doctor.d.ts.map +1 -1
  33. package/dist/core/doctor.js +34 -56
  34. package/dist/core/doctor.js.map +1 -1
  35. package/dist/core/dreamer.d.ts +5 -2
  36. package/dist/core/dreamer.d.ts.map +1 -1
  37. package/dist/core/dreamer.js +108 -25
  38. package/dist/core/dreamer.js.map +1 -1
  39. package/dist/core/embedder.d.ts +5 -4
  40. package/dist/core/embedder.d.ts.map +1 -1
  41. package/dist/core/embedder.js +16 -8
  42. package/dist/core/embedder.js.map +1 -1
  43. package/dist/core/failure-analyzer.d.ts.map +1 -1
  44. package/dist/core/failure-analyzer.js +7 -12
  45. package/dist/core/failure-analyzer.js.map +1 -1
  46. package/dist/core/install-channel.d.ts +1 -1
  47. package/dist/core/install-channel.d.ts.map +1 -1
  48. package/dist/core/install-channel.js +16 -5
  49. package/dist/core/install-channel.js.map +1 -1
  50. package/dist/core/install-hooks.d.ts +5 -0
  51. package/dist/core/install-hooks.d.ts.map +1 -1
  52. package/dist/core/install-hooks.js +0 -0
  53. package/dist/core/install-hooks.js.map +1 -1
  54. package/dist/core/json-utils.d.ts +1 -0
  55. package/dist/core/json-utils.d.ts.map +1 -1
  56. package/dist/core/json-utils.js +19 -10
  57. package/dist/core/json-utils.js.map +1 -1
  58. package/dist/core/kg-backfill.d.ts +0 -1
  59. package/dist/core/kg-backfill.d.ts.map +1 -1
  60. package/dist/core/kg-backfill.js +0 -3
  61. package/dist/core/kg-backfill.js.map +1 -1
  62. package/dist/core/lifecycle.d.ts.map +1 -1
  63. package/dist/core/lifecycle.js +14 -21
  64. package/dist/core/lifecycle.js.map +1 -1
  65. package/dist/core/memory-tool.d.ts.map +1 -1
  66. package/dist/core/memory-tool.js +4 -4
  67. package/dist/core/memory-tool.js.map +1 -1
  68. package/dist/core/operations.d.ts.map +1 -1
  69. package/dist/core/operations.js +22 -13
  70. package/dist/core/operations.js.map +1 -1
  71. package/dist/core/prompt-safety.d.ts +1 -0
  72. package/dist/core/prompt-safety.d.ts.map +1 -1
  73. package/dist/core/prompt-safety.js +7 -0
  74. package/dist/core/prompt-safety.js.map +1 -1
  75. package/dist/core/schema-export.d.ts.map +1 -1
  76. package/dist/core/schema-export.js +31 -0
  77. package/dist/core/schema-export.js.map +1 -1
  78. package/dist/core/setup.d.ts +29 -0
  79. package/dist/core/setup.d.ts.map +1 -0
  80. package/dist/core/setup.js +127 -0
  81. package/dist/core/setup.js.map +1 -0
  82. package/dist/core/task-state-store.d.ts +17 -0
  83. package/dist/core/task-state-store.d.ts.map +1 -0
  84. package/dist/core/task-state-store.js +45 -0
  85. package/dist/core/task-state-store.js.map +1 -0
  86. package/dist/core/task-state.d.ts +19 -0
  87. package/dist/core/task-state.d.ts.map +1 -0
  88. package/dist/core/task-state.js +91 -0
  89. package/dist/core/task-state.js.map +1 -0
  90. package/dist/core/time-utils.d.ts +2 -0
  91. package/dist/core/time-utils.d.ts.map +1 -0
  92. package/dist/core/time-utils.js +14 -0
  93. package/dist/core/time-utils.js.map +1 -0
  94. package/dist/core/title.d.ts +5 -0
  95. package/dist/core/title.d.ts.map +1 -0
  96. package/dist/core/title.js +14 -0
  97. package/dist/core/title.js.map +1 -0
  98. package/dist/core/transcript-source.d.ts.map +1 -1
  99. package/dist/core/transcript-source.js +2 -3
  100. package/dist/core/transcript-source.js.map +1 -1
  101. package/dist/core/types.d.ts +4 -0
  102. package/dist/core/types.d.ts.map +1 -1
  103. package/dist/core/work-topology.d.ts +33 -0
  104. package/dist/core/work-topology.d.ts.map +1 -0
  105. package/dist/core/work-topology.js +183 -0
  106. package/dist/core/work-topology.js.map +1 -0
  107. package/dist/db.d.ts +2 -7
  108. package/dist/db.d.ts.map +1 -1
  109. package/dist/db.js +144 -284
  110. package/dist/db.js.map +1 -1
  111. package/dist/knowledge-graph.d.ts +1 -0
  112. package/dist/knowledge-graph.d.ts.map +1 -1
  113. package/dist/knowledge-graph.js +50 -40
  114. package/dist/knowledge-graph.js.map +1 -1
  115. package/dist/skills-manifest.json +48 -18
  116. package/dist/storage/conflicts.d.ts.map +1 -1
  117. package/dist/storage/conflicts.js +2 -7
  118. package/dist/storage/conflicts.js.map +1 -1
  119. package/dist/storage/fts-index.d.ts +4 -2
  120. package/dist/storage/fts-index.d.ts.map +1 -1
  121. package/dist/storage/fts-index.js +16 -4
  122. package/dist/storage/fts-index.js.map +1 -1
  123. package/dist/storage/schema.d.ts +20 -0
  124. package/dist/storage/schema.d.ts.map +1 -0
  125. package/dist/storage/schema.js +274 -0
  126. package/dist/storage/schema.js.map +1 -0
  127. package/dist/transports/cli/cli.d.ts +1 -4
  128. package/dist/transports/cli/cli.d.ts.map +1 -1
  129. package/dist/transports/cli/cli.js +382 -6
  130. package/dist/transports/cli/cli.js.map +1 -1
  131. package/dist/transports/http/server.d.ts.map +1 -1
  132. package/dist/transports/http/server.js +208 -307
  133. package/dist/transports/http/server.js.map +1 -1
  134. package/dist/transports/mcp/handlers.d.ts +46 -0
  135. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  136. package/dist/transports/mcp/handlers.js +57 -2
  137. package/dist/transports/mcp/handlers.js.map +1 -1
  138. package/dist/transports/schemas.d.ts +21 -10
  139. package/dist/transports/schemas.d.ts.map +1 -1
  140. package/dist/transports/schemas.js +26 -8
  141. package/dist/transports/schemas.js.map +1 -1
  142. package/llms-install.md +138 -0
  143. package/package.json +14 -9
  144. package/scripts/hooks/_generated/capture-flag.js +17 -0
  145. package/scripts/hooks/_generated/fts-index.js +16 -4
  146. package/scripts/hooks/_generated/schema.js +281 -0
  147. package/scripts/hooks/_generated/task-state.js +98 -0
  148. package/scripts/hooks/_generated/time-utils.js +21 -0
  149. package/scripts/hooks/_generated/title.js +21 -0
  150. package/scripts/hooks/_generated/work-topology.js +190 -0
  151. package/scripts/hooks/_shared.js +122 -478
  152. package/scripts/hooks/post-commit.js +4 -1
  153. package/scripts/hooks/pre-compact.js +13 -1
  154. package/scripts/hooks/pre-edit-recall.js +5 -3
  155. package/scripts/hooks/session-start.js +135 -59
  156. package/scripts/hooks/session-summary.js +59 -24
  157. package/skills/memesh/SKILL.md +97 -76
  158. package/README.es.md +0 -467
  159. package/README.fr.md +0 -459
  160. package/README.ja.md +0 -467
  161. package/README.ko.md +0 -467
  162. package/README.pt.md +0 -459
  163. package/README.th.md +0 -460
  164. package/README.vi.md +0 -459
  165. package/README.zh-CN.md +0 -466
  166. package/dist/cli/view.d.ts +0 -3
  167. package/dist/cli/view.d.ts.map +0 -1
  168. package/dist/cli/view.js +0 -523
  169. package/dist/cli/view.js.map +0 -1
@@ -203,20 +203,14 @@ app.get('/v1/health', (_req, res) => {
203
203
  }
204
204
  }
205
205
  });
206
- app.get('/v1/doctor', async (_req, res) => {
207
- try {
208
- const { runDoctor } = await import('../../core/doctor.js');
209
- const result = await runDoctor({
210
- packageRoot,
211
- packageVersion,
212
- });
213
- const safe = JSON.parse(redactUserPaths(redactSecrets(JSON.stringify(result))));
214
- res.json({ success: true, data: safe });
215
- }
216
- catch (err) {
217
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
218
- }
219
- });
206
+ app.get('/v1/doctor', (_req, res) => handleGet(res, async () => {
207
+ const { runDoctor } = await import('../../core/doctor.js');
208
+ const result = await runDoctor({
209
+ packageRoot,
210
+ packageVersion,
211
+ });
212
+ return JSON.parse(redactUserPaths(redactSecrets(JSON.stringify(result))));
213
+ }));
220
214
  function requireJsonBody(req, res) {
221
215
  if (req.body !== undefined)
222
216
  return true;
@@ -228,46 +222,73 @@ function requireJsonBody(req, res) {
228
222
  });
229
223
  return false;
230
224
  }
231
- function handlePost(schema, req, res, handler) {
232
- if (!requireJsonBody(req, res))
225
+ class HttpError extends Error {
226
+ status;
227
+ code;
228
+ constructor(status, code, message) {
229
+ super(message);
230
+ this.status = status;
231
+ this.code = code;
232
+ }
233
+ }
234
+ function sendError(res, err, fallbackStatus, fallbackCode) {
235
+ if (err instanceof HttpError) {
236
+ res.status(err.status).json({ success: false, errorCode: err.code, error: err.message });
233
237
  return;
234
- const parsed = schema.safeParse(req.body);
238
+ }
239
+ res.status(fallbackStatus).json({
240
+ success: false,
241
+ errorCode: fallbackCode,
242
+ error: err instanceof Error ? err.message : String(err),
243
+ });
244
+ }
245
+ function zodErrorText(error) {
246
+ return error.issues.map(i => (i.path.length ? `${i.path.join('.')}: ${i.message}` : i.message)).join('; ');
247
+ }
248
+ function parseQuery(schema, req, res) {
249
+ const parsed = schema.safeParse(req.query);
250
+ if (!parsed.success) {
251
+ res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: zodErrorText(parsed.error) });
252
+ return null;
253
+ }
254
+ return parsed.data;
255
+ }
256
+ function requireIdParam(req, res) {
257
+ const id = parseInt(String(req.params.id), 10);
258
+ if (!Number.isInteger(id) || id < 1) {
259
+ res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
260
+ return null;
261
+ }
262
+ return id;
263
+ }
264
+ function handlePost(schema, req, res, handler, opts) {
265
+ if (!opts?.allowEmptyBody && !requireJsonBody(req, res))
266
+ return;
267
+ const parsed = schema.safeParse(req.body ?? (opts?.allowEmptyBody ? {} : undefined));
235
268
  if (!parsed.success) {
236
269
  res.status(400).json({
237
270
  success: false,
238
271
  errorCode: 'validation.bad-body',
239
- error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
272
+ error: zodErrorText(parsed.error),
240
273
  });
241
274
  return;
242
275
  }
243
276
  Promise.resolve()
244
277
  .then(() => handler(parsed.data))
245
278
  .then((data) => res.json({ success: true, data }))
246
- .catch((err) => res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) }));
279
+ .catch((err) => sendError(res, err, opts?.errorStatus ?? 400, opts?.errorCode ?? 'operation.failed'));
247
280
  }
248
281
  function handleGet(res, produce) {
249
282
  Promise.resolve()
250
283
  .then(produce)
251
284
  .then((data) => res.json({ success: true, data }))
252
- .catch((err) => res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) }));
285
+ .catch((err) => sendError(res, err, 500, 'server.internal'));
253
286
  }
254
287
  app.post('/v1/remember', (req, res) => handlePost(RememberBody, req, res, (data) => remember({ ...data, sourceHost: 'http' })));
255
- app.post('/v1/recall', async (req, res) => {
256
- if (!requireJsonBody(req, res))
257
- return;
258
- const parsed = RecallBody.safeParse(req.body);
259
- if (!parsed.success) {
260
- res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
261
- return;
262
- }
263
- try {
264
- const { entities, conflicts } = await recallWithConflicts(parsed.data);
265
- res.json({ success: true, data: conflicts.length > 0 ? { entities, conflicts } : entities });
266
- }
267
- catch (err) {
268
- res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) });
269
- }
270
- });
288
+ app.post('/v1/recall', (req, res) => handlePost(RecallBody, req, res, async (data) => {
289
+ const { entities, conflicts } = await recallWithConflicts(data);
290
+ return conflicts.length > 0 ? { entities, conflicts } : { entities };
291
+ }));
271
292
  app.post('/v1/forget', (req, res) => handlePost(ForgetBody, req, res, forget));
272
293
  app.post('/v1/consolidate', (_req, res) => {
273
294
  res.status(410).json({ success: false, errorCode: 'route.retired', error: RETIRED_ROUTES['/v1/consolidate'] });
@@ -305,16 +326,11 @@ function preserveFallbackApiKeys(incoming, stored) {
305
326
  return clean;
306
327
  });
307
328
  }
308
- app.get('/v1/config', (_req, res) => {
309
- try {
310
- const config = readConfig();
311
- const caps = detectCapabilities(config);
312
- res.json({ success: true, data: { config: maskLlmSecrets(config), capabilities: maskLlmSecrets(caps) } });
313
- }
314
- catch (err) {
315
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
316
- }
317
- });
329
+ app.get('/v1/config', (_req, res) => handleGet(res, () => {
330
+ const config = readConfig();
331
+ const caps = detectCapabilities(config);
332
+ return { config: maskLlmSecrets(config), capabilities: maskLlmSecrets(caps) };
333
+ }));
318
334
  const ConfigBody = z.object({
319
335
  llm: z.union([
320
336
  z.object({
@@ -340,108 +356,71 @@ const ConfigBody = z.object({
340
356
  .optional(),
341
357
  setupCompleted: z.boolean().optional(),
342
358
  }).strip();
343
- app.post('/v1/config', async (req, res) => {
344
- if (!requireJsonBody(req, res))
345
- return;
346
- const parsed = ConfigBody.safeParse(req.body);
347
- if (!parsed.success) {
348
- res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
349
- return;
350
- }
351
- try {
352
- const before = readConfig();
353
- if (parsed.data.llm && parsed.data.llm.apiKey === API_KEY_MASK) {
354
- if (before.llm && before.llm.provider === parsed.data.llm.provider && before.llm.apiKey) {
355
- parsed.data.llm.apiKey = before.llm.apiKey;
356
- }
357
- else {
358
- delete parsed.data.llm.apiKey;
359
- }
359
+ app.post('/v1/config', (req, res) => handlePost(ConfigBody, req, res, (data) => {
360
+ const before = readConfig();
361
+ if (data.llm && data.llm.apiKey === API_KEY_MASK) {
362
+ if (before.llm && before.llm.provider === data.llm.provider && before.llm.apiKey) {
363
+ data.llm.apiKey = before.llm.apiKey;
360
364
  }
361
- if (parsed.data.llmFallbacks) {
362
- parsed.data.llmFallbacks = preserveFallbackApiKeys(parsed.data.llmFallbacks, before.llmFallbacks);
365
+ else {
366
+ delete data.llm.apiKey;
363
367
  }
364
- const updated = updateConfig(parsed.data);
365
- res.json({ success: true, data: maskLlmSecrets(updated) });
366
368
  }
367
- catch (err) {
368
- res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) });
369
+ if (data.llmFallbacks) {
370
+ data.llmFallbacks = preserveFallbackApiKeys(data.llmFallbacks, before.llmFallbacks);
369
371
  }
370
- });
372
+ const updated = updateConfig(data);
373
+ return maskLlmSecrets(updated);
374
+ }));
371
375
  const ConfigTestBody = z.object({
372
376
  provider: z.enum(['anthropic', 'openai', 'ollama']),
373
377
  apiKey: z.string().max(500).optional(),
374
378
  host: z.string().max(500).optional(),
375
379
  fallbackIndex: z.number().int().nonnegative().optional(),
376
380
  });
377
- app.post('/v1/config/test', async (req, res) => {
378
- if (!requireJsonBody(req, res))
379
- return;
380
- const parsed = ConfigTestBody.safeParse(req.body);
381
- if (!parsed.success) {
382
- res.status(400).json({
383
- success: false,
384
- errorCode: 'validation.bad-body',
385
- error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
386
- });
387
- return;
388
- }
389
- try {
390
- const { probeProvider } = await import('../../core/llm-validator.js');
391
- const { provider, host, fallbackIndex } = parsed.data;
392
- let { apiKey } = parsed.data;
393
- if (!apiKey && (provider === 'anthropic' || provider === 'openai')) {
394
- const existing = readConfig();
395
- if (typeof fallbackIndex === 'number') {
396
- const fb = existing.llmFallbacks?.[fallbackIndex];
397
- if (fb && fb.provider === provider && fb.apiKey)
398
- apiKey = fb.apiKey;
399
- }
400
- else if (existing.llm?.provider === provider && existing.llm.apiKey) {
401
- apiKey = existing.llm.apiKey;
402
- }
381
+ app.post('/v1/config/test', (req, res) => handlePost(ConfigTestBody, req, res, async (data) => {
382
+ const { probeProvider } = await import('../../core/llm-validator.js');
383
+ const { provider, host, fallbackIndex } = data;
384
+ let { apiKey } = data;
385
+ if (!apiKey && (provider === 'anthropic' || provider === 'openai')) {
386
+ const existing = readConfig();
387
+ if (typeof fallbackIndex === 'number') {
388
+ const fb = existing.llmFallbacks?.[fallbackIndex];
389
+ if (fb && fb.provider === provider && fb.apiKey)
390
+ apiKey = fb.apiKey;
391
+ }
392
+ else if (existing.llm?.provider === provider && existing.llm.apiKey) {
393
+ apiKey = existing.llm.apiKey;
403
394
  }
404
- const result = await probeProvider(provider, apiKey, host);
405
- res.json({ success: true, data: result });
406
- }
407
- catch (err) {
408
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
409
- }
410
- });
411
- app.get('/v1/update-status', async (req, res) => {
412
- try {
413
- const cached = req.query.cached === '1' || req.query.cached === 'true';
414
- const install = getCurrentInstallChannel({ packageRoot });
415
- const installSupport = getInstallChannelSupport(install);
416
- const update = await getUpdateCheck(packageVersion, { preferFresh: !cached });
417
- res.json({
418
- success: true,
419
- data: {
420
- currentVersion: packageVersion,
421
- latestVersion: update?.latestVersion ?? null,
422
- checkedAt: update?.checkedAt ?? null,
423
- lastAttemptAt: update?.lastAttemptAt ?? null,
424
- lastSuccessfulCheckAt: update?.lastSuccessfulCheckAt ?? null,
425
- lastError: update?.lastError ?? null,
426
- updateAvailable: update?.updateAvailable ?? false,
427
- checkSucceeded: update?.checkSucceeded ?? false,
428
- source: update?.source ?? null,
429
- freshness: update?.freshness ?? 'unavailable',
430
- installChannel: installSupport.channel,
431
- canSelfUpdate: installSupport.canSelfUpdate,
432
- recommendedCommand: (update?.currentVersionDeprecated
433
- && update.latestVersion
434
- && update.latestVersion === update.currentVersion
435
- && update.freshness === 'fresh') ? null : installSupport.recommendedCommand,
436
- currentVersionDeprecated: update?.currentVersionDeprecated ?? false,
437
- deprecationMessage: update?.deprecationMessage ?? null,
438
- },
439
- });
440
- }
441
- catch (err) {
442
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
443
395
  }
444
- });
396
+ return probeProvider(provider, apiKey, host);
397
+ }, { errorStatus: 500, errorCode: 'server.internal' }));
398
+ app.get('/v1/update-status', (req, res) => handleGet(res, async () => {
399
+ const cached = req.query.cached === '1' || req.query.cached === 'true';
400
+ const install = getCurrentInstallChannel({ packageRoot });
401
+ const installSupport = getInstallChannelSupport(install);
402
+ const update = await getUpdateCheck(packageVersion, { preferFresh: !cached });
403
+ return {
404
+ currentVersion: packageVersion,
405
+ latestVersion: update?.latestVersion ?? null,
406
+ checkedAt: update?.checkedAt ?? null,
407
+ lastAttemptAt: update?.lastAttemptAt ?? null,
408
+ lastSuccessfulCheckAt: update?.lastSuccessfulCheckAt ?? null,
409
+ lastError: update?.lastError ?? null,
410
+ updateAvailable: update?.updateAvailable ?? false,
411
+ checkSucceeded: update?.checkSucceeded ?? false,
412
+ source: update?.source ?? null,
413
+ freshness: update?.freshness ?? 'unavailable',
414
+ installChannel: installSupport.channel,
415
+ canSelfUpdate: installSupport.canSelfUpdate,
416
+ recommendedCommand: (update?.currentVersionDeprecated
417
+ && update.latestVersion
418
+ && update.latestVersion === update.currentVersion
419
+ && update.freshness === 'fresh') ? null : installSupport.recommendedCommand,
420
+ currentVersionDeprecated: update?.currentVersionDeprecated ?? false,
421
+ deprecationMessage: update?.deprecationMessage ?? null,
422
+ };
423
+ }));
445
424
  app.get('/v1/graph', (_req, res) => handleGet(res, () => computeGraph(getDatabase())));
446
425
  app.get('/v1/stats', (_req, res) => handleGet(res, () => computeStats(getDatabase())));
447
426
  app.get('/v1/analytics', (_req, res) => handleGet(res, () => computeAnalytics(getDatabase())));
@@ -451,80 +430,51 @@ app.get('/v1/analytics/pm', (req, res) => {
451
430
  const windowDays = Number.isFinite(window) && window > 0 ? window : 30;
452
431
  handleGet(res, () => computePmAnalytics(getDatabase(), windowDays));
453
432
  });
454
- app.post('/v1/demo/seed', async (_req, res) => {
455
- try {
456
- const { seedDemo } = await import('../../core/demo.js');
457
- const data = seedDemo(getDatabase());
458
- res.json({ success: true, data });
459
- }
460
- catch (err) {
461
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
462
- }
463
- });
464
- app.post('/v1/demo/reset', async (_req, res) => {
465
- try {
466
- const { seedDemo } = await import('../../core/demo.js');
467
- const data = seedDemo(getDatabase(), { reset: true });
468
- res.json({ success: true, data });
469
- }
470
- catch (err) {
471
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
472
- }
473
- });
433
+ app.post('/v1/demo/seed', (_req, res) => handleGet(res, async () => {
434
+ const { seedDemo } = await import('../../core/demo.js');
435
+ return seedDemo(getDatabase());
436
+ }));
437
+ app.post('/v1/demo/reset', (_req, res) => handleGet(res, async () => {
438
+ const { seedDemo } = await import('../../core/demo.js');
439
+ return seedDemo(getDatabase(), { reset: true });
440
+ }));
474
441
  app.get('/v1/projects', (_req, res) => handleGet(res, () => computeProjects(getDatabase())));
475
442
  app.get('/v1/patterns', (_req, res) => handleGet(res, () => computePatterns(getDatabase())));
476
443
  const TelemetryQuerySchema = z.object({
477
444
  window: z.coerce.number().int().min(1).max(365).default(30),
478
445
  });
479
- app.get('/v1/telemetry', async (req, res) => {
480
- try {
481
- const parsed = TelemetryQuerySchema.safeParse(req.query);
482
- if (!parsed.success) {
483
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: parsed.error.issues.map(i => i.message).join('; ') });
484
- return;
485
- }
446
+ app.get('/v1/telemetry', (req, res) => {
447
+ const query = parseQuery(TelemetryQuerySchema, req, res);
448
+ if (!query)
449
+ return;
450
+ handleGet(res, async () => {
486
451
  const { summariseTelemetry } = await import('../../core/llm-telemetry.js');
487
- const summaries = summariseTelemetry(parsed.data.window);
488
- res.json({ success: true, data: { window_days: parsed.data.window, summaries } });
489
- }
490
- catch (err) {
491
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
492
- }
452
+ return { window_days: query.window, summaries: summariseTelemetry(query.window) };
453
+ });
493
454
  });
494
455
  const DreamProposalsQuerySchema = z.object({
495
456
  status: z.enum(['pending', 'applied', 'rejected', 'all']).default('pending'),
496
457
  });
497
458
  app.get('/v1/dream/proposals', (req, res) => {
498
- try {
499
- const parsed = DreamProposalsQuerySchema.safeParse(req.query);
500
- if (!parsed.success) {
501
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: parsed.error.issues.map(i => i.message).join('; ') });
502
- return;
503
- }
504
- const status = parsed.data.status;
505
- import('../../core/dreamer.js').then(({ listProposals }) => {
506
- const db = getDatabase();
507
- const rows = status === 'all'
508
- ? [...listProposals(db, 'pending'), ...listProposals(db, 'applied'), ...listProposals(db, 'rejected')]
509
- : listProposals(db, status);
510
- res.json({ success: true, data: rows });
511
- }).catch((err) => res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) }));
512
- }
513
- catch (err) {
514
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
515
- }
459
+ const query = parseQuery(DreamProposalsQuerySchema, req, res);
460
+ if (!query)
461
+ return;
462
+ handleGet(res, async () => {
463
+ const { listProposals } = await import('../../core/dreamer.js');
464
+ const db = getDatabase();
465
+ return query.status === 'all'
466
+ ? [...listProposals(db, 'pending'), ...listProposals(db, 'applied'), ...listProposals(db, 'rejected')]
467
+ : listProposals(db, query.status);
468
+ });
516
469
  });
517
470
  app.get('/v1/dream/proposals/:id', (req, res) => {
518
- const id = parseInt(req.params.id, 10);
519
- if (!Number.isInteger(id) || id < 1) {
520
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
471
+ const id = requireIdParam(req, res);
472
+ if (id === null)
521
473
  return;
522
- }
523
- try {
474
+ handleGet(res, () => {
524
475
  const row = getDatabase().prepare('SELECT id, project, cluster_key, source_ids, proposed_digest, llm_model, prompt_version, status, reason, created_at, reviewed_at FROM dream_proposals WHERE id = ?').get(id);
525
476
  if (!row) {
526
- res.status(404).json({ success: false, errorCode: 'resource.not-found', error: `proposal #${id} not found` });
527
- return;
477
+ throw new HttpError(404, 'resource.not-found', `proposal #${id} not found`);
528
478
  }
529
479
  let digest = null;
530
480
  let sourceIds = [];
@@ -536,11 +486,8 @@ app.get('/v1/dream/proposals/:id', (req, res) => {
536
486
  sourceIds = JSON.parse(row.source_ids);
537
487
  }
538
488
  catch { }
539
- res.json({ success: true, data: { ...row, proposed_digest: digest, source_ids: sourceIds } });
540
- }
541
- catch (err) {
542
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
543
- }
489
+ return { ...row, proposed_digest: digest, source_ids: sourceIds };
490
+ });
544
491
  });
545
492
  const DreamRunBody = z.object({
546
493
  project: z.string().min(1).max(100).optional(),
@@ -548,96 +495,64 @@ const DreamRunBody = z.object({
548
495
  maxLlmCalls: z.number().int().min(1).max(20).default(5),
549
496
  validate: z.boolean().default(false),
550
497
  });
551
- app.post('/v1/dream/run', async (req, res) => {
552
- const parsed = DreamRunBody.safeParse(req.body ?? {});
553
- if (!parsed.success) {
554
- res.status(400).json({
555
- success: false,
556
- errorCode: 'validation.bad-body',
557
- error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
558
- });
559
- return;
560
- }
561
- try {
562
- const { runDreamer } = await import('../../core/dreamer.js');
563
- const caps = detectCapabilities();
564
- const llm = caps.llm;
565
- if (!llm) {
566
- res.status(400).json({
567
- success: false,
568
- errorCode: 'llm.not-configured',
569
- error: 'No LLM configured — dream run requires Smart Mode. Configure a provider in Settings.',
570
- });
571
- return;
572
- }
573
- const result = await runDreamer(getDatabase(), llm, {
574
- project: parsed.data.project,
575
- windowDays: parsed.data.windowDays,
576
- maxLlmCalls: parsed.data.maxLlmCalls,
577
- fallbacks: caps.llmFallbacks,
578
- validateBeforeStage: parsed.data.validate,
579
- });
580
- res.json({ success: true, data: result });
581
- }
582
- catch (err) {
583
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
584
- }
585
- });
586
- app.post('/v1/dream/proposals/:id/accept', async (req, res) => {
587
- const id = parseInt(req.params.id, 10);
588
- if (!Number.isInteger(id) || id < 1) {
589
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
498
+ app.post('/v1/dream/run', (req, res) => handlePost(DreamRunBody, req, res, async (data) => {
499
+ const { runDreamer } = await import('../../core/dreamer.js');
500
+ const caps = detectCapabilities();
501
+ const llm = caps.llm;
502
+ if (!llm) {
503
+ throw new HttpError(400, 'llm.not-configured', 'No LLM configured — dream run requires Smart Mode. Configure a provider in Settings.');
504
+ }
505
+ return runDreamer(getDatabase(), llm, {
506
+ project: data.project,
507
+ windowDays: data.windowDays,
508
+ maxLlmCalls: data.maxLlmCalls,
509
+ fallbacks: caps.llmFallbacks,
510
+ validateBeforeStage: data.validate,
511
+ });
512
+ }, { allowEmptyBody: true, errorStatus: 500, errorCode: 'server.internal' }));
513
+ app.post('/v1/dream/proposals/:id/accept', (req, res) => {
514
+ const id = requireIdParam(req, res);
515
+ if (id === null)
590
516
  return;
591
- }
592
- let NothingToClaim;
593
- try {
517
+ handleGet(res, async () => {
594
518
  const dreamer = await import('../../core/dreamer.js');
595
- NothingToClaim = dreamer.NothingToClaimError;
596
- const kg = new KnowledgeGraph(getDatabase());
597
- const result = dreamer.applyProposal(getDatabase(), id, kg);
598
- res.json({ success: true, data: result });
599
- }
600
- catch (err) {
601
- const msg = err instanceof Error ? err.message : String(err);
602
- if (NothingToClaim && err instanceof NothingToClaim) {
603
- res.status(400).json({ success: false, errorCode: 'operation.failed', error: msg });
604
- }
605
- else if (/not found or not pending/.test(msg)) {
606
- res.status(404).json({ success: false, errorCode: 'resource.not-found', error: msg });
519
+ try {
520
+ const kg = new KnowledgeGraph(getDatabase());
521
+ return dreamer.applyProposal(getDatabase(), id, kg);
607
522
  }
608
- else {
609
- res.status(500).json({ success: false, errorCode: 'server.internal', error: msg });
523
+ catch (err) {
524
+ const msg = err instanceof Error ? err.message : String(err);
525
+ if (err instanceof dreamer.NothingToClaimError) {
526
+ throw new HttpError(400, 'operation.failed', msg);
527
+ }
528
+ if (/not found or not pending/.test(msg)) {
529
+ throw new HttpError(404, 'resource.not-found', msg);
530
+ }
531
+ throw err;
610
532
  }
611
- }
533
+ });
612
534
  });
613
535
  const RejectBodySchema = z.object({
614
536
  reason: z.string().max(500).optional(),
615
537
  });
616
- app.post('/v1/dream/proposals/:id/reject', async (req, res) => {
617
- const id = parseInt(req.params.id, 10);
618
- if (!Number.isInteger(id) || id < 1) {
619
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
620
- return;
621
- }
622
- const parsed = RejectBodySchema.safeParse(req.body ?? {});
623
- if (!parsed.success) {
624
- res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => i.message).join('; ') });
538
+ app.post('/v1/dream/proposals/:id/reject', (req, res) => {
539
+ const id = requireIdParam(req, res);
540
+ if (id === null)
625
541
  return;
626
- }
627
- try {
542
+ handlePost(RejectBodySchema, req, res, async (data) => {
628
543
  const { rejectProposal } = await import('../../core/dreamer.js');
629
- rejectProposal(getDatabase(), id, parsed.data.reason);
630
- res.json({ success: true, data: { id, status: 'rejected' } });
631
- }
632
- catch (err) {
633
- const msg = err instanceof Error ? err.message : String(err);
634
- if (/not found or not pending/.test(msg)) {
635
- res.status(404).json({ success: false, errorCode: 'resource.not-found', error: msg });
544
+ try {
545
+ rejectProposal(getDatabase(), id, data.reason);
636
546
  }
637
- else {
638
- res.status(500).json({ success: false, errorCode: 'server.internal', error: msg });
547
+ catch (err) {
548
+ const msg = err instanceof Error ? err.message : String(err);
549
+ if (/not found or not pending/.test(msg)) {
550
+ throw new HttpError(404, 'resource.not-found', msg);
551
+ }
552
+ throw err;
639
553
  }
640
- }
554
+ return { id, status: 'rejected' };
555
+ }, { allowEmptyBody: true, errorStatus: 500, errorCode: 'server.internal' });
641
556
  });
642
557
  const EntitiesQuerySchema = z.object({
643
558
  type: z.string().min(1).max(100).optional(),
@@ -645,40 +560,26 @@ const EntitiesQuerySchema = z.object({
645
560
  status: z.enum(['all', 'active']).optional(),
646
561
  });
647
562
  app.get('/v1/entities', (req, res) => {
648
- try {
649
- const parsed = EntitiesQuerySchema.safeParse(req.query);
650
- if (!parsed.success) {
651
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: `Invalid query: ${parsed.error.message}` });
652
- return;
653
- }
654
- const { type: typeFilter, limit, status } = parsed.data;
563
+ const query = parseQuery(EntitiesQuerySchema, req, res);
564
+ if (!query)
565
+ return;
566
+ handleGet(res, () => {
567
+ const { type: typeFilter, limit, status } = query;
655
568
  const includeArchived = status === 'all';
656
- const db = getDatabase();
657
- const kg = new KnowledgeGraph(db);
658
- const entities = typeFilter
569
+ const kg = new KnowledgeGraph(getDatabase());
570
+ return typeFilter
659
571
  ? kg.listByType(typeFilter, limit, includeArchived)
660
572
  : kg.listRecent(limit, includeArchived);
661
- res.json({ success: true, data: entities });
662
- }
663
- catch (err) {
664
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
665
- }
666
- });
667
- app.get('/v1/entities/:name', (req, res) => {
668
- try {
669
- const db = getDatabase();
670
- const kg = new KnowledgeGraph(db);
671
- const entity = kg.getEntity(req.params.name);
672
- if (!entity) {
673
- res.status(404).json({ success: false, errorCode: 'resource.not-found', error: `Entity "${req.params.name}" not found` });
674
- return;
675
- }
676
- res.json({ success: true, data: entity });
677
- }
678
- catch (err) {
679
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
680
- }
573
+ });
681
574
  });
575
+ app.get('/v1/entities/:name', (req, res) => handleGet(res, () => {
576
+ const kg = new KnowledgeGraph(getDatabase());
577
+ const entity = kg.getEntity(String(req.params.name));
578
+ if (!entity) {
579
+ throw new HttpError(404, 'resource.not-found', `Entity "${String(req.params.name)}" not found`);
580
+ }
581
+ return entity;
582
+ }));
682
583
  const HOST = process.env.MEMESH_HTTP_HOST || '127.0.0.1';
683
584
  const PORT = parseInt(process.env.MEMESH_HTTP_PORT || '3737');
684
585
  const ALLOW_REMOTE_BY_ENV = /^(1|true|yes)$/i.test(process.env.MEMESH_HTTP_ALLOW_REMOTE || '');