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