@orth/cli 0.2.10 → 0.2.12

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/api.d.ts CHANGED
@@ -58,6 +58,15 @@ export interface DetailsResponse {
58
58
  description?: string;
59
59
  }>;
60
60
  };
61
+ action?: {
62
+ description?: string;
63
+ parameters?: Array<{
64
+ name: string;
65
+ type: string;
66
+ required: boolean;
67
+ description?: string;
68
+ }>;
69
+ };
61
70
  }
62
71
  export interface RunResponse {
63
72
  success: boolean;
@@ -71,6 +80,26 @@ export interface IntegrateResponse {
71
80
  path: string;
72
81
  snippets: Record<string, string>;
73
82
  }
83
+ export interface ListApisResponse {
84
+ success: boolean;
85
+ apis: Array<{
86
+ name: string;
87
+ slug: string;
88
+ description?: string;
89
+ baseUrl: string;
90
+ verified: boolean;
91
+ endpoints: Array<{
92
+ path: string;
93
+ method: string;
94
+ description?: string;
95
+ price?: number;
96
+ isPayable?: boolean;
97
+ }>;
98
+ }>;
99
+ count: number;
100
+ hasMore: boolean;
101
+ }
102
+ export declare function listApis(limit?: number, offset?: number): Promise<ListApisResponse>;
74
103
  export declare function search(prompt: string, limit?: number): Promise<SearchResponse>;
75
104
  export interface ApiBySlugResponse {
76
105
  success: boolean;
package/dist/api.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.apiRequest = apiRequest;
4
+ exports.listApis = listApis;
4
5
  exports.search = search;
5
6
  exports.getApiBySlug = getApiBySlug;
6
7
  exports.getDetails = getDetails;
@@ -54,6 +55,9 @@ async function apiRequest(endpoint, options = {}) {
54
55
  // Return the whole response, not just data field
55
56
  return data;
56
57
  }
58
+ async function listApis(limit = 100, offset = 0) {
59
+ return apiRequest(`/list-endpoints?limit=${limit}&offset=${offset}`);
60
+ }
57
61
  async function search(prompt, limit = 10) {
58
62
  return apiRequest("/search", {
59
63
  method: "POST",
@@ -11,32 +11,41 @@ async function apiCommand(slug, path, options) {
11
11
  const spinner = (0, ora_1.default)("Loading...").start();
12
12
  try {
13
13
  if (!slug) {
14
- // List all APIs - search multiple terms to get broader coverage
15
- const searches = await Promise.all([
16
- (0, api_js_1.search)("api", 50),
17
- (0, api_js_1.search)("data", 50),
18
- (0, api_js_1.search)("search", 50),
19
- (0, api_js_1.search)("email", 50),
20
- ]);
21
- spinner.stop();
22
- // Merge and dedupe results
23
- const allResults = searches.flatMap(s => s.results || []);
24
- const data = {
25
- results: allResults,
26
- count: allResults.length,
27
- apisCount: new Set(allResults.map(r => r.slug)).size,
28
- };
29
- console.log(chalk_1.default.bold("\nAvailable APIs:\n"));
30
- const seen = new Set();
31
- for (const api of data.results) {
32
- if (!api.slug || seen.has(api.slug))
33
- continue;
34
- seen.add(api.slug);
35
- console.log(chalk_1.default.cyan.bold(api.slug.padEnd(20)) +
36
- chalk_1.default.white(api.name || "") +
37
- chalk_1.default.gray(` (${api.endpoints?.length || 0} endpoints)`));
14
+ // List all APIs using the list-endpoints endpoint
15
+ try {
16
+ const data = await (0, api_js_1.listApis)(500);
17
+ spinner.stop();
18
+ console.log(chalk_1.default.bold("\nAvailable APIs:\n"));
19
+ const apis = (data.apis || []).sort((a, b) => a.slug.localeCompare(b.slug));
20
+ for (const api of apis) {
21
+ console.log(chalk_1.default.cyan.bold(api.slug.padEnd(20)) +
22
+ chalk_1.default.white(api.name || "") +
23
+ chalk_1.default.gray(` (${api.endpoints?.length || 0} endpoints)`));
24
+ }
25
+ console.log(chalk_1.default.gray("\nRun 'orth api show <slug>' to see endpoints for an API"));
26
+ }
27
+ catch {
28
+ // Fallback to search-based listing if list-endpoints not available
29
+ const searches = await Promise.all([
30
+ (0, api_js_1.search)("api", 50),
31
+ (0, api_js_1.search)("data", 50),
32
+ (0, api_js_1.search)("search", 50),
33
+ (0, api_js_1.search)("email", 50),
34
+ ]);
35
+ spinner.stop();
36
+ const allResults = searches.flatMap(s => s.results || []);
37
+ console.log(chalk_1.default.bold("\nAvailable APIs:\n"));
38
+ const seen = new Set();
39
+ for (const api of allResults) {
40
+ if (!api.slug || seen.has(api.slug))
41
+ continue;
42
+ seen.add(api.slug);
43
+ console.log(chalk_1.default.cyan.bold(api.slug.padEnd(20)) +
44
+ chalk_1.default.white(api.name || "") +
45
+ chalk_1.default.gray(` (${api.endpoints?.length || 0} endpoints)`));
46
+ }
47
+ console.log(chalk_1.default.gray("\nRun 'orth api show <slug>' to see endpoints for an API"));
38
48
  }
39
- console.log(chalk_1.default.gray("\nRun 'orth api show <slug>' to see endpoints for an API"));
40
49
  return;
41
50
  }
42
51
  if (path) {
@@ -44,12 +53,13 @@ async function apiCommand(slug, path, options) {
44
53
  const data = await (0, api_js_1.getDetails)(slug, path);
45
54
  spinner.stop();
46
55
  console.log(chalk_1.default.bold(`\n${chalk_1.default.cyan(slug)}${chalk_1.default.white(path)}\n`));
47
- // Get description from endpoint object if available
48
- const desc = data.description || data.endpoint?.description;
56
+ // Get description from endpoint or action object if available
57
+ const desc = data.description || data.endpoint?.description || data.action?.description;
49
58
  console.log(chalk_1.default.gray(desc || "No description"));
50
- // Get params from nested endpoint object if needed
59
+ // Get params from nested endpoint or action object if needed
60
+ const actionParams = data.action?.parameters ?? [];
51
61
  const queryParams = data.parameters?.query || data.endpoint?.queryParams || [];
52
- const bodyParams = data.parameters?.body || data.endpoint?.bodyParams || [];
62
+ const bodyParams = data.parameters?.body || data.endpoint?.bodyParams || actionParams;
53
63
  if (queryParams.length > 0) {
54
64
  console.log(chalk_1.default.bold("\nQuery Parameters:"));
55
65
  for (const param of queryParams) {
@@ -33,7 +33,7 @@ async function whoamiCommand() {
33
33
  const key = (0, config_js_1.getApiKey)();
34
34
  if (!key) {
35
35
  console.log(chalk_1.default.yellow("Not authenticated"));
36
- console.log(chalk_1.default.gray("Run 'ortho login' to authenticate"));
36
+ console.log(chalk_1.default.gray("Run 'orth login' to authenticate"));
37
37
  return;
38
38
  }
39
39
  console.log(chalk_1.default.green("✓ Authenticated"));
@@ -4,4 +4,5 @@ export declare function runCommand(api: string, path: string, options: {
4
4
  body?: string;
5
5
  data?: string;
6
6
  raw?: boolean;
7
+ output?: string;
7
8
  }): Promise<void>;
@@ -6,7 +6,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runCommand = runCommand;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const ora_1 = __importDefault(require("ora"));
9
+ const fs_1 = require("fs");
10
+ const path_1 = require("path");
9
11
  const api_js_1 = require("../api.js");
12
+ // Map content-type to file extension
13
+ const CONTENT_TYPE_EXT = {
14
+ "image/jpeg": "jpg",
15
+ "image/png": "png",
16
+ "image/gif": "gif",
17
+ "image/webp": "webp",
18
+ "image/svg+xml": "svg",
19
+ "application/pdf": "pdf",
20
+ "application/zip": "zip",
21
+ "application/octet-stream": "bin",
22
+ "audio/mpeg": "mp3",
23
+ "audio/wav": "wav",
24
+ "video/mp4": "mp4",
25
+ };
26
+ const VALID_ENCODINGS = new Set(["base64", "base64url", "hex", "utf8", "utf-8", "ascii", "latin1", "binary"]);
27
+ function writeExclusive(filePath, data) {
28
+ try {
29
+ (0, fs_1.writeFileSync)(filePath, data, { flag: "wx" });
30
+ }
31
+ catch (err) {
32
+ if (err?.code === "EEXIST") {
33
+ console.error(chalk_1.default.red(`\nError: File already exists: ${filePath}`));
34
+ console.error(chalk_1.default.gray("Remove it first or choose a different path."));
35
+ process.exit(1);
36
+ }
37
+ throw err;
38
+ }
39
+ }
40
+ function extFromContentType(contentType) {
41
+ // Try exact match first, then prefix match
42
+ if (CONTENT_TYPE_EXT[contentType])
43
+ return CONTENT_TYPE_EXT[contentType];
44
+ // Strip parameters (e.g., "image/jpeg; charset=utf-8")
45
+ const base = contentType.split(";")[0].trim();
46
+ if (CONTENT_TYPE_EXT[base])
47
+ return CONTENT_TYPE_EXT[base];
48
+ // Fallback: use subtype
49
+ const parts = base.split("/");
50
+ return parts[1] || "bin";
51
+ }
52
+ function isBinaryEnvelope(data) {
53
+ return (typeof data === "object" &&
54
+ data !== null &&
55
+ data._binary === true &&
56
+ typeof data.data === "string" &&
57
+ typeof data.encoding === "string" &&
58
+ typeof data.contentType === "string" &&
59
+ typeof data.size === "number");
60
+ }
10
61
  async function runCommand(api, path, options) {
11
62
  const spinner = (0, ora_1.default)(`Calling ${api}${path}...`).start();
12
63
  try {
@@ -64,6 +115,35 @@ async function runCommand(api, path, options) {
64
115
  body,
65
116
  });
66
117
  spinner.stop();
118
+ // Handle binary responses (base64-encoded by the server)
119
+ if (isBinaryEnvelope(result.data)) {
120
+ if (!options.output) {
121
+ const ext = extFromContentType(result.data.contentType);
122
+ const methodHint = options.method !== "GET" ? ` -X ${options.method}` : "";
123
+ const bodyHint = options.body || options.data ? " --body '...'" : "";
124
+ const queryHint = options.query?.length ? " -q '...'" : "";
125
+ console.log(chalk_1.default.yellow(`\nResponse contains binary ${ext.toUpperCase()} data (${result.data.size} bytes).` +
126
+ `\nUse -o to save it: orth api run ${api} ${path}${methodHint}${queryHint}${bodyHint} -o output.${ext}`));
127
+ return;
128
+ }
129
+ const ext = extFromContentType(result.data.contentType);
130
+ const outputPath = (0, path_1.resolve)(options.output);
131
+ if (!VALID_ENCODINGS.has(result.data.encoding)) {
132
+ console.error(chalk_1.default.red(`\nError: Server returned unsupported encoding "${result.data.encoding}".`));
133
+ process.exit(1);
134
+ }
135
+ const buffer = Buffer.from(result.data.data, result.data.encoding);
136
+ writeExclusive(outputPath, buffer);
137
+ console.log(chalk_1.default.green(`\n${ext.toUpperCase()} saved to: ${outputPath} (${buffer.length} bytes)`));
138
+ return;
139
+ }
140
+ // If --output specified for non-binary data, save JSON to file
141
+ if (options.output) {
142
+ const outputPath = (0, path_1.resolve)(options.output);
143
+ writeExclusive(outputPath, JSON.stringify(result.data, null, 2));
144
+ console.log(chalk_1.default.green(`\nResponse saved to: ${outputPath}`));
145
+ return;
146
+ }
67
147
  if (options.raw) {
68
148
  console.log(JSON.stringify(result.data, null, 2));
69
149
  }
package/dist/config.js CHANGED
@@ -29,7 +29,7 @@ function clearApiKey() {
29
29
  function requireApiKey() {
30
30
  const key = getApiKey();
31
31
  if (!key) {
32
- console.error("Error: Not authenticated. Run 'ortho login' first or set ORTHOGONAL_API_KEY.");
32
+ console.error("Error: Not authenticated. Run 'orth login' first or set ORTHOGONAL_API_KEY.");
33
33
  process.exit(1);
34
34
  }
35
35
  return key;
package/dist/index.js CHANGED
@@ -110,6 +110,7 @@ apiGroup
110
110
  .option("-b, --body <json>", "Request body JSON")
111
111
  .option("-d, --data <json>", "Alias for --body")
112
112
  .option("--raw", "Output raw JSON without formatting")
113
+ .option("-o, --output <file>", "Save response to file (auto-detects binary like images)")
113
114
  .action(asyncAction(async (slug, path, options) => {
114
115
  (0, analytics_js_1.trackEvent)("api.run", { slug, path });
115
116
  await (0, run_js_1.runCommand)(slug, path, options);
@@ -253,6 +254,7 @@ program
253
254
  .option("-b, --body <json>", "Request body JSON")
254
255
  .option("-d, --data <json>", "Alias for --body")
255
256
  .option("--raw", "Output raw JSON without formatting")
257
+ .option("-o, --output <file>", "Save response to file (auto-detects binary like images)")
256
258
  .action(asyncAction(async (api, path, options) => {
257
259
  (0, analytics_js_1.trackEvent)("run", { api, path });
258
260
  await (0, run_js_1.runCommand)(api, path, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orth/cli",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "CLI to access all APIs and skills on the Orthogonal platform",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -40,11 +40,11 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
- "chalk": "^5.3.0",
43
+ "chalk": "^4.1.2",
44
44
  "commander": "^12.0.0",
45
45
  "conf": "^12.0.0",
46
46
  "node-fetch": "^3.3.2",
47
- "ora": "^8.0.1"
47
+ "ora": "^5.4.1"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "^20.0.0",