explorbot 0.0.1 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -26
- package/bin/explorbot-cli.ts +679 -0
- package/boat/api-tester/src/ai/chief/styles.ts +15 -0
- package/boat/api-tester/src/ai/chief.ts +335 -0
- package/boat/api-tester/src/ai/curler-tools.ts +278 -0
- package/boat/api-tester/src/ai/curler.ts +306 -0
- package/boat/api-tester/src/api-client.ts +28 -0
- package/boat/api-tester/src/apibot.ts +203 -0
- package/boat/api-tester/src/cli.ts +301 -0
- package/boat/api-tester/src/config.ts +190 -0
- package/dist/bin/explorbot-cli.js +19 -98
- package/dist/boat/api-tester/bin/apibot-cli.js +0 -1
- package/dist/boat/api-tester/src/ai/chief/styles.js +0 -1
- package/dist/boat/api-tester/src/ai/chief.js +0 -1
- package/dist/boat/api-tester/src/ai/curler-tools.js +0 -1
- package/dist/boat/api-tester/src/ai/curler.js +0 -1
- package/dist/boat/api-tester/src/api-client.js +0 -1
- package/dist/boat/api-tester/src/apibot.js +0 -1
- package/dist/boat/api-tester/src/cli.js +0 -1
- package/dist/boat/api-tester/src/config.js +0 -1
- package/dist/src/action-result.js +0 -1
- package/dist/src/action.js +0 -1
- package/dist/src/activity.js +0 -1
- package/dist/src/ai/agent.js +0 -1
- package/dist/src/ai/bosun.js +0 -1
- package/dist/src/ai/captain/idle-mode.js +0 -1
- package/dist/src/ai/captain/mixin.js +0 -1
- package/dist/src/ai/captain/test-mode.js +0 -1
- package/dist/src/ai/captain/web-mode.js +0 -1
- package/dist/src/ai/captain.js +0 -1
- package/dist/src/ai/conversation.js +0 -1
- package/dist/src/ai/experience-compactor.js +0 -1
- package/dist/src/ai/fisherman-tools.js +0 -1
- package/dist/src/ai/fisherman.js +0 -1
- package/dist/src/ai/historian.js +0 -1
- package/dist/src/ai/navigator.js +0 -1
- package/dist/src/ai/pilot.js +0 -1
- package/dist/src/ai/planner/session-dedup.js +0 -1
- package/dist/src/ai/planner/styles.js +0 -1
- package/dist/src/ai/planner/subpages.js +0 -1
- package/dist/src/ai/planner.js +0 -1
- package/dist/src/ai/provider.js +0 -1
- package/dist/src/ai/quartermaster.js +0 -1
- package/dist/src/ai/researcher/cache.js +0 -1
- package/dist/src/ai/researcher/coordinates.js +0 -1
- package/dist/src/ai/researcher/deep-analysis.js +0 -1
- package/dist/src/ai/researcher/fingerprint-worker.js +0 -1
- package/dist/src/ai/researcher/focus.js +0 -1
- package/dist/src/ai/researcher/locators.js +0 -1
- package/dist/src/ai/researcher/mixin.js +0 -1
- package/dist/src/ai/researcher/parser.js +0 -1
- package/dist/src/ai/researcher/research-result.js +0 -1
- package/dist/src/ai/researcher.js +0 -1
- package/dist/src/ai/rules.js +0 -1
- package/dist/src/ai/task-agent.js +0 -1
- package/dist/src/ai/tester.js +0 -1
- package/dist/src/ai/tools.js +0 -1
- package/dist/src/api/api-client.js +0 -1
- package/dist/src/api/request-result.js +0 -1
- package/dist/src/api/request-store.js +0 -1
- package/dist/src/api/spec-reader.js +0 -1
- package/dist/src/api/xhr-capture.js +0 -1
- package/dist/src/browser-server.js +0 -1
- package/dist/src/command-handler.js +0 -1
- package/dist/src/commands/add-rule-command.js +0 -1
- package/dist/src/commands/base-command.js +0 -1
- package/dist/src/commands/clean-command.js +0 -1
- package/dist/src/commands/context-aria-command.js +0 -1
- package/dist/src/commands/context-command.js +0 -1
- package/dist/src/commands/context-data-command.js +0 -1
- package/dist/src/commands/context-experience-command.js +0 -1
- package/dist/src/commands/context-html-command.js +0 -1
- package/dist/src/commands/context-knowledge-command.js +0 -1
- package/dist/src/commands/debug-command.js +0 -1
- package/dist/src/commands/drill-command.js +0 -1
- package/dist/src/commands/exit-command.js +0 -1
- package/dist/src/commands/explore-command.js +2 -2
- package/dist/src/commands/freesail-command.js +0 -1
- package/dist/src/commands/help-command.js +0 -1
- package/dist/src/commands/index.js +0 -1
- package/dist/src/commands/init-command.js +115 -0
- package/dist/src/commands/knows-command.js +0 -1
- package/dist/src/commands/learn-command.js +0 -1
- package/dist/src/commands/navigate-command.js +0 -1
- package/dist/src/commands/path-command.js +0 -1
- package/dist/src/commands/plan-clear-command.js +0 -1
- package/dist/src/commands/plan-command.js +0 -1
- package/dist/src/commands/plan-edit-command.js +0 -1
- package/dist/src/commands/plan-load-command.js +0 -1
- package/dist/src/commands/plan-reload-command.js +0 -1
- package/dist/src/commands/plan-save-command.js +0 -1
- package/dist/src/commands/research-command.js +0 -1
- package/dist/src/commands/start-command.js +0 -1
- package/dist/src/commands/status-command.js +0 -1
- package/dist/src/commands/test-command.js +0 -1
- package/dist/src/components/ActivityPane.js +0 -1
- package/dist/src/components/AddKnowledge.js +0 -1
- package/dist/src/components/AddRule.js +0 -1
- package/dist/src/components/App.js +0 -1
- package/dist/src/components/Autocomplete.js +0 -1
- package/dist/src/components/InputPane.js +0 -1
- package/dist/src/components/InputReadline.js +0 -1
- package/dist/src/components/LogPane.js +0 -1
- package/dist/src/components/PlanEditor.js +0 -1
- package/dist/src/components/PlanPane.js +0 -1
- package/dist/src/components/SessionTimer.js +0 -1
- package/dist/src/components/StateTransitionPane.js +0 -1
- package/dist/src/components/StatusPane.js +0 -1
- package/dist/src/components/TaskPane.js +0 -1
- package/dist/src/components/Welcome.js +0 -1
- package/dist/src/components/WelcomeChecklist.js +0 -1
- package/dist/src/components/WelcomeCommands.js +0 -1
- package/dist/src/components/autocomplete-store.js +0 -1
- package/dist/src/components/parse-keypress.js +0 -1
- package/dist/src/config.js +0 -1
- package/dist/src/execution-controller.js +0 -1
- package/dist/src/experience-tracker.js +0 -1
- package/dist/src/explorbot.js +0 -1
- package/dist/src/explorer.js +0 -1
- package/dist/src/index.js +0 -1
- package/dist/src/knowledge-tracker.js +2 -2
- package/dist/src/observability.js +0 -1
- package/dist/src/reporter.js +0 -1
- package/dist/src/state-manager.js +0 -1
- package/dist/src/stats.js +0 -1
- package/dist/src/test-plan.js +0 -1
- package/dist/src/utils/aria.js +0 -1
- package/dist/src/utils/cli-name.js +16 -0
- package/dist/src/utils/code-extractor.js +0 -1
- package/dist/src/utils/context-formatter.js +0 -1
- package/dist/src/utils/error-page.js +0 -1
- package/dist/src/utils/expandable.js +0 -1
- package/dist/src/utils/hooks-runner.js +0 -1
- package/dist/src/utils/html-diff.js +0 -1
- package/dist/src/utils/html.js +0 -1
- package/dist/src/utils/logger.js +0 -1
- package/dist/src/utils/loop.js +0 -1
- package/dist/src/utils/markdown-parser.js +0 -1
- package/dist/src/utils/markdown-query.js +0 -1
- package/dist/src/utils/markdown-terminal.js +0 -1
- package/dist/src/utils/research-parser.js +0 -1
- package/dist/src/utils/retry.js +0 -1
- package/dist/src/utils/rules-loader.js +0 -1
- package/dist/src/utils/strings.js +0 -1
- package/dist/src/utils/test-plan-markdown.js +0 -1
- package/dist/src/utils/throttle.js +0 -1
- package/dist/src/utils/unique-names.js +0 -1
- package/dist/src/utils/url-matcher.js +0 -1
- package/dist/src/utils/web-element.js +0 -1
- package/dist/src/utils/xpath.js +0 -1
- package/package.json +27 -3
- package/src/action-result.ts +694 -0
- package/src/action.ts +445 -0
- package/src/activity.ts +111 -0
- package/src/ai/agent.ts +3 -0
- package/src/ai/bosun.ts +557 -0
- package/src/ai/captain/idle-mode.ts +116 -0
- package/src/ai/captain/mixin.ts +22 -0
- package/src/ai/captain/test-mode.ts +262 -0
- package/src/ai/captain/web-mode.ts +136 -0
- package/src/ai/captain.ts +504 -0
- package/src/ai/conversation.ts +205 -0
- package/src/ai/experience-compactor.ts +284 -0
- package/src/ai/fisherman-tools.ts +181 -0
- package/src/ai/fisherman.ts +223 -0
- package/src/ai/historian.ts +457 -0
- package/src/ai/navigator.ts +572 -0
- package/src/ai/pilot.ts +776 -0
- package/src/ai/planner/session-dedup.ts +35 -0
- package/src/ai/planner/styles.ts +17 -0
- package/src/ai/planner/subpages.ts +141 -0
- package/src/ai/planner.ts +536 -0
- package/src/ai/provider.ts +613 -0
- package/src/ai/quartermaster.ts +286 -0
- package/src/ai/researcher/cache.ts +103 -0
- package/src/ai/researcher/coordinates.ts +238 -0
- package/src/ai/researcher/deep-analysis.ts +415 -0
- package/src/ai/researcher/fingerprint-worker.ts +59 -0
- package/src/ai/researcher/focus.ts +42 -0
- package/src/ai/researcher/locators.ts +282 -0
- package/src/ai/researcher/mixin.ts +4 -0
- package/src/ai/researcher/parser.ts +186 -0
- package/src/ai/researcher/research-result.ts +115 -0
- package/src/ai/researcher.ts +857 -0
- package/src/ai/rules.ts +376 -0
- package/src/ai/task-agent.ts +141 -0
- package/src/ai/tester.ts +939 -0
- package/src/ai/tools.ts +1117 -0
- package/src/api/api-client.ts +109 -0
- package/src/api/request-result.ts +212 -0
- package/src/api/request-store.ts +130 -0
- package/src/api/spec-reader.ts +174 -0
- package/src/api/xhr-capture.ts +100 -0
- package/src/browser-server.ts +74 -0
- package/src/command-handler.ts +454 -0
- package/src/commands/add-rule-command.ts +63 -0
- package/src/commands/base-command.ts +27 -0
- package/src/commands/clean-command.ts +73 -0
- package/src/commands/context-aria-command.ts +22 -0
- package/src/commands/context-command.ts +67 -0
- package/src/commands/context-data-command.ts +30 -0
- package/src/commands/context-experience-command.ts +48 -0
- package/src/commands/context-html-command.ts +33 -0
- package/src/commands/context-knowledge-command.ts +43 -0
- package/src/commands/debug-command.ts +13 -0
- package/src/commands/drill-command.ts +34 -0
- package/src/commands/exit-command.ts +32 -0
- package/src/commands/explore-command.ts +129 -0
- package/src/commands/freesail-command.ts +95 -0
- package/src/commands/help-command.ts +8 -0
- package/src/commands/index.ts +69 -0
- package/src/commands/init-command.ts +128 -0
- package/src/commands/knows-command.ts +68 -0
- package/src/commands/learn-command.ts +44 -0
- package/src/commands/navigate-command.ts +18 -0
- package/src/commands/path-command.ts +83 -0
- package/src/commands/plan-clear-command.ts +14 -0
- package/src/commands/plan-command.ts +41 -0
- package/src/commands/plan-edit-command.ts +9 -0
- package/src/commands/plan-load-command.ts +18 -0
- package/src/commands/plan-reload-command.ts +28 -0
- package/src/commands/plan-save-command.ts +25 -0
- package/src/commands/research-command.ts +45 -0
- package/src/commands/start-command.ts +13 -0
- package/src/commands/status-command.tsx +23 -0
- package/src/commands/test-command.ts +84 -0
- package/src/components/ActivityPane.tsx +80 -0
- package/src/components/AddKnowledge.tsx +169 -0
- package/src/components/AddRule.tsx +174 -0
- package/src/components/App.tsx +377 -0
- package/src/components/Autocomplete.tsx +63 -0
- package/src/components/InputPane.tsx +259 -0
- package/src/components/InputReadline.tsx +704 -0
- package/src/components/LogPane.tsx +187 -0
- package/src/components/PlanEditor.tsx +150 -0
- package/src/components/PlanPane.tsx +71 -0
- package/src/components/SessionTimer.tsx +35 -0
- package/src/components/StateTransitionPane.tsx +149 -0
- package/src/components/StatusPane.tsx +62 -0
- package/src/components/TaskPane.tsx +119 -0
- package/src/components/Welcome.tsx +83 -0
- package/src/components/WelcomeChecklist.tsx +118 -0
- package/src/components/WelcomeCommands.tsx +102 -0
- package/src/components/autocomplete-store.ts +35 -0
- package/src/components/parse-keypress.ts +170 -0
- package/src/config.ts +490 -0
- package/src/execution-controller.ts +109 -0
- package/src/experience-tracker.ts +350 -0
- package/src/explorbot.ts +405 -0
- package/src/explorer.ts +713 -0
- package/src/index.tsx +62 -0
- package/src/knowledge-tracker.ts +230 -0
- package/src/observability.ts +150 -0
- package/src/reporter.ts +224 -0
- package/src/state-manager.ts +556 -0
- package/src/stats.ts +53 -0
- package/src/test-plan.ts +432 -0
- package/src/utils/aria.ts +629 -0
- package/src/utils/cli-name.ts +13 -0
- package/src/utils/code-extractor.ts +22 -0
- package/src/utils/context-formatter.ts +239 -0
- package/src/utils/error-page.ts +23 -0
- package/src/utils/expandable.ts +38 -0
- package/src/utils/hooks-runner.ts +79 -0
- package/src/utils/html-diff.ts +918 -0
- package/src/utils/html.ts +1316 -0
- package/src/utils/logger.ts +534 -0
- package/src/utils/loop.ts +176 -0
- package/src/utils/markdown-parser.ts +127 -0
- package/src/utils/markdown-query.ts +466 -0
- package/src/utils/markdown-terminal.ts +43 -0
- package/src/utils/research-parser.ts +11 -0
- package/src/utils/retry.ts +73 -0
- package/src/utils/rules-loader.ts +118 -0
- package/src/utils/strings.ts +13 -0
- package/src/utils/test-plan-markdown.ts +332 -0
- package/src/utils/throttle.ts +18 -0
- package/src/utils/unique-names.ts +14 -0
- package/src/utils/url-matcher.ts +45 -0
- package/src/utils/web-element.ts +145 -0
- package/src/utils/xpath.ts +129 -0
- package/dist/bin/explorbot-cli.js.map +0 -1
- package/dist/boat/api-tester/bin/apibot-cli.js.map +0 -1
- package/dist/boat/api-tester/example/apibot.config.js +0 -31
- package/dist/boat/api-tester/example/apibot.config.js.map +0 -1
- package/dist/boat/api-tester/src/ai/chief/styles.js.map +0 -1
- package/dist/boat/api-tester/src/ai/chief.js.map +0 -1
- package/dist/boat/api-tester/src/ai/curler-tools.js.map +0 -1
- package/dist/boat/api-tester/src/ai/curler.js.map +0 -1
- package/dist/boat/api-tester/src/api-client.js.map +0 -1
- package/dist/boat/api-tester/src/apibot.js.map +0 -1
- package/dist/boat/api-tester/src/cli.js.map +0 -1
- package/dist/boat/api-tester/src/config.js.map +0 -1
- package/dist/prompts/audit-rules.md +0 -124
- package/dist/src/action-result.js.map +0 -1
- package/dist/src/action.js.map +0 -1
- package/dist/src/activity.js.map +0 -1
- package/dist/src/ai/agent.js.map +0 -1
- package/dist/src/ai/bosun.js.map +0 -1
- package/dist/src/ai/captain/idle-mode.js.map +0 -1
- package/dist/src/ai/captain/mixin.js.map +0 -1
- package/dist/src/ai/captain/test-mode.js.map +0 -1
- package/dist/src/ai/captain/web-mode.js.map +0 -1
- package/dist/src/ai/captain.js.map +0 -1
- package/dist/src/ai/conversation.js.map +0 -1
- package/dist/src/ai/experience-compactor.js.map +0 -1
- package/dist/src/ai/fisherman-tools.js.map +0 -1
- package/dist/src/ai/fisherman.js.map +0 -1
- package/dist/src/ai/historian.js.map +0 -1
- package/dist/src/ai/navigator.js.map +0 -1
- package/dist/src/ai/pilot.js.map +0 -1
- package/dist/src/ai/planner/session-dedup.js.map +0 -1
- package/dist/src/ai/planner/styles.js.map +0 -1
- package/dist/src/ai/planner/subpages.js.map +0 -1
- package/dist/src/ai/planner.js.map +0 -1
- package/dist/src/ai/provider.js.map +0 -1
- package/dist/src/ai/quartermaster.js.map +0 -1
- package/dist/src/ai/researcher/cache.js.map +0 -1
- package/dist/src/ai/researcher/coordinates.js.map +0 -1
- package/dist/src/ai/researcher/deep-analysis.js.map +0 -1
- package/dist/src/ai/researcher/fingerprint-worker.js.map +0 -1
- package/dist/src/ai/researcher/focus.js.map +0 -1
- package/dist/src/ai/researcher/locators.js.map +0 -1
- package/dist/src/ai/researcher/mixin.js.map +0 -1
- package/dist/src/ai/researcher/parser.js.map +0 -1
- package/dist/src/ai/researcher/research-result.js.map +0 -1
- package/dist/src/ai/researcher.js.map +0 -1
- package/dist/src/ai/rules.js.map +0 -1
- package/dist/src/ai/task-agent.js.map +0 -1
- package/dist/src/ai/tester.js.map +0 -1
- package/dist/src/ai/tools.js.map +0 -1
- package/dist/src/api/api-client.js.map +0 -1
- package/dist/src/api/request-result.js.map +0 -1
- package/dist/src/api/request-store.js.map +0 -1
- package/dist/src/api/spec-reader.js.map +0 -1
- package/dist/src/api/xhr-capture.js.map +0 -1
- package/dist/src/browser-server.js.map +0 -1
- package/dist/src/command-handler.js.map +0 -1
- package/dist/src/commands/add-rule-command.js.map +0 -1
- package/dist/src/commands/base-command.js.map +0 -1
- package/dist/src/commands/clean-command.js.map +0 -1
- package/dist/src/commands/context-aria-command.js.map +0 -1
- package/dist/src/commands/context-command.js.map +0 -1
- package/dist/src/commands/context-data-command.js.map +0 -1
- package/dist/src/commands/context-experience-command.js.map +0 -1
- package/dist/src/commands/context-html-command.js.map +0 -1
- package/dist/src/commands/context-knowledge-command.js.map +0 -1
- package/dist/src/commands/debug-command.js.map +0 -1
- package/dist/src/commands/drill-command.js.map +0 -1
- package/dist/src/commands/exit-command.js.map +0 -1
- package/dist/src/commands/explore-command.js.map +0 -1
- package/dist/src/commands/freesail-command.js.map +0 -1
- package/dist/src/commands/help-command.js.map +0 -1
- package/dist/src/commands/index.js.map +0 -1
- package/dist/src/commands/knows-command.js.map +0 -1
- package/dist/src/commands/learn-command.js.map +0 -1
- package/dist/src/commands/navigate-command.js.map +0 -1
- package/dist/src/commands/path-command.js.map +0 -1
- package/dist/src/commands/plan-clear-command.js.map +0 -1
- package/dist/src/commands/plan-command.js.map +0 -1
- package/dist/src/commands/plan-edit-command.js.map +0 -1
- package/dist/src/commands/plan-load-command.js.map +0 -1
- package/dist/src/commands/plan-reload-command.js.map +0 -1
- package/dist/src/commands/plan-save-command.js.map +0 -1
- package/dist/src/commands/research-command.js.map +0 -1
- package/dist/src/commands/start-command.js.map +0 -1
- package/dist/src/commands/status-command.js.map +0 -1
- package/dist/src/commands/test-command.js.map +0 -1
- package/dist/src/components/ActivityPane.js.map +0 -1
- package/dist/src/components/AddKnowledge.js.map +0 -1
- package/dist/src/components/AddRule.js.map +0 -1
- package/dist/src/components/App.js.map +0 -1
- package/dist/src/components/Autocomplete.js.map +0 -1
- package/dist/src/components/InputPane.js.map +0 -1
- package/dist/src/components/InputReadline.js.map +0 -1
- package/dist/src/components/LogPane.js.map +0 -1
- package/dist/src/components/PlanEditor.js.map +0 -1
- package/dist/src/components/PlanPane.js.map +0 -1
- package/dist/src/components/SessionTimer.js.map +0 -1
- package/dist/src/components/StateTransitionPane.js.map +0 -1
- package/dist/src/components/StatusPane.js.map +0 -1
- package/dist/src/components/TaskPane.js.map +0 -1
- package/dist/src/components/Welcome.js.map +0 -1
- package/dist/src/components/WelcomeChecklist.js.map +0 -1
- package/dist/src/components/WelcomeCommands.js.map +0 -1
- package/dist/src/components/autocomplete-store.js.map +0 -1
- package/dist/src/components/parse-keypress.js.map +0 -1
- package/dist/src/config.js.map +0 -1
- package/dist/src/execution-controller.js.map +0 -1
- package/dist/src/experience-tracker.js.map +0 -1
- package/dist/src/explorbot.js.map +0 -1
- package/dist/src/explorer.js.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/knowledge-tracker.js.map +0 -1
- package/dist/src/observability.js.map +0 -1
- package/dist/src/reporter.js.map +0 -1
- package/dist/src/state-manager.js.map +0 -1
- package/dist/src/stats.js.map +0 -1
- package/dist/src/test-plan.js.map +0 -1
- package/dist/src/utils/aria.js.map +0 -1
- package/dist/src/utils/code-extractor.js.map +0 -1
- package/dist/src/utils/context-formatter.js.map +0 -1
- package/dist/src/utils/error-page.js.map +0 -1
- package/dist/src/utils/expandable.js.map +0 -1
- package/dist/src/utils/hooks-runner.js.map +0 -1
- package/dist/src/utils/html-diff.js.map +0 -1
- package/dist/src/utils/html.js.map +0 -1
- package/dist/src/utils/logger.js.map +0 -1
- package/dist/src/utils/loop.js.map +0 -1
- package/dist/src/utils/markdown-parser.js.map +0 -1
- package/dist/src/utils/markdown-query.js.map +0 -1
- package/dist/src/utils/markdown-terminal.js.map +0 -1
- package/dist/src/utils/research-parser.js.map +0 -1
- package/dist/src/utils/retry.js.map +0 -1
- package/dist/src/utils/rules-loader.js.map +0 -1
- package/dist/src/utils/strings.js.map +0 -1
- package/dist/src/utils/test-plan-markdown.js.map +0 -1
- package/dist/src/utils/throttle.js.map +0 -1
- package/dist/src/utils/unique-names.js.map +0 -1
- package/dist/src/utils/url-matcher.js.map +0 -1
- package/dist/src/utils/web-element.js.map +0 -1
- package/dist/src/utils/xpath.js.map +0 -1
- package/prompts/audit-rules.md +0 -124
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ExplorBot } from '../explorbot.js';
|
|
2
|
+
|
|
3
|
+
export interface CommandOption {
|
|
4
|
+
flags: string;
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export abstract class BaseCommand {
|
|
9
|
+
abstract name: string;
|
|
10
|
+
abstract description: string;
|
|
11
|
+
aliases: string[] = [];
|
|
12
|
+
options: CommandOption[] = [];
|
|
13
|
+
tuiEnabled = true;
|
|
14
|
+
suggestions: string[] = [];
|
|
15
|
+
|
|
16
|
+
protected explorBot: ExplorBot;
|
|
17
|
+
|
|
18
|
+
constructor(explorBot: ExplorBot) {
|
|
19
|
+
this.explorBot = explorBot;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
abstract execute(args: string): Promise<void>;
|
|
23
|
+
|
|
24
|
+
matches(commandName: string): boolean {
|
|
25
|
+
return this.name === commandName || this.aliases.includes(commandName);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync, readdirSync, rmSync, statSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { ConfigParser, outputPath } from '../config.js';
|
|
4
|
+
import { tag } from '../utils/logger.js';
|
|
5
|
+
import { BaseCommand } from './base-command.js';
|
|
6
|
+
|
|
7
|
+
export const CLEAN_TARGETS: Record<string, { description: string; getDir: () => string }> = {
|
|
8
|
+
states: { description: 'page states', getDir: () => outputPath('states') },
|
|
9
|
+
research: { description: 'research cache', getDir: () => outputPath('research') },
|
|
10
|
+
plans: { description: 'test plans', getDir: () => outputPath('plans') },
|
|
11
|
+
experiences: { description: 'experience files', getDir: () => getExperienceDir() },
|
|
12
|
+
output: { description: 'all output files', getDir: () => outputPath() },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function getExperienceDir(): string {
|
|
16
|
+
const configParser = ConfigParser.getInstance();
|
|
17
|
+
const config = configParser.getConfig();
|
|
18
|
+
const configPath = configParser.getConfigPath();
|
|
19
|
+
if (configPath) {
|
|
20
|
+
return join(dirname(configPath), config.dirs?.experience || 'experience');
|
|
21
|
+
}
|
|
22
|
+
return config.dirs?.experience || 'experience';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cleanDirectoryContents(dirPath: string): number {
|
|
26
|
+
if (!existsSync(dirPath)) return 0;
|
|
27
|
+
let count = 0;
|
|
28
|
+
for (const item of readdirSync(dirPath)) {
|
|
29
|
+
const itemPath = join(dirPath, item);
|
|
30
|
+
if (statSync(itemPath).isDirectory()) {
|
|
31
|
+
count += cleanDirectoryContents(itemPath);
|
|
32
|
+
rmSync(itemPath, { recursive: true });
|
|
33
|
+
} else {
|
|
34
|
+
unlinkSync(itemPath);
|
|
35
|
+
count++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return count;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class CleanCommand extends BaseCommand {
|
|
42
|
+
name = 'clean';
|
|
43
|
+
description = 'Clean files: clean [states|research|plans|experiences|output]';
|
|
44
|
+
suggestions = Object.keys(CLEAN_TARGETS).map((t) => `/clean ${t}`);
|
|
45
|
+
|
|
46
|
+
async execute(args: string): Promise<void> {
|
|
47
|
+
const target = args.trim().toLowerCase();
|
|
48
|
+
|
|
49
|
+
if (!target) {
|
|
50
|
+
this.cleanTarget('output');
|
|
51
|
+
this.cleanTarget('experiences');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!CLEAN_TARGETS[target]) {
|
|
56
|
+
tag('error').log(`Unknown clean target: ${target}. Available: ${Object.keys(CLEAN_TARGETS).join(', ')}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.cleanTarget(target);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private cleanTarget(name: string): void {
|
|
64
|
+
const target = CLEAN_TARGETS[name];
|
|
65
|
+
const dir = target.getDir();
|
|
66
|
+
if (!existsSync(dir)) {
|
|
67
|
+
tag('info').log(`${name}: nothing to clean (${dir} not found)`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const count = cleanDirectoryContents(dir);
|
|
71
|
+
tag('success').log(`Cleaned ${count} ${target.description} files from ${dir}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { tag } from '../utils/logger.js';
|
|
2
|
+
import { BaseCommand } from './base-command.js';
|
|
3
|
+
|
|
4
|
+
export class ContextAriaCommand extends BaseCommand {
|
|
5
|
+
name = 'context:aria';
|
|
6
|
+
description = 'Print full ARIA snapshot for current page';
|
|
7
|
+
|
|
8
|
+
async execute(_args: string): Promise<void> {
|
|
9
|
+
const state = this.explorBot.getExplorer().getStateManager().getCurrentState();
|
|
10
|
+
|
|
11
|
+
if (!state) {
|
|
12
|
+
throw new Error('No active page to snapshot');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ariaSnapshot = state.ariaSnapshot;
|
|
16
|
+
if (!ariaSnapshot) {
|
|
17
|
+
throw new Error('No ARIA snapshot available for current page');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
tag('multiline').log(`ARIA Snapshot:\n\n${ariaSnapshot}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ActionResult } from '../action-result.js';
|
|
2
|
+
import { Researcher } from '../ai/researcher.js';
|
|
3
|
+
import { outputPath } from '../config.js';
|
|
4
|
+
import { type ContextData, type ContextMode, formatContextSummary } from '../utils/context-formatter.js';
|
|
5
|
+
import { tag } from '../utils/logger.js';
|
|
6
|
+
import { extractValidContainers } from '../utils/research-parser.js';
|
|
7
|
+
import { BaseCommand } from './base-command.js';
|
|
8
|
+
|
|
9
|
+
export class ContextCommand extends BaseCommand {
|
|
10
|
+
name = 'context';
|
|
11
|
+
description = 'Show page context summary (URL, headings, experience, knowledge, ARIA, HTML, research)';
|
|
12
|
+
suggestions = ['context:aria', 'context:html', 'context:knowledge', 'context:experience', 'context:data'];
|
|
13
|
+
|
|
14
|
+
async execute(args: string): Promise<void> {
|
|
15
|
+
const explorer = this.explorBot.getExplorer();
|
|
16
|
+
const state = explorer.getStateManager().getCurrentState();
|
|
17
|
+
|
|
18
|
+
if (!state) {
|
|
19
|
+
throw new Error('No active page to show context for');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isVisual = args.includes('--visual') || args.includes('--screenshot');
|
|
23
|
+
|
|
24
|
+
await explorer.annotateElements();
|
|
25
|
+
|
|
26
|
+
if (isVisual) {
|
|
27
|
+
const cachedResearch = Researcher.getCachedResearch(state);
|
|
28
|
+
const containers = cachedResearch ? extractValidContainers(cachedResearch) : [];
|
|
29
|
+
await explorer.visuallyAnnotateElements({ containers });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const actionResult = await explorer.createAction().capturePageState({ includeScreenshot: isVisual });
|
|
33
|
+
const experienceTracker = explorer.getStateManager().getExperienceTracker();
|
|
34
|
+
const knowledgeTracker = this.explorBot.getKnowledgeTracker();
|
|
35
|
+
|
|
36
|
+
let mode: ContextMode = 'compact';
|
|
37
|
+
if (args.includes('--full')) {
|
|
38
|
+
mode = 'full';
|
|
39
|
+
} else if (args.includes('--attached')) {
|
|
40
|
+
mode = 'attached';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const contextData: ContextData = {
|
|
44
|
+
url: actionResult.url,
|
|
45
|
+
title: actionResult.title,
|
|
46
|
+
headings: {
|
|
47
|
+
h1: actionResult.h1,
|
|
48
|
+
h2: actionResult.h2,
|
|
49
|
+
h3: actionResult.h3,
|
|
50
|
+
h4: actionResult.h4,
|
|
51
|
+
},
|
|
52
|
+
experience: experienceTracker.getRelevantExperience(actionResult),
|
|
53
|
+
knowledge: knowledgeTracker.getRelevantKnowledge(actionResult),
|
|
54
|
+
ariaSnapshot: actionResult.ariaSnapshot,
|
|
55
|
+
combinedHtml: mode === 'full' ? await actionResult.combinedHtml() : undefined,
|
|
56
|
+
research: Researcher.getCachedResearch(state),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const output = formatContextSummary(contextData, mode);
|
|
60
|
+
tag('multiline').log(output);
|
|
61
|
+
|
|
62
|
+
if (isVisual && actionResult.screenshotFile) {
|
|
63
|
+
const fullPath = outputPath('states', actionResult.screenshotFile);
|
|
64
|
+
tag('info').log(`Screenshot saved: file://${fullPath}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ActionResult } from '../action-result.js';
|
|
2
|
+
import { tag } from '../utils/logger.js';
|
|
3
|
+
import { BaseCommand } from './base-command.js';
|
|
4
|
+
|
|
5
|
+
export class ContextDataCommand extends BaseCommand {
|
|
6
|
+
name = 'context:data';
|
|
7
|
+
description = 'Extract structured data from current page';
|
|
8
|
+
|
|
9
|
+
async execute(_args: string): Promise<void> {
|
|
10
|
+
const explorer = this.explorBot.getExplorer();
|
|
11
|
+
const state = explorer.getStateManager().getCurrentState();
|
|
12
|
+
|
|
13
|
+
if (!state) {
|
|
14
|
+
throw new Error('No active page to extract data from');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const actionResult = ActionResult.fromState(state);
|
|
18
|
+
|
|
19
|
+
if (!actionResult.html || actionResult.html.trim().length < 100) {
|
|
20
|
+
tag('info').log('Capturing fresh page content...');
|
|
21
|
+
const freshResult = await explorer.createAction().capturePageState();
|
|
22
|
+
const table = await this.explorBot.agentResearcher().extractData(freshResult);
|
|
23
|
+
tag('multiline').log(table);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const table = await this.explorBot.agentResearcher().extractData(state);
|
|
28
|
+
tag('multiline').log(table);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { ActionResult } from '../action-result.js';
|
|
4
|
+
import { tag } from '../utils/logger.js';
|
|
5
|
+
import { BaseCommand } from './base-command.js';
|
|
6
|
+
|
|
7
|
+
export class ContextExperienceCommand extends BaseCommand {
|
|
8
|
+
name = 'context:experience';
|
|
9
|
+
description = 'Print all matching experience for current page';
|
|
10
|
+
|
|
11
|
+
async execute(_args: string): Promise<void> {
|
|
12
|
+
const explorer = this.explorBot.getExplorer();
|
|
13
|
+
const state = explorer.getStateManager().getCurrentState();
|
|
14
|
+
|
|
15
|
+
if (!state) {
|
|
16
|
+
throw new Error('No active page');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const actionResult = ActionResult.fromState(state);
|
|
20
|
+
const experienceTracker = explorer.getStateManager().getExperienceTracker();
|
|
21
|
+
const experience = experienceTracker.getRelevantExperience(actionResult);
|
|
22
|
+
|
|
23
|
+
if (experience.length === 0) {
|
|
24
|
+
tag('info').log(`No experience found for: ${actionResult.url}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const lines: string[] = [];
|
|
29
|
+
lines.push(chalk.bold.cyan(`📁 Experience for ${actionResult.url} (${experience.length} files)`));
|
|
30
|
+
lines.push('');
|
|
31
|
+
|
|
32
|
+
for (const exp of experience) {
|
|
33
|
+
lines.push(chalk.yellow(`--- ${basename(exp.filePath)} ---`));
|
|
34
|
+
if (exp.data?.url) {
|
|
35
|
+
lines.push(chalk.gray(`URL: ${exp.data.url}`));
|
|
36
|
+
}
|
|
37
|
+
if (exp.data?.title) {
|
|
38
|
+
lines.push(chalk.gray(`Title: ${exp.data.title}`));
|
|
39
|
+
}
|
|
40
|
+
if (exp.content.trim()) {
|
|
41
|
+
lines.push(exp.content.trim());
|
|
42
|
+
}
|
|
43
|
+
lines.push('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
tag('multiline').log(lines.join('\n'));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ActionResult } from '../action-result.js';
|
|
2
|
+
import { tag } from '../utils/logger.js';
|
|
3
|
+
import { BaseCommand } from './base-command.js';
|
|
4
|
+
|
|
5
|
+
export class ContextHtmlCommand extends BaseCommand {
|
|
6
|
+
name = 'context:html';
|
|
7
|
+
description = 'Print combined HTML snapshot for current page';
|
|
8
|
+
|
|
9
|
+
async execute(_args: string): Promise<void> {
|
|
10
|
+
const explorer = this.explorBot.getExplorer();
|
|
11
|
+
const manager = explorer.getStateManager();
|
|
12
|
+
const state = manager.getCurrentState();
|
|
13
|
+
|
|
14
|
+
if (!state) {
|
|
15
|
+
throw new Error('No active page to snapshot');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let actionResult = ActionResult.fromState(state);
|
|
19
|
+
|
|
20
|
+
if (!actionResult.html || actionResult.html.trim().length < 100) {
|
|
21
|
+
tag('info').log('Capturing fresh page content...');
|
|
22
|
+
actionResult = await explorer.createAction().capturePageState();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const html = await actionResult.combinedHtml();
|
|
26
|
+
|
|
27
|
+
if (!html) {
|
|
28
|
+
throw new Error('No HTML snapshot available for current page');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
tag('html').log(html);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { ActionResult } from '../action-result.js';
|
|
4
|
+
import { tag } from '../utils/logger.js';
|
|
5
|
+
import { BaseCommand } from './base-command.js';
|
|
6
|
+
|
|
7
|
+
export class ContextKnowledgeCommand extends BaseCommand {
|
|
8
|
+
name = 'context:knowledge';
|
|
9
|
+
description = 'Print all matching knowledge for current page';
|
|
10
|
+
|
|
11
|
+
async execute(_args: string): Promise<void> {
|
|
12
|
+
const explorer = this.explorBot.getExplorer();
|
|
13
|
+
const state = explorer.getStateManager().getCurrentState();
|
|
14
|
+
|
|
15
|
+
if (!state) {
|
|
16
|
+
throw new Error('No active page');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const actionResult = ActionResult.fromState(state);
|
|
20
|
+
const knowledgeTracker = this.explorBot.getKnowledgeTracker();
|
|
21
|
+
const knowledge = knowledgeTracker.getRelevantKnowledge(actionResult);
|
|
22
|
+
|
|
23
|
+
if (knowledge.length === 0) {
|
|
24
|
+
tag('info').log(`No knowledge found for: ${actionResult.url}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const lines: string[] = [];
|
|
29
|
+
lines.push(chalk.bold.cyan(`📚 Knowledge for ${actionResult.url} (${knowledge.length} files)`));
|
|
30
|
+
lines.push('');
|
|
31
|
+
|
|
32
|
+
for (const k of knowledge) {
|
|
33
|
+
lines.push(chalk.yellow(`--- ${basename(k.filePath)} ---`));
|
|
34
|
+
lines.push(chalk.gray(`Pattern: ${k.url}`));
|
|
35
|
+
if (k.content.trim()) {
|
|
36
|
+
lines.push(k.content.trim());
|
|
37
|
+
}
|
|
38
|
+
lines.push('');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
tag('multiline').log(lines.join('\n'));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isDebugMode, setDebugMode, tag } from '../utils/logger.js';
|
|
2
|
+
import { BaseCommand } from './base-command.js';
|
|
3
|
+
|
|
4
|
+
export class DebugCommand extends BaseCommand {
|
|
5
|
+
name = 'debug';
|
|
6
|
+
description = 'Toggle debug output';
|
|
7
|
+
|
|
8
|
+
async execute(_args: string): Promise<void> {
|
|
9
|
+
const enabled = !isDebugMode();
|
|
10
|
+
setDebugMode(enabled);
|
|
11
|
+
tag('info').log(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BaseCommand } from './base-command.js';
|
|
2
|
+
|
|
3
|
+
export class DrillCommand extends BaseCommand {
|
|
4
|
+
name = 'drill';
|
|
5
|
+
description = 'Drill all components on current page to learn interactions';
|
|
6
|
+
aliases = ['bosun'];
|
|
7
|
+
suggestions = ['/research - to see UI map first', '/navigate <page> - to go to another page'];
|
|
8
|
+
|
|
9
|
+
async execute(args: string): Promise<void> {
|
|
10
|
+
const knowledgePath = this.parseKnowledgeArg(args);
|
|
11
|
+
const maxComponents = this.parseMaxArg(args);
|
|
12
|
+
|
|
13
|
+
const state = this.explorBot.getExplorer().getStateManager().getCurrentState();
|
|
14
|
+
if (!state) {
|
|
15
|
+
throw new Error('No active page to drill');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await this.explorBot.agentBosun().drill({
|
|
19
|
+
knowledgePath,
|
|
20
|
+
maxComponents,
|
|
21
|
+
interactive: true,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private parseKnowledgeArg(args: string): string | undefined {
|
|
26
|
+
const match = args.match(/--knowledge\s+(\S+)/);
|
|
27
|
+
return match ? match[1] : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private parseMaxArg(args: string): number | undefined {
|
|
31
|
+
const match = args.match(/--max\s+(\d+)/);
|
|
32
|
+
return match ? Number.parseInt(match[1], 10) : undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { render } from 'ink';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { StatusPane } from '../components/StatusPane.js';
|
|
4
|
+
import { Stats } from '../stats.js';
|
|
5
|
+
import { BaseCommand } from './base-command.js';
|
|
6
|
+
|
|
7
|
+
export class ExitCommand extends BaseCommand {
|
|
8
|
+
name = 'exit';
|
|
9
|
+
description = 'Exit the application';
|
|
10
|
+
aliases = ['quit'];
|
|
11
|
+
|
|
12
|
+
async execute(_args: string): Promise<void> {
|
|
13
|
+
await this.explorBot.getExplorer().stop();
|
|
14
|
+
|
|
15
|
+
if (Stats.hasActivity()) {
|
|
16
|
+
await new Promise<void>((resolve) => {
|
|
17
|
+
const { unmount } = render(
|
|
18
|
+
React.createElement(StatusPane, {
|
|
19
|
+
onComplete: () => {
|
|
20
|
+
unmount();
|
|
21
|
+
resolve();
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
{ exitOnCtrlC: false, patchConsole: false }
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log('\nGoodbye!');
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import figureSet from 'figures';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getStyles } from '../ai/planner/styles.js';
|
|
4
|
+
import { getCliName } from '../utils/cli-name.ts';
|
|
5
|
+
import type { Plan } from '../test-plan.js';
|
|
6
|
+
import { jsonToTable } from '../utils/markdown-parser.js';
|
|
7
|
+
import { tag } from '../utils/logger.js';
|
|
8
|
+
import { BaseCommand } from './base-command.js';
|
|
9
|
+
|
|
10
|
+
export class ExploreCommand extends BaseCommand {
|
|
11
|
+
name = 'explore';
|
|
12
|
+
description = 'Start web exploration';
|
|
13
|
+
options = [{ flags: '--max-tests <number>', description: 'Maximum number of tests to run' }];
|
|
14
|
+
suggestions = ['/navigate <page> - to go to another page', '/research - to analyze', '/plan <feature> - to plan testing'];
|
|
15
|
+
|
|
16
|
+
maxTests?: number;
|
|
17
|
+
private testsRun = 0;
|
|
18
|
+
private completedPlans: Plan[] = [];
|
|
19
|
+
|
|
20
|
+
async execute(args: string): Promise<void> {
|
|
21
|
+
const maxTestsMatch = args.match(/--max-tests\s+(\d+)/);
|
|
22
|
+
if (maxTestsMatch) {
|
|
23
|
+
this.maxTests = Number.parseInt(maxTestsMatch[1], 10);
|
|
24
|
+
args = args.replace(/--max-tests\s+\d+/, '').trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const feature = args.trim() || undefined;
|
|
28
|
+
const mainUrl = this.explorBot.getExplorer().getStateManager().getCurrentState()?.url;
|
|
29
|
+
|
|
30
|
+
await this.runAllStyles(mainUrl, feature);
|
|
31
|
+
const mainPlan = this.explorBot.getCurrentPlan();
|
|
32
|
+
if (!mainPlan) return;
|
|
33
|
+
this.completedPlans.push(mainPlan);
|
|
34
|
+
|
|
35
|
+
if (!this.isLimitReached()) {
|
|
36
|
+
const planner = this.explorBot.agentPlanner();
|
|
37
|
+
while (true) {
|
|
38
|
+
if (this.isLimitReached()) break;
|
|
39
|
+
|
|
40
|
+
const candidates = planner.collectSubPageCandidates(mainPlan, mainUrl || '/');
|
|
41
|
+
if (candidates.length === 0) break;
|
|
42
|
+
|
|
43
|
+
const pick = await planner.pickNextSubPage(candidates);
|
|
44
|
+
if (!pick) break;
|
|
45
|
+
|
|
46
|
+
tag('info').log(`Exploring sub-page: ${pick.url} (${pick.reason})`);
|
|
47
|
+
try {
|
|
48
|
+
await this.explorBot.visit(pick.url);
|
|
49
|
+
await this.runAllStyles(pick.url, undefined, mainPlan, this.completedPlans);
|
|
50
|
+
const subPlan = this.explorBot.getCurrentPlan();
|
|
51
|
+
if (subPlan) {
|
|
52
|
+
this.completedPlans.push(subPlan);
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
tag('warning').log(`Sub-page exploration failed: ${err instanceof Error ? err.message : err}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.explorBot.setCurrentPlan(mainPlan);
|
|
61
|
+
if (mainUrl) await this.explorBot.visit(mainUrl);
|
|
62
|
+
const savedPath = this.explorBot.savePlans(this.completedPlans);
|
|
63
|
+
this.printResults(savedPath);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private async runAllStyles(pageUrl?: string, feature?: string, parentPlan?: Plan, completedPlans?: Plan[]): Promise<void> {
|
|
67
|
+
let fresh = true;
|
|
68
|
+
for (const style of Object.keys(getStyles())) {
|
|
69
|
+
if (!fresh && pageUrl) {
|
|
70
|
+
await this.explorBot.visit(pageUrl);
|
|
71
|
+
}
|
|
72
|
+
const opts: { fresh: boolean; style: string; extend?: Plan; completedPlans?: Plan[] } = { fresh, style, completedPlans };
|
|
73
|
+
if (fresh && parentPlan) opts.extend = parentPlan;
|
|
74
|
+
await this.explorBot.plan(feature, opts);
|
|
75
|
+
await this.runPendingTests();
|
|
76
|
+
fresh = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private printResults(savedPath?: string | null): void {
|
|
81
|
+
const allTests = this.completedPlans.flatMap((plan) => plan.tests.filter((t) => t.status !== 'pending').map((test) => ({ test, planTitle: plan.title })));
|
|
82
|
+
|
|
83
|
+
if (allTests.length === 0) return;
|
|
84
|
+
|
|
85
|
+
const hasSubPages = this.completedPlans.length > 1;
|
|
86
|
+
const rows = allTests.map(({ test, planTitle }, index) => {
|
|
87
|
+
const durationMs = test.getDurationMs();
|
|
88
|
+
const duration = durationMs != null ? `${(durationMs / 1000).toFixed(1)}s` : '-';
|
|
89
|
+
let status = 'failed';
|
|
90
|
+
if (test.isSuccessful) status = 'passed';
|
|
91
|
+
else if (test.isSkipped) status = 'skipped';
|
|
92
|
+
const row: Record<string, string> = {
|
|
93
|
+
'#': String(index + 1),
|
|
94
|
+
Status: status,
|
|
95
|
+
Title: test.scenario.replace(/\|/g, '-'),
|
|
96
|
+
Priority: test.priority,
|
|
97
|
+
Time: duration,
|
|
98
|
+
Steps: String(Object.keys(test.notes).length),
|
|
99
|
+
};
|
|
100
|
+
if (hasSubPages) {
|
|
101
|
+
row.Plan = planTitle;
|
|
102
|
+
}
|
|
103
|
+
return row;
|
|
104
|
+
});
|
|
105
|
+
const columns = ['#', 'Status', 'Title', 'Priority', 'Time', 'Steps'];
|
|
106
|
+
if (hasSubPages) columns.push('Plan');
|
|
107
|
+
tag('multiline').log(jsonToTable(rows, columns));
|
|
108
|
+
tag('info').log(`${figureSet.tick} ${allTests.length} tests completed`);
|
|
109
|
+
|
|
110
|
+
if (savedPath) {
|
|
111
|
+
const relativePath = path.relative(process.cwd(), savedPath);
|
|
112
|
+
tag('info').log(`Re-run tests: ${getCliName()} test ${relativePath} <index>`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private isLimitReached(): boolean {
|
|
117
|
+
return this.maxTests != null && this.testsRun >= this.maxTests;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private async runPendingTests(): Promise<void> {
|
|
121
|
+
const plan = this.explorBot.getCurrentPlan();
|
|
122
|
+
if (!plan) return;
|
|
123
|
+
for (const test of plan.getPendingTests()) {
|
|
124
|
+
if (this.isLimitReached()) break;
|
|
125
|
+
await this.explorBot.agentTester().test(test);
|
|
126
|
+
this.testsRun++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Planner } from '../ai/planner.js';
|
|
2
|
+
import { Researcher } from '../ai/researcher.js';
|
|
3
|
+
import { tag } from '../utils/logger.js';
|
|
4
|
+
import { loop } from '../utils/loop.js';
|
|
5
|
+
import { BaseCommand } from './base-command.js';
|
|
6
|
+
import { ExploreCommand } from './explore-command.js';
|
|
7
|
+
|
|
8
|
+
export class FreesailCommand extends BaseCommand {
|
|
9
|
+
name = 'freesail';
|
|
10
|
+
description = 'Continuously explore and navigate to new pages autonomously';
|
|
11
|
+
aliases = ['freeride'];
|
|
12
|
+
tuiEnabled = true;
|
|
13
|
+
options = [
|
|
14
|
+
{ flags: '--deep', description: 'Use deep navigation strategy' },
|
|
15
|
+
{ flags: '--shallow', description: 'Use shallow navigation strategy' },
|
|
16
|
+
{ flags: '--scope <url>', description: 'Limit navigation to URLs starting with this prefix' },
|
|
17
|
+
{ flags: '--max-tests <number>', description: 'Maximum number of tests to run' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
async execute(args: string): Promise<void> {
|
|
21
|
+
const { strategy, scope, maxTests } = parseArgs(args);
|
|
22
|
+
|
|
23
|
+
await this.explorBot.visitInitialState();
|
|
24
|
+
|
|
25
|
+
let testsRun = 0;
|
|
26
|
+
|
|
27
|
+
await loop(
|
|
28
|
+
async (ctx) => {
|
|
29
|
+
if (maxTests != null && testsRun >= maxTests) ctx.stop();
|
|
30
|
+
|
|
31
|
+
const stateManager = this.explorBot.getExplorer().getStateManager();
|
|
32
|
+
const state = stateManager.getCurrentState();
|
|
33
|
+
|
|
34
|
+
if (state && !Researcher.getCachedResearch(state)) {
|
|
35
|
+
await this.explorBot.agentResearcher().research(state, { deep: true, screenshot: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const cachedPlan = state?.url ? Planner.getCachedPlan(state.url) : null;
|
|
39
|
+
if (cachedPlan?.tests.some((t) => t.result)) {
|
|
40
|
+
tag('info').log(`Page already tested (${cachedPlan.tests.length} tests in plan), skipping exploration`);
|
|
41
|
+
} else {
|
|
42
|
+
const exploreCmd = new ExploreCommand(this.explorBot);
|
|
43
|
+
if (maxTests != null) exploreCmd.maxTests = maxTests - testsRun;
|
|
44
|
+
await exploreCmd.execute('');
|
|
45
|
+
|
|
46
|
+
const plan = this.explorBot.getCurrentPlan();
|
|
47
|
+
if (plan) testsRun += plan.tests.filter((t) => t.hasFinished).length;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (maxTests != null && testsRun >= maxTests) ctx.stop();
|
|
51
|
+
|
|
52
|
+
const navigator = this.explorBot.agentNavigator();
|
|
53
|
+
const visitedUrls = stateManager.getAllVisitedUrls();
|
|
54
|
+
const suggestion = await navigator.freeSail({ strategy, scope, visitedUrls });
|
|
55
|
+
if (!suggestion) {
|
|
56
|
+
tag('info').log('No navigation suggestion available');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (scope && !suggestion.target.startsWith(scope)) {
|
|
61
|
+
tag('warning').log(`Suggestion ${suggestion.target} is outside scope ${scope}, skipping`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
tag('info').log(`Navigating to: ${suggestion.target} - ${suggestion.reason}`);
|
|
66
|
+
await this.explorBot.openFreshTab();
|
|
67
|
+
await this.explorBot.visit(suggestion.target);
|
|
68
|
+
this.explorBot.clearPlan();
|
|
69
|
+
},
|
|
70
|
+
{ maxAttempts: Number.POSITIVE_INFINITY }
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseArgs(args: string): { strategy: 'deep' | 'shallow' | undefined; scope: string | undefined; maxTests: number | undefined } {
|
|
76
|
+
const parts = args.trim().split(/\s+/);
|
|
77
|
+
let strategy: 'deep' | 'shallow' | undefined;
|
|
78
|
+
let scope: string | undefined;
|
|
79
|
+
let maxTests: number | undefined;
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < parts.length; i++) {
|
|
82
|
+
if (parts[i] === '--deep') strategy = 'deep';
|
|
83
|
+
if (parts[i] === '--shallow') strategy = 'shallow';
|
|
84
|
+
if (parts[i] === '--scope' && parts[i + 1]) {
|
|
85
|
+
scope = parts[i + 1];
|
|
86
|
+
i++;
|
|
87
|
+
}
|
|
88
|
+
if (parts[i] === '--max-tests' && parts[i + 1]) {
|
|
89
|
+
maxTests = Number.parseInt(parts[i + 1], 10);
|
|
90
|
+
i++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { strategy, scope, maxTests };
|
|
95
|
+
}
|