@bfun-bot/cli 1.0.12 → 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 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
@@ -49,7 +49,7 @@ export function registerLlm(program) {
49
49
  return;
50
50
  }
51
51
  printCard('BFunBot LLM Credits', [
52
- ['Balance', chalk.bold.whiteBright(`$${res.balance_usd}`)],
52
+ ['Balance', chalk.bold(`$${res.balance_usd}`)],
53
53
  ]);
54
54
  // Show agent reload config
55
55
  if (res.agent_reload) {
@@ -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;
@@ -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.12",
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": {