viteboard 0.2.0 → 0.2.1

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.
@@ -16,9 +16,9 @@ export async function renderPdfFromHtml(html, fileName, pdf = {}) {
16
16
  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "viteboard-pdf-"));
17
17
  const htmlPath = path.join(tmpDir, "publication.html");
18
18
  const profileDir = path.join(tmpDir, "profile");
19
- const browser = await resolveBrowserBinary();
20
19
  let browserProcess = null;
21
20
  try {
21
+ const browser = await resolveBrowserBinary();
22
22
  await fs.writeFile(htmlPath, html, "utf-8");
23
23
  await fs.mkdir(profileDir, { recursive: true });
24
24
  browserProcess = execFile(browser, [
@@ -28,12 +28,12 @@ export async function renderPdfFromHtml(html, fileName, pdf = {}) {
28
28
  `--user-data-dir=${profileDir}`,
29
29
  "--remote-debugging-port=0",
30
30
  "about:blank",
31
- ], { windowsHide: true });
31
+ ], { windowsHide: true, detached: process.platform !== "win32" });
32
32
 
33
33
  const activePortPath = path.join(profileDir, "DevToolsActivePort");
34
34
  const port = await waitForDevToolsPort(activePortPath);
35
35
  const wsUrl = await openCdpPage(port);
36
- return await printPdfViaCdp(wsUrl, pathToFileURL(htmlPath).href, {
36
+ return await withTimeout(printPdfViaCdp(wsUrl, pathToFileURL(htmlPath).href, {
37
37
  displayHeaderFooter: Boolean(pdf.displayHeaderFooter),
38
38
  headerTemplate: pdf.headerTemplate,
39
39
  footerTemplate: pdf.footerTemplate,
@@ -41,17 +41,72 @@ export async function renderPdfFromHtml(html, fileName, pdf = {}) {
41
41
  marginBottomMm: pdf.marginBottomMm,
42
42
  marginLeftMm: pdf.marginLeftMm,
43
43
  marginRightMm: pdf.marginRightMm,
44
- });
44
+ }), 120_000, "PDF rendering timed out.");
45
45
  } finally {
46
- if (browserProcess) {
47
- const exited = new Promise((resolve) => {
48
- browserProcess.once("exit", resolve);
49
- browserProcess.once("close", resolve);
50
- });
51
- try { browserProcess.kill(); } catch { /* ignore */ }
52
- await Promise.race([exited, delay(500)]);
46
+ await stopBrowserProcess(browserProcess);
47
+ await removeTemporaryDirectory(tmpDir);
48
+ }
49
+ }
50
+
51
+ export async function evaluatePageFromUrl(url, expression) {
52
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "viteboard-pdf-"));
53
+ const profileDir = path.join(tmpDir, "profile");
54
+ let browserProcess = null;
55
+ try {
56
+ const browser = await resolveBrowserBinary();
57
+ await fs.mkdir(profileDir, { recursive: true });
58
+ browserProcess = execFile(browser, [
59
+ "--headless",
60
+ "--disable-gpu",
61
+ "--no-sandbox",
62
+ `--user-data-dir=${profileDir}`,
63
+ "--remote-debugging-port=0",
64
+ "about:blank",
65
+ ], { windowsHide: true, detached: process.platform !== "win32" });
66
+ const port = await waitForDevToolsPort(path.join(profileDir, "DevToolsActivePort"));
67
+ const wsUrl = await openCdpPage(port);
68
+ return await withTimeout(evaluatePageViaCdp(wsUrl, url, expression), 120_000, "PDF preparation timed out.");
69
+ } finally {
70
+ await stopBrowserProcess(browserProcess);
71
+ await removeTemporaryDirectory(tmpDir);
72
+ }
73
+ }
74
+
75
+ async function stopBrowserProcess(browserProcess) {
76
+ if (!browserProcess || !browserProcess.pid) return;
77
+ const exited = new Promise((resolve) => {
78
+ if (browserProcess.exitCode !== null || browserProcess.signalCode !== null) return resolve();
79
+ browserProcess.once("close", resolve);
80
+ });
81
+ try {
82
+ if (process.platform === "win32") {
83
+ await execFileAsync("taskkill", ["/pid", String(browserProcess.pid), "/t", "/f"]).catch(() => browserProcess.kill());
84
+ } else {
85
+ process.kill(-browserProcess.pid, "SIGTERM");
86
+ }
87
+ } catch {
88
+ try { browserProcess.kill(); } catch { /* already stopped */ }
89
+ }
90
+ if (await Promise.race([exited.then(() => true), delay(1000).then(() => false)])) return;
91
+ try {
92
+ if (process.platform !== "win32") process.kill(-browserProcess.pid, "SIGKILL");
93
+ else browserProcess.kill("SIGKILL");
94
+ } catch { /* already stopped */ }
95
+ await Promise.race([exited, delay(1000)]);
96
+ }
97
+
98
+ async function removeTemporaryDirectory(tmpDir) {
99
+ for (let attempt = 0; attempt < 8; attempt += 1) {
100
+ try {
101
+ await fs.rm(tmpDir, { recursive: true, force: true, maxRetries: 2, retryDelay: 100 });
102
+ return;
103
+ } catch (error) {
104
+ if (attempt === 7) {
105
+ console.warn("ViteBoard could not remove a temporary PDF export directory:", error.message);
106
+ return;
107
+ }
108
+ await delay(100 * (attempt + 1));
53
109
  }
54
- await fs.rm(tmpDir, { recursive: true, force: true });
55
110
  }
56
111
  }
