@pcircle/memesh 4.5.0 → 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 (256) hide show
  1. package/.claude-plugin/marketplace.json +5 -3
  2. package/.claude-plugin/plugin.json +6 -4
  3. package/.mcp.json +1 -1
  4. package/AGENTS.md +95 -0
  5. package/README.de.md +130 -39
  6. package/README.md +188 -43
  7. package/README.zh-TW.md +133 -41
  8. package/dashboard/dist/index.html +9 -9
  9. package/dist/cli/view-live.js +3 -3
  10. package/dist/core/analytics.d.ts +3 -3
  11. package/dist/core/analytics.d.ts.map +1 -1
  12. package/dist/core/analytics.js.map +1 -1
  13. package/dist/core/auto-tagger.d.ts.map +1 -1
  14. package/dist/core/auto-tagger.js +4 -9
  15. package/dist/core/auto-tagger.js.map +1 -1
  16. package/dist/core/briefing.d.ts +8 -0
  17. package/dist/core/briefing.d.ts.map +1 -0
  18. package/dist/core/briefing.js +91 -0
  19. package/dist/core/briefing.js.map +1 -0
  20. package/dist/core/capture-flag.d.ts +5 -0
  21. package/dist/core/capture-flag.d.ts.map +1 -0
  22. package/dist/core/capture-flag.js +10 -0
  23. package/dist/core/capture-flag.js.map +1 -0
  24. package/dist/core/config.d.ts +0 -1
  25. package/dist/core/config.d.ts.map +1 -1
  26. package/dist/core/config.js +2 -1
  27. package/dist/core/config.js.map +1 -1
  28. package/dist/core/conflict-candidates.d.ts +20 -0
  29. package/dist/core/conflict-candidates.d.ts.map +1 -0
  30. package/dist/core/conflict-candidates.js +79 -0
  31. package/dist/core/conflict-candidates.js.map +1 -0
  32. package/dist/core/conflict-judge.d.ts +47 -0
  33. package/dist/core/conflict-judge.d.ts.map +1 -0
  34. package/dist/core/conflict-judge.js +189 -0
  35. package/dist/core/conflict-judge.js.map +1 -0
  36. package/dist/core/demo.d.ts +2 -2
  37. package/dist/core/demo.d.ts.map +1 -1
  38. package/dist/core/demo.js.map +1 -1
  39. package/dist/core/digest-validator.d.ts.map +1 -1
  40. package/dist/core/digest-validator.js +3 -5
  41. package/dist/core/digest-validator.js.map +1 -1
  42. package/dist/core/doctor.d.ts +3 -0
  43. package/dist/core/doctor.d.ts.map +1 -1
  44. package/dist/core/doctor.js +207 -85
  45. package/dist/core/doctor.js.map +1 -1
  46. package/dist/core/dreamer.d.ts +20 -9
  47. package/dist/core/dreamer.d.ts.map +1 -1
  48. package/dist/core/dreamer.js +416 -58
  49. package/dist/core/dreamer.js.map +1 -1
  50. package/dist/core/embedder.d.ts +7 -5
  51. package/dist/core/embedder.d.ts.map +1 -1
  52. package/dist/core/embedder.js +38 -9
  53. package/dist/core/embedder.js.map +1 -1
  54. package/dist/core/extractor.d.ts.map +1 -1
  55. package/dist/core/extractor.js +2 -1
  56. package/dist/core/extractor.js.map +1 -1
  57. package/dist/core/failure-analyzer.d.ts.map +1 -1
  58. package/dist/core/failure-analyzer.js +7 -12
  59. package/dist/core/failure-analyzer.js.map +1 -1
  60. package/dist/core/graph.d.ts +4 -4
  61. package/dist/core/graph.d.ts.map +1 -1
  62. package/dist/core/graph.js.map +1 -1
  63. package/dist/core/install-channel.d.ts +1 -1
  64. package/dist/core/install-channel.d.ts.map +1 -1
  65. package/dist/core/install-channel.js +16 -5
  66. package/dist/core/install-channel.js.map +1 -1
  67. package/dist/core/install-hooks.d.ts +6 -0
  68. package/dist/core/install-hooks.d.ts.map +1 -1
  69. package/dist/core/install-hooks.js +0 -0
  70. package/dist/core/install-hooks.js.map +1 -1
  71. package/dist/core/json-utils.d.ts +1 -0
  72. package/dist/core/json-utils.d.ts.map +1 -1
  73. package/dist/core/json-utils.js +19 -10
  74. package/dist/core/json-utils.js.map +1 -1
  75. package/dist/core/kg-backfill.d.ts +3 -4
  76. package/dist/core/kg-backfill.d.ts.map +1 -1
  77. package/dist/core/kg-backfill.js +1 -4
  78. package/dist/core/kg-backfill.js.map +1 -1
  79. package/dist/core/lesson-engine.d.ts +1 -0
  80. package/dist/core/lesson-engine.d.ts.map +1 -1
  81. package/dist/core/lesson-engine.js +1 -0
  82. package/dist/core/lesson-engine.js.map +1 -1
  83. package/dist/core/lifecycle.d.ts +4 -4
  84. package/dist/core/lifecycle.d.ts.map +1 -1
  85. package/dist/core/lifecycle.js +15 -22
  86. package/dist/core/lifecycle.js.map +1 -1
  87. package/dist/core/llm-client.d.ts.map +1 -1
  88. package/dist/core/llm-client.js +3 -6
  89. package/dist/core/llm-client.js.map +1 -1
  90. package/dist/core/llm-telemetry.d.ts +4 -4
  91. package/dist/core/llm-telemetry.d.ts.map +1 -1
  92. package/dist/core/llm-telemetry.js +1 -1
  93. package/dist/core/llm-telemetry.js.map +1 -1
  94. package/dist/core/memory-tool.d.ts.map +1 -1
  95. package/dist/core/memory-tool.js +8 -4
  96. package/dist/core/memory-tool.js.map +1 -1
  97. package/dist/core/operations.d.ts.map +1 -1
  98. package/dist/core/operations.js +41 -17
  99. package/dist/core/operations.js.map +1 -1
  100. package/dist/core/paths.d.ts +3 -0
  101. package/dist/core/paths.d.ts.map +1 -1
  102. package/dist/core/paths.js +67 -1
  103. package/dist/core/paths.js.map +1 -1
  104. package/dist/core/patterns.d.ts +2 -2
  105. package/dist/core/patterns.d.ts.map +1 -1
  106. package/dist/core/patterns.js.map +1 -1
  107. package/dist/core/project-tags.d.ts +3 -3
  108. package/dist/core/project-tags.d.ts.map +1 -1
  109. package/dist/core/project-tags.js.map +1 -1
  110. package/dist/core/projects.d.ts +2 -2
  111. package/dist/core/projects.d.ts.map +1 -1
  112. package/dist/core/projects.js.map +1 -1
  113. package/dist/core/prompt-safety.d.ts +1 -0
  114. package/dist/core/prompt-safety.d.ts.map +1 -1
  115. package/dist/core/prompt-safety.js +7 -0
  116. package/dist/core/prompt-safety.js.map +1 -1
  117. package/dist/core/schema-export.d.ts.map +1 -1
  118. package/dist/core/schema-export.js +27 -30
  119. package/dist/core/schema-export.js.map +1 -1
  120. package/dist/core/serializer.d.ts.map +1 -1
  121. package/dist/core/serializer.js +45 -4
  122. package/dist/core/serializer.js.map +1 -1
  123. package/dist/core/setup.d.ts +29 -0
  124. package/dist/core/setup.d.ts.map +1 -0
  125. package/dist/core/setup.js +127 -0
  126. package/dist/core/setup.js.map +1 -0
  127. package/dist/core/stats.d.ts +2 -2
  128. package/dist/core/stats.d.ts.map +1 -1
  129. package/dist/core/stats.js.map +1 -1
  130. package/dist/core/task-state-store.d.ts +17 -0
  131. package/dist/core/task-state-store.d.ts.map +1 -0
  132. package/dist/core/task-state-store.js +45 -0
  133. package/dist/core/task-state-store.js.map +1 -0
  134. package/dist/core/task-state.d.ts +19 -0
  135. package/dist/core/task-state.d.ts.map +1 -0
  136. package/dist/core/task-state.js +91 -0
  137. package/dist/core/task-state.js.map +1 -0
  138. package/dist/core/time-utils.d.ts +2 -0
  139. package/dist/core/time-utils.d.ts.map +1 -0
  140. package/dist/core/time-utils.js +14 -0
  141. package/dist/core/time-utils.js.map +1 -0
  142. package/dist/core/title.d.ts +5 -0
  143. package/dist/core/title.d.ts.map +1 -0
  144. package/dist/core/title.js +14 -0
  145. package/dist/core/title.js.map +1 -0
  146. package/dist/core/transcript-extractor.d.ts +5 -6
  147. package/dist/core/transcript-extractor.d.ts.map +1 -1
  148. package/dist/core/transcript-extractor.js +4 -24
  149. package/dist/core/transcript-extractor.js.map +1 -1
  150. package/dist/core/transcript-source.d.ts.map +1 -1
  151. package/dist/core/transcript-source.js +2 -3
  152. package/dist/core/transcript-source.js.map +1 -1
  153. package/dist/core/types.d.ts +21 -7
  154. package/dist/core/types.d.ts.map +1 -1
  155. package/dist/core/types.js +2 -0
  156. package/dist/core/types.js.map +1 -1
  157. package/dist/core/work-topology.d.ts +33 -0
  158. package/dist/core/work-topology.d.ts.map +1 -0
  159. package/dist/core/work-topology.js +183 -0
  160. package/dist/core/work-topology.js.map +1 -0
  161. package/dist/db.d.ts +5 -10
  162. package/dist/db.d.ts.map +1 -1
  163. package/dist/db.js +194 -196
  164. package/dist/db.js.map +1 -1
  165. package/dist/knowledge-graph.d.ts +4 -2
  166. package/dist/knowledge-graph.d.ts.map +1 -1
  167. package/dist/knowledge-graph.js +68 -49
  168. package/dist/knowledge-graph.js.map +1 -1
  169. package/dist/mcp/server.js +2 -1
  170. package/dist/mcp/server.js.map +1 -1
  171. package/dist/skills-manifest.json +61 -36
  172. package/dist/storage/conflicts.d.ts +3 -3
  173. package/dist/storage/conflicts.d.ts.map +1 -1
  174. package/dist/storage/conflicts.js +2 -7
  175. package/dist/storage/conflicts.js.map +1 -1
  176. package/dist/storage/fts-index.d.ts +6 -4
  177. package/dist/storage/fts-index.d.ts.map +1 -1
  178. package/dist/storage/fts-index.js +16 -4
  179. package/dist/storage/fts-index.js.map +1 -1
  180. package/dist/storage/schema.d.ts +20 -0
  181. package/dist/storage/schema.d.ts.map +1 -0
  182. package/dist/storage/schema.js +274 -0
  183. package/dist/storage/schema.js.map +1 -0
  184. package/dist/storage/sqlite.d.ts +20 -0
  185. package/dist/storage/sqlite.d.ts.map +1 -0
  186. package/dist/storage/sqlite.js +64 -0
  187. package/dist/storage/sqlite.js.map +1 -0
  188. package/dist/storage/vector-index.d.ts +3 -0
  189. package/dist/storage/vector-index.d.ts.map +1 -0
  190. package/dist/storage/vector-index.js +7 -0
  191. package/dist/storage/vector-index.js.map +1 -0
  192. package/dist/transports/cli/cli.d.ts +1 -4
  193. package/dist/transports/cli/cli.d.ts.map +1 -1
  194. package/dist/transports/cli/cli.js +494 -76
  195. package/dist/transports/cli/cli.js.map +1 -1
  196. package/dist/transports/http/retired-routes.d.ts.map +1 -1
  197. package/dist/transports/http/retired-routes.js +1 -0
  198. package/dist/transports/http/retired-routes.js.map +1 -1
  199. package/dist/transports/http/server.d.ts.map +1 -1
  200. package/dist/transports/http/server.js +243 -323
  201. package/dist/transports/http/server.js.map +1 -1
  202. package/dist/transports/mcp/handlers.d.ts +48 -94
  203. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  204. package/dist/transports/mcp/handlers.js +77 -56
  205. package/dist/transports/mcp/handlers.js.map +1 -1
  206. package/dist/transports/schemas.d.ts +31 -40
  207. package/dist/transports/schemas.d.ts.map +1 -1
  208. package/dist/transports/schemas.js +31 -37
  209. package/dist/transports/schemas.js.map +1 -1
  210. package/hooks/hooks.json +0 -10
  211. package/llms-install.md +138 -0
  212. package/package.json +19 -18
  213. package/scripts/hooks/_generated/capture-flag.js +17 -0
  214. package/scripts/hooks/_generated/core-paths.js +67 -1
  215. package/scripts/hooks/_generated/fts-index.js +16 -4
  216. package/scripts/hooks/_generated/schema.js +281 -0
  217. package/scripts/hooks/_generated/sqlite.js +71 -0
  218. package/scripts/hooks/_generated/task-state.js +98 -0
  219. package/scripts/hooks/_generated/time-utils.js +21 -0
  220. package/scripts/hooks/_generated/title.js +21 -0
  221. package/scripts/hooks/_generated/work-topology.js +190 -0
  222. package/scripts/hooks/_shared.js +269 -534
  223. package/scripts/hooks/post-commit.js +55 -10
  224. package/scripts/hooks/pre-compact.js +22 -7
  225. package/scripts/hooks/pre-edit-recall.js +9 -11
  226. package/scripts/hooks/session-start.js +230 -106
  227. package/scripts/hooks/session-summary.js +176 -73
  228. package/scripts/hooks/user-prompt-intent.js +3 -2
  229. package/skills/memesh/SKILL.md +97 -77
  230. package/README.es.md +0 -470
  231. package/README.fr.md +0 -462
  232. package/README.ja.md +0 -470
  233. package/README.ko.md +0 -470
  234. package/README.pt.md +0 -462
  235. package/README.th.md +0 -463
  236. package/README.vi.md +0 -462
  237. package/README.zh-CN.md +0 -469
  238. package/dist/cli/view.d.ts +0 -3
  239. package/dist/cli/view.d.ts.map +0 -1
  240. package/dist/cli/view.js +0 -523
  241. package/dist/cli/view.js.map +0 -1
  242. package/dist/core/skill-usage-log.d.ts +0 -11
  243. package/dist/core/skill-usage-log.d.ts.map +0 -1
  244. package/dist/core/skill-usage-log.js +0 -125
  245. package/dist/core/skill-usage-log.js.map +0 -1
  246. package/dist/core/verifier.d.ts +0 -40
  247. package/dist/core/verifier.d.ts.map +0 -1
  248. package/dist/core/verifier.js +0 -206
  249. package/dist/core/verifier.js.map +0 -1
  250. package/dist/mcp/launcher.d.ts +0 -3
  251. package/dist/mcp/launcher.d.ts.map +0 -1
  252. package/dist/mcp/launcher.js +0 -37
  253. package/dist/mcp/launcher.js.map +0 -1
  254. package/scripts/hooks/pre-bash-orchestration-nudge.js +0 -155
  255. package/scripts/postinstall-rebuild.mjs +0 -41
  256. package/skills/agentic-orchestration/SKILL.md +0 -399
