opensteer 0.5.0 → 0.5.2
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/CHANGELOG.md +3 -0
- package/README.md +127 -66
- package/bin/opensteer.mjs +87 -1
- package/dist/cli/skills-installer.cjs +230 -0
- package/dist/cli/skills-installer.d.cts +28 -0
- package/dist/cli/skills-installer.d.ts +28 -0
- package/dist/cli/skills-installer.js +201 -0
- package/package.json +9 -3
- package/skills/README.md +29 -0
- package/skills/electron/SKILL.md +85 -0
- package/skills/electron/references/opensteer-electron-recipes.md +86 -0
- package/skills/electron/references/opensteer-electron-workflow.md +85 -0
- package/skills/opensteer/SKILL.md +168 -0
- package/skills/opensteer/references/cli-reference.md +154 -0
- package/skills/opensteer/references/examples.md +116 -0
- package/skills/opensteer/references/sdk-reference.md +143 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// src/cli/skills-installer.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { existsSync, realpathSync } from "fs";
|
|
5
|
+
import { dirname, join, resolve } from "path";
|
|
6
|
+
var HELP_TEXT = `Usage: opensteer skills <install|add> [options]
|
|
7
|
+
|
|
8
|
+
Installs the first-party Opensteer skill using the upstream "skills" CLI.
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
install Install the opensteer skill
|
|
12
|
+
add Alias for install
|
|
13
|
+
|
|
14
|
+
Supported Options:
|
|
15
|
+
-a, --agent <agents...> Target specific agent(s)
|
|
16
|
+
-g, --global Install globally
|
|
17
|
+
-y, --yes Skip confirmations
|
|
18
|
+
--copy Copy files instead of symlinking
|
|
19
|
+
--all Install to all agents
|
|
20
|
+
-h, --help Show this help
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
opensteer skills install
|
|
24
|
+
opensteer skills add --agent codex --global --yes
|
|
25
|
+
opensteer skills install --all --yes
|
|
26
|
+
`;
|
|
27
|
+
function parseOpensteerSkillsArgs(rawArgs) {
|
|
28
|
+
if (rawArgs.length === 0) {
|
|
29
|
+
return { mode: "help", passthroughArgs: [] };
|
|
30
|
+
}
|
|
31
|
+
const [subcommand, ...rest] = rawArgs;
|
|
32
|
+
if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
33
|
+
return { mode: "help", passthroughArgs: [] };
|
|
34
|
+
}
|
|
35
|
+
if (subcommand !== "install" && subcommand !== "add") {
|
|
36
|
+
return {
|
|
37
|
+
mode: "error",
|
|
38
|
+
passthroughArgs: [],
|
|
39
|
+
error: `Unsupported skills subcommand "${subcommand}". Use "install" or "add".`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const passthroughArgs = [];
|
|
43
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
44
|
+
const arg = rest[i];
|
|
45
|
+
if (arg === "--help" || arg === "-h") {
|
|
46
|
+
return { mode: "help", passthroughArgs: [] };
|
|
47
|
+
}
|
|
48
|
+
if (arg === "--global" || arg === "-g") {
|
|
49
|
+
passthroughArgs.push(arg);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (arg === "--yes" || arg === "-y") {
|
|
53
|
+
passthroughArgs.push(arg);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (arg === "--copy" || arg === "--all") {
|
|
57
|
+
passthroughArgs.push(arg);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (arg === "--agent" || arg === "-a") {
|
|
61
|
+
passthroughArgs.push(arg);
|
|
62
|
+
if (i + 1 >= rest.length || rest[i + 1]?.startsWith("-")) {
|
|
63
|
+
return {
|
|
64
|
+
mode: "error",
|
|
65
|
+
passthroughArgs: [],
|
|
66
|
+
error: `${arg} requires at least one value.`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
while (i + 1 < rest.length && !rest[i + 1]?.startsWith("-")) {
|
|
70
|
+
i += 1;
|
|
71
|
+
const agent = rest[i];
|
|
72
|
+
if (agent) {
|
|
73
|
+
passthroughArgs.push(agent);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (arg.startsWith("-")) {
|
|
79
|
+
return {
|
|
80
|
+
mode: "error",
|
|
81
|
+
passthroughArgs: [],
|
|
82
|
+
error: `Unsupported option "${arg}" for "opensteer skills".`
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
mode: "error",
|
|
87
|
+
passthroughArgs: [],
|
|
88
|
+
error: `Unexpected argument "${arg}".`
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
mode: "install",
|
|
93
|
+
passthroughArgs
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function resolveLocalSkillSourcePath() {
|
|
97
|
+
const packageRoot = resolvePackageRoot();
|
|
98
|
+
const sourcePath = join(packageRoot, "skills");
|
|
99
|
+
if (!existsSync(sourcePath)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Opensteer skill source was not found at "${sourcePath}".`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return sourcePath;
|
|
105
|
+
}
|
|
106
|
+
function resolveSkillsCliPath() {
|
|
107
|
+
const require2 = createRequire(resolveCliEntrypointPath());
|
|
108
|
+
const skillsPackagePath = require2.resolve("skills/package.json");
|
|
109
|
+
const skillsPackageDir = dirname(skillsPackagePath);
|
|
110
|
+
const cliPath = join(skillsPackageDir, "bin", "cli.mjs");
|
|
111
|
+
if (!existsSync(cliPath)) {
|
|
112
|
+
throw new Error(`skills CLI entrypoint was not found at "${cliPath}".`);
|
|
113
|
+
}
|
|
114
|
+
return cliPath;
|
|
115
|
+
}
|
|
116
|
+
function resolveCliEntrypointPath() {
|
|
117
|
+
const cliEntrypoint = process.argv[1];
|
|
118
|
+
if (!cliEntrypoint) {
|
|
119
|
+
throw new Error("Unable to resolve CLI entrypoint path for skills installer.");
|
|
120
|
+
}
|
|
121
|
+
return realpathSync(cliEntrypoint);
|
|
122
|
+
}
|
|
123
|
+
function resolvePackageRoot() {
|
|
124
|
+
const cliEntrypointPath = resolveCliEntrypointPath();
|
|
125
|
+
const binDir = dirname(cliEntrypointPath);
|
|
126
|
+
return resolve(binDir, "..");
|
|
127
|
+
}
|
|
128
|
+
function createSkillsInstallInvocation(args) {
|
|
129
|
+
return {
|
|
130
|
+
cliPath: args.skillsCliPath,
|
|
131
|
+
cliArgs: [
|
|
132
|
+
"add",
|
|
133
|
+
args.localSkillSourcePath,
|
|
134
|
+
"--skill",
|
|
135
|
+
"opensteer",
|
|
136
|
+
...args.passthroughArgs
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function spawnInvocation(invocation) {
|
|
141
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
142
|
+
const child = spawn(process.execPath, [invocation.cliPath, ...invocation.cliArgs], {
|
|
143
|
+
stdio: "inherit",
|
|
144
|
+
env: process.env,
|
|
145
|
+
cwd: process.cwd()
|
|
146
|
+
});
|
|
147
|
+
child.once("error", (error) => {
|
|
148
|
+
rejectPromise(error);
|
|
149
|
+
});
|
|
150
|
+
child.once("exit", (code) => {
|
|
151
|
+
if (typeof code === "number") {
|
|
152
|
+
resolvePromise(code);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
resolvePromise(1);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function createDefaultDeps() {
|
|
160
|
+
return {
|
|
161
|
+
resolveSkillsCliPath,
|
|
162
|
+
resolveLocalSkillSourcePath,
|
|
163
|
+
spawnInvocation,
|
|
164
|
+
writeStdout(message) {
|
|
165
|
+
process.stdout.write(message);
|
|
166
|
+
},
|
|
167
|
+
writeStderr(message) {
|
|
168
|
+
process.stderr.write(message);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async function runOpensteerSkillsInstaller(rawArgs, overrideDeps = {}) {
|
|
173
|
+
const deps = {
|
|
174
|
+
...createDefaultDeps(),
|
|
175
|
+
...overrideDeps
|
|
176
|
+
};
|
|
177
|
+
const parsed = parseOpensteerSkillsArgs(rawArgs);
|
|
178
|
+
if (parsed.mode === "help") {
|
|
179
|
+
deps.writeStdout(HELP_TEXT);
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
if (parsed.mode === "error") {
|
|
183
|
+
deps.writeStderr(`${parsed.error}
|
|
184
|
+
`);
|
|
185
|
+
deps.writeStderr('Run "opensteer skills --help" for usage.\n');
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
const invocation = createSkillsInstallInvocation({
|
|
189
|
+
localSkillSourcePath: deps.resolveLocalSkillSourcePath(),
|
|
190
|
+
passthroughArgs: parsed.passthroughArgs,
|
|
191
|
+
skillsCliPath: deps.resolveSkillsCliPath()
|
|
192
|
+
});
|
|
193
|
+
return await deps.spawnInvocation(invocation);
|
|
194
|
+
}
|
|
195
|
+
export {
|
|
196
|
+
createSkillsInstallInvocation,
|
|
197
|
+
parseOpensteerSkillsArgs,
|
|
198
|
+
resolveLocalSkillSourcePath,
|
|
199
|
+
resolveSkillsCliPath,
|
|
200
|
+
runOpensteerSkillsInstaller
|
|
201
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opensteer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Open-source browser automation SDK with robust selectors and deterministic replay.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
22
|
"bin",
|
|
23
|
+
"skills",
|
|
23
24
|
"README.md",
|
|
24
25
|
"LICENSE",
|
|
25
26
|
"CHANGELOG.md"
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
"dotenv": "^17.2.4",
|
|
38
39
|
"openai": "^6.25.0",
|
|
39
40
|
"playwright": "^1.50.0",
|
|
41
|
+
"skills": "1.4.3",
|
|
40
42
|
"ws": "^8.18.0",
|
|
41
43
|
"zod": "^4.3.6"
|
|
42
44
|
},
|
|
@@ -61,10 +63,14 @@
|
|
|
61
63
|
],
|
|
62
64
|
"repository": {
|
|
63
65
|
"type": "git",
|
|
64
|
-
"url": "https://github.com/
|
|
66
|
+
"url": "https://github.com/steerlabs/opensteer"
|
|
67
|
+
},
|
|
68
|
+
"homepage": "https://opensteer.com",
|
|
69
|
+
"bugs": {
|
|
70
|
+
"url": "https://github.com/steerlabs/opensteer/issues"
|
|
65
71
|
},
|
|
66
72
|
"scripts": {
|
|
67
|
-
"build": "tsup src/index.ts src/cli/server.ts --dts --format esm,cjs --clean --external ai --external zod --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/google --external @ai-sdk/xai --external @ai-sdk/groq --external openai --external @anthropic-ai/sdk --external @google/genai",
|
|
73
|
+
"build": "tsup src/index.ts src/cli/server.ts src/cli/skills-installer.ts --dts --format esm,cjs --clean --external ai --external zod --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/google --external @ai-sdk/xai --external @ai-sdk/groq --external openai --external @anthropic-ai/sdk --external @google/genai",
|
|
68
74
|
"test": "vitest run",
|
|
69
75
|
"test:live-web": "vitest run --config vitest.live-web.config.ts",
|
|
70
76
|
"test:unit": "vitest run tests/html tests/element-path tests/config.test.ts tests/storage",
|
package/skills/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Opensteer Skills
|
|
2
|
+
|
|
3
|
+
This directory contains Opensteer-maintained skill packs.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
Use this structure for each skill:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
skills/<skill-name>/
|
|
11
|
+
SKILL.md
|
|
12
|
+
references/
|
|
13
|
+
*.md
|
|
14
|
+
templates/
|
|
15
|
+
*
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Conventions
|
|
19
|
+
|
|
20
|
+
- Keep one canonical skill per folder (`<skill-name>`).
|
|
21
|
+
- Use `SKILL.md` frontmatter with at least `name` and `description`.
|
|
22
|
+
- Put detailed instructions in `references/` and link them from `SKILL.md`.
|
|
23
|
+
- Put reusable snippets in `templates/` when needed.
|
|
24
|
+
- Use relative Markdown links.
|
|
25
|
+
|
|
26
|
+
## Built-in Skills
|
|
27
|
+
|
|
28
|
+
- `opensteer`: [skills/opensteer/SKILL.md](./opensteer/SKILL.md)
|
|
29
|
+
- `electron`: [skills/electron/SKILL.md](./electron/SKILL.md)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: electron
|
|
3
|
+
description: "Automate Electron desktop apps (Slack, VS Code, Discord, Notion, and other Chromium-based desktop apps) with Opensteer by connecting to a running Chrome DevTools endpoint. Use when tasks require interacting with Electron UI, testing desktop app workflows, extracting structured data from app windows, or capturing Electron screenshots."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Electron App Automation with Opensteer
|
|
7
|
+
|
|
8
|
+
Use this skill when a task targets an Electron desktop app instead of a normal browser tab.
|
|
9
|
+
|
|
10
|
+
Read [opensteer-electron-workflow.md](references/opensteer-electron-workflow.md) first for the Opensteer connection model and execution flow.
|
|
11
|
+
|
|
12
|
+
## Core Workflow
|
|
13
|
+
|
|
14
|
+
1. Launch the Electron app with `--remote-debugging-port=<port>`.
|
|
15
|
+
2. Connect Opensteer with `open --connect-url http://127.0.0.1:<port>`.
|
|
16
|
+
3. List windows/webviews with `tabs`, then switch with `tab-switch`.
|
|
17
|
+
4. Run `snapshot action`, interact (`click`, `input`, `press`), then re-snapshot.
|
|
18
|
+
5. Use `snapshot extraction` and `extract` for structured data.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Connect Opensteer to a running Electron app on port 9222
|
|
22
|
+
opensteer open --connect-url http://127.0.0.1:9222 --session slack-desktop --name slack-desktop
|
|
23
|
+
|
|
24
|
+
# Discover available windows/webviews
|
|
25
|
+
opensteer tabs --session slack-desktop
|
|
26
|
+
opensteer tab-switch 0 --session slack-desktop
|
|
27
|
+
|
|
28
|
+
# Standard action loop
|
|
29
|
+
opensteer snapshot action --session slack-desktop
|
|
30
|
+
opensteer click 12 --session slack-desktop
|
|
31
|
+
opensteer input 8 "release notes" --pressEnter --session slack-desktop
|
|
32
|
+
opensteer snapshot action --session slack-desktop
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Launching Electron Apps
|
|
36
|
+
|
|
37
|
+
If the app is already running, quit it first, then relaunch with the debugging flag.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# macOS examples
|
|
41
|
+
open -a "Slack" --args --remote-debugging-port=9222
|
|
42
|
+
open -a "Visual Studio Code" --args --remote-debugging-port=9223
|
|
43
|
+
open -a "Discord" --args --remote-debugging-port=9224
|
|
44
|
+
|
|
45
|
+
# Linux examples
|
|
46
|
+
slack --remote-debugging-port=9222
|
|
47
|
+
code --remote-debugging-port=9223
|
|
48
|
+
discord --remote-debugging-port=9224
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Structured Extraction Pattern
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
opensteer snapshot extraction --session slack-desktop
|
|
55
|
+
|
|
56
|
+
opensteer extract '{"channels":[{"name":{"element":21},"unread":{"element":22}}]}' \
|
|
57
|
+
--description "sidebar channels with unread state" \
|
|
58
|
+
--session slack-desktop
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Use `--description` when possible so selector paths persist for replay.
|
|
62
|
+
|
|
63
|
+
## Multiple Apps at Once
|
|
64
|
+
|
|
65
|
+
Use one Opensteer session per app:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
opensteer open --connect-url http://127.0.0.1:9222 --session slack --name slack-electron
|
|
69
|
+
opensteer open --connect-url http://127.0.0.1:9223 --session vscode --name vscode-electron
|
|
70
|
+
|
|
71
|
+
opensteer snapshot action --session slack
|
|
72
|
+
opensteer snapshot action --session vscode
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Guardrails
|
|
76
|
+
|
|
77
|
+
- Prefer Opensteer commands over raw Playwright for interaction/extraction.
|
|
78
|
+
- Re-snapshot after each navigation or large UI change before reusing counters.
|
|
79
|
+
- Keep `--name` stable inside a session for deterministic selector replay.
|
|
80
|
+
- Close sessions when done: `opensteer close --session <id>`.
|
|
81
|
+
|
|
82
|
+
## References
|
|
83
|
+
|
|
84
|
+
- [opensteer-electron-workflow.md](references/opensteer-electron-workflow.md)
|
|
85
|
+
- [opensteer-electron-recipes.md](references/opensteer-electron-recipes.md)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Opensteer Electron Recipes
|
|
2
|
+
|
|
3
|
+
## Launch Commands
|
|
4
|
+
|
|
5
|
+
Relaunch the app with a debugging port before connecting.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# macOS
|
|
9
|
+
open -a "Slack" --args --remote-debugging-port=9222
|
|
10
|
+
open -a "Visual Studio Code" --args --remote-debugging-port=9223
|
|
11
|
+
open -a "Discord" --args --remote-debugging-port=9224
|
|
12
|
+
|
|
13
|
+
# Linux
|
|
14
|
+
slack --remote-debugging-port=9222
|
|
15
|
+
code --remote-debugging-port=9223
|
|
16
|
+
discord --remote-debugging-port=9224
|
|
17
|
+
|
|
18
|
+
# Windows (PowerShell examples)
|
|
19
|
+
"$env:LOCALAPPDATA\slack\slack.exe" --remote-debugging-port=9222
|
|
20
|
+
"$env:LOCALAPPDATA\Programs\Microsoft VS Code\Code.exe" --remote-debugging-port=9223
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Optional health check:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
curl -s http://127.0.0.1:9222/json/version
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Connect and Select Target
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
opensteer open --connect-url http://127.0.0.1:9222 --session electron --name electron
|
|
33
|
+
opensteer tabs --session electron
|
|
34
|
+
opensteer tab-switch 0 --session electron
|
|
35
|
+
opensteer snapshot action --session electron
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If the wrong content appears, iterate with `tab-switch` and `snapshot action` until you reach the correct window/webview.
|
|
39
|
+
|
|
40
|
+
## Navigation and Interaction
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
opensteer snapshot action --session electron
|
|
44
|
+
opensteer click 11 --description "left sidebar channel" --session electron
|
|
45
|
+
opensteer input 7 "deployment notes" --pressEnter --description "search input" --session electron
|
|
46
|
+
opensteer press Enter --session electron
|
|
47
|
+
opensteer screenshot electron-state.png --session electron
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Structured Data Extraction
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Inspect extraction-focused DOM
|
|
54
|
+
opensteer snapshot extraction --session electron
|
|
55
|
+
|
|
56
|
+
# Build schema from observed counters
|
|
57
|
+
opensteer extract '{"items":[{"title":{"element":15},"meta":{"element":16}}]}' \
|
|
58
|
+
--description "visible item list with metadata" \
|
|
59
|
+
--session electron
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Multi-App Pattern
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Slack
|
|
66
|
+
opensteer open --connect-url http://127.0.0.1:9222 --session slack --name slack-electron
|
|
67
|
+
|
|
68
|
+
# VS Code
|
|
69
|
+
opensteer open --connect-url http://127.0.0.1:9223 --session vscode --name vscode-electron
|
|
70
|
+
|
|
71
|
+
opensteer snapshot action --session slack
|
|
72
|
+
opensteer snapshot action --session vscode
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Troubleshooting
|
|
76
|
+
|
|
77
|
+
- `connection refused`:
|
|
78
|
+
- App was not launched with `--remote-debugging-port`.
|
|
79
|
+
- Port is wrong or blocked by another process.
|
|
80
|
+
- Empty or incorrect snapshot:
|
|
81
|
+
- You are in the wrong target window; use `tabs` and `tab-switch`.
|
|
82
|
+
- The app has not finished rendering; wait briefly and snapshot again.
|
|
83
|
+
- Counter failures (`element not found`):
|
|
84
|
+
- UI changed and counters are stale; take a fresh `snapshot action`.
|
|
85
|
+
- Wrong selectors on replay:
|
|
86
|
+
- `--description` string differs from the original text; use exact wording.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Opensteer Electron Workflow
|
|
2
|
+
|
|
3
|
+
This document describes the Opensteer-native flow for automating Electron apps.
|
|
4
|
+
|
|
5
|
+
## 1. Connection Model
|
|
6
|
+
|
|
7
|
+
Electron apps embed Chromium. Opensteer connects through Chrome DevTools Protocol (CDP):
|
|
8
|
+
|
|
9
|
+
- Launch app with `--remote-debugging-port=<port>`
|
|
10
|
+
- Attach with `opensteer open --connect-url http://127.0.0.1:<port>`
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
opensteer open --connect-url http://127.0.0.1:9222 --session electron --name electron
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`--session` controls runtime routing (which daemon/browser instance handles commands).
|
|
19
|
+
`--name` controls selector cache namespace for deterministic replay.
|
|
20
|
+
|
|
21
|
+
## 2. Window/Webview Targeting
|
|
22
|
+
|
|
23
|
+
Electron apps often expose multiple targets. After connecting:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
opensteer tabs --session electron
|
|
27
|
+
opensteer tab-switch 0 --session electron
|
|
28
|
+
opensteer snapshot action --session electron
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Iterate `tab-switch` + `snapshot action` until you are on the correct app window/webview.
|
|
32
|
+
|
|
33
|
+
## 3. Action Loop
|
|
34
|
+
|
|
35
|
+
Use this loop for all interactions:
|
|
36
|
+
|
|
37
|
+
1. `snapshot action`
|
|
38
|
+
2. `click` / `input` / `press`
|
|
39
|
+
3. re-run `snapshot action`
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
opensteer snapshot action --session electron
|
|
45
|
+
opensteer click 10 --description "navigation item" --session electron
|
|
46
|
+
opensteer input 6 "release checklist" --pressEnter --description "search input" --session electron
|
|
47
|
+
opensteer snapshot action --session electron
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 4. Extraction Loop
|
|
51
|
+
|
|
52
|
+
For structured data:
|
|
53
|
+
|
|
54
|
+
1. `snapshot extraction`
|
|
55
|
+
2. build schema from element counters
|
|
56
|
+
3. `extract <schema-json>`
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
opensteer snapshot extraction --session electron
|
|
62
|
+
opensteer extract '{"rows":[{"title":{"element":14},"status":{"element":15}}]}' \
|
|
63
|
+
--description "current visible rows with status" \
|
|
64
|
+
--session electron
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 5. Replay Stability
|
|
68
|
+
|
|
69
|
+
- Keep `--name` stable for the same automation namespace.
|
|
70
|
+
- Keep `--description` text exact across runs.
|
|
71
|
+
- Re-snapshot after meaningful UI changes before reusing counters.
|
|
72
|
+
|
|
73
|
+
## 6. Session Cleanup
|
|
74
|
+
|
|
75
|
+
Close the session when done:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
opensteer close --session electron
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or list active sessions:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
opensteer sessions
|
|
85
|
+
```
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: opensteer
|
|
3
|
+
description: "Browser automation, web scraping, and structured data extraction using Opensteer CLI and SDK. Use when the agent needs to: navigate web pages, interact with elements (click, type, select, hover), extract structured data from pages, take snapshots or screenshots, manage browser tabs and cookies, or generate scraper/automation scripts. Also use when the user asks to create a scraper, automation script, or replay a browsing session as code."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Opensteer Browser Automation
|
|
7
|
+
|
|
8
|
+
Opensteer provides persistent browser automation via a CLI and TypeScript SDK. It maintains browser sessions across calls and caches resolved element paths for deterministic replay.
|
|
9
|
+
|
|
10
|
+
## CRITICAL: Always Use Opensteer Methods Over Playwright
|
|
11
|
+
|
|
12
|
+
Opensteer methods are optimized for scraping — they handle waiting, element resolution, and selector caching automatically. **Never use raw Playwright when an Opensteer method exists.**
|
|
13
|
+
|
|
14
|
+
| Wrong (raw Playwright) | Right (Opensteer) |
|
|
15
|
+
| ----------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
16
|
+
| `page.evaluate(() => [...document.querySelectorAll('.item')].map(...))` | `opensteer.extract({ description: "product listing" })` |
|
|
17
|
+
| `page.click('.submit')` | `opensteer.click({ description: "the submit button" })` |
|
|
18
|
+
| `page.fill('#search', 'query')` | `opensteer.input({ description: "search input", text: "q" })` |
|
|
19
|
+
|
|
20
|
+
**Why:** `opensteer.extract()` caches structural selectors that work across pages sharing the same template. Raw `querySelectorAll` is brittle, non-replayable, and bypasses the caching system. The only valid use of `opensteer.page.evaluate()` is calling `fetch()` for API-based extraction when a site has internal REST/GraphQL endpoints.
|
|
21
|
+
|
|
22
|
+
## Default Workflow
|
|
23
|
+
|
|
24
|
+
**Always use the CLI for exploration first. Only write scripts when the user asks.**
|
|
25
|
+
|
|
26
|
+
1. **Explore with CLI** — Open pages, snapshot, interact with elements interactively
|
|
27
|
+
2. **Cache selectors** — Re-run actions with `--description` flags to cache element paths for replay
|
|
28
|
+
3. **Cache extractions** — Run `extract` with `--description` for every page type the scraper will visit
|
|
29
|
+
4. **Generate script** — Use cached descriptions in TypeScript (no counters needed)
|
|
30
|
+
|
|
31
|
+
**Namespace links CLI and SDK.** The `--name` flag on `opensteer open` defines the cache namespace. `new Opensteer({ name: "..." })` in the SDK reads from the same cache. These must match.
|
|
32
|
+
|
|
33
|
+
## CLI Exploration
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# 1. Set session once per shell
|
|
37
|
+
export OPENSTEER_SESSION=my-session
|
|
38
|
+
|
|
39
|
+
# 2. Open page with namespace
|
|
40
|
+
opensteer open https://example.com/products --name "product-scraper"
|
|
41
|
+
|
|
42
|
+
# 3. Snapshot for interactions or data
|
|
43
|
+
opensteer snapshot action # Interactive elements with counters
|
|
44
|
+
opensteer snapshot extraction # Data-oriented HTML with counters
|
|
45
|
+
|
|
46
|
+
# 4. Interact using counter numbers from snapshot
|
|
47
|
+
opensteer click 3
|
|
48
|
+
opensteer input 5 "laptop" --pressEnter
|
|
49
|
+
|
|
50
|
+
# 5. Cache actions with --description for replay
|
|
51
|
+
opensteer click 3 --description "the products link"
|
|
52
|
+
opensteer input 5 "laptop" --pressEnter --description "the search input"
|
|
53
|
+
|
|
54
|
+
# 6. Extract data: snapshot extraction → identify counters → extract with schema
|
|
55
|
+
opensteer snapshot extraction
|
|
56
|
+
opensteer extract '{"products":[{"name":{"element":11},"price":{"element":12}},{"name":{"element":25},"price":{"element":26}}]}' \
|
|
57
|
+
--description "product listing with name and price"
|
|
58
|
+
|
|
59
|
+
# 7. Cache extractions for ALL page types the scraper will visit
|
|
60
|
+
opensteer click 11 --description "first product link"
|
|
61
|
+
opensteer snapshot extraction
|
|
62
|
+
opensteer extract '{"title":{"element":3},"price":{"element":7}}' \
|
|
63
|
+
--description "product detail page"
|
|
64
|
+
|
|
65
|
+
# 8. Always close when done
|
|
66
|
+
opensteer close
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Key rules:**
|
|
70
|
+
|
|
71
|
+
- Set `--name` on `open` to define cache namespace
|
|
72
|
+
- Specify snapshot mode explicitly: `action` (interactions) or `extraction` (data)
|
|
73
|
+
- `snapshot extraction` shows structure; `extract` produces JSON — never parse snapshot HTML manually
|
|
74
|
+
- Use `--description` to cache selectors for replay (one character difference = cache miss)
|
|
75
|
+
- For arrays, include all items in the schema — Opensteer caches the structural pattern and finds all matches on replay
|
|
76
|
+
- `open` does raw `page.goto()`; use `navigate` for subsequent pages (includes stability wait)
|
|
77
|
+
- Re-snapshot after navigation or significant page changes
|
|
78
|
+
|
|
79
|
+
## Writing Scraper Scripts
|
|
80
|
+
|
|
81
|
+
Read [sdk-reference.md](references/sdk-reference.md) for exact method signatures before writing any script.
|
|
82
|
+
|
|
83
|
+
### Template
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { Opensteer } from "opensteer";
|
|
87
|
+
|
|
88
|
+
async function run() {
|
|
89
|
+
const opensteer = new Opensteer({
|
|
90
|
+
name: "product-scraper", // MUST match --name from CLI exploration
|
|
91
|
+
storage: { rootDir: process.cwd() },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await opensteer.launch({ headless: false });
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await opensteer.goto("https://example.com/products");
|
|
98
|
+
|
|
99
|
+
await opensteer.input({
|
|
100
|
+
text: "laptop",
|
|
101
|
+
description: "the search input", // exact match to CLI --description
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Use extract with description — no schema needed when cache exists
|
|
105
|
+
const data = await opensteer.extract({
|
|
106
|
+
description: "product listing with name and price",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
console.log(JSON.stringify(data, null, 2));
|
|
110
|
+
} finally {
|
|
111
|
+
await opensteer.close();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
run().catch((err) => {
|
|
116
|
+
console.error(err);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Script Rules
|
|
122
|
+
|
|
123
|
+
- No top-level `await` — wrap in `async function run()` + `run().catch(...)`
|
|
124
|
+
- Default to `headless: false` (many sites block headless)
|
|
125
|
+
- Use cached `description` strings for all interactions and extractions
|
|
126
|
+
- Do NOT add wait calls before SDK actions — they handle waiting internally
|
|
127
|
+
- Use `opensteer.waitForText("literal text")` or `page.waitForSelector("css")` only for page transitions or confirming SPA content loaded
|
|
128
|
+
- Run with: `npx tsx scraper.ts`
|
|
129
|
+
|
|
130
|
+
## Browser Connection
|
|
131
|
+
|
|
132
|
+
- **Sandbox (default):** `opensteer open <url>` — fresh Chromium, no user sessions
|
|
133
|
+
- **Connect (existing browser):** `opensteer open --connect-url http://localhost:9222` — attach to a running CDP-enabled browser. Verify CDP: `curl -s http://127.0.0.1:9222/json/version`
|
|
134
|
+
|
|
135
|
+
## Element Targeting (preference order)
|
|
136
|
+
|
|
137
|
+
1. **Counter** (from snapshot): `click 5` — fast, needs fresh snapshot
|
|
138
|
+
2. **Description** (cached): `click --description "the submit button"` — replayable
|
|
139
|
+
3. **CSS selector**: `click --selector "#btn"` — explicit but brittle
|
|
140
|
+
|
|
141
|
+
## Snapshot Modes
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
opensteer snapshot action # Interactable elements (default)
|
|
145
|
+
opensteer snapshot extraction # Flattened HTML for data extraction
|
|
146
|
+
opensteer snapshot clickable # Only clickable elements
|
|
147
|
+
opensteer snapshot scrollable # Only scrollable containers
|
|
148
|
+
opensteer snapshot full # Raw HTML — only for debugging
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
All modes except `full` are intelligently filtered to show only relevant elements with counters.
|
|
152
|
+
|
|
153
|
+
## Debugging
|
|
154
|
+
|
|
155
|
+
When a scraper produces wrong or missing data, diagnose in this order:
|
|
156
|
+
|
|
157
|
+
1. **Timing** — SPA content not rendered. Add `waitForSelector` or `waitForText` before extraction.
|
|
158
|
+
2. **Missing cache** — Forgot to cache extraction during CLI exploration for a page type.
|
|
159
|
+
3. **Obstacles** — Cookie banners, modals, or login walls blocking the target.
|
|
160
|
+
4. **Missing data** — Some pages genuinely lack certain fields. Handle with null checks.
|
|
161
|
+
|
|
162
|
+
**Do NOT replace `opensteer.extract()` with `page.evaluate()` + `querySelectorAll` when debugging.** The extraction logic is not the problem — fix timing, caching, or obstacles instead.
|
|
163
|
+
|
|
164
|
+
## Reference
|
|
165
|
+
|
|
166
|
+
- CLI commands: [cli-reference.md](references/cli-reference.md)
|
|
167
|
+
- SDK API: [sdk-reference.md](references/sdk-reference.md)
|
|
168
|
+
- Full examples: [examples.md](references/examples.md)
|