opensteer 0.8.12 → 0.8.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,340 +1,214 @@
1
- # Opensteer Package
1
+ # Opensteer
2
2
 
3
- `opensteer` is the main product surface for the repository. It exposes:
3
+ `opensteer` is a browser-backed toolkit for agents exploring websites.
4
4
 
5
- - a semantic SDK with session continuity inside one process
6
- - a thin JSON-first CLI
7
- - a local per-session service so CLI commands can share one live browser session
8
- - browser-native observation and instrumentation primitives
9
- - deterministic replay through request plans, recipes, and saved evidence
10
- - HTML-first snapshots, DOM action replay, computer-use actions, descriptor persistence, traces,
11
- and artifacts
5
+ It focuses on the parts normal code cannot do reliably on its own:
12
6
 
13
- The package is organized around three lanes:
7
+ - capture real browser traffic from real browser actions
8
+ - inspect captured requests without dumping huge raw payloads
9
+ - replay requests with browser-grade transports
10
+ - read browser cookies, storage, and page state
11
+ - turn discoveries into plain TypeScript with `session.fetch()`
14
12
 
15
- - `Interact`: open pages, navigate, evaluate, inspect DOM state, manage pages, use computer actions
16
- - `Observe / Instrument`: capture network, capture scripts, add init scripts, route requests, replace scripts
17
- - `Replay / Execute`: `direct-http`, `context-http`, `page-http`, reverse workflows, request plans, and recipes
13
+ The goal is discovery first, code second. The artifact should usually be working code, not a custom registry abstraction.
18
14
 
19
15
  ## Install
20
16
 
21
- CLI:
22
-
23
- ```bash
24
- npx --yes opensteer@latest skills install
25
- ```
26
-
27
- SDK:
28
-
29
17
  ```bash
30
18
  pnpm add opensteer
31
19
  pnpm exec playwright install chromium
32
20
 
33
21
  # npm
34
22
  npm install opensteer
35
-
36
- # Optional ABP backend for `--engine abp`
37
- pnpm add @opensteer/engine-abp
23
+ npx playwright install chromium
38
24
  ```
39
25
 
40
- `npx --yes opensteer@latest skills install` installs the packaged first-party skill pack
41
- through the upstream `skills` CLI. Use
42
- `npx --yes opensteer@latest skills install --agent claude-code` when you want to target
43
- Claude Code explicitly.
26
+ The package uses the Playwright-backed local engine by default.
44
27
 
45
- `opensteer` installs the Playwright-backed local engine by default. Add
46
- `@opensteer/engine-abp` only when you need the ABP backend.
28
+ ## CLI Quickstart
47
29
 
48
- Cloud features require access to an Opensteer Cloud deployment. This repository includes cloud
49
- client code and shared contracts; the managed Opensteer Cloud service is operated separately.
30
+ ```bash
31
+ opensteer open https://example.com --workspace demo
32
+ opensteer goto https://example.com/search --workspace demo --capture-network search
33
+ opensteer network query --workspace demo --capture search
34
+ opensteer network detail rec_123 --workspace demo
35
+ opensteer replay rec_123 --workspace demo
36
+ opensteer cookies example.com --workspace demo
37
+ opensteer storage example.com --workspace demo
38
+ opensteer state example.com --workspace demo
39
+ opensteer close --workspace demo
40
+ ```
50
41
 
51
- ## SDK
42
+ For DOM exploration:
43
+
44
+ ```bash
45
+ opensteer snapshot action --workspace demo
46
+ opensteer input 5 laptop --workspace demo --persist "search input" --capture-network search
47
+ opensteer click 7 --workspace demo --persist "search button" --capture-network search
48
+ opensteer snapshot extraction --workspace demo
49
+ opensteer extract '{"title":{"element":3}}' --workspace demo --persist "page summary"
50
+ ```
51
+
52
+ ## SDK Quickstart
52
53
 
