@blockrun/franklin 3.0.3 → 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 CHANGED
@@ -1,218 +1,411 @@
1
- <h1 align="center">Franklin</h1>
1
+ <div align="center">
2
2
 
3
- <p align="center">
4
- <strong>The AI agent with a wallet.</strong>
3
+ <br>
4
+
5
+ <h1>
6
+ <code>◆</code> &nbsp; Franklin &nbsp; <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 align="center">
8
- While others chat, Franklin spends — turning your USDC into real work.
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 align="center">
12
- <a href="https://npmjs.com/package/@blockrun/franklin"><img src="https://img.shields.io/npm/v/@blockrun/franklin.svg?style=flat-square&color=FFD700" alt="npm version"></a>
13
- <a href="LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-blue?style=flat-square" alt="License: Apache-2.0"></a>
14
- <a href="https://x402.org"><img src="https://img.shields.io/badge/x402-Payments-10B981?style=flat-square" alt="x402"></a>
15
- <a href="https://franklin.run"><img src="https://img.shields.io/badge/Marketing-franklin.run-06B6D4?style=flat-square" alt="franklin.run"></a>
16
- <a href="https://franklin.bet"><img src="https://img.shields.io/badge/Trading-franklin.bet-10B981?style=flat-square" alt="franklin.bet"></a>
28
+ <p>
29
+ <a href="#quick-start">Quick&nbsp;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>
17
37
  </p>
18
38
 
39
+ </div>
40
+
19
41
  ---
20
42
 
21
- ## Franklin runs your money
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.
22
46
 
