diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5b327da..8c8236f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -67,3 +67,21 @@ jobs:
with:
files: dist/*
fail_on_unmatched_files: true
+
+ install-smoke:
+ # Post-release verification: the tagged release must already exist before the
+ # tarballs are downloadable, so this runs after `release` publishes and is
+ # informational (not a merge gate). Exercises install.sh's full path:
+ # asset resolution → download → SHA256 verify → extract → install → --verify.
+ name: Install smoke (linux)
+ needs: release
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install via install.sh against the tagged release
+ run: |
+ TAG="${GITHUB_REF#refs/tags/}"
+ bash install.sh --ref "${TAG}" --verify --no-mcp --quiet
+ export PATH="$HOME/.local/bin:$PATH"
+ "$HOME/.local/bin/lksr" --version
+ "$HOME/.local/bin/lksr" guide | head -5
diff --git a/README.md b/README.md
index ff5e83b..9e94f0a 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,8 @@
-
-
+
+
[](https://opensource.org/licenses/MIT)
[](https://github.com/quangdang46/livekit_agent_simulator/actions/workflows/ci.yml)
[](https://github.com/quangdang46/livekit_agent_simulator/releases)
@@ -15,7 +15,7 @@
**Dial any LiveKit voice agent with an AI simulated caller — WebRTC room, inbound SIP, or outbound SIP — and keep a full forensic log.**
-Standalone MCP server + CLI (`lks`). Black-box testing: no imports from the agent under test, no edits to its code or `.env`.
+Standalone MCP server + CLI (`lksr`, the Rust build). Black-box testing: no imports from the agent under test, no edits to its code or `.env`.
Quick Install
@@ -32,10 +32,10 @@ curl -fsSL "https://raw.githubusercontent.com/quangdang46/livekit_agent_simulato
Paste into Claude Code, Cursor, Codex, AmpCode, Windsurf, or any coding agent **from the repo you want to test**:
```text
-Install and configure livekit-agent-simulator (CLI: lks) for this project by following the instructions here:
+Install and configure livekit-agent-simulator (CLI: lksr) for this project by following the instructions here:
https://raw.githubusercontent.com/quangdang46/livekit_agent_simulator/main/docs/guide/installation.md
-Target project root is this workspace. Use absolute --root paths. Install the portable CLI if missing, run lks init, help fill .agent-sim/config.yaml from my local env or ask me for LiveKit + active caller provider key (Gemini Live or OpenAI Realtime) + agent_name, ensure .agent-sim is gitignored, run preflight, and stop before execute if the voice agent worker is not running. Do not edit agent application source outside .agent-sim/.
+Target project root is this workspace. Use absolute --root paths. Install the CLI if missing, run lksr init, help fill .agent-sim/config.yaml from my local env or ask me for LiveKit + active caller provider key (Gemini Live or OpenAI Realtime) + agent_name, ensure .agent-sim is gitignored, run preflight, and stop before execute if the voice agent worker is not running. Do not edit agent application source outside .agent-sim/.
```
Same idea, one line:
@@ -65,7 +65,7 @@ Voice agents fail in ways unit tests never see:
| Surface | What you get |
|---------|--------------|
-| `lks` CLI | init → preflight → execute → report → web |
+| `lksr` CLI | init → preflight → execute → report → web |
| MCP server | Same ops for Claude Code, Cursor, Codex, … |
| Transport modes | `webrtc_sim` · `inbound_sip` · `outbound_human_pickup` · `outbound_sim_callee` (optional `agent_dials`) |
| Reports | `events.jsonl`, `timeline.md`, `summary.json`, optional stereo WAV |
@@ -93,13 +93,13 @@ curl -fsSL "https://raw.githubusercontent.com/quangdang46/livekit_agent_simulato
| bash -s -- --verify
# In the repo you want to test (agent worker must already be running)
-lks init --root /path/to/target
+lksr init --root /path/to/target
# edit /path/to/target/.agent-sim/config.yaml (LiveKit + active provider keys, agent_name)
-lks preflight --root /path/to/target
-lks execute smoke-hello --root /path/to/target
-lks report
--root /path/to/target
-lks web --root /path/to/target # Ctrl+C to stop
+lksr preflight --root /path/to/target
+lksr execute smoke-hello --root /path/to/target
+lksr report --root /path/to/target
+lksr web --root /path/to/target # Ctrl+C to stop
```
---
@@ -183,7 +183,7 @@ Mode details and config: [docs/telephony.md](docs/telephony.md). Templates: `inb
### Quick install (recommended)
-**Download only — no uv/pip/build on your machine.** CI ships a portable pack (embedded Python + deps + report player).
+**Download only — no uv/pip/build on your machine.** CI ships a single static `lksr` binary (SHA256-verified).
```bash
# macOS / Linux
@@ -192,26 +192,20 @@ curl -fsSL "https://raw.githubusercontent.com/quangdang46/livekit_agent_simulato
```
```powershell
-# Windows PowerShell
+# Windows PowerShell (installs the Python build — a Rust Windows binary is not yet shipped)
irm "https://raw.githubusercontent.com/quangdang46/livekit_agent_simulator/main/install.ps1" -OutFile "$env:TEMP\lks-install.ps1"
powershell -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\lks-install.ps1" -Verify
```
-Also available from a release asset:
-
-```bash
-curl -fsSL "https://github.com/quangdang46/livekit_agent_simulator/releases/download/v0.1.0/install.sh" \
- | bash -s -- --verify
-```
-
| Flag | Purpose |
|------|---------|
-| `--verify` | Checksum verification |
-| `--ref v0.1.0` | Pin release tag |
+| `--verify` | Post-install check (`lksr --version` + `lksr guide`) |
+| `--ref v0.1.0-rust` | Pin release tag |
| `--no-mcp` | Skip MCP registration into coding tools |
+| `--easy-mode` | Append install dir to shell `PATH` in rc files |
| `--uninstall` | Remove install |
-By default the installer registers the MCP server `livekit-agent-simulator` (`lks mcp`) into detected tools: Claude Code, Cursor, Cline, Windsurf, VS Code Copilot, Gemini CLI, Amazon Q, OpenCode, Codex, Warp.
+By default the installer registers the MCP server `livekit-agent-simulator` (`lksr mcp`) into detected tools: Claude Code, Cursor, Cline, Windsurf, VS Code Copilot, Gemini CLI, Amazon Q, OpenCode, Codex, Warp.
**Agent-oriented install playbook (long form):** [docs/guide/installation.md](docs/guide/installation.md)
Raw URL for paste into agents:
@@ -235,7 +229,7 @@ Users never build this — CI packs `web/dist` into the wheel as `web_static`. S
```bash
pnpm --dir web install
pnpm --dir web build # → web/dist/ (attached by Hatch on uv build)
-pnpm --dir web dev # HMR; proxy /api + /runs → lks web :8765
+pnpm --dir web dev # HMR; proxy /api + /runs → lksr web :8765
```
See `web/README.md`.
@@ -246,17 +240,17 @@ See `web/README.md`.
```bash
# Agent worker must be running and registered with LiveKit
-lks guide
-lks init --root /path/to/target
+lksr guide
+lksr init --root /path/to/target
# fill .agent-sim/config.yaml
-lks preflight --root /path/to/target
-lks scenario-init smoke-hello --root /path/to/target # if needed
-lks validate smoke-hello --root /path/to/target
-lks execute smoke-hello --root /path/to/target
-lks runs --root /path/to/target
-lks report --root /path/to/target
-lks web --root /path/to/target
+lksr preflight --root /path/to/target
+lksr scenario-init smoke-hello --root /path/to/target # if needed
+lksr validate smoke-hello --root /path/to/target
+lksr execute smoke-hello --root /path/to/target
+lksr runs --root /path/to/target
+lksr report --root /path/to/target
+lksr web --root /path/to/target
```
### Minimal scenario (`smoke-hello`)
@@ -285,7 +279,7 @@ pass_criteria:
- The agent responded in the caller's language
```
-Optional multi-judge PassCriteria: `judges[]` + `mode` (`all` \| `majority` \| `any`). Assert highlights (`tool_order`, `constraint_respected`, recovery/latency): `lks guide`.
+Optional multi-judge PassCriteria: `judges[]` + `mode` (`all` \| `majority` \| `any`). Assert highlights (`tool_order`, `constraint_respected`, recovery/latency): `lksr guide`.
Full-line `#` comments in scaffolded YAML are guides — runtime ignores them. Legacy `*.jsonl` scenarios are still read.
@@ -341,9 +335,9 @@ simulator:
```
```bash
-lks execute smoke-hello # `gemini` (marked default: true)
-lks execute smoke-hello --profile gemini # Gemini Live caller
-lks execute smoke-hello --profile openai # OpenAI Realtime caller
+lksr execute smoke-hello # `gemini` (marked default: true)
+lksr execute smoke-hello --profile gemini # Gemini Live caller
+lksr execute smoke-hello --profile openai # OpenAI Realtime caller
```
**Selection** (`--profile` absent): if **exactly one** profile has
@@ -374,7 +368,7 @@ block (backward compatible).
## Commands
-CLI and MCP share the same public ops (`ops.py`). Prefer `execute` (validate then run).
+CLI and MCP share the same public ops. Prefer `execute` (validate then run). All commands below run as `lksr `. MCP tool names are identical (accessed via the `livekit-agent-simulator` server).
| CLI | MCP tool | Purpose |
|-----|----------|---------|
@@ -401,15 +395,15 @@ CLI and MCP share the same public ops (`ops.py`). Prefer `execute` (validate the
| `mcp` | — | Start MCP server (stdio) |
```bash
-lks execute smoke-hello --root /path/to/target
-lks execute-all --tag smoke --root /path/to/target
-lks serve --root /path/to/target # REST API on :8787 (same ops as CLI/MCP)
-lks log --root /path/to/target
-lks compare --root /path/to/target
-lks compare --baseline --root /path/to/target
-lks optimize scen-a,scen-b --held-out scen-c --root /path/to/target # → optimized//
-lks execute scen-a --optimized --root /path/to/target # apply the winner
-lks web --port 8765 --root /path/to/target
+lksr execute smoke-hello --root /path/to/target
+lksr execute-all --tag smoke --root /path/to/target
+lksr serve --root /path/to/target # REST API on :8787 (same ops as CLI/MCP)
+lksr log --root /path/to/target
+lksr compare --root /path/to/target
+lksr compare --baseline --root /path/to/target
+lksr optimize scen-a,scen-b --held-out scen-c --root /path/to/target # → optimized//
+lksr execute scen-a --optimized --root /path/to/target # apply the winner
+lksr web --port 8765 --root /path/to/target
```
Every MCP tool needs `project_root` **except** `guide`.
@@ -424,9 +418,9 @@ the MCP tools return. Single-dict commands (`init`, `export`, `convert`,
`scenario-init`, `scenario-from-run`, `guide`, `web`) always print JSON.
```bash
-lks scenarios # human table
-lks scenarios --json # raw JSON for scripts / CI / agents
-lks execute-all --json | jq '.suite' # pipe JSON to jq
+lksr scenarios # human table
+lksr scenarios --json # raw JSON for scripts / CI / agents
+lksr execute-all --json | jq '.suite' # pipe JSON to jq
```
**Agents & CI:** use `--json` — the default table is for humans.
@@ -439,7 +433,7 @@ Installer writes this when tools are detected. Manual Cursor:
{
"mcpServers": {
"livekit-agent-simulator": {
- "command": "lks",
+ "command": "lksr",
"args": ["mcp"],
"env": {}
}
@@ -447,20 +441,20 @@ Installer writes this when tools are detected. Manual Cursor:
}
```
-Dev checkout (package not installed globally):
+Dev checkout (Rust build from source, not installed globally):
```json
{
"mcpServers": {
"livekit-agent-simulator": {
- "command": "uv",
- "args": ["run", "--directory", "/abs/path/livekit-agent-simulator", "lks", "mcp"]
+ "command": "cargo",
+ "args": ["run", "--manifest-path", "/abs/path/livekit-agent-simulator/src/livekit_agent_simulator_rust/crates/lks/Cargo.toml", "--", "mcp"]
}
}
}
```
-Equivalent one-shot entry: `lks-mcp` (same process as `lks mcp`).
+Equivalent one-shot entry: `lksr mcp`.
---
@@ -494,8 +488,8 @@ src/livekit_agent_simulator/
| Workflow | Trigger | What it does |
|----------|---------|--------------|
-| [CI](.github/workflows/ci.yml) | PR / push → `main` | web UI build, `pytest` (3.10 + 3.12), `lks --help` |
-| [Release](.github/workflows/release.yml) | tag `v*` | pytest → wheel → portable packs (win/linux/mac) → GitHub Release |
+| [Rust CI](.github/workflows/rust-ci.yml) | PR / push → `main` | `cargo fmt`/`clippy`/`test` on the Rust workspace (offline; Windows `cargo check` only) |
+| [Release](.github/workflows/release.yml) | tag `v*` | Build `lksr` release binaries (macOS aarch64 + Linux x86_64), tarballs + `SHA256SUMS.txt` → GitHub Release; post-release install smoke |
```bash
# Local check
@@ -515,10 +509,10 @@ git push origin v0.1.0
### `preflight` fails connectivity
```bash
-lks preflight --root /path/to/target
+lksr preflight --root /path/to/target
# Confirm livekit.url / api_key / api_secret and that the project is reachable.
# Skip API check while editing config:
-lks preflight --no-connectivity --root /path/to/target
+lksr preflight --no-connectivity --root /path/to/target
```
### Agent never joins the room
@@ -536,13 +530,13 @@ Set `simulator.api_key` in `.agent-sim/config.yaml` for the active `simulator.pr
With `observe.record_audio` enabled (default `true`): `reports//conversation.wav`
```bash
-lks web --root /path/to/target
+lksr web --root /path/to/target
```
### MCP tools not listed
```bash
-lks mcp # must be what the host launches
+lksr mcp # must be what the host launches
# or reinstall without --no-mcp
curl -fsSL "https://raw.githubusercontent.com/quangdang46/livekit_agent_simulator/main/install.sh?$(date +%s)" \
| bash -s -- --verify
@@ -551,8 +545,8 @@ curl -fsSL "https://raw.githubusercontent.com/quangdang46/livekit_agent_simulato
### Scenario validation errors
```bash
-lks validate my-case --root /path/to/target
-lks scenario-init my-case --root /path/to/target # fresh scaffold with // guides
+lksr validate my-case --root /path/to/target
+lksr scenario-init my-case --root /path/to/target # fresh scaffold with // guides
```
---
@@ -593,7 +587,7 @@ Same ops. Use CLI in terminals/CI; MCP inside coding agents. Prefer `execute_*`
### Can I assert on tool calls?
-Yes — `Assert.spec.tools`, **`tool_order`** (required `tool.start` subsequence), `observe.tool_event_patterns`, Script/assert plugins, and/or PassCriteria + judge. See [`docs/plugins.md`](docs/plugins.md) and `lks guide`.
+Yes — `Assert.spec.tools`, **`tool_order`** (required `tool.start` subsequence), `observe.tool_event_patterns`, Script/assert plugins, and/or PassCriteria + judge. See [`docs/plugins.md`](docs/plugins.md) and `lksr guide`.
### Where are reports stored?
@@ -601,7 +595,7 @@ Yes — `Assert.spec.tools`, **`tool_order`** (required `tool.start` subsequence
### Is the report player separate?
-No — `lks web` serves the prebuilt player from the install pack. Maintainers build from `web/`.
+No — `lksr web` serves the prebuilt player from the install pack. Maintainers build from `web/`.
---
@@ -615,7 +609,7 @@ No — `lks web` serves the prebuilt player from the install pack. Maintainers b
| [docs/plugins.md](docs/plugins.md) | Verify plugins + Python API |
| [docs/telephony.md](docs/telephony.md) | SIP modes + outbound_sim_callee preflight |
| [docs/interrupt-scenario-matrix.md](docs/interrupt-scenario-matrix.md) | Barge / backchannel / noise authoring |
-| `lks guide` | On-demand setup/ops guide (Assert, compare --baseline, PassCriteria) |
+| `lksr guide` | On-demand setup/ops guide (Assert, compare --baseline, PassCriteria) |
---
diff --git a/docs/guide/installation.md b/docs/guide/installation.md
index ebe99dc..58880e5 100644
--- a/docs/guide/installation.md
+++ b/docs/guide/installation.md
@@ -1,8 +1,8 @@
-# Agent install guide — livekit-agent-simulator (`lks`)
+# Agent install guide — livekit-agent-simulator (`lksr`)
**Audience:** coding agents (Claude Code, Cursor, Codex, AmpCode, Windsurf, …) and humans who paste this URL into an agent.
-**Goal:** install the `lks` CLI on the user’s machine, scaffold `.agent-sim/` inside **the user’s project repo** (the LiveKit voice agent under test), fill config safely, register MCP if useful, and prove the setup with `preflight` (and optionally a smoke run).
+**Goal:** install the `lksr` CLI on the user’s machine, scaffold `.agent-sim/` inside **the user’s project repo** (the LiveKit voice agent under test), fill config safely, register MCP if useful, and prove the setup with `preflight` (and optionally a smoke run). On Windows the installed CLI is still `lks` (the Python build).
**Hard rules for the agent:**
@@ -12,7 +12,7 @@
4. **Discover before `AskQuestion`.** Read target docs and existing `.agent-sim/` (read-only). Do not assume consumer-specific file paths or metadata keys (e.g. one repo’s `job-metadata.ts` or `yourProjectKey` is not universal).
5. Prefer **non-interactive** commands. Use `--root ` always.
6. Prefer the **portable installer** (no uv/pip on the user machine) unless the user is developing the simulator package itself.
-7. **CLI is `lks`**. Runtime IDs: sim participant `lks-caller` / room prefix `lks-`.
+7. **CLI is `lksr`** on macOS/Linux (Rust build); **`lks`** on Windows (Python build). Runtime IDs: sim participant `lks-caller` / room prefix `lks-`.
---
@@ -20,8 +20,8 @@
| Piece | Purpose |
|-------|---------|
-| CLI `lks` | Black-box LiveKit room tester + report player |
-| MCP server `livekit-agent-simulator` | Same ops for coding agents (`lks mcp`) |
+| CLI `lksr` (macOS/Linux) / `lks` (Windows) | Black-box LiveKit room tester + report player |
+| MCP server `livekit-agent-simulator` | Same ops for coding agents (`lksr mcp` / `lks mcp`) |
| Target folder `.agent-sim/` | Config, scenarios, reports, local cues/plugins (gitignored) |
The simulator joins LiveKit as `lks-caller`, talks to the user’s already-running agent under test via Gemini Live, and writes forensic reports.
@@ -30,7 +30,8 @@ The simulator joins LiveKit as `lks-caller`, talks to the user’s already-runni
| Need | Why |
|------|-----|
-| macOS / Linux / Windows | Portable packs ship for these |
+| macOS / Linux | Install the Rust `lksr` binary via `install.sh` |
+| Windows | Installs the Python `lks` build via `install.ps1` (a Rust Windows binary is not yet shipped) |
| Network access | Download release + LiveKit + Gemini |
| A **running** LiveKit agent under test | Registered with a known `agent_name` |
| LiveKit Cloud (or self-host) URL + API key/secret | Room create + dispatch |
@@ -48,8 +49,8 @@ macOS / Linux (bash):
```bash
uname -s
-which lks || true
-lks --help 2>/dev/null | head -5 || true
+which lksr || true
+lksr --version 2>/dev/null || true
pwd
# If the user said "this project", use the current workspace root as TARGET_ROOT
```
@@ -77,13 +78,13 @@ $TARGET_ROOT = (Get-Location).Path # or the path the user named
# Optional pin: $env:LK_SIM_REF = "v0.1.0"
```
-If `lks --help` already works, skip §2 install and go to §3 init.
+If `lksr --version` (macOS/Linux) or `lks --help` (Windows) already works, skip §2 install and go to §3 init.
---
-## 2. Install `lks` (portable)
+## 2. Install `lksr` / `lks`
-### macOS / Linux
+### macOS / Linux (Rust binary)
```bash
curl -fsSL "https://raw.githubusercontent.com/quangdang46/livekit-agent-simulator/main/install.sh?$(date +%s)" \
@@ -94,30 +95,30 @@ Pin a release (recommended for CI / reproducible agent setups):
```bash
curl -fsSL "https://raw.githubusercontent.com/quangdang46/livekit-agent-simulator/main/install.sh?$(date +%s)" \
- | bash -s -- --ref v0.1.0 --verify
+ | bash -s -- --ref v0.1.0-rust --verify
```
Flags:
| Flag | Meaning |
|------|---------|
-| `--verify` | Run post-install check (`lks --help`) |
+| `--verify` | Run post-install check (`lksr --version` + `lksr guide`) |
| `--ref vX.Y.Z` / `--version` | Pin release tag (default: latest) |
| `--no-mcp` | Skip auto MCP registration |
| `--easy-mode` | Append install dir to shell PATH in rc files |
| `--uninstall` | Remove install |
-Default binary location: `$HOME/.local/bin/lks`
-If `lks` is not found after install, ensure `~/.local/bin` is on `PATH` for this shell:
+Default binary location: `$HOME/.local/bin/lksr`
+If `lksr` is not found after install, ensure `~/.local/bin` is on `PATH` for this shell:
```bash
export PATH="$HOME/.local/bin:$PATH"
hash -r 2>/dev/null || true
-command -v lks
-lks --help | head -20
+command -v lksr
+lksr --version
```
-### Windows PowerShell
+### Windows PowerShell (Python build)
```powershell
irm "https://raw.githubusercontent.com/quangdang46/livekit-agent-simulator/main/install.ps1" -OutFile "$env:TEMP\lks-install.ps1"
@@ -147,10 +148,12 @@ Get-Command lks, lks
lks --help | Select-Object -First 20
```
+> **Windows note:** `install.ps1` still installs the **Python build** (`lks`). A Rust Windows binary is not yet shipped (tracked in the Rust-port plan §1.6). The Python build downloads a portable zip, so it needs no `uv`/`pip` on the user machine.
+
### Installer success criteria
-- `command -v lks` / `Get-Command lks` resolves
-- `lks --help` exits 0 and lists: `init`, `preflight`, `execute`, `scenario-from-run`, `web`, `mcp`, …
+- macOS/Linux: `command -v lksr` resolves; `lksr --version` prints a version
+- Windows: `Get-Command lks` resolves; `lks --help` exits 0 and lists: `init`, `preflight`, `execute`, `scenario-from-run`, `web`, `mcp`, …
- Prefer not to use `uv run` / `pip install` for end users
### From source (only if user is developing the simulator package)
@@ -169,7 +172,7 @@ uv run lks init --root /abs/path/to/target
## 3. Init project scaffold in the user’s repo
```bash
-lks init --root "$TARGET_ROOT"
+lksr init --root "$TARGET_ROOT"
```
This creates (if missing):
@@ -183,7 +186,7 @@ $TARGET_ROOT/.agent-sim/
cues/README.md
```
-Plugin API (verify + lifecycle): [plugins.md](../plugins.md). Ops detail: `lks guide`.
+Plugin API (verify + lifecycle): [plugins.md](../plugins.md). Ops detail: `lksr guide`.
Re-running `init` also ensures `.agent-sim/` is listed in `$TARGET_ROOT/.gitignore`.
**Idempotent:** re-running `init` does **not** overwrite existing `config.yaml`, scenarios, or reports — only creates missing scaffold files.
@@ -194,7 +197,7 @@ Re-running `init` also ensures `.agent-sim/` is listed in `$TARGET_ROOT/.gitigno
test -f "$TARGET_ROOT/.agent-sim/config.yaml"
test -d "$TARGET_ROOT/.agent-sim/scenarios"
grep -q '\.agent-sim/' "$TARGET_ROOT/.gitignore" || true
-lks guide | head -5
+lksr guide | head -5
```
---
@@ -228,11 +231,11 @@ simulator:
api_key: "AQ.Ab8..." # Gemini Live key
profiles: # optional named caller profiles
- gemini: # lks execute --profile gemini
+ gemini: # lksr execute --profile gemini
default: true # auto-selected when no --profile flag
provider: google
api_key: "AQ.Ab8..."
- openai: # lks execute --profile openai
+ openai: # lksr execute --profile openai
provider: openai
api_key: "sk-..."
voice:
@@ -385,7 +388,7 @@ Rules:
},
{
"id": "record_audio",
- "prompt": "Enable local call recording for lks web replay?",
+ "prompt": "Enable local call recording for lksr web replay?",
"options": [
{"id": "yes", "label": "Yes — observe.record_audio: true (Recommended)"},
{"id": "no", "label": "No — skip conversation.wav"}
@@ -468,35 +471,35 @@ observe:
timezone: "Asia/Ho_Chi_Minh"
```
-More on dispatch / data topics: [portability.md](../portability.md). Voice / cues / plugins after setup: `lks guide`.
+More on dispatch / data topics: [portability.md](../portability.md). Voice / cues / plugins after setup: `lksr guide`.
---
## 5. MCP registration (coding agents)
-Default installer registers MCP server name **`livekit-agent-simulator`** → command `lks mcp` into detected tools (Claude Code, Cursor, Cline, Windsurf, VS Code Copilot, Gemini CLI, Amazon Q, OpenCode, Codex, Warp).
+Default installer registers MCP server name **`livekit-agent-simulator`** → command `lksr mcp` (macOS/Linux) or `lks mcp` (Windows) into detected tools (Claude Code, Cursor, Cline, Windsurf, VS Code Copilot, Gemini CLI, Amazon Q, OpenCode, Codex, Warp).
-If the user skipped MCP or tools were installed later, manual MCP config example:
+If the user skipped MCP or tools were installed later, manual MCP config example (macOS/Linux):
```json
{
"mcpServers": {
"livekit-agent-simulator": {
- "command": "lks",
+ "command": "lksr",
"args": ["mcp"]
}
}
}
```
-Dev checkout (package not on PATH):
+Dev checkout (Rust build from source, not on PATH):
```json
{
"mcpServers": {
"livekit-agent-simulator": {
- "command": "uv",
- "args": ["run", "--directory", "/abs/path/livekit-agent-simulator", "lks", "mcp"]
+ "command": "cargo",
+ "args": ["run", "--manifest-path", "/abs/path/livekit-agent-simulator/src/livekit_agent_simulator_rust/crates/lks/Cargo.toml", "--", "mcp"]
}
}
}
@@ -518,9 +521,9 @@ Typical MCP flow:
## 6. Preflight (must pass before promising a full call)
```bash
-lks preflight --root "$TARGET_ROOT"
+lksr preflight --root "$TARGET_ROOT"
# offline config-only:
-lks preflight --no-connectivity --root "$TARGET_ROOT"
+lksr preflight --no-connectivity --root "$TARGET_ROOT"
```
**Success:** JSON `ok: true` with checks for config, livekit.url, folders, active provider key, and (if connectivity on) `livekit.api` list_rooms.
@@ -529,7 +532,7 @@ Common failures:
| Symptom | Fix |
|---------|-----|
-| config missing | `lks init --root …` first |
+| config missing | `lksr init --root …` first |
| livekit.api 401 | Wrong URL / api_key / api_secret |
| agent timeout later | Agent not running or `agent_name` mismatch |
| Windows: `No module named 'encodings'` / `Could not find platform independent libraries` | Broken portable layout from older installer — run `install.ps1 -Repair -Verify` or reinstall with latest `install.ps1` |
@@ -541,9 +544,9 @@ Common failures:
List / scaffold:
```bash
-lks scenarios --root "$TARGET_ROOT"
-lks scenario-init my-case --root "$TARGET_ROOT" # scaffolds a .yaml with # guide comments
-lks validate smoke-hello --root "$TARGET_ROOT"
+lksr scenarios --root "$TARGET_ROOT"
+lksr scenario-init my-case --root "$TARGET_ROOT" # scaffolds a .yaml with # guide comments
+lksr validate smoke-hello --root "$TARGET_ROOT"
```
### Scenario knobs after setup (STT / dead-air / noise / authoring)
@@ -560,11 +563,11 @@ These are **not** required for install — use when writing scenarios under `.ag
| Script `action: wait` + `silence_after_cue_ms` | Intentional caller silence hold (suppresses freestyle). On `action: speak`, `silence_after_cue_ms` does **not** long-mute freestyle after the line. |
| Soft metrics `user_words_natural_*` | Freestyle word stats excluding Script-matched finals; prefer over overall `user_words_p50` for naturalness on Script-heavy packs |
| `noise_when: "background"` / Script `"loop": true` | Continuous ambient noise under the call |
-| `lks validate` → `authoring.tier` / `warning_codes` | Soft authoring quality gate (no LLM; does not flip `valid`) |
+| `lksr validate` → `authoring.tier` / `warning_codes` | Soft authoring quality gate (no LLM; does not flip `valid`) |
### Example scenario pack (`templates/examples/`)
-Copy the ones you need to `/.agent-sim/scenarios/`, then `lks validate --root `. Each pair (`.yaml` canonical + `.jsonl` legacy twin where present) is smoke-validated.
+Copy the ones you need to `/.agent-sim/scenarios/`, then `lksr validate --root `. Each pair (`.yaml` canonical + `.jsonl` legacy twin where present) is smoke-validated.
**Dialogue (freestyle caller):**
- `dialogue-signup-basic` — first_speaker=user, open goals
@@ -603,27 +606,27 @@ persona `traits` (length band + interaction style), and `speech_conditions.verbo
audio cues. Pair each locale scenario with a `pass_criteria` that checks the agent
responds in that language.
-Ops detail: **`lks guide`**.
+Ops detail: **`lksr guide`**.
**Agent under test must be running** and registered with the same `livekit.agent_name` before execute.
```bash
-lks execute smoke-hello --root "$TARGET_ROOT"
+lksr execute smoke-hello --root "$TARGET_ROOT"
# → .agent-sim/reports/001-smoke-hello-YYYYMMDD-HHMMSS-xxxx/ (NNN + UTC stamp; unique vs SQLite)
-lks execute smoke-hello --name demo --root "$TARGET_ROOT"
+lksr execute smoke-hello --name demo --root "$TARGET_ROOT"
# → .agent-sim/reports/002-demo-YYYYMMDD-HHMMSS-xxxx/ (--name overrides the slug after the seq prefix)
# flake control (each iteration gets its own NNN folder):
-lks execute smoke-hello --root "$TARGET_ROOT" --repeat 3 --pass-at-k 2
+lksr execute smoke-hello --root "$TARGET_ROOT" --repeat 3 --pass-at-k 2
# named caller profile (provider/key from simulator.profiles.):
-lks execute smoke-hello --profile openai --root "$TARGET_ROOT"
+lksr execute smoke-hello --profile openai --root "$TARGET_ROOT"
# suite:
-lks execute-all --tag smoke --root "$TARGET_ROOT"
-# lks execute-all --tag smoke --parallel 2 --root "$TARGET_ROOT"
-# lks execute-all --tag smoke --profile openai --root "$TARGET_ROOT"
+lksr execute-all --tag smoke --root "$TARGET_ROOT"
+# lksr execute-all --tag smoke --parallel 2 --root "$TARGET_ROOT"
+# lksr execute-all --tag smoke --profile openai --root "$TARGET_ROOT"
```
#### Risk-tier recipe (skip exploratory in CI)
@@ -634,17 +637,17 @@ want, so exploratory scenarios don't gate merges:
```bash
# CI merge gate: blocking + regression only
-lks execute-all --tag blocking --root "$TARGET_ROOT"
-lks execute-all --tag regression --root "$TARGET_ROOT"
+lksr execute-all --tag blocking --root "$TARGET_ROOT"
+lksr execute-all --tag regression --root "$TARGET_ROOT"
# nightly: include scheduled + exploratory
-lks execute-all --tag scheduled --root "$TARGET_ROOT"
-lks execute-all --tag exploratory --root "$TARGET_ROOT"
+lksr execute-all --tag scheduled --root "$TARGET_ROOT"
+lksr execute-all --tag exploratory --root "$TARGET_ROOT"
```
-`authoring.tier` from `lks validate --json` and the `risk_tags` score also surface
-these tiers; `lks execute-all --tag ` is the execution-side filter. A
-scenario with **no** risk tag is reported by `lks validate` (`no_risk_tag`).
+`authoring.tier` from `lksr validate --json` and the `risk_tags` score also surface
+these tiers; `lksr execute-all --tag ` is the execution-side filter. A
+scenario with **no** risk tag is reported by `lksr validate` (`no_risk_tag`).
`run_id` format: `{NNN}-{slug}-{YYYYMMDD}-{HHMMSS}-{xxxx}` — default slug is the scenario id;
`--name` / MCP `run_name` replaces the slug only (`scenario_id` remains in `meta.json`).
@@ -653,24 +656,24 @@ Timestamp + hex keep ids unique when a report folder was deleted but SQLite stil
Inspect:
```bash
-lks runs --root "$TARGET_ROOT"
-lks report 001-smoke-hello-20260716-144623-a1b2 --root "$TARGET_ROOT"
-lks log 001-smoke-hello-20260716-144623-a1b2 --kind "transcript.*" --root "$TARGET_ROOT"
+lksr runs --root "$TARGET_ROOT"
+lksr report 001-smoke-hello-20260716-144623-a1b2 --root "$TARGET_ROOT"
+lksr log 001-smoke-hello-20260716-144623-a1b2 --kind "transcript.*" --root "$TARGET_ROOT"
# --kind accepts one kind or one prefix (e.g. sim.script*); not a comma list
-lks web --root "$TARGET_ROOT" # http://127.0.0.1:8765 — list auto-updates ~3s; Ctrl+C to stop
+lksr web --root "$TARGET_ROOT" # http://127.0.0.1:8765 — list auto-updates ~3s; Ctrl+C to stop
# CI golden gate (exit 1 on regression):
-# lks compare --baseline --root "$TARGET_ROOT"
+# lksr compare --baseline --root "$TARGET_ROOT"
```
Promote a failure to a draft regression case:
```bash
-lks scenario-from-run 001-smoke-hello-20260716-144623-a1b2 --root "$TARGET_ROOT" # dry-run
-lks scenario-from-run 001-smoke-hello-20260716-144623-a1b2 --root "$TARGET_ROOT" --write # write .yaml draft
+lksr scenario-from-run 001-smoke-hello-20260716-144623-a1b2 --root "$TARGET_ROOT" # dry-run
+lksr scenario-from-run 001-smoke-hello-20260716-144623-a1b2 --root "$TARGET_ROOT" --write # write .yaml draft
# then human/agent reviews Persona + Assert before treating as golden
```
-Draft extract (see `lks guide` → promote section): goals/constraints (not transcript paste into brief),
+Draft extract (see `lksr guide` → promote section): goals/constraints (not transcript paste into brief),
one Behavior barge stub from run markers when present, Script open when `first_speaker=user`,
transcript sample in `Context.notes` only.
@@ -701,7 +704,7 @@ assert:
tool_order: [lookup, book]
```
-PassCriteria can use flat `criteria[]` or multi-judge `judges[]` + `mode` (`all` \| `majority` \| `any`). Full recipes: `lks guide`.
+PassCriteria can use flat `criteria[]` or multi-judge `judges[]` + `mode` (`all` \| `majority` \| `any`). Full recipes: `lksr guide`.
#### Audio-onset latency (perceived vs transcript)
@@ -777,7 +780,7 @@ pass_criteria:
criteria: [builtin:conversation_naturalness]
```
-Available presets (package `src/livekit_agent_simulator/evals/presets.py`, `lks plugins`/`list_presets`):
+Available presets (package `src/livekit_agent_simulator/evals/presets.py`, `lksr plugins`/`list_presets`):
| builtin key | What it grades |
|---|---|
@@ -817,13 +820,13 @@ Package templates (copy into `.agent-sim/scenarios/`):
```bash
# After telephony: block in config.yaml (trunk + dial_in / sim_inbound_number):
-lks validate inbound-caller-sim --root "$TARGET_ROOT"
-# lks execute inbound-caller-sim --root "$TARGET_ROOT" # needs real trunk + DID routing
+lksr validate inbound-caller-sim --root "$TARGET_ROOT"
+# lksr execute inbound-caller-sim --root "$TARGET_ROOT" # needs real trunk + DID routing
```
Mode is **only** in scenario `Caller` — never in `config.yaml`.
Guide: https://github.com/quangdang46/livekit-agent-simulator/blob/main/docs/telephony.md
-Ops detail: `lks guide` (templates/GUIDE.md).
+Ops detail: `lksr guide` (templates/GUIDE.md).
SIP asserts: `Assert.spec.sip.participant_present` / `dial_answered` / `call_status_any`.
@@ -833,17 +836,17 @@ SIP asserts: `Assert.spec.sip.participant_present` / `dial_answered` / `call_sta
Mark setup complete only when **all** of these are true:
-- [ ] `lks --help` works on PATH
+- [ ] `lksr --version` works on PATH (macOS/Linux)
- [ ] `$TARGET_ROOT/.agent-sim/config.yaml` exists with LiveKit + `agent_name` + active provider key (Gemini Live / OpenAI Realtime) set
- [ ] `.agent-sim/` is gitignored
-- [ ] `lks preflight --root "$TARGET_ROOT"` → `ok: true`
+- [ ] `lksr preflight --root "$TARGET_ROOT"` → `ok: true`
- [ ] User knows the agent under test must be running before `execute`
- [ ] Consumer dispatch metadata / `data_topics` set when discovery (§4.0) shows they are required
- [ ] (Tool scenarios) report contains `tool.*` and `session.chat_history`, with no `tool_events` observe gap
- [ ] (Optional) MCP `livekit-agent-simulator` registered if they use a coding agent
-- [ ] (Optional) `lks execute smoke-hello --root "$TARGET_ROOT"` → `status: done` or a clear next fix (agent timeout / Gemini quota)
+- [ ] (Optional) `lksr execute smoke-hello --root "$TARGET_ROOT"` → `status: done` or a clear next fix (agent timeout / Gemini quota)
- [ ] (Optional SIP) `telephony:` trunk/DID filled when testing `inbound_sip` / `outbound_human_pickup` / `outbound_sim_callee`; scenarios validated
-- [ ] (Optional) new surfaces known: `lks serve` (REST API on :8787), `lks optimize` (persona-prompt optimizer), `speech_conditions.effects` (audio degradation), `handoff` / `no_unplanned_handoff` asserts — see `lks guide`
+- [ ] (Optional) new surfaces known: `lksr serve` (REST API on :8787), `lksr optimize` (persona-prompt optimizer), `speech_conditions.effects` (audio degradation), `handoff` / `no_unplanned_handoff` asserts — see `lksr guide`
**Do not claim “fully working E2E”** if preflight failed or the agent is not registered.
@@ -868,16 +871,16 @@ export PATH="$HOME/.local/bin:$PATH"
TARGET_ROOT="$(pwd)" # change if needed
# 1) Install CLI (skip if already present)
-if ! command -v lks >/dev/null 2>&1; then
+if ! command -v lksr >/dev/null 2>&1; then
curl -fsSL "https://raw.githubusercontent.com/quangdang46/livekit-agent-simulator/main/install.sh?$(date +%s)" \
| bash -s -- --verify
export PATH="$HOME/.local/bin:$PATH"
hash -r 2>/dev/null || true
fi
-lks --help >/dev/null
+lksr --version >/dev/null
# 2) Scaffold target project
-lks init --root "$TARGET_ROOT"
+lksr init --root "$TARGET_ROOT"
# 3) STOP: discover TARGET_ROOT (§4.0), then fill $TARGET_ROOT/.agent-sim/config.yaml
# livekit.url / api_key / api_secret / agent_name
@@ -885,10 +888,10 @@ lks init --root "$TARGET_ROOT"
# optional: livekit.dispatch_metadata, observe.data_topics (from consumer docs/search)
# Then continue:
-lks preflight --root "$TARGET_ROOT"
+lksr preflight --root "$TARGET_ROOT"
# 4) Ensure agent under test is running with matching agent_name
-# lks execute smoke-hello --root "$TARGET_ROOT"
-# lks web --root "$TARGET_ROOT"
+# lksr execute smoke-hello --root "$TARGET_ROOT"
+# lksr web --root "$TARGET_ROOT"
```
### Windows (PowerShell)
@@ -928,14 +931,14 @@ lks preflight --root $TARGET_ROOT
| Resource | URL |
|----------|-----|
| Repo | https://github.com/quangdang46/livekit-agent-simulator |
-| Installer (macOS/Linux) | https://raw.githubusercontent.com/quangdang46/livekit-agent-simulator/main/install.sh |
-| Installer (Windows) | https://raw.githubusercontent.com/quangdang46/livekit-agent-simulator/main/install.ps1 |
+| Installer (macOS/Linux, Rust binary) | https://raw.githubusercontent.com/quangdang46/livekit-agent-simulator/main/install.sh |
+| Installer (Windows, Python build) | https://raw.githubusercontent.com/quangdang46/livekit-agent-simulator/main/install.ps1 |
| This guide (raw) | https://raw.githubusercontent.com/quangdang46/livekit-agent-simulator/main/docs/guide/installation.md |
-| Ops guide | package `lks guide` or `templates/GUIDE.md` (voice, cues, plugins) |
+| Ops guide | package `lksr guide` or `templates/GUIDE.md` (voice, cues, plugins) |
| Plugins (verify + before_run / after_run) | https://github.com/quangdang46/livekit-agent-simulator/blob/main/docs/plugins.md |
| Portability | https://github.com/quangdang46/livekit-agent-simulator/blob/main/docs/portability.md |
| Telephony (SIP modes) | https://github.com/quangdang46/livekit-agent-simulator/blob/main/docs/telephony.md |
| Smoke notes | https://github.com/quangdang46/livekit-agent-simulator/blob/main/docs/smoke-test.md |
| L3 observer design | `docs/plans/PLAN-20260713-lk-agent-session-observer.md` |
-When instructions conflict: **this file + `lks guide`** beat outdated blog snippets. Prefer latest release unless the user pins a tag.
+When instructions conflict: **this file + `lksr guide`** beat outdated blog snippets. Prefer latest release unless the user pins a tag.
diff --git a/install-rust.sh b/install-rust.sh
deleted file mode 100755
index d64f564..0000000
--- a/install-rust.sh
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/usr/bin/env bash
-# Install lksr (the Rust build of livekit-agent-simulator) from GitHub Releases.
-# Single static-ish binary — no uv/pip/build on the user machine.
-#
-# curl -fsSL "https://github.com/quangdang46/livekit-agent-simulator/releases/download/v0.1.0-rust/install-rust.sh" | bash
-# curl -fsSL "…/install-rust.sh" | bash -s -- --verify
-#
-set -euo pipefail
-umask 022
-
-BINARY_NAME="lksr"
-PKG_NAME="livekit-agent-simulator"
-OWNER="quangdang46"
-REPO="livekit-agent-simulator"
-DEST="${DEST:-$HOME/.local/bin}"
-GIT_REF="${LK_SIM_REF:-}"
-QUIET=0
-VERIFY=0
-UNINSTALL=0
-
-log_info() { [ "$QUIET" -eq 1 ] && return; echo "[${BINARY_NAME}] $*" >&2; }
-log_success() { [ "$QUIET" -eq 1 ] && return; echo "OK $*" >&2; }
-die() { echo "ERROR: $*" >&2; exit 1; }
-
-usage() {
- cat </dev/null \
- | sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | head -1
-}
-
-asset_name() {
- local os arch
- case "$(uname -s)" in
- Darwin) os="macos" ;;
- Linux) os="linux" ;;
- *) die "unsupported OS: $(uname -s)" ;;
- esac
- case "$(uname -m)" in
- x86_64|amd64) arch="x86_64" ;;
- arm64|aarch64) arch="aarch64" ;;
- *) die "unsupported arch: $(uname -m)" ;;
- esac
- echo "lksr-${os}-${arch}.tar.gz"
-}
-
-install_binary() {
- local ref="$1" tag asset url work tarball
- tag="$(release_tag_from_ref "$ref")"
- [ -n "$tag" ] || die "No GitRef and no GitHub releases. Pass --ref v0.1.0"
-
- asset="$(asset_name)"
- log_info "Looking for ${BINARY_NAME} release ${tag}: ${asset}"
-
- url="$(curl -fsSL "https://api.github.com/repos/${OWNER}/${REPO}/releases/tags/${tag}" \
- | sed -n "s/.*\"browser_download_url\":[[:space:]]*\"\([^\"]*${asset}\)\".*/\1/p" \
- | head -1)"
- [ -n "$url" ] || die "Release ${tag} missing asset ${asset}"
-
- work="$(mktemp -d "${TMPDIR:-/tmp}/lksr-install.XXXXXX")"
- tarball="${work}/${asset}"
- log_info "Downloading ${url}"
- curl -fsSL "$url" -o "$tarball"
- [ -s "$tarball" ] || die "empty download"
-
- log_info "Extracting..."
- tar -xzf "$tarball" -C "$work"
- local bin
- bin="$(find "$work" -mindepth 1 -maxdepth 2 -type f -name "${BINARY_NAME}" | head -1)"
- [ -n "$bin" ] || die "lksr binary not found in tarball"
-
- mkdir -p "$DEST"
- install -m 0755 "$bin" "$DEST/${BINARY_NAME}"
- log_success "Installed -> $DEST/${BINARY_NAME}"
-
- case ":$PATH:" in *":$DEST:"*) ;; *)
- log_info "Ensure CLI on PATH: export PATH=\"$DEST:\$PATH\""
- ;; esac
-
- rm -rf "$work"
-}
-
-[ "$UNINSTALL" -eq 1 ] && { rm -f "$DEST/${BINARY_NAME}"; log_success "Uninstalled $DEST/${BINARY_NAME}"; exit 0; }
-
-install_binary "$GIT_REF"
-
-if [ "$VERIFY" -eq 1 ]; then
- "$DEST/${BINARY_NAME}" --version
- log_success "${BINARY_NAME} verify OK"
-fi
diff --git a/install.sh b/install.sh
index d8d710b..6788666 100755
--- a/install.sh
+++ b/install.sh
@@ -1,20 +1,22 @@
#!/usr/bin/env bash
-# Install lks from GitHub Releases (CI portable pack).
-# No uv/pip/build on the user machine - download zip + PATH.
+# Install lksr (the Rust build of livekit-agent-simulator) from GitHub Releases.
+# Single static binary, SHA256-verified — no uv/pip/build on the user machine.
#
-# curl -fsSL "https://github.com/quangdang46/livekit-agent-simulator/releases/download/v0.1.0/install.sh" | bash
+# curl -fsSL "https://github.com/quangdang46/livekit-agent-simulator/releases/download/v0.1.0-rust/install.sh" | bash
#
set -euo pipefail
umask 022
-BINARY_NAME="lks"
+BINARY_NAME="lksr"
MCP_SERVER_NAME="livekit-agent-simulator"
PKG_NAME="livekit-agent-simulator"
OWNER="quangdang46"
REPO="livekit-agent-simulator"
DEST="${DEST:-$HOME/.local/bin}"
-INSTALL_ROOT="${INSTALL_ROOT:-$HOME/.local/share/lks}"
-CURRENT_DIR="$INSTALL_ROOT/current"
+# Optional override root. When set, the binary lands under $INSTALL_ROOT/current/
+# with a symlink at $DEST/lksr; when unset (default) it installs directly to $DEST.
+INSTALL_ROOT="${INSTALL_ROOT:-}"
+CURRENT_DIR="${INSTALL_ROOT:+$INSTALL_ROOT/current}"
GIT_REF="${LK_SIM_REF:-}"
QUIET=0
EASY=0
@@ -41,18 +43,19 @@ acquire_lock() {
usage() {
cat </dev/null 2>&1; then
command -v "$BINARY_NAME"
return 0
@@ -162,7 +165,7 @@ resolve_lk_sim() {
configure_all_mcp_providers() {
local lk_bin
lk_bin=$(resolve_lk_sim) || {
- log_warn "lks not found - skip MCP config"
+ log_warn "lksr not found - skip MCP config"
return 0
}
log_info "MCP providers -> ${lk_bin} mcp"
@@ -186,8 +189,8 @@ EOF
uninstall_all() {
log_info "Uninstalling ${PKG_NAME}..."
- rm -rf "$INSTALL_ROOT" 2>/dev/null || true
- rm -f "$DEST/lks" "$DEST/lks-mcp" 2>/dev/null || true
+ rm -f "$DEST/lksr" "$DEST/lks" "$DEST/lks-mcp" 2>/dev/null || true
+ [ -n "$INSTALL_ROOT" ] && rm -rf "$INSTALL_ROOT" 2>/dev/null || true
_remove_mcp_from_file "$HOME/.claude.json" "$MCP_SERVER_NAME"
_remove_mcp_from_file "$HOME/.cursor/mcp.json" "$MCP_SERVER_NAME"
_remove_mcp_from_file "$HOME/.vscode/mcp.json" "$MCP_SERVER_NAME" "servers"
@@ -207,7 +210,7 @@ resolve_install_ref() {
fi
local latest
latest="$(latest_release_tag || true)"
- [ -n "$latest" ] || die "No GitRef and no GitHub releases. Pass --ref v0.1.0"
+ [ -n "$latest" ] || die "No GitRef and no GitHub releases. Pass --ref v0.1.0-rust"
log_info "Default ref -> latest release ${latest}"
echo "$latest"
}
@@ -221,78 +224,74 @@ release_tag_from_ref() {
esac
}
-portable_asset_name() {
+asset_name() {
local os arch
- os="$(uname -s | tr 'A-Z' 'a-z')"
- arch="$(uname -m)"
- case "$os" in
- darwin) os="macos" ;;
- linux) os="linux" ;;
- msys*|cygwin*|mingw*) os="windows" ;;
+ case "$(uname -s)" in
+ Darwin) os="macos" ;;
+ Linux) os="linux" ;;
+ *) die "unsupported OS: $(uname -s). On Windows use install.ps1 (Python build)." ;;
esac
- case "$arch" in
- x86_64|amd64) arch="x64" ;;
- arm64|aarch64) arch="arm64" ;;
+ case "$(uname -m)" in
+ x86_64|amd64) arch="x86_64" ;;
+ arm64|aarch64) arch="aarch64" ;;
+ *) die "unsupported arch: $(uname -m)" ;;
esac
- echo "lks-${os}-${arch}.zip"
+ echo "lksr-${os}-${arch}.tar.gz"
}
-install_portable() {
- local ref="$1" tag asset url work zip payload
+sha256_of() {
+ if command -v sha256sum >/dev/null 2>&1; then
+ sha256sum "$1" | awk '{print $1}'
+ elif command -v shasum >/dev/null 2>&1; then
+ shasum -a 256 "$1" | awk '{print $1}'
+ else
+ die "no sha256sum/shasum available for checksum verification"
+ fi
+}
+
+install_binary() {
+ local ref="$1" tag asset url work tarball bin expected actual
tag="$(release_tag_from_ref "$ref")"
- [ -n "$tag" ] || die "Portable packs require a version tag (e.g. v0.1.0), not '$ref'"
+ [ -n "$tag" ] || die "Release binary install requires a version tag (e.g. v0.1.0-rust), not '$ref'"
- asset="$(portable_asset_name)"
- log_info "Looking for CI portable pack on release ${tag}: ${asset}"
+ asset="$(asset_name)"
+ log_info "Looking for ${BINARY_NAME} release ${tag}: ${asset}"
url="$(curl -fsSL "https://api.github.com/repos/${OWNER}/${REPO}/releases/tags/${tag}" \
| sed -n "s/.*\"browser_download_url\":[[:space:]]*\"\\([^\"]*${asset}\\)\".*/\\1/p" \
| head -1)"
[ -n "$url" ] || die "Release ${tag} missing asset ${asset}"
- work="$(mktemp -d "${TMPDIR:-/tmp}/lks-portable.XXXXXX")"
- zip="${work}/${asset}"
+ work="$(mktemp -d "${TMPDIR:-/tmp}/lksr-install.XXXXXX")"
+ tarball="${work}/${asset}"
log_info "Downloading ${url}"
- curl -fsSL "$url" -o "$zip"
- [ -s "$zip" ] || die "empty download"
-
- log_info "Extracting portable pack..."
- mkdir -p "$work/out"
- unzip -q "$zip" -d "$work/out"
- payload="$(find "$work/out" -mindepth 1 -maxdepth 1 -type d -name 'lks-*' | head -1)"
- [ -n "$payload" ] || die "portable folder not found in zip"
-
- rm -rf "$INSTALL_ROOT"
- mkdir -p "$CURRENT_DIR"
- # Copy lks-linux-x64/* into current/, not current/lks-linux-x64/
- cp -a "$payload"/. "$CURRENT_DIR/"
- repair_nested_portable_layout() {
- local dir="$1"
- if [ -f "$dir/python/Lib/encodings/__init__.py" ] || [ -f "$dir/python/lib/python3.12/encodings/__init__.py" ]; then
- return 0
- fi
- local nested
- nested="$(find "$dir" -mindepth 1 -maxdepth 1 -type d -name 'lks-*' | head -1)"
- [ -n "$nested" ] || return 1
- if [ ! -f "$nested/python/Lib/encodings/__init__.py" ] && [ ! -f "$nested/python/lib/python3.12/encodings/__init__.py" ]; then
- return 1
- fi
- log_info "Repairing nested portable layout ($(basename "$nested") -> $dir)"
- if [ -d "$dir/python" ]; then
- rm -rf "$dir/python"
- fi
- cp -a "$nested"/. "$dir/"
- rm -rf "$nested"
- [ -f "$dir/python/Lib/encodings/__init__.py" ] || [ -f "$dir/python/lib/python3.12/encodings/__init__.py" ]
- }
- repair_nested_portable_layout "$CURRENT_DIR" || die "portable pack invalid: python missing under $CURRENT_DIR/python"
- chmod +x "$CURRENT_DIR/lks" "$CURRENT_DIR/lks-mcp" 2>/dev/null || true
-
- mkdir -p "$DEST"
- # Primary
- ln -sfn "$CURRENT_DIR/lks" "$DEST/lks"
- ln -sfn "$CURRENT_DIR/lks-mcp" "$DEST/lks-mcp"
- log_info "Installed -> $CURRENT_DIR (shims in $DEST: lks)"
+ curl -fsSL "$url" -o "$tarball"
+ [ -s "$tarball" ] || die "empty download"
+
+ log_info "Verifying SHA256..."
+ curl -fsSL "https://github.com/${OWNER}/${REPO}/releases/download/${tag}/SHA256SUMS.txt" -o "$work/SHA256SUMS.txt" \
+ || die "release ${tag} has no SHA256SUMS.txt"
+ expected="$(awk -v a="$asset" '$2==a {print $1; exit}' "$work/SHA256SUMS.txt")"
+ [ -n "$expected" ] || die "SHA256SUMS.txt has no entry for ${asset}"
+ actual="$(sha256_of "$tarball")"
+ [ "$expected" = "$actual" ] || die "SHA256 mismatch for ${asset} (expected ${expected}, got ${actual})"
+ log_info "SHA256 OK (${expected})"
+
+ log_info "Extracting..."
+ tar -xzf "$tarball" -C "$work"
+ bin="$(find "$work" -mindepth 1 -maxdepth 2 -type f -name "${BINARY_NAME}" | head -1)"
+ [ -n "$bin" ] || die "lksr binary not found in tarball"
+
+ if [ -n "$CURRENT_DIR" ]; then
+ mkdir -p "$CURRENT_DIR" "$DEST"
+ install -m 0755 "$bin" "$CURRENT_DIR/lksr"
+ ln -sfn "$CURRENT_DIR/lksr" "$DEST/lksr"
+ log_success "Installed -> $CURRENT_DIR/lksr (symlink in $DEST/lksr)"
+ else
+ mkdir -p "$DEST"
+ install -m 0755 "$bin" "$DEST/lksr"
+ log_success "Installed -> $DEST/lksr"
+ fi
rm -rf "$work"
}
@@ -306,9 +305,9 @@ main() {
local ref
ref="$(resolve_install_ref)"
- log_info "Installing ${PKG_NAME} portable pack (ref ${ref})"
- log_info "No uv/pip/build on this machine - CI already built everything"
- install_portable "$ref"
+ log_info "Installing ${PKG_NAME} Rust binary (ref ${ref})"
+ log_info "Single static binary - no uv/pip/build on this machine"
+ install_binary "$ref"
export PATH="${DEST}:${PATH}"
maybe_add_path
@@ -323,8 +322,8 @@ main() {
local lk
lk="$(resolve_lk_sim 2>/dev/null || true)"
[ -n "$lk" ] || die "${BINARY_NAME} not on PATH after install"
- "$lk" --help >/dev/null
- log_success "Verified ${BINARY_NAME} --help"
+ "$lk" --version >/dev/null && "$lk" guide >/dev/null
+ log_success "Verified ${BINARY_NAME} --version + guide"
fi
echo ""
@@ -333,7 +332,7 @@ main() {
echo " CLI: $lkb"
echo " MCP: $lkb mcp"
fi
- echo " Pack: $CURRENT_DIR"
+ echo " Bin: $DEST/lksr"
echo ""
echo " Quick start:"
echo " ${BINARY_NAME} guide"