diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2759bf4..a4c1e43 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
check:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
@@ -35,6 +35,8 @@ jobs:
run: cargo test --workspace --features lash-cli/fff-zlob --locked
- name: Check operator scripts
run: python3 -m py_compile scripts/*.py
+ - name: Check documentation
+ run: python3 scripts/check_docs.py
build:
strategy:
@@ -42,11 +44,19 @@ jobs:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
+ linker: ""
+ packages: ""
+ - os: ubuntu-latest
+ target: aarch64-unknown-linux-gnu
+ linker: aarch64-linux-gnu-gcc
+ packages: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
- os: macos-14
target: aarch64-apple-darwin
+ linker: ""
+ packages: ""
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
@@ -54,5 +64,10 @@ jobs:
with:
version: 0.15.2
- uses: Swatinem/rust-cache@v2
+ - name: Install cross compiler
+ if: runner.os == 'Linux' && matrix.packages != ''
+ run: scripts/ci-install-apt-packages.sh ${{ matrix.packages }}
- name: Build release binary
+ env:
+ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: ${{ matrix.linker }}
run: cargo build --release --locked -p lash-cli --features fff-zlob --target ${{ matrix.target }}
diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml
new file mode 100644
index 0000000..15c07c6
--- /dev/null
+++ b/.github/workflows/docs-pages.yml
@@ -0,0 +1,34 @@
+name: Docs Pages
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "docs/**"
+ - ".github/workflows/docs-pages.yml"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: lash-cli-pages
+ cancel-in-progress: true
+
+jobs:
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/configure-pages@v6
+ - uses: actions/upload-pages-artifact@v5
+ with:
+ path: docs
+ - name: Deploy documentation
+ id: deployment
+ uses: actions/deploy-pages@v5
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1a4b816..56e0a30 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,6 +12,10 @@ permissions:
contents: write
actions: read
+concurrency:
+ group: lash-cli-release-main
+ cancel-in-progress: false
+
env:
CARGO_TERM_COLOR: always
@@ -22,7 +26,7 @@ jobs:
sha: ${{ steps.release.outputs.sha }}
tag: ${{ steps.release.outputs.tag }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
with:
ref: main
fetch-depth: 0
@@ -73,7 +77,7 @@ jobs:
packages: ""
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.tag }}
- uses: dtolnay/rust-toolchain@stable
@@ -95,7 +99,7 @@ jobs:
run: cargo build --release --locked -p lash-cli --features fff-zlob --target ${{ matrix.target }}
- name: Package
run: scripts/package-release.sh ${{ matrix.target }} ${{ matrix.asset }}
- - uses: actions/upload-artifact@v4
+ - uses: actions/upload-artifact@v7
with:
name: ${{ matrix.asset }}
path: dist/${{ matrix.asset }}
@@ -104,12 +108,12 @@ jobs:
needs: [prepare, build]
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.tag }}
fetch-depth: 0
fetch-tags: true
- - uses: actions/download-artifact@v4
+ - uses: actions/download-artifact@v8
with:
path: dist
pattern: lash-*.tar.gz
@@ -123,7 +127,7 @@ jobs:
sha256sum lash-*.tar.gz > SHA256SUMS
- name: Collect release notes
run: python3 scripts/release_notes.py collect --end "${{ needs.prepare.outputs.tag }}" --out "${{ runner.temp }}/RELEASE_NOTES.md"
- - uses: softprops/action-gh-release@v2
+ - uses: softprops/action-gh-release@v3
with:
tag_name: ${{ needs.prepare.outputs.tag }}
body_path: ${{ runner.temp }}/RELEASE_NOTES.md
diff --git a/README.md b/README.md
index 5f6ad8e..c347cf2 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,10 @@ The workspace owns the `lash` executable and its private TUI, export, file
index, autoresearch, and test-harness crates. Lash itself remains an embeddable
runtime; this repository is one complete host built on top of it.
+Documentation: [CLI reference](https://samgalanakis.github.io/lash-cli/) ·
+[architecture](https://samgalanakis.github.io/lash-cli/architecture.html) ·
+[publishing](https://samgalanakis.github.io/lash-cli/publishing.html)
+
## Build
```sh
diff --git a/docs/architecture.html b/docs/architecture.html
new file mode 100644
index 0000000..298e997
--- /dev/null
+++ b/docs/architecture.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
lash-cli owns
Argument parsing, setup and stored configuration, provider materialization, concrete plugin wiring, execution-mode selection, TUI state, autonomous JSON/RPC surfaces, session navigation, export, update policy, installer assets, profiling, and binary releases.
+
Lash owns
The reusable runtime and facade, protocol implementations, provider factories, generic tools and plugins, stores, durable process machinery, Lashlang, trace contracts, and embedding documentation.
+
+
+
+
+
+
lash-cli
Binary bootstrap, configuration, providers, plugin composition, commands, session flows, autonomous modes, and terminal application state.
+
lash-tui
Terminal input, frame lifecycle, and rendering primitives.
+
lash-tui-extensions
Host-owned UI extension contracts and activity presentation.
+
lash-search-tools
FFF-backed local search and the optional Lashlang search surface.
+
lash-file-index
Ignore-aware file walking, filesystem notifications, and fuzzy file completion.
+
lash-export
Session-tree HTML and JSON export for CLI persistence and traces.
+
lash-autoresearch
CLI-owned applied workflow plugin and terminal extension.
+
harness + performance
The debug CLI harness and CLI performance helpers exercise product behavior without becoming runtime SDK contracts.
+
+
+
+
+ arguments + ~/.lash/config.json
+ │
+ ▼
+provider factories + plugin factories + store
+ │
+ ▼
+ LashCore / LashSession
+ │
+ ┌──────┴────────┐
+ ▼ ▼
+interactive TUI --print / JSON / RPC
+ Execution modes such as standard and rlm are Lash protocol plugins. The CLI chooses and configures one for a session; it does not implement protocol semantics itself.
+
+
+
+ lash-cli may depend on published or Git-pinned Lash SDK crates. Lash must not depend back on CLI crates. A second application that needs CLI-private code is evidence for extracting a focused SDK capability—not for making applications depend on one another.
+
+
+
+
+
+
$ lash
+# Setup wizard:
+# 1. Choose a provider (Anthropic, OpenAI, OpenAI-compatible, Codex, Google OAuth)
+# 2. Paste API key (or run OAuth / device-code flow)
+# 3. Optionally paste a Tavily API key for the built-in web-search tool
+# 4. Drop into the interactive TUI
+
Re-run the wizard at any time with lash --provider. Use /provider from inside a session to switch providers without restarting.
+
+
+
+
+
+
+ | Command | Aliases | Usage | Description |
+
+ /clear | /new | /clear | Reset conversation |
+ /compact | — | /compact [focus instructions] | Open a compaction Agent Frame seeded by a summary |
+ /controls | — | /controls | Show keyboard shortcuts |
+ /fork | — | /fork | Open a forked session in a new terminal |
+ /tree | — | /tree | Browse and switch branches in the current session |
+ /version | — | /version | Show lash-cli and lash-sansio versions |
+ /info | /status | /info | Show current session/runtime info |
+ /model | — | /model [name] | Show or switch the LLM model |
+ /variant | — | /variant [name] | Show or switch the model variant (e.g. high, max) |
+ /mode | — | /mode [name] | Show the session's execution mode (standard / rlm); it is locked for the session, so changing it requires a new session started with --execution-mode |
+ /provider | /login | /provider | Switch, add, or re-authenticate providers |
+ /logout | — | /logout | Remove stored credentials for active provider |
+ /retry | — | /retry | Replay the previous turn payload |
+ /resume | /continue | /resume [id-or-name] | Search sessions or resume by id/name (interactive picker if no arg) |
+ /skills | — | /skills | Browse loaded skills |
+ /help | /? | /help | Open the commands and shortcuts overlay |
+ /exit | /quit | /exit | Quit |
+
+
+
+
+
+
+
+
+
+ History navigation
+
+ - PageUp / PageDown
- Scroll history or the active document overlay.
+ - ↑ / ↓
- Cycle the input-draft history when not navigating a suggestion popup.
+
+
+
+ Prompt editing
+
+ - Enter
- Submit immediately when idle; while a turn is active, inject at the earliest safe runtime boundary.
+ - Tab
- Accept an autocomplete suggestion when the popup is open; with a draft, submit immediately when idle or queue a next-turn draft while active.
+ - Ctrl+U
- Delete draft text from the cursor to the start of the line.
+ - Ctrl+K
- Delete draft text from the cursor to the end of the line.
+ - Ctrl+← / Ctrl+→
- Move the cursor by word.
+ - Home / End
- Jump to the start / end of the prompt.
+
+
+
+ Selection & clipboard
+
+ - Ctrl+Shift+C
- Copy the current selection or the last assistant response. Configure via
LASH_COPY_BINDING if the default conflicts with your terminal.
+ - Ctrl+C
- Close a popup or overlay, cancel the active turn, clear a non-empty draft, or quit from an idle empty prompt.
+ - Esc
- Close overlays, pickers, and modal flows.
+
+
+
+ Command palette
+
+ - Ctrl+P
- Open the command palette settings overlay: a searchable list of Settings actions (theme, model, variant, provider, execution mode, logout) and Session actions (new, resume, tree, fork), each annotated with the equivalent slash command.
+
+
+
+ Pickers & multi-select
+
+ - ↑ / ↓ or k / j
- Move selection.
+ - Space
- Toggle a multi-select option when supported.
+ - Tab / BackTab
- Add / remove a note on a selection when supported.
+
+
+
+ Background process dock
+
+ - Tab / Shift+Tab
- With an empty prompt, cycle focus through the dock of background processes. The dock lists only processes granted to the current session.
+ - Enter
- Open an overview of the focused process.
+ - Delete
- Cancel the focused process.
+
+ A running process shows running; a terminal one shows its outcome — success, error, cancelled, or abandoned. abandoned (ADR 0019) means the owner was lost without recording an outcome and renders in the failure style, like error. A detached command (shell.start with detach: true) never appears in the dock: it returns immediately with status: "detached" and is owned by the host/OS, not tracked as a session process.
+
+
+
+
+
+
+
+
+ Provider & model
+
+ --provider- Re-run the provider setup wizard.
+ --model <name>- Override the default model for this session.
+ --variant <name>- Override the model-native variant (e.g.
high, max, xhigh).
+ --api-key <key>- One-shot API key for OpenAI or OpenAI-compatible providers.
+ --base-url <url>- Base URL for the LLM API (OpenAI-compatible).
+ --tavily-api-key <key> (env TAVILY_API_KEY)- Tavily key for the bundled web-search tool.
+
+
+
+ Execution
+
+ --execution-mode <mode>standard (default) or rlm.
+ -c, --context-approach <approach>- For standard mode:
rolling_history or observational_memory.
+ --rlm-var name=<json>- Repeatable. Project a read-only bound variable into the RLM autonomous turn.
+ --rlm-vars-file <path>- Same as above but load the projections from a JSON object file.
+ --rlm-termination <mode>- Choose
natural (default) or require an explicit structured finish with finish-required.
+
+
+
+ Session
+
+ --resume <id_or_name>- Resume by session id, name, or legacy
.db filename.
+ --resume-prompt <prompt>- Send a prompt right after resume.
+
+
+
+ Output modes
+
+ -p, --print <prompt>- Autonomous: run the prompt once, write the answer to stdout, exit. See Print mode below.
+ --mode <interactive|json|rpc>- Select the host surface.
interactive is the TUI default; json requires --print and writes JSONL events to stdout; rpc reads LF-delimited requests from stdin and writes LF-delimited records to stdout.
+ --await-background-work- In autonomous mode, wait for plugin background work before exiting.
+ --turn-usage-json <path>- In autonomous mode, write per-turn and cumulative usage JSON to a file.
+
+
+
+ Debug & tracing
+
+ --debug- Detailed lifecycle/debug logs plus per-session LLM traces.
+ --trace-level <standard|extended>- Trace detail. Default
standard.
+ --debug-ui-trace trace.json- Record the live TUI session as a replayable UI trace plus a final snapshot.
+ --debug-ui-trace-interval-ms <ms>- When recording a UI trace, also capture numbered checkpoint snapshots at the given interval.
+
+
+
+ Observational memory
+
+ --om-observation-message-tokens <tokens>- Observe recent raw history after this many new message tokens.
+ --om-observation-buffer-tokens <tokens>- Keep this much message-token slack before observation pressure starts.
+ --om-observation-block-after-tokens <tokens>- Block new turns once unobserved message tokens pass this threshold.
+ --om-observation-max-tokens-per-batch <tokens>- Cap the raw-history slice sent to one observer pass.
+ --om-previous-observer-tokens <tokens>- Budget previous observer notes included in a fresh observation pass.
+ --om-reflection-observation-tokens <tokens>- Target observation-token volume before reflection becomes useful.
+ --om-reflection-buffer-activation-percent <percent>- Start reflection when the reflection buffer reaches this percentage.
+ --om-reflection-block-after-tokens <tokens>- Block new turns once pending reflection material passes this threshold.
+
+
+
+ Admin
+
+ --reset- Delete
~/.lash/ and ~/.cache/lash/ and exit.
+ --info- Print current config / runtime info and exit.
+ --check-update / --update- Check / install the latest release.
+ --export <db>- Export a persisted session database. Combine with
--export-format html|json, --export-trace <jsonl>, and --export-out <path>.
+
+
+
+
+
+
+
+
$ lash --print "Summarise this branch's changes in two bullets."
+# Assistant prose to stdout, tool/lifecycle logs to stderr:
+# [tool] exec_command · ok · 41ms
+# [tool] read_file · ok
+
+$ lash --print "Investigate the latest test failure." \
+ --await-background-work \
+ --turn-usage-json out/usage.json
+
+
+
+
+
$ lash --mode json --print "Summarise this repository."
+{"type":"turn_start","protocol_version":1,"stream_id":1}
+{"type":"event","stream_id":1,"activity":{"id":"a1","correlation_id":"c1","type":"model_request_started","protocol_iteration":0}}
+{"type":"event","stream_id":1,"activity":{"id":"a2","correlation_id":"call-3","type":"tool_call_completed","call_id":"call-3","name":"exec_command","args":{"...":"..."},"output":{"...":"..."},"duration_ms":41}}
+{"type":"turn_finish","stream_id":1,"ok":true,"cancelled":false,"assistant_text":"...","outcome":{...},"usage":{...},"children_usage":[],"errors":[],"execution":{...},"tool_calls":[...]}
+
Each event wraps one TurnActivity: an id, a correlation_id, and a flattened TurnEvent tagged by type. Tool events report the runtime tool name (exec_command), not the shell.exec surface path; see the Lash SDK's event vocabulary and stability contract.
+
RPC mode starts a reusable session and reads one JSON object per line from stdin. It opens with a ready record ({"type":"ready","protocol_version":1,"protocol":"lash.rpc.v1","methods":["prompt","ping","shutdown"]}). A prompt request streams turn_start, event, and turn_finish records tagged with the request id, then writes a final response.
+
{"id":1,"method":"prompt","params":{"prompt":"List the top-level crates."}}
+{"id":2,"method":"shutdown"}
+
+
+
+
+
{
+ "active_provider": "openai",
+ "providers": {
+ "openai": { "type": "openai", "api_key": "sk-..." },
+ "anthropic": {
+ "type": "anthropic",
+ "api_key": "sk-ant-...",
+ "options": {
+ "cache_retention": "long",
+ "max_output_tokens": 16384
+ }
+ }
+ },
+ "auxiliary_secrets": {
+ "tavily_api_key": "tvly-..."
+ },
+ "mcp_servers": {
+ "docs": {
+ "transport": "stdio",
+ "command": "uvx",
+ "args": ["mcp-server-docs"]
+ }
+ },
+ "agent_models": {},
+ "model_defaults": {
+ "openai": { "model": "openai/gpt-5.5", "variant": "medium" }
+ }
+}
+
+
+ | Field | Type | Purpose |
+
+ active_provider | string | Key of the currently active provider; must exist in providers. |
+ providers | map | Per-provider credentials and metadata, keyed by provider kind. Shared provider tuning lives under options, including cache_retention (none | short | long) and max_output_tokens. |
+ auxiliary_secrets | object | Secrets for non-LLM services. Currently tavily_api_key. |
+ mcp_servers | map | MCP server configs by name. Discriminated by transport: stdio, streamable_http, or sse. |
+ agent_models | map | User-overridable model names per subagent capability. |
+ model_defaults | map | Fresh-session model + variant default per provider kind. Session resumes ignore this and use the persisted head. |
+
+
+
+
+
+
+
+
+
+ | Variable | Purpose |
+
+ LASH_HOME | Override the root data directory (defaults to ~/.lash/). |
+ OPENAI_API_KEY / OPENAI_COMPATIBLE_API_KEY | Provider API keys used during setup or one-shot sessions when no stored key is configured. |
+ OPENROUTER_API_KEY | OpenRouter API key accepted as an OpenAI-compatible shortcut when --base-url points at https://openrouter.ai/api/v1. |
+ TAVILY_API_KEY | Tavily API key for the bundled web search tool. Alternative to --tavily-api-key and the config file. |
+ LASH_GOOGLE_CLIENT_ID / LASH_GOOGLE_CLIENT_SECRET | Override the OAuth client credentials used by the Google / Code Assist provider. |
+ CODE_ASSIST_ENDPOINT / CODE_ASSIST_API_VERSION | Override the Google Code Assist API endpoint and version for provider integration testing. |
+ LASH_COPY_BINDING | Override the copy keystroke: ctrl-shift-c (default) or ctrl-y. Ctrl+C stays reserved for cancel semantics, so ctrl-c falls back to the default. |
+ LASH_LOG | tracing-subscriber log filter (e.g. debug, warn, lash=trace). |
+ LASH_NO_OSC52 | Disable OSC 52 clipboard escape sequences. |
+ LASH_INSTALL_DIR | Override the install dir used by --update. |
+ LASH_REPO | Override the GitHub repo used by --check-update / --update. |
+ LASH_TOKIO_STACK_BYTES | Override the worker-thread stack size used by the CLI Tokio runtime. |
+ GOOGLE_CLOUD_PROJECT[_ID] | Google Cloud project ID for the Gemini / Code Assist provider. |
+
+
+
+
+
+
+
+
/fork branches the current session at the current message and opens the fork in a new terminal window. Lash resolves the launcher from the running executable and falls back to finding lash on $PATH if that binary isn't launchable. The launched fork runs lash --resume <new-session-id> in the new terminal.
+
+
+
+
+
+
+
+ - Merge a focused pull request into
main after required CI passes.
+ - Ensure the releasable commit range contains a
Release-Notes: commit section.
+ - Dispatch the repository's Release workflow. Leave
release_sha blank for current main, or provide a green ancestor commit.
+ - The workflow validates ancestry and successful push CI, creates the next tag if needed, stamps the binary version, builds every supported target, and publishes the release.
+
+ Do not create release tags or upload artifacts by hand.
+
+
+
+
+
Binaries
lash-linux-x86_64.tar.gz, lash-linux-aarch64.tar.gz, and lash-macos-aarch64.tar.gz.
+
Integrity + install
SHA256SUMS covers the binary archives. install_lash.sh is rendered for the exact release tag and installs the matching asset.
+
+
+
+
+ The CLI records one exact Lash Git revision in its root Cargo.toml. Updating that revision is an explicit compatibility change with its own checks. CLI and SDK version numbers do not need to advance together.
+
+
+