Skip to content

kavilo-bot/kavilo

Repository files navigation

kavilo

A lightweight personal AI assistant — single binary, zero dependencies.

Install

Ubuntu / Debian (APT)

For Ubuntu users, the APT repository is the recommended install path:

curl -fsSL https://kavilo-bot.github.io/homebrew-tap/apt/keyring.asc \
  | sudo gpg --dearmor -o /usr/share/keyrings/kavilo-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/kavilo-archive-keyring.gpg] https://kavilo-bot.github.io/homebrew-tap/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/kavilo.list >/dev/null

sudo apt update
sudo apt install kavilo

From binary (manual download)

Download the latest public binary mirror from Releases:

# macOS (Apple Silicon)
curl -LO https://github.com/kavilo-bot/homebrew-tap/releases/latest/download/kavilo_darwin_arm64.tar.gz
tar xzf kavilo_darwin_arm64.tar.gz
sudo mv kavilo /usr/local/bin/

# macOS (Intel)
curl -LO https://github.com/kavilo-bot/homebrew-tap/releases/latest/download/kavilo_darwin_amd64.tar.gz
tar xzf kavilo_darwin_amd64.tar.gz
sudo mv kavilo /usr/local/bin/

# Linux (x86_64)
curl -LO https://github.com/kavilo-bot/homebrew-tap/releases/latest/download/kavilo_linux_amd64.tar.gz
tar xzf kavilo_linux_amd64.tar.gz
sudo mv kavilo /usr/local/bin/

From source

kavilo is a Rust project (Cargo workspace). The supported toolchain is the current stable Rust release (edition 2024).

cargo install --git https://github.com/kavilo-bot/kavilo --locked kavilo-cli

Homebrew

brew install kavilo-bot/tap/kavilo

After onboarding and configuration, macOS users can keep kavilo running in the background at login with brew services start kavilo.

Quick start

# Initialize configuration and workspace
kavilo onboard

# Add your API key to ~/.kavilo/config.json
# Get one at: https://openrouter.ai/keys

# Interactive chat
kavilo agent

# One-shot message
kavilo agent -m "What is the capital of France?"

# Start enabled channels and background services
kavilo start

# For long-running use, run it inside tmux so it survives terminal exit
tmux new -s kavilo
kavilo start

User Guide

For a practical walkthrough of configuration, everyday usage, channels, MCP, and troubleshooting examples, see docs/user-guide.md.

Commands

Command Description
kavilo agent Interactive CLI agent
kavilo agent -m "msg" One-shot message
kavilo start Start enabled channels and background services
kavilo onboard Initialize config and workspace
kavilo profile list / use <name> / show / rm <name> Manage isolated ~/.kavilo/profiles/<name>/ homes (alias: kavilo instance ...)
kavilo auth claude login / status / logout Authenticate with a Claude Pro/Max subscription via OAuth
kavilo mcp slack login <alias> Log in to Slack MCP and save a named workspace alias
kavilo mcp slack status [alias] Show Slack MCP alias status
kavilo mcp slack logout <alias> Revoke a Slack MCP alias server-side and delete its saved token
kavilo mcp login --server <name> Generic OAuth login for any RFC-compliant remote MCP server (Atlassian, Linear, Notion, …)
kavilo mcp serve <self|slack> Run a built-in MCP server over stdio (for Cursor/Claude Desktop)
kavilo status Show configuration status
kavilo version Print version

Most commands accept a global --profile/-p <name> flag (alias: --instance/-i) that pins the KAVILO_HOME directory for that invocation; see Instance profiles.

Configuration

Configuration lives at ~/.kavilo/config.json. Key sections:

Current runtime status:

  • kavilo start runs enabled channels and background services.
  • The Rust runtime does not open an inbound HTTP listener.
  • For persistent use, run it inside tmux.
{
  "agents": {
    "defaults": {
      "model": "anthropic/claude-opus-4-5",
      "provider": "auto",
      "maxTokens": 8192,
      "workspace": "~/.kavilo/workspace"
    }
  },
  "providers": {
    "openrouter": { "apiKey": "sk-or-..." },
    "anthropic":  { "apiKey": "sk-ant-..." },
    "openai":     { "apiKey": "sk-..." }
  },
  "channels": {
    "telegram": { "enabled": true, "token": "...", "allowFrom": ["*"] },
    "slack":    { "enabled": true, "botToken": "xoxb-...", "appToken": "xapp-...", "groupPolicy": "mention", "allowFrom": ["U..."] }
  },
  "runtime": {
    "heartbeat": { "enabled": true, "intervalS": 1800 }
  }
}

