@orth/cli 0.2.33 → 0.3.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.
- package/dist/analytics.d.ts +1 -1
- package/dist/analytics.js +7 -3
- package/dist/api.d.ts +2 -0
- package/dist/commands/api.js +13 -2
- package/dist/commands/search.d.ts +2 -1
- package/dist/commands/search.js +13 -4
- package/dist/commands/skills.d.ts +28 -1
- package/dist/commands/skills.js +3 -3
- package/dist/index.js +19 -8
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +12 -0
- package/package.json +1 -1
package/dist/analytics.d.ts
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Fire-and-forget analytics event for CLI usage tracking.
|
|
3
3
|
* Never blocks, never throws, never shows errors to the user.
|
|
4
4
|
*/
|
|
5
|
-
export declare function trackEvent(command: string, args?: Record<string, string | undefined
|
|
5
|
+
export declare function trackEvent(command: string, args?: Record<string, string | undefined>, response?: unknown): void;
|
package/dist/analytics.js
CHANGED
|
@@ -3,12 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.trackEvent = trackEvent;
|
|
4
4
|
const config_js_1 = require("./config.js");
|
|
5
5
|
const CLI_VERSION = process.env.npm_package_version || "0.2.0";
|
|
6
|
-
const
|
|
6
|
+
const PRODUCTION_BASE_URL = "https://api.orthogonal.com/v1";
|
|
7
|
+
function getAnalyticsBaseUrl() {
|
|
8
|
+
return process.env.ORTH_API_URL || PRODUCTION_BASE_URL;
|
|
9
|
+
}
|
|
7
10
|
/**
|
|
8
11
|
* Fire-and-forget analytics event for CLI usage tracking.
|
|
9
12
|
* Never blocks, never throws, never shows errors to the user.
|
|
10
13
|
*/
|
|
11
|
-
function trackEvent(command, args) {
|
|
14
|
+
function trackEvent(command, args, response) {
|
|
12
15
|
// Don't track if no API key (not authenticated)
|
|
13
16
|
const apiKey = (0, config_js_1.getApiKey)();
|
|
14
17
|
if (!apiKey)
|
|
@@ -16,6 +19,7 @@ function trackEvent(command, args) {
|
|
|
16
19
|
const payload = {
|
|
17
20
|
command,
|
|
18
21
|
args: args ? sanitizeArgs(args) : undefined,
|
|
22
|
+
response,
|
|
19
23
|
cliVersion: CLI_VERSION,
|
|
20
24
|
os: process.platform,
|
|
21
25
|
nodeVersion: process.version,
|
|
@@ -23,7 +27,7 @@ function trackEvent(command, args) {
|
|
|
23
27
|
timestamp: new Date().toISOString(),
|
|
24
28
|
};
|
|
25
29
|
// Fire and forget - don't await, don't catch visible errors
|
|
26
|
-
fetch(`${
|
|
30
|
+
fetch(`${getAnalyticsBaseUrl()}/cli/events`, {
|
|
27
31
|
method: "POST",
|
|
28
32
|
headers: {
|
|
29
33
|
Authorization: `Bearer ${apiKey}`,
|
package/dist/api.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface SearchResponse {
|
|
|
13
13
|
price?: number;
|
|
14
14
|
hasDynamicPricing?: boolean;
|
|
15
15
|
pricing_formula?: string;
|
|
16
|
+
apiSlug?: string;
|
|
16
17
|
}>;
|
|
17
18
|
}>;
|
|
18
19
|
count: number;
|
|
@@ -139,6 +140,7 @@ export interface ApiBySlugResponse {
|
|
|
139
140
|
required: boolean;
|
|
140
141
|
description?: string;
|
|
141
142
|
}>;
|
|
143
|
+
apiSlug?: string;
|
|
142
144
|
}>;
|
|
143
145
|
count: number;
|
|
144
146
|
}
|
package/dist/commands/api.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.apiCommand = apiCommand;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const ora_1 = __importDefault(require("ora"));
|
|
9
9
|
const api_js_1 = require("../api.js");
|
|
10
|
+
const utils_js_1 = require("../utils.js");
|
|
10
11
|
async function apiCommand(slug, path, options) {
|
|
11
12
|
const spinner = (0, ora_1.default)("Loading...").start();
|
|
12
13
|
try {
|
|
@@ -162,7 +163,14 @@ async function apiCommand(slug, path, options) {
|
|
|
162
163
|
? chalk_1.default.dim(ep)
|
|
163
164
|
: chalk_1.default.dim(`$${parseFloat(Number(ep).toFixed(4))}`);
|
|
164
165
|
})();
|
|
165
|
-
|
|
166
|
+
// Group members commonly collide on the same upstream path (e.g.
|
|
167
|
+
// AbstractAPI's many products all expose "/v1") — when the endpoint's
|
|
168
|
+
// own callable slug differs from the card's flat slug, show it so the
|
|
169
|
+
// right identifier is obvious before calling `orth run`.
|
|
170
|
+
const memberTag = endpoint.apiSlug && endpoint.apiSlug !== api.slug
|
|
171
|
+
? chalk_1.default.dim(` [${(0, utils_js_1.sanitizeForTerminal)(endpoint.apiSlug)}]`)
|
|
172
|
+
: "";
|
|
173
|
+
console.log(`${method} ${chalk_1.default.white(endpoint.path)} ${price}${memberTag}`);
|
|
166
174
|
if (endpoint.description) {
|
|
167
175
|
console.log(chalk_1.default.gray(` ${endpoint.description.slice(0, 80)}${endpoint.description.length > 80 ? "..." : ""}`));
|
|
168
176
|
}
|
|
@@ -195,7 +203,10 @@ async function apiCommand(slug, path, options) {
|
|
|
195
203
|
? chalk_1.default.dim(ep2)
|
|
196
204
|
: chalk_1.default.dim(`$${parseFloat(Number(ep2).toFixed(4))}`);
|
|
197
205
|
})();
|
|
198
|
-
|
|
206
|
+
const memberTag = endpoint.apiSlug && endpoint.apiSlug !== api.slug
|
|
207
|
+
? chalk_1.default.dim(` [${(0, utils_js_1.sanitizeForTerminal)(endpoint.apiSlug)}]`)
|
|
208
|
+
: "";
|
|
209
|
+
console.log(`${method} ${chalk_1.default.white(endpoint.path)} ${price}${memberTag}`);
|
|
199
210
|
if (endpoint.description) {
|
|
200
211
|
console.log(chalk_1.default.gray(` ${endpoint.description.slice(0, 80)}${endpoint.description.length > 80 ? "..." : ""}`));
|
|
201
212
|
}
|
package/dist/commands/search.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.searchCommand = searchCommand;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const ora_1 = __importDefault(require("ora"));
|
|
9
9
|
const api_js_1 = require("../api.js");
|
|
10
|
+
const utils_js_1 = require("../utils.js");
|
|
10
11
|
async function searchCommand(query, options) {
|
|
11
12
|
const spinner = (0, ora_1.default)("Searching APIs...").start();
|
|
12
13
|
try {
|
|
@@ -14,16 +15,24 @@ async function searchCommand(query, options) {
|
|
|
14
15
|
spinner.stop();
|
|
15
16
|
if (!data.results || data.results.length === 0) {
|
|
16
17
|
console.log(chalk_1.default.yellow("No APIs found matching your query."));
|
|
17
|
-
return;
|
|
18
|
+
return data;
|
|
18
19
|
}
|
|
19
20
|
console.log(chalk_1.default.bold(`\nFound ${data.apisCount} APIs with ${data.count} matching endpoints:\n`));
|
|
20
21
|
for (const api of data.results) {
|
|
21
22
|
console.log(chalk_1.default.cyan.bold(`${api.name}`) + chalk_1.default.gray(` (${api.slug})`));
|
|
22
23
|
for (const endpoint of api.endpoints.slice(0, 3)) {
|
|
23
24
|
const method = endpoint.method.padEnd(6);
|
|
25
|
+
// Group members commonly collide on the same upstream path (e.g.
|
|
26
|
+
// AbstractAPI's many products all expose "/v1") — when the endpoint's
|
|
27
|
+
// own callable slug differs from the card's flat slug, show it so the
|
|
28
|
+
// right identifier is obvious before calling `orth run`.
|
|
29
|
+
const memberTag = endpoint.apiSlug && endpoint.apiSlug !== api.slug
|
|
30
|
+
? chalk_1.default.dim(` [${(0, utils_js_1.sanitizeForTerminal)(endpoint.apiSlug)}]`)
|
|
31
|
+
: "";
|
|
24
32
|
console.log(chalk_1.default.gray(" ") +
|
|
25
33
|
chalk_1.default.yellow(method) +
|
|
26
|
-
chalk_1.default.white(endpoint.path)
|
|
34
|
+
chalk_1.default.white(endpoint.path) +
|
|
35
|
+
memberTag);
|
|
27
36
|
if (endpoint.description) {
|
|
28
37
|
console.log(chalk_1.default.gray(` ${endpoint.description.slice(0, 80)}${endpoint.description.length > 80 ? "..." : ""}`));
|
|
29
38
|
}
|
|
@@ -35,10 +44,10 @@ async function searchCommand(query, options) {
|
|
|
35
44
|
}
|
|
36
45
|
console.log(chalk_1.default.gray(`Run 'orth api <slug>' to see all endpoints for an API`));
|
|
37
46
|
console.log(chalk_1.default.gray(`Run 'orth run <api> <path>' to call an endpoint`));
|
|
47
|
+
return data;
|
|
38
48
|
}
|
|
39
49
|
catch (error) {
|
|
40
50
|
spinner.stop();
|
|
41
|
-
|
|
42
|
-
process.exit(1);
|
|
51
|
+
throw error;
|
|
43
52
|
}
|
|
44
53
|
}
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
interface SkillResponse {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
sourceType: "github" | "upload";
|
|
7
|
+
githubOwner?: string;
|
|
8
|
+
githubRepo?: string;
|
|
9
|
+
githubPath?: string;
|
|
10
|
+
githubRef?: string;
|
|
11
|
+
discoverable: boolean;
|
|
12
|
+
verified: boolean;
|
|
13
|
+
installCount: number;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
installCommand?: string;
|
|
16
|
+
githubUrl?: string;
|
|
17
|
+
files?: {
|
|
18
|
+
filePath: string;
|
|
19
|
+
content: string;
|
|
20
|
+
isPrimary: boolean;
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
interface SkillSearchResponse {
|
|
24
|
+
results: SkillResponse[];
|
|
25
|
+
count: number;
|
|
26
|
+
}
|
|
1
27
|
export declare function skillsListCommand(options: {
|
|
2
28
|
limit: string;
|
|
3
29
|
}): Promise<void>;
|
|
@@ -6,7 +32,7 @@ export declare function skillsMineCommand(options: {
|
|
|
6
32
|
}): Promise<void>;
|
|
7
33
|
export declare function skillsSearchCommand(query: string, options: {
|
|
8
34
|
limit: string;
|
|
9
|
-
}): Promise<
|
|
35
|
+
}): Promise<SkillSearchResponse>;
|
|
10
36
|
export declare function skillsShowCommand(slug: string): Promise<void>;
|
|
11
37
|
export declare function skillsCreateCommand(githubRepo: string, options: {
|
|
12
38
|
path?: string;
|
|
@@ -37,3 +63,4 @@ export declare function skillsPublishCommand(slug: string, options: {
|
|
|
37
63
|
unpublish?: boolean;
|
|
38
64
|
}): Promise<void>;
|
|
39
65
|
export declare function skillsRequestCommand(input: string): Promise<void>;
|
|
66
|
+
export {};
|
package/dist/commands/skills.js
CHANGED
|
@@ -150,7 +150,7 @@ async function skillsSearchCommand(query, options) {
|
|
|
150
150
|
spinner.stop();
|
|
151
151
|
if (!data.results || data.results.length === 0) {
|
|
152
152
|
console.log(chalk_1.default.yellow("No skills found matching your query."));
|
|
153
|
-
return;
|
|
153
|
+
return data;
|
|
154
154
|
}
|
|
155
155
|
console.log(chalk_1.default.bold(`\nFound ${data.count} skills:\n`));
|
|
156
156
|
for (const skill of data.results) {
|
|
@@ -164,11 +164,11 @@ async function skillsSearchCommand(query, options) {
|
|
|
164
164
|
console.log();
|
|
165
165
|
}
|
|
166
166
|
console.log(chalk_1.default.gray("Run 'orth skills show <slug>' for full details"));
|
|
167
|
+
return data;
|
|
167
168
|
}
|
|
168
169
|
catch (error) {
|
|
169
170
|
spinner.stop();
|
|
170
|
-
|
|
171
|
-
process.exit(1);
|
|
171
|
+
throw error;
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/dist/index.js
CHANGED
|
@@ -13,17 +13,31 @@ const apiRequest_js_1 = require("./commands/apiRequest.js");
|
|
|
13
13
|
const analytics_js_1 = require("./analytics.js");
|
|
14
14
|
/**
|
|
15
15
|
* Wraps an async action callback so that rejected promises are caught,
|
|
16
|
-
* logged, and
|
|
16
|
+
* logged, and set the process exit code to 1. Without this,
|
|
17
17
|
* Commander.js silently swallows unhandled rejections from async actions.
|
|
18
18
|
*/
|
|
19
19
|
function asyncAction(fn) {
|
|
20
20
|
return (...args) => {
|
|
21
21
|
fn(...args).catch((err) => {
|
|
22
22
|
console.error(err instanceof Error ? err.message : "Unknown error");
|
|
23
|
-
|
|
23
|
+
// Avoid a hard exit so fire-and-forget failure analytics can flush.
|
|
24
|
+
process.exitCode = 1;
|
|
24
25
|
});
|
|
25
26
|
};
|
|
26
27
|
}
|
|
28
|
+
async function runTrackedSearch(command, query, operation) {
|
|
29
|
+
try {
|
|
30
|
+
const response = await operation();
|
|
31
|
+
(0, analytics_js_1.trackEvent)(command, { query }, response);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
(0, analytics_js_1.trackEvent)(command, { query }, {
|
|
35
|
+
success: false,
|
|
36
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
37
|
+
});
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
27
41
|
const program = new commander_1.Command();
|
|
28
42
|
program
|
|
29
43
|
.name("orth")
|
|
@@ -99,8 +113,7 @@ apiGroup
|
|
|
99
113
|
.description("Search for APIs using natural language")
|
|
100
114
|
.option("-l, --limit <number>", "Max results", "10")
|
|
101
115
|
.action(asyncAction(async (query, options) => {
|
|
102
|
-
|
|
103
|
-
await (0, search_js_1.searchCommand)(query, options);
|
|
116
|
+
await runTrackedSearch("api.search", query, () => (0, search_js_1.searchCommand)(query, options));
|
|
104
117
|
}));
|
|
105
118
|
apiGroup
|
|
106
119
|
.command("run <slug> <path>")
|
|
@@ -159,8 +172,7 @@ skillsGroup
|
|
|
159
172
|
.description("Search for agent skills")
|
|
160
173
|
.option("-l, --limit <number>", "Max results", "20")
|
|
161
174
|
.action(asyncAction(async (query, options) => {
|
|
162
|
-
|
|
163
|
-
await (0, skills_js_1.skillsSearchCommand)(query, options);
|
|
175
|
+
await runTrackedSearch("skills.search", query, () => (0, skills_js_1.skillsSearchCommand)(query, options));
|
|
164
176
|
}));
|
|
165
177
|
skillsGroup
|
|
166
178
|
.command("show <slug>")
|
|
@@ -254,8 +266,7 @@ program
|
|
|
254
266
|
.description("Search for APIs (alias for 'orth api search')")
|
|
255
267
|
.option("-l, --limit <number>", "Max results", "10")
|
|
256
268
|
.action(asyncAction(async (query, options) => {
|
|
257
|
-
|
|
258
|
-
await (0, search_js_1.searchCommand)(query, options);
|
|
269
|
+
await runTrackedSearch("search", query, () => (0, search_js_1.searchCommand)(query, options));
|
|
259
270
|
}));
|
|
260
271
|
program
|
|
261
272
|
.command("run <api> <path>")
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sanitizeForTerminal(value: string): string;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanitizeForTerminal = sanitizeForTerminal;
|
|
4
|
+
// Strips ASCII control characters (including ESC, 0x1B) from a string before
|
|
5
|
+
// it's written to the terminal. Needed for any remotely-sourced value we
|
|
6
|
+
// render directly (e.g. apiSlug, which an API owner sets) — without this, a
|
|
7
|
+
// crafted value containing terminal escape sequences could manipulate another
|
|
8
|
+
// user's terminal (spoofed output, hidden/cleared text, deceptive hyperlinks).
|
|
9
|
+
function sanitizeForTerminal(value) {
|
|
10
|
+
// eslint-disable-next-line no-control-regex
|
|
11
|
+
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
12
|
+
}
|