53
54
  ```ts
54
55
  import { Opensteer } from "opensteer";
55
56
 
56
57
  const opensteer = new Opensteer({
57
- name: "docs-example",
58
+ workspace: "demo",
58
59
  rootDir: process.cwd(),
59
- browser: { headless: true },
60
60
  });
61
61
 
62
- try {
63
- await opensteer.open({
64
- url: "https://example.com",
65
- browser: {
66
- headless: true,
67
- },
68
- });
69
- const snapshot = await opensteer.snapshot("action");
70
- const firstButton = snapshot.counters.find((counter) => counter.tagName === "BUTTON");
71
- if (firstButton) {
72
- await opensteer.click({
73
- element: firstButton.element,
74
- description: "primary button",
75
- });
76
- }
62
+ await opensteer.open("https://example.com");
63
+ await opensteer.goto("https://example.com/search", {
64
+ captureNetwork: "search",
65
+ });
77
66
 
78
- const extracted = await opensteer.extract({
79
- description: "page summary",
80
- schema: {
81
- url: { source: "current_url" },
82
- title: { selector: "title" },
83
- },
84
- });
67
+ const records = await opensteer.network.query({
68
+ capture: "search",
69
+ json: true,
70
+ });
85
71
 
86
- console.log(snapshot.html);
87
- console.log(extracted);
88
- } finally {
89
- await opensteer.close();
90
- }
72
+ const detail = await opensteer.network.detail(records.records[0]!.recordId);
73
+ const replay = await opensteer.network.replay(records.records[0]!.recordId);
74
+
75
+ console.log(detail.summary.url);
76
+ console.log(replay.transport);
91
77
  ```
92
78
 
93
- Browser-backed replay:
79
+ ## `session.fetch()`
80
+
81
+ After discovery, write ordinary TypeScript using `fetch()` on the session.
94
82
 
95
83
  ```ts
96
84
  import { Opensteer } from "opensteer";
97
85
 
98
86
  const opensteer = new Opensteer({
99
- name: "browser-backed-replay",
87
+ workspace: "target",
100
88
  rootDir: process.cwd(),
101
- browser: { headless: true },
102
89
  });
103
90
 
104
- try {
105
- await opensteer.open("https://example.com/app");
106
- const token = await opensteer.evaluate<string>({
107
- script: "() => window.exampleToken",
108
- });
109
-
110
- const response = await opensteer.rawRequest({
111
- transport: "context-http",
112
- url: "https://example.com/api/items",
113
- method: "POST",
114
- body: {
115
- json: { token },
116
- },
117
- });
118
-
119
- console.log(response.data);
120
- } finally {
121
- await opensteer.close();
91
+ async function ensureTargetSession() {
92
+ const cookies = await opensteer.cookies(".target.com");
93
+ if (cookies.has("visitorId")) {
94
+ return;
95
+ }
96
+ await opensteer.goto("https://target.com");
122
97
  }
123
- ```
124
98
 
125
- Attach to an existing CLI-opened local session:
126
-
127
- ```ts
128
- import { Opensteer } from "opensteer";
129
-
130
- const opensteer = Opensteer.attach({
131
- name: "docs-example",
132
- rootDir: process.cwd(),
133
- });
99
+ export async function searchTarget(keyword: string, count = 24) {
100
+ await ensureTargetSession();
101
+
102
+ const response = await opensteer.fetch(
103
+ "https://redsky.target.com/redsky_aggregations/v1/web/plp_search_v2",
104
+ {
105
+ query: {
106
+ keyword,
107
+ count,
108
+ offset: 0,
109
+ channel: "WEB",
110
+ platform: "desktop",
111
+ },
112
+ },
113
+ );
134
114
 
135
- try {
136
- const state = await opensteer.open();
137
- console.log(state.url);
138
- } finally {
139
- await opensteer.disconnect();
115
+ return response.json();
140
116
  }
141
117
  ```
142
118
 
143
- Launch a cloud session with a specific browser profile:
119
+ Transport is selected automatically by default. Force it only when discovery showed a specific requirement:
144
120
 
145
121
  ```ts
146
- import { Opensteer } from "opensteer";
147
-
148
- const opensteer = new Opensteer({
149
- cloud: {
150
- apiKey: process.env.OPENSTEER_API_KEY!,
151
- browserProfile: {
152
- profileId: "bp_123",
153
- reuseIfActive: true,
154
- },
155
- },
122
+ const response = await opensteer.fetch("https://api.example.com/search", {
123
+ query: { keyword: "laptop" },
124
+ transport: "matched-tls",
156
125
  });
157
126
  ```
158
127
 