@@ -13,11 +13,10 @@ import { computeAnalytics, computePmAnalytics } from '../../core/analytics.js';
13
13
  import { computeStats } from '../../core/stats.js';
14
14
  import { computeProjects } from '../../core/projects.js';
15
15
  import { computeGraph } from '../../core/graph.js';
16
- import { verifyAgentWork } from '../../core/verifier.js';
17
- import { RememberSchema as RememberBody, RecallSchema as RecallBody, ForgetSchema as ForgetBody, ExportSchema as ExportBody, ImportSchema as ImportBody, LearnSchema as LearnBody, VerifyAgentWorkSchema as VerifyBody, } from '../schemas.js';
16
+ import { RememberSchema as RememberBody, RecallSchema as RecallBody, ForgetSchema as ForgetBody, ExportSchema as ExportBody, ImportSchema as ImportBody, LearnSchema as LearnBody, } from '../schemas.js';
18
17
  import { checkForUpdate, getLastUpdateCheck, getUpdateCheck } from '../../core/version-check.js';
19
18
  import { getCurrentInstallChannel, getInstallChannelSupport } from '../../core/install-channel.js';
20
- import { getDbPath, getMemeshDirFromDbPath } from '../../core/paths.js';
19
+ import { getDbPath, getMemeshDirFromDbPath, redactSecrets, redactUserPaths } from '../../core/paths.js';
21
20
  import { RETIRED_ROUTES } from './retired-routes.js';
