explorbot 0.0.1 → 0.1.0
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 +680 -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 +23 -101
- 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 +14 -12
- 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 +42 -7
- package/dist/src/ai/planner.js +15 -4
- package/dist/src/ai/provider.js +0 -1
- package/dist/src/ai/quartermaster.js +0 -1
- package/dist/src/ai/researcher/cache.js +13 -9
- package/dist/src/ai/researcher/coordinates.js +4 -3
- package/dist/src/ai/researcher/deep-analysis.js +16 -20
- 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 +1 -2
- package/dist/src/ai/researcher/mixin.js +0 -1
- package/dist/src/ai/researcher/parser.js +4 -4
- package/dist/src/ai/researcher/research-result.js +2 -1
- package/dist/src/ai/researcher.js +6 -6
- 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 +4 -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 +2 -3
- 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 +3 -3
- 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 +117 -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 +6 -2
- 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 +1 -2
- package/dist/src/explorer.js +58 -17
- 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 +6 -5
- package/dist/src/utils/xpath.js +0 -1
- package/package.json +28 -4
- package/src/action-result.ts +694 -0
- package/src/action.ts +449 -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 +171 -0
- package/src/ai/planner.ts +549 -0
- package/src/ai/provider.ts +613 -0
- package/src/ai/quartermaster.ts +286 -0
- package/src/ai/researcher/cache.ts +109 -0
- package/src/ai/researcher/coordinates.ts +239 -0
- package/src/ai/researcher/deep-analysis.ts +412 -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 +116 -0
- package/src/ai/researcher.ts +858 -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 +1122 -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 +131 -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 +46 -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 +491 -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 +760 -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 +147 -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,301 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { setPreserveConsoleLogs } from '../../../src/utils/logger.ts';
|
|
5
|
+
import { getStyles } from './ai/chief/styles.ts';
|
|
6
|
+
import { ApiBot, type ApibotOptions } from './apibot.ts';
|
|
7
|
+
|
|
8
|
+
function buildOptions(options: any): ApibotOptions {
|
|
9
|
+
return {
|
|
10
|
+
verbose: options.verbose || options.debug,
|
|
11
|
+
config: options.config,
|
|
12
|
+
path: options.path,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function addCommonOptions(cmd: Command): Command {
|
|
17
|
+
return cmd.option('-v, --verbose', 'Enable verbose logging').option('--debug', 'Enable debug logging').option('-c, --config <path>', 'Path to configuration file').option('-p, --path <path>', 'Working directory path');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function selectTests(tests: any[], index?: string): any[] {
|
|
21
|
+
if (!index || index === '*' || index === 'all') {
|
|
22
|
+
return tests.filter((t) => t.status === 'pending');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const rangeMatch = index.match(/^(\d+)-(\d+)$/);
|
|
26
|
+
if (rangeMatch) {
|
|
27
|
+
const start = Number.parseInt(rangeMatch[1]) - 1;
|
|
28
|
+
const end = Number.parseInt(rangeMatch[2]);
|
|
29
|
+
return tests.slice(start, end);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (index.includes(',')) {
|
|
33
|
+
const indices = index.split(',').map((i) => Number.parseInt(i.trim()) - 1);
|
|
34
|
+
return indices.map((i) => tests[i]).filter(Boolean);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const num = Number.parseInt(index);
|
|
38
|
+
if (!Number.isNaN(num) && tests[num - 1]) {
|
|
39
|
+
return [tests[num - 1]];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return tests.filter((t) => t.status === 'pending');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createApiCommands(name = 'api'): Command {
|
|
46
|
+
const cmd = new Command(name);
|
|
47
|
+
cmd.description('AI-powered API testing tool');
|
|
48
|
+
|
|
49
|
+
addCommonOptions(cmd.command('plan <endpoint>').description('Generate test plan for an API endpoint').option('--style <style>', 'Planning style: basename of a file in rules/chief/styles/').option('--fresh', 'Start planning from scratch')).action(async (endpoint, options) => {
|
|
50
|
+
setPreserveConsoleLogs(true);
|
|
51
|
+
try {
|
|
52
|
+
const bot = new ApiBot(buildOptions(options));
|
|
53
|
+
await bot.start();
|
|
54
|
+
|
|
55
|
+
await bot.plan(endpoint, { style: options.style, fresh: options.fresh });
|
|
56
|
+
|
|
57
|
+
const plan = bot.getCurrentPlan();
|
|
58
|
+
if (!plan?.tests.length) {
|
|
59
|
+
console.error('No test scenarios generated.');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(`\nPlan: ${plan.title} (${plan.tests.length} tests)\n`);
|
|
64
|
+
plan.tests.forEach((test, i) => {
|
|
65
|
+
console.log(` ${i + 1}. [${test.priority}] ${test.scenario}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const savedPath = bot.savePlan();
|
|
69
|
+
if (savedPath) {
|
|
70
|
+
console.log(`\nSaved to: ${savedPath}`);
|
|
71
|
+
console.log('\nRun tests:');
|
|
72
|
+
console.log(` ${name} test ${savedPath} 1 # run first test`);
|
|
73
|
+
console.log(` ${name} test ${savedPath} 1-3 # run tests 1 to 3`);
|
|
74
|
+
console.log(` ${name} test ${savedPath} * # run all tests`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await bot.stop();
|
|
78
|
+
process.exit(0);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
addCommonOptions(cmd.command('test <planfile> [index]').description('Execute tests from a plan file. Index: 1, 1-3, *')).action(async (planfile, index, options) => {
|
|
86
|
+
setPreserveConsoleLogs(true);
|
|
87
|
+
try {
|
|
88
|
+
const bot = new ApiBot(buildOptions(options));
|
|
89
|
+
await bot.start();
|
|
90
|
+
|
|
91
|
+
const plan = bot.loadPlan(planfile);
|
|
92
|
+
console.log(`Plan loaded: "${plan.title}" (${plan.tests.length} tests)`);
|
|
93
|
+
|
|
94
|
+
const tests = selectTests(plan.tests, index);
|
|
95
|
+
console.log(`Running ${tests.length} test(s)\n`);
|
|
96
|
+
|
|
97
|
+
let passed = 0;
|
|
98
|
+
let failed = 0;
|
|
99
|
+
|
|
100
|
+
for (const test of tests) {
|
|
101
|
+
const specDefinition = bot.tryGetEndpointDefinition(test.startUrl);
|
|
102
|
+
const result = await bot.agentCurler().test(test, {
|
|
103
|
+
specDefinition,
|
|
104
|
+
baseEndpoint: bot.getConfig().api.baseEndpoint,
|
|
105
|
+
searchSpec: (query) => bot.searchSpec(query),
|
|
106
|
+
});
|
|
107
|
+
if (result.success) passed++;
|
|
108
|
+
else failed++;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
bot.savePlan();
|
|
112
|
+
|
|
113
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed out of ${tests.length}`);
|
|
114
|
+
await bot.stop();
|
|
115
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
addCommonOptions(cmd.command('explore <endpoint>').description('Full cycle: plan all styles, execute tests, re-plan')).action(async (endpoint, options) => {
|
|
123
|
+
setPreserveConsoleLogs(true);
|
|
124
|
+
try {
|
|
125
|
+
const bot = new ApiBot(buildOptions(options));
|
|
126
|
+
await bot.start();
|
|
127
|
+
|
|
128
|
+
const styles = Object.keys(getStyles());
|
|
129
|
+
let totalPassed = 0;
|
|
130
|
+
let totalFailed = 0;
|
|
131
|
+
let totalTests = 0;
|
|
132
|
+
|
|
133
|
+
for (const style of styles) {
|
|
134
|
+
console.log(`\n=== Style: ${style} ===\n`);
|
|
135
|
+
|
|
136
|
+
const plan = await bot.plan(endpoint, { style, fresh: true });
|
|
137
|
+
if (!plan?.tests.length) {
|
|
138
|
+
console.log(`No tests generated for style: ${style}`);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const pending = plan.getPendingTests();
|
|
143
|
+
for (const test of pending) {
|
|
144
|
+
const specDefinition = bot.tryGetEndpointDefinition(test.startUrl);
|
|
145
|
+
const result = await bot.agentCurler().test(test, {
|
|
146
|
+
specDefinition,
|
|
147
|
+
baseEndpoint: bot.getConfig().api.baseEndpoint,
|
|
148
|
+
searchSpec: (query) => bot.searchSpec(query),
|
|
149
|
+
});
|
|
150
|
+
totalTests++;
|
|
151
|
+
if (result.success) totalPassed++;
|
|
152
|
+
else totalFailed++;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
bot.savePlan(`${endpoint.replace(/^\//, '').replace(/[^a-zA-Z0-9]/g, '_')}_${style}.md`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('\n=== Final Results ===');
|
|
159
|
+
console.log(`Total: ${totalTests} tests, ${totalPassed} passed, ${totalFailed} failed`);
|
|
160
|
+
await bot.stop();
|
|
161
|
+
process.exit(totalFailed > 0 ? 1 : 0);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
cmd
|
|
169
|
+
.command('init')
|
|
170
|
+
.description('Initialize a new apibot project with configuration')
|
|
171
|
+
.option('-f, --force', 'Overwrite existing config file')
|
|
172
|
+
.option('-p, --path <path>', 'Working directory for initialization')
|
|
173
|
+
.action(async (options) => {
|
|
174
|
+
const originalCwd = process.cwd();
|
|
175
|
+
if (options.path) {
|
|
176
|
+
const resolvedPath = path.resolve(options.path);
|
|
177
|
+
fs.mkdirSync(resolvedPath, { recursive: true });
|
|
178
|
+
process.chdir(resolvedPath);
|
|
179
|
+
console.log(`Working in: ${resolvedPath}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const configPath = path.resolve('apibot.config.ts');
|
|
183
|
+
|
|
184
|
+
if (fs.existsSync(configPath) && !options.force) {
|
|
185
|
+
console.log(`Config file already exists: ${configPath}`);
|
|
186
|
+
console.log('Use --force to overwrite.');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const rl = await import('node:readline');
|
|
191
|
+
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
192
|
+
const ask = (q: string, fallback = ''): Promise<string> => new Promise((resolve) => iface.question(q, (a: string) => resolve(a.trim() || fallback)));
|
|
193
|
+
|
|
194
|
+
console.log('Apibot — API Testing Tool Setup\n');
|
|
195
|
+
|
|
196
|
+
const baseEndpoint = await ask('Base API endpoint (e.g., https://api.example.com/v1): ');
|
|
197
|
+
if (!baseEndpoint) {
|
|
198
|
+
console.error('Base endpoint is required.');
|
|
199
|
+
iface.close();
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const spec = await ask('OpenAPI spec file or URL (e.g., openapi.yaml or https://.../ — or press Enter to skip): ');
|
|
204
|
+
const knowledge = await ask('Describe your API (auth method, data formats, special rules — or press Enter to skip): ');
|
|
205
|
+
|
|
206
|
+
iface.close();
|
|
207
|
+
|
|
208
|
+
const specLine = spec ? `\n spec: ['${spec}'],` : '';
|
|
209
|
+
|
|
210
|
+
const configContent = `import { openai } from '@ai-sdk/openai';
|
|
211
|
+
|
|
212
|
+
export default {
|
|
213
|
+
ai: {
|
|
214
|
+
model: openai('gpt-4o'),
|
|
215
|
+
},
|
|
216
|
+
api: {
|
|
217
|
+
baseEndpoint: '${baseEndpoint}',${specLine}
|
|
218
|
+
headers: {
|
|
219
|
+
// 'Authorization': 'Bearer <token>',
|
|
220
|
+
},
|
|
221
|
+
// bootstrap: async ({ headers, baseEndpoint }) => {
|
|
222
|
+
// // Run before tests — e.g. obtain auth token
|
|
223
|
+
// // Return headers to merge: { Authorization: 'Bearer ...' }
|
|
224
|
+
// },
|
|
225
|
+
// teardown: async ({ headers, baseEndpoint }) => {
|
|
226
|
+
// // Run after tests — e.g. cleanup test data
|
|
227
|
+
// },
|
|
228
|
+
},
|
|
229
|
+
dirs: {
|
|
230
|
+
output: 'output',
|
|
231
|
+
knowledge: 'knowledge',
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
`;
|
|
235
|
+
|
|
236
|
+
fs.writeFileSync(configPath, configContent, 'utf8');
|
|
237
|
+
console.log(`\nCreated: ${configPath}`);
|
|
238
|
+
|
|
239
|
+
fs.mkdirSync('output', { recursive: true });
|
|
240
|
+
fs.mkdirSync('knowledge', { recursive: true });
|
|
241
|
+
|
|
242
|
+
if (knowledge) {
|
|
243
|
+
const knowledgePath = path.resolve('knowledge', 'general.md');
|
|
244
|
+
fs.writeFileSync(knowledgePath, `---\nendpoint: "*"\n---\n${knowledge}\n`, 'utf8');
|
|
245
|
+
console.log(`Created: ${knowledgePath}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log('\nNext steps:');
|
|
249
|
+
console.log('1. Edit apibot.config.ts — set your AI provider and API headers');
|
|
250
|
+
console.log(`2. Add API knowledge: ${name} know /users "CRUD endpoint for user management"`);
|
|
251
|
+
console.log(`3. Plan tests: ${name} plan /users`);
|
|
252
|
+
|
|
253
|
+
if (process.cwd() !== originalCwd) process.chdir(originalCwd);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
cmd
|
|
257
|
+
.command('know <endpoint> [description]')
|
|
258
|
+
.alias('add-knowledge')
|
|
259
|
+
.description('Add API knowledge for an endpoint')
|
|
260
|
+
.option('-c, --config <path>', 'Path to configuration file')
|
|
261
|
+
.option('-p, --path <path>', 'Working directory path')
|
|
262
|
+
.action(async (endpoint, description, options) => {
|
|
263
|
+
if (!description) {
|
|
264
|
+
const rl = await import('node:readline');
|
|
265
|
+
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
266
|
+
description = await new Promise<string>((resolve) => iface.question(`Describe ${endpoint}: `, (a: string) => resolve(a.trim())));
|
|
267
|
+
iface.close();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!description) {
|
|
271
|
+
console.error('Description is required.');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let knowledgeDir = 'knowledge';
|
|
276
|
+
try {
|
|
277
|
+
const { ApibotConfigParser } = await import('./config.ts');
|
|
278
|
+
await ApibotConfigParser.getInstance().loadConfig({ config: options.config, path: options.path });
|
|
279
|
+
knowledgeDir = ApibotConfigParser.getInstance().getKnowledgeDir();
|
|
280
|
+
} catch {
|
|
281
|
+
if (options.path) knowledgeDir = path.join(path.resolve(options.path), 'knowledge');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
285
|
+
|
|
286
|
+
const filename = endpoint.replace(/^\//, '').replace(/[^a-zA-Z0-9]/g, '_') || 'general';
|
|
287
|
+
const filePath = path.join(knowledgeDir, `${filename}.md`);
|
|
288
|
+
|
|
289
|
+
const content = `---\nendpoint: "${endpoint}"\n---\n${description}\n`;
|
|
290
|
+
|
|
291
|
+
if (fs.existsSync(filePath)) {
|
|
292
|
+
fs.appendFileSync(filePath, `\n---\n${description}\n`, 'utf8');
|
|
293
|
+
console.log(`Updated: ${filePath}`);
|
|
294
|
+
} else {
|
|
295
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
296
|
+
console.log(`Created: ${filePath}`);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return cmd;
|
|
301
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path, { resolve } from 'node:path';
|
|
3
|
+
import { parseEnv } from 'node:util';
|
|
4
|
+
import { EXPLORBOT_CONFIG_PATHS, type AIConfig, type ApiConfig as BaseApiConfig, type ApiHookFn } from '../../../src/config.ts';
|
|
5
|
+
|
|
6
|
+
export type { AIConfig };
|
|
7
|
+
|
|
8
|
+
type HookFn = ApiHookFn;
|
|
9
|
+
|
|
10
|
+
interface ApiConfig extends BaseApiConfig {
|
|
11
|
+
baseEndpoint: string;
|
|
12
|
+
specs?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ApibotConfig {
|
|
16
|
+
ai: AIConfig;
|
|
17
|
+
api: ApiConfig;
|
|
18
|
+
dirs?: {
|
|
19
|
+
output: string;
|
|
20
|
+
knowledge?: string;
|
|
21
|
+
styles?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ApibotConfigParser {
|
|
26
|
+
private static instance: ApibotConfigParser;
|
|
27
|
+
private config: ApibotConfig | null = null;
|
|
28
|
+
private configPath: string | null = null;
|
|
29
|
+
|
|
30
|
+
private constructor() {}
|
|
31
|
+
|
|
32
|
+
static getInstance(): ApibotConfigParser {
|
|
33
|
+
if (!ApibotConfigParser.instance) {
|
|
34
|
+
ApibotConfigParser.instance = new ApibotConfigParser();
|
|
35
|
+
}
|
|
36
|
+
return ApibotConfigParser.instance;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static loadEnv(filePath: string): void {
|
|
40
|
+
const resolved = resolve(filePath);
|
|
41
|
+
if (!existsSync(resolved)) return;
|
|
42
|
+
Object.assign(process.env, parseEnv(readFileSync(resolved, 'utf8')));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async loadConfig(options?: { config?: string; path?: string }): Promise<ApibotConfig> {
|
|
46
|
+
if (this.config && !options?.config && !options?.path) return this.config;
|
|
47
|
+
|
|
48
|
+
const originalCwd = process.cwd();
|
|
49
|
+
if (options?.path) {
|
|
50
|
+
process.chdir(resolve(options.path));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ApibotConfigParser.loadEnv('.env');
|
|
54
|
+
|
|
55
|
+
const resolvedPath = options?.config || this.findConfigFile();
|
|
56
|
+
if (!resolvedPath) {
|
|
57
|
+
if (options?.path) process.chdir(originalCwd);
|
|
58
|
+
throw new Error('No configuration file found. Create apibot.config.js or apibot.config.ts');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const configModule = await this.loadConfigModule(resolvedPath);
|
|
63
|
+
let loadedConfig = configModule.default || configModule;
|
|
64
|
+
|
|
65
|
+
if (!loadedConfig) {
|
|
66
|
+
throw new Error('Configuration file is empty or invalid');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (loadedConfig.playwright || loadedConfig.web) {
|
|
70
|
+
loadedConfig = {
|
|
71
|
+
ai: loadedConfig.ai,
|
|
72
|
+
api: loadedConfig.api || {},
|
|
73
|
+
dirs: loadedConfig.dirs,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.config = this.mergeWithDefaults(loadedConfig);
|
|
78
|
+
this.configPath = resolvedPath;
|
|
79
|
+
this.validateConfig(this.config);
|
|
80
|
+
|
|
81
|
+
return this.config;
|
|
82
|
+
} finally {
|
|
83
|
+
if (options?.path && originalCwd !== process.cwd()) {
|
|
84
|
+
process.chdir(originalCwd);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getConfig(): ApibotConfig {
|
|
90
|
+
if (!this.config) throw new Error('Configuration not loaded. Call loadConfig() first.');
|
|
91
|
+
return this.config;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getConfigPath(): string | null {
|
|
95
|
+
return this.configPath;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getOutputDir(): string {
|
|
99
|
+
const config = this.getConfig();
|
|
100
|
+
const configPath = this.getConfigPath();
|
|
101
|
+
if (!configPath) throw new Error('Config path not found');
|
|
102
|
+
return path.join(path.dirname(configPath), config.dirs?.output || 'output');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getPlansDir(): string {
|
|
106
|
+
return path.join(this.getOutputDir(), 'plans');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getRequestsDir(): string {
|
|
110
|
+
return path.join(this.getOutputDir(), 'requests');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getKnowledgeDir(): string {
|
|
114
|
+
const config = this.getConfig();
|
|
115
|
+
const configPath = this.getConfigPath();
|
|
116
|
+
if (!configPath) throw new Error('Config path not found');
|
|
117
|
+
return path.join(path.dirname(configPath), config.dirs?.knowledge || 'knowledge');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
ensureDirectory(dirPath: string): void {
|
|
121
|
+
if (!existsSync(dirPath)) {
|
|
122
|
+
mkdirSync(dirPath, { recursive: true });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private findConfigFile(): string | null {
|
|
127
|
+
const apibotPaths = ['apibot.config.js', 'apibot.config.mjs', 'apibot.config.ts'];
|
|
128
|
+
for (const p of apibotPaths) {
|
|
129
|
+
const fullPath = resolve(process.cwd(), p);
|
|
130
|
+
if (existsSync(fullPath)) return fullPath;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const p of EXPLORBOT_CONFIG_PATHS) {
|
|
134
|
+
const fullPath = resolve(process.cwd(), p);
|
|
135
|
+
if (existsSync(fullPath)) return fullPath;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private async loadConfigModule(configPath: string): Promise<any> {
|
|
142
|
+
const ext = configPath.split('.').pop();
|
|
143
|
+
|
|
144
|
+
if (ext === 'ts') {
|
|
145
|
+
try {
|
|
146
|
+
return await import(configPath);
|
|
147
|
+
} catch {
|
|
148
|
+
const require = (await import('node:module')).createRequire(import.meta.url);
|
|
149
|
+
return require(configPath);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (ext === 'js' || ext === 'mjs') {
|
|
154
|
+
return await import(configPath);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const content = readFileSync(configPath, 'utf8');
|
|
158
|
+
return JSON.parse(content);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private validateConfig(config: ApibotConfig): void {
|
|
162
|
+
if (!config.ai?.model) {
|
|
163
|
+
throw new Error('Missing required configuration: ai.model');
|
|
164
|
+
}
|
|
165
|
+
if (!config.api?.baseEndpoint) {
|
|
166
|
+
throw new Error('Missing required configuration: api.baseEndpoint');
|
|
167
|
+
}
|
|
168
|
+
if (config.api.specs && !config.api.spec) {
|
|
169
|
+
config.api.spec = config.api.specs;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private mergeWithDefaults(config: Partial<ApibotConfig>): ApibotConfig {
|
|
174
|
+
return this.deepMerge({ dirs: { output: 'output' } }, config);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private deepMerge(target: any, source: any): any {
|
|
178
|
+
const result = { ...target };
|
|
179
|
+
for (const key in source) {
|
|
180
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) && source[key].constructor === Object) {
|
|
181
|
+
result[key] = this.deepMerge(result[key] || {}, source[key]);
|
|
182
|
+
} else {
|
|
183
|
+
result[key] = source[key];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export type { ApibotConfig, ApiConfig, HookFn };
|
|
@@ -9,12 +9,14 @@ import { StatusPane } from '../src/components/StatusPane.js';
|
|
|
9
9
|
import { ConfigParser } from '../src/config.js';
|
|
10
10
|
import { ExplorBot } from '../src/explorbot.js';
|
|
11
11
|
import { Stats } from '../src/stats.js';
|
|
12
|
-
import { log, setPreserveConsoleLogs } from '../src/utils/logger.js';
|
|
13
|
-
import { parseMarkdownToTerminal } from '../src/utils/markdown-terminal.js';
|
|
14
12
|
import { Plan } from '../src/test-plan.js';
|
|
13
|
+
import { getCliName } from "../src/utils/cli-name.js";
|
|
14
|
+
import { log, setPreserveConsoleLogs } from '../src/utils/logger.js';
|
|
15
15
|
import { jsonToTable } from '../src/utils/markdown-parser.js';
|
|
16
|
+
import { parseMarkdownToTerminal } from '../src/utils/markdown-terminal.js';
|
|
16
17
|
const program = new Command();
|
|
17
|
-
|
|
18
|
+
const cli = getCliName();
|
|
19
|
+
program.name(cli).description('AI-powered web exploration tool');
|
|
18
20
|
function buildExplorBotOptions(from, options) {
|
|
19
21
|
return {
|
|
20
22
|
from,
|
|
@@ -100,10 +102,11 @@ addCommonOptions(program.command('explore <path>').description('Start web explor
|
|
|
100
102
|
await showStatsAndExit(1);
|
|
101
103
|
}
|
|
102
104
|
});
|
|
103
|
-
addCommonOptions(program.command('plan <path>
|
|
105
|
+
addCommonOptions(program.command('plan <path>').description('Generate test plan for a page and exit'))
|
|
104
106
|
.option('-a, --append', 'Add tests to existing plan file')
|
|
105
107
|
.option('--style <style>', 'Planning style: normal, curious, psycho')
|
|
106
|
-
.
|
|
108
|
+
.option('--focus <feature>', 'Focus area for test planning')
|
|
109
|
+
.action(async (planPath, options) => {
|
|
107
110
|
try {
|
|
108
111
|
const explorBot = new ExplorBot(buildExplorBotOptions(planPath, options));
|
|
109
112
|
await explorBot.start();
|
|
@@ -115,7 +118,7 @@ addCommonOptions(program.command('plan <path> [feature]').description('Generate
|
|
|
115
118
|
explorBot.loadPlan(existingPlanPath);
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
|
-
await explorBot.plan(
|
|
121
|
+
await explorBot.plan(options.focus || undefined, {
|
|
119
122
|
fresh: !options.append,
|
|
120
123
|
style: options.style,
|
|
121
124
|
});
|
|
@@ -131,9 +134,9 @@ addCommonOptions(program.command('plan <path> [feature]').description('Generate
|
|
|
131
134
|
const cliSuffix = cliFlags ? ` ${cliFlags}` : '';
|
|
132
135
|
const lines = [];
|
|
133
136
|
lines.push('Run tests:');
|
|
134
|
-
lines.push(
|
|
135
|
-
lines.push(
|
|
136
|
-
lines.push(
|
|
137
|
+
lines.push(`\`${cli} test ${planFile} 1${cliSuffix}\` → run first test`);
|
|
138
|
+
lines.push(`\`${cli} test ${planFile} 1-3${cliSuffix}\` → run tests 1 to 3`);
|
|
139
|
+
lines.push(`\`${cli} test ${planFile} *${cliSuffix}\` → run all tests`);
|
|
137
140
|
log(parseMarkdownToTerminal(lines.join('\n')));
|
|
138
141
|
await explorBot.stop();
|
|
139
142
|
await showStatsAndExit(0);
|
|
@@ -178,7 +181,7 @@ addCommonOptions(program.command('plan:load <planfile> [index]').description('Lo
|
|
|
178
181
|
lines.push(`- ${exp}`);
|
|
179
182
|
}
|
|
180
183
|
lines.push('');
|
|
181
|
-
lines.push(`Run:
|
|
184
|
+
lines.push(`Run: \`${cli} test ${planFile} ${idx}\``);
|
|
182
185
|
console.log(parseMarkdownToTerminal(lines.join('\n')));
|
|
183
186
|
return;
|
|
184
187
|
}
|
|
@@ -196,11 +199,11 @@ addCommonOptions(program.command('plan:load <planfile> [index]').description('Lo
|
|
|
196
199
|
}));
|
|
197
200
|
lines.push(jsonToTable(rows, ['#', 'Priority', 'Title', 'Steps', 'Expected']));
|
|
198
201
|
lines.push('View test details:');
|
|
199
|
-
lines.push(
|
|
202
|
+
lines.push(`\`${cli} plan:load ${planFile} <index>\`\n`);
|
|
200
203
|
lines.push('Run tests:');
|
|
201
|
-
lines.push(
|
|
202
|
-
lines.push(
|
|
203
|
-
lines.push(
|
|
204
|
+
lines.push(`\`${cli} test ${planFile} 1\` → run first test`);
|
|
205
|
+
lines.push(`\`${cli} test ${planFile} 1-3\` → run tests 1 to 3`);
|
|
206
|
+
lines.push(`\`${cli} test ${planFile} *\` → run all tests`);
|
|
204
207
|
console.log(parseMarkdownToTerminal(lines.join('\n')));
|
|
205
208
|
}
|
|
206
209
|
catch (error) {
|
|
@@ -258,92 +261,12 @@ program
|
|
|
258
261
|
.option('-f, --force', 'Overwrite existing config file')
|
|
259
262
|
.option('-p, --path <path>', 'Working directory for initialization')
|
|
260
263
|
.action(async (options) => {
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
268
|
-
fs.mkdirSync(resolvedPath, { recursive: true });
|
|
269
|
-
log(`Created directory: ${resolvedPath}`);
|
|
270
|
-
}
|
|
271
|
-
process.chdir(resolvedPath);
|
|
272
|
-
log(`Working in directory: ${resolvedPath}`);
|
|
273
|
-
}
|
|
274
|
-
const defaultConfig = `import { '<your provider here>' } from '<your provider package here>';
|
|
275
|
-
|
|
276
|
-
// This example uses OpenRouter (one API key, many providers). Any Vercel AI SDK provider works; see
|
|
277
|
-
// https://github.com/testomatio/explorbot/blob/main/docs/providers.md
|
|
278
|
-
const openrouter = createOpenRouter({
|
|
279
|
-
apiKey: process.env.OPENROUTER_API_KEY,
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
const config = {
|
|
283
|
-
web: {
|
|
284
|
-
url: 'http://localhost:3000',
|
|
285
|
-
},
|
|
286
|
-
|
|
287
|
-
ai: {
|
|
288
|
-
model: '<your model here>',
|
|
289
|
-
visionModel: '<your vision model here>',
|
|
290
|
-
agenticModel: '<your agentic model here>',
|
|
291
|
-
},
|
|
292
|
-
|
|
293
|
-
reporter: {
|
|
294
|
-
enabled: true,
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
// api: {
|
|
298
|
-
// baseEndpoint: 'http://localhost:3000/api',
|
|
299
|
-
// spec: ['http://localhost:3000/api/openapi.json'],
|
|
300
|
-
// headers: {
|
|
301
|
-
// 'Authorization': 'Bearer <token>',
|
|
302
|
-
// },
|
|
303
|
-
// },
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
export default config;
|
|
307
|
-
`;
|
|
308
|
-
try {
|
|
309
|
-
let resolvedPath = path.resolve(configPath);
|
|
310
|
-
if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) {
|
|
311
|
-
resolvedPath = path.join(resolvedPath, 'explorbot.config.js');
|
|
312
|
-
}
|
|
313
|
-
else if (!path.extname(resolvedPath)) {
|
|
314
|
-
resolvedPath = path.join(resolvedPath, 'explorbot.config.js');
|
|
315
|
-
}
|
|
316
|
-
const dir = path.dirname(resolvedPath);
|
|
317
|
-
if (!fs.existsSync(dir)) {
|
|
318
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
319
|
-
log(`Created directory: ${dir}`);
|
|
320
|
-
}
|
|
321
|
-
if (fs.existsSync(resolvedPath) && !force) {
|
|
322
|
-
log(`Config file already exists: ${resolvedPath}`);
|
|
323
|
-
log('Use --force to overwrite existing file');
|
|
324
|
-
process.exit(1);
|
|
325
|
-
}
|
|
326
|
-
fs.writeFileSync(resolvedPath, defaultConfig, 'utf8');
|
|
327
|
-
log(`Created config file: ${resolvedPath}`);
|
|
328
|
-
log('');
|
|
329
|
-
log('Next steps:');
|
|
330
|
-
log('1. Set your API key in the config file or as environment variable');
|
|
331
|
-
log('2. Customize the configuration as needed');
|
|
332
|
-
log('3. Run: explorbot start');
|
|
333
|
-
if (!fs.existsSync('./output')) {
|
|
334
|
-
fs.mkdirSync('./output', { recursive: true });
|
|
335
|
-
log('Created directory: ./output');
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
catch (error) {
|
|
339
|
-
log('Failed to create config file:', error);
|
|
340
|
-
process.exit(1);
|
|
341
|
-
}
|
|
342
|
-
finally {
|
|
343
|
-
if (process.cwd() !== originalCwd) {
|
|
344
|
-
process.chdir(originalCwd);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
264
|
+
const { runInitCommand } = await import('../src/commands/init-command.js');
|
|
265
|
+
runInitCommand({
|
|
266
|
+
configPath: options.configPath,
|
|
267
|
+
force: options.force,
|
|
268
|
+
path: options.path,
|
|
269
|
+
});
|
|
347
270
|
});
|
|
348
271
|
program
|
|
349
272
|
.command('clean [target]')
|
|
@@ -680,4 +603,3 @@ program
|
|
|
680
603
|
import { createApiCommands } from "../boat/api-tester/src/cli.js";
|
|
681
604
|
program.addCommand(createApiCommands('api'));
|
|
682
605
|
program.parse();
|
|
683
|
-
//# sourceMappingURL=explorbot-cli.js.map
|