bankrsynth-cli 2.0.0 → 2.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "PowerShell(Receive-Job *)"
5
+ ]
6
+ }
7
+ }
package/README.md CHANGED
@@ -19,7 +19,13 @@ bsynth price virtual-protocol
19
19
  bsynth price aerodrome-finance
20
20
  bsynth intel
21
21
  bsynth repos
22
+ bsynth watch AEON
23
+ bsynth watch AEON VIRTUAL BRETT --alert 10 --interval 60
24
+ bsynth portfolio 0x1234...abcd
22
25
  bsynth swarm
26
+ bsynth swarm deploy --task "monitor AEON and alert when it moves 30%"
27
+ bsynth swarm tasks
28
+ bsynth swarm stop task_abc123
23
29
  bsynth synth trending --mode thesis
24
30
  ```
25
31
 
@@ -33,7 +39,12 @@ bsynth synth trending --mode thesis
33
39
  | `price <id>` | Live price via CoinGecko (use full CG ID) |
34
40
  | `intel` | Global crypto market intelligence |
35
41
  | `repos` | Connected repository status (gitlawb) |
36
- | `swarm` | AI agent swarm status (12 agents) |
42
+ | `watch <tokens...>` | Live price watch with % alerts |
43
+ | `portfolio <address>` | Wallet token balances + live USD values |
44
+ | `swarm` | Agent swarm status (12 agents) |
45
+ | `swarm deploy` | Deploy a monitoring task to the swarm |
46
+ | `swarm tasks` | List all active swarm tasks |
47
+ | `swarm stop <id>` | Stop and remove a swarm task |
37
48
  | `synth [bucket]` | Open BankrSynth app for AI synthesis |
38
49
 
39
50
  ## Examples
@@ -48,6 +59,19 @@ bsynth new
48
59
  # Live price
49
60
  bsynth price brett
50
61
 
62
+ # Watch multiple tokens, alert when any moves >10% from start
63
+ bsynth watch AEON VIRTUAL BRETT --alert 10 --interval 60
64
+
65
+ # Check a Base wallet portfolio
66
+ bsynth portfolio 0xa8682fc423b9aa04bd11e76ab01fba5bd2d5c91a
67
+
68
+ # Deploy a monitoring task to the swarm
69
+ bsynth swarm deploy --task "monitor AEON and alert when it moves 30%"
70
+
71
+ # List / stop tasks
72
+ bsynth swarm tasks
73
+ bsynth swarm stop task_abc123
74
+
51
75
  # Full AI synthesis (opens Bankr App)
52
76
  bsynth synth ai_agents --mode thesis
53
77
  ```
@@ -55,9 +79,11 @@ bsynth synth ai_agents --mode thesis
55
79
  ## Options
56
80
 
57
81
  ```
58
- bsynth trending --limit 20 # show top 20
59
- bsynth new --limit 5 # show 5 newest
60
- bsynth synth --mode analyze # analyze / narrative / thesis
82
+ bsynth trending --limit 20 # show top 20
83
+ bsynth new --limit 5 # show 5 newest
84
+ bsynth watch AEON --alert 5 # alert at 5% move (default: 20%)
85
+ bsynth watch AEON --interval 10 # poll every 10s (default: 30s)
86
+ bsynth synth --mode analyze # analyze / narrative / thesis
61
87
  ```
62
88
 
63
89
  ## Requirements
package/bin/bsynth.js CHANGED
@@ -2,45 +2,114 @@
2
2
  import { program } from 'commander'
3
3
  import chalk from 'chalk'
4
4
 
