testdriverai 6.2.1 → 7.0.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/.github/workflows/acceptance-linux.yml +75 -0
- package/.github/workflows/acceptance-sdk-tests.yml +133 -0
- package/.vscode/settings.json +5 -1
- package/MIGRATION.md +389 -0
- package/PLUGIN_MIGRATION.md +222 -0
- package/PROMPT_CACHE.md +200 -0
- package/SDK_LOGGING.md +222 -0
- package/SDK_MIGRATION.md +474 -0
- package/SDK_README.md +1122 -0
- package/{testdriver → _testdriver}/acceptance/drag-and-drop.yaml +2 -2
- package/{testdriver → _testdriver}/acceptance/snippets/login.yaml +1 -1
- package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
- package/{testdriver → _testdriver}/examples/web/lifecycle/prerun.yaml +6 -1
- package/{testdriver → _testdriver}/lifecycle/postrun.yaml +3 -2
- package/_testdriver/lifecycle/prerun.yaml +15 -0
- package/{testdriver → _testdriver}/lifecycle/provision.yaml +7 -2
- package/agent/index.js +258 -68
- package/agent/interface.js +15 -0
- package/agent/lib/cache.js +142 -0
- package/agent/lib/commander.js +1 -39
- package/agent/lib/commands.js +143 -188
- package/agent/lib/redraw.js +6 -3
- package/agent/lib/sandbox.js +19 -5
- package/agent/lib/sdk.js +1 -0
- package/agent/lib/system.js +0 -3
- package/agent/lib/validation.js +1 -7
- package/debug-locate-response.js +82 -0
- package/debug-screenshot-1763401388589.png +0 -0
- package/debugger/index.html +16 -5
- package/docs/ARCHITECTURE.md +424 -0
- package/docs/AWESOME_LOGS_QUICK_REF.md +100 -0
- package/docs/QUICK_START_TEST_RECORDING.md +215 -0
- package/docs/SDK_AWESOME_LOGS.md +468 -0
- package/docs/TEST_RECORDING.md +388 -0
- package/docs/docs.json +232 -152
- package/docs/sdk-browser-rendering.md +167 -0
- package/docs/v6/getting-started/self-hosting.mdx +407 -0
- package/docs/{guide → v6/guide}/dashcam.mdx +1 -1
- package/docs/{guide → v6/guide}/environment-variables.mdx +4 -5
- package/docs/{guide → v6/guide}/lifecycle.mdx +1 -1
- package/docs/v6/overview/comparison.mdx +101 -0
- package/docs/v7/README.md +135 -0
- package/docs/v7/api/ai.mdx +205 -0
- package/docs/v7/api/assert.mdx +285 -0
- package/docs/v7/api/assertions.mdx +403 -0
- package/docs/v7/api/click.mdx +287 -0
- package/docs/v7/api/client.mdx +322 -0
- package/docs/v7/api/elements.mdx +479 -0
- package/docs/v7/api/exec.mdx +346 -0
- package/docs/v7/api/find.mdx +316 -0
- package/docs/v7/api/focusApplication.mdx +294 -0
- package/docs/v7/api/hover.mdx +279 -0
- package/docs/v7/api/pressKeys.mdx +349 -0
- package/docs/v7/api/sandbox.mdx +404 -0
- package/docs/v7/api/scroll.mdx +300 -0
- package/docs/v7/api/type.mdx +314 -0
- package/docs/v7/commands/assert.mdx +45 -0
- package/docs/v7/commands/exec.mdx +282 -0
- package/docs/v7/commands/focus-application.mdx +44 -0
- package/docs/v7/commands/hover-image.mdx +69 -0
- package/docs/v7/commands/hover-text.mdx +47 -0
- package/docs/v7/commands/if.mdx +53 -0
- package/docs/v7/commands/match-image.mdx +67 -0
- package/docs/v7/commands/press-keys.mdx +87 -0
- package/docs/v7/commands/remember.mdx +49 -0
- package/docs/v7/commands/run.mdx +44 -0
- package/docs/v7/commands/scroll-until-image.mdx +66 -0
- package/docs/v7/commands/scroll-until-text.mdx +60 -0
- package/docs/v7/commands/scroll.mdx +69 -0
- package/docs/v7/commands/type.mdx +45 -0
- package/docs/v7/commands/wait-for-image.mdx +54 -0
- package/docs/v7/commands/wait-for-text.mdx +48 -0
- package/docs/v7/commands/wait.mdx +45 -0
- package/docs/v7/getting-started/quickstart.mdx +199 -0
- package/docs/v7/guides/migration.mdx +562 -0
- package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
- package/docs/v7/playwright.mdx +342 -0
- package/eslint.config.js +19 -1
- package/examples/run-tests-with-recording.sh +70 -0
- package/examples/screenshot-example.js +63 -0
- package/examples/sdk-awesome-logs-demo.js +177 -0
- package/examples/sdk-cache-thresholds.js +96 -0
- package/examples/sdk-element-properties.js +155 -0
- package/examples/sdk-simple-example.js +65 -0
- package/examples/test-recording-example.test.js +166 -0
- package/interfaces/cli/lib/base.js +10 -4
- package/interfaces/logger.js +2 -1
- package/interfaces/shared-test-state.mjs +69 -0
- package/interfaces/vitest-plugin.mjs +744 -0
- package/mcp-server/AI_GUIDELINES.md +57 -0
- package/package.json +18 -5
- package/schema.json +8 -29
- package/scripts/view-test-results.mjs +96 -0
- package/sdk-log-formatter.js +714 -0
- package/sdk.d.ts +735 -0
- package/sdk.js +1906 -0
- package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
- package/setup/aws/cloudformation.yaml +9 -2
- package/test/mcp-example-test.yaml +27 -0
- package/test-find-api.js +73 -0
- package/test-prompt-cache.js +96 -0
- package/test-sandbox-render.js +28 -0
- package/test-sdk-methods.js +15 -0
- package/test-sdk-refactor.js +53 -0
- package/test-stack-trace.mjs +57 -0
- package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +61 -0
- package/testdriver/acceptance-sdk/README.md +128 -0
- package/testdriver/acceptance-sdk/TEST_REPORTING.md +245 -0
- package/testdriver/acceptance-sdk/assert.test.mjs +44 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +70 -0
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +38 -0
- package/testdriver/acceptance-sdk/exec-js.test.mjs +55 -0
- package/testdriver/acceptance-sdk/exec-output.test.mjs +71 -0
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +69 -0
- package/testdriver/acceptance-sdk/focus-window.test.mjs +48 -0
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +41 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +43 -0
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +50 -0
- package/testdriver/acceptance-sdk/hover-text.test.mjs +41 -0
- package/testdriver/acceptance-sdk/match-image.test.mjs +48 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +64 -0
- package/testdriver/acceptance-sdk/prompt.test.mjs +45 -0
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +52 -0
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +51 -0
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +42 -0
- package/testdriver/acceptance-sdk/scroll.test.mjs +50 -0
- package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
- package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +239 -0
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +648 -0
- package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
- package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
- package/testdriver/acceptance-sdk/type.test.mjs +84 -0
- package/verify-element-api.js +89 -0
- package/verify-types.js +0 -0
- package/vitest.config.example.js +19 -0
- package/vitest.config.mjs +65 -0
- package/vitest.config.mjs.bak +44 -0
- package/.github/workflows/acceptance-v6.yml +0 -169
- package/docs/overview/comparison.mdx +0 -82
- package/testdriver/lifecycle/prerun.yaml +0 -17
- /package/{testdriver/examples/desktop/lifecycle/prerun.yaml → .env.example} +0 -0
- /package/{testdriver → _testdriver}/acceptance/assert.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/dashcam.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/embed.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-js.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-output.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-shell.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/focus-window.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text-with-description.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/if-else.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/match-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/press-keys.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/prompt.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/remember.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/screenshots/cart.png +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-keyboard.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/snippets/match-cart.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/type.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/failure.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/prerun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/secrets.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/dashcam-chrome.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/exec-pwsh-multiline.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-exception.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-promise.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-in-middle.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-nested.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/success-test.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/example.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/readme.md +0 -0
- /package/{testdriver → _testdriver}/examples/chrome-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/desktop/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/vscode-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/web/lifecycle/postrun.yaml +0 -0
- /package/docs/{account → v6/account}/dashboard.mdx +0 -0
- /package/docs/{account → v6/account}/enterprise.mdx +0 -0
- /package/docs/{account → v6/account}/pricing.mdx +0 -0
- /package/docs/{account → v6/account}/projects.mdx +0 -0
- /package/docs/{account → v6/account}/team.mdx +0 -0
- /package/docs/{action → v6/action}/ami.mdx +0 -0
- /package/docs/{action → v6/action}/performance.mdx +0 -0
- /package/docs/{action → v6/action}/secrets.mdx +0 -0
- /package/docs/{apps → v6/apps}/chrome-extensions.mdx +0 -0
- /package/docs/{apps → v6/apps}/desktop-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/mobile-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/static-websites.mdx +0 -0
- /package/docs/{apps → v6/apps}/tauri-apps.mdx +0 -0
- /package/docs/{bugs → v6/bugs}/jira.mdx +0 -0
- /package/docs/{cli → v6/cli}/overview.mdx +0 -0
- /package/docs/{commands → v6/commands}/assert.mdx +0 -0
- /package/docs/{commands → v6/commands}/exec.mdx +0 -0
- /package/docs/{commands → v6/commands}/focus-application.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/if.mdx +0 -0
- /package/docs/{commands → v6/commands}/match-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/press-keys.mdx +0 -0
- /package/docs/{commands → v6/commands}/remember.mdx +0 -0
- /package/docs/{commands → v6/commands}/run.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll.mdx +0 -0
- /package/docs/{commands → v6/commands}/type.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/junit.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/playwright.mdx +0 -0
- /package/docs/{features → v6/features}/auto-healing.mdx +0 -0
- /package/docs/{features → v6/features}/generation.mdx +0 -0
- /package/docs/{features → v6/features}/parallel-testing.mdx +0 -0
- /package/docs/{features → v6/features}/reusable-snippets.mdx +0 -0
- /package/docs/{features → v6/features}/selectorless.mdx +0 -0
- /package/docs/{features → v6/features}/visual-assertions.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/ci.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/cli.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/editing.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/playwright.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/running.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/vscode.mdx +0 -0
- /package/docs/{guide → v6/guide}/assertions.mdx +0 -0
- /package/docs/{guide → v6/guide}/authentication.mdx +0 -0
- /package/docs/{guide → v6/guide}/code.mdx +0 -0
- /package/docs/{guide → v6/guide}/locating.mdx +0 -0
- /package/docs/{guide → v6/guide}/protips.mdx +0 -0
- /package/docs/{guide → v6/guide}/variables.mdx +0 -0
- /package/docs/{guide → v6/guide}/waiting.mdx +0 -0
- /package/docs/{importing → v6/importing}/csv.mdx +0 -0
- /package/docs/{importing → v6/importing}/gherkin.mdx +0 -0
- /package/docs/{importing → v6/importing}/jira.mdx +0 -0
- /package/docs/{importing → v6/importing}/testrail.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/electron.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/netlify.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/vercel.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/explore.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/run.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/save.mdx +0 -0
- /package/docs/{overview → v6/overview}/faq.mdx +0 -0
- /package/docs/{overview → v6/overview}/performance.mdx +0 -0
- /package/docs/{overview → v6/overview}/quickstart.mdx +0 -0
- /package/docs/{overview → v6/overview}/what-is-testdriver.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/ai-chatbot.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/cookie-banner.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/file-upload.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/form-filling.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/log-in.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/pdf-generation.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/spell-check.mdx +0 -0
- /package/docs/{security → v6/security}/action.mdx +0 -0
- /package/docs/{security → v6/security}/agent.mdx +0 -0
- /package/docs/{security → v6/security}/platform.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/advanced-test.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/basic-test.mdx +0 -0
|
@@ -37,11 +37,11 @@ steps:
|
|
|
37
37
|
- command: hover-text
|
|
38
38
|
text: New Text Document
|
|
39
39
|
description: new text document icon in the center of the desktop
|
|
40
|
-
action:
|
|
40
|
+
action: mouseDown
|
|
41
41
|
- command: hover-text
|
|
42
42
|
text: Recycle Bin
|
|
43
43
|
description: recycle bin icon in the top left corner of the desktop
|
|
44
|
-
action:
|
|
44
|
+
action: mouseUp
|
|
45
45
|
|
|
46
46
|
- prompt: '"New Text Document" icon is not on the Desktop'
|
|
47
47
|
commands:
|
|
File without changes
|
|
@@ -4,7 +4,12 @@ steps:
|
|
|
4
4
|
commands:
|
|
5
5
|
- command: exec
|
|
6
6
|
lang: pwsh
|
|
7
|
-
code:
|
|
7
|
+
code: |
|
|
8
|
+
npm install dashcam@beta -g
|
|
9
|
+
npm list -g dashcam
|
|
10
|
+
- command: exec
|
|
11
|
+
lang: pwsh
|
|
12
|
+
code: dashcam track --name=TestDriver --type=app --pattern="C:\Users\testdriver\Documents\testdriver.log"
|
|
8
13
|
- command: exec
|
|
9
14
|
lang: pwsh
|
|
10
15
|
code: dashcam start
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: 6.0.0
|
|
2
|
+
steps:
|
|
3
|
+
- prompt: dashcam
|
|
4
|
+
commands:
|
|
5
|
+
- command: exec
|
|
6
|
+
lang: sh
|
|
7
|
+
code: |
|
|
8
|
+
dashcam auth 4e93d8bf-3886-4d26-a144-116c4063522d
|
|
9
|
+
dashcam logs --add --name="TestDriver" --type=file --file="/tmp/testdriver.log"
|
|
10
|
+
- command: exec
|
|
11
|
+
lang: sh
|
|
12
|
+
code: dashcam record > /dev/null 2>&1 &
|
|
13
|
+
- command: exec
|
|
14
|
+
lang: sh
|
|
15
|
+
code: dashcam logs --add --name=TestDriver --type=file --file="/tmp/testdriver.log"
|
|
@@ -3,6 +3,11 @@ session: 67f00511acbd9ccac373edf7
|
|
|
3
3
|
steps:
|
|
4
4
|
- prompt: launch chrome
|
|
5
5
|
commands:
|
|
6
|
+
- command: exec
|
|
7
|
+
lang: sh
|
|
8
|
+
code: |
|
|
9
|
+
npm install dashcam@beta -g
|
|
10
|
+
npm list -g dashcam
|
|
6
11
|
- command: exec
|
|
7
12
|
lang: sh
|
|
8
13
|
code: |
|
|
@@ -13,8 +18,8 @@ steps:
|
|
|
13
18
|
--no-default-browser-check \
|
|
14
19
|
--no-first-run \
|
|
15
20
|
--guest \
|
|
16
|
-
"
|
|
21
|
+
"https://testdriver-sandbox.vercel.app/" \
|
|
17
22
|
>/dev/null 2>&1 &
|
|
18
23
|
- command: wait-for-text
|
|
19
|
-
text:
|
|
24
|
+
text: testdriver-sandbox
|
|
20
25
|
timeout: 60000
|
package/agent/index.js
CHANGED
|
@@ -19,6 +19,7 @@ const diff = require("diff");
|
|
|
19
19
|
|
|
20
20
|
// global utilities
|
|
21
21
|
const generator = require("./lib/generator.js");
|
|
22
|
+
const promptCache = require("./lib/cache.js");
|
|
22
23
|
const theme = require("./lib/theme.js");
|
|
23
24
|
const SourceMapper = require("./lib/source-mapper.js");
|
|
24
25
|
|
|
@@ -74,6 +75,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
74
75
|
this.sandboxId = flags["sandbox-id"] || null;
|
|
75
76
|
this.sandboxAmi = flags["sandbox-ami"] || null;
|
|
76
77
|
this.sandboxInstance = flags["sandbox-instance"] || null;
|
|
78
|
+
this.sandboxOs = flags.os || "linux";
|
|
77
79
|
this.ip = flags.ip || null;
|
|
78
80
|
this.workingDir = flags.workingDir || process.cwd();
|
|
79
81
|
|
|
@@ -101,7 +103,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
101
103
|
// Create outputs instance for this agent
|
|
102
104
|
this.outputs = createOutputs();
|
|
103
105
|
|
|
104
|
-
// Create SDK instance with this agent's emitter, config, and
|
|
106
|
+
// Create SDK instance with this agent's emitter, config, session, and abort signal
|
|
105
107
|
this.sdk = createSDK(this.emitter, this.config, this.session);
|
|
106
108
|
|
|
107
109
|
// Create analytics instance with this agent's emitter, config, and session
|
|
@@ -110,6 +112,9 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
110
112
|
// Create sandbox instance with this agent's emitter and analytics
|
|
111
113
|
this.sandbox = createSandbox(this.emitter, this.analytics);
|
|
112
114
|
|
|
115
|
+
// Set the OS for the sandbox to use
|
|
116
|
+
this.sandbox.os = this.sandboxOs;
|
|
117
|
+
|
|
113
118
|
// Create system instance with emitter, sandbox and config
|
|
114
119
|
this.system = createSystem(this.emitter, this.sandbox, this.config);
|
|
115
120
|
|
|
@@ -121,6 +126,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
121
126
|
this.config,
|
|
122
127
|
this.session,
|
|
123
128
|
() => this.sourceMapper.currentFilePath || this.thisFile,
|
|
129
|
+
this.cliArgs.options.redrawThreshold,
|
|
124
130
|
);
|
|
125
131
|
this.commands = commandsResult.commands;
|
|
126
132
|
this.redraw = commandsResult.redraw;
|
|
@@ -272,6 +278,15 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
272
278
|
// Get error message
|
|
273
279
|
let eMessage = error.message ? error.message : error;
|
|
274
280
|
|
|
281
|
+
// Truncate error message if too long to prevent 400 errors from API
|
|
282
|
+
// Keep first 5000 characters as a reasonable limit for API payloads
|
|
283
|
+
const MAX_ERROR_LENGTH = 5000;
|
|
284
|
+
if (typeof eMessage === "string" && eMessage.length > MAX_ERROR_LENGTH) {
|
|
285
|
+
eMessage =
|
|
286
|
+
eMessage.substring(0, MAX_ERROR_LENGTH) +
|
|
287
|
+
"\n\n[Error message truncated - message was too long]";
|
|
288
|
+
}
|
|
289
|
+
|
|
275
290
|
// we sanitize the error message to use it as a key in the errorCounts object
|
|
276
291
|
let safeKey = JSON.stringify(error.message ? error.message : error);
|
|
277
292
|
this.errorCounts[safeKey] = this.errorCounts[safeKey]
|
|
@@ -323,19 +338,40 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
323
338
|
const streamId = `error-${Date.now()}`;
|
|
324
339
|
this.emitter.emit(events.log.markdown.start, streamId);
|
|
325
340
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
341
|
+
// Truncate markdown if too long to prevent 400 errors
|
|
342
|
+
const MAX_MARKDOWN_LENGTH = 10000;
|
|
343
|
+
let truncatedMarkdown = markdown;
|
|
344
|
+
if (typeof markdown === "string" && markdown.length > MAX_MARKDOWN_LENGTH) {
|
|
345
|
+
truncatedMarkdown =
|
|
346
|
+
markdown.substring(0, MAX_MARKDOWN_LENGTH) +
|
|
347
|
+
"\n\n[Markdown truncated - content was too long]";
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let response;
|
|
351
|
+
try {
|
|
352
|
+
response = await this.sdk.req(
|
|
353
|
+
"error",
|
|
354
|
+
{
|
|
355
|
+
description: eMessage,
|
|
356
|
+
markdown: truncatedMarkdown,
|
|
357
|
+
image,
|
|
358
|
+
},
|
|
359
|
+
(chunk) => {
|
|
360
|
+
if (chunk.type === "data") {
|
|
361
|
+
this.emitter.emit(events.log.markdown.chunk, streamId, chunk.data);
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
);
|
|
365
|
+
} catch (apiError) {
|
|
366
|
+
// If the error API call itself fails, prevent infinite loop
|
|
367
|
+
// by not retrying and instead treating as fatal
|
|
368
|
+
this.emitter.emit(
|
|
369
|
+
events.log.error,
|
|
370
|
+
theme.red(`Failed to get AI error resolution: ${apiError.message}`),
|
|
371
|
+
);
|
|
372
|
+
this.emitter.emit(events.log.log, "Original error: " + eMessage);
|
|
373
|
+
return await this.dieOnFatal(error);
|
|
374
|
+
}
|
|
339
375
|
|
|
340
376
|
this.emitter.emit(events.log.markdown.end, streamId);
|
|
341
377
|
|
|
@@ -835,6 +871,7 @@ commands:
|
|
|
835
871
|
dry = false,
|
|
836
872
|
validateAndLoop = false,
|
|
837
873
|
shouldSave = true,
|
|
874
|
+
useCache = true,
|
|
838
875
|
) {
|
|
839
876
|
// Check if execution has been stopped
|
|
840
877
|
if (this.stopped) {
|
|
@@ -853,6 +890,49 @@ commands:
|
|
|
853
890
|
|
|
854
891
|
this.tasks.push(currentTask);
|
|
855
892
|
|
|
893
|
+
// Check cache first (if enabled via parameter)
|
|
894
|
+
const cachedYaml = useCache ? promptCache.readCache(currentTask) : null;
|
|
895
|
+
|
|
896
|
+
if (cachedYaml) {
|
|
897
|
+
// Cache hit - load and execute the cached YAML file
|
|
898
|
+
this.emitter.emit(
|
|
899
|
+
events.log.debug,
|
|
900
|
+
`Using cached response for prompt: "${currentTask}"`,
|
|
901
|
+
);
|
|
902
|
+
this.emitter.emit(events.log.log, theme.dim("(using cached response)"));
|
|
903
|
+
|
|
904
|
+
try {
|
|
905
|
+
// Load the YAML using hydrateFromYML
|
|
906
|
+
const parsed = await generator.hydrateFromYML(
|
|
907
|
+
cachedYaml,
|
|
908
|
+
this.sessionInstance,
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
// Execute the commands from the first step
|
|
912
|
+
if (parsed.steps && parsed.steps.length > 0) {
|
|
913
|
+
const step = parsed.steps[0];
|
|
914
|
+
if (step.commands) {
|
|
915
|
+
await this.executeCommands(
|
|
916
|
+
step.commands,
|
|
917
|
+
0,
|
|
918
|
+
false,
|
|
919
|
+
dry,
|
|
920
|
+
shouldSave,
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
} catch (err) {
|
|
925
|
+
this.emitter.emit(
|
|
926
|
+
events.log.debug,
|
|
927
|
+
`Error loading cached YAML: ${err.message}, falling back to API`,
|
|
928
|
+
);
|
|
929
|
+
// Fall through to make API call if cache is invalid
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// Cache miss - call the API
|
|
856
936
|
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
857
937
|
|
|
858
938
|
this.lastScreenshot = await this.system.captureScreenBase64();
|
|
@@ -877,7 +957,49 @@ commands:
|
|
|
877
957
|
|
|
878
958
|
this.emitter.emit(events.log.markdown.end, streamId);
|
|
879
959
|
|
|
880
|
-
if (message) {
|
|
960
|
+
if (message && message.data) {
|
|
961
|
+
// Save the YAML to cache (if enabled)
|
|
962
|
+
if (useCache) {
|
|
963
|
+
try {
|
|
964
|
+
// Extract YAML code blocks from the markdown response
|
|
965
|
+
const codeblocks = await this.parser.findCodeBlocks(message.data);
|
|
966
|
+
if (codeblocks && codeblocks.length > 0) {
|
|
967
|
+
// Parse commands from all code blocks
|
|
968
|
+
const allCommands = [];
|
|
969
|
+
for (const block of codeblocks) {
|
|
970
|
+
const commands = await this.parser.getCommands(block);
|
|
971
|
+
allCommands.push(...commands);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Create a proper step with prompt
|
|
975
|
+
const step = {
|
|
976
|
+
prompt: currentTask,
|
|
977
|
+
commands: allCommands,
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
// Use dumpToYML to create a valid testdriver yaml file
|
|
981
|
+
const yamlContent = await generator.dumpToYML(
|
|
982
|
+
[step],
|
|
983
|
+
this.sessionInstance,
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
const cachePath = promptCache.writeCache(currentTask, yamlContent);
|
|
987
|
+
if (cachePath) {
|
|
988
|
+
this.emitter.emit(
|
|
989
|
+
events.log.debug,
|
|
990
|
+
`Cached YAML saved to: ${cachePath}`,
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
} catch (err) {
|
|
995
|
+
// If we can't extract YAML, just skip caching
|
|
996
|
+
this.emitter.emit(
|
|
997
|
+
events.log.debug,
|
|
998
|
+
`Could not cache response: ${err.message}`,
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
881
1003
|
await this.aiExecute(message.data, validateAndLoop, dry, shouldSave);
|
|
882
1004
|
this.emitter.emit(
|
|
883
1005
|
events.log.debug,
|
|
@@ -1619,7 +1741,8 @@ ${regression}
|
|
|
1619
1741
|
const storedInstance = sandboxInfo.instanceType || null;
|
|
1620
1742
|
|
|
1621
1743
|
if (currentAmi === storedAmi && currentInstance === storedInstance) {
|
|
1622
|
-
|
|
1744
|
+
// Return sandboxId (new format) or instanceId (old format for backwards compatibility)
|
|
1745
|
+
return sandboxInfo.sandboxId || sandboxInfo.instanceId;
|
|
1623
1746
|
} else {
|
|
1624
1747
|
this.emitter.emit(
|
|
1625
1748
|
events.log.log,
|
|
@@ -1637,14 +1760,15 @@ ${regression}
|
|
|
1637
1760
|
return null;
|
|
1638
1761
|
}
|
|
1639
1762
|
|
|
1640
|
-
saveLastSandboxId(
|
|
1763
|
+
saveLastSandboxId(sandboxId, osType = "linux") {
|
|
1641
1764
|
const lastSandboxFile = path.join(
|
|
1642
1765
|
os.homedir(),
|
|
1643
1766
|
".testdriverai-last-sandbox",
|
|
1644
1767
|
);
|
|
1645
1768
|
try {
|
|
1646
1769
|
const sandboxInfo = {
|
|
1647
|
-
|
|
1770
|
+
sandboxId: sandboxId,
|
|
1771
|
+
os: osType,
|
|
1648
1772
|
ami: this.sandboxAmi || null,
|
|
1649
1773
|
instanceType: this.sandboxInstance || null,
|
|
1650
1774
|
timestamp: new Date().toISOString(),
|
|
@@ -1682,6 +1806,11 @@ ${regression}
|
|
|
1682
1806
|
|
|
1683
1807
|
let { headless = false, heal, new: createNew = false } = options;
|
|
1684
1808
|
|
|
1809
|
+
// Prioritize this.newSandbox flag if it's set
|
|
1810
|
+
if (this.newSandbox) {
|
|
1811
|
+
createNew = true;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1685
1814
|
// If CI environment variable is true, always create a new sandbox
|
|
1686
1815
|
if (this.config.CI) {
|
|
1687
1816
|
createNew = true;
|
|
@@ -1696,11 +1825,18 @@ ${regression}
|
|
|
1696
1825
|
// If createNew flag is set, clear the recent sandbox file to force creating a new sandbox
|
|
1697
1826
|
if (createNew) {
|
|
1698
1827
|
this.clearRecentSandboxId();
|
|
1699
|
-
|
|
1828
|
+
// Also clear this.sandboxId to prevent reconnection attempts
|
|
1829
|
+
this.sandboxId = null;
|
|
1830
|
+
if (!this.config.CI && !this.newSandbox) {
|
|
1700
1831
|
this.emitter.emit(
|
|
1701
1832
|
events.log.log,
|
|
1702
1833
|
theme.dim("--`new` flag detected, will create a new sandbox"),
|
|
1703
1834
|
);
|
|
1835
|
+
} else if (this.newSandbox) {
|
|
1836
|
+
this.emitter.emit(
|
|
1837
|
+
events.log.log,
|
|
1838
|
+
theme.dim("--new-sandbox flag detected, will create a new sandbox"),
|
|
1839
|
+
);
|
|
1704
1840
|
}
|
|
1705
1841
|
}
|
|
1706
1842
|
|
|
@@ -1718,23 +1854,47 @@ ${regression}
|
|
|
1718
1854
|
ip: this.ip,
|
|
1719
1855
|
});
|
|
1720
1856
|
|
|
1721
|
-
|
|
1857
|
+
this.emitter.emit(events.sandbox.connected);
|
|
1858
|
+
|
|
1859
|
+
this.instance = instance.instance;
|
|
1860
|
+
await this.renderSandbox(this.instance, headless);
|
|
1722
1861
|
await this.newSession();
|
|
1723
1862
|
await this.runLifecycle("provision");
|
|
1724
1863
|
|
|
1725
1864
|
return;
|
|
1726
1865
|
} else if (!createNew && recentId) {
|
|
1866
|
+
// Only attempt to connect to existing sandbox if not in CI mode and not creating new
|
|
1727
1867
|
this.emitter.emit(
|
|
1728
1868
|
events.log.narration,
|
|
1729
1869
|
theme.dim(`using recent sandbox: ${recentId}`),
|
|
1730
1870
|
);
|
|
1731
1871
|
this.sandboxId = recentId;
|
|
1732
|
-
|
|
1872
|
+
|
|
1873
|
+
try {
|
|
1874
|
+
let instance = await this.connectToSandboxDirect(
|
|
1875
|
+
this.sandboxId,
|
|
1876
|
+
true, // always persist by default
|
|
1877
|
+
);
|
|
1878
|
+
|
|
1879
|
+
this.instance = instance;
|
|
1880
|
+
|
|
1881
|
+
await this.renderSandbox(instance, headless);
|
|
1882
|
+
await this.newSession();
|
|
1883
|
+
return;
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
// If connection fails, fall through to creating a new sandbox
|
|
1886
|
+
this.emitter.emit(
|
|
1887
|
+
events.log.narration,
|
|
1888
|
+
theme.dim(`failed to connect to recent sandbox, creating new one...`),
|
|
1889
|
+
);
|
|
1890
|
+
console.error("Failed to reconnect to sandbox:", error);
|
|
1891
|
+
}
|
|
1892
|
+
} else if (!createNew && !recentId) {
|
|
1733
1893
|
this.emitter.emit(
|
|
1734
1894
|
events.log.narration,
|
|
1735
1895
|
theme.dim(`no recent sandbox found, creating a new one.`),
|
|
1736
1896
|
);
|
|
1737
|
-
} else if (this.sandboxId && !this.config.CI) {
|
|
1897
|
+
} else if (!createNew && this.sandboxId && !this.config.CI) {
|
|
1738
1898
|
// Only attempt to connect to existing sandbox if not in CI mode and not creating new
|
|
1739
1899
|
// Attempt to connect to known instance
|
|
1740
1900
|
this.emitter.emit(
|
|
@@ -1754,46 +1914,48 @@ ${regression}
|
|
|
1754
1914
|
await this.newSession();
|
|
1755
1915
|
return;
|
|
1756
1916
|
} catch (error) {
|
|
1757
|
-
//
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1917
|
+
// If connection fails, fall through to creating a new sandbox
|
|
1918
|
+
this.emitter.emit(
|
|
1919
|
+
events.log.narration,
|
|
1920
|
+
theme.dim(`failed to connect to recent sandbox, creating new one...`),
|
|
1921
|
+
);
|
|
1922
|
+
console.error("Failed to reconnect to sandbox:", error);
|
|
1761
1923
|
}
|
|
1762
1924
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
theme.dim(`creating new sandbox (can take up to 2 minutes)...`),
|
|
1767
|
-
);
|
|
1768
|
-
// We don't have resiliency/retries baked in, so let's at least give it 1 attempt
|
|
1769
|
-
// to see if that fixes the issue.
|
|
1770
|
-
let newSandbox = await this.createNewSandbox().catch(() => {
|
|
1925
|
+
|
|
1926
|
+
// Create new sandbox (either because createNew is true, or no existing sandbox to connect to)
|
|
1927
|
+
if (!this.instance) {
|
|
1771
1928
|
this.emitter.emit(
|
|
1772
1929
|
events.log.narration,
|
|
1773
|
-
theme.dim(`
|
|
1930
|
+
theme.dim(`creating new sandbox (can take up to 2 minutes)...`),
|
|
1774
1931
|
);
|
|
1932
|
+
// We don't have resiliency/retries baked in, so let's at least give it 1 attempt
|
|
1933
|
+
// to see if that fixes the issue.
|
|
1934
|
+
let newSandbox = await this.createNewSandbox().catch(() => {
|
|
1935
|
+
this.emitter.emit(
|
|
1936
|
+
events.log.narration,
|
|
1937
|
+
theme.dim(`double-checking sandbox availability`),
|
|
1938
|
+
);
|
|
1939
|
+
return this.createNewSandbox();
|
|
1940
|
+
});
|
|
1775
1941
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
this.emitter.emit(events.showWindow, urlToOpen);
|
|
1792
|
-
|
|
1793
|
-
await this.newSession();
|
|
1794
|
-
await this.runLifecycle("provision");
|
|
1942
|
+
// Extract the sandbox ID from the newly created sandbox
|
|
1943
|
+
this.sandboxId = newSandbox?.sandbox?.sandboxId || newSandbox?.sandbox?.instanceId;
|
|
1944
|
+
|
|
1945
|
+
// Use the configured sandbox OS type
|
|
1946
|
+
this.saveLastSandboxId(this.sandboxId, this.sandboxOs);
|
|
1947
|
+
|
|
1948
|
+
let instance = await this.connectToSandboxDirect(
|
|
1949
|
+
this.sandboxId,
|
|
1950
|
+
true, // always persist by default
|
|
1951
|
+
);
|
|
1952
|
+
this.instance = instance;
|
|
1953
|
+
await this.renderSandbox(instance, headless);
|
|
1954
|
+
await this.newSession();
|
|
1955
|
+
await this.runLifecycle("provision");
|
|
1795
1956
|
|
|
1796
|
-
|
|
1957
|
+
console.log("provision run");
|
|
1958
|
+
}
|
|
1797
1959
|
}
|
|
1798
1960
|
|
|
1799
1961
|
async start() {
|
|
@@ -1913,13 +2075,27 @@ ${regression}
|
|
|
1913
2075
|
}
|
|
1914
2076
|
|
|
1915
2077
|
async renderSandbox(instance, headless = false) {
|
|
2078
|
+
console.log("renderSandbox", instance);
|
|
2079
|
+
|
|
1916
2080
|
if (!headless) {
|
|
1917
|
-
let url
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
instance.
|
|
1922
|
-
|
|
2081
|
+
let url;
|
|
2082
|
+
|
|
2083
|
+
// If the instance already has a URL (from reconnection), use it
|
|
2084
|
+
if (instance.url) {
|
|
2085
|
+
url = instance.url;
|
|
2086
|
+
} else if (instance.ip || instance.publicIp) {
|
|
2087
|
+
// Otherwise construct it from IP and port
|
|
2088
|
+
url =
|
|
2089
|
+
"http://" +
|
|
2090
|
+
(instance.ip || instance.publicIp) +
|
|
2091
|
+
":" +
|
|
2092
|
+
(instance.vncPort || "5800") +
|
|
2093
|
+
"/vnc_lite.html?token=V3b8wG9";
|
|
2094
|
+
} else {
|
|
2095
|
+
// If we don't have URL or IP, we can't render
|
|
2096
|
+
console.warn("renderSandbox: Missing URL and IP in instance", instance);
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
1923
2099
|
|
|
1924
2100
|
let data = {
|
|
1925
2101
|
resolution: this.config.TD_RESOLUTION,
|
|
@@ -1927,7 +2103,8 @@ ${regression}
|
|
|
1927
2103
|
token: "V3b8wG9",
|
|
1928
2104
|
};
|
|
1929
2105
|
|
|
1930
|
-
|
|
2106
|
+
// Base64 encode the data (the debugger expects base64, not URL encoding)
|
|
2107
|
+
const encodedData = Buffer.from(JSON.stringify(data)).toString("base64");
|
|
1931
2108
|
|
|
1932
2109
|
// Use the debugger URL instead of the VNC URL
|
|
1933
2110
|
const urlToOpen = `${this.debuggerUrl}?data=${encodedData}`;
|
|
@@ -1965,16 +2142,26 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
1965
2142
|
|
|
1966
2143
|
async connectToSandboxDirect(sandboxId, persist = false) {
|
|
1967
2144
|
this.emitter.emit(events.log.narration, theme.dim(`connecting...`));
|
|
1968
|
-
let
|
|
1969
|
-
|
|
2145
|
+
let reply = await this.sandbox.connect(sandboxId, persist);
|
|
2146
|
+
|
|
2147
|
+
// reply includes { success, url, sandbox: {...} }
|
|
2148
|
+
// For renderSandbox, we need the sandbox object with url merged in
|
|
2149
|
+
const sandbox = reply.sandbox || {};
|
|
2150
|
+
|
|
2151
|
+
// If reply has a URL at top level, merge it into the sandbox object
|
|
2152
|
+
if (reply.url && !sandbox.url) {
|
|
2153
|
+
sandbox.url = reply.url;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
return sandbox;
|
|
1970
2157
|
}
|
|
1971
2158
|
|
|
1972
2159
|
async createNewSandbox() {
|
|
1973
2160
|
const sandboxConfig = {
|
|
1974
2161
|
type: "create",
|
|
1975
|
-
os: "linux",
|
|
1976
2162
|
resolution: this.config.TD_RESOLUTION,
|
|
1977
2163
|
ci: this.config.CI,
|
|
2164
|
+
os: this.sandboxOs || "windows",
|
|
1978
2165
|
};
|
|
1979
2166
|
|
|
1980
2167
|
// Add AMI and instance type if specified
|
|
@@ -1985,11 +2172,14 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
1985
2172
|
sandboxConfig.instanceType = this.sandboxInstance;
|
|
1986
2173
|
}
|
|
1987
2174
|
|
|
1988
|
-
console.log("sending create");
|
|
1989
|
-
|
|
1990
2175
|
let instance = await this.sandbox.send(sandboxConfig);
|
|
1991
2176
|
|
|
1992
|
-
|
|
2177
|
+
// Save the sandbox ID for reconnection with the correct OS type
|
|
2178
|
+
if (instance.sandbox && instance.sandbox.sandboxId) {
|
|
2179
|
+
this.saveLastSandboxId(instance.sandbox.sandboxId, this.sandboxOs);
|
|
2180
|
+
} else if (instance.sandbox && instance.sandbox.instanceId) {
|
|
2181
|
+
this.saveLastSandboxId(instance.sandbox.instanceId, this.sandboxOs);
|
|
2182
|
+
}
|
|
1993
2183
|
|
|
1994
2184
|
return instance;
|
|
1995
2185
|
}
|
package/agent/interface.js
CHANGED
|
@@ -66,6 +66,11 @@ function createCommandDefinitions(agent) {
|
|
|
66
66
|
description: "Generate JUnit XML test report to specified file",
|
|
67
67
|
default: false,
|
|
68
68
|
}),
|
|
69
|
+
os: Flags.string({
|
|
70
|
+
description: "Operating system for the sandbox (windows or linux)",
|
|
71
|
+
options: ["windows", "linux"],
|
|
72
|
+
default: "windows",
|
|
73
|
+
}),
|
|
69
74
|
},
|
|
70
75
|
handler: async (args, flags) => {
|
|
71
76
|
// Use --path flag if provided, otherwise fall back to args.file
|
|
@@ -140,6 +145,11 @@ function createCommandDefinitions(agent) {
|
|
|
140
145
|
summary: Flags.string({
|
|
141
146
|
description: "Specify output file for summarize results",
|
|
142
147
|
}),
|
|
148
|
+
os: Flags.string({
|
|
149
|
+
description: "Operating system for the sandbox (windows or linux)",
|
|
150
|
+
options: ["windows", "linux"],
|
|
151
|
+
default: "windows",
|
|
152
|
+
}),
|
|
143
153
|
},
|
|
144
154
|
handler: async () => {
|
|
145
155
|
// Edit mode is handled by the CLI interface via factory.js
|
|
@@ -233,6 +243,11 @@ function createCommandDefinitions(agent) {
|
|
|
233
243
|
"sandbox-instance": Flags.string({
|
|
234
244
|
description: "Specify EC2 instance type for sandbox (e.g., i3.metal)",
|
|
235
245
|
}),
|
|
246
|
+
os: Flags.string({
|
|
247
|
+
description: "Operating system for the sandbox (windows or linux)",
|
|
248
|
+
options: ["windows", "linux"],
|
|
249
|
+
default: "windows",
|
|
250
|
+
}),
|
|
236
251
|
},
|
|
237
252
|
handler: async (args, flags) => {
|
|
238
253
|
// Call generate with the count and prompt
|