context-forgev1.0.0
User guide

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.

FlagValueMeaning
-c, -cmd, --commandBIN [ARG...]Required. The program to run and its arguments. BIN is resolved on PATH like a shell would.
-d, -fd, --redirect0 | 1 | 2 | nWhich 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, --verbosenone | basic | advanced | debugClient-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

CommandWhat 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 / restartThin 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).
removeStops and disables the unit, deletes the unit file, reloads systemd, and deletes ~/.config/context-forge/ entirely. Does not uninstall the binary.

install-ollama · pull

CommandWhat it does
sudo context-forge install-ollamaRuns curl -fsSL https://ollama.com/install.sh | sh. Refuses to run unless effective UID is 0.
context-forge pull MODELReplaces itself with ollama pull MODEL. Requires Ollama to be installed and (for pulling) its server reachable.

Flags shared by server, check and setup

FlagValueMeaningDefault
-P, -so, --pluginsdirectoryWhere to look for *.so plugins. Must exist.none → rules are ignored
-r, -rules, --rulesdirectoryWhere 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-promptfileText file sent as the system field to Ollama. Enables the LLM pass.none → LLM pass off
-a, -ip, --iphost/IPOllama host (resolved at start-up).localhost
-p, -port, --port1–65535Ollama port.11434
-m, -model, --modelname[:tag]Model name; verified with /api/show at start-up.qwen2.5-coder:1.5b
-v, --verbosenone|basic|advanced|debugLog 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:

VariableEquivalent 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

exec -c ls -la→ spawn child, capture fd→ child exits→ shm: "ls" + output→ server: rule files→ server: LLM→ shm: result→ write to fd, exit like child
  1. 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).
  2. The payload is bin + a DLE byte (0x10) + the captured text. bin is exactly the BIN word you passed — ls, not /usr/bin/ls — and is what triggers compare against.
  3. 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.
  4. If the LLM pass is active, the text is sent to Ollama and replaced by the model's response.
  5. 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

SymptomCause / 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 setupThe executable is neither in /usr/local/bin nor /usr/bin. Install it or add a symlink.
[WARNING] loadPlugin: no valid plugins where foundThe 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: fooA 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:linelibconfig 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 restartNo --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> tagsThe 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 changesRule 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 nothingSee Shell completion: the directory is probably not in $fpath, or compinit/bash-completion is not loaded.
Wrapped command hangsIt waits on stdin or never closes the captured descriptor (daemonising children keep the pipe open). Don't wrap interactive programs.