@zibby/skills 0.1.25 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mcp-lark.mjs +112 -1
- package/bin/mcp-sentry.mjs +15 -36
- package/bin/mcp-slack.mjs +404 -0
- package/dist/chat-notify.js +18 -0
- package/dist/github.js +3 -3
- package/dist/index.js +105 -88
- package/dist/integrations.js +1 -1
- package/dist/jira.js +12 -12
- package/dist/lark.js +3 -2
- package/dist/llm-billing.js +1 -0
- package/dist/package.json +12 -5
- package/dist/report.js +12 -0
- package/dist/sentry.js +2 -2
- package/dist/slack.js +4 -2
- package/docs/get-started/install.md +1 -1
- package/docs/get-started/trigger-and-logs.md +1 -1
- package/docs/get-started/use-from-agents.md +153 -0
- package/docs/intro.md +1 -0
- package/docs/legacy/test-automation.md +1 -2
- package/docs/packages/mcp-cli.md +176 -0
- package/docs/recipes/test.md +1 -1
- package/package.json +12 -5
package/dist/integrations.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
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 T}from"module";import{resolveIntegrationToken as $,clearTokenCache as q}from"@zibby/core/backend-client.js";var
|
|
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
|
|
5
|
-
${
|
|
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
|
|
8
|
-
${
|
|
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
|
|
10
|
+
`)}else if(t.type==="heading"){let i=t.attrs?.level||2;e.push(`
|
|
11
11
|
|
|
12
|
-
${"#".repeat(
|
|
12
|
+
${"#".repeat(i)} ${r.trim()}
|
|
13
13
|
|
|
14
|
-
`)}else
|
|
14
|
+
`)}else P.has(t.type)?e.push(`
|
|
15
15
|
|
|
16
|
-
${
|
|
17
|
-
`):e.push(
|
|
16
|
+
${r}
|
|
17
|
+
`):e.push(r)}return e.join("").replace(/\n{3,}/g,`
|
|
18
18
|
|
|
19
|
-
`)}function k(c){return String(c||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function
|
|
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=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(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 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 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 A(e),l=L(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 R(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=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: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=k(s),g=d.find(l=>k(l?.name||"")===y||k(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{F 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,8 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as y}from"fs";import{fileURLToPath as f}from"url";import{dirname as k,resolve as b}from"path";import{resolveIntegrationToken as v}from"@zibby/core/backend-client.js";var h=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"}),I=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 S(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let i=k(f(import.meta.url)),e=b(i,"..","bin","mcp-lark.mjs");return y(e)?e:null}var T=6e3*1e3,p=null;async function O(){let{appId:i,appSecret:e,host:t}=await v("lark");if(p&&p.appId===i&&p.expiresAt>Date.now())return{token:p.token,host:t};let n=await(await fetch(`${t}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:i,app_secret:e})})).json();if(n.code!==0)throw new Error(`Lark tenant_access_token failed: ${n.msg||n.code}`);return p={token:n.tenant_access_token,expiresAt:Date.now()+T,appId:i},{token:n.tenant_access_token,host:t}}async function _(i,e,t={}){let{token:a,host:n}=await O(),r=`${n}${e}`,m={method:i,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json; charset=utf-8"}};i!=="GET"&&(m.body=JSON.stringify(t));let c=await(await fetch(r,m)).json();if(c.code!==0)throw new Error(`Lark API ${e} error: ${c.msg||c.code}`);return c.data||{}}function g(i){return JSON.stringify({text:i})}function N(i){return!i||typeof i!="string"||i.startsWith("oc_")?"chat_id":i.startsWith("ou_")?"open_id":i.startsWith("on_")?"union_id":i.startsWith("cli_")?"app_id":i.includes("@")?"email":"chat_id"}var E={id:"lark",serverName:"lark",allowedTools:["mcp__lark__*"],requiresIntegration:h.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
|
-
|
|
7
|
+
- lark_lookup_user_by_email: resolve an email \u2192 open_id for direct DM (prefer this over emailing through lark_send_message when the agent has a user_id already)
|
|
8
|
+
When responding to an incoming event, prefer lark_reply with the source message_id so the response threads cleanly.`,resolve(){let i=S();if(!i)return null;let e={};for(let t of["PROJECT_API_TOKEN","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[t]&&(e[t]=process.env[t]);return{type:"stdio",command:"node",args:[i],env:e,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"]}},{name:"lark_lookup_user_by_email",description:"Resolve an email address to a Lark user id (open_id). Returns { ok:true, user:{open_id,email,name} } on hit, { ok:false } if no Lark user has that email. Use the open_id as `receive_id` in lark_send_message to DM.",input_schema:{type:"object",properties:{email:{type:"string",description:"Email address to look up"}},required:["email"]}},{name:"lark_search_users",description:'Fuzzy-search users by name across chats the bot is a member of. Lark has no public org-wide user search API for bots \u2014 this walks the bot\'s chat memberships and matches names client-side. Best for "send to Sam" style routing where you have a name but no email. Returns up to `limit` ranked matches { open_id, name }.',input_schema:{type:"object",properties:{query:{type:"string",description:"Substring to match against user names (case-insensitive)"},limit:{type:"number",description:"Max matches to return (default 5, max 25)"}},required:["query"]}}],async handleToolCall(i,e){try{switch(i){case"lark_send_message":{if(!e.receive_id||!e.text)return JSON.stringify({error:"receive_id and text are required"});let t=N(e.receive_id),a=await _("POST",`/open-apis/im/v1/messages?receive_id_type=${t}`,{receive_id:e.receive_id,msg_type:"text",content:g(e.text)});return JSON.stringify({ok:!0,message_id:a.message_id})}case"lark_reply":{if(!e.message_id||!e.text)return JSON.stringify({error:"message_id and text are required"});let t=await _("POST",`/open-apis/im/v1/messages/${encodeURIComponent(e.message_id)}/reply`,{msg_type:"text",content:g(e.text)});return JSON.stringify({ok:!0,message_id:t.message_id})}case"lark_list_chats":{let t=e.page_size||50,n=((await _("GET",`/open-apis/im/v1/chats?page_size=${t}`)).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(!e.chat_id)return JSON.stringify({error:"chat_id is required"});let t=e.page_size||20,n=((await _("GET",`/open-apis/im/v1/messages?container_id_type=chat&container_id=${encodeURIComponent(e.chat_id)}&page_size=${t}&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})}case"lark_lookup_user_by_email":{if(!e.email)return JSON.stringify({error:"email is required"});let a=((await _("POST","/open-apis/contact/v3/users/batch_get_id?user_id_type=open_id",{emails:[e.email]})).user_list||[]).find(n=>n.email===e.email&&n.user_id);return JSON.stringify(a?{ok:!0,user:{open_id:a.user_id,email:a.email,name:a.name||void 0}}:{ok:!1,reason:"no_lark_user_for_email"})}case"lark_search_users":{if(!e.query||typeof e.query!="string")return JSON.stringify({error:"query is required"});let t=e.query.trim().toLowerCase();if(!t)return JSON.stringify({ok:!0,matches:[]});let a=Math.max(1,Math.min(Number(e.limit)||5,25)),n=200,m=((await _("GET","/open-apis/im/v1/chats?page_size=100")).items||[]).map(o=>o.chat_id),l=new Set,c=[];for(let o of m){if(c.length>=n)break;try{let s=await _("GET",`/open-apis/im/v1/chats/${encodeURIComponent(o)}/members?member_id_type=open_id&page_size=100`);for(let d of s.items||[])if(!(!d.member_id||l.has(d.member_id))&&(l.add(d.member_id),c.push({open_id:d.member_id,name:d.name||""}),c.length>=n))break}catch(s){console.warn(`[lark] member scan failed for ${o}: ${s.message}`)}}let u=[];for(let o of c){let s=(o.name||"").toLowerCase();if(!s)continue;let d=0;s.includes(t)&&(d+=100-Math.abs(s.length-t.length)),s===t&&(d+=200),d>0&&u.push({open_id:o.open_id,name:o.name,_score:d})}return u.sort((o,s)=>s._score-o._score),JSON.stringify({ok:!0,matches:u.slice(0,a).map(({_score:o,...s})=>s),scanned:c.length})}default:return JSON.stringify({error:`Unknown tool: ${i}`})}}catch(t){return JSON.stringify({error:t.message})}}};function C(){p=null}export{C as _resetLarkTokenCache,E 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.
|
|
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",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
".": "./dist/index.js",
|
|
9
9
|
"./bin/mcp-sentry.mjs": "./bin/mcp-sentry.mjs",
|
|
10
10
|
"./bin/mcp-lark.mjs": "./bin/mcp-lark.mjs",
|
|
11
|
+
"./bin/mcp-slack.mjs": "./bin/mcp-slack.mjs",
|
|
11
12
|
"./browser": "./dist/browser.js",
|
|
12
13
|
"./jira": "./dist/jira.js",
|
|
13
14
|
"./github": "./dist/github.js",
|
|
@@ -15,7 +16,10 @@
|
|
|
15
16
|
"./lark": "./dist/lark.js",
|
|
16
17
|
"./memory": "./dist/memory.js",
|
|
17
18
|
"./function": "./dist/function-skill.js",
|
|
18
|
-
"./integrations": "./dist/integrations.js"
|
|
19
|
+
"./integrations": "./dist/integrations.js",
|
|
20
|
+
"./report": "./dist/report.js",
|
|
21
|
+
"./llm-billing": "./dist/llm-billing.js",
|
|
22
|
+
"./sentry": "./dist/sentry.js"
|
|
19
23
|
},
|
|
20
24
|
"scripts": {
|
|
21
25
|
"build": "node ../scripts/build.mjs",
|
|
@@ -35,10 +39,10 @@
|
|
|
35
39
|
"homepage": "https://zibby.dev",
|
|
36
40
|
"repository": {
|
|
37
41
|
"type": "git",
|
|
38
|
-
"url": "https://github.com/ZibbyHQ/
|
|
42
|
+
"url": "https://github.com/ZibbyHQ/skills"
|
|
39
43
|
},
|
|
40
44
|
"bugs": {
|
|
41
|
-
"url": "https://github.com/ZibbyHQ/
|
|
45
|
+
"url": "https://github.com/ZibbyHQ/skills/issues"
|
|
42
46
|
},
|
|
43
47
|
"files": [
|
|
44
48
|
"dist/",
|
|
@@ -63,6 +67,9 @@
|
|
|
63
67
|
"@zibby/mcp-memory": "*"
|
|
64
68
|
},
|
|
65
69
|
"devDependencies": {
|
|
66
|
-
"
|
|
70
|
+
"@eslint/js": "^10.0.1",
|
|
71
|
+
"esbuild": "^0.28.0",
|
|
72
|
+
"eslint": "^10.0.2",
|
|
73
|
+
"globals": "^17.4.0"
|
|
67
74
|
}
|
|
68
75
|
}
|
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
|
|
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
|
|
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,7 @@
|
|
|
1
|
-
import{resolveIntegrationToken as
|
|
1
|
+
import{existsSync as h}from"fs";import{fileURLToPath as g}from"url";import{dirname as f,resolve as y}from"path";import{resolveIntegrationToken as k}from"@zibby/core/backend-client.js";var _=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 b(){if(process.env.MCP_SLACK_PATH)return process.env.MCP_SLACK_PATH;let a=f(g(import.meta.url)),t=y(a,"..","bin","mcp-slack.mjs");return h(t)?t:null}async function n(a,t={}){let{token:e}=await k("slack"),r=["conversations.list","users.list","users.profile.get","users.lookupByEmail","usergroups.list","usergroups.users.list","conversations.history","conversations.replies"].includes(a),c=`https://slack.com/api/${a}`,o={Authorization:`Bearer ${e}`},m;if(r){let i=new URLSearchParams(t).toString();i&&(c+=`?${i}`)}else o["Content-Type"]="application/json; charset=utf-8",m=JSON.stringify(t);let s=await(await fetch(c,{method:r?"GET":"POST",headers:o,body:m})).json();if(!s.ok)throw new Error(`Slack API error: ${s.error}`);return s}var P={id:"slack",serverName:"slack",allowedTools:["mcp__slack__*"],requiresIntegration:_.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
|
|
5
|
+
- slack_get_users, slack_get_user_profile
|
|
6
|
+
- slack_lookup_user_by_email (precise email\u2192user_id, prefer this over scanning slack_get_users)
|
|
7
|
+
- slack_list_usergroups, slack_get_usergroup_members (workspace-defined teams like @oncall, @platform)`,resolve(){let a=b();if(!a)return null;let t={};for(let e of["PROJECT_API_TOKEN","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[e]&&(t[e]=process.env[e]);for(let e of this.envKeys)process.env[e]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[a],env:t,alwaysLoad:!0}},async handleToolCall(a,t){try{switch(a){case"slack_list_channels":{let e=await n("conversations.list",{types:"public_channel",limit:100});return JSON.stringify({channels:(e.channels||[]).map(r=>({id:r.id,name:r.name,topic:r.topic?.value}))})}case"slack_post_message":{if(!t.channel||!t.text)return JSON.stringify({error:"channel and text are required"});let e=await n("chat.postMessage",{channel:t.channel,text:t.text});return JSON.stringify({ok:!0,ts:e.ts,channel:e.channel})}case"slack_reply_to_thread":{if(!t.channel||!t.thread_ts||!t.text)return JSON.stringify({error:"channel, thread_ts, and text are required"});let e=await n("chat.postMessage",{channel:t.channel,thread_ts:t.thread_ts,text:t.text});return JSON.stringify({ok:!0,ts:e.ts})}case"slack_add_reaction":return!t.channel||!t.timestamp||!t.reaction?JSON.stringify({error:"channel, timestamp, and reaction are required"}):(await n("reactions.add",{channel:t.channel,timestamp:t.timestamp,name:t.reaction}),JSON.stringify({ok:!0}));case"slack_get_channel_history":{if(!t.channel)return JSON.stringify({error:"channel is required"});let e=await n("conversations.history",{channel:t.channel,limit:t.limit||20});return JSON.stringify({messages:(e.messages||[]).map(r=>({user:r.user,text:r.text,ts:r.ts}))})}case"slack_get_thread_replies":{if(!t.channel||!t.thread_ts)return JSON.stringify({error:"channel and thread_ts are required"});let e=await n("conversations.replies",{channel:t.channel,ts:t.thread_ts});return JSON.stringify({messages:(e.messages||[]).map(r=>({user:r.user,text:r.text,ts:r.ts}))})}case"slack_get_users":{let e=await n("users.list",{limit:100});return JSON.stringify({users:(e.members||[]).filter(r=>!r.is_bot&&!r.deleted).map(r=>({id:r.id,name:r.real_name||r.name}))})}case"slack_get_user_profile":{if(!t.user_id)return JSON.stringify({error:"user_id is required"});let e=await n("users.profile.get",{user:t.user_id});return JSON.stringify({profile:e.profile})}case"slack_lookup_user_by_email":{if(!t.email)return JSON.stringify({error:"email is required"});try{let e=await n("users.lookupByEmail",{email:t.email});return JSON.stringify({ok:!0,user:{id:e.user?.id,name:e.user?.real_name||e.user?.name,email:e.user?.profile?.email||t.email}})}catch(e){if(/users_not_found/.test(e.message))return JSON.stringify({ok:!1,reason:"users_not_found"});throw e}}case"slack_list_usergroups":{let e=await n("usergroups.list",{});return JSON.stringify({usergroups:(e.usergroups||[]).map(r=>({id:r.id,handle:r.handle,name:r.name,description:r.description||"",user_count:Number(r.user_count||0)}))})}case"slack_get_usergroup_members":{if(!t.usergroup)return JSON.stringify({error:"usergroup id is required"});let e=await n("usergroups.users.list",{usergroup:t.usergroup});return JSON.stringify({users:e.users||[]})}case"slack_search_users":{if(!t.query||typeof t.query!="string")return JSON.stringify({error:"query is required"});let e=t.query.trim().toLowerCase();if(!e)return JSON.stringify({ok:!0,matches:[]});let r=Math.max(1,Math.min(Number(t.limit)||5,25)),c=[],o,m=5;for(let s=0;s<m;s+=1){let i={limit:200};o&&(i.cursor=o);let l=await n("users.list",i);for(let p of l.members||[])p.deleted||p.is_bot||c.push(p);if(o=l.response_metadata?.next_cursor,!o)break}let d=[];for(let s of c){let i=(s.real_name||"").toLowerCase(),l=(s.profile?.display_name||"").toLowerCase(),p=(s.name||"").toLowerCase(),u=0;i.includes(e)&&(u+=100-Math.abs(i.length-e.length)),l.includes(e)&&(u+=60-Math.abs(l.length-e.length)),p.includes(e)&&(u+=30-Math.abs(p.length-e.length)),(i===e||l===e)&&(u+=200),u>0&&d.push({id:s.id,name:s.real_name||s.profile?.display_name||s.name,email:s.profile?.email||void 0,_score:u})}return d.sort((s,i)=>i._score-s._score),JSON.stringify({ok:!0,matches:d.slice(0,r).map(({_score:s,...i})=>i),scanned:c.length})}default:return JSON.stringify({error:`Unknown tool: ${a}`})}}catch(e){return JSON.stringify({error:e.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"]}},{name:"slack_lookup_user_by_email",description:"Find a Slack user by email. Returns { ok:true, user:{id,name,email} } on hit, { ok:false } when no user has that email. Prefer this over slack_get_users for email-based routing \u2014 single API call, exact match.",input_schema:{type:"object",properties:{email:{type:"string",description:"Email address to look up"}},required:["email"]}},{name:"slack_list_usergroups",description:"List workspace-defined user groups (e.g. @oncall, @platform). Each item has { id, handle, name, description, user_count }. Use the id with slack_get_usergroup_members to expand the membership.",input_schema:{type:"object",properties:{}}},{name:"slack_get_usergroup_members",description:"List user IDs that belong to a Slack usergroup. Pair with slack_post_message to DM each member, or use the group id directly in a channel message as <!subteam^ID> to @-mention.",input_schema:{type:"object",properties:{usergroup:{type:"string",description:"Usergroup id, e.g. S012ABC"}},required:["usergroup"]}},{name:"slack_search_users",description:'Fuzzy-search workspace users by display name or real name. Use when the user said something like "send to Sam" without an email. Returns up to `limit` ranked matches { id, name, email }. Slack has no native name-search API \u2014 this scans paginated users.list + does substring scoring (real_name > display_name > name). For large workspaces consider higher limit + ask the user to confirm if multiple hit.',input_schema:{type:"object",properties:{query:{type:"string",description:"Substring to match against names (case-insensitive)"},limit:{type:"number",description:"Max matches to return (default 5, max 25)"}},required:["query"]}}]};export{P as slackSkill};
|
|
@@ -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.
|
|
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
|
-
- [
|
|
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
|