57
112
 
@@ -205,6 +260,59 @@ async function printPdfViaCdp(wsUrl, url, pdf) {
205
260
  }
206
261
  }
207
262
 
263
+ async function evaluatePageViaCdp(wsUrl, url, expression) {
264
+ const ws = await connectWebSocket(wsUrl);
265
+ let nextId = 0;
266
+ const pending = new Map();
267
+ let loadResolve = null;
268
+ const loaded = new Promise((resolve) => { loadResolve = resolve; });
269
+
270
+ ws.onMessage = (text) => {
271
+ const message = JSON.parse(text);
272
+ if (message.method === "Page.loadEventFired") {
273
+ if (loadResolve) loadResolve();
274
+ loadResolve = null;
275
+ return;
276
+ }
277
+ if (!message.id) return;
278
+ const entry = pending.get(message.id);
279
+ if (!entry) return;
280
+ pending.delete(message.id);
281
+ if (message.error) entry.reject(new Error(message.error.message || "CDP error"));
282
+ else entry.resolve(message.result);
283
+ };
284
+
285
+ const send = (method, params = {}) => new Promise((resolve, reject) => {
286
+ const id = ++nextId;
287
+ pending.set(id, { resolve, reject });
288
+ ws.send(JSON.stringify({ id, method, params }));
289
+ });
290
+
291
+ try {
292
+ await send("Page.enable");
293
+ await send("Runtime.enable");
294
+ await send("Page.navigate", { url });
295
+ await loaded;
296
+ await send("Runtime.evaluate", {
297
+ expression: "document.fonts ? document.fonts.ready.then(() => true) : true",
298
+ awaitPromise: true,
299
+ returnByValue: true,
300
+ });
301
+ const result = await send("Runtime.evaluate", {
302
+ expression,
303
+ awaitPromise: true,
304
+ returnByValue: true,
305
+ });
306
+ if (result.exceptionDetails) {
307
+ throw new Error(result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Could not prepare ViteBoard PDF export.");
308
+ }
309
+ if (result.result?.type === "undefined") return null;
310
+ return result.result?.value ?? null;
311
+ } finally {
312
+ ws.close();
313
+ }
314
+ }
315
+
208
316
  function mmToInches(mm) {
209
317
  return Number(mm ?? 0) / 25.4;
210
318
  }
@@ -335,3 +443,15 @@ function decodeFrame(buffer) {
335
443
  function delay(ms) {
336
444
  return new Promise((resolve) => setTimeout(resolve, ms));
337
445
  }
446
+
447
+ async function withTimeout(promise, ms, message) {
448
+ let timer;
449
+ try {
450
+ return await Promise.race([
451
+ promise,
452
+ new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(message)), ms); }),
453
+ ]);
454
+ } finally {
455
+ clearTimeout(timer);
456
+ }
457
+ }
@@ -32,10 +32,11 @@ The easiest way to create an embed:
32
32
 
