@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.
@@ -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
- }, 3e4);
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) conn.provider.destroy();
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: \"doc\", \"kanban\", \"calendar\", \"table\", \"outline\", \"gallery\", \"slides\", \"timeline\", \"whiteboard\", \"map\", \"dashboard\", \"mindmap\", \"graph\". Omit to inherit parent view."),
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 displayText = match[6] ?? match[5];
20283
- tokens.push({ text: displayText });
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). 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.")
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 Limit |
21743
- |------|-------------|-------------------|-------------|
21744
- | **doc** | Sub-documents | Sub-sub-documents | Unlimited |
21745
- | **kanban** | Columns | Cards | 2 |
21746
- | **table** | Columns | Cells (positional rows) | 2 |
21747
- | **calendar** | Events | — | 1 |
21748
- | **timeline** | Epics | Tasks | 2 |
21749
- | **outline** | Items | Sub-items | Unlimited |
21750
- | **mindmap** | Central nodes | Branches | Unlimited |
21751
- | **graph** | Nodes | | 1 |
21752
- | **gallery** | Items | — | 1 |
21753
- | **slides** | Slides | — | 1 |
21754
- | **whiteboard** | Objects | | 1 |
21755
- | **map** | Markers/Lines | Points (for lines) | 2 |
21756
- | **desktop** | Items | | 1 |
21757
- | **call** | | — | 0 |
21758
- | **game** | | — | 0 |
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. Create the kanban document: \`create_document(parentId, "Project Board", "kanban")\`
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. Create calendar: \`create_document(parentId, "Team Calendar", "calendar")\`
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 with tasks:**
21777
- 1. Create timeline: \`create_document(parentId, "Q1 Roadmap", "timeline")\`
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/progress: \`update_metadata(taskId, { dateStart: "2026-03-01", dateEnd: "2026-03-15", taskProgress: 50 })\`
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
- **Table with data:**
21783
- 1. Create table: \`create_document(parentId, "Contacts", "table")\`
21784
- 2. Create columns: \`create_document(tableId, "Name")\`, \`create_document(tableId, "Email")\`, \`create_document(tableId, "Phone")\`
21785
- 3. Create cells (children of columns): \`create_document(nameColId, "Alice")\`, \`create_document(emailColId, "alice@example.com")\`
21786
- 4. Rows are positional the Nth child of each column forms row N
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", "calendar-days", "book-open"). **Never emoji.** Omit to use the page type's default icon. |
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
- ### Type-Specific Meta Schemas
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
- **Calendar events** require: \`datetimeStart\`, \`datetimeEnd\`, optionally \`allDay\`, \`color\`
22009
+ ### Spatial 3D Keys (for spatial children)
21819
22010
 
21820
- **Timeline tasks** require: \`dateStart\`, \`dateEnd\`, optionally \`taskProgress\` (0–100), \`color\`
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
- **Map markers** require: \`geoLat\`, \`geoLng\`, \`geoType\` ("marker"|"line"|"measure"), optionally \`icon\`, \`color\`
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 with these top-level elements:
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~~, \`code\`, [links](url)
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
- - MDC components: callout, collapsible, steps, card, accordion, tabs, code-group, etc.
22040
+ - Images: \`![alt](src){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
- You can set document title and metadata via YAML frontmatter:
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 on completion:
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. These include the sender name, task ID, and document context. Use the
21923
- \`reply\` tool to send your response back — it creates a reply document and signals
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 document watching:** Use \`watch_chat\` to observe a document for new messages.
21927
- Changes are pushed as channel notifications in real time. Reply using the \`reply\` tool
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
- User: "Check out the Marketing doc"
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. Each child's response also includes ITS children.
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
- ### Write rich content to a document
22153
+ ### Populate a kanban board from a list
21973
22154
  \`\`\`
21974
- 1. read_document to see current content (and children)
21975
- 2. write_document with markdown (mode: "replace" to overwrite, "append" to add)
21976
- 3. Include frontmatter for title/metadata if needed
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
- ### Organize documents into a hierarchy
22161
+ ### Write rich content with references
21980
22162
  \`\`\`
21981
- 1. get_document_tree to understand current structure
21982
- 2. create_document to add new nodes
21983
- 3. move_document to reorganize
21984
- 4. change_document_type to switch views
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
  };