Every csl subcommand, grouped by purpose. Flags use long form unless a short flag exists. Required flags are marked in the table.
Every command that reads repos loads ~/.config/csl/config.yaml and walks the configured dirs. See configuration for the schema.
| Command | Purpose |
|---|---|
csl search |
Search code across all indexed repos |
csl count |
Count matches, optionally grouped by repo or language |
csl read |
Read a file from a named repo by relative path |
csl repo |
List or interactively pick a repo |
csl index |
Manage the zoekt index (status, repair, clean) |
csl doctor |
Report index and daemon health |
csl query |
Validate a zoekt query without running it |
csl mcp |
Start the MCP stdio server (for Claude Code) |
csl version |
Print the build version |
Search code across all indexed repos using zoekt query syntax.
csl search [flags] <pattern>
csl search --serve # start daemon in foreground
csl search --stop # stop the running daemoncsl search tries the daemon first, falling back to an in-process search if the socket is unreachable. On the first search after a clean install (or after csl index --clean), csl indexes every configured repo synchronously and reports progress on stderr, then runs the query.
After every search, stale repos (new commits, dirty tree, branch switched) are re-indexed in the background before the next call.
| Flag | Default | Description |
|---|---|---|
-r, --repo <regex> |
"" |
Restrict to repos matching this regex or substring |
-l, --lang <name> |
"" |
Restrict to a single language (e.g. go, python, swift) |
-f, --file <regex> |
"" |
Restrict to file paths matching this regex (e.g. \.go$) |
-o, --output-mode <mode> |
files_with_matches |
files_with_matches prints unique paths; content prints matching lines |
-C, --context-lines <n> |
0 |
Context lines around each match. Only applies to content mode |
--limit <n> |
50 |
Maximum number of file results |
--case-sensitive |
false |
Force case-sensitive matching. Default is smart case (case-insensitive unless the query has uppercase) |
--reindex |
false |
Synchronously re-index before searching |
--json |
false |
Emit results as a JSON array of match objects |
--toon |
false |
Emit results as TOON, a compact format designed for LLM consumption |
--serve |
false |
Run the search daemon in the foreground (blocks until SIGINT or idle timeout) |
--stop |
false |
Send a shutdown request to the running daemon |
csl passes the pattern to zoekt's query parser. The common operators:
| Syntax | Meaning |
|---|---|
foo |
Literal substring match |
foo.*bar |
Regular expression |
"func Walk" |
Quoted phrase — exact match with spaces |
foo bar |
Space-separated terms are AND |
foo|bar |
OR |
-test |
NOT |
repo:<regex> |
Restrict to repos matching |
file:\.go$ |
Restrict to file paths matching |
lang:go |
Restrict to a language |
case:yes |
Case-sensitive this term |
Regex metacharacters inside the pattern need shell-escaping too. Validate a tricky query first with csl query.
Find a function across every repo:
csl search "func Walk"Narrow to one repo and one language, show surrounding context:
csl search "fmt\.Errorf" --repo code-search-local --lang go --output-mode content -C 3Machine-readable output for piping into tools:
csl search "import.*cobra" --file "\.go$" --limit 10 --jsonForce a re-index before searching, useful after a manual git checkout:
csl search --reindex "NewBuilder"Count matches across indexed repos, optionally grouped.
csl count [flags] <pattern>Takes the same query syntax as csl search. Always goes through the daemon first, falling back to in-process. Exits with a single total unless --group-by is set.
| Flag | Default | Description |
|---|---|---|
-r, --repo <regex> |
"" |
Restrict to repos matching |
-l, --lang <name> |
"" |
Restrict to a language |
--group-by <key> |
"" |
Group counts by repo or language |
--json |
false |
Emit results as JSON |
Single total:
csl count "TODO"Per-repo breakdown:
csl count "func.*Error" --group-by repoOutput:
mad01/code-search-local 87
myorg/service-a 55
total: 142
Per-language breakdown, filtered to Go files:
csl count "import" --lang go --group-by languageRead a file from a named local repo.
csl read --repo <name> [flags] <path>Resolves --repo against every configured repo using substring match (not regex). The file path is relative to the repo root. Lines are 1-based; --start-line 0 or --end-line 0 means open-ended.
| Flag | Required | Default | Description |
|---|---|---|---|
-r, --repo <name> |
yes | — | Repo name to read from. Substring match |
--start-line <n> |
no | 0 |
First line to show (1-based, 0 for start) |
--end-line <n> |
no | 0 |
Last line to show (1-based inclusive, 0 for end) |
--json |
no | false |
Emit as JSON: {repo, path, lines: [{number, text}]} |
csl read internal/cli/root.go --repo code-search-local
csl read main.go --repo code-search-local --start-line 10 --end-line 30
csl read README.md --repo code-search-local --jsonDiscover and pick git repos.
csl repo # interactive fuzzy finder
csl repo --list # tab-separated name + path
csl repo --json # structured output
csl repo --toon # TOON-encoded output for LLMsWithout flags, csl repo opens a fuzzy finder and prints the absolute path of the selected repo on stdout, useful for cd $(csl repo) workflows.
| Flag | Default | Description |
|---|---|---|
--list |
false |
Print every repo, tab-separated: name<TAB>path |
--json |
false |
Emit array of {name, path, remote, host} (implies --list) |
--toon |
false |
TOON-encoded output under a repos key (implies --list) |
cd $(csl repo) # pick and cd
csl repo --list | grep service-
csl repo --json | jq '.[] | .host' | sort -uManage the zoekt index.
csl index # re-index stale repos only
csl index --all # force re-index of every repo
csl index --status # report per-repo freshness
csl index --repair # validate and drop corrupted shards
csl index --clean # delete the entire index directoryBy default, csl index diffs the current repo fingerprints against state.json and re-indexes only the repos whose fingerprint changed. The fingerprint covers HEAD, branch, and git status --porcelain, so any commit, checkout, or working-tree change triggers a re-index.
--repair opens every *.zoekt shard, parses its metadata, and removes any that fail to read. Run csl index after repair to rebuild affected repos.
| Flag | Default | Description |
|---|---|---|
--all |
false |
Re-index every repo, regardless of fingerprint |
--status |
false |
Print a status table instead of indexing |
--clean |
false |
Remove ~/.config/csl/search-index/ entirely |
--repair |
false |
Validate shards, remove corrupted ones |
--json |
false |
JSON output for --status and --repair |
Status table:
csl index --statusOutput:
REPO STATUS DIRTY INDEXED AT BRANCH
mad01/code-search-local fresh no 2m12s ago main
myorg/service-a stale yes 1h4m ago feat/auth
Full rebuild:
csl index --allRepair after a crash or bad disk:
csl index --repair
csl indexReport index and daemon health in one view.
csl doctor [--json]doctor combines everything csl index --status reports plus shard integrity, daemon state, and total index size. It classifies issues into stale, missing, and dirty. Run it when searches look wrong or slow.
Index directory: /Users/you/.config/csl/search-index
Index size: 84.3 MB
Total repos: 17
Index shards: 17 (17 healthy, 0 corrupted)
Search daemon: running
Issues (1):
dirty myorg/service-a 3 modified, 1 untracked
Healthy (16):
mad01/code-search-local indexed 2m12s ago
...
If corrupted shards are reported, the output suggests csl index --repair followed by csl index.
Validate and parse a zoekt query without running it.
csl query [--json] <pattern>Useful when the query has escaped regex metacharacters and you want to confirm the parser sees what you mean. Returns the parsed tree on success, or the parse error plus a fixing hint on failure.
csl query "func Walk"
# valid: true
# parsed: (and substr:"func Walk")
csl query "func \(Walk"
# valid: false
# error: ...
# hint: Escape special regex characters with backslash, e.g. "func \(Walk"Start the MCP stdio server.
csl mcpReads JSON-RPC 2.0 requests from stdin, writes responses to stdout, and exits when stdin closes. The MCP client spawns one per session; there is no persistent server.
Register with Claude Code:
claude mcp add --scope user csl -- csl mcpSmoke test without an MCP client:
( printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}\n{"jsonrpc":"2.0","method":"notifications/initialized"}\n{"jsonrpc":"2.0","id":2,"method":"tools/list"}\n'; sleep 1 ) | csl mcpExpected: an initialize response followed by tools/list listing eight csl_* tools with their input/output schemas. See MCP reference for per-tool details.
Print the version string.
csl versionThe version is set at build time via -ldflags "-X github.com/mad01/code-search-local/internal/cli.Version=<value>". make install sets it to the short git hash; go install without ldflags yields dev.