33
33
  1. Open the board in the editor
34
34
  2. Navigate to the region you want to embed
35
- 3. Click **Copy embed** in the toolbar
36
- 4. Paste the snippet into your markdown document
35
+ 3. Click **Select region to embed or export** in the toolbar and drag over the area
36
+ 4. Choose **Embed in a document** from the popup
37
+ 5. Paste the copied snippet into your markdown document
37
38
 
38
- The copied snippet includes the exact viewport coordinates for the current view.
39
+ The copied snippet includes the exact board coordinates for the selected region. Region selection also offers PDF, JPEG, and SVG export; SVG contains the rendered board as an image rather than editable vector shapes.
39
40
 
40
41
  ## How It Works
41
42
 
@@ -52,3 +52,11 @@ These files are small, diffable, and work naturally with git.
52
52
  ## Exiting the Board Editor
53
53
 
54
54
  Click the back arrow in the top-left corner to return to the documentation view. Your board is saved automatically.
55
+
56
+ ## Exporting
57
+
58
+ Open **Export / Import** in the board toolbar to export the current view or full board as PNG, or to export the board JSON or a JSON-plus-images bundle.
59
+
60
+ To export or embed a specific region, choose **Select region to embed or export** and drag over the board. Then choose **Embed in a document**, **Export as PDF**, **Export as JPEG**, or **Export as SVG**. SVG preserves the canvas appearance by embedding a PNG image; its shapes are not editable vector paths. PDF export uses a Chromium-compatible browser installed on the machine running ViteBoard.
61
+
62
+ From a Markdown document, use its **Export to PDF** action to create a PDF. Before exporting, choose the page format and any optional sections or publication details. ViteBoard returns the PDF for download and does not select a save path on your behalf.
@@ -54,6 +54,19 @@ Important discovery endpoints:
54
54
  - `GET /__api/project-tree?projectId={projectId}`
55
55
  - `GET /__api/tree?rootId={rootId}`
56
56
 
57
+ ### Live change notifications
58
+
59
+ The UI subscribes to `GET /__api/events` as a Server-Sent Events (SSE) stream. API clients can use the same endpoint with the browser `EventSource` API or another SSE client. It is a one-way notification channel; after receiving an event, fetch the authoritative workspace, tree, file, or board again rather than treating the event as the content itself. Native SSE clients reconnect automatically, and a newly opened stream should trigger a fresh snapshot fetch to cover any events missed while disconnected.
60
+
61
+ Mutations made through the HTTP API publish these named events after writing:
62
+
63
+ - `workspace-changed`: project or docs-root registration changes (`action`, optional `projectId`).
64
+ - `file-changed`: file/directory writes, renames, and deletes (`rootId`, `path`, `action`; rename includes `oldPath`).
65
+ - `board-changed`: board create, write, or delete (`rootId`, `boardId`, `action`).
66
+ - `asset-changed`: asset write or cleanup (`rootId`, optional `path`, `action`).
67
+
68
+ Events include `actorId` when the caller sends `X-ViteBoard-Actor-Id`; the UI uses this to ignore its own write notifications while keeping other tabs current. Notifications refresh views and flag revision conflicts—they do not merge live drafts or replace revision-checked writes. Direct filesystem changes that bypass the API do not emit events.
69
+
57
70
  The machine can navigate the same app surfaces a human uses:
58
71
 
59
72
  - workspace home: `#/`
@@ -144,6 +157,33 @@ automation/docs-refresh
144
157
 
145
158
  Machines should treat the whole branch as the capability context and use `llms.txt` as the first machine-facing file when it exists.
146
159
 
