@vpxa/aikit 0.1.41 → 0.1.42
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 +1 -1
- package/packages/server/dist/tools/present/helpers.js +4 -1
- package/packages/server/dist/tools/present/html.js +4 -4
- package/packages/server/dist/tools/present/tool.js +23 -2
- package/packages/server/dist/tools/present-blocks.js +1 -1
- package/scaffold/general/skills/present/SKILL.md +5 -3
package/package.json
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
-
function e(t){return t==null?`null`:Array.isArray(t)?`[${t.map(e).join(`, `)}]`:String(t)}function t(e){return e.replace(/[^a-zA-Z0-9_]/g,`_`)}function n(e){if(typeof e!=`object`||!e||!(`type`in e))return!1;let t=e;return`value`in t||typeof t.text==`string`||Array.isArray(t.headers)&&Array.isArray(t.rows)||typeof t.code==`string`||Array.isArray(t.items)||!!t.entries&&typeof t.entries==`object`&&!Array.isArray(t.entries)||!!t.columns&&typeof t.columns==`object`||`chartType`in t||Array.isArray(t.data)}function r(e){if(Array.isArray(e)){let t=e.filter(e=>typeof e==`object`&&!!e),n=t.map(e=>String(e.title??``)),r=Math.max(0,...t.map(e=>Array.isArray(e.items)?e.items.length:0));return{headers:n,rows:Array.from({length:r},(e,n)=>t.map(e=>Array.isArray(e.items)?e.items[n]??``:``))}}if(!e||typeof e!=`object`)return null;let t=e,n=Object.keys(t),r=Math.max(0,...n.map(e=>Array.isArray(t[e])?t[e].length:0));return{headers:n,rows:Array.from({length:r},(e,r)=>n.map(e=>Array.isArray(t[e])?t[e][r]??``:``))}}function i(e){let t=e,n=e;if(`value`in n||(typeof t.text==`string`?n={...n,value:t.text}:typeof t.code==`string`?n={...n,value:t.code}:Array.isArray(t.headers)&&Array.isArray(t.rows)?n={...n,value:{headers:t.headers,rows:t.rows}}:Array.isArray(t.items)?n={...n,value:{items:t.items}}:(`chartType`in t||Array.isArray(t.data))&&(n={...n,value:{chartType:t.chartType,data:t.data,xKey:t.xKey??`label`,yKeys:t.yKeys??[`value`]}})),n.type===`chart`){let e=n.value;if(e&&typeof e==`object`&&!Array.isArray(e)){let t=e,r=t.data&&typeof t.data==`object`&&!Array.isArray(t.data)?t.data:null,i=Array.isArray(t.labels)?t.labels:Array.isArray(r?.labels)?r.labels:null,a=Array.isArray(t.datasets)?t.datasets:Array.isArray(r?.datasets)?r.datasets:null
|
|
1
|
+
function e(t){return t==null?`null`:Array.isArray(t)?`[${t.map(e).join(`, `)}]`:String(t)}function t(e){return e.replace(/[^a-zA-Z0-9_]/g,`_`)}function n(e){if(typeof e!=`object`||!e||!(`type`in e))return!1;let t=e;return`value`in t||typeof t.text==`string`||Array.isArray(t.headers)&&Array.isArray(t.rows)||typeof t.code==`string`||Array.isArray(t.items)||!!t.entries&&typeof t.entries==`object`&&!Array.isArray(t.entries)||!!t.columns&&typeof t.columns==`object`||`chartType`in t||Array.isArray(t.data)}function r(e){if(Array.isArray(e)){let t=e.filter(e=>typeof e==`object`&&!!e),n=t.map(e=>String(e.title??``)),r=Math.max(0,...t.map(e=>Array.isArray(e.items)?e.items.length:0));return{headers:n,rows:Array.from({length:r},(e,n)=>t.map(e=>Array.isArray(e.items)?e.items[n]??``:``))}}if(!e||typeof e!=`object`)return null;let t=e,n=Object.keys(t),r=Math.max(0,...n.map(e=>Array.isArray(t[e])?t[e].length:0));return{headers:n,rows:Array.from({length:r},(e,r)=>n.map(e=>Array.isArray(t[e])?t[e][r]??``:``))}}function i(e){let t=e,n=e;if(`value`in n||(typeof t.text==`string`?n={...n,value:t.text}:typeof t.code==`string`?n={...n,value:t.code}:Array.isArray(t.headers)&&Array.isArray(t.rows)?n={...n,value:{headers:t.headers,rows:t.rows}}:Array.isArray(t.items)?n={...n,value:{items:t.items}}:(`chartType`in t||Array.isArray(t.data))&&(n={...n,value:{chartType:t.chartType,data:t.data,xKey:t.xKey??`label`,yKeys:t.yKeys??[`value`]}})),n.type===`chart`){let e=n.value;if(e&&typeof e==`object`&&!Array.isArray(e)){let t=e,r=t.data&&typeof t.data==`object`&&!Array.isArray(t.data)?t.data:null,i=Array.isArray(t.labels)?t.labels:Array.isArray(r?.labels)?r.labels:null,a=Array.isArray(t.datasets)?t.datasets:Array.isArray(r?.datasets)?r.datasets:null,o=typeof t.type==`string`?t.type:typeof t.chartType==`string`?t.chartType:null;if(o&&i&&a&&!Array.isArray(t.data)){let e=a.map((e,t)=>String(e.label??`series${t+1}`)),t=i.map((e,t)=>{let n={label:String(e)};for(let[e,r]of a.entries()){let i=r,a=String(i.label??`series${e+1}`);n[a]=i.data?.[t]??0}return n});n={...n,value:{chartType:String(o),data:t,xKey:`label`,yKeys:e}}}}}let i=n;if(n.type===`text`||n.type===`paragraph`)return{...n,type:`markdown`};if(n.type===`heading`)return{...n,type:`markdown`,value:`## ${String(i.value??``)}`};if(n.type===`kv`){let e=t.entries&&typeof t.entries==`object`&&!Array.isArray(t.entries)?t.entries:i.value&&typeof i.value==`object`&&!Array.isArray(i.value)?i.value:null;if(e)return{...n,type:`table`,value:{headers:[`Key`,`Value`],rows:Object.entries(e).map(([e,t])=>[e,t])}}}if(n.type===`comparison`){let e=r(t.columns??(i.value&&typeof i.value==`object`?i.value.columns:void 0));if(e)return{...n,type:`table`,value:e};let a=i.value;if(a&&typeof a==`object`&&!Array.isArray(a)){let e=a;if(Array.isArray(e.headers)&&Array.isArray(e.rows))return{...n,type:`table`}}}if(n.type===`timeline`){let e=i.value;if(Array.isArray(e)){let t=e.map(e=>{if(e&&typeof e==`object`){let t=e;return[String(t.status??t.state??``),String(t.time??t.date??t.timestamp??``),String(t.label??t.description??t.title??t.text??``)]}return[``,``,String(e)]});return{...n,type:`table`,value:{headers:[`Status`,`Time`,`Description`],rows:t}}}}return n}function a(e){let t=e.value;if(e.type===`separator`)return{type:`markdown`,title:e.title,value:`---`};if(e.type===`checklist`){if(!t||typeof t!=`object`||!Array.isArray(t.items))return e;let n=t.items.map(e=>{if(!e||typeof e!=`object`)return null;let t=e;if(typeof t.label!=`string`)return null;let n=t.checked===!0?`x`:` `,r=typeof t.note==`string`&&t.note.length>0?` — ${t.note}`:``;return`- [${n}] ${t.label}${r}`}).filter(e=>typeof e==`string`);return n.length>0?{type:`markdown`,title:e.title,value:n.join(`
|
|
2
|
+
`)}:e}if(e.type===`status-board`){if(!t||typeof t!=`object`||!Array.isArray(t.items))return e;let n={success:`✅`,warning:`⚠️`,error:`❌`,info:`ℹ️`,pending:`⏳`},r=t.items.map(e=>{if(!e||typeof e!=`object`)return null;let t=e;return typeof t.label!=`string`||typeof t.status!=`string`?null:[n[t.status.toLowerCase()]??`●`,t.label,typeof t.detail==`string`?t.detail:``]}).filter(e=>Array.isArray(e));return r.length>0?{type:`table`,title:e.title,value:{headers:[`Status`,`Service`,`Detail`],rows:r}}:e}if(e.type===`prompt`){if(!t||typeof t!=`object`)return e;let n=t;if(typeof n.question!=`string`)return e;let r=[`**${n.question}**`];return typeof n.context==`string`&&n.context.length>0&&r.push(n.context),typeof n.placeholder==`string`&&n.placeholder.length>0&&r.push(`*${n.placeholder}*`),{type:`markdown`,title:e.title,value:r.join(`
|
|
3
|
+
|
|
4
|
+
`)}}if(e.type===`progress`){if(!t||typeof t!=`object`||!Array.isArray(t.items))return e;let n=t.items.map(e=>{if(!e||typeof e!=`object`)return null;let t=e;if(typeof t.label!=`string`||typeof t.value!=`number`)return null;let n=typeof t.max==`number`?t.max:100,r=Number.isFinite(n)&&n>0?n:100,i=Math.round(t.value/r*100);return[t.label,`${i}%`,`${t.value} / ${r}`]}).filter(e=>Array.isArray(e));return n.length>0?{type:`table`,title:e.title,value:{headers:[`Item`,`Progress`,`Value`],rows:n}}:e}if(e.type===`timeline`){if(!t||typeof t!=`object`||!Array.isArray(t.items))return e;let n={done:`✅`,active:`🔵`,pending:`⏳`,error:`❌`},r=t.items.map(e=>{if(!e||typeof e!=`object`)return null;let t=e;return typeof t.title==`string`?[n[typeof t.status==`string`?t.status.toLowerCase():``]??`●`,typeof t.phase==`string`&&t.phase.length>0?t.phase:t.title,typeof t.description==`string`?t.description:``]:null}).filter(e=>Array.isArray(e));return r.length>0?{type:`table`,title:e.title,value:{headers:[`Status`,`Phase`,`Description`],rows:r}}:e}return e}function o(e){return Array.isArray(e)?e.map(e=>n(e)?a(i(e)):e):e}export{e as formatValue,n as isTypedBlock,i as normalizeBlock,o as normalizeContentForApp,t as sanitizeId};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{escHtml as e}from"../present-utils.js";import{renderChecklistHtml as t,renderComparisonHtml as n,renderProgressHtml as r,renderPromptHtml as i,renderStatusBoardHtml as a,renderTimelineHtml as o}from"../present-blocks.js";import{renderChartAsHtml as s}from"../present-charts.js";import{formatValue as c,isTypedBlock as l,normalizeBlock as u,sanitizeId as d}from"./helpers.js";import{marked as f}from"marked";f.setOptions({async:!1,gfm:!0,breaks:!0}),f.use({renderer:{html(t){return e(t.text)}}});function p(e){return e.replace(/<table\b/g,`<div class="table-wrap"><table`).replace(/<\/table>/g,`</table></div>`)}function m(t,n){let r=[];if(t&&r.push(`<h1>${e(t)}</h1>`),typeof n==`string`)r.push(`<div class="md-content">${p(f.parse(n))}</div>`);else if(Array.isArray(n))if(n.length===0)r.push(`<p><em>empty</em></p>`);else if(l(n[0]))for(let e of n)r.push(h(u(e)));else typeof n[0]==`object`&&n[0]!==null?r.push(g(n)):r.push(`<ul>${n.map(t=>`<li>${e(String(t))}</li>`).join(``)}</ul>`);else if(typeof n==`object`&&n){let t=n;if(Array.isArray(t.blocks)&&t.blocks.length>0)for(let n of t.blocks)l(n)?r.push(h(u(n))):r.push(`<p>${e(String(n))}</p>`);else Array.isArray(t.metrics)?r.push(_(t.metrics)):Array.isArray(t.nodes)&&Array.isArray(t.edges)?(r.push(`<pre class="mermaid">${e(
|
|
2
|
-
`)}function h(c){let l=[];switch(c.title&&l.push(`<h2>${e(c.title)}</h2>`),c.type){case`markdown`:l.push(`<div class="md-content">${p(f.parse(String(c.value??``)))}</div>`);break;case`code`:l.push(`<pre><code>${e(String(c.value??``))}</code></pre>`);break;case`mermaid`:l.push(`<pre class="mermaid">${e(String(c.value??``))}</pre>`);break;case`table`:if(Array.isArray(c.value)){let e=c.value;if(e.length>0&&Array.isArray(e[0])){let t=e,n=t[0].map(String),r=t.slice(1).map(e=>Object.fromEntries(n.map((t,n)=>[t,e[n]])));l.push(g(r))}else l.push(g(e))}else if(c.value&&typeof c.value==`object`&&`headers`in c.value&&`rows`in c.value){let{headers:e,rows:t}=c.value,n=t.map(t=>Object.fromEntries(e.map((e,n)=>[e,t[n]])));l.push(g(n))}break;case`metrics`:{let e;Array.isArray(c.value)?e=c.value:c.value&&typeof c.value==`object`&&(e=Object.entries(c.value).map(([e,t])=>({label:e,value:String(t)}))),e&&l.push(_(e));break}case`cards`:Array.isArray(c.value)&&l.push(v(c.value));break;case`tree`:c.value&&typeof c.value==`object`&&l.push(y(c.value));break;case`graph`:c.value&&typeof c.value==`object`&&l.push(`<pre class="mermaid">${e(
|
|
1
|
+
import{escHtml as e}from"../present-utils.js";import{renderChecklistHtml as t,renderComparisonHtml as n,renderProgressHtml as r,renderPromptHtml as i,renderStatusBoardHtml as a,renderTimelineHtml as o}from"../present-blocks.js";import{renderChartAsHtml as s}from"../present-charts.js";import{formatValue as c,isTypedBlock as l,normalizeBlock as u,sanitizeId as d}from"./helpers.js";import{marked as f}from"marked";f.setOptions({async:!1,gfm:!0,breaks:!0}),f.use({renderer:{html(t){return e(t.text)}}});function p(e){return e.replace(/<table\b/g,`<div class="table-wrap"><table`).replace(/<\/table>/g,`</table></div>`)}function m(t,n){let r=[];if(t&&r.push(`<h1>${e(t)}</h1>`),typeof n==`string`)r.push(`<div class="md-content">${p(f.parse(n))}</div>`);else if(Array.isArray(n))if(n.length===0)r.push(`<p><em>empty</em></p>`);else if(l(n[0]))for(let e of n)r.push(h(u(e)));else typeof n[0]==`object`&&n[0]!==null?r.push(g(n)):r.push(`<ul>${n.map(t=>`<li>${e(String(t))}</li>`).join(``)}</ul>`);else if(typeof n==`object`&&n){let t=n;if(Array.isArray(t.blocks)&&t.blocks.length>0)for(let n of t.blocks)l(n)?r.push(h(u(n))):r.push(`<p>${e(String(n))}</p>`);else Array.isArray(t.metrics)?r.push(_(t.metrics)):Array.isArray(t.nodes)&&Array.isArray(t.edges)?(r.push(`<pre class="mermaid">${e(x(t))}</pre>`),r.push(`<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"><\/script>`),r.push(`<script>mermaid.run();<\/script>`)):r.push(y(t))}else r.push(`<p>${e(String(n))}</p>`);return r.join(`
|
|
2
|
+
`)}function h(c){let l=[];switch(c.title&&l.push(`<h2>${e(c.title)}</h2>`),c.type){case`markdown`:l.push(`<div class="md-content">${p(f.parse(String(c.value??``)))}</div>`);break;case`code`:l.push(`<pre><code>${e(String(c.value??``))}</code></pre>`);break;case`mermaid`:l.push(`<pre class="mermaid">${e(String(c.value??``))}</pre>`);break;case`table`:if(Array.isArray(c.value)){let e=c.value;if(e.length>0&&Array.isArray(e[0])){let t=e,n=t[0].map(String),r=t.slice(1).map(e=>Object.fromEntries(n.map((t,n)=>[t,e[n]])));l.push(g(r))}else l.push(g(e))}else if(c.value&&typeof c.value==`object`&&`headers`in c.value&&`rows`in c.value){let{headers:e,rows:t}=c.value,n=t.map(t=>Object.fromEntries(e.map((e,n)=>[e,t[n]])));l.push(g(n))}break;case`metrics`:{let e;Array.isArray(c.value)?e=c.value:c.value&&typeof c.value==`object`&&(e=Object.entries(c.value).map(([e,t])=>({label:e,value:String(t)}))),e&&l.push(_(e));break}case`cards`:Array.isArray(c.value)&&l.push(v(c.value));break;case`tree`:c.value&&typeof c.value==`object`&&l.push(y(c.value));break;case`graph`:c.value&&typeof c.value==`object`&&l.push(`<pre class="mermaid">${e(x(c.value))}</pre>`);break;case`chart`:l.push(s(c));break;case`timeline`:{let e=c.value;Array.isArray(e)&&(e={items:e.map(e=>({title:String(e.event??e.title??``),phase:e.date==null?e.phase==null?void 0:String(e.phase):String(e.date),description:e.description==null?void 0:String(e.description),status:e.status??`done`}))}),e&&l.push(o(e));break}case`checklist`:c.value&&l.push(t(c.value));break;case`comparison`:c.value&&l.push(n(c.value));break;case`status-board`:c.value&&l.push(a(c.value));break;case`prompt`:c.value&&l.push(i(c.value));break;case`progress`:c.value&&l.push(r(c.value));break;case`text`:l.push(`<div class="md-content">${f.parse(String(c.value??``))}</div>`);break;case`heading`:{let t=Math.min(Math.max(Number(c.level)||2,1),6);l.push(`<h${t}>${e(String(c.value??``))}</h${t}>`);break}case`paragraph`:l.push(`<p>${e(String(c.value??``))}</p>`);break;case`separator`:l.push(`<hr class="separator">`);break;case`actions`:{let t=(Array.isArray(c.value)?c.value:[]).map(t=>{if(t.type===`select`){let n=Array.isArray(t.options)?t.options.map(t=>`<option value="${e(String(t.value??t.label??``))}">${e(String(t.label??t.value??``))}</option>`).join(``):``;return`<select class="action-select"><option value="">${e(String(t.label??`Select...`))}</option>${n}</select>`}return`<button class="action-btn action-${String(t.variant??`secondary`)}">${e(String(t.label??``))}</button>`}).join(``);l.push(`<div class="action-bar">${t}</div>`);break}default:l.push(`<pre>${e(JSON.stringify(c.value,null,2))}</pre>`)}return l.join(`
|
|
3
3
|
`)}function g(t){if(t.length===0)return`<p><em>empty table</em></p>`;let n=Object.keys(t[0]);return`<div class="table-wrap"><table><thead><tr>${n.map(t=>`<th>${e(t)}</th>`).join(``)}</tr></thead><tbody>${t.map(t=>`<tr>${n.map(n=>`<td>${e(String(t[n]??``))}</td>`).join(``)}</tr>`).join(`
|
|
4
|
-
`)}</tbody></table></div>`}function _(t){return`<div class="metric-grid">${t.map(t=>`<div class="metric"><div class="metric-value">${e(String(t.value))}</div><div class="metric-label">${e(t.label)}</div></div>`).join(``)}</div>`}function v(t){return`<div class="card-grid">${t.map(t=>{let n=[`<div class="card">`];return t.title&&n.push(`<div class="card-title">${e(String(t.title))}</div>`),(t.body||t.description)&&n.push(`<div class="card-body">${e(String(t.body??t.description))}</div>`),(t.badge||t.status)&&n.push(`<span class="badge">${e(String(t.badge??t.status))}</span>`),n.push(`</div>`),n.join(``)}).join(``)}</div>`}function y(t){let n=[];for(let[r,i]of Object.entries(t))typeof i==`object`&&i&&!Array.isArray(i)?n.push(`<div class="tree-node"><span class="tree-key">${e(r)}:</span><div class="tree-children">${y(i)}</div></div>`):n.push(`<div class="tree-node"><span class="tree-key">${e(r)}:</span> ${e(c(i))}</div>`);return n.join(``)}function b(e){let t=[`graph LR`];for(let n of e.nodes){let e=d(String(n.id??n.name??``)),r=String(n.label??n.name??e);t.push(` ${e}["${r}"]`)}for(let n of e.edges){let e=d(String(n.source??n.from??``)),r=d(String(n.target??n.to??``)),i=n.label?`|${String(n.label)}|`:``;t.push(` ${e} -->${i} ${r}`)}return t.join(`
|
|
5
|
-
`)}export{g as arrayToHtmlTable,v as cardsToHtml,m as contentToHtml,
|
|
4
|
+
`)}</tbody></table></div>`}function _(t){return`<div class="metric-grid">${t.map(t=>`<div class="metric"><div class="metric-value">${e(String(t.value))}</div><div class="metric-label">${e(t.label)}</div></div>`).join(``)}</div>`}function v(t){return`<div class="card-grid">${t.map(t=>{let n=[`<div class="card">`];return t.title&&n.push(`<div class="card-title">${e(String(t.title))}</div>`),(t.body||t.description)&&n.push(`<div class="card-body">${e(String(t.body??t.description))}</div>`),(t.badge||t.status)&&n.push(`<span class="badge">${e(String(t.badge??t.status))}</span>`),n.push(`</div>`),n.join(``)}).join(``)}</div>`}function y(t){if(`name`in t&&`children`in t&&Array.isArray(t.children))return b(t);if(`name`in t&&!(`children`in t))return`<div class="tree-node"><span class="tree-key">${e(String(t.name))}</span></div>`;let n=[];for(let[r,i]of Object.entries(t))typeof i==`object`&&i&&!Array.isArray(i)?n.push(`<div class="tree-node"><span class="tree-key">${e(r)}:</span><div class="tree-children">${y(i)}</div></div>`):n.push(`<div class="tree-node"><span class="tree-key">${e(r)}:</span> ${e(c(i))}</div>`);return n.join(``)}function b(t){let n=e(String(t.name));return!t.children||t.children.length===0?`<div class="tree-node"><span class="tree-key">${n}</span></div>`:`<div class="tree-node"><span class="tree-key">${n}</span><div class="tree-children">${t.children.map(t=>typeof t==`object`&&t?b(t):`<div class="tree-node"><span class="tree-key">${e(String(t))}</span></div>`).join(``)}</div></div>`}function x(e){let t=[`graph LR`];for(let n of e.nodes){let e=d(String(n.id??n.name??``)),r=String(n.label??n.name??e);t.push(` ${e}["${r}"]`)}for(let n of e.edges){let e=d(String(n.source??n.from??``)),r=d(String(n.target??n.to??``)),i=n.label?`|${String(n.label)}|`:``;t.push(` ${e} -->${i} ${r}`)}return t.join(`
|
|
5
|
+
`)}export{g as arrayToHtmlTable,v as cardsToHtml,m as contentToHtml,x as graphToMermaidRaw,_ as metricsToHtml,y as objectToHtmlTree,h as renderBlockAsHtml};
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import{getToolMeta as e}from"../../tool-metadata.js";import{normalizeContentForApp as t}from"./helpers.js";import{buildBrowserHtml as n}from"./browser.js";import{buildMarkdown as r}from"./markdown.js";import{readFileSync as i}from"node:fs";import{dirname as a,join as o}from"node:path";import{fileURLToPath as s}from"node:url";import{z as c}from"zod";import{RESOURCE_MIME_TYPE as l,registerAppResource as u,registerAppTool as d}from"@modelcontextprotocol/ext-apps/server";import{exec as f}from"node:child_process";import{createServer as p}from"node:http";import{createUIResource as m}from"@mcp-ui/server";const h=import.meta.dirname??a(s(import.meta.url)),g=`ui://aikit/present.html`,_=c.object({type:c.enum([`button`,`select`]).describe(`Action type`),id:c.string().describe(`Unique action identifier`),label:c.string().describe(`Display label`),variant:c.enum([`primary`,`danger`,`default`]).optional().describe(`Button style variant`),options:c.array(c.union([c.string(),c.object({label:c.string(),value:c.string()})])).optional().describe(`Select options (for type=select)`)}),v={format:c.enum([`html`,`browser`]).default(`html`).describe(`Output format.
|
|
2
2
|
- "html" (default): Rich markdown in chat + embedded UIResource for MCP-UI hosts. Actions shown as numbered choices.
|
|
3
|
-
- "browser": Rich markdown in chat + opens beautiful themed dashboard in the browser. When actions are provided, the browser shows interactive buttons and the tool blocks until user clicks, returning their selection.`),title:c.string().optional().describe(`Optional heading`),content:c.any().describe(`Content to present. Accepts
|
|
3
|
+
- "browser": Rich markdown in chat + opens beautiful themed dashboard in the browser. When actions are provided, the browser shows interactive buttons and the tool blocks until user clicks, returning their selection.`),title:c.string().optional().describe(`Optional heading`),content:c.any().describe(`Content to present. Accepts these shapes:
|
|
4
|
+
• markdown string — rendered as rich text
|
|
5
|
+
• array of objects — auto-rendered as table
|
|
6
|
+
• { nodes, edges } — rendered as mermaid graph
|
|
7
|
+
• typed blocks array [{ type, title?, value }] — the primary format:
|
|
8
|
+
- type:"markdown" → value: string (markdown text)
|
|
9
|
+
- type:"code" → value: string, language?: string
|
|
10
|
+
- type:"mermaid" → value: string (mermaid syntax)
|
|
11
|
+
- type:"table" → value: Record[] | {headers:string[], rows:any[][]}
|
|
12
|
+
- type:"metrics" → value: [{label,value,trend?,status?}]
|
|
13
|
+
- type:"chart" → value: {chartType:"line"|"area"|"bar"|"horizontal-bar"|"pie"|"donut"|"sparkline"|"heatmap", data:Record[], xKey:string, yKeys:string[]}
|
|
14
|
+
- type:"cards" → value: [{title,body?,badge?,status?}]
|
|
15
|
+
- type:"tree" → value: {name,children?:[...]} | object
|
|
16
|
+
- type:"graph" → value: {nodes:[{id,label}], edges:[{from,to}]}
|
|
17
|
+
- type:"timeline" → value: [{title,description?,timestamp?,status?}]
|
|
18
|
+
- type:"checklist" → value: [{label,checked:boolean}]
|
|
19
|
+
- type:"status-board" → value: [{category,items:[{label,status}]}]
|
|
20
|
+
- type:"comparison" → value: [{title,items:string[]}] (columns)
|
|
21
|
+
- type:"progress" → value: {label,value:number,max?:number}
|
|
22
|
+
• any JSON — rendered as collapsible tree
|
|
23
|
+
IMPORTANT: For charts, use the ChartValue format above. Do NOT use Chart.js format ({labels, datasets}) — it will be auto-converted but the native format is preferred.`),actions:c.array(_).optional().describe(`Interactive actions (buttons/selects). In html mode, shown as numbered choices. In browser mode, rendered as clickable buttons and the tool blocks until user clicks.`),template:c.enum([`auto`,`list-sort`,`data-table`,`picker`,`flame-graph`,`form`,`timeline`,`kanban`,`tree`,`diff-view`,`dashboard`]).optional().describe(`UI template for interactive display in MCP Apps hosts.
|
|
4
24
|
- auto (default): detect from content shape
|
|
5
25
|
- list-sort: drag-drop reorderable list — content: { items: [{id, label}] }
|
|
6
26
|
- data-table: filterable sortable table — content: { columns: [{key, label}], rows: [{...}], stats?: [{label, value}] }
|
|
@@ -15,4 +35,5 @@ import{getToolMeta as e}from"../../tool-metadata.js";import{normalizeContentForA
|
|
|
15
35
|
- "html" (default): Rich markdown in chat + embedded UIResource. Use for display-only content (tables, charts, reports, status boards) where no user interaction is needed.
|
|
16
36
|
- "browser": Serves a themed dashboard on a local URL. Use ONLY when you need user interaction back (confirmations, selections, form input). The tool blocks until user clicks an action button, then returns their selection.
|
|
17
37
|
FORMAT RULE: If no user interaction is needed → use "html". If you need user input back → use "browser".
|
|
18
|
-
|
|
38
|
+
CONTENT GUIDE: Pass typed blocks [{ type, title?, value }] for structured content. See the \`content\` parameter description for all supported block types and their value shapes. For charts: use {chartType, data:Record[], xKey, yKeys} — NOT Chart.js format.
|
|
39
|
+
BROWSER WORKFLOW: After calling present with format "browser", you MUST extract the URL from the response and call openBrowserPage({ url }) tool - PlayWright MCP to open it in VS Code Simple Browser. A system browser fallback also opens automatically, but always call openBrowserPage tool - PlayWright MCP yourself.`,o=async({format:e,title:t,content:r,actions:i,template:a})=>(e??`html`)===`browser`||Array.isArray(i)&&i.length>0?await T(t,r,i,n,a):E(t,r,i,a);if(i){b||=(u(t,`AI Kit Present App`,g,{description:`Rich interactive content viewer for AI Kit tools`},async()=>({contents:[{uri:g,mimeType:l,text:i}]})),!0),d(t,`present`,{title:r.title,description:a,annotations:r.annotations,inputSchema:v,_meta:{ui:{resourceUri:g}}},o);return}t.tool(`present`,a,v,r.annotations,o)}async function T(e,t,i,a,o){let s=r(e,t,{compactTables:!0}),c=n(e,t,i,o),l=m({uri:`ui://aikit/present-browser.html`,content:{type:`rawHtml`,htmlString:c},encoding:`text`,adapters:{mcpApps:{enabled:!0}}}),u,d,h=Array.isArray(i)?i:[],g=``,_;try{x&&=(x.close(),null),h.length>0&&(u=new Promise(e=>{d=e}));let e=!1;g=await new Promise((t,n)=>{let r=p((t,n)=>{if(e||(e=!0,_&&clearTimeout(_)),t.method===`POST`&&t.url===`/callback`){let e=``;t.on(`data`,t=>{e+=t.toString()}),t.on(`end`,()=>{n.writeHead(200,{"Content-Type":`application/json`,"Access-Control-Allow-Origin":`*`}),n.end(`{"ok":true}`);try{let t=JSON.parse(e);d?.(t)}catch{}});return}if(t.method===`OPTIONS`){n.writeHead(204,{"Access-Control-Allow-Origin":`*`,"Access-Control-Allow-Methods":`POST`,"Access-Control-Allow-Headers":`Content-Type`}),n.end();return}n.writeHead(200,{"Content-Type":`text/html; charset=utf-8`}),n.end(c)});r.listen(0,`127.0.0.1`,()=>{let i=r.address();if(typeof i==`object`&&i){x=r;let n=`http://127.0.0.1:${i.port}`;_=setTimeout(()=>{if(!e)try{f(process.platform===`win32`?`start "" "${n}"`:process.platform===`darwin`?`open "${n}"`:`xdg-open "${n}"`)}catch{}},8e3),_.unref(),t(n)}else n(Error(`Failed to start present server`))}),setTimeout(()=>{r.close(),x===r&&(x=null)},300*1e3).unref()})}catch{}let v=g?`${s}\n\n---\n🌐 **Dashboard opened in browser:** ${g}\n\n**IMPORTANT:** You MUST now open this URL using the \`openBrowserPage\` tool - PlayWright MCP. If that tool is unavailable, open the URL in the system browser as a fallback. If neither works, ask the user to open the URL manually.`:s;if(h.length>0&&a?.available&&u)try{let e=await Promise.race([u,new Promise((e,t)=>setTimeout(()=>t(Error(`timeout`)),300*1e3))]);return{content:[{type:`text`,text:`${v}\n\n✅ **Selected:** ${e.actionId} = \`${e.value}\``},l]}}catch{return{content:[{type:`text`,text:`${v}\n\n⚠️ *No selection received (timed out).*`},l]}}return{content:[{type:`text`,text:v},l]}}function E(e,r,i,a){let o=Array.isArray(i)?i:[],s=e?`📊 **${e}**`:`📊 **Dashboard**`,c=m({uri:`ui://aikit/present-static.html`,content:{type:`rawHtml`,htmlString:n(e,r,i,a)},encoding:`text`,adapters:{mcpApps:{enabled:!0}}});return{content:[{type:`text`,text:s},c],structuredContent:{title:e,content:t(r),actions:o}}}export{T as formatAsBrowser,E as formatAsHtml,C as getPresentHtml,w as registerPresentTool,S as resolvePresentHtml};
|
|
@@ -9,7 +9,7 @@ import{escHtml as e}from"./present-utils.js";const t=e;function n(e){let n=e;ret
|
|
|
9
9
|
<div class="checklist-label">${t(e.label)}</div>
|
|
10
10
|
${i}
|
|
11
11
|
</div>
|
|
12
|
-
</div>`}).join(``)}</div>`}function i(e){let n=e;if(!n?.columns||!Array.isArray(n.columns)||n.columns.length===0)return``;let r=n.columns.length,
|
|
12
|
+
</div>`}).join(``)}</div>`}function i(e){let n=e;if(n?.left&&n?.right)return i({columns:[{title:n.left.label??`Left`,items:n.left.items??[]},{title:n.right.label??`Right`,items:n.right.items??[]}]});if(!n?.columns||!Array.isArray(n.columns)||n.columns.length===0)return``;let r=n.columns.length,a=Math.max(...n.columns.map(e=>e.items?.length??0));return`<div class="comparison-grid" style="grid-template-columns:repeat(${r},1fr)">${n.columns.map(e=>{let n=`<div class="comparison-header">${t(e.title)}</div>`,r=[];for(let n=0;n<a;n++){let i=e.items?.[n]??``;r.push(`<div class="comparison-item">${t(i)}</div>`)}return`<div class="comparison-col">${n}${r.join(``)}</div>`}).join(``)}</div>`}function a(e){let n=e;return!n?.items||!Array.isArray(n.items)?``:`<div class="status-board">${n.items.map(e=>{let n=e.detail?`<span class="status-detail">${t(e.detail)}</span>`:``;return`<div class="status-row">
|
|
13
13
|
<div class="status-indicator ${e.status??`pending`}"></div>
|
|
14
14
|
<span class="status-label">${t(e.label)}</span>
|
|
15
15
|
${n}
|
|
@@ -34,17 +34,17 @@ When using `format: "browser"`, the tool starts a local HTTP server and returns
|
|
|
34
34
|
### Steps:
|
|
35
35
|
1. Call `present` with `format: "browser"` and `actions` — it returns text containing `🌐 **Dashboard opened in browser:** http://127.0.0.1:{port}`
|
|
36
36
|
2. **Extract the URL from the response**
|
|
37
|
-
3. **Call `openBrowserPage({ url: "http://127.0.0.1:{port}" })` to open it in VS Code's Simple Browser**
|
|
37
|
+
3. **Call `openBrowserPage({ url: "http://127.0.0.1:{port}" })` - PlayWright MCP to open it in VS Code's Simple Browser**
|
|
38
38
|
|
|
39
39
|
```
|
|
40
40
|
// Step 1: Call present
|
|
41
41
|
result = present({ format: "browser", title: "...", content: [...], actions: [...] })
|
|
42
42
|
|
|
43
43
|
// Step 2: MUST open in VS Code Simple Browser
|
|
44
|
-
openBrowserPage({ url: "http://127.0.0.1:{port}" })
|
|
44
|
+
openBrowserPage({ url: "http://127.0.0.1:{port}" }) // PlayWright MCP API to open URL in VS Code's Simple Browser
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
> **Fallback**: The server also auto-opens the system browser as a safety net. But you should ALWAYS call `openBrowserPage` yourself so the user sees it inside VS Code.
|
|
47
|
+
> **Fallback**: The server also auto-opens the system browser as a safety net. But you should ALWAYS call `openBrowserPage` - PlayWright MCP yourself so the user sees it inside VS Code.
|
|
48
48
|
|
|
49
49
|
**Note:** The HTTP server auto-closes after 5 minutes. Open the page promptly after receiving the URL.
|
|
50
50
|
|
|
@@ -87,6 +87,8 @@ Content is an array of **typed blocks**. Each block has `{ type, title?, value }
|
|
|
87
87
|
|
|
88
88
|
**Chart types:** `line`, `area`, `bar`, `horizontal-bar`, `pie`, `donut`, `sparkline`, `heatmap`
|
|
89
89
|
|
|
90
|
+
> **⚠️ Chart Format**: Always use the ChartValue format shown above (`chartType`, `data`, `xKey`, `yKeys`). Do **NOT** use Chart.js format (`labels`, `datasets`) — while it will be auto-converted, the native ChartValue format is required for reliable rendering.
|
|
91
|
+
|
|
90
92
|
### New Block Types
|
|
91
93
|
|
|
92
94
|
| Type | Value Shape | Renders As |
|