CodeBlock.__rich_console__
def __rich_console__(
console, options
):Jupyter does work with rich.live.Live, this fixes it.
The most cross platfrom way is to use tmux
The ssage_clear shell binding (see ssage_clear.sh in the repo root) gives ctrl-L “clear” semantics without destroying anything: it scrolls the visible screen into tmux history, then records the pane’s absolute history line in ~/.cache/shell_sage/mark-<pane>. Because it’s a readline binding, it is only active at the shell prompt — full-screen apps still receive a plain ctrl-L. get_pane treats any recorded mark as a context boundary: captures never reach past the most recent clear, while your own scrollback stays intact.
Absolute history line recorded by the ssage_clear shell binding, or None
Get output from a tmux pane, never reaching past an ssage_clear mark
We can also use the terminal app’s API to get the history. Here is how to do it for Apple Terminal and iTerm.app.
Due to macOS sandboxing, osascript can only script the terminal app it was executed from and cannot access other terminal apps. So, the easiest way to test this is to run these commands manually in the terminal:
Rich’s Live display and input() can’t coexist — Live manages the terminal cursor, and input() needs it too. When Live.stop() is called, it prints its current renderable as static text, which causes duplication since get_res accumulates the full response. The fix: clear Live’s renderable before stopping, flush the accumulated response as static markdown, and reset the buffer. This way streaming resumes fresh after the tool interaction with no overlap.
Find files recursively by name (gitignore respected)
List a directory, one level, like ls -l
Search file contents recursively (ripgrep semantics, gitignore respected)
About to View file/directory with the following arguments: {'args': ['.'], 'kwargs': {}}
Directory contents of /Users/rensdimmendaal/aai/ws/shell_sage/nbs: /Users/rensdimmendaal/aai/ws/shell_sage/nbs/00_core.ipynb (28.9k) /Users/rensdimmendaal/aai/ws/shell_sage/nbs/_quarto.yml (0.3k) /Users/rensdimmendaal/aai/ws/shell_sage/nbs/sidebar.yml (0.1k) /Users/rensdimmendaal/aai/ws/shell_sage/nbs/styles.css (0.6k) /Users/rensdimmendaal/aai/ws/shell_sage/nbs/CNAME (0.0k) /Users/rensdimmendaal/aai/ws/shell_sage/nbs/01_config.ipynb (4.1k) /Users/rensdimmendaal/aai/ws/shell_sage/nbs/tmux.conf (1.4k) /Users/rensdimmendaal/aai/ws/shell_sage/nbs/nbdev.yml (0.2k) /Users/rensdimmendaal/aai/ws/shell_sage/nbs/index.ipynb (65.2k)
def get_sage(
model, # LiteLLM compatible model name
mode:str='default', search:bool=False, # Search (l,m,h), if model supports it
use_safecmd:bool=False, custom_instructions:NoneType=None, *, sp:str='', # System prompt
temp:NoneType=None, # Temperature
tools:list=None, # Add tools
hist:list=None, # Chat history
ns:Optional[dict]=None, # Custom namespace for tool calling
cache:bool=False, # Anthropic prompt caching
cache_idxs:list=[-1], # Anthropic cache breakpoint idxs, use `0` for sys prompt if provided
ttl:NoneType=None, # Anthropic prompt caching ttl
api_name:NoneType=None, # API to use, one of ApiName: openai (responses), openai_chat, anthropic, gemini
vendor_name:NoneType=None, # Vendor name, one of vendor_mapping which resolves api_base/api_key automatically
api_key:NoneType=None, # API key when model can't be resolved or vendor_name is not known or codex
oauth_token:NoneType=None, # A subscription's OAuth token (Codex, Claude Code) in place of an API key
base_url:NoneType=None, # API base url when model can't be resolved or vendor_name is not known
endpoint:NoneType=None, # Override the transport's request path, for a server mounting Responses at a custom location
extra_headers:NoneType=None, # Extra HTTP headers for custom providers
use_previous_response_id:bool=False, # Continue tool rounds with Responses IDs instead of replaying history
markup:int=0, # Cost markup multiplier (e.g. 0.5 for 50%)
showthink:bool=False, # Stamp streamed thinking parts to display their text rather than 🧠 glyphs
cbs:list=None, # Chat callbacks
default_cbs:bool=True, # Whether to include default callbacks
):LiteLLM chat client.
I’m doing great, thanks for asking! Ready to help you navigate the command line. 🖥️
What shell commands or sysadmin topics can I help you with today?
claude-sonnet-4-6stopUsage(prompt_tokens=3811, completion_tokens=43, total_tokens=3854, cached_tokens=0, cache_creation_tokens=0, reasoning_tokens=0, raw={'input_tokens': 3811, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'cache_creation': {'ephemeral_5m_input_tokens': 0, 'ephemeral_1h_input_tokens': 0}, 'output_tokens': 43, 'output_tokens_details': {'thinking_tokens': 0}, 'service_tier': 'standard', 'inference_geo': 'global'})def get_res(
sage, q, opts, *, msg:NoneType=None, step:int=1, search:NoneType=None, tool_choice:NoneType=None,
initial_body:NoneType=None, api_name:NoneType=None, vendor_name:NoneType=None, api_key:NoneType=None,
oauth_token:NoneType=None, base_url:NoneType=None, endpoint:NoneType=None, xtra_body:NoneType=None,
xtra_hdrs:NoneType=None, stream:bool=False, previous_response_id:NoneType=None, stop_callables:NoneType=None,
retries:int=2, retry_delay:float=0.5, system:NoneType=None, max_tokens:NoneType=None, temperature:NoneType=None,
tools:NoneType=None, parallel_tool_calls:NoneType=None, reasoning_effort:NoneType=None,
web_search_options:NoneType=None, cache_idxs:NoneType=None, ttl:NoneType=None
):One model turn plus its tool round, recursing for the next; turn-constant options come from turn_opts
Queries and their context are literal user text, even when a diff or terminal history contains fastllm tool or usage markers. They must not be parsed as assistant history.
['No.']
Hi! What shell or sysadmin topic can I help you with today? 😊
Here we use bash tool instead of view as it includes file sizes too which are changing:
The current directory contains the following files: _quarto.yml, 00_core.ipynb, 01_config.ipynb, CNAME, index.ipynb, nbdev.yml, sidebar.yml, styles.css, and tmux.conf.
Here are some fascinating facts about Linux: *Linus Torvalds, a Finnish computer science student, created the first version of Linux in 1991 as a hobby project while studying at the University of Helsinki. *He originally wanted to call it "FreaX" (a combination of "free", "freak", and "Unix"), but was persuaded otherwise by the owner of the server hosting his early code, who preferred the name "Linux." *The first Linux release weighed in at less than 1MB uncompressed — quite a contrast to today, and *all 500 of the fastest supercomputers in the world now run Linux. *The International Space Station has been running Linux since 2013. *Linux is written over 95% in C language, and *today over 80% of Linux contributions come from developers paid by big enterprises, with Intel topping the list of contributors for most kernel releases. On a fun note, *Linux is also a genuine washing powder brand in Switzerland, and *one of the unexpected workloads for Linux has been milking cows — the DeLaval "Voluntary Milking System" lets cows decide when they'll be milked, all managed by a single-board computer running Linux!
async def main(
query:str, # The query to send to the LLM
v:str='%(prog)s 1.1.3', # Print version
pid:str='current', # `current`, `all` or tmux pane_id (e.g. %0) for context
skip_system:bool=False, # Whether to skip system information in the AI's context
history_lines:int=None, # Number of history lines. Defaults to tmux scrollback history length
mode:str='default', # Available ShellSage modes: ['default', 'sassy']
model:str=None, # The LLM model, optionally vendor-prefixed (e.g. 'codex/gpt-5.5', 'claude_code/claude-sonnet-4-6')
search:str=None, # Wheather to allow the LLM to search the internet
base_url:str=None, # If using a custom LLM base url
api_key:str=None, # If don't have the default environment variables set
think:str=None, # Reasoning effort level: 'l', 'm', 'h' (for supported models)
trust:str=None, # Comma-delimited list of tools to always allow (e.g. "view_file,rg")
code_theme:str=None, # The code theme to use when rendering ShellSage's responses
code_lexer:str=None, # The lexer to use for inline code markdown blocks
raw:bool=False, # Skip markdown rendering and print plain text
custom_instructions:str=None, # Extra instructions appended to the system prompt
):No, I don't have a bash tool. My available tools are: • rg — run ripgrep searches • view — view files/directories • create / str_replace / insert — edit files • web_search — search the web So I can read and edit files, and search through code, but I can't execute arbitrary shell commands. That's by design — the idea is for you to run the commands in your terminal, which keeps you in the driver's seat for learning. If you need me to look something up in your project files, I can do that. But for running commands, I'll give you the snippet to paste and run yourself.
Arrr, ahoy there, matey! 🏴☠️ Welcome aboard! Looks like ye've just been editin' yer Shell Sage config file. What can this ol' sea dog help ye with today? Whether it be shell commands, system administration, or navigatin' the treacherous waters of the terminal — I be at yer service!
Log(id=799, timestamp='2026-05-28T15:23:20.783718', query='', response="\nHello, user! Here are some code blocks:\n\n```python\nfor i in range(10): print(i)\n```\n\n```\nThis doesn't even have a language definition!\n```\n\n```bash\nls **/*\n```\n", model='', mode='')