@bfun-bot/cli 1.0.7 → 1.0.8
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 +6 -6
- package/dist/__tests__/commands/balances.test.d.ts +1 -0
- package/dist/__tests__/commands/balances.test.js +97 -0
- package/dist/__tests__/commands/fees.test.d.ts +1 -0
- package/dist/__tests__/commands/fees.test.js +220 -0
- package/dist/__tests__/commands/quota.test.d.ts +1 -0
- package/dist/__tests__/commands/quota.test.js +104 -0
- package/dist/__tests__/lib/api.test.d.ts +1 -0
- package/dist/__tests__/lib/api.test.js +77 -0
- package/dist/__tests__/lib/display.test.d.ts +1 -0
- package/dist/__tests__/lib/display.test.js +210 -0
- package/dist/commands/balances.js +16 -16
- package/dist/commands/fees.d.ts +3 -3
- package/dist/commands/fees.js +71 -27
- package/dist/commands/llm.js +1 -2
- package/dist/commands/quota.js +10 -7
- package/dist/commands/token.js +5 -5
- package/dist/index.js +0 -2
- package/dist/lib/display.d.ts +6 -0
- package/dist/lib/display.js +59 -7
- package/dist/types.d.ts +2 -0
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ bfunbot login
|
|
|
17
17
|
# Create a token
|
|
18
18
|
bfunbot token create --name "My Token" --symbol MYT
|
|
19
19
|
|
|
20
|
-
# Check balances
|
|
20
|
+
# Check balances (with live USD values)
|
|
21
21
|
bfunbot balances
|
|
22
22
|
|
|
23
23
|
# Check fee earnings
|
|
@@ -35,15 +35,15 @@ bfunbot fees
|
|
|
35
35
|
| `bfunbot token info <address>` | Get token details |
|
|
36
36
|
| `bfunbot tokens created` | List tokens you've created |
|
|
37
37
|
| `bfunbot status <jobId>` | Check job status |
|
|
38
|
-
| `bfunbot balances` | Show wallet balances (BSC) |
|
|
39
|
-
| `bfunbot quota` | Show daily
|
|
40
|
-
| `bfunbot fees` | Check fee earnings summary |
|
|
41
|
-
| `bfunbot fees
|
|
38
|
+
| `bfunbot balances` | Show wallet balances (BSC) with live USD values |
|
|
39
|
+
| `bfunbot quota` | Show daily token creation quota |
|
|
40
|
+
| `bfunbot fees` | Check total fee earnings summary (with live USD) |
|
|
41
|
+
| `bfunbot fees breakdown` | Per-platform fee breakdown (flap + fourmeme) |
|
|
42
|
+
| `bfunbot fees --platform <p> --token <addr>` | Per-token fee detail |
|
|
42
43
|
| `bfunbot llm models` | List available LLM models |
|
|
43
44
|
| `bfunbot llm credits` | Check BFun.bot Credits balance |
|
|
44
45
|
| `bfunbot llm reload` | Reload credits from trading wallet |
|
|
45
46
|
| `bfunbot llm setup openclaw` | Configure OpenClaw integration |
|
|
46
|
-
| `bfunbot skills` | List agent capabilities |
|
|
47
47
|
| `bfunbot config get` | Show current config |
|
|
48
48
|
| `bfunbot config set` | Set a config value |
|
|
49
49
|
| `bfunbot about` | About BFunBot CLI |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for bfunbot balances command.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
vi.mock('../../lib/api.js', () => ({
|
|
7
|
+
agent: {
|
|
8
|
+
walletBalance: vi.fn(),
|
|
9
|
+
},
|
|
10
|
+
handleApiError: vi.fn((err) => { throw err; }),
|
|
11
|
+
}));
|
|
12
|
+
import { agent } from '../../lib/api.js';
|
|
13
|
+
import { registerBalances } from '../../commands/balances.js';
|
|
14
|
+
const MOCK_BALANCE = {
|
|
15
|
+
evm_main: {
|
|
16
|
+
address: '0xB305ce8D75498E4D0c2963089C4894b3589F7777',
|
|
17
|
+
balance_bnb: '0.412',
|
|
18
|
+
balance_usdt_bsc: '100.5',
|
|
19
|
+
bnb_error: null,
|
|
20
|
+
usdt_bsc_error: null,
|
|
21
|
+
},
|
|
22
|
+
evm_trading: {
|
|
23
|
+
address: '0xDeaD000000000000000042069420694206942069',
|
|
24
|
+
balance_bnb: '0.050',
|
|
25
|
+
balance_usdt_bsc: '0',
|
|
26
|
+
bnb_error: null,
|
|
27
|
+
usdt_bsc_error: null,
|
|
28
|
+
},
|
|
29
|
+
bnb_price_usd: 600,
|
|
30
|
+
};
|
|
31
|
+
function buildProgram() {
|
|
32
|
+
const program = new Command();
|
|
33
|
+
program.option('--json');
|
|
34
|
+
registerBalances(program);
|
|
35
|
+
return program;
|
|
36
|
+
}
|
|
37
|
+
describe('bfunbot balances', () => {
|
|
38
|
+
let output;
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
output = [];
|
|
41
|
+
vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
42
|
+
output.push(args.map(String).join(' '));
|
|
43
|
+
});
|
|
44
|
+
vi.mocked(agent.walletBalance).mockResolvedValue(MOCK_BALANCE);
|
|
45
|
+
});
|
|
46
|
+
afterEach(() => vi.restoreAllMocks());
|
|
47
|
+
it('shows EVM Main wallet label', async () => {
|
|
48
|
+
const program = buildProgram();
|
|
49
|
+
await program.parseAsync(['node', 'bfunbot', 'balances']);
|
|
50
|
+
expect(output.join('\n')).toContain('EVM Main');
|
|
51
|
+
});
|
|
52
|
+
it('shows EVM Trading wallet label', async () => {
|
|
53
|
+
const program = buildProgram();
|
|
54
|
+
await program.parseAsync(['node', 'bfunbot', 'balances']);
|
|
55
|
+
expect(output.join('\n')).toContain('EVM Trading');
|
|
56
|
+
});
|
|
57
|
+
it('shows BNB balance', async () => {
|
|
58
|
+
const program = buildProgram();
|
|
59
|
+
await program.parseAsync(['node', 'bfunbot', 'balances']);
|
|
60
|
+
expect(output.join('\n')).toContain('0.412');
|
|
61
|
+
});
|
|
62
|
+
it('shows USD equivalent alongside BNB', async () => {
|
|
63
|
+
const program = buildProgram();
|
|
64
|
+
await program.parseAsync(['node', 'bfunbot', 'balances']);
|
|
65
|
+
expect(output.join('\n')).toContain('$');
|
|
66
|
+
});
|
|
67
|
+
it('shows USDT balance when non-zero', async () => {
|
|
68
|
+
const program = buildProgram();
|
|
69
|
+
await program.parseAsync(['node', 'bfunbot', 'balances']);
|
|
70
|
+
expect(output.join('\n')).toContain('100.5');
|
|
71
|
+
});
|
|
72
|
+
it('shows error indicator when bnb_error is set', async () => {
|
|
73
|
+
vi.mocked(agent.walletBalance).mockResolvedValue({
|
|
74
|
+
...MOCK_BALANCE,
|
|
75
|
+
evm_main: { ...MOCK_BALANCE.evm_main, bnb_error: 'rpc_unavailable' },
|
|
76
|
+
});
|
|
77
|
+
const stderrOutput = [];
|
|
78
|
+
vi.spyOn(console, 'error').mockImplementation((...args) => stderrOutput.push(args.map(String).join(' ')));
|
|
79
|
+
const program = buildProgram();
|
|
80
|
+
await program.parseAsync(['node', 'bfunbot', 'balances']);
|
|
81
|
+
expect(output.join('\n')).toContain('error');
|
|
82
|
+
});
|
|
83
|
+
it('shows truncated address', async () => {
|
|
84
|
+
const program = buildProgram();
|
|
85
|
+
await program.parseAsync(['node', 'bfunbot', 'balances']);
|
|
86
|
+
expect(output.join('\n')).toContain('...');
|
|
87
|
+
});
|
|
88
|
+
it('outputs JSON when --json flag is passed', async () => {
|
|
89
|
+
const program = buildProgram();
|
|
90
|
+
await program.parseAsync(['node', 'bfunbot', '--json', 'balances']);
|
|
91
|
+
const jsonLine = output.find(l => l.trim().startsWith('{'));
|
|
92
|
+
expect(jsonLine).toBeDefined();
|
|
93
|
+
const parsed = JSON.parse(jsonLine);
|
|
94
|
+
expect(parsed.evm_main.balance_bnb).toBe('0.412');
|
|
95
|
+
expect(parsed.bnb_price_usd).toBe(600);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for bfunbot fees command.
|
|
3
|
+
*
|
|
4
|
+
* Mocks the agent API and asserts stdout output contains expected values.
|
|
5
|
+
* Does NOT assert exact formatting strings — those are covered in display.test.ts.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
// Mock the API module before importing the command
|
|
10
|
+
vi.mock('../../lib/api.js', () => ({
|
|
11
|
+
agent: {
|
|
12
|
+
feesSummary: vi.fn(),
|
|
13
|
+
feesEarnings: vi.fn(),
|
|
14
|
+
feesToken: vi.fn(),
|
|
15
|
+
},
|
|
16
|
+
handleApiError: vi.fn((err) => { throw err; }),
|
|
17
|
+
}));
|
|
18
|
+
import { agent } from '../../lib/api.js';
|
|
19
|
+
import { registerFees } from '../../commands/fees.js';
|
|
20
|
+
const MOCK_SUMMARY = {
|
|
21
|
+
bsc: { token_count: 3, total_earned_bnb: 0.00412 },
|
|
22
|
+
bnb_price_usd: 600,
|
|
23
|
+
};
|
|
24
|
+
const MOCK_EARNINGS = {
|
|
25
|
+
chain: 'bsc',
|
|
26
|
+
flap: { total_earned_bnb: 0.00300, earning_token_count: 2 },
|
|
27
|
+
fourmeme: { total_earned_bnb: 0.00112, earning_token_count: 1 },
|
|
28
|
+
};
|
|
29
|
+
const MOCK_TOKEN_BSC = {
|
|
30
|
+
token_address: '0xB305ce8D75498E4D0c2963089C4894b3589F7777',
|
|
31
|
+
token_name: 'TestToken',
|
|
32
|
+
token_symbol: 'TEST',
|
|
33
|
+
platform: 'flap',
|
|
34
|
+
chain: 'bsc',
|
|
35
|
+
earned_bnb: 0.00300,
|
|
36
|
+
};
|
|
37
|
+
function buildProgram() {
|
|
38
|
+
const program = new Command();
|
|
39
|
+
program.option('--json');
|
|
40
|
+
registerFees(program);
|
|
41
|
+
return program;
|
|
42
|
+
}
|
|
43
|
+
describe('bfunbot fees (summary)', () => {
|
|
44
|
+
let output;
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
output = [];
|
|
47
|
+
vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
48
|
+
output.push(args.map(String).join(' '));
|
|
49
|
+
});
|
|
50
|
+
vi.mocked(agent.feesSummary).mockResolvedValue(MOCK_SUMMARY);
|
|
51
|
+
});
|
|
52
|
+
afterEach(() => vi.restoreAllMocks());
|
|
53
|
+
it('shows total earned BNB', async () => {
|
|
54
|
+
const program = buildProgram();
|
|
55
|
+
await program.parseAsync(['node', 'bfunbot', 'fees']);
|
|
56
|
+
const all = output.join('\n');
|
|
57
|
+
expect(all).toContain('BNB');
|
|
58
|
+
expect(all).toContain('0.00412');
|
|
59
|
+
});
|
|
60
|
+
it('shows USD value when bnb_price_usd is provided', async () => {
|
|
61
|
+
const program = buildProgram();
|
|
62
|
+
await program.parseAsync(['node', 'bfunbot', 'fees']);
|
|
63
|
+
const all = output.join('\n');
|
|
64
|
+
expect(all).toContain('$');
|
|
65
|
+
});
|
|
66
|
+
it('shows hint to run fees breakdown', async () => {
|
|
67
|
+
const program = buildProgram();
|
|
68
|
+
await program.parseAsync(['node', 'bfunbot', 'fees']);
|
|
69
|
+
expect(output.join('\n')).toContain('breakdown');
|
|
70
|
+
});
|
|
71
|
+
it('outputs JSON when --json flag is passed', async () => {
|
|
72
|
+
const program = buildProgram();
|
|
73
|
+
await program.parseAsync(['node', 'bfunbot', '--json', 'fees']);
|
|
74
|
+
const jsonLine = output.find(l => l.trim().startsWith('{'));
|
|
75
|
+
expect(jsonLine).toBeDefined();
|
|
76
|
+
const parsed = JSON.parse(jsonLine);
|
|
77
|
+
expect(parsed.bsc.total_earned_bnb).toBe(0.00412);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('bfunbot fees breakdown', () => {
|
|
81
|
+
let output;
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
output = [];
|
|
84
|
+
vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
85
|
+
output.push(args.map(String).join(' '));
|
|
86
|
+
});
|
|
87
|
+
vi.mocked(agent.feesEarnings).mockResolvedValue(MOCK_EARNINGS);
|
|
88
|
+
vi.mocked(agent.feesSummary).mockResolvedValue(MOCK_SUMMARY);
|
|
89
|
+
});
|
|
90
|
+
afterEach(() => vi.restoreAllMocks());
|
|
91
|
+
it('still works when feesSummary fails (graceful fallback — no USD)', async () => {
|
|
92
|
+
vi.mocked(agent.feesSummary).mockRejectedValue(new Error('summary unavailable'));
|
|
93
|
+
const program = buildProgram();
|
|
94
|
+
await program.parseAsync(['node', 'bfunbot', 'fees', 'breakdown']);
|
|
95
|
+
const all = output.join('\n');
|
|
96
|
+
expect(all).toContain('flap');
|
|
97
|
+
expect(all).toContain('fourmeme');
|
|
98
|
+
});
|
|
99
|
+
it('does not call feesSummary when --json flag is used', async () => {
|
|
100
|
+
vi.mocked(agent.feesSummary).mockClear();
|
|
101
|
+
const program = buildProgram();
|
|
102
|
+
await program.parseAsync(['node', 'bfunbot', '--json', 'fees', 'breakdown']);
|
|
103
|
+
expect(agent.feesSummary).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
it('shows flap and fourmeme platforms', async () => {
|
|
106
|
+
const program = buildProgram();
|
|
107
|
+
await program.parseAsync(['node', 'bfunbot', 'fees', 'breakdown']);
|
|
108
|
+
const all = output.join('\n');
|
|
109
|
+
expect(all).toContain('flap');
|
|
110
|
+
expect(all).toContain('fourmeme');
|
|
111
|
+
});
|
|
112
|
+
it('shows BNB earned for both platforms', async () => {
|
|
113
|
+
const program = buildProgram();
|
|
114
|
+
await program.parseAsync(['node', 'bfunbot', 'fees', 'breakdown']);
|
|
115
|
+
const all = output.join('\n');
|
|
116
|
+
expect(all).toContain('0.003');
|
|
117
|
+
expect(all).toContain('0.00112');
|
|
118
|
+
});
|
|
119
|
+
it('shows USD column', async () => {
|
|
120
|
+
const program = buildProgram();
|
|
121
|
+
await program.parseAsync(['node', 'bfunbot', 'fees', 'breakdown']);
|
|
122
|
+
expect(output.join('\n')).toContain('$');
|
|
123
|
+
});
|
|
124
|
+
it('shows token counts', async () => {
|
|
125
|
+
const program = buildProgram();
|
|
126
|
+
await program.parseAsync(['node', 'bfunbot', 'fees', 'breakdown']);
|
|
127
|
+
const all = output.join('\n');
|
|
128
|
+
expect(all).toContain('2');
|
|
129
|
+
expect(all).toContain('1');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe('bfunbot fees --token --platform', () => {
|
|
133
|
+
let output;
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
output = [];
|
|
136
|
+
vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
137
|
+
output.push(args.map(String).join(' '));
|
|
138
|
+
});
|
|
139
|
+
vi.mocked(agent.feesToken).mockResolvedValue(MOCK_TOKEN_BSC);
|
|
140
|
+
vi.mocked(agent.feesSummary).mockResolvedValue(MOCK_SUMMARY);
|
|
141
|
+
});
|
|
142
|
+
afterEach(() => vi.restoreAllMocks());
|
|
143
|
+
it('shows token name and symbol', async () => {
|
|
144
|
+
const program = buildProgram();
|
|
145
|
+
await program.parseAsync([
|
|
146
|
+
'node', 'bfunbot', 'fees',
|
|
147
|
+
'--platform', 'flap',
|
|
148
|
+
'--token', '0xB305ce8D75498E4D0c2963089C4894b3589F7777',
|
|
149
|
+
]);
|
|
150
|
+
const all = output.join('\n');
|
|
151
|
+
expect(all).toContain('TestToken');
|
|
152
|
+
expect(all).toContain('TEST');
|
|
153
|
+
});
|
|
154
|
+
it('shows earned BNB with USD', async () => {
|
|
155
|
+
const program = buildProgram();
|
|
156
|
+
await program.parseAsync([
|
|
157
|
+
'node', 'bfunbot', 'fees',
|
|
158
|
+
'--platform', 'flap',
|
|
159
|
+
'--token', '0xB305ce8D75498E4D0c2963089C4894b3589F7777',
|
|
160
|
+
]);
|
|
161
|
+
const all = output.join('\n');
|
|
162
|
+
expect(all).toContain('BNB');
|
|
163
|
+
expect(all).toContain('$');
|
|
164
|
+
});
|
|
165
|
+
it('errors if --token given without --platform', async () => {
|
|
166
|
+
const stderrOutput = [];
|
|
167
|
+
vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
168
|
+
stderrOutput.push(args.map(String).join(' '));
|
|
169
|
+
});
|
|
170
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {
|
|
171
|
+
throw new Error('process.exit(1)');
|
|
172
|
+
}));
|
|
173
|
+
const program = buildProgram();
|
|
174
|
+
await expect(program.parseAsync(['node', 'bfunbot', 'fees', '--token', '0xabc123'])).rejects.toThrow('process.exit(1)');
|
|
175
|
+
expect(stderrOutput[0]).toContain('--platform');
|
|
176
|
+
});
|
|
177
|
+
it('still works when feesSummary fails for per-token (graceful fallback)', async () => {
|
|
178
|
+
vi.mocked(agent.feesSummary).mockRejectedValue(new Error('summary unavailable'));
|
|
179
|
+
const program = buildProgram();
|
|
180
|
+
await program.parseAsync([
|
|
181
|
+
'node', 'bfunbot', 'fees',
|
|
182
|
+
'--platform', 'flap',
|
|
183
|
+
'--token', '0xB305ce8D75498E4D0c2963089C4894b3589F7777',
|
|
184
|
+
]);
|
|
185
|
+
const all = output.join('\n');
|
|
186
|
+
expect(all).toContain('TestToken');
|
|
187
|
+
expect(all).toContain('BNB');
|
|
188
|
+
});
|
|
189
|
+
it('does not call feesSummary for --token when --json flag is used', async () => {
|
|
190
|
+
vi.mocked(agent.feesSummary).mockClear();
|
|
191
|
+
const program = buildProgram();
|
|
192
|
+
await program.parseAsync([
|
|
193
|
+
'node', 'bfunbot', '--json', 'fees',
|
|
194
|
+
'--platform', 'flap',
|
|
195
|
+
'--token', '0xB305ce8D75498E4D0c2963089C4894b3589F7777',
|
|
196
|
+
]);
|
|
197
|
+
expect(agent.feesSummary).not.toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
it('routes --platform alone to breakdown with deprecation hint', async () => {
|
|
200
|
+
vi.mocked(agent.feesEarnings).mockResolvedValue(MOCK_EARNINGS);
|
|
201
|
+
const program = buildProgram();
|
|
202
|
+
await program.parseAsync(['node', 'bfunbot', 'fees', '--platform', 'flap']);
|
|
203
|
+
const all = output.join('\n');
|
|
204
|
+
expect(all).toContain('flap');
|
|
205
|
+
expect(all).toContain('fourmeme');
|
|
206
|
+
expect(all).toContain('fees breakdown');
|
|
207
|
+
});
|
|
208
|
+
it('errors if invalid platform given', async () => {
|
|
209
|
+
const stderrOutput = [];
|
|
210
|
+
vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
211
|
+
stderrOutput.push(args.map(String).join(' '));
|
|
212
|
+
});
|
|
213
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {
|
|
214
|
+
throw new Error('process.exit(1)');
|
|
215
|
+
}));
|
|
216
|
+
const program = buildProgram();
|
|
217
|
+
await expect(program.parseAsync(['node', 'bfunbot', 'fees', '--platform', 'invalid', '--token', '0xabc'])).rejects.toThrow('process.exit(1)');
|
|
218
|
+
expect(stderrOutput[0]).toContain('Platform must be one of');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for bfunbot quota command.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
vi.mock('../../lib/api.js', () => ({
|
|
7
|
+
agent: {
|
|
8
|
+
quota: vi.fn(),
|
|
9
|
+
},
|
|
10
|
+
handleApiError: vi.fn((err) => { throw err; }),
|
|
11
|
+
}));
|
|
12
|
+
import { agent } from '../../lib/api.js';
|
|
13
|
+
import { registerQuota } from '../../commands/quota.js';
|
|
14
|
+
const MOCK_QUOTA = {
|
|
15
|
+
chains: [
|
|
16
|
+
{
|
|
17
|
+
chain: 'bsc',
|
|
18
|
+
free_used_today: 2,
|
|
19
|
+
free_limit: 5,
|
|
20
|
+
sponsored_remaining: 3,
|
|
21
|
+
can_create_paid: true,
|
|
22
|
+
trading_wallet_balance: '0.050000 BNB',
|
|
23
|
+
trading_wallet_address: '0xDeaD000000000000000042069420694206942069',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
function buildProgram() {
|
|
28
|
+
const program = new Command();
|
|
29
|
+
program.option('--json');
|
|
30
|
+
registerQuota(program);
|
|
31
|
+
return program;
|
|
32
|
+
}
|
|
33
|
+
describe('bfunbot quota', () => {
|
|
34
|
+
let output;
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
output = [];
|
|
37
|
+
vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
38
|
+
output.push(args.map(String).join(' '));
|
|
39
|
+
});
|
|
40
|
+
vi.mocked(agent.quota).mockResolvedValue(MOCK_QUOTA);
|
|
41
|
+
});
|
|
42
|
+
afterEach(() => vi.restoreAllMocks());
|
|
43
|
+
it('shows chain name', async () => {
|
|
44
|
+
const program = buildProgram();
|
|
45
|
+
await program.parseAsync(['node', 'bfunbot', 'quota']);
|
|
46
|
+
expect(output.join('\n')).toContain('Bsc');
|
|
47
|
+
});
|
|
48
|
+
it('shows free used and limit', async () => {
|
|
49
|
+
const program = buildProgram();
|
|
50
|
+
await program.parseAsync(['node', 'bfunbot', 'quota']);
|
|
51
|
+
const all = output.join('\n');
|
|
52
|
+
expect(all).toContain('2');
|
|
53
|
+
expect(all).toContain('5');
|
|
54
|
+
});
|
|
55
|
+
it('shows remaining quota count', async () => {
|
|
56
|
+
const program = buildProgram();
|
|
57
|
+
await program.parseAsync(['node', 'bfunbot', 'quota']);
|
|
58
|
+
expect(output.join('\n')).toContain('3');
|
|
59
|
+
});
|
|
60
|
+
it('shows Yes for can_create_paid=true', async () => {
|
|
61
|
+
const program = buildProgram();
|
|
62
|
+
await program.parseAsync(['node', 'bfunbot', 'quota']);
|
|
63
|
+
expect(output.join('\n')).toContain('Yes');
|
|
64
|
+
});
|
|
65
|
+
it('shows No only when both free remaining and can_create_paid are false/zero', async () => {
|
|
66
|
+
vi.mocked(agent.quota).mockResolvedValue({
|
|
67
|
+
chains: [{ ...MOCK_QUOTA.chains[0], can_create_paid: false, sponsored_remaining: 0 }],
|
|
68
|
+
});
|
|
69
|
+
const program = buildProgram();
|
|
70
|
+
await program.parseAsync(['node', 'bfunbot', 'quota']);
|
|
71
|
+
expect(output.join('\n')).toContain('No');
|
|
72
|
+
});
|
|
73
|
+
it('shows Yes when free remaining > 0 even if can_create_paid is false', async () => {
|
|
74
|
+
vi.mocked(agent.quota).mockResolvedValue({
|
|
75
|
+
chains: [{ ...MOCK_QUOTA.chains[0], can_create_paid: false, sponsored_remaining: 9 }],
|
|
76
|
+
});
|
|
77
|
+
const program = buildProgram();
|
|
78
|
+
await program.parseAsync(['node', 'bfunbot', 'quota']);
|
|
79
|
+
expect(output.join('\n')).toContain('Yes');
|
|
80
|
+
});
|
|
81
|
+
it('table columns align (ANSI-aware)', async () => {
|
|
82
|
+
const program = buildProgram();
|
|
83
|
+
await program.parseAsync(['node', 'bfunbot', 'quota']);
|
|
84
|
+
const strip = (s) => s.replace(/\x1B\[[0-9;]*m/g, '');
|
|
85
|
+
// Find the header row (contains all column names)
|
|
86
|
+
const headerIdx = output.findIndex(l => strip(l).includes('Chain') && strip(l).includes('Used Today'));
|
|
87
|
+
expect(headerIdx).toBeGreaterThan(-1);
|
|
88
|
+
const headerLen = strip(output[headerIdx]).length;
|
|
89
|
+
// Data rows immediately follow the separator line — check they match header width
|
|
90
|
+
const separatorIdx = output.findIndex((l, i) => i > headerIdx && strip(l).includes('─'));
|
|
91
|
+
const dataRows = output.slice(separatorIdx + 1).filter(l => l.trim() !== '');
|
|
92
|
+
for (const row of dataRows) {
|
|
93
|
+
expect(strip(row).length).toBe(headerLen);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
it('outputs JSON when --json flag is passed', async () => {
|
|
97
|
+
const program = buildProgram();
|
|
98
|
+
await program.parseAsync(['node', 'bfunbot', '--json', 'quota']);
|
|
99
|
+
const jsonLine = output.find(l => l.trim().startsWith('{'));
|
|
100
|
+
expect(jsonLine).toBeDefined();
|
|
101
|
+
const parsed = JSON.parse(jsonLine);
|
|
102
|
+
expect(parsed.chains[0].chain).toBe('bsc');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/lib/api.ts
|
|
3
|
+
*
|
|
4
|
+
* Covers: ApiError construction, handleApiError friendly messages,
|
|
5
|
+
* request() — correct headers, error mapping, network failure.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import { ApiError, handleApiError } from '../../lib/api.js';
|
|
9
|
+
// ─── ApiError ────────────────────────────────────────────────────────────────
|
|
10
|
+
describe('ApiError', () => {
|
|
11
|
+
it('constructs with status code and message', () => {
|
|
12
|
+
const err = new ApiError(404, 'Not Found', { detail: 'Token not found' });
|
|
13
|
+
expect(err.statusCode).toBe(404);
|
|
14
|
+
expect(err.message).toBe('Token not found');
|
|
15
|
+
expect(err.name).toBe('ApiError');
|
|
16
|
+
});
|
|
17
|
+
it('falls back to statusText when no body detail', () => {
|
|
18
|
+
const err = new ApiError(500, 'Internal Server Error');
|
|
19
|
+
expect(err.message).toBe('Internal Server Error');
|
|
20
|
+
});
|
|
21
|
+
it('uses body.message if detail is absent', () => {
|
|
22
|
+
const err = new ApiError(400, 'Bad Request', { message: 'Bad input' });
|
|
23
|
+
expect(err.message).toBe('Bad input');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
// ─── handleApiError friendly messages ────────────────────────────────────────
|
|
27
|
+
describe('handleApiError', () => {
|
|
28
|
+
let stderrOutput;
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
stderrOutput = [];
|
|
31
|
+
vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
32
|
+
stderrOutput.push(args.map(String).join(' '));
|
|
33
|
+
});
|
|
34
|
+
vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
35
|
+
});
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
vi.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
it('shows login hint for 401', () => {
|
|
40
|
+
handleApiError(new ApiError(401, 'Unauthorized'));
|
|
41
|
+
expect(stderrOutput[0]).toContain('bfunbot login');
|
|
42
|
+
});
|
|
43
|
+
it('shows access denied for 403', () => {
|
|
44
|
+
handleApiError(new ApiError(403, 'Forbidden'));
|
|
45
|
+
expect(stderrOutput[0]).toContain('Access denied');
|
|
46
|
+
});
|
|
47
|
+
it('shows 403 detail string if provided', () => {
|
|
48
|
+
handleApiError(new ApiError(403, 'Forbidden', { detail: 'IP not allowed' }));
|
|
49
|
+
expect(stderrOutput[0]).toContain('IP not allowed');
|
|
50
|
+
});
|
|
51
|
+
it('shows rate limit message for 429', () => {
|
|
52
|
+
handleApiError(new ApiError(429, 'Too Many Requests'));
|
|
53
|
+
expect(stderrOutput[0]).toContain('Rate limited');
|
|
54
|
+
});
|
|
55
|
+
it('shows server error for 500+', () => {
|
|
56
|
+
handleApiError(new ApiError(503, 'Service Unavailable'));
|
|
57
|
+
expect(stderrOutput[0]).toContain('Server error');
|
|
58
|
+
});
|
|
59
|
+
it('joins FastAPI 422 validation errors', () => {
|
|
60
|
+
handleApiError(new ApiError(422, 'Unprocessable Entity', {
|
|
61
|
+
detail: [
|
|
62
|
+
{ msg: 'field required', loc: ['body', 'name'], type: 'missing' },
|
|
63
|
+
{ msg: 'invalid value', loc: ['body', 'symbol'], type: 'value_error' },
|
|
64
|
+
],
|
|
65
|
+
}));
|
|
66
|
+
expect(stderrOutput[0]).toContain('field required');
|
|
67
|
+
expect(stderrOutput[0]).toContain('invalid value');
|
|
68
|
+
});
|
|
69
|
+
it('shows connection error for network TypeError', () => {
|
|
70
|
+
handleApiError(new TypeError('fetch failed', { cause: new Error('ECONNREFUSED') }));
|
|
71
|
+
expect(stderrOutput[0]).toContain('Could not reach');
|
|
72
|
+
});
|
|
73
|
+
it('calls process.exit(1)', () => {
|
|
74
|
+
handleApiError(new ApiError(401, 'Unauthorized'));
|
|
75
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/lib/display.ts
|
|
3
|
+
*
|
|
4
|
+
* Covers: fmtBalance, fmtBnbWithUsd, fmtUsd, shortAddr, fmtDate, printTable alignment.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { fmtBalance, fmtBnbWithUsd, fmtUsd, shortAddr, fmtDate, printTable, } from '../../lib/display.js';
|
|
8
|
+
// Strip ANSI codes from output for readable assertions
|
|
9
|
+
const strip = (s) => s.replace(/\x1B\[[0-9;]*m/g, '');
|
|
10
|
+
// ─── fmtBalance ──────────────────────────────────────────────────────────────
|
|
11
|
+
describe('fmtBalance', () => {
|
|
12
|
+
it('formats a normal BNB value', () => {
|
|
13
|
+
expect(strip(fmtBalance('1.5', 'BNB'))).toBe('1.5 BNB');
|
|
14
|
+
});
|
|
15
|
+
it('trims trailing zeros', () => {
|
|
16
|
+
expect(strip(fmtBalance('1.500000', 'BNB'))).toBe('1.5 BNB');
|
|
17
|
+
});
|
|
18
|
+
it('formats a small value without scientific notation', () => {
|
|
19
|
+
const result = strip(fmtBalance('0.00000597', 'BNB'));
|
|
20
|
+
expect(result).not.toContain('e');
|
|
21
|
+
expect(result).toContain('0.00000597');
|
|
22
|
+
expect(result).toContain('BNB');
|
|
23
|
+
});
|
|
24
|
+
it('formats an extremely small value without scientific notation', () => {
|
|
25
|
+
const result = strip(fmtBalance('0.0000000012', 'BNB'));
|
|
26
|
+
expect(result).not.toContain('e');
|
|
27
|
+
expect(result).toContain('BNB');
|
|
28
|
+
});
|
|
29
|
+
it('returns dim zero for zero value', () => {
|
|
30
|
+
expect(strip(fmtBalance('0', 'BNB'))).toBe('0 BNB');
|
|
31
|
+
});
|
|
32
|
+
it('returns dim zero for null', () => {
|
|
33
|
+
expect(strip(fmtBalance(null, 'BNB'))).toBe('0 BNB');
|
|
34
|
+
});
|
|
35
|
+
it('returns dim zero for undefined', () => {
|
|
36
|
+
expect(strip(fmtBalance(undefined, 'BNB'))).toBe('0 BNB');
|
|
37
|
+
});
|
|
38
|
+
it('returns dim zero for "0.0"', () => {
|
|
39
|
+
expect(strip(fmtBalance('0.0', 'BNB'))).toBe('0 BNB');
|
|
40
|
+
});
|
|
41
|
+
it('formats USDT correctly', () => {
|
|
42
|
+
expect(strip(fmtBalance('100.5', 'USDT'))).toBe('100.5 USDT');
|
|
43
|
+
});
|
|
44
|
+
it('handles 6 decimal places without trailing zeros', () => {
|
|
45
|
+
expect(strip(fmtBalance('0.412000', 'BNB'))).toBe('0.412 BNB');
|
|
46
|
+
});
|
|
47
|
+
it('never shows 0 for a non-zero sub-1e-10 value', () => {
|
|
48
|
+
const result = strip(fmtBalance('0.00000000001', 'BNB'));
|
|
49
|
+
expect(result).not.toMatch(/^0 BNB/);
|
|
50
|
+
expect(result).not.toContain('e');
|
|
51
|
+
expect(result).toContain('BNB');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
// ─── fmtBnbWithUsd ───────────────────────────────────────────────────────────
|
|
55
|
+
describe('fmtBnbWithUsd', () => {
|
|
56
|
+
it('appends USD value in parentheses', () => {
|
|
57
|
+
const result = strip(fmtBnbWithUsd('1.0', 600));
|
|
58
|
+
expect(result).toContain('1 BNB');
|
|
59
|
+
expect(result).toContain('~$600.00');
|
|
60
|
+
});
|
|
61
|
+
it('calculates USD correctly for fractional BNB', () => {
|
|
62
|
+
const result = strip(fmtBnbWithUsd('0.5', 600));
|
|
63
|
+
expect(result).toContain('~$300.00');
|
|
64
|
+
});
|
|
65
|
+
it('returns plain BNB when price is 0', () => {
|
|
66
|
+
const result = strip(fmtBnbWithUsd('1.0', 0));
|
|
67
|
+
expect(result).toContain('BNB');
|
|
68
|
+
expect(result).not.toContain('$');
|
|
69
|
+
});
|
|
70
|
+
it('returns plain BNB for null value', () => {
|
|
71
|
+
const result = strip(fmtBnbWithUsd(null, 600));
|
|
72
|
+
expect(result).not.toContain('$');
|
|
73
|
+
});
|
|
74
|
+
it('returns plain BNB for zero value', () => {
|
|
75
|
+
const result = strip(fmtBnbWithUsd('0', 600));
|
|
76
|
+
expect(result).not.toContain('$');
|
|
77
|
+
});
|
|
78
|
+
it('handles very small BNB with USD', () => {
|
|
79
|
+
const result = strip(fmtBnbWithUsd('0.00001', 600));
|
|
80
|
+
expect(result).not.toContain('e');
|
|
81
|
+
expect(result).toContain('BNB');
|
|
82
|
+
expect(result).toContain('~$');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
// ─── fmtUsd ──────────────────────────────────────────────────────────────────
|
|
86
|
+
describe('fmtUsd', () => {
|
|
87
|
+
it('formats values >= $1 to 2 decimal places', () => {
|
|
88
|
+
expect(strip(fmtUsd('12.5'))).toBe('$12.50');
|
|
89
|
+
});
|
|
90
|
+
it('formats zero with em dash', () => {
|
|
91
|
+
expect(strip(fmtUsd('0'))).toBe('—');
|
|
92
|
+
});
|
|
93
|
+
it('formats null with em dash', () => {
|
|
94
|
+
expect(strip(fmtUsd(null))).toBe('—');
|
|
95
|
+
});
|
|
96
|
+
it('formats undefined with em dash', () => {
|
|
97
|
+
expect(strip(fmtUsd(undefined))).toBe('—');
|
|
98
|
+
});
|
|
99
|
+
it('formats small values with enough significant digits', () => {
|
|
100
|
+
const result = strip(fmtUsd('0.000123'));
|
|
101
|
+
expect(result).toContain('$');
|
|
102
|
+
expect(result).toContain('0.000123');
|
|
103
|
+
});
|
|
104
|
+
it('formats large USD values correctly', () => {
|
|
105
|
+
expect(strip(fmtUsd('1234.567'))).toBe('$1234.57');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// ─── shortAddr ───────────────────────────────────────────────────────────────
|
|
109
|
+
describe('shortAddr', () => {
|
|
110
|
+
it('truncates a long address', () => {
|
|
111
|
+
const addr = '0xB305ce8D75498E4D0c2963089C4894b3589F7777';
|
|
112
|
+
const result = strip(shortAddr(addr));
|
|
113
|
+
expect(result).toContain('0xB305ce');
|
|
114
|
+
expect(result).toContain('...');
|
|
115
|
+
expect(result).toContain('F7777');
|
|
116
|
+
expect(result.length).toBeLessThan(addr.length);
|
|
117
|
+
});
|
|
118
|
+
it('returns em dash for null', () => {
|
|
119
|
+
expect(strip(shortAddr(null))).toBe('—');
|
|
120
|
+
});
|
|
121
|
+
it('returns em dash for undefined', () => {
|
|
122
|
+
expect(strip(shortAddr(undefined))).toBe('—');
|
|
123
|
+
});
|
|
124
|
+
it('returns short address as-is', () => {
|
|
125
|
+
expect(strip(shortAddr('0x1234'))).toBe('0x1234');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
// ─── fmtDate ─────────────────────────────────────────────────────────────────
|
|
129
|
+
describe('fmtDate', () => {
|
|
130
|
+
it('formats a valid ISO date string', () => {
|
|
131
|
+
const result = strip(fmtDate('2024-01-15T10:30:00Z'));
|
|
132
|
+
expect(result).toContain('2024');
|
|
133
|
+
expect(result).toContain('Jan');
|
|
134
|
+
expect(result).toContain('15');
|
|
135
|
+
});
|
|
136
|
+
it('returns em dash for null', () => {
|
|
137
|
+
expect(strip(fmtDate(null))).toBe('—');
|
|
138
|
+
});
|
|
139
|
+
it('returns em dash for undefined', () => {
|
|
140
|
+
expect(strip(fmtDate(undefined))).toBe('—');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
// ─── printTable alignment ────────────────────────────────────────────────────
|
|
144
|
+
describe('printTable', () => {
|
|
145
|
+
let output;
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
output = [];
|
|
148
|
+
vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
149
|
+
output.push(args.map(String).join(' '));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
afterEach(() => {
|
|
153
|
+
vi.restoreAllMocks();
|
|
154
|
+
});
|
|
155
|
+
it('produces consistent column widths with plain text', () => {
|
|
156
|
+
printTable(['Name', 'Value', 'Status'], [
|
|
157
|
+
['Alice', '100', 'active'],
|
|
158
|
+
['Bob', '9999', 'inactive'],
|
|
159
|
+
]);
|
|
160
|
+
const rows = output.filter(l => l.trim() && !l.includes('─'));
|
|
161
|
+
// All rows should have the same length (same column widths)
|
|
162
|
+
const header = strip(rows[0]);
|
|
163
|
+
const row1 = strip(rows[1]);
|
|
164
|
+
const row2 = strip(rows[2]);
|
|
165
|
+
expect(row1.length).toBe(header.length);
|
|
166
|
+
expect(row2.length).toBe(header.length);
|
|
167
|
+
});
|
|
168
|
+
it('correctly aligns columns when cells contain chalk colors (ANSI codes)', async () => {
|
|
169
|
+
const { default: chalk } = await import('chalk');
|
|
170
|
+
printTable(['Chain', 'Used Today', 'Can Create'], [
|
|
171
|
+
[chalk.bold('BSC'), '5', chalk.green('Yes')],
|
|
172
|
+
[chalk.bold('ETH'), '10', chalk.dim('No')],
|
|
173
|
+
]);
|
|
174
|
+
const rows = output.filter(l => l.trim() && !l.includes('─'));
|
|
175
|
+
// Visible (stripped) lengths of all rows must be equal
|
|
176
|
+
const lengths = rows.map(r => strip(r).length);
|
|
177
|
+
expect(lengths.every(l => l === lengths[0])).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
it('correctly handles CJK wide characters in column width calculation', async () => {
|
|
180
|
+
printTable(['Name', 'Symbol'], [
|
|
181
|
+
['普通Token', 'ABC'], // CJK chars — each is 2 columns wide
|
|
182
|
+
['NormalName', 'XYZ'],
|
|
183
|
+
]);
|
|
184
|
+
// Find separator line to locate data rows
|
|
185
|
+
const sepIdx = output.findIndex(l => strip(l).includes('─'));
|
|
186
|
+
const dataRows = output.slice(sepIdx + 1).filter(l => l.trim() !== '');
|
|
187
|
+
// Both data rows must have the same visible terminal width
|
|
188
|
+
const visW = (s) => {
|
|
189
|
+
let w = 0;
|
|
190
|
+
for (const ch of strip(s).trimEnd()) {
|
|
191
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
192
|
+
w += (cp >= 0x4E00 && cp <= 0x9FFF) ? 2 : 1;
|
|
193
|
+
}
|
|
194
|
+
return w;
|
|
195
|
+
};
|
|
196
|
+
const lengths = dataRows.map(r => visW(r));
|
|
197
|
+
expect(lengths).toHaveLength(2);
|
|
198
|
+
expect(lengths[0]).toBe(lengths[1]);
|
|
199
|
+
});
|
|
200
|
+
it('handles empty rows gracefully', () => {
|
|
201
|
+
expect(() => printTable(['Col A', 'Col B'], [])).not.toThrow();
|
|
202
|
+
});
|
|
203
|
+
it('renders headers and separator', () => {
|
|
204
|
+
printTable(['Symbol', 'Name'], [['BTC', 'Bitcoin']]);
|
|
205
|
+
const allOutput = output.join('\n');
|
|
206
|
+
expect(allOutput).toContain('Symbol');
|
|
207
|
+
expect(allOutput).toContain('Name');
|
|
208
|
+
expect(allOutput).toContain('─');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { agent, handleApiError } from '../lib/api.js';
|
|
3
|
-
import { fmtBalance, shortAddr } from '../lib/display.js';
|
|
4
|
-
function printWallet(label, slot) {
|
|
3
|
+
import { fmtBalance, fmtBnbWithUsd, shortAddr } from '../lib/display.js';
|
|
4
|
+
function printWallet(label, slot, bnbPriceUsd) {
|
|
5
5
|
if (!slot.address) {
|
|
6
6
|
console.log(` ${chalk.dim(label + ':')} ${chalk.dim('Not configured')}`);
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
9
|
console.log(` ${chalk.bold(label)} ${chalk.dim(shortAddr(slot.address))}`);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
10
|
+
if (slot.bnb_error) {
|
|
11
|
+
console.log(` ${'BNB (BSC)'.padEnd(14)} ${chalk.red('error')}`);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
console.log(` ${'BNB (BSC)'.padEnd(14)} ${fmtBnbWithUsd(slot.balance_bnb, bnbPriceUsd)}`);
|
|
15
|
+
}
|
|
16
|
+
if (slot.usdt_bsc_error) {
|
|
17
|
+
console.log(` ${'USDT (BSC)'.padEnd(14)} ${chalk.red('error')}`);
|
|
18
|
+
}
|
|
19
|
+
else if (slot.balance_usdt_bsc && parseFloat(slot.balance_usdt_bsc) > 0) {
|
|
20
|
+
console.log(` ${'USDT (BSC)'.padEnd(14)} ${fmtBalance(slot.balance_usdt_bsc, 'USDT')}`);
|
|
22
21
|
}
|
|
23
22
|
console.log();
|
|
24
23
|
}
|
|
@@ -38,8 +37,9 @@ export function registerBalances(program) {
|
|
|
38
37
|
console.log(chalk.bold.cyan('Wallet Balances'));
|
|
39
38
|
console.log(chalk.dim('─'.repeat(40)));
|
|
40
39
|
console.log();
|
|
41
|
-
|
|
42
|
-
printWallet('EVM
|
|
40
|
+
const bnbPrice = res.bnb_price_usd ?? 0;
|
|
41
|
+
printWallet('EVM Main', res.evm_main, bnbPrice);
|
|
42
|
+
printWallet('EVM Trading', res.evm_trading, bnbPrice);
|
|
43
43
|
}
|
|
44
44
|
catch (err) {
|
|
45
45
|
handleApiError(err);
|
package/dist/commands/fees.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* bfunbot fees
|
|
3
|
-
* bfunbot fees
|
|
4
|
-
* bfunbot fees --token <addr>
|
|
2
|
+
* bfunbot fees — summary (total earned)
|
|
3
|
+
* bfunbot fees breakdown — per-platform table
|
|
4
|
+
* bfunbot fees --token <addr> --platform <plat> — per-token detail
|
|
5
5
|
*/
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
export declare function registerFees(program: Command): void;
|
package/dist/commands/fees.js
CHANGED
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { agent, handleApiError } from '../lib/api.js';
|
|
3
|
-
import { fmtBalance, printTable } from '../lib/display.js';
|
|
3
|
+
import { fmtBalance, fmtBnbWithUsd, printTable } from '../lib/display.js';
|
|
4
4
|
const VALID_PLATFORMS = ['flap', 'fourmeme'];
|
|
5
5
|
// ─── Display Helpers ─────────────────────────────────────
|
|
6
6
|
function printSummary(res) {
|
|
7
|
+
const bnbPrice = res.bnb_price_usd ?? 0;
|
|
7
8
|
console.log();
|
|
8
9
|
console.log(chalk.bold.cyan('Fee Earnings Summary'));
|
|
9
10
|
console.log(chalk.dim('─'.repeat(44)));
|
|
10
11
|
console.log();
|
|
11
|
-
console.log(` ${chalk.bold('BSC (BNB Chain)')}
|
|
12
|
-
console.log(` ${'Total Earned'.padEnd(22)} ${
|
|
12
|
+
console.log(` ${chalk.bold('BSC (BNB Chain)')}`);
|
|
13
|
+
console.log(` ${'Total Earned'.padEnd(22)} ${fmtBnbWithUsd(res.bsc.total_earned_bnb.toString(), bnbPrice)}`);
|
|
14
|
+
console.log();
|
|
15
|
+
console.log(chalk.dim(` For per-platform breakdown: bfunbot fees breakdown`));
|
|
13
16
|
console.log();
|
|
14
17
|
}
|
|
15
|
-
function
|
|
18
|
+
function printBreakdown(res, bnbPriceUsd) {
|
|
16
19
|
console.log();
|
|
17
|
-
console.log(chalk.bold.cyan(
|
|
20
|
+
console.log(chalk.bold.cyan('Fee Earnings · Platform Breakdown'));
|
|
18
21
|
console.log(chalk.dim('─'.repeat(44)));
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
printTable(['Platform', 'Earned', 'USD Value', 'Tokens'], [
|
|
23
|
+
[
|
|
24
|
+
'flap',
|
|
25
|
+
fmtBalance(res.flap.total_earned_bnb.toString(), 'BNB'),
|
|
26
|
+
bnbPriceUsd ? chalk.dim(`~$${(res.flap.total_earned_bnb * bnbPriceUsd).toFixed(2)}`) : chalk.dim('—'),
|
|
27
|
+
res.flap.earning_token_count.toString(),
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
'fourmeme',
|
|
31
|
+
fmtBalance(res.fourmeme.total_earned_bnb.toString(), 'BNB'),
|
|
32
|
+
bnbPriceUsd ? chalk.dim(`~$${(res.fourmeme.total_earned_bnb * bnbPriceUsd).toFixed(2)}`) : chalk.dim('—'),
|
|
33
|
+
res.fourmeme.earning_token_count.toString(),
|
|
34
|
+
],
|
|
23
35
|
]);
|
|
24
36
|
}
|
|
25
|
-
function printToken(res) {
|
|
37
|
+
function printToken(res, bnbPriceUsd) {
|
|
26
38
|
if ('supported' in res && res.supported === false) {
|
|
27
39
|
console.log();
|
|
28
40
|
console.log(chalk.yellow('⚠') + ' ' + res.message);
|
|
@@ -37,16 +49,16 @@ function printToken(res) {
|
|
|
37
49
|
console.log(` ${'Token'.padEnd(14)} ${bsc.token_name} (${bsc.token_symbol})`);
|
|
38
50
|
console.log(` ${'Address'.padEnd(14)} ${chalk.dim(bsc.token_address)}`);
|
|
39
51
|
console.log(` ${'Platform'.padEnd(14)} ${bsc.platform}`);
|
|
40
|
-
console.log(` ${'Earned'.padEnd(14)} ${
|
|
52
|
+
console.log(` ${'Earned'.padEnd(14)} ${fmtBnbWithUsd(bsc.earned_bnb.toString(), bnbPriceUsd)}`);
|
|
41
53
|
console.log();
|
|
42
54
|
}
|
|
43
55
|
// ─── Command Registration ─────────────────────────────────
|
|
44
56
|
export function registerFees(program) {
|
|
45
|
-
program
|
|
57
|
+
const feesCmd = program
|
|
46
58
|
.command('fees')
|
|
47
|
-
.description('Check fee earnings (summary,
|
|
48
|
-
.option('--platform <platform>', 'Platform: flap or fourmeme')
|
|
49
|
-
.option('--token <address>', 'Token contract address')
|
|
59
|
+
.description('Check fee earnings (summary, breakdown, or per-token)')
|
|
60
|
+
.option('--platform <platform>', 'Platform for per-token lookup: flap or fourmeme')
|
|
61
|
+
.option('--token <address>', 'Token contract address (requires --platform)')
|
|
50
62
|
.action(async (opts, cmd) => {
|
|
51
63
|
const isJson = cmd.optsWithGlobals().json;
|
|
52
64
|
try {
|
|
@@ -61,27 +73,35 @@ export function registerFees(program) {
|
|
|
61
73
|
console.error(chalk.red('Error:') + ` Platform must be one of: ${VALID_PLATFORMS.join(', ')}`);
|
|
62
74
|
process.exit(1);
|
|
63
75
|
}
|
|
64
|
-
const
|
|
76
|
+
const tokenRes = await agent.feesToken('bsc', platform, opts.token);
|
|
65
77
|
if (isJson) {
|
|
66
|
-
console.log(JSON.stringify(
|
|
78
|
+
console.log(JSON.stringify(tokenRes, null, 2));
|
|
67
79
|
return;
|
|
68
80
|
}
|
|
69
|
-
|
|
81
|
+
// Fetch price separately — graceful fallback if summary is unavailable
|
|
82
|
+
let bnbPrice = 0;
|
|
83
|
+
try {
|
|
84
|
+
bnbPrice = (await agent.feesSummary()).bnb_price_usd ?? 0;
|
|
85
|
+
}
|
|
86
|
+
catch { /* non-fatal */ }
|
|
87
|
+
printToken(tokenRes, bnbPrice);
|
|
70
88
|
return;
|
|
71
89
|
}
|
|
72
|
-
//
|
|
90
|
+
// --platform alone (legacy): route to breakdown with deprecation hint
|
|
73
91
|
if (opts.platform) {
|
|
74
|
-
if (!
|
|
75
|
-
console.
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
// Show full breakdown (both platforms) — the platform flag is for future filtering
|
|
79
|
-
const res = await agent.feesEarnings('bsc');
|
|
92
|
+
if (!isJson)
|
|
93
|
+
console.log(chalk.dim(' Tip: use `bfunbot fees breakdown` for per-platform earnings'));
|
|
94
|
+
const earningsRes = await agent.feesEarnings('bsc');
|
|
80
95
|
if (isJson) {
|
|
81
|
-
console.log(JSON.stringify(
|
|
96
|
+
console.log(JSON.stringify(earningsRes, null, 2));
|
|
82
97
|
return;
|
|
83
98
|
}
|
|
84
|
-
|
|
99
|
+
let bnbPrice = 0;
|
|
100
|
+
try {
|
|
101
|
+
bnbPrice = (await agent.feesSummary()).bnb_price_usd ?? 0;
|
|
102
|
+
}
|
|
103
|
+
catch { /* non-fatal */ }
|
|
104
|
+
printBreakdown(earningsRes, bnbPrice);
|
|
85
105
|
return;
|
|
86
106
|
}
|
|
87
107
|
// Default: summary
|
|
@@ -96,4 +116,28 @@ export function registerFees(program) {
|
|
|
96
116
|
handleApiError(err);
|
|
97
117
|
}
|
|
98
118
|
});
|
|
119
|
+
// ── fees breakdown ────────────────────────────────────
|
|
120
|
+
feesCmd
|
|
121
|
+
.command('breakdown')
|
|
122
|
+
.description('Per-platform fee earnings (flap + fourmeme)')
|
|
123
|
+
.action(async (_, cmd) => {
|
|
124
|
+
const isJson = cmd.optsWithGlobals().json;
|
|
125
|
+
try {
|
|
126
|
+
const earningsRes = await agent.feesEarnings('bsc');
|
|
127
|
+
if (isJson) {
|
|
128
|
+
console.log(JSON.stringify(earningsRes, null, 2));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Price fetch is non-fatal — breakdown still works without USD values
|
|
132
|
+
let bnbPrice = 0;
|
|
133
|
+
try {
|
|
134
|
+
bnbPrice = (await agent.feesSummary()).bnb_price_usd ?? 0;
|
|
135
|
+
}
|
|
136
|
+
catch { /* non-fatal */ }
|
|
137
|
+
printBreakdown(earningsRes, bnbPrice);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
handleApiError(err);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
99
143
|
}
|
package/dist/commands/llm.js
CHANGED
|
@@ -49,12 +49,11 @@ export function registerLlm(program) {
|
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
printCard('BFun.bot Credits', [
|
|
52
|
-
['Balance', `$${res.balance_usd}`],
|
|
52
|
+
['Balance', chalk.bold.white(`$${res.balance_usd}`)],
|
|
53
53
|
]);
|
|
54
54
|
// Show agent reload config
|
|
55
55
|
if (res.agent_reload) {
|
|
56
56
|
const r = res.agent_reload;
|
|
57
|
-
console.log();
|
|
58
57
|
if (r.enabled) {
|
|
59
58
|
console.log(` ${chalk.dim('Agent Reload:')} ${chalk.green('ON')}`);
|
|
60
59
|
console.log(` ${chalk.dim('Amount:')} $${r.amount_usd.toFixed(2)}`);
|
package/dist/commands/quota.js
CHANGED
|
@@ -13,13 +13,16 @@ export function registerQuota(program) {
|
|
|
13
13
|
console.log(JSON.stringify(res, null, 2));
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
|
-
printTable(['Chain', '
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
printTable(['Chain', 'Used Today', 'Daily Limit', 'Remaining', 'Can Create'], res.chains.map((c) => {
|
|
17
|
+
const canCreate = c.sponsored_remaining > 0 || c.can_create_paid;
|
|
18
|
+
return [
|
|
19
|
+
chalk.bold(c.chain.charAt(0).toUpperCase() + c.chain.slice(1)),
|
|
20
|
+
`${c.free_used_today}`,
|
|
21
|
+
`${c.free_limit}`,
|
|
22
|
+
`${c.sponsored_remaining}`,
|
|
23
|
+
canCreate ? chalk.green('Yes') : chalk.dim('No'),
|
|
24
|
+
];
|
|
25
|
+
}));
|
|
23
26
|
}
|
|
24
27
|
catch (err) {
|
|
25
28
|
handleApiError(err);
|
package/dist/commands/token.js
CHANGED
|
@@ -4,8 +4,8 @@ import { agent, handleApiError } from '../lib/api.js';
|
|
|
4
4
|
import { printCard, fmtUsd, fmtDate } from '../lib/display.js';
|
|
5
5
|
const POLL_INTERVAL_MS = 2000;
|
|
6
6
|
const POLL_TIMEOUT_MS = 120_000;
|
|
7
|
-
const
|
|
8
|
-
bsc: 'https://
|
|
7
|
+
const TOKEN_VIEW_URLS = {
|
|
8
|
+
bsc: 'https://bfun.bot/tokens/',
|
|
9
9
|
};
|
|
10
10
|
export function registerToken(program) {
|
|
11
11
|
const tokenCmd = program
|
|
@@ -97,7 +97,7 @@ export function registerToken(program) {
|
|
|
97
97
|
printCard('Token Deployed', [
|
|
98
98
|
['Address', job.token_address],
|
|
99
99
|
['Chain', 'BSC'],
|
|
100
|
-
['View', `${
|
|
100
|
+
['View', `${TOKEN_VIEW_URLS.bsc}${job.token_address}`],
|
|
101
101
|
]);
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
@@ -129,7 +129,7 @@ export function registerToken(program) {
|
|
|
129
129
|
console.log(JSON.stringify(res, null, 2));
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
|
-
const
|
|
132
|
+
const viewBase = TOKEN_VIEW_URLS[res.chain] || '';
|
|
133
133
|
printCard(`${res.symbol} — ${res.name}`, [
|
|
134
134
|
['Address', res.token_address],
|
|
135
135
|
['Chain', res.chain],
|
|
@@ -140,7 +140,7 @@ export function registerToken(program) {
|
|
|
140
140
|
['24h Volume', fmtUsd(res.volume_24h_usd)],
|
|
141
141
|
['Creator Reward', fmtUsd(res.creator_reward_usd)],
|
|
142
142
|
['Created', fmtDate(res.created_at)],
|
|
143
|
-
['
|
|
143
|
+
['View', viewBase ? `${viewBase}${res.token_address}` : undefined],
|
|
144
144
|
]);
|
|
145
145
|
}
|
|
146
146
|
catch (err) {
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,6 @@ import { registerToken } from './commands/token.js';
|
|
|
17
17
|
import { registerStatus } from './commands/status.js';
|
|
18
18
|
import { registerBalances } from './commands/balances.js';
|
|
19
19
|
import { registerQuota } from './commands/quota.js';
|
|
20
|
-
import { registerSkills } from './commands/skills.js';
|
|
21
20
|
import { registerLlm } from './commands/llm.js';
|
|
22
21
|
import { registerConfig } from './commands/config.js';
|
|
23
22
|
import { registerAbout } from './commands/about.js';
|
|
@@ -78,7 +77,6 @@ registerToken(program);
|
|
|
78
77
|
registerStatus(program);
|
|
79
78
|
registerBalances(program);
|
|
80
79
|
registerQuota(program);
|
|
81
|
-
registerSkills(program);
|
|
82
80
|
registerLlm(program);
|
|
83
81
|
registerConfig(program);
|
|
84
82
|
registerAbout(program);
|
package/dist/lib/display.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export declare function printCard(title: string, fields: [string, string | undefined][]): void;
|
|
5
5
|
/**
|
|
6
6
|
* Print a simple table with headers.
|
|
7
|
+
* Uses ANSI-stripped lengths for column width calculation so chalk colors don't break alignment.
|
|
7
8
|
*/
|
|
8
9
|
export declare function printTable(headers: string[], rows: string[][]): void;
|
|
9
10
|
/**
|
|
@@ -14,6 +15,11 @@ export declare function shortAddr(addr: string | undefined | null, chars?: numbe
|
|
|
14
15
|
* Format a balance value.
|
|
15
16
|
*/
|
|
16
17
|
export declare function fmtBalance(value: string | undefined | null, symbol: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Format a BNB value with USD equivalent in parentheses.
|
|
20
|
+
* e.g. "0.00412 BNB (~$2.47)"
|
|
21
|
+
*/
|
|
22
|
+
export declare function fmtBnbWithUsd(value: string | undefined | null, bnbPriceUsd: number): string;
|
|
17
23
|
/**
|
|
18
24
|
* Format a USD value.
|
|
19
25
|
*/
|
package/dist/lib/display.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Terminal display helpers — tables, cards, formatting.
|
|
3
3
|
*/
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
+
import stringWidth from 'string-width';
|
|
5
6
|
/**
|
|
6
7
|
* Print a key-value card.
|
|
7
8
|
*/
|
|
@@ -16,23 +17,38 @@ export function printCard(title, fields) {
|
|
|
16
17
|
}
|
|
17
18
|
console.log();
|
|
18
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the visible terminal width of a string.
|
|
22
|
+
* Uses string-width which correctly handles ANSI codes, CJK wide chars,
|
|
23
|
+
* ZWJ emoji sequences, variation selectors, and all Unicode edge cases.
|
|
24
|
+
*/
|
|
25
|
+
function visibleWidth(str) {
|
|
26
|
+
return stringWidth(str);
|
|
27
|
+
}
|
|
19
28
|
/**
|
|
20
29
|
* Print a simple table with headers.
|
|
30
|
+
* Uses ANSI-stripped lengths for column width calculation so chalk colors don't break alignment.
|
|
21
31
|
*/
|
|
22
32
|
export function printTable(headers, rows) {
|
|
23
|
-
// Calculate column widths
|
|
33
|
+
// Calculate column widths using visible terminal width (handles ANSI + CJK wide chars)
|
|
24
34
|
const widths = headers.map((h, i) => {
|
|
25
|
-
const maxRow = rows.reduce((max, row) => Math.max(max, (row[i] || '')
|
|
26
|
-
return Math.max(h
|
|
35
|
+
const maxRow = rows.reduce((max, row) => Math.max(max, visibleWidth(row[i] || '')), 0);
|
|
36
|
+
return Math.max(visibleWidth(h), maxRow);
|
|
27
37
|
});
|
|
28
38
|
// Header
|
|
29
|
-
const headerLine = headers.map((h, i) =>
|
|
39
|
+
const headerLine = headers.map((h, i) => {
|
|
40
|
+
const padding = widths[i] - visibleWidth(h);
|
|
41
|
+
return h + ' '.repeat(Math.max(0, padding));
|
|
42
|
+
}).join(' ');
|
|
30
43
|
console.log();
|
|
31
44
|
console.log(chalk.bold(headerLine));
|
|
32
45
|
console.log(chalk.dim(widths.map(w => '─'.repeat(w)).join(' ')));
|
|
33
|
-
// Rows
|
|
46
|
+
// Rows — pad by visible terminal width, not raw string length
|
|
34
47
|
for (const row of rows) {
|
|
35
|
-
const line = row.map((cell, i) =>
|
|
48
|
+
const line = row.map((cell, i) => {
|
|
49
|
+
const padding = widths[i] - visibleWidth(cell || '');
|
|
50
|
+
return (cell || '') + ' '.repeat(Math.max(0, padding));
|
|
51
|
+
}).join(' ');
|
|
36
52
|
console.log(line);
|
|
37
53
|
}
|
|
38
54
|
console.log();
|
|
@@ -59,9 +75,45 @@ export function fmtBalance(value, symbol) {
|
|
|
59
75
|
return chalk.dim(`0 ${symbol}`);
|
|
60
76
|
if (num === 0)
|
|
61
77
|
return chalk.dim(`0 ${symbol}`);
|
|
62
|
-
|
|
78
|
+
// Use enough decimal places to always show significant digits for very small values.
|
|
79
|
+
// toFixed(10) rounds below 5e-11 to zero, so fall back to toPrecision for anything smaller.
|
|
80
|
+
let formatted;
|
|
81
|
+
if (num >= 0.0001) {
|
|
82
|
+
formatted = num.toFixed(6).replace(/0+$/, '').replace(/\.$/, '');
|
|
83
|
+
}
|
|
84
|
+
else if (num >= 5e-11) {
|
|
85
|
+
formatted = num.toFixed(10).replace(/0+$/, '').replace(/\.$/, '');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Extremely small: toPrecision gives enough sig figs, then convert from sci notation
|
|
89
|
+
formatted = parseFloat(num.toPrecision(4)).toFixed(20).replace(/0+$/, '').replace(/\.$/, '');
|
|
90
|
+
}
|
|
63
91
|
return `${formatted} ${symbol}`;
|
|
64
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Format a BNB value with USD equivalent in parentheses.
|
|
95
|
+
* e.g. "0.00412 BNB (~$2.47)"
|
|
96
|
+
*/
|
|
97
|
+
export function fmtBnbWithUsd(value, bnbPriceUsd) {
|
|
98
|
+
const bnbStr = fmtBalance(value, 'BNB');
|
|
99
|
+
if (!value || value === '0' || value === '0.0' || !bnbPriceUsd)
|
|
100
|
+
return bnbStr;
|
|
101
|
+
const num = parseFloat(value);
|
|
102
|
+
if (isNaN(num) || num === 0)
|
|
103
|
+
return bnbStr;
|
|
104
|
+
const usd = num * bnbPriceUsd;
|
|
105
|
+
return `${bnbStr} ${chalk.dim('(~' + fmtUsdInline(usd) + ')')}`;
|
|
106
|
+
}
|
|
107
|
+
function fmtUsdInline(usd) {
|
|
108
|
+
if (usd >= 1)
|
|
109
|
+
return `$${usd.toFixed(2)}`;
|
|
110
|
+
if (usd === 0)
|
|
111
|
+
return '$0';
|
|
112
|
+
const str = usd.toFixed(10);
|
|
113
|
+
const match = str.match(/^0\.(0*)/);
|
|
114
|
+
const leadingZeros = match ? match[1].length : 0;
|
|
115
|
+
return `$${usd.toFixed(leadingZeros + 4)}`;
|
|
116
|
+
}
|
|
65
117
|
/**
|
|
66
118
|
* Format a USD value.
|
|
67
119
|
*/
|
package/dist/types.d.ts
CHANGED
|
@@ -85,6 +85,7 @@ export interface WalletSlot {
|
|
|
85
85
|
export interface WalletBalanceResponse {
|
|
86
86
|
evm_main: WalletSlot;
|
|
87
87
|
evm_trading: WalletSlot;
|
|
88
|
+
bnb_price_usd?: number;
|
|
88
89
|
}
|
|
89
90
|
export interface CreditBalanceResponse {
|
|
90
91
|
balance_usd: string;
|
|
@@ -144,6 +145,7 @@ export interface FeeSummaryBsc {
|
|
|
144
145
|
}
|
|
145
146
|
export interface FeeSummaryResponse {
|
|
146
147
|
bsc: FeeSummaryBsc;
|
|
148
|
+
bnb_price_usd?: number;
|
|
147
149
|
}
|
|
148
150
|
export interface FeeEarningsBsc {
|
|
149
151
|
chain: 'bsc';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bfun-bot/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "BFunBot CLI — deploy tokens, check balances, and manage your AI agent from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsc",
|
|
18
18
|
"dev": "tsc --watch",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"test:coverage": "vitest run --coverage",
|
|
19
22
|
"prepublishOnly": "npm run build"
|
|
20
23
|
},
|
|
21
24
|
"engines": {
|
|
@@ -35,10 +38,13 @@
|
|
|
35
38
|
"dependencies": {
|
|
36
39
|
"chalk": "^5.3.0",
|
|
37
40
|
"commander": "^12.1.0",
|
|
38
|
-
"ora": "^8.1.1"
|
|
41
|
+
"ora": "^8.1.1",
|
|
42
|
+
"string-width": "^8.2.0"
|
|
39
43
|
},
|
|
40
44
|
"devDependencies": {
|
|
41
45
|
"@types/node": "^22.0.0",
|
|
42
|
-
"
|
|
46
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
47
|
+
"typescript": "^5.5.4",
|
|
48
|
+
"vitest": "^4.1.2"
|
|
43
49
|
}
|
|
44
50
|
}
|