stpr 1.0.9 → 2.0.0

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.
Files changed (3) hide show
  1. package/README.md +159 -0
  2. package/dist/cli.js +166 -100
  3. package/package.json +3 -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
@@ -50,6 +50,19 @@ function writeCache(key, data) {
50
50
  } catch {
51
51
  }
52
52
  }
53
+ function filterCatalog(catalog, service, connection) {
54
+ if (!service && !connection) return catalog;
55
+ let services = catalog.services;
56
+ let libs = catalog.component_libraries;
57
+ if (service) {
58
+ services = services.filter((s) => s.service === service);
59
+ libs = libs.filter((l) => l.library === service);
60
+ }
61
+ if (connection) {
62
+ services = services.filter((s) => s.connection_slug === connection);
63
+ }
64
+ return { services, component_libraries: libs };
65
+ }
53
66
  var StepperClient = class {
54
67
  token;
55
68
  baseUrl;
@@ -80,6 +93,19 @@ var StepperClient = class {
80
93
  }
81
94
  return await response.json();
82
95
  }
96
+ /**
97
+ * Calls a static tool via tools/call RPC.
98
+ */
99
+ async callStaticTool(toolName, args) {
100
+ const res = await this.rpc("tools/call", {
101
+ name: toolName,
102
+ arguments: args
103
+ });
104
+ if (res.error) {
105
+ throw new Error(`tools/call (${toolName}) failed: ${res.error.message}`);
106
+ }
107
+ return res.result;
108
+ }
83
109
  async initialize() {
84
110
  const res = await this.rpc("initialize");
85
111
  if (res.error) {
@@ -100,73 +126,66 @@ var StepperClient = class {
100
126
  }
101
127
  async listTools({
102
128
  service,
129
+ connection,
103
130
  noCache
104
131
  }) {
105
- const cacheKey = `tools-list:${this.baseUrl}:${this.token}`;
106
- let allTools;
132
+ const cacheKey = `skills-list:${this.baseUrl}:${this.token}`;
133
+ let catalog;
107
134
  if (!noCache) {
108
135
  const cached = readCache(cacheKey);
109
136
  if (cached) {
110
- allTools = cached;
111
- return allTools.filter(
112
- (tool) => service ? tool.service === service : !tool.service.startsWith("__")
113
- );
137
+ catalog = cached;
114
138
  }
115
139
  }
116
- const res = await this.rpc("tools/list");
117
- if (res.error) {
118
- throw new Error(`tools/list failed: ${res.error.message}`);
119
- }
120
- const tools = res.result.tools;
121
- allTools = tools.filter((tool) => !tool.name.startsWith("__")).map(
122
- (tool) => ({
123
- service: tool.name.split("__")[0],
124
- action: tool.name.split("__")[1],
125
- description: tool.description,
126
- inputSchema: tool.inputSchema
127
- })
128
- );
129
- writeCache(cacheKey, allTools);
130
- return allTools.filter(
131
- (tool) => service ? tool.service === service : true
132
- );
140
+ if (!catalog) {
141
+ const result = await this.callStaticTool("list_skills", {});
142
+ const text = result.content[0]?.text ?? "{}";
143
+ catalog = JSON.parse(text);
144
+ writeCache(cacheKey, catalog);
145
+ }
146
+ return filterCatalog(catalog, service, connection);
133
147
  }
134
- async callTool(name, args) {
135
- const res = await this.rpc("tools/call", { name, arguments: args });
136
- if (res.error) {
137
- throw new Error(`tools/call failed: ${res.error.message}`);
148
+ async callTool(service, action, args, connection) {
149
+ const toolArgs = { service, action, args };
150
+ if (connection) {
151
+ toolArgs.connection = connection;
138
152
  }
139
- return res.result;
153
+ return this.callStaticTool("call_skill", toolArgs);
140
154
  }
141
155
  async pollStatus(statusId) {
142
- const res = await this.rpc("tools/poll-status", { statusId });
143
- if (res.error) {
144
- throw new Error(`tools/poll-status failed: ${res.error.message}`);
145
- }
146
- return res.result;
156
+ return this.callStaticTool("poll_status", { statusId });
147
157
  }
148
- async getToolParams(toolName, args) {
149
- const res = await this.rpc("tools/params", {
150
- name: toolName,
151
- arguments: args
152
- });
153
- if (res.error) {
154
- throw new Error(`tools/params failed: ${res.error.message}`);
158
+ async getToolParams(service, action, currentValues, connection) {
159
+ const args = { service, action };
160
+ if (connection) {
161
+ args.connection = connection;
155
162
  }
156
- return res.result;
163
+ if (currentValues && Object.keys(currentValues).length > 0) {
164
+ args.current_parameter_values = currentValues;
165
+ }
166
+ return this.callStaticTool("get_skill_params", args);
157
167
  }
158
- async getParameterOptions(toolName, parameterId, opts) {
159
- const res = await this.rpc("tools/options", {
160
- name: toolName,
161
- parameter_id: parameterId,
162
- current_parameter_values: opts?.currentParameterValues ?? {},
163
- search: opts?.search ?? null,
164
- cursor: opts?.cursor ?? null
165
- });
166
- if (res.error) {
167
- throw new Error(`tools/options failed: ${res.error.message}`);
168
+ async getParameterOptions(service, action, parameterId, opts) {
169
+ const args = {
170
+ service,
171
+ action,
172
+ parameter_id: parameterId
173
+ };
174
+ if (opts?.connection) {
175
+ args.connection = opts.connection;
168
176
  }
169
- return res.result;
177
+ if (opts?.currentParameterValues) {
178
+ args.current_parameter_values = opts.currentParameterValues;
179
+ }
180
+ if (opts?.search) {
181
+ args.search = opts.search;
182
+ }
183
+ if (opts?.cursor) {
184
+ args.cursor = opts.cursor;
185
+ }
186
+ const result = await this.callStaticTool("get_parameter_options", args);
187
+ const text = result.content[0]?.text ?? "{}";
188
+ return JSON.parse(text);
170
189
  }
171
190
  };
172
191
 
@@ -540,6 +559,8 @@ function parseArgs(argv) {
540
559
  flags.search = argv[++i];
541
560
  } else if (arg === "--cursor" && i + 1 < argv.length) {
542
561
  flags.cursor = argv[++i];
562
+ } else if (arg === "--connection" && i + 1 < argv.length) {
563
+ flags.connection = argv[++i];
543
564
  } else if (arg === "--call") {
544
565
  boolFlags.call = true;
545
566
  } else if (arg === "--no-cache") {
@@ -565,22 +586,24 @@ function parseArgs(argv) {
565
586
  input: flags.input,
566
587
  search: flags.search,
567
588
  cursor: flags.cursor,
589
+ connection: flags.connection,
568
590
  call: boolFlags.call ?? false,
569
591
  verbose: boolFlags.verbose ?? false,
570
592
  noCache: boolFlags.noCache ?? false,
571
593
  options: flags.options
572
594
  };
573
595
  }
574
- function toToolName(service, action) {
575
- return `${service}__${action}`;
576
- }
577
- function formatToolsForList(tools, verbose) {
596
+ function formatCatalogForList(catalog, verbose) {
578
597
  if (verbose) {
579
- return tools;
598
+ return catalog;
580
599
  }
581
600
  const out = {};
582
- for (const tool of tools) {
583
- out[tool.service] = [...out[tool.service] || [], tool.action];
601
+ for (const svc of catalog.services) {
602
+ const key = catalog.services.filter((s) => s.service === svc.service).length > 1 ? `${svc.service}--${svc.connection_slug}` : svc.service;
603
+ out[key] = svc.actions.map((a) => a.action);
604
+ }
605
+ for (const lib of catalog.component_libraries) {
606
+ out[`${lib.library}`] = lib.components.map((c) => c.component);
584
607
  }
585
608
  return out;
586
609
  }
@@ -596,9 +619,9 @@ Commands:
596
619
  profiles List all stored skillsets
597
620
  use <name> Switch active skillset
598
621
  whoami Show active skillset and server info
599
- list [<service>] List available skills (optionally for a service). Use --verbose for inputSchema.
622
+ list [<service>] List available skills (optionally for a service). Use --verbose for full catalog.
600
623
  status <statusId> Poll the status of a component tool run
601
- <service> List available skills for that service. Use --verbose for inputSchema.
624
+ <service> List available skills for that service. Use --verbose for full catalog.
602
625
  <service> <action> (-i <json> | stdin)
603
626
  Get the current parameters for a skill (default, for dynamic parameters)
604
627
  <service> <action> --call (-i <json> | stdin)
@@ -610,8 +633,9 @@ Options:
610
633
  --token <token> Auth token (or set STEPPER_SKILL_TOKEN env var)
611
634
  --base-url <url> Override base URL (or set STEPPER_URL env var)
612
635
  --skillset <name> Use a specific skill set
636
+ --connection <slug> Connection slug (for multi-connection disambiguation)
613
637
  --call Execute the skill (default is to fetch parameters only)
614
- --verbose Include inputSchema when listing skills
638
+ --verbose Include full catalog when listing skills
615
639
  --no-cache Bypass the 5-minute tools list cache
616
640
  -i, --input <json> JSON input for call, parameters or options (alternative to stdin)
617
641
  --search <query> Search query for dynamic dropdown options
@@ -632,17 +656,37 @@ Examples:
632
656
  Dynamic dropdown options:
633
657
  Some actions have dynamic dropdown options, which change depending on the
634
658
  value of other parameters. You can request the current dropdown options for
635
- a skill by using the --options flag. This will return the current dropdown
636
- options only, it will not call the skill. Dynamic dropdowns are also often
637
- searchable and paginated via a cursor.
659
+ a skill by using the --options flag.
638
660
 
639
661
  stpr google-sheets add_row --options worksheet_id -i '{"spreadsheet_id": "abc123"}' --search "Sheet" --cursor "next_page"
640
-
662
+
663
+ Multi-connection disambiguation:
664
+ When multiple connections exist for the same service, use the compound
665
+ format from "stpr list" or the --connection flag:
666
+
667
+ stpr google-sheets--my-sheets add_row --call -i '{"spreadsheet_id": "abc123"}'
668
+ stpr google-sheets add_row --connection my-sheets --call -i '{"spreadsheet_id": "abc123"}'
669
+
641
670
  Call a skill:
642
671
  stpr google-sheets create_sheet --call -i '{"name": "My Sheet", "columns": "Name, Email, Phone"}'
643
-
672
+
644
673
  `);
645
674
  }
675
+ function resolveServiceFromCatalog(rawService, catalog) {
676
+ if (catalog.services.some((s) => s.service === rawService)) {
677
+ return null;
678
+ }
679
+ if (catalog.component_libraries.some((l) => l.library === rawService)) {
680
+ return null;
681
+ }
682
+ for (const svc of catalog.services) {
683
+ const compound = `${svc.service}--${svc.connection_slug}`;
684
+ if (compound === rawService) {
685
+ return { service: svc.service, connection: svc.connection_slug };
686
+ }
687
+ }
688
+ return null;
689
+ }
646
690
  function die(message) {
647
691
  process.stderr.write(`\x1B[31m[Error] ${message} \x1B[0m
648
692
  `);
@@ -658,6 +702,7 @@ async function main() {
658
702
  input: inputFlag,
659
703
  search: searchFlag,
660
704
  cursor: cursorFlag,
705
+ connection: connectionFlag,
661
706
  call: callFlag,
662
707
  verbose: verboseFlag,
663
708
  noCache: noCacheFlag,
@@ -812,40 +857,53 @@ async function main() {
812
857
  console.log(result2.content.map((c) => c.text ?? "").join("\n"));
813
858
  return;
814
859
  }
815
- if (subcommand === "list") {
816
- const service2 = args[0];
817
- const tools = await client.listTools({
818
- service: service2 ?? void 0,
819
- noCache: noCacheFlag
820
- });
821
- const formatted = formatToolsForList(tools, verboseFlag);
822
- if (!tools.length) {
823
- die(
824
- service2 ? `No skills found for service "${service2}".` : "No skills found."
825
- );
826
- }
827
- console.log(JSON.stringify(formatted, null, 2));
828
- return;
829
- }
830
- const service = subcommand;
831
- const action = args[0];
832
- if (service.startsWith("--")) {
860
+ const rawServiceArg = subcommand === "list" ? args[0] : subcommand;
861
+ if (rawServiceArg?.startsWith("--")) {
833
862
  printUsage();
834
863
  process.exit(1);
835
864
  return;
836
865
  }
837
- if (!action) {
838
- const tools = await client.listTools({ service, noCache: noCacheFlag });
839
- if (!tools.length) {
866
+ let service = rawServiceArg;
867
+ let connection = connectionFlag;
868
+ const action = subcommand === "list" ? void 0 : args[0];
869
+ if (service && !connection) {
870
+ const fullCatalog = await client.listTools({
871
+ service: void 0,
872
+ noCache: noCacheFlag
873
+ });
874
+ const resolved = resolveServiceFromCatalog(service, fullCatalog);
875
+ if (resolved) {
876
+ service = resolved.service;
877
+ connection = resolved.connection;
878
+ }
879
+ }
880
+ if (subcommand === "list" || !action) {
881
+ const catalog = await client.listTools({
882
+ service: service ?? void 0,
883
+ connection,
884
+ noCache: noCacheFlag
885
+ });
886
+ const hasContent = catalog.services.length > 0 || catalog.component_libraries.length > 0;
887
+ if (!hasContent) {
840
888
  die(
841
- service ? `No skills found for service "${service}".` : "No skills found."
889
+ rawServiceArg ? `No skills found for service "${rawServiceArg}".` : "No skills found."
842
890
  );
843
891
  }
844
- const formatted = verboseFlag ? formatToolsForList(tools, verboseFlag) : tools.map(({ action: action2 }) => action2);
845
- console.log(JSON.stringify(formatted, null, 2));
892
+ if (subcommand === "list" || verboseFlag) {
893
+ const formatted = formatCatalogForList(catalog, verboseFlag);
894
+ console.log(JSON.stringify(formatted, null, 2));
895
+ } else {
896
+ const actions = [];
897
+ for (const svc of catalog.services) {
898
+ actions.push(...svc.actions.map((a) => a.action));
899
+ }
900
+ for (const lib of catalog.component_libraries) {
901
+ actions.push(...lib.components.map((c) => c.component));
902
+ }
903
+ console.log(JSON.stringify(actions, null, 2));
904
+ }
846
905
  return;
847
906
  }
848
- const toolName = toToolName(service, action);
849
907
  const rawInput = inputFlag ?? await readStdin();
850
908
  let input = {};
851
909
  if (rawInput.trim()) {
@@ -856,27 +914,35 @@ async function main() {
856
914
  }
857
915
  }
858
916
  if (optionsFlag) {
859
- const result2 = await client.getParameterOptions(toolName, optionsFlag, {
860
- currentParameterValues: input,
861
- search: searchFlag,
862
- cursor: cursorFlag
863
- });
917
+ const result2 = await client.getParameterOptions(
918
+ service,
919
+ action,
920
+ optionsFlag,
921
+ {
922
+ currentParameterValues: input,
923
+ search: searchFlag,
924
+ cursor: cursorFlag,
925
+ connection
926
+ }
927
+ );
864
928
  console.log(JSON.stringify(result2, null, 2));
865
929
  return;
866
930
  }
867
931
  if (callFlag) {
868
- const result2 = await client.callTool(toolName, input);
932
+ const result2 = await client.callTool(service, action, input, connection);
869
933
  if (result2.isError) {
870
934
  die(result2.content.map((c) => c.text).join("\n"));
871
935
  }
872
936
  console.log(result2.content.map((c) => c.text ?? "").join("\n"));
873
937
  return;
874
938
  }
875
- const result = await client.getToolParams(toolName, input);
939
+ const result = await client.getToolParams(service, action, input, connection);
876
940
  if (result.isError) {
877
941
  die(result.content?.map((c) => c.text).join("\n") ?? "Unknown error");
878
942
  }
879
- console.log(JSON.stringify(result, null, 2));
943
+ console.log(
944
+ result.content?.map((c) => c.text ?? "").join("\n") ?? JSON.stringify(result, null, 2)
945
+ );
880
946
  }
881
947
  main().then(() => process.exit(0)).catch((err) => {
882
948
  console.error(`Error: ${err.message}`);
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "stpr",
3
- "version": "1.0.9",
3
+ "version": "2.0.0",
4
4
  "description": "CLI for Stepper skill sets",
5
5
  "type": "module",
6
6
  "files": [
7
- "dist"
7
+ "dist",
8
+ "README.md"
8
9
  ],
9
10
  "bin": {
10
11
  "stpr": "./dist/cli.js"