fss-link 1.0.49 → 1.0.51
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/index.js +0 -0
- package/dist/package.json +2 -2
- package/dist/src/config/auth.js +8 -5
- package/dist/src/config/auth.js.map +1 -1
- package/dist/src/config/database.d.ts +103 -11
- package/dist/src/config/database.js +301 -59
- package/dist/src/config/database.js.map +1 -1
- package/dist/src/config/databaseBackup.d.ts +114 -0
- package/dist/src/config/databaseBackup.js +334 -0
- package/dist/src/config/databaseBackup.js.map +1 -0
- package/dist/src/config/databaseMigrations.d.ts +63 -0
- package/dist/src/config/databaseMigrations.js +379 -0
- package/dist/src/config/databaseMigrations.js.map +1 -0
- package/dist/src/config/databasePool.d.ts +70 -0
- package/dist/src/config/databasePool.js +193 -0
- package/dist/src/config/databasePool.js.map +1 -0
- package/dist/src/config/queryOptimizer.d.ts +127 -0
- package/dist/src/config/queryOptimizer.js +309 -0
- package/dist/src/config/queryOptimizer.js.map +1 -0
- package/dist/src/utils/sandbox.js +2 -8
- package/dist/src/utils/sandbox.js.map +1 -1
- package/dist/src/validateNonInterActiveAuth.js +3 -7
- package/dist/src/validateNonInterActiveAuth.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/dist/commands/mcp/add.test.ts +0 -122
- package/dist/commands/mcp/add.ts +0 -222
- package/dist/commands/mcp/list.test.ts +0 -154
- package/dist/commands/mcp/list.ts +0 -139
- package/dist/commands/mcp/remove.test.ts +0 -69
- package/dist/commands/mcp/remove.ts +0 -60
- package/dist/commands/mcp.test.ts +0 -55
- package/dist/commands/mcp.ts +0 -27
- package/dist/config/apiValidation.test.ts +0 -118
- package/dist/config/auth.test.ts +0 -79
- package/dist/config/auth.ts +0 -100
- package/dist/config/config.integration.test.ts +0 -407
- package/dist/config/config.test.ts +0 -1952
- package/dist/config/config.ts +0 -690
- package/dist/config/database.test.ts +0 -96
- package/dist/config/database.ts +0 -824
- package/dist/config/extension.test.ts +0 -236
- package/dist/config/extension.ts +0 -180
- package/dist/config/keyBindings.test.ts +0 -62
- package/dist/config/keyBindings.ts +0 -184
- package/dist/config/modelManager.ts +0 -326
- package/dist/config/providerManager.ts +0 -244
- package/dist/config/providerPersistence.test.ts +0 -377
- package/dist/config/providerPersistence.ts +0 -105
- package/dist/config/sandboxConfig.ts +0 -107
- package/dist/config/settings.test.ts +0 -1424
- package/dist/config/settings.ts +0 -517
- package/dist/config/settingsSchema.test.ts +0 -252
- package/dist/config/settingsSchema.ts +0 -728
- package/dist/config/trustedFolders.test.ts +0 -208
- package/dist/config/trustedFolders.ts +0 -167
- package/dist/gemini.test.tsx +0 -252
- package/dist/gemini.tsx +0 -357
- package/dist/generated/git-commit.ts +0 -10
- package/dist/index.ts +0 -21
- package/dist/nonInteractiveCli.test.ts +0 -276
- package/dist/nonInteractiveCli.ts +0 -143
- package/dist/patches/is-in-ci.ts +0 -17
- package/dist/services/BuiltinCommandLoader.test.ts +0 -127
- package/dist/services/BuiltinCommandLoader.ts +0 -95
- package/dist/services/CommandService.test.ts +0 -352
- package/dist/services/CommandService.ts +0 -103
- package/dist/services/FileCommandLoader.test.ts +0 -1002
- package/dist/services/FileCommandLoader.ts +0 -289
- package/dist/services/McpPromptLoader.ts +0 -231
- package/dist/services/SearchEngineConfigProvider.ts +0 -100
- package/dist/services/prompt-processors/argumentProcessor.test.ts +0 -41
- package/dist/services/prompt-processors/argumentProcessor.ts +0 -23
- package/dist/services/prompt-processors/shellProcessor.test.ts +0 -709
- package/dist/services/prompt-processors/shellProcessor.ts +0 -248
- package/dist/services/prompt-processors/types.ts +0 -44
- package/dist/services/types.ts +0 -24
- package/dist/src/config/apiValidation.test.d.ts +0 -6
- package/dist/src/config/apiValidation.test.js +0 -99
- package/dist/src/config/apiValidation.test.js.map +0 -1
- package/dist/src/config/database.test.d.ts +0 -6
- package/dist/src/config/database.test.js +0 -80
- package/dist/src/config/database.test.js.map +0 -1
- package/dist/src/config/providerManager.d.ts +0 -74
- package/dist/src/config/providerManager.js +0 -203
- package/dist/src/config/providerManager.js.map +0 -1
- package/dist/src/config/providerPersistence.test.d.ts +0 -6
- package/dist/src/config/providerPersistence.test.js +0 -283
- package/dist/src/config/providerPersistence.test.js.map +0 -1
- package/dist/src/ui/components/GeminiKeyDialog.d.ts +0 -11
- package/dist/src/ui/components/GeminiKeyDialog.js +0 -156
- package/dist/src/ui/components/GeminiKeyDialog.js.map +0 -1
- package/dist/src/ui/components/OpenAIEndpointDialog.d.ts +0 -19
- package/dist/src/ui/components/OpenAIEndpointDialog.js +0 -163
- package/dist/src/ui/components/OpenAIEndpointDialog.js.map +0 -1
- package/dist/test-setup.ts +0 -12
- package/dist/test-utils/customMatchers.ts +0 -65
- package/dist/test-utils/mockCommandContext.test.ts +0 -62
- package/dist/test-utils/mockCommandContext.ts +0 -105
- package/dist/test-utils/render.tsx +0 -18
- package/dist/ui/App.test.tsx +0 -2181
- package/dist/ui/App.tsx +0 -1344
- package/dist/ui/IdeIntegrationNudge.tsx +0 -98
- package/dist/ui/__snapshots__/App.test.tsx.snap +0 -124
- package/dist/ui/colors.ts +0 -56
- package/dist/ui/commands/aboutCommand.test.ts +0 -153
- package/dist/ui/commands/aboutCommand.ts +0 -49
- package/dist/ui/commands/authCommand.test.ts +0 -36
- package/dist/ui/commands/authCommand.ts +0 -17
- package/dist/ui/commands/bugCommand.test.ts +0 -114
- package/dist/ui/commands/bugCommand.ts +0 -92
- package/dist/ui/commands/chatCommand.test.ts +0 -414
- package/dist/ui/commands/chatCommand.ts +0 -280
- package/dist/ui/commands/clearCommand.test.ts +0 -100
- package/dist/ui/commands/clearCommand.ts +0 -29
- package/dist/ui/commands/compressCommand.test.ts +0 -129
- package/dist/ui/commands/compressCommand.ts +0 -78
- package/dist/ui/commands/contextCommand.ts +0 -132
- package/dist/ui/commands/copyCommand.test.ts +0 -296
- package/dist/ui/commands/copyCommand.ts +0 -67
- package/dist/ui/commands/corgiCommand.test.ts +0 -34
- package/dist/ui/commands/corgiCommand.ts +0 -16
- package/dist/ui/commands/directoryCommand.test.tsx +0 -185
- package/dist/ui/commands/directoryCommand.tsx +0 -179
- package/dist/ui/commands/docsCommand.test.ts +0 -99
- package/dist/ui/commands/docsCommand.ts +0 -42
- package/dist/ui/commands/editorCommand.test.ts +0 -30
- package/dist/ui/commands/editorCommand.ts +0 -21
- package/dist/ui/commands/extensionsCommand.test.ts +0 -67
- package/dist/ui/commands/extensionsCommand.ts +0 -46
- package/dist/ui/commands/helpCommand.test.ts +0 -52
- package/dist/ui/commands/helpCommand.ts +0 -23
- package/dist/ui/commands/ideCommand.test.ts +0 -255
- package/dist/ui/commands/ideCommand.ts +0 -283
- package/dist/ui/commands/initCommand.test.ts +0 -127
- package/dist/ui/commands/initCommand.ts +0 -117
- package/dist/ui/commands/mcpCommand.test.ts +0 -1057
- package/dist/ui/commands/mcpCommand.ts +0 -531
- package/dist/ui/commands/memoryCommand.test.ts +0 -344
- package/dist/ui/commands/memoryCommand.ts +0 -305
- package/dist/ui/commands/privacyCommand.test.ts +0 -38
- package/dist/ui/commands/privacyCommand.ts +0 -17
- package/dist/ui/commands/quitCommand.test.ts +0 -55
- package/dist/ui/commands/quitCommand.ts +0 -36
- package/dist/ui/commands/restoreCommand.test.ts +0 -250
- package/dist/ui/commands/restoreCommand.ts +0 -157
- package/dist/ui/commands/searchEngineSetupCommand.ts +0 -18
- package/dist/ui/commands/settingsCommand.test.ts +0 -36
- package/dist/ui/commands/settingsCommand.ts +0 -17
- package/dist/ui/commands/setupGithubCommand.test.ts +0 -238
- package/dist/ui/commands/setupGithubCommand.ts +0 -212
- package/dist/ui/commands/speakCommand.ts +0 -175
- package/dist/ui/commands/statsCommand.test.ts +0 -78
- package/dist/ui/commands/statsCommand.ts +0 -70
- package/dist/ui/commands/terminalSetupCommand.test.ts +0 -85
- package/dist/ui/commands/terminalSetupCommand.ts +0 -45
- package/dist/ui/commands/themeCommand.test.ts +0 -38
- package/dist/ui/commands/themeCommand.ts +0 -17
- package/dist/ui/commands/toolsCommand.test.ts +0 -105
- package/dist/ui/commands/toolsCommand.ts +0 -71
- package/dist/ui/commands/ttsCommand.ts +0 -143
- package/dist/ui/commands/types.ts +0 -204
- package/dist/ui/commands/vimCommand.ts +0 -25
- package/dist/ui/commands/voiceCommand.ts +0 -125
- package/dist/ui/components/AboutBox.tsx +0 -133
- package/dist/ui/components/AsciiArt.ts +0 -54
- package/dist/ui/components/AuthDialog.test.tsx +0 -334
- package/dist/ui/components/AuthDialog.tsx +0 -289
- package/dist/ui/components/AuthInProgress.tsx +0 -62
- package/dist/ui/components/AutoAcceptIndicator.tsx +0 -47
- package/dist/ui/components/ConsoleSummaryDisplay.tsx +0 -35
- package/dist/ui/components/ContextSummaryDisplay.test.tsx +0 -85
- package/dist/ui/components/ContextSummaryDisplay.tsx +0 -120
- package/dist/ui/components/ContextUsageDisplay.tsx +0 -77
- package/dist/ui/components/DebugProfiler.tsx +0 -36
- package/dist/ui/components/DetailedMessagesDisplay.tsx +0 -82
- package/dist/ui/components/EditorSettingsDialog.tsx +0 -172
- package/dist/ui/components/FolderTrustDialog.test.tsx +0 -36
- package/dist/ui/components/FolderTrustDialog.tsx +0 -74
- package/dist/ui/components/Footer.test.tsx +0 -159
- package/dist/ui/components/Footer.tsx +0 -158
- package/dist/ui/components/GeminiKeyDialog.tsx +0 -252
- package/dist/ui/components/GeminiRespondingSpinner.tsx +0 -34
- package/dist/ui/components/Header.test.tsx +0 -44
- package/dist/ui/components/Header.tsx +0 -70
- package/dist/ui/components/Help.tsx +0 -174
- package/dist/ui/components/HistoryItemDisplay.test.tsx +0 -125
- package/dist/ui/components/HistoryItemDisplay.tsx +0 -98
- package/dist/ui/components/InputPrompt.test.tsx +0 -1467
- package/dist/ui/components/InputPrompt.tsx +0 -641
- package/dist/ui/components/LMStudioModelPrompt.tsx +0 -215
- package/dist/ui/components/LoadingIndicator.test.tsx +0 -296
- package/dist/ui/components/LoadingIndicator.tsx +0 -82
- package/dist/ui/components/MemoryUsageDisplay.tsx +0 -36
- package/dist/ui/components/ModelStatsDisplay.test.tsx +0 -252
- package/dist/ui/components/ModelStatsDisplay.tsx +0 -197
- package/dist/ui/components/OllamaModelPrompt.tsx +0 -206
- package/dist/ui/components/OpenAIEndpointDialog.tsx +0 -261
- package/dist/ui/components/OpenAIKeyPrompt.test.tsx +0 -64
- package/dist/ui/components/OpenAIKeyPrompt.tsx +0 -197
- package/dist/ui/components/PrepareLabel.tsx +0 -48
- package/dist/ui/components/SearchEngineConfigDialog.tsx +0 -280
- package/dist/ui/components/SessionSummaryDisplay.test.tsx +0 -75
- package/dist/ui/components/SessionSummaryDisplay.tsx +0 -18
- package/dist/ui/components/SettingsDialog.test.tsx +0 -865
- package/dist/ui/components/SettingsDialog.tsx +0 -753
- package/dist/ui/components/ShellConfirmationDialog.test.tsx +0 -53
- package/dist/ui/components/ShellConfirmationDialog.tsx +0 -103
- package/dist/ui/components/ShellModeIndicator.tsx +0 -18
- package/dist/ui/components/ShowMoreLines.tsx +0 -40
- package/dist/ui/components/StatsDisplay.test.tsx +0 -401
- package/dist/ui/components/StatsDisplay.tsx +0 -273
- package/dist/ui/components/SuggestionsDisplay.tsx +0 -102
- package/dist/ui/components/ThemeDialog.tsx +0 -310
- package/dist/ui/components/Tips.tsx +0 -45
- package/dist/ui/components/TodoDisplay.test.tsx +0 -97
- package/dist/ui/components/TodoDisplay.tsx +0 -72
- package/dist/ui/components/ToolStatsDisplay.test.tsx +0 -180
- package/dist/ui/components/ToolStatsDisplay.tsx +0 -208
- package/dist/ui/components/UpdateNotification.tsx +0 -23
- package/dist/ui/components/WelcomeBackDialog.tsx +0 -290
- package/dist/ui/components/__snapshots__/IDEContextDetailDisplay.test.tsx.snap +0 -24
- package/dist/ui/components/__snapshots__/ModelStatsDisplay.test.tsx.snap +0 -121
- package/dist/ui/components/__snapshots__/SessionSummaryDisplay.test.tsx.snap +0 -30
- package/dist/ui/components/__snapshots__/ShellConfirmationDialog.test.tsx.snap +0 -21
- package/dist/ui/components/__snapshots__/StatsDisplay.test.tsx.snap +0 -264
- package/dist/ui/components/__snapshots__/ToolStatsDisplay.test.tsx.snap +0 -91
- package/dist/ui/components/messages/CompressionMessage.tsx +0 -49
- package/dist/ui/components/messages/DiffRenderer.test.tsx +0 -365
- package/dist/ui/components/messages/DiffRenderer.tsx +0 -358
- package/dist/ui/components/messages/ErrorMessage.tsx +0 -31
- package/dist/ui/components/messages/GeminiMessage.tsx +0 -43
- package/dist/ui/components/messages/GeminiMessageContent.tsx +0 -43
- package/dist/ui/components/messages/InfoMessage.tsx +0 -32
- package/dist/ui/components/messages/ToolConfirmationMessage.test.tsx +0 -58
- package/dist/ui/components/messages/ToolConfirmationMessage.tsx +0 -297
- package/dist/ui/components/messages/ToolGroupMessage.tsx +0 -126
- package/dist/ui/components/messages/ToolMessage.test.tsx +0 -183
- package/dist/ui/components/messages/ToolMessage.tsx +0 -296
- package/dist/ui/components/messages/UserMessage.tsx +0 -43
- package/dist/ui/components/messages/UserShellMessage.tsx +0 -25
- package/dist/ui/components/shared/MaxSizedBox.test.tsx +0 -425
- package/dist/ui/components/shared/MaxSizedBox.tsx +0 -624
- package/dist/ui/components/shared/RadioButtonSelect.test.tsx +0 -181
- package/dist/ui/components/shared/RadioButtonSelect.tsx +0 -234
- package/dist/ui/components/shared/__snapshots__/RadioButtonSelect.test.tsx.snap +0 -47
- package/dist/ui/components/shared/text-buffer.test.ts +0 -1728
- package/dist/ui/components/shared/text-buffer.ts +0 -2227
- package/dist/ui/components/shared/vim-buffer-actions.test.ts +0 -1119
- package/dist/ui/components/shared/vim-buffer-actions.ts +0 -814
- package/dist/ui/constants.ts +0 -17
- package/dist/ui/contexts/KeypressContext.test.tsx +0 -391
- package/dist/ui/contexts/KeypressContext.tsx +0 -440
- package/dist/ui/contexts/OverflowContext.tsx +0 -87
- package/dist/ui/contexts/SessionContext.test.tsx +0 -132
- package/dist/ui/contexts/SessionContext.tsx +0 -143
- package/dist/ui/contexts/SettingsContext.tsx +0 -20
- package/dist/ui/contexts/StreamingContext.tsx +0 -22
- package/dist/ui/contexts/VimModeContext.tsx +0 -79
- package/dist/ui/editors/editorSettingsManager.ts +0 -66
- package/dist/ui/hooks/atCommandProcessor.test.ts +0 -1102
- package/dist/ui/hooks/atCommandProcessor.ts +0 -485
- package/dist/ui/hooks/shellCommandProcessor.test.ts +0 -481
- package/dist/ui/hooks/shellCommandProcessor.ts +0 -314
- package/dist/ui/hooks/slashCommandProcessor.test.ts +0 -1044
- package/dist/ui/hooks/slashCommandProcessor.ts +0 -595
- package/dist/ui/hooks/useAtCompletion.test.ts +0 -497
- package/dist/ui/hooks/useAtCompletion.ts +0 -244
- package/dist/ui/hooks/useAuthCommand.ts +0 -129
- package/dist/ui/hooks/useAutoAcceptIndicator.test.ts +0 -300
- package/dist/ui/hooks/useAutoAcceptIndicator.ts +0 -52
- package/dist/ui/hooks/useBracketedPaste.ts +0 -37
- package/dist/ui/hooks/useCommandCompletion.test.ts +0 -518
- package/dist/ui/hooks/useCommandCompletion.tsx +0 -238
- package/dist/ui/hooks/useCompletion.ts +0 -128
- package/dist/ui/hooks/useConsoleMessages.test.ts +0 -147
- package/dist/ui/hooks/useConsoleMessages.ts +0 -110
- package/dist/ui/hooks/useEditorSettings.test.ts +0 -283
- package/dist/ui/hooks/useEditorSettings.ts +0 -75
- package/dist/ui/hooks/useFocus.test.ts +0 -119
- package/dist/ui/hooks/useFocus.ts +0 -48
- package/dist/ui/hooks/useFolderTrust.test.ts +0 -159
- package/dist/ui/hooks/useFolderTrust.ts +0 -72
- package/dist/ui/hooks/useGeminiStream.test.tsx +0 -1998
- package/dist/ui/hooks/useGeminiStream.ts +0 -1017
- package/dist/ui/hooks/useGitBranchName.test.ts +0 -280
- package/dist/ui/hooks/useGitBranchName.ts +0 -79
- package/dist/ui/hooks/useHistoryManager.test.ts +0 -202
- package/dist/ui/hooks/useHistoryManager.ts +0 -111
- package/dist/ui/hooks/useInputHistory.test.ts +0 -261
- package/dist/ui/hooks/useInputHistory.ts +0 -111
- package/dist/ui/hooks/useKeypress.test.ts +0 -280
- package/dist/ui/hooks/useKeypress.ts +0 -39
- package/dist/ui/hooks/useKittyKeyboardProtocol.ts +0 -31
- package/dist/ui/hooks/useLoadingIndicator.test.ts +0 -139
- package/dist/ui/hooks/useLoadingIndicator.ts +0 -57
- package/dist/ui/hooks/useLogger.ts +0 -32
- package/dist/ui/hooks/useMessageQueue.test.ts +0 -226
- package/dist/ui/hooks/useMessageQueue.ts +0 -69
- package/dist/ui/hooks/usePhraseCycler.test.ts +0 -145
- package/dist/ui/hooks/usePhraseCycler.ts +0 -198
- package/dist/ui/hooks/usePrivacySettings.test.ts +0 -242
- package/dist/ui/hooks/usePrivacySettings.ts +0 -150
- package/dist/ui/hooks/useReactToolScheduler.ts +0 -309
- package/dist/ui/hooks/useRefreshMemoryCommand.ts +0 -7
- package/dist/ui/hooks/useReverseSearchCompletion.test.tsx +0 -260
- package/dist/ui/hooks/useReverseSearchCompletion.tsx +0 -95
- package/dist/ui/hooks/useSettingsCommand.ts +0 -25
- package/dist/ui/hooks/useShellHistory.test.ts +0 -219
- package/dist/ui/hooks/useShellHistory.ts +0 -133
- package/dist/ui/hooks/useShowMemoryCommand.ts +0 -75
- package/dist/ui/hooks/useSlashCompletion.test.ts +0 -434
- package/dist/ui/hooks/useSlashCompletion.ts +0 -187
- package/dist/ui/hooks/useStateAndRef.ts +0 -36
- package/dist/ui/hooks/useTerminalSize.ts +0 -32
- package/dist/ui/hooks/useThemeCommand.ts +0 -110
- package/dist/ui/hooks/useTimer.test.ts +0 -120
- package/dist/ui/hooks/useTimer.ts +0 -65
- package/dist/ui/hooks/useToolScheduler.test.ts +0 -1123
- package/dist/ui/hooks/useWelcomeBack.ts +0 -253
- package/dist/ui/hooks/vim.test.ts +0 -1691
- package/dist/ui/hooks/vim.ts +0 -784
- package/dist/ui/keyMatchers.test.ts +0 -337
- package/dist/ui/keyMatchers.ts +0 -105
- package/dist/ui/privacy/CloudFreePrivacyNotice.tsx +0 -117
- package/dist/ui/privacy/CloudPaidPrivacyNotice.tsx +0 -59
- package/dist/ui/privacy/GeminiPrivacyNotice.tsx +0 -62
- package/dist/ui/privacy/PrivacyNotice.tsx +0 -42
- package/dist/ui/semantic-colors.ts +0 -26
- package/dist/ui/themes/ansi-light.ts +0 -150
- package/dist/ui/themes/ansi.ts +0 -159
- package/dist/ui/themes/atom-one-dark.ts +0 -147
- package/dist/ui/themes/ayu-light.ts +0 -139
- package/dist/ui/themes/ayu.ts +0 -113
- package/dist/ui/themes/color-utils.test.ts +0 -221
- package/dist/ui/themes/color-utils.ts +0 -231
- package/dist/ui/themes/default-light.ts +0 -108
- package/dist/ui/themes/default.ts +0 -151
- package/dist/ui/themes/dracula.ts +0 -124
- package/dist/ui/themes/fss-code-dark.ts +0 -156
- package/dist/ui/themes/fss-dark.ts +0 -113
- package/dist/ui/themes/fss-light.ts +0 -139
- package/dist/ui/themes/github-dark.ts +0 -147
- package/dist/ui/themes/github-light.ts +0 -149
- package/dist/ui/themes/googlecode.ts +0 -146
- package/dist/ui/themes/no-color.ts +0 -125
- package/dist/ui/themes/qwen-dark.ts +0 -118
- package/dist/ui/themes/qwen-light.ts +0 -144
- package/dist/ui/themes/semantic-tokens.ts +0 -127
- package/dist/ui/themes/shades-of-purple.ts +0 -352
- package/dist/ui/themes/theme-manager.test.ts +0 -99
- package/dist/ui/themes/theme-manager.ts +0 -257
- package/dist/ui/themes/theme.test.ts +0 -97
- package/dist/ui/themes/theme.ts +0 -451
- package/dist/ui/themes/xcode.ts +0 -154
- package/dist/ui/types.ts +0 -255
- package/dist/ui/utils/CodeColorizer.tsx +0 -217
- package/dist/ui/utils/ConsolePatcher.ts +0 -71
- package/dist/ui/utils/InlineMarkdownRenderer.tsx +0 -173
- package/dist/ui/utils/MarkdownDisplay.test.tsx +0 -244
- package/dist/ui/utils/MarkdownDisplay.tsx +0 -415
- package/dist/ui/utils/TableRenderer.tsx +0 -159
- package/dist/ui/utils/__snapshots__/MarkdownDisplay.test.tsx.snap +0 -93
- package/dist/ui/utils/clipboardUtils.test.ts +0 -76
- package/dist/ui/utils/clipboardUtils.ts +0 -149
- package/dist/ui/utils/commandUtils.test.ts +0 -384
- package/dist/ui/utils/commandUtils.ts +0 -106
- package/dist/ui/utils/computeStats.test.ts +0 -292
- package/dist/ui/utils/computeStats.ts +0 -86
- package/dist/ui/utils/displayUtils.test.ts +0 -58
- package/dist/ui/utils/displayUtils.ts +0 -32
- package/dist/ui/utils/formatters.test.ts +0 -72
- package/dist/ui/utils/formatters.ts +0 -63
- package/dist/ui/utils/isNarrowWidth.ts +0 -9
- package/dist/ui/utils/kittyProtocolDetector.ts +0 -105
- package/dist/ui/utils/markdownUtilities.test.ts +0 -50
- package/dist/ui/utils/markdownUtilities.ts +0 -125
- package/dist/ui/utils/platformConstants.ts +0 -52
- package/dist/ui/utils/terminalSetup.ts +0 -342
- package/dist/ui/utils/textUtils.ts +0 -40
- package/dist/ui/utils/updateCheck.test.ts +0 -163
- package/dist/ui/utils/updateCheck.ts +0 -100
- package/dist/utils/checks.ts +0 -28
- package/dist/utils/cleanup.test.ts +0 -68
- package/dist/utils/cleanup.ts +0 -36
- package/dist/utils/dialogScopeUtils.ts +0 -64
- package/dist/utils/events.ts +0 -14
- package/dist/utils/gitUtils.test.ts +0 -149
- package/dist/utils/gitUtils.ts +0 -116
- package/dist/utils/handleAutoUpdate.test.ts +0 -272
- package/dist/utils/handleAutoUpdate.ts +0 -145
- package/dist/utils/installationInfo.test.ts +0 -315
- package/dist/utils/installationInfo.ts +0 -176
- package/dist/utils/package.ts +0 -38
- package/dist/utils/readStdin.ts +0 -51
- package/dist/utils/resolvePath.ts +0 -21
- package/dist/utils/sandbox-macos-permissive-closed.sb +0 -32
- package/dist/utils/sandbox-macos-permissive-open.sb +0 -25
- package/dist/utils/sandbox-macos-permissive-proxied.sb +0 -37
- package/dist/utils/sandbox-macos-restrictive-closed.sb +0 -93
- package/dist/utils/sandbox-macos-restrictive-open.sb +0 -96
- package/dist/utils/sandbox-macos-restrictive-proxied.sb +0 -98
- package/dist/utils/sandbox.ts +0 -962
- package/dist/utils/settingsUtils.test.ts +0 -797
- package/dist/utils/settingsUtils.ts +0 -489
- package/dist/utils/spawnWrapper.ts +0 -9
- package/dist/utils/startupWarnings.test.ts +0 -83
- package/dist/utils/startupWarnings.ts +0 -40
- package/dist/utils/updateEventEmitter.ts +0 -13
- package/dist/utils/userStartupWarnings.test.ts +0 -87
- package/dist/utils/userStartupWarnings.ts +0 -69
- package/dist/utils/version.ts +0 -12
- package/dist/validateNonInterActiveAuth.test.ts +0 -260
- package/dist/validateNonInterActiveAuth.ts +0 -51
- package/dist/vitest.config.ts +0 -37
- package/dist/zed-integration/acp.ts +0 -366
- package/dist/zed-integration/fileSystemService.ts +0 -47
- package/dist/zed-integration/schema.ts +0 -466
- package/dist/zed-integration/zedIntegration.ts +0 -944
|
@@ -1,1002 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as path from 'node:path';
|
|
8
|
-
import {
|
|
9
|
-
Config,
|
|
10
|
-
getProjectCommandsDir,
|
|
11
|
-
getUserCommandsDir,
|
|
12
|
-
} from 'fss-link-core';
|
|
13
|
-
import mock from 'mock-fs';
|
|
14
|
-
import { FileCommandLoader } from './FileCommandLoader.js';
|
|
15
|
-
import { assert, vi } from 'vitest';
|
|
16
|
-
import { createMockCommandContext } from '../test-utils/mockCommandContext.js';
|
|
17
|
-
import {
|
|
18
|
-
SHELL_INJECTION_TRIGGER,
|
|
19
|
-
SHORTHAND_ARGS_PLACEHOLDER,
|
|
20
|
-
} from './prompt-processors/types.js';
|
|
21
|
-
import {
|
|
22
|
-
ConfirmationRequiredError,
|
|
23
|
-
ShellProcessor,
|
|
24
|
-
} from './prompt-processors/shellProcessor.js';
|
|
25
|
-
import { DefaultArgumentProcessor } from './prompt-processors/argumentProcessor.js';
|
|
26
|
-
import { CommandContext } from '../ui/commands/types.js';
|
|
27
|
-
|
|
28
|
-
const mockShellProcess = vi.hoisted(() => vi.fn());
|
|
29
|
-
vi.mock('./prompt-processors/shellProcessor.js', () => ({
|
|
30
|
-
ShellProcessor: vi.fn().mockImplementation(() => ({
|
|
31
|
-
process: mockShellProcess,
|
|
32
|
-
})),
|
|
33
|
-
ConfirmationRequiredError: class extends Error {
|
|
34
|
-
constructor(
|
|
35
|
-
message: string,
|
|
36
|
-
public commandsToConfirm: string[],
|
|
37
|
-
) {
|
|
38
|
-
super(message);
|
|
39
|
-
this.name = 'ConfirmationRequiredError';
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
}));
|
|
43
|
-
|
|
44
|
-
vi.mock('./prompt-processors/argumentProcessor.js', async (importOriginal) => {
|
|
45
|
-
const original =
|
|
46
|
-
await importOriginal<
|
|
47
|
-
typeof import('./prompt-processors/argumentProcessor.js')
|
|
48
|
-
>();
|
|
49
|
-
return {
|
|
50
|
-
DefaultArgumentProcessor: vi
|
|
51
|
-
.fn()
|
|
52
|
-
.mockImplementation(() => new original.DefaultArgumentProcessor()),
|
|
53
|
-
};
|
|
54
|
-
});
|
|
55
|
-
vi.mock('fss-link-core', async (importOriginal) => {
|
|
56
|
-
const original =
|
|
57
|
-
await importOriginal<typeof import('fss-link-core')>();
|
|
58
|
-
return {
|
|
59
|
-
...original,
|
|
60
|
-
isCommandAllowed: vi.fn(),
|
|
61
|
-
ShellExecutionService: {
|
|
62
|
-
execute: vi.fn(),
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe('FileCommandLoader', () => {
|
|
68
|
-
const signal: AbortSignal = new AbortController().signal;
|
|
69
|
-
|
|
70
|
-
beforeEach(() => {
|
|
71
|
-
vi.clearAllMocks();
|
|
72
|
-
mockShellProcess.mockImplementation(
|
|
73
|
-
(prompt: string, context: CommandContext) => {
|
|
74
|
-
const userArgsRaw = context?.invocation?.args || '';
|
|
75
|
-
const processedPrompt = prompt.replaceAll(
|
|
76
|
-
SHORTHAND_ARGS_PLACEHOLDER,
|
|
77
|
-
userArgsRaw,
|
|
78
|
-
);
|
|
79
|
-
return Promise.resolve(processedPrompt);
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
afterEach(() => {
|
|
85
|
-
mock.restore();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('loads a single command from a file', async () => {
|
|
89
|
-
const userCommandsDir = getUserCommandsDir();
|
|
90
|
-
mock({
|
|
91
|
-
[userCommandsDir]: {
|
|
92
|
-
'test.toml': 'prompt = "This is a test prompt"',
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const loader = new FileCommandLoader(null);
|
|
97
|
-
const commands = await loader.loadCommands(signal);
|
|
98
|
-
|
|
99
|
-
expect(commands).toHaveLength(1);
|
|
100
|
-
const command = commands[0];
|
|
101
|
-
expect(command).toBeDefined();
|
|
102
|
-
expect(command.name).toBe('test');
|
|
103
|
-
|
|
104
|
-
const result = await command.action?.(
|
|
105
|
-
createMockCommandContext({
|
|
106
|
-
invocation: {
|
|
107
|
-
raw: '/test',
|
|
108
|
-
name: 'test',
|
|
109
|
-
args: '',
|
|
110
|
-
},
|
|
111
|
-
}),
|
|
112
|
-
'',
|
|
113
|
-
);
|
|
114
|
-
if (result?.type === 'submit_prompt') {
|
|
115
|
-
expect(result.content).toBe('This is a test prompt');
|
|
116
|
-
} else {
|
|
117
|
-
assert.fail('Incorrect action type');
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Symlink creation on Windows requires special permissions that are not
|
|
122
|
-
// available in the standard CI environment. Therefore, we skip these tests
|
|
123
|
-
// on Windows to prevent CI failures. The core functionality is still
|
|
124
|
-
// validated on Linux and macOS.
|
|
125
|
-
const itif = (condition: boolean) => (condition ? it : it.skip);
|
|
126
|
-
|
|
127
|
-
itif(process.platform !== 'win32')(
|
|
128
|
-
'loads commands from a symlinked directory',
|
|
129
|
-
async () => {
|
|
130
|
-
const userCommandsDir = getUserCommandsDir();
|
|
131
|
-
const realCommandsDir = '/real/commands';
|
|
132
|
-
mock({
|
|
133
|
-
[realCommandsDir]: {
|
|
134
|
-
'test.toml': 'prompt = "This is a test prompt"',
|
|
135
|
-
},
|
|
136
|
-
// Symlink the user commands directory to the real one
|
|
137
|
-
[userCommandsDir]: mock.symlink({
|
|
138
|
-
path: realCommandsDir,
|
|
139
|
-
}),
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
143
|
-
const commands = await loader.loadCommands(signal);
|
|
144
|
-
|
|
145
|
-
expect(commands).toHaveLength(1);
|
|
146
|
-
const command = commands[0];
|
|
147
|
-
expect(command).toBeDefined();
|
|
148
|
-
expect(command.name).toBe('test');
|
|
149
|
-
},
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
itif(process.platform !== 'win32')(
|
|
153
|
-
'loads commands from a symlinked subdirectory',
|
|
154
|
-
async () => {
|
|
155
|
-
const userCommandsDir = getUserCommandsDir();
|
|
156
|
-
const realNamespacedDir = '/real/namespaced-commands';
|
|
157
|
-
mock({
|
|
158
|
-
[userCommandsDir]: {
|
|
159
|
-
namespaced: mock.symlink({
|
|
160
|
-
path: realNamespacedDir,
|
|
161
|
-
}),
|
|
162
|
-
},
|
|
163
|
-
[realNamespacedDir]: {
|
|
164
|
-
'my-test.toml': 'prompt = "This is a test prompt"',
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
169
|
-
const commands = await loader.loadCommands(signal);
|
|
170
|
-
|
|
171
|
-
expect(commands).toHaveLength(1);
|
|
172
|
-
const command = commands[0];
|
|
173
|
-
expect(command).toBeDefined();
|
|
174
|
-
expect(command.name).toBe('namespaced:my-test');
|
|
175
|
-
},
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
it('loads multiple commands', async () => {
|
|
179
|
-
const userCommandsDir = getUserCommandsDir();
|
|
180
|
-
mock({
|
|
181
|
-
[userCommandsDir]: {
|
|
182
|
-
'test1.toml': 'prompt = "Prompt 1"',
|
|
183
|
-
'test2.toml': 'prompt = "Prompt 2"',
|
|
184
|
-
},
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const loader = new FileCommandLoader(null);
|
|
188
|
-
const commands = await loader.loadCommands(signal);
|
|
189
|
-
|
|
190
|
-
expect(commands).toHaveLength(2);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('creates deeply nested namespaces correctly', async () => {
|
|
194
|
-
const userCommandsDir = getUserCommandsDir();
|
|
195
|
-
|
|
196
|
-
mock({
|
|
197
|
-
[userCommandsDir]: {
|
|
198
|
-
gcp: {
|
|
199
|
-
pipelines: {
|
|
200
|
-
'run.toml': 'prompt = "run pipeline"',
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
const mockConfig = {
|
|
206
|
-
getProjectRoot: vi.fn(() => '/path/to/project'),
|
|
207
|
-
getExtensions: vi.fn(() => []),
|
|
208
|
-
} as unknown as Config;
|
|
209
|
-
const loader = new FileCommandLoader(mockConfig);
|
|
210
|
-
const commands = await loader.loadCommands(signal);
|
|
211
|
-
expect(commands).toHaveLength(1);
|
|
212
|
-
expect(commands[0]!.name).toBe('gcp:pipelines:run');
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('creates namespaces from nested directories', async () => {
|
|
216
|
-
const userCommandsDir = getUserCommandsDir();
|
|
217
|
-
mock({
|
|
218
|
-
[userCommandsDir]: {
|
|
219
|
-
git: {
|
|
220
|
-
'commit.toml': 'prompt = "git commit prompt"',
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
const loader = new FileCommandLoader(null);
|
|
226
|
-
const commands = await loader.loadCommands(signal);
|
|
227
|
-
|
|
228
|
-
expect(commands).toHaveLength(1);
|
|
229
|
-
const command = commands[0];
|
|
230
|
-
expect(command).toBeDefined();
|
|
231
|
-
expect(command.name).toBe('git:commit');
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('returns both user and project commands in order', async () => {
|
|
235
|
-
const userCommandsDir = getUserCommandsDir();
|
|
236
|
-
const projectCommandsDir = getProjectCommandsDir(process.cwd());
|
|
237
|
-
mock({
|
|
238
|
-
[userCommandsDir]: {
|
|
239
|
-
'test.toml': 'prompt = "User prompt"',
|
|
240
|
-
},
|
|
241
|
-
[projectCommandsDir]: {
|
|
242
|
-
'test.toml': 'prompt = "Project prompt"',
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const mockConfig = {
|
|
247
|
-
getProjectRoot: vi.fn(() => process.cwd()),
|
|
248
|
-
getExtensions: vi.fn(() => []),
|
|
249
|
-
} as unknown as Config;
|
|
250
|
-
const loader = new FileCommandLoader(mockConfig);
|
|
251
|
-
const commands = await loader.loadCommands(signal);
|
|
252
|
-
|
|
253
|
-
expect(commands).toHaveLength(2);
|
|
254
|
-
const userResult = await commands[0].action?.(
|
|
255
|
-
createMockCommandContext({
|
|
256
|
-
invocation: {
|
|
257
|
-
raw: '/test',
|
|
258
|
-
name: 'test',
|
|
259
|
-
args: '',
|
|
260
|
-
},
|
|
261
|
-
}),
|
|
262
|
-
'',
|
|
263
|
-
);
|
|
264
|
-
if (userResult?.type === 'submit_prompt') {
|
|
265
|
-
expect(userResult.content).toBe('User prompt');
|
|
266
|
-
} else {
|
|
267
|
-
assert.fail('Incorrect action type for user command');
|
|
268
|
-
}
|
|
269
|
-
const projectResult = await commands[1].action?.(
|
|
270
|
-
createMockCommandContext({
|
|
271
|
-
invocation: {
|
|
272
|
-
raw: '/test',
|
|
273
|
-
name: 'test',
|
|
274
|
-
args: '',
|
|
275
|
-
},
|
|
276
|
-
}),
|
|
277
|
-
'',
|
|
278
|
-
);
|
|
279
|
-
if (projectResult?.type === 'submit_prompt') {
|
|
280
|
-
expect(projectResult.content).toBe('Project prompt');
|
|
281
|
-
} else {
|
|
282
|
-
assert.fail('Incorrect action type for project command');
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it('ignores files with TOML syntax errors', async () => {
|
|
287
|
-
const userCommandsDir = getUserCommandsDir();
|
|
288
|
-
mock({
|
|
289
|
-
[userCommandsDir]: {
|
|
290
|
-
'invalid.toml': 'this is not valid toml',
|
|
291
|
-
'good.toml': 'prompt = "This one is fine"',
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
const loader = new FileCommandLoader(null);
|
|
296
|
-
const commands = await loader.loadCommands(signal);
|
|
297
|
-
|
|
298
|
-
expect(commands).toHaveLength(1);
|
|
299
|
-
expect(commands[0].name).toBe('good');
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('ignores files that are semantically invalid (missing prompt)', async () => {
|
|
303
|
-
const userCommandsDir = getUserCommandsDir();
|
|
304
|
-
mock({
|
|
305
|
-
[userCommandsDir]: {
|
|
306
|
-
'no_prompt.toml': 'description = "This file is missing a prompt"',
|
|
307
|
-
'good.toml': 'prompt = "This one is fine"',
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
const loader = new FileCommandLoader(null);
|
|
312
|
-
const commands = await loader.loadCommands(signal);
|
|
313
|
-
|
|
314
|
-
expect(commands).toHaveLength(1);
|
|
315
|
-
expect(commands[0].name).toBe('good');
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it('handles filename edge cases correctly', async () => {
|
|
319
|
-
const userCommandsDir = getUserCommandsDir();
|
|
320
|
-
mock({
|
|
321
|
-
[userCommandsDir]: {
|
|
322
|
-
'test.v1.toml': 'prompt = "Test prompt"',
|
|
323
|
-
},
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
const loader = new FileCommandLoader(null);
|
|
327
|
-
const commands = await loader.loadCommands(signal);
|
|
328
|
-
const command = commands[0];
|
|
329
|
-
expect(command).toBeDefined();
|
|
330
|
-
expect(command.name).toBe('test.v1');
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it('handles file system errors gracefully', async () => {
|
|
334
|
-
mock({}); // Mock an empty file system
|
|
335
|
-
const loader = new FileCommandLoader(null);
|
|
336
|
-
const commands = await loader.loadCommands(signal);
|
|
337
|
-
expect(commands).toHaveLength(0);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('uses a default description if not provided', async () => {
|
|
341
|
-
const userCommandsDir = getUserCommandsDir();
|
|
342
|
-
mock({
|
|
343
|
-
[userCommandsDir]: {
|
|
344
|
-
'test.toml': 'prompt = "Test prompt"',
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
const loader = new FileCommandLoader(null);
|
|
349
|
-
const commands = await loader.loadCommands(signal);
|
|
350
|
-
const command = commands[0];
|
|
351
|
-
expect(command).toBeDefined();
|
|
352
|
-
expect(command.description).toBe('Custom command from test.toml');
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
it('uses the provided description', async () => {
|
|
356
|
-
const userCommandsDir = getUserCommandsDir();
|
|
357
|
-
mock({
|
|
358
|
-
[userCommandsDir]: {
|
|
359
|
-
'test.toml': 'prompt = "Test prompt"\ndescription = "My test command"',
|
|
360
|
-
},
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const loader = new FileCommandLoader(null);
|
|
364
|
-
const commands = await loader.loadCommands(signal);
|
|
365
|
-
const command = commands[0];
|
|
366
|
-
expect(command).toBeDefined();
|
|
367
|
-
expect(command.description).toBe('My test command');
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it('should sanitize colons in filenames to prevent namespace conflicts', async () => {
|
|
371
|
-
const userCommandsDir = getUserCommandsDir();
|
|
372
|
-
mock({
|
|
373
|
-
[userCommandsDir]: {
|
|
374
|
-
'legacy:command.toml': 'prompt = "This is a legacy command"',
|
|
375
|
-
},
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
const loader = new FileCommandLoader(null);
|
|
379
|
-
const commands = await loader.loadCommands(signal);
|
|
380
|
-
|
|
381
|
-
expect(commands).toHaveLength(1);
|
|
382
|
-
const command = commands[0];
|
|
383
|
-
expect(command).toBeDefined();
|
|
384
|
-
|
|
385
|
-
// Verify that the ':' in the filename was replaced with an '_'
|
|
386
|
-
expect(command.name).toBe('legacy_command');
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
describe('Processor Instantiation Logic', () => {
|
|
390
|
-
it('instantiates only DefaultArgumentProcessor if no {{args}} or !{} are present', async () => {
|
|
391
|
-
const userCommandsDir = getUserCommandsDir();
|
|
392
|
-
mock({
|
|
393
|
-
[userCommandsDir]: {
|
|
394
|
-
'simple.toml': `prompt = "Just a regular prompt"`,
|
|
395
|
-
},
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
399
|
-
await loader.loadCommands(signal);
|
|
400
|
-
|
|
401
|
-
expect(ShellProcessor).not.toHaveBeenCalled();
|
|
402
|
-
expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1);
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
it('instantiates only ShellProcessor if {{args}} is present (but not !{})', async () => {
|
|
406
|
-
const userCommandsDir = getUserCommandsDir();
|
|
407
|
-
mock({
|
|
408
|
-
[userCommandsDir]: {
|
|
409
|
-
'args.toml': `prompt = "Prompt with {{args}}"`,
|
|
410
|
-
},
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
414
|
-
await loader.loadCommands(signal);
|
|
415
|
-
|
|
416
|
-
expect(ShellProcessor).toHaveBeenCalledTimes(1);
|
|
417
|
-
expect(DefaultArgumentProcessor).not.toHaveBeenCalled();
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it('instantiates ShellProcessor and DefaultArgumentProcessor if !{} is present (but not {{args}})', async () => {
|
|
421
|
-
const userCommandsDir = getUserCommandsDir();
|
|
422
|
-
mock({
|
|
423
|
-
[userCommandsDir]: {
|
|
424
|
-
'shell.toml': `prompt = "Prompt with !{cmd}"`,
|
|
425
|
-
},
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
429
|
-
await loader.loadCommands(signal);
|
|
430
|
-
|
|
431
|
-
expect(ShellProcessor).toHaveBeenCalledTimes(1);
|
|
432
|
-
expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1);
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
it('instantiates only ShellProcessor if both {{args}} and !{} are present', async () => {
|
|
436
|
-
const userCommandsDir = getUserCommandsDir();
|
|
437
|
-
mock({
|
|
438
|
-
[userCommandsDir]: {
|
|
439
|
-
'both.toml': `prompt = "Prompt with {{args}} and !{cmd}"`,
|
|
440
|
-
},
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
444
|
-
await loader.loadCommands(signal);
|
|
445
|
-
|
|
446
|
-
expect(ShellProcessor).toHaveBeenCalledTimes(1);
|
|
447
|
-
expect(DefaultArgumentProcessor).not.toHaveBeenCalled();
|
|
448
|
-
});
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
describe('Extension Command Loading', () => {
|
|
452
|
-
it('loads commands from active extensions', async () => {
|
|
453
|
-
const userCommandsDir = getUserCommandsDir();
|
|
454
|
-
const projectCommandsDir = getProjectCommandsDir(process.cwd());
|
|
455
|
-
const extensionDir = path.join(
|
|
456
|
-
process.cwd(),
|
|
457
|
-
'.gemini/extensions/test-ext',
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
mock({
|
|
461
|
-
[userCommandsDir]: {
|
|
462
|
-
'user.toml': 'prompt = "User command"',
|
|
463
|
-
},
|
|
464
|
-
[projectCommandsDir]: {
|
|
465
|
-
'project.toml': 'prompt = "Project command"',
|
|
466
|
-
},
|
|
467
|
-
[extensionDir]: {
|
|
468
|
-
'gemini-extension.json': JSON.stringify({
|
|
469
|
-
name: 'test-ext',
|
|
470
|
-
version: '1.0.0',
|
|
471
|
-
}),
|
|
472
|
-
commands: {
|
|
473
|
-
'ext.toml': 'prompt = "Extension command"',
|
|
474
|
-
},
|
|
475
|
-
},
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
const mockConfig = {
|
|
479
|
-
getProjectRoot: vi.fn(() => process.cwd()),
|
|
480
|
-
getExtensions: vi.fn(() => [
|
|
481
|
-
{
|
|
482
|
-
name: 'test-ext',
|
|
483
|
-
version: '1.0.0',
|
|
484
|
-
isActive: true,
|
|
485
|
-
path: extensionDir,
|
|
486
|
-
},
|
|
487
|
-
]),
|
|
488
|
-
} as unknown as Config;
|
|
489
|
-
const loader = new FileCommandLoader(mockConfig);
|
|
490
|
-
const commands = await loader.loadCommands(signal);
|
|
491
|
-
|
|
492
|
-
expect(commands).toHaveLength(3);
|
|
493
|
-
const commandNames = commands.map((cmd) => cmd.name);
|
|
494
|
-
expect(commandNames).toEqual(['user', 'project', 'ext']);
|
|
495
|
-
|
|
496
|
-
const extCommand = commands.find((cmd) => cmd.name === 'ext');
|
|
497
|
-
expect(extCommand?.extensionName).toBe('test-ext');
|
|
498
|
-
expect(extCommand?.description).toMatch(/^\[test-ext\]/);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it('extension commands have extensionName metadata for conflict resolution', async () => {
|
|
502
|
-
const userCommandsDir = getUserCommandsDir();
|
|
503
|
-
const projectCommandsDir = getProjectCommandsDir(process.cwd());
|
|
504
|
-
const extensionDir = path.join(
|
|
505
|
-
process.cwd(),
|
|
506
|
-
'.gemini/extensions/test-ext',
|
|
507
|
-
);
|
|
508
|
-
|
|
509
|
-
mock({
|
|
510
|
-
[extensionDir]: {
|
|
511
|
-
'gemini-extension.json': JSON.stringify({
|
|
512
|
-
name: 'test-ext',
|
|
513
|
-
version: '1.0.0',
|
|
514
|
-
}),
|
|
515
|
-
commands: {
|
|
516
|
-
'deploy.toml': 'prompt = "Extension deploy command"',
|
|
517
|
-
},
|
|
518
|
-
},
|
|
519
|
-
[userCommandsDir]: {
|
|
520
|
-
'deploy.toml': 'prompt = "User deploy command"',
|
|
521
|
-
},
|
|
522
|
-
[projectCommandsDir]: {
|
|
523
|
-
'deploy.toml': 'prompt = "Project deploy command"',
|
|
524
|
-
},
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
const mockConfig = {
|
|
528
|
-
getProjectRoot: vi.fn(() => process.cwd()),
|
|
529
|
-
getExtensions: vi.fn(() => [
|
|
530
|
-
{
|
|
531
|
-
name: 'test-ext',
|
|
532
|
-
version: '1.0.0',
|
|
533
|
-
isActive: true,
|
|
534
|
-
path: extensionDir,
|
|
535
|
-
},
|
|
536
|
-
]),
|
|
537
|
-
} as unknown as Config;
|
|
538
|
-
const loader = new FileCommandLoader(mockConfig);
|
|
539
|
-
const commands = await loader.loadCommands(signal);
|
|
540
|
-
|
|
541
|
-
// Return all commands, even duplicates
|
|
542
|
-
expect(commands).toHaveLength(3);
|
|
543
|
-
|
|
544
|
-
expect(commands[0].name).toBe('deploy');
|
|
545
|
-
expect(commands[0].extensionName).toBeUndefined();
|
|
546
|
-
const result0 = await commands[0].action?.(
|
|
547
|
-
createMockCommandContext({
|
|
548
|
-
invocation: {
|
|
549
|
-
raw: '/deploy',
|
|
550
|
-
name: 'deploy',
|
|
551
|
-
args: '',
|
|
552
|
-
},
|
|
553
|
-
}),
|
|
554
|
-
'',
|
|
555
|
-
);
|
|
556
|
-
expect(result0?.type).toBe('submit_prompt');
|
|
557
|
-
if (result0?.type === 'submit_prompt') {
|
|
558
|
-
expect(result0.content).toBe('User deploy command');
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
expect(commands[1].name).toBe('deploy');
|
|
562
|
-
expect(commands[1].extensionName).toBeUndefined();
|
|
563
|
-
const result1 = await commands[1].action?.(
|
|
564
|
-
createMockCommandContext({
|
|
565
|
-
invocation: {
|
|
566
|
-
raw: '/deploy',
|
|
567
|
-
name: 'deploy',
|
|
568
|
-
args: '',
|
|
569
|
-
},
|
|
570
|
-
}),
|
|
571
|
-
'',
|
|
572
|
-
);
|
|
573
|
-
expect(result1?.type).toBe('submit_prompt');
|
|
574
|
-
if (result1?.type === 'submit_prompt') {
|
|
575
|
-
expect(result1.content).toBe('Project deploy command');
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
expect(commands[2].name).toBe('deploy');
|
|
579
|
-
expect(commands[2].extensionName).toBe('test-ext');
|
|
580
|
-
expect(commands[2].description).toMatch(/^\[test-ext\]/);
|
|
581
|
-
const result2 = await commands[2].action?.(
|
|
582
|
-
createMockCommandContext({
|
|
583
|
-
invocation: {
|
|
584
|
-
raw: '/deploy',
|
|
585
|
-
name: 'deploy',
|
|
586
|
-
args: '',
|
|
587
|
-
},
|
|
588
|
-
}),
|
|
589
|
-
'',
|
|
590
|
-
);
|
|
591
|
-
expect(result2?.type).toBe('submit_prompt');
|
|
592
|
-
if (result2?.type === 'submit_prompt') {
|
|
593
|
-
expect(result2.content).toBe('Extension deploy command');
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
it('only loads commands from active extensions', async () => {
|
|
598
|
-
const extensionDir1 = path.join(
|
|
599
|
-
process.cwd(),
|
|
600
|
-
'.gemini/extensions/active-ext',
|
|
601
|
-
);
|
|
602
|
-
const extensionDir2 = path.join(
|
|
603
|
-
process.cwd(),
|
|
604
|
-
'.gemini/extensions/inactive-ext',
|
|
605
|
-
);
|
|
606
|
-
|
|
607
|
-
mock({
|
|
608
|
-
[extensionDir1]: {
|
|
609
|
-
'gemini-extension.json': JSON.stringify({
|
|
610
|
-
name: 'active-ext',
|
|
611
|
-
version: '1.0.0',
|
|
612
|
-
}),
|
|
613
|
-
commands: {
|
|
614
|
-
'active.toml': 'prompt = "Active extension command"',
|
|
615
|
-
},
|
|
616
|
-
},
|
|
617
|
-
[extensionDir2]: {
|
|
618
|
-
'gemini-extension.json': JSON.stringify({
|
|
619
|
-
name: 'inactive-ext',
|
|
620
|
-
version: '1.0.0',
|
|
621
|
-
}),
|
|
622
|
-
commands: {
|
|
623
|
-
'inactive.toml': 'prompt = "Inactive extension command"',
|
|
624
|
-
},
|
|
625
|
-
},
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
const mockConfig = {
|
|
629
|
-
getProjectRoot: vi.fn(() => process.cwd()),
|
|
630
|
-
getExtensions: vi.fn(() => [
|
|
631
|
-
{
|
|
632
|
-
name: 'active-ext',
|
|
633
|
-
version: '1.0.0',
|
|
634
|
-
isActive: true,
|
|
635
|
-
path: extensionDir1,
|
|
636
|
-
},
|
|
637
|
-
{
|
|
638
|
-
name: 'inactive-ext',
|
|
639
|
-
version: '1.0.0',
|
|
640
|
-
isActive: false,
|
|
641
|
-
path: extensionDir2,
|
|
642
|
-
},
|
|
643
|
-
]),
|
|
644
|
-
} as unknown as Config;
|
|
645
|
-
const loader = new FileCommandLoader(mockConfig);
|
|
646
|
-
const commands = await loader.loadCommands(signal);
|
|
647
|
-
|
|
648
|
-
expect(commands).toHaveLength(1);
|
|
649
|
-
expect(commands[0].name).toBe('active');
|
|
650
|
-
expect(commands[0].extensionName).toBe('active-ext');
|
|
651
|
-
expect(commands[0].description).toMatch(/^\[active-ext\]/);
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
it('handles missing extension commands directory gracefully', async () => {
|
|
655
|
-
const extensionDir = path.join(
|
|
656
|
-
process.cwd(),
|
|
657
|
-
'.gemini/extensions/no-commands',
|
|
658
|
-
);
|
|
659
|
-
|
|
660
|
-
mock({
|
|
661
|
-
[extensionDir]: {
|
|
662
|
-
'gemini-extension.json': JSON.stringify({
|
|
663
|
-
name: 'no-commands',
|
|
664
|
-
version: '1.0.0',
|
|
665
|
-
}),
|
|
666
|
-
// No commands directory
|
|
667
|
-
},
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
const mockConfig = {
|
|
671
|
-
getProjectRoot: vi.fn(() => process.cwd()),
|
|
672
|
-
getExtensions: vi.fn(() => [
|
|
673
|
-
{
|
|
674
|
-
name: 'no-commands',
|
|
675
|
-
version: '1.0.0',
|
|
676
|
-
isActive: true,
|
|
677
|
-
path: extensionDir,
|
|
678
|
-
},
|
|
679
|
-
]),
|
|
680
|
-
} as unknown as Config;
|
|
681
|
-
const loader = new FileCommandLoader(mockConfig);
|
|
682
|
-
const commands = await loader.loadCommands(signal);
|
|
683
|
-
expect(commands).toHaveLength(0);
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
it('handles nested command structure in extensions', async () => {
|
|
687
|
-
const extensionDir = path.join(process.cwd(), '.gemini/extensions/a');
|
|
688
|
-
|
|
689
|
-
mock({
|
|
690
|
-
[extensionDir]: {
|
|
691
|
-
'gemini-extension.json': JSON.stringify({
|
|
692
|
-
name: 'a',
|
|
693
|
-
version: '1.0.0',
|
|
694
|
-
}),
|
|
695
|
-
commands: {
|
|
696
|
-
b: {
|
|
697
|
-
'c.toml': 'prompt = "Nested command from extension a"',
|
|
698
|
-
d: {
|
|
699
|
-
'e.toml': 'prompt = "Deeply nested command"',
|
|
700
|
-
},
|
|
701
|
-
},
|
|
702
|
-
'simple.toml': 'prompt = "Simple command"',
|
|
703
|
-
},
|
|
704
|
-
},
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
const mockConfig = {
|
|
708
|
-
getProjectRoot: vi.fn(() => process.cwd()),
|
|
709
|
-
getExtensions: vi.fn(() => [
|
|
710
|
-
{ name: 'a', version: '1.0.0', isActive: true, path: extensionDir },
|
|
711
|
-
]),
|
|
712
|
-
} as unknown as Config;
|
|
713
|
-
const loader = new FileCommandLoader(mockConfig);
|
|
714
|
-
const commands = await loader.loadCommands(signal);
|
|
715
|
-
|
|
716
|
-
expect(commands).toHaveLength(3);
|
|
717
|
-
|
|
718
|
-
const commandNames = commands.map((cmd) => cmd.name).sort();
|
|
719
|
-
expect(commandNames).toEqual(['b:c', 'b:d:e', 'simple']);
|
|
720
|
-
|
|
721
|
-
const nestedCmd = commands.find((cmd) => cmd.name === 'b:c');
|
|
722
|
-
expect(nestedCmd?.extensionName).toBe('a');
|
|
723
|
-
expect(nestedCmd?.description).toMatch(/^\[a\]/);
|
|
724
|
-
expect(nestedCmd).toBeDefined();
|
|
725
|
-
const result = await nestedCmd!.action?.(
|
|
726
|
-
createMockCommandContext({
|
|
727
|
-
invocation: {
|
|
728
|
-
raw: '/b:c',
|
|
729
|
-
name: 'b:c',
|
|
730
|
-
args: '',
|
|
731
|
-
},
|
|
732
|
-
}),
|
|
733
|
-
'',
|
|
734
|
-
);
|
|
735
|
-
if (result?.type === 'submit_prompt') {
|
|
736
|
-
expect(result.content).toBe('Nested command from extension a');
|
|
737
|
-
} else {
|
|
738
|
-
assert.fail('Incorrect action type');
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
describe('Argument Handling Integration (via ShellProcessor)', () => {
|
|
744
|
-
it('correctly processes a command with {{args}}', async () => {
|
|
745
|
-
const userCommandsDir = getUserCommandsDir();
|
|
746
|
-
mock({
|
|
747
|
-
[userCommandsDir]: {
|
|
748
|
-
'shorthand.toml':
|
|
749
|
-
'prompt = "The user wants to: {{args}}"\ndescription = "Shorthand test"',
|
|
750
|
-
},
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
754
|
-
const commands = await loader.loadCommands(signal);
|
|
755
|
-
const command = commands.find((c) => c.name === 'shorthand');
|
|
756
|
-
expect(command).toBeDefined();
|
|
757
|
-
|
|
758
|
-
const result = await command!.action?.(
|
|
759
|
-
createMockCommandContext({
|
|
760
|
-
invocation: {
|
|
761
|
-
raw: '/shorthand do something cool',
|
|
762
|
-
name: 'shorthand',
|
|
763
|
-
args: 'do something cool',
|
|
764
|
-
},
|
|
765
|
-
}),
|
|
766
|
-
'do something cool',
|
|
767
|
-
);
|
|
768
|
-
expect(result?.type).toBe('submit_prompt');
|
|
769
|
-
if (result?.type === 'submit_prompt') {
|
|
770
|
-
expect(result.content).toBe('The user wants to: do something cool');
|
|
771
|
-
}
|
|
772
|
-
});
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
describe('Default Argument Processor Integration', () => {
|
|
776
|
-
it('correctly processes a command without {{args}}', async () => {
|
|
777
|
-
const userCommandsDir = getUserCommandsDir();
|
|
778
|
-
mock({
|
|
779
|
-
[userCommandsDir]: {
|
|
780
|
-
'model_led.toml':
|
|
781
|
-
'prompt = "This is the instruction."\ndescription = "Default processor test"',
|
|
782
|
-
},
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
786
|
-
const commands = await loader.loadCommands(signal);
|
|
787
|
-
const command = commands.find((c) => c.name === 'model_led');
|
|
788
|
-
expect(command).toBeDefined();
|
|
789
|
-
|
|
790
|
-
const result = await command!.action?.(
|
|
791
|
-
createMockCommandContext({
|
|
792
|
-
invocation: {
|
|
793
|
-
raw: '/model_led 1.2.0 added "a feature"',
|
|
794
|
-
name: 'model_led',
|
|
795
|
-
args: '1.2.0 added "a feature"',
|
|
796
|
-
},
|
|
797
|
-
}),
|
|
798
|
-
'1.2.0 added "a feature"',
|
|
799
|
-
);
|
|
800
|
-
expect(result?.type).toBe('submit_prompt');
|
|
801
|
-
if (result?.type === 'submit_prompt') {
|
|
802
|
-
const expectedContent =
|
|
803
|
-
'This is the instruction.\n\n/model_led 1.2.0 added "a feature"';
|
|
804
|
-
expect(result.content).toBe(expectedContent);
|
|
805
|
-
}
|
|
806
|
-
});
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
describe('Shell Processor Integration', () => {
|
|
810
|
-
it('instantiates ShellProcessor if {{args}} is present (even without shell trigger)', async () => {
|
|
811
|
-
const userCommandsDir = getUserCommandsDir();
|
|
812
|
-
mock({
|
|
813
|
-
[userCommandsDir]: {
|
|
814
|
-
'args_only.toml': `prompt = "Hello {{args}}"`,
|
|
815
|
-
},
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
819
|
-
await loader.loadCommands(signal);
|
|
820
|
-
|
|
821
|
-
expect(ShellProcessor).toHaveBeenCalledWith('args_only');
|
|
822
|
-
});
|
|
823
|
-
it('instantiates ShellProcessor if the trigger is present', async () => {
|
|
824
|
-
const userCommandsDir = getUserCommandsDir();
|
|
825
|
-
mock({
|
|
826
|
-
[userCommandsDir]: {
|
|
827
|
-
'shell.toml': `prompt = "Run this: ${SHELL_INJECTION_TRIGGER}echo hello}"`,
|
|
828
|
-
},
|
|
829
|
-
});
|
|
830
|
-
|
|
831
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
832
|
-
await loader.loadCommands(signal);
|
|
833
|
-
|
|
834
|
-
expect(ShellProcessor).toHaveBeenCalledWith('shell');
|
|
835
|
-
});
|
|
836
|
-
|
|
837
|
-
it('does not instantiate ShellProcessor if no triggers ({{args}} or !{}) are present', async () => {
|
|
838
|
-
const userCommandsDir = getUserCommandsDir();
|
|
839
|
-
mock({
|
|
840
|
-
[userCommandsDir]: {
|
|
841
|
-
'regular.toml': `prompt = "Just a regular prompt"`,
|
|
842
|
-
},
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
846
|
-
await loader.loadCommands(signal);
|
|
847
|
-
|
|
848
|
-
expect(ShellProcessor).not.toHaveBeenCalled();
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
it('returns a "submit_prompt" action if shell processing succeeds', async () => {
|
|
852
|
-
const userCommandsDir = getUserCommandsDir();
|
|
853
|
-
mock({
|
|
854
|
-
[userCommandsDir]: {
|
|
855
|
-
'shell.toml': `prompt = "Run !{echo 'hello'}"`,
|
|
856
|
-
},
|
|
857
|
-
});
|
|
858
|
-
mockShellProcess.mockResolvedValue('Run hello');
|
|
859
|
-
|
|
860
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
861
|
-
const commands = await loader.loadCommands(signal);
|
|
862
|
-
const command = commands.find((c) => c.name === 'shell');
|
|
863
|
-
expect(command).toBeDefined();
|
|
864
|
-
|
|
865
|
-
const result = await command!.action!(
|
|
866
|
-
createMockCommandContext({
|
|
867
|
-
invocation: { raw: '/shell', name: 'shell', args: '' },
|
|
868
|
-
}),
|
|
869
|
-
'',
|
|
870
|
-
);
|
|
871
|
-
|
|
872
|
-
expect(result?.type).toBe('submit_prompt');
|
|
873
|
-
if (result?.type === 'submit_prompt') {
|
|
874
|
-
expect(result.content).toBe('Run hello');
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
it('returns a "confirm_shell_commands" action if shell processing requires it', async () => {
|
|
879
|
-
const userCommandsDir = getUserCommandsDir();
|
|
880
|
-
const rawInvocation = '/shell rm -rf /';
|
|
881
|
-
mock({
|
|
882
|
-
[userCommandsDir]: {
|
|
883
|
-
'shell.toml': `prompt = "Run !{rm -rf /}"`,
|
|
884
|
-
},
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
// Mock the processor to throw the specific error
|
|
888
|
-
const error = new ConfirmationRequiredError('Confirmation needed', [
|
|
889
|
-
'rm -rf /',
|
|
890
|
-
]);
|
|
891
|
-
mockShellProcess.mockRejectedValue(error);
|
|
892
|
-
|
|
893
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
894
|
-
const commands = await loader.loadCommands(signal);
|
|
895
|
-
const command = commands.find((c) => c.name === 'shell');
|
|
896
|
-
expect(command).toBeDefined();
|
|
897
|
-
|
|
898
|
-
const result = await command!.action!(
|
|
899
|
-
createMockCommandContext({
|
|
900
|
-
invocation: { raw: rawInvocation, name: 'shell', args: 'rm -rf /' },
|
|
901
|
-
}),
|
|
902
|
-
'rm -rf /',
|
|
903
|
-
);
|
|
904
|
-
|
|
905
|
-
expect(result?.type).toBe('confirm_shell_commands');
|
|
906
|
-
if (result?.type === 'confirm_shell_commands') {
|
|
907
|
-
expect(result.commandsToConfirm).toEqual(['rm -rf /']);
|
|
908
|
-
expect(result.originalInvocation.raw).toBe(rawInvocation);
|
|
909
|
-
}
|
|
910
|
-
});
|
|
911
|
-
|
|
912
|
-
it('re-throws other errors from the processor', async () => {
|
|
913
|
-
const userCommandsDir = getUserCommandsDir();
|
|
914
|
-
mock({
|
|
915
|
-
[userCommandsDir]: {
|
|
916
|
-
'shell.toml': `prompt = "Run !{something}"`,
|
|
917
|
-
},
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
const genericError = new Error('Something else went wrong');
|
|
921
|
-
mockShellProcess.mockRejectedValue(genericError);
|
|
922
|
-
|
|
923
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
924
|
-
const commands = await loader.loadCommands(signal);
|
|
925
|
-
const command = commands.find((c) => c.name === 'shell');
|
|
926
|
-
expect(command).toBeDefined();
|
|
927
|
-
|
|
928
|
-
await expect(
|
|
929
|
-
command!.action!(
|
|
930
|
-
createMockCommandContext({
|
|
931
|
-
invocation: { raw: '/shell', name: 'shell', args: '' },
|
|
932
|
-
}),
|
|
933
|
-
'',
|
|
934
|
-
),
|
|
935
|
-
).rejects.toThrow('Something else went wrong');
|
|
936
|
-
});
|
|
937
|
-
it('assembles the processor pipeline in the correct order (Shell -> Default)', async () => {
|
|
938
|
-
const userCommandsDir = getUserCommandsDir();
|
|
939
|
-
mock({
|
|
940
|
-
[userCommandsDir]: {
|
|
941
|
-
// This prompt uses !{} but NOT {{args}}, so both processors should be active.
|
|
942
|
-
'pipeline.toml': `
|
|
943
|
-
prompt = "Shell says: ${SHELL_INJECTION_TRIGGER}echo foo}."
|
|
944
|
-
`,
|
|
945
|
-
},
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
const defaultProcessMock = vi
|
|
949
|
-
.fn()
|
|
950
|
-
.mockImplementation((p) => Promise.resolve(`${p}-default-processed`));
|
|
951
|
-
|
|
952
|
-
mockShellProcess.mockImplementation((p) =>
|
|
953
|
-
Promise.resolve(`${p}-shell-processed`),
|
|
954
|
-
);
|
|
955
|
-
|
|
956
|
-
vi.mocked(DefaultArgumentProcessor).mockImplementation(
|
|
957
|
-
() =>
|
|
958
|
-
({
|
|
959
|
-
process: defaultProcessMock,
|
|
960
|
-
}) as unknown as DefaultArgumentProcessor,
|
|
961
|
-
);
|
|
962
|
-
|
|
963
|
-
const loader = new FileCommandLoader(null as unknown as Config);
|
|
964
|
-
const commands = await loader.loadCommands(signal);
|
|
965
|
-
const command = commands.find((c) => c.name === 'pipeline');
|
|
966
|
-
expect(command).toBeDefined();
|
|
967
|
-
|
|
968
|
-
const result = await command!.action!(
|
|
969
|
-
createMockCommandContext({
|
|
970
|
-
invocation: {
|
|
971
|
-
raw: '/pipeline bar',
|
|
972
|
-
name: 'pipeline',
|
|
973
|
-
args: 'bar',
|
|
974
|
-
},
|
|
975
|
-
}),
|
|
976
|
-
'bar',
|
|
977
|
-
);
|
|
978
|
-
|
|
979
|
-
expect(mockShellProcess.mock.invocationCallOrder[0]).toBeLessThan(
|
|
980
|
-
defaultProcessMock.mock.invocationCallOrder[0],
|
|
981
|
-
);
|
|
982
|
-
|
|
983
|
-
// Verify the flow of the prompt through the processors
|
|
984
|
-
// 1. Shell processor runs first
|
|
985
|
-
expect(mockShellProcess).toHaveBeenCalledWith(
|
|
986
|
-
expect.stringContaining(SHELL_INJECTION_TRIGGER),
|
|
987
|
-
expect.any(Object),
|
|
988
|
-
);
|
|
989
|
-
// 2. Default processor runs second
|
|
990
|
-
expect(defaultProcessMock).toHaveBeenCalledWith(
|
|
991
|
-
expect.stringContaining('-shell-processed'),
|
|
992
|
-
expect.any(Object),
|
|
993
|
-
);
|
|
994
|
-
|
|
995
|
-
if (result?.type === 'submit_prompt') {
|
|
996
|
-
expect(result.content).toContain('-shell-processed-default-processed');
|
|
997
|
-
} else {
|
|
998
|
-
assert.fail('Incorrect action type');
|
|
999
|
-
}
|
|
1000
|
-
});
|
|
1001
|
-
});
|
|
1002
|
-
});
|