SuperCharge Claude Code v1.0.0 - Complete Customization Package
Features: - 30+ Custom Skills (cognitive, development, UI/UX, autonomous agents) - RalphLoop autonomous agent integration - Multi-AI consultation (Qwen) - Agent management system with sync capabilities - Custom hooks for session management - MCP servers integration - Plugin marketplace setup - Comprehensive installation script Components: - Skills: always-use-superpowers, ralph, brainstorming, ui-ux-pro-max, etc. - Agents: 100+ agents across engineering, marketing, product, etc. - Hooks: session-start-superpowers, qwen-consult, ralph-auto-trigger - Commands: /brainstorm, /write-plan, /execute-plan - MCP Servers: zai-mcp-server, web-search-prime, web-reader, zread - Binaries: ralphloop wrapper Installation: ./supercharge.sh
This commit is contained in:
579
plugins/core/plugin-manager.ts
Normal file
579
plugins/core/plugin-manager.ts
Normal 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
|
||||
Reference in New Issue
Block a user