lonny-agent 0.2.6 → 0.2.7
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/.github/workflows/ci.yml +21 -0
- package/AGENTS.md +106 -0
- package/CHANGELOG.md +24 -0
- package/dist/agent/__tests__/session-restore.test.d.ts +2 -0
- package/dist/agent/__tests__/session-restore.test.d.ts.map +1 -0
- package/dist/agent/__tests__/session-restore.test.js +260 -0
- package/dist/agent/__tests__/session-restore.test.js.map +1 -0
- package/dist/agent/commands.d.ts +26 -0
- package/dist/agent/commands.d.ts.map +1 -0
- package/dist/agent/commands.js +253 -0
- package/dist/agent/commands.js.map +1 -0
- package/dist/agent/compaction.js +8 -1
- package/dist/agent/compaction.js.map +1 -1
- package/dist/agent/index.d.ts +2 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +2 -2
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/memory.d.ts +12 -0
- package/dist/agent/memory.d.ts.map +1 -0
- package/dist/agent/memory.js +51 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/prompt-builder.d.ts +6 -1
- package/dist/agent/prompt-builder.d.ts.map +1 -1
- package/dist/agent/prompt-builder.js +89 -26
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/openai.d.ts +1 -0
- package/dist/agent/providers/openai.d.ts.map +1 -1
- package/dist/agent/providers/openai.js +16 -3
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/session-chat.d.ts +3 -0
- package/dist/agent/session-chat.d.ts.map +1 -0
- package/dist/agent/session-chat.js +384 -0
- package/dist/agent/session-chat.js.map +1 -0
- package/dist/agent/session-display.d.ts +29 -0
- package/dist/agent/session-display.d.ts.map +1 -0
- package/dist/agent/session-display.js +179 -0
- package/dist/agent/session-display.js.map +1 -0
- package/dist/agent/session-persistence.d.ts +55 -0
- package/dist/agent/session-persistence.d.ts.map +1 -0
- package/dist/agent/session-persistence.js +101 -0
- package/dist/agent/session-persistence.js.map +1 -0
- package/dist/agent/session-utils.d.ts +5 -0
- package/dist/agent/session-utils.d.ts.map +1 -0
- package/dist/agent/session-utils.js +51 -0
- package/dist/agent/session-utils.js.map +1 -0
- package/dist/agent/session.d.ts +15 -17
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +226 -608
- package/dist/agent/session.js.map +1 -1
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +67 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/tools/__tests__/bash.test.js +168 -1
- package/dist/tools/__tests__/bash.test.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +253 -6
- package/dist/tools/__tests__/edit.test.js.map +1 -1
- package/dist/tools/__tests__/fetch.test.js +1 -1
- package/dist/tools/__tests__/fetch.test.js.map +1 -1
- package/dist/tools/__tests__/glob.test.js +1 -1
- package/dist/tools/__tests__/glob.test.js.map +1 -1
- package/dist/tools/__tests__/grep.test.js +1 -1
- package/dist/tools/__tests__/grep.test.js.map +1 -1
- package/dist/tools/__tests__/ls.test.js +1 -1
- package/dist/tools/__tests__/ls.test.js.map +1 -1
- package/dist/tools/__tests__/read.test.js +1 -1
- package/dist/tools/__tests__/read.test.js.map +1 -1
- package/dist/tools/__tests__/registry.test.js +313 -0
- package/dist/tools/__tests__/registry.test.js.map +1 -1
- package/dist/tools/__tests__/write_plan.test.js +1 -1
- package/dist/tools/__tests__/write_plan.test.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +659 -39
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts +2 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js +7 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts +6 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-final.js +11 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-final.js.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts +2 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-test.js +9 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-test.js.map +1 -0
- package/dist/tools/codebase/ast/__tests__/integration.test.d.ts +2 -0
- package/dist/tools/codebase/ast/__tests__/integration.test.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/integration.test.js +415 -0
- package/dist/tools/codebase/ast/__tests__/integration.test.js.map +1 -0
- package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts +2 -0
- package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js +414 -0
- package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js.map +1 -0
- package/dist/tools/codebase/ast/adapter.d.ts +23 -0
- package/dist/tools/codebase/ast/adapter.d.ts.map +1 -0
- package/dist/tools/codebase/ast/adapter.js +11 -0
- package/dist/tools/codebase/ast/adapter.js.map +1 -0
- package/dist/tools/codebase/ast/tools.d.ts +3 -0
- package/dist/tools/codebase/ast/tools.d.ts.map +1 -0
- package/dist/tools/codebase/ast/tools.js +286 -0
- package/dist/tools/codebase/ast/tools.js.map +1 -0
- package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts +3 -0
- package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts.map +1 -0
- package/dist/tools/codebase/ast/tree-sitter-adapter.js +412 -0
- package/dist/tools/codebase/ast/tree-sitter-adapter.js.map +1 -0
- package/dist/tools/codebase/ast/tree-sitter-converters.d.ts +4 -0
- package/dist/tools/codebase/ast/tree-sitter-converters.d.ts.map +1 -0
- package/dist/tools/codebase/ast/tree-sitter-converters.js +447 -0
- package/dist/tools/codebase/ast/tree-sitter-converters.js.map +1 -0
- package/dist/tools/codebase/ast/types.d.ts +103 -0
- package/dist/tools/codebase/ast/types.d.ts.map +1 -0
- package/dist/tools/codebase/ast/types.js +2 -0
- package/dist/tools/codebase/ast/types.js.map +1 -0
- package/dist/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
- package/dist/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
- package/dist/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
- package/dist/tools/codebase/find.d.ts +3 -0
- package/dist/tools/codebase/find.d.ts.map +1 -0
- package/dist/tools/codebase/find.js +61 -0
- package/dist/tools/codebase/find.js.map +1 -0
- package/dist/tools/codebase/glob.d.ts +3 -0
- package/dist/tools/codebase/glob.d.ts.map +1 -0
- package/dist/tools/codebase/glob.js +37 -0
- package/dist/tools/codebase/glob.js.map +1 -0
- package/dist/tools/codebase/grep.d.ts +3 -0
- package/dist/tools/codebase/grep.d.ts.map +1 -0
- package/dist/tools/codebase/grep.js +169 -0
- package/dist/tools/codebase/grep.js.map +1 -0
- package/dist/tools/codebase/ls.d.ts +3 -0
- package/dist/tools/codebase/ls.d.ts.map +1 -0
- package/dist/tools/codebase/ls.js +39 -0
- package/dist/tools/codebase/ls.js.map +1 -0
- package/dist/tools/codebase/read.d.ts +10 -0
- package/dist/tools/codebase/read.d.ts.map +1 -0
- package/dist/tools/codebase/read.js +81 -0
- package/dist/tools/codebase/read.js.map +1 -0
- package/dist/tools/delete_memory.d.ts +3 -0
- package/dist/tools/delete_memory.d.ts.map +1 -0
- package/dist/tools/delete_memory.js +46 -0
- package/dist/tools/delete_memory.js.map +1 -0
- package/dist/tools/edit/diff-compute.d.ts +4 -0
- package/dist/tools/edit/diff-compute.d.ts.map +1 -0
- package/dist/tools/edit/diff-compute.js +18 -0
- package/dist/tools/edit/diff-compute.js.map +1 -0
- package/dist/tools/edit/diff-render.d.ts +18 -0
- package/dist/tools/edit/diff-render.d.ts.map +1 -0
- package/dist/tools/edit/diff-render.js +128 -0
- package/dist/tools/edit/diff-render.js.map +1 -0
- package/dist/tools/edit/edit.d.ts +4 -0
- package/dist/tools/edit/edit.d.ts.map +1 -0
- package/dist/tools/edit/edit.js +440 -0
- package/dist/tools/edit/edit.js.map +1 -0
- package/dist/tools/edit/matcher.d.ts +24 -0
- package/dist/tools/edit/matcher.d.ts.map +1 -0
- package/dist/tools/edit/matcher.js +66 -0
- package/dist/tools/edit/matcher.js.map +1 -0
- package/dist/tools/edit/parser.d.ts +10 -0
- package/dist/tools/edit/parser.d.ts.map +1 -0
- package/dist/tools/edit/parser.js +120 -0
- package/dist/tools/edit/parser.js.map +1 -0
- package/dist/tools/edit/types.d.ts +16 -0
- package/dist/tools/edit/types.d.ts.map +1 -0
- package/dist/tools/edit/types.js +2 -0
- package/dist/tools/edit/types.js.map +1 -0
- package/dist/tools/edit/write_plan.d.ts +5 -0
- package/dist/tools/edit/write_plan.d.ts.map +1 -0
- package/dist/tools/edit/write_plan.js +98 -0
- package/dist/tools/edit/write_plan.js.map +1 -0
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +66 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/execute/bash/constants.d.ts +24 -0
- package/dist/tools/execute/bash/constants.d.ts.map +1 -0
- package/dist/tools/execute/bash/constants.js +88 -0
- package/dist/tools/execute/bash/constants.js.map +1 -0
- package/dist/tools/execute/bash/errors.d.ts +39 -0
- package/dist/tools/execute/bash/errors.d.ts.map +1 -0
- package/dist/tools/execute/bash/errors.js +249 -0
- package/dist/tools/execute/bash/errors.js.map +1 -0
- package/dist/tools/execute/bash/execution.d.ts +18 -0
- package/dist/tools/execute/bash/execution.d.ts.map +1 -0
- package/dist/tools/execute/bash/execution.js +207 -0
- package/dist/tools/execute/bash/execution.js.map +1 -0
- package/dist/tools/execute/bash/index.d.ts +3 -0
- package/dist/tools/execute/bash/index.d.ts.map +1 -0
- package/dist/tools/execute/bash/index.js +117 -0
- package/dist/tools/execute/bash/index.js.map +1 -0
- package/dist/tools/execute/bash/platform.d.ts +6 -0
- package/dist/tools/execute/bash/platform.d.ts.map +1 -0
- package/dist/tools/execute/bash/platform.js +64 -0
- package/dist/tools/execute/bash/platform.js.map +1 -0
- package/dist/tools/execute/bash/security.d.ts +12 -0
- package/dist/tools/execute/bash/security.d.ts.map +1 -0
- package/dist/tools/execute/bash/security.js +26 -0
- package/dist/tools/execute/bash/security.js.map +1 -0
- package/dist/tools/execute/bash/validation.d.ts +16 -0
- package/dist/tools/execute/bash/validation.d.ts.map +1 -0
- package/dist/tools/execute/bash/validation.js +57 -0
- package/dist/tools/execute/bash/validation.js.map +1 -0
- package/dist/tools/execute/bash.d.ts +3 -0
- package/dist/tools/execute/bash.d.ts.map +1 -0
- package/dist/tools/execute/bash.js +728 -0
- package/dist/tools/execute/bash.js.map +1 -0
- package/dist/tools/execute/git.d.ts +3 -0
- package/dist/tools/execute/git.d.ts.map +1 -0
- package/dist/tools/execute/git.js +183 -0
- package/dist/tools/execute/git.js.map +1 -0
- package/dist/tools/execute/task_complete.d.ts +7 -0
- package/dist/tools/execute/task_complete.d.ts.map +1 -0
- package/dist/tools/execute/task_complete.js +38 -0
- package/dist/tools/execute/task_complete.js.map +1 -0
- package/dist/tools/fetch.d.ts.map +1 -1
- package/dist/tools/fetch.js +2 -0
- package/dist/tools/fetch.js.map +1 -1
- package/dist/tools/find.d.ts.map +1 -1
- package/dist/tools/find.js +2 -0
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/git.d.ts.map +1 -1
- package/dist/tools/git.js +2 -0
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +2 -0
- package/dist/tools/glob.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +2 -0
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/install/clawhub.d.ts +26 -0
- package/dist/tools/install/clawhub.d.ts.map +1 -0
- package/dist/tools/install/clawhub.js +182 -0
- package/dist/tools/install/clawhub.js.map +1 -0
- package/dist/tools/install/constants.d.ts +5 -0
- package/dist/tools/install/constants.d.ts.map +1 -0
- package/dist/tools/install/constants.js +5 -0
- package/dist/tools/install/constants.js.map +1 -0
- package/dist/tools/install/install_skill.d.ts +3 -0
- package/dist/tools/install/install_skill.d.ts.map +1 -0
- package/dist/tools/install/install_skill.js +132 -0
- package/dist/tools/install/install_skill.js.map +1 -0
- package/dist/tools/install/npm.d.ts +8 -0
- package/dist/tools/install/npm.d.ts.map +1 -0
- package/dist/tools/install/npm.js +52 -0
- package/dist/tools/install/npm.js.map +1 -0
- package/dist/tools/install/skill-content.d.ts +9 -0
- package/dist/tools/install/skill-content.d.ts.map +1 -0
- package/dist/tools/install/skill-content.js +89 -0
- package/dist/tools/install/skill-content.js.map +1 -0
- package/dist/tools/install_skill.d.ts.map +1 -1
- package/dist/tools/install_skill.js +2 -0
- package/dist/tools/install_skill.js.map +1 -1
- package/dist/tools/list_memory.d.ts +3 -0
- package/dist/tools/list_memory.d.ts.map +1 -0
- package/dist/tools/list_memory.js +55 -0
- package/dist/tools/list_memory.js.map +1 -0
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +2 -0
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/memory/delete_memory.d.ts +3 -0
- package/dist/tools/memory/delete_memory.d.ts.map +1 -0
- package/dist/tools/memory/delete_memory.js +46 -0
- package/dist/tools/memory/delete_memory.js.map +1 -0
- package/dist/tools/memory/list_memory.d.ts +3 -0
- package/dist/tools/memory/list_memory.d.ts.map +1 -0
- package/dist/tools/memory/list_memory.js +55 -0
- package/dist/tools/memory/list_memory.js.map +1 -0
- package/dist/tools/memory/save_memory.d.ts +3 -0
- package/dist/tools/memory/save_memory.d.ts.map +1 -0
- package/dist/tools/memory/save_memory.js +45 -0
- package/dist/tools/memory/save_memory.js.map +1 -0
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +2 -0
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/registry.d.ts +7 -2
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +112 -17
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/save_memory.d.ts +3 -0
- package/dist/tools/save_memory.d.ts.map +1 -0
- package/dist/tools/save_memory.js +45 -0
- package/dist/tools/save_memory.js.map +1 -0
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +2 -0
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/tree.d.ts +12 -0
- package/dist/tools/tree.d.ts.map +1 -0
- package/dist/tools/tree.js +87 -0
- package/dist/tools/tree.js.map +1 -0
- package/dist/tools/types.d.ts +12 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/web/fetch.d.ts +3 -0
- package/dist/tools/web/fetch.d.ts.map +1 -0
- package/dist/tools/web/fetch.js +49 -0
- package/dist/tools/web/fetch.js.map +1 -0
- package/dist/tools/web/search.d.ts +3 -0
- package/dist/tools/web/search.d.ts.map +1 -0
- package/dist/tools/web/search.js +134 -0
- package/dist/tools/web/search.js.map +1 -0
- package/dist/tools/write_plan.d.ts.map +1 -1
- package/dist/tools/write_plan.js +2 -0
- package/dist/tools/write_plan.js.map +1 -1
- package/dist/tui/commands.d.ts +4 -0
- package/dist/tui/commands.d.ts.map +1 -0
- package/dist/tui/commands.js +118 -0
- package/dist/tui/commands.js.map +1 -0
- package/dist/tui/components/colors.d.ts +36 -0
- package/dist/tui/components/colors.d.ts.map +1 -0
- package/dist/tui/components/colors.js +48 -0
- package/dist/tui/components/colors.js.map +1 -0
- package/dist/tui/components/header-bar.d.ts +26 -0
- package/dist/tui/components/header-bar.d.ts.map +1 -0
- package/dist/tui/components/header-bar.js +78 -0
- package/dist/tui/components/header-bar.js.map +1 -0
- package/dist/tui/components/index.d.ts +9 -0
- package/dist/tui/components/index.d.ts.map +1 -0
- package/dist/tui/components/index.js +9 -0
- package/dist/tui/components/index.js.map +1 -0
- package/dist/tui/components/landing-screen.d.ts +12 -0
- package/dist/tui/components/landing-screen.d.ts.map +1 -0
- package/dist/tui/components/landing-screen.js +53 -0
- package/dist/tui/components/landing-screen.js.map +1 -0
- package/dist/tui/components/pixel-logo.d.ts +5 -0
- package/dist/tui/components/pixel-logo.d.ts.map +1 -0
- package/dist/tui/components/pixel-logo.js +27 -0
- package/dist/tui/components/pixel-logo.js.map +1 -0
- package/dist/tui/components/plan-utils.d.ts +11 -0
- package/dist/tui/components/plan-utils.d.ts.map +1 -0
- package/dist/tui/components/plan-utils.js +62 -0
- package/dist/tui/components/plan-utils.js.map +1 -0
- package/dist/tui/components/plans-list.d.ts +18 -0
- package/dist/tui/components/plans-list.d.ts.map +1 -0
- package/dist/tui/components/plans-list.js +46 -0
- package/dist/tui/components/plans-list.js.map +1 -0
- package/dist/tui/components/rich-footer.d.ts +29 -0
- package/dist/tui/components/rich-footer.d.ts.map +1 -0
- package/dist/tui/components/rich-footer.js +124 -0
- package/dist/tui/components/rich-footer.js.map +1 -0
- package/dist/tui/components/todo-panel.d.ts +14 -0
- package/dist/tui/components/todo-panel.d.ts.map +1 -0
- package/dist/tui/components/todo-panel.js +83 -0
- package/dist/tui/components/todo-panel.js.map +1 -0
- package/dist/tui/components.d.ts.map +1 -1
- package/dist/tui/components.js +14 -7
- package/dist/tui/components.js.map +1 -1
- package/dist/tui/index.d.ts +2 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +99 -505
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/overlays.d.ts +28 -0
- package/dist/tui/overlays.d.ts.map +1 -0
- package/dist/tui/overlays.js +154 -0
- package/dist/tui/overlays.js.map +1 -0
- package/dist/tui/themes.d.ts +5 -0
- package/dist/tui/themes.d.ts.map +1 -0
- package/dist/tui/themes.js +38 -0
- package/dist/tui/themes.js.map +1 -0
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +60 -122
- package/dist/web/index.js.map +1 -1
- package/dist/web/public/index.html +1 -1
- package/dist/web/public/messages.js +41 -25
- package/dist/web/public/state.js +72 -72
- package/dist/web/public/style.css +1126 -996
- package/dist/web/public/websocket.js +267 -267
- package/dist/web/session-bridge.d.ts.map +1 -1
- package/dist/web/session-bridge.js +82 -3
- package/dist/web/session-bridge.js.map +1 -1
- package/docs/ast-tools.md +117 -0
- package/package.json +60 -55
- package/scripts/check-line-count.mjs +53 -0
- package/scripts/copy-ast-wasm.mjs +32 -0
- package/src/agent/__tests__/session-restore.test.ts +284 -0
- package/src/agent/commands.ts +274 -0
- package/src/agent/compaction.ts +9 -2
- package/src/agent/index.ts +30 -25
- package/src/agent/memory.ts +60 -0
- package/src/agent/prompt-builder.ts +100 -26
- package/src/agent/providers/anthropic.ts +179 -179
- package/src/agent/providers/google.ts +210 -210
- package/src/agent/providers/ollama.ts +186 -186
- package/src/agent/providers/openai.ts +18 -3
- package/src/agent/session-chat.ts +480 -0
- package/src/agent/session-display.ts +195 -0
- package/src/agent/session-persistence.ts +146 -0
- package/src/agent/session-utils.ts +54 -0
- package/src/agent/session.ts +261 -718
- package/src/cli/index.ts +73 -4
- package/src/config/index.ts +342 -342
- package/src/index.ts +30 -3
- package/src/tools/__tests__/bash.test.ts +187 -1
- package/src/tools/__tests__/edit.test.ts +275 -11
- package/src/tools/__tests__/fetch.test.ts +1 -1
- package/src/tools/__tests__/glob.test.ts +1 -1
- package/src/tools/__tests__/grep.test.ts +1 -1
- package/src/tools/__tests__/ls.test.ts +1 -1
- package/src/tools/__tests__/read.test.ts +1 -1
- package/src/tools/__tests__/registry.test.ts +332 -0
- package/src/tools/__tests__/write_plan.test.ts +1 -1
- package/src/tools/codebase/ast/__tests__/integration.test.ts +452 -0
- package/src/tools/codebase/ast/__tests__/tree-sitter.test.ts +466 -0
- package/src/tools/codebase/ast/adapter.ts +44 -0
- package/src/tools/codebase/ast/tools.ts +322 -0
- package/src/tools/codebase/ast/tree-sitter-adapter.ts +468 -0
- package/src/tools/codebase/ast/tree-sitter-converters.ts +480 -0
- package/src/tools/codebase/ast/types.ts +102 -0
- package/src/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
- package/src/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
- package/src/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
- package/src/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
- package/src/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
- package/src/tools/{find.ts → codebase/find.ts} +4 -2
- package/src/tools/{glob.ts → codebase/glob.ts} +4 -2
- package/src/tools/{grep.ts → codebase/grep.ts} +4 -2
- package/src/tools/{ls.ts → codebase/ls.ts} +4 -2
- package/src/tools/{read.ts → codebase/read.ts} +5 -3
- package/src/tools/edit/diff-compute.ts +21 -0
- package/src/tools/edit/diff-render.ts +145 -0
- package/src/tools/edit/edit.ts +468 -0
- package/src/tools/edit/matcher.ts +70 -0
- package/src/tools/edit/parser.ts +131 -0
- package/src/tools/edit/types.ts +19 -0
- package/src/tools/{write_plan.ts → edit/write_plan.ts} +4 -2
- package/src/tools/execute/bash/constants.ts +93 -0
- package/src/tools/execute/bash/errors.ts +304 -0
- package/src/tools/execute/bash/execution.ts +211 -0
- package/src/tools/execute/bash/index.ts +131 -0
- package/src/tools/execute/bash/platform.ts +65 -0
- package/src/tools/execute/bash/security.ts +28 -0
- package/src/tools/execute/bash/validation.ts +78 -0
- package/src/tools/execute/git.ts +209 -0
- package/src/tools/execute/task_complete.ts +40 -0
- package/src/tools/install/clawhub.ts +222 -0
- package/src/tools/install/constants.ts +4 -0
- package/src/tools/install/install_skill.ts +150 -0
- package/src/tools/install/npm.ts +60 -0
- package/src/tools/install/skill-content.ts +108 -0
- package/src/tools/memory/delete_memory.ts +46 -0
- package/src/tools/memory/list_memory.ts +53 -0
- package/src/tools/memory/save_memory.ts +45 -0
- package/src/tools/registry.ts +119 -19
- package/src/tools/tree.ts +103 -0
- package/src/tools/types.ts +13 -0
- package/src/tools/{fetch.ts → web/fetch.ts} +4 -2
- package/src/tools/{search.ts → web/search.ts} +4 -2
- package/src/tui/commands.ts +135 -0
- package/src/tui/components/colors.ts +52 -0
- package/src/tui/components/header-bar.ts +88 -0
- package/src/tui/components/index.ts +8 -0
- package/src/tui/components/landing-screen.ts +67 -0
- package/src/tui/components/pixel-logo.ts +33 -0
- package/src/tui/components/plan-utils.ts +69 -0
- package/src/tui/components/plans-list.ts +55 -0
- package/src/tui/components/rich-footer.ts +146 -0
- package/src/tui/components/todo-panel.ts +95 -0
- package/src/tui/index.ts +335 -786
- package/src/tui/overlays.ts +203 -0
- package/src/tui/themes.ts +41 -0
- package/src/web/index.ts +64 -127
- package/src/web/public/index.html +1 -1
- package/src/web/public/messages.js +41 -25
- package/src/web/public/state.js +72 -72
- package/src/web/public/style.css +1126 -996
- package/src/web/public/websocket.js +267 -267
- package/src/web/session-bridge.ts +272 -193
- package/AGENT.md +0 -99
- package/src/tools/bash.ts +0 -125
- package/src/tools/edit.ts +0 -747
- package/src/tools/git.ts +0 -76
- package/src/tools/install_skill.ts +0 -539
- package/src/tui/components.ts +0 -635
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process'
|
|
2
|
+
import { execSync, spawn } from 'node:child_process'
|
|
3
|
+
import * as os from 'node:os'
|
|
4
|
+
|
|
5
|
+
import { fmtErr } from '../../errors.js'
|
|
6
|
+
import { MAX_ERROR_LENGTH, MAX_OUTPUT_LENGTH } from './constants.js'
|
|
7
|
+
import { ENCODING } from './platform.js'
|
|
8
|
+
|
|
9
|
+
interface ExecResult {
|
|
10
|
+
stdout: string
|
|
11
|
+
stderr: string
|
|
12
|
+
exitCode: number
|
|
13
|
+
timedOut: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Kill a child process tree reliably across platforms.
|
|
18
|
+
* - On Windows: uses `taskkill /F /T` for force-kill of the process tree when possible.
|
|
19
|
+
* - On Unix: sends SIGTERM first, then SIGKILL after a grace period.
|
|
20
|
+
* - Handles missing pid and protects against thrown errors.
|
|
21
|
+
*/
|
|
22
|
+
function killProcess(child: ChildProcess): NodeJS.Timeout | undefined {
|
|
23
|
+
try {
|
|
24
|
+
if (!child || typeof (child as any).pid !== 'number' || Number.isNaN((child as any).pid)) {
|
|
25
|
+
// Best-effort kill if pid is not available
|
|
26
|
+
try {
|
|
27
|
+
child.kill()
|
|
28
|
+
} catch {
|
|
29
|
+
/* ignore */
|
|
30
|
+
}
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (os.platform() === 'win32') {
|
|
35
|
+
try {
|
|
36
|
+
// Use taskkill to terminate the whole process tree. If it fails, fall back to child.kill().
|
|
37
|
+
execSync(`taskkill /F /T /PID ${child.pid}`, {
|
|
38
|
+
stdio: 'ignore',
|
|
39
|
+
timeout: 3000,
|
|
40
|
+
})
|
|
41
|
+
} catch {
|
|
42
|
+
try {
|
|
43
|
+
child.kill()
|
|
44
|
+
} catch {
|
|
45
|
+
/* ignore */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return undefined
|
|
49
|
+
} else {
|
|
50
|
+
try {
|
|
51
|
+
child.kill('SIGTERM')
|
|
52
|
+
} catch {
|
|
53
|
+
/* ignore */
|
|
54
|
+
}
|
|
55
|
+
// Track timer so we can force-KILL after a grace period
|
|
56
|
+
const killTimer = setTimeout(() => {
|
|
57
|
+
try {
|
|
58
|
+
child.kill('SIGKILL')
|
|
59
|
+
} catch {
|
|
60
|
+
/* already exited or cannot kill */
|
|
61
|
+
}
|
|
62
|
+
}, 5000)
|
|
63
|
+
// Ensure we clean up the timer and the listener when the child exits
|
|
64
|
+
const onExit = () => {
|
|
65
|
+
try {
|
|
66
|
+
clearTimeout(killTimer)
|
|
67
|
+
} catch {
|
|
68
|
+
/* ignore */
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
child.removeListener('exit', onExit)
|
|
72
|
+
} catch {
|
|
73
|
+
/* ignore */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
child.on('exit', onExit)
|
|
77
|
+
return killTimer
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
return undefined
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function execCommand(command: string, timeout: number, cwd?: string): Promise<ExecResult> {
|
|
85
|
+
return new Promise(resolve => {
|
|
86
|
+
const isWindows = os.platform() === 'win32'
|
|
87
|
+
const shellExe = isWindows ? 'powershell.exe' : process.env.SHELL || '/bin/sh'
|
|
88
|
+
const shellArgs = isWindows
|
|
89
|
+
? ['-NoProfile', '-NonInteractive', '-Command', command]
|
|
90
|
+
: ['-c', command]
|
|
91
|
+
|
|
92
|
+
// Protect against spawn throwing synchronously (e.g. missing shell)
|
|
93
|
+
let child: ChildProcess | undefined
|
|
94
|
+
try {
|
|
95
|
+
child = spawn(shellExe, shellArgs, {
|
|
96
|
+
cwd: cwd || process.cwd(),
|
|
97
|
+
env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
|
|
98
|
+
windowsHide: true,
|
|
99
|
+
})
|
|
100
|
+
} catch (err) {
|
|
101
|
+
// Return a normalized error result rather than throwing
|
|
102
|
+
resolve({ stdout: '', stderr: fmtErr(err), exitCode: 1, timedOut: false })
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let stdout = ''
|
|
107
|
+
let stderr = ''
|
|
108
|
+
let timedOut = false
|
|
109
|
+
|
|
110
|
+
// Keep memory bounded by limiting how much we accumulate for each stream
|
|
111
|
+
const MAX_STDOUT_BUFFER = Math.max(MAX_OUTPUT_LENGTH * 2, 20000)
|
|
112
|
+
const MAX_STDERR_BUFFER = Math.max(MAX_ERROR_LENGTH * 2, 4000)
|
|
113
|
+
let stdoutTruncated = false
|
|
114
|
+
let stderrTruncated = false
|
|
115
|
+
|
|
116
|
+
// Track which encodings work for each stream (per-chunk fallback).
|
|
117
|
+
let stdoutEncoding: string | undefined
|
|
118
|
+
let stderrEncoding: string | undefined
|
|
119
|
+
|
|
120
|
+
let treeKillTimer: NodeJS.Timeout | undefined
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
timedOut = true
|
|
123
|
+
// Attempt to kill the process tree; capture any returned timer so we can clear it on close
|
|
124
|
+
treeKillTimer = killProcess(child!)
|
|
125
|
+
}, timeout)
|
|
126
|
+
|
|
127
|
+
child!.stdout?.on('data', (data: Buffer) => {
|
|
128
|
+
if (stdout.length >= MAX_STDOUT_BUFFER) {
|
|
129
|
+
stdoutTruncated = true
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const enc = stdoutEncoding || ENCODING
|
|
134
|
+
stdout += data.toString(enc as BufferEncoding)
|
|
135
|
+
stdoutEncoding = enc
|
|
136
|
+
} catch {
|
|
137
|
+
// Detected encoding failed; fall back to utf-8
|
|
138
|
+
try {
|
|
139
|
+
stdout += data.toString('utf-8')
|
|
140
|
+
stdoutEncoding = 'utf-8'
|
|
141
|
+
} catch {
|
|
142
|
+
// Give up on this chunk
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
child!.stderr?.on('data', (data: Buffer) => {
|
|
148
|
+
if (stderr.length >= MAX_STDERR_BUFFER) {
|
|
149
|
+
stderrTruncated = true
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const enc = stderrEncoding || ENCODING
|
|
154
|
+
stderr += data.toString(enc as BufferEncoding)
|
|
155
|
+
stderrEncoding = enc
|
|
156
|
+
} catch {
|
|
157
|
+
try {
|
|
158
|
+
stderr += data.toString('utf-8')
|
|
159
|
+
stderrEncoding = 'utf-8'
|
|
160
|
+
} catch {
|
|
161
|
+
// ignore
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const finish = (code: number | null) => {
|
|
167
|
+
try {
|
|
168
|
+
clearTimeout(timer)
|
|
169
|
+
} catch {
|
|
170
|
+
/* ignore */
|
|
171
|
+
}
|
|
172
|
+
if (treeKillTimer) {
|
|
173
|
+
try {
|
|
174
|
+
clearTimeout(treeKillTimer)
|
|
175
|
+
} catch {
|
|
176
|
+
/* ignore */
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// If we truncated during streaming, append a short notice so final output can indicate truncation
|
|
181
|
+
if (stdoutTruncated) stdout += '\n... [stdout truncated due to size limit]'
|
|
182
|
+
if (stderrTruncated) stderr += '\n... [stderr truncated due to size limit]'
|
|
183
|
+
|
|
184
|
+
resolve({ stdout, stderr, exitCode: code ?? 0, timedOut })
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
child!.on('close', code => finish(code))
|
|
188
|
+
|
|
189
|
+
child!.on('error', err => {
|
|
190
|
+
try {
|
|
191
|
+
clearTimeout(timer)
|
|
192
|
+
} catch {
|
|
193
|
+
/* ignore */
|
|
194
|
+
}
|
|
195
|
+
if (treeKillTimer) {
|
|
196
|
+
try {
|
|
197
|
+
clearTimeout(treeKillTimer)
|
|
198
|
+
} catch {
|
|
199
|
+
/* ignore */
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Include the spawn error message in stderr so callers can surface it
|
|
203
|
+
const errMsg = fmtErr(err)
|
|
204
|
+
if (errMsg) stderr += (stderr ? '\n' : '') + errMsg
|
|
205
|
+
resolve({ stdout, stderr, exitCode: 1, timedOut })
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export type { ExecResult }
|
|
211
|
+
export { execCommand, killProcess }
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as os from 'node:os'
|
|
2
|
+
|
|
3
|
+
import { fmtErr } from '../../errors.js'
|
|
4
|
+
import type { Tool, ToolResult } from '../../types.js'
|
|
5
|
+
import { MAX_OUTPUT_LENGTH } from './constants.js'
|
|
6
|
+
import { buildErrorMsg, extractPowerShellNativeOutput, truncateOutput } from './errors.js'
|
|
7
|
+
import { execCommand } from './execution.js'
|
|
8
|
+
import { ENCODING, env } from './platform.js'
|
|
9
|
+
import { checkDestructive, redactSensitive } from './security.js'
|
|
10
|
+
import { validateInput } from './validation.js'
|
|
11
|
+
|
|
12
|
+
export const bashTool: Tool = {
|
|
13
|
+
definition: {
|
|
14
|
+
name: 'bash',
|
|
15
|
+
category: 'Execute',
|
|
16
|
+
group: 'Shell',
|
|
17
|
+
description: `Execute a shell command. Returns stdout and stderr.
|
|
18
|
+
|
|
19
|
+
Environment: ${env.osInfo}
|
|
20
|
+
Shell: ${env.shell}
|
|
21
|
+
Console encoding: ${ENCODING}
|
|
22
|
+
|
|
23
|
+
⚠️ Use this tool for READ-ONLY operations (run tests, check builds, list files).
|
|
24
|
+
For creating/modifying files, use the 'edit' tool instead.
|
|
25
|
+
For git operations (status, diff, log), use the 'git' tool instead.`,
|
|
26
|
+
parameters: {
|
|
27
|
+
command: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Shell command to execute (required, non-empty string)',
|
|
30
|
+
required: true,
|
|
31
|
+
},
|
|
32
|
+
description: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description:
|
|
35
|
+
'Brief description (e.g. "Run unit tests"). Included in the output for traceability.',
|
|
36
|
+
},
|
|
37
|
+
timeout: {
|
|
38
|
+
type: 'number',
|
|
39
|
+
description:
|
|
40
|
+
'Timeout in ms (100-600000, default: 120000). Increase for slow operations like npm install.',
|
|
41
|
+
},
|
|
42
|
+
cwd: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description:
|
|
45
|
+
'Working directory path. Use when you need to run a command inside a subdirectory. Must exist.',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
async execute(input): Promise<ToolResult> {
|
|
50
|
+
// ── Step 1: Validate input ──────────────────────────────────────────
|
|
51
|
+
const validated = validateInput(input)
|
|
52
|
+
if (!validated.ok) {
|
|
53
|
+
return { success: false, output: '', error: validated.error }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { command, timeout, description, cwd } = validated
|
|
57
|
+
|
|
58
|
+
// ── Step 2: Security check ──────────────────────────────────────────
|
|
59
|
+
const destructiveHint = checkDestructive(command)
|
|
60
|
+
if (destructiveHint) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
output: '',
|
|
64
|
+
error: `Destructive command blocked: ${destructiveHint}.\nUse the 'edit' tool for file modifications instead.`,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const { stdout, stderr, exitCode, timedOut } = await execCommand(command, timeout, cwd)
|
|
70
|
+
|
|
71
|
+
// ── Step 3: Build output ──────────────────────────────────────────
|
|
72
|
+
let output = ''
|
|
73
|
+
if (description) {
|
|
74
|
+
output += `[bash] ${description}\n`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Redact sensitive data from output (API keys, tokens, passwords, etc.)
|
|
78
|
+
const stdoutTrimmed = redactSensitive(stdout.trim())
|
|
79
|
+
if (stdoutTrimmed) {
|
|
80
|
+
output += stdoutTrimmed
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let stderrTrimmed = redactSensitive(stderr.trim())
|
|
84
|
+
|
|
85
|
+
// On Windows, PowerShell wraps native command stderr in ErrorRecord format
|
|
86
|
+
// even when the command succeeds (exit code 0). Detect this and extract the
|
|
87
|
+
// actual message content so it doesn't pollute output with "Command exited..." noise.
|
|
88
|
+
if (exitCode === 0 && stderrTrimmed && os.platform() === 'win32') {
|
|
89
|
+
const cleaned = extractPowerShellNativeOutput(stderrTrimmed)
|
|
90
|
+
if (cleaned) {
|
|
91
|
+
// Prepend a newline if there's already output
|
|
92
|
+
output += (output ? '\n' : '') + cleaned
|
|
93
|
+
stderrTrimmed = ''
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (stderrTrimmed) {
|
|
98
|
+
if (output) output += '\n'
|
|
99
|
+
output += `(stderr):\n${stderrTrimmed}`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Apply global truncation
|
|
103
|
+
output = truncateOutput(output, MAX_OUTPUT_LENGTH, 'output')
|
|
104
|
+
|
|
105
|
+
if (!output || output === (description ? `[bash] ${description}\n` : '')) {
|
|
106
|
+
output = description ? `[bash] ${description}\n(no output)` : '(no output)'
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (timedOut) {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
output,
|
|
113
|
+
error: buildErrorMsg(0, stderr, command, true, cwd),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (exitCode !== 0) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
output,
|
|
121
|
+
error: buildErrorMsg(exitCode, stderr, command, false, cwd),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { success: true, output }
|
|
126
|
+
} catch (err) {
|
|
127
|
+
const msg = fmtErr(err)
|
|
128
|
+
return { success: false, output: '', error: `Command failed unexpectedly: ${msg}` }
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import * as os from 'node:os'
|
|
3
|
+
|
|
4
|
+
function detectEnv(): { osInfo: string; shell: string } {
|
|
5
|
+
const platform = os.platform()
|
|
6
|
+
const release = os.release()
|
|
7
|
+
const arch = os.arch()
|
|
8
|
+
|
|
9
|
+
if (platform === 'win32') {
|
|
10
|
+
return {
|
|
11
|
+
osInfo: `Windows ${release} (${arch})`,
|
|
12
|
+
shell: 'powershell.exe',
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const osName = platform === 'darwin' ? 'macOS' : platform === 'linux' ? 'Linux' : platform
|
|
17
|
+
return {
|
|
18
|
+
osInfo: `${osName} ${release} (${arch})`,
|
|
19
|
+
shell: process.env.SHELL || '/bin/sh',
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const env = detectEnv()
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Detect Windows console code page to handle non-UTF-8 output correctly.
|
|
27
|
+
* Falls back to 'utf-8' if detection fails.
|
|
28
|
+
* On non-Windows platforms, always returns 'utf-8'.
|
|
29
|
+
*/
|
|
30
|
+
function detectEncoding(): string {
|
|
31
|
+
if (os.platform() !== 'win32') return 'utf-8'
|
|
32
|
+
try {
|
|
33
|
+
const result = execSync('[Console]::OutputEncoding.CodePage', {
|
|
34
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
35
|
+
timeout: 2000,
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
encoding: 'utf-8',
|
|
38
|
+
shell: 'powershell.exe',
|
|
39
|
+
})
|
|
40
|
+
const cp = Number.parseInt(result.toString().trim(), 10)
|
|
41
|
+
// Common Windows code pages that need special handling
|
|
42
|
+
if (cp === 936) return 'gbk' // Chinese (Simplified)
|
|
43
|
+
if (cp === 950) return 'big5' // Chinese (Traditional)
|
|
44
|
+
if (cp === 932) return 'shift-jis' // Japanese
|
|
45
|
+
if (cp === 949) return 'euc-kr' // Korean
|
|
46
|
+
if (
|
|
47
|
+
cp === 1250 ||
|
|
48
|
+
cp === 1251 ||
|
|
49
|
+
cp === 1252 ||
|
|
50
|
+
cp === 1253 ||
|
|
51
|
+
cp === 1254 ||
|
|
52
|
+
cp === 1255 ||
|
|
53
|
+
cp === 1256 ||
|
|
54
|
+
cp === 1257
|
|
55
|
+
) {
|
|
56
|
+
return `cp${cp}` // Windows code page
|
|
57
|
+
}
|
|
58
|
+
return 'utf-8'
|
|
59
|
+
} catch {
|
|
60
|
+
return 'utf-8'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Cache encoding detection once at startup
|
|
65
|
+
export const ENCODING = detectEncoding()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DESTRUCTIVE_PATTERNS, SENSITIVE_PATTERNS } from './constants.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Check if a command matches destructive patterns.
|
|
5
|
+
* Returns a warning string if detected, null otherwise.
|
|
6
|
+
*/
|
|
7
|
+
function checkDestructive(command: string): string | null {
|
|
8
|
+
for (const { regex, hint } of DESTRUCTIVE_PATTERNS) {
|
|
9
|
+
if (regex.test(command)) {
|
|
10
|
+
return hint
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Redact sensitive data from a string by replacing matches with "[REDACTED]".
|
|
18
|
+
* Returns the redacted string.
|
|
19
|
+
*/
|
|
20
|
+
function redactSensitive(s: string): string {
|
|
21
|
+
let result = s
|
|
22
|
+
for (const pattern of SENSITIVE_PATTERNS) {
|
|
23
|
+
result = result.replace(pattern, '[REDACTED]')
|
|
24
|
+
}
|
|
25
|
+
return result
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { checkDestructive, redactSensitive }
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_TIMEOUT, MAX_TIMEOUT, MIN_TIMEOUT } from './constants.js'
|
|
5
|
+
|
|
6
|
+
interface ValidatedInput {
|
|
7
|
+
ok: true
|
|
8
|
+
command: string
|
|
9
|
+
timeout: number
|
|
10
|
+
description: string
|
|
11
|
+
cwd: string | undefined
|
|
12
|
+
}
|
|
13
|
+
interface InputError {
|
|
14
|
+
ok: false
|
|
15
|
+
error: string
|
|
16
|
+
}
|
|
17
|
+
type ValidateResult = ValidatedInput | InputError
|
|
18
|
+
|
|
19
|
+
function validateInput(input: Record<string, unknown>): ValidateResult {
|
|
20
|
+
// command: must be a non-empty string
|
|
21
|
+
const command = input.command
|
|
22
|
+
if (command === undefined || command === null) {
|
|
23
|
+
return { ok: false, error: 'command is required' }
|
|
24
|
+
}
|
|
25
|
+
if (typeof command !== 'string') {
|
|
26
|
+
return { ok: false, error: `command must be a string, got ${typeof command}` }
|
|
27
|
+
}
|
|
28
|
+
if (command.trim() === '') {
|
|
29
|
+
return { ok: false, error: 'command must not be empty or whitespace-only' }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// timeout: must be a number (or default), within bounds
|
|
33
|
+
let timeout = DEFAULT_TIMEOUT
|
|
34
|
+
if (input.timeout !== undefined) {
|
|
35
|
+
if (
|
|
36
|
+
typeof input.timeout === 'number' &&
|
|
37
|
+
!Number.isNaN(input.timeout) &&
|
|
38
|
+
Number.isFinite(input.timeout)
|
|
39
|
+
) {
|
|
40
|
+
timeout = Math.round(input.timeout)
|
|
41
|
+
if (timeout < MIN_TIMEOUT) {
|
|
42
|
+
timeout = MIN_TIMEOUT
|
|
43
|
+
} else if (timeout > MAX_TIMEOUT) {
|
|
44
|
+
timeout = MAX_TIMEOUT
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// If timeout is not a valid number, silently use default
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// description: optional string
|
|
51
|
+
const description = typeof input.description === 'string' ? input.description : ''
|
|
52
|
+
|
|
53
|
+
// cwd: optional string, must be a valid directory, no path traversal
|
|
54
|
+
let cwd: string | undefined
|
|
55
|
+
if (typeof input.cwd === 'string' && input.cwd.trim()) {
|
|
56
|
+
const rawCwd = input.cwd.trim()
|
|
57
|
+
// Path traversal protection
|
|
58
|
+
if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(rawCwd)) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: `cwd contains path traversal (..) which is not allowed: "${rawCwd}"`,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const resolvedCwd = path.resolve(rawCwd)
|
|
65
|
+
if (!fs.existsSync(resolvedCwd)) {
|
|
66
|
+
return { ok: false, error: `cwd directory does not exist: "${resolvedCwd}"` }
|
|
67
|
+
}
|
|
68
|
+
if (!fs.statSync(resolvedCwd).isDirectory()) {
|
|
69
|
+
return { ok: false, error: `cwd is not a directory: "${resolvedCwd}"` }
|
|
70
|
+
}
|
|
71
|
+
cwd = resolvedCwd
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { ok: true, command: command.trim(), timeout, description, cwd }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type { InputError, ValidatedInput, ValidateResult }
|
|
78
|
+
export { validateInput }
|