Command reference
One binary, eleven sub-commands. The first positional word selects the mode; flags accept a short form (-c), a one-dash long form (-cmd) and a GNU form (--command).
Synopsis
context-forge -h | --help
context-forge exec [-v LEVEL] [-d FD] [-n] -c BIN [ARG...]
context-forge server [-P DIR] [-r DIR] [-R] [-s FILE] [-a IP] [-p PORT] [-m MODEL] [-v LEVEL]
context-forge check local [same flags as server]
context-forge check dist [-a IP] [-p PORT] [-m MODEL] [-v LEVEL]
context-forge setup [-C] [same flags as server]
context-forge start | stop | restart | remove | install-ollama [-v LEVEL]
context-forge status [-a IP] [-p PORT]
context-forge pull MODEL
Every mode accepts -h/--help, which prints the usages the argument parser knows for that mode.
exec — wrap a command
context-forge exec [-v LEVEL] [-d FD] [-n] -c BIN [ARG...]
Spawns BIN ARG..., captures the file descriptor selected by -d (default 1, stdout) through a pipe, hands the captured text to the server, and writes the server's answer to that same descriptor once the child has exited. Everything after -c BIN belongs to the wrapped program, so put context-forge's own flags before -c.
| Flag | Value | Meaning |
|---|---|---|
-c, -cmd, --command | BIN [ARG...] | Required. The program to run and its arguments. BIN is resolved on PATH like a shell would. |
-d, -fd, --redirect | 0 | 1 | 2 | n | Which descriptor of the child to capture and rewrite. Must be an open descriptor of the calling process. Default: 1 (stdout). Use 2 to reformat stderr only. |
-n, -!nl, --no-nl | — | Do not append a trailing newline to the formatted output when it lacks one. |
-v, --verbose | none | basic | advanced | debug | Client-side logging on stderr. advanced shows the raw and formatted blocks; debug adds the IPC steps. |
Exit status is the wrapped program's: its exit code, or its signal number if it was killed. Descriptors that are not captured are inherited as-is, so exec -c make still streams stderr live while stdout is buffered for formatting.
Output is buffered, not streamed. The captured descriptor is read to the end before anything is printed. Interactive or long-running programs (editors, tail -f, servers) are not good candidates for wrapping.
Failsafe: if the systemd unit is missing, stopped or crashed, exec prints [FALLBACK] on stderr (unless -v none) and replaces itself with the plain command. A wrapped command therefore always runs.
server / check — run or validate the daemon
context-forge server [-P DIR] [-r DIR] [-R] [-s FILE] [-a IP] [-p PORT] [-m MODEL] [-v LEVEL]
Loads plugins, then rule files, then the Ollama client, creates the shared-memory channel and loops forever answering clients. You rarely run it by hand — setup generates a unit that does — but it is handy in a terminal with -v debug while writing rules:
context-forge stop # free the unit first: the client always talks to the systemd unit's PID
context-forge server -R -P ./plugins -r ./rules -v debug
Clients locate the server through systemctl --user show context-forge.service -p MainPID. A server started manually in a terminal is not found by exec; it is only useful to watch the loading logs. For an end-to-end test, use setup (or edit the unit) and read journalctl --user -fu context-forge.
check performs the load phase and exits, reporting each plugin/rule/LLM problem instead of ignoring it:
context-forge check local -R -P ./plugins -r ./rules -s ./system-prompt -v debug # validate given paths
context-forge check dist -v debug # validate ~/.config/context-forge/* (after setup --copy)
setup · start · stop · restart · status · remove
| Command | What it does |
|---|---|
setup [-C] [server flags] | Writes ~/.config/systemd/user/context-forge.service with ExecStart=<binary> server <flags>, then daemon-reload, enable, restart. With -C/--copy, first clones plugins, rules and system prompt under ~/.config/context-forge/ and points the unit at the copies. Paths are made absolute. See Register the daemon. |
start / stop / restart | Thin wrappers over systemctl --user start|stop|restart context-forge.service. Restart after editing rule files: they are read once at start-up. |
status [-a IP] [-p PORT] | Prints two lines: the unit state (not installed / running / stopped / crashed / starting / stopping) and whether the ollama binary is installed and its HTTP API answers at IP:PORT (default localhost:11434, probed with curl). |
remove | Stops and disables the unit, deletes the unit file, reloads systemd, and deletes ~/.config/context-forge/ entirely. Does not uninstall the binary. |
install-ollama · pull
| Command | What it does |
|---|---|
sudo context-forge install-ollama | Runs curl -fsSL https://ollama.com/install.sh | sh. Refuses to run unless effective UID is 0. |
context-forge pull MODEL | Replaces itself with ollama pull MODEL. Requires Ollama to be installed and (for pulling) its server reachable. |
Flags shared by server, check and setup
| Flag | Value | Meaning | Default |
|---|---|---|---|
-P, -so, --plugins | directory | Where to look for *.so plugins. Must exist. | none → rules are ignored |
-r, -rules, --rules | directory | Where to look for *.cfg rule files. Must exist. Without it the whole rule stage is skipped (and plugins are not loaded either). | none |
-R, -rec, --recursive | — | Descend into sub-directories of both directories above. The shipped layout plugins/{triggers,pre-rules,rules}/ needs it. | off |
-s, --system-prompt | file | Text file sent as the system field to Ollama. Enables the LLM pass. | none → LLM pass off |
-a, -ip, --ip | host/IP | Ollama host (resolved at start-up). | localhost |
-p, -port, --port | 1–65535 | Ollama port. | 11434 |
-m, -model, --model | name[:tag] | Model name; verified with /api/show at start-up. | qwen2.5-coder:1.5b |
-v, --verbose | none|basic|advanced|debug | Log level on stdout/stderr (journal for the unit). | basic |
-C, -cp, --copy (setup only) | — | Clone resources into ~/.config/context-forge/. | off |
Environment variables
The argument parser declares an environment fallback for some flags. They are useful in the generated unit (add Environment= lines) or in CI:
| Variable | Equivalent flag |
|---|---|
CONTEXT_FORGE_HOST_IPV4 | --ip |
CONTEXT_FORGE_HOST_PORT | --port |
CONTEXT_FORGE_MODEL | --model |
CONTEXT_FORGE_SYSTEM_PROMPT | --system-prompt |
VERBOSE | --verbose |
An explicit flag always wins over the variable.
What happens on exec
- The client asks systemd for the unit's main PID and opens the POSIX shared-memory segment named
context-forge:<pid>(created by the server: 10 channels of 4 KiB; larger payloads are chunked and reassembled). - The payload is
bin+ aDLEbyte (0x10) + the captured text.binis exactly theBINword you passed —ls, not/usr/bin/ls— and is what triggers compare against. - For each loaded rule file, in load order: if its triggers accept (bin, content), the file's block/pre-rules/rules are applied to the content, and the result feeds the next file.
- If the LLM pass is active, the text is sent to Ollama and replaced by the model's response.
- The client writes the answer to the captured descriptor (adding a final newline unless
-n) and exits with the child's status.
Recipes
Alias a command for good
alias ls='context-forge exec -c ls'
alias grep='context-forge exec -c grep'
Because of the fallback, the aliases keep working when the daemon is down. Note that with an alias, bin seen by triggers is still ls.
Reformat only stderr
context-forge exec -d 2 -c clang++ -Wall main.cpp
Wrap a build tool's summary
trigger = { bin = ["make", "ninja"]; };
ansi = false;
drop = ["^\\s*$", "^make\\[\\d+\\]: (Entering|Leaving) directory"];
ln = { tail = 40; };
Ask the model for a summary of the whole output only
Give the server a system prompt and no rules directory. Everything you wrap goes straight to the model.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
[FALLBACK] context-forge: server is unavailable… | The unit is not running. context-forge status, then journalctl --user -u context-forge -n 50. Common causes: the binary path baked into the unit no longer exists; a rule/plugin directory was deleted (the server logs it and continues without rules). |
Missing binary, can't find it using known path on setup | The executable is neither in /usr/local/bin nor /usr/bin. Install it or add a symlink. |
[WARNING] loadPlugin: no valid plugins where found | The plugins directory holds no *.so at the top level. Add --recursive for the plugins/{triggers,pre-rules,rules}/ layout, or check that the packages-only install is complemented by a source build. |
Unknow plugin used: foo | A rule file uses key foo but no loaded plugin reports name() == "foo". That rule file is skipped entirely; others still load. |
Parse error at file:line | libconfig syntax: every setting ends with ;, strings are double-quoted, arrays use [ ], groups use { }. See Rule files. |
[FAILED] loadLLM: the llm formating part will be ignored until a valid restart | No --system-prompt, the file is missing, Ollama is unreachable, or the model is not pulled (context-forge pull MODEL). Rules still work. |
Output suddenly contains <binary>…</binary> and <output> tags | The LLM pass was active but the request failed at run time (Ollama stopped, timeout > 120 s); the server returns the prompt it built. Restart Ollama, or restart the unit without a system prompt. |
| Rules edited but nothing changes | Rule files and plugins are read once at start-up: context-forge restart. With setup --copy, re-run setup so the copies are refreshed. |
| Tab completion does nothing | See Shell completion: the directory is probably not in $fpath, or compinit/bash-completion is not loaded. |
| Wrapped command hangs | It waits on stdin or never closes the captured descriptor (daemonising children keep the pipe open). Don't wrap interactive programs. |