@zibby/skills 2.0.7 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/jira.js CHANGED
@@ -1,8 +1,8 @@
1
- import{createRequire as we}from"module";import{resolveIntegrationToken as xe,clearTokenCache as Ne}from"@zibby/core/backend-client.js";var G=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",LARK_DOCS:"lark_docs",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",VIKUNJA:"vikunja",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),Ke=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark Chat",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark App",connectPath:"/integrations?provider=lark_docs"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},vikunja:{id:"vikunja",name:"Vikunja",connectPath:"/integrations?provider=vikunja"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});var R={api:{knob:"SKILL_API_TIMEOUT_MS",fallback:3e4},transfer:{knob:"SKILL_TRANSFER_TIMEOUT_MS",fallback:12e4},job:{knob:"SKILL_JOB_TIMEOUT_MS",fallback:3e5}};function oe(s,n,t=process.env){let e=Number(t?.[s]);return Number.isFinite(e)&&e>0?Math.min(6e5,Math.max(1e3,Math.floor(e))):n}function ae(s="api",n=process.env){let t=R[s]||R.api;return oe(t.knob,t.fallback,n)}function P(s){return s?.name==="TimeoutError"||s?.name==="AbortError"}function ce(s){try{return new URL(String(s?.url??s)).host||"unknown host"}catch{return"unknown host"}}function le(s,n){if(!s)return n;let t=new AbortController,e=a=>{t.signal.aborted||t.abort(a)},i=()=>e(s.reason),r=()=>e(n.reason);return t.signal.addEventListener("abort",()=>{s.removeEventListener("abort",i),n.removeEventListener("abort",r)},{once:!0}),s.aborted?e(s.reason):n.aborted?e(n.reason):(s.addEventListener("abort",i,{once:!0}),n.addEventListener("abort",r,{once:!0})),t.signal}async function F(s,n={},t={}){let e=t.kind||"api",i=(R[e]||R.api).knob,r=t.timeoutMs?Math.min(6e5,Math.max(1e3,Math.floor(t.timeoutMs))):ae(e),a=n?.signal;if(a?.aborted)throw a.reason??new DOMException("This operation was aborted","AbortError");let o=AbortSignal.timeout(r),c=le(a,o);try{return await fetch(s,{...n,signal:c})}catch(l){throw o.aborted&&P(l)?new Error(`${t.what||"request"} TIMED OUT after ${r}ms against ${ce(s)} (${i})`):l}}var ue={NOTE:"info",TIP:"success",IMPORTANT:"note",WARNING:"warning",CAUTION:"error"},V={info:"NOTE",success:"TIP",note:"IMPORTANT",warning:"WARNING",error:"CAUTION"},pe=2e5,ee=6,de=/^```([A-Za-z0-9_+.-]{0,32})\s*$/,fe=/^(#{1,6})[ \t]+(.*?)\s*$/,me=/^(?:-{3,}|\*{3,}|_{3,})\s*$/,W=/^>(?: ?(.*))?$/,q=/^[-*][ \t]+\[([ xX])\][ \t]+(.*)$/,z=/^[-*][ \t]+(.*)$/,H=/^(\d{1,3})[.)][ \t]+(.*)$/,Y=/^\|.*\|\s*$/,ge=/^\|?\s*:?-{1,}:?\s*(?:\|\s*:?-{1,}:?\s*)*\|?\s*$/,he=/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i;function Q(s){let n=s.trim().replace(/^\|/,"").replace(/\|$/,""),t=[],e="";for(let i=0;i<n.length;i++){let r=n[i];if(r==="\\"&&n[i+1]==="|"){e+="|",i++;continue}if(r==="|"){t.push(e.trim()),e="";continue}e+=r}return t.push(e.trim()),t}function te(s){let t=String(s??"").slice(0,pe).replace(/\r\n?/g,`
1
+ import{existsSync as we}from"fs";import{dirname as xe,resolve as Ne}from"path";import{fileURLToPath as Oe}from"url";import{resolveIntegrationToken as Re,clearTokenCache as $e}from"@zibby/core/backend-client.js";var G=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",LARK_DOCS:"lark_docs",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",VIKUNJA:"vikunja",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),Be=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark Chat",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark App",connectPath:"/integrations?provider=lark_docs"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},vikunja:{id:"vikunja",name:"Vikunja",connectPath:"/integrations?provider=vikunja"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});var R={api:{knob:"SKILL_API_TIMEOUT_MS",fallback:3e4},transfer:{knob:"SKILL_TRANSFER_TIMEOUT_MS",fallback:12e4},job:{knob:"SKILL_JOB_TIMEOUT_MS",fallback:3e5}};function oe(s,n,t=process.env){let e=Number(t?.[s]);return Number.isFinite(e)&&e>0?Math.min(6e5,Math.max(1e3,Math.floor(e))):n}function ae(s="api",n=process.env){let t=R[s]||R.api;return oe(t.knob,t.fallback,n)}function P(s){return s?.name==="TimeoutError"||s?.name==="AbortError"}function ce(s){try{return new URL(String(s?.url??s)).host||"unknown host"}catch{return"unknown host"}}function le(s,n){if(!s)return n;let t=new AbortController,e=a=>{t.signal.aborted||t.abort(a)},i=()=>e(s.reason),r=()=>e(n.reason);return t.signal.addEventListener("abort",()=>{s.removeEventListener("abort",i),n.removeEventListener("abort",r)},{once:!0}),s.aborted?e(s.reason):n.aborted?e(n.reason):(s.addEventListener("abort",i,{once:!0}),n.addEventListener("abort",r,{once:!0})),t.signal}async function F(s,n={},t={}){let e=t.kind||"api",i=(R[e]||R.api).knob,r=t.timeoutMs?Math.min(6e5,Math.max(1e3,Math.floor(t.timeoutMs))):ae(e),a=n?.signal;if(a?.aborted)throw a.reason??new DOMException("This operation was aborted","AbortError");let o=AbortSignal.timeout(r),c=le(a,o);try{return await fetch(s,{...n,signal:c})}catch(l){throw o.aborted&&P(l)?new Error(`${t.what||"request"} TIMED OUT after ${r}ms against ${ce(s)} (${i})`):l}}var ue={NOTE:"info",TIP:"success",IMPORTANT:"note",WARNING:"warning",CAUTION:"error"},V={info:"NOTE",success:"TIP",note:"IMPORTANT",warning:"WARNING",error:"CAUTION"},pe=2e5,ee=6,de=/^```([A-Za-z0-9_+.-]{0,32})\s*$/,me=/^(#{1,6})[ \t]+(.*?)\s*$/,fe=/^(?:-{3,}|\*{3,}|_{3,})\s*$/,W=/^>(?: ?(.*))?$/,q=/^[-*][ \t]+\[([ xX])\][ \t]+(.*)$/,z=/^[-*][ \t]+(.*)$/,H=/^(\d{1,3})[.)][ \t]+(.*)$/,Y=/^\|.*\|\s*$/,he=/^\|?\s*:?-{1,}:?\s*(?:\|\s*:?-{1,}:?\s*)*\|?\s*$/,ge=/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i;function Q(s){let n=s.trim().replace(/^\|/,"").replace(/\|$/,""),t=[],e="";for(let i=0;i<n.length;i++){let r=n[i];if(r==="\\"&&n[i+1]==="|"){e+="|",i++;continue}if(r==="|"){t.push(e.trim()),e="";continue}e+=r}return t.push(e.trim()),t}function te(s){let t=String(s??"").slice(0,pe).replace(/\r\n?/g,`
2
2
  `).split(`
