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,709 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
|
|
8
|
-
import { ConfirmationRequiredError, ShellProcessor } from './shellProcessor.js';
|
|
9
|
-
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
|
10
|
-
import { CommandContext } from '../../ui/commands/types.js';
|
|
11
|
-
import { ApprovalMode, Config } from 'fss-link-core';
|
|
12
|
-
import os from 'os';
|
|
13
|
-
import { quote } from 'shell-quote';
|
|
14
|
-
|
|
15
|
-
// Helper function to determine the expected escaped string based on the current OS,
|
|
16
|
-
// mirroring the logic in the actual `escapeShellArg` implementation. This makes
|
|
17
|
-
// our tests robust and platform-agnostic.
|
|
18
|
-
function getExpectedEscapedArgForPlatform(arg: string): string {
|
|
19
|
-
if (os.platform() === 'win32') {
|
|
20
|
-
const comSpec = (process.env['ComSpec'] || 'cmd.exe').toLowerCase();
|
|
21
|
-
const isPowerShell =
|
|
22
|
-
comSpec.endsWith('powershell.exe') || comSpec.endsWith('pwsh.exe');
|
|
23
|
-
|
|
24
|
-
if (isPowerShell) {
|
|
25
|
-
return `'${arg.replace(/'/g, "''")}'`;
|
|
26
|
-
} else {
|
|
27
|
-
return `"${arg.replace(/"/g, '""')}"`;
|
|
28
|
-
}
|
|
29
|
-
} else {
|
|
30
|
-
return quote([arg]);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const mockCheckCommandPermissions = vi.hoisted(() => vi.fn());
|
|
35
|
-
const mockShellExecute = vi.hoisted(() => vi.fn());
|
|
36
|
-
|
|
37
|
-
vi.mock('fss-link-core', async (importOriginal) => {
|
|
38
|
-
const original = await importOriginal<object>();
|
|
39
|
-
return {
|
|
40
|
-
...original,
|
|
41
|
-
checkCommandPermissions: mockCheckCommandPermissions,
|
|
42
|
-
ShellExecutionService: {
|
|
43
|
-
execute: mockShellExecute,
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const SUCCESS_RESULT = {
|
|
49
|
-
output: 'default shell output',
|
|
50
|
-
exitCode: 0,
|
|
51
|
-
error: null,
|
|
52
|
-
aborted: false,
|
|
53
|
-
signal: null,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
describe('ShellProcessor', () => {
|
|
57
|
-
let context: CommandContext;
|
|
58
|
-
let mockConfig: Partial<Config>;
|
|
59
|
-
|
|
60
|
-
beforeEach(() => {
|
|
61
|
-
vi.clearAllMocks();
|
|
62
|
-
|
|
63
|
-
mockConfig = {
|
|
64
|
-
getTargetDir: vi.fn().mockReturnValue('/test/dir'),
|
|
65
|
-
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
|
|
66
|
-
getShouldUseNodePtyShell: vi.fn().mockReturnValue(false),
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
context = createMockCommandContext({
|
|
70
|
-
invocation: {
|
|
71
|
-
raw: '/cmd default args',
|
|
72
|
-
name: 'cmd',
|
|
73
|
-
args: 'default args',
|
|
74
|
-
},
|
|
75
|
-
services: {
|
|
76
|
-
config: mockConfig as Config,
|
|
77
|
-
},
|
|
78
|
-
session: {
|
|
79
|
-
sessionShellAllowlist: new Set(),
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
mockShellExecute.mockReturnValue({
|
|
84
|
-
result: Promise.resolve(SUCCESS_RESULT),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
88
|
-
allAllowed: true,
|
|
89
|
-
disallowedCommands: [],
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should throw an error if config is missing', async () => {
|
|
94
|
-
const processor = new ShellProcessor('test-command');
|
|
95
|
-
const prompt = '!{ls}';
|
|
96
|
-
const contextWithoutConfig = createMockCommandContext({
|
|
97
|
-
services: {
|
|
98
|
-
config: null,
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
await expect(
|
|
103
|
-
processor.process(prompt, contextWithoutConfig),
|
|
104
|
-
).rejects.toThrow(/Security configuration not loaded/);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should not change the prompt if no shell injections are present', async () => {
|
|
108
|
-
const processor = new ShellProcessor('test-command');
|
|
109
|
-
const prompt = 'This is a simple prompt with no injections.';
|
|
110
|
-
const result = await processor.process(prompt, context);
|
|
111
|
-
expect(result).toBe(prompt);
|
|
112
|
-
expect(mockShellExecute).not.toHaveBeenCalled();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('should process a single valid shell injection if allowed', async () => {
|
|
116
|
-
const processor = new ShellProcessor('test-command');
|
|
117
|
-
const prompt = 'The current status is: !{git status}';
|
|
118
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
119
|
-
allAllowed: true,
|
|
120
|
-
disallowedCommands: [],
|
|
121
|
-
});
|
|
122
|
-
mockShellExecute.mockReturnValue({
|
|
123
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: 'On branch main' }),
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const result = await processor.process(prompt, context);
|
|
127
|
-
|
|
128
|
-
expect(mockCheckCommandPermissions).toHaveBeenCalledWith(
|
|
129
|
-
'git status',
|
|
130
|
-
expect.any(Object),
|
|
131
|
-
context.session.sessionShellAllowlist,
|
|
132
|
-
);
|
|
133
|
-
expect(mockShellExecute).toHaveBeenCalledWith(
|
|
134
|
-
'git status',
|
|
135
|
-
expect.any(String),
|
|
136
|
-
expect.any(Function),
|
|
137
|
-
expect.any(Object),
|
|
138
|
-
false,
|
|
139
|
-
);
|
|
140
|
-
expect(result).toBe('The current status is: On branch main');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should process multiple valid shell injections if all are allowed', async () => {
|
|
144
|
-
const processor = new ShellProcessor('test-command');
|
|
145
|
-
const prompt = '!{git status} in !{pwd}';
|
|
146
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
147
|
-
allAllowed: true,
|
|
148
|
-
disallowedCommands: [],
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
mockShellExecute
|
|
152
|
-
.mockReturnValueOnce({
|
|
153
|
-
result: Promise.resolve({
|
|
154
|
-
...SUCCESS_RESULT,
|
|
155
|
-
output: 'On branch main',
|
|
156
|
-
}),
|
|
157
|
-
})
|
|
158
|
-
.mockReturnValueOnce({
|
|
159
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: '/usr/home' }),
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const result = await processor.process(prompt, context);
|
|
163
|
-
|
|
164
|
-
expect(mockCheckCommandPermissions).toHaveBeenCalledTimes(2);
|
|
165
|
-
expect(mockShellExecute).toHaveBeenCalledTimes(2);
|
|
166
|
-
expect(result).toBe('On branch main in /usr/home');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('should throw ConfirmationRequiredError if a command is not allowed in default mode', async () => {
|
|
170
|
-
const processor = new ShellProcessor('test-command');
|
|
171
|
-
const prompt = 'Do something dangerous: !{rm -rf /}';
|
|
172
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
173
|
-
allAllowed: false,
|
|
174
|
-
disallowedCommands: ['rm -rf /'],
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
await expect(processor.process(prompt, context)).rejects.toThrow(
|
|
178
|
-
ConfirmationRequiredError,
|
|
179
|
-
);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('should NOT throw ConfirmationRequiredError if a command is not allowed but approval mode is YOLO', async () => {
|
|
183
|
-
const processor = new ShellProcessor('test-command');
|
|
184
|
-
const prompt = 'Do something dangerous: !{rm -rf /}';
|
|
185
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
186
|
-
allAllowed: false,
|
|
187
|
-
disallowedCommands: ['rm -rf /'],
|
|
188
|
-
});
|
|
189
|
-
// Override the approval mode for this test
|
|
190
|
-
(mockConfig.getApprovalMode as Mock).mockReturnValue(ApprovalMode.YOLO);
|
|
191
|
-
mockShellExecute.mockReturnValue({
|
|
192
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: 'deleted' }),
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const result = await processor.process(prompt, context);
|
|
196
|
-
|
|
197
|
-
// It should proceed with execution
|
|
198
|
-
expect(mockShellExecute).toHaveBeenCalledWith(
|
|
199
|
-
'rm -rf /',
|
|
200
|
-
expect.any(String),
|
|
201
|
-
expect.any(Function),
|
|
202
|
-
expect.any(Object),
|
|
203
|
-
false,
|
|
204
|
-
);
|
|
205
|
-
expect(result).toBe('Do something dangerous: deleted');
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('should still throw an error for a hard-denied command even in YOLO mode', async () => {
|
|
209
|
-
const processor = new ShellProcessor('test-command');
|
|
210
|
-
const prompt = 'Do something forbidden: !{reboot}';
|
|
211
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
212
|
-
allAllowed: false,
|
|
213
|
-
disallowedCommands: ['reboot'],
|
|
214
|
-
isHardDenial: true, // This is the key difference
|
|
215
|
-
blockReason: 'System commands are blocked',
|
|
216
|
-
});
|
|
217
|
-
// Set approval mode to YOLO
|
|
218
|
-
(mockConfig.getApprovalMode as Mock).mockReturnValue(ApprovalMode.YOLO);
|
|
219
|
-
|
|
220
|
-
await expect(processor.process(prompt, context)).rejects.toThrow(
|
|
221
|
-
/Blocked command: "reboot". Reason: System commands are blocked/,
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// Ensure it never tried to execute
|
|
225
|
-
expect(mockShellExecute).not.toHaveBeenCalled();
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('should throw ConfirmationRequiredError with the correct command', async () => {
|
|
229
|
-
const processor = new ShellProcessor('test-command');
|
|
230
|
-
const prompt = 'Do something dangerous: !{rm -rf /}';
|
|
231
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
232
|
-
allAllowed: false,
|
|
233
|
-
disallowedCommands: ['rm -rf /'],
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
await processor.process(prompt, context);
|
|
238
|
-
// Fail if it doesn't throw
|
|
239
|
-
expect(true).toBe(false);
|
|
240
|
-
} catch (e) {
|
|
241
|
-
expect(e).toBeInstanceOf(ConfirmationRequiredError);
|
|
242
|
-
if (e instanceof ConfirmationRequiredError) {
|
|
243
|
-
expect(e.commandsToConfirm).toEqual(['rm -rf /']);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
expect(mockShellExecute).not.toHaveBeenCalled();
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('should throw ConfirmationRequiredError with multiple commands if multiple are disallowed', async () => {
|
|
251
|
-
const processor = new ShellProcessor('test-command');
|
|
252
|
-
const prompt = '!{cmd1} and !{cmd2}';
|
|
253
|
-
mockCheckCommandPermissions.mockImplementation((cmd) => {
|
|
254
|
-
if (cmd === 'cmd1') {
|
|
255
|
-
return { allAllowed: false, disallowedCommands: ['cmd1'] };
|
|
256
|
-
}
|
|
257
|
-
if (cmd === 'cmd2') {
|
|
258
|
-
return { allAllowed: false, disallowedCommands: ['cmd2'] };
|
|
259
|
-
}
|
|
260
|
-
return { allAllowed: true, disallowedCommands: [] };
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
await processor.process(prompt, context);
|
|
265
|
-
// Fail if it doesn't throw
|
|
266
|
-
expect(true).toBe(false);
|
|
267
|
-
} catch (e) {
|
|
268
|
-
expect(e).toBeInstanceOf(ConfirmationRequiredError);
|
|
269
|
-
if (e instanceof ConfirmationRequiredError) {
|
|
270
|
-
expect(e.commandsToConfirm).toEqual(['cmd1', 'cmd2']);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('should not execute any commands if at least one requires confirmation', async () => {
|
|
276
|
-
const processor = new ShellProcessor('test-command');
|
|
277
|
-
const prompt = 'First: !{echo "hello"}, Second: !{rm -rf /}';
|
|
278
|
-
|
|
279
|
-
mockCheckCommandPermissions.mockImplementation((cmd) => {
|
|
280
|
-
if (cmd.includes('rm')) {
|
|
281
|
-
return { allAllowed: false, disallowedCommands: [cmd] };
|
|
282
|
-
}
|
|
283
|
-
return { allAllowed: true, disallowedCommands: [] };
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
await expect(processor.process(prompt, context)).rejects.toThrow(
|
|
287
|
-
ConfirmationRequiredError,
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
// Ensure no commands were executed because the pipeline was halted.
|
|
291
|
-
expect(mockShellExecute).not.toHaveBeenCalled();
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it('should only request confirmation for disallowed commands in a mixed prompt', async () => {
|
|
295
|
-
const processor = new ShellProcessor('test-command');
|
|
296
|
-
const prompt = 'Allowed: !{ls -l}, Disallowed: !{rm -rf /}';
|
|
297
|
-
|
|
298
|
-
mockCheckCommandPermissions.mockImplementation((cmd) => ({
|
|
299
|
-
allAllowed: !cmd.includes('rm'),
|
|
300
|
-
disallowedCommands: cmd.includes('rm') ? [cmd] : [],
|
|
301
|
-
}));
|
|
302
|
-
|
|
303
|
-
try {
|
|
304
|
-
await processor.process(prompt, context);
|
|
305
|
-
expect.fail('Should have thrown ConfirmationRequiredError');
|
|
306
|
-
} catch (e) {
|
|
307
|
-
expect(e).toBeInstanceOf(ConfirmationRequiredError);
|
|
308
|
-
if (e instanceof ConfirmationRequiredError) {
|
|
309
|
-
expect(e.commandsToConfirm).toEqual(['rm -rf /']);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('should execute all commands if they are on the session allowlist', async () => {
|
|
315
|
-
const processor = new ShellProcessor('test-command');
|
|
316
|
-
const prompt = 'Run !{cmd1} and !{cmd2}';
|
|
317
|
-
|
|
318
|
-
// Add commands to the session allowlist
|
|
319
|
-
context.session.sessionShellAllowlist = new Set(['cmd1', 'cmd2']);
|
|
320
|
-
|
|
321
|
-
// checkCommandPermissions should now pass for these
|
|
322
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
323
|
-
allAllowed: true,
|
|
324
|
-
disallowedCommands: [],
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
mockShellExecute
|
|
328
|
-
.mockReturnValueOnce({
|
|
329
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: 'output1' }),
|
|
330
|
-
})
|
|
331
|
-
.mockReturnValueOnce({
|
|
332
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: 'output2' }),
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const result = await processor.process(prompt, context);
|
|
336
|
-
|
|
337
|
-
expect(mockCheckCommandPermissions).toHaveBeenCalledWith(
|
|
338
|
-
'cmd1',
|
|
339
|
-
expect.any(Object),
|
|
340
|
-
context.session.sessionShellAllowlist,
|
|
341
|
-
);
|
|
342
|
-
expect(mockCheckCommandPermissions).toHaveBeenCalledWith(
|
|
343
|
-
'cmd2',
|
|
344
|
-
expect.any(Object),
|
|
345
|
-
context.session.sessionShellAllowlist,
|
|
346
|
-
);
|
|
347
|
-
expect(mockShellExecute).toHaveBeenCalledTimes(2);
|
|
348
|
-
expect(result).toBe('Run output1 and output2');
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('should trim whitespace from the command inside the injection before interpolation', async () => {
|
|
352
|
-
const processor = new ShellProcessor('test-command');
|
|
353
|
-
const prompt = 'Files: !{ ls {{args}} -l }';
|
|
354
|
-
|
|
355
|
-
const rawArgs = context.invocation!.args;
|
|
356
|
-
|
|
357
|
-
const expectedEscapedArgs = getExpectedEscapedArgForPlatform(rawArgs);
|
|
358
|
-
|
|
359
|
-
const expectedCommand = `ls ${expectedEscapedArgs} -l`;
|
|
360
|
-
|
|
361
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
362
|
-
allAllowed: true,
|
|
363
|
-
disallowedCommands: [],
|
|
364
|
-
});
|
|
365
|
-
mockShellExecute.mockReturnValue({
|
|
366
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: 'total 0' }),
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
await processor.process(prompt, context);
|
|
370
|
-
|
|
371
|
-
expect(mockCheckCommandPermissions).toHaveBeenCalledWith(
|
|
372
|
-
expectedCommand,
|
|
373
|
-
expect.any(Object),
|
|
374
|
-
context.session.sessionShellAllowlist,
|
|
375
|
-
);
|
|
376
|
-
expect(mockShellExecute).toHaveBeenCalledWith(
|
|
377
|
-
expectedCommand,
|
|
378
|
-
expect.any(String),
|
|
379
|
-
expect.any(Function),
|
|
380
|
-
expect.any(Object),
|
|
381
|
-
false,
|
|
382
|
-
);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it('should handle an empty command inside the injection gracefully (skips execution)', async () => {
|
|
386
|
-
const processor = new ShellProcessor('test-command');
|
|
387
|
-
const prompt = 'This is weird: !{}';
|
|
388
|
-
|
|
389
|
-
const result = await processor.process(prompt, context);
|
|
390
|
-
|
|
391
|
-
expect(mockCheckCommandPermissions).not.toHaveBeenCalled();
|
|
392
|
-
expect(mockShellExecute).not.toHaveBeenCalled();
|
|
393
|
-
|
|
394
|
-
// It replaces !{} with an empty string.
|
|
395
|
-
expect(result).toBe('This is weird: ');
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
describe('Robust Parsing (Balanced Braces)', () => {
|
|
399
|
-
it('should correctly parse commands containing nested braces (e.g., awk)', async () => {
|
|
400
|
-
const processor = new ShellProcessor('test-command');
|
|
401
|
-
const command = "awk '{print $1}' file.txt";
|
|
402
|
-
const prompt = `Output: !{${command}}`;
|
|
403
|
-
mockShellExecute.mockReturnValue({
|
|
404
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: 'result' }),
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
const result = await processor.process(prompt, context);
|
|
408
|
-
|
|
409
|
-
expect(mockCheckCommandPermissions).toHaveBeenCalledWith(
|
|
410
|
-
command,
|
|
411
|
-
expect.any(Object),
|
|
412
|
-
context.session.sessionShellAllowlist,
|
|
413
|
-
);
|
|
414
|
-
expect(mockShellExecute).toHaveBeenCalledWith(
|
|
415
|
-
command,
|
|
416
|
-
expect.any(String),
|
|
417
|
-
expect.any(Function),
|
|
418
|
-
expect.any(Object),
|
|
419
|
-
false,
|
|
420
|
-
);
|
|
421
|
-
expect(result).toBe('Output: result');
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it('should handle deeply nested braces correctly', async () => {
|
|
425
|
-
const processor = new ShellProcessor('test-command');
|
|
426
|
-
const command = "echo '{{a},{b}}'";
|
|
427
|
-
const prompt = `!{${command}}`;
|
|
428
|
-
mockShellExecute.mockReturnValue({
|
|
429
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: '{{a},{b}}' }),
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
const result = await processor.process(prompt, context);
|
|
433
|
-
expect(mockShellExecute).toHaveBeenCalledWith(
|
|
434
|
-
command,
|
|
435
|
-
expect.any(String),
|
|
436
|
-
expect.any(Function),
|
|
437
|
-
expect.any(Object),
|
|
438
|
-
false,
|
|
439
|
-
);
|
|
440
|
-
expect(result).toBe('{{a},{b}}');
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
it('should throw an error for unclosed shell injections', async () => {
|
|
444
|
-
const processor = new ShellProcessor('test-command');
|
|
445
|
-
const prompt = 'This prompt is broken: !{ls -l';
|
|
446
|
-
|
|
447
|
-
await expect(processor.process(prompt, context)).rejects.toThrow(
|
|
448
|
-
/Unclosed shell injection/,
|
|
449
|
-
);
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
it('should throw an error for unclosed nested braces', async () => {
|
|
453
|
-
const processor = new ShellProcessor('test-command');
|
|
454
|
-
const prompt = 'Broken: !{echo {a}';
|
|
455
|
-
|
|
456
|
-
await expect(processor.process(prompt, context)).rejects.toThrow(
|
|
457
|
-
/Unclosed shell injection/,
|
|
458
|
-
);
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
describe('Error Reporting', () => {
|
|
463
|
-
it('should append exit code and command name on failure', async () => {
|
|
464
|
-
const processor = new ShellProcessor('test-command');
|
|
465
|
-
const prompt = '!{cmd}';
|
|
466
|
-
mockShellExecute.mockReturnValue({
|
|
467
|
-
result: Promise.resolve({
|
|
468
|
-
...SUCCESS_RESULT,
|
|
469
|
-
output: 'some error output',
|
|
470
|
-
stderr: '',
|
|
471
|
-
exitCode: 1,
|
|
472
|
-
}),
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
const result = await processor.process(prompt, context);
|
|
476
|
-
|
|
477
|
-
expect(result).toBe(
|
|
478
|
-
"some error output\n[Shell command 'cmd' exited with code 1]",
|
|
479
|
-
);
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
it('should append signal info and command name if terminated by signal', async () => {
|
|
483
|
-
const processor = new ShellProcessor('test-command');
|
|
484
|
-
const prompt = '!{cmd}';
|
|
485
|
-
mockShellExecute.mockReturnValue({
|
|
486
|
-
result: Promise.resolve({
|
|
487
|
-
...SUCCESS_RESULT,
|
|
488
|
-
output: 'output',
|
|
489
|
-
stderr: '',
|
|
490
|
-
exitCode: null,
|
|
491
|
-
signal: 'SIGTERM',
|
|
492
|
-
}),
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
const result = await processor.process(prompt, context);
|
|
496
|
-
|
|
497
|
-
expect(result).toBe(
|
|
498
|
-
"output\n[Shell command 'cmd' terminated by signal SIGTERM]",
|
|
499
|
-
);
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
it('should throw a detailed error if the shell fails to spawn', async () => {
|
|
503
|
-
const processor = new ShellProcessor('test-command');
|
|
504
|
-
const prompt = '!{bad-command}';
|
|
505
|
-
const spawnError = new Error('spawn EACCES');
|
|
506
|
-
mockShellExecute.mockReturnValue({
|
|
507
|
-
result: Promise.resolve({
|
|
508
|
-
...SUCCESS_RESULT,
|
|
509
|
-
stdout: '',
|
|
510
|
-
stderr: '',
|
|
511
|
-
exitCode: null,
|
|
512
|
-
error: spawnError,
|
|
513
|
-
aborted: false,
|
|
514
|
-
}),
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
await expect(processor.process(prompt, context)).rejects.toThrow(
|
|
518
|
-
"Failed to start shell command in 'test-command': spawn EACCES. Command: bad-command",
|
|
519
|
-
);
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
it('should report abort status with command name if aborted', async () => {
|
|
523
|
-
const processor = new ShellProcessor('test-command');
|
|
524
|
-
const prompt = '!{long-running-command}';
|
|
525
|
-
const spawnError = new Error('Aborted');
|
|
526
|
-
mockShellExecute.mockReturnValue({
|
|
527
|
-
result: Promise.resolve({
|
|
528
|
-
...SUCCESS_RESULT,
|
|
529
|
-
output: 'partial output',
|
|
530
|
-
stderr: '',
|
|
531
|
-
exitCode: null,
|
|
532
|
-
error: spawnError,
|
|
533
|
-
aborted: true, // Key difference
|
|
534
|
-
}),
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
const result = await processor.process(prompt, context);
|
|
538
|
-
expect(result).toBe(
|
|
539
|
-
"partial output\n[Shell command 'long-running-command' aborted]",
|
|
540
|
-
);
|
|
541
|
-
});
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
describe('Context-Aware Argument Interpolation ({{args}})', () => {
|
|
545
|
-
const rawArgs = 'user input';
|
|
546
|
-
|
|
547
|
-
beforeEach(() => {
|
|
548
|
-
// Update context for these tests to use specific arguments
|
|
549
|
-
context.invocation!.args = rawArgs;
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
it('should perform raw replacement if no shell injections are present (optimization path)', async () => {
|
|
553
|
-
const processor = new ShellProcessor('test-command');
|
|
554
|
-
const prompt = 'The user said: {{args}}';
|
|
555
|
-
|
|
556
|
-
const result = await processor.process(prompt, context);
|
|
557
|
-
|
|
558
|
-
expect(result).toBe(`The user said: ${rawArgs}`);
|
|
559
|
-
expect(mockShellExecute).not.toHaveBeenCalled();
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
it('should perform raw replacement outside !{} blocks', async () => {
|
|
563
|
-
const processor = new ShellProcessor('test-command');
|
|
564
|
-
const prompt = 'Outside: {{args}}. Inside: !{echo "hello"}';
|
|
565
|
-
mockShellExecute.mockReturnValue({
|
|
566
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: 'hello' }),
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
const result = await processor.process(prompt, context);
|
|
570
|
-
|
|
571
|
-
expect(result).toBe(`Outside: ${rawArgs}. Inside: hello`);
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
it('should perform escaped replacement inside !{} blocks', async () => {
|
|
575
|
-
const processor = new ShellProcessor('test-command');
|
|
576
|
-
const prompt = 'Command: !{grep {{args}} file.txt}';
|
|
577
|
-
mockShellExecute.mockReturnValue({
|
|
578
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: 'match found' }),
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
const result = await processor.process(prompt, context);
|
|
582
|
-
|
|
583
|
-
const expectedEscapedArgs = getExpectedEscapedArgForPlatform(rawArgs);
|
|
584
|
-
const expectedCommand = `grep ${expectedEscapedArgs} file.txt`;
|
|
585
|
-
|
|
586
|
-
expect(mockShellExecute).toHaveBeenCalledWith(
|
|
587
|
-
expectedCommand,
|
|
588
|
-
expect.any(String),
|
|
589
|
-
expect.any(Function),
|
|
590
|
-
expect.any(Object),
|
|
591
|
-
false,
|
|
592
|
-
);
|
|
593
|
-
|
|
594
|
-
expect(result).toBe('Command: match found');
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
it('should handle both raw (outside) and escaped (inside) injection simultaneously', async () => {
|
|
598
|
-
const processor = new ShellProcessor('test-command');
|
|
599
|
-
const prompt = 'User "({{args}})" requested search: !{search {{args}}}';
|
|
600
|
-
mockShellExecute.mockReturnValue({
|
|
601
|
-
result: Promise.resolve({ ...SUCCESS_RESULT, output: 'results' }),
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
const result = await processor.process(prompt, context);
|
|
605
|
-
|
|
606
|
-
const expectedEscapedArgs = getExpectedEscapedArgForPlatform(rawArgs);
|
|
607
|
-
const expectedCommand = `search ${expectedEscapedArgs}`;
|
|
608
|
-
expect(mockShellExecute).toHaveBeenCalledWith(
|
|
609
|
-
expectedCommand,
|
|
610
|
-
expect.any(String),
|
|
611
|
-
expect.any(Function),
|
|
612
|
-
expect.any(Object),
|
|
613
|
-
false,
|
|
614
|
-
);
|
|
615
|
-
|
|
616
|
-
expect(result).toBe(`User "(${rawArgs})" requested search: results`);
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
it('should perform security checks on the final, resolved (escaped) command', async () => {
|
|
620
|
-
const processor = new ShellProcessor('test-command');
|
|
621
|
-
const prompt = '!{rm {{args}}}';
|
|
622
|
-
|
|
623
|
-
const expectedEscapedArgs = getExpectedEscapedArgForPlatform(rawArgs);
|
|
624
|
-
const expectedResolvedCommand = `rm ${expectedEscapedArgs}`;
|
|
625
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
626
|
-
allAllowed: false,
|
|
627
|
-
disallowedCommands: [expectedResolvedCommand],
|
|
628
|
-
isHardDenial: false,
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
await expect(processor.process(prompt, context)).rejects.toThrow(
|
|
632
|
-
ConfirmationRequiredError,
|
|
633
|
-
);
|
|
634
|
-
|
|
635
|
-
expect(mockCheckCommandPermissions).toHaveBeenCalledWith(
|
|
636
|
-
expectedResolvedCommand,
|
|
637
|
-
expect.any(Object),
|
|
638
|
-
context.session.sessionShellAllowlist,
|
|
639
|
-
);
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
it('should report the resolved command if a hard denial occurs', async () => {
|
|
643
|
-
const processor = new ShellProcessor('test-command');
|
|
644
|
-
const prompt = '!{rm {{args}}}';
|
|
645
|
-
const expectedEscapedArgs = getExpectedEscapedArgForPlatform(rawArgs);
|
|
646
|
-
const expectedResolvedCommand = `rm ${expectedEscapedArgs}`;
|
|
647
|
-
mockCheckCommandPermissions.mockReturnValue({
|
|
648
|
-
allAllowed: false,
|
|
649
|
-
disallowedCommands: [expectedResolvedCommand],
|
|
650
|
-
isHardDenial: true,
|
|
651
|
-
blockReason: 'It is forbidden.',
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
await expect(processor.process(prompt, context)).rejects.toThrow(
|
|
655
|
-
`Blocked command: "${expectedResolvedCommand}". Reason: It is forbidden.`,
|
|
656
|
-
);
|
|
657
|
-
});
|
|
658
|
-
});
|
|
659
|
-
describe('Real-World Escaping Scenarios', () => {
|
|
660
|
-
it('should correctly handle multiline arguments', async () => {
|
|
661
|
-
const processor = new ShellProcessor('test-command');
|
|
662
|
-
const multilineArgs = 'first line\nsecond line';
|
|
663
|
-
context.invocation!.args = multilineArgs;
|
|
664
|
-
const prompt = 'Commit message: !{git commit -m {{args}}}';
|
|
665
|
-
|
|
666
|
-
const expectedEscapedArgs =
|
|
667
|
-
getExpectedEscapedArgForPlatform(multilineArgs);
|
|
668
|
-
const expectedCommand = `git commit -m ${expectedEscapedArgs}`;
|
|
669
|
-
|
|
670
|
-
await processor.process(prompt, context);
|
|
671
|
-
|
|
672
|
-
expect(mockShellExecute).toHaveBeenCalledWith(
|
|
673
|
-
expectedCommand,
|
|
674
|
-
expect.any(String),
|
|
675
|
-
expect.any(Function),
|
|
676
|
-
expect.any(Object),
|
|
677
|
-
false,
|
|
678
|
-
);
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
it.each([
|
|
682
|
-
{ name: 'spaces', input: 'file with spaces.txt' },
|
|
683
|
-
{ name: 'double quotes', input: 'a "quoted" string' },
|
|
684
|
-
{ name: 'single quotes', input: "it's a string" },
|
|
685
|
-
{ name: 'command substitution (backticks)', input: '`reboot`' },
|
|
686
|
-
{ name: 'command substitution (dollar)', input: '$(reboot)' },
|
|
687
|
-
{ name: 'variable expansion', input: '$HOME' },
|
|
688
|
-
{ name: 'command chaining (semicolon)', input: 'a; reboot' },
|
|
689
|
-
{ name: 'command chaining (ampersand)', input: 'a && reboot' },
|
|
690
|
-
])('should safely escape args containing $name', async ({ input }) => {
|
|
691
|
-
const processor = new ShellProcessor('test-command');
|
|
692
|
-
context.invocation!.args = input;
|
|
693
|
-
const prompt = '!{echo {{args}}}';
|
|
694
|
-
|
|
695
|
-
const expectedEscapedArgs = getExpectedEscapedArgForPlatform(input);
|
|
696
|
-
const expectedCommand = `echo ${expectedEscapedArgs}`;
|
|
697
|
-
|
|
698
|
-
await processor.process(prompt, context);
|
|
699
|
-
|
|
700
|
-
expect(mockShellExecute).toHaveBeenCalledWith(
|
|
701
|
-
expectedCommand,
|
|
702
|
-
expect.any(String),
|
|
703
|
-
expect.any(Function),
|
|
704
|
-
expect.any(Object),
|
|
705
|
-
false,
|
|
706
|
-
);
|
|
707
|
-
});
|
|
708
|
-
});
|
|
709
|
-
});
|