Freebuild OpenCode sponsor panel
A V2 TUI plugin for the paid Freebuild Lite and Freebuild Standard
plans. It adds a host-managed sponsor panel while the agent works, automatically
connects the freebuild model provider, and shows plan and token usage.
Freebuild is not built by the OpenCode team and is not affiliated with OpenCode.
Docs: https://freebuild.dev/docs/
┌────────────────────────────────┬────────────────────────────────┐
│ │ FREEBUILD · Acme │
│ normal OpenCode session │ │
│ reflowed to half width │ SHIP FASTER │
│ │ Acme CI runs your tests... │
│ │ │
│ │ │
└────────────────────────────────┴────────────────────────────────┘
Panel labels
SPONSOREDfor paid campaignsAFFILIATEfor affiliate campaignsFREEBUILDfor house inventoryADVERTISEfor advertising inventory
Paid campaigns outrank affiliates, and affiliates outrank house inventory.
Paid plan
There is no free or ad-free tier. The plugin is inactive until a sponsored license is connected:
- No license: no panel, no ads, no broker tokens.
- Active Lite or Standard license: broker tokens plus the mandatory sponsor panel.
- Canceled or unpaid subscription: the broker rejects requests and the panel stays closed.
Founding beta (invite only)
Founding beta uses the same checkout as the paid plans. Run
/freebuild-founding and paste the private code. The plan is free for 90 days,
grants 10,000,000 weighted tokens per billing month, and includes the sponsor panel.
The Stripe subscription cancels automatically at day 90, before any payment. The
license connects automatically, the same way as Lite and Standard.
Install
Install the published package with OpenCode V2:
opencode plugin add @freebuild/opencode@latest
The old unscoped freebuild-sponsor package is legacy. New installs should use
@freebuild/opencode.
Restart OpenCode so it loads the plugin. To update later:
opencode plugin update
For local development from this repository, install the directory directly:
powershell -ExecutionPolicy Bypass -File .\opencode-plugin\install.ps1
bash ./opencode-plugin/install.sh
To build the npm tarball before publishing:
cd opencode-plugin
npm pack
The package contains src/tui.tsx, package.json, and this README. Test the
tarball with a clean OpenCode config before publishing.
Connect a license
You can start in OpenCode or buy first in the browser. Both paths require the
plugin installed above. Browser buyers run /freebuild-key and paste the key
from the Stripe success page; the plugin validates it, writes the provider, and
selects the model. The screenshot guide is at
https://freebuild.dev/docs/install/.
Inside OpenCode, with no key connected:
/freebuild-license
The plugin asks the app for a one-time activation code, opens
/terminal/activate.html in the browser, and polls until Stripe Checkout
finishes. The browser asks for the email first and sends a 6-digit code. Verify
it before checkout. The same verified email gets a one-time license link, so a
typo cannot attach the license to an address you do not control. When the
webhook issues the license, the plugin:
- stores the key in
~/.config/freebuild/credentials.json, - writes a
freebuildprovider to~/.config/opencode/opencode.json(c), - refreshes entitlement and enables the sponsor panel,
- shows the license key and the remaining setup steps.
The plugin writes the provider config, connects the credential, and selects
Freebuild → DeepSeek V4.1 Flash 1017 for the current session. In /models
it appears under Freebuild the first time. After you use it, OpenCode moves
it to Recent. If it is still missing, run /freebuild-setup and restart
OpenCode once.
The model has the same thinking levels as DeepSeek's own API: none, low,
high, and max. Use /thinking to switch levels without changing models.
The provider points at the Freebuild broker; the license key is the broker API
key.
Model metadata
GET https://freebuild.dev/broker/v1/models is OpenAI-compatible and returns
the model id, context and output limits, capabilities, compatibility fields,
token weights, and plan prices. Clients and routers can use it to configure the
provider without hardcoding limits.
When a key is already connected, /freebuild-license checks it first and offers
reconnect, paste a replacement, open the account page, or buy
another license. It never starts a second checkout on its own, and a rejected
key (rotated, revoked, or cancelled) only offers a replacement or the account
page.
To move this terminal to a key recovered at /account:
/freebuild-key
The plugin prompts for the key, checks GET /api/me/entitlement before saving
anything, then writes the provider config, connects the credential, and selects
the model. An inactive key is reported and the current license is left untouched.
A network failure also leaves the current key in place.
If the automatic connect fails, run /freebuild-setup to write the provider
config and select the model again.
Useful commands:
/freebuild-license— connect, reconnect, replace, or buy a license./freebuild-key— paste and validate a replacement key./models— select the Freebuild provider model. It appears under Freebuild until first use, then moves to Recent./thinking— choosenone,low,high, ormaxthinking for DeepSeek V4.1 Flash 1017./freebuild-plan- show weighted used/limit, raw tokens, cache hit rate, and reservations./freebuild-billing— open the Stripe customer portal./freebuild-setup— repair the provider configuration./freebuild-sponsor,ctrl+o,ctrl+shift+o, oralt+o— open the sponsor link currently on screen. Clicking anywhere on the sponsor panel does the same. If no campaign is cached yet, Ctrl+O resolves one with a non-counting preview request so it still opens a link instead of a menu or document. Ctrl+O keeps its normal OpenCode meaning when no sponsored license is active; use/openfor the picker while a sponsored license is active./freebuild-adtest- preview the next run's sponsor panel for 4 seconds without counting an impression.
Behavior
- Opens on the first agent step and closes when the run ends. Each run fetches one authenticated sponsor for a fresh run ID and keeps it for the whole run; the server counts one impression when that run's panel opens. Repeated panel fetches and duplicate events are idempotent, so a long run stays one impression. There is no mid-run carousel.
/freebuild-adtestuses the preview endpoint, stores the selected campaign as the next run's campaign for that license and install, and never counts an impression or emits trackable open, close, or click events. The next run uses the previewed campaign when it is still eligible.- The panel measures its own box and fits the campaign wordmark and copy to it.
Larger logo tiers are only used when both width and height leave room, and
body and hint rows drop out as the panel gets smaller. A fixed
wordmarkFontfalls back to Auto when it does not fit;showWordmark: falsehides the big text entirely. - OpenCode uses a split layout at 108+ columns; narrower terminals render the panel fullscreen and show a resize hint.
- The panel content comes from the sponsor feed. Image ads render a static image above the copy; text-only ads render the wordmark and formatted copy. Image-only ads render the image across the whole creative area with no kicker, headline, body, or wordmark. GIFs are flattened to their first frame. If the feed or image is unavailable, a bundled house ad or text fallback is used.
- Clicking the panel resolves the sponsor destination through the authenticated
click endpoint. The TUI sends the license, install, session, and run IDs, then
opens the returned URL. Direct anonymous click URLs return
401. - The panel cannot be hidden with a keybinding. Ads are part of the sponsored plan.
- In the experimental Freebuild Terminal wrapper, the TUI panel stays closed and the plugin writes busy and entitlement state for the wrapper's side pane. The wrapper is not part of the first launch.
Wrapper mode
Set FREEBUILD_WRAPPER=1 and FREEBUILD_WRAPPER_STATE to a writable file path.
The plugin then writes:
{
"busy": true,
"panel": true,
"sessionId": "c13792d2135744c5bb309fa7",
"updatedAt": 1793480000000
}
busy drives the wrapper's status line. panel is the entitlement decision:
true for an active sponsored license. The wrapper keeps the sponsor pane
visible for the whole app session whenever panel is true.
updatedAt is Unix milliseconds. It is refreshed every 10 seconds while a run
is active. A run that finishes in under 3 seconds keeps busy: true for the
remainder of a 3-second minimum window so a short run is not missed. The wrapper
clears the file before starting OpenCode and ignores a stale busy flag older
than 90 seconds.
Without FREEBUILD_WRAPPER_STATE, FREEBUILD_WRAPPER=1 is ignored and the
normal TUI panel remains available.
Entitlement
The plugin reads a license key from:
FREEBUILD_LICENSE_KEYorFREEBUILD_API_KEY,~/.config/freebuild/credentials.json, or- the legacy
freebuildprovider entry in OpenCode'sauth.json.
On OpenCode V2, provider calls use the credential connected through the
integration API. The plugin keeps credentials.json so it can refresh
entitlement and the sponsor panel across launches.
Credentials file:
{
"installId": "a stable installation id",
"licenseKey": "fb_...",
"apiUrl": "https://freebuild.dev",
"brokerUrl": "https://freebuild.dev/broker",
"sponsorUrl": "https://freebuild.dev/api/sponsor/panel"
}
It calls:
GET {brokerUrl}/api/me/entitlement
Authorization: Bearer <licenseKey>
X-Freebuild-Install: <installId>
sponsored: true→ the panel may open during runs.sponsored: false→ the panel never opens.- No license key → inactive; run
/freebuild-license. 401for a connected key → rotated, revoked, or cancelled; run/freebuild-keywith the replacement from/accountor/freebuild-licensefor the recovery menu. The plugin does not start a new checkout automatically.- Broker unreachable with a license key → the panel stays closed and the license is not discarded.
/freebuild-adtestforces a preview without changing entitlement.
The generated provider also sends X-Freebuild-Install and
X-Freebuild-Client. The install ID lets broker usage line up with a
license, and the client ID records which surface made the request.
Sponsor feed
The plugin fetches:
GET {sponsorUrl}
Authorization: Bearer <licenseKey>
X-Freebuild-Install: <installId>
X-Freebuild-Client: opencode/<pluginVersion>
Expected response:
{
"campaign": {
"id": "acme-2026-11",
"brand": "Acme",
"kicker": "SHIP FASTER",
"title": "Acme CI runs your tests in 30 seconds.",
"body": "Use code FREEBUILD for 3 months free.",
"url": "https://acme.example/freebuild",
"clickUrl": "https://freebuild.dev/api/sponsor/click?id=acme-2026-11",
"eventUrl": "https://freebuild.dev/api/sponsor/event",
"accent": "#39d98a",
"wordmark": "ACME",
"wordmarkFont": "block",
"showWordmark": true,
"imageOnly": false,
"titleStyle": { "bold": true, "italic": false, "underline": false },
"bodyStyle": { "bold": false, "italic": true, "underline": false },
"align": "center",
"imageUrl": "https://freebuild.dev/ads/acme.webp"
},
"serverTime": 1793480000
}
imageUrl is optional. When present, the panel and wrapper render it as a
static image above the copy; when absent, the panel shows the wordmark and the
formatted text. imageOnly: true requires an imageUrl and renders the image
across the full creative area without a kicker, headline, body, or wordmark.
wordmark defaults to FREEBUILD; wordmarkFont accepts
auto, huge, block, slick, shade, tiny, grid, or pallet;
showWordmark: false hides it. Uploads from /admin/ads.html are normalized to
static PNG; GIFs use their first frame. titleStyle and bodyStyle accept
bold, italic, and underline; align accepts center or left.
videoUrl is legacy and ignored.
Panel events are posted as:
{
"campaignId": "acme-2026-11",
"sessionId": "c13792d2135744c5bb309fa7",
"installId": "fa0515ea6e0c419db3f00901ea697316",
"runId": "run-0001",
"client": "opencode/0.0.27",
"event": "open",
"durationMs": 0,
"tokens": 75
}
close events include the visible duration. The client field is
client-reported, not verified. Use it to split events by surface, not as proof
that the panel was visible.
Environment
FREEBUILD_LICENSE_KEY broker license key
FREEBUILD_API_KEY fallback license key
FREEBUILD_BROKER_URL broker base URL (default https://freebuild.dev/broker)
FREEBUILD_API_URL app base URL for activation (default https://freebuild.dev)
FREEBUILD_SPONSOR_URL sponsor panel feed
FREEBUILD_CREDENTIALS alternate credentials JSON path
FREEBUILD_AD_DEMO=1 preview the panel on startup
FREEBUILD_AD_DEMO_MS preview duration in milliseconds
FREEBUILD_WRAPPER=1 suppress the TUI panel and emit wrapper busy state
FREEBUILD_WRAPPER_STATE busy-state file for the desktop wrapper
FREEBUILD_WRAPPER_DEBUG optional log file for plugin/wrapper diagnostics
Development
OpenCode 2.2.0 requires removing the resize listener with renderer.off(...);
calling the return value of renderer.on(...) crashes the plugin during cleanup.
The TUI loads plugins live, so a setup or cleanup exception can take down the
host terminal. Test changed plugins in a separate OpenCode process before
replacing the copy used for daily work, and keep FREEBUILD_WRAPPER unset
unless the desktop wrapper is actually launching OpenCode.
Validation used in this repository:
wrapper\node_modules\.bin\esbuild.cmd opencode-plugin\src\tui.tsx --bundle `
--loader:.tsx=tsx --external:* --outfile=$env:TEMP\plugin-check.js
Test
- With no license key, no panel opens during a run.
/freebuild-licensecompletes Stripe Checkout and writes the provider config.- After checkout, the plugin stores the credential and
/freebuild-setupselects the model. - With an existing key,
/freebuild-licenseasks before buying and never opens a second checkout. /freebuild-keyrejects an inactive key and keeps the current license; an active key is saved, connects the provider, and selects the Freebuild model./accountreveals, rotates, and opens billing for licenses owned by the signed-in email.- With a sponsored license, the panel opens and shows the fetched campaign.
/freebuild-planshows weighted used, reserved, limit, raw tokens, and cache hit rate.- A canceled subscription makes
/api/me/entitlementreturn401and the panel stops opening.
License
Proprietary. Copyright (c) 2026 Freebuild. All rights reserved.
This package is licensed under the Freebuild Proprietary License. See LICENSE.