brainllm 10.5.0 → 10.5.2
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/README.md +1 -1
- package/dist/index.js +352 -350
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/brainllm/SKILL.md +18 -0
package/dist/index.js
CHANGED
|
@@ -28322,258 +28322,12 @@ section="<heading>" reads one section instead of the whole log.`, {
|
|
|
28322
28322
|
server.tool("insights_recall", "Skim recent change logs (days + previews).", { limit: exports_external.number().optional() }, async ({ limit }) => txt(await skim(trilium, b().insights.logs, { kind: "log", limit: limit ?? 14 })));
|
|
28323
28323
|
}
|
|
28324
28324
|
|
|
28325
|
-
// src/tools-advanced.ts
|
|
28326
|
-
var txt3 = (obj) => ({
|
|
28327
|
-
content: [{ type: "text", text: typeof obj === "string" ? obj : JSON.stringify(obj, null, 2) }]
|
|
28328
|
-
});
|
|
28329
|
-
var noteStub = (n) => ({
|
|
28330
|
-
id: n.noteId,
|
|
28331
|
-
title: n.title,
|
|
28332
|
-
...n.type ? { type: n.type } : {}
|
|
28333
|
-
});
|
|
28334
|
-
var attrStub = (a) => ({
|
|
28335
|
-
id: a.attributeId,
|
|
28336
|
-
noteId: a.noteId,
|
|
28337
|
-
type: a.type,
|
|
28338
|
-
name: a.name,
|
|
28339
|
-
value: a.value
|
|
28340
|
-
});
|
|
28341
|
-
function registerAdvancedTools(server, trilium, brainRef) {
|
|
28342
|
-
const b = () => brainRef.config;
|
|
28343
|
-
server.tool("search_notes", `Raw Trilium query search. Native query language: #label=value, note.title =* "x",
|
|
28344
|
-
note.dateModified >= 'YYYY-MM-DD', AND/OR. Unscoped unless ancestorNoteId is given.`, {
|
|
28345
|
-
query: exports_external.string().describe("Trilium search query"),
|
|
28346
|
-
ancestorNoteId: exports_external.string().optional().describe("Limit to this subtree"),
|
|
28347
|
-
limit: exports_external.number().optional(),
|
|
28348
|
-
orderBy: exports_external.string().optional().describe("title | dateModified | dateCreated"),
|
|
28349
|
-
orderDirection: exports_external.enum(["asc", "desc"]).optional(),
|
|
28350
|
-
fastSearch: exports_external.boolean().optional().describe("Skip content body scan"),
|
|
28351
|
-
includeArchived: exports_external.boolean().optional(),
|
|
28352
|
-
debug: exports_external.boolean().optional().describe("Return query parse debug info")
|
|
28353
|
-
}, async ({ query, ancestorNoteId, limit, orderBy, orderDirection, fastSearch, includeArchived, debug }) => {
|
|
28354
|
-
const result = await trilium.searchNotes(query, {
|
|
28355
|
-
ancestorNoteId,
|
|
28356
|
-
limit,
|
|
28357
|
-
orderBy,
|
|
28358
|
-
orderDirection,
|
|
28359
|
-
fastSearch,
|
|
28360
|
-
includeArchivedNotes: includeArchived,
|
|
28361
|
-
debug
|
|
28362
|
-
});
|
|
28363
|
-
const out = { results: result.results.map(noteStub) };
|
|
28364
|
-
if (result.debugInfo !== undefined)
|
|
28365
|
-
out.debugInfo = result.debugInfo;
|
|
28366
|
-
return txt3(out);
|
|
28367
|
-
});
|
|
28368
|
-
server.tool("get_note", "Full note metadata: title, type, mime, attributes (labels + relations), parent/child ids, dates.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
28369
|
-
const n = await trilium.getNote(noteId);
|
|
28370
|
-
return txt3({
|
|
28371
|
-
id: n.noteId,
|
|
28372
|
-
title: n.title,
|
|
28373
|
-
type: n.type,
|
|
28374
|
-
mime: n.mime,
|
|
28375
|
-
attributes: n.attributes.map((a) => ({ id: a.attributeId, type: a.type, name: a.name, value: a.value })),
|
|
28376
|
-
parents: n.parentNoteIds,
|
|
28377
|
-
children: n.childNoteIds,
|
|
28378
|
-
created: n.dateCreated,
|
|
28379
|
-
modified: n.dateModified
|
|
28380
|
-
});
|
|
28381
|
-
});
|
|
28382
|
-
server.tool("get_note_content", "Raw note content (HTML / text / code).", { noteId: exports_external.string() }, async ({ noteId }) => txt3(await trilium.getNoteContent(noteId)));
|
|
28383
|
-
server.tool("create_note", "Create a note at an explicit parent. Types: text/code/book/canvas/mermaid/relationMap/render/search/file/image.", {
|
|
28384
|
-
parentNoteId: exports_external.string(),
|
|
28385
|
-
title: exports_external.string(),
|
|
28386
|
-
content: exports_external.string(),
|
|
28387
|
-
type: exports_external.enum(["text", "code", "book", "canvas", "mermaid", "relationMap", "render", "search", "file", "image"]).optional(),
|
|
28388
|
-
mime: exports_external.string().optional()
|
|
28389
|
-
}, async ({ parentNoteId, title, content, type, mime }) => {
|
|
28390
|
-
const r = await trilium.createNote(parentNoteId, title, content, type ?? "text", mime);
|
|
28391
|
-
return txt3({ noteId: r.note.noteId, branchId: r.branch.branchId, title: r.note.title });
|
|
28392
|
-
});
|
|
28393
|
-
server.tool("update_note_content", "Replace a note's full content.", { noteId: exports_external.string(), content: exports_external.string() }, async ({ noteId, content }) => {
|
|
28394
|
-
await trilium.updateNoteContent(noteId, content);
|
|
28395
|
-
return txt3({ ok: true, noteId });
|
|
28396
|
-
});
|
|
28397
|
-
server.tool("patch_note", "Mutate note metadata: title, type, or mime.", { noteId: exports_external.string(), title: exports_external.string().optional(), type: exports_external.string().optional(), mime: exports_external.string().optional() }, async ({ noteId, title, type, mime }) => {
|
|
28398
|
-
const fields = {};
|
|
28399
|
-
if (title != null)
|
|
28400
|
-
fields.title = title;
|
|
28401
|
-
if (type != null)
|
|
28402
|
-
fields.type = type;
|
|
28403
|
-
if (mime != null)
|
|
28404
|
-
fields.mime = mime;
|
|
28405
|
-
const note = await trilium.patchNote(noteId, fields);
|
|
28406
|
-
return txt3({ noteId: note.noteId, title: note.title, type: note.type });
|
|
28407
|
-
});
|
|
28408
|
-
server.tool("delete_note", "Hard-delete a note (and its subtree if this is its last branch). Irreversible.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
28409
|
-
await trilium.deleteNote(noteId);
|
|
28410
|
-
return txt3({ ok: true, deleted: noteId });
|
|
28411
|
-
});
|
|
28412
|
-
server.tool("undelete_note", "Recover a recently Trilium-deleted note from Trilium's trash. canBeUndeleted must be true (check note_history). Distinct from recover() which restores BrainLLM-archived notes. Re-applies the BrainLLM label set for the container it lands in \u2014 the restore itself does not bring attributes back, and a note without #noteType is invisible to every read path.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
28413
|
-
await trilium.undeleteNote(noteId);
|
|
28414
|
-
const cfg = b();
|
|
28415
|
-
const note = await trilium.getNote(noteId).catch(() => null);
|
|
28416
|
-
const restored = [];
|
|
28417
|
-
let inferredKind;
|
|
28418
|
-
if (note) {
|
|
28419
|
-
const already = ownedLabel(note, "noteType");
|
|
28420
|
-
if (already) {
|
|
28421
|
-
inferredKind = already;
|
|
28422
|
-
} else {
|
|
28423
|
-
const parents = note.parentNoteIds ?? [];
|
|
28424
|
-
const byContainer = [
|
|
28425
|
-
[cfg.memory.sessions, "session"],
|
|
28426
|
-
[cfg.memory.threads, "thread"],
|
|
28427
|
-
[cfg.llm.diary, "diary"],
|
|
28428
|
-
[cfg.insights.logs, "log"],
|
|
28429
|
-
[cfg.knowledge.master, "user"],
|
|
28430
|
-
[cfg.knowledge.domains, "domain"]
|
|
28431
|
-
];
|
|
28432
|
-
for (const [containerId, kind] of byContainer) {
|
|
28433
|
-
if (containerId && parents.includes(containerId)) {
|
|
28434
|
-
inferredKind = kind;
|
|
28435
|
-
break;
|
|
28436
|
-
}
|
|
28437
|
-
}
|
|
28438
|
-
if (!inferredKind && parents.length) {
|
|
28439
|
-
const parent = await trilium.getNote(parents[0]).catch(() => null);
|
|
28440
|
-
if (parent && ownedLabel(parent, "noteType") === "thread")
|
|
28441
|
-
inferredKind = "threadEntry";
|
|
28442
|
-
else if (parent && ownedLabel(parent, "noteType") === "domain")
|
|
28443
|
-
inferredKind = "information";
|
|
28444
|
-
}
|
|
28445
|
-
if (inferredKind) {
|
|
28446
|
-
for (const l of labelPlan(inferredKind, {}, localToday())) {
|
|
28447
|
-
if (!ownedLabel(note, l.name)) {
|
|
28448
|
-
await trilium.addLabel(noteId, l.name, l.value, l.inheritable ?? false).catch(() => null);
|
|
28449
|
-
restored.push(l.value ? `${l.name}=${l.value}` : l.name);
|
|
28450
|
-
}
|
|
28451
|
-
}
|
|
28452
|
-
}
|
|
28453
|
-
}
|
|
28454
|
-
}
|
|
28455
|
-
return txt3({
|
|
28456
|
-
ok: true,
|
|
28457
|
-
undeleted: noteId,
|
|
28458
|
-
...inferredKind ? { kind: inferredKind } : {},
|
|
28459
|
-
...restored.length ? { labelsRestored: restored } : {},
|
|
28460
|
-
...note && !inferredKind ? {
|
|
28461
|
-
warning: "Could not infer this note's kind from its placement, so no #noteType was applied \u2014 it is currently INVISIBLE to brain(), recall() and every surface read. " + "Move it under its proper container and re-run, or set the label with add_label."
|
|
28462
|
-
} : {}
|
|
28463
|
-
});
|
|
28464
|
-
});
|
|
28465
|
-
server.tool("note_history", "Recent changes feed (creations / modifications / deletions), newest first.", { ancestorNoteId: exports_external.string().optional() }, async ({ ancestorNoteId }) => {
|
|
28466
|
-
const changes = await trilium.getNoteHistory(ancestorNoteId);
|
|
28467
|
-
return txt3(changes.map((c) => ({ id: c.noteId, title: c.current_title, deleted: c.current_isDeleted, date: c.date })));
|
|
28468
|
-
});
|
|
28469
|
-
server.tool("get_attribute", "Fetch a single attribute by id.", { attributeId: exports_external.string() }, async ({ attributeId }) => txt3(attrStub(await trilium.getAttribute(attributeId))));
|
|
28470
|
-
server.tool("add_label", "Add a #label to a note (empty value = boolean flag). Adds a new attribute; does not dedupe.", { noteId: exports_external.string(), name: exports_external.string().describe("Label name (no # prefix)"), value: exports_external.string().optional(), isInheritable: exports_external.boolean().optional() }, async ({ noteId, name, value, isInheritable }) => {
|
|
28471
|
-
const attr = await trilium.addLabel(noteId, name, value ?? "", isInheritable ?? false);
|
|
28472
|
-
return txt3(attrStub(attr));
|
|
28473
|
-
});
|
|
28474
|
-
server.tool("add_relation", "Add a ~relation with any name (the core connect() enforces the canonical vocabulary).", { fromNoteId: exports_external.string(), relationName: exports_external.string(), toNoteId: exports_external.string(), isInheritable: exports_external.boolean().optional() }, async ({ fromNoteId, relationName, toNoteId, isInheritable }) => {
|
|
28475
|
-
const attr = await trilium.addRelation(fromNoteId, relationName, toNoteId, isInheritable ?? false);
|
|
28476
|
-
return txt3(attrStub(attr));
|
|
28477
|
-
});
|
|
28478
|
-
server.tool("update_attribute", "Update an attribute's value (labels) and/or position by id.", { attributeId: exports_external.string(), value: exports_external.string().optional(), position: exports_external.number().optional() }, async ({ attributeId, value, position }) => {
|
|
28479
|
-
const fields = {};
|
|
28480
|
-
if (value != null)
|
|
28481
|
-
fields.value = value;
|
|
28482
|
-
if (position != null)
|
|
28483
|
-
fields.position = position;
|
|
28484
|
-
return txt3(attrStub(await trilium.updateAttribute(attributeId, fields)));
|
|
28485
|
-
});
|
|
28486
|
-
server.tool("delete_attribute", "Delete any label or relation by attributeId.", { attributeId: exports_external.string() }, async ({ attributeId }) => {
|
|
28487
|
-
await trilium.deleteAttribute(attributeId);
|
|
28488
|
-
return txt3({ ok: true, deleted: attributeId });
|
|
28489
|
-
});
|
|
28490
|
-
server.tool("get_branch", "Fetch a branch (a note's placement under one parent).", { branchId: exports_external.string() }, async ({ branchId }) => txt3(await trilium.getBranch(branchId)));
|
|
28491
|
-
server.tool("clone_note", "Place a note under an additional parent (multi-parent branch; shared content, no copy).", { noteId: exports_external.string(), parentNoteId: exports_external.string(), prefix: exports_external.string().optional() }, async ({ noteId, parentNoteId, prefix }) => {
|
|
28492
|
-
const branch = await trilium.cloneNote(noteId, parentNoteId, prefix);
|
|
28493
|
-
return txt3({ id: branch.branchId, noteId: branch.noteId, parentNoteId: branch.parentNoteId });
|
|
28494
|
-
});
|
|
28495
|
-
server.tool("move_note", "Move a note to a new parent (clone to the new parent, then remove the old branch).", { noteId: exports_external.string(), fromParentNoteId: exports_external.string(), toParentNoteId: exports_external.string() }, async ({ noteId, fromParentNoteId, toParentNoteId }) => {
|
|
28496
|
-
const newBranch = await trilium.cloneNote(noteId, toParentNoteId);
|
|
28497
|
-
const fresh = await trilium.getNote(noteId);
|
|
28498
|
-
for (const bid of fresh.parentBranchIds) {
|
|
28499
|
-
if (bid === newBranch.branchId)
|
|
28500
|
-
continue;
|
|
28501
|
-
const branch = await trilium.getBranch(bid);
|
|
28502
|
-
if (branch.parentNoteId === fromParentNoteId) {
|
|
28503
|
-
await trilium.deleteBranch(bid);
|
|
28504
|
-
break;
|
|
28505
|
-
}
|
|
28506
|
-
}
|
|
28507
|
-
return txt3({ ok: true, noteId, movedTo: toParentNoteId, newBranchId: newBranch.branchId });
|
|
28508
|
-
});
|
|
28509
|
-
server.tool("delete_branch", "Remove one placement of a note (deletes the note if it was the last branch).", { branchId: exports_external.string() }, async ({ branchId }) => {
|
|
28510
|
-
await trilium.deleteBranch(branchId);
|
|
28511
|
-
return txt3({ ok: true, deleted: branchId });
|
|
28512
|
-
});
|
|
28513
|
-
server.tool("create_revision", "Snapshot a note's current content as a revision.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
28514
|
-
await trilium.createRevision(noteId);
|
|
28515
|
-
return txt3({ ok: true, noteId });
|
|
28516
|
-
});
|
|
28517
|
-
server.tool("get_revisions", "List a note's revisions, newest first.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
28518
|
-
const revs = await trilium.getNoteRevisions(noteId);
|
|
28519
|
-
return txt3(revs.map((r) => ({ id: r.revisionId, title: r.title, date: r.utcDateCreated, size: r.contentLength })));
|
|
28520
|
-
});
|
|
28521
|
-
server.tool("get_revision_content", "Content of a historical revision snapshot.", { revisionId: exports_external.string() }, async ({ revisionId }) => txt3(await trilium.getRevisionContent(revisionId)));
|
|
28522
|
-
server.tool("get_attachments", "List attachments on a note (id + title + mime + size).", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
28523
|
-
const attachments = await trilium.getNoteAttachments(noteId);
|
|
28524
|
-
return txt3(attachments.map((a) => ({ id: a.attachmentId, title: a.title, mime: a.mime, size: a.contentLength })));
|
|
28525
|
-
});
|
|
28526
|
-
server.tool("get_attachment_content", "Read the content of a text/code attachment.", { attachmentId: exports_external.string() }, async ({ attachmentId }) => txt3(await trilium.getAttachmentContent(attachmentId)));
|
|
28527
|
-
server.tool("create_attachment", "Attach a file or text blob to a note (role: file | image).", { ownerId: exports_external.string(), title: exports_external.string(), mime: exports_external.string(), content: exports_external.string().describe("Text content (base64 for binary)"), role: exports_external.enum(["file", "image"]).optional() }, async ({ ownerId, title, mime, content, role }) => {
|
|
28528
|
-
const att = await trilium.createAttachment(ownerId, title, mime, content, role ?? "file");
|
|
28529
|
-
return txt3({ id: att.attachmentId, title: att.title, mime: att.mime, size: att.contentLength });
|
|
28530
|
-
});
|
|
28531
|
-
server.tool("update_attachment", "Update an attachment's content and/or metadata (title, mime). Pass content to replace the binary/text data in place; pass title/mime to update metadata only.", {
|
|
28532
|
-
attachmentId: exports_external.string(),
|
|
28533
|
-
title: exports_external.string().optional(),
|
|
28534
|
-
mime: exports_external.string().optional().describe("MIME type \u2014 also used as Content-Type when writing content"),
|
|
28535
|
-
content: exports_external.string().optional().describe("New content (replaces existing; text or base64 for binary)")
|
|
28536
|
-
}, async ({ attachmentId, title, mime, content }) => {
|
|
28537
|
-
if (content != null) {
|
|
28538
|
-
await trilium.updateAttachmentContent(attachmentId, content, mime ?? "text/plain");
|
|
28539
|
-
}
|
|
28540
|
-
if (title != null || mime != null) {
|
|
28541
|
-
const fields = {};
|
|
28542
|
-
if (title != null)
|
|
28543
|
-
fields.title = title;
|
|
28544
|
-
if (mime != null)
|
|
28545
|
-
fields.mime = mime;
|
|
28546
|
-
const att = await trilium.updateAttachment(attachmentId, fields);
|
|
28547
|
-
return txt3({ id: att.attachmentId, title: att.title, mime: att.mime, contentUpdated: content != null });
|
|
28548
|
-
}
|
|
28549
|
-
return txt3({ ok: true, attachmentId, contentUpdated: content != null });
|
|
28550
|
-
});
|
|
28551
|
-
server.tool("delete_attachment", "Permanently delete an attachment. Irreversible.", { attachmentId: exports_external.string() }, async ({ attachmentId }) => {
|
|
28552
|
-
await trilium.deleteAttachment(attachmentId);
|
|
28553
|
-
return txt3({ ok: true, deleted: attachmentId });
|
|
28554
|
-
});
|
|
28555
|
-
server.tool("get_day_note", "Get (or auto-create) the journal day note. Format: YYYY-MM-DD (default: today).", { date: exports_external.string().optional() }, async ({ date: date4 }) => txt3(await trilium.getDayNote(date4 ?? localToday())));
|
|
28556
|
-
server.tool("get_week_note", "Get (or auto-create) the journal week note. Format: YYYY-Www.", { week: exports_external.string() }, async ({ week }) => txt3(await trilium.getWeekNote(week)));
|
|
28557
|
-
server.tool("get_month_note", "Get (or auto-create) the journal month note. Format: YYYY-MM.", { month: exports_external.string() }, async ({ month }) => txt3(await trilium.getMonthNote(month)));
|
|
28558
|
-
server.tool("get_year_note", "Get (or auto-create) the journal year note. Format: YYYY.", { year: exports_external.string() }, async ({ year }) => txt3(await trilium.getYearNote(year)));
|
|
28559
|
-
server.tool("get_inbox_note", "Get the Trilium inbox note for a date (fixed #inbox note, or the day note). Format: YYYY-MM-DD.", { date: exports_external.string().optional() }, async ({ date: date4 }) => txt3(await trilium.getInboxNote(date4 ?? localToday())));
|
|
28560
|
-
server.tool("get_app_info", "Trilium server version, DB version, runtime metadata.", {}, async () => txt3(await trilium.getAppInfo()));
|
|
28561
|
-
server.tool("create_backup", "Trigger a named Trilium database backup. The backup file is written to Trilium's backup directory as <name>.db (default name: brainllm-{date}). Use a descriptive name for milestone snapshots (e.g. 'before-migration').", {
|
|
28562
|
-
name: exports_external.string().optional().describe("Backup file name without .db extension (default: brainllm-{today})"),
|
|
28563
|
-
date: exports_external.string().optional().describe("ISO date used in the default name when name is omitted (default: today)")
|
|
28564
|
-
}, async ({ name, date: date4 }) => {
|
|
28565
|
-
const backupName = name ?? `brainllm-${date4 ?? localToday()}`;
|
|
28566
|
-
await trilium.createBackup(backupName);
|
|
28567
|
-
return txt3({ ok: true, backup: `${backupName}.db` });
|
|
28568
|
-
});
|
|
28569
|
-
}
|
|
28570
|
-
|
|
28571
28325
|
// src/tools.ts
|
|
28572
|
-
var
|
|
28326
|
+
var txt3 = (obj) => ({
|
|
28573
28327
|
content: [{ type: "text", text: typeof obj === "string" ? obj : JSON.stringify(obj, null, 2) }]
|
|
28574
28328
|
});
|
|
28575
28329
|
var today = () => localToday();
|
|
28576
|
-
var err = (code, detail, hint) =>
|
|
28330
|
+
var err = (code, detail, hint) => txt3({ error: code, detail, ...hint ? { hint } : {} });
|
|
28577
28331
|
var labelOf2 = (n, name) => n.attributes.find((a) => a.type === "label" && a.name === name)?.value;
|
|
28578
28332
|
var hasLabel = (n, name) => n.attributes.some((a) => a.type === "label" && a.name === name);
|
|
28579
28333
|
function insertBeforeResolution(html, section) {
|
|
@@ -28602,7 +28356,7 @@ function isDuplicateAppend(current, incomingHtml) {
|
|
|
28602
28356
|
const block = nextH === -1 ? afterHeader : afterHeader.slice(0, nextH);
|
|
28603
28357
|
return norm(block) === norm(incomingHtml);
|
|
28604
28358
|
}
|
|
28605
|
-
function registerTools(server, trilium, brainRef
|
|
28359
|
+
function registerTools(server, trilium, brainRef) {
|
|
28606
28360
|
const b = () => brainRef.config;
|
|
28607
28361
|
const preCloseSteps = new Map;
|
|
28608
28362
|
let preCloseSeq = 0;
|
|
@@ -28744,7 +28498,7 @@ sessions need one section, not five documents.`, {
|
|
|
28744
28498
|
}, async ({ depth }) => {
|
|
28745
28499
|
const cfg = b();
|
|
28746
28500
|
if (!cfg.root) {
|
|
28747
|
-
return
|
|
28501
|
+
return txt3({ status: "uninitialized", action: "Run bootstrap to create the BrainLLM structure." });
|
|
28748
28502
|
}
|
|
28749
28503
|
const [hygiene, digest] = await Promise.all([
|
|
28750
28504
|
sweep(trilium, cfg, { deep: false, dryRun: false }).catch((e) => ({
|
|
@@ -28818,7 +28572,7 @@ sessions need one section, not five documents.`, {
|
|
|
28818
28572
|
}));
|
|
28819
28573
|
} catch {}
|
|
28820
28574
|
}
|
|
28821
|
-
return
|
|
28575
|
+
return txt3({
|
|
28822
28576
|
status: "ready",
|
|
28823
28577
|
today: todayStr,
|
|
28824
28578
|
weekday,
|
|
@@ -28925,7 +28679,7 @@ rather than listing them unconditionally for every caller to work around.`, {
|
|
|
28925
28679
|
singletonsWrittenToday: touchedToday.length ? touchedToday : "none"
|
|
28926
28680
|
};
|
|
28927
28681
|
const scoped = scope === "agent";
|
|
28928
|
-
return
|
|
28682
|
+
return txt3({
|
|
28929
28683
|
date: d,
|
|
28930
28684
|
...scoped ? { scope: "agent" } : {},
|
|
28931
28685
|
...!full ? { mode: "light", note: "Singleton content and the diary body are omitted (default) \u2014 fetch via master()/llm() only where lastModified indicates a revision is needed; start() already served all singletons in full, and diary() needs only the id." } : {},
|
|
@@ -28967,9 +28721,9 @@ This tool writes nothing \u2014 answer the cues as prose in today's diary via di
|
|
|
28967
28721
|
outright rather than padding it; two honest paragraphs beat eight forced ones.`, {}, async () => {
|
|
28968
28722
|
const cfg = b();
|
|
28969
28723
|
if (!cfg.root)
|
|
28970
|
-
return
|
|
28724
|
+
return txt3({ status: "uninitialized", action: "Run bootstrap first." });
|
|
28971
28725
|
await markStep("remarks");
|
|
28972
|
-
return
|
|
28726
|
+
return txt3({
|
|
28973
28727
|
cues: {
|
|
28974
28728
|
experience: [
|
|
28975
28729
|
"1. Experience \u2014 what was this session actually like from the inside? The work, the flow, what happened and how it went.",
|
|
@@ -29101,7 +28855,7 @@ ${contentBlock}` }));
|
|
|
29101
28855
|
if (backup !== false)
|
|
29102
28856
|
backedUp = await trilium.createBackup(d).then(() => true).catch(() => false);
|
|
29103
28857
|
await clearGate(d);
|
|
29104
|
-
return
|
|
28858
|
+
return txt3({
|
|
29105
28859
|
action,
|
|
29106
28860
|
noteId,
|
|
29107
28861
|
date: d,
|
|
@@ -29121,7 +28875,7 @@ close() already triggers a backup automatically \u2014 use this for on-demand mi
|
|
|
29121
28875
|
const d = today();
|
|
29122
28876
|
const backupName = name ?? `brainllm-${d}`;
|
|
29123
28877
|
await trilium.createBackup(backupName);
|
|
29124
|
-
return
|
|
28878
|
+
return txt3({ ok: true, backup: `${backupName}.db`, date: d });
|
|
29125
28879
|
});
|
|
29126
28880
|
server.tool("diary", `Write to today's LLM diary \u2014 your daily maintained, unfiltered first-person record: your
|
|
29127
28881
|
experience, opinions, and remarks on your own existence during this session in this
|
|
@@ -29161,13 +28915,13 @@ ${sanitized.html}` : sanitized.html;
|
|
|
29161
28915
|
const incoming = norm(html);
|
|
29162
28916
|
const blocks = current.split(/<h2>Addendum \u2014 \d{2}:\d{2}<\/h2>\n?/i).slice(1);
|
|
29163
28917
|
if (incoming && blocks.some((b2) => norm(b2) === incoming)) {
|
|
29164
|
-
return
|
|
28918
|
+
return txt3({ action: "already_written", noteId: noteId2, date: d });
|
|
29165
28919
|
}
|
|
29166
28920
|
await trilium.createRevision(noteId2).catch(() => null);
|
|
29167
28921
|
await trilium.updateNoteContent(noteId2, safeAppend(current, `<h2>Addendum \u2014 ${time4}</h2>`, html));
|
|
29168
28922
|
await trilium.updateLabelValue(noteId2, "updated", d);
|
|
29169
28923
|
const iconSet2 = await applyIcon(noteId2, icon);
|
|
29170
|
-
return
|
|
28924
|
+
return txt3({ action: "appended", noteId: noteId2, date: d, ...iconSet2 ? { icon: iconSet2 } : {}, ...warnings.length ? { sanitized: warnings } : {} });
|
|
29171
28925
|
}
|
|
29172
28926
|
const time3 = localNowTime();
|
|
29173
28927
|
const created = await trilium.createNote(parentId, `[${d}]`, contentFor("diary", { date: d, body: `<h2>Addendum \u2014 ${time3}</h2>
|
|
@@ -29176,7 +28930,7 @@ ${html}` }));
|
|
|
29176
28930
|
await trilium.addLabel(noteId, "noteType", "diary");
|
|
29177
28931
|
await trilium.addLabel(noteId, "created", d);
|
|
29178
28932
|
const iconSet = await applyIcon(noteId, icon);
|
|
29179
|
-
return
|
|
28933
|
+
return txt3({ action: "created", noteId, date: d, location: locationLabel("diary"), ...iconSet ? { icon: iconSet } : {}, ...warnings.length ? { sanitized: warnings } : {} });
|
|
29180
28934
|
});
|
|
29181
28935
|
server.tool("remember", `Store something the moment it matters. The server owns placement, naming, labels and dedup.
|
|
29182
28936
|
|
|
@@ -29262,7 +29016,7 @@ For diary entries use the dedicated diary() tool \u2014 remember(kind="diary") i
|
|
|
29262
29016
|
const connected2 = await wireRequested(id);
|
|
29263
29017
|
const iconSet2 = await applyIcon(id, icon);
|
|
29264
29018
|
const relations2 = relationSnippet(await trilium.getNote(id));
|
|
29265
|
-
return
|
|
29019
|
+
return txt3({ action: wrote ? "maintained" : "already_written", noteId: id, kind, location: locationLabel(kind), ...connected2.length ? { connected: connected2 } : {}, ...iconSet2 ? { icon: iconSet2 } : {}, ...relations2 ? { relations: relations2 } : {}, ...sanitizeWarnings.length ? { sanitized: sanitizeWarnings } : {} });
|
|
29266
29020
|
}
|
|
29267
29021
|
if (kind === "sources") {
|
|
29268
29022
|
if (!domain)
|
|
@@ -29320,7 +29074,7 @@ For diary entries use the dedicated diary() tool \u2014 remember(kind="diary") i
|
|
|
29320
29074
|
const placeholderLeft = hasPlaceholderRow(finalHtml, "Revision");
|
|
29321
29075
|
const bodyWritten = !!html && !!toText(html, 50);
|
|
29322
29076
|
const looseProse = bodyWritten && !createdDomain && !/<(?:ul|ol|li|h[34]|table)\b/i.test(html);
|
|
29323
|
-
return
|
|
29077
|
+
return txt3({
|
|
29324
29078
|
action: wrote ? "maintained" : "already_written",
|
|
29325
29079
|
noteId: sid,
|
|
29326
29080
|
kind,
|
|
@@ -29352,7 +29106,7 @@ For diary entries use the dedicated diary() tool \u2014 remember(kind="diary") i
|
|
|
29352
29106
|
if (existing2) {
|
|
29353
29107
|
const current = await trilium.getNoteContent(existing2.noteId).catch(() => "");
|
|
29354
29108
|
if (isDuplicateAppend(current, html))
|
|
29355
|
-
return
|
|
29109
|
+
return txt3({ action: "already_written", noteId: existing2.noteId, kind, title: existing2.title, domainId });
|
|
29356
29110
|
await trilium.createRevision(existing2.noteId).catch(() => null);
|
|
29357
29111
|
const appended = bumpLastUpdated(safeAppend(current, `<h2>Addendum \u2014 ${d}</h2>`, html), d);
|
|
29358
29112
|
await trilium.updateNoteContent(existing2.noteId, appended.html);
|
|
@@ -29360,7 +29114,7 @@ For diary entries use the dedicated diary() tool \u2014 remember(kind="diary") i
|
|
|
29360
29114
|
const connected3 = await wireRequested(existing2.noteId);
|
|
29361
29115
|
const iconSet3 = await applyIcon(existing2.noteId, icon);
|
|
29362
29116
|
const relations2 = relationSnippet(existing2);
|
|
29363
|
-
return
|
|
29117
|
+
return txt3({ action: "updated", noteId: existing2.noteId, kind, title: existing2.title, domainId, ...connected3.length ? { connected: connected3 } : {}, ...iconSet3 ? { icon: iconSet3 } : {}, ...relations2 ? { relations: relations2 } : {}, ...sanitizeWarnings.length ? { sanitized: sanitizeWarnings } : {} });
|
|
29364
29118
|
}
|
|
29365
29119
|
const created2 = await trilium.createNote(domainId, subTitle, contentFor("information", { date: d, body: html, domain: domainTitle }));
|
|
29366
29120
|
const nid2 = created2.note.noteId;
|
|
@@ -29369,7 +29123,7 @@ For diary entries use the dedicated diary() tool \u2014 remember(kind="diary") i
|
|
|
29369
29123
|
}
|
|
29370
29124
|
const connected2 = await wireRequested(nid2);
|
|
29371
29125
|
const iconSet2 = await applyIcon(nid2, icon);
|
|
29372
|
-
return
|
|
29126
|
+
return txt3({
|
|
29373
29127
|
action: "created",
|
|
29374
29128
|
noteId: nid2,
|
|
29375
29129
|
kind,
|
|
@@ -29409,7 +29163,7 @@ ${html}` : html;
|
|
|
29409
29163
|
if (kind === "thread") {
|
|
29410
29164
|
const entry = await appendThreadEntry(existing.noteId, block, d);
|
|
29411
29165
|
if (entry.action === "already_written") {
|
|
29412
|
-
return
|
|
29166
|
+
return txt3({ action: "already_written", noteId: existing.noteId, entryId: entry.noteId, kind, title: existing.title });
|
|
29413
29167
|
}
|
|
29414
29168
|
await trilium.updateLabelValue(existing.noteId, "updated", d);
|
|
29415
29169
|
for (const t of topics ?? []) {
|
|
@@ -29421,11 +29175,11 @@ ${html}` : html;
|
|
|
29421
29175
|
const connected3 = await wireRequested(existing.noteId);
|
|
29422
29176
|
const iconSet3 = await applyIcon(existing.noteId, icon);
|
|
29423
29177
|
const relations3 = relationSnippet(existing);
|
|
29424
|
-
return
|
|
29178
|
+
return txt3({ action: "updated", noteId: existing.noteId, entryId: entry.noteId, entryAction: entry.action, kind, title: existing.title, ...connected3.length ? { connected: connected3 } : {}, ...iconSet3 ? { icon: iconSet3 } : {}, ...relations3 ? { relations: relations3 } : {}, ...sanitizeWarnings.length ? { sanitized: sanitizeWarnings } : {} });
|
|
29425
29179
|
}
|
|
29426
29180
|
const current = await trilium.getNoteContent(existing.noteId).catch(() => "");
|
|
29427
29181
|
if (isDuplicateAppend(current, block))
|
|
29428
|
-
return
|
|
29182
|
+
return txt3({ action: "already_written", noteId: existing.noteId, kind, title: existing.title });
|
|
29429
29183
|
await trilium.createRevision(existing.noteId).catch(() => null);
|
|
29430
29184
|
const updatedContent = bumpLastUpdated(insertBeforeResolution(closeDangling(current), `<h2>Addendum \u2014 ${d}</h2>
|
|
29431
29185
|
${block}`), d);
|
|
@@ -29441,7 +29195,7 @@ ${block}`), d);
|
|
|
29441
29195
|
const iconSet2 = await applyIcon(existing.noteId, icon);
|
|
29442
29196
|
const relations2 = relationSnippet(existing);
|
|
29443
29197
|
const dupes = duplicateHeadings(updatedContent.html);
|
|
29444
|
-
return
|
|
29198
|
+
return txt3({ action: "updated", noteId: existing.noteId, kind, title: existing.title, ...connected2.length ? { connected: connected2 } : {}, ...iconSet2 ? { icon: iconSet2 } : {}, ...relations2 ? { relations: relations2 } : {}, ...dupes.length ? { duplicateHeadings: dupes, structureHint: "The note now carries duplicated section headings \u2014 merge them with revise(section=\u2026, mode=replace)." } : {}, ...sanitizeWarnings.length ? { sanitized: sanitizeWarnings } : {} });
|
|
29445
29199
|
}
|
|
29446
29200
|
if (kind === "thread" && !goal && !/<h2(?:\s[^>]*)?>\s*Context\s*<\/h2>/i.test(html)) {
|
|
29447
29201
|
return err("missing_goal", "A new thread requires a goal \u2014 the Context \u2192 Goal section is queried from the user at creation.", `Ask the user what this thread's goal is, then re-call remember(kind="thread", title, goal="<the goal statement>", body?).`);
|
|
@@ -29470,7 +29224,7 @@ ${block}`), d);
|
|
|
29470
29224
|
extraRelations.push(...connected);
|
|
29471
29225
|
const iconSet = await applyIcon(nid, icon);
|
|
29472
29226
|
const relations = [...relationSnippet(created.note) ?? [], ...extraRelations];
|
|
29473
|
-
return
|
|
29227
|
+
return txt3({
|
|
29474
29228
|
action: "created",
|
|
29475
29229
|
noteId: nid,
|
|
29476
29230
|
kind,
|
|
@@ -29589,7 +29343,7 @@ a doubly-escaped tag", "which cite a 2026 date"). Backslashes must be escaped.`,
|
|
|
29589
29343
|
tagSpanning ? `${tagSpanning} match(es) were found only after stripping markup \u2014 the phrase is split by an inline tag there.` : null,
|
|
29590
29344
|
results2.length === 0 ? "No bodies matched that pattern, searched both as stored HTML and tag-stripped. Note that Trilium's %= pre-filter reads a striptags'd copy, so a pattern anchored ON tags may never reach verification \u2014 consistency() scans exhaustively if you need certainty." : null
|
|
29591
29345
|
].filter(Boolean);
|
|
29592
|
-
return
|
|
29346
|
+
return txt3({
|
|
29593
29347
|
mode: "regex",
|
|
29594
29348
|
pattern: regex,
|
|
29595
29349
|
results: results2,
|
|
@@ -29602,7 +29356,7 @@ a doubly-escaped tag", "which cite a 2026 date"). Backslashes must be escaped.`,
|
|
|
29602
29356
|
const notes = await run(q, fast, ord);
|
|
29603
29357
|
const filtered = notes.filter(filterNote).slice(0, max);
|
|
29604
29358
|
const results2 = await Promise.all(filtered.map(buildResult));
|
|
29605
|
-
return
|
|
29359
|
+
return txt3({ results: results2, ...results2.length === 0 ? noMatch : {} });
|
|
29606
29360
|
}
|
|
29607
29361
|
const scores = new Map;
|
|
29608
29362
|
const add = (notes, weight) => {
|
|
@@ -29652,7 +29406,7 @@ a doubly-escaped tag", "which cite a 2026 date"). Backslashes must be escaped.`,
|
|
|
29652
29406
|
return fuzzyIds.has(note.noteId) ? { ...row, fuzzy: true } : row;
|
|
29653
29407
|
}));
|
|
29654
29408
|
const fuzzyCount = results.filter((r) => ("fuzzy" in r)).length;
|
|
29655
|
-
return
|
|
29409
|
+
return txt3({
|
|
29656
29410
|
results,
|
|
29657
29411
|
...fuzzyCount ? { fuzzyMatches: `${fuzzyCount} result(s) came from the fuzzy pass (marked fuzzy: true) \u2014 treat them as leads to verify, not as answers.` } : {},
|
|
29658
29412
|
...results.length === 0 ? noMatch : {}
|
|
@@ -29715,7 +29469,7 @@ Use recall() for keyword or full-text search instead.`, {
|
|
|
29715
29469
|
const k = ownedLabel(n, "noteType");
|
|
29716
29470
|
return k && k !== "domain";
|
|
29717
29471
|
}).length;
|
|
29718
|
-
return
|
|
29472
|
+
return txt3({
|
|
29719
29473
|
domain: name,
|
|
29720
29474
|
slug,
|
|
29721
29475
|
knowledgeDomain,
|
|
@@ -29767,7 +29521,7 @@ find=; to add content next to a heading without touching its body, use mode="bef
|
|
|
29767
29521
|
identity: exports_external.string().optional().describe(`append mode: the addendum's identification line \u2014 "LLM \xB7 environment \xB7 agent/mode [\xB7 Run N]"; REQUIRED when appending to a thread (unless the body already leads with the h3)`),
|
|
29768
29522
|
icon: exports_external.string().optional().describe('Display icon \u2014 a boxicons class ("bx bx-brain") or a bare name; normalized server-side'),
|
|
29769
29523
|
date: exports_external.string().optional().describe("ISO date (default: today)")
|
|
29770
|
-
}, async ({ noteId, body, title, section, occurrence, mode
|
|
29524
|
+
}, async ({ noteId, body, title, section, occurrence, mode, find, nth, edits, identity, icon, date: date4 }) => {
|
|
29771
29525
|
if (isContainer(b(), noteId))
|
|
29772
29526
|
return err("protected_note", `Note ${noteId} is a container \u2014 its content cannot be edited directly.`, "Use remember() to write to singletons, or specify a content note id.");
|
|
29773
29527
|
const d = date4 ?? today();
|
|
@@ -29880,7 +29634,7 @@ find=; to add content next to a heading without touching its body, use mode="bef
|
|
|
29880
29634
|
results.push({ find: e.find, replaced: applied.count, matchMode: applied.matchMode });
|
|
29881
29635
|
}
|
|
29882
29636
|
if (total === 0) {
|
|
29883
|
-
return
|
|
29637
|
+
return txt3({
|
|
29884
29638
|
ok: true,
|
|
29885
29639
|
noteId,
|
|
29886
29640
|
mode: edits ? "edits" : "find-replace",
|
|
@@ -29905,7 +29659,7 @@ find=; to add content next to a heading without touching its body, use mode="bef
|
|
|
29905
29659
|
const rels = relationSnippet(note);
|
|
29906
29660
|
const missed = results.filter((r) => r.replaced === 0);
|
|
29907
29661
|
const structure = structuralFindings(noteKind, stamped.html);
|
|
29908
|
-
return
|
|
29662
|
+
return txt3({
|
|
29909
29663
|
ok: true,
|
|
29910
29664
|
noteId,
|
|
29911
29665
|
mode: edits ? "edits" : "find-replace",
|
|
@@ -29921,15 +29675,15 @@ find=; to add content next to a heading without touching its body, use mode="bef
|
|
|
29921
29675
|
...replacedResult.warnings.length ? { sanitized: replacedResult.warnings } : {}
|
|
29922
29676
|
});
|
|
29923
29677
|
}
|
|
29924
|
-
if ((
|
|
29925
|
-
return err("missing_param", `mode="${
|
|
29926
|
-
if (
|
|
29678
|
+
if ((mode === "before" || mode === "after") && !section)
|
|
29679
|
+
return err("missing_param", `mode="${mode}" inserts relative to a heading and needs one.`, 'Pass section="<heading text>" alongside it, or use mode="append" for a whole-note addendum.');
|
|
29680
|
+
if (mode === "remove" && !section)
|
|
29927
29681
|
return err("missing_param", 'mode="remove" deletes a section and needs one.', 'Pass section="<heading text>". To delete the whole note use forget(noteId).');
|
|
29928
|
-
if (
|
|
29682
|
+
if (mode === "remove" && section) {
|
|
29929
29683
|
const current = await trilium.getNoteContent(noteId).catch(() => "");
|
|
29930
29684
|
const result = setSection(current, section, "", "remove", occurrence ?? 1);
|
|
29931
29685
|
if (!result.matched) {
|
|
29932
|
-
return
|
|
29686
|
+
return txt3({
|
|
29933
29687
|
ok: true,
|
|
29934
29688
|
noteId,
|
|
29935
29689
|
mode: `section:remove:${section}`,
|
|
@@ -29948,7 +29702,7 @@ find=; to add content next to a heading without touching its body, use mode="bef
|
|
|
29948
29702
|
if (labelOf2(note, "status") === "dormant")
|
|
29949
29703
|
await trilium.updateLabelValue(noteId, "status", "active");
|
|
29950
29704
|
const relsOnRemove = relationSnippet(note);
|
|
29951
|
-
return
|
|
29705
|
+
return txt3({
|
|
29952
29706
|
ok: true,
|
|
29953
29707
|
noteId,
|
|
29954
29708
|
mode: `section:remove:${section}`,
|
|
@@ -29969,13 +29723,13 @@ find=; to add content next to a heading without touching its body, use mode="bef
|
|
|
29969
29723
|
const sanitized = renderBody(body);
|
|
29970
29724
|
const html = sanitized.html;
|
|
29971
29725
|
warnings.push(...sanitized.warnings);
|
|
29972
|
-
if (noteKind === "thread" &&
|
|
29726
|
+
if (noteKind === "thread" && mode !== "replace" && /<h[2-4](?:\s[^>]*)?>\s*Resolution\s*<\/h[2-4]>/i.test(html) && !isOpenResolutionOnly(html)) {
|
|
29973
29727
|
return err("structure_violation", "Thread bodies must not carry a FILLED or duplicate Resolution \u2014 a thread has exactly one Resolution, at the bottom, owned by resolve(). A single empty '\u2014 open \u2014' placeholder is the canonical skeleton and is accepted.", "Remove the Resolution content from the body; close the thread with resolve(noteId, outcome) when the work completes.");
|
|
29974
29728
|
}
|
|
29975
29729
|
const current = await trilium.getNoteContent(noteId).catch(() => "");
|
|
29976
29730
|
if (section) {
|
|
29977
29731
|
await trilium.createRevision(noteId).catch(() => null);
|
|
29978
|
-
const sectionMode =
|
|
29732
|
+
const sectionMode = mode === "append" || mode === "before" || mode === "after" ? mode : "replace";
|
|
29979
29733
|
const result = setSection(current, section, html, sectionMode, occurrence ?? 1);
|
|
29980
29734
|
finalContent = bumpLastUpdated(result.html, d).html;
|
|
29981
29735
|
await trilium.updateNoteContent(noteId, finalContent);
|
|
@@ -29988,7 +29742,7 @@ find=; to add content next to a heading without touching its body, use mode="bef
|
|
|
29988
29742
|
...result.appendedAtLevel ? { appendedAtLevel: result.appendedAtLevel } : {},
|
|
29989
29743
|
...result.available?.length ? { available: result.available } : {}
|
|
29990
29744
|
};
|
|
29991
|
-
} else if (
|
|
29745
|
+
} else if (mode === "replace") {
|
|
29992
29746
|
await trilium.createRevision(noteId).catch(() => null);
|
|
29993
29747
|
finalContent = bumpLastUpdated(html, d).html;
|
|
29994
29748
|
await trilium.updateNoteContent(noteId, finalContent);
|
|
@@ -30000,13 +29754,13 @@ find=; to add content next to a heading without touching its body, use mode="bef
|
|
|
30000
29754
|
${html}` : html;
|
|
30001
29755
|
threadEntryResult = await appendThreadEntry(noteId, block, d);
|
|
30002
29756
|
if (threadEntryResult.action === "already_written") {
|
|
30003
|
-
return
|
|
29757
|
+
return txt3({ ok: true, noteId, mode: "already_written", entryId: threadEntryResult.noteId, date: d });
|
|
30004
29758
|
}
|
|
30005
29759
|
} else {
|
|
30006
29760
|
const block = identity && !leadingIdentification(html) ? `<h3>${escapeHtml(identity)}</h3>
|
|
30007
29761
|
${html}` : html;
|
|
30008
29762
|
if (isDuplicateAppend(current, block))
|
|
30009
|
-
return
|
|
29763
|
+
return txt3({ ok: true, noteId, mode: "already_written", date: d });
|
|
30010
29764
|
await trilium.createRevision(noteId).catch(() => null);
|
|
30011
29765
|
finalContent = bumpLastUpdated(insertBeforeResolution(closeDangling(current), `<h2>Addendum \u2014 ${d}</h2>
|
|
30012
29766
|
${block}`), d).html;
|
|
@@ -30020,12 +29774,12 @@ ${block}`), d).html;
|
|
|
30020
29774
|
await trilium.updateLabelValue(noteId, "status", "active");
|
|
30021
29775
|
const relations = relationSnippet(note);
|
|
30022
29776
|
const targeted = occurrence && occurrence > 1 ? ` occurrence ${occurrence} of` : "";
|
|
30023
|
-
const verb =
|
|
29777
|
+
const verb = mode === "append" ? "appended to" : mode === "before" || mode === "after" ? `inserted ${mode}` : "replaced";
|
|
30024
29778
|
const sectionHint = !sectionResult ? undefined : !sectionResult.matched ? `No existing "${section}" heading found at h2/h3/h4 \u2014 wrote a NEW h${sectionMiss.appendedAtLevel ?? 2} section instead of replacing anything. Check available= for the note's real heading texts, then re-target.` : sectionResult.headingCount > 1 && !occurrence ? `${sectionResult.headingCount} headings match "${section}" \u2014 the FIRST was ${verb}. Pass occurrence= (1-${sectionResult.headingCount}) to reach a different one; outline(noteId) lists them.` : sectionResult.headingCount > 1 ? `${sectionResult.headingCount} headings match "${section}" \u2014${targeted} that one was ${verb}.` : undefined;
|
|
30025
|
-
return
|
|
29779
|
+
return txt3({
|
|
30026
29780
|
ok: true,
|
|
30027
29781
|
noteId,
|
|
30028
|
-
mode: body ? section ? `section:${
|
|
29782
|
+
mode: body ? section ? `section:${mode === "before" || mode === "after" ? `insert-${mode}` : mode === "append" ? "append-within" : "replace"}:${section}` : mode ?? "append" : titled.retitled ? "rename" : iconSet ? "icon" : "no-op",
|
|
30029
29783
|
date: d,
|
|
30030
29784
|
...sectionResult ? { matched: sectionResult.matched, headingCount: sectionResult.headingCount } : {},
|
|
30031
29785
|
...sectionResult?.replacedSubsections?.length ? {
|
|
@@ -30070,7 +29824,7 @@ status, and archive it in place (it stays where it is, excluded from default rec
|
|
|
30070
29824
|
followUps.push(`superseded by ${supersededBy}`);
|
|
30071
29825
|
}
|
|
30072
29826
|
const relations = relationSnippet(note);
|
|
30073
|
-
return
|
|
29827
|
+
return txt3({
|
|
30074
29828
|
ok: true,
|
|
30075
29829
|
noteId,
|
|
30076
29830
|
kind: labelOf2(note, "noteType") ?? "note",
|
|
@@ -30110,7 +29864,7 @@ Use when a resolved or dormant thread resurfaces as live work.`, {
|
|
|
30110
29864
|
await trilium.updateLabelValue(noteId, "updated", d);
|
|
30111
29865
|
}
|
|
30112
29866
|
const relations = relationSnippet(note);
|
|
30113
|
-
return
|
|
29867
|
+
return txt3({
|
|
30114
29868
|
ok: true,
|
|
30115
29869
|
noteId,
|
|
30116
29870
|
kind: labelOf2(note, "noteType") ?? "note",
|
|
@@ -30153,7 +29907,7 @@ setting updated itself.`, {
|
|
|
30153
29907
|
await trilium.addLabel(noteId, l.name, l.value, l.inheritable ?? false).catch(() => null);
|
|
30154
29908
|
applied.push(l.value ? `${l.name}=${l.value}` : l.name);
|
|
30155
29909
|
}
|
|
30156
|
-
return
|
|
29910
|
+
return txt3({
|
|
30157
29911
|
ok: true,
|
|
30158
29912
|
noteId,
|
|
30159
29913
|
name,
|
|
@@ -30167,11 +29921,11 @@ setting updated itself.`, {
|
|
|
30167
29921
|
if (remove) {
|
|
30168
29922
|
const attr = note.attributes.find((a) => a.type === "label" && a.name === name);
|
|
30169
29923
|
if (!attr)
|
|
30170
|
-
return
|
|
29924
|
+
return txt3({ ok: true, noteId, name, action: "not_found" });
|
|
30171
29925
|
await trilium.deleteAttribute(attr.attributeId);
|
|
30172
29926
|
if (name !== "updated")
|
|
30173
29927
|
await trilium.updateLabelValue(noteId, "updated", today()).catch(() => null);
|
|
30174
|
-
return
|
|
29928
|
+
return txt3({ ok: true, noteId, name, action: "removed" });
|
|
30175
29929
|
}
|
|
30176
29930
|
if (value === undefined)
|
|
30177
29931
|
return err("missing_param", "label() requires value unless remove=true.", 'Add value="..." or set remove=true.');
|
|
@@ -30181,7 +29935,7 @@ setting updated itself.`, {
|
|
|
30181
29935
|
await trilium.updateLabelValue(noteId, name, finalValue);
|
|
30182
29936
|
if (name !== "updated")
|
|
30183
29937
|
await trilium.updateLabelValue(noteId, "updated", today()).catch(() => null);
|
|
30184
|
-
return
|
|
29938
|
+
return txt3({ ok: true, noteId, name, value: finalValue, action: "set" });
|
|
30185
29939
|
});
|
|
30186
29940
|
server.tool("connect", `Wire a typed relation between two notes when you notice a real connection.
|
|
30187
29941
|
Vocabulary (closed): ${RelationTypes.join(" | ")}.
|
|
@@ -30197,7 +29951,7 @@ calling twice is safe. Use remove=true to delete an edge.`, {
|
|
|
30197
29951
|
await trilium.removeRelation(fromNoteId, relation, toNoteId).catch(() => null);
|
|
30198
29952
|
if (symmetric)
|
|
30199
29953
|
await trilium.removeRelation(toNoteId, relation, fromNoteId).catch(() => null);
|
|
30200
|
-
return
|
|
29954
|
+
return txt3({ ok: true, removed: `${fromNoteId} ~${relation}\u2192 ${toNoteId}` });
|
|
30201
29955
|
}
|
|
30202
29956
|
const from = await trilium.getNote(fromNoteId);
|
|
30203
29957
|
const exists = from.attributes.some((a) => a.type === "relation" && a.name === relation && a.value === toNoteId);
|
|
@@ -30209,7 +29963,7 @@ calling twice is safe. Use remove=true to delete an edge.`, {
|
|
|
30209
29963
|
if (!reverseExists)
|
|
30210
29964
|
await trilium.addRelation(toNoteId, relation, fromNoteId);
|
|
30211
29965
|
}
|
|
30212
|
-
return
|
|
29966
|
+
return txt3({ ok: true, action: exists ? "already-existed" : "created", edge: `${fromNoteId} ~${relation}${symmetric ? "\u2194" : "\u2192"} ${toNoteId}` });
|
|
30213
29967
|
});
|
|
30214
29968
|
server.tool("explore", `Walk the relation graph around a note.
|
|
30215
29969
|
mode=links what this note points to (one hop)
|
|
@@ -30222,8 +29976,8 @@ calling twice is safe. Use remove=true to delete an edge.`, {
|
|
|
30222
29976
|
toNoteId: exports_external.string().optional().describe("Target note (mode=path)"),
|
|
30223
29977
|
depth: exports_external.number().optional().describe("Hops for neighborhood (default: 2)"),
|
|
30224
29978
|
relation: exports_external.string().optional().describe("Restrict to one relation type")
|
|
30225
|
-
}, async ({ noteId, mode
|
|
30226
|
-
switch (
|
|
29979
|
+
}, async ({ noteId, mode, toNoteId, depth, relation }) => {
|
|
29980
|
+
switch (mode) {
|
|
30227
29981
|
case "links": {
|
|
30228
29982
|
const note = await trilium.getNote(noteId);
|
|
30229
29983
|
const rels = note.attributes.filter((a) => a.type === "relation" && a.name !== "template" && (!relation || a.name === relation));
|
|
@@ -30231,25 +29985,25 @@ calling twice is safe. Use remove=true to delete an edge.`, {
|
|
|
30231
29985
|
const n = await trilium.getNote(r.value).catch(() => null);
|
|
30232
29986
|
return n ? { id: n.noteId, title: n.title, via: r.name } : null;
|
|
30233
29987
|
}));
|
|
30234
|
-
return
|
|
29988
|
+
return txt3({ mode, links: linked.filter(Boolean) });
|
|
30235
29989
|
}
|
|
30236
29990
|
case "backlinks": {
|
|
30237
29991
|
const backlinks = await trilium.getBacklinks(noteId);
|
|
30238
|
-
return
|
|
29992
|
+
return txt3({ mode, backlinks: relation ? backlinks.filter((b2) => b2.relationName === relation) : backlinks });
|
|
30239
29993
|
}
|
|
30240
29994
|
case "neighborhood": {
|
|
30241
29995
|
const nodes = await trilium.getNeighborhood(noteId, depth ?? 2, relation);
|
|
30242
|
-
return
|
|
29996
|
+
return txt3({ mode, nodeCount: nodes.length, nodes });
|
|
30243
29997
|
}
|
|
30244
29998
|
case "path": {
|
|
30245
29999
|
if (!toNoteId)
|
|
30246
30000
|
throw new Error("mode=path requires toNoteId");
|
|
30247
30001
|
if (noteId === toNoteId) {
|
|
30248
30002
|
const self = await trilium.getNote(noteId);
|
|
30249
|
-
return
|
|
30003
|
+
return txt3({ mode, found: true, hops: 0, path: [{ noteId, title: self.title, depth: 0 }] });
|
|
30250
30004
|
}
|
|
30251
30005
|
const path = await trilium.findNeuralPath(noteId, toNoteId, depth ?? 6);
|
|
30252
|
-
return
|
|
30006
|
+
return txt3(path ? { mode, found: true, hops: path.length - 1, path } : { mode, found: false });
|
|
30253
30007
|
}
|
|
30254
30008
|
}
|
|
30255
30009
|
});
|
|
@@ -30343,7 +30097,7 @@ anyway when scope is wide and speed matters more than completeness.`, {
|
|
|
30343
30097
|
noCapture.push(stub);
|
|
30344
30098
|
}
|
|
30345
30099
|
if (!hasCaptureGroup) {
|
|
30346
|
-
return
|
|
30100
|
+
return txt3({
|
|
30347
30101
|
mode: "presence",
|
|
30348
30102
|
pattern,
|
|
30349
30103
|
scan: fast ? "fast (%= pre-filter)" : "exhaustive",
|
|
@@ -30355,7 +30109,7 @@ anyway when scope is wide and speed matters more than completeness.`, {
|
|
|
30355
30109
|
}
|
|
30356
30110
|
const groups = [...byValue.entries()].map(([value, notes2]) => ({ value, count: notes2.length, notes: notes2 })).sort((a, b2) => b2.count - a.count);
|
|
30357
30111
|
const agrees = groups.length <= 1;
|
|
30358
|
-
return
|
|
30112
|
+
return txt3({
|
|
30359
30113
|
mode: "consistency",
|
|
30360
30114
|
pattern,
|
|
30361
30115
|
scan: fast ? "fast (%= pre-filter)" : "exhaustive",
|
|
@@ -30390,7 +30144,7 @@ remember(revision=) matches on exactly) without reading the note.`, {
|
|
|
30390
30144
|
const report = structureReport(content);
|
|
30391
30145
|
const tables = headings.map((h) => ({ section: h.text, keys: tableRows(content, h.text).map((c) => c[0]).filter(Boolean) })).filter((t) => t.keys.length);
|
|
30392
30146
|
const sizeWarning = report.size >= LARGE_NOTE_CHARS ? `${Math.round(report.size / 1000)}k characters \u2014 approaching the tool output ceiling. Prefer section=/find= edits and targeted reads over whole-note reads.` : undefined;
|
|
30393
|
-
return
|
|
30147
|
+
return txt3({
|
|
30394
30148
|
noteId,
|
|
30395
30149
|
title: note.title,
|
|
30396
30150
|
kind: labelOf2(note, "noteType"),
|
|
@@ -30481,7 +30235,7 @@ miss is diagnosed in the same call rather than in three more.`, {
|
|
|
30481
30235
|
}
|
|
30482
30236
|
const labels = note.attributes.filter((a) => a.type === "label").map((a) => ({ name: a.name, value: a.value, ...a.isInheritable ? { inheritable: true } : {} }));
|
|
30483
30237
|
const relations = relationSnippet(note, 50);
|
|
30484
|
-
return
|
|
30238
|
+
return txt3({
|
|
30485
30239
|
id: note.noteId,
|
|
30486
30240
|
title: note.title,
|
|
30487
30241
|
kind: labelOf2(note, "noteType"),
|
|
@@ -30542,7 +30296,7 @@ broken ones, so staleness arrives as a maintenance finding instead of a surprise
|
|
|
30542
30296
|
}, async ({ assertion, check: check2, noteId, intervalDays, claimId, holds, evidence, status, retire }) => {
|
|
30543
30297
|
const cfg = b();
|
|
30544
30298
|
if (!cfg.root)
|
|
30545
|
-
return
|
|
30299
|
+
return txt3({ status: "uninitialized", action: "Run bootstrap first." });
|
|
30546
30300
|
const d = today();
|
|
30547
30301
|
const container = await resolveClaimsContainer();
|
|
30548
30302
|
const lapsed = (n) => {
|
|
@@ -30566,11 +30320,11 @@ broken ones, so staleness arrives as a maintenance finding instead of a surprise
|
|
|
30566
30320
|
return err("not_found", `${claimId} is not a claim.`, "List them with claim() and no arguments.");
|
|
30567
30321
|
if (retire) {
|
|
30568
30322
|
await trilium.addLabel(claimId, "archived", "").catch(() => null);
|
|
30569
|
-
return
|
|
30323
|
+
return txt3({ ok: true, claimId, action: "retired", assertion: note.title });
|
|
30570
30324
|
}
|
|
30571
30325
|
if (holds === undefined) {
|
|
30572
30326
|
const content2 = await trilium.getNoteContent(claimId).catch(() => "");
|
|
30573
|
-
return
|
|
30327
|
+
return txt3({ ...row(note), check: getSection(content2, "Check").content, history: getSection(content2, "Verifications").content, relations: relationSnippet(note) });
|
|
30574
30328
|
}
|
|
30575
30329
|
if (!evidence)
|
|
30576
30330
|
return err("missing_param", "A verification needs evidence.", 'Pass evidence="<what you actually observed>" \u2014 the output, the count, the response. Recording a verdict without it makes the register a record of opinions.');
|
|
@@ -30583,7 +30337,7 @@ broken ones, so staleness arrives as a maintenance finding instead of a surprise
|
|
|
30583
30337
|
await trilium.updateLabelValue(claimId, "claimState", holds ? "holding" : "broken").catch(() => null);
|
|
30584
30338
|
await trilium.updateLabelValue(claimId, "updated", d).catch(() => null);
|
|
30585
30339
|
const refreshed = await trilium.getNote(claimId).catch(() => note);
|
|
30586
|
-
return
|
|
30340
|
+
return txt3({
|
|
30587
30341
|
ok: true,
|
|
30588
30342
|
action: holds ? "verified" : "marked-broken",
|
|
30589
30343
|
...row(refreshed),
|
|
@@ -30608,7 +30362,7 @@ broken ones, so staleness arrives as a maintenance finding instead of a surprise
|
|
|
30608
30362
|
await trilium.updateNoteContent(existing.noteId, setSection(current, "Check", `<p>${escapeHtml(check2)}</p>`, "replace").html);
|
|
30609
30363
|
if (intervalDays)
|
|
30610
30364
|
await trilium.updateLabelValue(existing.noteId, "interval", String(intervalDays)).catch(() => null);
|
|
30611
|
-
return
|
|
30365
|
+
return txt3({ ok: true, action: "updated", ...row(existing), note: "A claim with this assertion already existed \u2014 its check was updated and its verification history kept." });
|
|
30612
30366
|
}
|
|
30613
30367
|
const created = await trilium.createNote(container, title, body);
|
|
30614
30368
|
const id = created.note.noteId;
|
|
@@ -30619,7 +30373,7 @@ broken ones, so staleness arrives as a maintenance finding instead of a surprise
|
|
|
30619
30373
|
await trilium.addLabel(id, "iconClass", "bx bx-check-shield").catch(() => null);
|
|
30620
30374
|
if (noteId)
|
|
30621
30375
|
await trilium.addRelation(id, "derivedFrom", noteId).catch(() => null);
|
|
30622
|
-
return
|
|
30376
|
+
return txt3({
|
|
30623
30377
|
ok: true,
|
|
30624
30378
|
action: "registered",
|
|
30625
30379
|
claimId: id,
|
|
@@ -30633,7 +30387,7 @@ broken ones, so staleness arrives as a maintenance finding instead of a surprise
|
|
|
30633
30387
|
const rows = all.map(row).filter((r) => !status || status === "all" || r.state === status);
|
|
30634
30388
|
const broken = rows.filter((r) => r.state === "broken").length;
|
|
30635
30389
|
const stale = rows.filter((r) => r.state === "lapsed").length;
|
|
30636
|
-
return
|
|
30390
|
+
return txt3({
|
|
30637
30391
|
total: rows.length,
|
|
30638
30392
|
...broken ? { broken } : {},
|
|
30639
30393
|
...stale ? { needingReverification: stale } : {},
|
|
@@ -30661,7 +30415,7 @@ HTML, so a formatting-only change is a real difference and shows as one.`, {
|
|
|
30661
30415
|
}, async ({ noteId, revisionId, context }) => {
|
|
30662
30416
|
const revisions = await trilium.getNoteRevisions(noteId).catch(() => []);
|
|
30663
30417
|
if (!revisions.length)
|
|
30664
|
-
return
|
|
30418
|
+
return txt3({ noteId, note: "No revisions \u2014 this note has not been written through a content-mutating tool yet, or its revisions have been pruned.", revisions: [] });
|
|
30665
30419
|
const target = revisionId ? revisions.find((r) => r.revisionId === revisionId) : revisions[0];
|
|
30666
30420
|
if (!target)
|
|
30667
30421
|
return err("not_found", `Revision ${revisionId} does not belong to note ${noteId}.`, `Available: ${revisions.slice(0, 10).map((r) => r.revisionId).join(", ")}`);
|
|
@@ -30676,7 +30430,7 @@ HTML, so a formatting-only change is a real difference and shows as one.`, {
|
|
|
30676
30430
|
...r.revisionId === target.revisionId ? { compared: true } : {}
|
|
30677
30431
|
}));
|
|
30678
30432
|
if (before === after) {
|
|
30679
|
-
return
|
|
30433
|
+
return txt3({
|
|
30680
30434
|
noteId,
|
|
30681
30435
|
comparedTo: target.revisionId,
|
|
30682
30436
|
identical: true,
|
|
@@ -30699,7 +30453,7 @@ HTML, so a formatting-only change is a real difference and shows as one.`, {
|
|
|
30699
30453
|
const removed = a.slice(head, a.length - tail);
|
|
30700
30454
|
const added = bLines.slice(head, bLines.length - tail);
|
|
30701
30455
|
const cap = (arr) => arr.length > 40 ? [...arr.slice(0, 40), `\u2026 ${arr.length - 40} more line(s)`] : arr;
|
|
30702
|
-
return
|
|
30456
|
+
return txt3({
|
|
30703
30457
|
noteId,
|
|
30704
30458
|
comparedTo: target.revisionId,
|
|
30705
30459
|
revisionDate: target.dateCreated?.slice(0, 16),
|
|
@@ -30734,16 +30488,16 @@ inspect(noteId); remove with detach().`, {
|
|
|
30734
30488
|
if (!existing)
|
|
30735
30489
|
return err("not_found", `No attachment titled "${title}" on note ${noteId}.`, "inspect(noteId) lists its attachments; provide content to create this one.");
|
|
30736
30490
|
const data = await trilium.getAttachmentContent(existing.attachmentId).catch(() => "");
|
|
30737
|
-
return
|
|
30491
|
+
return txt3({ id: existing.attachmentId, noteId, title, mime: existing.mime, role: existing.role, size: existing.contentLength, content: data });
|
|
30738
30492
|
}
|
|
30739
30493
|
if (existing) {
|
|
30740
30494
|
await trilium.updateAttachmentContent(existing.attachmentId, content, mime ?? existing.mime);
|
|
30741
30495
|
if (mime && mime !== existing.mime)
|
|
30742
30496
|
await trilium.updateAttachment(existing.attachmentId, { mime }).catch(() => null);
|
|
30743
|
-
return
|
|
30497
|
+
return txt3({ action: "updated", id: existing.attachmentId, noteId, title, mime: mime ?? existing.mime });
|
|
30744
30498
|
}
|
|
30745
30499
|
const created = await trilium.createAttachment(noteId, title, mime ?? "text/plain", content, role ?? "file");
|
|
30746
|
-
return
|
|
30500
|
+
return txt3({ action: "created", id: created.attachmentId, noteId, title, mime: created.mime, role: created.role });
|
|
30747
30501
|
});
|
|
30748
30502
|
server.tool("detach", `Remove an attachment from a note \u2014 by attachmentId directly, or by (noteId + title).
|
|
30749
30503
|
Permanent: attachments have no archive tier; re-attach() from source to undo. Retry-safe \u2014
|
|
@@ -30758,7 +30512,7 @@ an already-removed target returns cleanly instead of erroring.`, {
|
|
|
30758
30512
|
return err("missing_param", "detach() needs attachmentId, or noteId + title.", "inspect(noteId) lists attachments with their ids.");
|
|
30759
30513
|
const found = (await trilium.getNoteAttachments(noteId).catch(() => [])).find((a) => a.title === title);
|
|
30760
30514
|
if (!found)
|
|
30761
|
-
return
|
|
30515
|
+
return txt3({ ok: true, action: "already_removed", noteId, title });
|
|
30762
30516
|
id = found.attachmentId;
|
|
30763
30517
|
}
|
|
30764
30518
|
try {
|
|
@@ -30766,10 +30520,10 @@ an already-removed target returns cleanly instead of erroring.`, {
|
|
|
30766
30520
|
} catch (e) {
|
|
30767
30521
|
const msg = e instanceof Error ? e.message : String(e);
|
|
30768
30522
|
if (msg.includes("404"))
|
|
30769
|
-
return
|
|
30523
|
+
return txt3({ ok: true, action: "already_removed", attachmentId: id });
|
|
30770
30524
|
throw e;
|
|
30771
30525
|
}
|
|
30772
|
-
return
|
|
30526
|
+
return txt3({ ok: true, removed: id, ...noteId ? { noteId, title } : {} });
|
|
30773
30527
|
});
|
|
30774
30528
|
server.tool("addendum", `Search Master, LLM singletons (responsibilities + protocols only, not diary), and Knowledge
|
|
30775
30529
|
for notes containing pending addendum blocks that need to be folded into the main content.
|
|
@@ -30783,7 +30537,7 @@ entries, and logs \u2014 records by nature whose history has value. Everywhere e
|
|
|
30783
30537
|
Returns note IDs, titles, kinds, and content snippets so you can identify what to fold in.`, {}, async () => {
|
|
30784
30538
|
const cfg = b();
|
|
30785
30539
|
if (!cfg.root)
|
|
30786
|
-
return
|
|
30540
|
+
return txt3({ error: "BrainLLM not bootstrapped \u2014 run bootstrap." });
|
|
30787
30541
|
await markStep("addendum");
|
|
30788
30542
|
const searchIn = (ancestorNoteId) => trilium.searchNotes("Addendum", { ancestorNoteId, limit: 50 }).catch(() => ({ results: [] }));
|
|
30789
30543
|
const [masterRes, llmRes, knowledgeRes] = await Promise.all([
|
|
@@ -30821,7 +30575,7 @@ Returns note IDs, titles, kinds, and content snippets so you can identify what t
|
|
|
30821
30575
|
};
|
|
30822
30576
|
}));
|
|
30823
30577
|
const found = notes.filter(Boolean);
|
|
30824
|
-
return
|
|
30578
|
+
return txt3({
|
|
30825
30579
|
found: found.length,
|
|
30826
30580
|
notes: found,
|
|
30827
30581
|
...found.length === 0 ? { note: "No notes with pending addendums." } : { hint: "Fold each addendum into its note: read the block, then revise(noteId, section='<heading>', body='<merged content>', mode='replace') to absorb it into the right section. These notes must be left clean \u2014 no addendum markers. Addendum-style history belongs only in sessions, diary, and logs. Scoped/autonomous agents: fold only what's in your lane \u2014 leaving personal or out-of-scope addendums for the next interactive session is correct, and calling this tool is what satisfies the pre-close gate." }
|
|
@@ -30868,7 +30622,7 @@ coverage names any pass that hit a cap, so a short list is never mistaken for a
|
|
|
30868
30622
|
...ack?.length ? { ack } : {},
|
|
30869
30623
|
...repair?.length ? { repair } : {}
|
|
30870
30624
|
});
|
|
30871
|
-
return
|
|
30625
|
+
return txt3(report);
|
|
30872
30626
|
});
|
|
30873
30627
|
server.tool("forget", `Archive a note (default) or hard-delete it (hard=true). Archiving keeps it in place,
|
|
30874
30628
|
hidden from default recall \u2014 the safe choice and the only one for anything with history.
|
|
@@ -30884,7 +30638,7 @@ re-wire with connect() first). To undo an archive, use recover().`, {
|
|
|
30884
30638
|
if (hard) {
|
|
30885
30639
|
const backlinks = await trilium.getBacklinks(noteId).catch(() => []);
|
|
30886
30640
|
if (backlinks.length > 0) {
|
|
30887
|
-
return
|
|
30641
|
+
return txt3({
|
|
30888
30642
|
blocked: true,
|
|
30889
30643
|
why: "Other notes still link here. Re-wire or remove these relations first (connect with remove=true), or archive instead.",
|
|
30890
30644
|
backlinks
|
|
@@ -30894,7 +30648,7 @@ re-wire with connect() first). To undo an archive, use recover().`, {
|
|
|
30894
30648
|
const parents = note.parentNoteIds ?? [];
|
|
30895
30649
|
if (children.length > 0 || parents.length > 1) {
|
|
30896
30650
|
const childTitles = await Promise.all(children.slice(0, 25).map((id) => trilium.getNote(id).then((c) => `${c.title} [${id}]`).catch(() => id)));
|
|
30897
|
-
return
|
|
30651
|
+
return txt3({
|
|
30898
30652
|
blocked: true,
|
|
30899
30653
|
why: children.length > 0 ? `Hard delete takes the whole subtree \u2014 ${children.length} child note(s) would be destroyed with it.` : `This note is cloned into ${parents.length} containers; deleting it removes it from all of them, not just the one you have in mind.`,
|
|
30900
30654
|
children: childTitles,
|
|
@@ -30903,7 +30657,7 @@ re-wire with connect() first). To undo an archive, use recover().`, {
|
|
|
30903
30657
|
});
|
|
30904
30658
|
}
|
|
30905
30659
|
await trilium.deleteNote(noteId);
|
|
30906
|
-
return
|
|
30660
|
+
return txt3({ ok: true, deleted: noteId, title: note.title, hardDeleted: true });
|
|
30907
30661
|
}
|
|
30908
30662
|
if (reason) {
|
|
30909
30663
|
const current = await trilium.getNoteContent(noteId).catch(() => "");
|
|
@@ -30912,7 +30666,7 @@ re-wire with connect() first). To undo an archive, use recover().`, {
|
|
|
30912
30666
|
await trilium.updateLabelValue(noteId, "closed", today());
|
|
30913
30667
|
await ensureArchivedFlag(trilium, note);
|
|
30914
30668
|
const relations = relationSnippet(note);
|
|
30915
|
-
return
|
|
30669
|
+
return txt3({ ok: true, archived: noteId, title: note.title, ...relations ? { relations } : {} });
|
|
30916
30670
|
});
|
|
30917
30671
|
server.tool("recover", `Restore an archived or resolved note: removes #archived, clears #closed, resets status
|
|
30918
30672
|
to active. Use to undo forget() or reconsider a resolved thread / note. Does not restore
|
|
@@ -30942,7 +30696,7 @@ prior snapshot. For notes deleted from Trilium entirely (not just archived), use
|
|
|
30942
30696
|
await trilium.updateLabelValue(noteId, "updated", d);
|
|
30943
30697
|
}
|
|
30944
30698
|
const relations = relationSnippet(note);
|
|
30945
|
-
return
|
|
30699
|
+
return txt3({
|
|
30946
30700
|
ok: true,
|
|
30947
30701
|
noteId,
|
|
30948
30702
|
kind: labelOf2(note, "noteType") ?? "note",
|
|
@@ -30972,7 +30726,7 @@ its siblings, and a note that satisfies the skeleton can still be the odd one ou
|
|
|
30972
30726
|
domain: kind === "sources" || kind === "information" ? "<Domain>" : undefined,
|
|
30973
30727
|
goal: kind === "thread" ? "<goal statement \u2014 queried from the user>" : undefined
|
|
30974
30728
|
});
|
|
30975
|
-
return
|
|
30729
|
+
return txt3({
|
|
30976
30730
|
kind,
|
|
30977
30731
|
...rule ? { structure: rule.structure, rules: rule.rules } : { note: "No bespoke structure for this kind \u2014 server meta line + body." },
|
|
30978
30732
|
skeleton,
|
|
@@ -30999,7 +30753,7 @@ whole-brain view.`, {
|
|
|
30999
30753
|
}, async ({ noteId, depth, includeArchived }) => {
|
|
31000
30754
|
const cfg = b();
|
|
31001
30755
|
if (!cfg.root)
|
|
31002
|
-
return
|
|
30756
|
+
return txt3({ status: "uninitialized", action: "Run bootstrap first." });
|
|
31003
30757
|
let notes;
|
|
31004
30758
|
if (noteId) {
|
|
31005
30759
|
const hood = await trilium.getNeighborhood(noteId, depth ?? 2);
|
|
@@ -31070,7 +30824,7 @@ whole-brain view.`, {
|
|
|
31070
30824
|
await trilium.addLabel(graphNoteId, "iconClass", "bx bx-network-chart");
|
|
31071
30825
|
}
|
|
31072
30826
|
} catch {}
|
|
31073
|
-
return
|
|
30827
|
+
return txt3({
|
|
31074
30828
|
scope: noteId ? { noteId, depth: depth ?? 2 } : "brain",
|
|
31075
30829
|
nodes: notes.length,
|
|
31076
30830
|
edges: edgeCount,
|
|
@@ -31096,7 +30850,7 @@ exception.`, {
|
|
|
31096
30850
|
}, async ({ date: date4, recap }) => {
|
|
31097
30851
|
const cfg = b();
|
|
31098
30852
|
if (!cfg.root)
|
|
31099
|
-
return
|
|
30853
|
+
return txt3({ status: "uninitialized", action: "Run bootstrap first." });
|
|
31100
30854
|
const todayStr = date4 ?? today();
|
|
31101
30855
|
if (recap) {
|
|
31102
30856
|
const BLOCK = /<h2(?:\s[^>]*)?>\s*Addendum\s*[\u2014\u2013-]\s*([^<]*)<\/h2>([\s\S]*?)(?=<h2(?:\s[^>]*)?>\s*Addendum\s*[\u2014\u2013-]|$)/gi;
|
|
@@ -31129,7 +30883,7 @@ exception.`, {
|
|
|
31129
30883
|
harvest(surface === "thread" ? `thread: ${(await trilium.getNote(n.parentNoteIds[0]).catch(() => null))?.title ?? n.title}` : surface, n.noteId, content);
|
|
31130
30884
|
}
|
|
31131
30885
|
entries.sort((a, b2) => a.time.localeCompare(b2.time));
|
|
31132
|
-
return
|
|
30886
|
+
return txt3({
|
|
31133
30887
|
date: todayStr,
|
|
31134
30888
|
mode: "recap",
|
|
31135
30889
|
instances: new Set(entries.map((e) => e.identity ?? "unattributed")).size,
|
|
@@ -31185,7 +30939,7 @@ exception.`, {
|
|
|
31185
30939
|
if (monthNote) {
|
|
31186
30940
|
deliverables = { id: monthNote.noteId, title: monthNote.title, content: await trilium.getNoteContent(monthNote.noteId).catch(() => "") };
|
|
31187
30941
|
}
|
|
31188
|
-
return
|
|
30942
|
+
return txt3({
|
|
31189
30943
|
date: todayStr,
|
|
31190
30944
|
newDay,
|
|
31191
30945
|
month: monthName,
|
|
@@ -31208,7 +30962,7 @@ Structural containers are excluded; only content notes appear.`, {
|
|
|
31208
30962
|
}, async ({ includeArchived }) => {
|
|
31209
30963
|
const cfg = b();
|
|
31210
30964
|
if (!cfg.root)
|
|
31211
|
-
return
|
|
30965
|
+
return txt3({ status: "uninitialized", action: "Run bootstrap first." });
|
|
31212
30966
|
const fetchFrom = async (id) => {
|
|
31213
30967
|
if (!id)
|
|
31214
30968
|
return [];
|
|
@@ -31273,7 +31027,7 @@ Structural containers are excluded; only content notes appear.`, {
|
|
|
31273
31027
|
Insights: insights.map(row)
|
|
31274
31028
|
};
|
|
31275
31029
|
const total = masterAll.length + llmSingletons.length + llmDiary.length + sessions.length + threads.length + kMaster.length + kDomains.length + insights.length;
|
|
31276
|
-
return
|
|
31030
|
+
return txt3({ total, areas });
|
|
31277
31031
|
});
|
|
31278
31032
|
server.tool("assembly", `What the brain HOLDS \u2014 every note by title, grouped under the surface it lives in, with each
|
|
31279
31033
|
surface's own purpose alongside it. The awareness read: "what do I already know here", answered
|
|
@@ -31299,7 +31053,7 @@ Pass area= to zoom into one surface and get its full detail.`, {
|
|
|
31299
31053
|
}, async ({ area, includeArchived }) => {
|
|
31300
31054
|
const cfg = b();
|
|
31301
31055
|
if (!cfg.root)
|
|
31302
|
-
return
|
|
31056
|
+
return txt3({ status: "uninitialized", action: "Run bootstrap first." });
|
|
31303
31057
|
const fetchFrom = async (id) => {
|
|
31304
31058
|
if (!id)
|
|
31305
31059
|
return [];
|
|
@@ -31390,7 +31144,7 @@ Pass area= to zoom into one surface and get its full detail.`, {
|
|
|
31390
31144
|
claims: claims.length ? { count: claims.length, assertions: titles(claims), note: "Each claim's title IS its assertion. claim(claimId) reads one with its verification history." } : { count: 0, note: "No claims registered \u2014 nothing is currently being checked against the world." }
|
|
31391
31145
|
};
|
|
31392
31146
|
}
|
|
31393
|
-
return
|
|
31147
|
+
return txt3({
|
|
31394
31148
|
brain: await purposeOf(cfg.root),
|
|
31395
31149
|
...area ? { scope: area } : {},
|
|
31396
31150
|
...out,
|
|
@@ -31424,7 +31178,7 @@ immediately, no restart needed.`, {}, async () => {
|
|
|
31424
31178
|
const note = await trilium.getNote(id).catch(() => null);
|
|
31425
31179
|
refreshed.push(note?.title ?? id);
|
|
31426
31180
|
}
|
|
31427
|
-
return
|
|
31181
|
+
return txt3({
|
|
31428
31182
|
status: "already_initialized",
|
|
31429
31183
|
message: `BrainLLM structure exists. Config refreshed at: ${saved}`,
|
|
31430
31184
|
...refreshed.length ? { purposesRefreshed: refreshed, note: "These containers described themselves with text that no longer matched the canonical purpose, and have been re-engraved. Container notes are unreachable through revise(), so bootstrap is the only path that can correct them." } : {},
|
|
@@ -31440,7 +31194,7 @@ immediately, no restart needed.`, {}, async () => {
|
|
|
31440
31194
|
const newConfig = await createBrainLLMStructure(trilium);
|
|
31441
31195
|
const savedPath = saveConfig(newConfig);
|
|
31442
31196
|
brainRef.config = newConfig;
|
|
31443
|
-
return
|
|
31197
|
+
return txt3({
|
|
31444
31198
|
status: "initialized",
|
|
31445
31199
|
message: `BrainLLM bootstrapped. Config written to: ${savedPath}. Ready to use \u2014 no restart needed.`,
|
|
31446
31200
|
config: newConfig
|
|
@@ -31451,9 +31205,252 @@ immediately, no restart needed.`, {}, async () => {
|
|
|
31451
31205
|
registerMemoryTools(server, trilium, brainRef);
|
|
31452
31206
|
registerKnowledgeTools(server, trilium, brainRef);
|
|
31453
31207
|
registerInsightsTools(server, trilium, brainRef);
|
|
31454
|
-
|
|
31455
|
-
|
|
31456
|
-
|
|
31208
|
+
}
|
|
31209
|
+
|
|
31210
|
+
// src/tools-advanced.ts
|
|
31211
|
+
var txt4 = (obj) => ({
|
|
31212
|
+
content: [{ type: "text", text: typeof obj === "string" ? obj : JSON.stringify(obj, null, 2) }]
|
|
31213
|
+
});
|
|
31214
|
+
var noteStub = (n) => ({
|
|
31215
|
+
id: n.noteId,
|
|
31216
|
+
title: n.title,
|
|
31217
|
+
...n.type ? { type: n.type } : {}
|
|
31218
|
+
});
|
|
31219
|
+
var attrStub = (a) => ({
|
|
31220
|
+
id: a.attributeId,
|
|
31221
|
+
noteId: a.noteId,
|
|
31222
|
+
type: a.type,
|
|
31223
|
+
name: a.name,
|
|
31224
|
+
value: a.value
|
|
31225
|
+
});
|
|
31226
|
+
function registerAdvancedTools(server, trilium, brainRef) {
|
|
31227
|
+
const b = () => brainRef.config;
|
|
31228
|
+
server.tool("search_notes", `Raw Trilium query search. Native query language: #label=value, note.title =* "x",
|
|
31229
|
+
note.dateModified >= 'YYYY-MM-DD', AND/OR. Unscoped unless ancestorNoteId is given.`, {
|
|
31230
|
+
query: exports_external.string().describe("Trilium search query"),
|
|
31231
|
+
ancestorNoteId: exports_external.string().optional().describe("Limit to this subtree"),
|
|
31232
|
+
limit: exports_external.number().optional(),
|
|
31233
|
+
orderBy: exports_external.string().optional().describe("title | dateModified | dateCreated"),
|
|
31234
|
+
orderDirection: exports_external.enum(["asc", "desc"]).optional(),
|
|
31235
|
+
fastSearch: exports_external.boolean().optional().describe("Skip content body scan"),
|
|
31236
|
+
includeArchived: exports_external.boolean().optional(),
|
|
31237
|
+
debug: exports_external.boolean().optional().describe("Return query parse debug info")
|
|
31238
|
+
}, async ({ query, ancestorNoteId, limit, orderBy, orderDirection, fastSearch, includeArchived, debug }) => {
|
|
31239
|
+
const result = await trilium.searchNotes(query, {
|
|
31240
|
+
ancestorNoteId,
|
|
31241
|
+
limit,
|
|
31242
|
+
orderBy,
|
|
31243
|
+
orderDirection,
|
|
31244
|
+
fastSearch,
|
|
31245
|
+
includeArchivedNotes: includeArchived,
|
|
31246
|
+
debug
|
|
31247
|
+
});
|
|
31248
|
+
const out = { results: result.results.map(noteStub) };
|
|
31249
|
+
if (result.debugInfo !== undefined)
|
|
31250
|
+
out.debugInfo = result.debugInfo;
|
|
31251
|
+
return txt4(out);
|
|
31252
|
+
});
|
|
31253
|
+
server.tool("get_note", "Full note metadata: title, type, mime, attributes (labels + relations), parent/child ids, dates.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
31254
|
+
const n = await trilium.getNote(noteId);
|
|
31255
|
+
return txt4({
|
|
31256
|
+
id: n.noteId,
|
|
31257
|
+
title: n.title,
|
|
31258
|
+
type: n.type,
|
|
31259
|
+
mime: n.mime,
|
|
31260
|
+
attributes: n.attributes.map((a) => ({ id: a.attributeId, type: a.type, name: a.name, value: a.value })),
|
|
31261
|
+
parents: n.parentNoteIds,
|
|
31262
|
+
children: n.childNoteIds,
|
|
31263
|
+
created: n.dateCreated,
|
|
31264
|
+
modified: n.dateModified
|
|
31265
|
+
});
|
|
31266
|
+
});
|
|
31267
|
+
server.tool("get_note_content", "Raw note content (HTML / text / code).", { noteId: exports_external.string() }, async ({ noteId }) => txt4(await trilium.getNoteContent(noteId)));
|
|
31268
|
+
server.tool("create_note", "Create a note at an explicit parent. Types: text/code/book/canvas/mermaid/relationMap/render/search/file/image.", {
|
|
31269
|
+
parentNoteId: exports_external.string(),
|
|
31270
|
+
title: exports_external.string(),
|
|
31271
|
+
content: exports_external.string(),
|
|
31272
|
+
type: exports_external.enum(["text", "code", "book", "canvas", "mermaid", "relationMap", "render", "search", "file", "image"]).optional(),
|
|
31273
|
+
mime: exports_external.string().optional()
|
|
31274
|
+
}, async ({ parentNoteId, title, content, type, mime }) => {
|
|
31275
|
+
const r = await trilium.createNote(parentNoteId, title, content, type ?? "text", mime);
|
|
31276
|
+
return txt4({ noteId: r.note.noteId, branchId: r.branch.branchId, title: r.note.title });
|
|
31277
|
+
});
|
|
31278
|
+
server.tool("update_note_content", "Replace a note's full content.", { noteId: exports_external.string(), content: exports_external.string() }, async ({ noteId, content }) => {
|
|
31279
|
+
await trilium.updateNoteContent(noteId, content);
|
|
31280
|
+
return txt4({ ok: true, noteId });
|
|
31281
|
+
});
|
|
31282
|
+
server.tool("patch_note", "Mutate note metadata: title, type, or mime.", { noteId: exports_external.string(), title: exports_external.string().optional(), type: exports_external.string().optional(), mime: exports_external.string().optional() }, async ({ noteId, title, type, mime }) => {
|
|
31283
|
+
const fields = {};
|
|
31284
|
+
if (title != null)
|
|
31285
|
+
fields.title = title;
|
|
31286
|
+
if (type != null)
|
|
31287
|
+
fields.type = type;
|
|
31288
|
+
if (mime != null)
|
|
31289
|
+
fields.mime = mime;
|
|
31290
|
+
const note = await trilium.patchNote(noteId, fields);
|
|
31291
|
+
return txt4({ noteId: note.noteId, title: note.title, type: note.type });
|
|
31292
|
+
});
|
|
31293
|
+
server.tool("delete_note", "Hard-delete a note (and its subtree if this is its last branch). Irreversible.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
31294
|
+
await trilium.deleteNote(noteId);
|
|
31295
|
+
return txt4({ ok: true, deleted: noteId });
|
|
31296
|
+
});
|
|
31297
|
+
server.tool("undelete_note", "Recover a recently Trilium-deleted note from Trilium's trash. canBeUndeleted must be true (check note_history). Distinct from recover() which restores BrainLLM-archived notes. Re-applies the BrainLLM label set for the container it lands in \u2014 the restore itself does not bring attributes back, and a note without #noteType is invisible to every read path.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
31298
|
+
await trilium.undeleteNote(noteId);
|
|
31299
|
+
const cfg = b();
|
|
31300
|
+
const note = await trilium.getNote(noteId).catch(() => null);
|
|
31301
|
+
const restored = [];
|
|
31302
|
+
let inferredKind;
|
|
31303
|
+
if (note) {
|
|
31304
|
+
const already = ownedLabel(note, "noteType");
|
|
31305
|
+
if (already) {
|
|
31306
|
+
inferredKind = already;
|
|
31307
|
+
} else {
|
|
31308
|
+
const parents = note.parentNoteIds ?? [];
|
|
31309
|
+
const byContainer = [
|
|
31310
|
+
[cfg.memory.sessions, "session"],
|
|
31311
|
+
[cfg.memory.threads, "thread"],
|
|
31312
|
+
[cfg.llm.diary, "diary"],
|
|
31313
|
+
[cfg.insights.logs, "log"],
|
|
31314
|
+
[cfg.knowledge.master, "user"],
|
|
31315
|
+
[cfg.knowledge.domains, "domain"]
|
|
31316
|
+
];
|
|
31317
|
+
for (const [containerId, kind] of byContainer) {
|
|
31318
|
+
if (containerId && parents.includes(containerId)) {
|
|
31319
|
+
inferredKind = kind;
|
|
31320
|
+
break;
|
|
31321
|
+
}
|
|
31322
|
+
}
|
|
31323
|
+
if (!inferredKind && parents.length) {
|
|
31324
|
+
const parent = await trilium.getNote(parents[0]).catch(() => null);
|
|
31325
|
+
if (parent && ownedLabel(parent, "noteType") === "thread")
|
|
31326
|
+
inferredKind = "threadEntry";
|
|
31327
|
+
else if (parent && ownedLabel(parent, "noteType") === "domain")
|
|
31328
|
+
inferredKind = "information";
|
|
31329
|
+
}
|
|
31330
|
+
if (inferredKind) {
|
|
31331
|
+
for (const l of labelPlan(inferredKind, {}, localToday())) {
|
|
31332
|
+
if (!ownedLabel(note, l.name)) {
|
|
31333
|
+
await trilium.addLabel(noteId, l.name, l.value, l.inheritable ?? false).catch(() => null);
|
|
31334
|
+
restored.push(l.value ? `${l.name}=${l.value}` : l.name);
|
|
31335
|
+
}
|
|
31336
|
+
}
|
|
31337
|
+
}
|
|
31338
|
+
}
|
|
31339
|
+
}
|
|
31340
|
+
return txt4({
|
|
31341
|
+
ok: true,
|
|
31342
|
+
undeleted: noteId,
|
|
31343
|
+
...inferredKind ? { kind: inferredKind } : {},
|
|
31344
|
+
...restored.length ? { labelsRestored: restored } : {},
|
|
31345
|
+
...note && !inferredKind ? {
|
|
31346
|
+
warning: "Could not infer this note's kind from its placement, so no #noteType was applied \u2014 it is currently INVISIBLE to brain(), recall() and every surface read. " + "Move it under its proper container and re-run, or set the label with add_label."
|
|
31347
|
+
} : {}
|
|
31348
|
+
});
|
|
31349
|
+
});
|
|
31350
|
+
server.tool("note_history", "Recent changes feed (creations / modifications / deletions), newest first.", { ancestorNoteId: exports_external.string().optional() }, async ({ ancestorNoteId }) => {
|
|
31351
|
+
const changes = await trilium.getNoteHistory(ancestorNoteId);
|
|
31352
|
+
return txt4(changes.map((c) => ({ id: c.noteId, title: c.current_title, deleted: c.current_isDeleted, date: c.date })));
|
|
31353
|
+
});
|
|
31354
|
+
server.tool("get_attribute", "Fetch a single attribute by id.", { attributeId: exports_external.string() }, async ({ attributeId }) => txt4(attrStub(await trilium.getAttribute(attributeId))));
|
|
31355
|
+
server.tool("add_label", "Add a #label to a note (empty value = boolean flag). Adds a new attribute; does not dedupe.", { noteId: exports_external.string(), name: exports_external.string().describe("Label name (no # prefix)"), value: exports_external.string().optional(), isInheritable: exports_external.boolean().optional() }, async ({ noteId, name, value, isInheritable }) => {
|
|
31356
|
+
const attr = await trilium.addLabel(noteId, name, value ?? "", isInheritable ?? false);
|
|
31357
|
+
return txt4(attrStub(attr));
|
|
31358
|
+
});
|
|
31359
|
+
server.tool("add_relation", "Add a ~relation with any name (the core connect() enforces the canonical vocabulary).", { fromNoteId: exports_external.string(), relationName: exports_external.string(), toNoteId: exports_external.string(), isInheritable: exports_external.boolean().optional() }, async ({ fromNoteId, relationName, toNoteId, isInheritable }) => {
|
|
31360
|
+
const attr = await trilium.addRelation(fromNoteId, relationName, toNoteId, isInheritable ?? false);
|
|
31361
|
+
return txt4(attrStub(attr));
|
|
31362
|
+
});
|
|
31363
|
+
server.tool("update_attribute", "Update an attribute's value (labels) and/or position by id.", { attributeId: exports_external.string(), value: exports_external.string().optional(), position: exports_external.number().optional() }, async ({ attributeId, value, position }) => {
|
|
31364
|
+
const fields = {};
|
|
31365
|
+
if (value != null)
|
|
31366
|
+
fields.value = value;
|
|
31367
|
+
if (position != null)
|
|
31368
|
+
fields.position = position;
|
|
31369
|
+
return txt4(attrStub(await trilium.updateAttribute(attributeId, fields)));
|
|
31370
|
+
});
|
|
31371
|
+
server.tool("delete_attribute", "Delete any label or relation by attributeId.", { attributeId: exports_external.string() }, async ({ attributeId }) => {
|
|
31372
|
+
await trilium.deleteAttribute(attributeId);
|
|
31373
|
+
return txt4({ ok: true, deleted: attributeId });
|
|
31374
|
+
});
|
|
31375
|
+
server.tool("get_branch", "Fetch a branch (a note's placement under one parent).", { branchId: exports_external.string() }, async ({ branchId }) => txt4(await trilium.getBranch(branchId)));
|
|
31376
|
+
server.tool("clone_note", "Place a note under an additional parent (multi-parent branch; shared content, no copy).", { noteId: exports_external.string(), parentNoteId: exports_external.string(), prefix: exports_external.string().optional() }, async ({ noteId, parentNoteId, prefix }) => {
|
|
31377
|
+
const branch = await trilium.cloneNote(noteId, parentNoteId, prefix);
|
|
31378
|
+
return txt4({ id: branch.branchId, noteId: branch.noteId, parentNoteId: branch.parentNoteId });
|
|
31379
|
+
});
|
|
31380
|
+
server.tool("move_note", "Move a note to a new parent (clone to the new parent, then remove the old branch).", { noteId: exports_external.string(), fromParentNoteId: exports_external.string(), toParentNoteId: exports_external.string() }, async ({ noteId, fromParentNoteId, toParentNoteId }) => {
|
|
31381
|
+
const newBranch = await trilium.cloneNote(noteId, toParentNoteId);
|
|
31382
|
+
const fresh = await trilium.getNote(noteId);
|
|
31383
|
+
for (const bid of fresh.parentBranchIds) {
|
|
31384
|
+
if (bid === newBranch.branchId)
|
|
31385
|
+
continue;
|
|
31386
|
+
const branch = await trilium.getBranch(bid);
|
|
31387
|
+
if (branch.parentNoteId === fromParentNoteId) {
|
|
31388
|
+
await trilium.deleteBranch(bid);
|
|
31389
|
+
break;
|
|
31390
|
+
}
|
|
31391
|
+
}
|
|
31392
|
+
return txt4({ ok: true, noteId, movedTo: toParentNoteId, newBranchId: newBranch.branchId });
|
|
31393
|
+
});
|
|
31394
|
+
server.tool("delete_branch", "Remove one placement of a note (deletes the note if it was the last branch).", { branchId: exports_external.string() }, async ({ branchId }) => {
|
|
31395
|
+
await trilium.deleteBranch(branchId);
|
|
31396
|
+
return txt4({ ok: true, deleted: branchId });
|
|
31397
|
+
});
|
|
31398
|
+
server.tool("create_revision", "Snapshot a note's current content as a revision.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
31399
|
+
await trilium.createRevision(noteId);
|
|
31400
|
+
return txt4({ ok: true, noteId });
|
|
31401
|
+
});
|
|
31402
|
+
server.tool("get_revisions", "List a note's revisions, newest first.", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
31403
|
+
const revs = await trilium.getNoteRevisions(noteId);
|
|
31404
|
+
return txt4(revs.map((r) => ({ id: r.revisionId, title: r.title, date: r.utcDateCreated, size: r.contentLength })));
|
|
31405
|
+
});
|
|
31406
|
+
server.tool("get_revision_content", "Content of a historical revision snapshot.", { revisionId: exports_external.string() }, async ({ revisionId }) => txt4(await trilium.getRevisionContent(revisionId)));
|
|
31407
|
+
server.tool("get_attachments", "List attachments on a note (id + title + mime + size).", { noteId: exports_external.string() }, async ({ noteId }) => {
|
|
31408
|
+
const attachments = await trilium.getNoteAttachments(noteId);
|
|
31409
|
+
return txt4(attachments.map((a) => ({ id: a.attachmentId, title: a.title, mime: a.mime, size: a.contentLength })));
|
|
31410
|
+
});
|
|
31411
|
+
server.tool("get_attachment_content", "Read the content of a text/code attachment.", { attachmentId: exports_external.string() }, async ({ attachmentId }) => txt4(await trilium.getAttachmentContent(attachmentId)));
|
|
31412
|
+
server.tool("create_attachment", "Attach a file or text blob to a note (role: file | image).", { ownerId: exports_external.string(), title: exports_external.string(), mime: exports_external.string(), content: exports_external.string().describe("Text content (base64 for binary)"), role: exports_external.enum(["file", "image"]).optional() }, async ({ ownerId, title, mime, content, role }) => {
|
|
31413
|
+
const att = await trilium.createAttachment(ownerId, title, mime, content, role ?? "file");
|
|
31414
|
+
return txt4({ id: att.attachmentId, title: att.title, mime: att.mime, size: att.contentLength });
|
|
31415
|
+
});
|
|
31416
|
+
server.tool("update_attachment", "Update an attachment's content and/or metadata (title, mime). Pass content to replace the binary/text data in place; pass title/mime to update metadata only.", {
|
|
31417
|
+
attachmentId: exports_external.string(),
|
|
31418
|
+
title: exports_external.string().optional(),
|
|
31419
|
+
mime: exports_external.string().optional().describe("MIME type \u2014 also used as Content-Type when writing content"),
|
|
31420
|
+
content: exports_external.string().optional().describe("New content (replaces existing; text or base64 for binary)")
|
|
31421
|
+
}, async ({ attachmentId, title, mime, content }) => {
|
|
31422
|
+
if (content != null) {
|
|
31423
|
+
await trilium.updateAttachmentContent(attachmentId, content, mime ?? "text/plain");
|
|
31424
|
+
}
|
|
31425
|
+
if (title != null || mime != null) {
|
|
31426
|
+
const fields = {};
|
|
31427
|
+
if (title != null)
|
|
31428
|
+
fields.title = title;
|
|
31429
|
+
if (mime != null)
|
|
31430
|
+
fields.mime = mime;
|
|
31431
|
+
const att = await trilium.updateAttachment(attachmentId, fields);
|
|
31432
|
+
return txt4({ id: att.attachmentId, title: att.title, mime: att.mime, contentUpdated: content != null });
|
|
31433
|
+
}
|
|
31434
|
+
return txt4({ ok: true, attachmentId, contentUpdated: content != null });
|
|
31435
|
+
});
|
|
31436
|
+
server.tool("delete_attachment", "Permanently delete an attachment. Irreversible.", { attachmentId: exports_external.string() }, async ({ attachmentId }) => {
|
|
31437
|
+
await trilium.deleteAttachment(attachmentId);
|
|
31438
|
+
return txt4({ ok: true, deleted: attachmentId });
|
|
31439
|
+
});
|
|
31440
|
+
server.tool("get_day_note", "Get (or auto-create) the journal day note. Format: YYYY-MM-DD (default: today).", { date: exports_external.string().optional() }, async ({ date: date4 }) => txt4(await trilium.getDayNote(date4 ?? localToday())));
|
|
31441
|
+
server.tool("get_week_note", "Get (or auto-create) the journal week note. Format: YYYY-Www.", { week: exports_external.string() }, async ({ week }) => txt4(await trilium.getWeekNote(week)));
|
|
31442
|
+
server.tool("get_month_note", "Get (or auto-create) the journal month note. Format: YYYY-MM.", { month: exports_external.string() }, async ({ month }) => txt4(await trilium.getMonthNote(month)));
|
|
31443
|
+
server.tool("get_year_note", "Get (or auto-create) the journal year note. Format: YYYY.", { year: exports_external.string() }, async ({ year }) => txt4(await trilium.getYearNote(year)));
|
|
31444
|
+
server.tool("get_inbox_note", "Get the Trilium inbox note for a date (fixed #inbox note, or the day note). Format: YYYY-MM-DD.", { date: exports_external.string().optional() }, async ({ date: date4 }) => txt4(await trilium.getInboxNote(date4 ?? localToday())));
|
|
31445
|
+
server.tool("get_app_info", "Trilium server version, DB version, runtime metadata.", {}, async () => txt4(await trilium.getAppInfo()));
|
|
31446
|
+
server.tool("create_backup", "Trigger a named Trilium database backup. The backup file is written to Trilium's backup directory as <name>.db (default name: brainllm-{date}). Use a descriptive name for milestone snapshots (e.g. 'before-migration').", {
|
|
31447
|
+
name: exports_external.string().optional().describe("Backup file name without .db extension (default: brainllm-{today})"),
|
|
31448
|
+
date: exports_external.string().optional().describe("ISO date used in the default name when name is omitted (default: today)")
|
|
31449
|
+
}, async ({ name, date: date4 }) => {
|
|
31450
|
+
const backupName = name ?? `brainllm-${date4 ?? localToday()}`;
|
|
31451
|
+
await trilium.createBackup(backupName);
|
|
31452
|
+
return txt4({ ok: true, backup: `${backupName}.db` });
|
|
31453
|
+
});
|
|
31457
31454
|
}
|
|
31458
31455
|
|
|
31459
31456
|
// src/annotations.ts
|
|
@@ -31472,6 +31469,8 @@ var TOOL_ANNOTATIONS = {
|
|
|
31472
31469
|
template: READ,
|
|
31473
31470
|
explore: READ,
|
|
31474
31471
|
consistency: READ,
|
|
31472
|
+
assembly: READ,
|
|
31473
|
+
diff: READ,
|
|
31475
31474
|
master: READ,
|
|
31476
31475
|
master_recall: READ,
|
|
31477
31476
|
llm: READ,
|
|
@@ -31487,6 +31486,7 @@ var TOOL_ANNOTATIONS = {
|
|
|
31487
31486
|
graph: WRITE,
|
|
31488
31487
|
maintain: WRITE,
|
|
31489
31488
|
addendum: READ,
|
|
31489
|
+
claim: APPEND,
|
|
31490
31490
|
remember: WRITE,
|
|
31491
31491
|
diary: APPEND,
|
|
31492
31492
|
revise: WRITE,
|
|
@@ -32075,10 +32075,12 @@ function createServer(origin = null) {
|
|
|
32075
32075
|
const s = new McpServer({
|
|
32076
32076
|
name: "BrainLLM",
|
|
32077
32077
|
title: "BrainLLM",
|
|
32078
|
-
version: "10.5.
|
|
32078
|
+
version: "10.5.2",
|
|
32079
32079
|
icons: brandingIcons(origin)
|
|
32080
32080
|
});
|
|
32081
|
-
registerTools(s, trilium, brainRef
|
|
32081
|
+
registerTools(s, trilium, brainRef);
|
|
32082
|
+
if (mode === "full")
|
|
32083
|
+
registerAdvancedTools(s, trilium, brainRef);
|
|
32082
32084
|
const { unclassified } = applyToolAnnotations(s);
|
|
32083
32085
|
if (unclassified.length) {
|
|
32084
32086
|
console.error(`[brainllm] Unclassified tools, treated as writes: ${unclassified.join(", ")}`);
|