Claude Pro/Max subscription

kavilo can authenticate against an Anthropic Claude Pro or Max subscription via OAuth instead of (or alongside) an sk-ant-... developer API key. When you log in, requests for claude-* models route through Anthropic's native /v1/messages endpoint with a bearer token and are billed against your subscription quota — not the per-token developer API meter.

# One-shot browser login. Drops a token at <KAVILO_HOME>/auth/anthropic/oauth.json.
kavilo auth claude login

# What's stored, when does it expire, who is it for?
kavilo auth claude status

# Best-effort revoke + delete the local file.
kavilo auth claude logout

The token file is per-profile and lives next to mcp-auth/, so kavilo --profile work and kavilo --profile personal keep separate sessions.

The factory automatically prefers the OAuth path whenever a token is present and the configured model contains claude or anthropic. To force the API-key path even when an OAuth token exists (e.g. while debugging a quota issue), set KAVILO_ANTHROPIC_OAUTH_DISABLED=1.

Caveat. Subscription-based auth for Claude isn't an officially documented integration path, so it can stop working without notice if Anthropic changes the OAuth surface. If kavilo auth claude login stops working, fall back to a developer API key (providers.anthropic.apiKey in ~/.kavilo/config.json). You can also point KAVILO_ANTHROPIC_OAUTH_CLIENT_ID at your own OAuth client if you have one.

ChatGPT Codex subscription

kavilo can also authenticate against an OpenAI ChatGPT Plus / Pro / Team subscription via the same OAuth credentials the official @openai/codex CLI uses. Requests route through ChatGPT's Responses backend at https://chatgpt.com/backend-api/codex/responses and are billed against your subscription, not a developer API key.

# One-time setup: install the official CLI and run its OAuth login.
# This drops credentials at ~/.codex/auth.json (or $CODEX_HOME/auth.json).
npm install -g @openai/codex
codex login

Then opt in by setting provider = "openai_codex" in ~/.kavilo/config.json:

{
  "agents": {
    "defaults": {
      "provider": "openai_codex",
      "model": "gpt-5.5",
      "reasoningEffort": "high"
    }
  }
}

reasoningEffort accepts "low" | "medium" | "high" and maps to the Responses API's reasoning.effort field. The provider supports streaming, tool calls, prompt caching (via a deterministic prompt_cache_key hashed from the canonicalized message array), and returns the input/output/reasoning token counts on kavilo usage.

If ~/.codex/auth.json is missing or stale, kavilo surfaces the error with a "run codex login" hint instead of failing opaquely.

Codex OAuth auto-refresh

When kavilo start is running, it keeps the Codex access token in ~/.codex/auth.json (or $CODEX_HOME/auth.json) alive so an idle runtime never wakes up to a 401. The access token is a JWT good for ~10 days; kavilo decodes its exp claim once an hour and refreshes anything that expires within the next 24 hours via the same OAuth exchange the official @openai/codex CLI uses:

POST https://auth.openai.com/oauth/token
Content-Type: application/x-www-form-urlencoded

client_id=app_EMoamEEZ73f0CkXaXp7hrann&grant_type=refresh_token&refresh_token=<rt>

The client ID and endpoint were extracted from the codex CLI binary itself. On success kavilo atomically rewrites auth.json with the rotated access / refresh / id tokens (preserving every other key like OPENAI_API_KEY and auth_mode), bumps last_refresh, and the provider's per-request auth.load() picks up the new bearer on the next chat call. No restart needed.

Skipped automatically:

  • No auth.json cached (run codex login once).
  • auth.json has no refresh_token (very old codex login shape) — recover with codex login --force.

Disable the loop entirely:

export KAVILO_CODEX_REFRESH_DISABLED=1

Tune the cadence:

"codex": {
  "enabled": true,
  "refreshIntervalS": 3600,
  "refreshLeadS": 86400
}

