opensteer 0.9.4 → 0.9.6

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 (33) hide show
  1. package/dist/{chunk-ZRF7WMS3.js → chunk-3I3A5OLB.js} +3 -3
  2. package/dist/{chunk-ZRF7WMS3.js.map → chunk-3I3A5OLB.js.map} +1 -1
  3. package/dist/{chunk-GSCQQKZZ.js → chunk-3XBQRZZC.js} +221 -6
  4. package/dist/chunk-3XBQRZZC.js.map +1 -0
  5. package/dist/{chunk-GEUHKPC2.js → chunk-BVRIPCWA.js} +878 -572
  6. package/dist/chunk-BVRIPCWA.js.map +1 -0
  7. package/dist/chunk-L4NF74KI.js +458 -0
  8. package/dist/chunk-L4NF74KI.js.map +1 -0
  9. package/dist/cli/bin.cjs +1313 -494
  10. package/dist/cli/bin.cjs.map +1 -1
  11. package/dist/cli/bin.js +235 -108
  12. package/dist/cli/bin.js.map +1 -1
  13. package/dist/index.cjs +1152 -647
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +37 -460
  16. package/dist/index.d.ts +37 -460
  17. package/dist/index.js +3 -4
  18. package/dist/local-view/serve-entry.cjs +5354 -4
  19. package/dist/local-view/serve-entry.cjs.map +1 -1
  20. package/dist/local-view/serve-entry.js +1 -1
  21. package/dist/opensteer-UGA6YBRN.js +6 -0
  22. package/dist/{opensteer-PJI7VUIT.js.map → opensteer-UGA6YBRN.js.map} +1 -1
  23. package/dist/{session-control-M3JD7ZKA.js → session-control-U3L5H2ZI.js} +3 -3
  24. package/dist/{session-control-M3JD7ZKA.js.map → session-control-U3L5H2ZI.js.map} +1 -1
  25. package/package.json +7 -7
  26. package/skills/opensteer/SKILL.md +134 -94
  27. package/dist/chunk-GEUHKPC2.js.map +0 -1
  28. package/dist/chunk-GSCQQKZZ.js.map +0 -1
  29. package/dist/chunk-HQCMXRBE.js +0 -335
  30. package/dist/chunk-HQCMXRBE.js.map +0 -1
  31. package/dist/chunk-KCINASQC.js +0 -3
  32. package/dist/chunk-KCINASQC.js.map +0 -1
  33. package/dist/opensteer-PJI7VUIT.js +0 -6
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runLocalViewService } from '../chunk-ZRF7WMS3.js';
2
+ import { runLocalViewService } from '../chunk-3I3A5OLB.js';
3
3
  import '../chunk-T5P2QGZ3.js';
4
4
 
5
5
  // src/local-view/serve-entry.ts
@@ -0,0 +1,6 @@
1
+ export { Opensteer } from './chunk-L4NF74KI.js';
2
+ import './chunk-BVRIPCWA.js';
3
+ import './chunk-3XBQRZZC.js';
4
+ import './chunk-T5P2QGZ3.js';
5
+ //# sourceMappingURL=opensteer-UGA6YBRN.js.map
6
+ //# sourceMappingURL=opensteer-UGA6YBRN.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"opensteer-PJI7VUIT.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"opensteer-UGA6YBRN.js"}
@@ -1,4 +1,4 @@
1
- import { OpensteerBrowserManager } from './chunk-GSCQQKZZ.js';
1
+ import { OpensteerBrowserManager } from './chunk-3XBQRZZC.js';
2
2
  import { readLocalViewSessionManifest, readPersistedLocalBrowserSessionRecord, deleteLocalViewSessionManifest } from './chunk-T5P2QGZ3.js';
3
3
 
4
4
  // src/local-view/session-control.ts
@@ -35,5 +35,5 @@ async function closeLocalViewSessionBrowser(sessionId) {
35
35
  }
36
36
 
37
37
  export { LocalViewSessionCloseError, closeLocalViewSessionBrowser };
38
- //# sourceMappingURL=session-control-M3JD7ZKA.js.map
39
- //# sourceMappingURL=session-control-M3JD7ZKA.js.map
38
+ //# sourceMappingURL=session-control-U3L5H2ZI.js.map
39
+ //# sourceMappingURL=session-control-U3L5H2ZI.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-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"]}
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-U3L5H2ZI.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.4",
3
+ "version": "0.9.6",
4
4
  "description": "Opensteer browser automation, replay, and reverse-engineering toolkit.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -56,14 +56,14 @@
