@pcircle/memesh 4.4.0 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +1 -1
- package/README.de.md +32 -12
- package/README.es.md +13 -12
- package/README.fr.md +31 -11
- package/README.ja.md +13 -12
- package/README.ko.md +14 -13
- package/README.md +41 -16
- package/README.pt.md +30 -10
- package/README.th.md +16 -9
- package/README.vi.md +32 -12
- package/README.zh-CN.md +14 -13
- package/README.zh-TW.md +14 -13
- package/dashboard/dist/index.html +10 -10
- package/dist/cli/view-live.js +1 -1
- package/dist/cli/view.d.ts.map +1 -1
- package/dist/cli/view.js +2 -2
- package/dist/cli/view.js.map +1 -1
- package/dist/core/analytics.d.ts +7 -3
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js +8 -8
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/auto-tagger.d.ts.map +1 -1
- package/dist/core/auto-tagger.js.map +1 -1
- package/dist/core/config.d.ts +5 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +30 -14
- package/dist/core/config.js.map +1 -1
- package/dist/core/demo.d.ts +2 -2
- package/dist/core/demo.d.ts.map +1 -1
- package/dist/core/demo.js.map +1 -1
- package/dist/core/digest-validator.d.ts.map +1 -1
- package/dist/core/digest-validator.js +3 -1
- package/dist/core/digest-validator.js.map +1 -1
- package/dist/core/doctor.d.ts +3 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +255 -101
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +35 -7
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +394 -41
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts +3 -5
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +26 -95
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/extractor.d.ts.map +1 -1
- package/dist/core/extractor.js +2 -1
- package/dist/core/extractor.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +2 -1
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/graph.d.ts +4 -4
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js.map +1 -1
- package/dist/core/install-hooks.d.ts +1 -0
- package/dist/core/install-hooks.d.ts.map +1 -1
- package/dist/core/install-hooks.js +0 -0
- package/dist/core/install-hooks.js.map +1 -1
- package/dist/core/kg-backfill.d.ts +4 -4
- package/dist/core/kg-backfill.d.ts.map +1 -1
- package/dist/core/kg-backfill.js +1 -1
- package/dist/core/kg-backfill.js.map +1 -1
- package/dist/core/lesson-engine.d.ts +1 -0
- package/dist/core/lesson-engine.d.ts.map +1 -1
- package/dist/core/lesson-engine.js +1 -0
- package/dist/core/lesson-engine.js.map +1 -1
- package/dist/core/lifecycle.d.ts +4 -4
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +1 -1
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core/llm-client.d.ts.map +1 -1
- package/dist/core/llm-client.js +3 -6
- package/dist/core/llm-client.js.map +1 -1
- package/dist/core/llm-telemetry.d.ts +4 -4
- package/dist/core/llm-telemetry.d.ts.map +1 -1
- package/dist/core/llm-telemetry.js +1 -1
- package/dist/core/llm-telemetry.js.map +1 -1
- package/dist/core/llm-validator.d.ts +1 -0
- package/dist/core/llm-validator.d.ts.map +1 -1
- package/dist/core/llm-validator.js +33 -10
- package/dist/core/llm-validator.js.map +1 -1
- package/dist/core/memory-tool.d.ts.map +1 -1
- package/dist/core/memory-tool.js +4 -0
- package/dist/core/memory-tool.js.map +1 -1
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +23 -8
- package/dist/core/operations.js.map +1 -1
- package/dist/core/output-language.d.ts +6 -0
- package/dist/core/output-language.d.ts.map +1 -0
- package/dist/core/output-language.js +25 -0
- package/dist/core/output-language.js.map +1 -0
- package/dist/core/paths.d.ts +3 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +67 -1
- package/dist/core/paths.js.map +1 -1
- package/dist/core/patterns.d.ts +2 -3
- package/dist/core/patterns.d.ts.map +1 -1
- package/dist/core/patterns.js +1 -5
- package/dist/core/patterns.js.map +1 -1
- package/dist/core/project-tags.d.ts +3 -3
- package/dist/core/project-tags.d.ts.map +1 -1
- package/dist/core/project-tags.js.map +1 -1
- package/dist/core/projects.d.ts +2 -2
- package/dist/core/projects.d.ts.map +1 -1
- package/dist/core/projects.js.map +1 -1
- package/dist/core/schema-export.d.ts.map +1 -1
- package/dist/core/schema-export.js +0 -34
- package/dist/core/schema-export.js.map +1 -1
- package/dist/core/serializer.d.ts.map +1 -1
- package/dist/core/serializer.js +45 -4
- package/dist/core/serializer.js.map +1 -1
- package/dist/core/stats.d.ts +2 -2
- package/dist/core/stats.d.ts.map +1 -1
- package/dist/core/stats.js.map +1 -1
- package/dist/core/transcript-extractor.d.ts +88 -0
- package/dist/core/transcript-extractor.d.ts.map +1 -0
- package/dist/core/transcript-extractor.js +417 -0
- package/dist/core/transcript-extractor.js.map +1 -0
- package/dist/core/transcript-source.d.ts +21 -0
- package/dist/core/transcript-source.d.ts.map +1 -0
- package/dist/core/transcript-source.js +142 -0
- package/dist/core/transcript-source.js.map +1 -0
- package/dist/core/types.d.ts +17 -7
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -1
- package/dist/db.d.ts +5 -5
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +165 -17
- package/dist/db.js.map +1 -1
- package/dist/knowledge-graph.d.ts +3 -2
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +19 -10
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/mcp/server.js +2 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/skills-manifest.json +28 -33
- package/dist/storage/conflicts.d.ts +3 -3
- package/dist/storage/conflicts.d.ts.map +1 -1
- package/dist/storage/conflicts.js.map +1 -1
- package/dist/storage/fts-index.d.ts +4 -4
- package/dist/storage/fts-index.d.ts.map +1 -1
- package/dist/storage/fts-index.js.map +1 -1
- package/dist/storage/sqlite.d.ts +20 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +64 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/storage/vector-index.d.ts +3 -0
- package/dist/storage/vector-index.d.ts.map +1 -0
- package/dist/storage/vector-index.js +7 -0
- package/dist/storage/vector-index.js.map +1 -0
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +278 -91
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/retired-routes.d.ts.map +1 -1
- package/dist/transports/http/retired-routes.js +1 -0
- package/dist/transports/http/retired-routes.js.map +1 -1
- package/dist/transports/http/server.d.ts +7 -0
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +165 -79
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +4 -96
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +26 -59
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/dist/transports/schemas.d.ts +10 -30
- package/dist/transports/schemas.d.ts.map +1 -1
- package/dist/transports/schemas.js +5 -29
- package/dist/transports/schemas.js.map +1 -1
- package/hooks/hooks.json +0 -10
- package/package.json +6 -19
- package/scripts/hooks/_generated/core-paths.js +67 -1
- package/scripts/hooks/_generated/sqlite.js +71 -0
- package/scripts/hooks/_shared.js +286 -195
- package/scripts/hooks/post-commit.js +52 -10
- package/scripts/hooks/pre-compact.js +10 -7
- package/scripts/hooks/pre-edit-recall.js +4 -8
- package/scripts/hooks/session-start.js +95 -47
- package/scripts/hooks/session-summary.js +120 -52
- package/scripts/hooks/user-prompt-intent.js +3 -2
- package/skills/memesh/SKILL.md +3 -2
- package/dist/core/skill-usage-log.d.ts +0 -11
- package/dist/core/skill-usage-log.d.ts.map +0 -1
- package/dist/core/skill-usage-log.js +0 -125
- package/dist/core/skill-usage-log.js.map +0 -1
- package/dist/core/verifier.d.ts +0 -40
- package/dist/core/verifier.d.ts.map +0 -1
- package/dist/core/verifier.js +0 -206
- package/dist/core/verifier.js.map +0 -1
- package/dist/mcp/launcher.d.ts +0 -3
- package/dist/mcp/launcher.d.ts.map +0 -1
- package/dist/mcp/launcher.js +0 -37
- package/dist/mcp/launcher.js.map +0 -1
- package/scripts/hooks/pre-bash-orchestration-nudge.js +0 -155
- package/scripts/postinstall-rebuild.mjs +0 -41
- package/skills/agentic-orchestration/SKILL.md +0 -399
|
@@ -7,16 +7,16 @@ import { openDatabase, closeDatabase, getDatabase } from '../../db.js';
|
|
|
7
7
|
import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn } from '../../core/operations.js';
|
|
8
8
|
import { KnowledgeGraph } from '../../knowledge-graph.js';
|
|
9
9
|
import { logCapabilities, readConfig, updateConfig, detectCapabilities } from '../../core/config.js';
|
|
10
|
+
import { languageValueError } from '../../core/output-language.js';
|
|
10
11
|
import { computePatterns } from '../../core/patterns.js';
|
|
11
12
|
import { computeAnalytics, computePmAnalytics } from '../../core/analytics.js';
|
|
12
13
|
import { computeStats } from '../../core/stats.js';
|
|
13
14
|
import { computeProjects } from '../../core/projects.js';
|
|
14
15
|
import { computeGraph } from '../../core/graph.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { getUpdateCheck } from '../../core/version-check.js';
|
|
16
|
+
import { RememberSchema as RememberBody, RecallSchema as RecallBody, ForgetSchema as ForgetBody, ExportSchema as ExportBody, ImportSchema as ImportBody, LearnSchema as LearnBody, } from '../schemas.js';
|
|
17
|
+
import { checkForUpdate, getLastUpdateCheck, getUpdateCheck } from '../../core/version-check.js';
|
|
18
18
|
import { getCurrentInstallChannel, getInstallChannelSupport } from '../../core/install-channel.js';
|
|
19
|
-
import { getDbPath, getMemeshDirFromDbPath } from '../../core/paths.js';
|
|
19
|
+
import { getDbPath, getMemeshDirFromDbPath, redactSecrets, redactUserPaths } from '../../core/paths.js';
|
|
20
20
|
import { RETIRED_ROUTES } from './retired-routes.js';
|
|
21
21
|
import fs from 'fs';
|
|
22
22
|
import path from 'path';
|
|
@@ -25,12 +25,23 @@ const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)
|
|
|
25
25
|
const packageRoot = path.dirname(packageJsonPath);
|
|
26
26
|
const packageVersion = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).version ?? '0.0.0';
|
|
27
27
|
const app = express();
|
|
28
|
+
export function isLoopbackRequest(req) {
|
|
29
|
+
const ip = req.ip ?? '';
|
|
30
|
+
return ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
|
|
31
|
+
}
|
|
28
32
|
const apiLimiter = rateLimit({
|
|
29
33
|
windowMs: 15 * 60 * 1000,
|
|
30
34
|
max: 100,
|
|
31
35
|
standardHeaders: true,
|
|
32
36
|
legacyHeaders: false,
|
|
33
|
-
|
|
37
|
+
skip: (req) => isLoopbackRequest(req),
|
|
38
|
+
handler: (_req, res) => {
|
|
39
|
+
res.status(429).json({
|
|
40
|
+
success: false,
|
|
41
|
+
errorCode: 'rate.limited',
|
|
42
|
+
error: 'Too many requests in a short time. Wait a moment and try again.',
|
|
43
|
+
});
|
|
44
|
+
},
|
|
34
45
|
});
|
|
35
46
|
let remoteToken = null;
|
|
36
47
|
const serverAuthRequired = new WeakMap();
|
|
@@ -97,6 +108,7 @@ function bearerAuth(req, res, next) {
|
|
|
97
108
|
if (!remoteToken) {
|
|
98
109
|
res.status(503).json({
|
|
99
110
|
success: false,
|
|
111
|
+
errorCode: 'auth.not-configured',
|
|
100
112
|
error: 'remote bearer auth not configured on this server',
|
|
101
113
|
});
|
|
102
114
|
return;
|
|
@@ -105,17 +117,17 @@ function bearerAuth(req, res, next) {
|
|
|
105
117
|
const trimmed = header.trim();
|
|
106
118
|
const wsIndex = trimmed.search(/\s/);
|
|
107
119
|
if (wsIndex < 0 || trimmed.slice(0, wsIndex).toLowerCase() !== 'bearer') {
|
|
108
|
-
res.status(401).json({ success: false, error: 'Missing Authorization: Bearer <token>' });
|
|
120
|
+
res.status(401).json({ success: false, errorCode: 'auth.missing-bearer', error: 'Missing Authorization: Bearer <token>' });
|
|
109
121
|
return;
|
|
110
122
|
}
|
|
111
123
|
const tokenPart = trimmed.slice(wsIndex + 1).trim();
|
|
112
124
|
if (!tokenPart) {
|
|
113
|
-
res.status(401).json({ success: false, error: 'Missing Authorization: Bearer <token>' });
|
|
125
|
+
res.status(401).json({ success: false, errorCode: 'auth.missing-bearer', error: 'Missing Authorization: Bearer <token>' });
|
|
114
126
|
return;
|
|
115
127
|
}
|
|
116
128
|
const presented = Buffer.from(tokenPart, 'utf8');
|
|
117
129
|
if (!constantTimeEquals(presented, remoteToken)) {
|
|
118
|
-
res.status(401).json({ success: false, error: 'Invalid bearer token' });
|
|
130
|
+
res.status(401).json({ success: false, errorCode: 'auth.invalid-token', error: 'Invalid bearer token' });
|
|
119
131
|
return;
|
|
120
132
|
}
|
|
121
133
|
next();
|
|
@@ -130,6 +142,7 @@ function payloadTooLargeHandler(err, _req, res, next) {
|
|
|
130
142
|
if (e.type === 'entity.parse.failed' || (err instanceof SyntaxError && (e.status === 400 || e.statusCode === 400))) {
|
|
131
143
|
res.status(400).json({
|
|
132
144
|
success: false,
|
|
145
|
+
errorCode: 'validation.bad-body',
|
|
133
146
|
error: 'Request body is not valid JSON.',
|
|
134
147
|
hint: 'Send a JSON object with Content-Type: application/json.',
|
|
135
148
|
});
|
|
@@ -140,6 +153,7 @@ function payloadTooLargeHandler(err, _req, res, next) {
|
|
|
140
153
|
return next(err);
|
|
141
154
|
res.status(413).json({
|
|
142
155
|
success: false,
|
|
156
|
+
errorCode: 'payload.too-large',
|
|
143
157
|
error: 'Request body exceeds the 1MB limit',
|
|
144
158
|
code: 'PAYLOAD_TOO_LARGE',
|
|
145
159
|
limit: '1mb',
|
|
@@ -179,25 +193,16 @@ app.get('/v1/health', (_req, res) => {
|
|
|
179
193
|
if (message === 'Database not opened') {
|
|
180
194
|
res.status(503).json({
|
|
181
195
|
success: false,
|
|
196
|
+
errorCode: 'server.internal',
|
|
182
197
|
error: 'Database not initialized',
|
|
183
198
|
details: 'MeMesh database failed to open at startup. Check server logs for details, or run "memesh doctor" to diagnose.',
|
|
184
199
|
});
|
|
185
200
|
}
|
|
186
201
|
else {
|
|
187
|
-
res.status(500).json({ success: false, error: message });
|
|
202
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: message });
|
|
188
203
|
}
|
|
189
204
|
}
|
|
190
205
|
});
|
|
191
|
-
function redactSecrets(input) {
|
|
192
|
-
return input
|
|
193
|
-
.replace(/sk-ant-[A-Za-z0-9_-]{20,}/g, 'sk-ant-***REDACTED***')
|
|
194
|
-
.replace(/sk-proj-[A-Za-z0-9_-]{20,}/g, 'sk-proj-***REDACTED***')
|
|
195
|
-
.replace(/sk-[A-Za-z0-9]{32,}/g, 'sk-***REDACTED***')
|
|
196
|
-
.replace(/ghp_[A-Za-z0-9]{30,}/g, 'ghp_***REDACTED***')
|
|
197
|
-
.replace(/gho_[A-Za-z0-9]{30,}/g, 'gho_***REDACTED***')
|
|
198
|
-
.replace(/AKIA[A-Z0-9]{16}/g, 'AKIA***REDACTED***')
|
|
199
|
-
.replace(/Bearer\s+[A-Za-z0-9._-]{20,}/gi, 'Bearer ***REDACTED***');
|
|
200
|
-
}
|
|
201
206
|
app.get('/v1/doctor', async (_req, res) => {
|
|
202
207
|
try {
|
|
203
208
|
const { runDoctor } = await import('../../core/doctor.js');
|
|
@@ -205,11 +210,11 @@ app.get('/v1/doctor', async (_req, res) => {
|
|
|
205
210
|
packageRoot,
|
|
206
211
|
packageVersion,
|
|
207
212
|
});
|
|
208
|
-
const safe = JSON.parse(redactSecrets(JSON.stringify(result)));
|
|
213
|
+
const safe = JSON.parse(redactUserPaths(redactSecrets(JSON.stringify(result))));
|
|
209
214
|
res.json({ success: true, data: safe });
|
|
210
215
|
}
|
|
211
216
|
catch (err) {
|
|
212
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
217
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
213
218
|
}
|
|
214
219
|
});
|
|
215
220
|
function requireJsonBody(req, res) {
|
|
@@ -217,6 +222,7 @@ function requireJsonBody(req, res) {
|
|
|
217
222
|
return true;
|
|
218
223
|
res.status(400).json({
|
|
219
224
|
success: false,
|
|
225
|
+
errorCode: 'validation.bad-body',
|
|
220
226
|
error: 'No JSON body was parsed from this request.',
|
|
221
227
|
hint: 'Send the payload with Content-Type: application/json.',
|
|
222
228
|
});
|
|
@@ -229,27 +235,29 @@ function handlePost(schema, req, res, handler) {
|
|
|
229
235
|
if (!parsed.success) {
|
|
230
236
|
res.status(400).json({
|
|
231
237
|
success: false,
|
|
238
|
+
errorCode: 'validation.bad-body',
|
|
232
239
|
error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
|
|
233
240
|
});
|
|
234
241
|
return;
|
|
235
242
|
}
|
|
236
|
-
Promise.resolve(
|
|
243
|
+
Promise.resolve()
|
|
244
|
+
.then(() => handler(parsed.data))
|
|
237
245
|
.then((data) => res.json({ success: true, data }))
|
|
238
|
-
.catch((err) => res.status(400).json({ success: false, error: err instanceof Error ? err.message : String(err) }));
|
|
246
|
+
.catch((err) => res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) }));
|
|
239
247
|
}
|
|
240
248
|
function handleGet(res, produce) {
|
|
241
249
|
Promise.resolve()
|
|
242
250
|
.then(produce)
|
|
243
251
|
.then((data) => res.json({ success: true, data }))
|
|
244
|
-
.catch((err) => res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) }));
|
|
252
|
+
.catch((err) => res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) }));
|
|
245
253
|
}
|
|
246
|
-
app.post('/v1/remember', (req, res) => handlePost(RememberBody, req, res, remember));
|
|
254
|
+
app.post('/v1/remember', (req, res) => handlePost(RememberBody, req, res, (data) => remember({ ...data, sourceHost: 'http' })));
|
|
247
255
|
app.post('/v1/recall', async (req, res) => {
|
|
248
256
|
if (!requireJsonBody(req, res))
|
|
249
257
|
return;
|
|
250
258
|
const parsed = RecallBody.safeParse(req.body);
|
|
251
259
|
if (!parsed.success) {
|
|
252
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
|
|
260
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
|
|
253
261
|
return;
|
|
254
262
|
}
|
|
255
263
|
try {
|
|
@@ -257,27 +265,46 @@ app.post('/v1/recall', async (req, res) => {
|
|
|
257
265
|
res.json({ success: true, data: conflicts.length > 0 ? { entities, conflicts } : entities });
|
|
258
266
|
}
|
|
259
267
|
catch (err) {
|
|
260
|
-
res.status(400).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
268
|
+
res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) });
|
|
261
269
|
}
|
|
262
270
|
});
|
|
263
271
|
app.post('/v1/forget', (req, res) => handlePost(ForgetBody, req, res, forget));
|
|
264
272
|
app.post('/v1/consolidate', (_req, res) => {
|
|
265
|
-
res.status(410).json({ success: false, error: RETIRED_ROUTES['/v1/consolidate'] });
|
|
273
|
+
res.status(410).json({ success: false, errorCode: 'route.retired', error: RETIRED_ROUTES['/v1/consolidate'] });
|
|
266
274
|
});
|
|
267
275
|
app.post('/v1/export', (req, res) => handlePost(ExportBody, req, res, exportMemories));
|
|
268
276
|
app.post('/v1/import', (req, res) => handlePost(ImportBody, req, res, importMemories));
|
|
269
|
-
app.post('/v1/learn', (req, res) => handlePost(LearnBody, req, res, learn));
|
|
270
|
-
app.post('/v1/verify', (
|
|
277
|
+
app.post('/v1/learn', (req, res) => handlePost(LearnBody, req, res, (data) => learn({ ...data, sourceHost: 'http' })));
|
|
278
|
+
app.post('/v1/verify', (_req, res) => {
|
|
279
|
+
res.status(410).json({ success: false, errorCode: 'route.retired', error: RETIRED_ROUTES['/v1/verify'] });
|
|
280
|
+
});
|
|
281
|
+
const API_KEY_MASK = '***';
|
|
271
282
|
function maskLlmSecrets(obj) {
|
|
272
283
|
const masked = { ...obj };
|
|
273
284
|
if (masked.llm?.apiKey) {
|
|
274
|
-
masked.llm = { ...masked.llm, apiKey:
|
|
285
|
+
masked.llm = { ...masked.llm, apiKey: API_KEY_MASK };
|
|
275
286
|
}
|
|
276
287
|
if (Array.isArray(masked.llmFallbacks) && masked.llmFallbacks.length > 0) {
|
|
277
|
-
masked.llmFallbacks = masked.llmFallbacks.map(fb => fb?.apiKey ? { ...fb, apiKey:
|
|
288
|
+
masked.llmFallbacks = masked.llmFallbacks.map(fb => fb?.apiKey ? { ...fb, apiKey: API_KEY_MASK } : fb);
|
|
278
289
|
}
|
|
279
290
|
return masked;
|
|
280
291
|
}
|
|
292
|
+
function preserveFallbackApiKeys(incoming, stored) {
|
|
293
|
+
return incoming.map((entry) => {
|
|
294
|
+
const { keepKeyFrom, ...clean } = entry;
|
|
295
|
+
if (clean.apiKey === API_KEY_MASK)
|
|
296
|
+
delete clean.apiKey;
|
|
297
|
+
if (clean.apiKey)
|
|
298
|
+
return clean;
|
|
299
|
+
if (typeof keepKeyFrom === 'number' && stored && keepKeyFrom >= 0 && keepKeyFrom < stored.length) {
|
|
300
|
+
const src = stored[keepKeyFrom];
|
|
301
|
+
if (src && src.provider === clean.provider && src.apiKey) {
|
|
302
|
+
return { ...clean, apiKey: src.apiKey };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return clean;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
281
308
|
app.get('/v1/config', (_req, res) => {
|
|
282
309
|
try {
|
|
283
310
|
const config = readConfig();
|
|
@@ -285,7 +312,7 @@ app.get('/v1/config', (_req, res) => {
|
|
|
285
312
|
res.json({ success: true, data: { config: maskLlmSecrets(config), capabilities: maskLlmSecrets(caps) } });
|
|
286
313
|
}
|
|
287
314
|
catch (err) {
|
|
288
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
315
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
289
316
|
}
|
|
290
317
|
});
|
|
291
318
|
const ConfigBody = z.object({
|
|
@@ -301,11 +328,16 @@ const ConfigBody = z.object({
|
|
|
301
328
|
provider: z.enum(['anthropic', 'openai', 'ollama']),
|
|
302
329
|
model: z.string().optional(),
|
|
303
330
|
apiKey: z.string().optional(),
|
|
331
|
+
keepKeyFrom: z.number().int().nonnegative().nullable().optional(),
|
|
304
332
|
})).optional(),
|
|
305
333
|
autoCapture: z.boolean().optional(),
|
|
306
334
|
sessionLimit: z.number().int().min(1).max(100).optional(),
|
|
307
|
-
enableAgenticOrchestration: z.boolean().optional(),
|
|
308
335
|
autoUpdate: z.enum(['off', 'patch', 'minor', 'major']).optional(),
|
|
336
|
+
language: z.string().trim().min(1).max(60)
|
|
337
|
+
.refine((v) => languageValueError(v) === null, {
|
|
338
|
+
message: 'language must not contain line breaks or other control characters',
|
|
339
|
+
})
|
|
340
|
+
.optional(),
|
|
309
341
|
setupCompleted: z.boolean().optional(),
|
|
310
342
|
}).strip();
|
|
311
343
|
app.post('/v1/config', async (req, res) => {
|
|
@@ -313,29 +345,34 @@ app.post('/v1/config', async (req, res) => {
|
|
|
313
345
|
return;
|
|
314
346
|
const parsed = ConfigBody.safeParse(req.body);
|
|
315
347
|
if (!parsed.success) {
|
|
316
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
|
|
348
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
|
|
317
349
|
return;
|
|
318
350
|
}
|
|
319
351
|
try {
|
|
320
352
|
const before = readConfig();
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (parsed.data.llmFallbacks) {
|
|
362
|
+
parsed.data.llmFallbacks = preserveFallbackApiKeys(parsed.data.llmFallbacks, before.llmFallbacks);
|
|
328
363
|
}
|
|
364
|
+
const updated = updateConfig(parsed.data);
|
|
329
365
|
res.json({ success: true, data: maskLlmSecrets(updated) });
|
|
330
366
|
}
|
|
331
367
|
catch (err) {
|
|
332
|
-
res.status(400).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
368
|
+
res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) });
|
|
333
369
|
}
|
|
334
370
|
});
|
|
335
371
|
const ConfigTestBody = z.object({
|
|
336
372
|
provider: z.enum(['anthropic', 'openai', 'ollama']),
|
|
337
373
|
apiKey: z.string().max(500).optional(),
|
|
338
374
|
host: z.string().max(500).optional(),
|
|
375
|
+
fallbackIndex: z.number().int().nonnegative().optional(),
|
|
339
376
|
});
|
|
340
377
|
app.post('/v1/config/test', async (req, res) => {
|
|
341
378
|
if (!requireJsonBody(req, res))
|
|
@@ -344,17 +381,23 @@ app.post('/v1/config/test', async (req, res) => {
|
|
|
344
381
|
if (!parsed.success) {
|
|
345
382
|
res.status(400).json({
|
|
346
383
|
success: false,
|
|
384
|
+
errorCode: 'validation.bad-body',
|
|
347
385
|
error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
|
|
348
386
|
});
|
|
349
387
|
return;
|
|
350
388
|
}
|
|
351
389
|
try {
|
|
352
390
|
const { probeProvider } = await import('../../core/llm-validator.js');
|
|
353
|
-
const { provider, host } = parsed.data;
|
|
391
|
+
const { provider, host, fallbackIndex } = parsed.data;
|
|
354
392
|
let { apiKey } = parsed.data;
|
|
355
393
|
if (!apiKey && (provider === 'anthropic' || provider === 'openai')) {
|
|
356
394
|
const existing = readConfig();
|
|
357
|
-
if (
|
|
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) {
|
|
358
401
|
apiKey = existing.llm.apiKey;
|
|
359
402
|
}
|
|
360
403
|
}
|
|
@@ -362,7 +405,7 @@ app.post('/v1/config/test', async (req, res) => {
|
|
|
362
405
|
res.json({ success: true, data: result });
|
|
363
406
|
}
|
|
364
407
|
catch (err) {
|
|
365
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
408
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
366
409
|
}
|
|
367
410
|
});
|
|
368
411
|
app.get('/v1/update-status', async (req, res) => {
|
|
@@ -396,7 +439,7 @@ app.get('/v1/update-status', async (req, res) => {
|
|
|
396
439
|
});
|
|
397
440
|
}
|
|
398
441
|
catch (err) {
|
|
399
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
442
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
400
443
|
}
|
|
401
444
|
});
|
|
402
445
|
app.get('/v1/graph', (_req, res) => handleGet(res, () => computeGraph(getDatabase())));
|
|
@@ -415,7 +458,7 @@ app.post('/v1/demo/seed', async (_req, res) => {
|
|
|
415
458
|
res.json({ success: true, data });
|
|
416
459
|
}
|
|
417
460
|
catch (err) {
|
|
418
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
461
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
419
462
|
}
|
|
420
463
|
});
|
|
421
464
|
app.post('/v1/demo/reset', async (_req, res) => {
|
|
@@ -425,7 +468,7 @@ app.post('/v1/demo/reset', async (_req, res) => {
|
|
|
425
468
|
res.json({ success: true, data });
|
|
426
469
|
}
|
|
427
470
|
catch (err) {
|
|
428
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
471
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
429
472
|
}
|
|
430
473
|
});
|
|
431
474
|
app.get('/v1/projects', (_req, res) => handleGet(res, () => computeProjects(getDatabase())));
|
|
@@ -437,7 +480,7 @@ app.get('/v1/telemetry', async (req, res) => {
|
|
|
437
480
|
try {
|
|
438
481
|
const parsed = TelemetryQuerySchema.safeParse(req.query);
|
|
439
482
|
if (!parsed.success) {
|
|
440
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
483
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
441
484
|
return;
|
|
442
485
|
}
|
|
443
486
|
const { summariseTelemetry } = await import('../../core/llm-telemetry.js');
|
|
@@ -445,7 +488,7 @@ app.get('/v1/telemetry', async (req, res) => {
|
|
|
445
488
|
res.json({ success: true, data: { window_days: parsed.data.window, summaries } });
|
|
446
489
|
}
|
|
447
490
|
catch (err) {
|
|
448
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
491
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
449
492
|
}
|
|
450
493
|
});
|
|
451
494
|
const DreamProposalsQuerySchema = z.object({
|
|
@@ -455,7 +498,7 @@ app.get('/v1/dream/proposals', (req, res) => {
|
|
|
455
498
|
try {
|
|
456
499
|
const parsed = DreamProposalsQuerySchema.safeParse(req.query);
|
|
457
500
|
if (!parsed.success) {
|
|
458
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
501
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
459
502
|
return;
|
|
460
503
|
}
|
|
461
504
|
const status = parsed.data.status;
|
|
@@ -465,22 +508,22 @@ app.get('/v1/dream/proposals', (req, res) => {
|
|
|
465
508
|
? [...listProposals(db, 'pending'), ...listProposals(db, 'applied'), ...listProposals(db, 'rejected')]
|
|
466
509
|
: listProposals(db, status);
|
|
467
510
|
res.json({ success: true, data: rows });
|
|
468
|
-
}).catch((err) => res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) }));
|
|
511
|
+
}).catch((err) => res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) }));
|
|
469
512
|
}
|
|
470
513
|
catch (err) {
|
|
471
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
514
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
472
515
|
}
|
|
473
516
|
});
|
|
474
517
|
app.get('/v1/dream/proposals/:id', (req, res) => {
|
|
475
518
|
const id = parseInt(req.params.id, 10);
|
|
476
519
|
if (!Number.isInteger(id) || id < 1) {
|
|
477
|
-
res.status(400).json({ success: false, error: 'invalid id' });
|
|
520
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
|
|
478
521
|
return;
|
|
479
522
|
}
|
|
480
523
|
try {
|
|
481
524
|
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);
|
|
482
525
|
if (!row) {
|
|
483
|
-
res.status(404).json({ success: false, error: `proposal #${id} not found` });
|
|
526
|
+
res.status(404).json({ success: false, errorCode: 'resource.not-found', error: `proposal #${id} not found` });
|
|
484
527
|
return;
|
|
485
528
|
}
|
|
486
529
|
let digest = null;
|
|
@@ -496,7 +539,7 @@ app.get('/v1/dream/proposals/:id', (req, res) => {
|
|
|
496
539
|
res.json({ success: true, data: { ...row, proposed_digest: digest, source_ids: sourceIds } });
|
|
497
540
|
}
|
|
498
541
|
catch (err) {
|
|
499
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
542
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
500
543
|
}
|
|
501
544
|
});
|
|
502
545
|
const DreamRunBody = z.object({
|
|
@@ -510,52 +553,60 @@ app.post('/v1/dream/run', async (req, res) => {
|
|
|
510
553
|
if (!parsed.success) {
|
|
511
554
|
res.status(400).json({
|
|
512
555
|
success: false,
|
|
556
|
+
errorCode: 'validation.bad-body',
|
|
513
557
|
error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
|
|
514
558
|
});
|
|
515
559
|
return;
|
|
516
560
|
}
|
|
517
561
|
try {
|
|
518
562
|
const { runDreamer } = await import('../../core/dreamer.js');
|
|
519
|
-
const
|
|
520
|
-
|
|
563
|
+
const caps = detectCapabilities();
|
|
564
|
+
const llm = caps.llm;
|
|
565
|
+
if (!llm) {
|
|
521
566
|
res.status(400).json({
|
|
522
567
|
success: false,
|
|
568
|
+
errorCode: 'llm.not-configured',
|
|
523
569
|
error: 'No LLM configured — dream run requires Smart Mode. Configure a provider in Settings.',
|
|
524
570
|
});
|
|
525
571
|
return;
|
|
526
572
|
}
|
|
527
|
-
const result = await runDreamer(getDatabase(),
|
|
573
|
+
const result = await runDreamer(getDatabase(), llm, {
|
|
528
574
|
project: parsed.data.project,
|
|
529
575
|
windowDays: parsed.data.windowDays,
|
|
530
576
|
maxLlmCalls: parsed.data.maxLlmCalls,
|
|
531
|
-
fallbacks:
|
|
577
|
+
fallbacks: caps.llmFallbacks,
|
|
532
578
|
validateBeforeStage: parsed.data.validate,
|
|
533
579
|
});
|
|
534
580
|
res.json({ success: true, data: result });
|
|
535
581
|
}
|
|
536
582
|
catch (err) {
|
|
537
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
583
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
538
584
|
}
|
|
539
585
|
});
|
|
540
586
|
app.post('/v1/dream/proposals/:id/accept', async (req, res) => {
|
|
541
587
|
const id = parseInt(req.params.id, 10);
|
|
542
588
|
if (!Number.isInteger(id) || id < 1) {
|
|
543
|
-
res.status(400).json({ success: false, error: 'invalid id' });
|
|
589
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
|
|
544
590
|
return;
|
|
545
591
|
}
|
|
592
|
+
let NothingToClaim;
|
|
546
593
|
try {
|
|
547
|
-
const
|
|
594
|
+
const dreamer = await import('../../core/dreamer.js');
|
|
595
|
+
NothingToClaim = dreamer.NothingToClaimError;
|
|
548
596
|
const kg = new KnowledgeGraph(getDatabase());
|
|
549
|
-
const result = applyProposal(getDatabase(), id, kg);
|
|
597
|
+
const result = dreamer.applyProposal(getDatabase(), id, kg);
|
|
550
598
|
res.json({ success: true, data: result });
|
|
551
599
|
}
|
|
552
600
|
catch (err) {
|
|
553
601
|
const msg = err instanceof Error ? err.message : String(err);
|
|
554
|
-
if (
|
|
555
|
-
res.status(
|
|
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 });
|
|
556
607
|
}
|
|
557
608
|
else {
|
|
558
|
-
res.status(500).json({ success: false, error: msg });
|
|
609
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: msg });
|
|
559
610
|
}
|
|
560
611
|
}
|
|
561
612
|
});
|
|
@@ -565,12 +616,12 @@ const RejectBodySchema = z.object({
|
|
|
565
616
|
app.post('/v1/dream/proposals/:id/reject', async (req, res) => {
|
|
566
617
|
const id = parseInt(req.params.id, 10);
|
|
567
618
|
if (!Number.isInteger(id) || id < 1) {
|
|
568
|
-
res.status(400).json({ success: false, error: 'invalid id' });
|
|
619
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
|
|
569
620
|
return;
|
|
570
621
|
}
|
|
571
622
|
const parsed = RejectBodySchema.safeParse(req.body ?? {});
|
|
572
623
|
if (!parsed.success) {
|
|
573
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
624
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
574
625
|
return;
|
|
575
626
|
}
|
|
576
627
|
try {
|
|
@@ -581,10 +632,10 @@ app.post('/v1/dream/proposals/:id/reject', async (req, res) => {
|
|
|
581
632
|
catch (err) {
|
|
582
633
|
const msg = err instanceof Error ? err.message : String(err);
|
|
583
634
|
if (/not found or not pending/.test(msg)) {
|
|
584
|
-
res.status(404).json({ success: false, error: msg });
|
|
635
|
+
res.status(404).json({ success: false, errorCode: 'resource.not-found', error: msg });
|
|
585
636
|
}
|
|
586
637
|
else {
|
|
587
|
-
res.status(500).json({ success: false, error: msg });
|
|
638
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: msg });
|
|
588
639
|
}
|
|
589
640
|
}
|
|
590
641
|
});
|
|
@@ -597,7 +648,7 @@ app.get('/v1/entities', (req, res) => {
|
|
|
597
648
|
try {
|
|
598
649
|
const parsed = EntitiesQuerySchema.safeParse(req.query);
|
|
599
650
|
if (!parsed.success) {
|
|
600
|
-
res.status(400).json({ success: false, error: `Invalid query: ${parsed.error.message}` });
|
|
651
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: `Invalid query: ${parsed.error.message}` });
|
|
601
652
|
return;
|
|
602
653
|
}
|
|
603
654
|
const { type: typeFilter, limit, status } = parsed.data;
|
|
@@ -610,7 +661,7 @@ app.get('/v1/entities', (req, res) => {
|
|
|
610
661
|
res.json({ success: true, data: entities });
|
|
611
662
|
}
|
|
612
663
|
catch (err) {
|
|
613
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
664
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
614
665
|
}
|
|
615
666
|
});
|
|
616
667
|
app.get('/v1/entities/:name', (req, res) => {
|
|
@@ -619,13 +670,13 @@ app.get('/v1/entities/:name', (req, res) => {
|
|
|
619
670
|
const kg = new KnowledgeGraph(db);
|
|
620
671
|
const entity = kg.getEntity(req.params.name);
|
|
621
672
|
if (!entity) {
|
|
622
|
-
res.status(404).json({ success: false, error: `Entity "${req.params.name}" not found` });
|
|
673
|
+
res.status(404).json({ success: false, errorCode: 'resource.not-found', error: `Entity "${req.params.name}" not found` });
|
|
623
674
|
return;
|
|
624
675
|
}
|
|
625
676
|
res.json({ success: true, data: entity });
|
|
626
677
|
}
|
|
627
678
|
catch (err) {
|
|
628
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
679
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
629
680
|
}
|
|
630
681
|
});
|
|
631
682
|
const HOST = process.env.MEMESH_HTTP_HOST || '127.0.0.1';
|
|
@@ -645,6 +696,7 @@ function isLoopbackHost(host) {
|
|
|
645
696
|
app.use((req, res) => {
|
|
646
697
|
res.status(404).json({
|
|
647
698
|
success: false,
|
|
699
|
+
errorCode: 'route.not-found',
|
|
648
700
|
code: 'NOT_FOUND',
|
|
649
701
|
error: `No route for ${req.method} ${req.path}`,
|
|
650
702
|
});
|
|
@@ -655,6 +707,10 @@ export function startServer(host = HOST, port = PORT, opts) {
|
|
|
655
707
|
if (!allowRemote && isRemote) {
|
|
656
708
|
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.`);
|
|
657
709
|
}
|
|
710
|
+
if (allowRemote && !isRemote) {
|
|
711
|
+
process.stderr.write(`MeMesh HTTP: --allow-remote has no effect on loopback host "${host}" — the server stays local ` +
|
|
712
|
+
'and no bearer token is generated. Add --host <address> to bind somewhere reachable.\n');
|
|
713
|
+
}
|
|
658
714
|
if (isRemote) {
|
|
659
715
|
const { token, freshlyCreated } = loadOrCreateRemoteToken();
|
|
660
716
|
remoteToken = token;
|
|
@@ -694,16 +750,39 @@ export function startServer(host = HOST, port = PORT, opts) {
|
|
|
694
750
|
throw new Error(`Database initialization failed: ${message}`, { cause: err });
|
|
695
751
|
}
|
|
696
752
|
logCapabilities();
|
|
753
|
+
const injectedUpdateSeam = Boolean(opts?.updateCheckImpl || opts?.lastUpdateCheckImpl);
|
|
754
|
+
const updateCheckWanted = opts?.autoUpdateCheck === true || injectedUpdateSeam;
|
|
755
|
+
if (updateCheckWanted && !process.env.MEMESH_SKIP_UPDATE_CHECK) {
|
|
756
|
+
void (async () => {
|
|
757
|
+
const readLast = opts?.lastUpdateCheckImpl ?? getLastUpdateCheck;
|
|
758
|
+
const refresh = opts?.updateCheckImpl ?? checkForUpdate;
|
|
759
|
+
const cached = readLast(packageVersion);
|
|
760
|
+
if (cached && (cached.freshness === 'fresh' || cached.freshness === 'cached'))
|
|
761
|
+
return;
|
|
762
|
+
await refresh(packageVersion);
|
|
763
|
+
})().catch(() => { });
|
|
764
|
+
}
|
|
697
765
|
const server = app.listen(port, host, () => {
|
|
698
766
|
const addr = server.address();
|
|
699
767
|
if (addr && typeof addr === 'object') {
|
|
700
768
|
console.log(`MeMesh HTTP server running at http://${addr.address}:${addr.port}`);
|
|
701
769
|
console.log(`MeMesh dashboard: http://${addr.address}:${addr.port}/dashboard`);
|
|
702
770
|
}
|
|
771
|
+
});
|
|
772
|
+
server.on('error', (err) => {
|
|
773
|
+
const where = `${host}:${port}`;
|
|
774
|
+
if (err.code === 'EADDRINUSE') {
|
|
775
|
+
console.error(`MeMesh: cannot start — ${where} is already in use.`);
|
|
776
|
+
console.error(`MeMesh: stop whatever is listening there, or pick another port with --port <n>.`);
|
|
777
|
+
}
|
|
778
|
+
else if (err.code === 'EACCES') {
|
|
779
|
+
console.error(`MeMesh: cannot start — not permitted to bind ${where}.`);
|
|
780
|
+
console.error(`MeMesh: ports below 1024 need elevated privileges; pick a port above 1024 with --port <n>.`);
|
|
781
|
+
}
|
|
703
782
|
else {
|
|
704
|
-
console.
|
|
705
|
-
console.log(`MeMesh dashboard: http://${host}:${port}/dashboard`);
|
|
783
|
+
console.error(`MeMesh: cannot start on ${where} — ${err.message}`);
|
|
706
784
|
}
|
|
785
|
+
process.exit(1);
|
|
707
786
|
});
|
|
708
787
|
serverAuthRequired.set(server, isRemote);
|
|
709
788
|
return server;
|
|
@@ -713,7 +792,14 @@ export function __setRemoteTokenForTest(value) {
|
|
|
713
792
|
}
|
|
714
793
|
const isMain = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/'));
|
|
715
794
|
if (isMain || process.argv[1]?.endsWith('memesh-http')) {
|
|
716
|
-
|
|
795
|
+
let server;
|
|
796
|
+
try {
|
|
797
|
+
server = startServer();
|
|
798
|
+
}
|
|
799
|
+
catch (err) {
|
|
800
|
+
console.error(`MeMesh: ${err instanceof Error ? err.message : String(err)}`);
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
717
803
|
function shutdown() {
|
|
718
804
|
server.close();
|
|
719
805
|
try {
|