Server for ai autocomplete, next edit suggestions using embeddings and an openai compatible endpoint
  • Rust 73.5%
  • Vue 15.3%
  • Lua 5%
  • JavaScript 3.3%
  • TypeScript 2.5%
  • Other 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-06 14:26:43 +02:00
docs/screenshots docs: screenshots 2026-10-02 16:15:36 +02:00
migrations fix: fix duplicate chunks with absolute/relative paths 2026-10-02 09:48:52 +02:00
nvim chore: deduplicate code 2026-10-05 22:18:21 +02:00
src feat: increase rule generation token limits 2026-10-06 14:26:43 +02:00
tests feat: pausing/continuing rule jobs 2026-10-06 14:08:17 +02:00
vscode feat: add daemon start command to both editor extensions 2026-10-02 15:51:48 +02:00
web feat: pausing/continuing rule jobs 2026-10-06 14:08:17 +02:00
.gitignore feat: vscode extension 2026-09-23 14:30:04 +02:00
.luarc.json feat: types for lazy.nvim config autocomplete 2026-09-21 11:11:07 +02:00
.tool-versions chore: web boilerplate 2026-09-22 09:06:52 +02:00
build.rs fix: fix built daemon not seeing built web frontend, now frontend is linked statically into the daemon bin 2026-10-05 12:42:54 +02:00
build.sh fix: fix completions not showing up in vscode 2026-09-23 14:49:43 +02:00
Cargo.lock feat: mcp server 2026-10-02 14:59:40 +02:00
Cargo.toml feat: mcp server 2026-10-02 14:59:40 +02:00
dev.md feat: pausing/continuing rule jobs 2026-10-06 14:08:17 +02:00
justfile chore: build web when running just install 2026-10-02 15:19:25 +02:00
plan-lsp.md chore: lsp feature plan 2026-10-05 12:05:09 +02:00
plan-mcp.md feat: mcp server 2026-10-02 14:59:40 +02:00
plan.md fix: read HISTFILE env var directly instead of through a cli option 2026-10-02 13:36:04 +02:00
README.md feat: increase rule generation token limits 2026-10-06 14:26:43 +02:00
research.md docs: some research 2026-09-18 14:45:35 +02:00

ai-cmp-lsp

AI code completions as ghost text in Neovim and VS Code, AI command-line completion for bash/zsh/fish, a web dashboard, and an MCP server — one local daemon (ai-cmp-daemon) behind OpenAI-compatible APIs serves them all. Development instructions: dev.md.

Build

./build.sh            # web dashboard + daemon + VS Code .vsix (debug)
./build.sh --release  # optimized

Configuration — ~/.ai-cmp/config.toml

On first start the daemon creates this file with the defaults below (mode 0600) when it doesn't exist — nothing to copy by hand.

# Top-level, before the sections: paths the daemon must not index.
# Globs — `*`/`?` stop at "/", `**` crosses directories.
# ignore = ["**/node_modules/api/**", "/home/marc/old-projects/*"]

# Any string value can come from a file: `api_key = "{file:~/.secrets/key}"`
# (absolute, `~`- or config-relative; a missing file fails startup).

[server]
# port = 8890  # optional fixed port; unset = free port, the plugins read
#              # the actual one from the runtime dir's daemon.json

[embeddings]
api_url = "http://localhost:8888/v1/embeddings"
api_key = ""
model = "unsloth/bge-small-en-v1.5"

[completion]
provider = "openai"
model = "unsloth/Qwen3.5-4B-GGUF"
api_key = ""
max_tokens = 96

[completion.providers.openai]
api_url = "http://localhost:8888/v1"
format = "openai"     # "openai" | "anthropic" | "fim"
auth = "bearer"

# Extra request fields, deep-merged into the LLM call (vLLM/llama.cpp
# flags; public APIs reject unknown top-level fields).
[completion.providers.openai.extra]
chat_template_kwargs = { enable_thinking = false }

# Rules generation (dashboard → Rules). Optional; defaults shown.
# Rules calls run with thinking enabled, and reasoning counts against
# these caps — keep them big enough to think and answer in. Reasoning is
# stripped before anything is persisted.
[rules]
batch_chars = 8000          # source characters per LLM call
map_max_tokens = 10240      # generated tokens per batch extraction
reduce_max_tokens = 15360   # generated tokens for merge/final calls
concurrency = 2             # parallel LLM calls
max_chunks = 0              # uniformly sample at most N chunks; 0 = all

# Shell autocomplete. Optional; defaults shown.
[shell]
mode = "cycle"            # cycle | single | ghost (zsh only; bash/fish → single)
keybind = "<C-Space>"     # nvim notation, translated per shell
accept_key = "<S-Right>"  # ghost mode: accepts the suggestion
history_count = 20        # last N history entries sent to the LLM
timeout_ms = 2000         # LLM budget; past it the shell shows an error
candidates = 3            # command lines per call (cycle depth)