Override the OAuth endpoint (mirrors the CLI's CODEX_REFRESH_TOKEN_URL_OVERRIDE env var):

export CODEX_REFRESH_TOKEN_URL_OVERRIDE=https://your-proxy.example/oauth/token

If the refresh token itself gets revoked server-side — admin session-policy change, manual codex logout from another device, client-ID rotation upstream — kavilo logs a RefreshFailed line every tick and the next agent turn will surface the existing "Re-run codex login and retry" message. Auto-refresh extends the supported window but does not turn an invalidated grant into a recoverable one.

Caveat. Like the Claude Pro path, this is not an officially documented OpenAI integration. The chatgpt.com Codex backend can shift its protocol without notice, and the app_EMoamEEZ73f0CkXaXp7hrann client ID is hard-coded — if OpenAI rotates either, auto-refresh stops working and you fall back to interactive codex login. If kavilo starts returning 401 after working previously, re-run codex login; if the wire format changes, fall back to a developer API key on the standard openai provider.

Instance profiles

An instance profile is an isolated KAVILO_HOME directory with its own config, sessions, MCP auth, cron jobs, and workspace — think of it as one self-contained kavilo "instance". The default instance profile lives at ~/.kavilo/ itself; named ones live under ~/.kavilo/profiles/<name>/.

The CLI accepts both spellings interchangeably: kavilo profile ... and kavilo instance ... are the same subcommand, and --profile/-p and --instance/-i are the same flag.

# Create / switch to an instance profile (sticky across shells)
kavilo profile use work       # or: kavilo instance use work
kavilo profile show           # which instance profile is active and where it lives
kavilo profile list           # all instance profiles in ~/.kavilo/profiles
kavilo profile rm scratch     # delete an instance profile (asks for confirmation)

# Or override per-invocation without changing the sticky profile
kavilo --profile work agent   # or: kavilo --instance work agent
KAVILO_HOME=/path/to/home kavilo agent

Resolution order, evaluated once at process start and pinned for the lifetime of that process:

  1. KAVILO_HOME env (set explicitly or by --profile)
  2. Sticky ~/.kavilo/active_profile
  3. The default ~/.kavilo/

If KAVILO_HOME is already set in your shell, it wins over --profile and kavilo prints a warning when the two disagree. A kavilo profile use <other> issued while kavilo start or kavilo mcp serve … is already running does not affect that running process; the active instance profile is fixed for the lifetime of each process.

kavilo profile rm refuses to delete the default instance profile (which contains every named profile under it) and refuses to delete the active instance profile.

What is and isn't isolated per profile

Per-profile (lives under <KAVILO_HOME>/): config.json, the workspace (sessions, AGENTS.md, skills, memory), mcp-auth/slack/<alias>.{json,app.json}, cron/jobs.json, media/, usage/, logs/, CLI history.

Process-global, shared across profiles:

  • Provider env vars (ANTHROPIC_API_KEY, OPENAI_API_KEY, GROQ_API_KEY, …) and KAVILO_* overlays (KAVILO_MODEL, KAVILO_PROVIDER, KAVILO_API_KEY, KAVILO_API_BASE, KAVILO_WORKSPACE). These override any value placed in a profile's config.json. If you want different keys per profile, leave them out of your shell environment and put them in each profile's config.json instead.
  • The sticky profile file at ~/.kavilo/active_profile (singleton).
  • The Slack OAuth loopback port (127.0.0.1:7898).

Sandbox boundaries

  • tools.restrictToWorkspace (default true) is a correctness boundary, not an adversarial one. It prevents read_file / write_file / list_directory / glob_files from touching paths outside the workspace, but the shell tool still runs sh -c with full host access.
  • For genuinely untrusted execution, run kavilo itself inside a container / VM / seccomp jail.
  • tools.exec.scrubEnv = true (off by default) drops *_API_KEY, *_API_TOKEN, *_SECRET, and KAVILO_* from the shell tool's child env so the agent cannot exfiltrate provider credentials via printenv. Add more patterns via tools.exec.scrubEnvExtra.

Use kavilo as an MCP server

kavilo mcp serve <name> runs a built-in server over stdio so editors like Cursor or Claude Desktop can use kavilo's own state and Slack tools without running kavilo start. Two servers are shipped:

  • self — exposes status, sessions, channels, MCP servers, cron jobs, token usage, channel send, and the generic MCP OAuth login flow. Sensitive fields (auth tokens, webhook secrets, API keys) are sanitized in responses.
  • slack — exposes whoami, channel history and replies, message / user / file search, user listing and lookup, permalinks, posting as the authenticated user, and DM open/self-DM, against the workspace whose alias is configured.

Example Claude Desktop / Cursor entry:

{
  "mcpServers": {
    "kavilo-self": {
      "command": "kavilo",
      "args": ["mcp", "serve", "self"]
    }
  }
}

The same tools are also registered in-process when you run kavilo agent or kavilo start, so the agent can call them without spawning a child process.

Architecture

For a detailed system design overview, see docs/architecture.md.

Releasing

Maintainers: see docs/releasing.md for the tag-driven release automation and public Homebrew/binary mirror flow.

Slack (direct channel)

kavilo connects to Slack over Socket Mode — no public HTTPS redirect or port forwarding required. A Slack DM or @bot mention becomes an inbound prompt to the agent; replies post back into the same thread. This is distinct from Slack MCP (next section), which is a tool surface the agent calls into.

"channels": {
  "slack": {
    "enabled": true,
    "botToken": "xoxb-...",
    "appToken": "xapp-...",
    "groupPolicy": "mention",
    "allowFrom": ["U..."]
  }
}

For the full end-to-end setup, scope list, and troubleshooting guide, see docs/slack-channel-setup.md. It also covers the "kavilo bot ↔ kavilo bot in a private channel" multi-agent coordination pattern.

Slack MCP

kavilo can log into Slack MCP with a named alias and store the OAuth token outside config.json, injecting the bearer token only at runtime.

You bring your own Slack app — kavilo does not ship a hosted client. Register a Slack app, capture its client_id and client_secret, and add http://127.0.0.1:7898/oauth/callback as a redirect URI (Slack accepts loopback HTTP for this). Then log in:

kavilo mcp slack login workspace-alias \
    --client-id     YOUR_SLACK_CLIENT_ID \
    --client-secret YOUR_SLACK_CLIENT_SECRET \
    --team          YOUR_TEAM_ID

# Inspect all Slack aliases or one alias
kavilo mcp slack status
kavilo mcp slack status workspace-alias

# Revoke server-side and remove the saved token
kavilo mcp slack logout workspace-alias
# or skip the revoke call (e.g. token is already invalid):
kavilo mcp slack logout workspace-alias --local-only

After the first successful login, --client-id / --client-secret are persisted to ~/.kavilo/mcp-auth/slack/<alias>.app.json; subsequent logins for the same alias don't need them.

If your Slack app must use an HTTPS redirect URI (some workspaces enforce this), point it at any small page you control that 302-redirects to http://127.0.0.1:7898/oauth/callback and pass that HTTPS URL as --redirect-uri. docs/slack-mcp-setup.md has the full end-to-end setup, including a sample GitHub Pages relay.

Saved tokens live under ~/.kavilo/mcp-auth/slack/.

Slack MCP token auto-refresh

When kavilo start is running, it keeps every Slack-provider remote MCP server's user token fresh so the agent's MCP calls never go out with an expired bearer — even if the runtime sits idle for hours.

Two complementary mechanisms drive this:

  1. Periodic background refresh. Every 30 minutes (configurable via KAVILO_SLACK_REFRESH_TICK_SECS) the runtime walks each tools.mcpServers.<alias> entry whose auth.provider == "slack", loads ~/.kavilo/mcp-auth/slack/<alias>.json, and — when the cached expires_at is inside the 60s leeway window and a refresh_token is on file — calls Slack's oauth.v2.access with grant_type=refresh_token, then atomically rewrites the cache file with the new access token, the rotated refresh token, and a fresh expires_at.
  2. Live Authorization header per request. Each remote Slack MCP client re-reads <alias>.json on every outbound MCP call instead of pinning the bearer at startup. So the moment the background refresher rewrites the file, the next MCP request uses the new token without forcing a reconnect.

The runtime also runs one proactive refresh at startup (inside build_slack_auth_provider) so the very first MCP request after a long-idle window never goes out with a stale bearer.

Skipped automatically:

  • Aliases with no <alias>.json cached (run kavilo mcp slack login <alias> first).
  • Legacy non-rotating tokens (no expires_at recorded). Slack treats these as long-lived; if the workspace admin eventually invalidates one, run kavilo mcp slack login <alias> --force to re-auth.
  • Tokens whose refresh_token is missing — kavilo cannot recover these silently and logs a warning pointing you at kavilo mcp slack login <alias> --force.
  • Disabled servers (enabled: false in tools.mcpServers.<alias>).

Disable the loop entirely:

export KAVILO_SLACK_REFRESH_DISABLED=1   # or any of: 1/true/TRUE/yes/YES

Tune the tick:

# Refresh every 5 minutes instead of every 30
export KAVILO_SLACK_REFRESH_TICK_SECS=300

Caveats:

  • This only applies to kavilo start. kavilo agent (one-shot) and kavilo mcp serve slack refresh opportunistically on each Slack API call instead, which is sufficient for short-lived processes.
  • The refresh token itself can expire if Slack's app or workspace policy says so (e.g. token rotation revoked, scope change, admin session policy). When that happens you'll see a RefreshFailed log line and the next MCP call will surface MCP_AUTH_REQUIRED:server=<alias>; reason=invalid_token to the agent — re-auth with kavilo mcp slack login <alias> --force.

The config entry written by kavilo mcp slack login ... looks similar to this:

{
  "tools": {
    "mcpServers": {
      "workspace-alias": {
        "type": "http",
        "url": "https://mcp.slack.com/mcp",
        "enabledTools": [
          "slack_search_channels",
          "slack_search_public",
          "slack_search_public_and_private",
          "slack_search_users",
          "slack_read_channel",
          "slack_read_thread",
          "slack_send_message",
          "slack_send_message_draft"
        ],
        "auth": {
          "provider": "slack",
          "teamId": "YOUR_TEAM_ID",
          "label": "My Workspace"
        }
      }
    }
  }
}

Per-MCP description field for tool routing

Every tool from a remote MCP shows up to the LLM as mcp_<alias>_<tool> with whatever description the upstream server ships. Slack's hosted MCP, for example, ships generic descriptions like "Search public Slack messages" — with no hint about which workspace or whose identity. When you wire two slack-shaped surfaces into one agent (a Foothill Mac bot via the built-in slack_* tools and a Zillow Slack user-OAuth via a remote MCP), the model has nothing to disambiguate them with and thrashes between the two.

Set tools.mcpServers.<alias>.description to a single sentence of routing context and kavilo will prepend it to every tool description exposed by that server, in the form:

[server: <your text>]

<upstream tool description>

Example:

"zillow_user": {
  "type": "http",
  "url": "https://mcp.slack.com/mcp",
  "description": "Acts as your human user account in the Zillow Slack workspace (T024JJ69R). Use these tools when you need to search across Zillow channels, read Zillow threads, or act on your behalf in Zillow Slack. Do NOT use these for the Foothill Mac workspace where kavilo runs as the @Foothill Mac bot.",
  "auth": { "provider": "slack", "teamId": "T024JJ69R", "label": "Zillow Group" },
  "enabledTools": ["slack_search_public_and_private", "slack_read_thread"]
}

Notes:

  • Empty / whitespace-only descriptions are treated as opt-out and the tool description goes through unchanged. Existing configs keep their current behavior.
  • The prefix applies uniformly to every tool the server exposes — write the description once, not per tool.
  • Equally useful for non-Slack MCPs: tag atlassian-jira with the Atlassian site URL, glean_default with the corp tenant, etc., so the model has a one-line answer to "what is this server for?".

LLM HTTP client: fast-fail chat timeout + fresh-connection retry

The OpenAI-compatible HTTP client (crates/kavilo-providers/src/openai_compat.rs, shared by deepseek, openai, dashscope, etc.) splits its per-request timeout into two values and rotates onto a pool-disabled client when it suspects a half-open socket. This was introduced after a single Slack-driven agent turn spent 22 minutes hanging across 11 LLM iterations — every iteration hit the old uniform 120 s timeout on a stale TCP connection that the upstream LB had silently dropped, then succeeded on the immediate retry.

  • CHAT_REQUEST_TIMEOUT_SECS (default 45 s) bounds non-streaming chat completions. Healthy upstreams reply in well under 30 s; anything past this is overwhelmingly a dead socket, so kavilo aborts and retries fast rather than wasting two minutes per iteration.
  • STREAM_REQUEST_TIMEOUT_SECS (default 180 s) is intentionally generous — it covers the entire SSE stream lifetime, not just headers.
  • When a retry is triggered by a timeout or connect failure (is_timeout() / is_connect()), the next attempt is sent through oneshot_client, built with pool_max_idle_per_host(0), so it cannot pick the same half-open socket out of the pool. Retries triggered by 5xx / 429 status keep using the warm pooled client because the connection is fine.
  • After a successful retry, the next chat call goes back to the pooled client; the no-pool path only pays its handshake cost on actual failures.

You'll see fresh_retry=true in the transient network error; retrying after backoff log line when this path activates — that's the signal the fix is working as intended on a flaky network.

AWS SSO auto-refresh

When kavilo start is running, it keeps every already-logged-in AWS SSO session alive so the agent's shell tool, sub-shells, and any AWS SDK process running under the same user always see a non-expired access token — and so the rolling 90-day refresh-token window never lapses even if the machine sits idle for weeks.

Every 5 minutes (configurable) the runtime walks each [sso-session <name>] block in ~/.aws/config. For any session whose cached access token in ~/.aws/sso/cache/ is within 10 minutes of expiry and has a refreshToken, kavilo runs:

aws sso-oidc create-token \
  --client-id <cached> --client-secret <cached> \
  --grant-type refresh_token --refresh-token <cached> \
  --region <cached>

…and atomically rewrites the cache file with the new accessToken, new refreshToken, and an expiresAt of now + expiresIn. The refresh runs silently in well under a second — no browser, no interactive prompt. kavilo never initiates a fresh login itself, so each SSO session must have been logged in at least once manually.

Recommended ~/.aws/config shape (all SSO profiles share one [sso-session] block so they all stay alive together):

[sso-session corp]
sso_start_url = https://corp.awsapps.com/start
sso_region = us-east-1
sso_registration_scopes = sso:account:access

[profile dev]
sso_session = corp
sso_account_id = 111111111111
sso_role_name = Developer
region = us-east-1

[profile prod]
sso_session = corp
sso_account_id = 222222222222
sso_role_name = ReadOnly
region = us-east-1

Caveats:

  • Legacy SSO profiles (the kind written under [profile X] with sso_start_url directly and no [sso-session …] block) have no refresh token and can only be re-authenticated interactively. kavilo skips them with a warning.
  • The aws CLI must be on PATH. If it isn't, the refresher logs a warning and disables itself for that run.

Defaults and how to disable:

"aws": {
  "enabled": true,
  "refreshIntervalS": 300,
  "refreshLeadS": 600
}

Set aws.enabled = false in ~/.kavilo/config.json or export KAVILO_AWS_REFRESH_DISABLED=1 to turn the loop off entirely.

Features

  • Multi-provider LLM support — OpenAI, Anthropic, Azure, Groq, DeepSeek, Gemini, Ollama, and many more via OpenAI-compatible API
  • Chat channels — Telegram and Slack, with group policies, media support, and per-sender allow-lists
  • Built-in tools — File operations, shell execution, web search/fetch, message sending, subagent spawning
  • MCP support — Connect to Model Context Protocol servers (stdio and HTTP transports)
  • Memory system — Long-term memory (MEMORY.md) and history with LLM-driven consolidation
  • Skills — Workspace and built-in skills with frontmatter configuration
  • Cron scheduler — Schedule recurring tasks and one-shot reminders
  • Heartbeat service — Periodic task checking via HEARTBEAT.md
  • Token tracking — Per-call usage logging with trip/total counters

Running in the background

kavilo stores its runtime data in ~/.kavilo/. For long-running use, start kavilo start inside tmux so it survives terminal exit:

tmux new -s kavilo
kavilo start

On macOS, brew services start kavilo will keep it running at login.

Build from source

kavilo is a Cargo workspace (Rust 2024). Use the included Makefile or plain cargo invocations:

git clone https://github.com/kavilo-bot/kavilo.git
cd kavilo
make build       # Release build of the kavilo binary for the host
make test        # cargo test --workspace
make clippy      # cargo clippy --workspace --all-targets -- -D warnings
make fmt         # cargo fmt --all -- --check
make cross       # Cross-compile for all published targets
make deb         # Build a .deb for the host architecture (cargo-deb)
make install     # cargo install --path crates/kavilo-cli

License

MIT

About

A lightweight personal AI assistant — single binary, zero dependencies. Multi-provider LLM (Anthropic/OpenAI/Codex/Groq/DeepSeek/…), Slack & Telegram channels, MCP tools, cron, durable memory.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors