@within-7/minto 0.3.6 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{cli.js → cli.cjs} +25 -23
- package/dist/commands/agents/AgentsCommand.js +459 -655
- package/dist/commands/agents/AgentsCommand.js.map +2 -2
- package/dist/commands/agents/types.js +1 -0
- package/dist/commands/agents/types.js.map +2 -2
- package/dist/commands/agents/utils/fileOperations.js +96 -36
- package/dist/commands/agents/utils/fileOperations.js.map +3 -3
- package/dist/commands/agents/utils/index.js +3 -1
- package/dist/commands/agents/utils/index.js.map +2 -2
- package/dist/commands/context.js +54 -23
- package/dist/commands/context.js.map +2 -2
- package/dist/commands/export.js +673 -93
- package/dist/commands/export.js.map +2 -2
- package/dist/commands/language.js +110 -0
- package/dist/commands/language.js.map +7 -0
- package/dist/commands/mcp-interactive.js +419 -217
- package/dist/commands/mcp-interactive.js.map +2 -2
- package/dist/commands/model.js +415 -66
- package/dist/commands/model.js.map +2 -2
- package/dist/commands/new.js +56 -0
- package/dist/commands/new.js.map +7 -0
- package/dist/commands/permissions.js +75 -49
- package/dist/commands/permissions.js.map +2 -2
- package/dist/commands/plugin.js +882 -185
- package/dist/commands/plugin.js.map +3 -3
- package/dist/commands/resume.js +251 -16
- package/dist/commands/resume.js.map +2 -2
- package/dist/commands/sandbox.js +168 -70
- package/dist/commands/sandbox.js.map +2 -2
- package/dist/commands/sessions.js +224 -0
- package/dist/commands/sessions.js.map +7 -0
- package/dist/commands/setup.js +596 -109
- package/dist/commands/setup.js.map +2 -2
- package/dist/commands/stats.js +292 -0
- package/dist/commands/stats.js.map +7 -0
- package/dist/commands/status.js +75 -7
- package/dist/commands/status.js.map +2 -2
- package/dist/commands/undo.js +154 -180
- package/dist/commands/undo.js.map +2 -2
- package/dist/commands.js +6 -0
- package/dist/commands.js.map +2 -2
- package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
- package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
- package/dist/components/Config.js +9 -8
- package/dist/components/Config.js.map +2 -2
- package/dist/components/HeaderBar.js +2 -1
- package/dist/components/HeaderBar.js.map +2 -2
- package/dist/components/Help.js +166 -32
- package/dist/components/Help.js.map +2 -2
- package/dist/components/HotkeyHelpPanel.js +46 -44
- package/dist/components/HotkeyHelpPanel.js.map +2 -2
- package/dist/components/InfoPanel/InfoPanel.js +123 -0
- package/dist/components/InfoPanel/InfoPanel.js.map +7 -0
- package/dist/components/InfoPanel/index.js +5 -0
- package/dist/components/InfoPanel/index.js.map +7 -0
- package/dist/components/InfoPanel/types.js +1 -0
- package/dist/components/InfoPanel/types.js.map +7 -0
- package/dist/components/Logo.js +5 -2
- package/dist/components/Logo.js.map +2 -2
- package/dist/components/MCPServerApprovalDialog.js +6 -5
- package/dist/components/MCPServerApprovalDialog.js.map +2 -2
- package/dist/components/MCPServerMultiselectDialog.js +5 -4
- package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
- package/dist/components/MessageSelector.js +4 -3
- package/dist/components/MessageSelector.js.map +2 -2
- package/dist/components/ModelConfig.js +13 -12
- package/dist/components/ModelConfig.js.map +2 -2
- package/dist/components/ModelListManager.js +4 -3
- package/dist/components/ModelListManager.js.map +2 -2
- package/dist/components/ModelSelector/BrandTextInput.js +43 -0
- package/dist/components/ModelSelector/BrandTextInput.js.map +7 -0
- package/dist/components/ModelSelector/ModelSelector.js +419 -501
- package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
- package/dist/components/ModelSelector/WizardContainer.js +45 -0
- package/dist/components/ModelSelector/WizardContainer.js.map +7 -0
- package/dist/components/ModelSelector/index.js +1 -3
- package/dist/components/ModelSelector/index.js.map +2 -2
- package/dist/components/PromptInput.js +77 -44
- package/dist/components/PromptInput.js.map +2 -2
- package/dist/components/SensitiveFileWarning.js +12 -8
- package/dist/components/SensitiveFileWarning.js.map +2 -2
- package/dist/components/SimpleSelector/SimpleSelector.js +154 -0
- package/dist/components/SimpleSelector/SimpleSelector.js.map +7 -0
- package/dist/components/SimpleSelector/index.js +5 -0
- package/dist/components/SimpleSelector/index.js.map +7 -0
- package/dist/components/SimpleSelector/types.js +1 -0
- package/dist/components/SimpleSelector/types.js.map +7 -0
- package/dist/components/StatusOverlayContent.js +21 -0
- package/dist/components/StatusOverlayContent.js.map +7 -0
- package/dist/components/TabbedListView/ScrollableList.js +117 -0
- package/dist/components/TabbedListView/ScrollableList.js.map +7 -0
- package/dist/components/TabbedListView/SearchInput.js +23 -0
- package/dist/components/TabbedListView/SearchInput.js.map +7 -0
- package/dist/components/TabbedListView/TabBar.js +20 -0
- package/dist/components/TabbedListView/TabBar.js.map +7 -0
- package/dist/components/TabbedListView/TabbedListView.js +246 -0
- package/dist/components/TabbedListView/TabbedListView.js.map +7 -0
- package/dist/components/TabbedListView/index.js +11 -0
- package/dist/components/TabbedListView/index.js.map +7 -0
- package/dist/components/TabbedListView/types.js +1 -0
- package/dist/components/TabbedListView/types.js.map +7 -0
- package/dist/components/TodoChangeBlock.js +6 -5
- package/dist/components/TodoChangeBlock.js.map +3 -3
- package/dist/components/TodoPanel.js +6 -3
- package/dist/components/TodoPanel.js.map +3 -3
- package/dist/components/TrustDialog.js +6 -5
- package/dist/components/TrustDialog.js.map +2 -2
- package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js +2 -1
- package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js.map +2 -2
- package/dist/constants/macros.js +1 -1
- package/dist/constants/macros.js.map +1 -1
- package/dist/constants/product.js +2 -2
- package/dist/constants/product.js.map +1 -1
- package/dist/constants/prompts.js +17 -0
- package/dist/constants/prompts.js.map +2 -2
- package/dist/constants/toolInputExamples.js +5 -1
- package/dist/constants/toolInputExamples.js.map +2 -2
- package/dist/core/backupHook.js +29 -0
- package/dist/core/backupHook.js.map +7 -0
- package/dist/core/config/defaults.js +8 -2
- package/dist/core/config/defaults.js.map +2 -2
- package/dist/core/config/schema.js +14 -2
- package/dist/core/config/schema.js.map +2 -2
- package/dist/core/costTracker.js +0 -16
- package/dist/core/costTracker.js.map +2 -2
- package/dist/core/tokenStatsManager.js +5 -0
- package/dist/core/tokenStatsManager.js.map +2 -2
- package/dist/cost-tracker.js +0 -16
- package/dist/cost-tracker.js.map +2 -2
- package/dist/entrypoints/bootstrap.js +56 -0
- package/dist/entrypoints/bootstrap.js.map +7 -0
- package/dist/entrypoints/cli.js +164 -23
- package/dist/entrypoints/cli.js.map +3 -3
- package/dist/history.js +75 -15
- package/dist/history.js.map +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/index.js.map +2 -2
- package/dist/i18n/locales/en.js +582 -1
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/zh-CN.js +582 -1
- package/dist/i18n/locales/zh-CN.js.map +2 -2
- package/dist/i18n/types.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +2 -2
- package/dist/messages.js +11 -0
- package/dist/messages.js.map +2 -2
- package/dist/permissions.js.map +2 -2
- package/dist/query.js +9 -0
- package/dist/query.js.map +2 -2
- package/dist/screens/REPL.js +45 -7
- package/dist/screens/REPL.js.map +2 -2
- package/dist/services/customCommands.js +44 -16
- package/dist/services/customCommands.js.map +2 -2
- package/dist/services/plugins/lspServers.js +1 -1
- package/dist/services/plugins/lspServers.js.map +2 -2
- package/dist/services/plugins/pluginRuntime.js +2 -1
- package/dist/services/plugins/pluginRuntime.js.map +2 -2
- package/dist/services/plugins/pluginValidation.js +10 -3
- package/dist/services/plugins/pluginValidation.js.map +2 -2
- package/dist/services/plugins/skillMarketplace.js +16 -8
- package/dist/services/plugins/skillMarketplace.js.map +2 -2
- package/dist/services/systemReminder.js +17 -6
- package/dist/services/systemReminder.js.map +2 -2
- package/dist/tools/FileEditTool/FileEditTool.js +7 -0
- package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
- package/dist/tools/FileWriteTool/FileWriteTool.js +7 -0
- package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
- package/dist/tools/MultiEditTool/MultiEditTool.js +7 -0
- package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
- package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -0
- package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
- package/dist/tools/TaskTool/TaskTool.js +179 -1
- package/dist/tools/TaskTool/TaskTool.js.map +2 -2
- package/dist/tools/TodoWriteTool/prompt.js +21 -0
- package/dist/tools/TodoWriteTool/prompt.js.map +2 -2
- package/dist/tools/URLFetcherTool/prompt.js +14 -9
- package/dist/tools/URLFetcherTool/prompt.js.map +2 -2
- package/dist/tools/WebSearchTool/prompt.js +12 -6
- package/dist/tools/WebSearchTool/prompt.js.map +2 -2
- package/dist/types/PermissionMode.js +30 -1
- package/dist/types/PermissionMode.js.map +2 -2
- package/dist/types/plugin.js +2 -4
- package/dist/types/plugin.js.map +2 -2
- package/dist/utils/agentHookExecutor.js +103 -0
- package/dist/utils/agentHookExecutor.js.map +7 -0
- package/dist/utils/agentLoader.js +272 -32
- package/dist/utils/agentLoader.js.map +2 -2
- package/dist/utils/agentMemory.js +134 -0
- package/dist/utils/agentMemory.js.map +7 -0
- package/dist/utils/claudeCodeSync.js +439 -0
- package/dist/utils/claudeCodeSync.js.map +7 -0
- package/dist/utils/config.js +52 -24
- package/dist/utils/config.js.map +2 -2
- package/dist/utils/configPaths.js +199 -0
- package/dist/utils/configPaths.js.map +7 -0
- package/dist/utils/execFileNoThrow.js +2 -1
- package/dist/utils/execFileNoThrow.js.map +2 -2
- package/dist/utils/historyManager.js +234 -0
- package/dist/utils/historyManager.js.map +7 -0
- package/dist/utils/marketplaceManager.js +80 -43
- package/dist/utils/marketplaceManager.js.map +2 -2
- package/dist/utils/messages.js +13 -8
- package/dist/utils/messages.js.map +2 -2
- package/dist/utils/migration/index.js +37 -0
- package/dist/utils/migration/index.js.map +7 -0
- package/dist/utils/migration/migrateHistory.js +273 -0
- package/dist/utils/migration/migrateHistory.js.map +7 -0
- package/dist/utils/migration/migrateTodos.js +323 -0
- package/dist/utils/migration/migrateTodos.js.map +7 -0
- package/dist/utils/pasteCache.js +309 -0
- package/dist/utils/pasteCache.js.map +7 -0
- package/dist/utils/pluginInstaller.js +34 -24
- package/dist/utils/pluginInstaller.js.map +2 -2
- package/dist/utils/pluginLoader.js +54 -28
- package/dist/utils/pluginLoader.js.map +2 -2
- package/dist/utils/repoFetcher.js +110 -0
- package/dist/utils/repoFetcher.js.map +7 -0
- package/dist/utils/sessionIndex.js +192 -0
- package/dist/utils/sessionIndex.js.map +7 -0
- package/dist/utils/sessionTracker.js +170 -0
- package/dist/utils/sessionTracker.js.map +7 -0
- package/dist/utils/skillLoader.js +103 -5
- package/dist/utils/skillLoader.js.map +2 -2
- package/dist/utils/stats.js +417 -0
- package/dist/utils/stats.js.map +7 -0
- package/dist/utils/stringSubstitution.js +106 -0
- package/dist/utils/stringSubstitution.js.map +7 -0
- package/dist/utils/teamConfig.js +156 -14
- package/dist/utils/teamConfig.js.map +2 -2
- package/dist/utils/terminal.js +1 -1
- package/dist/utils/terminal.js.map +2 -2
- package/dist/utils/todoStorage.js +51 -19
- package/dist/utils/todoStorage.js.map +2 -2
- package/dist/utils/tooling/safeRender.js.map +2 -2
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +71 -28
- package/scripts/{postinstall.js → postinstall.cjs} +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/pasteCache.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Paste Cache Module\n *\n * Provides content-addressable caching for large pasted text.\n * Uses SHA-256 hashing for deduplication and efficient storage.\n *\n * Storage: ~/.minto/history/paste-cache/{hash}.txt\n *\n * Features:\n * - Content-addressable storage (hash-based)\n * - Automatic deduplication\n * - Size threshold for caching (default: 1000 chars)\n * - TTL-based cleanup for old entries\n */\n\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n readdirSync,\n statSync,\n unlinkSync,\n} from 'fs'\nimport { join } from 'path'\nimport crypto from 'crypto'\nimport { CONFIG_PATHS, ensureConfigDirs } from './configPaths'\nimport { debug as debugLogger } from './debugLogger'\n\n/**\n * Paste cache entry metadata\n */\nexport interface PasteCacheEntry {\n /** SHA-256 hash of the content */\n hash: string\n /** Original content length */\n length: number\n /** Line count */\n lineCount: number\n /** Timestamp when cached */\n timestamp: number\n}\n\n/**\n * Options for caching paste content\n */\nexport interface PasteCacheOptions {\n /** Minimum character count to trigger caching (default: 1000) */\n threshold?: number\n /** Cache entry time-to-live in milliseconds (default: 7 days) */\n ttl?: number\n}\n\n// Default configuration\nconst DEFAULT_THRESHOLD = 1000 // Characters\nconst DEFAULT_TTL = 7 * 24 * 60 * 60 * 1000 // 7 days in milliseconds\nconst MAX_CACHE_SIZE = 100 // Maximum number of cached entries\n\n/**\n * Get the paste cache directory path\n */\nexport function getPasteCacheDir(): string {\n return CONFIG_PATHS.pasteCache\n}\n\n/**\n * Ensure the paste cache directory exists\n */\nfunction ensureCacheDir(): void {\n const cacheDir = getPasteCacheDir()\n if (!existsSync(cacheDir)) {\n mkdirSync(cacheDir, { recursive: true })\n }\n}\n\n/**\n * Calculate SHA-256 hash of content\n */\nfunction hashContent(content: string): string {\n return crypto.createHash('sha256').update(content, 'utf-8').digest('hex')\n}\n\n/**\n * Get the file path for a cached entry\n */\nfunction getCacheFilePath(hash: string): string {\n return join(getPasteCacheDir(), `${hash}.txt`)\n}\n\n/**\n * Get the metadata file path for a cached entry\n */\nfunction getMetadataFilePath(hash: string): string {\n return join(getPasteCacheDir(), `${hash}.meta.json`)\n}\n\n/**\n * Count lines in content\n */\nfunction countLines(content: string): number {\n return (content.match(/\\r\\n|\\r|\\n/g) || []).length + 1\n}\n\n/**\n * Cache paste content if it exceeds the threshold\n *\n * @param content The pasted content to cache\n * @param options Caching options\n * @returns Cache entry if cached, null if below threshold\n */\nexport function cachePaste(\n content: string,\n options: PasteCacheOptions = {},\n): PasteCacheEntry | null {\n const { threshold = DEFAULT_THRESHOLD } = options\n\n // Check if content exceeds threshold\n if (content.length < threshold) {\n return null\n }\n\n try {\n ensureCacheDir()\n\n // Calculate hash\n const hash = hashContent(content)\n const filePath = getCacheFilePath(hash)\n const metadataPath = getMetadataFilePath(hash)\n\n // Create metadata\n const entry: PasteCacheEntry = {\n hash,\n length: content.length,\n lineCount: countLines(content),\n timestamp: Date.now(),\n }\n\n // Write content file (only if not already cached)\n if (!existsSync(filePath)) {\n writeFileSync(filePath, content, 'utf-8')\n writeFileSync(metadataPath, JSON.stringify(entry, null, 2), 'utf-8')\n\n debugLogger.trace('PASTE_CACHE_STORED', {\n hash: hash.slice(0, 16),\n length: content.length,\n lineCount: entry.lineCount,\n })\n } else {\n // Update timestamp for existing entry\n const existingMeta = getMetadata(hash)\n if (existingMeta) {\n existingMeta.timestamp = Date.now()\n writeFileSync(\n metadataPath,\n JSON.stringify(existingMeta, null, 2),\n 'utf-8',\n )\n }\n\n debugLogger.trace('PASTE_CACHE_HIT', {\n hash: hash.slice(0, 16),\n })\n }\n\n return entry\n } catch (error) {\n debugLogger.error('PASTE_CACHE_STORE_FAILED', {\n error: error instanceof Error ? error.message : String(error),\n contentLength: content.length,\n })\n return null\n }\n}\n\n/**\n * Get cached paste content by hash\n *\n * @param hash SHA-256 hash of the content\n * @returns The cached content, or null if not found\n */\nexport function getPaste(hash: string): string | null {\n const filePath = getCacheFilePath(hash)\n\n if (!existsSync(filePath)) {\n debugLogger.trace('PASTE_CACHE_MISS', { hash: hash.slice(0, 16) })\n return null\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8')\n\n debugLogger.trace('PASTE_CACHE_RETRIEVED', {\n hash: hash.slice(0, 16),\n length: content.length,\n })\n\n return content\n } catch (error) {\n debugLogger.error('PASTE_CACHE_READ_FAILED', {\n error: error instanceof Error ? error.message : String(error),\n hash: hash.slice(0, 16),\n })\n return null\n }\n}\n\n/**\n * Get metadata for a cached entry\n *\n * @param hash SHA-256 hash of the content\n * @returns Metadata entry, or null if not found\n */\nexport function getMetadata(hash: string): PasteCacheEntry | null {\n const metadataPath = getMetadataFilePath(hash)\n\n if (!existsSync(metadataPath)) {\n return null\n }\n\n try {\n const content = readFileSync(metadataPath, 'utf-8')\n return JSON.parse(content) as PasteCacheEntry\n } catch {\n return null\n }\n}\n\n/**\n * Check if content is cached\n *\n * @param content Content to check\n * @returns true if cached, false otherwise\n */\nexport function isCached(content: string): boolean {\n const hash = hashContent(content)\n return existsSync(getCacheFilePath(hash))\n}\n\n/**\n * Get hash for content without caching\n *\n * @param content Content to hash\n * @returns SHA-256 hash\n */\nexport function getContentHash(content: string): string {\n return hashContent(content)\n}\n\n/**\n * Create a placeholder for cached content\n * This is what gets stored in history instead of the full content\n *\n * @param entry Cache entry\n * @returns Placeholder string\n */\nexport function createPlaceholder(entry: PasteCacheEntry): string {\n return `[Cached paste: ${entry.lineCount} lines, ${entry.length} chars, hash:${entry.hash.slice(0, 8)}]`\n}\n\n/**\n * Parse a placeholder to extract the hash\n *\n * @param placeholder Placeholder string\n * @returns Hash if found, null otherwise\n */\nexport function parsePlaceholder(placeholder: string): string | null {\n const match = placeholder.match(/hash:([a-f0-9]{8,64})\\]/)\n return match ? match[1] : null\n}\n\n/**\n * Expand a placeholder to its full content\n *\n * @param placeholder Placeholder string\n * @returns Full content if found, original placeholder otherwise\n */\nexport function expandPlaceholder(placeholder: string): string {\n const hash = parsePlaceholder(placeholder)\n if (!hash) {\n return placeholder\n }\n\n // Try to find the full hash (we only stored 8 chars in placeholder)\n const cacheDir = getPasteCacheDir()\n if (!existsSync(cacheDir)) {\n return placeholder\n }\n\n try {\n const files = readdirSync(cacheDir)\n const matchingFile = files.find(\n f => f.startsWith(hash) && f.endsWith('.txt'),\n )\n\n if (matchingFile) {\n const fullHash = matchingFile.replace('.txt', '')\n const content = getPaste(fullHash)\n if (content) {\n return content\n }\n }\n } catch {\n // Ignore errors\n }\n\n return placeholder\n}\n\n/**\n * List all cached entries\n *\n * @returns Array of cache entries\n */\nexport function listCachedEntries(): PasteCacheEntry[] {\n const cacheDir = getPasteCacheDir()\n\n if (!existsSync(cacheDir)) {\n return []\n }\n\n const entries: PasteCacheEntry[] = []\n\n try {\n const files = readdirSync(cacheDir)\n\n for (const file of files) {\n if (file.endsWith('.meta.json')) {\n const metadataPath = join(cacheDir, file)\n try {\n const content = readFileSync(metadataPath, 'utf-8')\n const entry = JSON.parse(content) as PasteCacheEntry\n entries.push(entry)\n } catch {\n // Skip invalid metadata files\n }\n }\n }\n } catch {\n // Ignore errors\n }\n\n return entries.sort((a, b) => b.timestamp - a.timestamp)\n}\n\n/**\n * Clean up expired cache entries\n *\n * @param options Cleanup options\n * @returns Number of entries removed\n */\nexport function cleanupExpiredEntries(options: PasteCacheOptions = {}): number {\n const { ttl = DEFAULT_TTL } = options\n const cacheDir = getPasteCacheDir()\n const now = Date.now()\n let removed = 0\n\n if (!existsSync(cacheDir)) {\n return 0\n }\n\n try {\n const entries = listCachedEntries()\n\n for (const entry of entries) {\n if (now - entry.timestamp > ttl) {\n try {\n const filePath = getCacheFilePath(entry.hash)\n const metadataPath = getMetadataFilePath(entry.hash)\n\n if (existsSync(filePath)) {\n unlinkSync(filePath)\n }\n if (existsSync(metadataPath)) {\n unlinkSync(metadataPath)\n }\n\n removed++\n debugLogger.trace('PASTE_CACHE_EXPIRED_REMOVED', {\n hash: entry.hash.slice(0, 16),\n age: now - entry.timestamp,\n })\n } catch {\n // Ignore individual file deletion errors\n }\n }\n }\n\n if (removed > 0) {\n debugLogger.info('PASTE_CACHE_CLEANUP_COMPLETE', {\n removed,\n remaining: entries.length - removed,\n })\n }\n } catch (error) {\n debugLogger.error('PASTE_CACHE_CLEANUP_FAILED', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n return removed\n}\n\n/**\n * Enforce maximum cache size by removing oldest entries\n *\n * @returns Number of entries removed\n */\nexport function enforceMaxCacheSize(): number {\n const entries = listCachedEntries()\n\n if (entries.length <= MAX_CACHE_SIZE) {\n return 0\n }\n\n // Sort by timestamp (oldest first) and remove excess\n const toRemove = entries\n .sort((a, b) => a.timestamp - b.timestamp)\n .slice(0, entries.length - MAX_CACHE_SIZE)\n\n let removed = 0\n\n for (const entry of toRemove) {\n try {\n const filePath = getCacheFilePath(entry.hash)\n const metadataPath = getMetadataFilePath(entry.hash)\n\n if (existsSync(filePath)) {\n unlinkSync(filePath)\n }\n if (existsSync(metadataPath)) {\n unlinkSync(metadataPath)\n }\n\n removed++\n } catch {\n // Ignore individual file deletion errors\n }\n }\n\n if (removed > 0) {\n debugLogger.info('PASTE_CACHE_SIZE_ENFORCED', {\n removed,\n maxSize: MAX_CACHE_SIZE,\n })\n }\n\n return removed\n}\n\n/**\n * Get cache statistics\n */\nexport function getCacheStats(): {\n entryCount: number\n totalSize: number\n oldestEntry: number | null\n newestEntry: number | null\n} {\n const entries = listCachedEntries()\n\n if (entries.length === 0) {\n return {\n entryCount: 0,\n totalSize: 0,\n oldestEntry: null,\n newestEntry: null,\n }\n }\n\n const totalSize = entries.reduce((sum, e) => sum + e.length, 0)\n const timestamps = entries.map(e => e.timestamp)\n\n return {\n entryCount: entries.length,\n totalSize,\n oldestEntry: Math.min(...timestamps),\n newestEntry: Math.max(...timestamps),\n }\n}\n\n/**\n * Clear all cached entries\n * Use with caution - this is destructive\n */\nexport function clearCache(): number {\n const cacheDir = getPasteCacheDir()\n let removed = 0\n\n if (!existsSync(cacheDir)) {\n return 0\n }\n\n try {\n const files = readdirSync(cacheDir)\n\n for (const file of files) {\n try {\n unlinkSync(join(cacheDir, file))\n removed++\n } catch {\n // Ignore individual file deletion errors\n }\n }\n\n debugLogger.info('PASTE_CACHE_CLEARED', { removed })\n } catch (error) {\n debugLogger.error('PASTE_CACHE_CLEAR_FAILED', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n return removed\n}\n"],
|
|
5
|
+
"mappings": "AAeA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AACP,SAAS,YAAY;AACrB,OAAO,YAAY;AACnB,SAAS,oBAAsC;AAC/C,SAAS,SAAS,mBAAmB;AA2BrC,MAAM,oBAAoB;AAC1B,MAAM,cAAc,IAAI,KAAK,KAAK,KAAK;AACvC,MAAM,iBAAiB;AAKhB,SAAS,mBAA2B;AACzC,SAAO,aAAa;AACtB;AAKA,SAAS,iBAAuB;AAC9B,QAAM,WAAW,iBAAiB;AAClC,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,cAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,EACzC;AACF;AAKA,SAAS,YAAY,SAAyB;AAC5C,SAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,SAAS,OAAO,EAAE,OAAO,KAAK;AAC1E;AAKA,SAAS,iBAAiB,MAAsB;AAC9C,SAAO,KAAK,iBAAiB,GAAG,GAAG,IAAI,MAAM;AAC/C;AAKA,SAAS,oBAAoB,MAAsB;AACjD,SAAO,KAAK,iBAAiB,GAAG,GAAG,IAAI,YAAY;AACrD;AAKA,SAAS,WAAW,SAAyB;AAC3C,UAAQ,QAAQ,MAAM,aAAa,KAAK,CAAC,GAAG,SAAS;AACvD;AASO,SAAS,WACd,SACA,UAA6B,CAAC,GACN;AACxB,QAAM,EAAE,YAAY,kBAAkB,IAAI;AAG1C,MAAI,QAAQ,SAAS,WAAW;AAC9B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,mBAAe;AAGf,UAAM,OAAO,YAAY,OAAO;AAChC,UAAM,WAAW,iBAAiB,IAAI;AACtC,UAAM,eAAe,oBAAoB,IAAI;AAG7C,UAAM,QAAyB;AAAA,MAC7B;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,WAAW,WAAW,OAAO;AAAA,MAC7B,WAAW,KAAK,IAAI;AAAA,IACtB;AAGA,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,oBAAc,UAAU,SAAS,OAAO;AACxC,oBAAc,cAAc,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,OAAO;AAEnE,kBAAY,MAAM,sBAAsB;AAAA,QACtC,MAAM,KAAK,MAAM,GAAG,EAAE;AAAA,QACtB,QAAQ,QAAQ;AAAA,QAChB,WAAW,MAAM;AAAA,MACnB,CAAC;AAAA,IACH,OAAO;AAEL,YAAM,eAAe,YAAY,IAAI;AACrC,UAAI,cAAc;AAChB,qBAAa,YAAY,KAAK,IAAI;AAClC;AAAA,UACE;AAAA,UACA,KAAK,UAAU,cAAc,MAAM,CAAC;AAAA,UACpC;AAAA,QACF;AAAA,MACF;AAEA,kBAAY,MAAM,mBAAmB;AAAA,QACnC,MAAM,KAAK,MAAM,GAAG,EAAE;AAAA,MACxB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,gBAAY,MAAM,4BAA4B;AAAA,MAC5C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC5D,eAAe,QAAQ;AAAA,IACzB,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAQO,SAAS,SAAS,MAA6B;AACpD,QAAM,WAAW,iBAAiB,IAAI;AAEtC,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,gBAAY,MAAM,oBAAoB,EAAE,MAAM,KAAK,MAAM,GAAG,EAAE,EAAE,CAAC;AACjE,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,UAAU,OAAO;AAE9C,gBAAY,MAAM,yBAAyB;AAAA,MACzC,MAAM,KAAK,MAAM,GAAG,EAAE;AAAA,MACtB,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAED,WAAO;AAAA,EACT,SAAS,OAAO;AACd,gBAAY,MAAM,2BAA2B;AAAA,MAC3C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC5D,MAAM,KAAK,MAAM,GAAG,EAAE;AAAA,IACxB,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAQO,SAAS,YAAY,MAAsC;AAChE,QAAM,eAAe,oBAAoB,IAAI;AAE7C,MAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,cAAc,OAAO;AAClD,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,SAAS,SAAS,SAA0B;AACjD,QAAM,OAAO,YAAY,OAAO;AAChC,SAAO,WAAW,iBAAiB,IAAI,CAAC;AAC1C;AAQO,SAAS,eAAe,SAAyB;AACtD,SAAO,YAAY,OAAO;AAC5B;AASO,SAAS,kBAAkB,OAAgC;AAChE,SAAO,kBAAkB,MAAM,SAAS,WAAW,MAAM,MAAM,gBAAgB,MAAM,KAAK,MAAM,GAAG,CAAC,CAAC;AACvG;AAQO,SAAS,iBAAiB,aAAoC;AACnE,QAAM,QAAQ,YAAY,MAAM,yBAAyB;AACzD,SAAO,QAAQ,MAAM,CAAC,IAAI;AAC5B;AAQO,SAAS,kBAAkB,aAA6B;AAC7D,QAAM,OAAO,iBAAiB,WAAW;AACzC,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,iBAAiB;AAClC,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,QAAQ,YAAY,QAAQ;AAClC,UAAM,eAAe,MAAM;AAAA,MACzB,OAAK,EAAE,WAAW,IAAI,KAAK,EAAE,SAAS,MAAM;AAAA,IAC9C;AAEA,QAAI,cAAc;AAChB,YAAM,WAAW,aAAa,QAAQ,QAAQ,EAAE;AAChD,YAAM,UAAU,SAAS,QAAQ;AACjC,UAAI,SAAS;AACX,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAOO,SAAS,oBAAuC;AACrD,QAAM,WAAW,iBAAiB;AAElC,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,UAA6B,CAAC;AAEpC,MAAI;AACF,UAAM,QAAQ,YAAY,QAAQ;AAElC,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,SAAS,YAAY,GAAG;AAC/B,cAAM,eAAe,KAAK,UAAU,IAAI;AACxC,YAAI;AACF,gBAAM,UAAU,aAAa,cAAc,OAAO;AAClD,gBAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,kBAAQ,KAAK,KAAK;AAAA,QACpB,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AACzD;AAQO,SAAS,sBAAsB,UAA6B,CAAC,GAAW;AAC7E,QAAM,EAAE,MAAM,YAAY,IAAI;AAC9B,QAAM,WAAW,iBAAiB;AAClC,QAAM,MAAM,KAAK,IAAI;AACrB,MAAI,UAAU;AAEd,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,kBAAkB;AAElC,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,MAAM,YAAY,KAAK;AAC/B,YAAI;AACF,gBAAM,WAAW,iBAAiB,MAAM,IAAI;AAC5C,gBAAM,eAAe,oBAAoB,MAAM,IAAI;AAEnD,cAAI,WAAW,QAAQ,GAAG;AACxB,uBAAW,QAAQ;AAAA,UACrB;AACA,cAAI,WAAW,YAAY,GAAG;AAC5B,uBAAW,YAAY;AAAA,UACzB;AAEA;AACA,sBAAY,MAAM,+BAA+B;AAAA,YAC/C,MAAM,MAAM,KAAK,MAAM,GAAG,EAAE;AAAA,YAC5B,KAAK,MAAM,MAAM;AAAA,UACnB,CAAC;AAAA,QACH,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,GAAG;AACf,kBAAY,KAAK,gCAAgC;AAAA,QAC/C;AAAA,QACA,WAAW,QAAQ,SAAS;AAAA,MAC9B,CAAC;AAAA,IACH;AAAA,EACF,SAAS,OAAO;AACd,gBAAY,MAAM,8BAA8B;AAAA,MAC9C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAOO,SAAS,sBAA8B;AAC5C,QAAM,UAAU,kBAAkB;AAElC,MAAI,QAAQ,UAAU,gBAAgB;AACpC,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,QACd,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS,EACxC,MAAM,GAAG,QAAQ,SAAS,cAAc;AAE3C,MAAI,UAAU;AAEd,aAAW,SAAS,UAAU;AAC5B,QAAI;AACF,YAAM,WAAW,iBAAiB,MAAM,IAAI;AAC5C,YAAM,eAAe,oBAAoB,MAAM,IAAI;AAEnD,UAAI,WAAW,QAAQ,GAAG;AACxB,mBAAW,QAAQ;AAAA,MACrB;AACA,UAAI,WAAW,YAAY,GAAG;AAC5B,mBAAW,YAAY;AAAA,MACzB;AAEA;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,UAAU,GAAG;AACf,gBAAY,KAAK,6BAA6B;AAAA,MAC5C;AAAA,MACA,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAKO,SAAS,gBAKd;AACA,QAAM,UAAU,kBAAkB;AAElC,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,aAAa;AAAA,MACb,aAAa;AAAA,IACf;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAC9D,QAAM,aAAa,QAAQ,IAAI,OAAK,EAAE,SAAS;AAE/C,SAAO;AAAA,IACL,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA,aAAa,KAAK,IAAI,GAAG,UAAU;AAAA,IACnC,aAAa,KAAK,IAAI,GAAG,UAAU;AAAA,EACrC;AACF;AAMO,SAAS,aAAqB;AACnC,QAAM,WAAW,iBAAiB;AAClC,MAAI,UAAU;AAEd,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,QAAQ,YAAY,QAAQ;AAElC,eAAW,QAAQ,OAAO;AACxB,UAAI;AACF,mBAAW,KAAK,UAAU,IAAI,CAAC;AAC/B;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,gBAAY,KAAK,uBAAuB,EAAE,QAAQ,CAAC;AAAA,EACrD,SAAS,OAAO;AACd,gBAAY,MAAM,4BAA4B;AAAA,MAC5C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D,CAAC;AAAA,EACH;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { join, resolve } from "path";
|
|
11
11
|
import { homedir } from "os";
|
|
12
12
|
import { EventEmitter } from "events";
|
|
13
|
-
import {
|
|
13
|
+
import { fetchRepo } from "./repoFetcher.js";
|
|
14
14
|
import { getCwd } from "./state.js";
|
|
15
15
|
import {
|
|
16
16
|
PluginManifestSchema,
|
|
@@ -111,9 +111,9 @@ class PluginInstaller extends EventEmitter {
|
|
|
111
111
|
this.saveRegistry(filtered, global);
|
|
112
112
|
}
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
114
|
+
* Fetch a git repository to a temporary directory using repoFetcher
|
|
115
115
|
*/
|
|
116
|
-
async
|
|
116
|
+
async fetchToTempDir(source) {
|
|
117
117
|
const tempDir = join(
|
|
118
118
|
homedir(),
|
|
119
119
|
".minto",
|
|
@@ -123,21 +123,13 @@ class PluginInstaller extends EventEmitter {
|
|
|
123
123
|
);
|
|
124
124
|
this.tempDirs.push(tempDir);
|
|
125
125
|
mkdirSync(tempDir, { recursive: true });
|
|
126
|
+
this.emitProgress("cloning" /* CLONING */, `Fetching repository...`, 20);
|
|
126
127
|
try {
|
|
127
|
-
|
|
128
|
-
if (ref) {
|
|
129
|
-
args.push("--branch", ref);
|
|
130
|
-
}
|
|
131
|
-
args.push(url, tempDir);
|
|
132
|
-
this.emitProgress("cloning" /* CLONING */, `Cloning repository: ${url}`, 20);
|
|
133
|
-
const result = await execFileNoThrow("git", args);
|
|
134
|
-
if (result.code !== 0) {
|
|
135
|
-
throw new Error(`Git clone failed: ${result.stderr || result.stdout}`);
|
|
136
|
-
}
|
|
128
|
+
await fetchRepo(source, tempDir);
|
|
137
129
|
return tempDir;
|
|
138
130
|
} catch (error) {
|
|
139
131
|
throw new PluginError(
|
|
140
|
-
`Failed to
|
|
132
|
+
`Failed to fetch repository: ${error instanceof Error ? error.message : String(error)}`,
|
|
141
133
|
PluginErrorCode.PERMISSION_DENIED,
|
|
142
134
|
void 0,
|
|
143
135
|
error
|
|
@@ -281,9 +273,25 @@ ${missing.join("\n")}`);
|
|
|
281
273
|
0
|
|
282
274
|
);
|
|
283
275
|
switch (source.type) {
|
|
284
|
-
case "git":
|
|
285
|
-
|
|
276
|
+
case "git": {
|
|
277
|
+
const ghMatch = source.repo.match(
|
|
278
|
+
/github\.com[/:]([^/]+\/[^/.]+?)(?:\.git)?$/
|
|
279
|
+
);
|
|
280
|
+
if (ghMatch) {
|
|
281
|
+
sourceDir = await this.fetchToTempDir({
|
|
282
|
+
type: "github",
|
|
283
|
+
repo: ghMatch[1],
|
|
284
|
+
ref: source.ref
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
sourceDir = await this.fetchToTempDir({
|
|
288
|
+
type: "url",
|
|
289
|
+
url: source.repo,
|
|
290
|
+
ref: source.ref
|
|
291
|
+
});
|
|
292
|
+
}
|
|
286
293
|
break;
|
|
294
|
+
}
|
|
287
295
|
case "local":
|
|
288
296
|
sourceDir = resolve(source.path);
|
|
289
297
|
if (!existsSync(sourceDir)) {
|
|
@@ -313,15 +321,17 @@ ${missing.join("\n")}`);
|
|
|
313
321
|
PluginErrorCode.MANIFEST_NOT_FOUND
|
|
314
322
|
);
|
|
315
323
|
} else if (plugin.source.source === "github") {
|
|
316
|
-
sourceDir = await this.
|
|
317
|
-
|
|
318
|
-
plugin.source.
|
|
319
|
-
|
|
324
|
+
sourceDir = await this.fetchToTempDir({
|
|
325
|
+
type: "github",
|
|
326
|
+
repo: plugin.source.repo,
|
|
327
|
+
ref: plugin.source.ref
|
|
328
|
+
});
|
|
320
329
|
} else if (plugin.source.source === "url") {
|
|
321
|
-
sourceDir = await this.
|
|
322
|
-
|
|
323
|
-
plugin.source.
|
|
324
|
-
|
|
330
|
+
sourceDir = await this.fetchToTempDir({
|
|
331
|
+
type: "url",
|
|
332
|
+
url: plugin.source.url,
|
|
333
|
+
ref: plugin.source.ref
|
|
334
|
+
});
|
|
325
335
|
} else {
|
|
326
336
|
throw new PluginError(
|
|
327
337
|
"Unsupported marketplace plugin source type",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/pluginInstaller.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Plugin Installer\n *\n * Comprehensive plugin installation utility with proper error handling,\n * progress tracking, and cleanup on failure.\n *\n * Supports installation from:\n * - Git repositories (GitHub shorthand, full URLs)\n * - Local directories (copy or symlink)\n * - NPM packages (future)\n * - Marketplaces (via marketplaceManager)\n */\n\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n rmSync,\n cpSync,\n symlinkSync,\n statSync,\n readdirSync,\n} from 'fs'\nimport { join, resolve, basename } from 'path'\nimport { homedir } from 'os'\nimport { EventEmitter } from 'events'\nimport { execFileNoThrow } from './execFileNoThrow'\nimport { getCwd } from './state'\nimport {\n PluginManifest,\n PluginManifestSchema,\n PluginSource,\n PluginError,\n PluginErrorCode,\n} from '../types/plugin'\nimport { findPlugin } from './marketplaceManager'\n\n/**\n * Installation options\n */\nexport interface InstallOptions {\n /** Target directory (default: ~/.minto/plugins/<name> or ./.minto/plugins/<name>) */\n targetDir?: string\n\n /** Overwrite existing installation */\n force?: boolean\n\n /** Create symlink instead of copy (for local development) */\n dev?: boolean\n\n /** Install globally (~/minto/plugins/) vs project-level (./.minto/plugins/) */\n global?: boolean\n\n /** Silent mode (no progress events) */\n silent?: boolean\n\n /** Validation mode: strict (require all components) or loose */\n validation?: 'strict' | 'loose'\n}\n\n/**\n * Installation progress phases\n */\nexport enum InstallPhase {\n VALIDATING_SOURCE = 'validating_source',\n CLONING = 'cloning',\n COPYING = 'copying',\n VALIDATING_MANIFEST = 'validating_manifest',\n CHECKING_DEPENDENCIES = 'checking_dependencies',\n INSTALLING = 'installing',\n REGISTERING = 'registering',\n CLEANUP = 'cleanup',\n COMPLETE = 'complete',\n}\n\n/**\n * Installation progress event\n */\nexport interface InstallProgress {\n phase: InstallPhase\n message: string\n percentage: number\n pluginName?: string\n}\n\n/**\n * Plugin registry entry\n */\ninterface PluginRegistryEntry {\n name: string\n version: string\n source: PluginSource\n installedAt: string\n location: string\n}\n\n/**\n * Plugin installer with event-based progress tracking\n */\nexport class PluginInstaller extends EventEmitter {\n private tempDirs: string[] = []\n\n /**\n * Emit progress event\n */\n private emitProgress(\n phase: InstallPhase,\n message: string,\n percentage: number,\n pluginName?: string,\n ): void {\n this.emit('progress', {\n phase,\n message,\n percentage,\n pluginName,\n } as InstallProgress)\n }\n\n /**\n * Get plugin installation directory\n */\n private getInstallDir(pluginName: string, options?: InstallOptions): string {\n if (options?.targetDir) {\n return resolve(options.targetDir)\n }\n\n const baseDir = options?.global ? homedir() : getCwd()\n return join(baseDir, '.minto', 'plugins', pluginName)\n }\n\n /**\n * Get plugin registry path\n */\n private getRegistryPath(global: boolean): string {\n const baseDir = global ? homedir() : getCwd()\n return join(baseDir, '.minto', 'plugins', 'registry.json')\n }\n\n /**\n * Load plugin registry\n */\n private loadRegistry(global: boolean): PluginRegistryEntry[] {\n const registryPath = this.getRegistryPath(global)\n\n if (!existsSync(registryPath)) {\n return []\n }\n\n try {\n const content = readFileSync(registryPath, 'utf-8')\n return JSON.parse(content)\n } catch (error) {\n console.error('Error loading plugin registry:', error)\n return []\n }\n }\n\n /**\n * Save plugin registry\n */\n private saveRegistry(entries: PluginRegistryEntry[], global: boolean): void {\n const registryPath = this.getRegistryPath(global)\n const registryDir = join(registryPath, '..')\n\n // Ensure directory exists\n if (!existsSync(registryDir)) {\n mkdirSync(registryDir, { recursive: true })\n }\n\n writeFileSync(registryPath, JSON.stringify(entries, null, 2), 'utf-8')\n }\n\n /**\n * Register plugin in registry\n */\n private registerPlugin(\n manifest: PluginManifest,\n source: PluginSource,\n location: string,\n global: boolean,\n ): void {\n const registry = this.loadRegistry(global)\n\n // Remove existing entry if present\n const filtered = registry.filter(e => e.name !== manifest.name)\n\n // Add new entry\n filtered.push({\n name: manifest.name,\n version: manifest.version,\n source,\n installedAt: new Date().toISOString(),\n location,\n })\n\n this.saveRegistry(filtered, global)\n }\n\n /**\n * Unregister plugin from registry\n */\n private unregisterPlugin(pluginName: string, global: boolean): void {\n const registry = this.loadRegistry(global)\n const filtered = registry.filter(e => e.name !== pluginName)\n this.saveRegistry(filtered, global)\n }\n\n /**\n * Clone git repository to temporary directory\n */\n private async cloneGitRepo(url: string, ref?: string): Promise<string> {\n const tempDir = join(\n homedir(),\n '.minto',\n 'temp',\n 'plugins',\n Date.now().toString(),\n )\n\n this.tempDirs.push(tempDir)\n mkdirSync(tempDir, { recursive: true })\n\n try {\n // Build git clone arguments\n const args = ['clone', '--depth', '1']\n\n if (ref) {\n args.push('--branch', ref)\n }\n\n args.push(url, tempDir)\n\n this.emitProgress(InstallPhase.CLONING, `Cloning repository: ${url}`, 20)\n\n // Clone repository\n const result = await execFileNoThrow('git', args)\n\n if (result.code !== 0) {\n throw new Error(`Git clone failed: ${result.stderr || result.stdout}`)\n }\n\n return tempDir\n } catch (error) {\n throw new PluginError(\n `Failed to clone repository: ${error instanceof Error ? error.message : String(error)}`,\n PluginErrorCode.PERMISSION_DENIED,\n undefined,\n error,\n )\n }\n }\n\n /**\n * Validate plugin manifest exists and is valid\n */\n private validateManifest(pluginPath: string): PluginManifest {\n const manifestPath = join(pluginPath, 'plugin.json')\n\n if (!existsSync(manifestPath)) {\n throw new PluginError(\n `Plugin manifest (plugin.json) not found in ${pluginPath}`,\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n\n try {\n const content = readFileSync(manifestPath, 'utf-8')\n const data = JSON.parse(content)\n\n // Validate with Zod schema\n return PluginManifestSchema.parse(data)\n } catch (error) {\n throw new PluginError(\n `Invalid plugin manifest: ${error instanceof Error ? error.message : String(error)}`,\n PluginErrorCode.MANIFEST_INVALID,\n undefined,\n error,\n )\n }\n }\n\n /**\n * Check if plugin is already installed\n */\n private isPluginInstalled(pluginName: string, global: boolean): boolean {\n const registry = this.loadRegistry(global)\n return registry.some(e => e.name === pluginName)\n }\n\n /**\n * Validate plugin components exist\n */\n private validateComponents(\n pluginPath: string,\n manifest: PluginManifest,\n strict: boolean,\n ): void {\n const missing: string[] = []\n\n // Check agents\n for (const agentPath of manifest.agents || []) {\n const fullPath = join(pluginPath, agentPath)\n if (!existsSync(fullPath)) {\n missing.push(`Agent: ${agentPath}`)\n }\n }\n\n // Check commands\n for (const commandPath of manifest.commands || []) {\n const fullPath = join(pluginPath, commandPath)\n if (!existsSync(fullPath)) {\n missing.push(`Command: ${commandPath}`)\n }\n }\n\n // Check skills\n for (const skillPath of manifest.skills || []) {\n const fullPath = join(pluginPath, skillPath)\n if (!existsSync(fullPath)) {\n missing.push(`Skill: ${skillPath}`)\n }\n }\n\n if (missing.length > 0 && strict) {\n throw new PluginError(\n `Missing plugin components:\\n${missing.join('\\n')}`,\n PluginErrorCode.COMPONENT_NOT_FOUND,\n manifest.name,\n )\n } else if (missing.length > 0) {\n console.warn(`Warning: Missing plugin components:\\n${missing.join('\\n')}`)\n }\n }\n\n /**\n * Check disk space availability\n */\n private checkDiskSpace(targetDir: string): void {\n try {\n // Create parent directory if it doesn't exist\n const parentDir = join(targetDir, '..')\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true })\n }\n } catch (error) {\n throw new PluginError(\n `Insufficient permissions or disk space: ${error instanceof Error ? error.message : String(error)}`,\n PluginErrorCode.PERMISSION_DENIED,\n undefined,\n error,\n )\n }\n }\n\n /**\n * Install plugin from source directory to target directory\n */\n private installToTarget(\n sourceDir: string,\n targetDir: string,\n options?: InstallOptions,\n ): void {\n this.emitProgress(InstallPhase.INSTALLING, `Installing to ${targetDir}`, 60)\n\n // Check if target exists\n if (existsSync(targetDir)) {\n if (options?.force) {\n rmSync(targetDir, { recursive: true, force: true })\n } else {\n throw new PluginError(\n `Plugin directory already exists: ${targetDir}. Use --force to overwrite.`,\n PluginErrorCode.ALREADY_INSTALLED,\n )\n }\n }\n\n // Create parent directory\n const parentDir = join(targetDir, '..')\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true })\n }\n\n // Install: symlink for dev mode, copy otherwise\n if (options?.dev) {\n symlinkSync(sourceDir, targetDir, 'dir')\n } else {\n cpSync(sourceDir, targetDir, { recursive: true })\n }\n }\n\n /**\n * Cleanup temporary directories\n */\n private cleanup(): void {\n for (const tempDir of this.tempDirs) {\n try {\n if (existsSync(tempDir)) {\n rmSync(tempDir, { recursive: true, force: true })\n }\n } catch (error) {\n console.error(`Error cleaning up temp directory ${tempDir}:`, error)\n }\n }\n\n this.tempDirs = []\n }\n\n /**\n * Install plugin from any source\n */\n async installPlugin(\n source: PluginSource,\n options?: InstallOptions,\n ): Promise<string> {\n let sourceDir: string | undefined\n let manifest: PluginManifest | undefined\n\n try {\n // Phase 1: Validate and fetch source\n this.emitProgress(\n InstallPhase.VALIDATING_SOURCE,\n 'Validating plugin source',\n 0,\n )\n\n switch (source.type) {\n case 'git':\n sourceDir = await this.cloneGitRepo(source.repo, source.ref)\n break\n\n case 'local':\n sourceDir = resolve(source.path)\n if (!existsSync(sourceDir)) {\n throw new PluginError(\n `Local plugin path does not exist: ${sourceDir}`,\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n break\n\n case 'npm':\n throw new PluginError(\n 'NPM package installation not yet supported',\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n\n case 'marketplace':\n // Find plugin in marketplace\n const found = findPlugin(source.name, source.marketplace)\n\n if (!found) {\n throw new PluginError(\n source.marketplace\n ? `Plugin \"${source.name}\" not found in marketplace \"${source.marketplace}\"`\n : `Plugin \"${source.name}\" not found in any marketplace`,\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n\n const { plugin } = found\n\n // Recursively install from plugin's source\n if (typeof plugin.source === 'string') {\n throw new PluginError(\n 'Relative path plugins not yet supported',\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n } else if (plugin.source.source === 'github') {\n sourceDir = await this.cloneGitRepo(\n `https://github.com/${plugin.source.repo}.git`,\n plugin.source.ref,\n )\n } else if (plugin.source.source === 'url') {\n sourceDir = await this.cloneGitRepo(\n plugin.source.url,\n plugin.source.ref,\n )\n } else {\n throw new PluginError(\n 'Unsupported marketplace plugin source type',\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n break\n\n default:\n throw new PluginError(\n 'Unknown plugin source type',\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n\n // Phase 2: Validate manifest\n this.emitProgress(\n InstallPhase.VALIDATING_MANIFEST,\n 'Validating plugin manifest',\n 30,\n )\n\n manifest = this.validateManifest(sourceDir)\n\n // Check if already installed\n const global = options?.global ?? false\n if (this.isPluginInstalled(manifest.name, global) && !options?.force) {\n throw new PluginError(\n `Plugin \"${manifest.name}\" is already installed. Use --force to overwrite.`,\n PluginErrorCode.ALREADY_INSTALLED,\n manifest.name,\n )\n }\n\n // Phase 3: Validate components\n this.emitProgress(\n InstallPhase.VALIDATING_MANIFEST,\n 'Validating plugin components',\n 40,\n )\n\n const strict = options?.validation === 'strict'\n this.validateComponents(sourceDir, manifest, strict)\n\n // Phase 4: Check dependencies (placeholder)\n this.emitProgress(\n InstallPhase.CHECKING_DEPENDENCIES,\n 'Checking dependencies',\n 50,\n )\n\n // TODO: Check engine versions, dependencies, etc.\n\n // Phase 5: Determine target directory\n const targetDir = this.getInstallDir(manifest.name, options)\n\n // Check disk space and permissions\n this.checkDiskSpace(targetDir)\n\n // Phase 6: Install to target\n this.installToTarget(sourceDir, targetDir, options)\n\n // Phase 7: Register plugin\n this.emitProgress(InstallPhase.REGISTERING, 'Registering plugin', 80)\n\n this.registerPlugin(manifest, source, targetDir, global)\n\n // Phase 8: Complete\n this.emitProgress(\n InstallPhase.COMPLETE,\n `Successfully installed ${manifest.name}`,\n 100,\n manifest.name,\n )\n\n return targetDir\n } catch (error) {\n // Cleanup on error\n this.emitProgress(\n InstallPhase.CLEANUP,\n 'Installation failed, cleaning up',\n 0,\n )\n\n throw error\n } finally {\n // Always cleanup temp directories\n this.cleanup()\n }\n }\n\n /**\n * Uninstall a plugin by name\n */\n async uninstallPlugin(\n pluginName: string,\n options?: { global?: boolean },\n ): Promise<void> {\n const global = options?.global ?? false\n const registry = this.loadRegistry(global)\n\n // Find plugin in registry\n const entry = registry.find(e => e.name === pluginName)\n\n if (!entry) {\n throw new PluginError(\n `Plugin \"${pluginName}\" is not installed`,\n PluginErrorCode.NOT_INSTALLED,\n pluginName,\n )\n }\n\n // Remove plugin directory\n if (existsSync(entry.location)) {\n try {\n rmSync(entry.location, { recursive: true, force: true })\n } catch (error) {\n throw new PluginError(\n `Failed to remove plugin directory: ${error instanceof Error ? error.message : String(error)}`,\n PluginErrorCode.PERMISSION_DENIED,\n pluginName,\n error,\n )\n }\n }\n\n // Unregister from registry\n this.unregisterPlugin(pluginName, global)\n }\n\n /**\n * Update a plugin (re-install from source)\n */\n async updatePlugin(\n pluginName: string,\n options?: { global?: boolean },\n ): Promise<void> {\n const global = options?.global ?? false\n const registry = this.loadRegistry(global)\n\n // Find plugin in registry\n const entry = registry.find(e => e.name === pluginName)\n\n if (!entry) {\n throw new PluginError(\n `Plugin \"${pluginName}\" is not installed`,\n PluginErrorCode.NOT_INSTALLED,\n pluginName,\n )\n }\n\n // Uninstall and reinstall\n await this.uninstallPlugin(pluginName, { global })\n await this.installPlugin(entry.source, { global, force: true })\n }\n}\n\n/**\n * Convenience function: Install plugin from source\n */\nexport async function installPlugin(\n source: PluginSource,\n options?: InstallOptions,\n): Promise<string> {\n const installer = new PluginInstaller()\n\n // Setup progress listener if not silent\n if (!options?.silent) {\n installer.on('progress', (progress: InstallProgress) => {\n console.log(\n `[${progress.percentage}%] ${progress.phase}: ${progress.message}`,\n )\n })\n }\n\n return installer.installPlugin(source, options)\n}\n\n/**\n * Convenience function: Uninstall plugin by name\n */\nexport async function uninstallPlugin(\n name: string,\n options?: { global?: boolean },\n): Promise<void> {\n const installer = new PluginInstaller()\n return installer.uninstallPlugin(name, options)\n}\n\n/**\n * Convenience function: Update plugin by name\n */\nexport async function updatePlugin(\n name: string,\n options?: { global?: boolean },\n): Promise<void> {\n const installer = new PluginInstaller()\n return installer.updatePlugin(name, options)\n}\n\n/**\n * List all installed plugins\n */\nexport function listInstalledPlugins(global?: boolean): PluginRegistryEntry[] {\n const installer = new PluginInstaller()\n const globalRegistry = installer['loadRegistry'](true)\n const projectRegistry = installer['loadRegistry'](false)\n\n if (global === true) {\n return globalRegistry\n } else if (global === false) {\n return projectRegistry\n } else {\n // Return both, with project plugins overriding global\n const combined = new Map<string, PluginRegistryEntry>()\n\n for (const entry of globalRegistry) {\n combined.set(entry.name, entry)\n }\n\n for (const entry of projectRegistry) {\n combined.set(entry.name, entry)\n }\n\n return Array.from(combined.values())\n }\n}\n"],
|
|
5
|
-
"mappings": "AAaA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,MAAM,eAAyB;AACxC,SAAS,eAAe;AACxB,SAAS,oBAAoB;AAC7B,SAAS,
|
|
4
|
+
"sourcesContent": ["/**\n * Plugin Installer\n *\n * Comprehensive plugin installation utility with proper error handling,\n * progress tracking, and cleanup on failure.\n *\n * Supports installation from:\n * - Git repositories (GitHub shorthand, full URLs)\n * - Local directories (copy or symlink)\n * - NPM packages (future)\n * - Marketplaces (via marketplaceManager)\n */\n\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n rmSync,\n cpSync,\n symlinkSync,\n statSync,\n readdirSync,\n} from 'fs'\nimport { join, resolve, basename } from 'path'\nimport { homedir } from 'os'\nimport { EventEmitter } from 'events'\nimport { fetchRepo, RepoSource } from './repoFetcher'\nimport { getCwd } from './state'\nimport {\n PluginManifest,\n PluginManifestSchema,\n PluginSource,\n PluginError,\n PluginErrorCode,\n} from '../types/plugin'\nimport { findPlugin } from './marketplaceManager'\n\n/**\n * Installation options\n */\nexport interface InstallOptions {\n /** Target directory (default: ~/.minto/plugins/<name> or ./.minto/plugins/<name>) */\n targetDir?: string\n\n /** Overwrite existing installation */\n force?: boolean\n\n /** Create symlink instead of copy (for local development) */\n dev?: boolean\n\n /** Install globally (~/minto/plugins/) vs project-level (./.minto/plugins/) */\n global?: boolean\n\n /** Silent mode (no progress events) */\n silent?: boolean\n\n /** Validation mode: strict (require all components) or loose */\n validation?: 'strict' | 'loose'\n}\n\n/**\n * Installation progress phases\n */\nexport enum InstallPhase {\n VALIDATING_SOURCE = 'validating_source',\n CLONING = 'cloning',\n COPYING = 'copying',\n VALIDATING_MANIFEST = 'validating_manifest',\n CHECKING_DEPENDENCIES = 'checking_dependencies',\n INSTALLING = 'installing',\n REGISTERING = 'registering',\n CLEANUP = 'cleanup',\n COMPLETE = 'complete',\n}\n\n/**\n * Installation progress event\n */\nexport interface InstallProgress {\n phase: InstallPhase\n message: string\n percentage: number\n pluginName?: string\n}\n\n/**\n * Plugin registry entry\n */\ninterface PluginRegistryEntry {\n name: string\n version: string\n source: PluginSource\n installedAt: string\n location: string\n}\n\n/**\n * Plugin installer with event-based progress tracking\n */\nexport class PluginInstaller extends EventEmitter {\n private tempDirs: string[] = []\n\n /**\n * Emit progress event\n */\n private emitProgress(\n phase: InstallPhase,\n message: string,\n percentage: number,\n pluginName?: string,\n ): void {\n this.emit('progress', {\n phase,\n message,\n percentage,\n pluginName,\n } as InstallProgress)\n }\n\n /**\n * Get plugin installation directory\n */\n private getInstallDir(pluginName: string, options?: InstallOptions): string {\n if (options?.targetDir) {\n return resolve(options.targetDir)\n }\n\n const baseDir = options?.global ? homedir() : getCwd()\n return join(baseDir, '.minto', 'plugins', pluginName)\n }\n\n /**\n * Get plugin registry path\n */\n private getRegistryPath(global: boolean): string {\n const baseDir = global ? homedir() : getCwd()\n return join(baseDir, '.minto', 'plugins', 'registry.json')\n }\n\n /**\n * Load plugin registry\n */\n private loadRegistry(global: boolean): PluginRegistryEntry[] {\n const registryPath = this.getRegistryPath(global)\n\n if (!existsSync(registryPath)) {\n return []\n }\n\n try {\n const content = readFileSync(registryPath, 'utf-8')\n return JSON.parse(content)\n } catch (error) {\n console.error('Error loading plugin registry:', error)\n return []\n }\n }\n\n /**\n * Save plugin registry\n */\n private saveRegistry(entries: PluginRegistryEntry[], global: boolean): void {\n const registryPath = this.getRegistryPath(global)\n const registryDir = join(registryPath, '..')\n\n // Ensure directory exists\n if (!existsSync(registryDir)) {\n mkdirSync(registryDir, { recursive: true })\n }\n\n writeFileSync(registryPath, JSON.stringify(entries, null, 2), 'utf-8')\n }\n\n /**\n * Register plugin in registry\n */\n private registerPlugin(\n manifest: PluginManifest,\n source: PluginSource,\n location: string,\n global: boolean,\n ): void {\n const registry = this.loadRegistry(global)\n\n // Remove existing entry if present\n const filtered = registry.filter(e => e.name !== manifest.name)\n\n // Add new entry\n filtered.push({\n name: manifest.name,\n version: manifest.version,\n source,\n installedAt: new Date().toISOString(),\n location,\n })\n\n this.saveRegistry(filtered, global)\n }\n\n /**\n * Unregister plugin from registry\n */\n private unregisterPlugin(pluginName: string, global: boolean): void {\n const registry = this.loadRegistry(global)\n const filtered = registry.filter(e => e.name !== pluginName)\n this.saveRegistry(filtered, global)\n }\n\n /**\n * Fetch a git repository to a temporary directory using repoFetcher\n */\n private async fetchToTempDir(source: RepoSource): Promise<string> {\n const tempDir = join(\n homedir(),\n '.minto',\n 'temp',\n 'plugins',\n Date.now().toString(),\n )\n\n this.tempDirs.push(tempDir)\n mkdirSync(tempDir, { recursive: true })\n\n this.emitProgress(InstallPhase.CLONING, `Fetching repository...`, 20)\n\n try {\n await fetchRepo(source, tempDir)\n return tempDir\n } catch (error) {\n throw new PluginError(\n `Failed to fetch repository: ${error instanceof Error ? error.message : String(error)}`,\n PluginErrorCode.PERMISSION_DENIED,\n undefined,\n error,\n )\n }\n }\n\n /**\n * Validate plugin manifest exists and is valid\n */\n private validateManifest(pluginPath: string): PluginManifest {\n const manifestPath = join(pluginPath, 'plugin.json')\n\n if (!existsSync(manifestPath)) {\n throw new PluginError(\n `Plugin manifest (plugin.json) not found in ${pluginPath}`,\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n\n try {\n const content = readFileSync(manifestPath, 'utf-8')\n const data = JSON.parse(content)\n\n // Validate with Zod schema\n return PluginManifestSchema.parse(data)\n } catch (error) {\n throw new PluginError(\n `Invalid plugin manifest: ${error instanceof Error ? error.message : String(error)}`,\n PluginErrorCode.MANIFEST_INVALID,\n undefined,\n error,\n )\n }\n }\n\n /**\n * Check if plugin is already installed\n */\n private isPluginInstalled(pluginName: string, global: boolean): boolean {\n const registry = this.loadRegistry(global)\n return registry.some(e => e.name === pluginName)\n }\n\n /**\n * Validate plugin components exist\n */\n private validateComponents(\n pluginPath: string,\n manifest: PluginManifest,\n strict: boolean,\n ): void {\n const missing: string[] = []\n\n // Check agents\n for (const agentPath of manifest.agents || []) {\n const fullPath = join(pluginPath, agentPath)\n if (!existsSync(fullPath)) {\n missing.push(`Agent: ${agentPath}`)\n }\n }\n\n // Check commands\n for (const commandPath of manifest.commands || []) {\n const fullPath = join(pluginPath, commandPath)\n if (!existsSync(fullPath)) {\n missing.push(`Command: ${commandPath}`)\n }\n }\n\n // Check skills\n for (const skillPath of manifest.skills || []) {\n const fullPath = join(pluginPath, skillPath)\n if (!existsSync(fullPath)) {\n missing.push(`Skill: ${skillPath}`)\n }\n }\n\n if (missing.length > 0 && strict) {\n throw new PluginError(\n `Missing plugin components:\\n${missing.join('\\n')}`,\n PluginErrorCode.COMPONENT_NOT_FOUND,\n manifest.name,\n )\n } else if (missing.length > 0) {\n console.warn(`Warning: Missing plugin components:\\n${missing.join('\\n')}`)\n }\n }\n\n /**\n * Check disk space availability\n */\n private checkDiskSpace(targetDir: string): void {\n try {\n // Create parent directory if it doesn't exist\n const parentDir = join(targetDir, '..')\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true })\n }\n } catch (error) {\n throw new PluginError(\n `Insufficient permissions or disk space: ${error instanceof Error ? error.message : String(error)}`,\n PluginErrorCode.PERMISSION_DENIED,\n undefined,\n error,\n )\n }\n }\n\n /**\n * Install plugin from source directory to target directory\n */\n private installToTarget(\n sourceDir: string,\n targetDir: string,\n options?: InstallOptions,\n ): void {\n this.emitProgress(InstallPhase.INSTALLING, `Installing to ${targetDir}`, 60)\n\n // Check if target exists\n if (existsSync(targetDir)) {\n if (options?.force) {\n rmSync(targetDir, { recursive: true, force: true })\n } else {\n throw new PluginError(\n `Plugin directory already exists: ${targetDir}. Use --force to overwrite.`,\n PluginErrorCode.ALREADY_INSTALLED,\n )\n }\n }\n\n // Create parent directory\n const parentDir = join(targetDir, '..')\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true })\n }\n\n // Install: symlink for dev mode, copy otherwise\n if (options?.dev) {\n symlinkSync(sourceDir, targetDir, 'dir')\n } else {\n cpSync(sourceDir, targetDir, { recursive: true })\n }\n }\n\n /**\n * Cleanup temporary directories\n */\n private cleanup(): void {\n for (const tempDir of this.tempDirs) {\n try {\n if (existsSync(tempDir)) {\n rmSync(tempDir, { recursive: true, force: true })\n }\n } catch (error) {\n console.error(`Error cleaning up temp directory ${tempDir}:`, error)\n }\n }\n\n this.tempDirs = []\n }\n\n /**\n * Install plugin from any source\n */\n async installPlugin(\n source: PluginSource,\n options?: InstallOptions,\n ): Promise<string> {\n let sourceDir: string | undefined\n let manifest: PluginManifest | undefined\n\n try {\n // Phase 1: Validate and fetch source\n this.emitProgress(\n InstallPhase.VALIDATING_SOURCE,\n 'Validating plugin source',\n 0,\n )\n\n switch (source.type) {\n case 'git': {\n // Detect GitHub URLs and use tarball; otherwise use URL source\n const ghMatch = source.repo.match(\n /github\\.com[/:]([^/]+\\/[^/.]+?)(?:\\.git)?$/,\n )\n if (ghMatch) {\n sourceDir = await this.fetchToTempDir({\n type: 'github',\n repo: ghMatch[1],\n ref: source.ref,\n })\n } else {\n sourceDir = await this.fetchToTempDir({\n type: 'url',\n url: source.repo,\n ref: source.ref,\n })\n }\n break\n }\n\n case 'local':\n sourceDir = resolve(source.path)\n if (!existsSync(sourceDir)) {\n throw new PluginError(\n `Local plugin path does not exist: ${sourceDir}`,\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n break\n\n case 'npm':\n throw new PluginError(\n 'NPM package installation not yet supported',\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n\n case 'marketplace':\n // Find plugin in marketplace\n const found = findPlugin(source.name, source.marketplace)\n\n if (!found) {\n throw new PluginError(\n source.marketplace\n ? `Plugin \"${source.name}\" not found in marketplace \"${source.marketplace}\"`\n : `Plugin \"${source.name}\" not found in any marketplace`,\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n\n const { plugin } = found\n\n // Recursively install from plugin's source\n if (typeof plugin.source === 'string') {\n throw new PluginError(\n 'Relative path plugins not yet supported',\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n } else if (plugin.source.source === 'github') {\n sourceDir = await this.fetchToTempDir({\n type: 'github',\n repo: plugin.source.repo,\n ref: plugin.source.ref,\n })\n } else if (plugin.source.source === 'url') {\n sourceDir = await this.fetchToTempDir({\n type: 'url',\n url: plugin.source.url,\n ref: plugin.source.ref,\n })\n } else {\n throw new PluginError(\n 'Unsupported marketplace plugin source type',\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n break\n\n default:\n throw new PluginError(\n 'Unknown plugin source type',\n PluginErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n\n // Phase 2: Validate manifest\n this.emitProgress(\n InstallPhase.VALIDATING_MANIFEST,\n 'Validating plugin manifest',\n 30,\n )\n\n manifest = this.validateManifest(sourceDir)\n\n // Check if already installed\n const global = options?.global ?? false\n if (this.isPluginInstalled(manifest.name, global) && !options?.force) {\n throw new PluginError(\n `Plugin \"${manifest.name}\" is already installed. Use --force to overwrite.`,\n PluginErrorCode.ALREADY_INSTALLED,\n manifest.name,\n )\n }\n\n // Phase 3: Validate components\n this.emitProgress(\n InstallPhase.VALIDATING_MANIFEST,\n 'Validating plugin components',\n 40,\n )\n\n const strict = options?.validation === 'strict'\n this.validateComponents(sourceDir, manifest, strict)\n\n // Phase 4: Check dependencies (placeholder)\n this.emitProgress(\n InstallPhase.CHECKING_DEPENDENCIES,\n 'Checking dependencies',\n 50,\n )\n\n // TODO: Check engine versions, dependencies, etc.\n\n // Phase 5: Determine target directory\n const targetDir = this.getInstallDir(manifest.name, options)\n\n // Check disk space and permissions\n this.checkDiskSpace(targetDir)\n\n // Phase 6: Install to target\n this.installToTarget(sourceDir, targetDir, options)\n\n // Phase 7: Register plugin\n this.emitProgress(InstallPhase.REGISTERING, 'Registering plugin', 80)\n\n this.registerPlugin(manifest, source, targetDir, global)\n\n // Phase 8: Complete\n this.emitProgress(\n InstallPhase.COMPLETE,\n `Successfully installed ${manifest.name}`,\n 100,\n manifest.name,\n )\n\n return targetDir\n } catch (error) {\n // Cleanup on error\n this.emitProgress(\n InstallPhase.CLEANUP,\n 'Installation failed, cleaning up',\n 0,\n )\n\n throw error\n } finally {\n // Always cleanup temp directories\n this.cleanup()\n }\n }\n\n /**\n * Uninstall a plugin by name\n */\n async uninstallPlugin(\n pluginName: string,\n options?: { global?: boolean },\n ): Promise<void> {\n const global = options?.global ?? false\n const registry = this.loadRegistry(global)\n\n // Find plugin in registry\n const entry = registry.find(e => e.name === pluginName)\n\n if (!entry) {\n throw new PluginError(\n `Plugin \"${pluginName}\" is not installed`,\n PluginErrorCode.NOT_INSTALLED,\n pluginName,\n )\n }\n\n // Remove plugin directory\n if (existsSync(entry.location)) {\n try {\n rmSync(entry.location, { recursive: true, force: true })\n } catch (error) {\n throw new PluginError(\n `Failed to remove plugin directory: ${error instanceof Error ? error.message : String(error)}`,\n PluginErrorCode.PERMISSION_DENIED,\n pluginName,\n error,\n )\n }\n }\n\n // Unregister from registry\n this.unregisterPlugin(pluginName, global)\n }\n\n /**\n * Update a plugin (re-install from source)\n */\n async updatePlugin(\n pluginName: string,\n options?: { global?: boolean },\n ): Promise<void> {\n const global = options?.global ?? false\n const registry = this.loadRegistry(global)\n\n // Find plugin in registry\n const entry = registry.find(e => e.name === pluginName)\n\n if (!entry) {\n throw new PluginError(\n `Plugin \"${pluginName}\" is not installed`,\n PluginErrorCode.NOT_INSTALLED,\n pluginName,\n )\n }\n\n // Uninstall and reinstall\n await this.uninstallPlugin(pluginName, { global })\n await this.installPlugin(entry.source, { global, force: true })\n }\n}\n\n/**\n * Convenience function: Install plugin from source\n */\nexport async function installPlugin(\n source: PluginSource,\n options?: InstallOptions,\n): Promise<string> {\n const installer = new PluginInstaller()\n\n // Setup progress listener if not silent\n if (!options?.silent) {\n installer.on('progress', (progress: InstallProgress) => {\n console.log(\n `[${progress.percentage}%] ${progress.phase}: ${progress.message}`,\n )\n })\n }\n\n return installer.installPlugin(source, options)\n}\n\n/**\n * Convenience function: Uninstall plugin by name\n */\nexport async function uninstallPlugin(\n name: string,\n options?: { global?: boolean },\n): Promise<void> {\n const installer = new PluginInstaller()\n return installer.uninstallPlugin(name, options)\n}\n\n/**\n * Convenience function: Update plugin by name\n */\nexport async function updatePlugin(\n name: string,\n options?: { global?: boolean },\n): Promise<void> {\n const installer = new PluginInstaller()\n return installer.updatePlugin(name, options)\n}\n\n/**\n * List all installed plugins\n */\nexport function listInstalledPlugins(global?: boolean): PluginRegistryEntry[] {\n const installer = new PluginInstaller()\n const globalRegistry = installer['loadRegistry'](true)\n const projectRegistry = installer['loadRegistry'](false)\n\n if (global === true) {\n return globalRegistry\n } else if (global === false) {\n return projectRegistry\n } else {\n // Return both, with project plugins overriding global\n const combined = new Map<string, PluginRegistryEntry>()\n\n for (const entry of globalRegistry) {\n combined.set(entry.name, entry)\n }\n\n for (const entry of projectRegistry) {\n combined.set(entry.name, entry)\n }\n\n return Array.from(combined.values())\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["InstallPhase"]
|
|
7
7
|
}
|
|
@@ -18,8 +18,12 @@ function getPluginDirectories() {
|
|
|
18
18
|
const cwd = getCwd();
|
|
19
19
|
const home = homedir();
|
|
20
20
|
return [
|
|
21
|
+
join(home, ".claude", "plugins"),
|
|
22
|
+
// User global (legacy)
|
|
21
23
|
join(home, ".minto", "plugins"),
|
|
22
24
|
// User global
|
|
25
|
+
join(cwd, ".claude", "plugins"),
|
|
26
|
+
// Project (legacy)
|
|
23
27
|
join(cwd, ".minto", "plugins")
|
|
24
28
|
// Project (highest priority)
|
|
25
29
|
];
|
|
@@ -39,8 +43,13 @@ function discoverPluginPaths() {
|
|
|
39
43
|
".minto-plugin",
|
|
40
44
|
"plugin.json"
|
|
41
45
|
);
|
|
46
|
+
const claudeManifestPath = join(
|
|
47
|
+
pluginPath,
|
|
48
|
+
".claude-plugin",
|
|
49
|
+
"plugin.json"
|
|
50
|
+
);
|
|
42
51
|
const rootManifestPath = join(pluginPath, "plugin.json");
|
|
43
|
-
if (!existsSync(mintoManifestPath) && !existsSync(rootManifestPath))
|
|
52
|
+
if (!existsSync(mintoManifestPath) && !existsSync(claudeManifestPath) && !existsSync(rootManifestPath))
|
|
44
53
|
continue;
|
|
45
54
|
pluginPaths.set(entry, pluginPath);
|
|
46
55
|
}
|
|
@@ -50,35 +59,35 @@ function discoverPluginPaths() {
|
|
|
50
59
|
return pluginPaths;
|
|
51
60
|
}
|
|
52
61
|
function loadManifest(pluginPath) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
if (!manifestPath) {
|
|
62
|
+
const candidates = [
|
|
63
|
+
join(pluginPath, ".minto-plugin", "plugin.json"),
|
|
64
|
+
join(pluginPath, ".claude-plugin", "plugin.json"),
|
|
65
|
+
join(pluginPath, "plugin.json")
|
|
66
|
+
];
|
|
67
|
+
const existing = candidates.filter((p) => existsSync(p));
|
|
68
|
+
if (existing.length === 0) {
|
|
62
69
|
throw new PluginError(
|
|
63
70
|
`Plugin manifest not found. Tried:
|
|
64
|
-
- ${
|
|
65
|
-
- ${rootManifest}`,
|
|
71
|
+
${candidates.map((p) => ` - ${p}`).join("\n")}`,
|
|
66
72
|
PluginErrorCode.MANIFEST_NOT_FOUND
|
|
67
73
|
);
|
|
68
74
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
void 0,
|
|
79
|
-
error
|
|
80
|
-
);
|
|
75
|
+
let lastError = null;
|
|
76
|
+
for (const manifestPath of existing) {
|
|
77
|
+
try {
|
|
78
|
+
const manifestContent = readFileSync(manifestPath, "utf-8");
|
|
79
|
+
const manifestData = JSON.parse(manifestContent);
|
|
80
|
+
return PluginManifestSchema.parse(manifestData);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
lastError = error;
|
|
83
|
+
}
|
|
81
84
|
}
|
|
85
|
+
throw new PluginError(
|
|
86
|
+
`Invalid plugin manifest at ${existing[0]}: ${lastError instanceof Error ? lastError.message : String(lastError)}`,
|
|
87
|
+
PluginErrorCode.MANIFEST_INVALID,
|
|
88
|
+
void 0,
|
|
89
|
+
lastError
|
|
90
|
+
);
|
|
82
91
|
}
|
|
83
92
|
function loadAgents(pluginPath, manifest) {
|
|
84
93
|
const agents = [];
|
|
@@ -218,7 +227,8 @@ function loadSkills(pluginPath, manifest) {
|
|
|
218
227
|
description: data.description || "",
|
|
219
228
|
content: skillContent.trim()
|
|
220
229
|
},
|
|
221
|
-
pluginName: manifest.name
|
|
230
|
+
pluginName: manifest.name,
|
|
231
|
+
source: "plugin"
|
|
222
232
|
});
|
|
223
233
|
} catch (error) {
|
|
224
234
|
console.error(`Error loading skill ${skillPath}:`, error);
|
|
@@ -242,7 +252,8 @@ function loadSkills(pluginPath, manifest) {
|
|
|
242
252
|
description: data.description || "",
|
|
243
253
|
content: skillContent.trim()
|
|
244
254
|
},
|
|
245
|
-
pluginName: manifest.name
|
|
255
|
+
pluginName: manifest.name,
|
|
256
|
+
source: "plugin"
|
|
246
257
|
});
|
|
247
258
|
} catch (error) {
|
|
248
259
|
console.error(`Error loading skill from ${skillMdPath}:`, error);
|
|
@@ -262,7 +273,8 @@ function loadSkills(pluginPath, manifest) {
|
|
|
262
273
|
description: data.description || "",
|
|
263
274
|
content: skillContent.trim()
|
|
264
275
|
},
|
|
265
|
-
pluginName: manifest.name
|
|
276
|
+
pluginName: manifest.name,
|
|
277
|
+
source: "plugin"
|
|
266
278
|
});
|
|
267
279
|
} catch (error) {
|
|
268
280
|
console.error(`Error loading skill ${entry.name}:`, error);
|
|
@@ -321,10 +333,12 @@ function loadHooks(pluginPath, manifest) {
|
|
|
321
333
|
function expandEnvVars(value, pluginPath) {
|
|
322
334
|
let expanded = value.replace(/\$\{MINTO_PLUGIN_ROOT\}/g, pluginPath);
|
|
323
335
|
expanded = expanded.replace(/\$MINTO_PLUGIN_ROOT(?![A-Za-z_])/g, pluginPath);
|
|
336
|
+
expanded = expanded.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginPath);
|
|
337
|
+
expanded = expanded.replace(/\$CLAUDE_PLUGIN_ROOT(?![A-Za-z_])/g, pluginPath);
|
|
324
338
|
expanded = expanded.replace(
|
|
325
339
|
/\$\{([^}:]+)(?::-([^}]*))?\}/g,
|
|
326
340
|
(match, varName, defaultValue) => {
|
|
327
|
-
if (varName === "MINTO_PLUGIN_ROOT") {
|
|
341
|
+
if (varName === "MINTO_PLUGIN_ROOT" || varName === "CLAUDE_PLUGIN_ROOT") {
|
|
328
342
|
return match;
|
|
329
343
|
}
|
|
330
344
|
return process.env[varName] || defaultValue || "";
|
|
@@ -422,6 +436,18 @@ function loadMCPServers(pluginPath, manifest) {
|
|
|
422
436
|
function determinePluginSource(pluginPath) {
|
|
423
437
|
const home = homedir();
|
|
424
438
|
const cwd = getCwd();
|
|
439
|
+
const ccSyncPath = join(pluginPath, ".cc-sync.json");
|
|
440
|
+
if (existsSync(ccSyncPath)) {
|
|
441
|
+
try {
|
|
442
|
+
const ccMeta = JSON.parse(readFileSync(ccSyncPath, "utf-8"));
|
|
443
|
+
return {
|
|
444
|
+
type: "claude-code",
|
|
445
|
+
marketplace: ccMeta.marketplace || "",
|
|
446
|
+
name: ccMeta.name || basename(pluginPath)
|
|
447
|
+
};
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
}
|
|
425
451
|
const marketplaceMetaPath = join(pluginPath, ".marketplace-meta.json");
|
|
426
452
|
if (existsSync(marketplaceMetaPath)) {
|
|
427
453
|
try {
|