xapi-to 0.1.7 → 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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # xapi-cli
1
+ # xapi-to
2
2
 
3
3
  Agent-friendly command-line interface for [xAPI](https://xapi.to) — discover and call capabilities and APIs from your terminal or AI agent.
4
4
 
@@ -6,10 +6,10 @@ Agent-friendly command-line interface for [xAPI](https://xapi.to) — discover a
6
6
 
7
7
  ```bash
8
8
  # Via npx (no install needed)
9
- npx @xapi-to/xapi --help
9
+ npx xapi-to --help
10
10
 
11
11
  # Or install globally with bun
12
- bun add -g @xapi-to/xapi
12
+ bun add -g xapi-to
13
13
 
14
14
  # Or from source
15
15
  cd xapi-cli && bun install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xapi-to",
3
- "version": "0.1.7",
3
+ "version": "0.1.10",
4
4
  "description": "Agent-friendly CLI for xapi - discover and call capabilities and APIs",
5
5
  "type": "module",
6
6
  "bin": {
package/src/client.ts CHANGED
@@ -32,7 +32,7 @@ export async function request<T>(
32
32
  if (data?.statusCode === 401 || data?.error === 'Unauthorized') {
33
33
  throw new Error(
34
34
  'Authentication failed: ' + (data.message || 'Invalid or missing API key')
35
- + '. Run "npx @xapi-to/xapi config set apiKey=<key>" to update your key.',
35
+ + '. Run "npx xapi-to config set apiKey=<key>" to update your key.',
36
36
  );
37
37
  }
38
38
  if (data?.error === 'OAuth Required' || (data?.statusCode === 403 && data?.message?.includes('OAuth'))) {
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
+ }
@@ -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);
@@ -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/config.ts CHANGED
@@ -44,7 +44,7 @@ export function getConfig(): XapiConfig {
44
44
 
45
45
  export function requireApiKey(cfg: XapiConfig): void {
46
46
  if (!cfg.apiKey) {
47
- err('API key not configured', 'Run "npx @xapi-to/xapi register" to create an account, or "npx @xapi-to/xapi config set apiKey=<key>" to set an existing key.');
47
+ 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.');
48
48
  }
49
49
  }
50
50
 
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 this help
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 (flags.help || positional.length === 0) {
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);