@hilbras/remembra 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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/retrieval.d.ts +10 -0
- package/dist/retrieval.js +51 -0
- package/dist/retrieval.js.map +1 -0
- package/dist/store.d.ts +23 -0
- package/dist/store.js +143 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/docs/clients.md +90 -0
- package/docs/memory-model.md +114 -0
- package/docs/tools.md +75 -0
- package/package.json +64 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Remembra will be documented in this file.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
5
|
+
|
|
6
|
+
## [0.1.0] — 2026-09-23
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- Initial release: MCP memory server for AI assistants.
|
|
10
|
+
- Four memory types: `fact`, `decision`, `role`, `history`.
|
|
11
|
+
- Hybrid scopes: `global` + per-project isolation (no cross-project leaks).
|
|
12
|
+
- File-based storage (`~/.remembra`, markdown + frontmatter), override via `REMEMBRA_HOME`.
|
|
13
|
+
- Layered retrieval: roles always surface → scope → importance → recency → keywords.
|
|
14
|
+
- MCP tools: `memory_store`, `memory_search`, `memory_list`, `memory_forget`.
|
|
15
|
+
- Client setup docs for OpenCode, Claude Code, Cline, and Kimi Code.
|
|
16
|
+
- Test suite (store round-trip, scope isolation, role priority, ranking, delete).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hilbras
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Remembra 🧠
|
|
2
|
+
|
|
3
|
+
**External memory for AI assistants.** Remembra stores facts, decisions, roles, and history
|
|
4
|
+
outside the context window, and hands back only what's relevant — so your AI stops forgetting
|
|
5
|
+
when the conversation gets long.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@hilbras/remembra)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://modelcontextprotocol.io)
|
|
10
|
+
|
|
11
|
+
One memory server, many clients:
|
|
12
|
+
|
|
13
|
+
| Client | Connection |
|
|
14
|
+
|--------|-----------|
|
|
15
|
+
| OpenCode | MCP (stdio) |
|
|
16
|
+
| Claude Code | MCP (stdio) |
|
|
17
|
+
| Cline | MCP (stdio) |
|
|
18
|
+
| Kimi Code | MCP (stdio) |
|
|
19
|
+
| ChatGPT | HTTP API + Custom GPT action *(v1.5)* |
|
|
20
|
+
|
|
21
|
+
## The problem
|
|
22
|
+
|
|
23
|
+
Every AI assistant has a context window. Fill it, and the model starts forgetting earlier
|
|
24
|
+
decisions, repeating questions, and losing track of roles and preferences. Remembra moves
|
|
25
|
+
long-term memory **out of the window and into storage**, then injects only the relevant
|
|
26
|
+
subset each session.
|
|
27
|
+
|
|
28
|
+
## How it works
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Conversation ──► memory_store ──► ~/.remembra/ (markdown files)
|
|
32
|
+
│
|
|
33
|
+
New session ──► memory_search ◄─────────┘ ──► relevant subset injected
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Every memory has a **type**, a **scope**, tags, and an importance score:
|
|
37
|
+
|
|
38
|
+
- **fact** — stable knowledge ("Project uses PostgreSQL 16")
|
|
39
|
+
- **decision** — choices already made ("Chose JWT over sessions")
|
|
40
|
+
- **role** — standing instructions ("Answer concisely"; *always* injected)
|
|
41
|
+
- **history** — condensed chronology of past work
|
|
42
|
+
|
|
43
|
+
**Scopes:**
|
|
44
|
+
- `global` — relevant everywhere (preferences, roles)
|
|
45
|
+
- any project path/id — only loaded when working in that scope, never leaks into other projects
|
|
46
|
+
|
|
47
|
+
**Retrieval ranking:** roles always surface → scope match → importance → recency → keyword overlap.
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install -g @hilbras/remembra
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Claude Code:**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
claude mcp add remembra -- remembra
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**OpenCode** (`~/.config/opencode/opencode.json`):
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcp": {
|
|
66
|
+
"remembra": {
|
|
67
|
+
"type": "local",
|
|
68
|
+
"command": ["remembra"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
More clients (Cline, Kimi Code) in **[docs/clients.md](docs/clients.md)**.
|
|
75
|
+
|
|
76
|
+
## Tools
|
|
77
|
+
|
|
78
|
+
| Tool | Purpose |
|
|
79
|
+
|------|---------|
|
|
80
|
+
| `memory_store` | Save a fact / decision / role / history |
|
|
81
|
+
| `memory_search` | Retrieve relevant memories (pass `scope` = current project) |
|
|
82
|
+
| `memory_list` | Browse stored memories |
|
|
83
|
+
| `memory_forget` | Delete by id |
|
|
84
|
+
|
|
85
|
+
Full reference: **[docs/tools.md](docs/tools.md)**
|
|
86
|
+
|
|
87
|
+
## Documentation
|
|
88
|
+
|
|
89
|
+
| Doc | What's inside |
|
|
90
|
+
|-----|--------------|
|
|
91
|
+
| [Memory model](docs/memory-model.md) | Types, scopes, ranking, storage format |
|
|
92
|
+
| [Tool reference](docs/tools.md) | Every MCP tool with arguments |
|
|
93
|
+
| [Client setup](docs/clients.md) | Config for each supported tool |
|
|
94
|
+
| [Contributing](CONTRIBUTING.md) | Dev workflow and guidelines |
|
|
95
|
+
| [Changelog](CHANGELOG.md) | Release history |
|
|
96
|
+
|
|
97
|
+
## Storage
|
|
98
|
+
|
|
99
|
+
Memories live as readable markdown files you can inspect, edit, and version:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
~/.remembra/
|
|
103
|
+
├── global/ # always-relevant memories
|
|
104
|
+
└── scopes/
|
|
105
|
+
└── <project>/ # project-scoped memories
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Override the location with `REMEMBRA_HOME`.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/Hilbras/Remembra.git
|
|
114
|
+
cd Remembra
|
|
115
|
+
npm install
|
|
116
|
+
npm run build # compile
|
|
117
|
+
npm test # run tests
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Roadmap
|
|
121
|
+
|
|
122
|
+
- **v1** *(current)* — MCP server for coding tools, file storage, layered retrieval
|
|
123
|
+
- **v1.5** — HTTP API + ChatGPT Custom GPT action
|
|
124
|
+
- **v2** — automatic session-digest extraction, embeddings behind `memory_search`
|
|
125
|
+
- **v3** — SQLite for scale, duplicate merging, memory decay
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
[MIT](LICENSE) © Hilbras
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { MemoryStore } from "./store.js";
|
|
6
|
+
import { search } from "./retrieval.js";
|
|
7
|
+
import { StoreInput } from "./types.js";
|
|
8
|
+
const store = new MemoryStore(MemoryStore.defaultRoot());
|
|
9
|
+
const server = new McpServer({ name: "remembra", version: "0.1.0" });
|
|
10
|
+
server.registerTool("memory_store", {
|
|
11
|
+
title: "Store a memory",
|
|
12
|
+
description: "Persist a fact, decision, role or history entry so it survives context window resets. " +
|
|
13
|
+
"Use type 'fact' for stable knowledge, 'decision' for choices already made, " +
|
|
14
|
+
"'role' for standing instructions/roles, 'history' for condensed chronology of past work.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: z.enum(["fact", "decision", "role", "history"]),
|
|
17
|
+
content: z.string().describe("The memory itself, written as a standalone statement"),
|
|
18
|
+
scope: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("'global' for always-relevant memories, or a project path/id for project-scoped ones"),
|
|
22
|
+
tags: z.array(z.string()).optional(),
|
|
23
|
+
importance: z.number().int().min(1).max(5).optional().describe("1=minor, 5=critical (default 3)"),
|
|
24
|
+
source: z.string().optional().describe("Originating session or client"),
|
|
25
|
+
},
|
|
26
|
+
}, async ({ type, content, scope, tags, importance, source }) => {
|
|
27
|
+
const memory = await store.store(StoreInput.parse({ type, content, scope, tags, importance, source }));
|
|
28
|
+
return { content: [{ type: "text", text: `Stored ${memory.type} memory ${memory.id} (scope: ${memory.scope})` }] };
|
|
29
|
+
});
|
|
30
|
+
server.registerTool("memory_search", {
|
|
31
|
+
title: "Search memories",
|
|
32
|
+
description: "Retrieve relevant memories from external storage. Call this at the start of a session " +
|
|
33
|
+
"(or whenever prior context might exist) to recover facts, decisions, roles and history.",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
query: z.string().optional().describe("Keywords to match (omit to get a scope/recency-ranked list)"),
|
|
36
|
+
scope: z.string().optional().describe("Current project path or workspace id to filter by"),
|
|
37
|
+
type: z.enum(["fact", "decision", "role", "history"]).optional(),
|
|
38
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
39
|
+
},
|
|
40
|
+
}, async ({ query, scope, type, limit }) => {
|
|
41
|
+
const results = search(await store.all(), { query, scope, type, limit });
|
|
42
|
+
const text = results.length === 0
|
|
43
|
+
? "No matching memories."
|
|
44
|
+
: results
|
|
45
|
+
.map((m) => `[${m.id}] ${m.type.toUpperCase()} (scope: ${m.scope}, importance: ${m.importance}, ${m.updatedAt.slice(0, 10)})\n${m.content}`)
|
|
46
|
+
.join("\n\n");
|
|
47
|
+
return { content: [{ type: "text", text }] };
|
|
48
|
+
});
|
|
49
|
+
server.registerTool("memory_list", {
|
|
50
|
+
title: "List memories",
|
|
51
|
+
description: "List stored memories, optionally filtered by scope or type.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
scope: z.string().optional(),
|
|
54
|
+
type: z.enum(["fact", "decision", "role", "history"]).optional(),
|
|
55
|
+
},
|
|
56
|
+
}, async ({ scope, type }) => {
|
|
57
|
+
let memories = await store.all();
|
|
58
|
+
if (scope)
|
|
59
|
+
memories = memories.filter((m) => m.scope === scope || m.scope === "global");
|
|
60
|
+
if (type)
|
|
61
|
+
memories = memories.filter((m) => m.type === type);
|
|
62
|
+
memories.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
63
|
+
const text = memories.length === 0
|
|
64
|
+
? "No memories stored yet."
|
|
65
|
+
: memories.map((m) => `[${m.id}] ${m.type} (${m.scope}): ${m.content.split("\n")[0]}`).join("\n");
|
|
66
|
+
return { content: [{ type: "text", text }] };
|
|
67
|
+
});
|
|
68
|
+
server.registerTool("memory_forget", {
|
|
69
|
+
title: "Delete a memory",
|
|
70
|
+
description: "Permanently delete a memory by its id.",
|
|
71
|
+
inputSchema: { id: z.string() },
|
|
72
|
+
}, async ({ id }) => {
|
|
73
|
+
const ok = await store.forget(id);
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text: ok ? `Deleted memory ${id}.` : `No memory with id ${id}.` }],
|
|
76
|
+
isError: !ok,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
const transport = new StdioServerTransport();
|
|
80
|
+
await server.connect(transport);
|
|
81
|
+
console.error(`Remembra memory server running (root: ${MemoryStore.defaultRoot()})`);
|
|
82
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Memory, SearchQuery } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Layered retrieval (decision from Q4):
|
|
4
|
+
* 1. Roles always pass (they are instructions).
|
|
5
|
+
* 2. Scope match (global + current scope beat other scopes).
|
|
6
|
+
* 3. Importance, recency, and keyword overlap contribute to the score.
|
|
7
|
+
*
|
|
8
|
+
* Embeddings can replace the keyword part later without changing callers.
|
|
9
|
+
*/
|
|
10
|
+
export declare function search(memories: Memory[], q: SearchQuery): Memory[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layered retrieval (decision from Q4):
|
|
3
|
+
* 1. Roles always pass (they are instructions).
|
|
4
|
+
* 2. Scope match (global + current scope beat other scopes).
|
|
5
|
+
* 3. Importance, recency, and keyword overlap contribute to the score.
|
|
6
|
+
*
|
|
7
|
+
* Embeddings can replace the keyword part later without changing callers.
|
|
8
|
+
*/
|
|
9
|
+
export function search(memories, q) {
|
|
10
|
+
const now = Date.now();
|
|
11
|
+
const terms = (q.query ?? "")
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.split(/\s+/)
|
|
14
|
+
.filter((t) => t.length > 1);
|
|
15
|
+
const scored = memories
|
|
16
|
+
.filter((m) => (q.type ? m.type === q.type : true))
|
|
17
|
+
.map((m) => ({ m, score: score(m, terms, q.scope, now) }))
|
|
18
|
+
.filter(({ m, score }) => m.type === "role" || score > 0)
|
|
19
|
+
.sort((a, b) => b.score - a.score || b.m.updatedAt.localeCompare(a.m.updatedAt));
|
|
20
|
+
return scored.slice(0, q.limit ?? 10).map(({ m }) => m);
|
|
21
|
+
}
|
|
22
|
+
function score(m, terms, scope, now) {
|
|
23
|
+
let s = 0;
|
|
24
|
+
if (m.type === "role")
|
|
25
|
+
s += 1000;
|
|
26
|
+
if (m.scope === "global")
|
|
27
|
+
s += 100;
|
|
28
|
+
if (scope && m.scope === scope)
|
|
29
|
+
s += 150;
|
|
30
|
+
if (scope && m.scope !== scope && m.scope !== "global")
|
|
31
|
+
return 0; // other projects' memories don't leak in
|
|
32
|
+
s += m.importance * 10;
|
|
33
|
+
// Recency: half-life of ~30 days.
|
|
34
|
+
const ageDays = (now - Date.parse(m.updatedAt)) / 86_400_000;
|
|
35
|
+
if (!Number.isNaN(ageDays))
|
|
36
|
+
s += Math.max(0, 20 - ageDays / 3);
|
|
37
|
+
// Keyword overlap over content + tags.
|
|
38
|
+
if (terms.length > 0) {
|
|
39
|
+
const hay = (m.content + " " + m.tags.join(" ")).toLowerCase();
|
|
40
|
+
let hits = 0;
|
|
41
|
+
for (const t of terms)
|
|
42
|
+
if (hay.includes(t))
|
|
43
|
+
hits++;
|
|
44
|
+
s += (hits / terms.length) * 60;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
s += 10; // no query: everything eligible scores a little
|
|
48
|
+
}
|
|
49
|
+
return s;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=retrieval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retrieval.js","sourceRoot":"","sources":["../src/retrieval.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Memory, StoreInput } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* File-based memory store.
|
|
4
|
+
*
|
|
5
|
+
* Layout (default root: ~/.remembra):
|
|
6
|
+
* global/<id>.md — memories valid everywhere
|
|
7
|
+
* scopes/<scope>/<id>.md — memories scoped to a project/workspace
|
|
8
|
+
*
|
|
9
|
+
* Each file is markdown with YAML-ish frontmatter for human readability
|
|
10
|
+
* and simple greppability.
|
|
11
|
+
*/
|
|
12
|
+
export declare class MemoryStore {
|
|
13
|
+
private readonly root;
|
|
14
|
+
constructor(root: string);
|
|
15
|
+
static defaultRoot(): string;
|
|
16
|
+
private fileFor;
|
|
17
|
+
store(input: StoreInput): Promise<Memory>;
|
|
18
|
+
forget(id: string): Promise<boolean>;
|
|
19
|
+
/** Load every memory across all scopes (global + scoped). */
|
|
20
|
+
all(): Promise<Memory[]>;
|
|
21
|
+
get(id: string): Promise<Memory | null>;
|
|
22
|
+
private findFile;
|
|
23
|
+
}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
/**
|
|
6
|
+
* File-based memory store.
|
|
7
|
+
*
|
|
8
|
+
* Layout (default root: ~/.remembra):
|
|
9
|
+
* global/<id>.md — memories valid everywhere
|
|
10
|
+
* scopes/<scope>/<id>.md — memories scoped to a project/workspace
|
|
11
|
+
*
|
|
12
|
+
* Each file is markdown with YAML-ish frontmatter for human readability
|
|
13
|
+
* and simple greppability.
|
|
14
|
+
*/
|
|
15
|
+
export class MemoryStore {
|
|
16
|
+
root;
|
|
17
|
+
constructor(root) {
|
|
18
|
+
this.root = root;
|
|
19
|
+
}
|
|
20
|
+
static defaultRoot() {
|
|
21
|
+
return process.env.REMEMBRA_HOME ?? path.join(os.homedir(), ".remembra");
|
|
22
|
+
}
|
|
23
|
+
fileFor(m) {
|
|
24
|
+
if (m.scope === "global") {
|
|
25
|
+
return path.join(this.root, "global", `${m.id}.md`);
|
|
26
|
+
}
|
|
27
|
+
const safeScope = m.scope.replace(/[^a-zA-Z0-9._/-]/g, "_");
|
|
28
|
+
return path.join(this.root, "scopes", safeScope, `${m.id}.md`);
|
|
29
|
+
}
|
|
30
|
+
async store(input) {
|
|
31
|
+
const now = new Date().toISOString();
|
|
32
|
+
const memory = {
|
|
33
|
+
id: randomUUID().slice(0, 8),
|
|
34
|
+
type: input.type,
|
|
35
|
+
content: input.content,
|
|
36
|
+
scope: input.scope,
|
|
37
|
+
tags: input.tags,
|
|
38
|
+
importance: input.importance,
|
|
39
|
+
createdAt: now,
|
|
40
|
+
updatedAt: now,
|
|
41
|
+
source: input.source,
|
|
42
|
+
};
|
|
43
|
+
const file = this.fileFor(memory);
|
|
44
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
45
|
+
await fs.writeFile(file, render(memory), "utf8");
|
|
46
|
+
return memory;
|
|
47
|
+
}
|
|
48
|
+
async forget(id) {
|
|
49
|
+
const file = await this.findFile(id);
|
|
50
|
+
if (!file)
|
|
51
|
+
return false;
|
|
52
|
+
await fs.unlink(file);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
/** Load every memory across all scopes (global + scoped). */
|
|
56
|
+
async all() {
|
|
57
|
+
const files = [];
|
|
58
|
+
for (const dir of [path.join(this.root, "global"), path.join(this.root, "scopes")]) {
|
|
59
|
+
files.push(...(await walk(dir)));
|
|
60
|
+
}
|
|
61
|
+
const memories = await Promise.all(files.map((f) => parse(f)));
|
|
62
|
+
return memories.filter((m) => m !== null);
|
|
63
|
+
}
|
|
64
|
+
async get(id) {
|
|
65
|
+
const file = await this.findFile(id);
|
|
66
|
+
if (!file)
|
|
67
|
+
return null;
|
|
68
|
+
return parse(file);
|
|
69
|
+
}
|
|
70
|
+
async findFile(id) {
|
|
71
|
+
const files = await walk(path.join(this.root, "global"), path.join(this.root, "scopes"));
|
|
72
|
+
return files.find((f) => path.basename(f, ".md") === id) ?? null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function walk(...dirs) {
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const dir of dirs) {
|
|
78
|
+
let entries;
|
|
79
|
+
try {
|
|
80
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
for (const e of entries) {
|
|
86
|
+
const full = path.join(dir, e.name);
|
|
87
|
+
if (e.isDirectory())
|
|
88
|
+
out.push(...(await walk(full)));
|
|
89
|
+
else if (e.name.endsWith(".md"))
|
|
90
|
+
out.push(full);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
function render(m) {
|
|
96
|
+
const lines = [
|
|
97
|
+
"---",
|
|
98
|
+
`id: ${m.id}`,
|
|
99
|
+
`type: ${m.type}`,
|
|
100
|
+
`scope: ${m.scope}`,
|
|
101
|
+
`tags: [${m.tags.join(", ")}]`,
|
|
102
|
+
`importance: ${m.importance}`,
|
|
103
|
+
`created: ${m.createdAt}`,
|
|
104
|
+
`updated: ${m.updatedAt}`,
|
|
105
|
+
m.source ? `source: ${m.source}` : undefined,
|
|
106
|
+
"---",
|
|
107
|
+
"",
|
|
108
|
+
m.content,
|
|
109
|
+
"",
|
|
110
|
+
];
|
|
111
|
+
return lines.filter((l) => l !== undefined).join("\n");
|
|
112
|
+
}
|
|
113
|
+
async function parse(file) {
|
|
114
|
+
try {
|
|
115
|
+
const raw = await fs.readFile(file, "utf8");
|
|
116
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);
|
|
117
|
+
if (!match)
|
|
118
|
+
return null;
|
|
119
|
+
const meta = {};
|
|
120
|
+
for (const line of match[1].split("\n")) {
|
|
121
|
+
const idx = line.indexOf(":");
|
|
122
|
+
if (idx === -1)
|
|
123
|
+
continue;
|
|
124
|
+
meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
125
|
+
}
|
|
126
|
+
const tagsRaw = (meta.tags ?? "[]").replace(/^\[|\]$/g, "");
|
|
127
|
+
return {
|
|
128
|
+
id: meta.id ?? path.basename(file, ".md"),
|
|
129
|
+
type: (meta.type ?? "fact"),
|
|
130
|
+
content: match[2].trim(),
|
|
131
|
+
scope: meta.scope ?? "global",
|
|
132
|
+
tags: tagsRaw ? tagsRaw.split(",").map((t) => t.trim()) : [],
|
|
133
|
+
importance: Number(meta.importance ?? 3),
|
|
134
|
+
createdAt: meta.created ?? new Date(0).toISOString(),
|
|
135
|
+
updatedAt: meta.updated ?? meta.created ?? new Date(0).toISOString(),
|
|
136
|
+
source: meta.source,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** The four memory types Remembra stores. */
|
|
3
|
+
export declare const MemoryType: z.ZodEnum<["fact", "decision", "role", "history"]>;
|
|
4
|
+
export type MemoryType = z.infer<typeof MemoryType>;
|
|
5
|
+
/**
|
|
6
|
+
* Scope of a memory.
|
|
7
|
+
* - "global": always relevant (user preferences, roles, general facts)
|
|
8
|
+
* - any other string: project/workspace scope (e.g. a repo path or project id)
|
|
9
|
+
*/
|
|
10
|
+
export type MemoryScope = string;
|
|
11
|
+
export interface Memory {
|
|
12
|
+
id: string;
|
|
13
|
+
type: MemoryType;
|
|
14
|
+
content: string;
|
|
15
|
+
scope: MemoryScope;
|
|
16
|
+
tags: string[];
|
|
17
|
+
importance: number;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
source?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare const StoreInput: z.ZodObject<{
|
|
23
|
+
type: z.ZodEnum<["fact", "decision", "role", "history"]>;
|
|
24
|
+
content: z.ZodString;
|
|
25
|
+
scope: z.ZodDefault<z.ZodString>;
|
|
26
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
27
|
+
importance: z.ZodDefault<z.ZodNumber>;
|
|
28
|
+
source: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
type: "fact" | "decision" | "role" | "history";
|
|
31
|
+
content: string;
|
|
32
|
+
scope: string;
|
|
33
|
+
tags: string[];
|
|
34
|
+
importance: number;
|
|
35
|
+
source?: string | undefined;
|
|
36
|
+
}, {
|
|
37
|
+
type: "fact" | "decision" | "role" | "history";
|
|
38
|
+
content: string;
|
|
39
|
+
scope?: string | undefined;
|
|
40
|
+
tags?: string[] | undefined;
|
|
41
|
+
importance?: number | undefined;
|
|
42
|
+
source?: string | undefined;
|
|
43
|
+
}>;
|
|
44
|
+
export type StoreInput = z.infer<typeof StoreInput>;
|
|
45
|
+
export interface SearchQuery {
|
|
46
|
+
query?: string;
|
|
47
|
+
scope?: string;
|
|
48
|
+
type?: MemoryType;
|
|
49
|
+
limit?: number;
|
|
50
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** The four memory types Remembra stores. */
|
|
3
|
+
export const MemoryType = z.enum(["fact", "decision", "role", "history"]);
|
|
4
|
+
export const StoreInput = z.object({
|
|
5
|
+
type: MemoryType,
|
|
6
|
+
content: z.string().min(1),
|
|
7
|
+
scope: z.string().default("global"),
|
|
8
|
+
tags: z.array(z.string()).default([]),
|
|
9
|
+
importance: z.number().int().min(1).max(5).default(3),
|
|
10
|
+
source: z.string().optional(),
|
|
11
|
+
});
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,6CAA6C;AAC7C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAsB1E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IACnC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC"}
|
package/docs/clients.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Client Setup
|
|
2
|
+
|
|
3
|
+
Remembra speaks MCP over stdio — one server process, any compatible client.
|
|
4
|
+
|
|
5
|
+
> Replace `/home/gin/work/Hilbras/Memory` with wherever you installed Remembra,
|
|
6
|
+
> or use the global install: `npx @hilbras/remembra`.
|
|
7
|
+
|
|
8
|
+
## OpenCode
|
|
9
|
+
|
|
10
|
+
Add to `~/.config/opencode/opencode.json` (global) or a project's `opencode.json`:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"mcp": {
|
|
15
|
+
"remembra": {
|
|
16
|
+
"type": "local",
|
|
17
|
+
"command": ["node", "/home/gin/work/Hilbras/Memory/dist/index.js"]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Claude Code
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
claude mcp add remembra -- node /home/gin/work/Hilbras/Memory/dist/index.js
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or in `.mcp.json` (project) / `~/.claude.json` (user):
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"remembra": {
|
|
35
|
+
"command": "node",
|
|
36
|
+
"args": ["/home/gin/work/Hilbras/Memory/dist/index.js"]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Cline
|
|
43
|
+
|
|
44
|
+
Cline settings → MCP Servers → Add:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"remembra": {
|
|
50
|
+
"command": "node",
|
|
51
|
+
"args": ["/home/gin/work/Hilbras/Memory/dist/index.js"]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Kimi Code
|
|
58
|
+
|
|
59
|
+
Add to the MCP config file used by Kimi CLI:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"remembra": {
|
|
65
|
+
"command": "node",
|
|
66
|
+
"args": ["/home/gin/work/Hilbras/Memory/dist/index.js"]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## ChatGPT
|
|
73
|
+
|
|
74
|
+
*Planned for v1.5* — an HTTP layer exposing the same handlers, wired to a
|
|
75
|
+
Custom GPT action with API-key auth.
|
|
76
|
+
|
|
77
|
+
## Environment
|
|
78
|
+
|
|
79
|
+
| Variable | Default | Purpose |
|
|
80
|
+
|----------|---------|---------|
|
|
81
|
+
| `REMEMBRA_HOME` | `~/.remembra` | Where memory files live |
|
|
82
|
+
|
|
83
|
+
## Tips
|
|
84
|
+
|
|
85
|
+
- **Pass `scope`** — every client should give Remembra its current project path when
|
|
86
|
+
calling `memory_search`, so project memories don't mix across repos.
|
|
87
|
+
- **Roles are always injected** — put standing instructions in `type: "role"` and they'll
|
|
88
|
+
never be filtered out by ranking.
|
|
89
|
+
- **One server, shared brain** — all clients write to the same storage, so a decision made
|
|
90
|
+
in Claude Code is visible in OpenCode.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# The Memory Model
|
|
2
|
+
|
|
3
|
+
Remembra stores four kinds of memories. Every memory you save carries one of these types,
|
|
4
|
+
and the type controls how it's used at retrieval time.
|
|
5
|
+
|
|
6
|
+
## Types
|
|
7
|
+
|
|
8
|
+
### `fact`
|
|
9
|
+
Stable knowledge about the world, a project, or the user.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
type: fact
|
|
13
|
+
content: The API rate limit is 100 requests/minute.
|
|
14
|
+
content: User prefers concise answers without filler.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### `decision`
|
|
18
|
+
Choices that have already been made — so they don't get re-litigated every session.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
type: decision
|
|
22
|
+
content: Chose PostgreSQL over MongoDB for the events table (decided 2026-09-20).
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### `role`
|
|
26
|
+
Standing instructions and roles. **Roles always surface in search results** — they are
|
|
27
|
+
treated as instructions, not suggestions.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
type: role
|
|
31
|
+
content: You are the architect. Never modify test files without asking.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `history`
|
|
35
|
+
Condensed chronology of what already happened — a lean summary instead of a raw transcript.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
type: history
|
|
39
|
+
content: Phase 1: built the auth API, hit a token refresh bug, resolved by rotating keys.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Scopes
|
|
43
|
+
|
|
44
|
+
Every memory belongs to exactly one scope:
|
|
45
|
+
|
|
46
|
+
| Scope | Meaning | Injected when |
|
|
47
|
+
|-------|---------|---------------|
|
|
48
|
+
| `global` | Relevant everywhere (preferences, roles, general facts) | Any session |
|
|
49
|
+
| `<project path or id>` | Belongs to one project/workspace | Only sessions in that scope |
|
|
50
|
+
|
|
51
|
+
Scope isolation is strict: memories scoped to `/repo/a` are **never** returned to a session
|
|
52
|
+
in `/repo/b`. Global memories are always visible.
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{ "type": "decision", "content": "...", "scope": "/home/gin/work/Hilbras/Memory" }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Metadata
|
|
59
|
+
|
|
60
|
+
| Field | Type | Notes |
|
|
61
|
+
|-------|------|-------|
|
|
62
|
+
| `type` | `fact \| decision \| role \| history` | required |
|
|
63
|
+
| `content` | string | required — written as a standalone statement |
|
|
64
|
+
| `scope` | string | defaults to `global` |
|
|
65
|
+
| `tags` | string[] | boosts keyword matching |
|
|
66
|
+
| `importance` | 1–5 | defaults to 3; higher ranks higher |
|
|
67
|
+
| `source` | string | originating session/client (optional) |
|
|
68
|
+
| `id` | 8-char id | assigned automatically |
|
|
69
|
+
| `createdAt` / `updatedAt` | ISO timestamps | assigned automatically |
|
|
70
|
+
|
|
71
|
+
## Retrieval ranking
|
|
72
|
+
|
|
73
|
+
When `memory_search` runs, memories are scored in layers:
|
|
74
|
+
|
|
75
|
+
1. **Roles always pass** (+1000) — instructions never get filtered out.
|
|
76
|
+
2. **Scope gate** — other projects' memories are excluded entirely;
|
|
77
|
+
the current scope scores highest (+150), `global` always passes (+100).
|
|
78
|
+
3. **Importance** — up to +50 for importance 5.
|
|
79
|
+
4. **Recency** — decays over roughly a 30-day half-life (up to +20).
|
|
80
|
+
5. **Keyword overlap** — up to +60 based on the fraction of query terms matched
|
|
81
|
+
in content and tags.
|
|
82
|
+
|
|
83
|
+
Embeddings are planned for v2 and will slot in behind the same `memory_search`
|
|
84
|
+
interface without changing any client.
|
|
85
|
+
|
|
86
|
+
## Storage format
|
|
87
|
+
|
|
88
|
+
Memories are plain markdown files with frontmatter — greppable, editable by hand,
|
|
89
|
+
git-friendly:
|
|
90
|
+
|
|
91
|
+
```markdown
|
|
92
|
+
---
|
|
93
|
+
id: 7133edba
|
|
94
|
+
type: decision
|
|
95
|
+
scope: global
|
|
96
|
+
tags: [architecture]
|
|
97
|
+
importance: 4
|
|
98
|
+
created: 2026-09-23T02:51:41.999Z
|
|
99
|
+
updated: 2026-09-23T02:51:41.999Z
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
Chose file-based storage for v1
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Layout:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
$REMEMBRA_HOME/ # defaults to ~/.remembra
|
|
109
|
+
├── global/
|
|
110
|
+
│ └── <id>.md
|
|
111
|
+
└── scopes/
|
|
112
|
+
└── <scope>/
|
|
113
|
+
└── <id>.md
|
|
114
|
+
```
|
package/docs/tools.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Tool Reference
|
|
2
|
+
|
|
3
|
+
Remembra exposes four MCP tools. All of them work the same way across every
|
|
4
|
+
MCP-compatible client.
|
|
5
|
+
|
|
6
|
+
## `memory_store`
|
|
7
|
+
|
|
8
|
+
Persist a memory so it survives context-window resets.
|
|
9
|
+
|
|
10
|
+
| Argument | Type | Required | Default | Description |
|
|
11
|
+
|----------|------|----------|---------|-------------|
|
|
12
|
+
| `type` | `fact \| decision \| role \| history` | ✅ | — | What kind of memory this is |
|
|
13
|
+
| `content` | string | ✅ | — | The memory, written as a standalone statement |
|
|
14
|
+
| `scope` | string | no | `global` | `global`, or a project path/id |
|
|
15
|
+
| `tags` | string[] | no | `[]` | Keywords that boost retrieval |
|
|
16
|
+
| `importance` | 1–5 | no | `3` | Ranking weight |
|
|
17
|
+
| `source` | string | no | — | Originating session/client |
|
|
18
|
+
|
|
19
|
+
**When to use which type:**
|
|
20
|
+
- Something the model should *know* → `fact`
|
|
21
|
+
- Something already *decided* → `decision`
|
|
22
|
+
- A standing *instruction* or persona → `role`
|
|
23
|
+
- A summary of *what happened* → `history`
|
|
24
|
+
|
|
25
|
+
Returns the assigned memory id.
|
|
26
|
+
|
|
27
|
+
## `memory_search`
|
|
28
|
+
|
|
29
|
+
Retrieve relevant memories. Call at the **start of a session** (to recover prior context)
|
|
30
|
+
and whenever earlier work might be referenced.
|
|
31
|
+
|
|
32
|
+
| Argument | Type | Required | Description |
|
|
33
|
+
|----------|------|----------|-------------|
|
|
34
|
+
| `query` | string | no | Keywords; omit for a scope/recency-ranked list |
|
|
35
|
+
| `scope` | string | no | Current project path/id — pass it whenever you have one |
|
|
36
|
+
| `type` | memory type | no | Restrict to one type |
|
|
37
|
+
| `limit` | 1–50 | no | Max results (default 10) |
|
|
38
|
+
|
|
39
|
+
Returns formatted memories:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
[7133edba] DECISION (scope: global, importance: 4, 2026-09-23)
|
|
43
|
+
Chose file-based storage for v1
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## `memory_list`
|
|
47
|
+
|
|
48
|
+
Browse what's stored — useful for auditing or showing the user their memory.
|
|
49
|
+
|
|
50
|
+
| Argument | Type | Required | Description |
|
|
51
|
+
|----------|------|----------|-------------|
|
|
52
|
+
| `scope` | string | no | Filter by scope (includes `global`) |
|
|
53
|
+
| `type` | memory type | no | Filter by type |
|
|
54
|
+
|
|
55
|
+
Returns one line per memory, newest first.
|
|
56
|
+
|
|
57
|
+
## `memory_forget`
|
|
58
|
+
|
|
59
|
+
Permanently delete a memory.
|
|
60
|
+
|
|
61
|
+
| Argument | Type | Required | Description |
|
|
62
|
+
|----------|------|----------|-------------|
|
|
63
|
+
| `id` | string | ✅ | Memory id (from `memory_store` or `memory_list`) |
|
|
64
|
+
|
|
65
|
+
Returns an error result if no memory matches the id.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Suggested session flow
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
1. memory_search { scope: <current project> } → recover roles, facts, decisions
|
|
73
|
+
2. ... work happens; model calls memory_store when something worth keeping emerges ...
|
|
74
|
+
3. (v2) automatic session digest sweeps anything missed
|
|
75
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hilbras/remembra",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "External memory for AI assistants — remember facts, decisions, roles and history across sessions. MCP server for OpenCode, Claude Code, Cline, Kimi Code and more.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"remembra": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"!dist/test",
|
|
14
|
+
"docs",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"CHANGELOG.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"dev": "tsc --watch",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"test": "node --test \"dist/test/*.test.js\"",
|
|
24
|
+
"prepublishOnly": "npm run build && npm test"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/Hilbras/Remembra.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/Hilbras/Remembra#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/Hilbras/Remembra/issues"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"mcp",
|
|
36
|
+
"model-context-protocol",
|
|
37
|
+
"memory",
|
|
38
|
+
"ai",
|
|
39
|
+
"assistant",
|
|
40
|
+
"context",
|
|
41
|
+
"external-memory",
|
|
42
|
+
"long-term-memory",
|
|
43
|
+
"llm",
|
|
44
|
+
"opencode",
|
|
45
|
+
"claude-code",
|
|
46
|
+
"cline"
|
|
47
|
+
],
|
|
48
|
+
"author": "Hilbras",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
58
|
+
"zod": "^3.24.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "^24.0.0",
|
|
62
|
+
"typescript": "^5.8.0"
|
|
63
|
+
}
|
|
64
|
+
}
|