From d738f339cb78d96010d57f8522c6dbde4fb630da Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Sat, 16 May 2026 21:14:43 +0000 Subject: [PATCH] repl: configurable prompt template via config.shell.prompt (closes #10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At-a-glance situational awareness: see the active model, context fill, mode flags, and cwd in the prompt itself — prevents "wait, am I still in plan mode?" surprises. Example config: shell = { prompt = "[{model} {ctx_used}/{ctx_max}t T{turn} {mode}] {cwd_short} > ", } Variables (substituted via {name}): {model} active preset name {ctx_used} char/4 token heuristic (Phase 0 §8; accurate is Q1) {ctx_max} config.context.token_budget {turn} #ctx.turns {cwd} libc.getcwd() (chdir-aware; PWD env may drift) {cwd_short} cwd with $HOME -> ~ {last_status} last exec exit code, "" if none yet {mode} "norris" | "plan" | "normal" Default behavior unchanged when shell.prompt is unset — keeps the "[aish:]>" form with norris ⚡ and plan markers. Side wiring: - ffi/libc.lua gains getcwd() (chdir() doesn't update PWD). - run_shell records exit code into last_exec_code for {last_status}. Co-Authored-By: Claude Opus 4.7 (1M context) --- config.lua | 8 ++++++++ ffi/libc.lua | 15 +++++++++++++++ repl.lua | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/config.lua b/config.lua index b46ff45..0246bf1 100644 --- a/config.lua +++ b/config.lua @@ -42,6 +42,14 @@ return { }, capture_output = true, -- inject exec output into context confirm_cmd = true, -- prompt before executing CMD: suggestions + + -- Issue #10: prompt template. When set, replaces the default + -- "[aish:]> " prompt. Variables (substituted via {name}): + -- {model} {ctx_used} {ctx_max} {turn} + -- {cwd} {cwd_short} (cwd with $HOME -> ~) + -- {last_status} (last exec exit code, empty if none yet) + -- {mode} (norris / plan / normal) + -- prompt = "[{model} {ctx_used}/{ctx_max}t T{turn} {mode}] {cwd_short} > ", }, context = { diff --git a/ffi/libc.lua b/ffi/libc.lua index da8196a..71635a8 100644 --- a/ffi/libc.lua +++ b/ffi/libc.lua @@ -42,6 +42,11 @@ int flock(int fd, int operation); /* TTY detection for non-interactive mode (`aish -p`). Returns 1 if the fd refers to a terminal, 0 otherwise (sets errno on error). */ int isatty(int fd); + +/* getcwd — chdir() doesn't update PWD env, so prompt {cwd} needs the + real cwd. NULL buffer + size 0 is the GNU extension that malloc()s + the buffer; we use a fixed-size stack buffer instead. */ +char *getcwd(char *buf, size_t size); ]] local C = ffi.C @@ -183,4 +188,14 @@ function M.isatty(fd) return C.isatty(fd) == 1 end +-- ---------------------------------------------------------------- getcwd +local CWD_BUF = ffi.new("char[?]", 4096) +function M.getcwd() + local p = C.getcwd(CWD_BUF, 4096) + if p == nil then + return nil, ffi.string(C.strerror(C.__errno_location()[0])) + end + return ffi.string(CWD_BUF) +end + return M diff --git a/repl.lua b/repl.lua index 2da2740..094c3af 100644 --- a/repl.lua +++ b/repl.lua @@ -449,7 +449,40 @@ function M.run(config) if session then session:append(turn) end end + -- Issue #10: configurable prompt template. When config.shell.prompt is + -- set, substitute {model}/{ctx_used}/{ctx_max}/{turn}/{cwd}/{cwd_short} + -- /{last_status}/{mode}. Otherwise fall back to the default with the + -- norris ⚡ + plan markers. + local libc = require("ffi.libc") + local last_exec_code = nil + local function _cwd_short() + local c = libc.getcwd() or os.getenv("PWD") or "?" + local home = os.getenv("HOME") + if home and c:sub(1, #home) == home then + c = "~" .. c:sub(#home + 1) + end + return c + end + local function _mode() + if ctx.norris_active then return "norris" end + if plan_mode then return "plan" end + return "normal" + end local function prompt() + local tmpl = config.shell and config.shell.prompt + if tmpl then + local vars = { + model = active_name, + ctx_used = tostring(ctx:estimate_tokens()), + ctx_max = tostring(ctx.token_budget), + turn = tostring(#ctx.turns), + cwd = libc.getcwd() or "?", + cwd_short = _cwd_short(), + last_status = last_exec_code and tostring(last_exec_code) or "", + mode = _mode(), + } + return (tmpl:gsub("{(%w+)}", function(k) return vars[k] or "" end)) + end if ctx.norris_active then return ("[aish:%s \xE2\x9A\xA1]> "):format(active_name) end @@ -544,6 +577,7 @@ function M.run(config) end renderer.exec_begin() local out, code = executor.exec(cmd) + last_exec_code = code renderer.exec_end(code) if config.shell and config.shell.capture_output then ctx:append_exec_output(out)