@hilbras/remembra 0.1.0 → 0.2.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 +12 -0
- package/README.md +25 -3
- package/dist/http.d.ts +19 -0
- package/dist/http.js +101 -0
- package/dist/http.js.map +1 -0
- package/dist/index.js +79 -75
- package/dist/index.js.map +1 -1
- package/dist/service.d.ts +35 -0
- package/dist/service.js +46 -0
- package/dist/service.js.map +1 -0
- package/docs/chatgpt.md +189 -0
- package/docs/clients.md +9 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to Remembra will be documented in this file.
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
5
5
|
|
|
6
|
+
## [0.2.0] — 2026-09-23
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- HTTP API mode: `remembra --http [--port N]` — same handlers as the MCP tools.
|
|
10
|
+
- Routes: `GET /health`, `POST /memories`, `GET /memories/search`, `GET /memories`,
|
|
11
|
+
`DELETE /memories/:id`.
|
|
12
|
+
- API-key auth via `REMEMBRA_API_KEY` (`x-api-key` header or `Authorization: Bearer`).
|
|
13
|
+
- `REMEMBRA_PORT` env var as default port.
|
|
14
|
+
- Shared `MemoryService` core used by both MCP and HTTP transports.
|
|
15
|
+
- ChatGPT setup guide with full Custom GPT OpenAPI action schema (`docs/chatgpt.md`).
|
|
16
|
+
- HTTP and service test suites.
|
|
17
|
+
|
|
6
18
|
## [0.1.0] — 2026-09-23
|
|
7
19
|
|
|
8
20
|
### Added
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ One memory server, many clients:
|
|
|
16
16
|
| Claude Code | MCP (stdio) |
|
|
17
17
|
| Cline | MCP (stdio) |
|
|
18
18
|
| Kimi Code | MCP (stdio) |
|
|
19
|
-
| ChatGPT | HTTP API + Custom GPT action
|
|
19
|
+
| ChatGPT | HTTP API + Custom GPT action ✅ |
|
|
20
20
|
|
|
21
21
|
## The problem
|
|
22
22
|
|
|
@@ -73,6 +73,27 @@ claude mcp add remembra -- remembra
|
|
|
73
73
|
|
|
74
74
|
More clients (Cline, Kimi Code) in **[docs/clients.md](docs/clients.md)**.
|
|
75
75
|
|
|
76
|
+
### ChatGPT (HTTP mode)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
REMEMBRA_API_KEY="your-secret" remembra --http --port 8787
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then wire a Custom GPT to the API — full walkthrough in **[docs/chatgpt.md](docs/chatgpt.md)**.
|
|
83
|
+
|
|
84
|
+
### HTTP API
|
|
85
|
+
|
|
86
|
+
| Method | Route | Purpose |
|
|
87
|
+
|--------|-------|---------|
|
|
88
|
+
| GET | `/health` | Liveness (no auth) |
|
|
89
|
+
| POST | `/memories` | Store a memory |
|
|
90
|
+
| GET | `/memories/search?query=&scope=` | Search |
|
|
91
|
+
| GET | `/memories?scope=&type=` | List |
|
|
92
|
+
| DELETE | `/memories/:id` | Forget |
|
|
93
|
+
|
|
94
|
+
All routes except `/health` require `x-api-key` (or `Authorization: Bearer`) when
|
|
95
|
+
`REMEMBRA_API_KEY` is set.
|
|
96
|
+
|
|
76
97
|
## Tools
|
|
77
98
|
|
|
78
99
|
| Tool | Purpose |
|
|
@@ -91,6 +112,7 @@ Full reference: **[docs/tools.md](docs/tools.md)**
|
|
|
91
112
|
| [Memory model](docs/memory-model.md) | Types, scopes, ranking, storage format |
|
|
92
113
|
| [Tool reference](docs/tools.md) | Every MCP tool with arguments |
|
|
93
114
|
| [Client setup](docs/clients.md) | Config for each supported tool |
|
|
115
|
+
| [ChatGPT setup](docs/chatgpt.md) | HTTP API + Custom GPT walkthrough |
|
|
94
116
|
| [Contributing](CONTRIBUTING.md) | Dev workflow and guidelines |
|
|
95
117
|
| [Changelog](CHANGELOG.md) | Release history |
|
|
96
118
|
|
|
@@ -119,8 +141,8 @@ npm test # run tests
|
|
|
119
141
|
|
|
120
142
|
## Roadmap
|
|
121
143
|
|
|
122
|
-
- **v1**
|
|
123
|
-
- **v1.5** — HTTP API + ChatGPT Custom GPT action
|
|
144
|
+
- **v1** — MCP server for coding tools, file storage, layered retrieval ✅
|
|
145
|
+
- **v1.5** *(current)* — HTTP API + ChatGPT Custom GPT action ✅
|
|
124
146
|
- **v2** — automatic session-digest extraction, embeddings behind `memory_search`
|
|
125
147
|
- **v3** — SQLite for scale, duplicate merging, memory decay
|
|
126
148
|
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import { MemoryService } from "./service.js";
|
|
3
|
+
interface HttpOptions {
|
|
4
|
+
port?: number;
|
|
5
|
+
/** If set, all endpoints except GET /health require `x-api-key` or `Authorization: Bearer`. */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Minimal HTTP API over the same handlers the MCP tools use.
|
|
10
|
+
*
|
|
11
|
+
* Routes:
|
|
12
|
+
* GET /health → liveness (no auth)
|
|
13
|
+
* POST /memories → store a memory
|
|
14
|
+
* GET /memories/search → ?query=&scope=&type=&limit=
|
|
15
|
+
* GET /memories → ?scope=&type=
|
|
16
|
+
* DELETE /memories/:id → forget
|
|
17
|
+
*/
|
|
18
|
+
export declare function createHttpServer(service: MemoryService, opts?: HttpOptions): http.Server;
|
|
19
|
+
export {};
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal HTTP API over the same handlers the MCP tools use.
|
|
4
|
+
*
|
|
5
|
+
* Routes:
|
|
6
|
+
* GET /health → liveness (no auth)
|
|
7
|
+
* POST /memories → store a memory
|
|
8
|
+
* GET /memories/search → ?query=&scope=&type=&limit=
|
|
9
|
+
* GET /memories → ?scope=&type=
|
|
10
|
+
* DELETE /memories/:id → forget
|
|
11
|
+
*/
|
|
12
|
+
export function createHttpServer(service, opts = {}) {
|
|
13
|
+
const server = http.createServer(async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
16
|
+
const path = url.pathname.replace(/\/+$/, "") || "/";
|
|
17
|
+
if (path === "/health") {
|
|
18
|
+
return send(res, 200, { status: "ok" });
|
|
19
|
+
}
|
|
20
|
+
if (opts.apiKey && !authorized(req, opts.apiKey)) {
|
|
21
|
+
return send(res, 401, { error: "Unauthorized: missing or invalid API key" });
|
|
22
|
+
}
|
|
23
|
+
// POST /memories
|
|
24
|
+
if (req.method === "POST" && path === "/memories") {
|
|
25
|
+
const body = await readBody(req);
|
|
26
|
+
const result = await service.store(body);
|
|
27
|
+
return send(res, 201, result);
|
|
28
|
+
}
|
|
29
|
+
// GET /memories/search
|
|
30
|
+
if (req.method === "GET" && path === "/memories/search") {
|
|
31
|
+
const result = await service.search({
|
|
32
|
+
query: url.searchParams.get("query") ?? url.searchParams.get("q") ?? undefined,
|
|
33
|
+
scope: url.searchParams.get("scope") ?? undefined,
|
|
34
|
+
type: url.searchParams.get("type") ?? undefined,
|
|
35
|
+
limit: url.searchParams.get("limit") ? Number(url.searchParams.get("limit")) : undefined,
|
|
36
|
+
});
|
|
37
|
+
return send(res, 200, result);
|
|
38
|
+
}
|
|
39
|
+
// GET /memories
|
|
40
|
+
if (req.method === "GET" && path === "/memories") {
|
|
41
|
+
const result = await service.list({
|
|
42
|
+
scope: url.searchParams.get("scope") ?? undefined,
|
|
43
|
+
type: url.searchParams.get("type") ?? undefined,
|
|
44
|
+
});
|
|
45
|
+
return send(res, 200, result);
|
|
46
|
+
}
|
|
47
|
+
// DELETE /memories/:id
|
|
48
|
+
const del = path.match(/^\/memories\/([^/]+)$/);
|
|
49
|
+
if (req.method === "DELETE" && del) {
|
|
50
|
+
const result = await service.forget(decodeURIComponent(del[1]));
|
|
51
|
+
return send(res, result.ok ? 200 : 404, result);
|
|
52
|
+
}
|
|
53
|
+
send(res, 404, { error: `No route: ${req.method} ${path}` });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const name = err.name;
|
|
57
|
+
const isClientError = name === "ZodError" || name === "BadRequestError";
|
|
58
|
+
send(res, isClientError ? 400 : 500, {
|
|
59
|
+
error: err instanceof Error ? err.message : String(err),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const port = opts.port ?? Number(process.env.REMEMBRA_PORT ?? 8787);
|
|
64
|
+
server.listen(port, () => {
|
|
65
|
+
console.error(`Remembra HTTP API listening on :${port}${opts.apiKey ? " (auth required)" : " (no auth!)"}`);
|
|
66
|
+
});
|
|
67
|
+
return server;
|
|
68
|
+
}
|
|
69
|
+
function authorized(req, key) {
|
|
70
|
+
const header = req.headers["x-api-key"];
|
|
71
|
+
const auth = req.headers.authorization;
|
|
72
|
+
const provided = (typeof header === "string" ? header : undefined) ??
|
|
73
|
+
(auth?.startsWith("Bearer ") ? auth.slice(7) : undefined);
|
|
74
|
+
return provided === key;
|
|
75
|
+
}
|
|
76
|
+
function send(res, status, body) {
|
|
77
|
+
const data = JSON.stringify(body, null, 2);
|
|
78
|
+
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
79
|
+
res.end(data);
|
|
80
|
+
}
|
|
81
|
+
function readBody(req) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
let chunks = [];
|
|
84
|
+
req.on("data", (c) => chunks.push(c));
|
|
85
|
+
req.on("end", () => {
|
|
86
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
87
|
+
if (!raw)
|
|
88
|
+
return resolve({});
|
|
89
|
+
try {
|
|
90
|
+
resolve(JSON.parse(raw));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
const e = new Error("Invalid JSON body");
|
|
94
|
+
e.name = "BadRequestError";
|
|
95
|
+
reject(e);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
req.on("error", reject);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -3,80 +3,84 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { MemoryStore } from "./store.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { MemoryService } from "./service.js";
|
|
7
|
+
import { createHttpServer } from "./http.js";
|
|
8
8
|
const store = new MemoryStore(MemoryStore.defaultRoot());
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}, async (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
9
|
+
const service = new MemoryService(store);
|
|
10
|
+
const httpFlag = process.argv.includes("--http");
|
|
11
|
+
const portArg = process.argv.indexOf("--port");
|
|
12
|
+
const port = portArg !== -1 ? Number(process.argv[portArg + 1]) : undefined;
|
|
13
|
+
if (httpFlag) {
|
|
14
|
+
// HTTP mode: long-running API for non-MCP clients (ChatGPT, scripts, ...).
|
|
15
|
+
createHttpServer(service, {
|
|
16
|
+
port,
|
|
17
|
+
apiKey: process.env.REMEMBRA_API_KEY,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
// MCP mode (default): stdio transport launched by an MCP client.
|
|
22
|
+
await startMcp();
|
|
23
|
+
}
|
|
24
|
+
async function startMcp() {
|
|
25
|
+
const server = new McpServer({ name: "remembra", version: "0.2.0" });
|
|
26
|
+
server.registerTool("memory_store", {
|
|
27
|
+
title: "Store a memory",
|
|
28
|
+
description: "Persist a fact, decision, role or history entry so it survives context window resets. " +
|
|
29
|
+
"Use type 'fact' for stable knowledge, 'decision' for choices already made, " +
|
|
30
|
+
"'role' for standing instructions/roles, 'history' for condensed chronology of past work.",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: z.enum(["fact", "decision", "role", "history"]),
|
|
33
|
+
content: z.string().describe("The memory itself, written as a standalone statement"),
|
|
34
|
+
scope: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("'global' for always-relevant memories, or a project path/id for project-scoped ones"),
|
|
38
|
+
tags: z.array(z.string()).optional(),
|
|
39
|
+
importance: z.number().int().min(1).max(5).optional().describe("1=minor, 5=critical (default 3)"),
|
|
40
|
+
source: z.string().optional().describe("Originating session or client"),
|
|
41
|
+
},
|
|
42
|
+
}, async (args) => {
|
|
43
|
+
const result = await service.store(args);
|
|
44
|
+
return { content: [{ type: "text", text: result.message }] };
|
|
45
|
+
});
|
|
46
|
+
server.registerTool("memory_search", {
|
|
47
|
+
title: "Search memories",
|
|
48
|
+
description: "Retrieve relevant memories from external storage. Call this at the start of a session " +
|
|
49
|
+
"(or whenever prior context might exist) to recover facts, decisions, roles and history.",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
query: z.string().optional().describe("Keywords to match (omit to get a scope/recency-ranked list)"),
|
|
52
|
+
scope: z.string().optional().describe("Current project path or workspace id to filter by"),
|
|
53
|
+
type: z.enum(["fact", "decision", "role", "history"]).optional(),
|
|
54
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
55
|
+
},
|
|
56
|
+
}, async (args) => {
|
|
57
|
+
const result = await service.search(args);
|
|
58
|
+
return { content: [{ type: "text", text: result.text }] };
|
|
59
|
+
});
|
|
60
|
+
server.registerTool("memory_list", {
|
|
61
|
+
title: "List memories",
|
|
62
|
+
description: "List stored memories, optionally filtered by scope or type.",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
scope: z.string().optional(),
|
|
65
|
+
type: z.enum(["fact", "decision", "role", "history"]).optional(),
|
|
66
|
+
},
|
|
67
|
+
}, async (args) => {
|
|
68
|
+
const result = await service.list(args);
|
|
69
|
+
return { content: [{ type: "text", text: result.text }] };
|
|
70
|
+
});
|
|
71
|
+
server.registerTool("memory_forget", {
|
|
72
|
+
title: "Delete a memory",
|
|
73
|
+
description: "Permanently delete a memory by its id.",
|
|
74
|
+
inputSchema: { id: z.string() },
|
|
75
|
+
}, async ({ id }) => {
|
|
76
|
+
const result = await service.forget(id);
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: "text", text: result.text }],
|
|
79
|
+
isError: !result.ok,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
const transport = new StdioServerTransport();
|
|
83
|
+
await server.connect(transport);
|
|
84
|
+
console.error(`Remembra MCP server running (root: ${MemoryStore.defaultRoot()})`);
|
|
85
|
+
}
|
|
82
86
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { MemoryStore } from "./store.js";
|
|
2
|
+
import { MemoryType } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Transport-agnostic handlers. Both the MCP tools and the HTTP API
|
|
5
|
+
* call into this module, so behavior is guaranteed to match.
|
|
6
|
+
*/
|
|
7
|
+
export declare class MemoryService {
|
|
8
|
+
readonly db: MemoryStore;
|
|
9
|
+
constructor(db: MemoryStore);
|
|
10
|
+
store(input: unknown): Promise<{
|
|
11
|
+
id: string;
|
|
12
|
+
message: string;
|
|
13
|
+
memory: import("./types.js").Memory;
|
|
14
|
+
}>;
|
|
15
|
+
search(q: {
|
|
16
|
+
query?: string;
|
|
17
|
+
scope?: string;
|
|
18
|
+
type?: MemoryType;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
text: string;
|
|
22
|
+
results: import("./types.js").Memory[];
|
|
23
|
+
}>;
|
|
24
|
+
list(q: {
|
|
25
|
+
scope?: string;
|
|
26
|
+
type?: MemoryType;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
text: string;
|
|
29
|
+
memories: import("./types.js").Memory[];
|
|
30
|
+
}>;
|
|
31
|
+
forget(id: string): Promise<{
|
|
32
|
+
ok: boolean;
|
|
33
|
+
text: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { search } from "./retrieval.js";
|
|
2
|
+
import { StoreInput } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Transport-agnostic handlers. Both the MCP tools and the HTTP API
|
|
5
|
+
* call into this module, so behavior is guaranteed to match.
|
|
6
|
+
*/
|
|
7
|
+
export class MemoryService {
|
|
8
|
+
db;
|
|
9
|
+
constructor(db) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
}
|
|
12
|
+
async store(input) {
|
|
13
|
+
const memory = await this.db.store(StoreInput.parse(input));
|
|
14
|
+
return {
|
|
15
|
+
id: memory.id,
|
|
16
|
+
message: `Stored ${memory.type} memory ${memory.id} (scope: ${memory.scope})`,
|
|
17
|
+
memory,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async search(q) {
|
|
21
|
+
const results = search(await this.db.all(), q);
|
|
22
|
+
const text = results.length === 0
|
|
23
|
+
? "No matching memories."
|
|
24
|
+
: results
|
|
25
|
+
.map((m) => `[${m.id}] ${m.type.toUpperCase()} (scope: ${m.scope}, importance: ${m.importance}, ${m.updatedAt.slice(0, 10)})\n${m.content}`)
|
|
26
|
+
.join("\n\n");
|
|
27
|
+
return { text, results };
|
|
28
|
+
}
|
|
29
|
+
async list(q) {
|
|
30
|
+
let memories = await this.db.all();
|
|
31
|
+
if (q.scope)
|
|
32
|
+
memories = memories.filter((m) => m.scope === q.scope || m.scope === "global");
|
|
33
|
+
if (q.type)
|
|
34
|
+
memories = memories.filter((m) => m.type === q.type);
|
|
35
|
+
memories.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
36
|
+
const text = memories.length === 0
|
|
37
|
+
? "No memories stored yet."
|
|
38
|
+
: memories.map((m) => `[${m.id}] ${m.type} (${m.scope}): ${m.content.split("\n")[0]}`).join("\n");
|
|
39
|
+
return { text, memories };
|
|
40
|
+
}
|
|
41
|
+
async forget(id) {
|
|
42
|
+
const ok = await this.db.forget(id);
|
|
43
|
+
return { ok, text: ok ? `Deleted memory ${id}.` : `No memory with id ${id}.` };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/docs/chatgpt.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# ChatGPT Setup (v1.5)
|
|
2
|
+
|
|
3
|
+
ChatGPT doesn't speak MCP, so Remembra exposes the same memory handlers over a plain
|
|
4
|
+
HTTP API. You connect it by creating a **Custom GPT** with API actions.
|
|
5
|
+
|
|
6
|
+
## 1. Start the HTTP server
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
export REMEMBRA_API_KEY="pick-a-long-random-secret"
|
|
10
|
+
remembra --http --port 8787
|
|
11
|
+
# or, from source:
|
|
12
|
+
node dist/index.js --http --port 8787
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Check it's up:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
curl http://localhost:8787/health
|
|
19
|
+
# {"status": "ok"}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- `REMEMBRA_API_KEY` **strongly recommended** — without it every endpoint is open.
|
|
23
|
+
- The server binds to localhost by default. For ChatGPT to reach it you must expose it
|
|
24
|
+
publicly (see [§4](#4-exposing-the-server)).
|
|
25
|
+
|
|
26
|
+
## 2. HTTP API reference
|
|
27
|
+
|
|
28
|
+
All endpoints except `/health` require the key via `x-api-key` or `Authorization: Bearer`.
|
|
29
|
+
|
|
30
|
+
### Store a memory
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl -X POST http://localhost:8787/memories \
|
|
34
|
+
-H "content-type: application/json" \
|
|
35
|
+
-H "x-api-key: $REMEMBRA_API_KEY" \
|
|
36
|
+
-d '{
|
|
37
|
+
"type": "decision",
|
|
38
|
+
"content": "Chose monthly billing over annual",
|
|
39
|
+
"scope": "global",
|
|
40
|
+
"tags": ["pricing"],
|
|
41
|
+
"importance": 4
|
|
42
|
+
}'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Search memories
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
curl "http://localhost:8787/memories/search?query=billing&scope=global&limit=5" \
|
|
49
|
+
-H "x-api-key: $REMEMBRA_API_KEY"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| Query param | Description |
|
|
53
|
+
|-------------|-------------|
|
|
54
|
+
| `query` (or `q`) | keywords to match |
|
|
55
|
+
| `scope` | project/workspace filter |
|
|
56
|
+
| `type` | `fact` \| `decision` \| `role` \| `history` |
|
|
57
|
+
| `limit` | max results (default 10) |
|
|
58
|
+
|
|
59
|
+
### List memories
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
curl "http://localhost:8787/memories?scope=global&type=role" \
|
|
63
|
+
-H "x-api-key: $REMEMBRA_API_KEY"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Delete a memory
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
curl -X DELETE http://localhost:8787/memories/<id> \
|
|
70
|
+
-H "x-api-key: $REMEMBRA_API_KEY"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 3. Create the Custom GPT
|
|
74
|
+
|
|
75
|
+
1. Go to **chatgpt.com → Explore GPTs → Create a GPT**.
|
|
76
|
+
2. **Name**: Remembra (or whatever you like).
|
|
77
|
+
3. **Instructions** — paste this:
|
|
78
|
+
|
|
79
|
+
> You have long-term memory powered by Remembra. At the start of every conversation,
|
|
80
|
+
> call `search_memories` with no query to load your roles, facts and decisions.
|
|
81
|
+
> Whenever the user tells you something worth remembering (a fact, a decision,
|
|
82
|
+
> their role or preferences) or a decision is made, call `store_memory`.
|
|
83
|
+
> Scope for this chat is `chatgpt` unless the user is discussing a specific project.
|
|
84
|
+
> If the model asks you to forget something, call `delete_memory` with its id.
|
|
85
|
+
|
|
86
|
+
4. **Capabilities** → enable **Actions**.
|
|
87
|
+
5. Under **Actions → Create new action**, paste this schema (fill in your domain and key):
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"openapi": "3.1.0",
|
|
92
|
+
"info": { "title": "Remembra Memory", "version": "1.0.0" },
|
|
93
|
+
"servers": [{ "url": "https://YOUR-DOMAIN.example" }],
|
|
94
|
+
"paths": {
|
|
95
|
+
"/memories": {
|
|
96
|
+
"post": {
|
|
97
|
+
"operationId": "store_memory",
|
|
98
|
+
"summary": "Store a fact, decision, role or history",
|
|
99
|
+
"requestBody": {
|
|
100
|
+
"required": true,
|
|
101
|
+
"content": {
|
|
102
|
+
"application/json": {
|
|
103
|
+
"schema": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"required": ["type", "content"],
|
|
106
|
+
"properties": {
|
|
107
|
+
"type": { "type": "string", "enum": ["fact", "decision", "role", "history"] },
|
|
108
|
+
"content": { "type": "string", "description": "Standalone statement" },
|
|
109
|
+
"scope": { "type": "string", "default": "global" },
|
|
110
|
+
"tags": { "type": "array", "items": { "type": "string" } },
|
|
111
|
+
"importance": { "type": "integer", "minimum": 1, "maximum": 5 }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"responses": { "201": { "description": "Stored" } }
|
|
118
|
+
},
|
|
119
|
+
"get": {
|
|
120
|
+
"operationId": "list_memories",
|
|
121
|
+
"summary": "List stored memories",
|
|
122
|
+
"parameters": [
|
|
123
|
+
{ "name": "scope", "in": "query", "schema": { "type": "string" } },
|
|
124
|
+
{ "name": "type", "in": "query", "schema": { "type": "string" } }
|
|
125
|
+
],
|
|
126
|
+
"responses": { "200": { "description": "OK" } }
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"/memories/search": {
|
|
130
|
+
"get": {
|
|
131
|
+
"operationId": "search_memories",
|
|
132
|
+
"summary": "Search memories",
|
|
133
|
+
"parameters": [
|
|
134
|
+
{ "name": "query", "in": "query", "schema": { "type": "string" } },
|
|
135
|
+
{ "name": "scope", "in": "query", "schema": { "type": "string" } },
|
|
136
|
+
{ "name": "type", "in": "query", "schema": { "type": "string" } },
|
|
137
|
+
{ "name": "limit", "in": "query", "schema": { "type": "integer" } }
|
|
138
|
+
],
|
|
139
|
+
"responses": { "200": { "description": "OK" } }
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"/memories/{id}": {
|
|
143
|
+
"delete": {
|
|
144
|
+
"operationId": "delete_memory",
|
|
145
|
+
"summary": "Delete a memory by id",
|
|
146
|
+
"parameters": [
|
|
147
|
+
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
|
|
148
|
+
],
|
|
149
|
+
"responses": { "200": { "description": "Deleted" } }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"components": {
|
|
154
|
+
"securitySchemes": {
|
|
155
|
+
"apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"security": [{ "apiKeyAuth": [] }]
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
6. In **Authentication**, choose **API Key → Custom → Header → `x-api-key`**, and paste
|
|
163
|
+
your `REMEMBRA_API_KEY`.
|
|
164
|
+
7. **Save** (Only me / Anyone, your choice) and test:
|
|
165
|
+
|
|
166
|
+
> *"Remember that I prefer metric units."* → the GPT should call `store_memory`.
|
|
167
|
+
> Then in a **new chat**: *"What units do I prefer?"* → it calls `search_memories`
|
|
168
|
+
> and answers. That's the whole point of Remembra — the memory survived the
|
|
169
|
+
> context reset.
|
|
170
|
+
|
|
171
|
+
## 4. Exposing the server
|
|
172
|
+
|
|
173
|
+
ChatGPT must reach your server over HTTPS. Options, easiest first:
|
|
174
|
+
|
|
175
|
+
| Approach | Notes |
|
|
176
|
+
|----------|-------|
|
|
177
|
+
| **Cloud VM / VPS** | Run `remembra --http` behind nginx/Caddy with TLS. Simplest for a personal server. |
|
|
178
|
+
| **Tunnel** | `cloudflared tunnel` or `ngrok http 8787` — quick HTTPS URL for testing. |
|
|
179
|
+
| **Serverless** | Port `service.ts` handlers to a Lambda/Cloudflare Worker (v2 territory). |
|
|
180
|
+
|
|
181
|
+
> ⚠️ Always set `REMEMBRA_API_KEY` when the server is reachable from the internet,
|
|
182
|
+
> and prefer a tunnel with access restrictions while testing.
|
|
183
|
+
|
|
184
|
+
## 5. Sharing memory with your coding tools
|
|
185
|
+
|
|
186
|
+
Because both transports use the same storage (`~/.remembra`), memories written by
|
|
187
|
+
ChatGPT appear in OpenCode/Claude Code and vice versa — just point both at the same
|
|
188
|
+
`REMEMBRA_HOME`. Use scope `chatgpt` for chat-specific memories and `global` for
|
|
189
|
+
anything that should follow you everywhere.
|
package/docs/clients.md
CHANGED
|
@@ -71,14 +71,21 @@ Add to the MCP config file used by Kimi CLI:
|
|
|
71
71
|
|
|
72
72
|
## ChatGPT
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
Custom GPT action
|
|
74
|
+
Uses the HTTP mode instead of MCP — see the full walkthrough in
|
|
75
|
+
**[chatgpt.md](chatgpt.md)** (server start, API reference, Custom GPT action schema,
|
|
76
|
+
tunneling options).
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
REMEMBRA_API_KEY="your-secret" remembra --http
|
|
80
|
+
```
|
|
76
81
|
|
|
77
82
|
## Environment
|
|
78
83
|
|
|
79
84
|
| Variable | Default | Purpose |
|
|
80
85
|
|----------|---------|---------|
|
|
81
86
|
| `REMEMBRA_HOME` | `~/.remembra` | Where memory files live |
|
|
87
|
+
| `REMEMBRA_API_KEY` | *(unset)* | Enables auth on the HTTP API |
|
|
88
|
+
| `REMEMBRA_PORT` | `8787` | HTTP API port (`--port` overrides) |
|
|
82
89
|
|
|
83
90
|
## Tips
|
|
84
91
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hilbras/remembra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"bin": {
|