@zibby/skills 0.1.24 → 0.1.26

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.
@@ -0,0 +1 @@
1
+ var i=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion"}),n=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",connectPath:"/integrations?provider=lark"},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"}});export{i as INTEGRATIONS,n as INTEGRATION_REGISTRY};
package/dist/jira.js CHANGED
@@ -1,22 +1,22 @@
1
- import{createRequire as $}from"module";import{resolveIntegrationToken as q,clearTokenCache as R}from"@zibby/core/backend-client.js";var K=$(import.meta.url);function T(){if(process.env.MCP_JIRA_PATH)return process.env.MCP_JIRA_PATH;try{return K.resolve("@zibby/mcp-jira/index.js")}catch{return null}}var C=new Set(["paragraph","heading","bulletList","orderedList","listItem","blockquote","codeBlock","rule","table","tableRow","tableCell","tableHeader","mediaSingle","panel"]);function x(c,a){if(!a||!a.length)return c;let e=c;for(let t of a)t.type==="strong"?e=`**${e}**`:t.type==="em"?e=`_${e}_`:t.type==="code"?e=`\`${e}\``:t.type==="strike"?e=`~~${e}~~`:t.type==="link"&&t.attrs?.href&&(e=`[${e}](${t.attrs.href})`);return e}function k(c,a=0){if(!Array.isArray(c))return"";let e=[];for(let t of c){if(t.type==="text"){e.push(x(t.text||"",t.marks));continue}if(t.type==="hardBreak"){e.push(`
1
+ import{createRequire as T}from"module";import{resolveIntegrationToken as $,clearTokenCache as q}from"@zibby/core/backend-client.js";var O=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion"}),U=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",connectPath:"/integrations?provider=lark"},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"}});var K=T(import.meta.url);function C(){if(process.env.MCP_JIRA_PATH)return process.env.MCP_JIRA_PATH;try{return K.resolve("@zibby/mcp-jira/index.js")}catch{return null}}var P=new Set(["paragraph","heading","bulletList","orderedList","listItem","blockquote","codeBlock","rule","table","tableRow","tableCell","tableHeader","mediaSingle","panel"]);function x(c,a){if(!a||!a.length)return c;let e=c;for(let t of a)t.type==="strong"?e=`**${e}**`:t.type==="em"?e=`_${e}_`:t.type==="code"?e=`\`${e}\``:t.type==="strike"?e=`~~${e}~~`:t.type==="link"&&t.attrs?.href&&(e=`[${e}](${t.attrs.href})`);return e}function S(c,a=0){if(!Array.isArray(c))return"";let e=[];for(let t of c){if(t.type==="text"){e.push(x(t.text||"",t.marks));continue}if(t.type==="hardBreak"){e.push(`
2
2
  `);continue}if(t.type==="rule"){e.push(`
3
3
  ---
4
- `);continue}let i=t.content?k(t.content,a+1):"";if(t.type==="listItem")e.push(i);else if(t.type==="bulletList"){let r=(t.content||[]).map(o=>`- ${k(o.content||[],a+1).trim()}`);e.push(`
5
- ${r.join(`
4
+ `);continue}let r=t.content?S(t.content,a+1):"";if(t.type==="listItem")e.push(r);else if(t.type==="bulletList"){let i=(t.content||[]).map(o=>`- ${S(o.content||[],a+1).trim()}`);e.push(`
5
+ ${i.join(`
6
6
  `)}
7
- `)}else if(t.type==="orderedList"){let r=(t.content||[]).map((o,s)=>`${s+1}. ${k(o.content||[],a+1).trim()}`);e.push(`
8
- ${r.join(`
7
+ `)}else if(t.type==="orderedList"){let i=(t.content||[]).map((o,s)=>`${s+1}. ${S(o.content||[],a+1).trim()}`);e.push(`
8
+ ${i.join(`
9
9
  `)}
10
- `)}else if(t.type==="heading"){let r=t.attrs?.level||2;e.push(`
10
+ `)}else if(t.type==="heading"){let i=t.attrs?.level||2;e.push(`
11
11
 
12
- ${"#".repeat(r)} ${i.trim()}
12
+ ${"#".repeat(i)} ${r.trim()}
13
13
 
14
- `)}else C.has(t.type)?e.push(`
14
+ `)}else P.has(t.type)?e.push(`
15
15
 
16
- ${i}
17
- `):e.push(i)}return e.join("").replace(/\n{3,}/g,`
16
+ ${r}
17
+ `):e.push(r)}return e.join("").replace(/\n{3,}/g,`
18
18
 
19
- `)}function S(c){return String(c||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function I(c){return S(c).replace(/[a-z0-9]+/g,"")}function w(c,a){let e=S(c),t=S(a);if(!e||!t)return 0;if(e===t)return 1;if(e.length===1||t.length===1)return e===t?1:0;let i=u=>{let d=new Map;for(let y=0;y<u.length-1;y++){let g=u.slice(y,y+2);d.set(g,(d.get(g)||0)+1)}return d},r=i(e),o=i(t),s=0,n=0,p=0;for(let u of r.values())n+=u;for(let u of o.values())p+=u;for(let[u,d]of r.entries()){let y=o.get(u)||0;s+=Math.min(d,y)}return 2*s/Math.max(1,n+p)}function b(c){return String(c||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function P(c,a=[]){let e=Array.isArray(a)?a:[];if(e.length===0)return{requested:c||null,resolved:null,strategy:"none"};let t=e.filter(s=>!s.subtask),i=t.length>0?t:e,r=b(c);if(r){let s=i.find(u=>b(u.name)===r);if(s)return{requested:c,resolved:s,strategy:"exact"};let n={task:["task","\u4EFB\u52A1","\u4E8B\u9879","to do","todo"],story:["story","\u7528\u6237\u6545\u4E8B","\u9700\u6C42"],bug:["bug","\u7F3A\u9677","\u95EE\u9898"],improvement:["improvement","\u4F18\u5316","\u6539\u8FDB"],epic:["epic","\u53F2\u8BD7"]};for(let u of Object.values(n)){if(!u.some(y=>b(y)===r))continue;let d=i.find(y=>u.some(g=>b(g)===b(y.name)));if(d)return{requested:c,resolved:d,strategy:"alias"}}let p=i.map(u=>({t:u,score:w(c,u.name)})).sort((u,d)=>d.score-u.score);if(p[0]&&p[0].score>=.5)return{requested:c,resolved:p[0].t,strategy:"fuzzy"}}let o=["task","story","bug","improvement","epic"];for(let s of o){let n=i.find(p=>b(p.name)===s);if(n)return{requested:c||null,resolved:n,strategy:"default-preferred"}}return{requested:c||null,resolved:i[0],strategy:"default-first"}}async function N(c){let a=`projectKeys=${encodeURIComponent(c)}&expand=projects.issuetypes`,e=await f(`/rest/api/3/issue/createmeta?${a}`),t=Array.isArray(e?.projects)?e.projects:[],r=t.find(s=>String(s?.key||"").toUpperCase()===String(c||"").toUpperCase())||t[0]||null;return(Array.isArray(r?.issuetypes)?r.issuetypes:[]).map(s=>({id:s.id,name:s.name,subtask:!!s.subtask,description:s.description||null}))}async function A(c,a){if(!c)throw new Error("projectKey is required");let e="sprint is not EMPTY";a==="active"?e="sprint in openSprints()":a==="closed"?e="sprint in closedSprints()":a==="future"&&(e="sprint in futureSprints()");let t=`project = ${c} AND ${e} ORDER BY updated DESC`,i=`jql=${encodeURIComponent(t)}&maxResults=100&fields=customfield_10020`,r=await f(`/rest/api/3/search/jql?${i}`),o=new Map;for(let s of r.issues||[])for(let n of s.fields?.customfield_10020||[])n&&!o.has(n.id)&&o.set(n.id,{id:n.id,name:n.name,state:n.state,boardId:n.boardId||null,startDate:n.startDate||null,endDate:n.endDate||null,goal:n.goal||null});return[...o.values()].sort((s,n)=>{let p={active:0,future:1,closed:2},u=(p[s.state]??3)-(p[n.state]??3);return u!==0?u:String(n.startDate||"").localeCompare(String(s.startDate||""))})}function D(c,{sprintId:a,sprintName:e,target:t}={}){let i=Array.isArray(c)?c:[];if(!i.length)return{sprint:null,selectedBy:"none"};if(a!=null&&String(a).trim()!=="")return{sprint:i.find(s=>String(s.id)===String(a))||null,selectedBy:"id"};if(e&&String(e).trim()){let o=String(e).trim(),s=i.find(p=>String(p.name||"").toLowerCase()===o.toLowerCase());if(s)return{sprint:s,selectedBy:"name-exact"};let n=i.map(p=>({s:p,score:w(o,p.name||"")})).sort((p,u)=>u.score-p.score);return n[0]&&n[0].score>=.5?{sprint:n[0].s,selectedBy:"name-fuzzy"}:{sprint:null,selectedBy:"name-none"}}let r=String(t||"current").trim().toLowerCase();return r==="active"||r==="current"||r==="latest"?{sprint:i[0],selectedBy:r}:{sprint:i[0],selectedBy:"default"}}function L(c,a){let e=c?.fields?.customfield_10020;return Array.isArray(e)?e.some(t=>String(t?.id)===String(a)):!1}async function B({issueKey:c,projectKey:a,sprintId:e,attempts:t=3,delayMs:i=450}){let r=[];for(let o=0;o<t;o++){try{let s=`project = ${a} AND key = ${c} AND sprint = ${e}`,n=`jql=${encodeURIComponent(s)}&maxResults=1&fields=key,status`,p=await f(`/rest/api/3/search/jql?${n}`);if(Number(p?.total||0)>0)return r.push({attempt:o+1,jql:!0,issueField:null}),{ok:!0,method:"jql",traces:r};let d=await f(`/rest/api/3/issue/${c}?fields=customfield_10020,status`),y=L(d,e);if(r.push({attempt:o+1,jql:!1,issueField:y}),y)return{ok:!0,method:"issue_field",traces:r}}catch(s){r.push({attempt:o+1,error:String(s?.message||s)})}o<t-1&&await new Promise(s=>setTimeout(s,i))}return{ok:!1,method:"none",traces:r}}async function O({issueKey:c,projectKey:a,sprintId:e,sprintName:t,target:i}){if(!c)return{ok:!1,error:"issueKey is required"};let r=a;if(!r&&(r=(await f(`/rest/api/3/issue/${c}?fields=project`))?.fields?.project?.key||null,!r))return{ok:!1,error:`Could not resolve project for ${c}`};let o=await A(r,"active");if(!o.length)return{ok:!1,error:`No assignable active sprint found for project ${r}`};let{sprint:s,selectedBy:n}=D(o,{sprintId:e,sprintName:t,target:i});if(!s)return{ok:!1,error:`No matching sprint found in ${r}`,requested:{sprintId:e??null,sprintName:t??null,target:i??"current"},availableSprints:o.map(d=>({id:d.id,name:d.name,state:d.state}))};await f(`/rest/api/3/issue/${c}`,{method:"PUT",body:{fields:{customfield_10020:Number(s.id)}}});let p=await B({issueKey:c,projectKey:r,sprintId:s.id}),u=p.ok;return{ok:u,issueKey:c,projectKey:r,sprintId:s.id,sprintName:s.name,selectedBy:n,verifiedBy:p.method,verified:u,verificationTrace:p.traces,warning:u?null:`Sprint assignment attempted but verification did not find ${c} in sprint ${s.id}`}}async function f(c,a={}){let e=async()=>{let{token:t,cloudId:i}=await q("jira");if(typeof t!="string"||!t)throw new Error(`Invalid jira token type: ${typeof t}`);if(!i)throw new Error("Invalid jira cloudId: missing");let r=`https://api.atlassian.com/ex/jira/${i}${c}`,o=await fetch(r,{method:a.method||"GET",headers:{Authorization:`Bearer ${t}`,Accept:"application/json",...a.body?{"Content-Type":"application/json"}:{},...a.headers},body:a.body?JSON.stringify(a.body):void 0});if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`Jira API ${o.status}: ${n.slice(0,300)}`)}let s=await o.text().catch(()=>"");if(!s||!s.trim())return{};try{return JSON.parse(s)}catch{return{raw:s}}};try{return await e()}catch(t){let i=String(t?.message||t||"").toLowerCase();if(!(i.includes("token")||i.includes("401")||i.includes("403")||i.includes("substring")))throw t;return R("jira"),e()}}var M={id:"jira",serverName:"jira",allowedTools:["mcp__jira__*"],envKeys:["ATLASSIAN_ACCESS_TOKEN","ATLASSIAN_CLOUD_ID"],description:"Zibby Jira MCP Server (OAuth Bearer)",promptFragment:`## Jira (connected)
19
+ `)}function k(c){return String(c||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function w(c){return k(c).replace(/[a-z0-9]+/g,"")}function I(c,a){let e=k(c),t=k(a);if(!e||!t)return 0;if(e===t)return 1;if(e.length===1||t.length===1)return e===t?1:0;let r=u=>{let d=new Map;for(let m=0;m<u.length-1;m++){let g=u.slice(m,m+2);d.set(g,(d.get(g)||0)+1)}return d},i=r(e),o=r(t),s=0,n=0,p=0;for(let u of i.values())n+=u;for(let u of o.values())p+=u;for(let[u,d]of i.entries()){let m=o.get(u)||0;s+=Math.min(d,m)}return 2*s/Math.max(1,n+p)}function b(c){return String(c||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function L(c,a=[]){let e=Array.isArray(a)?a:[];if(e.length===0)return{requested:c||null,resolved:null,strategy:"none"};let t=e.filter(s=>!s.subtask),r=t.length>0?t:e,i=b(c);if(i){let s=r.find(u=>b(u.name)===i);if(s)return{requested:c,resolved:s,strategy:"exact"};let n={task:["task","\u4EFB\u52A1","\u4E8B\u9879","to do","todo"],story:["story","\u7528\u6237\u6545\u4E8B","\u9700\u6C42"],bug:["bug","\u7F3A\u9677","\u95EE\u9898"],improvement:["improvement","\u4F18\u5316","\u6539\u8FDB"],epic:["epic","\u53F2\u8BD7"]};for(let u of Object.values(n)){if(!u.some(m=>b(m)===i))continue;let d=r.find(m=>u.some(g=>b(g)===b(m.name)));if(d)return{requested:c,resolved:d,strategy:"alias"}}let p=r.map(u=>({t:u,score:I(c,u.name)})).sort((u,d)=>d.score-u.score);if(p[0]&&p[0].score>=.5)return{requested:c,resolved:p[0].t,strategy:"fuzzy"}}let o=["task","story","bug","improvement","epic"];for(let s of o){let n=r.find(p=>b(p.name)===s);if(n)return{requested:c||null,resolved:n,strategy:"default-preferred"}}return{requested:c||null,resolved:r[0],strategy:"default-first"}}async function A(c){let a=`projectKeys=${encodeURIComponent(c)}&expand=projects.issuetypes`,e=await f(`/rest/api/3/issue/createmeta?${a}`),t=Array.isArray(e?.projects)?e.projects:[],i=t.find(s=>String(s?.key||"").toUpperCase()===String(c||"").toUpperCase())||t[0]||null;return(Array.isArray(i?.issuetypes)?i.issuetypes:[]).map(s=>({id:s.id,name:s.name,subtask:!!s.subtask,description:s.description||null}))}async function R(c,a){if(!c)throw new Error("projectKey is required");let e="sprint is not EMPTY";a==="active"?e="sprint in openSprints()":a==="closed"?e="sprint in closedSprints()":a==="future"&&(e="sprint in futureSprints()");let t=`project = ${c} AND ${e} ORDER BY updated DESC`,r=`jql=${encodeURIComponent(t)}&maxResults=100&fields=customfield_10020`,i=await f(`/rest/api/3/search/jql?${r}`),o=new Map;for(let s of i.issues||[])for(let n of s.fields?.customfield_10020||[])n&&!o.has(n.id)&&o.set(n.id,{id:n.id,name:n.name,state:n.state,boardId:n.boardId||null,startDate:n.startDate||null,endDate:n.endDate||null,goal:n.goal||null});return[...o.values()].sort((s,n)=>{let p={active:0,future:1,closed:2},u=(p[s.state]??3)-(p[n.state]??3);return u!==0?u:String(n.startDate||"").localeCompare(String(s.startDate||""))})}function D(c,{sprintId:a,sprintName:e,target:t}={}){let r=Array.isArray(c)?c:[];if(!r.length)return{sprint:null,selectedBy:"none"};if(a!=null&&String(a).trim()!=="")return{sprint:r.find(s=>String(s.id)===String(a))||null,selectedBy:"id"};if(e&&String(e).trim()){let o=String(e).trim(),s=r.find(p=>String(p.name||"").toLowerCase()===o.toLowerCase());if(s)return{sprint:s,selectedBy:"name-exact"};let n=r.map(p=>({s:p,score:I(o,p.name||"")})).sort((p,u)=>u.score-p.score);return n[0]&&n[0].score>=.5?{sprint:n[0].s,selectedBy:"name-fuzzy"}:{sprint:null,selectedBy:"name-none"}}let i=String(t||"current").trim().toLowerCase();return i==="active"||i==="current"||i==="latest"?{sprint:r[0],selectedBy:i}:{sprint:r[0],selectedBy:"default"}}function E(c,a){let e=c?.fields?.customfield_10020;return Array.isArray(e)?e.some(t=>String(t?.id)===String(a)):!1}async function B({issueKey:c,projectKey:a,sprintId:e,attempts:t=3,delayMs:r=450}){let i=[];for(let o=0;o<t;o++){try{let s=`project = ${a} AND key = ${c} AND sprint = ${e}`,n=`jql=${encodeURIComponent(s)}&maxResults=1&fields=key,status`,p=await f(`/rest/api/3/search/jql?${n}`);if(Number(p?.total||0)>0)return i.push({attempt:o+1,jql:!0,issueField:null}),{ok:!0,method:"jql",traces:i};let d=await f(`/rest/api/3/issue/${c}?fields=customfield_10020,status`),m=E(d,e);if(i.push({attempt:o+1,jql:!1,issueField:m}),m)return{ok:!0,method:"issue_field",traces:i}}catch(s){i.push({attempt:o+1,error:String(s?.message||s)})}o<t-1&&await new Promise(s=>setTimeout(s,r))}return{ok:!1,method:"none",traces:i}}async function N({issueKey:c,projectKey:a,sprintId:e,sprintName:t,target:r}){if(!c)return{ok:!1,error:"issueKey is required"};let i=a;if(!i&&(i=(await f(`/rest/api/3/issue/${c}?fields=project`))?.fields?.project?.key||null,!i))return{ok:!1,error:`Could not resolve project for ${c}`};let o=await R(i,"active");if(!o.length)return{ok:!1,error:`No assignable active sprint found for project ${i}`};let{sprint:s,selectedBy:n}=D(o,{sprintId:e,sprintName:t,target:r});if(!s)return{ok:!1,error:`No matching sprint found in ${i}`,requested:{sprintId:e??null,sprintName:t??null,target:r??"current"},availableSprints:o.map(d=>({id:d.id,name:d.name,state:d.state}))};await f(`/rest/api/3/issue/${c}`,{method:"PUT",body:{fields:{customfield_10020:Number(s.id)}}});let p=await B({issueKey:c,projectKey:i,sprintId:s.id}),u=p.ok;return{ok:u,issueKey:c,projectKey:i,sprintId:s.id,sprintName:s.name,selectedBy:n,verifiedBy:p.method,verified:u,verificationTrace:p.traces,warning:u?null:`Sprint assignment attempted but verification did not find ${c} in sprint ${s.id}`}}async function f(c,a={}){let e=async()=>{let{token:t,cloudId:r}=await $("jira");if(typeof t!="string"||!t)throw new Error(`Invalid jira token type: ${typeof t}`);if(!r)throw new Error("Invalid jira cloudId: missing");let i=`https://api.atlassian.com/ex/jira/${r}${c}`,o=await fetch(i,{method:a.method||"GET",headers:{Authorization:`Bearer ${t}`,Accept:"application/json",...a.body?{"Content-Type":"application/json"}:{},...a.headers},body:a.body?JSON.stringify(a.body):void 0});if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`Jira API ${o.status}: ${n.slice(0,300)}`)}let s=await o.text().catch(()=>"");if(!s||!s.trim())return{};try{return JSON.parse(s)}catch{return{raw:s}}};try{return await e()}catch(t){let r=String(t?.message||t||"").toLowerCase();if(!(r.includes("token")||r.includes("401")||r.includes("403")||r.includes("substring")))throw t;return q("jira"),e()}}var F={id:"jira",serverName:"jira",allowedTools:["mcp__jira__*"],requiresIntegration:O.JIRA,envKeys:["ATLASSIAN_ACCESS_TOKEN","ATLASSIAN_CLOUD_ID"],description:"Zibby Jira MCP Server (OAuth Bearer)",promptFragment:`## Jira (connected)
20
20
  You have direct access to the user's Jira. Use these tools proactively:
21
21
 
22
22
  ### Issue tools
@@ -66,4 +66,4 @@ When user asks to move/transition ticket status:
66
66
  3. Pick the correct transition from returned list (match by "to" status name, not guesswork), then call jira_transition_issue with transitionId.
67
67
  4. Call jira_get_issue(issueKey) to verify final status before claiming success.
68
68
  5. If target wording differs (e.g. \u5DF2\u7ECF\u9A8C\u6536 vs \u5DF2\u9A8C\u6536), try toStatus first; only ask user to confirm when no reasonable match exists.
69
- 6. IMPORTANT: When target is clear, complete transition + verification in SAME turn. Do NOT stop after listing options.`,resolve(){let c=T();if(!c)return null;let a={};for(let e of this.envKeys)process.env[e]&&(a[e]=process.env[e]);return process.env.ATLASSIAN_INSTANCE_URL&&(a.ATLASSIAN_INSTANCE_URL=process.env.ATLASSIAN_INSTANCE_URL),{command:"node",args:[c],env:a,description:this.description}},async handleToolCall(c,a){try{switch(c){case"jira_list_projects":{let e=await f("/rest/api/3/project"),t=(Array.isArray(e)?e:[]).map(i=>({id:i.id,key:i.key,name:i.name,style:i.style}));return JSON.stringify({count:t.length,projects:t})}case"jira_list_statuses":{let{projectKey:e}=a||{};if(e){let r=await f(`/rest/api/3/project/${encodeURIComponent(e)}/statuses`),o=Array.isArray(r)?r:[],s=new Map;for(let p of o)for(let u of p.statuses||[])u?.id&&(s.has(u.id)||s.set(u.id,{id:u.id,name:u.name,category:u.statusCategory?.name||null}));let n=[...s.values()].sort((p,u)=>String(p.name).localeCompare(String(u.name)));return JSON.stringify({scope:"project",projectKey:e,count:n.length,statuses:n})}let t=await f("/rest/api/3/status"),i=(Array.isArray(t)?t:[]).map(r=>({id:r.id,name:r.name,category:r.statusCategory?.name||null})).sort((r,o)=>String(r.name).localeCompare(String(o.name)));return JSON.stringify({scope:"global",count:i.length,statuses:i})}case"jira_list_issue_types":{let{projectKey:e}=a||{};if(!e)return JSON.stringify({error:"projectKey is required"});let t=await N(e);return JSON.stringify({projectKey:e,count:t.length,issueTypes:t})}case"jira_search":{let e=a.jql||"",t=a.maxResults||20;e.replace(/\s*ORDER\s+BY\s+.*/i,"").trim()||(e=`created >= -365d ${e}`.trim());let r=`jql=${encodeURIComponent(e)}&maxResults=${t}&fields=summary,status,assignee,priority,updated,issuetype,project`,s=((await f(`/rest/api/3/search/jql?${r}`)).issues||[]).map(n=>({key:n.key,project:n.fields?.project?.key,summary:n.fields?.summary,status:n.fields?.status?.name,assignee:n.fields?.assignee?.displayName||"Unassigned",priority:n.fields?.priority?.name,type:n.fields?.issuetype?.name}));return JSON.stringify({count:s.length,issues:s})}case"jira_get_issue":{let e=a.issueKey;if(!e)return JSON.stringify({error:"issueKey is required"});let t=await f(`/rest/api/3/issue/${e}`);return JSON.stringify({key:t.key,project:t.fields?.project?.key,summary:t.fields?.summary,description:t.fields?.description,status:t.fields?.status?.name,assignee:t.fields?.assignee?.displayName||"Unassigned",priority:t.fields?.priority?.name,type:t.fields?.issuetype?.name,labels:t.fields?.labels,created:t.fields?.created,updated:t.fields?.updated})}case"jira_create_issue":{let{projectKey:e,summary:t,issueType:i,description:r,priority:o,labels:s,assigneeId:n,moveToSprint:p,moveToActiveSprint:u,sprintId:d,sprintName:y,target:g}=a;if(!e||!t)return JSON.stringify({error:"projectKey and summary are required"});let l={requested:i||null,resolved:null,strategy:"none"},j=[];try{j=await N(e),l=P(i,j)}catch{}let m={project:{key:e},summary:t,issuetype:l?.resolved?.id?{id:l.resolved.id}:{name:i||"Task"}};r&&(m.description={type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:r}]}]}),o&&(m.priority={name:o}),s?.length&&(m.labels=s),n&&(m.assignee={id:n});let _=await f("/rest/api/3/issue",{method:"POST",body:{fields:m}}),h={ok:!0,key:_.key,id:_.id,self:_.self};return l?.resolved&&(h.issueType=l.resolved.name,h.issueTypeResolution=l.strategy,l.strategy!=="exact"&&l.requested&&b(l.requested)!==b(l.resolved.name)&&(h.issueTypeWarning=`Requested "${l.requested}" is not available in ${e}; used "${l.resolved.name}" instead.`)),j.length>0&&(h.availableIssueTypes=j.map(v=>v.name)),(p||u)&&(h.sprintMove=await O({issueKey:_.key,projectKey:e,sprintId:d,sprintName:y,target:g})),JSON.stringify(h)}case"jira_list_sprints":{let{projectKey:e,state:t}=a,i=await A(e,t);return JSON.stringify({count:i.length,sprints:i})}case"jira_move_to_active_sprint":{let{issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o}=a||{},s=await O({issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o||"current"});return JSON.stringify(s)}case"jira_move_issue_to_sprint":{let{issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o}=a||{},s=await O({issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o});return JSON.stringify(s)}case"jira_get_sprint_issues":{let{sprintName:e,sprintId:t,projectKey:i,status:r,maxResults:o}=a;if(!e&&!t)return JSON.stringify({error:"sprintName or sprintId is required"});let s=o||50,n=t?`sprint = ${t}`:`sprint = "${e}"`,p=i?`project = ${i} AND `:"",u=r?` AND status = "${r}"`:"",d=`${p}${n}${u} ORDER BY status ASC, priority DESC`,y=`jql=${encodeURIComponent(d)}&maxResults=${s}&fields=summary,status,assignee,priority,issuetype,project`,g=await f(`/rest/api/3/search/jql?${y}`),l=(g.issues||[]).map(m=>({key:m.key,project:m.fields?.project?.key,summary:m.fields?.summary,status:m.fields?.status?.name,assignee:m.fields?.assignee?.displayName||"Unassigned",priority:m.fields?.priority?.name,type:m.fields?.issuetype?.name})),j={};for(let m of l)j[m.status]=(j[m.status]||0)+1;return JSON.stringify({count:l.length,total:g.total||l.length,statusCounts:j,issues:l})}case"jira_get_comments":{let{issueKey:e,maxResults:t}=a;if(!e)return JSON.stringify({error:"issueKey is required"});let r=await f(`/rest/api/3/issue/${e}/comment?maxResults=${t||50}&orderBy=-created`),o=(r.comments||[]).map(s=>{let n="";return s.body?.content&&(n=k(s.body.content)),{id:s.id,author:s.author?.displayName||"Unknown",body:n,created:s.created,updated:s.updated}});return JSON.stringify({count:o.length,total:r.total||o.length,comments:o})}case"jira_add_comment":{let{issueKey:e,body:t}=a;return!e||!t?JSON.stringify({error:"issueKey and body are required"}):(await f(`/rest/api/3/issue/${e}/comment`,{method:"POST",body:{body:{type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:t}]}]}}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_edit_issue":{let{issueKey:e,fields:t}=a;return!e||!t?JSON.stringify({error:"issueKey and fields are required"}):(await f(`/rest/api/3/issue/${e}`,{method:"PUT",body:{fields:t}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_transition_issue":{let{issueKey:e,transitionId:t,toStatus:i,statusName:r,status:o}=a;if(!e)return JSON.stringify({error:"issueKey is required"});let s=String(i||r||o||"").trim();if(!t&&!s){let d=((await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[]).map(y=>({id:y.id,name:y.name,to:y.to?.name}));return JSON.stringify({ok:!1,error:"transitionId or toStatus is required",issueKey:e,availableTransitions:d})}let n=t;if(!n){let d=(await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[],y=S(s),g=d.find(l=>S(l?.name||"")===y||S(l?.to?.name||"")===y);if(!g){let l=I(s);l.length>=2&&(g=d.find(j=>{let m=I(j?.name||""),_=I(j?.to?.name||""),h=m.length>=2&&(m.includes(l)||l.includes(m)),v=_.length>=2&&(_.includes(l)||l.includes(_));return h||v}))}if(!g){let l=d.map(h=>{let v=w(s,h?.name||""),J=w(s,h?.to?.name||"");return{t:h,score:Math.max(v,J)}}).sort((h,v)=>v.score-h.score),j=l[0],m=l[1];j&&j.score>=.45&&(!m||j.score-m.score>=.12)&&(g=j.t)}if(!g?.id)return JSON.stringify({ok:!1,error:`No transition matches target status: "${s}"`,issueKey:e,availableTransitions:d.map(l=>({id:l.id,name:l.name,to:l.to?.name}))});n=g.id}await f(`/rest/api/3/issue/${e}/transitions`,{method:"POST",body:{transition:{id:n}}});let p=await f(`/rest/api/3/issue/${e}?fields=status`);return JSON.stringify({ok:!0,issueKey:e,transitionId:n,statusAfter:p?.fields?.status?.name||null})}default:return JSON.stringify({error:`Unknown tool: ${c}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"jira_list_projects",description:"List all Jira projects accessible to the user",input_schema:{type:"object",properties:{}}},{name:"jira_list_statuses",description:"List Jira statuses. Use projectKey to get statuses applicable in that project workflow.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Optional project key (e.g. PROJ). If omitted, returns global status catalog."}}}},{name:"jira_list_issue_types",description:"List issue types allowed for issue creation in the given project.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"}},required:["projectKey"]}},{name:"jira_search",description:"Search Jira issues using JQL",input_schema:{type:"object",properties:{jql:{type:"string",description:'JQL query string, e.g. "project = PROJ AND status = Open"'},maxResults:{type:"number",description:"Max results to return (default 20)"}},required:["jql"]}},{name:"jira_get_issue",description:"Get details of a specific Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"}},required:["issueKey"]}},{name:"jira_create_issue",description:"Create a new Jira issue",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},summary:{type:"string",description:"Issue title/summary"},issueType:{type:"string",description:"Issue type (default: Task). Common: Task, Bug, Story, Epic"},description:{type:"string",description:"Issue description (plain text)"},priority:{type:"string",description:"Priority name, e.g. High, Medium, Low"},labels:{type:"array",items:{type:"string"},description:"Array of label strings"},assigneeId:{type:"string",description:"Atlassian account ID to assign to"},moveToSprint:{type:"boolean",description:"If true, move created issue to a sprint and verify."},moveToActiveSprint:{type:"boolean",description:"Backward-compatible alias for moveToSprint."},sprintId:{type:"number",description:"Optional sprint id for placement."},sprintName:{type:"string",description:"Optional sprint name for placement."},target:{type:"string",description:"Placement target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["projectKey","summary"]}},{name:"jira_list_sprints",description:"List sprints for a Jira project (returns sprint names, IDs, states, dates)",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},state:{type:"string",description:"Filter: active, closed, future. Omit for all."}},required:["projectKey"]}},{name:"jira_get_sprint_issues",description:"Get all issues in a sprint, optionally filtered by status column name",input_schema:{type:"object",properties:{sprintName:{type:"string",description:"Sprint name (from jira_list_sprints). Use this OR sprintId."},sprintId:{type:"number",description:"Sprint ID (from jira_list_sprints). Use this OR sprintName."},projectKey:{type:"string",description:"Project key to scope the search (optional)"},status:{type:"string",description:'Filter by status name (e.g. "\u8FDB\u884C\u4E2D", "\u6D4B\u8BD5", "Done")'},maxResults:{type:"number",description:"Max issues to return (default 50)"}}}},{name:"jira_move_to_active_sprint",description:"Backward-compatible alias: move issue to sprint target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_move_issue_to_sprint",description:"Move an issue to a sprint by id/name/target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_get_comments",description:"Get comments on a Jira issue (newest first)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},maxResults:{type:"number",description:"Max comments to return (default 50)"}},required:["issueKey"]}},{name:"jira_add_comment",description:"Add a comment to a Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},body:{type:"string",description:"Comment text (plain text)"}},required:["issueKey","body"]}},{name:"jira_edit_issue",description:"Update fields on a Jira issue (summary, story points, labels, priority)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},fields:{type:"object",description:"Object of field names to values",additionalProperties:!0}},required:["issueKey","fields"]}},{name:"jira_transition_issue",description:"Move a Jira issue to a different status. Always pass toStatus when user gave a target; only pass issueKey alone when you explicitly need to list transitions.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},transitionId:{type:"string",description:"Transition ID to perform (optional if toStatus is provided)"},toStatus:{type:"string",description:'Target status/column name (e.g. "\u5DF2\u7ECF\u9A8C\u6536", "Done", "In Progress"). If provided, tool resolves matching transition automatically.'}},required:["issueKey"]}}]};export{M as jiraSkill};
69
+ 6. IMPORTANT: When target is clear, complete transition + verification in SAME turn. Do NOT stop after listing options.`,resolve(){let c=C();if(!c)return null;let a={};for(let e of this.envKeys)process.env[e]&&(a[e]=process.env[e]);return process.env.ATLASSIAN_INSTANCE_URL&&(a.ATLASSIAN_INSTANCE_URL=process.env.ATLASSIAN_INSTANCE_URL),{command:"node",args:[c],env:a,description:this.description}},async handleToolCall(c,a){try{switch(c){case"jira_list_projects":{let e=await f("/rest/api/3/project"),t=(Array.isArray(e)?e:[]).map(r=>({id:r.id,key:r.key,name:r.name,style:r.style}));return JSON.stringify({count:t.length,projects:t})}case"jira_list_statuses":{let{projectKey:e}=a||{};if(e){let i=await f(`/rest/api/3/project/${encodeURIComponent(e)}/statuses`),o=Array.isArray(i)?i:[],s=new Map;for(let p of o)for(let u of p.statuses||[])u?.id&&(s.has(u.id)||s.set(u.id,{id:u.id,name:u.name,category:u.statusCategory?.name||null}));let n=[...s.values()].sort((p,u)=>String(p.name).localeCompare(String(u.name)));return JSON.stringify({scope:"project",projectKey:e,count:n.length,statuses:n})}let t=await f("/rest/api/3/status"),r=(Array.isArray(t)?t:[]).map(i=>({id:i.id,name:i.name,category:i.statusCategory?.name||null})).sort((i,o)=>String(i.name).localeCompare(String(o.name)));return JSON.stringify({scope:"global",count:r.length,statuses:r})}case"jira_list_issue_types":{let{projectKey:e}=a||{};if(!e)return JSON.stringify({error:"projectKey is required"});let t=await A(e);return JSON.stringify({projectKey:e,count:t.length,issueTypes:t})}case"jira_search":{let e=a.jql||"",t=a.maxResults||20;e.replace(/\s*ORDER\s+BY\s+.*/i,"").trim()||(e=`created >= -365d ${e}`.trim());let i=`jql=${encodeURIComponent(e)}&maxResults=${t}&fields=summary,status,assignee,priority,updated,issuetype,project`,s=((await f(`/rest/api/3/search/jql?${i}`)).issues||[]).map(n=>({key:n.key,project:n.fields?.project?.key,summary:n.fields?.summary,status:n.fields?.status?.name,assignee:n.fields?.assignee?.displayName||"Unassigned",priority:n.fields?.priority?.name,type:n.fields?.issuetype?.name}));return JSON.stringify({count:s.length,issues:s})}case"jira_get_issue":{let e=a.issueKey;if(!e)return JSON.stringify({error:"issueKey is required"});let t=await f(`/rest/api/3/issue/${e}`);return JSON.stringify({key:t.key,project:t.fields?.project?.key,summary:t.fields?.summary,description:t.fields?.description,status:t.fields?.status?.name,assignee:t.fields?.assignee?.displayName||"Unassigned",priority:t.fields?.priority?.name,type:t.fields?.issuetype?.name,labels:t.fields?.labels,created:t.fields?.created,updated:t.fields?.updated})}case"jira_create_issue":{let{projectKey:e,summary:t,issueType:r,description:i,priority:o,labels:s,assigneeId:n,moveToSprint:p,moveToActiveSprint:u,sprintId:d,sprintName:m,target:g}=a;if(!e||!t)return JSON.stringify({error:"projectKey and summary are required"});let l={requested:r||null,resolved:null,strategy:"none"},h=[];try{h=await A(e),l=L(r,h)}catch{}let y={project:{key:e},summary:t,issuetype:l?.resolved?.id?{id:l.resolved.id}:{name:r||"Task"}};i&&(y.description={type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:i}]}]}),o&&(y.priority={name:o}),s?.length&&(y.labels=s),n&&(y.assignee={id:n});let _=await f("/rest/api/3/issue",{method:"POST",body:{fields:y}}),j={ok:!0,key:_.key,id:_.id,self:_.self};return l?.resolved&&(j.issueType=l.resolved.name,j.issueTypeResolution=l.strategy,l.strategy!=="exact"&&l.requested&&b(l.requested)!==b(l.resolved.name)&&(j.issueTypeWarning=`Requested "${l.requested}" is not available in ${e}; used "${l.resolved.name}" instead.`)),h.length>0&&(j.availableIssueTypes=h.map(v=>v.name)),(p||u)&&(j.sprintMove=await N({issueKey:_.key,projectKey:e,sprintId:d,sprintName:m,target:g})),JSON.stringify(j)}case"jira_list_sprints":{let{projectKey:e,state:t}=a,r=await R(e,t);return JSON.stringify({count:r.length,sprints:r})}case"jira_move_to_active_sprint":{let{issueKey:e,projectKey:t,sprintId:r,sprintName:i,target:o}=a||{},s=await N({issueKey:e,projectKey:t,sprintId:r,sprintName:i,target:o||"current"});return JSON.stringify(s)}case"jira_move_issue_to_sprint":{let{issueKey:e,projectKey:t,sprintId:r,sprintName:i,target:o}=a||{},s=await N({issueKey:e,projectKey:t,sprintId:r,sprintName:i,target:o});return JSON.stringify(s)}case"jira_get_sprint_issues":{let{sprintName:e,sprintId:t,projectKey:r,status:i,maxResults:o}=a;if(!e&&!t)return JSON.stringify({error:"sprintName or sprintId is required"});let s=o||50,n=t?`sprint = ${t}`:`sprint = "${e}"`,p=r?`project = ${r} AND `:"",u=i?` AND status = "${i}"`:"",d=`${p}${n}${u} ORDER BY status ASC, priority DESC`,m=`jql=${encodeURIComponent(d)}&maxResults=${s}&fields=summary,status,assignee,priority,issuetype,project`,g=await f(`/rest/api/3/search/jql?${m}`),l=(g.issues||[]).map(y=>({key:y.key,project:y.fields?.project?.key,summary:y.fields?.summary,status:y.fields?.status?.name,assignee:y.fields?.assignee?.displayName||"Unassigned",priority:y.fields?.priority?.name,type:y.fields?.issuetype?.name})),h={};for(let y of l)h[y.status]=(h[y.status]||0)+1;return JSON.stringify({count:l.length,total:g.total||l.length,statusCounts:h,issues:l})}case"jira_get_comments":{let{issueKey:e,maxResults:t}=a;if(!e)return JSON.stringify({error:"issueKey is required"});let i=await f(`/rest/api/3/issue/${e}/comment?maxResults=${t||50}&orderBy=-created`),o=(i.comments||[]).map(s=>{let n="";return s.body?.content&&(n=S(s.body.content)),{id:s.id,author:s.author?.displayName||"Unknown",body:n,created:s.created,updated:s.updated}});return JSON.stringify({count:o.length,total:i.total||o.length,comments:o})}case"jira_add_comment":{let{issueKey:e,body:t}=a;return!e||!t?JSON.stringify({error:"issueKey and body are required"}):(await f(`/rest/api/3/issue/${e}/comment`,{method:"POST",body:{body:{type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:t}]}]}}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_edit_issue":{let{issueKey:e,fields:t}=a;return!e||!t?JSON.stringify({error:"issueKey and fields are required"}):(await f(`/rest/api/3/issue/${e}`,{method:"PUT",body:{fields:t}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_transition_issue":{let{issueKey:e,transitionId:t,toStatus:r,statusName:i,status:o}=a;if(!e)return JSON.stringify({error:"issueKey is required"});let s=String(r||i||o||"").trim();if(!t&&!s){let d=((await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[]).map(m=>({id:m.id,name:m.name,to:m.to?.name}));return JSON.stringify({ok:!1,error:"transitionId or toStatus is required",issueKey:e,availableTransitions:d})}let n=t;if(!n){let d=(await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[],m=k(s),g=d.find(l=>k(l?.name||"")===m||k(l?.to?.name||"")===m);if(!g){let l=w(s);l.length>=2&&(g=d.find(h=>{let y=w(h?.name||""),_=w(h?.to?.name||""),j=y.length>=2&&(y.includes(l)||l.includes(y)),v=_.length>=2&&(_.includes(l)||l.includes(_));return j||v}))}if(!g){let l=d.map(j=>{let v=I(s,j?.name||""),J=I(s,j?.to?.name||"");return{t:j,score:Math.max(v,J)}}).sort((j,v)=>v.score-j.score),h=l[0],y=l[1];h&&h.score>=.45&&(!y||h.score-y.score>=.12)&&(g=h.t)}if(!g?.id)return JSON.stringify({ok:!1,error:`No transition matches target status: "${s}"`,issueKey:e,availableTransitions:d.map(l=>({id:l.id,name:l.name,to:l.to?.name}))});n=g.id}await f(`/rest/api/3/issue/${e}/transitions`,{method:"POST",body:{transition:{id:n}}});let p=await f(`/rest/api/3/issue/${e}?fields=status`);return JSON.stringify({ok:!0,issueKey:e,transitionId:n,statusAfter:p?.fields?.status?.name||null})}default:return JSON.stringify({error:`Unknown tool: ${c}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"jira_list_projects",description:"List all Jira projects accessible to the user",input_schema:{type:"object",properties:{}}},{name:"jira_list_statuses",description:"List Jira statuses. Use projectKey to get statuses applicable in that project workflow.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Optional project key (e.g. PROJ). If omitted, returns global status catalog."}}}},{name:"jira_list_issue_types",description:"List issue types allowed for issue creation in the given project.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"}},required:["projectKey"]}},{name:"jira_search",description:"Search Jira issues using JQL",input_schema:{type:"object",properties:{jql:{type:"string",description:'JQL query string, e.g. "project = PROJ AND status = Open"'},maxResults:{type:"number",description:"Max results to return (default 20)"}},required:["jql"]}},{name:"jira_get_issue",description:"Get details of a specific Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"}},required:["issueKey"]}},{name:"jira_create_issue",description:"Create a new Jira issue",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},summary:{type:"string",description:"Issue title/summary"},issueType:{type:"string",description:"Issue type (default: Task). Common: Task, Bug, Story, Epic"},description:{type:"string",description:"Issue description (plain text)"},priority:{type:"string",description:"Priority name, e.g. High, Medium, Low"},labels:{type:"array",items:{type:"string"},description:"Array of label strings"},assigneeId:{type:"string",description:"Atlassian account ID to assign to"},moveToSprint:{type:"boolean",description:"If true, move created issue to a sprint and verify."},moveToActiveSprint:{type:"boolean",description:"Backward-compatible alias for moveToSprint."},sprintId:{type:"number",description:"Optional sprint id for placement."},sprintName:{type:"string",description:"Optional sprint name for placement."},target:{type:"string",description:"Placement target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["projectKey","summary"]}},{name:"jira_list_sprints",description:"List sprints for a Jira project (returns sprint names, IDs, states, dates)",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},state:{type:"string",description:"Filter: active, closed, future. Omit for all."}},required:["projectKey"]}},{name:"jira_get_sprint_issues",description:"Get all issues in a sprint, optionally filtered by status column name",input_schema:{type:"object",properties:{sprintName:{type:"string",description:"Sprint name (from jira_list_sprints). Use this OR sprintId."},sprintId:{type:"number",description:"Sprint ID (from jira_list_sprints). Use this OR sprintName."},projectKey:{type:"string",description:"Project key to scope the search (optional)"},status:{type:"string",description:'Filter by status name (e.g. "\u8FDB\u884C\u4E2D", "\u6D4B\u8BD5", "Done")'},maxResults:{type:"number",description:"Max issues to return (default 50)"}}}},{name:"jira_move_to_active_sprint",description:"Backward-compatible alias: move issue to sprint target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_move_issue_to_sprint",description:"Move an issue to a sprint by id/name/target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_get_comments",description:"Get comments on a Jira issue (newest first)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},maxResults:{type:"number",description:"Max comments to return (default 50)"}},required:["issueKey"]}},{name:"jira_add_comment",description:"Add a comment to a Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},body:{type:"string",description:"Comment text (plain text)"}},required:["issueKey","body"]}},{name:"jira_edit_issue",description:"Update fields on a Jira issue (summary, story points, labels, priority)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},fields:{type:"object",description:"Object of field names to values",additionalProperties:!0}},required:["issueKey","fields"]}},{name:"jira_transition_issue",description:"Move a Jira issue to a different status. Always pass toStatus when user gave a target; only pass issueKey alone when you explicitly need to list transitions.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},transitionId:{type:"string",description:"Transition ID to perform (optional if toStatus is provided)"},toStatus:{type:"string",description:'Target status/column name (e.g. "\u5DF2\u7ECF\u9A8C\u6536", "Done", "In Progress"). If provided, tool resolves matching transition automatically.'}},required:["issueKey"]}}]};export{F as jiraSkill};
package/dist/lark.js CHANGED
@@ -1,7 +1,7 @@
1
- import{existsSync as p}from"fs";import{fileURLToPath as m}from"url";import{dirname as l,resolve as h}from"path";import{resolveIntegrationToken as u}from"@zibby/core/backend-client.js";function g(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let e=l(m(import.meta.url)),t=h(e,"..","bin","mcp-lark.mjs");return p(t)?t:null}var y=6e3*1e3,a=null;async function f(){let{appId:e,appSecret:t,host:s}=await u("lark");if(a&&a.appId===e&&a.expiresAt>Date.now())return{token:a.token,host:s};let n=await(await fetch(`${s}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:e,app_secret:t})})).json();if(n.code!==0)throw new Error(`Lark tenant_access_token failed: ${n.msg||n.code}`);return a={token:n.tenant_access_token,expiresAt:Date.now()+y,appId:e},{token:n.tenant_access_token,host:s}}async function c(e,t,s={}){let{token:i,host:n}=await f(),r=`${n}${t}`,_={method:e,headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json; charset=utf-8"}};e!=="GET"&&(_.body=JSON.stringify(s));let o=await(await fetch(r,_)).json();if(o.code!==0)throw new Error(`Lark API ${t} error: ${o.msg||o.code}`);return o.data||{}}function d(e){return JSON.stringify({text:e})}function k(e){return!e||typeof e!="string"||e.startsWith("oc_")?"chat_id":e.startsWith("ou_")?"open_id":e.startsWith("on_")?"union_id":e.startsWith("cli_")?"app_id":e.includes("@")?"email":"chat_id"}var O={id:"lark",serverName:"lark",allowedTools:["mcp__lark__*"],description:"Lark / Feishu messaging \u2014 send messages and reply in threads.",envKeys:[],promptFragment:`## Lark (connected)
1
+ import{existsSync as l}from"fs";import{fileURLToPath as m}from"url";import{dirname as g,resolve as h}from"path";import{resolveIntegrationToken as u}from"@zibby/core/backend-client.js";var p=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion"}),b=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",connectPath:"/integrations?provider=lark"},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"}});function y(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let e=g(m(import.meta.url)),t=h(e,"..","bin","mcp-lark.mjs");return l(t)?t:null}var f=6e3*1e3,s=null;async function k(){let{appId:e,appSecret:t,host:i}=await u("lark");if(s&&s.appId===e&&s.expiresAt>Date.now())return{token:s.token,host:i};let r=await(await fetch(`${i}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:e,app_secret:t})})).json();if(r.code!==0)throw new Error(`Lark tenant_access_token failed: ${r.msg||r.code}`);return s={token:r.tenant_access_token,expiresAt:Date.now()+f,appId:e},{token:r.tenant_access_token,host:i}}async function c(e,t,i={}){let{token:a,host:r}=await k(),n=`${r}${t}`,d={method:e,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json; charset=utf-8"}};e!=="GET"&&(d.body=JSON.stringify(i));let o=await(await fetch(n,d)).json();if(o.code!==0)throw new Error(`Lark API ${t} error: ${o.msg||o.code}`);return o.data||{}}function _(e){return JSON.stringify({text:e})}function T(e){return!e||typeof e!="string"||e.startsWith("oc_")?"chat_id":e.startsWith("ou_")?"open_id":e.startsWith("on_")?"union_id":e.startsWith("cli_")?"app_id":e.includes("@")?"email":"chat_id"}var L={id:"lark",serverName:"lark",allowedTools:["mcp__lark__*"],requiresIntegration:p.LARK,description:"Lark / Feishu messaging \u2014 send messages and reply in threads.",envKeys:[],promptFragment:`## Lark (connected)
2
2
  You can send messages and replies on Lark. Use:
3
3
  - lark_send_message: post a message to a chat, user, or DM
4
4
  - lark_reply: reply to an existing message (threaded)
5
5
  - lark_list_chats: list chats the bot is a member of
6
6
  - lark_get_chat_history: fetch recent messages in a chat
7
- When responding to an incoming event, prefer lark_reply with the source message_id so the response threads cleanly.`,resolve(){let e=g();if(!e)return null;let t={};for(let s of["PROJECT_API_TOKEN","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[s]&&(t[s]=process.env[s]);return{type:"stdio",command:"node",args:[e],env:t,alwaysLoad:!0}},tools:[{name:"lark_send_message",description:"Send a text message to a Lark chat, user, or DM. receive_id can be a chat_id (oc_*), open_id (ou_*), union_id (on_*), or email.",input_schema:{type:"object",properties:{receive_id:{type:"string",description:"Target id: chat_id (oc_*), open_id (ou_*), union_id (on_*), or email"},text:{type:"string",description:"Message text"}},required:["receive_id","text"]}},{name:"lark_reply",description:"Reply to an existing Lark message (creates a thread). Use the message_id from the inbound event.",input_schema:{type:"object",properties:{message_id:{type:"string",description:"Lark message id (om_*) to reply to"},text:{type:"string",description:"Reply text"}},required:["message_id","text"]}},{name:"lark_list_chats",description:"List chats (groups + DMs) the bot is a member of.",input_schema:{type:"object",properties:{page_size:{type:"number",description:"Max results (default 50)"}}}},{name:"lark_get_chat_history",description:"Fetch recent messages in a chat.",input_schema:{type:"object",properties:{chat_id:{type:"string",description:"Chat id (oc_*)"},page_size:{type:"number",description:"Max messages (default 20)"}},required:["chat_id"]}}],async handleToolCall(e,t){try{switch(e){case"lark_send_message":{if(!t.receive_id||!t.text)return JSON.stringify({error:"receive_id and text are required"});let s=k(t.receive_id),i=await c("POST",`/open-apis/im/v1/messages?receive_id_type=${s}`,{receive_id:t.receive_id,msg_type:"text",content:d(t.text)});return JSON.stringify({ok:!0,message_id:i.message_id})}case"lark_reply":{if(!t.message_id||!t.text)return JSON.stringify({error:"message_id and text are required"});let s=await c("POST",`/open-apis/im/v1/messages/${encodeURIComponent(t.message_id)}/reply`,{msg_type:"text",content:d(t.text)});return JSON.stringify({ok:!0,message_id:s.message_id})}case"lark_list_chats":{let s=t.page_size||50,n=((await c("GET",`/open-apis/im/v1/chats?page_size=${s}`)).items||[]).map(r=>({chat_id:r.chat_id,name:r.name,description:r.description,owner_id:r.owner_id,chat_mode:r.chat_mode}));return JSON.stringify({chats:n})}case"lark_get_chat_history":{if(!t.chat_id)return JSON.stringify({error:"chat_id is required"});let s=t.page_size||20,n=((await c("GET",`/open-apis/im/v1/messages?container_id_type=chat&container_id=${encodeURIComponent(t.chat_id)}&page_size=${s}&sort_type=ByCreateTimeDesc`)).items||[]).map(r=>({message_id:r.message_id,sender_id:r.sender?.id,sender_type:r.sender?.sender_type,msg_type:r.msg_type,content:r.body?.content,create_time:r.create_time}));return JSON.stringify({messages:n})}default:return JSON.stringify({error:`Unknown tool: ${e}`})}}catch(s){return JSON.stringify({error:s.message})}}};function b(){a=null}export{b as _resetLarkTokenCache,O as larkSkill};
7
+ When responding to an incoming event, prefer lark_reply with the source message_id so the response threads cleanly.`,resolve(){let e=y();if(!e)return null;let t={};for(let i of["PROJECT_API_TOKEN","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[i]&&(t[i]=process.env[i]);return{type:"stdio",command:"node",args:[e],env:t,alwaysLoad:!0}},tools:[{name:"lark_send_message",description:"Send a text message to a Lark chat, user, or DM. receive_id can be a chat_id (oc_*), open_id (ou_*), union_id (on_*), or email.",input_schema:{type:"object",properties:{receive_id:{type:"string",description:"Target id: chat_id (oc_*), open_id (ou_*), union_id (on_*), or email"},text:{type:"string",description:"Message text"}},required:["receive_id","text"]}},{name:"lark_reply",description:"Reply to an existing Lark message (creates a thread). Use the message_id from the inbound event.",input_schema:{type:"object",properties:{message_id:{type:"string",description:"Lark message id (om_*) to reply to"},text:{type:"string",description:"Reply text"}},required:["message_id","text"]}},{name:"lark_list_chats",description:"List chats (groups + DMs) the bot is a member of.",input_schema:{type:"object",properties:{page_size:{type:"number",description:"Max results (default 50)"}}}},{name:"lark_get_chat_history",description:"Fetch recent messages in a chat.",input_schema:{type:"object",properties:{chat_id:{type:"string",description:"Chat id (oc_*)"},page_size:{type:"number",description:"Max messages (default 20)"}},required:["chat_id"]}}],async handleToolCall(e,t){try{switch(e){case"lark_send_message":{if(!t.receive_id||!t.text)return JSON.stringify({error:"receive_id and text are required"});let i=T(t.receive_id),a=await c("POST",`/open-apis/im/v1/messages?receive_id_type=${i}`,{receive_id:t.receive_id,msg_type:"text",content:_(t.text)});return JSON.stringify({ok:!0,message_id:a.message_id})}case"lark_reply":{if(!t.message_id||!t.text)return JSON.stringify({error:"message_id and text are required"});let i=await c("POST",`/open-apis/im/v1/messages/${encodeURIComponent(t.message_id)}/reply`,{msg_type:"text",content:_(t.text)});return JSON.stringify({ok:!0,message_id:i.message_id})}case"lark_list_chats":{let i=t.page_size||50,r=((await c("GET",`/open-apis/im/v1/chats?page_size=${i}`)).items||[]).map(n=>({chat_id:n.chat_id,name:n.name,description:n.description,owner_id:n.owner_id,chat_mode:n.chat_mode}));return JSON.stringify({chats:r})}case"lark_get_chat_history":{if(!t.chat_id)return JSON.stringify({error:"chat_id is required"});let i=t.page_size||20,r=((await c("GET",`/open-apis/im/v1/messages?container_id_type=chat&container_id=${encodeURIComponent(t.chat_id)}&page_size=${i}&sort_type=ByCreateTimeDesc`)).items||[]).map(n=>({message_id:n.message_id,sender_id:n.sender?.id,sender_type:n.sender?.sender_type,msg_type:n.msg_type,content:n.body?.content,create_time:n.create_time}));return JSON.stringify({messages:r})}default:return JSON.stringify({error:`Unknown tool: ${e}`})}}catch(i){return JSON.stringify({error:i.message})}}};function P(){s=null}export{P as _resetLarkTokenCache,L as larkSkill};
@@ -0,0 +1 @@
1
+ import{resolveIntegrationToken as w}from"@zibby/core/backend-client.js";var k=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion"}),N=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",connectPath:"/integrations?provider=lark"},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"}});var O=Object.freeze({id:"openai_billing",requiresIntegration:k.OPENAI_BILLING,description:"OpenAI organization billing/usage admin API (paste sk-admin-... key)"}),j=Object.freeze({id:"anthropic_billing",requiresIntegration:k.ANTHROPIC_BILLING,description:"Anthropic organization cost/usage admin API (paste sk-ant-admin-... key)"}),x=Object.freeze({id:"cursor_admin",requiresIntegration:k.CURSOR_ADMIN,description:"Cursor Team/Enterprise admin API (paste admin key)"});function b(t){return Math.floor(t/1e3)}function _(t){return new Date(t).toISOString().slice(0,10)}function I(t){return new Date(t).toISOString()}async function f(t){let r=await w(t);if(!r?.token)throw new Error(`${t} token resolver returned no token`);return r.token}async function y({startMs:t,endMs:r,groupBy:o=["project_id","line_item"]}){let a=await f("openai_billing"),e=[],n=0,m=o.map(d=>`group_by[]=${encodeURIComponent(d)}`).join("&"),l=null;for(let d=0;d<50;d++){let i=`https://api.openai.com/v1/organization/costs?${[`start_time=${b(t)}`,`end_time=${b(r)}`,"bucket_width=1d","limit=180",m,l?`page=${encodeURIComponent(l)}`:""].filter(Boolean).join("&")}`,c=await fetch(i,{headers:{Authorization:`Bearer ${a}`}});if(!c.ok){let p=await c.text().catch(()=>"");throw new Error(`OpenAI costs API ${c.status}: ${p.slice(0,200)}`)}let u=await c.json();for(let p of u.data||[]){n+=1;let g=_((p.start_time||0)*1e3);for(let s of p.results||[])e.push({provider:"openai",day:g,costUsd:Number(s.amount?.value??0),projectId:s.project_id||void 0,apiKeyId:s.api_key_id||void 0,model:s.line_item||void 0})}if(!u.has_more||!u.next_page)break;l=u.next_page}return{ok:!0,items:e,rawBuckets:n}}async function R(){let t=await f("openai_billing"),o=await fetch("https://api.openai.com/v1/organization/projects?limit=100",{headers:{Authorization:`Bearer ${t}`}});if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`OpenAI projects API ${o.status}: ${n.slice(0,200)}`)}let a=await o.json(),e=new Map;for(let n of a.data||[])e.set(n.id,n.name);return e}async function A({startMs:t,endMs:r,groupBy:o=["workspace_id"]}){let a=await f("anthropic_billing"),e=[],n=0,m=o.map(d=>`group_by[]=${encodeURIComponent(d)}`).join("&"),l=null;for(let d=0;d<50;d++){let i=`https://api.anthropic.com/v1/organizations/cost_report?${[`starting_at=${encodeURIComponent(I(t))}`,`ending_at=${encodeURIComponent(I(r))}`,"bucket=1d","limit=100",m,l?`page=${encodeURIComponent(l)}`:""].filter(Boolean).join("&")}`,c=await fetch(i,{headers:{"x-api-key":a,"anthropic-version":"2023-06-01"}});if(!c.ok){let p=await c.text().catch(()=>"");throw new Error(`Anthropic cost_report ${c.status}: ${p.slice(0,200)}`)}let u=await c.json();for(let p of u.data||[]){n+=1;let g=(p.starting_at||"").slice(0,10);for(let s of p.results||[])e.push({provider:"anthropic",day:g,costUsd:Number(s.amount??s.cost??0),workspaceId:s.workspace_id||void 0,apiKeyId:s.api_key_id||void 0,model:s.model||void 0,tokensIn:s.uncached_input_tokens!=null?Number(s.uncached_input_tokens):void 0,tokensOut:s.output_tokens!=null?Number(s.output_tokens):void 0,cachedTokens:s.cached_input_tokens!=null?Number(s.cached_input_tokens):void 0})}if(!u.has_more||!u.next_page)break;l=u.next_page}return{ok:!0,items:e,rawBuckets:n}}async function C(){let t=await f("anthropic_billing"),o=await fetch("https://api.anthropic.com/v1/organizations/workspaces?limit=100",{headers:{"x-api-key":t,"anthropic-version":"2023-06-01"}});if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`Anthropic workspaces ${o.status}: ${n.slice(0,200)}`)}let a=await o.json(),e=new Map;for(let n of a.data||[])e.set(n.id,n.name);return e}async function v({startMs:t,endMs:r}){let o=await f("cursor_admin"),a=_(t),e=_(r),n=`https://api.cursor.com/teams/daily-usage-data?startDate=${a}&endDate=${e}`,m=await fetch(n,{headers:{Authorization:`Bearer ${o}`}});if(!m.ok){let i=await m.text().catch(()=>"");throw new Error(`Cursor daily-usage ${m.status}: ${i.slice(0,200)}`)}let l=await m.json(),d=[],h=0;for(let i of l.data||[]){h+=1;let c=i.date;for(let u of i.userMetrics||[]){for(let p of u.modelUsage||[]){let g=Number(p.acceptedLines??0),s=Number(p.suggestedLines??0);d.push({provider:"cursor",day:c,costUsd:Number(p.totalCents??0)/100,userEmail:u.email,model:p.model,requestCount:Number(p.requestCount??0),acceptanceRate:s>0?g/s:void 0})}(!u.modelUsage||u.modelUsage.length===0)&&d.push({provider:"cursor",day:c,costUsd:Number(u.totalCents??0)/100,userEmail:u.email})}}return{ok:!0,items:d,rawBuckets:h}}async function S({startMs:t,endMs:r}){let[o,a,e]=await Promise.allSettled([y({startMs:t,endMs:r}),A({startMs:t,endMs:r}),v({startMs:t,endMs:r})]),n=i=>i.status==="fulfilled"?i.value:{ok:!1,error:i.reason?.message||String(i.reason),items:[]},m=n(o),l=n(a),d=n(e),h=[{provider:"openai",totalUsd:m.items.reduce((i,c)=>i+(c.costUsd||0),0)},{provider:"anthropic",totalUsd:l.items.reduce((i,c)=>i+(c.costUsd||0),0)},{provider:"cursor",totalUsd:d.items.reduce((i,c)=>i+(c.costUsd||0),0)}];return{openai:m,anthropic:l,cursor:d,totals:h}}function B(t,r){let o=new Map;for(let a of t){let e=r(a);if(!e)continue;let n=o.get(e)||{key:e,totalUsd:0,count:0};n.totalUsd+=a.costUsd||0,n.count+=1,o.set(e,n)}return[...o.values()].sort((a,e)=>e.totalUsd-a.totalUsd)}function T(t){if(!t.length)return{mean:0,stddev:0};let r=t.reduce((a,e)=>a+e,0)/t.length,o=t.reduce((a,e)=>a+(e-r)**2,0)/t.length;return{mean:r,stddev:Math.sqrt(o)}}export{j as anthropicBillingSkill,x as cursorAdminSkill,S as fetchAllProviders,A as fetchAnthropicCosts,C as fetchAnthropicWorkspaces,v as fetchCursorSpend,y as fetchOpenAICosts,R as fetchOpenAIProjects,B as groupByKey,T as meanStddev,O as openaiBillingSkill};
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/skills",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Built-in skill definitions for Zibby test automation framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,11 @@
14
14
  "./slack": "./dist/slack.js",
15
15
  "./lark": "./dist/lark.js",
16
16
  "./memory": "./dist/memory.js",
17
- "./function": "./dist/function-skill.js"
17
+ "./function": "./dist/function-skill.js",
18
+ "./integrations": "./dist/integrations.js",
19
+ "./report": "./dist/report.js",
20
+ "./llm-billing": "./dist/llm-billing.js",
21
+ "./sentry": "./dist/sentry.js"
18
22
  },
19
23
  "scripts": {
20
24
  "build": "node ../scripts/build.mjs",
@@ -34,10 +38,10 @@
34
38
  "homepage": "https://zibby.dev",
35
39
  "repository": {
36
40
  "type": "git",
37
- "url": "https://github.com/ZibbyHQ/zibby-agent"
41
+ "url": "https://github.com/ZibbyHQ/skills"
38
42
  },
39
43
  "bugs": {
40
- "url": "https://github.com/ZibbyHQ/zibby-agent/issues"
44
+ "url": "https://github.com/ZibbyHQ/skills/issues"
41
45
  },
42
46
  "files": [
43
47
  "dist/",
@@ -62,6 +66,9 @@
62
66
  "@zibby/mcp-memory": "*"
63
67
  },
64
68
  "devDependencies": {
65
- "esbuild": "^0.28.0"
69
+ "@eslint/js": "^10.0.1",
70
+ "esbuild": "^0.28.0",
71
+ "eslint": "^10.0.2",
72
+ "globals": "^17.4.0"
66
73
  }
67
74
  }
package/dist/report.js ADDED
@@ -0,0 +1,12 @@
1
+ import{z as a}from"zod";var g=["ok","info","warn","critical"],R=a.object({primary:a.string().min(1).max(200).describe('Headline number or phrase (e.g. "$8,240"). Rendered in large/bold.'),delta:a.object({value:a.string().max(40).describe('Delta vs baseline (e.g. "+12% wow"). Free-form string.'),direction:a.enum(["up","down","flat"]).optional(),severity:a.enum(g).optional().describe("Color severity for the delta (warn/critical highlights regressions).")}).optional().describe("Optional comparison vs baseline. Renders inline next to primary."),summary:a.string().max(800).optional().describe('One-sentence narrative ("why this number"). Plain prose.')}),U=a.object({kind:a.literal("trend"),title:a.string().max(120).optional(),labels:a.array(a.string().max(60)).min(2).max(20).describe('Bucket labels (e.g. ["Week-3", "Week-2", "Week-1", "This wk"]).'),values:a.array(a.number()).min(2).max(20).describe("Numeric values, one per label. Must match labels.length."),highlight:a.enum(["last","max","min","none"]).default("last").optional().describe("Which bucket to visually highlight in the rendered card."),severity:a.enum(g).optional()}).refine(o=>o.labels.length===o.values.length,{message:"labels.length must equal values.length"}),O=a.object({kind:a.literal("table"),title:a.string().max(120).optional(),headers:a.array(a.string().max(40)).min(1).max(8),rows:a.array(a.array(a.union([a.string().max(200),a.number()])).min(1).max(8)).max(40).describe("2D matrix. Each inner array must have headers.length entries.")}).refine(o=>o.rows.every(e=>e.length===o.headers.length),{message:"every row must have headers.length entries"}),T=a.object({kind:a.literal("callouts"),title:a.string().max(120).optional(),tone:a.enum(g).default("info").optional(),items:a.array(a.string().min(1).max(600)).min(1).max(10).describe("Each item renders as a bullet with a severity emoji.")}),E=a.object({kind:a.literal("breakdown"),title:a.string().max(120).optional(),rows:a.array(a.object({label:a.string().min(1).max(80),value:a.string().min(1).max(80),sub:a.string().max(120).optional(),severity:a.enum(g).optional()})).min(1).max(20)}),M=a.object({kind:a.literal("paragraph"),title:a.string().max(120).optional(),text:a.string().min(1).max(3e3)}),I=a.discriminatedUnion("kind",[U,O,T,E,M]),k=a.object({title:a.string().min(1).max(200).describe('Card title (e.g. "Weekly AI Spend Report").'),subtitle:a.string().max(200).optional().describe('Date range or smaller header (e.g. "May 13 \u2014 May 20").'),headline:R,sections:a.array(I).max(20).default([]),footer:a.object({viewUrl:a.string().url().optional().describe('Optional "View in Zibby" button URL.'),rerunUrl:a.string().url().optional().describe('Optional "Run again" button URL.')}).optional()}),p=Object.freeze({ok:"\u{1F7E2}",info:"\u{1F535}",warn:"\u{1F7E0}",critical:"\u{1F534}"}),_=Object.freeze({up:"\u2191",down:"\u2193",flat:"\u2192"}),L=Object.freeze({ok:"green",info:"blue",warn:"orange",critical:"red"});function w(o,e,t=12){if(!Number.isFinite(o)||!Number.isFinite(e)||e<=0)return"";let c=Math.max(0,Math.min(1,o/e)),h=Math.round(c*t);return"\u2593".repeat(h)+"\u2591".repeat(t-h)}function x(o,e){let t=String(o);return t.length>=e?t:t+" ".repeat(e-t.length)}function $(o,e){let t=String(o);return t.length>=e?t:" ".repeat(e-t.length)+t}function S({headers:o,rows:e}){let t=o.map((n,r)=>{let l=Math.max(String(n).length,...e.map(s=>String(s[r]??"").length));return Math.min(l,32)}),c=n=>n.map((r,l)=>x(r,t[l])).join(" "),h=t.map(n=>"\u2500".repeat(n)).join(" ");return"```\n"+[c(o),h,...e.map(n=>c(n))].join(`
2
+ `)+"\n```"}function A(o){let e=k.parse(o),t=[];t.push({type:"header",text:{type:"plain_text",text:e.title.slice(0,150),emoji:!0}}),e.subtitle&&t.push({type:"context",elements:[{type:"mrkdwn",text:e.subtitle}]});let c=[`*${e.headline.primary}*`];if(e.headline.delta){let i=_[e.headline.delta.direction]||"",n=e.headline.delta.severity?p[e.headline.delta.severity]:"";c.push(`${i} ${e.headline.delta.value} ${n}`.trim())}let h=c.join(" ");e.headline.summary&&(h+=`
3
+ `+e.headline.summary),t.push({type:"section",text:{type:"mrkdwn",text:h}});for(let i of e.sections)switch(t.push({type:"divider"}),i.title&&t.push({type:"section",text:{type:"mrkdwn",text:`*${i.title}*`}}),i.kind){case"trend":{let n=Math.max(...i.values),r=i.labels.map((l,s)=>{let u=i.values[s],d=w(u,n),y=(i.highlight==="last"&&s===i.labels.length-1||i.highlight==="max"&&u===n||i.highlight==="min"&&u===Math.min(...i.values))&&i.severity?` ${p[i.severity]}`:"";return`${x(l,10)} ${$(u.toLocaleString(),8)} ${d}${y}`});t.push({type:"section",text:{type:"mrkdwn",text:"```\n"+r.join(`
4
+ `)+"\n```"}});break}case"table":{t.push({type:"section",text:{type:"mrkdwn",text:S(i)}});break}case"callouts":{let n=p[i.tone||"info"];t.push({type:"section",text:{type:"mrkdwn",text:i.items.map(r=>`${n} ${r}`).join(`
5
+ `)}});break}case"breakdown":{let n=i.rows.map(r=>({type:"mrkdwn",text:`*${r.label}*
6
+ ${r.value}${r.sub?`
7
+ _${r.sub}_`:""}${r.severity?` ${p[r.severity]}`:""}`}));for(let r=0;r<n.length;r+=10)t.push({type:"section",fields:n.slice(r,r+10)});break}case"paragraph":{t.push({type:"section",text:{type:"mrkdwn",text:i.text}});break}}if(e.footer&&(e.footer.viewUrl||e.footer.rerunUrl)){let i=[];e.footer.viewUrl&&i.push({type:"button",text:{type:"plain_text",text:"View in Zibby"},url:e.footer.viewUrl,style:"primary"}),e.footer.rerunUrl&&i.push({type:"button",text:{type:"plain_text",text:"Run again"},url:e.footer.rerunUrl}),t.push({type:"divider"}),t.push({type:"actions",elements:i})}return t}function C(o){let e=k.parse(o),t=[],c=[`**${e.headline.primary}**`];if(e.headline.delta){let n=_[e.headline.delta.direction]||"",r=e.headline.delta.severity?p[e.headline.delta.severity]:"";c.push(`${n} ${e.headline.delta.value} ${r}`.trim())}let h=c.join(" ");e.headline.summary&&(h+=`
8
+ `+e.headline.summary),t.push({tag:"div",text:{tag:"lark_md",content:h}});for(let n of e.sections)switch(t.push({tag:"hr"}),n.title&&t.push({tag:"div",text:{tag:"lark_md",content:`**${n.title}**`}}),n.kind){case"trend":{let r=Math.max(...n.values),l=n.labels.map((s,u)=>{let d=n.values[u],b=w(d,r),f=(n.highlight==="last"&&u===n.labels.length-1||n.highlight==="max"&&d===r||n.highlight==="min"&&d===Math.min(...n.values))&&n.severity?` ${p[n.severity]}`:"";return`${x(s,10)} ${$(d.toLocaleString(),8)} ${b}${f}`});t.push({tag:"div",text:{tag:"lark_md",content:"```\n"+l.join(`
9
+ `)+"\n```"}});break}case"table":{t.push({tag:"div",text:{tag:"lark_md",content:S(n)}});break}case"callouts":{let r=p[n.tone||"info"];t.push({tag:"div",text:{tag:"lark_md",content:n.items.map(l=>`${r} ${l}`).join(`
10
+ `)}});break}case"breakdown":{let r=n.rows.map(l=>{let s=l.severity?` ${p[l.severity]}`:"",u=l.sub?` *${l.sub}*`:"";return`**${l.label}** ${l.value}${u}${s}`});t.push({tag:"div",text:{tag:"lark_md",content:r.join(`
11
+ `)}});break}case"paragraph":{t.push({tag:"div",text:{tag:"lark_md",content:n.text}});break}}if(e.footer&&(e.footer.viewUrl||e.footer.rerunUrl)){let n=[];e.footer.viewUrl&&n.push({tag:"button",text:{tag:"plain_text",content:"View in Zibby"},url:e.footer.viewUrl,type:"primary"}),e.footer.rerunUrl&&n.push({tag:"button",text:{tag:"plain_text",content:"Run again"},url:e.footer.rerunUrl,type:"default"}),t.push({tag:"hr"}),t.push({tag:"action",actions:n})}let i="blue";return e.headline.delta?.severity&&(i=L[e.headline.delta.severity]||"blue"),{config:{wide_screen_mode:!0},header:{title:{tag:"plain_text",content:e.title.slice(0,200)},subtitle:e.subtitle?{tag:"plain_text",content:e.subtitle.slice(0,200)}:void 0,template:i},elements:t}}var N=Object.freeze({ok:"green_background",info:"blue_background",warn:"orange_background",critical:"red_background"}),j=Object.freeze({ok:"\u{1F7E2}",info:"\u2139\uFE0F",warn:"\u26A0\uFE0F",critical:"\u{1F6A8}"});function m(o,e={}){let t={type:"text",text:{content:String(o).slice(0,2e3)}};return e.annotations&&(t.annotations=e.annotations),[t]}function v(o,e){let t={object:"block",type:"paragraph",paragraph:{rich_text:m(o)}};return e&&(t.paragraph.color=e),t}function V(o){return{object:"block",type:"code",code:{rich_text:m(o),language:"plain text"}}}function W(o){let e=k.parse(o),t=[];t.push({object:"block",type:"heading_1",heading_1:{rich_text:m(e.title.slice(0,200))}}),e.subtitle&&t.push(v(e.subtitle,"gray_background"));let c=[{type:"text",text:{content:e.headline.primary.slice(0,200)},annotations:{bold:!0}}];if(e.headline.delta){let i=_[e.headline.delta.direction]||"",n=e.headline.delta.severity?p[e.headline.delta.severity]:"",r=` ${i} ${e.headline.delta.value} ${n}`.trimEnd();c.push({type:"text",text:{content:r}})}t.push({object:"block",type:"paragraph",paragraph:{rich_text:c}}),e.headline.summary&&t.push(v(e.headline.summary));for(let i of e.sections)switch(t.push({object:"block",type:"divider",divider:{}}),i.title&&t.push({object:"block",type:"heading_2",heading_2:{rich_text:m(i.title)}}),i.kind){case"trend":{let n=Math.max(...i.values),r=Math.min(...i.values),l=i.labels.map((s,u)=>{let d=i.values[u],b=w(d,n),f=(i.highlight==="last"&&u===i.labels.length-1||i.highlight==="max"&&d===n||i.highlight==="min"&&d===r)&&i.severity?` ${p[i.severity]}`:"";return`${x(s,10)} ${$(d.toLocaleString(),8)} ${b}${f}`});t.push(V(l.join(`
12
+ `)));break}case"table":{let n={object:"block",type:"table_row",table_row:{cells:i.headers.map(l=>m(l))}},r=i.rows.map(l=>({object:"block",type:"table_row",table_row:{cells:l.map(s=>m(String(s)))}}));t.push({object:"block",type:"table",table:{table_width:i.headers.length,has_column_header:!0,has_row_header:!1,children:[n,...r]}});break}case"callouts":{let n=i.tone||"info",r=j[n],l=N[n];for(let s of i.items)t.push({object:"block",type:"callout",callout:{rich_text:m(s),icon:{type:"emoji",emoji:r},color:l}});break}case"breakdown":{for(let n of i.rows){let r=[{type:"text",text:{content:`${n.label}: `},annotations:{bold:!0}},{type:"text",text:{content:String(n.value)}}];n.sub&&r.push({type:"text",text:{content:` (${n.sub})`},annotations:{italic:!0}}),n.severity&&r.push({type:"text",text:{content:` ${p[n.severity]}`}}),t.push({object:"block",type:"bulleted_list_item",bulleted_list_item:{rich_text:r}})}break}case"paragraph":{t.push(v(i.text));break}}e.footer&&(e.footer.viewUrl||e.footer.rerunUrl)&&(t.push({object:"block",type:"divider",divider:{}}),e.footer.viewUrl&&t.push({object:"block",type:"embed",embed:{url:e.footer.viewUrl}}),e.footer.rerunUrl&&t.push({object:"block",type:"embed",embed:{url:e.footer.rerunUrl}}));let h=e.headline.delta?.severity?j[e.headline.delta.severity]:void 0;return{blocks:t,title:e.title.slice(0,200),icon:h}}export{g as SEVERITIES,k as reportObjectSchema,A as reportToBlockKit,C as reportToLarkCard,W as reportToNotionBlocks};
package/dist/sentry.js CHANGED
@@ -1,5 +1,5 @@
1
- import{existsSync as l}from"fs";import{fileURLToPath as y}from"url";import{dirname as d,resolve as m}from"path";import{resolveIntegrationToken as p}from"@zibby/core/backend-client.js";function f(){if(process.env.MCP_SENTRY_PATH)return process.env.MCP_SENTRY_PATH;let r=d(y(import.meta.url)),s=m(r,"..","bin","mcp-sentry.mjs");return l(s)?s:null}async function c(r,s={}){let{token:t,organizationSlug:n}=await p("sentry"),i=`https://sentry.io/api/0/organizations/${n}${r}`,e=await fetch(i,{method:s.method||"GET",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"}});if(!e.ok){let a=await e.text().catch(()=>"");throw new Error(`Sentry API ${e.status}: ${a.slice(0,300)}`)}return e.json()}var u={id:"sentry",serverName:"sentry",allowedTools:["mcp__sentry__*"],description:"Sentry error tracking \u2014 projects, issues, events",envKeys:[],tools:[],promptFragment:`## Sentry (connected)
1
+ import{existsSync as u}from"fs";import{fileURLToPath as d}from"url";import{dirname as y,resolve as m}from"path";import{resolveIntegrationToken as c}from"@zibby/core/backend-client.js";var o=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion"}),S=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",connectPath:"/integrations?provider=lark"},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"}});function _(){if(process.env.MCP_SENTRY_PATH)return process.env.MCP_SENTRY_PATH;let n=y(d(import.meta.url)),r=m(n,"..","bin","mcp-sentry.mjs");return u(r)?r:null}async function p(n,r={}){let{token:t,organizationSlug:e}=await c("sentry"),s=`https://sentry.io/api/0/organizations/${e}${n}`,i=await fetch(s,{method:r.method||"GET",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"}});if(!i.ok){let l=await i.text().catch(()=>"");throw new Error(`Sentry API ${i.status}: ${l.slice(0,300)}`)}return i.json()}async function g(){return p("/projects/?per_page=50")}async function h({query:n="is:unresolved",sort:r="date",project:t,limit:e=25}={}){let s=`/issues/?query=${encodeURIComponent(n)}&sort=${r}&per_page=${e}`;return t&&(s+=`&project=${encodeURIComponent(t)}`),p(s)}async function f(n){if(!n)throw new Error("sentryGetIssue: issueId is required");let{token:r}=await c("sentry"),t=await fetch(`https://sentry.io/api/0/issues/${n}/`,{headers:{Authorization:`Bearer ${r}`}});if(!t.ok){let e=await t.text().catch(()=>"");throw new Error(`Sentry API ${t.status}: ${e.slice(0,300)}`)}return t.json()}var a={id:"sentry",serverName:"sentry",allowedTools:["mcp__sentry__*"],requiresIntegration:o.SENTRY,description:"Sentry error tracking \u2014 projects, issues, events",envKeys:[],tools:[],promptFragment:`## Sentry (connected)
2
2
  You have access to the user's Sentry. Use these tools:
3
3
  - sentry_list_projects: List projects in the organization
4
4
  - sentry_list_issues: List errors/issues (supports Sentry search query, project filter, sort)
5
- - sentry_get_issue: Get detailed info about a specific issue (requires issueId)`,resolve(){let r=f();if(!r)return null;let s={};for(let t of["PROJECT_API_TOKEN","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[t]&&(s[t]=process.env[t]);return{type:"stdio",command:"node",args:[r],env:s,alwaysLoad:!0}},async handleToolCall(r,s={}){try{switch(r){case"sentry_list_projects":{let t=await c("/projects/?per_page=50");return JSON.stringify({projects:t.map(n=>({slug:n.slug,name:n.name,platform:n.platform}))})}case"sentry_list_issues":{let t=s.project||"",n=s.query||"is:unresolved",i=s.sort||"date",e=`/issues/?query=${encodeURIComponent(n)}&sort=${i}&per_page=${s.limit||25}`;t&&(e+=`&project=${encodeURIComponent(t)}`);let a=await c(e);return JSON.stringify({issues:a.map(o=>({id:o.id,title:o.title,culprit:o.culprit,count:o.count,firstSeen:o.firstSeen,lastSeen:o.lastSeen,level:o.level,status:o.status}))})}case"sentry_get_issue":{let{issueId:t}=s;if(!t)return JSON.stringify({error:"issueId is required"});let{token:n}=await p("sentry"),i=await fetch(`https://sentry.io/api/0/issues/${t}/`,{headers:{Authorization:`Bearer ${n}`}});if(!i.ok)throw new Error(`Sentry API ${i.status}`);let e=await i.json();return JSON.stringify({id:e.id,title:e.title,culprit:e.culprit,metadata:e.metadata,count:e.count,userCount:e.userCount,firstSeen:e.firstSeen,lastSeen:e.lastSeen,level:e.level,status:e.status,project:{slug:e.project?.slug,name:e.project?.name}})}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(t){return JSON.stringify({error:t.message})}},toolsForAssistant:[{name:"sentry_list_projects",description:"List Sentry projects",input_schema:{type:"object",properties:{}}},{name:"sentry_list_issues",description:"List Sentry issues (errors)",input_schema:{type:"object",properties:{project:{type:"string",description:"Project slug (optional)"},query:{type:"string",description:"Sentry search query (default: is:unresolved)"},sort:{type:"string",description:"Sort order: date, new, priority, freq, user (default: date)"},limit:{type:"number",description:"Max issues to return (default 25)"}}}},{name:"sentry_get_issue",description:"Get details of a specific Sentry issue",input_schema:{type:"object",properties:{issueId:{type:"string",description:"Sentry issue ID"}},required:["issueId"]}}]};u.tools=u.toolsForAssistant;export{u as sentrySkill};
5
+ - sentry_get_issue: Get detailed info about a specific issue (requires issueId)`,resolve(){let n=_();if(!n)return null;let r={};for(let t of["PROJECT_API_TOKEN","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[t]&&(r[t]=process.env[t]);return{type:"stdio",command:"node",args:[n],env:r,alwaysLoad:!0}},async handleToolCall(n,r={}){try{switch(n){case"sentry_list_projects":{let t=await g();return JSON.stringify({projects:t.map(e=>({slug:e.slug,name:e.name,platform:e.platform}))})}case"sentry_list_issues":{let t=await h({query:r.query,sort:r.sort,project:r.project,limit:r.limit});return JSON.stringify({issues:t.map(e=>({id:e.id,title:e.title,culprit:e.culprit,count:e.count,firstSeen:e.firstSeen,lastSeen:e.lastSeen,level:e.level,status:e.status}))})}case"sentry_get_issue":{let t=await f(r.issueId);return JSON.stringify({id:t.id,title:t.title,culprit:t.culprit,metadata:t.metadata,count:t.count,userCount:t.userCount,firstSeen:t.firstSeen,lastSeen:t.lastSeen,level:t.level,status:t.status,project:{slug:t.project?.slug,name:t.project?.name}})}default:return JSON.stringify({error:`Unknown tool: ${n}`})}}catch(t){return JSON.stringify({error:t.message})}},toolsForAssistant:[{name:"sentry_list_projects",description:"List Sentry projects",input_schema:{type:"object",properties:{}}},{name:"sentry_list_issues",description:"List Sentry issues (errors)",input_schema:{type:"object",properties:{project:{type:"string",description:"Project slug (optional)"},query:{type:"string",description:"Sentry search query (default: is:unresolved)"},sort:{type:"string",description:"Sort order: date, new, priority, freq, user (default: date)"},limit:{type:"number",description:"Max issues to return (default 25)"}}}},{name:"sentry_get_issue",description:"Get details of a specific Sentry issue",input_schema:{type:"object",properties:{issueId:{type:"string",description:"Sentry issue ID"}},required:["issueId"]}}]};a.tools=a.toolsForAssistant;export{p as sentryFetch,f as sentryGetIssue,h as sentryListIssues,g as sentryListProjects,a as sentrySkill};
package/dist/slack.js CHANGED
@@ -1,5 +1,5 @@
1
- import{resolveIntegrationToken as p}from"@zibby/core/backend-client.js";async function n(r,e={}){let{token:s}=await p("slack"),t=["conversations.list","users.list","users.profile.get","conversations.history","conversations.replies"].includes(r),i=`https://slack.com/api/${r}`,c={Authorization:`Bearer ${s}`},o;if(t){let l=new URLSearchParams(e).toString();l&&(i+=`?${l}`)}else c["Content-Type"]="application/json; charset=utf-8",o=JSON.stringify(e);let a=await(await fetch(i,{method:t?"GET":"POST",headers:c,body:o})).json();if(!a.ok)throw new Error(`Slack API error: ${a.error}`);return a}var _={id:"slack",serverName:"slack",allowedTools:["mcp__slack__*"],envKeys:["SLACK_BOT_TOKEN","SLACK_TEAM_ID"],description:"Slack MCP Server",promptFragment:`## Slack (connected)
1
+ import{resolveIntegrationToken as d}from"@zibby/core/backend-client.js";var p=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion"}),_=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",connectPath:"/integrations?provider=lark"},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"}});async function r(i,e={}){let{token:n}=await d("slack"),t=["conversations.list","users.list","users.profile.get","conversations.history","conversations.replies"].includes(i),a=`https://slack.com/api/${i}`,o={Authorization:`Bearer ${n}`},c;if(t){let l=new URLSearchParams(e).toString();l&&(a+=`?${l}`)}else o["Content-Type"]="application/json; charset=utf-8",c=JSON.stringify(e);let s=await(await fetch(a,{method:t?"GET":"POST",headers:o,body:c})).json();if(!s.ok)throw new Error(`Slack API error: ${s.error}`);return s}var y={id:"slack",serverName:"slack",allowedTools:["mcp__slack__*"],requiresIntegration:p.SLACK,envKeys:["SLACK_BOT_TOKEN","SLACK_TEAM_ID"],description:"Slack MCP Server",promptFragment:`## Slack (connected)
2
2
  You have access to the user's Slack workspace. Use these tools:
3
3
  - slack_list_channels, slack_post_message, slack_reply_to_thread
4
4
  - slack_add_reaction, slack_get_channel_history, slack_get_thread_replies
5
- - slack_get_users, slack_get_user_profile`,resolve(){let r={};for(let e of this.envKeys)process.env[e]&&(r[e]=process.env[e]);return{command:"npx",args:["-y","@modelcontextprotocol/server-slack@latest"],env:r}},async handleToolCall(r,e){try{switch(r){case"slack_list_channels":{let s=await n("conversations.list",{types:"public_channel",limit:100});return JSON.stringify({channels:(s.channels||[]).map(t=>({id:t.id,name:t.name,topic:t.topic?.value}))})}case"slack_post_message":{if(!e.channel||!e.text)return JSON.stringify({error:"channel and text are required"});let s=await n("chat.postMessage",{channel:e.channel,text:e.text});return JSON.stringify({ok:!0,ts:s.ts,channel:s.channel})}case"slack_reply_to_thread":{if(!e.channel||!e.thread_ts||!e.text)return JSON.stringify({error:"channel, thread_ts, and text are required"});let s=await n("chat.postMessage",{channel:e.channel,thread_ts:e.thread_ts,text:e.text});return JSON.stringify({ok:!0,ts:s.ts})}case"slack_add_reaction":return!e.channel||!e.timestamp||!e.reaction?JSON.stringify({error:"channel, timestamp, and reaction are required"}):(await n("reactions.add",{channel:e.channel,timestamp:e.timestamp,name:e.reaction}),JSON.stringify({ok:!0}));case"slack_get_channel_history":{if(!e.channel)return JSON.stringify({error:"channel is required"});let s=await n("conversations.history",{channel:e.channel,limit:e.limit||20});return JSON.stringify({messages:(s.messages||[]).map(t=>({user:t.user,text:t.text,ts:t.ts}))})}case"slack_get_thread_replies":{if(!e.channel||!e.thread_ts)return JSON.stringify({error:"channel and thread_ts are required"});let s=await n("conversations.replies",{channel:e.channel,ts:e.thread_ts});return JSON.stringify({messages:(s.messages||[]).map(t=>({user:t.user,text:t.text,ts:t.ts}))})}case"slack_get_users":{let s=await n("users.list",{limit:100});return JSON.stringify({users:(s.members||[]).filter(t=>!t.is_bot&&!t.deleted).map(t=>({id:t.id,name:t.real_name||t.name}))})}case"slack_get_user_profile":{if(!e.user_id)return JSON.stringify({error:"user_id is required"});let s=await n("users.profile.get",{user:e.user_id});return JSON.stringify({profile:s.profile})}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(s){return JSON.stringify({error:s.message})}},tools:[{name:"slack_list_channels",description:"List public channels in the workspace",input_schema:{type:"object",properties:{}}},{name:"slack_post_message",description:"Post a message to a Slack channel or DM",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID or name"},text:{type:"string",description:"Message text"}},required:["channel","text"]}},{name:"slack_reply_to_thread",description:"Reply to a specific message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"},text:{type:"string",description:"Reply text"}},required:["channel","thread_ts","text"]}},{name:"slack_add_reaction",description:"Add an emoji reaction to a message",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},timestamp:{type:"string",description:"Message timestamp"},reaction:{type:"string",description:"Emoji name without colons"}},required:["channel","timestamp","reaction"]}},{name:"slack_get_channel_history",description:"Get recent messages from a channel",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},limit:{type:"number",description:"Number of messages"}},required:["channel"]}},{name:"slack_get_thread_replies",description:"Get all replies in a message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"}},required:["channel","thread_ts"]}},{name:"slack_get_users",description:"List workspace users with basic profiles",input_schema:{type:"object",properties:{}}},{name:"slack_get_user_profile",description:"Get detailed profile for a specific user",input_schema:{type:"object",properties:{user_id:{type:"string",description:"Slack user ID"}},required:["user_id"]}}]};export{_ as slackSkill};
5
+ - slack_get_users, slack_get_user_profile`,resolve(){let i={};for(let e of this.envKeys)process.env[e]&&(i[e]=process.env[e]);return{command:"npx",args:["-y","@modelcontextprotocol/server-slack@latest"],env:i}},async handleToolCall(i,e){try{switch(i){case"slack_list_channels":{let n=await r("conversations.list",{types:"public_channel",limit:100});return JSON.stringify({channels:(n.channels||[]).map(t=>({id:t.id,name:t.name,topic:t.topic?.value}))})}case"slack_post_message":{if(!e.channel||!e.text)return JSON.stringify({error:"channel and text are required"});let n=await r("chat.postMessage",{channel:e.channel,text:e.text});return JSON.stringify({ok:!0,ts:n.ts,channel:n.channel})}case"slack_reply_to_thread":{if(!e.channel||!e.thread_ts||!e.text)return JSON.stringify({error:"channel, thread_ts, and text are required"});let n=await r("chat.postMessage",{channel:e.channel,thread_ts:e.thread_ts,text:e.text});return JSON.stringify({ok:!0,ts:n.ts})}case"slack_add_reaction":return!e.channel||!e.timestamp||!e.reaction?JSON.stringify({error:"channel, timestamp, and reaction are required"}):(await r("reactions.add",{channel:e.channel,timestamp:e.timestamp,name:e.reaction}),JSON.stringify({ok:!0}));case"slack_get_channel_history":{if(!e.channel)return JSON.stringify({error:"channel is required"});let n=await r("conversations.history",{channel:e.channel,limit:e.limit||20});return JSON.stringify({messages:(n.messages||[]).map(t=>({user:t.user,text:t.text,ts:t.ts}))})}case"slack_get_thread_replies":{if(!e.channel||!e.thread_ts)return JSON.stringify({error:"channel and thread_ts are required"});let n=await r("conversations.replies",{channel:e.channel,ts:e.thread_ts});return JSON.stringify({messages:(n.messages||[]).map(t=>({user:t.user,text:t.text,ts:t.ts}))})}case"slack_get_users":{let n=await r("users.list",{limit:100});return JSON.stringify({users:(n.members||[]).filter(t=>!t.is_bot&&!t.deleted).map(t=>({id:t.id,name:t.real_name||t.name}))})}case"slack_get_user_profile":{if(!e.user_id)return JSON.stringify({error:"user_id is required"});let n=await r("users.profile.get",{user:e.user_id});return JSON.stringify({profile:n.profile})}default:return JSON.stringify({error:`Unknown tool: ${i}`})}}catch(n){return JSON.stringify({error:n.message})}},tools:[{name:"slack_list_channels",description:"List public channels in the workspace",input_schema:{type:"object",properties:{}}},{name:"slack_post_message",description:"Post a message to a Slack channel or DM",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID or name"},text:{type:"string",description:"Message text"}},required:["channel","text"]}},{name:"slack_reply_to_thread",description:"Reply to a specific message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"},text:{type:"string",description:"Reply text"}},required:["channel","thread_ts","text"]}},{name:"slack_add_reaction",description:"Add an emoji reaction to a message",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},timestamp:{type:"string",description:"Message timestamp"},reaction:{type:"string",description:"Emoji name without colons"}},required:["channel","timestamp","reaction"]}},{name:"slack_get_channel_history",description:"Get recent messages from a channel",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},limit:{type:"number",description:"Number of messages"}},required:["channel"]}},{name:"slack_get_thread_replies",description:"Get all replies in a message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"}},required:["channel","thread_ts"]}},{name:"slack_get_users",description:"List workspace users with basic profiles",input_schema:{type:"object",properties:{}}},{name:"slack_get_user_profile",description:"Get detailed profile for a specific user",input_schema:{type:"object",properties:{user_id:{type:"string",description:"Slack user ID"}},required:["user_id"]}}]};export{y as slackSkill};
@@ -7,6 +7,8 @@ title: Skills
7
7
 
8
8
  A **skill** is a named bundle of MCP tools (and optional prompt fragments) that a node can opt into. Skills let you compose tool access per-node without giving every node every tool.
9
9
 
10
+ See also: [Skills reference](../skills/index.md) for per-skill docs (tools, setup, code samples).
11
+
10
12
  ## Built-in skills
11
13
 
12
14
  `@zibby/skills` ships these:
@@ -17,7 +17,7 @@ Verify:
17
17
  zibby --version
18
18
  ```
19
19
 
20
- You should see `zibby v0.1.x`.
20
+ You should see something like `zibby v0.4.x`.
21
21
 
22
22
  ## No global install? Use npx
23
23
 
@@ -50,7 +50,7 @@ zibby workflow logs 2b1ea07f-3ede-4bfd-a51d-431f0bab008e -t
50
50
  Streaming logs for workflow 2b1ea07f-3ede-4bfd-a51d-431f0bab008e...
51
51
  Press Ctrl+C to stop.
52
52
 
53
- 2026-05-02 23:30:51.345 zibby v0.1.x
53
+ 2026-05-02 23:30:51.345 zibby v0.4.x
54
54
  ────────────────────────────────────────────────────────────
55
55
  Workflow: my-pipeline
56
56
  Job: ee333411-...
@@ -0,0 +1,153 @@
1
+ ---
2
+ sidebar_position: 6
3
+ title: 6. Use from your AI agent
4
+ pagination_prev: get-started/trigger-and-logs
5
+ ---
6
+
7
+ # Use Zibby from Claude Code, Cursor, Codex, or Gemini
8
+
9
+ Zibby ships an MCP (Model Context Protocol) server — [`@zibby/mcp-cli`](../packages/mcp-cli). Add it once to your agent's config and Zibby becomes a first-class tool the agent can call directly from chat. Deploy workflows, trigger runs, tail logs — all without leaving your editor.
10
+
11
+ ```
12
+ You: Deploy the browser-test template to my Playhouse project, then run it.
13
+ Agent: → zibby_scaffold_workflow
14
+ → zibby_deploy_workflow
15
+ → zibby_trigger_workflow
16
+ → "Done. Job a23e… completed in 47s, 0 failures."
17
+ ```
18
+
19
+ The MCP server runs **locally** as a subprocess of your agent — no Zibby-hosted endpoint, no credentials shared with us, no proxy in the middle. Your existing `~/.zibby/config.json` session is reused if you've already run `zibby login`.
20
+
21
+ ## Install
22
+
23
+ Pick your agent. All flavors run the same npm package via `npx -y` — no global install.
24
+
25
+ ### Claude Code
26
+
27
+ Add to `~/.claude/settings.json`:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "zibby": {
33
+ "command": "npx",
34
+ "args": ["-y", "@zibby/mcp-cli"]
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### Cursor
41
+
42
+ Add to `~/.cursor/mcp.json`:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "zibby": {
48
+ "command": "npx",
49
+ "args": ["-y", "@zibby/mcp-cli"]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### OpenAI Codex CLI
56
+
57
+ Add to `~/.codex/config.toml`:
58
+
59
+ ```toml
60
+ [mcp_servers.zibby]
61
+ command = "npx"
62
+ args = ["-y", "@zibby/mcp-cli"]
63
+ ```
64
+
65
+ ### Gemini CLI
66
+
67
+ Add to `~/.gemini/settings.json`:
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "zibby": {
73
+ "command": "npx",
74
+ "args": ["-y", "@zibby/mcp-cli"]
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### Windows note
81
+
82
+ If your agent on Windows can't find `npx`, wrap with `cmd /c`:
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "zibby": {
88
+ "command": "cmd",
89
+ "args": ["/c", "npx", "-y", "@zibby/mcp-cli"]
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## First-time login
96
+
97
+ If you haven't run `zibby login` yet in a terminal, just tell the agent:
98
+
99
+ ```
100
+ You: Log in to Zibby.
101
+ Agent: → zibby_login
102
+ (browser opens to the Zibby login page)
103
+ → "Logged in as you@yourcompany.com (3 projects cached)."
104
+ ```
105
+
106
+ Login state lives in `~/.zibby/config.json` (mode `0600`) — the same file the CLI writes. Subsequent agent sessions reuse it.
107
+
108
+ ## What the agent can do
109
+
110
+ | Read-only | Write |
111
+ |---|---|
112
+ | List projects | Scaffold a workflow from an official template |
113
+ | List official templates | Deploy a workflow to a project |
114
+ | List workflows (local + remote) | Trigger a deployed workflow |
115
+ | Show login status | Run a workflow locally (debug) |
116
+ | Fetch logs from a run | Download a deployed workflow (with explicit user confirmation) |
117
+ | Static-validate a workflow | |
118
+
119
+ **Destructive operations stay out of the agent's hands** — workflow delete, env-var changes, schedule mutation, and credential management require you to run `zibby` in a terminal. Two-stage authorization for anything that could surprise you.
120
+
121
+ See the [`@zibby/mcp-cli` package reference](../packages/mcp-cli) for the full tool list and security model.
122
+
123
+ ## Example session
124
+
125
+ ```
126
+ You: What templates are available?
127
+
128
+ Agent: → zibby_list_templates
129
+ "Three official templates:
130
+ - browser-test-automation: Playwright preflight + live execution + script gen
131
+ - code-analysis: Jira ticket → analyze → generate code → emit tests
132
+ - generate-test-cases: code diff → prioritized AI-runnable test specs"
133
+
134
+ You: Scaffold generate-test-cases into a new workflow called "release-tests" in my Playhouse project.
135
+
136
+ Agent: → zibby_list_projects (resolves "Playhouse" → projectId)
137
+ → zibby_scaffold_workflow name=release-tests template=generate-test-cases
138
+ → zibby_validate_workflow name=release-tests
139
+ "Scaffolded and validated. Ready to deploy."
140
+
141
+ You: Deploy it.
142
+
143
+ Agent: → zibby_deploy_workflow name=release-tests projectId=…
144
+ "Deployed v1 — UUID 33039db5-2f32-4094-9841-317bc56a88c9."
145
+
146
+ You: Run it against this PR diff: ‹pastes diff›
147
+
148
+ Agent: → zibby_trigger_workflow uuid=33039db5… input={diff: "...", changedFiles: [...]}
149
+ → zibby_workflow_logs jobId=c62e97a4… lines=100
150
+ "Run completed in 1m 23s. Generated 7 test specs (4 Critical, 2 High, 1 Medium)."
151
+ ```
152
+
153
+ → Full tool reference: [`@zibby/mcp-cli`](../packages/mcp-cli)
package/docs/intro.md CHANGED
@@ -56,6 +56,7 @@ zibby template add <name> # add a template later (overwrites =
56
56
  - **Run anywhere** — local with hot reload, or cloud with Heroku-style bundles (~3s cold start).
57
57
  - **Session replay** — every run lands as on-disk JSONL + artifacts. Re-run any node via `--session <id> --node <name>`.
58
58
  - **Cloud-native** — SSE log streaming, dedicated egress IPs for firewalled GitLab / GitHub Enterprise / Salesforce.
59
+ - **Drive it from your AI agent** — [`@zibby/mcp-cli`](./packages/mcp-cli) exposes deploy / trigger / logs / debug as MCP tools. Add one snippet to Claude Code, Cursor, Codex, or Gemini and they call Zibby directly from chat. See [Use from your AI agent](./get-started/use-from-agents).
59
60
 
60
61
  ## How it compares
61
62
 
@@ -107,6 +107,5 @@ To upload results to the [Zibby dashboard](https://zibby.app):
107
107
 
108
108
  ## Next Steps
109
109
 
110
- - [Installation](/installation) — detailed setup and configuration
111
- - [Running Tests](/running-tests) — all run modes and options
110
+ - [Install](/get-started/install) — detailed setup and configuration
112
111
  - [CLI Reference](/cli-reference) — every command and flag