@zibby/skills 2.0.0 → 2.0.1
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.js +20 -3
- package/dist/browser.js +3 -3
- package/dist/index.js +44 -27
- package/dist/package.json +4 -4
- package/package.json +4 -4
package/dist/artifact.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{SKILL_META as We}from"@zibby/skill-ids";import{existsSync as oe,readFileSync as Fe}from"node:fs";import{homedir as Ze}from"node:os";import{join as Ye,dirname as Ve,resolve as Ge}from"node:path";import{fileURLToPath as Ke}from"node:url";import{existsSync as ot}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 t=1;for(let e=0;e<n&&e<r.length;e+=1)r.charCodeAt(e)===10&&(t+=1);return t}function v(r,n,t){let e=n[t.code]||0;e>=de||r.length>=he||(n[t.code]=e+1,r.push(t))}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,t)=>{if(t[0]==="#"){let a=t[1]==="x"||t[1]==="X"?parseInt(t.slice(2),16):parseInt(t.slice(1),10);return Number.isFinite(a)?String.fromCodePoint(a):n}let e=ge[String(t).toLowerCase()];return e??n})}function O(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"]),M=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={},t=/([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'=<>`]+))|([a-zA-Z_:][-a-zA-Z0-9_:.]*)/g,e;for(;(e=t.exec(r))!==null;)e[1]?n[e[1].toLowerCase()]=e[3]!=null?e[3]:e[4]!=null?e[4]:e[5]==null?"":e[5]:e[6]&&(n[e[6].toLowerCase()]="");return n}function ke(r){let n={tag:"#root",attrs:{},children:[],parent:null,start:0},t=n,e=[n],a=0,s=(o,i)=>{if(i<=o)return;let c=r.slice(o,i);c.trim()&&t.children.push({tag:"#text",attrs:{},children:[],parent:t,start:o,value:c})};for(;a<r.length;){let o=r.indexOf("<",a);if(o<0){s(a,r.length);break}if(s(a,o),r.startsWith("<!--",o)){let d=r.indexOf("-->",o);a=d<0?r.length:d+3;continue}if(r.startsWith("<!",o)||r.startsWith("<?",o)){let d=r.indexOf(">",o);a=d<0?r.length:d+1;continue}let i=be.exec(r.slice(o));if(!i){a=o+1;continue}let c=i[0],l=i[2].toLowerCase();if(i[1]){for(let d=e.length-1;d>0;d-=1)if(e[d].tag===l){e.length=d,t=e[d-1];break}a=o+c.length;continue}let u={tag:l,attrs:we(i[3]||""),children:[],parent:t,start:o};if(t.children.push(u),a=o+c.length,!(i[4]||me.has(l))){if(M.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}e.push(u),t=u}}return n}function T(r,n){for(let t of r.children)t.tag!=="#text"&&(n(t),T(t,n))}function $(r){let n=[],t=e=>{for(let a of e.children)a.tag==="#text"?n.push(a.value||""):(n.push(" "),t(a),n.push(" "))};return t(r),O(n.join(""))}function ve(r){let n=[],t=e=>{for(let a of e.children)a.tag==="#text"?n.push({value:a.value||"",start:a.start,parent:e}):a.tag!=="pre"&&a.tag!=="code"&&t(a)};return t(r),n}function _(r){let n={};for(let t of String(r||"").split(";")){let e=t.indexOf(":");e<0||(n[t.slice(0,e).trim().toLowerCase()]=t.slice(e+1).trim().replace(/\s*!important$/i,""))}return n}function Se(r){let n=[];return T(r,t=>{if(t.tag!=="style"||!t.raw)return;let e=t.raw.replace(/@[a-z-]+[^{]*\{/gi," "),a=/([^{}]+)\{([^{}]*)\}/g,s;for(;(s=a.exec(e))!==null;){let o=_(s[2]);for(let i of s[1].split(",")){let c=i.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:o})}}}),n}function Y(r){return String(r.attrs.class||"").split(/\s+/).filter(Boolean).map(n=>n.toLowerCase())}function Te(r,n,t){if(r.tag&&r.tag!==n.tag||r.id&&r.id!==n.attrs.id)return!1;for(let e of r.classes)if(t.indexOf(e)<0)return!1;return!0}function S(r,n,t){let e=_(r.attrs.style)[n];if(e!=null&&e!=="")return e.toLowerCase();let a=Y(r),s=null;for(let o of t){if(!Te(o,r,a))continue;let i=o.decls[n];i!=null&&i!==""&&(s=i.toLowerCase())}return s}function F(r,n){let t=S(r,"display",n);return t||(ye.has(r.tag)?"inline":"block")}function U(r){if(!r)return null;let n=/^(-?\d+(?:\.\d+)?)px$/.exec(String(r).trim());return n?Number(n[1]):null}function V(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 Ne(r){if(!r.length||!P(r[0]))return r.map(e=>e==null?"":String(e));let n=null,t=0;for(let e of Object.keys(r[0])){let a=r.map(o=>P(o)&&o[e]!=null?String(o[e]):"");if(a.some(o=>o.length<2))continue;let s=new Set(a.map(O)).size;if(s>t&&(t=s,n=a),s===r.length)break}return n||r.map((e,a)=>String(P(e)?Object.values(e)[0]==null?a:Object.values(e)[0]:e))}function Oe(r){let n=[],t=[],e=new Set,a=(o,i)=>{if(!(i.length<2)){if(i.every(P)){n.push({path:o,records:i,identities:Ne(i)});let c=Object.keys(i[0]),l=c.filter(d=>i.every(h=>typeof h[d]=="number"&&Number.isFinite(h[d]))),u=c.filter(d=>i.every(h=>typeof h[d]=="string"));l.length===1&&u.length>=1&&t.push({path:o,labels:i.map(d=>String(d[u[0]])),values:i.map(d=>Number(d[l[0]]))});return}i.every(c=>typeof c=="number"&&Number.isFinite(c))&&t.push({path:o,labels:i.map((c,l)=>`#${l}`),values:i.slice()})}},s=(o,i,c)=>{if(!(c>6||o==null||typeof o!="object"||e.has(o))){if(e.add(o),Array.isArray(o)){a(i,o);for(let l=0;l<o.length&&l<200;l+=1)s(o[l],`${i}[${l}]`,c+1);return}for(let l of Object.keys(o))s(o[l],i?`${i}.${l}`:l,c+1)}};return s(r,"",0),{collections:n,series:t}}function xe(r){let n=String(r||"").match(/[MmLlHhVvZz]|-?\d*\.?\d+(?:e-?\d+)?/gi);if(!n)return null;let t=0,e=0,a="",s=1/0,o=-1/0,i=1/0,c=-1/0,l=()=>{!Number.isFinite(t)||!Number.isFinite(e)||(t<s&&(s=t),t>o&&(o=t),e<i&&(i=e),e>c&&(c=e))},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":t=p(),e=p(),l(),a="L";break;case"m":t+=p(),e+=p(),l(),a="l";break;case"L":t=p(),e=p(),l();break;case"l":t+=p(),e+=p(),l();break;case"H":t=p(),l();break;case"h":t+=p(),l();break;case"V":e=p(),l();break;case"v":e+=p(),l();break;default:u+=1;break}}return!Number.isFinite(s)||!Number.isFinite(i)?null:{w:o-s,h:c-i}}var Re=/(^|[-_])(bar|gauge|meter|fill|progress|track)([-_]|$)/i;function Ee(r){let n=r.parent;for(let t=0;n&&t<3;t+=1,n=n.parent){let e=$(n);if(e)return e}return""}function G(r,n){if(Y(r).some(a=>Re.test(a)))return!0;let t=_(r.attrs.style);return!!(t.background||t["background-color"]||S(r,"background",n)||S(r,"background-color",n))&&$(r)===""}function _e(r){let n=new Map;for(let e of r)for(let a=e.node.parent;a;a=a.parent)n.set(a,(n.get(a)||0)+1);let t=new Map;for(let e of r){let a=null;for(let s=e.node.parent;s;s=s.parent)if((n.get(s)||0)>=2){a=s;break}a&&(t.has(a)||t.set(a,[]),t.get(a).push(e))}return[...t.values()].filter(e=>e.length>=2)}function Ae(r,n){let t=new Map,e=[];T(r,s=>{if(s.tag==="path"||s.tag==="rect"){let u=s.attrs.ecmeta_series_index;if(u==null)return;let d=null;if(s.tag==="rect"){let f=Number(s.attrs.width),b=Number(s.attrs.height);Number.isFinite(f)&&Number.isFinite(b)&&(d={w:f,h:b})}else d=xe(s.attrs.d||"");if(!d)return;let h=s.attrs.ecmeta_data_index,p=String(u);t.has(p)||t.set(p,[]),t.get(p).push({bar:{node:s,extent:NaN,unit:"user",dataIndex:h==null||h===""?null:Number(h),context:"",start:s.start},box:d});return}if(M.has(s.tag)||s.tag==="svg")return;let o=_(s.attrs.style),i=o.width==null?o["inline-size"]:o.width;if(i==null||i==="")return;let c=V(i),l=U(i);c==null&&l==null||G(s,n)&&e.push({node:s,extent:c??l,unit:c==null?"px":"%",dataIndex:null,context:Ee(s),start:s.start})});let a=[];for(let s of t.values()){if(s.length<2)continue;let o=l=>Math.max(...l)-Math.min(...l),i=o(s.map(l=>l.box.h))>=o(s.map(l=>l.box.w));for(let l of s)l.bar.extent=i?l.box.h:l.box.w;let c=s.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 s of _e(e)){let o=s.filter(l=>l.unit==="%"),i=s.filter(l=>l.unit==="px"),c=o.length>=i.length?o:i;c.length>=2&&a.push(c)}return a}function Ie(r){let n=[];return T(r,t=>{if(t.tag!=="table")return;let e=[];T(t,a=>{if(a.tag!=="tr")return;let s=a.children.filter(o=>o.tag==="td"||o.tag==="th");s.length&&s.every(o=>o.tag==="th")||e.push(a)}),n.push({node:t,rows:e,rowTexts:e.map($),start:t.start})}),n}function Ce(r,n,t,e,a,s,o){let i=$(n);for(let c of e){let l=c.records.length;if(l<3)continue;let u=[],d=0;for(let g of c.identities){let y=O(g);!y||y.length<2||(d+=1,i.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 t){if(!g.rows.length)continue;let y=0,R=new Set;for(let L of g.rowTexts){let E=!1;c.identities.forEach((le,ce)=>{let J=O(le);J.length>=2&&L.indexOf(J)>=0&&(E=!0,R.add(ce))}),E&&(y+=1)}y<Math.max(2,Math.ceil(g.rows.length*.5))||R.size<Math.ceil(y*.8)||y>b&&(b=y,f=g)}let m=f!=null;o.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,s,{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,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 Le(r,n){let t=new Array(r.length).fill(-1);if(r.every(s=>s.dataIndex!=null)){let s=0;if(r.forEach((o,i)=>{let c=o.dataIndex;c>=0&&c<n.values.length&&(t[i]=c,s+=1)}),s===r.length)return t}t.fill(-1);let e=new Set,a=0;if(r.forEach((s,o)=>{for(let i=0;i<n.labels.length;i+=1){if(e.has(i))continue;let c=O(n.labels[i]);if(c.length>=2&&s.context.indexOf(c)>=0){t[o]=i,e.add(i),a+=1;break}}}),a===r.length)return t;if(r.length!==n.values.length)return null;for(let s=0;s<r.length;s+=1)t[s]=s;return t}function Pe(r,n){let t=null,e=0;for(let s of n){let o=s.labels.filter(i=>{let c=O(i);return c.length>=2&&r.some(l=>l.context.indexOf(c)>=0)}).length;o>e&&(e=o,t=s)}if(e>=2)return t;let a=n.filter(s=>s.values.length===r.length);return a.length===1?a[0]:null}function $e(r,n,t,e,a,s){s.barGroups=n.map(o=>o.length),s.boundBars=0;for(let o of n){let i=Pe(o,t);if(!i)continue;if(i.values.length!==o.length){v(e,a,{code:w.BAR_COUNT,line:k(r,o[0].start),message:`The chart draws ${o.length} bar(s) but the series at \`${i.path||"(root)"}\` has ${i.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=Le(o,i);if(!c||c.some(h=>h<0))continue;s.boundBars+=o.length;let l=[];for(let h=0;h<o.length;h+=1)o[h].extent>0&&i.values[c[h]]>0&&l.push(h);if(l.length>=pe){let h=l.map(g=>o[g].extent/i.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(...i.values.map(g=>Math.abs(g))),m=f*b;if(f>0&&m>0)for(let g of l){let y=i.values[c[g]],R=f*y,L=Math.abs(o[g].extent-R)/m;if(L<=ue)continue;let E=o[g].unit==="%"?"%":"";v(e,a,{code:w.BAR_PROPORTION,line:k(r,o[g].start),message:`The bar for ${JSON.stringify(i.labels[c[g]])} (value ${y}) is drawn at ${W(o[g].extent)}${E}, but every other bar in this chart puts it at ${W(R)}${E} \u2014 off by ${Math.round(L*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=i.values.reduce((h,p)=>h+p,0),d=Math.max(...i.values.map(h=>Math.abs(h)));for(let h=0;h<o.length;h+=1){let p=i.values[c[h]],f=o[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(e,a,{code:w.VALUE_MISMATCH,line:k(r,o[h].start),message:`The row for ${JSON.stringify(i.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,t,e,a,s){let o=0;T(n,i=>{if(M.has(i.tag))return;let c=_(i.attrs.style).width;if(!c)return;let l=V(c);if(l==null||l<=0||!G(i,t))return;o+=1;let u=F(i,t);if(u==="none"){v(e,a,{code:w.ZERO_WIDTH,line:k(r,i.start),message:`<${i.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=i.parent;if(d&&d.tag!=="#root"&&He.test(F(d,t)))return;let h=S(i,"position",t);if(h==="absolute"||h==="fixed")return;let p=S(i,"float",t);p==="left"||p==="right"||v(e,a,{code:w.ZERO_WIDTH,line:k(r,i.start),message:`<${i.tag}${i.attrs.class?` class="${i.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=o}function Ue(r,n,t,e,a){T(n,s=>{let o=U(S(s,"width",t));if(!(o==null||o<=0))for(let i=s.parent;i&&i.tag!=="#root";i=i.parent){let c=U(S(i,"width",t));if(c==null||c<=0)continue;if(o<=c)return;let l=S(i,"overflow",t)||S(i,"overflow-x",t);if(l!=="hidden"&&l!=="clip")return;v(e,a,{code:w.OVERFLOW,line:k(r,s.start),message:`<${s.tag}> is ${o}px wide inside a ${c}px ancestor with overflow:${l} \u2014 ${Math.round(o-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"}],je=/^(undefined|null)$/i,De=new Set(["td","th","li","span","div","strong","em","b"]);function Je(r,n,t,e,a){let s=0;for(let o of ve(n)){let i=o.value;s+=i.length;for(let l of Me){l.re.lastIndex=0;let u;for(;(u=l.re.exec(i))!==null;){let d=u[1]==null?u[0]:u[1];v(t,e,{code:w.PLACEHOLDER_TEXT,line:k(r,o.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(i).trim();je.test(c)&&De.has(o.parent.tag)&&v(t,e,{code:w.PLACEHOLDER_TEXT,line:k(r,o.start),message:`A <${o.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 K({html:r,data:n}){let t=typeof r=="string"?r:"",e=[],a={};if(!t.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=ke(t),o=Se(s),i=Ie(s),c=Ae(s,o),l=Oe(n),u={tables:i.length,bodyRows:i.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(t,s,i,l.collections,e,a,u.collections),$e(t,c,l.series,e,a,u),Be(t,s,o,e,a,u),Ue(t,s,o,e,a),Je(t,s,e,a,u),e.sort((d,h)=>(d.line||0)-(h.line||0)),{ok:e.length===0,defects:e,checked:u}}function ze(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=Ve(Ke(import.meta.url)),n=Ge(r,"..","bin","mcp-skill.mjs");return oe(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 oe(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 ie(r){return`${qe()}:artifact:${r}`}var x="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,N=0;function bt(){N=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,t){return JSON.stringify({published:!1,stop:!1,attemptsRemaining:A-N,defects:t||[],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 ${x}.`:` Markdown supports ${x}; it does NOT support ${B}.`)})}var z="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,t=(r||[]).map((a,s)=>{let o=a.line?` line ${a.line}:`:"",i="";return a.hint&&!n.has(a.code)&&(n.add(a.code),i=` \u2014 ${a.hint}`),`${s+1}. [${a.code}]${o} ${a.message}${i}`}),e=t.length;return`Artifact NOT published and NOTHING was sent \u2014 the page was checked against the \`data\` you passed and disagrees with it in ${e} place${e===1?"":"s"}:
|
|
1
|
+
import{SKILL_META as Fe}from"@zibby/skill-ids";import{existsSync as oe,readFileSync as We}from"node:fs";import{homedir as Ze}from"node:os";import{join as Ye,dirname as Ve,resolve as Ge}from"node:path";import{fileURLToPath as Ke}from"node:url";import{existsSync as it}from"node:fs";import{dirname as lt,resolve as ct}from"node:path";import{fileURLToPath as ht}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(a,n){if(n==null||n<0)return null;let t=1;for(let e=0;e<n&&e<a.length;e+=1)a.charCodeAt(e)===10&&(t+=1);return t}function v(a,n,t){let e=n[t.code]||0;e>=de||a.length>=he||(n[t.code]=e+1,a.push(t))}var ge={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:" ",ndash:"\u2013",mdash:"\u2014",hellip:"\u2026"};function Z(a){return String(a).replace(/&(#x?[0-9a-f]+|[a-z]+);/gi,(n,t)=>{if(t[0]==="#"){let r=t[1]==="x"||t[1]==="X"?parseInt(t.slice(2),16):parseInt(t.slice(1),10);return Number.isFinite(r)?String.fromCodePoint(r):n}let e=ge[String(t).toLowerCase()];return e??n})}function O(a){return Z(String(a??"")).replace(/\s+/g," ").trim().toLowerCase()}function F(a){return Math.round(a*10)/10}var me=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),M=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(a){let n={},t=/([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'=<>`]+))|([a-zA-Z_:][-a-zA-Z0-9_:.]*)/g,e;for(;(e=t.exec(a))!==null;)e[1]?n[e[1].toLowerCase()]=e[3]!=null?e[3]:e[4]!=null?e[4]:e[5]==null?"":e[5]:e[6]&&(n[e[6].toLowerCase()]="");return n}function ke(a){let n={tag:"#root",attrs:{},children:[],parent:null,start:0},t=n,e=[n],r=0,i=(s,o)=>{if(o<=s)return;let l=a.slice(s,o);l.trim()&&t.children.push({tag:"#text",attrs:{},children:[],parent:t,start:s,value:l})};for(;r<a.length;){let s=a.indexOf("<",r);if(s<0){i(r,a.length);break}if(i(r,s),a.startsWith("<!--",s)){let u=a.indexOf("-->",s);r=u<0?a.length:u+3;continue}if(a.startsWith("<!",s)||a.startsWith("<?",s)){let u=a.indexOf(">",s);r=u<0?a.length:u+1;continue}let o=be.exec(a.slice(s));if(!o){r=s+1;continue}let l=o[0],c=o[2].toLowerCase();if(o[1]){for(let u=e.length-1;u>0;u-=1)if(e[u].tag===c){e.length=u,t=e[u-1];break}r=s+l.length;continue}let h={tag:c,attrs:we(o[3]||""),children:[],parent:t,start:s};if(t.children.push(h),r=s+l.length,!(o[4]||me.has(c))){if(M.has(c)){let u=a.slice(r),d=new RegExp(`</\\s*${c}\\s*>`,"i").exec(u);h.raw=d?u.slice(0,d.index):u,r+=d?d.index+d[0].length:u.length;continue}e.push(h),t=h}}return n}function T(a,n){for(let t of a.children)t.tag!=="#text"&&(n(t),T(t,n))}function H(a){let n=[],t=e=>{for(let r of e.children)r.tag==="#text"?n.push(r.value||""):(n.push(" "),t(r),n.push(" "))};return t(a),O(n.join(""))}function ve(a){let n=[],t=e=>{for(let r of e.children)r.tag==="#text"?n.push({value:r.value||"",start:r.start,parent:e}):r.tag!=="pre"&&r.tag!=="code"&&t(r)};return t(a),n}function A(a){let n={};for(let t of String(a||"").split(";")){let e=t.indexOf(":");e<0||(n[t.slice(0,e).trim().toLowerCase()]=t.slice(e+1).trim().replace(/\s*!important$/i,""))}return n}function Se(a){let n=[];return T(a,t=>{if(t.tag!=="style"||!t.raw)return;let e=t.raw.replace(/@[a-z-]+[^{]*\{/gi," "),r=/([^{}]+)\{([^{}]*)\}/g,i;for(;(i=r.exec(e))!==null;){let s=A(i[2]);for(let o of i[1].split(",")){let l=o.trim();if(!l||/[\s>+~:[\]*]/.test(l))continue;let c=[],h=null,u=null,d=!0,p=/([.#]?)([A-Za-z_][-\w]*)/g,f;for(;(f=p.exec(l))!==null;)if(f[1]===".")c.push(f[2].toLowerCase());else if(f[1]==="#")h=f[2];else if(f.index===0)u=f[2].toLowerCase();else{d=!1;break}d&&(u||h||c.length)&&n.push({classes:c,id:h,tag:u,decls:s})}}}),n}function Y(a){return String(a.attrs.class||"").split(/\s+/).filter(Boolean).map(n=>n.toLowerCase())}function Te(a,n,t){if(a.tag&&a.tag!==n.tag||a.id&&a.id!==n.attrs.id)return!1;for(let e of a.classes)if(t.indexOf(e)<0)return!1;return!0}function S(a,n,t){let e=A(a.attrs.style)[n];if(e!=null&&e!=="")return e.toLowerCase();let r=Y(a),i=null;for(let s of t){if(!Te(s,a,r))continue;let o=s.decls[n];o!=null&&o!==""&&(i=o.toLowerCase())}return i}function W(a,n){let t=S(a,"display",n);return t||(ye.has(a.tag)?"inline":"block")}function U(a){if(!a)return null;let n=/^(-?\d+(?:\.\d+)?)px$/.exec(String(a).trim());return n?Number(n[1]):null}function V(a){if(!a)return null;let n=/^(-?\d+(?:\.\d+)?)%$/.exec(String(a).trim());return n?Number(n[1]):null}function P(a){return a!=null&&typeof a=="object"&&!Array.isArray(a)}function Ne(a){if(!a.length||!P(a[0]))return a.map(e=>e==null?"":String(e));let n=null,t=0;for(let e of Object.keys(a[0])){let r=a.map(s=>P(s)&&s[e]!=null?String(s[e]):"");if(r.some(s=>s.length<2))continue;let i=new Set(r.map(O)).size;if(i>t&&(t=i,n=r),i===a.length)break}return n||a.map((e,r)=>String(P(e)?Object.values(e)[0]==null?r:Object.values(e)[0]:e))}function Oe(a){let n=[],t=[],e=new Set,r=(s,o)=>{if(!(o.length<2)){if(o.every(P)){n.push({path:s,records:o,identities:Ne(o)});let l=Object.keys(o[0]),c=l.filter(u=>o.every(d=>typeof d[u]=="number"&&Number.isFinite(d[u]))),h=l.filter(u=>o.every(d=>typeof d[u]=="string"));c.length===1&&h.length>=1&&t.push({path:s,labels:o.map(u=>String(u[h[0]])),values:o.map(u=>Number(u[c[0]]))});return}o.every(l=>typeof l=="number"&&Number.isFinite(l))&&t.push({path:s,labels:o.map((l,c)=>`#${c}`),values:o.slice()})}},i=(s,o,l)=>{if(!(l>6||s==null||typeof s!="object"||e.has(s))){if(e.add(s),Array.isArray(s)){r(o,s);for(let c=0;c<s.length&&c<200;c+=1)i(s[c],`${o}[${c}]`,l+1);return}for(let c of Object.keys(s))i(s[c],o?`${o}.${c}`:c,l+1)}};return i(a,"",0),{collections:n,series:t}}function xe(a){let n=String(a||"").match(/[MmLlHhVvZz]|-?\d*\.?\d+(?:e-?\d+)?/gi);if(!n)return null;let t=0,e=0,r="",i=1/0,s=-1/0,o=1/0,l=-1/0,c=()=>{!Number.isFinite(t)||!Number.isFinite(e)||(t<i&&(i=t),t>s&&(s=t),e<o&&(o=e),e>l&&(l=e))},h=0,u=0;for(;h<n.length&&u++<2e4;){let d=n[h];if(/[A-Za-z]/.test(d)&&(r=d,h+=1,r==="Z"||r==="z"))continue;if(h>=n.length)break;let p=()=>{let f=Number(n[h]);return h+=1,f};switch(r){case"M":t=p(),e=p(),c(),r="L";break;case"m":t+=p(),e+=p(),c(),r="l";break;case"L":t=p(),e=p(),c();break;case"l":t+=p(),e+=p(),c();break;case"H":t=p(),c();break;case"h":t+=p(),c();break;case"V":e=p(),c();break;case"v":e+=p(),c();break;default:h+=1;break}}return!Number.isFinite(i)||!Number.isFinite(o)?null:{w:s-i,h:l-o}}var Re=/(^|[-_])(bar|gauge|meter|fill|progress|track)([-_]|$)/i;function Ee(a){let n=a.parent;for(let t=0;n&&t<3;t+=1,n=n.parent){let e=H(n);if(e)return e}return""}function G(a,n){if(Y(a).some(r=>Re.test(r)))return!0;let t=A(a.attrs.style);return!!(t.background||t["background-color"]||S(a,"background",n)||S(a,"background-color",n))&&H(a)===""}function Ae(a){let n=new Map;for(let e of a)for(let r=e.node.parent;r;r=r.parent)n.set(r,(n.get(r)||0)+1);let t=new Map;for(let e of a){let r=null;for(let i=e.node.parent;i;i=i.parent)if((n.get(i)||0)>=2){r=i;break}r&&(t.has(r)||t.set(r,[]),t.get(r).push(e))}return[...t.values()].filter(e=>e.length>=2)}function _e(a,n){let t=new Map,e=[];T(a,i=>{if(i.tag==="path"||i.tag==="rect"){let h=i.attrs.ecmeta_series_index;if(h==null)return;let u=null;if(i.tag==="rect"){let f=Number(i.attrs.width),b=Number(i.attrs.height);Number.isFinite(f)&&Number.isFinite(b)&&(u={w:f,h:b})}else u=xe(i.attrs.d||"");if(!u)return;let d=i.attrs.ecmeta_data_index,p=String(h);t.has(p)||t.set(p,[]),t.get(p).push({bar:{node:i,extent:NaN,unit:"user",dataIndex:d==null||d===""?null:Number(d),context:"",start:i.start},box:u});return}if(M.has(i.tag)||i.tag==="svg")return;let s=A(i.attrs.style),o=s.width==null?s["inline-size"]:s.width;if(o==null||o==="")return;let l=V(o),c=U(o);l==null&&c==null||G(i,n)&&e.push({node:i,extent:l??c,unit:l==null?"px":"%",dataIndex:null,context:Ee(i),start:i.start})});let r=[];for(let i of t.values()){if(i.length<2)continue;let s=c=>Math.max(...c)-Math.min(...c),o=s(i.map(c=>c.box.h))>=s(i.map(c=>c.box.w));for(let c of i)c.bar.extent=o?c.box.h:c.box.w;let l=i.map(c=>c.bar).sort((c,h)=>c.dataIndex!=null&&h.dataIndex!=null?c.dataIndex-h.dataIndex:c.start-h.start);r.push(l)}for(let i of Ae(e)){let s=i.filter(c=>c.unit==="%"),o=i.filter(c=>c.unit==="px"),l=s.length>=o.length?s:o;l.length>=2&&r.push(l)}return r}function Ie(a){let n=[];return T(a,t=>{if(t.tag!=="table")return;let e=[];T(t,r=>{if(r.tag!=="tr")return;let i=r.children.filter(s=>s.tag==="td"||s.tag==="th");i.length&&i.every(s=>s.tag==="th")||e.push(r)}),n.push({node:t,rows:e,rowTexts:e.map(H),start:t.start})}),n}function Ce(a,n,t,e,r,i,s){let o=H(n);for(let l of e){let c=l.records.length;if(c<3)continue;let h=[],u=0;for(let g of l.identities){let y=O(g);!y||y.length<2||(u+=1,o.includes(y)||h.push(String(g)))}if(u<3)continue;let d=u-h.length,p=u?d/u:1,f=null,b=0;for(let g of t){if(!g.rows.length)continue;let y=0,R=new Set;for(let L of g.rowTexts){let E=!1;l.identities.forEach((le,ce)=>{let J=O(le);J.length>=2&&L.indexOf(J)>=0&&(E=!0,R.add(ce))}),E&&(y+=1)}y<Math.max(2,Math.ceil(g.rows.length*.5))||R.size<Math.ceil(y*.8)||y>b&&(b=y,f=g)}let m=f!=null;s.push({path:l.path||"(root)",records:c,identitiesSearched:u,presentInPage:d,renderedFraction:Number(p.toFixed(3)),boundTableRows:m?f.rows.length:null}),!(p<fe)&&(h.length&&v(r,i,{code:w.ROW_MISSING,line:m?k(a,f.start):1,message:`The data at \`${l.path||"(root)"}\` has ${c} 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!==c&&v(r,i,{code:w.ROW_COUNT,line:k(a,f.start),message:`The table renders ${f.rows.length} body row(s) but the data at \`${l.path||"(root)"}\` has ${c} records \u2014 ${Math.abs(c-f.rows.length)} ${c>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 Le(a,n){let t=new Array(a.length).fill(-1);if(a.every(i=>i.dataIndex!=null)){let i=0;if(a.forEach((s,o)=>{let l=s.dataIndex;l>=0&&l<n.values.length&&(t[o]=l,i+=1)}),i===a.length)return t}t.fill(-1);let e=new Set,r=0;if(a.forEach((i,s)=>{for(let o=0;o<n.labels.length;o+=1){if(e.has(o))continue;let l=O(n.labels[o]);if(l.length>=2&&i.context.indexOf(l)>=0){t[s]=o,e.add(o),r+=1;break}}}),r===a.length)return t;if(a.length!==n.values.length)return null;for(let i=0;i<a.length;i+=1)t[i]=i;return t}function Pe(a,n){let t=null,e=0;for(let i of n){let s=i.labels.filter(o=>{let l=O(o);return l.length>=2&&a.some(c=>c.context.indexOf(l)>=0)}).length;s>e&&(e=s,t=i)}if(e>=2)return t;let r=n.filter(i=>i.values.length===a.length);return r.length===1?r[0]:null}function He(a,n,t,e,r,i){i.barGroups=n.map(s=>s.length),i.boundBars=0;for(let s of n){let o=Pe(s,t);if(!o)continue;if(o.values.length!==s.length){v(e,r,{code:w.BAR_COUNT,line:k(a,s[0].start),message:`The chart draws ${s.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 l=Le(s,o);if(!l||l.some(d=>d<0))continue;i.boundBars+=s.length;let c=[];for(let d=0;d<s.length;d+=1)s[d].extent>0&&o.values[l[d]]>0&&c.push(d);if(c.length>=pe){let d=c.map(g=>s[g].extent/o.values[l[g]]).sort((g,y)=>g-y),p=Math.floor(d.length/2),f=d.length%2?d[p]:(d[p-1]+d[p])/2,b=Math.max(...o.values.map(g=>Math.abs(g))),m=f*b;if(f>0&&m>0)for(let g of c){let y=o.values[l[g]],R=f*y,L=Math.abs(s[g].extent-R)/m;if(L<=ue)continue;let E=s[g].unit==="%"?"%":"";v(e,r,{code:w.BAR_PROPORTION,line:k(a,s[g].start),message:`The bar for ${JSON.stringify(o.labels[l[g]])} (value ${y}) is drawn at ${F(s[g].extent)}${E}, but every other bar in this chart puts it at ${F(R)}${E} \u2014 off by ${Math.round(L*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((d,p)=>d+p,0),u=Math.max(...o.values.map(d=>Math.abs(d)));for(let d=0;d<s.length;d+=1){let p=o.values[l[d]],f=s[d].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)||h>0&&b.some(m=>Math.abs(m-p/h*100)<.6)||u>0&&b.some(m=>Math.abs(m-p/u*100)<.6)||v(e,r,{code:w.VALUE_MISMATCH,line:k(a,s[d].start),message:`The row for ${JSON.stringify(o.labels[l[d]])} 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 $e=/^(flex|inline-flex|grid|inline-grid)$/;function Be(a,n,t,e,r,i){let s=0;T(n,o=>{if(M.has(o.tag))return;let l=A(o.attrs.style).width;if(!l)return;let c=V(l);if(c==null||c<=0||!G(o,t))return;s+=1;let h=W(o,t);if(h==="none"){v(e,r,{code:w.ZERO_WIDTH,line:k(a,o.start),message:`<${o.tag}> is given style="width:${l}" 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 u=o.parent;if(u&&u.tag!=="#root"&&$e.test(W(u,t)))return;let d=S(o,"position",t);if(d==="absolute"||d==="fixed")return;let p=S(o,"float",t);p==="left"||p==="right"||v(e,r,{code:w.ZERO_WIDTH,line:k(a,o.start),message:`<${o.tag}${o.attrs.class?` class="${o.attrs.class}"`:""}> is given style="width:${l}" 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."})}),i.percentWidthBars=s}function Ue(a,n,t,e,r){T(n,i=>{let s=U(S(i,"width",t));if(!(s==null||s<=0))for(let o=i.parent;o&&o.tag!=="#root";o=o.parent){let l=U(S(o,"width",t));if(l==null||l<=0)continue;if(s<=l)return;let c=S(o,"overflow",t)||S(o,"overflow-x",t);if(c!=="hidden"&&c!=="clip")return;v(e,r,{code:w.OVERFLOW,line:k(a,i.start),message:`<${i.tag}> is ${s}px wide inside a ${l}px ancestor with overflow:${c} \u2014 ${Math.round(s-l)}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,je=new Set(["td","th","li","span","div","strong","em","b"]);function Je(a,n,t,e,r){let i=0;for(let s of ve(n)){let o=s.value;i+=o.length;for(let c of Me){c.re.lastIndex=0;let h;for(;(h=c.re.exec(o))!==null;){let u=h[1]==null?h[0]:h[1];v(t,e,{code:w.PLACEHOLDER_TEXT,line:k(a,s.start+h.index+h[0].indexOf(u)),message:`The page prints ${JSON.stringify(u.slice(0,60))} as VISIBLE TEXT \u2014 ${c.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===c.re.lastIndex&&(c.re.lastIndex+=1)}}let l=Z(o).trim();De.test(l)&&je.has(s.parent.tag)&&v(t,e,{code:w.PLACEHOLDER_TEXT,line:k(a,s.start),message:`A <${s.parent.tag}> contains only the literal text "${l}" \u2014 the value it was meant to hold was ${l}.`,hint:'Render an em dash (\u2014) or "n/a" for a missing value; never let the JavaScript literal reach the reader.'})}r.textCharsScanned=i}function K({html:a,data:n}){let t=typeof a=="string"?a:"",e=[],r={};if(!t.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 i=ke(t),s=Se(i),o=Ie(i),l=_e(i,s),c=Oe(n),h={tables:o.length,bodyRows:o.reduce((u,d)=>u+d.rows.length,0),bars:l.reduce((u,d)=>u+d.length,0),barGroups:[],boundBars:0,seriesInData:c.series.length,collections:[]};return Ce(t,i,o,c.collections,e,r,h.collections),He(t,l,c.series,e,r,h),Be(t,i,s,e,r,h),Ue(t,i,s,e,r),Je(t,i,e,r,h),e.sort((u,d)=>(u.line||0)-(d.line||0)),{ok:e.length===0,defects:e,checked:h}}function qe(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let a=Ve(Ke(import.meta.url)),n=Ge(a,"..","bin","mcp-skill.mjs");return oe(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 a=Ye(Ze(),".zibby","config.json");return oe(a)&&JSON.parse(We(a,"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 ze(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function ie(a){return`${ze()}:artifact:${a}`}function Xe(){let a=typeof process.env.EXECUTION_ID=="string"?process.env.EXECUTION_ID.trim():"";return a?{runId:a}:null}var x="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",_=2,N=0;function wt(){N=0}function j(a,n){return JSON.stringify({published:!1,stop:!0,attemptsUsed:_,defects:n||[],error:a,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(a,n,t){return JSON.stringify({published:!1,stop:!1,attemptsRemaining:_-N,defects:t||[],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."+(a==="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 ${x}.`:` Markdown supports ${x}; it does NOT support ${B}.`)})}var q="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 et(a){let n=new Set,t=(a||[]).map((r,i)=>{let s=r.line?` line ${r.line}:`:"",o="";return r.hint&&!n.has(r.code)&&(n.add(r.code),o=` \u2014 ${r.hint}`),`${i+1}. [${r.code}]${s} ${r.message}${o}`}),e=t.length;return`Artifact NOT published and NOTHING was sent \u2014 the page was checked against the \`data\` you passed and disagrees with it in ${e} place${e===1?"":"s"}:
|
|
2
2
|
${t.join(`
|
|
3
|
-
`)}`}function
|
|
3
|
+
`)}`}function tt(a,n){let t;try{t=K({html:a,data:n})}catch(i){return{unverified:{status:"unverified",reason:`the data\u2194render pre-flight threw and was skipped: ${String(i?.message||i).slice(0,200)}`,note:q}}}if(!t?.ok)return{rejected:{message:et(t?.defects),defects:t?.defects||[]}};let e=t.checked||{};return(Number(e.tables)||0)+(Number(e.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:e,note:q}}:null}function z(a,n,t){return N+=1,{__rejected:!0,payload:N>=_?j(n,t):Qe(a,n,t)}}async function nt(a){let n=I();if(!n)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let t=await fetch(`${C()}/credits/artifacts`,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify(a)});if(!t.ok){let e=await t.text().catch(()=>""),r=null;try{r=JSON.parse(e)}catch{}let i=new Error(t.status===422&&r?.error?r.error:`artifact write failed (${t.status}): ${e.slice(0,300)}`);throw i.status=t.status,Array.isArray(r?.defects)&&(i.defects=r.defects),i}return t.json()}async function X(a,n,t){let e=t?tt(t.html,t.data):null;if(e?.rejected)return z(n,e.rejected.message,e.rejected.defects);try{let r=await nt(a);return N=0,e?.unverified&&(r.__preflightNote=e.unverified),r}catch(r){if(r?.status!==422)throw r;return z(n,r.message,r.defects)}}async function se(a){let n=I();if(!n)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let t=await fetch(`${C()}/credits/artifacts/${encodeURIComponent(a)}`,{method:"GET",headers:{Authorization:`Bearer ${n}`}});if(!t.ok){let e=await t.text().catch(()=>"");throw new Error(`artifact get failed (${t.status}): ${e.slice(0,300)}`)}return t.json()}async function Q(a,n,t={}){let e=I();if(!e)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let r=await fetch(`${C()}/credits/artifacts/${encodeURIComponent(a)}/${n}`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){let i=await r.text().catch(()=>""),s=null;try{s=JSON.parse(i)}catch{}throw new Error(s?.error||`artifact ${n} failed (${r.status}): ${i.slice(0,300)}`)}return r.json()}var D=60;function ee(a,n){let t=Math.max(0,n-Math.floor(D/2)),r=a.slice(t,t+D).replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t");return(t>0?"\u2026":"")+r+(t+D<a.length?"\u2026":"")}function at(a,n){if(a===n)return null;let t=Math.min(a.length,n.length),e=0;for(;e<t&&a[e]===n[e];)e+=1;return{kind:e===t?n.length<a.length?"truncated":"longer":"diverged",sentChars:a.length,storedChars:n.length,delta:n.length-a.length,firstDiffAt:e,sentExcerpt:ee(a,e),storedExcerpt:ee(n,e)}}var $="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(a,n){let t;try{t=await se(a)}catch(i){return{status:"unverified",scope:$,reason:`read-back request failed: ${String(i?.message||i).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 e=t?.content;if(typeof e!="string")return{status:"unverified",scope:$,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(e===""&&n!=="")return{status:"unverified",scope:$,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 r=at(n,e);return r?{status:"mismatch",scope:$,...r,note:`The stored page source is NOT what was sent (${r.kind}: sent ${r.sentChars} chars, stored ${r.storedChars}, first difference at character ${r.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(a,n){let t=I();if(!t)return;let e=await fetch(`${C()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify({op:"store",scope:ie(a),content:JSON.stringify(n)})});if(!e.ok){let r=await e.text().catch(()=>"");throw new Error(`artifact index write failed (${e.status}): ${r.slice(0,200)}`)}}async function rt(a){let n=I();if(!n)return null;let t=await fetch(`${C()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify({op:"recall",scope:ie(a)})});if(!t.ok)return null;let e=await t.json().catch(()=>null);if(!e?.found||!e?.memory?.content)return null;try{return JSON.parse(e.memory.content)}catch{return null}}function ae(a){let n=typeof a?.html=="string"&&a.html.length>0,t=typeof a?.markdown=="string"&&a.markdown.length>0,e=typeof a?.text=="string"&&a.text.length>0;return e&&(n||t)?{error:"Pass exactly ONE of markdown, html or text \u2014 text is raw source stored as-is (code/logs/JSON/CSV); markdown and html are rendered documents."}:n&&t?{error:"Pass EITHER markdown OR html, not both \u2014 they are different rendering paths. Use markdown unless the page needs a custom visual layout."}:t?{format:"markdown",content:a.markdown}:n?{format:"html",content:a.html}:e?{format:"text",content:a.text}:null}function re(a,n){let t=a?.data;if(t==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 e=t;if(typeof e=="string")try{e=JSON.parse(e)}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:e}}}var kt={id:"artifact",callsBackend:!0,meta:Fe.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)
|
|
4
4
|
You can PUBLISH a standalone page \u2014 a status report, a plan, a comparison table,
|
|
5
5
|
a dashboard-y summary, a diagram, a "here's what I found" write-up \u2014 and get back
|
|
6
6
|
a shareable URL.
|
|
@@ -21,6 +21,23 @@ credentials), so on the html path keep ALL CSS/JS/images INLINE (inline
|
|
|
21
21
|
<style>/<script>, data: URIs) \u2014 every external URL is blocked and will simply not
|
|
22
22
|
load. NEVER escape the document: send real tags, not \`<div>\`.
|
|
23
23
|
|
|
24
|
+
### NAME IT FOR SOMEONE SCROLLING A LIST OF FIFTY, and CAPTION IT.
|
|
25
|
+
The \`title\` is read in a list next to everything else you and your teammates
|
|
26
|
+
have ever published, so it must say WHAT THIS ONE IS \u2014 the subject, not the
|
|
27
|
+
genre. "Vikunja label sort toggle \u2014 3 of 5 checks failed" survives that list;
|
|
28
|
+
"Exploration Report" does not, and three of those in a row are indistinguishable.
|
|
29
|
+
Name the specific thing: the ticket, the feature, the file, the finding.
|
|
30
|
+
(You do not have to make it unique \u2014 Zibby stamps the run that produced each
|
|
31
|
+
artifact and shows it beside the title, so two honest titles that happen to
|
|
32
|
+
match are still told apart. Your job is to be SPECIFIC, not to be unique.)
|
|
33
|
+
|
|
34
|
+
Then pass \`description\`: a short Markdown write-up shown UNDER the artifact on
|
|
35
|
+
its page. A recording or a screenshot cannot explain itself \u2014 without this, the
|
|
36
|
+
whole page says \`video/webm \xB7 3.8 MB \xB7 Download\`. Say what it shows, what was
|
|
37
|
+
checked, what the reader should conclude, and anything surprising. Headings,
|
|
38
|
+
bullets and tables are all fine. It never appears in list views, so a real
|
|
39
|
+
description costs nothing. Write one for every artifact a human will open.
|
|
40
|
+
|
|
24
41
|
### Every number on the page must come from a tool result or this transcript.
|
|
25
42
|
Do not do arithmetic in prose and then print the answer \u2014 that is how a page ends
|
|
26
43
|
up saying 42 in one place and 45 in another. If you need a total, compute it with
|
|
@@ -103,4 +120,4 @@ sharing it silently.
|
|
|
103
120
|
To recall WHAT YOU HAVE ALREADY PUBLISHED, use your kv-memory tool
|
|
104
121
|
kv_recall_prefix with keyPrefix "artifact:" \u2014 each entry is the index record
|
|
105
122
|
{ id, title, url, kind, createdAt, summary } for a page you made. (Publishing
|
|
106
|
-
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 t of["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_USER_TOKEN","WORKFLOW_TYPE"])process.env[t]&&(n[t]=process.env[t]);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(N>=A)return D("Publish budget already spent this turn.",[]);let t=typeof n?.title=="string"?n.title.trim():"";if(!t)return JSON.stringify({error:"title is required"});let e=re(n);if(!e)return JSON.stringify({error:"provide exactly one of markdown (preferred) or html or text (non-empty string)"});if(e.error)return JSON.stringify({error:e.error});let a=ae(n,e);if(a?.error)return JSON.stringify({error:a.error});if(e.format==="text"&&!(typeof n?.contentType=="string"?n.contentType.trim():""))return JSON.stringify({error:"contentType is required with text \u2014 text/plain for code/logs, application/json for JSON data, text/csv for CSV"});let s={title:t,[e.format]:e.content};e.format==="text"&&(s.contentType=n.contentType.trim()),typeof n?.kind=="string"&&n.kind.trim()&&(s.kind=n.kind.trim()),typeof n?.favicon=="string"&&n.favicon.trim()&&(s.favicon=n.favicon.trim()),n?.share===!0&&(s.share=!0);let o=await X(s,e.format,a?.preflight);if(o?.__rejected)return o.payload;let i=o?.id,c=o?.url;if(!i||!c)return JSON.stringify({error:"artifact write returned no id/url",response:o});let l={id:i,url:c};typeof o.rn=="string"&&o.rn&&(l.rn=o.rn),o.shared&&(l.shared=!0,l.shareUrl=o.shareUrl,l.password=o.password,l.instruction="Send BOTH the shareUrl and the password in your reply. Copy the password EXACTLY \u2014 it is case-sensitive, shown once, and cannot be looked up again (artifact_share issues a new one, which kills this one)."),o.__preflightNote&&(l.preflight=o.__preflightNote);let u=await te(i,e.content);u&&(l.readBack=u);let d={id:i,title:t,url:c,kind:s.kind||null,createdAt:o.createdAt||new Date().toISOString(),summary:typeof n?.summary=="string"&&n.summary.trim()?n.summary.trim():t};e.format==="text"?(d.contentType=typeof o.contentType=="string"?o.contentType:s.contentType,d.bytes=typeof o.bytes=="number"?o.bytes:Buffer.byteLength(e.content,"utf8")):(d.format=e.format,d.bytes=Buffer.byteLength(e.content,"utf8"));try{await ne(i,d)}catch(h){l.indexWarning=h.message}return JSON.stringify(l)}case"artifact_update":{if(N>=A)return D("Publish budget already spent this turn.",[]);let t=typeof n?.id=="string"?n.id.trim():"";if(!t)return JSON.stringify({error:"id is required"});let e=re(n);if(e?.error)return JSON.stringify({error:e.error});let a=typeof n?.title=="string"?n.title.trim():"";if(!e&&!a)return JSON.stringify({error:"nothing to update \u2014 pass a new title and/or markdown|html"});let s=ae(n,e);if(s?.error)return JSON.stringify({error:s.error});let o={id:t};a&&(o.title=a),e&&(o[e.format]=e.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,e?.format,s?.preflight);if(i?.__rejected)return i.payload;let c=i?.url;if(!c)return JSON.stringify({error:"artifact update returned no url",response:i});let l={id:t,url:c};if(typeof i.rn=="string"&&i.rn&&(l.rn=i.rn),i.__preflightNote&&(l.preflight=i.__preflightNote),e){let h=await te(t,e.content);h&&(l.readBack=h)}let u=await rt(t)||{},d={...u,id:t,url:c,title:a||u.title||"Untitled",kind:o.kind||u.kind||null,createdAt:u.createdAt||i.createdAt||new Date().toISOString(),updatedAt:i.updatedAt||new Date().toISOString()};e&&(e.format==="text"?(d.contentType=typeof i.contentType=="string"?i.contentType:u.contentType||null,d.bytes=typeof i.bytes=="number"?i.bytes:Buffer.byteLength(e.content,"utf8")):(d.format=e.format,d.bytes=Buffer.byteLength(e.content,"utf8"))),typeof n?.summary=="string"&&n.summary.trim()?d.summary=n.summary.trim():d.summary||(d.summary=d.title);try{await ne(t,d)}catch(h){l.indexWarning=h.message}return JSON.stringify(l)}case"artifact_get":{let t=typeof n?.id=="string"?n.id.trim():"";if(!t)return JSON.stringify({error:"id is required"});let e=await se(t);return JSON.stringify(e)}case"artifact_share":{let t=typeof n?.id=="string"?n.id.trim():"";if(!t)return JSON.stringify({error:"id is required"});let e=await Q(t,"protect");return JSON.stringify({id:e.id,url:e.url,password:e.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 t=typeof n?.id=="string"?n.id.trim():"";if(!t)return JSON.stringify({error:"id is required"});let e=await Q(t,"unprotect");return JSON.stringify({id:e.id,shared:!1,wasShared:e.wasShared})}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(t){return JSON.stringify({error:t.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 ${x}; 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. Use \`text\` (+ required \`contentType\`) to publish RAW SOURCE as-is \u2014 code, a script, logs, JSON data, CSV. It is stored verbatim, never rendered as a page, and shown in a read-only code viewer with a download link. contentType: text/plain for code/logs/scripts, application/json for JSON data, text/csv for CSV. For code, put the FILENAME in \`title\` (e.g. "deploy.sh") \u2014 viewers derive the language label from the extension \u2014 and set kind: "code". Exactly one of markdown|html|text per call. 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 }. PASS \`share: true\` WHEN THE READERS HAVE NO ZIBBY ACCOUNT \u2014 you get back { shareUrl, password } in this same call, instead of publishing a link they cannot open and then having to call artifact_share. The password is GENERATED; you never invent one. Without \`share\` the page is member-only, which is the default and is correct for anything internal.`,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 ${x}. 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."},text:{type:"string",description:'RAW SOURCE published as-is \u2014 code, a script, logs, JSON data, CSV. Stored verbatim (never rendered as an HTML page) and shown in a read-only code viewer with a download link. Requires `contentType`. For code, make `title` the filename (e.g. "deploy.sh" \u2014 the viewer derives the language from the extension) and set kind: "code". Provide this OR markdown OR html.'},contentType:{type:"string",description:"REQUIRED with `text`, meaningless otherwise. One of: text/plain (code, scripts, logs), application/json (JSON data), text/csv (CSV). Nothing else is accepted \u2014 HTML and Markdown are published through their own fields, never as text."},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."},share:{type:"boolean",description:"Set true to publish the page WITH a password link in one step \u2014 the response then also carries { shareUrl, password }, which you send together. Use it whenever the people who asked are in a chat channel and have no Zibby account. Omit it (the default) for anything internal: the page is then readable only by logged-in members of this project, and no password exists."}},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|text). `text` only replaces an artifact that was PUBLISHED as text, and it keeps its original contentType \u2014 the server refuses a type change and any doc\u2194text conversion. 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 ${x}. 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."},text:{type:"string",description:"New raw text content (optional) \u2014 only for an artifact that was PUBLISHED as text. It keeps its original contentType; the server refuses a type change or a doc\u2194text conversion. Provide at most one of markdown|html|text."},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:`REMOVE an artifact's password \u2014 this is the tool for "take the password off", "remove the password", "stop sharing" and "revoke the link", which are all the same act. The page goes back to the DEFAULT it was published with: viewable only by logged-in members of this project, with no password anywhere. 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. Note what this does NOT do: there is no way to leave a page open to anyone with the link and no password. Removing the password closes the anonymous door; it does not prop it open. Say that plainly if you are asked for a passwordless public link.`,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};
|
|
123
|
+
records this automatically; you don't store it yourself.)`,resolve(){let a=qe();if(!a)return{command:null,args:[],env:{},description:this.description};let n={};for(let t of["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_USER_TOKEN","WORKFLOW_TYPE"])process.env[t]&&(n[t]=process.env[t]);return{type:"stdio",command:"node",args:[a,"../dist/artifact.js","artifactSkill"],env:n,description:this.description,alwaysLoad:!0}},async handleToolCall(a,n){try{switch(a){case"artifact_publish":{if(N>=_)return j("Publish budget already spent this turn.",[]);let t=typeof n?.title=="string"?n.title.trim():"";if(!t)return JSON.stringify({error:"title is required"});let e=ae(n);if(!e)return JSON.stringify({error:"provide exactly one of markdown (preferred) or html or text (non-empty string)"});if(e.error)return JSON.stringify({error:e.error});let r=re(n,e);if(r?.error)return JSON.stringify({error:r.error});if(e.format==="text"&&!(typeof n?.contentType=="string"?n.contentType.trim():""))return JSON.stringify({error:"contentType is required with text \u2014 text/plain for code/logs, application/json for JSON data, text/csv for CSV"});let i={title:t,[e.format]:e.content};e.format==="text"&&(i.contentType=n.contentType.trim()),typeof n?.kind=="string"&&n.kind.trim()&&(i.kind=n.kind.trim()),typeof n?.favicon=="string"&&n.favicon.trim()&&(i.favicon=n.favicon.trim()),typeof n?.description=="string"&&n.description.trim()&&(i.description=n.description);let s=Xe();s&&(i.context=s),n?.share===!0&&(i.share=!0);let o=await X(i,e.format,r?.preflight);if(o?.__rejected)return o.payload;let l=o?.id,c=o?.url;if(!l||!c)return JSON.stringify({error:"artifact write returned no id/url",response:o});let h={id:l,url:c};typeof o.rn=="string"&&o.rn&&(h.rn=o.rn),o.shared&&(h.shared=!0,h.shareUrl=o.shareUrl,h.password=o.password,h.instruction="Send BOTH the shareUrl and the password in your reply. Copy the password EXACTLY \u2014 it is case-sensitive, shown once, and cannot be looked up again (artifact_share issues a new one, which kills this one)."),o.__preflightNote&&(h.preflight=o.__preflightNote);let u=await te(l,e.content);u&&(h.readBack=u);let d={id:l,title:t,url:c,kind:i.kind||null,createdAt:o.createdAt||new Date().toISOString(),summary:typeof n?.summary=="string"&&n.summary.trim()?n.summary.trim():t};o.context&&typeof o.context=="object"&&(d.context=o.context),e.format==="text"?(d.contentType=typeof o.contentType=="string"?o.contentType:i.contentType,d.bytes=typeof o.bytes=="number"?o.bytes:Buffer.byteLength(e.content,"utf8")):(d.format=e.format,d.bytes=Buffer.byteLength(e.content,"utf8"));try{await ne(l,d)}catch(p){h.indexWarning=p.message}return JSON.stringify(h)}case"artifact_update":{if(N>=_)return j("Publish budget already spent this turn.",[]);let t=typeof n?.id=="string"?n.id.trim():"";if(!t)return JSON.stringify({error:"id is required"});let e=ae(n);if(e?.error)return JSON.stringify({error:e.error});let r=typeof n?.title=="string"?n.title.trim():"",i=typeof n?.description=="string"&&n.description.trim();if(!e&&!r&&!i)return JSON.stringify({error:"nothing to update \u2014 pass a new title, description, and/or markdown|html"});let s=re(n,e);if(s?.error)return JSON.stringify({error:s.error});let o={id:t};r&&(o.title=r),e&&(o[e.format]=e.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()),i&&(o.description=n.description);let l=await X(o,e?.format,s?.preflight);if(l?.__rejected)return l.payload;let c=l?.url;if(!c)return JSON.stringify({error:"artifact update returned no url",response:l});let h={id:t,url:c};if(typeof l.rn=="string"&&l.rn&&(h.rn=l.rn),l.__preflightNote&&(h.preflight=l.__preflightNote),e){let p=await te(t,e.content);p&&(h.readBack=p)}let u=await rt(t)||{},d={...u,id:t,url:c,title:r||u.title||"Untitled",kind:o.kind||u.kind||null,createdAt:u.createdAt||l.createdAt||new Date().toISOString(),updatedAt:l.updatedAt||new Date().toISOString()};e&&(e.format==="text"?(d.contentType=typeof l.contentType=="string"?l.contentType:u.contentType||null,d.bytes=typeof l.bytes=="number"?l.bytes:Buffer.byteLength(e.content,"utf8")):(d.format=e.format,d.bytes=Buffer.byteLength(e.content,"utf8"))),typeof n?.summary=="string"&&n.summary.trim()?d.summary=n.summary.trim():d.summary||(d.summary=d.title);try{await ne(t,d)}catch(p){h.indexWarning=p.message}return JSON.stringify(h)}case"artifact_get":{let t=typeof n?.id=="string"?n.id.trim():"";if(!t)return JSON.stringify({error:"id is required"});let e=await se(t);return JSON.stringify(e)}case"artifact_share":{let t=typeof n?.id=="string"?n.id.trim():"";if(!t)return JSON.stringify({error:"id is required"});let e=await Q(t,"protect");return JSON.stringify({id:e.id,url:e.url,password:e.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 t=typeof n?.id=="string"?n.id.trim():"";if(!t)return JSON.stringify({error:"id is required"});let e=await Q(t,"unprotect");return JSON.stringify({id:e.id,shared:!1,wasShared:e.wasShared})}default:return JSON.stringify({error:`Unknown tool: ${a}`})}}catch(t){return JSON.stringify({error:t.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 ${x}; 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. Use \`text\` (+ required \`contentType\`) to publish RAW SOURCE as-is \u2014 code, a script, logs, JSON data, CSV. It is stored verbatim, never rendered as a page, and shown in a read-only code viewer with a download link. contentType: text/plain for code/logs/scripts, application/json for JSON data, text/csv for CSV. For code, put the FILENAME in \`title\` (e.g. "deploy.sh") \u2014 viewers derive the language label from the extension \u2014 and set kind: "code". Exactly one of markdown|html|text per call. 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 }. PASS \`share: true\` WHEN THE READERS HAVE NO ZIBBY ACCOUNT \u2014 you get back { shareUrl, password } in this same call, instead of publishing a link they cannot open and then having to call artifact_share. The password is GENERATED; you never invent one. Without \`share\` the page is member-only, which is the default and is correct for anything internal.`,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 ${x}. 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."},text:{type:"string",description:'RAW SOURCE published as-is \u2014 code, a script, logs, JSON data, CSV. Stored verbatim (never rendered as an HTML page) and shown in a read-only code viewer with a download link. Requires `contentType`. For code, make `title` the filename (e.g. "deploy.sh" \u2014 the viewer derives the language from the extension) and set kind: "code". Provide this OR markdown OR html.'},contentType:{type:"string",description:"REQUIRED with `text`, meaningless otherwise. One of: text/plain (code, scripts, logs), application/json (JSON data), text/csv (CSV). Nothing else is accepted \u2014 HTML and Markdown are published through their own fields, never as text."},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."},description:{type:"string",description:"Optional Markdown note ABOUT this artifact, shown UNDERNEATH it on its detail page \u2014 what it shows, what was checked, what the reader should conclude, anything surprising. This is the artifact's caption, NOT its content: the page/file itself is the content, and a reader who opens it should be able to understand what they are looking at without asking you. Write it as a short report with headings, bullets and tables (same Markdown subset as the `markdown` field, 16 KB max). It is NOT shown in list views, so it costs nothing to write a real one."},share:{type:"boolean",description:"Set true to publish the page WITH a password link in one step \u2014 the response then also carries { shareUrl, password }, which you send together. Use it whenever the people who asked are in a chat channel and have no Zibby account. Omit it (the default) for anything internal: the page is then readable only by logged-in members of this project, and no password exists."}},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|text). `text` only replaces an artifact that was PUBLISHED as text, and it keeps its original contentType \u2014 the server refuses a type change and any doc\u2194text conversion. 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 ${x}. 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."},text:{type:"string",description:"New raw text content (optional) \u2014 only for an artifact that was PUBLISHED as text. It keeps its original contentType; the server refuses a type change or a doc\u2194text conversion. Provide at most one of markdown|html|text."},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."},description:{type:"string",description:"Optional updated Markdown note shown under the artifact on its detail page. You may pass this ALONE to add or rewrite the explanation without touching the artifact itself \u2014 the content keeps its current version."}},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:`REMOVE an artifact's password \u2014 this is the tool for "take the password off", "remove the password", "stop sharing" and "revoke the link", which are all the same act. The page goes back to the DEFAULT it was published with: viewable only by logged-in members of this project, with no password anywhere. 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. Note what this does NOT do: there is no way to leave a page open to anyone with the link and no password. Removing the password closes the anonymous door; it does not prop it open. Say that plainly if you are asked for a passwordless public link.`,input_schema:{type:"object",properties:{id:{type:"string",description:"The artifact id to stop sharing."}},required:["id"]}}]};export{at as __compareStoredSource,wt as __resetPublishBudget,kt as artifactSkill};
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{createRequire as
|
|
1
|
+
import{createRequire as h}from"module";import{join as u}from"path";var c=h(import.meta.url);function p(){if(process.env.MCP_BROWSER_PATH)return process.env.MCP_BROWSER_PATH;try{return c.resolve("@zibby/mcp-browser/dist/bin/mcp-browser-zibby.js")}catch{return null}}var v="1280x720",d="1280x720";function w({headless:e}={}){if(e===!0)return!0;if(e===!1)return!1;let t=process.env.ZIBBY_HEADLESS;return t==="1"||String(t).toLowerCase()==="true"}function b(e,t){let r=(e||[]).filter(o=>o!=="--headless");return t?[...r,"--headless"]:r}var f=`Execute this test using the browser tools available to you. You MUST make actual browser tool calls \u2014 do not fabricate results.
|
|
2
2
|
If you DO NOT have access to browser tools \u2192 return {"success": false, "steps": [], "browserClosed": false, "notes": "No browser tools available"}.
|
|
3
|
-
DO NOT return success: true unless you ACTUALLY called browser tools.`;function
|
|
3
|
+
DO NOT return success: true unless you ACTUALLY called browser tools.`;function E(){return!process.env.PREVIEW_BASE_URL||!process.env.PREVIEW_TOKEN?"":'\n\n## Previewing a server YOU start in this container (dev-server preview)\nThe browser runs in an ISOLATED container: localhost/private URLs are unreachable from it \u2014 NEVER navigate to http://localhost:<port>. To let the browser view a server you start in this run (npm run dev, vite, a mock API \u2014 anything HTTP):\n1. Start the server bound to 0.0.0.0 (e.g. `npm run dev -- --host` or `HOST=0.0.0.0`); a localhost-only bind is unreachable from outside this container.\n2. Build the preview URL from this run\'s injected env (Bash): `echo "$PREVIEW_BASE_URL/<port>/?pvt=$PREVIEW_TOKEN"` \u2014 substitute your server\'s actual port.\n3. Serve your app at its own ROOT and leave the URL prefix alone \u2014 do NOT set a base path for the preview (Vite `base`, Next.js `basePath`, CRA `PUBLIC_URL`). The platform routes this page\'s root-absolute requests (`/assets/\u2026`, `/@vite/client`, XHR) back to your server for you. Setting a base to the preview prefix makes the dev server redirect to it in a LOOP, and re-adding the prefix by hand is never needed.\n4. browser_navigate to that exact URL. The first load does a cookie handshake and redirects; after that, assets, XHR and hot-reload all work \u2014 screenshots and video too.\n5. THE PREVIEW URL IS THE DOOR, NOT A ROUTE. After that first load the browser sits on `.../preview/<id>/<port>/`, which is not a path your app has \u2014 a client-side router (Vue Router, React Router, Next\'s app router) matches nothing there and renders its own 404/"Not found", on a dev server that is working perfectly. To open a specific route, drop the preview prefix and navigate to it at the ORIGIN ROOT: `echo "${PREVIEW_BASE_URL%/preview/*}/<your/route>"` (e.g. `http://host:port/settings`). The handshake cookie routes it to your server with the path UNCHANGED, so the router sees `/settings` and renders it. Do NOT reach for the prefixed `.../preview/<id>/<port>/settings` \u2014 the proxy strips the prefix before your server sees it, but the BROWSER keeps it, which is exactly what the router chokes on.\nThe preview lives only as long as this run. Do not put PREVIEW_TOKEN anywhere except that URL.'}var O={id:"browser",serverName:"playwright",cursorKey:"playwright-official",allowedTools:["mcp__playwright__*"],sessionEnvKey:"ZIBBY_SESSION_INFO",description:"Playwright Browser MCP Server",envKeys:[],tools:[],promptFragment:()=>f+E(),resolve({sessionPath:e,workspace:t,nodeName:r,headless:o}={}){let a=p(),s=e&&r?u(e,r):null,n=s||e||t||"test-results",l=w({headless:o}),i={};if(s&&(i.ZIBBY_NODE_SESSION_PATH=s),e&&(i.ZIBBY_SESSION_PATH=e),!a)throw new Error(`@zibby/mcp-browser is not installed.
|
|
4
4
|
Cloud: verify the Fargate image has it (packages/Dockerfile installs it globally alongside @zibby/cli).
|
|
5
5
|
Local: \`npm install @zibby/mcp-browser\` in your workflow, or use the global @zibby/cli install (which pulls it transitively).
|
|
6
|
-
Override: set MCP_BROWSER_PATH to the path of mcp-browser-zibby.js.`);return{command:"node",args:
|
|
6
|
+
Override: set MCP_BROWSER_PATH to the path of mcp-browser-zibby.js.`);return{command:"node",args:b([a,"--isolated",`--save-video=${v}`,`--viewport-size=${d}`,`--output-dir=${n}`],l),env:i}}};export{O as browserSkill,E as devServerPreviewRecipe};
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{registerSkill as
|
|
1
|
+
import{registerSkill as dg}from"@zibby/agent-workflow";var Rn=Object.freeze(["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV"]),Rt=new WeakMap;function En(r){if(!r||r.callsBackend!==!0)return r;if(Rt.has(r))return Rt.get(r);let t=Object.isExtensible(r)&&!Object.isFrozen(r)?r:{...r};if(Rt.set(r,t),Rt.set(t,t),t.command||Array.isArray(t.envKeys)){let e=Array.isArray(t.envKeys)?[...t.envKeys]:[];for(let n of Rn)e.includes(n)||e.push(n);t.envKeys=e}if(typeof t.resolve=="function"){let e=t.resolve;t.resolve=function(...s){let i=e.apply(this,s);if(!i||!i.command)return i;let o={...i.env||{}};for(let a of Rn)o[a]===void 0&&process.env[a]&&(o[a]=process.env[a]);return{...i,env:o}}}return t}import{createRequire as va}from"module";import{join as Ia}from"path";var Na=va(import.meta.url);function Oa(){if(process.env.MCP_BROWSER_PATH)return process.env.MCP_BROWSER_PATH;try{return Na.resolve("@zibby/mcp-browser/dist/bin/mcp-browser-zibby.js")}catch{return null}}var Ta="1280x720",Aa="1280x720";function Ra({headless:r}={}){if(r===!0)return!0;if(r===!1)return!1;let t=process.env.ZIBBY_HEADLESS;return t==="1"||String(t).toLowerCase()==="true"}function Ea(r,t){let e=(r||[]).filter(n=>n!=="--headless");return t?[...e,"--headless"]:e}var xa=`Execute this test using the browser tools available to you. You MUST make actual browser tool calls \u2014 do not fabricate results.
|
|
2
2
|
If you DO NOT have access to browser tools \u2192 return {"success": false, "steps": [], "browserClosed": false, "notes": "No browser tools available"}.
|
|
3
|
-
DO NOT return success: true unless you ACTUALLY called browser tools.`;function xn(){return!process.env.PREVIEW_BASE_URL||!process.env.PREVIEW_TOKEN?"":
|
|
3
|
+
DO NOT return success: true unless you ACTUALLY called browser tools.`;function xn(){return!process.env.PREVIEW_BASE_URL||!process.env.PREVIEW_TOKEN?"":'\n\n## Previewing a server YOU start in this container (dev-server preview)\nThe browser runs in an ISOLATED container: localhost/private URLs are unreachable from it \u2014 NEVER navigate to http://localhost:<port>. To let the browser view a server you start in this run (npm run dev, vite, a mock API \u2014 anything HTTP):\n1. Start the server bound to 0.0.0.0 (e.g. `npm run dev -- --host` or `HOST=0.0.0.0`); a localhost-only bind is unreachable from outside this container.\n2. Build the preview URL from this run\'s injected env (Bash): `echo "$PREVIEW_BASE_URL/<port>/?pvt=$PREVIEW_TOKEN"` \u2014 substitute your server\'s actual port.\n3. Serve your app at its own ROOT and leave the URL prefix alone \u2014 do NOT set a base path for the preview (Vite `base`, Next.js `basePath`, CRA `PUBLIC_URL`). The platform routes this page\'s root-absolute requests (`/assets/\u2026`, `/@vite/client`, XHR) back to your server for you. Setting a base to the preview prefix makes the dev server redirect to it in a LOOP, and re-adding the prefix by hand is never needed.\n4. browser_navigate to that exact URL. The first load does a cookie handshake and redirects; after that, assets, XHR and hot-reload all work \u2014 screenshots and video too.\n5. THE PREVIEW URL IS THE DOOR, NOT A ROUTE. After that first load the browser sits on `.../preview/<id>/<port>/`, which is not a path your app has \u2014 a client-side router (Vue Router, React Router, Next\'s app router) matches nothing there and renders its own 404/"Not found", on a dev server that is working perfectly. To open a specific route, drop the preview prefix and navigate to it at the ORIGIN ROOT: `echo "${PREVIEW_BASE_URL%/preview/*}/<your/route>"` (e.g. `http://host:port/settings`). The handshake cookie routes it to your server with the path UNCHANGED, so the router sees `/settings` and renders it. Do NOT reach for the prefixed `.../preview/<id>/<port>/settings` \u2014 the proxy strips the prefix before your server sees it, but the BROWSER keeps it, which is exactly what the router chokes on.\nThe preview lives only as long as this run. Do not put PREVIEW_TOKEN anywhere except that URL.'}var Ln={id:"browser",serverName:"playwright",cursorKey:"playwright-official",allowedTools:["mcp__playwright__*"],sessionEnvKey:"ZIBBY_SESSION_INFO",description:"Playwright Browser MCP Server",envKeys:[],tools:[],promptFragment:()=>xa+xn(),resolve({sessionPath:r,workspace:t,nodeName:e,headless:n}={}){let s=Oa(),i=r&&e?Ia(r,e):null,o=i||r||t||"test-results",a=Ra({headless:n}),c={};if(i&&(c.ZIBBY_NODE_SESSION_PATH=i),r&&(c.ZIBBY_SESSION_PATH=r),!s)throw new Error(`@zibby/mcp-browser is not installed.
|
|
4
4
|
Cloud: verify the Fargate image has it (packages/Dockerfile installs it globally alongside @zibby/cli).
|
|
5
5
|
Local: \`npm install @zibby/mcp-browser\` in your workflow, or use the global @zibby/cli install (which pulls it transitively).
|
|
6
6
|
Override: set MCP_BROWSER_PATH to the path of mcp-browser-zibby.js.`);return{command:"node",args:Ea([s,"--isolated",`--save-video=${Ta}`,`--viewport-size=${Aa}`,`--output-dir=${o}`],a),env:c}}};import{createRequire as ja}from"module";import{resolveIntegrationToken as $a,clearTokenCache as Pa}from"@zibby/core/backend-client.js";var S=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",LARK_DOCS:"lark_docs",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",VIKUNJA:"vikunja",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),La=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark Chat",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark App",connectPath:"/integrations?provider=lark_docs"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},vikunja:{id:"vikunja",name:"Vikunja",connectPath:"/integrations?provider=vikunja"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});var Ca=ja(import.meta.url);function Ua(){if(process.env.MCP_JIRA_PATH)return process.env.MCP_JIRA_PATH;try{return Ca.resolve("@zibby/mcp-jira/index.js")}catch{return null}}var Ba=new Set(["paragraph","heading","bulletList","orderedList","listItem","blockquote","codeBlock","rule","table","tableRow","tableCell","tableHeader","mediaSingle","panel"]);function Da(r,t){if(!t||!t.length)return r;let e=r;for(let n of t)n.type==="strong"?e=`**${e}**`:n.type==="em"?e=`_${e}_`:n.type==="code"?e=`\`${e}\``:n.type==="strike"?e=`~~${e}~~`:n.type==="link"&&n.attrs?.href&&(e=`[${e}](${n.attrs.href})`);return e}function Et(r,t=0){if(!Array.isArray(r))return"";let e=[];for(let n of r){if(n.type==="text"){e.push(Da(n.text||"",n.marks));continue}if(n.type==="hardBreak"){e.push(`
|
|
@@ -806,9 +806,9 @@ from the data by itself.
|
|
|
806
806
|
|
|
807
807
|
Then READ the \`checked\` block in the result: it says how many tables, rows and
|
|
808
808
|
bars were actually found. If it reports 0 bars for a page full of bars, the check
|
|
809
|
-
never saw your chart and a clean verdict means nothing.`,resolve(){let r=Km();return r?{type:"stdio",command:"node",args:[r,"../dist/reportCheck.js","reportCheckSkill"],env:{},description:this.description,alwaysLoad:!0}:{command:null,args:[],env:{},description:this.description}},async handleToolCall(r,t){if(r!=="report_check")return JSON.stringify({error:`Unknown tool: ${r}`});try{let e=typeof t?.html=="string"?t.html:"";if(!e.trim())return JSON.stringify({error:"html is required \u2014 pass the exact page source you are about to publish."});let n=t?.data;if(typeof n=="string")try{n=JSON.parse(n)}catch{return JSON.stringify({error:"data was a string but not valid JSON \u2014 pass the data as a JSON object."})}return n==null?JSON.stringify({error:"data is required \u2014 pass the tool results the page was built from, unchanged."}):JSON.stringify(dr({html:e,data:n}))}catch(e){return JSON.stringify({error:`report_check failed: ${e.message}`})}},tools:[{name:"report_check",description:'Pre-flight a data page BEFORE publishing it: compares the rendered HTML against the DATA it was built from and reports every place they disagree. Catches what the publish validator structurally cannot \u2014 a table that renders fewer rows than the data has records, a bar drawn at a length that is not proportional to its value, a number printed beside a bar that is not that bar\'s datum, a bar whose width is silently nullified by the CSS cascade, and placeholder residue ([object Object], NaN, an un-substituted ${\u2026}) left in visible text. Pass the data UNCHANGED, exactly as your tools returned it (wrap a bare array as {"rows": [...]}) \u2014 the check discovers the collections and series inside it and derives what must be true on its own. Do NOT write your own expectations. Returns { ok, defects:[{code,line,message,hint}], checked }. Always read `checked`: it reports how many tables, rows and bars were found, so a clean `ok:true` with `bars: 0` on a chart page tells you the check never saw your chart.',input_schema:{type:"object",properties:{html:{type:"string",description:"The exact HTML you are about to publish (the same string you would pass to artifact_publish)."},data:{type:"object",description:'The data the page was built from, as JSON \u2014 the raw tool results. Any shape: arrays of records and label/value series are found automatically. Wrap a bare top-level array as {"rows": [...]}.'}},required:["html","data"]}}]};function Xm(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=Wm(Vm(import.meta.url)),t=Zm(r,"..","bin","mcp-skill.mjs");return uo(t)?t:null}function wt(){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=zm(Ym(),".zibby","config.json");return uo(r)&&JSON.parse(Hm(r,"utf-8")).sessionToken||null}catch{return null}}function St(){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 Qm(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function mo(r){return`${Qm()}:artifact:${r}`}var We="headings, paragraphs, **bold**, *italic*, `inline code`, fenced code blocks, bullet + numbered lists, blockquotes, horizontal rules, links, and GitHub-style pipe tables (including |---:| alignment)",ur="raw HTML (tags are escaped and print as literal text), images, nested lists, footnotes and strikethrough",kt=2,Ze=0;function on(r,t){return JSON.stringify({published:!1,stop:!0,attemptsUsed:kt,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
|
|
809
|
+
never saw your chart and a clean verdict means nothing.`,resolve(){let r=Km();return r?{type:"stdio",command:"node",args:[r,"../dist/reportCheck.js","reportCheckSkill"],env:{},description:this.description,alwaysLoad:!0}:{command:null,args:[],env:{},description:this.description}},async handleToolCall(r,t){if(r!=="report_check")return JSON.stringify({error:`Unknown tool: ${r}`});try{let e=typeof t?.html=="string"?t.html:"";if(!e.trim())return JSON.stringify({error:"html is required \u2014 pass the exact page source you are about to publish."});let n=t?.data;if(typeof n=="string")try{n=JSON.parse(n)}catch{return JSON.stringify({error:"data was a string but not valid JSON \u2014 pass the data as a JSON object."})}return n==null?JSON.stringify({error:"data is required \u2014 pass the tool results the page was built from, unchanged."}):JSON.stringify(dr({html:e,data:n}))}catch(e){return JSON.stringify({error:`report_check failed: ${e.message}`})}},tools:[{name:"report_check",description:'Pre-flight a data page BEFORE publishing it: compares the rendered HTML against the DATA it was built from and reports every place they disagree. Catches what the publish validator structurally cannot \u2014 a table that renders fewer rows than the data has records, a bar drawn at a length that is not proportional to its value, a number printed beside a bar that is not that bar\'s datum, a bar whose width is silently nullified by the CSS cascade, and placeholder residue ([object Object], NaN, an un-substituted ${\u2026}) left in visible text. Pass the data UNCHANGED, exactly as your tools returned it (wrap a bare array as {"rows": [...]}) \u2014 the check discovers the collections and series inside it and derives what must be true on its own. Do NOT write your own expectations. Returns { ok, defects:[{code,line,message,hint}], checked }. Always read `checked`: it reports how many tables, rows and bars were found, so a clean `ok:true` with `bars: 0` on a chart page tells you the check never saw your chart.',input_schema:{type:"object",properties:{html:{type:"string",description:"The exact HTML you are about to publish (the same string you would pass to artifact_publish)."},data:{type:"object",description:'The data the page was built from, as JSON \u2014 the raw tool results. Any shape: arrays of records and label/value series are found automatically. Wrap a bare top-level array as {"rows": [...]}.'}},required:["html","data"]}}]};function Xm(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=Wm(Vm(import.meta.url)),t=Zm(r,"..","bin","mcp-skill.mjs");return uo(t)?t:null}function wt(){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=zm(Ym(),".zibby","config.json");return uo(r)&&JSON.parse(Hm(r,"utf-8")).sessionToken||null}catch{return null}}function St(){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 Qm(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function mo(r){return`${Qm()}:artifact:${r}`}function eh(){let r=typeof process.env.EXECUTION_ID=="string"?process.env.EXECUTION_ID.trim():"";return r?{runId:r}:null}var We="headings, paragraphs, **bold**, *italic*, `inline code`, fenced code blocks, bullet + numbered lists, blockquotes, horizontal rules, links, and GitHub-style pipe tables (including |---:| alignment)",ur="raw HTML (tags are escaped and print as literal text), images, nested lists, footnotes and strikethrough",kt=2,Ze=0;function on(r,t){return JSON.stringify({published:!1,stop:!0,attemptsUsed:kt,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 th(r,t,e){return JSON.stringify({published:!1,stop:!1,attemptsRemaining:kt-Ze,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 ${We}.`:` Markdown supports ${We}; it does NOT support ${ur}.`)})}var ro="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 rh(r){let t=new Set,e=(r||[]).map((s,i)=>{let o=s.line?` line ${s.line}:`:"",a="";return s.hint&&!t.has(s.code)&&(t.add(s.code),a=` \u2014 ${s.hint}`),`${i+1}. [${s.code}]${o} ${s.message}${a}`}),n=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 ${n} place${n===1?"":"s"}:
|
|
810
810
|
${e.join(`
|
|
811
|
-
`)}`}function
|
|
811
|
+
`)}`}function nh(r,t){let e;try{e=dr({html:r,data:t})}catch(i){return{unverified:{status:"unverified",reason:`the data\u2194render pre-flight threw and was skipped: ${String(i?.message||i).slice(0,200)}`,note:ro}}}if(!e?.ok)return{rejected:{message:rh(e?.defects),defects:e?.defects||[]}};let n=e.checked||{};return(Number(n.tables)||0)+(Number(n.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:n,note:ro}}:null}function no(r,t,e){return Ze+=1,{__rejected:!0,payload:Ze>=kt?on(t,e):th(r,t,e)}}async function sh(r){let t=wt();if(!t)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${St()}/credits/artifacts`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify(r)});if(!e.ok){let n=await e.text().catch(()=>""),s=null;try{s=JSON.parse(n)}catch{}let i=new Error(e.status===422&&s?.error?s.error:`artifact write failed (${e.status}): ${n.slice(0,300)}`);throw i.status=e.status,Array.isArray(s?.defects)&&(i.defects=s.defects),i}return e.json()}async function so(r,t,e){let n=e?nh(e.html,e.data):null;if(n?.rejected)return no(t,n.rejected.message,n.rejected.defects);try{let s=await sh(r);return Ze=0,n?.unverified&&(s.__preflightNote=n.unverified),s}catch(s){if(s?.status!==422)throw s;return no(t,s.message,s.defects)}}async function ho(r){let t=wt();if(!t)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let e=await fetch(`${St()}/credits/artifacts/${encodeURIComponent(r)}`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});if(!e.ok){let n=await e.text().catch(()=>"");throw new Error(`artifact get failed (${e.status}): ${n.slice(0,300)}`)}return e.json()}async function io(r,t,e={}){let n=wt();if(!n)throw new Error("No backend credential (PROJECT_API_TOKEN). Artifacts are only available inside a Zibby run.");let s=await fetch(`${St()}/credits/artifacts/${encodeURIComponent(r)}/${t}`,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify(e)});if(!s.ok){let i=await s.text().catch(()=>""),o=null;try{o=JSON.parse(i)}catch{}throw new Error(o?.error||`artifact ${t} failed (${s.status}): ${i.slice(0,300)}`)}return s.json()}var sn=60;function oo(r,t){let e=Math.max(0,t-Math.floor(sn/2)),s=r.slice(e,e+sn).replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t");return(e>0?"\u2026":"")+s+(e+sn<r.length?"\u2026":"")}function ih(r,t){if(r===t)return null;let e=Math.min(r.length,t.length),n=0;for(;n<e&&r[n]===t[n];)n+=1;return{kind:n===e?t.length<r.length?"truncated":"longer":"diverged",sentChars:r.length,storedChars:t.length,delta:t.length-r.length,firstDiffAt:n,sentExcerpt:oo(r,n),storedExcerpt:oo(t,n)}}var pr="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 ao(r,t){let e;try{e=await ho(r)}catch(i){return{status:"unverified",scope:pr,reason:`read-back request failed: ${String(i?.message||i).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 n=e?.content;if(typeof n!="string")return{status:"unverified",scope:pr,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(n===""&&t!=="")return{status:"unverified",scope:pr,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 s=ih(t,n);return s?{status:"mismatch",scope:pr,...s,note:`The stored page source is NOT what was sent (${s.kind}: sent ${s.sentChars} chars, stored ${s.storedChars}, first difference at character ${s.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 co(r,t){let e=wt();if(!e)return;let n=await fetch(`${St()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({op:"store",scope:mo(r),content:JSON.stringify(t)})});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`artifact index write failed (${n.status}): ${s.slice(0,200)}`)}}async function oh(r){let t=wt();if(!t)return null;let e=await fetch(`${St()}/credits/review-memory`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify({op:"recall",scope:mo(r)})});if(!e.ok)return null;let n=await e.json().catch(()=>null);if(!n?.found||!n?.memory?.content)return null;try{return JSON.parse(n.memory.content)}catch{return null}}function lo(r){let t=typeof r?.html=="string"&&r.html.length>0,e=typeof r?.markdown=="string"&&r.markdown.length>0,n=typeof r?.text=="string"&&r.text.length>0;return n&&(t||e)?{error:"Pass exactly ONE of markdown, html or text \u2014 text is raw source stored as-is (code/logs/JSON/CSV); markdown and html are rendered documents."}: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}:n?{format:"text",content:r.text}:null}function po(r,t){let e=r?.data;if(e==null)return null;if(!t||t.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 n=e;if(typeof n=="string")try{n=JSON.parse(n)}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:t.content,data:n}}}var fo={id:"artifact",callsBackend:!0,meta:Gm.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)
|
|
812
812
|
You can PUBLISH a standalone page \u2014 a status report, a plan, a comparison table,
|
|
813
813
|
a dashboard-y summary, a diagram, a "here's what I found" write-up \u2014 and get back
|
|
814
814
|
a shareable URL.
|
|
@@ -829,6 +829,23 @@ credentials), so on the html path keep ALL CSS/JS/images INLINE (inline
|
|
|
829
829
|
<style>/<script>, data: URIs) \u2014 every external URL is blocked and will simply not
|
|
830
830
|
load. NEVER escape the document: send real tags, not \`<div>\`.
|
|
831
831
|
|
|
832
|
+
### NAME IT FOR SOMEONE SCROLLING A LIST OF FIFTY, and CAPTION IT.
|
|
833
|
+
The \`title\` is read in a list next to everything else you and your teammates
|
|
834
|
+
have ever published, so it must say WHAT THIS ONE IS \u2014 the subject, not the
|
|
835
|
+
genre. "Vikunja label sort toggle \u2014 3 of 5 checks failed" survives that list;
|
|
836
|
+
"Exploration Report" does not, and three of those in a row are indistinguishable.
|
|
837
|
+
Name the specific thing: the ticket, the feature, the file, the finding.
|
|
838
|
+
(You do not have to make it unique \u2014 Zibby stamps the run that produced each
|
|
839
|
+
artifact and shows it beside the title, so two honest titles that happen to
|
|
840
|
+
match are still told apart. Your job is to be SPECIFIC, not to be unique.)
|
|
841
|
+
|
|
842
|
+
Then pass \`description\`: a short Markdown write-up shown UNDER the artifact on
|
|
843
|
+
its page. A recording or a screenshot cannot explain itself \u2014 without this, the
|
|
844
|
+
whole page says \`video/webm \xB7 3.8 MB \xB7 Download\`. Say what it shows, what was
|
|
845
|
+
checked, what the reader should conclude, and anything surprising. Headings,
|
|
846
|
+
bullets and tables are all fine. It never appears in list views, so a real
|
|
847
|
+
description costs nothing. Write one for every artifact a human will open.
|
|
848
|
+
|
|
832
849
|
### Every number on the page must come from a tool result or this transcript.
|
|
833
850
|
Do not do arithmetic in prose and then print the answer \u2014 that is how a page ends
|
|
834
851
|
up saying 42 in one place and 45 in another. If you need a total, compute it with
|
|
@@ -911,7 +928,7 @@ sharing it silently.
|
|
|
911
928
|
To recall WHAT YOU HAVE ALREADY PUBLISHED, use your kv-memory tool
|
|
912
929
|
kv_recall_prefix with keyPrefix "artifact:" \u2014 each entry is the index record
|
|
913
930
|
{ id, title, url, kind, createdAt, summary } for a page you made. (Publishing
|
|
914
|
-
records this automatically; you don't store it yourself.)`,resolve(){let r=Xm();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(Ze>=kt)return on("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 n=lo(t);if(!n)return JSON.stringify({error:"provide exactly one of markdown (preferred) or html or text (non-empty string)"});if(n.error)return JSON.stringify({error:n.error});let s=po(t,n);if(s?.error)return JSON.stringify({error:s.error});if(n.format==="text"&&!(typeof t?.contentType=="string"?t.contentType.trim():""))return JSON.stringify({error:"contentType is required with text \u2014 text/plain for code/logs, application/json for JSON data, text/csv for CSV"});let i={title:e,[n.format]:n.content};n.format==="text"&&(i.contentType=t.contentType.trim()),typeof t?.kind=="string"&&t.kind.trim()&&(i.kind=t.kind.trim()),typeof t?.favicon=="string"&&t.favicon.trim()&&(i.favicon=t.favicon.trim()),t?.share===!0&&(i.share=!0);let o=await so(i,n.format,s?.preflight);if(o?.__rejected)return o.payload;let a=o?.id,c=o?.url;if(!a||!c)return JSON.stringify({error:"artifact write returned no id/url",response:o});let l={id:a,url:c};typeof o.rn=="string"&&o.rn&&(l.rn=o.rn),o.shared&&(l.shared=!0,l.shareUrl=o.shareUrl,l.password=o.password,l.instruction="Send BOTH the shareUrl and the password in your reply. Copy the password EXACTLY \u2014 it is case-sensitive, shown once, and cannot be looked up again (artifact_share issues a new one, which kills this one)."),o.__preflightNote&&(l.preflight=o.__preflightNote);let d=await ao(a,n.content);d&&(l.readBack=d);let p={id:a,title:e,url:c,kind:i.kind||null,createdAt:o.createdAt||new Date().toISOString(),summary:typeof t?.summary=="string"&&t.summary.trim()?t.summary.trim():e};n.format==="text"?(p.contentType=typeof o.contentType=="string"?o.contentType:i.contentType,p.bytes=typeof o.bytes=="number"?o.bytes:Buffer.byteLength(n.content,"utf8")):(p.format=n.format,p.bytes=Buffer.byteLength(n.content,"utf8"));try{await co(a,p)}catch(u){l.indexWarning=u.message}return JSON.stringify(l)}case"artifact_update":{if(Ze>=kt)return on("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 n=lo(t);if(n?.error)return JSON.stringify({error:n.error});let s=typeof t?.title=="string"?t.title.trim():"";if(!n&&!s)return JSON.stringify({error:"nothing to update \u2014 pass a new title and/or markdown|html"});let i=po(t,n);if(i?.error)return JSON.stringify({error:i.error});let o={id:e};s&&(o.title=s),n&&(o[n.format]=n.content),typeof t?.kind=="string"&&t.kind.trim()&&(o.kind=t.kind.trim()),typeof t?.favicon=="string"&&t.favicon.trim()&&(o.favicon=t.favicon.trim());let a=await so(o,n?.format,i?.preflight);if(a?.__rejected)return a.payload;let c=a?.url;if(!c)return JSON.stringify({error:"artifact update returned no url",response:a});let l={id:e,url:c};if(typeof a.rn=="string"&&a.rn&&(l.rn=a.rn),a.__preflightNote&&(l.preflight=a.__preflightNote),n){let u=await ao(e,n.content);u&&(l.readBack=u)}let d=await ih(e)||{},p={...d,id:e,url:c,title:s||d.title||"Untitled",kind:o.kind||d.kind||null,createdAt:d.createdAt||a.createdAt||new Date().toISOString(),updatedAt:a.updatedAt||new Date().toISOString()};n&&(n.format==="text"?(p.contentType=typeof a.contentType=="string"?a.contentType:d.contentType||null,p.bytes=typeof a.bytes=="number"?a.bytes:Buffer.byteLength(n.content,"utf8")):(p.format=n.format,p.bytes=Buffer.byteLength(n.content,"utf8"))),typeof t?.summary=="string"&&t.summary.trim()?p.summary=t.summary.trim():p.summary||(p.summary=p.title);try{await co(e,p)}catch(u){l.indexWarning=u.message}return JSON.stringify(l)}case"artifact_get":{let e=typeof t?.id=="string"?t.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let n=await ho(e);return JSON.stringify(n)}case"artifact_share":{let e=typeof t?.id=="string"?t.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let n=await io(e,"protect");return JSON.stringify({id:n.id,url:n.url,password:n.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 t?.id=="string"?t.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let n=await io(e,"unprotect");return JSON.stringify({id:n.id,shared:!1,wasShared:n.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 ${We}; it does NOT support ${ur}. 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. Use \`text\` (+ required \`contentType\`) to publish RAW SOURCE as-is \u2014 code, a script, logs, JSON data, CSV. It is stored verbatim, never rendered as a page, and shown in a read-only code viewer with a download link. contentType: text/plain for code/logs/scripts, application/json for JSON data, text/csv for CSV. For code, put the FILENAME in \`title\` (e.g. "deploy.sh") \u2014 viewers derive the language label from the extension \u2014 and set kind: "code". Exactly one of markdown|html|text per call. 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 }. PASS \`share: true\` WHEN THE READERS HAVE NO ZIBBY ACCOUNT \u2014 you get back { shareUrl, password } in this same call, instead of publishing a link they cannot open and then having to call artifact_share. The password is GENERATED; you never invent one. Without \`share\` the page is member-only, which is the default and is correct for anything internal.`,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 ${We}. Does NOT support ${ur}. 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."},text:{type:"string",description:'RAW SOURCE published as-is \u2014 code, a script, logs, JSON data, CSV. Stored verbatim (never rendered as an HTML page) and shown in a read-only code viewer with a download link. Requires `contentType`. For code, make `title` the filename (e.g. "deploy.sh" \u2014 the viewer derives the language from the extension) and set kind: "code". Provide this OR markdown OR html.'},contentType:{type:"string",description:"REQUIRED with `text`, meaningless otherwise. One of: text/plain (code, scripts, logs), application/json (JSON data), text/csv (CSV). Nothing else is accepted \u2014 HTML and Markdown are published through their own fields, never as text."},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."},share:{type:"boolean",description:"Set true to publish the page WITH a password link in one step \u2014 the response then also carries { shareUrl, password }, which you send together. Use it whenever the people who asked are in a chat channel and have no Zibby account. Omit it (the default) for anything internal: the page is then readable only by logged-in members of this project, and no password exists."}},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|text). `text` only replaces an artifact that was PUBLISHED as text, and it keeps its original contentType \u2014 the server refuses a type change and any doc\u2194text conversion. 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 ${We}. 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."},text:{type:"string",description:"New raw text content (optional) \u2014 only for an artifact that was PUBLISHED as text. It keeps its original contentType; the server refuses a type change or a doc\u2194text conversion. Provide at most one of markdown|html|text."},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:`REMOVE an artifact's password \u2014 this is the tool for "take the password off", "remove the password", "stop sharing" and "revoke the link", which are all the same act. The page goes back to the DEFAULT it was published with: viewable only by logged-in members of this project, with no password anywhere. 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. Note what this does NOT do: there is no way to leave a page open to anyone with the link and no password. Removing the password closes the anonymous door; it does not prop it open. Say that plainly if you are asked for a passwordless public link.`,input_schema:{type:"object",properties:{id:{type:"string",description:"The artifact id to stop sharing."}},required:["id"]}}]};import{createRequire as oh}from"node:module";import{randomBytes as ah}from"node:crypto";import{existsSync as ko,mkdirSync as ch,writeFileSync as yo}from"node:fs";import{dirname as wo,join as Ve,resolve as So}from"node:path";import{fileURLToPath as vo}from"node:url";var Io=oh(import.meta.url),an=null;function lh(){return an||(an=Io("echarts")),an}var cn=null;function dh(){return cn||(cn=Io("@resvg/resvg-js")),cn}var ph=800,uh=600,mh=4096,hh=16,go=60,No="Noto Sans",_o=256*1024;function fh(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=wo(vo(import.meta.url)),t=So(r,"..","bin","mcp-skill.mjs");return ko(t)?t:null}function yh(){let r=wo(vo(import.meta.url)),t=So(r,"..","assets","fonts");return["NotoSans-Regular.ttf","NotoSans-Bold.ttf"].map(e=>Ve(t,e)).filter(e=>ko(e))}function gh(){let r=process.env.ZIBBY_NODE_SESSION_PATH,t=process.env.ZIBBY_SESSION_PATH,e=r||(t?Ve(t,"chart-render"):Ve(process.cwd(),".zibby","output","charts"));return ch(e,{recursive:!0}),e}function $e(r){return r!=null&&typeof r=="object"&&!Array.isArray(r)}function bo(r,t){let e=Number(r);return Number.isFinite(e)?Math.max(hh,Math.min(mh,Math.round(e))):t}function ln(r){return typeof r!="string"||r.length<=go?r:`${r.slice(0,go-1)}\u2026`}function mr(r){if(Array.isArray(r))for(let t=0;t<r.length;t++){let e=r[t];typeof e=="string"?r[t]=ln(e):$e(e)&&typeof e.name=="string"&&(e.name=ln(e.name))}}function _h(r){for(let n of["xAxis","yAxis"]){let s=Array.isArray(r[n])?r[n]:r[n]?[r[n]]:[];for(let i of s)$e(i)&&mr(i.data)}let t=Array.isArray(r.radar)?r.radar:r.radar?[r.radar]:[];for(let n of t)$e(n)&&mr(n.indicator);$e(r.legend)&&mr(r.legend.data);let e=Array.isArray(r.series)?r.series:r.series?[r.series]:[];for(let n of e)$e(n)&&(typeof n.name=="string"&&(n.name=ln(n.name)),mr(n.data))}function bh(r){let t=JSON.parse(JSON.stringify(r));return t.animation=!1,t.backgroundColor==null&&(t.backgroundColor="#fff"),$e(t.textStyle)||(t.textStyle={}),t.textStyle.fontFamily==null&&(t.textStyle.fontFamily=No),_h(t),t}function kh(r){return typeof r!="string"?null:r.trim().replace(/\.(svg|png)$/i,"").replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^[.-]+|[.-]+$/g,"")||null}function wh(r,t,e){let s=lh().init(null,null,{renderer:"svg",ssr:!0,width:t,height:e});try{return s.setOption(r),s.renderToSVGString()}finally{s.dispose()}}function Sh(r){return r.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi,"")}function vh(r,t){return r.replace(/id="zr\d+-/g,`id="${t}-`).replace(/url\(#zr\d+-/g,`url(#${t}-`).replace(/class="zr\d+-/g,`class="${t}-`)}function Ih(r){let t=[];for(let e of r.matchAll(/(?:xlink:)?href\s*=\s*"([^"]*)"/gi)){let n=e[1].trim();n&&!n.startsWith("#")&&!/^data:/i.test(n)&&t.push(n)}for(let e of r.matchAll(/url\(\s*['"]?([^'")]*)['"]?\s*\)/gi)){let n=e[1].trim();n&&!n.startsWith("#")&&!/^data:/i.test(n)&&t.push(n)}return/@import/i.test(r)&&t.push("@import"),/<script\b/i.test(r)&&t.push("<script>"),/<foreignObject\b/i.test(r)&&t.push("<foreignObject>"),t}function Nh(r){let t=vh(Sh(r),`chart-${ah(4).toString("hex")}`),e=Ih(t);if(e.length){let n=[...new Set(e)].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 Oh(r){let{Resvg:t}=dh();return new t(r,{font:{fontFiles:yh(),loadSystemFonts:!0,defaultFontFamily:No}}).render().asPng()}var Oo={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)
|
|
931
|
+
records this automatically; you don't store it yourself.)`,resolve(){let r=Xm();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(Ze>=kt)return on("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 n=lo(t);if(!n)return JSON.stringify({error:"provide exactly one of markdown (preferred) or html or text (non-empty string)"});if(n.error)return JSON.stringify({error:n.error});let s=po(t,n);if(s?.error)return JSON.stringify({error:s.error});if(n.format==="text"&&!(typeof t?.contentType=="string"?t.contentType.trim():""))return JSON.stringify({error:"contentType is required with text \u2014 text/plain for code/logs, application/json for JSON data, text/csv for CSV"});let i={title:e,[n.format]:n.content};n.format==="text"&&(i.contentType=t.contentType.trim()),typeof t?.kind=="string"&&t.kind.trim()&&(i.kind=t.kind.trim()),typeof t?.favicon=="string"&&t.favicon.trim()&&(i.favicon=t.favicon.trim()),typeof t?.description=="string"&&t.description.trim()&&(i.description=t.description);let o=eh();o&&(i.context=o),t?.share===!0&&(i.share=!0);let a=await so(i,n.format,s?.preflight);if(a?.__rejected)return a.payload;let c=a?.id,l=a?.url;if(!c||!l)return JSON.stringify({error:"artifact write returned no id/url",response:a});let d={id:c,url:l};typeof a.rn=="string"&&a.rn&&(d.rn=a.rn),a.shared&&(d.shared=!0,d.shareUrl=a.shareUrl,d.password=a.password,d.instruction="Send BOTH the shareUrl and the password in your reply. Copy the password EXACTLY \u2014 it is case-sensitive, shown once, and cannot be looked up again (artifact_share issues a new one, which kills this one)."),a.__preflightNote&&(d.preflight=a.__preflightNote);let p=await ao(c,n.content);p&&(d.readBack=p);let u={id:c,title:e,url:l,kind:i.kind||null,createdAt:a.createdAt||new Date().toISOString(),summary:typeof t?.summary=="string"&&t.summary.trim()?t.summary.trim():e};a.context&&typeof a.context=="object"&&(u.context=a.context),n.format==="text"?(u.contentType=typeof a.contentType=="string"?a.contentType:i.contentType,u.bytes=typeof a.bytes=="number"?a.bytes:Buffer.byteLength(n.content,"utf8")):(u.format=n.format,u.bytes=Buffer.byteLength(n.content,"utf8"));try{await co(c,u)}catch(m){d.indexWarning=m.message}return JSON.stringify(d)}case"artifact_update":{if(Ze>=kt)return on("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 n=lo(t);if(n?.error)return JSON.stringify({error:n.error});let s=typeof t?.title=="string"?t.title.trim():"",i=typeof t?.description=="string"&&t.description.trim();if(!n&&!s&&!i)return JSON.stringify({error:"nothing to update \u2014 pass a new title, description, and/or markdown|html"});let o=po(t,n);if(o?.error)return JSON.stringify({error:o.error});let a={id:e};s&&(a.title=s),n&&(a[n.format]=n.content),typeof t?.kind=="string"&&t.kind.trim()&&(a.kind=t.kind.trim()),typeof t?.favicon=="string"&&t.favicon.trim()&&(a.favicon=t.favicon.trim()),i&&(a.description=t.description);let c=await so(a,n?.format,o?.preflight);if(c?.__rejected)return c.payload;let l=c?.url;if(!l)return JSON.stringify({error:"artifact update returned no url",response:c});let d={id:e,url:l};if(typeof c.rn=="string"&&c.rn&&(d.rn=c.rn),c.__preflightNote&&(d.preflight=c.__preflightNote),n){let m=await ao(e,n.content);m&&(d.readBack=m)}let p=await oh(e)||{},u={...p,id:e,url:l,title:s||p.title||"Untitled",kind:a.kind||p.kind||null,createdAt:p.createdAt||c.createdAt||new Date().toISOString(),updatedAt:c.updatedAt||new Date().toISOString()};n&&(n.format==="text"?(u.contentType=typeof c.contentType=="string"?c.contentType:p.contentType||null,u.bytes=typeof c.bytes=="number"?c.bytes:Buffer.byteLength(n.content,"utf8")):(u.format=n.format,u.bytes=Buffer.byteLength(n.content,"utf8"))),typeof t?.summary=="string"&&t.summary.trim()?u.summary=t.summary.trim():u.summary||(u.summary=u.title);try{await co(e,u)}catch(m){d.indexWarning=m.message}return JSON.stringify(d)}case"artifact_get":{let e=typeof t?.id=="string"?t.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let n=await ho(e);return JSON.stringify(n)}case"artifact_share":{let e=typeof t?.id=="string"?t.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let n=await io(e,"protect");return JSON.stringify({id:n.id,url:n.url,password:n.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 t?.id=="string"?t.id.trim():"";if(!e)return JSON.stringify({error:"id is required"});let n=await io(e,"unprotect");return JSON.stringify({id:n.id,shared:!1,wasShared:n.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 ${We}; it does NOT support ${ur}. 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. Use \`text\` (+ required \`contentType\`) to publish RAW SOURCE as-is \u2014 code, a script, logs, JSON data, CSV. It is stored verbatim, never rendered as a page, and shown in a read-only code viewer with a download link. contentType: text/plain for code/logs/scripts, application/json for JSON data, text/csv for CSV. For code, put the FILENAME in \`title\` (e.g. "deploy.sh") \u2014 viewers derive the language label from the extension \u2014 and set kind: "code". Exactly one of markdown|html|text per call. 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 }. PASS \`share: true\` WHEN THE READERS HAVE NO ZIBBY ACCOUNT \u2014 you get back { shareUrl, password } in this same call, instead of publishing a link they cannot open and then having to call artifact_share. The password is GENERATED; you never invent one. Without \`share\` the page is member-only, which is the default and is correct for anything internal.`,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 ${We}. Does NOT support ${ur}. 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."},text:{type:"string",description:'RAW SOURCE published as-is \u2014 code, a script, logs, JSON data, CSV. Stored verbatim (never rendered as an HTML page) and shown in a read-only code viewer with a download link. Requires `contentType`. For code, make `title` the filename (e.g. "deploy.sh" \u2014 the viewer derives the language from the extension) and set kind: "code". Provide this OR markdown OR html.'},contentType:{type:"string",description:"REQUIRED with `text`, meaningless otherwise. One of: text/plain (code, scripts, logs), application/json (JSON data), text/csv (CSV). Nothing else is accepted \u2014 HTML and Markdown are published through their own fields, never as text."},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."},description:{type:"string",description:"Optional Markdown note ABOUT this artifact, shown UNDERNEATH it on its detail page \u2014 what it shows, what was checked, what the reader should conclude, anything surprising. This is the artifact's caption, NOT its content: the page/file itself is the content, and a reader who opens it should be able to understand what they are looking at without asking you. Write it as a short report with headings, bullets and tables (same Markdown subset as the `markdown` field, 16 KB max). It is NOT shown in list views, so it costs nothing to write a real one."},share:{type:"boolean",description:"Set true to publish the page WITH a password link in one step \u2014 the response then also carries { shareUrl, password }, which you send together. Use it whenever the people who asked are in a chat channel and have no Zibby account. Omit it (the default) for anything internal: the page is then readable only by logged-in members of this project, and no password exists."}},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|text). `text` only replaces an artifact that was PUBLISHED as text, and it keeps its original contentType \u2014 the server refuses a type change and any doc\u2194text conversion. 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 ${We}. 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."},text:{type:"string",description:"New raw text content (optional) \u2014 only for an artifact that was PUBLISHED as text. It keeps its original contentType; the server refuses a type change or a doc\u2194text conversion. Provide at most one of markdown|html|text."},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."},description:{type:"string",description:"Optional updated Markdown note shown under the artifact on its detail page. You may pass this ALONE to add or rewrite the explanation without touching the artifact itself \u2014 the content keeps its current version."}},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:`REMOVE an artifact's password \u2014 this is the tool for "take the password off", "remove the password", "stop sharing" and "revoke the link", which are all the same act. The page goes back to the DEFAULT it was published with: viewable only by logged-in members of this project, with no password anywhere. 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. Note what this does NOT do: there is no way to leave a page open to anyone with the link and no password. Removing the password closes the anonymous door; it does not prop it open. Say that plainly if you are asked for a passwordless public link.`,input_schema:{type:"object",properties:{id:{type:"string",description:"The artifact id to stop sharing."}},required:["id"]}}]};import{createRequire as ah}from"node:module";import{randomBytes as ch}from"node:crypto";import{existsSync as ko,mkdirSync as lh,writeFileSync as yo}from"node:fs";import{dirname as wo,join as Ve,resolve as So}from"node:path";import{fileURLToPath as vo}from"node:url";var Io=ah(import.meta.url),an=null;function dh(){return an||(an=Io("echarts")),an}var cn=null;function ph(){return cn||(cn=Io("@resvg/resvg-js")),cn}var uh=800,mh=600,hh=4096,fh=16,go=60,No="Noto Sans",_o=256*1024;function yh(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=wo(vo(import.meta.url)),t=So(r,"..","bin","mcp-skill.mjs");return ko(t)?t:null}function gh(){let r=wo(vo(import.meta.url)),t=So(r,"..","assets","fonts");return["NotoSans-Regular.ttf","NotoSans-Bold.ttf"].map(e=>Ve(t,e)).filter(e=>ko(e))}function _h(){let r=process.env.ZIBBY_NODE_SESSION_PATH,t=process.env.ZIBBY_SESSION_PATH,e=r||(t?Ve(t,"chart-render"):Ve(process.cwd(),".zibby","output","charts"));return lh(e,{recursive:!0}),e}function $e(r){return r!=null&&typeof r=="object"&&!Array.isArray(r)}function bo(r,t){let e=Number(r);return Number.isFinite(e)?Math.max(fh,Math.min(hh,Math.round(e))):t}function ln(r){return typeof r!="string"||r.length<=go?r:`${r.slice(0,go-1)}\u2026`}function mr(r){if(Array.isArray(r))for(let t=0;t<r.length;t++){let e=r[t];typeof e=="string"?r[t]=ln(e):$e(e)&&typeof e.name=="string"&&(e.name=ln(e.name))}}function bh(r){for(let n of["xAxis","yAxis"]){let s=Array.isArray(r[n])?r[n]:r[n]?[r[n]]:[];for(let i of s)$e(i)&&mr(i.data)}let t=Array.isArray(r.radar)?r.radar:r.radar?[r.radar]:[];for(let n of t)$e(n)&&mr(n.indicator);$e(r.legend)&&mr(r.legend.data);let e=Array.isArray(r.series)?r.series:r.series?[r.series]:[];for(let n of e)$e(n)&&(typeof n.name=="string"&&(n.name=ln(n.name)),mr(n.data))}function kh(r){let t=JSON.parse(JSON.stringify(r));return t.animation=!1,t.backgroundColor==null&&(t.backgroundColor="#fff"),$e(t.textStyle)||(t.textStyle={}),t.textStyle.fontFamily==null&&(t.textStyle.fontFamily=No),bh(t),t}function wh(r){return typeof r!="string"?null:r.trim().replace(/\.(svg|png)$/i,"").replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^[.-]+|[.-]+$/g,"")||null}function Sh(r,t,e){let s=dh().init(null,null,{renderer:"svg",ssr:!0,width:t,height:e});try{return s.setOption(r),s.renderToSVGString()}finally{s.dispose()}}function vh(r){return r.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi,"")}function Ih(r,t){return r.replace(/id="zr\d+-/g,`id="${t}-`).replace(/url\(#zr\d+-/g,`url(#${t}-`).replace(/class="zr\d+-/g,`class="${t}-`)}function Nh(r){let t=[];for(let e of r.matchAll(/(?:xlink:)?href\s*=\s*"([^"]*)"/gi)){let n=e[1].trim();n&&!n.startsWith("#")&&!/^data:/i.test(n)&&t.push(n)}for(let e of r.matchAll(/url\(\s*['"]?([^'")]*)['"]?\s*\)/gi)){let n=e[1].trim();n&&!n.startsWith("#")&&!/^data:/i.test(n)&&t.push(n)}return/@import/i.test(r)&&t.push("@import"),/<script\b/i.test(r)&&t.push("<script>"),/<foreignObject\b/i.test(r)&&t.push("<foreignObject>"),t}function Oh(r){let t=Ih(vh(r),`chart-${ch(4).toString("hex")}`),e=Nh(t);if(e.length){let n=[...new Set(e)].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 Th(r){let{Resvg:t}=ph();return new t(r,{font:{fontFiles:gh(),loadSystemFonts:!0,defaultFontFamily:No}}).render().asPng()}var Oo={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)
|
|
915
932
|
You can render charts LOCALLY with the chart_render tool \u2014 pass a standard
|
|
916
933
|
Apache ECharts option object as \`spec\` (any chart type: bar, line, pie,
|
|
917
934
|
radar, scatter, heatmap, \u2026). No browser, no external chart service \u2014 the data
|
|
@@ -926,7 +943,7 @@ Pick the destination with \`output\`:
|
|
|
926
943
|
compute the geometry from the data so the bars cannot come out wrong.
|
|
927
944
|
- **Attaching a chart to the run \u2192 \`output:'png'\` (default), \`'svg'\` or
|
|
928
945
|
\`'both'\`.** Writes files to the run's output folder (auto-uploaded as run
|
|
929
|
-
artifacts) and returns the paths. Default 800\xD7600.`,resolve({sessionPath:r,nodeName:t}={}){let e=
|
|
946
|
+
artifacts) and returns the paths. Default 800\xD7600.`,resolve({sessionPath:r,nodeName:t}={}){let e=yh();if(!e)return{command:null,args:[],env:{},description:this.description};let n={},s=r&&t?Ve(r,t):null;s&&(n.ZIBBY_NODE_SESSION_PATH=s),r&&(n.ZIBBY_SESSION_PATH=r);for(let i of["ZIBBY_NODE_SESSION_PATH","ZIBBY_SESSION_PATH"])!n[i]&&process.env[i]&&(n[i]=process.env[i]);return{type:"stdio",command:"node",args:[e,"../dist/chartRender.js","chartRenderSkill"],env:n,description:this.description,alwaysLoad:!0}},async handleToolCall(r,t){if(r!=="chart_render")return JSON.stringify({error:`Unknown tool: ${r}`});try{let e=t?.spec;if(!$e(e))return JSON.stringify({error:"spec must be a plain ECharts option OBJECT (e.g. { xAxis: {...}, yAxis: {...}, series: [...] }) \u2014 got "+(e===null?"null":Array.isArray(e)?"an array":typeof e)+". Pass the option object itself, not a string or an array."});let n=bo(t?.width,uh),s=bo(t?.height,mh),i=["svg","png","both","svg-inline"].includes(t?.output)?t.output:"png",o=wh(t?.filename)||`chart-${Date.now()}`,a;try{a=Sh(kh(e),n,s)}catch(d){return JSON.stringify({error:`Chart render failed: ${d.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 a!="string"||!a.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(i==="svg-inline"){let d;try{d=Oh(a)}catch(u){return JSON.stringify({error:`Chart cannot be inlined: ${u.message}`})}let p=Buffer.byteLength(d,"utf-8");return p>_o?JSON.stringify({error:`Rendered SVG is ${Math.round(p/1024)}KB, over the ${Math.round(_o/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:s,svg:d,bytes:p,files:[]})}let c=_h(),l=[];if(i==="svg"||i==="both"){let d=Ve(c,`${o}.svg`);yo(d,a,"utf-8"),l.push({path:d,format:"svg",bytes:Buffer.byteLength(a,"utf-8")})}if(i==="png"||i==="both"){let d;try{d=Th(a)}catch(u){return JSON.stringify({error:`PNG rasterization failed: ${u.message}. Retry with output:'svg' if you only need the vector.`})}let p=Ve(c,`${o}.png`);yo(p,d),l.push({path:p,format:"png",bytes:d.length})}return JSON.stringify({ok:!0,width:n,height:s,files:l})}catch(e){return JSON.stringify({error:`chart_render failed: ${e.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"]}}]};import{existsSync as Ah}from"fs";import{fileURLToPath as Rh}from"url";import{dirname as Eh,resolve as xh}from"path";function Lh(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=Eh(Rh(import.meta.url)),t=xh(r,"..","bin","mcp-skill.mjs");return Ah(t)?t:null}var jh=[{name:"trivial",max:5},{name:"small",max:30},{name:"medium",max:150},{name:"large",max:600},{name:"huge",max:1/0}];function $h(r,t){let e=(Number(r)||0)+(Number(t)||0);for(let n of jh)if(e<=n.max)return n.name;return"huge"}function Ph(r){let e=String(r||"").toLowerCase().split("/").pop()||"",n=e.match(/\.(test|spec)\.[a-z0-9]+$/);if(n)return`.${n[1]}`;let s=e.lastIndexOf(".");return s<=0?/^(dockerfile|makefile|jenkinsfile|procfile|gemfile|rakefile)$/i.test(e)?e.toLowerCase():"":e.slice(s)}var Ch={code:[".js",".jsx",".ts",".tsx",".mjs",".cjs",".py",".go",".rs",".java",".kt",".rb",".php",".c",".cc",".cpp",".h",".hpp",".cs",".swift",".scala",".m",".dart",".ex",".exs",".clj",".sh",".bash"],test:[".test",".spec"],docs:[".md",".mdx",".rst",".txt",".adoc"],config:[".json",".yml",".yaml",".toml",".ini",".env",".cfg",".conf",".lock",".xml",".properties","dockerfile","makefile","jenkinsfile","procfile",".tf",".gradle"],style:[".css",".scss",".sass",".less",".styl"],data:[".csv",".tsv",".sql",".parquet",".proto",".graphql",".gql"]},Uh=(()=>{let r={};for(let[t,e]of Object.entries(Ch))for(let n of e)r[n]=t;return r})();function Bh(r){return Uh[String(r||"").toLowerCase()]||"other"}function Dh(r,t={}){let e=Array.isArray(r)?r:[],n=[],s={},i={code:0,test:0,docs:0,config:0,style:0,data:0,other:0},o=0,a=0,c={path:"",changed:-1};for(let m of e){if(!m||typeof m!="object")continue;let h=String(m.path||m.newPath||m.filename||m.new_path||"").trim();if(!h)continue;n.push(h);let f=Number(m.additions??m.add??0)||0,b=Number(m.deletions??m.del??0)||0;o+=f,a+=b;let y=Ph(h);s[y||"(none)"]=(s[y||"(none)"]||0)+1,i[Bh(y)]+=1;let _=f+b;_>c.changed&&(c={path:h,changed:_})}let l=Number(t.additions),d=Number(t.deletions),p=Number.isFinite(l)?l:o,u=Number.isFinite(d)?d:a;return{filesChanged:n.length,filePaths:n,extCounts:s,kindCounts:i,additions:p,deletions:u,sizeBucket:$h(p,u),largestFile:c.path||null,dirs:[...new Set(n.map(m=>m.includes("/")?m.split("/")[0]:"(root)"))].sort()}}function Jh(r){let t="",e=String(r||"");for(let n=0;n<e.length;n++){let s=e[n];s==="*"?e[n+1]==="*"?(t+=".*",n++,e[n+1]==="/"&&n++):t+="[^/]*":s==="?"?t+="[^/]":"\\^$.|+()[]{}".includes(s)?t+=`\\${s}`:t+=s}return new RegExp(`^${t}$`)}function Mh(r,t){let e=Array.isArray(r)?r.map(String):[],n=(Array.isArray(t)?t:[]).filter(Boolean).map(Jh);if(!n.length)return{total:e.length,hits:0,ratio:0,matched:[]};let s=e.filter(i=>n.some(o=>o.test(i)));return{total:e.length,hits:s.length,ratio:e.length?Number((s.length/e.length).toFixed(4)):0,matched:s}}function qh(r,t){let e=(Array.isArray(r)?r:[]).map(Number).filter(i=>Number.isFinite(i)),n=Number(t);if(!e.length||!Number.isFinite(n))return null;let s=e.filter(i=>i<=n).length;return Number((s/e.length*100).toFixed(1))}var To={id:"code-stats",serverName:"code_stats",allowedTools:["mcp__code_stats__*"],description:"Deterministic code statistics \u2014 pure, reproducible facts from changed-file lists + numbers (extension/work-kind mix, size bucket, core-path glob coverage, percentile rank). Zero LLM, zero network.",promptFragment:`## Code Stats (DETERMINISTIC facts \u2014 pure, reproducible, zero-LLM)
|
|
930
947
|
These tools COMPUTE reproducible facts from data you already have. Use them so
|
|
931
948
|
your numbers are auditable and survive challenge (same input \u2192 same output).
|
|
932
949
|
|
|
@@ -944,7 +961,7 @@ Tools:
|
|
|
944
961
|
reproducible "works in the core vs the edges" number.
|
|
945
962
|
- code_stats_percentile: Given \`values\` (a cohort's numbers) + a \`value\`,
|
|
946
963
|
returns the 0-100 percentile rank. Use to express every metric as a rank
|
|
947
|
-
within the team (reproducible + comparable across teams), not a raw number.`,resolve(){let r=
|
|
964
|
+
within the team (reproducible + comparable across teams), not a raw number.`,resolve(){let r=Lh();return r?{type:"stdio",command:"node",args:[r,"../dist/codeStats.js","codeStatsSkill"],env:{},description:this.description,alwaysLoad:!0}:{command:null,args:[],env:{},description:this.description}},async handleToolCall(r,t){try{switch(r){case"code_stats_commit_facts":return JSON.stringify(Dh(t?.files,{additions:t?.additions,deletions:t?.deletions}));case"code_stats_glob_hit":return JSON.stringify(Mh(t?.paths,t?.globs));case"code_stats_percentile":return JSON.stringify({percentile:qh(t?.values,t?.value)});default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"code_stats_commit_facts",description:"Deterministic per-commit facts from its changed-file list. Pass files: [{path, additions, deletions}] (gitlab_get_commit / github_get_commit shape). Returns filePaths, extCounts, kindCounts (code/test/docs/config/style/data/other), additions, deletions, sizeBucket (trivial/small/medium/large/huge), largestFile, dirs. Pure \u2014 same input always same output.",input_schema:{type:"object",properties:{files:{type:"array",description:"Changed files. Each { path (or newPath/filename), additions?, deletions? }.",items:{type:"object",properties:{path:{type:"string"},additions:{type:"number"},deletions:{type:"number"}}}},additions:{type:"number",description:"Optional commit-level total additions override (else summed from files)."},deletions:{type:"number",description:"Optional commit-level total deletions override."}},required:["files"]}},{name:"code_stats_glob_hit",description:"How many of paths match ANY of globs (supports **, *, ?). Returns { total, hits, ratio, matched }. Use for reproducible core-path coverage against the caller's own core globs.",input_schema:{type:"object",properties:{paths:{type:"array",items:{type:"string"},description:"File paths to test."},globs:{type:"array",items:{type:"string"},description:'Glob patterns, e.g. ["src/payment/**", "auth/**"].'}},required:["paths","globs"]}},{name:"code_stats_percentile",description:'The 0-100 percentile rank of value within values (cumulative "% at or below"). Reproducible + cross-team comparable. Empty cohort \u2192 null.',input_schema:{type:"object",properties:{values:{type:"array",items:{type:"number"},description:"The cohort's numbers."},value:{type:"number",description:"The value to rank."}},required:["values","value"]}}]};import{existsSync as Fh}from"fs";import{fileURLToPath as Kh}from"url";import{dirname as Gh,resolve as Hh}from"path";function Yh(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=Gh(Kh(import.meta.url)),t=Hh(r,"..","bin","mcp-skill.mjs");return Fh(t)?t:null}function zh(r={}){let t=String(r.provider||process.env.ZIBBY_PROGRESS_PROVIDER||(process.env.LARK_RECEIVE_ID?"lark":process.env.SLACK_CHANNEL||process.env.SLACK_BOT_TOKEN?"slack":"")).toLowerCase(),e=String(r.chatId||r.channel||process.env.ZIBBY_PROGRESS_CHAT_ID||(t==="lark"?process.env.LARK_RECEIVE_ID:process.env.SLACK_CHANNEL)||"").trim(),n=String(r.mention||process.env.ZIBBY_PROGRESS_MENTION||"").trim();return{provider:t,chatId:e,mention:n}}var Ao={id:"chat-progress",serverName:"chat_progress",allowedTools:["mcp__chat_progress__*"],envKeys:[...P.envKeys||[],...F.envKeys||[],"ZIBBY_PROGRESS_PROVIDER","ZIBBY_PROGRESS_CHAT_ID","ZIBBY_PROGRESS_MENTION","SLACK_CHANNEL","LARK_RECEIVE_ID"],description:"Report progress back to the triggering chat during a long-running job \u2014 a one-line status the human sees while the work runs. General + reusable; fire-and-forget.",promptFragment:`## Chat Progress (tell the human you're still working \u2014 long jobs only)
|
|
948
965
|
When a run is LONG (many API pages, a shard-capped backfill, dozens of scored
|
|
949
966
|
items), the human who triggered it can't see the middle \u2014 only the final
|
|
950
967
|
notify. Post brief milestones so they know it's alive:
|
|
@@ -954,11 +971,11 @@ notify. Post brief milestones so they know it's alive:
|
|
|
954
971
|
commits, continuing\u2026"). Pass the chat target from your \`notify\` input when
|
|
955
972
|
you have it (provider + chatId); otherwise it resolves from the runtime.
|
|
956
973
|
Fire-and-forget \u2014 if it can't post, just keep working; never treat a failed
|
|
957
|
-
progress ping as an error.`,resolve(){let r=
|
|
958
|
-
`),height:a}}function
|
|
974
|
+
progress ping as an error.`,resolve(){let r=Yh();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","SLACK_BOT_TOKEN","SLACK_TEAM_ID","ZIBBY_PROGRESS_PROVIDER","ZIBBY_PROGRESS_CHAT_ID","ZIBBY_PROGRESS_MENTION","SLACK_CHANNEL","LARK_RECEIVE_ID"])process.env[e]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[r,"../dist/chatProgress.js","chatProgressSkill"],env:t,description:this.description,alwaysLoad:!0}},async handleToolCall(r,t){if(r!=="report_progress")return JSON.stringify({error:`Unknown tool: ${r}`});try{let e=String(t?.message||"").trim().slice(0,2e3);if(!e)return JSON.stringify({ok:!1,skipped:"empty message"});let{provider:n,chatId:s,mention:i}=zh(t);if(!n||!s)return JSON.stringify({ok:!1,skipped:"no chat target"});let o=i&&n==="slack"?`<@${i}> ${e}`:e,a;n==="lark"?a=await F.handleToolCall("lark_send_message",{receive_id:s,text:e}):a=await P.handleToolCall("slack_post_message",{channel:s,text:o});let c=null;try{c=JSON.parse(a)}catch{}return c&&c.error?JSON.stringify({ok:!1,skipped:`post failed: ${c.error}`}):JSON.stringify({ok:!0,provider:n,posted:!0})}catch(e){return JSON.stringify({ok:!1,skipped:`error: ${e.message}`})}},tools:[{name:"report_progress",description:"Post a ONE-LINE progress status to the chat that triggered this run (so the human sees the long job is alive). Fire-and-forget \u2014 never fails the run. Target resolves from your notify input (provider + chatId) or the runtime; you usually just pass the message.",input_schema:{type:"object",properties:{message:{type:"string",description:'A short human status line, e.g. "Scored 60/188 commits, continuing\u2026".'},provider:{type:"string",enum:["lark","slack"],description:"Optional \u2014 the chat provider (from your notify input). Defaults from the runtime."},chatId:{type:"string",description:"Optional \u2014 the target chat/channel id (from your notify input). Defaults from the runtime."}},required:["message"]}}]};import{createRequire as Wh}from"node:module";import{existsSync as Lo,mkdirSync as Zh,writeFileSync as Ro}from"node:fs";import{dirname as jo,join as Qe,resolve as $o}from"node:path";import{fileURLToPath as Po}from"node:url";var Vh=Wh(import.meta.url),dn=null;function Xh(){return dn||(dn=Vh("@resvg/resvg-js")),dn}var Co=1200,Qh=627,ef=4096,tf=320,rf="#3b82f6",Pe="Noto Sans",nf=3;function sf(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=jo(Po(import.meta.url)),t=$o(r,"..","bin","mcp-skill.mjs");return Lo(t)?t:null}function of(){let r=jo(Po(import.meta.url)),t=$o(r,"..","assets","fonts");return["NotoSans-Regular.ttf","NotoSans-Bold.ttf"].map(e=>Qe(t,e)).filter(e=>Lo(e))}function af(){let r=process.env.ZIBBY_NODE_SESSION_PATH,t=process.env.ZIBBY_SESSION_PATH,e=r||(t?Qe(t,"social-card"):Qe(process.cwd(),".zibby","output","social-cards"));return Zh(e,{recursive:!0}),e}function Eo(r,t){let e=Number(r);return Number.isFinite(e)?Math.max(tf,Math.min(ef,Math.round(e))):t}function Xe(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function cf(r){return typeof r!="string"?null:r.trim().replace(/\.(png|svg)$/i,"").replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^[.-]+|[.-]+$/g,"")||null}function lf(r,t){if(typeof r!="string")return t;let e=r.trim();return/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(e)?e:t}function Uo(r,t,e){return String(r).length*t*e}function pn(r,t,e,n){let s=String(r).trim().split(/\s+/).filter(Boolean),i=[],o="";for(let a of s){let c=o?`${o} ${a}`:a;o&&Uo(c,e,n)>t?(i.push(o),o=a):o=c}return o&&i.push(o),i.length?i:[""]}function df(r){return r==="light"?{bg:"#ffffff",fg:"#0b0f14",muted:"#5b6673",pillText:"#0b0f14",checkFg:"#ffffff",divider:"#e2e6ea"}:{bg:"#0b0f14",fg:"#e9edf1",muted:"#8b98a5",pillText:"#e9edf1",checkFg:"#0b0f14",divider:"#22303c"}}function xo(r,t,e,n,s){let i=(Array.isArray(r)?r:[]).map(b=>String(b??"").trim()).filter(Boolean).slice(0,nf);if(i.length<2)return null;let o=24,a=54,c=26,l=90,d=17,p=i.map(b=>Math.max(96,Uo(b,o,.58)+c*2)),u=p.reduce((b,y)=>b+y,0)+l*(i.length-1),m=t-u/2,h=e+a/2,f=[];for(let b=0;b<i.length;b++){let y=p[b];if(f.push(`<rect x="${m.toFixed(1)}" y="${e}" width="${y.toFixed(1)}" height="${a}" rx="${a/2}" fill="${s}" fill-opacity="0.12" stroke="${s}" stroke-opacity="0.55" stroke-width="1.5"/>`,`<text x="${(m+y/2).toFixed(1)}" y="${(h+o*.35).toFixed(1)}" text-anchor="middle" font-family="${Pe}" font-size="${o}" font-weight="700" fill="${n.pillText}">${Xe(i[b])}</text>`),m+=y,b<i.length-1){let _=m,g=m+l,k=(_+g)/2;f.push(`<line x1="${_.toFixed(1)}" y1="${h}" x2="${g.toFixed(1)}" y2="${h}" stroke="${s}" stroke-opacity="0.55" stroke-width="2"/>`,`<circle cx="${k.toFixed(1)}" cy="${h}" r="${d}" fill="${s}"/>`,`<path d="M ${(k-8).toFixed(1)} ${h.toFixed(1)} l 5 5 l 9 -11" fill="none" stroke="${n.checkFg}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>`),m=g}}return{svg:f.join(`
|
|
975
|
+
`),height:a}}function pf(r){let{width:t,height:e,theme:n,accent:s}=r,i=df(n),o=t/2,a=Math.round(t*.07),c=t-a*2,l=String(r.headline).trim(),d=r.eyebrow?String(r.eyebrow).trim().toUpperCase():"",p=r.subhead?String(r.subhead).trim():"",u=r.stat?String(r.stat).trim():"",m=r.footer?String(r.footer).trim():"",h=t/Co,f=Math.round(22*h),b=Math.round(29*h),y=p?pn(p,c,b,.52):[],_=Math.round(b*1.28),g=4,k=Math.round(26*h),O=Math.round(20*h),M=Math.round(24*h),ye=Math.round(30*h),T=!!(u||m),R=a,q=T?Math.round(110*h):a,x=e-R-q,Ie=xo(r.diagram,o,0,i,s),Tn=(C,ge)=>{let tt=g+k;return d&&(tt+=f+O),tt+=ge.length*Math.round(C*1.16),y.length&&(tt+=M+y.length*_),Ie&&(tt+=ye+Ie.height),tt},Sr=[76,66,58,50,44,38].map(C=>Math.round(C*h)),ka=4,Ue=Sr[Sr.length-1],vr=pn(l,c,Ue,.6);for(let C of Sr){let ge=pn(l,c,C,.6);if(ge.length<=ka&&Tn(C,ge)<=x){Ue=C,vr=ge;break}}let wa=Math.round(Ue*1.16),Sa=Tn(Ue,vr),W=R+Math.max(0,(x-Sa)/2),le=[];le.push(`<rect x="0" y="0" width="${t}" height="${e}" fill="${i.bg}"/>`);let An=Math.round(60*h);le.push(`<rect x="${(o-An/2).toFixed(1)}" y="${W}" width="${An}" height="${g}" rx="2" fill="${s}"/>`),W+=g+k,d&&(le.push(`<text x="${o}" y="${(W+f*.82).toFixed(1)}" text-anchor="middle" font-family="${Pe}" font-size="${f}" font-weight="700" letter-spacing="3" fill="${s}">${Xe(d)}</text>`),W+=f+O);for(let C of vr)le.push(`<text x="${o}" y="${(W+Ue*.82).toFixed(1)}" text-anchor="middle" font-family="${Pe}" font-size="${Ue}" font-weight="700" fill="${i.fg}">${Xe(C)}</text>`),W+=wa;if(y.length){W+=M;for(let C of y)le.push(`<text x="${o}" y="${(W+b*.82).toFixed(1)}" text-anchor="middle" font-family="${Pe}" font-size="${b}" font-weight="400" fill="${i.muted}">${Xe(C)}</text>`),W+=_}if(Ie){W+=ye;let C=xo(r.diagram,o,W,i,s);C&&le.push(C.svg)}if(T){let C=Math.round(24*h),ge=e-Math.round(46*h);m&&le.push(`<text x="${a}" y="${ge}" text-anchor="start" font-family="${Pe}" font-size="${C}" font-weight="400" fill="${i.muted}">${Xe(m)}</text>`),u&&le.push(`<text x="${t-a}" y="${ge}" text-anchor="end" font-family="${Pe}" font-size="${C}" font-weight="700" fill="${s}">${Xe(u)}</text>`)}return`<svg xmlns="http://www.w3.org/2000/svg" width="${t}" height="${e}" viewBox="0 0 ${t} ${e}">
|
|
959
976
|
${le.join(`
|
|
960
977
|
`)}
|
|
961
|
-
</svg>`}function
|
|
978
|
+
</svg>`}function uf(r){let{Resvg:t}=Xh();return new t(r,{font:{fontFiles:of(),loadSystemFonts:!0,defaultFontFamily:Pe}}).render().asPng()}var Bo={id:"social-card",serverName:"social_card",allowedTools:["mcp__social_card__*"],description:'Social card \u2014 render a branded LinkedIn "concept card" PNG locally (bold headline + eyebrow + optional stat/footer/diagram); nothing leaves the box',promptFragment:`## Social Card (branded concept card, local)
|
|
962
979
|
You can render a BRANDED concept card PNG with the social_card_render tool \u2014 the
|
|
963
980
|
clean, high-signal LinkedIn "concept card" style (bold headline + small uppercase
|
|
964
981
|
eyebrow + optional stat/footer + optional A\u2014\u2713\u2014B diagram). Give it STRUCTURED
|
|
@@ -972,15 +989,15 @@ fields that capture the ONE key idea of your post:
|
|
|
972
989
|
It renders server-side to a PNG in the run's output folder and returns
|
|
973
990
|
{ ok:true, files:[{ path, format:'png', bytes }] }. Pass the returned \`path\` as
|
|
974
991
|
\`imagePath\` when you draft/publish a LinkedIn post to ATTACH the card as the
|
|
975
|
-
post image. No browser, no external service \u2014 the data never leaves the machine.`,resolve({sessionPath:r,nodeName:t}={}){let e=
|
|
976
|
-
`)){let s=n.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch{continue}if(!i||typeof i!="object")continue;let o=i.location||{};e.push({file:o.file||"",line:Number.isFinite(o.line)?o.line:"",severity:i.severity||"warning",rule:i.code||"",message:i.message||""})}return e.filter(n=>n.file||n.message)}var
|
|
992
|
+
post image. No browser, no external service \u2014 the data never leaves the machine.`,resolve({sessionPath:r,nodeName:t}={}){let e=sf();if(!e)return{command:null,args:[],env:{},description:this.description};let n={},s=r&&t?Qe(r,t):null;s&&(n.ZIBBY_NODE_SESSION_PATH=s),r&&(n.ZIBBY_SESSION_PATH=r);for(let i of["ZIBBY_NODE_SESSION_PATH","ZIBBY_SESSION_PATH"])!n[i]&&process.env[i]&&(n[i]=process.env[i]);return{type:"stdio",command:"node",args:[e,"../dist/socialCard.js","socialCardSkill"],env:n,description:this.description,alwaysLoad:!0}},async handleToolCall(r,t){if(r!=="social_card_render")return JSON.stringify({error:`Unknown tool: ${r}`});try{let e=t?.headline;if(typeof e!="string"||!e.trim())return JSON.stringify({error:"headline (the big bold line) is required and must be a non-empty string."});let n=Eo(t?.width,Co),s=Eo(t?.height,Qh),i=t?.theme==="light"?"light":"dark",o=lf(t?.accent,rf),a;try{a=pf({width:n,height:s,theme:i,accent:o,headline:e,eyebrow:t?.eyebrow,subhead:t?.subhead,stat:t?.stat,footer:t?.footer,diagram:t?.diagram})}catch(m){return JSON.stringify({error:`Card layout failed: ${m.message}`})}let c;try{c=uf(a)}catch(m){return JSON.stringify({error:`PNG rasterization failed: ${m.message}`})}let l=af(),d=cf(t?.filename)||`social-card-${Date.now()}`,p=Qe(l,`${d}.png`);Ro(p,c);let u=[{path:p,format:"png",bytes:c.length}];if(t?.output==="both"||t?.output==="svg"){let m=Qe(l,`${d}.svg`);Ro(m,a,"utf-8"),u.push({path:m,format:"svg",bytes:Buffer.byteLength(a,"utf-8")})}return JSON.stringify({ok:!0,width:n,height:s,theme:i,files:u})}catch(e){return JSON.stringify({error:`social_card_render failed: ${e.message}`})}},tools:[{name:"social_card_render",description:'Render a BRANDED "concept card" PNG LOCALLY (no external service) from structured fields \u2014 the clean LinkedIn concept-card style: bold headline + small uppercase eyebrow + optional supporting subhead, stat, footer, and an A\u2014\u2713\u2014B diagram. Returns { ok:true, files:[{ path, format:"png", bytes }] }. Pass the returned `path` as `imagePath` to a LinkedIn post tool to attach it as the post image. Example: { "headline":"No more copy-paste between AI tools", "eyebrow":"MY WORKFLOW", "stat":"11.8k stars", "footer":"Apache 2.0", "diagram":["Claude Code","Codex"] }.',input_schema:{type:"object",properties:{headline:{type:"string",description:"REQUIRED. The big, bold line \u2014 the single key idea/takeaway. Word-wrapped + auto-sized."},eyebrow:{type:"string",description:'Small uppercase kicker above the headline (e.g. "MY WORKFLOW", "OPEN SOURCE"). Rendered uppercased in the accent color.'},subhead:{type:"string",description:"One supporting line under the headline (muted)."},stat:{type:"string",description:'Optional stat, shown bottom-right in the accent color (e.g. "11.8k stars", "3x faster").'},footer:{type:"string",description:'Optional footer, shown bottom-left (muted) (e.g. "Apache 2.0", a repo name).'},diagram:{type:"array",items:{type:"string"},description:'Optional 2-3 short labels rendered as a chain of pills joined by a checkmark (A \u2014\u2713\u2014 B), e.g. ["Claude Code","Codex"].'},theme:{type:"string",enum:["dark","light"],description:"Card theme (default 'dark' \u2014 near-black bg, off-white text)."},accent:{type:"string",description:'Accent hex color (e.g. "#f97316"). Defaults to a tasteful blue (#3b82f6).'},width:{type:"number",description:"Image width in px (default 1200 \u2014 LinkedIn 1.91:1 link image)."},height:{type:"number",description:"Image height in px (default 627)."},filename:{type:"string",description:"Optional file basename (no extension); defaults to social-card-<timestamp>."},output:{type:"string",enum:["png","both"],description:"Which file(s) to write (default 'png'; 'both' also writes the .svg)."}},required:["headline"]}}]};import{spawnSync as mf}from"node:child_process";import{existsSync as J,readdirSync as Mo,statSync as qo,writeFileSync as mn,mkdirSync as hn}from"node:fs";import{dirname as un,extname as gr,join as U,relative as hf,resolve as yr}from"node:path";import{tmpdir as fn}from"node:os";import{fileURLToPath as ff}from"node:url";import{SKILL_META as yf}from"@zibby/skill-ids";import{binPath as gf}from"@zibby/bin-oxlint";import{binPath as _f}from"@zibby/bin-semgrep";function bf(){if(process.env.OXLINT_BIN)return process.env.OXLINT_BIN;try{let r=gf();if(r&&J(r))return r}catch{}return"oxlint"}function kf(){if(process.env.SEMGREP_CORE_BIN)return process.env.SEMGREP_CORE_BIN;try{let r=_f();if(r&&J(r))return r}catch{}return"semgrep-core"}function wf(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=un(ff(import.meta.url)),t=yr(r,"..","bin","mcp-skill.mjs");return J(t)?t:null}var Fo=new Set(["node_modules",".git","dist","build","out","vendor","target",".venv","venv","__pycache__",".next",".turbo","coverage",".zibby"]),Sf=400,vf={plugins:["react","typescript","unicorn","oxc"],categories:{correctness:"error",suspicious:"warn"},rules:{"react/react-in-jsx-scope":"off","react/jsx-max-depth":"off","react/no-array-index-key":"off","react/jsx-key":"error","react/no-unknown-property":"error","no-unused-vars":"warn",eqeqeq:"warn"}},If=[".oxlintrc.json",".oxlintrc","oxlint.json"],hr=null;function Nf(){if(hr&&J(hr))return hr;try{let r=U(fn(),"zibby-code-scan");hn(r,{recursive:!0});let t=U(r,"oxlintrc.curated.json");return mn(t,JSON.stringify(vf),"utf-8"),hr=t,t}catch{return null}}var Ko={".java":"java",".py":"python",".go":"go",".rb":"ruby",".php":"php"},Do=Object.keys(Ko),Go={rules:[{id:"zibby-java-command-injection",languages:["java"],severity:"ERROR",message:"Command execution (Runtime.exec / ProcessBuilder) \u2014 command injection risk if the argument is attacker-influenced. Validate/allow-list the input or avoid a shell.",patterns:[{"pattern-either":[{pattern:"Runtime.getRuntime().exec(...)"},{pattern:"new ProcessBuilder(...)"}]}]},{id:"zibby-python-subprocess-shell",languages:["python"],severity:"ERROR",message:"subprocess call with shell=True \u2014 command injection risk. Pass an argv list and shell=False.",pattern:"subprocess.$F(..., shell=True, ...)"},{id:"zibby-python-yaml-load",languages:["python"],severity:"WARNING",message:"yaml.load without a safe loader can instantiate arbitrary Python objects. Use yaml.safe_load.",pattern:"yaml.load(...)"},{id:"zibby-go-command-injection",languages:["go"],severity:"WARNING",message:"os/exec with a non-constant command \u2014 verify the value is not attacker-controlled (command injection).",pattern:"exec.Command($CMD, ...)"},{id:"zibby-ruby-command-injection",languages:["ruby"],severity:"ERROR",message:"Shell/eval execution (system / eval) \u2014 command injection risk if the argument is attacker-influenced.",patterns:[{"pattern-either":[{pattern:"system(...)"},{pattern:"eval(...)"}]}]},{id:"zibby-php-command-injection",languages:["php"],severity:"ERROR",message:"Shell/eval execution (system / exec / shell_exec) \u2014 command injection risk if the argument is attacker-influenced.",patterns:[{"pattern-either":[{pattern:"system(...);"},{pattern:"exec(...);"},{pattern:"shell_exec(...);"}]}]}]},Of=[".semgrep.yml",".semgrep.yaml","semgrep.yml","semgrep.yaml"],fr=null;function Tf(){if(fr&&J(fr))return fr;try{let r=U(fn(),"zibby-code-scan");hn(r,{recursive:!0});let t=U(r,"semgrep.curated.rules.json");return mn(t,JSON.stringify(Go),"utf-8"),fr=t,t}catch{return null}}function Af(r){return Of.map(e=>U(r,e)).find(e=>J(e))||Tf()}function Rf(r){let t=[];for(let e of Array.isArray(r)?r:[]){if(typeof e!="string"||!e)continue;let n=Ko[gr(e).toLowerCase()];if(!n)continue;let s=e.replace(/\\/g,"/");t.push(["CodeTarget",{path:{fpath:s,ppath:`/${s.replace(/^\/+/,"")}`},analyzer:n,products:["sast"]}])}return["Targets",t]}var Ef=0;function xf(r){let t=Rf(r),e=t[1].length,n=U(fn(),"zibby-code-scan");hn(n,{recursive:!0});let s=U(n,`semgrep.targets.${process.pid}.${Ef++}.json`);return mn(s,JSON.stringify(t),"utf-8"),{path:s,count:e}}function Jo(r){let t=typeof r=="string"?r.toUpperCase():"";return t==="ERROR"?"error":t==="INFO"||t==="INVENTORY"||t==="EXPERIMENT"?"info":"warning"}var Lf=Object.fromEntries(Go.rules.map(r=>[r.id,r.severity]));function jf(r,t){if(t)return Jo(t);let e=Lf[r];return e?Jo(e):"warning"}function $f(r){let t=String(r||""),e=t.indexOf("{");if(e<0)return[];let n;try{n=JSON.parse(t.slice(e))}catch{return[]}return(n&&Array.isArray(n.results)?n.results:[]).map(i=>{if(!i||typeof i!="object")return null;let o=i.start&&typeof i.start=="object"?i.start:{},a=i.extra&&typeof i.extra=="object"?i.extra:{};return{file:i.path||"",line:Number.isFinite(o.line)?o.line:"",severity:jf(i.check_id,a.severity),rule:i.check_id||"",message:(a.message||"").trim()}}).filter(i=>i&&(i.file||i.message))}function Pf(r,t,e=4e3){let n=new Set(t.map(o=>o.toLowerCase())),s=[r],i=0;for(;s.length;){let o=s.pop(),a;try{a=Mo(o,{withFileTypes:!0})}catch{continue}for(let c of a){if(++i>e)return!1;if(c.isDirectory())!Fo.has(c.name)&&!c.name.startsWith(".")&&s.push(U(o,c.name));else if(c.isFile()&&n.has(gr(c.name).toLowerCase()))return!0}}return!1}function Cf(r){let t=String(r||"").trim();if(!t)return[];let e;try{e=JSON.parse(t)}catch{return[]}return(Array.isArray(e)?e:e&&Array.isArray(e.diagnostics)?e.diagnostics:[]).map(s=>{if(!s||typeof s!="object")return null;let i=Array.isArray(s.labels)&&s.labels.length?s.labels[0]:null,o=i&&i.span?i.span:null;return{file:s.filename||o&&o.filename||"",line:o&&Number.isFinite(o.line)?o.line:"",severity:s.severity||"warning",rule:s.code||"",message:s.message||""}}).filter(s=>s&&(s.file||s.message))}function Uf(r){let t=String(r||"").trim();if(!t)return[];let e;try{e=JSON.parse(t)}catch{return[]}return Array.isArray(e)?e.map(n=>n&&typeof n=="object"?{file:n.filename||"",line:n.location&&Number.isFinite(n.location.row)?n.location.row:"",severity:"warning",rule:n.code||"",message:n.message||""}:null).filter(n=>n&&(n.file||n.message)):[]}function Bf(r){let t=String(r||"").trim();if(!t)return[];let e=[];for(let n of t.split(`
|
|
993
|
+
`)){let s=n.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch{continue}if(!i||typeof i!="object")continue;let o=i.location||{};e.push({file:o.file||"",line:Number.isFinite(o.line)?o.line:"",severity:i.severity||"warning",rule:i.code||"",message:i.message||""})}return e.filter(n=>n.file||n.message)}var Df=[{id:"oxlint",detect:r=>J(U(r,"package.json")),langs:[".ts",".tsx",".js",".jsx",".mjs",".cjs"],bin:()=>bf(),args:(r,t={})=>{let e=t.baseDir||".",s=If.some(o=>J(U(e,o)))?null:Nf();return["--format","json",...s?["--config",s]:[],...r]},parse:Cf},{id:"semgrep",detect:r=>Pf(r,Do),langs:Do,bin:()=>kf(),args:(r,t={})=>{let e=t.baseDir||".",n=Af(e),{path:s}=xf(r);return[...n?["-rules",n]:[],"-targets",s,"-json"]},parse:$f},{id:"ruff",detect:r=>J(U(r,"pyproject.toml"))||J(U(r,"requirements.txt"))||J(U(r,"setup.py")),langs:[".py"],bin:()=>process.env.RUFF_BIN||"ruff",args:r=>["check","--output-format","json",...r],parse:Uf},{id:"staticcheck",detect:r=>J(U(r,"go.mod")),langs:[".go"],bin:()=>process.env.STATICCHECK_BIN||"staticcheck",args:r=>["-f","json",...r],parse:Bf}];function Jf(r,t,e){let n=[],s=new Set(t.map(o=>o.toLowerCase())),i=[r];for(;i.length&&n.length<e;){let o=i.pop(),a;try{a=Mo(o,{withFileTypes:!0})}catch{continue}for(let c of a){if(n.length>=e)break;c.isDirectory()?!Fo.has(c.name)&&!c.name.startsWith(".")&&i.push(U(o,c.name)):c.isFile()&&s.has(gr(c.name).toLowerCase())&&n.push(U(o,c.name))}}return n}function Mf(r,t,e){let n=e.map(a=>hf(t,a)).filter(Boolean);if(!n.length)return{scanner:r.id,skipped:"no matching files"};let s=r.bin(),i=mf(s,r.args(n,{baseDir:t}),{cwd:t,encoding:"utf-8",timeout:180*1e3,maxBuffer:32*1024*1024});if(i.error){let a=i.error.code==="ENOENT"?`binary not installed (${s})`:String(i.error.message||i.error);return{scanner:r.id,skipped:a}}let o=[];try{let a=r.parse(i.stdout,i.stderr,i.status);o=Array.isArray(a)?a.filter(Boolean):[]}catch{o=[]}return{scanner:r.id,filesScanned:n.length,findings:o}}var Ho={id:"code-scan",serverName:"code_scan",meta:yf["code-scan"],allowedTools:["mcp__code_scan__*"],description:"Code scan \u2014 run the RIGHT deterministic linter/analyzer for a checked-out repo (auto-detects the stack: JS/TS\u2192oxlint; Java/Python/Go/Ruby/PHP\u2192semgrep) and return structured findings. Fully local; the code never leaves the box.",promptFragment:`## Code Scan (deterministic linter, auto-detects the stack)
|
|
977
994
|
After you've cloned the repo, call \`scan_code\` to get DETERMINISTIC linter
|
|
978
995
|
findings for WHATEVER stack this repo is \u2014 it auto-detects (JS/TS\u2192oxlint;
|
|
979
996
|
Java/Python/Go/Ruby/PHP\u2192semgrep) and runs the matching tool. Pass \`files\` (the changed files, ideal for
|
|
980
997
|
a review) or \`dir\` (a directory to scan). Findings are GROUND-TRUTH CANDIDATES:
|
|
981
998
|
triage them for THIS change, verify each in context (false positives exist \u2014
|
|
982
999
|
trace before asserting), fold noise, and turn the real ones into inline
|
|
983
|
-
suggestions. Don't hand-lint what the tool already covers, and don't re-run it.`,resolve(){let r=
|
|
1000
|
+
suggestions. Don't hand-lint what the tool already covers, and don't re-run it.`,resolve(){let r=wf();return r?{type:"stdio",command:"node",args:[r,"../dist/code-scan.js","codeScanSkill"],env:{},description:this.description,alwaysLoad:!0}:{command:null,args:[],env:{},description:this.description}},async handleToolCall(r,t){if(r!=="scan_code")return JSON.stringify({error:`Unknown tool: ${r}`});try{let e=Array.isArray(t?.files)?t.files.filter(a=>typeof a=="string"&&a.trim()):null,n;if(t?.dir&&typeof t.dir=="string"?n=yr(t.dir):e&&e.length?n=qf(e.map(a=>yr(a))):n=process.cwd(),!J(n)||!qo(n).isDirectory())return JSON.stringify({error:`dir does not exist or is not a directory: ${n}`});let s=e?e.map(a=>yr(n,a)):null,i=[],o=0;for(let a of Df){let c=!1;try{c=!!a.detect(n)}catch{c=!1}if(!c)continue;let l=new Set(a.langs.map(u=>u.toLowerCase())),d=s?s.filter(u=>l.has(gr(u).toLowerCase())):Jf(n,a.langs,Sf),p=Mf(a,n,d);Array.isArray(p.findings)&&(o+=p.findings.length),i.push(p)}return i.length?JSON.stringify({ok:!0,baseDir:n,totalFindings:o,scanners:i}):JSON.stringify({ok:!0,baseDir:n,scanners:[],totalFindings:0,note:"No known stack detected (no package.json / pyproject / go.mod \u2026). Review by hand."})}catch(e){return JSON.stringify({error:`scan_code failed: ${e.message}`})}},tools:[{name:"scan_code",description:"Run the right deterministic linter for a checked-out repo and return structured findings. Auto-detects the stack (JS/TS \u2192 oxlint; Java/Python/Go/Ruby/PHP \u2192 semgrep OSS) and runs each matching tool, scoped to files in its languages. Pass `files` (e.g. the changed files of the PR \u2014 recommended for a review) OR `dir` (a directory to scan). Returns { scanners: [ { scanner, findings: [ { file, line, severity, rule, message } ] } ] }. Findings are CANDIDATES \u2014 verify each in context before asserting. Best-effort: a stack whose linter is not installed is skipped with a note.",input_schema:{type:"object",properties:{dir:{type:"string",description:"Absolute path to the checked-out repo (or subdirectory) to scan. Defaults to the current working directory."},files:{type:"array",items:{type:"string"},description:"Explicit list of files to scan (paths relative to `dir`, or absolute). Best for a code review \u2014 pass the PR's changed files. When omitted, the whole `dir` is walked (bounded)."}}}}]};function qf(r){if(!r.length)return process.cwd();if(r.length===1)return un(r[0]);let t=r.map(i=>i.split("/")),e=t[0],n=[];for(let i=0;i<e.length;i++){let o=e[i];if(t.every(a=>a[i]===o))n.push(o);else break}let s=n.join("/");return s&&J(s)&&qo(s).isDirectory()?s:un(r[0])}import{dirname as Ff,resolve as Kf}from"path";import{fileURLToPath as Gf}from"url";import{existsSync as Hf}from"fs";function Yf(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=Ff(Gf(import.meta.url)),t=Kf(r,"..","bin","mcp-skill.mjs");return Hf(t)?t:null}function zf(){return process.env.PROJECT_API_TOKEN||process.env.ZIBBY_USER_TOKEN||null}function Wf(){return((process.env.PROGRESS_API_URL||"").replace(/\/executions\/?$/,"")||process.env.ZIBBY_ACCOUNT_API_URL||process.env.ZIBBY_PROD_ACCOUNT_API_URL||(process.env.ZIBBY_ENV==="local"?"http://localhost:3001":"https://api-prod.zibby.app")).replace(/\/+$/,"")}var Zf={name:"trigger_agent",description:"Trigger another Zibby workflow/agent run in THIS project (fire-and-forget). Call it once per run you want to start \u2014 the agent decides which and how many. Omit workflowType to re-run THIS same agent (self-dispatch, e.g. with a different trigger input). Returns the started run's executionId; does NOT wait for it to finish.",input_schema:{type:"object",properties:{workflowType:{type:"string",description:"Which workflow to trigger (its type/slug in this project). Omit to trigger THIS same agent (self-dispatch)."},input:{type:"object",description:"The trigger payload passed to the target workflow (validated against its state schema)."}},required:[]}},Yo={id:"trigger-agent",callsBackend:!0,serverName:"trigger",allowedTools:["mcp__trigger__*"],envKeys:[],description:"Trigger another Zibby workflow/agent run in this project (agent-driven, fire-and-forget; cloud + self-hosted).",promptFragment:"## Trigger another agent (agent-driven)\nYou can start another Zibby agent run yourself with the `trigger_agent`\ntool \u2014 and YOU decide when and how many times to call it. Each call starts ONE\nindependent run (fire-and-forget) and returns its executionId; it does NOT wait for\nthat run to finish.\n- To re-run THIS same agent (self-dispatch) \u2014 e.g. to hand an item to another of\n this agent's scenarios \u2014 OMIT `workflowType` and pass the `input` for that run.\n- To trigger a DIFFERENT agent in the project, pass its `workflowType` + `input`.\nCall it once per run you want to start (loop over your items and call it for each).\nIt never throws \u2014 a failure comes back as { ok:false, error }; log it and move on.",resolve(){let r=Yf();if(!r)return{command:null,args:[],env:{},description:this.description};let t={};for(let e of["PROJECT_API_TOKEN","PROJECT_ID","WORKFLOW_TYPE","PROGRESS_API_URL","ZIBBY_ACCOUNT_API_URL","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_USER_TOKEN"])process.env[e]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[r,"../dist/triggerAgent.js","triggerAgentSkill"],env:t,description:this.description,alwaysLoad:!1}},async handleToolCall(r,t={}){if(r!=="trigger_agent")return JSON.stringify({ok:!1,error:`unknown tool: ${r}`});try{let e=process.env.PROJECT_ID,n=zf(),s=typeof t.workflowType=="string"&&t.workflowType.trim()?t.workflowType.trim():(process.env.WORKFLOW_TYPE||"").trim();if(!e)return JSON.stringify({ok:!1,error:"PROJECT_ID not set \u2014 cannot resolve the target project."});if(!n)return JSON.stringify({ok:!1,error:"PROJECT_API_TOKEN not set \u2014 cannot authenticate the trigger."});if(!s)return JSON.stringify({ok:!1,error:"No workflowType given and WORKFLOW_TYPE is unset \u2014 nothing to trigger."});let i=`${Wf()}/projects/${encodeURIComponent(e)}/workflows/${encodeURIComponent(s)}/trigger`,o=new AbortController,a=setTimeout(()=>o.abort(),2e4),c;try{c=await fetch(i,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${n}`},body:JSON.stringify({input:t.input&&typeof t.input=="object"?t.input:{}}),signal:o.signal})}finally{clearTimeout(a)}let l=await c.text().catch(()=>""),d;try{d=l?JSON.parse(l):{}}catch{d={raw:l}}if(!c.ok)return JSON.stringify({ok:!1,error:`trigger failed (HTTP ${c.status})`,detail:d&&(d.error||d.message)||l.slice(0,300)});let p=d.executionId||d.execution?.id||d.id||null;return JSON.stringify({ok:!0,workflowType:s,executionId:p,note:"run started (fire-and-forget)"})}catch(e){return JSON.stringify({ok:!1,error:`trigger_agent failed: ${e?.message||String(e)}`})}},tools:[Zf]};import{spawnSync as Vf}from"node:child_process";import{existsSync as yn,mkdirSync as zo,readdirSync as Xf,writeFileSync as Qf}from"node:fs";import{createHash as ey}from"node:crypto";import{join as _r}from"node:path";import{SKILL_META as ty}from"@zibby/skill-ids";function Wo(){return process.env.CBM_BIN||"/usr/local/bin/codebase-memory-mcp"}function Zo(){if(process.env.CBM_CACHE_DIR)return process.env.CBM_CACHE_DIR;let r=process.env.WORKSPACE||process.env.ZIBBY_WORKSPACE;return r?_r(r,".zibby","cbm-cache"):"/tmp/zibby-cbm-cache"}function ry(){let r=process.env.WORKSPACE||process.env.ZIBBY_WORKSPACE||"/workspace",t=_r(r,".zibby","repos");try{if(yn(t)){let e=Xf(t,{withFileTypes:!0}).filter(n=>n.isDirectory()).map(n=>_r(t,n.name));if(e.length===1)return e[0];if(e.length>1)return t}}catch{}return null}function ny(r){return ey("sha256").update(r).digest("hex").slice(0,16)}var Vo={id:"codebase-memory",serverName:"codebase_memory",meta:ty["codebase-memory"],allowedTools:["mcp__codebase_memory__*"],description:"Codebase memory \u2014 code-graph + semantic index over the checked-out repo (architecture, graph search, dependency trace, change detection)",promptFragment:`## Codebase Memory (code-graph + semantic index over THIS repo)
|
|
984
1001
|
The checked-out repository is indexed into a queryable code graph + semantic
|
|
985
1002
|
index. Reach for these instead of blindly grepping when you need structure,
|
|
986
1003
|
relationships, or "where does X live / what depends on Y":
|
|
@@ -992,8 +1009,8 @@ relationships, or "where does X live / what depends on Y":
|
|
|
992
1009
|
- get_code_snippet / search_code: pull the exact code for a node / text match.
|
|
993
1010
|
- index_status / list_projects: confirm the index is present before querying.
|
|
994
1011
|
The repo is indexed for you at the start of the run; if a query comes back
|
|
995
|
-
empty, call index_status, and only re-index (index_repository) if needed.`,resolve(){let r=Zo();try{zo(r,{recursive:!0})}catch{}let t={CBM_CACHE_DIR:r};return process.env.WORKSPACE&&(t.WORKSPACE=process.env.WORKSPACE),{type:"stdio",command:Wo(),args:[],env:t,description:this.description,alwaysLoad:!0}},invokeAgentOptions(){try{let r=
|
|
996
|
-
`)}catch{}}catch{}return{}}};import{existsSync as Xo,readFileSync as
|
|
1012
|
+
empty, call index_status, and only re-index (index_repository) if needed.`,resolve(){let r=Zo();try{zo(r,{recursive:!0})}catch{}let t={CBM_CACHE_DIR:r};return process.env.WORKSPACE&&(t.WORKSPACE=process.env.WORKSPACE),{type:"stdio",command:Wo(),args:[],env:t,description:this.description,alwaysLoad:!0}},invokeAgentOptions(){try{let r=ry();if(!r)return{};let t=Zo();try{zo(t,{recursive:!0})}catch{}let e=_r(t,`.cbm-indexed-${ny(r)}`);if(yn(e))return{};let n=Wo();if(!yn(n)&&!process.env.CBM_BIN)return{};let s=Vf(n,["cli","index_repository",JSON.stringify({repo_path:r})],{env:{...process.env,CBM_CACHE_DIR:t},encoding:"utf-8",timeout:300*1e3,maxBuffer:32*1024*1024});try{Qf(e,`${new Date().toISOString()} status=${s.status}
|
|
1013
|
+
`)}catch{}}catch{}return{}}};import{existsSync as Xo,readFileSync as sy}from"node:fs";import{homedir as iy}from"node:os";import{join as oy,dirname as ay,resolve as cy}from"node:path";import{fileURLToPath as ly}from"node:url";import{SKILL_META as dy}from"@zibby/skill-ids";var py=3e4,gn=1;function uy(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=ay(ly(import.meta.url)),t=cy(r,"..","bin","mcp-skill.mjs");return Xo(t)?t:null}function my(){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=oy(iy(),".zibby","config.json");return Xo(r)&&JSON.parse(sy(r,"utf-8")).sessionToken||null}catch{return null}}function hy(){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 fy(){let r={};for(let[t,e]of Object.entries(process.env)){let n=/^ZIBBY_STORE__(.+)$/.exec(t);if(!n)continue;let s=typeof e=="string"?e.trim():"";s&&(r[n[1]]=s)}return r}function _n(r){let t=fy(),e=Object.keys(t),n=typeof r=="string"?r.trim():"";return n?Object.prototype.hasOwnProperty.call(t,n)?{storeId:t[n],name:n}:{error:`unknown store '${n}'; available: ${e.join(", ")}`}:e.length===1?{storeId:t[e[0]],name:e[0]}:e.length===0?{error:"no knowledge-base store is bound to this agent"}:{error:`multiple stores are bound; pass \`store\` (one of: ${e.join(", ")})`}}async function bn(r,t,e){let n=my();if(!n)throw new Error("No backend credential (PROJECT_API_TOKEN). The knowledge base is only available inside a Zibby run.");let s=`${hy()}/datasets/stores/${encodeURIComponent(r)}/${t}`,i={Authorization:`Bearer ${n}`,"Content-Type":"application/json"},o=JSON.stringify(e),a;for(let c=0;c<=gn;c++){let l=new AbortController,d=setTimeout(()=>l.abort(),py);try{let p=await fetch(s,{method:"POST",headers:i,body:o,signal:l.signal}),u=await p.text().catch(()=>"");if(p.status>=500&&c<gn){a=new Error(`store ${p.status}`);continue}if(!p.ok)throw new Error(`gbrain ${t} failed (${p.status}): ${u.slice(0,300)}`);try{return JSON.parse(u)}catch{throw new Error(`store returned non-JSON: ${u.slice(0,200)}`)}}catch(p){if(a=p,!((p?.name==="AbortError"||p?.code==="ECONNREFUSED"||/fetch failed|network/i.test(String(p?.message)))&&c<gn))break}finally{clearTimeout(d)}}throw a||new Error(`gbrain ${t} request failed`)}var Qo={id:"gbrain",callsBackend:!0,serverName:"gbrain",allowedTools:["mcp__gbrain__*"],meta:dy.gbrain,description:"Knowledge base (GBrain) \u2014 ingest source documents into, semantically query, and prune a per-tenant Postgres/pgvector brain (a `postgres`-type store, brokered by the control-plane)",promptFragment:`## Knowledge Base (GBrain \u2014 per-tenant document brain)
|
|
997
1014
|
You have a per-tenant KNOWLEDGE BASE (a Postgres + pgvector "brain"), bound as a
|
|
998
1015
|
\`postgres\`-type store in the "AVAILABLE STORES" block below. Ingested documents
|
|
999
1016
|
are addressed by a STABLE \`sourceId\` so re-ingesting the same source UPSERTS
|
|
@@ -1004,7 +1021,7 @@ Tools:
|
|
|
1004
1021
|
Pass deleted:true to remove a source that no longer exists upstream.
|
|
1005
1022
|
- gbrain_query({ query, topK, store? }): semantic search; returns the topK most
|
|
1006
1023
|
relevant document chunks with their sourceId and relevance score.
|
|
1007
|
-
- gbrain_delete({ sourceIds, store? }): remove documents by their stable sourceId(s).`,resolve(){let r=
|
|
1024
|
+
- gbrain_delete({ sourceIds, store? }): remove documents by their stable sourceId(s).`,resolve(){let r=uy();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]);for(let e of Object.keys(process.env))/^ZIBBY_STORE__.+$/.test(e)&&process.env[e]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[r,"../dist/gbrain.js","gbrainSkill"],env:t,description:this.description,alwaysLoad:!0}},async handleToolCall(r,t){try{switch(r){case"gbrain_ingest":{let e=Array.isArray(t?.docs)?t.docs:null;if(!e)return JSON.stringify({error:"docs is required (array of { sourceId, markdown, deleted? })"});if(e.length===0)return JSON.stringify({ok:!0,upserted:0,deleted:0,chunks:0,note:"empty docs \u2014 nothing to ingest"});if(e.find(a=>!a||typeof a.sourceId!="string"||!a.sourceId.trim())!==void 0)return JSON.stringify({error:"every doc requires a non-empty string sourceId"});let s=e.find(a=>a.deleted!==!0&&(typeof a.markdown!="string"||!a.markdown.length));if(s!==void 0)return JSON.stringify({error:`doc "${s.sourceId}" has no markdown (required unless deleted:true)`});let i=_n(t?.store);if(i.error)return JSON.stringify({error:i.error});let o=await bn(i.storeId,"ingest",{docs:e});return JSON.stringify({...o,store:i.name,storeId:i.storeId})}case"gbrain_query":{let e=typeof t?.query=="string"?t.query.trim():"";if(!e)return JSON.stringify({error:"query is required"});let n=_n(t?.store);if(n.error)return JSON.stringify({error:n.error});let s={query:e};Number.isInteger(t?.topK)&&t.topK>0&&(s.topK=Math.min(t.topK,50));let i=await bn(n.storeId,"query",s);return JSON.stringify({...i,store:n.name,storeId:n.storeId})}case"gbrain_delete":{let e=Array.isArray(t?.sourceIds)?t.sourceIds.filter(i=>typeof i=="string"&&i.trim()):null;if(!e||e.length===0)return JSON.stringify({error:"sourceIds is required (non-empty array of strings)"});let n=_n(t?.store);if(n.error)return JSON.stringify({error:n.error});let s=await bn(n.storeId,"delete",{sourceIds:e});return JSON.stringify({...s,store:n.name,storeId:n.storeId})}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(e){return JSON.stringify({error:String(e?.message||e)})}},tools:[{name:"gbrain_ingest",description:'Upsert documents into the knowledge base. Each doc is { sourceId, markdown, deleted? }; sourceId is a STABLE id (e.g. "owner/repo#docs/x.md" or a Lark doc token) so re-ingesting the same source UPDATES in place instead of duplicating. Set deleted:true to remove a source.',input_schema:{type:"object",properties:{docs:{type:"array",description:"Documents to upsert.",items:{type:"object",properties:{sourceId:{type:"string",description:"Stable source id \u2014 the upsert/delete key."},markdown:{type:"string",description:"The normalized markdown content of the document."},deleted:{type:"boolean",description:"When true, remove this sourceId from the brain (markdown ignored)."}},required:["sourceId"]}},store:{type:"string",description:"The bound knowledge-base store NAME (from AVAILABLE STORES). Omit if exactly one store is bound."}},required:["docs"]}},{name:"gbrain_query",description:"Semantic-search the knowledge base and return the most relevant document chunks (each with its sourceId and relevance score).",input_schema:{type:"object",properties:{query:{type:"string",description:"Natural-language query."},topK:{type:"integer",description:"How many chunks to return (default 8, max 50)."},store:{type:"string",description:"The bound knowledge-base store NAME (from AVAILABLE STORES). Omit if exactly one store is bound."}},required:["query"]}},{name:"gbrain_delete",description:"Remove documents from the knowledge base by their stable sourceId(s).",input_schema:{type:"object",properties:{sourceIds:{type:"array",description:"Stable source ids to delete.",items:{type:"string"}},store:{type:"string",description:"The bound knowledge-base store NAME (from AVAILABLE STORES). Omit if exactly one store is bound."}},required:["sourceIds"]}}]};import{existsSync as fe,readFileSync as It,readdirSync as kn,mkdirSync as yy,writeFileSync as et,statSync as ra}from"fs";import{join as E,resolve as wn,relative as na,dirname as sa}from"path";import{fileURLToPath as gy}from"url";import{createRequire as _y}from"module";var by=_y(import.meta.url),ky=`## Workflow Builder
|
|
1008
1025
|
|
|
1009
1026
|
You can help users build custom AI workflows using the Zibby workflow framework.
|
|
1010
1027
|
|
|
@@ -1114,9 +1131,9 @@ Call with no arguments to see all available topics.
|
|
|
1114
1131
|
- Workflow names must be kebab-case (e.g., ticket-triage, pr-review).
|
|
1115
1132
|
- State flows through: each node's validated output is stored under its name in state (e.g., state.classify_ticket).
|
|
1116
1133
|
- Downstream nodes reference upstream outputs in their prompt function (e.g., \\\`\\\${JSON.stringify(state.classify_ticket, null, 2)}\\\`).
|
|
1117
|
-
- Nodes can declare skills to get MCP tool access \u2014 the framework handles server lifecycle automatically.`,ia=/^[a-z][a-z0-9-]{0,62}[a-z0-9]$/;function oa(r){return`${r.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join("")}Workflow`}function vt(r){return`${r.replace(/_([a-z])/g,(t,e)=>e.toUpperCase())}Node`}function
|
|
1134
|
+
- Nodes can declare skills to get MCP tool access \u2014 the framework handles server lifecycle automatically.`,ia=/^[a-z][a-z0-9-]{0,62}[a-z0-9]$/;function oa(r){return`${r.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join("")}Workflow`}function vt(r){return`${r.replace(/_([a-z])/g,(t,e)=>e.toUpperCase())}Node`}function wy(r){let t=r?.agent;return t?t.provider?t.provider:t.gemini?"gemini":t.codex?"codex":t.claude?"claude":t.cursor?"cursor":process.env.AGENT_TYPE||"cursor":process.env.AGENT_TYPE||"cursor"}async function Sy(r){let t=wn(r,".zibby.config.mjs");if(!fe(t))return{};try{return(await import(t)).default||{}}catch{return{}}}function vy(){try{let r=sa(by.resolve("@zibby/core/package.json")),t=E(r,"templates","browser-test-automation"),e=It(E(t,"nodes","preflight.mjs"),"utf-8"),n=It(E(t,"graph.mjs"),"utf-8");return{preflight:e,graph:n}}catch{return null}}var ea=sa(gy(import.meta.url));function aa(){let r=wn(ea,"..","..","..","docsite","docs");if(fe(r))return r;let t=wn(ea,"..","docs");return fe(t)?t:null}function ta(){let r=aa();if(!r)return[];try{let t=(e,n="")=>{let s=[];for(let i of kn(e)){let o=E(e,i);try{if(ra(o).isDirectory())s=s.concat(t(o,`${n}${i}/`));else if(i.endsWith(".md")){let a=`${n}${i.replace(/\.md$/,"")}`;s.push(a)}}catch{}}return s};return t(r)}catch{return[]}}function ca(r){let t=aa();if(!t)return null;let e=E(t,`${r}.md`);if(!fe(e))return null;try{return It(e,"utf-8")}catch{return null}}function Iy(r){let t=r.nodes.map(o=>{let a=o.inputFields?.length?`Input fields: ${o.inputFields.join(", ")}`:"Input: receives full state",c=o.outputFields?.length?`Output fields: ${o.outputFields.join(", ")}`:"Output: determined by task",l=o.skills?.length?`Skills: ${o.skills.join(", ")}`:"";return`- ${o.name}: ${o.description}. ${a}. ${c}.${l?` ${l}`:""}`}).join(`
|
|
1118
1135
|
`),e=r.edges.map(o=>o.condition?`- ${o.from} \u2192 ${o.to} (conditional: ${o.condition})`:`- ${o.from} \u2192 ${o.to}`).join(`
|
|
1119
|
-
`),n=
|
|
1136
|
+
`),n=vy(),s=ca("custom-workflows"),i="";return n&&(i+=`
|
|
1120
1137
|
## Real working examples from the Zibby framework
|
|
1121
1138
|
|
|
1122
1139
|
### Example node (preflight.mjs) \u2014 a prompt-only node with Zod schema and onComplete hook:
|
|
@@ -1193,7 +1210,7 @@ Return a JSON object with this exact structure:
|
|
|
1193
1210
|
}
|
|
1194
1211
|
}
|
|
1195
1212
|
|
|
1196
|
-
IMPORTANT: Return ONLY valid JSON. No markdown fences, no explanation outside the JSON.`}async function la(r,t){let e=await
|
|
1213
|
+
IMPORTANT: Return ONLY valid JSON. No markdown fences, no explanation outside the JSON.`}async function la(r,t){let e=await Sy(t),n=wy(e);try{let{invokeAgent:s}=await import("@zibby/core"),i=Iy(r),o=await s(i,{state:{agentType:n,config:e,cwd:t,workspace:t}},{model:e?.agent?.[n]?.model||"auto",workspace:t,config:e,timeout:12e4}),c=(typeof o=="string"?o:o?.raw||JSON.stringify(o?.structured||o)).match(/\{[\s\S]*\}/);if(!c)throw new Error("Agent did not return valid JSON");return JSON.parse(c[0])}catch(s){return console.warn(`Agent code generation failed (${s.message}), using templates`),Ny(r)}}function Ny(r){let t={};for(let e of r.nodes){let n=vt(e.name),s=`${e.name.split("_").map(c=>c.charAt(0).toUpperCase()+c.slice(1)).join("")}OutputSchema`,i=e.outputFields?.length?e.outputFields.map(c=>` ${c}: z.string().describe('${c}'),`).join(`
|
|
1197
1214
|
`):` summary: z.string().describe('Summary of the result'),
|
|
1198
1215
|
status: z.enum(['ok', 'warn', 'error']).describe('Overall status'),`,o=r.edges.filter(c=>c.to===e.name&&c.from!=="START").map(c=>`Previous step (${c.from}): \${JSON.stringify(state.${c.from} || {}, null, 2)}`).join(`
|
|
1199
1216
|
`),a=o?`${e.description}
|
|
@@ -1215,7 +1232,7 @@ export const ${n} = {
|
|
|
1215
1232
|
prompt: (state) => \`${a}\`,
|
|
1216
1233
|
outputSchema: ${s},
|
|
1217
1234
|
};
|
|
1218
|
-
`}}return{nodes:t}}function
|
|
1235
|
+
`}}return{nodes:t}}function Oy(r,t,e,n){let s=t.toLowerCase(),i=oa(s),o=E(r,".zibby","workflows",s),a=E(o,"nodes");yy(a,{recursive:!0});let c=e.nodes.map(y=>y.name);for(let y of e.nodes){let _=n.nodes?.[y.name]?.code;_&&et(E(a,`${y.name.replace(/_/g,"-")}.mjs`),_,"utf-8")}let l=c.map(y=>{let _=vt(y),g=y.replace(/_/g,"-");return`export { ${_} } from './${g}.mjs';`});et(E(a,"index.mjs"),`${l.join(`
|
|
1219
1236
|
`)}
|
|
1220
1237
|
`,"utf-8");let d=c[0],p=c.map(y=>vt(y)).join(", "),u=c.map(y=>` graph.addNode('${y}', ${vt(y)});`).join(`
|
|
1221
1238
|
`),m=e.edges.map(y=>y.condition?` graph.addConditionalEdges('${y.from}', (state) => {
|
|
@@ -1241,17 +1258,17 @@ ${m}
|
|
|
1241
1258
|
}
|
|
1242
1259
|
}
|
|
1243
1260
|
`;et(E(o,"graph.mjs"),h,"utf-8");let f={name:s,description:e.description||`${i} workflow`,entryClass:i,triggers:{api:!0}};et(E(o,"workflow.json"),`${JSON.stringify(f,null,2)}
|
|
1244
|
-
`,"utf-8");let b=["graph.mjs","workflow.json","nodes/index.mjs",...c.map(y=>`nodes/${y.replace(/_/g,"-")}.mjs`)];return{workflowDir:na(r,o),files:b,className:i,slug:s}}async function
|
|
1245
|
-
`,_=fe(f)?It(f,"utf-8"):"";return _.includes(b)||et(f,_+y,"utf-8"),JSON.stringify({ok:!0,file:`nodes/${h}`,exportName:b,message:`Node "${c}" added. Update graph.mjs to wire it into the graph.`})}async function Ry(r,t){let{name:e,projectId:n}=r,s=(e||"").toLowerCase();if(!s)return JSON.stringify({error:"Workflow name is required."});if(!n)return JSON.stringify({error:"projectId is required."});let i=E(t,".zibby","workflows",s);if(!fe(i))return JSON.stringify({error:`Workflow "${s}" not found at .zibby/workflows/${s}/`});try{let{execSync:o}=await import("child_process"),a=o(`node "${E(t,"packages/cli/bin/zibby.js")}" deploy ${s} --project ${n}`,{cwd:t,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]});return JSON.stringify({ok:!0,output:a.trim()})}catch{try{let{execSync:a}=await import("child_process"),c=a(`npx zibby deploy ${s} --project ${n}`,{cwd:t,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]});return JSON.stringify({ok:!0,output:c.trim()})}catch(a){return JSON.stringify({error:`Deploy failed: ${a.message}`})}}}function Ey(r){let t=E(r,".zibby","workflows");if(!fe(t))return JSON.stringify({workflows:[],message:"No workflows found. Use build_workflow to create one."});let n=kn(t).filter(s=>{try{return ra(E(t,s)).isDirectory()}catch{return!1}}).map(s=>{let i=E(t,s,"workflow.json"),o={};try{o=JSON.parse(It(i,"utf-8"))}catch{}let a=E(t,s,"nodes"),c=0;try{c=kn(a).filter(l=>l.endsWith(".mjs")&&l!=="index.mjs").length}catch{}return{name:s,description:o.description||"",nodeCount:c,path:na(r,E(t,s))}});return JSON.stringify({workflows:n})}var da={id:"workflow-builder",description:"Build, scaffold, and deploy custom AI workflows via conversation",envKeys:[],promptFragment:by,tools:[{name:"design_workflow",description:"Design a workflow spec (nodes, edges, descriptions) for the user to review before building. Call this after understanding requirements.",input_schema:{type:"object",properties:{name:{type:"string",description:"Workflow name in kebab-case (e.g., ticket-triage)"},description:{type:"string",description:"What the workflow does"},nodes:{type:"array",items:{type:"object",properties:{name:{type:"string",description:"Node name in snake_case (e.g., classify_ticket)"},description:{type:"string",description:"What this node does \u2014 be specific about input/output"},inputFields:{type:"array",items:{type:"string"},description:"Key fields this node reads from state"},outputFields:{type:"array",items:{type:"string"},description:"Key fields this node produces"}},required:["name","description"]},description:"Workflow nodes (processing steps)"},edges:{type:"array",items:{type:"object",properties:{from:{type:"string",description:"Source node name"},to:{type:"string",description:'Target node name (or "END")'},condition:{type:"string",description:"JS expression for conditional routing (optional)"}},required:["from","to"]},description:"Edges connecting nodes. If omitted, nodes are wired linearly."}},required:["name","description","nodes"]}},{name:"build_workflow",description:"Generate real workflow code on disk from a design spec. Uses the configured AI agent for high-quality code generation.",input_schema:{type:"object",properties:{name:{type:"string",description:"Workflow name (from design_workflow)"},spec:{type:"object",description:"The full spec object returned by design_workflow",properties:{name:{type:"string"},description:{type:"string"},nodes:{type:"array",items:{type:"object"}},edges:{type:"array",items:{type:"object"}}}}},required:["name","spec"]}},{name:"add_node",description:"Add a new node to an existing workflow. Generates the node file and updates the barrel export.",input_schema:{type:"object",properties:{workflowName:{type:"string",description:"Existing workflow name (kebab-case)"},nodeName:{type:"string",description:"New node name (snake_case)"},description:{type:"string",description:"What this node does"},inputFields:{type:"array",items:{type:"string"},description:"Fields read from state"},outputFields:{type:"array",items:{type:"string"},description:"Fields produced"}},required:["workflowName","nodeName","description"]}},{name:"deploy_workflow",description:"Deploy a workflow to Zibby Cloud. Returns the trigger URL.",input_schema:{type:"object",properties:{name:{type:"string",description:"Workflow name to deploy"},projectId:{type:"string",description:"Target project ID"}},required:["name","projectId"]}},{name:"list_workflows",description:"List all local workflows in .zibby/workflows/.",input_schema:{type:"object",properties:{}}},{name:"explore_framework_docs",description:"Read Zibby framework documentation on demand. Call this before building complex workflows or when you need details on advanced patterns (middleware, conditional routing, skills, deployment, CLI commands).",input_schema:{type:"object",properties:{topic:{type:"string",description:'Doc topic to read (e.g., "workflow", "custom-workflows", "cli-reference", "packages/core", "packages/skills", "integrations/jira"). Call with no topic to list all available docs.'}}}}],async handleToolCall(r,t,e){let n=e?.options?.workspace||process.cwd();try{switch(r){case"design_workflow":return await Oy(t);case"build_workflow":return await Ty(t,n);case"add_node":return await Ay(t,n);case"deploy_workflow":return await Ry(t,n);case"list_workflows":return Ey(n);case"explore_framework_docs":{let s=(t.topic||"").trim();if(!s){let o=ta();return JSON.stringify({available:o,hint:"Call again with a topic to read its content."})}let i=ca(s);if(!i){let o=ta();return JSON.stringify({error:`Doc "${s}" not found.`,available:o})}return JSON.stringify({topic:s,content:i})}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(s){return JSON.stringify({error:s.message})}},resolve(){return null}};import{resolveIntegrationToken as xy}from"@zibby/core/backend-client.js";var vn=Object.freeze({id:"openai_billing",callsBackend:!0,requiresIntegration:S.OPENAI_BILLING,description:"OpenAI organization billing/usage admin API (paste sk-admin-... key)"}),In=Object.freeze({id:"anthropic_billing",callsBackend:!0,requiresIntegration:S.ANTHROPIC_BILLING,description:"Anthropic organization cost/usage admin API (paste sk-ant-admin-... key)"}),Nn=Object.freeze({id:"cursor_admin",callsBackend:!0,requiresIntegration:S.CURSOR_ADMIN,description:"Cursor Team/Enterprise admin API (paste admin key)"});function pa(r){return Math.floor(r/1e3)}function Sn(r){return new Date(r).toISOString().slice(0,10)}function ua(r){return new Date(r).toISOString()}async function Nt(r){let t=await xy(r);if(!t?.token)throw new Error(`${r} token resolver returned no token`);return t.token}async function ma({startMs:r,endMs:t,groupBy:e=["project_id","line_item"]}){let n=await Nt("openai_billing"),s=[],i=0,o=e.map(c=>`group_by[]=${encodeURIComponent(c)}`).join("&"),a=null;for(let c=0;c<50;c++){let d=`https://api.openai.com/v1/organization/costs?${[`start_time=${pa(r)}`,`end_time=${pa(t)}`,"bucket_width=1d","limit=180",o,a?`page=${encodeURIComponent(a)}`:""].filter(Boolean).join("&")}`,p=await fetch(d,{headers:{Authorization:`Bearer ${n}`}});if(!p.ok){let m=await p.text().catch(()=>"");throw new Error(`OpenAI costs API ${p.status}: ${m.slice(0,200)}`)}let u=await p.json();for(let m of u.data||[]){i+=1;let h=Sn((m.start_time||0)*1e3);for(let f of m.results||[])s.push({provider:"openai",day:h,costUsd:Number(f.amount?.value??0),projectId:f.project_id||void 0,apiKeyId:f.api_key_id||void 0,model:f.line_item||void 0})}if(!u.has_more||!u.next_page)break;a=u.next_page}return{ok:!0,items:s,rawBuckets:i}}async function Ly(){let r=await Nt("openai_billing"),e=await fetch("https://api.openai.com/v1/organization/projects?limit=100",{headers:{Authorization:`Bearer ${r}`}});if(!e.ok){let i=await e.text().catch(()=>"");throw new Error(`OpenAI projects API ${e.status}: ${i.slice(0,200)}`)}let n=await e.json(),s=new Map;for(let i of n.data||[])s.set(i.id,i.name);return s}async function ha({startMs:r,endMs:t,groupBy:e=["workspace_id"]}){let n=await Nt("anthropic_billing"),s=[],i=0,o=e.map(c=>`group_by[]=${encodeURIComponent(c)}`).join("&"),a=null;for(let c=0;c<50;c++){let d=`https://api.anthropic.com/v1/organizations/cost_report?${[`starting_at=${encodeURIComponent(ua(r))}`,`ending_at=${encodeURIComponent(ua(t))}`,"bucket=1d","limit=100",o,a?`page=${encodeURIComponent(a)}`:""].filter(Boolean).join("&")}`,p=await fetch(d,{headers:{"x-api-key":n,"anthropic-version":"2023-06-01"}});if(!p.ok){let m=await p.text().catch(()=>"");throw new Error(`Anthropic cost_report ${p.status}: ${m.slice(0,200)}`)}let u=await p.json();for(let m of u.data||[]){i+=1;let h=(m.starting_at||"").slice(0,10);for(let f of m.results||[])s.push({provider:"anthropic",day:h,costUsd:Number(f.amount??f.cost??0),workspaceId:f.workspace_id||void 0,apiKeyId:f.api_key_id||void 0,model:f.model||void 0,tokensIn:f.uncached_input_tokens!=null?Number(f.uncached_input_tokens):void 0,tokensOut:f.output_tokens!=null?Number(f.output_tokens):void 0,cachedTokens:f.cached_input_tokens!=null?Number(f.cached_input_tokens):void 0})}if(!u.has_more||!u.next_page)break;a=u.next_page}return{ok:!0,items:s,rawBuckets:i}}async function jy(){let r=await Nt("anthropic_billing"),e=await fetch("https://api.anthropic.com/v1/organizations/workspaces?limit=100",{headers:{"x-api-key":r,"anthropic-version":"2023-06-01"}});if(!e.ok){let i=await e.text().catch(()=>"");throw new Error(`Anthropic workspaces ${e.status}: ${i.slice(0,200)}`)}let n=await e.json(),s=new Map;for(let i of n.data||[])s.set(i.id,i.name);return s}async function fa({startMs:r,endMs:t}){let e=await Nt("cursor_admin"),n=Sn(r),s=Sn(t),i=`https://api.cursor.com/teams/daily-usage-data?startDate=${n}&endDate=${s}`,o=await fetch(i,{headers:{Authorization:`Bearer ${e}`}});if(!o.ok){let d=await o.text().catch(()=>"");throw new Error(`Cursor daily-usage ${o.status}: ${d.slice(0,200)}`)}let a=await o.json(),c=[],l=0;for(let d of a.data||[]){l+=1;let p=d.date;for(let u of d.userMetrics||[]){for(let m of u.modelUsage||[]){let h=Number(m.acceptedLines??0),f=Number(m.suggestedLines??0);c.push({provider:"cursor",day:p,costUsd:Number(m.totalCents??0)/100,userEmail:u.email,model:m.model,requestCount:Number(m.requestCount??0),acceptanceRate:f>0?h/f:void 0})}(!u.modelUsage||u.modelUsage.length===0)&&c.push({provider:"cursor",day:p,costUsd:Number(u.totalCents??0)/100,userEmail:u.email})}}return{ok:!0,items:c,rawBuckets:l}}async function $y({startMs:r,endMs:t}){let[e,n,s]=await Promise.allSettled([ma({startMs:r,endMs:t}),ha({startMs:r,endMs:t}),fa({startMs:r,endMs:t})]),i=d=>d.status==="fulfilled"?d.value:{ok:!1,error:d.reason?.message||String(d.reason),items:[]},o=i(e),a=i(n),c=i(s),l=[{provider:"openai",totalUsd:o.items.reduce((d,p)=>d+(p.costUsd||0),0)},{provider:"anthropic",totalUsd:a.items.reduce((d,p)=>d+(p.costUsd||0),0)},{provider:"cursor",totalUsd:c.items.reduce((d,p)=>d+(p.costUsd||0),0)}];return{openai:o,anthropic:a,cursor:c,totals:l}}function Py(r,t){let e=new Map;for(let n of r){let s=t(n);if(!s)continue;let i=e.get(s)||{key:s,totalUsd:0,count:0};i.totalUsd+=n.costUsd||0,i.count+=1,e.set(s,i)}return[...e.values()].sort((n,s)=>s.totalUsd-n.totalUsd)}function Cy(r){if(!r.length)return{mean:0,stddev:0};let t=r.reduce((n,s)=>n+s,0)/r.length,e=r.reduce((n,s)=>n+(s-t)**2,0)/r.length;return{mean:t,stddev:Math.sqrt(e)}}import{SKILL_IDS as bS}from"@zibby/skill-ids";import{z as w}from"zod";var Ot=["ok","info","warn","critical"],Uy=w.object({primary:w.string().min(1).max(200).describe('Headline number or phrase (e.g. "$8,240"). Rendered in large/bold.'),delta:w.object({value:w.string().max(40).describe('Delta vs baseline (e.g. "+12% wow"). Free-form string.'),direction:w.enum(["up","down","flat"]).optional(),severity:w.enum(Ot).optional().describe("Color severity for the delta (warn/critical highlights regressions).")}).optional().describe("Optional comparison vs baseline. Renders inline next to primary."),summary:w.string().max(800).optional().describe('One-sentence narrative ("why this number"). Plain prose.')}),By=w.object({kind:w.literal("trend"),title:w.string().max(120).optional(),labels:w.array(w.string().max(60)).min(2).max(20).describe('Bucket labels (e.g. ["Week-3", "Week-2", "Week-1", "This wk"]).'),values:w.array(w.number()).min(2).max(20).describe("Numeric values, one per label. Must match labels.length."),highlight:w.enum(["last","max","min","none"]).default("last").optional().describe("Which bucket to visually highlight in the rendered card."),severity:w.enum(Ot).optional()}),Dy=w.object({kind:w.literal("table"),title:w.string().max(120).optional(),headers:w.array(w.string().max(40)).min(1).max(8),rows:w.array(w.array(w.union([w.string().max(200),w.number()])).min(1).max(8)).max(40).describe("2D matrix. Each inner array must have headers.length entries.")}),Jy=w.object({kind:w.literal("callouts"),title:w.string().max(120).optional(),tone:w.enum(Ot).default("info").optional(),items:w.array(w.string().min(1).max(600)).min(1).max(10).describe("Each item renders as a bullet with a severity emoji.")}),My=w.object({kind:w.literal("breakdown"),title:w.string().max(120).optional(),rows:w.array(w.object({label:w.string().min(1).max(80),value:w.string().min(1).max(80),sub:w.string().max(120).optional(),severity:w.enum(Ot).optional()})).min(1).max(20)}),qy=w.object({kind:w.literal("paragraph"),title:w.string().max(120).optional(),text:w.string().min(1).max(3e3)}),Fy=w.discriminatedUnion("kind",[By,Dy,Jy,My,qy]),Tt=w.object({title:w.string().min(1).max(200).describe('Card title (e.g. "Weekly AI Spend Report").'),subtitle:w.string().max(200).optional().describe('Date range or smaller header (e.g. "May 13 \u2014 May 20").'),headline:Uy,sections:w.array(Fy).max(20).default([]).superRefine((r,t)=>{r.forEach((e,n)=>{e.kind==="trend"&&e.labels.length!==e.values.length&&t.addIssue({code:w.ZodIssueCode.custom,path:[n,"values"],message:"labels.length must equal values.length"}),e.kind==="table"&&!e.rows.every(s=>s.length===e.headers.length)&&t.addIssue({code:w.ZodIssueCode.custom,path:[n,"rows"],message:"every row must have headers.length entries"})})}),footer:w.object({viewUrl:w.string().url().optional().describe('Optional "View in Zibby" button URL.'),rerunUrl:w.string().url().optional().describe('Optional "Run again" button URL.')}).optional()}),B=Object.freeze({ok:"\u{1F7E2}",info:"\u{1F535}",warn:"\u{1F7E0}",critical:"\u{1F534}"}),br=Object.freeze({up:"\u2191",down:"\u2193",flat:"\u2192"}),Ky=Object.freeze({ok:"green",info:"blue",warn:"orange",critical:"red"});function kr(r,t,e=12){if(!Number.isFinite(r)||!Number.isFinite(t)||t<=0)return"";let n=Math.max(0,Math.min(1,r/t)),s=Math.round(n*e);return"\u2593".repeat(s)+"\u2591".repeat(e-s)}function At(r,t){let e=String(r);return e.length>=t?e:e+" ".repeat(t-e.length)}function wr(r,t){let e=String(r);return e.length>=t?e:" ".repeat(t-e.length)+e}function _a({headers:r,rows:t}){let e=r.map((o,a)=>{let c=Math.max(String(o).length,...t.map(l=>String(l[a]??"").length));return Math.min(c,32)}),n=o=>o.map((a,c)=>At(a,e[c])).join(" "),s=e.map(o=>"\u2500".repeat(o)).join(" ");return"```\n"+[n(r),s,...t.map(o=>n(o))].join(`
|
|
1246
|
-
`)+"\n```"}function
|
|
1261
|
+
`,"utf-8");let b=["graph.mjs","workflow.json","nodes/index.mjs",...c.map(y=>`nodes/${y.replace(/_/g,"-")}.mjs`)];return{workflowDir:na(r,o),files:b,className:i,slug:s}}async function Ty(r){let{name:t,description:e,nodes:n,edges:s}=r;if(!t||!ia.test(t.toLowerCase()))return JSON.stringify({error:`Invalid workflow name "${t}". Must be kebab-case, 2-64 chars, lowercase letters/numbers/hyphens.`});if(!n||n.length===0)return JSON.stringify({error:"At least one node is required."});let i={name:t.toLowerCase(),description:e||`${oa(t.toLowerCase())} workflow`,nodes:n.map(o=>({name:o.name.replace(/-/g,"_"),description:o.description||`Process ${o.name}`,inputFields:o.inputFields||[],outputFields:o.outputFields||[]})),edges:s||[]};if(i.edges.length===0&&i.nodes.length>0){for(let o=0;o<i.nodes.length-1;o++)i.edges.push({from:i.nodes[o].name,to:i.nodes[o+1].name});i.edges.push({from:i.nodes[i.nodes.length-1].name,to:"END"})}return JSON.stringify({ok:!0,spec:i,message:`Workflow "${i.name}" designed with ${i.nodes.length} node(s). Call build_workflow to generate the code.`,preview:{nodes:i.nodes.map(o=>o.name),flow:i.edges.map(o=>o.condition?`${o.from} \u2192(if ${o.condition})\u2192 ${o.to}`:`${o.from} \u2192 ${o.to}`)}})}async function Ay(r,t){let{name:e,spec:n}=r,s=(e||n?.name||"").toLowerCase();if(!s||!ia.test(s))return JSON.stringify({error:`Invalid workflow name "${s}".`});if(!n||!n.nodes||n.nodes.length===0)return JSON.stringify({error:"spec with nodes is required. Call design_workflow first."});let i=E(t,".zibby","workflows",s);if(fe(i))return JSON.stringify({error:`Workflow "${s}" already exists at .zibby/workflows/${s}/. Delete it first or choose a different name.`});let o=await la(n,t),a=Oy(t,s,n,o);return JSON.stringify({ok:!0,...a,message:`Workflow "${s}" created at ${a.workflowDir}/`,nextSteps:[`Test locally: zibby start ${s}`,`Deploy to cloud: zibby deploy ${s} --project <project-id>`,`Tail logs: zibby logs --workflow ${s} --project <project-id>`]})}async function Ry(r,t){let{workflowName:e,nodeName:n,description:s,inputFields:i,outputFields:o}=r,a=(e||"").toLowerCase(),c=(n||"").replace(/-/g,"_"),l=E(t,".zibby","workflows",a);if(!fe(l))return JSON.stringify({error:`Workflow "${a}" not found. Create it first with build_workflow.`});let d={name:a,description:"",nodes:[{name:c,description:s||`Process ${c}`,inputFields:i||[],outputFields:o||[]}],edges:[]},u=(await la(d,t)).nodes?.[c]?.code;if(!u)return JSON.stringify({error:"Failed to generate node code."});let m=E(l,"nodes"),h=`${c.replace(/_/g,"-")}.mjs`;et(E(m,h),u,"utf-8");let f=E(m,"index.mjs"),b=vt(c),y=`export { ${b} } from './${c.replace(/_/g,"-")}.mjs';
|
|
1262
|
+
`,_=fe(f)?It(f,"utf-8"):"";return _.includes(b)||et(f,_+y,"utf-8"),JSON.stringify({ok:!0,file:`nodes/${h}`,exportName:b,message:`Node "${c}" added. Update graph.mjs to wire it into the graph.`})}async function Ey(r,t){let{name:e,projectId:n}=r,s=(e||"").toLowerCase();if(!s)return JSON.stringify({error:"Workflow name is required."});if(!n)return JSON.stringify({error:"projectId is required."});let i=E(t,".zibby","workflows",s);if(!fe(i))return JSON.stringify({error:`Workflow "${s}" not found at .zibby/workflows/${s}/`});try{let{execSync:o}=await import("child_process"),a=o(`node "${E(t,"packages/cli/bin/zibby.js")}" deploy ${s} --project ${n}`,{cwd:t,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]});return JSON.stringify({ok:!0,output:a.trim()})}catch{try{let{execSync:a}=await import("child_process"),c=a(`npx zibby deploy ${s} --project ${n}`,{cwd:t,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]});return JSON.stringify({ok:!0,output:c.trim()})}catch(a){return JSON.stringify({error:`Deploy failed: ${a.message}`})}}}function xy(r){let t=E(r,".zibby","workflows");if(!fe(t))return JSON.stringify({workflows:[],message:"No workflows found. Use build_workflow to create one."});let n=kn(t).filter(s=>{try{return ra(E(t,s)).isDirectory()}catch{return!1}}).map(s=>{let i=E(t,s,"workflow.json"),o={};try{o=JSON.parse(It(i,"utf-8"))}catch{}let a=E(t,s,"nodes"),c=0;try{c=kn(a).filter(l=>l.endsWith(".mjs")&&l!=="index.mjs").length}catch{}return{name:s,description:o.description||"",nodeCount:c,path:na(r,E(t,s))}});return JSON.stringify({workflows:n})}var da={id:"workflow-builder",description:"Build, scaffold, and deploy custom AI workflows via conversation",envKeys:[],promptFragment:ky,tools:[{name:"design_workflow",description:"Design a workflow spec (nodes, edges, descriptions) for the user to review before building. Call this after understanding requirements.",input_schema:{type:"object",properties:{name:{type:"string",description:"Workflow name in kebab-case (e.g., ticket-triage)"},description:{type:"string",description:"What the workflow does"},nodes:{type:"array",items:{type:"object",properties:{name:{type:"string",description:"Node name in snake_case (e.g., classify_ticket)"},description:{type:"string",description:"What this node does \u2014 be specific about input/output"},inputFields:{type:"array",items:{type:"string"},description:"Key fields this node reads from state"},outputFields:{type:"array",items:{type:"string"},description:"Key fields this node produces"}},required:["name","description"]},description:"Workflow nodes (processing steps)"},edges:{type:"array",items:{type:"object",properties:{from:{type:"string",description:"Source node name"},to:{type:"string",description:'Target node name (or "END")'},condition:{type:"string",description:"JS expression for conditional routing (optional)"}},required:["from","to"]},description:"Edges connecting nodes. If omitted, nodes are wired linearly."}},required:["name","description","nodes"]}},{name:"build_workflow",description:"Generate real workflow code on disk from a design spec. Uses the configured AI agent for high-quality code generation.",input_schema:{type:"object",properties:{name:{type:"string",description:"Workflow name (from design_workflow)"},spec:{type:"object",description:"The full spec object returned by design_workflow",properties:{name:{type:"string"},description:{type:"string"},nodes:{type:"array",items:{type:"object"}},edges:{type:"array",items:{type:"object"}}}}},required:["name","spec"]}},{name:"add_node",description:"Add a new node to an existing workflow. Generates the node file and updates the barrel export.",input_schema:{type:"object",properties:{workflowName:{type:"string",description:"Existing workflow name (kebab-case)"},nodeName:{type:"string",description:"New node name (snake_case)"},description:{type:"string",description:"What this node does"},inputFields:{type:"array",items:{type:"string"},description:"Fields read from state"},outputFields:{type:"array",items:{type:"string"},description:"Fields produced"}},required:["workflowName","nodeName","description"]}},{name:"deploy_workflow",description:"Deploy a workflow to Zibby Cloud. Returns the trigger URL.",input_schema:{type:"object",properties:{name:{type:"string",description:"Workflow name to deploy"},projectId:{type:"string",description:"Target project ID"}},required:["name","projectId"]}},{name:"list_workflows",description:"List all local workflows in .zibby/workflows/.",input_schema:{type:"object",properties:{}}},{name:"explore_framework_docs",description:"Read Zibby framework documentation on demand. Call this before building complex workflows or when you need details on advanced patterns (middleware, conditional routing, skills, deployment, CLI commands).",input_schema:{type:"object",properties:{topic:{type:"string",description:'Doc topic to read (e.g., "workflow", "custom-workflows", "cli-reference", "packages/core", "packages/skills", "integrations/jira"). Call with no topic to list all available docs.'}}}}],async handleToolCall(r,t,e){let n=e?.options?.workspace||process.cwd();try{switch(r){case"design_workflow":return await Ty(t);case"build_workflow":return await Ay(t,n);case"add_node":return await Ry(t,n);case"deploy_workflow":return await Ey(t,n);case"list_workflows":return xy(n);case"explore_framework_docs":{let s=(t.topic||"").trim();if(!s){let o=ta();return JSON.stringify({available:o,hint:"Call again with a topic to read its content."})}let i=ca(s);if(!i){let o=ta();return JSON.stringify({error:`Doc "${s}" not found.`,available:o})}return JSON.stringify({topic:s,content:i})}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(s){return JSON.stringify({error:s.message})}},resolve(){return null}};import{resolveIntegrationToken as Ly}from"@zibby/core/backend-client.js";var vn=Object.freeze({id:"openai_billing",callsBackend:!0,requiresIntegration:S.OPENAI_BILLING,description:"OpenAI organization billing/usage admin API (paste sk-admin-... key)"}),In=Object.freeze({id:"anthropic_billing",callsBackend:!0,requiresIntegration:S.ANTHROPIC_BILLING,description:"Anthropic organization cost/usage admin API (paste sk-ant-admin-... key)"}),Nn=Object.freeze({id:"cursor_admin",callsBackend:!0,requiresIntegration:S.CURSOR_ADMIN,description:"Cursor Team/Enterprise admin API (paste admin key)"});function pa(r){return Math.floor(r/1e3)}function Sn(r){return new Date(r).toISOString().slice(0,10)}function ua(r){return new Date(r).toISOString()}async function Nt(r){let t=await Ly(r);if(!t?.token)throw new Error(`${r} token resolver returned no token`);return t.token}async function ma({startMs:r,endMs:t,groupBy:e=["project_id","line_item"]}){let n=await Nt("openai_billing"),s=[],i=0,o=e.map(c=>`group_by[]=${encodeURIComponent(c)}`).join("&"),a=null;for(let c=0;c<50;c++){let d=`https://api.openai.com/v1/organization/costs?${[`start_time=${pa(r)}`,`end_time=${pa(t)}`,"bucket_width=1d","limit=180",o,a?`page=${encodeURIComponent(a)}`:""].filter(Boolean).join("&")}`,p=await fetch(d,{headers:{Authorization:`Bearer ${n}`}});if(!p.ok){let m=await p.text().catch(()=>"");throw new Error(`OpenAI costs API ${p.status}: ${m.slice(0,200)}`)}let u=await p.json();for(let m of u.data||[]){i+=1;let h=Sn((m.start_time||0)*1e3);for(let f of m.results||[])s.push({provider:"openai",day:h,costUsd:Number(f.amount?.value??0),projectId:f.project_id||void 0,apiKeyId:f.api_key_id||void 0,model:f.line_item||void 0})}if(!u.has_more||!u.next_page)break;a=u.next_page}return{ok:!0,items:s,rawBuckets:i}}async function jy(){let r=await Nt("openai_billing"),e=await fetch("https://api.openai.com/v1/organization/projects?limit=100",{headers:{Authorization:`Bearer ${r}`}});if(!e.ok){let i=await e.text().catch(()=>"");throw new Error(`OpenAI projects API ${e.status}: ${i.slice(0,200)}`)}let n=await e.json(),s=new Map;for(let i of n.data||[])s.set(i.id,i.name);return s}async function ha({startMs:r,endMs:t,groupBy:e=["workspace_id"]}){let n=await Nt("anthropic_billing"),s=[],i=0,o=e.map(c=>`group_by[]=${encodeURIComponent(c)}`).join("&"),a=null;for(let c=0;c<50;c++){let d=`https://api.anthropic.com/v1/organizations/cost_report?${[`starting_at=${encodeURIComponent(ua(r))}`,`ending_at=${encodeURIComponent(ua(t))}`,"bucket=1d","limit=100",o,a?`page=${encodeURIComponent(a)}`:""].filter(Boolean).join("&")}`,p=await fetch(d,{headers:{"x-api-key":n,"anthropic-version":"2023-06-01"}});if(!p.ok){let m=await p.text().catch(()=>"");throw new Error(`Anthropic cost_report ${p.status}: ${m.slice(0,200)}`)}let u=await p.json();for(let m of u.data||[]){i+=1;let h=(m.starting_at||"").slice(0,10);for(let f of m.results||[])s.push({provider:"anthropic",day:h,costUsd:Number(f.amount??f.cost??0),workspaceId:f.workspace_id||void 0,apiKeyId:f.api_key_id||void 0,model:f.model||void 0,tokensIn:f.uncached_input_tokens!=null?Number(f.uncached_input_tokens):void 0,tokensOut:f.output_tokens!=null?Number(f.output_tokens):void 0,cachedTokens:f.cached_input_tokens!=null?Number(f.cached_input_tokens):void 0})}if(!u.has_more||!u.next_page)break;a=u.next_page}return{ok:!0,items:s,rawBuckets:i}}async function $y(){let r=await Nt("anthropic_billing"),e=await fetch("https://api.anthropic.com/v1/organizations/workspaces?limit=100",{headers:{"x-api-key":r,"anthropic-version":"2023-06-01"}});if(!e.ok){let i=await e.text().catch(()=>"");throw new Error(`Anthropic workspaces ${e.status}: ${i.slice(0,200)}`)}let n=await e.json(),s=new Map;for(let i of n.data||[])s.set(i.id,i.name);return s}async function fa({startMs:r,endMs:t}){let e=await Nt("cursor_admin"),n=Sn(r),s=Sn(t),i=`https://api.cursor.com/teams/daily-usage-data?startDate=${n}&endDate=${s}`,o=await fetch(i,{headers:{Authorization:`Bearer ${e}`}});if(!o.ok){let d=await o.text().catch(()=>"");throw new Error(`Cursor daily-usage ${o.status}: ${d.slice(0,200)}`)}let a=await o.json(),c=[],l=0;for(let d of a.data||[]){l+=1;let p=d.date;for(let u of d.userMetrics||[]){for(let m of u.modelUsage||[]){let h=Number(m.acceptedLines??0),f=Number(m.suggestedLines??0);c.push({provider:"cursor",day:p,costUsd:Number(m.totalCents??0)/100,userEmail:u.email,model:m.model,requestCount:Number(m.requestCount??0),acceptanceRate:f>0?h/f:void 0})}(!u.modelUsage||u.modelUsage.length===0)&&c.push({provider:"cursor",day:p,costUsd:Number(u.totalCents??0)/100,userEmail:u.email})}}return{ok:!0,items:c,rawBuckets:l}}async function Py({startMs:r,endMs:t}){let[e,n,s]=await Promise.allSettled([ma({startMs:r,endMs:t}),ha({startMs:r,endMs:t}),fa({startMs:r,endMs:t})]),i=d=>d.status==="fulfilled"?d.value:{ok:!1,error:d.reason?.message||String(d.reason),items:[]},o=i(e),a=i(n),c=i(s),l=[{provider:"openai",totalUsd:o.items.reduce((d,p)=>d+(p.costUsd||0),0)},{provider:"anthropic",totalUsd:a.items.reduce((d,p)=>d+(p.costUsd||0),0)},{provider:"cursor",totalUsd:c.items.reduce((d,p)=>d+(p.costUsd||0),0)}];return{openai:o,anthropic:a,cursor:c,totals:l}}function Cy(r,t){let e=new Map;for(let n of r){let s=t(n);if(!s)continue;let i=e.get(s)||{key:s,totalUsd:0,count:0};i.totalUsd+=n.costUsd||0,i.count+=1,e.set(s,i)}return[...e.values()].sort((n,s)=>s.totalUsd-n.totalUsd)}function Uy(r){if(!r.length)return{mean:0,stddev:0};let t=r.reduce((n,s)=>n+s,0)/r.length,e=r.reduce((n,s)=>n+(s-t)**2,0)/r.length;return{mean:t,stddev:Math.sqrt(e)}}import{SKILL_IDS as kS}from"@zibby/skill-ids";import{z as w}from"zod";var Ot=["ok","info","warn","critical"],By=w.object({primary:w.string().min(1).max(200).describe('Headline number or phrase (e.g. "$8,240"). Rendered in large/bold.'),delta:w.object({value:w.string().max(40).describe('Delta vs baseline (e.g. "+12% wow"). Free-form string.'),direction:w.enum(["up","down","flat"]).optional(),severity:w.enum(Ot).optional().describe("Color severity for the delta (warn/critical highlights regressions).")}).optional().describe("Optional comparison vs baseline. Renders inline next to primary."),summary:w.string().max(800).optional().describe('One-sentence narrative ("why this number"). Plain prose.')}),Dy=w.object({kind:w.literal("trend"),title:w.string().max(120).optional(),labels:w.array(w.string().max(60)).min(2).max(20).describe('Bucket labels (e.g. ["Week-3", "Week-2", "Week-1", "This wk"]).'),values:w.array(w.number()).min(2).max(20).describe("Numeric values, one per label. Must match labels.length."),highlight:w.enum(["last","max","min","none"]).default("last").optional().describe("Which bucket to visually highlight in the rendered card."),severity:w.enum(Ot).optional()}),Jy=w.object({kind:w.literal("table"),title:w.string().max(120).optional(),headers:w.array(w.string().max(40)).min(1).max(8),rows:w.array(w.array(w.union([w.string().max(200),w.number()])).min(1).max(8)).max(40).describe("2D matrix. Each inner array must have headers.length entries.")}),My=w.object({kind:w.literal("callouts"),title:w.string().max(120).optional(),tone:w.enum(Ot).default("info").optional(),items:w.array(w.string().min(1).max(600)).min(1).max(10).describe("Each item renders as a bullet with a severity emoji.")}),qy=w.object({kind:w.literal("breakdown"),title:w.string().max(120).optional(),rows:w.array(w.object({label:w.string().min(1).max(80),value:w.string().min(1).max(80),sub:w.string().max(120).optional(),severity:w.enum(Ot).optional()})).min(1).max(20)}),Fy=w.object({kind:w.literal("paragraph"),title:w.string().max(120).optional(),text:w.string().min(1).max(3e3)}),Ky=w.discriminatedUnion("kind",[Dy,Jy,My,qy,Fy]),Tt=w.object({title:w.string().min(1).max(200).describe('Card title (e.g. "Weekly AI Spend Report").'),subtitle:w.string().max(200).optional().describe('Date range or smaller header (e.g. "May 13 \u2014 May 20").'),headline:By,sections:w.array(Ky).max(20).default([]).superRefine((r,t)=>{r.forEach((e,n)=>{e.kind==="trend"&&e.labels.length!==e.values.length&&t.addIssue({code:w.ZodIssueCode.custom,path:[n,"values"],message:"labels.length must equal values.length"}),e.kind==="table"&&!e.rows.every(s=>s.length===e.headers.length)&&t.addIssue({code:w.ZodIssueCode.custom,path:[n,"rows"],message:"every row must have headers.length entries"})})}),footer:w.object({viewUrl:w.string().url().optional().describe('Optional "View in Zibby" button URL.'),rerunUrl:w.string().url().optional().describe('Optional "Run again" button URL.')}).optional()}),B=Object.freeze({ok:"\u{1F7E2}",info:"\u{1F535}",warn:"\u{1F7E0}",critical:"\u{1F534}"}),br=Object.freeze({up:"\u2191",down:"\u2193",flat:"\u2192"}),Gy=Object.freeze({ok:"green",info:"blue",warn:"orange",critical:"red"});function kr(r,t,e=12){if(!Number.isFinite(r)||!Number.isFinite(t)||t<=0)return"";let n=Math.max(0,Math.min(1,r/t)),s=Math.round(n*e);return"\u2593".repeat(s)+"\u2591".repeat(e-s)}function At(r,t){let e=String(r);return e.length>=t?e:e+" ".repeat(t-e.length)}function wr(r,t){let e=String(r);return e.length>=t?e:" ".repeat(t-e.length)+e}function _a({headers:r,rows:t}){let e=r.map((o,a)=>{let c=Math.max(String(o).length,...t.map(l=>String(l[a]??"").length));return Math.min(c,32)}),n=o=>o.map((a,c)=>At(a,e[c])).join(" "),s=e.map(o=>"\u2500".repeat(o)).join(" ");return"```\n"+[n(r),s,...t.map(o=>n(o))].join(`
|
|
1263
|
+
`)+"\n```"}function Hy(r){let t=Tt.parse(r),e=[];e.push({type:"header",text:{type:"plain_text",text:t.title.slice(0,150),emoji:!0}}),t.subtitle&&e.push({type:"context",elements:[{type:"mrkdwn",text:t.subtitle}]});let n=[`*${t.headline.primary}*`];if(t.headline.delta){let i=br[t.headline.delta.direction]||"",o=t.headline.delta.severity?B[t.headline.delta.severity]:"";n.push(`${i} ${t.headline.delta.value} ${o}`.trim())}let s=n.join(" ");t.headline.summary&&(s+=`
|
|
1247
1264
|
`+t.headline.summary),e.push({type:"section",text:{type:"mrkdwn",text:s}});for(let i of t.sections)switch(e.push({type:"divider"}),i.title&&e.push({type:"section",text:{type:"mrkdwn",text:`*${i.title}*`}}),i.kind){case"trend":{let o=Math.max(...i.values),a=i.labels.map((c,l)=>{let d=i.values[l],p=kr(d,o),m=(i.highlight==="last"&&l===i.labels.length-1||i.highlight==="max"&&d===o||i.highlight==="min"&&d===Math.min(...i.values))&&i.severity?` ${B[i.severity]}`:"";return`${At(c,10)} ${wr(d.toLocaleString(),8)} ${p}${m}`});e.push({type:"section",text:{type:"mrkdwn",text:"```\n"+a.join(`
|
|
1248
1265
|
`)+"\n```"}});break}case"table":{e.push({type:"section",text:{type:"mrkdwn",text:_a(i)}});break}case"callouts":{let o=B[i.tone||"info"];e.push({type:"section",text:{type:"mrkdwn",text:i.items.map(a=>`${o} ${a}`).join(`
|
|
1249
1266
|
`)}});break}case"breakdown":{let o=i.rows.map(a=>({type:"mrkdwn",text:`*${a.label}*
|
|
1250
1267
|
${a.value}${a.sub?`
|
|
1251
|
-
_${a.sub}_`:""}${a.severity?` ${B[a.severity]}`:""}`}));for(let a=0;a<o.length;a+=10)e.push({type:"section",fields:o.slice(a,a+10)});break}case"paragraph":{e.push({type:"section",text:{type:"mrkdwn",text:i.text}});break}}if(t.footer&&(t.footer.viewUrl||t.footer.rerunUrl)){let i=[];t.footer.viewUrl&&i.push({type:"button",text:{type:"plain_text",text:"View in Zibby"},url:t.footer.viewUrl,style:"primary"}),t.footer.rerunUrl&&i.push({type:"button",text:{type:"plain_text",text:"Run again"},url:t.footer.rerunUrl}),e.push({type:"divider"}),e.push({type:"actions",elements:i})}return e}function
|
|
1268
|
+
_${a.sub}_`:""}${a.severity?` ${B[a.severity]}`:""}`}));for(let a=0;a<o.length;a+=10)e.push({type:"section",fields:o.slice(a,a+10)});break}case"paragraph":{e.push({type:"section",text:{type:"mrkdwn",text:i.text}});break}}if(t.footer&&(t.footer.viewUrl||t.footer.rerunUrl)){let i=[];t.footer.viewUrl&&i.push({type:"button",text:{type:"plain_text",text:"View in Zibby"},url:t.footer.viewUrl,style:"primary"}),t.footer.rerunUrl&&i.push({type:"button",text:{type:"plain_text",text:"Run again"},url:t.footer.rerunUrl}),e.push({type:"divider"}),e.push({type:"actions",elements:i})}return e}function Yy(r){let t=Tt.parse(r),e=[],n=[`**${t.headline.primary}**`];if(t.headline.delta){let o=br[t.headline.delta.direction]||"",a=t.headline.delta.severity?B[t.headline.delta.severity]:"";n.push(`${o} ${t.headline.delta.value} ${a}`.trim())}let s=n.join(" ");t.headline.summary&&(s+=`
|
|
1252
1269
|
`+t.headline.summary),e.push({tag:"div",text:{tag:"lark_md",content:s}});for(let o of t.sections)switch(e.push({tag:"hr"}),o.title&&e.push({tag:"div",text:{tag:"lark_md",content:`**${o.title}**`}}),o.kind){case"trend":{let a=Math.max(...o.values),c=o.labels.map((l,d)=>{let p=o.values[d],u=kr(p,a),h=(o.highlight==="last"&&d===o.labels.length-1||o.highlight==="max"&&p===a||o.highlight==="min"&&p===Math.min(...o.values))&&o.severity?` ${B[o.severity]}`:"";return`${At(l,10)} ${wr(p.toLocaleString(),8)} ${u}${h}`});e.push({tag:"div",text:{tag:"lark_md",content:"```\n"+c.join(`
|
|
1253
1270
|
`)+"\n```"}});break}case"table":{e.push({tag:"div",text:{tag:"lark_md",content:_a(o)}});break}case"callouts":{let a=B[o.tone||"info"];e.push({tag:"div",text:{tag:"lark_md",content:o.items.map(c=>`${a} ${c}`).join(`
|
|
1254
1271
|
`)}});break}case"breakdown":{let a=o.rows.map(c=>{let l=c.severity?` ${B[c.severity]}`:"",d=c.sub?` *${c.sub}*`:"";return`**${c.label}** ${c.value}${d}${l}`});e.push({tag:"div",text:{tag:"lark_md",content:a.join(`
|
|
1255
|
-
`)}});break}case"paragraph":{e.push({tag:"div",text:{tag:"lark_md",content:o.text}});break}}if(t.footer&&(t.footer.viewUrl||t.footer.rerunUrl)){let o=[];t.footer.viewUrl&&o.push({tag:"button",text:{tag:"plain_text",content:"View in Zibby"},url:t.footer.viewUrl,type:"primary"}),t.footer.rerunUrl&&o.push({tag:"button",text:{tag:"plain_text",content:"Run again"},url:t.footer.rerunUrl,type:"default"}),e.push({tag:"hr"}),e.push({tag:"action",actions:o})}let i="blue";return t.headline.delta?.severity&&(i=
|
|
1256
|
-
`)));break}case"table":{let o={object:"block",type:"table_row",table_row:{cells:i.headers.map(c=>Ce(c))}},a=i.rows.map(c=>({object:"block",type:"table_row",table_row:{cells:c.map(l=>Ce(String(l)))}}));e.push({object:"block",type:"table",table:{table_width:i.headers.length,has_column_header:!0,has_row_header:!1,children:[o,...a]}});break}case"callouts":{let o=i.tone||"info",a=ya[o],c=
|
|
1257
|
-
`)}import{createRequire as
|
|
1272
|
+
`)}});break}case"paragraph":{e.push({tag:"div",text:{tag:"lark_md",content:o.text}});break}}if(t.footer&&(t.footer.viewUrl||t.footer.rerunUrl)){let o=[];t.footer.viewUrl&&o.push({tag:"button",text:{tag:"plain_text",content:"View in Zibby"},url:t.footer.viewUrl,type:"primary"}),t.footer.rerunUrl&&o.push({tag:"button",text:{tag:"plain_text",content:"Run again"},url:t.footer.rerunUrl,type:"default"}),e.push({tag:"hr"}),e.push({tag:"action",actions:o})}let i="blue";return t.headline.delta?.severity&&(i=Gy[t.headline.delta.severity]||"blue"),{config:{wide_screen_mode:!0},header:{title:{tag:"plain_text",content:t.title.slice(0,200)},subtitle:t.subtitle?{tag:"plain_text",content:t.subtitle.slice(0,200)}:void 0,template:i},elements:e}}var zy=Object.freeze({ok:"green_background",info:"blue_background",warn:"orange_background",critical:"red_background"}),ya=Object.freeze({ok:"\u{1F7E2}",info:"\u2139\uFE0F",warn:"\u26A0\uFE0F",critical:"\u{1F6A8}"});function Ce(r,t={}){let e={type:"text",text:{content:String(r).slice(0,2e3)}};return t.annotations&&(e.annotations=t.annotations),[e]}function On(r,t){let e={object:"block",type:"paragraph",paragraph:{rich_text:Ce(r)}};return t&&(e.paragraph.color=t),e}function Wy(r){return{object:"block",type:"code",code:{rich_text:Ce(r),language:"plain text"}}}function Zy(r){let t=Tt.parse(r),e=[];e.push({object:"block",type:"heading_1",heading_1:{rich_text:Ce(t.title.slice(0,200))}}),t.subtitle&&e.push(On(t.subtitle,"gray_background"));let n=[{type:"text",text:{content:t.headline.primary.slice(0,200)},annotations:{bold:!0}}];if(t.headline.delta){let i=br[t.headline.delta.direction]||"",o=t.headline.delta.severity?B[t.headline.delta.severity]:"",a=` ${i} ${t.headline.delta.value} ${o}`.trimEnd();n.push({type:"text",text:{content:a}})}e.push({object:"block",type:"paragraph",paragraph:{rich_text:n}}),t.headline.summary&&e.push(On(t.headline.summary));for(let i of t.sections)switch(e.push({object:"block",type:"divider",divider:{}}),i.title&&e.push({object:"block",type:"heading_2",heading_2:{rich_text:Ce(i.title)}}),i.kind){case"trend":{let o=Math.max(...i.values),a=Math.min(...i.values),c=i.labels.map((l,d)=>{let p=i.values[d],u=kr(p,o),h=(i.highlight==="last"&&d===i.labels.length-1||i.highlight==="max"&&p===o||i.highlight==="min"&&p===a)&&i.severity?` ${B[i.severity]}`:"";return`${At(l,10)} ${wr(p.toLocaleString(),8)} ${u}${h}`});e.push(Wy(c.join(`
|
|
1273
|
+
`)));break}case"table":{let o={object:"block",type:"table_row",table_row:{cells:i.headers.map(c=>Ce(c))}},a=i.rows.map(c=>({object:"block",type:"table_row",table_row:{cells:c.map(l=>Ce(String(l)))}}));e.push({object:"block",type:"table",table:{table_width:i.headers.length,has_column_header:!0,has_row_header:!1,children:[o,...a]}});break}case"callouts":{let o=i.tone||"info",a=ya[o],c=zy[o];for(let l of i.items)e.push({object:"block",type:"callout",callout:{rich_text:Ce(l),icon:{type:"emoji",emoji:a},color:c}});break}case"breakdown":{for(let o of i.rows){let a=[{type:"text",text:{content:`${o.label}: `},annotations:{bold:!0}},{type:"text",text:{content:String(o.value)}}];o.sub&&a.push({type:"text",text:{content:` (${o.sub})`},annotations:{italic:!0}}),o.severity&&a.push({type:"text",text:{content:` ${B[o.severity]}`}}),e.push({object:"block",type:"bulleted_list_item",bulleted_list_item:{rich_text:a}})}break}case"paragraph":{e.push(On(i.text));break}}t.footer&&(t.footer.viewUrl||t.footer.rerunUrl)&&(e.push({object:"block",type:"divider",divider:{}}),t.footer.viewUrl&&e.push({object:"block",type:"embed",embed:{url:t.footer.viewUrl}}),t.footer.rerunUrl&&e.push({object:"block",type:"embed",embed:{url:t.footer.rerunUrl}}));let s=t.headline.delta?.severity?ya[t.headline.delta.severity]:void 0;return{blocks:e,title:t.title.slice(0,200),icon:s}}function ga(r){return String(r??"").replace(/\|/g,"\\|")}function Vy(r){let t=Tt.parse(r),e=[];e.push(`# ${t.title}`),t.subtitle&&e.push("",t.subtitle);let n=[`**${t.headline.primary}**`];if(t.headline.delta){let s=br[t.headline.delta.direction]||"",i=t.headline.delta.severity?B[t.headline.delta.severity]:"";n.push(`${s} ${t.headline.delta.value} ${i}`.trim())}e.push("",n.join(" ")),t.headline.summary&&e.push("",t.headline.summary);for(let s of t.sections)switch(e.push("","---",""),s.title&&e.push(`## ${s.title}`,""),s.kind){case"trend":{let i=Math.max(...s.values),o=Math.min(...s.values),a=s.labels.map((c,l)=>{let d=s.values[l],p=kr(d,i),m=(s.highlight==="last"&&l===s.labels.length-1||s.highlight==="max"&&d===i||s.highlight==="min"&&d===o)&&s.severity?` ${B[s.severity]}`:"";return`${At(c,10)} ${wr(d.toLocaleString(),8)} ${p}${m}`});e.push("```",...a,"```");break}case"table":{e.push(`| ${s.headers.map(ga).join(" | ")} |`),e.push(`| ${s.headers.map(()=>"---").join(" | ")} |`);for(let i of s.rows)e.push(`| ${i.map(ga).join(" | ")} |`);break}case"callouts":{let i=B[s.tone||"info"];for(let o of s.items)e.push(`- ${i} ${o}`);break}case"breakdown":{for(let i of s.rows){let o=i.sub?` _${i.sub}_`:"",a=i.severity?` ${B[i.severity]}`:"";e.push(`- **${i.label}**: ${i.value}${o}${a}`)}break}case"paragraph":{e.push(s.text);break}}if(t.footer&&(t.footer.viewUrl||t.footer.rerunUrl)){let s=[];t.footer.viewUrl&&s.push(`[View in Zibby](${t.footer.viewUrl})`),t.footer.rerunUrl&&s.push(`[Run again](${t.footer.rerunUrl})`),e.push("","---","",s.join(" \xB7 "))}return e.join(`
|
|
1274
|
+
`)}import{createRequire as Xy}from"module";import{fileURLToPath as Qy}from"url";import{registerHandlers as eg}from"@zibby/core/function-skill-registry.js";import{registerSkill as tg}from"@zibby/agent-workflow";var rg=Xy(import.meta.url);function ng(){try{return rg.resolve("@zibby/core/function-bridge.js")}catch{return null}}var sg=import.meta.url;function ig(){let r=Error.prepareStackTrace;try{Error.prepareStackTrace=(n,s)=>s;let e=new Error().stack;for(let n=2;n<e.length;n++){let s=e[n].getFileName();if(s&&s!==sg&&!s.startsWith("node:"))return s.startsWith("file://")?Qy(s):s}return null}finally{Error.prepareStackTrace=r}}function og(r){if(!r||typeof r!="object")return{type:"object",properties:{},required:[]};let t={},e=[];for(let[n,s]of Object.entries(r))if(typeof s=="string")t[n]={type:s},e.push(n);else{let{required:i,...o}=s;t[n]=o,i!==!1&&e.push(n)}return{type:"object",properties:t,required:e}}function ag(r,t,e){if(typeof e.handler!="function")throw new Error(`Skill "${r}" must have a handler function`);let n={[r]:e.handler},s=[{name:r,description:e.description||"",input_schema:og(e.input)}];return eg(r,n,s),{id:r,type:"function",serverName:r,allowedTools:[`mcp__${r}__*`],description:e.description||`Function skill: ${r}`,envKeys:[],tools:s,resolve(){let i=ng();return i?{command:"node",args:[i,t,r]}:null}}}function cg(r,t){return{id:r,type:"mcp",serverName:t.serverName||r,allowedTools:t.allowedTools||[`mcp__${t.serverName||r}__*`],description:t.description||`MCP skill: ${r}`,envKeys:t.envKeys||[],tools:t.tools||[],resolve:t.resolve,...t.cursorKey&&{cursorKey:t.cursorKey},...t.sessionEnvKey&&{sessionEnvKey:t.sessionEnvKey}}}function ba(r,t){let e;if("handler"in t){if(typeof t.handler!="function")throw new Error(`Skill "${r}" must have a handler function`);let n=ig();if(!n)throw new Error(`Could not resolve caller file for skill "${r}".`);e=ag(r,n,t)}else if(typeof t.resolve=="function")e=cg(r,t);else throw new Error(`Skill "${r}" must have either a handler (function skill) or resolve (MCP skill).`);return tg(e),e}var lg=ba;import{registerSkill as TS,getSkill as AS,hasSkill as RS,getAllSkills as ES,listSkillIds as xS}from"@zibby/agent-workflow";var I=r=>dg(En(r));I(Ln);I(Pn);I(De);I(Je);I(Zn);I(Xn);I(es);I(is);I(os);I(ls);I(P);I(F);I(us);I(ks);I(Is);I(Rs);I(Us);I(Fs);I(Ks);I(Vt);I(Hs);I(yi);I(be);I(wi);I(zs);I(Qs);I(Pi);I(qi);I(zi);I(fo);I(Oo);I(to);I(To);I(Ao);I(Bo);I(Ho);I(Yo);I(Vo);I(Qo);I(da);I(vn);I(In);I(Nn);I({...P,id:"slack_notify"});export{S as INTEGRATIONS,La as INTEGRATION_REGISTRY,Q as REPORT_CODES,Ot as REPORT_SEVERITIES,kS as SKILLS,In as anthropicBillingSkill,fo as artifactSkill,Ln as browserSkill,Oo as chartRenderSkill,Pi as chatMemorySkill,Ks as chatNotifySkill,Ao as chatProgressSkill,dr as checkRenderedReport,Ho as codeScanSkill,To as codeStatsSkill,Vo as codebaseMemorySkill,Qs as coreToolsSkill,Nn as cursorAdminSkill,zi as datasetStoreSkill,xn as devServerPreviewRecipe,us as discordSkill,Py as fetchAllProviders,ha as fetchAnthropicCosts,$y as fetchAnthropicWorkspaces,fa as fetchCursorSpend,ma as fetchOpenAICosts,jy as fetchOpenAIProjects,Zn as figmaSkill,lg as functionSkill,Qo as gbrainSkill,ES as getAllSkills,AS as getSkill,be as gitSkill,wi as gitWriteSkill,De as githubSkill,Je as gitlabSkill,Rs as googleDocsSkill,Cy as groupByKey,RS as hasSkill,Xn as hubspotSkill,Pn as jiraSkill,qi as kvMemorySkill,Fs as larkAttendanceSkill,Us as larkDocsSkill,F as larkSkill,es as linearSkill,Is as linkedinSkill,xS as listSkillIds,Uy as meanStddev,Hs as memorySkill,ks as notionSkill,vn as openaiBillingSkill,ls as opendesignSkill,os as planeSkill,TS as registerSkill,to as reportCheckSkill,Tt as reportObjectSchema,Hy as reportToBlockKit,Yy as reportToLarkCard,Vy as reportToMarkdown,Zy as reportToNotionBlocks,yi as runnerSkill,Vt as sentrySkill,ba as skill,zs as skillInstallerSkill,P as slackSkill,Bo as socialCardSkill,yi as testRunnerSkill,is as vikunjaSkill,da as workflowBuilderSkill};
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zibby/skills",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Built-in skill definitions for the Zibby agent-workflow framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -116,10 +116,10 @@
|
|
|
116
116
|
"dependencies": {
|
|
117
117
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
118
118
|
"@resvg/resvg-js": "^2.6.2",
|
|
119
|
-
"@zibby/agent-workflow": "2.0.
|
|
119
|
+
"@zibby/agent-workflow": "2.0.1",
|
|
120
120
|
"@zibby/bin-oxlint": "^1.73.0",
|
|
121
121
|
"@zibby/bin-semgrep": "^1.169.0",
|
|
122
|
-
"@zibby/skill-ids": "2.0.
|
|
122
|
+
"@zibby/skill-ids": "2.0.1",
|
|
123
123
|
"echarts": "^6.1.0",
|
|
124
124
|
"mem0ai": "npm:@zibby/mem0ai@^3.0.5",
|
|
125
125
|
"zod": "^3.23.0 || ^4.0.0"
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
136
|
"optionalDependencies": {
|
|
137
|
-
"@zibby/mcp-browser": "2.0.
|
|
137
|
+
"@zibby/mcp-browser": "2.0.1",
|
|
138
138
|
"@zibby/mcp-memory": "*"
|
|
139
139
|
},
|
|
140
140
|
"devDependencies": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zibby/skills",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Built-in skill definitions for the Zibby agent-workflow framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -116,10 +116,10 @@
|
|
|
116
116
|
"dependencies": {
|
|
117
117
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
118
118
|
"@resvg/resvg-js": "^2.6.2",
|
|
119
|
-
"@zibby/agent-workflow": "2.0.
|
|
119
|
+
"@zibby/agent-workflow": "2.0.1",
|
|
120
120
|
"@zibby/bin-oxlint": "^1.73.0",
|
|
121
121
|
"@zibby/bin-semgrep": "^1.169.0",
|
|
122
|
-
"@zibby/skill-ids": "2.0.
|
|
122
|
+
"@zibby/skill-ids": "2.0.1",
|
|
123
123
|
"echarts": "^6.1.0",
|
|
124
124
|
"mem0ai": "npm:@zibby/mem0ai@^3.0.5",
|
|
125
125
|
"zod": "^3.23.0 || ^4.0.0"
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
136
|
"optionalDependencies": {
|
|
137
|
-
"@zibby/mcp-browser": "2.0.
|
|
137
|
+
"@zibby/mcp-browser": "2.0.1",
|
|
138
138
|
"@zibby/mcp-memory": "*"
|
|
139
139
|
},
|
|
140
140
|
"devDependencies": {
|