Reorganize: Move all skills to skills/ folder

- Created skills/ directory
- Moved 272 skills to skills/ subfolder
- Kept agents/ at root level
- Kept installation scripts and docs at root level

Repository structure:
- skills/           - All 272 skills from skills.sh
- agents/           - Agent definitions
- *.sh, *.ps1       - Installation scripts
- README.md, etc.   - Documentation

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
admin
2026-01-23 18:05:17 +00:00
Unverified
parent 2b4e974878
commit b723e2bd7d
4083 changed files with 1056 additions and 1098063 deletions

View File

@@ -0,0 +1,579 @@
/**
* Claude Code Plugin Manager - Conduit-style Plugin System
*
* Features:
* - GitHub-based plugin discovery
* - Secure plugin installation with validation
* - Version management and updates
* - Dependency resolution
* - Plugin lifecycle management
*/
import fs from 'fs/promises'
import path from 'path'
import { spawn } from 'child_process'
import { createHash } from 'crypto'
// ============================================================================
// TYPES AND INTERFACES
// ============================================================================
export interface PluginMetadata {
name: string
version: string
description: string
author: string
license?: string
repository?: string
homepage?: string
keywords?: string[]
claude: {
minVersion?: string
maxVersion?: string
permissions: string[]
hooks?: HookDefinition[]
commands?: CommandDefinition[]
skills?: SkillDefinition[]
}
dependencies?: Record<string, string>
}
export interface HookDefinition {
event: string
handler: string
priority?: number
condition?: string
}
export interface CommandDefinition {
name: string
description: string
handler: string
permissions?: string[]
}
export interface SkillDefinition {
name: string
description: string
file: string
}
export interface InstalledPlugin {
metadata: PluginMetadata
installPath: string
version: string
installedAt: string
lastUpdated: string
scope: 'global' | 'project'
projectPath?: string
enabled: boolean
integrity: string
}
export interface MarketplaceSource {
type: 'github' | 'directory' | 'npm'
url?: string
repo?: string
path?: string
lastUpdated?: string
}
export interface PluginDiscoveryResult {
name: string
description: string
version: string
author: string
source: string
downloads?: number
stars?: number
updated: string
}
// ============================================================================
// PLUGIN MANAGER CLASS
// ============================================================================
export class PluginManager {
private pluginsDir: string
private cacheDir: string
private marketplacesFile: string
private installedFile: string
private configDir: string
constructor(claudeDir: string = path.join(process.env.HOME || '', '.claude')) {
this.configDir = claudeDir
this.pluginsDir = path.join(claudeDir, 'plugins')
this.cacheDir = path.join(this.pluginsDir, 'cache')
this.marketplacesFile = path.join(this.pluginsDir, 'known_marketplaces.json')
this.installedFile = path.join(this.pluginsDir, 'installed_plugins.json')
}
// ============================================================================
// INITIALIZATION
// ============================================================================
async initialize(): Promise<void> {
await this.ensureDirectories()
await this.loadMarketplaces()
await this.loadInstalledPlugins()
}
private async ensureDirectories(): Promise<void> {
const dirs = [
this.pluginsDir,
this.cacheDir,
path.join(this.pluginsDir, 'marketplaces'),
path.join(this.pluginsDir, 'tmp'),
]
for (const dir of dirs) {
try {
await fs.mkdir(dir, { recursive: true })
} catch (error) {
// Directory might already exist
}
}
}
// ============================================================================
// MARKETPLACE MANAGEMENT
// ============================================================================
async addMarketplace(name: string, source: MarketplaceSource): Promise<void> {
const marketplaces = await this.loadMarketplaces()
marketplaces[name] = {
source,
installLocation: path.join(this.pluginsDir, 'marketplaces', name),
lastUpdated: new Date().toISOString(),
}
await fs.writeFile(
this.marketplacesFile,
JSON.stringify(marketplaces, null, 2)
)
// Clone/download marketplace if it's a GitHub repo
if (source.type === 'github' && source.repo) {
await this.cloneRepository(
`https://github.com/${source.repo}.git`,
path.join(this.pluginsDir, 'marketplaces', name)
)
}
}
async loadMarketplaces(): Promise<Record<string, any>> {
try {
const content = await fs.readFile(this.marketplacesFile, 'utf-8')
return JSON.parse(content)
} catch {
return {}
}
}
// ============================================================================
// PLUGIN DISCOVERY
// ============================================================================
async discoverPlugins(query?: string): Promise<PluginDiscoveryResult[]> {
const marketplaces = await this.loadMarketplaces()
const results: PluginDiscoveryResult[] = []
for (const [name, marketplace]: Object.entries(marketplaces)) {
const mp = marketplace as any
const pluginsPath = path.join(mp.installLocation, 'plugins')
try {
const pluginDirs = await fs.readdir(pluginsPath)
for (const pluginDir of pluginDirs) {
const pluginJsonPath = path.join(
pluginsPath,
pluginDir,
'.claude-plugin',
'plugin.json'
)
try {
const metadata = JSON.parse(
await fs.readFile(pluginJsonPath, 'utf-8')
) as PluginMetadata
// Filter by query if provided
if (
query &&
!metadata.name.toLowerCase().includes(query.toLowerCase()) &&
!metadata.description?.toLowerCase().includes(query.toLowerCase()) &&
!metadata.keywords?.some((k) =>
k.toLowerCase().includes(query.toLowerCase())
)
) {
continue
}
results.push({
name: metadata.name,
description: metadata.description,
version: metadata.version,
author: metadata.author,
source: name,
updated: new Date().toISOString(),
})
} catch {
// Skip invalid plugins
}
}
} catch {
// Marketplace might not have plugins directory
}
}
return results
}
// ============================================================================
// PLUGIN INSTALLATION
// ============================================================================
async installPlugin(
marketplace: string,
pluginName: string,
scope: 'global' | 'project' = 'global'
): Promise<InstalledPlugin> {
const marketplaces = await this.loadMarketplaces()
const mp = marketplaces[marketplace] as any
if (!mp) {
throw new Error(`Marketplace "${marketplace}" not found`)
}
const sourcePath = path.join(mp.installLocation, 'plugins', pluginName)
const pluginJsonPath = path.join(sourcePath, '.claude-plugin', 'plugin.json')
// Read plugin metadata
const metadata: PluginMetadata = JSON.parse(
await fs.readFile(pluginJsonPath, 'utf-8')
)
// Validate permissions
await this.validatePermissions(metadata.claude.permissions)
// Calculate integrity hash
const integrity = await this.calculateIntegrity(sourcePath)
// Install to cache
const versionedPath = path.join(
this.cacheDir,
marketplace,
`${pluginName}-${metadata.version}`
)
await fs.mkdir(versionedPath, { recursive: true })
await this.copyDirectory(sourcePath, versionedPath)
const installedPlugin: InstalledPlugin = {
metadata,
installPath: versionedPath,
version: metadata.version,
installedAt: new Date().toISOString(),
lastUpdated: new Date().toISOString(),
scope,
enabled: true,
integrity,
}
// Register plugin
await this.registerPlugin(installedPlugin)
// Run install script if present
const installScript = path.join(sourcePath, 'install.sh')
try {
await this.runScript(installScript, versionedPath)
} catch {
// No install script or failed
}
return installedPlugin
}
async installFromGitHub(
repo: string,
scope: 'global' | 'project' = 'global'
): Promise<InstalledPlugin> {
const [owner, name] = repo.split('/')
const tempDir = path.join(this.pluginsDir, 'tmp', `${name}-${Date.now()}`)
// Clone repository
await this.cloneRepository(`https://github.com/${repo}.git`, tempDir)
// Read plugin metadata
const pluginJsonPath = path.join(tempDir, '.claude-plugin', 'plugin.json')
const metadata: PluginMetadata = JSON.parse(
await fs.readFile(pluginJsonPath, 'utf-8')
)
// Validate and install
const integrity = await this.calculateIntegrity(tempDir)
const versionedPath = path.join(
this.cacheDir,
'github',
`${name}-${metadata.version}`
)
await fs.mkdir(versionedPath, { recursive: true })
await this.copyDirectory(tempDir, versionedPath)
const installedPlugin: InstalledPlugin = {
metadata,
installPath: versionedPath,
version: metadata.version,
installedAt: new Date().toISOString(),
lastUpdated: new Date().toISOString(),
scope,
enabled: true,
integrity,
}
await this.registerPlugin(installedPlugin)
// Cleanup temp dir
await fs.rm(tempDir, { recursive: true, force: true })
return installedPlugin
}
// ============================================================================
// PLUGIN MANAGEMENT
// ============================================================================
async uninstallPlugin(name: string, marketplace?: string): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = marketplace ? `${name}@${marketplace}` : name
if (!installed[key]) {
throw new Error(`Plugin "${name}" is not installed`)
}
const plugin = installed[key][0] as InstalledPlugin
// Run uninstall script if present
const uninstallScript = path.join(plugin.installPath, 'uninstall.sh')
try {
await this.runScript(uninstallScript, plugin.installPath)
} catch {
// No uninstall script or failed
}
// Remove from cache
await fs.rm(plugin.installPath, { recursive: true, force: true })
// Unregister
delete installed[key]
await fs.writeFile(
this.installedFile,
JSON.stringify({ version: 2, plugins: installed }, null, 2)
)
}
async enablePlugin(name: string, marketplace?: string): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = marketplace ? `${name}@${marketplace}` : name
if (installed[key]) {
installed[key][0].enabled = true
await fs.writeFile(
this.installedFile,
JSON.stringify({ version: 2, plugins: installed }, null, 2)
)
}
}
async disablePlugin(name: string, marketplace?: string): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = marketplace ? `${name}@${marketplace}` : name
if (installed[key]) {
installed[key][0].enabled = false
await fs.writeFile(
this.installedFile,
JSON.stringify({ version: 2, plugins: installed }, null, 2)
)
}
}
async updatePlugin(name: string, marketplace?: string): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = marketplace ? `${name}@${marketplace}` : name
if (!installed[key]) {
throw new Error(`Plugin "${name}" is not installed`)
}
const plugin = installed[key][0] as InstalledPlugin
// Reinstall to update
if (plugin.metadata.repository) {
await this.installFromGitHub(
plugin.metadata.repository.replace('https://github.com/', ''),
plugin.scope
)
} else {
// Reinstall from marketplace
// Implementation depends on marketplace type
}
}
// ============================================================================
// PLUGIN LOADING
// ============================================================================
async loadInstalledPlugins(): Promise<Record<string, InstalledPlugin[]>> {
try {
const content = await fs.readFile(this.installedFile, 'utf-8')
const data = JSON.parse(content)
return data.plugins || {}
} catch {
return {}
}
}
async getEnabledPlugins(): Promise<InstalledPlugin[]> {
const installed = await this.loadInstalledPlugins()
const enabled: InstalledPlugin[] = []
for (const plugins of Object.values(installed)) {
for (const plugin of plugins) {
if (plugin.enabled) {
enabled.push(plugin as InstalledPlugin)
}
}
}
return enabled
}
// ============================================================================
// SECURITY
// ============================================================================
private async validatePermissions(permissions: string[]): Promise<void> {
const allowedPermissions = [
'read:files',
'write:files',
'execute:commands',
'network:request',
'read:config',
'write:config',
'hook:events',
]
for (const perm of permissions) {
if (!allowedPermissions.includes(perm)) {
throw new Error(`Unknown permission: ${perm}`)
}
}
}
private async calculateIntegrity(dirPath: string): Promise<string> {
const hash = createHash('sha256')
const files = await this.getAllFiles(dirPath)
for (const file of files.sort()) {
const content = await fs.readFile(file)
hash.update(content)
}
return hash.digest('hex')
}
private async getAllFiles(dirPath: string): Promise<string[]> {
const files: string[] = []
const entries = await fs.readdir(dirPath, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name)
if (entry.isDirectory()) {
files.push(...(await this.getAllFiles(fullPath)))
} else {
files.push(fullPath)
}
}
return files
}
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
private async registerPlugin(plugin: InstalledPlugin): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = `${plugin.metadata.name}@${plugin.installPath.split('/').slice(-2).join('/')}`
if (!installed[key]) {
installed[key] = []
}
installed[key].push(plugin)
await fs.writeFile(
this.installedFile,
JSON.stringify({ version: 2, plugins: installed }, null, 2)
)
}
private async cloneRepository(repoUrl: string, targetPath: string): Promise<void> {
return new Promise((resolve, reject) => {
const git = spawn('git', ['clone', '--depth', '1', repoUrl, targetPath], {
stdio: 'inherit',
})
git.on('close', (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`Git clone failed with code ${code}`))
}
})
})
}
private async copyDirectory(source: string, target: string): Promise<void> {
await fs.mkdir(target, { recursive: true })
const entries = await fs.readdir(source, { withFileTypes: true })
for (const entry of entries) {
const srcPath = path.join(source, entry.name)
const destPath = path.join(target, entry.name)
if (entry.isDirectory()) {
await this.copyDirectory(srcPath, destPath)
} else {
await fs.copyFile(srcPath, destPath)
}
}
}
private async runScript(scriptPath: string, cwd: string): Promise<void> {
return new Promise((resolve, reject) => {
const shell = spawn('bash', [scriptPath], {
cwd,
stdio: 'inherit',
})
shell.on('close', (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`Script failed with code ${code}`))
}
})
})
}
}
// ============================================================================
// EXPORTS
// ============================================================================
export default PluginManager