56
56
  "sharp": "^0.34.5",
57
57
  "skills": "^1.4.6",
58
58
  "ws": "^8.18.0",
59
- "@opensteer/engine-playwright": "0.8.9",
60
- "@opensteer/runtime-core": "0.2.3"
59
+ "@opensteer/engine-playwright": "0.8.10",
60
+ "@opensteer/runtime-core": "0.2.5"
61
61
  },
62
62
  "optionalDependencies": {
63
63
  "webcrack": "^2.15.1"
64
64
  },
65
65
  "peerDependencies": {
66
- "@opensteer/engine-abp": "0.8.9"
66
+ "@opensteer/engine-abp": "0.8.10"
67
67
  },
68
68
  "peerDependenciesMeta": {
69
69
  "@opensteer/engine-abp": {
@@ -71,9 +71,9 @@
71
71
  }
72
72
  },
73
73
  "devDependencies": {
74
- "@opensteer/browser-core": "0.7.9",
75
- "@opensteer/protocol": "0.8.3",
76
- "@opensteer/engine-abp": "0.8.9"
74
+ "@opensteer/browser-core": "0.7.10",
75
+ "@opensteer/engine-abp": "0.8.10",
76
+ "@opensteer/protocol": "0.8.5"
77
77
  },
78
78
  "scripts": {
79
79
  "build": "tsup && node ../../scripts/copy-opensteer-local-view-assets.mjs && node ../../scripts/sync-package-skills.mjs && node ../../scripts/sync-package-readme.mjs",
@@ -6,25 +6,48 @@ argument-hint: "[goal]"
6
6
 
7
7
  # Opensteer
8
8
 
9
- Opensteer gives AI agents a real Chromium browser — local or cloud. Use it when normal code is not enough because the task depends on a live browser session.
9
+ Opensteer gives AI agents a real Chromium browser. Use it when the task depends on a live browser session — clicks, forms, extraction, cookies, network capture, or browser-backed fetch.
10
10
 
11
- Default workflow:
11
+ ## Core Workflow
12
12
 
13
- 1. CLI to explore the site and discover behavior.
14
- 2. Save stable targets with `persist`.
15
- 3. SDK to write the final reusable TypeScript.
13
+ Follow this order. Do not skip steps.
16
14
 
17
- Do not stop at manual exploration if the user needs automation.
15
+ 1. **Open** a browser in a workspace.
16
+ 2. **Snapshot** to see the page and get element numbers.
17
+ 3. **Interact** using element numbers from the latest snapshot. Every action requires `--persist <key>`.
18
+ 4. **Re-snapshot** after navigation or UI changes before reusing element numbers.
19
+ 5. **Extract** data using a template with `--persist <key>`.
20
+ 6. **Write SDK code** that replays persisted targets — no templates in the SDK, only persist keys.
21
+ 7. **Close** the browser when done.
18
22
 
19
- ## Setup
23
+ ```bash
24
+ opensteer open https://example.com --workspace demo
25
+ opensteer snapshot action --workspace demo
26
+ opensteer input 5 "laptop" --workspace demo --press-enter --persist "search input"
27
+ opensteer click 7 --workspace demo --persist "search button"
28
+ opensteer snapshot extraction --workspace demo
29
+ opensteer extract '{"items":[{"title":13,"price":14},{"title":22,"price":23},{"title":31,"price":32}]}' --workspace demo --persist "search results"
30
+ ```
31
+
32
+ ```ts
33
+ import { Opensteer } from "opensteer";
34
+
35
+ const opensteer = new Opensteer({ workspace: "demo", rootDir: process.cwd() });
36
+
37
+ await opensteer.open("https://example.com");
38
+ await opensteer.input({ persist: "search input", text: "laptop", pressEnter: true });
39
+ await opensteer.click({ persist: "search button" });
40
+ const data = await opensteer.extract({ persist: "search results" });
41
+ await opensteer.close();
42
+ ```
20
43
 
21
- Install the Opensteer skill so the coding agent can use it:
44
+ ## Setup
22
45
 
23
46
  ```bash
24
47
  opensteer skills install
25
48
  ```
26
49
 
27
- This registers the skill with the agent's tool system. Only needed once per environment.
50
+ Only needed once per environment.
28
51
 
29
52
  ## When To Use
30
53
 
@@ -37,17 +60,6 @@ This registers the skill with the agent's tool system. Only needed once per envi
37
60
 
38
61
  If the user wants to manually drive a browser and record the flow, use the `recorder` skill instead.
39
62
 
40
- ## Core Rules
41
-
42
- 1. Always use a workspace for stateful commands: `--workspace <id>` or `OPENSTEER_WORKSPACE`.
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.
50
-
51
63
  ## Choose A Path
52
64
 
53
65
  ```
@@ -67,26 +79,86 @@ What does the task need?
67
79
 
68
80
  Use this when the goal is clicking, typing, navigating, or extracting visible data.
69
81
 
70
- ### CLI exploration
82
+ ### Persist is required
83
+
84
+ Every `click`, `hover`, `input`, `scroll`, and `extract` command requires `--persist <key>`. This saves a stable element descriptor so the action is replayable across sessions. Name the key after what the element is:
71
85
 
72
86
  ```bash
73
- opensteer open https://example.com --workspace demo
74
- opensteer snapshot action --workspace demo
75
- opensteer input 5 "laptop" --workspace demo --press-enter --persist "search input"
76
87
  opensteer click 7 --workspace demo --persist "search button"
77
- opensteer snapshot extraction --workspace demo
78
- opensteer extract '{"items":[{"name":{"element":13},"price":{"element":14}}]}' \
79
- --workspace demo \
80
- --persist "search results"
88
+ opensteer input 5 "laptop" --workspace demo --press-enter --persist "search input"
89
+ opensteer scroll down 500 --workspace demo --persist "page scroll"
81
90
  ```
82
91
 
83
- Element numbers come from `c="N"` markers in the snapshot HTML.
92
+ ### Element numbers
93
+
94
+ Element numbers come from `c="N"` markers in the snapshot HTML. They are only valid for the current snapshot. After navigation or DOM changes, snapshot again to get fresh numbers.
95
+
96
+ ```bash
97
+ opensteer snapshot action --workspace demo # for interactions
98
+ opensteer snapshot extraction --workspace demo # for data extraction
99
+ ```
100
+
101
+ Read the full snapshot output. Do not pipe it through `head`, `grep`, or `sed` — filtering destroys the structural context you need to identify which elements belong to the same card.
102
+
103
+ ### Extraction templates
104
+
105
+ The `extract` command takes a JSON template that describes the fields in one or more items. Opensteer merges the structural pattern across all provided examples and generalizes to every matching item on the page.
106
+
107
+ **Template format:**
108
+
109
+ - Bare number: `13` reads text content of element `c="13"`.
110
+ - Object with attribute: `{"c": 13, "attr": "href"}` reads an attribute from that element.
111
+ - Selector: `{"selector": "#price"}` targets by CSS selector.
112
+ - Page source: `{"source": "current_url"}` reads page metadata.
113
+
114
+ **How many items to include:**
115
+
116
+ **Lazy — 3 items from 3 different positions (recommended for reusable SDK descriptors).** Give one entry per card for 3 different cards. Opensteer compares the 3 examples, cancels out position noise, and produces a descriptor that matches all similar items. Use this when the goal is a persist key the SDK can replay later.
117
+
118
+ ```bash
119
+ opensteer extract '{
120
+ "products": [
121
+ {"title": 47, "price": 51, "url": {"c": 47, "attr": "href"}},
122
+ {"title": 62, "price": 66, "url": {"c": 62, "attr": "href"}},
123
+ {"title": 78, "price": 83, "url": {"c": 78, "attr": "href"}}
124
+ ]
125
+ }' --workspace demo --persist "search results"
126
+ ```
127
+
128
+ **Eager — all visible items (use when you need the full data immediately).** Include every item visible in the snapshot. This returns all data in one shot from the current session. The descriptor is still saved under `--persist` and can be replayed, but the generalization is weaker than the 3-item approach.
129
+
130
+ ```bash
131
+ opensteer extract '{
132
+ "products": [
133
+ {"title": 47, "price": 51, "url": {"c": 47, "attr": "href"}},
134
+ {"title": 62, "price": 66, "url": {"c": 62, "attr": "href"}},
135
+ {"title": 78, "price": 83, "url": {"c": 78, "attr": "href"}},
136
+ ...continue for every visible card...
137
+ ]
138
+ }' --workspace demo --persist "search results"
139
+ ```
140
+
141
+ **Rule: all fields in each array entry must come from the same card.** Never take a field from card 1 and another field from card 2 within the same entry — that produces a broken descriptor.
142
+
143
+ Wrong — title from card 1, price from card 2 mixed in the same entry:
144
+
145
+ ```bash
146
+ # DO NOT DO THIS — fields across cards in one entry
147
+ opensteer extract '{"products":[{"title":47,"price":66}]}' --workspace demo --persist "search results"
148
+ ```
149
+
150
+ For non-array fields at the top level, point to the elements directly:
151
+
152
+ ```bash
153
+ opensteer extract '{"pageTitle":3,"totalResults":8,"url":{"source":"current_url"}}' \
154
+ --workspace demo --persist "page metadata"
155
+ ```
84
156
 
85
157
  ### SDK implementation
86
158
 
87
- ```ts
88
- import { Opensteer } from "opensteer";
159
+ The SDK `extract()` method replays a previously persisted template. It does not accept inline templates — those belong in the CLI exploration phase.
89
160
 