160
+ ### Agent Skills compatibility
161
+
162
+ Every capability is also exposed as a portable [Agent Skills](https://agentskills.io) skill. ViteBoard generates `SKILL.md` from the branch metadata, optional branch-local `SKILL.md`, `llms.txt` rules, and the supporting-file list; `index.md`, `llms.txt`, and the rest of the capability branch remain the source of truth.
163
+
164
+ Discover and inspect capabilities through the HTTP API:
165
+
166
+ - `GET /__api/capabilities` lists capabilities across registered docs roots. Add `?rootId={rootId}` or `?projectId={projectId}` to scope the result.
167
+ - `GET /__api/capability?rootId={rootId}&path={branchPath}` returns one capability, including its rules, generated `skillMarkdown`, and supporting-file URLs. `path` is the capability branch path, for example `capabilities/automation/docs-refresh`. `omittedFiles` lists items too large or unsupported for the skill bundle; they remain in the source branch.
168
+ - `GET /__api/skills/catalog/index.json` returns an OpenCode-compatible HTTP skill catalog. Skill files are served under `/__api/skills/catalog/{skillId}/{filePath}`; the catalog version changes when included skill/source content changes.
169
+
170
+ For OpenCode, add the live catalog URL to `skills` in the project's `opencode.json` or `opencode.jsonc` (merge this into existing config):
171
+
172
+ ```jsonc
173
+ {
174
+ "$schema": "https://opencode.ai/config.json",
175
+ "skills": ["http://localhost:5173/__api/skills/catalog/"]
176
+ }
177
+ ```
178
+
179
+ Use the exact ViteBoard origin instead of the example URL. This keeps OpenCode skills live; the catalog includes every registered docs root. For local Agent Skills consumers, save the API's `skillMarkdown` as `SKILL.md` and preserve the returned `catalogPath` files beside it:
180
+
181
+ - Codex: `.agents/skills/{skillId}/`
182
+ - Claude Code: `.claude/skills/{skillId}/`
183
+ - OpenCode: `.opencode/skills/{skillId}/` or `.agents/skills/{skillId}/`
184
+
185
+ The `SKILL.md` uses portable `name` and `description` frontmatter. Up to 200 text/support files (1 MiB each) and common image/PDF assets (5 MiB each) are bundled, with a 16 MiB aggregate content limit; see `omittedFiles` for anything excluded. Catalog metadata is cached briefly, while skill file contents are read on demand. Local copies are snapshots; fetch them again after the capability changes. ViteBoard does not write into agent configuration directories automatically.
186
+
147
187
  ## 3. Markdown board embeds
148
188
 
149
189
  Use fenced `board` blocks inside Markdown:
@@ -272,11 +312,14 @@ When deciding how much to change:
272
312
 
273
313
  ## 8. Concurrent edits
274
314
 
275
- ViteBoard keeps autosave, but it does not coordinate live writers.
315
+ ViteBoard keeps autosave, but it does not coordinate live writers with locks.
316
+
317
+ 1. Read `GET /__api/file?rootId={rootId}&path={path}` or `GET /__api/board?rootId={rootId}&id={boardId}` and retain its `revision`.
318
+ 2. Send that value as `expectedRevision` in the matching `PUT /__api/file` or `PUT /__api/board` request.
319
+ 3. For a create-only write, send `"expectedRevision": null`; this succeeds only if the target does not already exist.
320
+ 4. If the write returns `409`, reload the latest version and re-apply your change.
276
321
 
277
- 1. Read the current file or board revision before editing.
278
- 2. Write changes with the expected revision.
279
- 3. If the write returns a conflict, reload the latest version and re-apply your change.
322
+ The API serializes writes and deletes for each target, so simultaneous revision-checked requests cannot both pass against the same old state. Omitting `expectedRevision` remains available for compatibility but permits unconditional overwrites; machine clients should always send it.
280
323
 
281
324
  This keeps the runtime simple and fast. The UI does not lock files or boards while an agent writes.
282
325
 
@@ -296,3 +339,57 @@ Avoid these anti-patterns:
296
339
  - Embeds resolve by relative path.
297
340
  - Embed crops stay inside the intended frame.
298
341
  - The resulting layout is readable to a human.
342
+
343
+ ## 10. HTTP PDF export
344
+
345
+ Use `POST /__api/export/document/pdf` to export a document or a directory of Markdown documents. The server renders Markdown and board embeds with ViteBoard's renderer, then returns PDF bytes with `Content-Type: application/pdf` and a suggested filename in `Content-Disposition`. Save those bytes with the agent's own file tools. The server does not write to a path on the agent's machine.
346
+
347
+ Example request:
348
+
349
+ ```http
350
+ POST /__api/export/document/pdf
351
+ Content-Type: application/json
352
+
353
+ {
354
+ "rootId": "root-...",
355
+ "kind": "doc",
356
+ "path": "guide/getting-started.md",
357
+ "options": {
358
+ "paper": "A4",
359
+ "style": "clean",
360
+ "scale": "100",
361
+ "showHeaderChrome": false,
362
+ "showFooterChrome": false,
363
+ "includeCoverPage": false,
364
+ "includeTableOfContents": false,
365
+ "includeFilePaths": false,
366
+ "pageBreakBetweenDocs": false
367
+ }
368
+ }
369
+ ```
370
+
371
+ Set `kind` to `dir` and `path` to a docs-root-relative directory to export its Markdown files. Optional `selectedPaths` limits that set. The `options` object supports the same publication settings as the UI: `title`, `subtitle`, `style` (`clean` or `report`), `paper` (`A4` or `Letter`), `scale` (`90`, `100`, or `110`), `includeCoverPage`, `includeTableOfContents`, `includeFilePaths`, `pageBreakBetweenDocs`, `showHeaderChrome`, `showFooterChrome`, `author`, `organization`, `publicationDate`, `classification`, `legalNotice`, and `logoDataUrl`.
372
+
373
+ Optional features and metadata default to off or blank. Before every export, ask the human to choose the options. Explain each option and do not silently enable one. For agent workflows, see `/viteboard-skill.md`.
374
+
375
+ ## 11. HTTP board-region export
376
+
377
+ Use `POST /__api/export/board` to export a saved board as PDF, JPEG, or SVG bytes. Provide a docs `rootId`, `boardId` (board ID without `.board.json`), and `format`. An optional `region` is `[x, y, width, height]` in board world units; it must have positive width and height. If omitted, ViteBoard exports the visible board content with 48 world units of padding. `padding` can override that inferred-content padding; for an explicit region it defaults to zero.
378
+
379
+ ```http
380
+ POST /__api/export/board
381
+ Content-Type: application/json
382
+
383
+ {
384
+ "rootId": "root-...",
385
+ "boardId": "product-roadmap",
386
+ "format": "jpeg",
387
+ "region": [-400, 0, 1800, 900],
388
+ "scale": 2,
389
+ "quality": 0.92
390
+ }
391
+ ```
392
+
393
+ The optional `scale` sets output pixels per world unit (default `2`, accepted range `0.1`–`16`); dimensions are safely capped. `quality` sets JPEG quality from `0.1` to `1` (default `0.92`). For PDF, `paper` may be `A4` or `Letter` (default `A4`); page orientation follows the selected region. The endpoint returns bytes with the matching `Content-Type` and a suggested `Content-Disposition` filename. Save the response on the agent's machine.
394
+
395
+ SVG exports are standards-compliant SVG containers with the rendered board embedded as a PNG image. This preserves ViteBoard's canvas rendering accurately, but the shapes inside are not editable vector paths; use `scale` to choose raster resolution.
@@ -71,6 +71,7 @@ When helpful, tell the human which of these are relevant in the current workspac
71
71
  ## Core machine docs
72
72
 
73
73
  - [ViteBoard authoring guide](/viteboard-guide.md): Workspace model, routes, boards, embeds, capabilities, and revision-checked editing.
74
+ - [ViteBoard agent skill](/viteboard-skill.md): HTTP workflows, safe edits, and user-selected PDF export settings.
74
75
  - [Board schema v1](/board-v1.schema.json): JSON Schema for `.board.json` files.
75
76
  - [Example board](/viteboard-example.board.json): One complete valid ViteBoard board.
76
77
 
@@ -79,8 +80,11 @@ When helpful, tell the human which of these are relevant in the current workspac
79
80
  - Capabilities are managed directory branches under `capabilities/` inside a docs root.
80
81
  - Human entrypoint: `index.md`
81
82
  - Machine entrypoint: `llms.txt`
83
+ - Agent Skills entrypoint: generated standard `SKILL.md`, including branch rules from `llms.txt`.
82
84
  - The rest of the branch may contain markdown, scripts, helpers, assets, examples, and nested directories.
83
85
 
86
+ Discover skills with `GET /__api/capabilities`; read a capability's rules, generated skill, and file URLs with `GET /__api/capability?rootId={rootId}&path={branchPath}`. OpenCode can consume the live catalog at `/__api/skills/catalog/`. See `/viteboard-guide.md` for setup and portable local skill locations.
87
+
84
88
  ## App discovery
85
89
 
86
90
  - [Workspace listing](/__api/workspace): Registered projects and docs roots.
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: viteboard-http
3
+ description: Use ViteBoard's HTTP API to inspect and edit docs, boards, capabilities, assets, and export board or document PDFs.
4
+ ---
5
+
6
+ # ViteBoard HTTP Skill
7
+
8
+ Use the exact ViteBoard origin supplied by the user. Start at `/.well-known/viteboard.json` and `/llms.txt`. Read `/viteboard-guide.md` for the API and content rules. Do not assume a JavaScript SDK or direct server filesystem access.
9
+
10
+ ## Install this skill
11
+
12
+ When the user asks to install the ViteBoard skill, use the agent's local file tools to save this skill in that agent's native skill location. The skill URL is `/viteboard-skill.md`. Ask which agent environment to target if you cannot identify it. Do not send a filesystem path to ViteBoard; installation happens on the agent's machine. Do not install or overwrite a skill without the user's request.
13
+
14
+ ## Safe editing
15
+
16
+ 1. Discover the workspace and relevant docs root with `GET /__api/workspace` and `GET /__api/tree?rootId=...`.
17
+ 2. Read the current document or board and its revision before editing.
18
+ 3. Send the read revision as `expectedRevision` when updating. For create-only writes, send `expectedRevision: null`. If the API returns `409`, read the latest version and re-apply only the requested change.
19
+ 4. Ask before deleting, replacing, or making broad changes.
20
+
21
+ ## Capability skills
22
+
23
+ Discover workspace capabilities with `GET /__api/capabilities`. Read one capability with `GET /__api/capability?rootId={rootId}&path={branchPath}`; its response includes the current `llms.txt` rules, a portable `skillMarkdown`, and supporting-file URLs. Treat the capability branch as the source of truth and follow its rules. OpenCode can load the live catalog from `/__api/skills/catalog/`; Codex and Claude Code can use the same `SKILL.md` plus the returned support files in their native skill directories. Do not overwrite an existing agent config to install a skill.
24
+
25
+ ## PDF export
26
+
27
+ Before **every** PDF export, present the user with structured question blocks. Do not start the export until the user answers. Do not infer preferences from a previous export. Explain each option in the question text. Ask again on every export, even if the user exported the same document before.
28
+
29
+ Use separate blocks for these settings:
30
+
31
+ 1. **Optional sections** — multi-select, with nothing selected by default:
32
+ - Cover page: adds a title page.
33
+ - Table of contents: lists included documents; only applies to multiple documents.
34
+ - File paths: prints each document's path.
35
+ - Page break between documents: starts each document on a new page.
36
+ - Page header: adds publication metadata at the top of pages.
37
+ - Page footer: adds a label and page numbers.
38
+ 2. **Page format** — ask for paper (`A4` or `Letter`), style (`clean` or `report`), and scale (`90`, `100`, or `110`). Explain that these change page size, visual treatment, and content scale.
39
+ 3. **Publication details** — ask whether to override the generated title and whether to add a subtitle, author, organization, publication date, classification, legal notice, or logo. Leave each optional field blank unless the user supplies it.
40
+ 4. **Directory exports only** — ask whether to include all Markdown documents or select specific paths.
41
+ 5. **Save location** — if the user has not given an output path, ask where to save the PDF. This path is on the agent's machine.
42
+
43
+ For agents with a question-block tool, use these blocks in one interaction where possible:
44
+
45
+ ```text
46
+ Question: Which optional sections should I include? Select none to keep the PDF plain.
47
+ Type: multi-select; no options selected by default
48
+ Options: Cover page; Table of contents; File paths; Page breaks between documents; Page header; Page footer
49
+
50
+ Question: Which paper size should I use?
51
+ Type: single-select
52
+ Options: A4; Letter
53
+
54
+ Question: Which visual style should I use?
55
+ Type: single-select
56
+ Options: Clean; Report
57
+
58
+ Question: Which content scale should I use?
59
+ Type: single-select
60
+ Options: 90%; 100%; 110%
61
+
62
+ Question: Which publication details should I add? Leave values blank to omit them.
63
+ Type: text
64
+ Fields: Title override; Subtitle; Author; Organization; Publication date; Classification; Legal notice; Logo image
65
+
66
+ For a directory export, ask which Markdown paths to include. List the paths and explain that selecting none cancels the export.
67
+
68
+ If the task already names an output path, use it. Do not ask the user to repeat it.
69
+ ```
70
+
71
+ Use the agent environment's native structured-question feature. If it has no question-block feature, ask the same questions as a short grouped prompt. Do not silently fall back to non-default features.
72
+
73
+ Send the selected settings to `POST /__api/export/document/pdf`:
74
+
75
+ ```json
76
+ {
77
+ "rootId": "root-id",
78
+ "kind": "doc",
79
+ "path": "guide/getting-started.md",
80
+ "options": {
81
+ "paper": "A4",
82
+ "style": "clean",
83
+ "scale": "100",
84
+ "includeCoverPage": false,
85
+ "includeTableOfContents": false,
86
+ "includeFilePaths": false,
87
+ "pageBreakBetweenDocs": false,
88
+ "showHeaderChrome": false,
89
+ "showFooterChrome": false
90
+ }
91
+ }
92
+ ```
93
+
94
+ The endpoint returns PDF bytes. Save the response to the path the user requests with the agent's own file/download tools. The path is on the agent's machine, not the ViteBoard server. Confirm the saved file and report its path. Never send a local output path to ViteBoard; the server cannot safely write to the agent's filesystem.
95
+
96
+ If the user supplies a logo image, read it with the agent's local tools and send a PNG, JPEG, or WebP data URL in `options.logoDataUrl`. Do not send a server-local path.
97
+
98
+ ## Board-region export
99
+
100
+ For a board export, ask the user which region to export (a world-space `[x, y, width, height]` or the whole board), which format they want (PDF, JPEG, or SVG), and the output path if they have not already named one. Explain that SVG currently contains a raster image inside an SVG wrapper rather than editable vector shapes. For PDF, ask whether to use A4 or Letter. Scale defaults to 2 output pixels per board unit; JPEG quality defaults to 92%. Before every export, ask again rather than reusing prior preferences.
101
+
102
+ Send `POST /__api/export/board` with `rootId`, `boardId`, and `format`, plus `region` when the user specified one. Optional `scale`, `quality`, `paper`, and `padding` are documented in `/viteboard-guide.md`. The response is binary PDF, JPEG, or SVG data; save it to the agent's requested local path. Never send a local output path to ViteBoard.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ViteBoard",
3
- "appVersion": "0.2.0",
3
+ "appVersion": "0.2.1",
4
4
  "machineVersion": 1,
5
5
  "summary": "Local-first documentation and infinite-canvas boards stored as plain files.",
6
6
  "interactionModel": "human-directed-via-agent",
@@ -29,6 +29,8 @@
29
29
  "discovery": {
30
30
  "llms": "/llms.txt",
31
31
  "guide": "/viteboard-guide.md",
32
+ "skill": "/viteboard-skill.md",
33
+ "skillInstall": "/viteboard-skill.md",
32
34
  "boardSchema": "/board-v1.schema.json",
33
35
  "exampleBoard": "/viteboard-example.board.json"
34
36
  },
@@ -36,12 +38,34 @@
36
38
  "branchModel": "directory",
37
39
  "entrypoints": {
38
40
  "human": "index.md",
39
- "machine": "llms.txt"
41
+ "machine": "llms.txt",
42
+ "agentSkill": "generated SKILL.md"
40
43
  },
41
44
  "notes": [
42
45
  "A capability is a managed directory branch beneath capabilities/ inside a docs root.",
43
46
  "The branch, not a single file, is the capability unit.",
44
- "Machines should prefer llms.txt as the first machine-facing entrypoint when present."
47
+ "Machines should prefer llms.txt as the first machine-facing entrypoint when present.",
48
+ "Agent Skills are generated from branch metadata, llms.txt rules, and supporting files; those files remain the source of truth.",
49
+ "OpenCode can load the live HTTP catalog; other Agent Skills consumers use portable local SKILL.md files."
50
+ ]
51
+ },
52
+ "agentSkills": {
53
+ "format": "Agent Skills",
54
+ "api": {
55
+ "list": "/__api/capabilities",
56
+ "detail": "/__api/capability?rootId={rootId}&path={branchPath}",
57
+ "openCodeCatalog": "/__api/skills/catalog/",
58
+ "openCodeCatalogIndex": "/__api/skills/catalog/index.json"
59
+ },
60
+ "locations": {
61
+ "openCode": ".opencode/skills/{skillId}/ or .agents/skills/{skillId}/",
62
+ "codex": ".agents/skills/{skillId}/",
63
+ "claudeCode": ".claude/skills/{skillId}/"
64
+ },
65
+ "notes": [
66
+ "Generated SKILL.md uses portable name and description frontmatter.",
67
+ "Capability rules and included supporting files are available in the detail API and OpenCode catalog.",
68
+ "Local skill installations are snapshots; OpenCode's HTTP catalog stays live."
45
69
  ]
46
70
  },
