@orth/cli 0.2.28 → 0.2.29

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Orthogonal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,204 +1,269 @@
1
+ <div align="center">
2
+
1
3
  # Orthogonal CLI
2
4
 
3
- CLI to access all APIs and agent skills on the Orthogonal platform.
5
+ **One command line for every API and agent skill on the [Orthogonal](https://orthogonal.com) platform.**
6
+
7
+ Discover APIs in natural language, call them with a single command, and pay per request from your Orthogonal credit balance — no per-provider signups, keys, or contracts.
8
+
9
+ [![npm version](https://img.shields.io/npm/v/@orth/cli.svg?logo=npm&color=cb3837)](https://www.npmjs.com/package/@orth/cli)
10
+ [![npm downloads](https://img.shields.io/npm/dm/@orth/cli.svg?color=cb3837)](https://www.npmjs.com/package/@orth/cli)
11
+ [![node](https://img.shields.io/node/v/@orth/cli.svg?logo=node.js&color=339933)](https://nodejs.org)
12
+ [![license](https://img.shields.io/npm/l/@orth/cli.svg?color=blue)](./LICENSE)
4
13
 
5
14
  ![Demo](https://raw.githubusercontent.com/orthogonal-sh/cli/main/demos/demo.gif)
6
15
 
16
+ </div>
17
+
18
+ ## Table of Contents
19
+
20
+ - [Why Orthogonal](#why-orthogonal)
21
+ - [Installation](#installation)
22
+ - [Quick Start](#quick-start)
23
+ - [Authentication](#authentication)
24
+ - [Calling APIs](#calling-apis)
25
+ - [Agent Skills](#agent-skills)
26
+ - [Scheduled Tasks](#scheduled-tasks)
27
+ - [Account & Usage](#account--usage)
28
+ - [Command Reference](#command-reference)
29
+ - [Configuration](#configuration)
30
+ - [Programmatic Use](#programmatic-use)
31
+ - [Development](#development)
32
+ - [License](#license)
33
+
34
+ ## Why Orthogonal
35
+
36
+ Orthogonal is a marketplace of production APIs and agent skills behind one account and one balance. The CLI lets you — and your AI agents — find and call any of them without leaving the terminal:
37
+
38
+ - **Discover** APIs and skills with natural-language search.
39
+ - **Call** any endpoint with one command — auth, billing, and routing are handled for you.
40
+ - **Pay per use** from a single credit balance instead of juggling dozens of provider subscriptions.
41
+ - **Script it** — clean JSON output (`--raw`) pipes straight into `jq` and shell pipelines.
42
+
7
43
  ## Installation
8
44
 
45
+ Requires **Node.js 18+**.
46
+
9
47
  ```bash
10
48
  # Install globally
11
49
  npm install -g @orth/cli
12
50
 
13
- # Or use npx
51
+ # …or run without installing
14
52
  npx @orth/cli <command>
15
53
  ```
16
54
 
55
+ Verify the install:
56
+
57
+ ```bash
58
+ orth --version
59
+ ```
60
+
61
+ ## Quick Start
62
+
63
+ ```bash
64
+ # 1. Authenticate (get a key at https://orthogonal.com)
65
+ orth login --key orth_live_your_key
66
+
67
+ # 2. Find an API in plain English
68
+ orth api search "search the web"
69
+
70
+ # 3. Call an endpoint
71
+ orth run tavily /search -q query="latest AI news"
72
+
73
+ # 4. See what it cost
74
+ orth balance
75
+ ```
76
+
17
77
  ## Authentication
18
78
 
19
79
  ```bash
20
- # Login with API key
80
+ # Log in with your API key (stored in your OS user-config directory)
21
81
  orth login --key orth_live_your_key
22
82
 
23
- # Or set environment variable
83
+ # …or use an environment variable (takes precedence — great for CI)
24
84
  export ORTHOGONAL_API_KEY=orth_live_your_key
25
85
 
26
- # Check auth status
86
+ # Show the current identity
27
87
  orth whoami
28
88
 
29
- # Logout
89
+ # Remove the stored key
30
90
  orth logout
31
91
  ```
32
92
 
33
- ## API Commands
93
+ Get an API key from your [Orthogonal dashboard](https://orthogonal.com/dashboard).
34
94
 
35
- ### Search & Browse
95
+ ## Calling APIs
96
+
97
+ ### Discover & inspect
36
98
 
37
99
  ```bash
38
- # Search for APIs
39
- orth api search "email finder"
40
- orth api search "web scraping" --limit 20
100
+ # Natural-language search across the marketplace
101
+ orth api search "generate images"
41
102
 
42
- # List all APIs
103
+ # List every available API
43
104
  orth api list
44
105
 
45
- # Show API endpoints
46
- orth api show hunter
106
+ # Show an API's endpoints
107
+ orth api show tavily
47
108
 
48
- # Show endpoint details
49
- orth api show hunter /v2/domain-search
109
+ # Show one endpoint's parameters, pricing, and schema
110
+ orth api show tavily /search
50
111
  ```
51
112
 
52
- ### Call APIs
113
+ ### Call an endpoint
53
114
 
54
115
  ```bash
55
- # GET request with query params
56
- orth run hunter /v2/domain-search -q domain=stripe.com
57
-
58
- # Multiple query params (two ways)
59
- orth run searchapi /api/v1/search -q 'engine=amazon_search&q=wireless earbuds'
60
- orth run searchapi /api/v1/search -q engine=amazon_search -q q=wireless earbuds
61
-
62
- # POST request with body
63
- orth run olostep /v1/scrapes -d '{"url": "https://stripe.com"}'
116
+ # GET with query params
117
+ orth run fantastic-jobs /v1/active-ats -q time_frame=1h -q limit=10
64
118
 
65
- # Raw output for piping
66
- orth run hunter /v2/domain-search -q domain=stripe.com --raw | jq '.emails'
67
- ```
119
+ # Query params can also be passed as a single string
120
+ orth run fantastic-jobs /v1/active-ats -q "time_frame=1h&limit=10"
68
121
 
69
- ### Generate Code
122
+ # POST with a JSON body
123
+ orth run some-api /v1/generate -X POST -b '{"prompt":"a red bicycle"}'
70
124
 
71
- ```bash
72
- # TypeScript (default)
73
- orth api code hunter /v2/domain-search
125
+ # Estimate the cost without spending credits
126
+ orth run some-api /v1/generate --dry-run
74
127
 
75
- # Python
76
- orth api code hunter /v2/domain-search --lang python
128
+ # Save a binary response (image, audio, PDF, …) to a file
129
+ orth run image-api /v1/render -q prompt="sunset" -o out.png
77
130
 
78
- # cURL
79
- orth api code hunter /v2/domain-search --lang curl
131
+ # Machine-readable output for pipelines
132
+ orth run tavily /search -q query="orthogonal" --raw | jq '.results[0].url'
80
133
  ```
81
134
 
82
- ### Request an API
135
+ ### Generate integration code
136
+
137
+ Scaffold a ready-to-run snippet for any endpoint:
83
138
 
84
139
  ```bash
85
- # Request an API to be added to the platform
86
- orth api request https://docs.example.com/api -d "Weather data API"
140
+ orth api code tavily /search # TypeScript (default)
141
+ orth api code tavily /search --lang python # Python
142
+ orth api code tavily /search --lang curl # cURL
87
143
  ```
88
144
 
89
- ## Skills Commands
90
-
91
- ### Browse & Search
145
+ ### Request a new API
92
146
 
93
147
  ```bash
94
- # List verified & discoverable skills
95
- orth skills list
96
-
97
- # Search for skills
98
- orth skills search "react best practices"
99
-
100
- # Show skill details
101
- orth skills show owner/skill-name
148
+ orth api request https://docs.example.com/api
102
149
  ```
103
150
 
104
- ### Install Skills
151
+ ## Agent Skills
105
152
 
106
- ```bash
107
- # Install a skill to all supported agents
108
- orth skills add owner/skill-name
153
+ Skills are packaged capabilities your AI agents can install and run. Browse the library, publish your own, and sync them to your local agent directories.
109
154
 
110
- # Install for a specific agent only
111
- orth skills add owner/skill-name --agent cursor
155
+ ```bash
156
+ # Browse & search
157
+ orth skills list # verified, discoverable skills
158
+ orth skills search "web scraping"
159
+ orth skills show <slug> # details + files
160
+
161
+ # Install into your local agent skill directories
162
+ orth skills add <slug> # alias: orth skills install <slug>
163
+
164
+ # Author & publish
165
+ orth skills init [name] # scaffold a SKILL.md template
166
+ orth skills create <owner/repo> # create a skill from a GitHub repo
167
+ orth skills submit [path] # submit a local skill
168
+ orth skills push <slug> [path] # push local changes to the platform
169
+ orth skills update <slug> [path] # pull the latest version locally
170
+ orth skills mine # list your skills
171
+ orth skills request-verification <slug>
172
+ orth skills request <input> # request a skill by URL or description
112
173
  ```
113
174
 
114
- Installs to 7 agent skill directories:
175
+ **Verification workflow:** to make a skill discoverable to others, go `init`/`create` → `submit` → `request-verification`. Once approved, it becomes discoverable in the library.
115
176
 
116
- | Agent | Directory |
117
- |-------|-----------|
118
- | Cursor | `~/.cursor/skills/` |
119
- | Claude Code | `~/.claude/skills/` |
120
- | GitHub Copilot | `~/.github/skills/` |
121
- | Windsurf | `~/.codeium/windsurf/skills/` |
122
- | Codex | `~/.agents/skills/` |
123
- | Gemini | `~/.gemini/skills/` |
124
- | OpenClaw | `~/.openclaw/skills/` |
177
+ ## Scheduled Tasks
125
178
 
126
- ### Create & Publish Skills
179
+ Run API calls and skills on a schedule.
127
180
 
128
181
  ```bash
129
- # Initialize a new skill from template
130
- orth skills init my-skill
131
- orth skills init my-skill --bare # SKILL.md only, no subdirectories
182
+ orth tasks list # your scheduled tasks
183
+ orth tasks create # create a task
184
+ orth tasks show <id> # task details
185
+ orth tasks trigger <id> # run it now
186
+ orth tasks logs <id> # run history
187
+ orth tasks pause <id> # pause
188
+ orth tasks resume <id> # resume
189
+ orth tasks delete <id> # delete
190
+ ```
132
191
 
133
- # Create a skill from a GitHub repo
134
- orth skills create https://github.com/owner/repo
135
- orth skills create owner/repo --path skills/my-skill --ref main
192
+ ## Account & Usage
136
193
 
137
- # Submit a local skill to the platform
138
- orth skills submit ./my-skill
139
- orth skills submit --name "My Skill" --tags "react,testing"
194
+ ```bash
195
+ orth balance # current credit balance
196
+ orth usage # recent API usage
197
+ ```
140
198
 
141
- # Update local skill from platform (pull)
142
- orth skills update owner/my-skill
143
- orth skills update owner/my-skill ./local-dir --force
199
+ ## Command Reference
144
200
 
145
- # Push local changes to platform
146
- orth skills push owner/my-skill
147
- orth skills push owner/my-skill ./local-dir
201
+ | Command | Description |
202
+ | --- | --- |
203
+ | `orth login` / `logout` / `whoami` | Manage authentication |
204
+ | `orth balance` / `usage` | Credit balance and recent usage |
205
+ | `orth api list` | List available APIs |
206
+ | `orth api search <query>` | Natural-language API search |
207
+ | `orth api show <slug> [path]` | Show endpoints / endpoint details (alias: `info`) |
208
+ | `orth api run <slug> <path>` | Call an endpoint |
209
+ | `orth api code <slug> <path>` | Generate integration code |
210
+ | `orth api request <docsUrl>` | Request an API to be added |
211
+ | `orth skills …` | Browse, install, author, and publish skills |
212
+ | `orth tasks …` | Create and manage scheduled tasks |
148
213
 
149
- # Request verification (required before discoverability)
150
- orth skills request-verification owner/my-skill
214
+ **Shorthand aliases** for the most common commands:
151
215
 
152
- # Request a skill to be added (by description or GitHub URL)
153
- orth skills request "A skill for React testing patterns"
154
- orth skills request https://github.com/owner/cool-skill
216
+ ```bash
217
+ orth search <query> # orth api search
218
+ orth run <slug> <path> # → orth api run
219
+ orth code <slug> <path> # → orth api code
155
220
  ```
156
221
 
157
- ### Verification Workflow
222
+ Run `orth <command> --help` for the full options on any command.
158
223
 
159
- 1. **Submit** your skill → it's on the platform but not publicly visible
160
- 2. **Request verification** → our team reviews it
161
- 3. **Once verified** → toggle discoverability on/off from your [dashboard](https://orthogonal.com/dashboard/skills)
224
+ ### `run` options
162
225
 
163
- ## Account
226
+ | Flag | Description |
227
+ | --- | --- |
228
+ | `-X, --method <method>` | HTTP method (default `GET`) |
229
+ | `-q, --query <k=v...>` | Query params (repeatable, or one `a=1&b=2` string) |
230
+ | `-b, --body <json>` / `-d, --data <json>` | JSON request body |
231
+ | `-o, --output <file>` | Save the response to a file (auto-detects binary) |
232
+ | `--raw` | Print raw JSON (ideal for piping) |
233
+ | `--dry-run` | Estimate the cost without executing |
164
234
 
165
- ```bash
166
- # Check balance
167
- orth balance
235
+ ## Configuration
168
236
 
169
- # View usage
170
- orth usage --limit 20
171
- ```
237
+ | Environment variable | Purpose |
238
+ | --- | --- |
239
+ | `ORTHOGONAL_API_KEY` | API key; overrides the stored credential (use in CI) |
240
+ | `ORTH_API_URL` | Override the API base URL (advanced) |
172
241
 
173
- ## Shorthand Aliases
242
+ Credentials from `orth login` are stored in your OS user-config directory (managed automatically); use `orth logout` to remove them.
174
243
 
175
- These top-level commands are aliases for their `orth api` counterparts:
244
+ ## Programmatic Use
245
+
246
+ Building an application instead of scripting the terminal? Use the official TypeScript SDK, [`@orth/sdk`](https://www.npmjs.com/package/@orth/sdk):
176
247
 
177
248
  ```bash
178
- orth search "query" # → orth api search "query"
179
- orth run slug /path # → orth api run slug /path
180
- orth code slug /path # → orth api code slug /path
249
+ npm install @orth/sdk
181
250
  ```
182
251
 
183
252
  ## Development
184
253
 
185
254
  ```bash
186
- # Install dependencies
255
+ git clone https://github.com/orthogonal-sh/cli.git
256
+ cd cli
187
257
  npm install
188
258
 
189
- # Build
190
- npm run build
191
-
192
- # Watch mode
193
- npm run dev
194
-
195
- # Run tests
196
- npm test
197
-
198
- # Watch tests
199
- npm run test:watch
259
+ npm run build # compile TypeScript to dist/
260
+ npm run dev # compile in watch mode
261
+ npm test # run the test suite (vitest)
262
+ npm start # run the built CLI
200
263
  ```
201
264
 
265
+ Contributions are welcome — please open an issue or a pull request.
266
+
202
267
  ## License
203
268
 
204
- MIT
269
+ [MIT](./LICENSE) © Orthogonal
package/dist/api.js CHANGED
@@ -25,8 +25,10 @@ async function apiRequest(endpoint, options = {}) {
25
25
  // or non-JSON body, which would otherwise throw here and lose the status.
26
26
  const rawBody = await res.text();
27
27
  let data;
28
+ let bodyParsed = false;
28
29
  try {
29
30
  data = (rawBody ? JSON.parse(rawBody) : {});
31
+ bodyParsed = rawBody.length > 0;
30
32
  }
31
33
  catch {
32
34
  data = {};
@@ -64,6 +66,19 @@ async function apiRequest(endpoint, options = {}) {
64
66
  // string-matching the message (e.g. whoami treating 404 as "no /me").
65
67
  const err = new Error(errorMsg);
66
68
  err.status = res.status;
69
+ // Surface the self-correction hint the API attaches on contract violations
70
+ // (missing/out-of-range params, upstream 4xx). Without this the run command
71
+ // only sees `error.message` and the expected-schema diagnostics are lost.
72
+ if (data._orthogonal) {
73
+ err.orthogonal = data._orthogonal;
74
+ }
75
+ // Keep the full parsed error body too, so callers can render structured
76
+ // diagnostics (missing / out_of_range) and emit machine-readable JSON.
77
+ // Only when the body actually parsed as JSON — otherwise a non-JSON body
78
+ // would surface as `{}` and mask the real error message.
79
+ if (bodyParsed) {
80
+ err.responseBody = data;
81
+ }
67
82
  throw err;
68
83
  }
69
84
  // Return the whole response, not just data field
@@ -58,6 +58,80 @@ function isBinaryEnvelope(data) {
58
58
  typeof data.contentType === "string" &&
59
59
  typeof data.size === "number");
60
60
  }
61
+ /**
62
+ * Render the API's self-correction diagnostics attached to a failed run.
63
+ * Pulls the `_orthogonal` hint (expected schema + field diagnostics) plus the
64
+ * top-level `missing` / `out_of_range` arrays from the error response body.
65
+ * The upstream-4xx path returns the schema ONLY inside `_orthogonal`, and the
66
+ * pre-validation path returns `out_of_range` at the top level — so without this
67
+ * the concrete violation is invisible in the human output.
68
+ */
69
+ function printFailureHint(body) {
70
+ if (!body || typeof body !== "object")
71
+ return;
72
+ const hint = body._orthogonal;
73
+ const hasHint = hint && typeof hint === "object";
74
+ const outOfRange = Array.isArray(body.out_of_range) ? body.out_of_range : [];
75
+ const missing = Array.isArray(body.missing) ? body.missing : [];
76
+ if (!hasHint && outOfRange.length === 0 && missing.length === 0)
77
+ return;
78
+ console.error(chalk_1.default.yellow("\nHint:"));
79
+ if (hasHint && hint.message)
80
+ console.error(chalk_1.default.gray(` ${hint.message}`));
81
+ if (missing.length > 0) {
82
+ console.error(chalk_1.default.gray(" Missing required params: ") + chalk_1.default.white(missing.join(", ")));
83
+ }
84
+ if (outOfRange.length > 0) {
85
+ const parts = outOfRange.map((p) => {
86
+ const bounds = [
87
+ typeof p.min === "number" ? `>= ${p.min}` : null,
88
+ typeof p.max === "number" ? `<= ${p.max}` : null,
89
+ ]
90
+ .filter(Boolean)
91
+ .join(" and ");
92
+ return bounds ? `${p.name}=${p.value} (must be ${bounds})` : `${p.name}=${p.value}`;
93
+ });
94
+ console.error(chalk_1.default.gray(" Out of range: ") + chalk_1.default.white(parts.join(", ")));
95
+ }
96
+ if (!hasHint)
97
+ return;
98
+ const diagnostics = [
99
+ ["Missing required query params", hint.missing_required_query],
100
+ ["Unexpected query fields", hint.unexpected_query_fields],
101
+ ["Missing required body params", hint.missing_required_body],
102
+ ["Unexpected body fields", hint.unexpected_body_fields],
103
+ ];
104
+ for (const [label, value] of diagnostics) {
105
+ if (Array.isArray(value) && value.length > 0) {
106
+ console.error(chalk_1.default.gray(` ${label}: `) + chalk_1.default.white(value.join(", ")));
107
+ }
108
+ }
109
+ const summarize = (schema, kind) => {
110
+ if (!schema?.properties)
111
+ return;
112
+ const required = new Set(schema.required || []);
113
+ const names = Object.keys(schema.properties).map((name) => {
114
+ const prop = schema.properties[name] || {};
115
+ const bits = [name];
116
+ if (required.has(name))
117
+ bits.push("(required)");
118
+ const range = [
119
+ typeof prop.minimum === "number" ? `min ${prop.minimum}` : null,
120
+ typeof prop.maximum === "number" ? `max ${prop.maximum}` : null,
121
+ ]
122
+ .filter(Boolean)
123
+ .join(", ");
124
+ if (range)
125
+ bits.push(`[${range}]`);
126
+ return bits.join(" ");
127
+ });
128
+ if (names.length > 0) {
129
+ console.error(chalk_1.default.gray(` Expected ${kind}: `) + chalk_1.default.white(names.join(", ")));
130
+ }
131
+ };
132
+ summarize(hint.expected_schema?.queryParams, "query params");
133
+ summarize(hint.expected_schema?.body, "body params");
134
+ }
61
135
  function formatCost(result) {
62
136
  if (result.price)
63
137
  return result.price;
@@ -67,7 +141,11 @@ function formatCost(result) {
67
141
  return null;
68
142
  }
69
143
  async function runCommand(api, path, options) {
70
- const spinner = (0, ora_1.default)(`Calling ${api}${path}...`).start();
144
+ // In --raw mode keep the streams clean for piping: don't animate the spinner
145
+ // on stderr, so a failed call's stderr is a single parseable JSON document.
146
+ const spinner = (0, ora_1.default)(`Calling ${api}${path}...`);
147
+ if (!options.raw)
148
+ spinner.start();
71
149
  try {
72
150
  // Parse query params
73
151
  // Supports both `-q key=value -q key2=value2` and `-q 'key=value&key2=value2'`
@@ -190,7 +268,24 @@ async function runCommand(api, path, options) {
190
268
  }
191
269
  catch (error) {
192
270
  spinner.stop();
193
- console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
271
+ const err = error;
272
+ const message = error instanceof Error ? error.message : "Unknown error";
273
+ if (options.raw) {
274
+ // In --raw mode, stderr must always be a single parseable JSON document
275
+ // so agents can JSON.parse it. Prefer the server's parsed body; otherwise
276
+ // (e.g. a non-JSON HTML 502) emit a synthesized error envelope.
277
+ const envelope = err?.responseBody ?? {
278
+ success: false,
279
+ error: message,
280
+ ...(typeof err?.status === "number" ? { status: err.status } : {}),
281
+ };
282
+ console.error(JSON.stringify(envelope, null, 2));
283
+ }
284
+ else {
285
+ console.error(chalk_1.default.red(`Error: ${message}`));
286
+ if (err?.responseBody)
287
+ printFailureHint(err.responseBody);
288
+ }
194
289
  process.exit(1);
195
290
  }
196
291
  }
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orth/cli",
3
- "version": "0.2.28",
3
+ "version": "0.2.29",
4
4
  "description": "CLI to access all APIs and skills on the Orthogonal platform",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
- "README.md"
11
+ "README.md",
12
+ "LICENSE"
12
13
  ],
13
14
  "scripts": {
14
15
  "build": "tsc",