Skip to content

Releases: BlockRunAI/Franklin

v3.2.4 — Roll back to v3.2.2 chafa colour portrait

11 Apr 22:15
ff9148b

Choose a tag to compare

User preference: the v3.2.2 chafa colour-block portrait looks more like an actual oil painting of Ben Franklin. v3.2.3's braille line-art was cleaner but felt abstract — you could see the silhouette but lost the skin tones, warm palette, and "painted portrait" feel.

Reverted

  • src/banner.ts → restored to v3.2.2's state exactly. 28 chars × 14 rows chafa half-block output with 256-colour palette. Skin tones, dark hair, warm jacket — the oil-painting character is back.
  • Layout: MIN_WIDTH_FOR_PORTRAIT = 100, PORTRAIT_WIDTH = 29, TEXT_TOP_OFFSET = 4 (v3.2.2 values).

Kept

  • Tagline stays at blockrun.ai · The AI agent with a wallet · vX.Y.Z (the v3.2.2 change, unaffected).

Net effect vs v3.2.2: just the version number.

Lesson

Braille's higher resolution is a win for line art — schematics, diagrams, sharp silhouettes. For a tonal subject like an oil-painting portrait, chafa's half-block + 256-colour palette preserves the character better. The colour IS the content, and the "mushy" effect of half-block cells acts as natural anti-aliasing rather than a flaw.

Not changed

Every other subsystem — franklin social, agent loop, wallet, tools, sessions. Identical to v3.2.3.


npm: @blockrun/franklin@3.2.4 · Full changelog: v3.2.3...v3.2.4

v3.2.3 — Braille portrait (recognizable Ben)

11 Apr 22:12
c6737da

Choose a tag to compare

User feedback on v3.2.2: "看不清" — the portrait was still too fuzzy to recognize. chafa's half-block output looked like a coloured blob at banner sizes. Switched converters and settings; you can now actually see it's Benjamin Franklin.

What changed

Converter: chafa → ascii-image-converter in braille mode

Braille characters (U+2800..U+28FF) encode 2×4 dot matrices per cell, giving 2.7× the effective resolution of chafa's half-block mode at the same visible size. For a face — which is all about silhouette and feature placement, not colour — braille is the right tool.

  • Half-blocks are for colour images (photos, screenshots). Each cell packs two 3-byte RGB values into one character.
  • Braille is for line portraits. Every dot is independent, every cell is 8 "pixels", and the result looks like a high-res line drawing instead of a mushy heatmap.

Portrait size: 28×14 → 34×16

Same cropped Duplessis painting source. At 34×16 braille, the effective pixel grid is 68×64 = 4,352 "pixels" — enough to show Ben's hairline, eye sockets, nose, mouth, collar, and shoulders clearly.

Conversion command

ascii-image-converter ben-face.jpg --dimensions 34,16 --braille --threshold 110

Threshold swept from 90→130; picked 110 as the cleanest silhouette without losing collar/shoulder definition.

Tinting

Braille characters carry no colour on their own, so we wrap them in chalk.hex('#E8E8E8') at render time for a dim-white "pencil portrait" look. The FRANKLIN gold → emerald gradient next to it is untouched.

Text alignment

The FRANKLIN gradient block now starts at portrait row 5, so it aligns with Ben's face region (head rows 1-4, face rows 5-10, shoulders 11-16) — the classic "portrait + nameplate" composition.

Layout thresholds

Side-by-side threshold raised 100 → 105 cols (34-col portrait + 3-col gap + 65-col FRANKLIN text + 3-col margin).

Dependencies

  • No new runtime deps. The portrait is baked into src/banner.ts as a plain Unicode string array. The npm package doesn't ship ascii-image-converter.
  • Build-time dep (optional): ascii-image-converter — only needed if we ever regenerate the portrait.

Not changed

Every other subsystem — franklin social, agent loop, wallet, tools, sessions. Identical to v3.2.2.


npm: @blockrun/franklin@3.2.3 · Full changelog: v3.2.2...v3.2.3

v3.2.2 — Bigger cropped portrait + blockrun.ai tagline

11 Apr 21:53
43a8dbc

Choose a tag to compare

Two polish fixes on the v3.2.1 portrait banner, both from user feedback.

Fixed

1. Portrait was too small to recognize

