@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.
@@ -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
- }, 3e4);
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) conn.provider.destroy();
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: \"doc\", \"kanban\", \"calendar\", \"table\", \"outline\", \"gallery\", \"slides\", \"timeline\", \"whiteboard\", \"map\", \"dashboard\", \"mindmap\", \"graph\". Omit to inherit parent view."),
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 displayText = match[6] ?? match[5];
20280
- tokens.push({ text: displayText });
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). Standard PageMeta keys: color (hex string), icon (Lucide kebab-case name like \"star\"/\"code-2\"/\"users\" — NEVER emoji), dateStart, dateEnd, datetimeStart, datetimeEnd, allDay, tags, checked, priority (0-4), status, rating, url, taskProgress (0-100), subtitle, note. Set a key to null to clear it.")
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 Limit |
21737
- |------|-------------|-------------------|-------------|
21738
- | **doc** | Sub-documents | Sub-sub-documents | Unlimited |
21739
- | **kanban** | Columns | Cards | 2 |
21740
- | **table** | Columns | Cells (positional rows) | 2 |
21741
- | **calendar** | Events | — | 1 |
21742
- | **timeline** | Epics | Tasks | 2 |
21743
- | **outline** | Items | Sub-items | Unlimited |
21744
- | **mindmap** | Central nodes | Branches | Unlimited |
21745
- | **graph** | Nodes | | 1 |
21746
- | **gallery** | Items | — | 1 |
21747
- | **slides** | Slides | — | 1 |
21748
- | **whiteboard** | Objects | | 1 |
21749
- | **map** | Markers/Lines | Points (for lines) | 2 |
21750
- | **desktop** | Items | | 1 |
21751
- | **call** | | — | 0 |
21752
- | **game** | | — | 0 |
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. Create the kanban document: \`create_document(parentId, "Project Board", "kanban")\`
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. Create calendar: \`create_document(parentId, "Team Calendar", "calendar")\`
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 with tasks:**
21771
- 1. Create timeline: \`create_document(parentId, "Q1 Roadmap", "timeline")\`
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/progress: \`update_metadata(taskId, { dateStart: "2026-03-01", dateEnd: "2026-03-15", taskProgress: 50 })\`
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
- **Table with data:**
21777
- 1. Create table: \`create_document(parentId, "Contacts", "table")\`
21778
- 2. Create columns: \`create_document(tableId, "Name")\`, \`create_document(tableId, "Email")\`, \`create_document(tableId, "Phone")\`
21779
- 3. Create cells (children of columns): \`create_document(nameColId, "Alice")\`, \`create_document(emailColId, "alice@example.com")\`
21780
- 4. Rows are positional the Nth child of each column forms row N
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", "calendar-days", "book-open"). **Never emoji.** Omit to use the page type's default icon. |
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
- ### Type-Specific Meta Schemas
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
- **Calendar events** require: \`datetimeStart\`, \`datetimeEnd\`, optionally \`allDay\`, \`color\`
22002
+ ### Spatial 3D Keys (for spatial children)
21813
22003
 
21814
- **Timeline tasks** require: \`dateStart\`, \`dateEnd\`, optionally \`taskProgress\` (0–100), \`color\`
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
- **Map markers** require: \`geoLat\`, \`geoLng\`, \`geoType\` ("marker"|"line"|"measure"), optionally \`icon\`, \`color\`
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 with these top-level elements:
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~~, \`code\`, [links](url)
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
- - MDC components: callout, collapsible, steps, card, accordion, tabs, code-group, etc.
22033
+ - Images: \`![alt](src){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
- You can set document title and metadata via YAML frontmatter:
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 on completion:
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. These include the sender name, task ID, and document context. Use the
21917
- \`reply\` tool to send your response back — it creates a reply document and signals
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 document watching:** Use \`watch_chat\` to observe a document for new messages.
21921
- Changes are pushed as channel notifications in real time. Reply using the \`reply\` tool
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
- User: "Check out the Marketing doc"
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. Each child's response also includes ITS children.
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
- ### Write rich content to a document
22146
+ ### Populate a kanban board from a list
21967
22147
  \`\`\`
21968
- 1. read_document to see current content (and children)
21969
- 2. write_document with markdown (mode: "replace" to overwrite, "append" to add)
21970
- 3. Include frontmatter for title/metadata if needed
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
- ### Organize documents into a hierarchy
22154
+ ### Write rich content with references
21974
22155
  \`\`\`
21975
- 1. get_document_tree to understand current structure
21976
- 2. create_document to add new nodes
21977
- 3. move_document to reorganize
21978
- 4. change_document_type to switch views
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
  };