跨 Claude Code / Codex / Gemini CLI / OpenCode 的统一 skill / MCP 管理 + LLM 智能路由器 + 实时遥测仪表盘。
Quickstart · Three Pillars · Architecture · AGENT guide
Single Rust binary · macOS / Linux / Windows · No runtime deps · CC BY-NC-SA 4.0
runai unifies how you install, enable, recommend, and observe AI CLI skills across four host CLIs. Skills are real folders on disk symlinked into each CLI's skills directory; MCP servers are real entries in each CLI's config file. Filesystem = source of truth, DB only holds metadata.
On top of that core, an opt-in LLM skill router auto-picks the right skill for every user prompt (BM25 prefilter + LLM rerank with verified-adoption counting), and a local dashboard at http://127.0.0.1:17888 shows every hook invocation, token cost, latency, and chosen skill in real time.
| Before | runai |
|---|---|
| Skills scattered across Claude Code / Codex / Gemini / OpenCode, each with its own config quirks | One TUI + CLI + MCP server manages all four; native config format per target |
git clone a skill repo, copy folders, edit JSON / TOML by hand, repeat for every CLI |
runai install owner/repo — downloads, registers, groups, symlinks into every CLI in one shot |
| 2000+ skills out there, no way to browse without leaving the terminal | Built-in market: runai market browses cached index, Enter to install |
| Hard-deleted skills can't be recovered when you change your mind | Trash-first: runai uninstall moves to ~/.runai/trash/, runai trash restore brings it back |
"Did I enable that skill?" — ls four directories, compare with config files, hope they agree |
Source of truth = symlink existence + config entry presence; runai status reads filesystem live |
| No idea which skills you actually use, no idea what the router is doing per turn | Dashboard at 127.0.0.1:17888 — every routing attempt logs Fast/Precise mode, candidates, optional Precise expansion, router input/output, hook output, latency, tokens, recovery and empty attribution |
- Install once, enabled everywhere —
runai install owner/repo[@branch]downloads the skill, registers it in DB, and symlinks into all four CLI skill dirs. MCP entries get written into each CLI's native config (Claude JSON / Codex TOML / Gemini JSON / OpenCode JSON). - Filesystem = truth — Skill enabled ⇔ symlink exists at
<cli-home>/skills/<name>. MCP enabled ⇔ entry present (without"disabled": true) in target config. The DB is metadata-only; nothing breaks if you blow it away. - Groups — Cluster related skills (
figma,ktv-car-project,ppt-slides, …) into named groups; enable / disable / rename whole groups atomically. - Market — Built-in skill marketplace with 2,000+ skills curated; cached locally, refreshed in the background (1h TTL).
runai market install <name>is one-shot. - Safe delete — Everything trash-first. Restore until you
runai trash purge.
- Hook integration — Claude Code's
UserPromptSubmithook →runai recommend→ router decides → output injected into the agent's prompt as additional context. In dashboard/team use, local hooks read~/.runai-identity; a valid key applies that user's dashboard preferences, while a stale key returns empty hook output instead of silently reverting to anonymous defaults. - Fast by default, Precise on demand — Fast builds a bounded head+tail task anchor, retrieves from structured
task / triggers / inputs / outputsfields, and makes at most one router-model call. It never injects transcript history, project files, conversation replay, or session intent memory. Precise adds one bounded semantic expansion call and may use opted-in context; cwd, client kind, transcript messages and Conversation replay all have hard per-field/turn/total caps. - Evidence-bounded retrieval — BM25 and bilingual structured triggers rank a configurable candidate set. Quality/adoption/feedback can re-rank candidates that have retrieval evidence, but cannot revive zero-evidence skills into an oversized Fast pool. A fully zero-evidence oversized pool returns empty with
retrieval_zero; a tiny pool may still reach the router because BM25 IDF can be zero in small corpora. - AI summary enrichment — Every skill gets a structured summary (
task / triggers / inputs / outputs / not-for / score) generated by the same LLM in your chosensummary_lang; positive fields feed the BM25 index whilenot-forstays as a negative/admission signal and router context. Enrichment is gated on an explicit language pick and the output language is enforced (mismatches retry, then drop rather than write), so the index stays single-language;triggerskeeps cross-language keywords for recall. Auto-refreshes on SKILL.md edit, andrunai install/scanfire targeted re-enrich for just the changed skills. - Strict short-ID routing — Each request maps candidates to
C01..Cnn; parsed output is filtered through that request's whitelist.not-foris a veto. Alternatives useEXCLUSIVE; necessary complementary workflow steps useCOMPATIBLE. The same relevant skill may be recommended again on a later turn; there is no session no-repeat suppression. - Verified adoption — Hook output tells the agent to run
runai-client activate <skill>, optionally with a literal runai-ownedrnai_sess_*session id. That command printsSKILL.mdonly after the server has ACKed/skills/use/{name}or after a durable local outbox event has been written under~/.runai/client-cache/servers/<server-key>/skills/<skill-key>/.outbox/. - Client-side cache —
runai-client activateandsynccache the whole skill directory under~/.runai/client-cache, never under the managed~/.runai/skillspool. Cache hits still send or queue a usage event before printingSKILL.md, so reduced content traffic does not drop adoption counts. When a skill references support files inside its bundle, agents read them withrunai-client file <skill> <relpath>from that cache; runtime/user-home paths such as~/.tool-name/...remain local filesystem data, not bundle files.
- Single binary, no CDN —
runai serverboots an embedded axum HTTP server;web/{index.html,app.css,app.js}areinclude_str!'d into the Rust binary. - Auto-launch on every Claude Code session —
runai server --install-hookadds aSessionStarthook so the dashboard is always athttp://127.0.0.1:17888when you open Claude Code. - Every routing attempt instrumented — Per-event telemetry includes Fast/Precise mode, actual LLM attempt count, empty attribution, retrieval query, parsed/filtered candidates, parser recovery, candidate set, raw/cleaned Precise expansion, router input/raw output, hook output, latency and tokens.
- Admin provider checks — Dashboard Admin separates the global recommend switch from per-user preferences and can test a saved provider by sending a tiny real model request, surfacing success or the provider error inline. Provider/model changes are global config; prompt-injection toggles, recommend context controls (session memory on/off + queue limit + BM25 candidate count), reminder text, and library scope are per-user preferences and only affect authenticated
/recommendcalls or local hooks with a valid identity. - Per-skill drill-down —
/skillslists every managed skill with usage count, LLM quality score, AI summary; click into one to see its full directory tree (browse SKILL.md + supporting files), recent usage events, raw description vs. enriched summary. - Live polling — 5s refresh with
inFlightguard andvisibilitychangepause. Per-boot cache-buster on static assets means a server restart aftercargo installpropagates without a hard refresh.
One-liner (macOS / Linux) — detects your platform, downloads + checksum-verifies the release binary, puts it on PATH, then sets up the edition you pick:
curl -fsSL https://raw.githubusercontent.com/Crosery/runai/main/install.sh | sh
# non-interactive (agents / CI):
curl -fsSL https://raw.githubusercontent.com/Crosery/runai/main/install.sh | sh -s -- --edition personal --yesWindows (PowerShell):
irm https://raw.githubusercontent.com/Crosery/runai/main/install.ps1 | iexFrom source (needs Rust):
cargo install --git https://github.com/Crosery/runaiEditions (one binary, picked at install time): personal = local skill router + Claude Code hook, no account, no server. team = run the multi-user dashboard server; other machines connect with curl -fsSL http://<host>:17888/install | bash.
Installer flags: --edition personal|team, --version <tag>, --bin-dir <path>, --yes, --no-hook, --no-setup, --dry-run, --uninstall. Prebuilt artifacts for {linux,darwin,windows} × {amd64,arm64} on the releases page. Windows needs Developer Mode or Administrator for symlinks.
# 1) Boot the TUI to browse / enable existing skills you already have plus 2000+ market entries
runai
# 2) Opt-in to the LLM router (default DeepSeek v4-flash, ~$0.0001 per route call)
runai recommend setup
runai recommend install-hook # writes UserPromptSubmit + SessionStart hooks
# into ~/.claude/settings.json (idempotent, .runai-bak backup)
# 3) Launch the dashboard once; the hook keeps it running thereafter
runai server --port 17888 --ensure
runai server --install-hook # auto-launch on every Claude Code sessionAfter step 2, every Claude Code prompt routes through runai recommend; accepted skills are activated through runai-client activate, which records or queues adoption before printing cached content, and every router event lands in the dashboard.
runai # TUI
runai install owner/repo # install skill from GitHub into all CLIs
runai market install <name> # install from market
runai search <query> # search installed + market
runai status # show enabled / disabled across all CLIs
runai list --target claude # one-CLI view
runai backup # timestamped backup of skills + configs
runai trash # browse deleted, restore or purge
runai recommend enrich # regenerate AI summaries (changed-mtime detection)
runai recommend enrich --fix-lang # re-enrich only summaries in the wrong language
runai recommend stats # router LLM usage / cost / latency over time
runai doctor # health check; `--fix` prunes dangling symlinksFull CLI list: runai --help.
~/.runai/ ~/.{claude,codex,gemini,opencode}/skills/
├── skills/<name>/SKILL.md └── <name> -> ~/.runai/skills/<name> ← symlink = enabled
├── mcps/<name>.json ~/.claude.json ← MCP entries (Claude)
├── groups/<id>.toml ~/.codex/config.toml ← MCP entries (Codex)
├── trash/<trash-id>/ ~/.gemini/settings.json ← MCP entries (Gemini)
├── backups/<timestamp>/ ~/.config/opencode/opencode.json ← MCP entries (OpenCode)
├── market-cache/
├── users/<user_id>/skills/<name>/ ← v0.11.0-beta.5: per-user private skill isolation
├── config.toml ← runai recommend config (provider, model, api_key)
└── runai.db ← SQLite: skill metadata, usage, router_events, AI summaries
Auto-migrated from ~/.skill-manager/ on first launch (v0.5.0 transition). Env overrides honored: RUNE_DATA_DIR and SKILL_MANAGER_DATA_DIR.
The dashboard's Market tab is a mirror of the skills.sh leaderboard — 20K+ skills across 2.6K GitHub repos, no API key needed.
- All Time / Trending (24h) / Hot sort tabs match skills.sh, all sorted server-side
- 8W TREND column: per-row sparkline (from skills.sh
weeklyInstalls) - INSTALLS column: formatted as 1.8M / 478.6K
- Server-side pagination: 50/page default + prev / next buttons — no 20K-row freeze
- Search filters via server-side query (250ms debounce)
- Click install: runai resolves the real GitHub repo on demand and drops the whole skill directory into
~/.runai/users/<user_id>/skills/<name>/ - Login persistence: api_key stored in localStorage so a fresh browser session stays signed in
- The old per-repo builtin sources were retired; add your own GitHub repos via
+ GitHub(stored as user-added sources)
runai server --install-autostart # register login auto-start
runai server --uninstall-autostart # remove it- macOS: writes
~/Library/LaunchAgents/cn.crosery.runai.plistandlaunchctl load -w. KeepAlive relaunches the server if it crashes. - Linux: writes
~/.config/systemd/user/runai.serviceandsystemctl --user enable --now.Restart=on-failure. - Windows: not implemented yet — the command prints Task Scheduler instructions.
runai (the default TUI) also auto-spawns the server via ensure_running. Set RUNAI_NO_AUTOSPAWN=1 to disable.
runai server now isolates skills per registered user. Every dashboard / hook user installs into their own ~/.runai/users/<user_id>/skills/<name>/; the DB column owner_user_id separates public from private rows.
- Public skills (
owner_user_id IS NULL) live in~/.runai/skills/<name>/and are visible to every user - Private skills are visible only to their owner; admin sees everything via scope
"*" - The same skill name can coexist as public + N user-private copies; DB ids gain a
u:<uid>:prefix to avoid PK collisions - After running the client install script (
curl <SERVER>/install | bash) and registering an account, dashboard installs go private. CLI / TUI installs still write to the public pool.
See the "Per-user physical skill isolation" section in AGENTS.md for the full invariant set.
When a user forgets their password, an admin has three supported ways to reset it. All three rotate the target's api_key too, so every previously-issued Bearer (browser cookie / installed client) dies and the user must log in again with the new password.
Local CLI (on the machine running the server; writes the local runai.db directly, no server needs to be up):
runai admin reset-password <username> # interactive hidden prompt + confirm
runai admin reset-password <username> --password <pw> # non-interactive, for scripts / agentsThis is the supported replacement for hand-editing the users table via SQL. An unknown username errors cleanly (no panic).
Server API (team mode, admin Bearer / session cookie):
curl -X POST http://<host>:17888/api/admin/users/<user_id>/reset-password \
-H "Authorization: Bearer <admin_key>" \
-H "Content-Type: application/json" \
-d '{"new_password":"<new password>"}'200 on success (the response carries only user_id / username — the new key is never returned); 403 for non-admin, 401 unauthenticated, 404 unknown user, 400 when the password is shorter than 6 chars. An admin may reset their own password.
Dashboard (team mode, logged in as admin): Admin tab → user table → 重置密码 button on each row (works on your own row too). Prompts for the new password, calls the same /api/admin/users/{id}/reset-password endpoint, then confirms with a reminder to hand the new password to the user — their old api_key/session is already dead.
| Module | Source | What it does |
|---|---|---|
cli/ |
src/cli/mod.rs |
clap subcommand dispatch; entry point for every runai <verb> |
core::manager |
src/core/manager.rs |
SkillManager orchestrates install / enable / disable / trash / migrate |
core::scanner |
src/core/scanner.rs |
Filesystem discovery + adoption of unmanaged skills (with cross-data-dir safety guard) |
core::linker |
src/core/linker.rs |
Cross-platform symlink create / remove / detect |
core::recommend |
src/core/recommend.rs |
LLM skill router (BM25 + AI summary + LLM rerank + adoption tracking) |
core::db |
src/core/db.rs |
SQLite schema (v14) + migrations + queries |
core::installer |
src/core/installer.rs |
GitHub / market install pipeline |
mcp::tools |
src/mcp/tools.rs |
22 sm_* tools exposed via MCP stdio |
tui/ |
src/tui/ |
ratatui + crossterm full-screen UI |
server |
src/server.rs |
axum dashboard for router telemetry |
Per-module deep-dive docs in src/**/*.LLM.md. Architecture invariants in AGENTS.md.
- Filesystem is the source of truth. Skill enabled = symlink exists. MCP enabled = config entry present. DB carries metadata only; rebuild it from disk any time.
- Trash-first everywhere. Delete is reversible until
runai trash purge. Backups timestamped, restorable. - Single binary, no runtime deps. Web dashboard assets
include_str!'d in. rusqlite bundled. No node, no python, no Docker. - Router is opt-in. Default
enabled = false; nothing reaches a network untilrunai recommend setup. - Verified adoption over self-report. Counting comes from
runai-client activate: cached content is printed only after/skills/use/{name}is ACKed or a durable local outbox event is written. - Safety guards on destructive syscalls.
scan/adoptrefuse torenameacross data dirs after the 2026-04-27 incident. Physical-e2e tests intests/safety_e2e.rslock the invariant. - Documentation invariant. Every code change ships its
*.LLM.mdupdate in the same commit (see AGENTS.md).
CC BY-NC-SA 4.0 — Attribution-NonCommercial-ShareAlike 4.0 International.
- Non-commercial only — you may not use the material for commercial purposes.
- ShareAlike — if you remix, transform, or build upon the material, you must distribute your contributions under the same license.
- Attribution — credit the original author and link back to this repository.
Full text: creativecommons.org/licenses/by-nc-sa/4.0.
