xapi-to 0.1.11 → 0.1.13

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
@@ -21,6 +21,9 @@ cd xapi-cli && bun install
21
21
  # 1. Register a new account (apiKey saved automatically)
22
22
  xapi register
23
23
 
24
+ # 1b. Or register with an inviter's referral code (please replace xapito to your referral code)
25
+ xapi register --referral-code xapito
26
+
24
27
  # 2. Or set an existing key
25
28
  xapi config set apiKey=sk-xxx
26
29
 
@@ -76,6 +79,8 @@ xapi oauth providers # list available providers
76
79
 
77
80
  ```bash
78
81
  xapi register # create account, saves apiKey automatically
82
+ xapi register --referral-code xapito # register with an inviter's referral code (please replace xapito to your referral code)
83
+ xapi register xapito # positional shorthand for --referral-code
79
84
  xapi balance # show USD balance
80
85
  xapi topup # generate payment URL
81
86
  xapi topup --method stripe --amount 10 # stripe, $10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xapi-to",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
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
@@ -124,13 +124,14 @@ export async function actionCall(
124
124
  actionId: string,
125
125
  input: Record<string, unknown>,
126
126
  opts: ClientOptions,
127
+ httpMethod?: string,
127
128
  ) {
128
129
  return request<unknown>(
129
130
  `${baseUrl(opts)}/v1/actions/execute`,
130
131
  {
131
132
  method: 'POST',
132
133
  headers: headers(opts.apiKey),
133
- body: JSON.stringify({ action_id: actionId, input }),
134
+ body: JSON.stringify({ action_id: actionId, ...(httpMethod ? { method: httpMethod } : {}), input }),
134
135
  },
135
136
  EXECUTE_TIMEOUT_MS,
136
137
  );
package/src/codegen.ts CHANGED
@@ -13,6 +13,7 @@ export interface CodegenParams {
13
13
  actionId: string;
14
14
  input: Record<string, unknown>;
15
15
  actionHost: string;
16
+ method?: string;
16
17
  }
17
18
 
18
19
  interface ResolvedTarget {
@@ -109,8 +110,8 @@ function baseUrl(actionHost: string): string {
109
110
  return `${scheme(actionHost)}://${actionHost}/v1/actions/execute`;
110
111
  }
111
112
 
