shellgpt¶
ShellGPT (sgpt, catalog name shellgpt) is an open-source Python command-line client for
OpenAI-compatible chat APIs. It answers one-shot prompts, generates shell
commands that the user can execute from a prompt, keeps named chat and REPL
sessions, and can let the model run shell commands through a "function"
plug-in. Its configuration lives under the home, but its chat history lives in
the system temporary directory. It stores a named chat or REPL session as the raw OpenAI messages
array it sends to the API, one JSON file per session, with no timestamps, model or working
directory. Paths cited are relative to the clone. The agent is parsed: the shellgpt parser
reads the chat files, four keys of the settings file, the function store and the response cache
(Parser plan).
Source and evidence¶
TheR1D/shell_gpt, MIT, open source, commit
a082bd5327ce0c4ef5a0284d9060e833be9444a6
(2026-07-02), version 1.5.1
(sgpt/__version__.py:1).
All claims from source. The older temp-directory layout is cited from the history commits
1465c4a
(first in tag 0.7.3) and
a054a90
(first in tag 0.9.0).
Observed¶
- No real install.
Source reviews¶
- 2026-10-21: TheR1D/shell_gpt at
a082bd5
(1.5.1): the parser now reads the
.sgptrcpermission and model keys, the function store and the response cache, so the Parser plan states those rows instead of proposing them; it added that the answer after a function call is cached on its own (Response cache) and the source-host statement (Source host rows). - 2026-10-19: the head of the default branch is still
a082bd5, the commit above, so every path here stands. It added the finding that the response cache also holds chat answers (Response cache) and the permission and model settings of Settings file and function store.
Collection¶
Per-user storage¶
Scratch directories¶
None. ShellGPT runs commands in the caller's
working directory and creates no directory of its own to work in
(--install-functions writes only into functions/).
Configuration folder¶
The config folder is os.path.expanduser("~/.config")/shell_gpt on every OS, Windows included:
there is no platform branch and no XDG_CONFIG_HOME support
(sgpt/config.py:9-13).
So Windows hosts have C:\Users\<u>\.config\shell_gpt, and macOS uses ~/.config, not Library.
~/.config/shell_gpt/.sgptrc:KEY=valuelines, one per setting (config.py:75-87). On first run every default inDEFAULT_CONFIGis written out, and keys added in later versions are appended to an existing file (config.py:18-46,53-69). An environment variable of the same name overrides the file (config.py:89-94).~/.config/shell_gpt/roles/<name>.json: system roles, JSON withnameandrole(role.py:142-153). Four defaults are created (ShellGPT,Shell Command Generator,Shell Command Descriptor,Code Generator;role.py:63-73); others come from--create-role. Overridable withROLE_STORAGE_PATH(config.py:28).~/.config/shell_gpt/functions/*.py: the function store, overridable withOPENAI_FUNCTIONS_PATH(config.py:32). Every.pyfile here is imported and executed on everysgptstart (function.py:31-36,54-56).--install-functionscopies inexecute_shell.py, which runs any command the model asks for withshell=True(llm_functions/init_functions.py:13-35;common/execute_shell.py:18-25). A file planted here is a code-execution and persistence point.
Chat history and response cache¶
Chat history is not under the home. CHAT_CACHE_PATH defaults to
tempfile.gettempdir()/chat_cache and the response cache CACHE_PATH to gettempdir()/cache
(config.py:14-15,
20-21).
That resolves to /tmp/chat_cache on Linux, $TMPDIR/chat_cache (/var/folders/.../T/) on macOS
and %TEMP%\chat_cache (AppData\Local\Temp\chat_cache) on Windows. Before 0.9.0 the defaults were
gettempdir()/shell_gpt/chat_cache and gettempdir()/shell_gpt/cache
(1465c4a config.py:10-11);
0.9.0 dropped the shell_gpt level
(a054a90 config.py:15-16).
Because .sgptrc freezes the value written at first run and only appends missing keys, a user who
installed before 0.9.0 still writes to /tmp/shell_gpt/chat_cache; the current README also shows
that form
(README.md:368-373).
The official Docker image mounts a volume at /tmp/shell_gpt
(Dockerfile:12-14).
Read CHAT_CACHE_PATH and CACHE_PATH from .sgptrc to find the real location.
<chat cache>/<chat id>: one JSON file per--chator--replsession, named exactly as the user typed the id, no extension (chat_handler.py:65-78). The idtempis deleted at the start of each use (chat_handler.py:106-108).<cache>/<md5>: one file per cached completion, one-shot prompts and chat answers alike, holding only the response text (Response cache); the name is an MD5 of the request arguments, so the prompt is not recoverable from it. The oldest files beyondCACHE_LENGTH(100) are deleted (cache.py:30-61). Caching is on by default (app.py:88-91). The catalog collects the pre-0.9.0 Windows form underAppData/Local/Temp/shell_gpt; the current form,AppData/Local/Temp/cache, is not a catalog line, andcacheis too common a directory name in the temp directory to add as one without collecting other programs' caches. ReadingCACHE_PATHfrom.sgptrcis the manual step.
One-shot prompts and the shell integration¶
One-shot prompts without --chat are not stored anywhere except as that response cache
(app.py:222-239;
default_handler.py:17-22).
The interactive [E]xecute prompt uses an in-memory PromptSession with no history file
(app.py:241).
The command line itself lands in the shell history.
--install-integration appends a Shell-GPT integration block binding Ctrl+L to sgpt --shell to
~/.zshrc or ~/.bashrc
(utils.py:66-87;
integration.py:1-27).
Environment variables¶
OPENAI_API_KEY, API_BASE_URL, CHAT_CACHE_PATH,
CACHE_PATH, ROLE_STORAGE_PATH, OPENAI_FUNCTIONS_PATH, USE_LITELLM,
DEFAULT_MODEL and the rest of DEFAULT_CONFIG.
Credentials¶
~/.config/shell_gpt/.sgptrc:OPENAI_API_KEY=<key>in plain text. On first run sgpt prompts for the key and writes it, unlessOPENAI_API_KEYis already in the environment (config.py:63-69;README.md:10). The key is passed to whichever backendAPI_BASE_URLnames (handler.py:15-21), soAPI_BASE_URLidentifies the provider or proxy. No keychain use.- With
USE_LITELLM=trueother providers' keys come from their own environment variables; nothing extra is written by sgpt.
Exclusions¶
Nothing large. The function store and roles are a few KB. The response cache is capped at 100 files by default.
Project-local files¶
None. ShellGPT reads nothing from the working directory and records no project.
Where the project path is recorded¶
Nowhere: chat files carry no working directory. For discovery of the
history location rather than projects, .sgptrc keys CHAT_CACHE_PATH,
CACHE_PATH, ROLE_STORAGE_PATH and OPENAI_FUNCTIONS_PATH hold absolute
paths that may point outside the defaults.
Parsing¶
Transcript stores¶
<CHAT_CACHE_PATH>/<chat id>: one file per session started with--chat <id>or--repl <id>, named exactly as typed, no extension, in the chat cache directory of Chat history and response cache (by default under the temp directory,.sgptrckeeping whichever value was written at first run). Home-relative globs the catalog can reach:AppData/Local/Temp/chat_cache/*andAppData/Local/Temp/shell_gpt/chat_cache/*(Windows). On Linux and macOS the files are under/tmpor/var/folders/*/*/Tand arrive only as loose input.- The id
tempis invalidated at each start, so it holds only the most recent throwaway session.
What survives a delete¶
ShellGPT has no delete command. What removes chat history is the temp chat id, invalidated at each
start; the cut on every write that keeps the system message and the last 100 other messages
(Record schema); and, the main loss, the operating system's cleanup of its
temporary directory, where the chat files live by default. A per-prompt history file is not
stated. The response cache (Response cache) keeps the answers to chat turns
that the cut dropped, and to one-shot prompts that never had a chat file, as text with no prompt,
time or session, the newest 100 files kept; its current Windows path is named by no catalog line
(Chat history and response cache). Deletion markers are not
stated. The analyzer reads the chat files and, where it was collected, the response cache, a
secondary source joined on text only (see
Secondary sources).
Record schema¶
The file is a single JSON array written by json.dump with default settings (one line,
ASCII-escaped)
(chat_handler.py:72-78).
Each element is an OpenAI chat message:
{"role":"system","content":str}: always element 0, written only when the session is new (chat_handler.py:165-170). The content is"You are <role name>\n<role text>"(sgpt/role.py:44,role.py:150-153); sgpt itself parses the role name back from the first line (role.py:106-112).{"role":"user","content":str}: the prompt. In REPL mode the first prompt is prefixed with any stdin or command-line prompt separated by three newlines (repl_handler.py:53-55).{"role":"assistant","content":null,"tool_calls":[{"id","type":"function","function":{"name","arguments"}}]}: a function call;argumentsis a JSON string (sgpt/handlers/handler.py:72-84). Only one call per turn (parallel_tool_callsis false;handler.py:118-121).{"role":"tool","content":str,"tool_call_id":str}: the function's return value (handler.py:98-100). For the bundledexecute_shell_commandit is"Exit code: <n>, Output:\n<stdout+stderr>"(llm_functions/common/execute_shell.py:18-25).{"role":"assistant","content":str}: the full streamed response, appended after the call returns (chat_handler.py:56-61). When a function was called it also contains the marker line> @FunctionCall `name(k="v")`and, withSHOW_FUNCTIONS_OUTPUT=true, the output in a fenced block (handler.py:89-95). The marker is preceded by a"\n", which is always emitted because the check runs right after the assistant message is appended (handler.py:86-87).
So a turn with a function call is stored as user, assistant(tool_calls), tool, assistant(content), in that order, because the handler mutates the same list the session later writes.
Truncation: on every write the system message is kept and only the last CHAT_CACHE_LENGTH
(default 100) other messages survive
(chat_handler.py:75-77;
config.py:22).
Older turns of a long session are gone, and a cut can separate a tool message from its call.
Joins and timestamps¶
- Session to project: none recorded.
- Tool call to result:
tool_calls[0].idequals the nexttoolmessage'stool_call_id. - No subagents. Session id is the file name.
No timestamp, model, temperature or cwd is stored. The only time is the file's mtime, which is the last write. The file is rewritten in full each turn.
Other stores¶
No SQLite or binary store.
Response cache¶
<CACHE_PATH>/<md5> files hold only a response string with no prompt
(sgpt/cache.py:30-41).
Not a transcript; because it also holds chat answers it is a secondary source
(Past-use rows and secondary sources). get_completion of
every handler, the chat handler included, is wrapped by the cache decorator, which writes the full
response text to <CACHE_PATH>/<md5 of the request> unless it contains a function call
(handler.py:102-110;
sgpt/cache.py:30-44).
So the answers to chat turns that the 100-message cut dropped, and to one-shot prompts that have no
chat file, survive there as text with no prompt, time or session; they join on text only. The newest
100 files are kept
(cache.py:46-61).
When the model calls a function, the handler runs it and calls the cached get_completion again
with caching=False, which skips the cache read but not the write
(handler.py:155-166;
cache.py:33-41). So the answer after a function call is
cached on its own, while the chat file stores it behind the > @FunctionCall marker
(Record schema); the outer call's text holds the marker and is not cached. An
empty response is written as an empty file.
Settings file and function store¶
.sgptrc keys OPENAI_USE_FUNCTIONS (default true: function calls allowed),
DEFAULT_EXECUTE_SHELL_CMD (default false; true makes Execute the default answer) and
SHELL_INTERACTION (default true; false skips the prompt)
(sgpt/config.py:29,
33,
38;
read at
app.py:60-61,
79-80,
247).
The environment override of SHELL_INTERACTION reads the variable "SHELL_INTERACTION ", with a
trailing space, so only the file value takes effect in practice. The functions the model may call
are the *.py files in ~/.config/shell_gpt/functions/, which --install-functions fills with
execute_shell.py
(sgpt/llm_functions/init_functions.py:13-22),
and on macOS also apple_script.py
(init_functions.py:29-35).
Every *.py file in that directory is loaded at start and offered to the model
(sgpt/function.py:54-56).
ShellGPT reads the file line by line, skipping blank lines and lines that start with #, and
splits each other line on its first =; a line with no = stops it with an error
(config.py:82-87).
.sgptrc DEFAULT_MODEL (default gpt-5.4-mini,
config.py:25)
is the model used when no --model was given; nothing records a per-call choice.
Format versions¶
No version field. Function calls arrived in 1.1.0
(20ff0f2)
using the legacy functions API: the call was stored as
{"role":"assistant","content":"","function_call":{"name","arguments"}} and the result as
{"role":"function","content":str,"name":str}, with no call id
(handler.py:65-71,
83
at 6bd0bde,
the parent of the change below). 1.5.0 switched to tool_calls and tool messages
(4ea2f83,
"Migrate to OpenAI v2"). A parser must accept both: pair a function message with the immediately
preceding function_call by position. Before 1.1.0 files hold only system, user and assistant
messages.
Secrets¶
The API key is never written into chat files; it is in .sgptrc
(config.py:63-69), beside
the settings the parser reads (Permission rows), so the parser reads only
those named keys and never puts another key, value or line of that file in a row.
Tool messages contain raw shell output and can contain anything the commands printed.
Parser plan¶
Detection: any regular file directly under a chat_cache directory whose
content parses as a JSON array of objects with role.
| Column | Source |
|---|---|
| timestamp_utc | empty; the manifest mtime may be given to the last row only |
| session_id | file name |
| project_path | empty |
| git_branch | empty |
| turn_type | user → user; assistant with content → assistant; each tool_calls[] → tool_use; tool → tool_result; system → system |
| model | empty |
| tool_name | tool_calls[].function.name |
| tool_use_id | tool_calls[].id / tool_call_id |
| text | content; for tool_use, shell_command from the parsed arguments when present, else arguments |
| source_line | 1 for every row (single-line file); the array index can go in the text or a later column |
Permission rows¶
Per call: none recorded, and none asked for function calls. handle_function_call appends the call,
runs get_function(name)(**dict_args) and appends the result with no confirmation
(sgpt/handlers/handler.py:64-100),
so every tool_use row in a chat file is a command that ran. The --shell prompt
[E]xecute, [M]odify, [D]escribe, [A]bort runs the generated command with run_command after the
answer is written and records the choice nowhere
(sgpt/app.py:243-260),
nor does the REPL's e in the shell role
(sgpt/handlers/repl_handler.py:56-59).
Standing: the .sgptrc switches and the function store described in
Settings file and function store. The parser yields one
permission setting: <key>=<value> (from .config/shell_gpt/.sgptrc, file mtime) row for each of
OPENAI_USE_FUNCTIONS, DEFAULT_EXECUTE_SHELL_CMD and SHELL_INTERACTION present in the file, in
file order, and one permission setting: function <file name> (from .config/shell_gpt/functions,
file mtime) row per file in the function store, without reading the code; each at its file's
modification time with an empty session_id. Source-derived; not observed on any real install.
.sgptrc is a secret glob, so a --no-secrets collection lacks it, and an environment variable
that overrode a key at run time leaves no trace in the file.
Model rows¶
ShellGPT records no model in the chat cache, so no chat row names one. The configured default is
DEFAULT_MODEL in .sgptrc
(Settings file and function store). The parser yields one
model: <DEFAULT_MODEL> (configured default, from .config/shell_gpt/.sgptrc, file mtime) row at
the file's modification time, with the value in the model column; it is the default, not a
per-session model, since --model is recorded nowhere. Source-derived; not observed on any real
install.
Past-use rows and secondary sources¶
The chat files are the primary store. The response cache (Response cache) is
secondary: the parser reads every shell_gpt/cache/<md5> file the catalog collects (the pre-0.9.0
Windows AppData/Local/Temp/shell_gpt/cache/; the current AppData/Local/Temp/cache is no
catalog line) and yields each non-empty one as an assistant row with no timestamp, session or
project, so it matches a chat row on text alone and is kept as recovered when no chat row holds
it. The answer after a function call never matches, because the chat file stores it behind the
marker, and is shown twice. Source-derived; not observed on any real install.
Sync, relay and origin rows¶
The agent records none. ShellGPT records no sync state, because it has no feature that uploads a
session. Its only network client is the chat completion call: the OpenAI client pointed at
API_BASE_URL, or litellm.completion when USE_LITELLM=true (LiteLLM is a third-party library
that routes one call format to many model providers)
(handler.py:15-33),
which sends the session's messages with each prompt and nothing else
(handler.py:123-130).
The runtime dependencies are openai, typer, click, rich, distro, pyreadline3 and
prompt_toolkit, with litellm optional
(pyproject.toml:26-39);
no HTTP library, telemetry or crash reporter.
The command-line options for sessions are --chat, --repl, --show-chat and --list-chats, all
local
(app.py:98-119),
and the session store reads, writes, lists and deletes files in CHAT_CACHE_PATH only
(chat_handler.py:65-95).
No setting in DEFAULT_CONFIG names a sync, share or account
(config.py:18-46).
The terms sync, share, upload, cloud, remote, publish, telemetry, analytics,
export, backup, team, account, login, requests, httpx, urllib, socket and sentry
match nothing in sgpt/, and the only URLs in sgpt/ and pyproject.toml are the project's own
GitHub links.
Two paths are outside this source: what LiteLLM itself sends when it is enabled (ShellGPT sets only
suppress_debug_info on it) and what a user-installed function under
~/.config/shell_gpt/functions/ does, since each is arbitrary Python imported at start
(function.py:31-36).
Scratch directory rows¶
None (Scratch directories).
Deletion rows¶
No delete command; see Transcript stores and
What survives a delete for temp, and Record schema
for the truncation. The parser yields no deletion rows.
Bad-line rows¶
A truncated array yields the messages before the cut and one system row. A line of .sgptrc
with no = yields parser: unparseable line <n> (no "=" separator; not shown, the file holds the
API key), the line itself left out because the file holds the key; the other lines are still
read. The response cache is one text per file with nothing to cut.
Fixtures¶
Fixture (chat_cache/deploy-check, shown wrapped; the real file is one line):
[{"role": "system", "content": "You are ShellGPT\nYou are programming and system administration assistant.\nYou are managing Linux/Ubuntu 24.04 LTS operating system with bash shell.\nAPPLY MARKDOWN formatting when possible."},
{"role": "user", "content": "what is listening on port 8080"},
{"role": "assistant", "content": null, "tool_calls": [{"id": "call_Q1w2e3r4", "type": "function", "function": {"name": "execute_shell_command", "arguments": "{\"shell_command\": \"ss -ltnp | grep 8080\"}"}}]},
{"role": "tool", "content": "Exit code: 0, Output:\nLISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:((\"python3\",pid=4242,fd=3))\n", "tool_call_id": "call_Q1w2e3r4"},
{"role": "assistant", "content": "\n> @FunctionCall `execute_shell_command(shell_command=\"ss -ltnp | grep 8080\")` \n\nA python3 process (PID 4242) is listening on 8080."}]
Fixture (.config/shell_gpt/.sgptrc, a shortened file in the order of DEFAULT_CONFIG, with a
placeholder key):
CHAT_CACHE_PATH=/tmp/chat_cache
CACHE_PATH=/tmp/cache
DEFAULT_MODEL=gpt-5.4-mini
DEFAULT_EXECUTE_SHELL_CMD=true
OPENAI_USE_FUNCTIONS=true
API_BASE_URL=default
SHELL_INTERACTION=false
OPENAI_API_KEY=sk-not-real
Fixture (shell_gpt/cache/<md5>, the whole file): 30G is free on /.
Source host rows¶
The agent records no source-host integration. github, gitlab, bitbucket, oauth,
installation, repositor, pull request and clone match nothing in sgpt/ at
a082bd5,
and the only matches in pyproject.toml are the project's own homepage and repository links
(pyproject.toml:58-60).
Confidence¶
- High (source and history): the config folder, the
.sgptrcformat and key, roles, the function store, the chat and cache layout, the 0.9.0 change of temp path, the chat file location, array shape, message keys, ordering and truncation. - Medium: that
/tmpis the effective Linux location;gettempdirhonoursTMPDIR,TEMPandTMP, so a user with any of those set writes elsewhere. - Medium: the pre-1.5.0 shape, read from the parent of
4ea2f83in history rather than a released package. - Not determined: how long the chat files survive.
/tmpis a tmpfs cleared at boot on many distributions and macOS purges old/var/folderscontent, but this depends on the host and was not checked.