15 KiB
Fido Project Overview
Fido is a minimal, interactive terminal coding assistant. It connects an OpenRouter-hosted model to a deliberately small set of workspace tools, then presents the conversation and tool activity in a full-screen terminal UI.
The project is written in modern JavaScript as native ES modules. It runs directly from source with Node.js; there is no transpiler, bundler, or JSX build step.
What Fido Can Do
- Inspect and search a project without leaving the terminal.
- List directories, find files by name, search file contents, and read files in chunks.
- Apply precise file edits, create files and directories, and remove files or directory trees.
- Run shell commands after explicit user approval.
- Search the web and extract web pages through Tavily when configured.
- Ask the user clarifying questions or request help from a human expert.
- Stream model reasoning and tool notifications into a scrollable terminal interface.
- Navigate project files from the prompt and insert whole-file, single-line, or line-range mentions.
- Keep mutation and conversation state isolated per workspace, outside repositories when installed.
- Compact large tool payloads between turns to reduce token usage and avoid retaining stale file contents.
Runtime Architecture
bin/fido
├─ loads Fido configuration
├─ selects per-workspace state
└─ starts src/fido.js
└─ runRepl()
├─ builds the system prompt
├─ drives LLMOrbiter + OpenRouter
├─ executes tools from src/tools/
└─ drives the Ink UI through src/ui/
model tool call
└─ tool registry
├─ workspace path validation
├─ filesystem, shell, or Tavily operation
└─ structured result returned to the model and shown in the UI
Main Modules
| Module | Responsibility |
|---|---|
bin/fido |
Installed/source launcher; loads user configuration and creates per-workspace state paths. |
src/fido.js |
Process entry point; checks for the required API key and starts the REPL. |
src/repl.js |
Owns conversation state, provider turns, cancellation, tool events, and transcript compaction. |
src/config.js |
Loads .env files and resolves the model, workspace root, and state locations. |
src/system-prompt.js |
Builds the workspace-aware model instructions sent at session start. |
src/commands.js |
Implements local slash commands without spending a model turn. |
src/slash-commands.js |
Defines and resolves the built-in command registry. |
src/tools/index.js |
Registers the model-facing tools and their UI notification renderers. |
src/tools/*.js |
Implements individual filesystem, shell, web, and interaction tools. |
src/sandbox.js |
Resolves relative tool paths and rejects absolute paths or lexical traversal outside the workspace. |
src/dir-listing.js |
Shared .gitignore-aware directory walking and pagination. |
src/glob.js |
Small shared glob dialect used by name and content searches. |
src/lines.js |
Shared line splitting with editor-compatible trailing-newline behavior. |
src/compact.js |
Shrinks old patch arguments and invalidates file reads made stale by later patches. |
src/undo.js |
Persists a newest-first log describing mutations made by tools. |
src/suspend.js |
Saves and restores manually suspended conversation snapshots. |
src/transcript.js |
Formats assistant, user, tool, error, command, and recalled-message output. |
src/file-browser.js |
Provides testable file-browser navigation and mention-formatting helpers. |
src/ui/index.js |
Exposes an imperative UI singleton to the REPL and tools. |
src/ui/store.js |
External, subscriber-based state store for prompts, history, status, and usage totals. |
src/ui/app.js |
Root Ink application and fixed terminal layout. |
src/ui/main-input.js |
Main input, slash-command completion, and Ctrl+O file/line browser. |
Conversation Flow
- Configuration resolves the workspace root using the first positional argument, then
ROOT_DIR, then the current directory. - The REPL loads undo and suspend state and mounts the Ink UI.
- It builds a system prompt containing the workspace root, a short root listing, tool contracts, and operating rules.
- The user submits a normal message or a local slash command.
- Normal input is sent to
LLMOrbiter, which uses the OpenRouter provider and advertises the registered tools to the model. - A provider turn can iterate up to 20 times, allowing the model to call tools and continue reasoning before returning a final response.
- Tool calls and results are rendered in the transcript; the structured result is also returned to the model.
- Between user turns, Fido compacts large patch arguments and replaces pre-patch file reads that are no longer current.
- The resulting message array remains in memory for the session. It can be saved explicitly with
/suspend.
Model-Facing Tools
All currently registered tools are defined in src/tools/index.js.
| Tool | Purpose and notable behavior |
|---|---|
list_files |
Lists one directory, optionally recursively, with .gitignore support and pagination. Dot-directories are hidden unless requested. |
find_files |
Recursively finds files or directories by a simple name glob. Slashless patterns also match basenames. |
read_file |
Reads a workspace file as line-based content and supports zero-indexed seek/limit chunking. |
ripgrep |
Performs a pure-JavaScript regular-expression content search. It skips binary files, files over 2 MiB, ignored paths, and hidden directories by default. Long matching lines are capped. |
patch |
Applies ordered create/overwrite or exact first-match search/replace operations. Supports skiplines, CRLF adaptation, partial-progress reporting, and undo records. |
mkdir |
Recursively creates a directory and records its creation. |
rm |
Deletes a file while retaining its contents in the undo log. |
rmdir |
Recursively deletes a directory. Its undo entry records the path but not the removed contents. |
bash_exec |
Runs bash -c with the workspace as its initial working directory. Requires explicit approval, closes stdin, disables interactive/color behavior, limits output, and kills the process tree at timeout. |
websearch |
Uses Tavily to return a synthesized answer and ranked web results. It fails when no Tavily key is configured. |
browse_url |
Uses Tavily to extract one or more web pages, optionally focused by a relevance query. It fails when no Tavily key is configured. |
ask_user |
Prompts the user for clarification or a decision, with up to three suggested choices and a free-text option. |
ask_an_expert |
Displays a copyable question for a human expert and waits for the user to paste the answer back. It does not contact an external expert directly. |
The web tools remain registered even when TAVILY_API_KEY is absent; configuration is validated lazily when one is invoked.
Built-In Commands
| Command | Behavior |
|---|---|
/paste |
Temporarily hands the terminal to $EDITOR for multiline input, then sends the resulting text to the model. |
/suspend [description] |
Saves the current message array as a timestamped JSON snapshot. A description is requested if omitted. |
/recall |
Lets the user select a saved snapshot. The old conversation is restored under a newly built system prompt. |
/undo |
Displays the most recent mutation-log entries. It currently inspects the log; it does not execute a rollback operation. |
/reasoning [level] |
Shows or sets provider reasoning effort to minimal, low, medium, or high. Omitting the level leaves the model default in effect. |
/quit |
Ends the session. |
Typing / in the main input opens inline command completion. All recognized slash commands are handled locally and do not consume a model turn.
Terminal UI
The UI is a React application rendered by Ink in the terminal's alternate screen.
- Header: model, workspace, reasoning setting, and history-scroll state.
- History: bottom-anchored transcript with streaming reasoning and line-granular scrolling.
- Command bar: main input, local command completion, approval prompts, selections, confirmations, and the
Ctrl+Ofile browser. - Footer: cumulative model calls, input/output/cache tokens, estimated cost, and key hints.
- History controls:
PgUp/PgDn, mouse wheel, andEndto return to the live tail. - Cancellation:
Ctrl+Cat the main prompt exits; during a model turn it aborts the provider request and restores the draft.
The store retains the latest 1,000 logical history entries, while the view mounts only the lines that fit in the current terminal. Mouse reporting can be disabled with FIDO_NO_MOUSE=1.
File Mentions
Press Ctrl+O in the main prompt to open the file browser.
- Arrow keys move the selection.
Enteropens a directory or enters a file's line selector.Tabinserts a whole-file mention from the browser.Backspacemoves to the parent directory.Escleaves an overlay.- In line mode,
vstarts or ends a range,g/Gjump to the first/last line, andEnterinserts the mention.
Mentions are references, not embedded file contents:
@README.md
@src/fido.js:12
@src/fido.js:12-40
The browser hides ignored and dot-directory entries by default. Mentions use forward-slash workspace-relative paths and one-based line numbers; the model maps those numbers to read_file's zero-based seek.
Safety and Trust Boundaries
Fido has useful path and workflow safeguards, but it is not an operating-system sandbox.
- Dedicated filesystem tools require relative paths and reject lexical paths that resolve outside
ROOT_DIR. - Direct filesystem access can still follow filesystem semantics such as symlinks; path checks should not be treated as a hard kernel-enforced isolation boundary.
bash_execpins only the shell's starting directory. The shell can use absolute paths or change directories, so interactive approval is the primary protection around shell execution.- Users can approve one command or all shell commands for the rest of that Fido session.
- Network and extracted web content is explicitly treated as untrusted third-party input.
- The model is instructed to inspect files before patching and to prefer targeted replacements over whole-file rewrites.
- API keys are loaded from environment files that are excluded by
.gitignore; installed configuration lives underFIDO_HOME. - Tool notifications shorten large arguments, but full model tool results remain part of the provider conversation.
State and Persistence
Installed layout
The installer defaults to FIDO_HOME=~/.fido:
~/.fido/
app/ # this repository
llmorbiter/ # bundled local dependency
bin/fido # launcher
.env # API keys and configuration
state/
<workspace-slug>-<checksum>/
undo.json
suspends/
<timestamp>-<description>.json
The workspace slug is derived from the canonical workspace path and augmented with a checksum, keeping state isolated between projects and avoiding repository litter.
Source-checkout defaults
When run without the installed launcher, the defaults are:
UNDO_FILE=.fido-undo.json
SUSPEND_DIR=.fido-suspends
These paths are resolved from the selected workspace. They are covered by the repository's .fido-* ignore rule.
Normal conversations are not automatically persisted on exit. /suspend and /recall provide explicit manual snapshots.
Configuration
Copy .env.example to .env and set at least OPENROUTER_API_KEY.
| Variable | Required | Default / meaning |
|---|---|---|
OPENROUTER_API_KEY |
Yes | Credential for the OpenRouter provider. |
MODEL |
No | xiaomi/mimo-v2.6-flash |
TAVILY_API_KEY |
No | Enables websearch and browse_url. |
ROOT_DIR |
No | Workspace root when no positional path is supplied. |
UNDO_FILE |
No | Undo-log file; the installed launcher overrides it per workspace. |
SUSPEND_DIR |
No | Suspend directory; the installed launcher overrides it per workspace. |
COMPACT_DEBUG |
No | Set to 1 to report transcript-compaction statistics. |
FIDO_HOME |
No | Installer/launcher home, default ~/.fido. |
LLMORBITER_SRC |
No | Installer override for the sibling llmorbiter checkout. |
BASHRC |
No | Installer shell RC file, default ~/.bashrc. |
FIDO_SKIP_DEPS |
No | Set to 1 to let the installer skip npm install. |
FIDO_NO_MOUSE |
No | Set to 1 to disable terminal mouse reporting. |
For installed use, launcher-exported variables take precedence over workspace .env values. The source application also checks the repository's .env and then the current working directory's .env.
Installation and Development
Source checkout
Fido requires the sibling local dependency declared in package.json:
<parent-directory>/
fido/
llmorbiter/
From the Fido checkout:
npm install
cp .env.example .env
# Edit .env and set OPENROUTER_API_KEY.
npm start
Run against another workspace with:
npm start -- /path/to/project
Installed launcher
./install.sh
source ~/.bashrc
fido
# or
fido /path/to/project
The installer copies the app and llmorbiter into FIDO_HOME, installs dependencies, installs the launcher, and adds $FIDO_HOME/bin to PATH. Reinstalling preserves an existing $FIDO_HOME/.env. Use ./install.sh --uninstall to remove the install and its PATH block.
If llmorbiter is not a sibling checkout:
LLMORBITER_SRC=/path/to/llmorbiter ./install.sh
Tests
npm test
# or one file
node --test test/compact.test.js
The suite uses Node's built-in test runner and focuses on pure helper behavior: transcript compaction, file-browser paths and mention formatting, glob translation, line counting, mouse decoding, and UI/store rendering behavior. Unit tests do not make live model or Tavily requests.
Extending the Project
To add a model-facing tool:
- Create a module under
src/tools/usingdefineToolfromllmorbiter. - Validate arguments and return structured data or throw
ToolError. - Use
resolveSafefor workspace-relative filesystem paths. - Export a concise
notifyCalland, when useful, a tailorednotifyResultrenderer. - Register the tool and notification renderers in
src/tools/index.js. - Add its contract to
src/system-prompt.jswhen the model needs extra usage rules. - Add focused tests that do not require live network or provider calls.
The non-React REPL and tool modules should interact with rendering through the ui facade in src/ui/index.js; direct console writes would bypass the full-screen UI state.
Current Status and Planned Work
The current implementation does not include persistent todo plans or subagent delegation. PLAN.md is a proposed design for those features, including an update_plan tool, a right-side plan panel, a bounded read-only delegate tool, and separate subagent activity/status UI. Those capabilities should not be treated as available until their modules, registry entries, persistence, and tests are implemented.
Other practical limitations today include manual-only conversation snapshots, a view-only /undo command, no kernel-level sandbox, and no live provider integration tests in the standard suite.
License
Fido is released under the 0BSD License.