ICL is an enhanced REPL for Common Lisp. It provides a modern interactive experience with readline-style editing, persistent history, tab completion, and an extensible command system. ICL works in your terminal, in a web browser with package browsing and data visualization, or integrated with Emacs via SLY/SLIME.
Syntax highlighting, paren matching, tab completion, paredit mode, and interactive inspector
Package browser, symbol inspector, data visualization, and class hierarchy graphs
Jupyter-style notebooks with the full ICL editor per cell, rich outputs, interactive pivot tables, and reactive widgets
ICL includes a Jupyter-style notebook that runs in the browser interface. Open one with the ,notebook command or the --notebook flag:
icl --notebook # new, empty notebook
icl --notebook analysis.iclnb # open an existing notebook
icl --notebook data.csv # scaffold a notebook that loads the data file
,notebook ; new notebook (browser starts if needed)
,notebook analysis.iclnb ; open a saved notebook (Tab completes filenames)
Data-file templates. Pointing --notebook (or -b) at a data file scaffolds
a fresh notebook wired to load it, chosen by the file's extension, and saves it as a
sibling .iclnb (your data file is never touched). csv and tsv ship built-in
(they load Lisp-Stat and read-csv the file into *df*).
Register your own for any extension in ~/.iclrc:
(icl:register-notebook-template "json"
;; A template is a function of the data file's pathname returning a list of
;; cell specs, each a plist (:kind :code|:markdown :source STRING).
(lambda (path)
(list (list :kind :code
:source (format nil "(defparameter *data* (load-json ~S))"
(namestring path))))))
Notebooks can also be run headlessly (papermill-style) — parameterize and execute without a browser, then save the result:
icl --notebook report.iclnb --execute --param region=West -o report-west.iclnb
--param NAME=VALUE (repeatable) overrides bindings before the run, so
parameters declared with defvar pick up the override.
Highlights:
(display obj) emits several rich outputs from one cell; diagrams fit to width with scroll-to-zoom and drag-to-pan; each code cell shows its execution time.data-frame) renders as a sortable, filterable, pivotable grid, powered by Apache Arrow and Perspective (bundled, so it works fully offline). Drag columns into Group By to pivot and aggregate, or switch the same data to a bar / line / scatter / heatmap / treemap chart from the viewer settings — no chart spec needed. Column types are preserved, the layout is saved with the notebook, and any output can be maximized to fill the window (Esc restores).(slider 'n :min 0 :max 100), (dropdown 'r choices), (checkbox 'b), (text-input 's), (button 'go) bind a control to a symbol; changing it re-runs the cells that reference the symbol (reactive, no callbacks).$…$, $$…$$), and > [!NOTE]/WARNING/TIP admonitions.Shift-Enter runs and advances, Alt-Enter runs and inserts a cell below, Ctrl-Enter runs in place; plain Enter inserts a newline. Toolbar adds Run-above / Run-below and Restart-&-run-below.Esc, then j/k to move the selection, a/b to insert, dd to delete, m/y to switch markdown/code.parameters, hide-input, slide, …); collapse a cell's input to show only its output.Ctrl-F), a variable inspector of everything the notebook has defined, and (out N) to reuse an earlier cell's value..iclnb s-expressions; download any output (grid → CSV, chart → SVG), and export to a self-contained HTML file, a reveal.js slide deck (driven by slide tags), or a loadable jupytext-style .lisp. Grids/charts are snapshotted so they render in the exported files.Tabular values — a list of plists/alists, or a Lisp-Stat data-frame — are
encoded as typed Apache Arrow and handed to an embedded
Perspective view. Perspective and its WebAssembly
engine are bundled (Apache-2.0), so everything works fully offline. All of the
following is point-and-click in the view's settings panel — no chart spec, and the
same configuration drives whichever visualization you pick:
Esc restores).See examples/notebook-tour.iclnb for a guided tour, and
examples/arrow-tables.iclnb for table/pivot examples.
Interactive Speedscope flame graphs for performance profiling (SBCL only)
Profile any expression with ,flame and explore the results in an interactive Speedscope visualization. Switch between Time Order, Left Heavy, and Sandwich views to identify performance bottlenecks.
Synchronized browser visualization with SLY/SLIME - updates on C-x C-e, C-c C-c, and REPL input
,help),bt command to view stack traces,explain to get AI-powered explanations of code, errors, and resultsdisplay(), command-mode shortcuts, and export to HTML / .lispicl-runtime:visualize methods for your own classesAdd the ICL repository and install:
sudo dnf config-manager addrepo --from-repofile=https://atgreen.github.io/icl/rpm-repo/icl.repo
sudo dnf install icl
The RPM packages are GPG-signed. The signing key is imported automatically by dnf on first install.
Add the ICL repository and install:
curl -fsSL https://atgreen.github.io/icl/deb-repo/icl-archive-keyring.gpg | sudo tee /usr/share/keyrings/icl-archive-keyring.asc > /dev/null
echo "deb [signed-by=/usr/share/keyrings/icl-archive-keyring.asc] https://atgreen.github.io/icl/deb-repo stable main" | sudo tee /etc/apt/sources.list.d/icl.list
sudo apt update
sudo apt install icl
The DEB packages are GPG-signed. The signing key is imported in the first step above.
Download from GitHub Releases:
| Platform | Formats |
|---|---|
| Linux | RPM, DEB, tarball |
| macOS | tarball (arm64, amd64) |
| Windows | ZIP, EXE installer, MSI installer, Chocolatey |
Roswell users can install with:
ros install atgreen/icl
cd ~/.roswell/local-projects/atgreen/icl
ocicl install
ICL depends on libraries that aren't published to Quicklisp/Ultralisp, so the
ocicl step is required to fetch them — ros install alone
will not produce a working icl.
git clone https://github.com/atgreen/icl.git
cd icl
ocicl install
make
Requires SBCL, ocicl, and libfixposix-devel.
Start ICL (auto-detects available Lisp):
icl
Specify a Lisp implementation:
icl --lisp ccl
icl --lisp ecl
icl --lisp roswell # Use Roswell's managed environment
Evaluate an expression and exit:
icl -e '(+ 1 2 3)'
Load a file before starting the REPL:
icl -l init.lisp
Connect to an existing Slynk server:
icl --connect localhost:4005
Skip loading config file:
icl --no-config
Start with browser interface (opens IDE alongside terminal REPL):
icl -b
Open a browser notebook (a new one, an existing .iclnb file, or a data file
scaffolded from its extension template):
icl --notebook
icl --notebook analysis.iclnb
icl --notebook data.csv
ICL ships an Emacs helper for SLY or SLIME that launches the browser REPL and syncs visualizations with evals.
Minimal setup (adjust the path for your install):
(add-to-list 'load-path "/path/to/icl/emacs")
(require 'icl)
Commands:
M-x icl — start ICL browser connected to the current SLY/SLIME sessionM-x icl-stop — stop the ICL processM-x icl-restart — restart ICLIf you have both SLY and SLIME installed, ICL prefers SLY by default. You can override this:
(setq icl-backend 'slime) ;; or 'sly or 'auto
If you install the package via RPM/DEB/Windows installer, the Emacs
files are placed in a standard site-lisp directory and you can just
(require 'icl).
Suggested install locations for Emacs files:
%{_datadir}/emacs/site-lisp/icl/icl.el and icl-autoloads.el/usr/share/emacs/site-lisp/icl/icl.el and icl-autoloads.el<INSTALL>/share/emacs/site-lisp/icl/icl.el and icl-autoloads.elFor packaging, include icl-autoloads.el and add the site-lisp path to
Emacs’ load-path (via site-start.d or the installer), then users can:
(require 'icl)
Commands are prefixed with a comma. Type ,help for a full list.
| Command | Description |
|---|---|
,cd <package> | Change current package |
,pwd | Show current package |
,ls [filter] | List symbols (filters: functions, macros, variables, classes) |
| Command | Description |
|---|---|
,doc <symbol> | Show documentation |
,describe <symbol> | Full description of symbol |
,apropos <pattern> | Search for matching symbols |
,arglist <function> | Show function arguments |
,source <symbol> | View source in Monaco editor panel |
,edit <symbol> | Open source in $EDITOR (alias: ,ed) |
| Command | Description |
|---|---|
,callers <symbol> | Show functions that call symbol (alias: ,xc) |
,callees <symbol> | Show functions called by symbol (alias: ,xe) |
,references <symbol> | Show code that references variable (alias: ,xr) |
| Command | Description |
|---|---|
,inspect <expr> | Interactive object inspector (alias: ,i) |
,i | Inspect last result (*) |
,inspect-static <expr> | Non-interactive inspection output |
,slots <expr> | Show slots of a class instance |
The interactive inspector (,i or ,inspect) provides a TUI for exploring objects:
| Key | Action |
|---|---|
↑/↓ or j/k | Navigate entries |
Enter | Drill into selected entry |
b or Backspace | Go back to parent object |
q or Escape | Quit inspector |
| Command | Description |
|---|---|
,macroexpand <form> | Expand macro once |
,macroexpand-all <form> | Fully expand all macros |
| Command | Description |
|---|---|
,load-system <name> | Load system via ocicl/Quicklisp/ASDF (alias: ,ql) |
,libyear | Show dependency freshness metric (requires ocicl) |
,changes [system] | Show LLM-generated changelogs (requires ocicl) |
,time <form> | Time expression evaluation |
,load <file> | Load a Lisp file |
,compile-file <file> | Compile a file |
,disassemble <fn> | Disassemble a function |
| Command | Description |
|---|---|
,sql <query> | Query data with DuckDB — CSV/Parquet/JSON files, session data frames, and attached databases. Runs in the backend: prints a table and leaves the result (a list of plists) in *, usable by your next form (-o NAME also binds *NAME*). In a notebook cell it returns rows that render as an interactive grid. |
,attach NAME TYPE CONNINFO | Attach a SQLite / Postgres / DuckDB database so ,sql can query it as NAME.table (joinable with files and data frames). No args lists attachments. |
,sql needs the duckdb CLI on PATH; the sqlite/postgres
extensions auto-install on first use. Attach databases in three scopes:
;; Global, in ~/.iclrc:
(icl:register-sql-source "cache" :sqlite "/var/tmp/cache.db")
(icl:register-sql-source "pg" :postgres "host=localhost dbname=app user=me")
;; Terminal, for this session:
,attach cache sqlite /var/tmp/cache.db
;; Notebook-local: an ,attach CELL, saved with the .iclnb and scoped to it:
,attach pg postgres host=localhost dbname=app user=me
,sql SELECT u.name, o.total
FROM pg.public.users u JOIN cache.orders o USING (user_id)
Conninfo may use ${ENV_VARS}; passwords come from the environment (PGPASSWORD)
or ~/.pgpass, never the notebook file.
Parameterize with Lisp values. ${LISP-FORM} inside a query is evaluated in
the backend and spliced as a SQL literal — numbers inline, strings quoted, lists
as (a, b) for IN (always a literal, so a string value can't inject SQL):
(defparameter *min-age* 40)
,sql SELECT * FROM 'people.csv' WHERE age > ${*min-age*}
,sql SELECT * FROM 'people.csv' WHERE city IN ${(list "London" "NYC")}
| Command | Description |
|---|---|
,bt | Show backtrace from last error |
,step <form> | Show traced function calls during evaluation |
,threads | List all threads in inferior Lisp |
,trace <function> | Enable tracing |
,untrace <function> | Disable tracing |
,untrace-all | Disable all tracing |
| Command | Description |
|---|---|
,profile <form> | Profile a form with the statistical profiler |
,profile-start | Start ongoing profiling |
,profile-stop | Stop profiling and show results |
,profile-reset | Reset profiler data |
,flame <form> | Profile and show interactive flame graph in browser |
,cover-ql <system> | Load system with code coverage instrumentation |
,cover-load <path> | Load file with code coverage instrumentation |
,cover-report | Generate and display HTML coverage report |
,cover-reset | Clear all coverage data |
The ,flame command (aliases: ,flamegraph, ,fg) profiles the expression and opens an interactive Speedscope flame graph in the browser. Requires browser mode (,browser or icl -b).
The ,cover-* commands use SBCL's sb-cover for code coverage analysis. Load code with ,cover-ql or ,cover-load, run your tests, then ,cover-report to view coverage in a Monaco editor panel with expression-level highlighting:
Aliases: ,cql for ,cover-ql, ,cl for ,cover-load.
The ,source command opens a Monaco editor panel in the browser with:
The Symbol Info panel also includes a [Source] link next to [Inspect] for functions, providing quick access to source code.
| Command | Description |
|---|---|
,browser | Start browser-based IDE interface |
,notebook [file] | Open a browser notebook (optionally an existing .iclnb file) |
,viz <expr> | Visualize data in browser (class hierarchies, hash-tables, images, JSON, and more) |
The ,viz command automatically detects the type and displays an appropriate visualization:
'standard-object → interactive class hierarchy graph with slots*my-ht* → key-value table"{\"key\": \"value\"}" → syntax-highlighted, pretty-printed JSON"<svg>...</svg>" → rendered SVG graphics"<!DOCTYPE html>..." → rendered in sandboxed iframe#'mapcar → disassembly output with theme-aware stylingVisualize one or more FSet sets as Venn diagrams:
,viz *fruits* ; Single set as circle with members
,viz *fruits* *red-things* ; Two-set Venn diagram showing overlap
,viz *set-a* *set-b* *set-c* ; Three-set Venn diagram
Venn diagrams automatically refresh after each REPL evaluation to reflect data changes.
The class hierarchy graph supports interactive exploration:
Visualize image data stored in byte arrays:
;; Load an image file into a byte vector
(defvar *img* (alexandria:read-file-into-byte-vector #P"photo.png"))
,viz *img*
Supported formats (detected by magic bytes): PNG, JPEG, GIF, WebP.
JSON strings are automatically pretty-printed with syntax highlighting:
(defvar *data* "{\"name\": \"Alice\", \"scores\": [95, 87, 92]}")
,viz *data*
All visualization panels auto-refresh after REPL evaluations.
Define methods on icl-runtime:visualize to create custom visualizations for your own classes:
;; Visualize a game board as HTML
(defmethod icl-runtime:visualize ((obj my-game-state))
(list :html (render-board-html obj)))
;; Visualize data as SVG chart
(defmethod icl-runtime:visualize ((obj my-data-series))
(list :svg (generate-chart-svg obj)))
;; Visualize config as JSON
(defmethod icl-runtime:visualize ((obj my-config))
(list :json (serialize-to-json obj)))
;; Visualize metrics as Vega-Lite bar chart
(defmethod icl-runtime:visualize ((obj my-metrics))
(list :vega-lite
(format nil "{\"$schema\":\"https://vega.github.io/schema/vega-lite/v6.json\",
\"data\":{\"values\":~A},
\"mark\":\"bar\",
\"encoding\":{\"x\":{\"field\":\"name\",\"type\":\"nominal\"},
\"y\":{\"field\":\"value\",\"type\":\"quantitative\"}}}"
(metrics-to-json obj))))
Supported visualization types:
(:html string) - Render HTML in sandboxed iframe(:svg string) - Render SVG graphics(:json string) - Syntax-highlighted JSON(:vega-lite spec-string) - Render Vega-Lite chart(:mermaid definition-string) - Render Mermaid diagram(:regexp pattern-string) - Render regex railroad diagram via Regulex(:image-base64 mime-type base64-string) - Image from base64 dataReturn NIL from your method to fall back to ICL's built-in type detection.
See examples/vega.lisp for Vega-Lite, examples/mermaid.lisp for Mermaid diagrams, and examples/regexp.lisp for regex visualization.
Libraries can provide visualizations that work even when loaded before ICL connects (e.g., when attaching ICL to a running Lisp from Emacs). Define a REGISTER-ICL-VIZ function in your package:
(in-package :my-library)
(defun register-icl-viz ()
"Called by ICL to register visualizations for this library."
(defmethod icl-runtime:visualize ((obj my-data-structure))
(list :mermaid (my-struct-to-mermaid obj)))
(defmethod icl-runtime:visualize ((obj my-config))
(list :json (config-to-json obj))))
ICL automatically discovers and calls REGISTER-ICL-VIZ in all packages when ,viz is invoked. The icl-runtime package is guaranteed to exist when your function is called. Each package is only processed once per session.
Custom visualizations are protected with multiple layers of security:
| Type | Security Measures |
|---|---|
| HTML | Sanitized server-side (scripts and event handlers removed), rendered in sandboxed iframe |
| Mermaid | Rendered in strict mode (click handlers and JavaScript disabled) |
| Vega-Lite | Expression functions disabled, AST-based evaluation only |
| SVG | Protected by CSP (inline scripts and event handlers blocked) |
The browser interface also enforces:
These protections ensure that loading untrusted Lisp libraries with custom visualize methods cannot execute arbitrary JavaScript in your browser.
To disable these restrictions (for trusted code that requires JavaScript in visualizations), use:
icl -b --unsafe-visualizations
| Command | Description |
|---|---|
,show-config | Show config file location and customization options |
,reload-config | Reload config file |
,paredit [on/off] | Toggle paredit structural editing mode |
| Command | Description |
|---|---|
,help | Show all commands |
,info | Show session information |
,history | Show value history variables |
,lisp [name] | Show or switch Lisp backend |
,clear | Clear terminal |
,quit | Exit ICL |
| Command | Description |
|---|---|
,explain | Explain last result or error using AI |
,explain <code> | Explain specific code |
,ai-cli [name] | Show or set AI backend (gemini, claude, codex) |
The ,explain command uses an AI CLI (auto-detected from PATH) to provide explanations of Lisp code, errors, and results. When using Gemini or Claude CLI, ICL provides an MCP server that gives the AI read-only access to the live Lisp environment - it can query documentation, describe symbols, and search for functions, but cannot execute any code.
Requires one of: Gemini CLI, Claude CLI, or Codex CLI
ICL maintains history of recent values and inputs:
| Variable | Description |
|---|---|
icl:_ / icl:icl-* | Last result |
icl:__ / icl:icl-** | Second-to-last result |
icl:___ / icl:icl-*** | Third-to-last result |
icl:icl-+ | Last input form |
icl:icl-/ | Last returned values (all values) |
ICL loads a config file on startup (unless --no-config is specified). This file can contain any Common Lisp code.
Config file locations:
$XDG_CONFIG_HOME/icl/config.lisp (default: ~/.config/icl/config.lisp)%APPDATA%\icl\config.lispHistory file locations:
$XDG_STATE_HOME/icl/history (default: ~/.local/state/icl/history)%LOCALAPPDATA%\icl\historyUse ,show-config to see the actual paths on your system.
| Variable | Description |
|---|---|
*default-lisp* | Lisp implementation to use (:sbcl, :ccl, :ecl, :clisp, :abcl, :clasp, :roswell) |
*prompt-string* | Prompt format string (default: "~A> ") |
*result-prefix* | Prefix for results (default: "=> ") |
*colors-enabled* | Enable syntax coloring (default: t) |
*history-size* | Maximum history entries (default: 1000) |
*paredit-mode* | Enable structural editing (default: nil) |
*ai-cli* | AI CLI for ,explain (:gemini, :claude, :codex, or :auto) |
Use configure-lisp to customize how ICL invokes a Lisp implementation:
(icl:configure-lisp impl &key program args eval-arg)
:program - Path to the executable:args - List of command-line arguments:eval-arg - The eval flag (e.g., "--eval");; Use CCL instead of SBCL
(setf icl:*default-lisp* :ccl)
;; Custom SBCL with more memory
(icl:configure-lisp :sbcl
:program "/opt/sbcl/bin/sbcl"
:args '("--dynamic-space-size" "8192"))
;; Enable paredit mode
(setf icl:*paredit-mode* t)
;; Custom prompt
(setf icl:*prompt-string* "λ ~A> ")
;; Load commonly used systems
(asdf:load-system :alexandria)
;; Define custom utilities
(defun reload ()
(asdf:load-system :my-project :force t))
| Key | Description |
|---|---|
Enter | Submit a complete form only at the end of the buffer; otherwise insert newline |
Alt+Enter | Always insert newline (works in most terminals) |
Shift+Enter | Always insert newline (requires kitty keyboard protocol) |
Tab | Complete symbol or show completion menu |
Ctrl+A / Home | Move to beginning of line |
Ctrl+E / End | Move to end of line |
Ctrl+F | Forward character |
Ctrl+B | Backward character |
Ctrl+N | Next line (in multi-line input) |
Ctrl+P | Previous line (in multi-line input) |
Ctrl+K | Kill to end of line |
Ctrl+O | Open line (insert newline, cursor stays) |
Ctrl+T | Transpose characters |
Ctrl+U | Clear entire line |
Alt+D | Kill word forward |
Alt+Backspace | Kill word backward |
Ctrl+L | Clear screen |
Ctrl+D | Delete character at cursor, or EOF if line empty (Emacs-style) |
Ctrl+Z | Undo |
Ctrl+Y | Redo |
Ctrl+C | Cancel current input |
Ctrl+\ | Suspend process |
Ctrl+R | Reverse history search (substring match) |
Ctrl+G | Cancel search |
Up/Down | Navigate history (on first/last line) or move cursor |
Alt+P | History search backward (prefix match) |
Alt+N | History search forward (prefix match) |
Alt+Q | Reindent current form |
Alt+F | Forward sexp (paredit mode only) |
Alt+B | Backward sexp (paredit mode only) |
Ctrl+X | Cut the selection to the clipboard |
| Mouse drag | Select text; releasing copies the selection to the clipboard |
Notes:
wl-copy/xclip/pbcopy) and stays highlighted until the next edit or cursor move. Typing, paste, or Backspace/Delete replaces the selection; Ctrl+X cuts it.Shift) for its own native selection and scrollback.| Variable | Description |
|---|---|
ICL_SLYNK_PATH | Override path to Slynk directory |
ICL_ASDF_PATH | Override path to bundled ASDF (for backends without ASDF) |
ICL_BACKGROUND | Override terminal background detection (dark or light) |
NO_COLOR | When set to any non-empty value, disables colored output (see no-color.org) |
ICL aims to support multiple Common Lisp implementations. SBCL is the primary development and testing platform.
| Implementation | Status |
|---|---|
| SBCL | Tested |
| CCL | Tested |
| ECL | Tested |
| ABCL | Tested |
| Roswell | Tested |
| Clasp | Untested |
| CLISP | Experimental |
ICL operates as a frontend that communicates with a backend Lisp process via the Slynk protocol (from SLY). This architecture allows ICL to work with any Common Lisp implementation, provide consistent features regardless of backend, and connect to remote Lisp processes.
flowchart LR
subgraph Clients
Terminal
Browser
Emacs["Emacs"]
AI["AI CLIs"]
end
subgraph ICL
direction TB
Web["Web Server"]
MCP["MCP Server"]
Client["Slynk Client"]
end
subgraph Lisp ["Lisp Image"]
Server["Slynk Server"]
end
Terminal ---|stdin/stdout| ICL
Browser ---|HTTP/WS| Web
AI ---|HTTP| MCP
Client & Emacs ---|Slynk| Server
All connections use randomly-assigned ports on localhost. When ICL starts an inferior Lisp, it finds an available port and configures Slynk to listen there. The browser interface (started with ,browser or icl -b) serves a Dockview-based IDE with package browser, symbol list, inspector panels, and class hierarchy visualization. The browser automatically closes when ICL terminates. The MCP server (started on-demand by ,explain) provides read-only AI tool integration.
MIT License. See LICENSE file for details.
Anthony Green green@moxielogic.com
ICL is an enhanced REPL for Common Lisp. It provides a modern interactive experience with readline-style editing, persistent history, tab completion, and an extensible command system. ICL works in your terminal, in a web browser with package browsing and data visualization, or integrated with Emacs via SLY/SLIME.
Syntax highlighting, paren matching, tab completion, paredit mode, and interactive inspector
Package browser, symbol inspector, data visualization, and class hierarchy graphs
Jupyter-style notebooks with the full ICL editor per cell, rich outputs, interactive pivot tables, and reactive widgets
ICL includes a Jupyter-style notebook that runs in the browser interface. Open one with the ,notebook command or the --notebook flag:
icl --notebook # new, empty notebook
icl --notebook analysis.iclnb # open an existing notebook
icl --notebook data.csv # scaffold a notebook that loads the data file
,notebook ; new notebook (browser starts if needed)
,notebook analysis.iclnb ; open a saved notebook (Tab completes filenames)
Data-file templates. Pointing --notebook (or -b) at a data file scaffolds
a fresh notebook wired to load it, chosen by the file's extension, and saves it as a
sibling .iclnb (your data file is never touched). csv and tsv ship built-in
(they load Lisp-Stat and read-csv the file into *df*).
Register your own for any extension in ~/.iclrc:
(icl:register-notebook-template "json"
;; A template is a function of the data file's pathname returning a list of
;; cell specs, each a plist (:kind :code|:markdown :source STRING).
(lambda (path)
(list (list :kind :code
:source (format nil "(defparameter *data* (load-json ~S))"
(namestring path))))))
Notebooks can also be run headlessly (papermill-style) — parameterize and execute without a browser, then save the result:
icl --notebook report.iclnb --execute --param region=West -o report-west.iclnb
--param NAME=VALUE (repeatable) overrides bindings before the run, so
parameters declared with defvar pick up the override.
Highlights:
(display obj) emits several rich outputs from one cell; diagrams fit to width with scroll-to-zoom and drag-to-pan; each code cell shows its execution time.data-frame) renders as a sortable, filterable, pivotable grid, powered by Apache Arrow and Perspective (bundled, so it works fully offline). Drag columns into Group By to pivot and aggregate, or switch the same data to a bar / line / scatter / heatmap / treemap chart from the viewer settings — no chart spec needed. Column types are preserved, the layout is saved with the notebook, and any output can be maximized to fill the window (Esc restores).(slider 'n :min 0 :max 100), (dropdown 'r choices), (checkbox 'b), (text-input 's), (button 'go) bind a control to a symbol; changing it re-runs the cells that reference the symbol (reactive, no callbacks).$…$, $$…$$), and > [!NOTE]/WARNING/TIP admonitions.Shift-Enter runs and advances, Alt-Enter runs and inserts a cell below, Ctrl-Enter runs in place; plain Enter inserts a newline. Toolbar adds Run-above / Run-below and Restart-&-run-below.Esc, then j/k to move the selection, a/b to insert, dd to delete, m/y to switch markdown/code.parameters, hide-input, slide, …); collapse a cell's input to show only its output.Ctrl-F), a variable inspector of everything the notebook has defined, and (out N) to reuse an earlier cell's value..iclnb s-expressions; download any output (grid → CSV, chart → SVG), and export to a self-contained HTML file, a reveal.js slide deck (driven by slide tags), or a loadable jupytext-style .lisp. Grids/charts are snapshotted so they render in the exported files.Tabular values — a list of plists/alists, or a Lisp-Stat data-frame — are
encoded as typed Apache Arrow and handed to an embedded
Perspective view. Perspective and its WebAssembly
engine are bundled (Apache-2.0), so everything works fully offline. All of the
following is point-and-click in the view's settings panel — no chart spec, and the
same configuration drives whichever visualization you pick:
Esc restores).See examples/notebook-tour.iclnb for a guided tour, and
examples/arrow-tables.iclnb for table/pivot examples.
Interactive Speedscope flame graphs for performance profiling (SBCL only)
Profile any expression with ,flame and explore the results in an interactive Speedscope visualization. Switch between Time Order, Left Heavy, and Sandwich views to identify performance bottlenecks.
Synchronized browser visualization with SLY/SLIME - updates on C-x C-e, C-c C-c, and REPL input
,help),bt command to view stack traces,explain to get AI-powered explanations of code, errors, and resultsdisplay(), command-mode shortcuts, and export to HTML / .lispicl-runtime:visualize methods for your own classesAdd the ICL repository and install:
sudo dnf config-manager addrepo --from-repofile=https://atgreen.github.io/icl/rpm-repo/icl.repo
sudo dnf install icl
The RPM packages are GPG-signed. The signing key is imported automatically by dnf on first install.
Add the ICL repository and install:
curl -fsSL https://atgreen.github.io/icl/deb-repo/icl-archive-keyring.gpg | sudo tee /usr/share/keyrings/icl-archive-keyring.asc > /dev/null
echo "deb [signed-by=/usr/share/keyrings/icl-archive-keyring.asc] https://atgreen.github.io/icl/deb-repo stable main" | sudo tee /etc/apt/sources.list.d/icl.list
sudo apt update
sudo apt install icl
The DEB packages are GPG-signed. The signing key is imported in the first step above.
Download from GitHub Releases:
| Platform | Formats |
|---|---|
| Linux | RPM, DEB, tarball |
| macOS | tarball (arm64, amd64) |
| Windows | ZIP, EXE installer, MSI installer, Chocolatey |
Roswell users can install with:
ros install atgreen/icl
cd ~/.roswell/local-projects/atgreen/icl
ocicl install
ICL depends on libraries that aren't published to Quicklisp/Ultralisp, so the
ocicl step is required to fetch them — ros install alone
will not produce a working icl.
git clone https://github.com/atgreen/icl.git
cd icl
ocicl install
make
Requires SBCL, ocicl, and libfixposix-devel.
Start ICL (auto-detects available Lisp):
icl
Specify a Lisp implementation:
icl --lisp ccl
icl --lisp ecl
icl --lisp roswell # Use Roswell's managed environment
Evaluate an expression and exit:
icl -e '(+ 1 2 3)'
Load a file before starting the REPL:
icl -l init.lisp
Connect to an existing Slynk server:
icl --connect localhost:4005
Skip loading config file:
icl --no-config
Start with browser interface (opens IDE alongside terminal REPL):
icl -b
Open a browser notebook (a new one, an existing .iclnb file, or a data file
scaffolded from its extension template):
icl --notebook
icl --notebook analysis.iclnb
icl --notebook data.csv
ICL ships an Emacs helper for SLY or SLIME that launches the browser REPL and syncs visualizations with evals.
Minimal setup (adjust the path for your install):
(add-to-list 'load-path "/path/to/icl/emacs")
(require 'icl)
Commands:
M-x icl — start ICL browser connected to the current SLY/SLIME sessionM-x icl-stop — stop the ICL processM-x icl-restart — restart ICLIf you have both SLY and SLIME installed, ICL prefers SLY by default. You can override this:
(setq icl-backend 'slime) ;; or 'sly or 'auto
If you install the package via RPM/DEB/Windows installer, the Emacs
files are placed in a standard site-lisp directory and you can just
(require 'icl).
Suggested install locations for Emacs files:
%{_datadir}/emacs/site-lisp/icl/icl.el and icl-autoloads.el/usr/share/emacs/site-lisp/icl/icl.el and icl-autoloads.el<INSTALL>/share/emacs/site-lisp/icl/icl.el and icl-autoloads.elFor packaging, include icl-autoloads.el and add the site-lisp path to
Emacs’ load-path (via site-start.d or the installer), then users can:
(require 'icl)
Commands are prefixed with a comma. Type ,help for a full list.
| Command | Description |
|---|---|
,cd <package> | Change current package |
,pwd | Show current package |
,ls [filter] | List symbols (filters: functions, macros, variables, classes) |
| Command | Description |
|---|---|
,doc <symbol> | Show documentation |
,describe <symbol> | Full description of symbol |
,apropos <pattern> | Search for matching symbols |
,arglist <function> | Show function arguments |
,source <symbol> | View source in Monaco editor panel |
,edit <symbol> | Open source in $EDITOR (alias: ,ed) |
| Command | Description |
|---|---|
,callers <symbol> | Show functions that call symbol (alias: ,xc) |
,callees <symbol> | Show functions called by symbol (alias: ,xe) |
,references <symbol> | Show code that references variable (alias: ,xr) |
| Command | Description |
|---|---|
,inspect <expr> | Interactive object inspector (alias: ,i) |
,i | Inspect last result (*) |
,inspect-static <expr> | Non-interactive inspection output |
,slots <expr> | Show slots of a class instance |
The interactive inspector (,i or ,inspect) provides a TUI for exploring objects:
| Key | Action |
|---|---|
↑/↓ or j/k | Navigate entries |
Enter | Drill into selected entry |
b or Backspace | Go back to parent object |
q or Escape | Quit inspector |
| Command | Description |
|---|---|
,macroexpand <form> | Expand macro once |
,macroexpand-all <form> | Fully expand all macros |
| Command | Description |
|---|---|
,load-system <name> | Load system via ocicl/Quicklisp/ASDF (alias: ,ql) |
,libyear | Show dependency freshness metric (requires ocicl) |
,changes [system] | Show LLM-generated changelogs (requires ocicl) |
,time <form> | Time expression evaluation |
,load <file> | Load a Lisp file |
,compile-file <file> | Compile a file |
,disassemble <fn> | Disassemble a function |
| Command | Description |
|---|---|
,sql <query> | Query data with DuckDB — CSV/Parquet/JSON files, session data frames, and attached databases. Runs in the backend: prints a table and leaves the result (a list of plists) in *, usable by your next form (-o NAME also binds *NAME*). In a notebook cell it returns rows that render as an interactive grid. |
,attach NAME TYPE CONNINFO | Attach a SQLite / Postgres / DuckDB database so ,sql can query it as NAME.table (joinable with files and data frames). No args lists attachments. |
,sql needs the duckdb CLI on PATH; the sqlite/postgres
extensions auto-install on first use. Attach databases in three scopes:
;; Global, in ~/.iclrc:
(icl:register-sql-source "cache" :sqlite "/var/tmp/cache.db")
(icl:register-sql-source "pg" :postgres "host=localhost dbname=app user=me")
;; Terminal, for this session:
,attach cache sqlite /var/tmp/cache.db
;; Notebook-local: an ,attach CELL, saved with the .iclnb and scoped to it:
,attach pg postgres host=localhost dbname=app user=me
,sql SELECT u.name, o.total
FROM pg.public.users u JOIN cache.orders o USING (user_id)
Conninfo may use ${ENV_VARS}; passwords come from the environment (PGPASSWORD)
or ~/.pgpass, never the notebook file.
Parameterize with Lisp values. ${LISP-FORM} inside a query is evaluated in
the backend and spliced as a SQL literal — numbers inline, strings quoted, lists
as (a, b) for IN (always a literal, so a string value can't inject SQL):
(defparameter *min-age* 40)
,sql SELECT * FROM 'people.csv' WHERE age > ${*min-age*}
,sql SELECT * FROM 'people.csv' WHERE city IN ${(list "London" "NYC")}
| Command | Description |
|---|---|
,bt | Show backtrace from last error |
,step <form> | Show traced function calls during evaluation |
,threads | List all threads in inferior Lisp |
,trace <function> | Enable tracing |
,untrace <function> | Disable tracing |
,untrace-all | Disable all tracing |
| Command | Description |
|---|---|
,profile <form> | Profile a form with the statistical profiler |
,profile-start | Start ongoing profiling |
,profile-stop | Stop profiling and show results |
,profile-reset | Reset profiler data |
,flame <form> | Profile and show interactive flame graph in browser |
,cover-ql <system> | Load system with code coverage instrumentation |
,cover-load <path> | Load file with code coverage instrumentation |
,cover-report | Generate and display HTML coverage report |
,cover-reset | Clear all coverage data |
The ,flame command (aliases: ,flamegraph, ,fg) profiles the expression and opens an interactive Speedscope flame graph in the browser. Requires browser mode (,browser or icl -b).
The ,cover-* commands use SBCL's sb-cover for code coverage analysis. Load code with ,cover-ql or ,cover-load, run your tests, then ,cover-report to view coverage in a Monaco editor panel with expression-level highlighting:
Aliases: ,cql for ,cover-ql, ,cl for ,cover-load.
The ,source command opens a Monaco editor panel in the browser with:
The Symbol Info panel also includes a [Source] link next to [Inspect] for functions, providing quick access to source code.
| Command | Description |
|---|---|
,browser | Start browser-based IDE interface |
,notebook [file] | Open a browser notebook (optionally an existing .iclnb file) |
,viz <expr> | Visualize data in browser (class hierarchies, hash-tables, images, JSON, and more) |
The ,viz command automatically detects the type and displays an appropriate visualization:
'standard-object → interactive class hierarchy graph with slots*my-ht* → key-value table"{\"key\": \"value\"}" → syntax-highlighted, pretty-printed JSON"<svg>...</svg>" → rendered SVG graphics"<!DOCTYPE html>..." → rendered in sandboxed iframe#'mapcar → disassembly output with theme-aware stylingVisualize one or more FSet sets as Venn diagrams:
,viz *fruits* ; Single set as circle with members
,viz *fruits* *red-things* ; Two-set Venn diagram showing overlap
,viz *set-a* *set-b* *set-c* ; Three-set Venn diagram
Venn diagrams automatically refresh after each REPL evaluation to reflect data changes.
The class hierarchy graph supports interactive exploration:
Visualize image data stored in byte arrays:
;; Load an image file into a byte vector
(defvar *img* (alexandria:read-file-into-byte-vector #P"photo.png"))
,viz *img*
Supported formats (detected by magic bytes): PNG, JPEG, GIF, WebP.
JSON strings are automatically pretty-printed with syntax highlighting:
(defvar *data* "{\"name\": \"Alice\", \"scores\": [95, 87, 92]}")
,viz *data*
All visualization panels auto-refresh after REPL evaluations.
Define methods on icl-runtime:visualize to create custom visualizations for your own classes:
;; Visualize a game board as HTML
(defmethod icl-runtime:visualize ((obj my-game-state))
(list :html (render-board-html obj)))
;; Visualize data as SVG chart
(defmethod icl-runtime:visualize ((obj my-data-series))
(list :svg (generate-chart-svg obj)))
;; Visualize config as JSON
(defmethod icl-runtime:visualize ((obj my-config))
(list :json (serialize-to-json obj)))
;; Visualize metrics as Vega-Lite bar chart
(defmethod icl-runtime:visualize ((obj my-metrics))
(list :vega-lite
(format nil "{\"$schema\":\"https://vega.github.io/schema/vega-lite/v6.json\",
\"data\":{\"values\":~A},
\"mark\":\"bar\",
\"encoding\":{\"x\":{\"field\":\"name\",\"type\":\"nominal\"},
\"y\":{\"field\":\"value\",\"type\":\"quantitative\"}}}"
(metrics-to-json obj))))
Supported visualization types:
(:html string) - Render HTML in sandboxed iframe(:svg string) - Render SVG graphics(:json string) - Syntax-highlighted JSON(:vega-lite spec-string) - Render Vega-Lite chart(:mermaid definition-string) - Render Mermaid diagram(:regexp pattern-string) - Render regex railroad diagram via Regulex(:image-base64 mime-type base64-string) - Image from base64 dataReturn NIL from your method to fall back to ICL's built-in type detection.
See examples/vega.lisp for Vega-Lite, examples/mermaid.lisp for Mermaid diagrams, and examples/regexp.lisp for regex visualization.
Libraries can provide visualizations that work even when loaded before ICL connects (e.g., when attaching ICL to a running Lisp from Emacs). Define a REGISTER-ICL-VIZ function in your package:
(in-package :my-library)
(defun register-icl-viz ()
"Called by ICL to register visualizations for this library."
(defmethod icl-runtime:visualize ((obj my-data-structure))
(list :mermaid (my-struct-to-mermaid obj)))
(defmethod icl-runtime:visualize ((obj my-config))
(list :json (config-to-json obj))))
ICL automatically discovers and calls REGISTER-ICL-VIZ in all packages when ,viz is invoked. The icl-runtime package is guaranteed to exist when your function is called. Each package is only processed once per session.
Custom visualizations are protected with multiple layers of security:
| Type | Security Measures |
|---|---|
| HTML | Sanitized server-side (scripts and event handlers removed), rendered in sandboxed iframe |
| Mermaid | Rendered in strict mode (click handlers and JavaScript disabled) |
| Vega-Lite | Expression functions disabled, AST-based evaluation only |
| SVG | Protected by CSP (inline scripts and event handlers blocked) |
The browser interface also enforces:
These protections ensure that loading untrusted Lisp libraries with custom visualize methods cannot execute arbitrary JavaScript in your browser.
To disable these restrictions (for trusted code that requires JavaScript in visualizations), use:
icl -b --unsafe-visualizations
| Command | Description |
|---|---|
,show-config | Show config file location and customization options |
,reload-config | Reload config file |
,paredit [on/off] | Toggle paredit structural editing mode |
| Command | Description |
|---|---|
,help | Show all commands |
,info | Show session information |
,history | Show value history variables |
,lisp [name] | Show or switch Lisp backend |
,clear | Clear terminal |
,quit | Exit ICL |
| Command | Description |
|---|---|
,explain | Explain last result or error using AI |
,explain <code> | Explain specific code |
,ai-cli [name] | Show or set AI backend (gemini, claude, codex) |
The ,explain command uses an AI CLI (auto-detected from PATH) to provide explanations of Lisp code, errors, and results. When using Gemini or Claude CLI, ICL provides an MCP server that gives the AI read-only access to the live Lisp environment - it can query documentation, describe symbols, and search for functions, but cannot execute any code.
Requires one of: Gemini CLI, Claude CLI, or Codex CLI
ICL maintains history of recent values and inputs:
| Variable | Description |
|---|---|
icl:_ / icl:icl-* | Last result |
icl:__ / icl:icl-** | Second-to-last result |
icl:___ / icl:icl-*** | Third-to-last result |
icl:icl-+ | Last input form |
icl:icl-/ | Last returned values (all values) |
ICL loads a config file on startup (unless --no-config is specified). This file can contain any Common Lisp code.
Config file locations:
$XDG_CONFIG_HOME/icl/config.lisp (default: ~/.config/icl/config.lisp)%APPDATA%\icl\config.lispHistory file locations:
$XDG_STATE_HOME/icl/history (default: ~/.local/state/icl/history)%LOCALAPPDATA%\icl\historyUse ,show-config to see the actual paths on your system.
| Variable | Description |
|---|---|
*default-lisp* | Lisp implementation to use (:sbcl, :ccl, :ecl, :clisp, :abcl, :clasp, :roswell) |
*prompt-string* | Prompt format string (default: "~A> ") |
*result-prefix* | Prefix for results (default: "=> ") |
*colors-enabled* | Enable syntax coloring (default: t) |
*history-size* | Maximum history entries (default: 1000) |
*paredit-mode* | Enable structural editing (default: nil) |
*ai-cli* | AI CLI for ,explain (:gemini, :claude, :codex, or :auto) |
Use configure-lisp to customize how ICL invokes a Lisp implementation:
(icl:configure-lisp impl &key program args eval-arg)
:program - Path to the executable:args - List of command-line arguments:eval-arg - The eval flag (e.g., "--eval");; Use CCL instead of SBCL
(setf icl:*default-lisp* :ccl)
;; Custom SBCL with more memory
(icl:configure-lisp :sbcl
:program "/opt/sbcl/bin/sbcl"
:args '("--dynamic-space-size" "8192"))
;; Enable paredit mode
(setf icl:*paredit-mode* t)
;; Custom prompt
(setf icl:*prompt-string* "λ ~A> ")
;; Load commonly used systems
(asdf:load-system :alexandria)
;; Define custom utilities
(defun reload ()
(asdf:load-system :my-project :force t))
| Key | Description |
|---|---|
Enter | Submit a complete form only at the end of the buffer; otherwise insert newline |
Alt+Enter | Always insert newline (works in most terminals) |
Shift+Enter | Always insert newline (requires kitty keyboard protocol) |
Tab | Complete symbol or show completion menu |
Ctrl+A / Home | Move to beginning of line |
Ctrl+E / End | Move to end of line |
Ctrl+F | Forward character |
Ctrl+B | Backward character |
Ctrl+N | Next line (in multi-line input) |
Ctrl+P | Previous line (in multi-line input) |
Ctrl+K | Kill to end of line |
Ctrl+O | Open line (insert newline, cursor stays) |
Ctrl+T | Transpose characters |
Ctrl+U | Clear entire line |
Alt+D | Kill word forward |
Alt+Backspace | Kill word backward |
Ctrl+L | Clear screen |
Ctrl+D | Delete character at cursor, or EOF if line empty (Emacs-style) |
Ctrl+Z | Undo |
Ctrl+Y | Redo |
Ctrl+C | Cancel current input |
Ctrl+\ | Suspend process |
Ctrl+R | Reverse history search (substring match) |
Ctrl+G | Cancel search |
Up/Down | Navigate history (on first/last line) or move cursor |
Alt+P | History search backward (prefix match) |
Alt+N | History search forward (prefix match) |
Alt+Q | Reindent current form |
Alt+F | Forward sexp (paredit mode only) |
Alt+B | Backward sexp (paredit mode only) |
Ctrl+X | Cut the selection to the clipboard |
| Mouse drag | Select text; releasing copies the selection to the clipboard |
Notes:
wl-copy/xclip/pbcopy) and stays highlighted until the next edit or cursor move. Typing, paste, or Backspace/Delete replaces the selection; Ctrl+X cuts it.Shift) for its own native selection and scrollback.| Variable | Description |
|---|---|
ICL_SLYNK_PATH | Override path to Slynk directory |
ICL_ASDF_PATH | Override path to bundled ASDF (for backends without ASDF) |
ICL_BACKGROUND | Override terminal background detection (dark or light) |
NO_COLOR | When set to any non-empty value, disables colored output (see no-color.org) |
ICL aims to support multiple Common Lisp implementations. SBCL is the primary development and testing platform.
| Implementation | Status |
|---|---|
| SBCL | Tested |
| CCL | Tested |
| ECL | Tested |
| ABCL | Tested |
| Roswell | Tested |
| Clasp | Untested |
| CLISP | Experimental |
ICL operates as a frontend that communicates with a backend Lisp process via the Slynk protocol (from SLY). This architecture allows ICL to work with any Common Lisp implementation, provide consistent features regardless of backend, and connect to remote Lisp processes.
flowchart LR
subgraph Clients
Terminal
Browser
Emacs["Emacs"]
AI["AI CLIs"]
end
subgraph ICL
direction TB
Web["Web Server"]
MCP["MCP Server"]
Client["Slynk Client"]
end
subgraph Lisp ["Lisp Image"]
Server["Slynk Server"]
end
Terminal ---|stdin/stdout| ICL
Browser ---|HTTP/WS| Web
AI ---|HTTP| MCP
Client & Emacs ---|Slynk| Server
All connections use randomly-assigned ports on localhost. When ICL starts an inferior Lisp, it finds an available port and configures Slynk to listen there. The browser interface (started with ,browser or icl -b) serves a Dockview-based IDE with package browser, symbol list, inspector panels, and class hierarchy visualization. The browser automatically closes when ICL terminates. The MCP server (started on-demand by ,explain) provides read-only AI tool integration.
MIT License. See LICENSE file for details.
Anthony Green green@moxielogic.com