diff --git a/.gitignore b/.gitignore index 6f6d9a4..3568f31 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ dist/ *.log .openskills-temp/ .claude/ +.agent/ +vscode-extension/*.vsix diff --git a/package-lock.json b/package-lock.json index a168c6d..2bc0e9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@inquirer/prompts": "^7.9.0", "chalk": "^5.6.2", "commander": "^12.1.0", - "ora": "^9.0.0" + "nanospinner": "^1.2.2" }, "bin": { "openskills": "dist/cli.js" @@ -1420,6 +1420,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1545,33 +1546,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.3.0.tgz", - "integrity": "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ==", - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -1804,22 +1778,10 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -1862,30 +1824,6 @@ "node": ">=8" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1956,22 +1894,6 @@ "dev": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", - "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -1989,18 +1911,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -2087,6 +1997,15 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nanospinner": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/nanospinner/-/nanospinner-1.2.2.tgz", + "integrity": "sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2097,60 +2016,6 @@ "node": ">=0.10.0" } }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.0.0.tgz", - "integrity": "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==", - "license": "MIT", - "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.2.2", - "string-width": "^8.1.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -2196,7 +2061,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2342,22 +2206,6 @@ "node": ">=8" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/rollup": { "version": "4.52.5", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", @@ -2486,18 +2334,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -2566,6 +2402,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -3111,18 +2948,6 @@ "node": ">=8" } }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yoctocolors-cjs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", diff --git a/package.json b/package.json index e6240d8..609d415 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,15 @@ "version": "1.3.0", "description": "Universal skills loader for AI coding agents - install and load Anthropic SKILL.md format skills in any agent", "type": "module", - "main": "./dist/cli.js", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./cli": "./dist/cli.js" + }, "bin": { "openskills": "./dist/cli.js" }, @@ -49,7 +57,7 @@ "@inquirer/prompts": "^7.9.0", "chalk": "^5.6.2", "commander": "^12.1.0", - "ora": "^9.0.0" + "nanospinner": "^1.2.2" }, "devDependencies": { "@types/node": "^24.9.1", @@ -57,4 +65,4 @@ "typescript": "^5.9.3", "vitest": "^4.0.3" } -} +} \ No newline at end of file diff --git a/src/commands/install.ts b/src/commands/install.ts index dc35b18..a97900b 100644 --- a/src/commands/install.ts +++ b/src/commands/install.ts @@ -1,9 +1,9 @@ import { readFileSync, readdirSync, existsSync, mkdirSync, rmSync, cpSync, statSync } from 'fs'; import { join, basename, resolve } from 'path'; import { homedir } from 'os'; -import { execSync } from 'child_process'; +import { spawn } from 'child_process'; import chalk from 'chalk'; -import ora from 'ora'; +import { createSpinner } from 'nanospinner'; import { checkbox, confirm } from '@inquirer/prompts'; import { ExitPromptError } from '@inquirer/core'; import { hasValidFrontmatter, extractYamlField } from '../utils/yaml.js'; @@ -97,17 +97,33 @@ export async function installSkill(source: string, options: InstallOptions): Pro mkdirSync(tempDir, { recursive: true }); try { - const spinner = ora('Cloning repository...').start(); + const spinner = createSpinner('Cloning repository...').start(); try { - execSync(`git clone --depth 1 --quiet "${repoUrl}" "${tempDir}/repo"`, { - stdio: 'pipe', + await new Promise((resolve, reject) => { + const gitProcess = spawn('git', ['clone', '--depth', '1', '--quiet', repoUrl, `${tempDir}/repo`], { + stdio: 'pipe', + }); + let stderr = ''; + gitProcess.stderr?.on('data', (data) => { + stderr += data.toString(); + }); + gitProcess.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(stderr.trim() || `git clone failed with code ${code}`)); + } + }); + gitProcess.on('error', (err) => { + reject(err); + }); }); - spinner.succeed('Repository cloned'); + spinner.success({ text: 'Repository cloned' }); } catch (error) { - spinner.fail('Failed to clone repository'); - const err = error as { stderr?: Buffer }; - if (err.stderr) { - console.error(chalk.dim(err.stderr.toString().trim())); + spinner.error({ text: 'Failed to clone repository' }); + const err = error as Error; + if (err.message) { + console.error(chalk.dim(err.message)); } console.error(chalk.yellow('\nTip: For private repos, ensure git SSH keys or credentials are configured')); process.exit(1); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7db313e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,26 @@ +/** + * OpenSkills Library Exports + * + * This module exports utility functions and types for external consumption, + * enabling VSCode extensions and other tools to reuse openskills functionality. + */ + +// YAML utilities +export { extractYamlField, hasValidFrontmatter } from './utils/yaml.js'; + +// Directory utilities +export { getSkillsDir, getSearchDirs } from './utils/dirs.js'; + +// Skills discovery +export { findAllSkills, findSkill } from './utils/skills.js'; + +// AGENTS.md manipulation +export { + parseCurrentSkills, + generateSkillsXml, + replaceSkillsSection, + removeSkillsSection, +} from './utils/agents-md.js'; + +// Types +export type { Skill, SkillLocation } from './types.js'; diff --git a/tsup.config.ts b/tsup.config.ts index 92af3ef..728950e 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -3,15 +3,17 @@ import { defineConfig } from 'tsup'; export default defineConfig({ entry: { cli: 'src/cli.ts', + index: 'src/index.ts', }, format: ['esm'], target: 'node18', outDir: 'dist', clean: true, sourcemap: false, - dts: false, + dts: true, splitting: false, treeshake: false, platform: 'node', skipNodeModulesBundle: true, // Don't bundle node_modules }); + diff --git a/vscode-extension/.gitignore b/vscode-extension/.gitignore new file mode 100644 index 0000000..99860fe --- /dev/null +++ b/vscode-extension/.gitignore @@ -0,0 +1,8 @@ +out +node_modules +.vscode-test +*.vsix +.cursor/* +.trae/* +.windsurf/* +.claude/ diff --git a/vscode-extension/.vscodeignore b/vscode-extension/.vscodeignore new file mode 100644 index 0000000..73b554b --- /dev/null +++ b/vscode-extension/.vscodeignore @@ -0,0 +1,19 @@ +.vscode +.vscode-test +node_modules +src/ +*.ts +!*.d.ts +tsconfig.json +*.map +.gitignore +# Avoid ignoring essential markdown files +!README.md +!CHANGELOG.md +!**/SKILL.md +esbuild.js +resources/image.png +resources/skills-icon.png +.trae +.cursor +.agent \ No newline at end of file diff --git a/vscode-extension/LICENSE b/vscode-extension/LICENSE new file mode 100644 index 0000000..c13f991 --- /dev/null +++ b/vscode-extension/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vscode-extension/README.md b/vscode-extension/README.md new file mode 100644 index 0000000..15fadd6 --- /dev/null +++ b/vscode-extension/README.md @@ -0,0 +1,52 @@ +# Agent Skills Manager + +AgentSkills multi-IDE management extension: browse and install skill repositories for Antigravity, CodeBuddy, Cursor, Qoder, Trae, Windsurf (and VS Code), and search a cloud catalog (~58K skills) from https://claude-plugins.dev/. + +![image](https://raw.githubusercontent.com/lasoons/AgentSkillsManager/refs/heads/main/resources/image.png) + +## Features + +- **Repository Management**: Add, remove, and switch branches of skill repositories +- **Skill Installation**: Install skills into the active IDE skills directory +- **Cloud Skills Search (~58K)**: Search the cloud catalog from https://claude-plugins.dev/ and install with one click/Enter +- **Multi-IDE Support**: Works with VSCode, Cursor, Trae, Antigravity, Qoder, Windsurf, and CodeBuddy +- **Active Skills Directory**: The local skills group shows which directory is active + +## Usage + +1. Open the **Agent Skills** panel in the Activity Bar ![icon](https://raw.githubusercontent.com/lasoons/AgentSkillsManager/refs/heads/main/resources/skills-icon.png) +2. Click **+** to add a skill repository (e.g., `https://github.com/anthropics/skills`) +3. Expand the repository to browse available skills +4. Check the skills you want, then click **Install** +5. Click the search icon to search cloud skills, then press **Enter** (or click **Install**) to download and install + +## Skill Collections + +Preset repositories bundled by default: + +| Repository | Description | +|------------|-------------| +| [anthropics/skills](https://github.com/anthropics/skills) | Official Anthropic skills collection | +| [openai/skills](https://github.com/openai/skills) | Official OpenAI skills catalog | +| [skillcreatorai/Ai-Agent-Skills](https://github.com/skillcreatorai/Ai-Agent-Skills) | Community skills collection | +| [obra/superpowers](https://github.com/obra/superpowers) | Superpowers skill collection | +| [ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills) | Curated awesome-claude-skills collection | + +For more repositories, see [heilcheng/awesome-agent-skills](https://github.com/heilcheng/awesome-agent-skills). + +## Configuration + +Skills are installed to the active skills directory in your workspace: +- **VSCode**: `.github/skills` +- **Cursor**: `.cursor/skills` +- **Trae**: `.trae/skills` +- **Antigravity**: `.agent/skills` +- **Qoder**: `.qoder/skills` +- **Windsurf**: `.windsurf/skills` +- **CodeBuddy**: `.codebuddy/skills` + +The extension also scans skills in hidden directories inside repositories (for example `.curated`, `.experimental`). + +## License + +MIT diff --git a/vscode-extension/README.zh-CN.md b/vscode-extension/README.zh-CN.md new file mode 100644 index 0000000..93902f6 --- /dev/null +++ b/vscode-extension/README.zh-CN.md @@ -0,0 +1,52 @@ +# Agent Skills Manager + +AgentSkills 多 IDE 管理扩展:用于在 Antigravity、CodeBuddy、Cursor、Qoder、Trae、Windsurf(以及 VS Code)中浏览与安装 skill 仓库,并支持从 https://claude-plugins.dev/ 搜索云端目录(约 58K skills)。 + +![image](https://raw.githubusercontent.com/lasoons/AgentSkillsManager/refs/heads/main/resources/image.png) + +## 功能 + +- **仓库管理**:添加、删除、切换 skill 仓库分支 +- **Skill 安装**:安装到当前 IDE 对应的 skills 目录 +- **云端 Skill 搜索(约 58K)**:从 https://claude-plugins.dev/ 的云端目录搜索,一键/回车安装 +- **多 IDE 支持**:支持 VSCode、Cursor、Trae、Antigravity、Qoder、Windsurf、CodeBuddy +- **激活目录标识**:在本地 skills 分组上标识当前 IDE 的激活目录 + +## 使用方法 + +1. 在 Activity Bar 打开 **Agent Skills** 面板 ![icon](https://raw.githubusercontent.com/lasoons/AgentSkillsManager/refs/heads/main/resources/skills-icon.png) +2. 点击 **+** 添加 skill 仓库(例如 `https://github.com/anthropics/skills`) +3. 展开仓库浏览可用 skills +4. 勾选需要的 skills,点击 **Install** +5. 点击搜索图标搜索云端 skills,然后按 **回车**(或点 **Install**)下载安装 + +## Skill 仓库推荐 + +扩展默认内置的预置仓库: + +| 仓库 | 说明 | +|------|------| +| [anthropics/skills](https://github.com/anthropics/skills) | Anthropic 官方 skills 集合 | +| [openai/skills](https://github.com/openai/skills) | OpenAI 官方 skills 目录 | +| [skillcreatorai/Ai-Agent-Skills](https://github.com/skillcreatorai/Ai-Agent-Skills) | 社区 skills 集合 | +| [obra/superpowers](https://github.com/obra/superpowers) | Superpowers skills 集合 | +| [ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills) | awesome-claude-skills 精选集合 | + +更多仓库可参考 [heilcheng/awesome-agent-skills](https://github.com/heilcheng/awesome-agent-skills)。 + +## 配置说明 + +Skills 会安装到工作区内“当前 IDE 激活的 skills 目录”: +- **VSCode**:`.github/skills` +- **Cursor**:`.cursor/skills` +- **Trae**:`.trae/skills` +- **Antigravity**:`.agent/skills` +- **Qoder**:`.qoder/skills` +- **Windsurf**:`.windsurf/skills` +- **CodeBuddy**:`.codebuddy/skills` + +仓库扫描会包含隐藏目录中的 skills(例如 `.curated`、`.experimental`)。 + +## License + +MIT diff --git a/vscode-extension/esbuild.js b/vscode-extension/esbuild.js new file mode 100644 index 0000000..77a4717 --- /dev/null +++ b/vscode-extension/esbuild.js @@ -0,0 +1,30 @@ +const esbuild = require('esbuild'); + +const production = process.argv.includes('--production'); + +async function main() { + const ctx = await esbuild.context({ + entryPoints: ['src/extension.ts'], + bundle: true, + format: 'cjs', + minify: production, + sourcemap: !production, + sourcesContent: false, + platform: 'node', + outfile: 'out/extension.js', + external: ['vscode'], + logLevel: 'info', + }); + + if (process.argv.includes('--watch')) { + await ctx.watch(); + } else { + await ctx.rebuild(); + await ctx.dispose(); + } +} + +main().catch(e => { + console.error(e); + process.exit(1); +}); diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json new file mode 100644 index 0000000..90c2321 --- /dev/null +++ b/vscode-extension/package-lock.json @@ -0,0 +1,2613 @@ +{ + "name": "agent-skills-manager", + "version": "0.6.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agent-skills-manager", + "version": "0.6.0", + "license": "MIT", + "dependencies": { + "adm-zip": "^0.5.12" + }, + "devDependencies": { + "@types/adm-zip": "^0.5.7", + "@types/node": "^20.0.0", + "@types/vscode": "^1.96.0", + "esbuild": "^0.24.0", + "npm-run-all": "^4.1.5", + "typescript": "^5.0.0" + }, + "engines": { + "vscode": "^1.96.0" + } + }, + "..": { + "version": "1.3.0", + "extraneous": true, + "license": "Apache-2.0", + "dependencies": { + "@inquirer/prompts": "^7.9.0", + "chalk": "^5.6.2", + "commander": "^12.1.0", + "nanospinner": "^1.2.2" + }, + "bin": { + "openskills": "dist/cli.js" + }, + "devDependencies": { + "@types/node": "^24.9.1", + "tsup": "^8.5.0", + "typescript": "^5.9.3", + "vitest": "^4.0.3" + }, + "engines": { + "node": ">=20.6.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/adm-zip": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.7.tgz", + "integrity": "sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.107.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.107.0.tgz", + "integrity": "sha512-XS8YE1jlyTIowP64+HoN30OlC1H9xqSlq1eoLZUgFEC8oUTO6euYZxti1xRiLSfZocs4qytTzR6xCBYtioQTCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + } + } +} diff --git a/vscode-extension/package.json b/vscode-extension/package.json new file mode 100644 index 0000000..2712046 --- /dev/null +++ b/vscode-extension/package.json @@ -0,0 +1,252 @@ +{ + "name": "agent-skills-manager", + "displayName": "AgentSkillsManager", + "description": "Manage, install, and sync Agent Skills (Claude Code compatible skills) from VSCode.", + "version": "0.6.0", + "publisher": "whyuds", + "repository": { + "type": "git", + "url": "https://github.com/lasoons/AgentSkillsManager" + }, + "license": "MIT", + "icon": "resources/skills-logo.png", + "engines": { + "vscode": "^1.96.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [], + "main": "./out/extension.js", + "contributes": { + "configuration": { + "title": "Agent Skills Manager", + "properties": { + "agentskills.repositories": { + "type": "array", + "default": [], + "description": "List of Git repositories containing skills.", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "name": { + "type": "string" + }, + "branch": { + "type": "string" + } + } + } + } + } + }, + "commands": [ + { + "command": "agentskills.addRepo", + "title": "Add Skill Repository", + "icon": "$(add)" + }, + { + "command": "agentskills.search", + "title": "Search Skills", + "icon": "$(search)" + }, + { + "command": "agentskills.clearSearch", + "title": "Clear Filter", + "icon": "$(clear-all)" + }, + { + "command": "agentskills.refresh", + "title": "Refresh Skills", + "icon": "$(refresh)" + }, + { + "command": "agentskills.selectAllInRepo", + "title": "Select All (Repo)", + "icon": "$(checklist)" + }, + { + "command": "agentskills.clearInRepo", + "title": "Clear (Repo)", + "icon": "$(clear-all)" + }, + { + "command": "agentskills.removeRepo", + "title": "Remove Repository", + "icon": "$(trash)" + }, + { + "command": "agentskills.installSelected", + "title": "Install Selected Skills", + "icon": "$(cloud-download)" + }, + { + "command": "agentskills.deleteSelected", + "title": "Delete Selected Skills", + "icon": "$(trash)" + }, + { + "command": "agentskills.switchBranch", + "title": "Switch Branch", + "icon": "$(git-branch)" + }, + { + "command": "agentskills.pullRepo", + "title": "Pull Latest", + "icon": "$(repo-sync)" + }, + { + "command": "agentskills.deleteLocalSkill", + "title": "Delete Skill", + "icon": "$(trash)" + }, + { + "command": "agentskills.openLocalSkill", + "title": "Open SKILL.md", + "icon": "$(go-to-file)" + }, + { + "command": "agentskills.installSkill", + "title": "Install Skill", + "icon": "$(cloud-download)" + }, + { + "command": "agentskills.deleteSkill", + "title": "Delete Skill", + "icon": "$(trash)" + }, + { + "command": "agentskills.installPersonalSkill", + "title": "Install Skill", + "icon": "$(cloud-download)" + } + ], + "viewsContainers": { + "activitybar": [ + { + "id": "agentskills-explorer", + "title": "Agent Skills", + "icon": "resources/skills-icon.svg" + } + ] + }, + "views": { + "agentskills-explorer": [ + { + "id": "agentskills-skills", + "name": "Skills" + } + ] + }, + "menus": { + "view/title": [ + { + "command": "agentskills.addRepo", + "when": "view == agentskills-skills", + "group": "navigation@1" + }, + { + "command": "agentskills.installSelected", + "when": "view == agentskills-skills", + "group": "navigation@2" + }, + { + "command": "agentskills.deleteSelected", + "when": "view == agentskills-skills", + "group": "navigation@3" + }, + { + "command": "agentskills.search", + "when": "view == agentskills-skills", + "group": "navigation@4" + }, + { + "command": "agentskills.clearSearch", + "when": "view == agentskills-skills && agentskills.hasFilter", + "group": "navigation@4.1" + }, + { + "command": "agentskills.refresh", + "when": "view == agentskills-skills", + "group": "navigation@5" + } + ], + "view/item/context": [ + { + "command": "agentskills.pullRepo", + "when": "viewItem == skillRepo", + "group": "inline" + }, + { + "command": "agentskills.selectAllInRepo", + "when": "viewItem == skillRepo", + "group": "navigation@1" + }, + { + "command": "agentskills.clearInRepo", + "when": "viewItem == skillRepo", + "group": "navigation@2" + }, + { + "command": "agentskills.switchBranch", + "when": "viewItem == skillRepo", + "group": "inline" + }, + { + "command": "agentskills.removeRepo", + "when": "viewItem == skillRepo", + "group": "inline" + }, + { + "command": "agentskills.openLocalSkill", + "when": "viewItem == localSkill", + "group": "inline" + }, + { + "command": "agentskills.deleteLocalSkill", + "when": "viewItem == localSkill", + "group": "inline" + }, + { + "command": "agentskills.installSkill", + "when": "viewItem == skill", + "group": "inline" + }, + { + "command": "agentskills.deleteSkill", + "when": "viewItem == skillInstalled", + "group": "inline" + }, + { + "command": "agentskills.installPersonalSkill", + "when": "viewItem == personalSkill", + "group": "inline" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run package", + "compile": "npm run check-types && node esbuild.js", + "watch": "npm-run-all -p watch:*", + "watch:esbuild": "node esbuild.js --watch", + "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", + "package": "npm run check-types && node esbuild.js --production", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "adm-zip": "^0.5.12" + }, + "devDependencies": { + "@types/adm-zip": "^0.5.7", + "@types/node": "^20.0.0", + "@types/vscode": "^1.96.0", + "esbuild": "^0.24.0", + "npm-run-all": "^4.1.5", + "typescript": "^5.0.0" + } +} diff --git a/vscode-extension/resources/image.png b/vscode-extension/resources/image.png new file mode 100644 index 0000000..66e9a8e Binary files /dev/null and b/vscode-extension/resources/image.png differ diff --git a/vscode-extension/resources/skills-icon.png b/vscode-extension/resources/skills-icon.png new file mode 100644 index 0000000..230a52d Binary files /dev/null and b/vscode-extension/resources/skills-icon.png differ diff --git a/vscode-extension/resources/skills-icon.svg b/vscode-extension/resources/skills-icon.svg new file mode 100644 index 0000000..d81c305 --- /dev/null +++ b/vscode-extension/resources/skills-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/vscode-extension/resources/skills-logo.png b/vscode-extension/resources/skills-logo.png new file mode 100644 index 0000000..0024ddd Binary files /dev/null and b/vscode-extension/resources/skills-logo.png differ diff --git a/vscode-extension/src/configManager.ts b/vscode-extension/src/configManager.ts new file mode 100644 index 0000000..cb84777 --- /dev/null +++ b/vscode-extension/src/configManager.ts @@ -0,0 +1,65 @@ +import * as vscode from 'vscode'; +import { SkillRepo } from './types'; +import { GitService } from './services/git'; + +// Preset repositories that are always included +const PRESET_REPOS: SkillRepo[] = [ + { url: 'https://github.com/anthropics/skills.git', name: 'anthropics/skills', isPreset: true }, + { url: 'https://github.com/openai/skills.git', name: 'openai/skills', isPreset: true }, + { url: 'https://github.com/skillcreatorai/Ai-Agent-Skills.git', name: 'skillcreatorai/Ai-Agent-Skills', isPreset: true }, + { url: 'https://github.com/obra/superpowers.git', name: 'obra/superpowers', isPreset: true }, + { url: 'https://github.com/ComposioHQ/awesome-claude-skills.git', name: 'ComposioHQ/awesome-claude-skills', isPreset: true } +]; + +export class ConfigManager { + static getRepos(): SkillRepo[] { + const config = vscode.workspace.getConfiguration('agentskills'); + const userRepos = config.get('repositories') || []; + // Merge preset repos with user repos, preset first, avoiding duplicates + return [...PRESET_REPOS, ...userRepos.filter(r => !PRESET_REPOS.some(p => p.url === r.url))]; + } + + static async ensurePresetRepos(): Promise { + // Auto-pull preset repos on startup + for (const repo of PRESET_REPOS) { + try { + await GitService.pullRepo(repo.url, repo.branch); + } catch (e) { + console.error(`Failed to pull preset repo ${repo.name}:`, e); + } + } + } + + static async addRepo(url: string) { + const config = vscode.workspace.getConfiguration('agentskills'); + const repos = this.getRepos(); + if (repos.some(r => r.url === url)) { + return; + } + + // Extract "owner/repo" format for better identification + const urlParts = url.replace('.git', '').split('/'); + const repo = urlParts.pop() || ''; + const owner = urlParts.pop() || ''; + let name = owner && repo ? `${owner}/${repo}` : repo || url; + repos.push({ url, name }); + await config.update('repositories', repos, vscode.ConfigurationTarget.Global); + } + + static async updateRepo(url: string, updates: Partial) { + const config = vscode.workspace.getConfiguration('agentskills'); + let repos = this.getRepos(); + const index = repos.findIndex(r => r.url === url); + if (index !== -1) { + repos[index] = { ...repos[index], ...updates }; + await config.update('repositories', repos, vscode.ConfigurationTarget.Global); + } + } + + static async removeRepo(url: string) { + const config = vscode.workspace.getConfiguration('agentskills'); + let repos = this.getRepos(); + repos = repos.filter(r => r.url !== url); + await config.update('repositories', repos, vscode.ConfigurationTarget.Global); + } +} diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts new file mode 100644 index 0000000..5b10d11 --- /dev/null +++ b/vscode-extension/src/extension.ts @@ -0,0 +1,813 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as https from 'https'; +import AdmZip = require('adm-zip'); +import { SkillsProvider } from './skillsProvider'; +import { ConfigManager } from './configManager'; +import { GitService } from './services/git'; +import { Skill, SkillRepo, LocalSkill, LocalSkillsGroup } from './types'; +import { copyRecursiveSync } from './utils/fs'; +import { detectIde, getProjectSkillsDir } from './utils/ide'; + +type TreeNode = SkillRepo | Skill | LocalSkillsGroup | LocalSkill; +let skillsTreeView: vscode.TreeView; + +interface RemoteSkill { + id: string; + name: string; + namespace: string; + sourceUrl: string; + description: string; + author: string; + installs: number; + stars: number; +} + +interface RemoteSkillsResponse { + skills: RemoteSkill[]; + total: number; + limit: number; + offset: number; +} + +type RemoteSkillPickItem = vscode.QuickPickItem & { itemType: 'remoteSkill'; skill: RemoteSkill; installed: boolean }; + +export function activate(context: vscode.ExtensionContext) { + const outputChannel = vscode.window.createOutputChannel('Agent Skills Manager'); + context.subscriptions.push(outputChannel); + + const originalLog = console.log; + const originalInfo = console.info; + const originalWarn = console.warn; + const originalError = console.error; + const originalDebug = console.debug; + + console.log = (...args: any[]) => { + const message = args.map(arg => String(arg)).join(' '); + outputChannel.appendLine(`[INFO] ${message}`); + originalLog.apply(console, args); + }; + + console.info = (...args: any[]) => { + const message = args.map(arg => String(arg)).join(' '); + outputChannel.appendLine(`[INFO] ${message}`); + originalInfo.apply(console, args); + }; + + console.warn = (...args: any[]) => { + const message = args.map(arg => String(arg)).join(' '); + outputChannel.appendLine(`[WARN] ${message}`); + originalWarn.apply(console, args); + }; + + console.error = (...args: any[]) => { + const message = args.map(arg => String(arg)).join(' '); + outputChannel.appendLine(`[ERROR] ${message}`); + originalError.apply(console, args); + }; + + console.debug = (...args: any[]) => { + const message = args.map(arg => String(arg)).join(' '); + outputChannel.appendLine(`[DEBUG] ${message}`); + originalDebug.apply(console, args); + }; + + outputChannel.appendLine('[INFO] Agent Skills Manager extension activated'); + outputChannel.appendLine(`[INFO] IDE detect hint (vscode.env.appName): ${vscode.env.appName}`); + outputChannel.appendLine(`[INFO] ENV AGENTSKILLS_IDE=${process.env.AGENTSKILLS_IDE ?? ''}`); + outputChannel.appendLine(`[INFO] ENV VSCODE_BRAND=${process.env.VSCODE_BRAND ?? ''}`); + outputChannel.appendLine(`[INFO] ENV VSCODE_ENV_APPNAME=${process.env.VSCODE_ENV_APPNAME ?? ''}`); + outputChannel.appendLine(`[INFO] ENV PROG_IDE_NAME=${process.env.PROG_IDE_NAME ?? ''}`); + + outputChannel.appendLine('[INFO] Creating SkillsProvider'); + const skillsProvider = new SkillsProvider(context.globalState, outputChannel); + + const dragMimeType = 'application/vnd.agentskills.node'; + const dragAndDropController: vscode.TreeDragAndDropController = { + dragMimeTypes: [dragMimeType], + dropMimeTypes: [dragMimeType], + handleDrag: (source, dataTransfer, _token) => { + const first = source[0]; + if (!first) return; + + if ('type' in first && first.type === 'local-group') { + const group = first as LocalSkillsGroup; + if (group.isActive) return; + dataTransfer.set(dragMimeType, new vscode.DataTransferItem(JSON.stringify({ + kind: 'localGroup', + key: group.path + }))); + return; + } + + if ('url' in first) { + const repo = first as SkillRepo; + dataTransfer.set(dragMimeType, new vscode.DataTransferItem(JSON.stringify({ + kind: 'repo', + key: repo.url + }))); + } + }, + handleDrop: (_target, dataTransfer, _token) => { + const item = dataTransfer.get(dragMimeType); + const raw = item?.value; + if (typeof raw !== 'string') return; + + const parsed = JSON.parse(raw) as { kind: 'repo' | 'localGroup'; key: string }; + const target = _target as any | undefined; + + if (parsed.kind === 'repo') { + const targetKey = target && ('url' in target) ? String(target.url) : undefined; + skillsProvider.reorderAfterDrop('repo', parsed.key, targetKey); + return; + } + + if (parsed.kind === 'localGroup') { + const isTargetGroup = target && target.type === 'local-group'; + const targetKey = isTargetGroup && !target.isActive ? String(target.path) : undefined; + skillsProvider.reorderAfterDrop('localGroup', parsed.key, targetKey); + } + } + }; + + skillsTreeView = vscode.window.createTreeView('agentskills-skills', { + treeDataProvider: skillsProvider, + canSelectMany: true, + dragAndDropController + }); + + context.subscriptions.push(skillsTreeView); + outputChannel.appendLine('[INFO] Skills TreeView created'); + outputChannel.appendLine('[INFO] Starting initial refresh/indexing'); + skillsProvider.refresh(); + + const updateSearchUi = (query: string) => { + skillsTreeView.message = query ? `Filter: ${query}` : undefined; + void vscode.commands.executeCommand('setContext', 'agentskills.hasFilter', Boolean(query)); + }; + + updateSearchUi(skillsProvider.getSearchQuery()); + context.subscriptions.push(skillsProvider.onDidChangeSearchQuery(updateSearchUi)); + + skillsTreeView.onDidChangeCheckboxState(e => { + e.items.forEach(([item, state]) => { + // Only handle repo skills (have repoUrl, not local skills) + if ('repoUrl' in item) { + skillsProvider.setChecked(item as Skill, state === vscode.TreeItemCheckboxState.Checked); + } + }); + }); + + context.subscriptions.push( + vscode.commands.registerCommand('agentskills.refresh', () => skillsProvider.refresh()), + vscode.commands.registerCommand('agentskills.search', async () => { + const installButton: vscode.QuickInputButton = { + iconPath: new vscode.ThemeIcon('cloud-download'), + tooltip: 'Install' + }; + const prevPageButton: vscode.QuickInputButton = { + iconPath: new vscode.ThemeIcon('arrow-left'), + tooltip: 'Previous page' + }; + const nextPageButton: vscode.QuickInputButton = { + iconPath: new vscode.ThemeIcon('arrow-right'), + tooltip: 'Next page' + }; + const openSiteButton: vscode.QuickInputButton = { + iconPath: new vscode.ThemeIcon('link-external'), + tooltip: 'Open https://claude-plugins.dev/' + }; + + const quickPick = vscode.window.createQuickPick(); + quickPick.title = 'Search Skills'; + quickPick.placeholder = 'Search skills by name or description'; + quickPick.matchOnDescription = true; + quickPick.matchOnDetail = true; + quickPick.value = skillsProvider.getSearchQuery(); + + let debounceTimer: NodeJS.Timeout | undefined; + let activeRequest = 0; + + let query = quickPick.value.trim(); + let offset = 0; + const limit = 20; + let total = 0; + + const getTargetBase = (): string | undefined => { + const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + if (!workspaceRoot) return undefined; + return getProjectSkillsDir(workspaceRoot, detectIde(process.env, vscode.env.appName)); + }; + + const updateButtons = () => { + const buttons: vscode.QuickInputButton[] = []; + buttons.push(openSiteButton); + if (offset > 0) buttons.push(prevPageButton); + if (offset + limit < total) buttons.push(nextPageButton); + quickPick.buttons = buttons; + }; + + const updateTitle = () => { + const start = total === 0 ? 0 : offset + 1; + const end = Math.min(offset + limit, total); + quickPick.title = `Search Skills — ${start}-${end} / ${total}`; + }; + + const makeItem = (skill: RemoteSkill, targetBase: string | undefined): RemoteSkillPickItem => { + const safeName = sanitizeDirName(skill.name); + const installed = Boolean(targetBase && fs.existsSync(path.join(targetBase, safeName))); + const installsText = formatCompactNumber(skill.installs); + const starsText = formatCompactNumber(skill.stars); + + return { + itemType: 'remoteSkill', + skill, + installed, + label: skill.name, + description: `${starsText} ★ ${installsText} ⬇`, + detail: [skill.namespace, skill.author ? `by ${skill.author}` : '', skill.description].filter(Boolean).join(' — '), + buttons: installed ? [] : [installButton] + }; + }; + + const refreshRemote = async (nextQuery: string, nextOffset: number) => { + const requestId = ++activeRequest; + const trimmed = nextQuery.trim(); + + query = trimmed; + offset = nextOffset; + quickPick.items = []; + quickPick.busy = true; + quickPick.title = 'Search Skills — Loading...'; + + try { + const result = await fetchRemoteSkills(trimmed, limit, nextOffset); + if (requestId !== activeRequest) return; + + offset = result.offset; + total = result.total; + + const targetBase = getTargetBase(); + quickPick.items = result.skills.map(s => makeItem(s, targetBase)); + quickPick.activeItems = []; + quickPick.selectedItems = []; + updateTitle(); + updateButtons(); + } catch (e) { + if (requestId !== activeRequest) return; + quickPick.items = []; + total = 0; + offset = 0; + updateTitle(); + updateButtons(); + console.warn(`Remote search failed: ${e}`); + } finally { + if (requestId === activeRequest) quickPick.busy = false; + } + }; + + const scheduleRefresh = (nextQuery: string) => { + if (debounceTimer) clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + skillsProvider.setSearchQuery(nextQuery); + void refreshRemote(nextQuery, 0); + }, 500); + }; + + quickPick.onDidChangeValue(value => { + offset = 0; + scheduleRefresh(value); + }); + + quickPick.onDidTriggerButton(button => { + if (button === openSiteButton) { + void vscode.env.openExternal(vscode.Uri.parse('https://claude-plugins.dev/')); + return; + } + if (button === prevPageButton) { + const nextOffset = Math.max(0, offset - limit); + void refreshRemote(query, nextOffset); + return; + } + if (button === nextPageButton) { + const nextOffset = offset + limit; + void refreshRemote(query, nextOffset); + } + }); + + quickPick.onDidTriggerItemButton(async e => { + if (e.button !== installButton) return; + + const item = e.item; + if (!vscode.workspace.workspaceFolders?.[0]) { + vscode.window.showErrorMessage('Please open a workspace folder first.'); + return; + } + + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Installing ${item.skill.name}...`, + cancellable: false + }, async () => { + await installRemoteSkillFromZip(item.skill, getTargetBase()!); + }); + + skillsProvider.refreshInstalledAndLocal(); + void refreshRemote(query, offset); + vscode.window.showInformationMessage(`Installed skill "${item.skill.name}".`); + }); + + quickPick.onDidAccept(async () => { + const selected = (quickPick.selectedItems[0] ?? quickPick.activeItems[0]) as RemoteSkillPickItem | undefined; + if (selected?.itemType === 'remoteSkill') { + if (!vscode.workspace.workspaceFolders?.[0]) { + vscode.window.showErrorMessage('Please open a workspace folder first.'); + return; + } + + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Installing ${selected.skill.name}...`, + cancellable: false + }, async () => { + await installRemoteSkillFromZip(selected.skill, getTargetBase()!); + }); + + skillsProvider.refreshInstalledAndLocal(); + void refreshRemote(query, offset); + vscode.window.showInformationMessage(`Installed skill "${selected.skill.name}".`); + quickPick.hide(); + return; + } + + const acceptedQuery = quickPick.value; + if (!acceptedQuery.trim()) { + quickPick.hide(); + return; + } + + skillsProvider.setSearchQuery(acceptedQuery); + await skillsProvider.waitForIndexing(); + await skillsProvider.recomputeRootNodesForReveal(); + await vscode.commands.executeCommand('workbench.actions.treeView.agentskills-skills.collapseAll'); + for (const node of skillsProvider.getExpandableRootsForSearch()) { + await skillsTreeView.reveal(node as any, { expand: true, select: false, focus: false }); + } + quickPick.hide(); + }); + + quickPick.onDidHide(() => { + if (debounceTimer) clearTimeout(debounceTimer); + activeRequest++; + quickPick.dispose(); + }); + + quickPick.show(); + void refreshRemote(quickPick.value, 0); + }), + vscode.commands.registerCommand('agentskills.clearSearch', () => { + skillsProvider.setSearchQuery(''); + void vscode.commands.executeCommand('workbench.actions.treeView.agentskills-skills.collapseAll'); + }), + vscode.commands.registerCommand('agentskills.selectAllInRepo', async (node: SkillRepo) => { + if (!node || !('url' in node)) return; + await skillsProvider.waitForIndexing(); + skillsProvider.checkAllSkillsInRepo(node); + }), + vscode.commands.registerCommand('agentskills.clearInRepo', async (node: SkillRepo) => { + if (!node || !('url' in node)) return; + await skillsProvider.waitForIndexing(); + skillsProvider.clearCheckedSkillsInRepo(node); + }), + + vscode.commands.registerCommand('agentskills.addRepo', async () => { + const url = await vscode.window.showInputBox({ + placeHolder: 'Enter Git Repository URL (e.g., https://github.com/anthropics/skills)', + prompt: 'Add a new Skill Repository' + }); + if (url) { + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Adding repository...', + cancellable: false + }, async () => { + await ConfigManager.addRepo(url); + skillsProvider.refresh(); + }); + } + }), + + vscode.commands.registerCommand('agentskills.removeRepo', async (node: SkillRepo) => { + const result = await vscode.window.showWarningMessage(`Remove repository ${node.name}?`, 'Yes', 'No'); + if (result === 'Yes') { + await ConfigManager.removeRepo(node.url); + skillsProvider.refresh(); + } + }), + + vscode.commands.registerCommand('agentskills.switchBranch', async (node: SkillRepo) => { + const branches = await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Fetching branches...', + cancellable: false + }, async () => { + return await GitService.getRemoteBranches(node.url); + }); + + if (branches.length === 0) { + vscode.window.showErrorMessage('Could not list branches or no branches found.'); + return; + } + + const selected = await vscode.window.showQuickPick(branches, { + placeHolder: `Select branch for ${node.name} (current: ${node.branch || 'default'})` + }); + + if (selected) { + await ConfigManager.updateRepo(node.url, { branch: selected }); + skillsProvider.refresh(); + } + }), + + vscode.commands.registerCommand('agentskills.pullRepo', async (node: SkillRepo) => { + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Pulling ${node.name}...`, + cancellable: false + }, async () => { + await GitService.pullRepo(node.url, node.branch); + }); + skillsProvider.refresh(); + vscode.window.showInformationMessage(`${node.name} updated.`); + }), + + vscode.commands.registerCommand('agentskills.installSelected', async () => { + const checked = skillsProvider.getCheckedSkills(); + const selected = checked.length > 0 + ? checked + : (skillsTreeView.selection.filter(item => !('url' in item)) as Skill[]); + + if (selected.length === 0) { + vscode.window.showWarningMessage('Please select skills to install (use checkboxes).'); + return; + } + + if (!vscode.workspace.workspaceFolders) { + vscode.window.showErrorMessage('Please open a workspace folder first.'); + return; + } + + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Installing ${selected.length} skill(s)...`, + cancellable: false + }, async (progress) => { + const workspaceRoot = vscode.workspace.workspaceFolders![0].uri.fsPath; + const targetBase = getProjectSkillsDir(workspaceRoot, detectIde(process.env, vscode.env.appName)); + + for (let i = 0; i < selected.length; i++) { + const skill = selected[i]; + progress.report({ message: `${skill.name} (${i + 1}/${selected.length})` }); + + const targetDir = path.join(targetBase, skill.name); + + if (!skill.localPath || !fs.existsSync(skill.localPath)) { + vscode.window.showWarningMessage(`Source files missing for ${skill.name}. Try refreshing.`); + continue; + } + + fs.mkdirSync(path.dirname(targetDir), { recursive: true }); + copyRecursiveSync(skill.localPath, targetDir); + } + }); + + skillsProvider.clearSelection(); + skillsProvider.refreshInstalledAndLocal(); + vscode.window.showInformationMessage(`Installed ${selected.length} skill(s).`); + }), + + vscode.commands.registerCommand('agentskills.deleteSelected', async () => { + const checked = skillsProvider.getCheckedSkills(); + const selected = checked.length > 0 + ? checked + : (skillsTreeView.selection.filter(item => !('url' in item)) as Skill[]); + + if (selected.length === 0) { + vscode.window.showWarningMessage('Please select skills to delete.'); + return; + } + + if (!vscode.workspace.workspaceFolders) { + vscode.window.showErrorMessage('Please open a workspace folder first.'); + return; + } + + const confirm = await vscode.window.showWarningMessage( + `Delete ${selected.length} skill(s) from this project?`, + 'Yes', 'No' + ); + + if (confirm !== 'Yes') return; + + const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.fsPath; + const targetBase = getProjectSkillsDir(workspaceRoot, detectIde(process.env, vscode.env.appName)); + + for (const skill of selected) { + const targetDir = path.join(targetBase, skill.name); + if (fs.existsSync(targetDir)) { + fs.rmSync(targetDir, { recursive: true, force: true }); + } + } + + skillsProvider.clearSelection(); + skillsProvider.refreshInstalledAndLocal(); + vscode.window.showInformationMessage(`Deleted ${selected.length} skill(s).`); + }), + + vscode.commands.registerCommand('agentskills.installSkill', async (node: Skill) => { + if (!node || !('repoUrl' in node)) { + vscode.window.showErrorMessage('Please select a skill to install.'); + return; + } + + if (!vscode.workspace.workspaceFolders) { + vscode.window.showErrorMessage('Please open a workspace folder first.'); + return; + } + + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Installing ${node.name}...`, + cancellable: false + }, async () => { + const workspaceRoot = vscode.workspace.workspaceFolders![0].uri.fsPath; + const targetBase = getProjectSkillsDir(workspaceRoot, detectIde(process.env, vscode.env.appName)); + const targetDir = path.join(targetBase, node.name); + + if (!node.localPath || !fs.existsSync(node.localPath)) { + vscode.window.showWarningMessage(`Source files missing for ${node.name}. Try refreshing.`); + return; + } + + fs.mkdirSync(path.dirname(targetDir), { recursive: true }); + copyRecursiveSync(node.localPath, targetDir); + }); + + skillsProvider.clearSelection(); + skillsProvider.refreshInstalledAndLocal(); + vscode.window.showInformationMessage(`Installed skill "${node.name}".`); + }), + + vscode.commands.registerCommand('agentskills.deleteSkill', async (node: Skill) => { + if (!node || !('repoUrl' in node)) { + vscode.window.showErrorMessage('Please select a skill to delete.'); + return; + } + + if (!vscode.workspace.workspaceFolders) { + vscode.window.showErrorMessage('Please open a workspace folder first.'); + return; + } + + const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.fsPath; + const targetBase = getProjectSkillsDir(workspaceRoot, detectIde(process.env, vscode.env.appName)); + const targetDir = path.join(targetBase, node.name); + + if (fs.existsSync(targetDir)) { + fs.rmSync(targetDir, { recursive: true, force: true }); + } + + skillsProvider.clearSelection(); + skillsProvider.refreshInstalledAndLocal(); + vscode.window.showInformationMessage(`Deleted skill "${node.name}".`); + }), + + vscode.commands.registerCommand('agentskills.deleteLocalSkill', async (node: LocalSkill) => { + if (!node || node.type !== 'local-skill') { + vscode.window.showErrorMessage('Please select a local skill to delete.'); + return; + } + + try { + if (fs.existsSync(node.path)) { + fs.rmSync(node.path, { recursive: true, force: true }); + } + skillsProvider.clearSelection(); + skillsProvider.refreshInstalledAndLocal(); + vscode.window.showInformationMessage(`Deleted skill "${node.name}".`); + } catch (e) { + vscode.window.showErrorMessage(`Failed to delete skill: ${e}`); + } + }), + + vscode.commands.registerCommand('agentskills.openLocalSkill', async (node: LocalSkill) => { + if (!node || node.type !== 'local-skill') return; + + const skillMdPath = path.join(node.path, 'SKILL.md'); + if (fs.existsSync(skillMdPath)) { + const doc = await vscode.workspace.openTextDocument(skillMdPath); + await vscode.window.showTextDocument(doc); + } + }), + + vscode.commands.registerCommand('agentskills.installPersonalSkill', async (node: LocalSkill) => { + if (!node || node.type !== 'local-skill') { + vscode.window.showErrorMessage('Please select a skill to install.'); + return; + } + + if (!vscode.workspace.workspaceFolders) { + vscode.window.showErrorMessage('Please open a workspace folder first.'); + return; + } + + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Installing ${node.name}...`, + cancellable: false + }, async () => { + const workspaceRoot = vscode.workspace.workspaceFolders![0].uri.fsPath; + const targetBase = getProjectSkillsDir(workspaceRoot, detectIde(process.env, vscode.env.appName)); + const targetDir = path.join(targetBase, node.name); + + if (!fs.existsSync(node.path)) { + vscode.window.showWarningMessage(`Source files missing for ${node.name}.`); + return; + } + + fs.mkdirSync(path.dirname(targetDir), { recursive: true }); + copyRecursiveSync(node.path, targetDir); + }); + + skillsProvider.clearSelection(); + skillsProvider.refreshInstalledAndLocal(); + vscode.window.showInformationMessage(`Installed skill "${node.name}".`); + }) + ); + + // Show the view on first load or update to help user find the extension + const extensionId = 'whyuds.agent-skills-manager'; + const extension = vscode.extensions.getExtension(extensionId); + const currentVersion = extension?.packageJSON.version; + const lastVersion = context.globalState.get('agentskills.lastShownVersion'); + + if (currentVersion && currentVersion !== lastVersion) { + // Delay slightly to ensure UI is ready + setTimeout(() => { + vscode.commands.executeCommand('workbench.view.extension.agentskills-explorer'); + }, 1000); + context.globalState.update('agentskills.lastShownVersion', currentVersion); + } + +} + +export function deactivate() { } + +function formatCompactNumber(value: number): string { + if (!Number.isFinite(value)) return '0'; + const abs = Math.abs(value); + if (abs < 1000) return String(Math.trunc(value)); + if (abs < 1_000_000) return `${(value / 1000).toFixed(1).replace(/\.0$/, '')}k`; + if (abs < 1_000_000_000) return `${(value / 1_000_000).toFixed(1).replace(/\.0$/, '')}m`; + return `${(value / 1_000_000_000).toFixed(1).replace(/\.0$/, '')}b`; +} + +function sanitizeDirName(name: string): string { + const cleaned = name.replace(/[\\/:*"<>|?]+/g, '-').trim(); + return cleaned || 'skill'; +} + +function downloadUrlToBuffer(url: string, headers: Record | undefined, redirectsLeft = 3): Promise { + return new Promise((resolve, reject) => { + const request = https.get(url, { headers }, res => { + const status = res.statusCode ?? 0; + const location = res.headers.location; + if ([301, 302, 303, 307, 308].includes(status) && location && redirectsLeft > 0) { + res.resume(); + const redirected = new URL(location, url).toString(); + void downloadUrlToBuffer(redirected, headers, redirectsLeft - 1).then(resolve, reject); + return; + } + + if (status < 200 || status >= 300) { + const chunks: Buffer[] = []; + res.on('data', chunk => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))); + res.on('end', () => { + const body = Buffer.concat(chunks).toString('utf8'); + reject(new Error(`Request failed (${status}): ${body.slice(0, 300)}`)); + }); + return; + } + + const chunks: Buffer[] = []; + res.on('data', chunk => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))); + res.on('end', () => resolve(Buffer.concat(chunks))); + }); + request.on('error', reject); + }); +} + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function fetchRemoteSkills(q: string, limit: number, offset: number): Promise { + const url = new URL('https://claude-plugins.dev/api/skills'); + const trimmed = q.trim(); + if (trimmed) { + url.searchParams.set('q', trimmed); + } + url.searchParams.set('limit', String(limit)); + url.searchParams.set('offset', String(offset)); + + const headers = { + Accept: 'application/json', + 'User-Agent': 'claude-plugins-web/1.0' + }; + + let lastError: unknown; + for (let attempt = 1; attempt <= 3; attempt++) { + try { + const buf = await downloadUrlToBuffer(url.toString(), headers); + const parsed = JSON.parse(buf.toString('utf8')) as RemoteSkillsResponse; + if (!parsed || !Array.isArray(parsed.skills)) { + throw new Error('Invalid response'); + } + return parsed; + } catch (e) { + lastError = e; + if (attempt < 3) { + console.debug(`Remote search retry ${attempt}/2: ${e}`); + await sleep(attempt === 1 ? 200 : 500); + continue; + } + } + } + + throw lastError instanceof Error ? lastError : new Error(String(lastError)); +} + +function findDirsWithFile(rootDir: string, fileName: string, maxDepth: number): string[] { + const results: string[] = []; + + const visit = (dir: string, depth: number) => { + if (depth > maxDepth) return; + const candidate = path.join(dir, fileName); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) { + results.push(dir); + } + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory()) continue; + if (entry.name === '.git' || entry.name === 'node_modules') continue; + visit(path.join(dir, entry.name), depth + 1); + } + }; + + visit(rootDir, 0); + return results; +} + +function pickSkillRoot(extractedDir: string, expectedName: string): string { + const candidates = findDirsWithFile(extractedDir, 'SKILL.md', 4); + if (candidates.length === 0) return extractedDir; + + const expectedLower = expectedName.toLowerCase(); + const best = candidates.find(d => path.basename(d).toLowerCase() === expectedLower); + return best ?? candidates[0]!; +} + +async function installRemoteSkillFromZip(skill: RemoteSkill, targetBase: string): Promise { + const safeName = sanitizeDirName(skill.name); + const targetDir = path.join(targetBase, safeName); + + const zipUrl = new URL('https://github-zip-api.val.run/zip'); + zipUrl.searchParams.set('source', skill.sourceUrl); + + const buf = await downloadUrlToBuffer(zipUrl.toString(), undefined); + + const tempDir = path.join(os.tmpdir(), `agentskills-zip-${Date.now()}-${Math.random().toString(16).slice(2)}`); + const extractDir = path.join(tempDir, 'extract'); + fs.mkdirSync(extractDir, { recursive: true }); + + try { + const zip = new AdmZip(buf); + zip.extractAllTo(extractDir, true); + + const selectedRoot = pickSkillRoot(extractDir, safeName); + + fs.mkdirSync(path.dirname(targetDir), { recursive: true }); + if (fs.existsSync(targetDir)) { + fs.rmSync(targetDir, { recursive: true, force: true }); + } + + copyRecursiveSync(selectedRoot, targetDir); + } finally { + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + } +} diff --git a/vscode-extension/src/services/git.ts b/vscode-extension/src/services/git.ts new file mode 100644 index 0000000..edadbbd --- /dev/null +++ b/vscode-extension/src/services/git.ts @@ -0,0 +1,170 @@ +import * as cp from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import * as dns from 'dns'; +import { Skill } from '../types'; +import { scanSkillsFromDir, getRepoCachePath } from './skillScanner'; + +const CACHE_DIR = path.join(os.tmpdir(), 'agentskills-git-cache'); + +// Ensure cache directory exists +if (!fs.existsSync(CACHE_DIR)) { + fs.mkdirSync(CACHE_DIR, { recursive: true }); +} + +export class GitService { + static getRepoPath(url: string): string { + return getRepoCachePath(url); + } + + static isRepoCloned(url: string): boolean { + const repoPath = this.getRepoPath(url); + return fs.existsSync(path.join(repoPath, '.git')); + } + + private static async isValidGitRepo(repoPath: string): Promise { + try { + const output = await this.execGitOutputInDir(repoPath, ['rev-parse', '--is-inside-work-tree']); + return output.trim() === 'true'; + } catch { + return false; + } + } + + static async getRemoteBranches(url: string): Promise { + try { + const output = await this.execGitOutput(['ls-remote', '--heads', url]); + return output.split('\n') + .filter(line => line.trim()) + .map(line => { + const parts = line.split('\t'); + return parts[1].replace('refs/heads/', ''); + }); + } catch (e) { + console.error(`Failed to list branches for ${url}`, e); + return []; + } + } + + static async checkRepoConnectivity(url: string, timeoutMs: number = 200): Promise { + const host = this.getRepoHost(url); + if (!host) return false; + + const lookup = new Promise((resolve) => { + dns.lookup(host, { all: false }, (err) => resolve(!err)); + }); + + const timeout = new Promise((resolve) => { + setTimeout(() => resolve(false), timeoutMs); + }); + + return Promise.race([lookup, timeout]); + } + + static getRepoHost(url: string): string | undefined { + const trimmed = url.trim(); + if (!trimmed) return undefined; + + try { + const parsed = new URL(trimmed); + if (parsed.hostname) return parsed.hostname; + } catch { } + + const scpLike = trimmed.match(/^(?:.+@)?([^:\/]+):.+$/); + if (scpLike?.[1]) return scpLike[1]; + + return undefined; + } + + static async ensureRepoCloned(url: string, branch?: string): Promise { + const repoPath = this.getRepoPath(url); + + if (!fs.existsSync(path.join(repoPath, '.git')) || !(await this.isValidGitRepo(repoPath))) { + if (fs.existsSync(repoPath)) { + fs.rmSync(repoPath, { recursive: true, force: true }); + } + const dirName = path.basename(repoPath); + const args = ['clone', '--depth', '1']; + if (branch) args.push('--branch', branch); + args.push(url, dirName); + await this.execGitSilent(CACHE_DIR, args); + } + return repoPath; + } + + static async pullRepo(url: string, branch?: string): Promise { + const repoPath = this.getRepoPath(url); + + await this.ensureRepoCloned(url, branch); + + await this.execGitSilent(repoPath, ['fetch', 'origin']); + if (branch) { + await this.execGitSilent(repoPath, ['checkout', branch]); + await this.execGitSilent(repoPath, ['pull', 'origin', branch]); + } else { + await this.execGitSilent(repoPath, ['pull']); + } + } + + static async getSkillsFromRepo(url: string, branch?: string): Promise { + const repoPath = await this.ensureRepoCloned(url, branch); + if (branch) { + const currentBranch = await this.getCurrentBranch(repoPath); + if (currentBranch !== branch) { + await this.execGitSilent(repoPath, ['fetch', 'origin', branch]); + await this.execGitSilent(repoPath, ['checkout', '-B', branch, `origin/${branch}`]); + } + } + return scanSkillsFromDir(repoPath, url); + } + + private static async getCurrentBranch(repoPath: string): Promise { + const output = await this.execGitOutputInDir(repoPath, ['rev-parse', '--abbrev-ref', 'HEAD']); + return output.trim(); + } + + private static execGitOutput(args: string[]): Promise { + return new Promise((resolve, reject) => { + cp.execFile('git', args, { + windowsHide: true + }, (error, stdout, stderr) => { + if (error) { + reject(new Error(`Git command failed: ${stderr || error.message}`)); + } else { + resolve(stdout); + } + }); + }); + } + + private static execGitOutputInDir(cwd: string, args: string[]): Promise { + return new Promise((resolve, reject) => { + cp.execFile('git', args, { + cwd, + windowsHide: true + }, (error, stdout, stderr) => { + if (error) { + reject(new Error(`Git command failed: ${stderr || error.message}`)); + } else { + resolve(stdout); + } + }); + }); + } + + private static execGitSilent(cwd: string, args: string[]): Promise { + return new Promise((resolve, reject) => { + cp.execFile('git', args, { + cwd, + windowsHide: true + }, (error, _stdout, stderr) => { + if (error) { + reject(new Error(`Git command failed: ${stderr || error.message}`)); + } else { + resolve(); + } + }); + }); + } +} diff --git a/vscode-extension/src/services/skillScanner.ts b/vscode-extension/src/services/skillScanner.ts new file mode 100644 index 0000000..76a602f --- /dev/null +++ b/vscode-extension/src/services/skillScanner.ts @@ -0,0 +1,59 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { Skill } from '../types'; +import { extractYamlField, hasValidFrontmatter } from '../utils/yaml'; + +const CACHE_DIR = path.join(os.tmpdir(), 'agentskills-git-cache'); + +/** + * Scan skills from a local directory + */ +export function scanSkillsFromDir(repoPath: string, repoUrl: string): Skill[] { + const skills: Skill[] = []; + + if (!fs.existsSync(repoPath)) { + return skills; + } + + const findSkills = (dir: string) => { + try { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + if (entry.name === '.git') continue; + + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + const skillMdPath = path.join(fullPath, 'SKILL.md'); + if (fs.existsSync(skillMdPath)) { + const content = fs.readFileSync(skillMdPath, 'utf-8'); + if (hasValidFrontmatter(content)) { + skills.push({ + name: extractYamlField(content, 'name') || entry.name, + description: extractYamlField(content, 'description'), + path: path.relative(repoPath, fullPath).replace(/\\/g, '/'), + repoUrl: repoUrl, + localPath: fullPath + }); + } + } else { + findSkills(fullPath); + } + } + } + } catch (e) { + console.error(`Error scanning dir ${dir}`, e); + } + }; + + findSkills(repoPath); + return skills; +} + +/** + * Get the cache path for a repo URL + */ +export function getRepoCachePath(url: string): string { + const dirName = Buffer.from(url).toString('base64').replace(/[^a-zA-Z0-9]/g, ''); + return path.join(CACHE_DIR, dirName); +} diff --git a/vscode-extension/src/skillsProvider.ts b/vscode-extension/src/skillsProvider.ts new file mode 100644 index 0000000..b3df01f --- /dev/null +++ b/vscode-extension/src/skillsProvider.ts @@ -0,0 +1,851 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ConfigManager } from './configManager'; +import { GitService } from './services/git'; +import { Skill, SkillRepo, SkillMatchStatus, LocalSkillsGroup, LocalSkill } from './types'; +import { detectIde, getProjectSkillsDir } from './utils/ide'; +import { getCachedSkillHash, clearHashCache } from './utils/skillCompare'; +import { getSkillDirectories } from './utils/skills'; +import { extractYamlField } from './utils/yaml'; + +// Union type for all tree node types +type TreeNode = SkillRepo | Skill | LocalSkillsGroup | LocalSkill; + +// Type guards +function isSkillRepo(node: TreeNode): node is SkillRepo { + return 'url' in node && !('type' in node); +} + +function isSkill(node: TreeNode): node is Skill { + return 'repoUrl' in node && !('type' in node); +} + +function isLocalSkillsGroup(node: TreeNode): node is LocalSkillsGroup { + return 'type' in node && node.type === 'local-group'; +} + +function isLocalSkill(node: TreeNode): node is LocalSkill { + return 'type' in node && node.type === 'local-skill'; +} + +export class SkillsProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + private _onDidChangeSearchQuery: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeSearchQuery: vscode.Event = this._onDidChangeSearchQuery.event; + + private checkedSkills: Set = new Set(); + private skillCache: Map = new Map(); + private installedSkillHashes: Map = new Map(); // skillName -> hash + private repoSkillsIndex: Map = new Map(); + private localSkillsIndex: Map = new Map(); + private repoConnectivity: Map = new Map(); + private repoOrder: string[] = []; + private localGroupOrder: string[] = []; + private searchQuery: string = ''; + private indexingPromise: Promise | undefined; + private localGroupByPath: Map = new Map(); + private repoByUrl: Map = new Map(); + private lastRootNodes: TreeNode[] = []; + + constructor( + private readonly memento?: vscode.Memento, + private readonly outputChannel?: vscode.OutputChannel + ) { + this.log('SkillsProvider initialized'); + this.repoOrder = this.memento?.get('agentskills.repoOrder') ?? []; + this.localGroupOrder = this.memento?.get('agentskills.localGroupOrder') ?? []; + } + + refresh(): void { + clearHashCache(); + this.updateInstalledSkillHashes(); + this._onDidChangeTreeData.fire(); + void this.buildIndex(); + } + + refreshInstalledAndLocal(): void { + clearHashCache(); + this.updateInstalledSkillHashes(); + this.recomputeRepoSkillStates(); + this.rebuildLocalIndex(); + this._onDidChangeTreeData.fire(); + } + + setChecked(skill: Skill, checked: boolean): void { + const key = this.getSkillKey(skill); + if (checked) { + this.checkedSkills.add(key); + this.skillCache.set(key, skill); + } else { + this.checkedSkills.delete(key); + } + } + + getCheckedSkills(): Skill[] { + const result: Skill[] = []; + for (const key of this.checkedSkills) { + const skill = this.skillCache.get(key); + if (skill) { + result.push(skill); + } + } + return result; + } + + clearSelection(): void { + this.checkedSkills.clear(); + this._onDidChangeTreeData.fire(); + } + + setSearchQuery(query: string): void { + this.searchQuery = query.trim(); + this._onDidChangeSearchQuery.fire(this.searchQuery); + this._onDidChangeTreeData.fire(); + } + + getSearchQuery(): string { + return this.searchQuery; + } + + checkAllMatchingRepoSkills(): void { + const skills = this.getAllMatchingRepoSkills(); + for (const skill of skills) { + if (skill.matchStatus === SkillMatchStatus.Conflict) continue; + this.setChecked(skill, true); + } + this._onDidChangeTreeData.fire(); + } + + checkAllSkillsInRepo(repo: SkillRepo): void { + const skills = this.repoSkillsIndex.get(repo.url) ?? []; + const candidates = this.searchQuery ? this.filterSkills(skills) : skills; + for (const skill of candidates) { + if ((skill.matchStatus ?? this.getSkillMatchStatus(skill)) === SkillMatchStatus.Conflict) continue; + this.setChecked(skill, true); + } + this._onDidChangeTreeData.fire(); + } + + clearCheckedSkillsInRepo(repo: SkillRepo): void { + const skills = this.repoSkillsIndex.get(repo.url) ?? []; + for (const skill of skills) { + const key = this.getSkillKey(skill); + this.checkedSkills.delete(key); + } + this._onDidChangeTreeData.fire(); + } + + uncheckAllSkills(): void { + this.checkedSkills.clear(); + this._onDidChangeTreeData.fire(); + } + + private rebuildLocalIndex(): void { + const localGroups = this.getLocalSkillsGroups(); + this.localSkillsIndex.clear(); + for (const group of localGroups) { + const skills = this.getLocalSkillsFromGroup(group); + this.localSkillsIndex.set(group.path, skills); + } + } + + private recomputeRepoSkillStates(): void { + for (const skills of this.repoSkillsIndex.values()) { + for (const s of skills) { + s.matchStatus = this.getSkillMatchStatus(s); + s.installed = s.matchStatus === SkillMatchStatus.Matched; + this.skillCache.set(this.getSkillKey(s), s); + } + } + } + + async waitForIndexing(): Promise { + await this.indexingPromise; + } + + getExpandableRootsForSearch(): Array { + if (!this.searchQuery) return []; + const result: Array = []; + for (const node of this.lastRootNodes) { + if (isLocalSkillsGroup(node)) { + if (node.isActive || this.getLocalGroupMatchedCount(node) > 0) result.push(node); + } else if (isSkillRepo(node)) { + if (this.getRepoMatchedCount(node) > 0) result.push(node); + } + } + return result; + } + + async recomputeRootNodesForReveal(): Promise { + this.lastRootNodes = await this.getRootNodes(); + } + + reorderAfterDrop( + kind: 'repo' | 'localGroup', + draggedKey: string, + targetKey?: string + ): void { + if (kind === 'localGroup') { + const groups = this.getLocalSkillsGroups().filter(g => !g.isActive).map(g => g.path); + this.localGroupOrder = this.ensureOrder(this.localGroupOrder, groups); + this.localGroupOrder = this.reorderKey(this.localGroupOrder, draggedKey, targetKey); + void this.memento?.update('agentskills.localGroupOrder', this.localGroupOrder); + this._onDidChangeTreeData.fire(); + return; + } + + const repos = ConfigManager.getRepos().map(r => r.url); + this.repoOrder = this.ensureOrder(this.repoOrder, repos); + this.repoOrder = this.reorderKey(this.repoOrder, draggedKey, targetKey); + void this.memento?.update('agentskills.repoOrder', this.repoOrder); + this._onDidChangeTreeData.fire(); + } + + private reorderKey(order: string[], draggedKey: string, targetKey?: string): string[] { + if (draggedKey === targetKey) return order; + const next = order.filter(k => k !== draggedKey); + if (!targetKey) { + next.push(draggedKey); + return next; + } + const index = next.indexOf(targetKey); + if (index === -1) { + next.push(draggedKey); + return next; + } + next.splice(index, 0, draggedKey); + return next; + } + + moveUp(node: SkillRepo | LocalSkillsGroup): void { + this.moveNode(node, -1); + } + + moveDown(node: SkillRepo | LocalSkillsGroup): void { + this.moveNode(node, 1); + } + + private moveNode(node: SkillRepo | LocalSkillsGroup, delta: -1 | 1): void { + if (isLocalSkillsGroup(node)) { + if (node.isActive) return; + const key = node.path; + const groups = this.getLocalSkillsGroups().map(g => g.path); + this.localGroupOrder = this.ensureOrder(this.localGroupOrder, groups); + this.localGroupOrder = this.moveKey(this.localGroupOrder, key, delta); + void this.memento?.update('agentskills.localGroupOrder', this.localGroupOrder); + this._onDidChangeTreeData.fire(); + return; + } + + const key = node.url; + const repos = ConfigManager.getRepos().map(r => r.url); + this.repoOrder = this.ensureOrder(this.repoOrder, repos); + this.repoOrder = this.moveKey(this.repoOrder, key, delta); + void this.memento?.update('agentskills.repoOrder', this.repoOrder); + this._onDidChangeTreeData.fire(); + } + + private ensureOrder(existing: string[], keys: string[]): string[] { + const keySet = new Set(keys); + const normalized = existing.filter(k => keySet.has(k)); + const existingSet = new Set(normalized); + for (const k of keys) { + if (!existingSet.has(k)) normalized.push(k); + } + return normalized; + } + + private moveKey(order: string[], key: string, delta: -1 | 1): string[] { + const next = order.slice(); + const currentIndex = next.indexOf(key); + if (currentIndex === -1) { + next.push(key); + return this.moveKey(next, key, delta); + } + const targetIndex = currentIndex + delta; + if (targetIndex < 0 || targetIndex >= next.length) return next; + next.splice(currentIndex, 1); + next.splice(targetIndex, 0, key); + return next; + } + + private getSkillKey(skill: Skill): string { + return `${skill.repoUrl}::${skill.name}`; + } + + private getWorkspaceRoot(): string | undefined { + return vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + } + + private getInstalledSkillsDir(): string | undefined { + const root = this.getWorkspaceRoot(); + return root ? getProjectSkillsDir(root, detectIde(process.env, vscode.env.appName)) : undefined; + } + + private isSkillInstalled(skillName: string): boolean { + const dir = this.getInstalledSkillsDir(); + return dir ? fs.existsSync(path.join(dir, skillName, 'SKILL.md')) : false; + } + + private updateInstalledSkillHashes(): void { + this.installedSkillHashes.clear(); + const dir = this.getInstalledSkillsDir(); + if (!dir || !fs.existsSync(dir)) return; + + try { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory() && !entry.name.startsWith('.')) { + const skillDir = path.join(dir, entry.name); + if (fs.existsSync(path.join(skillDir, 'SKILL.md'))) { + const hash = getCachedSkillHash(skillDir); + this.installedSkillHashes.set(entry.name, hash); + } + } + } + } catch (e) { + console.error('Error updating installed skill hashes:', e); + } + } + + private getSkillMatchStatus(skill: Skill): SkillMatchStatus { + const installedHash = this.installedSkillHashes.get(skill.name); + if (!installedHash) { + return SkillMatchStatus.NotInstalled; + } + + // Compare with repo skill hash + if (skill.localPath && fs.existsSync(skill.localPath)) { + const repoHash = getCachedSkillHash(skill.localPath); + return installedHash === repoHash ? SkillMatchStatus.Matched : SkillMatchStatus.Conflict; + } + + return SkillMatchStatus.NotInstalled; + } + + private getLocalSkillsGroups(): LocalSkillsGroup[] { + const root = this.getWorkspaceRoot(); + if (!root) return []; + + const activeProjectSkillsPath = getProjectSkillsDir(root, detectIde(process.env, vscode.env.appName)); + + return getSkillDirectories(root) + .map(dir => { + return { + type: 'local-group' as const, + name: dir.displayName, + path: dir.path, + icon: dir.icon, + exists: fs.existsSync(dir.path), + isActive: dir.path === activeProjectSkillsPath + }; + }) + .filter(group => { + if (group.isActive) return true; + if (!group.exists) return false; + try { + const entries = fs.readdirSync(group.path, { withFileTypes: true }); + return entries.some(entry => + entry.isDirectory() && + !entry.name.startsWith('.') && + fs.existsSync(path.join(group.path, entry.name, 'SKILL.md')) + ); + } catch { + return false; + } + }); + } + + private getLocalSkillsFromGroup(group: LocalSkillsGroup): LocalSkill[] { + if (!fs.existsSync(group.path)) return []; + + const skills: LocalSkill[] = []; + try { + const entries = fs.readdirSync(group.path, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory() && !entry.name.startsWith('.')) { + const skillPath = path.join(group.path, entry.name); + const skillMdPath = path.join(skillPath, 'SKILL.md'); + if (fs.existsSync(skillMdPath)) { + const content = fs.readFileSync(skillMdPath, 'utf-8'); + skills.push({ + type: 'local-skill', + name: entry.name, + description: extractYamlField(content, 'description') || '', + path: skillPath, + groupPath: group.path, + }); + } + } + } + } catch (e) { + console.error(`Error scanning local skills in ${group.path}:`, e); + } + return skills; + } + + getTreeItem(element: TreeNode): vscode.TreeItem { + // Local skills group + if (isLocalSkillsGroup(element)) { + const item = new vscode.TreeItem( + element.name, + vscode.TreeItemCollapsibleState.Collapsed + ); + + item.contextValue = 'localSkillsGroup'; + const root = this.getWorkspaceRoot(); + const isProjectGroup = root && element.path.startsWith(root); + item.iconPath = element.isActive + ? new vscode.ThemeIcon('layers-active') + : isProjectGroup + ? new vscode.ThemeIcon('layers') + : new vscode.ThemeIcon(element.icon); + item.tooltip = this.getLocalGroupTooltip(element); + return item; + } + + // Local skill + if (isLocalSkill(element)) { + const item = new vscode.TreeItem(element.name, vscode.TreeItemCollapsibleState.None); + + // Check if this is a project skill or personal directory skill + const root = this.getWorkspaceRoot(); + const isProjectSkill = root && element.groupPath.startsWith(root); + + if (isProjectSkill) { + // Project skills: have delete and open buttons + item.contextValue = 'localSkill'; + item.description = this.truncateDescription(element.description ?? ''); + item.iconPath = new vscode.ThemeIcon('tools'); + } else { + // Personal directory skills: have install button only (like git skills) + item.contextValue = 'personalSkill'; + + // Check if already installed in project + const installed = this.isSkillInstalled(element.name); + + const truncatedDesc = element.description.length > 10 + ? element.description.substring(0, 10) + '...' + : element.description; + item.description = truncatedDesc; + + if (installed) { + item.contextValue = 'personalSkillInstalled'; + item.iconPath = new vscode.ThemeIcon('check'); + } else { + item.iconPath = new vscode.ThemeIcon('tools'); + } + } + + item.tooltip = new vscode.MarkdownString( + `**${element.name}**\n\n` + + `${element.description}\n\n` + + `---\n\n` + + `📁 ${element.path}` + ); + + return item; + } + + // Skill repo + if (isSkillRepo(element)) { + const item = new vscode.TreeItem(element.name, vscode.TreeItemCollapsibleState.Collapsed); + item.contextValue = 'skillRepo'; + item.description = element.branch ? element.branch : ''; + item.tooltip = this.getRepoTooltip(element); + item.iconPath = new vscode.ThemeIcon('github'); + return item; + } + + // Repo skill + if (isSkill(element)) { + const matchStatus = element.matchStatus ?? this.getSkillMatchStatus(element); + const item = new vscode.TreeItem(element.name, vscode.TreeItemCollapsibleState.None); + const key = this.getSkillKey(element); + + // Handle conflict state - skill installed from different repo + if (matchStatus === SkillMatchStatus.Conflict) { + item.contextValue = 'skillConflict'; + item.description = this.truncateDescription(element.description ?? ''); + item.tooltip = new vscode.MarkdownString( + `**${element.name}**\n\n` + + `${element.description}\n\n` + + `---\n\n` + + `⚠️ **冲突**: 此skill已从其他仓库安装,版本与当前仓库不一致。\n\n` + + `如需安装此版本,请先删除已安装的版本。` + ); + item.iconPath = new vscode.ThemeIcon('warning', new vscode.ThemeColor('disabledForeground')); + // No checkbox for conflicting skills + return item; + } + + // Normal installed or not installed state + const installed = matchStatus === SkillMatchStatus.Matched; + + item.contextValue = installed ? 'skillInstalled' : 'skill'; + item.description = this.truncateDescription(element.description ?? ''); + item.tooltip = `${element.name}\n${element.description}\n${element.path}`; + + let iconName = 'tools'; + if (installed) { + iconName = 'check'; + } + item.iconPath = new vscode.ThemeIcon(iconName); + + item.checkboxState = this.checkedSkills.has(key) + ? vscode.TreeItemCheckboxState.Checked + : vscode.TreeItemCheckboxState.Unchecked; + + return item; + } + + // Fallback + return new vscode.TreeItem('Unknown'); + } + + getParent(element: TreeNode): TreeNode | undefined { + if (isSkillRepo(element) || isLocalSkillsGroup(element)) return undefined; + + if (isSkill(element)) { + const repo = this.repoByUrl.get(element.repoUrl); + if (repo) return repo; + const fallback = ConfigManager.getRepos().find(r => r.url === element.repoUrl); + if (fallback) return this.reconcileRepo(fallback); + return undefined; + } + + if (isLocalSkill(element)) { + const group = this.localGroupByPath.get(element.groupPath); + if (group) return group; + const fallback = this.getLocalSkillsGroups().find(g => g.path === element.groupPath); + if (fallback) return this.reconcileLocalGroup(fallback); + return undefined; + } + + return undefined; + } + + async getChildren(element?: TreeNode): Promise { + if (!element) { + const nodes = await this.getRootNodes(); + this.lastRootNodes = nodes; + return nodes; + } + + // Local skills group children + if (isLocalSkillsGroup(element)) { + const skills = this.localSkillsIndex.get(element.path) ?? this.getLocalSkillsFromGroup(element); + return this.filterSkills(skills); + } + + // Skill repo children + if (isSkillRepo(element)) { + try { + // Ensure hashes are up to date + if (this.installedSkillHashes.size === 0) { + this.updateInstalledSkillHashes(); + } + + const indexed = this.repoSkillsIndex.get(element.url); + if (indexed) { + return this.filterSkills(indexed); + } + + if (!GitService.isRepoCloned(element.url)) { + const ok = await this.ensureRepoConnectivity(element.url); + if (!ok) return []; + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Initializing ${element.name}...`, + cancellable: false + }, async () => { + await GitService.pullRepo(element.url, element.branch); + }); + } + + const skills = await GitService.getSkillsFromRepo(element.url, element.branch); + const normalized = this.normalizeRepoSkills(skills); + this.repoSkillsIndex.set(element.url, normalized); + this._onDidChangeTreeData.fire(); + return this.filterSkills(normalized); + } catch (e) { + vscode.window.showErrorMessage(`Failed to load skills from ${element.name}: ${e}`); + return []; + } + } + + return []; + } + + private async buildIndex(): Promise { + if (this.indexingPromise) return this.indexingPromise; + this.indexingPromise = this.doBuildIndex().finally(() => { + this.indexingPromise = undefined; + }); + return this.indexingPromise; + } + + private async doBuildIndex(): Promise { + this.log('Indexing started'); + const localGroups = this.getLocalSkillsGroups(); + this.log(`Local groups: ${localGroups.length}`); + this.localSkillsIndex.clear(); + for (const group of localGroups) { + const skills = this.getLocalSkillsFromGroup(group); + this.localSkillsIndex.set(group.path, skills); + } + + const repos = await this.getVisibleRepos(); + this.log(`Visible repos: ${repos.length}`); + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Indexing skills...', + cancellable: false + }, async (progress) => { + for (let i = 0; i < repos.length; i++) { + const repo = repos[i]; + progress.report({ message: `${repo.name} (${i + 1}/${repos.length})` }); + this.log(`Indexing repo: ${repo.url}`); + try { + if (!GitService.isRepoCloned(repo.url)) { + await GitService.pullRepo(repo.url, repo.branch); + } + const skills = await GitService.getSkillsFromRepo(repo.url, repo.branch); + this.repoSkillsIndex.set(repo.url, this.normalizeRepoSkills(skills)); + this.log(`Indexed repo: ${repo.url} (skills: ${skills.length})`); + } catch (e) { + console.error(`Failed to index repo ${repo.url}`, e); + } + } + }); + + this.log('Indexing finished'); + this._onDidChangeTreeData.fire(); + } + + private normalizeRepoSkills(skills: Skill[]): Skill[] { + skills.forEach(s => { + s.matchStatus = this.getSkillMatchStatus(s); + s.installed = s.matchStatus === SkillMatchStatus.Matched; + this.skillCache.set(this.getSkillKey(s), s); + }); + return skills; + } + + private filterSkills(skills: T[]): T[] { + if (!this.searchQuery) return skills; + const q = this.searchQuery.toLowerCase(); + return skills.filter(s => { + const name = (s.name ?? '').toLowerCase(); + const desc = ('description' in s ? (s.description ?? '') : '').toLowerCase(); + return name.includes(q) || desc.includes(q); + }); + } + + private getLocalGroupTotalCount(group: LocalSkillsGroup): number { + const skills = this.localSkillsIndex.get(group.path); + if (skills) return skills.length; + return this.getLocalSkillsFromGroup(group).length; + } + + private getLocalGroupMatchedCount(group: LocalSkillsGroup): number { + const skills = this.localSkillsIndex.get(group.path) ?? []; + return this.filterSkills(skills).length; + } + + private getRepoTotalCount(repo: SkillRepo): number { + return this.repoSkillsIndex.get(repo.url)?.length ?? 0; + } + + private getRepoMatchedCount(repo: SkillRepo): number { + const skills = this.repoSkillsIndex.get(repo.url) ?? []; + return this.filterSkills(skills).length; + } + + private sortLocalGroups(groups: LocalSkillsGroup[]): LocalSkillsGroup[] { + const orderIndex = new Map(this.localGroupOrder.map((k, i) => [k, i])); + const originalIndex = new Map(groups.map((g, i) => [g.path, i])); + return groups.slice().sort((a, b) => { + if (a.isActive !== b.isActive) return a.isActive ? -1 : 1; + const ai = orderIndex.get(a.path); + const bi = orderIndex.get(b.path); + if (ai !== undefined || bi !== undefined) return (ai ?? Number.MAX_SAFE_INTEGER) - (bi ?? Number.MAX_SAFE_INTEGER); + return (originalIndex.get(a.path) ?? 0) - (originalIndex.get(b.path) ?? 0); + }); + } + + private sortRepos(repos: SkillRepo[]): SkillRepo[] { + const orderIndex = new Map(this.repoOrder.map((k, i) => [k, i])); + const originalIndex = new Map(repos.map((r, i) => [r.url, i])); + return repos.slice().sort((a, b) => { + const ai = orderIndex.get(a.url); + const bi = orderIndex.get(b.url); + if (ai !== undefined || bi !== undefined) return (ai ?? Number.MAX_SAFE_INTEGER) - (bi ?? Number.MAX_SAFE_INTEGER); + return (originalIndex.get(a.url) ?? 0) - (originalIndex.get(b.url) ?? 0); + }); + } + + private async getVisibleRepos(): Promise { + const repos = this.sortRepos(ConfigManager.getRepos()); + const checks = await this.mapWithConcurrency(repos, 6, async (repo) => { + if (GitService.isRepoCloned(repo.url)) return repo; + const ok = await this.ensureRepoConnectivity(repo.url); + return ok ? repo : undefined; + }); + return checks.filter((r): r is SkillRepo => Boolean(r)); + } + + private async ensureRepoConnectivity(url: string): Promise { + const now = Date.now(); + const cached = this.repoConnectivity.get(url); + if (cached && now - cached.checkedAt < 10 * 60 * 1000) return cached.ok; + const ok = await GitService.checkRepoConnectivity(url, 200); + if (!ok) { + this.warn(`Repo not reachable by DNS, hidden: ${url}`); + } + this.repoConnectivity.set(url, { ok, checkedAt: now }); + return ok; + } + + private log(message: string): void { + this.outputChannel?.appendLine(`[INFO] ${message}`); + console.log(message); + } + + private warn(message: string): void { + this.outputChannel?.appendLine(`[WARN] ${message}`); + console.warn(message); + } + + private async getRootNodes(): Promise { + const rawLocal = this.getLocalSkillsGroups(); + const localGroups = this.sortLocalGroups( + rawLocal.map(g => this.reconcileLocalGroup(g)) + ); + + const rawRepos = await this.getVisibleRepos(); + const repos = this.sortRepos( + rawRepos.map(r => this.reconcileRepo(r)) + ); + + const rootNodes: TreeNode[] = []; + const activeLocal = localGroups.filter(g => g.isActive); + const otherLocal = localGroups.filter(g => !g.isActive); + rootNodes.push(...activeLocal, ...otherLocal); + + if (this.searchQuery) { + const filteredLocal = rootNodes.filter(node => { + if (!isLocalSkillsGroup(node)) return true; + if (node.isActive) return true; + return this.getLocalGroupMatchedCount(node) > 0; + }); + const filteredRepos = repos.filter(r => this.getRepoMatchedCount(r) > 0); + return filteredLocal.concat(filteredRepos); + } + + return rootNodes.concat(repos); + } + + private reconcileLocalGroup(group: LocalSkillsGroup): LocalSkillsGroup { + const existing = this.localGroupByPath.get(group.path); + if (existing) { + Object.assign(existing, group); + return existing; + } + this.localGroupByPath.set(group.path, group); + return group; + } + + private reconcileRepo(repo: SkillRepo): SkillRepo { + const existing = this.repoByUrl.get(repo.url); + if (existing) { + Object.assign(existing, repo); + return existing; + } + this.repoByUrl.set(repo.url, repo); + return repo; + } + + private truncateDescription(description: string): string { + const text = description ?? ''; + return text.length > 10 ? text.substring(0, 10) + '...' : text; + } + + private getRepoTooltip(repo: SkillRepo): vscode.MarkdownString { + const total = this.getRepoTotalCount(repo); + const installed = this.getRepoInstalledCount(repo); + const matched = this.searchQuery ? this.getRepoMatchedCount(repo) : undefined; + + const lines: string[] = []; + lines.push(`**${repo.name}**`); + lines.push(''); + lines.push(`[${repo.url}](${repo.url})`); + lines.push(''); + if (repo.branch) lines.push(`Branch: ${repo.branch}`); + lines.push(`Total: ${total}`); + if (matched !== undefined) lines.push(`Matched: ${matched}`); + lines.push(`Installed: ${installed}`); + + const md = new vscode.MarkdownString(lines.join('\n')); + md.isTrusted = true; + return md; + } + + private getLocalGroupTooltip(group: LocalSkillsGroup): vscode.MarkdownString { + const total = this.getLocalGroupTotalCount(group); + const matched = this.searchQuery ? this.getLocalGroupMatchedCount(group) : undefined; + + const lines: string[] = []; + lines.push(`**${group.name}**`); + lines.push(''); + lines.push(`[${group.path}](${vscode.Uri.file(group.path)})`); + lines.push(''); + lines.push(`Total: ${total}`); + if (matched !== undefined) lines.push(`Matched: ${matched}`); + + const md = new vscode.MarkdownString(lines.join('\n')); + md.isTrusted = true; + return md; + } + + private getRepoInstalledCount(repo: SkillRepo): number { + const skills = this.repoSkillsIndex.get(repo.url) ?? []; + return skills.filter(s => { + const status = s.matchStatus ?? this.getSkillMatchStatus(s); + return status === SkillMatchStatus.Matched || status === SkillMatchStatus.Conflict; + }).length; + } + + + + private async mapWithConcurrency( + items: T[], + concurrency: number, + mapper: (item: T) => Promise + ): Promise { + const results: R[] = new Array(items.length) as R[]; + let nextIndex = 0; + + const worker = async () => { + while (true) { + const current = nextIndex++; + if (current >= items.length) return; + results[current] = await mapper(items[current]); + } + }; + + const workers = new Array(Math.min(concurrency, items.length)).fill(0).map(() => worker()); + await Promise.all(workers); + return results; + } + + private getAllMatchingRepoSkills(): Skill[] { + const all: Skill[] = []; + for (const skills of this.repoSkillsIndex.values()) { + all.push(...this.filterSkills(skills)); + } + return all; + } +} diff --git a/vscode-extension/src/types.ts b/vscode-extension/src/types.ts new file mode 100644 index 0000000..2effdd7 --- /dev/null +++ b/vscode-extension/src/types.ts @@ -0,0 +1,49 @@ +/** + * Match status between installed skill and repository skill + */ +export enum SkillMatchStatus { + NotInstalled = 'not_installed', // Skill not installed in workspace + Matched = 'matched', // Installed skill matches this repo version + Conflict = 'conflict', // Installed skill differs from this repo version +} + +export interface Skill { + name: string; + description: string; + path: string; // Relative path in the repo + repoUrl: string; + localPath?: string; // If locally cloned + installed?: boolean; // Whether installed in current workspace + matchStatus?: SkillMatchStatus; // Match status with installed version +} + +export interface SkillRepo { + url: string; + name: string; // User friendly name or derived from URL + branch?: string; + isPreset?: boolean; // Whether this is a preset (built-in) repository +} + +/** + * Represents a local skills directory group in the TreeView + */ +export interface LocalSkillsGroup { + type: 'local-group'; + name: string; // Display name, e.g. "project/.claude/skills" + path: string; // Actual directory path + icon: string; // Icon name + exists: boolean; // Whether the directory exists + isActive: boolean; // Whether this is the active skills directory for current IDE +} + +/** + * Represents a skill in a local directory + */ +export interface LocalSkill { + type: 'local-skill'; + name: string; + description: string; + path: string; // Full path to skill directory + groupPath: string; // Parent group directory path +} + diff --git a/vscode-extension/src/utils/fs.ts b/vscode-extension/src/utils/fs.ts new file mode 100644 index 0000000..cf0ddba --- /dev/null +++ b/vscode-extension/src/utils/fs.ts @@ -0,0 +1,32 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Recursively copy a directory or file + */ +export function copyRecursiveSync(src: string, dest: string): void { + const exists = fs.existsSync(src); + const stats = exists && fs.statSync(src); + const isDirectory = stats && stats.isDirectory(); + + if (isDirectory) { + if (fs.existsSync(dest)) { + fs.rmSync(dest, { recursive: true, force: true }); + } + fs.mkdirSync(dest, { recursive: true }); + fs.readdirSync(src).forEach((childItemName) => { + copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); + }); + } else { + fs.copyFileSync(src, dest); + } +} + +/** + * Ensure a directory exists, creating it if necessary + */ +export function ensureDir(dirPath: string): void { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} diff --git a/vscode-extension/src/utils/ide.ts b/vscode-extension/src/utils/ide.ts new file mode 100644 index 0000000..50b30f0 --- /dev/null +++ b/vscode-extension/src/utils/ide.ts @@ -0,0 +1,98 @@ +import { join } from 'path'; + +export enum IdeType { + VSCODE = 'vscode', + CURSOR = 'cursor', + TRAE = 'trae', + ANTIGRAVITY = 'antigravity', + QODER = 'qoder', + WINDSURF = 'windsurf', + CODEBUDDY = 'codebuddy', +} + +export interface IdeConfig { + type: IdeType; + skillsDir: string; +} + +export const IDE_CONFIGS: Record = { + [IdeType.ANTIGRAVITY]: { + type: IdeType.ANTIGRAVITY, + skillsDir: join('.agent', 'skills'), + }, + [IdeType.CODEBUDDY]: { + type: IdeType.CODEBUDDY, + skillsDir: join('.codebuddy', 'skills'), + }, + [IdeType.CURSOR]: { + type: IdeType.CURSOR, + skillsDir: join('.cursor', 'skills'), + }, + [IdeType.QODER]: { + type: IdeType.QODER, + skillsDir: join('.qoder', 'skills'), + }, + [IdeType.TRAE]: { + type: IdeType.TRAE, + skillsDir: join('.trae', 'skills'), + }, + [IdeType.VSCODE]: { + type: IdeType.VSCODE, + skillsDir: join('.github', 'skills'), + }, + [IdeType.WINDSURF]: { + type: IdeType.WINDSURF, + skillsDir: join('.windsurf', 'skills'), + }, +}; + +export function resolveIdeType(appName: string): IdeType { + const lowerAppName = appName.toLowerCase(); + + if (lowerAppName.includes('codebuddy')) return IdeType.CODEBUDDY; + if (lowerAppName.includes('cursor')) return IdeType.CURSOR; + if (lowerAppName.includes('qoder')) return IdeType.QODER; + if (lowerAppName.includes('trae')) return IdeType.TRAE; + if (lowerAppName.includes('antigravity')) return IdeType.ANTIGRAVITY; + if (lowerAppName.includes('windsurf')) return IdeType.WINDSURF; + + return IdeType.VSCODE; +} + +/** + * Detect the current IDE based on environment variables. + */ +export function detectIde(env: NodeJS.ProcessEnv = process.env, appNameHint?: string): IdeType { + // Check explicit override first + if (env.AGENTSKILLS_IDE) { + const override = env.AGENTSKILLS_IDE.toLowerCase(); + if (Object.values(IdeType).includes(override as IdeType)) { + return override as IdeType; + } + } + + const brand = env.VSCODE_BRAND || ''; + if (brand) return resolveIdeType(brand); + + const appName = env.VSCODE_ENV_APPNAME || env.PROG_IDE_NAME || ''; + if (appName) return resolveIdeType(appName); + + if (appNameHint) return resolveIdeType(appNameHint); + + return IdeType.VSCODE; +} + +/** + * Get configuration for a specific IDE or the detected one. + */ +export function getIdeConfig(ide: IdeType | string): IdeConfig { + const ideType = Object.values(IdeType).includes(ide as IdeType) + ? ide as IdeType + : resolveIdeType(ide); + return IDE_CONFIGS[ideType]; +} + +export function getProjectSkillsDir(workspaceRoot: string, ide: IdeType | string): string { + const config = getIdeConfig(ide); + return join(workspaceRoot, config.skillsDir); +} diff --git a/vscode-extension/src/utils/skillCompare.ts b/vscode-extension/src/utils/skillCompare.ts new file mode 100644 index 0000000..789a24b --- /dev/null +++ b/vscode-extension/src/utils/skillCompare.ts @@ -0,0 +1,78 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; + +/** + * Compute a hash of a single file's content + */ +function computeFileHash(filePath: string): string { + const content = fs.readFileSync(filePath); + return crypto.createHash('md5').update(content).digest('hex'); +} + +/** + * Compute the hash of the SKILL.md file in the directory + * Only uses SKILL.md for comparison as requested + */ +export function computeSkillHash(dirPath: string): string { + const skillMdPath = path.join(dirPath, 'SKILL.md'); + if (!fs.existsSync(skillMdPath)) { + return ''; + } + return computeFileHash(skillMdPath); +} + +/** + * Compare two skill directories based on their SKILL.md content + * @param dir1 First directory path + * @param dir2 Second directory path + * @returns true if SKILL.md files are identical, false otherwise + */ +export function compareSkillDirectories(dir1: string, dir2: string): boolean { + if (!fs.existsSync(dir1) || !fs.existsSync(dir2)) { + return false; + } + + const hash1 = computeSkillHash(dir1); + const hash2 = computeSkillHash(dir2); + + return hash1 === hash2; +} + +/** + * Cache for skill hashes to avoid recomputing + */ +const hashCache = new Map(); + +/** + * Get cached skill hash (of SKILL.md), recomputing if file has been modified + */ +export function getCachedSkillHash(dirPath: string): string { + const skillMdPath = path.join(dirPath, 'SKILL.md'); + if (!fs.existsSync(skillMdPath)) { + return ''; + } + + try { + const stats = fs.statSync(skillMdPath); + const mtime = stats.mtimeMs; + const cached = hashCache.get(dirPath); + + if (cached && cached.mtime === mtime) { + return cached.hash; + } + + const hash = computeSkillHash(dirPath); + hashCache.set(dirPath, { hash, mtime }); + return hash; + } catch { + return ''; + } +} + +/** + * Clear the hash cache (useful after installations/deletions) + */ +export function clearHashCache(): void { + hashCache.clear(); +} diff --git a/vscode-extension/src/utils/skills.ts b/vscode-extension/src/utils/skills.ts new file mode 100644 index 0000000..ec38d78 --- /dev/null +++ b/vscode-extension/src/utils/skills.ts @@ -0,0 +1,54 @@ +import * as os from 'os'; +import * as path from 'path'; +import { IDE_CONFIGS } from './ide'; + +/** + * Represents a skill directory with metadata + */ +export interface SkillDirectory { + path: string; + displayName: string; + isProject: boolean; + icon: string; +} + +function toDisplayPath(relPath: string): string { + return relPath.replace(/\\/g, '/'); +} + +/** + * Get all skill directories with metadata + */ +export function getSkillDirectories(workspaceRoot: string): SkillDirectory[] { + const uniqueProjectSkillDirs = Array.from( + new Set(Object.values(IDE_CONFIGS).map(c => c.skillsDir)) + ); + + const projectDirs: SkillDirectory[] = uniqueProjectSkillDirs.map((relDir) => { + const displayName = toDisplayPath(relDir); + const icon = displayName.startsWith('.claude/') ? 'folder' : 'folder-library'; + return { + path: path.join(workspaceRoot, relDir), + displayName, + isProject: true, + icon, + }; + }); + + const globalDirs: SkillDirectory[] = [ + { + path: path.join(os.homedir(), '.claude', 'skills'), + displayName: '~/.claude/skills', + isProject: false, + icon: 'home', + }, + { + path: path.join(os.homedir(), '.codex', 'skills'), + displayName: '~/.codex/skills', + isProject: false, + icon: 'home', + }, + ]; + + return [...projectDirs, ...globalDirs]; +} diff --git a/vscode-extension/src/utils/yaml.ts b/vscode-extension/src/utils/yaml.ts new file mode 100644 index 0000000..409101e --- /dev/null +++ b/vscode-extension/src/utils/yaml.ts @@ -0,0 +1,14 @@ +/** + * Extract field from YAML frontmatter + */ +export function extractYamlField(content: string, field: string): string { + const match = content.match(new RegExp(`^${field}:\\s*(.+?)$`, 'm')); + return match ? match[1].trim() : ''; +} + +/** + * Validate SKILL.md has proper YAML frontmatter + */ +export function hasValidFrontmatter(content: string): boolean { + return content.trim().startsWith('---'); +} diff --git a/vscode-extension/tsconfig.json b/vscode-extension/tsconfig.json new file mode 100644 index 0000000..eea8477 --- /dev/null +++ b/vscode-extension/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "outDir": "out", + "lib": [ + "es2020" + ], + "sourceMap": true, + "rootDir": "src", + "strict": true + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} \ No newline at end of file