22
21
  import fs from 'fs';
23
22
  import path from 'path';
@@ -204,30 +203,14 @@ app.get('/v1/health', (_req, res) => {
204
203
  }
205
204
  }
206
205
  });
207
- function redactSecrets(input) {
208
- return input
209
- .replace(/sk-ant-[A-Za-z0-9_-]{20,}/g, 'sk-ant-***REDACTED***')
210
- .replace(/sk-proj-[A-Za-z0-9_-]{20,}/g, 'sk-proj-***REDACTED***')
211
- .replace(/sk-[A-Za-z0-9]{32,}/g, 'sk-***REDACTED***')
212
- .replace(/ghp_[A-Za-z0-9]{30,}/g, 'ghp_***REDACTED***')
213
- .replace(/gho_[A-Za-z0-9]{30,}/g, 'gho_***REDACTED***')
214
- .replace(/AKIA[A-Z0-9]{16}/g, 'AKIA***REDACTED***')
215
- .replace(/Bearer\s+[A-Za-z0-9._-]{20,}/gi, 'Bearer ***REDACTED***');
216
- }
217
- app.get('/v1/doctor', async (_req, res) => {
218
- try {
219
- const { runDoctor } = await import('../../core/doctor.js');
220
- const result = await runDoctor({
221
- packageRoot,
222
- packageVersion,
223
- });
224
- const safe = JSON.parse(redactSecrets(JSON.stringify(result)));
225
- res.json({ success: true, data: safe });
226
- }
227
- catch (err) {
228
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
229
- }
230
- });
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
+ }));
231
214
  function requireJsonBody(req, res) {
232
215
  if (req.body !== undefined)
233
216
  return true;
@@ -239,53 +222,83 @@ function requireJsonBody(req, res) {
239
222
  });