161
+ ```ts
90
162
  const opensteer = new Opensteer({ workspace: "demo", rootDir: process.cwd() });
91
163
 
92
164
  await opensteer.open("https://example.com");
@@ -95,7 +167,7 @@ await opensteer.click({ persist: "search button" });
95
167
  const data = await opensteer.extract({ persist: "search results" });
96
168
  ```
97
169
 
98
- Use `selector` in SDK code only when a stable CSS selector is cleaner than `persist`.
170
+ Use `selector` in SDK action code only when a stable CSS selector is cleaner than persist.
99
171
 
100
172
  ## Network Path
101
173
 
@@ -106,7 +178,7 @@ Use this when the goal is to find or replay a site API.
106
178
  ```bash
107
179
  opensteer open https://example.com --workspace demo
108
180
  opensteer goto https://example.com/search --workspace demo --capture-network page-load
109
- opensteer input 5 "laptop" --workspace demo --press-enter --capture-network search
181
+ opensteer input 5 "laptop" --workspace demo --press-enter --persist "search input" --capture-network search
110
182
  opensteer network query --workspace demo --capture search --json
111
183
  opensteer network detail rec_123 --workspace demo --probe
112
184
  ```
@@ -174,10 +246,7 @@ Flags: `--inline`, `--external`, `--dynamic`, `--workers` to filter by source ty
174
246
  ### Beautify and deobfuscate
