feat(llm): basic skill to write scripts

This commit is contained in:
Elian Doran
2026-03-31 16:01:20 +03:00
parent 8eff623b67
commit c13b68ef42
3 changed files with 363 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
# Trilium Backend Scripting
Backend scripts run in Node.js on the server. They have direct access to notes in memory and can interact with the system (files, processes).
## Creating a backend script
1. Create a Code note with language "JS backend".
2. The script can be run manually (Execute button) or triggered automatically.
## Script API (`api` global)
### Note retrieval
- `api.getNote(noteId)` - get note by ID
- `api.searchForNotes(query, searchParams)` - search notes (returns array)
- `api.searchForNote(query)` - search notes (returns first match)
- `api.getNotesWithLabel(name, value?)` - find notes by label
- `api.getNoteWithLabel(name, value?)` - find first note by label
- `api.getBranch(branchId)` - get branch by ID
- `api.getAttribute(attributeId)` - get attribute by ID
### Note creation
- `api.createTextNote(parentNoteId, title, content)` - create text note
- `api.createDataNote(parentNoteId, title, content)` - create JSON note
- `api.createNewNote({ parentNoteId, title, content, type })` - create note with full options
### Branch management
- `api.ensureNoteIsPresentInParent(noteId, parentNoteId, prefix?)` - create or reuse branch
- `api.ensureNoteIsAbsentFromParent(noteId, parentNoteId)` - remove branch if exists
- `api.toggleNoteInParent(present, noteId, parentNoteId, prefix?)` - toggle branch
### Calendar/date notes
- `api.getTodayNote()` - get/create today's day note
- `api.getDayNote(date)` - get/create day note (YYYY-MM-DD)
- `api.getWeekNote(date)` - get/create week note
- `api.getMonthNote(date)` - get/create month note (YYYY-MM)
- `api.getYearNote(year)` - get/create year note (YYYY)
### Utilities
- `api.log(message)` - log to Trilium logs and UI
- `api.randomString(length)` - generate random string
- `api.escapeHtml(string)` / `api.unescapeHtml(string)`
- `api.getInstanceName()` - get instance name
- `api.getAppInfo()` - get application info
### Libraries
- `api.axios` - HTTP client
- `api.dayjs` - date manipulation
- `api.xml2js` - XML parser
- `api.cheerio` - HTML/XML parser
### Advanced
- `api.transactional(func)` - wrap code in a database transaction
- `api.sql` - direct SQL access
- `api.sortNotes(parentNoteId, sortConfig)` - sort child notes
- `api.runOnFrontend(script, params)` - execute code on all connected frontends
- `api.backupNow(backupName)` - create a backup
- `api.exportSubtreeToZipFile(noteId, format, zipFilePath)` - export subtree (format: "markdown" or "html")
- `api.duplicateSubtree(origNoteId, newParentNoteId)` - clone note and children
## BNote object
Available on notes returned from API methods (`api.getNote()`, `api.originEntity`, etc.).
### Content
- `note.getContent()` / `note.setContent(content)`
- `note.getJsonContent()` / `note.setJsonContent(obj)`
- `note.getJsonContentSafely()` - returns null on parse error
### Properties
- `note.noteId`, `note.title`, `note.type`, `note.mime`
- `note.dateCreated`, `note.dateModified`
- `note.isProtected`, `note.isArchived`
### Hierarchy
- `note.getParentNotes()` / `note.getChildNotes()`
- `note.getParentBranches()` / `note.getChildBranches()`
- `note.hasChildren()`, `note.getAncestors()`
- `note.getSubtreeNoteIds()` - all descendant IDs
- `note.hasAncestor(ancestorNoteId)`
### Attributes (including inherited)
- `note.getLabels(name?)` / `note.getLabelValue(name)`
- `note.getRelations(name?)` / `note.getRelation(name)`
- `note.hasLabel(name, value?)` / `note.hasRelation(name, value?)`
### Attribute modification
- `note.setLabel(name, value?)` / `note.removeLabel(name, value?)`
- `note.setRelation(name, targetNoteId)` / `note.removeRelation(name, value?)`
- `note.addLabel(name, value?, isInheritable?)` / `note.addRelation(name, targetNoteId, isInheritable?)`
- `note.toggleLabel(enabled, name, value?)`
### Operations
- `note.save()` - persist changes
- `note.deleteNote()` - soft delete
- `note.cloneTo(parentNoteId)` - clone to another parent
### Type checks
- `note.isJson()`, `note.isJavaScript()`, `note.isHtml()`, `note.isImage()`
- `note.hasStringContent()` - true if not binary
## Events and triggers
### Global events (via `#run` label on the script note)
- `#run=backendStartup` - run when server starts
- `#run=hourly` - run once per hour (use `#runAtHour=N` to specify which hours)
- `#run=daily` - run once per day
### Entity events (via relation from the entity to the script note)
These are defined as relations. `api.originEntity` contains the entity that triggered the event.
| Relation | Trigger | originEntity |
|---|---|---|
| `~runOnNoteCreation` | note created | BNote |
| `~runOnChildNoteCreation` | child note created under this note | BNote (child) |
| `~runOnNoteTitleChange` | note title changed | BNote |
| `~runOnNoteContentChange` | note content changed | BNote |
| `~runOnNoteChange` | note metadata changed (not content) | BNote |
| `~runOnNoteDeletion` | note deleted | BNote |
| `~runOnBranchCreation` | branch created (clone/move) | BBranch |
| `~runOnBranchChange` | branch updated | BBranch |
| `~runOnBranchDeletion` | branch deleted | BBranch |
| `~runOnAttributeCreation` | attribute created on this note | BAttribute |
| `~runOnAttributeChange` | attribute changed/deleted on this note | BAttribute |
Relations can be inheritable — when set, they apply to all descendant notes.
## Example: auto-color notes by category
```javascript
// Attach via ~runOnAttributeChange relation
const attr = api.originEntity;
if (attr.name !== "mycategory") return;
const note = api.getNote(attr.noteId);
if (attr.value === "Health") {
note.setLabel("color", "green");
} else {
note.removeLabel("color");
}
```
## Example: create a daily summary
```javascript
// Attach #run=daily label
const today = api.getTodayNote();
const tasks = api.searchForNotes('#task #!completed');
let summary = "## Open Tasks\n";
for (const task of tasks) {
summary += `- ${task.title}\n`;
}
api.createTextNote(today.noteId, "Daily Summary", summary);
```
## Module system
Child notes of a script act as modules. Export with `module.exports = ...` and import via function parameters matching the child note title, or use `require('noteName')`.