240
223
  return false;
241
224
  }
242
- function handlePost(schema, req, res, handler) {
243
- 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 });
244
237
  return;
245
- 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));
246
268
  if (!parsed.success) {
247
269
  res.status(400).json({
248
270
  success: false,
249
271
  errorCode: 'validation.bad-body',
250
- error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
272
+ error: zodErrorText(parsed.error),
251
273
  });
252
274
  return;
253
275
  }
254
- Promise.resolve(handler(parsed.data))
276
+ Promise.resolve()
277
+ .then(() => handler(parsed.data))
255
278
  .then((data) => res.json({ success: true, data }))
256
- .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'));
257
280
  }
258
281
  function handleGet(res, produce) {
259
282
  Promise.resolve()
260
283
  .then(produce)
261
284
  .then((data) => res.json({ success: true, data }))
262
- .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'));
263
286
  }
264
- app.post('/v1/remember', (req, res) => handlePost(RememberBody, req, res, remember));
265
- app.post('/v1/recall', async (req, res) => {
266
- if (!requireJsonBody(req, res))
267
- return;
268
- const parsed = RecallBody.safeParse(req.body);
269
- if (!parsed.success) {
270
- res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
271
- return;
272
- }
273
- try {
274
- const { entities, conflicts } = await recallWithConflicts(parsed.data);
275
- res.json({ success: true, data: conflicts.length > 0 ? { entities, conflicts } : entities });
276
- }
277
- catch (err) {
278
- res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) });
279
- }
280
- });
287
+ app.post('/v1/remember', (req, res) => handlePost(RememberBody, req, res, (data) => remember({ ...data, sourceHost: 'http' })));
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
+ }));
281
292
  app.post('/v1/forget', (req, res) => handlePost(ForgetBody, req, res, forget));
