meminbox 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The "mem0 as a component" piece — and ONLY that piece.
3
+ *
4
+ * Extraction turns raw material (a conversation, a session log) into
5
+ * candidate memories. Candidates are written to the INBOX, never directly
6
+ * to active memory: extraction is fallible, and shared memory amplifies
7
+ * errors. A human (or an explicit promote call) is the quality gate.
8
+ *
9
+ * This implementation is deliberately small and swappable. If a dedicated
10
+ * extractor (mem0, a local model, ...) proves better on your own data,
11
+ * replace `extractCandidates` behind this interface — nothing else changes,
12
+ * because extracted facts are not the source of truth; documents are.
13
+ */
14
+ export async function extractCandidates(cfg, rawText, opts = {}) {
15
+ if (!cfg.openaiApiKey) {
16
+ // No LLM configured: keep the raw material as a single inbox candidate
17
+ // rather than pretending extraction happened.
18
+ return [{ title: "Unprocessed note", contentMd: rawText }];
19
+ }
20
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
21
+ method: "POST",
22
+ headers: {
23
+ Authorization: `Bearer ${cfg.openaiApiKey}`,
24
+ "Content-Type": "application/json",
25
+ },
26
+ body: JSON.stringify({
27
+ model: opts.model ?? process.env.EXTRACT_MODEL ?? "gpt-4o-mini",
28
+ response_format: { type: "json_object" },
29
+ messages: [
30
+ {
31
+ role: "system",
32
+ content: "Extract durable knowledge worth remembering across future sessions: " +
33
+ "decisions and their reasons, procedures, stable facts about the user or a project. " +
34
+ "Skip small talk, transient status, and anything already generic. " +
35
+ 'Return JSON: {"items": [{"title": string, "content": string}]} where content is ' +
36
+ "self-contained Markdown that makes sense without the original conversation. " +
37
+ "Preserve concrete details (names, paths, versions, dates). If nothing is durable, return an empty list.",
38
+ },
39
+ { role: "user", content: rawText },
40
+ ],
41
+ }),
42
+ });
43
+ if (!res.ok) {
44
+ throw new Error(`Extraction request failed: ${res.status} ${await res.text()}`);
45
+ }
46
+ const json = (await res.json());
47
+ const parsed = JSON.parse(json.choices[0]?.message.content ?? "{}");
48
+ return (parsed.items ?? []).map((i) => ({
49
+ title: i.title,
50
+ contentMd: i.content,
51
+ }));
52
+ }
53
+ //# sourceMappingURL=extract.js.map
package/dist/init.js ADDED
@@ -0,0 +1,155 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { createClient } from "@supabase/supabase-js";
5
+ import pg from "pg";
6
+ import { formatDotEnv, meminboxEnvPath, meminboxHome } from "./dotenv.js";
7
+ function parseFlags(argv) {
8
+ const get = (name) => {
9
+ const i = argv.indexOf(`--${name}`);
10
+ return i >= 0 ? argv[i + 1] : undefined;
11
+ };
12
+ return {
13
+ url: get("url") ?? process.env.SUPABASE_URL,
14
+ serviceKey: get("service-key") ?? process.env.SUPABASE_SERVICE_ROLE_KEY,
15
+ databaseUrl: get("database-url") ?? process.env.DATABASE_URL,
16
+ openaiKey: get("openai-key") ?? process.env.OPENAI_API_KEY,
17
+ actor: get("actor"),
18
+ project: get("project"),
19
+ noMigrate: argv.includes("--no-migrate"),
20
+ force: argv.includes("--force"),
21
+ yes: argv.includes("--yes"),
22
+ };
23
+ }
24
+ async function promptSecret(query) {
25
+ if (!process.stdin.isTTY) {
26
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
27
+ const answer = await rl.question(query);
28
+ rl.close();
29
+ return answer.trim();
30
+ }
31
+ process.stderr.write(query);
32
+ return new Promise((resolve) => {
33
+ const stdin = process.stdin;
34
+ stdin.setRawMode(true);
35
+ stdin.resume();
36
+ let value = "";
37
+ const onData = (buf) => {
38
+ const ch = buf.toString("utf8");
39
+ if (ch === "\n" || ch === "\r" || ch === "\u0004") {
40
+ stdin.setRawMode(false);
41
+ stdin.pause();
42
+ stdin.removeListener("data", onData);
43
+ process.stderr.write("\n");
44
+ resolve(value.trim());
45
+ }
46
+ else if (ch === "\u0003") {
47
+ process.exit(130);
48
+ }
49
+ else if (ch === "\u007f") {
50
+ value = value.slice(0, -1);
51
+ }
52
+ else {
53
+ value += ch;
54
+ }
55
+ };
56
+ stdin.on("data", onData);
57
+ });
58
+ }
59
+ function migrationDir() {
60
+ // Works from both src/ (tsx) and dist/ (built): both sit one level
61
+ // below the package root, next to supabase/migrations.
62
+ return fileURLToPath(new URL("../supabase/migrations/", import.meta.url));
63
+ }
64
+ export function listMigrationSql() {
65
+ const dir = migrationDir();
66
+ return readdirSync(dir)
67
+ .filter((f) => f.endsWith(".sql"))
68
+ .sort()
69
+ .map((name) => ({ name, sql: readFileSync(`${dir}${name}`, "utf8") }));
70
+ }
71
+ async function applyMigrations(databaseUrl) {
72
+ const client = new pg.Client({ connectionString: databaseUrl });
73
+ await client.connect();
74
+ try {
75
+ for (const m of listMigrationSql()) {
76
+ await client.query(m.sql);
77
+ console.log(`applied migration ${m.name}`);
78
+ }
79
+ }
80
+ finally {
81
+ await client.end();
82
+ }
83
+ }
84
+ export async function runInit(argv) {
85
+ const opts = parseFlags(argv);
86
+ const envPath = meminboxEnvPath();
87
+ if (existsSync(envPath) && !opts.force) {
88
+ if (opts.yes) {
89
+ throw new Error(`${envPath} already exists. Re-run with --force to overwrite.`);
90
+ }
91
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
92
+ const answer = await rl.question(`${envPath} already exists. Overwrite? [y/N] `);
93
+ rl.close();
94
+ if (!/^y(es)?$/i.test(answer.trim())) {
95
+ console.log("aborted — existing config kept");
96
+ return;
97
+ }
98
+ }
99
+ let { url, serviceKey, databaseUrl, openaiKey } = opts;
100
+ if (!opts.yes) {
101
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
102
+ if (!url)
103
+ url = (await rl.question("Supabase project URL (https://….supabase.co): ")).trim();
104
+ rl.close();
105
+ if (!serviceKey)
106
+ serviceKey = await promptSecret("Supabase service role key (input hidden): ");
107
+ if (!databaseUrl && !opts.noMigrate) {
108
+ databaseUrl = await promptSecret("Postgres connection string for migrations (optional, Enter to skip — you can paste the SQL in the SQL Editor instead): ");
109
+ if (!databaseUrl)
110
+ databaseUrl = undefined;
111
+ }
112
+ if (!openaiKey) {
113
+ const rl2 = createInterface({ input: process.stdin, output: process.stderr });
114
+ openaiKey = (await rl2.question("OpenAI API key for embeddings (optional, Enter to skip): ")).trim() || undefined;
115
+ rl2.close();
116
+ }
117
+ }
118
+ if (!url || !serviceKey) {
119
+ throw new Error("SUPABASE_URL and the service role key are required. Pass --url / --service-key or run interactively.");
120
+ }
121
+ if (!/^https:\/\//.test(url))
122
+ throw new Error("Supabase URL must start with https://");
123
+ if (databaseUrl && !opts.noMigrate) {
124
+ await applyMigrations(databaseUrl);
125
+ }
126
+ else {
127
+ console.log("\nSchema not applied automatically. Paste the migration SQL into your Supabase SQL Editor:\n" +
128
+ listMigrationSql().map((m) => ` - supabase/migrations/${m.name} (in the meminbox package)`).join("\n") +
129
+ "\n");
130
+ }
131
+ mkdirSync(meminboxHome(), { recursive: true, mode: 0o700 });
132
+ const values = {
133
+ SUPABASE_URL: url,
134
+ SUPABASE_SERVICE_ROLE_KEY: serviceKey,
135
+ MEMORY_ACTOR: opts.actor ?? "cli",
136
+ MEMORY_DEFAULT_PROJECT: opts.project ?? "default",
137
+ };
138
+ if (openaiKey)
139
+ values.OPENAI_API_KEY = openaiKey;
140
+ writeFileSync(envPath, formatDotEnv(values), { mode: 0o600 });
141
+ console.log(`wrote ${envPath} (mode 0600; database URL was not saved)`);
142
+ // Smoke test: proves credentials AND schema in one round trip.
143
+ const db = createClient(url, serviceKey, { auth: { persistSession: false } });
144
+ const { error } = await db.from("projects").select("slug").limit(1);
145
+ if (error) {
146
+ console.log(`\nConnected to Supabase, but the meminbox schema is not reachable (${error.message}).\n` +
147
+ "If you skipped migrations, apply the SQL first, then verify with: meminbox list");
148
+ return;
149
+ }
150
+ console.log("\nSetup complete. Next steps:");
151
+ console.log(" meminbox configure-agent --tool claude-code # or codex / cursor / claude-desktop");
152
+ console.log(" meminbox add --title 'First memory' --text '…' --active");
153
+ console.log(" meminbox search 'First memory'");
154
+ }
155
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,122 @@
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 { loadConfig } from "../config.js";
6
+ import { createDb } from "../db.js";
7
+ import { extractCandidates } from "../extract.js";
8
+ import { MemoryService } from "../memory.js";
9
+ /**
10
+ * Unified memory interface for agents.
11
+ *
12
+ * Every MCP client (Claude Code, Codex, Cursor, ...) pointed at the same
13
+ * Supabase project reads and writes the same memory. Writes default to the
14
+ * inbox; search reads active memory. Set MEMORY_ACTOR per client so the
15
+ * audit log and provenance show WHICH agent wrote what.
16
+ */
17
+ const cfg = loadConfig();
18
+ const memory = new MemoryService(cfg, createDb(cfg));
19
+ const server = new McpServer({ name: "meminbox", version: "0.2.0" });
20
+ function text(payload) {
21
+ return {
22
+ content: [
23
+ {
24
+ type: "text",
25
+ text: typeof payload === "string" ? payload : JSON.stringify(payload, null, 2),
26
+ },
27
+ ],
28
+ };
29
+ }
30
+ server.registerTool("memory_search", {
31
+ description: "Search shared active memory across all agents. Returns the most relevant documents (hybrid keyword + vector when embeddings are configured).",
32
+ inputSchema: {
33
+ query: z.string().min(1),
34
+ limit: z.number().int().min(1).max(50).optional(),
35
+ includeInbox: z.boolean().optional(),
36
+ },
37
+ }, async ({ query, limit, includeInbox }) => {
38
+ const active = await memory.search(query, { limit });
39
+ if (!includeInbox)
40
+ return text(active);
41
+ const inbox = await memory.search(query, { limit, status: "inbox" });
42
+ return text({ active, inbox });
43
+ });
44
+ server.registerTool("memory_read", {
45
+ description: "Read one memory document in full by its id.",
46
+ inputSchema: { id: z.string().uuid() },
47
+ }, async ({ id }) => {
48
+ const doc = await memory.getDocument(id);
49
+ return text(doc ?? "not found");
50
+ });
51
+ server.registerTool("memory_write", {
52
+ description: "Write a memory document. It lands in the INBOX by default — it becomes trusted, searchable-by-default memory only after memory_promote. Pass status 'active' only for content a human has already reviewed.",
53
+ inputSchema: {
54
+ title: z.string().min(1),
55
+ content: z.string().min(1),
56
+ project: z.string().optional(),
57
+ status: z.enum(["inbox", "active"]).optional(),
58
+ source: z.string().optional(),
59
+ },
60
+ }, async ({ title, content, project, status, source }) => {
61
+ const doc = await memory.writeDocument({
62
+ title,
63
+ contentMd: content,
64
+ projectSlug: project,
65
+ status,
66
+ provenance: { source: source ?? "agent-write" },
67
+ });
68
+ return text({ id: doc.id, status: doc.status, title: doc.title });
69
+ });
70
+ server.registerTool("memory_extract", {
71
+ description: "Extract durable knowledge candidates from raw text (e.g. a session transcript) and file them in the inbox for review. Extraction never writes active memory directly.",
72
+ inputSchema: {
73
+ text: z.string().min(1),
74
+ project: z.string().optional(),
75
+ },
76
+ }, async ({ text: raw, project }) => {
77
+ const candidates = await extractCandidates(cfg, raw);
78
+ const written = [];
79
+ for (const c of candidates) {
80
+ const doc = await memory.writeDocument({
81
+ title: c.title,
82
+ contentMd: c.contentMd,
83
+ projectSlug: project,
84
+ provenance: { source: "extraction" },
85
+ });
86
+ written.push({ id: doc.id, title: doc.title });
87
+ }
88
+ return text({ filedToInbox: written });
89
+ });
90
+ server.registerTool("memory_promote", {
91
+ description: "Promote an inbox document to active (trusted) memory, or archive a document. This is the quality gate for shared memory.",
92
+ inputSchema: {
93
+ id: z.string().uuid(),
94
+ action: z.enum(["promote", "archive"]),
95
+ },
96
+ }, async ({ id, action }) => {
97
+ const doc = await memory.setStatus(id, action === "promote" ? "active" : "archived");
98
+ return text({ id: doc.id, status: doc.status });
99
+ });
100
+ server.registerTool("memory_list", {
101
+ description: "List recent memory documents, optionally filtered by status.",
102
+ inputSchema: {
103
+ status: z.enum(["inbox", "active", "archived"]).optional(),
104
+ limit: z.number().int().min(1).max(100).optional(),
105
+ },
106
+ }, async ({ status, limit }) => {
107
+ const docs = await memory.listDocuments({ status, limit });
108
+ return text(docs.map((d) => ({
109
+ id: d.id,
110
+ title: d.title,
111
+ status: d.status,
112
+ source_agent: d.source_agent,
113
+ updated_at: d.updated_at,
114
+ })));
115
+ });
116
+ server.registerTool("memory_projects", {
117
+ description: "List memory projects (namespaces).",
118
+ inputSchema: {},
119
+ }, async () => text(await memory.listProjects()));
120
+ const transport = new StdioServerTransport();
121
+ await server.connect(transport);
122
+ //# sourceMappingURL=server.js.map
package/dist/memory.js ADDED
@@ -0,0 +1,207 @@
1
+ import { chunkMarkdown } from "./chunk.js";
2
+ import { embedBatch, embedText } from "./embeddings.js";
3
+ /** pgvector accepts the "[1,2,3]" text literal; use it for inserts and RPC. */
4
+ function toVectorLiteral(vec) {
5
+ return `[${vec.join(",")}]`;
6
+ }
7
+ export class MemoryService {
8
+ cfg;
9
+ db;
10
+ constructor(cfg, db) {
11
+ this.cfg = cfg;
12
+ this.db = db;
13
+ }
14
+ // -- projects -------------------------------------------------------------
15
+ async ensureProject(slug, name) {
16
+ const { data, error } = await this.db
17
+ .from("projects")
18
+ .upsert({ slug, name: name ?? slug }, { onConflict: "slug" })
19
+ .select("id")
20
+ .single();
21
+ if (error)
22
+ throw new Error(`ensureProject failed: ${error.message}`);
23
+ return data.id;
24
+ }
25
+ async listProjects() {
26
+ const { data, error } = await this.db
27
+ .from("projects")
28
+ .select("slug, name")
29
+ .order("slug");
30
+ if (error)
31
+ throw new Error(`listProjects failed: ${error.message}`);
32
+ return data ?? [];
33
+ }
34
+ // -- writes ---------------------------------------------------------------
35
+ /**
36
+ * Write a memory document. Default status is "inbox": agent writes are
37
+ * NOT trusted memory until promoted. That staging step is the guardrail
38
+ * against one agent's mistake becoming every agent's fact.
39
+ */
40
+ async writeDocument(input) {
41
+ const projectId = await this.ensureProject(input.projectSlug ?? this.cfg.defaultProject);
42
+ const provenance = {
43
+ agent: this.cfg.actor,
44
+ source: "manual",
45
+ ...input.provenance,
46
+ };
47
+ const { data, error } = await this.db
48
+ .from("documents")
49
+ .insert({
50
+ project_id: projectId,
51
+ title: input.title,
52
+ content_md: input.contentMd,
53
+ status: input.status ?? "inbox",
54
+ source_agent: provenance.agent ?? null,
55
+ provenance,
56
+ })
57
+ .select("*")
58
+ .single();
59
+ if (error)
60
+ throw new Error(`writeDocument failed: ${error.message}`);
61
+ const doc = data;
62
+ await this.indexDocument(doc);
63
+ await this.audit("write", doc.id, { status: doc.status });
64
+ return doc;
65
+ }
66
+ async setStatus(id, status) {
67
+ const { data, error } = await this.db
68
+ .from("documents")
69
+ .update({ status, updated_at: new Date().toISOString() })
70
+ .eq("id", id)
71
+ .select("*")
72
+ .single();
73
+ if (error)
74
+ throw new Error(`setStatus failed: ${error.message}`);
75
+ await this.audit(status === "active" ? "promote" : status === "archived" ? "archive" : "demote", id, { status });
76
+ return data;
77
+ }
78
+ async getDocument(id) {
79
+ const { data, error } = await this.db
80
+ .from("documents")
81
+ .select("*")
82
+ .eq("id", id)
83
+ .maybeSingle();
84
+ if (error)
85
+ throw new Error(`getDocument failed: ${error.message}`);
86
+ return data ?? null;
87
+ }
88
+ async listDocuments(opts = {}) {
89
+ let q = this.db
90
+ .from("documents")
91
+ .select("*")
92
+ .order("updated_at", { ascending: false })
93
+ .limit(opts.limit ?? 50);
94
+ if (opts.status)
95
+ q = q.eq("status", opts.status);
96
+ const { data, error } = await q;
97
+ if (error)
98
+ throw new Error(`listDocuments failed: ${error.message}`);
99
+ return (data ?? []);
100
+ }
101
+ // -- derived index ----------------------------------------------------------
102
+ /** Rebuild the chunk/embedding index for one document. Safe to re-run. */
103
+ async indexDocument(doc) {
104
+ const chunks = chunkMarkdown(`${doc.title}\n\n${doc.content_md}`);
105
+ const embeddings = await embedBatch(this.cfg, chunks);
106
+ const { error: delError } = await this.db
107
+ .from("document_chunks")
108
+ .delete()
109
+ .eq("document_id", doc.id);
110
+ if (delError)
111
+ throw new Error(`indexDocument delete failed: ${delError.message}`);
112
+ if (chunks.length === 0)
113
+ return;
114
+ const rows = chunks.map((content, i) => ({
115
+ document_id: doc.id,
116
+ chunk_index: i,
117
+ content,
118
+ embedding: embeddings[i] ? toVectorLiteral(embeddings[i]) : null,
119
+ }));
120
+ const { error } = await this.db.from("document_chunks").insert(rows);
121
+ if (error)
122
+ throw new Error(`indexDocument insert failed: ${error.message}`);
123
+ }
124
+ /** Rebuild the derived index for every document. The index is disposable. */
125
+ async reindexAll() {
126
+ const docs = await this.listDocuments({ limit: 1000 });
127
+ for (const doc of docs)
128
+ await this.indexDocument(doc);
129
+ await this.audit("reindex", null, { documents: docs.length });
130
+ return docs.length;
131
+ }
132
+ // -- search ---------------------------------------------------------------
133
+ /**
134
+ * Hybrid search: keyword (FTS + substring fallback in SQL) merged with
135
+ * vector similarity when embeddings are configured. Keyword-only is a
136
+ * fully supported mode — for a personal-scale memory it is often enough.
137
+ */
138
+ async search(query, opts = {}) {
139
+ const limit = opts.limit ?? 10;
140
+ const status = opts.status ?? "active";
141
+ const byDoc = new Map();
142
+ // Keyword path
143
+ const { data: kw, error: kwError } = await this.db.rpc("search_documents", {
144
+ p_query: query,
145
+ p_limit: limit * 3,
146
+ p_status: status,
147
+ });
148
+ if (kwError)
149
+ throw new Error(`search (keyword) failed: ${kwError.message}`);
150
+ const kwRows = (kw ?? []);
151
+ const maxRank = Math.max(...kwRows.map((r) => r.rank), 0);
152
+ for (const row of kwRows) {
153
+ byDoc.set(row.id, {
154
+ documentId: row.id,
155
+ title: row.title,
156
+ snippet: row.content_md.slice(0, 300),
157
+ score: maxRank > 0 ? row.rank / maxRank : 0.5,
158
+ via: ["keyword"],
159
+ });
160
+ }
161
+ // Vector path (only when an embedding provider is configured)
162
+ const queryVec = await embedText(this.cfg, query);
163
+ if (queryVec) {
164
+ const { data: vec, error: vecError } = await this.db.rpc("match_chunks", {
165
+ p_embedding: toVectorLiteral(queryVec),
166
+ p_limit: limit * 3,
167
+ p_status: status,
168
+ });
169
+ if (vecError)
170
+ throw new Error(`search (vector) failed: ${vecError.message}`);
171
+ const vecRows = (vec ?? []);
172
+ for (const row of vecRows) {
173
+ const existing = byDoc.get(row.document_id);
174
+ if (existing) {
175
+ existing.via.push("vector");
176
+ existing.score = Math.min(1, existing.score * 0.5 + row.similarity * 0.5);
177
+ if (row.chunk.length > 0)
178
+ existing.snippet = row.chunk.slice(0, 300);
179
+ }
180
+ else {
181
+ byDoc.set(row.document_id, {
182
+ documentId: row.document_id,
183
+ title: row.title,
184
+ snippet: row.chunk.slice(0, 300),
185
+ score: row.similarity,
186
+ via: ["vector"],
187
+ });
188
+ }
189
+ }
190
+ }
191
+ return [...byDoc.values()]
192
+ .sort((a, b) => b.score - a.score)
193
+ .slice(0, limit);
194
+ }
195
+ // -- audit ------------------------------------------------------------------
196
+ async audit(action, documentId, metadata) {
197
+ const { error } = await this.db.from("audit_log").insert({
198
+ actor: this.cfg.actor,
199
+ action,
200
+ document_id: documentId,
201
+ metadata,
202
+ });
203
+ if (error)
204
+ throw new Error(`audit failed: ${error.message}`);
205
+ }
206
+ }
207
+ //# sourceMappingURL=memory.js.map
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,6 +1 @@
1
- {
2
- "name": "meminbox",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {"bin":{"meminbox":"./dist/cli.js","meminbox-mcp":"./dist/mcp/server.js"},"dependencies":{"@modelcontextprotocol/sdk":"^1.17.0","@supabase/supabase-js":"^2.45.0","pg":"^8.13.0","zod":"^4.6.7"},"description":"Shared memory for AI agents: Markdown documents in your own Supabase project are the source of truth; vector chunks are a rebuildable derived index. Agents connect over MCP, writes land in an inbox with provenance first.","devDependencies":{"@types/node":"^22.0.0","@types/pg":"^8.11.0","tsx":"^4.19.0","typescript":"^5.6.0"},"engines":{"node":">=20"},"files":["dist","supabase",".env.example"],"homepage":"https://github.com/daaquan/meminbox","keywords":["mcp","model-context-protocol","agent-memory","supabase","ai-agents"],"license":"MIT","name":"meminbox","repository":{"type":"git","url":"git+https://github.com/daaquan/meminbox.git"},"scripts":{"build":"tsc -p tsconfig.build.json","cli":"tsx src/cli.ts","mcp":"tsx src/mcp/server.ts","test":"tsx --test test/*.test.ts","typecheck":"tsc -p tsconfig.json --noEmit"},"type":"module","version":"0.2.1"}