@zibby/skills 0.2.14 → 0.2.16
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/dist/artifact.d.ts +23 -0
- package/dist/artifact.js +51 -11
- package/dist/chartRender.d.ts +8 -0
- package/dist/chartRender.js +14 -5
- package/dist/index.d.ts +3 -1
- package/dist/index.js +205 -140
- package/dist/package.json +1 -1
- package/dist/report.d.ts +8 -8
- package/dist/reportCheck.d.ts +138 -0
- package/dist/reportCheck.js +17 -0
- package/package.json +1 -1
package/dist/artifact.d.ts
CHANGED
|
@@ -1 +1,24 @@
|
|
|
1
|
+
/** Test hook — the budget is process-lifetime state, so a suite must clear it. */
|
|
2
|
+
export declare function __resetPublishBudget(): void;
|
|
3
|
+
/**
|
|
4
|
+
* Compare the source we sent with the source read back. Returns null when they
|
|
5
|
+
* are identical, otherwise a descriptor naming WHAT differs — length delta, the
|
|
6
|
+
* first differing character offset, and a short excerpt of each side, in the same
|
|
7
|
+
* "name the defect and where" spirit as the validation gate's defects.
|
|
8
|
+
*
|
|
9
|
+
* `kind` discriminates the three shapes a real failure takes, because the fix
|
|
10
|
+
* differs: `truncated` (stored is a strict prefix — a cut-off write/transfer),
|
|
11
|
+
* `longer` (stored has extra content appended), `diverged` (they differ mid-way —
|
|
12
|
+
* a mangled encoding, or a stale/overwritten object).
|
|
13
|
+
* Exported for direct unit testing; not part of the tool surface.
|
|
14
|
+
*/
|
|
15
|
+
export declare function __compareStoredSource(sent: any, stored: any): {
|
|
16
|
+
kind: string;
|
|
17
|
+
sentChars: any;
|
|
18
|
+
storedChars: any;
|
|
19
|
+
delta: number;
|
|
20
|
+
firstDiffAt: number;
|
|
21
|
+
sentExcerpt: string;
|
|
22
|
+
storedExcerpt: string;
|
|
23
|
+
};
|
|
1
24
|
export declare const artifactSkill: any;
|
package/dist/artifact.js
CHANGED
|
@@ -1,10 +1,48 @@
|
|
|
1
|
-
import{SKILL_META as b}from"@zibby/skill-ids";import{existsSync as m,readFileSync as O}from"node:fs";import{homedir as g}from"node:os";import{join as T,dirname as k,resolve as S}from"node:path";import{fileURLToPath as N}from"node:url";function v(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let i=k(N(import.meta.url)),t=S(i,"..","bin","mcp-skill.mjs");return m(t)?t:null}function l(){if(process.env.PROJECT_API_TOKEN)return process.env.PROJECT_API_TOKEN;if(process.env.ZIBBY_USER_TOKEN)return process.env.ZIBBY_USER_TOKEN;try{let i=T(g(),".zibby","config.json");return m(i)&&JSON.parse(O(i,"utf-8")).sessionToken||null}catch{return null}}function p(){return process.env.ZIBBY_ACCOUNT_API_URL?process.env.ZIBBY_ACCOUNT_API_URL.replace(/\/$/,""):(process.env.ZIBBY_ENV||"prod")==="local"?"http://localhost:3001":process.env.ZIBBY_PROD_ACCOUNT_API_URL||"https://api-prod.zibby.app"}function _(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function y(i){return`${_()}:artifact:${i}`}async function u(i){let t=l();if(!t)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${p()}/credits/artifacts`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify(i)});if(!e.ok){let r=await e.text().catch(()=>"");throw new Error(`artifact write failed (${e.status}): ${r.slice(0,300)}`)}return e.json()}async function I(i){let t=l();if(!t)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${p()}/credits/artifacts/${encodeURIComponent(i)}`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});if(!e.ok){let r=await e.text().catch(()=>"");throw new Error(`artifact get failed (${e.status}): ${r.slice(0,300)}`)}return e.json()}async function f(i,t){let e=l();if(!e)return;let r=await fetch(`${p()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({op:"store",scope:y(i),content:JSON.stringify(t)})});if(!r.ok){let n=await r.text().catch(()=>"");throw new Error(`artifact index write failed (${r.status}): ${n.slice(0,200)}`)}}async function E(i){let t=l();if(!t)return null;let e=await fetch(`${p()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify({op:"recall",scope:y(i)})});if(!e.ok)return null;let r=await e.json().catch(()=>null);if(!r?.found||!r?.memory?.content)return null;try{return JSON.parse(r.memory.content)}catch{return null}}function h(i){return typeof i?.html=="string"&&i.html.length>0?{format:"html",content:i.html}:typeof i?.markdown=="string"&&i.markdown.length>0?{format:"markdown",content:i.markdown}:null}var B={id:"artifact",callsBackend:!0,meta:b.artifact,serverName:"artifact",allowedTools:["mcp__artifact__*"],description:"Artifacts \u2014 publish a self-contained, shareable HTML/Markdown page to Zibby and get back a URL; the index of what you published is your memory.",promptFragment:`## Artifacts (publish a shareable page, remember what you made)
|
|
1
|
+
import{SKILL_META as Je}from"@zibby/skill-ids";import{existsSync as ae,readFileSync as Fe}from"node:fs";import{homedir as We}from"node:os";import{join as Ze,dirname as Ye,resolve as Ge}from"node:path";import{fileURLToPath as Ke}from"node:url";import{existsSync as at}from"node:fs";import{dirname as ot,resolve as st}from"node:path";import{fileURLToPath as ct}from"node:url";var w={ZERO_WIDTH:"zero-width-bar",ROW_COUNT:"data-row-count",ROW_MISSING:"data-row-missing",BAR_COUNT:"data-bar-count",BAR_PROPORTION:"bar-proportion",VALUE_MISMATCH:"data-value-mismatch",PLACEHOLDER_TEXT:"placeholder-text",OVERFLOW:"overflow-clipped"},ce=5,de=20,ue=.04,he=3,pe=.5;function k(r,n){if(n==null||n<0)return null;let e=1;for(let t=0;t<n&&t<r.length;t+=1)r.charCodeAt(t)===10&&(e+=1);return e}function v(r,n,e){let t=n[e.code]||0;t>=ce||r.length>=de||(n[e.code]=t+1,r.push(e))}var fe={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:" ",ndash:"\u2013",mdash:"\u2014",hellip:"\u2026"};function Z(r){return String(r).replace(/&(#x?[0-9a-f]+|[a-z]+);/gi,(n,e)=>{if(e[0]==="#"){let a=e[1]==="x"||e[1]==="X"?parseInt(e.slice(2),16):parseInt(e.slice(1),10);return Number.isFinite(a)?String.fromCodePoint(a):n}let t=fe[String(e).toLowerCase()];return t??n})}function T(r){return Z(String(r??"")).replace(/\s+/g," ").trim().toLowerCase()}function F(r){return Math.round(r*10)/10}var ge=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),D=new Set(["script","style","textarea"]),me=new Set(["span","a","em","strong","b","i","u","s","small","code","label","abbr","cite","q","sub","sup","mark","time","var","kbd","samp","font","tt"]),be=/^<\s*(\/?)\s*([a-zA-Z][a-zA-Z0-9:_.-]*)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>/;function ye(r){let n={},e=/([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'=<>`]+))|([a-zA-Z_:][-a-zA-Z0-9_:.]*)/g,t;for(;(t=e.exec(r))!==null;)t[1]?n[t[1].toLowerCase()]=t[3]!=null?t[3]:t[4]!=null?t[4]:t[5]==null?"":t[5]:t[6]&&(n[t[6].toLowerCase()]="");return n}function we(r){let n={tag:"#root",attrs:{},children:[],parent:null,start:0},e=n,t=[n],a=0,s=(i,o)=>{if(o<=i)return;let c=r.slice(i,o);c.trim()&&e.children.push({tag:"#text",attrs:{},children:[],parent:e,start:i,value:c})};for(;a<r.length;){let i=r.indexOf("<",a);if(i<0){s(a,r.length);break}if(s(a,i),r.startsWith("<!--",i)){let d=r.indexOf("-->",i);a=d<0?r.length:d+3;continue}if(r.startsWith("<!",i)||r.startsWith("<?",i)){let d=r.indexOf(">",i);a=d<0?r.length:d+1;continue}let o=be.exec(r.slice(i));if(!o){a=i+1;continue}let c=o[0],l=o[2].toLowerCase();if(o[1]){for(let d=t.length-1;d>0;d-=1)if(t[d].tag===l){t.length=d,e=t[d-1];break}a=i+c.length;continue}let h={tag:l,attrs:ye(o[3]||""),children:[],parent:e,start:i};if(e.children.push(h),a=i+c.length,!(o[4]||ge.has(l))){if(D.has(l)){let d=r.slice(a),u=new RegExp(`</\\s*${l}\\s*>`,"i").exec(d);h.raw=u?d.slice(0,u.index):d,a+=u?u.index+u[0].length:d.length;continue}t.push(h),e=h}}return n}function S(r,n){for(let e of r.children)e.tag!=="#text"&&(n(e),S(e,n))}function $(r){let n=[],e=t=>{for(let a of t.children)a.tag==="#text"?n.push(a.value||""):(n.push(" "),e(a),n.push(" "))};return e(r),T(n.join(""))}function ke(r){let n=[],e=t=>{for(let a of t.children)a.tag==="#text"?n.push({value:a.value||"",start:a.start,parent:t}):a.tag!=="pre"&&a.tag!=="code"&&e(a)};return e(r),n}function _(r){let n={};for(let e of String(r||"").split(";")){let t=e.indexOf(":");t<0||(n[e.slice(0,t).trim().toLowerCase()]=e.slice(t+1).trim().replace(/\s*!important$/i,""))}return n}function ve(r){let n=[];return S(r,e=>{if(e.tag!=="style"||!e.raw)return;let t=e.raw.replace(/@[a-z-]+[^{]*\{/gi," "),a=/([^{}]+)\{([^{}]*)\}/g,s;for(;(s=a.exec(t))!==null;){let i=_(s[2]);for(let o of s[1].split(",")){let c=o.trim();if(!c||/[\s>+~:[\]*]/.test(c))continue;let l=[],h=null,d=null,u=!0,p=/([.#]?)([A-Za-z_][-\w]*)/g,f;for(;(f=p.exec(c))!==null;)if(f[1]===".")l.push(f[2].toLowerCase());else if(f[1]==="#")h=f[2];else if(f.index===0)d=f[2].toLowerCase();else{u=!1;break}u&&(d||h||l.length)&&n.push({classes:l,id:h,tag:d,decls:i})}}}),n}function Y(r){return String(r.attrs.class||"").split(/\s+/).filter(Boolean).map(n=>n.toLowerCase())}function Ne(r,n,e){if(r.tag&&r.tag!==n.tag||r.id&&r.id!==n.attrs.id)return!1;for(let t of r.classes)if(e.indexOf(t)<0)return!1;return!0}function N(r,n,e){let t=_(r.attrs.style)[n];if(t!=null&&t!=="")return t.toLowerCase();let a=Y(r),s=null;for(let i of e){if(!Ne(i,r,a))continue;let o=i.decls[n];o!=null&&o!==""&&(s=o.toLowerCase())}return s}function W(r,n){let e=N(r,"display",n);return e||(me.has(r.tag)?"inline":"block")}function M(r){if(!r)return null;let n=/^(-?\d+(?:\.\d+)?)px$/.exec(String(r).trim());return n?Number(n[1]):null}function G(r){if(!r)return null;let n=/^(-?\d+(?:\.\d+)?)%$/.exec(String(r).trim());return n?Number(n[1]):null}function P(r){return r!=null&&typeof r=="object"&&!Array.isArray(r)}function Se(r){if(!r.length||!P(r[0]))return r.map(t=>t==null?"":String(t));let n=null,e=0;for(let t of Object.keys(r[0])){let a=r.map(i=>P(i)&&i[t]!=null?String(i[t]):"");if(a.some(i=>i.length<2))continue;let s=new Set(a.map(T)).size;if(s>e&&(e=s,n=a),s===r.length)break}return n||r.map((t,a)=>String(P(t)?Object.values(t)[0]==null?a:Object.values(t)[0]:t))}function Oe(r){let n=[],e=[],t=new Set,a=(i,o)=>{if(!(o.length<2)){if(o.every(P)){n.push({path:i,records:o,identities:Se(o)});let c=Object.keys(o[0]),l=c.filter(d=>o.every(u=>typeof u[d]=="number"&&Number.isFinite(u[d]))),h=c.filter(d=>o.every(u=>typeof u[d]=="string"));l.length===1&&h.length>=1&&e.push({path:i,labels:o.map(d=>String(d[h[0]])),values:o.map(d=>Number(d[l[0]]))});return}o.every(c=>typeof c=="number"&&Number.isFinite(c))&&e.push({path:i,labels:o.map((c,l)=>`#${l}`),values:o.slice()})}},s=(i,o,c)=>{if(!(c>6||i==null||typeof i!="object"||t.has(i))){if(t.add(i),Array.isArray(i)){a(o,i);for(let l=0;l<i.length&&l<200;l+=1)s(i[l],`${o}[${l}]`,c+1);return}for(let l of Object.keys(i))s(i[l],o?`${o}.${l}`:l,c+1)}};return s(r,"",0),{collections:n,series:e}}function Te(r){let n=String(r||"").match(/[MmLlHhVvZz]|-?\d*\.?\d+(?:e-?\d+)?/gi);if(!n)return null;let e=0,t=0,a="",s=1/0,i=-1/0,o=1/0,c=-1/0,l=()=>{!Number.isFinite(e)||!Number.isFinite(t)||(e<s&&(s=e),e>i&&(i=e),t<o&&(o=t),t>c&&(c=t))},h=0,d=0;for(;h<n.length&&d++<2e4;){let u=n[h];if(/[A-Za-z]/.test(u)&&(a=u,h+=1,a==="Z"||a==="z"))continue;if(h>=n.length)break;let p=()=>{let f=Number(n[h]);return h+=1,f};switch(a){case"M":e=p(),t=p(),l(),a="L";break;case"m":e+=p(),t+=p(),l(),a="l";break;case"L":e=p(),t=p(),l();break;case"l":e+=p(),t+=p(),l();break;case"H":e=p(),l();break;case"h":e+=p(),l();break;case"V":t=p(),l();break;case"v":t+=p(),l();break;default:h+=1;break}}return!Number.isFinite(s)||!Number.isFinite(o)?null:{w:i-s,h:c-o}}var Re=/(^|[-_])(bar|gauge|meter|fill|progress|track)([-_]|$)/i;function xe(r){let n=r.parent;for(let e=0;n&&e<3;e+=1,n=n.parent){let t=$(n);if(t)return t}return""}function K(r,n){if(Y(r).some(a=>Re.test(a)))return!0;let e=_(r.attrs.style);return!!(e.background||e["background-color"]||N(r,"background",n)||N(r,"background-color",n))&&$(r)===""}function Ee(r){let n=new Map;for(let t of r)for(let a=t.node.parent;a;a=a.parent)n.set(a,(n.get(a)||0)+1);let e=new Map;for(let t of r){let a=null;for(let s=t.node.parent;s;s=s.parent)if((n.get(s)||0)>=2){a=s;break}a&&(e.has(a)||e.set(a,[]),e.get(a).push(t))}return[...e.values()].filter(t=>t.length>=2)}function _e(r,n){let e=new Map,t=[];S(r,s=>{if(s.tag==="path"||s.tag==="rect"){let h=s.attrs.ecmeta_series_index;if(h==null)return;let d=null;if(s.tag==="rect"){let f=Number(s.attrs.width),y=Number(s.attrs.height);Number.isFinite(f)&&Number.isFinite(y)&&(d={w:f,h:y})}else d=Te(s.attrs.d||"");if(!d)return;let u=s.attrs.ecmeta_data_index,p=String(h);e.has(p)||e.set(p,[]),e.get(p).push({bar:{node:s,extent:NaN,unit:"user",dataIndex:u==null||u===""?null:Number(u),context:"",start:s.start},box:d});return}if(D.has(s.tag)||s.tag==="svg")return;let i=_(s.attrs.style),o=i.width==null?i["inline-size"]:i.width;if(o==null||o==="")return;let c=G(o),l=M(o);c==null&&l==null||K(s,n)&&t.push({node:s,extent:c??l,unit:c==null?"px":"%",dataIndex:null,context:xe(s),start:s.start})});let a=[];for(let s of e.values()){if(s.length<2)continue;let i=l=>Math.max(...l)-Math.min(...l),o=i(s.map(l=>l.box.h))>=i(s.map(l=>l.box.w));for(let l of s)l.bar.extent=o?l.box.h:l.box.w;let c=s.map(l=>l.bar).sort((l,h)=>l.dataIndex!=null&&h.dataIndex!=null?l.dataIndex-h.dataIndex:l.start-h.start);a.push(c)}for(let s of Ee(t)){let i=s.filter(l=>l.unit==="%"),o=s.filter(l=>l.unit==="px"),c=i.length>=o.length?i:o;c.length>=2&&a.push(c)}return a}function Ae(r){let n=[];return S(r,e=>{if(e.tag!=="table")return;let t=[];S(e,a=>{if(a.tag!=="tr")return;let s=a.children.filter(i=>i.tag==="td"||i.tag==="th");s.length&&s.every(i=>i.tag==="th")||t.push(a)}),n.push({node:e,rows:t,rowTexts:t.map($),start:e.start})}),n}function Ie(r,n,e,t,a,s,i){let o=$(n);for(let c of t){let l=c.records.length;if(l<3)continue;let h=[],d=0;for(let g of c.identities){let b=T(g);!b||b.length<2||(d+=1,o.includes(b)||h.push(String(g)))}if(d<3)continue;let u=d-h.length,p=d?u/d:1,f=null,y=0;for(let g of e){if(!g.rows.length)continue;let b=0,x=new Set;for(let I of g.rowTexts){let E=!1;c.identities.forEach((se,le)=>{let J=T(se);J.length>=2&&I.indexOf(J)>=0&&(E=!0,x.add(le))}),E&&(b+=1)}b<Math.max(2,Math.ceil(g.rows.length*.5))||x.size<Math.ceil(b*.8)||b>y&&(y=b,f=g)}let m=f!=null;i.push({path:c.path||"(root)",records:l,identitiesSearched:d,presentInPage:u,renderedFraction:Number(p.toFixed(3)),boundTableRows:m?f.rows.length:null}),!(p<pe)&&(h.length&&v(a,s,{code:w.ROW_MISSING,line:m?k(r,f.start):1,message:`The data at \`${c.path||"(root)"}\` has ${l} records but ${h.length} of them appear NOWHERE on the page: ${h.slice(0,6).map(g=>JSON.stringify(String(g).slice(0,40))).join(", ")}${h.length>6?`, +${h.length-6} more`:""}.`,hint:"Render every record you were given, or say in the page that it is a partial view and how many were left out. A silently truncated table reads as the complete answer."}),h.length===0&&m&&f.rows.length!==l&&v(a,s,{code:w.ROW_COUNT,line:k(r,f.start),message:`The table renders ${f.rows.length} body row(s) but the data at \`${c.path||"(root)"}\` has ${l} records \u2014 ${Math.abs(l-f.rows.length)} ${l>f.rows.length?"missing":"extra"}.`,hint:'Emit exactly one <tr> per record. If you meant to show only some of them, state the cut ("top 10 of 23") in the page itself.'}))}}function Pe(r,n){let e=new Array(r.length).fill(-1);if(r.every(s=>s.dataIndex!=null)){let s=0;if(r.forEach((i,o)=>{let c=i.dataIndex;c>=0&&c<n.values.length&&(e[o]=c,s+=1)}),s===r.length)return e}e.fill(-1);let t=new Set,a=0;if(r.forEach((s,i)=>{for(let o=0;o<n.labels.length;o+=1){if(t.has(o))continue;let c=T(n.labels[o]);if(c.length>=2&&s.context.indexOf(c)>=0){e[i]=o,t.add(o),a+=1;break}}}),a===r.length)return e;if(r.length!==n.values.length)return null;for(let s=0;s<r.length;s+=1)e[s]=s;return e}function $e(r,n){let e=null,t=0;for(let s of n){let i=s.labels.filter(o=>{let c=T(o);return c.length>=2&&r.some(l=>l.context.indexOf(c)>=0)}).length;i>t&&(t=i,e=s)}if(t>=2)return e;let a=n.filter(s=>s.values.length===r.length);return a.length===1?a[0]:null}function Ce(r,n,e,t,a,s){s.barGroups=n.map(i=>i.length),s.boundBars=0;for(let i of n){let o=$e(i,e);if(!o)continue;if(o.values.length!==i.length){v(t,a,{code:w.BAR_COUNT,line:k(r,i[0].start),message:`The chart draws ${i.length} bar(s) but the series at \`${o.path||"(root)"}\` has ${o.values.length} value(s).`,hint:"Draw one bar per value. A dropped bar silently removes a whole category from the reader's view of the data."});continue}let c=Pe(i,o);if(!c||c.some(u=>u<0))continue;s.boundBars+=i.length;let l=[];for(let u=0;u<i.length;u+=1)i[u].extent>0&&o.values[c[u]]>0&&l.push(u);if(l.length>=he){let u=l.map(g=>i[g].extent/o.values[c[g]]).sort((g,b)=>g-b),p=Math.floor(u.length/2),f=u.length%2?u[p]:(u[p-1]+u[p])/2,y=Math.max(...o.values.map(g=>Math.abs(g))),m=f*y;if(f>0&&m>0)for(let g of l){let b=o.values[c[g]],x=f*b,I=Math.abs(i[g].extent-x)/m;if(I<=ue)continue;let E=i[g].unit==="%"?"%":"";v(t,a,{code:w.BAR_PROPORTION,line:k(r,i[g].start),message:`The bar for ${JSON.stringify(o.labels[c[g]])} (value ${b}) is drawn at ${F(i[g].extent)}${E}, but every other bar in this chart puts it at ${F(x)}${E} \u2014 off by ${Math.round(I*100)}% of the full-scale bar. The chart misrepresents the data.`,hint:"Every bar in one chart must use the SAME value\u2192length scale. Compute each length from its datum with one formula (value / max * 100), never by writing the numbers out by hand."})}}let h=o.values.reduce((u,p)=>u+p,0),d=Math.max(...o.values.map(u=>Math.abs(u)));for(let u=0;u<i.length;u+=1){let p=o.values[c[u]],f=i[u].context;if(!f)continue;let y=(f.match(/-?\d[\d,]*(?:\.\d+)?/g)||[]).map(m=>Number(m.replace(/,/g,""))).filter(m=>Number.isFinite(m));y.length&&(y.some(m=>Math.abs(m-p)<1e-9)||h>0&&y.some(m=>Math.abs(m-p/h*100)<.6)||d>0&&y.some(m=>Math.abs(m-p/d*100)<.6)||v(t,a,{code:w.VALUE_MISMATCH,line:k(r,i[u].start),message:`The row for ${JSON.stringify(o.labels[c[u]])} prints ${y.slice(0,3).join(", ")} but its datum is ${p}.`,hint:"Print the figure your tools returned, unchanged, and use that same figure everywhere it appears on the page."}))}}}var Le=/^(flex|inline-flex|grid|inline-grid)$/;function He(r,n,e,t,a,s){let i=0;S(n,o=>{if(D.has(o.tag))return;let c=_(o.attrs.style).width;if(!c)return;let l=G(c);if(l==null||l<=0||!K(o,e))return;i+=1;let h=W(o,e);if(h==="none"){v(t,a,{code:w.ZERO_WIDTH,line:k(r,o.start),message:`<${o.tag}> is given style="width:${c}" but its effective display is \`none\` \u2014 it is not rendered at all.`,hint:"Remove the display:none rule that matches this element, or remove the element. A width on a display:none box is dead code."});return}if(h!=="inline")return;let d=o.parent;if(d&&d.tag!=="#root"&&Le.test(W(d,e)))return;let u=N(o,"position",e);if(u==="absolute"||u==="fixed")return;let p=N(o,"float",e);p==="left"||p==="right"||v(t,a,{code:w.ZERO_WIDTH,line:k(r,o.start),message:`<${o.tag}${o.attrs.class?` class="${o.attrs.class}"`:""}> is given style="width:${c}" but its effective display is \`inline\` \u2014 CSS IGNORES width on a non-replaced inline box, so this bar renders with no length at all. The source looks right; the page will not.`,hint:"Give the bar display:block or display:inline-block (or make its container display:flex). A <span> is inline by DEFAULT \u2014 that alone is enough to swallow the width."})}),s.percentWidthBars=i}function Be(r,n,e,t,a){S(n,s=>{let i=M(N(s,"width",e));if(!(i==null||i<=0))for(let o=s.parent;o&&o.tag!=="#root";o=o.parent){let c=M(N(o,"width",e));if(c==null||c<=0)continue;if(i<=c)return;let l=N(o,"overflow",e)||N(o,"overflow-x",e);if(l!=="hidden"&&l!=="clip")return;v(t,a,{code:w.OVERFLOW,line:k(r,s.start),message:`<${s.tag}> is ${i}px wide inside a ${c}px ancestor with overflow:${l} \u2014 ${Math.round(i-c)}px of it is clipped and can never be seen.`,hint:"Size the child from its container (a percentage, or max-width:100%), or widen the container. Content the reader cannot see is content you did not publish."});return}})}var Me=[{re:/\[object (?:Object|Array|Promise|Map|Set|Null|Undefined)\]/g,what:"an object stringified instead of a field being picked off it"},{re:/\$\{[^}\n]{0,80}\}/g,what:"an un-substituted JavaScript template placeholder"},{re:/\{\{[^}\n]{0,80}\}\}/g,what:"an un-substituted moustache placeholder"},{re:/(?:^|[^\w.])(NaN)(?![\w])/g,what:"NaN \u2014 an arithmetic result that was never a number"},{re:/(?:^|[^\w.])(-?Infinity)(?![\w])/g,what:"Infinity \u2014 a division by zero reached the page"}],De=/^(undefined|null)$/i,Ue=new Set(["td","th","li","span","div","strong","em","b"]);function je(r,n,e,t,a){let s=0;for(let i of ke(n)){let o=i.value;s+=o.length;for(let l of Me){l.re.lastIndex=0;let h;for(;(h=l.re.exec(o))!==null;){let d=h[1]==null?h[0]:h[1];v(e,t,{code:w.PLACEHOLDER_TEXT,line:k(r,i.start+h.index+h[0].indexOf(d)),message:`The page prints ${JSON.stringify(d.slice(0,60))} as VISIBLE TEXT \u2014 ${l.what}. A reader sees this literally.`,hint:"Format the value before it reaches the page: pick the field you meant off the object, and guard the arithmetic so a missing input renders as an em dash rather than NaN."}),h.index===l.re.lastIndex&&(l.re.lastIndex+=1)}}let c=Z(o).trim();De.test(c)&&Ue.has(i.parent.tag)&&v(e,t,{code:w.PLACEHOLDER_TEXT,line:k(r,i.start),message:`A <${i.parent.tag}> contains only the literal text "${c}" \u2014 the value it was meant to hold was ${c}.`,hint:'Render an em dash (\u2014) or "n/a" for a missing value; never let the JavaScript literal reach the reader.'})}a.textCharsScanned=s}function z({html:r,data:n}){let e=typeof r=="string"?r:"",t=[],a={};if(!e.trim())return{ok:!1,defects:[{code:"empty-content",line:1,message:"No html was passed to the check.",hint:"Pass the exact page source you are about to publish."}],checked:{tables:0,bodyRows:0,bars:0,barGroups:[],boundBars:0,seriesInData:0,collections:[]}};let s=we(e),i=ve(s),o=Ae(s),c=_e(s,i),l=Oe(n),h={tables:o.length,bodyRows:o.reduce((d,u)=>d+u.rows.length,0),bars:c.reduce((d,u)=>d+u.length,0),barGroups:[],boundBars:0,seriesInData:l.series.length,collections:[]};return Ie(e,s,o,l.collections,t,a,h.collections),Ce(e,c,l.series,t,a,h),He(e,s,i,t,a,h),Be(e,s,i,t,a),je(e,s,t,a,h),t.sort((d,u)=>(d.line||0)-(u.line||0)),{ok:t.length===0,defects:t,checked:h}}function ze(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=Ye(Ke(import.meta.url)),n=Ge(r,"..","bin","mcp-skill.mjs");return ae(n)?n:null}function H(){if(process.env.PROJECT_API_TOKEN)return process.env.PROJECT_API_TOKEN;if(process.env.ZIBBY_USER_TOKEN)return process.env.ZIBBY_USER_TOKEN;try{let r=Ze(We(),".zibby","config.json");return ae(r)&&JSON.parse(Fe(r,"utf-8")).sessionToken||null}catch{return null}}function B(){return process.env.ZIBBY_ACCOUNT_API_URL?process.env.ZIBBY_ACCOUNT_API_URL.replace(/\/$/,""):(process.env.ZIBBY_ENV||"prod")==="local"?"http://localhost:3001":process.env.ZIBBY_PROD_ACCOUNT_API_URL||"https://api-prod.zibby.app"}function Ve(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function ie(r){return`${Ve()}:artifact:${r}`}var R="headings, paragraphs, **bold**, *italic*, `inline code`, fenced code blocks, bullet + numbered lists, blockquotes, horizontal rules, links, and GitHub-style pipe tables (including |---:| alignment)",L="raw HTML (tags are escaped and print as literal text), images, nested lists, footnotes and strikethrough",A=2,O=0;function bt(){O=0}function j(r,n){return JSON.stringify({published:!1,stop:!0,attemptsUsed:A,defects:n||[],error:r,instruction:`STOP. You have used both publish attempts and the page is still rejected. Do NOT call artifact_publish or artifact_update again in this turn. Tell the user plainly that you could not produce a clean page, say in one line what was wrong, and give them the content directly in the chat instead. A broken link is worse than an honest "I couldn't".`})}function qe(r,n,e){return JSON.stringify({published:!1,stop:!1,attemptsRemaining:A-O,defects:e||[],error:n,instruction:"The page was NOT published and no URL exists. Fix EXACTLY the defects listed above and call once more \u2014 you get ONE more attempt, then this tool will stop you."+(r==="html"?` You are on the \`html\` path, where you author the entire document and every mistake ships. Unless this page genuinely needs a custom visual layout, the fastest and most reliable fix is to re-send the SAME content as \`markdown\` \u2014 Zibby then generates the document skeleton, CSS and tables for you and these defects become impossible. Markdown supports ${R}.`:` Markdown supports ${R}; it does NOT support ${L}.`)})}var V="The page WAS published and the url is valid. This only means the data\u2194render pre-flight could not reach a verdict \u2014 nothing is known to be wrong. Open the page yourself if the figures matter.";function Xe(r){let n=new Set,e=(r||[]).map((a,s)=>{let i=a.line?` line ${a.line}:`:"",o="";return a.hint&&!n.has(a.code)&&(n.add(a.code),o=` \u2014 ${a.hint}`),`${s+1}. [${a.code}]${i} ${a.message}${o}`}),t=e.length;return`Artifact NOT published and NOTHING was sent \u2014 the page was checked against the \`data\` you passed and disagrees with it in ${t} place${t===1?"":"s"}:
|
|
2
|
+
${e.join(`
|
|
3
|
+
`)}`}function Qe(r,n){let e;try{e=z({html:r,data:n})}catch(s){return{unverified:{status:"unverified",reason:`the data\u2194render pre-flight threw and was skipped: ${String(s?.message||s).slice(0,200)}`,note:V}}}if(!e?.ok)return{rejected:{message:Xe(e?.defects),defects:e?.defects||[]}};let t=e.checked||{};return(Number(t.tables)||0)+(Number(t.bars)||0)===0?{unverified:{status:"unverified",reason:"the pre-flight found no tables and no bars in this page, so it had nothing to compare against your data \u2014 a clean result here proves nothing",checked:t,note:V}}:null}function q(r,n,e){return O+=1,{__rejected:!0,payload:O>=A?j(n,e):qe(r,n,e)}}async function et(r){let n=H();if(!n)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${B()}/credits/artifacts`,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify(r)});if(!e.ok){let t=await e.text().catch(()=>""),a=null;try{a=JSON.parse(t)}catch{}let s=new Error(e.status===422&&a?.error?a.error:`artifact write failed (${e.status}): ${t.slice(0,300)}`);throw s.status=e.status,Array.isArray(a?.defects)&&(s.defects=a.defects),s}return e.json()}async function X(r,n,e){let t=e?Qe(e.html,e.data):null;if(t?.rejected)return q(n,t.rejected.message,t.rejected.defects);try{let a=await et(r);return O=0,t?.unverified&&(a.__preflightNote=t.unverified),a}catch(a){if(a?.status!==422)throw a;return q(n,a.message,a.defects)}}async function oe(r){let n=H();if(!n)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${B()}/credits/artifacts/${encodeURIComponent(r)}`,{method:"GET",headers:{Authorization:`Bearer ${n}`}});if(!e.ok){let t=await e.text().catch(()=>"");throw new Error(`artifact get failed (${e.status}): ${t.slice(0,300)}`)}return e.json()}var U=60;function Q(r,n){let e=Math.max(0,n-Math.floor(U/2)),a=r.slice(e,e+U).replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t");return(e>0?"\u2026":"")+a+(e+U<r.length?"\u2026":"")}function tt(r,n){if(r===n)return null;let e=Math.min(r.length,n.length),t=0;for(;t<e&&r[t]===n[t];)t+=1;return{kind:t===e?n.length<r.length?"truncated":"longer":"diverged",sentChars:r.length,storedChars:n.length,delta:n.length-r.length,firstDiffAt:t,sentExcerpt:Q(r,t),storedExcerpt:Q(n,t)}}var C="Read-back compares the STORED SOURCE with what was sent (transport/storage integrity only). It does NOT check that the page renders correctly.";async function ee(r,n){let e;try{e=await oe(r)}catch(s){return{status:"unverified",scope:C,reason:`read-back request failed: ${String(s?.message||s).slice(0,200)}`,note:"The page WAS published and the url is valid \u2014 this only means the extra integrity check could not run. No action needed."}}let t=e?.content;if(typeof t!="string")return{status:"unverified",scope:C,reason:"read-back returned no content field",note:"The page WAS published and the url is valid \u2014 this only means the extra integrity check could not run. No action needed."};if(t===""&&n!=="")return{status:"unverified",scope:C,reason:"read-back returned empty content, which the backend also returns when its object-store read errors \u2014 indistinguishable, so this is inconclusive rather than a mismatch",note:"The page WAS published and the url is valid. If the page looks blank when opened, re-send the same content with artifact_update."};let a=tt(n,t);return a?{status:"mismatch",scope:C,...a,note:`The stored page source is NOT what was sent (${a.kind}: sent ${a.sentChars} chars, stored ${a.storedChars}, first difference at character ${a.firstDiffAt}). The url exists and the page is live, so do NOT re-publish a new one. You may call artifact_update ONCE with the identical content to rewrite the blob; if it differs again, stop and tell the user the published page may be incomplete rather than retrying.`}:null}async function te(r,n){let e=H();if(!e)return;let t=await fetch(`${B()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({op:"store",scope:ie(r),content:JSON.stringify(n)})});if(!t.ok){let a=await t.text().catch(()=>"");throw new Error(`artifact index write failed (${t.status}): ${a.slice(0,200)}`)}}async function nt(r){let n=H();if(!n)return null;let e=await fetch(`${B()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify({op:"recall",scope:ie(r)})});if(!e.ok)return null;let t=await e.json().catch(()=>null);if(!t?.found||!t?.memory?.content)return null;try{return JSON.parse(t.memory.content)}catch{return null}}function ne(r){let n=typeof r?.html=="string"&&r.html.length>0,e=typeof r?.markdown=="string"&&r.markdown.length>0;return n&&e?{error:"Pass EITHER markdown OR html, not both \u2014 they are different rendering paths. Use markdown unless the page needs a custom visual layout."}:e?{format:"markdown",content:r.markdown}:n?{format:"html",content:r.html}:null}function re(r,n){let e=r?.data;if(e==null)return null;if(!n||n.format!=="html")return{error:"`data` is only meaningful together with `html`. The pre-flight reads a rendered HTML document (tables, bars, CSS), so on the markdown path it would check nothing while reporting a clean page \u2014 that is worse than not checking. Re-send WITHOUT `data`. Do NOT switch to `html` just to get the check: on the markdown path Zibby generates the document, so the collapsed-bar and broken-geometry defects it looks for cannot happen there in the first place."};let t=e;if(typeof t=="string")try{t=JSON.parse(t)}catch{return{error:'`data` was a string but not valid JSON \u2014 pass the tool results as a JSON object (wrap a bare array as {"rows": [...]}).'}}return{preflight:{html:n.content,data:t}}}var yt={id:"artifact",callsBackend:!0,meta:Je.artifact,serverName:"artifact",allowedTools:["mcp__artifact__*"],description:"Artifacts \u2014 publish a self-contained, shareable HTML/Markdown page to Zibby and get back a URL; the index of what you published is your memory.",promptFragment:`## Artifacts (publish a shareable page, remember what you made)
|
|
2
4
|
You can PUBLISH a standalone page \u2014 a status report, a plan, a comparison table,
|
|
3
5
|
a dashboard-y summary, a diagram, a "here's what I found" write-up \u2014 and get back
|
|
4
|
-
a shareable URL.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
a shareable URL.
|
|
7
|
+
|
|
8
|
+
### WRITE IT AS MARKDOWN. That is the default, and it is not a stylistic hint.
|
|
9
|
+
Pass \`markdown\` and Zibby generates the whole document for you \u2014 doctype, head,
|
|
10
|
+
CSS, typography, dark mode, table styling. You supply only the CONTENT. Markdown
|
|
11
|
+
supports ${R}. It does NOT support ${L}.
|
|
12
|
+
|
|
13
|
+
Pass \`html\` ONLY when the page genuinely needs a visual layout Markdown cannot
|
|
14
|
+
express \u2014 a bar chart, a gauge, a custom grid, an SVG diagram. On the html path
|
|
15
|
+
YOU author the entire document and every mistake ships: a table left in Markdown
|
|
16
|
+
syntax, an escaped \`<!doctype\`, a bar with no width. If the page is a report,
|
|
17
|
+
a plan, a list, a table or a write-up, it is markdown. No exceptions worth taking.
|
|
18
|
+
|
|
19
|
+
Whichever path: the page is sandboxed when viewed (no network at all, no ambient
|
|
20
|
+
credentials), so on the html path keep ALL CSS/JS/images INLINE (inline
|
|
21
|
+
<style>/<script>, data: URIs) \u2014 every external URL is blocked and will simply not
|
|
22
|
+
load. NEVER escape the document: send real tags, not \`<div>\`.
|
|
23
|
+
|
|
24
|
+
### Every number on the page must come from a tool result or this transcript.
|
|
25
|
+
Do not do arithmetic in prose and then print the answer \u2014 that is how a page ends
|
|
26
|
+
up saying 42 in one place and 45 in another. If you need a total, compute it with
|
|
27
|
+
a tool (or restate the figures exactly as your tools returned them) and cite the
|
|
28
|
+
same figure everywhere it appears.
|
|
29
|
+
|
|
30
|
+
### If an html page RENDERS DATA, pass \`data\` too.
|
|
31
|
+
A table of records, a bar chart, a row of meters \u2014 pass the raw tool results the
|
|
32
|
+
page was built from as \`data\`, unchanged and unsummarised. The page is then
|
|
33
|
+
compared against them before anything is sent, which catches the failures nobody
|
|
34
|
+
sees until a human opens the link: a table short of rows, a bar drawn at a length
|
|
35
|
+
that is not its value, a leftover \`\${\u2026}\` or \`[object Object]\`. Do not write
|
|
36
|
+
your own expectations \u2014 the check derives them from the data. Prose pages don't
|
|
37
|
+
need it, and it is an error on the \`markdown\` path.
|
|
38
|
+
|
|
39
|
+
### The page is validated BEFORE a URL exists.
|
|
40
|
+
If the content is broken you get back the exact defects and their line numbers,
|
|
41
|
+
and no page is created. You get ONE retry \u2014 and the \`data\` check and the server
|
|
42
|
+
check SHARE those two attempts, so a locally refused page costs an attempt just
|
|
43
|
+
like a rejected one. After that the tool STOPS you \u2014 at which point say plainly
|
|
44
|
+
that you could not produce a clean page and put the content in the chat instead.
|
|
45
|
+
Do not keep trying.
|
|
8
46
|
|
|
9
47
|
THE CONTENT COMES FROM THIS CONVERSATION \u2014 NEVER FROM YOUR IMAGINATION.
|
|
10
48
|
When someone says "make me a page / an artifact / a report", they mean THE THING
|
|
@@ -23,16 +61,18 @@ link, buries the real answer, and reads as if you ignored them. There is no
|
|
|
23
61
|
situation in which "\u793A\u4F8B / sample / demo" belongs in a title you publish.
|
|
24
62
|
|
|
25
63
|
Tools:
|
|
26
|
-
- artifact_publish: Publish a NEW page. Pass a \`title\` and EITHER \`
|
|
27
|
-
\`
|
|
28
|
-
|
|
29
|
-
|
|
64
|
+
- artifact_publish: Publish a NEW page. Pass a \`title\` and EITHER \`markdown\`
|
|
65
|
+
(the default \u2014 prefer this) OR \`html\` (never both). Pass \`data\` when the html
|
|
66
|
+
page renders a dataset (see above). Optional \`kind\` (e.g. "report", "plan",
|
|
67
|
+
"dashboard"), \`favicon\` (an emoji), and \`summary\` (one line for your own
|
|
68
|
+
index). Returns { id, url }. Share the url; keep the id if you'll update it
|
|
69
|
+
later.
|
|
30
70
|
- artifact_update: Revise an EXISTING page by \`id\` (same url, new version). Pass
|
|
31
|
-
the fields to change (\`title\`, \`html
|
|
71
|
+
the fields to change (\`title\`, \`markdown\`|\`html\`).
|
|
32
72
|
- artifact_get: Fetch one artifact by \`id\` \u2192 { metadata, content } so you can
|
|
33
73
|
reuse / edit / re-publish it.
|
|
34
74
|
|
|
35
75
|
To recall WHAT YOU HAVE ALREADY PUBLISHED, use your kv-memory tool
|
|
36
76
|
kv_recall_prefix with keyPrefix "artifact:" \u2014 each entry is the index record
|
|
37
77
|
{ id, title, url, kind, createdAt, summary } for a page you made. (Publishing
|
|
38
|
-
records this automatically; you don't store it yourself.)`,resolve(){let
|
|
78
|
+
records this automatically; you don't store it yourself.)`,resolve(){let r=ze();if(!r)return{command:null,args:[],env:{},description:this.description};let n={};for(let e of["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_USER_TOKEN","WORKFLOW_TYPE"])process.env[e]&&(n[e]=process.env[e]);return{type:"stdio",command:"node",args:[r,"../dist/artifact.js","artifactSkill"],env:n,description:this.description,alwaysLoad:!0}},async handleToolCall(r,n){try{switch(r){case"artifact_publish":{if(O>=A)return j("Publish budget already spent this turn.",[]);let e=typeof n?.title=="string"?n.title.trim():"";if(!e)return JSON.stringify({error:"title is required"});let t=ne(n);if(!t)return JSON.stringify({error:"provide exactly one of markdown (preferred) or html (non-empty string)"});if(t.error)return JSON.stringify({error:t.error});let a=re(n,t);if(a?.error)return JSON.stringify({error:a.error});let s={title:e,[t.format]:t.content};typeof n?.kind=="string"&&n.kind.trim()&&(s.kind=n.kind.trim()),typeof n?.favicon=="string"&&n.favicon.trim()&&(s.favicon=n.favicon.trim());let i=await X(s,t.format,a?.preflight);if(i?.__rejected)return i.payload;let o=i?.id,c=i?.url;if(!o||!c)return JSON.stringify({error:"artifact write returned no id/url",response:i});let l={id:o,url:c};i.__preflightNote&&(l.preflight=i.__preflightNote);let h=await ee(o,t.content);h&&(l.readBack=h);let d={id:o,title:e,url:c,kind:s.kind||null,createdAt:i.createdAt||new Date().toISOString(),summary:typeof n?.summary=="string"&&n.summary.trim()?n.summary.trim():e};try{await te(o,d)}catch(u){l.indexWarning=u.message}return JSON.stringify(l)}case"artifact_update":{if(O>=A)return j("Publish budget already spent this turn.",[]);let e=typeof n?.id=="string"?n.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let t=ne(n);if(t?.error)return JSON.stringify({error:t.error});let a=typeof n?.title=="string"?n.title.trim():"";if(!t&&!a)return JSON.stringify({error:"nothing to update \u2014 pass a new title and/or markdown|html"});let s=re(n,t);if(s?.error)return JSON.stringify({error:s.error});let i={id:e};a&&(i.title=a),t&&(i[t.format]=t.content),typeof n?.kind=="string"&&n.kind.trim()&&(i.kind=n.kind.trim()),typeof n?.favicon=="string"&&n.favicon.trim()&&(i.favicon=n.favicon.trim());let o=await X(i,t?.format,s?.preflight);if(o?.__rejected)return o.payload;let c=o?.url;if(!c)return JSON.stringify({error:"artifact update returned no url",response:o});let l={id:e,url:c};if(o.__preflightNote&&(l.preflight=o.__preflightNote),t){let u=await ee(e,t.content);u&&(l.readBack=u)}let h=await nt(e)||{},d={...h,id:e,url:c,title:a||h.title||"Untitled",kind:i.kind||h.kind||null,createdAt:h.createdAt||o.createdAt||new Date().toISOString(),updatedAt:o.updatedAt||new Date().toISOString()};typeof n?.summary=="string"&&n.summary.trim()?d.summary=n.summary.trim():d.summary||(d.summary=d.title);try{await te(e,d)}catch(u){l.indexWarning=u.message}return JSON.stringify(l)}case"artifact_get":{let e=typeof n?.id=="string"?n.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let t=await oe(e);return JSON.stringify(t)}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"artifact_publish",description:`Publish a NEW self-contained, shareable page (report/plan/table/dashboard/diagram/write-up) and get back a shareable URL. USE \`markdown\` \u2014 it is the default path and Zibby generates the entire document for you (doctype, head, CSS, dark mode, table styling); you supply only the content, so the page cannot come out malformed. Markdown supports ${R}; it does NOT support ${L}. Use \`html\` ONLY when the page needs a visual layout Markdown cannot express (a bar chart, a gauge, a custom grid, an SVG diagram) \u2014 on that path you author the whole document and every mistake ships verbatim. Never pass both. Every figure on the page must come from a tool result or this conversation \u2014 do not do arithmetic in prose and print the result, and use the same number everywhere it appears. The content MUST come from the current conversation or data you fetched this turn; if you are not sure what the page should be about, ASK instead of calling this tool, and never publish a demo/sample/placeholder or an account-overview page as a stand-in. On the html path keep all CSS/JS/images INLINE (inline <style>/<script>, data: URIs) and send real tags (never <escaped> markup) \u2014 the page is sandboxed on view with no network at all, so every external URL is dead. PASS \`data\` WHENEVER THE HTML PAGE RENDERS A DATASET \u2014 a table of records, a bar chart, a row of meters. Give it the raw tool results the page was built from, unchanged; the page is then checked AGAINST that data before anything is sent, catching a table short of rows, a bar drawn at the wrong length, and leftover \${\u2026} or [object Object]. It costs you nothing when the page is right. Skip it for prose pages, and never pass it with \`markdown\` (that is an error, not a no-op). The content is VALIDATED before any URL exists: if it is broken \u2014 by that data check or by the server \u2014 you get the exact defects and line numbers back, you get ONE retry, and then the tool stops you. Both kinds of rejection share the SAME two attempts. Returns { id, url }.`,input_schema:{type:"object",properties:{title:{type:"string",description:"The page title (also the browser tab title)."},markdown:{type:"string",description:`PREFERRED. The page content as Markdown \u2014 Zibby renders it into a styled, self-contained document, so you never author a skeleton. Supports ${R}. Does NOT support ${L}. Provide this OR html, never both.`},html:{type:"string",description:"The EXCEPTION path \u2014 only for a layout Markdown cannot express (bar chart, gauge, custom grid, SVG diagram). A self-contained HTML document or fragment, served VERBATIM: you own the doctype, head, CSS and every tag. All assets must be inline (<style>/<script>, data: URIs). Provide this OR markdown, never both."},data:{type:"object",description:'Optional, and worth passing WHENEVER the html page renders a dataset (a table of records, a bar chart, meters): the raw tool results the page was built from, as JSON, UNCHANGED \u2014 do not summarise it and do not write your own expectations, the check derives what must be true by itself (wrap a bare array as {"rows": [...]}). The page is then verified against it BEFORE publishing: row counts, bar lengths vs their values, bars whose width the CSS throws away, and placeholder residue. If they disagree nothing is sent and you get the defects back, using one of your two attempts. Only valid with `html` \u2014 passing it with `markdown` is an error.'},kind:{type:"string",description:'Optional label for what this is, e.g. "report", "plan", "dashboard", "diagram". Stored in your index.'},favicon:{type:"string",description:'Optional emoji used as the browser-tab icon, e.g. "\u{1F4CA}".'},summary:{type:"string",description:"Optional one-line summary for your own index (defaults to the title). Helps you recall later what this page was."}},required:["title"]}},{name:"artifact_update",description:"Revise an EXISTING artifact by id \u2014 the shareable URL stays the same, the content is replaced (new version). Pass the fields to change (title and/or markdown|html). Prefer `markdown` for the same reason artifact_publish does: Zibby generates the document, so it cannot come out malformed. An update is validated exactly like a publish \u2014 including the optional `data` check when you send new `html` that renders a dataset \u2014 and if the new content is broken it is REFUSED, the page keeps its last good version, and the rejection shares the same two attempts as publish. Returns { id, url }.",input_schema:{type:"object",properties:{id:{type:"string",description:'The id of the artifact to update (from a prior artifact_publish, or your kv-memory "artifact:" index).'},title:{type:"string",description:"New title (optional)."},markdown:{type:"string",description:`PREFERRED. New Markdown content (optional) \u2014 Zibby renders the document skeleton. Supports ${R}. Provide this OR html.`},html:{type:"string",description:"New HTML content (optional), served verbatim \u2014 only for layouts Markdown cannot express. Provide this OR markdown."},data:{type:"object",description:"Optional \u2014 same as on artifact_publish: when the new `html` renders a dataset, pass the raw tool results it was built from and the page is checked against them before anything is sent. Only valid alongside new `html`."},kind:{type:"string",description:"Optional updated kind label."},favicon:{type:"string",description:"Optional updated emoji favicon."},summary:{type:"string",description:"Optional updated one-line index summary."}},required:["id"]}},{name:"artifact_get",description:'Fetch ONE artifact you published, by id \u2192 { metadata, content }. Use to reuse / edit / re-publish a page. To LIST what you have published, use your kv-memory tool kv_recall_prefix with keyPrefix "artifact:".',input_schema:{type:"object",properties:{id:{type:"string",description:"The artifact id."}},required:["id"]}}]};export{tt as __compareStoredSource,bt as __resetPublishBudget,yt as artifactSkill};
|
package/dist/chartRender.d.ts
CHANGED
|
@@ -35,6 +35,14 @@
|
|
|
35
35
|
* 3. <cwd>/.zibby/output/charts (local dev fallback, agent-workflow's
|
|
36
36
|
* DEFAULT_OUTPUT_BASE '.zibby/output')
|
|
37
37
|
*
|
|
38
|
+
* INLINE SVG (`output:'svg-inline'`) — the HTML-page path. A published
|
|
39
|
+
* artifact/report page is strictly SELF-CONTAINED (a strict CSP blocks every
|
|
40
|
+
* external host), so a file on disk is useless to it: the chart can only
|
|
41
|
+
* appear as an inline <svg> element. This path writes NO file and returns the
|
|
42
|
+
* markup in the tool result. See prepareInlineSvg() for the three transforms
|
|
43
|
+
* that make ECharts' SSR output actually safe to paste into an HTML document
|
|
44
|
+
* (they are NOT free — each one is a hazard verified against real output).
|
|
45
|
+
*
|
|
38
46
|
* SAFETY INJECTIONS before render (never trusts SSR text measurement):
|
|
39
47
|
* - spec must be a plain JSON object (validated; clear errors for the LLM)
|
|
40
48
|
* - animation is FORCED off (SSR has no frames)
|
package/dist/chartRender.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import{createRequire as
|
|
1
|
+
import{createRequire as I}from"node:module";import{randomBytes as T}from"node:crypto";import{existsSync as _,mkdirSync as B,writeFileSync as S}from"node:fs";import{dirname as N,join as u,resolve as w}from"node:path";import{fileURLToPath as O}from"node:url";var E=I(import.meta.url),f=null;function k(){return f||(f=E("echarts")),f}var g=null;function C(){return g||(g=E("@resvg/resvg-js")),g}var M=800,R=600,D=4096,P=16,b=60,L="Noto Sans",A=256*1024;function H(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let e=N(O(import.meta.url)),t=w(e,"..","bin","mcp-skill.mjs");return _(t)?t:null}function F(){let e=N(O(import.meta.url)),t=w(e,"..","assets","fonts");return["NotoSans-Regular.ttf","NotoSans-Bold.ttf"].map(r=>u(t,r)).filter(r=>_(r))}function $(){let e=process.env.ZIBBY_NODE_SESSION_PATH,t=process.env.ZIBBY_SESSION_PATH,r=e||(t?u(t,"chart-render"):u(process.cwd(),".zibby","output","charts"));return B(r,{recursive:!0}),r}function c(e){return e!=null&&typeof e=="object"&&!Array.isArray(e)}function x(e,t){let r=Number(e);return Number.isFinite(r)?Math.max(P,Math.min(D,Math.round(r))):t}function m(e){return typeof e!="string"||e.length<=b?e:`${e.slice(0,b-1)}\u2026`}function d(e){if(Array.isArray(e))for(let t=0;t<e.length;t++){let r=e[t];typeof r=="string"?e[t]=m(r):c(r)&&typeof r.name=="string"&&(r.name=m(r.name))}}function j(e){for(let n of["xAxis","yAxis"]){let a=Array.isArray(e[n])?e[n]:e[n]?[e[n]]:[];for(let s of a)c(s)&&d(s.data)}let t=Array.isArray(e.radar)?e.radar:e.radar?[e.radar]:[];for(let n of t)c(n)&&d(n.indicator);c(e.legend)&&d(e.legend.data);let r=Array.isArray(e.series)?e.series:e.series?[e.series]:[];for(let n of r)c(n)&&(typeof n.name=="string"&&(n.name=m(n.name)),d(n.data))}function J(e){let t=JSON.parse(JSON.stringify(e));return t.animation=!1,t.backgroundColor==null&&(t.backgroundColor="#fff"),c(t.textStyle)||(t.textStyle={}),t.textStyle.fontFamily==null&&(t.textStyle.fontFamily=L),j(t),t}function G(e){return typeof e!="string"?null:e.trim().replace(/\.(svg|png)$/i,"").replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^[.-]+|[.-]+$/g,"")||null}function Y(e,t,r){let a=k().init(null,null,{renderer:"svg",ssr:!0,width:t,height:r});try{return a.setOption(e),a.renderToSVGString()}finally{a.dispose()}}function z(e){return e.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi,"")}function U(e,t){return e.replace(/id="zr\d+-/g,`id="${t}-`).replace(/url\(#zr\d+-/g,`url(#${t}-`).replace(/class="zr\d+-/g,`class="${t}-`)}function V(e){let t=[];for(let r of e.matchAll(/(?:xlink:)?href\s*=\s*"([^"]*)"/gi)){let n=r[1].trim();n&&!n.startsWith("#")&&!/^data:/i.test(n)&&t.push(n)}for(let r of e.matchAll(/url\(\s*['"]?([^'")]*)['"]?\s*\)/gi)){let n=r[1].trim();n&&!n.startsWith("#")&&!/^data:/i.test(n)&&t.push(n)}return/@import/i.test(e)&&t.push("@import"),/<script\b/i.test(e)&&t.push("<script>"),/<foreignObject\b/i.test(e)&&t.push("<foreignObject>"),t}function Z(e){let t=U(z(e),`chart-${T(4).toString("hex")}`),r=V(t);if(r.length){let n=[...new Set(r)].slice(0,3).join(", ");throw new Error(`the rendered chart references external resources (${n}), which a self-contained HTML page blocks. Remove image:// symbols / external image fills from the spec (use plain colors or a data: URI), or use output:'svg' to write a file instead.`)}return t}function K(e){let{Resvg:t}=C();return new t(e,{font:{fontFiles:F(),loadSystemFonts:!0,defaultFontFamily:L}}).render().asPng()}var te={id:"chart-render",serverName:"chart_render",allowedTools:["mcp__chart_render__*"],description:"Chart render \u2014 local server-side chart rendering (Apache ECharts SVG SSR + resvg PNG); data never leaves the box",promptFragment:`## Chart Render (local, no external service)
|
|
2
2
|
You can render charts LOCALLY with the chart_render tool \u2014 pass a standard
|
|
3
3
|
Apache ECharts option object as \`spec\` (any chart type: bar, line, pie,
|
|
4
|
-
radar, scatter, heatmap, \u2026).
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
radar, scatter, heatmap, \u2026). No browser, no external chart service \u2014 the data
|
|
5
|
+
never leaves the machine. Don't set animation (it's forced off).
|
|
6
|
+
|
|
7
|
+
Pick the destination with \`output\`:
|
|
8
|
+
- **Building an HTML page / artifact / report \u2192 \`output:'svg-inline'\`.** It
|
|
9
|
+
returns the \`svg\` markup in the result; paste that <svg> element straight
|
|
10
|
+
into your HTML. A published page is self-contained and blocks every external
|
|
11
|
+
host, so a file path is useless there \u2014 inline is the ONLY way a chart shows
|
|
12
|
+
up. Never hand-write bar/gauge markup with percentage widths; let ECharts
|
|
13
|
+
compute the geometry from the data so the bars cannot come out wrong.
|
|
14
|
+
- **Attaching a chart to the run \u2192 \`output:'png'\` (default), \`'svg'\` or
|
|
15
|
+
\`'both'\`.** Writes files to the run's output folder (auto-uploaded as run
|
|
16
|
+
artifacts) and returns the paths. Default 800\xD7600.`,resolve({sessionPath:e,nodeName:t}={}){let r=H();if(!r)return{command:null,args:[],env:{},description:this.description};let n={},a=e&&t?u(e,t):null;a&&(n.ZIBBY_NODE_SESSION_PATH=a),e&&(n.ZIBBY_SESSION_PATH=e);for(let s of["ZIBBY_NODE_SESSION_PATH","ZIBBY_SESSION_PATH"])!n[s]&&process.env[s]&&(n[s]=process.env[s]);return{type:"stdio",command:"node",args:[r,"../dist/chartRender.js","chartRenderSkill"],env:n,description:this.description,alwaysLoad:!0}},async handleToolCall(e,t){if(e!=="chart_render")return JSON.stringify({error:`Unknown tool: ${e}`});try{let r=t?.spec;if(!c(r))return JSON.stringify({error:"spec must be a plain ECharts option OBJECT (e.g. { xAxis: {...}, yAxis: {...}, series: [...] }) \u2014 got "+(r===null?"null":Array.isArray(r)?"an array":typeof r)+". Pass the option object itself, not a string or an array."});let n=x(t?.width,M),a=x(t?.height,R),s=["svg","png","both","svg-inline"].includes(t?.output)?t.output:"png",y=G(t?.filename)||`chart-${Date.now()}`,o;try{o=Y(J(r),n,a)}catch(i){return JSON.stringify({error:`Chart render failed: ${i.message}. The spec must be a valid Apache ECharts option (check series[].type, and that xAxis/yAxis/radar match the series type). Fix the spec and retry.`})}if(typeof o!="string"||!o.includes("<svg"))return JSON.stringify({error:"Chart render produced no SVG \u2014 the spec likely describes an empty chart (no series?). Add at least one series and retry."});if(s==="svg-inline"){let i;try{i=Z(o)}catch(p){return JSON.stringify({error:`Chart cannot be inlined: ${p.message}`})}let l=Buffer.byteLength(i,"utf-8");return l>A?JSON.stringify({error:`Rendered SVG is ${Math.round(l/1024)}KB, over the ${Math.round(A/1024)}KB inline limit. Inline SVG is returned through the conversation and pasted into the page, so it must stay small. Aggregate or sample the data down (a readable chart rarely needs >1000 points), or use output:'svg' to write a file instead.`}):JSON.stringify({ok:!0,width:n,height:a,svg:i,bytes:l,files:[]})}let v=$(),h=[];if(s==="svg"||s==="both"){let i=u(v,`${y}.svg`);S(i,o,"utf-8"),h.push({path:i,format:"svg",bytes:Buffer.byteLength(o,"utf-8")})}if(s==="png"||s==="both"){let i;try{i=K(o)}catch(p){return JSON.stringify({error:`PNG rasterization failed: ${p.message}. Retry with output:'svg' if you only need the vector.`})}let l=u(v,`${y}.png`);S(l,i),h.push({path:l,format:"png",bytes:i.length})}return JSON.stringify({ok:!0,width:n,height:a,files:h})}catch(r){return JSON.stringify({error:`chart_render failed: ${r.message}`})}},tools:[{name:"chart_render",description:`Render a chart LOCALLY (no external service) from a standard Apache ECharts option. Pass the raw ECharts option as \`spec\` \u2014 every ECharts chart type works (bar, line, pie, radar, scatter, heatmap, \u2026). USE output:'svg-inline' WHEN BUILDING AN HTML PAGE, ARTIFACT OR REPORT: it returns the chart as an \`svg\` string (self-contained, no external refs) to paste directly into your HTML \u2014 a published page blocks external hosts, so a file path will not render there, and hand-written bar markup is never needed because ECharts computes the geometry. Otherwise output 'png' (default) / 'svg' / 'both' writes files to the run output folder and returns their paths. animation is forced off; background defaults to white. Bar example: {"xAxis":{"type":"category","data":["Q1","Q2"]},"yAxis":{},"series":[{"type":"bar","data":[12,30]}]}. Radar example: {"legend":{"data":["A","B"]},"radar":{"indicator":[{"name":"speed","max":10},{"name":"cost","max":10},{"name":"quality","max":10}]},"series":[{"type":"radar","data":[{"name":"A","value":[7,4,9]},{"name":"B","value":[5,8,6]}]}]}.`,input_schema:{type:"object",properties:{spec:{type:"object",description:"The Apache ECharts option object, passed through as-is (series, xAxis/yAxis, radar, legend, title, \u2026)."},width:{type:"number",description:"Image width in px (default 800, max 4096)."},height:{type:"number",description:"Image height in px (default 600, max 4096)."},output:{type:"string",enum:["svg","png","both","svg-inline"],description:"Destination. 'svg-inline' writes NO file and returns the SVG markup in the result for pasting into an HTML page/artifact \u2014 use this whenever you are building HTML. 'png' (default) / 'svg' / 'both' write files to the run output folder and return their paths."},filename:{type:"string",description:"Optional file basename (no extension); defaults to chart-<timestamp>."}},required:["spec"]}}]};export{te as chartRenderSkill};
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { kvMemorySkill } from './kvMemory.js';
|
|
|
35
35
|
import { datasetStoreSkill } from './datasetStore.js';
|
|
36
36
|
import { artifactSkill } from './artifact.js';
|
|
37
37
|
import { chartRenderSkill } from './chartRender.js';
|
|
38
|
+
import { reportCheckSkill } from './reportCheck.js';
|
|
38
39
|
import { codeStatsSkill } from './codeStats.js';
|
|
39
40
|
import { chatProgressSkill } from './chatProgress.js';
|
|
40
41
|
import { socialCardSkill } from './socialCard.js';
|
|
@@ -43,9 +44,10 @@ import { codebaseMemorySkill } from './codebaseMemory.js';
|
|
|
43
44
|
import { gbrainSkill } from './gbrain.js';
|
|
44
45
|
import { workflowBuilderSkill } from './workflow-builder.js';
|
|
45
46
|
export { SKILL_IDS as SKILLS } from '@zibby/skill-ids';
|
|
46
|
-
export { browserSkill, jiraSkill, githubSkill, gitlabSkill, figmaSkill, hubspotSkill, linearSkill, vikunjaSkill, planeSkill, opendesignSkill, gitSkill, gitWriteSkill, slackSkill, larkSkill, discordSkill, notionSkill, linkedinSkill, googleDocsSkill, larkDocsSkill, chatNotifySkill, sentrySkill, memorySkill, chatMemorySkill, kvMemorySkill, datasetStoreSkill, artifactSkill, chartRenderSkill, codeStatsSkill, chatProgressSkill, socialCardSkill, codeScanSkill, codebaseMemorySkill, gbrainSkill, testRunnerSkill, testRunnerSkill as runnerSkill, skillInstallerSkill, coreToolsSkill, workflowBuilderSkill };
|
|
47
|
+
export { browserSkill, jiraSkill, githubSkill, gitlabSkill, figmaSkill, hubspotSkill, linearSkill, vikunjaSkill, planeSkill, opendesignSkill, gitSkill, gitWriteSkill, slackSkill, larkSkill, discordSkill, notionSkill, linkedinSkill, googleDocsSkill, larkDocsSkill, chatNotifySkill, sentrySkill, memorySkill, chatMemorySkill, kvMemorySkill, datasetStoreSkill, artifactSkill, chartRenderSkill, reportCheckSkill, codeStatsSkill, chatProgressSkill, socialCardSkill, codeScanSkill, codebaseMemorySkill, gbrainSkill, testRunnerSkill, testRunnerSkill as runnerSkill, skillInstallerSkill, coreToolsSkill, workflowBuilderSkill };
|
|
47
48
|
export { openaiBillingSkill, anthropicBillingSkill, cursorAdminSkill, fetchOpenAICosts, fetchOpenAIProjects, fetchAnthropicCosts, fetchAnthropicWorkspaces, fetchCursorSpend, fetchAllProviders, groupByKey, meanStddev, } from './llm-billing.js';
|
|
48
49
|
export { reportObjectSchema, reportToBlockKit, reportToLarkCard, reportToNotionBlocks, reportToMarkdown, SEVERITIES as REPORT_SEVERITIES, } from './report.js';
|
|
50
|
+
export { checkRenderedReport, REPORT_CODES } from './reportCheck.js';
|
|
49
51
|
export { skill, functionSkill } from './function-skill.js';
|
|
50
52
|
export { registerSkill, getSkill, hasSkill, getAllSkills, listSkillIds } from '@zibby/agent-workflow';
|
|
51
53
|
export { INTEGRATIONS, INTEGRATION_REGISTRY } from './integrations.js';
|