xapi-to 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,59 +0,0 @@
1
- /**
2
- * register command: create a new user account
3
- *
4
- * POST /auth/register — no auth required
5
- * Returns apiKey (shown once), claimCode, claimUrl, tweetTemplate
6
- * Automatically saves apiKey to ~/.xapi/config.json
7
- */
8
-
9
- import { XAPI_API_HOST, saveConfig, scheme } from '../config.ts';
10
- import { output, err } from '../format.ts';
11
-
12
- async function registerAccount() {
13
- const controller = new AbortController();
14
- const timer = setTimeout(() => controller.abort(), 15_000);
15
- try {
16
- const res = await fetch(`${scheme(XAPI_API_HOST)}://${XAPI_API_HOST}/api/auth/register`, {
17
- method: 'POST',
18
- headers: { 'Content-Type': 'application/json' },
19
- signal: controller.signal,
20
- });
21
- if (!res.ok) {
22
- const text = await res.text();
23
- throw new Error(`HTTP ${res.status}: ${text.slice(0, 300)}`);
24
- }
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
- }>;
33
- } finally {
34
- clearTimeout(timer);
35
- }
36
- }
37
-
38
- export async function register(args: string[], flags: Record<string, string>) {
39
- try {
40
- const res = await registerAccount();
41
-
42
- // auto-save apiKey
43
- saveConfig({ apiKey: res.apiKey });
44
-
45
- output({
46
- apiKey: res.apiKey,
47
- user: res.user,
48
- claim: {
49
- code: res.claimCode,
50
- sessionId: res.claimSessionId,
51
- url: res.claimUrl,
52
- },
53
- tweetTemplate: res.tweetTemplate,
54
- note: 'apiKey saved to ~/.xapi/config.json',
55
- }, flags.format as any);
56
- } catch (e: any) {
57
- err('register failed', e.message);
58
- }
59
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * topup command
3
- *
4
- * Generates a payment URL pointing to the xapi frontend topup page.
5
- * All params are optional.
6
- *
7
- * Usage:
8
- * xapi topup [--amount <usd>] [--method stripe|x402]
9
- */
10
-
11
- import { getConfig } from '../config.ts';
12
- import { output } from '../format.ts';
13
-
14
- const TOPUP_BASE_URL = 'https://www.xapi.to/topup/payment';
15
-
16
- export async function topup(args: string[], flags: Record<string, string>) {
17
- const cfg = getConfig();
18
-
19
- const url = new URL(TOPUP_BASE_URL);
20
-
21
- if (cfg.apiKey) url.searchParams.set('apikey', cfg.apiKey);
22
- if (flags.method) url.searchParams.set('method', flags.method);
23
-
24
- const amountStr = flags.amount || args[0];
25
- if (amountStr) {
26
- const amountUsd = parseFloat(amountStr);
27
- if (!isNaN(amountUsd) && amountUsd > 0) {
28
- url.searchParams.set('amount', String(amountUsd));
29
- }
30
- }
31
-
32
- output({ url: url.toString() }, flags.format as any);
33
- }
package/src/config.ts DELETED
@@ -1,69 +0,0 @@
1
- /**
2
- * Config management
3
- * Only apiKey is user-configurable. Host is built-in.
4
- * Reads from env var XAPI_KEY or ~/.xapi/config.json
5
- */
6
-
7
- import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
8
- import { err } from './format.ts';
9
- import { homedir } from 'os';
10
- import { join } from 'path';
11
-
12
- export const XAPI_ACTION_HOST = process.env.XAPI_ACTION_HOST || 'action.xapi.to'; // action service (capabilities + APIs)
13
- export const XAPI_API_HOST = process.env.XAPI_API_HOST || 'api.xapi.to'; // auth + agent API
14
-
15
- /** Returns https:// for remote hosts, http:// for localhost */
16
- export function scheme(host: string): string {
17
- return host.startsWith('localhost') || host.startsWith('127.') ? 'http' : 'https';
18
- }
19
-
20
- export interface XapiConfig {
21
- actionHost: string;
22
- apiKey?: string;
23
- }
24
-
25
- const CONFIG_DIR = join(homedir(), '.xapi');
26
- const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
27
-
28
- function loadFileConfig(): { apiKey?: string } {
29
- if (!existsSync(CONFIG_FILE)) return {};
30
- try {
31
- return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
32
- } catch {
33
- return {};
34
- }
35
- }
36
-
37
- export function getConfig(): XapiConfig {
38
- const file = loadFileConfig();
39
- return {
40
- actionHost: XAPI_ACTION_HOST,
41
- apiKey: process.env.XAPI_KEY || process.env.XAPI_API_KEY || file.apiKey,
42
- };
43
- }
44
-
45
- export function requireApiKey(cfg: XapiConfig): void {
46
- if (!cfg.apiKey) {
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
- }
49
- }
50
-
51
- export function saveConfig(updates: { apiKey?: string }): void {
52
- const current = loadFileConfig();
53
- const merged = { ...current, ...updates };
54
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
55
- writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), { mode: 0o600 });
56
- }
57
-
58
- export function showConfig(): void {
59
- const cfg = getConfig();
60
- const file = loadFileConfig();
61
- console.log(JSON.stringify({
62
- actionHost: cfg.actionHost,
63
- apiKey: cfg.apiKey ? `${cfg.apiKey.slice(0, 8)}...` : undefined,
64
- source: {
65
- apiKey: (process.env.XAPI_KEY || process.env.XAPI_API_KEY) ? 'env' : file.apiKey ? 'file' : 'none',
66
- },
67
- configFile: CONFIG_FILE,
68
- }, null, 2));
69
- }
package/src/format.ts DELETED
@@ -1,61 +0,0 @@
1
- /**
2
- * Output formatting
3
- * Supports: json (default, machine-readable), pretty (human-readable), table
4
- */
5
-
6
- export type OutputFormat = 'json' | 'pretty' | 'table';
7
-
8
- export function getFormat(): OutputFormat {
9
- const f = process.env.XAPI_OUTPUT || 'json';
10
- if (f === 'pretty' || f === 'table') return f;
11
- return 'json';
12
- }
13
-
14
- export function output(data: unknown, format?: OutputFormat): void {
15
- const fmt = format || getFormat();
16
- if (fmt === 'json') {
17
- console.log(JSON.stringify(data));
18
- return;
19
- }
20
- if (fmt === 'pretty') {
21
- console.log(JSON.stringify(data, null, 2));
22
- return;
23
- }
24
- // table: try to render arrays of objects as a table
25
- if (fmt === 'table' && Array.isArray(data)) {
26
- printTable(data as Record<string, unknown>[]);
27
- return;
28
- }
29
- console.log(JSON.stringify(data, null, 2));
30
- }
31
-
32
- function printTable(rows: Record<string, unknown>[]): void {
33
- if (rows.length === 0) {
34
- console.log('(empty)');
35
- return;
36
- }
37
- const keys = Object.keys(rows[0]);
38
- const widths = keys.map(k =>
39
- Math.min(40, Math.max(k.length, ...rows.map(r => String(r[k] ?? '').length)))
40
- );
41
- const sep = widths.map(w => '-'.repeat(w)).join(' ');
42
- const header = keys.map((k, i) => k.padEnd(widths[i])).join(' ');
43
- console.log(header);
44
- console.log(sep);
45
- for (const row of rows) {
46
- const line = keys.map((k, i) => String(row[k] ?? '').slice(0, widths[i]).padEnd(widths[i])).join(' ');
47
- console.log(line);
48
- }
49
- }
50
-
51
- export function err(msg: string, detail?: unknown): never {
52
- if (process.stderr.isTTY) {
53
- console.error(`Error: ${msg}`);
54
- if (detail !== undefined) console.error(` ${detail}`);
55
- } else {
56
- const out: Record<string, unknown> = { error: msg };
57
- if (detail !== undefined) out.detail = detail;
58
- console.error(JSON.stringify(out));
59
- }
60
- process.exit(1);
61
- }
package/src/index.ts DELETED
@@ -1,205 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * xapi CLI - agent-friendly command-line interface for xapi
4
- *
5
- * Usage:
6
- * xapi list [--source capability|api] [--page N] [--page-size N] [--category X]
7
- * xapi search <query> [--source capability|api] [--category X] [--page N] [--page-size N]
8
- * xapi categories [--source capability|api]
9
- * xapi services [--page N] [--page-size N] [--category X]
10
- * xapi get <id> [--code curl|py|js|ts|go]
11
- * xapi call <id> --input '{"k":"v"}' [--code curl|py|js|ts|go]
12
- *
13
- * xapi config show
14
- * xapi config set apiKey=<key>
15
- * xapi health
16
- *
17
- * Global flags:
18
- * --format json|pretty|table output format (default: json)
19
- * --help show help
20
- *
21
- * Env vars:
22
- * XAPI_KEY API key
23
- * XAPI_ACTION_HOST Action service host (default: action.xapi.to)
24
- * XAPI_OUTPUT default output format (json|pretty|table)
25
- */
26
-
27
- import * as actionCmds from './commands/action.ts';
28
- import * as cfgCmds from './commands/config.ts';
29
- import * as regCmds from './commands/register.ts';
30
- import * as topupCmds from './commands/topup.ts';
31
- import * as balanceCmds from './commands/balance.ts';
32
- import * as oauthCmds from './commands/oauth.ts';
33
- const { OAUTH_HELP } = oauthCmds;
34
-
35
- // ── Argument parser ───────────────────────────────────────────────────────────
36
-
37
- interface ParsedArgs {
38
- positional: string[];
39
- flags: Record<string, string>;
40
- }
41
-
42
- function parseArgs(argv: string[]): ParsedArgs {
43
- const positional: string[] = [];
44
- const flags: Record<string, string> = {};
45
- let i = 0;
46
- while (i < argv.length) {
47
- const arg = argv[i];
48
- if (arg.startsWith('--')) {
49
- const key = arg.slice(2);
50
- const next = argv[i + 1];
51
- if (next && !next.startsWith('--')) {
52
- flags[key] = next;
53
- i += 2;
54
- } else {
55
- flags[key] = 'true';
56
- i++;
57
- }
58
- } else {
59
- positional.push(arg);
60
- i++;
61
- }
62
- }
63
- return { positional, flags };
64
- }
65
-
66
- // ── Help ──────────────────────────────────────────────────────────────────────
67
-
68
- const HELP = `xapi - agent-friendly CLI for xapi
69
-
70
- USAGE
71
- xapi <command> [args] [flags]
72
-
73
- COMMANDS
74
- list List all actions
75
- --source capability|api Filter by source type
76
- --page N --page-size N Pagination
77
- --category <name> Filter by category
78
- --service-id <id> Filter by service
79
- search <query> Search actions by keyword
80
- --source capability|api Filter by source type
81
- --category <name> Filter by category
82
- --page N --page-size N Pagination
83
- categories List all action categories
84
- --source capability|api Filter by source type
85
- services List all services
86
- --page N --page-size N Pagination
87
- --category <name> Filter by category
88
- get <id> [--method GET|POST|...] Get action schema (filter by HTTP method)
89
- --code <target> Generate code snippet (curl, py, js, ts, go)
90
- call <id> --input '{"key":"val"}' Execute an action
91
- --method GET|POST|... Override HTTP method
92
- --code <target> Generate code snippet instead of executing
93
- Variants: python.requests, python.httpx, javascript.fetch, javascript.axios
94
-
95
- oauth bind [--provider twitter] Bind Twitter OAuth to your API key
96
- oauth status List current OAuth bindings
97
- oauth unbind <binding-id> Remove an OAuth binding
98
- oauth providers List available OAuth providers
99
-
100
- register Create a new user account (apiKey saved automatically)
101
- balance Show current account balance
102
- topup [--amount <usd>] [--method stripe|x402] Generate payment URL
103
-
104
- health Check backend connectivity
105
-
106
- config show Show current config
107
- config set apiKey=<key> Save API key to ~/.xapi/config.json
108
-
109
- GLOBAL FLAGS
110
- --format json|pretty|table Output format (default: json)
111
- --help Show help (use with a command for details, e.g. xapi get --help)
112
-
113
- ENV VARS
114
- XAPI_KEY API key (header: XAPI-Key)
115
- XAPI_ACTION_HOST Action service host (default: action.xapi.to)
116
- XAPI_OUTPUT Default output format
117
-
118
- EXAMPLES
119
- xapi register
120
- xapi list --format table
121
- xapi list --source capability
122
- xapi search twitter --source api
123
- xapi get twitter.tweet_detail
124
- xapi get twitter.tweet_detail --code curl
125
- xapi get twitter.tweet_detail --code py --format pretty
126
- xapi call twitter.tweet_detail --input '{"tweet_id":"1234567890"}'
127
- xapi call twitter.tweet_detail --input '{"tweet_id":"1234567890"}' --code python
128
- xapi categories
129
- xapi services --format table
130
- xapi config set apiKey=xapi_abc123
131
- xapi health
132
- `;
133
-
134
- // ── Router ────────────────────────────────────────────────────────────────────
135
-
136
- async function main() {
137
- const { positional, flags } = parseArgs(process.argv.slice(2));
138
-
139
- if (positional.length === 0) {
140
- console.log(HELP);
141
- process.exit(0);
142
- }
143
-
144
- // inject format from flag into env so format.ts picks it up
145
- if (flags.format) process.env.XAPI_OUTPUT = flags.format;
146
-
147
- const [cmd, ...rest] = positional;
148
-
149
- switch (cmd) {
150
- // ── Action commands (top-level) ──
151
- case 'list': return actionCmds.actionList(rest, flags);
152
- case 'search': return actionCmds.actionSearch(rest, flags);
153
- case 'categories': return actionCmds.actionCategories(rest, flags);
154
- case 'services': return actionCmds.actionServices(rest, flags);
155
- case 'get': return actionCmds.actionGet(rest, flags);
156
- case 'call': return actionCmds.actionCall(rest, flags);
157
-
158
- // ── OAuth commands ──
159
- case 'oauth': {
160
- if (flags.help || rest.length === 0) {
161
- console.log(OAUTH_HELP);
162
- process.exit(0);
163
- }
164
- const [subCmd, ...subRest] = rest;
165
- switch (subCmd) {
166
- case 'bind': return oauthCmds.oauthBind(subRest, flags);
167
- case 'status': return oauthCmds.oauthStatus(subRest, flags);
168
- case 'unbind': return oauthCmds.oauthUnbind(subRest, flags);
169
- case 'providers': return oauthCmds.oauthProviders(subRest, flags);
170
- default:
171
- console.error(JSON.stringify({ error: `unknown oauth command: ${subCmd}`, hint: 'valid commands: bind, status, unbind, providers' }));
172
- process.exit(1);
173
- }
174
- break;
175
- }
176
-
177
- // ── Account commands ──
178
- case 'register': return regCmds.register(rest, flags);
179
- case 'balance': return balanceCmds.balance(rest, flags);
180
- case 'topup': return topupCmds.topup(rest, flags);
181
- case 'health': return cfgCmds.configHealth(rest, flags);
182
-
183
- // ── Config commands ──
184
- case 'config': {
185
- const [subCmd, ...subRest] = rest;
186
- switch (subCmd) {
187
- case 'show': return cfgCmds.configShow(subRest, flags);
188
- case 'set': return cfgCmds.configSet(subRest, flags);
189
- default:
190
- console.error(JSON.stringify({ error: `unknown config command: ${subCmd}` }));
191
- process.exit(1);
192
- }
193
- break;
194
- }
195
-
196
- default:
197
- console.error(JSON.stringify({ error: `unknown command: ${cmd}`, hint: 'run xapi --help' }));
198
- process.exit(1);
199
- }
200
- }
201
-
202
- main().catch(e => {
203
- console.error(JSON.stringify({ error: 'fatal', message: e.message }));
204
- process.exit(1);
205
- });