282
293
  app.post('/v1/consolidate', (_req, res) => {
283
294
  res.status(410).json({ success: false, errorCode: 'route.retired', error: RETIRED_ROUTES['/v1/consolidate'] });
284
295
  });
285
296
  app.post('/v1/export', (req, res) => handlePost(ExportBody, req, res, exportMemories));
286
297
  app.post('/v1/import', (req, res) => handlePost(ImportBody, req, res, importMemories));
287
- app.post('/v1/learn', (req, res) => handlePost(LearnBody, req, res, learn));
288
- app.post('/v1/verify', (req, res) => handlePost(VerifyBody, req, res, verifyAgentWork));
298
+ app.post('/v1/learn', (req, res) => handlePost(LearnBody, req, res, (data) => learn({ ...data, sourceHost: 'http' })));
299
+ app.post('/v1/verify', (_req, res) => {
300
+ res.status(410).json({ success: false, errorCode: 'route.retired', error: RETIRED_ROUTES['/v1/verify'] });
301
+ });
289
302
  const API_KEY_MASK = '***';
290
303
  function maskLlmSecrets(obj) {
291
304
  const masked = { ...obj };
@@ -313,16 +326,11 @@ function preserveFallbackApiKeys(incoming, stored) {
313
326
  return clean;
314
327
  });
315
328
  }
316
- app.get('/v1/config', (_req, res) => {
317
- try {
318
- const config = readConfig();
319
- const caps = detectCapabilities(config);
320
- res.json({ success: true, data: { config: maskLlmSecrets(config), capabilities: maskLlmSecrets(caps) } });
321
- }
322
- catch (err) {
323
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
324
- }
325
- });
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
+ }));
326
334
  const ConfigBody = z.object({
327
335
  llm: z.union([
328
336
  z.object({
@@ -340,7 +348,6 @@ const ConfigBody = z.object({
340
348
  })).optional(),
341
349
  autoCapture: z.boolean().optional(),
342
350
  sessionLimit: z.number().int().min(1).max(100).optional(),
343
- enableAgenticOrchestration: z.boolean().optional(),
344
351
  autoUpdate: z.enum(['off', 'patch', 'minor', 'major']).optional(),
345
352
  language: z.string().trim().min(1).max(60)
346
353
  .refine((v) => languageValueError(v) === null, {
@@ -349,108 +356,71 @@ const ConfigBody = z.object({
349
356
  .optional(),
350
357
  setupCompleted: z.boolean().optional(),
351
358
  }).strip();
352
- app.post('/v1/config', async (req, res) => {
353
- if (!requireJsonBody(req, res))
354
- return;
355
- const parsed = ConfigBody.safeParse(req.body);
356
- if (!parsed.success) {
357
- res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
358
- return;
359
- }
360
- try {
361
- const before = readConfig();
362
- if (parsed.data.llm && parsed.data.llm.apiKey === API_KEY_MASK) {
363
- if (before.llm && before.llm.provider === parsed.data.llm.provider && before.llm.apiKey) {
364
- parsed.data.llm.apiKey = before.llm.apiKey;
365
- }
366
- else {
367
- delete parsed.data.llm.apiKey;
368
- }
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;
369
364
  }
370
- if (parsed.data.llmFallbacks) {
371
- parsed.data.llmFallbacks = preserveFallbackApiKeys(parsed.data.llmFallbacks, before.llmFallbacks);
365
+ else {
366
+ delete data.llm.apiKey;
372
367
  }
373
- const updated = updateConfig(parsed.data);
374
- res.json({ success: true, data: maskLlmSecrets(updated) });
375
368
  }
376
- catch (err) {
377
- 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);
378
371
  }
379
- });
372
+ const updated = updateConfig(data);
373
+ return maskLlmSecrets(updated);
374
+ }));
380
375
  const ConfigTestBody = z.object({
381
376
  provider: z.enum(['anthropic', 'openai', 'ollama']),
382
377
  apiKey: z.string().max(500).optional(),
383
378
  host: z.string().max(500).optional(),
384
379
  fallbackIndex: z.number().int().nonnegative().optional(),
385
380
  });
386
- app.post('/v1/config/test', async (req, res) => {
387
- if (!requireJsonBody(req, res))
388
- return;
389
- const parsed = ConfigTestBody.safeParse(req.body);
390
- if (!parsed.success) {
391
- res.status(400).json({
392
- success: false,
393
- errorCode: 'validation.bad-body',
394
- error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
395
- });
396
- return;
397
- }
398
- try {
399
- const { probeProvider } = await import('../../core/llm-validator.js');
400
- const { provider, host, fallbackIndex } = parsed.data;
401
- let { apiKey } = parsed.data;
402
- if (!apiKey && (provider === 'anthropic' || provider === 'openai')) {
403
- const existing = readConfig();
404
- if (typeof fallbackIndex === 'number') {
405
- const fb = existing.llmFallbacks?.[fallbackIndex];
406
- if (fb && fb.provider === provider && fb.apiKey)
407
- apiKey = fb.apiKey;
408
- }
409
- else if (existing.llm?.provider === provider && existing.llm.apiKey) {
410
- apiKey = existing.llm.apiKey;
411
- }
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;
412
394
  }
413
- const result = await probeProvider(provider, apiKey, host);
414
- res.json({ success: true, data: result });
415
- }
416
- catch (err) {
417
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
418
- }
419
- });
420
- app.get('/v1/update-status', async (req, res) => {
421
- try {
422
- const cached = req.query.cached === '1' || req.query.cached === 'true';
423
- const install = getCurrentInstallChannel({ packageRoot });
424
- const installSupport = getInstallChannelSupport(install);
425
- const update = await getUpdateCheck(packageVersion, { preferFresh: !cached });
426
- res.json({
427
- success: true,
428
- data: {
429
- currentVersion: packageVersion,
430
- latestVersion: update?.latestVersion ?? null,
431
- checkedAt: update?.checkedAt ?? null,
432
- lastAttemptAt: update?.lastAttemptAt ?? null,
433
- lastSuccessfulCheckAt: update?.lastSuccessfulCheckAt ?? null,
434
- lastError: update?.lastError ?? null,
435
- updateAvailable: update?.updateAvailable ?? false,
436
- checkSucceeded: update?.checkSucceeded ?? false,
437
- source: update?.source ?? null,
438
- freshness: update?.freshness ?? 'unavailable',
439
- installChannel: installSupport.channel,
440
- canSelfUpdate: installSupport.canSelfUpdate,
441
- recommendedCommand: (update?.currentVersionDeprecated
442
- && update.latestVersion
443
- && update.latestVersion === update.currentVersion
444
- && update.freshness === 'fresh') ? null : installSupport.recommendedCommand,
445
- currentVersionDeprecated: update?.currentVersionDeprecated ?? false,
446
- deprecationMessage: update?.deprecationMessage ?? null,
447
- },
448
- });
449
- }
450
- catch (err) {
451
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
452
395
  }
453
- });
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
+ }));
454
424
  app.get('/v1/graph', (_req, res) => handleGet(res, () => computeGraph(getDatabase())));
455
425
  app.get('/v1/stats', (_req, res) => handleGet(res, () => computeStats(getDatabase())));
456
426
  app.get('/v1/analytics', (_req, res) => handleGet(res, () => computeAnalytics(getDatabase())));
@@ -460,80 +430,51 @@ app.get('/v1/analytics/pm', (req, res) => {
460
430
  const windowDays = Number.isFinite(window) && window > 0 ? window : 30;
461
431
  handleGet(res, () => computePmAnalytics(getDatabase(), windowDays));
462
432
  });
463
- app.post('/v1/demo/seed', async (_req, res) => {
464
- try {
465
- const { seedDemo } = await import('../../core/demo.js');
466
- const data = seedDemo(getDatabase());
467
- res.json({ success: true, data });
468
- }
469
- catch (err) {
470
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
471
- }
472
- });
473
- app.post('/v1/demo/reset', async (_req, res) => {
474
- try {
475
- const { seedDemo } = await import('../../core/demo.js');
476
- const data = seedDemo(getDatabase(), { reset: true });
477
- res.json({ success: true, data });
478
- }
479
- catch (err) {
480
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
481
- }
482
- });
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
+ }));
483
441
  app.get('/v1/projects', (_req, res) => handleGet(res, () => computeProjects(getDatabase())));
