stpr 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -0
- package/dist/cli.js +24 -40
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# stpr — Stepper Skills CLI
|
|
2
|
+
|
|
3
|
+
Skill Sets are a Stepper feature that let you bundle integration actions into curated, authenticated toolkits — and expose them to AI agents, CLIs, and any MCP-compatible client.
|
|
4
|
+
|
|
5
|
+
`stpr` is a command-line interface for interacting with [Stepper](https://stepper.io) Skill Sets. Discover, inspect, and execute integration actions directly from your terminal. This is perfect for OpenClaw or agents that can interact with a CLI, or for developers who want to use skills in their own scripts.
|
|
6
|
+
|
|
7
|
+
If you prefer, you can directly use the Stepper MCP server at `https://mcp.stepper.io/skill-sets/mcp`instead of the CLI.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g stpr
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Authentication
|
|
16
|
+
|
|
17
|
+
The CLI supports three authentication methods:
|
|
18
|
+
|
|
19
|
+
### OAuth Login (recommended)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
stpr login
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Opens your browser to authenticate and select a Skill Set. Credentials are saved to `~/.config/stepper-skillsets/config.json` and automatically refreshed when they expire.
|
|
26
|
+
|
|
27
|
+
### Static Token
|
|
28
|
+
|
|
29
|
+
Generate a token from [Stepper](https://app.stepper.io/flow/skill-sets) and pass it directly:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
stpr --token sst_your_token_here list
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Environment Variable
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export STEPPER_SKILL_TOKEN=sst_your_token_here
|
|
39
|
+
stpr list
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
### Profile Management
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
stpr login # Authenticate via OAuth (opens browser)
|
|
48
|
+
stpr logout [name] # Remove a saved profile, or all profiles if no name given
|
|
49
|
+
stpr profiles # List all saved profiles
|
|
50
|
+
stpr use <name> # Switch the active profile
|
|
51
|
+
stpr whoami # Show active profile and server info
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Discovering Skills
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
stpr list # List all available skills, grouped by service
|
|
58
|
+
stpr list --verbose # Include full input schemas
|
|
59
|
+
stpr list <service> # List skills for a specific service
|
|
60
|
+
stpr <service> # Shorthand for listing a service's skills
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Inspecting Parameters
|
|
64
|
+
|
|
65
|
+
Many skills have dynamic parameters — fields that change based on the values of other fields. Calling a skill without `--call` returns its current parameter schema.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# See what fields are needed for add_row, given a spreadsheet
|
|
69
|
+
stpr google-sheets add_row -i '{"spreadsheet_id": "abc123"}'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Some parameters have dynamic dropdown options. Fetch them with `--options`:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
stpr google-sheets add_row --options worksheet_id \
|
|
76
|
+
-i '{"spreadsheet_id": "abc123"}' \
|
|
77
|
+
--search "Sheet" \
|
|
78
|
+
--cursor "next_page"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Calling Skills
|
|
82
|
+
|
|
83
|
+
Use the `--call` flag to execute an action:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
stpr google-sheets create_sheet --call \
|
|
87
|
+
-i '{"name": "Q1 Report", "columns": "Name, Email, Phone"}'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Polling Async Results
|
|
91
|
+
|
|
92
|
+
Component library tools run asynchronously. Poll for results with:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
stpr status <statusId>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Input
|
|
99
|
+
|
|
100
|
+
Pass JSON input via the `-i` / `--input` flag or pipe it through stdin:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Flag
|
|
104
|
+
stpr slack send_message --call -i '{"channel": "#general", "text": "Hello!"}'
|
|
105
|
+
|
|
106
|
+
# Stdin
|
|
107
|
+
echo '{"channel": "#general", "text": "Hello!"}' | stpr slack send_message --call
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Options Reference
|
|
111
|
+
|
|
112
|
+
| Flag | Description |
|
|
113
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
114
|
+
| `--token <token>` | Auth token (overrides saved profiles and `STEPPER_SKILL_TOKEN`) |
|
|
115
|
+
| `--base-url <url>` | Override MCP server URL (default: `https://mcp.stepper.io`) |
|
|
116
|
+
| `--skillset <name>` | Use a specific saved profile instead of the active one |
|
|
117
|
+
| `--call` | Execute the skill (default behavior is parameter inspection) |
|
|
118
|
+
| `--verbose` | Include full `inputSchema` when listing skills |
|
|
119
|
+
| `-i, --input <json>` | JSON input for calls, parameter fetches, or option queries |
|
|
120
|
+
| `--options <param>` | Fetch dynamic dropdown options for a parameter |
|
|
121
|
+
| `--search <query>` | Filter dropdown options by search term |
|
|
122
|
+
| `--cursor <cursor>` | Pagination cursor for dropdown options |
|
|
123
|
+
| `-h, --help` | Show help |
|
|
124
|
+
| `-v, --version` | Show version |
|
|
125
|
+
|
|
126
|
+
## Environment Variables
|
|
127
|
+
|
|
128
|
+
| Variable | Description |
|
|
129
|
+
| --------------------- | ------------------------------------------------------------- |
|
|
130
|
+
| `STEPPER_SKILL_TOKEN` | Auth token (used when no `--token` flag and no saved profile) |
|
|
131
|
+
| `STEPPER_URL` | Override the MCP server base URL |
|
|
132
|
+
|
|
133
|
+
## Examples
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Authenticate
|
|
137
|
+
stpr login
|
|
138
|
+
|
|
139
|
+
# List everything available
|
|
140
|
+
stpr list
|
|
141
|
+
|
|
142
|
+
# Explore a service
|
|
143
|
+
stpr google-sheets
|
|
144
|
+
|
|
145
|
+
# Inspect dynamic parameters step by step
|
|
146
|
+
stpr google-sheets add_row -i '{}'
|
|
147
|
+
stpr google-sheets add_row -i '{"spreadsheet_id": "abc123"}'
|
|
148
|
+
|
|
149
|
+
# Fetch dropdown options
|
|
150
|
+
stpr google-sheets add_row --options worksheet_id -i '{"spreadsheet_id": "abc123"}'
|
|
151
|
+
|
|
152
|
+
# Execute
|
|
153
|
+
stpr google-sheets add_row --call -i '{"spreadsheet_id": "abc123", "worksheet_id": "Sheet1", "values": {"Name": "Alice"}}'
|
|
154
|
+
|
|
155
|
+
# Switch between skill sets
|
|
156
|
+
stpr profiles
|
|
157
|
+
stpr use "Production Tools"
|
|
158
|
+
stpr list
|
|
159
|
+
```
|
package/dist/cli.js
CHANGED
|
@@ -138,7 +138,6 @@ var OAUTH_CALLBACK_PORT = 3847;
|
|
|
138
138
|
var CALLBACK_PATH = "/callback";
|
|
139
139
|
var CONFIG_DIR = path.join(os.homedir(), ".config", "stepper-skillsets");
|
|
140
140
|
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
141
|
-
var LEGACY_CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
142
141
|
function generatePKCE() {
|
|
143
142
|
const codeVerifier = crypto.randomBytes(32).toString("base64url");
|
|
144
143
|
const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest().toString("base64url");
|
|
@@ -158,29 +157,14 @@ function saveConfig(config) {
|
|
|
158
157
|
mode: 384
|
|
159
158
|
});
|
|
160
159
|
}
|
|
161
|
-
function migrateLegacyCredentials() {
|
|
162
|
-
try {
|
|
163
|
-
const data = fs.readFileSync(LEGACY_CREDENTIALS_FILE, "utf-8");
|
|
164
|
-
const creds = JSON.parse(data);
|
|
165
|
-
const config = {
|
|
166
|
-
active: "default",
|
|
167
|
-
skillsets: { default: creds }
|
|
168
|
-
};
|
|
169
|
-
saveConfig(config);
|
|
170
|
-
fs.unlinkSync(LEGACY_CREDENTIALS_FILE);
|
|
171
|
-
} catch {
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
160
|
function getConfigPathForDisplay() {
|
|
175
161
|
return CONFIG_FILE;
|
|
176
162
|
}
|
|
177
163
|
function getActiveSkillset() {
|
|
178
|
-
migrateLegacyCredentials();
|
|
179
164
|
const config = loadConfig();
|
|
180
165
|
return config.active;
|
|
181
166
|
}
|
|
182
167
|
function setActiveSkillset(name) {
|
|
183
|
-
migrateLegacyCredentials();
|
|
184
168
|
const config = loadConfig();
|
|
185
169
|
if (!(name in config.skillsets)) {
|
|
186
170
|
return false;
|
|
@@ -190,7 +174,6 @@ function setActiveSkillset(name) {
|
|
|
190
174
|
return true;
|
|
191
175
|
}
|
|
192
176
|
function listSkillsets() {
|
|
193
|
-
migrateLegacyCredentials();
|
|
194
177
|
const config = loadConfig();
|
|
195
178
|
return Object.entries(config.skillsets).map(([name, creds]) => ({
|
|
196
179
|
name,
|
|
@@ -199,12 +182,10 @@ function listSkillsets() {
|
|
|
199
182
|
}));
|
|
200
183
|
}
|
|
201
184
|
function getCredentials(name) {
|
|
202
|
-
migrateLegacyCredentials();
|
|
203
185
|
const config = loadConfig();
|
|
204
186
|
return config.skillsets[name] ?? null;
|
|
205
187
|
}
|
|
206
188
|
function saveCredentials(name, creds) {
|
|
207
|
-
migrateLegacyCredentials();
|
|
208
189
|
const config = loadConfig();
|
|
209
190
|
config.skillsets[name] = creds;
|
|
210
191
|
if (!config.active || !(config.active in config.skillsets)) {
|
|
@@ -213,7 +194,6 @@ function saveCredentials(name, creds) {
|
|
|
213
194
|
saveConfig(config);
|
|
214
195
|
}
|
|
215
196
|
function deleteSkillset(name) {
|
|
216
|
-
migrateLegacyCredentials();
|
|
217
197
|
const config = loadConfig();
|
|
218
198
|
if (!(name in config.skillsets)) {
|
|
219
199
|
return false;
|
|
@@ -226,7 +206,6 @@ function deleteSkillset(name) {
|
|
|
226
206
|
return true;
|
|
227
207
|
}
|
|
228
208
|
function deleteAllSkillsets() {
|
|
229
|
-
migrateLegacyCredentials();
|
|
230
209
|
const config = loadConfig();
|
|
231
210
|
const count = Object.keys(config.skillsets).length;
|
|
232
211
|
config.skillsets = {};
|
|
@@ -320,13 +299,30 @@ async function runLoginFlow(baseUrl) {
|
|
|
320
299
|
res.end("Not found");
|
|
321
300
|
return;
|
|
322
301
|
}
|
|
302
|
+
const requestHost = req.headers.host ?? `127.0.0.1:${OAUTH_CALLBACK_PORT}`;
|
|
303
|
+
const callbackUrl = new URL(req.url ?? "/", `http://${requestHost}`);
|
|
304
|
+
const receivedCallbackUrl = `${callbackUrl.origin}${callbackUrl.pathname}`;
|
|
305
|
+
if (receivedCallbackUrl !== redirectUri) {
|
|
306
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
307
|
+
res.end(
|
|
308
|
+
"<html><body><h1>Login failed</h1><p>Redirect URI mismatch</p><p>You can close this tab.</p></body></html>"
|
|
309
|
+
);
|
|
310
|
+
server.close();
|
|
311
|
+
reject(new Error("OAuth redirect URI mismatch"));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
323
314
|
const code = url.searchParams.get("code");
|
|
324
315
|
const returnedState = url.searchParams.get("state");
|
|
325
316
|
const error = url.searchParams.get("error");
|
|
326
317
|
if (error) {
|
|
318
|
+
const escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
319
|
+
const safeError = escapeHtml(error);
|
|
320
|
+
const safeDesc = escapeHtml(
|
|
321
|
+
url.searchParams.get("error_description") ?? "Unknown error"
|
|
322
|
+
);
|
|
327
323
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
328
324
|
res.end(
|
|
329
|
-
`<html><body><h1>Login failed</h1><p>${
|
|
325
|
+
`<html><body><h1>Login failed</h1><p>${safeError}: ${safeDesc}</p><p>You can close this tab.</p></body></html>`
|
|
330
326
|
);
|
|
331
327
|
server.close();
|
|
332
328
|
reject(new Error(`OAuth error: ${error}`));
|
|
@@ -407,7 +403,6 @@ async function refreshAccessToken(baseUrl, clientId, refreshToken) {
|
|
|
407
403
|
};
|
|
408
404
|
}
|
|
409
405
|
async function getValidToken(skillsetName, baseUrl) {
|
|
410
|
-
migrateLegacyCredentials();
|
|
411
406
|
const config = loadConfig();
|
|
412
407
|
const name = skillsetName ?? config.active;
|
|
413
408
|
if (!name || !(name in config.skillsets)) {
|
|
@@ -549,9 +544,7 @@ Commands:
|
|
|
549
544
|
whoami Show active skillset and server info
|
|
550
545
|
list [<service>] List available skills (optionally for a service). Use --verbose for inputSchema.
|
|
551
546
|
status <statusId> Poll the status of a component tool run
|
|
552
|
-
|
|
553
547
|
<service> List available skills for that service. Use --verbose for inputSchema.
|
|
554
|
-
|
|
555
548
|
<service> <action> (-i <json> | stdin)
|
|
556
549
|
Get the current parameters for a skill (default, for dynamic parameters)
|
|
557
550
|
<service> <action> --call (-i <json> | stdin)
|
|
@@ -572,24 +565,15 @@ Options:
|
|
|
572
565
|
-v, --version Show version
|
|
573
566
|
|
|
574
567
|
Examples:
|
|
575
|
-
|
|
576
568
|
Auth (optional, can use STEPPER_SKILL_TOKEN env var, or --token <token> from https://app.stepper.io/flow/skill-sets):
|
|
577
569
|
stpr login
|
|
578
570
|
|
|
579
|
-
List available skills:
|
|
580
|
-
stpr list
|
|
581
|
-
stpr stripe
|
|
582
|
-
|
|
583
|
-
|
|
584
571
|
Load parameters:
|
|
585
572
|
By default, requesting a skill returns the current parameters only; it does
|
|
586
573
|
not call the skill. Use --call to execute the action.
|
|
587
574
|
|
|
588
575
|
stpr google-sheets add_row -i '{"spreadsheet_id": "abc123"}'
|
|
589
576
|
|
|
590
|
-
Call a skill:
|
|
591
|
-
stpr stripe create_customer --call -i '{"email": "test@example.com"}'
|
|
592
|
-
|
|
593
577
|
Dynamic dropdown options:
|
|
594
578
|
Some actions have dynamic dropdown options, which change depending on the
|
|
595
579
|
value of other parameters. You can request the current dropdown options for
|
|
@@ -597,12 +581,12 @@ Examples:
|
|
|
597
581
|
options only, it will not call the skill. Dynamic dropdowns are also often
|
|
598
582
|
searchable and paginated via a cursor.
|
|
599
583
|
|
|
600
|
-
stpr google-sheets
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
584
|
+
stpr google-sheets add_row --options worksheet_id -i '{"spreadsheet_id": "abc123"}' --search "Sheet" --cursor "next_page"
|
|
585
|
+
|
|
586
|
+
Call a skill:
|
|
587
|
+
stpr google-sheets create_sheet --call -i '{"name": "My Sheet", "columns": "Name, Email, Phone"}'
|
|
588
|
+
|
|
589
|
+
`);
|
|
606
590
|
}
|
|
607
591
|
function die(message) {
|
|
608
592
|
process.stderr.write(`\x1B[31m[Error] ${message} \x1B[0m
|