xapi-to 0.1.13 → 0.1.15

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/index.js ADDED
@@ -0,0 +1,1335 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
7
+
8
+ // src/config.ts
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
10
+
11
+ // src/format.ts
12
+ function getFormat() {
13
+ const f = process.env.XAPI_OUTPUT || "json";
14
+ if (f === "pretty" || f === "table") return f;
15
+ return "json";
16
+ }
17
+ function output(data, format) {
18
+ const fmt = format || getFormat();
19
+ if (fmt === "json") {
20
+ console.log(JSON.stringify(data));
21
+ return;
22
+ }
23
+ if (fmt === "pretty") {
24
+ console.log(JSON.stringify(data, null, 2));
25
+ return;
26
+ }
27
+ if (fmt === "table" && Array.isArray(data)) {
28
+ printTable(data);
29
+ return;
30
+ }
31
+ console.log(JSON.stringify(data, null, 2));
32
+ }
33
+ function printTable(rows) {
34
+ if (rows.length === 0) {
35
+ console.log("(empty)");
36
+ return;
37
+ }
38
+ const keys = Object.keys(rows[0]);
39
+ const widths = keys.map(
40
+ (k) => Math.min(40, Math.max(k.length, ...rows.map((r) => String(r[k] ?? "").length)))
41
+ );
42
+ const sep = widths.map((w) => "-".repeat(w)).join(" ");
43
+ const header = keys.map((k, i) => k.padEnd(widths[i])).join(" ");
44
+ console.log(header);
45
+ console.log(sep);
46
+ for (const row of rows) {
47
+ const line = keys.map((k, i) => String(row[k] ?? "").slice(0, widths[i]).padEnd(widths[i])).join(" ");
48
+ console.log(line);
49
+ }
50
+ }
51
+ function err(msg, detail) {
52
+ if (process.stderr.isTTY) {
53
+ console.error(`Error: ${msg}`);
54
+ if (detail !== void 0) console.error(` ${detail}`);
55
+ } else {
56
+ const out = { error: msg };
57
+ if (detail !== void 0) out.detail = detail;
58
+ console.error(JSON.stringify(out));
59
+ }
60
+ process.exit(1);
61
+ }
62
+
63
+ // src/config.ts
64
+ import { homedir } from "os";
65
+ import { join } from "path";
66
+ var XAPI_ACTION_HOST = process.env.XAPI_ACTION_HOST || "action.xapi.to";
67
+ var XAPI_API_HOST = process.env.XAPI_API_HOST || "api.xapi.to";
68
+ function scheme(host) {
69
+ return host.startsWith("localhost") || host.startsWith("127.") ? "http" : "https";
70
+ }
71
+ var CONFIG_DIR = join(homedir(), ".xapi");
72
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
73
+ function loadFileConfig() {
74
+ if (!existsSync(CONFIG_FILE)) return {};
75
+ try {
76
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
77
+ } catch {
78
+ return {};
79
+ }
80
+ }
81
+ function getConfig() {
82
+ const file = loadFileConfig();
83
+ return {
84
+ actionHost: XAPI_ACTION_HOST,
85
+ apiKey: process.env.XAPI_KEY || process.env.XAPI_API_KEY || file.apiKey
86
+ };
87
+ }
88
+ function requireApiKey(cfg) {
89
+ if (!cfg.apiKey) {
90
+ err("API key not configured", 'Run "npx xapi-to register" to create an account, or "npx xapi-to config set apiKey=<key>" to set an existing key.');
91
+ }
92
+ }
93
+ function saveConfig(updates) {
94
+ const current = loadFileConfig();
95
+ const merged = { ...current, ...updates };
96
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
97
+ writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), { mode: 384 });
98
+ }
99
+ function showConfig() {
100
+ const cfg = getConfig();
101
+ const file = loadFileConfig();
102
+ console.log(JSON.stringify({
103
+ actionHost: cfg.actionHost,
104
+ apiKey: cfg.apiKey ? `${cfg.apiKey.slice(0, 8)}...` : void 0,
105
+ source: {
106
+ apiKey: process.env.XAPI_KEY || process.env.XAPI_API_KEY ? "env" : file.apiKey ? "file" : "none"
107
+ },
108
+ configFile: CONFIG_FILE
109
+ }, null, 2));
110
+ }
111
+
112
+ // src/client.ts
113
+ var DEFAULT_TIMEOUT_MS = 3e4;
114
+ var EXECUTE_TIMEOUT_MS = 6e4;
115
+ async function request(url, options, timeoutMs = DEFAULT_TIMEOUT_MS) {
116
+ const controller = new AbortController();
117
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
118
+ try {
119
+ const res = await fetch(url, { ...options, signal: controller.signal });
120
+ if (!res.ok) {
121
+ const text = await res.text();
122
+ throw new Error(`HTTP ${res.status}: ${text.slice(0, 300)}`);
123
+ }
124
+ const body = await res.json();
125
+ if (body && typeof body === "object" && "success" in body && body.success === false) {
126
+ const data = body.data;
127
+ if (data?.statusCode === 401 || data?.error === "Unauthorized") {
128
+ throw new Error(
129
+ "Authentication failed: " + (data.message || "Invalid or missing API key") + '. Run "npx xapi-to config set apiKey=<key>" to update your key.'
130
+ );
131
+ }
132
+ if (data?.error === "OAuth Required" || data?.statusCode === 403 && data?.message?.includes("OAuth")) {
133
+ throw new Error(
134
+ (data.message || "OAuth authorization required") + '. Run "xapi-to oauth bind" to connect your account.'
135
+ );
136
+ }
137
+ }
138
+ return body;
139
+ } finally {
140
+ clearTimeout(timer);
141
+ }
142
+ }
143
+ function headers(apiKey) {
144
+ const h = { "Content-Type": "application/json" };
145
+ if (apiKey) h["XAPI-Key"] = apiKey;
146
+ return h;
147
+ }
148
+ function baseUrl(opts) {
149
+ return `${scheme(opts.actionHost)}://${opts.actionHost}`;
150
+ }
151
+ async function actionList(opts, params = {}) {
152
+ const url = new URL(`${baseUrl(opts)}/v1/actions`);
153
+ if (params.page) url.searchParams.set("page", String(params.page));
154
+ if (params.page_size) url.searchParams.set("page_size", String(params.page_size));
155
+ if (params.category) url.searchParams.set("category", params.category);
156
+ if (params.source) url.searchParams.set("source", params.source);
157
+ if (params.service_id) url.searchParams.set("service_id", params.service_id);
158
+ return request(
159
+ url.toString(),
160
+ { method: "GET", headers: headers(opts.apiKey) }
161
+ );
162
+ }
163
+ async function actionSearch(query, opts, params = {}) {
164
+ const url = new URL(`${baseUrl(opts)}/v1/actions/search`);
165
+ url.searchParams.set("q", query);
166
+ if (params.category) url.searchParams.set("category", params.category);
167
+ if (params.source) url.searchParams.set("source", params.source);
168
+ if (params.page) url.searchParams.set("page", String(params.page));
169
+ if (params.page_size) url.searchParams.set("page_size", String(params.page_size));
170
+ return request(
171
+ url.toString(),
172
+ { method: "GET", headers: headers(opts.apiKey) }
173
+ );
174
+ }
175
+ async function actionCategories(opts, params = {}) {
176
+ const url = new URL(`${baseUrl(opts)}/v1/actions/categories`);
177
+ if (params.source) url.searchParams.set("source", params.source);
178
+ return request(
179
+ url.toString(),
180
+ { method: "GET", headers: headers(opts.apiKey) }
181
+ );
182
+ }
183
+ async function actionGet(id, opts) {
184
+ return request(
185
+ `${baseUrl(opts)}/v1/actions/${encodeURIComponent(id)}`,
186
+ { method: "GET", headers: headers(opts.apiKey) }
187
+ );
188
+ }
189
+ async function actionCall(actionId, input, opts, httpMethod) {
190
+ return request(
191
+ `${baseUrl(opts)}/v1/actions/execute`,
192
+ {
193
+ method: "POST",
194
+ headers: headers(opts.apiKey),
195
+ body: JSON.stringify({ action_id: actionId, ...httpMethod ? { method: httpMethod } : {}, input })
196
+ },
197
+ EXECUTE_TIMEOUT_MS
198
+ );
199
+ }
200
+ async function actionServices(opts, params = {}) {
201
+ const url = new URL(`${baseUrl(opts)}/v1/actions/services`);
202
+ if (params.page) url.searchParams.set("page", String(params.page));
203
+ if (params.page_size) url.searchParams.set("page_size", String(params.page_size));
204
+ if (params.category) url.searchParams.set("category", params.category);
205
+ return request(
206
+ url.toString(),
207
+ { method: "GET", headers: headers(opts.apiKey) }
208
+ );
209
+ }
210
+ async function healthCheck(opts) {
211
+ return request(
212
+ `${baseUrl(opts)}/health`,
213
+ { method: "GET", headers: headers(opts.apiKey) },
214
+ 5e3
215
+ );
216
+ }
217
+ async function loginWithApiKey(apiKey, apiHost) {
218
+ return request(
219
+ `${scheme(apiHost)}://${apiHost}/api/auth/login/apikey`,
220
+ {
221
+ method: "POST",
222
+ headers: { "Content-Type": "application/json" },
223
+ body: JSON.stringify({ apiKey })
224
+ }
225
+ );
226
+ }
227
+ function jwtHeaders(jwtToken) {
228
+ return { "Content-Type": "application/json", Authorization: `Bearer ${jwtToken}` };
229
+ }
230
+ async function listKeys(jwtToken, apiHost) {
231
+ return request(
232
+ `${scheme(apiHost)}://${apiHost}/api/keys`,
233
+ { method: "GET", headers: jwtHeaders(jwtToken) }
234
+ );
235
+ }
236
+ async function enableOAuthForKey(keyId, plaintextKey, jwtToken, apiHost) {
237
+ return request(
238
+ `${scheme(apiHost)}://${apiHost}/api/keys/${keyId}/enable-oauth`,
239
+ {
240
+ method: "POST",
241
+ headers: jwtHeaders(jwtToken),
242
+ body: JSON.stringify({ plaintextKey })
243
+ }
244
+ );
245
+ }
246
+ async function listOAuthProviders(apiHost) {
247
+ return request(
248
+ `${scheme(apiHost)}://${apiHost}/api/oauth/providers`,
249
+ { method: "GET", headers: { "Content-Type": "application/json" } }
250
+ );
251
+ }
252
+ async function initiateOAuth(apiKeyId, providerId, jwtToken, apiHost) {
253
+ return request(
254
+ `${scheme(apiHost)}://${apiHost}/api/oauth/authorize`,
255
+ {
256
+ method: "POST",
257
+ headers: jwtHeaders(jwtToken),
258
+ body: JSON.stringify({ apiKeyId, providerId })
259
+ }
260
+ );
261
+ }
262
+ async function listOAuthBindings(jwtToken, apiHost) {
263
+ return request(
264
+ `${scheme(apiHost)}://${apiHost}/api/oauth/bindings`,
265
+ { method: "GET", headers: jwtHeaders(jwtToken) }
266
+ );
267
+ }
268
+ async function deleteOAuthBinding(bindingId, jwtToken, apiHost) {
269
+ return request(
270
+ `${scheme(apiHost)}://${apiHost}/api/oauth/bindings/${bindingId}`,
271
+ { method: "DELETE", headers: jwtHeaders(jwtToken) }
272
+ );
273
+ }
274
+
275
+ // src/codegen.ts
276
+ var TARGET_MAP = {
277
+ "curl": { lang: "curl", lib: "curl" },
278
+ "python": { lang: "python", lib: "requests" },
279
+ "py": { lang: "python", lib: "requests" },
280
+ "python.requests": { lang: "python", lib: "requests" },
281
+ "python.httpx": { lang: "python", lib: "httpx" },
282
+ "py.requests": { lang: "python", lib: "requests" },
283
+ "py.httpx": { lang: "python", lib: "httpx" },
284
+ "javascript": { lang: "javascript", lib: "fetch" },
285
+ "js": { lang: "javascript", lib: "fetch" },
286
+ "javascript.fetch": { lang: "javascript", lib: "fetch" },
287
+ "javascript.axios": { lang: "javascript", lib: "axios" },
288
+ "js.fetch": { lang: "javascript", lib: "fetch" },
289
+ "js.axios": { lang: "javascript", lib: "axios" },
290
+ "typescript": { lang: "typescript", lib: "fetch" },
291
+ "ts": { lang: "typescript", lib: "fetch" },
292
+ "typescript.fetch": { lang: "typescript", lib: "fetch" },
293
+ "ts.fetch": { lang: "typescript", lib: "fetch" },
294
+ "go": { lang: "go", lib: "net/http" }
295
+ };
296
+ var SUPPORTED_TARGETS = [
297
+ "curl",
298
+ "python (py) [.requests, .httpx]",
299
+ "javascript (js) [.fetch, .axios]",
300
+ "typescript (ts) [.fetch]",
301
+ "go [net/http]"
302
+ ];
303
+ function resolveTarget(raw) {
304
+ const target = TARGET_MAP[raw.toLowerCase()];
305
+ if (!target) {
306
+ throw new Error(
307
+ `unknown --code target: "${raw}". Supported: ${SUPPORTED_TARGETS.join(", ")}`
308
+ );
309
+ }
310
+ return target;
311
+ }
312
+ function typeDefault(type) {
313
+ switch (type) {
314
+ case "string":
315
+ return "";
316
+ case "number":
317
+ case "integer":
318
+ return 0;
319
+ case "boolean":
320
+ return false;
321
+ case "object":
322
+ return {};
323
+ case "array":
324
+ return [];
325
+ default:
326
+ return "";
327
+ }
328
+ }
329
+ function buildDefaultInput(schema) {
330
+ if (!schema.properties) return {};
331
+ const result = {};
332
+ for (const [key, prop] of Object.entries(schema.properties)) {
333
+ if (prop.default !== void 0) {
334
+ const val = prop.default;
335
+ result[key] = typeof val === "object" && val !== null ? JSON.parse(JSON.stringify(val)) : val;
336
+ } else {
337
+ result[key] = typeDefault(prop.type ?? "string");
338
+ }
339
+ }
340
+ return result;
341
+ }
342
+ var SAFE_HOST_PATTERN = /^[a-zA-Z0-9._\-]+(:\d{1,5})?$/;
343
+ function validateHost(host) {
344
+ if (!SAFE_HOST_PATTERN.test(host)) {
345
+ throw new Error(`invalid actionHost: "${host}" \u2014 must be a valid hostname with optional port`);
346
+ }
347
+ }
348
+ function baseUrl2(actionHost) {
349
+ validateHost(actionHost);
350
+ return `${scheme(actionHost)}://${actionHost}/v1/actions/execute`;
351
+ }
352
+ function jsonBody(actionId, input, method) {
353
+ return JSON.stringify({ action_id: actionId, ...method ? { method } : {}, input }, null, 2);
354
+ }
355
+ function indent(text, spaces) {
356
+ const pad = " ".repeat(spaces);
357
+ const lines = text.split("\n");
358
+ return lines.map((line, i) => i === 0 ? line : pad + line).join("\n");
359
+ }
360
+ function shellEscape(s) {
361
+ return s.replace(/'/g, "'\\''");
362
+ }
363
+ function genCurl(params) {
364
+ const url = baseUrl2(params.actionHost);
365
+ const body = jsonBody(params.actionId, params.input, params.method);
366
+ return [
367
+ "# Set XAPI_KEY env var or replace with your key",
368
+ `curl -X POST '${shellEscape(url)}' \\`,
369
+ ` -H 'Content-Type: application/json' \\`,
370
+ ` -H "XAPI-Key: \${XAPI_KEY}" \\`,
371
+ ` -d '${shellEscape(body)}'`
372
+ ].join("\n");
373
+ }
374
+ function genPython(lib, params) {
375
+ const url = baseUrl2(params.actionHost);
376
+ const payload = { action_id: params.actionId, ...params.method ? { method: params.method } : {}, input: params.input };
377
+ return [
378
+ `# pip install ${lib}`,
379
+ "# Set XAPI_KEY env var or replace with your key",
380
+ "import os",
381
+ `import ${lib}`,
382
+ "",
383
+ `resp = ${lib}.post(`,
384
+ ` "${url}",`,
385
+ ` headers={`,
386
+ ` "Content-Type": "application/json",`,
387
+ ` "XAPI-Key": os.environ["XAPI_KEY"],`,
388
+ ` },`,
389
+ ` json=${indent(pythonDict(payload), 4)},`,
390
+ `)`,
391
+ "print(resp.json())"
392
+ ].join("\n");
393
+ }
394
+ function genJavaScriptFetch(params) {
395
+ const url = baseUrl2(params.actionHost);
396
+ const body = jsonBody(params.actionId, params.input, params.method);
397
+ return [
398
+ "// Set XAPI_KEY env var or replace with your key",
399
+ `const resp = await fetch("${url}", {`,
400
+ ` method: "POST",`,
401
+ ` headers: {`,
402
+ ` "Content-Type": "application/json",`,
403
+ ` "XAPI-Key": process.env.XAPI_KEY,`,
404
+ ` },`,
405
+ ` body: JSON.stringify(${indent(body, 2)}),`,
406
+ `});`,
407
+ "console.log(await resp.json());"
408
+ ].join("\n");
409
+ }
410
+ function genJavaScriptAxios(params) {
411
+ const url = baseUrl2(params.actionHost);
412
+ const body = jsonBody(params.actionId, params.input, params.method);
413
+ return [
414
+ "// npm install axios",
415
+ "// Set XAPI_KEY env var or replace with your key",
416
+ 'import axios from "axios";',
417
+ "",
418
+ `const resp = await axios.post(`,
419
+ ` "${url}",`,
420
+ ` ${indent(body, 2)},`,
421
+ ` {`,
422
+ ` headers: {`,
423
+ ` "Content-Type": "application/json",`,
424
+ ` "XAPI-Key": process.env.XAPI_KEY,`,
425
+ ` },`,
426
+ ` },`,
427
+ `);`,
428
+ "console.log(resp.data);"
429
+ ].join("\n");
430
+ }
431
+ function genTypescriptFetch(params) {
432
+ const url = baseUrl2(params.actionHost);
433
+ const body = jsonBody(params.actionId, params.input, params.method);
434
+ return [
435
+ "// Set XAPI_KEY env var or replace with your key",
436
+ `const resp: Response = await fetch("${url}", {`,
437
+ ` method: "POST",`,
438
+ ` headers: {`,
439
+ ` "Content-Type": "application/json",`,
440
+ ` "XAPI-Key": process.env.XAPI_KEY!,`,
441
+ ` },`,
442
+ ` body: JSON.stringify(${indent(body, 2)}),`,
443
+ `});`,
444
+ "const data: unknown = await resp.json();",
445
+ "console.log(data);"
446
+ ].join("\n");
447
+ }
448
+ function genGo(params) {
449
+ const url = baseUrl2(params.actionHost);
450
+ const body = jsonBody(params.actionId, params.input, params.method);
451
+ const escaped = body.replace(/`/g, '` + "`" + `');
452
+ return [
453
+ "// Set XAPI_KEY env var or replace with your key",
454
+ "package main",
455
+ "",
456
+ "import (",
457
+ ' "fmt"',
458
+ ' "io"',
459
+ ' "net/http"',
460
+ ' "os"',
461
+ ' "strings"',
462
+ ")",
463
+ "",
464
+ "func main() {",
465
+ ` body := \`${escaped}\``,
466
+ ` req, err := http.NewRequest("POST", "${url}", strings.NewReader(body))`,
467
+ " if err != nil {",
468
+ " panic(err)",
469
+ " }",
470
+ ' req.Header.Set("Content-Type", "application/json")',
471
+ ' req.Header.Set("XAPI-Key", os.Getenv("XAPI_KEY"))',
472
+ "",
473
+ " resp, err := http.DefaultClient.Do(req)",
474
+ " if err != nil {",
475
+ " panic(err)",
476
+ " }",
477
+ " defer resp.Body.Close()",
478
+ "",
479
+ " result, err := io.ReadAll(resp.Body)",
480
+ " if err != nil {",
481
+ " panic(err)",
482
+ " }",
483
+ " fmt.Println(string(result))",
484
+ "}"
485
+ ].join("\n");
486
+ }
487
+ function pythonDict(obj, depth = 0) {
488
+ const pad = " ".repeat(depth);
489
+ const inner = " ".repeat(depth + 1);
490
+ if (obj === null || obj === void 0) return "None";
491
+ if (typeof obj === "boolean") return obj ? "True" : "False";
492
+ if (typeof obj === "number") return String(obj);
493
+ if (typeof obj === "string") return JSON.stringify(obj);
494
+ if (Array.isArray(obj)) {
495
+ if (obj.length === 0) return "[]";
496
+ const items = obj.map((v) => `${inner}${pythonDict(v, depth + 1)}`);
497
+ return `[
498
+ ${items.join(",\n")}
499
+ ${pad}]`;
500
+ }
501
+ if (typeof obj === "object") {
502
+ const entries = Object.entries(obj);
503
+ if (entries.length === 0) return "{}";
504
+ const items = entries.map(
505
+ ([k, v]) => `${inner}${JSON.stringify(k)}: ${pythonDict(v, depth + 1)}`
506
+ );
507
+ return `{
508
+ ${items.join(",\n")}
509
+ ${pad}}`;
510
+ }
511
+ return String(obj);
512
+ }
513
+ var GENERATORS = {
514
+ curl: { curl: genCurl },
515
+ python: { requests: (p) => genPython("requests", p), httpx: (p) => genPython("httpx", p) },
516
+ javascript: { fetch: genJavaScriptFetch, axios: genJavaScriptAxios },
517
+ typescript: { fetch: genTypescriptFetch },
518
+ go: { "net/http": genGo }
519
+ };
520
+ function generateCode(target, params) {
521
+ const { lang, lib } = resolveTarget(target);
522
+ const generator = GENERATORS[lang]?.[lib];
523
+ if (!generator) {
524
+ throw new Error(`no generator for ${lang}.${lib}`);
525
+ }
526
+ return { lang, lib, code: generator(params) };
527
+ }
528
+
529
+ // src/commands/action.ts
530
+ var VALID_SOURCES = ["capability", "api"];
531
+ var LIST_HELP = `xapi-to list - List all actions
532
+
533
+ USAGE
534
+ xapi-to list [flags]
535
+
536
+ FLAGS
537
+ --source capability|api Filter by source type
538
+ --category <name> Filter by category
539
+ --service-id <id> Filter by service
540
+ --page N Page number (default: 1)
541
+ --page-size N Results per page
542
+ --format json|pretty|table Output format
543
+
544
+ EXAMPLES
545
+ xapi-to list
546
+ xapi-to list --source api --format table
547
+ xapi-to list --category social --page 2
548
+ `;
549
+ var SEARCH_HELP = `xapi-to search - Search actions by keyword
550
+
551
+ USAGE
552
+ xapi-to search <query> [flags]
553
+
554
+ FLAGS
555
+ --source capability|api Filter by source type
556
+ --category <name> Filter by category
557
+ --page N Page number (default: 1)
558
+ --page-size N Results per page
559
+ --format json|pretty|table Output format
560
+
561
+ EXAMPLES
562
+ xapi-to search twitter
563
+ xapi-to search "tweet detail" --source api
564
+ xapi-to search weather --category utility --format table
565
+ `;
566
+ var GET_HELP = `xapi-to get - Get action schema
567
+
568
+ USAGE
569
+ xapi-to get <id> [flags]
570
+
571
+ FLAGS
572
+ --method GET|POST|... Filter by HTTP method
573
+ --code <target> Generate code snippet instead of showing schema
574
+ --format json|pretty|table Output format
575
+
576
+ CODE TARGETS
577
+ curl cURL command
578
+ py, python Python (requests)
579
+ python.requests Python with requests
580
+ py.requests alias for python.requests
581
+ python.httpx Python with httpx
582
+ py.httpx alias for python.httpx
583
+ js, javascript JavaScript (fetch)
584
+ javascript.fetch JavaScript with fetch
585
+ js.fetch alias for javascript.fetch
586
+ javascript.axios JavaScript with axios
587
+ js.axios alias for javascript.axios
588
+ ts, typescript TypeScript (fetch)
589
+ typescript.fetch TypeScript with fetch
590
+ ts.fetch alias for typescript.fetch
591
+ go Go (net/http)
592
+
593
+ EXAMPLES
594
+ xapi-to get twitter.tweet_detail
595
+ xapi-to get twitter.tweet_detail --method POST
596
+ xapi-to get twitter.tweet_detail --code curl
597
+ xapi-to get twitter.tweet_detail --code python.httpx --format pretty
598
+ `;
599
+ var CALL_HELP = `xapi-to call - Execute an action
600
+
601
+ USAGE
602
+ xapi-to call <id> --input '{"key":"val"}' [flags]
603
+
604
+ FLAGS
605
+ --input <json> Input payload as JSON (required for execution)
606
+ --method GET|POST|... Override HTTP method
607
+ --code <target> Generate code snippet instead of executing
608
+ --format json|pretty|table Output format
609
+
610
+ CODE TARGETS
611
+ curl cURL command
612
+ py, python Python (requests)
613
+ python.requests Python with requests
614
+ py.requests alias for python.requests
615
+ python.httpx Python with httpx
616
+ py.httpx alias for python.httpx
617
+ js, javascript JavaScript (fetch)
618
+ javascript.fetch JavaScript with fetch
619
+ js.fetch alias for javascript.fetch
620
+ javascript.axios JavaScript with axios
621
+ js.axios alias for javascript.axios
622
+ ts, typescript TypeScript (fetch)
623
+ typescript.fetch TypeScript with fetch
624
+ ts.fetch alias for typescript.fetch
625
+ go Go (net/http)
626
+
627
+ EXAMPLES
628
+ xapi-to call twitter.tweet_detail --input '{"tweet_id":"1234567890"}'
629
+ xapi-to call twitter.tweet_detail --input '{"tweet_id":"123"}' --code py
630
+ xapi-to call twitter.tweet_detail --input '{"tweet_id":"123"}' --code curl --format pretty
631
+ `;
632
+ function showHelpIfRequested(flags, helpText) {
633
+ if (flags.help) {
634
+ console.log(helpText);
635
+ process.exit(0);
636
+ }
637
+ }
638
+ function validateCodeFlag(flags) {
639
+ if (flags.code === "true") {
640
+ err("--code requires a target language, e.g. --code curl, --code py, --code js");
641
+ }
642
+ resolveTarget(flags.code);
643
+ }
644
+ function outputCode(result, flags) {
645
+ const fmt = flags.format || getFormat();
646
+ if (fmt === "json") {
647
+ output({ language: result.lang, library: result.lib, code: result.code }, "json");
648
+ } else {
649
+ console.log(result.code);
650
+ }
651
+ }
652
+ function getSource(flags) {
653
+ if (!flags.source) return void 0;
654
+ if (!VALID_SOURCES.includes(flags.source)) {
655
+ err(`invalid --source value: "${flags.source}". Must be "capability" or "api".`);
656
+ }
657
+ return flags.source;
658
+ }
659
+ async function actionList2(args, flags) {
660
+ showHelpIfRequested(flags, LIST_HELP);
661
+ const cfg = getConfig();
662
+ try {
663
+ const res = await actionList(cfg, {
664
+ source: getSource(flags),
665
+ page: flags.page ? parseInt(flags.page) : void 0,
666
+ page_size: flags["page-size"] ? parseInt(flags["page-size"]) : void 0,
667
+ category: flags.category,
668
+ service_id: flags["service-id"]
669
+ });
670
+ const actions = res.actions || [];
671
+ if (flags.format === "table") {
672
+ output(actions.map((a) => ({
673
+ id: a.id,
674
+ method: a.method ?? "",
675
+ displayName: a.displayName ?? "",
676
+ source: a.source ?? "",
677
+ category: a.meta?.category ?? "",
678
+ status: a.status ?? "",
679
+ cost: a.meta?.cost ?? ""
680
+ })), "table");
681
+ } else {
682
+ output(res, flags.format);
683
+ }
684
+ } catch (e) {
685
+ err("list failed", e.message);
686
+ }
687
+ }
688
+ async function actionSearch2(args, flags) {
689
+ showHelpIfRequested(flags, SEARCH_HELP);
690
+ const query = args[0];
691
+ if (!query) err("usage: xapi-to search <query>");
692
+ const cfg = getConfig();
693
+ try {
694
+ const res = await actionSearch(query, cfg, {
695
+ source: getSource(flags),
696
+ category: flags.category,
697
+ page: flags.page ? parseInt(flags.page) : void 0,
698
+ page_size: flags["page-size"] ? parseInt(flags["page-size"]) : void 0
699
+ });
700
+ const results = res.results || [];
701
+ if (flags.format === "table") {
702
+ output(results.map((a) => ({
703
+ id: a.id,
704
+ method: a.method ?? "",
705
+ displayName: a.displayName ?? "",
706
+ source: a.source ?? "",
707
+ category: a.meta?.category ?? "",
708
+ status: a.status ?? "",
709
+ cost: a.meta?.cost ?? ""
710
+ })), "table");
711
+ } else {
712
+ output(res, flags.format);
713
+ }
714
+ } catch (e) {
715
+ err("search failed", e.message);
716
+ }
717
+ }
718
+ async function actionCategories2(args, flags) {
719
+ const cfg = getConfig();
720
+ try {
721
+ const res = await actionCategories(cfg, { source: getSource(flags) });
722
+ if (flags.format === "table") {
723
+ output(res.categories.map((c) => ({ category: c })), "table");
724
+ } else {
725
+ output(res, flags.format);
726
+ }
727
+ } catch (e) {
728
+ err("categories failed", e.message);
729
+ }
730
+ }
731
+ async function actionServices2(args, flags) {
732
+ const cfg = getConfig();
733
+ try {
734
+ const res = await actionServices(cfg, {
735
+ page: flags.page ? parseInt(flags.page) : void 0,
736
+ page_size: flags["page-size"] ? parseInt(flags["page-size"]) : void 0,
737
+ category: flags.category
738
+ });
739
+ const services = res.services || [];
740
+ if (flags.format === "table") {
741
+ output(services.map((s) => ({
742
+ id: s.id,
743
+ name: s.name ?? "",
744
+ category: s.category ?? "",
745
+ source: s.source ?? "",
746
+ endpoints: s.endpointCount ?? "",
747
+ status: s.status ?? ""
748
+ })), "table");
749
+ } else {
750
+ output(res, flags.format);
751
+ }
752
+ } catch (e) {
753
+ err("services failed", e.message);
754
+ }
755
+ }
756
+ async function actionGet2(args, flags) {
757
+ showHelpIfRequested(flags, GET_HELP);
758
+ const id = args[0];
759
+ if (!id) err("usage: xapi-to get <id> [--method GET|POST|DELETE|...]");
760
+ if (flags.code) validateCodeFlag(flags);
761
+ const cfg = getConfig();
762
+ try {
763
+ const res = await actionGet(id, cfg);
764
+ const actions = Array.isArray(res) ? res : [res];
765
+ const methodFilter = flags.method?.toUpperCase();
766
+ const filtered = methodFilter ? actions.filter((a) => a.method?.toUpperCase() === methodFilter) : actions;
767
+ if (filtered.length === 0) {
768
+ err(`no endpoint found for method "${methodFilter}" in action "${id}"`);
769
+ }
770
+ if (flags.code) {
771
+ if (filtered.length > 1) {
772
+ process.stderr.write(
773
+ `Warning: action "${id}" has ${filtered.length} endpoints; using method "${filtered[0].method}". Use --method to select a specific one.
774
+ `
775
+ );
776
+ }
777
+ const action = filtered[0];
778
+ const { method: _schemaMethod, ...cleanCodeInput } = buildDefaultInput(action.input ?? {});
779
+ const result = generateCode(flags.code, { actionId: id, input: cleanCodeInput, actionHost: cfg.actionHost, method: action.method });
780
+ outputCode(result, flags);
781
+ return;
782
+ }
783
+ output(filtered.length === 1 ? filtered[0] : filtered, flags.format);
784
+ } catch (e) {
785
+ err("get failed", e.message);
786
+ }
787
+ }
788
+ async function actionCall2(args, flags) {
789
+ showHelpIfRequested(flags, CALL_HELP);
790
+ const id = args[0];
791
+ if (!id) err(`usage: xapi-to call <id> --input '{"key":"val"}'`);
792
+ if (flags.code) validateCodeFlag(flags);
793
+ const cfg = getConfig();
794
+ let input = {};
795
+ if (flags.input) {
796
+ try {
797
+ input = JSON.parse(flags.input);
798
+ } catch {
799
+ err("--input must be valid JSON");
800
+ }
801
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
802
+ err("--input must be a JSON object");
803
+ }
804
+ }
805
+ const { method: inputMethod, ...cleanInput } = input;
806
+ const method = flags.method?.toUpperCase() || (typeof inputMethod === "string" ? inputMethod.toUpperCase() : void 0);
807
+ if (flags.code) {
808
+ const result = generateCode(flags.code, { actionId: id, input: cleanInput, actionHost: cfg.actionHost, method });
809
+ outputCode(result, flags);
810
+ return;
811
+ }
812
+ requireApiKey(cfg);
813
+ try {
814
+ const res = await actionCall(id, cleanInput, cfg, method);
815
+ output(res, flags.format);
816
+ } catch (e) {
817
+ err("call failed", e.message);
818
+ }
819
+ }
820
+
821
+ // src/commands/config.ts
822
+ var config_exports = {};
823
+ __export(config_exports, {
824
+ CONFIG_HELP: () => CONFIG_HELP,
825
+ configHealth: () => configHealth,
826
+ configSet: () => configSet,
827
+ configShow: () => configShow
828
+ });
829
+ var CONFIG_HELP = `xapi-to config - Manage CLI configuration
830
+
831
+ USAGE
832
+ xapi-to config <command> [flags]
833
+
834
+ COMMANDS
835
+ show Show current config (host, apiKey path, etc.)
836
+ set apiKey=<key> Save API key to ~/.xapi/config.json
837
+ health Check backend connectivity (alias: xapi-to health)
838
+
839
+ FLAGS
840
+ --format json|pretty|table Output format
841
+
842
+ EXAMPLES
843
+ xapi-to config show
844
+ xapi-to config set apiKey=xapi_abc123
845
+ xapi-to config health
846
+ `;
847
+ async function configShow(args, flags) {
848
+ showConfig();
849
+ }
850
+ async function configSet(args, flags) {
851
+ if (args.length === 0) err("usage: xapi-to config set apiKey=<key>");
852
+ const updates = {};
853
+ for (const arg of args) {
854
+ const eq = arg.indexOf("=");
855
+ if (eq < 1) err(`invalid key=value: ${arg}`);
856
+ const key = arg.slice(0, eq);
857
+ if (key === "host") err("host is built-in and cannot be configured");
858
+ if (key !== "apiKey") err(`unknown config key: ${key} (only apiKey is configurable)`);
859
+ updates.apiKey = arg.slice(eq + 1);
860
+ }
861
+ saveConfig(updates);
862
+ console.log(JSON.stringify({ ok: true, updated: Object.keys(updates) }));
863
+ }
864
+ async function configHealth(args, flags) {
865
+ const cfg = getConfig();
866
+ const start = Date.now();
867
+ try {
868
+ await healthCheck(cfg);
869
+ output({ status: "ok", host: cfg.actionHost, latency_ms: Date.now() - start }, flags.format);
870
+ } catch (e) {
871
+ output({ status: "error", host: cfg.actionHost, error: e.message }, flags.format);
872
+ process.exit(1);
873
+ }
874
+ }
875
+
876
+ // src/commands/register.ts
877
+ async function registerAccount(referralCode) {
878
+ const controller = new AbortController();
879
+ const timer = setTimeout(() => controller.abort(), 15e3);
880
+ try {
881
+ const res = await fetch(`${scheme(XAPI_API_HOST)}://${XAPI_API_HOST}/api/auth/register`, {
882
+ method: "POST",
883
+ headers: { "Content-Type": "application/json" },
884
+ body: JSON.stringify(referralCode ? { referralCode } : {}),
885
+ signal: controller.signal
886
+ });
887
+ if (!res.ok) {
888
+ const text = await res.text();
889
+ throw new Error(`HTTP ${res.status}: ${text.slice(0, 300)}`);
890
+ }
891
+ return res.json();
892
+ } finally {
893
+ clearTimeout(timer);
894
+ }
895
+ }
896
+ async function register(args, flags) {
897
+ try {
898
+ const rawReferral = flags["referral-code"] ?? flags["referralCode"] ?? args[0];
899
+ const referralCode = typeof rawReferral === "string" && rawReferral !== "true" && rawReferral.length > 0 ? rawReferral : void 0;
900
+ const res = await registerAccount(referralCode);
901
+ saveConfig({ apiKey: res.apiKey });
902
+ output({
903
+ apiKey: res.apiKey,
904
+ user: res.user,
905
+ referralCode: res.referralCode,
906
+ claim: {
907
+ code: res.claimCode,
908
+ sessionId: res.claimSessionId,
909
+ url: res.claimUrl
910
+ },
911
+ tweetTemplate: res.tweetTemplate,
912
+ ...referralCode ? { referredBy: referralCode } : {},
913
+ note: "apiKey saved to ~/.xapi/config.json"
914
+ }, flags.format);
915
+ } catch (e) {
916
+ err("register failed", e.message);
917
+ }
918
+ }
919
+
920
+ // src/commands/topup.ts
921
+ var TOPUP_BASE_URL = "https://www.xapi.to/topup/payment";
922
+ async function topup(args, flags) {
923
+ const cfg = getConfig();
924
+ const url = new URL(TOPUP_BASE_URL);
925
+ if (cfg.apiKey) url.searchParams.set("apikey", cfg.apiKey);
926
+ if (flags.method) url.searchParams.set("method", flags.method);
927
+ const amountStr = flags.amount || args[0];
928
+ if (amountStr) {
929
+ const amountUsd = parseFloat(amountStr);
930
+ if (!isNaN(amountUsd) && amountUsd > 0) {
931
+ url.searchParams.set("amount", String(amountUsd));
932
+ }
933
+ }
934
+ output({ url: url.toString() }, flags.format);
935
+ }
936
+
937
+ // src/commands/balance.ts
938
+ async function balance(args, flags) {
939
+ const cfg = getConfig();
940
+ requireApiKey(cfg);
941
+ let token;
942
+ try {
943
+ const res = await loginWithApiKey(cfg.apiKey, XAPI_API_HOST);
944
+ token = res.accessToken;
945
+ } catch (e) {
946
+ err("login failed", e.message);
947
+ }
948
+ try {
949
+ const me = await request(
950
+ `${scheme(XAPI_API_HOST)}://${XAPI_API_HOST}/api/auth/me`,
951
+ { method: "GET", headers: { Authorization: `Bearer ${token}` } }
952
+ );
953
+ output({
954
+ balance: me.balance,
955
+ accountType: me.accountType,
956
+ tier: me.tier
957
+ }, flags.format);
958
+ } catch (e) {
959
+ err("balance fetch failed", e.message);
960
+ }
961
+ }
962
+
963
+ // src/commands/oauth.ts
964
+ var oauth_exports = {};
965
+ __export(oauth_exports, {
966
+ OAUTH_HELP: () => OAUTH_HELP,
967
+ oauthBind: () => oauthBind,
968
+ oauthProviders: () => oauthProviders,
969
+ oauthStatus: () => oauthStatus,
970
+ oauthUnbind: () => oauthUnbind
971
+ });
972
+ import { spawnSync } from "child_process";
973
+ function openBrowser(url) {
974
+ const cmd = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
975
+ try {
976
+ spawnSync(cmd, [url], { stdio: "ignore" });
977
+ } catch {
978
+ }
979
+ }
980
+ async function pollForBinding(apiKeyId, providerId, jwtToken, timeoutMs = 5 * 60 * 1e3, intervalMs = 3e3) {
981
+ const deadline = Date.now() + timeoutMs;
982
+ const isTTY = process.stdout.isTTY;
983
+ while (Date.now() < deadline) {
984
+ await new Promise((r) => setTimeout(r, intervalMs));
985
+ try {
986
+ const bindings = await listOAuthBindings(jwtToken, XAPI_API_HOST);
987
+ const match = Array.isArray(bindings) ? bindings.find((b) => b.apiKeyId === apiKeyId && b.providerId === providerId) : null;
988
+ if (match) return match;
989
+ } catch {
990
+ }
991
+ if (isTTY) {
992
+ const remaining = Math.ceil((deadline - Date.now()) / 1e3);
993
+ process.stdout.write(`\r Waiting for authorization... (${remaining}s remaining) `);
994
+ }
995
+ }
996
+ if (process.stdout.isTTY) process.stdout.write("\n");
997
+ return null;
998
+ }
999
+ async function loginAndGetJwt(apiKey) {
1000
+ const result = await loginWithApiKey(apiKey, XAPI_API_HOST);
1001
+ if (!result?.accessToken) {
1002
+ throw new Error("Login failed: no access token returned");
1003
+ }
1004
+ return result.accessToken;
1005
+ }
1006
+ async function findCurrentKeyRecord(plaintextKey, jwtToken) {
1007
+ const keys = await listKeys(jwtToken, XAPI_API_HOST);
1008
+ if (!Array.isArray(keys) || keys.length === 0) {
1009
+ throw new Error("No API keys found for this account");
1010
+ }
1011
+ if (keys.length === 1) return keys[0];
1012
+ const prefix = plaintextKey.substring(0, 7);
1013
+ const match = keys.find((k) => k.keyPreview.startsWith(prefix));
1014
+ if (!match) {
1015
+ return keys[0];
1016
+ }
1017
+ return match;
1018
+ }
1019
+ var OAUTH_HELP = `xapi-to oauth - Manage OAuth bindings
1020
+
1021
+ USAGE
1022
+ xapi-to oauth <command> [flags]
1023
+
1024
+ COMMANDS
1025
+ bind [--provider <name>] Bind an OAuth account to your API key
1026
+ status List current OAuth bindings
1027
+ unbind <binding-id> Remove an OAuth binding
1028
+ providers List available OAuth providers
1029
+
1030
+ FLAGS
1031
+ --provider <name> OAuth provider (default: twitter)
1032
+ --format json|pretty|table Output format
1033
+
1034
+ EXAMPLES
1035
+ xapi-to oauth bind
1036
+ xapi-to oauth bind --provider twitter
1037
+ xapi-to oauth status
1038
+ xapi-to oauth status --format pretty
1039
+ xapi-to oauth unbind abc123
1040
+ xapi-to oauth providers
1041
+ `;
1042
+ async function oauthBind(args, flags) {
1043
+ const cfg = getConfig();
1044
+ requireApiKey(cfg);
1045
+ const apiKey = cfg.apiKey;
1046
+ const providerName = (flags.provider || "twitter").toLowerCase();
1047
+ try {
1048
+ const jwtToken = await loginAndGetJwt(apiKey);
1049
+ const keyRecord = await findCurrentKeyRecord(apiKey, jwtToken);
1050
+ if (!keyRecord.oauthEnabled) {
1051
+ await enableOAuthForKey(keyRecord.id, apiKey, jwtToken, XAPI_API_HOST);
1052
+ }
1053
+ const providers = await listOAuthProviders(XAPI_API_HOST);
1054
+ if (!Array.isArray(providers) || providers.length === 0) {
1055
+ throw new Error("No OAuth providers available");
1056
+ }
1057
+ const provider = providers.find(
1058
+ (p) => p.type.toLowerCase() === providerName || p.name.toLowerCase().includes(providerName)
1059
+ );
1060
+ if (!provider) {
1061
+ const available = providers.map((p) => p.type).join(", ");
1062
+ throw new Error(
1063
+ `Provider "${providerName}" not found. Available: ${available}`
1064
+ );
1065
+ }
1066
+ const result = await initiateOAuth(keyRecord.id, provider.id, jwtToken, XAPI_API_HOST);
1067
+ const { authorizationUrl } = result;
1068
+ const isTTY = process.stdout.isTTY;
1069
+ if (isTTY) {
1070
+ console.error(`
1071
+ Provider : ${provider.name}`);
1072
+ console.error(` API Key : ${keyRecord.keyPreview}`);
1073
+ console.error(`
1074
+ Authorization URL:
1075
+ ${authorizationUrl}
1076
+ `);
1077
+ console.error(" Opening browser...");
1078
+ openBrowser(authorizationUrl);
1079
+ console.error(" Waiting for you to complete authorization in the browser...\n");
1080
+ const binding = await pollForBinding(keyRecord.id, provider.id, jwtToken);
1081
+ if (process.stdout.isTTY) process.stdout.write("\n");
1082
+ if (binding) {
1083
+ const account = binding.providerAccountName || "unknown";
1084
+ console.error(`
1085
+ Authorization complete! Bound to @${account}
1086
+ `);
1087
+ output({ status: "success", provider: provider.name, account }, flags.format);
1088
+ } else {
1089
+ err("oauth bind timed out", 'Authorization was not completed within 5 minutes. Run "xapi-to oauth bind" again.');
1090
+ }
1091
+ } else {
1092
+ output({
1093
+ status: "pending",
1094
+ provider: provider.name,
1095
+ apiKey: keyRecord.keyPreview,
1096
+ authorizationUrl
1097
+ }, flags.format);
1098
+ }
1099
+ } catch (e) {
1100
+ err("oauth bind failed", e.message);
1101
+ }
1102
+ }
1103
+ async function oauthStatus(args, flags) {
1104
+ const cfg = getConfig();
1105
+ requireApiKey(cfg);
1106
+ const apiKey = cfg.apiKey;
1107
+ try {
1108
+ const jwtToken = await loginAndGetJwt(apiKey);
1109
+ const bindings = await listOAuthBindings(jwtToken, XAPI_API_HOST);
1110
+ if (!Array.isArray(bindings) || bindings.length === 0) {
1111
+ output({
1112
+ status: "no_bindings",
1113
+ message: 'No OAuth bindings found. Run "xapi-to oauth bind" to connect an account.'
1114
+ }, flags.format);
1115
+ return;
1116
+ }
1117
+ output({
1118
+ status: "ok",
1119
+ count: bindings.length,
1120
+ bindings: bindings.map((b) => ({
1121
+ id: b.id,
1122
+ provider: b.provider.name,
1123
+ providerType: b.provider.type,
1124
+ account: b.providerAccountName || b.providerAccountId,
1125
+ apiKeyId: b.apiKeyId,
1126
+ scopes: b.scopes,
1127
+ boundAt: b.createdAt
1128
+ }))
1129
+ }, flags.format);
1130
+ } catch (e) {
1131
+ err("oauth status failed", e.message);
1132
+ }
1133
+ }
1134
+ async function oauthUnbind(args, flags) {
1135
+ const cfg = getConfig();
1136
+ requireApiKey(cfg);
1137
+ const apiKey = cfg.apiKey;
1138
+ const bindingId = args[0];
1139
+ if (!bindingId) {
1140
+ err("usage: xapi-to oauth unbind <binding-id>", 'Get the binding ID from "xapi-to oauth status"');
1141
+ }
1142
+ try {
1143
+ const jwtToken = await loginAndGetJwt(apiKey);
1144
+ const result = await deleteOAuthBinding(bindingId, jwtToken, XAPI_API_HOST);
1145
+ output({ success: result.success, message: "OAuth binding removed" }, flags.format);
1146
+ } catch (e) {
1147
+ err("oauth unbind failed", e.message);
1148
+ }
1149
+ }
1150
+ async function oauthProviders(args, flags) {
1151
+ try {
1152
+ const providers = await listOAuthProviders(XAPI_API_HOST);
1153
+ output(providers, flags.format);
1154
+ } catch (e) {
1155
+ err("oauth providers failed", e.message);
1156
+ }
1157
+ }
1158
+
1159
+ // src/index.ts
1160
+ var { CONFIG_HELP: CONFIG_HELP2 } = config_exports;
1161
+ var { OAUTH_HELP: OAUTH_HELP2 } = oauth_exports;
1162
+ function parseArgs(argv) {
1163
+ const positional = [];
1164
+ const flags = {};
1165
+ let i = 0;
1166
+ while (i < argv.length) {
1167
+ const arg = argv[i];
1168
+ if (arg.startsWith("--")) {
1169
+ const key = arg.slice(2);
1170
+ const next = argv[i + 1];
1171
+ if (next && !next.startsWith("--")) {
1172
+ flags[key] = next;
1173
+ i += 2;
1174
+ } else {
1175
+ flags[key] = "true";
1176
+ i++;
1177
+ }
1178
+ } else {
1179
+ positional.push(arg);
1180
+ i++;
1181
+ }
1182
+ }
1183
+ return { positional, flags };
1184
+ }
1185
+ var HELP = `xapi-to - agent-friendly CLI for xapi
1186
+
1187
+ USAGE
1188
+ xapi-to <command> [args] [flags]
1189
+
1190
+ COMMANDS
1191
+ list List all actions
1192
+ --source capability|api Filter by source type
1193
+ --page N --page-size N Pagination
1194
+ --category <name> Filter by category
1195
+ --service-id <id> Filter by service
1196
+ search <query> Search actions by keyword
1197
+ --source capability|api Filter by source type
1198
+ --category <name> Filter by category
1199
+ --page N --page-size N Pagination
1200
+ categories List all action categories
1201
+ --source capability|api Filter by source type
1202
+ services List all services
1203
+ --page N --page-size N Pagination
1204
+ --category <name> Filter by category
1205
+ get <id> [--method GET|POST|...] Get action schema (filter by HTTP method)
1206
+ --code <target> Generate code snippet (curl, py, js, ts, go)
1207
+ call <id> --input '{"key":"val"}' Execute an action
1208
+ --method GET|POST|... Override HTTP method
1209
+ --code <target> Generate code snippet instead of executing
1210
+ Variants: python.requests, python.httpx, javascript.fetch, javascript.axios
1211
+
1212
+ oauth bind [--provider twitter] Bind Twitter OAuth to your API key
1213
+ oauth status List current OAuth bindings
1214
+ oauth unbind <binding-id> Remove an OAuth binding
1215
+ oauth providers List available OAuth providers
1216
+
1217
+ register [referral-code] Create a new user account (apiKey saved automatically)
1218
+ --referral-code <code> Register with an inviter's referral code (also: --referralCode, or as positional arg)
1219
+ balance Show current account balance
1220
+ topup [--amount <usd>] [--method stripe|x402] Generate payment URL
1221
+
1222
+ health Check backend connectivity
1223
+
1224
+ config show Show current config
1225
+ config set apiKey=<key> Save API key to ~/.xapi/config.json
1226
+ config health Check backend connectivity (alias: xapi-to health)
1227
+
1228
+ GLOBAL FLAGS
1229
+ --format json|pretty|table Output format (default: json)
1230
+ --help Show help (use with a command for details, e.g. xapi-to get --help)
1231
+
1232
+ ENV VARS
1233
+ XAPI_KEY API key (header: XAPI-Key)
1234
+ XAPI_ACTION_HOST Action service host (default: action.xapi.to)
1235
+ XAPI_OUTPUT Default output format
1236
+
1237
+ EXAMPLES
1238
+ xapi-to register
1239
+ xapi-to register --referral-code xapito # register with an inviter's referral code
1240
+ xapi-to register xapito # positional shorthand
1241
+ xapi-to list --format table
1242
+ xapi-to list --source capability
1243
+ xapi-to search twitter --source api
1244
+ xapi-to get twitter.tweet_detail
1245
+ xapi-to get twitter.tweet_detail --code curl
1246
+ xapi-to get twitter.tweet_detail --code py --format pretty
1247
+ xapi-to call twitter.tweet_detail --input '{"tweet_id":"1234567890"}'
1248
+ xapi-to call twitter.tweet_detail --input '{"tweet_id":"1234567890"}' --code python
1249
+ xapi-to categories
1250
+ xapi-to services --format table
1251
+ xapi-to config set apiKey=xapi_abc123
1252
+ xapi-to health
1253
+ `;
1254
+ async function main() {
1255
+ const { positional, flags } = parseArgs(process.argv.slice(2));
1256
+ if (positional.length === 0) {
1257
+ console.log(HELP);
1258
+ process.exit(0);
1259
+ }
1260
+ if (flags.format) process.env.XAPI_OUTPUT = flags.format;
1261
+ const [cmd, ...rest] = positional;
1262
+ switch (cmd) {
1263
+ // ── Action commands (top-level) ──
1264
+ case "list":
1265
+ return actionList2(rest, flags);
1266
+ case "search":
1267
+ return actionSearch2(rest, flags);
1268
+ case "categories":
1269
+ return actionCategories2(rest, flags);
1270
+ case "services":
1271
+ return actionServices2(rest, flags);
1272
+ case "get":
1273
+ return actionGet2(rest, flags);
1274
+ case "call":
1275
+ return actionCall2(rest, flags);
1276
+ // ── OAuth commands ──
1277
+ case "oauth": {
1278
+ if (flags.help || rest.length === 0) {
1279
+ console.log(OAUTH_HELP2);
1280
+ process.exit(0);
1281
+ }
1282
+ const [subCmd, ...subRest] = rest;
1283
+ switch (subCmd) {
1284
+ case "bind":
1285
+ return oauthBind(subRest, flags);
1286
+ case "status":
1287
+ return oauthStatus(subRest, flags);
1288
+ case "unbind":
1289
+ return oauthUnbind(subRest, flags);
1290
+ case "providers":
1291
+ return oauthProviders(subRest, flags);
1292
+ default:
1293
+ console.error(JSON.stringify({ error: `unknown oauth command: ${subCmd}`, hint: "valid commands: bind, status, unbind, providers" }));
1294
+ process.exit(1);
1295
+ }
1296
+ break;
1297
+ }
1298
+ // ── Account commands ──
1299
+ case "register":
1300
+ return register(rest, flags);
1301
+ case "balance":
1302
+ return balance(rest, flags);
1303
+ case "topup":
1304
+ return topup(rest, flags);
1305
+ case "health":
1306
+ return configHealth(rest, flags);
1307
+ // ── Config commands ──
1308
+ case "config": {
1309
+ if (flags.help || rest.length === 0) {
1310
+ console.log(CONFIG_HELP2);
1311
+ process.exit(0);
1312
+ }
1313
+ const [subCmd, ...subRest] = rest;
1314
+ switch (subCmd) {
1315
+ case "show":
1316
+ return configShow(subRest, flags);
1317
+ case "set":
1318
+ return configSet(subRest, flags);
1319
+ case "health":
1320
+ return configHealth(subRest, flags);
1321
+ default:
1322
+ console.error(JSON.stringify({ error: `unknown config command: ${subCmd}`, hint: "valid commands: show, set, health" }));
1323
+ process.exit(1);
1324
+ }
1325
+ break;
1326
+ }
1327
+ default:
1328
+ console.error(JSON.stringify({ error: `unknown command: ${cmd}`, hint: "run xapi-to --help" }));
1329
+ process.exit(1);
1330
+ }
1331
+ }
1332
+ main().catch((e) => {
1333
+ console.error(JSON.stringify({ error: "fatal", message: e.message }));
1334
+ process.exit(1);
1335
+ });