3
- `);return J(t,0)}function J(s,n){let t=[],e=0;for(;e<s.length;){let i=s[e];if(i.trim()===""){e++;continue}if(/^[ \t]/.test(i)){t.push({t:"paragraph",c:b(i)}),e++;continue}let r;if(r=de.exec(i)){let a=r[1]||"",o=[];for(e++;e<s.length&&!/^```\s*$/.test(s[e]);)o.push(s[e]),e++;e++,t.push({t:"code",lang:a,v:o.join(`
4
- `)});continue}if(me.test(i)){t.push({t:"rule"}),e++;continue}if(r=fe.exec(i)){let a=Math.min(ee,r[1].length);t.push({t:"heading",level:a,c:b(r[2])}),e++;continue}if(W.test(i)&&n<3){let a=[];for(;e<s.length&&(r=W.exec(s[e]));)a.push(r[1]??""),e++;let o=a.findIndex(l=>l.trim()!==""),c=o>=0?he.exec(a[o].trim()):null;if(c){let l=ue[c[1].toUpperCase()];t.push({t:"panel",kind:l,c:J(a.slice(o+1),n+1)})}else t.push({t:"quote",c:J(a,n+1)});continue}if(q.test(i)){let a=[];for(;e<s.length&&(r=q.exec(s[e]));)a.push({checked:r[1]!==" ",c:b(r[2])}),e++;t.push({t:"task",items:a});continue}if(z.test(i)){let a=[];for(;e<s.length&&!q.test(s[e])&&(r=z.exec(s[e]));)a.push([{t:"paragraph",c:b(r[1])}]),e++;t.push({t:"bullet",items:a});continue}if(r=H.exec(i)){let a=Math.max(1,parseInt(r[1],10)||1),o=[];for(;e<s.length&&(r=H.exec(s[e]));)o.push([{t:"paragraph",c:b(r[2])}]),e++;t.push({t:"ordered",start:a,items:o});continue}if(Y.test(i)&&e+1<s.length&&ge.test(s[e+1])){let a=Q(i).map(b);e+=2;let o=[];for(;e<s.length&&Y.test(s[e]);)o.push(Q(s[e]).map(b)),e++;t.push({t:"table",header:a,rows:o});continue}t.push({t:"paragraph",c:b(i)}),e++}return t}var ye=/^https?:\/\/[^\s<>()[\]]+/,ke=/^\[([^\]\n]{1,300})\]\((https?:\/\/[^\s)]{1,2000})\)/,be=/[.,;:!?'"]+$/;function _e(s,n){return n<=0?!0:/[\s([{"'“‘>:—–-]/.test(s[n-1])}function Ie(s,n){return n>=s.length?!0:/[\s)\]}"'”’.,;:!?—–-]/.test(s[n])}function je(s,n,t){let e=n;for(;e<s.length;){let i=s.indexOf(t,e);if(i<0)return-1;let r=s[i-1],a=s[i+t.length],o=t.length===1&&(r===t||a===t);if(i>n&&r&&!/\s/.test(r)&&!o&&Ie(s,i+t.length))return i;e=i+1}return-1}function b(s){let n=String(s??""),t=[],e="",i=()=>{e&&(t.push({t:"text",v:e}),e="")},r=0;for(;r<n.length;){let a=n[r];if(a==="`"){let o=1;for(;n[r+o]==="`";)o++;let c="`".repeat(o),l=n.indexOf(c,r+o);if(l>r+o-1&&l!==-1){let u=n.slice(r+o,l);if(u.trim()){i(),t.push({t:"code",v:u.length>1&&u.startsWith(" ")&&u.endsWith(" ")?u.slice(1,-1):u}),r=l+o;continue}}e+=c,r+=o;continue}if(a==="["){let o=ke.exec(n.slice(r));if(o){i(),t.push({t:"link",href:o[2],c:b(o[1])}),r+=o[0].length;continue}}if((a==="*"||a==="_"||a==="~")&&_e(n,r)){let o=n[r+1]===a;if(a==="~"&&!o){e+=a,r++;continue}let c=o?a+a:a,l=r+c.length;if(l<n.length&&!/\s/.test(n[l])){let u=je(n,l,c);if(u>l){i();let d=b(n.slice(l,u));t.push(a==="~"?{t:"strike",c:d}:o?{t:"strong",c:d}:{t:"em",c:d}),r=u+c.length;continue}}}if(a==="h"&&(r===0||/[\s(<]/.test(n[r-1]))){let o=ye.exec(n.slice(r));if(o){let c=o[0],l=be.exec(c);if(l&&(c=c.slice(0,-l[0].length)),c.length>8){i(),t.push({t:"link",href:c,c:[{t:"text",v:c}]}),r+=c.length;continue}}}e+=a,r++}return i(),t}function A(s,n=[]){let t=[];for(let e of s)switch(e.t){case"text":e.v&&t.push({type:"text",text:e.v,...n.length?{marks:n}:{}});break;case"br":t.push({type:"hardBreak"});break;case"code":t.push({type:"text",text:e.v,marks:[{type:"code"},...n.filter(i=>i.type==="link")]});break;case"strong":t.push(...A(e.c,$(n,{type:"strong"})));break;case"em":t.push(...A(e.c,$(n,{type:"em"})));break;case"strike":t.push(...A(e.c,$(n,{type:"strike"})));break;case"link":t.push(...A(e.c,$(n,{type:"link",attrs:{href:e.href}})));break}return t}function $(s,n){return s.some(t=>t.type===n.type)?s:[...s,n]}var C=0;function X(){return C=(C+1)%1e6,`m${Date.now().toString(36)}-${C}`}var w=s=>{let n=A(s);return n.length?{type:"paragraph",content:n}:{type:"paragraph"}};function S(s,n){let t=[];for(let e of s)switch(e.t){case"paragraph":t.push(w(e.c));break;case"heading":if(n==="quote"||n==="item")t.push(w([{t:"strong",c:e.c}]));else{let i=A(e.c);i.length&&t.push({type:"heading",attrs:{level:e.level},content:i})}break;case"bullet":t.push({type:"bulletList",content:e.items.map(i=>({type:"listItem",content:E(S(i,"item"))}))});break;case"ordered":t.push({type:"orderedList",attrs:{order:e.start},content:e.items.map(i=>({type:"listItem",content:E(S(i,"item"))}))});break;case"task":n==="quote"||n==="item"?t.push({type:"bulletList",content:e.items.map(i=>({type:"listItem",content:[w([{t:"text",v:i.checked?"\u2611 ":"\u2610 "},...i.c])]}))}):t.push({type:"taskList",attrs:{localId:X()},content:e.items.map(i=>({type:"taskItem",attrs:{localId:X(),state:i.checked?"DONE":"TODO"},content:ve(A(i.c))}))});break;case"code":t.push({type:"codeBlock",attrs:e.lang?{language:e.lang}:{},...e.v?{content:[{type:"text",text:e.v}]}:{}});break;case"quote":n==="quote"||n==="item"?t.push(...S(e.c,n)):t.push({type:"blockquote",content:E(S(e.c,"quote"))});break;case"panel":n!=="doc"?t.push(...S(e.c,n)):t.push({type:"panel",attrs:{panelType:e.kind},content:E(S(e.c,"panel"))});break;case"rule":if(n==="quote"||n==="item")break;t.push({type:"rule"});break;case"table":{if(n!=="doc"){for(let r of[...e.header?[e.header]:[],...e.rows])t.push(w(Te(r)));break}let i=[];e.header&&i.push({type:"tableRow",content:e.header.map(r=>({type:"tableHeader",attrs:{},content:[w(r)]}))});for(let r of e.rows)i.push({type:"tableRow",content:r.map(a=>({type:"tableCell",attrs:{},content:[w(a)]}))});i.length&&t.push({type:"table",attrs:{isNumberColumnEnabled:!1,layout:"default"},content:i});break}}return t}function E(s){return s.length?s:[{type:"paragraph"}]}function ve(s){return s.length?s:[{type:"text",text:" "}]}function Te(s){let n=[];return s.forEach((t,e)=>{e&&n.push({t:"text",v:" \xB7 "}),n.push(...t)}),n}function K(s){return{type:"doc",version:1,content:String(s??"").split(`
5
- `).map(n=>({type:"paragraph",...n?{content:[{type:"text",text:n}]}:{}}))}}function ne(s){try{let n=S(te(s),"doc");return{type:"doc",version:1,content:n.length?n:[{type:"paragraph"}]}}catch{return K(s)}}function v(s){let n="";for(let t of s)switch(t.t){case"text":n+=t.v;break;case"br":n+=`
3
+ `);return C(t,0)}function C(s,n){let t=[],e=0;for(;e<s.length;){let i=s[e];if(i.trim()===""){e++;continue}if(/^[ \t]/.test(i)){t.push({t:"paragraph",c:b(i)}),e++;continue}let r;if(r=de.exec(i)){let a=r[1]||"",o=[];for(e++;e<s.length&&!/^```\s*$/.test(s[e]);)o.push(s[e]),e++;e++,t.push({t:"code",lang:a,v:o.join(`
4
+ `)});continue}if(fe.test(i)){t.push({t:"rule"}),e++;continue}if(r=me.exec(i)){let a=Math.min(ee,r[1].length);t.push({t:"heading",level:a,c:b(r[2])}),e++;continue}if(W.test(i)&&n<3){let a=[];for(;e<s.length&&(r=W.exec(s[e]));)a.push(r[1]??""),e++;let o=a.findIndex(l=>l.trim()!==""),c=o>=0?ge.exec(a[o].trim()):null;if(c){let l=ue[c[1].toUpperCase()];t.push({t:"panel",kind:l,c:C(a.slice(o+1),n+1)})}else t.push({t:"quote",c:C(a,n+1)});continue}if(q.test(i)){let a=[];for(;e<s.length&&(r=q.exec(s[e]));)a.push({checked:r[1]!==" ",c:b(r[2])}),e++;t.push({t:"task",items:a});continue}if(z.test(i)){let a=[];for(;e<s.length&&!q.test(s[e])&&(r=z.exec(s[e]));)a.push([{t:"paragraph",c:b(r[1])}]),e++;t.push({t:"bullet",items:a});continue}if(r=H.exec(i)){let a=Math.max(1,parseInt(r[1],10)||1),o=[];for(;e<s.length&&(r=H.exec(s[e]));)o.push([{t:"paragraph",c:b(r[2])}]),e++;t.push({t:"ordered",start:a,items:o});continue}if(Y.test(i)&&e+1<s.length&&he.test(s[e+1])){let a=Q(i).map(b);e+=2;let o=[];for(;e<s.length&&Y.test(s[e]);)o.push(Q(s[e]).map(b)),e++;t.push({t:"table",header:a,rows:o});continue}t.push({t:"paragraph",c:b(i)}),e++}return t}var ye=/^https?:\/\/[^\s<>()[\]]+/,ke=/^\[([^\]\n]{1,300})\]\((https?:\/\/[^\s)]{1,2000})\)/,be=/[.,;:!?'"]+$/;function _e(s,n){return n<=0?!0:/[\s([{"'“‘>:—–-]/.test(s[n-1])}function Ie(s,n){return n>=s.length?!0:/[\s)\]}"'”’.,;:!?—–-]/.test(s[n])}function je(s,n,t){let e=n;for(;e<s.length;){let i=s.indexOf(t,e);if(i<0)return-1;let r=s[i-1],a=s[i+t.length],o=t.length===1&&(r===t||a===t);if(i>n&&r&&!/\s/.test(r)&&!o&&Ie(s,i+t.length))return i;e=i+1}return-1}function b(s){let n=String(s??""),t=[],e="",i=()=>{e&&(t.push({t:"text",v:e}),e="")},r=0;for(;r<n.length;){let a=n[r];if(a==="`"){let o=1;for(;n[r+o]==="`";)o++;let c="`".repeat(o),l=n.indexOf(c,r+o);if(l>r+o-1&&l!==-1){let u=n.slice(r+o,l);if(u.trim()){i(),t.push({t:"code",v:u.length>1&&u.startsWith(" ")&&u.endsWith(" ")?u.slice(1,-1):u}),r=l+o;continue}}e+=c,r+=o;continue}if(a==="["){let o=ke.exec(n.slice(r));if(o){i(),t.push({t:"link",href:o[2],c:b(o[1])}),r+=o[0].length;continue}}if((a==="*"||a==="_"||a==="~")&&_e(n,r)){let o=n[r+1]===a;if(a==="~"&&!o){e+=a,r++;continue}let c=o?a+a:a,l=r+c.length;if(l<n.length&&!/\s/.test(n[l])){let u=je(n,l,c);if(u>l){i();let d=b(n.slice(l,u));t.push(a==="~"?{t:"strike",c:d}:o?{t:"strong",c:d}:{t:"em",c:d}),r=u+c.length;continue}}}if(a==="h"&&(r===0||/[\s(<]/.test(n[r-1]))){let o=ye.exec(n.slice(r));if(o){let c=o[0],l=be.exec(c);if(l&&(c=c.slice(0,-l[0].length)),c.length>8){i(),t.push({t:"link",href:c,c:[{t:"text",v:c}]}),r+=c.length;continue}}}e+=a,r++}return i(),t}function A(s,n=[]){let t=[];for(let e of s)switch(e.t){case"text":e.v&&t.push({type:"text",text:e.v,...n.length?{marks:n}:{}});break;case"br":t.push({type:"hardBreak"});break;case"code":t.push({type:"text",text:e.v,marks:[{type:"code"},...n.filter(i=>i.type==="link")]});break;case"strong":t.push(...A(e.c,$(n,{type:"strong"})));break;case"em":t.push(...A(e.c,$(n,{type:"em"})));break;case"strike":t.push(...A(e.c,$(n,{type:"strike"})));break;case"link":t.push(...A(e.c,$(n,{type:"link",attrs:{href:e.href}})));break}return t}function $(s,n){return s.some(t=>t.type===n.type)?s:[...s,n]}var K=0;function X(){return K=(K+1)%1e6,`m${Date.now().toString(36)}-${K}`}var w=s=>{let n=A(s);return n.length?{type:"paragraph",content:n}:{type:"paragraph"}};function T(s,n){let t=[];for(let e of s)switch(e.t){case"paragraph":t.push(w(e.c));break;case"heading":if(n==="quote"||n==="item")t.push(w([{t:"strong",c:e.c}]));else{let i=A(e.c);i.length&&t.push({type:"heading",attrs:{level:e.level},content:i})}break;case"bullet":t.push({type:"bulletList",content:e.items.map(i=>({type:"listItem",content:E(T(i,"item"))}))});break;case"ordered":t.push({type:"orderedList",attrs:{order:e.start},content:e.items.map(i=>({type:"listItem",content:E(T(i,"item"))}))});break;case"task":n==="quote"||n==="item"?t.push({type:"bulletList",content:e.items.map(i=>({type:"listItem",content:[w([{t:"text",v:i.checked?"\u2611 ":"\u2610 "},...i.c])]}))}):t.push({type:"taskList",attrs:{localId:X()},content:e.items.map(i=>({type:"taskItem",attrs:{localId:X(),state:i.checked?"DONE":"TODO"},content:ve(A(i.c))}))});break;case"code":t.push({type:"codeBlock",attrs:e.lang?{language:e.lang}:{},...e.v?{content:[{type:"text",text:e.v}]}:{}});break;case"quote":n==="quote"||n==="item"?t.push(...T(e.c,n)):t.push({type:"blockquote",content:E(T(e.c,"quote"))});break;case"panel":n!=="doc"?t.push(...T(e.c,n)):t.push({type:"panel",attrs:{panelType:e.kind},content:E(T(e.c,"panel"))});break;case"rule":if(n==="quote"||n==="item")break;t.push({type:"rule"});break;case"table":{if(n!=="doc"){for(let r of[...e.header?[e.header]:[],...e.rows])t.push(w(Se(r)));break}let i=[];e.header&&i.push({type:"tableRow",content:e.header.map(r=>({type:"tableHeader",attrs:{},content:[w(r)]}))});for(let r of e.rows)i.push({type:"tableRow",content:r.map(a=>({type:"tableCell",attrs:{},content:[w(a)]}))});i.length&&t.push({type:"table",attrs:{isNumberColumnEnabled:!1,layout:"default"},content:i});break}}return t}function E(s){return s.length?s:[{type:"paragraph"}]}function ve(s){return s.length?s:[{type:"text",text:" "}]}function Se(s){let n=[];return s.forEach((t,e)=>{e&&n.push({t:"text",v:" \xB7 "}),n.push(...t)}),n}function M(s){return{type:"doc",version:1,content:String(s??"").split(`
5
+ `).map(n=>({type:"paragraph",...n?{content:[{type:"text",text:n}]}:{}}))}}function ne(s){try{let n=T(te(s),"doc");return{type:"doc",version:1,content:n.length?n:[{type:"paragraph"}]}}catch{return M(s)}}function v(s){let n="";for(let t of s)switch(t.t){case"text":n+=t.v;break;case"br":n+=`
6
6
  `;break;case"code":n+=`\`${t.v}\``;break;case"strong":n+=`**${v(t.c)}**`;break;case"em":n+=`_${v(t.c)}_`;break;case"strike":n+=`~~${v(t.c)}~~`;break;case"link":{let e=v(t.c);n+=!e||e===t.href?t.href:`[${e}](${t.href})`;break}}return n}function N(s){let n="";for(let t of s)switch(t.t){case"paragraph":n+=`${v(t.c)}
7
7
  `;break;case"heading":n+=`${"#".repeat(Math.max(1,Math.min(ee,t.level)))} ${v(t.c)}
8
8
  `;break;case"bullet":for(let e of t.items)n+=`- ${N(e).trim()}
@@ -15,10 +15,10 @@ ${t.v}
15
15
  ${Z(N(t.c))}`;break;case"rule":n+=`---
16
16
  `;break;case"table":{let e=i=>`| ${i.map(r=>v(r).replace(/\|/g,"\\|")).join(" | ")} |
17
17
  `;t.header&&(n+=e(t.header)+`|${t.header.map(()=>" --- |").join("")}
18
- `);for(let i of t.rows)n+=e(i);break}}return n}function Se(s){return s.replace(/^\n+/,"").replace(/\s+$/,"")}function Z(s){return s.replace(/\n$/,"").split(`
18
+ `);for(let i of t.rows)n+=e(i);break}}return n}function Te(s){return s.replace(/^\n+/,"").replace(/\s+$/,"")}function Z(s){return s.replace(/\n$/,"").split(`
19
19
  `).map(n=>n?`> ${n}`:">").join(`
20
20
  `)+`
21
- `}function x(s){let n=[];for(let t of s||[])if(!(!t||typeof t!="object"))switch(t.type){case"text":{let e={t:"text",v:String(t.text??"")},i=Array.isArray(t.marks)?t.marks:[];i.some(r=>r?.type==="code")&&(e={t:"code",v:String(t.text??"")});for(let r of i)!r||r.type==="code"||(r.type==="strong"?e={t:"strong",c:[e]}:r.type==="em"?e={t:"em",c:[e]}:r.type==="strike"?e={t:"strike",c:[e]}:r.type==="link"&&r.attrs?.href&&(e={t:"link",href:String(r.attrs.href),c:[e]}));n.push(e);break}case"hardBreak":n.push({t:"br"});break;case"mention":n.push({t:"text",v:String(t.attrs?.text||(t.attrs?.id?`@${t.attrs.id}`:"@someone"))});break;case"emoji":n.push({t:"text",v:String(t.attrs?.text||t.attrs?.shortName||"")});break;case"status":n.push({t:"text",v:String(t.attrs?.text||"")});break;case"date":n.push({t:"text",v:t.attrs?.timestamp?new Date(Number(t.attrs.timestamp)).toISOString().slice(0,10):""});break;case"inlineCard":{let e=String(t.attrs?.url||"");e&&n.push({t:"link",href:e,c:[{t:"text",v:e}]});break}default:Array.isArray(t.content)?n.push(...x(t.content)):typeof t.text=="string"&&n.push({t:"text",v:t.text})}return n}function _(s){let n=[];for(let t of s||[])if(!(!t||typeof t!="object"))switch(t.type){case"paragraph":n.push({t:"paragraph",c:x(t.content)});break;case"heading":n.push({t:"heading",level:Number(t.attrs?.level)||1,c:x(t.content)});break;case"bulletList":n.push({t:"bullet",items:(t.content||[]).map(e=>_(e?.content))});break;case"orderedList":n.push({t:"ordered",start:Number(t.attrs?.order)||1,items:(t.content||[]).map(e=>_(e?.content))});break;case"taskList":n.push({t:"task",items:(t.content||[]).map(e=>({checked:e?.attrs?.state==="DONE",c:x(e?.content)}))});break;case"decisionList":n.push({t:"bullet",items:(t.content||[]).map(e=>[{t:"paragraph",c:x(e?.content)}])});break;case"codeBlock":n.push({t:"code",lang:String(t.attrs?.language||""),v:(t.content||[]).map(e=>String(e?.text??"")).join("")});break;case"blockquote":n.push({t:"quote",c:_(t.content)});break;case"panel":{let e=String(t.attrs?.panelType||"info");n.push({t:"panel",kind:e in V?e:"info",c:_(t.content)});break}case"rule":n.push({t:"rule"});break;case"table":{let e=(t.content||[]).filter(o=>o?.type==="tableRow"),i=o=>(o.content||[]).map(c=>{let l=_(c?.content);return b(N(l).trim().replace(/\n+/g," "))}),r=o=>(o.content||[]).length>0&&(o.content||[]).every(c=>c?.type==="tableHeader"),a=e.length&&r(e[0])?i(e[0]):null;n.push({t:"table",header:a,rows:e.slice(a?1:0).map(i)});break}case"mediaSingle":case"mediaGroup":case"media":case"mediaInline":n.push({t:"paragraph",c:[{t:"text",v:"[attachment]"}]});break;case"expand":case"nestedExpand":{let e=String(t.attrs?.title||"").trim();e&&n.push({t:"paragraph",c:[{t:"strong",c:[{t:"text",v:e}]}]}),n.push(..._(t.content));break}default:{let e=Array.isArray(t.content)?t.content:[];e.some(i=>i?.type==="text"||i?.type==="hardBreak")?n.push({t:"paragraph",c:x(e)}):e.length?n.push(..._(e)):typeof t.text=="string"&&t.text&&n.push({t:"paragraph",c:[{t:"text",v:t.text}]})}}return n}function Ae(s){try{if(Array.isArray(s))return _(s);if(s&&typeof s=="object"){let n=s;return n.type==="doc"||!n.type&&Array.isArray(n.content)?_(n.content):_([n])}if(typeof s=="string")return te(s)}catch{}return[]}function M(s){try{return Se(N(Ae(s)))}catch{return""}}var Oe=we(import.meta.url);function Re(){if(process.env.MCP_JIRA_PATH)return process.env.MCP_JIRA_PATH;try{return Oe.resolve("@zibby/mcp-jira/index.js")}catch{return null}}function $e(s){return ne(s)}async function B(s,n){try{return await s($e(n))}catch(t){let e=Number(t?.status);if(!(e>=400&&e<500))throw t;return console.warn(`[jira] rich body rejected (${e}) \u2014 retrying as plain text: ${String(t?.message||t).slice(0,200)}`),await s(K(n))}}function O(s){return String(s||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function U(s){return O(s).replace(/[a-z0-9]+/g,"")}function L(s,n){let t=O(s),e=O(n);if(!t||!e)return 0;if(t===e)return 1;if(t.length===1||e.length===1)return t===e?1:0;let i=u=>{let d=new Map;for(let f=0;f<u.length-1;f++){let h=u.slice(f,f+2);d.set(h,(d.get(h)||0)+1)}return d},r=i(t),a=i(e),o=0,c=0,l=0;for(let u of r.values())c+=u;for(let u of a.values())l+=u;for(let[u,d]of r.entries()){let f=a.get(u)||0;o+=Math.min(d,f)}return 2*o/Math.max(1,c+l)}function T(s){return String(s||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function Ee(s,n=[]){let t=Array.isArray(n)?n:[];if(t.length===0)return{requested:s||null,resolved:null,strategy:"none"};let e=t.filter(o=>!o.subtask),i=e.length>0?e:t,r=T(s);if(r){let o=i.find(u=>T(u.name)===r);if(o)return{requested:s,resolved:o,strategy:"exact"};let c={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(c)){if(!u.some(f=>T(f)===r))continue;let d=i.find(f=>u.some(h=>T(h)===T(f.name)));if(d)return{requested:s,resolved:d,strategy:"alias"}}let l=i.map(u=>({t:u,score:L(s,u.name)})).sort((u,d)=>d.score-u.score);if(l[0]&&l[0].score>=.5)return{requested:s,resolved:l[0].t,strategy:"fuzzy"}}let a=["task","story","bug","improvement","epic"];for(let o of a){let c=i.find(l=>T(l.name)===o);if(c)return{requested:s||null,resolved:c,strategy:"default-preferred"}}return{requested:s||null,resolved:i[0],strategy:"default-first"}}async function se(s){let n=`projectKeys=${encodeURIComponent(s)}&expand=projects.issuetypes`,t=await g(`/rest/api/3/issue/createmeta?${n}`),e=Array.isArray(t?.projects)?t.projects:[],r=e.find(o=>String(o?.key||"").toUpperCase()===String(s||"").toUpperCase())||e[0]||null;return(Array.isArray(r?.issuetypes)?r.issuetypes:[]).map(o=>({id:o.id,name:o.name,subtask:!!o.subtask,description:o.description||null}))}async function re(s,n){if(!s)throw new Error("projectKey is required");let t="sprint is not EMPTY";n==="active"?t="sprint in openSprints()":n==="closed"?t="sprint in closedSprints()":n==="future"&&(t="sprint in futureSprints()");let e=`project = ${s} AND ${t} ORDER BY updated DESC`,i=`jql=${encodeURIComponent(e)}&maxResults=100&fields=customfield_10020`,r=await g(`/rest/api/3/search/jql?${i}`),a=new Map;for(let o of r.issues||[])for(let c of o.fields?.customfield_10020||[])c&&!a.has(c.id)&&a.set(c.id,{id:c.id,name:c.name,state:c.state,boardId:c.boardId||null,startDate:c.startDate||null,endDate:c.endDate||null,goal:c.goal||null});return[...a.values()].sort((o,c)=>{let l={active:0,future:1,closed:2},u=(l[o.state]??3)-(l[c.state]??3);return u!==0?u:String(c.startDate||"").localeCompare(String(o.startDate||""))})}function Le(s,{sprintId:n,sprintName:t,target:e}={}){let i=Array.isArray(s)?s:[];if(!i.length)return{sprint:null,selectedBy:"none"};if(n!=null&&String(n).trim()!=="")return{sprint:i.find(o=>String(o.id)===String(n))||null,selectedBy:"id"};if(t&&String(t).trim()){let a=String(t).trim(),o=i.find(l=>String(l.name||"").toLowerCase()===a.toLowerCase());if(o)return{sprint:o,selectedBy:"name-exact"};let c=i.map(l=>({s:l,score:L(a,l.name||"")})).sort((l,u)=>u.score-l.score);return c[0]&&c[0].score>=.5?{sprint:c[0].s,selectedBy:"name-fuzzy"}:{sprint:null,selectedBy:"name-none"}}let r=String(e||"current").trim().toLowerCase();return r==="active"||r==="current"||r==="latest"?{sprint:i[0],selectedBy:r}:{sprint:i[0],selectedBy:"default"}}function Pe(s,n){let t=s?.fields?.customfield_10020;return Array.isArray(t)?t.some(e=>String(e?.id)===String(n)):!1}async function qe({issueKey:s,projectKey:n,sprintId:t,attempts:e=3,delayMs:i=450}){let r=[];for(let a=0;a<e;a++){try{let o=`project = ${n} AND key = ${s} AND sprint = ${t}`,c=`jql=${encodeURIComponent(o)}&maxResults=1&fields=key,status`,l=await g(`/rest/api/3/search/jql?${c}`);if(Number(l?.total||0)>0)return r.push({attempt:a+1,jql:!0,issueField:null}),{ok:!0,method:"jql",traces:r};let d=await g(`/rest/api/3/issue/${s}?fields=customfield_10020,status`),f=Pe(d,t);if(r.push({attempt:a+1,jql:!1,issueField:f}),f)return{ok:!0,method:"issue_field",traces:r}}catch(o){r.push({attempt:a+1,error:String(o?.message||o)})}a<e-1&&await new Promise(o=>setTimeout(o,i))}return{ok:!1,method:"none",traces:r}}async function D({issueKey:s,projectKey:n,sprintId:t,sprintName:e,target:i}){if(!s)return{ok:!1,error:"issueKey is required"};let r=n;if(!r&&(r=(await g(`/rest/api/3/issue/${s}?fields=project`))?.fields?.project?.key||null,!r))return{ok:!1,error:`Could not resolve project for ${s}`};let a=await re(r,"active");if(!a.length)return{ok:!1,error:`No assignable active sprint found for project ${r}`};let{sprint:o,selectedBy:c}=Le(a,{sprintId:t,sprintName:e,target:i});if(!o)return{ok:!1,error:`No matching sprint found in ${r}`,requested:{sprintId:t??null,sprintName:e??null,target:i??"current"},availableSprints:a.map(d=>({id:d.id,name:d.name,state:d.state}))};await g(`/rest/api/3/issue/${s}`,{method:"PUT",body:{fields:{customfield_10020:Number(o.id)}}});let l=await qe({issueKey:s,projectKey:r,sprintId:o.id}),u=l.ok;return{ok:u,issueKey:s,projectKey:r,sprintId:o.id,sprintName:o.name,selectedBy:c,verifiedBy:l.method,verified:u,verificationTrace:l.traces,warning:u?null:`Sprint assignment attempted but verification did not find ${s} in sprint ${o.id}`}}async function Ce(){let s=process.env.JIRA_API_TOKEN,n=process.env.JIRA_EMAIL,t=process.env.JIRA_BASE_URL;if(s&&n&&t)return{authType:"token",apiToken:s,email:n,baseUrl:t};let e=await xe("jira");return e?.cloudId?{authType:"oauth",accessToken:e.token,cloudId:e.cloudId}:{authType:"token",apiToken:e?.token,email:n||e?.email||"",baseUrl:t||e?.instanceUrl||e?.baseUrl||process.env.ATLASSIAN_INSTANCE_URL||""}}function Je(s,n,t={}){let e=s||{},i={Accept:"application/json",...t.body?{"Content-Type":"application/json"}:{},...t.headers};if(e.authType==="token"){let r=String(e.baseUrl||e.instanceUrl||"").trim().replace(/\/+$/,""),a=e.apiToken||e.accessToken||"";if(!r)throw new Error("Jira token connection has no base URL \u2014 reconnect Jira with its instance URL, or set JIRA_BASE_URL.");if(!e.email)throw new Error("Jira token connection has no account email \u2014 reconnect Jira with the account email, or set JIRA_EMAIL.");if(!a)throw new Error("Jira token connection has no API token \u2014 reconnect Jira, or set JIRA_API_TOKEN.");return{url:`${r}${n}`,headers:{Authorization:"Basic "+Buffer.from(`${e.email}:${a}`).toString("base64"),...i}}}if(typeof e.accessToken!="string"||!e.accessToken)throw new Error(`Invalid jira token type: ${typeof e.accessToken}`);if(!e.cloudId)throw new Error("Invalid jira cloudId: missing");return{url:`https://api.atlassian.com/ex/jira/${e.cloudId}${n}`,headers:{Authorization:`Bearer ${e.accessToken}`,...i}}}async function g(s,n={}){let t=async()=>{let e=await Ce(),{url:i,headers:r}=Je(e,s,n),a=await F(i,{method:n.method||"GET",headers:r,body:n.body?JSON.stringify(n.body):void 0,signal:n.signal},{kind:/\/search\b/.test(String(s))?"job":"api",what:`Jira ${n.method||"GET"} ${s}`}),o=()=>a.text().catch(l=>{if(P(l)||n.signal?.aborted)throw l;return""});if(!a.ok){let l=await o(),u=new Error(`Jira API ${a.status}: ${l.slice(0,300)}`);throw u.status=a.status,u}let c=await o();if(!c||!c.trim())return{};try{return JSON.parse(c)}catch{return{raw:c}}};try{return await t()}catch(e){if(n.signal?.aborted)throw e;let i=String(e?.message||e||"").toLowerCase();if(!(i.includes("token")||i.includes("401")||i.includes("403")||i.includes("substring")))throw e;return Ne("jira"),t()}}var He={id:"jira",callsBackend:!0,serverName:"jira",allowedTools:["mcp__jira__*"],requiresIntegration:G.JIRA,envKeys:["ATLASSIAN_ACCESS_TOKEN","ATLASSIAN_CLOUD_ID"],description:"Zibby Jira MCP Server (OAuth Bearer)",promptFragment:`## Jira
21
+ `}function x(s){let n=[];for(let t of s||[])if(!(!t||typeof t!="object"))switch(t.type){case"text":{let e={t:"text",v:String(t.text??"")},i=Array.isArray(t.marks)?t.marks:[];i.some(r=>r?.type==="code")&&(e={t:"code",v:String(t.text??"")});for(let r of i)!r||r.type==="code"||(r.type==="strong"?e={t:"strong",c:[e]}:r.type==="em"?e={t:"em",c:[e]}:r.type==="strike"?e={t:"strike",c:[e]}:r.type==="link"&&r.attrs?.href&&(e={t:"link",href:String(r.attrs.href),c:[e]}));n.push(e);break}case"hardBreak":n.push({t:"br"});break;case"mention":n.push({t:"text",v:String(t.attrs?.text||(t.attrs?.id?`@${t.attrs.id}`:"@someone"))});break;case"emoji":n.push({t:"text",v:String(t.attrs?.text||t.attrs?.shortName||"")});break;case"status":n.push({t:"text",v:String(t.attrs?.text||"")});break;case"date":n.push({t:"text",v:t.attrs?.timestamp?new Date(Number(t.attrs.timestamp)).toISOString().slice(0,10):""});break;case"inlineCard":{let e=String(t.attrs?.url||"");e&&n.push({t:"link",href:e,c:[{t:"text",v:e}]});break}default:Array.isArray(t.content)?n.push(...x(t.content)):typeof t.text=="string"&&n.push({t:"text",v:t.text})}return n}function _(s){let n=[];for(let t of s||[])if(!(!t||typeof t!="object"))switch(t.type){case"paragraph":n.push({t:"paragraph",c:x(t.content)});break;case"heading":n.push({t:"heading",level:Number(t.attrs?.level)||1,c:x(t.content)});break;case"bulletList":n.push({t:"bullet",items:(t.content||[]).map(e=>_(e?.content))});break;case"orderedList":n.push({t:"ordered",start:Number(t.attrs?.order)||1,items:(t.content||[]).map(e=>_(e?.content))});break;case"taskList":n.push({t:"task",items:(t.content||[]).map(e=>({checked:e?.attrs?.state==="DONE",c:x(e?.content)}))});break;case"decisionList":n.push({t:"bullet",items:(t.content||[]).map(e=>[{t:"paragraph",c:x(e?.content)}])});break;case"codeBlock":n.push({t:"code",lang:String(t.attrs?.language||""),v:(t.content||[]).map(e=>String(e?.text??"")).join("")});break;case"blockquote":n.push({t:"quote",c:_(t.content)});break;case"panel":{let e=String(t.attrs?.panelType||"info");n.push({t:"panel",kind:e in V?e:"info",c:_(t.content)});break}case"rule":n.push({t:"rule"});break;case"table":{let e=(t.content||[]).filter(o=>o?.type==="tableRow"),i=o=>(o.content||[]).map(c=>{let l=_(c?.content);return b(N(l).trim().replace(/\n+/g," "))}),r=o=>(o.content||[]).length>0&&(o.content||[]).every(c=>c?.type==="tableHeader"),a=e.length&&r(e[0])?i(e[0]):null;n.push({t:"table",header:a,rows:e.slice(a?1:0).map(i)});break}case"mediaSingle":case"mediaGroup":case"media":case"mediaInline":n.push({t:"paragraph",c:[{t:"text",v:"[attachment]"}]});break;case"expand":case"nestedExpand":{let e=String(t.attrs?.title||"").trim();e&&n.push({t:"paragraph",c:[{t:"strong",c:[{t:"text",v:e}]}]}),n.push(..._(t.content));break}default:{let e=Array.isArray(t.content)?t.content:[];e.some(i=>i?.type==="text"||i?.type==="hardBreak")?n.push({t:"paragraph",c:x(e)}):e.length?n.push(..._(e)):typeof t.text=="string"&&t.text&&n.push({t:"paragraph",c:[{t:"text",v:t.text}]})}}return n}function Ae(s){try{if(Array.isArray(s))return _(s);if(s&&typeof s=="object"){let n=s;return n.type==="doc"||!n.type&&Array.isArray(n.content)?_(n.content):_([n])}if(typeof s=="string")return te(s)}catch{}return[]}function J(s){try{return Te(N(Ae(s)))}catch{return""}}function Ee(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let s=xe(Oe(import.meta.url)),n=Ne(s,"..","bin","mcp-skill.mjs");return we(n)?n:null}function Le(s){return ne(s)}async function B(s,n){try{return await s(Le(n))}catch(t){let e=Number(t?.status);if(!(e>=400&&e<500))throw t;return console.warn(`[jira] rich body rejected (${e}) \u2014 retrying as plain text: ${String(t?.message||t).slice(0,200)}`),await s(M(n))}}function O(s){return String(s||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function U(s){return O(s).replace(/[a-z0-9]+/g,"")}function L(s,n){let t=O(s),e=O(n);if(!t||!e)return 0;if(t===e)return 1;if(t.length===1||e.length===1)return t===e?1:0;let i=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},r=i(t),a=i(e),o=0,c=0,l=0;for(let u of r.values())c+=u;for(let u of a.values())l+=u;for(let[u,d]of r.entries()){let m=a.get(u)||0;o+=Math.min(d,m)}return 2*o/Math.max(1,c+l)}function S(s){return String(s||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function Pe(s,n=[]){let t=Array.isArray(n)?n:[];if(t.length===0)return{requested:s||null,resolved:null,strategy:"none"};let e=t.filter(o=>!o.subtask),i=e.length>0?e:t,r=S(s);if(r){let o=i.find(u=>S(u.name)===r);if(o)return{requested:s,resolved:o,strategy:"exact"};let c={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(c)){if(!u.some(m=>S(m)===r))continue;let d=i.find(m=>u.some(g=>S(g)===S(m.name)));if(d)return{requested:s,resolved:d,strategy:"alias"}}let l=i.map(u=>({t:u,score:L(s,u.name)})).sort((u,d)=>d.score-u.score);if(l[0]&&l[0].score>=.5)return{requested:s,resolved:l[0].t,strategy:"fuzzy"}}let a=["task","story","bug","improvement","epic"];for(let o of a){let c=i.find(l=>S(l.name)===o);if(c)return{requested:s||null,resolved:c,strategy:"default-preferred"}}return{requested:s||null,resolved:i[0],strategy:"default-first"}}async function se(s){let n=`projectKeys=${encodeURIComponent(s)}&expand=projects.issuetypes`,t=await h(`/rest/api/3/issue/createmeta?${n}`),e=Array.isArray(t?.projects)?t.projects:[],r=e.find(o=>String(o?.key||"").toUpperCase()===String(s||"").toUpperCase())||e[0]||null;return(Array.isArray(r?.issuetypes)?r.issuetypes:[]).map(o=>({id:o.id,name:o.name,subtask:!!o.subtask,description:o.description||null}))}async function re(s,n){if(!s)throw new Error("projectKey is required");let t="sprint is not EMPTY";n==="active"?t="sprint in openSprints()":n==="closed"?t="sprint in closedSprints()":n==="future"&&(t="sprint in futureSprints()");let e=`project = ${s} AND ${t} ORDER BY updated DESC`,i=`jql=${encodeURIComponent(e)}&maxResults=100&fields=customfield_10020`,r=await h(`/rest/api/3/search/jql?${i}`),a=new Map;for(let o of r.issues||[])for(let c of o.fields?.customfield_10020||[])c&&!a.has(c.id)&&a.set(c.id,{id:c.id,name:c.name,state:c.state,boardId:c.boardId||null,startDate:c.startDate||null,endDate:c.endDate||null,goal:c.goal||null});return[...a.values()].sort((o,c)=>{let l={active:0,future:1,closed:2},u=(l[o.state]??3)-(l[c.state]??3);return u!==0?u:String(c.startDate||"").localeCompare(String(o.startDate||""))})}function qe(s,{sprintId:n,sprintName:t,target:e}={}){let i=Array.isArray(s)?s:[];if(!i.length)return{sprint:null,selectedBy:"none"};if(n!=null&&String(n).trim()!=="")return{sprint:i.find(o=>String(o.id)===String(n))||null,selectedBy:"id"};if(t&&String(t).trim()){let a=String(t).trim(),o=i.find(l=>String(l.name||"").toLowerCase()===a.toLowerCase());if(o)return{sprint:o,selectedBy:"name-exact"};let c=i.map(l=>({s:l,score:L(a,l.name||"")})).sort((l,u)=>u.score-l.score);return c[0]&&c[0].score>=.5?{sprint:c[0].s,selectedBy:"name-fuzzy"}:{sprint:null,selectedBy:"name-none"}}let r=String(e||"current").trim().toLowerCase();return r==="active"||r==="current"||r==="latest"?{sprint:i[0],selectedBy:r}:{sprint:i[0],selectedBy:"default"}}function Ke(s,n){let t=s?.fields?.customfield_10020;return Array.isArray(t)?t.some(e=>String(e?.id)===String(n)):!1}async function Ce({issueKey:s,projectKey:n,sprintId:t,attempts:e=3,delayMs:i=450}){let r=[];for(let a=0;a<e;a++){try{let o=`project = ${n} AND key = ${s} AND sprint = ${t}`,c=`jql=${encodeURIComponent(o)}&maxResults=1&fields=key,status`,l=await h(`/rest/api/3/search/jql?${c}`);if(Number(l?.total||0)>0)return r.push({attempt:a+1,jql:!0,issueField:null}),{ok:!0,method:"jql",traces:r};let d=await h(`/rest/api/3/issue/${s}?fields=customfield_10020,status`),m=Ke(d,t);if(r.push({attempt:a+1,jql:!1,issueField:m}),m)return{ok:!0,method:"issue_field",traces:r}}catch(o){r.push({attempt:a+1,error:String(o?.message||o)})}a<e-1&&await new Promise(o=>setTimeout(o,i))}return{ok:!1,method:"none",traces:r}}async function D({issueKey:s,projectKey:n,sprintId:t,sprintName:e,target:i}){if(!s)return{ok:!1,error:"issueKey is required"};let r=n;if(!r&&(r=(await h(`/rest/api/3/issue/${s}?fields=project`))?.fields?.project?.key||null,!r))return{ok:!1,error:`Could not resolve project for ${s}`};let a=await re(r,"active");if(!a.length)return{ok:!1,error:`No assignable active sprint found for project ${r}`};let{sprint:o,selectedBy:c}=qe(a,{sprintId:t,sprintName:e,target:i});if(!o)return{ok:!1,error:`No matching sprint found in ${r}`,requested:{sprintId:t??null,sprintName:e??null,target:i??"current"},availableSprints:a.map(d=>({id:d.id,name:d.name,state:d.state}))};await h(`/rest/api/3/issue/${s}`,{method:"PUT",body:{fields:{customfield_10020:Number(o.id)}}});let l=await Ce({issueKey:s,projectKey:r,sprintId:o.id}),u=l.ok;return{ok:u,issueKey:s,projectKey:r,sprintId:o.id,sprintName:o.name,selectedBy:c,verifiedBy:l.method,verified:u,verificationTrace:l.traces,warning:u?null:`Sprint assignment attempted but verification did not find ${s} in sprint ${o.id}`}}async function Me(){let s=process.env.JIRA_API_TOKEN,n=process.env.JIRA_EMAIL,t=process.env.JIRA_BASE_URL;if(s&&n&&t)return{authType:"token",apiToken:s,email:n,baseUrl:t};let e=await Re("jira");return(e?.authType?e.authType!=="token":!!e?.cloudId)?{authType:"oauth",accessToken:e.token,cloudId:e.cloudId}:{authType:"token",apiToken:e?.token,email:n||e?.email||"",baseUrl:t||e?.baseUrl||e?.instanceUrl||process.env.ATLASSIAN_INSTANCE_URL||""}}function Je(s,n,t={}){let e=s||{},i={Accept:"application/json",...t.body?{"Content-Type":"application/json"}:{},...t.headers};if(e.authType==="token"){let r=String(e.baseUrl||e.instanceUrl||"").trim().replace(/\/+$/,""),a=e.apiToken||e.accessToken||"";if(!r)throw new Error("Jira token connection has no base URL \u2014 reconnect Jira with its instance URL, or set JIRA_BASE_URL.");if(!e.email)throw new Error("Jira token connection has no account email \u2014 reconnect Jira with the account email, or set JIRA_EMAIL.");if(!a)throw new Error("Jira token connection has no API token \u2014 reconnect Jira, or set JIRA_API_TOKEN.");return{url:`${r}${n}`,headers:{Authorization:"Basic "+Buffer.from(`${e.email}:${a}`).toString("base64"),...i}}}if(typeof e.accessToken!="string"||!e.accessToken)throw new Error(`Invalid jira token type: ${typeof e.accessToken}`);if(!e.cloudId)throw new Error("Invalid jira cloudId: missing");return{url:`https://api.atlassian.com/ex/jira/${e.cloudId}${n}`,headers:{Authorization:`Bearer ${e.accessToken}`,...i}}}async function h(s,n={}){let t=async()=>{let e=await Me(),{url:i,headers:r}=Je(e,s,n),a=await F(i,{method:n.method||"GET",headers:r,body:n.body?JSON.stringify(n.body):void 0,signal:n.signal},{kind:/\/search\b/.test(String(s))?"job":"api",what:`Jira ${n.method||"GET"} ${s}`}),o=()=>a.text().catch(l=>{if(P(l)||n.signal?.aborted)throw l;return""});if(!a.ok){let l=await o(),u=new Error(`Jira API ${a.status}: ${l.slice(0,300)}`);throw u.status=a.status,u}let c=await o();if(!c||!c.trim())return{};try{return JSON.parse(c)}catch{return{raw:c}}};try{return await t()}catch(e){if(n.signal?.aborted)throw e;let i=String(e?.message||e||"").toLowerCase();if(!(i.includes("token")||i.includes("401")||i.includes("403")||i.includes("substring")))throw e;return $e("jira"),t()}}var Ze={id:"jira",callsBackend:!0,serverName:"jira",allowedTools:["mcp__jira__*"],requiresIntegration:G.JIRA,envKeys:["ATLASSIAN_ACCESS_TOKEN","ATLASSIAN_CLOUD_ID"],description:"Zibby Jira MCP Server (OAuth Bearer)",promptFragment:`## Jira
22
22
  You have direct access to the user's Jira. Use these tools proactively:
23
23
 
24
24
  ### Issue tools
@@ -68,4 +68,4 @@ When user asks to move/transition ticket status:
68
68
  3. Pick the correct transition from returned list (match by "to" status name, not guesswork), then call jira_transition_issue with transitionId.
69
69
  4. Call jira_get_issue(issueKey) to verify final status before claiming success.
70
70
  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.
71
- 6. IMPORTANT: When target is clear, complete transition + verification in SAME turn. Do NOT stop after listing options.`,resolve(){let s=Re();if(!s)return null;let n={};for(let t of this.envKeys)process.env[t]&&(n[t]=process.env[t]);process.env.ATLASSIAN_INSTANCE_URL&&(n.ATLASSIAN_INSTANCE_URL=process.env.ATLASSIAN_INSTANCE_URL);for(let t of["JIRA_API_TOKEN","JIRA_EMAIL","JIRA_BASE_URL"])process.env[t]&&(n[t]=process.env[t]);return{command:"node",args:[s],env:n,description:this.description}},async handleToolCall(s,n){try{switch(s){case"jira_list_projects":{let t=await g("/rest/api/3/project"),e=(Array.isArray(t)?t:[]).map(i=>({id:i.id,key:i.key,name:i.name,style:i.style}));return JSON.stringify({count:e.length,projects:e})}case"jira_list_statuses":{let{projectKey:t}=n||{};if(t){let r=await g(`/rest/api/3/project/${encodeURIComponent(t)}/statuses`),a=Array.isArray(r)?r:[],o=new Map;for(let l of a)for(let u of l.statuses||[])u?.id&&(o.has(u.id)||o.set(u.id,{id:u.id,name:u.name,category:u.statusCategory?.name||null}));let c=[...o.values()].sort((l,u)=>String(l.name).localeCompare(String(u.name)));return JSON.stringify({scope:"project",projectKey:t,count:c.length,statuses:c})}let e=await g("/rest/api/3/status"),i=(Array.isArray(e)?e:[]).map(r=>({id:r.id,name:r.name,category:r.statusCategory?.name||null})).sort((r,a)=>String(r.name).localeCompare(String(a.name)));return JSON.stringify({scope:"global",count:i.length,statuses:i})}case"jira_list_issue_types":{let{projectKey:t}=n||{};if(!t)return JSON.stringify({error:"projectKey is required"});let e=await se(t);return JSON.stringify({projectKey:t,count:e.length,issueTypes:e})}case"jira_search":{let t=n.jql||"",e=n.maxResults||20;t.replace(/\s*ORDER\s+BY\s+.*/i,"").trim()||(t=`created >= -365d ${t}`.trim());let r=`jql=${encodeURIComponent(t)}&maxResults=${e}&fields=summary,status,assignee,priority,updated,issuetype,project`,o=((await g(`/rest/api/3/search/jql?${r}`)).issues||[]).map(c=>({key:c.key,project:c.fields?.project?.key,summary:c.fields?.summary,status:c.fields?.status?.name,assignee:c.fields?.assignee?.displayName||"Unassigned",priority:c.fields?.priority?.name,type:c.fields?.issuetype?.name}));return JSON.stringify({count:o.length,issues:o})}case"jira_get_issue":{let t=n.issueKey;if(!t)return JSON.stringify({error:"issueKey is required"});let e=await g(`/rest/api/3/issue/${t}`);return JSON.stringify({key:e.key,project:e.fields?.project?.key,summary:e.fields?.summary,description:e.fields?.description?M(e.fields.description):e.fields?.description??null,status:e.fields?.status?.name,assignee:e.fields?.assignee?.displayName||"Unassigned",priority:e.fields?.priority?.name,type:e.fields?.issuetype?.name,labels:e.fields?.labels,created:e.fields?.created,updated:e.fields?.updated})}case"jira_create_issue":{let{projectKey:t,summary:e,issueType:i,description:r,priority:a,labels:o,assigneeId:c,moveToSprint:l,moveToActiveSprint:u,sprintId:d,sprintName:f,target:h}=n;if(!t||!e)return JSON.stringify({error:"projectKey and summary are required"});let p={requested:i||null,resolved:null,strategy:"none"},y=[];try{y=await se(t),p=Ee(i,y)}catch{}let m={project:{key:t},summary:e,issuetype:p?.resolved?.id?{id:p.resolved.id}:{name:i||"Task"}};a&&(m.priority={name:a}),o?.length&&(m.labels=o),c&&(m.assignee={id:c});let I=r?await B(j=>g("/rest/api/3/issue",{method:"POST",body:{fields:{...m,description:j}}}),r):await g("/rest/api/3/issue",{method:"POST",body:{fields:m}}),k={ok:!0,key:I.key,id:I.id,self:I.self};return p?.resolved&&(k.issueType=p.resolved.name,k.issueTypeResolution=p.strategy,p.strategy!=="exact"&&p.requested&&T(p.requested)!==T(p.resolved.name)&&(k.issueTypeWarning=`Requested "${p.requested}" is not available in ${t}; used "${p.resolved.name}" instead.`)),y.length>0&&(k.availableIssueTypes=y.map(j=>j.name)),(l||u)&&(k.sprintMove=await D({issueKey:I.key,projectKey:t,sprintId:d,sprintName:f,target:h})),JSON.stringify(k)}case"jira_list_sprints":{let{projectKey:t,state:e}=n,i=await re(t,e);return JSON.stringify({count:i.length,sprints:i})}case"jira_move_to_active_sprint":{let{issueKey:t,projectKey:e,sprintId:i,sprintName:r,target:a}=n||{},o=await D({issueKey:t,projectKey:e,sprintId:i,sprintName:r,target:a||"current"});return JSON.stringify(o)}case"jira_move_issue_to_sprint":{let{issueKey:t,projectKey:e,sprintId:i,sprintName:r,target:a}=n||{},o=await D({issueKey:t,projectKey:e,sprintId:i,sprintName:r,target:a});return JSON.stringify(o)}case"jira_get_sprint_issues":{let{sprintName:t,sprintId:e,projectKey:i,status:r,maxResults:a}=n;if(!t&&!e)return JSON.stringify({error:"sprintName or sprintId is required"});let o=a||50,c=e?`sprint = ${e}`:`sprint = "${t}"`,l=i?`project = ${i} AND `:"",u=r?` AND status = "${r}"`:"",d=`${l}${c}${u} ORDER BY status ASC, priority DESC`,f=`jql=${encodeURIComponent(d)}&maxResults=${o}&fields=summary,status,assignee,priority,issuetype,project`,h=await g(`/rest/api/3/search/jql?${f}`),p=(h.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})),y={};for(let m of p)y[m.status]=(y[m.status]||0)+1;return JSON.stringify({count:p.length,total:h.total||p.length,statusCounts:y,issues:p})}case"jira_get_comments":{let{issueKey:t,maxResults:e}=n;if(!t)return JSON.stringify({error:"issueKey is required"});let r=await g(`/rest/api/3/issue/${t}/comment?maxResults=${e||50}&orderBy=-created`),a=(r.comments||[]).map(o=>{let c=o.body?.content?M(o.body):"";return{id:o.id,author:o.author?.displayName||"Unknown",body:c,created:o.created,updated:o.updated}});return JSON.stringify({count:a.length,total:r.total||a.length,comments:a})}case"jira_add_comment":{let{issueKey:t,body:e}=n;return!t||!e?JSON.stringify({error:"issueKey and body are required"}):(await B(i=>g(`/rest/api/3/issue/${t}/comment`,{method:"POST",body:{body:i}}),e),JSON.stringify({ok:!0,issueKey:t}))}case"jira_edit_issue":{let{issueKey:t,fields:e}=n;if(!t||!e)return JSON.stringify({error:"issueKey and fields are required"});if(typeof e.description=="string"){let{description:i,...r}=e;await B(a=>g(`/rest/api/3/issue/${t}`,{method:"PUT",body:{fields:{...r,description:a}}}),i)}else await g(`/rest/api/3/issue/${t}`,{method:"PUT",body:{fields:e}});return JSON.stringify({ok:!0,issueKey:t})}case"jira_transition_issue":{let{issueKey:t,transitionId:e,toStatus:i,statusName:r,status:a}=n;if(!t)return JSON.stringify({error:"issueKey is required"});let o=String(i||r||a||"").trim();if(!e&&!o){let d=((await g(`/rest/api/3/issue/${t}/transitions`)).transitions||[]).map(f=>({id:f.id,name:f.name,to:f.to?.name}));return JSON.stringify({ok:!1,error:"transitionId or toStatus is required",issueKey:t,availableTransitions:d})}let c=e;if(!c){let d=(await g(`/rest/api/3/issue/${t}/transitions`)).transitions||[],f=O(o),h=d.find(p=>O(p?.name||"")===f||O(p?.to?.name||"")===f);if(!h){let p=U(o);p.length>=2&&(h=d.find(y=>{let m=U(y?.name||""),I=U(y?.to?.name||""),k=m.length>=2&&(m.includes(p)||p.includes(m)),j=I.length>=2&&(I.includes(p)||p.includes(I));return k||j}))}if(!h){let p=d.map(k=>{let j=L(o,k?.name||""),ie=L(o,k?.to?.name||"");return{t:k,score:Math.max(j,ie)}}).sort((k,j)=>j.score-k.score),y=p[0],m=p[1];y&&y.score>=.45&&(!m||y.score-m.score>=.12)&&(h=y.t)}if(!h?.id)return JSON.stringify({ok:!1,error:`No transition matches target status: "${o}"`,issueKey:t,availableTransitions:d.map(p=>({id:p.id,name:p.name,to:p.to?.name}))});c=h.id}await g(`/rest/api/3/issue/${t}/transitions`,{method:"POST",body:{transition:{id:c}}});let l=await g(`/rest/api/3/issue/${t}?fields=status`);return JSON.stringify({ok:!0,issueKey:t,transitionId:c,statusAfter:l?.fields?.status?.name||null})}default:return JSON.stringify({error:`Unknown tool: ${s}`})}}catch(t){return JSON.stringify({error:t.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. Markdown is rendered (headings, **bold**, `code`, lists, - [ ] tasks, links, tables, > quotes, > [!NOTE]/[!TIP]/[!WARNING]/[!CAUTION] panels)."},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. Markdown is rendered: headings, **bold**, `code`, lists, - [ ] tasks, links, tables, > quotes, > [!NOTE]/[!TIP]/[!WARNING]/[!CAUTION] panels."}},required:["issueKey","body"]}},{name:"jira_edit_issue",description:"Update fields on a Jira issue (summary, description, story points, labels, priority). A string `description` is Markdown and is rendered.",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{Je as jiraApiCall,g as jiraFetch,He as jiraSkill,Ce as resolveJiraCredential};
71
+ 6. IMPORTANT: When target is clear, complete transition + verification in SAME turn. Do NOT stop after listing options.`,resolve(){let s=Ee();if(!s)return{command:null,args:[],env:{},description:this.description};let n={};for(let t of this.envKeys)process.env[t]&&(n[t]=process.env[t]);process.env.ATLASSIAN_INSTANCE_URL&&(n.ATLASSIAN_INSTANCE_URL=process.env.ATLASSIAN_INSTANCE_URL);for(let t of["JIRA_API_TOKEN","JIRA_EMAIL","JIRA_BASE_URL"])process.env[t]&&(n[t]=process.env[t]);return{type:"stdio",command:"node",args:[s,"../dist/jira.js","jiraSkill"],env:n,description:this.description}},async handleToolCall(s,n){try{switch(s){case"jira_list_projects":{let t=await h("/rest/api/3/project"),e=(Array.isArray(t)?t:[]).map(i=>({id:i.id,key:i.key,name:i.name,style:i.style}));return JSON.stringify({count:e.length,projects:e})}case"jira_list_statuses":{let{projectKey:t}=n||{};if(t){let r=await h(`/rest/api/3/project/${encodeURIComponent(t)}/statuses`),a=Array.isArray(r)?r:[],o=new Map;for(let l of a)for(let u of l.statuses||[])u?.id&&(o.has(u.id)||o.set(u.id,{id:u.id,name:u.name,category:u.statusCategory?.name||null}));let c=[...o.values()].sort((l,u)=>String(l.name).localeCompare(String(u.name)));return JSON.stringify({scope:"project",projectKey:t,count:c.length,statuses:c})}let e=await h("/rest/api/3/status"),i=(Array.isArray(e)?e:[]).map(r=>({id:r.id,name:r.name,category:r.statusCategory?.name||null})).sort((r,a)=>String(r.name).localeCompare(String(a.name)));return JSON.stringify({scope:"global",count:i.length,statuses:i})}case"jira_list_issue_types":{let{projectKey:t}=n||{};if(!t)return JSON.stringify({error:"projectKey is required"});let e=await se(t);return JSON.stringify({projectKey:t,count:e.length,issueTypes:e})}case"jira_search":{let t=n.jql||"",e=n.maxResults||20;t.replace(/\s*ORDER\s+BY\s+.*/i,"").trim()||(t=`created >= -365d ${t}`.trim());let r=`jql=${encodeURIComponent(t)}&maxResults=${e}&fields=summary,status,assignee,priority,updated,issuetype,project`,o=((await h(`/rest/api/3/search/jql?${r}`)).issues||[]).map(c=>({key:c.key,project:c.fields?.project?.key,summary:c.fields?.summary,status:c.fields?.status?.name,assignee:c.fields?.assignee?.displayName||"Unassigned",priority:c.fields?.priority?.name,type:c.fields?.issuetype?.name}));return JSON.stringify({count:o.length,issues:o})}case"jira_get_issue":{let t=n.issueKey;if(!t)return JSON.stringify({error:"issueKey is required"});let e=await h(`/rest/api/3/issue/${t}`);return JSON.stringify({key:e.key,project:e.fields?.project?.key,summary:e.fields?.summary,description:e.fields?.description?J(e.fields.description):e.fields?.description??null,status:e.fields?.status?.name,assignee:e.fields?.assignee?.displayName||"Unassigned",priority:e.fields?.priority?.name,type:e.fields?.issuetype?.name,labels:e.fields?.labels,created:e.fields?.created,updated:e.fields?.updated})}case"jira_create_issue":{let{projectKey:t,summary:e,issueType:i,description:r,priority:a,labels:o,assigneeId:c,moveToSprint:l,moveToActiveSprint:u,sprintId:d,sprintName:m,target:g}=n;if(!t||!e)return JSON.stringify({error:"projectKey and summary are required"});let p={requested:i||null,resolved:null,strategy:"none"},y=[];try{y=await se(t),p=Pe(i,y)}catch{}let f={project:{key:t},summary:e,issuetype:p?.resolved?.id?{id:p.resolved.id}:{name:i||"Task"}};a&&(f.priority={name:a}),o?.length&&(f.labels=o),c&&(f.assignee={id:c});let I=r?await B(j=>h("/rest/api/3/issue",{method:"POST",body:{fields:{...f,description:j}}}),r):await h("/rest/api/3/issue",{method:"POST",body:{fields:f}}),k={ok:!0,key:I.key,id:I.id,self:I.self};return p?.resolved&&(k.issueType=p.resolved.name,k.issueTypeResolution=p.strategy,p.strategy!=="exact"&&p.requested&&S(p.requested)!==S(p.resolved.name)&&(k.issueTypeWarning=`Requested "${p.requested}" is not available in ${t}; used "${p.resolved.name}" instead.`)),y.length>0&&(k.availableIssueTypes=y.map(j=>j.name)),(l||u)&&(k.sprintMove=await D({issueKey:I.key,projectKey:t,sprintId:d,sprintName:m,target:g})),JSON.stringify(k)}case"jira_list_sprints":{let{projectKey:t,state:e}=n,i=await re(t,e);return JSON.stringify({count:i.length,sprints:i})}case"jira_move_to_active_sprint":{let{issueKey:t,projectKey:e,sprintId:i,sprintName:r,target:a}=n||{},o=await D({issueKey:t,projectKey:e,sprintId:i,sprintName:r,target:a||"current"});return JSON.stringify(o)}case"jira_move_issue_to_sprint":{let{issueKey:t,projectKey:e,sprintId:i,sprintName:r,target:a}=n||{},o=await D({issueKey:t,projectKey:e,sprintId:i,sprintName:r,target:a});return JSON.stringify(o)}case"jira_get_sprint_issues":{let{sprintName:t,sprintId:e,projectKey:i,status:r,maxResults:a}=n;if(!t&&!e)return JSON.stringify({error:"sprintName or sprintId is required"});let o=a||50,c=e?`sprint = ${e}`:`sprint = "${t}"`,l=i?`project = ${i} AND `:"",u=r?` AND status = "${r}"`:"",d=`${l}${c}${u} ORDER BY status ASC, priority DESC`,m=`jql=${encodeURIComponent(d)}&maxResults=${o}&fields=summary,status,assignee,priority,issuetype,project`,g=await h(`/rest/api/3/search/jql?${m}`),p=(g.issues||[]).map(f=>({key:f.key,project:f.fields?.project?.key,summary:f.fields?.summary,status:f.fields?.status?.name,assignee:f.fields?.assignee?.displayName||"Unassigned",priority:f.fields?.priority?.name,type:f.fields?.issuetype?.name})),y={};for(let f of p)y[f.status]=(y[f.status]||0)+1;return JSON.stringify({count:p.length,total:g.total||p.length,statusCounts:y,issues:p})}case"jira_get_comments":{let{issueKey:t,maxResults:e}=n;if(!t)return JSON.stringify({error:"issueKey is required"});let r=await h(`/rest/api/3/issue/${t}/comment?maxResults=${e||50}&orderBy=-created`),a=(r.comments||[]).map(o=>{let c=o.body?.content?J(o.body):"";return{id:o.id,author:o.author?.displayName||"Unknown",body:c,created:o.created,updated:o.updated}});return JSON.stringify({count:a.length,total:r.total||a.length,comments:a})}case"jira_add_comment":{let{issueKey:t,body:e}=n;return!t||!e?JSON.stringify({error:"issueKey and body are required"}):(await B(i=>h(`/rest/api/3/issue/${t}/comment`,{method:"POST",body:{body:i}}),e),JSON.stringify({ok:!0,issueKey:t}))}case"jira_edit_issue":{let{issueKey:t,fields:e}=n;if(!t||!e)return JSON.stringify({error:"issueKey and fields are required"});if(typeof e.description=="string"){let{description:i,...r}=e;await B(a=>h(`/rest/api/3/issue/${t}`,{method:"PUT",body:{fields:{...r,description:a}}}),i)}else await h(`/rest/api/3/issue/${t}`,{method:"PUT",body:{fields:e}});return JSON.stringify({ok:!0,issueKey:t})}case"jira_transition_issue":{let{issueKey:t,transitionId:e,toStatus:i,statusName:r,status:a}=n;if(!t)return JSON.stringify({error:"issueKey is required"});let o=String(i||r||a||"").trim();if(!e&&!o){let d=((await h(`/rest/api/3/issue/${t}/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:t,availableTransitions:d})}let c=e;if(!c){let d=(await h(`/rest/api/3/issue/${t}/transitions`)).transitions||[],m=O(o),g=d.find(p=>O(p?.name||"")===m||O(p?.to?.name||"")===m);if(!g){let p=U(o);p.length>=2&&(g=d.find(y=>{let f=U(y?.name||""),I=U(y?.to?.name||""),k=f.length>=2&&(f.includes(p)||p.includes(f)),j=I.length>=2&&(I.includes(p)||p.includes(I));return k||j}))}if(!g){let p=d.map(k=>{let j=L(o,k?.name||""),ie=L(o,k?.to?.name||"");return{t:k,score:Math.max(j,ie)}}).sort((k,j)=>j.score-k.score),y=p[0],f=p[1];y&&y.score>=.45&&(!f||y.score-f.score>=.12)&&(g=y.t)}if(!g?.id)return JSON.stringify({ok:!1,error:`No transition matches target status: "${o}"`,issueKey:t,availableTransitions:d.map(p=>({id:p.id,name:p.name,to:p.to?.name}))});c=g.id}await h(`/rest/api/3/issue/${t}/transitions`,{method:"POST",body:{transition:{id:c}}});let l=await h(`/rest/api/3/issue/${t}?fields=status`);return JSON.stringify({ok:!0,issueKey:t,transitionId:c,statusAfter:l?.fields?.status?.name||null})}default:return JSON.stringify({error:`Unknown tool: ${s}`})}}catch(t){return JSON.stringify({error:t.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. Markdown is rendered (headings, **bold**, `code`, lists, - [ ] tasks, links, tables, > quotes, > [!NOTE]/[!TIP]/[!WARNING]/[!CAUTION] panels)."},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. Markdown is rendered: headings, **bold**, `code`, lists, - [ ] tasks, links, tables, > quotes, > [!NOTE]/[!TIP]/[!WARNING]/[!CAUTION] panels."}},required:["issueKey","body"]}},{name:"jira_edit_issue",description:"Update fields on a Jira issue (summary, description, story points, labels, priority). A string `description` is Markdown and is rendered.",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{Je as jiraApiCall,h as jiraFetch,Ze as jiraSkill,Me as resolveJiraCredential};
package/dist/kvMemory.js CHANGED
@@ -13,4 +13,4 @@ Tools:
13
13
  Use to record durable facts \u2014 e.g. dedup markers, prior decisions, summaries.
14
14
 
15
15
  Your namespace is added for you automatically; pass plain keys like
16
- "seen#owner/repo#42" or "lastRun".`,resolve(){let e=E();if(!e)return{command:null,args:[],env:{},description:this.description};let t={};for(let r of["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_USER_TOKEN","WORKFLOW_TYPE"])process.env[r]&&(t[r]=process.env[r]);return{type:"stdio",command:"node",args:[e,"../dist/kvMemory.js","kvMemorySkill"],env:t,description:this.description,alwaysLoad:!0}},async handleToolCall(e,t){try{switch(e){case"kv_recall":{let r=typeof t?.key=="string"?t.key.trim():"";if(!r)return JSON.stringify({error:"key is required"});let n=await p("recall",{scope:c(r)});return JSON.stringify(n)}case"kv_recall_prefix":{let r=typeof t?.keyPrefix=="string"?t.keyPrefix.trim():"";if(!r)return JSON.stringify({error:"keyPrefix is required"});let n=await p("recall-prefix",{scopePrefix:c(r)});return JSON.stringify(n)}case"kv_store":{let r=typeof t?.key=="string"?t.key.trim():"";if(!r)return JSON.stringify({error:"key is required"});if(typeof t?.content!="string"||t.content.length===0)return JSON.stringify({error:"content is required (non-empty string)"});let n={scope:c(r),content:t.content};t.metadata!=null&&(n.metadata=t.metadata);let o=await p("store",n);return JSON.stringify(o)}default:return JSON.stringify({error:`Unknown tool: ${e}`})}}catch(r){return JSON.stringify({error:r.message})}},tools:[{name:"kv_recall",description:'Recall the value you stored under a plain key (exact match). Your per-agent namespace is added automatically \u2014 pass a plain key like "seen#owner/repo#42".',input_schema:{type:"object",properties:{key:{type:"string",description:'Plain storage key (no namespace prefix needed) \u2014 e.g. "seen#owner/repo#42" or "lastRun".'}},required:["key"]}},{name:"kv_recall_prefix",description:'List your entries whose plain key STARTS WITH a prefix (e.g. "seen#"). Your per-agent namespace is added automatically. Capped at 25.',input_schema:{type:"object",properties:{keyPrefix:{type:"string",description:'Plain key prefix to match (no namespace prefix needed) \u2014 e.g. "seen#".'}},required:["keyPrefix"]}},{name:"kv_store",description:"Store (overwrite) a value under a plain key so a later run of yours can recall it. Your per-agent namespace is added automatically.",input_schema:{type:"object",properties:{key:{type:"string",description:"Plain storage key (no namespace prefix needed). Same key you recall by."},content:{type:"string",description:"The value to persist. Free-form markdown/text."},metadata:{type:"object",description:"Optional structured metadata."}},required:["key","content"]}}]};export{K as kvMemorySkill};
16
+ "seen#owner/repo#42" or "lastRun".`,resolve(){let e=E();if(!e)return{command:null,args:[],env:{},description:this.description};let t={};for(let r of["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_USER_TOKEN","WORKFLOW_TYPE"])process.env[r]&&(t[r]=process.env[r]);return{type:"stdio",command:"node",args:[e,"../dist/kvMemory.js","kvMemorySkill"],env:t,description:this.description}},async handleToolCall(e,t){try{switch(e){case"kv_recall":{let r=typeof t?.key=="string"?t.key.trim():"";if(!r)return JSON.stringify({error:"key is required"});let n=await p("recall",{scope:c(r)});return JSON.stringify(n)}case"kv_recall_prefix":{let r=typeof t?.keyPrefix=="string"?t.keyPrefix.trim():"";if(!r)return JSON.stringify({error:"keyPrefix is required"});let n=await p("recall-prefix",{scopePrefix:c(r)});return JSON.stringify(n)}case"kv_store":{let r=typeof t?.key=="string"?t.key.trim():"";if(!r)return JSON.stringify({error:"key is required"});if(typeof t?.content!="string"||t.content.length===0)return JSON.stringify({error:"content is required (non-empty string)"});let n={scope:c(r),content:t.content};t.metadata!=null&&(n.metadata=t.metadata);let o=await p("store",n);return JSON.stringify(o)}default:return JSON.stringify({error:`Unknown tool: ${e}`})}}catch(r){return JSON.stringify({error:r.message})}},tools:[{name:"kv_recall",description:'Recall the value you stored under a plain key (exact match). Your per-agent namespace is added automatically \u2014 pass a plain key like "seen#owner/repo#42".',input_schema:{type:"object",properties:{key:{type:"string",description:'Plain storage key (no namespace prefix needed) \u2014 e.g. "seen#owner/repo#42" or "lastRun".'}},required:["key"]}},{name:"kv_recall_prefix",description:'List your entries whose plain key STARTS WITH a prefix (e.g. "seen#"). Your per-agent namespace is added automatically. Capped at 25.',input_schema:{type:"object",properties:{keyPrefix:{type:"string",description:'Plain key prefix to match (no namespace prefix needed) \u2014 e.g. "seen#".'}},required:["keyPrefix"]}},{name:"kv_store",description:"Store (overwrite) a value under a plain key so a later run of yours can recall it. Your per-agent namespace is added automatically.",input_schema:{type:"object",properties:{key:{type:"string",description:"Plain storage key (no namespace prefix needed). Same key you recall by."},content:{type:"string",description:"The value to persist. Free-form markdown/text."},metadata:{type:"object",description:"Optional structured metadata."}},required:["key","content"]}}]};export{K as kvMemorySkill};
package/dist/lark.js CHANGED
@@ -5,4 +5,4 @@ You can send messages and replies on Lark. Use:
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 t=M();if(!t)return null;let e={};for(let n of["PROJECT_API_TOKEN","ZIBBY_USER_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[n]&&(e[n]=process.env[n]);return{type:"stdio",command:"node",args:[t],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(t,e){try{switch(t){case"lark_send_message":{if(!e.receive_id||!e.text)return JSON.stringify({error:"receive_id and text are required"});let n=R(e.receive_id),i=await p("POST",`/open-apis/im/v1/messages?receive_id_type=${n}`,{receive_id:e.receive_id,msg_type:"text",content:k(e.text)});return JSON.stringify({ok:!0,message_id:i.message_id})}case"lark_reply":{if(!e.message_id||!e.text)return JSON.stringify({error:"message_id and text are required"});let n=await p("POST",`/open-apis/im/v1/messages/${encodeURIComponent(e.message_id)}/reply`,{msg_type:"text",content:k(e.text)});return JSON.stringify({ok:!0,message_id:n.message_id})}case"lark_list_chats":{let n=e.page_size||50,r=((await p("GET",`/open-apis/im/v1/chats?page_size=${n}`)).items||[]).map(a=>({chat_id:a.chat_id,name:a.name,description:a.description,owner_id:a.owner_id,chat_mode:a.chat_mode}));return JSON.stringify({chats:r})}case"lark_get_chat_history":{if(!e.chat_id)return JSON.stringify({error:"chat_id is required"});let n=e.page_size||20,r=((await p("GET",`/open-apis/im/v1/messages?container_id_type=chat&container_id=${encodeURIComponent(e.chat_id)}&page_size=${n}&sort_type=ByCreateTimeDesc`)).items||[]).map(a=>({message_id:a.message_id,sender_id:a.sender?.id,sender_type:a.sender?.sender_type,msg_type:a.msg_type,content:a.body?.content,create_time:a.create_time}));return JSON.stringify({messages:r})}case"lark_lookup_user_by_email":{if(!e.email)return JSON.stringify({error:"email is required"});let i=((await p("POST","/open-apis/contact/v3/users/batch_get_id?user_id_type=open_id",{emails:[e.email]})).user_list||[]).find(r=>r.email===e.email&&r.user_id);return JSON.stringify(i?{ok:!0,user:{open_id:i.user_id,email:i.email,name:i.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 n=e.query.trim().toLowerCase();if(!n)return JSON.stringify({ok:!0,matches:[]});let i=Math.max(1,Math.min(Number(e.limit)||5,25)),r=200,d=((await p("GET","/open-apis/im/v1/chats?page_size=100")).items||[]).map(c=>c.chat_id),m=new Set,o=[];for(let c of d){if(o.length>=r)break;try{let s=await p("GET",`/open-apis/im/v1/chats/${encodeURIComponent(c)}/members?member_id_type=open_id&page_size=100`);for(let _ of s.items||[])if(!(!_.member_id||m.has(_.member_id))&&(m.add(_.member_id),o.push({open_id:_.member_id,name:_.name||""}),o.length>=r))break}catch(s){console.warn(`[lark] member scan failed for ${c}: ${s.message}`)}}let l=[];for(let c of o){let s=(c.name||"").toLowerCase();if(!s)continue;let _=0;s.includes(n)&&(_+=100-Math.abs(s.length-n.length)),s===n&&(_+=200),_>0&&l.push({open_id:c.open_id,name:c.name,_score:_})}return l.sort((c,s)=>s._score-c._score),JSON.stringify({ok:!0,matches:l.slice(0,i).map(({_score:c,...s})=>s),scanned:o.length})}default:return JSON.stringify({error:`Unknown tool: ${t}`})}}catch(n){return JSON.stringify({error:n.message})}}};function j(){u=null}export{j as _resetLarkTokenCache,G as larkSkill};
8
+ When responding to an incoming event, prefer lark_reply with the source message_id so the response threads cleanly.`,resolve(){let t=M();if(!t)return null;let e={};for(let n of["PROJECT_API_TOKEN","ZIBBY_USER_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[n]&&(e[n]=process.env[n]);return{type:"stdio",command:"node",args:[t],env:e}},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(t,e){try{switch(t){case"lark_send_message":{if(!e.receive_id||!e.text)return JSON.stringify({error:"receive_id and text are required"});let n=R(e.receive_id),i=await p("POST",`/open-apis/im/v1/messages?receive_id_type=${n}`,{receive_id:e.receive_id,msg_type:"text",content:k(e.text)});return JSON.stringify({ok:!0,message_id:i.message_id})}case"lark_reply":{if(!e.message_id||!e.text)return JSON.stringify({error:"message_id and text are required"});let n=await p("POST",`/open-apis/im/v1/messages/${encodeURIComponent(e.message_id)}/reply`,{msg_type:"text",content:k(e.text)});return JSON.stringify({ok:!0,message_id:n.message_id})}case"lark_list_chats":{let n=e.page_size||50,r=((await p("GET",`/open-apis/im/v1/chats?page_size=${n}`)).items||[]).map(a=>({chat_id:a.chat_id,name:a.name,description:a.description,owner_id:a.owner_id,chat_mode:a.chat_mode}));return JSON.stringify({chats:r})}case"lark_get_chat_history":{if(!e.chat_id)return JSON.stringify({error:"chat_id is required"});let n=e.page_size||20,r=((await p("GET",`/open-apis/im/v1/messages?container_id_type=chat&container_id=${encodeURIComponent(e.chat_id)}&page_size=${n}&sort_type=ByCreateTimeDesc`)).items||[]).map(a=>({message_id:a.message_id,sender_id:a.sender?.id,sender_type:a.sender?.sender_type,msg_type:a.msg_type,content:a.body?.content,create_time:a.create_time}));return JSON.stringify({messages:r})}case"lark_lookup_user_by_email":{if(!e.email)return JSON.stringify({error:"email is required"});let i=((await p("POST","/open-apis/contact/v3/users/batch_get_id?user_id_type=open_id",{emails:[e.email]})).user_list||[]).find(r=>r.email===e.email&&r.user_id);return JSON.stringify(i?{ok:!0,user:{open_id:i.user_id,email:i.email,name:i.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 n=e.query.trim().toLowerCase();if(!n)return JSON.stringify({ok:!0,matches:[]});let i=Math.max(1,Math.min(Number(e.limit)||5,25)),r=200,d=((await p("GET","/open-apis/im/v1/chats?page_size=100")).items||[]).map(c=>c.chat_id),m=new Set,o=[];for(let c of d){if(o.length>=r)break;try{let s=await p("GET",`/open-apis/im/v1/chats/${encodeURIComponent(c)}/members?member_id_type=open_id&page_size=100`);for(let _ of s.items||[])if(!(!_.member_id||m.has(_.member_id))&&(m.add(_.member_id),o.push({open_id:_.member_id,name:_.name||""}),o.length>=r))break}catch(s){console.warn(`[lark] member scan failed for ${c}: ${s.message}`)}}let l=[];for(let c of o){let s=(c.name||"").toLowerCase();if(!s)continue;let _=0;s.includes(n)&&(_+=100-Math.abs(s.length-n.length)),s===n&&(_+=200),_>0&&l.push({open_id:c.open_id,name:c.name,_score:_})}return l.sort((c,s)=>s._score-c._score),JSON.stringify({ok:!0,matches:l.slice(0,i).map(({_score:c,...s})=>s),scanned:o.length})}default:return JSON.stringify({error:`Unknown tool: ${t}`})}}catch(n){return JSON.stringify({error:n.message})}}};function j(){u=null}export{j as _resetLarkTokenCache,G as larkSkill};
@@ -1 +1 @@
1
- import{existsSync as j}from"fs";import{fileURLToPath as G}from"url";import{dirname as $,resolve as Y}from"path";var c=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",LARK_DOCS:"lark_docs",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",VIKUNJA:"vikunja",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),ie=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark Chat",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark App",connectPath:"/integrations?provider=lark_docs"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},vikunja:{id:"vikunja",name:"Vikunja",connectPath:"/integrations?provider=vikunja"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});import{resolveIntegrationToken as A}from"@zibby/core/backend-client.js";var N="https://open.larksuite.com",b=Object.freeze([Object.freeze({appId:"LARK_DOCS_APP_ID",appSecret:"LARK_DOCS_APP_SECRET",host:"LARK_DOCS_HOST"}),Object.freeze({appId:"LARK_APP_ID",appSecret:"LARK_APP_SECRET",host:"LARK_HOST"})]),O=Object.freeze(b.flatMap(t=>[t.appId,t.appSecret,t.host]));function g(t){let e=String(t||"").trim().replace(/\/+$/,"");return e?/^https?:\/\//i.test(e)?e:`https://${e}`:N}function P(t){let e=String(process.env[t.appId]||"").trim(),r=String(process.env[t.appSecret]||"").trim();return!e||!r?null:{appId:e,appSecret:r,host:g(process.env[t.host])}}async function v(){for(let t of b){let e=P(t);if(e)return e}try{let t=await A(c.LARK_DOCS);return{appId:t?.appId,appSecret:t?.appSecret,host:g(t?.host)}}catch(t){if(!/unknown provider/i.test(String(t?.message||"")))throw t;let e=await A(c.LARK);return{appId:e?.appId,appSecret:e?.appSecret,host:g(e?.host)}}}var y={api:{knob:"SKILL_API_TIMEOUT_MS",fallback:3e4},transfer:{knob:"SKILL_TRANSFER_TIMEOUT_MS",fallback:12e4},job:{knob:"SKILL_JOB_TIMEOUT_MS",fallback:3e5}};function U(t,e,r=process.env){let n=Number(r?.[t]);return Number.isFinite(n)&&n>0?Math.min(6e5,Math.max(1e3,Math.floor(n))):e}function C(t="api",e=process.env){let r=y[t]||y.api;return U(r.knob,r.fallback,e)}function x(t){return t?.name==="TimeoutError"||t?.name==="AbortError"}function K(t){try{return new URL(String(t?.url??t)).host||"unknown host"}catch{return"unknown host"}}function q(t,e){if(!t)return e;let r=new AbortController,n=s=>{r.signal.aborted||r.abort(s)},a=()=>n(t.reason),o=()=>n(e.reason);return r.signal.addEventListener("abort",()=>{t.removeEventListener("abort",a),e.removeEventListener("abort",o)},{once:!0}),t.aborted?n(t.reason):e.aborted?n(e.reason):(t.addEventListener("abort",a,{once:!0}),e.addEventListener("abort",o,{once:!0})),r.signal}async function k(t,e={},r={}){let n=r.kind||"api",a=(y[n]||y.api).knob,o=r.timeoutMs?Math.min(6e5,Math.max(1e3,Math.floor(r.timeoutMs))):C(n),s=e?.signal;if(s?.aborted)throw s.reason??new DOMException("This operation was aborted","AbortError");let d=AbortSignal.timeout(o),i=q(s,d);try{return await fetch(t,{...e,signal:i})}catch(u){throw d.aborted&&x(u)?new Error(`${r.what||"request"} TIMED OUT after ${o}ms against ${K(t)} (${a})`):u}}function F(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=$(G(import.meta.url)),e=Y(t,"..","bin","mcp-skill.mjs");return j(e)?e:null}var z=6e3*1e3,p=null,J=50,B=50,H=40,V=31,X=200,W=31,Q=50,D=50,L=Object.freeze(["daily","month"]),E=Object.freeze(["zh","en","ja"]),Z=Object.freeze(["employee_id","employee_no"]),ee=O;async function te(){let{appId:t,appSecret:e,host:r}=await v();if(p&&p.appId===t&&p.expiresAt>Date.now())return{token:p.token,host:r};let a=await(await k(`${r}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:t,app_secret:e})},{kind:"api",what:"Lark tenant_access_token"})).json();if(a.code!==0)throw new Error(`Lark tenant_access_token failed: ${a.msg||a.code}`);return p={token:a.tenant_access_token,expiresAt:Date.now()+z,appId:t},{token:a.tenant_access_token,host:r}}function re(t,e,r){let n=`Lark attendance API ${t} error ${e}: ${r||"(no message)"}`,a=t.includes("/attendance/v1/groups")?"attendance:rule:readonly (\u6253\u5361\u89C4\u5219/\u8003\u52E4\u7EC4 \u8BFB\u53D6)":t.includes("/contact/v3/users")?"contact:user.id:readonly (\u90AE\u7BB1 \u2192 \u7528\u6237 ID)":"attendance:task:readonly (\u6253\u5361\u7ED3\u679C/\u7EDF\u8BA1\u6570\u636E \u8BFB\u53D6)";return String(e)==="99991672"||/permission|scope|权限/i.test(String(r||""))?`${n}. The connected Lark app is missing the ${a} permission \u2014 add it in the Lark/Feishu developer console (\u5F00\u53D1\u8005\u540E\u53F0 > \u6743\u9650\u7BA1\u7406), then RE-PUBLISH a new app version so the scope takes effect.`:String(e)==="1220002"?`${n}. The tenant token was rejected \u2014 the Lark integration's app credentials look wrong; reconnect Lark in Integrations.`:String(e)==="1220001"?`${n}. Lark rejected the parameters \u2014 check the date range (yyyyMMdd, and within the documented span), stats_type, and that every user id matches the employeeType you passed.`:n}async function l(t,e,r){let{token:n,host:a}=await te(),o={method:t,headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json; charset=utf-8"}};t!=="GET"&&r!==void 0&&(o.body=JSON.stringify(r));let d=await(await k(`${a}${e}`,o,{kind:"api",what:`Lark Attendance ${t} ${e}`})).json();if(d.code!==0)throw new Error(re(e,d.code,d.msg));return d.data||{}}function w(t){if(t==null||t==="")return null;let e=String(t).trim().replace(/[-/.]/g,"");if(!/^\d{8}$/.test(e))return null;let r=Number(e.slice(0,4)),n=Number(e.slice(4,6)),a=Number(e.slice(6,8));if(r<1970||r>9999||n<1||n>12||a<1||a>31)return null;let o=new Date(Date.UTC(r,n-1,a));return o.getUTCFullYear()!==r||o.getUTCMonth()!==n-1||o.getUTCDate()!==a?null:Number(e)}function ne(t,e){let r=n=>Date.UTC(Math.floor(n/1e4),Math.floor(n%1e4/100)-1,n%100);return Math.floor((r(e)-r(t))/864e5)+1}function S(t,e){let r=w(t?.startDate??t?.start_date),n=w(t?.endDate??t?.end_date);if(!r||!n)return{error:"startDate and endDate are required, as yyyyMMdd (e.g. 20260401) or YYYY-MM-DD"};if(n<r)return{error:"endDate must not be earlier than startDate"};let a=ne(r,n);return a>e?{error:`Lark caps this query at ${e} days; the requested range is ${a} days. Split it into consecutive windows and merge the results.`}:{startDate:r,endDate:n}}function M(t,e){let r=t?.userIds??t?.user_ids,n=(Array.isArray(r)?r:typeof r=="string"?[r]:[]).map(a=>String(a||"").trim()).filter(Boolean);return n.length?n.length>e?{error:`Lark accepts at most ${e} user ids per call; ${n.length} were passed. Split into batches.`}:{ids:n}:{error:"userIds is required \u2014 attendance data is personal, so this tool never returns a whole tenant. Get the ids from larkattendance_get_group (bind_user_ids) or larkattendance_resolve_users."}}function m(t,e,r){let n=String(t||"").trim().toLowerCase();return e.includes(n)?n:r}function f(t){return m(t?.employeeType??t?.employee_type,Z,"employee_id")}function ae(t){return{code:String(t?.code||""),title:String(t?.title||""),fields:(Array.isArray(t?.child_fields)?t.child_fields:[]).map(e=>({code:String(e?.code||""),title:String(e?.title||""),...e?.time_unit?{timeUnit:String(e.time_unit)}:{}}))}}function se(t,e){let n=(Array.isArray(t?.datas)?t.datas:[]).filter(a=>!e||e.has(String(a?.code||""))||e.has(String(a?.title||""))).map(a=>({code:String(a?.code||""),title:String(a?.title||""),value:a?.value===void 0||a?.value===null?"":String(a.value),...a?.duration_num?{durationNum:a.duration_num}:{},...Array.isArray(a?.features)&&a.features.length?{features:a.features}:{}}));return{userId:String(t?.user_id||""),name:String(t?.name||""),fields:n}}function oe(t){return{userId:String(t?.user_id||""),name:String(t?.employee_name||""),day:t?.day??null,groupId:String(t?.group_id||""),shiftId:String(t?.shift_id||""),records:(Array.isArray(t?.records)?t.records:[]).map(e=>({checkInTime:e?.check_in_record?.check_time??null,checkInResult:String(e?.check_in_result||""),checkInResultSupplement:String(e?.check_in_result_supplement||""),checkInShiftTime:e?.check_in_shift_time??null,checkOutTime:e?.check_out_record?.check_time??null,checkOutResult:String(e?.check_out_result||""),checkOutResultSupplement:String(e?.check_out_result_supplement||""),checkOutShiftTime:e?.check_out_shift_time??null,...e?.task_shift_type===void 0?{}:{taskShiftType:e.task_shift_type}}))}}var ke={id:"lark-attendance",callsBackend:!0,serverName:"larkattendance",allowedTools:["mcp__larkattendance__*"],requiresIntegration:[c.LARK_DOCS,c.LARK],description:"Lark / Feishu attendance (\u8003\u52E4) \u2014 discover attendance groups and statistic fields, then read per-user work-hour / attendance statistics and clock-in records. Read-only.",envKeys:["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV",...ee],promptFragment:"## Lark Attendance (\u8003\u52E4)\nRead-only access to this tenant's Lark/Feishu attendance data. NOTHING about the tenant's setup is known in advance \u2014 group names, what counts as \"\u5DE5\u65F6\", and the per-tenant \u81EA\u5B9A\u4E49\u5B57\u6BB5 all differ \u2014 so DISCOVER before you fetch:\n1. `larkattendance_list_groups` \u2192 the \u8003\u52E4\u7EC4 that exist.\n2. `larkattendance_get_group` \u2192 that group's member user ids (the scope for every data call).\n3. `larkattendance_list_stats_fields` \u2192 every statistic column available for the period, INCLUDING this tenant's \u81EA\u5B9A\u4E49\u5B57\u6BB5. Match the user's words (\"\u5DE5\u65F6\", \"\u51FA\u52E4\u5929\u6570\", \u2026) against the returned `title`s and pass the matching `code`s onward.\n4. `larkattendance_query_stats` (aggregated numbers) and/or `larkattendance_query_records` (raw clock-in/out).\n`larkattendance_query_stats` also needs an `operatorUserId` \u2014 the attendance admin the query runs AS. Lark rejects the call without one, and it selects the saved statistics view that decides which columns come back, so this skill refuses to invent it: if the prompt did not supply one, ASK for it rather than substituting a member id.\nIdentity mapping: attendance ids are the tenant `user_id` (employee_id), NOT the `open_id` the `lark` skill's `lark_lookup_user_by_email` returns \u2014 use `larkattendance_resolve_users` to turn emails into attendance ids. For chat/DM routing keep using the `lark` skill's tools.\nAttendance is personal HR data: every data tool requires an explicit `userIds` list, and there is no whole-tenant dump. Report the numbers as returned; do not invent a column the tenant does not have.\nThese tools return { ok:false, error } on failure \u2014 read the error, it names the missing permission or the range limit.",resolve(){let t=F();if(!t)return null;let e={};for(let r of this.envKeys)process.env[r]&&(e[r]=process.env[r]);return{type:"stdio",command:"node",args:[t,"../dist/larkAttendance.js","larkAttendanceSkill"],env:e,description:this.description,alwaysLoad:!0}},async handleToolCall(t,e){try{switch(t){case"larkattendance_list_groups":{let r=Number(e?.pageSize??e?.page_size),n=Number.isFinite(r)&&r>0?Math.min(Math.floor(r),J):B,a=new URLSearchParams({page_size:String(n)}),o=String(e?.pageToken??e?.page_token??"").trim();o&&a.set("page_token",o);let s=await l("GET",`/open-apis/attendance/v1/groups?${a.toString()}`),d=(Array.isArray(s?.group_list)?s.group_list:[]).map(i=>({groupId:String(i?.group_id||""),groupName:String(i?.group_name||"")}));return JSON.stringify({ok:!0,count:d.length,groups:d,hasMore:!!s?.has_more,...s?.has_more&&s?.page_token?{pageToken:String(s.page_token)}:{}})}case"larkattendance_get_group":{let r=String(e?.groupId??e?.group_id??"").trim();if(!r)return JSON.stringify({ok:!1,error:"groupId is required (from larkattendance_list_groups)"});let n=f(e),a=new URLSearchParams({employee_type:n,dept_type:"open_id"}),o=await l("GET",`/open-apis/attendance/v1/groups/${encodeURIComponent(r)}?${a.toString()}`),s=d=>Array.isArray(d)?d.map(i=>String(i)):[];return JSON.stringify({ok:!0,groupId:String(o?.group_id||r),groupName:String(o?.group_name||""),employeeType:n,memberUserIds:s(o?.bind_user_ids),memberDeptIds:s(o?.bind_dept_ids),excludedUserIds:s(o?.except_user_ids),excludedDeptIds:s(o?.except_dept_ids),noPunchUserIds:s(o?.bind_default_user_ids),noPunchDeptIds:s(o?.bind_default_dept_ids),...Array.isArray(o?.group_leader_ids)?{leaderUserIds:s(o.group_leader_ids)}:{}})}case"larkattendance_list_stats_fields":{let r=S(e,H);if(r.error)return JSON.stringify({ok:!1,error:r.error});let n=m(e?.statsType??e?.stats_type,L,"month"),a=m(e?.locale,E,"zh"),o=f(e),d=(await l("POST",`/open-apis/attendance/v1/user_stats_fields/query?employee_type=${encodeURIComponent(o)}`,{locale:a,stats_type:n,start_date:r.startDate,end_date:r.endDate}))?.user_stats_field||{},i=(Array.isArray(d?.fields)?d.fields:[]).map(ae);return JSON.stringify({ok:!0,statsType:n,locale:a,startDate:r.startDate,endDate:r.endDate,fieldGroupCount:i.length,fieldGroups:i})}case"larkattendance_query_stats":{let r=S(e,V);if(r.error)return JSON.stringify({ok:!1,error:r.error});let n=M(e,X);if(n.error)return JSON.stringify({ok:!1,error:n.error});let a=m(e?.statsType??e?.stats_type,L,"month"),o=m(e?.locale,E,"zh"),s=f(e),d=String(e?.operatorUserId??e?.user_id??"").trim();if(!d)return JSON.stringify({ok:!1,error:'operatorUserId is required by Lark (the API rejects this call with 1220001 "Need user_id"). It is the OPERATOR performing the query \u2014 a third id, not one of `userIds` and not `employeeType`. It must be a user whose saved attendance statistics view contains the columns you want (normally an attendance admin), because that view decides which columns Lark returns. This skill will NOT pick one for you: substituting an arbitrary member would silently return the wrong column set instead of failing. Ask the caller/prompt for the attendance admin\'s user id, or resolve their email with larkattendance_resolve_users.'});let i={locale:o,stats_type:a,start_date:r.startDate,end_date:r.endDate,user_ids:n.ids,user_id:d,need_history:e?.needHistory===!0,current_group_only:e?.currentGroupOnly===!0},u=Array.isArray(e?.fieldCodes)?e.fieldCodes:null,I=u?u.map(_=>String(_||"").trim()).filter(Boolean):[],R=I.length?new Set(I):null,h=await l("POST",`/open-apis/attendance/v1/user_stats_datas/query?employee_type=${encodeURIComponent(s)}`,i),T=(Array.isArray(h?.user_datas)?h.user_datas:[]).map(_=>se(_,R));return JSON.stringify({ok:!0,statsType:a,startDate:r.startDate,endDate:r.endDate,employeeType:s,userCount:T.length,users:T,invalidUserIds:Array.isArray(h?.invalid_user_list)?h.invalid_user_list.map(_=>String(_)):[]})}case"larkattendance_query_records":{let r=S({startDate:e?.startDate??e?.checkDateFrom??e?.check_date_from,endDate:e?.endDate??e?.checkDateTo??e?.check_date_to},W);if(r.error)return JSON.stringify({ok:!1,error:r.error});let n=M(e,Q);if(n.error)return JSON.stringify({ok:!1,error:n.error});let a=f(e),o=new URLSearchParams({employee_type:a,ignore_invalid_users:"true"});e?.includeTerminatedUser===!0&&o.set("include_terminated_user","true");let s=await l("POST",`/open-apis/attendance/v1/user_tasks/query?${o.toString()}`,{user_ids:n.ids,check_date_from:r.startDate,check_date_to:r.endDate,need_overtime_result:e?.needOvertimeResult===!0}),d=(Array.isArray(s?.user_task_results)?s.user_task_results:[]).map(oe);return JSON.stringify({ok:!0,startDate:r.startDate,endDate:r.endDate,employeeType:a,count:d.length,results:d,invalidUserIds:Array.isArray(s?.invalid_user_ids)?s.invalid_user_ids.map(i=>String(i)):[],unauthorizedUserIds:Array.isArray(s?.unauthorized_user_ids)?s.unauthorized_user_ids.map(i=>String(i)):[]})}case"larkattendance_resolve_users":{let r=e?.emails,n=(Array.isArray(r)?r:typeof r=="string"?[r]:[]).map(i=>String(i||"").trim()).filter(Boolean);if(!n.length)return JSON.stringify({ok:!1,error:"emails is required (a non-empty array of work email addresses)"});if(n.length>D)return JSON.stringify({ok:!1,error:`At most ${D} emails per call; ${n.length} were passed. Split into batches.`});let a=await l("POST","/open-apis/contact/v3/users/batch_get_id?user_id_type=user_id",{emails:n}),s=(Array.isArray(a?.user_list)?a.user_list:[]).filter(i=>i?.user_id).map(i=>({email:String(i?.email||""),userId:String(i.user_id)})),d=new Set(s.map(i=>i.email));return JSON.stringify({ok:!0,employeeType:"employee_id",users:s,notFound:n.filter(i=>!d.has(i))})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${t}`})}}catch(r){return JSON.stringify({ok:!1,error:r?.message||String(r)})}},tools:[{name:"larkattendance_list_groups",description:"DISCOVERY (start here). List this tenant's Lark/Feishu attendance groups (\u8003\u52E4\u7EC4) \u2014 their names are tenant-specific, so never assume one. Returns { ok, count, groups:[{ groupId, groupName }], hasMore, pageToken }. When hasMore is true, call again with pageToken to continue. Needs the attendance:rule:readonly permission on the connected Lark app.",input_schema:{type:"object",properties:{pageSize:{type:"number",description:"Groups per page, 1-50 (default 50)."},pageToken:{type:"string",description:"Cursor from a previous call's pageToken \u2014 omit for the first page."}}}},{name:"larkattendance_get_group",description:"DISCOVERY. Read one attendance group and, crucially, ITS MEMBERS \u2014 memberUserIds is the scope you pass as userIds to larkattendance_query_stats / larkattendance_query_records (a real group can hold hundreds, so expect to batch). Returns { ok, groupId, groupName, employeeType, memberUserIds, memberDeptIds, excludedUserIds, excludedDeptIds, noPunchUserIds, noPunchDeptIds }. THREE different exclusion lists, do not conflate them: excludedUserIds are removed from the group; noPunchUserIds are in the group but not required to clock in (they still have statistics). Members bound by DEPARTMENT appear in memberDeptIds, not memberUserIds \u2014 resolve those people another way (e.g. larkattendance_resolve_users from emails you already have). Lark does not reliably return the group leaders here, so leaderUserIds may be absent \u2014 never source the query operator id from this tool.",input_schema:{type:"object",properties:{groupId:{type:"string",description:"Attendance group id from larkattendance_list_groups."},employeeType:{type:"string",description:"Which id form to return members in: 'employee_id' (tenant user_id, the default and what every other tool here expects) or 'employee_no' (\u5DE5\u53F7)."}},required:["groupId"]}},{name:"larkattendance_list_stats_fields",description:'DISCOVERY \u2014 call this BEFORE larkattendance_query_stats. Lists every statistic column this tenant has for the period, grouped (e.g. \u57FA\u672C\u4FE1\u606F / \u51FA\u52E4\u7EDF\u8BA1 / \u5F02\u5E38\u7EDF\u8BA1 / \u6BCF\u65E5\u7EDF\u8BA1 / \u81EA\u5B9A\u4E49\u5B57\u6BB5). The \u81EA\u5B9A\u4E49\u5B57\u6BB5 group is tenant-defined and exists nowhere else, so this is the only way to learn its codes. Match the user\'s wording ("\u5DE5\u65F6", "\u51FA\u52E4\u5929\u6570", "\u8FDF\u5230\u6B21\u6570") against the returned `title`s and pass the matching `code`s as fieldCodes to larkattendance_query_stats. Returns { ok, statsType, locale, startDate, endDate, fieldGroups:[{ code, title, fields:[{ code, title, timeUnit }] }] }. Range is capped at 40 days.',input_schema:{type:"object",properties:{startDate:{type:"string",description:"Period start as yyyyMMdd (20260401) or YYYY-MM-DD."},endDate:{type:"string",description:"Period end, same format. At most 40 days after startDate."},statsType:{type:"string",description:"'month' (default) for period totals, or 'daily' for per-day columns."},locale:{type:"string",description:"Field-title language: 'zh' (default), 'en', or 'ja'."},employeeType:{type:"string",description:"'employee_id' (default) or 'employee_no'."}},required:["startDate","endDate"]}},{name:"larkattendance_query_stats",description:'The numbers: per-user attendance statistics (work hours, attendance days, exceptions, custom fields) for a period. Needs THREE separate things: userIds (whose data you want, max 200), operatorUserId (WHO IS ASKING \u2014 required by Lark, see below), and the date range (max 31 days). Attendance is personal data and there is no whole-tenant read; get ids from larkattendance_get_group or larkattendance_resolve_users. Returns { ok, statsType, startDate, endDate, users:[{ userId, name, fields:[{ code, title, value, durationNum? }] }], invalidUserIds }. Values are STRINGS exactly as Lark returns them \u2014 report them as-is, do not re-derive. EXPECT MANY COLUMNS: a one-month query returns roughly 40+ cells per person, because Lark mixes period totals (e.g. an "actual attendance hours" column, late/early counts) with identity columns (department, employee number) AND one column PER DAY of the range \u2014 so match on `title` rather than assuming a short list, and pass fieldCodes to trim. The exact set of columns follows the SAVED STATISTICS VIEW of the operatorUserId (\u8003\u52E4 > \u7EDF\u8BA1\u8BBE\u7F6E), so if a field that larkattendance_list_stats_fields advertises is missing from the result, it is switched off in that operator\'s view and an admin must enable it \u2014 or you are querying as the wrong operator.',input_schema:{type:"object",properties:{userIds:{type:"array",items:{type:"string"},description:"The people to report on, in the employeeType id form. Max 200. Required."},startDate:{type:"string",description:"Period start as yyyyMMdd (20260401) or YYYY-MM-DD."},endDate:{type:"string",description:"Period end, same format. At most 31 days after startDate."},statsType:{type:"string",description:"'month' (default) for period totals, or 'daily' for per-day rows."},fieldCodes:{type:"array",items:{type:"string"},description:"Optional filter \u2014 only these field codes/titles are returned. Omit to get every column."},locale:{type:"string",description:"Field-title language: 'zh' (default), 'en', or 'ja'."},employeeType:{type:"string",description:"'employee_id' (default) or 'employee_no' \u2014 must match the form of userIds."},operatorUserId:{type:"string",description:'REQUIRED. The user id of the OPERATOR performing the query \u2014 a third id, distinct from `userIds` (whose data you want) and from `employeeType` (the id form). Lark rejects the call without it (1220001 "Need user_id"). It should be an attendance admin whose saved statistics view contains the columns you need, because that view decides which columns come back. This skill will not pick one for you \u2014 get it from the caller/prompt, or from an email via larkattendance_resolve_users.'},needHistory:{type:"boolean",description:"Include transferred/departed staff (default false)."},currentGroupOnly:{type:"boolean",description:"Restrict to the user's current attendance group (default false)."}},required:["userIds","operatorUserId","startDate","endDate"]}},{name:"larkattendance_query_records",description:"The raw clock-in/out results (\u6253\u5361\u7ED3\u679C) per person per day \u2014 use when the aggregated statistics are not enough (e.g. to show which days were late, or the actual punch times). REQUIRES an explicit userIds list (max 50); range capped at 31 days. Returns { ok, results:[{ userId, name, day, groupId, shiftId, records:[{ checkInTime, checkInResult, checkOutTime, checkOutResult, checkInShiftTime, checkOutShiftTime }] }], invalidUserIds, unauthorizedUserIds }. Unknown ids are reported in invalidUserIds rather than failing the batch. Result values are Lark's own enums (Normal / Early / Late / Lack / \u2026).",input_schema:{type:"object",properties:{userIds:{type:"array",items:{type:"string"},description:"The people to read, in the employeeType id form. Max 50. Required."},startDate:{type:"string",description:"First day as yyyyMMdd (20260401) or YYYY-MM-DD."},endDate:{type:"string",description:"Last day, same format. At most 31 days after startDate."},employeeType:{type:"string",description:"'employee_id' (default) or 'employee_no' \u2014 must match the form of userIds."},needOvertimeResult:{type:"boolean",description:"Also return overtime shift records (default false)."},includeTerminatedUser:{type:"boolean",description:"Include departed employees (default false)."}},required:["userIds","startDate","endDate"]}},{name:"larkattendance_resolve_users",description:"Map work EMAIL addresses to the attendance user ids (tenant user_id = the attendance API's employee_id). Use this to line a report's people up with attendance rows. NOTE this is deliberately different from the `lark` skill's lark_lookup_user_by_email, which returns an open_id for chat/DM \u2014 the attendance endpoints reject open_ids. Max 50 emails per call. Returns { ok, employeeType:\"employee_id\", users:[{ email, userId }], notFound:[email] }. Needs the contact:user.id:readonly permission.",input_schema:{type:"object",properties:{emails:{type:"array",items:{type:"string"},description:"Work email addresses to resolve. Max 50."}},required:["emails"]}}]};function Se(){p=null}export{ee as LARK_ATTENDANCE_APP_ENV_KEYS,Se as _resetLarkAttendanceTokenCache,ne as daySpan,ke as larkAttendanceSkill,w as toLarkDate};
1
+ import{existsSync as j}from"fs";import{fileURLToPath as G}from"url";import{dirname as $,resolve as Y}from"path";var c=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",LARK_DOCS:"lark_docs",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",VIKUNJA:"vikunja",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),ie=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark Chat",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark App",connectPath:"/integrations?provider=lark_docs"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},vikunja:{id:"vikunja",name:"Vikunja",connectPath:"/integrations?provider=vikunja"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});import{resolveIntegrationToken as A}from"@zibby/core/backend-client.js";var N="https://open.larksuite.com",b=Object.freeze([Object.freeze({appId:"LARK_DOCS_APP_ID",appSecret:"LARK_DOCS_APP_SECRET",host:"LARK_DOCS_HOST"}),Object.freeze({appId:"LARK_APP_ID",appSecret:"LARK_APP_SECRET",host:"LARK_HOST"})]),O=Object.freeze(b.flatMap(t=>[t.appId,t.appSecret,t.host]));function g(t){let e=String(t||"").trim().replace(/\/+$/,"");return e?/^https?:\/\//i.test(e)?e:`https://${e}`:N}function P(t){let e=String(process.env[t.appId]||"").trim(),r=String(process.env[t.appSecret]||"").trim();return!e||!r?null:{appId:e,appSecret:r,host:g(process.env[t.host])}}async function v(){for(let t of b){let e=P(t);if(e)return e}try{let t=await A(c.LARK_DOCS);return{appId:t?.appId,appSecret:t?.appSecret,host:g(t?.host)}}catch(t){if(!/unknown provider/i.test(String(t?.message||"")))throw t;let e=await A(c.LARK);return{appId:e?.appId,appSecret:e?.appSecret,host:g(e?.host)}}}var y={api:{knob:"SKILL_API_TIMEOUT_MS",fallback:3e4},transfer:{knob:"SKILL_TRANSFER_TIMEOUT_MS",fallback:12e4},job:{knob:"SKILL_JOB_TIMEOUT_MS",fallback:3e5}};function U(t,e,r=process.env){let n=Number(r?.[t]);return Number.isFinite(n)&&n>0?Math.min(6e5,Math.max(1e3,Math.floor(n))):e}function C(t="api",e=process.env){let r=y[t]||y.api;return U(r.knob,r.fallback,e)}function x(t){return t?.name==="TimeoutError"||t?.name==="AbortError"}function K(t){try{return new URL(String(t?.url??t)).host||"unknown host"}catch{return"unknown host"}}function q(t,e){if(!t)return e;let r=new AbortController,n=s=>{r.signal.aborted||r.abort(s)},a=()=>n(t.reason),o=()=>n(e.reason);return r.signal.addEventListener("abort",()=>{t.removeEventListener("abort",a),e.removeEventListener("abort",o)},{once:!0}),t.aborted?n(t.reason):e.aborted?n(e.reason):(t.addEventListener("abort",a,{once:!0}),e.addEventListener("abort",o,{once:!0})),r.signal}async function k(t,e={},r={}){let n=r.kind||"api",a=(y[n]||y.api).knob,o=r.timeoutMs?Math.min(6e5,Math.max(1e3,Math.floor(r.timeoutMs))):C(n),s=e?.signal;if(s?.aborted)throw s.reason??new DOMException("This operation was aborted","AbortError");let d=AbortSignal.timeout(o),i=q(s,d);try{return await fetch(t,{...e,signal:i})}catch(u){throw d.aborted&&x(u)?new Error(`${r.what||"request"} TIMED OUT after ${o}ms against ${K(t)} (${a})`):u}}function F(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=$(G(import.meta.url)),e=Y(t,"..","bin","mcp-skill.mjs");return j(e)?e:null}var z=6e3*1e3,p=null,J=50,B=50,H=40,V=31,X=200,W=31,Q=50,D=50,L=Object.freeze(["daily","month"]),E=Object.freeze(["zh","en","ja"]),Z=Object.freeze(["employee_id","employee_no"]),ee=O;async function te(){let{appId:t,appSecret:e,host:r}=await v();if(p&&p.appId===t&&p.expiresAt>Date.now())return{token:p.token,host:r};let a=await(await k(`${r}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:t,app_secret:e})},{kind:"api",what:"Lark tenant_access_token"})).json();if(a.code!==0)throw new Error(`Lark tenant_access_token failed: ${a.msg||a.code}`);return p={token:a.tenant_access_token,expiresAt:Date.now()+z,appId:t},{token:a.tenant_access_token,host:r}}function re(t,e,r){let n=`Lark attendance API ${t} error ${e}: ${r||"(no message)"}`,a=t.includes("/attendance/v1/groups")?"attendance:rule:readonly (\u6253\u5361\u89C4\u5219/\u8003\u52E4\u7EC4 \u8BFB\u53D6)":t.includes("/contact/v3/users")?"contact:user.id:readonly (\u90AE\u7BB1 \u2192 \u7528\u6237 ID)":"attendance:task:readonly (\u6253\u5361\u7ED3\u679C/\u7EDF\u8BA1\u6570\u636E \u8BFB\u53D6)";return String(e)==="99991672"||/permission|scope|权限/i.test(String(r||""))?`${n}. The connected Lark app is missing the ${a} permission \u2014 add it in the Lark/Feishu developer console (\u5F00\u53D1\u8005\u540E\u53F0 > \u6743\u9650\u7BA1\u7406), then RE-PUBLISH a new app version so the scope takes effect.`:String(e)==="1220002"?`${n}. The tenant token was rejected \u2014 the Lark integration's app credentials look wrong; reconnect Lark in Integrations.`:String(e)==="1220001"?`${n}. Lark rejected the parameters \u2014 check the date range (yyyyMMdd, and within the documented span), stats_type, and that every user id matches the employeeType you passed.`:n}async function l(t,e,r){let{token:n,host:a}=await te(),o={method:t,headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json; charset=utf-8"}};t!=="GET"&&r!==void 0&&(o.body=JSON.stringify(r));let d=await(await k(`${a}${e}`,o,{kind:"api",what:`Lark Attendance ${t} ${e}`})).json();if(d.code!==0)throw new Error(re(e,d.code,d.msg));return d.data||{}}function w(t){if(t==null||t==="")return null;let e=String(t).trim().replace(/[-/.]/g,"");if(!/^\d{8}$/.test(e))return null;let r=Number(e.slice(0,4)),n=Number(e.slice(4,6)),a=Number(e.slice(6,8));if(r<1970||r>9999||n<1||n>12||a<1||a>31)return null;let o=new Date(Date.UTC(r,n-1,a));return o.getUTCFullYear()!==r||o.getUTCMonth()!==n-1||o.getUTCDate()!==a?null:Number(e)}function ne(t,e){let r=n=>Date.UTC(Math.floor(n/1e4),Math.floor(n%1e4/100)-1,n%100);return Math.floor((r(e)-r(t))/864e5)+1}function S(t,e){let r=w(t?.startDate??t?.start_date),n=w(t?.endDate??t?.end_date);if(!r||!n)return{error:"startDate and endDate are required, as yyyyMMdd (e.g. 20260401) or YYYY-MM-DD"};if(n<r)return{error:"endDate must not be earlier than startDate"};let a=ne(r,n);return a>e?{error:`Lark caps this query at ${e} days; the requested range is ${a} days. Split it into consecutive windows and merge the results.`}:{startDate:r,endDate:n}}function M(t,e){let r=t?.userIds??t?.user_ids,n=(Array.isArray(r)?r:typeof r=="string"?[r]:[]).map(a=>String(a||"").trim()).filter(Boolean);return n.length?n.length>e?{error:`Lark accepts at most ${e} user ids per call; ${n.length} were passed. Split into batches.`}:{ids:n}:{error:"userIds is required \u2014 attendance data is personal, so this tool never returns a whole tenant. Get the ids from larkattendance_get_group (bind_user_ids) or larkattendance_resolve_users."}}function m(t,e,r){let n=String(t||"").trim().toLowerCase();return e.includes(n)?n:r}function f(t){return m(t?.employeeType??t?.employee_type,Z,"employee_id")}function ae(t){return{code:String(t?.code||""),title:String(t?.title||""),fields:(Array.isArray(t?.child_fields)?t.child_fields:[]).map(e=>({code:String(e?.code||""),title:String(e?.title||""),...e?.time_unit?{timeUnit:String(e.time_unit)}:{}}))}}function se(t,e){let n=(Array.isArray(t?.datas)?t.datas:[]).filter(a=>!e||e.has(String(a?.code||""))||e.has(String(a?.title||""))).map(a=>({code:String(a?.code||""),title:String(a?.title||""),value:a?.value===void 0||a?.value===null?"":String(a.value),...a?.duration_num?{durationNum:a.duration_num}:{},...Array.isArray(a?.features)&&a.features.length?{features:a.features}:{}}));return{userId:String(t?.user_id||""),name:String(t?.name||""),fields:n}}function oe(t){return{userId:String(t?.user_id||""),name:String(t?.employee_name||""),day:t?.day??null,groupId:String(t?.group_id||""),shiftId:String(t?.shift_id||""),records:(Array.isArray(t?.records)?t.records:[]).map(e=>({checkInTime:e?.check_in_record?.check_time??null,checkInResult:String(e?.check_in_result||""),checkInResultSupplement:String(e?.check_in_result_supplement||""),checkInShiftTime:e?.check_in_shift_time??null,checkOutTime:e?.check_out_record?.check_time??null,checkOutResult:String(e?.check_out_result||""),checkOutResultSupplement:String(e?.check_out_result_supplement||""),checkOutShiftTime:e?.check_out_shift_time??null,...e?.task_shift_type===void 0?{}:{taskShiftType:e.task_shift_type}}))}}var ke={id:"lark-attendance",callsBackend:!0,serverName:"larkattendance",allowedTools:["mcp__larkattendance__*"],requiresIntegration:[c.LARK_DOCS,c.LARK],description:"Lark / Feishu attendance (\u8003\u52E4) \u2014 discover attendance groups and statistic fields, then read per-user work-hour / attendance statistics and clock-in records. Read-only.",envKeys:["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV",...ee],promptFragment:"## Lark Attendance (\u8003\u52E4)\nRead-only access to this tenant's Lark/Feishu attendance data. NOTHING about the tenant's setup is known in advance \u2014 group names, what counts as \"\u5DE5\u65F6\", and the per-tenant \u81EA\u5B9A\u4E49\u5B57\u6BB5 all differ \u2014 so DISCOVER before you fetch:\n1. `larkattendance_list_groups` \u2192 the \u8003\u52E4\u7EC4 that exist.\n2. `larkattendance_get_group` \u2192 that group's member user ids (the scope for every data call).\n3. `larkattendance_list_stats_fields` \u2192 every statistic column available for the period, INCLUDING this tenant's \u81EA\u5B9A\u4E49\u5B57\u6BB5. Match the user's words (\"\u5DE5\u65F6\", \"\u51FA\u52E4\u5929\u6570\", \u2026) against the returned `title`s and pass the matching `code`s onward.\n4. `larkattendance_query_stats` (aggregated numbers) and/or `larkattendance_query_records` (raw clock-in/out).\n`larkattendance_query_stats` also needs an `operatorUserId` \u2014 the attendance admin the query runs AS. Lark rejects the call without one, and it selects the saved statistics view that decides which columns come back, so this skill refuses to invent it: if the prompt did not supply one, ASK for it rather than substituting a member id.\nIdentity mapping: attendance ids are the tenant `user_id` (employee_id), NOT the `open_id` the `lark` skill's `lark_lookup_user_by_email` returns \u2014 use `larkattendance_resolve_users` to turn emails into attendance ids. For chat/DM routing keep using the `lark` skill's tools.\nAttendance is personal HR data: every data tool requires an explicit `userIds` list, and there is no whole-tenant dump. Report the numbers as returned; do not invent a column the tenant does not have.\nThese tools return { ok:false, error } on failure \u2014 read the error, it names the missing permission or the range limit.",resolve(){let t=F();if(!t)return null;let e={};for(let r of this.envKeys)process.env[r]&&(e[r]=process.env[r]);return{type:"stdio",command:"node",args:[t,"../dist/larkAttendance.js","larkAttendanceSkill"],env:e,description:this.description}},async handleToolCall(t,e){try{switch(t){case"larkattendance_list_groups":{let r=Number(e?.pageSize??e?.page_size),n=Number.isFinite(r)&&r>0?Math.min(Math.floor(r),J):B,a=new URLSearchParams({page_size:String(n)}),o=String(e?.pageToken??e?.page_token??"").trim();o&&a.set("page_token",o);let s=await l("GET",`/open-apis/attendance/v1/groups?${a.toString()}`),d=(Array.isArray(s?.group_list)?s.group_list:[]).map(i=>({groupId:String(i?.group_id||""),groupName:String(i?.group_name||"")}));return JSON.stringify({ok:!0,count:d.length,groups:d,hasMore:!!s?.has_more,...s?.has_more&&s?.page_token?{pageToken:String(s.page_token)}:{}})}case"larkattendance_get_group":{let r=String(e?.groupId??e?.group_id??"").trim();if(!r)return JSON.stringify({ok:!1,error:"groupId is required (from larkattendance_list_groups)"});let n=f(e),a=new URLSearchParams({employee_type:n,dept_type:"open_id"}),o=await l("GET",`/open-apis/attendance/v1/groups/${encodeURIComponent(r)}?${a.toString()}`),s=d=>Array.isArray(d)?d.map(i=>String(i)):[];return JSON.stringify({ok:!0,groupId:String(o?.group_id||r),groupName:String(o?.group_name||""),employeeType:n,memberUserIds:s(o?.bind_user_ids),memberDeptIds:s(o?.bind_dept_ids),excludedUserIds:s(o?.except_user_ids),excludedDeptIds:s(o?.except_dept_ids),noPunchUserIds:s(o?.bind_default_user_ids),noPunchDeptIds:s(o?.bind_default_dept_ids),...Array.isArray(o?.group_leader_ids)?{leaderUserIds:s(o.group_leader_ids)}:{}})}case"larkattendance_list_stats_fields":{let r=S(e,H);if(r.error)return JSON.stringify({ok:!1,error:r.error});let n=m(e?.statsType??e?.stats_type,L,"month"),a=m(e?.locale,E,"zh"),o=f(e),d=(await l("POST",`/open-apis/attendance/v1/user_stats_fields/query?employee_type=${encodeURIComponent(o)}`,{locale:a,stats_type:n,start_date:r.startDate,end_date:r.endDate}))?.user_stats_field||{},i=(Array.isArray(d?.fields)?d.fields:[]).map(ae);return JSON.stringify({ok:!0,statsType:n,locale:a,startDate:r.startDate,endDate:r.endDate,fieldGroupCount:i.length,fieldGroups:i})}case"larkattendance_query_stats":{let r=S(e,V);if(r.error)return JSON.stringify({ok:!1,error:r.error});let n=M(e,X);if(n.error)return JSON.stringify({ok:!1,error:n.error});let a=m(e?.statsType??e?.stats_type,L,"month"),o=m(e?.locale,E,"zh"),s=f(e),d=String(e?.operatorUserId??e?.user_id??"").trim();if(!d)return JSON.stringify({ok:!1,error:'operatorUserId is required by Lark (the API rejects this call with 1220001 "Need user_id"). It is the OPERATOR performing the query \u2014 a third id, not one of `userIds` and not `employeeType`. It must be a user whose saved attendance statistics view contains the columns you want (normally an attendance admin), because that view decides which columns Lark returns. This skill will NOT pick one for you: substituting an arbitrary member would silently return the wrong column set instead of failing. Ask the caller/prompt for the attendance admin\'s user id, or resolve their email with larkattendance_resolve_users.'});let i={locale:o,stats_type:a,start_date:r.startDate,end_date:r.endDate,user_ids:n.ids,user_id:d,need_history:e?.needHistory===!0,current_group_only:e?.currentGroupOnly===!0},u=Array.isArray(e?.fieldCodes)?e.fieldCodes:null,I=u?u.map(_=>String(_||"").trim()).filter(Boolean):[],R=I.length?new Set(I):null,h=await l("POST",`/open-apis/attendance/v1/user_stats_datas/query?employee_type=${encodeURIComponent(s)}`,i),T=(Array.isArray(h?.user_datas)?h.user_datas:[]).map(_=>se(_,R));return JSON.stringify({ok:!0,statsType:a,startDate:r.startDate,endDate:r.endDate,employeeType:s,userCount:T.length,users:T,invalidUserIds:Array.isArray(h?.invalid_user_list)?h.invalid_user_list.map(_=>String(_)):[]})}case"larkattendance_query_records":{let r=S({startDate:e?.startDate??e?.checkDateFrom??e?.check_date_from,endDate:e?.endDate??e?.checkDateTo??e?.check_date_to},W);if(r.error)return JSON.stringify({ok:!1,error:r.error});let n=M(e,Q);if(n.error)return JSON.stringify({ok:!1,error:n.error});let a=f(e),o=new URLSearchParams({employee_type:a,ignore_invalid_users:"true"});e?.includeTerminatedUser===!0&&o.set("include_terminated_user","true");let s=await l("POST",`/open-apis/attendance/v1/user_tasks/query?${o.toString()}`,{user_ids:n.ids,check_date_from:r.startDate,check_date_to:r.endDate,need_overtime_result:e?.needOvertimeResult===!0}),d=(Array.isArray(s?.user_task_results)?s.user_task_results:[]).map(oe);return JSON.stringify({ok:!0,startDate:r.startDate,endDate:r.endDate,employeeType:a,count:d.length,results:d,invalidUserIds:Array.isArray(s?.invalid_user_ids)?s.invalid_user_ids.map(i=>String(i)):[],unauthorizedUserIds:Array.isArray(s?.unauthorized_user_ids)?s.unauthorized_user_ids.map(i=>String(i)):[]})}case"larkattendance_resolve_users":{let r=e?.emails,n=(Array.isArray(r)?r:typeof r=="string"?[r]:[]).map(i=>String(i||"").trim()).filter(Boolean);if(!n.length)return JSON.stringify({ok:!1,error:"emails is required (a non-empty array of work email addresses)"});if(n.length>D)return JSON.stringify({ok:!1,error:`At most ${D} emails per call; ${n.length} were passed. Split into batches.`});let a=await l("POST","/open-apis/contact/v3/users/batch_get_id?user_id_type=user_id",{emails:n}),s=(Array.isArray(a?.user_list)?a.user_list:[]).filter(i=>i?.user_id).map(i=>({email:String(i?.email||""),userId:String(i.user_id)})),d=new Set(s.map(i=>i.email));return JSON.stringify({ok:!0,employeeType:"employee_id",users:s,notFound:n.filter(i=>!d.has(i))})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${t}`})}}catch(r){return JSON.stringify({ok:!1,error:r?.message||String(r)})}},tools:[{name:"larkattendance_list_groups",description:"DISCOVERY (start here). List this tenant's Lark/Feishu attendance groups (\u8003\u52E4\u7EC4) \u2014 their names are tenant-specific, so never assume one. Returns { ok, count, groups:[{ groupId, groupName }], hasMore, pageToken }. When hasMore is true, call again with pageToken to continue. Needs the attendance:rule:readonly permission on the connected Lark app.",input_schema:{type:"object",properties:{pageSize:{type:"number",description:"Groups per page, 1-50 (default 50)."},pageToken:{type:"string",description:"Cursor from a previous call's pageToken \u2014 omit for the first page."}}}},{name:"larkattendance_get_group",description:"DISCOVERY. Read one attendance group and, crucially, ITS MEMBERS \u2014 memberUserIds is the scope you pass as userIds to larkattendance_query_stats / larkattendance_query_records (a real group can hold hundreds, so expect to batch). Returns { ok, groupId, groupName, employeeType, memberUserIds, memberDeptIds, excludedUserIds, excludedDeptIds, noPunchUserIds, noPunchDeptIds }. THREE different exclusion lists, do not conflate them: excludedUserIds are removed from the group; noPunchUserIds are in the group but not required to clock in (they still have statistics). Members bound by DEPARTMENT appear in memberDeptIds, not memberUserIds \u2014 resolve those people another way (e.g. larkattendance_resolve_users from emails you already have). Lark does not reliably return the group leaders here, so leaderUserIds may be absent \u2014 never source the query operator id from this tool.",input_schema:{type:"object",properties:{groupId:{type:"string",description:"Attendance group id from larkattendance_list_groups."},employeeType:{type:"string",description:"Which id form to return members in: 'employee_id' (tenant user_id, the default and what every other tool here expects) or 'employee_no' (\u5DE5\u53F7)."}},required:["groupId"]}},{name:"larkattendance_list_stats_fields",description:'DISCOVERY \u2014 call this BEFORE larkattendance_query_stats. Lists every statistic column this tenant has for the period, grouped (e.g. \u57FA\u672C\u4FE1\u606F / \u51FA\u52E4\u7EDF\u8BA1 / \u5F02\u5E38\u7EDF\u8BA1 / \u6BCF\u65E5\u7EDF\u8BA1 / \u81EA\u5B9A\u4E49\u5B57\u6BB5). The \u81EA\u5B9A\u4E49\u5B57\u6BB5 group is tenant-defined and exists nowhere else, so this is the only way to learn its codes. Match the user\'s wording ("\u5DE5\u65F6", "\u51FA\u52E4\u5929\u6570", "\u8FDF\u5230\u6B21\u6570") against the returned `title`s and pass the matching `code`s as fieldCodes to larkattendance_query_stats. Returns { ok, statsType, locale, startDate, endDate, fieldGroups:[{ code, title, fields:[{ code, title, timeUnit }] }] }. Range is capped at 40 days.',input_schema:{type:"object",properties:{startDate:{type:"string",description:"Period start as yyyyMMdd (20260401) or YYYY-MM-DD."},endDate:{type:"string",description:"Period end, same format. At most 40 days after startDate."},statsType:{type:"string",description:"'month' (default) for period totals, or 'daily' for per-day columns."},locale:{type:"string",description:"Field-title language: 'zh' (default), 'en', or 'ja'."},employeeType:{type:"string",description:"'employee_id' (default) or 'employee_no'."}},required:["startDate","endDate"]}},{name:"larkattendance_query_stats",description:'The numbers: per-user attendance statistics (work hours, attendance days, exceptions, custom fields) for a period. Needs THREE separate things: userIds (whose data you want, max 200), operatorUserId (WHO IS ASKING \u2014 required by Lark, see below), and the date range (max 31 days). Attendance is personal data and there is no whole-tenant read; get ids from larkattendance_get_group or larkattendance_resolve_users. Returns { ok, statsType, startDate, endDate, users:[{ userId, name, fields:[{ code, title, value, durationNum? }] }], invalidUserIds }. Values are STRINGS exactly as Lark returns them \u2014 report them as-is, do not re-derive. EXPECT MANY COLUMNS: a one-month query returns roughly 40+ cells per person, because Lark mixes period totals (e.g. an "actual attendance hours" column, late/early counts) with identity columns (department, employee number) AND one column PER DAY of the range \u2014 so match on `title` rather than assuming a short list, and pass fieldCodes to trim. The exact set of columns follows the SAVED STATISTICS VIEW of the operatorUserId (\u8003\u52E4 > \u7EDF\u8BA1\u8BBE\u7F6E), so if a field that larkattendance_list_stats_fields advertises is missing from the result, it is switched off in that operator\'s view and an admin must enable it \u2014 or you are querying as the wrong operator.',input_schema:{type:"object",properties:{userIds:{type:"array",items:{type:"string"},description:"The people to report on, in the employeeType id form. Max 200. Required."},startDate:{type:"string",description:"Period start as yyyyMMdd (20260401) or YYYY-MM-DD."},endDate:{type:"string",description:"Period end, same format. At most 31 days after startDate."},statsType:{type:"string",description:"'month' (default) for period totals, or 'daily' for per-day rows."},fieldCodes:{type:"array",items:{type:"string"},description:"Optional filter \u2014 only these field codes/titles are returned. Omit to get every column."},locale:{type:"string",description:"Field-title language: 'zh' (default), 'en', or 'ja'."},employeeType:{type:"string",description:"'employee_id' (default) or 'employee_no' \u2014 must match the form of userIds."},operatorUserId:{type:"string",description:'REQUIRED. The user id of the OPERATOR performing the query \u2014 a third id, distinct from `userIds` (whose data you want) and from `employeeType` (the id form). Lark rejects the call without it (1220001 "Need user_id"). It should be an attendance admin whose saved statistics view contains the columns you need, because that view decides which columns come back. This skill will not pick one for you \u2014 get it from the caller/prompt, or from an email via larkattendance_resolve_users.'},needHistory:{type:"boolean",description:"Include transferred/departed staff (default false)."},currentGroupOnly:{type:"boolean",description:"Restrict to the user's current attendance group (default false)."}},required:["userIds","operatorUserId","startDate","endDate"]}},{name:"larkattendance_query_records",description:"The raw clock-in/out results (\u6253\u5361\u7ED3\u679C) per person per day \u2014 use when the aggregated statistics are not enough (e.g. to show which days were late, or the actual punch times). REQUIRES an explicit userIds list (max 50); range capped at 31 days. Returns { ok, results:[{ userId, name, day, groupId, shiftId, records:[{ checkInTime, checkInResult, checkOutTime, checkOutResult, checkInShiftTime, checkOutShiftTime }] }], invalidUserIds, unauthorizedUserIds }. Unknown ids are reported in invalidUserIds rather than failing the batch. Result values are Lark's own enums (Normal / Early / Late / Lack / \u2026).",input_schema:{type:"object",properties:{userIds:{type:"array",items:{type:"string"},description:"The people to read, in the employeeType id form. Max 50. Required."},startDate:{type:"string",description:"First day as yyyyMMdd (20260401) or YYYY-MM-DD."},endDate:{type:"string",description:"Last day, same format. At most 31 days after startDate."},employeeType:{type:"string",description:"'employee_id' (default) or 'employee_no' \u2014 must match the form of userIds."},needOvertimeResult:{type:"boolean",description:"Also return overtime shift records (default false)."},includeTerminatedUser:{type:"boolean",description:"Include departed employees (default false)."}},required:["userIds","startDate","endDate"]}},{name:"larkattendance_resolve_users",description:"Map work EMAIL addresses to the attendance user ids (tenant user_id = the attendance API's employee_id). Use this to line a report's people up with attendance rows. NOTE this is deliberately different from the `lark` skill's lark_lookup_user_by_email, which returns an open_id for chat/DM \u2014 the attendance endpoints reject open_ids. Max 50 emails per call. Returns { ok, employeeType:\"employee_id\", users:[{ email, userId }], notFound:[email] }. Needs the contact:user.id:readonly permission.",input_schema:{type:"object",properties:{emails:{type:"array",items:{type:"string"},description:"Work email addresses to resolve. Max 50."}},required:["emails"]}}]};function Se(){p=null}export{ee as LARK_ATTENDANCE_APP_ENV_KEYS,Se as _resetLarkAttendanceTokenCache,ne as daySpan,ke as larkAttendanceSkill,w as toLarkDate};
package/dist/larkDocs.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import{existsSync as M,statSync as G,readFileSync as q}from"fs";import{fileURLToPath as z}from"url";import{basename as H,dirname as Y,resolve as W}from"path";var _=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",LARK_DOCS:"lark_docs",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",VIKUNJA:"vikunja",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),de=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark Chat",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark App",connectPath:"/integrations?provider=lark_docs"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},vikunja:{id:"vikunja",name:"Vikunja",connectPath:"/integrations?provider=vikunja"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});import{resolveIntegrationToken as A}from"@zibby/core/backend-client.js";var $="https://open.larksuite.com",E=Object.freeze([Object.freeze({appId:"LARK_DOCS_APP_ID",appSecret:"LARK_DOCS_APP_SECRET",host:"LARK_DOCS_HOST"}),Object.freeze({appId:"LARK_APP_ID",appSecret:"LARK_APP_SECRET",host:"LARK_HOST"})]),v=Object.freeze(E.flatMap(t=>[t.appId,t.appSecret,t.host]));function b(t){let e=String(t||"").trim().replace(/\/+$/,"");return e?/^https?:\/\//i.test(e)?e:`https://${e}`:$}function K(t){let e=String(process.env[t.appId]||"").trim(),n=String(process.env[t.appSecret]||"").trim();return!e||!n?null:{appId:e,appSecret:n,host:b(process.env[t.host])}}async function N(){for(let t of E){let e=K(t);if(e)return e}try{let t=await A(_.LARK_DOCS);return{appId:t?.appId,appSecret:t?.appSecret,host:b(t?.host)}}catch(t){if(!/unknown provider/i.test(String(t?.message||"")))throw t;let e=await A(_.LARK);return{appId:e?.appId,appSecret:e?.appSecret,host:b(e?.host)}}}var L={api:{knob:"SKILL_API_TIMEOUT_MS",fallback:3e4},transfer:{knob:"SKILL_TRANSFER_TIMEOUT_MS",fallback:12e4},job:{knob:"SKILL_JOB_TIMEOUT_MS",fallback:3e5}};function j(t,e,n=process.env){let o=Number(n?.[t]);return Number.isFinite(o)&&o>0?Math.min(6e5,Math.max(1e3,Math.floor(o))):e}function C(t="api",e=process.env){let n=L[t]||L.api;return j(n.knob,n.fallback,e)}function F(t){return t?.name==="TimeoutError"||t?.name==="AbortError"}function J(t){try{return new URL(String(t?.url??t)).host||"unknown host"}catch{return"unknown host"}}function B(t,e){if(!t)return e;let n=new AbortController,o=a=>{n.signal.aborted||n.abort(a)},r=()=>o(t.reason),i=()=>o(e.reason);return n.signal.addEventListener("abort",()=>{t.removeEventListener("abort",r),e.removeEventListener("abort",i)},{once:!0}),t.aborted?o(t.reason):e.aborted?o(e.reason):(t.addEventListener("abort",r,{once:!0}),e.addEventListener("abort",i,{once:!0})),n.signal}async function w(t,e={},n={}){let o=n.kind||"api",r=(L[o]||L.api).knob,i=n.timeoutMs?Math.min(6e5,Math.max(1e3,Math.floor(n.timeoutMs))):C(o),a=e?.signal;if(a?.aborted)throw a.reason??new DOMException("This operation was aborted","AbortError");let s=AbortSignal.timeout(i),c=B(a,s);try{return await fetch(t,{...e,signal:c})}catch(d){throw s.aborted&&F(d)?new Error(`${n.what||"request"} TIMED OUT after ${i}ms against ${J(t)} (${r})`):d}}function V(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=Y(z(import.meta.url)),e=W(t,"..","bin","mcp-skill.mjs");return M(e)?e:null}var R=2e4,P=50,X=50,Z=20,Q=10*1024*1024,ee=6e3*1e3,y=null,te=v;async function U(){let{appId:t,appSecret:e,host:n}=await N();if(y&&y.appId===t&&y.expiresAt>Date.now())return{token:y.token,host:n};let r=await(await w(`${n}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:t,app_secret:e})},{kind:"api",what:"Lark tenant_access_token"})).json();if(r.code!==0)throw new Error(`Lark tenant_access_token failed: ${r.msg||r.code}`);return y={token:r.tenant_access_token,expiresAt:Date.now()+ee,appId:t},{token:r.tenant_access_token,host:n}}async function l(t,e,n){let{token:o,host:r}=await U(),i={method:t,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json; charset=utf-8"}};t!=="GET"&&n!==void 0&&(i.body=JSON.stringify(n));let s=await(await w(`${r}${e}`,i,{kind:"api",what:`Lark Docx ${t} ${e}`})).json();if(s.code!==0)throw new Error(`Lark Docx API ${e} error: ${s.msg||s.code}`);return{data:s.data||{},host:r}}function g(t,e){return String(t||"").includes("feishu")?`https://feishu.cn/docx/${e}`:`https://www.larksuite.com/docx/${e}`}function ne(t,e){return String(t||"").includes("feishu")?`https://feishu.cn/wiki/${e}`:`https://www.larksuite.com/wiki/${e}`}function f(t){if(!t||typeof t!="string")return null;let e=t.trim();if(!e)return null;let n=e.match(/\/(docx|wiki)\/([A-Za-z0-9]+)/);return n?{type:n[1],token:n[2]}:/^[A-Za-z0-9]{10,}$/.test(e)?{type:"docx",token:e}:null}async function h(t){let e=typeof t=="string"?f(t):t;if(!e)throw new Error("A valid Lark doc id or URL is required");if(e.type==="docx")return e.token;let{data:n}=await l("GET",`/open-apis/wiki/v2/spaces/get_node?token=${encodeURIComponent(e.token)}`),o=n?.node||{};if(o.obj_type!=="docx"||!o.obj_token)throw new Error(`Wiki node is not a docx document (obj_type=${o.obj_type||"unknown"})`);return o.obj_token}function T(t){let e=String(t??"").replace(/\r\n/g,`
2
2
  `),n=[];for(let o of e.split(`
3
- `)){let r=o.replace(/\s+$/,"");if(!r.trim())continue;let i=/^(#{1,3})\s+(.*)$/.exec(r),a=/^\s*[-*]\s+(.*)$/.exec(r),s=/^\s*\d+[.)]\s+(.*)$/.exec(r),c,d,p;if(i){let u=i[1].length;c=`heading${u}`,d=2+u,p=i[2]}else a?(c="bullet",d=12,p=a[1]):s?(c="ordered",d=13,p=s[1]):(c="text",d=2,p=r);n.push({block_type:d,[c]:{elements:[{text_run:{content:p}}],style:{}}})}return n}function I(t){let e=typeof t?.markdown=="string"?t.markdown:null,n=typeof t?.text=="string"?t.text:null;return e??n}async function x(t,e){let n;for(let o=0;o<e.length;o+=P){let r=e.slice(o,o+P);n=(await l("POST",`/open-apis/docx/v1/documents/${t}/blocks/${t}/children?document_revision_id=-1`,{children:r})).host}return n}function oe(t){let e=typeof t=="string"?t.trim():"";if(!e)throw new Error("imagePath is required");if(!M(e)||!G(e).isFile())throw new Error(`imagePath not found (or not a file): ${e}`);if(!/\.(png|jpe?g)$/i.test(e))throw new Error("imagePath must be a .png or .jpg/.jpeg file");let n=q(e);if(n.length>Q)throw new Error(`image is ${(n.length/(1024*1024)).toFixed(1)}MB \u2014 max 10MB`);return n}async function re({fileName:t,parentNode:e,bytes:n}){let{token:o,host:r}=await U(),i=new FormData;i.set("file_name",t),i.set("parent_type","docx_image"),i.set("parent_node",e),i.set("size",String(n.length)),i.set("file",new Blob([n]),t);let s=await(await w(`${r}/open-apis/drive/v1/medias/upload_all`,{method:"POST",headers:{Authorization:`Bearer ${o}`},body:i},{kind:"transfer",what:"Lark Docx image upload"})).json();if(s.code!==0)throw new Error(`Lark media upload_all error: ${s.msg||s.code}`);let c=s.data?.file_token;if(!c)throw new Error("Lark media upload_all returned no file_token");return c}var ie="docx",ae=50;function D(t){return[{type:"text_run",text_run:{text:String(t??"")}}]}function se(t){return Array.isArray(t)?t.map(e=>e?.text_run?.text??e?.docs_link?.url??(e?.person?`@${e.person.user_id||""}`:"")).join(""):""}function ce(t){let e=Array.isArray(t?.reply_list?.replies)?t.reply_list.replies:[];return{commentId:t?.comment_id||"",resolved:!!t?.is_solved,replies:e.map(n=>({replyId:n?.reply_id||"",author:n?.user_id||"",text:se(n?.content?.elements),createTime:n?.create_time||""}))}}function O(t){return typeof t?.fileType=="string"&&t.fileType.trim()?t.fileType.trim():ie}var Se={id:"lark-docs",callsBackend:!0,serverName:"larkdocs",allowedTools:["mcp__larkdocs__*"],requiresIntegration:[_.LARK_DOCS,_.LARK],description:"Lark / Feishu Docs \u2014 read, create, append, and insert images into Lark documents (docx).",envKeys:["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV",...te],promptFragment:'## Lark Docs\nYou can read, create, and append Lark/Feishu documents (docx), and read/post/reply to their comments. This runs on the connected Lark Docs app (the chat app is used when no separate docs app is connected). Each `larkdoc_*` / `larkwiki_*` tool documents its own params and return shape in its tool description \u2014 they are not restated here.\nThese tools return { ok:false, error } on failure \u2014 treat an unavailable Lark connection as "cannot read/deliver to Lark Docs" and continue rather than blocking the task.',resolve(){let t=V();if(!t)return null;let e={};for(let n of this.envKeys)process.env[n]&&(e[n]=process.env[n]);return{type:"stdio",command:"node",args:[t,"../dist/larkDocs.js","larkDocsSkill"],env:e,description:this.description,alwaysLoad:!0}},async handleToolCall(t,e){try{switch(t){case"larkdoc_get":{let n=e?.documentId||e?.url||e?.id,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=await h(o),i="";try{i=(await l("GET",`/open-apis/docx/v1/documents/${r}`)).data?.document?.title||""}catch{}let{data:a,host:s}=await l("GET",`/open-apis/docx/v1/documents/${r}/raw_content?lang=0`),c=String(a?.content||""),d=!1;return c.length>R&&(c=c.slice(0,R),d=!0),JSON.stringify({ok:!0,documentId:r,title:i,url:g(s,r),text:c,...d?{truncated:!0}:{}})}case"larkdoc_create":{let n=typeof e?.title=="string"&&e.title.trim()?e.title.trim():null;if(!n)return JSON.stringify({ok:!1,error:"title is required"});let o=typeof e?.wikiSpaceId=="string"&&e.wikiSpaceId.trim()?e.wikiSpaceId.trim():null;if(o){let c=typeof e?.parentNodeToken=="string"&&e.parentNodeToken.trim()?e.parentNodeToken.trim():null,{data:d,host:p}=await l("POST",`/open-apis/wiki/v2/spaces/${encodeURIComponent(o)}/nodes`,{obj_type:"docx",node_type:"origin",title:n,...c?{parent_node_token:c}:{}}),u=d?.node||{},m=u.obj_token;if(!m)return JSON.stringify({ok:!1,error:"Lark wiki node create returned no obj_token"});let k=I(e);if(k&&k.trim()){let S=T(k);S.length&&await x(m,S)}return JSON.stringify({ok:!0,documentId:m,title:n,wikiSpaceId:o,wikiNodeToken:u.node_token||"",url:u.node_token?ne(p,u.node_token):g(p,m)})}let{data:r,host:i}=await l("POST","/open-apis/docx/v1/documents",{title:n,...e?.folderToken?{folder_token:String(e.folderToken)}:{}}),a=r?.document?.document_id;if(!a)return JSON.stringify({ok:!1,error:"Lark Docs create returned no document_id"});let s=I(e);if(s&&s.trim()){let c=T(s);c.length&&await x(a,c)}return JSON.stringify({ok:!0,documentId:a,title:n,url:g(i,a)})}case"larkdoc_append":{let n=e?.documentId||e?.url||e?.id,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=I(e);if(!r||!r.trim())return JSON.stringify({ok:!1,error:"markdown or text content is required"});let i=await h(o),a=T(r);if(!a.length)return JSON.stringify({ok:!1,error:"no non-empty content to append"});let s=await x(i,a);return JSON.stringify({ok:!0,documentId:i,url:g(s,i)})}case"larkdoc_insert_image":{let n=e?.documentId||e?.url||e?.id,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});if(!e?.imagePath||typeof e.imagePath!="string"||!e.imagePath.trim())return JSON.stringify({ok:!1,error:"imagePath is required"});let r=await h(o),i=e.imagePath.trim(),a=oe(i),s=H(i),d=(await l("POST",`/open-apis/docx/v1/documents/${r}/blocks/${r}/children?document_revision_id=-1`,{children:[{block_type:27,image:{}}]})).data?.children?.[0]?.block_id;if(!d)return JSON.stringify({ok:!1,error:"Lark image block create returned no block_id"});let p=await re({fileName:s,parentNode:d,bytes:a}),u={token:p},m=Number(e?.width),k=Number(e?.height);Number.isFinite(m)&&m>0&&(u.width=Math.round(m)),Number.isFinite(k)&&k>0&&(u.height=Math.round(k));let{host:S}=await l("PATCH",`/open-apis/docx/v1/documents/${r}/blocks/${d}?document_revision_id=-1`,{replace_image:u});return JSON.stringify({ok:!0,documentId:r,blockId:d,fileToken:p,url:g(S,r)})}case"larkwiki_list_spaces":{let n=[],o=null;for(let r=0;r<Z;r++){let i=new URLSearchParams({page_size:String(X)});o&&i.set("page_token",o);let{data:a}=await l("GET",`/open-apis/wiki/v2/spaces?${i.toString()}`),s=Array.isArray(a?.items)?a.items:[];for(let c of s)n.push({spaceId:String(c?.space_id||""),name:String(c?.name||"")});if(!a?.has_more||!a?.page_token)break;o=a.page_token}return JSON.stringify({ok:!0,count:n.length,spaces:n})}case"larkdoc_list_comments":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=await h(o),i=O(e),a=new URLSearchParams({file_type:i,page_size:String(ae)}),{data:s}=await l("GET",`/open-apis/drive/v1/files/${r}/comments?${a.toString()}`),d=(Array.isArray(s?.items)?s.items:[]).map(ce);return JSON.stringify({ok:!0,documentId:r,count:d.length,comments:d})}case"larkdoc_add_comment":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=I({text:e?.text??e?.body});if(!r||!r.trim())return JSON.stringify({ok:!1,error:"text is required"});let i=await h(o),a=O(e),{data:s}=await l("POST",`/open-apis/drive/v1/files/${i}/comments?file_type=${encodeURIComponent(a)}`,{reply_list:{replies:[{content:{elements:D(r)}}]}});return JSON.stringify({ok:!0,documentId:i,commentId:s?.comment_id||""})}case"larkdoc_reply_comment":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=e?.commentId||e?.comment_id;if(!r)return JSON.stringify({ok:!1,error:"commentId is required"});let i=I({text:e?.text??e?.body});if(!i||!i.trim())return JSON.stringify({ok:!1,error:"text is required"});let a=await h(o),s=O(e),{data:c}=await l("POST",`/open-apis/drive/v1/files/${a}/comments/${encodeURIComponent(r)}/replies?file_type=${encodeURIComponent(s)}`,{content:{elements:D(i)}});return JSON.stringify({ok:!0,documentId:a,commentId:String(r),replyId:c?.reply_id||""})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${t}`})}}catch(n){return JSON.stringify({ok:!1,error:n.message})}},tools:[{name:"larkdoc_get",description:"Read a Lark/Feishu document (docx) as plain text, for use as reference context. Accepts a raw doc id OR a full Lark doc URL (a /docx/ or /wiki/ link \u2014 wiki links are resolved to their backing docx). Returns { ok, documentId, title, url, text }. Text is truncated to ~20k chars.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL (/docx/\u2026 or /wiki/\u2026)."}},required:["documentId"]}},{name:"larkdoc_create",description:"Create a new Lark/Feishu document (docx) with a title and optional content (markdown: #/##/### headings, - bullets, 1. ordered; or plain text). Pass wikiSpaceId (from larkwiki_list_spaces) to create the doc INSIDE a wiki space instead of as a standalone doc \u2014 optionally nested under parentNodeToken. Returns { ok, documentId, url } (plus wikiNodeToken when created in a wiki) \u2014 share the url with the user.",input_schema:{type:"object",properties:{title:{type:"string",description:"Document title."},markdown:{type:"string",description:"Document body as markdown (preferred)."},text:{type:"string",description:"Document body as plain text (used when markdown is absent)."},folderToken:{type:"string",description:"Optional Lark drive folder token to create the doc in. Absent = the app root. Ignored when wikiSpaceId is set."},wikiSpaceId:{type:"string",description:"Optional wiki space id (from larkwiki_list_spaces) \u2014 create the doc as a node inside this wiki space."},parentNodeToken:{type:"string",description:"Optional wiki node token to nest the new doc under (only with wikiSpaceId). Absent = the space root."}},required:["title"]}},{name:"larkwiki_list_spaces",description:"List the Lark/Feishu wiki spaces visible to the connected app. Returns { ok, count, spaces:[{ spaceId, name }] }. Use to discover the wikiSpaceId for larkdoc_create.",input_schema:{type:"object",properties:{}}},{name:"larkdoc_append",description:"Append markdown/text content to the END of an existing Lark/Feishu document (docx). Accepts a documentId or a full Lark doc URL. Returns { ok, documentId, url }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},markdown:{type:"string",description:"Content to append, as markdown (preferred)."},text:{type:"string",description:"Content to append, as plain text (used when markdown is absent)."}},required:["documentId"]}},{name:"larkdoc_insert_image",description:"Append a LOCAL image file (png/jpg, max 10MB) to the END of an existing Lark/Feishu document (docx). Accepts a documentId or full doc URL plus a local file path. Optional width/height in pixels. Returns { ok, documentId, blockId, fileToken, url }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},imagePath:{type:"string",description:"Local filesystem path to a .png or .jpg/.jpeg image (max 10MB)."},width:{type:"number",description:"Optional display width in pixels."},height:{type:"number",description:"Optional display height in pixels."}},required:["documentId","imagePath"]}},{name:"larkdoc_list_comments",description:"List the comment threads on a Lark/Feishu document. Accepts a documentId or full doc URL. Returns { ok, comments:[{ commentId, replies:[{ replyId, author, text }] }] }. Use to read the thread you are replying to.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'. Only change for non-docx files (doc/sheet/bitable/file/slides)."}},required:["documentId"]}},{name:"larkdoc_add_comment",description:"Post a NEW top-level comment on a Lark/Feishu document. Accepts a documentId or full doc URL plus text. Returns { ok, documentId, commentId }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},text:{type:"string",description:"The comment body (plain text)."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'."}},required:["documentId","text"]}},{name:"larkdoc_reply_comment",description:"Reply INSIDE an existing comment thread on a Lark/Feishu document. Pass { documentId, commentId, text }. Use to answer a user who @mentioned Zibby in a doc comment (reply in the same commentId). Returns { ok, documentId, commentId, replyId }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},commentId:{type:"string",description:"The comment_id of the thread to reply into (from larkdoc_list_comments or the webhook event)."},text:{type:"string",description:"The reply body (plain text)."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'."}},required:["documentId","commentId","text"]}}]};function Le(){y=null}export{te as LARK_DOCS_APP_ENV_KEYS,Le as _resetLarkDocsTokenCache,g as docWebUrl,Se as larkDocsSkill,T as markdownToLarkBlocks,f as parseLarkDocRef,ne as wikiWebUrl};
3
+ `)){let r=o.replace(/\s+$/,"");if(!r.trim())continue;let i=/^(#{1,3})\s+(.*)$/.exec(r),a=/^\s*[-*]\s+(.*)$/.exec(r),s=/^\s*\d+[.)]\s+(.*)$/.exec(r),c,d,p;if(i){let u=i[1].length;c=`heading${u}`,d=2+u,p=i[2]}else a?(c="bullet",d=12,p=a[1]):s?(c="ordered",d=13,p=s[1]):(c="text",d=2,p=r);n.push({block_type:d,[c]:{elements:[{text_run:{content:p}}],style:{}}})}return n}function I(t){let e=typeof t?.markdown=="string"?t.markdown:null,n=typeof t?.text=="string"?t.text:null;return e??n}async function x(t,e){let n;for(let o=0;o<e.length;o+=P){let r=e.slice(o,o+P);n=(await l("POST",`/open-apis/docx/v1/documents/${t}/blocks/${t}/children?document_revision_id=-1`,{children:r})).host}return n}function oe(t){let e=typeof t=="string"?t.trim():"";if(!e)throw new Error("imagePath is required");if(!M(e)||!G(e).isFile())throw new Error(`imagePath not found (or not a file): ${e}`);if(!/\.(png|jpe?g)$/i.test(e))throw new Error("imagePath must be a .png or .jpg/.jpeg file");let n=q(e);if(n.length>Q)throw new Error(`image is ${(n.length/(1024*1024)).toFixed(1)}MB \u2014 max 10MB`);return n}async function re({fileName:t,parentNode:e,bytes:n}){let{token:o,host:r}=await U(),i=new FormData;i.set("file_name",t),i.set("parent_type","docx_image"),i.set("parent_node",e),i.set("size",String(n.length)),i.set("file",new Blob([n]),t);let s=await(await w(`${r}/open-apis/drive/v1/medias/upload_all`,{method:"POST",headers:{Authorization:`Bearer ${o}`},body:i},{kind:"transfer",what:"Lark Docx image upload"})).json();if(s.code!==0)throw new Error(`Lark media upload_all error: ${s.msg||s.code}`);let c=s.data?.file_token;if(!c)throw new Error("Lark media upload_all returned no file_token");return c}var ie="docx",ae=50;function D(t){return[{type:"text_run",text_run:{text:String(t??"")}}]}function se(t){return Array.isArray(t)?t.map(e=>e?.text_run?.text??e?.docs_link?.url??(e?.person?`@${e.person.user_id||""}`:"")).join(""):""}function ce(t){let e=Array.isArray(t?.reply_list?.replies)?t.reply_list.replies:[];return{commentId:t?.comment_id||"",resolved:!!t?.is_solved,replies:e.map(n=>({replyId:n?.reply_id||"",author:n?.user_id||"",text:se(n?.content?.elements),createTime:n?.create_time||""}))}}function O(t){return typeof t?.fileType=="string"&&t.fileType.trim()?t.fileType.trim():ie}var Se={id:"lark-docs",callsBackend:!0,serverName:"larkdocs",allowedTools:["mcp__larkdocs__*"],requiresIntegration:[_.LARK_DOCS,_.LARK],description:"Lark / Feishu Docs \u2014 read, create, append, and insert images into Lark documents (docx).",envKeys:["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV",...te],promptFragment:'## Lark Docs\nYou can read, create, and append Lark/Feishu documents (docx), and read/post/reply to their comments. This runs on the connected Lark Docs app (the chat app is used when no separate docs app is connected). Each `larkdoc_*` / `larkwiki_*` tool documents its own params and return shape in its tool description \u2014 they are not restated here.\nThese tools return { ok:false, error } on failure \u2014 treat an unavailable Lark connection as "cannot read/deliver to Lark Docs" and continue rather than blocking the task.',resolve(){let t=V();if(!t)return null;let e={};for(let n of this.envKeys)process.env[n]&&(e[n]=process.env[n]);return{type:"stdio",command:"node",args:[t,"../dist/larkDocs.js","larkDocsSkill"],env:e,description:this.description}},async handleToolCall(t,e){try{switch(t){case"larkdoc_get":{let n=e?.documentId||e?.url||e?.id,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=await h(o),i="";try{i=(await l("GET",`/open-apis/docx/v1/documents/${r}`)).data?.document?.title||""}catch{}let{data:a,host:s}=await l("GET",`/open-apis/docx/v1/documents/${r}/raw_content?lang=0`),c=String(a?.content||""),d=!1;return c.length>R&&(c=c.slice(0,R),d=!0),JSON.stringify({ok:!0,documentId:r,title:i,url:g(s,r),text:c,...d?{truncated:!0}:{}})}case"larkdoc_create":{let n=typeof e?.title=="string"&&e.title.trim()?e.title.trim():null;if(!n)return JSON.stringify({ok:!1,error:"title is required"});let o=typeof e?.wikiSpaceId=="string"&&e.wikiSpaceId.trim()?e.wikiSpaceId.trim():null;if(o){let c=typeof e?.parentNodeToken=="string"&&e.parentNodeToken.trim()?e.parentNodeToken.trim():null,{data:d,host:p}=await l("POST",`/open-apis/wiki/v2/spaces/${encodeURIComponent(o)}/nodes`,{obj_type:"docx",node_type:"origin",title:n,...c?{parent_node_token:c}:{}}),u=d?.node||{},m=u.obj_token;if(!m)return JSON.stringify({ok:!1,error:"Lark wiki node create returned no obj_token"});let k=I(e);if(k&&k.trim()){let S=T(k);S.length&&await x(m,S)}return JSON.stringify({ok:!0,documentId:m,title:n,wikiSpaceId:o,wikiNodeToken:u.node_token||"",url:u.node_token?ne(p,u.node_token):g(p,m)})}let{data:r,host:i}=await l("POST","/open-apis/docx/v1/documents",{title:n,...e?.folderToken?{folder_token:String(e.folderToken)}:{}}),a=r?.document?.document_id;if(!a)return JSON.stringify({ok:!1,error:"Lark Docs create returned no document_id"});let s=I(e);if(s&&s.trim()){let c=T(s);c.length&&await x(a,c)}return JSON.stringify({ok:!0,documentId:a,title:n,url:g(i,a)})}case"larkdoc_append":{let n=e?.documentId||e?.url||e?.id,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=I(e);if(!r||!r.trim())return JSON.stringify({ok:!1,error:"markdown or text content is required"});let i=await h(o),a=T(r);if(!a.length)return JSON.stringify({ok:!1,error:"no non-empty content to append"});let s=await x(i,a);return JSON.stringify({ok:!0,documentId:i,url:g(s,i)})}case"larkdoc_insert_image":{let n=e?.documentId||e?.url||e?.id,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});if(!e?.imagePath||typeof e.imagePath!="string"||!e.imagePath.trim())return JSON.stringify({ok:!1,error:"imagePath is required"});let r=await h(o),i=e.imagePath.trim(),a=oe(i),s=H(i),d=(await l("POST",`/open-apis/docx/v1/documents/${r}/blocks/${r}/children?document_revision_id=-1`,{children:[{block_type:27,image:{}}]})).data?.children?.[0]?.block_id;if(!d)return JSON.stringify({ok:!1,error:"Lark image block create returned no block_id"});let p=await re({fileName:s,parentNode:d,bytes:a}),u={token:p},m=Number(e?.width),k=Number(e?.height);Number.isFinite(m)&&m>0&&(u.width=Math.round(m)),Number.isFinite(k)&&k>0&&(u.height=Math.round(k));let{host:S}=await l("PATCH",`/open-apis/docx/v1/documents/${r}/blocks/${d}?document_revision_id=-1`,{replace_image:u});return JSON.stringify({ok:!0,documentId:r,blockId:d,fileToken:p,url:g(S,r)})}case"larkwiki_list_spaces":{let n=[],o=null;for(let r=0;r<Z;r++){let i=new URLSearchParams({page_size:String(X)});o&&i.set("page_token",o);let{data:a}=await l("GET",`/open-apis/wiki/v2/spaces?${i.toString()}`),s=Array.isArray(a?.items)?a.items:[];for(let c of s)n.push({spaceId:String(c?.space_id||""),name:String(c?.name||"")});if(!a?.has_more||!a?.page_token)break;o=a.page_token}return JSON.stringify({ok:!0,count:n.length,spaces:n})}case"larkdoc_list_comments":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=await h(o),i=O(e),a=new URLSearchParams({file_type:i,page_size:String(ae)}),{data:s}=await l("GET",`/open-apis/drive/v1/files/${r}/comments?${a.toString()}`),d=(Array.isArray(s?.items)?s.items:[]).map(ce);return JSON.stringify({ok:!0,documentId:r,count:d.length,comments:d})}case"larkdoc_add_comment":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=I({text:e?.text??e?.body});if(!r||!r.trim())return JSON.stringify({ok:!1,error:"text is required"});let i=await h(o),a=O(e),{data:s}=await l("POST",`/open-apis/drive/v1/files/${i}/comments?file_type=${encodeURIComponent(a)}`,{reply_list:{replies:[{content:{elements:D(r)}}]}});return JSON.stringify({ok:!0,documentId:i,commentId:s?.comment_id||""})}case"larkdoc_reply_comment":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,o=f(n);if(!o)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let r=e?.commentId||e?.comment_id;if(!r)return JSON.stringify({ok:!1,error:"commentId is required"});let i=I({text:e?.text??e?.body});if(!i||!i.trim())return JSON.stringify({ok:!1,error:"text is required"});let a=await h(o),s=O(e),{data:c}=await l("POST",`/open-apis/drive/v1/files/${a}/comments/${encodeURIComponent(r)}/replies?file_type=${encodeURIComponent(s)}`,{content:{elements:D(i)}});return JSON.stringify({ok:!0,documentId:a,commentId:String(r),replyId:c?.reply_id||""})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${t}`})}}catch(n){return JSON.stringify({ok:!1,error:n.message})}},tools:[{name:"larkdoc_get",description:"Read a Lark/Feishu document (docx) as plain text, for use as reference context. Accepts a raw doc id OR a full Lark doc URL (a /docx/ or /wiki/ link \u2014 wiki links are resolved to their backing docx). Returns { ok, documentId, title, url, text }. Text is truncated to ~20k chars.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL (/docx/\u2026 or /wiki/\u2026)."}},required:["documentId"]}},{name:"larkdoc_create",description:"Create a new Lark/Feishu document (docx) with a title and optional content (markdown: #/##/### headings, - bullets, 1. ordered; or plain text). Pass wikiSpaceId (from larkwiki_list_spaces) to create the doc INSIDE a wiki space instead of as a standalone doc \u2014 optionally nested under parentNodeToken. Returns { ok, documentId, url } (plus wikiNodeToken when created in a wiki) \u2014 share the url with the user.",input_schema:{type:"object",properties:{title:{type:"string",description:"Document title."},markdown:{type:"string",description:"Document body as markdown (preferred)."},text:{type:"string",description:"Document body as plain text (used when markdown is absent)."},folderToken:{type:"string",description:"Optional Lark drive folder token to create the doc in. Absent = the app root. Ignored when wikiSpaceId is set."},wikiSpaceId:{type:"string",description:"Optional wiki space id (from larkwiki_list_spaces) \u2014 create the doc as a node inside this wiki space."},parentNodeToken:{type:"string",description:"Optional wiki node token to nest the new doc under (only with wikiSpaceId). Absent = the space root."}},required:["title"]}},{name:"larkwiki_list_spaces",description:"List the Lark/Feishu wiki spaces visible to the connected app. Returns { ok, count, spaces:[{ spaceId, name }] }. Use to discover the wikiSpaceId for larkdoc_create.",input_schema:{type:"object",properties:{}}},{name:"larkdoc_append",description:"Append markdown/text content to the END of an existing Lark/Feishu document (docx). Accepts a documentId or a full Lark doc URL. Returns { ok, documentId, url }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},markdown:{type:"string",description:"Content to append, as markdown (preferred)."},text:{type:"string",description:"Content to append, as plain text (used when markdown is absent)."}},required:["documentId"]}},{name:"larkdoc_insert_image",description:"Append a LOCAL image file (png/jpg, max 10MB) to the END of an existing Lark/Feishu document (docx). Accepts a documentId or full doc URL plus a local file path. Optional width/height in pixels. Returns { ok, documentId, blockId, fileToken, url }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},imagePath:{type:"string",description:"Local filesystem path to a .png or .jpg/.jpeg image (max 10MB)."},width:{type:"number",description:"Optional display width in pixels."},height:{type:"number",description:"Optional display height in pixels."}},required:["documentId","imagePath"]}},{name:"larkdoc_list_comments",description:"List the comment threads on a Lark/Feishu document. Accepts a documentId or full doc URL. Returns { ok, comments:[{ commentId, replies:[{ replyId, author, text }] }] }. Use to read the thread you are replying to.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'. Only change for non-docx files (doc/sheet/bitable/file/slides)."}},required:["documentId"]}},{name:"larkdoc_add_comment",description:"Post a NEW top-level comment on a Lark/Feishu document. Accepts a documentId or full doc URL plus text. Returns { ok, documentId, commentId }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},text:{type:"string",description:"The comment body (plain text)."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'."}},required:["documentId","text"]}},{name:"larkdoc_reply_comment",description:"Reply INSIDE an existing comment thread on a Lark/Feishu document. Pass { documentId, commentId, text }. Use to answer a user who @mentioned Zibby in a doc comment (reply in the same commentId). Returns { ok, documentId, commentId, replyId }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},commentId:{type:"string",description:"The comment_id of the thread to reply into (from larkdoc_list_comments or the webhook event)."},text:{type:"string",description:"The reply body (plain text)."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'."}},required:["documentId","commentId","text"]}}]};function Le(){y=null}export{te as LARK_DOCS_APP_ENV_KEYS,Le as _resetLarkDocsTokenCache,g as docWebUrl,Se as larkDocsSkill,T as markdownToLarkBlocks,f as parseLarkDocRef,ne as wikiWebUrl};
package/dist/linear.js CHANGED
@@ -30,7 +30,7 @@ You have direct access to the user's Linear workspace (GraphQL API). Tools:
30
30
 
