ex-brain 0.2.6 → 0.3.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/package.json +3 -1
- package/src/ai/ax-pipeline.ts +114 -0
- package/src/ai/compiler.ts +118 -113
- package/src/ai/entity-link.ts +96 -78
- package/src/ai/timeline-extractor.ts +110 -99
- package/src/commands/compile-cmd.ts +1 -1
- package/src/commands/entity-links.ts +105 -0
- package/src/commands/import-cmd.ts +464 -0
- package/src/commands/index.ts +30 -2194
- package/src/commands/misc-cmds.ts +190 -0
- package/src/commands/misc-commands.ts +252 -0
- package/src/commands/put-cmd.ts +525 -0
- package/src/commands/query-cmd.ts +486 -0
- package/src/commands/shared.ts +109 -0
- package/src/commands/timeline-cmd.ts +159 -0
- package/src/config/index.ts +53 -0
- package/src/config/init.ts +50 -0
- package/src/config/paths.ts +21 -0
- package/src/config/schema.ts +121 -0
- package/src/config/settings.ts +168 -0
- package/src/db/client.ts +1 -1
- package/src/markdown/document-loader.ts +514 -0
- package/src/mcp/server.ts +148 -0
- package/src/repositories/brain-repo.ts +43 -1
- package/src/settings.ts +27 -282
- /package/src/{config.ts → slug-utils.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nowIso } from "../
|
|
1
|
+
import { nowIso } from "../slug-utils";
|
|
2
2
|
import type {
|
|
3
3
|
BrainStats,
|
|
4
4
|
PageRecord,
|
|
@@ -630,6 +630,48 @@ export class BrainRepository {
|
|
|
630
630
|
}
|
|
631
631
|
}
|
|
632
632
|
|
|
633
|
+
/**
|
|
634
|
+
* Sync tags from frontmatter to the page_tags table.
|
|
635
|
+
* This ensures `ebrain list --tag <tag>` works correctly for pages
|
|
636
|
+
* created via `put` with frontmatter tags.
|
|
637
|
+
*/
|
|
638
|
+
async syncTagsFromFrontmatter(slug: string, frontmatter: Record<string, unknown>): Promise<number> {
|
|
639
|
+
const fmTags = frontmatter.tags;
|
|
640
|
+
if (!fmTags) return 0;
|
|
641
|
+
|
|
642
|
+
const tags: string[] = Array.isArray(fmTags)
|
|
643
|
+
? fmTags.filter((t): t is string => typeof t === "string")
|
|
644
|
+
: typeof fmTags === "string"
|
|
645
|
+
? [fmTags]
|
|
646
|
+
: [];
|
|
647
|
+
|
|
648
|
+
if (tags.length === 0) return 0;
|
|
649
|
+
|
|
650
|
+
// Get current DB tags for this page
|
|
651
|
+
const existingTags = new Set(await this.tags(slug));
|
|
652
|
+
const desiredTags = new Set(tags);
|
|
653
|
+
|
|
654
|
+
let synced = 0;
|
|
655
|
+
|
|
656
|
+
// Add missing tags
|
|
657
|
+
for (const tag of desiredTags) {
|
|
658
|
+
if (!existingTags.has(tag)) {
|
|
659
|
+
await this.tag(slug, tag);
|
|
660
|
+
synced++;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Remove tags no longer in frontmatter
|
|
665
|
+
for (const tag of existingTags) {
|
|
666
|
+
if (!desiredTags.has(tag)) {
|
|
667
|
+
await this.untag(slug, tag);
|
|
668
|
+
synced++;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return synced;
|
|
673
|
+
}
|
|
674
|
+
|
|
633
675
|
async readRaw(slug: string, source?: string): Promise<unknown[]> {
|
|
634
676
|
try {
|
|
635
677
|
const params: unknown[] = [slug];
|
package/src/settings.ts
CHANGED
|
@@ -1,284 +1,29 @@
|
|
|
1
|
-
import { homedir } from "node:os";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { fileExists, readTextFile } from "./markdown/io";
|
|
5
|
-
|
|
6
|
-
export const SETTINGS_DIR = join(homedir(), ".ebrain");
|
|
7
|
-
export const SETTINGS_PATH = join(SETTINGS_DIR, "settings.json");
|
|
8
|
-
export const DEFAULT_DB_PATH = resolve(SETTINGS_DIR, "data", "ebrain.db");
|
|
9
|
-
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// Schema
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
const RemoteDbSchema = z.object({
|
|
15
|
-
host: z.string().optional(),
|
|
16
|
-
port: z.number().optional(),
|
|
17
|
-
user: z.string().optional(),
|
|
18
|
-
password: z.string().optional(),
|
|
19
|
-
database: z.string().optional(),
|
|
20
|
-
tenant: z.string().optional(),
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const EmbedSchema = z.object({
|
|
24
|
-
provider: z.enum(["hash", "openai_compatible"]).optional(),
|
|
25
|
-
baseURL: z.string().optional(),
|
|
26
|
-
model: z.string().optional(),
|
|
27
|
-
dimensions: z.number().optional(),
|
|
28
|
-
apiKey: z.string().optional(),
|
|
29
|
-
apiKeyEnv: z.string().optional(),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const LLMSchema = z.object({
|
|
33
|
-
baseURL: z.string().optional(),
|
|
34
|
-
model: z.string().optional(),
|
|
35
|
-
apiKey: z.string().optional(),
|
|
36
|
-
apiKeyEnv: z.string().optional(),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const SettingsSchema = z.object({
|
|
40
|
-
db: z
|
|
41
|
-
.object({
|
|
42
|
-
path: z.string().optional(),
|
|
43
|
-
remote: RemoteDbSchema.optional(),
|
|
44
|
-
})
|
|
45
|
-
.optional(),
|
|
46
|
-
embed: EmbedSchema.optional(),
|
|
47
|
-
llm: LLMSchema.optional(),
|
|
48
|
-
extraction: z
|
|
49
|
-
.object({
|
|
50
|
-
confidenceThreshold: z.number().min(0).max(1).optional(),
|
|
51
|
-
})
|
|
52
|
-
.optional(),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
// Resolved types (all values present after defaults + env merge)
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
|
|
59
|
-
export interface ResolvedSettings {
|
|
60
|
-
dbPath: string;
|
|
61
|
-
remote: ResolvedRemoteDb | null;
|
|
62
|
-
embed: ResolvedEmbed;
|
|
63
|
-
llm: ResolvedLLM;
|
|
64
|
-
extraction: ResolvedExtraction;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface ResolvedExtraction {
|
|
68
|
-
confidenceThreshold: number;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface ResolvedRemoteDb {
|
|
72
|
-
host: string;
|
|
73
|
-
port: number;
|
|
74
|
-
user: string;
|
|
75
|
-
password: string;
|
|
76
|
-
database: string;
|
|
77
|
-
tenant: string;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface ResolvedEmbed {
|
|
81
|
-
provider: "hash" | "openai_compatible";
|
|
82
|
-
baseURL: string;
|
|
83
|
-
model: string;
|
|
84
|
-
dimensions: number;
|
|
85
|
-
apiKey: string;
|
|
86
|
-
apiKeyEnv: string;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface ResolvedLLM {
|
|
90
|
-
baseURL: string;
|
|
91
|
-
model: string;
|
|
92
|
-
apiKey: string;
|
|
93
|
-
apiKeyEnv: string;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Defaults
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
const DEFAULT_REMOTE = {
|
|
101
|
-
port: 3306,
|
|
102
|
-
user: "root",
|
|
103
|
-
password: "",
|
|
104
|
-
database: "ebrain",
|
|
105
|
-
tenant: "",
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const DEFAULT_EMBED = {
|
|
109
|
-
provider: "hash" as const,
|
|
110
|
-
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
111
|
-
model: "text-embedding-v4",
|
|
112
|
-
dimensions: 1024,
|
|
113
|
-
apiKey: "",
|
|
114
|
-
apiKeyEnv: "DASHSCOPE_API_KEY",
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const DEFAULT_LLM = {
|
|
118
|
-
baseURL: "",
|
|
119
|
-
model: "qwen-plus",
|
|
120
|
-
apiKey: "",
|
|
121
|
-
apiKeyEnv: "DASHSCOPE_API_KEY",
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const DEFAULT_EXTRACTION = {
|
|
125
|
-
confidenceThreshold: 0.7,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// ---------------------------------------------------------------------------
|
|
129
|
-
// Load & resolve
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
export async function loadSettings(): Promise<ResolvedSettings> {
|
|
133
|
-
const raw = await readSettingsFile();
|
|
134
|
-
const parsed = SettingsSchema.parse(raw ?? {});
|
|
135
|
-
return resolveSettings(parsed);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export async function readSettingsFile(): Promise<unknown | null> {
|
|
139
|
-
if (!(await fileExists(SETTINGS_PATH))) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
const text = await readTextFile(SETTINGS_PATH);
|
|
143
|
-
try {
|
|
144
|
-
return JSON.parse(text) as unknown;
|
|
145
|
-
} catch {
|
|
146
|
-
console.warn(
|
|
147
|
-
`[ebrain] Failed to parse ${SETTINGS_PATH}, using defaults.`,
|
|
148
|
-
);
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
1
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
2
|
+
* @deprecated Import from "./config" instead.
|
|
3
|
+
* This file exists only for backward compatibility.
|
|
156
4
|
*/
|
|
157
|
-
export
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
dimensions: 0,
|
|
183
|
-
apiKey: "",
|
|
184
|
-
apiKeyEnv: "",
|
|
185
|
-
},
|
|
186
|
-
llm: {
|
|
187
|
-
baseURL: "",
|
|
188
|
-
model: "",
|
|
189
|
-
apiKey: "",
|
|
190
|
-
apiKeyEnv: "",
|
|
191
|
-
},
|
|
192
|
-
extraction: {
|
|
193
|
-
confidenceThreshold: 0.7,
|
|
194
|
-
},
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
writeFileSync(SETTINGS_PATH, JSON.stringify(defaults, null, 2) + "\n", "utf-8");
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export function resolveSettings(parsed: z.infer<typeof SettingsSchema>): ResolvedSettings {
|
|
202
|
-
const dbConf = parsed.db ?? {};
|
|
203
|
-
const remoteConf = dbConf.remote ?? {};
|
|
204
|
-
const embedConf = parsed.embed ?? {};
|
|
205
|
-
const extractionConf = parsed.extraction ?? {};
|
|
206
|
-
|
|
207
|
-
// Remote: settings → env → defaults
|
|
208
|
-
const host = remoteConf.host ?? process.env.EBRAIN_SEEKDB_HOST ?? "";
|
|
209
|
-
if (host) {
|
|
210
|
-
const remote: ResolvedRemoteDb = {
|
|
211
|
-
host: host.trim(),
|
|
212
|
-
port: numOr(remoteConf.port ?? process.env.EBRAIN_SEEKDB_PORT, DEFAULT_REMOTE.port),
|
|
213
|
-
user: nonEmpty(remoteConf.user ?? process.env.EBRAIN_SEEKDB_USER, DEFAULT_REMOTE.user),
|
|
214
|
-
password: nonEmpty(
|
|
215
|
-
remoteConf.password ?? process.env.EBRAIN_SEEKDB_PASSWORD,
|
|
216
|
-
DEFAULT_REMOTE.password,
|
|
217
|
-
),
|
|
218
|
-
database: nonEmpty(
|
|
219
|
-
remoteConf.database ?? process.env.EBRAIN_SEEKDB_DATABASE,
|
|
220
|
-
DEFAULT_REMOTE.database,
|
|
221
|
-
),
|
|
222
|
-
tenant: nonEmpty(remoteConf.tenant ?? process.env.EBRAIN_SEEKDB_TENANT, ""),
|
|
223
|
-
};
|
|
224
|
-
return { dbPath: dbConf.path ?? DEFAULT_DB_PATH, remote, embed: resolveEmbed(embedConf), llm: resolveLLM(parsed.llm ?? {}), extraction: resolveExtraction(extractionConf) };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Local mode
|
|
228
|
-
const dbPath = dbConf.path
|
|
229
|
-
? resolvePath(dbConf.path)
|
|
230
|
-
: DEFAULT_DB_PATH;
|
|
231
|
-
return { dbPath, remote: null, embed: resolveEmbed(embedConf), llm: resolveLLM(parsed.llm ?? {}), extraction: resolveExtraction(extractionConf) };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function resolveEmbed(conf: z.infer<typeof EmbedSchema>): ResolvedEmbed {
|
|
235
|
-
const provider = nonEmpty(
|
|
236
|
-
conf.provider ?? process.env.EBRAIN_EMBED_PROVIDER,
|
|
237
|
-
DEFAULT_EMBED.provider,
|
|
238
|
-
).trim().toLowerCase() as "hash" | "openai_compatible";
|
|
239
|
-
const baseURL = nonEmpty(conf.baseURL ?? process.env.EBRAIN_EMBED_BASE_URL, DEFAULT_EMBED.baseURL);
|
|
240
|
-
const model = nonEmpty(conf.model ?? process.env.EBRAIN_EMBED_MODEL, DEFAULT_EMBED.model);
|
|
241
|
-
const dimensions = numOr(conf.dimensions ?? process.env.EBRAIN_EMBED_DIMENSIONS, DEFAULT_EMBED.dimensions);
|
|
242
|
-
const apiKey = nonEmpty(conf.apiKey ?? process.env.EBRAIN_EMBED_API_KEY, "");
|
|
243
|
-
const apiKeyEnv = nonEmpty(conf.apiKeyEnv ?? process.env.EBRAIN_EMBED_API_KEY_ENV, DEFAULT_EMBED.apiKeyEnv);
|
|
244
|
-
return { provider, baseURL, model, dimensions, apiKey, apiKeyEnv };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function resolveLLM(conf: z.infer<typeof LLMSchema>): ResolvedLLM {
|
|
248
|
-
const baseURL = nonEmpty(conf.baseURL, DEFAULT_LLM.baseURL);
|
|
249
|
-
const model = nonEmpty(conf.model, DEFAULT_LLM.model);
|
|
250
|
-
const apiKey = nonEmpty(conf.apiKey, DEFAULT_LLM.apiKey);
|
|
251
|
-
const apiKeyEnv = nonEmpty(conf.apiKeyEnv, DEFAULT_LLM.apiKeyEnv);
|
|
252
|
-
return { baseURL, model, apiKey, apiKeyEnv };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function resolveExtraction(conf: { confidenceThreshold?: number }): ResolvedExtraction {
|
|
256
|
-
const threshold = conf.confidenceThreshold ?? process.env.EBRAIN_CONFIDENCE_THRESHOLD;
|
|
257
|
-
const value = typeof threshold === "number" ? threshold : (threshold ? parseFloat(threshold) : DEFAULT_EXTRACTION.confidenceThreshold);
|
|
258
|
-
return { confidenceThreshold: Math.max(0, Math.min(1, value)) };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// ---------------------------------------------------------------------------
|
|
262
|
-
// Helpers
|
|
263
|
-
// ---------------------------------------------------------------------------
|
|
264
|
-
|
|
265
|
-
function nonEmpty(val: string | undefined, fallback: string): string {
|
|
266
|
-
const trimmed = val?.trim();
|
|
267
|
-
return trimmed || fallback;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function numOr(val: number | string | undefined, fallback: number): number {
|
|
271
|
-
if (typeof val === "number") return val;
|
|
272
|
-
if (typeof val === "string") {
|
|
273
|
-
const n = Number(val.trim());
|
|
274
|
-
if (Number.isFinite(n)) return n;
|
|
275
|
-
}
|
|
276
|
-
return fallback;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function resolvePath(p: string): string {
|
|
280
|
-
if (p.startsWith("~")) {
|
|
281
|
-
return join(homedir(), p.slice(1));
|
|
282
|
-
}
|
|
283
|
-
return resolve(p);
|
|
284
|
-
}
|
|
5
|
+
export {
|
|
6
|
+
SETTINGS_DIR,
|
|
7
|
+
SETTINGS_PATH,
|
|
8
|
+
DEFAULT_DB_PATH,
|
|
9
|
+
SettingsSchema,
|
|
10
|
+
RemoteDbSchema,
|
|
11
|
+
EmbedSchema,
|
|
12
|
+
LLMSchema,
|
|
13
|
+
type RawSettings,
|
|
14
|
+
type ResolvedSettings,
|
|
15
|
+
type ResolvedExtraction,
|
|
16
|
+
type ResolvedRemoteDb,
|
|
17
|
+
type ResolvedEmbed,
|
|
18
|
+
type ResolvedLLM,
|
|
19
|
+
DEFAULT_REMOTE,
|
|
20
|
+
DEFAULT_EMBED,
|
|
21
|
+
DEFAULT_LLM,
|
|
22
|
+
DEFAULT_EXTRACTION,
|
|
23
|
+
type EnvSource,
|
|
24
|
+
readSettingsFile,
|
|
25
|
+
resolveSettings,
|
|
26
|
+
loadSettings,
|
|
27
|
+
createDefaultSettings,
|
|
28
|
+
expandTilde,
|
|
29
|
+
} from "./config";
|
|
File without changes
|