User report: "看不清" (can't see clearly).

v3.2.1 ran chafa on the full 2403×2971 Duplessis painting at size 20×10. The face occupied maybe 30% of the resulting 17×10 block — the rest was background, torso, and jacket. At that size, the face itself was ~12×5 "pixels" — far too few to recognize.

Fix:

  1. Pre-crop source image to a 1400×1400 square centred on the face:
    sips --cropToHeightWidth 1400 1400 --cropOffset 400 500 ben-franklin.jpg --out ben-face.jpg
  2. Run chafa at size 30x14 on the cropped square → outputs 28 chars × 14 rows, with all pixels dedicated to the face.
  3. Raise the side-by-side layout threshold from 90 to 100 terminal cols to accommodate the wider portrait.
  4. Vertically re-centre the FRANKLIN text inside the 14-row portrait (4 rows padding above, 4 below).

Result: the face fills the portrait. You can actually see it's Ben.

2. Redundant "Franklin" word in tagline

The big block-letter FRANKLIN above already says the product name. The small "Franklin" in the tagline underneath was redundant.

Fix: replace the tagline word with blockrun.ai — the parent company's live URL.

Unlike franklin.run (which we own but haven't deployed — see v3.1.0 changelog), blockrun.ai resolves to something, so linking it is honest brand anchoring rather than premature promise.

New tagline:

blockrun.ai · The AI agent with a wallet · v3.2.2

Test updates

Both test/local.mjs and test/e2e.mjs startup-banner tests now check for blockrun.ai + The AI agent with a wallet in the startup banner instead of the literal word Franklin (which is now only in the block-letter art that a regex won't catch).

All 8 local tests pass.

Not changed

Everything else from v3.2.1 — franklin social, agent loop, tools, wallet, sessions, Chrome profile location.


npm: @blockrun/franklin@3.2.2 · Full changelog: v3.2.1...v3.2.2

v3.2.1 — Benjamin Franklin portrait banner

11 Apr 21:44
adfdd08

Choose a tag to compare

Visual upgrade. The startup banner now shows Benjamin Franklin's face next to the FRANKLIN text block, side-by-side.

What it looks like

[10-row Ben Franklin portrait]   ███████╗██████╗  █████╗ ███╗   ██╗...
                                 ██╔════╝██╔══██╗██╔══██╗████╗  ██║...
                                 █████╗  ██████╔╝███████║██╔██╗ ██║...
                                 ██╔══╝  ██╔══██╗██╔══██║██║╚██╗██║...
                                 ██║     ██║  ██║██║  ██║██║ ╚████║...
                                 ╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝...
                                 Franklin · The AI agent with a wallet · v3.2.1

In a truecolor terminal the portrait renders in actual grayscale shading (block-character half-cells + 256-color palette). The FRANKLIN text keeps its gold → emerald gradient from v3.1.0.

Source image

Joseph Duplessis' 1785 oil painting of Benjamin Franklin. Public domain, Wikimedia Commons, File:BenFranklinDuplessis.jpg. Same painting the face on the US $100 bill is derived from — so one portrait anchors both "Franklin the person" and "Benjamins / $100" in one visual.

How it's rendered

chafa --size=20x10 --symbols=block --colors=256 ben-franklin.jpg

Output is a 10-row × ~17-column block-character ANSI string. Baked into src/banner.ts as a hex-escaped string array. No runtime dep on chafa — it's only used at build time, and only if we ever want to regenerate the portrait.

Layout rules

  • Terminal ≥ 90 cols → side-by-side portrait + FRANKLIN gradient text. FRANKLIN is vertically centred inside the portrait's 10 rows.
  • Terminal < 90 cols → text-only fallback, identical to the v3.2.0 banner. Nobody gets a wrapped/mangled hero.

Why

Block-letter "FRANKLIN" alone was generic. Every CLI tool has some version of it. Ben's face ties the brand to the person the tool is named after, and to the Benjamins / $100 cultural anchor, in one glance.

  • Docker has a whale.
  • Kubernetes has a helm.
  • Laravel has an "L".
  • Franklin now has Ben's face.

Not changed

  • Every other subsystem — franklin social, agent loop, wallet, tools, sessions — identical to v3.2.0.

npm: @blockrun/franklin@3.2.1 · Full changelog: v3.2.0...v3.2.1

v3.2.0 — Native X bot (franklin social)

11 Apr 21:21
7f63ee7

Choose a tag to compare

First shipped user-facing workflow. franklin social is a fully native X auto-reply subsystem living in src/social/. No MCP dep, no plugin-SDK indirection, no external CLI. Ships as part of the core npm package.

Architecture is a pattern-for-pattern port of mguozhen/social-bot but rewritten in native TypeScript inside Franklin, with several behavioural fixes.

New top-level CLI

franklin social setup        Install chromium, create default config
franklin social login x      Open browser to x.com, user logs in manually
                              (cookies persist across runs)
franklin social run          Search X, generate drafts
                              --dry-run (default) — preview only
                              --live — actually post
                              -m <model> — override AI model
franklin social stats        Posted / drafted / skipped totals + LLM cost
franklin social config       path | show | edit

What's under src/social/

  • browser.ts — Playwright-core wrapper, 9 primitives (open, snapshot, click, clickXY, type, press, scroll, screenshot, getUrl). Persistent Chrome profile at ~/.blockrun/social-chrome-profile/. Every call is argv-based — zero shell injection surface even if the LLM generates $(rm -rf /) as reply text.
  • a11y.ts[depth-idx] ref tree helpers ported from social-bot's Python regex model. Elements are located by role + label, not CSS. Survives X DOM changes better than selectors.
  • db.ts — JSONL-backed dedup + reply log (no SQLite dep). In-memory indexes for O(1) lookups. URL canonicalisation (x.com ≡ twitter.com ≡ mobile.twitter.com).
  • ai.tsdetectProduct() is a zero-cost keyword score (no LLM). generateReply() uses Franklin's ModelClient so reply generation gets multi-model routing, x402 payments, and automatic fallback for free.
  • x.ts — End-to-end flow: search → pre-key dedup → product routing → generate reply → canonical URL dedup → post → confirmation check.
  • config.ts — Typed config at ~/.blockrun/social-config.json.

Improved over social-bot's approach

  1. Pre-key dedup runs BEFORE the LLM call. social-bot generates with Sonnet then checks dedup — wasting tokens on every duplicate. Franklin hashes (author + snippet[0:80] + time) first and short-circuits duplicates with zero LLM spend.
  2. 'failed' status does NOT blacklist the URL. social-bot permanently blocks any URL that failed once, so transient errors kill the lead forever. Franklin only blacklists 'posted'.
  3. Multi-model routing instead of hardcoded Sonnet. social-bot calls claude-sonnet-4-6 for every generation including throwaway filter decisions. Franklin defaults to NVIDIA Nemotron (free tier) and lets --model or reply_style.model_tier escalate per task.
  4. No shell injection surface. social-bot's browse type '<text>' with shell=True f-string interpolation is an LLM-output → RCE primitive. Franklin's Playwright calls are all argv arrays.
  5. URL canonicalisation. x.com / twitter.com / mobile.twitter.com are aliased; ?s=20 / utm_* / trailing slashes stripped.

Quick start

npm install -g @blockrun/franklin@3.2.0
franklin social setup                  # install chromium, write default config
franklin social config edit            # set handle, products, X search queries
franklin social login x                # log in to x.com once
franklin social run                    # dry-run — preview drafts
franklin social run --live             # actually post
franklin social stats                  # see what's been done

Dependencies

  • playwright-core ^1.49.1 (~2MB). Chromium binary (~150MB) downloaded lazily on first franklin social setup.

Retired

  • src/plugins-bundled/social/ — the plugin-SDK-based skeleton that was never fully wired. The Plugin SDK contract itself stays for future third-party plugins.

Not changed

  • Agent loop, tools, wallet, session storage, payment flow — identical to v3.1.2.
  • The runcode alias still works through the 60-day window from v3.0.0.

npm: @blockrun/franklin@3.2.0 · Full changelog: v3.1.2...v3.2.0

v3.1.2 — Upstream 503 auto-retry

11 Apr 20:51
7cc0760

Choose a tag to compare

Fix for a user-reported bug: gateway returned HTTP 503 "Service temporarily unavailable: All workers are busy, please retry later" and Franklin surfaced the error directly instead of retrying.

Root cause

loop.ts already had a 3-attempt exponential backoff retry path (1s → 2s → 4s) for any isTransient error. The problem was upstream of it.

llm.ts was extracting only the inner .message field from JSON error bodies before throwing. For a response like:

{"message":"Service temporarily unavailable: All workers are busy, please retry later","type":"Service Unavailable","code":503}

…it threw new Error("Service temporarily unavailable: All workers are busy, please retry later") — which stripped the HTTP status code AND the literal "Service Unavailable" string. classifyAgentError() then saw a message that didn't match any of its server-error patterns, fell through to unknown / isTransient: false, and the retry branch in loop.ts was skipped.

Two stacked fixes

1. llm.ts — the thrown Error now includes the HTTP status prefix:

HTTP 503: Service temporarily unavailable: ...

The existing '503' pattern in classifyAgentError picks this up directly.

2. error-classifier.ts — broadened the server category with defensive patterns:

  • temporarily unavailable
  • workers are busy
  • server busy
  • overloaded
  • please retry later
  • retry in a few
  • upstream error

Even if the status prefix is ever lost again, the inner text still classifies correctly.

3. test/local.mjs — regression test locks in all three shapes of the error (with status prefix, inner message only, and just the workers are busy fragment).

What changed for the user

Before v3.1.2: a single 503 surfaced straight to the user with no retry.
After v3.1.2: a 503 triggers Retrying (1/3) after Server error... in the scrollback, a 2-second wait, then another attempt. Only after three failed attempts (≈7 seconds total) does the final error show with a recovery tip.

Verification

  • ✅ 8/8 local tests pass (includes new regression test)
  • classifyAgentError('HTTP 503: Service temporarily unavailable...'){server, transient}
  • classifyAgentError('Service temporarily unavailable: All workers are busy...'){server, transient}
  • classifyAgentError('All workers are busy, please retry later'){server, transient}

Not changed

  • Agent loop, plugin SDK, tools, wallet, session storage — identical to v3.1.1.
  • Token accounting on successful streams is unchanged.
  • Retry count, backoff curve, and max attempts unchanged (3 attempts / 1s→2s→4s).

npm: @blockrun/franklin@3.1.2 · Full changelog: v3.1.1...v3.1.2

Thanks for the bug report.

v3.1.1 — /model picker fixes

11 Apr 17:33
7c192e7

Choose a tag to compare

Two bugs reported by user Cheetah, both fixed. Pure bugfix release — no functional changes to the agent, plugin SDK, tools, or payment flow.

Fixed

1. /model picker only showed 16 of 55+ models

src/ui/app.tsx had its own hardcoded 16-entry PICKER_MODELS array, completely disconnected from the canonical list in src/pricing.ts. Users couldn't reach GLM, Grok, Kimi, Minimax, Gemini 3.1 Pro, O1, O3, GPT-5.3 Codex, or most of the NVIDIA free tier from the in-session picker at all.

Fix: single source of truth. PICKER_CATEGORIES, PICKER_MODELS_FLAT, and the ModelEntry type are now exported from src/ui/model-picker.ts and consumed by both the Ink UI picker and the readline pickModel() fallback. The new list has 32 models across 6 categories:

  • 🔥 Promo (2) — GLM-5.1, GLM-5.1 Turbo (flat $0.001/call)
  • 🧠 Smart routing (3) — blockrun/auto, blockrun/eco, blockrun/premium
  • Premium frontier (8) — Claude Sonnet/Opus 4.6, GPT-5.4, GPT-5.4 Pro, Gemini 2.5 Pro, Gemini 3.1 Pro, Grok 3, Grok 4
  • 🔬 Reasoning (6) — O3, O4 Mini, O1, GPT-5.3 Codex, DeepSeek R1, Grok 4.1 Fast Reasoning
  • 💰 Budget (7) — Haiku 4.5, GPT-5 Mini/Nano, Gemini 2.5 Flash, DeepSeek V3, Kimi K2.5, Minimax M2.7
  • 🆓 Free (6) — Nemotron Ultra, Qwen3 Coder, Devstral, Llama 4 Maverick, DeepSeek V3.2, GPT OSS 120B

Every ID is verified against pricing.ts; every shortcut is in MODEL_SHORTCUTS.

2. Switching models wiped the scrollback

When a user typed /model mid-session, the Ink UI had if (mode === 'model-picker') return <Box>...picker only...</Box>. That early-return unmounted the two <Static> components holding the conversation scrollback (completedTools and committedResponses). When the picker closed and mode flipped back, they re-mounted fresh — and Ink's <Static> doesn't re-commit already-written items. So the screen came back visually empty.

The agent's message history array was actually intact on the backend, so the next prompt still had full context. But from the user's seat, the scrollback was gone right when it was most needed — mid-debug on a large codebase, when referencing prior prompts is the whole point of switching models.

Fix: the picker now renders inline below the scrollback as an overlay. The render function always returns the same tree shape regardless of mode; only the picker vs. InputBox toggles at the bottom. The two <Static> components stay mounted across mode transitions — scrollback survives.

Also added a one-line reassurance inside the picker UI:

Your conversation stays above — picking a model keeps all history intact.

Internal

  • Removed duplicated PICKER_MODELS arrays. One in model-picker.ts, one in app.tsx — both 16 entries, both drifted from reality. Now a single PICKER_CATEGORIES export.
  • src/ui/app.tsx render function is safer for future UI modes (settings, wallet details, onboarding) — they can all reuse the same always-mounted Static tree instead of unmounting scrollback.

Verification

  • ✅ 7/7 local tests pass
  • ✅ 13/13 E2E tests pass (GLM-5.1)
  • franklin --version3.1.1
  • PICKER_CATEGORIES.length === 6, PICKER_MODELS_FLAT.length === 32
  • TypeScript strict clean

Not changed

  • Agent loop, plugin SDK, tools, wallet, session storage, payment flow — all identical to v3.1.0.
  • The runcode alias still works through the 60-day window from v3.0.0.

npm: @blockrun/franklin@3.1.1 · Full changelog: v3.1.0...v3.1.1

Thanks to Cheetah for the detailed bug report.

v3.1.0 — Brand cleanup + README rewrite

11 Apr 16:57
ffa7f82

Choose a tag to compare

Pure documentation and brand cleanup on top of v3.0.0. Zero functional changes — plugin SDK, agent loop, tools, tests, wallet, and sessions are all identical to v3.0.0.

Why

Two loose ends from the rebrand needed a clean close:

  1. Premature domain references. We own `franklin.run` and `franklin.bet` but haven't deployed them yet. The README, CLI help, and banner were all linking to URLs that 404 — which is worse than not linking at all. Trust breaks on the first click.
  2. Banner color experiments. v3.0.1 shipped a smooth 6-row gold→emerald gradient. v3.0.2 flattened it to pure `#FFD700`. v3.0.3 tried metallic `#D4AF37`. After A/B-ing in the terminal, the gradient won — it tells the Franklin story in one glance.

Changed

  • Banner — Restored the smooth 6-row gold→emerald gradient (`#FFD700` → `#10B981`) via a reintroduced `interpolateHex()` helper. Dropped the "Marketing: franklin.run · Trading: franklin.bet" tagline line.
  • CLI description — Removed "Marketing workflows: franklin.run" and "Trading workflows: franklin.bet" from `franklin --help`.
  • package.json — `homepage` now points to the GitHub repo (a real URL). Description shortened, no domain references.
  • README — Complete rewrite to 100k-star quality. 10 badges (npm, downloads, stars, CI, TypeScript, Node, x402, Telegram), anchor-linked ToC, real terminal transcript instead of the aspirational marketing-campaign demo, feature grid in two columns, real Plugin SDK example without `{ ... }` placeholders, honest roadmap split (✅ shipped / 🚧 in progress / 💭 under consideration), star-history chart, free-tier call-out. Grounded every claim in an audit of the repo.
  • CLAUDE.md — Same domain cleanup.
  • CHANGELOG.md — v3.1.0 entry added. v3.0.0 history preserved verbatim.

Verification

  • ✅ 6/6 local tests pass
  • ✅ 13/13 E2E tests pass (GLM-5.1)
  • ✅ `franklin --version` → `3.1.0`
  • ✅ `franklin --help` is free of franklin.run/bet
  • ✅ `grep -r "franklin\.(run|bet)" src/ README.md CLAUDE.md package.json` → empty (only CHANGELOG history matches)

When do the domains come back?

When the sites are actually live. Brand narrative can lead product, but URLs shouldn't. v3.2.0 or later, once `franklin.run` and `franklin.bet` resolve to something worth clicking.

Not changed

  • Everything else from v3.0.0. Plugin SDK, agent loop, tools, tests, wallet, sessions — all identical.
  • The `runcode` alias still works through the 60-day compatibility window (until ~June 2026).

npm: @blockrun/franklin@3.1.0 · Full changelog: v3.0.0...v3.1.0

v3.0.0 — From RunCode to Franklin

11 Apr 16:56
d9c0c83

Choose a tag to compare

Major rebrand. RunCode is now Franklin — The AI agent with a wallet.

Why the rename

RunCode was positioned as "a better Claude Code with multi-model support." That's defensive — it defines us by what we're not (not rate-limited, not subscription-locked, not Anthropic-only) rather than what we are. The moment Claude Code changes pricing, that entire differentiation evaporates.

The real opportunity isn't being a better coding tool. It's being the first AI agent that can actually spend money to get work done, not just write text about it.

  • Claude Code writes code.
  • Aider edits code.
  • Cursor completes code.
  • Franklin takes your USDC and autonomously runs workflows that cost money.

Benjamin Franklin was the founding father who said "time is money," printed his own currency, and deployed capital across the Atlantic to fund a revolution. Our Franklin does the same for AI agents: it turns your wallet into an autonomous economic engine.

Category

Autonomous Economic Agent — AI that spends money autonomously within wallet-enforced budget caps to deliver outcomes, not just text.

Built on three layers:

  1. x402 micropayment protocol — HTTP 402 native payments, wallet-as-identity
  2. BlockRun Gateway — aggregates 55+ LLMs + paid APIs behind one wallet
  3. Franklin Agent — reference client with Plugin SDK (this repo)

What changed

  • Package: `@blockrun/runcode` → `@blockrun/franklin` (new name on npm)
  • CLI command: `franklin` is the primary binary; `runcode` remains as a 60-day alias so existing scripts don't break
  • Banner: new FRANKLIN ASCII art with gold→green "money" gradient
  • README: complete rewrite with new positioning and Autonomous Economic Agent category framing
  • CLI help text: all mentions of "coding agent" updated to "the AI agent with a wallet"
  • Version: 2.8.0 → 3.0.0 (major bump because this is a category-level change, not a feature addition)

What did NOT change

  • All plugins work identically — `social` and any future verticals
  • Plugin SDK v2.7 contract is unchanged — no migration needed for third-party plugins
  • Hermes patterns from v2.8 (prompt caching, structured compaction, session search, insights) all remain
  • Config at `~/.blockrun/` is unchanged — no migration needed
  • Wallet, sessions, stats all preserved
  • Payment flow, API compatibility, E2E behavior — identical

Migration

Zero work required for existing users. The `runcode` binary continues to work as an alias for `franklin` through the 60-day compatibility window (until ~June 2026).

When you're ready, switch package names:

```bash
npm uninstall -g @blockrun/runcode
npm install -g @blockrun/franklin
```

The tagline

Franklin runs your money.

Short. Active. Ownership. Economic.

Long form: The AI agent with a wallet. While others chat, Franklin spends — turning your USDC into real work.


npm: @blockrun/franklin@3.0.0

v2.5.29 — AskUser Fix

07 Apr 21:01
34a2fd1

Choose a tag to compare

What's new

Fix: AskUser tool no longer crashes runcode

When the agent used the AskUser tool in Ink UI mode, runcode crashed. Root cause: readline.createInterface on stdin conflicted with Ink's raw-mode stdin ownership.

Fix: Added onAskUser callback to ExecutionScope and AgentConfig. When running in Ink UI mode, AskUser questions are now routed through a proper Ink dialog (cyan question box with TextInput) instead of readline.

What it looks like now:

  ╭─ Question ─────────────────────────────
  │ What language should I use?
  │ 1. TypeScript
  │ 2. Python
  │ 3. Go
  ╰─────────────────────────────────────
answer> 

The main input box is blocked while the dialog is active (same pattern as the permission dialog).

Also in this release

  • AskUser added to auto-allow list — no permission prompt before asking a question
  • Non-interactive fallback preserved for piped/scripted mode