@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.
Files changed (238) hide show
  1. package/{cli.js → cli.cjs} +25 -23
  2. package/dist/commands/agents/AgentsCommand.js +459 -655
  3. package/dist/commands/agents/AgentsCommand.js.map +2 -2
  4. package/dist/commands/agents/types.js +1 -0
  5. package/dist/commands/agents/types.js.map +2 -2
  6. package/dist/commands/agents/utils/fileOperations.js +96 -36
  7. package/dist/commands/agents/utils/fileOperations.js.map +3 -3
  8. package/dist/commands/agents/utils/index.js +3 -1
  9. package/dist/commands/agents/utils/index.js.map +2 -2
  10. package/dist/commands/context.js +54 -23
  11. package/dist/commands/context.js.map +2 -2
  12. package/dist/commands/export.js +673 -93
  13. package/dist/commands/export.js.map +2 -2
  14. package/dist/commands/language.js +110 -0
  15. package/dist/commands/language.js.map +7 -0
  16. package/dist/commands/mcp-interactive.js +419 -217
  17. package/dist/commands/mcp-interactive.js.map +2 -2
  18. package/dist/commands/model.js +415 -66
  19. package/dist/commands/model.js.map +2 -2
  20. package/dist/commands/new.js +56 -0
  21. package/dist/commands/new.js.map +7 -0
  22. package/dist/commands/permissions.js +75 -49
  23. package/dist/commands/permissions.js.map +2 -2
  24. package/dist/commands/plugin.js +882 -185
  25. package/dist/commands/plugin.js.map +3 -3
  26. package/dist/commands/resume.js +251 -16
  27. package/dist/commands/resume.js.map +2 -2
  28. package/dist/commands/sandbox.js +168 -70
  29. package/dist/commands/sandbox.js.map +2 -2
  30. package/dist/commands/sessions.js +224 -0
  31. package/dist/commands/sessions.js.map +7 -0
  32. package/dist/commands/setup.js +596 -109
  33. package/dist/commands/setup.js.map +2 -2
  34. package/dist/commands/stats.js +292 -0
  35. package/dist/commands/stats.js.map +7 -0
  36. package/dist/commands/status.js +75 -7
  37. package/dist/commands/status.js.map +2 -2
  38. package/dist/commands/undo.js +154 -180
  39. package/dist/commands/undo.js.map +2 -2
  40. package/dist/commands.js +6 -0
  41. package/dist/commands.js.map +2 -2
  42. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
  43. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
  44. package/dist/components/Config.js +9 -8
  45. package/dist/components/Config.js.map +2 -2
  46. package/dist/components/HeaderBar.js +2 -1
  47. package/dist/components/HeaderBar.js.map +2 -2
  48. package/dist/components/Help.js +166 -32
  49. package/dist/components/Help.js.map +2 -2
  50. package/dist/components/HotkeyHelpPanel.js +46 -44
  51. package/dist/components/HotkeyHelpPanel.js.map +2 -2
  52. package/dist/components/InfoPanel/InfoPanel.js +123 -0
  53. package/dist/components/InfoPanel/InfoPanel.js.map +7 -0
  54. package/dist/components/InfoPanel/index.js +5 -0
  55. package/dist/components/InfoPanel/index.js.map +7 -0
  56. package/dist/components/InfoPanel/types.js +1 -0
  57. package/dist/components/InfoPanel/types.js.map +7 -0
  58. package/dist/components/Logo.js +5 -2
  59. package/dist/components/Logo.js.map +2 -2
  60. package/dist/components/MCPServerApprovalDialog.js +6 -5
  61. package/dist/components/MCPServerApprovalDialog.js.map +2 -2
  62. package/dist/components/MCPServerMultiselectDialog.js +5 -4
  63. package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
  64. package/dist/components/MessageSelector.js +4 -3
  65. package/dist/components/MessageSelector.js.map +2 -2
  66. package/dist/components/ModelConfig.js +13 -12
  67. package/dist/components/ModelConfig.js.map +2 -2
  68. package/dist/components/ModelListManager.js +4 -3
  69. package/dist/components/ModelListManager.js.map +2 -2
  70. package/dist/components/ModelSelector/BrandTextInput.js +43 -0
  71. package/dist/components/ModelSelector/BrandTextInput.js.map +7 -0
  72. package/dist/components/ModelSelector/ModelSelector.js +419 -501
  73. package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
  74. package/dist/components/ModelSelector/WizardContainer.js +45 -0
  75. package/dist/components/ModelSelector/WizardContainer.js.map +7 -0
  76. package/dist/components/ModelSelector/index.js +1 -3
  77. package/dist/components/ModelSelector/index.js.map +2 -2
  78. package/dist/components/PromptInput.js +77 -44
  79. package/dist/components/PromptInput.js.map +2 -2
  80. package/dist/components/SensitiveFileWarning.js +12 -8
  81. package/dist/components/SensitiveFileWarning.js.map +2 -2
  82. package/dist/components/SimpleSelector/SimpleSelector.js +154 -0
  83. package/dist/components/SimpleSelector/SimpleSelector.js.map +7 -0
  84. package/dist/components/SimpleSelector/index.js +5 -0
  85. package/dist/components/SimpleSelector/index.js.map +7 -0
  86. package/dist/components/SimpleSelector/types.js +1 -0
  87. package/dist/components/SimpleSelector/types.js.map +7 -0
  88. package/dist/components/StatusOverlayContent.js +21 -0
  89. package/dist/components/StatusOverlayContent.js.map +7 -0
  90. package/dist/components/TabbedListView/ScrollableList.js +117 -0
  91. package/dist/components/TabbedListView/ScrollableList.js.map +7 -0
  92. package/dist/components/TabbedListView/SearchInput.js +23 -0
  93. package/dist/components/TabbedListView/SearchInput.js.map +7 -0
  94. package/dist/components/TabbedListView/TabBar.js +20 -0
  95. package/dist/components/TabbedListView/TabBar.js.map +7 -0
  96. package/dist/components/TabbedListView/TabbedListView.js +246 -0
  97. package/dist/components/TabbedListView/TabbedListView.js.map +7 -0
  98. package/dist/components/TabbedListView/index.js +11 -0
  99. package/dist/components/TabbedListView/index.js.map +7 -0
  100. package/dist/components/TabbedListView/types.js +1 -0
  101. package/dist/components/TabbedListView/types.js.map +7 -0
  102. package/dist/components/TodoChangeBlock.js +6 -5
  103. package/dist/components/TodoChangeBlock.js.map +3 -3
  104. package/dist/components/TodoPanel.js +6 -3
  105. package/dist/components/TodoPanel.js.map +3 -3
  106. package/dist/components/TrustDialog.js +6 -5
  107. package/dist/components/TrustDialog.js.map +2 -2
  108. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js +2 -1
  109. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js.map +2 -2
  110. package/dist/constants/macros.js +1 -1
  111. package/dist/constants/macros.js.map +1 -1
  112. package/dist/constants/product.js +2 -2
  113. package/dist/constants/product.js.map +1 -1
  114. package/dist/constants/prompts.js +17 -0
  115. package/dist/constants/prompts.js.map +2 -2
  116. package/dist/constants/toolInputExamples.js +5 -1
  117. package/dist/constants/toolInputExamples.js.map +2 -2
  118. package/dist/core/backupHook.js +29 -0
  119. package/dist/core/backupHook.js.map +7 -0
  120. package/dist/core/config/defaults.js +8 -2
  121. package/dist/core/config/defaults.js.map +2 -2
  122. package/dist/core/config/schema.js +14 -2
  123. package/dist/core/config/schema.js.map +2 -2
  124. package/dist/core/costTracker.js +0 -16
  125. package/dist/core/costTracker.js.map +2 -2
  126. package/dist/core/tokenStatsManager.js +5 -0
  127. package/dist/core/tokenStatsManager.js.map +2 -2
  128. package/dist/cost-tracker.js +0 -16
  129. package/dist/cost-tracker.js.map +2 -2
  130. package/dist/entrypoints/bootstrap.js +56 -0
  131. package/dist/entrypoints/bootstrap.js.map +7 -0
  132. package/dist/entrypoints/cli.js +164 -23
  133. package/dist/entrypoints/cli.js.map +3 -3
  134. package/dist/history.js +75 -15
  135. package/dist/history.js.map +2 -2
  136. package/dist/i18n/index.js +2 -2
  137. package/dist/i18n/index.js.map +2 -2
  138. package/dist/i18n/locales/en.js +582 -1
  139. package/dist/i18n/locales/en.js.map +2 -2
  140. package/dist/i18n/locales/zh-CN.js +582 -1
  141. package/dist/i18n/locales/zh-CN.js.map +2 -2
  142. package/dist/i18n/types.js.map +1 -1
  143. package/dist/index.js +1 -1
  144. package/dist/index.js.map +2 -2
  145. package/dist/messages.js +11 -0
  146. package/dist/messages.js.map +2 -2
  147. package/dist/permissions.js.map +2 -2
  148. package/dist/query.js +9 -0
  149. package/dist/query.js.map +2 -2
  150. package/dist/screens/REPL.js +45 -7
  151. package/dist/screens/REPL.js.map +2 -2
  152. package/dist/services/customCommands.js +44 -16
  153. package/dist/services/customCommands.js.map +2 -2
  154. package/dist/services/plugins/lspServers.js +1 -1
  155. package/dist/services/plugins/lspServers.js.map +2 -2
  156. package/dist/services/plugins/pluginRuntime.js +2 -1
  157. package/dist/services/plugins/pluginRuntime.js.map +2 -2
  158. package/dist/services/plugins/pluginValidation.js +10 -3
  159. package/dist/services/plugins/pluginValidation.js.map +2 -2
  160. package/dist/services/plugins/skillMarketplace.js +16 -8
  161. package/dist/services/plugins/skillMarketplace.js.map +2 -2
  162. package/dist/services/systemReminder.js +17 -6
  163. package/dist/services/systemReminder.js.map +2 -2
  164. package/dist/tools/FileEditTool/FileEditTool.js +7 -0
  165. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  166. package/dist/tools/FileWriteTool/FileWriteTool.js +7 -0
  167. package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
  168. package/dist/tools/MultiEditTool/MultiEditTool.js +7 -0
  169. package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
  170. package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -0
  171. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  172. package/dist/tools/TaskTool/TaskTool.js +179 -1
  173. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  174. package/dist/tools/TodoWriteTool/prompt.js +21 -0
  175. package/dist/tools/TodoWriteTool/prompt.js.map +2 -2
  176. package/dist/tools/URLFetcherTool/prompt.js +14 -9
  177. package/dist/tools/URLFetcherTool/prompt.js.map +2 -2
  178. package/dist/tools/WebSearchTool/prompt.js +12 -6
  179. package/dist/tools/WebSearchTool/prompt.js.map +2 -2
  180. package/dist/types/PermissionMode.js +30 -1
  181. package/dist/types/PermissionMode.js.map +2 -2
  182. package/dist/types/plugin.js +2 -4
  183. package/dist/types/plugin.js.map +2 -2
  184. package/dist/utils/agentHookExecutor.js +103 -0
  185. package/dist/utils/agentHookExecutor.js.map +7 -0
  186. package/dist/utils/agentLoader.js +272 -32
  187. package/dist/utils/agentLoader.js.map +2 -2
  188. package/dist/utils/agentMemory.js +134 -0
  189. package/dist/utils/agentMemory.js.map +7 -0
  190. package/dist/utils/claudeCodeSync.js +439 -0
  191. package/dist/utils/claudeCodeSync.js.map +7 -0
  192. package/dist/utils/config.js +52 -24
  193. package/dist/utils/config.js.map +2 -2
  194. package/dist/utils/configPaths.js +199 -0
  195. package/dist/utils/configPaths.js.map +7 -0
  196. package/dist/utils/execFileNoThrow.js +2 -1
  197. package/dist/utils/execFileNoThrow.js.map +2 -2
  198. package/dist/utils/historyManager.js +234 -0
  199. package/dist/utils/historyManager.js.map +7 -0
  200. package/dist/utils/marketplaceManager.js +80 -43
  201. package/dist/utils/marketplaceManager.js.map +2 -2
  202. package/dist/utils/messages.js +13 -8
  203. package/dist/utils/messages.js.map +2 -2
  204. package/dist/utils/migration/index.js +37 -0
  205. package/dist/utils/migration/index.js.map +7 -0
  206. package/dist/utils/migration/migrateHistory.js +273 -0
  207. package/dist/utils/migration/migrateHistory.js.map +7 -0
  208. package/dist/utils/migration/migrateTodos.js +323 -0
  209. package/dist/utils/migration/migrateTodos.js.map +7 -0
  210. package/dist/utils/pasteCache.js +309 -0
  211. package/dist/utils/pasteCache.js.map +7 -0
  212. package/dist/utils/pluginInstaller.js +34 -24
  213. package/dist/utils/pluginInstaller.js.map +2 -2
  214. package/dist/utils/pluginLoader.js +54 -28
  215. package/dist/utils/pluginLoader.js.map +2 -2
  216. package/dist/utils/repoFetcher.js +110 -0
  217. package/dist/utils/repoFetcher.js.map +7 -0
  218. package/dist/utils/sessionIndex.js +192 -0
  219. package/dist/utils/sessionIndex.js.map +7 -0
  220. package/dist/utils/sessionTracker.js +170 -0
  221. package/dist/utils/sessionTracker.js.map +7 -0
  222. package/dist/utils/skillLoader.js +103 -5
  223. package/dist/utils/skillLoader.js.map +2 -2
  224. package/dist/utils/stats.js +417 -0
  225. package/dist/utils/stats.js.map +7 -0
  226. package/dist/utils/stringSubstitution.js +106 -0
  227. package/dist/utils/stringSubstitution.js.map +7 -0
  228. package/dist/utils/teamConfig.js +156 -14
  229. package/dist/utils/teamConfig.js.map +2 -2
  230. package/dist/utils/terminal.js +1 -1
  231. package/dist/utils/terminal.js.map +2 -2
  232. package/dist/utils/todoStorage.js +51 -19
  233. package/dist/utils/todoStorage.js.map +2 -2
  234. package/dist/utils/tooling/safeRender.js.map +2 -2
  235. package/dist/version.js +2 -2
  236. package/dist/version.js.map +1 -1
  237. package/package.json +71 -28
  238. 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 { execFileNoThrow } from "./execFileNoThrow.js";
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
- * Clone git repository to temporary directory
114
+ * Fetch a git repository to a temporary directory using repoFetcher
115
115
  */
