@bfun-bot/cli 1.0.13 → 1.1.0
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 +72 -1
- package/dist/commands/profile.d.ts +7 -0
- package/dist/commands/profile.js +394 -0
- package/dist/index.js +2 -0
- package/dist/lib/api.d.ts +7 -0
- package/dist/lib/api.js +21 -1
- package/dist/lib/toml.d.ts +12 -0
- package/dist/lib/toml.js +48 -0
- package/dist/types.d.ts +77 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# BFunBot CLI
|
|
2
2
|
|
|
3
|
-
Deploy tokens, check balances, and manage your AI agent from the terminal.
|
|
3
|
+
Deploy tokens, check balances, manage your agent profile, and manage your AI agent from the terminal.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -44,6 +44,13 @@ bfunbot fees
|
|
|
44
44
|
| `bfunbot llm credits` | Check BFunBot LLM Credits balance |
|
|
45
45
|
| `bfunbot llm reload` | Reload credits from trading wallet |
|
|
46
46
|
| `bfunbot llm setup openclaw` | Configure OpenClaw integration |
|
|
47
|
+
| `bfunbot profile` | View your agent profile |
|
|
48
|
+
| `bfunbot profile create` | Create a new agent profile |
|
|
49
|
+
| `bfunbot profile update` | Update profile fields |
|
|
50
|
+
| `bfunbot profile submit` | Submit profile for admin review |
|
|
51
|
+
| `bfunbot profile add-project-update` | Add a project update |
|
|
52
|
+
| `bfunbot profile delete-project-update <id>` | Delete a project update |
|
|
53
|
+
| `bfunbot profile delete` | Permanently delete profile |
|
|
47
54
|
| `bfunbot config get` | Show current config |
|
|
48
55
|
| `bfunbot config set` | Set a config value |
|
|
49
56
|
| `bfunbot about` | About BFunBot CLI |
|
|
@@ -81,6 +88,70 @@ bfunbot llm credits
|
|
|
81
88
|
bfunbot llm reload
|
|
82
89
|
```
|
|
83
90
|
|
|
91
|
+
## Agent Profile
|
|
92
|
+
|
|
93
|
+
Manage your public agent profile on [bfun.bot/agent/profiles](https://bfun.bot/agent/profiles). Earnings data (total earnings, token count, top earning tokens) is automatically populated from your BFunBot token history — no manual token linking needed.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# View your profile
|
|
97
|
+
bfunbot profile
|
|
98
|
+
bfunbot profile --json
|
|
99
|
+
|
|
100
|
+
# Create a profile (interactive wizard, prompts to submit for review)
|
|
101
|
+
bfunbot profile create
|
|
102
|
+
|
|
103
|
+
# Create with flags
|
|
104
|
+
bfunbot profile create --name "My Agent" --description "An autonomous trading bot"
|
|
105
|
+
|
|
106
|
+
# Update profile fields
|
|
107
|
+
bfunbot profile update --name "New Name"
|
|
108
|
+
bfunbot profile update --description "Updated description"
|
|
109
|
+
|
|
110
|
+
# Submit for admin review
|
|
111
|
+
bfunbot profile submit
|
|
112
|
+
|
|
113
|
+
# Project updates (changelog / announcements)
|
|
114
|
+
bfunbot profile add-project-update --title "v2.0" --content "New features are here!"
|
|
115
|
+
bfunbot profile delete-project-update <uuid>
|
|
116
|
+
|
|
117
|
+
# Delete profile (draft/rejected only)
|
|
118
|
+
bfunbot profile delete
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### bfun.toml
|
|
122
|
+
|
|
123
|
+
Team members and products can be set via a `bfun.toml` file in your project directory. The CLI auto-reads it during `create` and `update`.
|
|
124
|
+
|
|
125
|
+
```toml
|
|
126
|
+
[profile]
|
|
127
|
+
name = "My Agent"
|
|
128
|
+
description = "Does something useful on-chain."
|
|
129
|
+
|
|
130
|
+
[[profile.team_members]]
|
|
131
|
+
name = "Alice"
|
|
132
|
+
role = "Lead Dev"
|
|
133
|
+
links = ["https://twitter.com/alice"]
|
|
134
|
+
|
|
135
|
+
[[profile.products]]
|
|
136
|
+
name = "Swap Engine"
|
|
137
|
+
description = "Optimised DEX routing"
|
|
138
|
+
url = "https://my-agent.xyz/swap"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Profile status flow
|
|
142
|
+
|
|
143
|
+
Profiles start as `draft` and must be submitted for admin review before appearing publicly.
|
|
144
|
+
|
|
145
|
+
| Status | Visible? | Description |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `draft` | No | Initial state, editable |
|
|
148
|
+
| `pending_review` | No | Awaiting admin approval |
|
|
149
|
+
| `approved` | Yes | Publicly listed on bfun.bot/agent/profiles |
|
|
150
|
+
| `rejected` | No | Admin rejected — edit and re-submit |
|
|
151
|
+
| `approved_pending_changes` | Yes | Approved profile with edits awaiting review |
|
|
152
|
+
|
|
153
|
+
Editing an approved profile stores changes as pending — the live version stays public until an admin approves the edits.
|
|
154
|
+
|
|
84
155
|
## Global Options
|
|
85
156
|
|
|
86
157
|
- `--json` — Machine-readable JSON output
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bfunbot profile — manage your agent profile on bfun.bot/agent/profiles
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
/** Coloured status label */
|
|
6
|
+
export declare function fmtStatus(status: string): string;
|
|
7
|
+
export declare function registerProfile(program: Command): void;
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { agent, ApiError, handleApiError } from '../lib/api.js';
|
|
4
|
+
import { printCard, printTable, fmtDate, success, warn } from '../lib/display.js';
|
|
5
|
+
import { readBfunToml } from '../lib/toml.js';
|
|
6
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
7
|
+
/** Coloured status label */
|
|
8
|
+
export function fmtStatus(status) {
|
|
9
|
+
switch (status) {
|
|
10
|
+
case 'draft': return chalk.yellow('draft');
|
|
11
|
+
case 'pending_review': return chalk.blue('pending_review');
|
|
12
|
+
case 'approved': return chalk.green('approved');
|
|
13
|
+
case 'rejected': return chalk.red('rejected');
|
|
14
|
+
case 'approved_pending_changes': return chalk.magenta('approved_pending_changes');
|
|
15
|
+
default: return status;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Interactive readline helper — reused across subcommands */
|
|
19
|
+
async function interactivePrompts(fields) {
|
|
20
|
+
const readline = await import('node:readline');
|
|
21
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
23
|
+
const answers = [];
|
|
24
|
+
for (const field of fields) {
|
|
25
|
+
if (field.current) {
|
|
26
|
+
answers.push(field.current);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const suffix = field.required ? ': ' : ' (optional, Enter to skip): ';
|
|
30
|
+
const answer = await ask(field.label + suffix);
|
|
31
|
+
answers.push(answer);
|
|
32
|
+
}
|
|
33
|
+
rl.close();
|
|
34
|
+
return answers;
|
|
35
|
+
}
|
|
36
|
+
/** Display a full profile with pending changes awareness */
|
|
37
|
+
function displayProfile(profile) {
|
|
38
|
+
const showRejection = profile.rejection_reason
|
|
39
|
+
? profile.rejection_reason
|
|
40
|
+
: undefined;
|
|
41
|
+
printCard(`Profile: ${profile.name}`, [
|
|
42
|
+
['Status', fmtStatus(profile.status)],
|
|
43
|
+
['Slug', profile.slug],
|
|
44
|
+
['Description', profile.description],
|
|
45
|
+
['Rejection', showRejection],
|
|
46
|
+
['Submitted', fmtDate(profile.submitted_at)],
|
|
47
|
+
['Approved', fmtDate(profile.approved_at)],
|
|
48
|
+
['Created', fmtDate(profile.created_at)],
|
|
49
|
+
['Updated', fmtDate(profile.updated_at)],
|
|
50
|
+
]);
|
|
51
|
+
// Earnings (auto-populated from BFunBot token history)
|
|
52
|
+
if (profile.total_earnings_usd || profile.total_tokens_created) {
|
|
53
|
+
printCard('Earnings', [
|
|
54
|
+
['Total Earnings', profile.total_earnings_usd ? `$${profile.total_earnings_usd.toFixed(2)}` : '$0.00'],
|
|
55
|
+
['Earning Tokens', String(profile.earning_tokens_count || 0)],
|
|
56
|
+
['Total Created', String(profile.total_tokens_created || 0)],
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
// Top earning tokens (BSC only — no chain column needed)
|
|
60
|
+
if (profile.top_earning_tokens && profile.top_earning_tokens.length > 0) {
|
|
61
|
+
const rows = profile.top_earning_tokens.map(t => [
|
|
62
|
+
`${t.token_name} (${t.token_symbol})`,
|
|
63
|
+
`$${t.creator_reward.toFixed(2)}`,
|
|
64
|
+
t.market_cap ? `$${t.market_cap.toFixed(0)}` : chalk.dim('—'),
|
|
65
|
+
]);
|
|
66
|
+
console.log(chalk.bold.cyan(`Top Earning Tokens (${profile.top_earning_tokens.length})`));
|
|
67
|
+
printTable(['Token', 'Earned', 'Mkt Cap'], rows);
|
|
68
|
+
}
|
|
69
|
+
// Pending changes banner
|
|
70
|
+
if (profile.pending_changes) {
|
|
71
|
+
const changedFields = Object.keys(profile.pending_changes);
|
|
72
|
+
if (profile.status === 'approved_pending_changes') {
|
|
73
|
+
warn(`Changes submitted for review: ${changedFields.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
else if (profile.status === 'approved') {
|
|
76
|
+
warn(`Unsent changes: ${changedFields.join(', ')}. Run 'bfunbot profile submit' to submit for review.`);
|
|
77
|
+
}
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
// Team members
|
|
81
|
+
const members = profile.team_members;
|
|
82
|
+
if (members && members.length > 0) {
|
|
83
|
+
const rows = members.map(m => [
|
|
84
|
+
m.name,
|
|
85
|
+
m.role || chalk.dim('—'),
|
|
86
|
+
m.links?.join(', ') || chalk.dim('—'),
|
|
87
|
+
]);
|
|
88
|
+
console.log(chalk.bold.cyan(`Team Members (${members.length})`));
|
|
89
|
+
printTable(['Name', 'Role', 'Links'], rows);
|
|
90
|
+
}
|
|
91
|
+
// Products
|
|
92
|
+
const products = profile.products;
|
|
93
|
+
if (products && products.length > 0) {
|
|
94
|
+
const rows = products.map(p => [
|
|
95
|
+
p.name,
|
|
96
|
+
p.description || chalk.dim('—'),
|
|
97
|
+
p.url || chalk.dim('—'),
|
|
98
|
+
]);
|
|
99
|
+
console.log(chalk.bold.cyan(`Products (${products.length})`));
|
|
100
|
+
printTable(['Name', 'Description', 'URL'], rows);
|
|
101
|
+
}
|
|
102
|
+
// Revenue sources (read-only, populated by backend)
|
|
103
|
+
const revenue = profile.revenue_sources;
|
|
104
|
+
if (revenue && revenue.length > 0) {
|
|
105
|
+
const rows = revenue.map(r => [
|
|
106
|
+
r.name,
|
|
107
|
+
r.description || chalk.dim('—'),
|
|
108
|
+
]);
|
|
109
|
+
console.log(chalk.bold.cyan(`Revenue Sources (${revenue.length})`));
|
|
110
|
+
printTable(['Name', 'Description'], rows);
|
|
111
|
+
}
|
|
112
|
+
// Project updates
|
|
113
|
+
const updates = profile.updates;
|
|
114
|
+
if (updates && updates.length > 0) {
|
|
115
|
+
const rows = updates.map(u => [
|
|
116
|
+
chalk.dim(u.id.slice(0, 8)),
|
|
117
|
+
u.title,
|
|
118
|
+
fmtDate(u.created_at),
|
|
119
|
+
]);
|
|
120
|
+
console.log(chalk.bold.cyan(`Project Updates (${updates.length})`));
|
|
121
|
+
printTable(['ID', 'Title', 'Date'], rows);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ─── Command Registration ───────────────────────────────
|
|
125
|
+
export function registerProfile(program) {
|
|
126
|
+
const profileCmd = program
|
|
127
|
+
.command('profile')
|
|
128
|
+
.description('Manage your agent profile on bfun.bot/agent/profiles');
|
|
129
|
+
// ── bfunbot profile (view) ─────────────────────────────
|
|
130
|
+
profileCmd
|
|
131
|
+
.action(async () => {
|
|
132
|
+
try {
|
|
133
|
+
const profile = await agent.profileGet();
|
|
134
|
+
const isJson = program.opts().json;
|
|
135
|
+
if (isJson) {
|
|
136
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
displayProfile(profile);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (err instanceof ApiError && err.statusCode === 404) {
|
|
143
|
+
console.log('\nNo profile found. Create one with `bfunbot profile create`.\n');
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
handleApiError(err);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
// ── bfunbot profile create ─────────────────────────────
|
|
150
|
+
profileCmd
|
|
151
|
+
.command('create')
|
|
152
|
+
.description('Create a new agent profile')
|
|
153
|
+
.option('--name <name>', 'Agent name')
|
|
154
|
+
.option('--description <desc>', 'Project description')
|
|
155
|
+
.action(async (opts, cmd) => {
|
|
156
|
+
let toml;
|
|
157
|
+
try {
|
|
158
|
+
toml = readBfunToml();
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
console.error(err.message);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
let spinner;
|
|
165
|
+
let submitSpinner;
|
|
166
|
+
try {
|
|
167
|
+
const isJson = cmd.optsWithGlobals().json;
|
|
168
|
+
if (toml && !isJson) {
|
|
169
|
+
console.log(chalk.dim(' Found bfun.toml in current directory.'));
|
|
170
|
+
}
|
|
171
|
+
// Merge: flags > toml > prompt
|
|
172
|
+
let name = opts.name || toml?.name;
|
|
173
|
+
let description = opts.description || toml?.description;
|
|
174
|
+
// Interactive prompts for missing fields (skip in --json mode)
|
|
175
|
+
if (!isJson && (!name || !description)) {
|
|
176
|
+
const answers = await interactivePrompts([
|
|
177
|
+
{ label: 'Agent name', current: name, required: true },
|
|
178
|
+
{ label: 'Description', current: description, required: true },
|
|
179
|
+
]);
|
|
180
|
+
name = answers[0] || name;
|
|
181
|
+
description = answers[1] || description;
|
|
182
|
+
}
|
|
183
|
+
if (!name) {
|
|
184
|
+
console.error('Error: --name is required.');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
const body = {
|
|
188
|
+
name,
|
|
189
|
+
description: description || undefined,
|
|
190
|
+
team_members: toml?.team_members,
|
|
191
|
+
products: toml?.products,
|
|
192
|
+
};
|
|
193
|
+
spinner = ora('Creating profile...').start();
|
|
194
|
+
const profile = await agent.profileCreate(body);
|
|
195
|
+
spinner.stop();
|
|
196
|
+
if (isJson) {
|
|
197
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
displayProfile(profile);
|
|
201
|
+
// Prompt to submit for review
|
|
202
|
+
const readline = await import('node:readline');
|
|
203
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
204
|
+
const submitAnswer = await new Promise((resolve) => rl.question('Submit for review now? (y/n): ', (a) => resolve(a.trim().toLowerCase())));
|
|
205
|
+
rl.close();
|
|
206
|
+
if (submitAnswer === 'y' || submitAnswer === 'yes') {
|
|
207
|
+
submitSpinner = ora('Submitting for review...').start();
|
|
208
|
+
await agent.profileSubmit();
|
|
209
|
+
submitSpinner.stop();
|
|
210
|
+
success('Profile submitted for review.');
|
|
211
|
+
console.log(chalk.dim(" Check your status at bfun.bot or run 'bfunbot profile'.\n"));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
console.log(chalk.dim(" Run 'bfunbot profile submit' when ready for review.\n"));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
spinner?.stop();
|
|
219
|
+
submitSpinner?.stop();
|
|
220
|
+
if (err instanceof ApiError && err.statusCode === 409) {
|
|
221
|
+
console.error('Error: Profile already exists. Use `bfunbot profile update` to modify it.');
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
handleApiError(err);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
// ── bfunbot profile update ─────────────────────────────
|
|
228
|
+
profileCmd
|
|
229
|
+
.command('update')
|
|
230
|
+
.description('Update your agent profile')
|
|
231
|
+
.option('--name <name>', 'Agent name')
|
|
232
|
+
.option('--description <desc>', 'Project description')
|
|
233
|
+
.action(async (opts, cmd) => {
|
|
234
|
+
let toml;
|
|
235
|
+
try {
|
|
236
|
+
toml = readBfunToml();
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
console.error(err.message);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
let spinner;
|
|
243
|
+
try {
|
|
244
|
+
const isJson = cmd.optsWithGlobals().json;
|
|
245
|
+
if (toml && !isJson) {
|
|
246
|
+
console.log(chalk.dim(' Found bfun.toml in current directory.'));
|
|
247
|
+
}
|
|
248
|
+
const body = {};
|
|
249
|
+
// Flags take priority
|
|
250
|
+
if (opts.name)
|
|
251
|
+
body.name = opts.name;
|
|
252
|
+
else if (toml?.name)
|
|
253
|
+
body.name = toml.name;
|
|
254
|
+
if (opts.description)
|
|
255
|
+
body.description = opts.description;
|
|
256
|
+
else if (toml?.description)
|
|
257
|
+
body.description = toml.description;
|
|
258
|
+
// Merge TOML array fields
|
|
259
|
+
if (toml?.team_members)
|
|
260
|
+
body.team_members = toml.team_members;
|
|
261
|
+
if (toml?.products)
|
|
262
|
+
body.products = toml.products;
|
|
263
|
+
if (Object.keys(body).length === 0) {
|
|
264
|
+
console.error('Error: No fields to update. Provide flags or a bfun.toml file.');
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
spinner = ora('Updating profile...').start();
|
|
268
|
+
const profile = await agent.profileUpdate(body);
|
|
269
|
+
spinner.stop();
|
|
270
|
+
if (isJson) {
|
|
271
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
displayProfile(profile);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
spinner?.stop();
|
|
278
|
+
handleApiError(err);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
// ── bfunbot profile submit ─────────────────────────────
|
|
282
|
+
profileCmd
|
|
283
|
+
.command('submit')
|
|
284
|
+
.description('Submit profile for admin review')
|
|
285
|
+
.action(async () => {
|
|
286
|
+
let spinner;
|
|
287
|
+
try {
|
|
288
|
+
const isJson = program.opts().json;
|
|
289
|
+
spinner = ora('Submitting for review...').start();
|
|
290
|
+
const profile = await agent.profileSubmit();
|
|
291
|
+
spinner.stop();
|
|
292
|
+
if (isJson) {
|
|
293
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (profile.status === 'approved_pending_changes') {
|
|
297
|
+
success('Pending changes submitted for review. Your approved profile stays live.');
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
success('Profile submitted for review.');
|
|
301
|
+
}
|
|
302
|
+
console.log(chalk.dim(" Check your status at bfun.bot or run 'bfunbot profile'.\n"));
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
spinner?.stop();
|
|
306
|
+
handleApiError(err);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
// ── bfunbot profile add-project-update ──────────────────
|
|
310
|
+
profileCmd
|
|
311
|
+
.command('add-project-update')
|
|
312
|
+
.description('Add a project update to your profile')
|
|
313
|
+
.option('--title <title>', 'Update title')
|
|
314
|
+
.option('--content <content>', 'Update content')
|
|
315
|
+
.action(async (opts, cmd) => {
|
|
316
|
+
try {
|
|
317
|
+
const isJson = cmd.optsWithGlobals().json;
|
|
318
|
+
let { title, content } = opts;
|
|
319
|
+
// Interactive prompts for missing fields
|
|
320
|
+
if (!isJson && (!title || !content)) {
|
|
321
|
+
const answers = await interactivePrompts([
|
|
322
|
+
{ label: 'Title', current: title, required: true },
|
|
323
|
+
{ label: 'Content', current: content, required: true },
|
|
324
|
+
]);
|
|
325
|
+
title = answers[0] || title;
|
|
326
|
+
content = answers[1] || content;
|
|
327
|
+
}
|
|
328
|
+
if (!title || !content) {
|
|
329
|
+
console.error('Error: --title and --content are required.');
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
const result = await agent.profileAddUpdate({ title, content });
|
|
333
|
+
if (isJson) {
|
|
334
|
+
console.log(JSON.stringify(result, null, 2));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
success(`Project update added: "${result.title}"`);
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
handleApiError(err);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
// ── bfunbot profile delete-project-update <id> ──────────
|
|
344
|
+
profileCmd
|
|
345
|
+
.command('delete-project-update <id>')
|
|
346
|
+
.description('Delete a project update')
|
|
347
|
+
.action(async (id) => {
|
|
348
|
+
try {
|
|
349
|
+
await agent.profileDeleteUpdate(id);
|
|
350
|
+
const isJson = program.opts().json;
|
|
351
|
+
if (isJson) {
|
|
352
|
+
console.log(JSON.stringify({ deleted: true, id }, null, 2));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
success(`Project update ${id} deleted.`);
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
handleApiError(err);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
// ── bfunbot profile delete ─────────────────────────────
|
|
362
|
+
profileCmd
|
|
363
|
+
.command('delete')
|
|
364
|
+
.description('Permanently delete your agent profile')
|
|
365
|
+
.option('--yes', 'Skip confirmation prompt')
|
|
366
|
+
.action(async (opts, cmd) => {
|
|
367
|
+
let spinner;
|
|
368
|
+
try {
|
|
369
|
+
const isJson = cmd.optsWithGlobals().json;
|
|
370
|
+
if (!opts.yes && !isJson) {
|
|
371
|
+
const readline = await import('node:readline');
|
|
372
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
373
|
+
const answer = await new Promise((resolve) => rl.question(chalk.red("Type 'delete' to confirm permanent deletion: "), (a) => resolve(a.trim())));
|
|
374
|
+
rl.close();
|
|
375
|
+
if (answer !== 'delete') {
|
|
376
|
+
console.log('Cancelled.');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
spinner = ora('Deleting profile...').start();
|
|
381
|
+
await agent.profileDelete();
|
|
382
|
+
spinner.stop();
|
|
383
|
+
if (isJson) {
|
|
384
|
+
console.log(JSON.stringify({ deleted: true }, null, 2));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
success('Profile deleted.');
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
spinner?.stop();
|
|
391
|
+
handleApiError(err);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { registerLlm } from './commands/llm.js';
|
|
|
21
21
|
import { registerConfig } from './commands/config.js';
|
|
22
22
|
import { registerAbout } from './commands/about.js';
|
|
23
23
|
import { registerFees } from './commands/fees.js';
|
|
24
|
+
import { registerProfile } from './commands/profile.js';
|
|
24
25
|
// Read version from package.json
|
|
25
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
26
27
|
const __dirname = dirname(__filename);
|
|
@@ -81,4 +82,5 @@ registerLlm(program);
|
|
|
81
82
|
registerConfig(program);
|
|
82
83
|
registerAbout(program);
|
|
83
84
|
registerFees(program);
|
|
85
|
+
registerProfile(program);
|
|
84
86
|
program.parse();
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -25,6 +25,13 @@ export declare const agent: {
|
|
|
25
25
|
feesSummary: () => Promise<import("../types.js").FeeSummaryResponse>;
|
|
26
26
|
feesEarnings: (chain: string) => Promise<import("../types.js").FeeEarningsBsc>;
|
|
27
27
|
feesToken: (chain: string, platform: string, tokenAddress: string) => Promise<import("../types.js").FeeTokenResponse>;
|
|
28
|
+
profileGet: () => Promise<import("../types.js").ProfileResponse>;
|
|
29
|
+
profileCreate: (body: import("../types.js").ProfileCreateRequest) => Promise<import("../types.js").ProfileResponse>;
|
|
30
|
+
profileUpdate: (body: import("../types.js").ProfileUpdateRequest) => Promise<import("../types.js").ProfileResponse>;
|
|
31
|
+
profileSubmit: () => Promise<import("../types.js").ProfileResponse>;
|
|
32
|
+
profileDelete: () => Promise<void>;
|
|
33
|
+
profileAddUpdate: (body: import("../types.js").ProjectUpdateCreateRequest) => Promise<import("../types.js").ProjectUpdateResponse>;
|
|
34
|
+
profileDeleteUpdate: (updateId: string) => Promise<void>;
|
|
28
35
|
};
|
|
29
36
|
export declare const llm: {
|
|
30
37
|
models: () => Promise<import("../types.js").LlmModelsResponse>;
|
package/dist/lib/api.js
CHANGED
|
@@ -92,7 +92,7 @@ async function request(path, opts = {}) {
|
|
|
92
92
|
const config = readConfig();
|
|
93
93
|
const baseUrl = baseLlm
|
|
94
94
|
? (config.llmUrl || 'https://llm.bfun.bot/v1')
|
|
95
|
-
: (config.apiUrl || 'https://api.bfun.bot');
|
|
95
|
+
: (process.env['BFUN_API_URL'] || config.apiUrl || 'https://api.bfun.bot');
|
|
96
96
|
const url = `${baseUrl}${path}`;
|
|
97
97
|
const headers = {
|
|
98
98
|
'Accept': 'application/json',
|
|
@@ -168,6 +168,26 @@ export const agent = {
|
|
|
168
168
|
feesSummary: () => request('/agent/v1/fees/summary'),
|
|
169
169
|
feesEarnings: (chain) => request(`/agent/v1/fees/earnings?chain=${chain}`),
|
|
170
170
|
feesToken: (chain, platform, tokenAddress) => request(`/agent/v1/fees/token?chain=${chain}&platform=${platform}&token_address=${encodeURIComponent(tokenAddress)}`),
|
|
171
|
+
// Profile
|
|
172
|
+
profileGet: () => request('/agent/v1/profile'),
|
|
173
|
+
profileCreate: (body) => request('/agent/v1/profile', {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
body,
|
|
176
|
+
}),
|
|
177
|
+
profileUpdate: (body) => request('/agent/v1/profile', {
|
|
178
|
+
method: 'PUT',
|
|
179
|
+
body,
|
|
180
|
+
}),
|
|
181
|
+
profileSubmit: () => request('/agent/v1/profile/submit', {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
body: {},
|
|
184
|
+
}),
|
|
185
|
+
profileDelete: () => request('/agent/v1/profile', { method: 'DELETE' }),
|
|
186
|
+
profileAddUpdate: (body) => request('/agent/v1/profile/update', {
|
|
187
|
+
method: 'POST',
|
|
188
|
+
body,
|
|
189
|
+
}),
|
|
190
|
+
profileDeleteUpdate: (updateId) => request(`/agent/v1/profile/updates/${updateId}`, { method: 'DELETE' }),
|
|
171
191
|
};
|
|
172
192
|
// LLM Gateway (base: /llm/v1)
|
|
173
193
|
export const llm = {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProfileTeamMember, ProfileProduct } from '../types.js';
|
|
2
|
+
export interface BfunTomlProfile {
|
|
3
|
+
name?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
team_members?: ProfileTeamMember[];
|
|
6
|
+
products?: ProfileProduct[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Read bfun.toml from CWD. Returns undefined if the file does not exist.
|
|
10
|
+
* Throws with a friendly message if the file exists but cannot be parsed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readBfunToml(): BfunTomlProfile | undefined;
|
package/dist/lib/toml.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads bfun.toml from the current working directory.
|
|
3
|
+
*
|
|
4
|
+
* Supports a [profile] section with scalar fields (name, description)
|
|
5
|
+
* and array fields (team_members, products).
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { parse } from 'smol-toml';
|
|
10
|
+
/**
|
|
11
|
+
* Read bfun.toml from CWD. Returns undefined if the file does not exist.
|
|
12
|
+
* Throws with a friendly message if the file exists but cannot be parsed.
|
|
13
|
+
*/
|
|
14
|
+
export function readBfunToml() {
|
|
15
|
+
const tomlPath = join(process.cwd(), 'bfun.toml');
|
|
16
|
+
if (!existsSync(tomlPath))
|
|
17
|
+
return undefined;
|
|
18
|
+
let raw;
|
|
19
|
+
try {
|
|
20
|
+
raw = readFileSync(tomlPath, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
throw new Error(`Error parsing bfun.toml: ${err instanceof Error ? err.message : String(err)}`);
|
|
31
|
+
}
|
|
32
|
+
const profile = parsed.profile;
|
|
33
|
+
if (!profile)
|
|
34
|
+
return undefined;
|
|
35
|
+
// Validate array element shapes — filter out malformed entries
|
|
36
|
+
const team_members = Array.isArray(profile.team_members)
|
|
37
|
+
? profile.team_members.filter((m) => typeof m === 'object' && m !== null && typeof m.name === 'string')
|
|
38
|
+
: undefined;
|
|
39
|
+
const products = Array.isArray(profile.products)
|
|
40
|
+
? profile.products.filter((p) => typeof p === 'object' && p !== null && typeof p.name === 'string')
|
|
41
|
+
: undefined;
|
|
42
|
+
return {
|
|
43
|
+
name: typeof profile.name === 'string' ? profile.name : undefined,
|
|
44
|
+
description: typeof profile.description === 'string' ? profile.description : undefined,
|
|
45
|
+
team_members: team_members && team_members.length > 0 ? team_members : undefined,
|
|
46
|
+
products: products && products.length > 0 ? products : undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -172,3 +172,80 @@ export interface FeeTokenUnsupported {
|
|
|
172
172
|
message: string;
|
|
173
173
|
}
|
|
174
174
|
export type FeeTokenResponse = FeeTokenBsc | FeeTokenUnsupported;
|
|
175
|
+
export interface ProfileTeamMember {
|
|
176
|
+
name: string;
|
|
177
|
+
role?: string;
|
|
178
|
+
links?: string[];
|
|
179
|
+
}
|
|
180
|
+
export interface ProfileProduct {
|
|
181
|
+
name: string;
|
|
182
|
+
description?: string;
|
|
183
|
+
url?: string;
|
|
184
|
+
}
|
|
185
|
+
export interface ProfileRevenueSource {
|
|
186
|
+
name: string;
|
|
187
|
+
description?: string;
|
|
188
|
+
}
|
|
189
|
+
export interface ProfileUpdate {
|
|
190
|
+
id: string;
|
|
191
|
+
title: string;
|
|
192
|
+
content: string;
|
|
193
|
+
created_at: string;
|
|
194
|
+
}
|
|
195
|
+
export interface TopEarningToken {
|
|
196
|
+
token_address: string;
|
|
197
|
+
token_name: string;
|
|
198
|
+
token_symbol: string;
|
|
199
|
+
chain_id: number;
|
|
200
|
+
image_url?: string;
|
|
201
|
+
platform: string;
|
|
202
|
+
creator_reward: number;
|
|
203
|
+
creator_reward_24h: number;
|
|
204
|
+
market_cap?: number;
|
|
205
|
+
price_usd: number;
|
|
206
|
+
}
|
|
207
|
+
export interface ProfileResponse {
|
|
208
|
+
id: string;
|
|
209
|
+
name: string;
|
|
210
|
+
slug: string;
|
|
211
|
+
description?: string;
|
|
212
|
+
twitter_username?: string;
|
|
213
|
+
profile_image_url?: string;
|
|
214
|
+
team_members?: ProfileTeamMember[];
|
|
215
|
+
products?: ProfileProduct[];
|
|
216
|
+
revenue_sources?: ProfileRevenueSource[];
|
|
217
|
+
total_earnings_usd?: number;
|
|
218
|
+
earning_tokens_count?: number;
|
|
219
|
+
total_tokens_created?: number;
|
|
220
|
+
top_earning_tokens?: TopEarningToken[];
|
|
221
|
+
status: string;
|
|
222
|
+
rejection_reason?: string;
|
|
223
|
+
pending_changes?: Record<string, unknown> | null;
|
|
224
|
+
submitted_at?: string;
|
|
225
|
+
approved_at?: string;
|
|
226
|
+
featured?: boolean;
|
|
227
|
+
updates?: ProfileUpdate[];
|
|
228
|
+
created_at?: string;
|
|
229
|
+
updated_at?: string;
|
|
230
|
+
}
|
|
231
|
+
export interface ProfileCreateRequest {
|
|
232
|
+
name: string;
|
|
233
|
+
description?: string;
|
|
234
|
+
team_members?: ProfileTeamMember[];
|
|
235
|
+
products?: ProfileProduct[];
|
|
236
|
+
}
|
|
237
|
+
export interface ProfileUpdateRequest {
|
|
238
|
+
name?: string;
|
|
239
|
+
description?: string;
|
|
240
|
+
team_members?: ProfileTeamMember[];
|
|
241
|
+
products?: ProfileProduct[];
|
|
242
|
+
}
|
|
243
|
+
export interface ProjectUpdateCreateRequest {
|
|
244
|
+
title: string;
|
|
245
|
+
content: string;
|
|
246
|
+
}
|
|
247
|
+
export interface ProjectUpdateResponse {
|
|
248
|
+
id: string;
|
|
249
|
+
title: string;
|
|
250
|
+
content: string;
|
|
251
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bfun-bot/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "BFunBot CLI — deploy tokens, check balances, and manage your AI agent from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"chalk": "^5.3.0",
|
|
41
41
|
"commander": "^12.1.0",
|
|
42
42
|
"ora": "^8.1.1",
|
|
43
|
+
"smol-toml": "^1.6.1",
|
|
43
44
|
"string-width": "^8.2.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|