@blockrun/franklin 3.1.0 → 3.1.1
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 +365 -150
- package/dist/plugins/runner.js +20 -3
- package/dist/ui/app.js +22 -35
- package/dist/ui/model-picker.d.ts +23 -0
- package/dist/ui/model-picker.js +52 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,213 +1,408 @@
|
|
|
1
|
-
<
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
<
|
|
4
|
-
|
|
3
|
+
<br>
|
|
4
|
+
|
|
5
|
+
<h1>
|
|
6
|
+
<code>◆</code> Franklin <code>◆</code>
|
|
7
|
+
</h1>
|
|
8
|
+
|
|
9
|
+
<h3>The AI agent with a wallet.</h3>
|
|
10
|
+
|
|
11
|
+
<p>
|
|
12
|
+
While others chat, Franklin spends.<br>
|
|
13
|
+
One wallet. Every model. Every paid API. Pay per action in USDC.
|
|
5
14
|
</p>
|
|
6
15
|
|
|
7
|
-
<p
|
|
8
|
-
|
|
16
|
+
<p>
|
|
17
|
+
<a href="https://npmjs.com/package/@blockrun/franklin"><img src="https://img.shields.io/npm/v/@blockrun/franklin.svg?style=flat-square&color=FFD700&label=npm" alt="npm"></a>
|
|
18
|
+
<a href="https://npmjs.com/package/@blockrun/franklin"><img src="https://img.shields.io/npm/dm/@blockrun/franklin.svg?style=flat-square&color=10B981&label=downloads" alt="downloads"></a>
|
|
19
|
+
<a href="https://github.com/BlockRunAI/franklin/stargazers"><img src="https://img.shields.io/github/stars/BlockRunAI/franklin?style=flat-square&color=FFD700&label=stars" alt="stars"></a>
|
|
20
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache_2.0-blue?style=flat-square" alt="license"></a>
|
|
21
|
+
<a href="https://github.com/BlockRunAI/franklin/actions"><img src="https://img.shields.io/github/actions/workflow/status/BlockRunAI/franklin/ci.yml?style=flat-square&label=ci" alt="ci"></a>
|
|
22
|
+
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript"></a>
|
|
23
|
+
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node-%E2%89%A520-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node"></a>
|
|
24
|
+
<a href="https://x402.org"><img src="https://img.shields.io/badge/x402-native-10B981?style=flat-square" alt="x402"></a>
|
|
25
|
+
<a href="https://t.me/blockrunAI"><img src="https://img.shields.io/badge/chat-telegram-26A5E4?style=flat-square&logo=telegram&logoColor=white" alt="telegram"></a>
|
|
9
26
|
</p>
|
|
10
27
|
|
|
11
|
-
<p
|
|
12
|
-
<a href="
|
|
13
|
-
<a href="
|
|
14
|
-
<a href="
|
|
28
|
+
<p>
|
|
29
|
+
<a href="#quick-start">Quick start</a> ·
|
|
30
|
+
<a href="#what-it-looks-like">Demo</a> ·
|
|
31
|
+
<a href="#why-franklin">Why</a> ·
|
|
32
|
+
<a href="#features">Features</a> ·
|
|
33
|
+
<a href="#plugin-sdk">Plugins</a> ·
|
|
34
|
+
<a href="#how-it-works">Architecture</a> ·
|
|
35
|
+
<a href="#roadmap">Roadmap</a> ·
|
|
36
|
+
<a href="#community">Community</a>
|
|
15
37
|
</p>
|
|
16
38
|
|
|
39
|
+
</div>
|
|
40
|
+
|
|
17
41
|
---
|
|
18
42
|
|
|
19
|
-
##
|
|
43
|
+
## The pitch in one paragraph
|
|
44
|
+
|
|
45
|
+
Every AI coding tool today writes text. `franklin` **spends money** — your USDC, from your wallet, on your behalf, under a hard budget cap — to actually get work done. One agent. 55+ models. Every paid API routed through the [x402](https://x402.org) micropayment protocol. No subscriptions. No API keys. No account. The wallet is your identity.
|
|
20
46
|
|
|
21
|
-
|
|
47
|
+
Built by the [BlockRun](https://blockrun.ai) team. Apache‑2.0. TypeScript. Ships as one npm package.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick start
|
|
22
52
|
|
|
23
53
|
```bash
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
✓ $1.50 Draft Reddit comments (Claude Sonnet 4.6)
|
|
34
|
-
✓ $0.10 Post 3 casts to Farcaster
|
|
35
|
-
|
|
36
|
-
Total spent: $8.58 · Time: 4m 32s
|
|
37
|
-
Deliverables: 5 blog posts, 3 hero images, 1 launch video, 1 music
|
|
38
|
-
track, 20 Reddit drafts, 3 Farcaster casts (live)
|
|
54
|
+
# 1. Install
|
|
55
|
+
npm install -g @blockrun/franklin
|
|
56
|
+
|
|
57
|
+
# 2. Run (free — uses NVIDIA Nemotron & Qwen3 Coder out of the box)
|
|
58
|
+
franklin
|
|
59
|
+
|
|
60
|
+
# 3. (optional) Fund a wallet to unlock Claude, GPT, Gemini, Grok, + paid APIs
|
|
61
|
+
franklin setup base # or: franklin setup solana
|
|
62
|
+
franklin balance # show address + USDC balance
|
|
39
63
|
```
|
|
40
64
|
|
|
41
|
-
**
|
|
65
|
+
That's it. Zero signup, zero credit card, zero phone verification. Send **$5 of USDC** to the wallet and you've unlocked every frontier model and every paid tool in the BlockRun gateway.
|
|
42
66
|
|
|
43
67
|
---
|
|
44
68
|
|
|
45
|
-
##
|
|
69
|
+
## What it looks like
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
```text
|
|
72
|
+
███████╗██████╗ █████╗ ███╗ ██╗██╗ ██╗██╗ ██╗███╗ ██╗
|
|
73
|
+
██╔════╝██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝██║ ██║████╗ ██║
|
|
74
|
+
█████╗ ██████╔╝███████║██╔██╗ ██║█████╔╝ ██║ ██║██╔██╗ ██║
|
|
75
|
+
██╔══╝ ██╔══██╗██╔══██║██║╚██╗██║██╔═██╗ ██║ ██║██║╚██╗██║
|
|
76
|
+
██║ ██║ ██║██║ ██║██║ ╚████║██║ ██╗███████╗██║██║ ╚████║
|
|
77
|
+
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═══╝
|
|
78
|
+
Franklin · The AI agent with a wallet · v3.1.0
|
|
51
79
|
|
|
52
|
-
|
|
80
|
+
Model : anthropic/claude-sonnet-4.6
|
|
81
|
+
Wallet: 0x7a9…4e2 · 12.47 USDC
|
|
53
82
|
|
|
54
|
-
|
|
83
|
+
> refactor src/auth.ts to use the new jwt helper, then run the tests
|
|
55
84
|
|
|
56
|
-
|
|
85
|
+
✓ Read src/auth.ts $0.0024
|
|
86
|
+
✓ Read src/lib/jwt.ts $0.0011
|
|
87
|
+
✓ Edit src/auth.ts (-24 +31 lines) $0.0082
|
|
88
|
+
✓ Bash npm test $0.0000
|
|
89
|
+
› 142 passing · 0 failing · 2.4s
|
|
57
90
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
| Writes text | ✅ | ✅ | ✅ | ✅ |
|
|
61
|
-
| Reads code | ✅ | ✅ | ✅ | ✅ |
|
|
62
|
-
| Multi-model | ❌ Claude only | ✅ BYOK | ✅ BYOK | ✅ **55+ via one wallet** |
|
|
63
|
-
| **Spends money autonomously** | ❌ | ❌ | ❌ | ✅ |
|
|
64
|
-
| **Wallet-native identity** | ❌ | ❌ | ❌ | ✅ |
|
|
65
|
-
| **Pay per action** | ❌ subscription | ❌ BYOK | ❌ BYOK | ✅ USDC |
|
|
66
|
-
| **Marketing workflows** | ❌ | ❌ | ❌ | ✅ (built-in) |
|
|
67
|
-
| **Trading workflows** | ❌ | ❌ | ❌ | ✅ (built-in) |
|
|
68
|
-
| No account / phone verification | ❌ | ⚠️ BYOK | ⚠️ BYOK | ✅ |
|
|
91
|
+
Done in 18s · 4 tool calls · 12.8k in / 2.1k out · $0.0117
|
|
92
|
+
```
|
|
69
93
|
|
|
70
|
-
|
|
94
|
+
Every tool call is itemised. Every token is priced. The wallet is the source of truth — when it hits zero, Franklin stops. No overdraft, no surprise bill, no rate-limit wall at 3am.
|
|
71
95
|
|
|
72
96
|
---
|
|
73
97
|
|
|
74
|
-
##
|
|
98
|
+
## Why Franklin
|
|
75
99
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
<table>
|
|
101
|
+
<tr>
|
|
102
|
+
<td width="33%" valign="top">
|
|
79
103
|
|
|
80
|
-
|
|
81
|
-
franklin setup base
|
|
104
|
+
### 💳 Pay per action
|
|
82
105
|
|
|
83
|
-
|
|
84
|
-
# Check address: franklin balance
|
|
106
|
+
No subscriptions. No "Pro" tier. You fund a wallet once and Franklin spends atomically per API call via HTTP 402. Cheap models cost fractions of a cent. Frontier models cost what they cost. When the wallet is empty, Franklin stops.
|
|
85
107
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
</td>
|
|
109
|
+
<td width="33%" valign="top">
|
|
110
|
+
|
|
111
|
+
### 🔐 Wallet is identity
|
|
112
|
+
|
|
113
|
+
No email. No phone. No KYC. Your Base or Solana address is your account. Portable across machines — `franklin setup` imports an existing wallet in one command. Your sessions, your config, your money.
|
|
114
|
+
|
|
115
|
+
</td>
|
|
116
|
+
<td width="33%" valign="top">
|
|
117
|
+
|
|
118
|
+
### 🧠 55+ models, one interface
|
|
119
|
+
|
|
120
|
+
Claude Sonnet/Opus 4.6, GPT‑5.4, Gemini 2.5 Pro, Grok 4, DeepSeek V3, GLM‑5.1, Kimi, Minimax, plus NVIDIA's free tier (Nemotron, Qwen3 Coder). Switch mid‑session with `/model`. Automatic fallback if one provider is down.
|
|
92
121
|
|
|
93
|
-
|
|
122
|
+
</td>
|
|
123
|
+
</tr>
|
|
124
|
+
</table>
|
|
94
125
|
|
|
95
126
|
---
|
|
96
127
|
|
|
97
|
-
##
|
|
128
|
+
## The comparison
|
|
98
129
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
└─────────────────────────────────────────────────┘
|
|
111
|
-
```
|
|
130
|
+
| | Claude Code | Aider | Cursor | **Franklin** |
|
|
131
|
+
| --------------------------------- | -------------- | ------------- | -------------- | --------------------- |
|
|
132
|
+
| Writes and edits code | ✅ | ✅ | ✅ | ✅ |
|
|
133
|
+
| Multi‑model support | ❌ Claude only | ✅ BYOK | ⚠️ limited | ✅ **55+ via 1 wallet** |
|
|
134
|
+
| Pricing model | Subscription | BYOK | Subscription | **Pay per action** |
|
|
135
|
+
| Identity | Account | API keys | Account | **Wallet** |
|
|
136
|
+
| Spend budget cap enforced on‑chain | ❌ | ❌ | ❌ | ✅ |
|
|
137
|
+
| Pay any API (images, search…) | ❌ | ❌ | ❌ | ✅ via x402 |
|
|
138
|
+
| Plugin SDK for custom workflows | ❌ | ⚠️ | ❌ | ✅ |
|
|
139
|
+
| Persistent sessions + search | ⚠️ | ⚠️ | ⚠️ | ✅ |
|
|
140
|
+
| Start free, no signup | ❌ | ⚠️ BYOK | ❌ | ✅ |
|
|
112
141
|
|
|
113
|
-
|
|
142
|
+
Franklin is the first agent in the **Autonomous Economic Agent** category — an agent that takes a goal, decides what to spend on, and executes within a hard budget cap enforced by the wallet.
|
|
114
143
|
|
|
115
144
|
---
|
|
116
145
|
|
|
117
146
|
## Features
|
|
118
147
|
|
|
119
|
-
|
|
148
|
+
<table>
|
|
149
|
+
<tr>
|
|
150
|
+
<td width="50%" valign="top">
|
|
151
|
+
|
|
152
|
+
**🧠 55+ models via one wallet**
|
|
153
|
+
Anthropic, OpenAI, Google, xAI, DeepSeek, GLM, Kimi, Minimax, NVIDIA free tier. One URL, one wallet, automatic fallback.
|
|
154
|
+
|
|
155
|
+
**💳 x402 micropayments**
|
|
156
|
+
HTTP 402 native. Every tool call is a tiny signed transaction against your USDC balance. No escrow, no refund API, no subscription.
|
|
157
|
+
|
|
158
|
+
**🚦 Smart tier routing**
|
|
159
|
+
Mark steps as `free` / `cheap` / `premium` — Franklin picks the best model per tier, per task. Configurable defaults in `franklin config`.
|
|
160
|
+
|
|
161
|
+
**🔌 Plugin SDK**
|
|
162
|
+
Core is workflow‑agnostic. Ship a new vertical (marketing, trading, research) without touching the agent loop. See [docs/plugin-sdk.md](docs/plugin-sdk.md).
|
|
163
|
+
|
|
164
|
+
**💾 Persistent sessions**
|
|
165
|
+
Every turn is streamed to disk with full metadata. Resume any session by ID. Survives crashes, reboots, context compaction.
|
|
166
|
+
|
|
167
|
+
**🔍 Full‑text session search**
|
|
168
|
+
`franklin search "payment loop"` — tokenised search across every past session. No SQLite, no indexing daemon, just fast.
|
|
169
|
+
|
|
170
|
+
</td>
|
|
171
|
+
<td width="50%" valign="top">
|
|
120
172
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
- **Smart tiers** — free / cheap / premium per task, user-configurable
|
|
124
|
-
- **Prompt caching** — 75% input token savings on Anthropic multi-turn (Hermes pattern)
|
|
125
|
-
- **Structured context compression** — Goal / Progress / Decisions / Files / Next Steps template
|
|
126
|
-
- **Session search** — `franklin search "payment loop"` — full-text across past sessions
|
|
127
|
-
- **Rich insights** — `franklin insights` — cost breakdown, daily activity sparklines, projections
|
|
173
|
+
**📊 Cost insights**
|
|
174
|
+
`franklin insights` — daily spend sparklines, per‑model breakdown, projections. Never wonder where the USDC went.
|
|
128
175
|
|
|
129
|
-
|
|
176
|
+
**⚡ Anthropic prompt caching**
|
|
177
|
+
Multi‑turn Sonnet/Opus sessions use ephemeral cache breakpoints (`system_and_3` strategy). Large input savings on long conversations.
|
|
130
178
|
|
|
131
|
-
|
|
179
|
+
**🛠 12 built‑in tools**
|
|
180
|
+
Read · Write · Edit · Bash · Glob · Grep · WebFetch · WebSearch · Task · ImageGen · AskUser · SubAgent.
|
|
181
|
+
|
|
182
|
+
**🔗 MCP auto‑discovery**
|
|
183
|
+
Drop‑in Model Context Protocol servers from `~/.blockrun/mcp.json`. Ships with awareness of `blockrun-mcp` (markets, X, prediction markets) and `unbrowse` (any site → API).
|
|
184
|
+
|
|
185
|
+
**🧭 Plan / Execute modes**
|
|
186
|
+
`/plan` to design read‑only, `/execute` to commit. No accidental writes while exploring.
|
|
187
|
+
|
|
188
|
+
**🪄 Slash ergonomics**
|
|
189
|
+
`/commit`, `/push`, `/pr`, `/review`, `/ultrathink`, `/compact`, `/model`, `/cost`, `/wallet`, and 20+ more.
|
|
190
|
+
|
|
191
|
+
</td>
|
|
192
|
+
</tr>
|
|
193
|
+
</table>
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Plugin SDK
|
|
198
|
+
|
|
199
|
+
Franklin is plugin‑first. The core agent doesn't know what a "marketing campaign" or "trading signal" is — it just runs workflows. Adding a new vertical is a single TypeScript file.
|
|
132
200
|
|
|
133
201
|
```typescript
|
|
134
202
|
import type { Plugin, Workflow } from '@blockrun/franklin/plugin-sdk';
|
|
135
203
|
|
|
136
|
-
const
|
|
137
|
-
id: '
|
|
138
|
-
name: '
|
|
139
|
-
description: '
|
|
204
|
+
const researchWorkflow: Workflow = {
|
|
205
|
+
id: 'research',
|
|
206
|
+
name: 'Competitor Research',
|
|
207
|
+
description: 'Find and summarise 10 competitors in a given space',
|
|
140
208
|
steps: [
|
|
141
|
-
{
|
|
142
|
-
|
|
143
|
-
|
|
209
|
+
{
|
|
210
|
+
name: 'search',
|
|
211
|
+
modelTier: 'none', // pure API call, no LLM
|
|
212
|
+
execute: async (ctx) => {
|
|
213
|
+
const results = await ctx.exa.search(ctx.input.topic, { limit: 10 });
|
|
214
|
+
return { output: `Found ${results.length}`, data: { results } };
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'summarise',
|
|
219
|
+
modelTier: 'cheap', // bulk work — use GLM or DeepSeek
|
|
220
|
+
execute: async (ctx) => {
|
|
221
|
+
const summaries = await ctx.llm.map(ctx.data.results, (r) =>
|
|
222
|
+
`Summarise in 3 bullets: ${r.text}`);
|
|
223
|
+
return { output: 'Summaries written', data: { summaries } };
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'synthesise',
|
|
228
|
+
modelTier: 'premium', // final output — use Claude Opus
|
|
229
|
+
execute: async (ctx) => {
|
|
230
|
+
const report = await ctx.llm.complete({
|
|
231
|
+
system: 'You are a strategy analyst.',
|
|
232
|
+
user: `Synthesise these into a 1‑page report: ${JSON.stringify(ctx.data.summaries)}`,
|
|
233
|
+
});
|
|
234
|
+
return { output: report };
|
|
235
|
+
},
|
|
236
|
+
},
|
|
144
237
|
],
|
|
145
|
-
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export const myPlugin: Plugin = {
|
|
241
|
+
id: 'research',
|
|
242
|
+
name: 'Research',
|
|
243
|
+
version: '0.1.0',
|
|
244
|
+
workflows: [researchWorkflow],
|
|
146
245
|
};
|
|
147
246
|
```
|
|
148
247
|
|
|
149
|
-
Full
|
|
248
|
+
Three steps, three tiers, three prices. The agent routes each step to the cheapest model that can do the job. Full guide: **[docs/plugin-sdk.md](docs/plugin-sdk.md)**.
|
|
150
249
|
|
|
151
|
-
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Slash commands
|
|
152
253
|
|
|
153
|
-
|
|
254
|
+
A curated subset of what `franklin` exposes inside an interactive session:
|
|
154
255
|
|
|
155
|
-
|
|
256
|
+
| Command | What it does |
|
|
257
|
+
| -------------------------------- | ---------------------------------------------------- |
|
|
258
|
+
| `/model [name]` | Interactive model picker, or switch directly |
|
|
259
|
+
| `/plan` · `/execute` | Read‑only planning mode → commit mode |
|
|
260
|
+
| `/ultrathink <q>` | Deep reasoning mode for hard problems |
|
|
261
|
+
| `/compact` | Structured context compression (Goal/Progress/Next) |
|
|
262
|
+
| `/search <q>` | Full‑text search across past sessions |
|
|
263
|
+
| `/history` · `/resume <id>` | Session management |
|
|
264
|
+
| `/commit` · `/push` · `/pr` | Git workflow helpers (Franklin writes the message) |
|
|
265
|
+
| `/review` · `/fix` · `/test` | One‑shot code review, bugfix, or test generation |
|
|
266
|
+
| `/explain <file>` · `/refactor` | Targeted explanation or refactor |
|
|
267
|
+
| `/cost` · `/wallet` | Session cost, wallet address & USDC balance |
|
|
268
|
+
| `/insights [--days N]` | Rich usage analytics |
|
|
269
|
+
| `/mcp` · `/doctor` · `/context` | Diagnostics |
|
|
270
|
+
| `/help` | Full command list |
|
|
156
271
|
|
|
157
|
-
|
|
272
|
+
Run `franklin` and type `/help` to see everything.
|
|
158
273
|
|
|
159
274
|
---
|
|
160
275
|
|
|
161
|
-
##
|
|
276
|
+
## CLI commands
|
|
277
|
+
|
|
278
|
+
Top‑level commands (`franklin --help`):
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
setup [chain] Create a wallet for payments (base | solana)
|
|
282
|
+
start Start the interactive agent (default command)
|
|
283
|
+
models List available models and pricing
|
|
284
|
+
balance Check wallet USDC balance
|
|
285
|
+
config <action> Manage config: default‑model, sonnet‑model, routing…
|
|
286
|
+
stats Usage statistics and cost savings vs. Claude Opus
|
|
287
|
+
insights Rich usage analytics (also /insights in session)
|
|
288
|
+
search <q> Full‑text session search (also /search in session)
|
|
289
|
+
social AI‑powered social engagement plugin
|
|
290
|
+
plugins List installed plugins
|
|
291
|
+
proxy Run a payment proxy for Claude Code compatibility
|
|
292
|
+
init / uninit Install/remove the background daemon (macOS LaunchAgent)
|
|
293
|
+
daemon <action> start | stop | status
|
|
294
|
+
logs Tail debug logs
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
162
298
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
299
|
+
## How it works
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
303
|
+
│ Franklin Agent │
|
|
304
|
+
│ Plugin SDK · Tool loop · Router · Session · Compaction │
|
|
305
|
+
├──────────────────────────────────────────────────────────────┤
|
|
306
|
+
│ BlockRun Gateway │
|
|
307
|
+
│ 55+ LLMs · Exa search · DALL·E · (soon) Runway · Suno │
|
|
308
|
+
│ CoinGecko · Dune · Apollo │
|
|
309
|
+
├──────────────────────────────────────────────────────────────┤
|
|
310
|
+
│ x402 Micropayment Protocol │
|
|
311
|
+
│ HTTP 402 · USDC on Base & Solana · on‑chain budget cap │
|
|
312
|
+
└──────────────────────────────────────────────────────────────┘
|
|
313
|
+
│
|
|
314
|
+
▼
|
|
315
|
+
┌─────────────┐
|
|
316
|
+
│ Your wallet │
|
|
317
|
+
│ (you own) │
|
|
318
|
+
└─────────────┘
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Every API call resolves to a signed micropayment against your wallet. You fund once; Franklin spends per task, priced by the upstream provider. No middlemen, no refund loop, no subscription renewal date.
|
|
176
322
|
|
|
177
323
|
---
|
|
178
324
|
|
|
179
|
-
##
|
|
325
|
+
## Project layout
|
|
180
326
|
|
|
181
|
-
|
|
327
|
+
```
|
|
328
|
+
src/
|
|
329
|
+
├── index.ts CLI entry (franklin + runcode alias)
|
|
330
|
+
├── banner.ts FRANKLIN gold→emerald gradient
|
|
331
|
+
├── agent/ Agent loop, LLM client, compaction, commands
|
|
332
|
+
├── tools/ 12 built‑in tools (Read/Write/Edit/Bash/…)
|
|
333
|
+
├── plugin-sdk/ Public plugin contract (Workflow/Plugin/Channel)
|
|
334
|
+
├── plugins/ Plugin registry + runner (plugin‑agnostic)
|
|
335
|
+
├── plugins-bundled/ Plugins shipped with Franklin
|
|
336
|
+
│ └── social/ AI‑powered social engagement
|
|
337
|
+
├── session/ Persistent sessions + FTS search
|
|
338
|
+
├── stats/ Usage tracking + insights engine
|
|
339
|
+
├── ui/ Ink‑based terminal UI
|
|
340
|
+
├── proxy/ Payment proxy for Claude Code compatibility
|
|
341
|
+
├── router/ Smart model tier routing (free/cheap/premium)
|
|
342
|
+
├── wallet/ Wallet management (Base + Solana)
|
|
343
|
+
├── mcp/ MCP server auto‑discovery
|
|
344
|
+
└── commands/ CLI subcommands
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Roadmap
|
|
350
|
+
|
|
351
|
+
### ✅ Shipped in v3.1.0
|
|
352
|
+
- Interactive agent with 12 built‑in tools
|
|
353
|
+
- 55+ models across Anthropic, OpenAI, Google, xAI, DeepSeek, GLM, Kimi, Minimax, NVIDIA free tier
|
|
354
|
+
- Plugin SDK (workflow, channel, plugin) — plugin‑first architecture
|
|
355
|
+
- **Social plugin** — AI‑powered engagement on Reddit / X
|
|
356
|
+
- x402 micropayment integration
|
|
357
|
+
- Wallet setup (Base + Solana), balance, funding flow
|
|
358
|
+
- Persistent sessions + full‑text search
|
|
359
|
+
- Cost insights engine with daily sparklines
|
|
360
|
+
- Anthropic prompt caching (ephemeral, multi‑breakpoint)
|
|
361
|
+
- Plan/Execute modes, slash commands, compaction, smart routing
|
|
362
|
+
- MCP auto‑discovery
|
|
363
|
+
- Proxy mode for Claude Code compatibility
|
|
364
|
+
|
|
365
|
+
### 🚧 In progress
|
|
366
|
+
- **Marketing plugin** — campaigns, Reddit/IG outreach, content generation pipelines
|
|
367
|
+
- **Trading plugin** — signals, market research, risk analysis
|
|
368
|
+
- Video (Runway) and audio (Suno) paid tool routes
|
|
369
|
+
- Per‑step budget caps inside workflows
|
|
370
|
+
- More languages in `franklin config` (`.yaml` support)
|
|
371
|
+
|
|
372
|
+
### 💭 Under consideration
|
|
373
|
+
- Shared plugin registry (install community plugins with `franklin plugins add`)
|
|
374
|
+
- Fiat on‑ramp inside `franklin setup`
|
|
375
|
+
- Mobile wallet handoff via WalletConnect
|
|
182
376
|
|
|
183
|
-
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Free tier, for real
|
|
380
|
+
|
|
381
|
+
Start with **zero dollars**. Franklin defaults to free NVIDIA models (Nemotron 70B, Qwen3 Coder 480B) that need no wallet funding. Rate‑limited to 60 requests/hour on the gateway, but genuinely free.
|
|
184
382
|
|
|
185
383
|
```bash
|
|
186
|
-
|
|
187
|
-
npm install -g @blockrun/franklin
|
|
384
|
+
franklin --model nvidia/nemotron-ultra-253b
|
|
188
385
|
```
|
|
189
386
|
|
|
190
|
-
|
|
387
|
+
Only fund a wallet when you want Claude, GPT, Gemini, Grok, or paid tools like Exa and DALL·E.
|
|
191
388
|
|
|
192
389
|
---
|
|
193
390
|
|
|
194
|
-
##
|
|
391
|
+
## Documentation
|
|
195
392
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
└── commands/ # CLI subcommands
|
|
210
|
-
```
|
|
393
|
+
- 📖 **[Plugin SDK guide](docs/plugin-sdk.md)** — build your own workflow
|
|
394
|
+
- 📜 **[Changelog](CHANGELOG.md)** — every release explained
|
|
395
|
+
- 🗺 **[Roadmap](docs/ROADMAP.md)** — what's coming next
|
|
396
|
+
- 🧭 **[Claude Code compatibility](docs/)** — use Franklin as a payment proxy
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Community
|
|
401
|
+
|
|
402
|
+
- 💬 **[Telegram](https://t.me/blockrunAI)** — realtime help, bug reports, feature requests
|
|
403
|
+
- 🐦 **[@BlockRunAI](https://x.com/BlockRunAI)** — release notes, demos
|
|
404
|
+
- 🐛 **[Issues](https://github.com/BlockRunAI/franklin/issues)** — bugs and feature requests
|
|
405
|
+
- 💡 **[Discussions](https://github.com/BlockRunAI/franklin/discussions)** — ideas, Q&A, show & tell
|
|
211
406
|
|
|
212
407
|
---
|
|
213
408
|
|
|
@@ -218,35 +413,55 @@ git clone https://github.com/BlockRunAI/franklin.git
|
|
|
218
413
|
cd franklin
|
|
219
414
|
npm install
|
|
220
415
|
npm run build
|
|
221
|
-
npm test
|
|
222
|
-
npm run test:e2e
|
|
416
|
+
npm test # deterministic local tests — no API calls
|
|
417
|
+
npm run test:e2e # live e2e tests — hits real models, needs wallet
|
|
223
418
|
node dist/index.js --help
|
|
224
419
|
```
|
|
225
420
|
|
|
421
|
+
**Contributing:** please open an issue first to discuss meaningful changes. For new features, prefer a plugin over a core change — [docs/plugin-sdk.md](docs/plugin-sdk.md) exists exactly so the core stays lean. PRs welcome on bugs, docs, typos, new models in pricing.
|
|
422
|
+
|
|
226
423
|
---
|
|
227
424
|
|
|
228
|
-
##
|
|
425
|
+
## Migrating from RunCode
|
|
426
|
+
|
|
427
|
+
If you were a RunCode user: **nothing breaks**. The `runcode` binary still works as an alias for `franklin` through the 60‑day compatibility window (until ~June 2026). Your config at `~/.blockrun/`, your wallet, your sessions — all migrate automatically.
|
|
428
|
+
|
|
429
|
+
To update when convenient:
|
|
229
430
|
|
|
230
|
-
|
|
431
|
+
```bash
|
|
432
|
+
npm uninstall -g @blockrun/runcode
|
|
433
|
+
npm install -g @blockrun/franklin
|
|
434
|
+
```
|
|
231
435
|
|
|
232
436
|
---
|
|
233
437
|
|
|
234
|
-
##
|
|
438
|
+
## Star history
|
|
235
439
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
-
|
|
239
|
-
-
|
|
440
|
+
<a href="https://star-history.com/#BlockRunAI/franklin&Date">
|
|
441
|
+
<picture>
|
|
442
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=BlockRunAI/franklin&type=Date&theme=dark">
|
|
443
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=BlockRunAI/franklin&type=Date">
|
|
444
|
+
<img alt="Star history" src="https://api.star-history.com/svg?repos=BlockRunAI/franklin&type=Date">
|
|
445
|
+
</picture>
|
|
446
|
+
</a>
|
|
240
447
|
|
|
241
448
|
---
|
|
242
449
|
|
|
243
450
|
## License
|
|
244
451
|
|
|
245
|
-
Apache
|
|
452
|
+
Apache‑2.0. See [LICENSE](LICENSE).
|
|
453
|
+
|
|
454
|
+
Built on the shoulders of giants: [x402](https://x402.org), [Anthropic](https://anthropic.com), [@modelcontextprotocol](https://github.com/modelcontextprotocol), [Ink](https://github.com/vadimdemedes/ink), [commander](https://github.com/tj/commander.js).
|
|
246
455
|
|
|
247
456
|
---
|
|
248
457
|
|
|
249
|
-
<
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
458
|
+
<div align="center">
|
|
459
|
+
|
|
460
|
+
**Franklin runs your money.**<br>
|
|
461
|
+
<sub>Your wallet. Your agent. Your results.</sub>
|
|
462
|
+
|
|
463
|
+
<br>
|
|
464
|
+
|
|
465
|
+
<sub>From the team at <a href="https://blockrun.ai">BlockRun</a>.</sub>
|
|
466
|
+
|
|
467
|
+
</div>
|
package/dist/plugins/runner.js
CHANGED
|
@@ -416,11 +416,12 @@ export function formatWorkflowResult(workflow, result) {
|
|
|
416
416
|
lines.push(sep);
|
|
417
417
|
for (const step of result.steps) {
|
|
418
418
|
const costStr = step.cost > 0 ? ` ($${step.cost.toFixed(4)})` : '';
|
|
419
|
-
const
|
|
419
|
+
const status = inferStepStatus(step);
|
|
420
|
+
const icon = status === 'error'
|
|
420
421
|
? '✗'
|
|
421
|
-
:
|
|
422
|
+
: status === 'aborted'
|
|
422
423
|
? '⚠'
|
|
423
|
-
:
|
|
424
|
+
: status === 'skipped'
|
|
424
425
|
? '○'
|
|
425
426
|
: '✓';
|
|
426
427
|
lines.push(` ${icon} ${step.name}: ${step.summary}${costStr}`);
|
|
@@ -430,6 +431,22 @@ export function formatWorkflowResult(workflow, result) {
|
|
|
430
431
|
lines.push(`${sep}\n`);
|
|
431
432
|
return lines.join('\n');
|
|
432
433
|
}
|
|
434
|
+
function inferStepStatus(step) {
|
|
435
|
+
if (step.status)
|
|
436
|
+
return step.status;
|
|
437
|
+
const summary = step.summary.toLowerCase();
|
|
438
|
+
if (summary.startsWith('error'))
|
|
439
|
+
return 'error';
|
|
440
|
+
if (summary.includes('abort'))
|
|
441
|
+
return 'aborted';
|
|
442
|
+
if (summary.includes('no posts found'))
|
|
443
|
+
return 'aborted';
|
|
444
|
+
if (summary.includes('[dry-run] skipped'))
|
|
445
|
+
return 'skipped';
|
|
446
|
+
if (summary.includes(' skipped'))
|
|
447
|
+
return 'skipped';
|
|
448
|
+
return 'ok';
|
|
449
|
+
}
|
|
433
450
|
export function formatWorkflowStats(workflow, stats) {
|
|
434
451
|
const lines = [];
|
|
435
452
|
const sep = '─'.repeat(40);
|
package/dist/ui/app.js
CHANGED
|
@@ -7,7 +7,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
7
7
|
import { render, Static, Box, Text, useApp, useInput, useStdout } from 'ink';
|
|
8
8
|
import Spinner from 'ink-spinner';
|
|
9
9
|
import TextInput from 'ink-text-input';
|
|
10
|
-
import { resolveModel } from './model-picker.js';
|
|
10
|
+
import { resolveModel, PICKER_CATEGORIES, PICKER_MODELS_FLAT, } from './model-picker.js';
|
|
11
11
|
import { estimateCost } from '../pricing.js';
|
|
12
12
|
// ─── Full-width input box ──────────────────────────────────────────────────
|
|
13
13
|
function InputBox({ input, setInput, onSubmit, model, balance, sessionCost, queued, focused, busy }) {
|
|
@@ -19,25 +19,6 @@ function InputBox({ input, setInput, onSubmit, model, balance, sessionCost, queu
|
|
|
19
19
|
: 'Type a message...';
|
|
20
20
|
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: '╭' + '─'.repeat(cols - 2) + '╮' }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "\u2502 " }), busy && !input ? _jsxs(Text, { color: "yellow", children: [_jsx(Spinner, { type: "dots" }), " "] }) : null, _jsx(Box, { width: busy && !input ? innerWidth - 4 : innerWidth, children: _jsx(TextInput, { value: input, onChange: setInput, onSubmit: onSubmit, placeholder: placeholder, focus: focused !== false }) }), _jsxs(Text, { dimColor: true, children: [' '.repeat(Math.max(0, cols - innerWidth - 4)), "\u2502"] })] }), _jsx(Text, { dimColor: true, children: '╰' + '─'.repeat(cols - 2) + '╯' }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { dimColor: true, children: [busy ? _jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }) : null, busy ? ' ' : '', model, " \u00B7 ", balance, sessionCost > 0.00001 ? _jsxs(Text, { color: "yellow", children: [" -$", sessionCost.toFixed(4)] }) : '', ' · esc to abort/quit'] }) })] }));
|
|
21
21
|
}
|
|
22
|
-
// ─── Model picker data ─────────────────────────────────────────────────────
|
|
23
|
-
const PICKER_MODELS = [
|
|
24
|
-
{ id: 'zai/glm-5.1', shortcut: 'glm', label: '🔥 GLM-5.1 (promo til Apr 15)', price: '$0.001/call', highlight: true },
|
|
25
|
-
{ id: 'zai/glm-5.1-turbo', shortcut: 'glm-turbo', label: 'GLM-5.1 Turbo', price: '$0.001/call' },
|
|
26
|
-
{ id: 'anthropic/claude-sonnet-4.6', shortcut: 'sonnet', label: 'Claude Sonnet 4.6', price: '$3/$15' },
|
|
27
|
-
{ id: 'anthropic/claude-opus-4.6', shortcut: 'opus', label: 'Claude Opus 4.6', price: '$5/$25' },
|
|
28
|
-
{ id: 'openai/gpt-5.4', shortcut: 'gpt', label: 'GPT-5.4', price: '$2.5/$15' },
|
|
29
|
-
{ id: 'google/gemini-2.5-pro', shortcut: 'gemini', label: 'Gemini 2.5 Pro', price: '$1.25/$10' },
|
|
30
|
-
{ id: 'deepseek/deepseek-chat', shortcut: 'deepseek', label: 'DeepSeek V3', price: '$0.28/$0.42' },
|
|
31
|
-
{ id: 'google/gemini-2.5-flash', shortcut: 'flash', label: 'Gemini 2.5 Flash', price: '$0.15/$0.6' },
|
|
32
|
-
{ id: 'openai/gpt-5-mini', shortcut: 'mini', label: 'GPT-5 Mini', price: '$0.25/$2' },
|
|
33
|
-
{ id: 'anthropic/claude-haiku-4.5-20251001', shortcut: 'haiku', label: 'Claude Haiku 4.5', price: '$1/$5' },
|
|
34
|
-
{ id: 'openai/gpt-5-nano', shortcut: 'nano', label: 'GPT-5 Nano', price: '$0.05/$0.4' },
|
|
35
|
-
{ id: 'deepseek/deepseek-reasoner', shortcut: 'r1', label: 'DeepSeek R1', price: '$0.28/$0.42' },
|
|
36
|
-
{ id: 'openai/o4-mini', shortcut: 'o4', label: 'O4 Mini', price: '$1.1/$4.4' },
|
|
37
|
-
{ id: 'nvidia/nemotron-ultra-253b', shortcut: 'free', label: 'Nemotron Ultra 253B', price: 'FREE' },
|
|
38
|
-
{ id: 'nvidia/qwen3-coder-480b', shortcut: 'qwen-coder', label: 'Qwen3 Coder 480B', price: 'FREE' },
|
|
39
|
-
{ id: 'nvidia/devstral-2-123b', shortcut: 'devstral', label: 'Devstral 2 123B', price: 'FREE' },
|
|
40
|
-
];
|
|
41
22
|
function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain, startWithPicker, onSubmit, onModelChange, onAbort, onExit, }) {
|
|
42
23
|
const { exit } = useApp();
|
|
43
24
|
const [input, setInput] = useState('');
|
|
@@ -51,7 +32,7 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
|
|
|
51
32
|
const [committedResponses, setCommittedResponses] = useState([]);
|
|
52
33
|
// Short preview of latest response shown in dynamic area (last ~5 lines, cleared on next turn)
|
|
53
34
|
const [responsePreview, setResponsePreview] = useState('');
|
|
54
|
-
const [currentModel, setCurrentModel] = useState(initialModel ||
|
|
35
|
+
const [currentModel, setCurrentModel] = useState(initialModel || PICKER_MODELS_FLAT[0].id);
|
|
55
36
|
const [ready, setReady] = useState(!startWithPicker);
|
|
56
37
|
const [mode, setMode] = useState(startWithPicker ? 'model-picker' : 'input');
|
|
57
38
|
const [pickerIdx, setPickerIdx] = useState(0);
|
|
@@ -148,9 +129,9 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
|
|
|
148
129
|
if (key.upArrow)
|
|
149
130
|
setPickerIdx(i => Math.max(0, i - 1));
|
|
150
131
|
else if (key.downArrow)
|
|
151
|
-
setPickerIdx(i => Math.min(
|
|
132
|
+
setPickerIdx(i => Math.min(PICKER_MODELS_FLAT.length - 1, i + 1));
|
|
152
133
|
else if (key.return) {
|
|
153
|
-
const selected =
|
|
134
|
+
const selected = PICKER_MODELS_FLAT[pickerIdx];
|
|
154
135
|
setCurrentModel(selected.id);
|
|
155
136
|
onModelChange(selected.id);
|
|
156
137
|
setStatusMsg(`Model → ${selected.label}`);
|
|
@@ -222,7 +203,7 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
|
|
|
222
203
|
setTimeout(() => setStatusMsg(''), 3000);
|
|
223
204
|
}
|
|
224
205
|
else {
|
|
225
|
-
const idx =
|
|
206
|
+
const idx = PICKER_MODELS_FLAT.findIndex(m => m.id === currentModel);
|
|
226
207
|
setPickerIdx(idx >= 0 ? idx : 0);
|
|
227
208
|
setMode('model-picker');
|
|
228
209
|
}
|
|
@@ -460,16 +441,13 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
|
|
|
460
441
|
delete globalThis.__runcode_submit;
|
|
461
442
|
};
|
|
462
443
|
}, [handleSubmit]);
|
|
463
|
-
// ──
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}), _jsx(Text, { children: " " })] }));
|
|
471
|
-
}
|
|
472
|
-
// ── Normal Mode ──
|
|
444
|
+
// ── Render ──
|
|
445
|
+
// Note: the tree is ALWAYS the same shape across mode changes. Static
|
|
446
|
+
// components (completedTools, committedResponses) stay mounted so Ink
|
|
447
|
+
// doesn't discard already-committed scrollback when the model picker
|
|
448
|
+
// opens/closes. The picker is rendered inline below scrollback, and the
|
|
449
|
+
// InputBox is hidden while it's active.
|
|
450
|
+
const inPicker = mode === 'model-picker';
|
|
473
451
|
return (_jsxs(Box, { flexDirection: "column", children: [statusMsg && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "green", children: statusMsg }) })), showHelp && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Commands" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/model" }), " [name] Switch model (picker if no name)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/wallet" }), " Show wallet address & balance"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/cost" }), " Session cost & savings"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/retry" }), " Retry the last prompt"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/compact" }), " Compress conversation history"] }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 Coding \u2500\u2500" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/test" }), " Run tests"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/fix" }), " Fix last error"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/review" }), " Code review"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/explain" }), " file Explain code"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/search" }), " query Search codebase"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/refactor" }), " desc Refactor code"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/scaffold" }), " desc Generate boilerplate"] }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 Git \u2500\u2500" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/commit" }), " Commit changes"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/push" }), " Push to remote"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/pr" }), " Create pull request"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/status" }), " Git status"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/diff" }), " Git diff"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/log" }), " Git log"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/branch" }), " [name] Branches"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/stash" }), " Stash changes"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/undo" }), " Undo last commit"] }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 Analysis \u2500\u2500" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/security" }), " Security audit"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/lint" }), " Quality check"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/optimize" }), " Performance check"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/todo" }), " Find TODOs"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/deps" }), " Dependencies"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/clean" }), " Dead code removal"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/context" }), " Session info (model, tokens, mode)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/plan" }), " Enter plan mode (read-only tools)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/execute" }), " Exit plan mode (enable all tools)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/sessions" }), " List saved sessions"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/resume" }), " id Resume a saved session"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/clear" }), " Clear conversation display"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/doctor" }), " Diagnose setup issues"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/help" }), " This help"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/exit" }), " Quit"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " Shortcuts: sonnet, opus, gpt, gemini, deepseek, flash, free, r1, o4, nano, mini, haiku" })] })), showWallet && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Wallet" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" Chain: ", _jsx(Text, { color: "magenta", children: chain })] }), _jsxs(Text, { children: [" Address: ", _jsx(Text, { color: "cyan", children: walletAddress })] }), _jsxs(Text, { children: [" Balance: ", _jsx(Text, { color: "green", children: balance })] })] })), _jsx(Static, { items: completedTools, children: (tool) => (_jsx(Box, { marginLeft: 1, children: tool.error
|
|
474
452
|
? _jsxs(Text, { color: "red", children: [" \u2717 ", tool.name, " ", _jsxs(Text, { dimColor: true, children: [tool.elapsed, "ms", tool.preview ? ` — ${tool.preview}` : ''] })] })
|
|
475
453
|
: _jsxs(Text, { color: "green", children: [" \u2713 ", tool.name, " ", _jsxs(Text, { dimColor: true, children: [tool.elapsed, "ms", tool.preview ? ` — ${tool.preview}` : ''] })] }) }, tool.key)) }), _jsx(Static, { items: committedResponses, children: (r) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { wrap: "wrap", children: r.text }), (r.tokens.input > 0 || r.tokens.output > 0) && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { dimColor: true, children: [r.tokens.calls > 0 && r.tokens.input === 0
|
|
@@ -480,7 +458,16 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
|
|
|
480
458
|
setAskUserRequest(null);
|
|
481
459
|
setAskUserInput('');
|
|
482
460
|
r(answer);
|
|
483
|
-
}, focus: true })] })] })), Array.from(tools.entries()).map(([id, tool]) => (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsxs(Text, { color: "cyan", children: [' ', _jsx(Spinner, { type: "dots" }), ' ', tool.name, tool.preview ? _jsxs(Text, { dimColor: true, children: [": ", tool.preview] }) : null, _jsx(Text, { dimColor: true, children: (() => { const s = Math.round((Date.now() - tool.startTime) / 1000); return s > 0 ? ` ${s}s` : ''; })() })] }), tool.liveOutput ? (_jsxs(Text, { dimColor: true, children: [" \u2514 ", tool.liveOutput] })) : null] }, id))), thinking && (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsxs(Text, { color: "magenta", children: [" ", _jsx(Spinner, { type: "dots" }), " thinking..."] }), thinkingText && (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [" ", thinkingText.split('\n').pop()?.slice(0, 80)] }))] })), waiting && !thinking && tools.size === 0 && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: "yellow", children: [" ", _jsx(Spinner, { type: "dots" }), " ", _jsx(Text, { dimColor: true, children: currentModel })] }) })), streamText && (_jsx(Box, { marginTop: 0, marginBottom: 0, children: _jsx(Text, { wrap: "wrap", children: streamText }) })), responsePreview && !streamText && (_jsx(Box, { flexDirection: "column", marginBottom: 0, children: _jsx(Text, { wrap: "wrap", children: responsePreview }) })),
|
|
461
|
+
}, focus: true })] })] })), Array.from(tools.entries()).map(([id, tool]) => (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsxs(Text, { color: "cyan", children: [' ', _jsx(Spinner, { type: "dots" }), ' ', tool.name, tool.preview ? _jsxs(Text, { dimColor: true, children: [": ", tool.preview] }) : null, _jsx(Text, { dimColor: true, children: (() => { const s = Math.round((Date.now() - tool.startTime) / 1000); return s > 0 ? ` ${s}s` : ''; })() })] }), tool.liveOutput ? (_jsxs(Text, { dimColor: true, children: [" \u2514 ", tool.liveOutput] })) : null] }, id))), thinking && (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsxs(Text, { color: "magenta", children: [" ", _jsx(Spinner, { type: "dots" }), " thinking..."] }), thinkingText && (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [" ", thinkingText.split('\n').pop()?.slice(0, 80)] }))] })), waiting && !thinking && tools.size === 0 && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: "yellow", children: [" ", _jsx(Spinner, { type: "dots" }), " ", _jsx(Text, { dimColor: true, children: currentModel })] }) })), streamText && (_jsx(Box, { marginTop: 0, marginBottom: 0, children: _jsx(Text, { wrap: "wrap", children: streamText }) })), responsePreview && !streamText && (_jsx(Box, { flexDirection: "column", marginBottom: 0, children: _jsx(Text, { wrap: "wrap", children: responsePreview }) })), inPicker && (() => {
|
|
462
|
+
let flatIdx = 0;
|
|
463
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { bold: true, children: "Select a model " }), _jsx(Text, { dimColor: true, children: "(\u2191\u2193 navigate, Enter select, Esc cancel)" })] }), PICKER_CATEGORIES.map((cat) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u2500\u2500 ", cat.category, " \u2500\u2500"] }) }), cat.models.map((m) => {
|
|
464
|
+
const myIdx = flatIdx++;
|
|
465
|
+
const isSelected = myIdx === pickerIdx;
|
|
466
|
+
const isCurrent = m.id === currentModel;
|
|
467
|
+
const isHighlight = m.highlight === true;
|
|
468
|
+
return (_jsxs(Box, { marginLeft: 2, children: [_jsxs(Text, { inverse: isSelected, color: isSelected ? 'cyan' : isHighlight ? 'yellow' : undefined, bold: isSelected || isHighlight, children: [' ', m.label.padEnd(26), ' '] }), _jsxs(Text, { dimColor: true, children: [" ", m.shortcut.padEnd(14)] }), _jsx(Text, { color: m.price === 'FREE' ? 'green' : isHighlight ? 'yellow' : undefined, dimColor: !isHighlight && m.price !== 'FREE', children: m.price }), isCurrent && _jsx(Text, { color: "green", children: " \u2190" })] }, m.id));
|
|
469
|
+
})] }, cat.category))), _jsx(Box, { marginTop: 1, marginLeft: 2, children: _jsx(Text, { dimColor: true, children: "Your conversation stays above \u2014 picking a model keeps all history intact." }) })] }));
|
|
470
|
+
})(), !inPicker && (_jsx(InputBox, { input: (permissionRequest || askUserRequest) ? '' : input, setInput: (permissionRequest || askUserRequest) ? () => { } : setInput, onSubmit: (permissionRequest || askUserRequest) ? () => { } : handleSubmit, model: currentModel, balance: liveBalance, sessionCost: totalCost, queued: queuedInput || undefined, focused: !permissionRequest && !askUserRequest, busy: !askUserRequest && (waiting || thinking || tools.size > 0) }))] }));
|
|
484
471
|
}
|
|
485
472
|
export function launchInkUI(opts) {
|
|
486
473
|
let resolveInput = null;
|
|
@@ -7,6 +7,29 @@ export declare const MODEL_SHORTCUTS: Record<string, string>;
|
|
|
7
7
|
* Resolve a model name — supports shortcuts.
|
|
8
8
|
*/
|
|
9
9
|
export declare function resolveModel(input: string): string;
|
|
10
|
+
export interface ModelEntry {
|
|
11
|
+
id: string;
|
|
12
|
+
shortcut: string;
|
|
13
|
+
label: string;
|
|
14
|
+
price: string;
|
|
15
|
+
highlight?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface ModelCategory {
|
|
18
|
+
category: string;
|
|
19
|
+
models: ModelEntry[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Single source of truth for the /model picker.
|
|
23
|
+
* ~30 models across 6 categories. Every ID here is present in src/pricing.ts
|
|
24
|
+
* and every shortcut is in MODEL_SHORTCUTS above.
|
|
25
|
+
*
|
|
26
|
+
* Both the Ink UI picker (src/ui/app.tsx) and the readline picker
|
|
27
|
+
* (pickModel() below) import from this array. To add or remove models,
|
|
28
|
+
* edit this one place.
|
|
29
|
+
*/
|
|
30
|
+
export declare const PICKER_CATEGORIES: ModelCategory[];
|
|
31
|
+
/** Flat list of all picker models (for index-based navigation). */
|
|
32
|
+
export declare const PICKER_MODELS_FLAT: ModelEntry[];
|
|
10
33
|
/**
|
|
11
34
|
* Show interactive model picker. Returns the selected model ID.
|
|
12
35
|
* Falls back to text input if terminal doesn't support raw mode.
|
package/dist/ui/model-picker.js
CHANGED
|
@@ -65,44 +65,83 @@ export function resolveModel(input) {
|
|
|
65
65
|
const lower = input.trim().toLowerCase();
|
|
66
66
|
return MODEL_SHORTCUTS[lower] || input.trim();
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Single source of truth for the /model picker.
|
|
70
|
+
* ~30 models across 6 categories. Every ID here is present in src/pricing.ts
|
|
71
|
+
* and every shortcut is in MODEL_SHORTCUTS above.
|
|
72
|
+
*
|
|
73
|
+
* Both the Ink UI picker (src/ui/app.tsx) and the readline picker
|
|
74
|
+
* (pickModel() below) import from this array. To add or remove models,
|
|
75
|
+
* edit this one place.
|
|
76
|
+
*/
|
|
77
|
+
export const PICKER_CATEGORIES = [
|
|
78
|
+
{
|
|
79
|
+
category: '🔥 Promo (flat $0.001/call)',
|
|
80
|
+
models: [
|
|
81
|
+
{ id: 'zai/glm-5.1', shortcut: 'glm', label: 'GLM-5.1', price: '$0.001/call', highlight: true },
|
|
82
|
+
{ id: 'zai/glm-5.1-turbo', shortcut: 'glm-turbo', label: 'GLM-5.1 Turbo', price: '$0.001/call', highlight: true },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
69
85
|
{
|
|
70
|
-
category: '
|
|
86
|
+
category: '🧠 Smart routing (auto-pick)',
|
|
87
|
+
models: [
|
|
88
|
+
{ id: 'blockrun/auto', shortcut: 'auto', label: 'Auto', price: 'routed' },
|
|
89
|
+
{ id: 'blockrun/eco', shortcut: 'eco', label: 'Eco', price: 'cheapest' },
|
|
90
|
+
{ id: 'blockrun/premium', shortcut: 'premium', label: 'Premium', price: 'best' },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
category: '✨ Premium frontier',
|
|
71
95
|
models: [
|
|
72
96
|
{ id: 'anthropic/claude-sonnet-4.6', shortcut: 'sonnet', label: 'Claude Sonnet 4.6', price: '$3/$15' },
|
|
73
97
|
{ id: 'anthropic/claude-opus-4.6', shortcut: 'opus', label: 'Claude Opus 4.6', price: '$5/$25' },
|
|
74
98
|
{ id: 'openai/gpt-5.4', shortcut: 'gpt', label: 'GPT-5.4', price: '$2.5/$15' },
|
|
99
|
+
{ id: 'openai/gpt-5.4-pro', shortcut: 'gpt-5.4-pro', label: 'GPT-5.4 Pro', price: '$30/$180' },
|
|
75
100
|
{ id: 'google/gemini-2.5-pro', shortcut: 'gemini', label: 'Gemini 2.5 Pro', price: '$1.25/$10' },
|
|
76
|
-
{ id: '
|
|
101
|
+
{ id: 'google/gemini-3.1-pro', shortcut: 'gemini-3', label: 'Gemini 3.1 Pro', price: '$2/$12' },
|
|
102
|
+
{ id: 'xai/grok-4-0709', shortcut: 'grok-4', label: 'Grok 4', price: '$0.2/$1.5' },
|
|
103
|
+
{ id: 'xai/grok-3', shortcut: 'grok', label: 'Grok 3', price: '$3/$15' },
|
|
77
104
|
],
|
|
78
105
|
},
|
|
79
106
|
{
|
|
80
|
-
category: '
|
|
107
|
+
category: '🔬 Reasoning',
|
|
81
108
|
models: [
|
|
82
|
-
{ id: '
|
|
83
|
-
{ id: 'openai/
|
|
84
|
-
{ id: '
|
|
85
|
-
{ id: 'openai/gpt-5-
|
|
109
|
+
{ id: 'openai/o3', shortcut: 'o3', label: 'O3', price: '$2/$8' },
|
|
110
|
+
{ id: 'openai/o4-mini', shortcut: 'o4', label: 'O4 Mini', price: '$1.1/$4.4' },
|
|
111
|
+
{ id: 'openai/o1', shortcut: 'o1', label: 'O1', price: '$15/$60' },
|
|
112
|
+
{ id: 'openai/gpt-5.3-codex', shortcut: 'codex', label: 'GPT-5.3 Codex', price: '$1.75/$14' },
|
|
113
|
+
{ id: 'deepseek/deepseek-reasoner', shortcut: 'r1', label: 'DeepSeek R1', price: '$0.28/$0.42' },
|
|
114
|
+
{ id: 'xai/grok-4-1-fast-reasoning', shortcut: 'grok-fast', label: 'Grok 4.1 Fast R.', price: '$0.2/$0.5' },
|
|
86
115
|
],
|
|
87
116
|
},
|
|
88
117
|
{
|
|
89
|
-
category: '
|
|
118
|
+
category: '💰 Budget',
|
|
90
119
|
models: [
|
|
91
|
-
{ id: '
|
|
92
|
-
{ id: 'openai/
|
|
93
|
-
{ id: 'openai/
|
|
120
|
+
{ id: 'anthropic/claude-haiku-4.5-20251001', shortcut: 'haiku', label: 'Claude Haiku 4.5', price: '$1/$5' },
|
|
121
|
+
{ id: 'openai/gpt-5-mini', shortcut: 'mini', label: 'GPT-5 Mini', price: '$0.25/$2' },
|
|
122
|
+
{ id: 'openai/gpt-5-nano', shortcut: 'nano', label: 'GPT-5 Nano', price: '$0.05/$0.4' },
|
|
123
|
+
{ id: 'google/gemini-2.5-flash', shortcut: 'flash', label: 'Gemini 2.5 Flash', price: '$0.3/$2.5' },
|
|
124
|
+
{ id: 'deepseek/deepseek-chat', shortcut: 'deepseek', label: 'DeepSeek V3', price: '$0.28/$0.42' },
|
|
125
|
+
{ id: 'moonshot/kimi-k2.5', shortcut: 'kimi', label: 'Kimi K2.5', price: '$0.6/$3' },
|
|
126
|
+
{ id: 'minimax/minimax-m2.7', shortcut: 'minimax', label: 'Minimax M2.7', price: '$0.3/$1.2' },
|
|
94
127
|
],
|
|
95
128
|
},
|
|
96
129
|
{
|
|
97
|
-
category: 'Free (no USDC needed)',
|
|
130
|
+
category: '🆓 Free (no USDC needed)',
|
|
98
131
|
models: [
|
|
99
132
|
{ id: 'nvidia/nemotron-ultra-253b', shortcut: 'free', label: 'Nemotron Ultra 253B', price: 'FREE' },
|
|
100
133
|
{ id: 'nvidia/qwen3-coder-480b', shortcut: 'qwen-coder', label: 'Qwen3 Coder 480B', price: 'FREE' },
|
|
101
134
|
{ id: 'nvidia/devstral-2-123b', shortcut: 'devstral', label: 'Devstral 2 123B', price: 'FREE' },
|
|
102
135
|
{ id: 'nvidia/llama-4-maverick', shortcut: 'maverick', label: 'Llama 4 Maverick', price: 'FREE' },
|
|
136
|
+
{ id: 'nvidia/deepseek-v3.2', shortcut: 'deepseek-free', label: 'DeepSeek V3.2', price: 'FREE' },
|
|
137
|
+
{ id: 'nvidia/gpt-oss-120b', shortcut: 'gpt-oss', label: 'GPT OSS 120B', price: 'FREE' },
|
|
103
138
|
],
|
|
104
139
|
},
|
|
105
140
|
];
|
|
141
|
+
/** Flat list of all picker models (for index-based navigation). */
|
|
142
|
+
export const PICKER_MODELS_FLAT = PICKER_CATEGORIES.flatMap(c => c.models);
|
|
143
|
+
// Kept for backward compatibility with the readline pickModel() below.
|
|
144
|
+
const PICKER_MODELS = PICKER_CATEGORIES;
|
|
106
145
|
/**
|
|
107
146
|
* Show interactive model picker. Returns the selected model ID.
|
|
108
147
|
* Falls back to text input if terminal doesn't support raw mode.
|
package/package.json
CHANGED