@webmcp-auto-ui/sdk 2.5.37 → 2.5.38

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.37",
3
+ "version": "2.5.38",
4
4
  "description": "Skills CRUD, HyperSkill format, Svelte 5 stores",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -8,14 +8,17 @@
8
8
  "main": "./src/index.ts",
9
9
  "exports": {
10
10
  ".": {
11
+ "types": "./dist/index.d.ts",
11
12
  "svelte": "./src/index.ts",
12
13
  "import": "./src/index.ts"
13
14
  },
14
15
  "./canvas": {
16
+ "types": "./dist/canvas.d.ts",
15
17
  "svelte": "./src/canvas.ts",
16
18
  "import": "./src/canvas.ts"
17
19
  },
18
20
  "./canvas-vanilla": {
21
+ "types": "./dist/canvas-vanilla.d.ts",
19
22
  "import": "./src/canvas-vanilla.ts"
20
23
  }
21
24
  },
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Canvas → HyperSkill notebook serializer.
3
+ * Each block becomes one or more SQL/JS/MD cells. Dynamic when lineage is
4
+ * present, snapshot fallback otherwise.
5
+ */
6
+
7
+ export interface WidgetLineage {
8
+ widgetType: string;
9
+ widgetParams: Record<string, unknown>;
10
+ toolCalls: Array<{
11
+ serverName: string;
12
+ toolName: string;
13
+ args: Record<string, unknown>;
14
+ resultPreview?: string;
15
+ }>;
16
+ originRecipe?: string;
17
+ }
18
+
19
+ export interface CanvasToNotebookOptions {
20
+ title?: string;
21
+ description?: string;
22
+ /** Active MCP servers (name, url) — copied as `servers:` in frontmatter. */
23
+ servers?: Array<{ name: string; url: string }>;
24
+ /** Active bundled WebMCP server ids (e.g. ['autoui', 'deckgl']). */
25
+ webmcpServers?: string[];
26
+ }
27
+
28
+ export interface NotebookCell {
29
+ kind: 'sql' | 'js' | 'md';
30
+ content: string;
31
+ varname?: string;
32
+ }
33
+
34
+ export interface CanvasBlock {
35
+ id: string;
36
+ type: string;
37
+ data: Record<string, unknown>;
38
+ }
39
+
40
+ const SQL_TOOL_RE = /(^|[._-])query_sql$|^(query|run|execute)(_sql)?$/i;
41
+ const ARRAY_PARAM_KEYS = ['rows', 'data', 'items', 'cards'] as const;
42
+
43
+ /** Convert a single block + lineage into one or more notebook cells. */
44
+ export function lineageToCells(
45
+ block: CanvasBlock,
46
+ lineage: WidgetLineage | null,
47
+ ): NotebookCell[] {
48
+ const cells: NotebookCell[] = [];
49
+
50
+ if (lineage?.originRecipe) {
51
+ cells.push({ kind: 'md', content: `> Generated from recipe: \`${lineage.originRecipe}\`` });
52
+ }
53
+
54
+ // Snapshot fallback — no lineage.
55
+ if (!lineage || !lineage.toolCalls || lineage.toolCalls.length === 0) {
56
+ cells.push({
57
+ kind: 'js',
58
+ content: `await widget('${block.type}', ${JSON.stringify(block.data, null, 2)});`,
59
+ });
60
+ return cells;
61
+ }
62
+
63
+ // Single SQL tool call → SQL cell + JS widget cell.
64
+ if (lineage.toolCalls.length === 1 && SQL_TOOL_RE.test(lineage.toolCalls[0]!.toolName)) {
65
+ const tc = lineage.toolCalls[0]!;
66
+ const sql = typeof tc.args['sql'] === 'string' ? (tc.args['sql'] as string) : '';
67
+ const varname = 'rows';
68
+ cells.push({ kind: 'sql', content: sql, varname });
69
+
70
+ const arrayKey = pickArrayParamKey(lineage.widgetParams);
71
+ let widgetCall: string;
72
+ if (arrayKey) {
73
+ const others = stripKeys(lineage.widgetParams, [arrayKey]);
74
+ const merged = { ...others, [arrayKey]: '__ROWS__' };
75
+ const json = JSON.stringify(merged, null, 2).replace('"__ROWS__"', varname);
76
+ widgetCall = `await widget('${block.type}', ${json});`;
77
+ } else {
78
+ widgetCall = `// TODO: map rows -> widget params\nawait widget('${block.type}', ${JSON.stringify(lineage.widgetParams, null, 2)});`;
79
+ }
80
+ cells.push({ kind: 'js', content: widgetCall });
81
+ return cells;
82
+ }
83
+
84
+ // General case — replay tool calls in order.
85
+ const lines: string[] = [];
86
+ lines.push('const unwrap = (r) => (r?.data ?? r?.results ?? r?.items ?? r ?? []);');
87
+ lineage.toolCalls.forEach((tc, i) => {
88
+ lines.push(
89
+ `const r${i} = await call('${tc.serverName}', '${tc.toolName}', ${JSON.stringify(tc.args, null, 2)});`,
90
+ );
91
+ });
92
+
93
+ const lastIdx = lineage.toolCalls.length - 1;
94
+ const arrayKey = pickArrayParamKey(lineage.widgetParams);
95
+ if (arrayKey) {
96
+ lines.push(`const ${arrayKey} = unwrap(r${lastIdx});`);
97
+ const others = stripKeys(lineage.widgetParams, [arrayKey]);
98
+ const merged = { ...others, [arrayKey]: '__ITEMS__' };
99
+ const json = JSON.stringify(merged, null, 2).replace('"__ITEMS__"', arrayKey);
100
+ lines.push(`await widget('${block.type}', ${json});`);
101
+ } else {
102
+ lines.push(`await widget('${block.type}', ${JSON.stringify(lineage.widgetParams, null, 2)});`);
103
+ }
104
+
105
+ cells.push({ kind: 'js', content: lines.join('\n') });
106
+ return cells;
107
+ }
108
+
109
+ /** Build the full notebook markdown from a snapshot of the canvas. */
110
+ export function canvasToNotebookMarkdown(
111
+ blocks: CanvasBlock[],
112
+ getLineage: (blockId: string) => WidgetLineage | null,
113
+ opts: CanvasToNotebookOptions = {},
114
+ ): string {
115
+ const fm: string[] = ['---'];
116
+ fm.push(`title: ${yamlQuote(opts.title ?? 'Canvas snapshot')}`);
117
+ if (opts.description) fm.push(`description: ${yamlQuote(opts.description)}`);
118
+ if (opts.servers && opts.servers.length > 0) {
119
+ fm.push('servers:');
120
+ for (const s of opts.servers) {
121
+ fm.push(` - name: ${yamlQuote(s.name)}`);
122
+ fm.push(` url: ${yamlQuote(s.url)}`);
123
+ }
124
+ }
125
+ if (opts.webmcpServers && opts.webmcpServers.length > 0) {
126
+ fm.push(`webmcp_servers: [${opts.webmcpServers.map((s) => yamlQuote(s)).join(', ')}]`);
127
+ }
128
+ fm.push('---', '');
129
+
130
+ const body: string[] = [];
131
+ for (const block of blocks) {
132
+ const cells = lineageToCells(block, getLineage(block.id));
133
+ for (const cell of cells) {
134
+ body.push(renderCell(cell), '');
135
+ }
136
+ }
137
+
138
+ return fm.join('\n') + '\n' + body.join('\n').replace(/\n+$/, '') + '\n';
139
+ }
140
+
141
+ function renderCell(cell: NotebookCell): string {
142
+ if (cell.kind === 'md') return cell.content;
143
+ if (cell.kind === 'sql') {
144
+ const meta = cell.varname ? `-- @meta {"varname": "${cell.varname}"}\n` : '';
145
+ return '```sql\n' + meta + cell.content + '\n```';
146
+ }
147
+ return '```js\n' + cell.content + '\n```';
148
+ }
149
+
150
+ function pickArrayParamKey(params: Record<string, unknown>): string | null {
151
+ for (const k of ARRAY_PARAM_KEYS) {
152
+ if (k in params && Array.isArray(params[k])) return k;
153
+ }
154
+ return null;
155
+ }
156
+
157
+ function stripKeys(obj: Record<string, unknown>, keys: string[]): Record<string, unknown> {
158
+ const out: Record<string, unknown> = {};
159
+ for (const [k, v] of Object.entries(obj)) {
160
+ if (!keys.includes(k)) out[k] = v;
161
+ }
162
+ return out;
163
+ }
164
+
165
+ function yamlQuote(s: string): string {
166
+ return '"' + s.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
167
+ }
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 } from './recipes/index.js';
94
+ export { parseBody, 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
@@ -99,3 +99,15 @@ export { buildShortUrl, getShortToken } from './short-url.js';
99
99
 
100
100
  // Widget sample-data extractor — parses the `## Example` block of a recipe
101
101
  export { extractSampleData } from './widget-sample-data.js';
102
+
103
+ // MCP response normalizer — flattens common envelope shapes to array
104
+ export { unwrap } from './unwrap.js';
105
+
106
+ // Canvas → HyperSkill notebook serializer
107
+ export { lineageToCells, canvasToNotebookMarkdown } from './canvas-to-notebook.js';
108
+ export type {
109
+ WidgetLineage,
110
+ CanvasToNotebookOptions,
111
+ NotebookCell,
112
+ CanvasBlock,
113
+ } from './canvas-to-notebook.js';
@@ -1,3 +1,3 @@
1
1
  export { parseBody } from './parse.js';
2
- export { runCode, estimateTokens, safeStringify, findCodeParamName, buildToolArgs } from './runner.js';
2
+ export { runCode, estimateTokens, safeStringify, findCodeParamName, buildToolArgs, parseWidgetDisplayCall } from './runner.js';
3
3
  export type { ParsedSegment, RunResult, RunLog, RunTab, RecipeData } from './types.js';
@@ -290,6 +290,7 @@ function createCanvasVanilla() {
290
290
 
291
291
  if (srv.enabled && !srv.connected && !liveUrls.has(srv.url)) {
292
292
  inFlight.add(name);
293
+ setDataServerMeta(name, { connecting: true, error: undefined });
293
294
  try {
294
295
  const opts = srv.headers ? { headers: srv.headers } : undefined;
295
296
  const { name: actualName, tools } = await multiClient.addServer(srv.url, opts);
package/src/unwrap.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Normalize MCP tool responses to an array. Handles common envelopes.
3
+ * Tries: direct array → .data → .results → .rows → .entries → .items → .feed.entry → .properties.parameter → []
4
+ */
5
+ export function unwrap(r: unknown): any[] {
6
+ if (Array.isArray(r)) return r;
7
+ if (r == null || typeof r !== 'object') return [];
8
+ const o = r as any;
9
+ return o.data ?? o.results ?? o.rows ?? o.entries ?? o.items
10
+ ?? o.feed?.entry ?? o.properties?.parameter ?? [];
11
+ }