View File

@@ -0,0 +1,197 @@
# Trilium Frontend Scripting
Frontend scripts run in the browser. They can manipulate the UI, navigate notes, show dialogs, and create custom widgets.
## Creating a frontend script
1. Create a Code note with language "JS frontend" (or "JSX" for Preact widgets).
2. Run manually (Execute button) or set `#run=frontendStartup` to auto-run on startup.
3. For mobile, use `#run=mobileStartup` instead.
## Script types
| Type | Description | Required attribute |
|---|---|---|
| Regular script | Runs with current app/note context | `#run=frontendStartup` (optional) |
| Custom widget | UI element in various positions | `#widget` |
| Launch bar widget | Button in the launch bar | `#widget` |
| Render note | Custom content inside a note | None (used via render relation) |
## Script API (`api` global)
### Navigation & tabs
- `api.activateNote(notePath)` - navigate to a note
- `api.activateNewNote(notePath)` - navigate and wait for sync
- `api.openTabWithNote(notePath, activate?)` - open in new tab
- `api.openSplitWithNote(notePath, activate?)` - open in new split
- `api.getActiveContextNote()` - get currently active note
- `api.getActiveContextNotePath()` - get path of active note
- `api.setHoistedNoteId(noteId)` - hoist/unhoist note in current tab
### Note access & search
- `api.getNote(noteId)` - get note by ID
- `api.getNotes(noteIds)` - bulk fetch notes
- `api.searchForNotes(searchString)` - search with full query syntax
- `api.searchForNote(searchString)` - search returning first result
- `api.reloadNotes(noteIds)` - refresh cache from backend
### Calendar/date notes
- `api.getTodayNote()` - get/create today's note
- `api.getDayNote(date)` - get/create day note for date
- `api.getWeekNote(date)` / `api.getMonthNote(month)` / `api.getYearNote(year)`
### Editor access
- `api.getActiveContextTextEditor()` - get CKEditor instance
- `api.getActiveContextCodeEditor()` - get CodeMirror instance
- `api.addTextToActiveContextEditor(text)` - insert text into active editor
### Dialogs & notifications
- `api.showMessage(msg)` - show info toast
- `api.showError(msg)` - show error toast
- `api.showInfoDialog(msg)` - show info dialog
- `api.showConfirmDialog(msg)` - show confirm dialog (returns boolean)
- `api.showPromptDialog(msg)` - show prompt dialog (returns user input)
### Links
- `api.createLink(notePath, { title?, showTooltip?, showNoteIcon? })` - create jQuery link element
### Backend integration
- `api.runOnBackend(func, params)` - execute a function on the backend (sync)
### Protection
- `api.protectNote(noteId, protect)` - protect/unprotect note
- `api.protectSubTree(noteId, protect)` - protect/unprotect subtree
### UI interaction
- `api.triggerCommand(name, data)` - trigger a command
- `api.triggerEvent(name, data)` - trigger an event
- `api.bindGlobalShortcut(shortcut, handler, namespace?)` - add keyboard shortcut
### Utilities
- `api.formatDateISO(date)` - format as YYYY-MM-DD
- `api.randomString(length)` - generate random string
- `api.dayjs` - day.js library
- `api.log(message)` - log to script log pane
### Widget base classes
- `api.BasicWidget` - base widget class
- `api.NoteContextAwareWidget` - widget aware of note context changes
- `api.RightPanelWidget` - right sidebar widget
## FNote object
Available via `api.getNote()`, `api.getActiveContextNote()`, etc.
### Properties
- `note.noteId`, `note.title`, `note.type`, `note.mime`
- `note.isProtected`, `note.isArchived`
### Content
- `note.getContent()` - get note content
- `note.getJsonContent()` - parse content as JSON
### Hierarchy
- `note.getParentNotes()` / `note.getChildNotes()`
- `note.hasChildren()`, `note.getSubtreeNoteIds()`
### Attributes
- `note.getAttributes(type?, name?)` - get all attributes (including inherited)
- `note.getOwnedAttributes(type?, name?)` - get only owned attributes
- `note.hasAttribute(type, name)` - check for attribute
## Custom widgets (legacy jQuery)
```javascript
class MyWidget extends api.BasicWidget {
get position() { return 1; }
get parentWidget() { return "center-pane"; }
doRender() {
this.$widget = $("<div>");
this.$widget.append($("<button>Click me</button>")
.on("click", () => api.showMessage("Hello!")));
return this.$widget;
}
}
module.exports = new MyWidget();
```
### Widget locations (`parentWidget` values)
- `left-pane` - alongside the note tree
- `center-pane` - in the content area, spanning all splits
- `note-detail-pane` - inside a note (split-aware, export class not instance, use static parentWidget)
- `right-pane` - in the right sidebar (use `RightPanelWidget`)
### Note context aware widget
```javascript
class MyWidget extends api.NoteContextAwareWidget {
static get parentWidget() { return "note-detail-pane"; }
get position() { return 100; }
doRender() {
this.$widget = $("<div>");
return this.$widget;
}
async refreshWithNote(note) {
// Called when the active note changes
this.$widget.text(`Current note: ${note.title}`);
}
}
module.exports = MyWidget; // Export class, not instance!
```
## Custom widgets (Preact JSX)
Requires JSX language enabled in Options -> Code Notes.
```jsx
import { defineWidget } from "trilium:preact";
import { useState } from "trilium:preact";
export default defineWidget({
parent: "center-pane",
position: 10,
render: () => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>
Clicked {count} times
</button>
</div>
);
}
});
```
### Preact imports
- `import { showMessage, getNote, ... } from "trilium:api"` - API methods
- `import { useState, useEffect, ... } from "trilium:preact"` - hooks
- `import { defineWidget, defineLauncherWidget } from "trilium:preact"` - widget helpers
- Built-in components: Button, ActionButton, Modal, NoteAutocomplete, FormTextBox, FormToggle, etc.
## Example: launcher button
```javascript
// Set #run=frontendStartup
api.createOrUpdateLauncher({
id: "my-task-button",
type: "customWidget",
title: "New Task",
icon: "bx bx-task",
action: async () => {
const todayNote = await api.getTodayNote();
await api.runOnBackend(async (parentNoteId) => {
api.createTextNote(parentNoteId, "New Task", "");
}, [todayNote.noteId]);
}
});
```
## Module system
Child notes of a script act as modules. For JS frontend, use `module.exports` and function parameters. For JSX, use `import`/`export` syntax.

View File

@@ -23,6 +23,16 @@ const SKILLS: SkillDefinition[] = [
name: "search_syntax",
description: "Trilium search query syntax reference — labels, relations, note properties, boolean logic, ordering, and more.",
file: "search_syntax.md"
},
{
name: "backend_scripting",
description: "Backend (Node.js) scripting API — creating notes, handling events, accessing entities, database operations, and automation.",
file: "backend_scripting.md"
},
{
name: "frontend_scripting",
description: "Frontend (browser) scripting API — UI widgets, navigation, dialogs, editor access, Preact/JSX components, and keyboard shortcuts.",
file: "frontend_scripting.md"
}
];