112
- function jsonBody(actionId: string, input: Record<string, unknown>): string {
113
- return JSON.stringify({ action_id: actionId, input }, null, 2);
113
+ function jsonBody(actionId: string, input: Record<string, unknown>, method?: string): string {
114
+ return JSON.stringify({ action_id: actionId, ...(method ? { method } : {}), input }, null, 2);
114
115
  }
115
116
 
116
117
  /** Re-indent a multi-line string so continuation lines are aligned */
@@ -127,7 +128,7 @@ function shellEscape(s: string): string {
127
128
 
128
129
  function genCurl(params: CodegenParams): string {
129
130
  const url = baseUrl(params.actionHost);
130
- const body = jsonBody(params.actionId, params.input);
131
+ const body = jsonBody(params.actionId, params.input, params.method);
131
132
  return [
132
133
  '# Set XAPI_KEY env var or replace with your key',
133
134
  `curl -X POST '${shellEscape(url)}' \\`,
@@ -139,7 +140,7 @@ function genCurl(params: CodegenParams): string {
139
140
 
140
141
  function genPython(lib: 'requests' | 'httpx', params: CodegenParams): string {
141
142
  const url = baseUrl(params.actionHost);
142
- const payload = { action_id: params.actionId, input: params.input };
143
+ const payload = { action_id: params.actionId, ...(params.method ? { method: params.method } : {}), input: params.input };
143
144
  return [
144
145
  `# pip install ${lib}`,
145
146
  '# Set XAPI_KEY env var or replace with your key',
@@ -160,7 +161,7 @@ function genPython(lib: 'requests' | 'httpx', params: CodegenParams): string {
160
161
 
161
162
  function genJavaScriptFetch(params: CodegenParams): string {
162
163
  const url = baseUrl(params.actionHost);
163
- const body = jsonBody(params.actionId, params.input);
164
+ const body = jsonBody(params.actionId, params.input, params.method);
164
165
  return [
165
166
  '// Set XAPI_KEY env var or replace with your key',
166
167
  `const resp = await fetch("${url}", {`,
@@ -177,7 +178,7 @@ function genJavaScriptFetch(params: CodegenParams): string {
177
178
 
178
179
  function genJavaScriptAxios(params: CodegenParams): string {
179
180
  const url = baseUrl(params.actionHost);
180
- const body = jsonBody(params.actionId, params.input);
181
+ const body = jsonBody(params.actionId, params.input, params.method);
181
182
  return [
182
183
  '// npm install axios',
183
184
  '// Set XAPI_KEY env var or replace with your key',
@@ -199,7 +200,7 @@ function genJavaScriptAxios(params: CodegenParams): string {
199
200
 
200
201
  function genTypescriptFetch(params: CodegenParams): string {
201
202
  const url = baseUrl(params.actionHost);
202
- const body = jsonBody(params.actionId, params.input);
203
+ const body = jsonBody(params.actionId, params.input, params.method);
203
204
  return [
204
205
  '// Set XAPI_KEY env var or replace with your key',
205
206
  `const resp: Response = await fetch("${url}", {`,
@@ -217,7 +218,7 @@ function genTypescriptFetch(params: CodegenParams): string {
217
218
 
218
219
  function genGo(params: CodegenParams): string {
219
220
  const url = baseUrl(params.actionHost);
220
- const body = jsonBody(params.actionId, params.input);
221
+ const body = jsonBody(params.actionId, params.input, params.method);
221
222
  const escaped = body.replace(/`/g, '` + "`" + `');
222
223
  return [
223
224
  '// Set XAPI_KEY env var or replace with your key',
@@ -278,8 +278,8 @@ export async function actionGet(args: string[], flags: Record<string, string>) {
278
278
  );
279
279
  }
280
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 });
281
+ const { method: _schemaMethod, ...cleanCodeInput } = buildDefaultInput(action.input ?? {});
282
+ const result = generateCode(flags.code, { actionId: id, input: cleanCodeInput, actionHost: cfg.actionHost, method: action.method });
283
283
  outputCode(result, flags);
284
284
  return;
285
285
  }
@@ -303,20 +303,24 @@ export async function actionCall(args: string[], flags: Record<string, string>)
303
303
  } catch {
304
304
  err('--input must be valid JSON');
305
305
  }
306
+ if (typeof input !== 'object' || input === null || Array.isArray(input)) {
307
+ err('--input must be a JSON object');
308
+ }
306
309
  }
307
- if (flags.method) {
308
- input = { ...input, method: flags.method.toUpperCase() };
309
- }
310
+ // method 作为独立参数传递,兼容 input 内的 method
311
+ const { method: inputMethod, ...cleanInput } = input;
312
+ const method = flags.method?.toUpperCase()
313
+ || (typeof inputMethod === 'string' ? inputMethod.toUpperCase() : undefined);
310
314
 
311
315
  if (flags.code) {
312
- const result = generateCode(flags.code, { actionId: id, input, actionHost: cfg.actionHost });
316
+ const result = generateCode(flags.code, { actionId: id, input: cleanInput, actionHost: cfg.actionHost, method });
313
317
  outputCode(result, flags);
314
318
  return;
315
319
  }
316
320
 
317
321
  requireApiKey(cfg);
318
322
  try {
319
- const res = await client.actionCall(id, input, cfg);
323
+ const res = await client.actionCall(id, cleanInput, cfg, method);
320
324
  output(res, flags.format as any);
321
325
  } catch (e: any) {
322
326
  err('call failed', e.message);
@@ -6,6 +6,25 @@ import { getConfig, saveConfig, showConfig } from '../config.ts';
6
6
  import { healthCheck } from '../client.ts';
7
7
  import { output, err } from '../format.ts';
8
8
 
9
+ export const CONFIG_HELP = `xapi config - Manage CLI configuration
10
+
11
+ USAGE
12
+ xapi config <command> [flags]
13
+
14
+ COMMANDS
15
+ show Show current config (host, apiKey path, etc.)
16
+ set apiKey=<key> Save API key to ~/.xapi/config.json
17
+ health Check backend connectivity (alias: xapi health)
18
+
19
+ FLAGS
20
+ --format json|pretty|table Output format
21
+
22
+ EXAMPLES
23
+ xapi config show
24
+ xapi config set apiKey=xapi_abc123
25
+ xapi config health
26
+ `;
27
+
9
28
  export async function configShow(args: string[], flags: Record<string, string>) {
10
29
  showConfig();
11
30
  }
@@ -2,34 +2,43 @@
2
2
  * register command: create a new user account
3
3
  *
4
4
  * POST /auth/register — no auth required
5
- * Returns apiKey (shown once), claimCode, claimUrl, tweetTemplate
5
+ * Returns apiKey (shown once), claimCode, referralCode, claimUrl, tweetTemplate
6
6
  * Automatically saves apiKey to ~/.xapi/config.json
7
+ *
8
+ * Optional referral code (please replace xapito to the actual referral code):
9
+ * xapi register --referral-code xapito
10
+ * xapi register --referralCode xapito # alias
11
+ * xapi register xapito # positional shorthand
7
12
  */
8
13
 
9
14
  import { XAPI_API_HOST, saveConfig, scheme } from '../config.ts';
10
15
  import { output, err } from '../format.ts';
11
16
 
12
- async function registerAccount() {
17
+ interface RegisterResponse {
18
+ apiKey: string;
19
+ claimCode: string;
20
+ referralCode?: string;
21
+ claimSessionId: string;
22
+ claimUrl: string;
23
+ tweetTemplate: string;
24
+ user: { id: string; accountType: string };
25
+ }
26
+
27
+ async function registerAccount(referralCode?: string): Promise<RegisterResponse> {
13
28
  const controller = new AbortController();
14
29
  const timer = setTimeout(() => controller.abort(), 15_000);
15
30
  try {
16
31
  const res = await fetch(`${scheme(XAPI_API_HOST)}://${XAPI_API_HOST}/api/auth/register`, {
17
32
  method: 'POST',
18
33
  headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify(referralCode ? { referralCode } : {}),
19
35
  signal: controller.signal,
20
36
  });
21
37
  if (!res.ok) {
22
38
  const text = await res.text();
23
39
  throw new Error(`HTTP ${res.status}: ${text.slice(0, 300)}`);
24
40
  }
25
- return res.json() as Promise<{
26
- apiKey: string;
27
- claimCode: string;
28
- claimSessionId: string;
29
- claimUrl: string;
30
- tweetTemplate: string;
31
- user: { id: string; accountType: string };
32
- }>;
41
+ return res.json() as Promise<RegisterResponse>;
33
42
  } finally {
34
43
  clearTimeout(timer);
35
44
  }
@@ -37,20 +46,29 @@ async function registerAccount() {
37
46
 
38
47
  export async function register(args: string[], flags: Record<string, string>) {
39
48
  try {
40
- const res = await registerAccount();
49
+ // 邀请码来源优先级:--referral-code > --referralCode > 第一个位置参数
50
+ const rawReferral =
51
+ flags['referral-code'] ?? flags['referralCode'] ?? args[0];
52
+ const referralCode =
53
+ typeof rawReferral === 'string' && rawReferral !== 'true' && rawReferral.length > 0
54
+ ? rawReferral
55
+ : undefined;
56
+
57
+ const res = await registerAccount(referralCode);
41
58
 
42
- // auto-save apiKey
43
59
  saveConfig({ apiKey: res.apiKey });
44
60
 
45
61
  output({
46
62
  apiKey: res.apiKey,
47
63
  user: res.user,
64
+ referralCode: res.referralCode,
48
65
  claim: {
49
66
  code: res.claimCode,
50
67
  sessionId: res.claimSessionId,
51
68
  url: res.claimUrl,
52
69
  },
53
70
  tweetTemplate: res.tweetTemplate,
71
+ ...(referralCode ? { referredBy: referralCode } : {}),
54
72
  note: 'apiKey saved to ~/.xapi/config.json',
55
73
  }, flags.format as any);
56
74
  } catch (e: any) {
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  *
13
13
  * xapi config show
14
14
  * xapi config set apiKey=<key>
15
+ * xapi config health
15
16
  * xapi health
16
17
  *
17
18
  * Global flags:
@@ -26,6 +27,7 @@
26
27
 
27
28
  import * as actionCmds from './commands/action.ts';
28
29
  import * as cfgCmds from './commands/config.ts';
30
+ const { CONFIG_HELP } = cfgCmds;
29
31
  import * as regCmds from './commands/register.ts';
30
32
  import * as topupCmds from './commands/topup.ts';
31
33
  import * as balanceCmds from './commands/balance.ts';
@@ -88,6 +90,7 @@ COMMANDS
88
90
  get <id> [--method GET|POST|...] Get action schema (filter by HTTP method)
89
91
  --code <target> Generate code snippet (curl, py, js, ts, go)
90
92
  call <id> --input '{"key":"val"}' Execute an action
93
+ --method GET|POST|... Override HTTP method
91
94
  --code <target> Generate code snippet instead of executing
92
95
  Variants: python.requests, python.httpx, javascript.fetch, javascript.axios
93
96
 
@@ -96,7 +99,8 @@ COMMANDS
96
99
  oauth unbind <binding-id> Remove an OAuth binding
97
100
  oauth providers List available OAuth providers
98
101
 
99
- register Create a new user account (apiKey saved automatically)
102
+ register [referral-code] Create a new user account (apiKey saved automatically)
103
+ --referral-code <code> Register with an inviter's referral code (also: --referralCode, or as positional arg)
100
104
  balance Show current account balance
101
105
  topup [--amount <usd>] [--method stripe|x402] Generate payment URL
102
106
 
@@ -104,6 +108,7 @@ COMMANDS
104
108
 
105
109
  config show Show current config
106
110
  config set apiKey=<key> Save API key to ~/.xapi/config.json
111
+ config health Check backend connectivity (alias: xapi health)
107
112
 
108
113
  GLOBAL FLAGS
109
114
  --format json|pretty|table Output format (default: json)
@@ -116,6 +121,8 @@ ENV VARS
116
121
 
117
122
  EXAMPLES
118
123
  xapi register
124
+ xapi register --referral-code xapito # register with an inviter's referral code
125
+ xapi register xapito # positional shorthand
119
126
  xapi list --format table
120
127
  xapi list --source capability
121
128
  xapi search twitter --source api
@@ -181,12 +188,17 @@ async function main() {
181
188
 
182
189
  // ── Config commands ──
183
190
  case 'config': {
191
+ if (flags.help || rest.length === 0) {
192
+ console.log(CONFIG_HELP);
193
+ process.exit(0);
194
+ }
184
195
  const [subCmd, ...subRest] = rest;
185
196
  switch (subCmd) {
186
197
  case 'show': return cfgCmds.configShow(subRest, flags);
187
198
  case 'set': return cfgCmds.configSet(subRest, flags);
199
+ case 'health': return cfgCmds.configHealth(subRest, flags);
188
200
  default:
189
- console.error(JSON.stringify({ error: `unknown config command: ${subCmd}` }));
201
+ console.error(JSON.stringify({ error: `unknown config command: ${subCmd}`, hint: 'valid commands: show, set, health' }));
190
202
  process.exit(1);
191
203
  }
192
204
  break;