@zibby/skills 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/larkDocs.js CHANGED
@@ -1,13 +1,3 @@
1
- import{existsSync as v,statSync as O,readFileSync as R}from"fs";import{fileURLToPath as P}from"url";import{basename as E,dirname as U,resolve as $}from"path";import{resolveIntegrationToken as D}from"@zibby/core/backend-client.js";var S=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),Y=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},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"},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 n=U(P(import.meta.url)),e=$(n,"..","bin","mcp-skill.mjs");return v(e)?e:null}var b=2e4,T=50,C=50,J=20,B=10*1024*1024,F=6e3*1e3,_=null;async function A(){let{appId:n,appSecret:e,host:t}=await D("lark");if(_&&_.appId===n&&_.expiresAt>Date.now())return{token:_.token,host:t};let o=await(await fetch(`${t}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:n,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()+F,appId:n},{token:o.tenant_access_token,host:t}}async function p(n,e,t){let{token:i,host:o}=await A(),r={method:n,headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json; charset=utf-8"}};n!=="GET"&&t!==void 0&&(r.body=JSON.stringify(t));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 y(n,e){return String(n||"").includes("feishu")?`https://feishu.cn/docx/${e}`:`https://www.larksuite.com/docx/${e}`}function q(n,e){return String(n||"").includes("feishu")?`https://feishu.cn/wiki/${e}`:`https://www.larksuite.com/wiki/${e}`}function h(n){if(!n||typeof n!="string")return null;let e=n.trim();if(!e)return null;let t=e.match(/\/(docx|wiki)\/([A-Za-z0-9]+)/);return t?{type:t[1],token:t[2]}:/^[A-Za-z0-9]{10,}$/.test(e)?{type:"docx",token:e}:null}async function f(n){let e=typeof n=="string"?h(n):n;if(!e)throw new Error("A valid Lark doc id or URL is required");if(e.type==="docx")return e.token;let{data:t}=await p("GET",`/open-apis/wiki/v2/spaces/get_node?token=${encodeURIComponent(e.token)}`),i=t?.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 I(n){let e=String(n??"").replace(/\r\n/g,`
1
+ import{existsSync as v,statSync as O,readFileSync as P}from"fs";import{fileURLToPath as R}from"url";import{basename as E,dirname as $,resolve as U}from"path";import{resolveIntegrationToken as D}from"@zibby/core/backend-client.js";var S=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),W=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},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"},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 n=$(R(import.meta.url)),e=U(n,"..","bin","mcp-skill.mjs");return v(e)?e:null}var b=2e4,T=50,C=50,J=20,F=10*1024*1024,B=6e3*1e3,_=null;async function A(){let{appId:n,appSecret:e,host:t}=await D("lark");if(_&&_.appId===n&&_.expiresAt>Date.now())return{token:_.token,host:t};let o=await(await fetch(`${t}/open-apis/auth/v3/tenant_access_token/internal`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:n,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()+B,appId:n},{token:o.tenant_access_token,host:t}}async function p(n,e,t){let{token:i,host:o}=await A(),r={method:n,headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json; charset=utf-8"}};n!=="GET"&&t!==void 0&&(r.body=JSON.stringify(t));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 y(n,e){return String(n||"").includes("feishu")?`https://feishu.cn/docx/${e}`:`https://www.larksuite.com/docx/${e}`}function q(n,e){return String(n||"").includes("feishu")?`https://feishu.cn/wiki/${e}`:`https://www.larksuite.com/wiki/${e}`}function f(n){if(!n||typeof n!="string")return null;let e=n.trim();if(!e)return null;let t=e.match(/\/(docx|wiki)\/([A-Za-z0-9]+)/);return t?{type:t[1],token:t[2]}:/^[A-Za-z0-9]{10,}$/.test(e)?{type:"docx",token:e}:null}async function h(n){let e=typeof n=="string"?f(n):n;if(!e)throw new Error("A valid Lark doc id or URL is required");if(e.type==="docx")return e.token;let{data:t}=await p("GET",`/open-apis/wiki/v2/spaces/get_node?token=${encodeURIComponent(e.token)}`),i=t?.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 I(n){let e=String(n??"").replace(/\r\n/g,`
2
2
  `),t=[];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,l;if(r){let u=r[1].length;c=`heading${u}`,d=2+u,l=r[2]}else s?(c="bullet",d=12,l=s[1]):a?(c="ordered",d=13,l=a[1]):(c="text",d=2,l=o);t.push({block_type:d,[c]:{elements:[{text_run:{content:l}}],style:{}}})}return t}function g(n){let e=typeof n?.markdown=="string"?n.markdown:null,t=typeof n?.text=="string"?n.text:null;return e??t}async function x(n,e){let t;for(let i=0;i<e.length;i+=T){let o=e.slice(i,i+T);t=(await p("POST",`/open-apis/docx/v1/documents/${n}/blocks/${n}/children?document_revision_id=-1`,{children:o})).host}return t}function G(n){let e=typeof n=="string"?n.trim():"";if(!e)throw new Error("imagePath is required");if(!v(e)||!O(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 t=R(e);if(t.length>B)throw new Error(`image is ${(t.length/(1024*1024)).toFixed(1)}MB \u2014 max 10MB`);return t}async function M({fileName:n,parentNode:e,bytes:t}){let{token:i,host:o}=await A(),r=new FormData;r.set("file_name",n),r.set("parent_type","docx_image"),r.set("parent_node",e),r.set("size",String(t.length)),r.set("file",new Blob([t]),n);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 K="docx",H=50;function N(n){return[{type:"text_run",text_run:{text:String(n??"")}}]}function z(n){return Array.isArray(n)?n.map(e=>e?.text_run?.text??e?.docs_link?.url??(e?.person?`@${e.person.user_id||""}`:"")).join(""):""}function W(n){let e=Array.isArray(n?.reply_list?.replies)?n.reply_list.replies:[];return{commentId:n?.comment_id||"",resolved:!!n?.is_solved,replies:e.map(t=>({replyId:t?.reply_id||"",author:t?.user_id||"",text:z(t?.content?.elements),createTime:t?.create_time||""}))}}function L(n){return typeof n?.fileType=="string"&&n.fileType.trim()?n.fileType.trim():K}var ne={id:"lark-docs",callsBackend:!0,serverName:"larkdocs",allowedTools:["mcp__larkdocs__*"],requiresIntegration:S.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"],promptFragment:`## Lark Docs
4
- You can read, create, and append Lark/Feishu documents (docx). This reuses the same connected Lark app as messaging.
5
- - larkdoc_get: pass a Lark doc id OR a full doc URL (a /docx/ or /wiki/ link); returns { ok, documentId, title, url, text } where text is the doc as plain text (truncated to ~20k chars). Use it as reference context.
6
- - larkdoc_create: create a new doc from a title + markdown/text (#/##/### headings, - bullets, 1. ordered supported); returns { ok, documentId, url }. Share the url. To create the doc INSIDE a wiki space, first call larkwiki_list_spaces to find the space id, then pass wikiSpaceId (+ optional parentNodeToken to nest under a wiki node) \u2014 the doc is created as a wiki node and the returned url is the wiki link.
7
- - larkwiki_list_spaces: list the wiki spaces the connected Lark app can see; returns { ok, spaces:[{ spaceId, name }] }. Use it to discover the wikiSpaceId before larkdoc_create.
8
- - larkdoc_append: append markdown/text to the end of an existing doc (pass the documentId or doc URL).
9
- - larkdoc_insert_image: append a LOCAL image file (png/jpg, \u226410MB) to the end of a doc \u2014 pass { documentId, imagePath, width?, height? } (width/height in px, optional). Returns { ok, documentId, blockId, url }. Use this to deliver a rendered chart/report image into the doc.
10
- - larkdoc_list_comments: list the comment threads on a doc (pass the documentId or doc URL); returns { ok, comments:[{ commentId, replies:[{ replyId, author, text }] }] }. Use to read the thread you are replying to.
11
- - larkdoc_reply_comment: reply INSIDE an existing comment thread \u2014 pass { documentId, commentId, text }. Use this to answer a user who @mentioned Zibby in a doc comment (reply in the SAME commentId).
12
- - larkdoc_add_comment: post a NEW top-level comment on a doc \u2014 pass { documentId, text }.
13
- These 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 n=j();if(!n)return null;let e={};for(let t of this.envKeys)process.env[t]&&(e[t]=process.env[t]);return{type:"stdio",command:"node",args:[n,"../dist/larkDocs.js","larkDocsSkill"],env:e,description:this.description,alwaysLoad:!0}},async handleToolCall(n,e){try{switch(n){case"larkdoc_get":{let t=e?.documentId||e?.url||e?.id,i=h(t);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=await f(i),r="";try{r=(await p("GET",`/open-apis/docx/v1/documents/${o}`)).data?.document?.title||""}catch{}let{data:s,host:a}=await p("GET",`/open-apis/docx/v1/documents/${o}/raw_content?lang=0`),c=String(s?.content||""),d=!1;return c.length>b&&(c=c.slice(0,b),d=!0),JSON.stringify({ok:!0,documentId:o,title:r,url:y(a,o),text:c,...d?{truncated:!0}:{}})}case"larkdoc_create":{let t=typeof e?.title=="string"&&e.title.trim()?e.title.trim():null;if(!t)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:l}=await p("POST",`/open-apis/wiki/v2/spaces/${encodeURIComponent(i)}/nodes`,{obj_type:"docx",node_type:"origin",title:t,...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=g(e);if(k&&k.trim()){let w=I(k);w.length&&await x(m,w)}return JSON.stringify({ok:!0,documentId:m,title:t,wikiSpaceId:i,wikiNodeToken:u.node_token||"",url:u.node_token?q(l,u.node_token):y(l,m)})}let{data:o,host:r}=await p("POST","/open-apis/docx/v1/documents",{title:t,...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=g(e);if(a&&a.trim()){let c=I(a);c.length&&await x(s,c)}return JSON.stringify({ok:!0,documentId:s,title:t,url:y(r,s)})}case"larkdoc_append":{let t=e?.documentId||e?.url||e?.id,i=h(t);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=g(e);if(!o||!o.trim())return JSON.stringify({ok:!1,error:"markdown or text content is required"});let r=await f(i),s=I(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:y(a,r)})}case"larkdoc_insert_image":{let t=e?.documentId||e?.url||e?.id,i=h(t);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 f(i),r=e.imagePath.trim(),s=G(r),a=E(r),d=(await p("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 l=await M({fileName:a,parentNode:d,bytes:s}),u={token:l},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 p("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:l,url:y(w,o)})}case"larkwiki_list_spaces":{let t=[],i=null;for(let o=0;o<J;o++){let r=new URLSearchParams({page_size:String(C)});i&&r.set("page_token",i);let{data:s}=await p("GET",`/open-apis/wiki/v2/spaces?${r.toString()}`),a=Array.isArray(s?.items)?s.items:[];for(let c of a)t.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:t.length,spaces:t})}case"larkdoc_list_comments":{let t=e?.documentId||e?.url||e?.id||e?.fileToken,i=h(t);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=await f(i),r=L(e),s=new URLSearchParams({file_type:r,page_size:String(H)}),{data:a}=await p("GET",`/open-apis/drive/v1/files/${o}/comments?${s.toString()}`),d=(Array.isArray(a?.items)?a.items:[]).map(W);return JSON.stringify({ok:!0,documentId:o,count:d.length,comments:d})}case"larkdoc_add_comment":{let t=e?.documentId||e?.url||e?.id||e?.fileToken,i=h(t);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=g({text:e?.text??e?.body});if(!o||!o.trim())return JSON.stringify({ok:!1,error:"text is required"});let r=await f(i),s=L(e),{data:a}=await p("POST",`/open-apis/drive/v1/files/${r}/comments?file_type=${encodeURIComponent(s)}`,{reply_list:{replies:[{content:{elements:N(o)}}]}});return JSON.stringify({ok:!0,documentId:r,commentId:a?.comment_id||""})}case"larkdoc_reply_comment":{let t=e?.documentId||e?.url||e?.id||e?.fileToken,i=h(t);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=g({text:e?.text??e?.body});if(!r||!r.trim())return JSON.stringify({ok:!1,error:"text is required"});let s=await f(i),a=L(e),{data:c}=await p("POST",`/open-apis/drive/v1/files/${s}/comments/${encodeURIComponent(o)}/replies?file_type=${encodeURIComponent(a)}`,{content:{elements:N(r)}});return JSON.stringify({ok:!0,documentId:s,commentId:String(o),replyId:c?.reply_id||""})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${n}`})}}catch(t){return JSON.stringify({ok:!1,error:t.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 oe(){_=null}export{oe as _resetLarkDocsTokenCache,y as docWebUrl,ne as larkDocsSkill,I as markdownToLarkBlocks,h as parseLarkDocRef,q 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,l;if(r){let u=r[1].length;c=`heading${u}`,d=2+u,l=r[2]}else s?(c="bullet",d=12,l=s[1]):a?(c="ordered",d=13,l=a[1]):(c="text",d=2,l=o);t.push({block_type:d,[c]:{elements:[{text_run:{content:l}}],style:{}}})}return t}function g(n){let e=typeof n?.markdown=="string"?n.markdown:null,t=typeof n?.text=="string"?n.text:null;return e??t}async function L(n,e){let t;for(let i=0;i<e.length;i+=T){let o=e.slice(i,i+T);t=(await p("POST",`/open-apis/docx/v1/documents/${n}/blocks/${n}/children?document_revision_id=-1`,{children:o})).host}return t}function G(n){let e=typeof n=="string"?n.trim():"";if(!e)throw new Error("imagePath is required");if(!v(e)||!O(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 t=P(e);if(t.length>F)throw new Error(`image is ${(t.length/(1024*1024)).toFixed(1)}MB \u2014 max 10MB`);return t}async function M({fileName:n,parentNode:e,bytes:t}){let{token:i,host:o}=await A(),r=new FormData;r.set("file_name",n),r.set("parent_type","docx_image"),r.set("parent_node",e),r.set("size",String(t.length)),r.set("file",new Blob([t]),n);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 K="docx",H=50;function N(n){return[{type:"text_run",text_run:{text:String(n??"")}}]}function z(n){return Array.isArray(n)?n.map(e=>e?.text_run?.text??e?.docs_link?.url??(e?.person?`@${e.person.user_id||""}`:"")).join(""):""}function Y(n){let e=Array.isArray(n?.reply_list?.replies)?n.reply_list.replies:[];return{commentId:n?.comment_id||"",resolved:!!n?.is_solved,replies:e.map(t=>({replyId:t?.reply_id||"",author:t?.user_id||"",text:z(t?.content?.elements),createTime:t?.create_time||""}))}}function x(n){return typeof n?.fileType=="string"&&n.fileType.trim()?n.fileType.trim():K}var ne={id:"lark-docs",callsBackend:!0,serverName:"larkdocs",allowedTools:["mcp__larkdocs__*"],requiresIntegration:S.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"],promptFragment:'## Lark Docs\nYou can read, create, and append Lark/Feishu documents (docx), and read/post/reply to their comments. This reuses the same connected Lark app as messaging. 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 n=j();if(!n)return null;let e={};for(let t of this.envKeys)process.env[t]&&(e[t]=process.env[t]);return{type:"stdio",command:"node",args:[n,"../dist/larkDocs.js","larkDocsSkill"],env:e,description:this.description,alwaysLoad:!0}},async handleToolCall(n,e){try{switch(n){case"larkdoc_get":{let t=e?.documentId||e?.url||e?.id,i=f(t);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 p("GET",`/open-apis/docx/v1/documents/${o}`)).data?.document?.title||""}catch{}let{data:s,host:a}=await p("GET",`/open-apis/docx/v1/documents/${o}/raw_content?lang=0`),c=String(s?.content||""),d=!1;return c.length>b&&(c=c.slice(0,b),d=!0),JSON.stringify({ok:!0,documentId:o,title:r,url:y(a,o),text:c,...d?{truncated:!0}:{}})}case"larkdoc_create":{let t=typeof e?.title=="string"&&e.title.trim()?e.title.trim():null;if(!t)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:l}=await p("POST",`/open-apis/wiki/v2/spaces/${encodeURIComponent(i)}/nodes`,{obj_type:"docx",node_type:"origin",title:t,...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=g(e);if(k&&k.trim()){let w=I(k);w.length&&await L(m,w)}return JSON.stringify({ok:!0,documentId:m,title:t,wikiSpaceId:i,wikiNodeToken:u.node_token||"",url:u.node_token?q(l,u.node_token):y(l,m)})}let{data:o,host:r}=await p("POST","/open-apis/docx/v1/documents",{title:t,...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=g(e);if(a&&a.trim()){let c=I(a);c.length&&await L(s,c)}return JSON.stringify({ok:!0,documentId:s,title:t,url:y(r,s)})}case"larkdoc_append":{let t=e?.documentId||e?.url||e?.id,i=f(t);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=g(e);if(!o||!o.trim())return JSON.stringify({ok:!1,error:"markdown or text content is required"});let r=await h(i),s=I(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:y(a,r)})}case"larkdoc_insert_image":{let t=e?.documentId||e?.url||e?.id,i=f(t);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=G(r),a=E(r),d=(await p("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 l=await M({fileName:a,parentNode:d,bytes:s}),u={token:l},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 p("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:l,url:y(w,o)})}case"larkwiki_list_spaces":{let t=[],i=null;for(let o=0;o<J;o++){let r=new URLSearchParams({page_size:String(C)});i&&r.set("page_token",i);let{data:s}=await p("GET",`/open-apis/wiki/v2/spaces?${r.toString()}`),a=Array.isArray(s?.items)?s.items:[];for(let c of a)t.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:t.length,spaces:t})}case"larkdoc_list_comments":{let t=e?.documentId||e?.url||e?.id||e?.fileToken,i=f(t);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(H)}),{data:a}=await p("GET",`/open-apis/drive/v1/files/${o}/comments?${s.toString()}`),d=(Array.isArray(a?.items)?a.items:[]).map(Y);return JSON.stringify({ok:!0,documentId:o,count:d.length,comments:d})}case"larkdoc_add_comment":{let t=e?.documentId||e?.url||e?.id||e?.fileToken,i=f(t);if(!i)return JSON.stringify({ok:!1,error:"A valid Lark doc id or URL is required"});let o=g({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 p("POST",`/open-apis/drive/v1/files/${r}/comments?file_type=${encodeURIComponent(s)}`,{reply_list:{replies:[{content:{elements:N(o)}}]}});return JSON.stringify({ok:!0,documentId:r,commentId:a?.comment_id||""})}case"larkdoc_reply_comment":{let t=e?.documentId||e?.url||e?.id||e?.fileToken,i=f(t);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=g({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 p("POST",`/open-apis/drive/v1/files/${s}/comments/${encodeURIComponent(o)}/replies?file_type=${encodeURIComponent(a)}`,{content:{elements:N(r)}});return JSON.stringify({ok:!0,documentId:s,commentId:String(o),replyId:c?.reply_id||""})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${n}`})}}catch(t){return JSON.stringify({ok:!1,error:t.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 oe(){_=null}export{oe as _resetLarkDocsTokenCache,y as docWebUrl,ne as larkDocsSkill,I as markdownToLarkBlocks,f as parseLarkDocRef,q as wikiWebUrl};
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/skills",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Built-in skill definitions for the Zibby agent-workflow framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/skills",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Built-in skill definitions for the Zibby agent-workflow framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",