xapi-to 0.1.9 → 0.1.10
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/package.json +1 -1
- package/src/codegen.ts +306 -0
- package/src/commands/action.ts +163 -2
- package/src/commands/oauth.ts +26 -0
- package/src/index.ts +15 -4
package/package.json
CHANGED
package/src/codegen.ts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code snippet generation for API actions.
|
|
3
|
+
*
|
|
4
|
+
* Generates executable code in curl, Python, JavaScript, TypeScript, and Go
|
|
5
|
+
* that calls the xapi action execute endpoint.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { scheme } from './config.ts';
|
|
9
|
+
|
|
10
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface CodegenParams {
|
|
13
|
+
actionId: string;
|
|
14
|
+
input: Record<string, unknown>;
|
|
15
|
+
actionHost: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ResolvedTarget {
|
|
19
|
+
lang: string;
|
|
20
|
+
lib: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Target resolution ────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
const TARGET_MAP: Record<string, ResolvedTarget> = {
|
|
26
|
+
'curl': { lang: 'curl', lib: 'curl' },
|
|
27
|
+
'python': { lang: 'python', lib: 'requests' },
|
|
28
|
+
'py': { lang: 'python', lib: 'requests' },
|
|
29
|
+
'python.requests': { lang: 'python', lib: 'requests' },
|
|
30
|
+
'python.httpx': { lang: 'python', lib: 'httpx' },
|
|
31
|
+
'py.requests': { lang: 'python', lib: 'requests' },
|
|
32
|
+
'py.httpx': { lang: 'python', lib: 'httpx' },
|
|
33
|
+
'javascript': { lang: 'javascript', lib: 'fetch' },
|
|
34
|
+
'js': { lang: 'javascript', lib: 'fetch' },
|
|
35
|
+
'javascript.fetch': { lang: 'javascript', lib: 'fetch' },
|
|
36
|
+
'javascript.axios': { lang: 'javascript', lib: 'axios' },
|
|
37
|
+
'js.fetch': { lang: 'javascript', lib: 'fetch' },
|
|
38
|
+
'js.axios': { lang: 'javascript', lib: 'axios' },
|
|
39
|
+
'typescript': { lang: 'typescript', lib: 'fetch' },
|
|
40
|
+
'ts': { lang: 'typescript', lib: 'fetch' },
|
|
41
|
+
'typescript.fetch': { lang: 'typescript', lib: 'fetch' },
|
|
42
|
+
'ts.fetch': { lang: 'typescript', lib: 'fetch' },
|
|
43
|
+
'go': { lang: 'go', lib: 'net/http' },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const SUPPORTED_TARGETS = [
|
|
47
|
+
'curl',
|
|
48
|
+
'python (py) [.requests, .httpx]',
|
|
49
|
+
'javascript (js) [.fetch, .axios]',
|
|
50
|
+
'typescript (ts) [.fetch]',
|
|
51
|
+
'go [net/http]',
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export function resolveTarget(raw: string): ResolvedTarget {
|
|
55
|
+
const target = TARGET_MAP[raw.toLowerCase()];
|
|
56
|
+
if (!target) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`unknown --code target: "${raw}". Supported: ${SUPPORTED_TARGETS.join(', ')}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return target;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Default input builder ────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
interface InputSchema {
|
|
67
|
+
properties?: Record<string, { type?: string; default?: unknown }>;
|
|
68
|
+
required?: string[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function typeDefault(type: string): unknown {
|
|
72
|
+
switch (type) {
|
|
73
|
+
case 'string': return '';
|
|
74
|
+
case 'number': case 'integer': return 0;
|
|
75
|
+
case 'boolean': return false;
|
|
76
|
+
case 'object': return {};
|
|
77
|
+
case 'array': return [];
|
|
78
|
+
default: return '';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function buildDefaultInput(schema: InputSchema): Record<string, unknown> {
|
|
83
|
+
if (!schema.properties) return {};
|
|
84
|
+
const result: Record<string, unknown> = {};
|
|
85
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
86
|
+
if (prop.default !== undefined) {
|
|
87
|
+
// Clone reference types to avoid shared mutation
|
|
88
|
+
const val = prop.default;
|
|
89
|
+
result[key] = (typeof val === 'object' && val !== null) ? JSON.parse(JSON.stringify(val)) : val;
|
|
90
|
+
} else {
|
|
91
|
+
result[key] = typeDefault(prop.type ?? 'string');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Code generators ──────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
const SAFE_HOST_PATTERN = /^[a-zA-Z0-9._\-]+(:\d{1,5})?$/;
|
|
100
|
+
|
|
101
|
+
function validateHost(host: string): void {
|
|
102
|
+
if (!SAFE_HOST_PATTERN.test(host)) {
|
|
103
|
+
throw new Error(`invalid actionHost: "${host}" — must be a valid hostname with optional port`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function baseUrl(actionHost: string): string {
|
|
108
|
+
validateHost(actionHost);
|
|
109
|
+
return `${scheme(actionHost)}://${actionHost}/v1/actions/execute`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function jsonBody(actionId: string, input: Record<string, unknown>): string {
|
|
113
|
+
return JSON.stringify({ action_id: actionId, input }, null, 2);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Re-indent a multi-line string so continuation lines are aligned */
|
|
117
|
+
function indent(text: string, spaces: number): string {
|
|
118
|
+
const pad = ' '.repeat(spaces);
|
|
119
|
+
const lines = text.split('\n');
|
|
120
|
+
return lines.map((line, i) => (i === 0 ? line : pad + line)).join('\n');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Escape single quotes for POSIX shell single-quoted strings */
|
|
124
|
+
function shellEscape(s: string): string {
|
|
125
|
+
return s.replace(/'/g, "'\\''");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function genCurl(params: CodegenParams): string {
|
|
129
|
+
const url = baseUrl(params.actionHost);
|
|
130
|
+
const body = jsonBody(params.actionId, params.input);
|
|
131
|
+
return [
|
|
132
|
+
'# Set XAPI_API_KEY env var or replace with your key',
|
|
133
|
+
`curl -X POST '${shellEscape(url)}' \\`,
|
|
134
|
+
` -H 'Content-Type: application/json' \\`,
|
|
135
|
+
` -H "XAPI-Key: \${XAPI_API_KEY}" \\`,
|
|
136
|
+
` -d '${shellEscape(body)}'`,
|
|
137
|
+
].join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function genPython(lib: 'requests' | 'httpx', params: CodegenParams): string {
|
|
141
|
+
const url = baseUrl(params.actionHost);
|
|
142
|
+
const payload = { action_id: params.actionId, input: params.input };
|
|
143
|
+
return [
|
|
144
|
+
`# pip install ${lib}`,
|
|
145
|
+
'# Set XAPI_API_KEY env var or replace with your key',
|
|
146
|
+
'import os',
|
|
147
|
+
`import ${lib}`,
|
|
148
|
+
'',
|
|
149
|
+
`resp = ${lib}.post(`,
|
|
150
|
+
` "${url}",`,
|
|
151
|
+
` headers={`,
|
|
152
|
+
` "Content-Type": "application/json",`,
|
|
153
|
+
` "XAPI-Key": os.environ["XAPI_API_KEY"],`,
|
|
154
|
+
` },`,
|
|
155
|
+
` json=${indent(pythonDict(payload), 4)},`,
|
|
156
|
+
`)`,
|
|
157
|
+
'print(resp.json())',
|
|
158
|
+
].join('\n');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function genJavaScriptFetch(params: CodegenParams): string {
|
|
162
|
+
const url = baseUrl(params.actionHost);
|
|
163
|
+
const body = jsonBody(params.actionId, params.input);
|
|
164
|
+
return [
|
|
165
|
+
'// Set XAPI_API_KEY env var or replace with your key',
|
|
166
|
+
`const resp = await fetch("${url}", {`,
|
|
167
|
+
` method: "POST",`,
|
|
168
|
+
` headers: {`,
|
|
169
|
+
` "Content-Type": "application/json",`,
|
|
170
|
+
` "XAPI-Key": process.env.XAPI_API_KEY,`,
|
|
171
|
+
` },`,
|
|
172
|
+
` body: JSON.stringify(${indent(body, 2)}),`,
|
|
173
|
+
`});`,
|
|
174
|
+
'console.log(await resp.json());',
|
|
175
|
+
].join('\n');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function genJavaScriptAxios(params: CodegenParams): string {
|
|
179
|
+
const url = baseUrl(params.actionHost);
|
|
180
|
+
const body = jsonBody(params.actionId, params.input);
|
|
181
|
+
return [
|
|
182
|
+
'// npm install axios',
|
|
183
|
+
'// Set XAPI_API_KEY env var or replace with your key',
|
|
184
|
+
'import axios from "axios";',
|
|
185
|
+
'',
|
|
186
|
+
`const resp = await axios.post(`,
|
|
187
|
+
` "${url}",`,
|
|
188
|
+
` ${indent(body, 2)},`,
|
|
189
|
+
` {`,
|
|
190
|
+
` headers: {`,
|
|
191
|
+
` "Content-Type": "application/json",`,
|
|
192
|
+
` "XAPI-Key": process.env.XAPI_API_KEY,`,
|
|
193
|
+
` },`,
|
|
194
|
+
` },`,
|
|
195
|
+
`);`,
|
|
196
|
+
'console.log(resp.data);',
|
|
197
|
+
].join('\n');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function genTypescriptFetch(params: CodegenParams): string {
|
|
201
|
+
const url = baseUrl(params.actionHost);
|
|
202
|
+
const body = jsonBody(params.actionId, params.input);
|
|
203
|
+
return [
|
|
204
|
+
'// Set XAPI_API_KEY env var or replace with your key',
|
|
205
|
+
`const resp: Response = await fetch("${url}", {`,
|
|
206
|
+
` method: "POST",`,
|
|
207
|
+
` headers: {`,
|
|
208
|
+
` "Content-Type": "application/json",`,
|
|
209
|
+
` "XAPI-Key": process.env.XAPI_API_KEY!,`,
|
|
210
|
+
` },`,
|
|
211
|
+
` body: JSON.stringify(${indent(body, 2)}),`,
|
|
212
|
+
`});`,
|
|
213
|
+
'const data: unknown = await resp.json();',
|
|
214
|
+
'console.log(data);',
|
|
215
|
+
].join('\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function genGo(params: CodegenParams): string {
|
|
219
|
+
const url = baseUrl(params.actionHost);
|
|
220
|
+
const body = jsonBody(params.actionId, params.input);
|
|
221
|
+
const escaped = body.replace(/`/g, '` + "`" + `');
|
|
222
|
+
return [
|
|
223
|
+
'// Set XAPI_API_KEY env var or replace with your key',
|
|
224
|
+
'package main',
|
|
225
|
+
'',
|
|
226
|
+
'import (',
|
|
227
|
+
'\t"fmt"',
|
|
228
|
+
'\t"io"',
|
|
229
|
+
'\t"net/http"',
|
|
230
|
+
'\t"os"',
|
|
231
|
+
'\t"strings"',
|
|
232
|
+
')',
|
|
233
|
+
'',
|
|
234
|
+
'func main() {',
|
|
235
|
+
`\tbody := \`${escaped}\``,
|
|
236
|
+
`\treq, err := http.NewRequest("POST", "${url}", strings.NewReader(body))`,
|
|
237
|
+
'\tif err != nil {',
|
|
238
|
+
'\t\tpanic(err)',
|
|
239
|
+
'\t}',
|
|
240
|
+
'\treq.Header.Set("Content-Type", "application/json")',
|
|
241
|
+
'\treq.Header.Set("XAPI-Key", os.Getenv("XAPI_API_KEY"))',
|
|
242
|
+
'',
|
|
243
|
+
'\tresp, err := http.DefaultClient.Do(req)',
|
|
244
|
+
'\tif err != nil {',
|
|
245
|
+
'\t\tpanic(err)',
|
|
246
|
+
'\t}',
|
|
247
|
+
'\tdefer resp.Body.Close()',
|
|
248
|
+
'',
|
|
249
|
+
'\tresult, err := io.ReadAll(resp.Body)',
|
|
250
|
+
'\tif err != nil {',
|
|
251
|
+
'\t\tpanic(err)',
|
|
252
|
+
'\t}',
|
|
253
|
+
'\tfmt.Println(string(result))',
|
|
254
|
+
'}',
|
|
255
|
+
].join('\n');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── Python dict formatter ────────────────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
function pythonDict(obj: unknown, depth = 0): string {
|
|
261
|
+
const pad = ' '.repeat(depth);
|
|
262
|
+
const inner = ' '.repeat(depth + 1);
|
|
263
|
+
|
|
264
|
+
if (obj === null || obj === undefined) return 'None';
|
|
265
|
+
if (typeof obj === 'boolean') return obj ? 'True' : 'False';
|
|
266
|
+
if (typeof obj === 'number') return String(obj);
|
|
267
|
+
if (typeof obj === 'string') return JSON.stringify(obj);
|
|
268
|
+
|
|
269
|
+
if (Array.isArray(obj)) {
|
|
270
|
+
if (obj.length === 0) return '[]';
|
|
271
|
+
const items = obj.map(v => `${inner}${pythonDict(v, depth + 1)}`);
|
|
272
|
+
return `[\n${items.join(',\n')}\n${pad}]`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (typeof obj === 'object') {
|
|
276
|
+
const entries = Object.entries(obj as Record<string, unknown>);
|
|
277
|
+
if (entries.length === 0) return '{}';
|
|
278
|
+
const items = entries.map(
|
|
279
|
+
([k, v]) => `${inner}${JSON.stringify(k)}: ${pythonDict(v, depth + 1)}`,
|
|
280
|
+
);
|
|
281
|
+
return `{\n${items.join(',\n')}\n${pad}}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return String(obj);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── Main entry point ─────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
type Generator = (params: CodegenParams) => string;
|
|
290
|
+
|
|
291
|
+
const GENERATORS: Record<string, Record<string, Generator>> = {
|
|
292
|
+
curl: { curl: genCurl },
|
|
293
|
+
python: { requests: p => genPython('requests', p), httpx: p => genPython('httpx', p) },
|
|
294
|
+
javascript: { fetch: genJavaScriptFetch, axios: genJavaScriptAxios },
|
|
295
|
+
typescript: { fetch: genTypescriptFetch },
|
|
296
|
+
go: { 'net/http': genGo },
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export function generateCode(target: string, params: CodegenParams): { lang: string; lib: string; code: string } {
|
|
300
|
+
const { lang, lib } = resolveTarget(target);
|
|
301
|
+
const generator = GENERATORS[lang]?.[lib];
|
|
302
|
+
if (!generator) {
|
|
303
|
+
throw new Error(`no generator for ${lang}.${lib}`);
|
|
304
|
+
}
|
|
305
|
+
return { lang, lib, code: generator(params) };
|
|
306
|
+
}
|
package/src/commands/action.ts
CHANGED
|
@@ -6,10 +6,144 @@
|
|
|
6
6
|
|
|
7
7
|
import { getConfig, requireApiKey } from '../config.ts';
|
|
8
8
|
import * as client from '../client.ts';
|
|
9
|
-
import { output, err } from '../format.ts';
|
|
9
|
+
import { output, err, getFormat } from '../format.ts';
|
|
10
|
+
import { generateCode, buildDefaultInput, resolveTarget } from '../codegen.ts';
|
|
10
11
|
|
|
11
12
|
const VALID_SOURCES = ['capability', 'api'];
|
|
12
13
|
|
|
14
|
+
// ── Subcommand help texts ────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const LIST_HELP = `xapi list - List all actions
|
|
17
|
+
|
|
18
|
+
USAGE
|
|
19
|
+
xapi list [flags]
|
|
20
|
+
|
|
21
|
+
FLAGS
|
|
22
|
+
--source capability|api Filter by source type
|
|
23
|
+
--category <name> Filter by category
|
|
24
|
+
--service-id <id> Filter by service
|
|
25
|
+
--page N Page number (default: 1)
|
|
26
|
+
--page-size N Results per page
|
|
27
|
+
--format json|pretty|table Output format
|
|
28
|
+
|
|
29
|
+
EXAMPLES
|
|
30
|
+
xapi list
|
|
31
|
+
xapi list --source api --format table
|
|
32
|
+
xapi list --category social --page 2
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const SEARCH_HELP = `xapi search - Search actions by keyword
|
|
36
|
+
|
|
37
|
+
USAGE
|
|
38
|
+
xapi search <query> [flags]
|
|
39
|
+
|
|
40
|
+
FLAGS
|
|
41
|
+
--source capability|api Filter by source type
|
|
42
|
+
--category <name> Filter by category
|
|
43
|
+
--page N Page number (default: 1)
|
|
44
|
+
--page-size N Results per page
|
|
45
|
+
--format json|pretty|table Output format
|
|
46
|
+
|
|
47
|
+
EXAMPLES
|
|
48
|
+
xapi search twitter
|
|
49
|
+
xapi search "tweet detail" --source api
|
|
50
|
+
xapi search weather --category utility --format table
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const GET_HELP = `xapi get - Get action schema
|
|
54
|
+
|
|
55
|
+
USAGE
|
|
56
|
+
xapi get <id> [flags]
|
|
57
|
+
|
|
58
|
+
FLAGS
|
|
59
|
+
--method GET|POST|... Filter by HTTP method
|
|
60
|
+
--code <target> Generate code snippet instead of showing schema
|
|
61
|
+
--format json|pretty|table Output format
|
|
62
|
+
|
|
63
|
+
CODE TARGETS
|
|
64
|
+
curl cURL command
|
|
65
|
+
py, python Python (requests)
|
|
66
|
+
python.requests Python with requests
|
|
67
|
+
py.requests alias for python.requests
|
|
68
|
+
python.httpx Python with httpx
|
|
69
|
+
py.httpx alias for python.httpx
|
|
70
|
+
js, javascript JavaScript (fetch)
|
|
71
|
+
javascript.fetch JavaScript with fetch
|
|
72
|
+
js.fetch alias for javascript.fetch
|
|
73
|
+
javascript.axios JavaScript with axios
|
|
74
|
+
js.axios alias for javascript.axios
|
|
75
|
+
ts, typescript TypeScript (fetch)
|
|
76
|
+
typescript.fetch TypeScript with fetch
|
|
77
|
+
ts.fetch alias for typescript.fetch
|
|
78
|
+
go Go (net/http)
|
|
79
|
+
|
|
80
|
+
EXAMPLES
|
|
81
|
+
xapi get twitter.tweet_detail
|
|
82
|
+
xapi get twitter.tweet_detail --method POST
|
|
83
|
+
xapi get twitter.tweet_detail --code curl
|
|
84
|
+
xapi get twitter.tweet_detail --code python.httpx --format pretty
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
const CALL_HELP = `xapi call - Execute an action
|
|
88
|
+
|
|
89
|
+
USAGE
|
|
90
|
+
xapi call <id> --input '{"key":"val"}' [flags]
|
|
91
|
+
|
|
92
|
+
FLAGS
|
|
93
|
+
--input <json> Input payload as JSON (required for execution)
|
|
94
|
+
--method GET|POST|... Override HTTP method
|
|
95
|
+
--code <target> Generate code snippet instead of executing
|
|
96
|
+
--format json|pretty|table Output format
|
|
97
|
+
|
|
98
|
+
CODE TARGETS
|
|
99
|
+
curl cURL command
|
|
100
|
+
py, python Python (requests)
|
|
101
|
+
python.requests Python with requests
|
|
102
|
+
py.requests alias for python.requests
|
|
103
|
+
python.httpx Python with httpx
|
|
104
|
+
py.httpx alias for python.httpx
|
|
105
|
+
js, javascript JavaScript (fetch)
|
|
106
|
+
javascript.fetch JavaScript with fetch
|
|
107
|
+
js.fetch alias for javascript.fetch
|
|
108
|
+
javascript.axios JavaScript with axios
|
|
109
|
+
js.axios alias for javascript.axios
|
|
110
|
+
ts, typescript TypeScript (fetch)
|
|
111
|
+
typescript.fetch TypeScript with fetch
|
|
112
|
+
ts.fetch alias for typescript.fetch
|
|
113
|
+
go Go (net/http)
|
|
114
|
+
|
|
115
|
+
EXAMPLES
|
|
116
|
+
xapi call twitter.tweet_detail --input '{"tweet_id":"1234567890"}'
|
|
117
|
+
xapi call twitter.tweet_detail --input '{"tweet_id":"123"}' --code py
|
|
118
|
+
xapi call twitter.tweet_detail --input '{"tweet_id":"123"}' --code curl --format pretty
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
/** Print subcommand help and exit if --help flag is set */
|
|
122
|
+
function showHelpIfRequested(flags: Record<string, string>, helpText: string): void {
|
|
123
|
+
if (flags.help) {
|
|
124
|
+
console.log(helpText);
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Validate --code flag: check for bare flag and unknown target (fail fast before I/O) */
|
|
130
|
+
function validateCodeFlag(flags: Record<string, string>): void {
|
|
131
|
+
if (flags.code === 'true') {
|
|
132
|
+
err('--code requires a target language, e.g. --code curl, --code py, --code js');
|
|
133
|
+
}
|
|
134
|
+
resolveTarget(flags.code);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Output code snippet respecting --format */
|
|
138
|
+
function outputCode(result: { lang: string; lib: string; code: string }, flags: Record<string, string>) {
|
|
139
|
+
const fmt = flags.format || getFormat();
|
|
140
|
+
if (fmt === 'json') {
|
|
141
|
+
output({ language: result.lang, library: result.lib, code: result.code }, 'json');
|
|
142
|
+
} else {
|
|
143
|
+
console.log(result.code);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
13
147
|
/** Validate and return source filter from --source flag */
|
|
14
148
|
function getSource(flags: Record<string, string>): string | undefined {
|
|
15
149
|
if (!flags.source) return undefined;
|
|
@@ -20,6 +154,7 @@ function getSource(flags: Record<string, string>): string | undefined {
|
|
|
20
154
|
}
|
|
21
155
|
|
|
22
156
|
export async function actionList(args: string[], flags: Record<string, string>) {
|
|
157
|
+
showHelpIfRequested(flags, LIST_HELP);
|
|
23
158
|
const cfg = getConfig();
|
|
24
159
|
try {
|
|
25
160
|
const res = await client.actionList(cfg, {
|
|
@@ -49,6 +184,7 @@ export async function actionList(args: string[], flags: Record<string, string>)
|
|
|
49
184
|
}
|
|
50
185
|
|
|
51
186
|
export async function actionSearch(args: string[], flags: Record<string, string>) {
|
|
187
|
+
showHelpIfRequested(flags, SEARCH_HELP);
|
|
52
188
|
const query = args[0];
|
|
53
189
|
if (!query) err('usage: xapi search <query>');
|
|
54
190
|
const cfg = getConfig();
|
|
@@ -119,8 +255,10 @@ export async function actionServices(args: string[], flags: Record<string, strin
|
|
|
119
255
|
}
|
|
120
256
|
|
|
121
257
|
export async function actionGet(args: string[], flags: Record<string, string>) {
|
|
258
|
+
showHelpIfRequested(flags, GET_HELP);
|
|
122
259
|
const id = args[0];
|
|
123
260
|
if (!id) err('usage: xapi get <id> [--method GET|POST|DELETE|...]');
|
|
261
|
+
if (flags.code) validateCodeFlag(flags);
|
|
124
262
|
const cfg = getConfig();
|
|
125
263
|
try {
|
|
126
264
|
const res = await client.actionGet(id, cfg);
|
|
@@ -132,6 +270,20 @@ export async function actionGet(args: string[], flags: Record<string, string>) {
|
|
|
132
270
|
if (filtered.length === 0) {
|
|
133
271
|
err(`no endpoint found for method "${methodFilter}" in action "${id}"`);
|
|
134
272
|
}
|
|
273
|
+
|
|
274
|
+
if (flags.code) {
|
|
275
|
+
if (filtered.length > 1) {
|
|
276
|
+
process.stderr.write(
|
|
277
|
+
`Warning: action "${id}" has ${filtered.length} endpoints; using method "${(filtered[0] as any).method}". Use --method to select a specific one.\n`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const action = filtered[0] as any;
|
|
281
|
+
const input = buildDefaultInput(action.input ?? {});
|
|
282
|
+
const result = generateCode(flags.code, { actionId: id, input, actionHost: cfg.actionHost });
|
|
283
|
+
outputCode(result, flags);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
135
287
|
output(filtered.length === 1 ? filtered[0] : filtered, flags.format as any);
|
|
136
288
|
} catch (e: any) {
|
|
137
289
|
err('get failed', e.message);
|
|
@@ -139,10 +291,11 @@ export async function actionGet(args: string[], flags: Record<string, string>) {
|
|
|
139
291
|
}
|
|
140
292
|
|
|
141
293
|
export async function actionCall(args: string[], flags: Record<string, string>) {
|
|
294
|
+
showHelpIfRequested(flags, CALL_HELP);
|
|
142
295
|
const id = args[0];
|
|
143
296
|
if (!id) err('usage: xapi call <id> --input \'{"key":"val"}\'');
|
|
297
|
+
if (flags.code) validateCodeFlag(flags);
|
|
144
298
|
const cfg = getConfig();
|
|
145
|
-
requireApiKey(cfg);
|
|
146
299
|
let input: Record<string, unknown> = {};
|
|
147
300
|
if (flags.input) {
|
|
148
301
|
try {
|
|
@@ -154,6 +307,14 @@ export async function actionCall(args: string[], flags: Record<string, string>)
|
|
|
154
307
|
if (flags.method) {
|
|
155
308
|
input = { ...input, method: flags.method.toUpperCase() };
|
|
156
309
|
}
|
|
310
|
+
|
|
311
|
+
if (flags.code) {
|
|
312
|
+
const result = generateCode(flags.code, { actionId: id, input, actionHost: cfg.actionHost });
|
|
313
|
+
outputCode(result, flags);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
requireApiKey(cfg);
|
|
157
318
|
try {
|
|
158
319
|
const res = await client.actionCall(id, input, cfg);
|
|
159
320
|
output(res, flags.format as any);
|
package/src/commands/oauth.ts
CHANGED
|
@@ -109,6 +109,32 @@ async function findCurrentKeyRecord(
|
|
|
109
109
|
return match;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// ── Help text ──────────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
export const OAUTH_HELP = `xapi oauth - Manage OAuth bindings
|
|
115
|
+
|
|
116
|
+
USAGE
|
|
117
|
+
xapi oauth <command> [flags]
|
|
118
|
+
|
|
119
|
+
COMMANDS
|
|
120
|
+
bind [--provider <name>] Bind an OAuth account to your API key
|
|
121
|
+
status List current OAuth bindings
|
|
122
|
+
unbind <binding-id> Remove an OAuth binding
|
|
123
|
+
providers List available OAuth providers
|
|
124
|
+
|
|
125
|
+
FLAGS
|
|
126
|
+
--provider <name> OAuth provider (default: twitter)
|
|
127
|
+
--format json|pretty|table Output format
|
|
128
|
+
|
|
129
|
+
EXAMPLES
|
|
130
|
+
xapi oauth bind
|
|
131
|
+
xapi oauth bind --provider twitter
|
|
132
|
+
xapi oauth status
|
|
133
|
+
xapi oauth status --format pretty
|
|
134
|
+
xapi oauth unbind abc123
|
|
135
|
+
xapi oauth providers
|
|
136
|
+
`;
|
|
137
|
+
|
|
112
138
|
// ── Commands ───────────────────────────────────────────────────────────────────
|
|
113
139
|
|
|
114
140
|
/**
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* xapi search <query> [--source capability|api] [--category X] [--page N] [--page-size N]
|
|
8
8
|
* xapi categories [--source capability|api]
|
|
9
9
|
* xapi services [--page N] [--page-size N] [--category X]
|
|
10
|
-
* xapi get <id>
|
|
11
|
-
* xapi call <id> --input '{"k":"v"}'
|
|
10
|
+
* xapi get <id> [--code curl|py|js|ts|go]
|
|
11
|
+
* xapi call <id> --input '{"k":"v"}' [--code curl|py|js|ts|go]
|
|
12
12
|
*
|
|
13
13
|
* xapi config show
|
|
14
14
|
* xapi config set apiKey=<key>
|
|
@@ -30,6 +30,7 @@ import * as regCmds from './commands/register.ts';
|
|
|
30
30
|
import * as topupCmds from './commands/topup.ts';
|
|
31
31
|
import * as balanceCmds from './commands/balance.ts';
|
|
32
32
|
import * as oauthCmds from './commands/oauth.ts';
|
|
33
|
+
const { OAUTH_HELP } = oauthCmds;
|
|
33
34
|
|
|
34
35
|
// ── Argument parser ───────────────────────────────────────────────────────────
|
|
35
36
|
|
|
@@ -85,7 +86,10 @@ COMMANDS
|
|
|
85
86
|
--page N --page-size N Pagination
|
|
86
87
|
--category <name> Filter by category
|
|
87
88
|
get <id> [--method GET|POST|...] Get action schema (filter by HTTP method)
|
|
89
|
+
--code <target> Generate code snippet (curl, py, js, ts, go)
|
|
88
90
|
call <id> --input '{"key":"val"}' Execute an action
|
|
91
|
+
--code <target> Generate code snippet instead of executing
|
|
92
|
+
Variants: python.requests, python.httpx, javascript.fetch, javascript.axios
|
|
89
93
|
|
|
90
94
|
oauth bind [--provider twitter] Bind Twitter OAuth to your API key
|
|
91
95
|
oauth status List current OAuth bindings
|
|
@@ -102,7 +106,7 @@ COMMANDS
|
|
|
102
106
|
|
|
103
107
|
GLOBAL FLAGS
|
|
104
108
|
--format json|pretty|table Output format (default: json)
|
|
105
|
-
--help Show
|
|
109
|
+
--help Show help (use with a command for details, e.g. xapi get --help)
|
|
106
110
|
|
|
107
111
|
ENV VARS
|
|
108
112
|
XAPI_API_KEY API key (header: XAPI-Key)
|
|
@@ -115,7 +119,10 @@ EXAMPLES
|
|
|
115
119
|
xapi list --source capability
|
|
116
120
|
xapi search twitter --source api
|
|
117
121
|
xapi get twitter.tweet_detail
|
|
122
|
+
xapi get twitter.tweet_detail --code curl
|
|
123
|
+
xapi get twitter.tweet_detail --code py --format pretty
|
|
118
124
|
xapi call twitter.tweet_detail --input '{"tweet_id":"1234567890"}'
|
|
125
|
+
xapi call twitter.tweet_detail --input '{"tweet_id":"1234567890"}' --code python
|
|
119
126
|
xapi categories
|
|
120
127
|
xapi services --format table
|
|
121
128
|
xapi config set apiKey=xapi_abc123
|
|
@@ -127,7 +134,7 @@ EXAMPLES
|
|
|
127
134
|
async function main() {
|
|
128
135
|
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
129
136
|
|
|
130
|
-
if (
|
|
137
|
+
if (positional.length === 0) {
|
|
131
138
|
console.log(HELP);
|
|
132
139
|
process.exit(0);
|
|
133
140
|
}
|
|
@@ -148,6 +155,10 @@ async function main() {
|
|
|
148
155
|
|
|
149
156
|
// ── OAuth commands ──
|
|
150
157
|
case 'oauth': {
|
|
158
|
+
if (flags.help || rest.length === 0) {
|
|
159
|
+
console.log(OAUTH_HELP);
|
|
160
|
+
process.exit(0);
|
|
161
|
+
}
|
|
151
162
|
const [subCmd, ...subRest] = rest;
|
|
152
163
|
switch (subCmd) {
|
|
153
164
|
case 'bind': return oauthCmds.oauthBind(subRest, flags);
|