484
442
  app.get('/v1/patterns', (_req, res) => handleGet(res, () => computePatterns(getDatabase())));
485
443
  const TelemetryQuerySchema = z.object({
486
444
  window: z.coerce.number().int().min(1).max(365).default(30),
487
445
  });
488
- app.get('/v1/telemetry', async (req, res) => {
489
- try {
490
- const parsed = TelemetryQuerySchema.safeParse(req.query);
491
- if (!parsed.success) {
492
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: parsed.error.issues.map(i => i.message).join('; ') });
493
- return;
494
- }
446
+ app.get('/v1/telemetry', (req, res) => {
447
+ const query = parseQuery(TelemetryQuerySchema, req, res);
448
+ if (!query)
449
+ return;
450
+ handleGet(res, async () => {
495
451
  const { summariseTelemetry } = await import('../../core/llm-telemetry.js');
496
- const summaries = summariseTelemetry(parsed.data.window);
497
- res.json({ success: true, data: { window_days: parsed.data.window, summaries } });
498
- }
499
- catch (err) {
500
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
501
- }
452
+ return { window_days: query.window, summaries: summariseTelemetry(query.window) };
453
+ });
502
454
  });
503
455
  const DreamProposalsQuerySchema = z.object({
504
456
  status: z.enum(['pending', 'applied', 'rejected', 'all']).default('pending'),
505
457
  });
506
458
  app.get('/v1/dream/proposals', (req, res) => {
507
- try {
508
- const parsed = DreamProposalsQuerySchema.safeParse(req.query);
509
- if (!parsed.success) {
510
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: parsed.error.issues.map(i => i.message).join('; ') });
511
- return;
512
- }
513
- const status = parsed.data.status;
514
- import('../../core/dreamer.js').then(({ listProposals }) => {
515
- const db = getDatabase();
516
- const rows = status === 'all'
517
- ? [...listProposals(db, 'pending'), ...listProposals(db, 'applied'), ...listProposals(db, 'rejected')]
518
- : listProposals(db, status);
519
- res.json({ success: true, data: rows });
520
- }).catch((err) => res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) }));
521
- }
522
- catch (err) {
523
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
524
- }
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
+ });
525
469
  });
