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/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 txt4 = (obj) => ({
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) => txt4({ error: code, detail, ...hint ? { 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, mode = "core") {
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 txt4({ status: "uninitialized", action: "Run bootstrap to create the BrainLLM structure." });
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 txt4({
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 txt4({
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 txt4({ status: "uninitialized", action: "Run bootstrap first." });
28724
+ return txt3({ status: "uninitialized", action: "Run bootstrap first." });
28971
28725
  await markStep("remarks");
28972
- return txt4({
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 txt4({
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 txt4({ ok: true, backup: `${backupName}.db`, date: d });
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 txt4({ action: "already_written", noteId: noteId2, date: d });
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 txt4({ action: "appended", noteId: noteId2, date: d, ...iconSet2 ? { icon: iconSet2 } : {}, ...warnings.length ? { sanitized: warnings } : {} });
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 txt4({ action: "created", noteId, date: d, location: locationLabel("diary"), ...iconSet ? { icon: iconSet } : {}, ...warnings.length ? { sanitized: warnings } : {} });
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 txt4({ 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 } : {} });
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 txt4({
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 txt4({ action: "already_written", noteId: existing2.noteId, kind, title: existing2.title, domainId });
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 txt4({ action: "updated", noteId: existing2.noteId, kind, title: existing2.title, domainId, ...connected3.length ? { connected: connected3 } : {}, ...iconSet3 ? { icon: iconSet3 } : {}, ...relations2 ? { relations: relations2 } : {}, ...sanitizeWarnings.length ? { sanitized: sanitizeWarnings } : {} });
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 txt4({
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 txt4({ action: "already_written", noteId: existing.noteId, entryId: entry.noteId, kind, title: existing.title });
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 txt4({ 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 } : {} });
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 txt4({ action: "already_written", noteId: existing.noteId, kind, title: existing.title });
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 txt4({ 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 } : {} });
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 txt4({
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 txt4({
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 txt4({ results: results2, ...results2.length === 0 ? noMatch : {} });
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 txt4({
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 txt4({
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: mode2, find, nth, edits, identity, icon, date: date4 }) => {
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 txt4({
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 txt4({
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 ((mode2 === "before" || mode2 === "after") && !section)
29925
- return err("missing_param", `mode="${mode2}" inserts relative to a heading and needs one.`, 'Pass section="<heading text>" alongside it, or use mode="append" for a whole-note addendum.');
29926
- if (mode2 === "remove" && !section)
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 (mode2 === "remove" && section) {
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 txt4({
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 txt4({
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" && mode2 !== "replace" && /<h[2-4](?:\s[^>]*)?>\s*Resolution\s*<\/h[2-4]>/i.test(html) && !isOpenResolutionOnly(html)) {
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 = mode2 === "append" || mode2 === "before" || mode2 === "after" ? mode2 : "replace";
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 (mode2 === "replace") {
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 txt4({ ok: true, noteId, mode: "already_written", entryId: threadEntryResult.noteId, date: d });
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 txt4({ ok: true, noteId, mode: "already_written", date: d });
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 = mode2 === "append" ? "appended to" : mode2 === "before" || mode2 === "after" ? `inserted ${mode2}` : "replaced";
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 txt4({
29779
+ return txt3({
30026
29780
  ok: true,
30027
29781
  noteId,
30028
- mode: body ? section ? `section:${mode2 === "before" || mode2 === "after" ? `insert-${mode2}` : mode2 === "append" ? "append-within" : "replace"}:${section}` : mode2 ?? "append" : titled.retitled ? "rename" : iconSet ? "icon" : "no-op",
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 txt4({
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 txt4({
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 txt4({
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 txt4({ ok: true, noteId, name, action: "not_found" });
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 txt4({ ok: true, noteId, name, action: "removed" });
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 txt4({ ok: true, noteId, name, value: finalValue, action: "set" });
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 txt4({ ok: true, removed: `${fromNoteId} ~${relation}\u2192 ${toNoteId}` });
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 txt4({ ok: true, action: exists ? "already-existed" : "created", edge: `${fromNoteId} ~${relation}${symmetric ? "\u2194" : "\u2192"} ${toNoteId}` });
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: mode2, toNoteId, depth, relation }) => {
30226
- switch (mode2) {
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 txt4({ mode: mode2, links: linked.filter(Boolean) });
29988
+ return txt3({ mode, links: linked.filter(Boolean) });
30235
29989
  }
30236
29990
  case "backlinks": {
30237
29991
  const backlinks = await trilium.getBacklinks(noteId);
30238
- return txt4({ mode: mode2, backlinks: relation ? backlinks.filter((b2) => b2.relationName === relation) : backlinks });
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 txt4({ mode: mode2, nodeCount: nodes.length, nodes });
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 txt4({ mode: mode2, found: true, hops: 0, path: [{ noteId, title: self.title, depth: 0 }] });
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 txt4(path ? { mode: mode2, found: true, hops: path.length - 1, path } : { mode: mode2, found: false });
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 txt4({
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 txt4({
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 txt4({
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 txt4({
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 txt4({ status: "uninitialized", action: "Run bootstrap first." });
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 txt4({ ok: true, claimId, action: "retired", assertion: note.title });
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 txt4({ ...row(note), check: getSection(content2, "Check").content, history: getSection(content2, "Verifications").content, relations: relationSnippet(note) });
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 txt4({
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 txt4({ ok: true, action: "updated", ...row(existing), note: "A claim with this assertion already existed \u2014 its check was updated and its verification history kept." });
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 txt4({
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 txt4({
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 txt4({ noteId, note: "No revisions \u2014 this note has not been written through a content-mutating tool yet, or its revisions have been pruned.", revisions: [] });
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 txt4({
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 txt4({
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 txt4({ id: existing.attachmentId, noteId, title, mime: existing.mime, role: existing.role, size: existing.contentLength, content: data });
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 txt4({ action: "updated", id: existing.attachmentId, noteId, title, mime: mime ?? existing.mime });
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 txt4({ action: "created", id: created.attachmentId, noteId, title, mime: created.mime, role: created.role });
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 txt4({ ok: true, action: "already_removed", noteId, title });
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 txt4({ ok: true, action: "already_removed", attachmentId: id });
30523
+ return txt3({ ok: true, action: "already_removed", attachmentId: id });
30770
30524
  throw e;
30771
30525
  }
30772
- return txt4({ ok: true, removed: id, ...noteId ? { noteId, title } : {} });
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 txt4({ error: "BrainLLM not bootstrapped \u2014 run bootstrap." });
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 txt4({
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 txt4(report);
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 txt4({
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 txt4({
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 txt4({ ok: true, deleted: noteId, title: note.title, hardDeleted: true });
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 txt4({ ok: true, archived: noteId, title: note.title, ...relations ? { relations } : {} });
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 txt4({
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 txt4({
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 txt4({ status: "uninitialized", action: "Run bootstrap first." });
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 txt4({
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 txt4({ status: "uninitialized", action: "Run bootstrap first." });
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 txt4({
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 txt4({
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 txt4({ status: "uninitialized", action: "Run bootstrap first." });
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 txt4({ total, areas });
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 txt4({ status: "uninitialized", action: "Run bootstrap first." });
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 txt4({
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 txt4({
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 txt4({
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
- if (mode === "full") {
31455
- registerAdvancedTools(server, trilium, brainRef);
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.0",
32078
+ version: "10.5.2",
32079
32079
  icons: brandingIcons(origin)
32080
32080
  });
32081
- registerTools(s, trilium, brainRef, mode);
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(", ")}`);