@zibby/skills 0.2.2 → 0.2.4
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/chat-notify.js +2 -2
- package/dist/chatProgress.js +2 -2
- package/dist/datasetStore.js +2 -2
- package/dist/discord.js +1 -1
- package/dist/figma.js +1 -1
- package/dist/git-write.js +9 -9
- package/dist/git.d.ts +23 -0
- package/dist/git.js +2 -2
- package/dist/github.js +5 -5
- package/dist/gitlab.js +3 -3
- package/dist/googleDocs.js +1 -1
- package/dist/index.js +95 -95
- package/dist/jira.js +1 -1
- package/dist/lark.js +1 -1
- package/dist/larkDocs.js +1 -1
- package/dist/linear.js +1 -1
- package/dist/linkedin.js +1 -1
- package/dist/notion.js +1 -1
- package/dist/package.json +2 -2
- package/dist/plane.js +1 -1
- package/dist/sentry.js +1 -1
- package/dist/slack.js +1 -1
- package/dist/trackers/github-adapter.js +5 -5
- package/dist/trackers/index.js +6 -6
- package/dist/trackers/jira-adapter.js +1 -1
- package/dist/trackers/linear-adapter.js +1 -1
- package/docs/self-host/backup-restore.md +4 -3
- package/docs/self-host/custom-sidecars.md +136 -0
- package/package.json +2 -2
package/dist/chat-notify.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{existsSync as b}from"fs";import{fileURLToPath as S}from"url";import{dirname as v,resolve as N}from"path";import{resolveIntegrationToken as O}from"@zibby/core/backend-client.js";var y=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"}),J=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 I(){if(process.env.MCP_SLACK_PATH)return process.env.MCP_SLACK_PATH;let r=v(S(import.meta.url)),e=N(r,"..","bin","mcp-slack.mjs");return b(e)?e:null}async function d(r,e={}){let{token:t}=await O("slack"),s=["conversations.list","users.list","users.profile.get","users.lookupByEmail","usergroups.list","usergroups.users.list","conversations.history","conversations.replies"].includes(r),n=`https://slack.com/api/${r}`,a={Authorization:`Bearer ${t}`},u;if(s){let l=new URLSearchParams(e).toString();l&&(n+=`?${l}`)}else a["Content-Type"]="application/json; charset=utf-8",u=JSON.stringify(e);let i=await(await fetch(n,{method:s?"GET":"POST",headers:a,body:u})).json();if(!i.ok)throw new Error(`Slack API error: ${i.error}`);return i}var _={id:"slack",serverName:"slack",allowedTools:["mcp__slack__*"],requiresIntegration:y.SLACK,envKeys:["SLACK_BOT_TOKEN","SLACK_TEAM_ID"],description:"Slack MCP Server",promptFragment:`## Slack
|
|
1
|
+
import{existsSync as b}from"fs";import{fileURLToPath as S}from"url";import{dirname as v,resolve as N}from"path";import{resolveIntegrationToken as O}from"@zibby/core/backend-client.js";var y=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"}),J=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 I(){if(process.env.MCP_SLACK_PATH)return process.env.MCP_SLACK_PATH;let r=v(S(import.meta.url)),e=N(r,"..","bin","mcp-slack.mjs");return b(e)?e:null}async function d(r,e={}){let{token:t}=await O("slack"),s=["conversations.list","users.list","users.profile.get","users.lookupByEmail","usergroups.list","usergroups.users.list","conversations.history","conversations.replies"].includes(r),n=`https://slack.com/api/${r}`,a={Authorization:`Bearer ${t}`},u;if(s){let l=new URLSearchParams(e).toString();l&&(n+=`?${l}`)}else a["Content-Type"]="application/json; charset=utf-8",u=JSON.stringify(e);let i=await(await fetch(n,{method:s?"GET":"POST",headers:a,body:u})).json();if(!i.ok)throw new Error(`Slack API error: ${i.error}`);return i}var _={id:"slack",serverName:"slack",allowedTools:["mcp__slack__*"],requiresIntegration:y.SLACK,envKeys:["SLACK_BOT_TOKEN","SLACK_TEAM_ID"],description:"Slack MCP Server",promptFragment:`## Slack
|
|
2
2
|
You have access to the user's Slack workspace. Use these tools:
|
|
3
3
|
- slack_list_channels, slack_post_message, slack_reply_to_thread
|
|
4
4
|
- slack_add_reaction, slack_get_channel_history, slack_get_thread_replies
|
|
5
5
|
- slack_get_users, slack_get_user_profile
|
|
6
6
|
- slack_lookup_user_by_email (precise email\u2192user_id, prefer this over scanning slack_get_users)
|
|
7
|
-
- slack_list_usergroups, slack_get_usergroup_members (workspace-defined teams like @oncall, @platform)`,resolve(){let r=I();if(!r)return null;let e={};for(let t of["PROJECT_API_TOKEN","ZIBBY_USER_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[t]&&(e[t]=process.env[t]);for(let t of this.envKeys)process.env[t]&&(e[t]=process.env[t]);return{type:"stdio",command:"node",args:[r],env:e,alwaysLoad:!0}},async handleToolCall(r,e){try{switch(r){case"slack_list_channels":{let t=await d("conversations.list",{types:"public_channel",limit:100});return JSON.stringify({channels:(t.channels||[]).map(s=>({id:s.id,name:s.name,topic:s.topic?.value}))})}case"slack_post_message":{if(!e.channel||!e.text)return JSON.stringify({error:"channel and text are required"});let t=await d("chat.postMessage",{channel:e.channel,text:e.text,...e.blocks?{blocks:e.blocks}:{}});return JSON.stringify({ok:!0,ts:t.ts,channel:t.channel})}case"slack_reply_to_thread":{if(!e.channel||!e.thread_ts||!e.text)return JSON.stringify({error:"channel, thread_ts, and text are required"});let t=await d("chat.postMessage",{channel:e.channel,thread_ts:e.thread_ts,text:e.text});return JSON.stringify({ok:!0,ts:t.ts})}case"slack_add_reaction":return!e.channel||!e.timestamp||!e.reaction?JSON.stringify({error:"channel, timestamp, and reaction are required"}):(await d("reactions.add",{channel:e.channel,timestamp:e.timestamp,name:e.reaction}),JSON.stringify({ok:!0}));case"slack_get_channel_history":{if(!e.channel)return JSON.stringify({error:"channel is required"});let t=await d("conversations.history",{channel:e.channel,limit:e.limit||20});return JSON.stringify({messages:(t.messages||[]).map(s=>({user:s.user,text:s.text,ts:s.ts}))})}case"slack_get_thread_replies":{if(!e.channel||!e.thread_ts)return JSON.stringify({error:"channel and thread_ts are required"});let t=await d("conversations.replies",{channel:e.channel,ts:e.thread_ts});return JSON.stringify({messages:(t.messages||[]).map(s=>({user:s.user,text:s.text,ts:s.ts}))})}case"slack_get_users":{let t=await d("users.list",{limit:100});return JSON.stringify({users:(t.members||[]).filter(s=>!s.is_bot&&!s.deleted).map(s=>({id:s.id,name:s.real_name||s.name}))})}case"slack_get_user_profile":{if(!e.user_id)return JSON.stringify({error:"user_id is required"});let t=await d("users.profile.get",{user:e.user_id});return JSON.stringify({profile:t.profile})}case"slack_lookup_user_by_email":{if(!e.email)return JSON.stringify({error:"email is required"});try{let t=await d("users.lookupByEmail",{email:e.email});return JSON.stringify({ok:!0,user:{id:t.user?.id,name:t.user?.real_name||t.user?.name,email:t.user?.profile?.email||e.email}})}catch(t){if(/users_not_found/.test(t.message))return JSON.stringify({ok:!1,reason:"users_not_found"});throw t}}case"slack_list_usergroups":{let t=await d("usergroups.list",{});return JSON.stringify({usergroups:(t.usergroups||[]).map(s=>({id:s.id,handle:s.handle,name:s.name,description:s.description||"",user_count:Number(s.user_count||0)}))})}case"slack_get_usergroup_members":{if(!e.usergroup)return JSON.stringify({error:"usergroup id is required"});let t=await d("usergroups.users.list",{usergroup:e.usergroup});return JSON.stringify({users:t.users||[]})}case"slack_search_users":{if(!e.query||typeof e.query!="string")return JSON.stringify({error:"query is required"});let t=e.query.trim().toLowerCase();if(!t)return JSON.stringify({ok:!0,matches:[]});let s=Math.max(1,Math.min(Number(e.limit)||5,25)),n=[],a,u=5;for(let i=0;i<u;i+=1){let l={limit:200};a&&(l.cursor=a);let c=await d("users.list",l);for(let o of c.members||[])o.deleted||o.is_bot||n.push(o);if(a=c.response_metadata?.next_cursor,!a)break}let m=[];for(let i of n){let l=(i.real_name||"").toLowerCase(),c=(i.profile?.display_name||"").toLowerCase(),o=(i.name||"").toLowerCase(),p=0;l.includes(t)&&(p+=100-Math.abs(l.length-t.length)),c.includes(t)&&(p+=60-Math.abs(c.length-t.length)),o.includes(t)&&(p+=30-Math.abs(o.length-t.length)),(l===t||c===t)&&(p+=200),p>0&&m.push({id:i.id,name:i.real_name||i.profile?.display_name||i.name,email:i.profile?.email||void 0,_score:p})}return m.sort((i,l)=>l._score-i._score),JSON.stringify({ok:!0,matches:m.slice(0,s).map(({_score:i,...l})=>l),scanned:n.length})}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(t){return JSON.stringify({error:t.message})}},tools:[{name:"slack_list_channels",description:"List public channels in the workspace",input_schema:{type:"object",properties:{}}},{name:"slack_post_message",description:"Post a message to a Slack channel or DM. Pass `blocks` (Block Kit) for a rich card; `text` is the required notification fallback.",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID or name"},text:{type:"string",description:"Notification/fallback text (required)"},blocks:{type:"array",description:"Block Kit blocks for rich formatting (optional). Each block is a Slack Block Kit object (header/section/divider/context). section blocks may carry a button accessory with a url."}},required:["channel","text"]}},{name:"slack_reply_to_thread",description:"Reply to a specific message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"},text:{type:"string",description:"Reply text"}},required:["channel","thread_ts","text"]}},{name:"slack_add_reaction",description:"Add an emoji reaction to a message",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},timestamp:{type:"string",description:"Message timestamp"},reaction:{type:"string",description:"Emoji name without colons"}},required:["channel","timestamp","reaction"]}},{name:"slack_get_channel_history",description:"Get recent messages from a channel",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},limit:{type:"number",description:"Number of messages"}},required:["channel"]}},{name:"slack_get_thread_replies",description:"Get all replies in a message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"}},required:["channel","thread_ts"]}},{name:"slack_get_users",description:"List workspace users with basic profiles",input_schema:{type:"object",properties:{}}},{name:"slack_get_user_profile",description:"Get detailed profile for a specific user",input_schema:{type:"object",properties:{user_id:{type:"string",description:"Slack user ID"}},required:["user_id"]}},{name:"slack_lookup_user_by_email",description:"Find a Slack user by email. Returns { ok:true, user:{id,name,email} } on hit, { ok:false } when no user has that email. Prefer this over slack_get_users for email-based routing \u2014 single API call, exact match.",input_schema:{type:"object",properties:{email:{type:"string",description:"Email address to look up"}},required:["email"]}},{name:"slack_list_usergroups",description:"List workspace-defined user groups (e.g. @oncall, @platform). Each item has { id, handle, name, description, user_count }. Use the id with slack_get_usergroup_members to expand the membership.",input_schema:{type:"object",properties:{}}},{name:"slack_get_usergroup_members",description:"List user IDs that belong to a Slack usergroup. Pair with slack_post_message to DM each member, or use the group id directly in a channel message as <!subteam^ID> to @-mention.",input_schema:{type:"object",properties:{usergroup:{type:"string",description:"Usergroup id, e.g. S012ABC"}},required:["usergroup"]}},{name:"slack_search_users",description:'Fuzzy-search workspace users by display name or real name. Use when the user said something like "send to Sam" without an email. Returns up to `limit` ranked matches { id, name, email }. Slack has no native name-search API \u2014 this scans paginated users.list + does substring scoring (real_name > display_name > name). For large workspaces consider higher limit + ask the user to confirm if multiple hit.',input_schema:{type:"object",properties:{query:{type:"string",description:"Substring to match against names (case-insensitive)"},limit:{type:"number",description:"Max matches to return (default 5, max 25)"}},required:["query"]}}]};import{existsSync as T}from"fs";import{fileURLToPath as w}from"url";import{dirname as A,resolve as L}from"path";import{resolveIntegrationToken as E}from"@zibby/core/backend-client.js";function C(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let r=A(w(import.meta.url)),e=L(r,"..","bin","mcp-lark.mjs");return T(e)?e:null}var P=6e3*1e3,f=null;async function R(){let{appId:r,appSecret:e,host:t}=await E("lark");if(f&&f.appId===r&&f.expiresAt>Date.now())return{token:f.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:r,app_secret:e})})).json();if(n.code!==0)throw new Error(`Lark tenant_access_token failed: ${n.msg||n.code}`);return f={token:n.tenant_access_token,expiresAt:Date.now()+P,appId:r},{token:n.tenant_access_token,host:t}}async function g(r,e,t={}){let{token:s,host:n}=await R(),a=`${n}${e}`,u={method:r,headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json; charset=utf-8"}};r!=="GET"&&(u.body=JSON.stringify(t));let i=await(await fetch(a,u)).json();if(i.code!==0)throw new Error(`Lark API ${e} error: ${i.msg||i.code}`);return i.data||{}}function k(r){return JSON.stringify({text:r})}function x(r){return!r||typeof r!="string"||r.startsWith("oc_")?"chat_id":r.startsWith("ou_")?"open_id":r.startsWith("on_")?"union_id":r.startsWith("cli_")?"app_id":r.includes("@")?"email":"chat_id"}var h={id:"lark",serverName:"lark",allowedTools:["mcp__lark__*"],requiresIntegration:y.LARK,description:"Lark / Feishu messaging \u2014 send messages and reply in threads.",envKeys:[],promptFragment:`## Lark (connected)
|
|
7
|
+
- slack_list_usergroups, slack_get_usergroup_members (workspace-defined teams like @oncall, @platform)`,resolve(){let r=I();if(!r)return null;let e={};for(let t of["PROJECT_API_TOKEN","ZIBBY_USER_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[t]&&(e[t]=process.env[t]);for(let t of this.envKeys)process.env[t]&&(e[t]=process.env[t]);return{type:"stdio",command:"node",args:[r],env:e,alwaysLoad:!0}},async handleToolCall(r,e){try{switch(r){case"slack_list_channels":{let t=await d("conversations.list",{types:"public_channel",limit:100});return JSON.stringify({channels:(t.channels||[]).map(s=>({id:s.id,name:s.name,topic:s.topic?.value}))})}case"slack_post_message":{if(!e.channel||!e.text)return JSON.stringify({error:"channel and text are required"});let t=await d("chat.postMessage",{channel:e.channel,text:e.text,...e.blocks?{blocks:e.blocks}:{}});return JSON.stringify({ok:!0,ts:t.ts,channel:t.channel})}case"slack_reply_to_thread":{if(!e.channel||!e.thread_ts||!e.text)return JSON.stringify({error:"channel, thread_ts, and text are required"});let t=await d("chat.postMessage",{channel:e.channel,thread_ts:e.thread_ts,text:e.text});return JSON.stringify({ok:!0,ts:t.ts})}case"slack_add_reaction":return!e.channel||!e.timestamp||!e.reaction?JSON.stringify({error:"channel, timestamp, and reaction are required"}):(await d("reactions.add",{channel:e.channel,timestamp:e.timestamp,name:e.reaction}),JSON.stringify({ok:!0}));case"slack_get_channel_history":{if(!e.channel)return JSON.stringify({error:"channel is required"});let t=await d("conversations.history",{channel:e.channel,limit:e.limit||20});return JSON.stringify({messages:(t.messages||[]).map(s=>({user:s.user,text:s.text,ts:s.ts}))})}case"slack_get_thread_replies":{if(!e.channel||!e.thread_ts)return JSON.stringify({error:"channel and thread_ts are required"});let t=await d("conversations.replies",{channel:e.channel,ts:e.thread_ts});return JSON.stringify({messages:(t.messages||[]).map(s=>({user:s.user,text:s.text,ts:s.ts}))})}case"slack_get_users":{let t=await d("users.list",{limit:100});return JSON.stringify({users:(t.members||[]).filter(s=>!s.is_bot&&!s.deleted).map(s=>({id:s.id,name:s.real_name||s.name}))})}case"slack_get_user_profile":{if(!e.user_id)return JSON.stringify({error:"user_id is required"});let t=await d("users.profile.get",{user:e.user_id});return JSON.stringify({profile:t.profile})}case"slack_lookup_user_by_email":{if(!e.email)return JSON.stringify({error:"email is required"});try{let t=await d("users.lookupByEmail",{email:e.email});return JSON.stringify({ok:!0,user:{id:t.user?.id,name:t.user?.real_name||t.user?.name,email:t.user?.profile?.email||e.email}})}catch(t){if(/users_not_found/.test(t.message))return JSON.stringify({ok:!1,reason:"users_not_found"});throw t}}case"slack_list_usergroups":{let t=await d("usergroups.list",{});return JSON.stringify({usergroups:(t.usergroups||[]).map(s=>({id:s.id,handle:s.handle,name:s.name,description:s.description||"",user_count:Number(s.user_count||0)}))})}case"slack_get_usergroup_members":{if(!e.usergroup)return JSON.stringify({error:"usergroup id is required"});let t=await d("usergroups.users.list",{usergroup:e.usergroup});return JSON.stringify({users:t.users||[]})}case"slack_search_users":{if(!e.query||typeof e.query!="string")return JSON.stringify({error:"query is required"});let t=e.query.trim().toLowerCase();if(!t)return JSON.stringify({ok:!0,matches:[]});let s=Math.max(1,Math.min(Number(e.limit)||5,25)),n=[],a,u=5;for(let i=0;i<u;i+=1){let l={limit:200};a&&(l.cursor=a);let c=await d("users.list",l);for(let o of c.members||[])o.deleted||o.is_bot||n.push(o);if(a=c.response_metadata?.next_cursor,!a)break}let m=[];for(let i of n){let l=(i.real_name||"").toLowerCase(),c=(i.profile?.display_name||"").toLowerCase(),o=(i.name||"").toLowerCase(),p=0;l.includes(t)&&(p+=100-Math.abs(l.length-t.length)),c.includes(t)&&(p+=60-Math.abs(c.length-t.length)),o.includes(t)&&(p+=30-Math.abs(o.length-t.length)),(l===t||c===t)&&(p+=200),p>0&&m.push({id:i.id,name:i.real_name||i.profile?.display_name||i.name,email:i.profile?.email||void 0,_score:p})}return m.sort((i,l)=>l._score-i._score),JSON.stringify({ok:!0,matches:m.slice(0,s).map(({_score:i,...l})=>l),scanned:n.length})}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(t){return JSON.stringify({error:t.message})}},tools:[{name:"slack_list_channels",description:"List public channels in the workspace",input_schema:{type:"object",properties:{}}},{name:"slack_post_message",description:"Post a message to a Slack channel or DM. Pass `blocks` (Block Kit) for a rich card; `text` is the required notification fallback.",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID or name"},text:{type:"string",description:"Notification/fallback text (required)"},blocks:{type:"array",description:"Block Kit blocks for rich formatting (optional). Each block is a Slack Block Kit object (header/section/divider/context). section blocks may carry a button accessory with a url."}},required:["channel","text"]}},{name:"slack_reply_to_thread",description:"Reply to a specific message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"},text:{type:"string",description:"Reply text"}},required:["channel","thread_ts","text"]}},{name:"slack_add_reaction",description:"Add an emoji reaction to a message",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},timestamp:{type:"string",description:"Message timestamp"},reaction:{type:"string",description:"Emoji name without colons"}},required:["channel","timestamp","reaction"]}},{name:"slack_get_channel_history",description:"Get recent messages from a channel",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},limit:{type:"number",description:"Number of messages"}},required:["channel"]}},{name:"slack_get_thread_replies",description:"Get all replies in a message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"}},required:["channel","thread_ts"]}},{name:"slack_get_users",description:"List workspace users with basic profiles",input_schema:{type:"object",properties:{}}},{name:"slack_get_user_profile",description:"Get detailed profile for a specific user",input_schema:{type:"object",properties:{user_id:{type:"string",description:"Slack user ID"}},required:["user_id"]}},{name:"slack_lookup_user_by_email",description:"Find a Slack user by email. Returns { ok:true, user:{id,name,email} } on hit, { ok:false } when no user has that email. Prefer this over slack_get_users for email-based routing \u2014 single API call, exact match.",input_schema:{type:"object",properties:{email:{type:"string",description:"Email address to look up"}},required:["email"]}},{name:"slack_list_usergroups",description:"List workspace-defined user groups (e.g. @oncall, @platform). Each item has { id, handle, name, description, user_count }. Use the id with slack_get_usergroup_members to expand the membership.",input_schema:{type:"object",properties:{}}},{name:"slack_get_usergroup_members",description:"List user IDs that belong to a Slack usergroup. Pair with slack_post_message to DM each member, or use the group id directly in a channel message as <!subteam^ID> to @-mention.",input_schema:{type:"object",properties:{usergroup:{type:"string",description:"Usergroup id, e.g. S012ABC"}},required:["usergroup"]}},{name:"slack_search_users",description:'Fuzzy-search workspace users by display name or real name. Use when the user said something like "send to Sam" without an email. Returns up to `limit` ranked matches { id, name, email }. Slack has no native name-search API \u2014 this scans paginated users.list + does substring scoring (real_name > display_name > name). For large workspaces consider higher limit + ask the user to confirm if multiple hit.',input_schema:{type:"object",properties:{query:{type:"string",description:"Substring to match against names (case-insensitive)"},limit:{type:"number",description:"Max matches to return (default 5, max 25)"}},required:["query"]}}]};import{existsSync as T}from"fs";import{fileURLToPath as w}from"url";import{dirname as A,resolve as L}from"path";import{resolveIntegrationToken as E}from"@zibby/core/backend-client.js";function C(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let r=A(w(import.meta.url)),e=L(r,"..","bin","mcp-lark.mjs");return T(e)?e:null}var P=6e3*1e3,f=null;async function R(){let{appId:r,appSecret:e,host:t}=await E("lark");if(f&&f.appId===r&&f.expiresAt>Date.now())return{token:f.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:r,app_secret:e})})).json();if(n.code!==0)throw new Error(`Lark tenant_access_token failed: ${n.msg||n.code}`);return f={token:n.tenant_access_token,expiresAt:Date.now()+P,appId:r},{token:n.tenant_access_token,host:t}}async function g(r,e,t={}){let{token:s,host:n}=await R(),a=`${n}${e}`,u={method:r,headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json; charset=utf-8"}};r!=="GET"&&(u.body=JSON.stringify(t));let i=await(await fetch(a,u)).json();if(i.code!==0)throw new Error(`Lark API ${e} error: ${i.msg||i.code}`);return i.data||{}}function k(r){return JSON.stringify({text:r})}function x(r){return!r||typeof r!="string"||r.startsWith("oc_")?"chat_id":r.startsWith("ou_")?"open_id":r.startsWith("on_")?"union_id":r.startsWith("cli_")?"app_id":r.includes("@")?"email":"chat_id"}var h={id:"lark",serverName:"lark",allowedTools:["mcp__lark__*"],requiresIntegration:y.LARK,description:"Lark / Feishu messaging \u2014 send messages and reply in threads.",envKeys:[],promptFragment:`## Lark
|
|
8
8
|
You can send messages and replies on Lark. Use:
|
|
9
9
|
- lark_send_message: post a message to a chat, user, or DM
|
|
10
10
|
- lark_reply: reply to an existing message (threaded)
|
package/dist/chatProgress.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{existsSync as x}from"fs";import{fileURLToPath as J}from"url";import{dirname as q,resolve as D}from"path";import{existsSync as S}from"fs";import{fileURLToPath as b}from"url";import{dirname as N,resolve as v}from"path";import{resolveIntegrationToken as O}from"@zibby/core/backend-client.js";var g=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"}),U=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},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 I(){if(process.env.MCP_SLACK_PATH)return process.env.MCP_SLACK_PATH;let s=N(b(import.meta.url)),e=v(s,"..","bin","mcp-slack.mjs");return S(e)?e:null}async function _(s,e={}){let{token:t}=await O("slack"),r=["conversations.list","users.list","users.profile.get","users.lookupByEmail","usergroups.list","usergroups.users.list","conversations.history","conversations.replies"].includes(s),n=`https://slack.com/api/${s}`,a={Authorization:`Bearer ${t}`},m;if(r){let l=new URLSearchParams(e).toString();l&&(n+=`?${l}`)}else a["Content-Type"]="application/json; charset=utf-8",m=JSON.stringify(e);let i=await(await fetch(n,{method:r?"GET":"POST",headers:a,body:m})).json();if(!i.ok)throw new Error(`Slack API error: ${i.error}`);return i}var f={id:"slack",serverName:"slack",allowedTools:["mcp__slack__*"],requiresIntegration:g.SLACK,envKeys:["SLACK_BOT_TOKEN","SLACK_TEAM_ID"],description:"Slack MCP Server",promptFragment:`## Slack
|
|
1
|
+
import{existsSync as x}from"fs";import{fileURLToPath as J}from"url";import{dirname as q,resolve as D}from"path";import{existsSync as S}from"fs";import{fileURLToPath as b}from"url";import{dirname as N,resolve as v}from"path";import{resolveIntegrationToken as O}from"@zibby/core/backend-client.js";var g=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"}),U=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},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 I(){if(process.env.MCP_SLACK_PATH)return process.env.MCP_SLACK_PATH;let s=N(b(import.meta.url)),e=v(s,"..","bin","mcp-slack.mjs");return S(e)?e:null}async function _(s,e={}){let{token:t}=await O("slack"),r=["conversations.list","users.list","users.profile.get","users.lookupByEmail","usergroups.list","usergroups.users.list","conversations.history","conversations.replies"].includes(s),n=`https://slack.com/api/${s}`,a={Authorization:`Bearer ${t}`},m;if(r){let l=new URLSearchParams(e).toString();l&&(n+=`?${l}`)}else a["Content-Type"]="application/json; charset=utf-8",m=JSON.stringify(e);let i=await(await fetch(n,{method:r?"GET":"POST",headers:a,body:m})).json();if(!i.ok)throw new Error(`Slack API error: ${i.error}`);return i}var f={id:"slack",serverName:"slack",allowedTools:["mcp__slack__*"],requiresIntegration:g.SLACK,envKeys:["SLACK_BOT_TOKEN","SLACK_TEAM_ID"],description:"Slack MCP Server",promptFragment:`## Slack
|
|
2
2
|
You have access to the user's Slack workspace. Use these tools:
|
|
3
3
|
- slack_list_channels, slack_post_message, slack_reply_to_thread
|
|
4
4
|
- slack_add_reaction, slack_get_channel_history, slack_get_thread_replies
|
|
5
5
|
- slack_get_users, slack_get_user_profile
|
|
6
6
|
- slack_lookup_user_by_email (precise email\u2192user_id, prefer this over scanning slack_get_users)
|
|
7
|
-
- slack_list_usergroups, slack_get_usergroup_members (workspace-defined teams like @oncall, @platform)`,resolve(){let s=I();if(!s)return null;let e={};for(let t of["PROJECT_API_TOKEN","ZIBBY_USER_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[t]&&(e[t]=process.env[t]);for(let t of this.envKeys)process.env[t]&&(e[t]=process.env[t]);return{type:"stdio",command:"node",args:[s],env:e,alwaysLoad:!0}},async handleToolCall(s,e){try{switch(s){case"slack_list_channels":{let t=await _("conversations.list",{types:"public_channel",limit:100});return JSON.stringify({channels:(t.channels||[]).map(r=>({id:r.id,name:r.name,topic:r.topic?.value}))})}case"slack_post_message":{if(!e.channel||!e.text)return JSON.stringify({error:"channel and text are required"});let t=await _("chat.postMessage",{channel:e.channel,text:e.text,...e.blocks?{blocks:e.blocks}:{}});return JSON.stringify({ok:!0,ts:t.ts,channel:t.channel})}case"slack_reply_to_thread":{if(!e.channel||!e.thread_ts||!e.text)return JSON.stringify({error:"channel, thread_ts, and text are required"});let t=await _("chat.postMessage",{channel:e.channel,thread_ts:e.thread_ts,text:e.text});return JSON.stringify({ok:!0,ts:t.ts})}case"slack_add_reaction":return!e.channel||!e.timestamp||!e.reaction?JSON.stringify({error:"channel, timestamp, and reaction are required"}):(await _("reactions.add",{channel:e.channel,timestamp:e.timestamp,name:e.reaction}),JSON.stringify({ok:!0}));case"slack_get_channel_history":{if(!e.channel)return JSON.stringify({error:"channel is required"});let t=await _("conversations.history",{channel:e.channel,limit:e.limit||20});return JSON.stringify({messages:(t.messages||[]).map(r=>({user:r.user,text:r.text,ts:r.ts}))})}case"slack_get_thread_replies":{if(!e.channel||!e.thread_ts)return JSON.stringify({error:"channel and thread_ts are required"});let t=await _("conversations.replies",{channel:e.channel,ts:e.thread_ts});return JSON.stringify({messages:(t.messages||[]).map(r=>({user:r.user,text:r.text,ts:r.ts}))})}case"slack_get_users":{let t=await _("users.list",{limit:100});return JSON.stringify({users:(t.members||[]).filter(r=>!r.is_bot&&!r.deleted).map(r=>({id:r.id,name:r.real_name||r.name}))})}case"slack_get_user_profile":{if(!e.user_id)return JSON.stringify({error:"user_id is required"});let t=await _("users.profile.get",{user:e.user_id});return JSON.stringify({profile:t.profile})}case"slack_lookup_user_by_email":{if(!e.email)return JSON.stringify({error:"email is required"});try{let t=await _("users.lookupByEmail",{email:e.email});return JSON.stringify({ok:!0,user:{id:t.user?.id,name:t.user?.real_name||t.user?.name,email:t.user?.profile?.email||e.email}})}catch(t){if(/users_not_found/.test(t.message))return JSON.stringify({ok:!1,reason:"users_not_found"});throw t}}case"slack_list_usergroups":{let t=await _("usergroups.list",{});return JSON.stringify({usergroups:(t.usergroups||[]).map(r=>({id:r.id,handle:r.handle,name:r.name,description:r.description||"",user_count:Number(r.user_count||0)}))})}case"slack_get_usergroup_members":{if(!e.usergroup)return JSON.stringify({error:"usergroup id is required"});let t=await _("usergroups.users.list",{usergroup:e.usergroup});return JSON.stringify({users:t.users||[]})}case"slack_search_users":{if(!e.query||typeof e.query!="string")return JSON.stringify({error:"query is required"});let t=e.query.trim().toLowerCase();if(!t)return JSON.stringify({ok:!0,matches:[]});let r=Math.max(1,Math.min(Number(e.limit)||5,25)),n=[],a,m=5;for(let i=0;i<m;i+=1){let l={limit:200};a&&(l.cursor=a);let c=await _("users.list",l);for(let o of c.members||[])o.deleted||o.is_bot||n.push(o);if(a=c.response_metadata?.next_cursor,!a)break}let d=[];for(let i of n){let l=(i.real_name||"").toLowerCase(),c=(i.profile?.display_name||"").toLowerCase(),o=(i.name||"").toLowerCase(),p=0;l.includes(t)&&(p+=100-Math.abs(l.length-t.length)),c.includes(t)&&(p+=60-Math.abs(c.length-t.length)),o.includes(t)&&(p+=30-Math.abs(o.length-t.length)),(l===t||c===t)&&(p+=200),p>0&&d.push({id:i.id,name:i.real_name||i.profile?.display_name||i.name,email:i.profile?.email||void 0,_score:p})}return d.sort((i,l)=>l._score-i._score),JSON.stringify({ok:!0,matches:d.slice(0,r).map(({_score:i,...l})=>l),scanned:n.length})}default:return JSON.stringify({error:`Unknown tool: ${s}`})}}catch(t){return JSON.stringify({error:t.message})}},tools:[{name:"slack_list_channels",description:"List public channels in the workspace",input_schema:{type:"object",properties:{}}},{name:"slack_post_message",description:"Post a message to a Slack channel or DM. Pass `blocks` (Block Kit) for a rich card; `text` is the required notification fallback.",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID or name"},text:{type:"string",description:"Notification/fallback text (required)"},blocks:{type:"array",description:"Block Kit blocks for rich formatting (optional). Each block is a Slack Block Kit object (header/section/divider/context). section blocks may carry a button accessory with a url."}},required:["channel","text"]}},{name:"slack_reply_to_thread",description:"Reply to a specific message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"},text:{type:"string",description:"Reply text"}},required:["channel","thread_ts","text"]}},{name:"slack_add_reaction",description:"Add an emoji reaction to a message",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},timestamp:{type:"string",description:"Message timestamp"},reaction:{type:"string",description:"Emoji name without colons"}},required:["channel","timestamp","reaction"]}},{name:"slack_get_channel_history",description:"Get recent messages from a channel",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},limit:{type:"number",description:"Number of messages"}},required:["channel"]}},{name:"slack_get_thread_replies",description:"Get all replies in a message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"}},required:["channel","thread_ts"]}},{name:"slack_get_users",description:"List workspace users with basic profiles",input_schema:{type:"object",properties:{}}},{name:"slack_get_user_profile",description:"Get detailed profile for a specific user",input_schema:{type:"object",properties:{user_id:{type:"string",description:"Slack user ID"}},required:["user_id"]}},{name:"slack_lookup_user_by_email",description:"Find a Slack user by email. Returns { ok:true, user:{id,name,email} } on hit, { ok:false } when no user has that email. Prefer this over slack_get_users for email-based routing \u2014 single API call, exact match.",input_schema:{type:"object",properties:{email:{type:"string",description:"Email address to look up"}},required:["email"]}},{name:"slack_list_usergroups",description:"List workspace-defined user groups (e.g. @oncall, @platform). Each item has { id, handle, name, description, user_count }. Use the id with slack_get_usergroup_members to expand the membership.",input_schema:{type:"object",properties:{}}},{name:"slack_get_usergroup_members",description:"List user IDs that belong to a Slack usergroup. Pair with slack_post_message to DM each member, or use the group id directly in a channel message as <!subteam^ID> to @-mention.",input_schema:{type:"object",properties:{usergroup:{type:"string",description:"Usergroup id, e.g. S012ABC"}},required:["usergroup"]}},{name:"slack_search_users",description:'Fuzzy-search workspace users by display name or real name. Use when the user said something like "send to Sam" without an email. Returns up to `limit` ranked matches { id, name, email }. Slack has no native name-search API \u2014 this scans paginated users.list + does substring scoring (real_name > display_name > name). For large workspaces consider higher limit + ask the user to confirm if multiple hit.',input_schema:{type:"object",properties:{query:{type:"string",description:"Substring to match against names (case-insensitive)"},limit:{type:"number",description:"Max matches to return (default 5, max 25)"}},required:["query"]}}]};import{existsSync as P}from"fs";import{fileURLToPath as E}from"url";import{dirname as T,resolve as R}from"path";import{resolveIntegrationToken as A}from"@zibby/core/backend-client.js";function w(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let s=T(E(import.meta.url)),e=R(s,"..","bin","mcp-lark.mjs");return P(e)?e:null}var L=6e3*1e3,h=null;async function C(){let{appId:s,appSecret:e,host:t}=await A("lark");if(h&&h.appId===s&&h.expiresAt>Date.now())return{token:h.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:s,app_secret:e})})).json();if(n.code!==0)throw new Error(`Lark tenant_access_token failed: ${n.msg||n.code}`);return h={token:n.tenant_access_token,expiresAt:Date.now()+L,appId:s},{token:n.tenant_access_token,host:t}}async function u(s,e,t={}){let{token:r,host:n}=await C(),a=`${n}${e}`,m={method:s,headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json; charset=utf-8"}};s!=="GET"&&(m.body=JSON.stringify(t));let i=await(await fetch(a,m)).json();if(i.code!==0)throw new Error(`Lark API ${e} error: ${i.msg||i.code}`);return i.data||{}}function k(s){return JSON.stringify({text:s})}function B(s){return!s||typeof s!="string"||s.startsWith("oc_")?"chat_id":s.startsWith("ou_")?"open_id":s.startsWith("on_")?"union_id":s.startsWith("cli_")?"app_id":s.includes("@")?"email":"chat_id"}var y={id:"lark",serverName:"lark",allowedTools:["mcp__lark__*"],requiresIntegration:g.LARK,description:"Lark / Feishu messaging \u2014 send messages and reply in threads.",envKeys:[],promptFragment:`## Lark (connected)
|
|
7
|
+
- slack_list_usergroups, slack_get_usergroup_members (workspace-defined teams like @oncall, @platform)`,resolve(){let s=I();if(!s)return null;let e={};for(let t of["PROJECT_API_TOKEN","ZIBBY_USER_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[t]&&(e[t]=process.env[t]);for(let t of this.envKeys)process.env[t]&&(e[t]=process.env[t]);return{type:"stdio",command:"node",args:[s],env:e,alwaysLoad:!0}},async handleToolCall(s,e){try{switch(s){case"slack_list_channels":{let t=await _("conversations.list",{types:"public_channel",limit:100});return JSON.stringify({channels:(t.channels||[]).map(r=>({id:r.id,name:r.name,topic:r.topic?.value}))})}case"slack_post_message":{if(!e.channel||!e.text)return JSON.stringify({error:"channel and text are required"});let t=await _("chat.postMessage",{channel:e.channel,text:e.text,...e.blocks?{blocks:e.blocks}:{}});return JSON.stringify({ok:!0,ts:t.ts,channel:t.channel})}case"slack_reply_to_thread":{if(!e.channel||!e.thread_ts||!e.text)return JSON.stringify({error:"channel, thread_ts, and text are required"});let t=await _("chat.postMessage",{channel:e.channel,thread_ts:e.thread_ts,text:e.text});return JSON.stringify({ok:!0,ts:t.ts})}case"slack_add_reaction":return!e.channel||!e.timestamp||!e.reaction?JSON.stringify({error:"channel, timestamp, and reaction are required"}):(await _("reactions.add",{channel:e.channel,timestamp:e.timestamp,name:e.reaction}),JSON.stringify({ok:!0}));case"slack_get_channel_history":{if(!e.channel)return JSON.stringify({error:"channel is required"});let t=await _("conversations.history",{channel:e.channel,limit:e.limit||20});return JSON.stringify({messages:(t.messages||[]).map(r=>({user:r.user,text:r.text,ts:r.ts}))})}case"slack_get_thread_replies":{if(!e.channel||!e.thread_ts)return JSON.stringify({error:"channel and thread_ts are required"});let t=await _("conversations.replies",{channel:e.channel,ts:e.thread_ts});return JSON.stringify({messages:(t.messages||[]).map(r=>({user:r.user,text:r.text,ts:r.ts}))})}case"slack_get_users":{let t=await _("users.list",{limit:100});return JSON.stringify({users:(t.members||[]).filter(r=>!r.is_bot&&!r.deleted).map(r=>({id:r.id,name:r.real_name||r.name}))})}case"slack_get_user_profile":{if(!e.user_id)return JSON.stringify({error:"user_id is required"});let t=await _("users.profile.get",{user:e.user_id});return JSON.stringify({profile:t.profile})}case"slack_lookup_user_by_email":{if(!e.email)return JSON.stringify({error:"email is required"});try{let t=await _("users.lookupByEmail",{email:e.email});return JSON.stringify({ok:!0,user:{id:t.user?.id,name:t.user?.real_name||t.user?.name,email:t.user?.profile?.email||e.email}})}catch(t){if(/users_not_found/.test(t.message))return JSON.stringify({ok:!1,reason:"users_not_found"});throw t}}case"slack_list_usergroups":{let t=await _("usergroups.list",{});return JSON.stringify({usergroups:(t.usergroups||[]).map(r=>({id:r.id,handle:r.handle,name:r.name,description:r.description||"",user_count:Number(r.user_count||0)}))})}case"slack_get_usergroup_members":{if(!e.usergroup)return JSON.stringify({error:"usergroup id is required"});let t=await _("usergroups.users.list",{usergroup:e.usergroup});return JSON.stringify({users:t.users||[]})}case"slack_search_users":{if(!e.query||typeof e.query!="string")return JSON.stringify({error:"query is required"});let t=e.query.trim().toLowerCase();if(!t)return JSON.stringify({ok:!0,matches:[]});let r=Math.max(1,Math.min(Number(e.limit)||5,25)),n=[],a,m=5;for(let i=0;i<m;i+=1){let l={limit:200};a&&(l.cursor=a);let c=await _("users.list",l);for(let o of c.members||[])o.deleted||o.is_bot||n.push(o);if(a=c.response_metadata?.next_cursor,!a)break}let d=[];for(let i of n){let l=(i.real_name||"").toLowerCase(),c=(i.profile?.display_name||"").toLowerCase(),o=(i.name||"").toLowerCase(),p=0;l.includes(t)&&(p+=100-Math.abs(l.length-t.length)),c.includes(t)&&(p+=60-Math.abs(c.length-t.length)),o.includes(t)&&(p+=30-Math.abs(o.length-t.length)),(l===t||c===t)&&(p+=200),p>0&&d.push({id:i.id,name:i.real_name||i.profile?.display_name||i.name,email:i.profile?.email||void 0,_score:p})}return d.sort((i,l)=>l._score-i._score),JSON.stringify({ok:!0,matches:d.slice(0,r).map(({_score:i,...l})=>l),scanned:n.length})}default:return JSON.stringify({error:`Unknown tool: ${s}`})}}catch(t){return JSON.stringify({error:t.message})}},tools:[{name:"slack_list_channels",description:"List public channels in the workspace",input_schema:{type:"object",properties:{}}},{name:"slack_post_message",description:"Post a message to a Slack channel or DM. Pass `blocks` (Block Kit) for a rich card; `text` is the required notification fallback.",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID or name"},text:{type:"string",description:"Notification/fallback text (required)"},blocks:{type:"array",description:"Block Kit blocks for rich formatting (optional). Each block is a Slack Block Kit object (header/section/divider/context). section blocks may carry a button accessory with a url."}},required:["channel","text"]}},{name:"slack_reply_to_thread",description:"Reply to a specific message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"},text:{type:"string",description:"Reply text"}},required:["channel","thread_ts","text"]}},{name:"slack_add_reaction",description:"Add an emoji reaction to a message",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},timestamp:{type:"string",description:"Message timestamp"},reaction:{type:"string",description:"Emoji name without colons"}},required:["channel","timestamp","reaction"]}},{name:"slack_get_channel_history",description:"Get recent messages from a channel",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},limit:{type:"number",description:"Number of messages"}},required:["channel"]}},{name:"slack_get_thread_replies",description:"Get all replies in a message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"}},required:["channel","thread_ts"]}},{name:"slack_get_users",description:"List workspace users with basic profiles",input_schema:{type:"object",properties:{}}},{name:"slack_get_user_profile",description:"Get detailed profile for a specific user",input_schema:{type:"object",properties:{user_id:{type:"string",description:"Slack user ID"}},required:["user_id"]}},{name:"slack_lookup_user_by_email",description:"Find a Slack user by email. Returns { ok:true, user:{id,name,email} } on hit, { ok:false } when no user has that email. Prefer this over slack_get_users for email-based routing \u2014 single API call, exact match.",input_schema:{type:"object",properties:{email:{type:"string",description:"Email address to look up"}},required:["email"]}},{name:"slack_list_usergroups",description:"List workspace-defined user groups (e.g. @oncall, @platform). Each item has { id, handle, name, description, user_count }. Use the id with slack_get_usergroup_members to expand the membership.",input_schema:{type:"object",properties:{}}},{name:"slack_get_usergroup_members",description:"List user IDs that belong to a Slack usergroup. Pair with slack_post_message to DM each member, or use the group id directly in a channel message as <!subteam^ID> to @-mention.",input_schema:{type:"object",properties:{usergroup:{type:"string",description:"Usergroup id, e.g. S012ABC"}},required:["usergroup"]}},{name:"slack_search_users",description:'Fuzzy-search workspace users by display name or real name. Use when the user said something like "send to Sam" without an email. Returns up to `limit` ranked matches { id, name, email }. Slack has no native name-search API \u2014 this scans paginated users.list + does substring scoring (real_name > display_name > name). For large workspaces consider higher limit + ask the user to confirm if multiple hit.',input_schema:{type:"object",properties:{query:{type:"string",description:"Substring to match against names (case-insensitive)"},limit:{type:"number",description:"Max matches to return (default 5, max 25)"}},required:["query"]}}]};import{existsSync as P}from"fs";import{fileURLToPath as E}from"url";import{dirname as T,resolve as R}from"path";import{resolveIntegrationToken as A}from"@zibby/core/backend-client.js";function w(){if(process.env.MCP_LARK_PATH)return process.env.MCP_LARK_PATH;let s=T(E(import.meta.url)),e=R(s,"..","bin","mcp-lark.mjs");return P(e)?e:null}var L=6e3*1e3,h=null;async function C(){let{appId:s,appSecret:e,host:t}=await A("lark");if(h&&h.appId===s&&h.expiresAt>Date.now())return{token:h.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:s,app_secret:e})})).json();if(n.code!==0)throw new Error(`Lark tenant_access_token failed: ${n.msg||n.code}`);return h={token:n.tenant_access_token,expiresAt:Date.now()+L,appId:s},{token:n.tenant_access_token,host:t}}async function u(s,e,t={}){let{token:r,host:n}=await C(),a=`${n}${e}`,m={method:s,headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json; charset=utf-8"}};s!=="GET"&&(m.body=JSON.stringify(t));let i=await(await fetch(a,m)).json();if(i.code!==0)throw new Error(`Lark API ${e} error: ${i.msg||i.code}`);return i.data||{}}function k(s){return JSON.stringify({text:s})}function B(s){return!s||typeof s!="string"||s.startsWith("oc_")?"chat_id":s.startsWith("ou_")?"open_id":s.startsWith("on_")?"union_id":s.startsWith("cli_")?"app_id":s.includes("@")?"email":"chat_id"}var y={id:"lark",serverName:"lark",allowedTools:["mcp__lark__*"],requiresIntegration:g.LARK,description:"Lark / Feishu messaging \u2014 send messages and reply in threads.",envKeys:[],promptFragment:`## Lark
|
|
8
8
|
You can send messages and replies on Lark. Use:
|
|
9
9
|
- lark_send_message: post a message to a chat, user, or DM
|
|
10
10
|
- lark_reply: reply to an existing message (threaded)
|
package/dist/datasetStore.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync as f,readFileSync as b}from"node:fs";import{homedir as T}from"node:os";import{join as S,dirname as O,resolve as w}from"node:path";import{fileURLToPath as _}from"node:url";function I(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let
|
|
1
|
+
import{existsSync as f,readFileSync as b}from"node:fs";import{homedir as T}from"node:os";import{join as S,dirname as O,resolve as w}from"node:path";import{fileURLToPath as _}from"node:url";function I(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let s=O(_(import.meta.url)),t=w(s,"..","bin","mcp-skill.mjs");return f(t)?t:null}function m(){if(process.env.PROJECT_API_TOKEN)return process.env.PROJECT_API_TOKEN;if(process.env.ZIBBY_USER_TOKEN)return process.env.ZIBBY_USER_TOKEN;try{let s=S(T(),".zibby","config.json");return f(s)&&JSON.parse(b(s,"utf-8")).sessionToken||null}catch{return null}}function h(){return process.env.ZIBBY_ACCOUNT_API_URL?process.env.ZIBBY_ACCOUNT_API_URL.replace(/\/$/,""):(process.env.ZIBBY_ENV||"prod")==="local"?"http://localhost:3001":process.env.ZIBBY_PROD_ACCOUNT_API_URL||"https://api-prod.zibby.app"}function y(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function A(){let s={};for(let[t,e]of Object.entries(process.env)){let r=/^ZIBBY_STORE__(.+)$/.exec(t);if(!r)continue;let o=typeof e=="string"?e.trim():"";o&&(s[r[1]]=o)}return s}var u={};function C(){for(let s of Object.keys(u))delete u[s]}function d(s){let t={...A(),...u},e=Object.keys(t),r=typeof s=="string"?s.trim():"";return r?Object.prototype.hasOwnProperty.call(t,r)?{storeId:t[r],name:r}:{error:`unknown store '${r}'; available: ${e.join(", ")}`}:e.length===1?{storeId:t[e[0]],name:e[0]}:e.length===0?{error:"no stores bound to this agent"}:{error:`multiple stores are bound; pass \`store\` (one of: ${e.join(", ")})`}}async function N(s){let t=m();if(!t)throw new Error("No backend credential (PROJECT_API_TOKEN). Stores are only available inside a Zibby run.");let e=await fetch(`${h()}/datasets/stores/ensure`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify(s)});if(!e.ok){let r=await e.text().catch(()=>"");throw new Error(`ensure_store failed (${e.status}): ${r.slice(0,300)}`)}return e.json()}async function a(s,t,e){let r=m();if(!r)throw new Error("No backend credential (PROJECT_API_TOKEN). Dataset store is only available inside a Zibby run.");let o=`${h()}/datasets/stores/${encodeURIComponent(s)}/${t}`,n=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},body:JSON.stringify(e)});if(!n.ok){let i=await n.text().catch(()=>"");throw new Error(`Store ${t} failed (${n.status}): ${i.slice(0,300)}`)}return n.json()}var L=Math.floor(3.5*1024*1024);async function B(s,t,e,r){let o=await a(s,"put-url",{path:t,contentType:r});if(!o?.url)throw new Error("put-url did not return an upload URL");let n=await fetch(o.url,{method:o.method||"PUT",headers:o.headers||{"Content-Type":r},body:e});if(!n.ok){let i=await n.text().catch(()=>"");throw new Error(`direct upload failed (${n.status}): ${i.slice(0,200)}`)}return{ok:!0,path:o.path||t,size:e.length,contentType:o.contentType||r,via:"presign"}}async function R(s,t){let e=await a(s,"get-url",{path:t});if(!e?.url)throw new Error("get-url did not return a download URL");let r=await fetch(e.url,{method:e.method||"GET"});if(!r.ok){let n=await r.text().catch(()=>"");throw new Error(`direct download failed (${r.status}): ${n.slice(0,200)}`)}let o=await r.arrayBuffer();return{buf:Buffer.from(o),contentType:r.headers.get("content-type")||"application/octet-stream"}}var P={id:"dataset-store",serverName:"dataset_store",allowedTools:["mcp__dataset_store__*"],description:"Dataset store \u2014 a durable, queryable store for structured JSON records; append rows now, run SQL-style aggregations/reports later",promptFragment:`## Dataset Store (durable, queryable structured-record store)
|
|
2
2
|
You have one or more durable stores for STRUCTURED records that survive across
|
|
3
3
|
your stateless runs. Unlike key-value memory (for picking up where you left
|
|
4
4
|
off), this is for accumulating DATA you want to QUERY and AGGREGATE later \u2014 e.g.
|
|
@@ -45,4 +45,4 @@ Tools:
|
|
|
45
45
|
- file_list: (file stores) List stored files (optionally by path prefix).
|
|
46
46
|
- file_delete: (file stores) Delete ONE file by path.
|
|
47
47
|
Pick the tool that matches the store's TYPE; using a dataset tool on a sqlite
|
|
48
|
-
store (or a file tool on either, etc.) is rejected.`,resolve(){let n=I();if(!n)return{command:null,args:[],env:{},description:this.description};let t={};for(let e of["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_USER_TOKEN","WORKFLOW_TYPE"])process.env[e]&&(t[e]=process.env[e]);for(let e of Object.keys(process.env))/^ZIBBY_STORE__.+$/.test(e)&&process.env[e]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[n,"../dist/datasetStore.js","datasetStoreSkill"],env:t,description:this.description,alwaysLoad:!1}},async handleToolCall(n,t){try{switch(n){case"dataset_append":{if(t?.record==null||typeof t.record!="object"||Array.isArray(t.record))return JSON.stringify({error:"record is required (a JSON object)"});let e=p(t?.store);if(e.error)return JSON.stringify({error:e.error});let r=typeof t?.agent=="string"&&t.agent.trim()?t.agent.trim():y(),o={record:t.record,agent:r};typeof t?.description=="string"&&t.description.trim()&&(o.description=t.description.trim());let s=await a(e.storeId,"append",o);return JSON.stringify({...s,store:e.name,storeId:e.storeId})}case"dataset_query":{let e=p(t?.store);if(e.error)return JSON.stringify({error:e.error});let r={};for(let s of["select","where","groupBy","orderBy","limit","since","until","agent"])t?.[s]!=null&&(r[s]=t[s]);let o=await a(e.storeId,"query",r);return JSON.stringify({...o,store:e.name,storeId:e.storeId})}case"ensure_store":{let e=typeof t?.name=="string"?t.name.trim():"";if(!e)return JSON.stringify({error:"name is required"});let r=typeof t?.type=="string"&&t.type.trim()?t.type.trim().toLowerCase():"sqlite",o=typeof t?.description=="string"?t.description.trim():"",s=await N({name:e,type:r,description:o,namespace:y()});return s?.storeId&&(u[e]=s.storeId),JSON.stringify({...s,store:e})}case"file_put":{let e=p(t?.store);if(e.error)return JSON.stringify({error:e.error});if(typeof t?.path!="string"||!t.path.trim())return JSON.stringify({error:'path is required (a relative file path like "reports/2026-07.json")'});let r=typeof t?.content=="string",o=typeof t?.contentBase64=="string"&&t.contentBase64.length>0;if(!r&&!o)return JSON.stringify({error:"content (text) or contentBase64 (base64 bytes) is required"});let s=t.path.trim(),i=typeof t?.contentType=="string"&&t.contentType.trim()?t.contentType.trim():"",d=r?Buffer.from(t.content,"utf8"):Buffer.from(String(t.contentBase64).replace(/\s+/g,""),"base64");if(d.length>L){let g=i||(r?"text/plain; charset=utf-8":"application/octet-stream"),E=await B(e.storeId,s,d,g);return JSON.stringify({...E,store:e.name,storeId:e.storeId})}let c={path:s};r?c.content=t.content:c.contentBase64=t.contentBase64,i&&(c.contentType=i);let l=await a(e.storeId,"put",c);return JSON.stringify({...l,store:e.name,storeId:e.storeId})}case"file_get":{let e=p(t?.store);if(e.error)return JSON.stringify({error:e.error});if(typeof t?.path!="string"||!t.path.trim())return JSON.stringify({error:"path is required"});let r=t.path.trim(),o;try{o=await a(e.storeId,"get",{path:r})}catch(c){if(!/failed \(413\)/.test(c?.message||""))throw c;let l=await R(e.storeId,r);o={path:r,size:l.buf.length,contentType:l.contentType,lastModified:null,contentBase64:l.buf.toString("base64")}}let s={ok:!0,store:e.name,storeId:e.storeId,path:o.path,size:o.size,contentType:o.contentType,lastModified:o.lastModified},i=Buffer.from(o.contentBase64||"","base64"),d=i.toString("utf8");return Buffer.compare(Buffer.from(d,"utf8"),i)===0&&!d.includes("\0")?(s.content=d,s.encoding="utf8"):(s.contentBase64=o.contentBase64,s.encoding="base64"),JSON.stringify(s)}case"file_list":{let e=p(t?.store);if(e.error)return JSON.stringify({error:e.error});let r={};typeof t?.prefix=="string"&&t.prefix.trim()&&(r.prefix=t.prefix.trim()),t?.limit!=null&&(r.limit=t.limit),typeof t?.cursor=="string"&&t.cursor&&(r.cursor=t.cursor);let o=await a(e.storeId,"list",r);return JSON.stringify({...o,store:e.name,storeId:e.storeId})}case"file_delete":{let e=p(t?.store);if(e.error)return JSON.stringify({error:e.error});if(typeof t?.path!="string"||!t.path.trim())return JSON.stringify({error:"path is required"});let r=await a(e.storeId,"delete",{path:t.path.trim()});return JSON.stringify({...r,store:e.name,storeId:e.storeId})}case"sqlite_exec":case"sqlite_query":{let e=p(t?.store);if(e.error)return JSON.stringify({error:e.error});if(typeof t?.sql!="string"||!t.sql.trim())return JSON.stringify({error:"sql is required (a non-empty SQL string)"});let r={sql:t.sql};Array.isArray(t?.params)&&(r.params=t.params);let o=await a(e.storeId,"sql",r);return JSON.stringify({...o,store:e.name,storeId:e.storeId})}default:return JSON.stringify({error:`Unknown tool: ${n}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"dataset_append",description:"Append ONE structured JSON record to a bound store, durably. Records persist across your stateless runs and are auto-tagged with your agent type so you can filter to your own writes later. Use to accumulate data you will query/aggregate (e.g. per-run metrics, processed items). Append ONE record per call.",input_schema:{type:"object",properties:{store:{type:"string",description:'The logical store NAME to write to (e.g. "scorecards"), taken from the AVAILABLE STORES list \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this and it defaults to that store; you can ONLY write to a bound store name.'},record:{type:"object",description:'An arbitrary JSON object \u2014 one row of data. Its keys become queryable fields (e.g. {"repo":"owner/x","stars":1200}). One record per call.'},description:{type:"string",description:"Optional, informational note about this write. The store already exists from deploy, so this is not required and does not create anything."},agent:{type:"string",description:"Optional writing-agent tag. Defaults to your own agent type \u2014 leave unset to auto-tag."}},required:["record"]}},{name:"dataset_query",description:"Run a SQL-style query over a bound store to build reports: select/aggregate (count|sum|avg|min|max), filter, group, order, limit, and bound by month. Returns { columns, rows }. Use this to compute summaries/aggregations from records you appended earlier.",input_schema:{type:"object",properties:{store:{type:"string",description:'The logical store NAME to query (e.g. "scorecards"), taken from the AVAILABLE STORES list \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this and it defaults to that store.'},select:{type:"array",description:"Columns to return. Each item is { field?, agg?, as? }. agg \u2208 count|sum|avg|min|max; omit field for count(*). Omit `select` entirely to return raw rows."},where:{type:"array",description:"Filters, ANDed. Each item is { field, op, value }; op \u2208 eq|ne|gt|gte|lt|lte|like. `field` is a JSON key of the stored record."},groupBy:{type:"array",description:"Field names to group by (array of strings) for aggregation."},orderBy:{type:"array",description:"Sort spec. Each item is { field|as, dir }; dir \u2208 asc|desc."},limit:{type:"number",description:"Maximum number of rows to return."},since:{type:"string",description:"Inclusive lower bound month, 'yyyy-MM' (e.g. '2026-01')."},until:{type:"string",description:"Inclusive upper bound month, 'yyyy-MM' (e.g. '2026-06')."},agent:{type:"string",description:"Filter to records written by one agent namespace. Omit to query across all writers."}},required:[]}},{name:"ensure_store",description:'Create (or reuse) a store ON DEMAND for THIS agent \u2014 use when you need a store that was NOT declared/bound at deploy. Idempotent by name and private to this agent: calling again with the same name returns the SAME store (safe to call at the start of every run). Returns { storeId }. For type "sqlite" you then define schema with sqlite_exec (CREATE TABLE IF NOT EXISTS) and read/write via sqlite_exec/sqlite_query using this name.',input_schema:{type:"object",properties:{name:{type:"string",description:'A short logical name for the store (e.g. "linkedin_posts"). Letters, digits, _ and - only.'},type:{type:"string",enum:["sqlite","dataset","file"],description:'Store type. "sqlite" (default) = a mutable relational DB whose schema YOU define with SQL. "dataset" = append-only JSON records for later aggregation/analytics. "file" = arbitrary file/blob storage by relative path (file_put/file_get/file_list/file_delete).'},description:{type:"string",description:"What this store is for (shown in the Storage UI)."}},required:["name"]}},{name:"sqlite_exec",description:"For SQLITE-type stores: run SQL that CHANGES data \u2014 CREATE TABLE (use IF NOT EXISTS), INSERT, UPDATE, DELETE (one or more statements in one call). The store is a real, mutable SQLite database that persists across your stateless runs. Returns { rowsModified, wrote }. Use this to build schema on the fly and to update rows / track changing state (e.g. a queue with a status column).",input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a sqlite-type store from AVAILABLE STORES) \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this."},sql:{type:"string",description:'The SQL to run. May contain multiple statements separated by ";". Prefer CREATE TABLE IF NOT EXISTS for idempotent schema.'},params:{type:"array",description:'Optional positional bind params for a SINGLE parameterized statement (safe binding of values), e.g. sql "UPDATE t SET s=? WHERE id=?" with params ["done", 1].'}},required:["sql"]}},{name:"sqlite_query",description:"For SQLITE-type stores: run a read-only SELECT (optionally with `params` for safe binding) against the store's SQLite database. Returns { columns, rows }. Never changes data. Use to read back rows/state you stored earlier.",input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a sqlite-type store from AVAILABLE STORES) \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this."},sql:{type:"string",description:"A single SELECT statement. Use ? placeholders + `params` for any values."},params:{type:"array",description:'Optional positional bind params for the SELECT, e.g. ["linkedin_personal"].'}},required:["sql"]}},{name:"file_put",description:"For FILE-type stores: write (or overwrite) ONE file at a relative path \u2014 raw JSON dumps, CSVs, images, snapshots, any blob you want to keep across your stateless runs. Text goes in `content`; binary goes base64-encoded in `contentBase64` (pass exactly one). SIZE: there is no practical limit \u2014 anything over ~3.5 MiB is streamed straight to object storage via a presigned URL automatically, so do NOT gzip, base64-wrap, chunk or split a file just to make it fit. Non-ASCII paths (e.g. \u4E2A\u4EBA\u62A5\u544A.html) are fine \u2014 do not transliterate. Overwriting the same path UPDATES the file. Returns { ok, path, size }.",input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a file-type store from AVAILABLE STORES) \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this."},path:{type:"string",description:'Relative file path, e.g. "dumps/run-42.json" or "img/chart.png". Letters, digits, ".", "_", "-" and "/" only; no "..", no leading "/".'},content:{type:"string",description:"Text content (UTF-8). Use for JSON/CSV/text files."},contentBase64:{type:"string",description:"Base64-encoded binary content (images etc.). Alternative to `content`."},contentType:{type:"string",description:'Optional MIME type (e.g. "application/json", "image/png"). Defaults sensibly.'}},required:["path"]}},{name:"file_get",description:'For FILE-type stores: read ONE stored file back by its relative path. Text files come back as `content` (encoding "utf8"); binary files come back as `contentBase64` (encoding "base64"). Returns { path, size, contentType, lastModified, content|contentBase64, encoding }.',input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a file-type store from AVAILABLE STORES). If exactly one store is bound you may omit this."},path:{type:"string",description:"The relative file path to fetch (as listed by file_list / used in file_put)."}},required:["path"]}},{name:"file_list",description:'For FILE-type stores: list the stored files \u2014 each entry has { path, size, lastModified }. Optionally filter by a path `prefix` (e.g. "dumps/"). Paginated via `cursor` when there are more results.',input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a file-type store from AVAILABLE STORES). If exactly one store is bound you may omit this."},prefix:{type:"string",description:'Optional path prefix filter, e.g. "dumps/" or "reports/2026".'},limit:{type:"number",description:"Max entries to return (default/max 1000)."},cursor:{type:"string",description:"Pagination cursor from a previous file_list response (nextCursor)."}},required:[]}},{name:"file_delete",description:"For FILE-type stores: delete ONE stored file by its relative path. Returns { ok, path, deleted }. Errors if the path does not exist.",input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a file-type store from AVAILABLE STORES). If exactly one store is bound you may omit this."},path:{type:"string",description:"The relative file path to delete."}},required:["path"]}}]};export{C as __clearEnsuredStores,P as datasetStoreSkill};
|
|
48
|
+
store (or a file tool on either, etc.) is rejected.`,resolve(){let s=I();if(!s)return{command:null,args:[],env:{},description:this.description};let t={};for(let e of["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_USER_TOKEN","WORKFLOW_TYPE"])process.env[e]&&(t[e]=process.env[e]);for(let e of Object.keys(process.env))/^ZIBBY_STORE__.+$/.test(e)&&process.env[e]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[s,"../dist/datasetStore.js","datasetStoreSkill"],env:t,description:this.description,alwaysLoad:!1}},async handleToolCall(s,t){try{switch(s){case"dataset_append":{if(t?.record==null||typeof t.record!="object"||Array.isArray(t.record))return JSON.stringify({error:"record is required (a JSON object)"});let e=d(t?.store);if(e.error)return JSON.stringify({error:e.error});let r=typeof t?.agent=="string"&&t.agent.trim()?t.agent.trim():y(),o={record:t.record,agent:r};typeof t?.description=="string"&&t.description.trim()&&(o.description=t.description.trim());let n=await a(e.storeId,"append",o);return JSON.stringify({...n,store:e.name,storeId:e.storeId})}case"dataset_query":{let e=d(t?.store);if(e.error)return JSON.stringify({error:e.error});let r={};for(let n of["select","where","groupBy","orderBy","limit","since","until","agent"])t?.[n]!=null&&(r[n]=t[n]);let o=await a(e.storeId,"query",r);return JSON.stringify({...o,store:e.name,storeId:e.storeId})}case"ensure_store":{let e=typeof t?.name=="string"?t.name.trim():"";if(!e)return JSON.stringify({error:"name is required"});let r=typeof t?.type=="string"&&t.type.trim()?t.type.trim().toLowerCase():"sqlite",o=typeof t?.description=="string"?t.description.trim():"",n=await N({name:e,type:r,description:o,namespace:y()});return n?.storeId&&(u[e]=n.storeId),JSON.stringify({...n,store:e})}case"file_put":{let e=d(t?.store);if(e.error)return JSON.stringify({error:e.error});if(typeof t?.path!="string"||!t.path.trim())return JSON.stringify({error:'path is required (a relative file path like "reports/2026-07.json")'});let r=typeof t?.content=="string",o=typeof t?.contentBase64=="string"&&t.contentBase64.length>0;if(!r&&!o)return JSON.stringify({error:"content (text) or contentBase64 (base64 bytes) is required"});let n=t.path.trim(),i=typeof t?.contentType=="string"&&t.contentType.trim()?t.contentType.trim():"",p=r?Buffer.from(t.content,"utf8"):Buffer.from(String(t.contentBase64).replace(/\s+/g,""),"base64");if(p.length>L){let g=i||(r?"text/plain; charset=utf-8":"application/octet-stream"),E=await B(e.storeId,n,p,g);return JSON.stringify({...E,store:e.name,storeId:e.storeId})}let c={path:n};r?c.content=t.content:c.contentBase64=t.contentBase64,i&&(c.contentType=i);let l=await a(e.storeId,"put",c);return JSON.stringify({...l,store:e.name,storeId:e.storeId})}case"file_get":{let e=d(t?.store);if(e.error)return JSON.stringify({error:e.error});if(typeof t?.path!="string"||!t.path.trim())return JSON.stringify({error:"path is required"});let r=t.path.trim(),o;try{o=await a(e.storeId,"get",{path:r})}catch(c){if(!/failed \(413\)/.test(c?.message||""))throw c;let l=await R(e.storeId,r);o={path:r,size:l.buf.length,contentType:l.contentType,lastModified:null,contentBase64:l.buf.toString("base64")}}let n={ok:!0,store:e.name,storeId:e.storeId,path:o.path,size:o.size,contentType:o.contentType,lastModified:o.lastModified},i=Buffer.from(o.contentBase64||"","base64"),p=i.toString("utf8");return Buffer.compare(Buffer.from(p,"utf8"),i)===0&&!p.includes("\0")?(n.content=p,n.encoding="utf8"):(n.contentBase64=o.contentBase64,n.encoding="base64"),JSON.stringify(n)}case"file_list":{let e=d(t?.store);if(e.error)return JSON.stringify({error:e.error});let r={};typeof t?.prefix=="string"&&t.prefix.trim()&&(r.prefix=t.prefix.trim()),t?.limit!=null&&(r.limit=t.limit),typeof t?.cursor=="string"&&t.cursor&&(r.cursor=t.cursor);let o=await a(e.storeId,"list",r);return JSON.stringify({...o,store:e.name,storeId:e.storeId})}case"file_delete":{let e=d(t?.store);if(e.error)return JSON.stringify({error:e.error});if(typeof t?.path!="string"||!t.path.trim())return JSON.stringify({error:"path is required"});let r=await a(e.storeId,"delete",{path:t.path.trim()});return JSON.stringify({...r,store:e.name,storeId:e.storeId})}case"sqlite_exec":case"sqlite_query":{let e=d(t?.store);if(e.error)return JSON.stringify({error:e.error});if(typeof t?.sql!="string"||!t.sql.trim())return JSON.stringify({error:"sql is required (a non-empty SQL string)"});let r={sql:t.sql};s==="sqlite_query"&&(r.readOnly=!0),Array.isArray(t?.params)&&(r.params=t.params);let o=await a(e.storeId,"sql",r);return JSON.stringify({...o,store:e.name,storeId:e.storeId})}default:return JSON.stringify({error:`Unknown tool: ${s}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"dataset_append",description:"Append ONE structured JSON record to a bound store, durably. Records persist across your stateless runs and are auto-tagged with your agent type so you can filter to your own writes later. Use to accumulate data you will query/aggregate (e.g. per-run metrics, processed items). Append ONE record per call.",input_schema:{type:"object",properties:{store:{type:"string",description:'The logical store NAME to write to (e.g. "scorecards"), taken from the AVAILABLE STORES list \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this and it defaults to that store; you can ONLY write to a bound store name.'},record:{type:"object",description:'An arbitrary JSON object \u2014 one row of data. Its keys become queryable fields (e.g. {"repo":"owner/x","stars":1200}). One record per call.'},description:{type:"string",description:"Optional, informational note about this write. The store already exists from deploy, so this is not required and does not create anything."},agent:{type:"string",description:"Optional writing-agent tag. Defaults to your own agent type \u2014 leave unset to auto-tag."}},required:["record"]}},{name:"dataset_query",description:"Run a SQL-style query over a bound store to build reports: select/aggregate (count|sum|avg|min|max), filter, group, order, limit, and bound by month. Returns { columns, rows }. Use this to compute summaries/aggregations from records you appended earlier.",input_schema:{type:"object",properties:{store:{type:"string",description:'The logical store NAME to query (e.g. "scorecards"), taken from the AVAILABLE STORES list \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this and it defaults to that store.'},select:{type:"array",description:"Columns to return. Each item is { field?, agg?, as? }. agg \u2208 count|sum|avg|min|max; omit field for count(*). Omit `select` entirely to return raw rows."},where:{type:"array",description:"Filters, ANDed. Each item is { field, op, value }; op \u2208 eq|ne|gt|gte|lt|lte|like. `field` is a JSON key of the stored record."},groupBy:{type:"array",description:"Field names to group by (array of strings) for aggregation."},orderBy:{type:"array",description:"Sort spec. Each item is { field|as, dir }; dir \u2208 asc|desc."},limit:{type:"number",description:"Maximum number of rows to return."},since:{type:"string",description:"Inclusive lower bound month, 'yyyy-MM' (e.g. '2026-01')."},until:{type:"string",description:"Inclusive upper bound month, 'yyyy-MM' (e.g. '2026-06')."},agent:{type:"string",description:"Filter to records written by one agent namespace. Omit to query across all writers."}},required:[]}},{name:"ensure_store",description:'Create (or reuse) a store ON DEMAND for THIS agent \u2014 use when you need a store that was NOT declared/bound at deploy. Idempotent by name and private to this agent: calling again with the same name returns the SAME store (safe to call at the start of every run). Returns { storeId }. For type "sqlite" you then define schema with sqlite_exec (CREATE TABLE IF NOT EXISTS) and read/write via sqlite_exec/sqlite_query using this name.',input_schema:{type:"object",properties:{name:{type:"string",description:'A short logical name for the store (e.g. "linkedin_posts"). Letters, digits, _ and - only.'},type:{type:"string",enum:["sqlite","dataset","file"],description:'Store type. "sqlite" (default) = a mutable relational DB whose schema YOU define with SQL. "dataset" = append-only JSON records for later aggregation/analytics. "file" = arbitrary file/blob storage by relative path (file_put/file_get/file_list/file_delete).'},description:{type:"string",description:"What this store is for (shown in the Storage UI)."}},required:["name"]}},{name:"sqlite_exec",description:"For SQLITE-type stores: run SQL that CHANGES data \u2014 CREATE TABLE (use IF NOT EXISTS), INSERT, UPDATE, DELETE (one or more statements in one call). The store is a real, mutable SQLite database that persists across your stateless runs. Returns { rowsModified, wrote }. Use this to build schema on the fly and to update rows / track changing state (e.g. a queue with a status column).",input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a sqlite-type store from AVAILABLE STORES) \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this."},sql:{type:"string",description:'The SQL to run. May contain multiple statements separated by ";". Prefer CREATE TABLE IF NOT EXISTS for idempotent schema.'},params:{type:"array",description:'Optional positional bind params for a SINGLE parameterized statement (safe binding of values), e.g. sql "UPDATE t SET s=? WHERE id=?" with params ["done", 1].'}},required:["sql"]}},{name:"sqlite_query",description:"For SQLITE-type stores: run a read-only SELECT (optionally with `params` for safe binding) against the store's SQLite database. Returns { columns, rows }. Never changes data. Use to read back rows/state you stored earlier.",input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a sqlite-type store from AVAILABLE STORES) \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this."},sql:{type:"string",description:"A single SELECT statement. Use ? placeholders + `params` for any values."},params:{type:"array",description:'Optional positional bind params for the SELECT, e.g. ["linkedin_personal"].'}},required:["sql"]}},{name:"file_put",description:"For FILE-type stores: write (or overwrite) ONE file at a relative path \u2014 raw JSON dumps, CSVs, images, snapshots, any blob you want to keep across your stateless runs. Text goes in `content`; binary goes base64-encoded in `contentBase64` (pass exactly one). SIZE: there is no practical limit \u2014 anything over ~3.5 MiB is streamed straight to object storage via a presigned URL automatically, so do NOT gzip, base64-wrap, chunk or split a file just to make it fit. Non-ASCII paths (e.g. \u4E2A\u4EBA\u62A5\u544A.html) are fine \u2014 do not transliterate. Overwriting the same path UPDATES the file. Returns { ok, path, size }.",input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a file-type store from AVAILABLE STORES) \u2014 pick by description. NOT an id. If exactly one store is bound you may omit this."},path:{type:"string",description:'Relative file path, e.g. "dumps/run-42.json" or "img/chart.png". Letters, digits, ".", "_", "-" and "/" only; no "..", no leading "/".'},content:{type:"string",description:"Text content (UTF-8). Use for JSON/CSV/text files."},contentBase64:{type:"string",description:"Base64-encoded binary content (images etc.). Alternative to `content`."},contentType:{type:"string",description:'Optional MIME type (e.g. "application/json", "image/png"). Defaults sensibly.'}},required:["path"]}},{name:"file_get",description:'For FILE-type stores: read ONE stored file back by its relative path. Text files come back as `content` (encoding "utf8"); binary files come back as `contentBase64` (encoding "base64"). Returns { path, size, contentType, lastModified, content|contentBase64, encoding }.',input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a file-type store from AVAILABLE STORES). If exactly one store is bound you may omit this."},path:{type:"string",description:"The relative file path to fetch (as listed by file_list / used in file_put)."}},required:["path"]}},{name:"file_list",description:'For FILE-type stores: list the stored files \u2014 each entry has { path, size, lastModified }. Optionally filter by a path `prefix` (e.g. "dumps/"). Paginated via `cursor` when there are more results.',input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a file-type store from AVAILABLE STORES). If exactly one store is bound you may omit this."},prefix:{type:"string",description:'Optional path prefix filter, e.g. "dumps/" or "reports/2026".'},limit:{type:"number",description:"Max entries to return (default/max 1000)."},cursor:{type:"string",description:"Pagination cursor from a previous file_list response (nextCursor)."}},required:[]}},{name:"file_delete",description:"For FILE-type stores: delete ONE stored file by its relative path. Returns { ok, path, deleted }. Errors if the path does not exist.",input_schema:{type:"object",properties:{store:{type:"string",description:"The logical store NAME (a file-type store from AVAILABLE STORES). If exactly one store is bound you may omit this."},path:{type:"string",description:"The relative file path to delete."}},required:["path"]}}]};export{C as __clearEnsuredStores,P as datasetStoreSkill};
|
package/dist/discord.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{existsSync as h}from"fs";import{fileURLToPath as g}from"url";import{dirname as u,resolve as m}from"path";import{resolveIntegrationToken as _}from"@zibby/core/backend-client.js";var d=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"}),S=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},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 I(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let i=u(g(import.meta.url)),e=m(i,"..","bin","mcp-skill.mjs");return h(e)?e:null}var f=process.env.DISCORD_API_URL||"https://discord.com/api/v10",D=2e3;function v(i,e=D){let n=String(i??"");if(!n.trim())return[];if(n.length<=e)return[n];let s=[],o=n;for(;o.length>e;){let t=o.slice(0,e).lastIndexOf(`
|
|
2
|
-
`),a=t>e/2?t+1:e;s.push(o.slice(0,a)),o=o.slice(a)}return o&&s.push(o),s}async function p(){let i=(process.env.DISCORD_BOT_TOKEN||"").trim();if(i)return{token:i,guildId:(process.env.DISCORD_GUILD_ID||"").trim()};let e=await _(d.DISCORD),n=(e&&e.token?String(e.token):"").trim();if(!n)throw new Error("Discord is not connected: connect Discord in the Zibby dashboard or set DISCORD_BOT_TOKEN.");return{token:n,guildId:(e.guildId||"").trim()}}async function l(i,e,{token:n,body:s}={}){let o=n.startsWith("Bot ")?n:`Bot ${n}`,r=await fetch(`${f}${e}`,{method:i,headers:{Authorization:o,...s!==void 0?{"Content-Type":"application/json"}:{}},...s!==void 0?{body:JSON.stringify(s)}:{}}),t=await r.json().catch(()=>null);if(!r.ok){let a=t&&(t.message||t.error)?t.message||t.error:`HTTP ${r.status}`;throw new Error(`Discord API error (${r.status}): ${a}`)}return t}async function O({token:i,guildId:e},n){let s=String(n||"").trim()||e||(process.env.DISCORD_GUILD_ID||"").trim();if(s)return s;let o=await l("GET","/users/@me/guilds",{token:i}),r=Array.isArray(o)?o:[];if(r.length===1)return r[0].id;if(r.length===0)throw new Error("The bot is not in any Discord server \u2014 invite it to a server first.");let t=r.slice(0,10).map(a=>`${a.name} (${a.id})`).join(", ");throw new Error(`The bot is in ${r.length} servers \u2014 pass guildId explicitly. Servers: ${t}`)}var A={id:"discord",serverName:"discord",allowedTools:["mcp__discord__*"],requiresIntegration:d.DISCORD,envKeys:["DISCORD_BOT_TOKEN","DISCORD_GUILD_ID"],description:"Discord bot tools (send messages, list channels)",promptFragment:`## Discord
|
|
2
|
+
`),a=t>e/2?t+1:e;s.push(o.slice(0,a)),o=o.slice(a)}return o&&s.push(o),s}async function p(){let i=(process.env.DISCORD_BOT_TOKEN||"").trim();if(i)return{token:i,guildId:(process.env.DISCORD_GUILD_ID||"").trim()};let e=await _(d.DISCORD),n=(e&&e.token?String(e.token):"").trim();if(!n)throw new Error("Discord is not connected: connect Discord in the Zibby dashboard or set DISCORD_BOT_TOKEN.");return{token:n,guildId:(e.guildId||"").trim()}}async function l(i,e,{token:n,body:s}={}){let o=n.startsWith("Bot ")?n:`Bot ${n}`,r=await fetch(`${f}${e}`,{method:i,headers:{Authorization:o,...s!==void 0?{"Content-Type":"application/json"}:{}},...s!==void 0?{body:JSON.stringify(s)}:{}}),t=await r.json().catch(()=>null);if(!r.ok){let a=t&&(t.message||t.error)?t.message||t.error:`HTTP ${r.status}`;throw new Error(`Discord API error (${r.status}): ${a}`)}return t}async function O({token:i,guildId:e},n){let s=String(n||"").trim()||e||(process.env.DISCORD_GUILD_ID||"").trim();if(s)return s;let o=await l("GET","/users/@me/guilds",{token:i}),r=Array.isArray(o)?o:[];if(r.length===1)return r[0].id;if(r.length===0)throw new Error("The bot is not in any Discord server \u2014 invite it to a server first.");let t=r.slice(0,10).map(a=>`${a.name} (${a.id})`).join(", ");throw new Error(`The bot is in ${r.length} servers \u2014 pass guildId explicitly. Servers: ${t}`)}var A={id:"discord",serverName:"discord",allowedTools:["mcp__discord__*"],requiresIntegration:d.DISCORD,envKeys:["DISCORD_BOT_TOKEN","DISCORD_GUILD_ID"],description:"Discord bot tools (send messages, list channels)",promptFragment:`## Discord
|
|
3
3
|
You can post to the user's Discord server as their bot. Tools:
|
|
4
4
|
- discord_send_message(channelId, text) \u2014 post a message to a channel (long text is auto-chunked to Discord's 2000-char limit)
|
|
5
5
|
- discord_list_channels(guildId?) \u2014 list the server's text channels (id + name) to find where to post; guildId is optional when the bot is in one server`,resolve(){let i={};for(let n of this.envKeys)process.env[n]&&(i[n]=process.env[n]);for(let n of["PROJECT_API_TOKEN","ZIBBY_USER_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_SELF_HOST","DISCORD_API_URL","EXECUTION_ID","PROJECT_ID","STAGE"])process.env[n]&&(i[n]=process.env[n]);let e=I();return e?{type:"stdio",command:"node",args:[e,"../dist/discord.js","discordSkill"],env:i,description:this.description,alwaysLoad:!0}:{command:null,args:[],env:i,description:this.description}},async handleToolCall(i,e={}){try{switch(i){case"discord_send_message":{let n=String(e.channelId||"").trim(),s=String(e.text||"");if(!n||!s.trim())return JSON.stringify({ok:!1,error:"channelId and text are required"});let o=await p(),r=v(s),t=[];for(let a of r){let c=await l("POST",`/channels/${encodeURIComponent(n)}/messages`,{token:o.token,body:{content:a}});t.push(c&&c.id?c.id:"")}return JSON.stringify({ok:!0,channelId:n,messageIds:t,chunks:r.length})}case"discord_list_channels":{let n=await p(),s=await O(n,e.guildId),o=await l("GET",`/guilds/${encodeURIComponent(s)}/channels`,{token:n.token}),r=(Array.isArray(o)?o:[]).filter(t=>t&&(t.type===0||t.type===5)).map(t=>({id:t.id,name:t.name,type:t.type===5?"announcement":"text",...t.topic?{topic:t.topic}:{}}));return JSON.stringify({ok:!0,guildId:s,channels:r})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${i}`})}}catch(n){return JSON.stringify({ok:!1,error:n.message})}},tools:[{name:"discord_send_message",description:"Post a message to a Discord channel as the connected bot. Text over 2000 chars is automatically split into sequential messages.",input_schema:{type:"object",properties:{channelId:{type:"string",description:"Discord channel id (snowflake). Use discord_list_channels to find it."},text:{type:"string",description:"Message text (Discord markdown supported)"}},required:["channelId","text"]}},{name:"discord_list_channels",description:"List the text channels of the connected Discord server (id + name). Pass guildId only when the bot is in multiple servers.",input_schema:{type:"object",properties:{guildId:{type:"string",description:"Discord server (guild) id \u2014 optional when the bot is in exactly one server or one was captured at connect time"}}}}]};export{D as DISCORD_MAX_CONTENT,v as chunkDiscordContent,A as discordSkill};
|
package/dist/figma.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync as b}from"fs";import{fileURLToPath as S}from"url";import{dirname as N,resolve as k}from"path";import{resolveIntegrationToken as O}from"@zibby/core/backend-client.js";var h=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),A=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 L(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let e=N(S(import.meta.url)),n=k(e,"..","bin","mcp-skill.mjs");return b(n)?n:null}async function f(e,n={}){let{token:i}=await O("figma"),r=e.startsWith("https://")?e:`https://api.figma.com${e}`,s={"X-Figma-Token":i,Accept:"application/json",...n.body?{"Content-Type":"application/json"}:{}},o=await fetch(r,{method:n.method||"GET",headers:s,body:n.body?JSON.stringify(n.body):void 0});if(!o.ok){let a=await o.text().catch(()=>"");throw new Error(`Figma API ${o.status}: ${a.slice(0,300)}`)}return o.json()}function g(e){let n=String(e||"").trim(),i=n.match(/figma\.com\/(?:file|design|board|proto)\/([A-Za-z0-9]+)/i);return i?i[1]:n}function u(e){return e==null?void 0:(Array.isArray(e)?e:String(e).split(",")).map(i=>String(i).trim().replace(/-/g,":")).filter(Boolean).join(",")}function I(e){let n=String(e||"").match(/[?&]node-id=([^&]+)/i);return n?decodeURIComponent(n[1]).replace(/-/g,":"):void 0}var p=e=>typeof e=="number"?Math.round(e*100)/100:e;function _(e){if(!e)return null;let n=r=>Math.round((r??0)*255).toString(16).padStart(2,"0"),i={hex:`#${n(e.r)}${n(e.g)}${n(e.b)}`.toUpperCase()};return e.a!=null&&e.a<1&&(i.opacity=p(e.a)),i}function R(e){return!e||e.visible===!1?null:e.type==="SOLID"?{type:"SOLID",..._(e.color),...e.opacity!=null?{opacity:e.opacity}:{}}:{type:e.type,...e.opacity!=null?{opacity:e.opacity}:{},...e.gradientStops?{stops:e.gradientStops.map(n=>({position:p(n.position),..._(n.color)}))}:{}}}function v(e,n){let i=e.absoluteBoundingBox,r={id:e.id,name:e.name,type:e.type};i&&(r.size={w:p(i.width),h:p(i.height)},r.position={x:p(i.x),y:p(i.y)}),e.opacity!=null&&e.opacity!==1&&(r.opacity=e.opacity),e.cornerRadius!=null&&(r.cornerRadius=e.cornerRadius),Array.isArray(e.rectangleCornerRadii)&&(r.cornerRadii=e.rectangleCornerRadii);let s=(e.fills||[]).map(R).filter(Boolean);s.length&&(r.fills=s);let o=(e.strokes||[]).map(R).filter(Boolean);o.length&&(r.strokes=o,e.strokeWeight!=null&&(r.strokeWeight=e.strokeWeight));let a=(e.effects||[]).filter(t=>t.visible!==!1).map(t=>({type:t.type,...t.radius!=null?{radius:t.radius}:{},...t.offset?{offset:{x:p(t.offset.x),y:p(t.offset.y)}}:{},...t.spread?{spread:t.spread}:{},...t.color?_(t.color):{}}));if(a.length&&(r.effects=a),e.layoutMode&&e.layoutMode!=="NONE"&&(r.autoLayout={direction:e.layoutMode,padding:{l:e.paddingLeft??0,r:e.paddingRight??0,t:e.paddingTop??0,b:e.paddingBottom??0},gap:e.itemSpacing??0,align:{primary:e.primaryAxisAlignItems,counter:e.counterAxisAlignItems}}),e.type==="TEXT"){let t=e.style||{};r.text=e.characters,r.textStyle={fontFamily:t.fontFamily,fontWeight:t.fontWeight,fontSize:t.fontSize,lineHeightPx:t.lineHeightPx!=null?p(t.lineHeightPx):void 0,letterSpacing:t.letterSpacing!=null?p(t.letterSpacing):void 0,align:t.textAlignHorizontal,case:t.textCase}}return n>0&&Array.isArray(e.children)&&e.children.length&&(r.children=e.children.map(t=>v(t,n-1))),r}var F={id:"figma",serverName:"figma",allowedTools:["mcp__figma__*"],requiresIntegration:h.FIGMA,envKeys:[],description:"Figma \u2014 read files, nodes and comments; extract dev-handoff specs (sizes/colors/fonts/auto-layout); list versions and image fills; render frames to PNG/JPG/SVG/PDF; browse team projects/files; and post comments",promptFragment:`## Figma
|
|
1
|
+
import{existsSync as b}from"fs";import{fileURLToPath as S}from"url";import{dirname as N,resolve as k}from"path";import{resolveIntegrationToken as O}from"@zibby/core/backend-client.js";var h=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),A=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 L(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let e=N(S(import.meta.url)),n=k(e,"..","bin","mcp-skill.mjs");return b(n)?n:null}async function f(e,n={}){let{token:i}=await O("figma"),r=e.startsWith("https://")?e:`https://api.figma.com${e}`,s={"X-Figma-Token":i,Accept:"application/json",...n.body?{"Content-Type":"application/json"}:{}},o=await fetch(r,{method:n.method||"GET",headers:s,body:n.body?JSON.stringify(n.body):void 0});if(!o.ok){let a=await o.text().catch(()=>"");throw new Error(`Figma API ${o.status}: ${a.slice(0,300)}`)}return o.json()}function g(e){let n=String(e||"").trim(),i=n.match(/figma\.com\/(?:file|design|board|proto)\/([A-Za-z0-9]+)/i);return i?i[1]:n}function u(e){return e==null?void 0:(Array.isArray(e)?e:String(e).split(",")).map(i=>String(i).trim().replace(/-/g,":")).filter(Boolean).join(",")}function I(e){let n=String(e||"").match(/[?&]node-id=([^&]+)/i);return n?decodeURIComponent(n[1]).replace(/-/g,":"):void 0}var p=e=>typeof e=="number"?Math.round(e*100)/100:e;function _(e){if(!e)return null;let n=r=>Math.round((r??0)*255).toString(16).padStart(2,"0"),i={hex:`#${n(e.r)}${n(e.g)}${n(e.b)}`.toUpperCase()};return e.a!=null&&e.a<1&&(i.opacity=p(e.a)),i}function R(e){return!e||e.visible===!1?null:e.type==="SOLID"?{type:"SOLID",..._(e.color),...e.opacity!=null?{opacity:e.opacity}:{}}:{type:e.type,...e.opacity!=null?{opacity:e.opacity}:{},...e.gradientStops?{stops:e.gradientStops.map(n=>({position:p(n.position),..._(n.color)}))}:{}}}function v(e,n){let i=e.absoluteBoundingBox,r={id:e.id,name:e.name,type:e.type};i&&(r.size={w:p(i.width),h:p(i.height)},r.position={x:p(i.x),y:p(i.y)}),e.opacity!=null&&e.opacity!==1&&(r.opacity=e.opacity),e.cornerRadius!=null&&(r.cornerRadius=e.cornerRadius),Array.isArray(e.rectangleCornerRadii)&&(r.cornerRadii=e.rectangleCornerRadii);let s=(e.fills||[]).map(R).filter(Boolean);s.length&&(r.fills=s);let o=(e.strokes||[]).map(R).filter(Boolean);o.length&&(r.strokes=o,e.strokeWeight!=null&&(r.strokeWeight=e.strokeWeight));let a=(e.effects||[]).filter(t=>t.visible!==!1).map(t=>({type:t.type,...t.radius!=null?{radius:t.radius}:{},...t.offset?{offset:{x:p(t.offset.x),y:p(t.offset.y)}}:{},...t.spread?{spread:t.spread}:{},...t.color?_(t.color):{}}));if(a.length&&(r.effects=a),e.layoutMode&&e.layoutMode!=="NONE"&&(r.autoLayout={direction:e.layoutMode,padding:{l:e.paddingLeft??0,r:e.paddingRight??0,t:e.paddingTop??0,b:e.paddingBottom??0},gap:e.itemSpacing??0,align:{primary:e.primaryAxisAlignItems,counter:e.counterAxisAlignItems}}),e.type==="TEXT"){let t=e.style||{};r.text=e.characters,r.textStyle={fontFamily:t.fontFamily,fontWeight:t.fontWeight,fontSize:t.fontSize,lineHeightPx:t.lineHeightPx!=null?p(t.lineHeightPx):void 0,letterSpacing:t.letterSpacing!=null?p(t.letterSpacing):void 0,align:t.textAlignHorizontal,case:t.textCase}}return n>0&&Array.isArray(e.children)&&e.children.length&&(r.children=e.children.map(t=>v(t,n-1))),r}var F={id:"figma",serverName:"figma",allowedTools:["mcp__figma__*"],requiresIntegration:h.FIGMA,envKeys:[],description:"Figma \u2014 read files, nodes and comments; extract dev-handoff specs (sizes/colors/fonts/auto-layout); list versions and image fills; render frames to PNG/JPG/SVG/PDF; browse team projects/files; and post comments",promptFragment:`## Figma
|
|
2
2
|
You have access to the user's Figma files via the Figma REST API. Every tool that takes a file accepts EITHER the raw file key OR a full figma.com URL (figma.com/file|design/<key>/...). Node ids accept URL form \`1-23\` or API form \`1:23\` interchangeably. Tools:
|
|
3
3
|
|
|
4
4
|
### Identity
|