175
247
 
176
248
  ```bash
177
- # Format minified code
178
249
  opensteer scripts beautify <artifactId> --workspace demo --persist
179
-
180
- # Deobfuscate packed/obfuscated code
181
250
  opensteer scripts deobfuscate <artifactId> --workspace demo --persist
182
251
  ```
183
252
 
@@ -208,7 +277,7 @@ opensteer scripts sandbox art_ghi789 --workspace demo
208
277
 
209
278
  ## Computer-Use
210
279
 
211
- Use this only when DOM targeting is not enough.
280
+ Use this only when DOM targeting is not enough — canvas, WebGL, or elements that cannot be reached by selector.
212
281
 
213
282
  ```bash
214
283
  opensteer computer click 245 380 --workspace demo --capture-network action
@@ -230,11 +299,10 @@ After coordinate-based actions, switch back to normal extraction or request anal
230
299
  Use when handling OAuth popups, multi-page flows, or any task that opens new tabs.
231
300
 
232
301
  ```bash
233
- opensteer tab list --workspace demo # List all open tabs
234
- opensteer tab new https://example.com --workspace demo # Open new tab
302
+ opensteer tab list --workspace demo
303
+ opensteer tab new https://example.com --workspace demo
235
304
  opensteer tab 2 --workspace demo # Switch to tab 2
236
- opensteer tab close 3 --workspace demo # Close tab 3
237
- opensteer tab close --workspace demo # Close current tab
305
+ opensteer tab close 3 --workspace demo
238
306
  ```
239
307
 
240
308
  ```ts
