@zibby/skills 2.0.4 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact.js +7 -7
- package/dist/chat-notify.js +4 -4
- package/dist/chatProgress.js +4 -4
- package/dist/datasetStore.js +2 -2
- package/dist/discord.js +3 -3
- package/dist/figma.js +2 -2
- package/dist/git-write.js +9 -9
- package/dist/github.js +5 -5
- package/dist/gitlab.js +5 -5
- package/dist/googleDocs.js +9 -9
- package/dist/hubspot.js +1 -1
- package/dist/index.js +179 -181
- package/dist/jira.d.ts +36 -1
- package/dist/jira.js +22 -20
- package/dist/kvMemory.js +2 -2
- package/dist/lark.js +2 -2
- package/dist/larkAttendance.js +1 -1
- package/dist/larkDocs.js +3 -3
- package/dist/lib/http-deadline.d.ts +117 -0
- package/dist/lib/http-deadline.js +1 -0
- package/dist/lib/markup.d.ts +158 -0
- package/dist/lib/markup.js +22 -0
- package/dist/linear.js +15 -15
- package/dist/linkedin.js +2 -2
- package/dist/llm-billing.js +1 -1
- package/dist/notion.js +7 -7
- package/dist/opendesign.js +2 -2
- package/dist/package.json +6 -5
- package/dist/report.d.ts +42 -42
- package/dist/review.js +4 -4
- package/dist/reviewMemoryIo.js +1 -1
- package/dist/sentry.js +2 -2
- package/dist/skill-installer.js +3 -3
- package/dist/slack.js +2 -2
- package/dist/trackers/github-adapter.js +5 -5
- package/dist/trackers/index.js +46 -44
- package/dist/trackers/jira-adapter.js +24 -22
- package/dist/trackers/linear-adapter.js +19 -19
- package/dist/trackers/plane-adapter.js +1 -1
- package/dist/triggerAgent.js +1 -1
- package/dist/vikunja.d.ts +8 -1
- package/dist/vikunja.js +22 -7
- package/docs/templates/catalog-metadata.md +101 -0
- package/docs/templates/composition.md +97 -0
- package/docs/templates/deploy-time-config.md +228 -0
- package/docs/templates/index.md +138 -0
- package/docs/templates/models.md +157 -0
- package/docs/templates/node-declarations.md +149 -0
- package/docs/templates/remote-mcp.md +105 -0
- package/docs/templates/requires.md +183 -0
- package/docs/templates/sidecars.md +163 -0
- package/docs/templates/stores.md +113 -0
- package/docs/templates/surfaces.md +184 -0
- package/docs/templates/update-behavior.md +108 -0
- package/package.json +6 -5
package/dist/jira.d.ts
CHANGED
|
@@ -75,8 +75,43 @@ export declare function jiraApiCall(cred: any, path: string, opts?: any): {
|
|
|
75
75
|
* cover. Keep this the single auth chokepoint; don't re-implement credential
|
|
76
76
|
* resolution at call sites.
|
|
77
77
|
*
|
|
78
|
+
* ── `opts.signal`: OPTIONAL, and it is what turns a caller's WAIT into a real
|
|
79
|
+
* ABORT ────────────────────────────────────────────────────────────────────
|
|
80
|
+
* Node's global fetch has NO default timeout, and A HANG IS NOT A THROW, so an
|
|
81
|
+
* Atlassian connection that is accepted and never answered parks whoever called
|
|
82
|
+
* this forever. Callers that care already bound the WAIT from outside —
|
|
83
|
+
* workflow-templates' `_shared/tracker.js jiraCall` races this promise against
|
|
84
|
+
* a `BOARD_API_TIMEOUT_MS` deadline — but a race can only stop waiting; it
|
|
85
|
+
* cannot close the socket, because the socket lives in here. Accepting a
|
|
86
|
+
* `signal` is the missing half: the same deadline that ends the wait now also
|
|
87
|
+
* ends the REQUEST.
|
|
88
|
+
*
|
|
89
|
+
* The signal covers the BODY read too — undici ties the response stream to the
|
|
90
|
+
* request's signal — so a response whose headers arrive and whose body then
|
|
91
|
+
* stalls is aborted on the same clock, which is the half a naive passthrough
|
|
92
|
+
* would miss.
|
|
93
|
+
*
|
|
94
|
+
* ── AND NOW A DEFAULT UNDER IT (#1124) ─────────────────────────────────────
|
|
95
|
+
* The passthrough shipped OPT-IN and inert, with no default timeout, on this
|
|
96
|
+
* reasoning: "a shared library whose callers know their own budget … inventing
|
|
97
|
+
* one for them is how a legitimately slow bulk JQL query starts failing in
|
|
98
|
+
* production for a reason nobody asked for."
|
|
99
|
+
*
|
|
100
|
+
* That reasoning held while there was nowhere to put a budget that was not a
|
|
101
|
+
* number invented in this file. It does not hold now: `lib/http-deadline.ts`
|
|
102
|
+
* declares budgets BY WHAT IS MOVING, once, for every skill — and the slow bulk
|
|
103
|
+
* JQL query is not a counter-example to bounding, it is one of the KINDS. A
|
|
104
|
+
* `/search` is the far end COMPUTING, so it draws the `job` budget (5 min);
|
|
105
|
+
* everything else here is a row read and draws `api` (30s). The knobs raise
|
|
106
|
+
* either one without touching this file.
|
|
107
|
+
*
|
|
108
|
+
* What survived unchanged is the important half: EVERY CALLER TODAY PASSES
|
|
109
|
+
* NOTHING, and a caller that does pass a signal still gets THEIR abort — their
|
|
110
|
+
* reason, their error — because `fetchWithDeadline` COMPOSES the two rather
|
|
111
|
+
* than replacing one with the other.
|
|
112
|
+
*
|
|
78
113
|
* @param {string} path Jira REST path, e.g. `/rest/api/3/issue/PROJ-1`
|
|
79
|
-
* @param {{ method?: string, body?: any, headers?: object }} [opts]
|
|
114
|
+
* @param {{ method?: string, body?: any, headers?: object, signal?: AbortSignal }} [opts]
|
|
80
115
|
* @returns {Promise<any>} parsed JSON response body
|
|
81
116
|
*/
|
|
82
117
|
export declare function jiraFetch(path: any, opts?: any): Promise<any>;
|
package/dist/jira.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import{createRequire as
|
|
2
|
-
`)
|
|
3
|
-
|
|
4
|
-
`);continue}let r
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
${
|
|
9
|
-
|
|
10
|
-
`)
|
|
11
|
-
|
|
12
|
-
${
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
${r}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`)
|
|
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,`
|
|
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+=`
|
|
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
|
+
`;break;case"heading":n+=`${"#".repeat(Math.max(1,Math.min(ee,t.level)))} ${v(t.c)}
|
|
8
|
+
`;break;case"bullet":for(let e of t.items)n+=`- ${N(e).trim()}
|
|
9
|
+
`;break;case"ordered":t.items.forEach((e,i)=>{n+=`${t.start+i}. ${N(e).trim()}
|
|
10
|
+
`});break;case"task":for(let e of t.items)n+=`- [${e.checked?"x":" "}] ${v(e.c)}
|
|
11
|
+
`;break;case"code":n+=`\`\`\`${t.lang}
|
|
12
|
+
${t.v}
|
|
13
|
+
\`\`\`
|
|
14
|
+
`;break;case"quote":n+=Z(N(t.c));break;case"panel":n+=`> [!${V[t.kind]}]
|
|
15
|
+
${Z(N(t.c))}`;break;case"rule":n+=`---
|
|
16
|
+
`;break;case"table":{let e=i=>`| ${i.map(r=>v(r).replace(/\|/g,"\\|")).join(" | ")} |
|
|
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(`
|
|
19
|
+
`).map(n=>n?`> ${n}`:">").join(`
|
|
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
|
|
20
22
|
You have direct access to the user's Jira. Use these tools proactively:
|
|
21
23
|
|
|
22
24
|
### Issue tools
|
|
@@ -66,4 +68,4 @@ When user asks to move/transition ticket status:
|
|
|
66
68
|
3. Pick the correct transition from returned list (match by "to" status name, not guesswork), then call jira_transition_issue with transitionId.
|
|
67
69
|
4. Call jira_get_issue(issueKey) to verify final status before claiming success.
|
|
68
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.
|
|
69
|
-
6. IMPORTANT: When target is clear, complete transition + verification in SAME turn. Do NOT stop after listing options.`,resolve(){let a=K();if(!a)return null;let n={};for(let e of this.envKeys)process.env[e]&&(n[e]=process.env[e]);process.env.ATLASSIAN_INSTANCE_URL&&(n.ATLASSIAN_INSTANCE_URL=process.env.ATLASSIAN_INSTANCE_URL);for(let e of["JIRA_API_TOKEN","JIRA_EMAIL","JIRA_BASE_URL"])process.env[e]&&(n[e]=process.env[e]);return{command:"node",args:[a],env:n,description:this.description}},async handleToolCall(a,n){try{switch(a){case"jira_list_projects":{let e=await f("/rest/api/3/project"),t=(Array.isArray(e)?e:[]).map(r=>({id:r.id,key:r.key,name:r.name,style:r.style}));return JSON.stringify({count:t.length,projects:t})}case"jira_list_statuses":{let{projectKey:e}=n||{};if(e){let i=await f(`/rest/api/3/project/${encodeURIComponent(e)}/statuses`),o=Array.isArray(i)?i:[],s=new Map;for(let u of o)for(let p of u.statuses||[])p?.id&&(s.has(p.id)||s.set(p.id,{id:p.id,name:p.name,category:p.statusCategory?.name||null}));let c=[...s.values()].sort((u,p)=>String(u.name).localeCompare(String(p.name)));return JSON.stringify({scope:"project",projectKey:e,count:c.length,statuses:c})}let t=await f("/rest/api/3/status"),r=(Array.isArray(t)?t:[]).map(i=>({id:i.id,name:i.name,category:i.statusCategory?.name||null})).sort((i,o)=>String(i.name).localeCompare(String(o.name)));return JSON.stringify({scope:"global",count:r.length,statuses:r})}case"jira_list_issue_types":{let{projectKey:e}=n||{};if(!e)return JSON.stringify({error:"projectKey is required"});let t=await O(e);return JSON.stringify({projectKey:e,count:t.length,issueTypes:t})}case"jira_search":{let e=n.jql||"",t=n.maxResults||20;e.replace(/\s*ORDER\s+BY\s+.*/i,"").trim()||(e=`created >= -365d ${e}`.trim());let i=`jql=${encodeURIComponent(e)}&maxResults=${t}&fields=summary,status,assignee,priority,updated,issuetype,project`,s=((await f(`/rest/api/3/search/jql?${i}`)).issues||[]).map(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:s.length,issues:s})}case"jira_get_issue":{let e=n.issueKey;if(!e)return JSON.stringify({error:"issueKey is required"});let t=await f(`/rest/api/3/issue/${e}`);return JSON.stringify({key:t.key,project:t.fields?.project?.key,summary:t.fields?.summary,description:t.fields?.description,status:t.fields?.status?.name,assignee:t.fields?.assignee?.displayName||"Unassigned",priority:t.fields?.priority?.name,type:t.fields?.issuetype?.name,labels:t.fields?.labels,created:t.fields?.created,updated:t.fields?.updated})}case"jira_create_issue":{let{projectKey:e,summary:t,issueType:r,description:i,priority:o,labels:s,assigneeId:c,moveToSprint:u,moveToActiveSprint:p,sprintId:d,sprintName:m,target:g}=n;if(!e||!t)return JSON.stringify({error:"projectKey and summary are required"});let l={requested:r||null,resolved:null,strategy:"none"},h=[];try{h=await O(e),l=E(r,h)}catch{}let y={project:{key:e},summary:t,issuetype:l?.resolved?.id?{id:l.resolved.id}:{name:r||"Task"}};i&&(y.description={type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:i}]}]}),o&&(y.priority={name:o}),s?.length&&(y.labels=s),c&&(y.assignee={id:c});let j=await f("/rest/api/3/issue",{method:"POST",body:{fields:y}}),_={ok:!0,key:j.key,id:j.id,self:j.self};return l?.resolved&&(_.issueType=l.resolved.name,_.issueTypeResolution=l.strategy,l.strategy!=="exact"&&l.requested&&b(l.requested)!==b(l.resolved.name)&&(_.issueTypeWarning=`Requested "${l.requested}" is not available in ${e}; used "${l.resolved.name}" instead.`)),h.length>0&&(_.availableIssueTypes=h.map(k=>k.name)),(u||p)&&(_.sprintMove=await w({issueKey:j.key,projectKey:e,sprintId:d,sprintName:m,target:g})),JSON.stringify(_)}case"jira_list_sprints":{let{projectKey:e,state:t}=n,r=await R(e,t);return JSON.stringify({count:r.length,sprints:r})}case"jira_move_to_active_sprint":{let{issueKey:e,projectKey:t,sprintId:r,sprintName:i,target:o}=n||{},s=await w({issueKey:e,projectKey:t,sprintId:r,sprintName:i,target:o||"current"});return JSON.stringify(s)}case"jira_move_issue_to_sprint":{let{issueKey:e,projectKey:t,sprintId:r,sprintName:i,target:o}=n||{},s=await w({issueKey:e,projectKey:t,sprintId:r,sprintName:i,target:o});return JSON.stringify(s)}case"jira_get_sprint_issues":{let{sprintName:e,sprintId:t,projectKey:r,status:i,maxResults:o}=n;if(!e&&!t)return JSON.stringify({error:"sprintName or sprintId is required"});let s=o||50,c=t?`sprint = ${t}`:`sprint = "${e}"`,u=r?`project = ${r} AND `:"",p=i?` AND status = "${i}"`:"",d=`${u}${c}${p} ORDER BY status ASC, priority DESC`,m=`jql=${encodeURIComponent(d)}&maxResults=${s}&fields=summary,status,assignee,priority,issuetype,project`,g=await f(`/rest/api/3/search/jql?${m}`),l=(g.issues||[]).map(y=>({key:y.key,project:y.fields?.project?.key,summary:y.fields?.summary,status:y.fields?.status?.name,assignee:y.fields?.assignee?.displayName||"Unassigned",priority:y.fields?.priority?.name,type:y.fields?.issuetype?.name})),h={};for(let y of l)h[y.status]=(h[y.status]||0)+1;return JSON.stringify({count:l.length,total:g.total||l.length,statusCounts:h,issues:l})}case"jira_get_comments":{let{issueKey:e,maxResults:t}=n;if(!e)return JSON.stringify({error:"issueKey is required"});let i=await f(`/rest/api/3/issue/${e}/comment?maxResults=${t||50}&orderBy=-created`),o=(i.comments||[]).map(s=>{let c="";return s.body?.content&&(c=S(s.body.content)),{id:s.id,author:s.author?.displayName||"Unknown",body:c,created:s.created,updated:s.updated}});return JSON.stringify({count:o.length,total:i.total||o.length,comments:o})}case"jira_add_comment":{let{issueKey:e,body:t}=n;return!e||!t?JSON.stringify({error:"issueKey and body are required"}):(await f(`/rest/api/3/issue/${e}/comment`,{method:"POST",body:{body:{type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:t}]}]}}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_edit_issue":{let{issueKey:e,fields:t}=n;return!e||!t?JSON.stringify({error:"issueKey and fields are required"}):(await f(`/rest/api/3/issue/${e}`,{method:"PUT",body:{fields:t}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_transition_issue":{let{issueKey:e,transitionId:t,toStatus:r,statusName:i,status:o}=n;if(!e)return JSON.stringify({error:"issueKey is required"});let s=String(r||i||o||"").trim();if(!t&&!s){let d=((await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[]).map(m=>({id:m.id,name:m.name,to:m.to?.name}));return JSON.stringify({ok:!1,error:"transitionId or toStatus is required",issueKey:e,availableTransitions:d})}let c=t;if(!c){let d=(await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[],m=v(s),g=d.find(l=>v(l?.name||"")===m||v(l?.to?.name||"")===m);if(!g){let l=A(s);l.length>=2&&(g=d.find(h=>{let y=A(h?.name||""),j=A(h?.to?.name||""),_=y.length>=2&&(y.includes(l)||l.includes(y)),k=j.length>=2&&(j.includes(l)||l.includes(j));return _||k}))}if(!g){let l=d.map(_=>{let k=I(s,_?.name||""),T=I(s,_?.to?.name||"");return{t:_,score:Math.max(k,T)}}).sort((_,k)=>k.score-_.score),h=l[0],y=l[1];h&&h.score>=.45&&(!y||h.score-y.score>=.12)&&(g=h.t)}if(!g?.id)return JSON.stringify({ok:!1,error:`No transition matches target status: "${s}"`,issueKey:e,availableTransitions:d.map(l=>({id:l.id,name:l.name,to:l.to?.name}))});c=g.id}await f(`/rest/api/3/issue/${e}/transitions`,{method:"POST",body:{transition:{id:c}}});let u=await f(`/rest/api/3/issue/${e}?fields=status`);return JSON.stringify({ok:!0,issueKey:e,transitionId:c,statusAfter:u?.fields?.status?.name||null})}default:return JSON.stringify({error:`Unknown tool: ${a}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"jira_list_projects",description:"List all Jira projects accessible to the user",input_schema:{type:"object",properties:{}}},{name:"jira_list_statuses",description:"List Jira statuses. Use projectKey to get statuses applicable in that project workflow.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Optional project key (e.g. PROJ). If omitted, returns global status catalog."}}}},{name:"jira_list_issue_types",description:"List issue types allowed for issue creation in the given project.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"}},required:["projectKey"]}},{name:"jira_search",description:"Search Jira issues using JQL",input_schema:{type:"object",properties:{jql:{type:"string",description:'JQL query string, e.g. "project = PROJ AND status = Open"'},maxResults:{type:"number",description:"Max results to return (default 20)"}},required:["jql"]}},{name:"jira_get_issue",description:"Get details of a specific Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"}},required:["issueKey"]}},{name:"jira_create_issue",description:"Create a new Jira issue",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},summary:{type:"string",description:"Issue title/summary"},issueType:{type:"string",description:"Issue type (default: Task). Common: Task, Bug, Story, Epic"},description:{type:"string",description:"Issue description (plain text)"},priority:{type:"string",description:"Priority name, e.g. High, Medium, Low"},labels:{type:"array",items:{type:"string"},description:"Array of label strings"},assigneeId:{type:"string",description:"Atlassian account ID to assign to"},moveToSprint:{type:"boolean",description:"If true, move created issue to a sprint and verify."},moveToActiveSprint:{type:"boolean",description:"Backward-compatible alias for moveToSprint."},sprintId:{type:"number",description:"Optional sprint id for placement."},sprintName:{type:"string",description:"Optional sprint name for placement."},target:{type:"string",description:"Placement target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["projectKey","summary"]}},{name:"jira_list_sprints",description:"List sprints for a Jira project (returns sprint names, IDs, states, dates)",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},state:{type:"string",description:"Filter: active, closed, future. Omit for all."}},required:["projectKey"]}},{name:"jira_get_sprint_issues",description:"Get all issues in a sprint, optionally filtered by status column name",input_schema:{type:"object",properties:{sprintName:{type:"string",description:"Sprint name (from jira_list_sprints). Use this OR sprintId."},sprintId:{type:"number",description:"Sprint ID (from jira_list_sprints). Use this OR sprintName."},projectKey:{type:"string",description:"Project key to scope the search (optional)"},status:{type:"string",description:'Filter by status name (e.g. "\u8FDB\u884C\u4E2D", "\u6D4B\u8BD5", "Done")'},maxResults:{type:"number",description:"Max issues to return (default 50)"}}}},{name:"jira_move_to_active_sprint",description:"Backward-compatible alias: move issue to sprint target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_move_issue_to_sprint",description:"Move an issue to a sprint by id/name/target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_get_comments",description:"Get comments on a Jira issue (newest first)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},maxResults:{type:"number",description:"Max comments to return (default 50)"}},required:["issueKey"]}},{name:"jira_add_comment",description:"Add a comment to a Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},body:{type:"string",description:"Comment text (plain text)"}},required:["issueKey","body"]}},{name:"jira_edit_issue",description:"Update fields on a Jira issue (summary, story points, labels, priority)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},fields:{type:"object",description:"Object of field names to values",additionalProperties:!0}},required:["issueKey","fields"]}},{name:"jira_transition_issue",description:"Move a Jira issue to a different status. Always pass toStatus when user gave a target; only pass issueKey alone when you explicitly need to list transitions.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},transitionId:{type:"string",description:"Transition ID to perform (optional if toStatus is provided)"},toStatus:{type:"string",description:'Target status/column name (e.g. "\u5DF2\u7ECF\u9A8C\u6536", "Done", "In Progress"). If provided, tool resolves matching transition automatically.'}},required:["issueKey"]}}]};export{M as jiraApiCall,f as jiraFetch,W as jiraSkill,B as resolveJiraCredential};
|
|
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};
|
package/dist/kvMemory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as y,readFileSync as v}from"node:fs";import{homedir as b}from"node:os";import{join as S,dirname as h,resolve as I}from"node:path";import{fileURLToPath as g}from"node:url";var s={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 f(e,t,r=process.env){let n=Number(r?.[e]);return Number.isFinite(n)&&n>0?Math.min(6e5,Math.max(1e3,Math.floor(n))):t}function _(e="api",t=process.env){let r=s[e]||s.api;return f(r.knob,r.fallback,t)}function k(e){return e?.name==="TimeoutError"||e?.name==="AbortError"}function T(e){try{return new URL(String(e?.url??e)).host||"unknown host"}catch{return"unknown host"}}function O(e,t){if(!e)return t;let r=new AbortController,n=i=>{r.signal.aborted||r.abort(i)},o=()=>n(e.reason),a=()=>n(t.reason);return r.signal.addEventListener("abort",()=>{e.removeEventListener("abort",o),t.removeEventListener("abort",a)},{once:!0}),e.aborted?n(e.reason):t.aborted?n(t.reason):(e.addEventListener("abort",o,{once:!0}),t.addEventListener("abort",a,{once:!0})),r.signal}async function d(e,t={},r={}){let n=r.kind||"api",o=(s[n]||s.api).knob,a=r.timeoutMs?Math.min(6e5,Math.max(1e3,Math.floor(r.timeoutMs))):_(n),i=t?.signal;if(i?.aborted)throw i.reason??new DOMException("This operation was aborted","AbortError");let l=AbortSignal.timeout(a),m=O(i,l);try{return await fetch(e,{...t,signal:m})}catch(u){throw l.aborted&&k(u)?new Error(`${r.what||"request"} TIMED OUT after ${a}ms against ${T(e)} (${o})`):u}}function E(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let e=h(g(import.meta.url)),t=I(e,"..","bin","mcp-skill.mjs");return y(t)?t:null}function M(){if(process.env.PROJECT_API_TOKEN)return process.env.PROJECT_API_TOKEN;if(process.env.ZIBBY_USER_TOKEN)return process.env.ZIBBY_USER_TOKEN;try{let e=S(b(),".zibby","config.json");return y(e)&&JSON.parse(v(e,"utf-8")).sessionToken||null}catch{return null}}function L(){return process.env.ZIBBY_ACCOUNT_API_URL?process.env.ZIBBY_ACCOUNT_API_URL.replace(/\/$/,""):(process.env.ZIBBY_ENV||"prod")==="local"?"http://localhost:3001":process.env.ZIBBY_PROD_ACCOUNT_API_URL||"https://api-prod.zibby.app"}function P(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function c(e){return`${P()}:${e}`}async function p(e,t){let r=M();if(!r)throw new Error("No backend credential (PROJECT_API_TOKEN). KV memory is only available inside a Zibby run.");let n=`${L()}/credits/review-memory`,o=await d(n,{method:"POST",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},body:JSON.stringify({op:e,...t})},{kind:"api",what:`kv-memory ${e}`});if(!o.ok){let a=await o.text().catch(()=>"");throw new Error(`KV memory ${e} failed (${o.status}): ${a.slice(0,300)}`)}return o.json()}var K={id:"kv-memory",callsBackend:!0,serverName:"kv_memory",allowedTools:["mcp__kv_memory__*"],description:"KV memory \u2014 a private, per-agent persistent key\u2192value store across stateless runs (auto-namespaced)",promptFragment:`## KV Memory (private, per-agent, persistent key-value store)
|
|
2
2
|
You have a PRIVATE per-agent key-value memory that survives across your
|
|
3
3
|
stateless runs. It is automatically namespaced to YOU (this agent type) \u2014 other
|
|
4
4
|
agents cannot see or collide with your entries, and you don't need to prefix
|
|
@@ -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
|
|
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};
|
package/dist/lark.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as O}from"fs";import{fileURLToPath as L}from"url";import{dirname as v,resolve as E}from"path";import{resolveIntegrationToken as N}from"@zibby/core/backend-client.js";var f=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"}),w=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 h={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 b(t,e,n=process.env){let i=Number(n?.[t]);return Number.isFinite(i)&&i>0?Math.min(6e5,Math.max(1e3,Math.floor(i))):e}function y(t="api",e=process.env){let n=h[t]||h.api;return b(n.knob,n.fallback,e)}function I(t){return t?.name==="TimeoutError"||t?.name==="AbortError"}function T(t){try{return new URL(String(t?.url??t)).host||"unknown host"}catch{return"unknown host"}}function S(t,e){if(!t)return e;let n=new AbortController,i=d=>{n.signal.aborted||n.abort(d)},r=()=>i(t.reason),a=()=>i(e.reason);return n.signal.addEventListener("abort",()=>{t.removeEventListener("abort",r),e.removeEventListener("abort",a)},{once:!0}),t.aborted?i(t.reason):e.aborted?i(e.reason):(t.addEventListener("abort",r,{once:!0}),e.addEventListener("abort",a,{once:!0})),n.signal}async function g(t,e={},n={}){let i=n.kind||"api",r=(h[i]||h.api).knob,a=n.timeoutMs?Math.min(6e5,Math.max(1e3,Math.floor(n.timeoutMs))):y(i),d=e?.signal;if(d?.aborted)throw d.reason??new DOMException("This operation was aborted","AbortError");let m=AbortSignal.timeout(a),o=S(d,m);try{return await fetch(t,{...e,signal:o})}catch(l){throw m.aborted&&I(l)?new Error(`${n.what||"request"} TIMED OUT after ${a}ms against ${T(t)} (${r})`):l}}function M(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let t=v(L(import.meta.url)),e=E(t,"..","bin","mcp-lark.mjs");return O(e)?e:null}var A=6e3*1e3,u=null;async function P(){let{appId:t,appSecret:e,host:n}=await N("lark");if(u&&u.appId===t&&u.expiresAt>Date.now())return{token:u.token,host:n};let r=await(await g(`${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 u={token:r.tenant_access_token,expiresAt:Date.now()+A,appId:t},{token:r.tenant_access_token,host:n}}async function p(t,e,n={}){let{token:i,host:r}=await P(),a=`${r}${e}`,d={method:t,headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json; charset=utf-8"}};t!=="GET"&&(d.body=JSON.stringify(n));let o=await(await g(a,d,{kind:"api",what:`Lark ${t} ${e}`})).json();if(o.code!==0)throw new Error(`Lark API ${e} error: ${o.msg||o.code}`);return o.data||{}}function k(t){return JSON.stringify({text:t})}function R(t){return!t||typeof t!="string"||t.startsWith("oc_")?"chat_id":t.startsWith("ou_")?"open_id":t.startsWith("on_")?"union_id":t.startsWith("cli_")?"app_id":t.includes("@")?"email":"chat_id"}var G={id:"lark",callsBackend:!0,serverName:"lark",allowedTools:["mcp__lark__*"],requiresIntegration:f.LARK,description:"Lark / Feishu messaging \u2014 send messages and reply in threads.",envKeys:["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV"],promptFragment:`## Lark
|
|
2
2
|
You can send messages and replies on Lark. Use:
|
|
3
3
|
- lark_send_message: post a message to a chat, user, or DM
|
|
4
4
|
- lark_reply: reply to an existing message (threaded)
|
|
5
5
|
- lark_list_chats: list chats the bot is a member of
|
|
6
6
|
- lark_get_chat_history: fetch recent messages in a chat
|
|
7
7
|
- lark_lookup_user_by_email: resolve an email \u2192 open_id for direct DM (prefer this over emailing through lark_send_message when the agent has a user_id already)
|
|
8
|
-
When responding to an incoming event, prefer lark_reply with the source message_id so the response threads cleanly.`,resolve(){let
|
|
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};
|
package/dist/larkAttendance.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as E}from"fs";import{fileURLToPath as U}from"url";import{dirname as M,resolve as C}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"}),ee=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 I}from"@zibby/core/backend-client.js";var P="https://open.larksuite.com",A=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"})]),T=Object.freeze(A.flatMap(t=>[t.appId,t.appSecret,t.host]));function y(t){let e=String(t||"").trim().replace(/\/+$/,"");return e?/^https?:\/\//i.test(e)?e:`https://${e}`:P}function R(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:y(process.env[t.host])}}async function b(){for(let t of A){let e=R(t);if(e)return e}try{let t=await I(c.LARK_DOCS);return{appId:t?.appId,appSecret:t?.appSecret,host:y(t?.host)}}catch(t){if(!/unknown provider/i.test(String(t?.message||"")))throw t;let e=await I(c.LARK);return{appId:e?.appId,appSecret:e?.appSecret,host:y(e?.host)}}}function q(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=M(U(import.meta.url)),e=C(t,"..","bin","mcp-skill.mjs");return E(e)?e:null}var j=6e3*1e3,p=null,x=50,G=50,Y=40,K=31,$=200,z=31,J=50,v=50,D=Object.freeze(["daily","month"]),w=Object.freeze(["zh","en","ja"]),B=Object.freeze(["employee_id","employee_no"]),F=T;async function H(){let{appId:t,appSecret:e,host:r}=await b();if(p&&p.appId===t&&p.expiresAt>Date.now())return{token:p.token,host:r};let a=await(await fetch(`${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})})).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()+j,appId:t},{token:a.tenant_access_token,host:r}}function V(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 H(),i={method:t,headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json; charset=utf-8"}};t!=="GET"&&r!==void 0&&(i.body=JSON.stringify(r));let d=await(await fetch(`${a}${e}`,i)).json();if(d.code!==0)throw new Error(V(e,d.code,d.msg));return d.data||{}}function O(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 i=new Date(Date.UTC(r,n-1,a));return i.getUTCFullYear()!==r||i.getUTCMonth()!==n-1||i.getUTCDate()!==a?null:Number(e)}function X(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 g(t,e){let r=O(t?.startDate??t?.start_date),n=O(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=X(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 N(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 h(t,e,r){let n=String(t||"").trim().toLowerCase();return e.includes(n)?n:r}function _(t){return h(t?.employeeType??t?.employee_type,B,"employee_id")}function W(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 Q(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 Z(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 le={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",...F],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=q();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),x):G,a=new URLSearchParams({page_size:String(n)}),i=String(e?.pageToken??e?.page_token??"").trim();i&&a.set("page_token",i);let s=await l("GET",`/open-apis/attendance/v1/groups?${a.toString()}`),d=(Array.isArray(s?.group_list)?s.group_list:[]).map(o=>({groupId:String(o?.group_id||""),groupName:String(o?.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=_(e),a=new URLSearchParams({employee_type:n,dept_type:"open_id"}),i=await l("GET",`/open-apis/attendance/v1/groups/${encodeURIComponent(r)}?${a.toString()}`),s=d=>Array.isArray(d)?d.map(o=>String(o)):[];return JSON.stringify({ok:!0,groupId:String(i?.group_id||r),groupName:String(i?.group_name||""),employeeType:n,memberUserIds:s(i?.bind_user_ids),memberDeptIds:s(i?.bind_dept_ids),excludedUserIds:s(i?.except_user_ids),excludedDeptIds:s(i?.except_dept_ids),noPunchUserIds:s(i?.bind_default_user_ids),noPunchDeptIds:s(i?.bind_default_dept_ids),...Array.isArray(i?.group_leader_ids)?{leaderUserIds:s(i.group_leader_ids)}:{}})}case"larkattendance_list_stats_fields":{let r=g(e,Y);if(r.error)return JSON.stringify({ok:!1,error:r.error});let n=h(e?.statsType??e?.stats_type,D,"month"),a=h(e?.locale,w,"zh"),i=_(e),d=(await l("POST",`/open-apis/attendance/v1/user_stats_fields/query?employee_type=${encodeURIComponent(i)}`,{locale:a,stats_type:n,start_date:r.startDate,end_date:r.endDate}))?.user_stats_field||{},o=(Array.isArray(d?.fields)?d.fields:[]).map(W);return JSON.stringify({ok:!0,statsType:n,locale:a,startDate:r.startDate,endDate:r.endDate,fieldGroupCount:o.length,fieldGroups:o})}case"larkattendance_query_stats":{let r=g(e,K);if(r.error)return JSON.stringify({ok:!1,error:r.error});let n=N(e,$);if(n.error)return JSON.stringify({ok:!1,error:n.error});let a=h(e?.statsType??e?.stats_type,D,"month"),i=h(e?.locale,w,"zh"),s=_(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 o={locale:i,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},f=Array.isArray(e?.fieldCodes)?e.fieldCodes:null,k=f?f.map(u=>String(u||"").trim()).filter(Boolean):[],L=k.length?new Set(k):null,m=await l("POST",`/open-apis/attendance/v1/user_stats_datas/query?employee_type=${encodeURIComponent(s)}`,o),S=(Array.isArray(m?.user_datas)?m.user_datas:[]).map(u=>Q(u,L));return JSON.stringify({ok:!0,statsType:a,startDate:r.startDate,endDate:r.endDate,employeeType:s,userCount:S.length,users:S,invalidUserIds:Array.isArray(m?.invalid_user_list)?m.invalid_user_list.map(u=>String(u)):[]})}case"larkattendance_query_records":{let r=g({startDate:e?.startDate??e?.checkDateFrom??e?.check_date_from,endDate:e?.endDate??e?.checkDateTo??e?.check_date_to},z);if(r.error)return JSON.stringify({ok:!1,error:r.error});let n=N(e,J);if(n.error)return JSON.stringify({ok:!1,error:n.error});let a=_(e),i=new URLSearchParams({employee_type:a,ignore_invalid_users:"true"});e?.includeTerminatedUser===!0&&i.set("include_terminated_user","true");let s=await l("POST",`/open-apis/attendance/v1/user_tasks/query?${i.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(Z);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(o=>String(o)):[],unauthorizedUserIds:Array.isArray(s?.unauthorized_user_ids)?s.unauthorized_user_ids.map(o=>String(o)):[]})}case"larkattendance_resolve_users":{let r=e?.emails,n=(Array.isArray(r)?r:typeof r=="string"?[r]:[]).map(o=>String(o||"").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>v)return JSON.stringify({ok:!1,error:`At most ${v} 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(o=>o?.user_id).map(o=>({email:String(o?.email||""),userId:String(o.user_id)})),d=new Set(s.map(o=>o.email));return JSON.stringify({ok:!0,employeeType:"employee_id",users:s,notFound:n.filter(o=>!d.has(o))})}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 pe(){p=null}export{F as LARK_ATTENDANCE_APP_ENV_KEYS,pe as _resetLarkAttendanceTokenCache,X as daySpan,le as larkAttendanceSkill,O 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,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};
|