@zibby/skills 0.2.15 → 0.2.17
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 +21 -0
- package/dist/artifact.js +48 -10
- package/dist/chartRender.d.ts +8 -0
- package/dist/chartRender.js +14 -5
- package/dist/index.d.ts +3 -1
- package/dist/index.js +202 -139
- 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,3 +1,24 @@
|
|
|
1
1
|
/** Test hook — the budget is process-lifetime state, so a suite must clear it. */
|
|
2
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
|
+
};
|
|
3
24
|
export declare const artifactSkill: any;
|
package/dist/artifact.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import{SKILL_META as v}from"@zibby/skill-ids";import{existsSync as k,readFileSync as S}from"node:fs";import{homedir as N}from"node:os";import{join as E,dirname as _,resolve as R}from"node:path";import{fileURLToPath as A}from"node:url";function I(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=_(A(import.meta.url)),t=R(r,"..","bin","mcp-skill.mjs");return k(t)?t:null}function m(){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=E(N(),".zibby","config.json");return k(r)&&JSON.parse(S(r,"utf-8")).sessionToken||null}catch{return null}}function f(){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 P(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function O(r){return`${P()}:artifact:${r}`}var u="headings, paragraphs, **bold**, *italic*, `inline code`, fenced code blocks, bullet + numbered lists, blockquotes, horizontal rules, links, and GitHub-style pipe tables (including |---:| alignment)",h="raw HTML (tags are escaped and print as literal text), images, nested lists, footnotes and strikethrough",p=2,c=0;function j(){c=0}function y(r,t){return JSON.stringify({published:!1,stop:!0,attemptsUsed:p,defects:t||[],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 U(r,t,e){return JSON.stringify({published:!1,stop:!1,attemptsRemaining:p-c,defects:e||[],error:t,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 ${u}.`:` Markdown supports ${u}; it does NOT support ${h}.`)})}async function x(r){let t=m();if(!t)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${f()}/credits/artifacts`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify(r)});if(!e.ok){let a=await e.text().catch(()=>""),n=null;try{n=JSON.parse(a)}catch{}let i=new Error(e.status===422&&n?.error?n.error:`artifact write failed (${e.status}): ${a.slice(0,300)}`);throw i.status=e.status,Array.isArray(n?.defects)&&(i.defects=n.defects),i}return e.json()}async function g(r,t){try{let e=await x(r);return c=0,e}catch(e){if(e?.status!==422)throw e;return c+=1,{__rejected:!0,payload:c>=p?y(e.message,e.defects):U(t,e.message,e.defects)}}}async function L(r){let t=m();if(!t)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${f()}/credits/artifacts/${encodeURIComponent(r)}`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});if(!e.ok){let a=await e.text().catch(()=>"");throw new Error(`artifact get failed (${e.status}): ${a.slice(0,300)}`)}return e.json()}async function b(r,t){let e=m();if(!e)return;let a=await fetch(`${f()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({op:"store",scope:O(r),content:JSON.stringify(t)})});if(!a.ok){let n=await a.text().catch(()=>"");throw new Error(`artifact index write failed (${a.status}): ${n.slice(0,200)}`)}}async function C(r){let t=m();if(!t)return null;let e=await fetch(`${f()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify({op:"recall",scope:O(r)})});if(!e.ok)return null;let a=await e.json().catch(()=>null);if(!a?.found||!a?.memory?.content)return null;try{return JSON.parse(a.memory.content)}catch{return null}}function w(r){let t=typeof r?.html=="string"&&r.html.length>0,e=typeof r?.markdown=="string"&&r.markdown.length>0;return t&&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}:t?{format:"html",content:r.html}:null}var H={id:"artifact",callsBackend:!0,meta:v.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 We}from"@zibby/skill-ids";import{existsSync as ie,readFileSync as Fe}from"node:fs";import{homedir as Ze}from"node:os";import{join as Ye,dirname as Ke,resolve as Ge}from"node:path";import{fileURLToPath as ze}from"node:url";import{existsSync as it}from"node:fs";import{dirname as st,resolve as lt}from"node:path";import{fileURLToPath as dt}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"},de=5,he=20,ue=.04,pe=3,fe=.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>=de||r.length>=he||(n[e.code]=t+1,r.push(e))}var ge={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=ge[String(e).toLowerCase()];return t??n})}function T(r){return Z(String(r??"")).replace(/\s+/g," ").trim().toLowerCase()}function W(r){return Math.round(r*10)/10}var me=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),j=new Set(["script","style","textarea"]),ye=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 we(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 ke(r){let n={tag:"#root",attrs:{},children:[],parent:null,start:0},e=n,t=[n],a=0,o=(i,s)=>{if(s<=i)return;let c=r.slice(i,s);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){o(a,r.length);break}if(o(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 s=be.exec(r.slice(i));if(!s){a=i+1;continue}let c=s[0],l=s[2].toLowerCase();if(s[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 u={tag:l,attrs:we(s[3]||""),children:[],parent:e,start:i};if(e.children.push(u),a=i+c.length,!(s[4]||me.has(l))){if(j.has(l)){let d=r.slice(a),h=new RegExp(`</\\s*${l}\\s*>`,"i").exec(d);u.raw=h?d.slice(0,h.index):d,a+=h?h.index+h[0].length:d.length;continue}t.push(u),e=u}}return n}function N(r,n){for(let e of r.children)e.tag!=="#text"&&(n(e),N(e,n))}function L(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 ve(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 Se(r){let n=[];return N(r,e=>{if(e.tag!=="style"||!e.raw)return;let t=e.raw.replace(/@[a-z-]+[^{]*\{/gi," "),a=/([^{}]+)\{([^{}]*)\}/g,o;for(;(o=a.exec(t))!==null;){let i=_(o[2]);for(let s of o[1].split(",")){let c=s.trim();if(!c||/[\s>+~:[\]*]/.test(c))continue;let l=[],u=null,d=null,h=!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]==="#")u=f[2];else if(f.index===0)d=f[2].toLowerCase();else{h=!1;break}h&&(d||u||l.length)&&n.push({classes:l,id:u,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 S(r,n,e){let t=_(r.attrs.style)[n];if(t!=null&&t!=="")return t.toLowerCase();let a=Y(r),o=null;for(let i of e){if(!Ne(i,r,a))continue;let s=i.decls[n];s!=null&&s!==""&&(o=s.toLowerCase())}return o}function F(r,n){let e=S(r,"display",n);return e||(ye.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 K(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 Oe(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 o=new Set(a.map(T)).size;if(o>e&&(e=o,n=a),o===r.length)break}return n||r.map((t,a)=>String(P(t)?Object.values(t)[0]==null?a:Object.values(t)[0]:t))}function Te(r){let n=[],e=[],t=new Set,a=(i,s)=>{if(!(s.length<2)){if(s.every(P)){n.push({path:i,records:s,identities:Oe(s)});let c=Object.keys(s[0]),l=c.filter(d=>s.every(h=>typeof h[d]=="number"&&Number.isFinite(h[d]))),u=c.filter(d=>s.every(h=>typeof h[d]=="string"));l.length===1&&u.length>=1&&e.push({path:i,labels:s.map(d=>String(d[u[0]])),values:s.map(d=>Number(d[l[0]]))});return}s.every(c=>typeof c=="number"&&Number.isFinite(c))&&e.push({path:i,labels:s.map((c,l)=>`#${l}`),values:s.slice()})}},o=(i,s,c)=>{if(!(c>6||i==null||typeof i!="object"||t.has(i))){if(t.add(i),Array.isArray(i)){a(s,i);for(let l=0;l<i.length&&l<200;l+=1)o(i[l],`${s}[${l}]`,c+1);return}for(let l of Object.keys(i))o(i[l],s?`${s}.${l}`:l,c+1)}};return o(r,"",0),{collections:n,series:e}}function Re(r){let n=String(r||"").match(/[MmLlHhVvZz]|-?\d*\.?\d+(?:e-?\d+)?/gi);if(!n)return null;let e=0,t=0,a="",o=1/0,i=-1/0,s=1/0,c=-1/0,l=()=>{!Number.isFinite(e)||!Number.isFinite(t)||(e<o&&(o=e),e>i&&(i=e),t<s&&(s=t),t>c&&(c=t))},u=0,d=0;for(;u<n.length&&d++<2e4;){let h=n[u];if(/[A-Za-z]/.test(h)&&(a=h,u+=1,a==="Z"||a==="z"))continue;if(u>=n.length)break;let p=()=>{let f=Number(n[u]);return u+=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:u+=1;break}}return!Number.isFinite(o)||!Number.isFinite(s)?null:{w:i-o,h:c-s}}var xe=/(^|[-_])(bar|gauge|meter|fill|progress|track)([-_]|$)/i;function Ee(r){let n=r.parent;for(let e=0;n&&e<3;e+=1,n=n.parent){let t=L(n);if(t)return t}return""}function G(r,n){if(Y(r).some(a=>xe.test(a)))return!0;let e=_(r.attrs.style);return!!(e.background||e["background-color"]||S(r,"background",n)||S(r,"background-color",n))&&L(r)===""}function _e(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 o=t.node.parent;o;o=o.parent)if((n.get(o)||0)>=2){a=o;break}a&&(e.has(a)||e.set(a,[]),e.get(a).push(t))}return[...e.values()].filter(t=>t.length>=2)}function Ae(r,n){let e=new Map,t=[];N(r,o=>{if(o.tag==="path"||o.tag==="rect"){let u=o.attrs.ecmeta_series_index;if(u==null)return;let d=null;if(o.tag==="rect"){let f=Number(o.attrs.width),b=Number(o.attrs.height);Number.isFinite(f)&&Number.isFinite(b)&&(d={w:f,h:b})}else d=Re(o.attrs.d||"");if(!d)return;let h=o.attrs.ecmeta_data_index,p=String(u);e.has(p)||e.set(p,[]),e.get(p).push({bar:{node:o,extent:NaN,unit:"user",dataIndex:h==null||h===""?null:Number(h),context:"",start:o.start},box:d});return}if(j.has(o.tag)||o.tag==="svg")return;let i=_(o.attrs.style),s=i.width==null?i["inline-size"]:i.width;if(s==null||s==="")return;let c=K(s),l=M(s);c==null&&l==null||G(o,n)&&t.push({node:o,extent:c??l,unit:c==null?"px":"%",dataIndex:null,context:Ee(o),start:o.start})});let a=[];for(let o of e.values()){if(o.length<2)continue;let i=l=>Math.max(...l)-Math.min(...l),s=i(o.map(l=>l.box.h))>=i(o.map(l=>l.box.w));for(let l of o)l.bar.extent=s?l.box.h:l.box.w;let c=o.map(l=>l.bar).sort((l,u)=>l.dataIndex!=null&&u.dataIndex!=null?l.dataIndex-u.dataIndex:l.start-u.start);a.push(c)}for(let o of _e(t)){let i=o.filter(l=>l.unit==="%"),s=o.filter(l=>l.unit==="px"),c=i.length>=s.length?i:s;c.length>=2&&a.push(c)}return a}function Ie(r){let n=[];return N(r,e=>{if(e.tag!=="table")return;let t=[];N(e,a=>{if(a.tag!=="tr")return;let o=a.children.filter(i=>i.tag==="td"||i.tag==="th");o.length&&o.every(i=>i.tag==="th")||t.push(a)}),n.push({node:e,rows:t,rowTexts:t.map(L),start:e.start})}),n}function Ce(r,n,e,t,a,o,i){let s=L(n);for(let c of t){let l=c.records.length;if(l<3)continue;let u=[],d=0;for(let g of c.identities){let y=T(g);!y||y.length<2||(d+=1,s.includes(y)||u.push(String(g)))}if(d<3)continue;let h=d-u.length,p=d?h/d:1,f=null,b=0;for(let g of e){if(!g.rows.length)continue;let y=0,x=new Set;for(let $ of g.rowTexts){let E=!1;c.identities.forEach((le,ce)=>{let J=T(le);J.length>=2&&$.indexOf(J)>=0&&(E=!0,x.add(ce))}),E&&(y+=1)}y<Math.max(2,Math.ceil(g.rows.length*.5))||x.size<Math.ceil(y*.8)||y>b&&(b=y,f=g)}let m=f!=null;i.push({path:c.path||"(root)",records:l,identitiesSearched:d,presentInPage:h,renderedFraction:Number(p.toFixed(3)),boundTableRows:m?f.rows.length:null}),!(p<fe)&&(u.length&&v(a,o,{code:w.ROW_MISSING,line:m?k(r,f.start):1,message:`The data at \`${c.path||"(root)"}\` has ${l} records but ${u.length} of them appear NOWHERE on the page: ${u.slice(0,6).map(g=>JSON.stringify(String(g).slice(0,40))).join(", ")}${u.length>6?`, +${u.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."}),u.length===0&&m&&f.rows.length!==l&&v(a,o,{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 $e(r,n){let e=new Array(r.length).fill(-1);if(r.every(o=>o.dataIndex!=null)){let o=0;if(r.forEach((i,s)=>{let c=i.dataIndex;c>=0&&c<n.values.length&&(e[s]=c,o+=1)}),o===r.length)return e}e.fill(-1);let t=new Set,a=0;if(r.forEach((o,i)=>{for(let s=0;s<n.labels.length;s+=1){if(t.has(s))continue;let c=T(n.labels[s]);if(c.length>=2&&o.context.indexOf(c)>=0){e[i]=s,t.add(s),a+=1;break}}}),a===r.length)return e;if(r.length!==n.values.length)return null;for(let o=0;o<r.length;o+=1)e[o]=o;return e}function Pe(r,n){let e=null,t=0;for(let o of n){let i=o.labels.filter(s=>{let c=T(s);return c.length>=2&&r.some(l=>l.context.indexOf(c)>=0)}).length;i>t&&(t=i,e=o)}if(t>=2)return e;let a=n.filter(o=>o.values.length===r.length);return a.length===1?a[0]:null}function Le(r,n,e,t,a,o){o.barGroups=n.map(i=>i.length),o.boundBars=0;for(let i of n){let s=Pe(i,e);if(!s)continue;if(s.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 \`${s.path||"(root)"}\` has ${s.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=$e(i,s);if(!c||c.some(h=>h<0))continue;o.boundBars+=i.length;let l=[];for(let h=0;h<i.length;h+=1)i[h].extent>0&&s.values[c[h]]>0&&l.push(h);if(l.length>=pe){let h=l.map(g=>i[g].extent/s.values[c[g]]).sort((g,y)=>g-y),p=Math.floor(h.length/2),f=h.length%2?h[p]:(h[p-1]+h[p])/2,b=Math.max(...s.values.map(g=>Math.abs(g))),m=f*b;if(f>0&&m>0)for(let g of l){let y=s.values[c[g]],x=f*y,$=Math.abs(i[g].extent-x)/m;if($<=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(s.labels[c[g]])} (value ${y}) is drawn at ${W(i[g].extent)}${E}, but every other bar in this chart puts it at ${W(x)}${E} \u2014 off by ${Math.round($*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 u=s.values.reduce((h,p)=>h+p,0),d=Math.max(...s.values.map(h=>Math.abs(h)));for(let h=0;h<i.length;h+=1){let p=s.values[c[h]],f=i[h].context;if(!f)continue;let b=(f.match(/-?\d[\d,]*(?:\.\d+)?/g)||[]).map(m=>Number(m.replace(/,/g,""))).filter(m=>Number.isFinite(m));b.length&&(b.some(m=>Math.abs(m-p)<1e-9)||u>0&&b.some(m=>Math.abs(m-p/u*100)<.6)||d>0&&b.some(m=>Math.abs(m-p/d*100)<.6)||v(t,a,{code:w.VALUE_MISMATCH,line:k(r,i[h].start),message:`The row for ${JSON.stringify(s.labels[c[h]])} prints ${b.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 He=/^(flex|inline-flex|grid|inline-grid)$/;function Be(r,n,e,t,a,o){let i=0;N(n,s=>{if(j.has(s.tag))return;let c=_(s.attrs.style).width;if(!c)return;let l=K(c);if(l==null||l<=0||!G(s,e))return;i+=1;let u=F(s,e);if(u==="none"){v(t,a,{code:w.ZERO_WIDTH,line:k(r,s.start),message:`<${s.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(u!=="inline")return;let d=s.parent;if(d&&d.tag!=="#root"&&He.test(F(d,e)))return;let h=S(s,"position",e);if(h==="absolute"||h==="fixed")return;let p=S(s,"float",e);p==="left"||p==="right"||v(t,a,{code:w.ZERO_WIDTH,line:k(r,s.start),message:`<${s.tag}${s.attrs.class?` class="${s.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."})}),o.percentWidthBars=i}function Me(r,n,e,t,a){N(n,o=>{let i=M(S(o,"width",e));if(!(i==null||i<=0))for(let s=o.parent;s&&s.tag!=="#root";s=s.parent){let c=M(S(s,"width",e));if(c==null||c<=0)continue;if(i<=c)return;let l=S(s,"overflow",e)||S(s,"overflow-x",e);if(l!=="hidden"&&l!=="clip")return;v(t,a,{code:w.OVERFLOW,line:k(r,o.start),message:`<${o.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 je=[{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"}],Ue=/^(undefined|null)$/i,De=new Set(["td","th","li","span","div","strong","em","b"]);function Je(r,n,e,t,a){let o=0;for(let i of ve(n)){let s=i.value;o+=s.length;for(let l of je){l.re.lastIndex=0;let u;for(;(u=l.re.exec(s))!==null;){let d=u[1]==null?u[0]:u[1];v(e,t,{code:w.PLACEHOLDER_TEXT,line:k(r,i.start+u.index+u[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."}),u.index===l.re.lastIndex&&(l.re.lastIndex+=1)}}let c=Z(s).trim();Ue.test(c)&&De.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=o}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 o=ke(e),i=Se(o),s=Ie(o),c=Ae(o,i),l=Te(n),u={tables:s.length,bodyRows:s.reduce((d,h)=>d+h.rows.length,0),bars:c.reduce((d,h)=>d+h.length,0),barGroups:[],boundBars:0,seriesInData:l.series.length,collections:[]};return Ce(e,o,s,l.collections,t,a,u.collections),Le(e,c,l.series,t,a,u),Be(e,o,i,t,a,u),Me(e,o,i,t,a),Je(e,o,t,a,u),t.sort((d,h)=>(d.line||0)-(h.line||0)),{ok:t.length===0,defects:t,checked:u}}function Ve(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=Ke(ze(import.meta.url)),n=Ge(r,"..","bin","mcp-skill.mjs");return ie(n)?n:null}function I(){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=Ye(Ze(),".zibby","config.json");return ie(r)&&JSON.parse(Fe(r,"utf-8")).sessionToken||null}catch{return null}}function C(){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 qe(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function oe(r){return`${qe()}: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)",B="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 D(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 Xe(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 ${B}.`)})}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 Qe(r){let n=new Set,e=(r||[]).map((a,o)=>{let i=a.line?` line ${a.line}:`:"",s="";return a.hint&&!n.has(a.code)&&(n.add(a.code),s=` \u2014 ${a.hint}`),`${o+1}. [${a.code}]${i} ${a.message}${s}`}),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 et(r,n){let e;try{e=z({html:r,data:n})}catch(o){return{unverified:{status:"unverified",reason:`the data\u2194render pre-flight threw and was skipped: ${String(o?.message||o).slice(0,200)}`,note:V}}}if(!e?.ok)return{rejected:{message:Qe(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?D(n,e):Xe(r,n,e)}}async function tt(r){let n=I();if(!n)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${C()}/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 o=new Error(e.status===422&&a?.error?a.error:`artifact write failed (${e.status}): ${t.slice(0,300)}`);throw o.status=e.status,Array.isArray(a?.defects)&&(o.defects=a.defects),o}return e.json()}async function X(r,n,e){let t=e?et(e.html,e.data):null;if(t?.rejected)return q(n,t.rejected.message,t.rejected.defects);try{let a=await tt(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 se(r){let n=I();if(!n)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${C()}/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()}async function Q(r,n,e={}){let t=I();if(!t)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let a=await fetch(`${C()}/credits/artifacts/${encodeURIComponent(r)}/${n}`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify(e)});if(!a.ok){let o=await a.text().catch(()=>""),i=null;try{i=JSON.parse(o)}catch{}throw new Error(i?.error||`artifact ${n} failed (${a.status}): ${o.slice(0,300)}`)}return a.json()}var U=60;function ee(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 nt(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:ee(r,t),storedExcerpt:ee(n,t)}}var H="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 te(r,n){let e;try{e=await se(r)}catch(o){return{status:"unverified",scope:H,reason:`read-back request failed: ${String(o?.message||o).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:H,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:H,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=nt(n,t);return a?{status:"mismatch",scope:H,...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 ne(r,n){let e=I();if(!e)return;let t=await fetch(`${C()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({op:"store",scope:oe(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 rt(r){let n=I();if(!n)return null;let e=await fetch(`${C()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify({op:"recall",scope:oe(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 re(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 ae(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 wt={id:"artifact",callsBackend:!0,meta:We.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
6
|
a shareable URL.
|
|
@@ -6,7 +8,7 @@ a shareable URL.
|
|
|
6
8
|
### WRITE IT AS MARKDOWN. That is the default, and it is not a stylistic hint.
|
|
7
9
|
Pass \`markdown\` and Zibby generates the whole document for you \u2014 doctype, head,
|
|
8
10
|
CSS, typography, dark mode, table styling. You supply only the CONTENT. Markdown
|
|
9
|
-
supports ${
|
|
11
|
+
supports ${R}. It does NOT support ${B}.
|
|
10
12
|
|
|
11
13
|
Pass \`html\` ONLY when the page genuinely needs a visual layout Markdown cannot
|
|
12
14
|
express \u2014 a bar chart, a gauge, a custom grid, an SVG diagram. On the html path
|
|
@@ -25,11 +27,22 @@ up saying 42 in one place and 45 in another. If you need a total, compute it wit
|
|
|
25
27
|
a tool (or restate the figures exactly as your tools returned them) and cite the
|
|
26
28
|
same figure everywhere it appears.
|
|
27
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
|
+
|
|
28
39
|
### The page is validated BEFORE a URL exists.
|
|
29
40
|
If the content is broken you get back the exact defects and their line numbers,
|
|
30
|
-
and no page is created. You get ONE retry
|
|
31
|
-
|
|
32
|
-
|
|
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.
|
|
33
46
|
|
|
34
47
|
THE CONTENT COMES FROM THIS CONVERSATION \u2014 NEVER FROM YOUR IMAGINATION.
|
|
35
48
|
When someone says "make me a page / an artifact / a report", they mean THE THING
|
|
@@ -49,16 +62,41 @@ situation in which "\u793A\u4F8B / sample / demo" belongs in a title you publish
|
|
|
49
62
|
|
|
50
63
|
Tools:
|
|
51
64
|
- artifact_publish: Publish a NEW page. Pass a \`title\` and EITHER \`markdown\`
|
|
52
|
-
(the default \u2014 prefer this) OR \`html\` (never both).
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
update it
|
|
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.
|
|
56
70
|
- artifact_update: Revise an EXISTING page by \`id\` (same url, new version). Pass
|
|
57
71
|
the fields to change (\`title\`, \`markdown\`|\`html\`).
|
|
58
72
|
- artifact_get: Fetch one artifact by \`id\` \u2192 { metadata, content } so you can
|
|
59
73
|
reuse / edit / re-publish it.
|
|
74
|
+
- artifact_share: Make a page readable WITHOUT a Zibby login \u2192 { url, password }.
|
|
75
|
+
- artifact_unshare: Revoke that, immediately and for everyone.
|
|
76
|
+
|
|
77
|
+
### If the people you are answering have no Zibby account, SHARE the page.
|
|
78
|
+
A published url only opens for logged-in members of this project. When you are
|
|
79
|
+
talking to a Lark or Slack channel, most of the people there are not Zibby users \u2014
|
|
80
|
+
so the link you just posted is, for them, a login screen they cannot get past.
|
|
81
|
+
|
|
82
|
+
So: when you publish a page for a chat channel, call \`artifact_share\` and post
|
|
83
|
+
the url AND the password together, in the same reply, formatted so both are easy
|
|
84
|
+
to copy. Copy the password character for character \u2014 it is case-sensitive, it is
|
|
85
|
+
shown exactly once, and nothing can look it up afterwards (you can only issue a
|
|
86
|
+
new one, which kills the old). Do not paraphrase it, do not "tidy" it, and do not
|
|
87
|
+
split it across lines.
|
|
88
|
+
|
|
89
|
+
This password is a page-viewing password and nothing else \u2014 it is not an account
|
|
90
|
+
password, it grants no access to Zibby, this project, or any other artifact, and
|
|
91
|
+
it is revocable with \`artifact_unshare\`. Treat it as the ordinary contents of the
|
|
92
|
+
message you are writing. That is the ONLY kind of secret you may ever put in a
|
|
93
|
+
message: never paste a token, key, or credential into chat, a prompt, or a page.
|
|
94
|
+
|
|
95
|
+
Sharing is per page and opt-in \u2014 publishing alone never exposes anything. If the
|
|
96
|
+
page is sensitive and the channel is wide, say so and let them decide rather than
|
|
97
|
+
sharing it silently.
|
|
60
98
|
|
|
61
99
|
To recall WHAT YOU HAVE ALREADY PUBLISHED, use your kv-memory tool
|
|
62
100
|
kv_recall_prefix with keyPrefix "artifact:" \u2014 each entry is the index record
|
|
63
101
|
{ id, title, url, kind, createdAt, summary } for a page you made. (Publishing
|
|
64
|
-
records this automatically; you don't store it yourself.)`,resolve(){let r=I();if(!r)return{command:null,args:[],env:{},description:this.description};let t={};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]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[r,"../dist/artifact.js","artifactSkill"],env:t,description:this.description,alwaysLoad:!0}},async handleToolCall(r,t){try{switch(r){case"artifact_publish":{if(c>=p)return y("Publish budget already spent this turn.",[]);let e=typeof t?.title=="string"?t.title.trim():"";if(!e)return JSON.stringify({error:"title is required"});let a=w(t);if(!a)return JSON.stringify({error:"provide exactly one of markdown (preferred) or html (non-empty string)"});if(a.error)return JSON.stringify({error:a.error});let n={title:e,[a.format]:a.content};typeof t?.kind=="string"&&t.kind.trim()&&(n.kind=t.kind.trim()),typeof t?.favicon=="string"&&t.favicon.trim()&&(n.favicon=t.favicon.trim());let i=await g(n,a.format);if(i?.__rejected)return i.payload;let o=i?.id,s=i?.url;if(!o||!s)return JSON.stringify({error:"artifact write returned no id/url",response:i});let l={id:o,title:e,url:s,kind:n.kind||null,createdAt:i.createdAt||new Date().toISOString(),summary:typeof t?.summary=="string"&&t.summary.trim()?t.summary.trim():e};try{await b(o,l)}catch(d){return JSON.stringify({id:o,url:s,indexWarning:d.message})}return JSON.stringify({id:o,url:s})}case"artifact_update":{if(c>=p)return y("Publish budget already spent this turn.",[]);let e=typeof t?.id=="string"?t.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let a=w(t);if(a?.error)return JSON.stringify({error:a.error});let n=typeof t?.title=="string"?t.title.trim():"";if(!a&&!n)return JSON.stringify({error:"nothing to update \u2014 pass a new title and/or markdown|html"});let i={id:e};n&&(i.title=n),a&&(i[a.format]=a.content),typeof t?.kind=="string"&&t.kind.trim()&&(i.kind=t.kind.trim()),typeof t?.favicon=="string"&&t.favicon.trim()&&(i.favicon=t.favicon.trim());let o=await g(i,a?.format);if(o?.__rejected)return o.payload;let s=o?.url;if(!s)return JSON.stringify({error:"artifact update returned no url",response:o});let l=await C(e)||{},d={...l,id:e,url:s,title:n||l.title||"Untitled",kind:i.kind||l.kind||null,createdAt:l.createdAt||o.createdAt||new Date().toISOString(),updatedAt:o.updatedAt||new Date().toISOString()};typeof t?.summary=="string"&&t.summary.trim()?d.summary=t.summary.trim():d.summary||(d.summary=d.title);try{await b(e,d)}catch(T){return JSON.stringify({id:e,url:s,indexWarning:T.message})}return JSON.stringify({id:e,url:s})}case"artifact_get":{let e=typeof t?.id=="string"?t.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let a=await L(e);return JSON.stringify(a)}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 ${u}; it does NOT support ${h}. 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. The content is VALIDATED before any URL exists: if it is broken you get the exact defects and line numbers back, you get ONE retry, and then the tool stops you. 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 ${u}. Does NOT support ${h}. 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."},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 if the new content is broken it is REFUSED and the page keeps its last good version. 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 ${u}. 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."},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{j as __resetPublishBudget,H as artifactSkill};
|
|
102
|
+
records this automatically; you don't store it yourself.)`,resolve(){let r=Ve();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 D("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=re(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=ae(n,t);if(a?.error)return JSON.stringify({error:a.error});let o={title:e,[t.format]:t.content};typeof n?.kind=="string"&&n.kind.trim()&&(o.kind=n.kind.trim()),typeof n?.favicon=="string"&&n.favicon.trim()&&(o.favicon=n.favicon.trim());let i=await X(o,t.format,a?.preflight);if(i?.__rejected)return i.payload;let s=i?.id,c=i?.url;if(!s||!c)return JSON.stringify({error:"artifact write returned no id/url",response:i});let l={id:s,url:c};i.__preflightNote&&(l.preflight=i.__preflightNote);let u=await te(s,t.content);u&&(l.readBack=u);let d={id:s,title:e,url:c,kind:o.kind||null,createdAt:i.createdAt||new Date().toISOString(),summary:typeof n?.summary=="string"&&n.summary.trim()?n.summary.trim():e};try{await ne(s,d)}catch(h){l.indexWarning=h.message}return JSON.stringify(l)}case"artifact_update":{if(O>=A)return D("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=re(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 o=ae(n,t);if(o?.error)return JSON.stringify({error:o.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 s=await X(i,t?.format,o?.preflight);if(s?.__rejected)return s.payload;let c=s?.url;if(!c)return JSON.stringify({error:"artifact update returned no url",response:s});let l={id:e,url:c};if(s.__preflightNote&&(l.preflight=s.__preflightNote),t){let h=await te(e,t.content);h&&(l.readBack=h)}let u=await rt(e)||{},d={...u,id:e,url:c,title:a||u.title||"Untitled",kind:i.kind||u.kind||null,createdAt:u.createdAt||s.createdAt||new Date().toISOString(),updatedAt:s.updatedAt||new Date().toISOString()};typeof n?.summary=="string"&&n.summary.trim()?d.summary=n.summary.trim():d.summary||(d.summary=d.title);try{await ne(e,d)}catch(h){l.indexWarning=h.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 se(e);return JSON.stringify(t)}case"artifact_share":{let e=typeof n?.id=="string"?n.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let t=await Q(e,"protect");return JSON.stringify({id:t.id,url:t.url,password:t.password,shared:!0,instruction:"Send BOTH the url and the password to the person who asked. Copy the password EXACTLY \u2014 it is case-sensitive and cannot be recovered later (call artifact_share again to issue a new one)."})}case"artifact_unshare":{let e=typeof n?.id=="string"?n.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let t=await Q(e,"unprotect");return JSON.stringify({id:t.id,shared:!1,wasShared:t.wasShared})}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 ${B}. 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 ${B}. 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"]}},{name:"artifact_share",description:"Make an artifact viewable WITHOUT a Zibby login, by password. Returns { url, password } \u2014 a link anyone can open plus the password that opens it. USE THIS when the people who need to read the page do not have Zibby accounts \u2014 the usual case is a Lark or Slack channel where you were asked for something and the channel members are not Zibby users. The normal artifact url only opens for logged-in members of this project, so for those recipients it is a dead link; this is what makes it readable. You MUST then send BOTH the url and the password in your reply, and copy the password EXACTLY (it is case-sensitive). It is shown ONCE and cannot be looked up afterwards \u2014 if it is lost, call this again to issue a new one, which also stops the old one working. Works on any artifact you published, old or new. Calling it again on an already-shared artifact replaces the password. Use artifact_unshare to revoke.",input_schema:{type:"object",properties:{id:{type:"string",description:'The id of the artifact to share (from artifact_publish, or your kv-memory "artifact:" index).'}},required:["id"]}},{name:"artifact_unshare",description:"Revoke an artifact's password link \u2014 it goes back to being viewable only by logged-in members of this project. Immediate and total: anyone who already entered the password loses access too, not just new visitors. Safe to call on an artifact that was never shared.",input_schema:{type:"object",properties:{id:{type:"string",description:"The artifact id to stop sharing."}},required:["id"]}}]};export{nt as __compareStoredSource,bt as __resetPublishBudget,wt 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';
|