atlas-mcp 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.
Files changed (54) hide show
  1. package/.env.example +32 -0
  2. package/README.md +282 -0
  3. package/package.json +72 -0
  4. package/public/app/assets/app-CxbS1w9p.js +3981 -0
  5. package/public/app/assets/index-BA6nxCuI.css +1 -0
  6. package/public/app/assets/index-BXmIRrQH.js +177 -0
  7. package/public/app/index.html +27 -0
  8. package/public/assets/brain-atlas.LICENSE.txt +16 -0
  9. package/public/assets/brain-atlas.glb +0 -0
  10. package/public/assets/brain.obj +27282 -0
  11. package/public/fonts/DepartureMono-Regular.woff +0 -0
  12. package/public/fonts/DepartureMono-Regular.woff2 +0 -0
  13. package/scripts/sync-memory-vectors.js +46 -0
  14. package/src/audit.js +9 -0
  15. package/src/cli/args.js +87 -0
  16. package/src/cli/commands/add.js +103 -0
  17. package/src/cli/commands/config.js +228 -0
  18. package/src/cli/commands/delete.js +75 -0
  19. package/src/cli/commands/entities.js +39 -0
  20. package/src/cli/commands/entity.js +47 -0
  21. package/src/cli/commands/get.js +46 -0
  22. package/src/cli/commands/list.js +53 -0
  23. package/src/cli/commands/related.js +56 -0
  24. package/src/cli/commands/search.js +68 -0
  25. package/src/cli/commands/update.js +58 -0
  26. package/src/cli/deps.js +114 -0
  27. package/src/cli/env-file.js +44 -0
  28. package/src/cli/format.js +246 -0
  29. package/src/cli.js +187 -0
  30. package/src/cognitive-worker.js +381 -0
  31. package/src/db.js +2674 -0
  32. package/src/extraction-context.js +31 -0
  33. package/src/ingestion-service.js +387 -0
  34. package/src/ingestion-worker.js +225 -0
  35. package/src/llm-config.js +31 -0
  36. package/src/llm.js +789 -0
  37. package/src/logger.js +51 -0
  38. package/src/mcp-server.js +577 -0
  39. package/src/memory-comparison.js +421 -0
  40. package/src/related-memories.js +232 -0
  41. package/src/run-cognitive-worker.js +12 -0
  42. package/src/run-ingestion-worker.js +13 -0
  43. package/src/run-vector-worker.js +12 -0
  44. package/src/schemas.js +413 -0
  45. package/src/semantic-validation.js +430 -0
  46. package/src/server.js +827 -0
  47. package/src/shared/brain-regions.js +61 -0
  48. package/src/shared/entity-lens.js +249 -0
  49. package/src/shared/memory-placement.js +171 -0
  50. package/src/shared/memory-search.js +55 -0
  51. package/src/shared/region-anchors.js +112 -0
  52. package/src/shared/region-mapper.js +247 -0
  53. package/src/vector-store.js +546 -0
  54. package/src/vector-worker.js +71 -0
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+
3
+ import "dotenv/config";
4
+ import { closeDb, getMemories } from "../src/db.js";
5
+ import {
6
+ QDRANT_COLLECTION,
7
+ createMemoryVectorStore,
8
+ createQdrantCloudClient,
9
+ } from "../src/vector-store.js";
10
+
11
+ const PAGE_SIZE = 100;
12
+
13
+ async function syncMemoryVectors() {
14
+ const store = createMemoryVectorStore({
15
+ client: createQdrantCloudClient(),
16
+ });
17
+ let offset = 0;
18
+ let synced = 0;
19
+
20
+ while (true) {
21
+ const memories = getMemories({ limit: PAGE_SIZE, offset });
22
+ if (!memories.length) break;
23
+
24
+ for (const memory of memories) {
25
+ await store.indexMemory(memory);
26
+ synced += 1;
27
+ console.log(`Indexed ${synced}: ${memory.id}`);
28
+ }
29
+
30
+ offset += memories.length;
31
+ if (memories.length < PAGE_SIZE) break;
32
+ }
33
+
34
+ console.log(
35
+ `Synced ${synced} memories to Qdrant Cloud collection "${QDRANT_COLLECTION}".`,
36
+ );
37
+ }
38
+
39
+ syncMemoryVectors()
40
+ .catch((error) => {
41
+ console.error(`Qdrant Cloud sync failed: ${error.message}`);
42
+ process.exitCode = 1;
43
+ })
44
+ .finally(() => {
45
+ closeDb();
46
+ });
package/src/audit.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { auditMemoryIntegrity, closeDb } from "./db.js";
4
+
5
+ try {
6
+ console.log(JSON.stringify(auditMemoryIntegrity(), null, 2));
7
+ } finally {
8
+ closeDb();
9
+ }
@@ -0,0 +1,87 @@
1
+ // Handwritten argv parser. Returns { positional, flags } where flags is a plain
2
+ // object (booleans for `--name`/`--no-name`, strings for `--name value` or
3
+ // `--name=value`, comma-split arrays for repeatable flags via repeat()). Stops
4
+ // parsing flags after `--`. Reserved globals (`--json`, `--help`, `--version`)
5
+ // are not consumed here — the entry point reads them before delegating.
6
+
7
+ const REPEATABLE = new Set(["tag", "tags"]);
8
+
9
+ function isFlag(token) {
10
+ return token.startsWith("--") && token.length > 2;
11
+ }
12
+
13
+ function splitFlag(token) {
14
+ const eq = token.indexOf("=");
15
+ if (eq === -1) return { name: token.slice(2), value: undefined };
16
+ return { name: token.slice(2, eq), value: token.slice(eq + 1) };
17
+ }
18
+
19
+ function camelize(name) {
20
+ return name.replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
21
+ }
22
+
23
+ function coerceScalar(value) {
24
+ if (value === "true") return true;
25
+ if (value === "false") return false;
26
+ if (/^-?\d+$/.test(value)) return Number(value);
27
+ if (/^-?\d+\.\d+$/.test(value)) return Number(value);
28
+ return value;
29
+ }
30
+
31
+ function assignFlag(flags, name, rawValue) {
32
+ if (name.startsWith("no-") && rawValue === undefined) {
33
+ flags[camelize(name.slice(3))] = false;
34
+ return;
35
+ }
36
+ const value = rawValue === undefined ? true : coerceScalar(rawValue);
37
+ if (REPEATABLE.has(name) && value !== true && value !== false) {
38
+ const list = String(value)
39
+ .split(",")
40
+ .map((v) => v.trim())
41
+ .filter(Boolean);
42
+ const key = camelize(name);
43
+ flags[key] = Array.isArray(flags[key]) ? [...flags[key], ...list] : list;
44
+ return;
45
+ }
46
+ flags[camelize(name)] = value;
47
+ }
48
+
49
+ export function parseArgs(argv) {
50
+ const positional = [];
51
+ const flags = {};
52
+ let i = 0;
53
+ let stopFlags = false;
54
+ while (i < argv.length) {
55
+ const token = argv[i];
56
+ if (stopFlags) {
57
+ positional.push(token);
58
+ i += 1;
59
+ continue;
60
+ }
61
+ if (token === "--") {
62
+ stopFlags = true;
63
+ i += 1;
64
+ continue;
65
+ }
66
+ if (isFlag(token)) {
67
+ const { name, value } = splitFlag(token);
68
+ if (value !== undefined) {
69
+ assignFlag(flags, name, value);
70
+ i += 1;
71
+ continue;
72
+ }
73
+ const next = argv[i + 1];
74
+ if (next !== undefined && !isFlag(next)) {
75
+ assignFlag(flags, name, next);
76
+ i += 2;
77
+ continue;
78
+ }
79
+ assignFlag(flags, name, undefined);
80
+ i += 1;
81
+ continue;
82
+ }
83
+ positional.push(token);
84
+ i += 1;
85
+ }
86
+ return { positional, flags };
87
+ }
@@ -0,0 +1,103 @@
1
+ import { z } from "zod";
2
+ import {
3
+ formatAddSummary,
4
+ printError,
5
+ printJson,
6
+ } from "../format.js";
7
+
8
+ const TYPE_VALUES = [
9
+ "relationship",
10
+ "preference",
11
+ "fact",
12
+ "decision",
13
+ "learning",
14
+ "event",
15
+ "instruction",
16
+ "observation",
17
+ "error",
18
+ ];
19
+
20
+ const AddInputSchema = z.object({
21
+ text: z
22
+ .string()
23
+ .min(1, "text is required")
24
+ .max(2000, "text must be 2000 characters or fewer"),
25
+ type: z.enum(TYPE_VALUES).optional(),
26
+ title: z
27
+ .string()
28
+ .min(1, "title must not be empty")
29
+ .max(50, "title must be 50 characters or fewer")
30
+ .optional(),
31
+ confidence: z.number().min(0).max(1).optional(),
32
+ tags: z.array(z.string().min(1)).optional(),
33
+ });
34
+
35
+ const HELP = `Usage: atlas add <text...> [options]
36
+
37
+ Save a NEW personal memory. The text is split into atomic memories, each
38
+ matched against existing memories, and either created, updated, or left
39
+ unchanged based on similarity. The LLM infers a per-atom type from the
40
+ extraction; --type is an optional caller-level fallback.
41
+
42
+ Options:
43
+ --type <enum> Optional fallback: relationship | preference | fact |
44
+ decision | learning | event | instruction | observation |
45
+ error
46
+ --title <str> Optional short title (max 50 chars).
47
+ --confidence <0-1> Optional confidence in the memory. Default: 0.6
48
+ --tags a,b,c Comma-separated tags. Repeat to add more.
49
+ --json Emit the raw result object instead of a summary.
50
+ `;
51
+
52
+ export const meta = {
53
+ name: "add",
54
+ help: HELP,
55
+ options: ["type", "title", "confidence", "tags", "json"],
56
+ };
57
+
58
+ export async function run({ positional, flags, deps, json }) {
59
+ const text = positional.join(" ").trim();
60
+ if (!text) {
61
+ printError("add requires text. Try: atlas add \"I prefer dark roast coffee\"");
62
+ return { exitCode: 2 };
63
+ }
64
+
65
+ const parsed = AddInputSchema.safeParse({
66
+ text,
67
+ type: flags.type,
68
+ title: flags.title,
69
+ confidence: flags.confidence,
70
+ tags: flags.tags,
71
+ });
72
+ if (!parsed.success) {
73
+ const issue = parsed.error.issues[0];
74
+ printError(`${issue.path.join(".") || "input"}: ${issue.message}`);
75
+ return { exitCode: 2 };
76
+ }
77
+
78
+ const { type, title, confidence, tags } = parsed.data;
79
+ const metadata = {};
80
+ if (type !== undefined) metadata.type = type;
81
+ if (title !== undefined) metadata.title = title;
82
+ if (confidence !== undefined) metadata.confidence = confidence;
83
+ if (tags !== undefined) metadata.tags = tags;
84
+
85
+ try {
86
+ await deps.getModel();
87
+ const result = await deps.ingestionService.ingest({
88
+ text: parsed.data.text,
89
+ source: "cli",
90
+ metadata,
91
+ });
92
+
93
+ if (json) {
94
+ printJson(result);
95
+ } else {
96
+ console.log(formatAddSummary(result.memories));
97
+ }
98
+ return { exitCode: 0 };
99
+ } catch (error) {
100
+ printError(`Could not add memory: ${error.message}`);
101
+ return { exitCode: 1 };
102
+ }
103
+ }
@@ -0,0 +1,228 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ printError,
5
+ printJson,
6
+ } from "../format.js";
7
+ import {
8
+ readEnvFile,
9
+ updateEnvValue,
10
+ maskValue,
11
+ isSecretKey,
12
+ } from "../env-file.js";
13
+
14
+ const KNOWN_PROVIDERS = ["tokenrouter", "openrouter"];
15
+
16
+ const CONFIG_KEYS = [
17
+ { key: "LLM_PROVIDER", description: "LLM backend", default: "tokenrouter" },
18
+ { key: "LLM_MODEL", description: "Model override", default: "(provider default)" },
19
+ { key: "TOKENROUTER_API_KEY", description: "TokenRouter API key" },
20
+ { key: "OPENROUTER_API_KEY", description: "OpenRouter API key" },
21
+ { key: "PORT", description: "HTTP server port", default: "3001" },
22
+ { key: "LOG_LEVEL", description: "Log level", default: "info" },
23
+ { key: "ENGRAM_DB_PATH", description: "SQLite database path", default: "./engram.db" },
24
+ { key: "EMBEDDING_MODEL", description: "Embedding model", default: "Xenova/all-MiniLM-L6-v2" },
25
+ { key: "EMBEDDING_DIMENSIONS", description: "Embedding dimensions", default: "384" },
26
+ { key: "EMBEDDING_DTYPE", description: "Embedding quantization", default: "q8" },
27
+ { key: "ATLAS_MODE", description: "Runtime mode", default: "local" },
28
+ { key: "AUTH_ENABLED", description: "Auth enabled", default: "false" },
29
+ { key: "SUPABASE_URL", description: "Supabase project URL" },
30
+ { key: "SUPABASE_PUBLISHABLE_KEY", description: "Supabase anon key" },
31
+ ];
32
+
33
+ const HELP = `Usage: atlas config [subcommand] [args]
34
+
35
+ View and edit Atlas configuration (.env file).
36
+
37
+ Subcommands:
38
+ (none) Show all current config values
39
+ get <KEY> Get a single config value
40
+ set <KEY> <VALUE> Set a config value in .env
41
+ provider [name] Show or switch LLM provider (tokenrouter|openrouter)
42
+ model [name] Show or set model override (LLM_MODEL)
43
+ apikey <provider> <key> Set API key for a provider
44
+
45
+ Examples:
46
+ atlas config Show all config
47
+ atlas config get LLM_PROVIDER Get one value
48
+ atlas config set PORT 4000 Change port
49
+ atlas config provider openrouter Switch provider
50
+ atlas config model gpt-4o Override model
51
+ atlas config apikey openrouter sk-or-v1-... Set API key
52
+ `;
53
+
54
+ export const meta = {
55
+ name: "config",
56
+ help: HELP,
57
+ options: ["json"],
58
+ };
59
+
60
+ function resolveEnvPath() {
61
+ return resolve(process.cwd(), ".env");
62
+ }
63
+
64
+ function envPath() {
65
+ const p = resolveEnvPath();
66
+ return existsSync(p) ? p : null;
67
+ }
68
+
69
+ function showConfig(json) {
70
+ const filePath = envPath();
71
+ const fileRecords = filePath ? readEnvFile(filePath) : {};
72
+ const rows = [];
73
+
74
+ for (const def of CONFIG_KEYS) {
75
+ const fileVal = fileRecords[def.key];
76
+ const envVal = process.env[def.key];
77
+ const raw = envVal || fileVal || "";
78
+ const source = envVal ? "env" : fileVal ? ".env" : "default";
79
+ const display = raw || def.default || "";
80
+ const masked = isSecretKey(def.key) && raw ? maskValue(raw) : display;
81
+ rows.push({ key: def.key, value: masked, raw, source, description: def.description });
82
+ }
83
+
84
+ if (json) {
85
+ const out = {};
86
+ for (const r of rows) {
87
+ if (r.raw) out[r.key] = r.raw;
88
+ }
89
+ printJson(out);
90
+ return;
91
+ }
92
+
93
+ if (!filePath) {
94
+ console.log("No .env file found. Run 'atlas config set <KEY> <VALUE>' to create one.\n");
95
+ }
96
+
97
+ const keyW = Math.max(4, ...rows.map((r) => r.key.length));
98
+ const valW = Math.max(5, ...rows.map((r) => r.value.length));
99
+
100
+ const header = [
101
+ "KEY".padEnd(keyW),
102
+ "VALUE".padEnd(valW),
103
+ "SOURCE",
104
+ ].join(" ");
105
+ console.log(header);
106
+ console.log("─".repeat(header.length));
107
+
108
+ for (const r of rows) {
109
+ const display = isSecretKey(r.key) && r.raw
110
+ ? `\x1b[2m${r.value}\x1b[0m`
111
+ : r.value;
112
+ const visibleLen = r.value.length;
113
+ const padded = visibleLen < valW ? display + " ".repeat(valW - visibleLen) : display;
114
+ console.log(`${r.key.padEnd(keyW)} ${padded} ${r.source}`);
115
+ }
116
+ }
117
+
118
+ function getConfig(key, json) {
119
+ const filePath = envPath();
120
+ const fileRecords = filePath ? readEnvFile(filePath) : {};
121
+ const value = process.env[key] || fileRecords[key] || "";
122
+ if (json) {
123
+ printJson({ key, value });
124
+ } else if (value) {
125
+ const display = isSecretKey(key) ? maskValue(value) : value;
126
+ console.log(`${key}=${display}`);
127
+ } else {
128
+ console.log(`${key} (not set)`);
129
+ }
130
+ }
131
+
132
+ function setConfig(key, value) {
133
+ const filePath = envPath() || resolve(process.cwd(), ".env");
134
+ updateEnvValue(filePath, key, value);
135
+ if (value) {
136
+ const display = isSecretKey(key) ? maskValue(value) : value;
137
+ console.log(`Set ${key}=${display} in .env`);
138
+ } else {
139
+ console.log(`Removed ${key} from .env`);
140
+ }
141
+ }
142
+
143
+ function switchProvider(name) {
144
+ if (!name) {
145
+ const current = process.env.LLM_PROVIDER || "tokenrouter";
146
+ console.log(current);
147
+ return;
148
+ }
149
+ if (!KNOWN_PROVIDERS.includes(name)) {
150
+ printError(`Unknown provider "${name}". Valid: ${KNOWN_PROVIDERS.join(", ")}`);
151
+ return;
152
+ }
153
+ setConfig("LLM_PROVIDER", name);
154
+ console.log(`Restart the server to apply.`);
155
+ }
156
+
157
+ function setModel(name) {
158
+ if (!name) {
159
+ const current = process.env.LLM_MODEL || "(provider default)";
160
+ console.log(current);
161
+ return;
162
+ }
163
+ setConfig("LLM_MODEL", name);
164
+ console.log(`Restart the server to apply.`);
165
+ }
166
+
167
+ function setApiKey(provider, key) {
168
+ if (!provider || !key) {
169
+ printError("Usage: atlas config apikey <provider> <key>");
170
+ return;
171
+ }
172
+ if (!KNOWN_PROVIDERS.includes(provider)) {
173
+ printError(`Unknown provider "${provider}". Valid: ${KNOWN_PROVIDERS.join(", ")}`);
174
+ return;
175
+ }
176
+ const envKey = provider === "tokenrouter"
177
+ ? "TOKENROUTER_API_KEY"
178
+ : "OPENROUTER_API_KEY";
179
+ setConfig(envKey, key);
180
+ }
181
+
182
+ export async function run({ positional, flags, json }) {
183
+ const [sub, ...rest] = positional;
184
+
185
+ if (!sub || sub === "show") {
186
+ showConfig(json);
187
+ return { exitCode: 0 };
188
+ }
189
+
190
+ if (sub === "get") {
191
+ const key = rest[0];
192
+ if (!key) {
193
+ printError("Usage: atlas config get <KEY>");
194
+ return { exitCode: 2 };
195
+ }
196
+ getConfig(key, json);
197
+ return { exitCode: 0 };
198
+ }
199
+
200
+ if (sub === "set") {
201
+ const key = rest[0];
202
+ const value = rest[1];
203
+ if (!key || value === undefined) {
204
+ printError("Usage: atlas config set <KEY> <VALUE>");
205
+ return { exitCode: 2 };
206
+ }
207
+ setConfig(key, value);
208
+ return { exitCode: 0 };
209
+ }
210
+
211
+ if (sub === "provider") {
212
+ switchProvider(rest[0]);
213
+ return { exitCode: 0 };
214
+ }
215
+
216
+ if (sub === "model") {
217
+ setModel(rest[0]);
218
+ return { exitCode: 0 };
219
+ }
220
+
221
+ if (sub === "apikey") {
222
+ setApiKey(rest[0], rest[1]);
223
+ return { exitCode: 0 };
224
+ }
225
+
226
+ printError(`Unknown subcommand "${sub}". Run 'atlas config --help' for usage.`);
227
+ return { exitCode: 2 };
228
+ }
@@ -0,0 +1,75 @@
1
+ import {
2
+ formatJson,
3
+ printError,
4
+ printJson,
5
+ } from "../format.js";
6
+
7
+ const HELP = `Usage: atlas delete <id> [--yes]
8
+
9
+ Permanently delete a memory and its vector. Prompts for confirmation on a
10
+ TTY; use --yes to skip the prompt (recommended for scripts).
11
+ `;
12
+
13
+ export const meta = {
14
+ name: "delete",
15
+ help: HELP,
16
+ options: ["yes"],
17
+ };
18
+
19
+ async function confirm(id) {
20
+ if (!process.stdout.isTTY) return false;
21
+ process.stdout.write(`Delete memory ${id}? [y/N] `);
22
+ return new Promise((resolve) => {
23
+ let buffer = "";
24
+ const onData = (chunk) => {
25
+ buffer += chunk.toString();
26
+ if (buffer.includes("\n")) {
27
+ process.stdin.removeListener("data", onData);
28
+ process.stdin.pause();
29
+ const answer = buffer.trim().toLowerCase();
30
+ resolve(answer === "y" || answer === "yes");
31
+ }
32
+ };
33
+ process.stdin.resume();
34
+ process.stdin.setEncoding("utf8");
35
+ process.stdin.on("data", onData);
36
+ });
37
+ }
38
+
39
+ export async function run({ positional, flags, deps, json }) {
40
+ const [id] = positional;
41
+ if (!id) {
42
+ printError("delete requires a memory ID.");
43
+ return { exitCode: 2 };
44
+ }
45
+
46
+ try {
47
+ if (!deps.getMemory(id)) {
48
+ printError(`Memory not found: ${id}`);
49
+ return { exitCode: 1 };
50
+ }
51
+ if (!flags.yes) {
52
+ const ok = await confirm(id);
53
+ if (!ok) {
54
+ console.log("Aborted.");
55
+ return { exitCode: 0 };
56
+ }
57
+ }
58
+ deps.deleteMemory(id);
59
+ try {
60
+ await deps.deleteMemoryVector(id);
61
+ } catch (error) {
62
+ printError(`Could not delete vector for ${id}: ${error.message}`);
63
+ }
64
+ const result = { ok: true, deletedMemoryId: id };
65
+ if (json) {
66
+ printJson(result);
67
+ } else {
68
+ console.log(`Deleted ${id}.`);
69
+ }
70
+ return { exitCode: 0 };
71
+ } catch (error) {
72
+ printError(`Could not delete memory: ${error.message}`);
73
+ return { exitCode: 1 };
74
+ }
75
+ }
@@ -0,0 +1,39 @@
1
+ import {
2
+ formatEntitiesTable,
3
+ printError,
4
+ printJson,
5
+ } from "../format.js";
6
+
7
+ const HELP = `Usage: atlas entities <query>
8
+
9
+ Look up canonical entities (people, places, objects, concepts, organizations)
10
+ by partial name. Use the returned numeric ID with \`atlas entity <id>\` to
11
+ list every memory linked to it.
12
+ `;
13
+
14
+ export const meta = {
15
+ name: "entities",
16
+ help: HELP,
17
+ options: ["json"],
18
+ };
19
+
20
+ export async function run({ positional, flags, deps, json }) {
21
+ const query = positional.join(" ").trim();
22
+ if (!query) {
23
+ printError("entities requires a query. Try: atlas entities Maya");
24
+ return { exitCode: 2 };
25
+ }
26
+
27
+ try {
28
+ const entities = deps.findEntities(query);
29
+ if (json) {
30
+ printJson({ entities });
31
+ } else {
32
+ console.log(formatEntitiesTable(entities));
33
+ }
34
+ return { exitCode: 0 };
35
+ } catch (error) {
36
+ printError(`Could not find entities: ${error.message}`);
37
+ return { exitCode: 1 };
38
+ }
39
+ }
@@ -0,0 +1,47 @@
1
+ import {
2
+ formatJson,
3
+ formatMemoriesTable,
4
+ printError,
5
+ printJson,
6
+ } from "../format.js";
7
+
8
+ const HELP = `Usage: atlas entity <id>
9
+
10
+ List every memory linked to a specific entity. Pass the numeric entity ID
11
+ returned by \`atlas entities <query>\`.
12
+
13
+ Options:
14
+ --json Emit the raw memory array.
15
+ `;
16
+
17
+ export const meta = {
18
+ name: "entity",
19
+ help: HELP,
20
+ options: ["json"],
21
+ };
22
+
23
+ export async function run({ positional, flags, deps, json }) {
24
+ const [raw] = positional;
25
+ if (!raw) {
26
+ printError("entity requires a numeric entity ID.");
27
+ return { exitCode: 2 };
28
+ }
29
+ const entityId = Number.parseInt(raw, 10);
30
+ if (!Number.isInteger(entityId) || entityId <= 0) {
31
+ printError(`entity ID must be a positive integer, got: ${raw}`);
32
+ return { exitCode: 2 };
33
+ }
34
+
35
+ try {
36
+ const memories = deps.getMemoriesForEntity(entityId);
37
+ if (json) {
38
+ printJson({ entityId, memories });
39
+ } else {
40
+ console.log(formatMemoriesTable(memories));
41
+ }
42
+ return { exitCode: 0 };
43
+ } catch (error) {
44
+ printError(`Could not get entity memories: ${error.message}`);
45
+ return { exitCode: 1 };
46
+ }
47
+ }
@@ -0,0 +1,46 @@
1
+ import {
2
+ formatJson,
3
+ formatMemoryDetail,
4
+ printError,
5
+ printJson,
6
+ } from "../format.js";
7
+
8
+ const HELP = `Usage: atlas get <id> [options]
9
+
10
+ Fetch one memory by ID with its full extraction (entities, relationships,
11
+ regions).
12
+
13
+ Options:
14
+ --json Emit the raw memory object.
15
+ `;
16
+
17
+ export const meta = {
18
+ name: "get",
19
+ help: HELP,
20
+ options: ["json"],
21
+ };
22
+
23
+ export async function run({ positional, flags, deps, json }) {
24
+ const [id] = positional;
25
+ if (!id) {
26
+ printError("get requires a memory ID. Try: atlas get mem_12ab34cd");
27
+ return { exitCode: 2 };
28
+ }
29
+
30
+ try {
31
+ const memory = deps.serializeMemory(deps.getMemory(id));
32
+ if (!memory || !memory.id) {
33
+ printError(`Memory not found: ${id}`);
34
+ return { exitCode: 1 };
35
+ }
36
+ if (json) {
37
+ printJson(memory);
38
+ } else {
39
+ console.log(formatMemoryDetail(memory));
40
+ }
41
+ return { exitCode: 0 };
42
+ } catch (error) {
43
+ printError(`Could not get memory: ${error.message}`);
44
+ return { exitCode: 1 };
45
+ }
46
+ }