Example extensions demonstrating each extension category. To try one, symlink it into your extensions directory and restart freshell:
# macOS/Linux
ln -sf "$(pwd)/examples/extensions/notepad" ~/.freshell/extensions/notepad
ln -sf "$(pwd)/examples/extensions/status-dashboard" ~/.freshell/extensions/status-dashboard
ln -sf "$(pwd)/examples/extensions/live-counter" ~/.freshell/extensions/live-counter
ln -sf "$(pwd)/examples/extensions/system-monitor" ~/.freshell/extensions/system-monitor
# Windows (use task-list instead of system-monitor)
ln -sf "$(pwd)/examples/extensions/task-list" ~/.freshell/extensions/task-listAfter restarting, each extension appears in the New Tab pane picker.
A scratchpad with auto-save to localStorage. No build step, no dependencies —
just an index.html and a freshell.json. Freshell serves the static files
directly.
Key manifest fields: category: "client", client.entry points to the
HTML file.
A live system resource monitor. Freshell spawns the Node process, allocates a
port via the {{port}} template variable, and waits for the readyPattern
to appear on stdout before rendering the iframe.
Key manifest fields: category: "server", server.command and
server.args define how to start the process, server.env passes the
allocated port, server.readyPattern tells freshell when the server is ready.
Note: If your server extension uses CommonJS (require()), include a
package.json without "type": "module" in the extension directory.
Otherwise Node may inherit an ESM package.json from a parent directory.
A shared counter with real-time updates. Click +/- in one pane and see the count update instantly in all other panes via WebSocket. Demonstrates that server extensions can use WebSocket through freshell's HTTP proxy.
Key manifest fields: Same as status-dashboard. The WebSocket connection uses a relative URL so it routes through the proxy automatically.
Wraps top as a terminal pane. The simplest possible extension — just a
manifest pointing at an existing binary. No code needed.
Key manifest fields: category: "cli", cli.command is the binary to run.
Wraps tasklist as a terminal pane — the Windows equivalent of the
system-monitor example.
Note: CLI extensions must also be enabled in freshell settings (Settings → Coding CLI → Enabled Providers) to appear in the picker.
Server extension iframes load through freshell's built-in HTTP proxy at
/api/proxy/http/:port/. This means:
- The browser only needs to reach freshell's port (e.g., 3001)
- Extension server ports are internal — they don't need to be exposed
- Docker/WSL2/containers work out of the box — no extra port mapping needed
- Both HTTP and WebSocket are proxied transparently
Important: Use relative URLs in your extension's JavaScript, not
root-relative ones. The iframe loads at a proxy subpath, so root-relative
URLs (starting with /) resolve against freshell's origin instead of the
extension's.
// GOOD — relative URL, resolves through the proxy
fetch('api/status')
const wsUrl = 'ws://' + location.host + location.pathname + '/ws'
// BAD — root-relative, hits freshell's /api/status instead of the extension's
fetch('/api/status')See live-counter/server.js and status-dashboard/server.js for examples.
Server extensions work in Docker without exposing extension ports.
See examples/docker/ for a ready-to-run Dockerfile.
- Create a directory with a
freshell.jsonmanifest - Choose a category (
client,server, orcli) - Symlink into
~/.freshell/extensions/<name> - Restart freshell
See the extension-installer skill for the full manifest reference and validation checklist.