31
31
  ### Notes
32
32
  - Always resolve a team first when you need states or want to create/move issues by state name \u2014 states only make sense within their team.
33
- - Issue identifier (ENG-123) and internal id (uuid) are both accepted by get/update tools.`,resolve(){let n={};for(let t of this.envKeys)process.env[t]&&(n[t]=process.env[t]);process.env.LINEAR_API_URL&&(n.LINEAR_API_URL=process.env.LINEAR_API_URL);let i=R();return i?{type:"stdio",command:"node",args:[i,"../dist/linear.js","linearSkill"],env:n,description:this.description,alwaysLoad:!0}:{command:null,args:[],env:n,description:this.description}},async handleToolCall(n,i){try{switch(n){case"linear_list_teams":{let e=(await f(`
33
+ - Issue identifier (ENG-123) and internal id (uuid) are both accepted by get/update tools.`,resolve(){let n={};for(let t of this.envKeys)process.env[t]&&(n[t]=process.env[t]);process.env.LINEAR_API_URL&&(n.LINEAR_API_URL=process.env.LINEAR_API_URL);let i=R();return i?{type:"stdio",command:"node",args:[i,"../dist/linear.js","linearSkill"],env:n,description:this.description}:{command:null,args:[],env:n,description:this.description}},async handleToolCall(n,i){try{switch(n){case"linear_list_teams":{let e=(await f(`
34
34
  query Teams($first: Int) {
35
35
  teams(first: $first) {
36
36
  nodes { id key name description }