159
- Sync cookies from a live Chromium browser into an existing cloud profile:
160
-
161
- ```ts
162
- import { OpensteerCloudClient } from "opensteer";
163
-
164
- const client = new OpensteerCloudClient({
165
- apiKey: process.env.OPENSTEER_API_KEY!,
166
- baseUrl: process.env.OPENSTEER_BASE_URL ?? "https://api.opensteer.dev",
167
- });
168
-
169
- await client.syncBrowserProfileCookies({
170
- profileId: "bp_123",
171
- attachEndpoint: "9222",
172
- domains: ["github.com"],
173
- });
174
- ```
128
+ ## Browser State
175
129
 
176
- ## CLI
130
+ Opensteer exposes the browser state agents need for request tracing:
177
131
 
178
- ```bash
179
- opensteer open https://example.com --name docs-example --headless true
180
- opensteer open https://example.com --name docs-example --browser attach-live --attach-endpoint 9222
181
- opensteer open https://example.com --name docs-example --browser snapshot-session \
182
- --source-user-data-dir "~/Library/Application Support/Google/Chrome" \
183
- --source-profile-directory Default
184
- opensteer open https://example.com --name docs-example --browser snapshot-authenticated \
185
- --source-user-data-dir "~/Library/Application Support/Google/Chrome" \
186
- --source-profile-directory "Profile 1"
187
- opensteer browser discover
188
- opensteer browser inspect --endpoint 9222
189
- opensteer local-profile list
190
- opensteer local-profile inspect --user-data-dir "~/Library/Application Support/Opensteer Chrome"
191
- opensteer local-profile unlock --user-data-dir "~/Library/Application Support/Opensteer Chrome"
192
- opensteer profile sync \
193
- --profile-id bp_123 \
194
- --attach-endpoint 9222 \
195
- --domain github.com
196
- opensteer open https://example.com --name docs-example --engine abp
197
- opensteer snapshot action --name docs-example
198
- opensteer click 3 --name docs-example --description "primary button"
199
- opensteer extract --name docs-example --description "page summary" \
200
- --schema '{"url":{"source":"current_url"},"title":{"selector":"title"}}'
201
- opensteer computer '{"type":"screenshot"}' --name docs-example
202
- opensteer close --name docs-example
132
+ ```ts
133
+ const cookies = await opensteer.cookies("example.com");
134
+ const localStorage = await opensteer.storage("example.com", "local");
135
+ const sessionStorage = await opensteer.storage("example.com", "session");
136
+ const state = await opensteer.state("example.com");
203
137
  ```
204
138
 
205
- CLI long flags are canonical kebab-case, for example `--root-dir`, `--network-tag`, and
206
- `--press-enter`. Unknown flags and flags used on the wrong command fail fast instead of being
207
- silently ignored.
208
-
209
- Action and data commands print JSON to stdout. Help commands print human-readable usage text.
210
- Browser state does not live in the CLI process. It lives
211
- in the local session service recorded under `.opensteer/runtime/sessions/<name>/service.json`.
212
- `opensteer computer` prints compact screenshot metadata and points at the persisted image through
213
- `screenshot.path` for local shells and `screenshot.payload.uri` for the canonical file-backed
214
- location.
215
-
216
- Use `--engine <playwright|abp>` on `open` to choose the backend for a new session. You can also
217
- set `OPENSTEER_ENGINE=abp` to change the default engine for `open` in the current shell. Engine
218
- selection is fixed when the session service starts, so `OPENSTEER_ENGINE` and `--engine` only
219
- affect `open`, not commands like `snapshot` or `click` that attach to an existing session.
220
- When using `--engine abp`, Opensteer accepts the ABP launch options it can actually honor:
221
- `--headless`, `--executable-path`, and equivalent `--browser-json` fields for `headless`, `args`,
222
- and `executablePath`. Unsupported shared browser/context options fail fast instead of being
223
- ignored.
224
-
225
- Use `opensteer local-profile inspect` to diagnose whether a live Chromium profile is safe to reuse
226
- as a snapshot source or whether it should be attached via CDP instead. `opensteer local-profile unlock`
227
- remains limited to explicit `stale_lock` recovery; Opensteer does not mutate or take ownership of
228
- real user-data-dirs as part of `open`.
229
-
230
- ## Connect To Real Browser
231
-
232
- - `managed` launches a fresh isolated Chrome/Chromium process with a temporary `user-data-dir`.
233
- - `snapshot-session` copies a source profile into a temporary owned browser directory without full authenticated OS-integrated state. Use it when persisted cookies/storage are enough.
234
- - `snapshot-authenticated` copies a source profile into a temporary owned browser directory and preserves the authenticated browser state needed for harder replay cases.
235
- - `attach-live` connects to an already-running Chrome/Chromium instance. Pass `endpoint` for an explicit CDP target, or omit it to auto-discover a locally attachable browser.
236
-
237
- When you are launching a browser yourself for `attach-live`, prefer a dedicated profile directory:
139
+ `cookies()` returns a lightweight cookie jar:
238
140
 
239
- ```bash
240
- /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
241
- --user-data-dir="$HOME/Library/Application Support/Opensteer Chrome" \
242
- --remote-debugging-port=9222
141
+ ```ts
142
+ cookies.has("session");
143
+ cookies.get("session");
144
+ cookies.getAll();
145
+ cookies.serialize();
243
146
  ```
