@travisennis/acai 0.0.9 → 0.0.11
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/README.md +51 -760
- package/bin/acai +52 -0
- package/dist/agent/index.d.ts +12 -2
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +380 -199
- package/dist/agent/sub-agent.d.ts +23 -0
- package/dist/agent/sub-agent.d.ts.map +1 -0
- package/dist/agent/sub-agent.js +109 -0
- package/dist/cli/index.d.ts +26 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/{cli.js → cli/index.js} +84 -77
- package/dist/{stdin.d.ts → cli/stdin.d.ts} +2 -1
- package/dist/cli/stdin.d.ts.map +1 -0
- package/dist/{stdin.js → cli/stdin.js} +11 -0
- package/dist/commands/copy/index.js +2 -2
- package/dist/commands/copy/utils.d.ts.map +1 -1
- package/dist/commands/copy/utils.js +15 -13
- package/dist/commands/generate-rules/index.d.ts +1 -1
- package/dist/commands/generate-rules/index.d.ts.map +1 -1
- package/dist/commands/generate-rules/index.js +16 -101
- package/dist/commands/generate-rules/service.d.ts +22 -0
- package/dist/commands/generate-rules/service.d.ts.map +1 -0
- package/dist/commands/generate-rules/service.js +103 -0
- package/dist/commands/handoff/index.js +2 -2
- package/dist/commands/health/index.js +1 -1
- package/dist/commands/health/utils.d.ts +3 -2
- package/dist/commands/health/utils.d.ts.map +1 -1
- package/dist/commands/health/utils.js +6 -0
- package/dist/commands/history/index.d.ts +1 -1
- package/dist/commands/history/index.d.ts.map +1 -1
- package/dist/commands/history/index.js +17 -18
- package/dist/commands/history/types.d.ts +38 -0
- package/dist/commands/history/types.d.ts.map +1 -1
- package/dist/commands/history/utils.d.ts.map +1 -1
- package/dist/commands/history/utils.js +63 -58
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +3 -8
- package/dist/commands/init-project/index.d.ts.map +1 -1
- package/dist/commands/init-project/index.js +3 -3
- package/dist/commands/init-project/utils.d.ts +2 -1
- package/dist/commands/init-project/utils.d.ts.map +1 -1
- package/dist/commands/init-project/utils.js +10 -2
- package/dist/commands/list-tools/index.d.ts.map +1 -1
- package/dist/commands/list-tools/index.js +7 -31
- package/dist/commands/manager.d.ts +2 -2
- package/dist/commands/manager.d.ts.map +1 -1
- package/dist/commands/manager.js +55 -33
- package/dist/commands/model/index.d.ts.map +1 -1
- package/dist/commands/model/index.js +20 -151
- package/dist/commands/model/model-panel.d.ts +4 -0
- package/dist/commands/model/model-panel.d.ts.map +1 -0
- package/dist/commands/model/model-panel.js +144 -0
- package/dist/commands/paste/index.d.ts.map +1 -1
- package/dist/commands/paste/index.js +59 -62
- package/dist/commands/paste/utils.d.ts.map +1 -1
- package/dist/commands/paste/utils.js +88 -58
- package/dist/commands/pickup/index.d.ts.map +1 -1
- package/dist/commands/pickup/index.js +6 -3
- package/dist/commands/pickup/utils.js +3 -3
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +33 -50
- package/dist/commands/review/index.d.ts.map +1 -1
- package/dist/commands/review/index.js +3 -117
- package/dist/commands/review/review-panel.d.ts +3 -0
- package/dist/commands/review/review-panel.d.ts.map +1 -0
- package/dist/commands/review/review-panel.js +186 -0
- package/dist/commands/review/utils.d.ts +15 -1
- package/dist/commands/review/utils.d.ts.map +1 -1
- package/dist/commands/review/utils.js +127 -68
- package/dist/commands/session/index.d.ts +1 -1
- package/dist/commands/session/index.d.ts.map +1 -1
- package/dist/commands/session/index.js +124 -135
- package/dist/commands/shell/index.d.ts.map +1 -1
- package/dist/commands/shell/index.js +16 -1
- package/dist/commands/types.d.ts +2 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/{config.d.ts → config/index.d.ts} +20 -9
- package/dist/config/index.d.ts.map +1 -0
- package/dist/{config.js → config/index.js} +43 -42
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +75 -55
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +148 -141
- package/dist/middleware/cache.d.ts.map +1 -1
- package/dist/middleware/cache.js +18 -36
- package/dist/models/ai-config.d.ts +1 -0
- package/dist/models/ai-config.d.ts.map +1 -1
- package/dist/models/ai-config.js +4 -3
- package/dist/models/anthropic-provider.d.ts +2 -5
- package/dist/models/anthropic-provider.d.ts.map +1 -1
- package/dist/models/anthropic-provider.js +3 -70
- package/dist/models/deepseek-provider.d.ts +1 -0
- package/dist/models/deepseek-provider.d.ts.map +1 -1
- package/dist/models/google-provider.d.ts +2 -3
- package/dist/models/google-provider.d.ts.map +1 -1
- package/dist/models/google-provider.js +0 -26
- package/dist/models/groq-provider.d.ts +1 -0
- package/dist/models/groq-provider.d.ts.map +1 -1
- package/dist/models/manager.d.ts +13 -2
- package/dist/models/manager.d.ts.map +1 -1
- package/dist/models/manager.js +20 -8
- package/dist/models/openai-provider.d.ts +2 -5
- package/dist/models/openai-provider.d.ts.map +1 -1
- package/dist/models/openai-provider.js +0 -52
- package/dist/models/opencode-go-provider.d.ts +25 -0
- package/dist/models/opencode-go-provider.d.ts.map +1 -0
- package/dist/models/opencode-go-provider.js +78 -0
- package/dist/models/opencode-zen-provider.d.ts +7 -3
- package/dist/models/opencode-zen-provider.d.ts.map +1 -1
- package/dist/models/opencode-zen-provider.js +49 -10
- package/dist/models/openrouter-provider.d.ts +27 -31
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +121 -180
- package/dist/models/providers.d.ts +3 -3
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +6 -0
- package/dist/models/xai-provider.d.ts +4 -3
- package/dist/models/xai-provider.d.ts.map +1 -1
- package/dist/models/xai-provider.js +18 -18
- package/dist/modes/manager.d.ts +24 -0
- package/dist/modes/manager.d.ts.map +1 -0
- package/dist/modes/manager.js +77 -0
- package/dist/modes/prompts.d.ts +2 -0
- package/dist/modes/prompts.d.ts.map +1 -0
- package/dist/modes/prompts.js +142 -0
- package/dist/prompts/mentions.d.ts +11 -0
- package/dist/prompts/mentions.d.ts.map +1 -0
- package/dist/{mentions.js → prompts/mentions.js} +55 -85
- package/dist/{prompts.d.ts → prompts/system-prompt.d.ts} +7 -2
- package/dist/prompts/system-prompt.d.ts.map +1 -0
- package/dist/{prompts.js → prompts/system-prompt.js} +31 -16
- package/dist/repl/index.d.ts +174 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/{repl-new.js → repl/index.js} +397 -76
- package/dist/repl/project-status.d.ts +1 -0
- package/dist/repl/project-status.d.ts.map +1 -1
- package/dist/repl/project-status.js +4 -1
- package/dist/sessions/manager.d.ts +92 -0
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +262 -9
- package/dist/sessions/summary.d.ts +4 -0
- package/dist/sessions/summary.d.ts.map +1 -0
- package/dist/sessions/summary.js +30 -0
- package/dist/skills/index.d.ts +29 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +294 -0
- package/dist/subagents/index.d.ts +16 -0
- package/dist/subagents/index.d.ts.map +1 -0
- package/dist/subagents/index.js +231 -0
- package/dist/terminal/control.d.ts +1 -1
- package/dist/terminal/control.d.ts.map +1 -1
- package/dist/terminal/control.js +3 -3
- package/dist/terminal/east-asian-width.d.ts.map +1 -1
- package/dist/terminal/east-asian-width.js +404 -351
- package/dist/terminal/keys.d.ts +17 -0
- package/dist/terminal/keys.d.ts.map +1 -1
- package/dist/terminal/keys.js +37 -0
- package/dist/terminal/select-prompt.d.ts.map +1 -1
- package/dist/terminal/select-prompt.js +24 -12
- package/dist/terminal/string-width.d.ts.map +1 -1
- package/dist/terminal/string-width.js +25 -27
- package/dist/terminal/style.d.ts.map +1 -1
- package/dist/terminal/style.js +4 -7
- package/dist/terminal/supports-color.d.ts.map +1 -1
- package/dist/terminal/supports-color.js +41 -27
- package/dist/terminal/table/cell.d.ts +12 -0
- package/dist/terminal/table/cell.d.ts.map +1 -1
- package/dist/terminal/table/cell.js +40 -25
- package/dist/terminal/table/layout-manager.d.ts.map +1 -1
- package/dist/terminal/table/layout-manager.js +100 -68
- package/dist/terminal/table/utils.d.ts +1 -1
- package/dist/terminal/table/utils.d.ts.map +1 -1
- package/dist/terminal/table/utils.js +17 -10
- package/dist/terminal/wrap-ansi.d.ts.map +1 -1
- package/dist/terminal/wrap-ansi.js +174 -105
- package/dist/tokens/tracker.d.ts +1 -0
- package/dist/tokens/tracker.d.ts.map +1 -1
- package/dist/tokens/tracker.js +3 -0
- package/dist/tools/agent.d.ts +27 -0
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/agent.js +81 -0
- package/dist/tools/apply-patch.d.ts +62 -0
- package/dist/tools/apply-patch.d.ts.map +1 -0
- package/dist/tools/apply-patch.js +377 -0
- package/dist/tools/bash.d.ts +4 -3
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +349 -141
- package/dist/tools/directory-tree.d.ts +3 -3
- package/dist/tools/directory-tree.d.ts.map +1 -1
- package/dist/tools/directory-tree.js +8 -5
- package/dist/tools/dynamic-tool-loader.d.ts +3 -6
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
- package/dist/tools/dynamic-tool-loader.js +20 -4
- package/dist/tools/edit-file.d.ts +7 -7
- package/dist/tools/edit-file.d.ts.map +1 -1
- package/dist/tools/edit-file.js +292 -85
- package/dist/tools/glob.d.ts +6 -6
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +110 -63
- package/dist/tools/grep.d.ts +15 -12
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +315 -193
- package/dist/tools/index.d.ts +114 -9
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +39 -24
- package/dist/tools/ls.d.ts +2 -2
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +7 -5
- package/dist/tools/read-file.d.ts +4 -6
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +84 -39
- package/dist/tools/save-file.d.ts +3 -3
- package/dist/tools/save-file.d.ts.map +1 -1
- package/dist/tools/save-file.js +36 -31
- package/dist/tools/skill.d.ts +23 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +65 -0
- package/dist/tools/think.d.ts.map +1 -1
- package/dist/tools/think.js +2 -9
- package/dist/tools/utils.d.ts +2 -0
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +12 -0
- package/dist/tools/web-fetch.d.ts +50 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +446 -0
- package/dist/tools/web-search.d.ts +44 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +226 -0
- package/dist/tui/autocomplete/attachment-provider.d.ts +3 -6
- package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/attachment-provider.js +25 -78
- package/dist/tui/autocomplete/base-provider.d.ts +1 -0
- package/dist/tui/autocomplete/base-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/combined-provider.d.ts +1 -4
- package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/combined-provider.js +3 -17
- package/dist/tui/autocomplete/command-provider.d.ts +1 -0
- package/dist/tui/autocomplete/command-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/command-provider.js +3 -0
- package/dist/tui/autocomplete/file-search-provider.d.ts +2 -1
- package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/file-search-provider.js +37 -17
- package/dist/tui/autocomplete/skill-provider.d.ts +17 -0
- package/dist/tui/autocomplete/skill-provider.d.ts.map +1 -0
- package/dist/tui/autocomplete/skill-provider.js +49 -0
- package/dist/tui/autocomplete/utils.d.ts +2 -1
- package/dist/tui/autocomplete/utils.d.ts.map +1 -1
- package/dist/tui/autocomplete/utils.js +25 -23
- package/dist/tui/autocomplete.d.ts +2 -2
- package/dist/tui/autocomplete.d.ts.map +1 -1
- package/dist/tui/autocomplete.js +3 -5
- package/dist/tui/components/assistant-message.d.ts.map +1 -1
- package/dist/tui/components/assistant-message.js +0 -4
- package/dist/tui/components/editor.d.ts +18 -3
- package/dist/tui/components/editor.d.ts.map +1 -1
- package/dist/tui/components/editor.js +211 -237
- package/dist/tui/components/footer.d.ts +6 -4
- package/dist/tui/components/footer.d.ts.map +1 -1
- package/dist/tui/components/footer.js +49 -25
- package/dist/tui/components/markdown.d.ts +10 -7
- package/dist/tui/components/markdown.d.ts.map +1 -1
- package/dist/tui/components/markdown.js +57 -39
- package/dist/tui/components/modal.d.ts.map +1 -1
- package/dist/tui/components/modal.js +35 -33
- package/dist/tui/components/notification.d.ts +13 -2
- package/dist/tui/components/notification.d.ts.map +1 -1
- package/dist/tui/components/notification.js +36 -2
- package/dist/tui/components/progress-bar.js +1 -1
- package/dist/tui/components/select-list.d.ts +1 -0
- package/dist/tui/components/select-list.d.ts.map +1 -1
- package/dist/tui/components/select-list.js +14 -11
- package/dist/tui/components/text.d.ts +16 -0
- package/dist/tui/components/text.d.ts.map +1 -1
- package/dist/tui/components/text.js +72 -57
- package/dist/tui/components/thinking-block.d.ts +9 -0
- package/dist/tui/components/thinking-block.d.ts.map +1 -1
- package/dist/tui/components/thinking-block.js +43 -11
- package/dist/tui/components/tool-execution.d.ts +5 -1
- package/dist/tui/components/tool-execution.d.ts.map +1 -1
- package/dist/tui/components/tool-execution.js +19 -10
- package/dist/tui/components/user-message.d.ts.map +1 -1
- package/dist/tui/components/user-message.js +0 -3
- package/dist/tui/components/welcome.d.ts +2 -1
- package/dist/tui/components/welcome.d.ts.map +1 -1
- package/dist/tui/components/welcome.js +2 -2
- package/dist/tui/editor-launcher.d.ts +3 -2
- package/dist/tui/editor-launcher.d.ts.map +1 -1
- package/dist/tui/index.d.ts +0 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/terminal.d.ts.map +1 -1
- package/dist/tui/terminal.js +10 -2
- package/dist/tui/tui.d.ts +43 -0
- package/dist/tui/tui.d.ts.map +1 -1
- package/dist/tui/tui.js +166 -41
- package/dist/tui/utils.d.ts +1 -5
- package/dist/tui/utils.d.ts.map +1 -1
- package/dist/tui/utils.js +271 -44
- package/dist/utils/bash/parse.d.ts +19 -0
- package/dist/utils/bash/parse.d.ts.map +1 -0
- package/dist/utils/bash/parse.js +223 -0
- package/dist/utils/bash/quote.d.ts +6 -0
- package/dist/utils/bash/quote.d.ts.map +1 -0
- package/dist/utils/bash/quote.js +23 -0
- package/dist/utils/bash.d.ts.map +1 -1
- package/dist/utils/bash.js +211 -126
- package/dist/utils/command-protection.d.ts +28 -0
- package/dist/utils/command-protection.d.ts.map +1 -0
- package/dist/utils/command-protection.js +324 -0
- package/dist/utils/dedent.d.ts.map +1 -0
- package/dist/utils/env-expand.d.ts +2 -0
- package/dist/utils/env-expand.d.ts.map +1 -0
- package/dist/utils/env-expand.js +8 -0
- package/dist/utils/filesystem/path-display.d.ts +11 -0
- package/dist/utils/filesystem/path-display.d.ts.map +1 -0
- package/dist/utils/filesystem/path-display.js +32 -0
- package/dist/utils/filesystem/security.d.ts +2 -2
- package/dist/utils/filesystem/security.d.ts.map +1 -1
- package/dist/utils/filesystem/security.js +28 -30
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/{formatting.js → utils/formatting.js} +1 -1
- package/dist/utils/git.d.ts +4 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +30 -0
- package/dist/utils/glob.d.ts +1 -1
- package/dist/utils/glob.d.ts.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/{logger.js → utils/logger.js} +1 -1
- package/dist/utils/parsing.d.ts.map +1 -0
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +90 -37
- package/dist/utils/templates.d.ts +2 -0
- package/dist/utils/templates.d.ts.map +1 -0
- package/dist/utils/templates.js +24 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/{version.js → utils/version.js} +1 -1
- package/package.json +35 -26
- package/dist/cli.d.ts +0 -23
- package/dist/cli.d.ts.map +0 -1
- package/dist/commands/add-directory/types.d.ts +0 -6
- package/dist/commands/add-directory/types.d.ts.map +0 -1
- package/dist/commands/add-directory/types.js +0 -1
- package/dist/commands/copy/types.d.ts +0 -3
- package/dist/commands/copy/types.d.ts.map +0 -1
- package/dist/commands/copy/types.js +0 -1
- package/dist/commands/exit/index.d.ts +0 -10
- package/dist/commands/exit/index.d.ts.map +0 -1
- package/dist/commands/exit/index.js +0 -21
- package/dist/commands/exit/types.d.ts +0 -8
- package/dist/commands/exit/types.d.ts.map +0 -1
- package/dist/commands/exit/types.js +0 -1
- package/dist/commands/exit/utils.d.ts +0 -2
- package/dist/commands/exit/utils.d.ts.map +0 -1
- package/dist/commands/exit/utils.js +0 -13
- package/dist/commands/prompt/index.d.ts +0 -5
- package/dist/commands/prompt/index.d.ts.map +0 -1
- package/dist/commands/prompt/index.js +0 -122
- package/dist/commands/prompt/types.d.ts +0 -15
- package/dist/commands/prompt/types.d.ts.map +0 -1
- package/dist/commands/prompt/types.js +0 -1
- package/dist/commands/prompt/utils.d.ts +0 -12
- package/dist/commands/prompt/utils.d.ts.map +0 -1
- package/dist/commands/prompt/utils.js +0 -107
- package/dist/commands/reset/index.d.ts +0 -3
- package/dist/commands/reset/index.d.ts.map +0 -1
- package/dist/commands/reset/index.js +0 -25
- package/dist/commands/reset/types.d.ts +0 -1
- package/dist/commands/reset/types.d.ts.map +0 -1
- package/dist/commands/reset/types.js +0 -3
- package/dist/commands/review/types.d.ts +0 -12
- package/dist/commands/review/types.d.ts.map +0 -1
- package/dist/commands/review/types.js +0 -1
- package/dist/commands/save/index.d.ts +0 -3
- package/dist/commands/save/index.d.ts.map +0 -1
- package/dist/commands/save/index.js +0 -19
- package/dist/config.d.ts.map +0 -1
- package/dist/dedent.d.ts.map +0 -1
- package/dist/formatting.d.ts.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/mentions.d.ts +0 -14
- package/dist/mentions.d.ts.map +0 -1
- package/dist/parsing.d.ts.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/repl-new.d.ts +0 -65
- package/dist/repl-new.d.ts.map +0 -1
- package/dist/skills.d.ts +0 -16
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -233
- package/dist/stdin.d.ts.map +0 -1
- package/dist/tui/autocomplete/path-provider.d.ts +0 -21
- package/dist/tui/autocomplete/path-provider.d.ts.map +0 -1
- package/dist/tui/autocomplete/path-provider.js +0 -164
- package/dist/utils/iterables.d.ts +0 -2
- package/dist/utils/iterables.d.ts.map +0 -1
- package/dist/utils/iterables.js +0 -6
- package/dist/version.d.ts.map +0 -1
- /package/dist/{dedent.d.ts → utils/dedent.d.ts} +0 -0
- /package/dist/{dedent.js → utils/dedent.js} +0 -0
- /package/dist/{formatting.d.ts → utils/formatting.d.ts} +0 -0
- /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
- /package/dist/{parsing.d.ts → utils/parsing.d.ts} +0 -0
- /package/dist/{parsing.js → utils/parsing.js} +0 -0
- /package/dist/{version.d.ts → utils/version.d.ts} +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// '<(' is process substitution operator and
|
|
2
|
+
// can be parsed the same as control operator
|
|
3
|
+
const CONTROL = `(?:${[
|
|
4
|
+
"\\|\\|",
|
|
5
|
+
"\\&\\&",
|
|
6
|
+
";;",
|
|
7
|
+
"\\|\\&",
|
|
8
|
+
"\\<\\(",
|
|
9
|
+
"\\<\\<\\<",
|
|
10
|
+
">>",
|
|
11
|
+
">\\&",
|
|
12
|
+
"<\\&",
|
|
13
|
+
"[&;()|<>]",
|
|
14
|
+
].join("|")})`;
|
|
15
|
+
const controlRe = new RegExp(`^${CONTROL}$`);
|
|
16
|
+
const META = "|&;()<> \t";
|
|
17
|
+
const SINGLE_QUOTE = '"((\\\\"|[^"])*?)"';
|
|
18
|
+
const DOUBLE_QUOTE = "'((\\\\'|[^'])*?)'";
|
|
19
|
+
const hash = /^#$/;
|
|
20
|
+
const SQ = "'";
|
|
21
|
+
const DQ = '"';
|
|
22
|
+
const DS = "$";
|
|
23
|
+
let Token = "";
|
|
24
|
+
const mult = 0x100000000; // Math.pow(16, 8);
|
|
25
|
+
for (let i = 0; i < 4; i++) {
|
|
26
|
+
Token += (mult * Math.random()).toString(16);
|
|
27
|
+
}
|
|
28
|
+
const startsWithToken = new RegExp(`^${Token}`);
|
|
29
|
+
function matchAll(s, r) {
|
|
30
|
+
const origIndex = r.lastIndex;
|
|
31
|
+
const matches = [];
|
|
32
|
+
let matchObj = r.exec(s);
|
|
33
|
+
while (matchObj !== null) {
|
|
34
|
+
matches.push(matchObj);
|
|
35
|
+
if (r.lastIndex === matchObj.index) {
|
|
36
|
+
r.lastIndex += 1;
|
|
37
|
+
}
|
|
38
|
+
matchObj = r.exec(s);
|
|
39
|
+
}
|
|
40
|
+
r.lastIndex = origIndex;
|
|
41
|
+
return matches;
|
|
42
|
+
}
|
|
43
|
+
function getVar(env, pre, key) {
|
|
44
|
+
let r = typeof env === "function" ? env(key) : env[key];
|
|
45
|
+
if (typeof r === "undefined" && key !== "") {
|
|
46
|
+
r = "";
|
|
47
|
+
}
|
|
48
|
+
else if (typeof r === "undefined") {
|
|
49
|
+
r = "$";
|
|
50
|
+
}
|
|
51
|
+
if (typeof r === "object") {
|
|
52
|
+
return pre + Token + JSON.stringify(r) + Token;
|
|
53
|
+
}
|
|
54
|
+
return pre + String(r);
|
|
55
|
+
}
|
|
56
|
+
function parseInternal(string, env, opts = {}) {
|
|
57
|
+
const Bs = opts.escape || "\\";
|
|
58
|
+
const Bareword = `(\\${Bs}["'${META}]|[^\\s'""${META}])+`;
|
|
59
|
+
const chunker = new RegExp([
|
|
60
|
+
`(${CONTROL})`, // control chars
|
|
61
|
+
`(${Bareword}|${SINGLE_QUOTE}|${DOUBLE_QUOTE})+`,
|
|
62
|
+
].join("|"), "g");
|
|
63
|
+
const matches = matchAll(string, chunker);
|
|
64
|
+
if (matches.length === 0) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const envToUse = env ?? {};
|
|
68
|
+
let commented = false;
|
|
69
|
+
return matches
|
|
70
|
+
.map(
|
|
71
|
+
// biome-ignore lint:noExcessiveCognitiveComplexity
|
|
72
|
+
(match) => {
|
|
73
|
+
const s = match[0];
|
|
74
|
+
if (!s || commented) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
if (controlRe.test(s)) {
|
|
78
|
+
return { op: s };
|
|
79
|
+
}
|
|
80
|
+
// Hand-written scanner/parser for Bash quoting rules:
|
|
81
|
+
//
|
|
82
|
+
// 1. inside single quotes, all characters are printed literally.
|
|
83
|
+
// 2. inside double quotes, all characters are printed literally
|
|
84
|
+
// except variables prefixed by '$' and backslashes followed by
|
|
85
|
+
// either a double quote or another backslash.
|
|
86
|
+
// 3. outside of any quotes, backslashes are treated as escape
|
|
87
|
+
// characters and not printed (unless they are themselves escaped)
|
|
88
|
+
// 4. quote context can switch mid-token if there is no whitespace
|
|
89
|
+
// between the two quote contexts (e.g. all'one'"token" parses as
|
|
90
|
+
// "allonetoken")
|
|
91
|
+
let quote = false;
|
|
92
|
+
let esc = false;
|
|
93
|
+
let out = "";
|
|
94
|
+
let isGlob = false;
|
|
95
|
+
let i = 0;
|
|
96
|
+
function parseEnvVar() {
|
|
97
|
+
i += 1;
|
|
98
|
+
let varend;
|
|
99
|
+
let varname;
|
|
100
|
+
const char = s.charAt(i);
|
|
101
|
+
if (char === "{") {
|
|
102
|
+
i += 1;
|
|
103
|
+
if (s.charAt(i) === "}") {
|
|
104
|
+
throw new Error(`Bad substitution: ${s.slice(i - 2, i + 1)}`);
|
|
105
|
+
}
|
|
106
|
+
varend = s.indexOf("}", i);
|
|
107
|
+
if (varend < 0) {
|
|
108
|
+
throw new Error(`Bad substitution: ${s.slice(i)}`);
|
|
109
|
+
}
|
|
110
|
+
varname = s.slice(i, varend);
|
|
111
|
+
i = varend;
|
|
112
|
+
}
|
|
113
|
+
else if (/[*@#?$!_-]/.test(char)) {
|
|
114
|
+
varname = char;
|
|
115
|
+
i += 1;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const slicedFromI = s.slice(i);
|
|
119
|
+
const varendMatch = slicedFromI.match(/[^\w\d_]/);
|
|
120
|
+
if (!varendMatch) {
|
|
121
|
+
varname = slicedFromI;
|
|
122
|
+
i = s.length;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
varname = slicedFromI.slice(0, varendMatch.index);
|
|
126
|
+
i += (varendMatch.index ?? 0) - 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return getVar(envToUse, "", varname);
|
|
130
|
+
}
|
|
131
|
+
for (i = 0; i < s.length; i++) {
|
|
132
|
+
const c = s.charAt(i);
|
|
133
|
+
isGlob = isGlob || (!quote && (c === "*" || c === "?"));
|
|
134
|
+
if (esc) {
|
|
135
|
+
out += c;
|
|
136
|
+
esc = false;
|
|
137
|
+
}
|
|
138
|
+
else if (quote) {
|
|
139
|
+
if (c === quote) {
|
|
140
|
+
quote = false;
|
|
141
|
+
}
|
|
142
|
+
else if (quote === SQ) {
|
|
143
|
+
out += c;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Double quote
|
|
147
|
+
if (c === Bs) {
|
|
148
|
+
i += 1;
|
|
149
|
+
const charAtI = s.charAt(i);
|
|
150
|
+
if (charAtI === DQ || charAtI === Bs || charAtI === DS) {
|
|
151
|
+
out += charAtI;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
out += Bs + charAtI;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else if (c === DS) {
|
|
158
|
+
out += parseEnvVar();
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
out += c;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (c === DQ || c === SQ) {
|
|
166
|
+
quote = c;
|
|
167
|
+
}
|
|
168
|
+
else if (controlRe.test(c)) {
|
|
169
|
+
return { op: s };
|
|
170
|
+
}
|
|
171
|
+
else if (hash.test(c)) {
|
|
172
|
+
commented = true;
|
|
173
|
+
const matchIndex = match.index;
|
|
174
|
+
const commentObj = {
|
|
175
|
+
comment: string.slice((matchIndex ?? 0) + i + 1),
|
|
176
|
+
};
|
|
177
|
+
if (out.length) {
|
|
178
|
+
return [out, commentObj];
|
|
179
|
+
}
|
|
180
|
+
return [commentObj];
|
|
181
|
+
}
|
|
182
|
+
else if (c === Bs) {
|
|
183
|
+
esc = true;
|
|
184
|
+
}
|
|
185
|
+
else if (c === DS) {
|
|
186
|
+
out += parseEnvVar();
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
out += c;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (isGlob) {
|
|
193
|
+
return { op: "glob", pattern: out };
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
})
|
|
197
|
+
.reduce((prev, arg) => {
|
|
198
|
+
// TODO: replace this whole reduce with a concat
|
|
199
|
+
return typeof arg === "undefined" ? prev : prev.concat(arg);
|
|
200
|
+
}, []);
|
|
201
|
+
}
|
|
202
|
+
export function parse(s, env = {}, opts) {
|
|
203
|
+
const mapped = parseInternal(s, env, opts);
|
|
204
|
+
if (typeof env !== "function") {
|
|
205
|
+
return mapped;
|
|
206
|
+
}
|
|
207
|
+
return mapped.reduce((acc, s) => {
|
|
208
|
+
if (typeof s === "object") {
|
|
209
|
+
return acc.concat(s);
|
|
210
|
+
}
|
|
211
|
+
const xs = s.split(RegExp(`(${Token}.*?${Token})`, "g"));
|
|
212
|
+
if (xs.length === 1) {
|
|
213
|
+
return acc.concat(xs[0]);
|
|
214
|
+
}
|
|
215
|
+
return acc.concat(xs.filter(Boolean).map((x) => {
|
|
216
|
+
if (startsWithToken.test(x)) {
|
|
217
|
+
const parts = x.split(Token);
|
|
218
|
+
return JSON.parse(parts[1]);
|
|
219
|
+
}
|
|
220
|
+
return x;
|
|
221
|
+
}));
|
|
222
|
+
}, []);
|
|
223
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote.d.ts","sourceRoot":"","sources":["../../../source/utils/bash/quote.ts"],"names":[],"mappings":"AAAA,KAAK,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC;AAE9E,wBAAgB,KAAK,CAAC,EAAE,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAyBrD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function quote(xs) {
|
|
2
|
+
return xs
|
|
3
|
+
.map((s) => {
|
|
4
|
+
if (s === null || s === undefined) {
|
|
5
|
+
return String(s);
|
|
6
|
+
}
|
|
7
|
+
const str = String(s);
|
|
8
|
+
if (str === "") {
|
|
9
|
+
return "''";
|
|
10
|
+
}
|
|
11
|
+
if (s && typeof s === "object") {
|
|
12
|
+
return s.op.replace(/(.)/g, "\\$1");
|
|
13
|
+
}
|
|
14
|
+
if (/["\s\\]/.test(str) && !/'/.test(str)) {
|
|
15
|
+
return `'${str.replace(/(['])/g, "\\$1")}'`;
|
|
16
|
+
}
|
|
17
|
+
if (/["'\s]/.test(str)) {
|
|
18
|
+
return `"${str.replace(/(["\\$`!])/g, "\\$1")}"`;
|
|
19
|
+
}
|
|
20
|
+
return str.replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, "$1\\$2");
|
|
21
|
+
})
|
|
22
|
+
.join(" ");
|
|
23
|
+
}
|
package/dist/utils/bash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../source/utils/bash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../source/utils/bash.ts"],"names":[],"mappings":"AAuJA,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EAAE,EACrB,GAAG,EAAE,MAAM,GACV;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAiDtC;AAED,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,YAAY,MAAM,EAClB,cAAc,MAAM,EAAE,KACrB,MAkEF,CAAC;AA6FF,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,KAAG,OAetD,CAAC"}
|
package/dist/utils/bash.js
CHANGED
|
@@ -2,63 +2,158 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { isPathWithinAllowedDirs } from "./filesystem/security.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
let inQuotes = false;
|
|
11
|
-
let quoteChar = "";
|
|
12
|
-
for (let i = 0; i < command.length; i++) {
|
|
13
|
-
const char = command[i] ?? "";
|
|
14
|
-
if ((char === '"' || char === "'") && !inQuotes) {
|
|
15
|
-
inQuotes = true;
|
|
16
|
-
quoteChar = char;
|
|
17
|
-
current += char;
|
|
5
|
+
function handleNormalMode(state, char, command, tokens) {
|
|
6
|
+
if (/\s/.test(char)) {
|
|
7
|
+
if (state.current) {
|
|
8
|
+
tokens.push(state.current);
|
|
9
|
+
state.current = "";
|
|
18
10
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (char === "'") {
|
|
14
|
+
state.mode = "single";
|
|
15
|
+
state.current += char;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (char === '"') {
|
|
19
|
+
state.mode = "double";
|
|
20
|
+
state.current += char;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (char === "\\") {
|
|
24
|
+
const next = command[state.i + 1];
|
|
25
|
+
if (next !== undefined) {
|
|
26
|
+
state.current += char + next;
|
|
27
|
+
state.i++;
|
|
28
|
+
return;
|
|
23
29
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
}
|
|
31
|
+
state.current += char;
|
|
32
|
+
}
|
|
33
|
+
function handleSingleQuoteMode(state, char) {
|
|
34
|
+
state.current += char;
|
|
35
|
+
if (char === "'")
|
|
36
|
+
state.mode = "normal";
|
|
37
|
+
}
|
|
38
|
+
function handleDoubleQuoteMode(state, char, command) {
|
|
39
|
+
state.current += char;
|
|
40
|
+
if (char === "\\") {
|
|
41
|
+
const next = command[state.i + 1];
|
|
42
|
+
if (next !== undefined) {
|
|
43
|
+
state.current += next;
|
|
44
|
+
state.i++;
|
|
29
45
|
}
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (char === '"')
|
|
49
|
+
state.mode = "normal";
|
|
50
|
+
}
|
|
51
|
+
// Tokenize shell command respecting quotes and escapes
|
|
52
|
+
function tokenizeShellWords(command) {
|
|
53
|
+
const tokens = [];
|
|
54
|
+
const state = { current: "", mode: "normal", i: 0 };
|
|
55
|
+
for (state.i = 0; state.i < command.length; state.i++) {
|
|
56
|
+
const char = command[state.i] ?? "";
|
|
57
|
+
if (state.mode === "normal")
|
|
58
|
+
handleNormalMode(state, char, command, tokens);
|
|
59
|
+
else if (state.mode === "single")
|
|
60
|
+
handleSingleQuoteMode(state, char);
|
|
61
|
+
else
|
|
62
|
+
handleDoubleQuoteMode(state, char, command);
|
|
63
|
+
}
|
|
64
|
+
if (state.current)
|
|
65
|
+
tokens.push(state.current);
|
|
66
|
+
return tokens;
|
|
67
|
+
}
|
|
68
|
+
// Strip surrounding quotes from a token
|
|
69
|
+
function stripQuotes(token) {
|
|
70
|
+
if ((token.startsWith('"') && token.endsWith('"')) ||
|
|
71
|
+
(token.startsWith("'") && token.endsWith("'"))) {
|
|
72
|
+
return token.slice(1, -1);
|
|
73
|
+
}
|
|
74
|
+
return token;
|
|
75
|
+
}
|
|
76
|
+
// Check if a token is fully quoted (content should not be path-validated)
|
|
77
|
+
function isFullyQuoted(token) {
|
|
78
|
+
if (token.length < 2)
|
|
79
|
+
return false;
|
|
80
|
+
const first = token[0];
|
|
81
|
+
const last = token[token.length - 1];
|
|
82
|
+
return (first === '"' && last === '"') || (first === "'" && last === "'");
|
|
83
|
+
}
|
|
84
|
+
// Compute which tokens should be skipped from path validation
|
|
85
|
+
function computeSkipTokenMask(tokens) {
|
|
86
|
+
const skip = new Array(tokens.length).fill(false);
|
|
87
|
+
const bin = stripQuotes(tokens[0] ?? "");
|
|
88
|
+
const sub = stripQuotes(tokens[1] ?? "");
|
|
89
|
+
// Commands where -m/--message is a string argument (not a path)
|
|
90
|
+
const gitMessageSubs = new Set(["commit", "merge", "tag", "revert", "notes"]);
|
|
91
|
+
if (bin === "git" && gitMessageSubs.has(sub)) {
|
|
92
|
+
let seenDoubleDash = false;
|
|
93
|
+
for (let i = 2; i < tokens.length; i++) {
|
|
94
|
+
const t = stripQuotes(tokens[i] ?? "");
|
|
95
|
+
if (t === "--") {
|
|
96
|
+
seenDoubleDash = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (seenDoubleDash)
|
|
100
|
+
continue;
|
|
101
|
+
// --message=<msg> - skip this entire token
|
|
102
|
+
if (t.startsWith("--message=")) {
|
|
103
|
+
skip[i] = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// -m<msg> (attached message) - skip this entire token
|
|
107
|
+
if (/^-m.+/.test(t)) {
|
|
108
|
+
skip[i] = true;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// -m or --message consumes next token
|
|
112
|
+
if (t === "-m" || t === "--message") {
|
|
113
|
+
if (i + 1 < tokens.length)
|
|
114
|
+
skip[i + 1] = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// Combined short opts containing m, e.g. -am, -avm
|
|
118
|
+
if (/^-[^-]+$/.test(t) && t.includes("m")) {
|
|
119
|
+
if (i + 1 < tokens.length)
|
|
120
|
+
skip[i + 1] = true;
|
|
121
|
+
}
|
|
32
122
|
}
|
|
33
123
|
}
|
|
34
|
-
|
|
35
|
-
|
|
124
|
+
return skip;
|
|
125
|
+
}
|
|
126
|
+
// Validate path arguments to ensure they're within the project
|
|
127
|
+
export function validatePaths(command, allowedDirs, cwd) {
|
|
128
|
+
const tokens = tokenizeShellWords(command);
|
|
129
|
+
const skip = computeSkipTokenMask(tokens);
|
|
36
130
|
// Check each token that looks like a path
|
|
37
131
|
for (let i = 1; i < tokens.length; i++) {
|
|
38
|
-
// Skip the command itself
|
|
39
132
|
const token = tokens[i];
|
|
40
133
|
if (!token)
|
|
41
134
|
continue;
|
|
135
|
+
// Skip tokens marked by command-aware logic
|
|
136
|
+
if (skip[i])
|
|
137
|
+
continue;
|
|
138
|
+
// Skip fully quoted tokens - they're string arguments, not paths
|
|
139
|
+
// (paths are typically unquoted or only quoted to handle spaces)
|
|
140
|
+
if (isFullyQuoted(token) && token.includes("\n")) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
42
143
|
// Remove quotes for path checking
|
|
43
|
-
const cleanToken = token
|
|
144
|
+
const cleanToken = stripQuotes(token);
|
|
44
145
|
// Skip if it's clearly not a path
|
|
45
146
|
if (cleanToken.startsWith("-") ||
|
|
46
147
|
cleanToken.includes("://") ||
|
|
47
148
|
(!cleanToken.includes("/") && cleanToken !== "~")) {
|
|
48
149
|
continue;
|
|
49
150
|
}
|
|
50
|
-
// Skip git commit messages and other special cases
|
|
51
|
-
const prevToken = tokens[i - 1]?.replace(/^['"]|['"]$/g, "");
|
|
52
|
-
if (prevToken === "-m" || prevToken === "--message") {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
151
|
try {
|
|
56
152
|
// Expand ~ to home directory for proper validation
|
|
57
153
|
const expandedToken = cleanToken.startsWith("~/") || cleanToken === "~"
|
|
58
154
|
? path.join(os.homedir(), cleanToken.slice(1))
|
|
59
155
|
: cleanToken;
|
|
60
156
|
const resolvedPath = path.resolve(cwd, expandedToken);
|
|
61
|
-
// Allow access to explicitly allowed paths
|
|
62
157
|
if (!isPathWithinAllowedDirs(resolvedPath, allowedDirs)) {
|
|
63
158
|
return {
|
|
64
159
|
isValid: false,
|
|
@@ -125,6 +220,87 @@ export const resolveCwd = (cwdInput, workingDir, allowedDirs) => {
|
|
|
125
220
|
}
|
|
126
221
|
return target;
|
|
127
222
|
};
|
|
223
|
+
const mutatingBinaries = new Set([
|
|
224
|
+
"rm",
|
|
225
|
+
"mv",
|
|
226
|
+
"cp",
|
|
227
|
+
"mkdir",
|
|
228
|
+
"rmdir",
|
|
229
|
+
"touch",
|
|
230
|
+
"chmod",
|
|
231
|
+
"chown",
|
|
232
|
+
"ln",
|
|
233
|
+
"truncate",
|
|
234
|
+
"dd",
|
|
235
|
+
"tee",
|
|
236
|
+
]);
|
|
237
|
+
const npmMutating = new Set([
|
|
238
|
+
"install",
|
|
239
|
+
"uninstall",
|
|
240
|
+
"update",
|
|
241
|
+
"ci",
|
|
242
|
+
"publish",
|
|
243
|
+
"link",
|
|
244
|
+
"dedupe",
|
|
245
|
+
"prune",
|
|
246
|
+
"rebuild",
|
|
247
|
+
"add",
|
|
248
|
+
]);
|
|
249
|
+
const gitMutating = new Set([
|
|
250
|
+
"add",
|
|
251
|
+
"am",
|
|
252
|
+
"apply",
|
|
253
|
+
"branch",
|
|
254
|
+
"checkout",
|
|
255
|
+
"switch",
|
|
256
|
+
"cherry-pick",
|
|
257
|
+
"clean",
|
|
258
|
+
"commit",
|
|
259
|
+
"merge",
|
|
260
|
+
"mv",
|
|
261
|
+
"pull",
|
|
262
|
+
"push",
|
|
263
|
+
"rebase",
|
|
264
|
+
"reset",
|
|
265
|
+
"revert",
|
|
266
|
+
"stash",
|
|
267
|
+
"tag",
|
|
268
|
+
"worktree",
|
|
269
|
+
"submodule",
|
|
270
|
+
"config",
|
|
271
|
+
]);
|
|
272
|
+
const actionMutating = new Set(["create", "update", "upgrade", "install"]);
|
|
273
|
+
const packageManagers = new Set(["npm", "pnpm", "yarn"]);
|
|
274
|
+
function isSegmentMutating(seg) {
|
|
275
|
+
const tokens = seg.split(/\s+/);
|
|
276
|
+
if (tokens.length === 0)
|
|
277
|
+
return false;
|
|
278
|
+
const bin = tokens[0];
|
|
279
|
+
if (!bin)
|
|
280
|
+
return false;
|
|
281
|
+
if (tokens.some((t) => actionMutating.has(t))) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
if (bin === "sed" && tokens.some((t) => /^-i/.test(t))) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
if (mutatingBinaries.has(bin)) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
if (bin === "git" && tokens.length > 1) {
|
|
291
|
+
const sub = tokens[1];
|
|
292
|
+
if (typeof sub === "string" && gitMutating.has(sub)) {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (packageManagers.has(bin) && tokens.length > 1) {
|
|
297
|
+
const sub = tokens[1];
|
|
298
|
+
if (typeof sub === "string" && npmMutating.has(sub)) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
128
304
|
export const isMutatingCommand = (rawCommand) => {
|
|
129
305
|
const command = rawCommand.trim();
|
|
130
306
|
// Redirections that write to disk
|
|
@@ -136,96 +312,5 @@ export const isMutatingCommand = (rawCommand) => {
|
|
|
136
312
|
.split(/\s*(?:&&|\|\||;|\|)\s*/)
|
|
137
313
|
.map((s) => s.trim())
|
|
138
314
|
.filter((s) => s.length > 0);
|
|
139
|
-
|
|
140
|
-
"rm",
|
|
141
|
-
"mv",
|
|
142
|
-
"cp",
|
|
143
|
-
"mkdir",
|
|
144
|
-
"rmdir",
|
|
145
|
-
"touch",
|
|
146
|
-
"chmod",
|
|
147
|
-
"chown",
|
|
148
|
-
"ln",
|
|
149
|
-
"truncate",
|
|
150
|
-
"dd",
|
|
151
|
-
"tee",
|
|
152
|
-
]);
|
|
153
|
-
const npmMutating = new Set([
|
|
154
|
-
"install",
|
|
155
|
-
"uninstall",
|
|
156
|
-
"update",
|
|
157
|
-
"ci",
|
|
158
|
-
"publish",
|
|
159
|
-
"link",
|
|
160
|
-
"dedupe",
|
|
161
|
-
"prune",
|
|
162
|
-
"rebuild",
|
|
163
|
-
"add",
|
|
164
|
-
]);
|
|
165
|
-
const gitMutating = new Set([
|
|
166
|
-
"add",
|
|
167
|
-
"am",
|
|
168
|
-
"apply",
|
|
169
|
-
"branch",
|
|
170
|
-
"checkout",
|
|
171
|
-
"switch",
|
|
172
|
-
"cherry-pick",
|
|
173
|
-
"clean",
|
|
174
|
-
"commit",
|
|
175
|
-
"merge",
|
|
176
|
-
"mv",
|
|
177
|
-
"pull",
|
|
178
|
-
"push",
|
|
179
|
-
"rebase",
|
|
180
|
-
"reset",
|
|
181
|
-
"revert",
|
|
182
|
-
"stash",
|
|
183
|
-
"tag",
|
|
184
|
-
"worktree",
|
|
185
|
-
"submodule",
|
|
186
|
-
"config",
|
|
187
|
-
]);
|
|
188
|
-
// Generic action words that should be considered mutating when present in the command
|
|
189
|
-
const actionMutating = new Set(["create", "update", "upgrade", "install"]);
|
|
190
|
-
for (const seg of segments) {
|
|
191
|
-
const tokens = seg.split(/\s+/);
|
|
192
|
-
if (tokens.length === 0)
|
|
193
|
-
continue;
|
|
194
|
-
const bin = tokens[0];
|
|
195
|
-
if (!bin)
|
|
196
|
-
continue;
|
|
197
|
-
// If any token is an action-like mutating word, consider mutating
|
|
198
|
-
if (tokens.some((t) => actionMutating.has(t))) {
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
201
|
-
// sed -i is mutating
|
|
202
|
-
if (bin === "sed") {
|
|
203
|
-
if (tokens.some((t) => /^-i/.test(t))) {
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
// sed without -i is not mutating
|
|
207
|
-
}
|
|
208
|
-
if (mutatingBinaries.has(bin)) {
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
if (bin === "git" && tokens.length > 1) {
|
|
212
|
-
const sub = tokens[1];
|
|
213
|
-
if (typeof sub === "string" && gitMutating.has(sub)) {
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (bin === "npm" && tokens.length > 1) {
|
|
218
|
-
const sub = tokens[1];
|
|
219
|
-
if (typeof sub === "string" && npmMutating.has(sub)) {
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if ((bin === "pnpm" || bin === "yarn") && tokens.length > 1) {
|
|
224
|
-
const sub = tokens[1];
|
|
225
|
-
if (typeof sub === "string" && npmMutating.has(sub)) {
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return false;
|
|
315
|
+
return segments.some((seg) => isSegmentMutating(seg));
|
|
231
316
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Protection Module
|
|
3
|
+
* Detects and blocks destructive commands that could cause data loss
|
|
4
|
+
*/
|
|
5
|
+
export interface BlockedCommandResult {
|
|
6
|
+
blocked: true;
|
|
7
|
+
reason: string;
|
|
8
|
+
command: string;
|
|
9
|
+
tip: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SafeCommandResult {
|
|
12
|
+
blocked: false;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Result type for command safety check
|
|
16
|
+
*/
|
|
17
|
+
export type CommandSafetyResult = BlockedCommandResult | SafeCommandResult;
|
|
18
|
+
/**
|
|
19
|
+
* Detects if a command is destructive and should be blocked
|
|
20
|
+
* @param command - The full command string to check
|
|
21
|
+
* @returns BlockedCommandResult if destructive, SafeCommandResult if safe
|
|
22
|
+
*/
|
|
23
|
+
export declare function detectDestructiveCommand(command: string): CommandSafetyResult;
|
|
24
|
+
/**
|
|
25
|
+
* Generate a user-friendly blocked command message
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatBlockedCommandMessage(result: BlockedCommandResult): string;
|
|
28
|
+
//# sourceMappingURL=command-protection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-protection.d.ts","sourceRoot":"","sources":["../../source/utils/command-protection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,KAAK,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,GAAG,iBAAiB,CAAC;AAE3E;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CA4B7E;AA8UD;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,oBAAoB,GAC3B,MAAM,CAQR"}
|