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.
- package/.claude/settings.local.json +7 -0
- package/README.md +30 -4
- package/bin/bsynth.js +109 -40
- package/lib/commands/portfolio.js +116 -0
- package/lib/commands/swarm.js +156 -25
- package/lib/commands/watch.js +98 -0
- package/package.json +7 -3
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
|
-
| `
|
|
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
|
|
59
|
-
bsynth new --limit 5
|
|
60
|
-
bsynth
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/lib/commands/swarm.js
CHANGED
|
@@ -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
|
-
|
|
4
|
-
|
|
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
|
|
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',
|
|
19
|
-
{ id: 'BS-02', name: 'PriceOracle',
|
|
20
|
-
{ id: 'BS-03', name: 'NarrativeAI',
|
|
21
|
-
{ id: 'BS-04', name: 'FlowMonitor',
|
|
22
|
-
{ id: 'BS-05', name: 'SentinelX',
|
|
23
|
-
{ id: 'BS-06', name: 'LiquidityBot', role: 'Liquidity depth & pool health monitor'
|
|
24
|
-
{ id: 'BS-07', name: 'ThesisForge',
|
|
25
|
-
{ id: 'BS-08', name: 'ChainLink',
|
|
26
|
-
{ id: 'BS-09', name: 'SocialPulse',
|
|
27
|
-
{ id: 'BS-10', name: 'RepoWatcher',
|
|
28
|
-
{ id: 'BS-11', name: 'x402Gateway',
|
|
29
|
-
{ id: 'BS-12', name: 'SwarmCoord',
|
|
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
|
-
`${
|
|
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.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "AI-native intelligence synthesis terminal for Base ecosystem tokens",
|
|
5
|
-
"bin": {
|
|
5
|
+
"bin": {
|
|
6
|
+
"bsynth": "./bin/bsynth.js"
|
|
7
|
+
},
|
|
6
8
|
"type": "module",
|
|
7
|
-
"engines": {
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
8
12
|
"dependencies": {
|
|
9
13
|
"chalk": "^5.3.0",
|
|
10
14
|
"commander": "^11.1.0",
|