@abraca/mcp 1.0.15 → 1.0.19
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/abracadabra-mcp.cjs +511 -102
- package/dist/abracadabra-mcp.cjs.map +1 -1
- package/dist/abracadabra-mcp.esm.js +510 -102
- package/dist/abracadabra-mcp.esm.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/package.json +1 -1
- package/src/converters/markdownToYjs.ts +52 -2
- package/src/converters/types.ts +54 -4
- package/src/converters/yjsToMarkdown.ts +5 -0
- package/src/hook-bridge.ts +204 -0
- package/src/index.ts +17 -1
- package/src/resources/agent-guide.ts +203 -92
- package/src/server.ts +18 -3
- package/src/tools/content.ts +1 -1
- package/src/tools/hooks.ts +42 -0
- package/src/tools/meta.ts +1 -1
- package/src/tools/tree.ts +4 -1
|
@@ -6,9 +6,11 @@ import { AbracadabraClient, AbracadabraProvider, awarenessStatesToArray } from "
|
|
|
6
6
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
|
+
import * as os from "node:os";
|
|
9
10
|
import { homedir } from "node:os";
|
|
10
11
|
import * as path from "node:path";
|
|
11
12
|
import { dirname, join } from "node:path";
|
|
13
|
+
import * as http from "node:http";
|
|
12
14
|
|
|
13
15
|
//#region \0rolldown/runtime.js
|
|
14
16
|
var __create = Object.create;
|
|
@@ -4061,7 +4063,7 @@ const cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-
|
|
|
4061
4063
|
const cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
|
|
4062
4064
|
const base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
|
|
4063
4065
|
const base64url = /^[A-Za-z0-9_-]*$/;
|
|
4064
|
-
const hostname = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
|
|
4066
|
+
const hostname$1 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
|
|
4065
4067
|
const e164 = /^\+(?:[0-9]){6,14}[0-9]$/;
|
|
4066
4068
|
const dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
|
|
4067
4069
|
const date$1 = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
|
|
@@ -4608,7 +4610,7 @@ const $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
|
|
|
4608
4610
|
code: "invalid_format",
|
|
4609
4611
|
format: "url",
|
|
4610
4612
|
note: "Invalid hostname",
|
|
4611
|
-
pattern: hostname.source,
|
|
4613
|
+
pattern: hostname$1.source,
|
|
4612
4614
|
input: payload.value,
|
|
4613
4615
|
inst,
|
|
4614
4616
|
continue: !def.abort
|
|
@@ -19543,6 +19545,9 @@ var AbracadabraMCPServer = class {
|
|
|
19543
19545
|
publicKey: this._userId,
|
|
19544
19546
|
isAgent: true
|
|
19545
19547
|
});
|
|
19548
|
+
provider.awareness.setLocalStateField("status", null);
|
|
19549
|
+
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19550
|
+
provider.awareness.setLocalStateField("statusContext", null);
|
|
19546
19551
|
const conn = {
|
|
19547
19552
|
doc,
|
|
19548
19553
|
provider,
|
|
@@ -19758,8 +19763,10 @@ var AbracadabraMCPServer = class {
|
|
|
19758
19763
|
}
|
|
19759
19764
|
/**
|
|
19760
19765
|
* Set the agent's status in root awareness with auto-clear after idle.
|
|
19766
|
+
* @param statusContext — scopes the status to a specific channel/context so the
|
|
19767
|
+
* dashboard only shows it in the relevant chat. Defaults to `_lastChatChannel`.
|
|
19761
19768
|
*/
|
|
19762
|
-
setAutoStatus(status, docId) {
|
|
19769
|
+
setAutoStatus(status, docId, statusContext) {
|
|
19763
19770
|
const provider = this._activeConnection?.provider;
|
|
19764
19771
|
if (!provider) return;
|
|
19765
19772
|
if (this._statusClearTimer) {
|
|
@@ -19768,12 +19775,15 @@ var AbracadabraMCPServer = class {
|
|
|
19768
19775
|
}
|
|
19769
19776
|
provider.awareness.setLocalStateField("status", status);
|
|
19770
19777
|
if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
|
|
19778
|
+
const context = status ? statusContext !== void 0 ? statusContext : this._lastChatChannel : null;
|
|
19779
|
+
provider.awareness.setLocalStateField("statusContext", context ?? null);
|
|
19771
19780
|
if (!status) this._stopTypingInterval();
|
|
19772
19781
|
if (status) this._statusClearTimer = setTimeout(() => {
|
|
19773
19782
|
provider.awareness.setLocalStateField("status", null);
|
|
19774
19783
|
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19784
|
+
provider.awareness.setLocalStateField("statusContext", null);
|
|
19775
19785
|
this._stopTypingInterval();
|
|
19776
|
-
},
|
|
19786
|
+
}, 1e4);
|
|
19777
19787
|
}
|
|
19778
19788
|
/** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
|
|
19779
19789
|
_startTypingInterval(channel) {
|
|
@@ -19821,7 +19831,12 @@ var AbracadabraMCPServer = class {
|
|
|
19821
19831
|
}
|
|
19822
19832
|
for (const [, cached] of this.childCache) cached.provider.destroy();
|
|
19823
19833
|
this.childCache.clear();
|
|
19824
|
-
for (const [, conn] of this._spaceConnections)
|
|
19834
|
+
for (const [, conn] of this._spaceConnections) {
|
|
19835
|
+
conn.provider.awareness.setLocalStateField("status", null);
|
|
19836
|
+
conn.provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19837
|
+
conn.provider.awareness.setLocalStateField("statusContext", null);
|
|
19838
|
+
conn.provider.destroy();
|
|
19839
|
+
}
|
|
19825
19840
|
this._spaceConnections.clear();
|
|
19826
19841
|
this._activeConnection = null;
|
|
19827
19842
|
console.error("[abracadabra-mcp] Shutdown complete");
|
|
@@ -19963,7 +19978,7 @@ function registerTreeTools(mcp, server) {
|
|
|
19963
19978
|
mcp.tool("create_document", "Create a new document in the tree. Returns the new document ID.", {
|
|
19964
19979
|
parentId: z.string().optional().describe("Parent document ID. Omit for top-level pages. Use a document ID for nested/child pages."),
|
|
19965
19980
|
label: z.string().describe("Display name / title for the document."),
|
|
19966
|
-
type: z.string().optional().describe("Page type
|
|
19981
|
+
type: z.string().optional().describe("Page type — sets how this document renders. \"doc\" (rich text), \"kanban\" (columns → cards), \"table\" (columns → cells, positional rows), \"calendar\" (events with datetimeStart/End), \"timeline\" (epics → tasks with dateStart/End + taskProgress), \"checklist\" (tasks with checked/priority, unlimited nesting), \"outline\" (nested items, unlimited depth), \"gallery\" (image/media items), \"map\" (markers/lines with geoLat/geoLng), \"graph\" (knowledge graph nodes), \"dashboard\" (positioned widgets with deskX/deskY/deskMode), \"mindmap\" (connected nodes), \"spatial\" (3D objects with spShape/spX/spY/spZ), \"media\" (audio/video tracks), \"slides\" (slide deck), \"whiteboard\" (freeform canvas). Omit to inherit parent view. Only set on the parent page, NEVER on child items."),
|
|
19967
19982
|
meta: z.record(z.unknown()).optional().describe("Initial metadata (PageMeta fields: color as hex string, icon as Lucide kebab-case name like \"star\"/\"code-2\"/\"users\" — never emoji, dateStart, dateEnd, priority 0-4, tags array, etc). Omit icon entirely to use page type default.")
|
|
19968
19983
|
}, async ({ parentId, label, type, meta }) => {
|
|
19969
19984
|
server.setAutoStatus("creating");
|
|
@@ -19994,6 +20009,7 @@ function registerTreeTools(mcp, server) {
|
|
|
19994
20009
|
updatedAt: now
|
|
19995
20010
|
});
|
|
19996
20011
|
});
|
|
20012
|
+
server.setFocusedDoc(id);
|
|
19997
20013
|
server.setActiveToolCall(null);
|
|
19998
20014
|
return { content: [{
|
|
19999
20015
|
type: "text",
|
|
@@ -20149,6 +20165,7 @@ function registerTreeTools(mcp, server) {
|
|
|
20149
20165
|
label: (entry.label || "Untitled") + " (copy)",
|
|
20150
20166
|
order: Date.now()
|
|
20151
20167
|
});
|
|
20168
|
+
server.setFocusedDoc(newId);
|
|
20152
20169
|
return { content: [{
|
|
20153
20170
|
type: "text",
|
|
20154
20171
|
text: JSON.stringify({
|
|
@@ -20234,11 +20251,44 @@ function parseFrontmatter(markdown) {
|
|
|
20234
20251
|
if (subtitle) meta.subtitle = subtitle;
|
|
20235
20252
|
const url = getStr(["url"]);
|
|
20236
20253
|
if (url) meta.url = url;
|
|
20254
|
+
const email = getStr(["email"]);
|
|
20255
|
+
if (email) meta.email = email;
|
|
20256
|
+
const phone = getStr(["phone"]);
|
|
20257
|
+
if (phone) meta.phone = phone;
|
|
20237
20258
|
const ratingRaw = getStr(["rating"]);
|
|
20238
20259
|
if (ratingRaw !== void 0) {
|
|
20239
20260
|
const n = Number(ratingRaw);
|
|
20240
20261
|
if (!Number.isNaN(n)) meta.rating = Math.min(5, Math.max(0, n));
|
|
20241
20262
|
}
|
|
20263
|
+
const datetimeStart = getStr(["datetimeStart"]);
|
|
20264
|
+
if (datetimeStart) meta.datetimeStart = datetimeStart;
|
|
20265
|
+
const datetimeEnd = getStr(["datetimeEnd"]);
|
|
20266
|
+
if (datetimeEnd) meta.datetimeEnd = datetimeEnd;
|
|
20267
|
+
const allDayRaw = raw["allDay"];
|
|
20268
|
+
if (allDayRaw !== void 0) meta.allDay = allDayRaw === "true" || allDayRaw === true;
|
|
20269
|
+
const geoLatRaw = getStr(["geoLat"]);
|
|
20270
|
+
if (geoLatRaw !== void 0) {
|
|
20271
|
+
const n = Number(geoLatRaw);
|
|
20272
|
+
if (!Number.isNaN(n)) meta.geoLat = n;
|
|
20273
|
+
}
|
|
20274
|
+
const geoLngRaw = getStr(["geoLng"]);
|
|
20275
|
+
if (geoLngRaw !== void 0) {
|
|
20276
|
+
const n = Number(geoLngRaw);
|
|
20277
|
+
if (!Number.isNaN(n)) meta.geoLng = n;
|
|
20278
|
+
}
|
|
20279
|
+
const geoType = getStr(["geoType"]);
|
|
20280
|
+
if (geoType && (geoType === "marker" || geoType === "line" || geoType === "measure")) meta.geoType = geoType;
|
|
20281
|
+
const geoDescription = getStr(["geoDescription"]);
|
|
20282
|
+
if (geoDescription) meta.geoDescription = geoDescription;
|
|
20283
|
+
const numberRaw = getStr(["number"]);
|
|
20284
|
+
if (numberRaw !== void 0) {
|
|
20285
|
+
const n = Number(numberRaw);
|
|
20286
|
+
if (!Number.isNaN(n)) meta.number = n;
|
|
20287
|
+
}
|
|
20288
|
+
const unit = getStr(["unit"]);
|
|
20289
|
+
if (unit) meta.unit = unit;
|
|
20290
|
+
const note = getStr(["note"]);
|
|
20291
|
+
if (note) meta.note = note;
|
|
20242
20292
|
return {
|
|
20243
20293
|
title: typeof raw["title"] === "string" ? raw["title"] : void 0,
|
|
20244
20294
|
meta,
|
|
@@ -20276,8 +20326,12 @@ function parseInline(text) {
|
|
|
20276
20326
|
attrs: { kbd: { value: kbdProps["value"] || "" } }
|
|
20277
20327
|
});
|
|
20278
20328
|
} else if (match[5] !== void 0) {
|
|
20279
|
-
const
|
|
20280
|
-
|
|
20329
|
+
const docId = match[5];
|
|
20330
|
+
const displayText = match[6] ?? docId;
|
|
20331
|
+
tokens.push({
|
|
20332
|
+
text: displayText,
|
|
20333
|
+
attrs: { link: { href: `/doc/${docId}` } }
|
|
20334
|
+
});
|
|
20281
20335
|
} else if (match[7] !== void 0) tokens.push({
|
|
20282
20336
|
text: match[7],
|
|
20283
20337
|
attrs: { strike: true }
|
|
@@ -20426,6 +20480,15 @@ function parseBlocks(markdown) {
|
|
|
20426
20480
|
i++;
|
|
20427
20481
|
continue;
|
|
20428
20482
|
}
|
|
20483
|
+
const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\]\s*$/);
|
|
20484
|
+
if (docEmbedMatch) {
|
|
20485
|
+
blocks.push({
|
|
20486
|
+
type: "docEmbed",
|
|
20487
|
+
docId: docEmbedMatch[1]
|
|
20488
|
+
});
|
|
20489
|
+
i++;
|
|
20490
|
+
continue;
|
|
20491
|
+
}
|
|
20429
20492
|
const imgMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)(\{[^}]*\})?\s*$/);
|
|
20430
20493
|
if (imgMatch) {
|
|
20431
20494
|
const alt = imgMatch[1] ?? "";
|
|
@@ -20733,6 +20796,7 @@ function blockElName(b) {
|
|
|
20733
20796
|
case "field": return "field";
|
|
20734
20797
|
case "fieldGroup": return "fieldGroup";
|
|
20735
20798
|
case "image": return "image";
|
|
20799
|
+
case "docEmbed": return "docEmbed";
|
|
20736
20800
|
}
|
|
20737
20801
|
}
|
|
20738
20802
|
function fillBlock(el, block) {
|
|
@@ -20948,6 +21012,9 @@ function fillBlock(el, block) {
|
|
|
20948
21012
|
if (block.width) el.setAttribute("width", block.width);
|
|
20949
21013
|
if (block.height) el.setAttribute("height", block.height);
|
|
20950
21014
|
break;
|
|
21015
|
+
case "docEmbed":
|
|
21016
|
+
el.setAttribute("docId", block.docId);
|
|
21017
|
+
break;
|
|
20951
21018
|
}
|
|
20952
21019
|
}
|
|
20953
21020
|
function populateYDocFromMarkdown(fragment, markdown, fallbackTitle = "Untitled") {
|
|
@@ -20996,6 +21063,7 @@ function populateYDocFromMarkdown(fragment, markdown, fallbackTitle = "Untitled"
|
|
|
20996
21063
|
case "field": return new Y.XmlElement("field");
|
|
20997
21064
|
case "fieldGroup": return new Y.XmlElement("fieldGroup");
|
|
20998
21065
|
case "image": return new Y.XmlElement("image");
|
|
21066
|
+
case "docEmbed": return new Y.XmlElement("docEmbed");
|
|
20999
21067
|
}
|
|
21000
21068
|
});
|
|
21001
21069
|
fragment.insert(0, [
|
|
@@ -21067,6 +21135,10 @@ function serializeElement(el, indent = "") {
|
|
|
21067
21135
|
}
|
|
21068
21136
|
case "horizontalRule": return "---";
|
|
21069
21137
|
case "table": return serializeTable(el);
|
|
21138
|
+
case "docEmbed": {
|
|
21139
|
+
const docId = el.getAttribute("docId");
|
|
21140
|
+
return docId ? `![[${docId}]]` : "";
|
|
21141
|
+
}
|
|
21070
21142
|
case "image": {
|
|
21071
21143
|
const src = el.getAttribute("src") || "";
|
|
21072
21144
|
const alt = el.getAttribute("alt") || "";
|
|
@@ -21282,7 +21354,7 @@ function registerContentTools(mcp, server) {
|
|
|
21282
21354
|
};
|
|
21283
21355
|
}
|
|
21284
21356
|
});
|
|
21285
|
-
mcp.tool("write_document", "Write markdown content to a document. Parses the markdown and writes it to the Y.js CRDT document, which syncs in real-time to all connected clients. Supports optional YAML frontmatter for title and metadata.", {
|
|
21357
|
+
mcp.tool("write_document", "Write markdown content to a document. Parses the markdown and writes it to the Y.js CRDT document, which syncs in real-time to all connected clients. Supports optional YAML frontmatter for title and metadata. Use ![[docId]] to embed another document as a block, or [[docId|label]] for inline doc links.", {
|
|
21286
21358
|
docId: z.string().describe("Document ID to write to."),
|
|
21287
21359
|
markdown: z.string().describe("Markdown content to write. Can include YAML frontmatter with title and metadata fields."),
|
|
21288
21360
|
mode: z.enum(["replace", "append"]).optional().describe("Write mode. \"replace\" clears existing content first (default). \"append\" adds to the end.")
|
|
@@ -21366,7 +21438,7 @@ function registerMetaTools(mcp, server) {
|
|
|
21366
21438
|
});
|
|
21367
21439
|
mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
|
|
21368
21440
|
docId: z.string().describe("Document ID."),
|
|
21369
|
-
meta: z.record(z.unknown()).describe("Metadata fields to update (merged with existing).
|
|
21441
|
+
meta: z.record(z.unknown()).describe("Metadata fields to update (merged with existing). Universal keys: color (hex), icon (Lucide kebab-case — NEVER emoji), dateStart/dateEnd, datetimeStart/datetimeEnd, allDay, tags (string[]), checked (bool), priority (0=none,1=low,2=med,3=high,4=urgent), status, rating (0-5), url, email, phone, number, unit, subtitle, note, taskProgress (0-100), members ({id,label}[]), coverUploadId. Geo/Map: geoType (\"marker\"|\"line\"|\"measure\"), geoLat, geoLng, geoDescription. Spatial 3D: spShape (\"box\"|\"sphere\"|\"cylinder\"|\"cone\"|\"plane\"|\"torus\"|\"glb\"), spX/spY/spZ, spRX/spRY/spRZ, spSX/spSY/spSZ, spColor, spOpacity (0-100). Dashboard: deskX, deskY, deskZ, deskMode (\"icon\"|\"widget-sm\"|\"widget-lg\"). Renderer config (on the page doc itself): kanbanColumnWidth, galleryColumns, galleryAspect, calendarView, calendarWeekStart, tableMode, showRefEdges. Set a key to null to clear it.")
|
|
21370
21442
|
}, async ({ docId, meta }) => {
|
|
21371
21443
|
const treeMap = server.getTreeMap();
|
|
21372
21444
|
if (!treeMap) return { content: [{
|
|
@@ -21714,6 +21786,18 @@ Every piece of visible data in Abracadabra — a calendar event, a kanban card,
|
|
|
21714
21786
|
|
|
21715
21787
|
**Page types are views over the same document hierarchy**, not isolated data stores. Switching a page from Kanban to Table to Outline works without migration — the tree doesn't change, only the rendering.
|
|
21716
21788
|
|
|
21789
|
+
The same tree viewed as different page types:
|
|
21790
|
+
\`\`\`
|
|
21791
|
+
My Doc
|
|
21792
|
+
├── "To Do" → Kanban: column / Table: column / Outline: item
|
|
21793
|
+
│ ├── "Fix bug" → Kanban: card / Table: cell / Outline: sub-item
|
|
21794
|
+
│ └── "Add tests"
|
|
21795
|
+
├── "In Progress"
|
|
21796
|
+
│ └── "Refactor"
|
|
21797
|
+
└── "Done"
|
|
21798
|
+
└── "Deploy"
|
|
21799
|
+
\`\`\`
|
|
21800
|
+
|
|
21717
21801
|
---
|
|
21718
21802
|
|
|
21719
21803
|
## Getting Oriented in a Space
|
|
@@ -21733,23 +21817,24 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21733
21817
|
|
|
21734
21818
|
## Page Types Reference
|
|
21735
21819
|
|
|
21736
|
-
| Type | Children Are | Grandchildren Are | Depth
|
|
21737
|
-
|
|
21738
|
-
| **doc** | Sub-documents | Sub-sub-documents |
|
|
21739
|
-
| **kanban** | Columns | Cards | 2 |
|
|
21740
|
-
| **table** | Columns | Cells (positional rows) | 2 |
|
|
21741
|
-
| **calendar** | Events | — | 1 |
|
|
21742
|
-
| **timeline** | Epics | Tasks | 2 |
|
|
21743
|
-
| **
|
|
21744
|
-
| **
|
|
21745
|
-
| **
|
|
21746
|
-
| **
|
|
21747
|
-
| **
|
|
21748
|
-
| **
|
|
21749
|
-
| **
|
|
21750
|
-
| **
|
|
21751
|
-
| **
|
|
21752
|
-
| **
|
|
21820
|
+
| Type | Children Are | Grandchildren Are | Depth | Key Meta on Children |
|
|
21821
|
+
|------|-------------|-------------------|-------|---------------------|
|
|
21822
|
+
| **doc** | Sub-documents | Sub-sub-documents | ∞ | — |
|
|
21823
|
+
| **kanban** | Columns | Cards | 2 | color, icon on cards |
|
|
21824
|
+
| **table** | Columns | Cells (positional rows) | 2 | — |
|
|
21825
|
+
| **calendar** | Events | — | 1 | datetimeStart, datetimeEnd, allDay, color |
|
|
21826
|
+
| **timeline** | Epics | Tasks | 2 | dateStart, dateEnd, taskProgress, color |
|
|
21827
|
+
| **checklist** | Tasks | Sub-tasks | ∞ | checked, priority, dateEnd |
|
|
21828
|
+
| **outline** | Items | Sub-items | ∞ | — |
|
|
21829
|
+
| **mindmap** | Central nodes | Branches | ∞ | mmX, mmY |
|
|
21830
|
+
| **graph** | Nodes | — | 1 | graphX, graphY, graphPinned, color |
|
|
21831
|
+
| **gallery** | Items | — | 1 | geoLat, geoLng, datetimeStart, tags |
|
|
21832
|
+
| **map** | Markers/Lines | Points (for lines) | 2 | geoType, geoLat, geoLng, icon, color |
|
|
21833
|
+
| **dashboard** | Items | — | 1 | deskX, deskY, deskMode |
|
|
21834
|
+
| **spatial** | Objects | Sub-parts | ∞ | spShape, spX/Y/Z, spColor, spOpacity |
|
|
21835
|
+
| **media** | Tracks | — | 1 | tags |
|
|
21836
|
+
| **slides** | Slides | — | 1 | — |
|
|
21837
|
+
| **whiteboard** | Objects | — | 1 | wbX, wbY, wbW, wbH |
|
|
21753
21838
|
|
|
21754
21839
|
---
|
|
21755
21840
|
|
|
@@ -21758,26 +21843,110 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21758
21843
|
### Creating Well-Structured Hierarchies
|
|
21759
21844
|
|
|
21760
21845
|
**Kanban board:**
|
|
21761
|
-
1.
|
|
21846
|
+
1. \`create_document(parentId, "Project Board", "kanban")\`
|
|
21762
21847
|
2. Create columns: \`create_document(boardId, "To Do")\`, \`create_document(boardId, "In Progress")\`, \`create_document(boardId, "Done")\`
|
|
21763
21848
|
3. Create cards under columns: \`create_document(toDoColumnId, "Fix bug #123")\`
|
|
21849
|
+
4. Optionally set card metadata: \`update_metadata(cardId, { color: "#ef4444", priority: 3, tags: ["urgent"] })\`
|
|
21850
|
+
5. Optionally configure column width: \`update_metadata(boardId, { kanbanColumnWidth: "wide" })\`
|
|
21851
|
+
|
|
21852
|
+
**Table with data:**
|
|
21853
|
+
1. \`create_document(parentId, "Contacts", "table")\`
|
|
21854
|
+
2. Create columns: \`create_document(tableId, "Name")\`, \`create_document(tableId, "Email")\`, \`create_document(tableId, "Phone")\`
|
|
21855
|
+
3. Create cells (children of columns): \`create_document(nameColId, "Alice")\`, \`create_document(emailColId, "alice@example.com")\`
|
|
21856
|
+
4. **Rows are positional** — the Nth child of each column forms row N
|
|
21857
|
+
5. Row count = max children across all columns. Missing cells render as empty.
|
|
21858
|
+
6. To add a row: create a child under each column at the same index position
|
|
21859
|
+
7. To delete row N: delete the Nth child from every column
|
|
21764
21860
|
|
|
21765
21861
|
**Calendar with events:**
|
|
21766
|
-
1.
|
|
21862
|
+
1. \`create_document(parentId, "Team Calendar", "calendar")\`
|
|
21767
21863
|
2. Create events: \`create_document(calendarId, "Sprint Planning")\`
|
|
21768
21864
|
3. Set event times: \`update_metadata(eventId, { datetimeStart: "2026-03-20T10:00:00Z", datetimeEnd: "2026-03-20T11:00:00Z" })\`
|
|
21865
|
+
4. All-day events: \`update_metadata(eventId, { datetimeStart: "2026-03-20", allDay: true })\`
|
|
21866
|
+
5. Multi-day events: \`update_metadata(eventId, { datetimeStart: "2026-03-20", datetimeEnd: "2026-03-22", allDay: true })\`
|
|
21867
|
+
6. Configure view: \`update_metadata(calendarId, { calendarView: "week" })\`
|
|
21769
21868
|
|
|
21770
|
-
**Timeline
|
|
21771
|
-
1.
|
|
21869
|
+
**Timeline (Gantt chart):**
|
|
21870
|
+
1. \`create_document(parentId, "Q1 Roadmap", "timeline")\`
|
|
21772
21871
|
2. Create epics: \`create_document(timelineId, "Auth Rewrite")\`
|
|
21773
|
-
3. Create tasks: \`create_document(epicId, "Implement JWT refresh")\`
|
|
21774
|
-
4. Set dates
|
|
21872
|
+
3. Create tasks under epics: \`create_document(epicId, "Implement JWT refresh")\`
|
|
21873
|
+
4. Set dates and progress: \`update_metadata(taskId, { dateStart: "2026-03-01", dateEnd: "2026-03-15", taskProgress: 50, color: "#6366f1" })\`
|
|
21775
21874
|
|
|
21776
|
-
**
|
|
21777
|
-
1.
|
|
21778
|
-
2. Create
|
|
21779
|
-
3.
|
|
21780
|
-
4.
|
|
21875
|
+
**Checklist (nested task list):**
|
|
21876
|
+
1. \`create_document(parentId, "Sprint Tasks", "checklist")\`
|
|
21877
|
+
2. Create tasks: \`create_document(checklistId, "Write tests")\`
|
|
21878
|
+
3. Set task properties: \`update_metadata(taskId, { checked: false, priority: 3, dateEnd: "2026-04-01" })\`
|
|
21879
|
+
4. Create sub-tasks (unlimited nesting): \`create_document(taskId, "Unit tests")\`
|
|
21880
|
+
|
|
21881
|
+
**Map with markers and lines:**
|
|
21882
|
+
1. \`create_document(parentId, "Travel Map", "map")\`
|
|
21883
|
+
2. Create markers: \`create_document(mapId, "Coffee Shop")\`
|
|
21884
|
+
3. Set marker position: \`update_metadata(markerId, { geoType: "marker", geoLat: 37.7749, geoLng: -122.4194, icon: "coffee", color: "#f97316" })\`
|
|
21885
|
+
4. Create a line/route: \`create_document(mapId, "Walking Route")\`
|
|
21886
|
+
5. Set line properties: \`update_metadata(routeId, { geoType: "line", color: "#3b82f6" })\`
|
|
21887
|
+
6. Create line points as children: \`create_document(routeId, "Start")\` then \`update_metadata(pointId, { geoLat: 37.7749, geoLng: -122.4194 })\`
|
|
21888
|
+
7. Create measuring tool: set \`geoType: "measure"\` instead of "line"
|
|
21889
|
+
|
|
21890
|
+
**Gallery:**
|
|
21891
|
+
1. \`create_document(parentId, "Photo Album", "gallery")\`
|
|
21892
|
+
2. Create items: \`create_document(galleryId, "Sunset at Beach")\`
|
|
21893
|
+
3. Set metadata: \`update_metadata(itemId, { geoLat: 37.7749, geoLng: -122.4194, tags: ["travel", "sunset"] })\`
|
|
21894
|
+
4. Configure layout: \`update_metadata(galleryId, { galleryColumns: 4, galleryAspect: "16:9" })\`
|
|
21895
|
+
|
|
21896
|
+
**Graph (knowledge graph):**
|
|
21897
|
+
1. \`create_document(parentId, "Tech Stack", "graph")\`
|
|
21898
|
+
2. Create nodes: \`create_document(graphId, "React")\`, \`create_document(graphId, "Vue")\`
|
|
21899
|
+
3. Set node properties: \`update_metadata(nodeId, { color: "#6366f1", icon: "file-code" })\`
|
|
21900
|
+
4. Edges are created by parent-child relationships AND document references (doc embeds/links in content)
|
|
21901
|
+
5. Enable reference edges: \`update_metadata(graphId, { showRefEdges: true })\`
|
|
21902
|
+
|
|
21903
|
+
**Dashboard:**
|
|
21904
|
+
1. \`create_document(parentId, "Overview", "dashboard")\`
|
|
21905
|
+
2. Create items: \`create_document(dashboardId, "Revenue Chart")\`
|
|
21906
|
+
3. Position items on grid: \`update_metadata(itemId, { deskX: 0, deskY: 0, deskMode: "widget-lg" })\`
|
|
21907
|
+
4. Modes: \`"icon"\` (small), \`"widget-sm"\` (240×180), \`"widget-lg"\` (400×320)
|
|
21908
|
+
5. Grid uses 80px cells
|
|
21909
|
+
|
|
21910
|
+
**Spatial (3D scene):**
|
|
21911
|
+
1. \`create_document(parentId, "3D Scene", "spatial")\`
|
|
21912
|
+
2. Create objects: \`create_document(sceneId, "Red Cube")\`
|
|
21913
|
+
3. Set 3D properties: \`update_metadata(objId, { spShape: "box", spColor: "#ef4444", spX: 0, spY: 1, spZ: 0, spSX: 2, spSY: 2, spSZ: 2 })\`
|
|
21914
|
+
4. Shapes: \`"box"\`, \`"sphere"\`, \`"cylinder"\`, \`"cone"\`, \`"plane"\`, \`"torus"\`, \`"glb"\` (uploaded 3D model)
|
|
21915
|
+
5. Rotation (degrees): \`spRX\`, \`spRY\`, \`spRZ\`. Scale: \`spSX\`, \`spSY\`, \`spSZ\` (default 1). Opacity: \`spOpacity\` (0–100)
|
|
21916
|
+
|
|
21917
|
+
**Outline (nested items):**
|
|
21918
|
+
1. \`create_document(parentId, "Meeting Notes", "outline")\`
|
|
21919
|
+
2. Create items: \`create_document(outlineId, "Agenda Item 1")\`
|
|
21920
|
+
3. Create sub-items (unlimited depth): \`create_document(itemId, "Sub-point")\`
|
|
21921
|
+
|
|
21922
|
+
---
|
|
21923
|
+
|
|
21924
|
+
## Document References
|
|
21925
|
+
|
|
21926
|
+
You can link and embed documents within rich-text content:
|
|
21927
|
+
|
|
21928
|
+
**Block-level doc embed** — renders an embedded document preview:
|
|
21929
|
+
\`\`\`
|
|
21930
|
+
![[docId]]
|
|
21931
|
+
\`\`\`
|
|
21932
|
+
|
|
21933
|
+
**Inline link to another document:**
|
|
21934
|
+
\`\`\`
|
|
21935
|
+
[Link text](/doc/docId)
|
|
21936
|
+
\`\`\`
|
|
21937
|
+
|
|
21938
|
+
**Inline wikilink** (shorthand — converted to doc link):
|
|
21939
|
+
\`\`\`
|
|
21940
|
+
[[docId]]
|
|
21941
|
+
[[docId|Display Text]]
|
|
21942
|
+
\`\`\`
|
|
21943
|
+
|
|
21944
|
+
Example — write content with references:
|
|
21945
|
+
\`\`\`
|
|
21946
|
+
write_document(docId, "# Project Overview\\n\\nSee the detailed spec:\\n\\n![[specDocId]]\\n\\nCheck the [timeline](/doc/timelineDocId) for dates.")
|
|
21947
|
+
\`\`\`
|
|
21948
|
+
|
|
21949
|
+
In the **graph** page type, document references (embeds and links) create visible edges between nodes when \`showRefEdges\` is enabled.
|
|
21781
21950
|
|
|
21782
21951
|
---
|
|
21783
21952
|
|
|
@@ -21788,7 +21957,7 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21788
21957
|
| Key | Type | Meaning |
|
|
21789
21958
|
|-----|------|---------|
|
|
21790
21959
|
| \`color\` | string | Hex/CSS color (e.g. "#6366f1") |
|
|
21791
|
-
| \`icon\` | string | Lucide icon name in kebab-case (e.g. "star", "code-2", "users"
|
|
21960
|
+
| \`icon\` | string | Lucide icon name in kebab-case (e.g. "star", "code-2", "users"). **Never emoji.** Omit for default. |
|
|
21792
21961
|
| \`datetimeStart\` / \`datetimeEnd\` | string | ISO datetime with time |
|
|
21793
21962
|
| \`allDay\` | boolean | Whether datetime is all-day |
|
|
21794
21963
|
| \`dateStart\` / \`dateEnd\` | string | ISO date-only (e.g. "2026-03-20") |
|
|
@@ -21806,45 +21975,86 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21806
21975
|
| \`subtitle\` | string | Secondary text |
|
|
21807
21976
|
| \`note\` | string | Free-text note |
|
|
21808
21977
|
| \`taskProgress\` | number | 0–100 progress (timeline tasks) |
|
|
21978
|
+
| \`members\` | { id, label }[] | Assigned users |
|
|
21979
|
+
| \`coverUploadId\` | string | Cover image (upload ID from upload_file) |
|
|
21980
|
+
|
|
21981
|
+
### Geo/Map Keys (for map children)
|
|
21982
|
+
|
|
21983
|
+
| Key | Type | Meaning |
|
|
21984
|
+
|-----|------|---------|
|
|
21985
|
+
| \`geoType\` | string | "marker", "line", or "measure" |
|
|
21986
|
+
| \`geoLat\` | number | Latitude |
|
|
21987
|
+
| \`geoLng\` | number | Longitude |
|
|
21988
|
+
| \`geoDescription\` | string | Location description text |
|
|
21989
|
+
|
|
21990
|
+
### Renderer Config Keys (set on the page doc itself, not its children)
|
|
21809
21991
|
|
|
21810
|
-
|
|
21992
|
+
| Key | Type | Applies to | Values |
|
|
21993
|
+
|-----|------|-----------|--------|
|
|
21994
|
+
| \`kanbanColumnWidth\` | string | kanban | "narrow", "default", "wide" |
|
|
21995
|
+
| \`galleryColumns\` | number | gallery | 1–6 |
|
|
21996
|
+
| \`galleryAspect\` | string | gallery | "square", "4:3", "16:9" |
|
|
21997
|
+
| \`calendarView\` | string | calendar | "month", "week", "day" |
|
|
21998
|
+
| \`calendarWeekStart\` | string | calendar | "sun", "mon" |
|
|
21999
|
+
| \`tableMode\` | string | table | "hierarchy", "flat" |
|
|
22000
|
+
| \`showRefEdges\` | boolean | graph | show doc-reference edges |
|
|
21811
22001
|
|
|
21812
|
-
|
|
22002
|
+
### Spatial 3D Keys (for spatial children)
|
|
21813
22003
|
|
|
21814
|
-
|
|
22004
|
+
| Key | Type | Default | Meaning |
|
|
22005
|
+
|-----|------|---------|---------|
|
|
22006
|
+
| \`spShape\` | string | — | "box", "sphere", "cylinder", "cone", "plane", "torus", "glb" |
|
|
22007
|
+
| \`spColor\` | string | — | CSS color |
|
|
22008
|
+
| \`spOpacity\` | number | 100 | 0–100 |
|
|
22009
|
+
| \`spX\`, \`spY\`, \`spZ\` | number | 0 | Position |
|
|
22010
|
+
| \`spRX\`, \`spRY\`, \`spRZ\` | number | 0 | Rotation (degrees) |
|
|
22011
|
+
| \`spSX\`, \`spSY\`, \`spSZ\` | number | 1 | Scale |
|
|
21815
22012
|
|
|
21816
|
-
|
|
22013
|
+
### Dashboard Keys (for dashboard children)
|
|
22014
|
+
|
|
22015
|
+
| Key | Type | Meaning |
|
|
22016
|
+
|-----|------|---------|
|
|
22017
|
+
| \`deskX\`, \`deskY\` | number | Grid position (80px cells) |
|
|
22018
|
+
| \`deskZ\` | number | Z-index (layering) |
|
|
22019
|
+
| \`deskMode\` | string | "icon", "widget-sm" (240×180), "widget-lg" (400×320) |
|
|
21817
22020
|
|
|
21818
22021
|
---
|
|
21819
22022
|
|
|
21820
22023
|
## Content Structure
|
|
21821
22024
|
|
|
21822
|
-
Documents use a TipTap editor schema
|
|
21823
|
-
- **documentHeader** — The document title (H1)
|
|
21824
|
-
- **documentMeta** — Metadata display block (skip when reading/writing)
|
|
21825
|
-
- **Body blocks** — paragraphs, headings (H2-H6), lists, code blocks, tables, images, and custom components
|
|
21826
|
-
|
|
21827
|
-
### Supported Markdown Elements
|
|
22025
|
+
Documents use a TipTap editor schema. The body supports:
|
|
21828
22026
|
- Headings (# through ######)
|
|
21829
|
-
- Paragraphs with **bold**, *italic*, ~~strikethrough~~,
|
|
21830
|
-
- Bullet lists, ordered lists, task lists
|
|
22027
|
+
- Paragraphs with **bold**, *italic*, ~~strikethrough~~, \\\`code\\\`, [links](url)
|
|
22028
|
+
- Bullet lists, ordered lists, task lists (- [x] / - [ ])
|
|
21831
22029
|
- Code blocks with language
|
|
21832
22030
|
- Blockquotes
|
|
21833
22031
|
- Tables (pipe syntax)
|
|
21834
22032
|
- Horizontal rules
|
|
21835
|
-
- Images
|
|
21836
|
-
-
|
|
22033
|
+
- Images: \`{width="..." height="..."}\`
|
|
22034
|
+
- Document embeds: \`![[docId]]\`
|
|
22035
|
+
- Inline doc links: \`[[docId|label]]\` or \`[label](/doc/docId)\`
|
|
22036
|
+
- MDC components: \`::tip\`, \`::note\`, \`::warning\`, \`::collapsible\`, \`::steps\`, \`::card\`, \`::accordion\`, \`::tabs\`, \`::code-group\`, etc.
|
|
22037
|
+
- Badges: \`:badge[Label]{color="..."}\`
|
|
22038
|
+
- Icons: \`:icon{name="star"}\`
|
|
21837
22039
|
|
|
21838
22040
|
### Writing Content with Frontmatter
|
|
21839
22041
|
|
|
21840
|
-
|
|
22042
|
+
Set document title and metadata via YAML frontmatter:
|
|
21841
22043
|
|
|
21842
22044
|
\`\`\`markdown
|
|
21843
22045
|
---
|
|
21844
22046
|
title: My Document
|
|
21845
22047
|
tags: [important, review]
|
|
21846
22048
|
color: "#6366f1"
|
|
22049
|
+
icon: star
|
|
21847
22050
|
priority: high
|
|
22051
|
+
checked: false
|
|
22052
|
+
datetimeStart: "2026-03-20T10:00:00Z"
|
|
22053
|
+
datetimeEnd: "2026-03-20T11:00:00Z"
|
|
22054
|
+
allDay: false
|
|
22055
|
+
geoLat: 37.7749
|
|
22056
|
+
geoLng: -122.4194
|
|
22057
|
+
geoType: marker
|
|
21848
22058
|
---
|
|
21849
22059
|
|
|
21850
22060
|
Content goes here...
|
|
@@ -21866,8 +22076,7 @@ You do **not** need to call \`set_presence\` after reading or writing content
|
|
|
21866
22076
|
|---|---|
|
|
21867
22077
|
| \`read_document\` | Root \`docId\` updated; TipTap cursor placed at start of document |
|
|
21868
22078
|
| \`write_document\` | Root \`docId\` updated; TipTap cursor placed at end of written content |
|
|
21869
|
-
|
|
21870
|
-
The TipTap cursor (\`anchor\`/\`head\` as \`Y.RelativePosition\`) makes your colored caret with name label appear inline in the document editor for human collaborators.
|
|
22079
|
+
| \`create_document\` | Root \`docId\` updated to the new document |
|
|
21871
22080
|
|
|
21872
22081
|
### set_presence — manual overrides only
|
|
21873
22082
|
|
|
@@ -21889,8 +22098,6 @@ Always clear fields when done by setting them to \`null\`.
|
|
|
21889
22098
|
| **Calendar** | \`calendar:focused\` | eventId | Interacting with an event |
|
|
21890
22099
|
| **Calendar** | \`calendar:viewing\` | "YYYY-MM" | The month/week you're looking at |
|
|
21891
22100
|
| **Outline** | \`outline:editing\` | nodeId | Editing an outline node |
|
|
21892
|
-
| **Outline** | \`outline:selected\` | nodeId | Node selected/hovered |
|
|
21893
|
-
| **Slides** | \`slides:viewing\` | slideId | The slide you're currently on |
|
|
21894
22101
|
| **Gallery** | \`gallery:focused\` | itemId | Item hovered/selected |
|
|
21895
22102
|
| **Timeline** | \`timeline:focused\` | taskId | Task selected |
|
|
21896
22103
|
| **Mindmap** | \`mindmap:focused\` | nodeId | Node selected/edited |
|
|
@@ -21898,7 +22105,7 @@ Always clear fields when done by setting them to \`null\`.
|
|
|
21898
22105
|
| **Map** | \`map:focused\` | markerId | Marker hovered/selected |
|
|
21899
22106
|
| **Doc** | \`doc:scroll\` | 0–1 number | Scroll position in document |
|
|
21900
22107
|
|
|
21901
|
-
Example — mark a kanban card as being inspected, then clear
|
|
22108
|
+
Example — mark a kanban card as being inspected, then clear:
|
|
21902
22109
|
\`\`\`
|
|
21903
22110
|
set_doc_awareness(boardDocId, { "kanban:hovering": cardId })
|
|
21904
22111
|
// ... do work ...
|
|
@@ -21909,73 +22116,48 @@ set_doc_awareness(boardDocId, { "kanban:hovering": null })
|
|
|
21909
22116
|
|
|
21910
22117
|
## Receiving Instructions from Humans
|
|
21911
22118
|
|
|
21912
|
-
Three ways humans can send you tasks:
|
|
21913
|
-
|
|
21914
22119
|
**Channel events (preferred):** When running in channel mode, \`ai:task\` events from
|
|
21915
22120
|
human users arrive as \`<channel source="abracadabra" ...>\` notifications directly in
|
|
21916
|
-
your session.
|
|
21917
|
-
|
|
21918
|
-
task completion.
|
|
22121
|
+
your session. Use the \`reply\` tool to send your response — it creates a reply document
|
|
22122
|
+
and signals task completion.
|
|
21919
22123
|
|
|
21920
|
-
**Chat
|
|
21921
|
-
|
|
21922
|
-
with the chat document's ID.
|
|
22124
|
+
**Chat messages:** Chat messages from the platform arrive as channel notifications. Use
|
|
22125
|
+
\`send_chat_message\` to respond in the same channel.
|
|
21923
22126
|
|
|
21924
22127
|
**Polling (fallback):** Call \`poll_inbox\` to read the "AI Inbox" document.
|
|
21925
|
-
Act on any pending tasks you find there. Write your response back with \`write_document\`
|
|
21926
|
-
or \`create_document\` under the inbox.
|
|
21927
|
-
|
|
21928
|
-
### Channel Tools
|
|
21929
|
-
|
|
21930
|
-
| Tool | Purpose |
|
|
21931
|
-
|------|---------|
|
|
21932
|
-
| \`reply\` | Send a reply to Abracadabra — creates a child doc with your response. Pass \`task_id\` to signal ai:task completion. |
|
|
21933
|
-
| \`watch_chat\` | Start/stop watching a document for chat messages. New content arrives as channel notifications. |
|
|
21934
|
-
|
|
21935
|
-
When you complete a task, always use \`reply\` (in channel mode) or write a response
|
|
21936
|
-
document (in polling mode) so the human knows the work is done.
|
|
21937
22128
|
|
|
21938
22129
|
---
|
|
21939
22130
|
|
|
21940
22131
|
## Common Patterns
|
|
21941
22132
|
|
|
21942
|
-
### Populate a kanban board from a list
|
|
21943
|
-
\`\`\`
|
|
21944
|
-
1. create_document → kanban parent
|
|
21945
|
-
2. For each column: create_document under kanban
|
|
21946
|
-
3. For each card: create_document under the appropriate column
|
|
21947
|
-
4. Optionally set metadata (color, tags, priority) on cards
|
|
21948
|
-
\`\`\`
|
|
21949
|
-
|
|
21950
22133
|
### Explore a document fully (the correct way)
|
|
21951
22134
|
\`\`\`
|
|
21952
|
-
|
|
21953
|
-
|
|
21954
|
-
1. read_document(marketingId)
|
|
22135
|
+
1. read_document(docId)
|
|
21955
22136
|
→ returns { markdown, children: [...] }
|
|
21956
22137
|
2. The body may be empty — that does NOT mean the doc is empty.
|
|
21957
22138
|
Children ARE the content. Read every child:
|
|
21958
22139
|
read_document(childId1), read_document(childId2), ...
|
|
21959
|
-
3.
|
|
21960
|
-
Continue recursively until no children remain.
|
|
22140
|
+
3. Continue recursively until no children remain.
|
|
21961
22141
|
\`\`\`
|
|
21962
22142
|
|
|
21963
22143
|
**Never conclude a document is "empty" just because its markdown body is empty.
|
|
21964
22144
|
Always check and traverse the \`children\` array returned by \`read_document\`.**
|
|
21965
22145
|
|
|
21966
|
-
###
|
|
22146
|
+
### Populate a kanban board from a list
|
|
21967
22147
|
\`\`\`
|
|
21968
|
-
1.
|
|
21969
|
-
2.
|
|
21970
|
-
3.
|
|
22148
|
+
1. create_document → kanban parent
|
|
22149
|
+
2. For each column: create_document under kanban
|
|
22150
|
+
3. For each card: create_document under the appropriate column
|
|
22151
|
+
4. Optionally set metadata (color, tags, priority) on cards
|
|
21971
22152
|
\`\`\`
|
|
21972
22153
|
|
|
21973
|
-
###
|
|
22154
|
+
### Write rich content with references
|
|
21974
22155
|
\`\`\`
|
|
21975
|
-
1.
|
|
21976
|
-
2.
|
|
21977
|
-
3.
|
|
21978
|
-
4.
|
|
22156
|
+
1. read_document to see current content (and children)
|
|
22157
|
+
2. write_document with markdown (mode: "replace" or "append")
|
|
22158
|
+
3. Use ![[docId]] to embed other documents
|
|
22159
|
+
4. Use [[docId|label]] for inline doc links
|
|
22160
|
+
5. Include frontmatter for title/metadata if needed
|
|
21979
22161
|
\`\`\`
|
|
21980
22162
|
|
|
21981
22163
|
---
|
|
@@ -22001,6 +22183,7 @@ Always check and traverse the \`children\` array returned by \`read_document\`.*
|
|
|
22001
22183
|
- Use emoji as \`icon\` values — only lowercase kebab-case Lucide icon names are valid
|
|
22002
22184
|
- Create top-level documents without first confirming the correct hub doc ID
|
|
22003
22185
|
- Rename or write content to the space hub document itself
|
|
22186
|
+
- Set \`type\` on child items (cards, cells, events) — only set type on the parent page
|
|
22004
22187
|
`;
|
|
22005
22188
|
function registerAgentGuide(mcp) {
|
|
22006
22189
|
mcp.resource("agent-guide", "abracadabra://agent-guide", {
|
|
@@ -22093,6 +22276,221 @@ function registerServerInfoResource(mcp, server) {
|
|
|
22093
22276
|
});
|
|
22094
22277
|
}
|
|
22095
22278
|
|
|
22279
|
+
//#endregion
|
|
22280
|
+
//#region packages/mcp/src/tools/hooks.ts
|
|
22281
|
+
function registerHookTools(mcp, hookBridge) {
|
|
22282
|
+
mcp.tool("get_hook_config", "Returns Claude Code hook configuration JSON for bridging activity to the Abracadabra dashboard. Copy the \"hooks\" object into your .claude/settings.local.json to enable real-time activity indicators (tool calls, subagents, etc.) visible to all connected users.", {}, async () => {
|
|
22283
|
+
const port = hookBridge.port;
|
|
22284
|
+
if (!port) return {
|
|
22285
|
+
content: [{
|
|
22286
|
+
type: "text",
|
|
22287
|
+
text: "Hook bridge is not running."
|
|
22288
|
+
}],
|
|
22289
|
+
isError: true
|
|
22290
|
+
};
|
|
22291
|
+
const hookEntry = {
|
|
22292
|
+
type: "http",
|
|
22293
|
+
url: `http://127.0.0.1:${port}/hook`,
|
|
22294
|
+
timeout: 3
|
|
22295
|
+
};
|
|
22296
|
+
const config = { hooks: {
|
|
22297
|
+
PreToolUse: [{ hooks: [hookEntry] }],
|
|
22298
|
+
PostToolUse: [{ hooks: [hookEntry] }],
|
|
22299
|
+
SubagentStart: [{ hooks: [hookEntry] }],
|
|
22300
|
+
SubagentStop: [{ hooks: [hookEntry] }],
|
|
22301
|
+
Stop: [{ hooks: [hookEntry] }]
|
|
22302
|
+
} };
|
|
22303
|
+
return { content: [{
|
|
22304
|
+
type: "text",
|
|
22305
|
+
text: JSON.stringify(config, null, 2)
|
|
22306
|
+
}] };
|
|
22307
|
+
});
|
|
22308
|
+
}
|
|
22309
|
+
|
|
22310
|
+
//#endregion
|
|
22311
|
+
//#region packages/mcp/src/hook-bridge.ts
|
|
22312
|
+
/**
|
|
22313
|
+
* HookBridge — lightweight HTTP server that receives Claude Code hook events
|
|
22314
|
+
* and translates them into Yjs awareness updates via AbracadabraMCPServer.
|
|
22315
|
+
*
|
|
22316
|
+
* Claude Code hooks (PreToolUse, PostToolUse, SubagentStart, SubagentStop, Stop)
|
|
22317
|
+
* POST JSON to http://127.0.0.1:{port}/hook. The bridge maps these to awareness
|
|
22318
|
+
* fields (status, activeToolCall) so the cou-sh dashboard shows real-time activity.
|
|
22319
|
+
*/
|
|
22320
|
+
/** Map Claude Code tool names to awareness-friendly names + extract a target string. */
|
|
22321
|
+
function mapToolCall(toolName, toolInput) {
|
|
22322
|
+
switch (toolName) {
|
|
22323
|
+
case "Bash": return {
|
|
22324
|
+
name: "bash",
|
|
22325
|
+
target: truncate(toolInput.command ?? toolInput.description, 60)
|
|
22326
|
+
};
|
|
22327
|
+
case "Read": return {
|
|
22328
|
+
name: "read_file",
|
|
22329
|
+
target: basename(toolInput.file_path)
|
|
22330
|
+
};
|
|
22331
|
+
case "Edit": return {
|
|
22332
|
+
name: "edit_file",
|
|
22333
|
+
target: basename(toolInput.file_path)
|
|
22334
|
+
};
|
|
22335
|
+
case "Write": return {
|
|
22336
|
+
name: "write_file",
|
|
22337
|
+
target: basename(toolInput.file_path)
|
|
22338
|
+
};
|
|
22339
|
+
case "Grep": return {
|
|
22340
|
+
name: "grep",
|
|
22341
|
+
target: truncate(toolInput.pattern, 40)
|
|
22342
|
+
};
|
|
22343
|
+
case "Glob": return {
|
|
22344
|
+
name: "glob",
|
|
22345
|
+
target: truncate(toolInput.pattern, 40)
|
|
22346
|
+
};
|
|
22347
|
+
case "Agent": return {
|
|
22348
|
+
name: "subagent",
|
|
22349
|
+
target: toolInput.description || toolInput.subagent_type || "agent"
|
|
22350
|
+
};
|
|
22351
|
+
case "WebFetch": return {
|
|
22352
|
+
name: "web_fetch",
|
|
22353
|
+
target: hostname(toolInput.url)
|
|
22354
|
+
};
|
|
22355
|
+
case "WebSearch": return {
|
|
22356
|
+
name: "web_search",
|
|
22357
|
+
target: truncate(toolInput.query, 40)
|
|
22358
|
+
};
|
|
22359
|
+
default: return { name: toolName.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase() };
|
|
22360
|
+
}
|
|
22361
|
+
}
|
|
22362
|
+
function truncate(str, max) {
|
|
22363
|
+
if (!str) return void 0;
|
|
22364
|
+
return str.length > max ? str.slice(0, max) + "..." : str;
|
|
22365
|
+
}
|
|
22366
|
+
function basename(filePath) {
|
|
22367
|
+
if (!filePath) return void 0;
|
|
22368
|
+
return path.basename(filePath);
|
|
22369
|
+
}
|
|
22370
|
+
function hostname(url) {
|
|
22371
|
+
if (!url) return void 0;
|
|
22372
|
+
try {
|
|
22373
|
+
return new URL(url).hostname;
|
|
22374
|
+
} catch {
|
|
22375
|
+
return url.slice(0, 30);
|
|
22376
|
+
}
|
|
22377
|
+
}
|
|
22378
|
+
var HookBridge = class {
|
|
22379
|
+
constructor(server) {
|
|
22380
|
+
this.server = server;
|
|
22381
|
+
this.httpServer = null;
|
|
22382
|
+
this._port = null;
|
|
22383
|
+
this.portFilePath = process.env.ABRA_HOOK_PORT_FILE || path.join(os.tmpdir(), "abracadabra-mcp-hook.port");
|
|
22384
|
+
}
|
|
22385
|
+
get port() {
|
|
22386
|
+
return this._port;
|
|
22387
|
+
}
|
|
22388
|
+
/** Start the HTTP server on a random port and write the port file. */
|
|
22389
|
+
async start() {
|
|
22390
|
+
return new Promise((resolve, reject) => {
|
|
22391
|
+
const srv = http.createServer((req, res) => this.handleRequest(req, res));
|
|
22392
|
+
srv.on("error", reject);
|
|
22393
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
22394
|
+
const addr = srv.address();
|
|
22395
|
+
if (!addr || typeof addr === "string") {
|
|
22396
|
+
reject(/* @__PURE__ */ new Error("Failed to get server address"));
|
|
22397
|
+
return;
|
|
22398
|
+
}
|
|
22399
|
+
this._port = addr.port;
|
|
22400
|
+
this.httpServer = srv;
|
|
22401
|
+
try {
|
|
22402
|
+
fs.writeFileSync(this.portFilePath, String(this._port), "utf-8");
|
|
22403
|
+
} catch (err) {
|
|
22404
|
+
console.error(`[hook-bridge] Warning: could not write port file: ${err.message}`);
|
|
22405
|
+
}
|
|
22406
|
+
console.error(`[hook-bridge] Listening on 127.0.0.1:${this._port}`);
|
|
22407
|
+
console.error(`[hook-bridge] Port file: ${this.portFilePath}`);
|
|
22408
|
+
resolve(this._port);
|
|
22409
|
+
});
|
|
22410
|
+
});
|
|
22411
|
+
}
|
|
22412
|
+
/** Shut down the HTTP server and remove the port file. */
|
|
22413
|
+
async destroy() {
|
|
22414
|
+
if (this.httpServer) {
|
|
22415
|
+
await new Promise((resolve) => {
|
|
22416
|
+
this.httpServer.close(() => resolve());
|
|
22417
|
+
});
|
|
22418
|
+
this.httpServer = null;
|
|
22419
|
+
}
|
|
22420
|
+
try {
|
|
22421
|
+
fs.unlinkSync(this.portFilePath);
|
|
22422
|
+
} catch {}
|
|
22423
|
+
this._port = null;
|
|
22424
|
+
console.error("[hook-bridge] Shut down");
|
|
22425
|
+
}
|
|
22426
|
+
handleRequest(req, res) {
|
|
22427
|
+
if (req.method !== "POST" || req.url !== "/hook") {
|
|
22428
|
+
res.writeHead(404);
|
|
22429
|
+
res.end();
|
|
22430
|
+
return;
|
|
22431
|
+
}
|
|
22432
|
+
let body = "";
|
|
22433
|
+
req.on("data", (chunk) => {
|
|
22434
|
+
body += chunk.toString();
|
|
22435
|
+
});
|
|
22436
|
+
req.on("end", () => {
|
|
22437
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
22438
|
+
res.end("{}");
|
|
22439
|
+
try {
|
|
22440
|
+
const payload = JSON.parse(body);
|
|
22441
|
+
this.routeEvent(payload);
|
|
22442
|
+
} catch {}
|
|
22443
|
+
});
|
|
22444
|
+
}
|
|
22445
|
+
routeEvent(payload) {
|
|
22446
|
+
switch (payload.hook_event_name) {
|
|
22447
|
+
case "PreToolUse":
|
|
22448
|
+
this.onPreToolUse(payload);
|
|
22449
|
+
break;
|
|
22450
|
+
case "PostToolUse":
|
|
22451
|
+
this.onPostToolUse(payload);
|
|
22452
|
+
break;
|
|
22453
|
+
case "SubagentStart":
|
|
22454
|
+
this.onSubagentStart(payload);
|
|
22455
|
+
break;
|
|
22456
|
+
case "SubagentStop":
|
|
22457
|
+
this.onSubagentStop(payload);
|
|
22458
|
+
break;
|
|
22459
|
+
case "Stop":
|
|
22460
|
+
this.onStop();
|
|
22461
|
+
break;
|
|
22462
|
+
}
|
|
22463
|
+
}
|
|
22464
|
+
onPreToolUse(payload) {
|
|
22465
|
+
const toolName = payload.tool_name ?? "";
|
|
22466
|
+
if (toolName.startsWith("mcp__abracadabra__")) return;
|
|
22467
|
+
const mapped = mapToolCall(toolName, payload.tool_input ?? {});
|
|
22468
|
+
if (mapped) {
|
|
22469
|
+
this.server.setActiveToolCall(mapped);
|
|
22470
|
+
this.server.setAutoStatus("working");
|
|
22471
|
+
}
|
|
22472
|
+
}
|
|
22473
|
+
onPostToolUse(payload) {
|
|
22474
|
+
if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
|
|
22475
|
+
this.server.setActiveToolCall(null);
|
|
22476
|
+
}
|
|
22477
|
+
onSubagentStart(payload) {
|
|
22478
|
+
const agentType = payload.agent_type ?? "agent";
|
|
22479
|
+
this.server.setActiveToolCall({
|
|
22480
|
+
name: "subagent",
|
|
22481
|
+
target: agentType
|
|
22482
|
+
});
|
|
22483
|
+
this.server.setAutoStatus("thinking");
|
|
22484
|
+
}
|
|
22485
|
+
onSubagentStop(_payload) {
|
|
22486
|
+
this.server.setActiveToolCall(null);
|
|
22487
|
+
}
|
|
22488
|
+
onStop() {
|
|
22489
|
+
this.server.setAutoStatus(null);
|
|
22490
|
+
this.server.setActiveToolCall(null);
|
|
22491
|
+
}
|
|
22492
|
+
};
|
|
22493
|
+
|
|
22096
22494
|
//#endregion
|
|
22097
22495
|
//#region packages/mcp/src/index.ts
|
|
22098
22496
|
/**
|
|
@@ -22134,8 +22532,9 @@ async function main() {
|
|
|
22134
22532
|
## Key Concepts
|
|
22135
22533
|
- Documents form a tree. A kanban board's columns are child documents; cards are grandchildren.
|
|
22136
22534
|
- A document's label IS its display name everywhere. Children ARE the content (not just the body text).
|
|
22137
|
-
- Page types (doc, kanban, table, calendar, timeline, outline, etc.) are views over the SAME tree — switching types preserves data.
|
|
22535
|
+
- Page types (doc, kanban, table, calendar, timeline, checklist, outline, gallery, map, graph, dashboard, spatial, media, mindmap, etc.) are views over the SAME tree — switching types preserves data.
|
|
22138
22536
|
- An empty markdown body does NOT mean empty content — always check the children array.
|
|
22537
|
+
- Use ![[docId]] in content to embed another document, or [[docId|label]] for inline links.
|
|
22139
22538
|
|
|
22140
22539
|
## Finding Documents
|
|
22141
22540
|
- list_documents only shows ONE level of children. If you don't find what you need, use find_document to search the entire tree by name, or get_document_tree to see the full hierarchy.
|
|
@@ -22172,12 +22571,21 @@ Read the resource at abracadabra://agent-guide for the complete guide covering p
|
|
|
22172
22571
|
console.error(`[abracadabra-mcp] Failed to connect: ${error.message}`);
|
|
22173
22572
|
process.exit(1);
|
|
22174
22573
|
}
|
|
22574
|
+
const hookBridge = new HookBridge(server);
|
|
22575
|
+
try {
|
|
22576
|
+
const hookPort = await hookBridge.start();
|
|
22577
|
+
console.error(`[abracadabra-mcp] Hook bridge listening on port ${hookPort}`);
|
|
22578
|
+
} catch (error) {
|
|
22579
|
+
console.error(`[abracadabra-mcp] Hook bridge failed to start: ${error.message}`);
|
|
22580
|
+
}
|
|
22581
|
+
registerHookTools(mcp, hookBridge);
|
|
22175
22582
|
const transport = new StdioServerTransport();
|
|
22176
22583
|
await mcp.connect(transport);
|
|
22177
22584
|
server.startChannelNotifications(mcp);
|
|
22178
22585
|
console.error("[abracadabra-mcp] MCP server running on stdio");
|
|
22179
22586
|
const shutdown = async () => {
|
|
22180
22587
|
console.error("[abracadabra-mcp] Shutting down...");
|
|
22588
|
+
await hookBridge.destroy();
|
|
22181
22589
|
await server.destroy();
|
|
22182
22590
|
process.exit(0);
|
|
22183
22591
|
};
|