drafted 1.17.12 → 1.17.13

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.
Files changed (2) hide show
  1. package/mcp/server.mjs +27 -3
  2. package/package.json +1 -1
package/mcp/server.mjs CHANGED
@@ -309,6 +309,7 @@ const TOOL_ANNOTATIONS = {
309
309
 
310
310
  // Canvas / view
311
311
  focus: { title: 'Focus on target', readOnlyHint: false, destructiveHint: false, openWorldHint: false, description: 'Pan the canvas viewport for connected clients to a frame, lane, or layer.' },
312
+ tour: { title: 'Guided tours', readOnlyHint: false, destructiveHint: false, openWorldHint: false, description: 'Play or stop an agent-authored guided tour (driver.js) on the project surface for all connected clients. Tours live on a frame\'s metadata.tour — author them with fs(write, metadata={tour: {title, steps}}). Dispatch by `action`: play (frameId) starts the walkthrough, stop (projectId) ends it. Use for presentations and collaborative walkthroughs.' },
312
313
  screenshot: { title: 'Screenshot', readOnlyHint: true, destructiveHint: false, openWorldHint: false, description: 'Render a PNG via headless browser. `scope=frame` for a single frame, `scope=canvas` for a region of the project surface.' },
313
314
 
314
315
  // Minions — checklist-driven intake surfaces bound to a project
@@ -418,7 +419,7 @@ function tool(name, descOrSchema, schemaOrHandler, handler) {
418
419
  const v = args[0][k];
419
420
  if (typeof v === 'string' && v.trim()) {
420
421
  try { args[0][k] = JSON.parse(v); }
421
- catch { return err(new Error(`Param "${k}" must be a JSON-encoded ${['excalidraw_data', 'frontmatter', 'target', 'output', 'state'].includes(k) ? 'object' : 'array'} string; JSON.parse failed.`)); }
422
+ catch { return err(new Error(`Param "${k}" must be a JSON-encoded ${['excalidraw_data', 'frontmatter', 'target', 'output', 'state', 'metadata'].includes(k) ? 'object' : 'array'} string; JSON.parse failed.`)); }
422
423
  }
423
424
  }
424
425
  }
@@ -2299,6 +2300,27 @@ tool('trigger', {
2299
2300
  } catch (error) { return err(error); }
2300
2301
  });
2301
2302
 
2303
+ tool('tour', {
2304
+ action: z.enum(['play', 'stop']).describe('play: start a guided tour on the project surface for all connected clients. stop: end the currently playing tour.'),
2305
+ frameId: z.string().optional().describe('[play] Tour frame: a frame path (/projects/<project>/<layer>/<lane>/<file>), frame URL, or frame UUID. The frame must carry metadata.tour (steps: [{target, title?, body?, side?, advanceOnClick?}]).'),
2306
+ projectId: z.string().optional().describe('[stop] Project UUID or slug owning the tour.'),
2307
+ }, async ({ action, frameId, projectId }) => {
2308
+ try {
2309
+ if (action === 'play') {
2310
+ if (!frameId) throw new Error('frameId is required for tour play');
2311
+ const { frameId: fid } = await resolveFsFramePath(frameId);
2312
+ const result = await api('POST', '/api/tour/play', { frameId: fid });
2313
+ return ok(result);
2314
+ }
2315
+ if (action === 'stop') {
2316
+ if (!projectId) throw new Error('projectId is required for tour stop');
2317
+ const result = await api('POST', '/api/tour/stop', { projectId });
2318
+ return ok(result);
2319
+ }
2320
+ throw new Error(`Unknown tour action: ${action}`);
2321
+ } catch (error) { return err(error); }
2322
+ });
2323
+
2302
2324
  tool('focus', {
2303
2325
  target: z.string().describe('What to pan the canvas to: a frame path (/projects/<project>/<layer>/<lane>/<file>), a frame URL (any URL containing /f/{uuid} or /o/<org>/projects/...), or a frame ID (UUID). When a user shares a Drafted link, pass it directly here.'),
2304
2326
  }, async ({ target }) => {
@@ -2565,7 +2587,7 @@ server.resource('info', 'drafted://info', {
2565
2587
  skills: '/o/<org>/skills/<slug>',
2566
2588
  projects: '/o/<org>/projects/<folder?>/<project>/<layer>/<lane>/<file>',
2567
2589
  },
2568
- tools: ['fs (ls/read/write/edit/mv/rm/mkdir/search)', 'whoami', 'auth', 'session', 'trigger', 'focus', 'screenshot', 'minion'],
2590
+ tools: ['fs (ls/read/write/edit/mv/rm/mkdir/search)', 'whoami', 'auth', 'session', 'trigger', 'focus', 'tour', 'screenshot', 'minion'],
2569
2591
  }, null, 2),
2570
2592
  }],
