zen-ai
v0.1.0
Published
Summon AI wherever you type in the terminal — a Ctrl+G omni-bar, inline @zen, and command-completion CLI for the Zen assistant.
Maintainers
Readme
@zen/cli
A terminal omni-bar for the Zen AI writing assistant. Press Ctrl+G in your
shell and a small TUI pops up, preseeded with your current command-line
buffer and context about where you are (cwd, git branch/status, project
manifest, and — inside tmux — your visible terminal screen). It talks to the
same Zen backend (https://zen.0xii.com) as the Chrome extension.
Requirements
- Node >= 20
Install
Published to npm as zen-ai (the installed command is zen):
npm install -g zen-aiOr from source:
cd apps/cli
npm install
npm run build
npm link # or: npm install -g .This puts a zen binary on your PATH (backed by dist/index.js).
Auth
zen login # prompts for email + password
zen logout # clears the local sessionzen login exchanges your credentials for a session token and writes it to
~/.config/zen/config.json (or $XDG_CONFIG_HOME/zen/config.json), created
with chmod 600. The password prompt suppresses terminal echo.
Shell integration
Either run the installer, which detects your shell ($ZEN_SHELL or $SHELL)
and idempotently appends a marked block to its rc file:
zen installor wire it up manually by eval-ing the generated snippet in your shell rc:
eval "$(zen shell-init zsh)" # or bash / fishSupported shells: zsh, bash, fish. Each binds Ctrl+G to open the
TUI seeded with the current line buffer:
| Shell | Binding mechanism | Rebindable via ZEN_KEY? |
|-------|-------------------|----------------------------|
| zsh | zle widget + bindkey | yes — set ZEN_KEY (e.g. export ZEN_KEY='^T') before sourcing rc |
| bash | bind -x on \C-g | no, hardcoded to Ctrl+G |
| fish | bind \cg | no, hardcoded to Ctrl+G |
zen install writes into ~/.zshrc, ~/.bashrc, or
~/.config/fish/config.fish depending on the detected shell, and is safe to
run more than once (it no-ops if the marker block is already present).
Usage
With the shell integration active, press Ctrl+G at any prompt. The TUI
opens on the terminal's alternate screen (your scrollback is untouched):
- The input is preseeded with whatever you'd typed on the command line.
Entersends the prompt and streams the reply in place.Escaborts an in-flight stream, or quits the TUI (restoring your prompt) when idle.Tabaccepts the answer — copies it to the clipboard (if a tool is available) and echoes it to stdout so it lands in your scrollback — then closes;↑/↓scroll a long answer.
You can also run zen tui directly (e.g. without a key binding).
Context capture
Every request auto-attaches: current working directory, shell name, OS,
git branch/status/last commit (if inside a repo), and a snippet of the
nearest project manifest (package.json, Cargo.toml, go.mod, or
pyproject.toml). Inside tmux, it also captures the last ~200 lines of
the visible pane via tmux capture-pane. Outside tmux this degrades
gracefully — no screen content is sent, but cwd/git/manifest context is
still included.
zen shell
zen shell wraps your login shell in a pseudo-terminal (via
node-pty, a native addon — the
npm install step must be able to build/download its prebuilt binary for
your platform) and transparently proxies everything — your shell behaves
exactly as normal — while layering three Zen capabilities on top.
zen shell # wraps $SHELL
zen shell --shell /bin/zsh # wrap a specific shellRequires a login session (zen login first) and a real TTY on both stdin
and stdout — it refuses to start otherwise (zen shell requires a TTY).
It also refuses to nest: running zen shell from inside an active one exits
immediately with already inside a zen shell (the wrapper sets
ZEN_SHELL_ACTIVE=1 in the child shell's environment and checks it on
startup). Unix only, like the rest of the shell integration; it is a
native addon (node-pty), so platforms without a prebuilt binary can't
install it.
3a — hotkey omni-bar (Ctrl+G)
Press Ctrl+G (or your ZEN_KEY override, same mechanism as the inline
binding above — and unlike that binding, this works regardless of shell,
since zen shell matches the raw hotkey byte itself rather than relying on
a shell-specific key binding) to suspend input forwarding and pop up the
same omni-bar TUI on the alternate screen, seeded with a snapshot of the
visible terminal output plus cwd/git context (not the shell's line-editor
buffer — zen shell doesn't parse it). Enter sends the prompt and streams
the reply in place; Tab accepts the answer (copies it to the clipboard when a tool is present
and echoes it into your scrollback), then closes; ↑/↓ scroll a long answer; Esc aborts an in-flight stream or quits the
overlay when idle. Either way, stdin is handed back to your shell underneath.
3b — inline @zen
Type a line starting with @zen and press Enter: a shell hook
(injected for the wrapped session only — zsh via a zle override on
accept-line, bash via a DEBUG trap under extdebug, fish via a
bind \r override; never touches your real rc files) intercepts the line
before the shell executes it, so the shell runs nothing (no
"command not found"). Zen streams the reply straight into the terminal,
using recent terminal output as context, and folds the turn into the
session's in-memory chat history so later @zen lines stay contextual. An
unrecognized $SHELL gets no hook installed, so @zen lines just run
(and fail) as an ordinary command.
3c — suggestions (Ctrl+O)
Press Ctrl+O for a one-shot completion suggestion for whatever you've
typed on the current command line. It's printed dimmed on the line below
the prompt ([zen suggestion] ...) and never touches your input line —
it's purely informational.
A real-time variant renders the suggestion as inline "ghost text" after the
cursor as you type (accept with Tab or Right-arrow). Enable it with
ZEN_GHOST_REALTIME=1. It is unit-tested (src/pty/ghost.test.ts) and
verified live against a real zsh under a pty: the ghost is drawn with cursor
save/restore, erased cleanly before the next keystroke is echoed, and
Tab/Right-arrow is consumed on accept (it no longer double-fires the shell's
own completion). It stays off by default because it makes a cloud
inference call on every typing pause — real latency and token cost — so
Ctrl+O on-demand is the recommended default; turn on the real-time variant
only if you want continuous suggestions and accept that cost.
Environment variables
| Variable | Purpose | Default |
|----------|---------|---------|
| ZEN_API_BASE_URL | Backend base URL | https://zen.0xii.com |
| ZEN_KEY | Rebind the omni-bar key (zsh only for the inline binding; used by zen shell on any shell) | ^G |
| ZEN_SHELL | Override shell detection for install/shell-init and the reported shell name in context | $SHELL |
| ZEN_SEED | Preseed the TUI input (set automatically by the shell integration from your buffer) | empty |
| ZEN_SHELL_ACTIVE | Set to 1 inside a zen shell session; used to refuse nesting. Not meant to be set manually | unset |
| ZEN_GHOST_REALTIME | Set to 1 to enable the experimental real-time ghost-text suggestion inside zen shell (see 3c above) | unset (off) |
| ZEN_GHOST_MODEL | Model used for the single-line completion suggestions (Ctrl+O and real-time ghost). Must be in the provider's allow-list. gpt-5.6-sol benchmarked fastest + most consistent for short command completion | gpt-5.6-sol |
| ZEN_LIVE_SHELL_TESTS | Set to 1 to run the gated live-shell tests (spawn a real zsh) in src/pty/hookinit.test.ts — dev-only, not run by default vitest run | unset (skipped) |
| ZEN_E2E_TOKEN | A live session token that enables the gated end-to-end test (src/e2e/shell.e2e.test.ts), which drives the real zen shell under a pty against the backend (spends AI tokens). Skipped by default. Optional companions: ZEN_E2E_PROVIDER_REF, ZEN_E2E_MODEL | unset (skipped) |
Manual end-to-end check
The interactive pieces below need a real TTY and a running backend, so they aren't automated. To verify a build by hand:
- Start a backend:
source ~/.cargo/env && cd services/backend && cargo run(or point at prod, the default). export ZEN_API_BASE_URL=http://localhost:8080(skip to use prod).node dist/index.js loginand enter test credentials (register the account viacurlagainst/api/auth/registerfirst if needed).ZEN_SEED='say hi in one word' node dist/index.js tui, pressEnterwith no edits, and confirm the reply streams into view.- Press
Escand confirm a clean exit: your shell prompt is restored with no leftover scrollback from the TUI. - In an interactive zsh:
eval "$(node /abs/path/to/apps/cli/dist/index.js shell-init zsh)", typeexplain this repo, pressCtrl+G, and confirm the TUI opens with the buffer preseeded and a populatedctx:line.
zen shell
node dist/index.js login(skip if already logged in).node dist/index.js shellto enter the wrapped shell.- Type an ordinary command (e.g.
ls) and confirm it runs and prints normally — you're inside a real proxied shell, not a sandbox. - Press
Ctrl+G: confirm the omni-bar opens on the alternate screen seeded with recent terminal output and a populatedctx:line;Esccloses it and hands control back to the shell prompt. - Type
@zen explain the last errorand pressEnter: confirm the line is not run as a command (no "command not found") and a streamed reply appears inline instead. - Half-type a command (e.g.
git status --) and pressCtrl+O: confirm a dimmed[zen suggestion] ...line appears below the prompt without altering what you'd typed. - Type
exit: confirm you land back in your original shell, and thatecho $?reflects the wrapped shell's real exit code (e.g.exit 3insidezen shellshould surface as3).
