@zibby/skills 0.2.21 → 0.2.22

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.
@@ -1 +1 @@
1
- var n=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"}),i=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark Docs",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"}});export{n as INTEGRATIONS,i as INTEGRATION_REGISTRY};
1
+ var n=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"}),i=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark 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"}});export{n as INTEGRATIONS,i as INTEGRATION_REGISTRY};
package/dist/jira.js CHANGED
@@ -1,4 +1,4 @@
1
- import{createRequire as J}from"module";import{resolveIntegrationToken as T,clearTokenCache as $}from"@zibby/core/backend-client.js";var O=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"}),U=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark Docs",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 K=J(import.meta.url);function q(){if(process.env.MCP_JIRA_PATH)return process.env.MCP_JIRA_PATH;try{return K.resolve("@zibby/mcp-jira/index.js")}catch{return null}}var L=new Set(["paragraph","heading","bulletList","orderedList","listItem","blockquote","codeBlock","rule","table","tableRow","tableCell","tableHeader","mediaSingle","panel"]);function C(c,n){if(!n||!n.length)return c;let e=c;for(let t of n)t.type==="strong"?e=`**${e}**`:t.type==="em"?e=`_${e}_`:t.type==="code"?e=`\`${e}\``:t.type==="strike"?e=`~~${e}~~`:t.type==="link"&&t.attrs?.href&&(e=`[${e}](${t.attrs.href})`);return e}function S(c,n=0){if(!Array.isArray(c))return"";let e=[];for(let t of c){if(t.type==="text"){e.push(C(t.text||"",t.marks));continue}if(t.type==="hardBreak"){e.push(`
1
+ import{createRequire as J}from"module";import{resolveIntegrationToken as T,clearTokenCache as $}from"@zibby/core/backend-client.js";var O=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"}),U=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark 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 K=J(import.meta.url);function q(){if(process.env.MCP_JIRA_PATH)return process.env.MCP_JIRA_PATH;try{return K.resolve("@zibby/mcp-jira/index.js")}catch{return null}}var C=new Set(["paragraph","heading","bulletList","orderedList","listItem","blockquote","codeBlock","rule","table","tableRow","tableCell","tableHeader","mediaSingle","panel"]);function L(c,n){if(!n||!n.length)return c;let e=c;for(let t of n)t.type==="strong"?e=`**${e}**`:t.type==="em"?e=`_${e}_`:t.type==="code"?e=`\`${e}\``:t.type==="strike"?e=`~~${e}~~`:t.type==="link"&&t.attrs?.href&&(e=`[${e}](${t.attrs.href})`);return e}function S(c,n=0){if(!Array.isArray(c))return"";let e=[];for(let t of c){if(t.type==="text"){e.push(L(t.text||"",t.marks));continue}if(t.type==="hardBreak"){e.push(`
2
2
  `);continue}if(t.type==="rule"){e.push(`
3
3
  ---
4
4
  `);continue}let r=t.content?S(t.content,n+1):"";if(t.type==="listItem")e.push(r);else if(t.type==="bulletList"){let i=(t.content||[]).map(o=>`- ${S(o.content||[],n+1).trim()}`);e.push(`
@@ -11,7 +11,7 @@ ${i.join(`
11
11
 
12
12
  ${"#".repeat(i)} ${r.trim()}
13
13
 
14
- `)}else L.has(t.type)?e.push(`
14
+ `)}else C.has(t.type)?e.push(`
15
15
 
16
16
  ${r}
17
17
  `):e.push(r)}return e.join("").replace(/\n{3,}/g,`
package/dist/lark.js CHANGED
@@ -1,4 +1,4 @@
1
- import{existsSync as k}from"fs";import{fileURLToPath as y}from"url";import{dirname as f,resolve as b}from"path";import{resolveIntegrationToken as v}from"@zibby/core/backend-client.js";var h=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",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"}),P=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark Docs",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"}});function I(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let i=f(y(import.meta.url)),e=b(i,"..","bin","mcp-lark.mjs");return k(e)?e:null}var N=6e3*1e3,p=null;async function S(){let{appId:i,appSecret:e,host:t}=await v("lark");if(p&&p.appId===i&&p.expiresAt>Date.now())return{token:p.token,host:t};let n=await(await fetch(`${t}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:i,app_secret:e})})).json();if(n.code!==0)throw new Error(`Lark tenant_access_token failed: ${n.msg||n.code}`);return p={token:n.tenant_access_token,expiresAt:Date.now()+N,appId:i},{token:n.tenant_access_token,host:t}}async function _(i,e,t={}){let{token:a,host:n}=await S(),r=`${n}${e}`,m={method:i,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json; charset=utf-8"}};i!=="GET"&&(m.body=JSON.stringify(t));let c=await(await fetch(r,m)).json();if(c.code!==0)throw new Error(`Lark API ${e} error: ${c.msg||c.code}`);return c.data||{}}function g(i){return JSON.stringify({text:i})}function O(i){return!i||typeof i!="string"||i.startsWith("oc_")?"chat_id":i.startsWith("ou_")?"open_id":i.startsWith("on_")?"union_id":i.startsWith("cli_")?"app_id":i.includes("@")?"email":"chat_id"}var C={id:"lark",callsBackend:!0,serverName:"lark",allowedTools:["mcp__lark__*"],requiresIntegration:h.LARK,description:"Lark / Feishu messaging \u2014 send messages and reply in threads.",envKeys:["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV"],promptFragment:`## Lark
1
+ import{existsSync as k}from"fs";import{fileURLToPath as y}from"url";import{dirname as f,resolve as b}from"path";import{resolveIntegrationToken as v}from"@zibby/core/backend-client.js";var h=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",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"}),P=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"}});function I(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let i=f(y(import.meta.url)),e=b(i,"..","bin","mcp-lark.mjs");return k(e)?e:null}var N=6e3*1e3,p=null;async function S(){let{appId:i,appSecret:e,host:t}=await v("lark");if(p&&p.appId===i&&p.expiresAt>Date.now())return{token:p.token,host:t};let n=await(await fetch(`${t}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:i,app_secret:e})})).json();if(n.code!==0)throw new Error(`Lark tenant_access_token failed: ${n.msg||n.code}`);return p={token:n.tenant_access_token,expiresAt:Date.now()+N,appId:i},{token:n.tenant_access_token,host:t}}async function _(i,e,t={}){let{token:a,host:n}=await S(),r=`${n}${e}`,m={method:i,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json; charset=utf-8"}};i!=="GET"&&(m.body=JSON.stringify(t));let c=await(await fetch(r,m)).json();if(c.code!==0)throw new Error(`Lark API ${e} error: ${c.msg||c.code}`);return c.data||{}}function g(i){return JSON.stringify({text:i})}function O(i){return!i||typeof i!="string"||i.startsWith("oc_")?"chat_id":i.startsWith("ou_")?"open_id":i.startsWith("on_")?"union_id":i.startsWith("cli_")?"app_id":i.includes("@")?"email":"chat_id"}var C={id:"lark",callsBackend:!0,serverName:"lark",allowedTools:["mcp__lark__*"],requiresIntegration:h.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)
@@ -0,0 +1,70 @@
1
+ /**
2
+ * larkApp — the ONE decider for "which Lark/Feishu APP credential do I mint a
3
+ * tenant_access_token from, and in what order do I look for it".
4
+ *
5
+ * Zibby has TWO Lark app credentials, and which one a consumer should use is a
6
+ * single decision, not one per skill:
7
+ * - the CHAT app (integration `lark`, env LARK_*) — PER-PROJECT
8
+ * - the DOCS/data app (integration `lark_docs`, env LARK_DOCS_*) — ACCOUNT-LEVEL
9
+ * `workflow-executor.js` resolves the chat row with resolveChatIntegration
10
+ * (project override → account fallback) and the docs row with
11
+ * getAccountProviderIntegration(accountId, 'lark_docs'), then injects each as
12
+ * its own env trio. TENANT-WIDE readers (docs/wiki, attendance) therefore lead
13
+ * with the ACCOUNT-LEVEL app: a project that never bound Lark chat has no
14
+ * project-scoped chat row at all, and an account-only lookup for `lark` lands
15
+ * on a credential-less LARK_TENANT# mapping row that silently yields nothing
16
+ * (observed live — see the comment at workflow-executor.js's `usesLark` block).
17
+ *
18
+ * PRECEDENCE (docs/account app first, chat app as the single-app fallback):
19
+ * 1. LARK_DOCS_* env — the account-level app the executor injected
20
+ * 2. LARK_* env — the chat app (one Lark app carrying every scope)
21
+ * 3. resolveIntegrationToken('lark_docs') → the backend resolver, which
22
+ * applies the SAME docs-row-then-chat-row order server-side for callers
23
+ * with no injected env (the Copilot: in-process/Lambda turns get no
24
+ * executor env, only the per-turn JWT).
25
+ *
26
+ * WHY IT LIVES IN ITS OWN MODULE (the bug this shape closes)
27
+ * ──────────────────────────────────────────────────────────
28
+ * The platform grew the dedicated `lark_docs` account integration (its own
29
+ * connect/disconnect in handlers/lark.js, its own "Lark App" card, its own
30
+ * LARK_DOCS_* injection) and the DECLARATION moved with it —
31
+ * skill-integrations.js maps `'lark-docs' → lark_docs`. The RUNTIME did not:
32
+ * the skill still asked for `lark`, and the backend token endpoint had no
33
+ * `lark_docs` case at all. So an account with the docs app connected and the
34
+ * chat app not passed every gate (green card, green toggle, deploy allowed)
35
+ * and then failed at call time with "lark is not connected" — a TWO-PLACES
36
+ * drift with nothing screaming, reproduced on two boxes 2026-08-12. Keeping the
37
+ * resolution in ONE module means the next Lark consumer inherits the fixed
38
+ * order instead of re-deriving (and re-breaking) it.
39
+ */
40
+ /**
41
+ * The two env trios that can carry a Lark app credential, in PRECEDENCE order.
42
+ * These names are the pre-existing platform contract — `workflow-executor.js`
43
+ * injects LARK_DOCS_* from the `lark_docs` integration row and LARK_* from the
44
+ * `lark` one, and lark-kb-sync's fetch-node reads them in this same order.
45
+ * A backend twin pins the pair: backend/src/services/__tests__/
46
+ * lark-docs-env-trio-tripwire.test.js.
47
+ */
48
+ export declare const LARK_APP_ENV_TRIOS: readonly (Readonly<{
49
+ appId: "LARK_DOCS_APP_ID";
50
+ appSecret: "LARK_DOCS_APP_SECRET";
51
+ host: "LARK_DOCS_HOST";
52
+ }> | Readonly<{
53
+ appId: "LARK_APP_ID";
54
+ appSecret: "LARK_APP_SECRET";
55
+ host: "LARK_HOST";
56
+ }>)[];
57
+ /** Every env name the resolution below can read (→ a consumer's envKeys). */
58
+ export declare const LARK_APP_ENV_KEYS: readonly ("LARK_DOCS_APP_ID" | "LARK_DOCS_APP_SECRET" | "LARK_DOCS_HOST" | "LARK_APP_ID" | "LARK_APP_SECRET" | "LARK_HOST")[];
59
+ /** Lark hosts are used as `${host}/open-apis/...` — force an absolute origin. */
60
+ export declare function normalizeLarkHost(raw: any): string;
61
+ /**
62
+ * Resolve the Lark APP CREDENTIAL to mint a tenant_access_token from, per the
63
+ * precedence documented above. Throws whatever the backend resolver throws when
64
+ * nothing is connected — callers fail-soft that into { ok:false, error }.
65
+ */
66
+ export declare function resolveLarkApp(): Promise<{
67
+ appId: any;
68
+ appSecret: any;
69
+ host: string;
70
+ }>;
@@ -0,0 +1 @@
1
+ import{resolveIntegrationToken as o}from"@zibby/core/backend-client.js";var i=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"}),c=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 s="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"})]),_=Object.freeze(a.flatMap(n=>[n.appId,n.appSecret,n.host]));function t(n){let e=String(n||"").trim().replace(/\/+$/,"");return e?/^https?:\/\//i.test(e)?e:`https://${e}`:s}function p(n){let e=String(process.env[n.appId]||"").trim(),r=String(process.env[n.appSecret]||"").trim();return!e||!r?null:{appId:e,appSecret:r,host:t(process.env[n.host])}}async function h(){for(let n of a){let e=p(n);if(e)return e}try{let n=await o(i.LARK_DOCS);return{appId:n?.appId,appSecret:n?.appSecret,host:t(n?.host)}}catch(n){if(!/unknown provider/i.test(String(n?.message||"")))throw n;let e=await o(i.LARK);return{appId:e?.appId,appSecret:e?.appSecret,host:t(e?.host)}}}export{_ as LARK_APP_ENV_KEYS,a as LARK_APP_ENV_TRIOS,t as normalizeLarkHost,h as resolveLarkApp};
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Lark / Feishu Attendance (考勤) skill — READ-ONLY, AGENT-DRIVEN.
3
+ *
4
+ * WHY THIS IS A SKILL AND NOT NODE CODE
5
+ * ─────────────────────────────────────
6
+ * Every tenant's attendance setup is different: different 考勤组 names, a
7
+ * different notion of "工时", and a per-tenant `自定义字段` group whose field
8
+ * codes exist nowhere in our source. So the fetch CANNOT be hard-coded — the
9
+ * model must DISCOVER what this tenant has (groups → members → available stat
10
+ * fields) and pick columns from a natural-language ask ("拿工时和出勤天数").
11
+ * That is why the surface leads with discovery tools and why every tool
12
+ * description is written to be self-sufficient (the agent has no docs).
13
+ * Deterministic aggregation/math over the returned rows is a DIFFERENT layer
14
+ * and deliberately not here — these tools return Lark's rows faithfully.
15
+ *
16
+ * Auth: the SHARED Lark app resolver (larkApp.ts) — LARK_DOCS_* env, then
17
+ * LARK_* env, then the backend `lark_docs` resolver — → tenant_access_token
18
+ * (cached ~100min) → Bearer. It leads with the ACCOUNT-LEVEL app on purpose:
19
+ * attendance is TENANT-WIDE (考勤组 are company-level, not per-project), while
20
+ * `lark` chat connects PER PROJECT — `workflow-executor.js` resolves the chat
21
+ * row with resolveChatIntegration(project override → account) and warns that an
22
+ * account-only lookup for `lark` lands on a credential-less LARK_TENANT#
23
+ * mapping row that silently yields nothing. Riding the project-scoped chat
24
+ * credential would therefore give a project with no Lark chat binding NO
25
+ * attendance data at all.
26
+ *
27
+ * KNOWN WRINKLE (deliberate, documented rather than hidden): the declared
28
+ * integration is the existing account-level `lark_docs` card, so the Settings
29
+ * UI does NOT tell an operator that the app behind that card also needs the
30
+ * attendance scopes (attendance:rule:readonly + attendance:task:readonly, plus
31
+ * contact:user.id:readonly for the email→user_id bridge). The additive upgrade
32
+ * path is a dedicated `lark_attendance` provider + card inserted at the FRONT
33
+ * of the fallback chain — zero migration for single-app setups, which keep
34
+ * resolving through the existing trios.
35
+ *
36
+ * PRIVACY: attendance is HR data. There is no "dump the tenant" tool — every
37
+ * data call takes an explicit, caller-supplied `userIds` scope, which is also
38
+ * what Lark's own API requires. Group/field listings are metadata only.
39
+ *
40
+ * PROVENANCE: shapes below marked [WIRE-VERIFIED] were confirmed against a real
41
+ * tenant on 2026-08-17; everything else comes from Lark's published docs. The
42
+ * probe corrected the docs twice (the group endpoint's member field names, and
43
+ * `user_id` being required on the stats query), so prefer the marked claims and
44
+ * treat an unmarked one as "documented, not observed".
45
+ *
46
+ * Attendance v1 API (host-relative), all read paths:
47
+ * GET /open-apis/attendance/v1/groups?page_size&page_token
48
+ * → { group_list:[{group_id,group_name}], page_token, has_more }
49
+ * scope: attendance:rule:readonly
50
+ * GET /open-apis/attendance/v1/groups/{id}?employee_type&dept_type=open_id
51
+ * → { group_id, group_name, bind_user_ids, bind_dept_ids,
52
+ * except_user_ids, except_dept_ids, bind_default_user_ids,
53
+ * bind_default_dept_ids, need_punch_members,
54
+ * no_need_punch_members, ... } [WIRE-VERIFIED]
55
+ * NB there is NO `member_ids` / `member_user_ids`; `bind_user_ids`
56
+ * IS the member list. `group_leader_ids` is documented but was not
57
+ * returned by the live tenant.
58
+ * scope: attendance:rule:readonly
59
+ * POST /open-apis/attendance/v1/user_stats_fields/query?employee_type
60
+ * body { locale, stats_type, start_date, end_date } (≤40-day span)
61
+ * → { user_stats_field:{ fields:[{code,title,child_fields:[…]}] } }
62
+ * scope: attendance:task:readonly
63
+ * POST /open-apis/attendance/v1/user_stats_datas/query?employee_type
64
+ * body { locale, stats_type, start_date, end_date, user_ids(≤200),
65
+ * user_id (REQUIRED — the OPERATOR; omitting it returns
66
+ * 1220001 "Need user_id" [WIRE-VERIFIED]),
67
+ * need_history, current_group_only } (≤31-day span)
68
+ * → { user_datas:[{name,user_id,datas:[{title,…}]}],
69
+ * invalid_user_list } [WIRE-VERIFIED: ~43 cells per user for a
70
+ * one-month range — period totals + identity columns + one per DAY]
71
+ * scope: attendance:task:readonly
72
+ * POST /open-apis/attendance/v1/user_tasks/query?employee_type&ignore_invalid_users
73
+ * body { user_ids(≤50), check_date_from, check_date_to,
74
+ * need_overtime_result }
75
+ * → { user_task_results:[…], invalid_user_ids, unauthorized_user_ids }
76
+ * scope: attendance:task:readonly
77
+ * POST /open-apis/contact/v3/users/batch_get_id?user_id_type=user_id
78
+ * body { emails } → { user_list:[{email,user_id}] }
79
+ * scope: contact:user.id:readonly
80
+ */
81
+ /**
82
+ * Every env name the SHARED app-credential resolution can read (→ envKeys).
83
+ * DERIVED from larkApp.ts's precedence order — never a second hand-kept list.
84
+ */
85
+ export declare const LARK_ATTENDANCE_APP_ENV_KEYS: readonly ("LARK_DOCS_APP_ID" | "LARK_DOCS_APP_SECRET" | "LARK_DOCS_HOST" | "LARK_APP_ID" | "LARK_APP_SECRET" | "LARK_HOST")[];
86
+ /**
87
+ * Normalize a date argument to Lark's yyyyMMdd INTEGER form. Accepts
88
+ * 20260401 (number), '20260401', '2026-04-01', '2026/04/01'. Returns null when
89
+ * the value cannot be read as a real calendar date — callers turn that into an
90
+ * explicit { ok:false } rather than sending garbage Lark answers 1220001 to.
91
+ */
92
+ export declare function toLarkDate(value: any): number | null;
93
+ /** Inclusive day span between two yyyyMMdd ints (20260401→20260401 = 1). */
94
+ export declare function daySpan(startInt: number, endInt: number): number;
95
+ export declare const larkAttendanceSkill: any;
96
+ export declare function _resetLarkAttendanceTokenCache(): void;
@@ -0,0 +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};
@@ -5,16 +5,18 @@
5
5
  * It runs on the DEDICATED Lark Docs app (integration `lark_docs`) — the app
6
6
  * carrying the docx (+ wiki/drive) scopes — and falls back to the chat app
7
7
  * (`lark`) so a single-app setup keeps working. Auth:
8
- * resolveLarkDocsApp() → { appId, appSecret, host } →
8
+ * resolveLarkApp() (shared, larkApp.ts) → { appId, appSecret, host } →
9
9
  * tenant_access_token (cached ~2h) → Bearer on every Docx call.
10
10
  *
11
11
  * Design rules (mirrors notion.js / googleDocs.js):
12
- * - resolveLarkDocsApp() is the SINGLE auth chokepoint (via the
13
- * larkDocsApi() helper). Don't re-resolve at call sites.
12
+ * - resolveLarkApp() is the SINGLE auth chokepoint (via the
13
+ * larkDocsApi() helper). Don't re-resolve at call sites. It lives in
14
+ * larkApp.ts because the docs app is also what other tenant-wide Lark
15
+ * readers (lark-attendance) must use — one decider, not one per skill.
14
16
  * - handleToolCall() dispatches the tools and NEVER throws — any HTTP or
15
17
  * parse failure is returned as { ok:false, error } so a missing/broken
16
18
  * Lark connection can't crash the run.
17
- * - Region-aware host: whatever resolveLarkDocsApp() returns as
19
+ * - Region-aware host: whatever resolveLarkApp() returns as
18
20
  * `host` (open.feishu.cn vs open.larksuite.com) is used verbatim, so the
19
21
  * skill always matches the connected app's region. No baked host, no CLI,
20
22
  * no heavy deps — pure `fetch`.
@@ -32,7 +34,11 @@
32
34
  * POST /open-apis/wiki/v2/spaces/{space_id}/nodes → { node:{node_token,obj_token,obj_type} }
33
35
  * (obj_type='docx', node_type='origin', optional parent_node_token)
34
36
  */
35
- /** Every env name the app-credential resolution below can read (→ envKeys). */
37
+ /**
38
+ * Every env name the SHARED app-credential resolution can read (→ envKeys).
39
+ * Re-exported under its historical name; the list is DERIVED from larkApp.ts's
40
+ * precedence order, never a second hand-kept copy.
41
+ */
36
42
  export declare const LARK_DOCS_APP_ENV_KEYS: readonly ("LARK_DOCS_APP_ID" | "LARK_DOCS_APP_SECRET" | "LARK_DOCS_HOST" | "LARK_APP_ID" | "LARK_APP_SECRET" | "LARK_HOST")[];
37
43
  /**
38
44
  * Derive the human-facing doc web URL from the open-api host + document id.
package/dist/larkDocs.js CHANGED
@@ -1,3 +1,3 @@
1
- import{existsSync as N,statSync as E,readFileSync as D}from"fs";import{fileURLToPath as $}from"url";import{basename as j,dirname as C,resolve as U}from"path";import{resolveIntegrationToken as T}from"@zibby/core/backend-client.js";var y=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",LARK_DOCS:"lark_docs",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",VIKUNJA:"vikunja",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),te=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark Docs",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"}});function J(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=C($(import.meta.url)),e=U(t,"..","bin","mcp-skill.mjs");return N(e)?e:null}var A=2e4,O=50,F=50,K=20,B=10*1024*1024,q=6e3*1e3,_=null,G="https://open.larksuite.com",R=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"})]),M=Object.freeze(R.flatMap(t=>[t.appId,t.appSecret,t.host]));function b(t){let e=String(t||"").trim().replace(/\/+$/,"");return e?/^https?:\/\//i.test(e)?e:`https://${e}`:G}function z(t){let e=String(process.env[t.appId]||"").trim(),n=String(process.env[t.appSecret]||"").trim();return!e||!n?null:{appId:e,appSecret:n,host:b(process.env[t.host])}}async function H(){for(let t of R){let e=z(t);if(e)return e}try{let t=await T(y.LARK_DOCS);return{appId:t?.appId,appSecret:t?.appSecret,host:b(t?.host)}}catch(t){if(!/unknown provider/i.test(String(t?.message||"")))throw t;let e=await T(y.LARK);return{appId:e?.appId,appSecret:e?.appSecret,host:b(e?.host)}}}async function P(){let{appId:t,appSecret:e,host:n}=await H();if(_&&_.appId===t&&_.expiresAt>Date.now())return{token:_.token,host:n};let o=await(await fetch(`${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})})).json();if(o.code!==0)throw new Error(`Lark tenant_access_token failed: ${o.msg||o.code}`);return _={token:o.tenant_access_token,expiresAt:Date.now()+q,appId:t},{token:o.tenant_access_token,host:n}}async function l(t,e,n){let{token:i,host:o}=await P(),r={method:t,headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json; charset=utf-8"}};t!=="GET"&&n!==void 0&&(r.body=JSON.stringify(n));let a=await(await fetch(`${o}${e}`,r)).json();if(a.code!==0)throw new Error(`Lark Docx API ${e} error: ${a.msg||a.code}`);return{data:a.data||{},host:o}}function g(t,e){return String(t||"").includes("feishu")?`https://feishu.cn/docx/${e}`:`https://www.larksuite.com/docx/${e}`}function Y(t,e){return String(t||"").includes("feishu")?`https://feishu.cn/wiki/${e}`:`https://www.larksuite.com/wiki/${e}`}function f(t){if(!t||typeof t!="string")return null;let e=t.trim();if(!e)return null;let n=e.match(/\/(docx|wiki)\/([A-Za-z0-9]+)/);return n?{type:n[1],token:n[2]}:/^[A-Za-z0-9]{10,}$/.test(e)?{type:"docx",token:e}:null}async function h(t){let e=typeof t=="string"?f(t):t;if(!e)throw new Error("A valid Lark doc id or URL is required");if(e.type==="docx")return e.token;let{data:n}=await l("GET",`/open-apis/wiki/v2/spaces/get_node?token=${encodeURIComponent(e.token)}`),i=n?.node||{};if(i.obj_type!=="docx"||!i.obj_token)throw new Error(`Wiki node is not a docx document (obj_type=${i.obj_type||"unknown"})`);return i.obj_token}function S(t){let e=String(t??"").replace(/\r\n/g,`
1
+ import{existsSync as E,statSync as C,readFileSync as U}from"fs";import{fileURLToPath as J}from"url";import{basename as K,dirname as F,resolve as B}from"path";var h=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"}),ne=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 b}from"@zibby/core/backend-client.js";var $="https://open.larksuite.com",T=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(T.flatMap(t=>[t.appId,t.appSecret,t.host]));function S(t){let e=String(t||"").trim().replace(/\/+$/,"");return e?/^https?:\/\//i.test(e)?e:`https://${e}`:$}function j(t){let e=String(process.env[t.appId]||"").trim(),n=String(process.env[t.appSecret]||"").trim();return!e||!n?null:{appId:e,appSecret:n,host:S(process.env[t.host])}}async function v(){for(let t of T){let e=j(t);if(e)return e}try{let t=await b(h.LARK_DOCS);return{appId:t?.appId,appSecret:t?.appSecret,host:S(t?.host)}}catch(t){if(!/unknown provider/i.test(String(t?.message||"")))throw t;let e=await b(h.LARK);return{appId:e?.appId,appSecret:e?.appSecret,host:S(e?.host)}}}function G(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=F(J(import.meta.url)),e=B(t,"..","bin","mcp-skill.mjs");return E(e)?e:null}var N=2e4,P=50,q=50,M=20,z=10*1024*1024,H=6e3*1e3,y=null,Y=O;async function D(){let{appId:t,appSecret:e,host:n}=await v();if(y&&y.appId===t&&y.expiresAt>Date.now())return{token:y.token,host:n};let o=await(await fetch(`${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})})).json();if(o.code!==0)throw new Error(`Lark tenant_access_token failed: ${o.msg||o.code}`);return y={token:o.tenant_access_token,expiresAt:Date.now()+H,appId:t},{token:o.tenant_access_token,host:n}}async function l(t,e,n){let{token:i,host:o}=await D(),r={method:t,headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json; charset=utf-8"}};t!=="GET"&&n!==void 0&&(r.body=JSON.stringify(n));let a=await(await fetch(`${o}${e}`,r)).json();if(a.code!==0)throw new Error(`Lark Docx API ${e} error: ${a.msg||a.code}`);return{data:a.data||{},host:o}}function g(t,e){return String(t||"").includes("feishu")?`https://feishu.cn/docx/${e}`:`https://www.larksuite.com/docx/${e}`}function V(t,e){return String(t||"").includes("feishu")?`https://feishu.cn/wiki/${e}`:`https://www.larksuite.com/wiki/${e}`}function f(t){if(!t||typeof t!="string")return null;let e=t.trim();if(!e)return null;let n=e.match(/\/(docx|wiki)\/([A-Za-z0-9]+)/);return n?{type:n[1],token:n[2]}:/^[A-Za-z0-9]{10,}$/.test(e)?{type:"docx",token:e}:null}async function _(t){let e=typeof t=="string"?f(t):t;if(!e)throw new Error("A valid Lark doc id or URL is required");if(e.type==="docx")return e.token;let{data:n}=await l("GET",`/open-apis/wiki/v2/spaces/get_node?token=${encodeURIComponent(e.token)}`),i=n?.node||{};if(i.obj_type!=="docx"||!i.obj_token)throw new Error(`Wiki node is not a docx document (obj_type=${i.obj_type||"unknown"})`);return i.obj_token}function L(t){let e=String(t??"").replace(/\r\n/g,`
2
2
  `),n=[];for(let i of e.split(`
3
- `)){let o=i.replace(/\s+$/,"");if(!o.trim())continue;let r=/^(#{1,3})\s+(.*)$/.exec(o),s=/^\s*[-*]\s+(.*)$/.exec(o),a=/^\s*\d+[.)]\s+(.*)$/.exec(o),c,d,p;if(r){let u=r[1].length;c=`heading${u}`,d=2+u,p=r[2]}else s?(c="bullet",d=12,p=s[1]):a?(c="ordered",d=13,p=a[1]):(c="text",d=2,p=o);n.push({block_type:d,[c]:{elements:[{text_run:{content:p}}],style:{}}})}return n}function I(t){let e=typeof t?.markdown=="string"?t.markdown:null,n=typeof t?.text=="string"?t.text:null;return e??n}async function L(t,e){let n;for(let i=0;i<e.length;i+=O){let o=e.slice(i,i+O);n=(await l("POST",`/open-apis/docx/v1/documents/${t}/blocks/${t}/children?document_revision_id=-1`,{children:o})).host}return n}function W(t){let e=typeof t=="string"?t.trim():"";if(!e)throw new Error("imagePath is required");if(!N(e)||!E(e).isFile())throw new Error(`imagePath not found (or not a file): ${e}`);if(!/\.(png|jpe?g)$/i.test(e))throw new Error("imagePath must be a .png or .jpg/.jpeg file");let n=D(e);if(n.length>B)throw new Error(`image is ${(n.length/(1024*1024)).toFixed(1)}MB \u2014 max 10MB`);return n}async function X({fileName:t,parentNode:e,bytes:n}){let{token:i,host:o}=await P(),r=new FormData;r.set("file_name",t),r.set("parent_type","docx_image"),r.set("parent_node",e),r.set("size",String(n.length)),r.set("file",new Blob([n]),t);let a=await(await fetch(`${o}/open-apis/drive/v1/medias/upload_all`,{method:"POST",headers:{Authorization:`Bearer ${i}`},body:r})).json();if(a.code!==0)throw new Error(`Lark media upload_all error: ${a.msg||a.code}`);let c=a.data?.file_token;if(!c)throw new Error("Lark media upload_all returned no file_token");return c}var Z="docx",V=50;function v(t){return[{type:"text_run",text_run:{text:String(t??"")}}]}function Q(t){return Array.isArray(t)?t.map(e=>e?.text_run?.text??e?.docs_link?.url??(e?.person?`@${e.person.user_id||""}`:"")).join(""):""}function ee(t){let e=Array.isArray(t?.reply_list?.replies)?t.reply_list.replies:[];return{commentId:t?.comment_id||"",resolved:!!t?.is_solved,replies:e.map(n=>({replyId:n?.reply_id||"",author:n?.user_id||"",text:Q(n?.content?.elements),createTime:n?.create_time||""}))}}function x(t){return typeof t?.fileType=="string"&&t.fileType.trim()?t.fileType.trim():Z}var ce={id:"lark-docs",callsBackend:!0,serverName:"larkdocs",allowedTools:["mcp__larkdocs__*"],requiresIntegration:[y.LARK_DOCS,y.LARK],description:"Lark / Feishu Docs \u2014 read, create, append, and insert images into Lark documents (docx).",envKeys:["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV",...M],promptFragment:'## Lark Docs\nYou can read, create, and append Lark/Feishu documents (docx), and read/post/reply to their comments. This runs on the connected Lark Docs app (the chat app is used when no separate docs app is connected). Each `larkdoc_*` / `larkwiki_*` tool documents its own params and return shape in its tool description \u2014 they are not restated here.\nThese tools return { ok:false, error } on failure \u2014 treat an unavailable Lark connection as "cannot read/deliver to Lark Docs" and continue rather than blocking the task.',resolve(){let t=J();if(!t)return null;let e={};for(let n of this.envKeys)process.env[n]&&(e[n]=process.env[n]);return{type:"stdio",command:"node",args:[t,"../dist/larkDocs.js","larkDocsSkill"],env:e,description:this.description,alwaysLoad:!0}},async handleToolCall(t,e){try{switch(t){case"larkdoc_get":{let n=e?.documentId||e?.url||e?.id,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=await h(i),r="";try{r=(await l("GET",`/open-apis/docx/v1/documents/${o}`)).data?.document?.title||""}catch{}let{data:s,host:a}=await l("GET",`/open-apis/docx/v1/documents/${o}/raw_content?lang=0`),c=String(s?.content||""),d=!1;return c.length>A&&(c=c.slice(0,A),d=!0),JSON.stringify({ok:!0,documentId:o,title:r,url:g(a,o),text:c,...d?{truncated:!0}:{}})}case"larkdoc_create":{let n=typeof e?.title=="string"&&e.title.trim()?e.title.trim():null;if(!n)return JSON.stringify({ok:!1,error:"title is required"});let i=typeof e?.wikiSpaceId=="string"&&e.wikiSpaceId.trim()?e.wikiSpaceId.trim():null;if(i){let c=typeof e?.parentNodeToken=="string"&&e.parentNodeToken.trim()?e.parentNodeToken.trim():null,{data:d,host:p}=await l("POST",`/open-apis/wiki/v2/spaces/${encodeURIComponent(i)}/nodes`,{obj_type:"docx",node_type:"origin",title:n,...c?{parent_node_token:c}:{}}),u=d?.node||{},m=u.obj_token;if(!m)return JSON.stringify({ok:!1,error:"Lark wiki node create returned no obj_token"});let k=I(e);if(k&&k.trim()){let w=S(k);w.length&&await L(m,w)}return JSON.stringify({ok:!0,documentId:m,title:n,wikiSpaceId:i,wikiNodeToken:u.node_token||"",url:u.node_token?Y(p,u.node_token):g(p,m)})}let{data:o,host:r}=await l("POST","/open-apis/docx/v1/documents",{title:n,...e?.folderToken?{folder_token:String(e.folderToken)}:{}}),s=o?.document?.document_id;if(!s)return JSON.stringify({ok:!1,error:"Lark Docs create returned no document_id"});let a=I(e);if(a&&a.trim()){let c=S(a);c.length&&await L(s,c)}return JSON.stringify({ok:!0,documentId:s,title:n,url:g(r,s)})}case"larkdoc_append":{let n=e?.documentId||e?.url||e?.id,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=I(e);if(!o||!o.trim())return JSON.stringify({ok:!1,error:"markdown or text content is required"});let r=await h(i),s=S(o);if(!s.length)return JSON.stringify({ok:!1,error:"no non-empty content to append"});let a=await L(r,s);return JSON.stringify({ok:!0,documentId:r,url:g(a,r)})}case"larkdoc_insert_image":{let n=e?.documentId||e?.url||e?.id,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});if(!e?.imagePath||typeof e.imagePath!="string"||!e.imagePath.trim())return JSON.stringify({ok:!1,error:"imagePath is required"});let o=await h(i),r=e.imagePath.trim(),s=W(r),a=j(r),d=(await l("POST",`/open-apis/docx/v1/documents/${o}/blocks/${o}/children?document_revision_id=-1`,{children:[{block_type:27,image:{}}]})).data?.children?.[0]?.block_id;if(!d)return JSON.stringify({ok:!1,error:"Lark image block create returned no block_id"});let p=await X({fileName:a,parentNode:d,bytes:s}),u={token:p},m=Number(e?.width),k=Number(e?.height);Number.isFinite(m)&&m>0&&(u.width=Math.round(m)),Number.isFinite(k)&&k>0&&(u.height=Math.round(k));let{host:w}=await l("PATCH",`/open-apis/docx/v1/documents/${o}/blocks/${d}?document_revision_id=-1`,{replace_image:u});return JSON.stringify({ok:!0,documentId:o,blockId:d,fileToken:p,url:g(w,o)})}case"larkwiki_list_spaces":{let n=[],i=null;for(let o=0;o<K;o++){let r=new URLSearchParams({page_size:String(F)});i&&r.set("page_token",i);let{data:s}=await l("GET",`/open-apis/wiki/v2/spaces?${r.toString()}`),a=Array.isArray(s?.items)?s.items:[];for(let c of a)n.push({spaceId:String(c?.space_id||""),name:String(c?.name||"")});if(!s?.has_more||!s?.page_token)break;i=s.page_token}return JSON.stringify({ok:!0,count:n.length,spaces:n})}case"larkdoc_list_comments":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=await h(i),r=x(e),s=new URLSearchParams({file_type:r,page_size:String(V)}),{data:a}=await l("GET",`/open-apis/drive/v1/files/${o}/comments?${s.toString()}`),d=(Array.isArray(a?.items)?a.items:[]).map(ee);return JSON.stringify({ok:!0,documentId:o,count:d.length,comments:d})}case"larkdoc_add_comment":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=I({text:e?.text??e?.body});if(!o||!o.trim())return JSON.stringify({ok:!1,error:"text is required"});let r=await h(i),s=x(e),{data:a}=await l("POST",`/open-apis/drive/v1/files/${r}/comments?file_type=${encodeURIComponent(s)}`,{reply_list:{replies:[{content:{elements:v(o)}}]}});return JSON.stringify({ok:!0,documentId:r,commentId:a?.comment_id||""})}case"larkdoc_reply_comment":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=e?.commentId||e?.comment_id;if(!o)return JSON.stringify({ok:!1,error:"commentId is required"});let r=I({text:e?.text??e?.body});if(!r||!r.trim())return JSON.stringify({ok:!1,error:"text is required"});let s=await h(i),a=x(e),{data:c}=await l("POST",`/open-apis/drive/v1/files/${s}/comments/${encodeURIComponent(o)}/replies?file_type=${encodeURIComponent(a)}`,{content:{elements:v(r)}});return JSON.stringify({ok:!0,documentId:s,commentId:String(o),replyId:c?.reply_id||""})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${t}`})}}catch(n){return JSON.stringify({ok:!1,error:n.message})}},tools:[{name:"larkdoc_get",description:"Read a Lark/Feishu document (docx) as plain text, for use as reference context. Accepts a raw doc id OR a full Lark doc URL (a /docx/ or /wiki/ link \u2014 wiki links are resolved to their backing docx). Returns { ok, documentId, title, url, text }. Text is truncated to ~20k chars.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL (/docx/\u2026 or /wiki/\u2026)."}},required:["documentId"]}},{name:"larkdoc_create",description:"Create a new Lark/Feishu document (docx) with a title and optional content (markdown: #/##/### headings, - bullets, 1. ordered; or plain text). Pass wikiSpaceId (from larkwiki_list_spaces) to create the doc INSIDE a wiki space instead of as a standalone doc \u2014 optionally nested under parentNodeToken. Returns { ok, documentId, url } (plus wikiNodeToken when created in a wiki) \u2014 share the url with the user.",input_schema:{type:"object",properties:{title:{type:"string",description:"Document title."},markdown:{type:"string",description:"Document body as markdown (preferred)."},text:{type:"string",description:"Document body as plain text (used when markdown is absent)."},folderToken:{type:"string",description:"Optional Lark drive folder token to create the doc in. Absent = the app root. Ignored when wikiSpaceId is set."},wikiSpaceId:{type:"string",description:"Optional wiki space id (from larkwiki_list_spaces) \u2014 create the doc as a node inside this wiki space."},parentNodeToken:{type:"string",description:"Optional wiki node token to nest the new doc under (only with wikiSpaceId). Absent = the space root."}},required:["title"]}},{name:"larkwiki_list_spaces",description:"List the Lark/Feishu wiki spaces visible to the connected app. Returns { ok, count, spaces:[{ spaceId, name }] }. Use to discover the wikiSpaceId for larkdoc_create.",input_schema:{type:"object",properties:{}}},{name:"larkdoc_append",description:"Append markdown/text content to the END of an existing Lark/Feishu document (docx). Accepts a documentId or a full Lark doc URL. Returns { ok, documentId, url }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},markdown:{type:"string",description:"Content to append, as markdown (preferred)."},text:{type:"string",description:"Content to append, as plain text (used when markdown is absent)."}},required:["documentId"]}},{name:"larkdoc_insert_image",description:"Append a LOCAL image file (png/jpg, max 10MB) to the END of an existing Lark/Feishu document (docx). Accepts a documentId or full doc URL plus a local file path. Optional width/height in pixels. Returns { ok, documentId, blockId, fileToken, url }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},imagePath:{type:"string",description:"Local filesystem path to a .png or .jpg/.jpeg image (max 10MB)."},width:{type:"number",description:"Optional display width in pixels."},height:{type:"number",description:"Optional display height in pixels."}},required:["documentId","imagePath"]}},{name:"larkdoc_list_comments",description:"List the comment threads on a Lark/Feishu document. Accepts a documentId or full doc URL. Returns { ok, comments:[{ commentId, replies:[{ replyId, author, text }] }] }. Use to read the thread you are replying to.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'. Only change for non-docx files (doc/sheet/bitable/file/slides)."}},required:["documentId"]}},{name:"larkdoc_add_comment",description:"Post a NEW top-level comment on a Lark/Feishu document. Accepts a documentId or full doc URL plus text. Returns { ok, documentId, commentId }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},text:{type:"string",description:"The comment body (plain text)."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'."}},required:["documentId","text"]}},{name:"larkdoc_reply_comment",description:"Reply INSIDE an existing comment thread on a Lark/Feishu document. Pass { documentId, commentId, text }. Use to answer a user who @mentioned Zibby in a doc comment (reply in the same commentId). Returns { ok, documentId, commentId, replyId }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},commentId:{type:"string",description:"The comment_id of the thread to reply into (from larkdoc_list_comments or the webhook event)."},text:{type:"string",description:"The reply body (plain text)."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'."}},required:["documentId","commentId","text"]}}]};function de(){_=null}export{M as LARK_DOCS_APP_ENV_KEYS,de as _resetLarkDocsTokenCache,g as docWebUrl,ce as larkDocsSkill,S as markdownToLarkBlocks,f as parseLarkDocRef,Y as wikiWebUrl};
3
+ `)){let o=i.replace(/\s+$/,"");if(!o.trim())continue;let r=/^(#{1,3})\s+(.*)$/.exec(o),s=/^\s*[-*]\s+(.*)$/.exec(o),a=/^\s*\d+[.)]\s+(.*)$/.exec(o),c,d,p;if(r){let u=r[1].length;c=`heading${u}`,d=2+u,p=r[2]}else s?(c="bullet",d=12,p=s[1]):a?(c="ordered",d=13,p=a[1]):(c="text",d=2,p=o);n.push({block_type:d,[c]:{elements:[{text_run:{content:p}}],style:{}}})}return n}function I(t){let e=typeof t?.markdown=="string"?t.markdown:null,n=typeof t?.text=="string"?t.text:null;return e??n}async function x(t,e){let n;for(let i=0;i<e.length;i+=P){let o=e.slice(i,i+P);n=(await l("POST",`/open-apis/docx/v1/documents/${t}/blocks/${t}/children?document_revision_id=-1`,{children:o})).host}return n}function W(t){let e=typeof t=="string"?t.trim():"";if(!e)throw new Error("imagePath is required");if(!E(e)||!C(e).isFile())throw new Error(`imagePath not found (or not a file): ${e}`);if(!/\.(png|jpe?g)$/i.test(e))throw new Error("imagePath must be a .png or .jpg/.jpeg file");let n=U(e);if(n.length>z)throw new Error(`image is ${(n.length/(1024*1024)).toFixed(1)}MB \u2014 max 10MB`);return n}async function X({fileName:t,parentNode:e,bytes:n}){let{token:i,host:o}=await D(),r=new FormData;r.set("file_name",t),r.set("parent_type","docx_image"),r.set("parent_node",e),r.set("size",String(n.length)),r.set("file",new Blob([n]),t);let a=await(await fetch(`${o}/open-apis/drive/v1/medias/upload_all`,{method:"POST",headers:{Authorization:`Bearer ${i}`},body:r})).json();if(a.code!==0)throw new Error(`Lark media upload_all error: ${a.msg||a.code}`);let c=a.data?.file_token;if(!c)throw new Error("Lark media upload_all returned no file_token");return c}var Z="docx",Q=50;function R(t){return[{type:"text_run",text_run:{text:String(t??"")}}]}function ee(t){return Array.isArray(t)?t.map(e=>e?.text_run?.text??e?.docs_link?.url??(e?.person?`@${e.person.user_id||""}`:"")).join(""):""}function te(t){let e=Array.isArray(t?.reply_list?.replies)?t.reply_list.replies:[];return{commentId:t?.comment_id||"",resolved:!!t?.is_solved,replies:e.map(n=>({replyId:n?.reply_id||"",author:n?.user_id||"",text:ee(n?.content?.elements),createTime:n?.create_time||""}))}}function A(t){return typeof t?.fileType=="string"&&t.fileType.trim()?t.fileType.trim():Z}var ue={id:"lark-docs",callsBackend:!0,serverName:"larkdocs",allowedTools:["mcp__larkdocs__*"],requiresIntegration:[h.LARK_DOCS,h.LARK],description:"Lark / Feishu Docs \u2014 read, create, append, and insert images into Lark documents (docx).",envKeys:["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV",...Y],promptFragment:'## Lark Docs\nYou can read, create, and append Lark/Feishu documents (docx), and read/post/reply to their comments. This runs on the connected Lark Docs app (the chat app is used when no separate docs app is connected). Each `larkdoc_*` / `larkwiki_*` tool documents its own params and return shape in its tool description \u2014 they are not restated here.\nThese tools return { ok:false, error } on failure \u2014 treat an unavailable Lark connection as "cannot read/deliver to Lark Docs" and continue rather than blocking the task.',resolve(){let t=G();if(!t)return null;let e={};for(let n of this.envKeys)process.env[n]&&(e[n]=process.env[n]);return{type:"stdio",command:"node",args:[t,"../dist/larkDocs.js","larkDocsSkill"],env:e,description:this.description,alwaysLoad:!0}},async handleToolCall(t,e){try{switch(t){case"larkdoc_get":{let n=e?.documentId||e?.url||e?.id,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=await _(i),r="";try{r=(await l("GET",`/open-apis/docx/v1/documents/${o}`)).data?.document?.title||""}catch{}let{data:s,host:a}=await l("GET",`/open-apis/docx/v1/documents/${o}/raw_content?lang=0`),c=String(s?.content||""),d=!1;return c.length>N&&(c=c.slice(0,N),d=!0),JSON.stringify({ok:!0,documentId:o,title:r,url:g(a,o),text:c,...d?{truncated:!0}:{}})}case"larkdoc_create":{let n=typeof e?.title=="string"&&e.title.trim()?e.title.trim():null;if(!n)return JSON.stringify({ok:!1,error:"title is required"});let i=typeof e?.wikiSpaceId=="string"&&e.wikiSpaceId.trim()?e.wikiSpaceId.trim():null;if(i){let c=typeof e?.parentNodeToken=="string"&&e.parentNodeToken.trim()?e.parentNodeToken.trim():null,{data:d,host:p}=await l("POST",`/open-apis/wiki/v2/spaces/${encodeURIComponent(i)}/nodes`,{obj_type:"docx",node_type:"origin",title:n,...c?{parent_node_token:c}:{}}),u=d?.node||{},m=u.obj_token;if(!m)return JSON.stringify({ok:!1,error:"Lark wiki node create returned no obj_token"});let k=I(e);if(k&&k.trim()){let w=L(k);w.length&&await x(m,w)}return JSON.stringify({ok:!0,documentId:m,title:n,wikiSpaceId:i,wikiNodeToken:u.node_token||"",url:u.node_token?V(p,u.node_token):g(p,m)})}let{data:o,host:r}=await l("POST","/open-apis/docx/v1/documents",{title:n,...e?.folderToken?{folder_token:String(e.folderToken)}:{}}),s=o?.document?.document_id;if(!s)return JSON.stringify({ok:!1,error:"Lark Docs create returned no document_id"});let a=I(e);if(a&&a.trim()){let c=L(a);c.length&&await x(s,c)}return JSON.stringify({ok:!0,documentId:s,title:n,url:g(r,s)})}case"larkdoc_append":{let n=e?.documentId||e?.url||e?.id,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=I(e);if(!o||!o.trim())return JSON.stringify({ok:!1,error:"markdown or text content is required"});let r=await _(i),s=L(o);if(!s.length)return JSON.stringify({ok:!1,error:"no non-empty content to append"});let a=await x(r,s);return JSON.stringify({ok:!0,documentId:r,url:g(a,r)})}case"larkdoc_insert_image":{let n=e?.documentId||e?.url||e?.id,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});if(!e?.imagePath||typeof e.imagePath!="string"||!e.imagePath.trim())return JSON.stringify({ok:!1,error:"imagePath is required"});let o=await _(i),r=e.imagePath.trim(),s=W(r),a=K(r),d=(await l("POST",`/open-apis/docx/v1/documents/${o}/blocks/${o}/children?document_revision_id=-1`,{children:[{block_type:27,image:{}}]})).data?.children?.[0]?.block_id;if(!d)return JSON.stringify({ok:!1,error:"Lark image block create returned no block_id"});let p=await X({fileName:a,parentNode:d,bytes:s}),u={token:p},m=Number(e?.width),k=Number(e?.height);Number.isFinite(m)&&m>0&&(u.width=Math.round(m)),Number.isFinite(k)&&k>0&&(u.height=Math.round(k));let{host:w}=await l("PATCH",`/open-apis/docx/v1/documents/${o}/blocks/${d}?document_revision_id=-1`,{replace_image:u});return JSON.stringify({ok:!0,documentId:o,blockId:d,fileToken:p,url:g(w,o)})}case"larkwiki_list_spaces":{let n=[],i=null;for(let o=0;o<M;o++){let r=new URLSearchParams({page_size:String(q)});i&&r.set("page_token",i);let{data:s}=await l("GET",`/open-apis/wiki/v2/spaces?${r.toString()}`),a=Array.isArray(s?.items)?s.items:[];for(let c of a)n.push({spaceId:String(c?.space_id||""),name:String(c?.name||"")});if(!s?.has_more||!s?.page_token)break;i=s.page_token}return JSON.stringify({ok:!0,count:n.length,spaces:n})}case"larkdoc_list_comments":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=await _(i),r=A(e),s=new URLSearchParams({file_type:r,page_size:String(Q)}),{data:a}=await l("GET",`/open-apis/drive/v1/files/${o}/comments?${s.toString()}`),d=(Array.isArray(a?.items)?a.items:[]).map(te);return JSON.stringify({ok:!0,documentId:o,count:d.length,comments:d})}case"larkdoc_add_comment":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=I({text:e?.text??e?.body});if(!o||!o.trim())return JSON.stringify({ok:!1,error:"text is required"});let r=await _(i),s=A(e),{data:a}=await l("POST",`/open-apis/drive/v1/files/${r}/comments?file_type=${encodeURIComponent(s)}`,{reply_list:{replies:[{content:{elements:R(o)}}]}});return JSON.stringify({ok:!0,documentId:r,commentId:a?.comment_id||""})}case"larkdoc_reply_comment":{let n=e?.documentId||e?.url||e?.id||e?.fileToken,i=f(n);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=e?.commentId||e?.comment_id;if(!o)return JSON.stringify({ok:!1,error:"commentId is required"});let r=I({text:e?.text??e?.body});if(!r||!r.trim())return JSON.stringify({ok:!1,error:"text is required"});let s=await _(i),a=A(e),{data:c}=await l("POST",`/open-apis/drive/v1/files/${s}/comments/${encodeURIComponent(o)}/replies?file_type=${encodeURIComponent(a)}`,{content:{elements:R(r)}});return JSON.stringify({ok:!0,documentId:s,commentId:String(o),replyId:c?.reply_id||""})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${t}`})}}catch(n){return JSON.stringify({ok:!1,error:n.message})}},tools:[{name:"larkdoc_get",description:"Read a Lark/Feishu document (docx) as plain text, for use as reference context. Accepts a raw doc id OR a full Lark doc URL (a /docx/ or /wiki/ link \u2014 wiki links are resolved to their backing docx). Returns { ok, documentId, title, url, text }. Text is truncated to ~20k chars.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL (/docx/\u2026 or /wiki/\u2026)."}},required:["documentId"]}},{name:"larkdoc_create",description:"Create a new Lark/Feishu document (docx) with a title and optional content (markdown: #/##/### headings, - bullets, 1. ordered; or plain text). Pass wikiSpaceId (from larkwiki_list_spaces) to create the doc INSIDE a wiki space instead of as a standalone doc \u2014 optionally nested under parentNodeToken. Returns { ok, documentId, url } (plus wikiNodeToken when created in a wiki) \u2014 share the url with the user.",input_schema:{type:"object",properties:{title:{type:"string",description:"Document title."},markdown:{type:"string",description:"Document body as markdown (preferred)."},text:{type:"string",description:"Document body as plain text (used when markdown is absent)."},folderToken:{type:"string",description:"Optional Lark drive folder token to create the doc in. Absent = the app root. Ignored when wikiSpaceId is set."},wikiSpaceId:{type:"string",description:"Optional wiki space id (from larkwiki_list_spaces) \u2014 create the doc as a node inside this wiki space."},parentNodeToken:{type:"string",description:"Optional wiki node token to nest the new doc under (only with wikiSpaceId). Absent = the space root."}},required:["title"]}},{name:"larkwiki_list_spaces",description:"List the Lark/Feishu wiki spaces visible to the connected app. Returns { ok, count, spaces:[{ spaceId, name }] }. Use to discover the wikiSpaceId for larkdoc_create.",input_schema:{type:"object",properties:{}}},{name:"larkdoc_append",description:"Append markdown/text content to the END of an existing Lark/Feishu document (docx). Accepts a documentId or a full Lark doc URL. Returns { ok, documentId, url }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},markdown:{type:"string",description:"Content to append, as markdown (preferred)."},text:{type:"string",description:"Content to append, as plain text (used when markdown is absent)."}},required:["documentId"]}},{name:"larkdoc_insert_image",description:"Append a LOCAL image file (png/jpg, max 10MB) to the END of an existing Lark/Feishu document (docx). Accepts a documentId or full doc URL plus a local file path. Optional width/height in pixels. Returns { ok, documentId, blockId, fileToken, url }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},imagePath:{type:"string",description:"Local filesystem path to a .png or .jpg/.jpeg image (max 10MB)."},width:{type:"number",description:"Optional display width in pixels."},height:{type:"number",description:"Optional display height in pixels."}},required:["documentId","imagePath"]}},{name:"larkdoc_list_comments",description:"List the comment threads on a Lark/Feishu document. Accepts a documentId or full doc URL. Returns { ok, comments:[{ commentId, replies:[{ replyId, author, text }] }] }. Use to read the thread you are replying to.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'. Only change for non-docx files (doc/sheet/bitable/file/slides)."}},required:["documentId"]}},{name:"larkdoc_add_comment",description:"Post a NEW top-level comment on a Lark/Feishu document. Accepts a documentId or full doc URL plus text. Returns { ok, documentId, commentId }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},text:{type:"string",description:"The comment body (plain text)."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'."}},required:["documentId","text"]}},{name:"larkdoc_reply_comment",description:"Reply INSIDE an existing comment thread on a Lark/Feishu document. Pass { documentId, commentId, text }. Use to answer a user who @mentioned Zibby in a doc comment (reply in the same commentId). Returns { ok, documentId, commentId, replyId }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Lark doc id (docx token) OR a full Lark/Feishu doc URL."},commentId:{type:"string",description:"The comment_id of the thread to reply into (from larkdoc_list_comments or the webhook event)."},text:{type:"string",description:"The reply body (plain text)."},fileType:{type:"string",description:"Drive file type \u2014 defaults to 'docx'."}},required:["documentId","commentId","text"]}}]};function me(){y=null}export{Y as LARK_DOCS_APP_ENV_KEYS,me as _resetLarkDocsTokenCache,g as docWebUrl,ue as larkDocsSkill,L as markdownToLarkBlocks,f as parseLarkDocRef,V as wikiWebUrl};
package/dist/linear.js CHANGED
@@ -1,4 +1,4 @@
1
- import{existsSync as N}from"fs";import{fileURLToPath as k}from"url";import{dirname as L,resolve as w}from"path";var h=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"}),R=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark Docs",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"}});function v(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let a=L(k(import.meta.url)),i=w(a,"..","bin","mcp-skill.mjs");return N(i)?i:null}var S=process.env.LINEAR_API_URL||"https://api.linear.app/graphql";function O(){if(process.env.LINEAR_OAUTH_TOKEN)return`Bearer ${process.env.LINEAR_OAUTH_TOKEN}`;let a=process.env.LINEAR_API_KEY;if(!a)throw new Error("Linear is not connected: set LINEAR_API_KEY (personal API key) or LINEAR_OAUTH_TOKEN.");return a}async function f(a,i={}){let t=await fetch(S,{method:"POST",headers:{Authorization:O(),"Content-Type":"application/json"},body:JSON.stringify({query:a,variables:i})});if(!t.ok){let s=await t.text().catch(()=>"");throw new Error(`Linear API ${t.status}: ${s.slice(0,300)}`)}let e=await t.json().catch(()=>null);if(!e)throw new Error("Linear API returned a non-JSON body");if(Array.isArray(e.errors)&&e.errors.length){let s=e.errors.map(n=>n?.message||String(n)).join("; ");throw new Error(`Linear GraphQL error: ${s.slice(0,300)}`)}return e.data}function _(a){return String(a||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function P(a,i){let t=_(a),e=_(i);if(!t||!e)return 0;if(t===e)return 1;if(t.length===1||e.length===1)return t===e?1:0;let s=d=>{let u=new Map;for(let y=0;y<d.length-1;y++){let g=d.slice(y,y+2);u.set(g,(u.get(g)||0)+1)}return u},n=s(t),r=s(e),o=0,l=0,m=0;for(let d of n.values())l+=d;for(let d of r.values())m+=d;for(let[d,u]of n.entries())o+=Math.min(u,r.get(d)||0);return 2*o/Math.max(1,l+m)}function E(a,i){let t=Array.isArray(a)?a:[];if(!t.length)return{state:null,strategy:"no-states"};let e=_(i);if(!e)return{state:null,strategy:"no-target"};let s=t.find(o=>_(o.name)===e);if(s)return{state:s,strategy:"exact"};let n={backlog:["backlog"],unstarted:["todo","unstarted","open"],started:["inprogress","started","doing","wip"],completed:["done","completed","closed","resolved","fixed"],canceled:["canceled","cancelled","wontfix","won'tfix"],triage:["triage"]};for(let[o,l]of Object.entries(n)){if(!l.some(d=>_(d)===e))continue;let m=t.find(d=>d.type===o);if(m)return{state:m,strategy:"type-alias"}}let r=t.map(o=>({s:o,score:P(i,o.name)})).sort((o,l)=>l.score-o.score);return r[0]&&r[0].score>=.5?{state:r[0].s,strategy:"fuzzy"}:{state:null,strategy:"no-match"}}var T=`
1
+ import{existsSync as N}from"fs";import{fileURLToPath as k}from"url";import{dirname as L,resolve as w}from"path";var h=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"}),R=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"}});function v(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let a=L(k(import.meta.url)),i=w(a,"..","bin","mcp-skill.mjs");return N(i)?i:null}var S=process.env.LINEAR_API_URL||"https://api.linear.app/graphql";function O(){if(process.env.LINEAR_OAUTH_TOKEN)return`Bearer ${process.env.LINEAR_OAUTH_TOKEN}`;let a=process.env.LINEAR_API_KEY;if(!a)throw new Error("Linear is not connected: set LINEAR_API_KEY (personal API key) or LINEAR_OAUTH_TOKEN.");return a}async function f(a,i={}){let t=await fetch(S,{method:"POST",headers:{Authorization:O(),"Content-Type":"application/json"},body:JSON.stringify({query:a,variables:i})});if(!t.ok){let s=await t.text().catch(()=>"");throw new Error(`Linear API ${t.status}: ${s.slice(0,300)}`)}let e=await t.json().catch(()=>null);if(!e)throw new Error("Linear API returned a non-JSON body");if(Array.isArray(e.errors)&&e.errors.length){let s=e.errors.map(n=>n?.message||String(n)).join("; ");throw new Error(`Linear GraphQL error: ${s.slice(0,300)}`)}return e.data}function _(a){return String(a||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function P(a,i){let t=_(a),e=_(i);if(!t||!e)return 0;if(t===e)return 1;if(t.length===1||e.length===1)return t===e?1:0;let s=d=>{let u=new Map;for(let y=0;y<d.length-1;y++){let g=d.slice(y,y+2);u.set(g,(u.get(g)||0)+1)}return u},n=s(t),r=s(e),o=0,l=0,m=0;for(let d of n.values())l+=d;for(let d of r.values())m+=d;for(let[d,u]of n.entries())o+=Math.min(u,r.get(d)||0);return 2*o/Math.max(1,l+m)}function E(a,i){let t=Array.isArray(a)?a:[];if(!t.length)return{state:null,strategy:"no-states"};let e=_(i);if(!e)return{state:null,strategy:"no-target"};let s=t.find(o=>_(o.name)===e);if(s)return{state:s,strategy:"exact"};let n={backlog:["backlog"],unstarted:["todo","unstarted","open"],started:["inprogress","started","doing","wip"],completed:["done","completed","closed","resolved","fixed"],canceled:["canceled","cancelled","wontfix","won'tfix"],triage:["triage"]};for(let[o,l]of Object.entries(n)){if(!l.some(d=>_(d)===e))continue;let m=t.find(d=>d.type===o);if(m)return{state:m,strategy:"type-alias"}}let r=t.map(o=>({s:o,score:P(i,o.name)})).sort((o,l)=>l.score-o.score);return r[0]&&r[0].score>=.5?{state:r[0].s,strategy:"fuzzy"}:{state:null,strategy:"no-match"}}var T=`
2
2
  id
3
3
  identifier
4
4
  number
@@ -12,7 +12,7 @@ import{existsSync as N}from"fs";import{fileURLToPath as k}from"url";import{dirna
12
12
  assignee { id name displayName email }
13
13
  labels { nodes { id name color } }
14
14
  team { id key name }
15
- `,K={id:"linear",serverName:"linear",allowedTools:["mcp__linear__*"],requiresIntegration:h.LINEAR,envKeys:["LINEAR_API_KEY","LINEAR_OAUTH_TOKEN"],description:"Linear \u2014 issues, comments, workflow states (GraphQL API key)",promptFragment:`## Linear
15
+ `,C={id:"linear",serverName:"linear",allowedTools:["mcp__linear__*"],requiresIntegration:h.LINEAR,envKeys:["LINEAR_API_KEY","LINEAR_OAUTH_TOKEN"],description:"Linear \u2014 issues, comments, workflow states (GraphQL API key)",promptFragment:`## Linear
16
16
  You have direct access to the user's Linear workspace (GraphQL API). Tools:
17
17
 
18
18
  ### Discovery
@@ -111,4 +111,4 @@ You have direct access to the user's Linear workspace (GraphQL API). Tools:
111
111
  query IssueById($id: String!) {
112
112
  issue(id: $id) { ${i} }
113
113
  }
114
- `,{id:t}))?.issue||null}export{f as linearFetch,K as linearSkill};
114
+ `,{id:t}))?.issue||null}export{f as linearFetch,C as linearSkill};
@@ -1 +1 @@
1
- import{resolveIntegrationToken as w}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"}),P=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark Docs",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 S=Object.freeze({id:"openai_billing",callsBackend:!0,requiresIntegration:f.OPENAI_BILLING,description:"OpenAI organization billing/usage admin API (paste sk-admin-... key)"}),j=Object.freeze({id:"anthropic_billing",callsBackend:!0,requiresIntegration:f.ANTHROPIC_BILLING,description:"Anthropic organization cost/usage admin API (paste sk-ant-admin-... key)"}),L=Object.freeze({id:"cursor_admin",callsBackend:!0,requiresIntegration:f.CURSOR_ADMIN,description:"Cursor Team/Enterprise admin API (paste admin key)"});function I(t){return Math.floor(t/1e3)}function _(t){return new Date(t).toISOString().slice(0,10)}function b(t){return new Date(t).toISOString()}async function k(t){let a=await w(t);if(!a?.token)throw new Error(`${t} token resolver returned no token`);return a.token}async function v({startMs:t,endMs:a,groupBy:o=["project_id","line_item"]}){let i=await k("openai_billing"),e=[],n=0,m=o.map(d=>`group_by[]=${encodeURIComponent(d)}`).join("&"),u=null;for(let d=0;d<50;d++){let r=`https://api.openai.com/v1/organization/costs?${[`start_time=${I(t)}`,`end_time=${I(a)}`,"bucket_width=1d","limit=180",m,u?`page=${encodeURIComponent(u)}`:""].filter(Boolean).join("&")}`,c=await fetch(r,{headers:{Authorization:`Bearer ${i}`}});if(!c.ok){let l=await c.text().catch(()=>"");throw new Error(`OpenAI costs API ${c.status}: ${l.slice(0,200)}`)}let p=await c.json();for(let l of p.data||[]){n+=1;let h=_((l.start_time||0)*1e3);for(let s of l.results||[])e.push({provider:"openai",day:h,costUsd:Number(s.amount?.value??0),projectId:s.project_id||void 0,apiKeyId:s.api_key_id||void 0,model:s.line_item||void 0})}if(!p.has_more||!p.next_page)break;u=p.next_page}return{ok:!0,items:e,rawBuckets:n}}async function R(){let t=await k("openai_billing"),o=await fetch("https://api.openai.com/v1/organization/projects?limit=100",{headers:{Authorization:`Bearer ${t}`}});if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`OpenAI projects API ${o.status}: ${n.slice(0,200)}`)}let i=await o.json(),e=new Map;for(let n of i.data||[])e.set(n.id,n.name);return e}async function y({startMs:t,endMs:a,groupBy:o=["workspace_id"]}){let i=await k("anthropic_billing"),e=[],n=0,m=o.map(d=>`group_by[]=${encodeURIComponent(d)}`).join("&"),u=null;for(let d=0;d<50;d++){let r=`https://api.anthropic.com/v1/organizations/cost_report?${[`starting_at=${encodeURIComponent(b(t))}`,`ending_at=${encodeURIComponent(b(a))}`,"bucket=1d","limit=100",m,u?`page=${encodeURIComponent(u)}`:""].filter(Boolean).join("&")}`,c=await fetch(r,{headers:{"x-api-key":i,"anthropic-version":"2023-06-01"}});if(!c.ok){let l=await c.text().catch(()=>"");throw new Error(`Anthropic cost_report ${c.status}: ${l.slice(0,200)}`)}let p=await c.json();for(let l of p.data||[]){n+=1;let h=(l.starting_at||"").slice(0,10);for(let s of l.results||[])e.push({provider:"anthropic",day:h,costUsd:Number(s.amount??s.cost??0),workspaceId:s.workspace_id||void 0,apiKeyId:s.api_key_id||void 0,model:s.model||void 0,tokensIn:s.uncached_input_tokens!=null?Number(s.uncached_input_tokens):void 0,tokensOut:s.output_tokens!=null?Number(s.output_tokens):void 0,cachedTokens:s.cached_input_tokens!=null?Number(s.cached_input_tokens):void 0})}if(!p.has_more||!p.next_page)break;u=p.next_page}return{ok:!0,items:e,rawBuckets:n}}async function $(){let t=await k("anthropic_billing"),o=await fetch("https://api.anthropic.com/v1/organizations/workspaces?limit=100",{headers:{"x-api-key":t,"anthropic-version":"2023-06-01"}});if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`Anthropic workspaces ${o.status}: ${n.slice(0,200)}`)}let i=await o.json(),e=new Map;for(let n of i.data||[])e.set(n.id,n.name);return e}async function A({startMs:t,endMs:a}){let o=await k("cursor_admin"),i=_(t),e=_(a),n=`https://api.cursor.com/teams/daily-usage-data?startDate=${i}&endDate=${e}`,m=await fetch(n,{headers:{Authorization:`Bearer ${o}`}});if(!m.ok){let r=await m.text().catch(()=>"");throw new Error(`Cursor daily-usage ${m.status}: ${r.slice(0,200)}`)}let u=await m.json(),d=[],g=0;for(let r of u.data||[]){g+=1;let c=r.date;for(let p of r.userMetrics||[]){for(let l of p.modelUsage||[]){let h=Number(l.acceptedLines??0),s=Number(l.suggestedLines??0);d.push({provider:"cursor",day:c,costUsd:Number(l.totalCents??0)/100,userEmail:p.email,model:l.model,requestCount:Number(l.requestCount??0),acceptanceRate:s>0?h/s:void 0})}(!p.modelUsage||p.modelUsage.length===0)&&d.push({provider:"cursor",day:c,costUsd:Number(p.totalCents??0)/100,userEmail:p.email})}}return{ok:!0,items:d,rawBuckets:g}}async function B({startMs:t,endMs:a}){let[o,i,e]=await Promise.allSettled([v({startMs:t,endMs:a}),y({startMs:t,endMs:a}),A({startMs:t,endMs:a})]),n=r=>r.status==="fulfilled"?r.value:{ok:!1,error:r.reason?.message||String(r.reason),items:[]},m=n(o),u=n(i),d=n(e),g=[{provider:"openai",totalUsd:m.items.reduce((r,c)=>r+(c.costUsd||0),0)},{provider:"anthropic",totalUsd:u.items.reduce((r,c)=>r+(c.costUsd||0),0)},{provider:"cursor",totalUsd:d.items.reduce((r,c)=>r+(c.costUsd||0),0)}];return{openai:m,anthropic:u,cursor:d,totals:g}}function E(t,a){let o=new Map;for(let i of t){let e=a(i);if(!e)continue;let n=o.get(e)||{key:e,totalUsd:0,count:0};n.totalUsd+=i.costUsd||0,n.count+=1,o.set(e,n)}return[...o.values()].sort((i,e)=>e.totalUsd-i.totalUsd)}function x(t){if(!t.length)return{mean:0,stddev:0};let a=t.reduce((i,e)=>i+e,0)/t.length,o=t.reduce((i,e)=>i+(e-a)**2,0)/t.length;return{mean:a,stddev:Math.sqrt(o)}}export{j as anthropicBillingSkill,L as cursorAdminSkill,B as fetchAllProviders,y as fetchAnthropicCosts,$ as fetchAnthropicWorkspaces,A as fetchCursorSpend,v as fetchOpenAICosts,R as fetchOpenAIProjects,E as groupByKey,x as meanStddev,S as openaiBillingSkill};
1
+ import{resolveIntegrationToken as w}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"}),P=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 S=Object.freeze({id:"openai_billing",callsBackend:!0,requiresIntegration:f.OPENAI_BILLING,description:"OpenAI organization billing/usage admin API (paste sk-admin-... key)"}),j=Object.freeze({id:"anthropic_billing",callsBackend:!0,requiresIntegration:f.ANTHROPIC_BILLING,description:"Anthropic organization cost/usage admin API (paste sk-ant-admin-... key)"}),L=Object.freeze({id:"cursor_admin",callsBackend:!0,requiresIntegration:f.CURSOR_ADMIN,description:"Cursor Team/Enterprise admin API (paste admin key)"});function I(t){return Math.floor(t/1e3)}function _(t){return new Date(t).toISOString().slice(0,10)}function b(t){return new Date(t).toISOString()}async function k(t){let a=await w(t);if(!a?.token)throw new Error(`${t} token resolver returned no token`);return a.token}async function v({startMs:t,endMs:a,groupBy:o=["project_id","line_item"]}){let i=await k("openai_billing"),e=[],n=0,m=o.map(d=>`group_by[]=${encodeURIComponent(d)}`).join("&"),u=null;for(let d=0;d<50;d++){let r=`https://api.openai.com/v1/organization/costs?${[`start_time=${I(t)}`,`end_time=${I(a)}`,"bucket_width=1d","limit=180",m,u?`page=${encodeURIComponent(u)}`:""].filter(Boolean).join("&")}`,c=await fetch(r,{headers:{Authorization:`Bearer ${i}`}});if(!c.ok){let l=await c.text().catch(()=>"");throw new Error(`OpenAI costs API ${c.status}: ${l.slice(0,200)}`)}let p=await c.json();for(let l of p.data||[]){n+=1;let h=_((l.start_time||0)*1e3);for(let s of l.results||[])e.push({provider:"openai",day:h,costUsd:Number(s.amount?.value??0),projectId:s.project_id||void 0,apiKeyId:s.api_key_id||void 0,model:s.line_item||void 0})}if(!p.has_more||!p.next_page)break;u=p.next_page}return{ok:!0,items:e,rawBuckets:n}}async function R(){let t=await k("openai_billing"),o=await fetch("https://api.openai.com/v1/organization/projects?limit=100",{headers:{Authorization:`Bearer ${t}`}});if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`OpenAI projects API ${o.status}: ${n.slice(0,200)}`)}let i=await o.json(),e=new Map;for(let n of i.data||[])e.set(n.id,n.name);return e}async function y({startMs:t,endMs:a,groupBy:o=["workspace_id"]}){let i=await k("anthropic_billing"),e=[],n=0,m=o.map(d=>`group_by[]=${encodeURIComponent(d)}`).join("&"),u=null;for(let d=0;d<50;d++){let r=`https://api.anthropic.com/v1/organizations/cost_report?${[`starting_at=${encodeURIComponent(b(t))}`,`ending_at=${encodeURIComponent(b(a))}`,"bucket=1d","limit=100",m,u?`page=${encodeURIComponent(u)}`:""].filter(Boolean).join("&")}`,c=await fetch(r,{headers:{"x-api-key":i,"anthropic-version":"2023-06-01"}});if(!c.ok){let l=await c.text().catch(()=>"");throw new Error(`Anthropic cost_report ${c.status}: ${l.slice(0,200)}`)}let p=await c.json();for(let l of p.data||[]){n+=1;let h=(l.starting_at||"").slice(0,10);for(let s of l.results||[])e.push({provider:"anthropic",day:h,costUsd:Number(s.amount??s.cost??0),workspaceId:s.workspace_id||void 0,apiKeyId:s.api_key_id||void 0,model:s.model||void 0,tokensIn:s.uncached_input_tokens!=null?Number(s.uncached_input_tokens):void 0,tokensOut:s.output_tokens!=null?Number(s.output_tokens):void 0,cachedTokens:s.cached_input_tokens!=null?Number(s.cached_input_tokens):void 0})}if(!p.has_more||!p.next_page)break;u=p.next_page}return{ok:!0,items:e,rawBuckets:n}}async function $(){let t=await k("anthropic_billing"),o=await fetch("https://api.anthropic.com/v1/organizations/workspaces?limit=100",{headers:{"x-api-key":t,"anthropic-version":"2023-06-01"}});if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`Anthropic workspaces ${o.status}: ${n.slice(0,200)}`)}let i=await o.json(),e=new Map;for(let n of i.data||[])e.set(n.id,n.name);return e}async function A({startMs:t,endMs:a}){let o=await k("cursor_admin"),i=_(t),e=_(a),n=`https://api.cursor.com/teams/daily-usage-data?startDate=${i}&endDate=${e}`,m=await fetch(n,{headers:{Authorization:`Bearer ${o}`}});if(!m.ok){let r=await m.text().catch(()=>"");throw new Error(`Cursor daily-usage ${m.status}: ${r.slice(0,200)}`)}let u=await m.json(),d=[],g=0;for(let r of u.data||[]){g+=1;let c=r.date;for(let p of r.userMetrics||[]){for(let l of p.modelUsage||[]){let h=Number(l.acceptedLines??0),s=Number(l.suggestedLines??0);d.push({provider:"cursor",day:c,costUsd:Number(l.totalCents??0)/100,userEmail:p.email,model:l.model,requestCount:Number(l.requestCount??0),acceptanceRate:s>0?h/s:void 0})}(!p.modelUsage||p.modelUsage.length===0)&&d.push({provider:"cursor",day:c,costUsd:Number(p.totalCents??0)/100,userEmail:p.email})}}return{ok:!0,items:d,rawBuckets:g}}async function B({startMs:t,endMs:a}){let[o,i,e]=await Promise.allSettled([v({startMs:t,endMs:a}),y({startMs:t,endMs:a}),A({startMs:t,endMs:a})]),n=r=>r.status==="fulfilled"?r.value:{ok:!1,error:r.reason?.message||String(r.reason),items:[]},m=n(o),u=n(i),d=n(e),g=[{provider:"openai",totalUsd:m.items.reduce((r,c)=>r+(c.costUsd||0),0)},{provider:"anthropic",totalUsd:u.items.reduce((r,c)=>r+(c.costUsd||0),0)},{provider:"cursor",totalUsd:d.items.reduce((r,c)=>r+(c.costUsd||0),0)}];return{openai:m,anthropic:u,cursor:d,totals:g}}function E(t,a){let o=new Map;for(let i of t){let e=a(i);if(!e)continue;let n=o.get(e)||{key:e,totalUsd:0,count:0};n.totalUsd+=i.costUsd||0,n.count+=1,o.set(e,n)}return[...o.values()].sort((i,e)=>e.totalUsd-i.totalUsd)}function C(t){if(!t.length)return{mean:0,stddev:0};let a=t.reduce((i,e)=>i+e,0)/t.length,o=t.reduce((i,e)=>i+(e-a)**2,0)/t.length;return{mean:a,stddev:Math.sqrt(o)}}export{j as anthropicBillingSkill,L as cursorAdminSkill,B as fetchAllProviders,y as fetchAnthropicCosts,$ as fetchAnthropicWorkspaces,A as fetchCursorSpend,v as fetchOpenAICosts,R as fetchOpenAIProjects,E as groupByKey,C as meanStddev,S as openaiBillingSkill};