2571
2593
  };
@@ -2583,13 +2605,14 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
2583
2605
  title: z.string().optional().describe('[write + googleType] title for a new native file'),
2584
2606
  ops: z.array(z.any()).optional().describe('[edit] hashline ops for text frames. Apply ONLY against a fresh fs(read) of the frame: each op = {type, lineHash, newContent} where type is one of replace | delete | insertAfter | insertBefore, and lineHash is the FULL anchor from the read output (line number + hash, e.g. the token left of the |, like 42srt) — NOT a bare hash. delete/replace consume the target line; at most one replace/delete per line per edit (combine into one replace). If an op targets a line that changed since read it is rejected — re-read and retry. Element ops for excalidraw ({id,x,y,...}), or structured ops for office'),
2585
2607
  state: z.any().optional().describe('[write] app-frame state (JSON) to persist for a deployed windowType:"app" frame; the canvas hydrates the app from it on load. Max 64KB.'),
2608
+ metadata: z.any().optional().describe('[write] JSON metadata to attach to the written frame (e.g. {"tour": {title, auto?, steps}} to define a guided tour on this frame). Max 64KB.'),
2586
2609
  recursive: z.boolean().optional().describe('[ls] recurse into subdirectories'),
2587
2610
  lines: z.string().optional().describe('[read] line range (e.g. "1-50") — partial read; content is hashline-annotated so a later edit stays surgical'),
2588
2611
  pattern: z.string().optional().describe('[ls] filter filenames (e.g. "*.html")'),
2589
2612
  org: z.string().optional().describe('Org (id or name) for bare /wiki and /skills paths (org-scoped /o/<org>/... paths don\'t need it). Per-request only; project paths self-derive org from the project row.'),
2590
2613
  projectId: PROJECT_OVERRIDE_PARAM,
2591
2614
  }, async (args) => {
2592
- const { action, path: rawPath, to: rawTo, query, content, file_path, base64, googleType, title, ops, state, recursive, pattern, lines, org } = args;
2615
+ const { action, path: rawPath, to: rawTo, query, content, file_path, base64, googleType, title, ops, state, metadata, recursive, pattern, lines, org } = args;
2593
2616
  // Org-scoped path (Shape A): /o/<org>/<root>/... — strip the org segment here,
2594
2617
  // carry it as the per-request org scope (orgHeader), validate per-root below.
2595
2618
  // The PATH is the address; the org= param is legacy for bare roots only, and a
@@ -2969,6 +2992,7 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
2969
2992
  }
2970
2993
  else if (base64) body.base64 = base64;
2971
2994
  else return err(new Error('write requires one of content / file_path / base64 / googleType / state'));
2995
+ if (metadata !== undefined) body.metadata = metadata;
2972
2996
  const result = await api('PUT', `/api/fs/${filePath}`, body, orgHeader);
2973
2997
  if (state !== undefined && result?.id) {
2974
2998
  try { await api('POST', `/api/file/${result.id}/state`, { state }); } catch { /* state best-effort */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.17.12",
3
+ "version": "1.17.13",
4
4
  "description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
5
5
  "type": "module",
6
6
  "files": [