@zibby/skills 0.1.80 → 0.1.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
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 k=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",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"},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:k.SLACK,envKeys:["SLACK_BOT_TOKEN","SLACK_TEAM_ID"],description:"Slack MCP Server",promptFragment:`## Slack (connected)
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 k=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:k.SLACK,envKeys:["SLACK_BOT_TOKEN","SLACK_TEAM_ID"],description:"Slack MCP Server",promptFragment:`## Slack (connected)
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
package/dist/discord.js CHANGED
@@ -1,5 +1,5 @@
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",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"},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 t=u(g(import.meta.url)),e=m(t,"..","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(t,e=D){let n=String(t??"");if(!n.trim())return[];if(n.length<=e)return[n];let s=[],o=n;for(;o.length>e;){let i=o.slice(0,e).lastIndexOf(`
2
- `),a=i>e/2?i+1:e;s.push(o.slice(0,a)),o=o.slice(a)}return o&&s.push(o),s}async function p(){let t=(process.env.DISCORD_BOT_TOKEN||"").trim();if(t)return{token:t,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(t,e,{token:n,body:s}={}){let o=n.startsWith("Bot ")?n:`Bot ${n}`,r=await fetch(`${f}${e}`,{method:t,headers:{Authorization:o,...s!==void 0?{"Content-Type":"application/json"}:{}},...s!==void 0?{body:JSON.stringify(s)}:{}}),i=await r.json().catch(()=>null);if(!r.ok){let a=i&&(i.message||i.error)?i.message||i.error:`HTTP ${r.status}`;throw new Error(`Discord API error (${r.status}): ${a}`)}return i}async function O({token:t,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:t}),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 i=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: ${i}`)}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 (connected)
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 (connected)
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
- - 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 t={};for(let n of this.envKeys)process.env[n]&&(t[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]&&(t[n]=process.env[n]);let e=I();return e?{type:"stdio",command:"node",args:[e,"../dist/discord.js","discordSkill"],env:t,description:this.description,alwaysLoad:!0}:{command:null,args:[],env:t,description:this.description}},async handleToolCall(t,e={}){try{switch(t){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),i=[];for(let a of r){let c=await l("POST",`/channels/${encodeURIComponent(n)}/messages`,{token:o.token,body:{content:a}});i.push(c&&c.id?c.id:"")}return JSON.stringify({ok:!0,channelId:n,messageIds:i,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(i=>i&&(i.type===0||i.type===5)).map(i=>({id:i.id,name:i.name,type:i.type===5?"announcement":"text",...i.topic?{topic:i.topic}:{}}));return JSON.stringify({ok:!0,guildId:s,channels:r})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${t}`})}}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};
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.d.ts CHANGED
@@ -1,3 +1,19 @@
1
+ /** Accept a raw file key OR a figma.com URL (/file/, /design/, /board/, /proto/) → the key. */
2
+ export function fileKeyFrom(fileKeyOrUrl: any): string;
3
+ /** Accept a comma-string OR array of node ids; map URL form `1-23` → API `1:23`; join with commas. */
4
+ export function normalizeNodeIds(ids: any): string;
5
+ /** Extract the `node-id` query param from a Figma URL, dash→colon. */
6
+ export function nodeIdFromUrl(url: any): string;
7
+ /**
8
+ * Dev-Mode-style spec for a REST node: size, position, opacity, cornerRadius,
9
+ * fills (hex), strokes, effects, auto-layout, and text style — recursing
10
+ * `depth` levels into children. Used by figma_get_node_specs.
11
+ */
12
+ export function nodeSpec(n: any, depth: any): {
13
+ id: any;
14
+ name: any;
15
+ type: any;
16
+ };
1
17
  export namespace figmaSkill {
2
18
  let id: string;
3
19
  let serverName: string;
@@ -31,7 +47,20 @@ export namespace figmaSkill {
31
47
  fileKey?: undefined;
32
48
  depth?: undefined;
33
49
  ids?: undefined;
50
+ file?: undefined;
51
+ format?: undefined;
34
52
  scale?: undefined;
53
+ svg_include_id?: undefined;
54
+ version?: undefined;
55
+ message?: undefined;
56
+ comment_id?: undefined;
57
+ node_id?: undefined;
58
+ x?: undefined;
59
+ y?: undefined;
60
+ team_id?: undefined;
61
+ project_id?: undefined;
62
+ branch_data?: undefined;
63
+ url?: undefined;
35
64
  };
36
65
  required?: undefined;
37
66
  };
@@ -50,7 +79,20 @@ export namespace figmaSkill {
50
79
  description: string;
51
80
  };
52
81
  ids?: undefined;
82
+ file?: undefined;
83
+ format?: undefined;
53
84
  scale?: undefined;
85
+ svg_include_id?: undefined;
86
+ version?: undefined;
87
+ message?: undefined;
88
+ comment_id?: undefined;
89
+ node_id?: undefined;
90
+ x?: undefined;
91
+ y?: undefined;
92
+ team_id?: undefined;
93
+ project_id?: undefined;
94
+ branch_data?: undefined;
95
+ url?: undefined;
54
96
  };
55
97
  required: string[];
56
98
  };
@@ -75,7 +117,58 @@ export namespace figmaSkill {
75
117
  type: string;
76
118
  description: string;
77
119
  };
120
+ file?: undefined;
121
+ format?: undefined;
78
122
  scale?: undefined;
123
+ svg_include_id?: undefined;
124
+ version?: undefined;
125
+ message?: undefined;
126
+ comment_id?: undefined;
127
+ node_id?: undefined;
128
+ x?: undefined;
129
+ y?: undefined;
130
+ team_id?: undefined;
131
+ project_id?: undefined;
132
+ branch_data?: undefined;
133
+ url?: undefined;
134
+ };
135
+ required: string[];
136
+ };
137
+ } | {
138
+ name: string;
139
+ description: string;
140
+ input_schema: {
141
+ type: string;
142
+ properties: {
143
+ file: {
144
+ type: string;
145
+ description: string;
146
+ };
147
+ ids: {
148
+ type: string;
149
+ items: {
150
+ type: string;
151
+ };
152
+ description: string;
153
+ };
154
+ depth: {
155
+ type: string;
156
+ description: string;
157
+ };
158
+ fileKey?: undefined;
159
+ format?: undefined;
160
+ scale?: undefined;
161
+ svg_include_id?: undefined;
162
+ version?: undefined;
163
+ message?: undefined;
164
+ comment_id?: undefined;
165
+ node_id?: undefined;
166
+ x?: undefined;
167
+ y?: undefined;
168
+ team_id?: undefined;
169
+ project_id?: undefined;
170
+ branch_data?: undefined;
171
+ url?: undefined;
79
172
  };
80
173
  required: string[];
81
174
  };
@@ -96,11 +189,63 @@ export namespace figmaSkill {
96
189
  };
97
190
  description: string;
98
191
  };
192
+ format: {
193
+ type: string;
194
+ enum: string[];
195
+ description: string;
196
+ };
99
197
  scale: {
100
198
  type: string;
101
199
  description: string;
102
200
  };
201
+ svg_include_id: {
202
+ type: string;
203
+ description: string;
204
+ };
205
+ version: {
206
+ type: string;
207
+ description: string;
208
+ };
103
209
  depth?: undefined;
210
+ file?: undefined;
211
+ message?: undefined;
212
+ comment_id?: undefined;
213
+ node_id?: undefined;
214
+ x?: undefined;
215
+ y?: undefined;
216
+ team_id?: undefined;
217
+ project_id?: undefined;
218
+ branch_data?: undefined;
219
+ url?: undefined;
220
+ };
221
+ required: string[];
222
+ };
223
+ } | {
224
+ name: string;
225
+ description: string;
226
+ input_schema: {
227
+ type: string;
228
+ properties: {
229
+ file: {
230
+ type: string;
231
+ description: string;
232
+ };
233
+ fileKey?: undefined;
234
+ depth?: undefined;
235
+ ids?: undefined;
236
+ format?: undefined;
237
+ scale?: undefined;
238
+ svg_include_id?: undefined;
239
+ version?: undefined;
240
+ message?: undefined;
241
+ comment_id?: undefined;
242
+ node_id?: undefined;
243
+ x?: undefined;
244
+ y?: undefined;
245
+ team_id?: undefined;
246
+ project_id?: undefined;
247
+ branch_data?: undefined;
248
+ url?: undefined;
104
249
  };
105
250
  required: string[];
106
251
  };
@@ -116,7 +261,154 @@ export namespace figmaSkill {
116
261
  };
117
262
  depth?: undefined;
118
263
  ids?: undefined;
264
+ file?: undefined;
265
+ format?: undefined;
266
+ scale?: undefined;
267
+ svg_include_id?: undefined;
268
+ version?: undefined;
269
+ message?: undefined;
270
+ comment_id?: undefined;
271
+ node_id?: undefined;
272
+ x?: undefined;
273
+ y?: undefined;
274
+ team_id?: undefined;
275
+ project_id?: undefined;
276
+ branch_data?: undefined;
277
+ url?: undefined;
278
+ };
279
+ required: string[];
280
+ };
281
+ } | {
282
+ name: string;
283
+ description: string;
284
+ input_schema: {
285
+ type: string;
286
+ properties: {
287
+ file: {
288
+ type: string;
289
+ description: string;
290
+ };
291
+ message: {
292
+ type: string;
293
+ description: string;
294
+ };
295
+ comment_id: {
296
+ type: string;
297
+ description: string;
298
+ };
299
+ node_id: {
300
+ type: string;
301
+ description: string;
302
+ };
303
+ x: {
304
+ type: string;
305
+ description: string;
306
+ };
307
+ y: {
308
+ type: string;
309
+ description: string;
310
+ };
311
+ fileKey?: undefined;
312
+ depth?: undefined;
313
+ ids?: undefined;
314
+ format?: undefined;
315
+ scale?: undefined;
316
+ svg_include_id?: undefined;
317
+ version?: undefined;
318
+ team_id?: undefined;
319
+ project_id?: undefined;
320
+ branch_data?: undefined;
321
+ url?: undefined;
322
+ };
323
+ required: string[];
324
+ };
325
+ } | {
326
+ name: string;
327
+ description: string;
328
+ input_schema: {
329
+ type: string;
330
+ properties: {
331
+ team_id: {
332
+ type: string;
333
+ description: string;
334
+ };
335
+ fileKey?: undefined;
336
+ depth?: undefined;
337
+ ids?: undefined;
338
+ file?: undefined;
339
+ format?: undefined;
340
+ scale?: undefined;
341
+ svg_include_id?: undefined;
342
+ version?: undefined;
343
+ message?: undefined;
344
+ comment_id?: undefined;
345
+ node_id?: undefined;
346
+ x?: undefined;
347
+ y?: undefined;
348
+ project_id?: undefined;
349
+ branch_data?: undefined;
350
+ url?: undefined;
351
+ };
352
+ required: string[];
353
+ };
354
+ } | {
355
+ name: string;
356
+ description: string;
357
+ input_schema: {
358
+ type: string;
359
+ properties: {
360
+ project_id: {
361
+ type: string;
362
+ description: string;
363
+ };
364
+ branch_data: {
365
+ type: string;
366
+ description: string;
367
+ };
368
+ fileKey?: undefined;
369
+ depth?: undefined;
370
+ ids?: undefined;
371
+ file?: undefined;
372
+ format?: undefined;
373
+ scale?: undefined;
374
+ svg_include_id?: undefined;
375
+ version?: undefined;
376
+ message?: undefined;
377
+ comment_id?: undefined;
378
+ node_id?: undefined;
379
+ x?: undefined;
380
+ y?: undefined;
381
+ team_id?: undefined;
382
+ url?: undefined;
383
+ };
384
+ required: string[];
385
+ };
386
+ } | {
387
+ name: string;
388
+ description: string;
389
+ input_schema: {
390
+ type: string;
391
+ properties: {
392
+ url: {
393
+ type: string;
394
+ description: string;
395
+ };
396
+ fileKey?: undefined;
397
+ depth?: undefined;
398
+ ids?: undefined;
399
+ file?: undefined;
400
+ format?: undefined;
119
401
  scale?: undefined;
402
+ svg_include_id?: undefined;
403
+ version?: undefined;
404
+ message?: undefined;
405
+ comment_id?: undefined;
406
+ node_id?: undefined;
407
+ x?: undefined;
408
+ y?: undefined;
409
+ team_id?: undefined;
410
+ project_id?: undefined;
411
+ branch_data?: undefined;
120
412
  };
121
413
  required: string[];
122
414
  };
package/dist/figma.js CHANGED
@@ -1,19 +1,32 @@
1
- import{existsSync as f}from"fs";import{fileURLToPath as h}from"url";import{dirname as u,resolve as y}from"path";import{resolveIntegrationToken as _}from"@zibby/core/backend-client.js";var p=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",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),b=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"},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 N(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let s=u(h(import.meta.url)),t=y(s,"..","bin","mcp-skill.mjs");return f(t)?t:null}async function g(s,t={}){let{token:e}=await _("figma"),r=s.startsWith("https://")?s:`https://api.figma.com${s}`,a={"X-Figma-Token":e,Accept:"application/json",...t.body?{"Content-Type":"application/json"}:{}},i=await fetch(r,{method:t.method||"GET",headers:a,body:t.body?JSON.stringify(t.body):void 0});if(!i.ok){let d=await i.text().catch(()=>"");throw new Error(`Figma API ${i.status}: ${d.slice(0,300)}`)}return i.json()}var O={id:"figma",serverName:"figma",allowedTools:["mcp__figma__*"],requiresIntegration:p.FIGMA,envKeys:[],description:"Figma \u2014 read files, nodes, comments, and render frames as PNGs",promptFragment:`## Figma (connected)
2
- You have read access to the user's Figma files via the Figma REST API. Tools:
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 (connected)
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
5
- - figma_get_me: Get the authenticated Figma user (handle, email, id)
5
+ - figma_get_me: Get the authenticated Figma user (handle, email, id) \u2014 confirms the token works.
6
6
 
7
7
  ### Files & nodes
8
- - figma_get_file: Get a file's document tree by fileKey. The fileKey is the token in a Figma URL: figma.com/file/<fileKey>/<name> (or /design/<fileKey>/). Pass an optional depth to limit how deep the node tree is returned (1-2 is usually enough to find frames/pages).
9
- - figma_get_nodes: Get specific nodes from a file by their node ids (comma-separated or array). Use this after figma_get_file to drill into a particular frame/component without re-fetching the whole tree.
8
+ - figma_get_file: Get a file's document tree by key or URL. Optional depth (1-2) limits how deep the tree is returned \u2014 usually enough to find pages/frames.
9
+ - figma_get_nodes: Get specific nodes by id (comma-separated or array). Use after figma_get_file to drill into a frame/component without re-fetching the whole file.
10
+
11
+ ### Dev handoff (build UI from a design)
12
+ - figma_get_node_specs: THE dev-mode tool. For the given node ids returns a compact spec computed from the REST tree \u2014 size, position, colors (hex), fonts/text-style, spacing/auto-layout, corner radius, strokes and effects \u2014 recursing depth levels (default 2). Prefer this over raw figma_get_nodes when your job is to reproduce a design in code.
13
+ - figma_get_image_fills: Map of imageRef \u2192 download URL for every image fill in a file.
10
14
 
11
15
  ### Rendering
12
- - figma_render_png: Render one or more nodes of a file to PNG and return the image URLs. Pass fileKey + node ids; optional scale (0.01-4, default 1). Returns a map of nodeId \u2192 image URL you can show the user or download.
16
+ - figma_render_png: Render nodes to PNG/JPG/SVG/PDF and return image URLs (a map of nodeId \u2192 URL). Optional format (default png), scale 0.01\u20134 (raster only), svg_include_id, version. Despite the name it renders any of the four formats.
13
17
 
14
18
  ### Comments
15
19
  - figma_get_comments: Read the comments on a file.
20
+ - figma_post_comment: Post a comment (WRITE \u2014 needs a PAT with comment-write scope). Optionally reply (comment_id) or pin to a node (node_id[+x/y]) or a canvas point (x/y).
21
+
22
+ ### Browse
23
+ - figma_get_file_versions: List a file's saved version history; a version id can be passed to render a past version.
24
+ - figma_get_team_projects: List the projects in a team (team_id from a team URL \u2026/files/team/<TEAM_ID>/\u2026).
25
+ - figma_get_project_files: List the files in a project (from figma_get_team_projects).
26
+
27
+ ### Local helper
28
+ - figma_parse_url: Extract { file_key, node_id } from a figma.com URL \u2014 no API call.
16
29
 
17
30
  ### Notes
18
- - The fileKey is NOT the file name \u2014 it's the opaque id segment in the URL.
19
- - Node ids look like "1:23" and come from figma_get_file / figma_get_nodes output.`,resolve(){let s=N();if(!s)return{command:null,args:[],env:{},description:this.description};let t={};for(let e of this.envKeys)process.env[e]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[s,"../dist/figma.js","figmaSkill"],env:t,description:this.description,alwaysLoad:!0}},async handleToolCall(s,t){try{switch(s){case"figma_get_me":{let e=await g("/v1/me");return JSON.stringify({id:e.id,handle:e.handle,email:e.email,imgUrl:e.img_url})}case"figma_get_file":{let{fileKey:e,depth:r}=t||{};if(!e)return JSON.stringify({error:"fileKey is required"});let a=`/v1/files/${encodeURIComponent(e)}`;if(r!=null){let n=Number(r);!Number.isNaN(n)&&n>0&&(a+=`?depth=${n}`)}let i=await g(a),d=(i.document?.children||[]).map(n=>({id:n.id,name:n.name,type:n.type,childCount:Array.isArray(n.children)?n.children.length:0,children:(n.children||[]).slice(0,50).map(o=>({id:o.id,name:o.name,type:o.type}))}));return JSON.stringify({name:i.name,lastModified:i.lastModified,version:i.version,editorType:i.editorType,role:i.role,pages:d})}case"figma_get_nodes":{let{fileKey:e,ids:r,depth:a}=t||{};if(!e)return JSON.stringify({error:"fileKey is required"});let d=(Array.isArray(r)?r:r?String(r).split(","):[]).map(m=>String(m).trim()).filter(Boolean);if(d.length===0)return JSON.stringify({error:"ids is required (comma-separated or array of node ids)"});let n=new URLSearchParams;if(n.set("ids",d.join(",")),a!=null){let m=Number(a);!Number.isNaN(m)&&m>0&&n.set("depth",String(m))}let o=await g(`/v1/files/${encodeURIComponent(e)}/nodes?${n.toString()}`),l={};for(let[m,c]of Object.entries(o.nodes||{}))l[m]=c?.document?{id:c.document.id,name:c.document.name,type:c.document.type,document:c.document}:c;return JSON.stringify({name:o.name,nodes:l})}case"figma_render_png":{let{fileKey:e,ids:r,scale:a}=t||{};if(!e)return JSON.stringify({error:"fileKey is required"});let d=(Array.isArray(r)?r:r?String(r).split(","):[]).map(m=>String(m).trim()).filter(Boolean);if(d.length===0)return JSON.stringify({error:"ids is required (comma-separated or array of node ids)"});let n=new URLSearchParams;n.set("ids",d.join(",")),n.set("format","png");let o=Number(a);(Number.isNaN(o)||o<=0)&&(o=1),o=Math.min(4,Math.max(.01,o)),n.set("scale",String(o));let l=await g(`/v1/images/${encodeURIComponent(e)}?${n.toString()}`);return l.err?JSON.stringify({error:`Figma render error: ${l.err}`}):JSON.stringify({scale:o,format:"png",images:l.images||{}})}case"figma_get_comments":{let{fileKey:e}=t||{};if(!e)return JSON.stringify({error:"fileKey is required"});let a=((await g(`/v1/files/${encodeURIComponent(e)}/comments`)).comments||[]).map(i=>({id:i.id,message:i.message,user:i.user?.handle,createdAt:i.created_at,resolvedAt:i.resolved_at||null,parentId:i.parent_id||null}));return JSON.stringify({count:a.length,comments:a})}default:return JSON.stringify({error:`Unknown tool: ${s}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"figma_get_me",description:"Get the authenticated Figma user profile (handle, email, id)",input_schema:{type:"object",properties:{}}},{name:"figma_get_file",description:"Get a Figma file's document tree by fileKey (the opaque id segment in a figma.com/file/<fileKey>/ or /design/<fileKey>/ URL \u2014 NOT the file name). Returns a summarized map of pages and their top-level frames/nodes. Use figma_get_nodes to drill into a specific node.",input_schema:{type:"object",properties:{fileKey:{type:"string",description:'The file key from the Figma URL (e.g. "aBcD1234" in figma.com/design/aBcD1234/My-File)'},depth:{type:"number",description:"Optional: limit how deep the node tree is traversed (1-2 is usually enough to list pages/frames). Omit for the full tree."}},required:["fileKey"]}},{name:"figma_get_nodes",description:"Get specific nodes from a Figma file by their node ids. Use after figma_get_file to inspect a particular frame/component without re-fetching the whole file.",input_schema:{type:"object",properties:{fileKey:{type:"string",description:"The file key from the Figma URL"},ids:{type:"array",items:{type:"string"},description:'Node ids to fetch (e.g. ["1:23","4:56"]). A comma-separated string is also accepted.'},depth:{type:"number",description:"Optional: limit traversal depth within each node."}},required:["fileKey","ids"]}},{name:"figma_render_png",description:"Render one or more Figma nodes to PNG and return the image URLs (a map of nodeId \u2192 URL). Use this to show or download a visual of a frame/component.",input_schema:{type:"object",properties:{fileKey:{type:"string",description:"The file key from the Figma URL"},ids:{type:"array",items:{type:"string"},description:'Node ids to render (e.g. ["1:23"]). A comma-separated string is also accepted.'},scale:{type:"number",description:"Render scale, 0.01\u20134 (default 1). 2 for retina/hi-dpi."}},required:["fileKey","ids"]}},{name:"figma_get_comments",description:"Read the comments on a Figma file",input_schema:{type:"object",properties:{fileKey:{type:"string",description:"The file key from the Figma URL"}},required:["fileKey"]}}]};export{O as figmaSkill};
31
+ - The file key is NOT the file name \u2014 it's the opaque id segment in the URL (or just paste the URL).
32
+ - Node ids look like "1:23" and come from figma_get_file / figma_get_nodes / figma_parse_url output.`,resolve(){let e=L();if(!e)return{command:null,args:[],env:{},description:this.description};let n={};for(let i of this.envKeys)process.env[i]&&(n[i]=process.env[i]);return{type:"stdio",command:"node",args:[e,"../dist/figma.js","figmaSkill"],env:n,description:this.description,alwaysLoad:!0}},async handleToolCall(e,n){try{switch(e){case"figma_get_me":{let i=await f("/v1/me");return JSON.stringify({id:i.id,handle:i.handle,email:i.email,imgUrl:i.img_url})}case"figma_get_file":{let i=g((n||{}).fileKey),{depth:r}=n||{};if(!i)return JSON.stringify({error:"fileKey is required (a file key or a figma.com URL)"});let s=`/v1/files/${encodeURIComponent(i)}`;if(r!=null){let t=Number(r);!Number.isNaN(t)&&t>0&&(s+=`?depth=${t}`)}let o=await f(s),a=(o.document?.children||[]).map(t=>({id:t.id,name:t.name,type:t.type,childCount:Array.isArray(t.children)?t.children.length:0,children:(t.children||[]).slice(0,50).map(c=>({id:c.id,name:c.name,type:c.type}))}));return JSON.stringify({name:o.name,lastModified:o.lastModified,version:o.version,editorType:o.editorType,role:o.role,pages:a})}case"figma_get_nodes":{let i=g((n||{}).fileKey),{ids:r,depth:s}=n||{};if(!i)return JSON.stringify({error:"fileKey is required (a file key or a figma.com URL)"});let o=u(r);if(!o)return JSON.stringify({error:"ids is required (comma-separated or array of node ids; `1-23` or `1:23`)"});let a=new URLSearchParams;if(a.set("ids",o),s!=null){let d=Number(s);!Number.isNaN(d)&&d>0&&a.set("depth",String(d))}let t=await f(`/v1/files/${encodeURIComponent(i)}/nodes?${a.toString()}`),c={};for(let[d,m]of Object.entries(t.nodes||{}))c[d]=m?.document?{id:m.document.id,name:m.document.name,type:m.document.type,document:m.document}:m;return JSON.stringify({name:t.name,nodes:c})}case"figma_get_node_specs":{let i=g((n||{}).file),{ids:r,depth:s}=n||{};if(!i)return JSON.stringify({error:"file is required (a file key or a figma.com URL)"});let o=u(r);if(!o)return JSON.stringify({error:"ids is required (comma-separated or array of node ids; `1-23` or `1:23`)"});let a=Number(s);Number.isNaN(a)&&(a=2),a=Math.min(6,Math.max(0,Math.trunc(a)));let t=new URLSearchParams;t.set("ids",o),t.set("depth",String(a));let c=await f(`/v1/files/${encodeURIComponent(i)}/nodes?${t.toString()}`),d={};for(let[m,l]of Object.entries(c.nodes||{}))d[m]=l&&l.document?v(l.document,a):null;return JSON.stringify(d)}case"figma_render_png":{let i=g((n||{}).fileKey??(n||{}).file),{ids:r,scale:s,format:o,svg_include_id:a,version:t}=n||{};if(!i)return JSON.stringify({error:"fileKey is required (a file key or a figma.com URL)"});let c=u(r);if(!c)return JSON.stringify({error:"ids is required (comma-separated or array of node ids; `1-23` or `1:23`)"});let d=["png","jpg","svg","pdf"].includes(String(o||"").toLowerCase())?String(o).toLowerCase():"png",m=new URLSearchParams;m.set("ids",c),m.set("format",d);let l;(d==="png"||d==="jpg")&&(l=Number(s),(Number.isNaN(l)||l<=0)&&(l=1),l=Math.min(4,Math.max(.01,l)),m.set("scale",String(l))),d==="svg"&&a!=null&&m.set("svg_include_id",String(!!a)),t&&m.set("version",String(t));let y=await f(`/v1/images/${encodeURIComponent(i)}?${m.toString()}`);return y.err?JSON.stringify({error:`Figma render error: ${y.err}`}):JSON.stringify({format:d,...l!=null?{scale:l}:{},images:y.images||{}})}case"figma_get_image_fills":{let i=g((n||{}).file);if(!i)return JSON.stringify({error:"file is required (a file key or a figma.com URL)"});let r=await f(`/v1/files/${encodeURIComponent(i)}/images`);return JSON.stringify({images:r.meta?.images??r.images??{}})}case"figma_get_file_versions":{let i=g((n||{}).file);if(!i)return JSON.stringify({error:"file is required (a file key or a figma.com URL)"});let s=((await f(`/v1/files/${encodeURIComponent(i)}/versions`)).versions||[]).map(o=>({id:o.id,label:o.label,description:o.description,createdAt:o.created_at,user:o.user?.handle}));return JSON.stringify({count:s.length,versions:s})}case"figma_get_comments":{let i=g((n||{}).fileKey);if(!i)return JSON.stringify({error:"fileKey is required (a file key or a figma.com URL)"});let s=((await f(`/v1/files/${encodeURIComponent(i)}/comments`)).comments||[]).map(o=>({id:o.id,message:o.message,user:o.user?.handle,createdAt:o.created_at,resolvedAt:o.resolved_at||null,parentId:o.parent_id||null}));return JSON.stringify({count:s.length,comments:s})}case"figma_post_comment":{let i=g((n||{}).file),{message:r,comment_id:s,node_id:o,x:a,y:t}=n||{};if(!i)return JSON.stringify({error:"file is required (a file key or a figma.com URL)"});if(!r)return JSON.stringify({error:"message is required"});let c={message:r};s&&(c.comment_id=s),o?c.client_meta={node_id:u(o),node_offset:{x:a??0,y:t??0}}:(a!=null||t!=null)&&(c.client_meta={x:a??0,y:t??0});let d=await f(`/v1/files/${encodeURIComponent(i)}/comments`,{method:"POST",body:c});return JSON.stringify({id:d.id,message:d.message,fileKey:i,createdAt:d.created_at,parentId:d.parent_id||null})}case"figma_get_team_projects":{let{team_id:i}=n||{};if(!i)return JSON.stringify({error:"team_id is required"});let r=await f(`/v1/teams/${encodeURIComponent(i)}/projects`);return JSON.stringify({name:r.name,projects:r.projects||[]})}case"figma_get_project_files":{let{project_id:i,branch_data:r}=n||{};if(!i)return JSON.stringify({error:"project_id is required"});let s=new URLSearchParams;r&&s.set("branch_data","true");let o=s.toString(),a=await f(`/v1/projects/${encodeURIComponent(i)}/files${o?`?${o}`:""}`);return JSON.stringify({name:a.name,files:a.files||[]})}case"figma_parse_url":{let{url:i}=n||{};return JSON.stringify({file_key:g(i),node_id:I(i)??null})}default:return JSON.stringify({error:`Unknown tool: ${e}`})}}catch(i){return JSON.stringify({error:i.message})}},tools:[{name:"figma_get_me",description:"Get the authenticated Figma user profile (handle, email, id)",input_schema:{type:"object",properties:{}}},{name:"figma_get_file",description:"Get a Figma file's document tree. Accepts a file key OR a figma.com URL (figma.com/file|design/<key>/... \u2014 NOT the file name). Returns a summarized map of pages and their top-level frames/nodes. Use figma_get_nodes or figma_get_node_specs to drill into a specific node.",input_schema:{type:"object",properties:{fileKey:{type:"string",description:'A file key OR a figma.com URL (e.g. "aBcD1234" or figma.com/design/aBcD1234/My-File)'},depth:{type:"number",description:"Optional: limit how deep the node tree is traversed (1-2 is usually enough to list pages/frames). Omit for the full tree."}},required:["fileKey"]}},{name:"figma_get_nodes",description:"Get specific nodes from a Figma file by their node ids. Use after figma_get_file to inspect a particular frame/component without re-fetching the whole file.",input_schema:{type:"object",properties:{fileKey:{type:"string",description:"A file key OR a figma.com URL"},ids:{type:"array",items:{type:"string"},description:'Node ids to fetch. Accepts URL form ["1-23"] or API form ["1:23"]. A comma-separated string is also accepted.'},depth:{type:"number",description:"Optional: limit traversal depth within each node."}},required:["fileKey","ids"]}},{name:"figma_get_node_specs",description:"Dev-Mode-style inspection computed from the REST tree: for the given nodes and their children returns size, position, colors (hex), fonts/text-style, spacing/auto-layout, corner radius, strokes and effects \u2014 a compact spec for reproducing a design in code. Prefer this over figma_get_nodes when your job is to build UI from a Figma file.",input_schema:{type:"object",properties:{file:{type:"string",description:"A file key OR a figma.com URL"},ids:{type:"array",items:{type:"string"},description:'Node ids to inspect (URL form "1-23" or API form "1:23"). A comma-separated string is also accepted.'},depth:{type:"number",description:"How many levels of children to walk (0-6, default 2)."}},required:["file","ids"]}},{name:"figma_render_png",description:"Render one or more Figma nodes to an image and return the URLs (a map of nodeId \u2192 URL). Despite the name it renders PNG/JPG/SVG/PDF (default png). Use to show or download a visual of a frame/component.",input_schema:{type:"object",properties:{fileKey:{type:"string",description:"A file key OR a figma.com URL"},ids:{type:"array",items:{type:"string"},description:'Node ids to render (URL form "1-23" or API form "1:23"). A comma-separated string is also accepted.'},format:{type:"string",enum:["png","jpg","svg","pdf"],description:"Output format (default png)."},scale:{type:"number",description:"Render scale, 0.01\u20134 (raster formats only; default 1). 2 for retina/hi-dpi."},svg_include_id:{type:"boolean",description:"For SVG: include element ids as attributes."},version:{type:"string",description:"Render a specific file version id (from figma_get_file_versions)."}},required:["fileKey","ids"]}},{name:"figma_get_image_fills",description:"Get a map of imageRef \u2192 download URL for every image fill used in a Figma file.",input_schema:{type:"object",properties:{file:{type:"string",description:"A file key OR a figma.com URL"}},required:["file"]}},{name:"figma_get_file_versions",description:"List a Figma file's saved version history (version id, label, created_at, user). A version id can be passed to figma_render_png to render a past version.",input_schema:{type:"object",properties:{file:{type:"string",description:"A file key OR a figma.com URL"}},required:["file"]}},{name:"figma_get_comments",description:"Read the comments on a Figma file",input_schema:{type:"object",properties:{fileKey:{type:"string",description:"A file key OR a figma.com URL"}},required:["fileKey"]}},{name:"figma_post_comment",description:"Post a comment on a Figma file (WRITE \u2014 needs a PAT with comment-write scope). Optionally reply to a comment (comment_id), pin it to a node (node_id, with optional x/y offset), or pin to a canvas point (x/y).",input_schema:{type:"object",properties:{file:{type:"string",description:"A file key OR a figma.com URL"},message:{type:"string",description:"The comment text."},comment_id:{type:"string",description:"Optional: reply to this parent comment id."},node_id:{type:"string",description:'Optional: pin the comment to this node ("1-23" or "1:23").'},x:{type:"number",description:"Optional: x position (node offset if node_id given, else canvas point)."},y:{type:"number",description:"Optional: y position (node offset if node_id given, else canvas point)."}},required:["file","message"]}},{name:"figma_get_team_projects",description:"List the projects in a Figma team. Get team_id from a Figma team URL (\u2026/files/team/<TEAM_ID>/\u2026).",input_schema:{type:"object",properties:{team_id:{type:"string",description:"The Figma team id."}},required:["team_id"]}},{name:"figma_get_project_files",description:"List the files in a Figma project (from figma_get_team_projects). Returns file key, name, thumbnail, last_modified.",input_schema:{type:"object",properties:{project_id:{type:"string",description:"The Figma project id."},branch_data:{type:"boolean",description:"Optional: include branch metadata for each file."}},required:["project_id"]}},{name:"figma_parse_url",description:"Local helper (no API call): extract the file key and node id (colon form) from a figma.com file/design URL. Returns { file_key, node_id }.",input_schema:{type:"object",properties:{url:{type:"string",description:"A full figma.com file/design URL."}},required:["url"]}}]};export{F as figmaSkill,g as fileKeyFrom,I as nodeIdFromUrl,v as nodeSpec,u as normalizeNodeIds};
package/dist/git-write.js CHANGED
@@ -9,7 +9,7 @@ When a test ticket lacks context, use this workflow:
9
9
  2. Use git_explore to understand the project structure
10
10
  3. Use shell commands (grep, cat) to read specific files for deeper understanding
11
11
  4. Use GitHub/GitLab skills to read related PRs and commits
12
- 5. Build well-informed test specs and save them to files before running tests`,resolve(){return null},async handleToolCall(m,n,r){let i=r?.options?.workspace||process.cwd();try{switch(m){case"git_checkout":return await Q(n,i);case"git_list_repos":return V(n,i);case"git_explore":return Y(n,i);default:return JSON.stringify({error:`Unknown tool: ${m}`})}}catch(t){return JSON.stringify({error:t.message})}},tools:[{name:"git_checkout",description:"Clone a git repository locally (or pull latest if already cloned). Auto-authenticates with GitHub/GitLab tokens if available.",input_schema:{type:"object",properties:{url:{type:"string",description:'Repository URL (e.g. "https://github.com/org/repo" or "org/repo" shorthand for GitHub)'},branch:{type:"string",description:"Branch to checkout (default: repo default branch)"},shallow:{type:"boolean",description:"Shallow clone with depth 1 (default: true, faster)"},name:{type:"string",description:"Local directory name override (default: repo name from URL)"}},required:["url"]}},{name:"git_list_repos",description:"List locally cloned repositories",input_schema:{type:"object",properties:{}}},{name:"git_explore",description:"Quick structural overview of a cloned repo: key files, package.json info, directory tree (top 2 levels), detected framework/language",input_schema:{type:"object",properties:{repo:{type:"string",description:"Repo name (as listed by git_list_repos)"},depth:{type:"number",description:"Directory tree depth (default: 2)"}},required:["repo"]}}]};async function Q(m,n){let{url:r,branch:i,shallow:t=!0,name:e}=m;!r.includes("://")&&!r.startsWith("git@")&&(r=`https://github.com/${r}`);let s=e||W(r.replace(/\.git$/,"")),a=G(n,E);F(a,{recursive:!0});let o=A(a,s),p=r,d=process.env.GITHUB_TOKEN,l=process.env.GITLAB_TOKEN,c=process.env.GITLAB_URL;if(r.includes("github.com")&&d)p=r.replace("https://github.com",`https://x-access-token:${d}@github.com`);else if(l&&c)try{let u=new URL(c).host;r.includes(u)&&(p=r.replace(`https://${u}`,`https://oauth2:${l}@${u}`))}catch{}if(O(A(o,".git"))){let u=i?`git -C "${o}" fetch origin ${i} && git -C "${o}" checkout ${i} && git -C "${o}" pull origin ${i}`:`git -C "${o}" pull`;await I(u,n);let _=await I(`git -C "${o}" log -1 --format="%h %s"`,n);return JSON.stringify({action:"updated",repo:s,path:o,branch:i||"default",head:_})}let h=["git","clone"];t&&h.push("--depth","1"),i&&h.push("--branch",i),h.push(`"${p}"`,`"${o}"`),await I(h.join(" "),n);let y=await I(`git -C "${o}" log -1 --format="%h %s"`,n);return JSON.stringify({action:"cloned",repo:s,path:o,branch:i||"default",shallow:t,head:y})}function V(m,n){let r=G(n,E);if(!O(r))return JSON.stringify({repos:[],message:"No repos cloned yet"});let i=[];for(let t of U(r,{withFileTypes:!0})){if(!t.isDirectory())continue;let e=A(r,t.name);if(!O(A(e,".git")))continue;let s=K(e);i.push({name:t.name,path:e,lastModified:s.mtime.toISOString()})}return JSON.stringify({repos:i,total:i.length,directory:r})}function Y(m,n){let{repo:r,depth:i=2}=m,t=G(n,E,r);if(!O(t))return JSON.stringify({error:`Repo not found: ${r}. Run git_checkout first.`});let e={repo:r,path:t},s=A(t,"package.json");if(O(s))try{let h=JSON.parse(z(s,"utf-8"));e.packageJson={name:h.name,version:h.version,scripts:h.scripts?Object.keys(h.scripts):[],dependencies:h.dependencies?Object.keys(h.dependencies).slice(0,30):[],devDependencies:h.devDependencies?Object.keys(h.devDependencies).slice(0,20):[]},h.dependencies?.react?e.framework="React":h.dependencies?.next?e.framework="Next.js":h.dependencies?.vue?e.framework="Vue":h.dependencies?.angular?e.framework="Angular":h.dependencies?.express?e.framework="Express":h.dependencies?.fastify&&(e.framework="Fastify")}catch{}let a=A(t,"pyproject.toml");O(a)&&(e.language="Python");let o=A(t,"go.mod");O(o)&&(e.language="Go");let p=A(t,"Cargo.toml");O(p)&&(e.language="Rust"),O(s)&&(e.language=e.language||"JavaScript/TypeScript");let d=[];function l(h,y,u){if(u>i)return;let _;try{_=U(h,{withFileTypes:!0})}catch{return}let v=_.filter(f=>!f.name.startsWith(".")&&f.name!=="node_modules"&&f.name!=="__pycache__"&&f.name!=="dist"&&f.name!=="build"&&f.name!==".git").sort((f,g)=>f.isDirectory()!==g.isDirectory()?f.isDirectory()?-1:1:f.name.localeCompare(g.name));for(let f of v){let g=f.isDirectory();d.push(`${y}${g?"\u{1F4C1}":"\u{1F4C4}"} ${f.name}`),g&&u<i&&l(A(h,f.name),`${y} `,u+1)}}l(t,"",1),e.tree=d.slice(0,80),d.length>80&&(e.treeTruncated=!0);let c=["README.md","README.rst","src/App.tsx","src/App.jsx","src/App.js","src/routes.tsx","src/routes.js","app/routes.tsx","app/routes.js","src/index.tsx","src/index.ts","src/main.tsx","src/main.ts","pages/_app.tsx","pages/_app.js","app/layout.tsx","docker-compose.yml","Dockerfile",".env.example"];return e.keyFilesFound=c.filter(h=>O(A(t,h))),JSON.stringify(e)}import{existsSync as Z}from"fs";import{fileURLToPath as X}from"url";import{dirname as ee,resolve as te}from"path";import{resolveIntegrationToken as C}from"@zibby/core/backend-client.js";var L=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",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),be=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",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"},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 re(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let m=ee(X(import.meta.url)),n=te(m,"..","bin","mcp-skill.mjs");return Z(n)?n:null}async function b(m,n={}){let{token:r}=await C("github"),i=m.startsWith("https://")?m:`https://api.github.com${m}`,t={Authorization:`Bearer ${r}`,Accept:n.accept||"application/vnd.github.v3+json","User-Agent":"Zibby-App",...n.body?{"Content-Type":"application/json"}:{}},e=await fetch(i,{method:n.method||"GET",headers:t,body:n.body?JSON.stringify(n.body):void 0});if(!e.ok){let s=await e.text().catch(()=>"");throw new Error(`GitHub API ${e.status}: ${s.slice(0,300)}`)}return n.raw?e.text():e.json()}var q={id:"github",serverName:"github",allowedTools:["mcp__github__*"],requiresIntegration:L.GITHUB,envKeys:["GITHUB_TOKEN"],description:"GitHub \u2014 issues, PRs, commits, code search, file reading",promptFragment:`## GitHub (connected)
12
+ 5. Build well-informed test specs and save them to files before running tests`,resolve(){return null},async handleToolCall(m,n,r){let i=r?.options?.workspace||process.cwd();try{switch(m){case"git_checkout":return await Q(n,i);case"git_list_repos":return V(n,i);case"git_explore":return Y(n,i);default:return JSON.stringify({error:`Unknown tool: ${m}`})}}catch(t){return JSON.stringify({error:t.message})}},tools:[{name:"git_checkout",description:"Clone a git repository locally (or pull latest if already cloned). Auto-authenticates with GitHub/GitLab tokens if available.",input_schema:{type:"object",properties:{url:{type:"string",description:'Repository URL (e.g. "https://github.com/org/repo" or "org/repo" shorthand for GitHub)'},branch:{type:"string",description:"Branch to checkout (default: repo default branch)"},shallow:{type:"boolean",description:"Shallow clone with depth 1 (default: true, faster)"},name:{type:"string",description:"Local directory name override (default: repo name from URL)"}},required:["url"]}},{name:"git_list_repos",description:"List locally cloned repositories",input_schema:{type:"object",properties:{}}},{name:"git_explore",description:"Quick structural overview of a cloned repo: key files, package.json info, directory tree (top 2 levels), detected framework/language",input_schema:{type:"object",properties:{repo:{type:"string",description:"Repo name (as listed by git_list_repos)"},depth:{type:"number",description:"Directory tree depth (default: 2)"}},required:["repo"]}}]};async function Q(m,n){let{url:r,branch:i,shallow:t=!0,name:e}=m;!r.includes("://")&&!r.startsWith("git@")&&(r=`https://github.com/${r}`);let s=e||W(r.replace(/\.git$/,"")),a=G(n,E);F(a,{recursive:!0});let o=A(a,s),p=r,d=process.env.GITHUB_TOKEN,l=process.env.GITLAB_TOKEN,c=process.env.GITLAB_URL;if(r.includes("github.com")&&d)p=r.replace("https://github.com",`https://x-access-token:${d}@github.com`);else if(l&&c)try{let u=new URL(c).host;r.includes(u)&&(p=r.replace(`https://${u}`,`https://oauth2:${l}@${u}`))}catch{}if(O(A(o,".git"))){let u=i?`git -C "${o}" fetch origin ${i} && git -C "${o}" checkout ${i} && git -C "${o}" pull origin ${i}`:`git -C "${o}" pull`;await I(u,n);let _=await I(`git -C "${o}" log -1 --format="%h %s"`,n);return JSON.stringify({action:"updated",repo:s,path:o,branch:i||"default",head:_})}let h=["git","clone"];t&&h.push("--depth","1"),i&&h.push("--branch",i),h.push(`"${p}"`,`"${o}"`),await I(h.join(" "),n);let y=await I(`git -C "${o}" log -1 --format="%h %s"`,n);return JSON.stringify({action:"cloned",repo:s,path:o,branch:i||"default",shallow:t,head:y})}function V(m,n){let r=G(n,E);if(!O(r))return JSON.stringify({repos:[],message:"No repos cloned yet"});let i=[];for(let t of U(r,{withFileTypes:!0})){if(!t.isDirectory())continue;let e=A(r,t.name);if(!O(A(e,".git")))continue;let s=K(e);i.push({name:t.name,path:e,lastModified:s.mtime.toISOString()})}return JSON.stringify({repos:i,total:i.length,directory:r})}function Y(m,n){let{repo:r,depth:i=2}=m,t=G(n,E,r);if(!O(t))return JSON.stringify({error:`Repo not found: ${r}. Run git_checkout first.`});let e={repo:r,path:t},s=A(t,"package.json");if(O(s))try{let h=JSON.parse(z(s,"utf-8"));e.packageJson={name:h.name,version:h.version,scripts:h.scripts?Object.keys(h.scripts):[],dependencies:h.dependencies?Object.keys(h.dependencies).slice(0,30):[],devDependencies:h.devDependencies?Object.keys(h.devDependencies).slice(0,20):[]},h.dependencies?.react?e.framework="React":h.dependencies?.next?e.framework="Next.js":h.dependencies?.vue?e.framework="Vue":h.dependencies?.angular?e.framework="Angular":h.dependencies?.express?e.framework="Express":h.dependencies?.fastify&&(e.framework="Fastify")}catch{}let a=A(t,"pyproject.toml");O(a)&&(e.language="Python");let o=A(t,"go.mod");O(o)&&(e.language="Go");let p=A(t,"Cargo.toml");O(p)&&(e.language="Rust"),O(s)&&(e.language=e.language||"JavaScript/TypeScript");let d=[];function l(h,y,u){if(u>i)return;let _;try{_=U(h,{withFileTypes:!0})}catch{return}let v=_.filter(f=>!f.name.startsWith(".")&&f.name!=="node_modules"&&f.name!=="__pycache__"&&f.name!=="dist"&&f.name!=="build"&&f.name!==".git").sort((f,g)=>f.isDirectory()!==g.isDirectory()?f.isDirectory()?-1:1:f.name.localeCompare(g.name));for(let f of v){let g=f.isDirectory();d.push(`${y}${g?"\u{1F4C1}":"\u{1F4C4}"} ${f.name}`),g&&u<i&&l(A(h,f.name),`${y} `,u+1)}}l(t,"",1),e.tree=d.slice(0,80),d.length>80&&(e.treeTruncated=!0);let c=["README.md","README.rst","src/App.tsx","src/App.jsx","src/App.js","src/routes.tsx","src/routes.js","app/routes.tsx","app/routes.js","src/index.tsx","src/index.ts","src/main.tsx","src/main.ts","pages/_app.tsx","pages/_app.js","app/layout.tsx","docker-compose.yml","Dockerfile",".env.example"];return e.keyFilesFound=c.filter(h=>O(A(t,h))),JSON.stringify(e)}import{existsSync as Z}from"fs";import{fileURLToPath as X}from"url";import{dirname as ee,resolve as te}from"path";import{resolveIntegrationToken as C}from"@zibby/core/backend-client.js";var L=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"}),be=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",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 re(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let m=ee(X(import.meta.url)),n=te(m,"..","bin","mcp-skill.mjs");return Z(n)?n:null}async function b(m,n={}){let{token:r}=await C("github"),i=m.startsWith("https://")?m:`https://api.github.com${m}`,t={Authorization:`Bearer ${r}`,Accept:n.accept||"application/vnd.github.v3+json","User-Agent":"Zibby-App",...n.body?{"Content-Type":"application/json"}:{}},e=await fetch(i,{method:n.method||"GET",headers:t,body:n.body?JSON.stringify(n.body):void 0});if(!e.ok){let s=await e.text().catch(()=>"");throw new Error(`GitHub API ${e.status}: ${s.slice(0,300)}`)}return n.raw?e.text():e.json()}var q={id:"github",serverName:"github",allowedTools:["mcp__github__*"],requiresIntegration:L.GITHUB,envKeys:["GITHUB_TOKEN"],description:"GitHub \u2014 issues, PRs, commits, code search, file reading",promptFragment:`## GitHub (connected)
13
13
  You have access to the user's GitHub repositories. Available tools:
14
14
 
15
15
  ### Discovery
package/dist/github.js CHANGED
@@ -1,4 +1,4 @@
1
- import{existsSync as k}from"fs";import{fileURLToPath as E}from"url";import{dirname as I,resolve as G}from"path";import{resolveIntegrationToken as q}from"@zibby/core/backend-client.js";var A=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",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),L=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"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});function J(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let b=I(E(import.meta.url)),a=G(b,"..","bin","mcp-skill.mjs");return k(a)?a:null}async function u(b,a={}){let{token:t}=await q("github"),n=b.startsWith("https://")?b:`https://api.github.com${b}`,i={Authorization:`Bearer ${t}`,Accept:a.accept||"application/vnd.github.v3+json","User-Agent":"Zibby-App",...a.body?{"Content-Type":"application/json"}:{}},e=await fetch(n,{method:a.method||"GET",headers:i,body:a.body?JSON.stringify(a.body):void 0});if(!e.ok){let r=await e.text().catch(()=>"");throw new Error(`GitHub API ${e.status}: ${r.slice(0,300)}`)}return a.raw?e.text():e.json()}var F={id:"github",serverName:"github",allowedTools:["mcp__github__*"],requiresIntegration:A.GITHUB,envKeys:["GITHUB_TOKEN"],description:"GitHub \u2014 issues, PRs, commits, code search, file reading",promptFragment:`## GitHub (connected)
1
+ import{existsSync as k}from"fs";import{fileURLToPath as E}from"url";import{dirname as I,resolve as G}from"path";import{resolveIntegrationToken as q}from"@zibby/core/backend-client.js";var A=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"}),H=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});function J(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let b=I(E(import.meta.url)),a=G(b,"..","bin","mcp-skill.mjs");return k(a)?a:null}async function u(b,a={}){let{token:t}=await q("github"),n=b.startsWith("https://")?b:`https://api.github.com${b}`,i={Authorization:`Bearer ${t}`,Accept:a.accept||"application/vnd.github.v3+json","User-Agent":"Zibby-App",...a.body?{"Content-Type":"application/json"}:{}},e=await fetch(n,{method:a.method||"GET",headers:i,body:a.body?JSON.stringify(a.body):void 0});if(!e.ok){let r=await e.text().catch(()=>"");throw new Error(`GitHub API ${e.status}: ${r.slice(0,300)}`)}return a.raw?e.text():e.json()}var F={id:"github",serverName:"github",allowedTools:["mcp__github__*"],requiresIntegration:A.GITHUB,envKeys:["GITHUB_TOKEN"],description:"GitHub \u2014 issues, PRs, commits, code search, file reading",promptFragment:`## GitHub (connected)
2
2
  You have access to the user's GitHub repositories. Available tools:
3
3
 
4
4
  ### Discovery