@zibby/skills 0.1.81 → 0.1.83
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 +1 -1
- package/dist/code-scan.d.ts +24 -0
- package/dist/code-scan.js +5 -5
- package/dist/discord.js +3 -3
- package/dist/figma.js +2 -2
- package/dist/git-write.js +7 -7
- package/dist/git.js +2 -2
- package/dist/github.js +3 -3
- package/dist/gitlab.js +3 -3
- package/dist/googleDocs.js +5 -5
- package/dist/hubspot.d.ts +381 -0
- package/dist/hubspot.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +145 -145
- package/dist/integrations.d.ts +6 -0
- package/dist/integrations.js +1 -1
- package/dist/jira.js +5 -5
- package/dist/lark.js +1 -1
- package/dist/larkDocs.js +3 -3
- package/dist/linear.js +3 -3
- package/dist/llm-billing.js +1 -1
- package/dist/notion.js +5 -5
- package/dist/opendesign.js +2 -2
- package/dist/package.json +2 -1
- package/dist/plane.js +1 -1
- package/dist/sentry.js +2 -2
- package/dist/slack.js +2 -2
- package/dist/trackers/github-adapter.js +3 -3
- package/dist/trackers/index.js +10 -10
- package/dist/trackers/jira-adapter.js +7 -7
- package/dist/trackers/linear-adapter.js +7 -7
- package/package.json +2 -1
package/dist/chat-notify.js
CHANGED
|
@@ -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/code-scan.d.ts
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the semgrep-core `-targets` document for a set of RELATIVE file paths. This
|
|
3
|
+
* is the internal ATD wire format semgrep-core expects (verified against the pinned
|
|
4
|
+
* engine): a top-level variant ["Targets", [ ["CodeTarget", { path:{fpath,ppath},
|
|
5
|
+
* analyzer, products }], … ]]. One entry per file we can map to a language; files
|
|
6
|
+
* of other languages are dropped. EXPORTED for unit-testing the shape.
|
|
7
|
+
*/
|
|
8
|
+
export function buildSemgrepTargets(relFiles: any): (string | (string | {
|
|
9
|
+
path: {
|
|
10
|
+
fpath: string;
|
|
11
|
+
ppath: string;
|
|
12
|
+
};
|
|
13
|
+
analyzer: any;
|
|
14
|
+
products: string[];
|
|
15
|
+
})[][])[];
|
|
16
|
+
/**
|
|
17
|
+
* Parse `semgrep-core … -json` output into the SAME finding shape as parseOxlint
|
|
18
|
+
* ({ file, line, severity, rule, message }). The engine prints progress DOTS
|
|
19
|
+
* (".\n") before the JSON object, so we start at the first '{'. Shape (verified
|
|
20
|
+
* against semgrep-core 1.169.0): { results: [ { check_id, path, start:{line,col},
|
|
21
|
+
* end, extra:{ message, severity? } } ], errors, paths }. Best-effort: empty /
|
|
22
|
+
* unparseable → []. EXPORTED for direct unit-testing of the shape assumption.
|
|
23
|
+
*/
|
|
24
|
+
export function parseSemgrep(stdout: any): any;
|
|
1
25
|
/**
|
|
2
26
|
* Parse `oxlint --format json` output. oxlint emits ONE JSON object:
|
|
3
27
|
* { diagnostics: [ { message, code:"eslint(no-cond-assign)", severity:"warning"|"error",
|
package/dist/code-scan.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{spawnSync as
|
|
2
|
-
`)){let i=t.trim();if(!i)continue;let
|
|
1
|
+
import{spawnSync as O}from"node:child_process";import{existsSync as u,readdirSync as _,statSync as w,writeFileSync as S,mkdirSync as b}from"node:fs";import{dirname as h,extname as m,join as l,relative as T,resolve as d}from"node:path";import{tmpdir as v}from"node:os";import{fileURLToPath as P}from"node:url";import{SKILL_META as C}from"@zibby/skill-ids";import{binPath as I}from"@zibby/bin-oxlint";import{binPath as F}from"@zibby/bin-semgrep";function L(){if(process.env.OXLINT_BIN)return process.env.OXLINT_BIN;try{let e=I();if(e&&u(e))return e}catch{}return"oxlint"}function B(){if(process.env.SEMGREP_CORE_BIN)return process.env.SEMGREP_CORE_BIN;try{let e=F();if(e&&u(e))return e}catch{}return"semgrep-core"}function D(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let e=h(P(import.meta.url)),n=d(e,"..","bin","mcp-skill.mjs");return u(n)?n:null}var E=new Set(["node_modules",".git","dist","build","out","vendor","target",".venv","venv","__pycache__",".next",".turbo","coverage",".zibby"]),G=400,J={plugins:["react","typescript","unicorn","oxc"],categories:{correctness:"error",suspicious:"warn"},rules:{"react/react-in-jsx-scope":"off","react/jsx-max-depth":"off","react/no-array-index-key":"off","react/jsx-key":"error","react/no-unknown-property":"error","no-unused-vars":"warn",eqeqeq:"warn"}},M=[".oxlintrc.json",".oxlintrc","oxlint.json"],p=null;function z(){if(p&&u(p))return p;try{let e=l(v(),"zibby-code-scan");b(e,{recursive:!0});let n=l(e,"oxlintrc.curated.json");return S(n,JSON.stringify(J),"utf-8"),p=n,n}catch{return null}}var N={".java":"java",".py":"python",".go":"go",".rb":"ruby",".php":"php"},x=Object.keys(N),j={rules:[{id:"zibby-java-command-injection",languages:["java"],severity:"ERROR",message:"Command execution (Runtime.exec / ProcessBuilder) \u2014 command injection risk if the argument is attacker-influenced. Validate/allow-list the input or avoid a shell.",patterns:[{"pattern-either":[{pattern:"Runtime.getRuntime().exec(...)"},{pattern:"new ProcessBuilder(...)"}]}]},{id:"zibby-python-subprocess-shell",languages:["python"],severity:"ERROR",message:"subprocess call with shell=True \u2014 command injection risk. Pass an argv list and shell=False.",pattern:"subprocess.$F(..., shell=True, ...)"},{id:"zibby-python-yaml-load",languages:["python"],severity:"WARNING",message:"yaml.load without a safe loader can instantiate arbitrary Python objects. Use yaml.safe_load.",pattern:"yaml.load(...)"},{id:"zibby-go-command-injection",languages:["go"],severity:"WARNING",message:"os/exec with a non-constant command \u2014 verify the value is not attacker-controlled (command injection).",pattern:"exec.Command($CMD, ...)"},{id:"zibby-ruby-command-injection",languages:["ruby"],severity:"ERROR",message:"Shell/eval execution (system / eval) \u2014 command injection risk if the argument is attacker-influenced.",patterns:[{"pattern-either":[{pattern:"system(...)"},{pattern:"eval(...)"}]}]},{id:"zibby-php-command-injection",languages:["php"],severity:"ERROR",message:"Shell/eval execution (system / exec / shell_exec) \u2014 command injection risk if the argument is attacker-influenced.",patterns:[{"pattern-either":[{pattern:"system(...);"},{pattern:"exec(...);"},{pattern:"shell_exec(...);"}]}]}]},H=[".semgrep.yml",".semgrep.yaml","semgrep.yml","semgrep.yaml"],f=null;function U(){if(f&&u(f))return f;try{let e=l(v(),"zibby-code-scan");b(e,{recursive:!0});let n=l(e,"semgrep.curated.rules.json");return S(n,JSON.stringify(j),"utf-8"),f=n,n}catch{return null}}function $(e){return H.map(s=>l(e,s)).find(s=>u(s))||U()}function W(e){let n=[];for(let s of Array.isArray(e)?e:[]){if(typeof s!="string"||!s)continue;let t=N[m(s).toLowerCase()];if(!t)continue;let i=s.replace(/\\/g,"/");n.push(["CodeTarget",{path:{fpath:i,ppath:`/${i.replace(/^\/+/,"")}`},analyzer:t,products:["sast"]}])}return["Targets",n]}var X=0;function q(e){let n=W(e),s=n[1].length,t=l(v(),"zibby-code-scan");b(t,{recursive:!0});let i=l(t,`semgrep.targets.${process.pid}.${X++}.json`);return S(i,JSON.stringify(n),"utf-8"),{path:i,count:s}}function R(e){let n=typeof e=="string"?e.toUpperCase():"";return n==="ERROR"?"error":n==="INFO"||n==="INVENTORY"||n==="EXPERIMENT"?"info":"warning"}var K=Object.fromEntries(j.rules.map(e=>[e.id,e.severity]));function V(e,n){if(n)return R(n);let s=K[e];return s?R(s):"warning"}function Y(e){let n=String(e||""),s=n.indexOf("{");if(s<0)return[];let t;try{t=JSON.parse(n.slice(s))}catch{return[]}return(t&&Array.isArray(t.results)?t.results:[]).map(r=>{if(!r||typeof r!="object")return null;let o=r.start&&typeof r.start=="object"?r.start:{},a=r.extra&&typeof r.extra=="object"?r.extra:{};return{file:r.path||"",line:Number.isFinite(o.line)?o.line:"",severity:V(r.check_id,a.severity),rule:r.check_id||"",message:(a.message||"").trim()}}).filter(r=>r&&(r.file||r.message))}function Q(e,n,s=4e3){let t=new Set(n.map(o=>o.toLowerCase())),i=[e],r=0;for(;i.length;){let o=i.pop(),a;try{a=_(o,{withFileTypes:!0})}catch{continue}for(let c of a){if(++r>s)return!1;if(c.isDirectory())!E.has(c.name)&&!c.name.startsWith(".")&&i.push(l(o,c.name));else if(c.isFile()&&t.has(m(c.name).toLowerCase()))return!0}}return!1}function Z(e){let n=String(e||"").trim();if(!n)return[];let s;try{s=JSON.parse(n)}catch{return[]}return(Array.isArray(s)?s:s&&Array.isArray(s.diagnostics)?s.diagnostics:[]).map(i=>{if(!i||typeof i!="object")return null;let r=Array.isArray(i.labels)&&i.labels.length?i.labels[0]:null,o=r&&r.span?r.span:null;return{file:i.filename||o&&o.filename||"",line:o&&Number.isFinite(o.line)?o.line:"",severity:i.severity||"warning",rule:i.code||"",message:i.message||""}}).filter(i=>i&&(i.file||i.message))}function ee(e){let n=String(e||"").trim();if(!n)return[];let s;try{s=JSON.parse(n)}catch{return[]}return Array.isArray(s)?s.map(t=>t&&typeof t=="object"?{file:t.filename||"",line:t.location&&Number.isFinite(t.location.row)?t.location.row:"",severity:"warning",rule:t.code||"",message:t.message||""}:null).filter(t=>t&&(t.file||t.message)):[]}function te(e){let n=String(e||"").trim();if(!n)return[];let s=[];for(let t of n.split(`
|
|
2
|
+
`)){let i=t.trim();if(!i)continue;let r;try{r=JSON.parse(i)}catch{continue}if(!r||typeof r!="object")continue;let o=r.location||{};s.push({file:o.file||"",line:Number.isFinite(o.line)?o.line:"",severity:r.severity||"warning",rule:r.code||"",message:r.message||""})}return s.filter(t=>t.file||t.message)}var ne=[{id:"oxlint",detect:e=>u(l(e,"package.json")),langs:[".ts",".tsx",".js",".jsx",".mjs",".cjs"],bin:()=>L(),args:(e,n={})=>{let s=n.baseDir||".",i=M.some(o=>u(l(s,o)))?null:z();return["--format","json",...i?["--config",i]:[],...e]},parse:Z},{id:"semgrep",detect:e=>Q(e,x),langs:x,bin:()=>B(),args:(e,n={})=>{let s=n.baseDir||".",t=$(s),{path:i}=q(e);return[...t?["-rules",t]:[],"-targets",i,"-json"]},parse:Y},{id:"ruff",detect:e=>u(l(e,"pyproject.toml"))||u(l(e,"requirements.txt"))||u(l(e,"setup.py")),langs:[".py"],bin:()=>process.env.RUFF_BIN||"ruff",args:e=>["check","--output-format","json",...e],parse:ee},{id:"staticcheck",detect:e=>u(l(e,"go.mod")),langs:[".go"],bin:()=>process.env.STATICCHECK_BIN||"staticcheck",args:e=>["-f","json",...e],parse:te}];function re(e,n,s){let t=[],i=new Set(n.map(o=>o.toLowerCase())),r=[e];for(;r.length&&t.length<s;){let o=r.pop(),a;try{a=_(o,{withFileTypes:!0})}catch{continue}for(let c of a){if(t.length>=s)break;c.isDirectory()?!E.has(c.name)&&!c.name.startsWith(".")&&r.push(l(o,c.name)):c.isFile()&&i.has(m(c.name).toLowerCase())&&t.push(l(o,c.name))}}return t}function se(e,n,s){let t=s.map(a=>T(n,a)).filter(Boolean);if(!t.length)return{scanner:e.id,skipped:"no matching files"};let i=e.bin(),r=O(i,e.args(t,{baseDir:n}),{cwd:n,encoding:"utf-8",timeout:180*1e3,maxBuffer:32*1024*1024});if(r.error){let a=r.error.code==="ENOENT"?`binary not installed (${i})`:String(r.error.message||r.error);return{scanner:e.id,skipped:a}}let o=[];try{let a=e.parse(r.stdout,r.stderr,r.status);o=Array.isArray(a)?a.filter(Boolean):[]}catch{o=[]}return{scanner:e.id,filesScanned:t.length,findings:o}}var me={id:"code-scan",serverName:"code_scan",meta:C["code-scan"],allowedTools:["mcp__code_scan__*"],description:"Code scan \u2014 run the RIGHT deterministic linter/analyzer for a checked-out repo (auto-detects the stack: JS/TS\u2192oxlint; Java/Python/Go/Ruby/PHP\u2192semgrep) and return structured findings. Fully local; the code never leaves the box.",promptFragment:`## Code Scan (deterministic linter, auto-detects the stack)
|
|
3
3
|
After you've cloned the repo, call \`scan_code\` to get DETERMINISTIC linter
|
|
4
|
-
findings for WHATEVER stack this repo is \u2014 it auto-detects (JS/TS\u2192oxlint
|
|
5
|
-
|
|
4
|
+
findings for WHATEVER stack this repo is \u2014 it auto-detects (JS/TS\u2192oxlint;
|
|
5
|
+
Java/Python/Go/Ruby/PHP\u2192semgrep) and runs the matching tool. Pass \`files\` (the changed files, ideal for
|
|
6
6
|
a review) or \`dir\` (a directory to scan). Findings are GROUND-TRUTH CANDIDATES:
|
|
7
7
|
triage them for THIS change, verify each in context (false positives exist \u2014
|
|
8
8
|
trace before asserting), fold noise, and turn the real ones into inline
|
|
9
|
-
suggestions. Don't hand-lint what the tool already covers, and don't re-run it.`,resolve(){let e=
|
|
9
|
+
suggestions. Don't hand-lint what the tool already covers, and don't re-run it.`,resolve(){let e=D();return e?{type:"stdio",command:"node",args:[e,"../dist/code-scan.js","codeScanSkill"],env:{},description:this.description,alwaysLoad:!0}:{command:null,args:[],env:{},description:this.description}},async handleToolCall(e,n){if(e!=="scan_code")return JSON.stringify({error:`Unknown tool: ${e}`});try{let s=Array.isArray(n?.files)?n.files.filter(a=>typeof a=="string"&&a.trim()):null,t;if(n?.dir&&typeof n.dir=="string"?t=d(n.dir):s&&s.length?t=ie(s.map(a=>d(a))):t=process.cwd(),!u(t)||!w(t).isDirectory())return JSON.stringify({error:`dir does not exist or is not a directory: ${t}`});let i=s?s.map(a=>d(t,a)):null,r=[],o=0;for(let a of ne){let c=!1;try{c=!!a.detect(t)}catch{c=!1}if(!c)continue;let k=new Set(a.langs.map(y=>y.toLowerCase())),A=i?i.filter(y=>k.has(m(y).toLowerCase())):re(t,a.langs,G),g=se(a,t,A);Array.isArray(g.findings)&&(o+=g.findings.length),r.push(g)}return r.length?JSON.stringify({ok:!0,baseDir:t,totalFindings:o,scanners:r}):JSON.stringify({ok:!0,baseDir:t,scanners:[],totalFindings:0,note:"No known stack detected (no package.json / pyproject / go.mod \u2026). Review by hand."})}catch(s){return JSON.stringify({error:`scan_code failed: ${s.message}`})}},tools:[{name:"scan_code",description:"Run the right deterministic linter for a checked-out repo and return structured findings. Auto-detects the stack (JS/TS \u2192 oxlint; Java/Python/Go/Ruby/PHP \u2192 semgrep OSS) and runs each matching tool, scoped to files in its languages. Pass `files` (e.g. the changed files of the PR \u2014 recommended for a review) OR `dir` (a directory to scan). Returns { scanners: [ { scanner, findings: [ { file, line, severity, rule, message } ] } ] }. Findings are CANDIDATES \u2014 verify each in context before asserting. Best-effort: a stack whose linter is not installed is skipped with a note.",input_schema:{type:"object",properties:{dir:{type:"string",description:"Absolute path to the checked-out repo (or subdirectory) to scan. Defaults to the current working directory."},files:{type:"array",items:{type:"string"},description:"Explicit list of files to scan (paths relative to `dir`, or absolute). Best for a code review \u2014 pass the PR's changed files. When omitted, the whole `dir` is walked (bounded)."}}}}]};function ie(e){if(!e.length)return process.cwd();if(e.length===1)return h(e[0]);let n=e.map(r=>r.split("/")),s=n[0],t=[];for(let r=0;r<s.length;r++){let o=s[r];if(n.every(a=>a[r]===o))t.push(o);else break}let i=t.join("/");return i&&u(i)&&w(i).isDirectory()?i:h(e[0])}export{ne as SCANNERS,W as buildSemgrepTargets,me as codeScanSkill,Z as parseOxlint,Y as parseSemgrep};
|
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
|
|
2
|
-
`),a=
|
|
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
|
|
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
|
|
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
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
|
|
@@ -29,4 +29,4 @@ You have access to the user's Figma files via the Figma REST API. Every tool tha
|
|
|
29
29
|
|
|
30
30
|
### Notes
|
|
31
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=O();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:o}=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(o!=null){let t=Number(o);!Number.isNaN(t)&&t>0&&(s+=`?depth=${t}`)}let r=await f(s),a=(r.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:r.name,lastModified:r.lastModified,version:r.version,editorType:r.editorType,role:r.role,pages:a})}case"figma_get_nodes":{let i=g((n||{}).fileKey),{ids:o,depth:s}=n||{};if(!i)return JSON.stringify({error:"fileKey is required (a file key or a figma.com URL)"});let r=u(o);if(!r)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",r),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:o,depth:s}=n||{};if(!i)return JSON.stringify({error:"file is required (a file key or a figma.com URL)"});let r=u(o);if(!r)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",r),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:o,scale:s,format:r,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(o);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(r||"").toLowerCase())?String(r).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 o=await f(`/v1/files/${encodeURIComponent(i)}/images`);return JSON.stringify({images:o.meta?.images??o.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(r=>({id:r.id,label:r.label,description:r.description,createdAt:r.created_at,user:r.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(r=>({id:r.id,message:r.message,user:r.user?.handle,createdAt:r.created_at,resolvedAt:r.resolved_at||null,parentId:r.parent_id||null}));return JSON.stringify({count:s.length,comments:s})}case"figma_post_comment":{let i=g((n||{}).file),{message:o,comment_id:s,node_id:r,x:a,y:t}=n||{};if(!i)return JSON.stringify({error:"file is required (a file key or a figma.com URL)"});if(!o)return JSON.stringify({error:"message is required"});let c={message:o};s&&(c.comment_id=s),r?c.client_meta={node_id:u(r),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 o=await f(`/v1/teams/${encodeURIComponent(i)}/projects`);return JSON.stringify({name:o.name,projects:o.projects||[]})}case"figma_get_project_files":{let{project_id:i,branch_data:o}=n||{};if(!i)return JSON.stringify({error:"project_id is required"});let s=new URLSearchParams;o&&s.set("branch_data","true");let r=s.toString(),a=await f(`/v1/projects/${encodeURIComponent(i)}/files${r?`?${r}`:""}`);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};
|
|
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};
|