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 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
- Open-source browser automation SDK for coding agents and deterministic replay.
3
+ Browser automation framework for developers and AI agents with deterministic replay.
4
4
 
5
- Opensteer combines descriptor-aware actions, resilient selector persistence,
6
- clean HTML snapshots, and first-class local or cloud runtime support.
5
+ Opensteer gives you one API for local and cloud runs, description-based actions,
6
+ structured extraction, and CUA agent workflows.
7
7
 
8
- ## Requirements
8
+ ## Install
9
9
 
10
- - Node.js `>=20`
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
- ## Install
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
- Repo maintenance defaults to `pnpm`, with compatibility checks for `npm` and
26
- `bun` in CI.
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 your environment skips Playwright browser downloads, run:
36
+ If browser binaries are missing:
29
37
 
30
38
  ```bash
31
39
  npx playwright install chromium
32
40
  ```
33
41
 
34
- ## Quickstart (SDK)
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: "my-scraper" });
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.click({ description: "main call to action", element: 3 });
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
- await opensteer.launch();
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
- const agent = opensteer.agent({
65
- mode: "cua",
66
- });
95
+ ## Quick Start: CLI
67
96
 
68
- const result = await agent.execute({
69
- instruction: "Go to Hacker News and open the top story.",
70
- maxSteps: 20,
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
- console.log(result.message);
75
- await opensteer.close();
76
- ```
101
+ # Action snapshot + interaction
102
+ opensteer snapshot action --session demo
103
+ opensteer click --description "main call to action" --session demo
77
104
 
78
- Supported CUA providers in V1: `openai`, `anthropic`, `google`.
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
- ## Quickstart (CLI)
109
+ # Close session
110
+ opensteer close --session demo
111
+ ```
81
112
 
82
- Opensteer CLI separates runtime routing from selector namespace routing.
113
+ For non-interactive runs, set `OPENSTEER_SESSION` or `OPENSTEER_CLIENT_ID`.
83
114
 
84
- - Runtime routing: `--session` or `OPENSTEER_SESSION`
85
- - Selector namespace: `--name` or `OPENSTEER_NAME` (used by `open`)
115
+ ## For AI Agents
86
116
 
87
- ```bash
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
- In non-interactive environments, set `OPENSTEER_SESSION` or
96
- `OPENSTEER_CLIENT_ID` explicitly.
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
- ## Resolution and Replay Model
125
+ First-party skills:
99
126
 
100
- For descriptor-aware actions (`click`, `input`, `hover`, `select`, `scroll`):
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
- 1. Reuse persisted path for `description`
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
- When steps 2-4 succeed and `description` is present, Opensteer persists the
109
- path for deterministic replay in `.opensteer/selectors/<namespace>`.
133
+ ```bash
134
+ opensteer skills install
135
+ ```
110
136
 
111
- ## Cloud Mode
137
+ Fallback (direct upstream `skills` CLI):
112
138
 
113
- Opensteer defaults to local mode.
139
+ ```bash
140
+ npx skills add https://github.com/steerlabs/opensteer-skills --skill opensteer
141
+ ```
114
142
 
115
- - `OPENSTEER_MODE=local|cloud`
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
- `.env` files are auto-loaded from `storage.rootDir` (default `process.cwd()`)
123
- in this order: `.env.<NODE_ENV>.local`, `.env.local` (except in test),
124
- `.env.<NODE_ENV>`, `.env`. Existing `process.env` values are not overwritten.
125
- Set `OPENSTEER_DISABLE_DOTENV_AUTOLOAD=true` to disable.
145
+ ```text
146
+ /plugin marketplace add steerlabs/opensteer
147
+ /plugin install opensteer@opensteer-marketplace
148
+ ```
126
149
 
127
- ## Agent Skills
150
+ ## Cloud Mode
128
151
 
129
- Opensteer maintains first-party agent skills in-repo under
130
- [`skills/`](skills/README.md).
152
+ Opensteer defaults to local mode. Enable cloud mode with env or constructor options:
131
153
 
132
- - Skill: [`skills/opensteer/SKILL.md`](skills/opensteer/SKILL.md)
133
- - Skill: [`skills/electron/SKILL.md`](skills/electron/SKILL.md)
134
- - Supporting references: [`skills/opensteer/references/`](skills/opensteer/references)
135
- - Supporting references: [`skills/electron/references/`](skills/electron/references)
154
+ ```bash
155
+ OPENSTEER_MODE=cloud
156
+ OPENSTEER_API_KEY=<your_api_key>
157
+ ```
136
158
 
137
- ### Claude Code Marketplace
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
- This repository also publishes a Claude Code plugin marketplace:
140
- [`.claude-plugin/marketplace.json`](.claude-plugin/marketplace.json)
165
+ ## Resolution and Replay
141
166
 
142
- Install from Claude Code:
167
+ For descriptor-aware actions (`click`, `input`, `hover`, `select`, `scroll`):
143
168
 
144
- ```text
145
- /plugin marketplace add steerlabs/opensteer
146
- /plugin install opensteer@opensteer-marketplace
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 Guide](CONTRIBUTING.md)
200
+ - [Contributing](CONTRIBUTING.md)
163
201
  - [Code of Conduct](CODE_OF_CONDUCT.md)
164
202
  - [Security Policy](SECURITY.md)
165
- - [Support](SUPPORT.md)
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 };