claude-memory-layer 1.0.31 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/cli/index.js +1 -1
- package/package.json +11 -2
- package/scripts/postinstall-embedding-backend.cjs +16 -12
- package/AGENTS.md +0 -71
- package/CLAUDE.md +0 -30
- package/HANDOFF.md +0 -92
- package/Memo.txt +0 -558
- package/benchmarks/replay/anonymized-real-sessions.json +0 -48
- package/config/kpi-thresholds.json +0 -7
- package/context.md +0 -636
- package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +0 -627
- package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +0 -440
- package/docs/MCP_MEMORY_SERVICE_COMPARATIVE_REVIEW.md +0 -271
- package/docs/MEMORY_USEFULNESS_AUDIT.md +0 -371
- package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +0 -80
- package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +0 -333
- package/docs/MEMU_ADOPTION.md +0 -40
- package/docs/OPERATIONS.md +0 -18
- package/docs/PRODUCT_VALIDATION_MATRIX.md +0 -82
- package/docs/PROJECT_STRUCTURE_ANALYSIS.md +0 -421
- package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +0 -501
- package/docs/REFACTORING_PLAN_THIN_CORE.md +0 -414
- package/docs/REFERENCE_PROJECT_ANALYSES.md +0 -25
- package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +0 -452
- package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +0 -446
- package/docs/architecture/comparison-index.md +0 -47
- package/docs/reports/codex-real-data-validation-20260505T040447Z.md +0 -46
- package/plan.md +0 -1642
- package/scripts/build.ts +0 -159
- package/scripts/bump-patch-version.sh +0 -18
- package/scripts/delete-unknown-projects.js +0 -154
- package/scripts/fix-sync-gap.js +0 -32
- package/scripts/generate-session-qrels.ts +0 -126
- package/scripts/heartbeat-memory-orchestrator.sh +0 -28
- package/scripts/replay-retrieval-benchmark.ts +0 -69
- package/scripts/report-sync-gap.js +0 -26
- package/scripts/review-queue-auto-resolve.js +0 -21
- package/scripts/sync-gap-auto-heal.sh +0 -17
- package/spec.md +0 -624
- package/specs/20260207-dashboard-upgrade/context.md +0 -38
- package/specs/20260207-dashboard-upgrade/spec.md +0 -96
- package/specs/citations-system/context.md +0 -243
- package/specs/citations-system/plan.md +0 -495
- package/specs/citations-system/spec.md +0 -371
- package/specs/endless-mode/context.md +0 -305
- package/specs/endless-mode/plan.md +0 -620
- package/specs/endless-mode/spec.md +0 -455
- package/specs/entity-edge-model/context.md +0 -401
- package/specs/entity-edge-model/plan.md +0 -459
- package/specs/entity-edge-model/spec.md +0 -391
- package/specs/evidence-aligner-v2/context.md +0 -401
- package/specs/evidence-aligner-v2/plan.md +0 -303
- package/specs/evidence-aligner-v2/spec.md +0 -312
- package/specs/mcp-desktop-integration/context.md +0 -278
- package/specs/mcp-desktop-integration/plan.md +0 -550
- package/specs/mcp-desktop-integration/spec.md +0 -494
- package/specs/memory-utilization-improvements/context.md +0 -145
- package/specs/memory-utilization-improvements/plan.md +0 -361
- package/specs/memory-utilization-improvements/spec.md +0 -361
- package/specs/post-tool-use-hook/context.md +0 -319
- package/specs/post-tool-use-hook/plan.md +0 -469
- package/specs/post-tool-use-hook/spec.md +0 -364
- package/specs/private-tags/context.md +0 -288
- package/specs/private-tags/plan.md +0 -412
- package/specs/private-tags/spec.md +0 -345
- package/specs/progressive-disclosure/context.md +0 -346
- package/specs/progressive-disclosure/plan.md +0 -663
- package/specs/progressive-disclosure/spec.md +0 -415
- package/specs/selective-tool-observation/context.md +0 -100
- package/specs/selective-tool-observation/plan.md +0 -158
- package/specs/selective-tool-observation/spec.md +0 -127
- package/specs/task-entity-system/context.md +0 -297
- package/specs/task-entity-system/plan.md +0 -301
- package/specs/task-entity-system/spec.md +0 -314
- package/specs/thin-core-refactor/context.md +0 -275
- package/specs/thin-core-refactor/plan.md +0 -536
- package/specs/thin-core-refactor/spec.md +0 -465
- package/specs/vector-outbox-v2/context.md +0 -470
- package/specs/vector-outbox-v2/plan.md +0 -562
- package/specs/vector-outbox-v2/spec.md +0 -466
- package/specs/web-viewer-ui/context.md +0 -384
- package/specs/web-viewer-ui/plan.md +0 -797
- package/specs/web-viewer-ui/spec.md +0 -516
- package/src/adapters/claude/capture/index.ts +0 -3
- package/src/adapters/claude/context/index.ts +0 -3
- package/src/adapters/claude/hooks/index.ts +0 -21
- package/src/adapters/claude/hooks/post-tool-use.ts +0 -239
- package/src/adapters/claude/hooks/prompt-injection-policy.ts +0 -104
- package/src/adapters/claude/hooks/semantic-daemon-client.ts +0 -209
- package/src/adapters/claude/hooks/semantic-daemon.ts +0 -283
- package/src/adapters/claude/hooks/session-end.ts +0 -59
- package/src/adapters/claude/hooks/session-start.ts +0 -73
- package/src/adapters/claude/hooks/stop.ts +0 -128
- package/src/adapters/claude/hooks/user-prompt-submit.ts +0 -361
- package/src/adapters/claude/index.ts +0 -4
- package/src/adapters/claude/transcript/index.ts +0 -4
- package/src/adapters/claude/transcript/transcript-reader.ts +0 -57
- package/src/adapters/claude/transcript/turn-reconstructor.ts +0 -65
- package/src/apps/cli/claude-settings-hooks.ts +0 -138
- package/src/apps/cli/codex-import-runner.ts +0 -125
- package/src/apps/cli/codex-validation-output.ts +0 -95
- package/src/apps/cli/hermes-import-runner.ts +0 -130
- package/src/apps/cli/hermes-validation-output.ts +0 -91
- package/src/apps/cli/index.ts +0 -1735
- package/src/apps/cli/mcp-install.ts +0 -106
- package/src/apps/cli/retrieval-disclosure-output.ts +0 -196
- package/src/apps/dashboard/assets/js/bootstrap.js +0 -244
- package/src/apps/dashboard/assets/js/chat.js +0 -373
- package/src/apps/dashboard/assets/js/disclosure.js +0 -232
- package/src/apps/dashboard/assets/js/modals.js +0 -298
- package/src/apps/dashboard/assets/js/overview.js +0 -655
- package/src/apps/dashboard/assets/js/state.js +0 -72
- package/src/apps/dashboard/assets/js/views.js +0 -468
- package/src/apps/dashboard/index.html +0 -543
- package/src/apps/dashboard/index.ts +0 -3
- package/src/apps/dashboard/style.css +0 -1750
- package/src/apps/index.ts +0 -5
- package/src/apps/server/api/chat.ts +0 -244
- package/src/apps/server/api/citations.ts +0 -105
- package/src/apps/server/api/events.ts +0 -137
- package/src/apps/server/api/health.ts +0 -53
- package/src/apps/server/api/index.ts +0 -26
- package/src/apps/server/api/projects.ts +0 -74
- package/src/apps/server/api/search.ts +0 -184
- package/src/apps/server/api/sessions.ts +0 -115
- package/src/apps/server/api/stats.ts +0 -723
- package/src/apps/server/api/turns.ts +0 -143
- package/src/apps/server/api/utils.ts +0 -65
- package/src/apps/server/index.ts +0 -111
- package/src/cli/index.ts +0 -3
- package/src/cli/retrieval-disclosure-output.ts +0 -2
- package/src/compat/index.ts +0 -5
- package/src/core/canonical-key.ts +0 -186
- package/src/core/citation-generator.ts +0 -63
- package/src/core/consolidated-store.ts +0 -356
- package/src/core/consolidation-worker.ts +0 -493
- package/src/core/context-formatter.ts +0 -276
- package/src/core/continuity-manager.ts +0 -341
- package/src/core/db-wrapper.ts +0 -64
- package/src/core/derive/fact-deriver.ts +0 -170
- package/src/core/derive/index.ts +0 -2
- package/src/core/derive/summary-deriver.ts +0 -76
- package/src/core/edge-repo.ts +0 -333
- package/src/core/embedder.ts +0 -4
- package/src/core/engine/embedding-maintenance-service.ts +0 -187
- package/src/core/engine/endless-memory-services.ts +0 -4
- package/src/core/engine/index.ts +0 -19
- package/src/core/engine/memory-engine-services.ts +0 -170
- package/src/core/engine/memory-ingest-service.ts +0 -317
- package/src/core/engine/memory-query-service.ts +0 -173
- package/src/core/engine/memory-runtime-service.ts +0 -162
- package/src/core/engine/memory-service-composition.ts +0 -231
- package/src/core/engine/retrieval-analytics-service.ts +0 -181
- package/src/core/engine/retrieval-disclosure-service.ts +0 -420
- package/src/core/engine/retrieval-orchestrator.ts +0 -377
- package/src/core/engine/retrieval-services.ts +0 -176
- package/src/core/engine/shared-memory-services.ts +0 -4
- package/src/core/entity-repo.ts +0 -349
- package/src/core/event-store.ts +0 -779
- package/src/core/evidence-aligner.ts +0 -635
- package/src/core/external-market-context.ts +0 -582
- package/src/core/graduation-worker.ts +0 -171
- package/src/core/graduation.ts +0 -377
- package/src/core/index.ts +0 -64
- package/src/core/ingest-interceptor.ts +0 -80
- package/src/core/markdown-mirror.ts +0 -70
- package/src/core/matcher.ts +0 -208
- package/src/core/md-mirror.ts +0 -92
- package/src/core/metadata-extractor.ts +0 -203
- package/src/core/model/memory-fact.ts +0 -30
- package/src/core/model/memory-rule.ts +0 -14
- package/src/core/model/memory-summary.ts +0 -21
- package/src/core/model/raw-event.ts +0 -28
- package/src/core/model/retrieval-result.ts +0 -35
- package/src/core/mongo-sync-config.ts +0 -165
- package/src/core/mongo-sync-worker.ts +0 -381
- package/src/core/privacy/filter.ts +0 -190
- package/src/core/privacy/index.ts +0 -20
- package/src/core/privacy/tag-parser.ts +0 -145
- package/src/core/product-validation-matrix.ts +0 -314
- package/src/core/progressive-retriever.ts +0 -414
- package/src/core/registry/project-path.ts +0 -54
- package/src/core/registry/session-registry.ts +0 -69
- package/src/core/replay-evaluator.ts +0 -625
- package/src/core/retrieval-benchmark.ts +0 -117
- package/src/core/retrieval-quality.ts +0 -109
- package/src/core/retriever.ts +0 -800
- package/src/core/session-qrels.ts +0 -360
- package/src/core/shared-event-store.ts +0 -114
- package/src/core/shared-promoter.ts +0 -249
- package/src/core/shared-store.ts +0 -289
- package/src/core/shared-vector-store.ts +0 -203
- package/src/core/sqlite-event-store.ts +0 -1846
- package/src/core/sqlite-wrapper.ts +0 -116
- package/src/core/sync-worker.ts +0 -228
- package/src/core/tag-taxonomy.ts +0 -51
- package/src/core/task/blocker-resolver.ts +0 -333
- package/src/core/task/index.ts +0 -9
- package/src/core/task/task-matcher.ts +0 -240
- package/src/core/task/task-projector.ts +0 -358
- package/src/core/task/task-resolver.ts +0 -421
- package/src/core/turn-state.ts +0 -207
- package/src/core/types.ts +0 -952
- package/src/core/vector-outbox.ts +0 -299
- package/src/core/vector-store.ts +0 -231
- package/src/core/vector-worker.ts +0 -521
- package/src/core/working-set-store.ts +0 -257
- package/src/extensions/endless-memory/endless-memory-services.ts +0 -350
- package/src/extensions/endless-memory/index.ts +0 -1
- package/src/extensions/index.ts +0 -5
- package/src/extensions/mcp/handlers.ts +0 -960
- package/src/extensions/mcp/index.ts +0 -48
- package/src/extensions/mcp/tools.ts +0 -252
- package/src/extensions/shared-memory/index.ts +0 -1
- package/src/extensions/shared-memory/shared-memory-services.ts +0 -211
- package/src/extensions/vector/embedder.ts +0 -234
- package/src/extensions/vector/index.ts +0 -1
- package/src/hooks/post-tool-use.ts +0 -9
- package/src/hooks/semantic-daemon-client.ts +0 -1
- package/src/hooks/semantic-daemon.ts +0 -11
- package/src/hooks/session-end.ts +0 -9
- package/src/hooks/session-start.ts +0 -9
- package/src/hooks/stop.ts +0 -9
- package/src/hooks/user-prompt-submit.ts +0 -9
- package/src/index.ts +0 -13
- package/src/mcp/handlers.ts +0 -2
- package/src/mcp/index.ts +0 -4
- package/src/mcp/tools.ts +0 -2
- package/src/server/api/chat.ts +0 -2
- package/src/server/api/citations.ts +0 -2
- package/src/server/api/events.ts +0 -2
- package/src/server/api/health.ts +0 -2
- package/src/server/api/index.ts +0 -2
- package/src/server/api/projects.ts +0 -2
- package/src/server/api/search.ts +0 -2
- package/src/server/api/sessions.ts +0 -2
- package/src/server/api/stats.ts +0 -2
- package/src/server/api/turns.ts +0 -2
- package/src/server/api/utils.ts +0 -2
- package/src/server/index.ts +0 -2
- package/src/services/bootstrap-organizer.ts +0 -463
- package/src/services/codex-session-history-importer.ts +0 -966
- package/src/services/hermes-session-history-importer.ts +0 -733
- package/src/services/memory-service-config.ts +0 -36
- package/src/services/memory-service-registry.ts +0 -150
- package/src/services/memory-service.ts +0 -688
- package/src/services/session-history-importer.ts +0 -629
- package/tests/README.md +0 -23
- package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +0 -54
- package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +0 -98
- package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +0 -99
- package/tests/apps/app-layer-boundary.test.ts +0 -48
- package/tests/apps/claude-settings-hooks.test.ts +0 -107
- package/tests/apps/cli-disclosure-output.test.ts +0 -212
- package/tests/apps/codex-import-runner.test.ts +0 -99
- package/tests/apps/codex-validation-output.test.ts +0 -100
- package/tests/apps/hermes-import-runner.test.ts +0 -99
- package/tests/apps/mcp-install-command.test.ts +0 -59
- package/tests/apps/package-build-entrypoints.test.ts +0 -30
- package/tests/apps/postinstall-embedding-backend.test.ts +0 -185
- package/tests/apps/search-api-disclosure.test.ts +0 -162
- package/tests/apps/stats-api-lightweight.test.ts +0 -67
- package/tests/apps/ui-disclosure-output.test.ts +0 -140
- package/tests/core/bootstrap-organizer.test.ts +0 -111
- package/tests/core/canonical-key.test.ts +0 -101
- package/tests/core/codex-session-history-importer-validation.test.ts +0 -185
- package/tests/core/consolidation-worker.test.ts +0 -75
- package/tests/core/embedding-maintenance-service.test.ts +0 -282
- package/tests/core/evidence-aligner.test.ts +0 -152
- package/tests/core/external-market-context.test.ts +0 -209
- package/tests/core/fact-deriver.test.ts +0 -79
- package/tests/core/hermes-session-history-importer-validation.test.ts +0 -609
- package/tests/core/ingest-interceptor.test.ts +0 -38
- package/tests/core/markdown-mirror.test.ts +0 -85
- package/tests/core/matcher.test.ts +0 -112
- package/tests/core/md-mirror.test.ts +0 -50
- package/tests/core/memory-engine-services.test.ts +0 -240
- package/tests/core/memory-ingest-service.test.ts +0 -296
- package/tests/core/memory-query-service.test.ts +0 -129
- package/tests/core/memory-runtime-service.test.ts +0 -201
- package/tests/core/memory-service-composition.test.ts +0 -192
- package/tests/core/memory-service-config.test.ts +0 -41
- package/tests/core/memory-service-facade.test.ts +0 -30
- package/tests/core/memory-service-registry.test.ts +0 -206
- package/tests/core/product-validation-matrix.test.ts +0 -61
- package/tests/core/project-registry.test.ts +0 -78
- package/tests/core/replay-evaluator.test.ts +0 -181
- package/tests/core/retrieval-analytics-service.test.ts +0 -210
- package/tests/core/retrieval-benchmark.test.ts +0 -93
- package/tests/core/retrieval-disclosure-service.test.ts +0 -264
- package/tests/core/retrieval-orchestrator.test.ts +0 -403
- package/tests/core/retrieval-quality.test.ts +0 -31
- package/tests/core/retrieval-services.test.ts +0 -185
- package/tests/core/retriever-fallback-chain.test.ts +0 -223
- package/tests/core/retriever-strategy-scope.test.ts +0 -164
- package/tests/core/retriever.memu-adoption.test.ts +0 -122
- package/tests/core/session-history-importer-filter.test.ts +0 -78
- package/tests/core/session-qrels.test.ts +0 -250
- package/tests/core/sqlite-event-store-replication.test.ts +0 -127
- package/tests/core/summary-deriver.test.ts +0 -66
- package/tests/extensions/embedder-warning-suppression.test.ts +0 -84
- package/tests/extensions/endless-memory-extension-boundary.test.ts +0 -17
- package/tests/extensions/endless-memory-services.test.ts +0 -325
- package/tests/extensions/mcp-context-tools.test.ts +0 -905
- package/tests/extensions/mcp-extension-boundary.test.ts +0 -21
- package/tests/extensions/mcp-package-build.test.ts +0 -22
- package/tests/extensions/mcp-project-aware-tools.test.ts +0 -102
- package/tests/extensions/shared-memory-extension-boundary.test.ts +0 -24
- package/tests/extensions/shared-memory-services.test.ts +0 -309
- package/tests/extensions/vector-extension-boundary.test.ts +0 -21
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -15
package/src/apps/index.ts
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chat API
|
|
3
|
-
* Endpoints for memory-aware chat using Claude CLI
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Hono } from 'hono';
|
|
7
|
-
import { streamSSE } from 'hono/streaming';
|
|
8
|
-
import { spawn } from 'child_process';
|
|
9
|
-
import type { ChildProcess } from 'child_process';
|
|
10
|
-
import { getServiceFromQuery } from './utils.js';
|
|
11
|
-
|
|
12
|
-
export const chatRouter = new Hono();
|
|
13
|
-
|
|
14
|
-
interface ChatRequest {
|
|
15
|
-
message: string;
|
|
16
|
-
history?: Array<{ role: 'user' | 'assistant'; content: string }>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const CLAUDE_TIMEOUT_MS = 120_000;
|
|
20
|
-
|
|
21
|
-
chatRouter.post('/', async (c) => {
|
|
22
|
-
let body: ChatRequest;
|
|
23
|
-
try {
|
|
24
|
-
body = await c.req.json<ChatRequest>();
|
|
25
|
-
} catch {
|
|
26
|
-
return c.json({ error: 'Invalid JSON body' }, 400);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!body.message?.trim()) {
|
|
30
|
-
return c.json({ error: 'Message is required' }, 400);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const memoryService = getServiceFromQuery(c);
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
await memoryService.initialize();
|
|
37
|
-
|
|
38
|
-
// Retrieve relevant memories for context
|
|
39
|
-
let memoryContext = '';
|
|
40
|
-
let statsContext = '';
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
const result = await memoryService.retrieveMemories(body.message, {
|
|
44
|
-
topK: 8,
|
|
45
|
-
minScore: 0.5
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
if (result.memories.length > 0) {
|
|
49
|
-
const parts: string[] = ['## Relevant Memories\n'];
|
|
50
|
-
for (const m of result.memories) {
|
|
51
|
-
const date = new Date(m.event.timestamp).toISOString().split('T')[0];
|
|
52
|
-
const content = m.event.content.slice(0, 500);
|
|
53
|
-
parts.push(`### [${m.event.eventType}] ${date} (score: ${m.score.toFixed(2)})`);
|
|
54
|
-
parts.push(content);
|
|
55
|
-
if (m.sessionContext) {
|
|
56
|
-
parts.push(`_Context: ${m.sessionContext}_`);
|
|
57
|
-
}
|
|
58
|
-
parts.push('');
|
|
59
|
-
}
|
|
60
|
-
memoryContext = parts.join('\n');
|
|
61
|
-
}
|
|
62
|
-
} catch {
|
|
63
|
-
// Continue without memory context if retrieval fails
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const stats = await memoryService.getStats();
|
|
68
|
-
const levels = stats.levelStats.map(l => `${l.level}: ${l.count}`).join(', ');
|
|
69
|
-
statsContext = [
|
|
70
|
-
'## Memory Stats',
|
|
71
|
-
`- Total events: ${stats.totalEvents}`,
|
|
72
|
-
`- Vector nodes: ${stats.vectorCount}`,
|
|
73
|
-
`- By level: ${levels}`
|
|
74
|
-
].join('\n');
|
|
75
|
-
} catch {
|
|
76
|
-
// Continue without stats if it fails
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const fullPrompt = buildPrompt(
|
|
80
|
-
statsContext,
|
|
81
|
-
memoryContext,
|
|
82
|
-
body.history || [],
|
|
83
|
-
body.message
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
// Stream response via SSE
|
|
87
|
-
return streamSSE(c, async (stream) => {
|
|
88
|
-
try {
|
|
89
|
-
await streamClaudeResponse(fullPrompt, stream);
|
|
90
|
-
} catch (err) {
|
|
91
|
-
await stream.writeSSE({
|
|
92
|
-
event: 'error',
|
|
93
|
-
data: JSON.stringify({ error: (err as Error).message })
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
} catch (error) {
|
|
98
|
-
return c.json({ error: (error as Error).message }, 500);
|
|
99
|
-
} finally {
|
|
100
|
-
await memoryService.shutdown();
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
function buildPrompt(
|
|
105
|
-
statsContext: string,
|
|
106
|
-
memoryContext: string,
|
|
107
|
-
history: Array<{ role: string; content: string }>,
|
|
108
|
-
currentMessage: string
|
|
109
|
-
): string {
|
|
110
|
-
const parts: string[] = [];
|
|
111
|
-
|
|
112
|
-
parts.push('You are a helpful assistant that answers questions about the user\'s code memory data.');
|
|
113
|
-
parts.push('The memory system tracks coding sessions, tool usage, prompts, and responses.');
|
|
114
|
-
parts.push('Answer concisely based on the memory context below. If you don\'t have enough data, say so.');
|
|
115
|
-
parts.push('Use markdown formatting in your responses.\n');
|
|
116
|
-
|
|
117
|
-
if (statsContext) {
|
|
118
|
-
parts.push(statsContext);
|
|
119
|
-
parts.push('');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (memoryContext) {
|
|
123
|
-
parts.push(memoryContext);
|
|
124
|
-
} else {
|
|
125
|
-
parts.push('No directly relevant memories found for this query.');
|
|
126
|
-
parts.push('Answer based on general knowledge or suggest the user rephrase.\n');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
parts.push('---\n');
|
|
130
|
-
|
|
131
|
-
// Include recent history (last 10 turns)
|
|
132
|
-
const recentHistory = history.slice(-10);
|
|
133
|
-
if (recentHistory.length > 0) {
|
|
134
|
-
parts.push('## Conversation History\n');
|
|
135
|
-
for (const msg of recentHistory) {
|
|
136
|
-
const prefix = msg.role === 'user' ? 'User' : 'Assistant';
|
|
137
|
-
parts.push(`**${prefix}:** ${msg.content}\n`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
parts.push(`**User:** ${currentMessage}`);
|
|
142
|
-
|
|
143
|
-
return parts.join('\n');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function streamClaudeResponse(
|
|
147
|
-
prompt: string,
|
|
148
|
-
stream: { writeSSE: (msg: { event?: string; data: string }) => Promise<void> }
|
|
149
|
-
): Promise<void> {
|
|
150
|
-
return new Promise((resolve, reject) => {
|
|
151
|
-
const proc: ChildProcess = spawn('claude', [
|
|
152
|
-
'-p',
|
|
153
|
-
'--output-format', 'stream-json',
|
|
154
|
-
'--verbose'
|
|
155
|
-
], {
|
|
156
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
157
|
-
env: { ...process.env }
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const timeout = setTimeout(() => {
|
|
161
|
-
proc.kill('SIGTERM');
|
|
162
|
-
reject(new Error('Chat response timed out after 2 minutes'));
|
|
163
|
-
}, CLAUDE_TIMEOUT_MS);
|
|
164
|
-
|
|
165
|
-
// Write prompt to stdin
|
|
166
|
-
proc.stdin!.write(prompt);
|
|
167
|
-
proc.stdin!.end();
|
|
168
|
-
|
|
169
|
-
let buffer = '';
|
|
170
|
-
let lastSentText = '';
|
|
171
|
-
|
|
172
|
-
proc.stdout!.on('data', async (chunk: Buffer) => {
|
|
173
|
-
buffer += chunk.toString();
|
|
174
|
-
const lines = buffer.split('\n');
|
|
175
|
-
buffer = lines.pop() || '';
|
|
176
|
-
|
|
177
|
-
for (const line of lines) {
|
|
178
|
-
if (!line.trim()) continue;
|
|
179
|
-
try {
|
|
180
|
-
const parsed = JSON.parse(line);
|
|
181
|
-
|
|
182
|
-
// Extract text from assistant messages
|
|
183
|
-
if (parsed.type === 'assistant' && parsed.message?.content) {
|
|
184
|
-
const textBlocks = parsed.message.content
|
|
185
|
-
.filter((b: { type: string }) => b.type === 'text')
|
|
186
|
-
.map((b: { text: string }) => b.text)
|
|
187
|
-
.join('');
|
|
188
|
-
|
|
189
|
-
if (textBlocks.length > lastSentText.length) {
|
|
190
|
-
const delta = textBlocks.slice(lastSentText.length);
|
|
191
|
-
lastSentText = textBlocks;
|
|
192
|
-
await stream.writeSSE({
|
|
193
|
-
event: 'message',
|
|
194
|
-
data: JSON.stringify({ content: delta })
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Handle completion
|
|
200
|
-
if (parsed.type === 'result') {
|
|
201
|
-
await stream.writeSSE({ event: 'done', data: '{}' });
|
|
202
|
-
}
|
|
203
|
-
} catch {
|
|
204
|
-
// Skip non-JSON lines
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
proc.stderr!.on('data', (chunk: Buffer) => {
|
|
210
|
-
if (process.env.CLAUDE_MEMORY_DEBUG) {
|
|
211
|
-
console.error('[chat] claude stderr:', chunk.toString());
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
proc.on('error', (err) => {
|
|
216
|
-
clearTimeout(timeout);
|
|
217
|
-
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
218
|
-
reject(new Error('Claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code'));
|
|
219
|
-
} else {
|
|
220
|
-
reject(err);
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
proc.on('close', async (code) => {
|
|
225
|
-
clearTimeout(timeout);
|
|
226
|
-
|
|
227
|
-
// Flush remaining buffer
|
|
228
|
-
if (buffer.trim()) {
|
|
229
|
-
try {
|
|
230
|
-
const parsed = JSON.parse(buffer);
|
|
231
|
-
if (parsed.type === 'result') {
|
|
232
|
-
await stream.writeSSE({ event: 'done', data: '{}' });
|
|
233
|
-
}
|
|
234
|
-
} catch { /* ignore */ }
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (code !== 0 && code !== null) {
|
|
238
|
-
reject(new Error(`Claude CLI exited with code ${code}`));
|
|
239
|
-
} else {
|
|
240
|
-
resolve();
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Citations API
|
|
3
|
-
* Endpoints for citation management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Hono } from 'hono';
|
|
7
|
-
import { getServiceFromQuery } from './utils.js';
|
|
8
|
-
import { generateCitationId, parseCitationId } from '../../../core/citation-generator.js';
|
|
9
|
-
|
|
10
|
-
export const citationsRouter = new Hono();
|
|
11
|
-
|
|
12
|
-
// GET /api/citations/:id - Get citation by ID
|
|
13
|
-
citationsRouter.get('/:id', async (c) => {
|
|
14
|
-
const { id } = c.req.param();
|
|
15
|
-
|
|
16
|
-
// Support both formats: "a7Bc3x" or "mem:a7Bc3x"
|
|
17
|
-
const citationId = parseCitationId(id) || id;
|
|
18
|
-
const memoryService = getServiceFromQuery(c);
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
await memoryService.initialize();
|
|
22
|
-
|
|
23
|
-
// Search through recent events to find the one matching this citation ID
|
|
24
|
-
const recentEvents = await memoryService.getRecentEvents(10000);
|
|
25
|
-
|
|
26
|
-
const event = recentEvents.find(e => {
|
|
27
|
-
const eventCitationId = generateCitationId(e.id);
|
|
28
|
-
return eventCitationId === citationId;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
if (!event) {
|
|
32
|
-
return c.json({ error: 'Citation not found' }, 404);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return c.json({
|
|
36
|
-
citation: {
|
|
37
|
-
id: citationId,
|
|
38
|
-
eventId: event.id
|
|
39
|
-
},
|
|
40
|
-
event: {
|
|
41
|
-
id: event.id,
|
|
42
|
-
eventType: event.eventType,
|
|
43
|
-
timestamp: event.timestamp,
|
|
44
|
-
sessionId: event.sessionId,
|
|
45
|
-
content: event.content,
|
|
46
|
-
metadata: event.metadata
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
return c.json({ error: (error as Error).message }, 500);
|
|
51
|
-
} finally {
|
|
52
|
-
await memoryService.shutdown();
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// GET /api/citations/:id/related - Get related citations
|
|
57
|
-
citationsRouter.get('/:id/related', async (c) => {
|
|
58
|
-
const { id } = c.req.param();
|
|
59
|
-
const citationId = parseCitationId(id) || id;
|
|
60
|
-
const memoryService = getServiceFromQuery(c);
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
await memoryService.initialize();
|
|
64
|
-
|
|
65
|
-
const recentEvents = await memoryService.getRecentEvents(10000);
|
|
66
|
-
|
|
67
|
-
// Find the main event
|
|
68
|
-
const event = recentEvents.find(e => {
|
|
69
|
-
const eventCitationId = generateCitationId(e.id);
|
|
70
|
-
return eventCitationId === citationId;
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
if (!event) {
|
|
74
|
-
return c.json({ error: 'Citation not found' }, 404);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Get surrounding events from same session
|
|
78
|
-
const sessionEvents = recentEvents
|
|
79
|
-
.filter(e => e.sessionId === event.sessionId)
|
|
80
|
-
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
81
|
-
|
|
82
|
-
const eventIndex = sessionEvents.findIndex(e => e.id === event.id);
|
|
83
|
-
const prev = eventIndex > 0 ? sessionEvents[eventIndex - 1] : null;
|
|
84
|
-
const next = eventIndex < sessionEvents.length - 1 ? sessionEvents[eventIndex + 1] : null;
|
|
85
|
-
|
|
86
|
-
return c.json({
|
|
87
|
-
previous: prev ? {
|
|
88
|
-
citationId: generateCitationId(prev.id),
|
|
89
|
-
eventType: prev.eventType,
|
|
90
|
-
timestamp: prev.timestamp,
|
|
91
|
-
preview: prev.content.slice(0, 100) + (prev.content.length > 100 ? '...' : '')
|
|
92
|
-
} : null,
|
|
93
|
-
next: next ? {
|
|
94
|
-
citationId: generateCitationId(next.id),
|
|
95
|
-
eventType: next.eventType,
|
|
96
|
-
timestamp: next.timestamp,
|
|
97
|
-
preview: next.content.slice(0, 100) + (next.content.length > 100 ? '...' : '')
|
|
98
|
-
} : null
|
|
99
|
-
});
|
|
100
|
-
} catch (error) {
|
|
101
|
-
return c.json({ error: (error as Error).message }, 500);
|
|
102
|
-
} finally {
|
|
103
|
-
await memoryService.shutdown();
|
|
104
|
-
}
|
|
105
|
-
});
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Events API
|
|
3
|
-
* Endpoints for event management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Hono } from 'hono';
|
|
7
|
-
import { getServiceFromQuery } from './utils.js';
|
|
8
|
-
|
|
9
|
-
export const eventsRouter = new Hono();
|
|
10
|
-
|
|
11
|
-
// GET /api/events - List events with filters
|
|
12
|
-
eventsRouter.get('/', async (c) => {
|
|
13
|
-
const sessionId = c.req.query('sessionId');
|
|
14
|
-
const eventType = c.req.query('type');
|
|
15
|
-
const level = c.req.query('level');
|
|
16
|
-
const sort = c.req.query('sort') || 'recent'; // recent | accessed | oldest
|
|
17
|
-
const q = (c.req.query('q') || '').trim().toLowerCase();
|
|
18
|
-
const limit = parseInt(c.req.query('limit') || '100', 10);
|
|
19
|
-
const offset = parseInt(c.req.query('offset') || '0', 10);
|
|
20
|
-
const memoryService = getServiceFromQuery(c);
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
await memoryService.initialize();
|
|
24
|
-
|
|
25
|
-
let events: any[];
|
|
26
|
-
|
|
27
|
-
// Filter by level using the dedicated method
|
|
28
|
-
if (level) {
|
|
29
|
-
events = await memoryService.getEventsByLevel(level, { limit: limit + offset + 1000, offset: 0 });
|
|
30
|
-
} else {
|
|
31
|
-
events = await memoryService.getRecentEvents(limit + offset + 1000);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Filter by session
|
|
35
|
-
if (sessionId) {
|
|
36
|
-
events = events.filter(e => e.sessionId === sessionId);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Filter by type
|
|
40
|
-
if (eventType) {
|
|
41
|
-
events = events.filter(e => e.eventType === eventType);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Content query filter
|
|
45
|
-
if (q) {
|
|
46
|
-
events = events.filter(e => (e.content || '').toLowerCase().includes(q));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Sort
|
|
50
|
-
if (sort === 'accessed') {
|
|
51
|
-
events.sort((a: any, b: any) => {
|
|
52
|
-
const aTime = a.last_accessed_at || '';
|
|
53
|
-
const bTime = b.last_accessed_at || '';
|
|
54
|
-
return bTime.localeCompare(aTime);
|
|
55
|
-
});
|
|
56
|
-
} else if (sort === 'most-accessed') {
|
|
57
|
-
events.sort((a: any, b: any) => (b.access_count || 0) - (a.access_count || 0));
|
|
58
|
-
} else if (sort === 'oldest') {
|
|
59
|
-
events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
60
|
-
}
|
|
61
|
-
// 'recent' is default (already sorted by timestamp DESC from the query)
|
|
62
|
-
|
|
63
|
-
// Pagination
|
|
64
|
-
const total = events.length;
|
|
65
|
-
events = events.slice(offset, offset + limit);
|
|
66
|
-
|
|
67
|
-
return c.json({
|
|
68
|
-
events: events.map(e => ({
|
|
69
|
-
id: e.id,
|
|
70
|
-
eventType: e.eventType,
|
|
71
|
-
timestamp: e.timestamp,
|
|
72
|
-
sessionId: e.sessionId,
|
|
73
|
-
preview: e.content.slice(0, 200) + (e.content.length > 200 ? '...' : ''),
|
|
74
|
-
contentLength: e.content.length,
|
|
75
|
-
metadata: e.metadata,
|
|
76
|
-
accessCount: (e as any).access_count || 0,
|
|
77
|
-
lastAccessedAt: (e as any).last_accessed_at || null
|
|
78
|
-
})),
|
|
79
|
-
total,
|
|
80
|
-
limit,
|
|
81
|
-
offset,
|
|
82
|
-
hasMore: offset + limit < total
|
|
83
|
-
});
|
|
84
|
-
} catch (error) {
|
|
85
|
-
return c.json({ error: (error as Error).message }, 500);
|
|
86
|
-
} finally {
|
|
87
|
-
await memoryService.shutdown();
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// GET /api/events/:id - Get event details
|
|
92
|
-
eventsRouter.get('/:id', async (c) => {
|
|
93
|
-
const { id } = c.req.param();
|
|
94
|
-
const memoryService = getServiceFromQuery(c);
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
await memoryService.initialize();
|
|
98
|
-
|
|
99
|
-
const recentEvents = await memoryService.getRecentEvents(10000);
|
|
100
|
-
const event = recentEvents.find(e => e.id === id);
|
|
101
|
-
|
|
102
|
-
if (!event) {
|
|
103
|
-
return c.json({ error: 'Event not found' }, 404);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Get surrounding events for context
|
|
107
|
-
const sessionEvents = recentEvents
|
|
108
|
-
.filter(e => e.sessionId === event.sessionId)
|
|
109
|
-
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
110
|
-
|
|
111
|
-
const eventIndex = sessionEvents.findIndex(e => e.id === id);
|
|
112
|
-
const start = Math.max(0, eventIndex - 2);
|
|
113
|
-
const end = Math.min(sessionEvents.length, eventIndex + 3);
|
|
114
|
-
const context = sessionEvents.slice(start, end).filter(e => e.id !== id);
|
|
115
|
-
|
|
116
|
-
return c.json({
|
|
117
|
-
event: {
|
|
118
|
-
id: event.id,
|
|
119
|
-
eventType: event.eventType,
|
|
120
|
-
timestamp: event.timestamp,
|
|
121
|
-
sessionId: event.sessionId,
|
|
122
|
-
content: event.content,
|
|
123
|
-
metadata: event.metadata
|
|
124
|
-
},
|
|
125
|
-
context: context.map(e => ({
|
|
126
|
-
id: e.id,
|
|
127
|
-
eventType: e.eventType,
|
|
128
|
-
timestamp: e.timestamp,
|
|
129
|
-
preview: e.content.slice(0, 100) + (e.content.length > 100 ? '...' : '')
|
|
130
|
-
}))
|
|
131
|
-
});
|
|
132
|
-
} catch (error) {
|
|
133
|
-
return c.json({ error: (error as Error).message }, 500);
|
|
134
|
-
} finally {
|
|
135
|
-
await memoryService.shutdown();
|
|
136
|
-
}
|
|
137
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Health API
|
|
3
|
-
* Operational health checks including outbox backlog/failures
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Hono } from 'hono';
|
|
7
|
-
import { getServiceFromQuery } from './utils.js';
|
|
8
|
-
|
|
9
|
-
export const healthRouter = new Hono();
|
|
10
|
-
|
|
11
|
-
// GET /api/health
|
|
12
|
-
healthRouter.get('/', async (c) => {
|
|
13
|
-
const memoryService = getServiceFromQuery(c);
|
|
14
|
-
try {
|
|
15
|
-
await memoryService.initialize();
|
|
16
|
-
|
|
17
|
-
const [stats, outbox] = await Promise.all([
|
|
18
|
-
memoryService.getStats(),
|
|
19
|
-
memoryService.getOutboxStats()
|
|
20
|
-
]);
|
|
21
|
-
|
|
22
|
-
const outboxPending = outbox.embedding.pending + outbox.vector.pending;
|
|
23
|
-
const outboxFailed = outbox.embedding.failed + outbox.vector.failed;
|
|
24
|
-
|
|
25
|
-
const status = outboxFailed > 0 ? 'needs-attention' : 'ok';
|
|
26
|
-
|
|
27
|
-
return c.json({
|
|
28
|
-
status,
|
|
29
|
-
timestamp: new Date().toISOString(),
|
|
30
|
-
storage: {
|
|
31
|
-
totalEvents: stats.totalEvents,
|
|
32
|
-
vectorCount: stats.vectorCount
|
|
33
|
-
},
|
|
34
|
-
outbox: {
|
|
35
|
-
embedding: outbox.embedding,
|
|
36
|
-
vector: outbox.vector,
|
|
37
|
-
totals: {
|
|
38
|
-
pending: outboxPending,
|
|
39
|
-
failed: outboxFailed
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
levelStats: stats.levelStats
|
|
43
|
-
});
|
|
44
|
-
} catch (error) {
|
|
45
|
-
return c.json({
|
|
46
|
-
status: 'error',
|
|
47
|
-
timestamp: new Date().toISOString(),
|
|
48
|
-
error: (error as Error).message
|
|
49
|
-
}, 500);
|
|
50
|
-
} finally {
|
|
51
|
-
await memoryService.shutdown();
|
|
52
|
-
}
|
|
53
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Router
|
|
3
|
-
* Central router for all API endpoints
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Hono } from 'hono';
|
|
7
|
-
import { sessionsRouter } from './sessions.js';
|
|
8
|
-
import { eventsRouter } from './events.js';
|
|
9
|
-
import { searchRouter } from './search.js';
|
|
10
|
-
import { statsRouter } from './stats.js';
|
|
11
|
-
import { citationsRouter } from './citations.js';
|
|
12
|
-
import { turnsRouter } from './turns.js';
|
|
13
|
-
import { projectsRouter } from './projects.js';
|
|
14
|
-
import { chatRouter } from './chat.js';
|
|
15
|
-
import { healthRouter } from './health.js';
|
|
16
|
-
|
|
17
|
-
export const apiRouter = new Hono()
|
|
18
|
-
.route('/sessions', sessionsRouter)
|
|
19
|
-
.route('/events', eventsRouter)
|
|
20
|
-
.route('/search', searchRouter)
|
|
21
|
-
.route('/stats', statsRouter)
|
|
22
|
-
.route('/citations', citationsRouter)
|
|
23
|
-
.route('/turns', turnsRouter)
|
|
24
|
-
.route('/projects', projectsRouter)
|
|
25
|
-
.route('/chat', chatRouter)
|
|
26
|
-
.route('/health', healthRouter);
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Projects API
|
|
3
|
-
* Endpoints for listing available projects
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Hono } from 'hono';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
import * as os from 'os';
|
|
10
|
-
import { loadSessionRegistry } from '../../../services/memory-service.js';
|
|
11
|
-
|
|
12
|
-
export const projectsRouter = new Hono();
|
|
13
|
-
|
|
14
|
-
// GET /api/projects - List available projects
|
|
15
|
-
projectsRouter.get('/', async (c) => {
|
|
16
|
-
try {
|
|
17
|
-
const projectsDir = path.join(os.homedir(), '.claude-code', 'memory', 'projects');
|
|
18
|
-
|
|
19
|
-
if (!fs.existsSync(projectsDir)) {
|
|
20
|
-
return c.json({ projects: [] });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Read project directories
|
|
24
|
-
const projectHashes = fs.readdirSync(projectsDir)
|
|
25
|
-
.filter(name => {
|
|
26
|
-
const fullPath = path.join(projectsDir, name);
|
|
27
|
-
return fs.statSync(fullPath).isDirectory();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Load session registry to map hashes to project paths
|
|
31
|
-
const registry = loadSessionRegistry();
|
|
32
|
-
const hashToPath = new Map<string, string>();
|
|
33
|
-
for (const entry of Object.values(registry.sessions)) {
|
|
34
|
-
if (!hashToPath.has(entry.projectHash)) {
|
|
35
|
-
hashToPath.set(entry.projectHash, entry.projectPath);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Build project list
|
|
40
|
-
const projects = projectHashes.map(hash => {
|
|
41
|
-
const dirPath = path.join(projectsDir, hash);
|
|
42
|
-
const dbPath = path.join(dirPath, 'events.sqlite');
|
|
43
|
-
let dbSize = 0;
|
|
44
|
-
if (fs.existsSync(dbPath)) {
|
|
45
|
-
dbSize = fs.statSync(dbPath).size;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const projectPath = hashToPath.get(hash) || `unknown (${hash})`;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
hash,
|
|
52
|
-
projectPath,
|
|
53
|
-
projectName: path.basename(projectPath),
|
|
54
|
-
dbSize,
|
|
55
|
-
dbSizeHuman: formatBytes(dbSize)
|
|
56
|
-
};
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Sort by project name
|
|
60
|
-
projects.sort((a, b) => a.projectName.localeCompare(b.projectName));
|
|
61
|
-
|
|
62
|
-
return c.json({ projects });
|
|
63
|
-
} catch (error) {
|
|
64
|
-
return c.json({ projects: [], error: (error as Error).message }, 500);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
function formatBytes(bytes: number): string {
|
|
69
|
-
if (bytes === 0) return '0 B';
|
|
70
|
-
const k = 1024;
|
|
71
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
72
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
73
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
74
|
-
}
|