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/README.md +42 -40
- 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 -58
- package/src/commands/oauth.ts +0 -297
- package/src/commands/register.ts +0 -77
- package/src/commands/topup.ts +0 -33
- package/src/config.ts +0 -69
- package/src/format.ts +0 -61
- package/src/index.ts +0 -216
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
|
+
});
|