@webmcp-auto-ui/sdk 2.5.38 → 2.5.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/sdk",
3
- "version": "2.5.38",
3
+ "version": "2.5.39",
4
4
  "description": "Skills CRUD, HyperSkill format, Svelte 5 stores",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -4,6 +4,8 @@
4
4
  * present, snapshot fallback otherwise.
5
5
  */
6
6
 
7
+ import { pickFence } from './recipes/fence.js';
8
+
7
9
  export interface WidgetLineage {
8
10
  widgetType: string;
9
11
  widgetParams: Record<string, unknown>;
@@ -138,13 +140,64 @@ export function canvasToNotebookMarkdown(
138
140
  return fm.join('\n') + '\n' + body.join('\n').replace(/\n+$/, '') + '\n';
139
141
  }
140
142
 
143
+ export interface CanvasToNotebookData {
144
+ title?: string;
145
+ description?: string;
146
+ /** Active MCP servers (name, url) — to seed the notebook widget's data. */
147
+ servers?: Array<{ name: string; url: string }>;
148
+ /** Active bundled WebMCP server ids. */
149
+ webmcpServers?: string[];
150
+ /** Cells ready to feed into a NotebookState. */
151
+ cells: Array<{
152
+ id: string;
153
+ type: 'md' | 'sql' | 'js';
154
+ content: string;
155
+ varname?: string;
156
+ status?: 'idle';
157
+ }>;
158
+ }
159
+
160
+ /** Same logic as canvasToNotebookMarkdown but returns structured cells for direct widget mounting. */
161
+ export function canvasToNotebookCells(
162
+ blocks: CanvasBlock[],
163
+ getLineage: (blockId: string) => WidgetLineage | null,
164
+ opts: CanvasToNotebookOptions = {},
165
+ ): CanvasToNotebookData {
166
+ let counter = 0;
167
+ const cells: CanvasToNotebookData['cells'] = [];
168
+
169
+ for (const block of blocks) {
170
+ const raw = lineageToCells(block, getLineage(block.id));
171
+ for (const cell of raw) {
172
+ const entry: CanvasToNotebookData['cells'][number] = {
173
+ id: `c${counter++}`,
174
+ type: cell.kind,
175
+ content: cell.content,
176
+ status: 'idle',
177
+ };
178
+ if (cell.varname !== undefined) entry.varname = cell.varname;
179
+ cells.push(entry);
180
+ }
181
+ }
182
+
183
+ const out: CanvasToNotebookData = { cells };
184
+ if (opts.title) out.title = opts.title;
185
+ if (opts.description) out.description = opts.description;
186
+ if (opts.servers && opts.servers.length > 0) out.servers = opts.servers;
187
+ if (opts.webmcpServers && opts.webmcpServers.length > 0) out.webmcpServers = opts.webmcpServers;
188
+ return out;
189
+ }
190
+
141
191
  function renderCell(cell: NotebookCell): string {
142
192
  if (cell.kind === 'md') return cell.content;
143
193
  if (cell.kind === 'sql') {
144
194
  const meta = cell.varname ? `-- @meta {"varname": "${cell.varname}"}\n` : '';
145
- return '```sql\n' + meta + cell.content + '\n```';
195
+ const body = meta + cell.content;
196
+ const fence = pickFence(body);
197
+ return fence + 'sql\n' + body + '\n' + fence;
146
198
  }
147
- return '```js\n' + cell.content + '\n```';
199
+ const fence = pickFence(cell.content);
200
+ return fence + 'js\n' + cell.content + '\n' + fence;
148
201
  }
149
202
 
150
203
  function pickArrayParamKey(params: Record<string, unknown>): string | null {
package/src/index.ts CHANGED
@@ -91,7 +91,7 @@ export type { RemoteMcpRegistryEntry } from './remote-mcp-registry.js';
91
91
  // import { canvas } from '@webmcp-auto-ui/sdk/canvas'
92
92
 
93
93
  // Recipe runner — markdown-fence parser + JS/TS/SQL/etc executor over MCP
94
- export { parseBody, runCode, estimateTokens, safeStringify, findCodeParamName, buildToolArgs, parseWidgetDisplayCall } from './recipes/index.js';
94
+ export { parseBody, pickFence, runCode, estimateTokens, safeStringify, findCodeParamName, buildToolArgs, parseWidgetDisplayCall } from './recipes/index.js';
95
95
  export type { ParsedSegment, RunResult, RunLog, RunTab, RecipeData } from './recipes/index.js';
96
96
 
97
97
  // Short URL — domain-dependent compact token
@@ -104,10 +104,11 @@ export { extractSampleData } from './widget-sample-data.js';
104
104
  export { unwrap } from './unwrap.js';
105
105
 
106
106
  // Canvas → HyperSkill notebook serializer
107
- export { lineageToCells, canvasToNotebookMarkdown } from './canvas-to-notebook.js';
107
+ export { lineageToCells, canvasToNotebookMarkdown, canvasToNotebookCells } from './canvas-to-notebook.js';
108
108
  export type {
109
109
  WidgetLineage,
110
110
  CanvasToNotebookOptions,
111
+ CanvasToNotebookData,
111
112
  NotebookCell,
112
113
  CanvasBlock,
113
114
  } from './canvas-to-notebook.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Pick a backtick fence longer than any run of backticks inside `content`.
3
+ * CommonMark rule: a fenced code block opens with N≥3 backticks; the content
4
+ * must not contain a closing fence of the same length, and the closing fence
5
+ * must be at least as long as the opening. Choosing N = max(3, longestRun+1)
6
+ * makes round-trips safe even when the cell content embeds Markdown fences.
7
+ */
8
+ export function pickFence(content: string): string {
9
+ const runs = content.match(/`+/g) ?? [];
10
+ const longest = runs.reduce((m, r) => Math.max(m, r.length), 0);
11
+ return '`'.repeat(Math.max(3, longest + 1));
12
+ }
@@ -1,3 +1,4 @@
1
1
  export { parseBody } from './parse.js';
2
+ export { pickFence } from './fence.js';
2
3
  export { runCode, estimateTokens, safeStringify, findCodeParamName, buildToolArgs, parseWidgetDisplayCall } from './runner.js';
3
4
  export type { ParsedSegment, RunResult, RunLog, RunTab, RecipeData } from './types.js';
@@ -16,14 +16,17 @@ export function parseBody(body: string): ParsedSegment[] {
16
16
 
17
17
  const segments: ParsedSegment[] = [];
18
18
  // Match fenced code blocks with optional language tag.
19
- // Non-greedy body; allow any chars between the fences.
20
- const re = /```([a-zA-Z0-9_+-]*)\r?\n([\s\S]*?)```/g;
19
+ // Variable-length fence (≥3 backticks, CommonMark §4.5): the closing fence
20
+ // must use the same number of backticks as the opening — captured via \1.
21
+ // This allows cells whose content embeds ``` (e.g. widget('text', { content: "...```js..." }))
22
+ // to be encoded with a longer fence (4+ backticks) without premature closure.
23
+ const re = /(`{3,})([a-zA-Z0-9_+-]*)\r?\n([\s\S]*?)\r?\n\1[ \t]*(?=\r?\n|$)/g;
21
24
 
22
25
  let lastIndex = 0;
23
26
  let match: RegExpExecArray | null;
24
27
 
25
28
  while ((match = re.exec(body)) !== null) {
26
- const [full, langRaw, codeRaw] = match;
29
+ const [full, , langRaw, codeRaw] = match;
27
30
  const start = match.index;
28
31
 
29
32
  if (start > lastIndex) {