@zibby/skills 2.0.4 → 2.0.6

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.
Files changed (55) hide show
  1. package/dist/artifact.js +7 -7
  2. package/dist/chat-notify.js +4 -4
  3. package/dist/chatProgress.js +4 -4
  4. package/dist/datasetStore.js +2 -2
  5. package/dist/discord.js +3 -3
  6. package/dist/figma.js +2 -2
  7. package/dist/git-write.js +9 -9
  8. package/dist/github.js +5 -5
  9. package/dist/gitlab.js +5 -5
  10. package/dist/googleDocs.js +9 -9
  11. package/dist/hubspot.js +1 -1
  12. package/dist/index.js +179 -181
  13. package/dist/jira.d.ts +36 -1
  14. package/dist/jira.js +22 -20
  15. package/dist/kvMemory.js +2 -2
  16. package/dist/lark.js +2 -2
  17. package/dist/larkAttendance.js +1 -1
  18. package/dist/larkDocs.js +3 -3
  19. package/dist/lib/http-deadline.d.ts +117 -0
  20. package/dist/lib/http-deadline.js +1 -0
  21. package/dist/lib/markup.d.ts +158 -0
  22. package/dist/lib/markup.js +22 -0
  23. package/dist/linear.js +15 -15
  24. package/dist/linkedin.js +2 -2
  25. package/dist/llm-billing.js +1 -1
  26. package/dist/notion.js +7 -7
  27. package/dist/opendesign.js +2 -2
  28. package/dist/package.json +6 -5
  29. package/dist/report.d.ts +42 -42
  30. package/dist/review.js +4 -4
  31. package/dist/reviewMemoryIo.js +1 -1
  32. package/dist/sentry.js +2 -2
  33. package/dist/skill-installer.js +3 -3
  34. package/dist/slack.js +2 -2
  35. package/dist/trackers/github-adapter.js +5 -5
  36. package/dist/trackers/index.js +46 -44
  37. package/dist/trackers/jira-adapter.js +24 -22
  38. package/dist/trackers/linear-adapter.js +19 -19
  39. package/dist/trackers/plane-adapter.js +1 -1
  40. package/dist/triggerAgent.js +1 -1
  41. package/dist/vikunja.d.ts +8 -1
  42. package/dist/vikunja.js +22 -7
  43. package/docs/templates/catalog-metadata.md +101 -0
  44. package/docs/templates/composition.md +97 -0
  45. package/docs/templates/deploy-time-config.md +228 -0
  46. package/docs/templates/index.md +138 -0
  47. package/docs/templates/models.md +157 -0
  48. package/docs/templates/node-declarations.md +149 -0
  49. package/docs/templates/remote-mcp.md +105 -0
  50. package/docs/templates/requires.md +183 -0
  51. package/docs/templates/sidecars.md +163 -0
  52. package/docs/templates/stores.md +113 -0
  53. package/docs/templates/surfaces.md +184 -0
  54. package/docs/templates/update-behavior.md +108 -0
  55. package/package.json +6 -5
