@within-7/minto 0.3.9 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agents/AgentsCommand.js +459 -655
- package/dist/commands/agents/AgentsCommand.js.map +2 -2
- package/dist/commands/agents/types.js +1 -0
- package/dist/commands/agents/types.js.map +2 -2
- package/dist/commands/agents/utils/fileOperations.js +96 -36
- package/dist/commands/agents/utils/fileOperations.js.map +3 -3
- package/dist/commands/agents/utils/index.js +3 -1
- package/dist/commands/agents/utils/index.js.map +2 -2
- package/dist/commands/context.js +54 -23
- package/dist/commands/context.js.map +2 -2
- package/dist/commands/export.js +673 -93
- package/dist/commands/export.js.map +2 -2
- package/dist/commands/language.js +19 -46
- package/dist/commands/language.js.map +2 -2
- package/dist/commands/mcp-interactive.js +419 -217
- package/dist/commands/mcp-interactive.js.map +2 -2
- package/dist/commands/model.js +415 -66
- package/dist/commands/model.js.map +2 -2
- package/dist/commands/permissions.js +75 -49
- package/dist/commands/permissions.js.map +2 -2
- package/dist/commands/plugin.js +882 -185
- package/dist/commands/plugin.js.map +3 -3
- package/dist/commands/resume.js +1 -1
- package/dist/commands/resume.js.map +1 -1
- package/dist/commands/sandbox.js +168 -70
- package/dist/commands/sandbox.js.map +2 -2
- package/dist/commands/setup.js +593 -107
- package/dist/commands/setup.js.map +2 -2
- package/dist/commands/stats.js +188 -131
- package/dist/commands/stats.js.map +2 -2
- package/dist/commands/status.js +75 -13
- package/dist/commands/status.js.map +2 -2
- package/dist/commands/undo.js +138 -174
- package/dist/commands/undo.js.map +2 -2
- package/dist/commands.js.map +1 -1
- package/dist/components/Help.js +165 -32
- package/dist/components/Help.js.map +2 -2
- package/dist/components/InfoPanel/InfoPanel.js +123 -0
- package/dist/components/InfoPanel/InfoPanel.js.map +7 -0
- package/dist/components/InfoPanel/index.js +5 -0
- package/dist/components/InfoPanel/index.js.map +7 -0
- package/dist/components/InfoPanel/types.js +1 -0
- package/dist/components/InfoPanel/types.js.map +7 -0
- package/dist/components/ModelSelector/BrandTextInput.js +43 -0
- package/dist/components/ModelSelector/BrandTextInput.js.map +7 -0
- package/dist/components/ModelSelector/ModelSelector.js +419 -501
- package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
- package/dist/components/ModelSelector/WizardContainer.js +45 -0
- package/dist/components/ModelSelector/WizardContainer.js.map +7 -0
- package/dist/components/ModelSelector/index.js +1 -3
- package/dist/components/ModelSelector/index.js.map +2 -2
- package/dist/components/PromptInput.js +5 -5
- package/dist/components/PromptInput.js.map +2 -2
- package/dist/components/SimpleSelector/SimpleSelector.js +154 -0
- package/dist/components/SimpleSelector/SimpleSelector.js.map +7 -0
- package/dist/components/SimpleSelector/index.js +5 -0
- package/dist/components/SimpleSelector/index.js.map +7 -0
- package/dist/components/SimpleSelector/types.js +1 -0
- package/dist/components/SimpleSelector/types.js.map +7 -0
- package/dist/components/StatusOverlayContent.js +21 -0
- package/dist/components/StatusOverlayContent.js.map +7 -0
- package/dist/components/TabbedListView/ScrollableList.js +31 -5
- package/dist/components/TabbedListView/ScrollableList.js.map +2 -2
- package/dist/components/TabbedListView/TabbedListView.js +122 -47
- package/dist/components/TabbedListView/TabbedListView.js.map +2 -2
- package/dist/core/backupHook.js +29 -0
- package/dist/core/backupHook.js.map +7 -0
- package/dist/core/config/defaults.js +8 -2
- package/dist/core/config/defaults.js.map +2 -2
- package/dist/core/config/schema.js +14 -2
- package/dist/core/config/schema.js.map +2 -2
- package/dist/core/costTracker.js +0 -16
- package/dist/core/costTracker.js.map +2 -2
- package/dist/cost-tracker.js +0 -16
- package/dist/cost-tracker.js.map +2 -2
- package/dist/entrypoints/bootstrap.js +3 -1
- package/dist/entrypoints/bootstrap.js.map +2 -2
- package/dist/entrypoints/cli.js +32 -0
- package/dist/entrypoints/cli.js.map +2 -2
- package/dist/i18n/locales/en.js +300 -1
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/zh-CN.js +301 -2
- package/dist/i18n/locales/zh-CN.js.map +2 -2
- package/dist/i18n/types.js.map +1 -1
- package/dist/services/customCommands.js +30 -8
- package/dist/services/customCommands.js.map +2 -2
- package/dist/services/plugins/lspServers.js +1 -1
- package/dist/services/plugins/lspServers.js.map +2 -2
- package/dist/services/plugins/pluginRuntime.js +2 -1
- package/dist/services/plugins/pluginRuntime.js.map +2 -2
- package/dist/services/plugins/pluginValidation.js +10 -3
- package/dist/services/plugins/pluginValidation.js.map +2 -2
- package/dist/services/plugins/skillMarketplace.js +16 -8
- package/dist/services/plugins/skillMarketplace.js.map +2 -2
- package/dist/services/systemReminder.js +17 -6
- package/dist/services/systemReminder.js.map +2 -2
- package/dist/tools/FileEditTool/FileEditTool.js +7 -0
- package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
- package/dist/tools/FileWriteTool/FileWriteTool.js +7 -0
- package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
- package/dist/tools/MultiEditTool/MultiEditTool.js +7 -0
- package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
- package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -0
- package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
- package/dist/tools/TaskTool/TaskTool.js +9 -6
- package/dist/tools/TaskTool/TaskTool.js.map +2 -2
- package/dist/types/PermissionMode.js.map +1 -1
- package/dist/types/plugin.js +2 -4
- package/dist/types/plugin.js.map +2 -2
- package/dist/utils/agentHookExecutor.js +1 -4
- package/dist/utils/agentHookExecutor.js.map +2 -2
- package/dist/utils/agentLoader.js +67 -13
- package/dist/utils/agentLoader.js.map +2 -2
- package/dist/utils/agentMemory.js.map +2 -2
- package/dist/utils/claudeCodeSync.js +439 -0
- package/dist/utils/claudeCodeSync.js.map +7 -0
- package/dist/utils/config.js +1 -23
- package/dist/utils/config.js.map +2 -2
- package/dist/utils/execFileNoThrow.js +2 -1
- package/dist/utils/execFileNoThrow.js.map +2 -2
- package/dist/utils/marketplaceManager.js +80 -43
- package/dist/utils/marketplaceManager.js.map +2 -2
- package/dist/utils/messages.js +2 -2
- package/dist/utils/messages.js.map +2 -2
- package/dist/utils/pluginInstaller.js +34 -24
- package/dist/utils/pluginInstaller.js.map +2 -2
- package/dist/utils/pluginLoader.js +48 -25
- package/dist/utils/pluginLoader.js.map +2 -2
- package/dist/utils/repoFetcher.js +110 -0
- package/dist/utils/repoFetcher.js.map +7 -0
- package/dist/utils/skillLoader.js +18 -6
- package/dist/utils/skillLoader.js.map +2 -2
- package/dist/utils/stringSubstitution.js +4 -5
- package/dist/utils/stringSubstitution.js.map +2 -2
- package/dist/utils/teamConfig.js +153 -13
- package/dist/utils/teamConfig.js.map +2 -2
- package/dist/utils/terminal.js +1 -1
- package/dist/utils/terminal.js.map +2 -2
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/services/plugins/pluginValidation.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Plugin Validation\n *\n * Validates plugin manifests and configurations.\n * Compatible with Claude Code CLI plugin specification.\n */\n\nimport { z } from 'zod'\nimport { existsSync, readFileSync, statSync } from 'fs'\nimport { join, resolve } from 'path'\n\n/**\n * Author schema (can be string or object)\n */\nconst AuthorSchema = z.union([\n z.string(),\n z.object({\n name: z.string().optional(),\n email: z.string().email().optional(),\n url: z.string().url().optional(),\n }),\n])\n\n/**\n * Hooks configuration schema\n */\nconst HooksConfigSchema = z.union([\n z.string(),\n z.array(z.string()),\n z.record(\n z.object({\n command: z.string().optional(),\n script: z.string().optional(),\n timeout: z.number().optional(),\n blocking: z.boolean().optional(),\n }),\n ),\n])\n\n/**\n * MCP Servers configuration schema\n */\nconst McpServersConfigSchema = z.union([\n z.string(),\n z.array(z.string()),\n z.record(\n z.object({\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n env: z.record(z.string()).optional(),\n type: z.enum(['stdio', 'http', 'sse']).optional(),\n url: z.string().optional(),\n headers: z.record(z.string()).optional(),\n timeout: z.number().optional(),\n }),\n ),\n])\n\n/**\n * LSP Servers configuration schema (Claude Code CLI specific)\n */\nconst LspServersConfigSchema = z.union([\n z.string(),\n z.array(z.string()),\n z.record(\n z.object({\n command: z.string(),\n args: z.array(z.string()).optional(),\n rootPath: z.string().optional(),\n filePatterns: z.array(z.string()).optional(),\n }),\n ),\n])\n\n/**\n * Minimal plugin manifest schema for loading\n * More permissive than full schema to allow partial manifests\n */\nexport const PluginManifestSchema = z\n .object({\n name: z.string().min(1),\n })\n .passthrough()\n\nexport type PluginManifestBasic = z.infer<typeof PluginManifestSchema>\n\n/**\n * Full Claude Code CLI plugin manifest schema\n * Used for strict validation\n */\nexport const FullPluginManifestSchema = z.object({\n // Required field\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\n // Metadata fields (Claude Code CLI specification)\n version: z\n .string()\n .regex(/^\\d+\\.\\d+\\.\\d+$/, 'Version must follow semver format (e.g., 1.0.0)')\n .optional(),\n description: z.string().optional(),\n author: AuthorSchema.optional(),\n homepage: z.string().url().optional(),\n repository: z.string().optional(), // Can be URL or \"owner/repo\"\n license: z.string().optional(),\n keywords: z.array(z.string()).optional(),\n\n // Component path fields\n commands: z.union([z.string(), z.array(z.string())]).optional(),\n agents: z.union([z.string(), z.array(z.string())]).optional(),\n skills: z.union([z.string(), z.array(z.string())]).optional(),\n hooks: HooksConfigSchema.optional(),\n mcpServers: McpServersConfigSchema.optional(),\n outputStyles: z.union([z.string(), z.array(z.string())]).optional(),\n lspServers: LspServersConfigSchema.optional(), // Claude Code CLI specific\n\n // Engine 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 // Dependencies\n dependencies: z.record(z.string()).optional(),\n})\n\nexport type FullPluginManifest = z.infer<typeof FullPluginManifestSchema>\n\n/**\n * Validation result type\n */\nexport interface ValidationResult {\n isValid: boolean\n errors: string[]\n warnings: string[]\n}\n\n/**\n * Validate a plugin manifest (permissive)\n */\nexport function validatePluginManifest(\n manifest: unknown,\n):\n | { success: true; data: PluginManifestBasic }\n | { success: false; error: z.ZodError } {\n const result = PluginManifestSchema.safeParse(manifest)\n if (result.success) {\n return { success: true, data: result.data }\n }\n return { success: false, error: result.error }\n}\n\n/**\n * Validate a plugin manifest (strict - full Claude Code CLI specification)\n */\nexport function validatePluginManifestStrict(\n manifest: unknown,\n):\n | { success: true; data: FullPluginManifest }\n | { success: false; error: z.ZodError } {\n const result = FullPluginManifestSchema.safeParse(manifest)\n if (result.success) {\n return { success: true, data: result.data }\n }\n return { success: false, error: result.error }\n}\n\n/**\n * Validate a plugin directory structure\n */\nexport function validatePluginDirectory(rootDir: string): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Resolve path\n const resolvedPath = resolve(rootDir)\n\n // Check directory exists\n if (!existsSync(resolvedPath)) {\n errors.push(`Directory does not exist: ${resolvedPath}`)\n return { isValid: false, errors, warnings }\n }\n\n // Check it's a directory\n try {\n if (!statSync(resolvedPath).isDirectory()) {\n errors.push(`Path is not a directory: ${resolvedPath}`)\n return { isValid: false, errors, warnings }\n }\n } catch {\n errors.push(`Cannot access path: ${resolvedPath}`)\n return { isValid: false, errors, warnings }\n }\n\n // Check for manifest file\n const mintoManifest = join(resolvedPath, '.minto-plugin', 'plugin.json')\n const rootManifest = join(resolvedPath, 'plugin.json')\n\n const hasMintoManifest = existsSync(mintoManifest)\n const hasRootManifest = existsSync(rootManifest)\n\n if (!hasMintoManifest && !hasRootManifest) {\n errors.push(\n 'No plugin manifest found. Expected .minto-plugin/plugin.json or plugin.json',\n )\n return { isValid: false, errors, warnings }\n }\n\n // Prefer .minto-plugin manifest if exists\n const manifestPath = hasMintoManifest ? mintoManifest : rootManifest\n\n // Validate manifest contents\n try {\n const content = readFileSync(manifestPath, 'utf-8')\n const data = JSON.parse(content)\n\n // Permissive validation first\n const basicResult = validatePluginManifest(data)\n if (basicResult.success === false) {\n errors.push(`Invalid manifest: ${basicResult.error.message}`)\n return { isValid: false, errors, warnings }\n }\n\n // Check for recommended fields\n if (!data.version) {\n warnings.push('Manifest is missing recommended field: version')\n }\n if (!data.description) {\n warnings.push('Manifest is missing recommended field: description')\n }\n\n // Validate version format if present\n if (data.version && !/^\\d+\\.\\d+\\.\\d+$/.test(data.version)) {\n warnings.push(\n `Version \"${data.version}\" does not follow semver format (X.Y.Z)`,\n )\n }\n\n // Validate name format\n if (data.name && !/^[a-z0-9-]+$/.test(data.name)) {\n warnings.push(\n `Name \"${data.name}\" should be lowercase alphanumeric with hyphens`,\n )\n }\n\n // Check component paths exist\n const componentChecks = [\n { field: 'commands', paths: normalizeToArray(data.commands) },\n { field: 'agents', paths: normalizeToArray(data.agents) },\n { field: 'skills', paths: normalizeToArray(data.skills) },\n { field: 'outputStyles', paths: normalizeToArray(data.outputStyles) },\n ]\n\n for (const check of componentChecks) {\n for (const path of check.paths) {\n const fullPath = join(resolvedPath, path)\n if (!existsSync(fullPath)) {\n warnings.push(`${check.field} path does not exist: ${path}`)\n }\n }\n }\n } catch (error) {\n if (error instanceof SyntaxError) {\n errors.push(`Invalid JSON in manifest: ${error.message}`)\n } else {\n errors.push(\n `Error reading manifest: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n return { isValid: false, errors, warnings }\n }\n\n return { isValid: errors.length === 0, errors, warnings }\n}\n\n/**\n * Normalize string or array to array\n */\nfunction normalizeToArray(value: unknown): string[] {\n if (!value) return []\n if (typeof value === 'string') return [value]\n if (Array.isArray(value)) return value.filter(v => typeof v === 'string')\n return []\n}\n\n/**\n * Validate marketplace manifest path\n */\nexport function validateMarketplacePath(path: string): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n const resolvedPath = resolve(path)\n\n if (!existsSync(resolvedPath)) {\n errors.push(`Marketplace path does not exist: ${resolvedPath}`)\n return { isValid: false, errors, warnings }\n }\n\n // Check for marketplace manifest\n const mintoManifest = join(resolvedPath, '.minto-plugin', 'marketplace.json')\n\n if (!existsSync(mintoManifest)) {\n errors.push(\n 'No marketplace manifest found. Expected .minto-plugin/marketplace.json',\n )\n }\n\n return { isValid: errors.length === 0, errors, warnings }\n}\n\n/**\n * Check if a manifest is valid for loading (permissive)\n */\nexport function isValidManifest(data: unknown): boolean {\n return validatePluginManifest(data).success\n}\n\n/**\n * Check if a manifest is strictly valid\n */\nexport function isStrictlyValidManifest(data: unknown): boolean {\n return validatePluginManifestStrict(data).success\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/**\n * Plugin Validation\n *\n * Validates plugin manifests and configurations.\n * Compatible with Claude Code CLI plugin specification.\n */\n\nimport { z } from 'zod'\nimport { existsSync, readFileSync, statSync } from 'fs'\nimport { join, resolve } from 'path'\n\n/**\n * Author schema (can be string or object)\n */\nconst AuthorSchema = z.union([\n z.string(),\n z.object({\n name: z.string().optional(),\n email: z.string().email().optional(),\n url: z.string().url().optional(),\n }),\n])\n\n/**\n * Hooks configuration schema\n */\nconst HooksConfigSchema = z.union([\n z.string(),\n z.array(z.string()),\n z.record(\n z.object({\n command: z.string().optional(),\n script: z.string().optional(),\n timeout: z.number().optional(),\n blocking: z.boolean().optional(),\n }),\n ),\n])\n\n/**\n * MCP Servers configuration schema\n */\nconst McpServersConfigSchema = z.union([\n z.string(),\n z.array(z.string()),\n z.record(\n z.object({\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n env: z.record(z.string()).optional(),\n type: z.enum(['stdio', 'http', 'sse']).optional(),\n url: z.string().optional(),\n headers: z.record(z.string()).optional(),\n timeout: z.number().optional(),\n }),\n ),\n])\n\n/**\n * LSP Servers configuration schema (Claude Code CLI specific)\n */\nconst LspServersConfigSchema = z.union([\n z.string(),\n z.array(z.string()),\n z.record(\n z.object({\n command: z.string(),\n args: z.array(z.string()).optional(),\n rootPath: z.string().optional(),\n filePatterns: z.array(z.string()).optional(),\n }),\n ),\n])\n\n/**\n * Minimal plugin manifest schema for loading\n * More permissive than full schema to allow partial manifests\n */\nexport const PluginManifestSchema = z\n .object({\n name: z.string().min(1),\n })\n .passthrough()\n\nexport type PluginManifestBasic = z.infer<typeof PluginManifestSchema>\n\n/**\n * Full Claude Code CLI plugin manifest schema\n * Used for strict validation\n */\nexport const FullPluginManifestSchema = z.object({\n // Required field\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\n // Metadata fields (Claude Code CLI specification)\n version: z\n .string()\n .regex(/^\\d+\\.\\d+\\.\\d+$/, 'Version must follow semver format (e.g., 1.0.0)')\n .optional(),\n description: z.string().optional(),\n author: AuthorSchema.optional(),\n homepage: z.string().url().optional(),\n repository: z.string().optional(), // Can be URL or \"owner/repo\"\n license: z.string().optional(),\n keywords: z.array(z.string()).optional(),\n\n // Component path fields\n commands: z.union([z.string(), z.array(z.string())]).optional(),\n agents: z.union([z.string(), z.array(z.string())]).optional(),\n skills: z.union([z.string(), z.array(z.string())]).optional(),\n hooks: HooksConfigSchema.optional(),\n mcpServers: McpServersConfigSchema.optional(),\n outputStyles: z.union([z.string(), z.array(z.string())]).optional(),\n lspServers: LspServersConfigSchema.optional(), // Claude Code CLI specific\n\n // Engine 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 // Dependencies\n dependencies: z.record(z.string()).optional(),\n})\n\nexport type FullPluginManifest = z.infer<typeof FullPluginManifestSchema>\n\n/**\n * Validation result type\n */\nexport interface ValidationResult {\n isValid: boolean\n errors: string[]\n warnings: string[]\n}\n\n/**\n * Validate a plugin manifest (permissive)\n */\nexport function validatePluginManifest(\n manifest: unknown,\n):\n | { success: true; data: PluginManifestBasic }\n | { success: false; error: z.ZodError } {\n const result = PluginManifestSchema.safeParse(manifest)\n if (result.success) {\n return { success: true, data: result.data }\n }\n return { success: false, error: result.error }\n}\n\n/**\n * Validate a plugin manifest (strict - full Claude Code CLI specification)\n */\nexport function validatePluginManifestStrict(\n manifest: unknown,\n):\n | { success: true; data: FullPluginManifest }\n | { success: false; error: z.ZodError } {\n const result = FullPluginManifestSchema.safeParse(manifest)\n if (result.success) {\n return { success: true, data: result.data }\n }\n return { success: false, error: result.error }\n}\n\n/**\n * Validate a plugin directory structure\n */\nexport function validatePluginDirectory(rootDir: string): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Resolve path\n const resolvedPath = resolve(rootDir)\n\n // Check directory exists\n if (!existsSync(resolvedPath)) {\n errors.push(`Directory does not exist: ${resolvedPath}`)\n return { isValid: false, errors, warnings }\n }\n\n // Check it's a directory\n try {\n if (!statSync(resolvedPath).isDirectory()) {\n errors.push(`Path is not a directory: ${resolvedPath}`)\n return { isValid: false, errors, warnings }\n }\n } catch {\n errors.push(`Cannot access path: ${resolvedPath}`)\n return { isValid: false, errors, warnings }\n }\n\n // Check for manifest file (.minto-plugin, .claude-plugin, or root)\n const mintoManifest = join(resolvedPath, '.minto-plugin', 'plugin.json')\n const claudeManifest = join(resolvedPath, '.claude-plugin', 'plugin.json')\n const rootManifest = join(resolvedPath, 'plugin.json')\n\n const hasMintoManifest = existsSync(mintoManifest)\n const hasClaudeManifest = existsSync(claudeManifest)\n const hasRootManifest = existsSync(rootManifest)\n\n if (!hasMintoManifest && !hasClaudeManifest && !hasRootManifest) {\n errors.push(\n 'No plugin manifest found. Expected .minto-plugin/plugin.json or plugin.json',\n )\n return { isValid: false, errors, warnings }\n }\n\n // Prefer .minto-plugin > .claude-plugin > root manifest\n const manifestPath = hasMintoManifest\n ? mintoManifest\n : hasClaudeManifest\n ? claudeManifest\n : rootManifest\n\n // Validate manifest contents\n try {\n const content = readFileSync(manifestPath, 'utf-8')\n const data = JSON.parse(content)\n\n // Permissive validation first\n const basicResult = validatePluginManifest(data)\n if (basicResult.success === false) {\n errors.push(`Invalid manifest: ${basicResult.error.message}`)\n return { isValid: false, errors, warnings }\n }\n\n // Check for recommended fields\n if (!data.version) {\n warnings.push('Manifest is missing recommended field: version')\n }\n if (!data.description) {\n warnings.push('Manifest is missing recommended field: description')\n }\n\n // Validate version format if present\n if (data.version && !/^\\d+\\.\\d+\\.\\d+$/.test(data.version)) {\n warnings.push(\n `Version \"${data.version}\" does not follow semver format (X.Y.Z)`,\n )\n }\n\n // Validate name format\n if (data.name && !/^[a-z0-9-]+$/.test(data.name)) {\n warnings.push(\n `Name \"${data.name}\" should be lowercase alphanumeric with hyphens`,\n )\n }\n\n // Check component paths exist\n const componentChecks = [\n { field: 'commands', paths: normalizeToArray(data.commands) },\n { field: 'agents', paths: normalizeToArray(data.agents) },\n { field: 'skills', paths: normalizeToArray(data.skills) },\n { field: 'outputStyles', paths: normalizeToArray(data.outputStyles) },\n ]\n\n for (const check of componentChecks) {\n for (const path of check.paths) {\n const fullPath = join(resolvedPath, path)\n if (!existsSync(fullPath)) {\n warnings.push(`${check.field} path does not exist: ${path}`)\n }\n }\n }\n } catch (error) {\n if (error instanceof SyntaxError) {\n errors.push(`Invalid JSON in manifest: ${error.message}`)\n } else {\n errors.push(\n `Error reading manifest: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n return { isValid: false, errors, warnings }\n }\n\n return { isValid: errors.length === 0, errors, warnings }\n}\n\n/**\n * Normalize string or array to array\n */\nfunction normalizeToArray(value: unknown): string[] {\n if (!value) return []\n if (typeof value === 'string') return [value]\n if (Array.isArray(value)) return value.filter(v => typeof v === 'string')\n return []\n}\n\n/**\n * Validate marketplace manifest path\n */\nexport function validateMarketplacePath(path: string): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n const resolvedPath = resolve(path)\n\n if (!existsSync(resolvedPath)) {\n errors.push(`Marketplace path does not exist: ${resolvedPath}`)\n return { isValid: false, errors, warnings }\n }\n\n // Check for marketplace manifest (.minto-plugin or .claude-plugin)\n const mintoManifest = join(resolvedPath, '.minto-plugin', 'marketplace.json')\n const claudeManifest = join(\n resolvedPath,\n '.claude-plugin',\n 'marketplace.json',\n )\n\n if (!existsSync(mintoManifest) && !existsSync(claudeManifest)) {\n errors.push(\n 'No marketplace manifest found. Expected .minto-plugin/marketplace.json',\n )\n }\n\n return { isValid: errors.length === 0, errors, warnings }\n}\n\n/**\n * Check if a manifest is valid for loading (permissive)\n */\nexport function isValidManifest(data: unknown): boolean {\n return validatePluginManifest(data).success\n}\n\n/**\n * Check if a manifest is strictly valid\n */\nexport function isStrictlyValidManifest(data: unknown): boolean {\n return validatePluginManifestStrict(data).success\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -132,7 +132,8 @@ async function fetchGitHubMarketplace(repo, ref, path) {
|
|
|
132
132
|
const url = `https://github.com/${repo}.git`;
|
|
133
133
|
const tempDir = await cloneGitRepo(url, ref);
|
|
134
134
|
try {
|
|
135
|
-
const
|
|
135
|
+
const baseDir = path ? join(tempDir, path) : tempDir;
|
|
136
|
+
const manifestPath = resolveManifestPath(baseDir, "marketplace.json");
|
|
136
137
|
return loadManifestFromPath(manifestPath);
|
|
137
138
|
} finally {
|
|
138
139
|
try {
|
|
@@ -144,7 +145,8 @@ async function fetchGitHubMarketplace(repo, ref, path) {
|
|
|
144
145
|
async function fetchGitMarketplace(url, ref, path) {
|
|
145
146
|
const tempDir = await cloneGitRepo(url, ref);
|
|
146
147
|
try {
|
|
147
|
-
const
|
|
148
|
+
const baseDir = path ? join(tempDir, path) : tempDir;
|
|
149
|
+
const manifestPath = resolveManifestPath(baseDir, "marketplace.json");
|
|
148
150
|
return loadManifestFromPath(manifestPath);
|
|
149
151
|
} finally {
|
|
150
152
|
try {
|
|
@@ -212,10 +214,8 @@ async function fetchNpmMarketplace(packageName, version) {
|
|
|
212
214
|
"NPM_ERROR" /* NPM_ERROR */
|
|
213
215
|
);
|
|
214
216
|
}
|
|
215
|
-
const manifestPath =
|
|
216
|
-
tempDir,
|
|
217
|
-
"package",
|
|
218
|
-
".minto-plugin",
|
|
217
|
+
const manifestPath = resolveManifestPath(
|
|
218
|
+
join(tempDir, "package"),
|
|
219
219
|
"marketplace.json"
|
|
220
220
|
);
|
|
221
221
|
return loadManifestFromPath(manifestPath);
|
|
@@ -237,9 +237,16 @@ function fetchDirectoryMarketplace(dirPath) {
|
|
|
237
237
|
} else if (!isAbsolute(dirPath)) {
|
|
238
238
|
resolvedPath = resolve(getCwd(), dirPath);
|
|
239
239
|
}
|
|
240
|
-
const manifestPath =
|
|
240
|
+
const manifestPath = resolveManifestPath(resolvedPath, "marketplace.json");
|
|
241
241
|
return loadManifestFromPath(manifestPath);
|
|
242
242
|
}
|
|
243
|
+
function resolveManifestPath(baseDir, filename) {
|
|
244
|
+
const mintoPath = join(baseDir, ".minto-plugin", filename);
|
|
245
|
+
if (existsSync(mintoPath)) return mintoPath;
|
|
246
|
+
const claudePath = join(baseDir, ".claude-plugin", filename);
|
|
247
|
+
if (existsSync(claudePath)) return claudePath;
|
|
248
|
+
return mintoPath;
|
|
249
|
+
}
|
|
243
250
|
function loadManifestFromPath(manifestPath) {
|
|
244
251
|
if (!existsSync(manifestPath)) {
|
|
245
252
|
throw new SkillMarketplaceError(
|
|
@@ -524,8 +531,9 @@ function validatePluginPath(path) {
|
|
|
524
531
|
return { isValid: false, errors };
|
|
525
532
|
}
|
|
526
533
|
const mintoManifest = join(resolvedPath, ".minto-plugin", "plugin.json");
|
|
534
|
+
const claudeManifest = join(resolvedPath, ".claude-plugin", "plugin.json");
|
|
527
535
|
const rootManifest = join(resolvedPath, "plugin.json");
|
|
528
|
-
if (!existsSync(mintoManifest) && !existsSync(rootManifest)) {
|
|
536
|
+
if (!existsSync(mintoManifest) && !existsSync(claudeManifest) && !existsSync(rootManifest)) {
|
|
529
537
|
errors.push(
|
|
530
538
|
"No plugin manifest found. Expected .minto-plugin/plugin.json or plugin.json"
|
|
531
539
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/services/plugins/skillMarketplace.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Skill Marketplace Service\n *\n * Implements Claude Code CLI compatible marketplace functionality\n * for plugin/skill discovery and installation.\n *\n * Supports all Claude Code CLI source types:\n * - github: owner/repo format\n * - git: generic git URL\n * - url: HTTP/HTTPS URL to marketplace JSON\n * - npm: npm package name\n * - file: local file path\n * - directory: local directory path\n */\n\nimport { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'fs'\nimport { join, resolve, isAbsolute } from 'path'\nimport { homedir } from 'os'\nimport { z } from 'zod'\nimport { execFileNoThrow } from '@utils/execFileNoThrow'\nimport { getCwd } from '@utils/state'\nimport type {\n SessionPlugin,\n PluginScope,\n InstalledSkillPlugin,\n} from '@minto-types/plugin'\n\n/**\n * Marketplace source types (Claude Code CLI compatible)\n */\nexport type MarketplaceSource =\n | { source: 'github'; repo: string; ref?: string; path?: string }\n | { source: 'git'; url: string; ref?: string; path?: string }\n | { source: 'url'; url: string; headers?: Record<string, string> }\n | { source: 'npm'; package: string; version?: string }\n | { source: 'file'; path: string }\n | { source: 'directory'; path: string }\n\n/**\n * Plugin entry schema in a marketplace manifest\n */\nconst PluginEntrySchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n source: z.string().optional().default('./'),\n strict: z.boolean().optional(),\n skills: z.union([z.string(), z.array(z.string())]).optional(),\n commands: z.union([z.string(), z.array(z.string())]).optional(),\n agents: z.union([z.string(), z.array(z.string())]).optional(),\n hooks: z.union([z.string(), z.array(z.string())]).optional(),\n mcpServers: z.union([z.string(), z.array(z.string())]).optional(),\n})\n\n/**\n * Plugin entry type (inferred from schema)\n */\nexport type PluginEntry = z.infer<typeof PluginEntrySchema>\n\n/**\n * Marketplace manifest schema (Claude Code CLI compatible)\n */\nexport const MarketplaceManifestSchema = z.object({\n $schema: z.string().optional(),\n name: z.string().min(1),\n description: z.string().optional(),\n owner: z\n .object({\n name: z.string().optional(),\n email: z.string().email().optional(),\n })\n .optional(),\n metadata: z.record(z.unknown()).optional(),\n plugins: z.array(PluginEntrySchema),\n})\n\nexport type MarketplaceManifest = z.infer<typeof MarketplaceManifestSchema>\n\n/**\n * Registered marketplace in user config\n */\nexport interface RegisteredMarketplace {\n name: string\n source: MarketplaceSource\n manifest: MarketplaceManifest\n lastUpdated: Date\n enabled: boolean\n}\n\n/**\n * Marketplace error codes\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 NPM_ERROR = 'NPM_ERROR',\n ALREADY_REGISTERED = 'ALREADY_REGISTERED',\n NOT_REGISTERED = 'NOT_REGISTERED',\n PLUGIN_NOT_FOUND = 'PLUGIN_NOT_FOUND',\n INSTALLATION_FAILED = 'INSTALLATION_FAILED',\n}\n\n/**\n * Marketplace error class\n */\nexport class SkillMarketplaceError extends Error {\n constructor(\n message: string,\n public code: MarketplaceErrorCode,\n public marketplaceName?: string,\n public details?: unknown,\n ) {\n super(message)\n this.name = 'SkillMarketplaceError'\n }\n}\n\n/**\n * Get marketplace storage directory\n */\nfunction getMarketplaceDir(): string {\n const home = homedir()\n const dir = join(home, '.minto', 'marketplaces')\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n\n return dir\n}\n\n/**\n * Get installed plugins storage directory based on scope\n */\nfunction getInstalledPluginsDir(scope: PluginScope): string {\n const base =\n scope === 'user'\n ? join(homedir(), '.minto', 'plugins')\n : join(getCwd(), '.minto', 'plugins')\n\n if (!existsSync(base)) {\n mkdirSync(base, { recursive: true })\n }\n\n return base\n}\n\n/**\n * Get registry file path\n */\nfunction getRegistryPath(): string {\n return join(getMarketplaceDir(), 'registry.json')\n}\n\n/**\n * Get installed skills registry path\n */\nfunction getInstalledSkillsPath(): string {\n return join(getMarketplaceDir(), 'installed-skills.json')\n}\n\n/**\n * Load marketplace registry\n */\nfunction loadRegistry(): RegisteredMarketplace[] {\n const registryPath = getRegistryPath()\n\n if (!existsSync(registryPath)) {\n return []\n }\n\n try {\n const content = readFileSync(registryPath, 'utf-8')\n return JSON.parse(content)\n } catch {\n return []\n }\n}\n\n/**\n * Save marketplace registry\n */\nfunction saveRegistry(marketplaces: RegisteredMarketplace[]): void {\n const registryPath = getRegistryPath()\n writeFileSync(registryPath, JSON.stringify(marketplaces, null, 2), 'utf-8')\n}\n\n/**\n * Load installed skills registry\n */\nexport function loadInstalledSkills(): InstalledSkillPlugin[] {\n const path = getInstalledSkillsPath()\n\n if (!existsSync(path)) {\n return []\n }\n\n try {\n const content = readFileSync(path, 'utf-8')\n return JSON.parse(content)\n } catch {\n return []\n }\n}\n\n/**\n * Save installed skills registry\n */\nfunction saveInstalledSkills(skills: InstalledSkillPlugin[]): void {\n const path = getInstalledSkillsPath()\n writeFileSync(path, JSON.stringify(skills, null, 2), 'utf-8')\n}\n\n/**\n * Parse marketplace source from string\n */\nexport function parseMarketplaceSource(input: string): MarketplaceSource {\n // GitHub shorthand: owner/repo\n if (/^[\\w-]+\\/[\\w-]+$/.test(input)) {\n return { source: 'github', repo: input }\n }\n\n // GitHub URL\n if (input.includes('github.com')) {\n const match = input.match(/github\\.com[/:]([\\w-]+\\/[\\w-]+)/)\n if (match) {\n return { source: 'github', repo: match[1] }\n }\n }\n\n // NPM package (starts with @ or contains no slashes and not a URL)\n if (\n input.startsWith('@') ||\n (input.startsWith('npm:') && !input.includes('/'))\n ) {\n const pkg = input.replace(/^npm:/, '')\n return { source: 'npm', package: pkg }\n }\n\n // Git URL (http/https with .git or git://)\n if (\n input.startsWith('git://') ||\n (input.startsWith('http') && input.endsWith('.git'))\n ) {\n return { source: 'git', url: input }\n }\n\n // HTTP/HTTPS URL\n if (input.startsWith('http://') || input.startsWith('https://')) {\n return { source: 'url', url: input }\n }\n\n // File path (starts with .)\n if (input.startsWith('./') || input.startsWith('../')) {\n return { source: 'file', path: input }\n }\n\n // Directory (absolute path or relative)\n if (input.startsWith('/') || input.startsWith('~')) {\n return { source: 'directory', path: input }\n }\n\n // Default to directory\n return { source: 'directory', path: input }\n}\n\n/**\n * Fetch marketplace manifest from GitHub\n */\nasync function fetchGitHubMarketplace(\n repo: string,\n ref?: string,\n path?: string,\n): Promise<MarketplaceManifest> {\n const url = `https://github.com/${repo}.git`\n const tempDir = await cloneGitRepo(url, ref)\n\n try {\n const manifestPath = path\n ? join(tempDir, path, '.minto-plugin', 'marketplace.json')\n : join(tempDir, '.minto-plugin', 'marketplace.json')\n\n return loadManifestFromPath(manifestPath)\n } finally {\n // Clean up temp directory\n try {\n rmSync(tempDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Fetch marketplace manifest from git URL\n */\nasync function fetchGitMarketplace(\n url: string,\n ref?: string,\n path?: string,\n): Promise<MarketplaceManifest> {\n const tempDir = await cloneGitRepo(url, ref)\n\n try {\n const manifestPath = path\n ? join(tempDir, path, '.minto-plugin', 'marketplace.json')\n : join(tempDir, '.minto-plugin', 'marketplace.json')\n\n return loadManifestFromPath(manifestPath)\n } finally {\n try {\n rmSync(tempDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Fetch marketplace manifest from URL\n */\nasync function fetchUrlMarketplace(\n url: string,\n headers?: Record<string, string>,\n): Promise<MarketplaceManifest> {\n try {\n const response = await fetch(url, { headers })\n\n if (!response.ok) {\n throw new SkillMarketplaceError(\n `HTTP ${response.status}: ${response.statusText}`,\n MarketplaceErrorCode.NETWORK_ERROR,\n )\n }\n\n const data = await response.json()\n const parsed = MarketplaceManifestSchema.safeParse(data)\n\n if (!parsed.success) {\n throw new SkillMarketplaceError(\n `Invalid manifest: ${parsed.error.message}`,\n MarketplaceErrorCode.MANIFEST_INVALID,\n )\n }\n\n return parsed.data\n } catch (error) {\n if (error instanceof SkillMarketplaceError) throw error\n\n throw new SkillMarketplaceError(\n `Failed to fetch manifest from URL: ${error instanceof Error ? error.message : String(error)}`,\n MarketplaceErrorCode.NETWORK_ERROR,\n undefined,\n error,\n )\n }\n}\n\n/**\n * Fetch marketplace manifest from NPM\n */\nasync function fetchNpmMarketplace(\n packageName: string,\n version?: string,\n): Promise<MarketplaceManifest> {\n const tempDir = join(getMarketplaceDir(), 'temp', Date.now().toString())\n mkdirSync(tempDir, { recursive: true })\n\n try {\n const pkgSpec = version ? `${packageName}@${version}` : packageName\n const result = await execFileNoThrow('npm', [\n 'pack',\n pkgSpec,\n '--pack-destination',\n tempDir,\n ])\n\n if (result.code !== 0) {\n throw new SkillMarketplaceError(\n `NPM pack failed: ${result.stderr || result.stdout}`,\n MarketplaceErrorCode.NPM_ERROR,\n )\n }\n\n // Extract the tarball\n const tarballName = result.stdout.trim().split('\\n').pop() || ''\n const tarballPath = join(tempDir, tarballName)\n\n const extractResult = await execFileNoThrow('tar', [\n '-xzf',\n tarballPath,\n '-C',\n tempDir,\n ])\n if (extractResult.code !== 0) {\n throw new SkillMarketplaceError(\n `Failed to extract NPM package: ${extractResult.stderr}`,\n MarketplaceErrorCode.NPM_ERROR,\n )\n }\n\n // Look for marketplace manifest in package\n const manifestPath = join(\n tempDir,\n 'package',\n '.minto-plugin',\n 'marketplace.json',\n )\n return loadManifestFromPath(manifestPath)\n } finally {\n try {\n rmSync(tempDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Load marketplace manifest from local file\n */\nfunction fetchFileMarketplace(filePath: string): MarketplaceManifest {\n const resolvedPath = isAbsolute(filePath)\n ? filePath\n : resolve(getCwd(), filePath)\n\n return loadManifestFromPath(resolvedPath)\n}\n\n/**\n * Load marketplace manifest from local directory\n */\nfunction fetchDirectoryMarketplace(dirPath: string): MarketplaceManifest {\n let resolvedPath = dirPath\n\n // Handle ~ expansion\n if (dirPath.startsWith('~')) {\n resolvedPath = join(homedir(), dirPath.slice(1))\n } else if (!isAbsolute(dirPath)) {\n resolvedPath = resolve(getCwd(), dirPath)\n }\n\n const manifestPath = join(resolvedPath, '.minto-plugin', 'marketplace.json')\n return loadManifestFromPath(manifestPath)\n}\n\n/**\n * Load manifest from file path\n */\nfunction loadManifestFromPath(manifestPath: string): MarketplaceManifest {\n if (!existsSync(manifestPath)) {\n throw new SkillMarketplaceError(\n `Marketplace manifest not found at ${manifestPath}`,\n MarketplaceErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n\n try {\n const content = readFileSync(manifestPath, 'utf-8')\n const data = JSON.parse(content)\n const parsed = MarketplaceManifestSchema.safeParse(data)\n\n if (!parsed.success) {\n throw new SkillMarketplaceError(\n `Invalid manifest: ${parsed.error.message}`,\n MarketplaceErrorCode.MANIFEST_INVALID,\n )\n }\n\n return parsed.data\n } catch (error) {\n if (error instanceof SkillMarketplaceError) throw error\n\n throw new SkillMarketplaceError(\n `Failed to load manifest: ${error instanceof Error ? error.message : String(error)}`,\n MarketplaceErrorCode.MANIFEST_INVALID,\n undefined,\n error,\n )\n }\n}\n\n/**\n * Clone git repository\n */\nasync function cloneGitRepo(url: string, ref?: string): Promise<string> {\n const tempDir = join(getMarketplaceDir(), 'temp', Date.now().toString())\n mkdirSync(tempDir, { recursive: true })\n\n try {\n const args = ['clone', '--depth', '1']\n\n if (ref) {\n args.push('--branch', ref)\n }\n\n args.push(url, tempDir)\n\n const result = await execFileNoThrow('git', args)\n\n if (result.code !== 0) {\n throw new SkillMarketplaceError(\n `Git clone failed: ${result.stderr || result.stdout}`,\n MarketplaceErrorCode.GIT_ERROR,\n )\n }\n\n return tempDir\n } catch (error) {\n try {\n rmSync(tempDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n\n if (error instanceof SkillMarketplaceError) throw error\n\n throw new SkillMarketplaceError(\n `Git clone failed: ${error instanceof Error ? error.message : String(error)}`,\n MarketplaceErrorCode.GIT_ERROR,\n undefined,\n error,\n )\n }\n}\n\n/**\n * Fetch marketplace manifest based on source type\n */\nexport async function fetchMarketplaceManifest(\n source: MarketplaceSource,\n): Promise<MarketplaceManifest> {\n switch (source.source) {\n case 'github':\n return fetchGitHubMarketplace(source.repo, source.ref, source.path)\n case 'git':\n return fetchGitMarketplace(source.url, source.ref, source.path)\n case 'url':\n return fetchUrlMarketplace(source.url, source.headers)\n case 'npm':\n return fetchNpmMarketplace(source.package, source.version)\n case 'file':\n return fetchFileMarketplace(source.path)\n case 'directory':\n return fetchDirectoryMarketplace(source.path)\n default:\n throw new SkillMarketplaceError(\n `Unknown source type: ${(source as any).source}`,\n MarketplaceErrorCode.MANIFEST_INVALID,\n )\n }\n}\n\n/**\n * Add/register a marketplace\n */\nexport async function addMarketplace(\n input: string | MarketplaceSource,\n): Promise<RegisteredMarketplace> {\n const source =\n typeof input === 'string' ? parseMarketplaceSource(input) : input\n\n const manifest = await fetchMarketplaceManifest(source)\n\n // Check if already registered\n const registry = loadRegistry()\n if (registry.some(m => m.name === manifest.name)) {\n throw new SkillMarketplaceError(\n `Marketplace \"${manifest.name}\" is already registered`,\n MarketplaceErrorCode.ALREADY_REGISTERED,\n manifest.name,\n )\n }\n\n const registered: RegisteredMarketplace = {\n name: manifest.name,\n source,\n manifest,\n lastUpdated: new Date(),\n enabled: true,\n }\n\n registry.push(registered)\n saveRegistry(registry)\n\n return registered\n}\n\n/**\n * Remove a marketplace\n */\nexport function removeMarketplace(name: string): void {\n const registry = loadRegistry()\n const filtered = registry.filter(m => m.name !== name)\n\n if (filtered.length === registry.length) {\n throw new SkillMarketplaceError(\n `Marketplace \"${name}\" is not registered`,\n MarketplaceErrorCode.NOT_REGISTERED,\n name,\n )\n }\n\n saveRegistry(filtered)\n}\n\n/**\n * Update marketplace manifest\n */\nexport async function updateMarketplace(\n name: string,\n): Promise<RegisteredMarketplace> {\n const registry = loadRegistry()\n const marketplace = registry.find(m => m.name === name)\n\n if (!marketplace) {\n throw new SkillMarketplaceError(\n `Marketplace \"${name}\" is not registered`,\n MarketplaceErrorCode.NOT_REGISTERED,\n name,\n )\n }\n\n const manifest = await fetchMarketplaceManifest(marketplace.source)\n\n marketplace.manifest = manifest\n marketplace.lastUpdated = new Date()\n\n saveRegistry(registry)\n\n return marketplace\n}\n\n/**\n * List all registered marketplaces\n */\nexport function listMarketplaces(): RegisteredMarketplace[] {\n return loadRegistry()\n}\n\n/**\n * Get a specific marketplace\n */\nexport function getMarketplace(\n name: string,\n): RegisteredMarketplace | undefined {\n return loadRegistry().find(m => m.name === name)\n}\n\n/**\n * Find plugin in marketplaces\n */\nexport function findPlugin(\n pluginName: string,\n marketplaceName?: string,\n): { marketplace: RegisteredMarketplace; plugin: PluginEntry } | undefined {\n const registry = loadRegistry()\n\n if (marketplaceName) {\n const marketplace = registry.find(m => m.name === marketplaceName)\n if (!marketplace) return undefined\n\n const plugin = marketplace.manifest.plugins.find(p => p.name === pluginName)\n if (!plugin) return undefined\n\n return { marketplace, plugin }\n }\n\n for (const marketplace of registry) {\n if (!marketplace.enabled) continue\n\n const plugin = marketplace.manifest.plugins.find(p => p.name === pluginName)\n if (plugin) {\n return { marketplace, plugin }\n }\n }\n\n return undefined\n}\n\n/**\n * Install plugin from marketplace\n */\nexport async function installPlugin(\n pluginName: string,\n options: {\n marketplace?: string\n scope?: PluginScope\n force?: boolean\n } = {},\n): Promise<string> {\n const {\n marketplace: marketplaceName,\n scope = 'user',\n force = false,\n } = options\n\n const found = findPlugin(pluginName, marketplaceName)\n\n if (!found) {\n throw new SkillMarketplaceError(\n marketplaceName\n ? `Plugin \"${pluginName}\" not found in marketplace \"${marketplaceName}\"`\n : `Plugin \"${pluginName}\" not found in any marketplace`,\n MarketplaceErrorCode.PLUGIN_NOT_FOUND,\n )\n }\n\n const { marketplace, plugin } = found\n const installDir = join(getInstalledPluginsDir(scope), plugin.name)\n\n // Check if already installed\n if (existsSync(installDir) && !force) {\n throw new SkillMarketplaceError(\n `Plugin \"${pluginName}\" is already installed. Use --force to reinstall.`,\n MarketplaceErrorCode.ALREADY_REGISTERED,\n pluginName,\n )\n }\n\n // Remove existing if force\n if (existsSync(installDir) && force) {\n rmSync(installDir, { recursive: true, force: true })\n }\n\n // Create installation directory\n mkdirSync(installDir, { recursive: true })\n\n try {\n // Install based on marketplace source type and plugin source\n // For now, we'll use the marketplace manager's implementation\n // This will be enhanced in future iterations\n\n // Record installation\n const installed = loadInstalledSkills()\n const record: InstalledSkillPlugin = {\n plugin: plugin.name,\n marketplace: marketplace.name,\n scope,\n isEnabled: true,\n installedAt: new Date().toISOString(),\n pluginRoot: installDir,\n skills:\n typeof plugin.skills === 'string'\n ? [plugin.skills]\n : plugin.skills || [],\n commands:\n typeof plugin.commands === 'string'\n ? [plugin.commands]\n : plugin.commands || [],\n sourceMarketplacePath:\n marketplace.source.source === 'directory'\n ? (marketplace.source as { source: 'directory'; path: string }).path\n : marketplace.name,\n }\n\n // Remove existing record if present\n const filtered = installed.filter(\n i => !(i.plugin === plugin.name && i.scope === scope),\n )\n filtered.push(record)\n saveInstalledSkills(filtered)\n\n return installDir\n } catch (error) {\n // Clean up on failure\n try {\n rmSync(installDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n\n throw new SkillMarketplaceError(\n `Failed to install plugin: ${error instanceof Error ? error.message : String(error)}`,\n MarketplaceErrorCode.INSTALLATION_FAILED,\n pluginName,\n error,\n )\n }\n}\n\n/**\n * Uninstall plugin\n */\nexport function uninstallPlugin(\n pluginName: string,\n scope: PluginScope = 'user',\n): void {\n const installed = loadInstalledSkills()\n const record = installed.find(\n i => i.plugin === pluginName && i.scope === scope,\n )\n\n if (!record) {\n throw new SkillMarketplaceError(\n `Plugin \"${pluginName}\" is not installed in ${scope} scope`,\n MarketplaceErrorCode.PLUGIN_NOT_FOUND,\n pluginName,\n )\n }\n\n // Remove plugin directory\n if (record.pluginRoot && existsSync(record.pluginRoot)) {\n rmSync(record.pluginRoot, { recursive: true, force: true })\n }\n\n // Update registry\n const filtered = installed.filter(\n i => !(i.plugin === pluginName && i.scope === scope),\n )\n saveInstalledSkills(filtered)\n}\n\n/**\n * Enable plugin\n */\nexport function enablePlugin(\n pluginName: string,\n scope: PluginScope = 'user',\n): void {\n const installed = loadInstalledSkills()\n const record = installed.find(\n i => i.plugin === pluginName && i.scope === scope,\n )\n\n if (!record) {\n throw new SkillMarketplaceError(\n `Plugin \"${pluginName}\" is not installed in ${scope} scope`,\n MarketplaceErrorCode.PLUGIN_NOT_FOUND,\n pluginName,\n )\n }\n\n record.isEnabled = true\n saveInstalledSkills(installed)\n}\n\n/**\n * Disable plugin\n */\nexport function disablePlugin(\n pluginName: string,\n scope: PluginScope = 'user',\n): void {\n const installed = loadInstalledSkills()\n const record = installed.find(\n i => i.plugin === pluginName && i.scope === scope,\n )\n\n if (!record) {\n throw new SkillMarketplaceError(\n `Plugin \"${pluginName}\" is not installed in ${scope} scope`,\n MarketplaceErrorCode.PLUGIN_NOT_FOUND,\n pluginName,\n )\n }\n\n record.isEnabled = false\n saveInstalledSkills(installed)\n}\n\n/**\n * List installed plugins\n */\nexport function listInstalledPlugins(\n scope?: PluginScope,\n): InstalledSkillPlugin[] {\n const installed = loadInstalledSkills()\n\n if (scope) {\n return installed.filter(i => i.scope === scope)\n }\n\n return installed\n}\n\n/**\n * Validate marketplace manifest\n */\nexport function validateMarketplaceManifest(data: unknown): {\n success: boolean\n data?: MarketplaceManifest\n error?: string\n} {\n const result = MarketplaceManifestSchema.safeParse(data)\n\n if (result.success) {\n return { success: true, data: result.data }\n }\n\n return { success: false, error: result.error.message }\n}\n\n/**\n * Validate plugin path (used for local validation)\n */\nexport function validatePluginPath(path: string): {\n isValid: boolean\n errors: string[]\n} {\n const errors: string[] = []\n\n const resolvedPath = isAbsolute(path) ? path : resolve(getCwd(), path)\n\n if (!existsSync(resolvedPath)) {\n errors.push(`Path does not exist: ${resolvedPath}`)\n return { isValid: false, errors }\n }\n\n // Check for plugin manifest\n const mintoManifest = join(resolvedPath, '.minto-plugin', 'plugin.json')\n const rootManifest = join(resolvedPath, 'plugin.json')\n\n if (!existsSync(mintoManifest) && !existsSync(rootManifest)) {\n errors.push(\n 'No plugin manifest found. Expected .minto-plugin/plugin.json or plugin.json',\n )\n }\n\n return { isValid: errors.length === 0, errors }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Skill Marketplace Service\n *\n * Implements Claude Code CLI compatible marketplace functionality\n * for plugin/skill discovery and installation.\n *\n * Supports all Claude Code CLI source types:\n * - github: owner/repo format\n * - git: generic git URL\n * - url: HTTP/HTTPS URL to marketplace JSON\n * - npm: npm package name\n * - file: local file path\n * - directory: local directory path\n */\n\nimport { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'fs'\nimport { join, resolve, isAbsolute } from 'path'\nimport { homedir } from 'os'\nimport { z } from 'zod'\nimport { execFileNoThrow } from '@utils/execFileNoThrow'\nimport { getCwd } from '@utils/state'\nimport type {\n SessionPlugin,\n PluginScope,\n InstalledSkillPlugin,\n} from '@minto-types/plugin'\n\n/**\n * Marketplace source types (Claude Code CLI compatible)\n */\nexport type MarketplaceSource =\n | { source: 'github'; repo: string; ref?: string; path?: string }\n | { source: 'git'; url: string; ref?: string; path?: string }\n | { source: 'url'; url: string; headers?: Record<string, string> }\n | { source: 'npm'; package: string; version?: string }\n | { source: 'file'; path: string }\n | { source: 'directory'; path: string }\n\n/**\n * Plugin entry schema in a marketplace manifest\n */\nconst PluginEntrySchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n source: z.string().optional().default('./'),\n strict: z.boolean().optional(),\n skills: z.union([z.string(), z.array(z.string())]).optional(),\n commands: z.union([z.string(), z.array(z.string())]).optional(),\n agents: z.union([z.string(), z.array(z.string())]).optional(),\n hooks: z.union([z.string(), z.array(z.string())]).optional(),\n mcpServers: z.union([z.string(), z.array(z.string())]).optional(),\n})\n\n/**\n * Plugin entry type (inferred from schema)\n */\nexport type PluginEntry = z.infer<typeof PluginEntrySchema>\n\n/**\n * Marketplace manifest schema (Claude Code CLI compatible)\n */\nexport const MarketplaceManifestSchema = z.object({\n $schema: z.string().optional(),\n name: z.string().min(1),\n description: z.string().optional(),\n owner: z\n .object({\n name: z.string().optional(),\n email: z.string().email().optional(),\n })\n .optional(),\n metadata: z.record(z.unknown()).optional(),\n plugins: z.array(PluginEntrySchema),\n})\n\nexport type MarketplaceManifest = z.infer<typeof MarketplaceManifestSchema>\n\n/**\n * Registered marketplace in user config\n */\nexport interface RegisteredMarketplace {\n name: string\n source: MarketplaceSource\n manifest: MarketplaceManifest\n lastUpdated: Date\n enabled: boolean\n}\n\n/**\n * Marketplace error codes\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 NPM_ERROR = 'NPM_ERROR',\n ALREADY_REGISTERED = 'ALREADY_REGISTERED',\n NOT_REGISTERED = 'NOT_REGISTERED',\n PLUGIN_NOT_FOUND = 'PLUGIN_NOT_FOUND',\n INSTALLATION_FAILED = 'INSTALLATION_FAILED',\n}\n\n/**\n * Marketplace error class\n */\nexport class SkillMarketplaceError extends Error {\n constructor(\n message: string,\n public code: MarketplaceErrorCode,\n public marketplaceName?: string,\n public details?: unknown,\n ) {\n super(message)\n this.name = 'SkillMarketplaceError'\n }\n}\n\n/**\n * Get marketplace storage directory\n */\nfunction getMarketplaceDir(): string {\n const home = homedir()\n const dir = join(home, '.minto', 'marketplaces')\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n\n return dir\n}\n\n/**\n * Get installed plugins storage directory based on scope\n */\nfunction getInstalledPluginsDir(scope: PluginScope): string {\n const base =\n scope === 'user'\n ? join(homedir(), '.minto', 'plugins')\n : join(getCwd(), '.minto', 'plugins')\n\n if (!existsSync(base)) {\n mkdirSync(base, { recursive: true })\n }\n\n return base\n}\n\n/**\n * Get registry file path\n */\nfunction getRegistryPath(): string {\n return join(getMarketplaceDir(), 'registry.json')\n}\n\n/**\n * Get installed skills registry path\n */\nfunction getInstalledSkillsPath(): string {\n return join(getMarketplaceDir(), 'installed-skills.json')\n}\n\n/**\n * Load marketplace registry\n */\nfunction loadRegistry(): RegisteredMarketplace[] {\n const registryPath = getRegistryPath()\n\n if (!existsSync(registryPath)) {\n return []\n }\n\n try {\n const content = readFileSync(registryPath, 'utf-8')\n return JSON.parse(content)\n } catch {\n return []\n }\n}\n\n/**\n * Save marketplace registry\n */\nfunction saveRegistry(marketplaces: RegisteredMarketplace[]): void {\n const registryPath = getRegistryPath()\n writeFileSync(registryPath, JSON.stringify(marketplaces, null, 2), 'utf-8')\n}\n\n/**\n * Load installed skills registry\n */\nexport function loadInstalledSkills(): InstalledSkillPlugin[] {\n const path = getInstalledSkillsPath()\n\n if (!existsSync(path)) {\n return []\n }\n\n try {\n const content = readFileSync(path, 'utf-8')\n return JSON.parse(content)\n } catch {\n return []\n }\n}\n\n/**\n * Save installed skills registry\n */\nfunction saveInstalledSkills(skills: InstalledSkillPlugin[]): void {\n const path = getInstalledSkillsPath()\n writeFileSync(path, JSON.stringify(skills, null, 2), 'utf-8')\n}\n\n/**\n * Parse marketplace source from string\n */\nexport function parseMarketplaceSource(input: string): MarketplaceSource {\n // GitHub shorthand: owner/repo\n if (/^[\\w-]+\\/[\\w-]+$/.test(input)) {\n return { source: 'github', repo: input }\n }\n\n // GitHub URL\n if (input.includes('github.com')) {\n const match = input.match(/github\\.com[/:]([\\w-]+\\/[\\w-]+)/)\n if (match) {\n return { source: 'github', repo: match[1] }\n }\n }\n\n // NPM package (starts with @ or contains no slashes and not a URL)\n if (\n input.startsWith('@') ||\n (input.startsWith('npm:') && !input.includes('/'))\n ) {\n const pkg = input.replace(/^npm:/, '')\n return { source: 'npm', package: pkg }\n }\n\n // Git URL (http/https with .git or git://)\n if (\n input.startsWith('git://') ||\n (input.startsWith('http') && input.endsWith('.git'))\n ) {\n return { source: 'git', url: input }\n }\n\n // HTTP/HTTPS URL\n if (input.startsWith('http://') || input.startsWith('https://')) {\n return { source: 'url', url: input }\n }\n\n // File path (starts with .)\n if (input.startsWith('./') || input.startsWith('../')) {\n return { source: 'file', path: input }\n }\n\n // Directory (absolute path or relative)\n if (input.startsWith('/') || input.startsWith('~')) {\n return { source: 'directory', path: input }\n }\n\n // Default to directory\n return { source: 'directory', path: input }\n}\n\n/**\n * Fetch marketplace manifest from GitHub\n */\nasync function fetchGitHubMarketplace(\n repo: string,\n ref?: string,\n path?: string,\n): Promise<MarketplaceManifest> {\n const url = `https://github.com/${repo}.git`\n const tempDir = await cloneGitRepo(url, ref)\n\n try {\n const baseDir = path ? join(tempDir, path) : tempDir\n const manifestPath = resolveManifestPath(baseDir, 'marketplace.json')\n\n return loadManifestFromPath(manifestPath)\n } finally {\n // Clean up temp directory\n try {\n rmSync(tempDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Fetch marketplace manifest from git URL\n */\nasync function fetchGitMarketplace(\n url: string,\n ref?: string,\n path?: string,\n): Promise<MarketplaceManifest> {\n const tempDir = await cloneGitRepo(url, ref)\n\n try {\n const baseDir = path ? join(tempDir, path) : tempDir\n const manifestPath = resolveManifestPath(baseDir, 'marketplace.json')\n\n return loadManifestFromPath(manifestPath)\n } finally {\n try {\n rmSync(tempDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Fetch marketplace manifest from URL\n */\nasync function fetchUrlMarketplace(\n url: string,\n headers?: Record<string, string>,\n): Promise<MarketplaceManifest> {\n try {\n const response = await fetch(url, { headers })\n\n if (!response.ok) {\n throw new SkillMarketplaceError(\n `HTTP ${response.status}: ${response.statusText}`,\n MarketplaceErrorCode.NETWORK_ERROR,\n )\n }\n\n const data = await response.json()\n const parsed = MarketplaceManifestSchema.safeParse(data)\n\n if (!parsed.success) {\n throw new SkillMarketplaceError(\n `Invalid manifest: ${parsed.error.message}`,\n MarketplaceErrorCode.MANIFEST_INVALID,\n )\n }\n\n return parsed.data\n } catch (error) {\n if (error instanceof SkillMarketplaceError) throw error\n\n throw new SkillMarketplaceError(\n `Failed to fetch manifest from URL: ${error instanceof Error ? error.message : String(error)}`,\n MarketplaceErrorCode.NETWORK_ERROR,\n undefined,\n error,\n )\n }\n}\n\n/**\n * Fetch marketplace manifest from NPM\n */\nasync function fetchNpmMarketplace(\n packageName: string,\n version?: string,\n): Promise<MarketplaceManifest> {\n const tempDir = join(getMarketplaceDir(), 'temp', Date.now().toString())\n mkdirSync(tempDir, { recursive: true })\n\n try {\n const pkgSpec = version ? `${packageName}@${version}` : packageName\n const result = await execFileNoThrow('npm', [\n 'pack',\n pkgSpec,\n '--pack-destination',\n tempDir,\n ])\n\n if (result.code !== 0) {\n throw new SkillMarketplaceError(\n `NPM pack failed: ${result.stderr || result.stdout}`,\n MarketplaceErrorCode.NPM_ERROR,\n )\n }\n\n // Extract the tarball\n const tarballName = result.stdout.trim().split('\\n').pop() || ''\n const tarballPath = join(tempDir, tarballName)\n\n const extractResult = await execFileNoThrow('tar', [\n '-xzf',\n tarballPath,\n '-C',\n tempDir,\n ])\n if (extractResult.code !== 0) {\n throw new SkillMarketplaceError(\n `Failed to extract NPM package: ${extractResult.stderr}`,\n MarketplaceErrorCode.NPM_ERROR,\n )\n }\n\n // Look for marketplace manifest in package\n const manifestPath = resolveManifestPath(\n join(tempDir, 'package'),\n 'marketplace.json',\n )\n return loadManifestFromPath(manifestPath)\n } finally {\n try {\n rmSync(tempDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Load marketplace manifest from local file\n */\nfunction fetchFileMarketplace(filePath: string): MarketplaceManifest {\n const resolvedPath = isAbsolute(filePath)\n ? filePath\n : resolve(getCwd(), filePath)\n\n return loadManifestFromPath(resolvedPath)\n}\n\n/**\n * Load marketplace manifest from local directory\n */\nfunction fetchDirectoryMarketplace(dirPath: string): MarketplaceManifest {\n let resolvedPath = dirPath\n\n // Handle ~ expansion\n if (dirPath.startsWith('~')) {\n resolvedPath = join(homedir(), dirPath.slice(1))\n } else if (!isAbsolute(dirPath)) {\n resolvedPath = resolve(getCwd(), dirPath)\n }\n\n const manifestPath = resolveManifestPath(resolvedPath, 'marketplace.json')\n return loadManifestFromPath(manifestPath)\n}\n\n/**\n * Resolve manifest file path, checking .minto-plugin then .claude-plugin (legacy)\n */\nfunction resolveManifestPath(baseDir: string, filename: string): string {\n const mintoPath = join(baseDir, '.minto-plugin', filename)\n if (existsSync(mintoPath)) return mintoPath\n\n const claudePath = join(baseDir, '.claude-plugin', filename)\n if (existsSync(claudePath)) return claudePath\n\n // Return .minto-plugin path as default (will produce a clear error message)\n return mintoPath\n}\n\n/**\n * Load manifest from file path\n */\nfunction loadManifestFromPath(manifestPath: string): MarketplaceManifest {\n if (!existsSync(manifestPath)) {\n throw new SkillMarketplaceError(\n `Marketplace manifest not found at ${manifestPath}`,\n MarketplaceErrorCode.MANIFEST_NOT_FOUND,\n )\n }\n\n try {\n const content = readFileSync(manifestPath, 'utf-8')\n const data = JSON.parse(content)\n const parsed = MarketplaceManifestSchema.safeParse(data)\n\n if (!parsed.success) {\n throw new SkillMarketplaceError(\n `Invalid manifest: ${parsed.error.message}`,\n MarketplaceErrorCode.MANIFEST_INVALID,\n )\n }\n\n return parsed.data\n } catch (error) {\n if (error instanceof SkillMarketplaceError) throw error\n\n throw new SkillMarketplaceError(\n `Failed to load manifest: ${error instanceof Error ? error.message : String(error)}`,\n MarketplaceErrorCode.MANIFEST_INVALID,\n undefined,\n error,\n )\n }\n}\n\n/**\n * Clone git repository\n */\nasync function cloneGitRepo(url: string, ref?: string): Promise<string> {\n const tempDir = join(getMarketplaceDir(), 'temp', Date.now().toString())\n mkdirSync(tempDir, { recursive: true })\n\n try {\n const args = ['clone', '--depth', '1']\n\n if (ref) {\n args.push('--branch', ref)\n }\n\n args.push(url, tempDir)\n\n const result = await execFileNoThrow('git', args)\n\n if (result.code !== 0) {\n throw new SkillMarketplaceError(\n `Git clone failed: ${result.stderr || result.stdout}`,\n MarketplaceErrorCode.GIT_ERROR,\n )\n }\n\n return tempDir\n } catch (error) {\n try {\n rmSync(tempDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n\n if (error instanceof SkillMarketplaceError) throw error\n\n throw new SkillMarketplaceError(\n `Git clone failed: ${error instanceof Error ? error.message : String(error)}`,\n MarketplaceErrorCode.GIT_ERROR,\n undefined,\n error,\n )\n }\n}\n\n/**\n * Fetch marketplace manifest based on source type\n */\nexport async function fetchMarketplaceManifest(\n source: MarketplaceSource,\n): Promise<MarketplaceManifest> {\n switch (source.source) {\n case 'github':\n return fetchGitHubMarketplace(source.repo, source.ref, source.path)\n case 'git':\n return fetchGitMarketplace(source.url, source.ref, source.path)\n case 'url':\n return fetchUrlMarketplace(source.url, source.headers)\n case 'npm':\n return fetchNpmMarketplace(source.package, source.version)\n case 'file':\n return fetchFileMarketplace(source.path)\n case 'directory':\n return fetchDirectoryMarketplace(source.path)\n default:\n throw new SkillMarketplaceError(\n `Unknown source type: ${(source as any).source}`,\n MarketplaceErrorCode.MANIFEST_INVALID,\n )\n }\n}\n\n/**\n * Add/register a marketplace\n */\nexport async function addMarketplace(\n input: string | MarketplaceSource,\n): Promise<RegisteredMarketplace> {\n const source =\n typeof input === 'string' ? parseMarketplaceSource(input) : input\n\n const manifest = await fetchMarketplaceManifest(source)\n\n // Check if already registered\n const registry = loadRegistry()\n if (registry.some(m => m.name === manifest.name)) {\n throw new SkillMarketplaceError(\n `Marketplace \"${manifest.name}\" is already registered`,\n MarketplaceErrorCode.ALREADY_REGISTERED,\n manifest.name,\n )\n }\n\n const registered: RegisteredMarketplace = {\n name: manifest.name,\n source,\n manifest,\n lastUpdated: new Date(),\n enabled: true,\n }\n\n registry.push(registered)\n saveRegistry(registry)\n\n return registered\n}\n\n/**\n * Remove a marketplace\n */\nexport function removeMarketplace(name: string): void {\n const registry = loadRegistry()\n const filtered = registry.filter(m => m.name !== name)\n\n if (filtered.length === registry.length) {\n throw new SkillMarketplaceError(\n `Marketplace \"${name}\" is not registered`,\n MarketplaceErrorCode.NOT_REGISTERED,\n name,\n )\n }\n\n saveRegistry(filtered)\n}\n\n/**\n * Update marketplace manifest\n */\nexport async function updateMarketplace(\n name: string,\n): Promise<RegisteredMarketplace> {\n const registry = loadRegistry()\n const marketplace = registry.find(m => m.name === name)\n\n if (!marketplace) {\n throw new SkillMarketplaceError(\n `Marketplace \"${name}\" is not registered`,\n MarketplaceErrorCode.NOT_REGISTERED,\n name,\n )\n }\n\n const manifest = await fetchMarketplaceManifest(marketplace.source)\n\n marketplace.manifest = manifest\n marketplace.lastUpdated = new Date()\n\n saveRegistry(registry)\n\n return marketplace\n}\n\n/**\n * List all registered marketplaces\n */\nexport function listMarketplaces(): RegisteredMarketplace[] {\n return loadRegistry()\n}\n\n/**\n * Get a specific marketplace\n */\nexport function getMarketplace(\n name: string,\n): RegisteredMarketplace | undefined {\n return loadRegistry().find(m => m.name === name)\n}\n\n/**\n * Find plugin in marketplaces\n */\nexport function findPlugin(\n pluginName: string,\n marketplaceName?: string,\n): { marketplace: RegisteredMarketplace; plugin: PluginEntry } | undefined {\n const registry = loadRegistry()\n\n if (marketplaceName) {\n const marketplace = registry.find(m => m.name === marketplaceName)\n if (!marketplace) return undefined\n\n const plugin = marketplace.manifest.plugins.find(p => p.name === pluginName)\n if (!plugin) return undefined\n\n return { marketplace, plugin }\n }\n\n for (const marketplace of registry) {\n if (!marketplace.enabled) continue\n\n const plugin = marketplace.manifest.plugins.find(p => p.name === pluginName)\n if (plugin) {\n return { marketplace, plugin }\n }\n }\n\n return undefined\n}\n\n/**\n * Install plugin from marketplace\n */\nexport async function installPlugin(\n pluginName: string,\n options: {\n marketplace?: string\n scope?: PluginScope\n force?: boolean\n } = {},\n): Promise<string> {\n const {\n marketplace: marketplaceName,\n scope = 'user',\n force = false,\n } = options\n\n const found = findPlugin(pluginName, marketplaceName)\n\n if (!found) {\n throw new SkillMarketplaceError(\n marketplaceName\n ? `Plugin \"${pluginName}\" not found in marketplace \"${marketplaceName}\"`\n : `Plugin \"${pluginName}\" not found in any marketplace`,\n MarketplaceErrorCode.PLUGIN_NOT_FOUND,\n )\n }\n\n const { marketplace, plugin } = found\n const installDir = join(getInstalledPluginsDir(scope), plugin.name)\n\n // Check if already installed\n if (existsSync(installDir) && !force) {\n throw new SkillMarketplaceError(\n `Plugin \"${pluginName}\" is already installed. Use --force to reinstall.`,\n MarketplaceErrorCode.ALREADY_REGISTERED,\n pluginName,\n )\n }\n\n // Remove existing if force\n if (existsSync(installDir) && force) {\n rmSync(installDir, { recursive: true, force: true })\n }\n\n // Create installation directory\n mkdirSync(installDir, { recursive: true })\n\n try {\n // Install based on marketplace source type and plugin source\n // For now, we'll use the marketplace manager's implementation\n // This will be enhanced in future iterations\n\n // Record installation\n const installed = loadInstalledSkills()\n const record: InstalledSkillPlugin = {\n plugin: plugin.name,\n marketplace: marketplace.name,\n scope,\n isEnabled: true,\n installedAt: new Date().toISOString(),\n pluginRoot: installDir,\n skills:\n typeof plugin.skills === 'string'\n ? [plugin.skills]\n : plugin.skills || [],\n commands:\n typeof plugin.commands === 'string'\n ? [plugin.commands]\n : plugin.commands || [],\n sourceMarketplacePath:\n marketplace.source.source === 'directory'\n ? (marketplace.source as { source: 'directory'; path: string }).path\n : marketplace.name,\n }\n\n // Remove existing record if present\n const filtered = installed.filter(\n i => !(i.plugin === plugin.name && i.scope === scope),\n )\n filtered.push(record)\n saveInstalledSkills(filtered)\n\n return installDir\n } catch (error) {\n // Clean up on failure\n try {\n rmSync(installDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors\n }\n\n throw new SkillMarketplaceError(\n `Failed to install plugin: ${error instanceof Error ? error.message : String(error)}`,\n MarketplaceErrorCode.INSTALLATION_FAILED,\n pluginName,\n error,\n )\n }\n}\n\n/**\n * Uninstall plugin\n */\nexport function uninstallPlugin(\n pluginName: string,\n scope: PluginScope = 'user',\n): void {\n const installed = loadInstalledSkills()\n const record = installed.find(\n i => i.plugin === pluginName && i.scope === scope,\n )\n\n if (!record) {\n throw new SkillMarketplaceError(\n `Plugin \"${pluginName}\" is not installed in ${scope} scope`,\n MarketplaceErrorCode.PLUGIN_NOT_FOUND,\n pluginName,\n )\n }\n\n // Remove plugin directory\n if (record.pluginRoot && existsSync(record.pluginRoot)) {\n rmSync(record.pluginRoot, { recursive: true, force: true })\n }\n\n // Update registry\n const filtered = installed.filter(\n i => !(i.plugin === pluginName && i.scope === scope),\n )\n saveInstalledSkills(filtered)\n}\n\n/**\n * Enable plugin\n */\nexport function enablePlugin(\n pluginName: string,\n scope: PluginScope = 'user',\n): void {\n const installed = loadInstalledSkills()\n const record = installed.find(\n i => i.plugin === pluginName && i.scope === scope,\n )\n\n if (!record) {\n throw new SkillMarketplaceError(\n `Plugin \"${pluginName}\" is not installed in ${scope} scope`,\n MarketplaceErrorCode.PLUGIN_NOT_FOUND,\n pluginName,\n )\n }\n\n record.isEnabled = true\n saveInstalledSkills(installed)\n}\n\n/**\n * Disable plugin\n */\nexport function disablePlugin(\n pluginName: string,\n scope: PluginScope = 'user',\n): void {\n const installed = loadInstalledSkills()\n const record = installed.find(\n i => i.plugin === pluginName && i.scope === scope,\n )\n\n if (!record) {\n throw new SkillMarketplaceError(\n `Plugin \"${pluginName}\" is not installed in ${scope} scope`,\n MarketplaceErrorCode.PLUGIN_NOT_FOUND,\n pluginName,\n )\n }\n\n record.isEnabled = false\n saveInstalledSkills(installed)\n}\n\n/**\n * List installed plugins\n */\nexport function listInstalledPlugins(\n scope?: PluginScope,\n): InstalledSkillPlugin[] {\n const installed = loadInstalledSkills()\n\n if (scope) {\n return installed.filter(i => i.scope === scope)\n }\n\n return installed\n}\n\n/**\n * Validate marketplace manifest\n */\nexport function validateMarketplaceManifest(data: unknown): {\n success: boolean\n data?: MarketplaceManifest\n error?: string\n} {\n const result = MarketplaceManifestSchema.safeParse(data)\n\n if (result.success) {\n return { success: true, data: result.data }\n }\n\n return { success: false, error: result.error.message }\n}\n\n/**\n * Validate plugin path (used for local validation)\n */\nexport function validatePluginPath(path: string): {\n isValid: boolean\n errors: string[]\n} {\n const errors: string[] = []\n\n const resolvedPath = isAbsolute(path) ? path : resolve(getCwd(), path)\n\n if (!existsSync(resolvedPath)) {\n errors.push(`Path does not exist: ${resolvedPath}`)\n return { isValid: false, errors }\n }\n\n // Check for plugin manifest (.minto-plugin, .claude-plugin, or root)\n const mintoManifest = join(resolvedPath, '.minto-plugin', 'plugin.json')\n const claudeManifest = join(resolvedPath, '.claude-plugin', 'plugin.json')\n const rootManifest = join(resolvedPath, 'plugin.json')\n\n if (\n !existsSync(mintoManifest) &&\n !existsSync(claudeManifest) &&\n !existsSync(rootManifest)\n ) {\n errors.push(\n 'No plugin manifest found. Expected .minto-plugin/plugin.json or plugin.json',\n )\n }\n\n return { isValid: errors.length === 0, errors }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["MarketplaceErrorCode"]
|
|
7
7
|
}
|
|
@@ -221,12 +221,23 @@ ${content}
|
|
|
221
221
|
this.sessionState.lastTodoUpdate = Date.now();
|
|
222
222
|
const reminder = this.generateFileChangeReminder(context);
|
|
223
223
|
if (reminder) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
224
|
+
const cacheKey = `file_changed_${agentId}_${Date.now()}`;
|
|
225
|
+
this.reminderCache.set(cacheKey, reminder);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
this.addEventListener("file:conflict", (context) => {
|
|
229
|
+
const filePath = context.filePath || context.path || "unknown";
|
|
230
|
+
const cacheKey = `file_conflict_${filePath}_${Date.now()}`;
|
|
231
|
+
if (!this.sessionState.remindersSent.has(cacheKey)) {
|
|
232
|
+
this.sessionState.remindersSent.add(cacheKey);
|
|
233
|
+
const reminder = this.createReminderMessage(
|
|
234
|
+
"file_conflict",
|
|
235
|
+
"general",
|
|
236
|
+
"high",
|
|
237
|
+
`File ${filePath} was modified externally since last read. Re-read the file before editing to avoid overwriting changes.`,
|
|
238
|
+
Date.now()
|
|
239
|
+
);
|
|
240
|
+
this.reminderCache.set(cacheKey, reminder);
|
|
230
241
|
}
|
|
231
242
|
});
|
|
232
243
|
this.addEventListener("file:read", (context) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/services/systemReminder.ts"],
|
|
4
|
-
"sourcesContent": ["import { getTodos, TodoItem } from '@utils/todoStorage'\n\nexport interface ReminderMessage {\n role: 'system'\n content: string\n isMeta: boolean\n timestamp: number\n type: string\n priority: 'low' | 'medium' | 'high'\n category: 'task' | 'security' | 'performance' | 'general'\n}\n\ninterface ReminderConfig {\n todoEmptyReminder: boolean\n securityReminder: boolean\n performanceReminder: boolean\n maxRemindersPerSession: number\n}\n\ninterface SessionReminderState {\n lastTodoUpdate: number\n lastFileAccess: number\n sessionStartTime: number\n remindersSent: Set<string>\n contextPresent: boolean\n reminderCount: number\n config: ReminderConfig\n}\n\nclass SystemReminderService {\n private sessionState: SessionReminderState = {\n lastTodoUpdate: 0,\n lastFileAccess: 0,\n sessionStartTime: Date.now(),\n remindersSent: new Set(),\n contextPresent: false,\n reminderCount: 0,\n config: {\n todoEmptyReminder: true,\n securityReminder: true,\n performanceReminder: true,\n maxRemindersPerSession: 10,\n },\n }\n\n private eventDispatcher = new Map<string, Array<(context: any) => void>>()\n private reminderCache = new Map<string, ReminderMessage>()\n\n constructor() {\n this.setupEventDispatcher()\n }\n\n /**\n * Conditional reminder injection - only when context is present\n * Enhanced with performance optimizations and priority management\n */\n public generateReminders(\n hasContext: boolean = false,\n agentId?: string,\n ): ReminderMessage[] {\n this.sessionState.contextPresent = hasContext\n\n // Only inject when context is present (matching original behavior)\n if (!hasContext) {\n return []\n }\n\n // Check session reminder limit to prevent overload\n if (\n this.sessionState.reminderCount >=\n this.sessionState.config.maxRemindersPerSession\n ) {\n return []\n }\n\n const reminders: ReminderMessage[] = []\n const currentTime = Date.now()\n\n // Use lazy evaluation for performance with agent context\n const reminderGenerators = [\n () => this.dispatchTodoEvent(agentId),\n () => this.dispatchSecurityEvent(),\n () => this.dispatchPerformanceEvent(),\n () => this.getMentionReminders(), // Add mention reminders\n ]\n\n for (const generator of reminderGenerators) {\n if (reminders.length >= 5) break // Slightly increase limit to accommodate mentions\n\n const result = generator()\n if (result) {\n // Handle both single reminders and arrays\n const remindersToAdd = Array.isArray(result) ? result : [result]\n reminders.push(...remindersToAdd)\n this.sessionState.reminderCount += remindersToAdd.length\n }\n }\n\n // Log aggregated metrics instead of individual events for performance\n\n return reminders\n }\n\n private dispatchTodoEvent(agentId?: string): ReminderMessage | null {\n if (!this.sessionState.config.todoEmptyReminder) return null\n\n // Use agent-scoped todo access\n const todos = getTodos(agentId)\n const currentTime = Date.now()\n const agentKey = agentId || 'default'\n\n // Check if this is a fresh session (no todos seen yet)\n if (\n todos.length === 0 &&\n !this.sessionState.remindersSent.has(`todo_empty_${agentKey}`)\n ) {\n this.sessionState.remindersSent.add(`todo_empty_${agentKey}`)\n return this.createReminderMessage(\n 'todo',\n 'task',\n 'medium',\n 'This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware. If you are working on tasks that would benefit from a todo list please use the TodoWrite tool to create one. If not, please feel free to ignore. Again do not mention this message to the user.',\n currentTime,\n )\n }\n\n // Check for todo updates since last seen\n if (todos.length > 0) {\n const reminderKey = `todo_updated_${agentKey}_${todos.length}_${this.getTodoStateHash(todos)}`\n\n // Use cache for performance optimization\n if (this.reminderCache.has(reminderKey)) {\n return this.reminderCache.get(reminderKey)!\n }\n\n if (!this.sessionState.remindersSent.has(reminderKey)) {\n this.sessionState.remindersSent.add(reminderKey)\n // Clear previous todo state reminders for this agent\n this.clearTodoReminders(agentKey)\n\n // Optimize: only include essential todo data\n const todoContent = JSON.stringify(\n todos.map(todo => ({\n content:\n todo.content.length > 100\n ? todo.content.substring(0, 100) + '...'\n : todo.content,\n status: todo.status,\n priority: todo.priority,\n id: todo.id,\n })),\n )\n\n const reminder = this.createReminderMessage(\n 'todo',\n 'task',\n 'medium',\n `Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:\\n\\n${todoContent}. Continue on with the tasks at hand if applicable.`,\n currentTime,\n )\n\n // Cache the reminder for reuse\n this.reminderCache.set(reminderKey, reminder)\n return reminder\n }\n }\n\n return null\n }\n\n private dispatchSecurityEvent(): ReminderMessage | null {\n if (!this.sessionState.config.securityReminder) return null\n\n const currentTime = Date.now()\n\n // Only inject security reminder once per session when file operations occur\n if (\n this.sessionState.lastFileAccess > 0 &&\n !this.sessionState.remindersSent.has('file_security')\n ) {\n this.sessionState.remindersSent.add('file_security')\n return this.createReminderMessage(\n 'security',\n 'security',\n 'high',\n 'Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.',\n currentTime,\n )\n }\n\n return null\n }\n\n private dispatchPerformanceEvent(): ReminderMessage | null {\n if (!this.sessionState.config.performanceReminder) return null\n\n const currentTime = Date.now()\n const sessionDuration = currentTime - this.sessionState.sessionStartTime\n\n // Remind about performance after long sessions (30 minutes)\n if (\n sessionDuration > 30 * 60 * 1000 &&\n !this.sessionState.remindersSent.has('performance_long_session')\n ) {\n this.sessionState.remindersSent.add('performance_long_session')\n return this.createReminderMessage(\n 'performance',\n 'performance',\n 'low',\n 'Long session detected. Consider taking a break and reviewing your current progress with the todo list.',\n currentTime,\n )\n }\n\n return null\n }\n\n /**\n * Retrieve cached mention reminders\n * Returns recent mentions (within 5 seconds) that haven't expired\n */\n private getMentionReminders(): ReminderMessage[] {\n const currentTime = Date.now()\n const MENTION_FRESHNESS_WINDOW = 5000 // 5 seconds\n const reminders: ReminderMessage[] = []\n const expiredKeys: string[] = []\n\n // Single pass through cache for both collection and cleanup identification\n for (const [key, reminder] of this.reminderCache.entries()) {\n if (this.isMentionReminder(reminder)) {\n const age = currentTime - reminder.timestamp\n if (age <= MENTION_FRESHNESS_WINDOW) {\n reminders.push(reminder)\n } else {\n expiredKeys.push(key)\n }\n }\n }\n\n // Clean up expired mention reminders in separate pass for performance\n expiredKeys.forEach(key => this.reminderCache.delete(key))\n\n return reminders\n }\n\n /**\n * Type guard for mention reminders - centralized type checking\n * Eliminates hardcoded type strings scattered throughout the code\n */\n private isMentionReminder(reminder: ReminderMessage): boolean {\n const mentionTypes = ['agent_mention', 'file_mention', 'ask_model_mention']\n return mentionTypes.includes(reminder.type)\n }\n\n /**\n * Generate reminders for external file changes\n * Called when todo files are modified externally\n */\n public generateFileChangeReminder(context: any): ReminderMessage | null {\n const { agentId, filePath, reminder } = context\n\n if (!reminder) {\n return null\n }\n\n const currentTime = Date.now()\n const reminderKey = `file_changed_${agentId}_${filePath}_${currentTime}`\n\n // Ensure this specific file change reminder is only shown once\n if (this.sessionState.remindersSent.has(reminderKey)) {\n return null\n }\n\n this.sessionState.remindersSent.add(reminderKey)\n\n return this.createReminderMessage(\n 'file_changed',\n 'general',\n 'medium',\n reminder,\n currentTime,\n )\n }\n\n private createReminderMessage(\n type: string,\n category: ReminderMessage['category'],\n priority: ReminderMessage['priority'],\n content: string,\n timestamp: number,\n ): ReminderMessage {\n return {\n role: 'system',\n content: `<system-reminder>\\n${content}\\n</system-reminder>`,\n isMeta: true,\n timestamp,\n type,\n priority,\n category,\n }\n }\n\n private getTodoStateHash(todos: TodoItem[]): string {\n return todos\n .map(t => `${t.id}:${t.status}`)\n .sort()\n .join('|')\n }\n\n private clearTodoReminders(agentId?: string): void {\n const agentKey = agentId || 'default'\n for (const key of this.sessionState.remindersSent) {\n if (key.startsWith(`todo_updated_${agentKey}_`)) {\n this.sessionState.remindersSent.delete(key)\n }\n }\n }\n\n private setupEventDispatcher(): void {\n // Session startup events\n this.addEventListener('session:startup', context => {\n // Reset session state on startup\n this.resetSession()\n\n // Initialize session tracking\n this.sessionState.sessionStartTime = Date.now()\n this.sessionState.contextPresent =\n Object.keys(context.context || {}).length > 0\n })\n\n // Todo change events\n this.addEventListener('todo:changed', context => {\n this.sessionState.lastTodoUpdate = Date.now()\n this.clearTodoReminders(context.agentId)\n })\n\n // Todo file changed externally\n this.addEventListener('todo:file_changed', context => {\n // External file change detected, trigger reminder injection\n const agentId = context.agentId || 'default'\n this.clearTodoReminders(agentId)\n this.sessionState.lastTodoUpdate = Date.now()\n\n // Generate and inject file change reminder immediately\n const reminder = this.generateFileChangeReminder(context)\n if (reminder) {\n // Inject reminder into the latest user message through event system\n this.emitEvent('reminder:inject', {\n reminder: reminder.content,\n agentId,\n type: 'file_changed',\n timestamp: Date.now(),\n })\n }\n })\n\n // File access events\n this.addEventListener('file:read', context => {\n this.sessionState.lastFileAccess = Date.now()\n })\n\n // File edit events for freshness detection\n this.addEventListener('file:edited', context => {\n // File edit handling\n })\n\n // Unified mention event handlers - eliminates code duplication\n this.addEventListener('agent:mentioned', context => {\n this.createMentionReminder({\n type: 'agent_mention',\n key: `agent_mention_${context.agentType}_${context.timestamp}`,\n category: 'task',\n priority: 'high',\n content: `The user mentioned @${context.originalMention}. You MUST use the Task tool with subagent_type=\"${context.agentType}\" to delegate this task to the specified agent. Provide a detailed, self-contained task description that fully captures the user's intent for the ${context.agentType} agent to execute.`,\n timestamp: context.timestamp,\n })\n })\n\n this.addEventListener('file:mentioned', context => {\n this.createMentionReminder({\n type: 'file_mention',\n key: `file_mention_${context.filePath}_${context.timestamp}`,\n category: 'general',\n priority: 'high',\n content: `The user mentioned @${context.originalMention}. You MUST read the entire content of the file at path: ${context.filePath} using the Read tool to understand the full context before proceeding with the user's request.`,\n timestamp: context.timestamp,\n })\n })\n\n this.addEventListener('ask-model:mentioned', context => {\n this.createMentionReminder({\n type: 'ask_model_mention',\n key: `ask_model_mention_${context.modelName}_${context.timestamp}`,\n category: 'task',\n priority: 'high',\n content: `The user mentioned @${context.modelName}. You MUST use the AskExpertModelTool to consult this specific model for expert opinions and analysis. Provide the user's question or context clearly to get the most relevant response from ${context.modelName}.`,\n timestamp: context.timestamp,\n })\n })\n }\n\n public addEventListener(\n event: string,\n callback: (context: any) => void,\n ): void {\n if (!this.eventDispatcher.has(event)) {\n this.eventDispatcher.set(event, [])\n }\n this.eventDispatcher.get(event)!.push(callback)\n }\n\n public emitEvent(event: string, context: any): void {\n const listeners = this.eventDispatcher.get(event) || []\n listeners.forEach(callback => {\n try {\n callback(context)\n } catch (error) {\n console.error(`Error in event listener for ${event}:`, error)\n }\n })\n }\n\n /**\n * Unified mention reminder creation - eliminates duplicate logic\n * Centralizes reminder creation with consistent deduplication\n */\n private createMentionReminder(params: {\n type: string\n key: string\n category: ReminderMessage['category']\n priority: ReminderMessage['priority']\n content: string\n timestamp: number\n }): void {\n if (!this.sessionState.remindersSent.has(params.key)) {\n this.sessionState.remindersSent.add(params.key)\n\n const reminder = this.createReminderMessage(\n params.type,\n params.category,\n params.priority,\n params.content,\n params.timestamp,\n )\n\n this.reminderCache.set(params.key, reminder)\n }\n }\n\n public resetSession(): void {\n this.sessionState = {\n lastTodoUpdate: 0,\n lastFileAccess: 0,\n sessionStartTime: Date.now(),\n remindersSent: new Set(),\n contextPresent: false,\n reminderCount: 0,\n config: { ...this.sessionState.config }, // Preserve config across resets\n }\n this.reminderCache.clear() // Clear cache on session reset\n }\n\n public updateConfig(config: Partial<ReminderConfig>): void {\n this.sessionState.config = { ...this.sessionState.config, ...config }\n }\n\n public getSessionState(): SessionReminderState {\n return { ...this.sessionState }\n }\n}\n\nexport const systemReminderService = new SystemReminderService()\n\nexport const generateSystemReminders = (\n hasContext: boolean = false,\n agentId?: string,\n) => systemReminderService.generateReminders(hasContext, agentId)\n\nexport const generateFileChangeReminder = (context: any) =>\n systemReminderService.generateFileChangeReminder(context)\n\nexport const emitReminderEvent = (event: string, context: any) =>\n systemReminderService.emitEvent(event, context)\n\nexport const resetReminderSession = () => systemReminderService.resetSession()\nexport const getReminderSessionState = () =>\n systemReminderService.getSessionState()\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { getTodos, TodoItem } from '@utils/todoStorage'\n\nexport interface ReminderMessage {\n role: 'system'\n content: string\n isMeta: boolean\n timestamp: number\n type: string\n priority: 'low' | 'medium' | 'high'\n category: 'task' | 'security' | 'performance' | 'general'\n}\n\ninterface ReminderConfig {\n todoEmptyReminder: boolean\n securityReminder: boolean\n performanceReminder: boolean\n maxRemindersPerSession: number\n}\n\ninterface SessionReminderState {\n lastTodoUpdate: number\n lastFileAccess: number\n sessionStartTime: number\n remindersSent: Set<string>\n contextPresent: boolean\n reminderCount: number\n config: ReminderConfig\n}\n\nclass SystemReminderService {\n private sessionState: SessionReminderState = {\n lastTodoUpdate: 0,\n lastFileAccess: 0,\n sessionStartTime: Date.now(),\n remindersSent: new Set(),\n contextPresent: false,\n reminderCount: 0,\n config: {\n todoEmptyReminder: true,\n securityReminder: true,\n performanceReminder: true,\n maxRemindersPerSession: 10,\n },\n }\n\n private eventDispatcher = new Map<string, Array<(context: any) => void>>()\n private reminderCache = new Map<string, ReminderMessage>()\n\n constructor() {\n this.setupEventDispatcher()\n }\n\n /**\n * Conditional reminder injection - only when context is present\n * Enhanced with performance optimizations and priority management\n */\n public generateReminders(\n hasContext: boolean = false,\n agentId?: string,\n ): ReminderMessage[] {\n this.sessionState.contextPresent = hasContext\n\n // Only inject when context is present (matching original behavior)\n if (!hasContext) {\n return []\n }\n\n // Check session reminder limit to prevent overload\n if (\n this.sessionState.reminderCount >=\n this.sessionState.config.maxRemindersPerSession\n ) {\n return []\n }\n\n const reminders: ReminderMessage[] = []\n const currentTime = Date.now()\n\n // Use lazy evaluation for performance with agent context\n const reminderGenerators = [\n () => this.dispatchTodoEvent(agentId),\n () => this.dispatchSecurityEvent(),\n () => this.dispatchPerformanceEvent(),\n () => this.getMentionReminders(), // Add mention reminders\n ]\n\n for (const generator of reminderGenerators) {\n if (reminders.length >= 5) break // Slightly increase limit to accommodate mentions\n\n const result = generator()\n if (result) {\n // Handle both single reminders and arrays\n const remindersToAdd = Array.isArray(result) ? result : [result]\n reminders.push(...remindersToAdd)\n this.sessionState.reminderCount += remindersToAdd.length\n }\n }\n\n // Log aggregated metrics instead of individual events for performance\n\n return reminders\n }\n\n private dispatchTodoEvent(agentId?: string): ReminderMessage | null {\n if (!this.sessionState.config.todoEmptyReminder) return null\n\n // Use agent-scoped todo access\n const todos = getTodos(agentId)\n const currentTime = Date.now()\n const agentKey = agentId || 'default'\n\n // Check if this is a fresh session (no todos seen yet)\n if (\n todos.length === 0 &&\n !this.sessionState.remindersSent.has(`todo_empty_${agentKey}`)\n ) {\n this.sessionState.remindersSent.add(`todo_empty_${agentKey}`)\n return this.createReminderMessage(\n 'todo',\n 'task',\n 'medium',\n 'This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware. If you are working on tasks that would benefit from a todo list please use the TodoWrite tool to create one. If not, please feel free to ignore. Again do not mention this message to the user.',\n currentTime,\n )\n }\n\n // Check for todo updates since last seen\n if (todos.length > 0) {\n const reminderKey = `todo_updated_${agentKey}_${todos.length}_${this.getTodoStateHash(todos)}`\n\n // Use cache for performance optimization\n if (this.reminderCache.has(reminderKey)) {\n return this.reminderCache.get(reminderKey)!\n }\n\n if (!this.sessionState.remindersSent.has(reminderKey)) {\n this.sessionState.remindersSent.add(reminderKey)\n // Clear previous todo state reminders for this agent\n this.clearTodoReminders(agentKey)\n\n // Optimize: only include essential todo data\n const todoContent = JSON.stringify(\n todos.map(todo => ({\n content:\n todo.content.length > 100\n ? todo.content.substring(0, 100) + '...'\n : todo.content,\n status: todo.status,\n priority: todo.priority,\n id: todo.id,\n })),\n )\n\n const reminder = this.createReminderMessage(\n 'todo',\n 'task',\n 'medium',\n `Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:\\n\\n${todoContent}. Continue on with the tasks at hand if applicable.`,\n currentTime,\n )\n\n // Cache the reminder for reuse\n this.reminderCache.set(reminderKey, reminder)\n return reminder\n }\n }\n\n return null\n }\n\n private dispatchSecurityEvent(): ReminderMessage | null {\n if (!this.sessionState.config.securityReminder) return null\n\n const currentTime = Date.now()\n\n // Only inject security reminder once per session when file operations occur\n if (\n this.sessionState.lastFileAccess > 0 &&\n !this.sessionState.remindersSent.has('file_security')\n ) {\n this.sessionState.remindersSent.add('file_security')\n return this.createReminderMessage(\n 'security',\n 'security',\n 'high',\n 'Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.',\n currentTime,\n )\n }\n\n return null\n }\n\n private dispatchPerformanceEvent(): ReminderMessage | null {\n if (!this.sessionState.config.performanceReminder) return null\n\n const currentTime = Date.now()\n const sessionDuration = currentTime - this.sessionState.sessionStartTime\n\n // Remind about performance after long sessions (30 minutes)\n if (\n sessionDuration > 30 * 60 * 1000 &&\n !this.sessionState.remindersSent.has('performance_long_session')\n ) {\n this.sessionState.remindersSent.add('performance_long_session')\n return this.createReminderMessage(\n 'performance',\n 'performance',\n 'low',\n 'Long session detected. Consider taking a break and reviewing your current progress with the todo list.',\n currentTime,\n )\n }\n\n return null\n }\n\n /**\n * Retrieve cached mention reminders\n * Returns recent mentions (within 5 seconds) that haven't expired\n */\n private getMentionReminders(): ReminderMessage[] {\n const currentTime = Date.now()\n const MENTION_FRESHNESS_WINDOW = 5000 // 5 seconds\n const reminders: ReminderMessage[] = []\n const expiredKeys: string[] = []\n\n // Single pass through cache for both collection and cleanup identification\n for (const [key, reminder] of this.reminderCache.entries()) {\n if (this.isMentionReminder(reminder)) {\n const age = currentTime - reminder.timestamp\n if (age <= MENTION_FRESHNESS_WINDOW) {\n reminders.push(reminder)\n } else {\n expiredKeys.push(key)\n }\n }\n }\n\n // Clean up expired mention reminders in separate pass for performance\n expiredKeys.forEach(key => this.reminderCache.delete(key))\n\n return reminders\n }\n\n /**\n * Type guard for mention reminders - centralized type checking\n * Eliminates hardcoded type strings scattered throughout the code\n */\n private isMentionReminder(reminder: ReminderMessage): boolean {\n const mentionTypes = ['agent_mention', 'file_mention', 'ask_model_mention']\n return mentionTypes.includes(reminder.type)\n }\n\n /**\n * Generate reminders for external file changes\n * Called when todo files are modified externally\n */\n public generateFileChangeReminder(context: any): ReminderMessage | null {\n const { agentId, filePath, reminder } = context\n\n if (!reminder) {\n return null\n }\n\n const currentTime = Date.now()\n const reminderKey = `file_changed_${agentId}_${filePath}_${currentTime}`\n\n // Ensure this specific file change reminder is only shown once\n if (this.sessionState.remindersSent.has(reminderKey)) {\n return null\n }\n\n this.sessionState.remindersSent.add(reminderKey)\n\n return this.createReminderMessage(\n 'file_changed',\n 'general',\n 'medium',\n reminder,\n currentTime,\n )\n }\n\n private createReminderMessage(\n type: string,\n category: ReminderMessage['category'],\n priority: ReminderMessage['priority'],\n content: string,\n timestamp: number,\n ): ReminderMessage {\n return {\n role: 'system',\n content: `<system-reminder>\\n${content}\\n</system-reminder>`,\n isMeta: true,\n timestamp,\n type,\n priority,\n category,\n }\n }\n\n private getTodoStateHash(todos: TodoItem[]): string {\n return todos\n .map(t => `${t.id}:${t.status}`)\n .sort()\n .join('|')\n }\n\n private clearTodoReminders(agentId?: string): void {\n const agentKey = agentId || 'default'\n for (const key of this.sessionState.remindersSent) {\n if (key.startsWith(`todo_updated_${agentKey}_`)) {\n this.sessionState.remindersSent.delete(key)\n }\n }\n }\n\n private setupEventDispatcher(): void {\n // Session startup events\n this.addEventListener('session:startup', context => {\n // Reset session state on startup\n this.resetSession()\n\n // Initialize session tracking\n this.sessionState.sessionStartTime = Date.now()\n this.sessionState.contextPresent =\n Object.keys(context.context || {}).length > 0\n })\n\n // Todo change events\n this.addEventListener('todo:changed', context => {\n this.sessionState.lastTodoUpdate = Date.now()\n this.clearTodoReminders(context.agentId)\n })\n\n // Todo file changed externally\n this.addEventListener('todo:file_changed', context => {\n // External file change detected, cache reminder for next generateReminders() call\n const agentId = context.agentId || 'default'\n this.clearTodoReminders(agentId)\n this.sessionState.lastTodoUpdate = Date.now()\n\n // Generate and cache file change reminder directly\n const reminder = this.generateFileChangeReminder(context)\n if (reminder) {\n const cacheKey = `file_changed_${agentId}_${Date.now()}`\n this.reminderCache.set(cacheKey, reminder)\n }\n })\n\n // File conflict events (external modification between reads)\n this.addEventListener('file:conflict', context => {\n const filePath = context.filePath || context.path || 'unknown'\n const cacheKey = `file_conflict_${filePath}_${Date.now()}`\n\n if (!this.sessionState.remindersSent.has(cacheKey)) {\n this.sessionState.remindersSent.add(cacheKey)\n\n const reminder = this.createReminderMessage(\n 'file_conflict',\n 'general',\n 'high',\n `File ${filePath} was modified externally since last read. Re-read the file before editing to avoid overwriting changes.`,\n Date.now(),\n )\n this.reminderCache.set(cacheKey, reminder)\n }\n })\n\n // File access events\n this.addEventListener('file:read', context => {\n this.sessionState.lastFileAccess = Date.now()\n })\n\n // File edit events for freshness detection\n this.addEventListener('file:edited', context => {\n // File edit handling\n })\n\n // Unified mention event handlers - eliminates code duplication\n this.addEventListener('agent:mentioned', context => {\n this.createMentionReminder({\n type: 'agent_mention',\n key: `agent_mention_${context.agentType}_${context.timestamp}`,\n category: 'task',\n priority: 'high',\n content: `The user mentioned @${context.originalMention}. You MUST use the Task tool with subagent_type=\"${context.agentType}\" to delegate this task to the specified agent. Provide a detailed, self-contained task description that fully captures the user's intent for the ${context.agentType} agent to execute.`,\n timestamp: context.timestamp,\n })\n })\n\n this.addEventListener('file:mentioned', context => {\n this.createMentionReminder({\n type: 'file_mention',\n key: `file_mention_${context.filePath}_${context.timestamp}`,\n category: 'general',\n priority: 'high',\n content: `The user mentioned @${context.originalMention}. You MUST read the entire content of the file at path: ${context.filePath} using the Read tool to understand the full context before proceeding with the user's request.`,\n timestamp: context.timestamp,\n })\n })\n\n this.addEventListener('ask-model:mentioned', context => {\n this.createMentionReminder({\n type: 'ask_model_mention',\n key: `ask_model_mention_${context.modelName}_${context.timestamp}`,\n category: 'task',\n priority: 'high',\n content: `The user mentioned @${context.modelName}. You MUST use the AskExpertModelTool to consult this specific model for expert opinions and analysis. Provide the user's question or context clearly to get the most relevant response from ${context.modelName}.`,\n timestamp: context.timestamp,\n })\n })\n }\n\n public addEventListener(\n event: string,\n callback: (context: any) => void,\n ): void {\n if (!this.eventDispatcher.has(event)) {\n this.eventDispatcher.set(event, [])\n }\n this.eventDispatcher.get(event)!.push(callback)\n }\n\n public emitEvent(event: string, context: any): void {\n const listeners = this.eventDispatcher.get(event) || []\n listeners.forEach(callback => {\n try {\n callback(context)\n } catch (error) {\n console.error(`Error in event listener for ${event}:`, error)\n }\n })\n }\n\n /**\n * Unified mention reminder creation - eliminates duplicate logic\n * Centralizes reminder creation with consistent deduplication\n */\n private createMentionReminder(params: {\n type: string\n key: string\n category: ReminderMessage['category']\n priority: ReminderMessage['priority']\n content: string\n timestamp: number\n }): void {\n if (!this.sessionState.remindersSent.has(params.key)) {\n this.sessionState.remindersSent.add(params.key)\n\n const reminder = this.createReminderMessage(\n params.type,\n params.category,\n params.priority,\n params.content,\n params.timestamp,\n )\n\n this.reminderCache.set(params.key, reminder)\n }\n }\n\n public resetSession(): void {\n this.sessionState = {\n lastTodoUpdate: 0,\n lastFileAccess: 0,\n sessionStartTime: Date.now(),\n remindersSent: new Set(),\n contextPresent: false,\n reminderCount: 0,\n config: { ...this.sessionState.config }, // Preserve config across resets\n }\n this.reminderCache.clear() // Clear cache on session reset\n }\n\n public updateConfig(config: Partial<ReminderConfig>): void {\n this.sessionState.config = { ...this.sessionState.config, ...config }\n }\n\n public getSessionState(): SessionReminderState {\n return { ...this.sessionState }\n }\n}\n\nexport const systemReminderService = new SystemReminderService()\n\nexport const generateSystemReminders = (\n hasContext: boolean = false,\n agentId?: string,\n) => systemReminderService.generateReminders(hasContext, agentId)\n\nexport const generateFileChangeReminder = (context: any) =>\n systemReminderService.generateFileChangeReminder(context)\n\nexport const emitReminderEvent = (event: string, context: any) =>\n systemReminderService.emitEvent(event, context)\n\nexport const resetReminderSession = () => systemReminderService.resetSession()\nexport const getReminderSessionState = () =>\n systemReminderService.getSessionState()\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -19,6 +19,7 @@ import { getCwd } from "../../utils/state.js";
|
|
|
19
19
|
import { getTheme } from "../../utils/theme.js";
|
|
20
20
|
import { emitReminderEvent } from "../../services/systemReminder.js";
|
|
21
21
|
import { recordFileEdit } from "../../services/fileFreshness.js";
|
|
22
|
+
import { triggerBackup } from "../../core/backupHook.js";
|
|
22
23
|
import { NotebookEditTool } from "../NotebookEditTool/NotebookEditTool.js";
|
|
23
24
|
import { DESCRIPTION } from "./prompt.js";
|
|
24
25
|
import { applyEdit } from "./utils.js";
|
|
@@ -213,6 +214,12 @@ const FileEditTool = {
|
|
|
213
214
|
const originalFile = existsSync(fullFilePath) ? readFileSync(fullFilePath, enc) : "";
|
|
214
215
|
writeTextContent(fullFilePath, updatedFile, enc, endings);
|
|
215
216
|
recordFileEdit(fullFilePath, updatedFile);
|
|
217
|
+
triggerBackup(
|
|
218
|
+
fullFilePath,
|
|
219
|
+
originalFile || null,
|
|
220
|
+
updatedFile,
|
|
221
|
+
old_string === "" ? "create" : "update"
|
|
222
|
+
);
|
|
216
223
|
readFileTimestamps[fullFilePath] = statSync(fullFilePath).mtimeMs;
|
|
217
224
|
if (fullFilePath.endsWith(`${sep}${PROJECT_FILE}`)) {
|
|
218
225
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/tools/FileEditTool/FileEditTool.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Hunk } from 'diff'\nimport { existsSync, lstatSync, mkdirSync, readFileSync, statSync } from 'fs'\nimport { Box, Text } from 'ink'\nimport { dirname, isAbsolute, relative, resolve, sep } from 'path'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { FileEditToolUpdatedMessage } from '@components/FileEditToolUpdatedMessage'\nimport { StructuredDiff } from '@components/StructuredDiff'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { Tool, ValidationResult } from '@tool'\nimport { intersperse } from '@utils/array'\nimport {\n addLineNumbers,\n detectFileEncoding,\n detectLineEndings,\n findSimilarFile,\n writeTextContent,\n} from '@utils/file'\nimport { logError } from '@utils/log'\nimport { getCwd } from '@utils/state'\nimport { getTheme } from '@utils/theme'\nimport { emitReminderEvent } from '@services/systemReminder'\nimport { recordFileEdit } from '@services/fileFreshness'\nimport { NotebookEditTool } from '@tools/NotebookEditTool/NotebookEditTool'\nimport { DESCRIPTION } from './prompt'\nimport { applyEdit } from './utils'\nimport {\n hasWritePermission,\n pathInOriginalCwd,\n} from '@utils/permissions/filesystem'\nimport { PROJECT_FILE } from '@constants/product'\n\nconst inputSchema = z.strictObject({\n file_path: z.string().describe('The absolute path to the file to modify'),\n old_string: z.string().describe('The text to replace'),\n new_string: z\n .string()\n .describe(\n 'The text to replace it with (must be different from old_string)',\n ),\n replace_all: z\n .boolean()\n .optional()\n .default(false)\n .describe('Replace all occurences of old_string (default false)'),\n})\n\nexport type In = typeof inputSchema\n\n// Number of lines of context to include before/after the change in our result message\nconst N_LINES_SNIPPET = 4\n\nexport const FileEditTool = {\n name: 'Edit',\n async description() {\n return 'A tool for editing files'\n },\n async prompt() {\n return DESCRIPTION\n },\n inputSchema,\n userFacingName() {\n return 'Edit'\n },\n async isEnabled() {\n return true\n },\n isReadOnly() {\n return false\n },\n isConcurrencySafe() {\n return false // FileEdit modifies files, not safe for concurrent execution\n },\n needsPermissions({ file_path }) {\n return !hasWritePermission(file_path)\n },\n renderToolUseMessage(input, { verbose }) {\n const filePath = verbose\n ? input.file_path\n : relative(getCwd(), input.file_path)\n const replaceAllSuffix = input.replace_all ? ', replace_all: true' : ''\n return `file_path: ${filePath}${replaceAllSuffix}`\n },\n renderToolResultMessage(output, options?: { verbose?: boolean }) {\n const verbose = options?.verbose ?? false\n\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box justifyContent=\"space-between\" width=\"100%\">\n <Box flexDirection=\"row\">\n <Text> \u23BF </Text>\n <Text color={getTheme().secondaryText}>Edit completed</Text>\n </Box>\n </Box>\n )\n }\n\n const { filePath, structuredPatch } = output\n return (\n <FileEditToolUpdatedMessage\n filePath={filePath}\n structuredPatch={structuredPatch}\n verbose={verbose}\n />\n )\n },\n renderToolUseRejectedMessage(\n { file_path, old_string, new_string, replace_all }: any = {},\n { columns, verbose }: any = {},\n ) {\n try {\n if (!file_path) {\n return <FallbackToolUseRejectedMessage />\n }\n const { patch } = applyEdit(\n file_path,\n old_string,\n new_string,\n replace_all,\n )\n return (\n <Box flexDirection=\"column\">\n <Text>\n {' '}\u23BF{' '}\n <Text color={getTheme().error}>\n User rejected {old_string === '' ? 'write' : 'update'} to{' '}\n </Text>\n <Text bold>\n {verbose ? file_path : relative(getCwd(), file_path)}\n </Text>\n </Text>\n {intersperse(\n patch.map(patch => (\n <Box flexDirection=\"column\" paddingLeft={5} key={patch.newStart}>\n <StructuredDiff patch={patch} dim={true} width={columns - 12} />\n </Box>\n )),\n i => (\n <Box paddingLeft={5} key={`ellipsis-${i}`}>\n <Text color={getTheme().secondaryText}>...</Text>\n </Box>\n ),\n )}\n </Box>\n )\n } catch (e) {\n // Handle the case where while we were showing the diff, the user manually made the change.\n // TODO: Find a way to show the diff in this case\n logError(e)\n return (\n <Box flexDirection=\"column\">\n <Text>{' '}\u23BF (No changes)</Text>\n </Box>\n )\n }\n },\n async validateInput(\n { file_path, old_string, new_string, replace_all },\n { readFileTimestamps },\n ) {\n if (old_string === new_string) {\n return {\n result: false,\n message:\n 'No changes to make: old_string and new_string are exactly the same.',\n meta: {\n old_string,\n },\n } as ValidationResult\n }\n\n const fullFilePath = isAbsolute(file_path)\n ? file_path\n : resolve(getCwd(), file_path)\n\n // Security check: Ensure path is within allowed directory\n if (!pathInOriginalCwd(fullFilePath) && !hasWritePermission(fullFilePath)) {\n return {\n result: false,\n message:\n 'Path traversal detected - file must be within the project directory or an allowed write location.',\n }\n }\n\n // Security check: Check for symlinks that could escape directory boundaries\n if (existsSync(fullFilePath)) {\n try {\n const lstats = lstatSync(fullFilePath)\n if (lstats.isSymbolicLink()) {\n return {\n result: false,\n message:\n 'Cannot edit symbolic links for security reasons. Edit the target file directly.',\n }\n }\n } catch {\n // If we can't stat the file, let other checks handle it\n }\n }\n\n if (existsSync(fullFilePath) && old_string === '') {\n return {\n result: false,\n message: 'Cannot create new file - file already exists.',\n }\n }\n\n if (!existsSync(fullFilePath) && old_string === '') {\n return {\n result: true,\n }\n }\n\n if (!existsSync(fullFilePath)) {\n // Try to find a similar file with a different extension\n const similarFilename = findSimilarFile(fullFilePath)\n let message = 'File does not exist.'\n\n // If we found a similar file, suggest it to the assistant\n if (similarFilename) {\n message += ` Did you mean ${similarFilename}?`\n }\n\n return {\n result: false,\n message,\n }\n }\n\n if (fullFilePath.endsWith('.ipynb')) {\n return {\n result: false,\n message: `File is a Jupyter Notebook. Use the ${NotebookEditTool.name} to edit this file.`,\n }\n }\n\n const readTimestamp = readFileTimestamps[fullFilePath]\n if (!readTimestamp) {\n return {\n result: false,\n message:\n 'File has not been read yet. Read it first before writing to it.',\n meta: {\n isFilePathAbsolute: String(isAbsolute(file_path)),\n },\n }\n }\n\n // Check if file exists and get its last modified time\n const stats = statSync(fullFilePath)\n const lastWriteTime = stats.mtimeMs\n if (lastWriteTime > readTimestamp) {\n return {\n result: false,\n message:\n 'File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.',\n }\n }\n\n const enc = detectFileEncoding(fullFilePath)\n const file = readFileSync(fullFilePath, enc)\n if (!file.includes(old_string)) {\n return {\n result: false,\n message: `String to replace not found in file.`,\n meta: {\n isFilePathAbsolute: String(isAbsolute(file_path)),\n },\n }\n }\n\n const matches = file.split(old_string).length - 1\n if (matches > 1 && !replace_all) {\n return {\n result: false,\n message: `Found ${matches} matches of the string to replace. For safety, this tool only supports replacing exactly one occurrence at a time. Add more lines of context to your edit and try again, or use replace_all to change every instance.`,\n meta: {\n isFilePathAbsolute: String(isAbsolute(file_path)),\n },\n }\n }\n\n return { result: true }\n },\n async *call(\n { file_path, old_string, new_string, replace_all },\n { readFileTimestamps },\n ) {\n const { patch, updatedFile } = applyEdit(\n file_path,\n old_string,\n new_string,\n replace_all,\n )\n\n const fullFilePath = isAbsolute(file_path)\n ? file_path\n : resolve(getCwd(), file_path)\n const dir = dirname(fullFilePath)\n mkdirSync(dir, { recursive: true })\n const enc = existsSync(fullFilePath)\n ? detectFileEncoding(fullFilePath)\n : 'utf8'\n const endings = existsSync(fullFilePath)\n ? detectLineEndings(fullFilePath)\n : 'LF'\n const originalFile = existsSync(fullFilePath)\n ? readFileSync(fullFilePath, enc)\n : ''\n writeTextContent(fullFilePath, updatedFile, enc, endings)\n\n // Record Agent edit operation for file freshness tracking\n recordFileEdit(fullFilePath, updatedFile)\n\n // Update read timestamp, to invalidate stale writes\n readFileTimestamps[fullFilePath] = statSync(fullFilePath).mtimeMs\n\n // Log when editing CLAUDE.md\n if (fullFilePath.endsWith(`${sep}${PROJECT_FILE}`)) {\n }\n\n // Emit file edited event for system reminders\n emitReminderEvent('file:edited', {\n filePath: fullFilePath,\n oldString: old_string,\n newString: new_string,\n timestamp: Date.now(),\n operation:\n old_string === '' ? 'create' : new_string === '' ? 'delete' : 'update',\n })\n\n const data = {\n filePath: file_path,\n oldString: old_string,\n newString: new_string,\n originalFile,\n structuredPatch: patch,\n }\n yield {\n type: 'result',\n data,\n resultForAssistant: this.renderResultForAssistant(data),\n }\n },\n renderResultForAssistant({ filePath, originalFile, oldString, newString }) {\n const { snippet, startLine } = getSnippet(\n originalFile || '',\n oldString,\n newString,\n )\n return `The file ${filePath} has been updated. Here's the result of running \\`cat -n\\` on a snippet of the edited file:\n${addLineNumbers({\n content: snippet,\n startLine,\n})}`\n },\n} satisfies Tool<\n typeof inputSchema,\n {\n filePath: string\n oldString: string\n newString: string\n originalFile: string\n structuredPatch: Hunk[]\n }\n>\n\nexport function getSnippet(\n initialText: string,\n oldStr: string,\n newStr: string,\n): { snippet: string; startLine: number } {\n const before = initialText.split(oldStr)[0] ?? ''\n const replacementLine = before.split(/\\r?\\n/).length - 1\n const newFileLines = initialText.replace(oldStr, newStr).split(/\\r?\\n/)\n // Calculate the start and end line numbers for the snippet\n const startLine = Math.max(0, replacementLine - N_LINES_SNIPPET)\n const endLine =\n replacementLine + N_LINES_SNIPPET + newStr.split(/\\r?\\n/).length\n // Get snippet\n const snippetLines = newFileLines.slice(startLine, endLine + 1)\n const snippet = snippetLines.join('\\n')\n return { snippet, startLine: startLine + 1 }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { Hunk } from 'diff'\nimport { existsSync, lstatSync, mkdirSync, readFileSync, statSync } from 'fs'\nimport { Box, Text } from 'ink'\nimport { dirname, isAbsolute, relative, resolve, sep } from 'path'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { FileEditToolUpdatedMessage } from '@components/FileEditToolUpdatedMessage'\nimport { StructuredDiff } from '@components/StructuredDiff'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { Tool, ValidationResult } from '@tool'\nimport { intersperse } from '@utils/array'\nimport {\n addLineNumbers,\n detectFileEncoding,\n detectLineEndings,\n findSimilarFile,\n writeTextContent,\n} from '@utils/file'\nimport { logError } from '@utils/log'\nimport { getCwd } from '@utils/state'\nimport { getTheme } from '@utils/theme'\nimport { emitReminderEvent } from '@services/systemReminder'\nimport { recordFileEdit } from '@services/fileFreshness'\nimport { triggerBackup } from '@core/backupHook'\nimport { NotebookEditTool } from '@tools/NotebookEditTool/NotebookEditTool'\nimport { DESCRIPTION } from './prompt'\nimport { applyEdit } from './utils'\nimport {\n hasWritePermission,\n pathInOriginalCwd,\n} from '@utils/permissions/filesystem'\nimport { PROJECT_FILE } from '@constants/product'\n\nconst inputSchema = z.strictObject({\n file_path: z.string().describe('The absolute path to the file to modify'),\n old_string: z.string().describe('The text to replace'),\n new_string: z\n .string()\n .describe(\n 'The text to replace it with (must be different from old_string)',\n ),\n replace_all: z\n .boolean()\n .optional()\n .default(false)\n .describe('Replace all occurences of old_string (default false)'),\n})\n\nexport type In = typeof inputSchema\n\n// Number of lines of context to include before/after the change in our result message\nconst N_LINES_SNIPPET = 4\n\nexport const FileEditTool = {\n name: 'Edit',\n async description() {\n return 'A tool for editing files'\n },\n async prompt() {\n return DESCRIPTION\n },\n inputSchema,\n userFacingName() {\n return 'Edit'\n },\n async isEnabled() {\n return true\n },\n isReadOnly() {\n return false\n },\n isConcurrencySafe() {\n return false // FileEdit modifies files, not safe for concurrent execution\n },\n needsPermissions({ file_path }) {\n return !hasWritePermission(file_path)\n },\n renderToolUseMessage(input, { verbose }) {\n const filePath = verbose\n ? input.file_path\n : relative(getCwd(), input.file_path)\n const replaceAllSuffix = input.replace_all ? ', replace_all: true' : ''\n return `file_path: ${filePath}${replaceAllSuffix}`\n },\n renderToolResultMessage(output, options?: { verbose?: boolean }) {\n const verbose = options?.verbose ?? false\n\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box justifyContent=\"space-between\" width=\"100%\">\n <Box flexDirection=\"row\">\n <Text> \u23BF </Text>\n <Text color={getTheme().secondaryText}>Edit completed</Text>\n </Box>\n </Box>\n )\n }\n\n const { filePath, structuredPatch } = output\n return (\n <FileEditToolUpdatedMessage\n filePath={filePath}\n structuredPatch={structuredPatch}\n verbose={verbose}\n />\n )\n },\n renderToolUseRejectedMessage(\n { file_path, old_string, new_string, replace_all }: any = {},\n { columns, verbose }: any = {},\n ) {\n try {\n if (!file_path) {\n return <FallbackToolUseRejectedMessage />\n }\n const { patch } = applyEdit(\n file_path,\n old_string,\n new_string,\n replace_all,\n )\n return (\n <Box flexDirection=\"column\">\n <Text>\n {' '}\u23BF{' '}\n <Text color={getTheme().error}>\n User rejected {old_string === '' ? 'write' : 'update'} to{' '}\n </Text>\n <Text bold>\n {verbose ? file_path : relative(getCwd(), file_path)}\n </Text>\n </Text>\n {intersperse(\n patch.map(patch => (\n <Box flexDirection=\"column\" paddingLeft={5} key={patch.newStart}>\n <StructuredDiff patch={patch} dim={true} width={columns - 12} />\n </Box>\n )),\n i => (\n <Box paddingLeft={5} key={`ellipsis-${i}`}>\n <Text color={getTheme().secondaryText}>...</Text>\n </Box>\n ),\n )}\n </Box>\n )\n } catch (e) {\n // Handle the case where while we were showing the diff, the user manually made the change.\n // TODO: Find a way to show the diff in this case\n logError(e)\n return (\n <Box flexDirection=\"column\">\n <Text>{' '}\u23BF (No changes)</Text>\n </Box>\n )\n }\n },\n async validateInput(\n { file_path, old_string, new_string, replace_all },\n { readFileTimestamps },\n ) {\n if (old_string === new_string) {\n return {\n result: false,\n message:\n 'No changes to make: old_string and new_string are exactly the same.',\n meta: {\n old_string,\n },\n } as ValidationResult\n }\n\n const fullFilePath = isAbsolute(file_path)\n ? file_path\n : resolve(getCwd(), file_path)\n\n // Security check: Ensure path is within allowed directory\n if (!pathInOriginalCwd(fullFilePath) && !hasWritePermission(fullFilePath)) {\n return {\n result: false,\n message:\n 'Path traversal detected - file must be within the project directory or an allowed write location.',\n }\n }\n\n // Security check: Check for symlinks that could escape directory boundaries\n if (existsSync(fullFilePath)) {\n try {\n const lstats = lstatSync(fullFilePath)\n if (lstats.isSymbolicLink()) {\n return {\n result: false,\n message:\n 'Cannot edit symbolic links for security reasons. Edit the target file directly.',\n }\n }\n } catch {\n // If we can't stat the file, let other checks handle it\n }\n }\n\n if (existsSync(fullFilePath) && old_string === '') {\n return {\n result: false,\n message: 'Cannot create new file - file already exists.',\n }\n }\n\n if (!existsSync(fullFilePath) && old_string === '') {\n return {\n result: true,\n }\n }\n\n if (!existsSync(fullFilePath)) {\n // Try to find a similar file with a different extension\n const similarFilename = findSimilarFile(fullFilePath)\n let message = 'File does not exist.'\n\n // If we found a similar file, suggest it to the assistant\n if (similarFilename) {\n message += ` Did you mean ${similarFilename}?`\n }\n\n return {\n result: false,\n message,\n }\n }\n\n if (fullFilePath.endsWith('.ipynb')) {\n return {\n result: false,\n message: `File is a Jupyter Notebook. Use the ${NotebookEditTool.name} to edit this file.`,\n }\n }\n\n const readTimestamp = readFileTimestamps[fullFilePath]\n if (!readTimestamp) {\n return {\n result: false,\n message:\n 'File has not been read yet. Read it first before writing to it.',\n meta: {\n isFilePathAbsolute: String(isAbsolute(file_path)),\n },\n }\n }\n\n // Check if file exists and get its last modified time\n const stats = statSync(fullFilePath)\n const lastWriteTime = stats.mtimeMs\n if (lastWriteTime > readTimestamp) {\n return {\n result: false,\n message:\n 'File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.',\n }\n }\n\n const enc = detectFileEncoding(fullFilePath)\n const file = readFileSync(fullFilePath, enc)\n if (!file.includes(old_string)) {\n return {\n result: false,\n message: `String to replace not found in file.`,\n meta: {\n isFilePathAbsolute: String(isAbsolute(file_path)),\n },\n }\n }\n\n const matches = file.split(old_string).length - 1\n if (matches > 1 && !replace_all) {\n return {\n result: false,\n message: `Found ${matches} matches of the string to replace. For safety, this tool only supports replacing exactly one occurrence at a time. Add more lines of context to your edit and try again, or use replace_all to change every instance.`,\n meta: {\n isFilePathAbsolute: String(isAbsolute(file_path)),\n },\n }\n }\n\n return { result: true }\n },\n async *call(\n { file_path, old_string, new_string, replace_all },\n { readFileTimestamps },\n ) {\n const { patch, updatedFile } = applyEdit(\n file_path,\n old_string,\n new_string,\n replace_all,\n )\n\n const fullFilePath = isAbsolute(file_path)\n ? file_path\n : resolve(getCwd(), file_path)\n const dir = dirname(fullFilePath)\n mkdirSync(dir, { recursive: true })\n const enc = existsSync(fullFilePath)\n ? detectFileEncoding(fullFilePath)\n : 'utf8'\n const endings = existsSync(fullFilePath)\n ? detectLineEndings(fullFilePath)\n : 'LF'\n const originalFile = existsSync(fullFilePath)\n ? readFileSync(fullFilePath, enc)\n : ''\n writeTextContent(fullFilePath, updatedFile, enc, endings)\n\n // Record Agent edit operation for file freshness tracking\n recordFileEdit(fullFilePath, updatedFile)\n triggerBackup(\n fullFilePath,\n originalFile || null,\n updatedFile,\n old_string === '' ? 'create' : 'update',\n )\n\n // Update read timestamp, to invalidate stale writes\n readFileTimestamps[fullFilePath] = statSync(fullFilePath).mtimeMs\n\n // Log when editing CLAUDE.md\n if (fullFilePath.endsWith(`${sep}${PROJECT_FILE}`)) {\n }\n\n // Emit file edited event for system reminders\n emitReminderEvent('file:edited', {\n filePath: fullFilePath,\n oldString: old_string,\n newString: new_string,\n timestamp: Date.now(),\n operation:\n old_string === '' ? 'create' : new_string === '' ? 'delete' : 'update',\n })\n\n const data = {\n filePath: file_path,\n oldString: old_string,\n newString: new_string,\n originalFile,\n structuredPatch: patch,\n }\n yield {\n type: 'result',\n data,\n resultForAssistant: this.renderResultForAssistant(data),\n }\n },\n renderResultForAssistant({ filePath, originalFile, oldString, newString }) {\n const { snippet, startLine } = getSnippet(\n originalFile || '',\n oldString,\n newString,\n )\n return `The file ${filePath} has been updated. Here's the result of running \\`cat -n\\` on a snippet of the edited file:\n${addLineNumbers({\n content: snippet,\n startLine,\n})}`\n },\n} satisfies Tool<\n typeof inputSchema,\n {\n filePath: string\n oldString: string\n newString: string\n originalFile: string\n structuredPatch: Hunk[]\n }\n>\n\nexport function getSnippet(\n initialText: string,\n oldStr: string,\n newStr: string,\n): { snippet: string; startLine: number } {\n const before = initialText.split(oldStr)[0] ?? ''\n const replacementLine = before.split(/\\r?\\n/).length - 1\n const newFileLines = initialText.replace(oldStr, newStr).split(/\\r?\\n/)\n // Calculate the start and end line numbers for the snippet\n const startLine = Math.max(0, replacementLine - N_LINES_SNIPPET)\n const endLine =\n replacementLine + N_LINES_SNIPPET + newStr.split(/\\r?\\n/).length\n // Get snippet\n const snippetLines = newFileLines.slice(startLine, endLine + 1)\n const snippet = snippetLines.join('\\n')\n return { snippet, startLine: startLine + 1 }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,YAAY,WAAW,WAAW,cAAc,gBAAgB;AACzE,SAAS,KAAK,YAAY;AAC1B,SAAS,SAAS,YAAY,UAAU,SAAS,WAAW;AAC5D,YAAY,WAAW;AACvB,SAAS,SAAS;AAClB,SAAS,kCAAkC;AAC3C,SAAS,sBAAsB;AAC/B,SAAS,sCAAsC;AAE/C,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,wBAAwB;AACjC,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAE7B,MAAM,cAAc,EAAE,aAAa;AAAA,EACjC,WAAW,EAAE,OAAO,EAAE,SAAS,yCAAyC;AAAA,EACxE,YAAY,EAAE,OAAO,EAAE,SAAS,qBAAqB;AAAA,EACrD,YAAY,EACT,OAAO,EACP;AAAA,IACC;AAAA,EACF;AAAA,EACF,aAAa,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,KAAK,EACb,SAAS,sDAAsD;AACpE,CAAC;AAKD,MAAM,kBAAkB;AAEjB,MAAM,eAAe;AAAA,EAC1B,MAAM;AAAA,EACN,MAAM,cAAc;AAClB,WAAO;AAAA,EACT;AAAA,EACA,MAAM,SAAS;AACb,WAAO;AAAA,EACT;AAAA,EACA;AAAA,EACA,iBAAiB;AACf,WAAO;AAAA,EACT;AAAA,EACA,MAAM,YAAY;AAChB,WAAO;AAAA,EACT;AAAA,EACA,aAAa;AACX,WAAO;AAAA,EACT;AAAA,EACA,oBAAoB;AAClB,WAAO;AAAA,EACT;AAAA,EACA,iBAAiB,EAAE,UAAU,GAAG;AAC9B,WAAO,CAAC,mBAAmB,SAAS;AAAA,EACtC;AAAA,EACA,qBAAqB,OAAO,EAAE,QAAQ,GAAG;AACvC,UAAM,WAAW,UACb,MAAM,YACN,SAAS,OAAO,GAAG,MAAM,SAAS;AACtC,UAAM,mBAAmB,MAAM,cAAc,wBAAwB;AACrE,WAAO,cAAc,QAAQ,GAAG,gBAAgB;AAAA,EAClD;AAAA,EACA,wBAAwB,QAAQ,SAAiC;AAC/D,UAAM,UAAU,SAAS,WAAW;AAGpC,QAAI,CAAC,QAAQ;AACX,aACE,oCAAC,OAAI,gBAAe,iBAAgB,OAAM,UACxC,oCAAC,OAAI,eAAc,SACjB,oCAAC,YAAK,qBAAoB,GAC1B,oCAAC,QAAK,OAAO,SAAS,EAAE,iBAAe,gBAAc,CACvD,CACF;AAAA,IAEJ;AAEA,UAAM,EAAE,UAAU,gBAAgB,IAAI;AACtC,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,EAEJ;AAAA,EACA,6BACE,EAAE,WAAW,YAAY,YAAY,YAAY,IAAS,CAAC,GAC3D,EAAE,SAAS,QAAQ,IAAS,CAAC,GAC7B;AACA,QAAI;AACF,UAAI,CAAC,WAAW;AACd,eAAO,oCAAC,oCAA+B;AAAA,MACzC;AACA,YAAM,EAAE,MAAM,IAAI;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,aACE,oCAAC,OAAI,eAAc,YACjB,oCAAC,YACE,MAAK,UAAE,KACR,oCAAC,QAAK,OAAO,SAAS,EAAE,SAAO,kBACd,eAAe,KAAK,UAAU,UAAS,OAAI,GAC5D,GACA,oCAAC,QAAK,MAAI,QACP,UAAU,YAAY,SAAS,OAAO,GAAG,SAAS,CACrD,CACF,GACC;AAAA,QACC,MAAM,IAAI,CAAAA,WACR,oCAAC,OAAI,eAAc,UAAS,aAAa,GAAG,KAAKA,OAAM,YACrD,oCAAC,kBAAe,OAAOA,QAAO,KAAK,MAAM,OAAO,UAAU,IAAI,CAChE,CACD;AAAA,QACD,OACE,oCAAC,OAAI,aAAa,GAAG,KAAK,YAAY,CAAC,MACrC,oCAAC,QAAK,OAAO,SAAS,EAAE,iBAAe,KAAG,CAC5C;AAAA,MAEJ,CACF;AAAA,IAEJ,SAAS,GAAG;AAGV,eAAS,CAAC;AACV,aACE,oCAAC,OAAI,eAAc,YACjB,oCAAC,YAAM,MAAK,qBAAc,CAC5B;AAAA,IAEJ;AAAA,EACF;AAAA,EACA,MAAM,cACJ,EAAE,WAAW,YAAY,YAAY,YAAY,GACjD,EAAE,mBAAmB,GACrB;AACA,QAAI,eAAe,YAAY;AAC7B,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SACE;AAAA,QACF,MAAM;AAAA,UACJ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,eAAe,WAAW,SAAS,IACrC,YACA,QAAQ,OAAO,GAAG,SAAS;AAG/B,QAAI,CAAC,kBAAkB,YAAY,KAAK,CAAC,mBAAmB,YAAY,GAAG;AACzE,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SACE;AAAA,MACJ;AAAA,IACF;AAGA,QAAI,WAAW,YAAY,GAAG;AAC5B,UAAI;AACF,cAAM,SAAS,UAAU,YAAY;AACrC,YAAI,OAAO,eAAe,GAAG;AAC3B,iBAAO;AAAA,YACL,QAAQ;AAAA,YACR,SACE;AAAA,UACJ;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,QAAI,WAAW,YAAY,KAAK,eAAe,IAAI;AACjD,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS;AAAA,MACX;AAAA,IACF;AAEA,QAAI,CAAC,WAAW,YAAY,KAAK,eAAe,IAAI;AAClD,aAAO;AAAA,QACL,QAAQ;AAAA,MACV;AAAA,IACF;AAEA,QAAI,CAAC,WAAW,YAAY,GAAG;AAE7B,YAAM,kBAAkB,gBAAgB,YAAY;AACpD,UAAI,UAAU;AAGd,UAAI,iBAAiB;AACnB,mBAAW,iBAAiB,eAAe;AAAA,MAC7C;AAEA,aAAO;AAAA,QACL,QAAQ;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,aAAa,SAAS,QAAQ,GAAG;AACnC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,uCAAuC,iBAAiB,IAAI;AAAA,MACvE;AAAA,IACF;AAEA,UAAM,gBAAgB,mBAAmB,YAAY;AACrD,QAAI,CAAC,eAAe;AAClB,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SACE;AAAA,QACF,MAAM;AAAA,UACJ,oBAAoB,OAAO,WAAW,SAAS,CAAC;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAGA,UAAM,QAAQ,SAAS,YAAY;AACnC,UAAM,gBAAgB,MAAM;AAC5B,QAAI,gBAAgB,eAAe;AACjC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SACE;AAAA,MACJ;AAAA,IACF;AAEA,UAAM,MAAM,mBAAmB,YAAY;AAC3C,UAAM,OAAO,aAAa,cAAc,GAAG;AAC3C,QAAI,CAAC,KAAK,SAAS,UAAU,GAAG;AAC9B,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAM;AAAA,UACJ,oBAAoB,OAAO,WAAW,SAAS,CAAC;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,MAAM,UAAU,EAAE,SAAS;AAChD,QAAI,UAAU,KAAK,CAAC,aAAa;AAC/B,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,SAAS,OAAO;AAAA,QACzB,MAAM;AAAA,UACJ,oBAAoB,OAAO,WAAW,SAAS,CAAC;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAAA,EACA,OAAO,KACL,EAAE,WAAW,YAAY,YAAY,YAAY,GACjD,EAAE,mBAAmB,GACrB;AACA,UAAM,EAAE,OAAO,YAAY,IAAI;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,eAAe,WAAW,SAAS,IACrC,YACA,QAAQ,OAAO,GAAG,SAAS;AAC/B,UAAM,MAAM,QAAQ,YAAY;AAChC,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,UAAM,MAAM,WAAW,YAAY,IAC/B,mBAAmB,YAAY,IAC/B;AACJ,UAAM,UAAU,WAAW,YAAY,IACnC,kBAAkB,YAAY,IAC9B;AACJ,UAAM,eAAe,WAAW,YAAY,IACxC,aAAa,cAAc,GAAG,IAC9B;AACJ,qBAAiB,cAAc,aAAa,KAAK,OAAO;AAGxD,mBAAe,cAAc,WAAW;AACxC;AAAA,MACE;AAAA,MACA,gBAAgB;AAAA,MAChB;AAAA,MACA,eAAe,KAAK,WAAW;AAAA,IACjC;AAGA,uBAAmB,YAAY,IAAI,SAAS,YAAY,EAAE;AAG1D,QAAI,aAAa,SAAS,GAAG,GAAG,GAAG,YAAY,EAAE,GAAG;AAAA,IACpD;AAGA,sBAAkB,eAAe;AAAA,MAC/B,UAAU;AAAA,MACV,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW,KAAK,IAAI;AAAA,MACpB,WACE,eAAe,KAAK,WAAW,eAAe,KAAK,WAAW;AAAA,IAClE,CAAC;AAED,UAAM,OAAO;AAAA,MACX,UAAU;AAAA,MACV,WAAW;AAAA,MACX,WAAW;AAAA,MACX;AAAA,MACA,iBAAiB;AAAA,IACnB;AACA,UAAM;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,MACA,oBAAoB,KAAK,yBAAyB,IAAI;AAAA,IACxD;AAAA,EACF;AAAA,EACA,yBAAyB,EAAE,UAAU,cAAc,WAAW,UAAU,GAAG;AACzE,UAAM,EAAE,SAAS,UAAU,IAAI;AAAA,MAC7B,gBAAgB;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AACA,WAAO,YAAY,QAAQ;AAAA,EAC7B,eAAe;AAAA,MACf,SAAS;AAAA,MACT;AAAA,IACF,CAAC,CAAC;AAAA,EACA;AACF;AAWO,SAAS,WACd,aACA,QACA,QACwC;AACxC,QAAM,SAAS,YAAY,MAAM,MAAM,EAAE,CAAC,KAAK;AAC/C,QAAM,kBAAkB,OAAO,MAAM,OAAO,EAAE,SAAS;AACvD,QAAM,eAAe,YAAY,QAAQ,QAAQ,MAAM,EAAE,MAAM,OAAO;AAEtE,QAAM,YAAY,KAAK,IAAI,GAAG,kBAAkB,eAAe;AAC/D,QAAM,UACJ,kBAAkB,kBAAkB,OAAO,MAAM,OAAO,EAAE;AAE5D,QAAM,eAAe,aAAa,MAAM,WAAW,UAAU,CAAC;AAC9D,QAAM,UAAU,aAAa,KAAK,IAAI;AACtC,SAAO,EAAE,SAAS,WAAW,YAAY,EAAE;AAC7C;",
|
|
6
6
|
"names": ["patch"]
|
|
7
7
|
}
|