The dashboard's Config page edits the same file: file pickers for {file:…} and path fields, comments preserved on save, daemon restarts to apply.

Neovim

-- lazy.nvim
{
  dir = "~/Dev/ai-cmp-lsp/nvim",
  name = "ai-cmp",
  config = function()
    require("ai-cmp").setup({
      trigger = { mode = "debounced", debounce_ms = 100, keymap = "<C-Space>" },
      display = { max_visible_lines = 12, hl_group = "Comment" },
      keymaps = { accept = "<Tab>", dismiss = "<Esc>" },
    })
  end,
}

Attaches to the shared daemon or spawns a detached one (a lock file keeps exactly one per user), syncs buffers, and streams completions as ghost text — <Tab> accepts, <Esc> dismisses. Every workspace is a session with its own embedding worker; idle sessions expire after 5 minutes (AI_CMP_SESSION_TTL_SECS, 0 = never).

Commands: :AICmpTrigger manual completion · :AICmpStatus daemon health · :AICmpLog log tail · :AICmpDashboard open the dashboard · :AICmpStart start or re-attach on demand · :AICmpShutdown stop the daemon (it outlives the editor otherwise).

Shell autocomplete

Press a key and the chat model completes the whole command line from your history — atuin's read-only history.db when installed, else the shell's history file ($HISTFILE / fish's), else just the cwd:

# ~/.bashrc                      # ~/.zshrc                      # ~/.config/fish/config.fish
eval "$(ai-cmp-daemon shell init bash)"   # or: … init zsh        # ai-cmp-daemon shell init fish | source

Default key Ctrl-Space; [shell] selects the mode:

  • cycle — insert the best candidate, press again to walk the rest (wraps; refetches whenever the line changes).
  • single — top candidate, a fresh call per press.
  • ghost (zsh) — dim suggestion after the cursor; accept_key (default Shift-Right) accepts it, Enter runs the line without it. bash/fish have no ghost-text API and fall back to single-insert.

Errors surface instead of failing silently (budget is timeout_ms); history is redacted (token/password/key-shaped values masked) before it reaches the LLM; the provider must be chat-format (format = "fim" is rejected). Re-run the eval … line after changing [shell] — keybind and mode are baked into the generated script. Needs bash ≥ 4.4 (mapfile), zsh with bindkey, or fish ≥ 3.1 (string split0).

Web dashboard

The daemon serves it on the same loopback port (127.0.0.1 only). Browse and delete projects/files/chunks/embeddings, see embedding staleness and live counters, run natural-language Search over a project's chunks, group duplicated code on the Shared page (exact by content hash, near-identical by cosine), and generate a project ruleset on the Rules page (chat-format provider required; live progress, pause, cancel, continue for jobs that stopped without a ruleset, rendered result). The sidebar shows daemon liveness; a dead daemon reads as a plain error, not a crash.

cd web && bun install && bun run build   # once → web/dist

Until web/dist exists the daemon serves a notice page with these instructions; files are read per request, so a rebuild only needs a reload.

Screenshots

Overview Search with results
Overview Semantic search
Files Chunks
Files Chunks
Embeddings Shared code
Embeddings Duplicate detection
Rules Log
Rules Log
Config
Config

MCP server

POST /mcp on the daemon's port — point a local MCP host at it:

{ "mcpServers": { "ai-cmp": { "type": "http", "url": "http://127.0.0.1:8890/mcp" } } }

Port: fixed by the editor plugin (serverPort), otherwise read port from $XDG_RUNTIME_DIR/ai-cmp/daemon.json. Scope: a read-only view plus indexing — rules jobs can be listed and inspected but never started, and nothing is ever deleted.

tool what it answers
overview server/db snapshot: counts, embedding progress, live counters
list_projects project hashes and row counts
index_project start indexing a root no editor has opened yet
search_code natural-language semantic search (one embedding round-trip)
shared_code duplicated / near-duplicated code across projects
chunk_at chunk covering a file line range + its same-kind neighbours
list_rules rules-job history (read-only)
get_rule_job one job's progress and finished ruleset (read-only)

VS Code

npx @vscode/vsce package   # in vscode/ → ai-cmp-0.1.0.vsix
code --install-extension ai-cmp-0.1.0.vsix

Same shared daemon as Neovim (settings ai-cmp.serverCommand, ai-cmp.serverConfig, ai-cmp.serverPort); completions ride the native inline suggestions (Tab accepts, Esc dismisses). The status bar shows embedding progress and opens the dashboard on click. Commands: AI Complete, Show Daemon Status, Show Daemon Log, Open Web Dashboard, Start Shared Daemon, Stop Shared Daemon. Debugging: Output panel → ai-cmp. Note: the stable inline-completion API resolves the provider once per request, so ghost text appears when the done event lands rather than token-by-token.