116
- async cloneGitRepo(url, ref) {
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
- const args = ["clone", "--depth", "1"];
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 clone repository: ${error instanceof Error ? error.message : String(error)}`,
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
- sourceDir = await this.cloneGitRepo(source.repo, source.ref);
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.cloneGitRepo(
317
- `https://github.com/${plugin.source.repo}.git`,
318
- plugin.source.ref
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.cloneGitRepo(
322
- plugin.source.url,
323
- plugin.source.ref
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": "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",
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 mintoPluginManifest = join(pluginPath, ".minto-plugin", "plugin.json");
54
- const rootManifest = join(pluginPath, "plugin.json");
55
- let manifestPath = null;
56
- if (existsSync(mintoPluginManifest)) {
57
- manifestPath = mintoPluginManifest;
58
- } else if (existsSync(rootManifest)) {
59
- manifestPath = rootManifest;
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
- - ${mintoPluginManifest}
65
- - ${rootManifest}`,
71
+ ${candidates.map((p) => ` - ${p}`).join("\n")}`,
66
72
  PluginErrorCode.MANIFEST_NOT_FOUND
67
73
  );
68
74
  }
69
- try {
70
- const manifestContent = readFileSync(manifestPath, "utf-8");
71
- const manifestData = JSON.parse(manifestContent);
72
- const manifest = PluginManifestSchema.parse(manifestData);
73
- return manifest;
74
- } catch (error) {
75
- throw new PluginError(
76
- `Invalid plugin manifest at ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`,
77
- PluginErrorCode.MANIFEST_INVALID,
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 {