47
71
  "routes": {
@@ -57,10 +81,16 @@
57
81
  },
58
82
  "api": {
59
83
  "workspace": "/__api/workspace",
84
+ "events": "/__api/events",
85
+ "capabilities": "/__api/capabilities",
86
+ "capability": "/__api/capability?rootId={rootId}&path={branchPath}",
87
+ "openCodeSkillCatalog": "/__api/skills/catalog/",
60
88
  "projectTree": "/__api/project-tree?projectId={projectId}",
61
89
  "tree": "/__api/tree?rootId={rootId}",
62
90
  "file": "/__api/file?rootId={rootId}&path={path}",
63
91
  "board": "/__api/board?rootId={rootId}&id={boardId}",
92
+ "exportDocumentPdf": "/__api/export/document/pdf",
93
+ "exportBoard": "/__api/export/board",
64
94
  "registerProject": "/__api/workspace/project",
65
95
  "ensureDocsRoot": "/__api/workspace/project/docs-root",
66
96
  "createDocsFolder": "/__api/workspace/project/create-docs-folder"
@@ -68,10 +98,10 @@
68
98
  "coordination": {
69
99
  "mode": "revision-checked",
70
100
  "notes": [
71
- "The app uses autosave plus revision checks instead of live locks.",
101
+ "SSE change notifications refresh connected views; they do not sync live drafts or merge edits.",
72
102
  "Always send the expected revision when writing a Markdown document or board.",
73
103
  "If a write conflicts, reload the latest version and re-apply the change.",
74
- "Do not add your own background coordination layer."
104
+ "Do not add a second polling or background coordination mechanism."
75
105
  ]
76
106
  }
77
107
  }