opensteer 0.9.2 → 0.9.4

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.
Files changed (39) hide show
  1. package/README.md +158 -165
  2. package/dist/{chunk-HD6KVZ42.js → chunk-GEUHKPC2.js} +46 -16
  3. package/dist/chunk-GEUHKPC2.js.map +1 -0
  4. package/dist/{chunk-2TIVULZY.js → chunk-GSCQQKZZ.js} +53 -9
  5. package/dist/chunk-GSCQQKZZ.js.map +1 -0
  6. package/dist/{chunk-KPYLS2KQ.js → chunk-HQCMXRBE.js} +5 -4
  7. package/dist/chunk-HQCMXRBE.js.map +1 -0
  8. package/dist/{chunk-BMPUL66S.js → chunk-T5P2QGZ3.js} +58 -53
  9. package/dist/chunk-T5P2QGZ3.js.map +1 -0
  10. package/dist/{chunk-FIMNKEG5.js → chunk-ZRF7WMS3.js} +4 -4
  11. package/dist/{chunk-FIMNKEG5.js.map → chunk-ZRF7WMS3.js.map} +1 -1
  12. package/dist/cli/bin.cjs +160 -72
  13. package/dist/cli/bin.cjs.map +1 -1
  14. package/dist/cli/bin.js +17 -7
  15. package/dist/cli/bin.js.map +1 -1
  16. package/dist/index.cjs +149 -69
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +19 -2
  19. package/dist/index.d.ts +19 -2
  20. package/dist/index.js +4 -4
  21. package/dist/local-view/public/assets/app.css +219 -55
  22. package/dist/local-view/public/assets/app.js +58 -2
  23. package/dist/local-view/public/index.html +101 -26
  24. package/dist/local-view/serve-entry.cjs +106 -57
  25. package/dist/local-view/serve-entry.cjs.map +1 -1
  26. package/dist/local-view/serve-entry.js +2 -2
  27. package/dist/opensteer-PJI7VUIT.js +6 -0
  28. package/dist/{opensteer-MIQ43CY4.js.map → opensteer-PJI7VUIT.js.map} +1 -1
  29. package/dist/{session-control-IFE3IPS3.js → session-control-M3JD7ZKA.js} +4 -4
  30. package/dist/{session-control-IFE3IPS3.js.map → session-control-M3JD7ZKA.js.map} +1 -1
  31. package/package.json +5 -5
  32. package/skills/opensteer/SKILL.md +7 -8
  33. package/skills/recorder/SKILL.md +43 -48
  34. package/dist/chunk-2TIVULZY.js.map +0 -1
  35. package/dist/chunk-BMPUL66S.js.map +0 -1
  36. package/dist/chunk-HD6KVZ42.js.map +0 -1
  37. package/dist/chunk-KPYLS2KQ.js.map +0 -1
  38. package/dist/opensteer-MIQ43CY4.js +0 -6
  39. package/skills/recorder/references/recorder-reference.md +0 -71
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { runLocalViewService } from '../chunk-FIMNKEG5.js';
3
- import '../chunk-BMPUL66S.js';
2
+ import { runLocalViewService } from '../chunk-ZRF7WMS3.js';
3
+ import '../chunk-T5P2QGZ3.js';
4
4
 
5
5
  // src/local-view/serve-entry.ts