244
147
 
245
- When attaching to an existing browser, Opensteer may land on an already-open tab such as `chrome://newtab`. Pass `--fresh-tab` when you want a clean working page immediately after attach.
246
-
247
- ## Transport Guide
248
-
249
- - `direct-http`: use when the request is replayable without a browser
250
- - `context-http`: use when browser cookies or browser session state are required
251
- - `page-http`: use when request execution must happen inside the live page JavaScript world
148
+ ## DOM Automation
252
149
 
253
- `goto()` plus `waitForNetwork()` is a separate pattern. It is how you observe the
254
- page's own traffic; it is not a transport.
255
-
256
- ## Session Root
257
-
258
- By default, Opensteer writes into:
150
+ ```ts
151
+ await opensteer.click({ persist: "search button", captureNetwork: "search" });
152
+ await opensteer.input({
153
+ persist: "search input",
154
+ text: "laptop",
155
+ pressEnter: true,
156
+ captureNetwork: "search",
157
+ });
259
158
 
260
- ```text
261
- <cwd>/.opensteer
159
+ const data = await opensteer.extract({
160
+ persist: "page summary",
161
+ schema: {
162
+ title: { selector: "title" },
163
+ url: { source: "current_url" },
164
+ },
165
+ });
262
166
  ```
263
167
 
264
- Important subtrees:
168
+ Use `snapshot("action")` or `snapshot("extraction")` during exploration. The snapshot result is the filtered HTML string, not a huge raw DOM object.
265
169
 
266
- ```text
267
- .opensteer/
268
- artifacts/
269
- traces/
270
- registry/
271
- runtime/
272
- sessions/
273
- ```
170
+ ## Public SDK Surface
274
171
 
275
- ## Public Methods
276
-
277
- - `Opensteer.attach({ name?, rootDir? })`
278
- - `discoverLocalCdpBrowsers({ timeoutMs? })`
279
- - `inspectCdpEndpoint({ endpoint, headers?, timeoutMs? })`
280
- - `inspectLocalBrowserProfile({ userDataDir? })`
281
- - `unlockLocalBrowserProfile({ userDataDir })`
282
- - `open(url | { url?, name?, browser?, context? })`
283
- - `goto(url | { url, captureNetwork? })`
284
- - `evaluate(script | { script, pageRef?, args? })`
285
- - `evaluateJson({ script, pageRef?, args? })`
286
- - `waitForNetwork({ ...filters, pageRef?, includeBodies?, timeoutMs? })`
287
- - `waitForResponse({ ...filters, pageRef?, includeBodies?, timeoutMs? })`
172
+ - `new Opensteer({ workspace?, rootDir?, browser?, provider? })`
173
+ - `open(url | input?)`
174
+ - `info()`
288
175
  - `listPages()`
289
- - `newPage({ url?, openerPageRef? })`
290
- - `activatePage({ pageRef })`
291
- - `closePage({ pageRef })`
292
- - `waitForPage({ openerPageRef?, urlIncludes?, timeoutMs? })`
176
+ - `newPage()`
177
+ - `activatePage()`
178
+ - `closePage()`
179
+ - `goto(url, { captureNetwork? })`
180
+ - `evaluate(script | input)`
181
+ - `evaluateJson(input)`
182
+ - `addInitScript(input)`
293
183
  - `snapshot("action" | "extraction")`
294
- - `click({ element | selector | description, captureNetwork? })`
295
- - `hover({ element | selector | description, captureNetwork? })`
296
- - `input({ element | selector | description, text, captureNetwork? })`
297
- - `scroll({ element | selector | description, direction, amount, captureNetwork? })`
298
- - `extract({ description, schema? })`
299
- - `queryNetwork({ recordId?, requestId?, capture?, tag?, url?, hostname?, path?, method?, status?, resourceType?, pageRef?, includeBodies?, limit? })`
300
- - `tagNetwork({ tag, ...filters })`
301
- - `clearNetwork({ tag? })`
302
- - `captureScripts({ pageRef?, includeInline?, includeExternal?, includeDynamic?, includeWorkers?, urlFilter?, persist? })`
303
- - `addInitScript({ script, args?, pageRef? })`
304
- - `route({ urlPattern, resourceTypes?, times?, handler })`
305
- - `interceptScript({ urlPattern, handler, times? })`
306
- - `rawRequest({ transport?, pageRef?, url, method?, headers?, body?, followRedirects? })`
307
- - `inferRequestPlan({ recordId, key, version, transport? })`
308
- - `writeRequestPlan({ key, version, payload, tags?, provenance?, freshness? })`
309
- - `getRequestPlan({ key, version? })`
310
- - `listRequestPlans({ key? })`
311
- - `writeRecipe({ key, version, payload, tags?, provenance? })`
312
- - `getRecipe({ key, version? })`
313
- - `listRecipes({ key? })`
314
- - `runRecipe({ key, version?, input? })`
315
- - `request(key, { path?, query?, headers?, body? })`
316
- - `computerExecute({ action, screenshot?, captureNetwork? })`
317
- - `disconnect()`
184
+ - `click({ element? | selector? | persist?, captureNetwork? })`
185
+ - `hover({ element? | selector? | persist?, captureNetwork? })`
186
+ - `input({ text, element? | selector? | persist?, captureNetwork? })`
187
+ - `scroll({ direction, amount, element? | selector? | persist?, captureNetwork? })`
188
+ - `extract({ persist, schema? })`
189
+ - `network.query(input?)`
190
+ - `network.detail(recordId)`
191
+ - `network.replay(recordId, overrides?)`
192
+ - `waitForNetwork(input?)`
193
+ - `waitForResponse(input?)`
194
+ - `waitForPage(input?)`
195
+ - `cookies(domain?)`
196
+ - `storage(domain?, "local" | "session")`
197
+ - `state(domain?)`
198
+ - `fetch(url, options?)`
199
+ - `computerExecute(input)`
200
+ - `route(input)`
201
+ - `interceptScript(input)`
202
+ - `browser.status()`
203
+ - `browser.clone(input)`
204
+ - `browser.reset()`
205
+ - `browser.delete()`
318
206
  - `close()`
207
+ - `disconnect()`
319
208
 
320
- `element` targets use counters from the latest snapshot. `description` replays a stored descriptor.
321
- `selector` resolves a CSS selector directly and, when not explicitly scoped, searches the current
322
- page before falling back to child frames.
323
-
324
- Use `disconnect()` for attached sessions when you want to release the SDK handle but keep the
325
- underlying session alive. Use `close()` when you want to destructively end the session.
326
-
327
- Profile inspection is independent from session ownership. `inspectLocalBrowserProfile()` returns a
328
- structured status union (`available`, `unsupported_default_user_data_dir`, `opensteer_owned`,
329
- `browser_owned`, `stale_lock`) that launch, CLI, and SDK all consume. Failed owned launches throw
330
- `OpensteerLocalProfileUnavailableError` with the inspection attached for programmatic handling.
331
-
332
- The reverse-engineering workflow is: perform a browser action, inspect traffic with
333
- `queryNetwork()`, optionally instrument with `addInitScript()`, `route()`, or
334
- `captureScripts()`, experiment with `rawRequest()`, promote a record with
335
- `inferRequestPlan()`, passing `transport` when you have already proven a portable
336
- request path, then replay with `request()`.
209
+ ## Design Notes
337
210
 
338
- `route()` and `interceptScript()` are available on owned local sessions and
339
- cloud-managed sessions. They remain unavailable on attached sessions because
340
- attached browsers do not provide an owned routing surface.
211
+ - `network query` is intentionally summary-oriented. Use `network detail` for deep inspection.
212
+ - `replay` is transport-aware and should usually replace manual probe logic.
213
+ - `browser status` intentionally does not leak the raw browser websocket endpoint.
214
+ - The package also exports advanced cloud and browser-management utilities, but the core agent workflow is the local discovery-first SDK and CLI shown above.