xapi-to 0.1.12 → 0.1.14
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/README.md +9 -2
- package/dist/index.js +1335 -0
- package/package.json +14 -9
- package/src/client.ts +0 -265
- package/src/codegen.ts +0 -307
- package/src/commands/action.ts +0 -328
- package/src/commands/balance.ts +0 -35
- package/src/commands/config.ts +0 -39
- package/src/commands/oauth.ts +0 -297
- package/src/commands/register.ts +0 -59
- package/src/commands/topup.ts +0 -33
- package/src/config.ts +0 -69
- package/src/format.ts +0 -61
- package/src/index.ts +0 -205
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 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 list - List all actions
|
|
532
|
+
|
|
533
|
+
USAGE
|
|
534
|
+
xapi 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 list
|
|
546
|
+
xapi list --source api --format table
|
|
547
|
+
xapi list --category social --page 2
|
|
548
|
+
`;
|
|
549
|
+
var SEARCH_HELP = `xapi search - Search actions by keyword
|
|
550
|
+
|
|
551
|
+
USAGE
|
|
552
|
+
xapi 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 search twitter
|
|
563
|
+
xapi search "tweet detail" --source api
|
|
564
|
+
xapi search weather --category utility --format table
|
|
565
|
+
`;
|
|
566
|
+
var GET_HELP = `xapi get - Get action schema
|
|
567
|
+
|
|
568
|
+
USAGE
|
|
569
|
+
xapi 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 get twitter.tweet_detail
|
|
595
|
+
xapi get twitter.tweet_detail --method POST
|
|
596
|
+
xapi get twitter.tweet_detail --code curl
|
|
597
|
+
xapi get twitter.tweet_detail --code python.httpx --format pretty
|
|
598
|
+
`;
|
|
599
|
+
var CALL_HELP = `xapi call - Execute an action
|
|
600
|
+
|
|
601
|
+
USAGE
|
|
602
|
+
xapi 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 call twitter.tweet_detail --input '{"tweet_id":"1234567890"}'
|
|
629
|
+
xapi call twitter.tweet_detail --input '{"tweet_id":"123"}' --code py
|
|
630
|
+
xapi 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 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 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 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 config - Manage CLI configuration
|
|
830
|
+
|
|
831
|
+
USAGE
|
|
832
|
+
xapi 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 health)
|
|
838
|
+
|
|
839
|
+
FLAGS
|
|
840
|
+
--format json|pretty|table Output format
|
|
841
|
+
|
|
842
|
+
EXAMPLES
|
|
843
|
+
xapi config show
|
|
844
|
+
xapi config set apiKey=xapi_abc123
|
|
845
|
+
xapi 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 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 oauth - Manage OAuth bindings
|
|
1020
|
+
|
|
1021
|
+
USAGE
|
|
1022
|
+
xapi 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 oauth bind
|
|
1036
|
+
xapi oauth bind --provider twitter
|
|
1037
|
+
xapi oauth status
|
|
1038
|
+
xapi oauth status --format pretty
|
|
1039
|
+
xapi oauth unbind abc123
|
|
1040
|
+
xapi 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 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 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 oauth unbind <binding-id>", 'Get the binding ID from "xapi 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 - agent-friendly CLI for xapi
|
|
1186
|
+
|
|
1187
|
+
USAGE
|
|
1188
|
+
xapi <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 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 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 register
|
|
1239
|
+
xapi register --referral-code xapito # register with an inviter's referral code
|
|
1240
|
+
xapi register xapito # positional shorthand
|
|
1241
|
+
xapi list --format table
|
|
1242
|
+
xapi list --source capability
|
|
1243
|
+
xapi search twitter --source api
|
|
1244
|
+
xapi get twitter.tweet_detail
|
|
1245
|
+
xapi get twitter.tweet_detail --code curl
|
|
1246
|
+
xapi get twitter.tweet_detail --code py --format pretty
|
|
1247
|
+
xapi call twitter.tweet_detail --input '{"tweet_id":"1234567890"}'
|
|
1248
|
+
xapi call twitter.tweet_detail --input '{"tweet_id":"1234567890"}' --code python
|
|
1249
|
+
xapi categories
|
|
1250
|
+
xapi services --format table
|
|
1251
|
+
xapi config set apiKey=xapi_abc123
|
|
1252
|
+
xapi 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 --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
|
+
});
|