@bankr/cli 0.1.3 → 0.2.2
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/dist/cli.js +439 -133
- package/dist/commands/balances.d.ts +1 -0
- package/dist/commands/balances.js +7 -32
- package/dist/commands/llm.js +74 -18
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.js +139 -33
- package/dist/commands/portfolio.d.ts +9 -0
- package/dist/commands/portfolio.js +134 -0
- package/dist/commands/tokens.d.ts +7 -0
- package/dist/commands/tokens.js +60 -0
- package/dist/commands/transfer.d.ts +8 -0
- package/dist/commands/transfer.js +80 -0
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.js +116 -0
- package/dist/commands/x402.d.ts +69 -0
- package/dist/commands/x402.js +636 -0
- package/dist/lib/api.d.ts +59 -2
- package/dist/lib/api.js +34 -8
- package/dist/lib/chains.d.ts +5 -0
- package/dist/lib/chains.js +39 -0
- package/dist/lib/output.d.ts +4 -0
- package/dist/lib/output.js +19 -0
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { searchTokens } from "../lib/api.js";
|
|
2
|
+
import * as output from "../lib/output.js";
|
|
3
|
+
export async function tokensSearchCommand(query, opts) {
|
|
4
|
+
const spin = output.spinner(`Searching tokens for "${query}"...`);
|
|
5
|
+
try {
|
|
6
|
+
const chainId = opts.chain ? Number(opts.chain) : undefined;
|
|
7
|
+
const res = await searchTokens(query, chainId);
|
|
8
|
+
spin.succeed(`Found ${res.tokens.length} result(s)`);
|
|
9
|
+
if (res.tokens.length === 0) {
|
|
10
|
+
output.info("No tokens found. Try a different search query.");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
console.log();
|
|
14
|
+
for (const token of res.tokens) {
|
|
15
|
+
const price = token.priceUsd
|
|
16
|
+
? `$${token.priceUsd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 })}`
|
|
17
|
+
: output.fmt.dim("n/a");
|
|
18
|
+
console.log(` ${output.fmt.brandBold(`${token.symbol}`)} ${output.fmt.dim(token.name)}`);
|
|
19
|
+
console.log(` Address: ${token.address}`);
|
|
20
|
+
console.log(` Price: ${price}`);
|
|
21
|
+
console.log();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
spin.fail(`Search failed: ${err.message}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function tokensInfoCommand(address, opts) {
|
|
30
|
+
const spin = output.spinner(`Fetching token info for ${address}...`);
|
|
31
|
+
try {
|
|
32
|
+
const chainId = opts.chain ? Number(opts.chain) : undefined;
|
|
33
|
+
const res = await searchTokens(address, chainId);
|
|
34
|
+
spin.succeed("Token info loaded");
|
|
35
|
+
// Match by the requested address (search returns all tokens from matching pools)
|
|
36
|
+
const addressLower = address.toLowerCase();
|
|
37
|
+
const token = res.tokens.find((t) => t.address.toLowerCase() === addressLower) ??
|
|
38
|
+
res.tokens[0];
|
|
39
|
+
if (!token) {
|
|
40
|
+
output.error("Token not found. Check the address and try again.");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
console.log();
|
|
44
|
+
output.label("Name", token.name);
|
|
45
|
+
output.label("Symbol", token.symbol);
|
|
46
|
+
output.label("Address", token.address);
|
|
47
|
+
output.label("Decimals", String(token.decimals));
|
|
48
|
+
if (token.priceUsd) {
|
|
49
|
+
output.label("Price", `$${token.priceUsd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 })}`);
|
|
50
|
+
}
|
|
51
|
+
if (token.logoURI) {
|
|
52
|
+
output.label("Logo", token.logoURI);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
spin.fail(`Failed to fetch token info: ${err.message}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { transfer, searchTokens } from "../lib/api.js";
|
|
2
|
+
import * as output from "../lib/output.js";
|
|
3
|
+
import { NATIVE_SYMBOLS, CHAIN_IDS } from "../lib/chains.js";
|
|
4
|
+
import { isAddress } from "viem";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a token symbol or address to a contract address.
|
|
7
|
+
* If it's already a valid address, return as-is. Otherwise search by symbol.
|
|
8
|
+
*/
|
|
9
|
+
async function resolveToken(tokenInput, chain) {
|
|
10
|
+
if (isAddress(tokenInput)) {
|
|
11
|
+
return { address: tokenInput, symbol: tokenInput };
|
|
12
|
+
}
|
|
13
|
+
const chainId = CHAIN_IDS[chain];
|
|
14
|
+
const res = await searchTokens(tokenInput, chainId);
|
|
15
|
+
// Exact symbol match (case-insensitive) to avoid resolving to wrong token
|
|
16
|
+
const inputUpper = tokenInput.toUpperCase();
|
|
17
|
+
const exactMatch = res.tokens.find((t) => t.symbol.toUpperCase() === inputUpper);
|
|
18
|
+
if (!exactMatch) {
|
|
19
|
+
const available = res.tokens
|
|
20
|
+
.slice(0, 5)
|
|
21
|
+
.map((t) => `${t.symbol} (${t.address})`)
|
|
22
|
+
.join(", ");
|
|
23
|
+
throw new Error(`No exact match for "${tokenInput}" on ${chain}. Did you mean: ${available || "no results"}`);
|
|
24
|
+
}
|
|
25
|
+
return { address: exactMatch.address, symbol: exactMatch.symbol };
|
|
26
|
+
}
|
|
27
|
+
export async function transferCommand(opts) {
|
|
28
|
+
const isNative = opts.native ?? false;
|
|
29
|
+
const chain = opts.chain ?? "base";
|
|
30
|
+
if (!isNative && !opts.token) {
|
|
31
|
+
output.error("Token required for ERC20 transfers. Use --token <symbol or address> or --native.");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
let tokenAddress;
|
|
35
|
+
let tokenLabel;
|
|
36
|
+
if (isNative) {
|
|
37
|
+
tokenAddress = "0x0000000000000000000000000000000000000000";
|
|
38
|
+
tokenLabel = NATIVE_SYMBOLS[chain] ?? "ETH";
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const spin = output.spinner(`Resolving token "${opts.token}"...`);
|
|
42
|
+
try {
|
|
43
|
+
const resolved = await resolveToken(opts.token, chain);
|
|
44
|
+
tokenAddress = resolved.address;
|
|
45
|
+
tokenLabel = resolved.symbol;
|
|
46
|
+
if (tokenAddress !== opts.token) {
|
|
47
|
+
spin.succeed(`Resolved ${opts.token} → ${tokenLabel} (${tokenAddress})`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
spin.stop();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
spin.fail(err.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const chainLabel = chain !== "base" ? ` on ${chain}` : "";
|
|
59
|
+
const spin = output.spinner(`Transferring ${opts.amount} ${tokenLabel} to ${opts.to}${chainLabel}...`);
|
|
60
|
+
try {
|
|
61
|
+
const result = await transfer({
|
|
62
|
+
tokenAddress,
|
|
63
|
+
recipientAddress: opts.to,
|
|
64
|
+
amount: opts.amount,
|
|
65
|
+
isNativeToken: isNative,
|
|
66
|
+
chain,
|
|
67
|
+
});
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
spin.fail(`Transfer failed: ${result.error}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
spin.succeed(`Transfer successful`);
|
|
73
|
+
output.label("Tx Hash", result.txHash ?? "unknown");
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
spin.fail(`Transfer failed: ${err.message}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=transfer.js.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import * as output from "../lib/output.js";
|
|
4
|
+
const PACKAGE_NAME = "@bankr/cli";
|
|
5
|
+
/**
|
|
6
|
+
* Compare two semver version strings.
|
|
7
|
+
* Returns -1 if a < b, 0 if equal, 1 if a > b.
|
|
8
|
+
*/
|
|
9
|
+
function compareSemver(a, b) {
|
|
10
|
+
const partsA = a.split(".").map(Number);
|
|
11
|
+
const partsB = b.split(".").map(Number);
|
|
12
|
+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
13
|
+
const numA = partsA[i] ?? 0;
|
|
14
|
+
const numB = partsB[i] ?? 0;
|
|
15
|
+
if (numA < numB)
|
|
16
|
+
return -1;
|
|
17
|
+
if (numA > numB)
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
async function fetchLatestVersion() {
|
|
23
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
24
|
+
signal: AbortSignal.timeout(10000),
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
throw new Error(`Failed to check for updates (HTTP ${res.status})`);
|
|
28
|
+
}
|
|
29
|
+
const data = (await res.json());
|
|
30
|
+
return data.version;
|
|
31
|
+
}
|
|
32
|
+
function getCurrentVersion() {
|
|
33
|
+
const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
|
|
34
|
+
return pkg.version;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Detect which package manager installed the CLI globally.
|
|
38
|
+
* Falls back to npm if we can't determine.
|
|
39
|
+
*/
|
|
40
|
+
function detectPackageManager() {
|
|
41
|
+
try {
|
|
42
|
+
const execPath = process.argv[1] ?? "";
|
|
43
|
+
// Check if running under bun
|
|
44
|
+
if (execPath.includes("/.bun/") || "bun" in process.versions) {
|
|
45
|
+
return "bun";
|
|
46
|
+
}
|
|
47
|
+
// Check if running under pnpm
|
|
48
|
+
if (execPath.includes("/pnpm/") || execPath.includes("/.pnpm/")) {
|
|
49
|
+
return "pnpm";
|
|
50
|
+
}
|
|
51
|
+
// Check if running under yarn
|
|
52
|
+
if (execPath.includes("/yarn/") || execPath.includes("/.yarn/")) {
|
|
53
|
+
return "yarn";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// ignore detection errors
|
|
58
|
+
}
|
|
59
|
+
return "npm";
|
|
60
|
+
}
|
|
61
|
+
function getInstallCommand(pm) {
|
|
62
|
+
switch (pm) {
|
|
63
|
+
case "bun":
|
|
64
|
+
return `bun install -g ${PACKAGE_NAME}@latest`;
|
|
65
|
+
case "pnpm":
|
|
66
|
+
return `pnpm add -g ${PACKAGE_NAME}@latest`;
|
|
67
|
+
case "yarn":
|
|
68
|
+
return `yarn global add ${PACKAGE_NAME}@latest`;
|
|
69
|
+
default:
|
|
70
|
+
return `npm install -g ${PACKAGE_NAME}@latest`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function updateCommand(opts) {
|
|
74
|
+
const current = getCurrentVersion();
|
|
75
|
+
const spin = output.spinner("Checking for updates...");
|
|
76
|
+
let latest;
|
|
77
|
+
try {
|
|
78
|
+
latest = await fetchLatestVersion();
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
spin.fail();
|
|
82
|
+
output.error(err.message);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const cmp = compareSemver(current, latest);
|
|
86
|
+
if (cmp === 0) {
|
|
87
|
+
spin.succeed(`Already on the latest version (${current})`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (cmp > 0) {
|
|
91
|
+
spin.succeed(`You're ahead of latest (${current} > ${latest}). No update needed.`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
spin.succeed(`Update available: ${current} → ${latest}`);
|
|
95
|
+
if (opts.check) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const pm = detectPackageManager();
|
|
99
|
+
const cmd = getInstallCommand(pm);
|
|
100
|
+
output.blank();
|
|
101
|
+
output.info(`Updating via ${pm}...`);
|
|
102
|
+
output.dim(` $ ${cmd}`);
|
|
103
|
+
output.blank();
|
|
104
|
+
try {
|
|
105
|
+
execSync(cmd, { stdio: "inherit" });
|
|
106
|
+
output.blank();
|
|
107
|
+
output.success(`Updated to ${PACKAGE_NAME}@${latest}`);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
output.blank();
|
|
111
|
+
output.error("Update failed. Try running manually:");
|
|
112
|
+
output.dim(` $ ${cmd}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI commands for x402 endpoint hosting.
|
|
3
|
+
*
|
|
4
|
+
* bankr x402 init — Scaffold x402/ folder + bankr.x402.json
|
|
5
|
+
* bankr x402 add <name> — Add a new service
|
|
6
|
+
* bankr x402 configure <name> — Interactive pricing/description setup
|
|
7
|
+
* bankr x402 deploy [name] — Deploy all or a single service
|
|
8
|
+
* bankr x402 list — List deployed services
|
|
9
|
+
* bankr x402 logs <name> — View request logs (future)
|
|
10
|
+
* bankr x402 pause <name> — Pause a service
|
|
11
|
+
* bankr x402 resume <name> — Resume a service
|
|
12
|
+
* bankr x402 delete <name> — Delete a service
|
|
13
|
+
* bankr x402 revenue [name] — View earnings
|
|
14
|
+
* bankr x402 env set KEY=VALUE — Set encrypted env var
|
|
15
|
+
* bankr x402 env list — List env var names
|
|
16
|
+
* bankr x402 env unset KEY — Remove env var
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* bankr x402 init — Scaffold x402/ folder + bankr.x402.json
|
|
20
|
+
*/
|
|
21
|
+
export declare function x402InitCommand(): Promise<void>;
|
|
22
|
+
export declare function x402AddCommand(name: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* bankr x402 configure <name> — Interactive pricing/description setup
|
|
25
|
+
*/
|
|
26
|
+
export declare function x402ConfigureCommand(name: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* bankr x402 deploy [name] — Deploy services
|
|
29
|
+
*
|
|
30
|
+
* This bundles the handler(s) with Bun and uploads via the Bankr API.
|
|
31
|
+
* If no name is provided, deploys all services in x402/.
|
|
32
|
+
*/
|
|
33
|
+
export declare function x402DeployCommand(name?: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* bankr x402 list — List deployed services
|
|
36
|
+
*/
|
|
37
|
+
export declare function x402ListCommand(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* bankr x402 pause/resume <name>
|
|
40
|
+
*/
|
|
41
|
+
export declare function x402PauseResumeCommand(name: string, action: "pause" | "resume"): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* bankr x402 delete <name>
|
|
44
|
+
*/
|
|
45
|
+
export declare function x402DeleteCommand(name: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* bankr x402 revenue [name]
|
|
48
|
+
*/
|
|
49
|
+
export declare function x402RevenueCommand(name?: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* bankr x402 env set KEY=VALUE
|
|
52
|
+
*/
|
|
53
|
+
export declare function x402EnvSetCommand(keyValue: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* bankr x402 env list
|
|
56
|
+
*/
|
|
57
|
+
export declare function x402EnvListCommand(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* bankr x402 env unset KEY
|
|
60
|
+
*/
|
|
61
|
+
export declare function x402EnvUnsetCommand(key: string): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* bankr x402 search <query> — Search the x402 service marketplace
|
|
64
|
+
*
|
|
65
|
+
* Public — no auth required. Uses vector search + keyword matching
|
|
66
|
+
* to find relevant paid API services.
|
|
67
|
+
*/
|
|
68
|
+
export declare function x402SearchCommand(queryParts: string[]): Promise<void>;
|
|
69
|
+
//# sourceMappingURL=x402.d.ts.map
|