package/dist/vikunja.js CHANGED
@@ -1,9 +1,24 @@
1
- import{existsSync as j}from"fs";import{fileURLToPath as I}from"url";import{dirname as w,resolve as N}from"path";var y=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"}),A=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"}});import{resolveIntegrationToken as S}from"@zibby/core/backend-client.js";function O(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=w(I(import.meta.url)),a=N(t,"..","bin","mcp-skill.mjs");return j(a)?a:null}function b(t){return String(t||"").trim().replace(/\/+$/,"").replace(/\/api\/v1$/,"")}var u=null;async function p(){if(u)return u;let t=b(process.env.VIKUNJA_URL),a=(process.env.VIKUNJA_TOKEN||"").trim();try{let e=await S("vikunja"),n=String(e?.token||"").trim(),i=b(e?.instanceUrl);if(n&&i)return u={origin:i,token:n},u;if(n&&!i)throw new Error("The connected Vikunja integration has no instance URL \u2014 reconnect it in Integrations \u2192 Vikunja.")}catch(e){if(!(t&&a))throw e}if(!(t&&a))throw new Error("No Vikunja credentials. Connect the board under Integrations \u2192 Vikunja (instance URL + an API token minted in the board's Settings \u2192 API Tokens).");return u={origin:t,token:a},u}function $(){u=null}async function s(t,a,e={}){let{origin:n,token:i}=await p(),r=`${n}/api/v1${a}`;if(e.query&&Object.keys(e.query).length){let l=new URLSearchParams;for(let[f,k]of Object.entries(e.query))k!=null&&k!==""&&l.set(f,String(k));let d=l.toString();d&&(r+=`?${d}`)}let o=await fetch(r,{method:t,headers:{Authorization:`Bearer ${i}`,...e.body?{"Content-Type":"application/json"}:{}},...e.body?{body:JSON.stringify(e.body)}:{}}),c=await o.text();if(!o.ok)throw new Error(`Vikunja ${t} ${a} \u2192 ${o.status}: ${c.slice(0,300)}`);if(!c)return null;try{return JSON.parse(c)}catch{return c}}function v(t){return typeof t!="string"||!t?"":t.replace(/<br\s*\/?>/gi,`
2
- `).replace(/<\/p>/gi,`
3
-
4
- `).replace(/<[^>]+>/g,"").replace(/&nbsp;/g," ").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/\n{3,}/g,`
5
-
6
- `).trim()}function h(t){if(t==null||t==="")return null;if(typeof t=="number"&&Number.isFinite(t))return Math.max(0,Math.min(5,Math.round(t)));let a=String(t).trim().toLowerCase(),e={unset:0,none:0,low:1,medium:2,normal:2,high:3,urgent:4,now:5,critical:5,blocker:5,do_now:5,"do now":5};return Object.prototype.hasOwnProperty.call(e,a)?e[a]:null}function m(t,a){return t?{id:String(t.id),identifier:t.identifier||null,projectId:t.project_id??null,title:t.title||"",description:v(t.description),done:!!t.done,priority:t.priority??0,labels:(Array.isArray(t.labels)?t.labels:[]).map(e=>e?.title).filter(Boolean),assignees:(Array.isArray(t.assignees)?t.assignees:[]).map(e=>e?.username).filter(Boolean),dueDate:t.due_date&&!String(t.due_date).startsWith("0001-")?t.due_date:null,url:a?`${a}/tasks/${t.id}`:void 0}:null}async function g(t,{create:a=!1}={}){let e=String(t||"").trim();if(!e)return null;let n=await s("GET","/labels",{query:{s:e}}),i=(Array.isArray(n)?n:[]).find(o=>(o?.title||"").trim().toLowerCase()===e.toLowerCase());return i?.id?i.id:a?(await s("PUT","/labels",{body:{title:e}}))?.id??null:null}async function _(t){let a=encodeURIComponent(String(t)),e=await s("GET",`/projects/${a}/views`),n=(Array.isArray(e)?e:[]).find(r=>r?.view_kind==="kanban");if(!n?.id)throw new Error(`Project ${t} has no kanban view \u2014 there are no columns to move between.`);let i=await s("GET",`/projects/${a}/views/${n.id}/buckets`);return{viewId:n.id,buckets:Array.isArray(i)?i:[]}}var R={id:"vikunja",serverName:"vikunja",allowedTools:["mcp__vikunja__*"],requiresIntegration:y.VIKUNJA,callsBackend:!0,envKeys:["VIKUNJA_URL","VIKUNJA_TOKEN"],description:"Vikunja \u2014 kanban board: tasks, columns, labels, comments (API token)",promptFragment:`## Vikunja
1
+ import{existsSync as Rt}from"fs";var lt={NOTE:"info",TIP:"success",IMPORTANT:"note",WARNING:"warning",CAUTION:"error"},B={info:"NOTE",success:"TIP",note:"IMPORTANT",warning:"WARNING",error:"CAUTION"},ut=2e5,Q=6,dt=/^```([A-Za-z0-9_+.-]{0,32})\s*$/,pt=/^(#{1,6})[ \t]+(.*?)\s*$/,kt=/^(?:-{3,}|\*{3,}|_{3,})\s*$/,J=/^>(?: ?(.*))?$/,U=/^[-*][ \t]+\[([ xX])\][ \t]+(.*)$/,V=/^[-*][ \t]+(.*)$/,F=/^(\d{1,3})[.)][ \t]+(.*)$/,H=/^\|.*\|\s*$/,ht=/^\|?\s*:?-{1,}:?\s*(?:\|\s*:?-{1,}:?\s*)*\|?\s*$/,gt=/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i;function W(n){let e=n.trim().replace(/^\|/,"").replace(/\|$/,""),t=[],r="";for(let i=0;i<e.length;i++){let s=e[i];if(s==="\\"&&e[i+1]==="|"){r+="|",i++;continue}if(s==="|"){t.push(r.trim()),r="";continue}r+=s}return t.push(r.trim()),t}function tt(n){let t=String(n??"").slice(0,ut).replace(/\r\n?/g,`
2
+ `).split(`
3
+ `);return D(t,0)}function D(n,e){let t=[],r=0;for(;r<n.length;){let i=n[r];if(i.trim()===""){r++;continue}if(/^[ \t]/.test(i)){t.push({t:"paragraph",c:f(i)}),r++;continue}let s;if(s=dt.exec(i)){let a=s[1]||"",o=[];for(r++;r<n.length&&!/^```\s*$/.test(n[r]);)o.push(n[r]),r++;r++,t.push({t:"code",lang:a,v:o.join(`
4
+ `)});continue}if(kt.test(i)){t.push({t:"rule"}),r++;continue}if(s=pt.exec(i)){let a=Math.min(Q,s[1].length);t.push({t:"heading",level:a,c:f(s[2])}),r++;continue}if(J.test(i)&&e<3){let a=[];for(;r<n.length&&(s=J.exec(n[r]));)a.push(s[1]??""),r++;let o=a.findIndex(c=>c.trim()!==""),d=o>=0?gt.exec(a[o].trim()):null;if(d){let c=lt[d[1].toUpperCase()];t.push({t:"panel",kind:c,c:D(a.slice(o+1),e+1)})}else t.push({t:"quote",c:D(a,e+1)});continue}if(U.test(i)){let a=[];for(;r<n.length&&(s=U.exec(n[r]));)a.push({checked:s[1]!==" ",c:f(s[2])}),r++;t.push({t:"task",items:a});continue}if(V.test(i)){let a=[];for(;r<n.length&&!U.test(n[r])&&(s=V.exec(n[r]));)a.push([{t:"paragraph",c:f(s[1])}]),r++;t.push({t:"bullet",items:a});continue}if(s=F.exec(i)){let a=Math.max(1,parseInt(s[1],10)||1),o=[];for(;r<n.length&&(s=F.exec(n[r]));)o.push([{t:"paragraph",c:f(s[2])}]),r++;t.push({t:"ordered",start:a,items:o});continue}if(H.test(i)&&r+1<n.length&&ht.test(n[r+1])){let a=W(i).map(f);r+=2;let o=[];for(;r<n.length&&H.test(n[r]);)o.push(W(n[r]).map(f)),r++;t.push({t:"table",header:a,rows:o});continue}t.push({t:"paragraph",c:f(i)}),r++}return t}var mt=/^https?:\/\/[^\s<>()[\]]+/,ft=/^\[([^\]\n]{1,300})\]\((https?:\/\/[^\s)]{1,2000})\)/,bt=/[.,;:!?'"]+$/;function yt(n,e){return e<=0?!0:/[\s([{"'“‘>:—–-]/.test(n[e-1])}function _t(n,e){return e>=n.length?!0:/[\s)\]}"'”’.,;:!?—–-]/.test(n[e])}function vt(n,e,t){let r=e;for(;r<n.length;){let i=n.indexOf(t,r);if(i<0)return-1;let s=n[i-1],a=n[i+t.length],o=t.length===1&&(s===t||a===t);if(i>e&&s&&!/\s/.test(s)&&!o&&_t(n,i+t.length))return i;r=i+1}return-1}function f(n){let e=String(n??""),t=[],r="",i=()=>{r&&(t.push({t:"text",v:r}),r="")},s=0;for(;s<e.length;){let a=e[s];if(a==="`"){let o=1;for(;e[s+o]==="`";)o++;let d="`".repeat(o),c=e.indexOf(d,s+o);if(c>s+o-1&&c!==-1){let l=e.slice(s+o,c);if(l.trim()){i(),t.push({t:"code",v:l.length>1&&l.startsWith(" ")&&l.endsWith(" ")?l.slice(1,-1):l}),s=c+o;continue}}r+=d,s+=o;continue}if(a==="["){let o=ft.exec(e.slice(s));if(o){i(),t.push({t:"link",href:o[2],c:f(o[1])}),s+=o[0].length;continue}}if((a==="*"||a==="_"||a==="~")&&yt(e,s)){let o=e[s+1]===a;if(a==="~"&&!o){r+=a,s++;continue}let d=o?a+a:a,c=s+d.length;if(c<e.length&&!/\s/.test(e[c])){let l=vt(e,c,d);if(l>c){i();let u=f(e.slice(c,l));t.push(a==="~"?{t:"strike",c:u}:o?{t:"strong",c:u}:{t:"em",c:u}),s=l+d.length;continue}}}if(a==="h"&&(s===0||/[\s(<]/.test(e[s-1]))){let o=mt.exec(e.slice(s));if(o){let d=o[0],c=bt.exec(d);if(c&&(d=d.slice(0,-c[0].length)),d.length>8){i(),t.push({t:"link",href:d,c:[{t:"text",v:d}]}),s+=d.length;continue}}}r+=a,s++}return i(),t}function N(n){return n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function v(n){let e="";for(let t of n)switch(t.t){case"text":e+=N(t.v);break;case"br":e+="<br>";break;case"code":e+=`<code>${N(t.v)}</code>`;break;case"strong":e+=`<strong>${v(t.c)}</strong>`;break;case"em":e+=`<em>${v(t.c)}</em>`;break;case"strike":e+=`<s>${v(t.c)}</s>`;break;case"link":e+=`<a href="${N(t.href)}" target="_blank" rel="noopener noreferrer">${v(t.c)}</a>`;break}return e}function It(n){return n.length&&n[0].t==="text"&&/^ /.test(n[0].v)?[{t:"text",v:`\xA0${n[0].v.slice(1)}`},...n.slice(1)]:n}function E(n){let e="";for(let t of n)switch(t.t){case"paragraph":e+=`<p>${v(It(t.c))}</p>`;break;case"heading":e+=`<h${t.level}>${v(t.c)}</h${t.level}>`;break;case"bullet":e+=`<ul>${t.items.map(r=>`<li>${E(r)}</li>`).join("")}</ul>`;break;case"ordered":e+=`<ol${t.start!==1?` start="${t.start}"`:""}>${t.items.map(r=>`<li>${E(r)}</li>`).join("")}</ol>`;break;case"task":e+=`<ul data-type="taskList">${t.items.map(r=>`<li data-type="taskItem" data-checked="${r.checked?"true":"false"}"><label><input type="checkbox"${r.checked?' checked="checked"':""}><span></span></label><div><p>${v(r.c)}</p></div></li>`).join("")}</ul>`;break;case"code":e+=`<pre><code${t.lang?` class="language-${N(t.lang)}"`:""}>${N(t.v)}</code></pre>`;break;case"quote":e+=`<blockquote>${E(t.c)}</blockquote>`;break;case"panel":e+=`<blockquote><p><strong>${B[t.kind][0]}${B[t.kind].slice(1).toLowerCase()}</strong></p>${E(t.c)}</blockquote>`;break;case"rule":e+="<hr>";break;case"table":{let r="<table><tbody>";t.header&&(r+=`<tr>${t.header.map(i=>`<th><p>${v(i)}</p></th>`).join("")}</tr>`);for(let i of t.rows)r+=`<tr>${i.map(s=>`<td><p>${v(s)}</p></td>`).join("")}</tr>`;e+=`${r}</tbody></table>`;break}}return e}function et(n){try{return E(tt(n))||`<p>${N(String(n??""))}</p>`}catch{return String(n??"").split(`
5
+ `).map(e=>`<p>${N(e)}</p>`).join("")}}function I(n){let e="";for(let t of n)switch(t.t){case"text":e+=t.v;break;case"br":e+=`
6
+ `;break;case"code":e+=`\`${t.v}\``;break;case"strong":e+=`**${I(t.c)}**`;break;case"em":e+=`_${I(t.c)}_`;break;case"strike":e+=`~~${I(t.c)}~~`;break;case"link":{let r=I(t.c);e+=!r||r===t.href?t.href:`[${r}](${t.href})`;break}}return e}function w(n){let e="";for(let t of n)switch(t.t){case"paragraph":e+=`${I(t.c)}
7
+ `;break;case"heading":e+=`${"#".repeat(Math.max(1,Math.min(Q,t.level)))} ${I(t.c)}
8
+ `;break;case"bullet":for(let r of t.items)e+=`- ${w(r).trim()}
9
+ `;break;case"ordered":t.items.forEach((r,i)=>{e+=`${t.start+i}. ${w(r).trim()}
10
+ `});break;case"task":for(let r of t.items)e+=`- [${r.checked?"x":" "}] ${I(r.c)}
11
+ `;break;case"code":e+=`\`\`\`${t.lang}
12
+ ${t.v}
13
+ \`\`\`
14
+ `;break;case"quote":e+=z(w(t.c));break;case"panel":e+=`> [!${B[t.kind]}]
15
+ ${z(w(t.c))}`;break;case"rule":e+=`---
16
+ `;break;case"table":{let r=i=>`| ${i.map(s=>I(s).replace(/\|/g,"\\|")).join(" | ")} |
17
+ `;t.header&&(e+=r(t.header)+`|${t.header.map(()=>" --- |").join("")}
18
+ `);for(let i of t.rows)e+=r(i);break}}return e}function wt(n){return n.replace(/^\n+/,"").replace(/\s+$/,"")}function z(n){return n.replace(/\n$/,"").split(`
19
+ `).map(e=>e?`> ${e}`:">").join(`
20
+ `)+`
21
+ `}var Tt={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:"\xA0",hellip:"\u2026",mdash:"\u2014",ndash:"\u2013",lsquo:"\u2018",rsquo:"\u2019",ldquo:"\u201C",rdquo:"\u201D",copy:"\xA9",reg:"\xAE",trade:"\u2122"};function X(n){return n.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(e,t)=>{if(t[0]==="#"){let i=t[1]==="x"||t[1]==="X"?parseInt(t.slice(2),16):parseInt(t.slice(1),10);return Number.isFinite(i)&&i>0&&i<1114112?String.fromCodePoint(i):e}let r=Tt[t.toLowerCase()];return r===void 0?e:r})}function xt(n){let e=[],t=/<!--[\s\S]*?-->|<\/([A-Za-z][\w:-]*)\s*>|<([A-Za-z][\w:-]*)((?:\s+[^\s=>/]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?)*)\s*(\/?)>|([^<]+)|(<)/g,r;for(;r=t.exec(n);)if(r[1])e.push({kind:"close",name:r[1].toLowerCase()});else if(r[2]){let i={},s=/([^\s=>/]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g,a;for(;a=s.exec(r[3]||"");)i[a[1].toLowerCase()]=X(a[2]??a[3]??a[4]??"");let o=r[2].toLowerCase();e.push({kind:"open",name:o,attrs:i,selfClose:!!r[4]||o==="br"||o==="hr"||o==="img"||o==="input"})}else r[5]!==void 0?e.push({kind:"text",v:X(r[5])}):r[6]&&e.push({kind:"text",v:"<"});return e}var Y=new Set(["p","div","h1","h2","h3","h4","h5","h6","ul","ol","li","blockquote","pre","hr","table","thead","tbody","tfoot","tr","td","th","section","article","header","footer","details","summary","figure","figcaption"]),jt=new Set(["script","style","label","input","button","svg","head","title"]);function A(n){let e=[],t=0,r=[],i=[],s=c=>{let l=At(r);r=[],l.length&&e.push(c?c(l):{t:"paragraph",c:l})},a=c=>{if(!c)return;let l={t:"text",v:c};for(let u=i.length-1;u>=0;u--){let p=i[u];p==="strong"?l={t:"strong",c:[l]}:p==="em"?l={t:"em",c:[l]}:p==="strike"?l={t:"strike",c:[l]}:l={t:"link",href:p.link,c:[l]}}r.push(l)},o=c=>{let l=[],u=1;for(;t<n.length;){let p=n[t++];if(p.kind==="open"&&p.name===c&&!p.selfClose&&u++,p.kind==="close"&&p.name===c&&(u--,u===0))break;l.push(p)}return l},d=c=>c.filter(l=>l.kind==="text").map(l=>l.v).join("");for(;t<n.length;){let c=n[t++];if(c.kind==="text"){a(c.v.replace(/\s+/g," "));continue}if(c.kind==="close"){if(c.name==="strong"||c.name==="b")q(i,"strong");else if(c.name==="em"||c.name==="i")q(i,"em");else if(c.name==="s"||c.name==="del"||c.name==="strike")q(i,"strike");else if(c.name==="a"){let u=i.map(p=>typeof p=="object").lastIndexOf(!0);u>=0&&i.splice(u,1)}else Y.has(c.name)&&s();continue}let l=c.name;if(jt.has(l)){c.selfClose||o(l);continue}if(l==="br"){r.push({t:"br"});continue}if(l==="hr"){s(),e.push({t:"rule"});continue}if(l==="strong"||l==="b"){i.push("strong");continue}if(l==="em"||l==="i"){i.push("em");continue}if(l==="s"||l==="del"||l==="strike"){i.push("strike");continue}if(l==="a"){c.attrs.href?i.push({link:c.attrs.href}):i.push({link:""});continue}if(l==="code"){let u=d(o("code"));u&&r.push({t:"code",v:u});continue}if(l==="pre"){s();let u=o("pre"),p=u.find(_=>_.kind==="open"&&_.name==="code"),y=p&&/language-([\w+.-]+)/.exec(p.attrs.class||"")?.[1]||"";e.push({t:"code",lang:y,v:d(u).replace(/\n$/,"")});continue}if(/^h[1-6]$/.test(l)){s();let u=Number(l[1]),p=A(o(l));e.push({t:"heading",level:u,c:f(w(p).trim().replace(/\n+/g," "))});continue}if(l==="blockquote"){s(),e.push({t:"quote",c:A(o(l))});continue}if(l==="ul"||l==="ol"){s();let u=o(l),p=(c.attrs["data-type"]||"")==="tasklist",y=[],_=[],T=0;for(;T<u.length;){let h=u[T++];if(h.kind==="open"&&h.name==="li"){let b=[],x=1;for(;T<u.length;){let g=u[T++];if(g.kind==="open"&&g.name==="li"&&!g.selfClose&&x++,g.kind==="close"&&g.name==="li"&&(x--,x===0))break;b.push(g)}y.push(b);let j=(h.attrs["data-checked"]||"")==="true"||b.some(g=>g.kind==="open"&&g.name==="input"&&"checked"in g.attrs);_.push(j)}}p||y.some(h=>h.some(b=>b.kind==="open"&&b.name==="input"&&(b.attrs.type||"")==="checkbox"))?e.push({t:"task",items:y.map((h,b)=>({checked:_[b],c:f(w(A(h)).trim().replace(/\n+/g," "))}))}):l==="ul"?e.push({t:"bullet",items:y.map(h=>Z(A(h)))}):e.push({t:"ordered",start:Math.max(1,parseInt(c.attrs.start||"1",10)||1),items:y.map(h=>Z(A(h)))});continue}if(l==="table"){s();let u=o("table"),p=[],y=[],_=0;for(;_<u.length;){let P=u[_++];if(P.kind==="open"&&P.name==="tr"){let h=[],b=!0,x=1,j=[];for(;_<u.length;){let m=u[_++];if(m.kind==="open"&&m.name==="tr"&&!m.selfClose&&x++,m.kind==="close"&&m.name==="tr"&&(x--,x===0))break;j.push(m)}let g=0;for(;g<j.length;){let m=j[g++];if(m.kind==="open"&&(m.name==="td"||m.name==="th")){m.name!=="th"&&(b=!1);let G=[],M=1;for(;g<j.length;){let $=j[g++];if($.kind==="open"&&$.name===m.name&&!$.selfClose&&M++,$.kind==="close"&&$.name===m.name&&(M--,M===0))break;G.push($)}h.push(f(w(A(G)).trim().replace(/\n+/g," ")))}}h.length&&(p.push(h),y.push(b))}}let T=p.length&&y[0]?p[0]:null;e.push({t:"table",header:T,rows:p.slice(T?1:0)});continue}if(Y.has(l)){s();continue}}return s(),e}function q(n,e){let t=n.lastIndexOf(e);t>=0&&n.splice(t,1)}function Z(n){return n.length?n:[{t:"paragraph",c:[]}]}function At(n){let e=[...n];for(;e.length&&e[0].t==="text"&&!e[0].v.trim();)e.shift();for(;e.length&&e[e.length-1].t==="text"&&!e[e.length-1].v.trim();)e.pop();e.length&&e[0].t==="text"&&(e[0]={t:"text",v:e[0].v.replace(/^\s+/," ")});let t=e.length-1;return t>=0&&e[t].t==="text"&&(e[t]={t:"text",v:e[t].v.replace(/\s+$/,"")}),e}function Nt(n){try{let e=String(n??"");return/<[a-z][\s\S]*>/i.test(e)?A(xt(e)):tt(e)}catch{return[]}}function nt(n){try{return wt(w(Nt(n)))}catch{return""}}import{fileURLToPath as Ct}from"url";import{dirname as Pt,resolve as Mt}from"path";var rt=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"}),Dt=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"}});import{resolveIntegrationToken as Ut}from"@zibby/core/backend-client.js";var O={api:{knob:"SKILL_API_TIMEOUT_MS",fallback:3e4},transfer:{knob:"SKILL_TRANSFER_TIMEOUT_MS",fallback:12e4},job:{knob:"SKILL_JOB_TIMEOUT_MS",fallback:3e5}};function St(n,e,t=process.env){let r=Number(t?.[n]);return Number.isFinite(r)&&r>0?Math.min(6e5,Math.max(1e3,Math.floor(r))):e}function $t(n="api",e=process.env){let t=O[n]||O.api;return St(t.knob,t.fallback,e)}function Et(n){return n?.name==="TimeoutError"||n?.name==="AbortError"}function Lt(n){try{return new URL(String(n?.url??n)).host||"unknown host"}catch{return"unknown host"}}function Ot(n,e){if(!n)return e;let t=new AbortController,r=a=>{t.signal.aborted||t.abort(a)},i=()=>r(n.reason),s=()=>r(e.reason);return t.signal.addEventListener("abort",()=>{n.removeEventListener("abort",i),e.removeEventListener("abort",s)},{once:!0}),n.aborted?r(n.reason):e.aborted?r(e.reason):(n.addEventListener("abort",i,{once:!0}),e.addEventListener("abort",s,{once:!0})),t.signal}async function it(n,e={},t={}){let r=t.kind||"api",i=(O[r]||O.api).knob,s=t.timeoutMs?Math.min(6e5,Math.max(1e3,Math.floor(t.timeoutMs))):$t(r),a=e?.signal;if(a?.aborted)throw a.reason??new DOMException("This operation was aborted","AbortError");let o=AbortSignal.timeout(s),d=Ot(a,o);try{return await fetch(n,{...e,signal:d})}catch(c){throw o.aborted&&Et(c)?new Error(`${t.what||"request"} TIMED OUT after ${s}ms against ${Lt(n)} (${i})`):c}}function qt(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let n=Pt(Ct(import.meta.url)),e=Mt(n,"..","bin","mcp-skill.mjs");return Rt(e)?e:null}function st(n){return String(n||"").trim().replace(/\/+$/,"").replace(/\/api\/v1$/,"")}var S=null;async function L(){if(S)return S;let n=st(process.env.VIKUNJA_URL),e=(process.env.VIKUNJA_TOKEN||"").trim();try{let t=await Ut("vikunja"),r=String(t?.token||"").trim(),i=st(t?.instanceUrl);if(r&&i)return S={origin:i,token:r},S;if(r&&!i)throw new Error("The connected Vikunja integration has no instance URL \u2014 reconnect it in Integrations \u2192 Vikunja.")}catch(t){if(!(n&&e))throw t}if(!(n&&e))throw new Error("No Vikunja credentials. Connect the board under Integrations \u2192 Vikunja (instance URL + an API token minted in the board's Settings \u2192 API Tokens).");return S={origin:n,token:e},S}function Yt(){S=null}async function k(n,e,t={}){let{origin:r,token:i}=await L(),s=`${r}/api/v1${e}`;if(t.query&&Object.keys(t.query).length){let d=new URLSearchParams;for(let[l,u]of Object.entries(t.query))u!=null&&u!==""&&d.set(l,String(u));let c=d.toString();c&&(s+=`?${c}`)}let a=await it(s,{method:n,headers:{Authorization:`Bearer ${i}`,...t.body?{"Content-Type":"application/json"}:{}},...t.body?{body:JSON.stringify(t.body)}:{}},{kind:"api",what:`Vikunja ${n} ${e}`}),o=await a.text();if(!a.ok)throw new Error(`Vikunja ${n} ${e} \u2192 ${a.status}: ${o.slice(0,300)}`);if(!o)return null;try{return JSON.parse(o)}catch{return o}}function ct(n){return typeof n!="string"||!n?"":nt(n)}function K(n){return et(n)}function ot(n){if(n==null||n==="")return null;if(typeof n=="number"&&Number.isFinite(n))return Math.max(0,Math.min(5,Math.round(n)));let e=String(n).trim().toLowerCase(),t={unset:0,none:0,low:1,medium:2,normal:2,high:3,urgent:4,now:5,critical:5,blocker:5,do_now:5,"do now":5};return Object.prototype.hasOwnProperty.call(t,e)?t[e]:null}function R(n,e){return n?{id:String(n.id),identifier:n.identifier||null,projectId:n.project_id??null,title:n.title||"",description:ct(n.description),done:!!n.done,priority:n.priority??0,labels:(Array.isArray(n.labels)?n.labels:[]).map(t=>t?.title).filter(Boolean),assignees:(Array.isArray(n.assignees)?n.assignees:[]).map(t=>t?.username).filter(Boolean),dueDate:n.due_date&&!String(n.due_date).startsWith("0001-")?n.due_date:null,url:e?`${e}/tasks/${n.id}`:void 0}:null}async function C(n,{create:e=!1}={}){let t=String(n||"").trim();if(!t)return null;let r=await k("GET","/labels",{query:{s:t}}),i=(Array.isArray(r)?r:[]).find(a=>(a?.title||"").trim().toLowerCase()===t.toLowerCase());return i?.id?i.id:e?(await k("PUT","/labels",{body:{title:t}}))?.id??null:null}async function at(n){let e=encodeURIComponent(String(n)),t=await k("GET",`/projects/${e}/views`),r=(Array.isArray(t)?t:[]).find(s=>s?.view_kind==="kanban");if(!r?.id)throw new Error(`Project ${n} has no kanban view \u2014 there are no columns to move between.`);let i=await k("GET",`/projects/${e}/views/${r.id}/buckets`);return{viewId:r.id,buckets:Array.isArray(i)?i:[]}}var Zt={id:"vikunja",serverName:"vikunja",allowedTools:["mcp__vikunja__*"],requiresIntegration:rt.VIKUNJA,callsBackend:!0,envKeys:["VIKUNJA_URL","VIKUNJA_TOKEN"],description:"Vikunja \u2014 kanban board: tasks, columns, labels, comments (API token)",promptFragment:`## Vikunja
7
22
  You can operate the user's Vikunja board directly. Tools:
8
23
 
9
24
  ### Discovery
@@ -24,4 +39,4 @@ You can operate the user's Vikunja board directly. Tools:
24
39
  ### Notes
25
40
  - Address tasks by their NUMERIC id. The '#1' identifier shown in the UI is per-project and not unique.
26
41
  - Descriptions may come back as plain text converted from HTML \u2014 that is expected.
27
- - Priority accepts a number 0-5 or a word: low, medium, high, urgent, now.`,resolve(){let t={};for(let e of this.envKeys)process.env[e]&&(t[e]=process.env[e]);let a=O();return a?{type:"stdio",command:"node",args:[a,"../dist/vikunja.js","vikunjaSkill"],env:t,description:this.description,alwaysLoad:!0}:{command:null,args:[],env:t,description:this.description}},async handleToolCall(t,a){try{let e=a||{};switch(t){case"vikunja_list_projects":{let n=await s("GET","/projects"),i=(Array.isArray(n)?n:[]).map(r=>({id:String(r.id),title:r.title||"",description:v(r.description)}));return JSON.stringify({count:i.length,projects:i})}case"vikunja_list_buckets":{let{viewId:n,buckets:i}=await _(e.projectId);return JSON.stringify({viewId:n,count:i.length,buckets:i.map(r=>({id:r.id,title:r.title,taskCount:r.count??null}))})}case"vikunja_list_labels":{let n=await s("GET","/labels",{query:{s:e.search}}),i=(Array.isArray(n)?n:[]).map(r=>({id:r.id,title:r.title}));return JSON.stringify({count:i.length,labels:i})}case"vikunja_list_tasks":{let{origin:n}=await p(),i=[];if(e.label){let c=await g(e.label);if(!c)return JSON.stringify({count:0,tasks:[],note:`No label named "${e.label}" exists.`});i.push(`labels in ${c}`)}(e.done===!0||e.done===!1)&&i.push(`done = ${e.done}`);let r=await s("GET",`/projects/${encodeURIComponent(String(e.projectId))}/tasks`,{query:{...i.length?{filter:i.join(" && ")}:{},sort_by:"updated",order_by:"desc",per_page:Math.min(Number(e.limit)||25,50)}}),o=(Array.isArray(r)?r:[]).map(c=>m(c,n));return JSON.stringify({count:o.length,tasks:o})}case"vikunja_get_task":{let{origin:n}=await p(),i=await s("GET",`/tasks/${encodeURIComponent(String(e.taskId))}`);return JSON.stringify({task:m(i,n)})}case"vikunja_create_task":{let{origin:n}=await p(),i=h(e.priority),r=await s("PUT",`/projects/${encodeURIComponent(String(e.projectId))}/tasks`,{body:{title:e.title,...e.description?{description:e.description}:{},...i!==null?{priority:i}:{},...e.dueDate?{due_date:e.dueDate}:{}}});if(!r?.id)throw new Error("Vikunja created no task (no id in the response)");let o=[];for(let l of Array.isArray(e.labels)?e.labels:[])try{let d=await g(l,{create:!0});d&&await s("PUT",`/tasks/${r.id}/labels`,{body:{label_id:d}})}catch(d){o.push(`${l}: ${d?.message||d}`)}let c=await s("GET",`/tasks/${r.id}`);return JSON.stringify({created:m(c||r,n),...o.length?{labelErrors:o}:{}})}case"vikunja_update_task":{let{origin:n}=await p(),i=encodeURIComponent(String(e.taskId)),r=await s("GET",`/tasks/${i}`);if(!r?.id)throw new Error(`No task ${e.taskId}`);let o=h(e.priority),c=await s("POST",`/tasks/${i}`,{body:{...r,...e.title!==void 0?{title:e.title}:{},...e.description!==void 0?{description:e.description}:{},...o!==null?{priority:o}:{},...e.done!==void 0?{done:!!e.done}:{},...e.dueDate!==void 0?{due_date:e.dueDate}:{}}});return JSON.stringify({updated:m(c,n)})}case"vikunja_move_task":{let n=await s("GET",`/tasks/${encodeURIComponent(String(e.taskId))}`);if(!n?.id)throw new Error(`No task ${e.taskId}`);let i=e.projectId??n.project_id,r=String(e.column||"").trim().toLowerCase();if(!r)throw new Error("No target column given.");let{viewId:o,buckets:c}=await _(i),l=c.find(d=>(d?.title||"").trim().toLowerCase()===r);return l?.id?(await s("POST",`/projects/${encodeURIComponent(String(i))}/views/${o}/buckets/${l.id}/tasks`,{body:{task_id:Number(n.id)}}),JSON.stringify({moved:!0,taskId:String(n.id),column:l.title})):JSON.stringify({moved:!1,error:`No column named "${e.column}".`,availableColumns:c.map(d=>d.title)})}case"vikunja_add_label":{let n=await g(e.label,{create:e.create!==!1});return n?(await s("PUT",`/tasks/${encodeURIComponent(String(e.taskId))}/labels`,{body:{label_id:n}}),JSON.stringify({ok:!0,taskId:String(e.taskId),label:e.label})):JSON.stringify({ok:!1,error:`No label named "${e.label}" and creating it was declined.`})}case"vikunja_remove_label":{let n=await g(e.label);return n?(await s("DELETE",`/tasks/${encodeURIComponent(String(e.taskId))}/labels/${n}`),JSON.stringify({ok:!0,taskId:String(e.taskId),label:e.label})):JSON.stringify({ok:!1,error:`No label named "${e.label}".`})}case"vikunja_add_comment":{let n=await s("PUT",`/tasks/${encodeURIComponent(String(e.taskId))}/comments`,{body:{comment:e.comment}});return JSON.stringify({ok:!0,commentId:n?.id??null})}case"vikunja_assign":{let n=await s("GET","/users",{query:{s:e.username}}),i=(Array.isArray(n)?n:[]).find(r=>(r?.username||"").toLowerCase()===String(e.username||"").toLowerCase());return i?.id?(await s("PUT",`/tasks/${encodeURIComponent(String(e.taskId))}/assignees`,{body:{user_id:i.id}}),JSON.stringify({ok:!0,taskId:String(e.taskId),assignee:i.username})):JSON.stringify({ok:!1,error:`No user "${e.username}".`})}default:return JSON.stringify({error:`Unknown vikunja tool: ${t}`})}}catch(e){return JSON.stringify({error:e?.message||String(e)})}},tools:[{name:"vikunja_list_projects",description:"List Vikunja projects/boards (id, title). Everything else is project-scoped \u2014 start here when you do not already have a project id.",input_schema:{type:"object",properties:{}}},{name:"vikunja_list_buckets",description:"List a project's kanban COLUMNS (bucket id + title). Call this before moving a card so you use a column that exists.",input_schema:{type:"object",properties:{projectId:{type:"string",description:"Project id"}},required:["projectId"]}},{name:"vikunja_list_labels",description:"List labels (id, title). Optionally narrow with a search string.",input_schema:{type:"object",properties:{search:{type:"string",description:"Optional server-side search on the label title"}}}},{name:"vikunja_list_tasks",description:"List a project's tasks, newest-updated first. Optionally filter by label title and/or done-state.",input_schema:{type:"object",properties:{projectId:{type:"string",description:"Project id"},label:{type:"string",description:"Only tasks carrying this label title"},done:{type:"boolean",description:"Filter on completion state"},limit:{type:"number",description:"Max tasks (default 25, max 50)"}},required:["projectId"]}},{name:"vikunja_get_task",description:'Get one task by its NUMERIC id (the "#1" identifier shown in the UI is per-project and not unique).',input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"}},required:["taskId"]}},{name:"vikunja_create_task",description:"Create a task in a project. Labels are attached after creation and fail soft \u2014 the task survives a label error.",input_schema:{type:"object",properties:{projectId:{type:"string",description:"Project id"},title:{type:"string",description:"Task title"},description:{type:"string",description:"Task body"},priority:{type:"string",description:"A number 0-5 or a word: low, medium, high, urgent, now"},labels:{type:"array",items:{type:"string"},description:"Label titles; created if missing"},dueDate:{type:"string",description:"RFC3339 timestamp"}},required:["projectId","title"]}},{name:"vikunja_update_task",description:"Change a task's title, description, priority, due date, or done state. To move it to another COLUMN use vikunja_move_task \u2014 a column is not a field on the task.",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},title:{type:"string"},description:{type:"string"},priority:{type:"string",description:"A number 0-5 or a word: low, medium, high, urgent, now"},done:{type:"boolean"},dueDate:{type:"string",description:"RFC3339 timestamp"}},required:["taskId"]}},{name:"vikunja_move_task",description:`Move a card to a kanban COLUMN by name (e.g. "Doing"). Vikunja has no task.status: a column is a bucket on the project's kanban view, so this resolves the view and puts the task in the matching bucket. Returns the available column names when the name does not match.`,input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},column:{type:"string",description:"Target column (bucket) title"},projectId:{type:"string",description:"Optional; defaults to the task's own project"}},required:["taskId","column"]}},{name:"vikunja_add_label",description:"Attach a label to a task by title. Creates the label when it does not exist (pass create=false to refuse instead).",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},label:{type:"string",description:"Label title"},create:{type:"boolean",description:"Create the label if missing (default true)"}},required:["taskId","label"]}},{name:"vikunja_remove_label",description:"Detach a label from a task by title.",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},label:{type:"string",description:"Label title"}},required:["taskId","label"]}},{name:"vikunja_add_comment",description:"Add a comment to a task.",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},comment:{type:"string",description:"Comment body"}},required:["taskId","comment"]}},{name:"vikunja_assign",description:"Assign a user to a task by username.",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},username:{type:"string",description:"Vikunja username"}},required:["taskId","username"]}}]};export{$ as clearVikunjaCredsCache,R as vikunjaSkill,v as vkPlainText,h as vkPriority};
42
+ - Priority accepts a number 0-5 or a word: low, medium, high, urgent, now.`,resolve(){let n={};for(let t of this.envKeys)process.env[t]&&(n[t]=process.env[t]);let e=qt();return e?{type:"stdio",command:"node",args:[e,"../dist/vikunja.js","vikunjaSkill"],env:n,description:this.description,alwaysLoad:!0}:{command:null,args:[],env:n,description:this.description}},async handleToolCall(n,e){try{let t=e||{};switch(n){case"vikunja_list_projects":{let r=await k("GET","/projects"),i=(Array.isArray(r)?r:[]).map(s=>({id:String(s.id),title:s.title||"",description:ct(s.description)}));return JSON.stringify({count:i.length,projects:i})}case"vikunja_list_buckets":{let{viewId:r,buckets:i}=await at(t.projectId);return JSON.stringify({viewId:r,count:i.length,buckets:i.map(s=>({id:s.id,title:s.title,taskCount:s.count??null}))})}case"vikunja_list_labels":{let r=await k("GET","/labels",{query:{s:t.search}}),i=(Array.isArray(r)?r:[]).map(s=>({id:s.id,title:s.title}));return JSON.stringify({count:i.length,labels:i})}case"vikunja_list_tasks":{let{origin:r}=await L(),i=[];if(t.label){let o=await C(t.label);if(!o)return JSON.stringify({count:0,tasks:[],note:`No label named "${t.label}" exists.`});i.push(`labels in ${o}`)}(t.done===!0||t.done===!1)&&i.push(`done = ${t.done}`);let s=await k("GET",`/projects/${encodeURIComponent(String(t.projectId))}/tasks`,{query:{...i.length?{filter:i.join(" && ")}:{},sort_by:"updated",order_by:"desc",per_page:Math.min(Number(t.limit)||25,50)}}),a=(Array.isArray(s)?s:[]).map(o=>R(o,r));return JSON.stringify({count:a.length,tasks:a})}case"vikunja_get_task":{let{origin:r}=await L(),i=await k("GET",`/tasks/${encodeURIComponent(String(t.taskId))}`);return JSON.stringify({task:R(i,r)})}case"vikunja_create_task":{let{origin:r}=await L(),i=ot(t.priority),s=await k("PUT",`/projects/${encodeURIComponent(String(t.projectId))}/tasks`,{body:{title:t.title,...t.description?{description:K(t.description)}:{},...i!==null?{priority:i}:{},...t.dueDate?{due_date:t.dueDate}:{}}});if(!s?.id)throw new Error("Vikunja created no task (no id in the response)");let a=[];for(let d of Array.isArray(t.labels)?t.labels:[])try{let c=await C(d,{create:!0});c&&await k("PUT",`/tasks/${s.id}/labels`,{body:{label_id:c}})}catch(c){a.push(`${d}: ${c?.message||c}`)}let o=await k("GET",`/tasks/${s.id}`);return JSON.stringify({created:R(o||s,r),...a.length?{labelErrors:a}:{}})}case"vikunja_update_task":{let{origin:r}=await L(),i=encodeURIComponent(String(t.taskId)),s=await k("GET",`/tasks/${i}`);if(!s?.id)throw new Error(`No task ${t.taskId}`);let a=ot(t.priority),o=await k("POST",`/tasks/${i}`,{body:{...s,...t.title!==void 0?{title:t.title}:{},...t.description!==void 0?{description:K(t.description)}:{},...a!==null?{priority:a}:{},...t.done!==void 0?{done:!!t.done}:{},...t.dueDate!==void 0?{due_date:t.dueDate}:{}}});return JSON.stringify({updated:R(o,r)})}case"vikunja_move_task":{let r=await k("GET",`/tasks/${encodeURIComponent(String(t.taskId))}`);if(!r?.id)throw new Error(`No task ${t.taskId}`);let i=t.projectId??r.project_id,s=String(t.column||"").trim().toLowerCase();if(!s)throw new Error("No target column given.");let{viewId:a,buckets:o}=await at(i),d=o.find(c=>(c?.title||"").trim().toLowerCase()===s);return d?.id?(await k("POST",`/projects/${encodeURIComponent(String(i))}/views/${a}/buckets/${d.id}/tasks`,{body:{task_id:Number(r.id)}}),JSON.stringify({moved:!0,taskId:String(r.id),column:d.title})):JSON.stringify({moved:!1,error:`No column named "${t.column}".`,availableColumns:o.map(c=>c.title)})}case"vikunja_add_label":{let r=await C(t.label,{create:t.create!==!1});return r?(await k("PUT",`/tasks/${encodeURIComponent(String(t.taskId))}/labels`,{body:{label_id:r}}),JSON.stringify({ok:!0,taskId:String(t.taskId),label:t.label})):JSON.stringify({ok:!1,error:`No label named "${t.label}" and creating it was declined.`})}case"vikunja_remove_label":{let r=await C(t.label);return r?(await k("DELETE",`/tasks/${encodeURIComponent(String(t.taskId))}/labels/${r}`),JSON.stringify({ok:!0,taskId:String(t.taskId),label:t.label})):JSON.stringify({ok:!1,error:`No label named "${t.label}".`})}case"vikunja_add_comment":{let r=await k("PUT",`/tasks/${encodeURIComponent(String(t.taskId))}/comments`,{body:{comment:K(t.comment)}});return JSON.stringify({ok:!0,commentId:r?.id??null})}case"vikunja_assign":{let r=await k("GET","/users",{query:{s:t.username}}),i=(Array.isArray(r)?r:[]).find(s=>(s?.username||"").toLowerCase()===String(t.username||"").toLowerCase());return i?.id?(await k("PUT",`/tasks/${encodeURIComponent(String(t.taskId))}/assignees`,{body:{user_id:i.id}}),JSON.stringify({ok:!0,taskId:String(t.taskId),assignee:i.username})):JSON.stringify({ok:!1,error:`No user "${t.username}".`})}default:return JSON.stringify({error:`Unknown vikunja tool: ${n}`})}}catch(t){return JSON.stringify({error:t?.message||String(t)})}},tools:[{name:"vikunja_list_projects",description:"List Vikunja projects/boards (id, title). Everything else is project-scoped \u2014 start here when you do not already have a project id.",input_schema:{type:"object",properties:{}}},{name:"vikunja_list_buckets",description:"List a project's kanban COLUMNS (bucket id + title). Call this before moving a card so you use a column that exists.",input_schema:{type:"object",properties:{projectId:{type:"string",description:"Project id"}},required:["projectId"]}},{name:"vikunja_list_labels",description:"List labels (id, title). Optionally narrow with a search string.",input_schema:{type:"object",properties:{search:{type:"string",description:"Optional server-side search on the label title"}}}},{name:"vikunja_list_tasks",description:"List a project's tasks, newest-updated first. Optionally filter by label title and/or done-state.",input_schema:{type:"object",properties:{projectId:{type:"string",description:"Project id"},label:{type:"string",description:"Only tasks carrying this label title"},done:{type:"boolean",description:"Filter on completion state"},limit:{type:"number",description:"Max tasks (default 25, max 50)"}},required:["projectId"]}},{name:"vikunja_get_task",description:'Get one task by its NUMERIC id (the "#1" identifier shown in the UI is per-project and not unique).',input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"}},required:["taskId"]}},{name:"vikunja_create_task",description:"Create a task in a project. Labels are attached after creation and fail soft \u2014 the task survives a label error.",input_schema:{type:"object",properties:{projectId:{type:"string",description:"Project id"},title:{type:"string",description:"Task title"},description:{type:"string",description:"Task body. Markdown is rendered (headings, **bold**, `code`, lists, - [ ] tasks, links, tables, > quotes)."},priority:{type:"string",description:"A number 0-5 or a word: low, medium, high, urgent, now"},labels:{type:"array",items:{type:"string"},description:"Label titles; created if missing"},dueDate:{type:"string",description:"RFC3339 timestamp"}},required:["projectId","title"]}},{name:"vikunja_update_task",description:"Change a task's title, description, priority, due date, or done state. To move it to another COLUMN use vikunja_move_task \u2014 a column is not a field on the task.",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},title:{type:"string"},description:{type:"string"},priority:{type:"string",description:"A number 0-5 or a word: low, medium, high, urgent, now"},done:{type:"boolean"},dueDate:{type:"string",description:"RFC3339 timestamp"}},required:["taskId"]}},{name:"vikunja_move_task",description:`Move a card to a kanban COLUMN by name (e.g. "Doing"). Vikunja has no task.status: a column is a bucket on the project's kanban view, so this resolves the view and puts the task in the matching bucket. Returns the available column names when the name does not match.`,input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},column:{type:"string",description:"Target column (bucket) title"},projectId:{type:"string",description:"Optional; defaults to the task's own project"}},required:["taskId","column"]}},{name:"vikunja_add_label",description:"Attach a label to a task by title. Creates the label when it does not exist (pass create=false to refuse instead).",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},label:{type:"string",description:"Label title"},create:{type:"boolean",description:"Create the label if missing (default true)"}},required:["taskId","label"]}},{name:"vikunja_remove_label",description:"Detach a label from a task by title.",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},label:{type:"string",description:"Label title"}},required:["taskId","label"]}},{name:"vikunja_add_comment",description:"Add a comment to a task.",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},comment:{type:"string",description:"Comment body. Markdown is rendered (headings, **bold**, `code`, lists, - [ ] tasks, links, tables, > quotes)."}},required:["taskId","comment"]}},{name:"vikunja_assign",description:"Assign a user to a task by username.",input_schema:{type:"object",properties:{taskId:{type:"string",description:"Numeric task id"},username:{type:"string",description:"Vikunja username"}},required:["taskId","username"]}}]};export{Yt as clearVikunjaCredsCache,Zt as vikunjaSkill,ct as vkPlainText,ot as vkPriority,K as vkRichHtml};
@@ -0,0 +1,101 @@
1
+ ---
2
+ sidebar_position: 11
3
+ title: Catalog metadata
4
+ ---
5
+
6
+ # Catalog metadata
7
+
8
+ Everything here reaches the **card**. Most of it is presentation, and most of it
9
+ is deliberately excluded from the update signal, so improving your copy never
10
+ pops an "Update available" badge on somebody's deployed agent for nothing.
11
+
12
+ ## Identity and presentation
13
+
14
+ | Name | Type | Required | Allowed values | Default | Update behavior |
15
+ |---|---|---|---|---|---|
16
+ | `slug` | string | **Yes** | the agent's identity — the folder name, the deploy route, the row of everybody who already installed it | — | **Never change it** |
17
+ | `tagline` | string | No | one clamped line — on the detail card this *is* the header | `''` | Card only |
18
+ | `tags` | string[] | No | catalog sections; the card is grouped by the first one in section order | `[]` | Card only |
19
+ | `browseRank` | number | No | lower sorts first; no rank sorts last | none | Card only |
20
+ | `capabilities` | string[] | No | around five bullets, each one thing, each starting with a verb | `[]` | Card only |
21
+ | `conversationStarters` | string[] | No | example prompts — the first five appear as "Try asking" on the detail card | `[]` | Card only |
22
+ | `icon` | string | No | `./icon.png` beside your graph, or an `https://` URL | the Zibby mark | Card only |
23
+ | `iconPrompt` / `iconBackground` | string | No | inputs to the offline icon generator | none | Card only |
24
+ | `releaseNotes` | string | No | markdown — see below | `''` | Card only |
25
+ | `defaultSlug` | string | No | pre-fills the "Deploy as" field | the slug | Seeded once |
26
+
27
+ :::tip Tagline vs description
28
+ The detail card reads **tagline first**; the browse cards and the featured hero
29
+ read **description first**. So keep the tagline to a few words and let the
30
+ description be two real sentences. They are not interchangeable.
31
+ :::
32
+
33
+ ## Behaviour and availability
34
+
35
+ | Name | Type | Required | Allowed values | Default | Update behavior |
36
+ |---|---|---|---|---|---|
37
+ | `maxRuntimeMinutes` | number | No | clamped to the platform window; the default is 25 and the ceiling 60 | none | **Seeded once** — a number you set on the Settings tab survives every Update |
38
+ | `selfHostOnly` | boolean | No | `true` when the agent needs a service container | `false` | Re-applied |
39
+ | `hidden` | boolean | No | `true` keeps the card out of browse while remaining installable by slug | `false` | Re-applied |
40
+ | `kind` | string | No | `fleet`, `app` — see [Composition](./composition.md) | none | Re-applied |
41
+ | `deps` | object | No | extra runtime dependencies the agent needs beyond `@zibby/core` | `{}` | **Frozen at deploy** |
42
+
43
+ ### `maxRuntimeMinutes` — measure it, don't guess
44
+
45
+ Declare it when you actually know your agent's cost. One shipped agent measured
46
+ a first pass at 24 minutes 53 seconds against a 25-minute default, which made
47
+ its repair pass unreachable on every fresh install — so it declares 60. Another
48
+ declares 30 because a cap that is too *low* gets the container killed between
49
+ two steps and strands work.
50
+
51
+ A cap that is too low is worse than one that is too high.
52
+
53
+ ### `selfHostOnly` means exactly one thing
54
+
55
+ **"This agent needs a service container."** It does not mean "absent from the
56
+ cloud": the card still ships to the cloud catalog so its documentation stays
57
+ readable everywhere. Only the deploy is refused, and the refusal says why.
58
+
59
+ ## Release notes
60
+
61
+ `releaseNotes` is what the "Update available" dialog shows somebody who already
62
+ runs your agent. It is **mandatory for any user-visible change**, and it is
63
+ short.
64
+
65
+ 1. **This version only.** One line per change.
66
+ 2. **The outcome, not the mechanism.** What is different for them, in their
67
+ words — not what you refactored.
68
+ 3. **Do not accumulate history.** Drop the previous version's notes rather than
69
+ prepending to them.
70
+ 4. **Ten short lines at most.**
71
+
72
+ Good:
73
+
74
+ ```
75
+ Answering the team's question is now just replying, once, like a person.
76
+ • Reply in your own words — any wording, any language.
77
+ • Answer on one ticket of a plan and it counts for the whole plan.
78
+ • You no longer touch labels.
79
+ ```
80
+
81
+ Bad: a changelog going back to the initial release, or a paragraph about which
82
+ module was rewritten.
83
+
84
+ No notes at all gives the user *"No release notes provided by the author"*.
85
+
86
+ ## Gotchas
87
+
88
+ **`hidden` and `selfHostOnly` are different axes.** `hidden` is visibility;
89
+ `selfHostOnly` is a real constraint. A card can be one, the other, or both.
90
+
91
+ **A `kind` the platform does not recognise is dropped.** The section you meant
92
+ to appear in simply never appears — check the allowed values.
93
+
94
+ **Your `spec` block lives outside your template directory**, so a
95
+ metadata-only change moves no source file. The platform tracks it separately, so
96
+ your edit still reaches the card — see [Update behavior](./update-behavior.md).
97
+
98
+ ## See also
99
+
100
+ - [Update behavior](./update-behavior.md)
101
+ - [The declaration model](./index.md)
@@ -0,0 +1,97 @@
1
+ ---
2
+ sidebar_position: 5
3
+ title: Composition & fleets
4
+ ---
5
+
6
+ # Composition — `dispatchesWorkflow` and `kind`
7
+
8
+ ## What it is
9
+
10
+ An agent can run **another** catalog agent as a sub-graph. Deploying the parent
11
+ cascades: every declared member is installed as the parent's own child, bound to
12
+ it, and recorded on its row — so one Deploy gives you a whole team.
13
+
14
+ ## Syntax — three forms
15
+
16
+ **1. The declarative sub-graph node.** The engine records the member for you:
17
+
18
+ ```js
19
+ graph.addNode('build', {
20
+ workflow: 'frontend-specialist',
21
+ input: (state) => ({ ticket: state.ticket }),
22
+ output: (result) => ({ pr: result.prUrl }),
23
+ });
24
+ ```
25
+
26
+ **2. A single member on a custom node:**
27
+
28
+ ```js
29
+ export const poPlanNode = {
30
+ name: 'plan',
31
+ dispatchesWorkflow: 'product-owner',
32
+ execute: async (state, ctx) => { /* … */ },
33
+ };
34
+ ```
35
+
36
+ **3. A roster — one node fanning out over many members:**
37
+
38
+ ```js
39
+ export const dispatchNode = {
40
+ name: 'dispatch',
41
+ dispatchesWorkflow: ['ticket-triage', 'developer', 'frontend-specialist',
42
+ 'gitlab-code-review', 'generate-test-cases', 'product-owner'],
43
+ execute: async (state, ctx) => { /* dispatches whichever the manager chose */ },
44
+ };
45
+ ```
46
+
47
+ ## Properties
48
+
49
+ | Name | Where | Type | Required | Allowed values | Default | Update behavior |
50
+ |---|---|---|---|---|---|---|
51
+ | `dispatchesWorkflow` | node | string \| string[] | No | catalog slugs | absent | Re-applied — a new member is installed on the next deploy |
52
+ | `kind` | `spec` | string | No | `fleet`, `app` | none | `fleet` is card-only; `app` changes the deploy path |
53
+ | `composedOf` | — | — | **derived, never declared** | — | — | Re-applied |
54
+
55
+ `kind: 'fleet'` moves the card into the Fleet section of the catalog and lets it
56
+ say "installs a team of N" *before* you install it. It does **not** change how
57
+ the agent deploys.
58
+
59
+ `kind: 'app'` is different in kind: the card is a hosted application you open in
60
+ a browser, not an agent, and it deploys down a different path.
61
+
62
+ ## What deploy does
63
+
64
+ 1. Builds one ordered member list — sub-graph children first, then anything
65
+ named by [`requires`](./requires.md).
66
+ 2. Installs each member (recursively), in parallel.
67
+ 3. Records the bindings on the parent, so a dispatch at run time resolves to the
68
+ right instance.
69
+ 4. Records the member names on the parent's row for the Agents list.
70
+
71
+ If any member fails, **the whole deploy fails** and the rows this deploy created
72
+ are rolled back. You never end up with half a fleet.
73
+
74
+ ## Gotchas
75
+
76
+ **A member can be a string or an array — always handle both.** A roster node
77
+ declares many members on one node; reading only the string form installs one and
78
+ leaves every other dispatch failing at run time.
79
+
80
+ **An agent cannot dispatch an ancestor.** A cycle is refused at deploy.
81
+
82
+ **A dispatch node runs no model of its own** and is excluded from "apply this
83
+ model to every node".
84
+
85
+ **A parent collects its children's model keys.** If your child runs a different
86
+ vendor, the deploy asks for that key too — and marks it non-blocking, so a
87
+ branch you will not use never stops the install.
88
+
89
+ **A member that subscribes to events is suppressed while it is a member.**
90
+ Otherwise one pull request would fire both the fleet (which runs the member
91
+ in-process) and the standalone member — a double review. Deploy the member
92
+ on its own and it starts responding to events again.
93
+
94
+ ## See also
95
+
96
+ - [`requires`](./requires.md) — the other kind of member.
97
+ - [Deploy-time configuration](./deploy-time-config.md) — event subscriptions.
@@ -0,0 +1,228 @@
1
+ ---
2
+ sidebar_position: 9
3
+ title: Deploy-time configuration
4
+ ---
5
+
6
+ # Deploy-time configuration
7
+
8
+ Four declarations that shape what happens **when somebody installs your agent**:
9
+ what they are asked, what is seeded for them, and what starts a run.
10
+
11
+ | Block | Writes to | Asked at |
12
+ |---|---|---|
13
+ | [`selections`](#selections) | the agent's Env bag — **configuration** | deploy |
14
+ | [`deployInput`](#deployinput) | the agent's default trigger payload | deploy |
15
+ | [`defaultSchedule`](#defaultschedule) | the agent's schedules | deploy (once) |
16
+ | [`workflow.json` → `triggers`](#triggers) | the agent's trigger settings | every deploy |
17
+
18
+ ---
19
+
20
+ ## `selections`
21
+
22
+ ### What it is
23
+
24
+ A value the operator must **pick from a live list some integration owns** —
25
+ which board this agent works, which space it writes to — rather than dig an id
26
+ out of a URL and type it into an environment variable.
27
+
28
+ One declaration renders a dropdown of the real options the connected system
29
+ returns, and the answer lands in the agent's encrypted Env bag.
30
+
31
+ ### Syntax
32
+
33
+ ```js
34
+ spec: {
35
+ selections: [{
36
+ id: 'board',
37
+ label: 'Board',
38
+ from: 'board_tracker', // a provider, or a one-of group
39
+ providerKey: 'BOARD_TRACKER', // records WHICH provider was chosen
40
+ keys: {
41
+ vikunja: 'BOARD_VIKUNJA_PROJECT_ID',
42
+ jira: 'BOARD_JIRA_PROJECT_KEY',
43
+ },
44
+ required: true,
45
+ help: 'The board this agent polls for tickets marked ready.',
46
+ }],
47
+ }
48
+ ```
49
+
50
+ ### Properties
51
+
52
+ | Name | Type | Required | Allowed values | Default | Update behavior |
53
+ |---|---|---|---|---|---|
54
+ | `id` | string | **Yes** | lowercase, starts with a letter, ≤ 41 chars, unique in the array | — | Seeded once |
55
+ | `label` | string | **Yes** | non-empty — it is what the picker is called | — | Card only |
56
+ | `from` | string | **Yes** | a provider id, or a one-of group the platform already knows | — | Seeded once |
57
+ | `keys` | object | **Yes** | `{ provider: 'ENV_KEY' }` — every provider must belong to `from` | — | Seeded once |
58
+ | `providerKey` | string | **Yes** when the group offers more than one provider | a writable Env key | none | Seeded once |
59
+ | `required` | boolean | No | | `true` | Seeded once |
60
+ | `help` | string | No | | none | Card only |
61
+
62
+ ### Why `keys` is a map
63
+
64
+ The value written is a different *kind* of thing per provider: Vikunja addresses
65
+ a board by a numeric project id, Jira by a project key (`"PROJ"`). One
66
+ environment variable cannot express that, and collapsing them would leave the
67
+ reader guessing which system's id it holds.
68
+
69
+ ### Gotchas
70
+
71
+ **A one-of group with more than one option needs a `providerKey`.** A choice
72
+ that records nothing deploys an agent that cannot tell which system it was
73
+ pointed at — so the declaration is refused.
74
+
75
+ **A provider with no key in `keys` is simply not offered.** That is legitimate:
76
+ some systems are reached another way and have no list to show.
77
+
78
+ **It is seeded once.** A value you have already set survives every Update, and
79
+ the deploy tells you what it kept.
80
+
81
+ ---
82
+
83
+ ## `deployInput`
84
+
85
+ ### What it is
86
+
87
+ A field of your agent's input schema, annotated so the deploy asks for it once
88
+ and stores it as the agent's default trigger payload.
89
+
90
+ ### Syntax
91
+
92
+ ```js
93
+ // state.js
94
+ repoUrl: z.string().url()
95
+ .meta({ deployInput: {
96
+ required: true,
97
+ source: 'github-repo',
98
+ label: 'GitHub repo',
99
+ help: 'Pick the repository whose flaky tests this agent should fix.',
100
+ }}),
101
+
102
+ circleProjectSlug: z.string().optional()
103
+ .meta({ deployInput: {
104
+ source: 'circleci-project',
105
+ deriveFrom: 'repoUrl', // resolved automatically, never typed
106
+ label: 'CircleCI project',
107
+ }}),
108
+ ```
109
+
110
+ ### Properties
111
+
112
+ | Name | Type | Required | Allowed values | Default | Update behavior |
113
+ |---|---|---|---|---|---|
114
+ | `required` | boolean | No | gates the deploy in the dashboard, the CLI **and** over MCP | `false` | Seeded once |
115
+ | `source` | string | No | which control renders it, or which resolver fills it | none | Seeded once |
116
+ | `deriveFrom` | string | No | another field's name — auto-resolved, never user-entered, never required of the user | none | Seeded once |
117
+ | `label` / `help` | string | No | | the field name | Card only |
118
+
119
+ ### Gotchas
120
+
121
+ `deployInput.required` is the **deploy gate**, which is separate from whether
122
+ the field is optional in your schema.
123
+
124
+ Use `selections` instead when the value is agent *configuration* your code reads
125
+ from the environment — not part of a run's input.
126
+
127
+ ---
128
+
129
+ ## `defaultSchedule`
130
+
131
+ ### What it is
132
+
133
+ A cron-driven agent does nothing until something wakes it. `defaultSchedule` is
134
+ the floor: deploy installs it as the agent's ordinary schedule **when the agent
135
+ has no schedule at all**.
136
+
137
+ ### Syntax
138
+
139
+ ```js
140
+ spec: {
141
+ defaultSchedule: { cron: '*/15 * * * *', name: 'Board tick' },
142
+ }
143
+ ```
144
+
145
+ ### Properties
146
+
147
+ | Name | Type | Required | Allowed values | Default | Update behavior |
148
+ |---|---|---|---|---|---|
149
+ | `cron` | string | **Yes** | a **5-field** Unix cron (`m h dom mon dow`) | — | Seeded once |
150
+ | `timezone` | string | No | an IANA zone | `UTC` | Seeded once |
151
+ | `name` | string | No | | none | Seeded once |
152
+ | `input` | object | No | the trigger body | `{}` | Seeded once |
153
+
154
+ A one-shot date is refused — a template cannot know a date.
155
+
156
+ ### Gotchas
157
+
158
+ **Any existing entry blocks the seed.** One you created, the seeded one after
159
+ you edited it, a paused one (pausing is a disable, not a delete), even a
160
+ one-shot that already fired. So an Update can never resurrect a schedule you
161
+ paused, and never re-points one you changed.
162
+
163
+ **One entry, not an array.** The declaration is a floor, and a floor is one
164
+ cadence. An agent that needs several schedules has an operator who set them, and
165
+ seed-once means those are never touched.
166
+
167
+ ---
168
+
169
+ ## `triggers`
170
+
171
+ ### What it is
172
+
173
+ An optional `workflow.json` beside your graph, declaring how runs start. It is
174
+ re-applied on **every** deploy, including an Update.
175
+
176
+ ### Syntax
177
+
178
+ ```json
179
+ { "triggers": { "events": ["github.pr.opened", "github.pr.updated"] } }
180
+ ```
181
+
182
+ ```json
183
+ { "triggers": { "api": true } }
184
+ ```
185
+
186
+ ### Properties
187
+
188
+ | Name | Type | Required | Allowed values | Default | Update behavior |
189
+ |---|---|---|---|---|---|
190
+ | `api` | boolean | No | `true` ships the inbound webhook on by default | off (opt-in) | Re-applied |
191
+ | `events` | string[] | No | `github.pr.opened`, `github.pr.updated`, `github.pr.commented`, `github.pr.mentioned`, and the `gitlab.mr.*` equivalents | `[]` | Re-applied |
192
+
193
+ Inbound webhooks are routed **by subscription, not by agent name**: a delivery is
194
+ normalised to an event, the repository is resolved to a project, and every
195
+ subscribed agent gets its own independent run.
196
+
197
+ ### Gotchas
198
+
199
+ **A member of a fleet is suppressed from event routing while it is a member.**
200
+ Otherwise one pull request fires both the fleet — which runs the member itself —
201
+ and the standalone member, giving you two reviews. Deploy the member on its own
202
+ and it starts responding again.
203
+
204
+ ---
205
+
206
+ ## Sample trigger bodies
207
+
208
+ Two card-only fields that make the Trigger dialog usable:
209
+
210
+ ```js
211
+ spec: {
212
+ triggerExample: { ticketKey: 'PROJ-123' },
213
+ triggerExamples: [
214
+ { label: 'board tick', body: '{}' },
215
+ { label: 'one ticket', body: '{"ticketKey":"PROJ-123"}' },
216
+ { label: 'plan from a PRD', body: '{"prd":"# Feature\\n\\nAs a user I want …"}' },
217
+ ],
218
+ }
219
+ ```
220
+
221
+ `triggerExample` pre-fills the editable body. `triggerExamples` renders one
222
+ click-to-use reference per source below it. Both must be **valid JSON** — JSON
223
+ has no comments.
224
+
225
+ ## See also
226
+
227
+ - [Update behavior](./update-behavior.md)
228
+ - [Composition](./composition.md)