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,70 @@
|
|
|
1
|
+
import type { MatchPos } from './types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a line for whitespace-tolerant comparison:
|
|
5
|
+
* 1. Trim leading/trailing whitespace
|
|
6
|
+
* 2. Collapse runs of 2+ spaces/tabs into a single space
|
|
7
|
+
*
|
|
8
|
+
* This handles whitespace differences ANYWHERE on the line:
|
|
9
|
+
* - Leading/trailing spaces → `trim()` removes them
|
|
10
|
+
* - Extra internal spaces → `"foo bar"` → `"foo bar"`
|
|
11
|
+
* - Blank lines with spaces → `" "` → `""`
|
|
12
|
+
*/
|
|
13
|
+
/** Export for testing */
|
|
14
|
+
export function normalizeLine(s: string): string {
|
|
15
|
+
return s.trim().replace(/[ \t]{2,}/g, ' ')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sliding-window line search that ignores whitespace differences
|
|
20
|
+
* (leading, trailing, and internal runs).
|
|
21
|
+
*
|
|
22
|
+
* Returns all match positions in the ORIGINAL (unnormalized) content,
|
|
23
|
+
* so the caller can do content.slice(match.index, match.index + match.length)
|
|
24
|
+
* to extract the actual matched text (with its original whitespace).
|
|
25
|
+
*/
|
|
26
|
+
/** Export for testing */
|
|
27
|
+
export function findAllLinesTolerant(content: string, oldString: string): MatchPos[] {
|
|
28
|
+
if (oldString === '') return []
|
|
29
|
+
|
|
30
|
+
const contentLines = content.split('\n')
|
|
31
|
+
const oldLines = oldString.split('\n')
|
|
32
|
+
|
|
33
|
+
if (oldLines.length > contentLines.length) return []
|
|
34
|
+
|
|
35
|
+
// Pre-normalize for speed
|
|
36
|
+
const normContent = contentLines.map(normalizeLine)
|
|
37
|
+
const normOld = oldLines.map(normalizeLine)
|
|
38
|
+
|
|
39
|
+
const matches: MatchPos[] = []
|
|
40
|
+
|
|
41
|
+
for (let start = 0; start <= normContent.length - normOld.length; start++) {
|
|
42
|
+
let match = true
|
|
43
|
+
for (let j = 0; j < normOld.length; j++) {
|
|
44
|
+
if (normContent[start + j] !== normOld[j]) {
|
|
45
|
+
match = false
|
|
46
|
+
break
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (match) {
|
|
50
|
+
// Compute byte position in ORIGINAL (unnormalized) content
|
|
51
|
+
let charPos = 0
|
|
52
|
+
for (let k = 0; k < start; k++) {
|
|
53
|
+
charPos += contentLines[k]!.length + 1 // +1 for the \n
|
|
54
|
+
}
|
|
55
|
+
// Compute matched text length in ORIGINAL content
|
|
56
|
+
let matchedLen = 0
|
|
57
|
+
for (let j = 0; j < oldLines.length; j++) {
|
|
58
|
+
const line = contentLines[start + j]!
|
|
59
|
+
let lineLen = line.length
|
|
60
|
+
// CRLF: the last line's trailing \r is part of content but not in oldString
|
|
61
|
+
if (j === oldLines.length - 1 && line.endsWith('\r')) lineLen -= 1
|
|
62
|
+
matchedLen += lineLen
|
|
63
|
+
if (j < oldLines.length - 1) matchedLen += 1 // +1 for the \n
|
|
64
|
+
}
|
|
65
|
+
matches.push({ index: charPos, length: matchedLen })
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return matches
|
|
70
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Edit, SingleEdit } from './types.js'
|
|
2
|
+
|
|
3
|
+
/** Build diagnostic JSON for error messages */
|
|
4
|
+
export function buildDiag(edit: SingleEdit): string {
|
|
5
|
+
return JSON.stringify({
|
|
6
|
+
file_path: edit.file_path,
|
|
7
|
+
old_string: edit.old_string,
|
|
8
|
+
new_string: edit.new_string,
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Summarize raw input for error messages to avoid dumping huge strings into the LLM context. */
|
|
13
|
+
export function summarizeRawInput(rawInput: unknown): string {
|
|
14
|
+
const s = JSON.stringify(rawInput)
|
|
15
|
+
if (s.length <= 500) return s
|
|
16
|
+
return `${s.slice(0, 500)}... [truncated, total ${s.length} chars]`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Export for testing */
|
|
20
|
+
export function parseMarkdownEdit(content: string): Edit[] {
|
|
21
|
+
const edits: Edit[] = []
|
|
22
|
+
|
|
23
|
+
function parseEditBlock(raw: string): Edit | null {
|
|
24
|
+
// Remove stray ``` lines (model may close the block early before new:)
|
|
25
|
+
const cleaned = raw.replace(/^```\s*$/gm, '')
|
|
26
|
+
|
|
27
|
+
const fileMatch = cleaned.match(/^file:\s*(.+)$/m)
|
|
28
|
+
if (!fileMatch) return null
|
|
29
|
+
const filePath = fileMatch[1]!.trim()
|
|
30
|
+
|
|
31
|
+
let oldString = ''
|
|
32
|
+
let newString = ''
|
|
33
|
+
|
|
34
|
+
const oldMatch = cleaned.match(/^old:(?:\s*\|\s*[\d\-+]*\r?\n)?([\s\S]*?)^new:/m)
|
|
35
|
+
const newMatch = cleaned.match(/^new:(?:\s*\|\s*[\d\-+]*\r?\n)?([\s\S]*)$/m)
|
|
36
|
+
|
|
37
|
+
if (oldMatch) {
|
|
38
|
+
oldString = oldMatch[1]!.replace(/^\n+/, '').replace(/\n+$/, '')
|
|
39
|
+
}
|
|
40
|
+
if (newMatch) {
|
|
41
|
+
newString = newMatch[1]!.replace(/^\n+/, '').replace(/\n+$/, '')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { file_path: filePath, old_string: oldString || '', new_string: newString || '' }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Strategy 1: Non-greedy block regex (handles multi-edit, correct formatting)
|
|
48
|
+
const blockRegex = /```edit\s*([\s\S]*?)```/gi
|
|
49
|
+
for (const regexMatch of content.matchAll(blockRegex)) {
|
|
50
|
+
const edit = parseEditBlock(regexMatch[1]!)
|
|
51
|
+
if (edit) edits.push(edit)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Strategy 2: If no edit with new_string found (model likely closed ``` before new:),
|
|
55
|
+
// retry with greedy matching to capture everything up to the last ```
|
|
56
|
+
if (!edits.some(e => e.new_string)) {
|
|
57
|
+
edits.length = 0
|
|
58
|
+
const greedyRegex = /```edit\s*([\s\S]*)```/g
|
|
59
|
+
for (const regexMatch of content.matchAll(greedyRegex)) {
|
|
60
|
+
const edit = parseEditBlock(regexMatch[1]!)
|
|
61
|
+
if (edit) edits.push(edit)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Strategy 3: No block markers at all — try parsing raw content directly
|
|
66
|
+
if (edits.length === 0) {
|
|
67
|
+
const edit = parseEditBlock(content)
|
|
68
|
+
if (edit) edits.push(edit)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return edits
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Extract edits from legacy JSON format (backward compatibility) */
|
|
75
|
+
export function extractEditsFromJSON(input: Record<string, unknown>): Edit[] {
|
|
76
|
+
// Pattern 0: input is an array (edits passed directly instead of wrapped)
|
|
77
|
+
if (Array.isArray(input)) {
|
|
78
|
+
// Preserve array for validation
|
|
79
|
+
return input as Edit[]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Pattern 1: input has file_path, old_string, new_string at top level (missing edits array)
|
|
83
|
+
if (!Array.isArray(input.edits)) {
|
|
84
|
+
const keys = Object.keys(input)
|
|
85
|
+
|
|
86
|
+
// Check if the keys look like a single edit object (file_path + old_string + new_string)
|
|
87
|
+
const hasFilePath = typeof input.file_path === 'string'
|
|
88
|
+
const hasOldString = typeof input.old_string === 'string'
|
|
89
|
+
const hasNewString = typeof input.new_string === 'string'
|
|
90
|
+
|
|
91
|
+
if (hasFilePath && hasOldString && hasNewString) {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
file_path: input.file_path as string,
|
|
95
|
+
old_string: input.old_string as string,
|
|
96
|
+
new_string: input.new_string as string,
|
|
97
|
+
},
|
|
98
|
+
]
|
|
99
|
+
} else if (hasFilePath && hasOldString) {
|
|
100
|
+
// Only file_path + old_string (missing new_string) — use a sentinel
|
|
101
|
+
// value so validation catches this as an error instead of silently deleting content.
|
|
102
|
+
return [
|
|
103
|
+
{
|
|
104
|
+
file_path: input.file_path as string,
|
|
105
|
+
old_string: input.old_string as string,
|
|
106
|
+
new_string: (input.new_string as string) || '__MISSING_NEW_STRING__',
|
|
107
|
+
},
|
|
108
|
+
]
|
|
109
|
+
} else if (keys.length === 2 && hasFilePath && typeof input.new_string === 'string') {
|
|
110
|
+
// file_path + new_string but no old_string — treat as new file creation
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
file_path: input.file_path as string,
|
|
114
|
+
old_string: '',
|
|
115
|
+
new_string: input.new_string as string,
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
} else if (keys.length === 1 && hasFilePath) {
|
|
119
|
+
// Only file_path — maybe they meant create file with empty content?
|
|
120
|
+
return [{ file_path: input.file_path as string, old_string: '', new_string: '' }]
|
|
121
|
+
}
|
|
122
|
+
return []
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// If edits is an array (even empty), preserve for validation
|
|
126
|
+
if (Array.isArray(input.edits)) {
|
|
127
|
+
return input.edits as Edit[]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return []
|
|
131
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type DiffLineType = 'delete' | 'insert' | 'equal'
|
|
2
|
+
|
|
3
|
+
export interface DiffLine {
|
|
4
|
+
type: DiffLineType
|
|
5
|
+
content: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface MatchPos {
|
|
9
|
+
index: number
|
|
10
|
+
length: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SingleEdit {
|
|
14
|
+
file_path: string
|
|
15
|
+
old_string: string
|
|
16
|
+
new_string: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Edit = SingleEdit
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
|
-
import { fmtErr } from '
|
|
4
|
-
import type { Tool, ToolResult } from '
|
|
3
|
+
import { fmtErr } from '../errors.js'
|
|
4
|
+
import type { Tool, ToolResult } from '../types.js'
|
|
5
5
|
|
|
6
6
|
/** Directory where plans are stored, relative to project root */
|
|
7
7
|
export const PLAN_DIR = '.lonny'
|
|
@@ -32,6 +32,8 @@ export function createWritePlanTool(
|
|
|
32
32
|
return {
|
|
33
33
|
definition: {
|
|
34
34
|
name: 'write_plan',
|
|
35
|
+
category: 'Edit',
|
|
36
|
+
group: 'Plan',
|
|
35
37
|
description: `Write a plan/todo document to the project's "${PLAN_DIR}/" folder. Use this in plan mode to persist the generated plan. The file is always created under "${PLAN_DIR}/" relative to the working directory; do NOT include the folder prefix in filename. Existing files with the same name will be overwritten.`,
|
|
36
38
|
parameters: {
|
|
37
39
|
filename: {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** Maximum output length (chars) to prevent context overflow. */
|
|
2
|
+
export const MAX_OUTPUT_LENGTH = 10_000
|
|
3
|
+
|
|
4
|
+
/** Maximum stderr length included in error messages */
|
|
5
|
+
export const MAX_ERROR_LENGTH = 2_000
|
|
6
|
+
|
|
7
|
+
/** Maximum allowed timeout (10 minutes) */
|
|
8
|
+
export const MAX_TIMEOUT = 600_000
|
|
9
|
+
|
|
10
|
+
/** Minimum allowed timeout (100ms) */
|
|
11
|
+
export const MIN_TIMEOUT = 100
|
|
12
|
+
|
|
13
|
+
/** Default timeout (2 minutes) */
|
|
14
|
+
export const DEFAULT_TIMEOUT = 120_000
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Patterns that indicate a destructive command.
|
|
18
|
+
* If matched, the tool will warn the user instead of executing.
|
|
19
|
+
*/
|
|
20
|
+
export const DESTRUCTIVE_PATTERNS: { regex: RegExp; hint: string }[] = [
|
|
21
|
+
// ═══════════════════════
|
|
22
|
+
// Unix destructive
|
|
23
|
+
// ═══════════════════════
|
|
24
|
+
{ regex: /\brm\s+-(?:rf|fr)\s+\/$/i, hint: 'rm -rf / would destroy the system' },
|
|
25
|
+
{ regex: /\brm\s+-(?:rf|fr)\s+\/\*/i, hint: 'rm -rf /* would destroy the system' },
|
|
26
|
+
{ regex: /\brm\s+-(?:rf|fr)\s+~$/i, hint: 'rm -rf ~ would delete the home directory' },
|
|
27
|
+
{ regex: /\b(?:mkfs|dd)\s+(?:\/dev|\\.)/i, hint: 'mkfs/dd would destroy a disk device' },
|
|
28
|
+
{ regex: /\bchmod\s+-R\s+0{4}\b/, hint: 'chmod -R 0000 would make files inaccessible' },
|
|
29
|
+
{
|
|
30
|
+
regex: /\b(?:shutdown|reboot|halt|poweroff)\s+(?:-h|-r|-now|now)\b/i,
|
|
31
|
+
hint: 'shutdown/reboot would halt the system',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
regex: /\b(?:killall|pkill)\s+-9\s+/i,
|
|
35
|
+
hint: 'killall -9 would terminate all matching processes',
|
|
36
|
+
},
|
|
37
|
+
// ═══════════════════════
|
|
38
|
+
// Windows destructive
|
|
39
|
+
// ═══════════════════════
|
|
40
|
+
{
|
|
41
|
+
regex: /\b(?:Remove-Item|rmdir|rd)\s+.*(?:-Recurse|-R)\s+.*(?:-Force|-F)\b/i,
|
|
42
|
+
hint: 'recursive force delete would destroy data',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
regex: /\b(?:Remove-Item|rmdir|rd)\s+\w:\s*[\\/]/i,
|
|
46
|
+
hint: 'recursive delete on drive root would destroy data',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
regex: /\b(?:del|erase)\s+.*(?:\/F|\/S)\b/i,
|
|
50
|
+
hint: 'force delete would destroy files irreversibly',
|
|
51
|
+
},
|
|
52
|
+
{ regex: /\bformat\s+\w:\s*\/[Qq]/i, hint: 'format would destroy a drive' },
|
|
53
|
+
{ regex: /\b(?:diskpart|clean-all)\b/i, hint: 'diskpart clean-all would destroy all partitions' },
|
|
54
|
+
{ regex: /\bClear-Item\s+/i, hint: 'Clear-Item would delete content irreversibly' },
|
|
55
|
+
{ regex: /\bClear-Content\s+/i, hint: 'Clear-Content would delete file content irreversibly' },
|
|
56
|
+
{ regex: /\bRemove-ItemProperty\s+/i, hint: 'Remove-ItemProperty would delete registry keys' },
|
|
57
|
+
{ regex: /\bRemove-Variable\s+-Force\b/i, hint: 'Remove-Variable -Force would delete variables' },
|
|
58
|
+
{ regex: /\breg\s+delete\s+/i, hint: 'reg delete would destroy registry entries' },
|
|
59
|
+
{ regex: /\bwmic\s+\w+\s+delete\b/i, hint: 'wmic delete would destroy system resources' },
|
|
60
|
+
{ regex: /\b(?:cipher|sdelete)\s+\/w:/i, hint: 'secure delete would irreversibly wipe data' },
|
|
61
|
+
{
|
|
62
|
+
regex: /\[System\.IO\.Directory\]::Delete\s*\(/i,
|
|
63
|
+
hint: 'Directory.Delete would destroy data irreversibly',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
regex: /\[System\.IO\.File\]::Delete\s*\(/i,
|
|
67
|
+
hint: 'File.Delete would destroy data irreversibly',
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Patterns that match sensitive data (API keys, tokens, passwords, etc.)
|
|
73
|
+
* in command output. Matching content is replaced with "[REDACTED]".
|
|
74
|
+
*/
|
|
75
|
+
export const SENSITIVE_PATTERNS: RegExp[] = [
|
|
76
|
+
// Generic API keys / tokens
|
|
77
|
+
/(?:api[_-]?key|apikey|token|secret|password|passwd|credential|auth[_-]?token)[=:]\s*['"]?[A-Za-z0-9_\-.]{16,}/gi,
|
|
78
|
+
// AWS keys
|
|
79
|
+
/(?:AKIA[0-9A-Z]{16}|[A-Za-z0-9+/]{40}\s*=[=]{0,2})/g,
|
|
80
|
+
// GitHub tokens
|
|
81
|
+
/gh[pousr]_[A-Za-z0-9_]{36,}/g,
|
|
82
|
+
// npm tokens
|
|
83
|
+
/npm_[A-Za-z0-9]{36,}/g,
|
|
84
|
+
// Slack tokens
|
|
85
|
+
/xox[baprs]-[A-Za-z0-9-]{24,}/g,
|
|
86
|
+
// SSH private key content (multi-line)
|
|
87
|
+
/-----BEGIN[ A-Z]+PRIVATE KEY-----[\s\S]*?-----END[ A-Z]+PRIVATE KEY-----/g,
|
|
88
|
+
// JWT tokens
|
|
89
|
+
/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
|
|
90
|
+
// Generic password-like strings in env vars
|
|
91
|
+
/export\s+\w*(?:PASS|SECRET|TOKEN|KEY)\w*\s*=\s*['"]?\S+['"]?/gi,
|
|
92
|
+
/\$env:\w*(?:PASS|SECRET|TOKEN|KEY)\w*\s*=\s*['"]?\S+['"]?/gi,
|
|
93
|
+
]
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import * as os from 'node:os'
|
|
2
|
+
|
|
3
|
+
import { MAX_ERROR_LENGTH } from './constants.js'
|
|
4
|
+
import { redactSensitive } from './security.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Truncate a string to maxLength chars. If truncated, appends a warning.
|
|
8
|
+
* Returns the (possibly truncated) string.
|
|
9
|
+
*/
|
|
10
|
+
function truncateOutput(s: string, maxLength: number, label: string): string {
|
|
11
|
+
if (s.length <= maxLength) return s
|
|
12
|
+
const truncated = s.slice(0, maxLength)
|
|
13
|
+
const warning = `\n... [${label} truncated at ${maxLength} chars, original ${s.length} chars]`
|
|
14
|
+
return truncated + warning
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if stderr contains PowerShell NativeCommandError wrapping.
|
|
19
|
+
* This happens when a native command (like git push) writes to stderr and
|
|
20
|
+
* PowerShell wraps the output in an ErrorRecord format even though the
|
|
21
|
+
* command exited successfully (exit code 0).
|
|
22
|
+
*
|
|
23
|
+
* Example format:
|
|
24
|
+
* git : Everything up-to-date
|
|
25
|
+
* At line:1 char:1
|
|
26
|
+
* + git push 2>&1
|
|
27
|
+
* + ~~~~~~~~~~~~~
|
|
28
|
+
* + CategoryInfo : NotSpecified: (...) [], RemoteException
|
|
29
|
+
* + FullyQualifiedErrorId : NativeCommandError
|
|
30
|
+
*
|
|
31
|
+
* Returns the extracted actual output if detected, null otherwise.
|
|
32
|
+
*/
|
|
33
|
+
function extractPowerShellNativeOutput(stderr: string): string | null {
|
|
34
|
+
// Detect PowerShell ErrorRecord wrapping around native command stderr output.
|
|
35
|
+
// The key marker is "FullyQualifiedErrorId : NativeCommandError".
|
|
36
|
+
if (!/FullyQualifiedErrorId\s*:\s*NativeCommandError/i.test(stderr)) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lines = stderr.split(/\r?\n/)
|
|
41
|
+
const outputLines: string[] = []
|
|
42
|
+
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
const trimmed = line.trim()
|
|
45
|
+
|
|
46
|
+
// Skip PowerShell error record wrapper lines:
|
|
47
|
+
// " + CategoryInfo : ..." (indented, with +)
|
|
48
|
+
// " + FullyQualifiedErrorId : ..."
|
|
49
|
+
// "At line:1 char:1" or Chinese garbled equivalent
|
|
50
|
+
// "+ command ..." (the command echo line with leading +)
|
|
51
|
+
// " + ~~~~~~~~~~~~~" (the caret underline)
|
|
52
|
+
if (
|
|
53
|
+
/^\s*\+/.test(trimmed) ||
|
|
54
|
+
/^At\s+line:\d+/i.test(trimmed) ||
|
|
55
|
+
/^\+?\s*CategoryInfo/i.test(trimmed) ||
|
|
56
|
+
/^\+?\s*FullyQualifiedErrorId/i.test(trimmed)
|
|
57
|
+
) {
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Skip blank lines within the error record block (but keep leading whitespace for formatting)
|
|
62
|
+
if (!trimmed && outputLines.length > 0) {
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Remove leading whitespace from the first content line (the "<command> : <message>" line)
|
|
67
|
+
outputLines.push(line)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const result = outputLines.join('\n').trim()
|
|
71
|
+
return result || null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if stderr indicates common PowerShell errors and return a helpful message.
|
|
76
|
+
* Returns a tip string if detected, null otherwise.
|
|
77
|
+
*/
|
|
78
|
+
function checkPowerShellError(stderr: string, command: string): string | null {
|
|
79
|
+
const errLower = stderr.toLowerCase()
|
|
80
|
+
|
|
81
|
+
// Execution policy errors
|
|
82
|
+
if (/execution.?policy/i.test(errLower) || /running scripts is disabled/i.test(errLower)) {
|
|
83
|
+
return 'PowerShell execution policy is blocking this command. Try: `powershell -ExecutionPolicy Bypass -Command "..."`'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Module not found
|
|
87
|
+
if (
|
|
88
|
+
/module.*not (found|available)/i.test(errLower) ||
|
|
89
|
+
/could not (find|load).*module/i.test(errLower)
|
|
90
|
+
) {
|
|
91
|
+
const moduleMatch = stderr.match(/module\s+['"]?(\w+)['"]?/i)
|
|
92
|
+
const moduleName = moduleMatch ? moduleMatch[1]! : '<unknown>'
|
|
93
|
+
return `PowerShell module not found: "${moduleName}". Install it with: Install-Module ${moduleName} -Force`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Cmdlet not found
|
|
97
|
+
if (/the term '(\w+)' is not recognized/i.test(errLower)) {
|
|
98
|
+
const cmdMatch = stderr.match(/the term '(\w+)'/i)
|
|
99
|
+
const cmdName = cmdMatch ? cmdMatch[1]! : command.split(/\s+/)[0] || command
|
|
100
|
+
return `Cmdlet not found: "${cmdName}". This command may need a module or be installed separately.`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Access denied
|
|
104
|
+
if (
|
|
105
|
+
/access is denied/i.test(errLower) ||
|
|
106
|
+
/permission denied/i.test(errLower) ||
|
|
107
|
+
/unauthorized/i.test(errLower)
|
|
108
|
+
) {
|
|
109
|
+
return 'Access denied. Try running as Administrator (on Windows) or using sudo (on Unix).'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Path not found
|
|
113
|
+
if (/cannot find (path|drive)/i.test(errLower) || /path.*does not exist/i.test(errLower)) {
|
|
114
|
+
return 'The specified path or drive was not found. Check that the path exists and is accessible.'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if stderr output indicates a "command not found" error.
|
|
122
|
+
* Returns a platform-appropriate suggestion if detected, null otherwise.
|
|
123
|
+
*/
|
|
124
|
+
function checkCommandNotFound(stderr: string, command: string): string | null {
|
|
125
|
+
const isWindows = os.platform() === 'win32'
|
|
126
|
+
const errLower = stderr.toLowerCase()
|
|
127
|
+
|
|
128
|
+
if (isWindows) {
|
|
129
|
+
// PowerShell "not recognized" patterns
|
|
130
|
+
if (
|
|
131
|
+
/not recognized/.test(errLower) ||
|
|
132
|
+
/is not recognized/.test(errLower) ||
|
|
133
|
+
/找不到/.test(errLower) ||
|
|
134
|
+
/command not found/.test(errLower) ||
|
|
135
|
+
/not found/.test(errLower) ||
|
|
136
|
+
/neither the command nor/.test(errLower)
|
|
137
|
+
) {
|
|
138
|
+
// Extract the command name that wasn't found (first word of the command)
|
|
139
|
+
const cmdName = command.split(/\s+/)[0] || command
|
|
140
|
+
return `Command not found: "${cmdName}". It may be a Unix command not available on Windows. Use "where" to find available commands or use PowerShell-native alternatives.`
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
// Unix "not found" patterns
|
|
144
|
+
if (
|
|
145
|
+
/not found/.test(errLower) ||
|
|
146
|
+
/command not found/.test(errLower) ||
|
|
147
|
+
/no such file/.test(errLower)
|
|
148
|
+
) {
|
|
149
|
+
const cmdName = command.split(/\s+/)[0] || command
|
|
150
|
+
return `Command not found: "${cmdName}". Check that it is installed, or use "which ${cmdName}" to find its location.`
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Build a helpful error message for a failed command, including
|
|
158
|
+
* platform-specific suggestions when applicable.
|
|
159
|
+
*/
|
|
160
|
+
function buildErrorMsg(
|
|
161
|
+
exitCode: number,
|
|
162
|
+
stderr: string,
|
|
163
|
+
command: string,
|
|
164
|
+
timedOut: boolean,
|
|
165
|
+
cwd?: string,
|
|
166
|
+
): string {
|
|
167
|
+
const parts: string[] = []
|
|
168
|
+
|
|
169
|
+
if (timedOut) {
|
|
170
|
+
parts.push(`Command timed out (exit code ${exitCode})`)
|
|
171
|
+
} else {
|
|
172
|
+
parts.push(`Command exited with code ${exitCode}`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check for "command not found" first (before including full stderr)
|
|
176
|
+
const notFoundMsg = checkCommandNotFound(stderr, command)
|
|
177
|
+
if (notFoundMsg) {
|
|
178
|
+
parts.push(notFoundMsg)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check for common PowerShell errors
|
|
182
|
+
if (os.platform() === 'win32') {
|
|
183
|
+
const psErrMsg = checkPowerShellError(stderr, command)
|
|
184
|
+
if (psErrMsg) {
|
|
185
|
+
parts.push(psErrMsg)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Include stderr content (truncated and redacted)
|
|
190
|
+
const stderrTrimmed = stderr.trim()
|
|
191
|
+
if (stderrTrimmed) {
|
|
192
|
+
const redactedStderr = redactSensitive(stderrTrimmed)
|
|
193
|
+
parts.push(`stderr:\n${truncateOutput(redactedStderr, MAX_ERROR_LENGTH, 'stderr')}`)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Platform-specific suggestions for common mistakes
|
|
197
|
+
const isWindows = os.platform() === 'win32'
|
|
198
|
+
|
|
199
|
+
if (isWindows) {
|
|
200
|
+
// Common Windows command mistakes
|
|
201
|
+
if (/ls\b/.test(command) && !/ls\b/.test(command.replace(/".*"/g, ''))) {
|
|
202
|
+
parts.push('💡 Tip: On Windows, use `dir` instead of `ls`, or `Get-ChildItem` in PowerShell.')
|
|
203
|
+
}
|
|
204
|
+
if (/grep\b/.test(command)) {
|
|
205
|
+
parts.push('💡 Tip: On Windows, use `findstr` or `Select-String` instead of `grep`.')
|
|
206
|
+
}
|
|
207
|
+
if (/touch\b/.test(command)) {
|
|
208
|
+
parts.push(
|
|
209
|
+
'💡 Tip: On Windows, `touch` is not available. Use `New-Item` or `edit` tool to create files.',
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
if (/ps\b/.test(command)) {
|
|
213
|
+
parts.push('💡 Tip: On Windows, use `Get-Process` instead of `ps` for listing processes.')
|
|
214
|
+
}
|
|
215
|
+
if (/pwd\b/.test(command)) {
|
|
216
|
+
parts.push('💡 Tip: On Windows, use `Get-Location` or `$pwd` instead of `pwd`.')
|
|
217
|
+
}
|
|
218
|
+
if (
|
|
219
|
+
/chmod\b/.test(command) ||
|
|
220
|
+
/cp\b/.test(command) ||
|
|
221
|
+
/mv\b/.test(command) ||
|
|
222
|
+
/rm\b/.test(command) ||
|
|
223
|
+
/mkdir\b/.test(command)
|
|
224
|
+
) {
|
|
225
|
+
parts.push(
|
|
226
|
+
'💡 Tip: On Windows, use native equivalents: `copy`, `move`, `del`, `mkdir`, `icacls` instead of Unix commands.',
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
if (/cat\b/.test(command)) {
|
|
230
|
+
parts.push('💡 Tip: On Windows, use `type` or `Get-Content` instead of `cat`.')
|
|
231
|
+
}
|
|
232
|
+
if (/which\b/.test(command)) {
|
|
233
|
+
parts.push('💡 Tip: On Windows, use `where` instead of `which` to find executable locations.')
|
|
234
|
+
}
|
|
235
|
+
if (/less\b/.test(command) || /more\b/.test(command)) {
|
|
236
|
+
parts.push('💡 Tip: On Windows, use `more` or `Out-Host -Paging` for paging output.')
|
|
237
|
+
}
|
|
238
|
+
if (/sort\b/.test(command) && !/sort-object/i.test(command) && !/Sort-Object/i.test(command)) {
|
|
239
|
+
parts.push('💡 Tip: On Windows, use `Sort-Object` instead of `sort`.')
|
|
240
|
+
}
|
|
241
|
+
if (/head\b/.test(command)) {
|
|
242
|
+
parts.push('💡 Tip: On Windows, use `Select-Object -First` instead of `head`.')
|
|
243
|
+
}
|
|
244
|
+
if (/tail\b/.test(command)) {
|
|
245
|
+
parts.push(
|
|
246
|
+
'💡 Tip: On Windows, use `Get-Content -Tail` or `Select-Object -Last` instead of `tail`.',
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
if (/diff\b/.test(command) || /cmp\b/.test(command)) {
|
|
250
|
+
parts.push('💡 Tip: On Windows, use `Compare-Object` instead of `diff`/`cmp`.')
|
|
251
|
+
}
|
|
252
|
+
if (/curl\b/.test(command) && !/curl\b/.test(command.replace(/".*"/g, ''))) {
|
|
253
|
+
parts.push(
|
|
254
|
+
'💡 Tip: On Windows, use `Invoke-WebRequest` or `Invoke-RestMethod` instead of `curl`. curl is available in newer Windows builds.',
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
if (/wget\b/.test(command)) {
|
|
258
|
+
parts.push('💡 Tip: On Windows, use `Invoke-WebRequest` instead of `wget`.')
|
|
259
|
+
}
|
|
260
|
+
if (/env\b/.test(command)) {
|
|
261
|
+
parts.push('💡 Tip: On Windows, use `Get-ChildItem Env:` to list environment variables.')
|
|
262
|
+
}
|
|
263
|
+
if (/kill\b/.test(command) && !/stop-process/i.test(command)) {
|
|
264
|
+
parts.push('💡 Tip: On Windows, use `Stop-Process` or `taskkill` instead of `kill`.')
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
// Unix-specific tips
|
|
268
|
+
if (/dir\b/.test(command)) {
|
|
269
|
+
parts.push('💡 Tip: On Unix, use `ls -la` instead of `dir`.')
|
|
270
|
+
}
|
|
271
|
+
if (/findstr\b/.test(command)) {
|
|
272
|
+
parts.push('💡 Tip: On Unix, use `grep` instead of `findstr`.')
|
|
273
|
+
}
|
|
274
|
+
if (/type\b/.test(command) && !/type\b/.test(command.replace(/".*"/g, ''))) {
|
|
275
|
+
parts.push('💡 Tip: On Unix, use `cat` instead of `type`.')
|
|
276
|
+
}
|
|
277
|
+
if (/where\b/.test(command)) {
|
|
278
|
+
parts.push('💡 Tip: On Unix, use `which` instead of `where` to find executable locations.')
|
|
279
|
+
}
|
|
280
|
+
if (/copy\b/.test(command)) {
|
|
281
|
+
parts.push('💡 Tip: On Unix, use `cp` instead of `copy`.')
|
|
282
|
+
}
|
|
283
|
+
if (/move\b/.test(command) || /ren\b/.test(command)) {
|
|
284
|
+
parts.push('💡 Tip: On Unix, use `mv` instead of `move`/`ren`.')
|
|
285
|
+
}
|
|
286
|
+
if (/del\b/.test(command)) {
|
|
287
|
+
parts.push('💡 Tip: On Unix, use `rm` instead of `del`.')
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (cwd) {
|
|
292
|
+
parts.push(`Working directory: ${cwd}`)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return parts.join('\n')
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export {
|
|
299
|
+
buildErrorMsg,
|
|
300
|
+
checkCommandNotFound,
|
|
301
|
+
checkPowerShellError,
|
|
302
|
+
extractPowerShellNativeOutput,
|
|
303
|
+
truncateOutput,
|
|
304
|
+
}
|