6
6
  runLocalViewService().catch((error) => {
@@ -0,0 +1,6 @@
1
+ export { Opensteer } from './chunk-HQCMXRBE.js';
2
+ import './chunk-GEUHKPC2.js';
3
+ import './chunk-GSCQQKZZ.js';
4
+ import './chunk-T5P2QGZ3.js';
5
+ //# sourceMappingURL=opensteer-PJI7VUIT.js.map
6
+ //# sourceMappingURL=opensteer-PJI7VUIT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"opensteer-MIQ43CY4.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"opensteer-PJI7VUIT.js"}
@@ -1,5 +1,5 @@
1
- import { OpensteerBrowserManager } from './chunk-2TIVULZY.js';
2
- import { readLocalViewSessionManifest, readPersistedLocalBrowserSessionRecord, deleteLocalViewSessionManifest } from './chunk-BMPUL66S.js';
1
+ import { OpensteerBrowserManager } from './chunk-GSCQQKZZ.js';
2
+ import { readLocalViewSessionManifest, readPersistedLocalBrowserSessionRecord, deleteLocalViewSessionManifest } from './chunk-T5P2QGZ3.js';
3
3
 
4
4
  // src/local-view/session-control.ts
5
5
  var LocalViewSessionCloseError = class extends Error {
@@ -35,5 +35,5 @@ async function closeLocalViewSessionBrowser(sessionId) {
35
35
  }
36
36
 
37
37
  export { LocalViewSessionCloseError, closeLocalViewSessionBrowser };
38
- //# sourceMappingURL=session-control-IFE3IPS3.js.map
39
- //# sourceMappingURL=session-control-IFE3IPS3.js.map
38
+ //# sourceMappingURL=session-control-M3JD7ZKA.js.map
39
+ //# sourceMappingURL=session-control-M3JD7ZKA.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/local-view/session-control.ts"],"names":[],"mappings":";;;;AAOO,IAAM,0BAAA,GAAN,cAAyC,KAAA,CAAM;AAAA,EACpD,WAAA,CACE,SACS,UAAA,EACT;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAFJ,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AAGT,IAAA,IAAA,CAAK,IAAA,GAAO,4BAAA;AAAA,EACd;AACF;AAEA,eAAsB,6BAA6B,SAAA,EAAkC;AACnF,EAAA,MAAM,QAAA,GAAW,MAAM,4BAAA,CAA6B,SAAS,CAAA;AAC7D,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAI,0BAAA,CAA2B,oBAAA,EAAsB,GAAG,CAAA;AAAA,EAChE;AAEA,EAAA,IAAI,QAAA,CAAS,cAAc,OAAA,EAAS;AAClC,IAAA,MAAM,IAAI,0BAAA;AAAA,MACR,wEAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,sCAAA,CAAuC,QAAA,CAAS,QAAQ,CAAA;AAC7E,EAAA,IACE,CAAC,MAAA,IACD,MAAA,CAAO,GAAA,KAAQ,QAAA,CAAS,GAAA,IACxB,MAAA,CAAO,SAAA,KAAc,QAAA,CAAS,SAAA,IAC9B,MAAA,CAAO,MAAA,KAAW,SAAS,MAAA,EAC3B;AACA,IAAA,MAAM,8BAAA,CAA+B,SAAS,CAAA,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AACrE,IAAA,MAAM,IAAI,0BAAA,CAA2B,oBAAA,EAAsB,GAAG,CAAA;AAAA,EAChE;AAEA,EAAA,MAAM,OAAA,GAAU,IAAI,uBAAA,CAAwB;AAAA,IAC1C,UAAU,QAAA,CAAS,QAAA;AAAA,IACnB,GAAI,SAAS,SAAA,KAAc,MAAA,GAAY,EAAC,GAAI,EAAE,SAAA,EAAW,QAAA,CAAS,SAAA,EAAU;AAAA,IAC5E,YAAY,MAAA,CAAO,MAAA;AAAA,IACnB,OAAA,EAAS;AAAA,GACV,CAAA;AACD,EAAA,MAAM,QAAQ,KAAA,EAAM;AACtB","file":"session-control-IFE3IPS3.js","sourcesContent":["import { readPersistedLocalBrowserSessionRecord } from \"../live-session.js\";\nimport { OpensteerBrowserManager } from \"../browser-manager.js\";\nimport {\n deleteLocalViewSessionManifest,\n readLocalViewSessionManifest,\n} from \"./session-manifest.js\";\n\nexport class LocalViewSessionCloseError extends Error {\n constructor(\n message: string,\n readonly statusCode: 404 | 409,\n ) {\n super(message);\n this.name = \"LocalViewSessionCloseError\";\n }\n}\n\nexport async function closeLocalViewSessionBrowser(sessionId: string): Promise<void> {\n const manifest = await readLocalViewSessionManifest(sessionId);\n if (!manifest) {\n throw new LocalViewSessionCloseError(\"Session not found.\", 404);\n }\n\n if (manifest.ownership !== \"owned\") {\n throw new LocalViewSessionCloseError(\n \"Only Opensteer-owned local browsers can be closed from the local view.\",\n 409,\n );\n }\n\n const record = await readPersistedLocalBrowserSessionRecord(manifest.rootPath);\n if (\n !record ||\n record.pid !== manifest.pid ||\n record.startedAt !== manifest.startedAt ||\n record.engine !== manifest.engine\n ) {\n await deleteLocalViewSessionManifest(sessionId).catch(() => undefined);\n throw new LocalViewSessionCloseError(\"Session not found.\", 404);\n }\n\n const manager = new OpensteerBrowserManager({\n rootPath: manifest.rootPath,\n ...(manifest.workspace === undefined ? {} : { workspace: manifest.workspace }),\n engineName: record.engine,\n browser: \"persistent\",\n });\n await manager.close();\n}\n"]}
1
+ {"version":3,"sources":["../src/local-view/session-control.ts"],"names":[],"mappings":";;;;AAOO,IAAM,0BAAA,GAAN,cAAyC,KAAA,CAAM;AAAA,EACpD,WAAA,CACE,SACS,UAAA,EACT;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAFJ,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AAGT,IAAA,IAAA,CAAK,IAAA,GAAO,4BAAA;AAAA,EACd;AACF;AAEA,eAAsB,6BAA6B,SAAA,EAAkC;AACnF,EAAA,MAAM,QAAA,GAAW,MAAM,4BAAA,CAA6B,SAAS,CAAA;AAC7D,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAI,0BAAA,CAA2B,oBAAA,EAAsB,GAAG,CAAA;AAAA,EAChE;AAEA,EAAA,IAAI,QAAA,CAAS,cAAc,OAAA,EAAS;AAClC,IAAA,MAAM,IAAI,0BAAA;AAAA,MACR,wEAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,sCAAA,CAAuC,QAAA,CAAS,QAAQ,CAAA;AAC7E,EAAA,IACE,CAAC,MAAA,IACD,MAAA,CAAO,GAAA,KAAQ,QAAA,CAAS,GAAA,IACxB,MAAA,CAAO,SAAA,KAAc,QAAA,CAAS,SAAA,IAC9B,MAAA,CAAO,MAAA,KAAW,SAAS,MAAA,EAC3B;AACA,IAAA,MAAM,8BAAA,CAA+B,SAAS,CAAA,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AACrE,IAAA,MAAM,IAAI,0BAAA,CAA2B,oBAAA,EAAsB,GAAG,CAAA;AAAA,EAChE;AAEA,EAAA,MAAM,OAAA,GAAU,IAAI,uBAAA,CAAwB;AAAA,IAC1C,UAAU,QAAA,CAAS,QAAA;AAAA,IACnB,GAAI,SAAS,SAAA,KAAc,MAAA,GAAY,EAAC,GAAI,EAAE,SAAA,EAAW,QAAA,CAAS,SAAA,EAAU;AAAA,IAC5E,YAAY,MAAA,CAAO,MAAA;AAAA,IACnB,OAAA,EAAS;AAAA,GACV,CAAA;AACD,EAAA,MAAM,QAAQ,KAAA,EAAM;AACtB","file":"session-control-M3JD7ZKA.js","sourcesContent":["import { readPersistedLocalBrowserSessionRecord } from \"../live-session.js\";\nimport { OpensteerBrowserManager } from \"../browser-manager.js\";\nimport {\n deleteLocalViewSessionManifest,\n readLocalViewSessionManifest,\n} from \"./session-manifest.js\";\n\nexport class LocalViewSessionCloseError extends Error {\n constructor(\n message: string,\n readonly statusCode: 404 | 409,\n ) {\n super(message);\n this.name = \"LocalViewSessionCloseError\";\n }\n}\n\nexport async function closeLocalViewSessionBrowser(sessionId: string): Promise<void> {\n const manifest = await readLocalViewSessionManifest(sessionId);\n if (!manifest) {\n throw new LocalViewSessionCloseError(\"Session not found.\", 404);\n }\n\n if (manifest.ownership !== \"owned\") {\n throw new LocalViewSessionCloseError(\n \"Only Opensteer-owned local browsers can be closed from the local view.\",\n 409,\n );\n }\n\n const record = await readPersistedLocalBrowserSessionRecord(manifest.rootPath);\n if (\n !record ||\n record.pid !== manifest.pid ||\n record.startedAt !== manifest.startedAt ||\n record.engine !== manifest.engine\n ) {\n await deleteLocalViewSessionManifest(sessionId).catch(() => undefined);\n throw new LocalViewSessionCloseError(\"Session not found.\", 404);\n }\n\n const manager = new OpensteerBrowserManager({\n rootPath: manifest.rootPath,\n ...(manifest.workspace === undefined ? {} : { workspace: manifest.workspace }),\n engineName: record.engine,\n browser: \"persistent\",\n });\n await manager.close();\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opensteer",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "Opensteer browser automation, replay, and reverse-engineering toolkit.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -56,8 +56,8 @@
56
56
  "sharp": "^0.34.5",
57
57
  "skills": "^1.4.6",
58
58
  "ws": "^8.18.0",
59
- "@opensteer/engine-playwright": "0.8.8",
60
- "@opensteer/runtime-core": "0.2.2"
59
+ "@opensteer/engine-playwright": "0.8.9",
60
+ "@opensteer/runtime-core": "0.2.3"
61
61
  },
62
62
  "optionalDependencies": {
63
63
  "webcrack": "^2.15.1"
@@ -72,11 +72,11 @@
72
72
  },
73
73
  "devDependencies": {
74
74
  "@opensteer/browser-core": "0.7.9",
75
- "@opensteer/protocol": "0.8.2",
75
+ "@opensteer/protocol": "0.8.3",
76
76
  "@opensteer/engine-abp": "0.8.9"
77
77
  },
78
78
  "scripts": {
79
- "build": "tsup && node ../../scripts/copy-opensteer-local-view-assets.mjs && node ../../scripts/sync-package-skills.mjs",
79
+ "build": "tsup && node ../../scripts/copy-opensteer-local-view-assets.mjs && node ../../scripts/sync-package-skills.mjs && node ../../scripts/sync-package-readme.mjs",
80
80
  "clean": "rimraf dist skills",
81
81
  "typecheck": "tsc --noEmit -p tsconfig.json"
82
82
  }
@@ -40,14 +40,13 @@ If the user wants to manually drive a browser and record the flow, use the `reco
40
40
  ## Core Rules
41
41
 
42
42
  1. Always use a workspace for stateful commands: `--workspace <id>` or `OPENSTEER_WORKSPACE`.
43
- 2. In this repo, prefer `pnpm run opensteer:local -- <command>` instead of bare `opensteer ...`.
44
- 3. Re-snapshot after navigation or big UI changes before reusing element numbers.
45
- 4. CLI to discover, SDK for the final implementation.
46
- 5. Use `persist` for stable reusable targets and extraction payloads.
47
- 6. Use `exec` for SDK code and API experiments. Use `evaluate` only for page-context JavaScript.
48
- 7. If `fetch()` fails with auth errors, inspect `state()`, `cookies()`, and `storage()` before changing transport.
49
- 8. Keep output simple. Prefer ordinary TypeScript with `Opensteer`, no extra abstraction.
50
- 9. Close the browser when done. Do not leave headed browsers running. Use `opensteer browser delete --workspace <id>` or SDK cleanup when the session does not need to stay open.
43
+ 2. Re-snapshot after navigation or big UI changes before reusing element numbers.
44
+ 3. CLI to discover, SDK for the final implementation.
45
+ 4. Use `persist` for stable reusable targets and extraction payloads.
46
+ 5. Use `exec` for SDK code and API experiments. Use `evaluate` only for page-context JavaScript.
47
+ 6. If `fetch()` fails with auth errors, inspect `state()`, `cookies()`, and `storage()` before changing transport.
48
+ 7. Keep output simple. Prefer ordinary TypeScript with `Opensteer`, no extra abstraction.
49
+ 8. Close the browser when done. Do not leave headed browsers running. Use `opensteer browser delete --workspace <id>` or SDK cleanup when the session does not need to stay open.
51
50
 
52
51
  ## Choose A Path
53
52
 
@@ -1,19 +1,25 @@
1
1
  ---
2
- name: recorder
3
- description: Use when the user wants to record a live browser workflow and turn it into a replayable Opensteer script. Prefer this for manual browser capture, multi-tab recording, and record-then-rerun workflows with the Opensteer CLI.
4
- argument-hint: "[url]"
2
+ name: "recorder"
3
+ description: "Use when the user wants to record a live browser workflow and turn it into a replayable script or API. Prefer this for manual browser capture, multi-tab recording, and record-then-rerun workflows with the Opensteer CLI."
5
4
  ---
6
5
 
7
6
  # Recorder
8
7
 
9
- Use the Opensteer recorder when the user wants to perform a real browser flow manually and save it as a deterministic replay script.
8
+ Record a real browser flow performed manually by the user and save it as a deterministic replay script. Do not use this when the user wants programmatic browser automation without manual recording — use the `opensteer` skill instead.
10
9
 
11
- ## Inputs
10
+ ## Prerequisites
12
11
 
13
- - `url`: starting URL to open
14
- - `workspace`: target Opensteer workspace id
15
- - `provider`: `local` or `cloud`
16
- - optional `output`: explicit output path
12
+ Verify the CLI is available:
13
+
14
+ ```bash
15
+ command -v opensteer >/dev/null 2>&1 && echo "ok" || echo "opensteer not found"
16
+ ```
17
+
18
+ For cloud recording, verify environment variables are set:
19
+
20
+ ```bash
21
+ test -n "$OPENSTEER_BASE_URL" && test -n "$OPENSTEER_API_KEY" && test -n "$OPENSTEER_CLOUD_APP_BASE_URL" && echo "ok" || echo "missing cloud env vars"
22
+ ```
17
23
 
18
24
  ## Quick Start
19
25
 
@@ -29,56 +35,45 @@ Cloud recording:
29
35
  opensteer record --provider cloud --workspace <id> --url <url>
30
36
  ```
31
37
 
32
- Cloud recording requires:
33
-
34
- - `OPENSTEER_BASE_URL`
35
- - `OPENSTEER_API_KEY`
36
- - `OPENSTEER_CLOUD_APP_BASE_URL`
37
-
38
38
  ## Mode Selection
39
39
 
40
- - Use `provider=local` when the user wants to interact with a local Playwright browser window.
41
- - Use `provider=cloud` when the user wants to interact through the cloud browser session UI.
42
- - Keep local recording on the default headed persistent browser flow.
43
- - In cloud mode, do not force `headless=false`. Use the normal cloud launch behavior unless the user explicitly overrides it.
40
+ - Use `provider=local` when the user wants to interact with a local Playwright browser window. Local requires a headed, persistent browser. Do not pass `--headless true`.
41
+ - Use `provider=cloud` when the user wants to interact through the cloud browser session UI. Do not force `headless=false` in cloud mode. Cloud does not support `browser.mode="attach"`.
44
42
 
45
43
  ## Workflow
46
44
 
47
- 1. Start the recorder with `opensteer record`.
48
- 2. If the provider is `cloud`, give the user the browser session URL printed by the CLI.
45
+ 1. Run `opensteer record --workspace <id> --url <url>` (add `--provider cloud` for cloud).
46
+ 2. If cloud, give the user the browser session URL printed by the CLI.
49
47
  3. Tell the user to perform the workflow manually.
50
48
  4. Tell the user exactly how to stop:
51
- - local: click the injected `Stop recording` button in the browser page
52
- - cloud: click `Stop recording` in the browser session toolbar UI
53
- 5. Wait for the recorder process to finish. Do not assume recording is complete just because the browser URL was printed.
54
- 6. Only after the CLI exits, read the generated script from disk and inspect what was captured.
55
- 7. If the user wants verification, rerun the generated script instead of only reviewing the file.
56
-
57
- ## Guardrails
49
+ - Local: click the injected **Stop recording** button in the browser page.
50
+ - Cloud: click **Stop recording** in the browser session toolbar UI.
51
+ 5. Keep the `record` command alive while the user is recording. Do not interrupt it. Do not stop with `Ctrl+C` unless the user explicitly wants to abort.
52
+ 6. Wait for the CLI process to exit. Do not assume recording is complete just because the browser URL was printed.
53
+ 7. Verify the output file exists at `.opensteer/workspaces/<id>/recorded-flow.ts` (or the `--output` path if specified).
54
+ 8. Read and summarize the generated script before editing it.
55
+ 9. If the user wants verification, replay the script: `npx tsx <path-to-recorded-flow.ts>`.
58
56
 
59
- - Recording requires `engine=playwright`.
60
- - Local recording only supports a persistent browser.
61
- - Local recording requires a headed browser. Do not pass `--headless true` in local mode.
62
- - Cloud recording does not support `browser.mode="attach"`.
63
- - Do not stop recording with `Ctrl+C` unless the user explicitly wants to abort the run.
64
- - Do not use removed timeout flags such as `--record-timeout-ms`.
65
- - If a launch argument value starts with `--`, pass it as `--arg=...`, not `--arg ...`.
66
- - If the flow depends on recorder limits such as iframes, file upload, drag-and-drop, or canvas behavior, read the reference file before promising support.
57
+ ## Limitations
67
58
 
68
- ## Output Contract
59
+ The recorder captures clicks, text entry, key presses, scrolling, select changes, navigation, and multi-tab operations. It does not fully support:
69
60
 
70
- - Default output path: `.opensteer/workspaces/<id>/recorded-flow.ts`
71
- - The CLI writes the replay script locally after recording completes in both local and cloud modes.
72
- - Generated scripts use the public Opensteer SDK surface. Cloud recordings bootstrap `provider.mode = "cloud"` and local recordings bootstrap the workspace-backed local flow.
61
+ - Cross-origin iframes (not recorded)
62
+ - Shadow DOM selectors (best effort)
63
+ - File uploads, drag-and-drop, and canvas interactions
64
+ - Browser back/forward detection (may fall back to direct navigation replay)
73
65
 
74
- ## Agent Guidance
66
+ ## Rules
75
67
 
76
- - Keep the `record` command alive while the user is recording.
77
- - If the user is actively driving the session, avoid mixing in extra agent actions unless they explicitly ask for help recording a combined flow.
78
- - After recording completes, summarize the captured flow before editing it.
79
- - If replay fails, debug the generated script and rerun it instead of re-recording immediately.
68
+ - Recording requires `engine=playwright`.
69
+ - Do not use removed timeout flags such as `--record-timeout-ms`.
70
+ - If a launch argument value starts with `--`, pass it as `--arg=...`, not `--arg ...`.
71
+ - Do not mix in extra agent actions while the user is recording unless they explicitly ask.
72
+ - If replay fails, debug and fix the generated script rather than re-recording immediately.
80
73
 
81
- ## References
74
+ ## Troubleshooting
82
75
 
83
- - [Recorder Reference](references/recorder-reference.md)
84
- - [Opensteer Skill](../opensteer/SKILL.md)
76
+ - **Recorder fails to start**: verify the workspace ID is valid and the browser engine is playwright.
77
+ - **CLI exits with an error**: read stderr for the error message before retrying.
78
+ - **Generated script has errors**: inspect and fix the script rather than re-recording.
79
+ - **Output file missing**: check if the user stopped recording correctly (button click, not Ctrl+C).