@blockrun/franklin 3.6.19 → 3.6.20
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 +63 -38
- package/dist/agent/commands.js +12 -5
- package/dist/agent/loop.js +10 -0
- package/dist/commands/daemon.js +12 -2
- package/dist/commands/panel.js +4 -3
- package/dist/tools/subagent.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
|
|
28
28
|
<p>
|
|
29
29
|
<a href="#quick-start">Quick start</a> ·
|
|
30
|
-
<a href="#
|
|
30
|
+
<a href="#yopo">YOPO</a> ·
|
|
31
|
+
<a href="#a-new-category">Category</a> ·
|
|
31
32
|
<a href="#what-franklin-can-execute">What it does</a> ·
|
|
32
33
|
<a href="#smart-router">Smart Router</a> ·
|
|
33
|
-
<a href="#the-comparison">
|
|
34
|
-
<a href="#features">Features</a> ·
|
|
34
|
+
<a href="#the-comparison">vs. Claude Code</a> ·
|
|
35
35
|
<a href="#how-it-works">Architecture</a> ·
|
|
36
36
|
<a href="#community">Community</a>
|
|
37
37
|
</p>
|
|
@@ -42,10 +42,16 @@
|
|
|
42
42
|
|
|
43
43
|
## The pitch in one paragraph
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Claude Code writes code. Cursor writes code. Franklin writes code **and spends money to get the job done**. It holds a USDC wallet, picks the best model per task from 55+ providers, purchases trading data, generates images, pays for web search — all autonomously. You state an outcome and set a budget. Franklin decides what to call, what to pay for, and when to stop. Every paid action routes through the [x402](https://x402.org) micropayment protocol and settles against your own wallet. No subscriptions. No API keys. No account. The wallet is the identity.
|
|
46
46
|
|
|
47
47
|
Built by the [BlockRun](https://blockrun.ai) team. Apache-2.0. TypeScript. Ships as one npm package.
|
|
48
48
|
|
|
49
|
+
> **YOPO — You Only Pay Outcome**
|
|
50
|
+
>
|
|
51
|
+
> Not a subscription (pay for access). Not a generic pay-per-call (pay for trying).
|
|
52
|
+
> You pay only for the work Franklin delivers. Provider cost + 5%, settled per action
|
|
53
|
+
> in USDC. No monthly fees. No rate limits. No overdraft.
|
|
54
|
+
|
|
49
55
|
---
|
|
50
56
|
|
|
51
57
|
## Quick start
|
|
@@ -66,24 +72,41 @@ That's it. Zero signup, zero credit card, zero phone verification. Send **$5 of
|
|
|
66
72
|
|
|
67
73
|
---
|
|
68
74
|
|
|
75
|
+
## YOPO
|
|
76
|
+
|
|
77
|
+
**You Only Pay Outcome.** This is Franklin's pricing model, and it is the opposite of almost every other AI product you use.
|
|
78
|
+
|
|
79
|
+
| | You pay for... | Result |
|
|
80
|
+
| ----------------------- | -------------------------------------------- | ------------------------------------ |
|
|
81
|
+
| Subscription (ChatGPT Plus, Claude Max, Cursor Pro) | Access. Paid whether you use it or not. | $20–200/month, rate-limited. |
|
|
82
|
+
| Pay-per-call (OpenAI API, etc.) | Every attempt — even failed ones. | Hidden cost from retries, dead ends. |
|
|
83
|
+
| **Franklin (YOPO)** | **The outcome.** Each signed micropayment. | **Provider cost + 5%. No more.** |
|
|
84
|
+
|
|
85
|
+
Three consequences fall out of this:
|
|
86
|
+
|
|
87
|
+
1. **No subscriptions.** Use Franklin for $0.50 one week and $50 the next — you pay for compute actually consumed, nothing more.
|
|
88
|
+
2. **No rate limits.** Subscriptions throttle you when you need AI most. YOPO has no artificial caps — if you have USDC, you have access.
|
|
89
|
+
3. **No overdraft.** The wallet balance IS the hard limit. When it's empty, Franklin stops. No surprise bills, no 3 a.m. rate-limit walls.
|
|
90
|
+
|
|
91
|
+
Concretely — $1 in USDC gets you roughly:
|
|
92
|
+
- ~400K GPT-4o input tokens
|
|
93
|
+
- ~7M DeepSeek tokens
|
|
94
|
+
- ~13M Gemini Flash tokens
|
|
95
|
+
- ~20 DALL-E 3 images
|
|
96
|
+
- ~40 Exa neural web searches
|
|
97
|
+
- Unlimited NVIDIA GPT-OSS (free tier, no wallet needed)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
69
101
|
## A new category
|
|
70
102
|
|
|
71
103
|
> **Economic Agent**
|
|
72
|
-
>
|
|
104
|
+
>
|
|
73
105
|
> Software that can hold a wallet, price its own actions, spend toward an outcome, and stop at a hard budget cap.
|
|
74
106
|
|
|
75
|
-
|
|
107
|
+
Most AI products fit into one of three buckets: they answer questions, write code, or automate a fixed workflow. None of them can spend money.
|
|
76
108
|
|
|
77
|
-
|
|
78
|
-
- They answer questions.
|
|
79
|
-
- They write code.
|
|
80
|
-
- They automate a fixed workflow.
|
|
81
|
-
|
|
82
|
-
Franklin does something different. It combines:
|
|
83
|
-
- **Purchasing power** — it can pay for models, tools, and APIs.
|
|
84
|
-
- **Budget awareness** — it knows spend is part of the problem, not an afterthought.
|
|
85
|
-
- **Cross-vertical execution** — marketing, trading, research, code, ops.
|
|
86
|
-
- **Hard stopping conditions** — when the wallet or budget says stop, it stops.
|
|
109
|
+
Franklin can. It combines **purchasing power** (it pays for models, tools, and APIs), **budget awareness** (cost is part of the loop, not an afterthought), **cross-vertical execution** (code, trading, research, marketing, ops), and **hard stopping conditions** (wallet balance is a real constraint, not a suggestion).
|
|
87
110
|
|
|
88
111
|
That is why Franklin is an economic agent, not just a task agent.
|
|
89
112
|
|
|
@@ -165,7 +188,7 @@ After running `franklin social setup && franklin social login x`, Franklin can s
|
|
|
165
188
|
|
|
166
189
|
Code is still first-class. It is just **one workload**, not the category.
|
|
167
190
|
|
|
168
|
-
Every tool call is itemized. Every token is priced. When the wallet hits zero, Franklin stops. No overdraft, no surprise bill, no rate-limit wall at
|
|
191
|
+
Every tool call is itemized. Every token is priced. When the wallet hits zero, Franklin stops. No overdraft, no surprise bill, no rate-limit wall at 3 a.m. — this is YOPO in practice.
|
|
169
192
|
|
|
170
193
|
---
|
|
171
194
|
|
|
@@ -221,23 +244,23 @@ The router also learns from **your** usage. If you keep retrying a model for cod
|
|
|
221
244
|
<tr>
|
|
222
245
|
<td width="33%" valign="top">
|
|
223
246
|
|
|
224
|
-
### 💳
|
|
247
|
+
### 💳 AI is utility, not SaaS
|
|
225
248
|
|
|
226
|
-
|
|
249
|
+
You don't subscribe to electricity, you pay for what you use. Franklin brings the same model to AI. YOPO settlement means Franklin never bills you for access, only for outcomes. No monthly fees, no rate limits, no overdraft.
|
|
227
250
|
|
|
228
251
|
</td>
|
|
229
252
|
<td width="33%" valign="top">
|
|
230
253
|
|
|
231
|
-
###
|
|
254
|
+
### 🧠 Multi-model is the future
|
|
232
255
|
|
|
233
|
-
No
|
|
256
|
+
No single model is best at everything. Claude writes better code, Gemini handles longer context, DeepSeek costs 20x less for simple tasks. The Smart Router routes every request to the optimal model in <1ms — up to 89% savings vs. always using Opus.
|
|
234
257
|
|
|
235
258
|
</td>
|
|
236
259
|
<td width="33%" valign="top">
|
|
237
260
|
|
|
238
|
-
###
|
|
261
|
+
### 🔐 Wallet is identity
|
|
239
262
|
|
|
240
|
-
|
|
263
|
+
No email. No phone. No KYC. Your Base or Solana address is your account — portable, permissionless, global. API keys require US banking and account approval. A wallet requires only USDC.
|
|
241
264
|
|
|
242
265
|
</td>
|
|
243
266
|
</tr>
|
|
@@ -247,17 +270,19 @@ Marketing, trading, research, code, and anything else you can express as tools p
|
|
|
247
270
|
|
|
248
271
|
## The comparison
|
|
249
272
|
|
|
250
|
-
|
|
|
251
|
-
|
|
|
252
|
-
|
|
|
253
|
-
|
|
|
254
|
-
|
|
|
255
|
-
|
|
|
256
|
-
|
|
|
257
|
-
|
|
|
258
|
-
|
|
|
259
|
-
|
|
|
260
|
-
|
|
|
273
|
+
| | Claude Code | Cursor | Chatbots | **Franklin** |
|
|
274
|
+
| -------------------------------------- | --------------- | ---------------- | ---------------- | ------------------------------- |
|
|
275
|
+
| Writes code | ✅ | ✅ | ⚠️ | ✅ |
|
|
276
|
+
| **Spends money for you** | ❌ | ❌ | ❌ | ✅ **USDC wallet, x402** |
|
|
277
|
+
| **Buys data + APIs + images + search** | ❌ | ❌ | ❌ | ✅ **55+ APIs, one wallet** |
|
|
278
|
+
| Picks best model per task | ❌ Anthropic only | ❌ plan-tied | ❌ | ✅ **Smart Router, 55+ models** |
|
|
279
|
+
| Pricing model | Subscription | Subscription | Subscription | **YOPO** — per outcome, USDC |
|
|
280
|
+
| Monthly fee | $20–$200 | $20–$40 | $20+ | **$0** |
|
|
281
|
+
| Rate-limited | Yes | Yes | Yes | No — limited only by wallet |
|
|
282
|
+
| Works when provider goes down | ❌ | ❌ | ❌ | ✅ **routes to another** |
|
|
283
|
+
| Identity | Anthropic account | Cursor account | Account / email | ✅ **wallet, no signup** |
|
|
284
|
+
| Start free, no KYC | ❌ | ❌ | ❌ | ✅ |
|
|
285
|
+
| Source | Closed | Closed | Closed | **Apache 2.0, local-first** |
|
|
261
286
|
|
|
262
287
|
**Franklin is the economic agent category in one sentence:** software with a wallet that can spend toward a result.
|
|
263
288
|
|
|
@@ -281,8 +306,8 @@ Ask "generate a logo" — Franklin calls DALL-E / GPT Image, saves the result lo
|
|
|
281
306
|
**🧠 55+ models via one wallet**
|
|
282
307
|
Anthropic, OpenAI, Google, xAI, DeepSeek, GLM, Kimi, Minimax, NVIDIA free tier. One wallet, one interface, automatic fallback.
|
|
283
308
|
|
|
284
|
-
**💳 x402 micropayments**
|
|
285
|
-
HTTP 402 native. Every paid action is a signed micropayment
|
|
309
|
+
**💳 x402 micropayments (YOPO)**
|
|
310
|
+
HTTP 402 native. Every paid action is a signed USDC micropayment via EIP-712 — non-custodial, your keys never leave your machine. YOPO: you pay only for outcomes.
|
|
286
311
|
|
|
287
312
|
**🧠 Learned model router**
|
|
288
313
|
Trained on 2M+ real requests. Classifies your task and picks the best model from 55+ LLMs. Four profiles (auto/eco/premium/free). Adapts to your usage over time.
|
|
@@ -480,8 +505,8 @@ Apache-2.0. See [LICENSE](LICENSE).
|
|
|
480
505
|
|
|
481
506
|
<div align="center">
|
|
482
507
|
|
|
483
|
-
**
|
|
484
|
-
<sub>Your wallet. Your budget. Your results.</sub>
|
|
508
|
+
**The AI agent with a wallet.**<br>
|
|
509
|
+
<sub>YOPO — You Only Pay Outcome. Your wallet. Your budget. Your results.</sub>
|
|
485
510
|
|
|
486
511
|
<br>
|
|
487
512
|
|
package/dist/agent/commands.js
CHANGED
|
@@ -636,7 +636,12 @@ export async function handleSlashCommand(input, ctx) {
|
|
|
636
636
|
ctx.config.model = newModel;
|
|
637
637
|
ctx.config.baseModel = newModel; // Update recovery target so loop doesn't reset
|
|
638
638
|
ctx.config.onModelChange?.(newModel, 'user');
|
|
639
|
-
|
|
639
|
+
// Warn when switching from free to paid so users know charges start now
|
|
640
|
+
const isFree = (m) => m.startsWith('nvidia/') || m === 'blockrun/free';
|
|
641
|
+
const paidWarning = !isFree(newModel)
|
|
642
|
+
? ` ⚠️ (paid — charges from your wallet per call)`
|
|
643
|
+
: '';
|
|
644
|
+
ctx.onEvent({ kind: 'text_delta', text: `Model → **${newModel}**${paidWarning}\n` });
|
|
640
645
|
}
|
|
641
646
|
emitDone(ctx);
|
|
642
647
|
return { handled: true };
|
|
@@ -748,8 +753,9 @@ export async function handleSlashCommand(input, ctx) {
|
|
|
748
753
|
saveSolanaWallet(key);
|
|
749
754
|
ctx.onEvent({ kind: 'text_delta', text: `**Wallet Imported (Solana)**\n` +
|
|
750
755
|
` Address: ${address}\n` +
|
|
751
|
-
` Saved to: ~/.blockrun
|
|
752
|
-
|
|
756
|
+
` Saved to: ~/.blockrun/solana-wallet.json\n\n` +
|
|
757
|
+
`⚠️ IMPORTANT: This session is still using the OLD wallet.\n` +
|
|
758
|
+
` Run \`/exit\` now, then restart \`franklin\` to use the new wallet.\n`
|
|
753
759
|
});
|
|
754
760
|
}
|
|
755
761
|
else {
|
|
@@ -759,8 +765,9 @@ export async function handleSlashCommand(input, ctx) {
|
|
|
759
765
|
saveWallet(key);
|
|
760
766
|
ctx.onEvent({ kind: 'text_delta', text: `**Wallet Imported (Base)**\n` +
|
|
761
767
|
` Address: ${account.address}\n` +
|
|
762
|
-
` Saved to: ~/.blockrun
|
|
763
|
-
|
|
768
|
+
` Saved to: ~/.blockrun/wallet.json\n\n` +
|
|
769
|
+
`⚠️ IMPORTANT: This session is still using the OLD wallet.\n` +
|
|
770
|
+
` Run \`/exit\` now, then restart \`franklin\` to use the new wallet.\n`
|
|
764
771
|
});
|
|
765
772
|
}
|
|
766
773
|
}
|
package/dist/agent/loop.js
CHANGED
|
@@ -256,6 +256,16 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
256
256
|
};
|
|
257
257
|
pruneOldSessions(sessionId); // Cleanup old sessions on start, protect current
|
|
258
258
|
persistSessionMeta();
|
|
259
|
+
// Flush session meta on SIGINT/SIGTERM so mid-stream Ctrl+C doesn't
|
|
260
|
+
// leave a stale .meta.json (wrong turnCount/messageCount/cost).
|
|
261
|
+
const exitFlush = () => {
|
|
262
|
+
try {
|
|
263
|
+
persistSessionMeta();
|
|
264
|
+
}
|
|
265
|
+
catch { /* best effort */ }
|
|
266
|
+
};
|
|
267
|
+
process.once('SIGINT', exitFlush);
|
|
268
|
+
process.once('SIGTERM', exitFlush);
|
|
259
269
|
while (true) {
|
|
260
270
|
let input = await getUserInput();
|
|
261
271
|
if (input === null)
|
package/dist/commands/daemon.js
CHANGED
|
@@ -18,11 +18,21 @@ function readPid() {
|
|
|
18
18
|
function isRunning(pid) {
|
|
19
19
|
try {
|
|
20
20
|
process.kill(pid, 0);
|
|
21
|
-
return true;
|
|
22
21
|
}
|
|
23
22
|
catch {
|
|
24
23
|
return false;
|
|
25
24
|
}
|
|
25
|
+
// PID may have been recycled to an unrelated process. Confirm the
|
|
26
|
+
// command line actually looks like Franklin before trusting the PID.
|
|
27
|
+
try {
|
|
28
|
+
const { execSync } = require('node:child_process');
|
|
29
|
+
const cmd = execSync(`ps -p ${pid} -o command=`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
30
|
+
return /franklin|runcode|node.*dist\/index/.test(cmd);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// ps failed — fall back to assuming PID is ours (conservative, avoids false "running")
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
26
36
|
}
|
|
27
37
|
export async function daemonCommand(action, options) {
|
|
28
38
|
const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
|
|
@@ -34,7 +44,7 @@ export async function daemonCommand(action, options) {
|
|
|
34
44
|
case 'start': {
|
|
35
45
|
const existing = readPid();
|
|
36
46
|
if (existing && isRunning(existing)) {
|
|
37
|
-
console.log(chalk.yellow(`
|
|
47
|
+
console.log(chalk.yellow(`franklin daemon already running (PID ${existing})`));
|
|
38
48
|
console.log(chalk.dim(` Proxy: http://localhost:${port}/api`));
|
|
39
49
|
return;
|
|
40
50
|
}
|
package/dist/commands/panel.js
CHANGED
|
@@ -5,18 +5,19 @@ import chalk from 'chalk';
|
|
|
5
5
|
import { createPanelServer } from '../panel/server.js';
|
|
6
6
|
export async function panelCommand(options) {
|
|
7
7
|
const requestedPort = parseInt(options.port || '3100', 10);
|
|
8
|
-
// Handle port-in-use by trying up to
|
|
8
|
+
// Handle port-in-use by trying up to 20 subsequent ports.
|
|
9
|
+
const MAX_ATTEMPTS = 20;
|
|
9
10
|
const tryListen = (port, attempt) => {
|
|
10
11
|
const server = createPanelServer(port);
|
|
11
12
|
server.on('error', (err) => {
|
|
12
|
-
if (err.code === 'EADDRINUSE' && attempt <
|
|
13
|
+
if (err.code === 'EADDRINUSE' && attempt < MAX_ATTEMPTS) {
|
|
13
14
|
console.log(chalk.yellow(` Port ${port} busy — trying ${port + 1}...`));
|
|
14
15
|
tryListen(port + 1, attempt + 1);
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
18
|
console.error(chalk.red(`\n Panel failed to start: ${err.message}`));
|
|
18
19
|
if (err.code === 'EADDRINUSE') {
|
|
19
|
-
console.error(chalk.dim(` All ports from ${requestedPort} to ${requestedPort +
|
|
20
|
+
console.error(chalk.dim(` All ports from ${requestedPort} to ${requestedPort + MAX_ATTEMPTS - 1} are busy.`));
|
|
20
21
|
console.error(chalk.dim(` Try: franklin panel --port 4000`));
|
|
21
22
|
}
|
|
22
23
|
process.exit(1);
|
package/dist/tools/subagent.js
CHANGED
|
@@ -21,8 +21,16 @@ async function execute(input, ctx) {
|
|
|
21
21
|
const subModel = model || registeredParentModel || 'nvidia/nemotron-ultra-253b';
|
|
22
22
|
// Cost gate: if parent is free but sub-agent wants paid, ask user first.
|
|
23
23
|
// Prevents silent charges when the agent decides to spawn a more capable sub-agent.
|
|
24
|
-
if (isFreeModel(registeredParentModel) && !isFreeModel(subModel)
|
|
24
|
+
if (isFreeModel(registeredParentModel) && !isFreeModel(subModel)) {
|
|
25
25
|
const shortLabel = subModel.split('/').pop() || subModel;
|
|
26
|
+
if (!ctx.onAskUser) {
|
|
27
|
+
// No way to prompt the user (daemon/panel/non-interactive mode).
|
|
28
|
+
// Fail closed — refuse the paid spawn rather than silently charging.
|
|
29
|
+
return {
|
|
30
|
+
output: `Sub-agent declined: parent is on a free model but sub-agent requested a paid model (${shortLabel}). No interactive prompt available. Retry with model='nemotron' or run interactively to approve.`,
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
26
34
|
const answer = await ctx.onAskUser(`Sub-agent wants to use ${shortLabel} (paid). Approve?`, ['y', 'n']);
|
|
27
35
|
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
28
36
|
return {
|
package/package.json
CHANGED