@bip-krishna/agentmind 0.1.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.
- package/.agentmind/memory.db +0 -0
- package/.context-memory/memory.db +0 -0
- package/README.md +176 -0
- package/apps/dashboard/src/server.ts +369 -0
- package/cli/src/index.ts +163 -0
- package/dist/apps/dashboard/src/server.d.ts +6 -0
- package/dist/apps/dashboard/src/server.d.ts.map +1 -0
- package/dist/apps/dashboard/src/server.js +360 -0
- package/dist/apps/dashboard/src/server.js.map +1 -0
- package/dist/cli/src/index.d.ts +3 -0
- package/dist/cli/src/index.d.ts.map +1 -0
- package/dist/cli/src/index.js +162 -0
- package/dist/cli/src/index.js.map +1 -0
- package/dist/examples/advanced.d.ts +2 -0
- package/dist/examples/advanced.d.ts.map +1 -0
- package/dist/examples/advanced.js +41 -0
- package/dist/examples/advanced.js.map +1 -0
- package/dist/examples/basic.d.ts +2 -0
- package/dist/examples/basic.d.ts.map +1 -0
- package/dist/examples/basic.js +22 -0
- package/dist/examples/basic.js.map +1 -0
- package/dist/packages/core/src/consolidation.d.ts +27 -0
- package/dist/packages/core/src/consolidation.d.ts.map +1 -0
- package/dist/packages/core/src/consolidation.js +97 -0
- package/dist/packages/core/src/consolidation.js.map +1 -0
- package/dist/packages/core/src/events.d.ts +18 -0
- package/dist/packages/core/src/events.d.ts.map +1 -0
- package/dist/packages/core/src/events.js +13 -0
- package/dist/packages/core/src/events.js.map +1 -0
- package/dist/packages/core/src/extraction.d.ts +32 -0
- package/dist/packages/core/src/extraction.d.ts.map +1 -0
- package/dist/packages/core/src/extraction.js +65 -0
- package/dist/packages/core/src/extraction.js.map +1 -0
- package/dist/packages/core/src/index.d.ts +10 -0
- package/dist/packages/core/src/index.d.ts.map +1 -0
- package/dist/packages/core/src/index.js +7 -0
- package/dist/packages/core/src/index.js.map +1 -0
- package/dist/packages/core/src/memory-store.d.ts +36 -0
- package/dist/packages/core/src/memory-store.d.ts.map +1 -0
- package/dist/packages/core/src/memory-store.js +2 -0
- package/dist/packages/core/src/memory-store.js.map +1 -0
- package/dist/packages/core/src/memory.d.ts +75 -0
- package/dist/packages/core/src/memory.d.ts.map +1 -0
- package/dist/packages/core/src/memory.js +402 -0
- package/dist/packages/core/src/memory.js.map +1 -0
- package/dist/packages/core/src/policy.d.ts +17 -0
- package/dist/packages/core/src/policy.d.ts.map +1 -0
- package/dist/packages/core/src/policy.js +22 -0
- package/dist/packages/core/src/policy.js.map +1 -0
- package/dist/packages/core/src/types.d.ts +102 -0
- package/dist/packages/core/src/types.d.ts.map +1 -0
- package/dist/packages/core/src/types.js +23 -0
- package/dist/packages/core/src/types.js.map +1 -0
- package/dist/packages/embeddings/src/index.d.ts +4 -0
- package/dist/packages/embeddings/src/index.d.ts.map +1 -0
- package/dist/packages/embeddings/src/index.js +4 -0
- package/dist/packages/embeddings/src/index.js.map +1 -0
- package/dist/packages/embeddings/src/local.d.ts +15 -0
- package/dist/packages/embeddings/src/local.d.ts.map +1 -0
- package/dist/packages/embeddings/src/local.js +45 -0
- package/dist/packages/embeddings/src/local.js.map +1 -0
- package/dist/packages/embeddings/src/openai.d.ts +17 -0
- package/dist/packages/embeddings/src/openai.d.ts.map +1 -0
- package/dist/packages/embeddings/src/openai.js +41 -0
- package/dist/packages/embeddings/src/openai.js.map +1 -0
- package/dist/packages/embeddings/src/provider.d.ts +8 -0
- package/dist/packages/embeddings/src/provider.d.ts.map +1 -0
- package/dist/packages/embeddings/src/provider.js +12 -0
- package/dist/packages/embeddings/src/provider.js.map +1 -0
- package/dist/packages/integrations/src/anthropic.d.ts +15 -0
- package/dist/packages/integrations/src/anthropic.d.ts.map +1 -0
- package/dist/packages/integrations/src/anthropic.js +25 -0
- package/dist/packages/integrations/src/anthropic.js.map +1 -0
- package/dist/packages/integrations/src/index.d.ts +4 -0
- package/dist/packages/integrations/src/index.d.ts.map +1 -0
- package/dist/packages/integrations/src/index.js +4 -0
- package/dist/packages/integrations/src/index.js.map +1 -0
- package/dist/packages/integrations/src/openai.d.ts +27 -0
- package/dist/packages/integrations/src/openai.d.ts.map +1 -0
- package/dist/packages/integrations/src/openai.js +26 -0
- package/dist/packages/integrations/src/openai.js.map +1 -0
- package/dist/packages/integrations/src/vercel.d.ts +24 -0
- package/dist/packages/integrations/src/vercel.d.ts.map +1 -0
- package/dist/packages/integrations/src/vercel.js +20 -0
- package/dist/packages/integrations/src/vercel.js.map +1 -0
- package/dist/packages/retrieval/src/index.d.ts +3 -0
- package/dist/packages/retrieval/src/index.d.ts.map +1 -0
- package/dist/packages/retrieval/src/index.js +3 -0
- package/dist/packages/retrieval/src/index.js.map +1 -0
- package/dist/packages/retrieval/src/ranker.d.ts +21 -0
- package/dist/packages/retrieval/src/ranker.d.ts.map +1 -0
- package/dist/packages/retrieval/src/ranker.js +54 -0
- package/dist/packages/retrieval/src/ranker.js.map +1 -0
- package/dist/packages/retrieval/src/token.d.ts +3 -0
- package/dist/packages/retrieval/src/token.d.ts.map +1 -0
- package/dist/packages/retrieval/src/token.js +7 -0
- package/dist/packages/retrieval/src/token.js.map +1 -0
- package/dist/packages/storage/src/config.d.ts +14 -0
- package/dist/packages/storage/src/config.d.ts.map +1 -0
- package/dist/packages/storage/src/config.js +41 -0
- package/dist/packages/storage/src/config.js.map +1 -0
- package/dist/packages/storage/src/index.d.ts +4 -0
- package/dist/packages/storage/src/index.d.ts.map +1 -0
- package/dist/packages/storage/src/index.js +4 -0
- package/dist/packages/storage/src/index.js.map +1 -0
- package/dist/packages/storage/src/memory-store.d.ts +37 -0
- package/dist/packages/storage/src/memory-store.d.ts.map +1 -0
- package/dist/packages/storage/src/memory-store.js +107 -0
- package/dist/packages/storage/src/memory-store.js.map +1 -0
- package/dist/packages/storage/src/migrations.d.ts +12 -0
- package/dist/packages/storage/src/migrations.d.ts.map +1 -0
- package/dist/packages/storage/src/migrations.js +89 -0
- package/dist/packages/storage/src/migrations.js.map +1 -0
- package/dist/packages/storage/src/postgres-store.d.ts +45 -0
- package/dist/packages/storage/src/postgres-store.d.ts.map +1 -0
- package/dist/packages/storage/src/postgres-store.js +282 -0
- package/dist/packages/storage/src/postgres-store.js.map +1 -0
- package/dist/packages/storage/src/sqlite-store.d.ts +36 -0
- package/dist/packages/storage/src/sqlite-store.d.ts.map +1 -0
- package/dist/packages/storage/src/sqlite-store.js +243 -0
- package/dist/packages/storage/src/sqlite-store.js.map +1 -0
- package/examples/advanced.ts +46 -0
- package/examples/basic.ts +25 -0
- package/package.json +52 -0
- package/packages/core/package.json +13 -0
- package/packages/core/src/consolidation.ts +104 -0
- package/packages/core/src/events.ts +25 -0
- package/packages/core/src/extraction.ts +81 -0
- package/packages/core/src/index.ts +22 -0
- package/packages/core/src/memory-store.ts +50 -0
- package/packages/core/src/memory.ts +447 -0
- package/packages/core/src/policy.ts +36 -0
- package/packages/core/src/types.ts +119 -0
- package/packages/core/tsconfig.json +6 -0
- package/packages/embeddings/package.json +9 -0
- package/packages/embeddings/src/index.ts +3 -0
- package/packages/embeddings/src/local.ts +47 -0
- package/packages/embeddings/src/openai.ts +50 -0
- package/packages/embeddings/src/provider.d.ts.map +1 -0
- package/packages/embeddings/src/provider.js.map +1 -0
- package/packages/embeddings/src/provider.ts +17 -0
- package/packages/embeddings/tsconfig.json +6 -0
- package/packages/integrations/package.json +7 -0
- package/packages/integrations/src/anthropic.ts +34 -0
- package/packages/integrations/src/index.ts +3 -0
- package/packages/integrations/src/openai.ts +52 -0
- package/packages/integrations/src/vercel.ts +26 -0
- package/packages/retrieval/package.json +8 -0
- package/packages/retrieval/src/index.ts +2 -0
- package/packages/retrieval/src/ranker.d.ts.map +1 -0
- package/packages/retrieval/src/ranker.js.map +1 -0
- package/packages/retrieval/src/ranker.ts +77 -0
- package/packages/retrieval/src/token.d.ts.map +1 -0
- package/packages/retrieval/src/token.js.map +1 -0
- package/packages/retrieval/src/token.ts +5 -0
- package/packages/retrieval/tsconfig.json +6 -0
- package/packages/storage/package.json +11 -0
- package/packages/storage/src/config.ts +44 -0
- package/packages/storage/src/index.ts +3 -0
- package/packages/storage/src/memory-store.ts +100 -0
- package/packages/storage/src/migrations.ts +96 -0
- package/packages/storage/src/postgres-store.ts +299 -0
- package/packages/storage/src/sqlite-store.ts +298 -0
- package/packages/storage/tsconfig.json +6 -0
- package/test-mvp.mjs +82 -0
- package/tests/consolidation.test.ts +45 -0
- package/tests/eval.test.ts +102 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +8 -0
|
Binary file
|
|
Binary file
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# agentmind
|
|
2
|
+
|
|
3
|
+
**Persistent, project-scoped context layer for AI applications.**
|
|
4
|
+
|
|
5
|
+
> A persistent, project-scoped context layer — not a vector DB wrapper.
|
|
6
|
+
> Formerly `context-memory` — now `agentmind`. The `context-memory` CLI/bin remains as an alias.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
memory.remember()
|
|
10
|
+
memory.search()
|
|
11
|
+
memory.context()
|
|
12
|
+
memory.forget()
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install agentmind
|
|
19
|
+
# legacy alias still works:
|
|
20
|
+
npm install context-memory
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Memory } from "agentmind";
|
|
27
|
+
import { SqliteMemoryStore } from "agentmind/storage";
|
|
28
|
+
import { LocalEmbeddingProvider } from "agentmind/embeddings";
|
|
29
|
+
|
|
30
|
+
const store = new SqliteMemoryStore({ path: "./.agentmind/memory.db" });
|
|
31
|
+
const memory = await Memory.create({
|
|
32
|
+
project: "research-mesh",
|
|
33
|
+
store,
|
|
34
|
+
embeddingProvider: new LocalEmbeddingProvider(), // or OpenAIEmbeddingProvider
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await memory.remember({
|
|
38
|
+
type: "decision",
|
|
39
|
+
content: "We decided to use PostgreSQL with pgvector.",
|
|
40
|
+
importance: 0.9,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const ctx = await memory.context({ query: "What database did we decide to use?" });
|
|
44
|
+
console.log(ctx.text);
|
|
45
|
+
// Relevant project memory (research-mesh):
|
|
46
|
+
// - We decided to use PostgreSQL with pgvector.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Architecture
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
agentmind
|
|
53
|
+
│
|
|
54
|
+
┌──────────────┼──────────────┐
|
|
55
|
+
▼ ▼ ▼
|
|
56
|
+
Memory Engine Embedding Engine Dashboard
|
|
57
|
+
│ │ │
|
|
58
|
+
▼ ▼ ▼
|
|
59
|
+
Retriever OpenAI/etc. HTTP API (4317)
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
Store Adapter
|
|
63
|
+
│
|
|
64
|
+
┌───┴─────────────────┐
|
|
65
|
+
▼ ▼
|
|
66
|
+
SQLite (node:sqlite) Postgres + pgvector
|
|
67
|
+
InMemory (tests) (pg Pool, hnsw)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Store is the abstraction** — `MemoryStore` interface: `SqliteMemoryStore` and `PostgresMemoryStore` interchangeable.
|
|
71
|
+
|
|
72
|
+
## Features
|
|
73
|
+
|
|
74
|
+
### Core (Phases 1-4)
|
|
75
|
+
- **Project isolation** — `WHERE project_id=?` enforced before vector search (0 leakage, tested 50 queries)
|
|
76
|
+
- **Manual memory creation** — `type`, `importance`, `confidence`, `metadata`, `sourceId`
|
|
77
|
+
- **Embedding abstraction** — `EmbeddingProvider` (`OpenAIEmbeddingProvider`, `LocalEmbeddingProvider` deterministic hash 256d)
|
|
78
|
+
- **Decoupled embeddings** — `embeddings` table separate, re-embeddable per model
|
|
79
|
+
- **Hybrid retrieval** — `semantic×0.6 + keyword×0.2 + importance×0.1 + recency×0.1` (configurable `HybridRanker`)
|
|
80
|
+
- **Token budgeting** — `maxTokens` enforced greedily
|
|
81
|
+
- **Deduplication** — Jaccard >0.85 updates existing
|
|
82
|
+
- **Conflict resolution** — triggers `switched to/migrated to/now using` + embedding similarity >0.82 supersedes old
|
|
83
|
+
|
|
84
|
+
### Intelligence (Phase 5)
|
|
85
|
+
- **Extraction** — `HeuristicExtractor` + `LLMExtractor` with `EXTRACTION_PROMPT`
|
|
86
|
+
- **Consolidation** — `Consolidator` clusters via cosine >0.82, creates `summary` memories, archives sources
|
|
87
|
+
- **Lifecycle** — `extracted -> candidate -> validated -> stored -> retrieved -> updated -> archived`
|
|
88
|
+
|
|
89
|
+
### Context (Phase 6)
|
|
90
|
+
- `memory.context({ query, maxTokens, limit, types }) -> { text, tokenCount, memories, sources, retrieval }`
|
|
91
|
+
- Retrieval logging to `retrieval_events`
|
|
92
|
+
|
|
93
|
+
### Production (Phase 9)
|
|
94
|
+
- **SQLite** via `node:sqlite` `DatabaseSync` (Node 22+, zero native deps) + `InMemoryStore`
|
|
95
|
+
- **Postgres + pgvector** — `PostgresMemoryStore` (`pg` Pool, `CREATE EXTENSION vector`, `vec vector(1536)`, `hnsw` index, `vec <=> $1` native search)
|
|
96
|
+
- **Migrations** — `packages/storage/src/migrations.ts` versioned, `schema_migrations`
|
|
97
|
+
- **Config** — `resolveStorageConfig()` auto-detects `DATABASE_URL` else `AGENTMIND_DB`/`CTX_MEMORY_DB` else `.agentmind/memory.db`
|
|
98
|
+
- **Security** — DB-level `project_id` filter, no cross-project leakage
|
|
99
|
+
- **Privacy** — `forget(id)` deletes embedding, `forgetProject()`, `export()`, `storeSensitiveData=false`
|
|
100
|
+
|
|
101
|
+
### Observability & Policy
|
|
102
|
+
```ts
|
|
103
|
+
memory.on("memory.created", m => ...)
|
|
104
|
+
memory.on("retrieval.completed", e => ...)
|
|
105
|
+
|
|
106
|
+
const memory = await Memory.create({
|
|
107
|
+
project: "x",
|
|
108
|
+
policy: {
|
|
109
|
+
autoExtract: true,
|
|
110
|
+
minimumImportance: 0.65,
|
|
111
|
+
maxMemoriesPerProject: 10000,
|
|
112
|
+
storeSensitiveData: false,
|
|
113
|
+
shouldRemember: (m) => !m.content.includes("greeting"),
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Integrations (Phase 10)
|
|
119
|
+
```ts
|
|
120
|
+
import { withMemory, createOpenAICaller } from "agentmind/integrations";
|
|
121
|
+
import OpenAI from "openai";
|
|
122
|
+
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
123
|
+
|
|
124
|
+
const { response } = await withMemory({
|
|
125
|
+
memory,
|
|
126
|
+
messages: [{ role: "user", content: "What's our DB?" }],
|
|
127
|
+
callLLM: createOpenAICaller(openai, "gpt-4o"),
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
Adapters: `withMemory` (OpenAI), `withAnthropicMemory`, `withMemoryAISDK` (Vercel), `withLangChainMemory`.
|
|
131
|
+
|
|
132
|
+
### Dashboard `npx agentmind dashboard`
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
http://localhost:4317
|
|
136
|
+
├── Overview (total/active/archived/byType)
|
|
137
|
+
├── Memories (search, filter, detail with embedding preview)
|
|
138
|
+
├── Context Inspector (query -> vector+keyword -> ranking -> final context)
|
|
139
|
+
├── Retrieval Logs
|
|
140
|
+
└── Consolidation
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**API:** `GET /api/projects`, `POST /api/search`, `POST /api/context`, `POST /api/remember`, `GET /api/retrievals/:id`, etc.
|
|
144
|
+
|
|
145
|
+
### CLI
|
|
146
|
+
```bash
|
|
147
|
+
npx agentmind init --project research-mesh
|
|
148
|
+
npx agentmind project list
|
|
149
|
+
npx agentmind memory search --project research-mesh --query "database"
|
|
150
|
+
npx agentmind memory consolidate --project research-mesh
|
|
151
|
+
npx agentmind ingest --project research-mesh --content "We use Postgres"
|
|
152
|
+
npx agentmind export --project research-mesh
|
|
153
|
+
npx agentmind dashboard --port 4317
|
|
154
|
+
# legacy alias still works:
|
|
155
|
+
npx context-memory dashboard
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Evaluation
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm test # vitest — 13 tests
|
|
162
|
+
npm run test:mvp # mvp harness
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Packaging
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
// package.json exports
|
|
169
|
+
".": "./dist/packages/core/src/index.js",
|
|
170
|
+
"./storage": "./dist/packages/storage/src/index.js",
|
|
171
|
+
"./embeddings": "./dist/packages/embeddings/src/index.js",
|
|
172
|
+
"./integrations": "./dist/packages/integrations/src/index.js",
|
|
173
|
+
"./retrieval": "./dist/packages/retrieval/src/index.js"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Modular: `@agentmind/core`, `@agentmind/storage`, etc. Umbrella `agentmind` installs defaults. Legacy `@context-memory/*` remains as alias via bin.
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { createServer, IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { Memory } from "../../../packages/core/src/memory.js";
|
|
3
|
+
import { SqliteMemoryStore } from "../../../packages/storage/src/sqlite-store.js";
|
|
4
|
+
import { LocalEmbeddingProvider } from "../../../packages/embeddings/src/local.js";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
|
|
8
|
+
const PORT = Number(process.env.PORT ?? 4317);
|
|
9
|
+
const DB_PATH = process.env.AGENTMIND_DB ?? process.env.CTX_MEMORY_DB ?? "./.agentmind/memory.db";
|
|
10
|
+
|
|
11
|
+
const store = new SqliteMemoryStore({ path: DB_PATH });
|
|
12
|
+
const embed = new LocalEmbeddingProvider();
|
|
13
|
+
|
|
14
|
+
function json(res: ServerResponse, data: unknown, status = 200) {
|
|
15
|
+
res.writeHead(status, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS", "Access-Control-Allow-Headers": "Content-Type" });
|
|
16
|
+
res.end(JSON.stringify(data, null, 2));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseBody(req: IncomingMessage): Promise<any> {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
let body = "";
|
|
22
|
+
req.on("data", (chunk) => (body += chunk));
|
|
23
|
+
req.on("end", () => {
|
|
24
|
+
try { resolve(body ? JSON.parse(body) : {}); } catch (e) { reject(e); }
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const server = createServer(async (req, res) => {
|
|
30
|
+
if (req.method === "OPTIONS") {
|
|
31
|
+
res.writeHead(204, { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS", "Access-Control-Allow-Headers": "Content-Type" });
|
|
32
|
+
return res.end();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
|
|
36
|
+
const pathname = url.pathname;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// GET /api/projects
|
|
40
|
+
if (req.method === "GET" && pathname === "/api/projects") {
|
|
41
|
+
const projects = await store.listProjects();
|
|
42
|
+
return json(res, projects);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// GET /api/projects/:id/stats
|
|
46
|
+
if (req.method === "GET" && pathname.match(/^\/api\/projects\/[^/]+\/stats$/)) {
|
|
47
|
+
const projectId = pathname.split("/")[3];
|
|
48
|
+
const memories = await store.listMemories(projectId);
|
|
49
|
+
const byType: Record<string, number> = {};
|
|
50
|
+
for (const m of memories) byType[m.type] = (byType[m.type] ?? 0) + 1;
|
|
51
|
+
return json(res, { projectId, total: memories.length, active: memories.filter(m => !m.archived && m.status==="active").length, archived: memories.filter(m=>m.archived).length, superseded: memories.filter(m=>m.status==="superseded").length, byType });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// GET /api/projects/:id/memories
|
|
55
|
+
if (req.method === "GET" && pathname.match(/^\/api\/projects\/[^/]+\/memories$/)) {
|
|
56
|
+
const projectId = pathname.split("/")[3];
|
|
57
|
+
const type = url.searchParams.get("type");
|
|
58
|
+
const importance = url.searchParams.get("minImportance");
|
|
59
|
+
let memories = await store.listMemories(projectId);
|
|
60
|
+
if (type) memories = memories.filter(m => m.type === type);
|
|
61
|
+
if (importance) memories = memories.filter(m => m.importance >= Number(importance));
|
|
62
|
+
if (url.searchParams.get("archived") === "false") memories = memories.filter(m => !m.archived);
|
|
63
|
+
return json(res, memories);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// POST /api/projects/:id/consolidate
|
|
67
|
+
if (req.method === "POST" && pathname.match(/^\/api\/projects\/[^/]+\/consolidate$/)) {
|
|
68
|
+
const projectId = pathname.split("/")[3];
|
|
69
|
+
const mem = await Memory.create({ project: projectId, store, embeddingProvider: embed });
|
|
70
|
+
// Try to resolve project id to name; if fails use id
|
|
71
|
+
const results = await mem.consolidate();
|
|
72
|
+
return json(res, results);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// GET /api/memories/:id
|
|
76
|
+
if (req.method === "GET" && pathname.match(/^\/api\/memories\/[^/]+$/)) {
|
|
77
|
+
const id = pathname.split("/")[3];
|
|
78
|
+
const mem = await store.getMemory(id);
|
|
79
|
+
if (!mem) return json(res, { error: "not found" }, 404);
|
|
80
|
+
const emb = await store.getEmbedding(id);
|
|
81
|
+
const source = mem.sourceId ? await store.getSource(mem.sourceId) : null;
|
|
82
|
+
return json(res, { ...mem, embedding: emb ? { model: emb.model, dimensions: emb.vector.length, preview: emb.vector.slice(0,5) } : null, source });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// PATCH /api/memories/:id
|
|
86
|
+
if (req.method === "PATCH" && pathname.match(/^\/api\/memories\/[^/]+$/)) {
|
|
87
|
+
const id = pathname.split("/")[3];
|
|
88
|
+
const body = await parseBody(req);
|
|
89
|
+
await store.updateMemory(id, body);
|
|
90
|
+
const mem = await store.getMemory(id);
|
|
91
|
+
return json(res, mem);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// DELETE /api/memories/:id
|
|
95
|
+
if (req.method === "DELETE" && pathname.match(/^\/api\/memories\/[^/]+$/)) {
|
|
96
|
+
const id = pathname.split("/")[3];
|
|
97
|
+
await store.deleteMemory(id);
|
|
98
|
+
return json(res, { ok: true });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// DELETE /api/projects/:id
|
|
102
|
+
if (req.method === "DELETE" && pathname.match(/^\/api\/projects\/[^/]+$/)) {
|
|
103
|
+
const id = pathname.split("/")[3];
|
|
104
|
+
await store.deleteProject(id);
|
|
105
|
+
return json(res, { ok: true });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// GET /api/projects/:id/export
|
|
109
|
+
if (req.method === "GET" && pathname.match(/^\/api\/projects\/[^/]+\/export$/)) {
|
|
110
|
+
const id = pathname.split("/")[3];
|
|
111
|
+
const memories = await store.listMemories(id);
|
|
112
|
+
return json(res, { projectId: id, memories });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// POST /api/search
|
|
116
|
+
if (req.method === "POST" && pathname === "/api/search") {
|
|
117
|
+
const body = await parseBody(req);
|
|
118
|
+
const { project, query, limit = 5, types } = body;
|
|
119
|
+
if (!project || !query) return json(res, { error: "project and query required" }, 400);
|
|
120
|
+
const mem = await Memory.create({ project, store, embeddingProvider: embed });
|
|
121
|
+
const results = await mem.search(query, { limit, types });
|
|
122
|
+
return json(res, results);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// POST /api/context — Context Inspector
|
|
126
|
+
if (req.method === "POST" && pathname === "/api/context") {
|
|
127
|
+
const body = await parseBody(req);
|
|
128
|
+
const { project, query, maxTokens = 6000, limit = 10 } = body;
|
|
129
|
+
if (!project || !query) return json(res, { error: "project and query required" }, 400);
|
|
130
|
+
const mem = await Memory.create({ project, store, embeddingProvider: embed });
|
|
131
|
+
const ctx = await mem.context({ query, maxTokens, limit });
|
|
132
|
+
// Also return retrieval details for inspector
|
|
133
|
+
const searchResults = await mem.search(query, { limit });
|
|
134
|
+
return json(res, { ...ctx, retrieval: searchResults.map(r => ({ id: r.memory.id, content: r.memory.content, score: r.score, semanticScore: r.semanticScore, keywordScore: r.keywordScore, type: r.memory.type, importance: r.memory.importance })) });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// POST /api/ingest
|
|
138
|
+
if (req.method === "POST" && pathname === "/api/ingest") {
|
|
139
|
+
const body = await parseBody(req);
|
|
140
|
+
const { project, role = "user", content, type = "conversation" } = body;
|
|
141
|
+
if (!project || !content) return json(res, { error: "project and content required" }, 400);
|
|
142
|
+
const mem = await Memory.create({ project, store, embeddingProvider: embed });
|
|
143
|
+
const result = await mem.ingest({ role, content, sourceType: type });
|
|
144
|
+
return json(res, result);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// POST /api/remember
|
|
148
|
+
if (req.method === "POST" && pathname === "/api/remember") {
|
|
149
|
+
const body = await parseBody(req);
|
|
150
|
+
const { project, content, type = "fact", importance = 0.5 } = body;
|
|
151
|
+
if (!project || !content) return json(res, { error: "project and content required" }, 400);
|
|
152
|
+
const mem = await Memory.create({ project, store, embeddingProvider: embed });
|
|
153
|
+
const created = await mem.remember({ content, type, importance });
|
|
154
|
+
return json(res, created);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// GET /api/retrievals/:projectId
|
|
158
|
+
if (req.method === "GET" && pathname.match(/^\/api\/retrievals\/[^/]+$/)) {
|
|
159
|
+
const projectId = pathname.split("/")[3];
|
|
160
|
+
const events = await store.listRetrievals(projectId);
|
|
161
|
+
return json(res, events);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Static dashboard
|
|
165
|
+
if (pathname === "/" || pathname === "/index.html") {
|
|
166
|
+
const htmlPath = path.join(import.meta.dirname ?? ".", "dashboard.html");
|
|
167
|
+
if (fs.existsSync(htmlPath)) {
|
|
168
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
169
|
+
return res.end(fs.readFileSync(htmlPath, "utf-8"));
|
|
170
|
+
}
|
|
171
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
172
|
+
return res.end(DASHBOARD_HTML);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
json(res, { error: "not found", path: pathname }, 404);
|
|
176
|
+
} catch (e: any) {
|
|
177
|
+
json(res, { error: e.message ?? String(e), stack: e.stack }, 500);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const DASHBOARD_HTML = `<!doctype html>
|
|
182
|
+
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
183
|
+
<title>AgentMind Dashboard</title>
|
|
184
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
185
|
+
<style>
|
|
186
|
+
::-webkit-scrollbar{width:6px;height:6px} ::-webkit-scrollbar-thumb{background:#3f3f46;border-radius:3px}
|
|
187
|
+
</style>
|
|
188
|
+
</head><body class="bg-zinc-950 text-zinc-100 min-h-screen">
|
|
189
|
+
<div class="max-w-7xl mx-auto p-6">
|
|
190
|
+
<header class="flex items-center justify-between mb-6">
|
|
191
|
+
<h1 class="text-2xl font-bold tracking-tight">AgentMind <span class="text-zinc-500 font-normal">localhost</span></h1>
|
|
192
|
+
<div class="flex gap-2">
|
|
193
|
+
<select id="projectSelect" class="bg-zinc-900 border border-zinc-800 rounded px-3 py-1.5 text-sm"></select>
|
|
194
|
+
<button onclick="loadProjects()" class="bg-zinc-800 hover:bg-zinc-700 px-3 py-1.5 rounded text-sm">Refresh</button>
|
|
195
|
+
</div>
|
|
196
|
+
</header>
|
|
197
|
+
|
|
198
|
+
<!-- Overview -->
|
|
199
|
+
<div id="overview" class="grid grid-cols-4 gap-4 mb-6">
|
|
200
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4"><div class="text-xs text-zinc-500">Total Memories</div><div id="stat-total" class="text-2xl font-mono">-</div></div>
|
|
201
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4"><div class="text-xs text-zinc-500">Active / Archived</div><div id="stat-active" class="text-2xl font-mono">-</div></div>
|
|
202
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4"><div class="text-xs text-zinc-500">By Type</div><div id="stat-types" class="text-xs font-mono">-</div></div>
|
|
203
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4"><div class="text-xs text-zinc-500">Embedding</div><div class="text-xs font-mono">Local hash 256d<br>pgvector ready</div></div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<!-- Tabs -->
|
|
207
|
+
<div class="flex gap-2 mb-4 border-b border-zinc-800">
|
|
208
|
+
<button onclick="showTab('memories')" id="tab-memories" class="px-4 py-2 text-sm font-medium border-b-2 border-white">Memories</button>
|
|
209
|
+
<button onclick="showTab('inspector')" id="tab-inspector" class="px-4 py-2 text-sm font-medium border-b-2 border-transparent text-zinc-400">Context Inspector</button>
|
|
210
|
+
<button onclick="showTab('retrieval')" id="tab-retrieval" class="px-4 py-2 text-sm font-medium border-b-2 border-transparent text-zinc-400">Retrieval Logs</button>
|
|
211
|
+
<button onclick="showTab('consolidate')" id="tab-consolidate" class="px-4 py-2 text-sm font-medium border-b-2 border-transparent text-zinc-400">Consolidation</button>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<!-- Memories -->
|
|
215
|
+
<div id="view-memories">
|
|
216
|
+
<div class="flex gap-2 mb-3">
|
|
217
|
+
<input id="memSearch" placeholder="Search memories..." class="flex-1 bg-zinc-900 border border-zinc-800 rounded px-3 py-2 text-sm" oninput="filterMemories()">
|
|
218
|
+
<select id="typeFilter" class="bg-zinc-900 border border-zinc-800 rounded px-3 py-2 text-sm" onchange="filterMemories()">
|
|
219
|
+
<option value="">All types</option><option>fact</option><option>decision</option><option>knowledge</option><option>preference</option><option>procedure</option><option>task</option><option>summary</option>
|
|
220
|
+
</select>
|
|
221
|
+
<button onclick="loadMemories()" class="bg-white text-black px-4 py-2 rounded text-sm font-medium">Search</button>
|
|
222
|
+
</div>
|
|
223
|
+
<div id="memoryList" class="grid gap-3"></div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<!-- Inspector -->
|
|
227
|
+
<div id="view-inspector" class="hidden">
|
|
228
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4 mb-4">
|
|
229
|
+
<div class="text-sm font-medium mb-2">Ask your memory</div>
|
|
230
|
+
<div class="flex gap-2">
|
|
231
|
+
<input id="inspectorQuery" placeholder="What database did we decide to use?" class="flex-1 bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm">
|
|
232
|
+
<button onclick="runInspector()" class="bg-white text-black px-4 py-2 rounded text-sm font-medium">Run</button>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="grid grid-cols-2 gap-4">
|
|
236
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4">
|
|
237
|
+
<div class="text-xs text-zinc-500 mb-2">RETRIEVED MEMORIES</div>
|
|
238
|
+
<div id="inspectorRetrieved" class="space-y-2 text-sm"></div>
|
|
239
|
+
</div>
|
|
240
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4">
|
|
241
|
+
<div class="text-xs text-zinc-500 mb-2">FINAL CONTEXT <span id="inspectorTokens" class="font-mono"></span></div>
|
|
242
|
+
<pre id="inspectorContext" class="text-xs font-mono whitespace-pre-wrap bg-zinc-950 p-3 rounded"></pre>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<!-- Retrieval -->
|
|
248
|
+
<div id="view-retrieval" class="hidden">
|
|
249
|
+
<div id="retrievalList" class="space-y-2"></div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<!-- Consolidate -->
|
|
253
|
+
<div id="view-consolidate" class="hidden">
|
|
254
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4">
|
|
255
|
+
<button onclick="runConsolidate()" class="bg-white text-black px-4 py-2 rounded text-sm font-medium">Run consolidation</button>
|
|
256
|
+
<div id="consolidateResult" class="mt-4 text-sm font-mono"></div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<!-- Memory detail modal -->
|
|
261
|
+
<dialog id="detailModal" class="bg-zinc-900 text-zinc-100 p-6 rounded-lg max-w-2xl w-full border border-zinc-800">
|
|
262
|
+
<div id="detailContent"></div>
|
|
263
|
+
<button onclick="this.closest('dialog').close()" class="mt-4 bg-zinc-800 px-4 py-2 rounded text-sm">Close</button>
|
|
264
|
+
</dialog>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<script>
|
|
268
|
+
let currentProject = null;
|
|
269
|
+
let allMemories = [];
|
|
270
|
+
|
|
271
|
+
async function loadProjects(){
|
|
272
|
+
const projects = await fetch('/api/projects').then(r=>r.json());
|
|
273
|
+
const sel = document.getElementById('projectSelect');
|
|
274
|
+
sel.innerHTML = projects.map(p=>\`<option value="\${p.id}">\${p.name}</option>\`).join('');
|
|
275
|
+
if(projects[0]){ currentProject = projects[0].id; sel.value = currentProject; loadOverview(); loadMemories(); loadRetrievals(); }
|
|
276
|
+
sel.onchange = e=>{ currentProject = e.target.value; loadOverview(); loadMemories(); loadRetrievals(); };
|
|
277
|
+
}
|
|
278
|
+
async function loadOverview(){
|
|
279
|
+
if(!currentProject) return;
|
|
280
|
+
const stats = await fetch('/api/projects/'+currentProject+'/stats').then(r=>r.json());
|
|
281
|
+
document.getElementById('stat-total').textContent = stats.total;
|
|
282
|
+
document.getElementById('stat-active').textContent = stats.active + ' / ' + stats.archived;
|
|
283
|
+
document.getElementById('stat-types').innerHTML = Object.entries(stats.byType).map(([k,v])=>\`\${k}: \${v}\`).join('<br>');
|
|
284
|
+
}
|
|
285
|
+
async function loadMemories(){
|
|
286
|
+
if(!currentProject) return;
|
|
287
|
+
const memories = await fetch('/api/projects/'+currentProject+'/memories').then(r=>r.json());
|
|
288
|
+
allMemories = memories;
|
|
289
|
+
renderMemories(memories);
|
|
290
|
+
}
|
|
291
|
+
function renderMemories(memories){
|
|
292
|
+
const typeFilter = document.getElementById('typeFilter').value;
|
|
293
|
+
const q = document.getElementById('memSearch').value.toLowerCase();
|
|
294
|
+
let filtered = memories;
|
|
295
|
+
if(typeFilter) filtered = filtered.filter(m=>m.type===typeFilter);
|
|
296
|
+
if(q) filtered = filtered.filter(m=>m.content.toLowerCase().includes(q));
|
|
297
|
+
document.getElementById('memoryList').innerHTML = filtered.map(m=>\`
|
|
298
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4 hover:border-zinc-700 cursor-pointer" onclick="showDetail('\${m.id}')">
|
|
299
|
+
<div class="flex items-center gap-2 mb-1">
|
|
300
|
+
<span class="text-xs px-2 py-0.5 rounded bg-zinc-800 border border-zinc-700">\${m.type}</span>
|
|
301
|
+
<span class="text-xs \${m.status==='active'?'text-emerald-400':m.status==='superseded'?'text-amber-400':'text-zinc-500'}">\${m.status}</span>
|
|
302
|
+
<span class="text-xs text-zinc-500 ml-auto">\${m.importance.toFixed(2)} · \${m.accessCount} uses</span>
|
|
303
|
+
</div>
|
|
304
|
+
<div class="text-sm">\${m.content}</div>
|
|
305
|
+
<div class="text-xs text-zinc-500 mt-1">\${new Date(m.createdAt).toLocaleDateString()} · \${m.archived?'archived':''}</div>
|
|
306
|
+
</div>\`).join('') || '<div class="text-sm text-zinc-500">No memories</div>';
|
|
307
|
+
}
|
|
308
|
+
function filterMemories(){ renderMemories(allMemories); }
|
|
309
|
+
async function showDetail(id){
|
|
310
|
+
const m = await fetch('/api/memories/'+id).then(r=>r.json());
|
|
311
|
+
document.getElementById('detailContent').innerHTML = \`
|
|
312
|
+
<div class="text-xs text-zinc-500">\${m.type} · \${m.status}</div>
|
|
313
|
+
<div class="text-lg mt-2">\${m.content}</div>
|
|
314
|
+
<div class="grid grid-cols-2 gap-4 mt-4 text-xs font-mono">
|
|
315
|
+
<div>Importance: \${m.importance}<br>Confidence: \${m.confidence}<br>Status: \${m.status}<br>Access: \${m.accessCount}</div>
|
|
316
|
+
<div>Created: \${new Date(m.createdAt).toLocaleString()}<br>Updated: \${new Date(m.updatedAt).toLocaleString()}<br>Project: \${m.projectId.slice(0,8)}</div>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="mt-3 text-xs">\${m.source ? 'Source: '+m.source.content : ''}</div>
|
|
319
|
+
<div class="mt-3 text-xs">Embedding: \${m.embedding ? m.embedding.model + ' ' + m.embedding.dimensions + 'd preview ' + JSON.stringify(m.embedding.preview) : 'none'}</div>
|
|
320
|
+
<button onclick="deleteMemory('\${m.id}')" class="mt-4 bg-red-900 hover:bg-red-800 px-3 py-1.5 rounded text-sm">Delete</button>
|
|
321
|
+
\`;
|
|
322
|
+
document.getElementById('detailModal').showModal();
|
|
323
|
+
}
|
|
324
|
+
async function deleteMemory(id){ await fetch('/api/memories/'+id,{method:'DELETE'}); document.getElementById('detailModal').close(); loadMemories(); loadOverview(); }
|
|
325
|
+
function showTab(name){
|
|
326
|
+
['memories','inspector','retrieval','consolidate'].forEach(t=>{
|
|
327
|
+
document.getElementById('view-'+t).classList.toggle('hidden', t!==name);
|
|
328
|
+
document.getElementById('tab-'+t).className = t===name ? 'px-4 py-2 text-sm font-medium border-b-2 border-white' : 'px-4 py-2 text-sm font-medium border-b-2 border-transparent text-zinc-400';
|
|
329
|
+
});
|
|
330
|
+
if(name==='retrieval') loadRetrievals();
|
|
331
|
+
}
|
|
332
|
+
async function runInspector(){
|
|
333
|
+
const query = document.getElementById('inspectorQuery').value;
|
|
334
|
+
if(!query) return;
|
|
335
|
+
const res = await fetch('/api/context',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({project: currentProject, query})}).then(r=>r.json());
|
|
336
|
+
document.getElementById('inspectorTokens').textContent = '['+res.tokenCount+' tokens]';
|
|
337
|
+
document.getElementById('inspectorContext').textContent = res.text || '(no relevant memories)';
|
|
338
|
+
document.getElementById('inspectorRetrieved').innerHTML = (res.retrieval||[]).map((r,i)=>\`
|
|
339
|
+
<div class="bg-zinc-950 border border-zinc-800 rounded p-2">
|
|
340
|
+
<div class="flex justify-between text-xs"><span>#\${i+1} \${r.type}</span><span class="font-mono">score \${r.score.toFixed(3)} · sem \${r.semanticScore.toFixed(2)} · kw \${r.keywordScore.toFixed(2)}</span></div>
|
|
341
|
+
<div class="text-sm mt-1">\${r.content}</div>
|
|
342
|
+
</div>\`).join('');
|
|
343
|
+
}
|
|
344
|
+
async function loadRetrievals(){
|
|
345
|
+
if(!currentProject) return;
|
|
346
|
+
const events = await fetch('/api/retrievals/'+currentProject).then(r=>r.json());
|
|
347
|
+
document.getElementById('retrievalList').innerHTML = events.map(e=>\`
|
|
348
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded p-3">
|
|
349
|
+
<div class="text-sm">Query: "\${e.query}"</div>
|
|
350
|
+
<div class="text-xs text-zinc-500">\${new Date(e.createdAt).toLocaleString()} · \${e.retrievedMemoryIds.length} memories · \${e.finalContextTokens} tokens</div>
|
|
351
|
+
<div class="text-xs font-mono mt-1">scores: \${JSON.stringify(e.scores).slice(0,120)}</div>
|
|
352
|
+
</div>\`).join('') || '<div class="text-sm text-zinc-500">No retrievals yet</div>';
|
|
353
|
+
}
|
|
354
|
+
async function runConsolidate(){
|
|
355
|
+
const res = await fetch('/api/projects/'+currentProject+'/consolidate',{method:'POST'}).then(r=>r.json());
|
|
356
|
+
document.getElementById('consolidateResult').textContent = JSON.stringify(res,null,2);
|
|
357
|
+
loadMemories(); loadOverview();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
loadProjects();
|
|
361
|
+
</script>
|
|
362
|
+
</body></html>`;
|
|
363
|
+
|
|
364
|
+
server.listen(PORT, () => {
|
|
365
|
+
console.log(`AgentMind dashboard at http://localhost:${PORT}`);
|
|
366
|
+
console.log(`DB: ${DB_PATH}`);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
export { server, store };
|