@@ -250,11 +318,11 @@ Re-snapshot after switching tabs — element numbers are per-page.
250
318
 
251
319
  Each workspace has one browser. Three modes:
252
320
 
253
- | Mode | What it does | Data persists? |
254
- | ------------------------ | ----------------------------------------------- | --------------------------------------------------------------------- |
255
- | **Persistent** (default) | Browser tied to workspace, survives restarts | Yes — cookies, localStorage, logins, history, extensions all retained |
256
- | **Temporary** | Headless browser in `/tmp`, cleaned up on close | No |
257
- | **Attach** | Connects to an already-running browser via CDP | Depends on that browser |
321
+ | Mode | What it does | Data persists? |
322
+ | ------------------------ | ----------------------------------------------- | ----------------------- |
323
+ | **Persistent** (default) | Browser tied to workspace, survives restarts | Yes |
324
+ | **Temporary** | Headless browser in `/tmp`, cleaned up on close | No |
325
+ | **Attach** | Connects to an already-running browser via CDP | Depends on that browser |
258
326
 
259
327
  ### Headless vs headed
260
328
 
@@ -266,99 +334,69 @@ opensteer open https://example.com --workspace demo --headless false
266
334
 
267
335
  Use headed mode for debugging or when the user wants to watch. For hands-free automation, keep headless and use `opensteer view` if a human needs to observe.
268
336
 
269
- ### Persistent sessions
270
-
271
- When you `opensteer open` with a workspace, the browser's full Chrome user-data directory lives at `~/.opensteer/workspaces/<id>/browser/user-data/`. Everything Chrome normally persists (cookies, localStorage, IndexedDB, history, extensions) survives between runs.
272
-
273
- Re-running `opensteer open --workspace demo` reconnects to the existing browser if it's still alive, or launches a fresh one with the same profile if it died.
274
-
275
337
  ### Profile cloning
276
338
 
277
339
  Clone a real user's Chrome profile to start a workspace with their logins already active:
278
340
 
279
341
  ```bash
280
- # Discover available local browsers and profiles
281
342
  opensteer browser discover
282
-
283
- # Clone a profile into a workspace
284
343
  opensteer browser clone --workspace demo \
285
344
  --source-user-data-dir "$HOME/Library/Application Support/Google/Chrome" \
286
345
  --source-profile-directory Default
287
346
  ```
288
347
 
289
- This copies cookies, localStorage, extensions, and settings from the source browser. Caches and lock files are skipped. The source browser does not need to be closed — cloning while running is safe.
290
-
291
- ### Attach to an existing browser
292
-
293
- ```bash
294
- opensteer open https://example.com --workspace demo --attach-endpoint http://localhost:9222
295
- ```
348
+ This copies cookies, localStorage, extensions, and settings. The source browser does not need to be closed.
296
349
 
297
350
  ### Workspace lifecycle
298
351
 
299
352
  ```bash
300
- opensteer browser status --workspace demo # Check if browser is running
353
+ opensteer browser status --workspace demo
301
354
  opensteer browser reset --workspace demo # Wipe browser data, keep workspace
302
355
  opensteer browser delete --workspace demo # Delete workspace entirely
303
356
  ```
304
357
 
305
358
  ## Cloud Mode
306
359
 
307
- Run the browser on Opensteer's cloud infrastructure instead of locally. Use cloud mode when you need browsers that run 24/7, or when the local machine should not run Chromium.
308
-
309
- ### Setup
360
+ Run the browser on Opensteer's cloud infrastructure instead of locally.
310
361
 
311
362
  ```bash
312
- export OPENSTEER_API_KEY=osk_your_key_here # Required
313
- export OPENSTEER_PROVIDER=cloud # Or use --provider cloud per command
363
+ export OPENSTEER_API_KEY=osk_your_key_here
364
+ export OPENSTEER_PROVIDER=cloud
314
365
  ```
315
366
 
316
- ### Usage
317
-
318
367
  All CLI commands work the same with `--provider cloud`:
319
368
 
320
369
  ```bash
321
370
  opensteer open https://example.com --workspace demo --provider cloud
322
371
  opensteer snapshot action --workspace demo
323
- opensteer click 5 --workspace demo
372
+ opensteer click 5 --workspace demo --persist "nav link"
324
373
  ```
325
374
 