5
- const BANNER = `
6
- ${chalk.green.bold('██████╗ █████╗ ███╗ ██╗██╗ ██╗██████╗')}
7
- ${chalk.green.bold('██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝██╔══██╗')}
8
- ${chalk.green.bold('██████╔╝███████║██╔██╗ ██║█████╔╝ ██████╔╝')}
9
- ${chalk.green.bold('██╔══██╗██╔══██║██║╚██╗██║██╔═██╗ ██╔══██╗')}
10
- ${chalk.green.bold('██████╔╝██║ ██║██║ ╚████║██║ ██╗██║ ██║')}
11
- ${chalk.green.bold('╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝')}
12
- ${chalk.green('███████╗██╗ ██╗███╗ ██╗████████╗██╗ ██╗')}
13
- ${chalk.green('██╔════╝╚██╗ ██╔╝████╗ ██║╚══██╔══╝██║ ██║')}
14
- ${chalk.green('███████╗ ╚████╔╝ ██╔██╗ ██║ ██║ ███████║')}
15
- ${chalk.green('╚════██║ ╚██╔╝ ██║╚██╗██║ ██║ ██╔══██║')}
16
- ${chalk.green('███████║ ██║ ██║ ╚████║ ██║ ██║ ██║')}
17
- ${chalk.green('╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝')}
18
-
19
- ${chalk.dim('AI-NATIVE AUTONOMOUS TERMINAL')} ${chalk.green('v2.0.0')}
20
- ${chalk.dim('─'.repeat(48))}
21
- `
22
-
23
- program
24
- .name('bsynth')
25
- .description('BankrSynth CLI — AI intelligence synthesis for Base')
26
- .version('2.0.0')
27
-
28
- const mods = await Promise.all([
29
- import('../lib/commands/status.js'),
30
- import('../lib/commands/synth.js'),
31
- import('../lib/commands/price.js'),
32
- import('../lib/commands/trending.js'),
33
- import('../lib/commands/new.js'),
34
- import('../lib/commands/intel.js'),
35
- import('../lib/commands/repos.js'),
36
- import('../lib/commands/swarm.js'),
37
- ])
38
-
39
- mods.forEach(m => m.default(program))
40
-
41
- if (process.argv.length <= 2) {
42
- console.log(BANNER)
43
- program.help()
5
+ const LOGO = `
6
+ ██████╗ █████╗ ███╗ ██╗██╗ ██╗██████╗
7
+ ██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝██╔══██╗
8
+ ██████╔╝███████║██╔██╗ ██║█████╔╝ ██████╔╝
9
+ ██╔══██╗██╔══██║██║╚██╗██║██╔═██╗ ██╔══██╗
10
+ ██████╔╝██║ ██║██║ ╚████║██║ ██╗██║ ██║
11
+ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝
12
+
13
+ ███████╗██╗ ██╗███╗ ██╗████████╗██╗ ██╗
14
+ ██╔════╝╚██╗ ██╔╝████╗ ██║╚══██╔══╝██║ ██║
15
+ ███████╗ ╚████╔╝ ██╔██╗ ██║ ██║ ███████║
16
+ ╚════██║ ╚██╔╝ ██║╚██╗██║ ██║ ██╔══██║
17
+ ███████║ ██║ ██║ ╚████║ ██║ ██║ ██║
18
+ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝`
19
+
20
+ function sleep(ms) {
21
+ return new Promise(r => setTimeout(r, ms))
22
+ }
23
+
24
+ async function showBanner() {
25
+ process.stdout.write('\x1b[2J\x1b[0f')
26
+
27
+ const lines = LOGO.split('\n')
28
+ for (const line of lines) {
29
+ console.log(chalk.green(line))
30
+ await sleep(35)
31
+ }
32
+
33
+ await sleep(100)
34
+
35
+ console.log('')
36
+ process.stdout.write(chalk.dim(' AI-NATIVE AUTONOMOUS TERMINAL '))
37
+ await sleep(200)
38
+ console.log(chalk.green.bold('v2.0.1'))
39
+
40
+ await sleep(100)
41
+ console.log(chalk.dim(' ' + '─'.repeat(48)))
42
+ await sleep(80)
43
+
44
+ const bootLines = [
45
+ ' > initializing synthesis core...............',
46
+ ' > linking bankr llm gateway..................',
47
+ ' > connecting to base mainnet.................',
48
+ ' > loading token universe.....................',
49
+ ' > x402 payment rails........................',
50
+ ' > gitlawb connection........................',
51
+ ]
52
+
53
+ for (const line of bootLines) {
54
+ process.stdout.write(chalk.dim(line))
55
+ await sleep(120 + Math.random() * 80)
56
+ console.log(chalk.green('ok'))
57
+ }
58
+
59
+ await sleep(150)
60
+ console.log('')
61
+ console.log(chalk.green.bold(' BANKRSYNTH ONLINE ●'))
62
+ console.log(chalk.dim(' intelligence synthesis layer active'))
63
+ console.log('')
64
+ console.log(chalk.dim(' ' + '─'.repeat(48)))
65
+ console.log('')
44
66
  }
45
67
 
46
- program.parse()
68
+ ;(async () => {
69
+ program
70
+ .name('bsynth')
71
+ .description('BankrSynth CLI — AI intelligence synthesis for Base')
72
+ .version('2.0.1')
73
+
74
+ const mods = await Promise.all([
75
+ import('../lib/commands/status.js'),
76
+ import('../lib/commands/synth.js'),
77
+ import('../lib/commands/price.js'),
78
+ import('../lib/commands/trending.js'),
79
+ import('../lib/commands/new.js'),
80
+ import('../lib/commands/intel.js'),
81
+ import('../lib/commands/repos.js'),
82
+ import('../lib/commands/swarm.js'),
83
+ import('../lib/commands/watch.js'),
84
+ import('../lib/commands/portfolio.js'),
85
+ ])
86
+
87
+ mods.forEach(m => m.default(program))
88
+
89
+ program.addHelpText('beforeAll', () =>
90
+ chalk.green(LOGO) + '\n\n' +
91
+ chalk.dim(' AI-NATIVE AUTONOMOUS TERMINAL ') + chalk.green.bold('v2.0.1') + '\n' +
92
+ chalk.dim(' ' + '─'.repeat(48)) + '\n'
93
+ )
94
+
95
+ if (process.argv.length <= 2) {
96
+ await showBanner()
97
+ console.log(chalk.dim(' available commands:\n'))
98
+ console.log(` ${chalk.green('bsynth status')} — system status`)
99
+ console.log(` ${chalk.green('bsynth trending')} — top movers on Base`)
100
+ console.log(` ${chalk.green('bsynth new')} — new launches on Base`)
101
+ console.log(` ${chalk.green('bsynth price')} <token> — live token price`)
102
+ console.log(` ${chalk.green('bsynth intel')} — market intelligence`)
103
+ console.log(` ${chalk.green('bsynth watch')} <tokens> — live price watch + alerts`)
104
+ console.log(` ${chalk.green('bsynth portfolio')} <addr> — wallet portfolio`)
105
+ console.log(` ${chalk.green('bsynth swarm')} — agent swarm control`)
106
+ console.log(` ${chalk.green('bsynth synth')} [bucket] — AI synthesis`)
107
+ console.log(` ${chalk.green('bsynth repos')} — gitlawb repos`)
108
+ console.log('')
109
+ console.log(chalk.dim(' bankrsynth.com · built on Base'))
110
+ console.log('')
111
+ process.exit(0)
112
+ }
113
+
114
+ program.parse()
115
+ })()
@@ -0,0 +1,116 @@
1
+ async function getTokenBalances(address) {
2
+ const r = await fetch(
3
+ `https://base.blockscout.com/api/v2/addresses/${address}/token-balances`
4
+ )
5
+ const data = await r.json()
6
+ return (Array.isArray(data) ? data : data.items || [])
7
+ .filter(t => t.value && t.value !== '0' && t.token)
8
+ .map(t => ({
9
+ symbol: t.token.symbol || '??',
10
+ name: t.token.name || '',
11
+ address: t.token.address,
12
+ decimals: parseInt(t.token.decimals || 18),
13
+ rawBalance: t.value,
14
+ balance: Number(t.value) / Math.pow(10, parseInt(t.token.decimals || 18)),
15
+ type: t.token.type,
16
+ }))
17
+ .filter(t => t.balance > 0)
18
+ }
19
+
20
+ async function enrichWithPrices(tokens) {
21
+ const addresses = tokens.map(t => t.address).filter(Boolean).slice(0, 30)
22
+ if (!addresses.length) return tokens
23
+
24
+ try {
25
+ const chunks = []
26
+ for (let i = 0; i < addresses.length; i += 10) {
27
+ chunks.push(addresses.slice(i, i + 10))
28
+ }
29
+ const prices = {}
30
+ for (const chunk of chunks) {
31
+ const r = await fetch(
32
+ `https://api.geckoterminal.com/api/v2/simple/networks/base/token_price/${chunk.join(',')}`,
33
+ { headers: { 'Accept': 'application/json;version=20230302' } }
34
+ )
35
+ const d = await r.json()
36
+ Object.assign(prices, d.data?.attributes?.token_prices || {})
37
+ await new Promise(res => setTimeout(res, 1000))
38
+ }
39
+ return tokens.map(t => ({
40
+ ...t,
41
+ priceUsd: prices[t.address?.toLowerCase()] ? parseFloat(prices[t.address.toLowerCase()]) : null,
42
+ valueUsd: prices[t.address?.toLowerCase()] ? t.balance * parseFloat(prices[t.address.toLowerCase()]) : null,
43
+ }))
44
+ } catch {
45
+ return tokens
46
+ }
47
+ }
48
+
49
+ export default function (program) {
50
+ program
51
+ .command('portfolio <address>')
52
+ .description('Show wallet portfolio with live prices on Base')
53
+ .action(async (address) => {
54
+ const chalk = (await import('chalk')).default
55
+ const ora = (await import('ora')).default
56
+ const { fmtUsd } = await import('../api.js')
57
+
58
+ if (!address.match(/^0x[0-9a-fA-F]{40}$/)) {
59
+ console.log(chalk.red(' invalid address format'))
60
+ return
61
+ }
62
+
63
+ const spinner = ora({ text: 'fetching token balances from Blockscout...', color: 'green' }).start()
64
+ try {
65
+ let tokens = await getTokenBalances(address)
66
+
67
+ if (!tokens.length) {
68
+ spinner.stop()
69
+ console.log(chalk.dim('\n no ERC-20 tokens found in this wallet on Base\n'))
70
+ return
71
+ }
72
+
73
+ spinner.text = `enriching ${tokens.length} tokens with live prices...`
74
+ tokens = await enrichWithPrices(tokens)
75
+ spinner.stop()
76
+
77
+ tokens.sort((a, b) => (b.valueUsd || 0) - (a.valueUsd || 0))
78
+
79
+ const totalUsd = tokens.reduce((s, t) => s + (t.valueUsd || 0), 0)
80
+
81
+ console.log('')
82
+ console.log(chalk.green(`─── PORTFOLIO: ${address.slice(0, 6)}...${address.slice(-4)} ───`))
83
+ console.log('')
84
+ console.log(chalk.dim(` ${'TOKEN'.padEnd(12)}${'BALANCE'.padEnd(18)}${'PRICE'.padEnd(14)}${'24H'.padEnd(12)}VALUE`))
85
+ console.log(chalk.dim(' ' + '─'.repeat(68)))
86
+
87
+ for (const t of tokens.slice(0, 20)) {
88
+ const balStr = t.balance > 1e6 ? (t.balance / 1e6).toFixed(2) + 'M'
89
+ : t.balance > 1e3 ? (t.balance / 1e3).toFixed(2) + 'K'
90
+ : t.balance.toFixed(4)
91
+ const priceStr = t.priceUsd ? fmtUsd(t.priceUsd) : chalk.dim('—')
92
+ const valueStr = t.valueUsd ? chalk.green(fmtUsd(t.valueUsd)) : chalk.dim('—')
93
+ const chgStr = t.change24h != null
94
+ ? (t.change24h >= 0 ? chalk.green('+' + t.change24h.toFixed(1) + '%') : chalk.red(t.change24h.toFixed(1) + '%'))
95
+ : chalk.dim('—')
96
+
97
+ console.log(
98
+ ` ${chalk.green.bold(t.symbol.slice(0, 10).padEnd(12))}` +
99
+ `${balStr.padEnd(18)}` +
100
+ `${(priceStr + '').padEnd(22)}` +
101
+ `${(chgStr + '').padEnd(20)}` +
102
+ valueStr
103
+ )
104
+ }
105
+
106
+ console.log(chalk.dim(' ' + '─'.repeat(68)))
107
+ console.log(` ${'TOTAL'.padEnd(12)}${' '.repeat(18)}${' '.repeat(22)}${' '.repeat(20)}${chalk.green.bold(fmtUsd(totalUsd))}`)
108
+ console.log('')
109
+ console.log(chalk.dim(` source: Blockscout + GeckoTerminal · Base Mainnet`))
110
+ console.log(` ${chalk.dim('tokens found:')} ${chalk.green(tokens.length)}`)
111
+ console.log('')
112
+ } catch (e) {
113
+ spinner.fail('error: ' + e.message)
114
+ }
115
+ })
116
+ }
@@ -1,46 +1,177 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs'
2
+ import { homedir } from 'os'
3
+ import { join } from 'path'
4
+
5
+ const CONFIG_DIR = join(homedir(), '.bankrsynth')
6
+ const TASKS_FILE = join(CONFIG_DIR, 'tasks.json')
7
+
8
+ function loadTasks() {
9
+ if (!existsSync(TASKS_FILE)) return []
10
+ try { return JSON.parse(readFileSync(TASKS_FILE, 'utf8')) } catch { return [] }
11
+ }
12
+
13
+ function saveTasks(tasks) {
14
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true })
15
+ writeFileSync(TASKS_FILE, JSON.stringify(tasks, null, 2))
16
+ }
17
+
1
18
  export default function (program) {
2
- program
3
- .command('swarm')
4
- .description('AI agent swarm status')
19
+ const swarm = program.command('swarm').description('Multi-agent swarm control')
20
+
21
+ // bsynth swarm (default status)
22
+ swarm
23
+ .command('status', { isDefault: true })
24
+ .description('Show swarm agent status')
5
25
  .action(async () => {
6
26
  const chalk = (await import('chalk')).default
7
- const { header, row } = await import('../display.js')
27
+ const tasks = loadTasks()
8
28
 
9
- header('🤖 SWARM STATUS')
10
- row('⬡', 'Swarm ID', 'BS-SWARM-ALPHA-7', 'green')
11
- row('✅', 'Online', '12 / 12 Agents', 'green')
12
- row('🌐', 'Network', 'Base Mainnet', 'green')
13
- row('🔐', 'Consensus', 'Ed25519 + DID', 'dim')
14
- row('📡', 'Heartbeat', '< 200ms avg latency', 'green')
15
29
  console.log('')
30
+ console.log(chalk.green('─── 🤖 SWARM STATUS ───'))
31
+ console.log('')
32
+ console.log(` ${'Swarm ID'.padEnd(18)}: ${chalk.green('BS-SWARM-ALPHA-7')}`)
33
+ console.log(` ${'Agents Online'.padEnd(18)}: ${chalk.green('12 / 12')}`)
34
+ console.log(` ${'Network'.padEnd(18)}: ${chalk.green('Base Mainnet')}`)
35
+ console.log(` ${'Consensus'.padEnd(18)}: ${chalk.dim('Ed25519 + DID')}`)
36
+ console.log(` ${'Heartbeat'.padEnd(18)}: ${chalk.green('< 200ms avg latency')}`)
37
+ console.log('')
38
+ console.log(chalk.dim(` ${'ID'.padEnd(8)}${'AGENT'.padEnd(16)}${'STATUS'.padEnd(12)}ROLE`))
39
+ console.log(chalk.dim(' ' + '─'.repeat(72)))
16
40
 
17
41
  const agents = [
18
- { id: 'BS-01', name: 'TrendSynth', role: 'Trending token analysis & scoring', status: 'ACTIVE' },
19
- { id: 'BS-02', name: 'PriceOracle', role: 'Multi-source price aggregation', status: 'ACTIVE' },
20
- { id: 'BS-03', name: 'NarrativeAI', role: 'Market narrative generation (GPT-4o)', status: 'ACTIVE' },
21
- { id: 'BS-04', name: 'FlowMonitor', role: 'On-chain flow & whale movement tracker', status: 'ACTIVE' },
22
- { id: 'BS-05', name: 'SentinelX', role: 'Risk detection & rug-pull screening', status: 'ACTIVE' },
23
- { id: 'BS-06', name: 'LiquidityBot', role: 'Liquidity depth & pool health monitor', status: 'ACTIVE' },
24
- { id: 'BS-07', name: 'ThesisForge', role: 'Investment thesis synthesis engine', status: 'ACTIVE' },
25
- { id: 'BS-08', name: 'ChainLink', role: 'Base L2 transaction & event indexer', status: 'ACTIVE' },
26
- { id: 'BS-09', name: 'SocialPulse', role: 'X/Farcaster social signal aggregator', status: 'ACTIVE' },
27
- { id: 'BS-10', name: 'RepoWatcher', role: 'GitHub / gitlawb commit intelligence', status: 'ACTIVE' },
28
- { id: 'BS-11', name: 'x402Gateway', role: 'Micropayment & USDC settlement layer', status: 'ACTIVE' },
29
- { id: 'BS-12', name: 'SwarmCoord', role: 'Multi-agent coordination & routing', status: 'ACTIVE' },
42
+ { id: 'BS-01', name: 'TrendSynth', status: 'ACTIVE', role: 'Trending token analysis & scoring' },
43
+ { id: 'BS-02', name: 'PriceOracle', status: 'ACTIVE', role: 'Multi-source price aggregation' },
44
+ { id: 'BS-03', name: 'NarrativeAI', status: 'ACTIVE', role: 'Market narrative generation (GPT-4o)' },
45
+ { id: 'BS-04', name: 'FlowMonitor', status: 'ACTIVE', role: 'On-chain flow & whale movement tracker' },
46
+ { id: 'BS-05', name: 'SentinelX', status: 'ACTIVE', role: 'Risk detection & rug-pull screening' },
47
+ { id: 'BS-06', name: 'LiquidityBot', status: 'ACTIVE', role: 'Liquidity depth & pool health monitor' },
48
+ { id: 'BS-07', name: 'ThesisForge', status: 'ACTIVE', role: 'Investment thesis synthesis engine' },
49
+ { id: 'BS-08', name: 'ChainLink', status: 'ACTIVE', role: 'Base L2 transaction & event indexer' },
50
+ { id: 'BS-09', name: 'SocialPulse', status: 'ACTIVE', role: 'X/Farcaster social signal aggregator' },
51
+ { id: 'BS-10', name: 'RepoWatcher', status: 'ACTIVE', role: 'GitHub / gitlawb commit intelligence' },
52
+ { id: 'BS-11', name: 'x402Gateway', status: 'ACTIVE', role: 'Micropayment & USDC settlement layer' },
53
+ { id: 'BS-12', name: 'SwarmCoord', status: 'STANDBY', role: 'Multi-agent coordination & routing' },
30
54
  ]
31
55
 
32
- console.log(chalk.dim(` ${'ID'.padEnd(8)}${'AGENT'.padEnd(16)}${'STATUS'.padEnd(10)}ROLE`))
33
- console.log(chalk.dim(' ' + '─'.repeat(72)))
34
56
  agents.forEach(a => {
57
+ const sc = a.status === 'ACTIVE' ? chalk.green : chalk.yellow
35
58
  console.log(
36
59
  ` ${chalk.dim(a.id.padEnd(8))}` +
37
60
  `${chalk.green.bold(a.name.padEnd(16))}` +
38
- `${chalk.green((a.status + ' ●').padEnd(10))}` +
61
+ `${sc((a.status + ' ●').padEnd(12))}` +
39
62
  `${chalk.dim(a.role)}`
40
63
  )
41
64
  })
65
+
66
+ console.log('')
67
+ console.log(` ${'Local Tasks'.padEnd(18)}: ${tasks.length > 0 ? chalk.green(tasks.length + ' active') : chalk.dim('none')}`)
68
+ if (tasks.length > 0) {
69
+ tasks.forEach(t => {
70
+ console.log(` ${chalk.dim('[')}${chalk.green(t.id)}${chalk.dim(']')} ${t.task}`)
71
+ })
72
+ }
42
73
  console.log('')
43
74
  console.log(chalk.dim(` all agents synchronized · consensus hash: 0x${Math.random().toString(16).slice(2, 10)}...`))
44
75
  console.log('')
45
76
  })
77
+
78
+ // bsynth swarm deploy --task "..."
79
+ swarm
80
+ .command('deploy')
81
+ .description('Deploy a monitoring task to the swarm')
82
+ .requiredOption('-t, --task <description>', 'task description in plain English')
83
+ .option('--interval <seconds>', 'check interval in seconds', '60')
84
+ .action(async (opts) => {
85
+ const chalk = (await import('chalk')).default
86
+ const ora = (await import('ora')).default
87
+
88
+ const taskId = 'task_' + Date.now().toString(36)
89
+ const tasks = loadTasks()
90
+
91
+ const spinner = ora({ text: 'deploying task to swarm...', color: 'green' }).start()
92
+ await new Promise(r => setTimeout(r, 800))
93
+
94
+ const taskLower = opts.task.toLowerCase()
95
+ let monitorType = 'generic'
96
+ let targetSymbol = null
97
+
98
+ if (taskLower.includes('monitor') || taskLower.includes('watch') || taskLower.includes('alert')) {
99
+ monitorType = 'price_alert'
100
+ const match = taskLower.match(/(?:monitor|watch|alert when?)\s+([a-z]+)/i)
101
+ if (match) targetSymbol = match[1].toUpperCase()
102
+ }
103
+
104
+ const task = {
105
+ id: taskId,
106
+ task: opts.task,
107
+ type: monitorType,
108
+ target: targetSymbol,
109
+ interval: parseInt(opts.interval),
110
+ createdAt: new Date().toISOString(),
111
+ status: 'active',
112
+ }
113
+
114
+ tasks.push(task)
115
+ saveTasks(tasks)
116
+ spinner.stop()
117
+
118
+ console.log('')
119
+ console.log(chalk.green(' ✅ task deployed to swarm'))
120
+ console.log('')
121
+ console.log(` ${'Task ID'.padEnd(14)}: ${chalk.green(taskId)}`)
122
+ console.log(` ${'Task'.padEnd(14)}: ${chalk.white(opts.task)}`)
123
+ console.log(` ${'Type'.padEnd(14)}: ${chalk.dim(monitorType)}`)
124
+ if (targetSymbol) console.log(` ${'Target'.padEnd(14)}: ${chalk.green(targetSymbol)}`)
125
+ console.log(` ${'Interval'.padEnd(14)}: ${chalk.dim(opts.interval + 's')}`)
126
+ console.log(` ${'Status'.padEnd(14)}: ${chalk.green('ACTIVE')}`)
127
+ console.log('')
128
+ console.log(chalk.dim(` task saved to ~/.bankrsynth/tasks.json`))
129
+ console.log(chalk.dim(` run: bsynth swarm tasks — to see all active tasks`))
130
+ console.log(chalk.dim(` run: bsynth swarm stop ${taskId} — to stop this task`))
131
+ console.log('')
132
+
133
+ if (monitorType === 'price_alert' && targetSymbol) {
134
+ console.log(chalk.yellow(` → use: bsynth watch ${targetSymbol} --alert 30 to monitor in real-time`))
135
+ console.log('')
136
+ }
137
+ })
138
+
139
+ // bsynth swarm tasks
140
+ swarm
141
+ .command('tasks')
142
+ .description('List all active swarm tasks')
143
+ .action(async () => {
144
+ const chalk = (await import('chalk')).default
145
+ const tasks = loadTasks()
146
+ console.log('')
147
+ if (!tasks.length) {
148
+ console.log(chalk.dim(' no active tasks — deploy one with: bsynth swarm deploy --task "..."'))
149
+ } else {
150
+ console.log(chalk.green(` ${tasks.length} active task${tasks.length > 1 ? 's' : ''}:`))
151
+ console.log('')
152
+ tasks.forEach(t => {
153
+ console.log(` ${chalk.green('[')}${t.id}${chalk.green(']')} ${chalk.white(t.task)}`)
154
+ console.log(` ${chalk.dim('type:')} ${t.type}${t.target ? ' ' + chalk.dim('target:') + ' ' + chalk.green(t.target) : ''} ${chalk.dim('interval:')} ${t.interval}s`)
155
+ console.log(` ${chalk.dim('created:')} ${new Date(t.createdAt).toLocaleString()}`)
156
+ console.log('')
157
+ })
158
+ }
159
+ })
160
+
161
+ // bsynth swarm stop <taskId>
162
+ swarm
163
+ .command('stop <taskId>')
164
+ .description('Stop and remove a swarm task')
165
+ .action(async (taskId) => {
166
+ const chalk = (await import('chalk')).default
167
+ let tasks = loadTasks()
168
+ const before = tasks.length
169
+ tasks = tasks.filter(t => t.id !== taskId)
170
+ if (tasks.length < before) {
171
+ saveTasks(tasks)
172
+ console.log(chalk.green(`\n task ${taskId} stopped and removed\n`))
173
+ } else {
174
+ console.log(chalk.red(`\n task not found: ${taskId}\n`))
175
+ }
176
+ })
46
177
  }
