Skip to content

pittcat/codex.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

codex.nvim

Lightweight Neovim integration for OpenAI Codex CLI. Opens the Codex TUI in a terminal split.

This plugin follows the structure of base.nvim and references ideas from claudecode.nvim, but is intentionally minimal.

Requirements

  • Codex CLI installed and on PATH: npm i -g @openai/codex or brew install codex
  • Optional: git for project root detection

Quickstart

require('codex').setup()

-- Example mappings
vim.keymap.set('n', '<leader>co', function() require('codex').open() end, { desc = 'Codex: Open TUI' })
vim.keymap.set('n', '<leader>ct', function() require('codex').toggle() end, { desc = 'Codex: Toggle terminal' })

Logging

  • Log Level: log_level = 'trace'|'debug'|'info'|'warn'|'error' (case‑insensitive). Also supports CODEX_LOG_LEVEL env var.
  • Log to File: set log_to_file = true to write logs to a fixed tmp file. The file is truncated on every setup so you always get a fresh log for a new debug session.
    • Default path: $TMPDIR/codex.nvim.log (uses your OS tmp dir; on macOS/Linux typically /tmp/codex.nvim.log).
    • Override path: set log_file = '/custom/path/codex.nvim.log' or provide a string value to log_to_file.

Example:

require('codex').setup({
  log_level = 'trace',
  log_to_file = true,           -- writes to $TMPDIR/codex.nvim.log and truncates on setup
  -- log_to_file = '/tmp/codex.nvim.log', -- custom path (also truncates)
})

Terminal Options

  • Direction: 'horizontal' (bottom/top split) or 'vertical' (left/right split).
  • Size: split height/width.
    • Number >= 1: absolute rows (horizontal) or columns (vertical).
    • 0 < Number < 1: fraction of your current window (not full screen).
      • Example: 0.33 → one-third of the current window.
  • Position: 'left' | 'right' | 'top' | 'bottom'. When omitted, respects your splitright/splitbelow settings.
  • Provider: 'native' | 'snacks' | 'auto'.
    • 'native': use Neovim splits.
    • 'snacks': use folke/snacks.nvim terminal. Errors if Snacks is missing (falls back in practice, with a warning).
    • 'auto': use Snacks when available, otherwise silently fall back to native.
  • Reuse: reuse the previous terminal buffer if present.
  • Auto Insert Mode: enter insert mode automatically after opening.
    • When set to false, the terminal opens without taking focus and stays in normal mode (both native and Snacks providers).
  • Fix Display Corruption: schedule a redraw! after opening to remedy rare artifacts.

Sizing Examples

-- Vertical split with 40% of the current window width
require('codex').setup({
  terminal = { direction = 'vertical', size = 0.40 }
})

-- Horizontal split with 12 rows
require('codex').setup({
  terminal = { direction = 'horizontal', size = 12 }
})

-- Explicit side + fraction
require('codex').setup({
  terminal = { direction = 'vertical', position = 'right', size = 0.25 }
})

-- Auto provider: Snacks if installed, else native
require('codex').setup({
  terminal = { provider = 'auto', direction = 'horizontal', size = 0.33 }
})

Behavior Details

  • Fractional Size: calculated against the current window before splitting, for both native and Snacks providers.
  • Toggle: :CodexToggle toggles the most recent Codex terminal (works with both providers).
  • Reuse: when enabled, new runs reattach to the existing terminal buffer and start the command again.
  • Headless: in headless (no UI) mode, commands run as background jobs without creating splits.
  • Compatibility: legacy split_width_percentage is accepted as an alias for terminal.size when size is not set.
  • Logging to File: when enabled, the plugin truncates the log file on setup and appends new entries with timestamps; warn/error also go through vim.notify.

Commands

  • :CodexOpen [prompt] — Open Codex TUI (optionally seeded with an initial prompt)
  • :CodexToggle — Toggle Codex terminal split

Terminal Bridge Commands

