opensteer 0.5.1 → 0.5.3
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 +123 -85
- 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 +8 -2
- 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
package/CHANGELOG.md
CHANGED
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
timeout/stale-target cases more accurately.
|
|
37
37
|
- Cloud action failures now accept optional structured failure details and map
|
|
38
38
|
them to `OpensteerActionError` when available.
|
|
39
|
+
- Added native skills installer commands (`opensteer skills install` and
|
|
40
|
+
`opensteer skills add`) that wrap the upstream `skills` CLI to install the
|
|
41
|
+
first-party `opensteer` skill pack without requiring separate global setup.
|
|
39
42
|
- Docs: refreshed README and getting-started guidance to match current SDK/CLI
|
|
40
43
|
behavior and env vars.
|
|
41
44
|
- Docs: added CLI reference and docs index.
|
package/README.md
CHANGED
|
@@ -1,150 +1,179 @@
|
|
|
1
1
|
# Opensteer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Browser automation framework for developers and AI agents with deterministic replay.
|
|
4
4
|
|
|
5
|
-
Opensteer
|
|
6
|
-
|
|
5
|
+
Opensteer gives you one API for local and cloud runs, description-based actions,
|
|
6
|
+
structured extraction, and CUA agent workflows.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Install
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- A browser environment supported by Playwright
|
|
12
|
-
- API key for your selected model provider if you use LLM resolve/extract
|
|
10
|
+
Main setup (recommended):
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
```bash
|
|
13
|
+
npm i -g opensteer
|
|
14
|
+
opensteer skills install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
SDK package (when importing `Opensteer` in app code):
|
|
15
18
|
|
|
16
19
|
```bash
|
|
17
20
|
# npm
|
|
18
21
|
npm install opensteer
|
|
22
|
+
|
|
19
23
|
# pnpm
|
|
20
24
|
pnpm add opensteer
|
|
25
|
+
|
|
21
26
|
# bun
|
|
22
27
|
bun add opensteer
|
|
23
28
|
```
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Node.js `>=20`
|
|
33
|
+
- Playwright-supported browser runtime
|
|
34
|
+
- Model provider API key for LLM-powered resolution/extraction/CUA
|
|
27
35
|
|
|
28
|
-
If
|
|
36
|
+
If browser binaries are missing:
|
|
29
37
|
|
|
30
38
|
```bash
|
|
31
39
|
npx playwright install chromium
|
|
32
40
|
```
|
|
33
41
|
|
|
34
|
-
##
|
|
42
|
+
## What It Does
|
|
43
|
+
|
|
44
|
+
- Unified local/cloud execution with the same API surface
|
|
45
|
+
- Descriptor-aware actions with selector persistence for replay
|
|
46
|
+
- Structured extraction with typed schemas
|
|
47
|
+
- CUA agent support (`openai`, `anthropic`, `google`)
|
|
48
|
+
|
|
49
|
+
## Quick Start: SDK
|
|
35
50
|
|
|
36
51
|
```ts
|
|
37
52
|
import { Opensteer } from "opensteer";
|
|
38
53
|
|
|
39
|
-
const opensteer = new Opensteer({ name: "
|
|
40
|
-
await opensteer.launch({ headless: false });
|
|
54
|
+
const opensteer = new Opensteer({ name: "quickstart" });
|
|
41
55
|
|
|
42
56
|
try {
|
|
57
|
+
await opensteer.launch();
|
|
43
58
|
await opensteer.goto("https://example.com");
|
|
44
|
-
const html = await opensteer.snapshot();
|
|
45
|
-
console.log(html.slice(0, 500));
|
|
46
59
|
|
|
47
|
-
await opensteer.
|
|
60
|
+
await opensteer.snapshot({ mode: "action" });
|
|
61
|
+
await opensteer.click({ description: "main call to action" });
|
|
62
|
+
|
|
63
|
+
await opensteer.snapshot({ mode: "extraction" });
|
|
64
|
+
const data = await opensteer.extract({
|
|
65
|
+
description: "hero section",
|
|
66
|
+
schema: { title: "string", href: "string" },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
console.log(data);
|
|
48
70
|
} finally {
|
|
49
71
|
await opensteer.close();
|
|
50
72
|
}
|
|
51
73
|
```
|
|
52
74
|
|
|
53
|
-
## CUA Agent
|
|
75
|
+
## Quick Start: CUA Agent
|
|
54
76
|
|
|
55
77
|
```ts
|
|
56
78
|
import { Opensteer } from "opensteer";
|
|
57
79
|
|
|
58
|
-
const opensteer = new Opensteer({
|
|
59
|
-
model: "openai/computer-use-preview",
|
|
60
|
-
});
|
|
80
|
+
const opensteer = new Opensteer({ model: "openai/computer-use-preview" });
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
try {
|
|
83
|
+
await opensteer.launch();
|
|
84
|
+
const agent = opensteer.agent({ mode: "cua" });
|
|
85
|
+
const result = await agent.execute({
|
|
86
|
+
instruction: "Go to Hacker News and open the top story.",
|
|
87
|
+
maxSteps: 20,
|
|
88
|
+
});
|
|
89
|
+
console.log(result.message);
|
|
90
|
+
} finally {
|
|
91
|
+
await opensteer.close();
|
|
92
|
+
}
|
|
93
|
+
```
|
|
63
94
|
|
|
64
|
-
|
|
65
|
-
mode: "cua",
|
|
66
|
-
});
|
|
95
|
+
## Quick Start: CLI
|
|
67
96
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
highlightCursor: true,
|
|
72
|
-
});
|
|
97
|
+
```bash
|
|
98
|
+
# Open a browser session and bind a selector namespace
|
|
99
|
+
opensteer open https://example.com --session demo --name quickstart
|
|
73
100
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
101
|
+
# Action snapshot + interaction
|
|
102
|
+
opensteer snapshot action --session demo
|
|
103
|
+
opensteer click --description "main call to action" --session demo
|
|
77
104
|
|
|
78
|
-
|
|
105
|
+
# Extraction snapshot + structured extract
|
|
106
|
+
opensteer snapshot extraction --session demo
|
|
107
|
+
opensteer extract '{"title":"string","href":"string"}' --description "hero section" --session demo
|
|
79
108
|
|
|
80
|
-
|
|
109
|
+
# Close session
|
|
110
|
+
opensteer close --session demo
|
|
111
|
+
```
|
|
81
112
|
|
|
82
|
-
|
|
113
|
+
For non-interactive runs, set `OPENSTEER_SESSION` or `OPENSTEER_CLIENT_ID`.
|
|
83
114
|
|
|
84
|
-
|
|
85
|
-
- Selector namespace: `--name` or `OPENSTEER_NAME` (used by `open`)
|
|
115
|
+
## For AI Agents
|
|
86
116
|
|
|
87
|
-
|
|
88
|
-
opensteer open https://example.com --session agent-a --name product-scraper
|
|
89
|
-
opensteer snapshot --session agent-a
|
|
90
|
-
opensteer click 3 --session agent-a
|
|
91
|
-
opensteer status --session agent-a
|
|
92
|
-
opensteer close --session agent-a
|
|
93
|
-
```
|
|
117
|
+
Use this workflow to keep scripts replayable and maintainable:
|
|
94
118
|
|
|
95
|
-
|
|
96
|
-
`
|
|
119
|
+
1. Use Opensteer APIs (`goto`, `snapshot`, `click`, `input`, `extract`) instead of raw Playwright calls.
|
|
120
|
+
2. Keep namespace consistent: SDK `name` must match CLI `--name`.
|
|
121
|
+
3. Take `snapshot({ mode: "action" })` before actions and `snapshot({ mode: "extraction" })` before extraction.
|
|
122
|
+
4. Prefer `description` targeting for persistence and deterministic reruns.
|
|
123
|
+
5. Always wrap runs in `try/finally` and call `close()`.
|
|
97
124
|
|
|
98
|
-
|
|
125
|
+
First-party skills:
|
|
99
126
|
|
|
100
|
-
|
|
127
|
+
- [skills/opensteer/SKILL.md](skills/opensteer/SKILL.md)
|
|
128
|
+
- [skills/electron/SKILL.md](skills/electron/SKILL.md)
|
|
129
|
+
- [skills/README.md](skills/README.md)
|
|
101
130
|
|
|
102
|
-
|
|
103
|
-
2. Use `element` counter from snapshot
|
|
104
|
-
3. Use explicit CSS `selector`
|
|
105
|
-
4. Use built-in LLM resolution (`description` required)
|
|
106
|
-
5. Throw actionable error
|
|
131
|
+
Install the Opensteer skill pack:
|
|
107
132
|
|
|
108
|
-
|
|
109
|
-
|
|
133
|
+
```bash
|
|
134
|
+
opensteer skills install
|
|
135
|
+
```
|
|
110
136
|
|
|
111
|
-
|
|
137
|
+
Fallback (direct upstream `skills` CLI):
|
|
112
138
|
|
|
113
|
-
|
|
139
|
+
```bash
|
|
140
|
+
npx skills add https://github.com/steerlabs/opensteer-skills --skill opensteer
|
|
141
|
+
```
|
|
114
142
|
|
|
115
|
-
|
|
116
|
-
- `OPENSTEER_API_KEY` or `cloud.apiKey` required in cloud mode
|
|
117
|
-
- `OPENSTEER_BASE_URL` or `cloud.baseUrl` to override the default cloud host
|
|
118
|
-
- `OPENSTEER_AUTH_SCHEME` or `cloud.authScheme` for auth header mode
|
|
119
|
-
(`api-key` or `bearer`)
|
|
120
|
-
- `cloud: true` or a `cloud` options object overrides `OPENSTEER_MODE`
|
|
143
|
+
Claude Code marketplace plugin:
|
|
121
144
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
```text
|
|
146
|
+
/plugin marketplace add steerlabs/opensteer
|
|
147
|
+
/plugin install opensteer@opensteer-marketplace
|
|
148
|
+
```
|
|
126
149
|
|
|
127
|
-
##
|
|
150
|
+
## Cloud Mode
|
|
128
151
|
|
|
129
|
-
Opensteer
|
|
130
|
-
[`skills/`](skills/README.md).
|
|
152
|
+
Opensteer defaults to local mode. Enable cloud mode with env or constructor options:
|
|
131
153
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
154
|
+
```bash
|
|
155
|
+
OPENSTEER_MODE=cloud
|
|
156
|
+
OPENSTEER_API_KEY=<your_api_key>
|
|
157
|
+
```
|
|
136
158
|
|
|
137
|
-
|
|
159
|
+
- `OPENSTEER_BASE_URL` overrides the default cloud host
|
|
160
|
+
- `OPENSTEER_AUTH_SCHEME` supports `api-key` (default) or `bearer`
|
|
161
|
+
- `cloud: true` or a `cloud` options object overrides `OPENSTEER_MODE`
|
|
162
|
+
- Cloud mode is fail-fast (no automatic fallback to local)
|
|
163
|
+
- `Opensteer.from(page)`, `uploadFile`, `exportCookies`, and `importCookies` are local-only
|
|
138
164
|
|
|
139
|
-
|
|
140
|
-
[`.claude-plugin/marketplace.json`](.claude-plugin/marketplace.json)
|
|
165
|
+
## Resolution and Replay
|
|
141
166
|
|
|
142
|
-
|
|
167
|
+
For descriptor-aware actions (`click`, `input`, `hover`, `select`, `scroll`):
|
|
143
168
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
169
|
+
1. Reuse persisted selector path from `description`
|
|
170
|
+
2. Try snapshot counter (`element`)
|
|
171
|
+
3. Try explicit CSS selector (`selector`)
|
|
172
|
+
4. Use LLM resolution (`description` required)
|
|
173
|
+
5. Return actionable error
|
|
174
|
+
|
|
175
|
+
When step 2-4 succeeds and `description` is present, selector paths are cached
|
|
176
|
+
in `.opensteer/selectors/<namespace>` for deterministic replay.
|
|
148
177
|
|
|
149
178
|
## Docs
|
|
150
179
|
|
|
@@ -157,12 +186,21 @@ Install from Claude Code:
|
|
|
157
186
|
- [Live Web Validation Suite](docs/live-web-tests.md)
|
|
158
187
|
- [Skills](docs/skills.md)
|
|
159
188
|
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
pnpm install
|
|
193
|
+
pnpm typecheck
|
|
194
|
+
pnpm test
|
|
195
|
+
pnpm build
|
|
196
|
+
```
|
|
197
|
+
|
|
160
198
|
## Community
|
|
161
199
|
|
|
162
|
-
- [Contributing
|
|
200
|
+
- [Contributing](CONTRIBUTING.md)
|
|
163
201
|
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
164
202
|
- [Security Policy](SECURITY.md)
|
|
165
|
-
- [
|
|
203
|
+
- [Discussions](https://github.com/steerlabs/opensteer/discussions)
|
|
166
204
|
- [Changelog](CHANGELOG.md)
|
|
167
205
|
|
|
168
206
|
## License
|
package/bin/opensteer.mjs
CHANGED
|
@@ -14,10 +14,38 @@ import {
|
|
|
14
14
|
import { connect } from 'net'
|
|
15
15
|
import { tmpdir } from 'os'
|
|
16
16
|
import { dirname, join } from 'path'
|
|
17
|
-
import { fileURLToPath } from 'url'
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from 'url'
|
|
18
18
|
|
|
19
19
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
20
20
|
const SERVER_SCRIPT = join(__dirname, '..', 'dist', 'cli', 'server.js')
|
|
21
|
+
const SKILLS_INSTALLER_SCRIPT = join(
|
|
22
|
+
__dirname,
|
|
23
|
+
'..',
|
|
24
|
+
'dist',
|
|
25
|
+
'cli',
|
|
26
|
+
'skills-installer.js'
|
|
27
|
+
)
|
|
28
|
+
const SKILLS_HELP_TEXT = `Usage: opensteer skills <install|add> [options]
|
|
29
|
+
|
|
30
|
+
Installs the first-party Opensteer skill using the upstream "skills" CLI.
|
|
31
|
+
|
|
32
|
+
Commands:
|
|
33
|
+
install Install the opensteer skill
|
|
34
|
+
add Alias for install
|
|
35
|
+
|
|
36
|
+
Supported Options:
|
|
37
|
+
-a, --agent <agents...> Target specific agent(s)
|
|
38
|
+
-g, --global Install globally
|
|
39
|
+
-y, --yes Skip confirmations
|
|
40
|
+
--copy Copy files instead of symlinking
|
|
41
|
+
--all Install to all agents
|
|
42
|
+
-h, --help Show this help
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
opensteer skills install
|
|
46
|
+
opensteer skills add --agent codex --global --yes
|
|
47
|
+
opensteer skills install --all --yes
|
|
48
|
+
`
|
|
21
49
|
|
|
22
50
|
const CONNECT_TIMEOUT = 15000
|
|
23
51
|
const POLL_INTERVAL = 100
|
|
@@ -735,6 +763,46 @@ function toObject(value) {
|
|
|
735
763
|
return value
|
|
736
764
|
}
|
|
737
765
|
|
|
766
|
+
function isSkillsHelpRequest(args) {
|
|
767
|
+
if (args.length === 0) return true
|
|
768
|
+
|
|
769
|
+
const [subcommand, ...rest] = args
|
|
770
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
771
|
+
return true
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (subcommand !== 'install' && subcommand !== 'add') {
|
|
775
|
+
return false
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return rest.includes('--help') || rest.includes('-h')
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function printSkillsHelp() {
|
|
782
|
+
process.stdout.write(SKILLS_HELP_TEXT)
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async function runSkillsSubcommand(args) {
|
|
786
|
+
if (isSkillsHelpRequest(args)) {
|
|
787
|
+
printSkillsHelp()
|
|
788
|
+
return
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (!existsSync(SKILLS_INSTALLER_SCRIPT)) {
|
|
792
|
+
throw new Error(
|
|
793
|
+
`Skills installer module not found: ${SKILLS_INSTALLER_SCRIPT}. Run the build script first.`
|
|
794
|
+
)
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const moduleUrl = pathToFileURL(SKILLS_INSTALLER_SCRIPT).href
|
|
798
|
+
const { runOpensteerSkillsInstaller } = await import(moduleUrl)
|
|
799
|
+
|
|
800
|
+
const exitCode = await runOpensteerSkillsInstaller(args)
|
|
801
|
+
if (exitCode !== 0) {
|
|
802
|
+
process.exit(exitCode)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
738
806
|
function printHelp() {
|
|
739
807
|
console.log(`Usage: opensteer <command> [options]
|
|
740
808
|
|
|
@@ -794,6 +862,11 @@ Utility:
|
|
|
794
862
|
wait-selector <selector> Wait for selector
|
|
795
863
|
extract <schema-json> Extract structured data
|
|
796
864
|
|
|
865
|
+
Skills:
|
|
866
|
+
skills install [options] Install Opensteer skill pack for supported agents
|
|
867
|
+
skills add [options] Alias for "skills install"
|
|
868
|
+
skills --help Show skills installer help
|
|
869
|
+
|
|
797
870
|
Global Flags:
|
|
798
871
|
--session <id> Runtime session id for daemon/browser routing
|
|
799
872
|
--name <namespace> Selector namespace for cache storage on 'open'
|
|
@@ -820,6 +893,19 @@ Environment:
|
|
|
820
893
|
}
|
|
821
894
|
|
|
822
895
|
async function main() {
|
|
896
|
+
const rawArgs = process.argv.slice(2)
|
|
897
|
+
if (rawArgs[0] === 'skills') {
|
|
898
|
+
try {
|
|
899
|
+
await runSkillsSubcommand(rawArgs.slice(1))
|
|
900
|
+
} catch (err) {
|
|
901
|
+
const message =
|
|
902
|
+
err instanceof Error ? err.message : 'Failed to run skills command'
|
|
903
|
+
process.stderr.write(`${message}\n`)
|
|
904
|
+
process.exit(1)
|
|
905
|
+
}
|
|
906
|
+
return
|
|
907
|
+
}
|
|
908
|
+
|
|
823
909
|
const { command, flags, positional } = parseArgs(process.argv)
|
|
824
910
|
|
|
825
911
|
if (command === 'sessions') {
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/cli/skills-installer.ts
|
|
21
|
+
var skills_installer_exports = {};
|
|
22
|
+
__export(skills_installer_exports, {
|
|
23
|
+
createSkillsInstallInvocation: () => createSkillsInstallInvocation,
|
|
24
|
+
parseOpensteerSkillsArgs: () => parseOpensteerSkillsArgs,
|
|
25
|
+
resolveLocalSkillSourcePath: () => resolveLocalSkillSourcePath,
|
|
26
|
+
resolveSkillsCliPath: () => resolveSkillsCliPath,
|
|
27
|
+
runOpensteerSkillsInstaller: () => runOpensteerSkillsInstaller
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(skills_installer_exports);
|
|
30
|
+
var import_node_child_process = require("child_process");
|
|
31
|
+
var import_node_module = require("module");
|
|
32
|
+
var import_node_fs = require("fs");
|
|
33
|
+
var import_node_path = require("path");
|
|
34
|
+
var HELP_TEXT = `Usage: opensteer skills <install|add> [options]
|
|
35
|
+
|
|
36
|
+
Installs the first-party Opensteer skill using the upstream "skills" CLI.
|
|
37
|
+
|
|
38
|
+
Commands:
|
|
39
|
+
install Install the opensteer skill
|
|
40
|
+
add Alias for install
|
|
41
|
+
|
|
42
|
+
Supported Options:
|
|
43
|
+
-a, --agent <agents...> Target specific agent(s)
|
|
44
|
+
-g, --global Install globally
|
|
45
|
+
-y, --yes Skip confirmations
|
|
46
|
+
--copy Copy files instead of symlinking
|
|
47
|
+
--all Install to all agents
|
|
48
|
+
-h, --help Show this help
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
opensteer skills install
|
|
52
|
+
opensteer skills add --agent codex --global --yes
|
|
53
|
+
opensteer skills install --all --yes
|
|
54
|
+
`;
|
|
55
|
+
function parseOpensteerSkillsArgs(rawArgs) {
|
|
56
|
+
if (rawArgs.length === 0) {
|
|
57
|
+
return { mode: "help", passthroughArgs: [] };
|
|
58
|
+
}
|
|
59
|
+
const [subcommand, ...rest] = rawArgs;
|
|
60
|
+
if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
61
|
+
return { mode: "help", passthroughArgs: [] };
|
|
62
|
+
}
|
|
63
|
+
if (subcommand !== "install" && subcommand !== "add") {
|
|
64
|
+
return {
|
|
65
|
+
mode: "error",
|
|
66
|
+
passthroughArgs: [],
|
|
67
|
+
error: `Unsupported skills subcommand "${subcommand}". Use "install" or "add".`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const passthroughArgs = [];
|
|
71
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
72
|
+
const arg = rest[i];
|
|
73
|
+
if (arg === "--help" || arg === "-h") {
|
|
74
|
+
return { mode: "help", passthroughArgs: [] };
|
|
75
|
+
}
|
|
76
|
+
if (arg === "--global" || arg === "-g") {
|
|
77
|
+
passthroughArgs.push(arg);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (arg === "--yes" || arg === "-y") {
|
|
81
|
+
passthroughArgs.push(arg);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (arg === "--copy" || arg === "--all") {
|
|
85
|
+
passthroughArgs.push(arg);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (arg === "--agent" || arg === "-a") {
|
|
89
|
+
passthroughArgs.push(arg);
|
|
90
|
+
if (i + 1 >= rest.length || rest[i + 1]?.startsWith("-")) {
|
|
91
|
+
return {
|
|
92
|
+
mode: "error",
|
|
93
|
+
passthroughArgs: [],
|
|
94
|
+
error: `${arg} requires at least one value.`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
while (i + 1 < rest.length && !rest[i + 1]?.startsWith("-")) {
|
|
98
|
+
i += 1;
|
|
99
|
+
const agent = rest[i];
|
|
100
|
+
if (agent) {
|
|
101
|
+
passthroughArgs.push(agent);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (arg.startsWith("-")) {
|
|
107
|
+
return {
|
|
108
|
+
mode: "error",
|
|
109
|
+
passthroughArgs: [],
|
|
110
|
+
error: `Unsupported option "${arg}" for "opensteer skills".`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
mode: "error",
|
|
115
|
+
passthroughArgs: [],
|
|
116
|
+
error: `Unexpected argument "${arg}".`
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
mode: "install",
|
|
121
|
+
passthroughArgs
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function resolveLocalSkillSourcePath() {
|
|
125
|
+
const packageRoot = resolvePackageRoot();
|
|
126
|
+
const sourcePath = (0, import_node_path.join)(packageRoot, "skills");
|
|
127
|
+
if (!(0, import_node_fs.existsSync)(sourcePath)) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Opensteer skill source was not found at "${sourcePath}".`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return sourcePath;
|
|
133
|
+
}
|
|
134
|
+
function resolveSkillsCliPath() {
|
|
135
|
+
const require2 = (0, import_node_module.createRequire)(resolveCliEntrypointPath());
|
|
136
|
+
const skillsPackagePath = require2.resolve("skills/package.json");
|
|
137
|
+
const skillsPackageDir = (0, import_node_path.dirname)(skillsPackagePath);
|
|
138
|
+
const cliPath = (0, import_node_path.join)(skillsPackageDir, "bin", "cli.mjs");
|
|
139
|
+
if (!(0, import_node_fs.existsSync)(cliPath)) {
|
|
140
|
+
throw new Error(`skills CLI entrypoint was not found at "${cliPath}".`);
|
|
141
|
+
}
|
|
142
|
+
return cliPath;
|
|
143
|
+
}
|
|
144
|
+
function resolveCliEntrypointPath() {
|
|
145
|
+
const cliEntrypoint = process.argv[1];
|
|
146
|
+
if (!cliEntrypoint) {
|
|
147
|
+
throw new Error("Unable to resolve CLI entrypoint path for skills installer.");
|
|
148
|
+
}
|
|
149
|
+
return (0, import_node_fs.realpathSync)(cliEntrypoint);
|
|
150
|
+
}
|
|
151
|
+
function resolvePackageRoot() {
|
|
152
|
+
const cliEntrypointPath = resolveCliEntrypointPath();
|
|
153
|
+
const binDir = (0, import_node_path.dirname)(cliEntrypointPath);
|
|
154
|
+
return (0, import_node_path.resolve)(binDir, "..");
|
|
155
|
+
}
|
|
156
|
+
function createSkillsInstallInvocation(args) {
|
|
157
|
+
return {
|
|
158
|
+
cliPath: args.skillsCliPath,
|
|
159
|
+
cliArgs: [
|
|
160
|
+
"add",
|
|
161
|
+
args.localSkillSourcePath,
|
|
162
|
+
"--skill",
|
|
163
|
+
"opensteer",
|
|
164
|
+
...args.passthroughArgs
|
|
165
|
+
]
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async function spawnInvocation(invocation) {
|
|
169
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
170
|
+
const child = (0, import_node_child_process.spawn)(process.execPath, [invocation.cliPath, ...invocation.cliArgs], {
|
|
171
|
+
stdio: "inherit",
|
|
172
|
+
env: process.env,
|
|
173
|
+
cwd: process.cwd()
|
|
174
|
+
});
|
|
175
|
+
child.once("error", (error) => {
|
|
176
|
+
rejectPromise(error);
|
|
177
|
+
});
|
|
178
|
+
child.once("exit", (code) => {
|
|
179
|
+
if (typeof code === "number") {
|
|
180
|
+
resolvePromise(code);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
resolvePromise(1);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function createDefaultDeps() {
|
|
188
|
+
return {
|
|
189
|
+
resolveSkillsCliPath,
|
|
190
|
+
resolveLocalSkillSourcePath,
|
|
191
|
+
spawnInvocation,
|
|
192
|
+
writeStdout(message) {
|
|
193
|
+
process.stdout.write(message);
|
|
194
|
+
},
|
|
195
|
+
writeStderr(message) {
|
|
196
|
+
process.stderr.write(message);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async function runOpensteerSkillsInstaller(rawArgs, overrideDeps = {}) {
|
|
201
|
+
const deps = {
|
|
202
|
+
...createDefaultDeps(),
|
|
203
|
+
...overrideDeps
|
|
204
|
+
};
|
|
205
|
+
const parsed = parseOpensteerSkillsArgs(rawArgs);
|
|
206
|
+
if (parsed.mode === "help") {
|
|
207
|
+
deps.writeStdout(HELP_TEXT);
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
if (parsed.mode === "error") {
|
|
211
|
+
deps.writeStderr(`${parsed.error}
|
|
212
|
+
`);
|
|
213
|
+
deps.writeStderr('Run "opensteer skills --help" for usage.\n');
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
const invocation = createSkillsInstallInvocation({
|
|
217
|
+
localSkillSourcePath: deps.resolveLocalSkillSourcePath(),
|
|
218
|
+
passthroughArgs: parsed.passthroughArgs,
|
|
219
|
+
skillsCliPath: deps.resolveSkillsCliPath()
|
|
220
|
+
});
|
|
221
|
+
return await deps.spawnInvocation(invocation);
|
|
222
|
+
}
|
|
223
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
224
|
+
0 && (module.exports = {
|
|
225
|
+
createSkillsInstallInvocation,
|
|
226
|
+
parseOpensteerSkillsArgs,
|
|
227
|
+
resolveLocalSkillSourcePath,
|
|
228
|
+
resolveSkillsCliPath,
|
|
229
|
+
runOpensteerSkillsInstaller
|
|
230
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type ParseMode = 'help' | 'install' | 'error';
|
|
2
|
+
interface ParsedSkillsArgs {
|
|
3
|
+
mode: ParseMode;
|
|
4
|
+
passthroughArgs: string[];
|
|
5
|
+
error?: string;
|
|
6
|
+
}
|
|
7
|
+
interface SkillsInstallInvocation {
|
|
8
|
+
cliPath: string;
|
|
9
|
+
cliArgs: string[];
|
|
10
|
+
}
|
|
11
|
+
interface SkillsInstallerDeps {
|
|
12
|
+
resolveSkillsCliPath: () => string;
|
|
13
|
+
resolveLocalSkillSourcePath: () => string;
|
|
14
|
+
spawnInvocation: (invocation: SkillsInstallInvocation) => Promise<number>;
|
|
15
|
+
writeStdout: (message: string) => void;
|
|
16
|
+
writeStderr: (message: string) => void;
|
|
17
|
+
}
|
|
18
|
+
declare function parseOpensteerSkillsArgs(rawArgs: string[]): ParsedSkillsArgs;
|
|
19
|
+
declare function resolveLocalSkillSourcePath(): string;
|
|
20
|
+
declare function resolveSkillsCliPath(): string;
|
|
21
|
+
declare function createSkillsInstallInvocation(args: {
|
|
22
|
+
localSkillSourcePath: string;
|
|
23
|
+
passthroughArgs: string[];
|
|
24
|
+
skillsCliPath: string;
|
|
25
|
+
}): SkillsInstallInvocation;
|
|
26
|
+
declare function runOpensteerSkillsInstaller(rawArgs: string[], overrideDeps?: Partial<SkillsInstallerDeps>): Promise<number>;
|
|
27
|
+
|
|
28
|
+
export { createSkillsInstallInvocation, parseOpensteerSkillsArgs, resolveLocalSkillSourcePath, resolveSkillsCliPath, runOpensteerSkillsInstaller };
|