526
470
  app.get('/v1/dream/proposals/:id', (req, res) => {
527
- const id = parseInt(req.params.id, 10);
528
- if (!Number.isInteger(id) || id < 1) {
529
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
471
+ const id = requireIdParam(req, res);
472
+ if (id === null)
530
473
  return;
531
- }
532
- try {
474
+ handleGet(res, () => {
533
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);
534
476
  if (!row) {
535
- res.status(404).json({ success: false, errorCode: 'resource.not-found', error: `proposal #${id} not found` });
536
- return;
477
+ throw new HttpError(404, 'resource.not-found', `proposal #${id} not found`);
537
478
  }
538
479
  let digest = null;
539
480
  let sourceIds = [];
@@ -545,11 +486,8 @@ app.get('/v1/dream/proposals/:id', (req, res) => {
545
486
  sourceIds = JSON.parse(row.source_ids);
546
487
  }
547
488
  catch { }
548
- res.json({ success: true, data: { ...row, proposed_digest: digest, source_ids: sourceIds } });
549
- }
550
- catch (err) {
551
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
552
- }
489
+ return { ...row, proposed_digest: digest, source_ids: sourceIds };
490
+ });
553
491
  });
554
492
  const DreamRunBody = z.object({
555
493
  project: z.string().min(1).max(100).optional(),
@@ -557,90 +495,64 @@ const DreamRunBody = z.object({
557
495
  maxLlmCalls: z.number().int().min(1).max(20).default(5),
558
496
  validate: z.boolean().default(false),
559
497
  });
560
- app.post('/v1/dream/run', async (req, res) => {
561
- const parsed = DreamRunBody.safeParse(req.body ?? {});
562
- if (!parsed.success) {
563
- res.status(400).json({
564
- success: false,
565
- errorCode: 'validation.bad-body',
566
- error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
567
- });
568
- return;
569
- }
570
- try {
571
- const { runDreamer } = await import('../../core/dreamer.js');
572
- const cfg = readConfig();
573
- if (!cfg.llm) {
574
- res.status(400).json({
575
- success: false,
576
- errorCode: 'llm.not-configured',
577
- error: 'No LLM configured — dream run requires Smart Mode. Configure a provider in Settings.',
578
- });
579
- return;
580
- }
581
- const result = await runDreamer(getDatabase(), cfg.llm, {
582
- project: parsed.data.project,
583
- windowDays: parsed.data.windowDays,
584
- maxLlmCalls: parsed.data.maxLlmCalls,
585
- fallbacks: cfg.llmFallbacks,
586
- validateBeforeStage: parsed.data.validate,
587
- });
588
- res.json({ success: true, data: result });
589
- }
590
- catch (err) {
591
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
592
- }
593
- });
594
- app.post('/v1/dream/proposals/:id/accept', async (req, res) => {
595
- const id = parseInt(req.params.id, 10);
596
- if (!Number.isInteger(id) || id < 1) {
597
- 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)
598
516
  return;
599
- }
600
- try {
601
- const { applyProposal } = await import('../../core/dreamer.js');
602
- const kg = new KnowledgeGraph(getDatabase());
603
- const result = applyProposal(getDatabase(), id, kg);
604
- res.json({ success: true, data: result });
605
- }
606
- catch (err) {
607
- const msg = err instanceof Error ? err.message : String(err);
608
- if (/not found or not pending/.test(msg)) {
609
- res.status(404).json({ success: false, errorCode: 'resource.not-found', error: msg });
517
+ handleGet(res, async () => {
518
+ const dreamer = await import('../../core/dreamer.js');
519
+ try {
520
+ const kg = new KnowledgeGraph(getDatabase());
521
+ return dreamer.applyProposal(getDatabase(), id, kg);
610
522
  }
611
- else {
612
- 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;
613
532
  }
614
- }
533
+ });
615
534
  });
616
535
  const RejectBodySchema = z.object({
617
536
  reason: z.string().max(500).optional(),
618
537
  });
619
- app.post('/v1/dream/proposals/:id/reject', async (req, res) => {
620
- const id = parseInt(req.params.id, 10);
621
- if (!Number.isInteger(id) || id < 1) {
622
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
538
+ app.post('/v1/dream/proposals/:id/reject', (req, res) => {
539
+ const id = requireIdParam(req, res);
540
+ if (id === null)
623
541
  return;
624
- }
625
- const parsed = RejectBodySchema.safeParse(req.body ?? {});
626
- if (!parsed.success) {
627
- res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => i.message).join('; ') });
628
- return;
629
- }
630
- try {
542
+ handlePost(RejectBodySchema, req, res, async (data) => {
631
543
  const { rejectProposal } = await import('../../core/dreamer.js');
632
- rejectProposal(getDatabase(), id, parsed.data.reason);
633
- res.json({ success: true, data: { id, status: 'rejected' } });
634
- }
635
- catch (err) {
636
- const msg = err instanceof Error ? err.message : String(err);
637
- if (/not found or not pending/.test(msg)) {
638
- res.status(404).json({ success: false, errorCode: 'resource.not-found', error: msg });
544
+ try {
545
+ rejectProposal(getDatabase(), id, data.reason);
639
546
  }
640
- else {
641
- 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;
642
553
  }
643
- }
554
+ return { id, status: 'rejected' };
555
+ }, { allowEmptyBody: true, errorStatus: 500, errorCode: 'server.internal' });
644
556
  });
645
557
  const EntitiesQuerySchema = z.object({
646
558
  type: z.string().min(1).max(100).optional(),
@@ -648,40 +560,26 @@ const EntitiesQuerySchema = z.object({
648
560
  status: z.enum(['all', 'active']).optional(),
649
561
  });
650
562
  app.get('/v1/entities', (req, res) => {
651
- try {
652
- const parsed = EntitiesQuerySchema.safeParse(req.query);
653
- if (!parsed.success) {
654
- res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: `Invalid query: ${parsed.error.message}` });
655
- return;
656
- }
657
- 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;
658
568
  const includeArchived = status === 'all';
659
- const db = getDatabase();
660
- const kg = new KnowledgeGraph(db);
661
- const entities = typeFilter
569
+ const kg = new KnowledgeGraph(getDatabase());
570
+ return typeFilter
662
571
  ? kg.listByType(typeFilter, limit, includeArchived)
663
572
  : kg.listRecent(limit, includeArchived);
664
- res.json({ success: true, data: entities });
665
- }
666
- catch (err) {
667
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
668
- }
669
- });
670
- app.get('/v1/entities/:name', (req, res) => {
671
- try {
672
- const db = getDatabase();
673
- const kg = new KnowledgeGraph(db);
674
- const entity = kg.getEntity(req.params.name);
675
- if (!entity) {
676
- res.status(404).json({ success: false, errorCode: 'resource.not-found', error: `Entity "${req.params.name}" not found` });
677
- return;
678
- }
679
- res.json({ success: true, data: entity });
680
- }
681
- catch (err) {
682
- res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
683
- }
573
+ });
684
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
+ }));
685
583
  const HOST = process.env.MEMESH_HTTP_HOST || '127.0.0.1';