23
- Franklin is the first AI agent that can actually **spend money** to get work done. Not just write text about it.
47
+ Built by the [BlockRun](https://blockrun.ai) team. Apache‑2.0. TypeScript. Ships as one npm package.
48
+
49
+ ---
50
+
51
+ ## Quick start
24
52
 
25
53
  ```bash
26
- $ franklin marketing campaign "Launch my AI coding app to indie hackers"
27
- Budget cap: $10
28
-
29
- $0.50 Research competitors via Exa
30
- ✓ $1.20 Write 5 blog post drafts (Claude Sonnet 4.6)
31
- ✓ $0.48 Generate 3 hero images (DALL-E 3)
32
- $3.50 Generate 15-second launch video (Runway)
33
- $0.80 Generate background music (Suno)
34
- $0.30 Find 20 relevant Reddit discussions
35
- ✓ $1.50 Draft Reddit comments (Claude Sonnet 4.6)
36
- ✓ $0.10 Post 3 casts to Farcaster
37
-
38
- Total spent: $8.58 · Time: 4m 32s
39
- Deliverables: 5 blog posts, 3 hero images, 1 launch video, 1 music
40
- 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
41
63
  ```
42
64
 
43
- **One wallet. Every model. Every paid API. No subscriptions. No accounts.**
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.
44
66
 
45
67
  ---
46
68
 
47
- ## Two products, one Franklin
69
+ ## What it looks like
48
70
 
49
- | | | |
50
- |---|---|---|
51
- | 🎯 | **[franklin.run](https://franklin.run)** | **Marketing agent** — campaigns, content, outreach, growth |
52
- | 📈 | **[franklin.bet](https://franklin.bet)** | **Trading agent** — signals, research, risk analysis |
71
+ ```text
72
+ ███████╗██████╗ █████╗ ███╗ ██╗██╗ ██╗██╗ ██╗███╗ ██╗
73
+ ██╔════╝██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝██║ ██║████╗ ██║
74
+ █████╗ ██████╔╝███████║██╔██╗ ██║█████╔╝ ██║ ██║██╔██╗ ██║
75
+ ██╔══╝ ██╔══██╗██╔══██║██║╚██╗██║██╔═██╗ ██║ ██║██║╚██╗██║
76
+ ██║ ██║ ██║██║ ██║██║ ╚████║██║ ██╗███████╗██║██║ ╚████║
77
+ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═══╝
78
+ Franklin · The AI agent with a wallet · v3.1.0
53
79
 
54
- Both paid per action in USDC. Both backed by the same [BlockRun gateway](https://blockrun.ai) and x402 protocol.
80
+ Model : anthropic/claude-sonnet-4.6
81
+ Wallet: 0x7a9…4e2 · 12.47 USDC
55
82
 
56
- ---
83
+ > refactor src/auth.ts to use the new jwt helper, then run the tests
57
84
 
58
- ## Why Franklin is different
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
59
90
 
60
- | | Claude Code | Hermes | OpenClaw | **Franklin** |
61
- |---|---|---|---|---|
62
- | Writes text | ✅ | ✅ | ✅ | ✅ |
63
- | Reads code | ✅ | ✅ | ✅ | ✅ |
64
- | Multi-model | ❌ Claude only | ✅ BYOK | ✅ BYOK | ✅ **55+ via one wallet** |
65
- | **Spends money autonomously** | ❌ | ❌ | ❌ | ✅ |
66
- | **Wallet-native identity** | ❌ | ❌ | ❌ | ✅ |
67
- | **Pay per action** | ❌ subscription | ❌ BYOK | ❌ BYOK | ✅ USDC |
68
- | **Marketing workflows** | ❌ | ❌ | ❌ | ✅ franklin.run |
69
- | **Trading workflows** | ❌ | ❌ | ❌ | ✅ franklin.bet |
70
- | No account / phone verification | ❌ | ⚠️ BYOK | ⚠️ BYOK | ✅ |
91
+ Done in 18s · 4 tool calls · 12.8k in / 2.1k out · $0.0117
92
+ ```
71
93
 
72
- **Franklin is the first AI agent in the "Autonomous Economic Agent" category** an agent that receives a goal, decides what to spend on, and executes autonomously within a hard budget cap enforced by the wallet.
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.
73
95
 
74
96
  ---
75
97
 
76
- ## Quick Start
98
+ ## Why Franklin
77
99
 
78
- ```bash
79
- # Install
80
- npm install -g @blockrun/franklin
100
+ <table>
101
+ <tr>
102
+ <td width="33%" valign="top">
81
103
 
82
- # Create a wallet (Base or Solana)
83
- franklin setup base
104
+ ### 💳 &nbsp;Pay per action
84
105
 
85
- # Fund wallet with USDC (any amount, $5 gets you started)
86
- # 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.
87
107
 
88
- # Run
89
- franklin # Interactive agent — code, research, anything
90
- franklin marketing run # Marketing workflow
91
- franklin trading signal "BTC" # Trading workflow
92
- franklin plugins # List installed plugins
93
- ```
108
+ </td>
109
+ <td width="33%" valign="top">
110
+
111
+ ### 🔐 &nbsp;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
+ ### 🧠 &nbsp;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.
94
121
 
95
- **Start free:** use `franklin` with NVIDIA's free models (Nemotron, Qwen3 Coder) — zero wallet funding required to try it out.
122
+ </td>
123
+ </tr>
124
+ </table>
96
125
 
97
126
  ---
98
127
 
99
- ## How it works
128
+ ## The comparison
100
129
 
101
- ```
102
- ┌─────────────────────────────────────────────────┐
103
- │ Franklin Agent (you) │
104
- │ Plugin SDK · CLI · Multi-model router │
105
- ├─────────────────────────────────────────────────┤
106
- │ BlockRun Gateway │
107
- │ Aggregates 55+ LLMs + Exa + DALL-E + (soon) │
108
- │ Runway + Suno + CoinGecko + Dune + Apollo │
109
- ├─────────────────────────────────────────────────┤
110
- │ x402 + Wallet Infrastructure
111
- │ HTTP 402 micropayments · USDC on Base/Solana │
112
- └─────────────────────────────────────────────────┘
113
- ```
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 | ❌ | ✅ |
114
141
 
115
- Every API call is paid atomically from your wallet via the x402 protocol. You fund once; Franklin spends per task. The wallet is your identity no accounts, no API keys, no KYC.
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.
116
143
 
117
144
  ---
118
145
 
119
146
  ## Features
120
147
 
121
- ### Core agent (55+ models)
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">
172
+
173
+ **📊 Cost insights**
174
+ `franklin insights` — daily spend sparklines, per‑model breakdown, projections. Never wonder where the USDC went.
122
175
 
123
- - **Multi-model routing** — Claude Sonnet/Opus, GPT-5.4, Gemini 2.5 Pro, DeepSeek, Grok 4, Kimi, GLM-5.1, Llama, NVIDIA free tier
124
- - **Automatic fallback** if one model fails, Franklin tries the next
125
- - **Smart tiers** — free / cheap / premium per task, user-configurable
126
- - **Prompt caching** — 75% input token savings on Anthropic multi-turn (Hermes pattern)
127
- - **Structured context compression** — Goal / Progress / Decisions / Files / Next Steps template
128
- - **Session search** — `franklin search "payment loop"` — full-text across past sessions
129
- - **Rich insights** — `franklin insights` — cost breakdown, daily activity sparklines, projections
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
- ### Plugin SDK
179
+ **🛠 12 built‑in tools**
180
+ Read · Write · Edit · Bash · Glob · Grep · WebFetch · WebSearch · Task · ImageGen · AskUser · SubAgent.
132
181
 
133
- Franklin is plugin-first. Core stays workflow-agnostic. Adding a new vertical requires zero core changes.
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.
134
200
 
135
201
  ```typescript
136
202
  import type { Plugin, Workflow } from '@blockrun/franklin/plugin-sdk';
137
203
 
138
- const myWorkflow: Workflow = {
139
- id: 'my-workflow',
140
- name: 'My Workflow',
141
- description: '...',
204
+ const researchWorkflow: Workflow = {
205
+ id: 'research',
206
+ name: 'Competitor Research',
207
+ description: 'Find and summarise 10 competitors in a given space',
142
208
  steps: [
143
- { name: 'search', modelTier: 'none', execute: async (ctx) => ({ ... }) },
144
- { name: 'filter', modelTier: 'cheap', execute: async (ctx) => ({ ... }) },
145
- { name: 'draft', modelTier: 'premium', execute: async (ctx) => ({ ... }) },
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
+ },
146
237
  ],
147
- // ...
148
238
  };
149
- ```
150
239
 
151
- Full plugin guide: [docs/plugin-sdk.md](docs/plugin-sdk.md)
240
+ export const myPlugin: Plugin = {
241
+ id: 'research',
242
+ name: 'Research',
243
+ version: '0.1.0',
244
+ workflows: [researchWorkflow],
245
+ };
246
+ ```
152
247
 
153
- ### Built-in tools
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)**.
154
249
 
155
- Read · Write · Edit · Bash · Glob · Grep · WebFetch · WebSearch · Task · ImageGen · AskUser · SubAgent (delegated child agents with isolated toolsets)
250
+ ---
156
251
 
157
- ### MCP integration
252
+ ## Slash commands
158
253
 
159
- Auto-discovers installed MCP servers including `blockrun-mcp` (market data, X data, prediction markets) and `unbrowse` (turn any website into an API). Add your own in `~/.blockrun/mcp.json`.
254
+ A curated subset of what `franklin` exposes inside an interactive session:
160
255
 
161
- ---
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 |
162
271
 
163
- ## Slash Commands
164
-
165
- | Command | Description |
166
- |---------|-------------|
167
- | `/model` | Interactive model picker · `/model <name>` to switch |
168
- | `/compact` | Compress conversation history (structured summary) |
169
- | `/search <query>` | Search past sessions by keyword |
170
- | `/insights` | Rich usage analytics — cost, trends, projections |
171
- | `/ultrathink` | Deep reasoning mode for hard problems |
172
- | `/plan` · `/execute` | Read-only planning mode → execution mode |
173
- | `/history` · `/resume <id>` | Session management |
174
- | `/commit` · `/push` · `/pr` · `/review` | Git workflow helpers |
175
- | `/cost` | Session cost and savings vs Claude Opus |
176
- | `/wallet` | Wallet address and USDC balance |
177
- | `/help` | Full command list |
272
+ Run `franklin` and type `/help` to see everything.
178
273
 
179
274
  ---
180
275
 
181
- ## Migration from RunCode
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
+ ```
182
296
 
183
- **Nothing breaks.** If you were using RunCode, your `runcode` command continues to work as an alias for `franklin` for 60 days. Your config at `~/.blockrun/` is unchanged. Your wallet, sessions, and settings all migrate automatically.
297
+ ---
184
298
 
185
- The package name is now `@blockrun/franklin`. Update when convenient:
299
+ ## How it works
186
300
 
187
- ```bash
188
- npm uninstall -g @blockrun/runcode
189
- npm install -g @blockrun/franklin
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
+ └─────────────┘
190
319
  ```
191
320
 
192
- Both commands (`franklin` and `runcode`) work for the next 60 days. After v3.1.0 ships, `runcode` will be removed.
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.
193
322
 
194
323
  ---
195
324
 
196
- ## Architecture
325
+ ## Project layout
197
326
 
198
327
  ```
199
328
  src/
200
- ├── agent/ # Core agent loop, multi-model LLM client, compaction
201
- ├── tools/ # 12 built-in tools
202
- ├── plugin-sdk/ # Public plugin contract (Workflow, Channel, Plugin)
203
- ├── plugins/ # Plugin registry + runner (plugin-agnostic)
204
- ├── plugins-bundled/ # Ships with Franklin — social, marketing (more coming)
205
- ├── session/ # Persistent sessions + FTS search
206
- ├── stats/ # Usage tracking + insights engine
207
- ├── ui/ # Terminal UI (Ink-based)
208
- ├── proxy/ # Payment proxy for Claude Code compatibility
209
- ├── router/ # Smart model tier routing
210
- ├── wallet/ # Wallet management (Base + Solana)
211
- └── commands/ # CLI subcommands
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
212
345
  ```
213
346
 
214
347
  ---
215
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
376
+
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.
382
+
383
+ ```bash
384
+ franklin --model nvidia/nemotron-ultra-253b
385
+ ```
386
+
387
+ Only fund a wallet when you want Claude, GPT, Gemini, Grok, or paid tools like Exa and DALL·E.
388
+
389
+ ---
390
+
391
+ ## Documentation
392
+
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
406
+
407
+ ---
408
+
216
409
  ## Development
217
410
 
218
411
  ```bash
@@ -220,37 +413,55 @@ git clone https://github.com/BlockRunAI/franklin.git
220
413
  cd franklin
221
414
  npm install
222
415
  npm run build
223
- npm test # Deterministic local tests (no API calls)
224
- npm run test:e2e # Live end-to-end tests (hits real models)
416
+ npm test # deterministic local tests no API calls
417
+ npm run test:e2e # live e2e tests hits real models, needs wallet
225
418
  node dist/index.js --help
226
419
  ```
227
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
+
228
423
  ---
229
424
 
230
- ## Contributing
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:
231
430
 
232
- Contributions welcome. Open an issue first to discuss meaningful changes. For plugin ideas, see [docs/plugin-sdk.md](docs/plugin-sdk.md) — the plugin system is how most new features should land.
431
+ ```bash
432
+ npm uninstall -g @blockrun/runcode
433
+ npm install -g @blockrun/franklin
434
+ ```
233
435
 
234
436
  ---
235
437
 
236
- ## Links
438
+ ## Star history
237
439
 
238
- - **Marketing**: [franklin.run](https://franklin.run)
239
- - **Trading**: [franklin.bet](https://franklin.bet)
240
- - **Parent**: [BlockRun](https://blockrun.ai)
241
- - **npm**: [@blockrun/franklin](https://npmjs.com/package/@blockrun/franklin)
242
- - **Telegram**: [t.me/blockrunAI](https://t.me/blockrunAI)
243
- - **x402 protocol**: [x402.org](https://x402.org)
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>
244
447
 
245
448
  ---
246
449
 
247
450
  ## License
248
451
 
249
- Apache-2.0. See [LICENSE](LICENSE).
452
+ Apache2.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).
250
455
 
251
456
  ---
252
457
 
253
- <p align="center">
254
- <strong>Franklin runs your money.</strong><br>
255
- <sub>Your wallet, your agent, your results.</sub>
256
- </p>
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/banner.js CHANGED
@@ -8,23 +8,38 @@ const FRANKLIN_ART = [
8
8
  ' ██║ ██║ ██║██║ ██║██║ ╚████║██║ ██╗███████╗██║██║ ╚████║',
9
9
  ' ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═══╝',
10
10
  ];
11
- // Franklin brand colors
12
- // Metallic gold (#D4AF37) traditional gold bar / royal insignia color
13
- // Warmer and more premium than CSS gold (#FFD700); better on dark terminals
14
- // Emerald is the secondary, reserved for franklin.bet (trading) product color
15
- const GOLD = '#D4AF37';
16
- const EMERALD = '#10B981';
11
+ // Franklin brand gradient: gold → emerald
12
+ // Gold (#FFD700) is the $100 bill / Benjamins color — marketing product energy
13
+ // Emerald (#10B981) is the trading / money-moving color
14
+ // The gradient between them tells the Franklin story in one glance
15
+ const GOLD_START = '#FFD700';
16
+ const EMERALD_END = '#10B981';
17
+ function hexToRgb(hex) {
18
+ const m = hex.replace('#', '');
19
+ return [
20
+ parseInt(m.slice(0, 2), 16),
21
+ parseInt(m.slice(2, 4), 16),
22
+ parseInt(m.slice(4, 6), 16),
23
+ ];
24
+ }
25
+ function rgbToHex(r, g, b) {
26
+ const toHex = (n) => Math.round(n).toString(16).padStart(2, '0');
27
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
28
+ }
29
+ function interpolateHex(start, end, t) {
30
+ const [r1, g1, b1] = hexToRgb(start);
31
+ const [r2, g2, b2] = hexToRgb(end);
32
+ return rgbToHex(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
33
+ }
17
34
  export function printBanner(version) {
18
- // Pure gold FRANKLIN single brand color, maximum signature strength
19
- const goldFn = chalk.hex(GOLD);
20
- for (const line of FRANKLIN_ART) {
21
- console.log(goldFn(line));
35
+ // Smooth 6-row gradient: each line gets its own interpolated color
36
+ const steps = FRANKLIN_ART.length;
37
+ for (let i = 0; i < steps; i++) {
38
+ const t = i / (steps - 1);
39
+ const color = interpolateHex(GOLD_START, EMERALD_END, t);
40
+ console.log(chalk.hex(color)(FRANKLIN_ART[i]));
22
41
  }
23
- console.log(chalk.bold.hex(GOLD)(' Franklin') +
24
- chalk.dim(' · The AI agent with a wallet · v' + version));
25
- console.log(chalk.dim(' Marketing: ') +
26
- chalk.hex(GOLD)('franklin.run') +
27
- chalk.dim(' · Trading: ') +
28
- chalk.hex(EMERALD)('franklin.bet') +
29
- chalk.dim('\n'));
42
+ console.log(chalk.bold.hex(GOLD_START)(' Franklin') +
43
+ chalk.dim(' · The AI agent with a wallet · v' + version) +
44
+ '\n');
30
45
  }
package/dist/index.js CHANGED
@@ -29,8 +29,6 @@ program
29
29
  .name('franklin')
30
30
  .description('Franklin — The AI agent with a wallet.\n\n' +
31
31
  'While others chat, Franklin spends — turning your USDC into real work.\n\n' +
32
- ' Marketing workflows: franklin.run\n' +
33
- ' Trading workflows: franklin.bet\n\n' +
34
32
  'Pay per action in USDC on Base or Solana. No subscriptions. No accounts.')
35
33
  .version(version);
36
34
  program
@@ -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 icon = step.status === 'error'
419
+ const status = inferStepStatus(step);
420
+ const icon = status === 'error'
420
421
  ? '✗'
421
- : step.status === 'aborted'
422
+ : status === 'aborted'
422
423
  ? '⚠'
423
- : step.status === 'skipped'
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 || PICKER_MODELS[0].id);
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(PICKER_MODELS.length - 1, i + 1));
132
+ setPickerIdx(i => Math.min(PICKER_MODELS_FLAT.length - 1, i + 1));
152
133
  else if (key.return) {
153
- const selected = PICKER_MODELS[pickerIdx];
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 = PICKER_MODELS.findIndex(m => m.id === currentModel);
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
- // ── Model Picker ──
464
- if (mode === 'model-picker') {
465
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: ['\n', " Select a model ", _jsx(Text, { dimColor: true, children: "(\u2191\u2193 navigate, Enter select, Esc cancel)" })] }), _jsx(Text, { children: " " }), PICKER_MODELS.map((m, i) => {
466
- const isHighlight = 'highlight' in m && m.highlight;
467
- const isSelected = i === pickerIdx;
468
- const isCurrent = m.id === currentModel;
469
- 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(12)] }), _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));
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 }) })), _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) })] }));
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.
@@ -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
- const PICKER_MODELS = [
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: 'Popular',
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: 'deepseek/deepseek-chat', shortcut: 'deepseek', label: 'DeepSeek V3', price: '$0.28/$0.42' },
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: 'Budget',
107
+ category: '🔬 Reasoning',
81
108
  models: [
82
- { id: 'google/gemini-2.5-flash', shortcut: 'flash', label: 'Gemini 2.5 Flash', price: '$0.15/$0.6' },
83
- { id: 'openai/gpt-5-mini', shortcut: 'mini', label: 'GPT-5 Mini', price: '$0.25/$2' },
84
- { id: 'anthropic/claude-haiku-4.5-20251001', shortcut: 'haiku', label: 'Claude Haiku 4.5', price: '$1/$5' },
85
- { id: 'openai/gpt-5-nano', shortcut: 'nano', label: 'GPT-5 Nano', price: '$0.05/$0.4' },
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: 'Reasoning',
118
+ category: '💰 Budget',
90
119
  models: [
91
- { id: 'deepseek/deepseek-reasoner', shortcut: 'r1', label: 'DeepSeek R1', price: '$0.28/$0.42' },
92
- { id: 'openai/o4-mini', shortcut: 'o4', label: 'O4 Mini', price: '$1.1/$4.4' },
93
- { id: 'openai/o3', shortcut: 'o3', label: 'O3', price: '$2/$8' },
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.0.3",
4
- "description": "Franklin — The AI agent with a wallet. Marketing at franklin.run, trading at franklin.bet. Pay per action in USDC.",
3
+ "version": "3.1.1",
4
+ "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
@@ -54,7 +54,7 @@
54
54
  "type": "git",
55
55
  "url": "https://github.com/BlockRunAI/franklin"
56
56
  },
57
- "homepage": "https://franklin.run",
57
+ "homepage": "https://github.com/BlockRunAI/franklin",
58
58
  "engines": {
59
59
  "node": ">=20"
60
60
  },