Version: 0.1.0 (dist-info) — offlinai-shell PyPI name
Type: Pure Python, single file
SPM target: Bundled in the Python framework (host-app glue, not a public PyPI dep)
File size: 342 KB (~8,330 lines)
Total @builtin decorations: 116 (covers ~108 unique command names — some are aliased via stacked decorators)
A zsh-shaped REPL that runs inside the CodeBench Python interpreter. iOS bans
fork(), exec(), and subprocess.Popen, so every shell builtin is a pure
Python function. Anything the user types that isn't a registered builtin runs
as Python code in the same interpreter, with multi-line input buffered until
code.compile_command() says the statement is complete.
Called from Swift as offlinai_shell.run_line(cmd) per user input, or
offlinai_shell.repl() to take over stdin and loop forever.
| File | What it does |
|---|---|
offlinai_shell.py |
Single module — Shell class, 116 @builtin decorations, run_line(), repl(), BUILTINS dispatch dict |
That's it — one pure-Python file, no submodules, no C extensions.
| Symbol | Purpose |
|---|---|
offlinai_shell.run_line(cmd: str) |
Run one user input line and stream output to stdout/stderr |
offlinai_shell.repl() |
Block forever as an interactive REPL — Swift's PTYBridge calls this |
offlinai_shell.Shell |
The shell state class — prompt(), ps2(), run_line() |
offlinai_shell.BUILTINS |
Dict mapping builtin name → handler function. Append-only at import time |
offlinai_shell.builtin(name) |
Decorator — register a new builtin. Usable from user code |
Found via grep -n "@builtin(" /Volumes/D/OfflinAi/app_packages/site-packages/offlinai_shell.py
(116 decorations total — count includes stacked aliases like
@builtin("python") @builtin("python3")).
| Command | Line | Notes |
|---|---|---|
pwd |
1180 | Print working directory |
cd |
1186 | Change directory; tracks history |
ls |
1310 | ANSI-coloured listing; -l, -a, -h |
cat |
1358 | Concatenate files |
head |
1383 | First N lines |
tail |
1410 | Last N lines |
mkdir |
1436 | Create directories; -p for parents |
rm |
1498 | Delete files; -r recursive, -f force. Tombstones direct children of ~/Documents/Workspace/ in .codebench_deleted so the starter-script seeder doesn't re-create them on next launch |
rmdir |
1531 | Remove empty directories |
touch |
1544 | Create empty files / update mtime |
cp |
1555 | Copy files |
mv |
1580 | Move / rename |
find |
1695 | Pure-Python find clone — -name, -type, -iname |
tree |
1722 | Indented tree view |
du |
3585 | Disk usage per file |
df |
3632 | Disk free (statvfs-based) |
ncdu |
3884 | Interactive disk-usage explorer |
stat |
4068 | File metadata |
| Command | Line | Notes |
|---|---|---|
echo |
1596 | Print arguments |
grep |
1666 | Regex search; -i, -n, -r, -v |
wc |
1748 | Word / line / byte count |
sort |
7284 | Stable sort with -r, -n, -u |
uniq |
7324 | Adjacent-duplicate removal |
tr |
7355 | Character translation |
cut |
7737 | Column extraction; -f, -d, -c |
nl |
7705 | Number lines |
tac |
7719 | Reverse line order |
rev |
7728 | Reverse characters in each line |
tee |
7797 | Split stdout to file + screen |
diff |
7817 | difflib unified diff |
xxd / hexdump |
7845–46 | Hex dump |
less / more |
7995–96 | Pager (one screen at a time) |
base64 |
7170 | Encode / decode |
| Command | Line | Algo |
|---|---|---|
sha256sum |
7227 | SHA-256 |
sha1sum |
7232 | SHA-1 |
md5sum |
7237 | MD5 |
| Command | Line | Notes |
|---|---|---|
uname |
7243 | Kernel info |
whoami |
7262 | Current user |
hostname |
7274 | Device hostname (iOS device name or platform fallback) |
env |
1602 | List env vars |
export |
1609 | Set env vars |
date |
1640 | Current time, formatted |
uptime |
1646 | Shell uptime (since the Shell was constructed — iOS has no per-process accounting) |
top / htop |
5104–05 | Live process list via psutil |
history |
1764 | Command history |
sleep |
7444 | Delay in seconds |
time |
7458 | Time a command |
nproc |
7584 | CPU core count |
id |
7596 | User / group IDs |
bc |
7867 | Calculator (Python expression evaluator) |
cal |
7887 | Calendar (calendar module) |
ps |
7907 | Process list (psutil) |
kill |
7938 | Send signal — limited on iOS (no fork() means no child processes to kill, but you can kill threads) |
watch |
7965 | Repeat a command every N seconds |
| Command | Line | Notes |
|---|---|---|
ping |
6664 | TCP-connect probe (iOS forbids raw ICMP sockets without entitlements) |
wget |
6837 | requests-based download |
curl |
6868 | requests-based, flag-compatible |
| Command | Line | Notes |
|---|---|---|
zip |
6906 | Create .zip |
unzip |
6949 | Extract .zip |
tar |
6993 | Create / extract tarballs |
gzip |
7071 | Compress |
gunzip |
7091 | Decompress |
| Command | Line | Notes |
|---|---|---|
python / python3 |
1972–73 | Run a .py file, -c snippet, -m module, version flags. Installs the interrupt watchdog (see below) for the duration of the script |
js / node |
2359–60 | QuickJS-backed JS interpreter (see js-engine.md) |
cc / gcc / clang |
4188–90 | C compiler (TCC-based — see c-interpreter.md) |
c++ / g++ / clang++ |
4200–02 | C++ compiler (see cpp-interpreter.md) |
gfortran / f77 / f90 / f95 |
4212–15 | Fortran interpreter (see fortran-interpreter.md) |
swift |
4225 | Swift interpreter wrapper |
| Command | Line | Notes |
|---|---|---|
pdflatex |
4424 | Route through offlinai_latex.compile_tex |
latex |
4468 | Same |
tex / pdftex |
4478–79 | Same |
xelatex |
4488 | XeLaTeX with xeCJK (CJK support) |
latex-diagnose |
4562 | Print pdftex / BusyTeX / kpathsea framework status |
manim |
7111 | Wrap manim's CLI; injects render subcommand if missing; uses click's standalone_mode=False so manim's sys.exit() doesn't tear down the REPL |
| Command | Line | Notes |
|---|---|---|
ai |
4506 | Drop into offlinai_ai REPL — local llama.cpp via Swift's LlamaRunner. Slash commands /help /file /show /mode /model /pull /load /ls /usage /reset /clear /quit |
| Command | Line | Notes |
|---|---|---|
pip / pip3 |
3107–08 | pip._internal.cli.main.main. Injects --target ~/Documents/site-packages (sandbox-writable). Skips packages already on sys.path so bundled torch / numpy / matplotlib aren't re-downloaded. Honours -U only when explicitly passed |
pip-install |
4691 | Shortcut → pip install |
pip-uninstall |
4697 | Shortcut → pip uninstall |
pip-list |
4703 | Shortcut → pip list |
pip-show |
4709 | Shortcut → pip show |
pip-freeze |
4715 | Shortcut → pip freeze |
pip-check |
4721 | Shortcut → pip check + diagnostic of bundled-vs-user-installed |
| Command | Line | Notes |
|---|---|---|
git |
5805 | git clone only — no daemon. Dispatches on URL host: GitHub / GitLab / Bitbucket / Codeberg / Gitea / Forgejo → zipball over HTTPS; HuggingFace → huggingface_hub.snapshot_download (LFS-aware). HuggingFace URLs auto-route based on prefix: /spaces/, /datasets/, bare USER/REPO (model). git@host:user/repo SSH form parsed too |
| Command | Line | Notes |
|---|---|---|
clear / cls |
1655–56 | ANSI clear screen |
exit / quit |
1774–75 | Close the app (Swift listens for OSC marker) |
help |
1078 | List builtins, or help <name> for one |
man |
4100 | Print docstring (no real groff) |
which |
1622 | Locate a builtin or console_scripts entry |
seq |
7397 | Number sequence |
yes |
7425 | Repeat a string forever (interruptible) |
file |
7638 | Magic-byte file type identifier |
mktemp |
7686 | tempfile wrapper |
basename |
7611 | Strip directory |
dirname |
7622 | Strip filename |
realpath |
7630 | Resolve symlinks + relative paths |
crash-log / crashlog |
7479–80 | Tail ~/Documents/log.txt for previous fatal-crash records |
test-libs / test_libs |
7535–36 | Smoke-test every bundled library — sanity check after a build |
Every builtin accepts the same set of help tokens (--help, -h, --h, -H,
-help, help, -?, /?). When the user types one of these as the sole
argument, the universal handler prints the builtin's docstring instead of
running the command. Builtins that wrap a richer external CLI
(python, pip, git, the C/C++/Fortran compilers, the LaTeX engines) are
on a _FORWARD_HELP allow-list — their own help output is preserved, and
non-standard synonyms get rewritten to --help before dispatch.
On iOS the Python REPL runs on a worker thread, not the main interpreter
thread, so signal.signal() can't install a SIGINT handler and Swift's
PyErr_SetInterrupt() only reaches the main thread. Long-blocking
servers (Flask, Dash, Streamlit, anything calling socket.accept in a
loop) would otherwise be impossible to stop.
The python builtin spawns a daemon thread that polls two signal files:
$TMPDIR/offlinai_interrupt— generic, any caller$TMPDIR/codebench_kill.signal— what the SwiftPythonRuntime.forceKillRunningTasklong-press Stop writes today
When either file appears, the watchdog calls
ctypes.pythonapi.PyThreadState_SetAsyncExc() on every live Python thread
to inject KeyboardInterrupt. Then it enters a 5-second burst window
re-injecting every 500 ms — the script might be in a tight C call that
won't tick bytecode until a syscall returns, so the next select/poll/sleep
return wakes up holding the exception.
Before runpy.run_path() executes a user script, the shell pre-populates
the script's globals with ~35 stdlib names so casual scripting works
without explicit imports:
- Modules:
collections,csv,filecmp,json,math,re,shutil,tempfile,threading,time,itertools,functools,pickle,pathlib,subprocess,io,glob,uuid,hashlib,random,statistics,warnings - From
collections:defaultdict,Counter,OrderedDict,deque,namedtuple,ChainMap - From
concurrent.futures:ThreadPoolExecutor,ProcessPoolExecutor,as_completed - From
datetime:datetime,timedelta,date - From
itertools:chain,combinations,permutations,product,groupby,count,cycle,islice,accumulate - From
functools:partial,reduce,lru_cache,wraps,cache - From
pathlib:Path
Scripts that rely on these aren't portable to vanilla Python — anything the
script's own import statements would resolve to overrides them, so it's
purely additive.
On import, sets SSL_CERT_FILE, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE to
certifi's bundled CA list, and monkey-patches ssl.create_default_context
SSLContext.load_default_certsto injectcafile=certifi.where()so every library that didn't ship its own bundle (httpx, urllib3, google-cloud-storage, openai) gets working TLS verification without per-library configuration. Required because iOS has no system CA store.
pip 26.3 prints DEPRECATION: Unexpected import of 'X' after pip install started. for every import that happens after pip install returns. iOS
runs everything in one long-lived process, so this fires ~50 times for
every subsequent shell command. The filter drops it from both the
logging machinery and sys.stderr.
Every builtin call writes a breadcrumb to ~/Documents/log.txt via direct
os.write() + os.fsync(). When a builtin raises, _log_crash appends
a full record: timestamp, command + argv, traceback, sys.platform,
uname, app bundle path, all SSL / proxy / Python env vars, OpenSSL
version, and versions of the most common HTTP libraries (requests,
urllib3, httpx, httpcore, h11). Non-zero exits get the same
record without the traceback.
faulthandler.enable() is installed early in repl() so C-level
crashes (SIGSEGV, SIGBUS, SIGFPE, SIGABRT) write a thread-by-thread
Python stack trace to the same log before the kernel kills the process.
rm of a direct child of ~/Documents/Workspace/ appends the basename
to .codebench_deleted (and the legacy .offlinai_deleted is still
read for backward compatibility). The Swift FilesBrowserViewController.markStarterDeleted
mirror checks this set before re-seeding starter scripts on next launch.
After builtin lookup fails, the shell checks for an importlib.metadata
console_scripts entry point matching the command name. If found, it
evicts the entry-point module + every submodule of its top package from
sys.modules (mirroring what a fresh subprocess would do — required
because module-level argparse parsers conflict on re-entry), then calls
the entry-point function directly. Makes pypistats recent numpy work
after pip install pypistats with no restart.
ANSI OSC sequences like \x1b]codebench;ai-on\x1b\\ switch Swift's
PTYBridge LineBuffer into AI-aware completion mode. \x1b]codebench;ai-off\x1b\\
restores shell defaults. The markers are stripped from the visible
terminal text.
import offlinai_shell
# Run a single line — same as typing it at the prompt
offlinai_shell.run_line("ls /path/Documents")
offlinai_shell.run_line("grep -i error /path/log.txt")
offlinai_shell.run_line("pip install some-package")
offlinai_shell.run_line("manim -ql scene.py SquareToCircle")
offlinai_shell.run_line("git clone https://huggingface.co/unsloth/Qwen3.5-0.8B-GGUF")
# Block forever as an interactive REPL (used by the host app)
# offlinai_shell.repl()import offlinai_shell
@offlinai_shell.builtin("hello")
def my_hello(sh: offlinai_shell.Shell, argv: list[str]) -> None:
"""hello [name] — print a greeting."""
name = argv[0] if argv else "world"
print(f"hello, {name}!")
# Now `hello manim` works at the prompt.The decorator inserts into BUILTINS at registration time. Stacking
two @builtin(...) calls aliases (@builtin("python") @builtin("python3")).
- No fork/exec. Every builtin is implemented in pure Python via bundled libraries (
requestsfor wget,zipfilefor zip,psutilfor top,huggingface_hubfor git HF clone). - No real
gitdaemon. Code-hosting clones use the provider's HTTPS zipball endpoint (GitHub/zipball/, GitLab/repository/archive.zip, etc.). HuggingFace useshuggingface_hub.snapshot_downloadfor proper LFS handling. - No real
signal.signal()on the worker thread — the watchdog file pattern is the iOS-safe substitute. - No
/etc/ssl/cert.pem— certifi is auto-wired at import. exit/quitclose the app (via an OSC marker the Swift host listens for); they don'tsys.exit(0)the Python interpreter.
- offlinai-ai.md — what the
aibuiltin opens - offlinai-latex.md — what
pdflatex/xelatexroute to - pip.md — how the
pipbuiltin's target-directory injection works - huggingface-hub.md — the HF clone path's backend
- c-interpreter.md, cpp-interpreter.md, fortran-interpreter.md — what the compiler builtins delegate to
- The host app's
PTYBridge.swiftandPythonRuntime.swiftfor the Swift-side bridges