Terminal
WebAssembly / Terminal
createFxTerminal() runs the fx terminal in a web page using fx-term.wasm. Connect it to xterm.js to display output and handle keyboard input. The try page uses this API.
Install libfx and check the WebAssembly runtime requirements before loading the terminal.
npm install libfx@0.0.12
Embed the terminal
Install xterm.js for the terminal interface:
npm install @xterm/xterm
In a browser project with a bundler, add this container and JavaScript:
<div id="terminal" style="height: 600px"></div>
import { Terminal } from '@xterm/xterm'
import '@xterm/xterm/css/xterm.css'
import { createFxTerminal, supportsJspi, xtermAdapter } from 'libfx/browser'
const container = document.querySelector('#terminal')
if (!(container instanceof HTMLElement)) {
throw new Error('Missing #terminal element')
}
if (!supportsJspi()) {
throw new Error('fx requires JSPI support')
}
const terminal = new Terminal()
terminal.open(container)
const runtime = await createFxTerminal({
terminal: xtermAdapter(terminal),
})
await runtime.interactive
runtime.exited.then((exitCode) => {
console.log(`fx exited with code ${exitCode}`)
})
Await runtime.interactive before sending input. Use write(), resize(), and abort() to control the terminal; abort() stops it and releases subscriptions. The exited promise resolves when fx stops.
The browser entry point resolves both WebAssembly assets relative to the installed package. If your bundler does not serve those files, copy them to a public asset location and pass wasm explicitly. It accepts a URL, Response, bytes, or a compiled WebAssembly.Module.
Connect host adapters
Add adapters to save terminal settings and conversations, complete login, or run commands through your application. These storage and login adapters belong to the terminal API. Headless agents use creation options and checkpoints; both APIs accept fetch.
| Option | Description |
|---|---|
configStore | Restore accepted model and mode settings. |
promptHistoryStore | Preserve terminal input history. |
sessionStore | Persist conversations. |
openUrl and oauthSessionStore | Complete browser device login. |
workspace | Add completion-only shell.run. |
fetch | Control network requests. |
Config store
Use configStore to restore settings such as the active model and mode:
const configStore = {
get(id) {
return localStorage.getItem(`fx.core.config.${id}`)
},
set(id, value) {
localStorage.setItem(`fx.core.config.${id}`, value)
},
}
get(id) returns a string or null. fx calls set(id, value) only after it accepts the new value.
Prompt history store
Use promptHistoryStore to preserve terminal input history across page loads:
type PromptHistoryStore = {
load(
workspaceRoot: string,
limit: number,
): string[] | Promise<string[]>
append(
workspaceRoot: string,
value: string,
timestampMs: number,
):
| void
| 'duplicate'
| 'record_too_large'
| Promise<void | 'duplicate' | 'record_too_large'>
clear(workspaceRoot: string): void | Promise<void>
}
load() returns prompts from oldest to newest. Return duplicate when the latest stored prompt already matches, or record_too_large when the app declines to store it.
Session store
Use sessionStore to persist conversations. Pass args: ['--resume', 'last'] to createFxTerminal() to resume the conversation with the latest updatedAtMs value.
type SessionStore = {
load(id: string):
| { bytes: Uint8Array; revision: string }
| null
| Promise<{ bytes: Uint8Array; revision: string } | null>
commit(
id: string,
bytes: Uint8Array,
expectedRevision: string | undefined,
): { revision: string } | Promise<{ revision: string }>
list():
| Array<{ id: string; updatedAtMs: number }>
| Promise<Array<{ id: string; updatedAtMs: number }>>
remove(id: string): void | Promise<void>
}
Store and return bytes unchanged. commit() receives undefined for the first revision and must return a new revision after every write.
If expectedRevision is stale, throw an error whose code is FX_SESSION_REVISION_CONFLICT. The conflict prevents the stale snapshot from overwriting the current record. For agent persistence, use checkpoints, not this terminal store.
Device login
createFxTerminal() supports browser device login through openUrl and oauthSessionStore:
const terminal = await createFxTerminal({
wasm: './fx-term.wasm',
terminal: terminalAdapter,
openUrl(url) {
return window.open(url, '_blank', 'noopener,noreferrer') !== null
},
oauthSessionStore,
})
Return false from openUrl() when the app cannot open the verification page. fx still prints the URL and verification code in the terminal.
The OAuth store uses the following contract:
type OAuthSessionStore = {
load():
| { bytes: Uint8Array; revision: string }
| null
| Promise<{ bytes: Uint8Array; revision: string } | null>
commit(
bytes: Uint8Array,
expectedRevision: string | undefined,
): { revision: string } | Promise<{ revision: string }>
remove(expectedRevision: string | undefined):
| void
| boolean
| 'missing'
| Promise<void | boolean | 'missing'>
}
Store the OAuth bytes without inspecting or logging them. Return false or missing when there is no record to remove. Reject a stale commit or removal with FX_OAUTH_SESSION_REVISION_CONFLICT.
Browser workspace
The optional workspace adapter lets your app run commands for the terminal. It provides shell.run with the schema { action: 'run', command } and returns the result when the command finishes. It does not provide native shell profiles, a TTY, or handles for interacting with running commands:
type WorkspaceAdapter = {
info: {
version: 1
root: string
cwd: string
home: string
gitAvailable: false
ephemeral: true
}
permission: 'allow-sandboxed' | 'prompt'
exec(input: {
command: string
cwd: string
signal: AbortSignal
timeoutMs: number
outputLimitBytes: number
}):
| { stdout: string; stderr: string; exitCode: number }
| Promise<{ stdout: string; stderr: string; exitCode: number }>
}
root, cwd, and home must be normalized absolute paths without NUL bytes. Version 1 also requires:
cwdequalsroot.gitAvailableisfalse.ephemeralistrue.permissionisallow-sandboxedorprompt.
With allow-sandboxed, fx runs commands without asking; your adapter enforces access restrictions. With prompt, fx applies its permission rules and active mode before calling the adapter.
exec() returns { stdout, stderr, exitCode }. Commands are limited to 64 KiB of UTF-8, combined output previews to 64 KiB, and execution to 30 seconds. The adapter must stop work when its AbortSignal is cancelled. Without a workspace adapter, the WebAssembly terminal does not expose shell.run.
The try page implements this adapter with an in-memory bash environment.
Network requests
The SDK uses globalThis.fetch by default. Pass a fetch function to createFxTerminal() or createFxAgent() when your app needs to proxy requests, add authentication, or apply its own network policy. Browser Cross-Origin Resource Sharing (CORS) and Content Security Policy (CSP) rules still apply.