686
584
  const PORT = parseInt(process.env.MEMESH_HTTP_PORT || '3737');
687
585
  const ALLOW_REMOTE_BY_ENV = /^(1|true|yes)$/i.test(process.env.MEMESH_HTTP_ALLOW_REMOTE || '');
@@ -710,6 +608,10 @@ export function startServer(host = HOST, port = PORT, opts) {
710
608
  if (!allowRemote && isRemote) {
711
609
  throw new Error(`Refusing to bind MeMesh HTTP server to non-loopback host "${host}" without explicit remote access opt-in. Use --allow-remote or MEMESH_HTTP_ALLOW_REMOTE=true.`);
712
610
  }
611
+ if (allowRemote && !isRemote) {
612
+ process.stderr.write(`MeMesh HTTP: --allow-remote has no effect on loopback host "${host}" — the server stays local ` +
613
+ 'and no bearer token is generated. Add --host <address> to bind somewhere reachable.\n');
614
+ }
713
615
  if (isRemote) {
714
616
  const { token, freshlyCreated } = loadOrCreateRemoteToken();
715
617
  remoteToken = token;
@@ -767,10 +669,21 @@ export function startServer(host = HOST, port = PORT, opts) {
767
669
  console.log(`MeMesh HTTP server running at http://${addr.address}:${addr.port}`);
768
670
  console.log(`MeMesh dashboard: http://${addr.address}:${addr.port}/dashboard`);
769
671
  }
672
+ });
673
+ server.on('error', (err) => {
674
+ const where = `${host}:${port}`;
675
+ if (err.code === 'EADDRINUSE') {
676
+ console.error(`MeMesh: cannot start — ${where} is already in use.`);
677
+ console.error(`MeMesh: stop whatever is listening there, or pick another port with --port <n>.`);
678
+ }
679
+ else if (err.code === 'EACCES') {
680
+ console.error(`MeMesh: cannot start — not permitted to bind ${where}.`);
681
+ console.error(`MeMesh: ports below 1024 need elevated privileges; pick a port above 1024 with --port <n>.`);
682
+ }
770
683
  else {
771
- console.log(`MeMesh HTTP server running at http://${host}:${port}`);
772
- console.log(`MeMesh dashboard: http://${host}:${port}/dashboard`);
684
+ console.error(`MeMesh: cannot start on ${where} — ${err.message}`);
773
685
  }
686
+ process.exit(1);
774
687
  });
775
688
  serverAuthRequired.set(server, isRemote);
776
689
  return server;
@@ -780,7 +693,14 @@ export function __setRemoteTokenForTest(value) {
780
693
  }
781
694
  const isMain = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/'));
782
695
  if (isMain || process.argv[1]?.endsWith('memesh-http')) {
783
- const server = startServer();
696
+ let server;
697
+ try {
698
+ server = startServer();
699
+ }
700
+ catch (err) {
701
+ console.error(`MeMesh: ${err instanceof Error ? err.message : String(err)}`);
702
+ process.exit(1);
703
+ }
784
704
  function shutdown() {
785
705
  server.close();
786
706
  try {