🍄 Foraged: Write Once, Run Everywhere (Terminal + Web) Using computers the way nature intended

Aug 1, 2025

There’s something beautiful about watching a skilled developer navigate their terminal. The fluid dance between commands, the elegant simplicity of text-based interfaces, and the raw efficiency of getting things done without the cruft of modern UIs. Yet every time I’d build a CLI tool, I’d find myself wrestling with the same problem: how do you create rich, interactive experiences in the terminal without losing your sanity?

That’s where Foraged was born – from equal parts frustration with terminal development and a wild idea: what if the same React components could run perfectly in both your terminal and your browser?

The Problem with Terminal Development

Most CLI tools fall into one of two camps. There are the simple, stateless commands that do one thing and exit cleanly. Think ls, grep, or git status. These are beautiful in their simplicity but limited in their interactivity. Then there are the complex TUI applications like htop, vim, or tmux – powerful, but often built with libraries that feel like they haven’t evolved since the 1990s.

What’s missing is the middle ground: applications that feel as natural to build as a React component but run entirely in your terminal. We have incredible tooling for web interfaces – hot reload, component isolation, state management, elegant testing patterns – but terminal development feels stuck in a different era.

But here’s the bigger problem: terminal applications are islands. You can’t easily demo them in documentation, share them with colleagues who don’t live in the terminal, or access them on mobile devices. They’re powerful but isolated from the broader computing ecosystem.

Write Once, Run Everywhere (For Real This Time)

Foraged starts with a simple but radical premise: every UI state in a terminal application should be representable as both a CLI command and a web URL. Not just similar – identical. The same React components, the same state, the same interactions, whether you’re running in your terminal or opening a link in your browser.

// This single component works identically in terminal AND browser
function ProjectDashboard() {
  const [filter, setFilter] = useCommandState('filter', 'all');
  const projects = useQuery('projects', fetchProjects);

  return (
    <Box flexDirection="column" padding={1}>
      <Text weight="bold">Project Dashboard</Text>
      <Menu>
        <MenuOption onSelect={() => setFilter('active')}>
          Active Projects
        </MenuOption>
        <MenuOption onSelect={() => setFilter('archived')}>
          Archived Projects  
        </MenuOption>
      </Menu>
    </Box>
  );
}

Run myapp projects --filter active in your terminal, or visit myapp.com?cmd=projects+--filter+active in your browser. Same state, same UI, different environment. Perfect synchronization between CLI commands and web URLs.

Universal Components with Terminal DNA

Foraged builds on React and Ink but provides a dual-rendering architecture that’s been missing from the ecosystem. We’re not trying to cram web components into terminals or make terminals act like browsers. Instead, we’re creating universal components that understand both environments natively.

The component hierarchy follows atomic design principles:

Atoms: The foundational building blocks

  • Button, Text, Input – basic interactive elements
  • Box – layout primitive that works beautifully in both terminal grids and web flexbox

Molecules: Simple combinations with specific purposes

  • Menu and MenuOption – keyboard navigation in terminal, click/touch on web
  • CommandPalette – global command runner that works everywhere
  • NotificationsFooter – status messages that feel native to each platform

Organisms: Complex interface sections

  • AppShell – responsive layout that adapts to terminal constraints and web viewports
  • DataTable – virtualized lists that scroll smoothly in both environments
  • FormBuilder – schema-driven forms with real-time validation

Templates & Pages: Complete application screens

  • Route-based organization that maps perfectly to both CLI subcommands and web paths

The Magic of Dual Rendering

Under the hood, Foraged automatically detects whether you’re running in a terminal or web environment and renders accordingly:

// Developer writes this once
export function ProjectList({ projects, filter }) {
  return (
    <Box borderStyle="round" padding={2}>
      <Text color="green">Found {projects.length} projects</Text>
      {projects.map(project => (
        <ProjectCard key={project.id} project={project} />
      ))}
    </Box>
  );
}

// Terminal: Renders with Ink using ANSI escape sequences
// Web: Renders as CSS-styled DOM with terminal aesthetics
// Identical functionality, platform-optimized presentation

The web version doesn’t just look like a terminal – it enhances the experience with responsive design, touch support, and features like shareable URLs while maintaining that distinctive terminal aesthetic.

Command-URL Parity That Actually Works

This is where things get really interesting. Every piece of application state that affects your UI must be serializable to command-line arguments. It sounds constraining, but it’s incredibly liberating:

Terminal CommandWeb URLShared State
foraged projects list --owner alice --status activeapp.com?cmd=projects+list+--owner+alice+--status+active{route: 'projects:list', owner: 'alice', status: 'active'}
foraged settings theme --mode darkapp.com?cmd=settings+theme+--mode+dark{route: 'settings:theme', mode: 'dark'}

Your terminal application becomes instantly shareable. Send someone a link, and they can see exactly what you’re seeing. Demo your CLI tool in browser-based documentation. Access your terminal interfaces on mobile devices. It’s the best of both worlds.

Developer Experience That Doesn’t Suck

Hot reload in terminal applications is almost unheard of, but it shouldn’t be. When you change a component in Foraged, the application restarts and restores your exact UI state by re-running the serialized command. It works in both terminal and web environments, maintaining that crucial feedback loop that makes modern development enjoyable.

The unified development workflow is genuinely game-changing:

# Single command supports both platforms
npm run dev          # Auto-detects terminal vs web
npm run dev:terminal # Force terminal mode  
npm run dev:web      # Opens browser automatically

# Debug anywhere
# Terminal: Traditional logging and state inspection
# Web: Full React DevTools, network tab, element inspection
# Both: Perfect state synchronization for debugging

A Framework Worth Building

The terminal isn’t going anywhere, but it shouldn’t be an island. Foraged represents a bet that terminal applications can be as delightful to build as they are to use, while also being universally accessible.

We’re not trying to replace every GUI with a terminal interface. We’re trying to eliminate the artificial barriers between different computing environments. The same React components that power your terminal application can seamlessly run in browsers, opening up new possibilities for distribution, collaboration, and accessibility.

The principles that revolutionized web development – component thinking, declarative interfaces, hot reload – deserve to work just as well in terminal development. And if your terminal application can also be a web application without any extra effort, why wouldn’t you want that superpower?

This isn’t about being a purist or making a philosophical statement about computing. It’s about building better tools with modern development practices and maximum reach. If calling it “foraging” makes people smile while they’re building great software, even better.

The first commit is already pushed. Time to start foraging for the future of universal interfaces.


Ready to build applications that work everywhere? Follow along as we forage the future of universal development. 🌿