testdriverai 7.2.64 → 7.2.65
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/agent/index.js +4 -3
- package/agent/interface.js +11 -251
- package/agent/lib/debugger-server.js +2 -1
- package/agent/lib/logger.js +56 -0
- package/agent/lib/sandbox.js +6 -7
- package/ai/agents/test-writer.md +457 -0
- package/{docs/v7/ai.mdx → ai/skills/testdriver:ai/SKILL.md} +3 -5
- package/{docs/v7/assert.mdx → ai/skills/testdriver:assert/SKILL.md} +3 -4
- package/{docs/v7/aws-setup.mdx → ai/skills/testdriver:aws-setup/SKILL.md} +3 -4
- package/{docs/v7/caching.mdx → ai/skills/testdriver:caching/SKILL.md} +3 -7
- package/{docs/v7/captcha.mdx → ai/skills/testdriver:captcha/SKILL.md} +4 -5
- package/{docs/v7/ci-cd.mdx → ai/skills/testdriver:ci-cd/SKILL.md} +3 -4
- package/{docs/v7/click.mdx → ai/skills/testdriver:click/SKILL.md} +3 -4
- package/{docs/v7/client.mdx → ai/skills/testdriver:client/SKILL.md} +11 -5
- package/{docs/v7/cloud.mdx → ai/skills/testdriver:cloud/SKILL.md} +3 -4
- package/{docs/v7/customizing-devices.mdx → ai/skills/testdriver:customizing-devices/SKILL.md} +36 -26
- package/{docs/v7/dashcam.mdx → ai/skills/testdriver:dashcam/SKILL.md} +3 -4
- package/{docs/v7/device-config.mdx → ai/skills/testdriver:device-config/SKILL.md} +3 -3
- package/{docs/v7/double-click.mdx → ai/skills/testdriver:double-click/SKILL.md} +3 -3
- package/{docs/v7/elements.mdx → ai/skills/testdriver:elements/SKILL.md} +3 -4
- package/{docs/v7/enterprise.mdx → ai/skills/testdriver:enterprise/SKILL.md} +3 -5
- package/ai/skills/testdriver:examples/SKILL.md +7 -0
- package/{docs/v7/exec.mdx → ai/skills/testdriver:exec/SKILL.md} +3 -4
- package/{docs/v7/find.mdx → ai/skills/testdriver:find/SKILL.md} +81 -4
- package/{docs/v7/focus-application.mdx → ai/skills/testdriver:focus-application/SKILL.md} +3 -4
- package/{docs/v7/generating-tests.mdx → ai/skills/testdriver:generating-tests/SKILL.md} +3 -3
- package/{docs/v7/hover.mdx → ai/skills/testdriver:hover/SKILL.md} +3 -4
- package/{docs/v7/locating-elements.mdx → ai/skills/testdriver:locating-elements/SKILL.md} +3 -3
- package/{docs/v7/making-assertions.mdx → ai/skills/testdriver:making-assertions/SKILL.md} +3 -3
- package/ai/skills/testdriver:mcp-workflow/SKILL.md +410 -0
- package/{docs/v7/mouse-down.mdx → ai/skills/testdriver:mouse-down/SKILL.md} +3 -3
- package/{docs/v7/mouse-up.mdx → ai/skills/testdriver:mouse-up/SKILL.md} +3 -3
- package/{docs/v7/performing-actions.mdx → ai/skills/testdriver:performing-actions/SKILL.md} +3 -3
- package/{docs/v7/press-keys.mdx → ai/skills/testdriver:press-keys/SKILL.md} +3 -4
- package/{docs/v7/quickstart.mdx → ai/skills/testdriver:quickstart/SKILL.md} +3 -4
- package/{docs/v7/reusable-code.mdx → ai/skills/testdriver:reusable-code/SKILL.md} +3 -3
- package/{docs/v7/right-click.mdx → ai/skills/testdriver:right-click/SKILL.md} +3 -3
- package/{docs/v7/running-tests.mdx → ai/skills/testdriver:running-tests/SKILL.md} +3 -3
- package/{docs/v7/screenshot.mdx → ai/skills/testdriver:screenshot/SKILL.md} +3 -4
- package/{docs/v7/scroll.mdx → ai/skills/testdriver:scroll/SKILL.md} +3 -4
- package/{docs/v7/secrets.mdx → ai/skills/testdriver:secrets/SKILL.md} +3 -3
- package/{docs/v7/self-hosted.mdx → ai/skills/testdriver:self-hosted/SKILL.md} +3 -4
- package/ai/skills/testdriver:testdriver/SKILL.md +31 -0
- package/{docs/v7/type.mdx → ai/skills/testdriver:type/SKILL.md} +3 -4
- package/{docs/v7/variables.mdx → ai/skills/testdriver:variables/SKILL.md} +3 -3
- package/{docs/v7/waiting-for-elements.mdx → ai/skills/testdriver:waiting-for-elements/SKILL.md} +3 -3
- package/{docs/v7/what-is-testdriver.mdx → ai/skills/testdriver:what-is-testdriver/SKILL.md} +3 -3
- package/interfaces/cli/commands/init.js +278 -1
- package/interfaces/cli/commands/setup.js +382 -0
- package/interfaces/vitest-plugin.mjs +190 -122
- package/lib/sentry.js +4 -3
- package/lib/vitest/hooks.mjs +70 -16
- package/package.json +29 -9
- package/sdk.d.ts +29 -2
- package/sdk.js +1 -0
- package/.env.example +0 -4
- package/.github/workflows/acceptance-linux-scheduled.yaml +0 -45
- package/.github/workflows/acceptance-windows-scheduled.yaml +0 -54
- package/.github/workflows/acceptance.yaml +0 -87
- package/.github/workflows/publish.yaml +0 -68
- package/.github/workflows/test-init.yml +0 -145
- package/.github/workflows/testdriver.yml +0 -170
- package/.github/workflows/windows-self-hosted.yaml +0 -82
- package/.prettierignore +0 -4
- package/.prettierrc +0 -1
- package/CHANGELOG.md +0 -34
- package/agents.md +0 -455
- package/debugger/bg.png +0 -0
- package/debugger/icon.png +0 -0
- package/debugger/index.html +0 -797
- package/debugger/td.png +0 -0
- package/debugger/tray-buffered.png +0 -0
- package/debugger/tray.png +0 -0
- package/docs/GITHUB_COMMENTS.md +0 -330
- package/docs/GITHUB_COMMENTS_ANNOUNCEMENT.md +0 -167
- package/docs/QUICK-START-GITHUB-COMMENTS.md +0 -84
- package/docs/TEST-GITHUB-COMMENTS.md +0 -129
- package/docs/_scripts/link-replacer.js +0 -164
- package/docs/_scripts/upload-docs-to-openai.js +0 -284
- package/docs/docs.json +0 -393
- package/docs/github-integration-setup.md +0 -266
- package/docs/guide/best-practices-polling.mdx +0 -154
- package/docs/images/content/account/newprojectsettings.png +0 -0
- package/docs/images/content/account/projectpage.png +0 -0
- package/docs/images/content/account/projectreplays.png +0 -0
- package/docs/images/content/account/team-manage.png +0 -0
- package/docs/images/content/account/teampage.png +0 -0
- package/docs/images/content/extension/cursor.svg +0 -1
- package/docs/images/content/extension/vscode.svg +0 -57
- package/docs/images/content/extension/windsurf.svg +0 -3
- package/docs/images/content/self-hosted/launchtemplateid.png +0 -0
- package/docs/images/content/side-by-side.png +0 -0
- package/docs/images/content/vscode/ide-full.png +0 -0
- package/docs/images/content/vscode/running.png +0 -0
- package/docs/images/content/vscode/vscode-2-assert.png +0 -0
- package/docs/images/content/vscode/vscode-agent-preview.png +0 -0
- package/docs/images/content/vscode/vscode-copilot-ask.png +0 -0
- package/docs/images/content/vscode/vscode-file-creation.png +0 -0
- package/docs/images/content/vscode/vscode-install.png +0 -0
- package/docs/images/content/vscode/vscode-overview.png +0 -0
- package/docs/images/content/vscode/vscode-setup-walkthrough.png +0 -0
- package/docs/images/content/vscode/vscode-stopchat.png +0 -0
- package/docs/images/content/vscode/vscode-stoptest.png +0 -0
- package/docs/images/content/vscode/vscode-tdservice.png +0 -0
- package/docs/images/content/vscode/vscode-test-output.png +0 -0
- package/docs/images/content/vscode/vscode-testhistory.png +0 -0
- package/docs/images/content/vscode/vscode-testpane-runtests.png +0 -0
- package/docs/images/content/vscode/vscode-testpane.png +0 -0
- package/docs/images/template/dark.png +0 -0
- package/docs/images/template/icon.png +0 -0
- package/docs/images/template/light.png +0 -0
- package/docs/snippets/calendar-link.mdx +0 -4
- package/docs/snippets/gitignore-warning.mdx +0 -7
- package/docs/snippets/lifecycle-warning.mdx +0 -6
- package/docs/snippets/test-prereqs.mdx +0 -12
- package/docs/snippets/tests/assert-replay.mdx +0 -7
- package/docs/snippets/tests/assert-yaml.mdx +0 -8
- package/docs/snippets/tests/exec-js-replay.mdx +0 -7
- package/docs/snippets/tests/exec-js-yaml.mdx +0 -32
- package/docs/snippets/tests/exec-shell-replay.mdx +0 -7
- package/docs/snippets/tests/exec-shell-yaml.mdx +0 -15
- package/docs/snippets/tests/hover-image-replay.mdx +0 -7
- package/docs/snippets/tests/hover-image-yaml.mdx +0 -17
- package/docs/snippets/tests/hover-text-replay.mdx +0 -7
- package/docs/snippets/tests/hover-text-with-description-replay.mdx +0 -7
- package/docs/snippets/tests/hover-text-with-description-yaml.mdx +0 -24
- package/docs/snippets/tests/hover-text-yaml.mdx +0 -14
- package/docs/snippets/tests/match-image-replay.mdx +0 -7
- package/docs/snippets/tests/match-image-yaml.mdx +0 -17
- package/docs/snippets/tests/press-keys-replay.mdx +0 -7
- package/docs/snippets/tests/press-keys-yaml.mdx +0 -36
- package/docs/snippets/tests/remember-replay.mdx +0 -7
- package/docs/snippets/tests/remember-yaml.mdx +0 -28
- package/docs/snippets/tests/scroll-replay.mdx +0 -7
- package/docs/snippets/tests/scroll-until-image-replay.mdx +0 -7
- package/docs/snippets/tests/scroll-until-image-yaml.mdx +0 -14
- package/docs/snippets/tests/scroll-until-text-replay.mdx +0 -7
- package/docs/snippets/tests/scroll-until-text-yaml.mdx +0 -17
- package/docs/snippets/tests/scroll-yaml.mdx +0 -30
- package/docs/snippets/tests/type-repeated-replay.mdx +0 -7
- package/docs/snippets/tests/type-repeated-yaml.mdx +0 -22
- package/docs/snippets/tests/type-replay.mdx +0 -7
- package/docs/snippets/tests/type-yaml.mdx +0 -28
- package/docs/snippets/tests/wait-for-image-replay.mdx +0 -7
- package/docs/snippets/tests/wait-for-image-yaml.mdx +0 -18
- package/docs/snippets/tests/wait-for-text-replay.mdx +0 -7
- package/docs/snippets/tests/wait-for-text-yaml.mdx +0 -18
- package/docs/snippets/tests/wait-replay.mdx +0 -7
- package/docs/snippets/tests/wait-yaml.mdx +0 -13
- package/docs/styles.css +0 -65
- package/docs/v6/account/dashboard.mdx +0 -16
- package/docs/v6/account/enterprise.mdx +0 -110
- package/docs/v6/account/pricing.mdx +0 -33
- package/docs/v6/account/projects.mdx +0 -33
- package/docs/v6/account/team.mdx +0 -35
- package/docs/v6/action/ami.mdx +0 -109
- package/docs/v6/action/performance.mdx +0 -105
- package/docs/v6/action/secrets.mdx +0 -93
- package/docs/v6/apps/chrome-extensions.mdx +0 -48
- package/docs/v6/apps/desktop-apps.mdx +0 -93
- package/docs/v6/apps/mobile-apps.mdx +0 -26
- package/docs/v6/apps/static-websites.mdx +0 -54
- package/docs/v6/apps/tauri-apps.mdx +0 -361
- package/docs/v6/bugs/jira.mdx +0 -232
- package/docs/v6/cli/overview.mdx +0 -66
- package/docs/v6/commands/assert.mdx +0 -45
- package/docs/v6/commands/exec.mdx +0 -282
- package/docs/v6/commands/focus-application.mdx +0 -44
- package/docs/v6/commands/hover-image.mdx +0 -69
- package/docs/v6/commands/hover-text.mdx +0 -47
- package/docs/v6/commands/if.mdx +0 -53
- package/docs/v6/commands/match-image.mdx +0 -67
- package/docs/v6/commands/press-keys.mdx +0 -87
- package/docs/v6/commands/remember.mdx +0 -49
- package/docs/v6/commands/run.mdx +0 -44
- package/docs/v6/commands/scroll-until-image.mdx +0 -66
- package/docs/v6/commands/scroll-until-text.mdx +0 -60
- package/docs/v6/commands/scroll.mdx +0 -69
- package/docs/v6/commands/type.mdx +0 -45
- package/docs/v6/commands/wait-for-image.mdx +0 -54
- package/docs/v6/commands/wait-for-text.mdx +0 -48
- package/docs/v6/commands/wait.mdx +0 -45
- package/docs/v6/exporting/junit.mdx +0 -218
- package/docs/v6/exporting/playwright.mdx +0 -197
- package/docs/v6/features/auto-healing.mdx +0 -144
- package/docs/v6/features/generation.mdx +0 -116
- package/docs/v6/features/parallel-testing.mdx +0 -151
- package/docs/v6/features/reusable-snippets.mdx +0 -131
- package/docs/v6/features/selectorless.mdx +0 -80
- package/docs/v6/features/visual-assertions.mdx +0 -139
- package/docs/v6/getting-started/ci.mdx +0 -146
- package/docs/v6/getting-started/cli.mdx +0 -91
- package/docs/v6/getting-started/editing.mdx +0 -100
- package/docs/v6/getting-started/playwright.mdx +0 -342
- package/docs/v6/getting-started/running.mdx +0 -48
- package/docs/v6/getting-started/self-hosting.mdx +0 -408
- package/docs/v6/getting-started/vscode.mdx +0 -89
- package/docs/v6/guide/assertions.mdx +0 -189
- package/docs/v6/guide/authentication.mdx +0 -136
- package/docs/v6/guide/code.mdx +0 -65
- package/docs/v6/guide/dashcam.mdx +0 -118
- package/docs/v6/guide/environment-variables.mdx +0 -26
- package/docs/v6/guide/lifecycle.mdx +0 -242
- package/docs/v6/guide/locating.mdx +0 -141
- package/docs/v6/guide/protips.mdx +0 -43
- package/docs/v6/guide/variables.mdx +0 -143
- package/docs/v6/guide/waiting.mdx +0 -130
- package/docs/v6/importing/csv.mdx +0 -196
- package/docs/v6/importing/gherkin.mdx +0 -143
- package/docs/v6/importing/jira.mdx +0 -164
- package/docs/v6/importing/testrail.mdx +0 -162
- package/docs/v6/integrations/electron.mdx +0 -146
- package/docs/v6/integrations/netlify.mdx +0 -100
- package/docs/v6/integrations/vercel.mdx +0 -125
- package/docs/v6/interactive/explore.mdx +0 -99
- package/docs/v6/interactive/run.mdx +0 -52
- package/docs/v6/interactive/save.mdx +0 -63
- package/docs/v6/overview/comparison.mdx +0 -101
- package/docs/v6/overview/faq.mdx +0 -162
- package/docs/v6/overview/performance.mdx +0 -52
- package/docs/v6/overview/quickstart.mdx +0 -137
- package/docs/v6/overview/what-is-testdriver.mdx +0 -85
- package/docs/v6/scenarios/ai-chatbot.mdx +0 -28
- package/docs/v6/scenarios/cookie-banner.mdx +0 -32
- package/docs/v6/scenarios/file-upload.mdx +0 -33
- package/docs/v6/scenarios/form-filling.mdx +0 -32
- package/docs/v6/scenarios/log-in.mdx +0 -75
- package/docs/v6/scenarios/pdf-generation.mdx +0 -25
- package/docs/v6/scenarios/spell-check.mdx +0 -22
- package/docs/v6/security/action.mdx +0 -84
- package/docs/v6/security/agent.mdx +0 -73
- package/docs/v6/security/platform.mdx +0 -77
- package/docs/v6/tutorials/advanced-test.mdx +0 -81
- package/docs/v6/tutorials/basic-test.mdx +0 -45
- package/docs/v7/_drafts/agents.mdx +0 -852
- package/docs/v7/_drafts/architecture.mdx +0 -399
- package/docs/v7/_drafts/auto-cache-key.mdx +0 -167
- package/docs/v7/_drafts/awesome-logs-quick-ref.mdx +0 -100
- package/docs/v7/_drafts/best-practices.mdx +0 -486
- package/docs/v7/_drafts/caching-ai.mdx +0 -215
- package/docs/v7/_drafts/caching-selectors.mdx +0 -424
- package/docs/v7/_drafts/caching.mdx +0 -366
- package/docs/v7/_drafts/cli-to-sdk-migration.mdx +0 -425
- package/docs/v7/_drafts/commands/assert.mdx +0 -45
- package/docs/v7/_drafts/commands/exec.mdx +0 -282
- package/docs/v7/_drafts/commands/focus-application.mdx +0 -44
- package/docs/v7/_drafts/commands/hover-image.mdx +0 -69
- package/docs/v7/_drafts/commands/hover-text.mdx +0 -47
- package/docs/v7/_drafts/commands/if.mdx +0 -53
- package/docs/v7/_drafts/commands/match-image.mdx +0 -67
- package/docs/v7/_drafts/commands/press-keys.mdx +0 -87
- package/docs/v7/_drafts/commands/remember.mdx +0 -49
- package/docs/v7/_drafts/commands/run.mdx +0 -44
- package/docs/v7/_drafts/commands/scroll-until-image.mdx +0 -66
- package/docs/v7/_drafts/commands/scroll-until-text.mdx +0 -60
- package/docs/v7/_drafts/commands/scroll.mdx +0 -69
- package/docs/v7/_drafts/commands/type.mdx +0 -45
- package/docs/v7/_drafts/commands/wait-for-image.mdx +0 -54
- package/docs/v7/_drafts/commands/wait-for-text.mdx +0 -48
- package/docs/v7/_drafts/commands/wait.mdx +0 -45
- package/docs/v7/_drafts/configuration.mdx +0 -378
- package/docs/v7/_drafts/contributing.mdx +0 -174
- package/docs/v7/_drafts/core.mdx +0 -458
- package/docs/v7/_drafts/dashcam-title-feature.mdx +0 -89
- package/docs/v7/_drafts/debugging.mdx +0 -349
- package/docs/v7/_drafts/error-handling.mdx +0 -501
- package/docs/v7/_drafts/faq.mdx +0 -393
- package/docs/v7/_drafts/hooks.mdx +0 -360
- package/docs/v7/_drafts/init-command.mdx +0 -95
- package/docs/v7/_drafts/installation.mdx +0 -420
- package/docs/v7/_drafts/migration.mdx +0 -562
- package/docs/v7/_drafts/observable.mdx +0 -604
- package/docs/v7/_drafts/playwright.mdx +0 -342
- package/docs/v7/_drafts/plugin-migration.mdx +0 -220
- package/docs/v7/_drafts/powerful.mdx +0 -419
- package/docs/v7/_drafts/presets.mdx +0 -210
- package/docs/v7/_drafts/progressive-disclosure.mdx +0 -230
- package/docs/v7/_drafts/prompt-cache.mdx +0 -200
- package/docs/v7/_drafts/provision.mdx +0 -390
- package/docs/v7/_drafts/quick-start-test-recording.mdx +0 -214
- package/docs/v7/_drafts/readme.mdx +0 -135
- package/docs/v7/_drafts/reports.mdx +0 -414
- package/docs/v7/_drafts/scalable.mdx +0 -754
- package/docs/v7/_drafts/screenshot.mdx +0 -155
- package/docs/v7/_drafts/sdk-awesome-logs.mdx +0 -468
- package/docs/v7/_drafts/sdk-browser-rendering.mdx +0 -167
- package/docs/v7/_drafts/sdk-migration.mdx +0 -474
- package/docs/v7/_drafts/sdk-v7-complete.mdx +0 -345
- package/docs/v7/_drafts/self-hosting.mdx +0 -369
- package/docs/v7/_drafts/test-recording.mdx +0 -382
- package/docs/v7/_drafts/troubleshooting.mdx +0 -526
- package/docs/v7/_drafts/vitest-plugin.mdx +0 -477
- package/docs/v7/_drafts/vitest.mdx +0 -535
- package/docs/v7/_drafts/writing-tests.mdx +0 -25
- package/docs/v7/examples.mdx +0 -5
- package/eslint.config.js +0 -67
- package/examples/ai.test.mjs +0 -30
- package/examples/assert.test.mjs +0 -46
- package/examples/captcha-api.test.mjs +0 -50
- package/examples/chrome-extension.test.mjs +0 -94
- package/examples/drag-and-drop.test.mjs +0 -58
- package/examples/element-not-found.test.mjs +0 -26
- package/examples/exec-output.test.mjs +0 -59
- package/examples/exec-pwsh.test.mjs +0 -57
- package/examples/focus-window.test.mjs +0 -36
- package/examples/formatted-logging.test.mjs +0 -26
- package/examples/hover-image.test.mjs +0 -52
- package/examples/hover-text-with-description.test.mjs +0 -56
- package/examples/hover-text.test.mjs +0 -27
- package/examples/installer.test.mjs +0 -49
- package/examples/launch-vscode-linux.test.mjs +0 -54
- package/examples/match-image.test.mjs +0 -54
- package/examples/no-provision.test.mjs +0 -23
- package/examples/press-keys.test.mjs +0 -50
- package/examples/prompt.test.mjs +0 -33
- package/examples/scroll-keyboard.test.mjs +0 -37
- package/examples/scroll-until-image.test.mjs +0 -39
- package/examples/scroll-until-text.test.mjs +0 -67
- package/examples/scroll.test.mjs +0 -41
- package/examples/type.test.mjs +0 -45
- package/examples/windows-installer.test.mjs +0 -53
- package/interfaces/cli/commands/edit.js +0 -3
- package/interfaces/cli/commands/generate.js +0 -3
- package/interfaces/cli/commands/run.js +0 -3
- package/interfaces/cli/utils/factory.js +0 -71
- package/jsconfig.json +0 -26
- package/manual/test-init-command.js +0 -223
- package/sdk-log-formatter.js +0 -930
- package/setup/aws/cloudformation.yaml +0 -470
- package/setup/aws/spawn-runner.sh +0 -190
- package/test/api-resilience.test.mjs +0 -0
- package/test/captcha-solver.test.mjs +0 -70
- package/test/chrome-remote-debugging.test.mjs +0 -66
- package/test/manual/debug-locate-response.js +0 -82
- package/test/manual/reconnect-provision.test.mjs +0 -49
- package/test/manual/test-console-logs.test.mjs +0 -42
- package/test/manual/test-find-api.js +0 -73
- package/test/manual/test-init.sh +0 -54
- package/test/manual/test-prompt-cache.js +0 -96
- package/test/manual/test-provision-auth.mjs +0 -22
- package/test/manual/test-sandbox-render.js +0 -28
- package/test/manual/test-sdk-methods.js +0 -15
- package/test/manual/test-sdk-refactor.js +0 -53
- package/test/manual/test-stack-trace.mjs +0 -57
- package/test/manual/verify-element-api.js +0 -89
- package/test/manual/verify-types.js +0 -0
- package/test/manual-unawaited-promise.test.mjs +0 -31
- package/testdriver-plugin/skills/actions/SKILL.md +0 -93
- package/testdriver-plugin/skills/assertions/SKILL.md +0 -77
- package/testdriver-plugin/skills/caching/SKILL.md +0 -66
- package/testdriver-plugin/skills/creating-tests/SKILL.md +0 -104
- package/testdriver-plugin/skills/finding-elements/SKILL.md +0 -77
- package/testdriver-plugin/skills/github-actions/SKILL.md +0 -100
- package/testdriver-plugin/skills/running-tests/SKILL.md +0 -77
- package/testdriver-plugin/skills/secrets/SKILL.md +0 -87
- package/testdriver-plugin/skills/self-hosting/SKILL.md +0 -89
- package/testdriver-plugin/skills/setup/SKILL.md +0 -76
- package/testdriver-plugin/skills/variables/SKILL.md +0 -88
- package/testdriver-plugin/skills/waiting/SKILL.md +0 -72
- package/vitest.config.mjs +0 -29
|
@@ -5,6 +5,7 @@ const path = require("path");
|
|
|
5
5
|
const chalk = require("chalk");
|
|
6
6
|
const { execSync } = require("child_process");
|
|
7
7
|
const readline = require("readline");
|
|
8
|
+
const os = require("os");
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Init command - scaffolds Vitest SDK example tests for TestDriver
|
|
@@ -19,7 +20,10 @@ class InitCommand extends BaseCommand {
|
|
|
19
20
|
await this.createVitestExample();
|
|
20
21
|
await this.createGitHubWorkflow();
|
|
21
22
|
await this.createGitignore();
|
|
23
|
+
await this.createVscodeMcpConfig();
|
|
22
24
|
await this.installDependencies();
|
|
25
|
+
await this.copySkills();
|
|
26
|
+
await this.createAgents();
|
|
23
27
|
await this.promptForApiKey();
|
|
24
28
|
|
|
25
29
|
console.log(chalk.green("\n✅ Project initialized successfully!\n"));
|
|
@@ -78,7 +82,11 @@ class InitCommand extends BaseCommand {
|
|
|
78
82
|
: "";
|
|
79
83
|
|
|
80
84
|
fs.writeFileSync(envPath, envContent + `TD_API_KEY=${apiKey.trim()}\n`);
|
|
85
|
+
process.env.TD_API_KEY = apiKey.trim();
|
|
81
86
|
console.log(chalk.green("\n ✓ API key saved to .env\n"));
|
|
87
|
+
|
|
88
|
+
// Also persist to shell profile so it's available in all terminals
|
|
89
|
+
this.addToShellProfile("TD_API_KEY", apiKey.trim());
|
|
82
90
|
} else {
|
|
83
91
|
console.log(
|
|
84
92
|
chalk.yellow(
|
|
@@ -132,6 +140,63 @@ class InitCommand extends BaseCommand {
|
|
|
132
140
|
});
|
|
133
141
|
}
|
|
134
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Add an environment variable export to the user's shell profile
|
|
145
|
+
*/
|
|
146
|
+
addToShellProfile(key, value) {
|
|
147
|
+
if (process.platform === "win32") {
|
|
148
|
+
// On Windows, set a persistent user environment variable via setx
|
|
149
|
+
try {
|
|
150
|
+
execSync(`setx ${key} "${value}"`, { stdio: "ignore" });
|
|
151
|
+
console.log(
|
|
152
|
+
chalk.green(` ✓ Set ${key} as user environment variable\n`),
|
|
153
|
+
);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.log(
|
|
156
|
+
chalk.yellow(` ⚠️ Could not set ${key} via setx. You can set it manually:\n`),
|
|
157
|
+
);
|
|
158
|
+
console.log(chalk.gray(` setx ${key} "your_api_key"\n`));
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Unix: append export to shell profile
|
|
164
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
165
|
+
const home = os.homedir();
|
|
166
|
+
let profilePath;
|
|
167
|
+
|
|
168
|
+
if (shell.includes("zsh")) {
|
|
169
|
+
profilePath = path.join(home, ".zshrc");
|
|
170
|
+
} else {
|
|
171
|
+
profilePath = path.join(home, ".bashrc");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const exportLine = `export ${key}="${value}"`;
|
|
175
|
+
|
|
176
|
+
// Check if already present
|
|
177
|
+
if (fs.existsSync(profilePath)) {
|
|
178
|
+
const content = fs.readFileSync(profilePath, "utf8");
|
|
179
|
+
if (content.includes(`export ${key}=`)) {
|
|
180
|
+
// Replace existing line
|
|
181
|
+
const updated = content.replace(
|
|
182
|
+
new RegExp(`^export ${key}=.*$`, "m"),
|
|
183
|
+
exportLine,
|
|
184
|
+
);
|
|
185
|
+
fs.writeFileSync(profilePath, updated);
|
|
186
|
+
console.log(
|
|
187
|
+
chalk.green(` ✓ Updated ${key} in ${profilePath}\n`),
|
|
188
|
+
);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Append to profile
|
|
194
|
+
fs.appendFileSync(profilePath, `\n${exportLine}\n`);
|
|
195
|
+
console.log(
|
|
196
|
+
chalk.green(` ✓ Added ${key} to ${profilePath}\n`),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
135
200
|
/**
|
|
136
201
|
* Ask a yes/no question
|
|
137
202
|
*/
|
|
@@ -398,6 +463,214 @@ jobs:
|
|
|
398
463
|
}
|
|
399
464
|
}
|
|
400
465
|
|
|
466
|
+
/**
|
|
467
|
+
* Create VSCode MCP configuration
|
|
468
|
+
*/
|
|
469
|
+
async createVscodeMcpConfig() {
|
|
470
|
+
const vscodeDir = path.join(process.cwd(), ".vscode");
|
|
471
|
+
const mcpConfigFile = path.join(vscodeDir, "mcp.json");
|
|
472
|
+
|
|
473
|
+
// Create .vscode directory if it doesn't exist
|
|
474
|
+
if (!fs.existsSync(vscodeDir)) {
|
|
475
|
+
fs.mkdirSync(vscodeDir, { recursive: true });
|
|
476
|
+
console.log(chalk.gray(` Created directory: ${vscodeDir}`));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (!fs.existsSync(mcpConfigFile)) {
|
|
480
|
+
const mcpConfig = {
|
|
481
|
+
mcpServers: {
|
|
482
|
+
testdriver: {
|
|
483
|
+
command: "npx",
|
|
484
|
+
args: ["testdriverai-mcp"],
|
|
485
|
+
env: {
|
|
486
|
+
TD_API_KEY: "${TD_API_KEY}",
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
fs.writeFileSync(
|
|
493
|
+
mcpConfigFile,
|
|
494
|
+
JSON.stringify(mcpConfig, null, 2) + "\n",
|
|
495
|
+
);
|
|
496
|
+
console.log(chalk.green(` Created MCP config: ${mcpConfigFile}`));
|
|
497
|
+
} else {
|
|
498
|
+
console.log(chalk.gray(" MCP config already exists, skipping..."));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Copy TestDriver skills from the package to the project
|
|
504
|
+
*/
|
|
505
|
+
async copySkills() {
|
|
506
|
+
const skillsDestDir = path.join(process.cwd(), ".github", "skills");
|
|
507
|
+
|
|
508
|
+
// Try to find skills in node_modules
|
|
509
|
+
const possibleSkillsSources = [
|
|
510
|
+
path.join(process.cwd(), "node_modules", "testdriverai", "ai", "skills"),
|
|
511
|
+
path.join(__dirname, "..", "..", "..", "ai", "skills"),
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
let skillsSourceDir = null;
|
|
515
|
+
for (const source of possibleSkillsSources) {
|
|
516
|
+
if (fs.existsSync(source)) {
|
|
517
|
+
skillsSourceDir = source;
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!skillsSourceDir) {
|
|
523
|
+
console.log(chalk.yellow(" ⚠️ Skills directory not found, skipping skills copy..."));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Create .github/skills directory if it doesn't exist
|
|
528
|
+
if (!fs.existsSync(skillsDestDir)) {
|
|
529
|
+
fs.mkdirSync(skillsDestDir, { recursive: true });
|
|
530
|
+
console.log(chalk.gray(` Created directory: ${skillsDestDir}`));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Copy all skill directories
|
|
534
|
+
const skillDirs = fs.readdirSync(skillsSourceDir);
|
|
535
|
+
let copiedCount = 0;
|
|
536
|
+
|
|
537
|
+
for (const skillDir of skillDirs) {
|
|
538
|
+
const sourcePath = path.join(skillsSourceDir, skillDir);
|
|
539
|
+
const destPath = path.join(skillsDestDir, skillDir);
|
|
540
|
+
|
|
541
|
+
if (fs.statSync(sourcePath).isDirectory()) {
|
|
542
|
+
// Create skill directory
|
|
543
|
+
if (!fs.existsSync(destPath)) {
|
|
544
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Copy SKILL.md file
|
|
548
|
+
const skillFile = path.join(sourcePath, "SKILL.md");
|
|
549
|
+
if (fs.existsSync(skillFile)) {
|
|
550
|
+
fs.copyFileSync(skillFile, path.join(destPath, "SKILL.md"));
|
|
551
|
+
copiedCount++;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
console.log(chalk.green(` Copied ${copiedCount} skills to ${skillsDestDir}`));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Create TestDriver agents in GitHub Copilot format
|
|
561
|
+
*/
|
|
562
|
+
async createAgents() {
|
|
563
|
+
const agentsDestDir = path.join(process.cwd(), ".github", "agents");
|
|
564
|
+
|
|
565
|
+
// Try to find agents in node_modules or local package
|
|
566
|
+
const possibleAgentsSources = [
|
|
567
|
+
path.join(process.cwd(), "node_modules", "testdriverai", "ai", "agents"),
|
|
568
|
+
path.join(__dirname, "..", "..", "..", "ai", "agents"),
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
let agentsSourceDir = null;
|
|
572
|
+
for (const source of possibleAgentsSources) {
|
|
573
|
+
if (fs.existsSync(source)) {
|
|
574
|
+
agentsSourceDir = source;
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Create .github/agents directory if it doesn't exist
|
|
580
|
+
if (!fs.existsSync(agentsDestDir)) {
|
|
581
|
+
fs.mkdirSync(agentsDestDir, { recursive: true });
|
|
582
|
+
console.log(chalk.gray(` Created directory: ${agentsDestDir}`));
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// If we found source agents, convert them to .agent.md format
|
|
586
|
+
if (agentsSourceDir) {
|
|
587
|
+
const agentFiles = fs.readdirSync(agentsSourceDir).filter(f => f.endsWith(".md"));
|
|
588
|
+
|
|
589
|
+
for (const agentFile of agentFiles) {
|
|
590
|
+
const sourcePath = path.join(agentsSourceDir, agentFile);
|
|
591
|
+
const agentName = agentFile.replace(".md", "");
|
|
592
|
+
const destPath = path.join(agentsDestDir, `${agentName}.agent.md`);
|
|
593
|
+
|
|
594
|
+
if (!fs.existsSync(destPath)) {
|
|
595
|
+
const sourceContent = fs.readFileSync(sourcePath, "utf8");
|
|
596
|
+
|
|
597
|
+
// Parse the source frontmatter and body
|
|
598
|
+
const frontmatterMatch = sourceContent.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
599
|
+
|
|
600
|
+
if (frontmatterMatch) {
|
|
601
|
+
const frontmatterText = frontmatterMatch[1];
|
|
602
|
+
const body = frontmatterMatch[2];
|
|
603
|
+
|
|
604
|
+
// Extract description from frontmatter
|
|
605
|
+
const descMatch = frontmatterText.match(/description:\s*["']?(.*?)["']?$/m);
|
|
606
|
+
const description = descMatch ? descMatch[1] : `TestDriver ${agentName} agent`;
|
|
607
|
+
|
|
608
|
+
// Create GitHub Copilot agent format
|
|
609
|
+
const agentContent = `---
|
|
610
|
+
name: ${agentName}
|
|
611
|
+
description: ${description}
|
|
612
|
+
tools:
|
|
613
|
+
- testdriver/*
|
|
614
|
+
mcp-servers:
|
|
615
|
+
testdriver:
|
|
616
|
+
command: npx
|
|
617
|
+
args:
|
|
618
|
+
- testdriverai-mcp
|
|
619
|
+
env:
|
|
620
|
+
TD_API_KEY: \${TD_API_KEY}
|
|
621
|
+
---
|
|
622
|
+
${body}`;
|
|
623
|
+
|
|
624
|
+
fs.writeFileSync(destPath, agentContent);
|
|
625
|
+
console.log(chalk.green(` Created agent: ${destPath}`));
|
|
626
|
+
}
|
|
627
|
+
} else {
|
|
628
|
+
console.log(chalk.gray(` Agent ${agentName}.agent.md already exists, skipping...`));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
} else {
|
|
632
|
+
// Create a default test-writer agent if no source found
|
|
633
|
+
const defaultAgentPath = path.join(agentsDestDir, "test-writer.agent.md");
|
|
634
|
+
|
|
635
|
+
if (!fs.existsSync(defaultAgentPath)) {
|
|
636
|
+
const defaultAgentContent = `---
|
|
637
|
+
name: test-writer
|
|
638
|
+
description: An expert at creating and refining automated tests using TestDriver.ai
|
|
639
|
+
tools:
|
|
640
|
+
- testdriver/*
|
|
641
|
+
mcp-servers:
|
|
642
|
+
testdriver:
|
|
643
|
+
command: npx
|
|
644
|
+
args:
|
|
645
|
+
- testdriverai-mcp
|
|
646
|
+
env:
|
|
647
|
+
TD_API_KEY: \${TD_API_KEY}
|
|
648
|
+
---
|
|
649
|
+
# TestDriver Expert
|
|
650
|
+
|
|
651
|
+
You are an expert at writing automated tests using the TestDriver library. Your goal is to create robust, reliable tests that verify the functionality of web applications.
|
|
652
|
+
|
|
653
|
+
## Workflow
|
|
654
|
+
|
|
655
|
+
1. **Start Session**: Use \`session_start\` to provision a sandbox with browser
|
|
656
|
+
2. **Interact**: Use \`find\`, \`click\`, \`type\` etc. - each returns a screenshot
|
|
657
|
+
3. **Verify**: Use \`check\` after actions and \`assert\` for test conditions
|
|
658
|
+
4. **Build Test**: Append generated code to your test file
|
|
659
|
+
5. **Validate**: Use \`verify\` to run the test from scratch
|
|
660
|
+
|
|
661
|
+
## Tips
|
|
662
|
+
|
|
663
|
+
- Be specific with element descriptions: "blue Sign In button in the header" > "button"
|
|
664
|
+
- Use \`check\` after actions to verify they succeeded
|
|
665
|
+
- Start simple - get one step working before adding more
|
|
666
|
+
`;
|
|
667
|
+
|
|
668
|
+
fs.writeFileSync(defaultAgentPath, defaultAgentContent);
|
|
669
|
+
console.log(chalk.green(` Created default agent: ${defaultAgentPath}`));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
401
674
|
/**
|
|
402
675
|
* Install dependencies
|
|
403
676
|
*/
|
|
@@ -431,8 +704,12 @@ jobs:
|
|
|
431
704
|
console.log(chalk.cyan("Next steps:\n"));
|
|
432
705
|
console.log(" 1. Run your tests:");
|
|
433
706
|
console.log(chalk.gray(" npx vitest run\n"));
|
|
707
|
+
console.log(" 2. Use AI agents to write tests:");
|
|
708
|
+
console.log(chalk.gray(" Open VSCode/Cursor and use @test-writer agent\n"));
|
|
709
|
+
console.log(" 3. MCP server configured:");
|
|
710
|
+
console.log(chalk.gray(" TestDriver tools available via MCP in .vscode/mcp.json\n"));
|
|
434
711
|
console.log(
|
|
435
|
-
"
|
|
712
|
+
" 4. For CI/CD, add TD_API_KEY to your GitHub repository secrets",
|
|
436
713
|
);
|
|
437
714
|
console.log(
|
|
438
715
|
chalk.gray(" Settings → Secrets → Actions → New repository secret\n"),
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
const { Command } = require("@oclif/core");
|
|
2
|
+
const { createCommandDefinitions } = require("../../../agent/interface.js");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const chalk = require("chalk");
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
const readline = require("readline");
|
|
9
|
+
|
|
10
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..");
|
|
11
|
+
const CLAUDE_HOME = path.join(os.homedir(), ".claude");
|
|
12
|
+
const CLAUDE_MCP_FILE = path.join(os.homedir(), ".claude.json");
|
|
13
|
+
const CURSOR_MCP_FILE = path.join(os.homedir(), ".cursor", "mcp.json");
|
|
14
|
+
|
|
15
|
+
const MCP_SERVER_CONFIG = {
|
|
16
|
+
"testdriver-cloud": {
|
|
17
|
+
type: "sse",
|
|
18
|
+
url: "https://replayable-dev-ian-mac-m1-16.ngrok.io/api/v1/mcp",
|
|
19
|
+
headers: {
|
|
20
|
+
"x-api-key": "${TD_API_KEY}",
|
|
21
|
+
},
|
|
22
|
+
description:
|
|
23
|
+
"Query TestDriver test runs, test cases, and filters for your team using an API key.",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const CURSOR_MCP_SERVER_CONFIG = {
|
|
28
|
+
"testdriver-cloud": {
|
|
29
|
+
type: "sse",
|
|
30
|
+
url: "https://replayable-dev-ian-mac-m1-16.ngrok.io/api/v1/mcp",
|
|
31
|
+
headers: {
|
|
32
|
+
"x-api-key": "${TD_API_KEY}",
|
|
33
|
+
},
|
|
34
|
+
description:
|
|
35
|
+
"Query TestDriver test runs, test cases, and filters for your team using an API key.",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
class SetupCommand extends Command {
|
|
40
|
+
async run() {
|
|
41
|
+
await this.parse(SetupCommand);
|
|
42
|
+
|
|
43
|
+
console.log(chalk.cyan("\nSetting up TestDriver for Claude Code...\n"));
|
|
44
|
+
|
|
45
|
+
const sourceSkills = path.join(PACKAGE_ROOT, "ai", "skills");
|
|
46
|
+
const sourceAgents = path.join(PACKAGE_ROOT, "ai", "agents");
|
|
47
|
+
|
|
48
|
+
this.installSkills(sourceSkills, path.join(CLAUDE_HOME, "skills"));
|
|
49
|
+
this.installAgents(sourceAgents, path.join(CLAUDE_HOME, "agents"));
|
|
50
|
+
this.installMcp();
|
|
51
|
+
this.installCursorMcp();
|
|
52
|
+
await this.promptForApiKey();
|
|
53
|
+
|
|
54
|
+
console.log(chalk.green("\nSetup complete!\n"));
|
|
55
|
+
this.printNextSteps();
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Recursively copy a directory's contents
|
|
61
|
+
*/
|
|
62
|
+
copyDirSync(src, dest) {
|
|
63
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
64
|
+
|
|
65
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
66
|
+
const srcPath = path.join(src, entry.name);
|
|
67
|
+
const destPath = path.join(dest, entry.name);
|
|
68
|
+
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
this.copyDirSync(srcPath, destPath);
|
|
71
|
+
} else {
|
|
72
|
+
fs.copyFileSync(srcPath, destPath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Install skills to ~/.claude/skills
|
|
79
|
+
*/
|
|
80
|
+
installSkills(source, dest) {
|
|
81
|
+
if (!fs.existsSync(source)) {
|
|
82
|
+
console.log(
|
|
83
|
+
chalk.yellow(" Skills source not found, skipping: " + source),
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const skills = fs
|
|
89
|
+
.readdirSync(source, { withFileTypes: true })
|
|
90
|
+
.filter((d) => d.isDirectory());
|
|
91
|
+
|
|
92
|
+
for (const skill of skills) {
|
|
93
|
+
const srcDir = path.join(source, skill.name);
|
|
94
|
+
const destDir = path.join(dest, skill.name);
|
|
95
|
+
this.copyDirSync(srcDir, destDir);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(chalk.green(` Installed ${skills.length} skills to ${dest}`));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Install agents to ~/.claude/agents
|
|
103
|
+
*/
|
|
104
|
+
installAgents(source, dest) {
|
|
105
|
+
if (!fs.existsSync(source)) {
|
|
106
|
+
console.log(
|
|
107
|
+
chalk.yellow(" Agents source not found, skipping: " + source),
|
|
108
|
+
);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
113
|
+
|
|
114
|
+
const agents = fs.readdirSync(source).filter((f) => f.endsWith(".md"));
|
|
115
|
+
|
|
116
|
+
for (const agent of agents) {
|
|
117
|
+
fs.copyFileSync(path.join(source, agent), path.join(dest, agent));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(
|
|
121
|
+
chalk.green(` Installed ${agents.length} agent(s) to ${dest}`),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Add testdriver MCP server to ~/.claude.json
|
|
127
|
+
*/
|
|
128
|
+
installMcp() {
|
|
129
|
+
let config = {};
|
|
130
|
+
|
|
131
|
+
if (fs.existsSync(CLAUDE_MCP_FILE)) {
|
|
132
|
+
try {
|
|
133
|
+
config = JSON.parse(fs.readFileSync(CLAUDE_MCP_FILE, "utf8"));
|
|
134
|
+
} catch {
|
|
135
|
+
// If the file is malformed, start fresh but warn
|
|
136
|
+
console.log(
|
|
137
|
+
chalk.yellow(
|
|
138
|
+
" Warning: existing ~/.claude.json was not valid JSON, overwriting",
|
|
139
|
+
),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!config.mcpServers) {
|
|
145
|
+
config.mcpServers = {};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const alreadyConfigured = config.mcpServers["testdriver-cloud"];
|
|
149
|
+
|
|
150
|
+
Object.assign(config.mcpServers, MCP_SERVER_CONFIG);
|
|
151
|
+
fs.writeFileSync(CLAUDE_MCP_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
152
|
+
|
|
153
|
+
if (alreadyConfigured) {
|
|
154
|
+
console.log(
|
|
155
|
+
chalk.green(` Updated testdriver MCP server in ${CLAUDE_MCP_FILE}`),
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
console.log(
|
|
159
|
+
chalk.green(` Added testdriver MCP server to ${CLAUDE_MCP_FILE}`),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Add testdriver MCP server to ~/.cursor/mcp.json
|
|
166
|
+
*/
|
|
167
|
+
installCursorMcp() {
|
|
168
|
+
const cursorDir = path.dirname(CURSOR_MCP_FILE);
|
|
169
|
+
fs.mkdirSync(cursorDir, { recursive: true });
|
|
170
|
+
|
|
171
|
+
let config = {};
|
|
172
|
+
|
|
173
|
+
if (fs.existsSync(CURSOR_MCP_FILE)) {
|
|
174
|
+
try {
|
|
175
|
+
config = JSON.parse(fs.readFileSync(CURSOR_MCP_FILE, "utf8"));
|
|
176
|
+
} catch {
|
|
177
|
+
console.log(
|
|
178
|
+
chalk.yellow(
|
|
179
|
+
" Warning: existing ~/.cursor/mcp.json was not valid JSON, overwriting",
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!config.mcpServers) {
|
|
186
|
+
config.mcpServers = {};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const alreadyConfigured = config.mcpServers["testdriver-cloud"];
|
|
190
|
+
|
|
191
|
+
Object.assign(config.mcpServers, CURSOR_MCP_SERVER_CONFIG);
|
|
192
|
+
fs.writeFileSync(CURSOR_MCP_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
193
|
+
|
|
194
|
+
if (alreadyConfigured) {
|
|
195
|
+
console.log(
|
|
196
|
+
chalk.green(` Updated testdriver MCP server in ${CURSOR_MCP_FILE}`),
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
console.log(
|
|
200
|
+
chalk.green(` Added testdriver MCP server to ${CURSOR_MCP_FILE}`),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Prompt user for API key and save globally to shell profile
|
|
207
|
+
*/
|
|
208
|
+
async promptForApiKey() {
|
|
209
|
+
// Check if TD_API_KEY is already set in the environment
|
|
210
|
+
if (process.env.TD_API_KEY) {
|
|
211
|
+
console.log(
|
|
212
|
+
chalk.gray("\n API key already set in environment, skipping...\n"),
|
|
213
|
+
);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(chalk.cyan("\n Setting up your TestDriver API key...\n"));
|
|
218
|
+
console.log(
|
|
219
|
+
chalk.gray(" Get your API key from: https://console.testdriver.ai/team"),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const shouldOpen = await this.askYesNo(
|
|
223
|
+
" Open API keys page in browser? (Y/n): ",
|
|
224
|
+
);
|
|
225
|
+
if (shouldOpen) {
|
|
226
|
+
try {
|
|
227
|
+
const open = (await import("open")).default;
|
|
228
|
+
await open("https://console.testdriver.ai/team");
|
|
229
|
+
console.log(chalk.gray(" Opening browser...\n"));
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.log(
|
|
232
|
+
chalk.yellow(" Could not open browser automatically\n"),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const apiKey = await this.promptHidden(
|
|
238
|
+
" Enter your API key (input will be hidden): ",
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (apiKey && apiKey.trim()) {
|
|
242
|
+
this.addToShellProfile("TD_API_KEY", apiKey.trim());
|
|
243
|
+
process.env.TD_API_KEY = apiKey.trim();
|
|
244
|
+
} else {
|
|
245
|
+
console.log(
|
|
246
|
+
chalk.yellow(
|
|
247
|
+
"\n No API key entered. You can set it later:\n",
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
console.log(chalk.gray(' export TD_API_KEY="your_api_key"\n'));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Prompt for hidden input (like password)
|
|
256
|
+
*/
|
|
257
|
+
async promptHidden(question) {
|
|
258
|
+
return new Promise((resolve) => {
|
|
259
|
+
process.stdout.write(question);
|
|
260
|
+
|
|
261
|
+
const stdin = process.stdin;
|
|
262
|
+
const wasRaw = stdin.isRaw;
|
|
263
|
+
stdin.setRawMode(true);
|
|
264
|
+
stdin.resume();
|
|
265
|
+
stdin.setEncoding("utf8");
|
|
266
|
+
|
|
267
|
+
let input = "";
|
|
268
|
+
|
|
269
|
+
const onData = (char) => {
|
|
270
|
+
if (char === "\u0003") {
|
|
271
|
+
stdin.setRawMode(wasRaw);
|
|
272
|
+
process.exit();
|
|
273
|
+
}
|
|
274
|
+
if (char === "\r" || char === "\n") {
|
|
275
|
+
stdin.setRawMode(wasRaw);
|
|
276
|
+
stdin.removeListener("data", onData);
|
|
277
|
+
stdin.pause();
|
|
278
|
+
console.log("");
|
|
279
|
+
resolve(input);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (char === "\u007F" || char === "\b") {
|
|
283
|
+
input = input.slice(0, -1);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
input += char;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
stdin.on("data", onData);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Ask a yes/no question
|
|
295
|
+
*/
|
|
296
|
+
async askYesNo(question) {
|
|
297
|
+
return new Promise((resolve) => {
|
|
298
|
+
const rl = readline.createInterface({
|
|
299
|
+
input: process.stdin,
|
|
300
|
+
output: process.stdout,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
rl.question(question, (answer) => {
|
|
304
|
+
rl.close();
|
|
305
|
+
const normalized = answer.toLowerCase().trim();
|
|
306
|
+
resolve(
|
|
307
|
+
normalized === "" || normalized === "y" || normalized === "yes",
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Add an environment variable export to the user's shell profile
|
|
315
|
+
*/
|
|
316
|
+
addToShellProfile(key, value) {
|
|
317
|
+
if (process.platform === "win32") {
|
|
318
|
+
try {
|
|
319
|
+
execSync(`setx ${key} "${value}"`, { stdio: "ignore" });
|
|
320
|
+
console.log(
|
|
321
|
+
chalk.green(`\n Set ${key} as user environment variable\n`),
|
|
322
|
+
);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.log(
|
|
325
|
+
chalk.yellow(`\n Could not set ${key} via setx. You can set it manually:\n`),
|
|
326
|
+
);
|
|
327
|
+
console.log(chalk.gray(` setx ${key} "your_api_key"\n`));
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
333
|
+
const home = os.homedir();
|
|
334
|
+
let profilePath;
|
|
335
|
+
|
|
336
|
+
if (shell.includes("zsh")) {
|
|
337
|
+
profilePath = path.join(home, ".zshrc");
|
|
338
|
+
} else {
|
|
339
|
+
profilePath = path.join(home, ".bashrc");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const exportLine = `export ${key}="${value}"`;
|
|
343
|
+
|
|
344
|
+
if (fs.existsSync(profilePath)) {
|
|
345
|
+
const content = fs.readFileSync(profilePath, "utf8");
|
|
346
|
+
if (content.includes(`export ${key}=`)) {
|
|
347
|
+
const updated = content.replace(
|
|
348
|
+
new RegExp(`^export ${key}=.*$`, "m"),
|
|
349
|
+
exportLine,
|
|
350
|
+
);
|
|
351
|
+
fs.writeFileSync(profilePath, updated);
|
|
352
|
+
console.log(
|
|
353
|
+
chalk.green(`\n Updated ${key} in ${profilePath}\n`),
|
|
354
|
+
);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
fs.appendFileSync(profilePath, `\n${exportLine}\n`);
|
|
360
|
+
console.log(
|
|
361
|
+
chalk.green(`\n Added ${key} to ${profilePath}\n`),
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
printNextSteps() {
|
|
366
|
+
console.log(chalk.cyan("Next steps:\n"));
|
|
367
|
+
console.log(" 1. Restart Claude Code to pick up the new MCP server\n");
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Get command definition from interface.js
|
|
372
|
+
const tempAgent = { workingDir: process.cwd() };
|
|
373
|
+
const definitions = createCommandDefinitions(tempAgent);
|
|
374
|
+
const commandDef = definitions["setup"];
|
|
375
|
+
|
|
376
|
+
SetupCommand.description =
|
|
377
|
+
commandDef?.description ||
|
|
378
|
+
"Set up TestDriver skills, agents, and MCP for Claude Code";
|
|
379
|
+
SetupCommand.args = commandDef?.args || {};
|
|
380
|
+
SetupCommand.flags = commandDef?.flags || {};
|
|
381
|
+
|
|
382
|
+
module.exports = SetupCommand;
|