326
- ### Export local browser profile to cloud
327
-
328
- Sync a local browser's cookies to a cloud browser profile so the cloud session starts logged in:
375
+ Export a local profile to cloud:
329
376
 
330
377
  ```bash
331
- # Reads cookies from local Chrome, decrypts them, uploads to cloud
332
378
  opensteer browser clone --workspace demo \
333
379
  --source-user-data-dir "$HOME/Library/Application Support/Google/Chrome" \
334
380
  --source-profile-directory Default \
335
381
  --provider cloud
336
382
  ```
337
383
 
338
- The cookies are extracted from the local SQLite database, decrypted, packaged into a portable format, and uploaded. The cloud browser then starts with those cookies applied.
339
-
340
384
  ## Local View
341
385
 
342
- When Opensteer runs headless, a human cannot see what the browser is doing. Local view streams live screenshots from headless sessions to a browser-based viewer.
386
+ Stream live screenshots from headless sessions to a browser-based viewer.
343
387
 
344
388
  ```bash
345
389
  opensteer view # Start viewer service, print URL
346
390
  opensteer view stop # Stop the viewer service
347
- opensteer view --auto # Auto-start viewer on every browser launch
348
- opensteer view --no-auto # Only start viewer when manually requested
391
+ opensteer view --auto # Auto-start on every browser launch
392
+ opensteer view --no-auto # Only start when manually requested
349
393
  ```
350
394
 
351
- The viewer is a local web UI that shows:
352
-
353
- - Live JPEG stream of the active browser tab
354
- - Tab bar with switching
355
- - Navigation controls (back, forward, reload, URL bar)
356
-
357
- Local view is a passive observer — it connects to the browser's existing CDP endpoint. Starting or stopping it has zero impact on running browser sessions.
395
+ Local view is a passive observer. Starting or stopping it has zero impact on running sessions.
358
396
 
359
397
  ## Interaction Capture & Replay
360
398
 
361
- Record a trace of browser interactions (clicks, typing, network, DOM changes) and replay them deterministically. Useful for building repeatable test flows or comparing behavior across runs.
399
+ Record browser interactions and replay them deterministically.
362
400
 
363
401
  ```bash
364
402
  opensteer interaction capture --workspace demo --key "login-flow" --duration 30000
@@ -375,14 +413,11 @@ Commands that use `--persist` save artifacts to the workspace. Read them back wi
375
413
  opensteer artifact read <artifactId> --workspace demo
376
414
  ```
377
415
 
378
- Artifacts are created by `extract --persist`, `scripts capture --persist`, `scripts beautify --persist`, and other persist-enabled commands.
379
-
380
- ## Useful SDK Surface
416
+ ## SDK Surface
381
417
 
382
418
  - `open(url)`, `goto(url, { captureNetwork? })`, `close()`
383
- - `snapshot("action" | "extraction")`
384
419
  - `click()`, `hover()`, `input()`, `scroll()`
385
- - `extract()`
420
+ - `extract({ persist })` — replay-only, no inline templates
386
421
  - `listPages()`, `newPage()`, `activatePage()`, `closePage()`
387
422
  - `network.query()`, `network.detail()`
388
423
  - `waitForPage()`
@@ -395,8 +430,13 @@ Artifacts are created by `extract --persist`, `scripts capture --persist`, `scri
395
430
 
396
431
  ## Guardrails
397
432
 
398
- - Snapshot before using element numbers. Snapshot again after UI changes.
433
+ - Always snapshot before using element numbers. Snapshot again after UI changes.
434
+ - Always include `--persist <key>` on click, hover, input, scroll, and extract.
435
+ - Extraction templates: use 3 items from 3 different positions for reusable descriptors; use all visible items when you need the full data immediately. All fields in each array entry must come from the same card/row.
436
+ - Do not pass templates to the SDK `extract()` — use persist keys only.
437
+ - Re-snapshot after navigation before reusing element numbers.
399
438
  - Do not use `evaluate` for API work — use `exec` or `fetch`.
439
+ - If `fetch()` fails with auth errors, check `state()`, `cookies()`, `storage()` first.
400
440
  - Do not keep the result as a manual-only workflow if the user needs reusable automation.
401
441
  - Prefer a small final script over a large framework.
402
442
  - Close browsers when done. Do not leave headed browser windows open.