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.
- package/.env.example +32 -0
- package/README.md +282 -0
- package/package.json +72 -0
- package/public/app/assets/app-CxbS1w9p.js +3981 -0
- package/public/app/assets/index-BA6nxCuI.css +1 -0
- package/public/app/assets/index-BXmIRrQH.js +177 -0
- package/public/app/index.html +27 -0
- package/public/assets/brain-atlas.LICENSE.txt +16 -0
- package/public/assets/brain-atlas.glb +0 -0
- package/public/assets/brain.obj +27282 -0
- package/public/fonts/DepartureMono-Regular.woff +0 -0
- package/public/fonts/DepartureMono-Regular.woff2 +0 -0
- package/scripts/sync-memory-vectors.js +46 -0
- package/src/audit.js +9 -0
- package/src/cli/args.js +87 -0
- package/src/cli/commands/add.js +103 -0
- package/src/cli/commands/config.js +228 -0
- package/src/cli/commands/delete.js +75 -0
- package/src/cli/commands/entities.js +39 -0
- package/src/cli/commands/entity.js +47 -0
- package/src/cli/commands/get.js +46 -0
- package/src/cli/commands/list.js +53 -0
- package/src/cli/commands/related.js +56 -0
- package/src/cli/commands/search.js +68 -0
- package/src/cli/commands/update.js +58 -0
- package/src/cli/deps.js +114 -0
- package/src/cli/env-file.js +44 -0
- package/src/cli/format.js +246 -0
- package/src/cli.js +187 -0
- package/src/cognitive-worker.js +381 -0
- package/src/db.js +2674 -0
- package/src/extraction-context.js +31 -0
- package/src/ingestion-service.js +387 -0
- package/src/ingestion-worker.js +225 -0
- package/src/llm-config.js +31 -0
- package/src/llm.js +789 -0
- package/src/logger.js +51 -0
- package/src/mcp-server.js +577 -0
- package/src/memory-comparison.js +421 -0
- package/src/related-memories.js +232 -0
- package/src/run-cognitive-worker.js +12 -0
- package/src/run-ingestion-worker.js +13 -0
- package/src/run-vector-worker.js +12 -0
- package/src/schemas.js +413 -0
- package/src/semantic-validation.js +430 -0
- package/src/server.js +827 -0
- package/src/shared/brain-regions.js +61 -0
- package/src/shared/entity-lens.js +249 -0
- package/src/shared/memory-placement.js +171 -0
- package/src/shared/memory-search.js +55 -0
- package/src/shared/region-anchors.js +112 -0
- package/src/shared/region-mapper.js +247 -0
- package/src/vector-store.js +546 -0
- package/src/vector-worker.js +71 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatJson,
|
|
3
|
+
formatMemoriesTable,
|
|
4
|
+
printError,
|
|
5
|
+
printJson,
|
|
6
|
+
} from "../format.js";
|
|
7
|
+
|
|
8
|
+
const HELP = `Usage: atlas list [options]
|
|
9
|
+
|
|
10
|
+
List recently stored memories, newest first.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--limit <n> Page size, 1-100. Default: 20
|
|
14
|
+
--offset <n> Pagination offset. Default: 0
|
|
15
|
+
--source <s> Filter by source: ui | mcp | cli
|
|
16
|
+
--json Emit the raw memory array.
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const meta = {
|
|
20
|
+
name: "list",
|
|
21
|
+
help: HELP,
|
|
22
|
+
options: ["limit", "offset", "source", "json"],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export async function run({ positional, flags, deps, json }) {
|
|
26
|
+
const limit = Number.isFinite(flags.limit) ? flags.limit : 20;
|
|
27
|
+
const offset = Number.isFinite(flags.offset) ? flags.offset : 0;
|
|
28
|
+
if (limit < 1 || limit > 100) {
|
|
29
|
+
printError("--limit must be between 1 and 100");
|
|
30
|
+
return { exitCode: 2 };
|
|
31
|
+
}
|
|
32
|
+
if (offset < 0) {
|
|
33
|
+
printError("--offset must be >= 0");
|
|
34
|
+
return { exitCode: 2 };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const memories = deps.getMemories({
|
|
39
|
+
limit,
|
|
40
|
+
offset,
|
|
41
|
+
source: flags.source,
|
|
42
|
+
});
|
|
43
|
+
if (json) {
|
|
44
|
+
printJson(memories);
|
|
45
|
+
} else {
|
|
46
|
+
console.log(formatMemoriesTable(memories));
|
|
47
|
+
}
|
|
48
|
+
return { exitCode: 0 };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
printError(`Could not list memories: ${error.message}`);
|
|
51
|
+
return { exitCode: 1 };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatJson,
|
|
3
|
+
formatRelatedList,
|
|
4
|
+
printError,
|
|
5
|
+
printJson,
|
|
6
|
+
} from "../format.js";
|
|
7
|
+
|
|
8
|
+
const HELP = `Usage: atlas related <id> [options]
|
|
9
|
+
|
|
10
|
+
Find memories connected to <id> through shared entities, relationships,
|
|
11
|
+
semantic similarity, and BM25 keyword matches.
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
--limit <n> Max results, 1-20. Default: 5
|
|
15
|
+
--threshold <f> Minimum similarity score in -1..1. Default: 0.65
|
|
16
|
+
--json Emit the raw result.
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const meta = {
|
|
20
|
+
name: "related",
|
|
21
|
+
help: HELP,
|
|
22
|
+
options: ["limit", "threshold", "json"],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export async function run({ positional, flags, deps, json }) {
|
|
26
|
+
const [id] = positional;
|
|
27
|
+
if (!id) {
|
|
28
|
+
printError("related requires a memory ID.");
|
|
29
|
+
return { exitCode: 2 };
|
|
30
|
+
}
|
|
31
|
+
const limit = Number.isFinite(flags.limit) ? flags.limit : 5;
|
|
32
|
+
if (limit < 1 || limit > 20) {
|
|
33
|
+
printError("--limit must be between 1 and 20");
|
|
34
|
+
return { exitCode: 2 };
|
|
35
|
+
}
|
|
36
|
+
const scoreThreshold = Number.isFinite(flags.threshold)
|
|
37
|
+
? flags.threshold
|
|
38
|
+
: 0.65;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const result = await deps.getRelatedMemories(id, { limit, scoreThreshold });
|
|
42
|
+
if (!result) {
|
|
43
|
+
printError(`Memory not found: ${id}`);
|
|
44
|
+
return { exitCode: 1 };
|
|
45
|
+
}
|
|
46
|
+
if (json) {
|
|
47
|
+
printJson(result);
|
|
48
|
+
} else {
|
|
49
|
+
console.log(formatRelatedList(result));
|
|
50
|
+
}
|
|
51
|
+
return { exitCode: 0 };
|
|
52
|
+
} catch (error) {
|
|
53
|
+
printError(`Could not get related memories: ${error.message}`);
|
|
54
|
+
return { exitCode: 1 };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatJson,
|
|
3
|
+
formatMemoriesTable,
|
|
4
|
+
printError,
|
|
5
|
+
printJson,
|
|
6
|
+
} from "../format.js";
|
|
7
|
+
|
|
8
|
+
const HELP = `Usage: atlas search <query> [options]
|
|
9
|
+
|
|
10
|
+
Find memories using hybrid search (semantic + keyword).
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--limit <n> Max results, 1-100. Default: 20
|
|
14
|
+
--threshold <f> Minimum similarity score in -1..1.
|
|
15
|
+
--strategy <name> hybrid (default) | vector | bm25
|
|
16
|
+
--json Emit the raw hits.
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const meta = {
|
|
20
|
+
name: "search",
|
|
21
|
+
help: HELP,
|
|
22
|
+
options: ["limit", "threshold", "strategy", "json"],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const STRATEGIES = new Set(["hybrid", "vector", "bm25"]);
|
|
26
|
+
|
|
27
|
+
export async function run({ positional, flags, deps, json }) {
|
|
28
|
+
const query = positional.join(" ").trim();
|
|
29
|
+
if (!query) {
|
|
30
|
+
printError("search requires a query. Try: atlas search \"coffee preference\"");
|
|
31
|
+
return { exitCode: 2 };
|
|
32
|
+
}
|
|
33
|
+
const limit = Number.isFinite(flags.limit) ? flags.limit : 20;
|
|
34
|
+
if (limit < 1 || limit > 100) {
|
|
35
|
+
printError("--limit must be between 1 and 100");
|
|
36
|
+
return { exitCode: 2 };
|
|
37
|
+
}
|
|
38
|
+
const strategy = flags.strategy || "hybrid";
|
|
39
|
+
if (!STRATEGIES.has(strategy)) {
|
|
40
|
+
printError(`--strategy must be one of: ${[...STRATEGIES].join(", ")}`);
|
|
41
|
+
return { exitCode: 2 };
|
|
42
|
+
}
|
|
43
|
+
const scoreThreshold = Number.isFinite(flags.threshold)
|
|
44
|
+
? flags.threshold
|
|
45
|
+
: undefined;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const hits = await deps.hybridSearchMemories(query, {
|
|
49
|
+
limit,
|
|
50
|
+
scoreThreshold,
|
|
51
|
+
strategy,
|
|
52
|
+
});
|
|
53
|
+
const memories = hits.flatMap(({ id, score }) => {
|
|
54
|
+
const memory = deps.serializeMemory(deps.getMemory(id));
|
|
55
|
+
return memory && memory.id ? [{ ...memory, rrfScore: score }] : [];
|
|
56
|
+
});
|
|
57
|
+
const payload = { query, strategy, memories };
|
|
58
|
+
if (json) {
|
|
59
|
+
printJson(payload);
|
|
60
|
+
} else {
|
|
61
|
+
console.log(formatMemoriesTable(memories));
|
|
62
|
+
}
|
|
63
|
+
return { exitCode: 0 };
|
|
64
|
+
} catch (error) {
|
|
65
|
+
printError(`Could not search memories: ${error.message}`);
|
|
66
|
+
return { exitCode: 1 };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatJson,
|
|
3
|
+
formatMemoryDetail,
|
|
4
|
+
printError,
|
|
5
|
+
printJson,
|
|
6
|
+
} from "../format.js";
|
|
7
|
+
|
|
8
|
+
const HELP = `Usage: atlas update <id> --summary <text>
|
|
9
|
+
|
|
10
|
+
Replace the editable summary of an existing memory. The original raw text,
|
|
11
|
+
type, and extraction graph are preserved. The vector embedding is reindexed
|
|
12
|
+
after the update.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--summary <str> Replacement summary. Required.
|
|
16
|
+
--json Emit the refreshed memory object.
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const meta = {
|
|
20
|
+
name: "update",
|
|
21
|
+
help: HELP,
|
|
22
|
+
options: ["summary", "json"],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export async function run({ positional, flags, deps, json }) {
|
|
26
|
+
const [id] = positional;
|
|
27
|
+
if (!id) {
|
|
28
|
+
printError("update requires a memory ID.");
|
|
29
|
+
return { exitCode: 2 };
|
|
30
|
+
}
|
|
31
|
+
if (typeof flags.summary !== "string" || !flags.summary.trim()) {
|
|
32
|
+
printError("update requires --summary <text>.");
|
|
33
|
+
return { exitCode: 2 };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
if (!deps.getMemory(id)) {
|
|
38
|
+
printError(`Memory not found: ${id}`);
|
|
39
|
+
return { exitCode: 1 };
|
|
40
|
+
}
|
|
41
|
+
deps.updateMemorySummary(id, flags.summary);
|
|
42
|
+
try {
|
|
43
|
+
await deps.indexMemoryVector(deps.getMemory(id));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
printError(`Could not reindex memory ${id}: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
const memory = deps.serializeMemory(deps.getMemory(id));
|
|
48
|
+
if (json) {
|
|
49
|
+
printJson(memory);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(formatMemoryDetail(memory));
|
|
52
|
+
}
|
|
53
|
+
return { exitCode: 0 };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
printError(`Could not update memory: ${error.message}`);
|
|
56
|
+
return { exitCode: 1 };
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/cli/deps.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeDb,
|
|
3
|
+
deleteMemory,
|
|
4
|
+
findEntities,
|
|
5
|
+
getEntitiesForMemory,
|
|
6
|
+
getLatestExtraction,
|
|
7
|
+
getMemories,
|
|
8
|
+
getMemoriesForEntity,
|
|
9
|
+
getMemory,
|
|
10
|
+
getRegionActivations,
|
|
11
|
+
getRelationshipsForMemory,
|
|
12
|
+
getStructuralMemoryLinks,
|
|
13
|
+
searchMemoriesFts,
|
|
14
|
+
storeMemory,
|
|
15
|
+
updateMemoryGraph,
|
|
16
|
+
updateMemorySummary,
|
|
17
|
+
createMemorySource,
|
|
18
|
+
enqueueAnnotationJob,
|
|
19
|
+
enqueueVectorIndexJob,
|
|
20
|
+
getAnnotationStatus,
|
|
21
|
+
getMemorySource,
|
|
22
|
+
getSourceMemoryLinks,
|
|
23
|
+
getVectorIndexStatus,
|
|
24
|
+
linkSourceMemory,
|
|
25
|
+
updateMemorySourceStatus,
|
|
26
|
+
withTransaction,
|
|
27
|
+
} from "../db.js";
|
|
28
|
+
import {
|
|
29
|
+
deleteMemoryVector,
|
|
30
|
+
hybridSearchMemories,
|
|
31
|
+
indexMemoryVector,
|
|
32
|
+
searchMemoryVectors,
|
|
33
|
+
} from "../vector-store.js";
|
|
34
|
+
import { getRelatedMemories as deriveRelatedMemories } from "../related-memories.js";
|
|
35
|
+
import { retrieveExtractionContext } from "../extraction-context.js";
|
|
36
|
+
import { createIngestionService } from "../ingestion-service.js";
|
|
37
|
+
|
|
38
|
+
function serializeMemory(memory) {
|
|
39
|
+
if (!memory) return null;
|
|
40
|
+
return {
|
|
41
|
+
...memory,
|
|
42
|
+
extraction: getLatestExtraction(memory.id),
|
|
43
|
+
entities: getEntitiesForMemory(memory.id),
|
|
44
|
+
relationships: getRelationshipsForMemory(memory.id),
|
|
45
|
+
regions: getRegionActivations(memory.id),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function defaultDependencies() {
|
|
50
|
+
const dependencies = {
|
|
51
|
+
closeDb,
|
|
52
|
+
decideMemoryWrite: async (...args) => {
|
|
53
|
+
const mod = await import("../llm.js");
|
|
54
|
+
return mod.decideMemoryWrite(...args);
|
|
55
|
+
},
|
|
56
|
+
deleteMemory,
|
|
57
|
+
deleteMemoryVector,
|
|
58
|
+
extractAtomicMemories: async (...args) => {
|
|
59
|
+
const mod = await import("../llm.js");
|
|
60
|
+
return mod.extractAtomicMemories(...args);
|
|
61
|
+
},
|
|
62
|
+
findEntities,
|
|
63
|
+
getEntitiesForMemory,
|
|
64
|
+
getLatestExtraction,
|
|
65
|
+
getMemories,
|
|
66
|
+
getMemoriesForEntity,
|
|
67
|
+
getMemory,
|
|
68
|
+
getRegionActivations,
|
|
69
|
+
getRelationshipsForMemory,
|
|
70
|
+
getStructuralMemoryLinks,
|
|
71
|
+
hybridSearchMemories,
|
|
72
|
+
indexMemoryVector,
|
|
73
|
+
model: undefined,
|
|
74
|
+
retrieveExtractionContext,
|
|
75
|
+
searchMemoryVectors,
|
|
76
|
+
serializeMemory,
|
|
77
|
+
storeMemory,
|
|
78
|
+
updateMemoryGraph,
|
|
79
|
+
updateMemorySummary,
|
|
80
|
+
createMemorySource,
|
|
81
|
+
updateMemorySourceStatus,
|
|
82
|
+
getMemorySource,
|
|
83
|
+
getSourceMemoryLinks,
|
|
84
|
+
linkSourceMemory,
|
|
85
|
+
enqueueAnnotationJob,
|
|
86
|
+
enqueueVectorIndexJob,
|
|
87
|
+
getAnnotationStatus,
|
|
88
|
+
getVectorIndexStatus,
|
|
89
|
+
withTransaction,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
dependencies.getModel = async () => {
|
|
93
|
+
const mod = await import("../llm-config.js");
|
|
94
|
+
dependencies.model = mod.model;
|
|
95
|
+
return mod.model;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
dependencies.getRelatedMemories = (id, options) =>
|
|
99
|
+
deriveRelatedMemories(
|
|
100
|
+
id,
|
|
101
|
+
{
|
|
102
|
+
getMemory,
|
|
103
|
+
getStructuralMemoryLinks,
|
|
104
|
+
searchMemoryVectors,
|
|
105
|
+
searchMemoriesFts,
|
|
106
|
+
serializeMemory,
|
|
107
|
+
},
|
|
108
|
+
options,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
dependencies.ingestionService = createIngestionService(dependencies);
|
|
112
|
+
|
|
113
|
+
return dependencies;
|
|
114
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const LINE_RE = /^([A-Z_][A-Z0-9_]*)=(.*)$/;
|
|
4
|
+
|
|
5
|
+
export function readEnvFile(filePath) {
|
|
6
|
+
if (!existsSync(filePath)) return {};
|
|
7
|
+
const content = readFileSync(filePath, "utf8");
|
|
8
|
+
const records = {};
|
|
9
|
+
for (const line of content.split("\n")) {
|
|
10
|
+
const m = line.match(LINE_RE);
|
|
11
|
+
if (m) {
|
|
12
|
+
records[m[1]] = m[2];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return records;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function writeEnvFile(filePath, records) {
|
|
19
|
+
const lines = Object.entries(records).map(([k, v]) => `${k}=${v}`);
|
|
20
|
+
writeFileSync(filePath, lines.join("\n") + "\n", "utf8");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function updateEnvValue(filePath, key, value) {
|
|
24
|
+
const records = readEnvFile(filePath);
|
|
25
|
+
if (value === "" || value === undefined || value === null) {
|
|
26
|
+
delete records[key];
|
|
27
|
+
} else {
|
|
28
|
+
records[key] = value;
|
|
29
|
+
}
|
|
30
|
+
writeEnvFile(filePath, records);
|
|
31
|
+
return records;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const SECRET_PATTERNS = [/KEY/i, /SECRET/i, /TOKEN/i, /PASSWORD/i];
|
|
35
|
+
|
|
36
|
+
export function isSecretKey(key) {
|
|
37
|
+
return SECRET_PATTERNS.some((p) => p.test(key));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function maskValue(val) {
|
|
41
|
+
if (!val) return "";
|
|
42
|
+
if (val.length <= 8) return "***";
|
|
43
|
+
return val.slice(0, 3) + "***" + val.slice(-4);
|
|
44
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
const RESET = "\x1b[0m";
|
|
2
|
+
const DIM = "\x1b[2m";
|
|
3
|
+
const BOLD = "\x1b[1m";
|
|
4
|
+
const CYAN = "\x1b[36m";
|
|
5
|
+
const YELLOW = "\x1b[33m";
|
|
6
|
+
const RED = "\x1b[31m";
|
|
7
|
+
const GREEN = "\x1b[32m";
|
|
8
|
+
|
|
9
|
+
const useColor = process.stdout.isTTY && process.env.NO_COLOR === undefined;
|
|
10
|
+
|
|
11
|
+
function paint(color, text) {
|
|
12
|
+
return useColor ? `${color}${text}${RESET}` : text;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function pad(value, width) {
|
|
16
|
+
const str = String(value ?? "");
|
|
17
|
+
if (str.length >= width) return str;
|
|
18
|
+
return str + " ".repeat(width - str.length);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function truncate(str, max = 80) {
|
|
22
|
+
const s = String(str ?? "").replace(/\s+/g, " ").trim();
|
|
23
|
+
if (s.length <= max) return s;
|
|
24
|
+
return s.slice(0, Math.max(0, max - 1)) + "…";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatScore(score) {
|
|
28
|
+
if (score === undefined || score === null) return "-";
|
|
29
|
+
return score.toFixed(3);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatTimestamp(value) {
|
|
33
|
+
if (!value) return "-";
|
|
34
|
+
return String(value).replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatMemoryRow(memory) {
|
|
38
|
+
const id = memory.id || memory.memory_id || "-";
|
|
39
|
+
const type = memory.type || "-";
|
|
40
|
+
const title = truncate(memory.title || memory.summary || memory.raw_text || "", 40);
|
|
41
|
+
const summary = truncate(memory.summary || memory.raw_text || "", 80);
|
|
42
|
+
const score = memory.rrfScore ?? memory.score;
|
|
43
|
+
const scoreStr = score !== undefined ? formatScore(score) : "-";
|
|
44
|
+
return { id, type, title, summary, scoreStr };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatMemoriesTable(memories) {
|
|
48
|
+
if (!memories || memories.length === 0) {
|
|
49
|
+
return paint(DIM, "(no memories)");
|
|
50
|
+
}
|
|
51
|
+
const rows = memories.map(formatMemoryRow);
|
|
52
|
+
const widths = {
|
|
53
|
+
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
54
|
+
type: Math.max(4, ...rows.map((r) => r.type.length)),
|
|
55
|
+
score: Math.max(5, ...rows.map((r) => r.scoreStr.length)),
|
|
56
|
+
title: Math.max(5, ...rows.map((r) => r.title.length)),
|
|
57
|
+
};
|
|
58
|
+
const header = [
|
|
59
|
+
paint(BOLD, pad("ID", widths.id)),
|
|
60
|
+
paint(BOLD, pad("TYPE", widths.type)),
|
|
61
|
+
paint(BOLD, pad("SCORE", widths.score)),
|
|
62
|
+
paint(BOLD, pad("TITLE", widths.title)),
|
|
63
|
+
paint(BOLD, "SUMMARY"),
|
|
64
|
+
].join(" ");
|
|
65
|
+
const lines = [header, paint(DIM, "─".repeat(Math.min(120, header.length)))];
|
|
66
|
+
for (const row of rows) {
|
|
67
|
+
lines.push(
|
|
68
|
+
[
|
|
69
|
+
paint(CYAN, pad(row.id, widths.id)),
|
|
70
|
+
paint(YELLOW, pad(row.type, widths.type)),
|
|
71
|
+
pad(row.scoreStr, widths.score),
|
|
72
|
+
pad(row.title, widths.title),
|
|
73
|
+
row.summary,
|
|
74
|
+
].join(" "),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatMemoryDetail(memory) {
|
|
81
|
+
if (!memory) return paint(DIM, "(no memory)");
|
|
82
|
+
const lines = [];
|
|
83
|
+
lines.push(`${paint(BOLD, "ID")} ${paint(CYAN, memory.id || "-")}`);
|
|
84
|
+
lines.push(`${paint(BOLD, "Type")} ${memory.type || "-"}`);
|
|
85
|
+
lines.push(`${paint(BOLD, "Title")} ${memory.title || "-"}`);
|
|
86
|
+
lines.push(`${paint(BOLD, "Summary")} ${memory.summary || "-"}`);
|
|
87
|
+
lines.push(`${paint(BOLD, "Confidence")} ${memory.confidence ?? "-"}`);
|
|
88
|
+
if (memory.tags && memory.tags.length) {
|
|
89
|
+
lines.push(`${paint(BOLD, "Tags")} ${memory.tags.join(", ")}`);
|
|
90
|
+
}
|
|
91
|
+
lines.push(
|
|
92
|
+
`${paint(BOLD, "Created")} ${formatTimestamp(memory.created_at || memory.createdAt)}`,
|
|
93
|
+
);
|
|
94
|
+
lines.push(
|
|
95
|
+
`${paint(BOLD, "Updated")} ${formatTimestamp(memory.updated_at || memory.updatedAt)}`,
|
|
96
|
+
);
|
|
97
|
+
lines.push("");
|
|
98
|
+
lines.push(paint(BOLD, "Raw text"));
|
|
99
|
+
lines.push(memory.raw_text || "-");
|
|
100
|
+
|
|
101
|
+
if (memory.extraction) {
|
|
102
|
+
lines.push("");
|
|
103
|
+
lines.push(paint(BOLD, "Extraction"));
|
|
104
|
+
if (memory.extraction.summary) {
|
|
105
|
+
lines.push(` summary: ${memory.extraction.summary}`);
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(memory.extraction.types) && memory.extraction.types.length) {
|
|
108
|
+
const ts = memory.extraction.types
|
|
109
|
+
.map((t) => `${t.type || t.name || "?"}:${t.weight ?? "?"}`)
|
|
110
|
+
.join(", ");
|
|
111
|
+
lines.push(` types: ${ts}`);
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(memory.extraction.entities) && memory.extraction.entities.length) {
|
|
114
|
+
lines.push(` entities (${memory.extraction.entities.length}):`);
|
|
115
|
+
for (const e of memory.extraction.entities.slice(0, 10)) {
|
|
116
|
+
lines.push(` - ${e.name || e.canonical_name || e.text} (${e.kind || "?"})`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(memory.entities) && memory.entities.length) {
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push(paint(BOLD, `Entities (${memory.entities.length})`));
|
|
124
|
+
for (const e of memory.entities.slice(0, 10)) {
|
|
125
|
+
lines.push(` - ${e.canonical_name || e.name} (${e.kind || "?"}) [${e.id}]`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (Array.isArray(memory.relationships) && memory.relationships.length) {
|
|
130
|
+
lines.push("");
|
|
131
|
+
lines.push(paint(BOLD, `Relationships (${memory.relationships.length})`));
|
|
132
|
+
for (const r of memory.relationships.slice(0, 10)) {
|
|
133
|
+
const source = r.source?.canonical_name || r.source_name || "?";
|
|
134
|
+
const target = r.target?.canonical_name || r.target_name || "?";
|
|
135
|
+
lines.push(` - ${source} ${r.predicate || "?"} ${target}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (Array.isArray(memory.regions) && memory.regions.length) {
|
|
140
|
+
lines.push("");
|
|
141
|
+
lines.push(paint(BOLD, `Regions (${memory.regions.length})`));
|
|
142
|
+
for (const r of memory.regions.slice(0, 10)) {
|
|
143
|
+
lines.push(` - ${r.region}: ${r.weight ?? "?"}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return lines.join("\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatRelatedList(result) {
|
|
151
|
+
if (!result || !Array.isArray(result.links) || result.links.length === 0) {
|
|
152
|
+
return paint(DIM, "(no related memories)");
|
|
153
|
+
}
|
|
154
|
+
const lines = [`${paint(BOLD, `Related to ${result.memoryId}`)}`, ""];
|
|
155
|
+
result.links.forEach((link, index) => {
|
|
156
|
+
const id = link.memoryId || link.id || "?";
|
|
157
|
+
const score = link.score !== undefined ? formatScore(link.score) : "-";
|
|
158
|
+
const title = link.title || link.summary || link.rawText || "";
|
|
159
|
+
const reasons = Array.isArray(link.reasons) && link.reasons.length
|
|
160
|
+
? ` (${link.reasons.join(", ")})`
|
|
161
|
+
: "";
|
|
162
|
+
lines.push(
|
|
163
|
+
`${paint(CYAN, `${index + 1}.`)} ${id} ${paint(DIM, score)}${reasons}`,
|
|
164
|
+
);
|
|
165
|
+
if (title) lines.push(` ${truncate(title, 100)}`);
|
|
166
|
+
});
|
|
167
|
+
return lines.join("\n");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function formatEntitiesTable(entities) {
|
|
171
|
+
if (!entities || entities.length === 0) {
|
|
172
|
+
return paint(DIM, "(no matching entities)");
|
|
173
|
+
}
|
|
174
|
+
const rows = entities.map((e) => ({
|
|
175
|
+
id: String(e.id ?? "?"),
|
|
176
|
+
name: e.canonical_name || e.name || "?",
|
|
177
|
+
kind: e.kind || "?",
|
|
178
|
+
aliases: Array.isArray(e.aliases) ? e.aliases.length : 0,
|
|
179
|
+
}));
|
|
180
|
+
const widths = {
|
|
181
|
+
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
182
|
+
name: Math.max(4, ...rows.map((r) => r.name.length)),
|
|
183
|
+
kind: Math.max(4, ...rows.map((r) => r.kind.length)),
|
|
184
|
+
aliases: Math.max(7, ...rows.map((r) => String(r.aliases).length)),
|
|
185
|
+
};
|
|
186
|
+
const header = [
|
|
187
|
+
paint(BOLD, pad("ID", widths.id)),
|
|
188
|
+
paint(BOLD, pad("NAME", widths.name)),
|
|
189
|
+
paint(BOLD, pad("KIND", widths.kind)),
|
|
190
|
+
paint(BOLD, pad("ALIASES", widths.aliases)),
|
|
191
|
+
].join(" ");
|
|
192
|
+
const lines = [header, paint(DIM, "─".repeat(header.length))];
|
|
193
|
+
for (const row of rows) {
|
|
194
|
+
lines.push(
|
|
195
|
+
[
|
|
196
|
+
paint(CYAN, pad(row.id, widths.id)),
|
|
197
|
+
pad(row.name, widths.name),
|
|
198
|
+
paint(YELLOW, pad(row.kind, widths.kind)),
|
|
199
|
+
pad(String(row.aliases), widths.aliases),
|
|
200
|
+
].join(" "),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return lines.join("\n");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function formatAddSummary(results) {
|
|
207
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
208
|
+
return paint(DIM, "(no memories)");
|
|
209
|
+
}
|
|
210
|
+
const lines = [];
|
|
211
|
+
for (const r of results) {
|
|
212
|
+
const id = r.memory?.id || r.matchedMemoryId || "?";
|
|
213
|
+
const color = r.action === "created"
|
|
214
|
+
? GREEN
|
|
215
|
+
: r.action === "updated"
|
|
216
|
+
? YELLOW
|
|
217
|
+
: DIM;
|
|
218
|
+
lines.push(
|
|
219
|
+
`${paint(color, r.action.padEnd(10))} ${paint(CYAN, id)} ${truncate(r.reason || "", 80)}`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return lines.join("\n");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function formatJson(data) {
|
|
226
|
+
return JSON.stringify(data, null, 2);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function printJson(data) {
|
|
230
|
+
console.log(formatJson(data));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function printError(message) {
|
|
234
|
+
console.error(paint(RED, `Error: ${message}`));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export {
|
|
238
|
+
formatAddSummary,
|
|
239
|
+
formatEntitiesTable,
|
|
240
|
+
formatJson,
|
|
241
|
+
formatMemoryDetail,
|
|
242
|
+
formatMemoriesTable,
|
|
243
|
+
formatRelatedList,
|
|
244
|
+
printError,
|
|
245
|
+
printJson,
|
|
246
|
+
};
|