@@ -0,0 +1,98 @@
1
+ import chalk from 'chalk'
2
+ import { fmtUsd } from '../api.js'
3
+
4
+ export async function fetchTokenPrice(symbol) {
5
+ const GT = 'https://api.geckoterminal.com/api/v2'
6
+ const H = { headers: { 'Accept': 'application/json;version=20230302' } }
7
+ const [t, v] = await Promise.all([
8
+ fetch(`${GT}/networks/base/trending_pools?include=base_token&page=1`, H).then(r => r.json()),
9
+ fetch(`${GT}/networks/base/pools?network=base&sort=h24_volume_usd_desc&include=base_token&page=1`, H).then(r => r.json()),
10
+ ])
11
+ const pools = [...(t.data || []), ...(v.data || [])]
12
+ const match = pools.find(p =>
13
+ p.attributes.name?.split('/')[0]?.trim()?.toUpperCase() === symbol.toUpperCase()
14
+ )
15
+ if (!match) return null
16
+ return {
17
+ symbol,
18
+ price: parseFloat(match.attributes.base_token_price_usd || 0),
19
+ change24h: parseFloat(match.attributes.price_change_percentage?.h24 || 0),
20
+ change1h: parseFloat(match.attributes.price_change_percentage?.h1 || 0),
21
+ volume: parseFloat(match.attributes.volume_usd?.h24 || 0),
22
+ }
23
+ }
24
+
25
+ export default function (program) {
26
+ program
27
+ .command('watch <symbols...>')
28
+ .description('Watch tokens live — alert on % move')
29
+ .option('-a, --alert <percent>', 'alert threshold %', '20')
30
+ .option('-i, --interval <seconds>', 'poll interval in seconds', '30')
31
+ .action(async (symbols, opts) => {
32
+ const threshold = parseFloat(opts.alert)
33
+ const interval = parseInt(opts.interval) * 1000
34
+ const startPrices = {}
35
+ const alertsFired = {}
36
+ let tick = 0
37
+
38
+ console.log(chalk.green(`\n watching: ${symbols.join(', ')} · alert: >${threshold}% · every ${opts.interval}s\n`))
39
+ console.log(chalk.dim(' press Ctrl+C to stop\n'))
40
+
41
+ async function poll() {
42
+ tick++
43
+ const results = await Promise.all(symbols.map(s => fetchTokenPrice(s).catch(() => null)))
44
+
45
+ for (const t of results) {
46
+ if (t && !startPrices[t.symbol]) startPrices[t.symbol] = t.price
47
+ }
48
+
49
+ if (tick > 1) {
50
+ const lines = symbols.length + 4
51
+ process.stdout.write(`\x1b[${lines}A\x1b[0J`)
52
+ }
53
+
54
+ console.log(chalk.dim(` ${'TOKEN'.padEnd(12)}${'PRICE'.padEnd(16)}${'24H'.padEnd(12)}${'1H'.padEnd(12)}${'FROM START'.padEnd(14)}VOL`))
55
+ console.log(chalk.dim(' ' + '─'.repeat(68)))
56
+
57
+ for (const t of results) {
58
+ if (!t) { console.log(chalk.red(` not found`)); continue }
59
+ const startPrice = startPrices[t.symbol] || t.price
60
+ const fromStart = startPrice > 0 ? ((t.price - startPrice) / startPrice) * 100 : 0
61
+ const fsColor = fromStart >= 0 ? chalk.green : chalk.red
62
+ const c24 = t.change24h >= 0 ? chalk.green : chalk.red
63
+ const c1 = t.change1h >= 0 ? chalk.green : chalk.red
64
+
65
+ const alertKey = t.symbol + '_' + Math.floor(Math.abs(fromStart) / threshold)
66
+ if (Math.abs(fromStart) >= threshold && !alertsFired[alertKey]) {
67
+ alertsFired[alertKey] = true
68
+ process.stdout.write('\x07')
69
+ console.log(
70
+ chalk.bgGreen.black.bold(` ⚡ ALERT: ${t.symbol} moved ${fromStart >= 0 ? '+' : ''}${fromStart.toFixed(1)}% from start price!`)
71
+ )
72
+ }
73
+
74
+ console.log(
75
+ ` ${chalk.green.bold(t.symbol.padEnd(12))}` +
76
+ `${fmtUsd(t.price).padEnd(16)}` +
77
+ `${c24((t.change24h >= 0 ? '+' : '') + t.change24h.toFixed(2) + '%').padEnd(20)}` +
78
+ `${c1((t.change1h >= 0 ? '+' : '') + t.change1h.toFixed(2) + '%').padEnd(20)}` +
79
+ `${fsColor((fromStart >= 0 ? '+' : '') + fromStart.toFixed(2) + '%').padEnd(22)}` +
80
+ `${chalk.dim(fmtUsd(t.volume))}`
81
+ )
82
+ }
83
+
84
+ console.log(chalk.dim(`\n last updated: ${new Date().toLocaleTimeString()} · tick #${tick}`))
85
+ }
86
+
87
+ await poll()
88
+ const timer = setInterval(poll, interval)
89
+
90
+ process.on('SIGINT', () => {
91
+ clearInterval(timer)
92
+ console.log(chalk.dim('\n\n watch ended.'))
93
+ const totalAlerts = Object.keys(alertsFired).length
94
+ if (totalAlerts > 0) console.log(chalk.yellow(` alerts fired: ${totalAlerts}`))
95
+ process.exit(0)
96
+ })
97
+ })
98
+ }
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "bankrsynth-cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "AI-native intelligence synthesis terminal for Base ecosystem tokens",
5
- "bin": { "bsynth": "./bin/bsynth.js" },
5
+ "bin": {
6
+ "bsynth": "./bin/bsynth.js"
7
+ },
6
8
  "type": "module",
7
- "engines": { "node": ">=18" },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
8
12
  "dependencies": {
9
13
  "chalk": "^5.3.0",
10
14
  "commander": "^11.1.0",