@within-7/minto 0.1.5 → 0.1.6
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/dist/commands/agents/AgentsCommand.js +2342 -0
- package/dist/commands/agents/AgentsCommand.js.map +7 -0
- package/dist/commands/agents/constants.js +58 -0
- package/dist/commands/agents/constants.js.map +7 -0
- package/dist/commands/agents/index.js +37 -0
- package/dist/commands/agents/index.js.map +7 -0
- package/dist/commands/agents/types.js +10 -0
- package/dist/commands/agents/types.js.map +7 -0
- package/dist/commands/agents/utils/fileOperations.js +185 -0
- package/dist/commands/agents/utils/fileOperations.js.map +7 -0
- package/dist/commands/agents/utils/index.js +21 -0
- package/dist/commands/agents/utils/index.js.map +7 -0
- package/dist/commands/bug.js +2 -2
- package/dist/commands/bug.js.map +2 -2
- package/dist/commands/compact.js +5 -5
- package/dist/commands/compact.js.map +2 -2
- package/dist/commands/ctx_viz.js +55 -22
- package/dist/commands/ctx_viz.js.map +2 -2
- package/dist/commands/mcp-interactive.js +11 -11
- package/dist/commands/mcp-interactive.js.map +2 -2
- package/dist/commands/model.js +94 -32
- package/dist/commands/model.js.map +3 -3
- package/dist/commands/plugin/AddMarketplaceForm.js +49 -21
- package/dist/commands/plugin/AddMarketplaceForm.js.map +2 -2
- package/dist/commands/plugin/ConfirmDialog.js +38 -26
- package/dist/commands/plugin/ConfirmDialog.js.map +2 -2
- package/dist/commands/plugin/InstalledPluginsByMarketplace.js +24 -8
- package/dist/commands/plugin/InstalledPluginsByMarketplace.js.map +2 -2
- package/dist/commands/plugin/InstalledPluginsManager.js +3 -1
- package/dist/commands/plugin/InstalledPluginsManager.js.map +2 -2
- package/dist/commands/plugin/MainMenu.js +16 -7
- package/dist/commands/plugin/MainMenu.js.map +2 -2
- package/dist/commands/plugin/MarketplaceManager.js +84 -39
- package/dist/commands/plugin/MarketplaceManager.js.map +2 -2
- package/dist/commands/plugin/MarketplaceSelector.js +7 -3
- package/dist/commands/plugin/MarketplaceSelector.js.map +2 -2
- package/dist/commands/plugin/PlaceholderScreen.js +16 -2
- package/dist/commands/plugin/PlaceholderScreen.js.map +2 -2
- package/dist/commands/plugin/PluginBrowser.js +4 -2
- package/dist/commands/plugin/PluginBrowser.js.map +2 -2
- package/dist/commands/plugin/PluginDetailsInstall.js +12 -6
- package/dist/commands/plugin/PluginDetailsInstall.js.map +2 -2
- package/dist/commands/plugin/PluginDetailsManage.js +14 -5
- package/dist/commands/plugin/PluginDetailsManage.js.map +2 -2
- package/dist/commands/plugin/example-usage.js.map +2 -2
- package/dist/commands/plugin/utils.js.map +2 -2
- package/dist/commands/plugin.js +226 -46
- package/dist/commands/plugin.js.map +2 -2
- package/dist/commands/refreshCommands.js +6 -3
- package/dist/commands/refreshCommands.js.map +2 -2
- package/dist/commands/resume.js +2 -1
- package/dist/commands/resume.js.map +2 -2
- package/dist/commands/setup.js +19 -5
- package/dist/commands/setup.js.map +2 -2
- package/dist/commands/terminalSetup.js +2 -2
- package/dist/commands/terminalSetup.js.map +1 -1
- package/dist/commands.js +14 -30
- package/dist/commands.js.map +2 -2
- package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
- package/dist/components/AskUserQuestionDialog/QuestionView.js +10 -1
- package/dist/components/AskUserQuestionDialog/QuestionView.js.map +2 -2
- package/dist/components/BackgroundTasksPanel.js +5 -1
- package/dist/components/BackgroundTasksPanel.js.map +2 -2
- package/dist/components/Config.js +17 -4
- package/dist/components/Config.js.map +2 -2
- package/dist/components/ConsoleOAuthFlow.js.map +2 -2
- package/dist/components/CustomSelect/select-option.js +4 -1
- package/dist/components/CustomSelect/select-option.js.map +2 -2
- package/dist/components/Help.js +6 -8
- package/dist/components/Help.js.map +2 -2
- package/dist/components/Logo.js +1 -1
- package/dist/components/Logo.js.map +2 -2
- package/dist/components/ModelListManager.js.map +2 -2
- package/dist/components/ModelSelector/ModelSelector.js +2030 -0
- package/dist/components/ModelSelector/ModelSelector.js.map +7 -0
- package/dist/components/ModelSelector/ScreenContainer.js +27 -0
- package/dist/components/ModelSelector/ScreenContainer.js.map +7 -0
- package/dist/components/ModelSelector/constants.js +37 -0
- package/dist/components/ModelSelector/constants.js.map +7 -0
- package/dist/components/ModelSelector/hooks/index.js +5 -0
- package/dist/components/ModelSelector/hooks/index.js.map +7 -0
- package/dist/components/ModelSelector/hooks/useEscapeNavigation.js +21 -0
- package/dist/components/ModelSelector/hooks/useEscapeNavigation.js.map +7 -0
- package/dist/components/ModelSelector/index.js +17 -0
- package/dist/components/ModelSelector/index.js.map +7 -0
- package/dist/components/ModelSelector/types.js +1 -0
- package/dist/components/ModelSelector/types.js.map +7 -0
- package/dist/components/PressEnterToContinue.js +1 -1
- package/dist/components/PressEnterToContinue.js.map +2 -2
- package/dist/components/ProjectOnboarding.js +1 -1
- package/dist/components/ProjectOnboarding.js.map +2 -2
- package/dist/components/PromptInput.js +88 -37
- package/dist/components/PromptInput.js.map +2 -2
- package/dist/components/QuitSummary.js +17 -10
- package/dist/components/QuitSummary.js.map +2 -2
- package/dist/components/SentryErrorBoundary.js.map +2 -2
- package/dist/components/StreamingBashOutput.js.map +2 -2
- package/dist/components/StructuredDiff.js.map +2 -2
- package/dist/components/SubagentProgress.js.map +2 -2
- package/dist/components/TaskCard.js.map +2 -2
- package/dist/components/TextInput.js.map +1 -1
- package/dist/components/TodoItem.js.map +1 -1
- package/dist/components/binary-feedback/BinaryFeedbackOption.js +1 -3
- package/dist/components/binary-feedback/BinaryFeedbackOption.js.map +2 -2
- package/dist/components/messages/AssistantLocalCommandOutputMessage.js.map +1 -1
- package/dist/components/messages/AssistantToolUseMessage.js +3 -1
- package/dist/components/messages/AssistantToolUseMessage.js.map +2 -2
- package/dist/components/messages/TaskProgressMessage.js.map +2 -2
- package/dist/components/messages/TaskToolMessage.js.map +2 -2
- package/dist/components/messages/UserToolResultMessage/utils.js.map +2 -2
- package/dist/components/permissions/FileEditPermissionRequest/FileEditToolDiff.js.map +2 -2
- package/dist/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.js.map +2 -2
- package/dist/components/permissions/hooks.js.map +2 -2
- package/dist/constants/modelCapabilities.js +1 -1
- package/dist/constants/modelCapabilities.js.map +2 -2
- package/dist/constants/prompts.js.map +1 -1
- package/dist/constants/timing.js +34 -0
- package/dist/constants/timing.js.map +7 -0
- package/dist/entrypoints/cli.js +128 -33
- package/dist/entrypoints/cli.js.map +3 -3
- package/dist/entrypoints/mcp.js +13 -18
- package/dist/entrypoints/mcp.js.map +2 -2
- package/dist/hooks/useCanUseTool.js.map +2 -2
- package/dist/hooks/useCancelRequest.js.map +1 -1
- package/dist/hooks/useHistorySearch.js.map +2 -2
- package/dist/hooks/useLogStartupTime.js.map +2 -2
- package/dist/hooks/usePermissionRequestLogging.js.map +2 -2
- package/dist/hooks/useTextInput.js.map +1 -1
- package/dist/hooks/useUnifiedCompletion.js +493 -394
- package/dist/hooks/useUnifiedCompletion.js.map +2 -2
- package/dist/index.js.map +2 -2
- package/dist/permissions.js +4 -7
- package/dist/permissions.js.map +2 -2
- package/dist/query.js +6 -1
- package/dist/query.js.map +2 -2
- package/dist/screens/REPL.js +72 -36
- package/dist/screens/REPL.js.map +2 -2
- package/dist/screens/ResumeConversation.js +2 -1
- package/dist/screens/ResumeConversation.js.map +2 -2
- package/dist/services/adapters/base.js.map +2 -2
- package/dist/services/adapters/chatCompletions.js.map +2 -2
- package/dist/services/adapters/responsesAPI.js +3 -1
- package/dist/services/adapters/responsesAPI.js.map +2 -2
- package/dist/services/claude.js +327 -328
- package/dist/services/claude.js.map +2 -2
- package/dist/services/customCommands.js +6 -1
- package/dist/services/customCommands.js.map +2 -2
- package/dist/services/fileFreshness.js.map +2 -2
- package/dist/services/gpt5ConnectionTest.js +20 -7
- package/dist/services/gpt5ConnectionTest.js.map +2 -2
- package/dist/services/hookExecutor.js +6 -12
- package/dist/services/hookExecutor.js.map +2 -2
- package/dist/services/mcpClient.js +29 -2
- package/dist/services/mcpClient.js.map +2 -2
- package/dist/services/mentionProcessor.js +23 -10
- package/dist/services/mentionProcessor.js.map +2 -2
- package/dist/services/modelAdapterFactory.js.map +2 -2
- package/dist/services/oauth.js.map +2 -2
- package/dist/services/openai.js +109 -72
- package/dist/services/openai.js.map +3 -3
- package/dist/services/responseStateManager.js.map +2 -2
- package/dist/services/systemReminder.js.map +2 -2
- package/dist/tools/ArchitectTool/ArchitectTool.js.map +1 -1
- package/dist/tools/AskExpertModelTool/AskExpertModelTool.js +14 -8
- package/dist/tools/AskExpertModelTool/AskExpertModelTool.js.map +2 -2
- package/dist/tools/BashOutputTool/BashOutputTool.js.map +2 -2
- package/dist/tools/BashTool/BashTool.js.map +2 -2
- package/dist/tools/FileReadTool/FileReadTool.js.map +1 -1
- package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
- package/dist/tools/GrepTool/GrepTool.js +1 -4
- package/dist/tools/GrepTool/GrepTool.js.map +2 -2
- package/dist/tools/MultiEditTool/MultiEditTool.js +4 -1
- package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
- package/dist/tools/NotebookReadTool/NotebookReadTool.js +3 -1
- package/dist/tools/NotebookReadTool/NotebookReadTool.js.map +2 -2
- package/dist/tools/SkillTool/SkillTool.js +12 -6
- package/dist/tools/SkillTool/SkillTool.js.map +2 -2
- package/dist/tools/TaskTool/TaskTool.js +14 -5
- package/dist/tools/TaskTool/TaskTool.js.map +2 -2
- package/dist/tools/TaskTool/prompt.js.map +2 -2
- package/dist/tools/ThinkTool/ThinkTool.js +6 -1
- package/dist/tools/ThinkTool/ThinkTool.js.map +2 -2
- package/dist/tools/TodoWriteTool/TodoWriteTool.js +23 -3
- package/dist/tools/TodoWriteTool/TodoWriteTool.js.map +2 -2
- package/dist/tools/URLFetcherTool/URLFetcherTool.js +2 -2
- package/dist/tools/URLFetcherTool/URLFetcherTool.js.map +2 -2
- package/dist/tools/URLFetcherTool/cache.js +6 -3
- package/dist/tools/URLFetcherTool/cache.js.map +2 -2
- package/dist/tools/URLFetcherTool/htmlToMarkdown.js +3 -1
- package/dist/tools/URLFetcherTool/htmlToMarkdown.js.map +2 -2
- package/dist/tools/WebSearchTool/WebSearchTool.js.map +2 -2
- package/dist/tools/WebSearchTool/prompt.js.map +2 -2
- package/dist/tools/WebSearchTool/searchProviders.js +15 -6
- package/dist/tools/WebSearchTool/searchProviders.js.map +2 -2
- package/dist/tools.js +4 -1
- package/dist/tools.js.map +2 -2
- package/dist/types/core.js +1 -0
- package/dist/types/core.js.map +7 -0
- package/dist/types/hooks.js +1 -4
- package/dist/types/hooks.js.map +2 -2
- package/dist/types/marketplace.js +8 -2
- package/dist/types/marketplace.js.map +2 -2
- package/dist/types/plugin.js +9 -6
- package/dist/types/plugin.js.map +2 -2
- package/dist/utils/BackgroundShellManager.js +76 -10
- package/dist/utils/BackgroundShellManager.js.map +2 -2
- package/dist/utils/PersistentShell.js +7 -2
- package/dist/utils/PersistentShell.js.map +2 -2
- package/dist/utils/advancedFuzzyMatcher.js +4 -1
- package/dist/utils/advancedFuzzyMatcher.js.map +2 -2
- package/dist/utils/agentLoader.js +69 -35
- package/dist/utils/agentLoader.js.map +2 -2
- package/dist/utils/agentStorage.js.map +2 -2
- package/dist/utils/async.js +163 -0
- package/dist/utils/async.js.map +7 -0
- package/dist/utils/autoUpdater.js +8 -2
- package/dist/utils/autoUpdater.js.map +2 -2
- package/dist/utils/commands.js +23 -11
- package/dist/utils/commands.js.map +2 -2
- package/dist/utils/commonUnixCommands.js +3 -1
- package/dist/utils/commonUnixCommands.js.map +2 -2
- package/dist/utils/compressionMode.js.map +2 -2
- package/dist/utils/config.js +30 -14
- package/dist/utils/config.js.map +2 -2
- package/dist/utils/debugLogger.js.map +2 -2
- package/dist/utils/env.js.map +2 -2
- package/dist/utils/envConfig.js +82 -0
- package/dist/utils/envConfig.js.map +7 -0
- package/dist/utils/errorHandling.js +89 -0
- package/dist/utils/errorHandling.js.map +7 -0
- package/dist/utils/expertChatStorage.js.map +2 -2
- package/dist/utils/fuzzyMatcher.js +13 -7
- package/dist/utils/fuzzyMatcher.js.map +2 -2
- package/dist/utils/hookManager.js +14 -4
- package/dist/utils/hookManager.js.map +2 -2
- package/dist/utils/log.js.map +2 -2
- package/dist/utils/marketplaceManager.js +44 -9
- package/dist/utils/marketplaceManager.js.map +2 -2
- package/dist/utils/messageContextManager.js.map +1 -1
- package/dist/utils/messages.js +6 -3
- package/dist/utils/messages.js.map +2 -2
- package/dist/utils/model.js +3 -1
- package/dist/utils/model.js.map +2 -2
- package/dist/utils/pluginInstaller.js +3 -15
- package/dist/utils/pluginInstaller.js.map +2 -2
- package/dist/utils/pluginLoader.js +41 -13
- package/dist/utils/pluginLoader.js.map +2 -2
- package/dist/utils/pluginRegistry.js.map +2 -2
- package/dist/utils/pluginValidator.js +71 -49
- package/dist/utils/pluginValidator.js.map +2 -2
- package/dist/utils/ptyCompat.js.map +2 -2
- package/dist/utils/roundConverter.js.map +2 -2
- package/dist/utils/secureFile.js +43 -14
- package/dist/utils/secureFile.js.map +2 -2
- package/dist/utils/sessionState.js.map +2 -2
- package/dist/utils/skillLoader.js.map +2 -2
- package/dist/utils/teamConfig.js +7 -4
- package/dist/utils/teamConfig.js.map +2 -2
- package/dist/utils/theme.js.map +2 -2
- package/dist/utils/thinking.js.map +2 -2
- package/dist/utils/unaryLogging.js.map +2 -2
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/types/marketplace.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Plugin Marketplace Type Definitions\n *\n * Compatible with Claude Code CLI marketplace specification:\n * https://docs.claude.com/en/docs/claude-code/plugin-marketplaces\n */\n\nimport { z } from 'zod'\n\n/**\n * Plugin source types for marketplace entries\n */\nexport const PluginSourceSchema = z.union([\n // Relative path (within same repository)\n z.string(),\n\n // GitHub repository\n z.object({\n source: z.literal('github'),\n repo: z.string().regex(/^[\\w-]+\\/[\\w-]+$/, 'Must be in format: owner/repo'),\n ref: z.string().optional(), // branch, tag, or commit SHA\n }),\n\n // Generic git URL\n z.object({\n source: z.literal('url'),\n url: z.string().url(),\n ref: z.string().optional(),\n }),\n\n // Local path\n z.object({\n source: z.literal('local'),\n path: z.string(),\n }),\n])\n\nexport type PluginSourceType = z.infer<typeof PluginSourceSchema>\n\n/**\n * Author information (can be string or object)\n */\nexport const AuthorSchema = z.union([\n z.string(),\n z.object({\n name: z.string(),\n email: z.string().email().optional(),\n url: z.string().url().optional(),\n })
|
|
5
|
-
"mappings": "AAOA,SAAS,SAAS;AAKX,MAAM,qBAAqB,EAAE,MAAM;AAAA;AAAA,EAExC,EAAE,OAAO;AAAA;AAAA,EAGT,EAAE,OAAO;AAAA,IACP,QAAQ,EAAE,QAAQ,QAAQ;AAAA,IAC1B,MAAM,EAAE,OAAO,EAAE,MAAM,oBAAoB,+BAA+B;AAAA,IAC1E,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA;AAAA,EAC3B,CAAC;AAAA;AAAA,EAGD,EAAE,OAAO;AAAA,IACP,QAAQ,EAAE,QAAQ,KAAK;AAAA,IACvB,KAAK,EAAE,OAAO,EAAE,IAAI;AAAA,IACpB,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,CAAC;AAAA;AAAA,EAGD,EAAE,OAAO;AAAA,IACP,QAAQ,EAAE,QAAQ,OAAO;AAAA,IACzB,MAAM,EAAE,OAAO;AAAA,EACjB,CAAC;AACH,CAAC;AAOM,MAAM,eAAe,EAAE,MAAM;AAAA,EAClC,EAAE,OAAO;AAAA,EACT,EAAE,OAAO;AAAA,IACP,MAAM,EAAE,OAAO;AAAA,IACf,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS;AAAA,IACnC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACjC,CAAC;AACH,CAAC;AAKM,MAAM,0BAA0B,EAAE,OAAO;AAAA;AAAA,EAE9C,MAAM,
|
|
4
|
+
"sourcesContent": ["/**\n * Plugin Marketplace Type Definitions\n *\n * Compatible with Claude Code CLI marketplace specification:\n * https://docs.claude.com/en/docs/claude-code/plugin-marketplaces\n */\n\nimport { z } from 'zod'\n\n/**\n * Plugin source types for marketplace entries\n */\nexport const PluginSourceSchema = z.union([\n // Relative path (within same repository)\n z.string(),\n\n // GitHub repository\n z.object({\n source: z.literal('github'),\n repo: z.string().regex(/^[\\w-]+\\/[\\w-]+$/, 'Must be in format: owner/repo'),\n ref: z.string().optional(), // branch, tag, or commit SHA\n }),\n\n // Generic git URL\n z.object({\n source: z.literal('url'),\n url: z.string().url(),\n ref: z.string().optional(),\n }),\n\n // Local path\n z.object({\n source: z.literal('local'),\n path: z.string(),\n }),\n])\n\nexport type PluginSourceType = z.infer<typeof PluginSourceSchema>\n\n/**\n * Author information (can be string or object)\n */\nexport const AuthorSchema = z.union([\n z.string(),\n z.object({\n name: z.string(),\n email: z.string().email().optional(),\n url: z.string().url().optional(),\n }),\n])\n\n/**\n * Marketplace plugin entry\n */\nexport const MarketplacePluginSchema = z.object({\n // Required\n name: z\n .string()\n .min(1)\n .regex(\n /^[a-z0-9-]+$/,\n 'Plugin name must be lowercase alphanumeric with hyphens',\n ),\n source: PluginSourceSchema,\n\n // Optional metadata\n description: z.string().optional(),\n version: z.string().optional(),\n author: AuthorSchema.optional(),\n homepage: z.string().url().optional(),\n repository: z.string().url().optional(),\n license: z.string().optional(),\n keywords: z.array(z.string()).optional(),\n category: z.string().optional(),\n tags: z.array(z.string()).optional(),\n\n // Component paths (relative to plugin root)\n commands: z.array(z.string()).optional(),\n agents: z.array(z.string()).optional(),\n hooks: z.array(z.string()).optional(),\n mcpServers: z.array(z.string()).optional(),\n skills: z.array(z.string()).optional(),\n\n // Strict mode: require plugin.json in plugin directory\n strict: z.boolean().optional().default(true),\n})\n\nexport type MarketplacePlugin = z.infer<typeof MarketplacePluginSchema>\n\n/**\n * Marketplace owner information\n */\nexport const MarketplaceOwnerSchema = z.object({\n name: z.string(),\n email: z.string().email().optional(),\n url: z.string().url().optional(),\n})\n\nexport type MarketplaceOwner = z.infer<typeof MarketplaceOwnerSchema>\n\n/**\n * Marketplace metadata\n */\nexport const MarketplaceMetadataSchema = z.object({\n description: z.string().optional(),\n version: z.string().optional(),\n pluginRoot: z.string().optional(), // Base path for relative sources\n})\n\nexport type MarketplaceMetadata = z.infer<typeof MarketplaceMetadataSchema>\n\n/**\n * Marketplace manifest (marketplace.json)\n */\nexport const MarketplaceManifestSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(\n /^[a-z0-9-]+$/,\n 'Marketplace name must be lowercase alphanumeric with hyphens',\n ),\n owner: MarketplaceOwnerSchema,\n plugins: z.array(MarketplacePluginSchema),\n metadata: MarketplaceMetadataSchema.optional(),\n})\n\nexport type MarketplaceManifest = z.infer<typeof MarketplaceManifestSchema>\n\n/**\n * Marketplace configuration source\n */\nexport type MarketplaceSource =\n | { type: 'github'; repo: string; ref?: string }\n | { type: 'url'; url: string; ref?: string }\n | { type: 'local'; path: string }\n\n/**\n * Registered marketplace\n */\nexport interface RegisteredMarketplace {\n name: string\n source: MarketplaceSource\n manifest: MarketplaceManifest\n lastUpdated: Date\n enabled: boolean\n}\n\n/**\n * Marketplace configuration in .claude/settings.json\n */\nexport interface MarketplaceSettings {\n extraKnownMarketplaces?: Record<\n string,\n {\n source: {\n source: 'github' | 'url' | 'local'\n repo?: string\n url?: string\n path?: string\n ref?: string\n }\n }\n >\n}\n\n/**\n * Marketplace error types\n */\nexport class MarketplaceError extends Error {\n constructor(\n message: string,\n public code: MarketplaceErrorCode,\n public marketplaceName?: string,\n public details?: any,\n ) {\n super(message)\n this.name = 'MarketplaceError'\n }\n}\n\nexport enum MarketplaceErrorCode {\n MANIFEST_INVALID = 'MANIFEST_INVALID',\n MANIFEST_NOT_FOUND = 'MANIFEST_NOT_FOUND',\n NETWORK_ERROR = 'NETWORK_ERROR',\n GIT_ERROR = 'GIT_ERROR',\n ALREADY_REGISTERED = 'ALREADY_REGISTERED',\n NOT_REGISTERED = 'NOT_REGISTERED',\n PLUGIN_NOT_FOUND = 'PLUGIN_NOT_FOUND',\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["MarketplaceErrorCode"]
|
|
7
7
|
}
|
package/dist/types/plugin.js
CHANGED
|
@@ -20,8 +20,14 @@ const MCPServerConfigSchema = z.object({
|
|
|
20
20
|
});
|
|
21
21
|
const PluginManifestSchema = z.object({
|
|
22
22
|
// Required fields
|
|
23
|
-
name: z.string().min(1).regex(
|
|
24
|
-
|
|
23
|
+
name: z.string().min(1).regex(
|
|
24
|
+
/^[a-z0-9-]+$/,
|
|
25
|
+
"Plugin name must be lowercase alphanumeric with hyphens"
|
|
26
|
+
),
|
|
27
|
+
version: z.string().regex(
|
|
28
|
+
/^\d+\.\d+\.\d+$/,
|
|
29
|
+
"Version must follow semver format (e.g., 1.0.0)"
|
|
30
|
+
),
|
|
25
31
|
description: z.string().min(1),
|
|
26
32
|
// Optional metadata
|
|
27
33
|
displayName: z.string().optional(),
|
|
@@ -37,10 +43,7 @@ const PluginManifestSchema = z.object({
|
|
|
37
43
|
// MCP servers support both:
|
|
38
44
|
// - Array of file paths: ["mcp-servers/server1.json"]
|
|
39
45
|
// - Object with inline configs: { "server1": { command: "..." } }
|
|
40
|
-
mcpServers: z.union([
|
|
41
|
-
z.array(z.string()),
|
|
42
|
-
z.record(MCPServerConfigSchema)
|
|
43
|
-
]).optional().default([]),
|
|
46
|
+
mcpServers: z.union([z.array(z.string()), z.record(MCPServerConfigSchema)]).optional().default([]),
|
|
44
47
|
// Dependencies
|
|
45
48
|
dependencies: z.record(z.string()).optional().default({}),
|
|
46
49
|
// Minto/Claude Code version requirements
|
package/dist/types/plugin.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/types/plugin.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Plugin System Type Definitions\n *\n * Fully compatible with Claude Code CLI plugin specification:\n * https://docs.claude.com/en/docs/claude-code/plugins-reference\n */\n\nimport { z } from 'zod'\n\n/**\n * Author information (can be string or object)\n */\nexport const AuthorSchema = z.union([\n z.string(),\n z.object({\n name: z.string(),\n email: z.string().email().optional(),\n url: z.string().url().optional(),\n })
|
|
5
|
-
"mappings": "AAOA,SAAS,SAAS;AAKX,MAAM,eAAe,EAAE,MAAM;AAAA,EAClC,EAAE,OAAO;AAAA,EACT,EAAE,OAAO;AAAA,IACP,MAAM,EAAE,OAAO;AAAA,IACf,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS;AAAA,IACnC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACjC,CAAC;AACH,CAAC;AAMM,MAAM,wBAAwB,EAAE,OAAO;AAAA;AAAA,EAE5C,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC/C,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC/C,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA;AAAA,EAG7B,MAAM,EAAE,KAAK,CAAC,SAAS,QAAQ,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,OAAO;AAAA,EACjE,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA,EACzB,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,SAAS;AACzC,CAAC;AAQM,MAAM,uBAAuB,EAAE,OAAO;AAAA;AAAA,EAE3C,MAAM,
|
|
4
|
+
"sourcesContent": ["/**\n * Plugin System Type Definitions\n *\n * Fully compatible with Claude Code CLI plugin specification:\n * https://docs.claude.com/en/docs/claude-code/plugins-reference\n */\n\nimport { z } from 'zod'\n\n/**\n * Author information (can be string or object)\n */\nexport const AuthorSchema = z.union([\n z.string(),\n z.object({\n name: z.string(),\n email: z.string().email().optional(),\n url: z.string().url().optional(),\n }),\n])\n\n/**\n * MCP Server Configuration Schema\n * Compatible with Claude Code CLI's .mcp.json format\n */\nexport const MCPServerConfigSchema = z.object({\n // Command-based (stdio) servers\n command: z.string().optional(),\n args: z.array(z.string()).optional().default([]),\n env: z.record(z.string()).optional().default({}),\n timeout: z.number().optional(),\n\n // HTTP/SSE servers\n type: z.enum(['stdio', 'http', 'sse']).optional().default('stdio'),\n url: z.string().optional(),\n headers: z.record(z.string()).optional(),\n})\n\nexport type MCPServerConfig = z.infer<typeof MCPServerConfigSchema>\n\n/**\n * Plugin Manifest Schema (plugin.json)\n * This is the source of truth for plugin metadata\n */\nexport const PluginManifestSchema = z.object({\n // Required fields\n name: z\n .string()\n .min(1)\n .regex(\n /^[a-z0-9-]+$/,\n 'Plugin name must be lowercase alphanumeric with hyphens',\n ),\n version: z\n .string()\n .regex(\n /^\\d+\\.\\d+\\.\\d+$/,\n 'Version must follow semver format (e.g., 1.0.0)',\n ),\n description: z.string().min(1),\n\n // Optional metadata\n displayName: z.string().optional(),\n author: AuthorSchema.optional(),\n homepage: z.string().url().optional(),\n repository: z.string().url().optional(),\n license: z.string().optional(),\n\n // Plugin components (relative paths from plugin root)\n agents: z.array(z.string()).optional().default([]),\n commands: z.array(z.string()).optional().default([]),\n skills: z.array(z.string()).optional().default([]),\n hooks: z.array(z.string()).optional().default([]),\n\n // MCP servers support both:\n // - Array of file paths: [\"mcp-servers/server1.json\"]\n // - Object with inline configs: { \"server1\": { command: \"...\" } }\n mcpServers: z\n .union([z.array(z.string()), z.record(MCPServerConfigSchema)])\n .optional()\n .default([]),\n\n // Dependencies\n dependencies: z.record(z.string()).optional().default({}),\n\n // Minto/Claude Code version requirements\n engines: z\n .object({\n minto: z.string().optional(),\n 'claude-code': z.string().optional(),\n node: z.string().optional(),\n })\n .optional(),\n\n // Plugin-specific configuration schema\n configSchema: z.record(z.any()).optional(),\n})\n\nexport type PluginManifest = z.infer<typeof PluginManifestSchema>\n\n/**\n * Loaded Plugin (runtime representation)\n */\nexport interface LoadedPlugin {\n name: string\n manifest: PluginManifest\n location: string // Absolute path to plugin directory\n source: PluginSource\n agents: LoadedAgent[]\n commands: LoadedCommand[]\n skills: LoadedSkill[]\n hooks: LoadedHook[]\n mcpServers: LoadedMCPServer[]\n enabled: boolean\n config?: Record<string, any>\n}\n\n/**\n * Plugin source types\n */\nexport type PluginSource =\n | { type: 'local'; path: string }\n | { type: 'git'; repo: string; ref?: string }\n | { type: 'npm'; package: string; version?: string }\n | { type: 'marketplace'; marketplace: string; name: string }\n\n/**\n * Agent component (from plugin)\n */\nexport interface LoadedAgent {\n name: string\n filePath: string\n config: {\n name: string\n description: string\n tools?: string | string[]\n model?: string\n content: string\n }\n pluginName: string\n}\n\n/**\n * Command component (from plugin)\n */\nexport interface LoadedCommand {\n name: string\n filePath: string\n config: {\n name: string\n description?: string\n aliases?: string[]\n enabled?: boolean\n hidden?: boolean\n progressMessage?: string\n argNames?: string[]\n 'allowed-tools'?: string[]\n content: string\n }\n pluginName: string\n}\n\n/**\n * Skill component (from plugin)\n */\nexport interface LoadedSkill {\n name: string\n filePath: string\n config: {\n name: string\n description: string\n content: string\n }\n pluginName: string\n}\n\n/**\n * Hook component (from plugin)\n */\nexport interface LoadedHook {\n name: string\n filePath: string\n config: {\n event: HookEvent\n matcher?: string\n type: 'command' | 'message' | 'notification'\n command?: string\n message?: string\n blocking?: boolean\n timeout?: number\n }\n pluginName: string\n event: HookEvent // Also store event at root level for easier access\n matcher?: string // Also store matcher at root level\n}\n\n/**\n * Hook lifecycle events\n */\nexport type HookEvent =\n | 'PreToolUse'\n | 'PostToolUse'\n | 'UserPromptSubmit'\n | 'SessionStart'\n | 'SessionEnd'\n | 'Stop'\n | 'SubagentStop'\n | 'Notification'\n | 'PreCompact'\n\n/**\n * MCP Server component (from plugin)\n */\nexport interface LoadedMCPServer {\n name: string\n filePath: string\n config: {\n command: string\n args?: string[]\n env?: Record<string, string>\n timeout?: number\n }\n pluginName: string\n}\n\n/**\n * Plugin installation options\n */\nexport interface PluginInstallOptions {\n source: PluginSource\n global?: boolean // Install globally vs project-level\n enable?: boolean // Auto-enable after install\n config?: Record<string, any> // Plugin-specific config\n}\n\n/**\n * Plugin marketplace configuration\n */\nexport interface PluginMarketplace {\n name: string\n source: PluginSource\n plugins: PluginManifest[]\n}\n\n/**\n * Plugin error types\n */\nexport class PluginError extends Error {\n constructor(\n message: string,\n public code: PluginErrorCode,\n public pluginName?: string,\n public details?: any,\n ) {\n super(message)\n this.name = 'PluginError'\n }\n}\n\nexport enum PluginErrorCode {\n MANIFEST_INVALID = 'MANIFEST_INVALID',\n MANIFEST_NOT_FOUND = 'MANIFEST_NOT_FOUND',\n VERSION_MISMATCH = 'VERSION_MISMATCH',\n DEPENDENCY_MISSING = 'DEPENDENCY_MISSING',\n COMPONENT_NOT_FOUND = 'COMPONENT_NOT_FOUND',\n COMPONENT_INVALID = 'COMPONENT_INVALID',\n ALREADY_INSTALLED = 'ALREADY_INSTALLED',\n NOT_INSTALLED = 'NOT_INSTALLED',\n PERMISSION_DENIED = 'PERMISSION_DENIED',\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["PluginErrorCode"]
|
|
7
7
|
}
|
|
@@ -3,9 +3,41 @@ import { EventEmitter } from "events";
|
|
|
3
3
|
import { randomUUID } from "crypto";
|
|
4
4
|
import { logError } from "./log.js";
|
|
5
5
|
const MAX_OUTPUT_LINES = 1e3;
|
|
6
|
+
const REGEX_CACHE_SIZE = 50;
|
|
7
|
+
class RegExpCache {
|
|
8
|
+
cache = /* @__PURE__ */ new Map();
|
|
9
|
+
maxSize;
|
|
10
|
+
constructor(maxSize = REGEX_CACHE_SIZE) {
|
|
11
|
+
this.maxSize = maxSize;
|
|
12
|
+
}
|
|
13
|
+
get(pattern) {
|
|
14
|
+
const regex = this.cache.get(pattern);
|
|
15
|
+
if (regex) {
|
|
16
|
+
this.cache.delete(pattern);
|
|
17
|
+
this.cache.set(pattern, regex);
|
|
18
|
+
}
|
|
19
|
+
return regex;
|
|
20
|
+
}
|
|
21
|
+
set(pattern, regex) {
|
|
22
|
+
if (this.cache.size >= this.maxSize) {
|
|
23
|
+
const oldest = this.cache.keys().next().value;
|
|
24
|
+
if (oldest) this.cache.delete(oldest);
|
|
25
|
+
}
|
|
26
|
+
this.cache.set(pattern, regex);
|
|
27
|
+
}
|
|
28
|
+
getOrCreate(pattern) {
|
|
29
|
+
let regex = this.get(pattern);
|
|
30
|
+
if (!regex) {
|
|
31
|
+
regex = new RegExp(pattern);
|
|
32
|
+
this.set(pattern, regex);
|
|
33
|
+
}
|
|
34
|
+
return regex;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
6
37
|
class BackgroundShellManager extends EventEmitter {
|
|
7
38
|
static instance = null;
|
|
8
39
|
shells = /* @__PURE__ */ new Map();
|
|
40
|
+
regexCache = new RegExpCache();
|
|
9
41
|
constructor() {
|
|
10
42
|
super();
|
|
11
43
|
}
|
|
@@ -48,7 +80,10 @@ class BackgroundShellManager extends EventEmitter {
|
|
|
48
80
|
if (shell.stdout.length > MAX_OUTPUT_LINES) {
|
|
49
81
|
const removed = shell.stdout.length - MAX_OUTPUT_LINES;
|
|
50
82
|
shell.stdout = shell.stdout.slice(removed);
|
|
51
|
-
shell.lastReadStdoutIndex = Math.max(
|
|
83
|
+
shell.lastReadStdoutIndex = Math.max(
|
|
84
|
+
0,
|
|
85
|
+
shell.lastReadStdoutIndex - removed
|
|
86
|
+
);
|
|
52
87
|
}
|
|
53
88
|
this.emit("output", shellId, "stdout", lines);
|
|
54
89
|
});
|
|
@@ -58,7 +93,10 @@ class BackgroundShellManager extends EventEmitter {
|
|
|
58
93
|
if (shell.stderr.length > MAX_OUTPUT_LINES) {
|
|
59
94
|
const removed = shell.stderr.length - MAX_OUTPUT_LINES;
|
|
60
95
|
shell.stderr = shell.stderr.slice(removed);
|
|
61
|
-
shell.lastReadStderrIndex = Math.max(
|
|
96
|
+
shell.lastReadStderrIndex = Math.max(
|
|
97
|
+
0,
|
|
98
|
+
shell.lastReadStderrIndex - removed
|
|
99
|
+
);
|
|
62
100
|
}
|
|
63
101
|
this.emit("output", shellId, "stderr", lines);
|
|
64
102
|
});
|
|
@@ -67,17 +105,27 @@ class BackgroundShellManager extends EventEmitter {
|
|
|
67
105
|
shell.exitCode = code ?? void 0;
|
|
68
106
|
shell.endTime = Date.now();
|
|
69
107
|
this.emit("statusChange", shellId, shell.status);
|
|
108
|
+
this.emitListChange();
|
|
70
109
|
});
|
|
71
110
|
childProcess.on("error", (error) => {
|
|
72
111
|
logError(`Background shell ${shellId} error: ${error.message}`);
|
|
73
112
|
shell.status = "killed";
|
|
74
113
|
shell.endTime = Date.now();
|
|
75
114
|
this.emit("statusChange", shellId, shell.status);
|
|
115
|
+
this.emitListChange();
|
|
76
116
|
});
|
|
77
117
|
this.shells.set(shellId, shell);
|
|
78
118
|
this.emit("shellCreated", shellId);
|
|
119
|
+
this.emitListChange();
|
|
79
120
|
return shellId;
|
|
80
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Emit a list change event with the current shell list
|
|
124
|
+
* Used for event-driven updates instead of polling
|
|
125
|
+
*/
|
|
126
|
+
emitListChange() {
|
|
127
|
+
this.emit("listChange", this.list());
|
|
128
|
+
}
|
|
81
129
|
/**
|
|
82
130
|
* Get a background shell by ID
|
|
83
131
|
*/
|
|
@@ -94,7 +142,7 @@ class BackgroundShellManager extends EventEmitter {
|
|
|
94
142
|
let newStderr = shell.stderr.slice(shell.lastReadStderrIndex);
|
|
95
143
|
if (filter) {
|
|
96
144
|
try {
|
|
97
|
-
const regex =
|
|
145
|
+
const regex = this.regexCache.getOrCreate(filter);
|
|
98
146
|
newStdout = newStdout.filter((line) => regex.test(line));
|
|
99
147
|
newStderr = newStderr.filter((line) => regex.test(line));
|
|
100
148
|
} catch (e) {
|
|
@@ -122,39 +170,55 @@ class BackgroundShellManager extends EventEmitter {
|
|
|
122
170
|
kill(shellId) {
|
|
123
171
|
const shell = this.shells.get(shellId);
|
|
124
172
|
if (!shell) {
|
|
125
|
-
console.log(
|
|
173
|
+
console.log(
|
|
174
|
+
`[BackgroundShellManager] Cannot kill: shell ${shellId} not found`
|
|
175
|
+
);
|
|
126
176
|
return false;
|
|
127
177
|
}
|
|
128
178
|
if (shell.status === "running") {
|
|
129
179
|
try {
|
|
130
180
|
const pid = shell.process.pid;
|
|
131
181
|
if (!pid) {
|
|
132
|
-
console.log(
|
|
182
|
+
console.log(
|
|
183
|
+
`[BackgroundShellManager] Cannot kill: shell ${shellId} has no PID`
|
|
184
|
+
);
|
|
133
185
|
return false;
|
|
134
186
|
}
|
|
135
|
-
console.log(
|
|
187
|
+
console.log(
|
|
188
|
+
`[BackgroundShellManager] Killing shell ${shellId} (PID: ${pid}, command: ${shell.command})`
|
|
189
|
+
);
|
|
136
190
|
if (process.platform === "win32") {
|
|
137
191
|
spawn("taskkill", ["/pid", pid.toString(), "/f", "/t"]);
|
|
138
192
|
} else {
|
|
139
193
|
try {
|
|
140
194
|
process.kill(-pid, "SIGKILL");
|
|
141
|
-
console.log(
|
|
195
|
+
console.log(
|
|
196
|
+
`[BackgroundShellManager] Sent SIGKILL to process group -${pid}`
|
|
197
|
+
);
|
|
142
198
|
} catch (e) {
|
|
143
|
-
console.log(
|
|
199
|
+
console.log(
|
|
200
|
+
`[BackgroundShellManager] Process group kill failed, trying main process only`
|
|
201
|
+
);
|
|
144
202
|
process.kill(pid, "SIGKILL");
|
|
145
203
|
}
|
|
146
204
|
}
|
|
147
205
|
shell.status = "killed";
|
|
148
206
|
shell.endTime = Date.now();
|
|
149
207
|
this.emit("statusChange", shellId, "killed");
|
|
208
|
+
this.emitListChange();
|
|
150
209
|
return true;
|
|
151
210
|
} catch (error) {
|
|
152
|
-
console.error(
|
|
211
|
+
console.error(
|
|
212
|
+
`[BackgroundShellManager] Failed to kill shell ${shellId}:`,
|
|
213
|
+
error
|
|
214
|
+
);
|
|
153
215
|
logError(`Failed to kill shell ${shellId}: ${error}`);
|
|
154
216
|
return false;
|
|
155
217
|
}
|
|
156
218
|
}
|
|
157
|
-
console.log(
|
|
219
|
+
console.log(
|
|
220
|
+
`[BackgroundShellManager] Shell ${shellId} is not running (status: ${shell.status})`
|
|
221
|
+
);
|
|
158
222
|
return false;
|
|
159
223
|
}
|
|
160
224
|
/**
|
|
@@ -178,6 +242,7 @@ class BackgroundShellManager extends EventEmitter {
|
|
|
178
242
|
if (!shell || shell.status === "running") return false;
|
|
179
243
|
this.shells.delete(shellId);
|
|
180
244
|
this.emit("shellRemoved", shellId);
|
|
245
|
+
this.emitListChange();
|
|
181
246
|
return true;
|
|
182
247
|
}
|
|
183
248
|
/**
|
|
@@ -194,6 +259,7 @@ class BackgroundShellManager extends EventEmitter {
|
|
|
194
259
|
}
|
|
195
260
|
if (removed > 0) {
|
|
196
261
|
this.emit("cleaned", removed);
|
|
262
|
+
this.emitListChange();
|
|
197
263
|
}
|
|
198
264
|
return removed;
|
|
199
265
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/BackgroundShellManager.ts"],
|
|
4
|
-
"sourcesContent": ["import { spawn, type ChildProcess } from 'child_process'\nimport { EventEmitter } from 'events'\nimport { randomUUID } from 'crypto'\nimport { logError } from './log'\n\nexport type BackgroundShellStatus = 'running' | 'done' | 'killed'\n\nexport interface BackgroundShell {\n id: string\n command: string\n status: BackgroundShellStatus\n startTime: number\n endTime?: number\n process: ChildProcess\n stdout: string[]\n stderr: string[]\n exitCode?: number\n lastReadStdoutIndex: number\n lastReadStderrIndex: number\n}\n\nconst MAX_OUTPUT_LINES = 1000 // Keep last N lines in memory\n\nexport class BackgroundShellManager extends EventEmitter {\n private static instance: BackgroundShellManager | null = null\n private shells: Map<string, BackgroundShell> = new Map()\n\n private constructor() {\n super()\n }\n\n static getInstance(): BackgroundShellManager {\n if (!BackgroundShellManager.instance) {\n BackgroundShellManager.instance = new BackgroundShellManager()\n }\n return BackgroundShellManager.instance\n }\n\n /**\n * Create a new background shell and start executing the command\n */\n create(command: string, cwd?: string): string {\n const shellId = randomUUID().slice(0, 8)\n\n // Spawn the command in a shell\n const childProcess = spawn(command, [], {\n shell: true,\n cwd: cwd || process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: true, // Create a new process group for easier cleanup\n env: {\n ...process.env,\n GIT_EDITOR: 'true',\n },\n })\n\n const shell: BackgroundShell = {\n id: shellId,\n command,\n status: 'running',\n startTime: Date.now(),\n process: childProcess,\n stdout: [],\n stderr: [],\n lastReadStdoutIndex: 0,\n lastReadStderrIndex: 0,\n }\n\n // Capture stdout\n childProcess.stdout?.on('data', (data: Buffer) => {\n const lines = data.toString().split('\\n')\n shell.stdout.push(...lines)\n\n // Keep only last MAX_OUTPUT_LINES\n if (shell.stdout.length > MAX_OUTPUT_LINES) {\n const removed = shell.stdout.length - MAX_OUTPUT_LINES\n shell.stdout = shell.stdout.slice(removed)\n shell.lastReadStdoutIndex = Math.max(0, shell.lastReadStdoutIndex - removed)\n }\n\n this.emit('output', shellId, 'stdout', lines)\n })\n\n // Capture stderr\n childProcess.stderr?.on('data', (data: Buffer) => {\n const lines = data.toString().split('\\n')\n shell.stderr.push(...lines)\n\n // Keep only last MAX_OUTPUT_LINES\n if (shell.stderr.length > MAX_OUTPUT_LINES) {\n const removed = shell.stderr.length - MAX_OUTPUT_LINES\n shell.stderr = shell.stderr.slice(removed)\n shell.lastReadStderrIndex = Math.max(0, shell.lastReadStderrIndex - removed)\n }\n\n this.emit('output', shellId, 'stderr', lines)\n })\n\n // Handle process exit\n childProcess.on('exit', (code, signal) => {\n shell.status = signal ? 'killed' : 'done'\n shell.exitCode = code ?? undefined\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, shell.status)\n })\n\n childProcess.on('error', (error) => {\n logError(`Background shell ${shellId} error: ${error.message}`)\n shell.status = 'killed'\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, shell.status)\n })\n\n this.shells.set(shellId, shell)\n this.emit('shellCreated', shellId)\n\n return shellId\n }\n\n /**\n * Get a background shell by ID\n */\n get(shellId: string): BackgroundShell | undefined {\n return this.shells.get(shellId)\n }\n\n /**\n * Get new output since last read\n */\n getNewOutput(shellId: string, filter?: string): {\n stdout: string[]\n stderr: string[]\n } | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n let newStdout = shell.stdout.slice(shell.lastReadStdoutIndex)\n let newStderr = shell.stderr.slice(shell.lastReadStderrIndex)\n\n // Apply regex filter if provided\n if (filter) {\n try {\n const regex = new RegExp(filter)\n newStdout = newStdout.filter(line => regex.test(line))\n newStderr = newStderr.filter(line => regex.test(line))\n } catch (e) {\n logError(`Invalid regex filter: ${filter}`)\n }\n }\n\n // Update read indices\n shell.lastReadStdoutIndex = shell.stdout.length\n shell.lastReadStderrIndex = shell.stderr.length\n\n return { stdout: newStdout, stderr: newStderr }\n }\n\n /**\n * Get all output (entire buffer)\n */\n getAllOutput(shellId: string): {\n stdout: string[]\n stderr: string[]\n } | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n return {\n stdout: [...shell.stdout],\n stderr: [...shell.stderr],\n }\n }\n\n /**\n * Kill a background shell\n */\n kill(shellId: string): boolean {\n const shell = this.shells.get(shellId)\n if (!shell) {\n console.log(`[BackgroundShellManager] Cannot kill: shell ${shellId} not found`)\n return false\n }\n\n if (shell.status === 'running') {\n try {\n const pid = shell.process.pid\n if (!pid) {\n console.log(`[BackgroundShellManager] Cannot kill: shell ${shellId} has no PID`)\n return false\n }\n\n console.log(`[BackgroundShellManager] Killing shell ${shellId} (PID: ${pid}, command: ${shell.command})`)\n\n // Kill the process and all its children\n if (process.platform === 'win32') {\n spawn('taskkill', ['/pid', pid.toString(), '/f', '/t'])\n } else {\n // Send SIGKILL to process group (more forceful than SIGTERM)\n // The negative PID sends signal to the entire process group\n try {\n process.kill(-pid, 'SIGKILL')\n console.log(`[BackgroundShellManager] Sent SIGKILL to process group -${pid}`)\n } catch (e) {\n // If process group kill fails, try killing just the main process\n console.log(`[BackgroundShellManager] Process group kill failed, trying main process only`)\n process.kill(pid, 'SIGKILL')\n }\n }\n\n shell.status = 'killed'\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, 'killed')\n return true\n } catch (error) {\n console.error(`[BackgroundShellManager] Failed to kill shell ${shellId}:`, error)\n logError(`Failed to kill shell ${shellId}: ${error}`)\n return false\n }\n }\n\n console.log(`[BackgroundShellManager] Shell ${shellId} is not running (status: ${shell.status})`)\n return false\n }\n\n /**\n * List all background shells\n */\n list(): BackgroundShell[] {\n return Array.from(this.shells.values())\n }\n\n /**\n * Get shell status\n */\n getStatus(shellId: string): BackgroundShellStatus | null {\n const shell = this.shells.get(shellId)\n return shell ? shell.status : null\n }\n\n /**\n * Remove a shell from the manager (only if done or killed)\n */\n remove(shellId: string): boolean {\n const shell = this.shells.get(shellId)\n if (!shell || shell.status === 'running') return false\n\n this.shells.delete(shellId)\n this.emit('shellRemoved', shellId)\n return true\n }\n\n /**\n * Clean up old completed/killed shells\n */\n cleanup(maxAge: number = 3600000): number {\n const now = Date.now()\n let removed = 0\n\n for (const [shellId, shell] of this.shells.entries()) {\n if (\n shell.status !== 'running' &&\n shell.endTime &&\n now - shell.endTime > maxAge\n ) {\n this.shells.delete(shellId)\n removed++\n }\n }\n\n if (removed > 0) {\n this.emit('cleaned', removed)\n }\n\n return removed\n }\n\n /**\n * Get summary of shell for display\n */\n getSummary(shellId: string): string | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n const duration = shell.endTime\n ? Math.floor((shell.endTime - shell.startTime) / 1000)\n : Math.floor((Date.now() - shell.startTime) / 1000)\n\n // Get last few lines of output for preview\n const lastLines = shell.stdout.slice(-2).join(' ').slice(0, 50)\n const preview = lastLines ? ` ${lastLines}\u2026` : ''\n\n return `${shell.command}${preview}`\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,aAAgC;AACzC,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AAkBzB,MAAM,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import { spawn, type ChildProcess } from 'child_process'\nimport { EventEmitter } from 'events'\nimport { randomUUID } from 'crypto'\nimport { logError } from './log'\n\nexport type BackgroundShellStatus = 'running' | 'done' | 'killed'\n\nexport interface BackgroundShell {\n id: string\n command: string\n status: BackgroundShellStatus\n startTime: number\n endTime?: number\n process: ChildProcess\n stdout: string[]\n stderr: string[]\n exitCode?: number\n lastReadStdoutIndex: number\n lastReadStderrIndex: number\n}\n\nconst MAX_OUTPUT_LINES = 1000 // Keep last N lines in memory\nconst REGEX_CACHE_SIZE = 50 // LRU cache size for compiled regexes\n\n// Simple LRU cache for compiled RegExp objects\nclass RegExpCache {\n private cache = new Map<string, RegExp>()\n private maxSize: number\n\n constructor(maxSize: number = REGEX_CACHE_SIZE) {\n this.maxSize = maxSize\n }\n\n get(pattern: string): RegExp | undefined {\n const regex = this.cache.get(pattern)\n if (regex) {\n // Move to end (most recently used)\n this.cache.delete(pattern)\n this.cache.set(pattern, regex)\n }\n return regex\n }\n\n set(pattern: string, regex: RegExp): void {\n // Evict oldest if at capacity\n if (this.cache.size >= this.maxSize) {\n const oldest = this.cache.keys().next().value\n if (oldest) this.cache.delete(oldest)\n }\n this.cache.set(pattern, regex)\n }\n\n getOrCreate(pattern: string): RegExp {\n let regex = this.get(pattern)\n if (!regex) {\n regex = new RegExp(pattern)\n this.set(pattern, regex)\n }\n return regex\n }\n}\n\nexport class BackgroundShellManager extends EventEmitter {\n private static instance: BackgroundShellManager | null = null\n private shells: Map<string, BackgroundShell> = new Map()\n private regexCache = new RegExpCache()\n\n private constructor() {\n super()\n }\n\n static getInstance(): BackgroundShellManager {\n if (!BackgroundShellManager.instance) {\n BackgroundShellManager.instance = new BackgroundShellManager()\n }\n return BackgroundShellManager.instance\n }\n\n /**\n * Create a new background shell and start executing the command\n */\n create(command: string, cwd?: string): string {\n const shellId = randomUUID().slice(0, 8)\n\n // Spawn the command in a shell\n const childProcess = spawn(command, [], {\n shell: true,\n cwd: cwd || process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: true, // Create a new process group for easier cleanup\n env: {\n ...process.env,\n GIT_EDITOR: 'true',\n },\n })\n\n const shell: BackgroundShell = {\n id: shellId,\n command,\n status: 'running',\n startTime: Date.now(),\n process: childProcess,\n stdout: [],\n stderr: [],\n lastReadStdoutIndex: 0,\n lastReadStderrIndex: 0,\n }\n\n // Capture stdout\n childProcess.stdout?.on('data', (data: Buffer) => {\n const lines = data.toString().split('\\n')\n shell.stdout.push(...lines)\n\n // Keep only last MAX_OUTPUT_LINES\n if (shell.stdout.length > MAX_OUTPUT_LINES) {\n const removed = shell.stdout.length - MAX_OUTPUT_LINES\n shell.stdout = shell.stdout.slice(removed)\n shell.lastReadStdoutIndex = Math.max(\n 0,\n shell.lastReadStdoutIndex - removed,\n )\n }\n\n this.emit('output', shellId, 'stdout', lines)\n })\n\n // Capture stderr\n childProcess.stderr?.on('data', (data: Buffer) => {\n const lines = data.toString().split('\\n')\n shell.stderr.push(...lines)\n\n // Keep only last MAX_OUTPUT_LINES\n if (shell.stderr.length > MAX_OUTPUT_LINES) {\n const removed = shell.stderr.length - MAX_OUTPUT_LINES\n shell.stderr = shell.stderr.slice(removed)\n shell.lastReadStderrIndex = Math.max(\n 0,\n shell.lastReadStderrIndex - removed,\n )\n }\n\n this.emit('output', shellId, 'stderr', lines)\n })\n\n // Handle process exit\n childProcess.on('exit', (code, signal) => {\n shell.status = signal ? 'killed' : 'done'\n shell.exitCode = code ?? undefined\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, shell.status)\n this.emitListChange()\n })\n\n childProcess.on('error', error => {\n logError(`Background shell ${shellId} error: ${error.message}`)\n shell.status = 'killed'\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, shell.status)\n this.emitListChange()\n })\n\n this.shells.set(shellId, shell)\n this.emit('shellCreated', shellId)\n this.emitListChange()\n\n return shellId\n }\n\n /**\n * Emit a list change event with the current shell list\n * Used for event-driven updates instead of polling\n */\n private emitListChange(): void {\n this.emit('listChange', this.list())\n }\n\n /**\n * Get a background shell by ID\n */\n get(shellId: string): BackgroundShell | undefined {\n return this.shells.get(shellId)\n }\n\n /**\n * Get new output since last read\n */\n getNewOutput(\n shellId: string,\n filter?: string,\n ): {\n stdout: string[]\n stderr: string[]\n } | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n let newStdout = shell.stdout.slice(shell.lastReadStdoutIndex)\n let newStderr = shell.stderr.slice(shell.lastReadStderrIndex)\n\n // Apply regex filter if provided (using cached regex)\n if (filter) {\n try {\n const regex = this.regexCache.getOrCreate(filter)\n newStdout = newStdout.filter(line => regex.test(line))\n newStderr = newStderr.filter(line => regex.test(line))\n } catch (e) {\n logError(`Invalid regex filter: ${filter}`)\n }\n }\n\n // Update read indices\n shell.lastReadStdoutIndex = shell.stdout.length\n shell.lastReadStderrIndex = shell.stderr.length\n\n return { stdout: newStdout, stderr: newStderr }\n }\n\n /**\n * Get all output (entire buffer)\n */\n getAllOutput(shellId: string): {\n stdout: string[]\n stderr: string[]\n } | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n return {\n stdout: [...shell.stdout],\n stderr: [...shell.stderr],\n }\n }\n\n /**\n * Kill a background shell\n */\n kill(shellId: string): boolean {\n const shell = this.shells.get(shellId)\n if (!shell) {\n console.log(\n `[BackgroundShellManager] Cannot kill: shell ${shellId} not found`,\n )\n return false\n }\n\n if (shell.status === 'running') {\n try {\n const pid = shell.process.pid\n if (!pid) {\n console.log(\n `[BackgroundShellManager] Cannot kill: shell ${shellId} has no PID`,\n )\n return false\n }\n\n console.log(\n `[BackgroundShellManager] Killing shell ${shellId} (PID: ${pid}, command: ${shell.command})`,\n )\n\n // Kill the process and all its children\n if (process.platform === 'win32') {\n spawn('taskkill', ['/pid', pid.toString(), '/f', '/t'])\n } else {\n // Send SIGKILL to process group (more forceful than SIGTERM)\n // The negative PID sends signal to the entire process group\n try {\n process.kill(-pid, 'SIGKILL')\n console.log(\n `[BackgroundShellManager] Sent SIGKILL to process group -${pid}`,\n )\n } catch (e) {\n // If process group kill fails, try killing just the main process\n console.log(\n `[BackgroundShellManager] Process group kill failed, trying main process only`,\n )\n process.kill(pid, 'SIGKILL')\n }\n }\n\n shell.status = 'killed'\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, 'killed')\n this.emitListChange()\n return true\n } catch (error) {\n console.error(\n `[BackgroundShellManager] Failed to kill shell ${shellId}:`,\n error,\n )\n logError(`Failed to kill shell ${shellId}: ${error}`)\n return false\n }\n }\n\n console.log(\n `[BackgroundShellManager] Shell ${shellId} is not running (status: ${shell.status})`,\n )\n return false\n }\n\n /**\n * List all background shells\n */\n list(): BackgroundShell[] {\n return Array.from(this.shells.values())\n }\n\n /**\n * Get shell status\n */\n getStatus(shellId: string): BackgroundShellStatus | null {\n const shell = this.shells.get(shellId)\n return shell ? shell.status : null\n }\n\n /**\n * Remove a shell from the manager (only if done or killed)\n */\n remove(shellId: string): boolean {\n const shell = this.shells.get(shellId)\n if (!shell || shell.status === 'running') return false\n\n this.shells.delete(shellId)\n this.emit('shellRemoved', shellId)\n this.emitListChange()\n return true\n }\n\n /**\n * Clean up old completed/killed shells\n */\n cleanup(maxAge: number = 3600000): number {\n const now = Date.now()\n let removed = 0\n\n for (const [shellId, shell] of this.shells.entries()) {\n if (\n shell.status !== 'running' &&\n shell.endTime &&\n now - shell.endTime > maxAge\n ) {\n this.shells.delete(shellId)\n removed++\n }\n }\n\n if (removed > 0) {\n this.emit('cleaned', removed)\n this.emitListChange()\n }\n\n return removed\n }\n\n /**\n * Get summary of shell for display\n */\n getSummary(shellId: string): string | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n const duration = shell.endTime\n ? Math.floor((shell.endTime - shell.startTime) / 1000)\n : Math.floor((Date.now() - shell.startTime) / 1000)\n\n // Get last few lines of output for preview\n const lastLines = shell.stdout.slice(-2).join(' ').slice(0, 50)\n const preview = lastLines ? ` ${lastLines}\u2026` : ''\n\n return `${shell.command}${preview}`\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -118,7 +118,10 @@ function detectShell() {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
try {
|
|
121
|
-
execSync('wsl.exe -e bash -lc "echo MINTO_OK"', {
|
|
121
|
+
execSync('wsl.exe -e bash -lc "echo MINTO_OK"', {
|
|
122
|
+
stdio: "ignore",
|
|
123
|
+
timeout: 1500
|
|
124
|
+
});
|
|
122
125
|
return { bin: "wsl.exe", args: ["-e", "bash", "-l"], type: "wsl" };
|
|
123
126
|
} catch {
|
|
124
127
|
}
|
|
@@ -299,7 +302,9 @@ class PersistentShell {
|
|
|
299
302
|
} else {
|
|
300
303
|
commandParts.push(`pwd > ${quoteForBash(this.cwdFileBashPath)}`);
|
|
301
304
|
}
|
|
302
|
-
commandParts.push(
|
|
305
|
+
commandParts.push(
|
|
306
|
+
`echo $EXEC_EXIT_CODE > ${quoteForBash(this.statusFileBashPath)}`
|
|
307
|
+
);
|
|
303
308
|
this.sendToShell(commandParts.join("\n"));
|
|
304
309
|
const start = Date.now();
|
|
305
310
|
const checkCompletion = setInterval(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/PersistentShell.ts"],
|
|
4
|
-
"sourcesContent": ["import * as fs from 'fs'\nimport { homedir } from 'os'\nimport { existsSync } from 'fs'\nimport shellquote from 'shell-quote'\nimport { spawn, execSync, execFileSync, type ChildProcess } from 'child_process'\nimport { isAbsolute, resolve, join } from 'path'\nimport { logError } from './log'\nimport * as os from 'os'\nimport { PRODUCT_COMMAND } from '@constants/product'\n\ntype ExecResult = {\n stdout: string\n stderr: string\n code: number\n interrupted: boolean\n}\ntype QueuedCommand = {\n command: string\n abortSignal?: AbortSignal\n timeout?: number\n resolve: (result: ExecResult) => void\n reject: (error: Error) => void\n}\n\nconst TEMPFILE_PREFIX = os.tmpdir() + `/${PRODUCT_COMMAND}-`\nconst DEFAULT_TIMEOUT = 30 * 60 * 1000\nconst SIGTERM_CODE = 143 // Standard exit code for SIGTERM\nconst FILE_SUFFIXES = {\n STATUS: '-status',\n STDOUT: '-stdout',\n STDERR: '-stderr',\n CWD: '-cwd',\n}\nconst SHELL_CONFIGS: Record<string, string> = {\n '/bin/bash': '.bashrc',\n '/bin/zsh': '.zshrc',\n}\n\ntype DetectedShell = {\n bin: string\n args: string[]\n type: 'posix' | 'msys' | 'wsl'\n}\n\nfunction quoteForBash(str: string): string {\n return `'${str.replace(/'/g, \"'\\\\''\")}'`\n}\n\nfunction toBashPath(pathStr: string, type: 'posix' | 'msys' | 'wsl'): string {\n // Already POSIX absolute path\n if (pathStr.startsWith('/')) return pathStr\n if (type === 'posix') return pathStr\n\n // Normalize backslashes\n const normalized = pathStr.replace(/\\\\/g, '/').replace(/\\\\\\\\/g, '/')\n const driveMatch = /^[A-Za-z]:/.exec(normalized)\n if (driveMatch) {\n const drive = normalized[0].toLowerCase()\n const rest = normalized.slice(2)\n if (type === 'msys') {\n return `/` + drive + (rest.startsWith('/') ? rest : `/${rest}`)\n }\n // wsl\n return `/mnt/` + drive + (rest.startsWith('/') ? rest : `/${rest}`)\n }\n // Relative path: just convert slashes\n return normalized\n}\n\nfunction fileExists(p: string | undefined): p is string {\n return !!p && existsSync(p)\n}\n\n// Robust PATH splitter for Windows and POSIX\nfunction splitPathEntries(pathEnv: string, platform: NodeJS.Platform): string[] {\n if (!pathEnv) return []\n\n // POSIX: ':' is the separator\n if (platform !== 'win32') {\n return pathEnv\n .split(':')\n .map(s => s.trim().replace(/^\"|\"$/g, ''))\n .filter(Boolean)\n }\n\n // Windows: primarily ';', but some environments may use ':'\n // We must not split drive letters like 'C:\\\\' or 'D:foo\\\\bar'\n const entries: string[] = []\n let current = ''\n const pushCurrent = () => {\n const cleaned = current.trim().replace(/^\"|\"$/g, '')\n if (cleaned) entries.push(cleaned)\n current = ''\n }\n\n for (let i = 0; i < pathEnv.length; i++) {\n const ch = pathEnv[i]\n\n if (ch === ';') {\n pushCurrent()\n continue\n }\n\n if (ch === ':') {\n const segmentLength = current.length\n const firstChar = current[0]\n const isDriveLetterPrefix = segmentLength === 1 && /[A-Za-z]/.test(firstChar || '')\n // Treat ':' as separator only if it's NOT the drive letter colon\n if (!isDriveLetterPrefix) {\n pushCurrent()\n continue\n }\n }\n\n current += ch\n }\n\n // Flush the final segment\n pushCurrent()\n\n return entries\n}\n\nfunction detectShell(): DetectedShell {\n const isWin = process.platform === 'win32'\n if (!isWin) {\n const bin = process.env.SHELL || '/bin/bash'\n return { bin, args: ['-l'], type: 'posix' }\n }\n\n // 1) Respect SHELL if it points to a bash.exe that exists\n if (process.env.SHELL && /bash\\.exe$/i.test(process.env.SHELL) && existsSync(process.env.SHELL)) {\n return { bin: process.env.SHELL, args: [], type: 'msys' }\n }\n\n // 1.1) Explicit override (MINTO_BASH or KODE_BASH for backward compatibility)\n const customBash = process.env.MINTO_BASH ?? process.env.KODE_BASH\n if (customBash && existsSync(customBash)) {\n return { bin: customBash, args: [], type: 'msys' }\n }\n\n // 2) Common Git Bash/MSYS2 locations\n const programFiles = [\n process.env['ProgramFiles'],\n process.env['ProgramFiles(x86)'],\n process.env['ProgramW6432'],\n ].filter(Boolean) as string[]\n\n const localAppData = process.env['LocalAppData']\n\n const candidates: string[] = []\n for (const base of programFiles) {\n candidates.push(\n join(base, 'Git', 'bin', 'bash.exe'),\n join(base, 'Git', 'usr', 'bin', 'bash.exe'),\n )\n }\n if (localAppData) {\n candidates.push(\n join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'),\n join(localAppData, 'Programs', 'Git', 'usr', 'bin', 'bash.exe'),\n )\n }\n // MSYS2 default\n candidates.push('C:/msys64/usr/bin/bash.exe')\n\n for (const c of candidates) {\n if (existsSync(c)) {\n return { bin: c, args: [], type: 'msys' }\n }\n }\n\n // 2.1) Search in PATH for bash.exe\n const pathEnv = process.env.PATH || process.env.Path || process.env.path || ''\n const pathEntries = splitPathEntries(pathEnv, process.platform)\n for (const p of pathEntries) {\n const candidate = join(p, 'bash.exe')\n if (existsSync(candidate)) {\n return { bin: candidate, args: [], type: 'msys' }\n }\n }\n\n // 3) WSL\n try {\n // Quick probe to ensure WSL+bash exists\n execSync('wsl.exe -e bash -lc \"echo MINTO_OK\"', { stdio: 'ignore', timeout: 1500 })\n return { bin: 'wsl.exe', args: ['-e', 'bash', '-l'], type: 'wsl' }\n } catch {}\n\n // 4) Last resort: meaningful error\n const hint = [\n '\u65E0\u6CD5\u627E\u5230\u53EF\u7528\u7684 bash\u3002\u8BF7\u5B89\u88C5 Git for Windows \u6216\u542F\u7528 WSL\u3002',\n '\u63A8\u8350\u5B89\u88C5 Git: https://git-scm.com/download/win',\n '\u6216\u542F\u7528 WSL \u5E76\u5B89\u88C5 Ubuntu: https://learn.microsoft.com/windows/wsl/install',\n ].join('\\n')\n throw new Error(hint)\n}\n\nexport class PersistentShell {\n private commandQueue: QueuedCommand[] = []\n private isExecuting: boolean = false\n private shell: ChildProcess\n private isAlive: boolean = true\n private commandInterrupted: boolean = false\n private statusFile: string\n private stdoutFile: string\n private stderrFile: string\n private cwdFile: string\n private cwd: string\n private binShell: string\n private shellArgs: string[]\n private shellType: 'posix' | 'msys' | 'wsl'\n private statusFileBashPath: string\n private stdoutFileBashPath: string\n private stderrFileBashPath: string\n private cwdFileBashPath: string\n\n constructor(cwd: string) {\n const { bin, args, type } = detectShell()\n this.binShell = bin\n this.shellArgs = args\n this.shellType = type\n\n this.shell = spawn(this.binShell, this.shellArgs, {\n stdio: ['pipe', 'pipe', 'pipe'],\n cwd,\n env: {\n ...process.env,\n GIT_EDITOR: 'true',\n },\n })\n\n this.cwd = cwd\n\n this.shell.on('exit', (code, signal) => {\n if (code) {\n // TODO: It would be nice to alert the user that shell crashed\n logError(`Shell exited with code ${code} and signal ${signal}`)\n }\n for (const file of [\n this.statusFile,\n this.stdoutFile,\n this.stderrFile,\n this.cwdFile,\n ]) {\n if (fs.existsSync(file)) {\n fs.unlinkSync(file)\n }\n }\n this.isAlive = false\n })\n\n const id = Math.floor(Math.random() * 0x10000)\n .toString(16)\n .padStart(4, '0')\n\n this.statusFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STATUS\n this.stdoutFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STDOUT\n this.stderrFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STDERR\n this.cwdFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.CWD\n for (const file of [this.statusFile, this.stdoutFile, this.stderrFile]) {\n fs.writeFileSync(file, '')\n }\n // Initialize CWD file with initial directory\n fs.writeFileSync(this.cwdFile, cwd)\n\n // Compute bash-visible paths for redirections\n this.statusFileBashPath = toBashPath(this.statusFile, this.shellType)\n this.stdoutFileBashPath = toBashPath(this.stdoutFile, this.shellType)\n this.stderrFileBashPath = toBashPath(this.stderrFile, this.shellType)\n this.cwdFileBashPath = toBashPath(this.cwdFile, this.shellType)\n\n // Source ~/.bashrc when available (avoid login shells on MSYS to prevent cwd resets)\n if (this.shellType === 'msys') {\n // Use non-login shell; explicitly source but keep working directory\n this.sendToShell('[ -f ~/.bashrc ] && source ~/.bashrc || true')\n // Ensure CWD file reflects current Windows path immediately on MSYS\n this.sendToShell(`pwd -W > ${quoteForBash(this.cwdFileBashPath)}`)\n } else {\n this.sendToShell('[ -f ~/.bashrc ] && source ~/.bashrc || true')\n }\n }\n\n private static instance: PersistentShell | null = null\n\n static restart() {\n if (PersistentShell.instance) {\n PersistentShell.instance.close()\n PersistentShell.instance = null\n }\n }\n\n static getInstance(): PersistentShell {\n if (!PersistentShell.instance || !PersistentShell.instance.isAlive) {\n PersistentShell.instance = new PersistentShell(process.cwd())\n }\n return PersistentShell.instance\n }\n\n killChildren() {\n const parentPid = this.shell.pid\n try {\n const childPids = execSync(`pgrep -P ${parentPid}`)\n .toString()\n .trim()\n .split('\\n')\n .filter(Boolean) // Filter out empty strings\n\n \n\n childPids.forEach(pid => {\n try {\n process.kill(Number(pid), 'SIGTERM')\n } catch (error) {\n logError(`Failed to kill process ${pid}: ${error}`)\n }\n })\n } catch {\n // pgrep returns non-zero when no processes are found - this is expected\n } finally {\n this.commandInterrupted = true\n }\n }\n\n private async processQueue() {\n /**\n * Processes commands from the queue one at a time.\n * Concurrency invariants:\n * - Only one instance runs at a time (controlled by isExecuting)\n * - Is the only caller of updateCwd() in the system\n * - Calls updateCwd() after each command completes\n * - Ensures commands execute serially via the queue\n * - Handles interruption via abortSignal by calling killChildren()\n * - Cleans up abortSignal listeners after command completion or interruption\n */\n if (this.isExecuting || this.commandQueue.length === 0) return\n\n this.isExecuting = true\n const { command, abortSignal, timeout, resolve, reject } =\n this.commandQueue.shift()!\n\n const killChildren = () => this.killChildren()\n if (abortSignal) {\n abortSignal.addEventListener('abort', killChildren)\n }\n\n try {\n const result = await this.exec_(command, timeout)\n\n // No need to update cwd - it's handled in exec_ via the CWD file\n\n resolve(result)\n } catch (error) {\n \n reject(error as Error)\n } finally {\n this.isExecuting = false\n if (abortSignal) {\n abortSignal.removeEventListener('abort', killChildren)\n }\n // Process next command in queue\n this.processQueue()\n }\n }\n\n async exec(\n command: string,\n abortSignal?: AbortSignal,\n timeout?: number,\n ): Promise<ExecResult> {\n return new Promise((resolve, reject) => {\n this.commandQueue.push({ command, abortSignal, timeout, resolve, reject })\n this.processQueue()\n })\n }\n\n private async exec_(command: string, timeout?: number): Promise<ExecResult> {\n /**\n * Direct command execution without going through the queue.\n * Concurrency invariants:\n * - Not safe for concurrent calls (uses shared files)\n * - Called only when queue is idle\n * - Relies on file-based IPC to handle shell interaction\n * - Does not modify the command queue state\n * - Tracks interruption state via commandInterrupted flag\n * - Resets interruption state at start of new command\n * - Reports interruption status in result object\n *\n * Exit Code & CWD Handling:\n * - Executes command and immediately captures its exit code into a shell variable\n * - Updates the CWD file with the working directory after capturing exit code\n * - Writes the preserved exit code to the status file as the final step\n * - This sequence eliminates race conditions between exit code capture and CWD updates\n * - The pwd() method reads the CWD file directly for current directory info\n */\n const quotedCommand = quoteForBash(command)\n\n // Check the syntax of the command\n try {\n if (this.shellType === 'wsl') {\n // On Windows WSL, avoid shell string quoting issues by using argv form\n execFileSync('wsl.exe', ['-e', 'bash', '-n', '-c', command], {\n stdio: 'ignore',\n timeout: 1000,\n })\n } else if (this.shellType === 'msys') {\n // On Windows Git Bash/MSYS, use execFileSync to bypass cmd.exe parsing\n execFileSync(this.binShell, ['-n', '-c', command], {\n stdio: 'ignore',\n timeout: 1000,\n })\n } else {\n // POSIX platforms: keep existing behavior\n execSync(`${this.binShell} -n -c ${quotedCommand}`, {\n stdio: 'ignore',\n timeout: 1000,\n })\n }\n } catch (error) {\n // If there's a syntax error, return an error with the actual exit code\n const execError = error as any\n const actualExitCode = execError?.status ?? execError?.code ?? 2 // Default to 2 (syntax error) if no code available\n const errorStr = execError?.stderr?.toString() || execError?.message || String(error || '')\n \n return Promise.resolve({\n stdout: '',\n stderr: errorStr,\n code: actualExitCode,\n interrupted: false,\n })\n }\n\n const commandTimeout = timeout || DEFAULT_TIMEOUT\n // Reset interrupted state for new command\n this.commandInterrupted = false\n return new Promise<ExecResult>(resolve => {\n // Truncate output files\n fs.writeFileSync(this.stdoutFile, '')\n fs.writeFileSync(this.stderrFile, '')\n fs.writeFileSync(this.statusFile, '')\n // Break up the command sequence for clarity using an array of commands\n const commandParts = []\n\n // 1. Execute the main command with redirections\n commandParts.push(\n `eval ${quotedCommand} < /dev/null > ${quoteForBash(this.stdoutFileBashPath)} 2> ${quoteForBash(this.stderrFileBashPath)}`,\n )\n\n // 2. Capture exit code immediately after command execution to avoid losing it\n commandParts.push(`EXEC_EXIT_CODE=$?`)\n\n // 3. Update CWD file (use Windows path on MSYS to keep Node path checks correct)\n if (this.shellType === 'msys') {\n commandParts.push(`pwd -W > ${quoteForBash(this.cwdFileBashPath)}`)\n } else {\n commandParts.push(`pwd > ${quoteForBash(this.cwdFileBashPath)}`)\n }\n\n // 4. Write the preserved exit code to status file to avoid race with pwd\n commandParts.push(`echo $EXEC_EXIT_CODE > ${quoteForBash(this.statusFileBashPath)}`)\n\n // Send the combined commands as a single operation to maintain atomicity\n this.sendToShell(commandParts.join('\\n'))\n\n // Check for command completion or timeout\n const start = Date.now()\n const checkCompletion = setInterval(() => {\n try {\n let statusFileSize = 0\n if (fs.existsSync(this.statusFile)) {\n statusFileSize = fs.statSync(this.statusFile).size\n }\n\n if (\n statusFileSize > 0 ||\n Date.now() - start > commandTimeout ||\n this.commandInterrupted\n ) {\n clearInterval(checkCompletion)\n const stdout = fs.existsSync(this.stdoutFile)\n ? fs.readFileSync(this.stdoutFile, 'utf8')\n : ''\n let stderr = fs.existsSync(this.stderrFile)\n ? fs.readFileSync(this.stderrFile, 'utf8')\n : ''\n let code: number\n if (statusFileSize) {\n code = Number(fs.readFileSync(this.statusFile, 'utf8'))\n } else {\n // Timeout occurred - kill any running processes\n this.killChildren()\n code = SIGTERM_CODE\n stderr += (stderr ? '\\n' : '') + 'Command execution timed out'\n \n }\n resolve({\n stdout,\n stderr,\n code,\n interrupted: this.commandInterrupted,\n })\n }\n } catch {\n // Ignore file system errors during polling - they are expected\n // as we check for completion before files exist\n }\n }, 10) // increasing this will introduce latency\n })\n }\n\n private sendToShell(command: string) {\n try {\n this.shell!.stdin!.write(command + '\\n')\n } catch (error) {\n const errorString =\n error instanceof Error\n ? error.message\n : String(error || 'Unknown error')\n logError(`Error in sendToShell: ${errorString}`)\n \n throw error\n }\n }\n\n pwd(): string {\n try {\n const newCwd = fs.readFileSync(this.cwdFile, 'utf8').trim()\n if (newCwd) {\n this.cwd = newCwd\n }\n } catch (error) {\n logError(`Shell pwd error ${error}`)\n }\n // Always return the cached value\n return this.cwd\n }\n\n async setCwd(cwd: string) {\n const resolved = isAbsolute(cwd) ? cwd : resolve(process.cwd(), cwd)\n if (!existsSync(resolved)) {\n throw new Error(`Path \"${resolved}\" does not exist`)\n }\n const bashPath = toBashPath(resolved, this.shellType)\n await this.exec(`cd ${quoteForBash(bashPath)}`)\n }\n\n close(): void {\n this.shell!.stdin!.end()\n this.shell.kill()\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,YAAY,QAAQ;AAEpB,SAAS,kBAAkB;AAE3B,SAAS,OAAO,UAAU,oBAAuC;AACjE,SAAS,YAAY,SAAS,YAAY;AAC1C,SAAS,gBAAgB;AACzB,YAAY,QAAQ;AACpB,SAAS,uBAAuB;AAgBhC,MAAM,kBAAkB,GAAG,OAAO,IAAI,IAAI,eAAe;AACzD,MAAM,kBAAkB,KAAK,KAAK;AAClC,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAAA,EACpB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,KAAK;AACP;AACA,MAAM,gBAAwC;AAAA,EAC5C,aAAa;AAAA,EACb,YAAY;AACd;AAQA,SAAS,aAAa,KAAqB;AACzC,SAAO,IAAI,IAAI,QAAQ,MAAM,OAAO,CAAC;AACvC;AAEA,SAAS,WAAW,SAAiB,MAAwC;AAE3E,MAAI,QAAQ,WAAW,GAAG,EAAG,QAAO;AACpC,MAAI,SAAS,QAAS,QAAO;AAG7B,QAAM,aAAa,QAAQ,QAAQ,OAAO,GAAG,EAAE,QAAQ,SAAS,GAAG;AACnE,QAAM,aAAa,aAAa,KAAK,UAAU;AAC/C,MAAI,YAAY;AACd,UAAM,QAAQ,WAAW,CAAC,EAAE,YAAY;AACxC,UAAM,OAAO,WAAW,MAAM,CAAC;AAC/B,QAAI,SAAS,QAAQ;AACnB,aAAO,MAAM,SAAS,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI;AAAA,IAC9D;AAEA,WAAO,UAAU,SAAS,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI;AAAA,EAClE;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,GAAoC;AACtD,SAAO,CAAC,CAAC,KAAK,WAAW,CAAC;AAC5B;AAGA,SAAS,
|
|
4
|
+
"sourcesContent": ["import * as fs from 'fs'\nimport { homedir } from 'os'\nimport { existsSync } from 'fs'\nimport shellquote from 'shell-quote'\nimport { spawn, execSync, execFileSync, type ChildProcess } from 'child_process'\nimport { isAbsolute, resolve, join } from 'path'\nimport { logError } from './log'\nimport * as os from 'os'\nimport { PRODUCT_COMMAND } from '@constants/product'\n\ntype ExecResult = {\n stdout: string\n stderr: string\n code: number\n interrupted: boolean\n}\ntype QueuedCommand = {\n command: string\n abortSignal?: AbortSignal\n timeout?: number\n resolve: (result: ExecResult) => void\n reject: (error: Error) => void\n}\n\nconst TEMPFILE_PREFIX = os.tmpdir() + `/${PRODUCT_COMMAND}-`\nconst DEFAULT_TIMEOUT = 30 * 60 * 1000\nconst SIGTERM_CODE = 143 // Standard exit code for SIGTERM\nconst FILE_SUFFIXES = {\n STATUS: '-status',\n STDOUT: '-stdout',\n STDERR: '-stderr',\n CWD: '-cwd',\n}\nconst SHELL_CONFIGS: Record<string, string> = {\n '/bin/bash': '.bashrc',\n '/bin/zsh': '.zshrc',\n}\n\ntype DetectedShell = {\n bin: string\n args: string[]\n type: 'posix' | 'msys' | 'wsl'\n}\n\nfunction quoteForBash(str: string): string {\n return `'${str.replace(/'/g, \"'\\\\''\")}'`\n}\n\nfunction toBashPath(pathStr: string, type: 'posix' | 'msys' | 'wsl'): string {\n // Already POSIX absolute path\n if (pathStr.startsWith('/')) return pathStr\n if (type === 'posix') return pathStr\n\n // Normalize backslashes\n const normalized = pathStr.replace(/\\\\/g, '/').replace(/\\\\\\\\/g, '/')\n const driveMatch = /^[A-Za-z]:/.exec(normalized)\n if (driveMatch) {\n const drive = normalized[0].toLowerCase()\n const rest = normalized.slice(2)\n if (type === 'msys') {\n return `/` + drive + (rest.startsWith('/') ? rest : `/${rest}`)\n }\n // wsl\n return `/mnt/` + drive + (rest.startsWith('/') ? rest : `/${rest}`)\n }\n // Relative path: just convert slashes\n return normalized\n}\n\nfunction fileExists(p: string | undefined): p is string {\n return !!p && existsSync(p)\n}\n\n// Robust PATH splitter for Windows and POSIX\nfunction splitPathEntries(\n pathEnv: string,\n platform: NodeJS.Platform,\n): string[] {\n if (!pathEnv) return []\n\n // POSIX: ':' is the separator\n if (platform !== 'win32') {\n return pathEnv\n .split(':')\n .map(s => s.trim().replace(/^\"|\"$/g, ''))\n .filter(Boolean)\n }\n\n // Windows: primarily ';', but some environments may use ':'\n // We must not split drive letters like 'C:\\\\' or 'D:foo\\\\bar'\n const entries: string[] = []\n let current = ''\n const pushCurrent = () => {\n const cleaned = current.trim().replace(/^\"|\"$/g, '')\n if (cleaned) entries.push(cleaned)\n current = ''\n }\n\n for (let i = 0; i < pathEnv.length; i++) {\n const ch = pathEnv[i]\n\n if (ch === ';') {\n pushCurrent()\n continue\n }\n\n if (ch === ':') {\n const segmentLength = current.length\n const firstChar = current[0]\n const isDriveLetterPrefix =\n segmentLength === 1 && /[A-Za-z]/.test(firstChar || '')\n // Treat ':' as separator only if it's NOT the drive letter colon\n if (!isDriveLetterPrefix) {\n pushCurrent()\n continue\n }\n }\n\n current += ch\n }\n\n // Flush the final segment\n pushCurrent()\n\n return entries\n}\n\nfunction detectShell(): DetectedShell {\n const isWin = process.platform === 'win32'\n if (!isWin) {\n const bin = process.env.SHELL || '/bin/bash'\n return { bin, args: ['-l'], type: 'posix' }\n }\n\n // 1) Respect SHELL if it points to a bash.exe that exists\n if (\n process.env.SHELL &&\n /bash\\.exe$/i.test(process.env.SHELL) &&\n existsSync(process.env.SHELL)\n ) {\n return { bin: process.env.SHELL, args: [], type: 'msys' }\n }\n\n // 1.1) Explicit override (MINTO_BASH or KODE_BASH for backward compatibility)\n const customBash = process.env.MINTO_BASH ?? process.env.KODE_BASH\n if (customBash && existsSync(customBash)) {\n return { bin: customBash, args: [], type: 'msys' }\n }\n\n // 2) Common Git Bash/MSYS2 locations\n const programFiles = [\n process.env['ProgramFiles'],\n process.env['ProgramFiles(x86)'],\n process.env['ProgramW6432'],\n ].filter(Boolean) as string[]\n\n const localAppData = process.env['LocalAppData']\n\n const candidates: string[] = []\n for (const base of programFiles) {\n candidates.push(\n join(base, 'Git', 'bin', 'bash.exe'),\n join(base, 'Git', 'usr', 'bin', 'bash.exe'),\n )\n }\n if (localAppData) {\n candidates.push(\n join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'),\n join(localAppData, 'Programs', 'Git', 'usr', 'bin', 'bash.exe'),\n )\n }\n // MSYS2 default\n candidates.push('C:/msys64/usr/bin/bash.exe')\n\n for (const c of candidates) {\n if (existsSync(c)) {\n return { bin: c, args: [], type: 'msys' }\n }\n }\n\n // 2.1) Search in PATH for bash.exe\n const pathEnv = process.env.PATH || process.env.Path || process.env.path || ''\n const pathEntries = splitPathEntries(pathEnv, process.platform)\n for (const p of pathEntries) {\n const candidate = join(p, 'bash.exe')\n if (existsSync(candidate)) {\n return { bin: candidate, args: [], type: 'msys' }\n }\n }\n\n // 3) WSL\n try {\n // Quick probe to ensure WSL+bash exists\n execSync('wsl.exe -e bash -lc \"echo MINTO_OK\"', {\n stdio: 'ignore',\n timeout: 1500,\n })\n return { bin: 'wsl.exe', args: ['-e', 'bash', '-l'], type: 'wsl' }\n } catch {}\n\n // 4) Last resort: meaningful error\n const hint = [\n '\u65E0\u6CD5\u627E\u5230\u53EF\u7528\u7684 bash\u3002\u8BF7\u5B89\u88C5 Git for Windows \u6216\u542F\u7528 WSL\u3002',\n '\u63A8\u8350\u5B89\u88C5 Git: https://git-scm.com/download/win',\n '\u6216\u542F\u7528 WSL \u5E76\u5B89\u88C5 Ubuntu: https://learn.microsoft.com/windows/wsl/install',\n ].join('\\n')\n throw new Error(hint)\n}\n\nexport class PersistentShell {\n private commandQueue: QueuedCommand[] = []\n private isExecuting: boolean = false\n private shell: ChildProcess\n private isAlive: boolean = true\n private commandInterrupted: boolean = false\n private statusFile: string\n private stdoutFile: string\n private stderrFile: string\n private cwdFile: string\n private cwd: string\n private binShell: string\n private shellArgs: string[]\n private shellType: 'posix' | 'msys' | 'wsl'\n private statusFileBashPath: string\n private stdoutFileBashPath: string\n private stderrFileBashPath: string\n private cwdFileBashPath: string\n\n constructor(cwd: string) {\n const { bin, args, type } = detectShell()\n this.binShell = bin\n this.shellArgs = args\n this.shellType = type\n\n this.shell = spawn(this.binShell, this.shellArgs, {\n stdio: ['pipe', 'pipe', 'pipe'],\n cwd,\n env: {\n ...process.env,\n GIT_EDITOR: 'true',\n },\n })\n\n this.cwd = cwd\n\n this.shell.on('exit', (code, signal) => {\n if (code) {\n // TODO: It would be nice to alert the user that shell crashed\n logError(`Shell exited with code ${code} and signal ${signal}`)\n }\n for (const file of [\n this.statusFile,\n this.stdoutFile,\n this.stderrFile,\n this.cwdFile,\n ]) {\n if (fs.existsSync(file)) {\n fs.unlinkSync(file)\n }\n }\n this.isAlive = false\n })\n\n const id = Math.floor(Math.random() * 0x10000)\n .toString(16)\n .padStart(4, '0')\n\n this.statusFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STATUS\n this.stdoutFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STDOUT\n this.stderrFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STDERR\n this.cwdFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.CWD\n for (const file of [this.statusFile, this.stdoutFile, this.stderrFile]) {\n fs.writeFileSync(file, '')\n }\n // Initialize CWD file with initial directory\n fs.writeFileSync(this.cwdFile, cwd)\n\n // Compute bash-visible paths for redirections\n this.statusFileBashPath = toBashPath(this.statusFile, this.shellType)\n this.stdoutFileBashPath = toBashPath(this.stdoutFile, this.shellType)\n this.stderrFileBashPath = toBashPath(this.stderrFile, this.shellType)\n this.cwdFileBashPath = toBashPath(this.cwdFile, this.shellType)\n\n // Source ~/.bashrc when available (avoid login shells on MSYS to prevent cwd resets)\n if (this.shellType === 'msys') {\n // Use non-login shell; explicitly source but keep working directory\n this.sendToShell('[ -f ~/.bashrc ] && source ~/.bashrc || true')\n // Ensure CWD file reflects current Windows path immediately on MSYS\n this.sendToShell(`pwd -W > ${quoteForBash(this.cwdFileBashPath)}`)\n } else {\n this.sendToShell('[ -f ~/.bashrc ] && source ~/.bashrc || true')\n }\n }\n\n private static instance: PersistentShell | null = null\n\n static restart() {\n if (PersistentShell.instance) {\n PersistentShell.instance.close()\n PersistentShell.instance = null\n }\n }\n\n static getInstance(): PersistentShell {\n if (!PersistentShell.instance || !PersistentShell.instance.isAlive) {\n PersistentShell.instance = new PersistentShell(process.cwd())\n }\n return PersistentShell.instance\n }\n\n killChildren() {\n const parentPid = this.shell.pid\n try {\n const childPids = execSync(`pgrep -P ${parentPid}`)\n .toString()\n .trim()\n .split('\\n')\n .filter(Boolean) // Filter out empty strings\n\n childPids.forEach(pid => {\n try {\n process.kill(Number(pid), 'SIGTERM')\n } catch (error) {\n logError(`Failed to kill process ${pid}: ${error}`)\n }\n })\n } catch {\n // pgrep returns non-zero when no processes are found - this is expected\n } finally {\n this.commandInterrupted = true\n }\n }\n\n private async processQueue() {\n /**\n * Processes commands from the queue one at a time.\n * Concurrency invariants:\n * - Only one instance runs at a time (controlled by isExecuting)\n * - Is the only caller of updateCwd() in the system\n * - Calls updateCwd() after each command completes\n * - Ensures commands execute serially via the queue\n * - Handles interruption via abortSignal by calling killChildren()\n * - Cleans up abortSignal listeners after command completion or interruption\n */\n if (this.isExecuting || this.commandQueue.length === 0) return\n\n this.isExecuting = true\n const { command, abortSignal, timeout, resolve, reject } =\n this.commandQueue.shift()!\n\n const killChildren = () => this.killChildren()\n if (abortSignal) {\n abortSignal.addEventListener('abort', killChildren)\n }\n\n try {\n const result = await this.exec_(command, timeout)\n\n // No need to update cwd - it's handled in exec_ via the CWD file\n\n resolve(result)\n } catch (error) {\n reject(error as Error)\n } finally {\n this.isExecuting = false\n if (abortSignal) {\n abortSignal.removeEventListener('abort', killChildren)\n }\n // Process next command in queue\n this.processQueue()\n }\n }\n\n async exec(\n command: string,\n abortSignal?: AbortSignal,\n timeout?: number,\n ): Promise<ExecResult> {\n return new Promise((resolve, reject) => {\n this.commandQueue.push({ command, abortSignal, timeout, resolve, reject })\n this.processQueue()\n })\n }\n\n private async exec_(command: string, timeout?: number): Promise<ExecResult> {\n /**\n * Direct command execution without going through the queue.\n * Concurrency invariants:\n * - Not safe for concurrent calls (uses shared files)\n * - Called only when queue is idle\n * - Relies on file-based IPC to handle shell interaction\n * - Does not modify the command queue state\n * - Tracks interruption state via commandInterrupted flag\n * - Resets interruption state at start of new command\n * - Reports interruption status in result object\n *\n * Exit Code & CWD Handling:\n * - Executes command and immediately captures its exit code into a shell variable\n * - Updates the CWD file with the working directory after capturing exit code\n * - Writes the preserved exit code to the status file as the final step\n * - This sequence eliminates race conditions between exit code capture and CWD updates\n * - The pwd() method reads the CWD file directly for current directory info\n */\n const quotedCommand = quoteForBash(command)\n\n // Check the syntax of the command\n try {\n if (this.shellType === 'wsl') {\n // On Windows WSL, avoid shell string quoting issues by using argv form\n execFileSync('wsl.exe', ['-e', 'bash', '-n', '-c', command], {\n stdio: 'ignore',\n timeout: 1000,\n })\n } else if (this.shellType === 'msys') {\n // On Windows Git Bash/MSYS, use execFileSync to bypass cmd.exe parsing\n execFileSync(this.binShell, ['-n', '-c', command], {\n stdio: 'ignore',\n timeout: 1000,\n })\n } else {\n // POSIX platforms: keep existing behavior\n execSync(`${this.binShell} -n -c ${quotedCommand}`, {\n stdio: 'ignore',\n timeout: 1000,\n })\n }\n } catch (error) {\n // If there's a syntax error, return an error with the actual exit code\n const execError = error as any\n const actualExitCode = execError?.status ?? execError?.code ?? 2 // Default to 2 (syntax error) if no code available\n const errorStr =\n execError?.stderr?.toString() ||\n execError?.message ||\n String(error || '')\n\n return Promise.resolve({\n stdout: '',\n stderr: errorStr,\n code: actualExitCode,\n interrupted: false,\n })\n }\n\n const commandTimeout = timeout || DEFAULT_TIMEOUT\n // Reset interrupted state for new command\n this.commandInterrupted = false\n return new Promise<ExecResult>(resolve => {\n // Truncate output files\n fs.writeFileSync(this.stdoutFile, '')\n fs.writeFileSync(this.stderrFile, '')\n fs.writeFileSync(this.statusFile, '')\n // Break up the command sequence for clarity using an array of commands\n const commandParts = []\n\n // 1. Execute the main command with redirections\n commandParts.push(\n `eval ${quotedCommand} < /dev/null > ${quoteForBash(this.stdoutFileBashPath)} 2> ${quoteForBash(this.stderrFileBashPath)}`,\n )\n\n // 2. Capture exit code immediately after command execution to avoid losing it\n commandParts.push(`EXEC_EXIT_CODE=$?`)\n\n // 3. Update CWD file (use Windows path on MSYS to keep Node path checks correct)\n if (this.shellType === 'msys') {\n commandParts.push(`pwd -W > ${quoteForBash(this.cwdFileBashPath)}`)\n } else {\n commandParts.push(`pwd > ${quoteForBash(this.cwdFileBashPath)}`)\n }\n\n // 4. Write the preserved exit code to status file to avoid race with pwd\n commandParts.push(\n `echo $EXEC_EXIT_CODE > ${quoteForBash(this.statusFileBashPath)}`,\n )\n\n // Send the combined commands as a single operation to maintain atomicity\n this.sendToShell(commandParts.join('\\n'))\n\n // Check for command completion or timeout\n const start = Date.now()\n const checkCompletion = setInterval(() => {\n try {\n let statusFileSize = 0\n if (fs.existsSync(this.statusFile)) {\n statusFileSize = fs.statSync(this.statusFile).size\n }\n\n if (\n statusFileSize > 0 ||\n Date.now() - start > commandTimeout ||\n this.commandInterrupted\n ) {\n clearInterval(checkCompletion)\n const stdout = fs.existsSync(this.stdoutFile)\n ? fs.readFileSync(this.stdoutFile, 'utf8')\n : ''\n let stderr = fs.existsSync(this.stderrFile)\n ? fs.readFileSync(this.stderrFile, 'utf8')\n : ''\n let code: number\n if (statusFileSize) {\n code = Number(fs.readFileSync(this.statusFile, 'utf8'))\n } else {\n // Timeout occurred - kill any running processes\n this.killChildren()\n code = SIGTERM_CODE\n stderr += (stderr ? '\\n' : '') + 'Command execution timed out'\n }\n resolve({\n stdout,\n stderr,\n code,\n interrupted: this.commandInterrupted,\n })\n }\n } catch {\n // Ignore file system errors during polling - they are expected\n // as we check for completion before files exist\n }\n }, 10) // increasing this will introduce latency\n })\n }\n\n private sendToShell(command: string) {\n try {\n this.shell!.stdin!.write(command + '\\n')\n } catch (error) {\n const errorString =\n error instanceof Error\n ? error.message\n : String(error || 'Unknown error')\n logError(`Error in sendToShell: ${errorString}`)\n\n throw error\n }\n }\n\n pwd(): string {\n try {\n const newCwd = fs.readFileSync(this.cwdFile, 'utf8').trim()\n if (newCwd) {\n this.cwd = newCwd\n }\n } catch (error) {\n logError(`Shell pwd error ${error}`)\n }\n // Always return the cached value\n return this.cwd\n }\n\n async setCwd(cwd: string) {\n const resolved = isAbsolute(cwd) ? cwd : resolve(process.cwd(), cwd)\n if (!existsSync(resolved)) {\n throw new Error(`Path \"${resolved}\" does not exist`)\n }\n const bashPath = toBashPath(resolved, this.shellType)\n await this.exec(`cd ${quoteForBash(bashPath)}`)\n }\n\n close(): void {\n this.shell!.stdin!.end()\n this.shell.kill()\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,QAAQ;AAEpB,SAAS,kBAAkB;AAE3B,SAAS,OAAO,UAAU,oBAAuC;AACjE,SAAS,YAAY,SAAS,YAAY;AAC1C,SAAS,gBAAgB;AACzB,YAAY,QAAQ;AACpB,SAAS,uBAAuB;AAgBhC,MAAM,kBAAkB,GAAG,OAAO,IAAI,IAAI,eAAe;AACzD,MAAM,kBAAkB,KAAK,KAAK;AAClC,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAAA,EACpB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,KAAK;AACP;AACA,MAAM,gBAAwC;AAAA,EAC5C,aAAa;AAAA,EACb,YAAY;AACd;AAQA,SAAS,aAAa,KAAqB;AACzC,SAAO,IAAI,IAAI,QAAQ,MAAM,OAAO,CAAC;AACvC;AAEA,SAAS,WAAW,SAAiB,MAAwC;AAE3E,MAAI,QAAQ,WAAW,GAAG,EAAG,QAAO;AACpC,MAAI,SAAS,QAAS,QAAO;AAG7B,QAAM,aAAa,QAAQ,QAAQ,OAAO,GAAG,EAAE,QAAQ,SAAS,GAAG;AACnE,QAAM,aAAa,aAAa,KAAK,UAAU;AAC/C,MAAI,YAAY;AACd,UAAM,QAAQ,WAAW,CAAC,EAAE,YAAY;AACxC,UAAM,OAAO,WAAW,MAAM,CAAC;AAC/B,QAAI,SAAS,QAAQ;AACnB,aAAO,MAAM,SAAS,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI;AAAA,IAC9D;AAEA,WAAO,UAAU,SAAS,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI;AAAA,EAClE;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,GAAoC;AACtD,SAAO,CAAC,CAAC,KAAK,WAAW,CAAC;AAC5B;AAGA,SAAS,iBACP,SACA,UACU;AACV,MAAI,CAAC,QAAS,QAAO,CAAC;AAGtB,MAAI,aAAa,SAAS;AACxB,WAAO,QACJ,MAAM,GAAG,EACT,IAAI,OAAK,EAAE,KAAK,EAAE,QAAQ,UAAU,EAAE,CAAC,EACvC,OAAO,OAAO;AAAA,EACnB;AAIA,QAAM,UAAoB,CAAC;AAC3B,MAAI,UAAU;AACd,QAAM,cAAc,MAAM;AACxB,UAAM,UAAU,QAAQ,KAAK,EAAE,QAAQ,UAAU,EAAE;AACnD,QAAI,QAAS,SAAQ,KAAK,OAAO;AACjC,cAAU;AAAA,EACZ;AAEA,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,KAAK,QAAQ,CAAC;AAEpB,QAAI,OAAO,KAAK;AACd,kBAAY;AACZ;AAAA,IACF;AAEA,QAAI,OAAO,KAAK;AACd,YAAM,gBAAgB,QAAQ;AAC9B,YAAM,YAAY,QAAQ,CAAC;AAC3B,YAAM,sBACJ,kBAAkB,KAAK,WAAW,KAAK,aAAa,EAAE;AAExD,UAAI,CAAC,qBAAqB;AACxB,oBAAY;AACZ;AAAA,MACF;AAAA,IACF;AAEA,eAAW;AAAA,EACb;AAGA,cAAY;AAEZ,SAAO;AACT;AAEA,SAAS,cAA6B;AACpC,QAAM,QAAQ,QAAQ,aAAa;AACnC,MAAI,CAAC,OAAO;AACV,UAAM,MAAM,QAAQ,IAAI,SAAS;AACjC,WAAO,EAAE,KAAK,MAAM,CAAC,IAAI,GAAG,MAAM,QAAQ;AAAA,EAC5C;AAGA,MACE,QAAQ,IAAI,SACZ,cAAc,KAAK,QAAQ,IAAI,KAAK,KACpC,WAAW,QAAQ,IAAI,KAAK,GAC5B;AACA,WAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,GAAG,MAAM,OAAO;AAAA,EAC1D;AAGA,QAAM,aAAa,QAAQ,IAAI,cAAc,QAAQ,IAAI;AACzD,MAAI,cAAc,WAAW,UAAU,GAAG;AACxC,WAAO,EAAE,KAAK,YAAY,MAAM,CAAC,GAAG,MAAM,OAAO;AAAA,EACnD;AAGA,QAAM,eAAe;AAAA,IACnB,QAAQ,IAAI,cAAc;AAAA,IAC1B,QAAQ,IAAI,mBAAmB;AAAA,IAC/B,QAAQ,IAAI,cAAc;AAAA,EAC5B,EAAE,OAAO,OAAO;AAEhB,QAAM,eAAe,QAAQ,IAAI,cAAc;AAE/C,QAAM,aAAuB,CAAC;AAC9B,aAAW,QAAQ,cAAc;AAC/B,eAAW;AAAA,MACT,KAAK,MAAM,OAAO,OAAO,UAAU;AAAA,MACnC,KAAK,MAAM,OAAO,OAAO,OAAO,UAAU;AAAA,IAC5C;AAAA,EACF;AACA,MAAI,cAAc;AAChB,eAAW;AAAA,MACT,KAAK,cAAc,YAAY,OAAO,OAAO,UAAU;AAAA,MACvD,KAAK,cAAc,YAAY,OAAO,OAAO,OAAO,UAAU;AAAA,IAChE;AAAA,EACF;AAEA,aAAW,KAAK,4BAA4B;AAE5C,aAAW,KAAK,YAAY;AAC1B,QAAI,WAAW,CAAC,GAAG;AACjB,aAAO,EAAE,KAAK,GAAG,MAAM,CAAC,GAAG,MAAM,OAAO;AAAA,IAC1C;AAAA,EACF;AAGA,QAAM,UAAU,QAAQ,IAAI,QAAQ,QAAQ,IAAI,QAAQ,QAAQ,IAAI,QAAQ;AAC5E,QAAM,cAAc,iBAAiB,SAAS,QAAQ,QAAQ;AAC9D,aAAW,KAAK,aAAa;AAC3B,UAAM,YAAY,KAAK,GAAG,UAAU;AACpC,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO,EAAE,KAAK,WAAW,MAAM,CAAC,GAAG,MAAM,OAAO;AAAA,IAClD;AAAA,EACF;AAGA,MAAI;AAEF,aAAS,uCAAuC;AAAA,MAC9C,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AACD,WAAO,EAAE,KAAK,WAAW,MAAM,CAAC,MAAM,QAAQ,IAAI,GAAG,MAAM,MAAM;AAAA,EACnE,QAAQ;AAAA,EAAC;AAGT,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACX,QAAM,IAAI,MAAM,IAAI;AACtB;AAEO,MAAM,gBAAgB;AAAA,EACnB,eAAgC,CAAC;AAAA,EACjC,cAAuB;AAAA,EACvB;AAAA,EACA,UAAmB;AAAA,EACnB,qBAA8B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,KAAa;AACvB,UAAM,EAAE,KAAK,MAAM,KAAK,IAAI,YAAY;AACxC,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,YAAY;AAEjB,SAAK,QAAQ,MAAM,KAAK,UAAU,KAAK,WAAW;AAAA,MAChD,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,MAC9B;AAAA,MACA,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,YAAY;AAAA,MACd;AAAA,IACF,CAAC;AAED,SAAK,MAAM;AAEX,SAAK,MAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AACtC,UAAI,MAAM;AAER,iBAAS,0BAA0B,IAAI,eAAe,MAAM,EAAE;AAAA,MAChE;AACA,iBAAW,QAAQ;AAAA,QACjB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP,GAAG;AACD,YAAI,GAAG,WAAW,IAAI,GAAG;AACvB,aAAG,WAAW,IAAI;AAAA,QACpB;AAAA,MACF;AACA,WAAK,UAAU;AAAA,IACjB,CAAC;AAED,UAAM,KAAK,KAAK,MAAM,KAAK,OAAO,IAAI,KAAO,EAC1C,SAAS,EAAE,EACX,SAAS,GAAG,GAAG;AAElB,SAAK,aAAa,kBAAkB,KAAK,cAAc;AACvD,SAAK,aAAa,kBAAkB,KAAK,cAAc;AACvD,SAAK,aAAa,kBAAkB,KAAK,cAAc;AACvD,SAAK,UAAU,kBAAkB,KAAK,cAAc;AACpD,eAAW,QAAQ,CAAC,KAAK,YAAY,KAAK,YAAY,KAAK,UAAU,GAAG;AACtE,SAAG,cAAc,MAAM,EAAE;AAAA,IAC3B;AAEA,OAAG,cAAc,KAAK,SAAS,GAAG;AAGlC,SAAK,qBAAqB,WAAW,KAAK,YAAY,KAAK,SAAS;AACpE,SAAK,qBAAqB,WAAW,KAAK,YAAY,KAAK,SAAS;AACpE,SAAK,qBAAqB,WAAW,KAAK,YAAY,KAAK,SAAS;AACpE,SAAK,kBAAkB,WAAW,KAAK,SAAS,KAAK,SAAS;AAG9D,QAAI,KAAK,cAAc,QAAQ;AAE7B,WAAK,YAAY,8CAA8C;AAE/D,WAAK,YAAY,YAAY,aAAa,KAAK,eAAe,CAAC,EAAE;AAAA,IACnE,OAAO;AACL,WAAK,YAAY,8CAA8C;AAAA,IACjE;AAAA,EACF;AAAA,EAEA,OAAe,WAAmC;AAAA,EAElD,OAAO,UAAU;AACf,QAAI,gBAAgB,UAAU;AAC5B,sBAAgB,SAAS,MAAM;AAC/B,sBAAgB,WAAW;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,OAAO,cAA+B;AACpC,QAAI,CAAC,gBAAgB,YAAY,CAAC,gBAAgB,SAAS,SAAS;AAClE,sBAAgB,WAAW,IAAI,gBAAgB,QAAQ,IAAI,CAAC;AAAA,IAC9D;AACA,WAAO,gBAAgB;AAAA,EACzB;AAAA,EAEA,eAAe;AACb,UAAM,YAAY,KAAK,MAAM;AAC7B,QAAI;AACF,YAAM,YAAY,SAAS,YAAY,SAAS,EAAE,EAC/C,SAAS,EACT,KAAK,EACL,MAAM,IAAI,EACV,OAAO,OAAO;AAEjB,gBAAU,QAAQ,SAAO;AACvB,YAAI;AACF,kBAAQ,KAAK,OAAO,GAAG,GAAG,SAAS;AAAA,QACrC,SAAS,OAAO;AACd,mBAAS,0BAA0B,GAAG,KAAK,KAAK,EAAE;AAAA,QACpD;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AAAA,IAER,UAAE;AACA,WAAK,qBAAqB;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAc,eAAe;AAW3B,QAAI,KAAK,eAAe,KAAK,aAAa,WAAW,EAAG;AAExD,SAAK,cAAc;AACnB,UAAM,EAAE,SAAS,aAAa,SAAS,SAAAA,UAAS,OAAO,IACrD,KAAK,aAAa,MAAM;AAE1B,UAAM,eAAe,MAAM,KAAK,aAAa;AAC7C,QAAI,aAAa;AACf,kBAAY,iBAAiB,SAAS,YAAY;AAAA,IACpD;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,MAAM,SAAS,OAAO;AAIhD,MAAAA,SAAQ,MAAM;AAAA,IAChB,SAAS,OAAO;AACd,aAAO,KAAc;AAAA,IACvB,UAAE;AACA,WAAK,cAAc;AACnB,UAAI,aAAa;AACf,oBAAY,oBAAoB,SAAS,YAAY;AAAA,MACvD;AAEA,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAM,KACJ,SACA,aACA,SACqB;AACrB,WAAO,IAAI,QAAQ,CAACA,UAAS,WAAW;AACtC,WAAK,aAAa,KAAK,EAAE,SAAS,aAAa,SAAS,SAAAA,UAAS,OAAO,CAAC;AACzE,WAAK,aAAa;AAAA,IACpB,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,MAAM,SAAiB,SAAuC;AAmB1E,UAAM,gBAAgB,aAAa,OAAO;AAG1C,QAAI;AACF,UAAI,KAAK,cAAc,OAAO;AAE5B,qBAAa,WAAW,CAAC,MAAM,QAAQ,MAAM,MAAM,OAAO,GAAG;AAAA,UAC3D,OAAO;AAAA,UACP,SAAS;AAAA,QACX,CAAC;AAAA,MACH,WAAW,KAAK,cAAc,QAAQ;AAEpC,qBAAa,KAAK,UAAU,CAAC,MAAM,MAAM,OAAO,GAAG;AAAA,UACjD,OAAO;AAAA,UACP,SAAS;AAAA,QACX,CAAC;AAAA,MACH,OAAO;AAEL,iBAAS,GAAG,KAAK,QAAQ,UAAU,aAAa,IAAI;AAAA,UAClD,OAAO;AAAA,UACP,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AAAA,IACF,SAAS,OAAO;AAEd,YAAM,YAAY;AAClB,YAAM,iBAAiB,WAAW,UAAU,WAAW,QAAQ;AAC/D,YAAM,WACJ,WAAW,QAAQ,SAAS,KAC5B,WAAW,WACX,OAAO,SAAS,EAAE;AAEpB,aAAO,QAAQ,QAAQ;AAAA,QACrB,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,MACf,CAAC;AAAA,IACH;AAEA,UAAM,iBAAiB,WAAW;AAElC,SAAK,qBAAqB;AAC1B,WAAO,IAAI,QAAoB,CAAAA,aAAW;AAExC,SAAG,cAAc,KAAK,YAAY,EAAE;AACpC,SAAG,cAAc,KAAK,YAAY,EAAE;AACpC,SAAG,cAAc,KAAK,YAAY,EAAE;AAEpC,YAAM,eAAe,CAAC;AAGtB,mBAAa;AAAA,QACX,QAAQ,aAAa,kBAAkB,aAAa,KAAK,kBAAkB,CAAC,OAAO,aAAa,KAAK,kBAAkB,CAAC;AAAA,MAC1H;AAGA,mBAAa,KAAK,mBAAmB;AAGrC,UAAI,KAAK,cAAc,QAAQ;AAC7B,qBAAa,KAAK,YAAY,aAAa,KAAK,eAAe,CAAC,EAAE;AAAA,MACpE,OAAO;AACL,qBAAa,KAAK,SAAS,aAAa,KAAK,eAAe,CAAC,EAAE;AAAA,MACjE;AAGA,mBAAa;AAAA,QACX,0BAA0B,aAAa,KAAK,kBAAkB,CAAC;AAAA,MACjE;AAGA,WAAK,YAAY,aAAa,KAAK,IAAI,CAAC;AAGxC,YAAM,QAAQ,KAAK,IAAI;AACvB,YAAM,kBAAkB,YAAY,MAAM;AACxC,YAAI;AACF,cAAI,iBAAiB;AACrB,cAAI,GAAG,WAAW,KAAK,UAAU,GAAG;AAClC,6BAAiB,GAAG,SAAS,KAAK,UAAU,EAAE;AAAA,UAChD;AAEA,cACE,iBAAiB,KACjB,KAAK,IAAI,IAAI,QAAQ,kBACrB,KAAK,oBACL;AACA,0BAAc,eAAe;AAC7B,kBAAM,SAAS,GAAG,WAAW,KAAK,UAAU,IACxC,GAAG,aAAa,KAAK,YAAY,MAAM,IACvC;AACJ,gBAAI,SAAS,GAAG,WAAW,KAAK,UAAU,IACtC,GAAG,aAAa,KAAK,YAAY,MAAM,IACvC;AACJ,gBAAI;AACJ,gBAAI,gBAAgB;AAClB,qBAAO,OAAO,GAAG,aAAa,KAAK,YAAY,MAAM,CAAC;AAAA,YACxD,OAAO;AAEL,mBAAK,aAAa;AAClB,qBAAO;AACP,yBAAW,SAAS,OAAO,MAAM;AAAA,YACnC;AACA,YAAAA,SAAQ;AAAA,cACN;AAAA,cACA;AAAA,cACA;AAAA,cACA,aAAa,KAAK;AAAA,YACpB,CAAC;AAAA,UACH;AAAA,QACF,QAAQ;AAAA,QAGR;AAAA,MACF,GAAG,EAAE;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAEQ,YAAY,SAAiB;AACnC,QAAI;AACF,WAAK,MAAO,MAAO,MAAM,UAAU,IAAI;AAAA,IACzC,SAAS,OAAO;AACd,YAAM,cACJ,iBAAiB,QACb,MAAM,UACN,OAAO,SAAS,eAAe;AACrC,eAAS,yBAAyB,WAAW,EAAE;AAE/C,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc;AACZ,QAAI;AACF,YAAM,SAAS,GAAG,aAAa,KAAK,SAAS,MAAM,EAAE,KAAK;AAC1D,UAAI,QAAQ;AACV,aAAK,MAAM;AAAA,MACb;AAAA,IACF,SAAS,OAAO;AACd,eAAS,mBAAmB,KAAK,EAAE;AAAA,IACrC;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OAAO,KAAa;AACxB,UAAM,WAAW,WAAW,GAAG,IAAI,MAAM,QAAQ,QAAQ,IAAI,GAAG,GAAG;AACnE,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,YAAM,IAAI,MAAM,SAAS,QAAQ,kBAAkB;AAAA,IACrD;AACA,UAAM,WAAW,WAAW,UAAU,KAAK,SAAS;AACpD,UAAM,KAAK,KAAK,MAAM,aAAa,QAAQ,CAAC,EAAE;AAAA,EAChD;AAAA,EAEA,QAAc;AACZ,SAAK,MAAO,MAAO,IAAI;AACvB,SAAK,MAAM,KAAK;AAAA,EAClB;AACF;",
|
|
6
6
|
"names": ["resolve"]
|
|
7
7
|
}
|
|
@@ -178,7 +178,10 @@ class AdvancedFuzzyMatcher {
|
|
|
178
178
|
const index = cleanText.indexOf(cleanPattern);
|
|
179
179
|
if (index !== -1) {
|
|
180
180
|
const positionPenalty = index * 5;
|
|
181
|
-
return {
|
|
181
|
+
return {
|
|
182
|
+
score: Math.max(50, 100 - positionPenalty),
|
|
183
|
+
algorithm: "fuzzy-contains"
|
|
184
|
+
};
|
|
182
185
|
}
|
|
183
186
|
return { score: 0, algorithm: "fuzzy-segment" };
|
|
184
187
|
}
|