Send file paths and code selections directly to an open Codex terminal:

  • :CodexSendPath — Send current buffer path to attached terminal with space separator for continuous sending
  • :CodexSendSelection — Send visual selection as reference or content based on configuration
  • :CodexSendReference — Send visual selection as file reference with space separator (e.g., @file.lua#L10-L20)
  • :CodexSendContent — Send visual selection with actual code content

Usage Examples:

" Send current file path
:CodexSendPath

" Visual mode: select code then send reference
:'<,'>CodexSendSelection

" Send specific line range as reference
:187,188CodexSendReference

Snacks Integration

codex.nvim can optionally use folke/snacks.nvim for opening the terminal with its window manager.

  • Install and set up folke/snacks.nvim in your config.
  • Set terminal.provider = 'snacks' to always use Snacks, or terminal.provider = 'auto' to use Snacks only when it’s available (falls back to native splits).
  • terminal.direction, size, and position are respected:
    • direction = 'horizontal' uses a bottom/top split; size applies to height.
    • direction = 'vertical' uses a left/right split; size applies to width.
    • position can be 'left' | 'right' | 'top' | 'bottom'. When omitted, your splitright/splitbelow settings decide the side.
    • When size is < 1, it’s treated as a fraction of your current window (not the full screen), for both native and Snacks providers.

Example with Snacks:

require('codex').setup({
  terminal = {
    provider = 'snacks',
    direction = 'vertical',
    size = 0.35,         -- 35% of the current window width
    position = 'right',  -- or 'left', 'bottom', 'top'
  }
})

Tip: legacy split_width_percentage is still accepted as an alias for terminal.size when size is not provided.

Alerts

  • Job Exit Alert and Idle Completion Alert: system notification on completion.
require('codex').setup({
  -- Exactly one of these is recommended for clarity:
  alert_on_idle = true,        -- notify when terminal output becomes idle (job keeps running)
  -- alert_on_exit = true,     -- notify when the job exits

  notification = {
    enabled = true,
    sound = 'Glass',             -- macOS notification sound name
    include_project_path = true, -- include project/cwd in the message
    speak = false,               -- off by default (no TTS)
    backend = 'terminal-notifier', -- prefer terminal-notifier on macOS
    terminal_notifier = {
      ignore_dnd = true,           -- pass -ignoreDnD
      sender = 'com.apple.Terminal',
      group = 'codex.nvim',
      activate = 'com.apple.Terminal',
    },
    -- Voice is only used if speak=true
    -- voice = 'Samantha',

    -- Idle detection tuning (used when alert_on_idle=true)
    idle = {
      check_interval = 1500,     -- ms between checks
      idle_checks = 3,           -- consecutive no-change checks to consider idle
      lines_to_check = 40,       -- how many tail lines to hash
      require_activity = true,   -- require seeing output changes before considering idle
      min_change_ticks = 3,      -- require at least N changes before eligible
    },
  },
})

Behavior:

  • macOS: prefers terminal-notifier for native banner + sound; falls back to osascript if available; otherwise uses vim.notify.
  • Other OS: falls back to vim.notify (no system sound).
  • Idle alert is one-shot: once notified, the idle monitor stops until you re-run Codex.
  • Exit alert is de-duplicated: suppresses repeated success notifications within a short window.
  • Idle alert suppression: if the tail content contains cancellation markers (e.g., "Request interrupted by user", "Canceled", "取消"), no notification is sent. Configure via notification.idle.cancel_markers.
    • Included by default: "🖐  Tell the model what to do differently" and its plain variant without the emoji.

Idle Parameters Explained:

  • check_interval (ms): polling interval to sample terminal tail content.
  • idle_checks: number of consecutive identical samples to consider idle.
  • lines_to_check: how many tail lines are included in the hash comparison.
  • require_activity: only consider idle after seeing at least one change.
  • min_change_ticks: require at least N changes before an idle period can trigger.

Tip: Prefer enabling either alert_on_idle or alert_on_exit to avoid redundant signals. Internally, duplicate notifications are still suppressed for safety.

Chinese Documentation: see README.zh-CN.md.

Terminal Bridge

The terminal bridge allows sending file paths and code selections directly to an open Codex terminal:

require('codex').setup({
  terminal_bridge = {
    path_format = 'abs',          -- 'abs' | 'rel' | 'basename' - format for file paths
    path_prefix = '@',            -- prefix added to paths (e.g., '@' for Claude Code)
    auto_attach = true,           -- automatically attach terminals created by CodexOpen
    selection_mode = 'reference', -- 'reference' | 'content' - default for visual selections
  },
})

Configuration Options:

  • path_format: How file paths are formatted:
    • 'abs': Absolute paths (e.g., @/Users/name/project/file.lua)
    • 'rel': Relative to current working directory (e.g., @file.lua or @src/module.lua)
    • 'basename': File name only (e.g., @file.lua)
  • path_prefix: String prepended to paths (typically '@' for Claude Code compatibility)
  • auto_attach: When true, terminals opened by :CodexOpen are automatically attached to current tab
  • selection_mode: Default behavior for :CodexSendSelection:
    • 'reference': Send file reference like @file.lua#L10-L20
    • 'content': Send reference + actual code content

Tab Isolation: Each tab maintains its own terminal connection, preventing confusion when working with multiple tabs.

Workflow:

  1. Open Codex terminal: :CodexOpen (automatically attaches to current tab)
  2. Send file path: :CodexSendPath (adds space after path for continuous input)
  3. Select code and send reference: :'<,'>CodexSendSelection

Note: Path and reference commands append a space instead of newline, allowing multiple paths/references to be sent on the same line. For example, executing :CodexSendPath twice will result in @file1.lua @file2.lua on the same line.

Example Key Mappings:

-- Terminal bridge shortcuts
vim.keymap.set('n', '<leader>cp', ':CodexSendPath<CR>', { desc = 'Codex: Send file path' })
vim.keymap.set('v', '<leader>cs', ":'<,'>CodexSendSelection<CR>", { desc = 'Codex: Send selection' })
vim.keymap.set('v', '<leader>cr', ":'<,'>CodexSendReference<CR>", { desc = 'Codex: Send reference' })

Notes

  • Root detection defaults to the Git repo root. Override via cwd_provider = 'cwd' or 'file'.
  • Environment variables and extra args can be passed via env and extra_args in setup.
  • For Codex CLI usage and flags, see: https://github.com/openai/codex (README and docs)

Testing

  • Requirements: nvim available on PATH.
  • Tests run headless and do not require the real Codex binary (they use echo).

Run all tests:

make test

Troubleshooting

  • Error: Invalid argument: env when using Snacks provider
    • Cause: some environments or older Neovim builds do not accept an empty env option through terminal APIs.
    • Fix: codex.nvim now omits env when empty and sanitizes values to strings. If you explicitly set env, ensure it is a table of string→string pairs. Consider upgrading Neovim if the issue persists.

About

No description, website, or topics provided.

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •