Sanook CLI
The terminal AI coding agent with a memory that outlives the session.
Bring your own key · 9 providers · MCP · Obsidian second brain · gateway & cron
🇹🇭 อ่านภาษาไทย
Pick one command below — all roads lead to sanook.
Requires Node.js ≥ 22 · node -v to check · npx sanook doctor auto-fixes PATH / Node issues
|
Recommended — npm npm install -g sanook-cli
# or run once without installing:
npx sanook-cliOne-liner install script (checks Node, then installs via npm) # GitHub raw (default)
curl -fsSL https://raw.githubusercontent.com/Sir-chawakorn/sanook-cli/main/scripts/install.sh | bash
# GitHub Pages mirror
curl -fsSL https://sir-chawakorn.github.io/sanook-cli/install.sh | bash
# jsDelivr CDN
curl -fsSL https://cdn.jsdelivr.net/gh/Sir-chawakorn/sanook-cli@main/scripts/install.sh | bashHomebrew brew trust Sir-chawakorn/tap # once on newer Homebrew
brew tap Sir-chawakorn/tap
brew install sanook-cli |
Recommended — npm (PowerShell or cmd) npm install -g sanook-cli
# or:
npx sanook-cliOne-liner install script # GitHub raw (default)
irm https://raw.githubusercontent.com/Sir-chawakorn/sanook-cli/main/scripts/install.ps1 | iex
# GitHub Pages mirror
irm https://sir-chawakorn.github.io/sanook-cli/install.ps1 | iexWinGet (after PR #391114 merges) winget install Sanook.SanookCLIPortable zip (no WinGet yet): sanook-cli-win-x64.zip |
📋 All install methods — status & links
| Method | Command | Status |
|---|---|---|
| npm / npx | npm install -g sanook-cli |
✅ npm · latest 0.5.7 |
| curl | bash | curl -fsSL …/install.sh | bash |
✅ raw · Pages · jsDelivr |
| irm | iex | irm …/install.ps1 | iex |
✅ raw · Pages |
| Homebrew | brew tap Sir-chawakorn/tap && brew install sanook-cli |
✅ homebrew-tap |
| GitHub Pages | curl -fsSL https://sir-chawakorn.github.io/sanook-cli/install.sh | bash |
✅ Live |
| WinGet | winget install Sanook.SanookCLI |
⏳ PR #391114 · CLA signed · release zip ready |
| sanook.ai (optional short URL) | curl -fsSL https://sanook.ai/install.sh | bash |
⏳ DNS at GoDaddy → run bash scripts/configure-sanook-ai-dns.sh
|
Full maintainer guide: docs/INSTALL_INFRA.md
Troubleshooting
Symptom Fix 'sanook' is not recognizedYou ran npm i sanook-cliwithout-g— reinstall withnpm install -g sanook-cli, or usenpx sanookNode too old Install Node ≥ 22 · scripts refuse older versions Homebrew refuses tap Run brew trust Sir-chawakorn/taponce, then retryNot sure what's wrong npx sanook doctor— prints OS-specific fixes (incl. Windows PATH one-liner)
First run → setup wizard & API key
Install · Quickstart · Memory · Persona · Dashboard · Providers · Usage · Gateway · MCP · Security
Close the terminal. Come back tomorrow. Sanook still knows what you decided.
Sanook is a transparent coding agent for your terminal — one loop, no magic:
prompt → LLM → tool call → result → loop → answer
Wrapped in the things real work needs: a permission gate, structured memory across sessions, a long-running gateway with cron and chat channels, on-demand skills, MCP servers, and first-class git awareness.
BYOK by design. Every provider uses a direct API key from that provider's console — Sanook never reuses OAuth or subscription credentials, because that violates provider terms and gets accounts banned.
The agent loop, BYOK, and MCP are table stakes now. What Sanook has that the big vendor CLIs don't is memory that survives the session — a structured Obsidian "second brain" the agent reads at the start of every run.
| Sanook | Claude Code | Codex CLI | Gemini CLI | |
|---|---|---|---|---|
| Open-source | ✅ | ❌ | ✅ | ✅ |
| Bring your own key | ✅ | — | ✅ | ✅ |
| Providers | 9 | 1 | 1 | 1 |
| Local models (Ollama / LM Studio) | ✅ | ❌ | ❌ | ❌ |
| MCP (stdio + remote HTTP) | ✅ | ✅ | ✅ | ✅ |
| OS sandbox (Seatbelt / bubblewrap) | ✅ | ✅ | ✅ | ✅ |
| Checkpoint / rewind | ✅ | ✅ | ✅ | ✅ |
| Image / vision input | ✅ | ✅ | ✅ | ✅ |
| Prompt caching | ✅ | ✅ | ✅ | ✅ |
| Durable cross-session memory | ✅ | ❌ | ❌ | ❌ |
| Local gateway + cron + messaging | ✅ | ❌ | ❌ | ❌ |
On raw benchmark scores the frontier vendors win — Sanook's bet is portability + persistent memory, not beating Opus on SWE-bench. Use whatever fits; this one remembers.
Sanook does not dump everything into one folder. Memory is layered — some lives in your Obsidian vault, some in ~/.sanook/ for speed and resume. Both are wired together at runtime.
flowchart LR
START([Start<br/>sanook / gateway / cron / resume]):::start
CFG["Load config<br/>~/.sanook/config.json"]:::local
MODE{"brainPath is set<br/>and vault is reachable?"}:::gate
LOCALONLY([Fallback end<br/>answer works with local session + usage only]):::warn
subgraph read["1. Read path · gather memory before the turn"]
direction TB
HIER["Hierarchical SANOOK.md<br/>global -> repo -> cwd instructions"]:::inject
AM["Auto-memory<br/>~/.sanook/memory/memory.json"]:::local
RESUME["Resume context<br/>~/.sanook/sessions/*.json"]:::local
HOT["Vault hot files<br/>AI-Context-Index + current-state + inbox"]:::vault
PROJECT["Project hot context<br/>Projects/<slug>/ matched from cwd"]:::vault
PACK["Context pack<br/>Shared/Context-Packs/*.md selected by task"]:::vault
SEARCH["Per-turn retrieval<br/>search/index.json over vault + memory + sessions + skills"]:::local
end
subgraph inject["2. Injection · build the model input"]
direction TB
STATIC["Static brain block<br/>stable context injected at session start"]:::inject
RECALLED["Recalled context block<br/>fresh matches for this prompt"]:::inject
SYS["System prompt<br/>policy + tools + memory + repo state"]:::inject
USER["User prompt<br/>or incoming gateway message"]:::process
end
subgraph loop["3. Agent loop · act, observe, answer"]
direction TB
LLM["LLM reasoning step"]:::process
NEED{"Need tools?"}:::gate
TOOLS["Tool calls<br/>files, shell, git, MCP, skills"]:::process
RESULT["Tool results<br/>fed back into context"]:::process
ANSWER["Final answer<br/>terminal or gateway reply"]:::finish
end
subgraph write["4. Write-back · make the next session smarter"]
direction TB
REMEMBER["remember tool<br/>redact -> merge -> rank"]:::write
MEMJSON["Persist auto-memory<br/>memory.json + MEMORY.md mirror"]:::write
INBOX["Append candidate fact<br/>Shared/Memory-Inbox/memory-inbox.md"]:::write
WORKLOG["Append daily worklog<br/>Sessions/YYYY-MM-DD-worklog.md"]:::write
CHAT["Append full transcript<br/>Sessions/*-chat.md when enabled"]:::write
SESSION["Save session JSON<br/>~/.sanook/sessions/*.json"]:::write
USAGE["Record usage<br/>~/.sanook/usage/events.jsonl"]:::write
INDEX["Re-index changed sources<br/>sanook index -> search/index.json"]:::write
END([End<br/>answer returned + memory ready for resume/retrieval]):::finish
end
START -->|entrypoint starts| CFG
CFG --> MODE
MODE -->|yes| HOT
MODE -->|no vault path| LOCALONLY
CFG --> AM
CFG --> RESUME
CFG --> SEARCH
HIER --> STATIC
AM --> STATIC
HOT --> STATIC
PROJECT --> STATIC
PACK --> RECALLED
RESUME --> RECALLED
SEARCH --> RECALLED
STATIC --> SYS
RECALLED --> SYS
USER --> LLM
SYS -->|injected every turn| LLM
LLM --> NEED
NEED -->|yes| TOOLS
TOOLS --> RESULT
RESULT --> LLM
NEED -->|no| ANSWER
LLM -->|durable fact found| REMEMBER
REMEMBER --> MEMJSON
REMEMBER --> INBOX
ANSWER --> WORKLOG
ANSWER --> CHAT
ANSWER --> SESSION
ANSWER --> USAGE
MEMJSON --> INDEX
INBOX --> INDEX
WORKLOG --> INDEX
CHAT --> INDEX
SESSION --> INDEX
ANSWER --> END
classDef start fill:#064e3b,stroke:#34d399,color:#ffffff,stroke-width:3px
classDef finish fill:#0f766e,stroke:#5eead4,color:#ffffff,stroke-width:3px
classDef gate fill:#7c2d12,stroke:#fdba74,color:#ffffff,stroke-width:2px
classDef local fill:#172554,stroke:#60a5fa,color:#ffffff
classDef vault fill:#312e81,stroke:#a78bfa,color:#ffffff
classDef inject fill:#164e63,stroke:#22d3ee,color:#ffffff
classDef process fill:#1f2937,stroke:#94a3b8,color:#ffffff
classDef write fill:#78350f,stroke:#fbbf24,color:#ffffff
classDef warn fill:#7f1d1d,stroke:#fca5a5,color:#ffffff,stroke-width:2pxHow to read the flow: green nodes mark start/end, orange diamonds are decisions, blue nodes are local runtime state, purple nodes live inside the Obsidian-style vault, cyan nodes are injected into the system prompt, dark nodes are the active agent loop, amber nodes write memory back after/during the turn, and the red node is the safe fallback when no vault is configured.
| Data | Stored at | When | Purpose |
|---|---|---|---|
| Worklog | {brainPath}/Sessions/YYYY-MM-DD-worklog.md |
After every agent turn (REPL + headless) | Daily trace of prompts & cost summary |
| Full transcript (opt-in) | {brainPath}/Sessions/YYYY-MM-DD-<id>-chat.md |
After every turn, when brainTranscript is on |
The actual conversation — your prompt and the AI's reply |
| Session note | {brainPath}/Sessions/YYYY-MM-DD-<id>-session.md |
On REPL exit (/quit or Ctrl+C at empty prompt) |
AI/heuristic summary + key facts |
| Memory inbox | {brainPath}/Shared/Memory-Inbox/memory-inbox.md |
When the agent calls remember
|
Candidate facts for vault curation |
| Project workspace | {brainPath}/Projects/<slug>/ |
Setup wizard / sanook brain init links your repo |
Project-scoped notes & hot context |
| Persona profile | {brainPath}/Shared/User-Persona/persona.md |
sanook persona or /persona in REPL |
Who you are + how you want the agent to work |
| Auto-memory | ~/.sanook/memory/memory.json |
When the agent calls remember, or sanook persona (protected) |
Structured facts (merge, rank, inject) |
| Session JSON | ~/.sanook/sessions/*.json |
Every turn |
--continue / --resume transcript |
| Search index | ~/.sanook/search/ |
sanook index (incremental) |
BM25 / hybrid retrieval |
| Usage ledger | ~/.sanook/usage/events.jsonl |
Every turn | Token/cost tracking — not semantic memory |
Read at session start: hierarchical SANOOK.md (global → project root → cwd), auto-memory block, vault hot files (AI-Context-Index, current-state, inbox, matched Projects/<slug>/), plus per-turn retrieval over vault + memory + past sessions.
Setup links your repo to the vault: the first-run wizard (or sanook brain init) saves brainPath in ~/.sanook/config.json, scaffolds Projects/<slug>/ inside the vault, and creates SANOOK.md in your repo if missing.
sanook brain init # pick vault folder + identity
sanook brain context # inspect what gets injected from cwd
sanook brain projects list # vault projects ↔ repo paths
sanook memory stats # auto-memory store overview
sanook usage daily # token/cost ledger (ccusage-style)Want the entire conversation in your vault? Turn on full transcripts — every prompt and reply is appended to Sessions/*-chat.md per session:
sanook config set brainTranscript on # or env: SANOOK_BRAIN_TRANSCRIPT=1It is opt-in because verbatim transcripts of long coding sessions grow a vault fast. Worklog (always on) keeps a lighter daily trace; the full transcript is the complete record.
Disable persistence: SANOOK_DISABLE_PERSISTENCE=1 · worklog only: SANOOK_DISABLE_WORKLOG=1 · usage ledger: SANOOK_DISABLE_USAGE=1
Already installed? Jump straight to setup — see Install if you haven't yet.
Run the setup wizard or set an API key manually:
sanook setup # provider + model wizard; offers to create a second brain
sanook model # re-run provider/model setup later
sanook persona # tell the agent who you are + how you like to work
sanook auth add anthropic --api-key sk-ant-... --use
export ANTHROPIC_API_KEY=sk-ant-... # macOS / Linux
setx ANTHROPIC_API_KEY "sk-ant-..." # Windows (export won't work in cmd) — then open a NEW terminal
sanook "read package.json and list the dependencies"Run sanook with no task to drop into an interactive REPL. On the very first run with no key configured, a setup wizard walks you through picking a provider, pasting a key, and choosing a model.
sanook # interactive REPL
sanook "fix the failing test" # one-shot, headless
sanook chat -q "fix the failing test" --provider anthropic
sanook -z "summarise the diff" # one-shot, final output only
sanook --json "..." # JSONL output for CI / scripts
sanook status # redacted provider/key/brain/gateway status
sanook web status # true web-search/fetch readiness + grounding policy
sanook sessions # list saved sessions for this project
sanook --resume <session_id> "continue here"
sanook dump # support snapshot; raw secrets are never printed| Area | What you get |
|---|---|
| Agent loop | Built on the Vercel AI SDK 6 (streamText + stopWhen + fullStream), with streamed output, a cost meter, a budget cap, Anthropic prompt caching on the static preamble, and rate-limit-aware retry/backoff (distinct from auth failures) with model fallback. |
| Tools |
read_file · write_file · edit_file (multi-tier matcher) · list_dir · glob · grep · run_bash · run_python · run_rust, plus git tools — gated by a permission layer that denies destructive commands, protected paths, and paths outside the workspace/brain by default. Non-bash tools are timeout-guarded so a runaway read/grep can't hang the loop. |
| Sandbox |
run_bash is confined by an OS sandbox — Seatbelt on macOS, bubblewrap on Linux — so shell writes stay inside the workspace/brain/tmp (reads + network unaffected). Opt out with SANOOK_NO_SANDBOX=1. |
| Approval |
ask mode is the default and prompts y/n before any file write or shell command. --yes for auto-approve; headless ask-mode safely denies mutations when no approval UI exists. |
| Input | Multiline editing, ↑/↓ persisted prompt history, readline keys (Ctrl-A/E/U/K/W), and @file mentions that inline a file's contents (or attach an image for vision-capable models). |
| Checkpoint | A shadow-git snapshot is taken before each turn; /rewind restores the files and truncates the conversation — recoverable (it stashes the current state first). |
| Memory | Layered memory: remember / recall, hierarchical SANOOK.md, Obsidian vault injection, per-turn retrieval, --continue / --resume, session notes on exit, daily worklogs, and sanook memory / sanook usage inspect commands. |
| Familiar CLI surfaces |
sanook setup, sanook model, sanook auth, sanook chat -q, sanook gateway, sanook status, sanook sessions, sanook dump, sanook tools, and sanook send provide familiar management entry points, all Sanook-branded. |
| Startup cockpit | The interactive REPL opens with a Sanook-branded wordmark, service routes (Code, Brain, Connect, Ship), and live readiness signals for second-brain, MCP servers, installed skills, and the current git branch. |
| Web grounding |
sanook web status separates local brain search from true internet search, detects configured MCP web/search/fetch candidates, and prints Sanook's citation + prompt-injection policy for current external facts. |
| Repo map | A lightweight symbol map of the repo (zero-dep, git-aware) is injected at session start so the agent picks the right files without blind grepping. |
| Skills | Built-in skills + install your own from a GitHub repo, URL, or local path. The agent can also author new skills after a repeatable task. |
| Custom commands | Drop a .sanook/commands/<name>.md prompt template and call it as /<name> (project commands require trust). Arguments replace $ARGUMENTS or {{ args }}; without a placeholder they are appended. |
| Subagents | A task tool spawns a fresh-context sub-agent for scoped exploration without bloating the main context — read-only by default, depth-guarded. |
| Gateway + cron |
sanook gateway run (alias: sanook serve) runs a long-lived daemon: a loopback OpenAI-compatible HTTP endpoint plus a cron scheduler. Scheduled tasks can use --to to deliver results back through the messaging gateway. |
| Channels | `sanook gateway setup telegram |
| MCP | Connect any Model Context Protocol server over stdio or remote Streamable-HTTP (filesystem, GitHub, Postgres, hosted servers, …) via ~/.sanook/mcp.json. |
| Git | Branch, uncommitted changes, and recent commits are injected automatically, with git_status / git_diff / git_log / git_commit tools. |
| Hooks | Run your own command before/after any tool. A non-zero PreToolUse exit blocks the tool — enforce lint, format, or policy. |
| Plan mode |
--plan restricts the agent to read-only tools and asks it to produce a plan before touching anything. |
| Auto-compaction | A token-aware sliding window keeps long sessions under the context limit with zero extra LLM cost. |
| Prompt budget inspectability |
sanook prompt-size [--json] reports the offline size of Sanook's system prompt, personality overlay, auto-memory, skills index, second-brain context, project memory, repo map, git context, and built-in tool schemas. |
| Polyglot runtime surface |
sanook runtimes [--json] shows optional Python/Rust readiness. The agent gets run_python for data/document/ML-style helper scripts and run_rust for small performance/safety-critical helpers, both approval-gated and no-shell. |
| Second brain |
sanook brain init scaffolds a structured Obsidian "second-brain" workspace (folders + _Index + a portable AI operating constitution) for organising work and giving the agent durable, cross-session memory. |
| Self-improvement | Sanook watches for repeated tasks and, past a threshold, auto-authors a skill for the pattern — announced in the terminal and surfaced on the Dashboard. Tune with SANOOK_SELF_IMPROVE_THRESHOLD; disable with SANOOK_DISABLE_SELF_IMPROVE=1. |
| Persona |
sanook persona runs a short questionnaire (who you are, language, tone, autonomy, stack, preferences) and stores the answers as protected memory plus a vault profile so every session starts in context. |
Teach the agent who you are once, and it carries that context into every session — name, language, tone, autonomy, your stack, and what you like (or don't like) it to do.
sanook persona # short questionnaire (A/B/C/D + free-text)
# or /persona inside the REPL — pre-fills from existing profileA mix of multiple-choice and free-text questions. Answers are saved in two places, wired into the agent automatically:
-
Auto-memory (
~/.sanook/memory/memory.json) as protected, owner-trust facts — injected at the start of every run, so the agent immediately knows how to address you and how you want it to work. -
Second brain (
{brainPath}/Shared/User-Persona/persona.md) as a readable profile note you can edit by hand — written only when a vault exists.
Re-run any time to update; existing answers are pre-filled from persona.md and protected memory. Blanks are skipped, and the vault profile is rewritten in place. The brain wizard (sanook brain init) already seeds a lighter identity (name + AI name + autonomy); sanook persona is the deeper, standalone pass.
A local, Hermes-style admin panel for everything the CLI knows — open it in a browser and drive Sanook without leaving the page.
sanook dashboard # http://127.0.0.1:9119
sanook dashboard --port 8080| Page | What it shows |
|---|---|
| Terminal | A real web terminal — an Agent console (the Sanook REPL, streamed over SSE with live tool activity + color-coded diffs) and an optional Raw shell (a system PTY via xterm.js, enabled when the node-pty + ws optional deps are installed). |
| Skills | Built-in and installed skills, including the ones Sanook auto-authored from repeated tasks. |
| Memory | Your structured auto-memory facts (incl. persona) with tier/trust. |
| Persona | Your saved persona profile (sanook persona / /persona) — view answers and profile path. |
| Usage | Token/cost ledger, daily/weekly/monthly. |
| Self-improve | The task ledger — what's repeating and which skills were created. |
| Install | The multi-platform install commands (see Quickstart), with copy buttons. |
| Sessions · Models · Files · Logs · Cron · Channels · Config · MCP · Brain | Inspect and manage the same surfaces as the CLI. |
It binds to loopback only and reads the same ~/.sanook/ state as the CLI, so the Dashboard and terminal always agree.
One model spec, nine providers. Switch with -m <spec> on the command line or /model in the REPL.
| Provider | Spec example | Key |
|---|---|---|
| Anthropic (Claude) |
-m sonnet, -m opus, -m haiku
|
ANTHROPIC_API_KEY |
| Google (Gemini) |
-m gemini, -m google:gemini-2.5-flash
|
GOOGLE_GENERATIVE_AI_API_KEY |
| OpenAI |
-m gpt, -m openai:gpt-5.5
|
OPENAI_API_KEY |
| xAI (Grok) | -m grok |
XAI_API_KEY |
| Mistral | -m mistral |
MISTRAL_API_KEY |
| Groq | -m groq:fast |
GROQ_API_KEY |
| Ollama | -m ollama |
— (local) |
| LM Studio | -m lmstudio |
— (local) |
| OpenAI Codex | -m codex |
via the official Codex CLI |
A spec is an alias (sonnet), a provider:model-id pair (openai:gpt-5.5), or a bare model id. sanook models <provider> lists the curated ids and, when a key is set, verifies them against the provider's live /models endpoint.
sanook models # list all providers
sanook models anthropic # curated ids (+ live verification if a key is set)
sanook auth list # redacted key status for every provider
sanook auth status openai # env/store/console detailssanook "<task>" run one task (headless)
sanook -z "<task>" one-shot final output (script-friendly)
sanook chat -q "<query>" direct one-shot query
sanook interactive REPL
sanook setup [section] setup model/gateway/tools/agent/brain
sanook model choose provider + model
sanook persona questionnaire → durable persona (memory + second brain)
sanook dashboard [--port] local web admin UI (terminal, skills, memory, usage, install)
sanook status redacted install/config status
sanook auth list redacted provider key status
sanook auth add openai --api-key <key> [--use]
sanook sessions list saved sessions for this project
sanook sessions show <id>
sanook sessions export <id> [--format json|markdown] [--output path]
sanook sessions rename <id> <title>
sanook sessions stats [--all]
sanook sessions prune --keep N [--all] [--yes]
sanook sessions rm <id>
sanook dump [--show-keys] support dump (keys are still redacted)
sanook usage [daily|weekly|monthly|session] [--days N] [--json]
sanook prompt-size [--json] inspect system/brain/skill/tool context budget
sanook runtimes [--json] inspect optional Python/Rust runtime surface
sanook web status [--json] inspect true web-search/fetch readiness
sanook web doctor [--json] probe web/search/fetch MCP candidates
sanook -c "<task>" resume the latest session for this project
sanook --resume <id> resume a specific saved session
sanook --continue-any resume the newest session across all projects
sanook --plan "<task>" plan mode (read-only)
sanook --json "<task>" JSONL output for scripts / CI
sanook update update the CLI to the latest npm release
-m, --model <spec> model or provider:model-id
--provider <id> provider shortcut for `sanook chat`
-b, --budget <usd> stop when estimated cost exceeds this
-y, --yes auto-approve tool calls (skip ask-mode)
--yolo alias for --yes
-v, --version print version
-h, --help show help
REPL slash commands: /new · /reset · /status · /model · /personality · /platforms · /tools · /skills · /cost · /usage · /insights · /diff · /retry · /stop · /undo · /rewind · /clear · /compact · /compress · /help · /quit — plus your own .sanook/commands/*.md. Input supports ↑/↓ history, @file mentions (text or image), and multiline (trailing \ or Alt+Enter).
Use the built-in updater whenever a new CLI version is available:
sanook update
sanook update --check # check onlyIt checks the npm latest release for sanook-cli and, when newer than your installed version, runs npm install -g sanook-cli@latest.
When you launch the interactive TUI with plain sanook, the CLI checks for updates at most once per day. If a newer version exists, it asks Yes/No before running the same updater. Set SANOOK_DISABLE_UPDATE_CHECK=1 to silence the prompt.
sanook gateway run starts a single long-lived foreground process that hosts an HTTP API, a cron scheduler, and optional chat channels — all driving the same agent core. sanook gateway start runs the same gateway in the background and records its pid/log path under ~/.sanook/gateway/. sanook serve remains as a compatibility alias.
sanook gateway status # redacted gateway config + token path
sanook gateway run --port 8787 # HTTP (127.0.0.1 only) + scheduler
sanook gateway start --port 8787 # background process + pid/log tracking
sanook gateway stop
sanook gateway restart
sanook gateway install # write launchd/systemd helper file
sanook serve --port 8787 # compatibility alias
sanook cron add "every 30m" "check the CI" # also "09:00", an ISO time, or "now"
sanook cron add "every 30m" "check the CI" --to slack:C01ABCDEF
sanook cron add "09:00" "summarise calendar" --to email:owner@example.com --model openai:gpt-5.1
sanook cron list
sanook cron rm <id>The HTTP server binds to loopback only and authenticates every endpoint (except /health) with a bearer token stored at ~/.sanook/gateway/token (chmod 600). It runs mutating tools in ask mode by default; opt into unattended writes with sanook config set permissionMode auto or SANOOK_GATEWAY_ALLOW_WRITE=1. It speaks the OpenAI chat-completions shape, so existing clients work unchanged:
curl http://127.0.0.1:8787/v1/chat/completions \
-H "Authorization: Bearer $(cat ~/.sanook/gateway/token)" \
-H 'content-type: application/json' \
-d '{"messages":[{"role":"user","content":"summarise today's commits"}]}'| Method | Endpoint | Purpose |
|---|---|---|
GET |
/health |
liveness (public) |
POST |
/v1/chat/completions |
run the agent (OpenAI-compatible) |
GET / POST
|
/tasks |
list / enqueue scheduled tasks |
Use the setup command, or set environment variables before sanook gateway run. Telegram uses long-polling (no public URL needed), Discord uses the Gateway websocket, Slack uses Socket Mode, Mattermost uses REST API v4 plus websocket events, Home Assistant uses /api/websocket for watched state_changed events plus REST persistent_notification.create for replies, Email uses IMAP polling plus SMTP threaded replies, LINE uses the official Messaging API webhook + Reply/Push endpoints, SMS uses Twilio Programmable Messaging with X-Twilio-Signature validation, ntfy uses the HTTP JSON stream + publish API, Signal uses signal-cli daemon --http with JSON-RPC + Server-Sent Events, WhatsApp Cloud uses Meta's official webhook + Graph Messages API, Matrix uses the Matrix Client-Server sync/send API, Google Chat uses incoming webhooks or service-account Chat REST API sends with Pub/Sub config saved for future inbound, BlueBubbles/iMessage uses the BlueBubbles REST API for outbound text with webhook settings saved for inbound parity, Microsoft Teams supports Incoming Webhook delivery and Graph chat/channel delivery, and generic Webhooks accept GitHub/GitLab/Jira/Stripe-style events with HMAC validation.
sanook gateway setup # platform menu
sanook gateway setup telegram --bot-token 123:abc --allowed-chats 5222385839
sanook gateway setup discord --bot-token "$DISCORD_BOT_TOKEN" --channel 123456789012345678
sanook gateway setup slack --bot-token "$SLACK_BOT_TOKEN" --app-token "$SLACK_APP_TOKEN" --channel C01ABCDEF
sanook gateway setup mattermost --url https://mm.example.com --token "$MATTERMOST_TOKEN" \
--allowed-users user_id_1 --home-channel chan_home_id --thread-replies
sanook gateway setup homeassistant --url http://homeassistant.local:8123 --token "$HASS_TOKEN" \
--home-channel sanook_agent --watch-domains light,binary_sensor,climate
sanook gateway setup email --address bot@example.com --password "$EMAIL_PASSWORD" \
--imap-host imap.example.com --smtp-host smtp.example.com --home-address owner@example.com
sanook gateway setup line --channel-access-token "$LINE_CHANNEL_ACCESS_TOKEN" \
--channel-secret "$LINE_CHANNEL_SECRET" --home-channel U1234567890abcdef
sanook gateway setup sms --account-sid "$TWILIO_ACCOUNT_SID" --auth-token "$TWILIO_AUTH_TOKEN" \
--phone-number "$TWILIO_PHONE_NUMBER" --home-channel +15551234567 \
--webhook-url https://your-tunnel.example.com/sms/webhook
sanook gateway setup ntfy --topic sanook-yourname-2026 --token "$NTFY_TOKEN" --markdown
sanook gateway setup signal --account +15550000000 --home-channel +15551234567 \
--http-url http://127.0.0.1:8080
sanook gateway setup whatsapp --phone-number-id "$WHATSAPP_CLOUD_PHONE_NUMBER_ID" \
--access-token "$WHATSAPP_CLOUD_ACCESS_TOKEN" --app-secret "$WHATSAPP_CLOUD_APP_SECRET" \
--home-channel 15551234567 --public-url https://your-tunnel.example.com
sanook gateway setup matrix --homeserver https://matrix.example.org \
--access-token "$MATRIX_ACCESS_TOKEN" --allowed-users @alice:matrix.org \
--home-room '!abc123:matrix.example.org'
sanook gateway setup googlechat --service-account-json "$GOOGLE_CHAT_SERVICE_ACCOUNT_JSON" \
--home-channel spaces/AAAA --allowed-spaces spaces/AAAA
sanook gateway setup googlechat --incoming-webhook-url "$GOOGLE_CHAT_INCOMING_WEBHOOK_URL"
sanook gateway setup bluebubbles --server-url http://localhost:1234 --password "$BLUEBUBBLES_PASSWORD" \
--home-channel user@example.com --allowed-users user@example.com,+15551234567
sanook gateway setup teams --incoming-webhook-url "$TEAMS_INCOMING_WEBHOOK_URL"
sanook gateway setup teams --delivery-mode graph --graph-access-token "$TEAMS_GRAPH_ACCESS_TOKEN" \
--chat-id '19:chatid@thread.v2'
sanook gateway setup webhooks --secret "$WEBHOOK_SECRET" --public-url https://your-tunnel.example.com
sanook webhook subscribe github-issues --events issues \
--prompt "New issue #{issue.number}: {issue.title}\n{issue.html_url}" --to slack:C01ABCDEF
sanook webhook subscribe deploy-notify --events push --deliver-only \
--prompt "Push to {repository.full_name}: {head_commit.message}" --to sms
sanook gateway run
sanook send --to telegram "deploy finished"
sanook send --to discord "deploy finished"
sanook send --to slack:C01ABCDEF "deploy finished"
sanook send --to mattermost:chan_home_id "deploy finished"
sanook send --to homeassistant:doorbell "deploy finished"
sanook send --to email:owner@example.com --subject "[CI]" "deploy finished"
sanook send --to line "deploy finished"
sanook send --to sms "deploy finished"
sanook send --to ntfy "deploy finished"
sanook send --to signal "deploy finished"
sanook send --to whatsapp "deploy finished"
sanook send --to matrix "deploy finished"
sanook send --to matrix:'!ops:matrix.example.org' "deploy finished"
sanook send --to googlechat "deploy finished"
sanook send --to googlechat:spaces/AAAA/threads/thread-1 "threaded update"
sanook send --to bluebubbles "deploy finished"
sanook send --to bluebubbles:'iMessage;-;user@example.com' "deploy finished"
sanook send --to teams "deploy finished"
sanook send --to teams:'19:chatid@thread.v2' "deploy finished"
sanook cron add "every 30m" "check the CI" --to line
sanook cron add "09:00" "daily check-in" --to sms
sanook cron add "09:00" "daily check-in" --to ntfy
sanook cron add "09:00" "daily check-in" --to mattermost
sanook cron add "09:00" "daily check-in" --to homeassistant
sanook cron add "09:00" "daily check-in" --to signal
sanook cron add "09:00" "daily check-in" --to whatsapp
sanook cron add "09:00" "daily check-in" --to matrix
sanook cron add "09:00" "daily check-in" --to googlechat
sanook cron add "09:00" "daily check-in" --to bluebubbles
sanook cron add "09:00" "daily check-in" --to teams
sanook send --to telegram --subject "[CI]" --file build.log
echo "RAM 92%" | sanook send --to telegram --quiet
sanook send --to telegram:5222385839:17585 "threaded reply"
sanook webhook list
sanook webhook test github-issues --payload '{"event_type":"issues","issue":{"number":42,"title":"Test"}}'
sanook send --list --jsonEnvironment overrides still work:
export TELEGRAM_BOT_TOKEN=123:abc
export TELEGRAM_ALLOWED_CHATS=5222385839 # required — comma-separated chat ids
export LINE_CHANNEL_ACCESS_TOKEN=xxx
export LINE_HOME_CHANNEL=U1234567890abcdef
export TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export TWILIO_AUTH_TOKEN=xxx
export TWILIO_PHONE_NUMBER=+15550000000
export SMS_HOME_CHANNEL=+15551234567
export SMS_WEBHOOK_URL=https://your-tunnel.example.com/sms/webhook
export NTFY_TOPIC=sanook-yourname-2026
export NTFY_ALLOWED_USERS=sanook-yourname-2026
export NTFY_HOME_CHANNEL=sanook-yourname-2026
export MATTERMOST_URL=https://mm.example.com
export MATTERMOST_TOKEN=xxx
export MATTERMOST_HOME_CHANNEL=chan_home_id
export MATTERMOST_ALLOWED_USERS=user_id_1,user_id_2
export MATTERMOST_ALLOWED_CHANNELS=chan_home_id,chan_ops_id
export MATTERMOST_REQUIRE_MENTION=true
export MATTERMOST_REPLY_MODE=thread
export HASS_URL=http://homeassistant.local:8123
export HASS_TOKEN=xxx
export HASS_HOME_CHANNEL=sanook_agent
export HASS_WATCH_DOMAINS=light,binary_sensor,climate
export HASS_WATCH_ENTITIES=sensor.temp,alarm_control_panel.home
export HASS_COOLDOWN_SECONDS=30
export SIGNAL_HTTP_URL=http://127.0.0.1:8080
export SIGNAL_ACCOUNT=+15550000000
export SIGNAL_HOME_CHANNEL=+15551234567
export SIGNAL_ALLOWED_USERS=+15551234567
export SIGNAL_GROUP_ALLOWED_USERS=groupIdBase64
export WHATSAPP_CLOUD_PHONE_NUMBER_ID=123456789012345
export WHATSAPP_CLOUD_ACCESS_TOKEN=EAA...
export WHATSAPP_CLOUD_APP_SECRET=xxx
export WHATSAPP_CLOUD_VERIFY_TOKEN=choose-a-long-random-token
export WHATSAPP_CLOUD_HOME_CHANNEL=15551234567
export WHATSAPP_CLOUD_ALLOWED_USERS=15551234567,15557654321
export WHATSAPP_CLOUD_PUBLIC_URL=https://your-tunnel.example.com
export WHATSAPP_CLOUD_API_VERSION=v20.0
export MATRIX_HOMESERVER=https://matrix.example.org
export MATRIX_ACCESS_TOKEN=syt_...
export MATRIX_USER_ID=@sanook:matrix.example.org
export MATRIX_HOME_ROOM='!abc123:matrix.example.org'
export MATRIX_ALLOWED_USERS=@alice:matrix.org,@bob:matrix.org
export MATRIX_ALLOWED_ROOMS='!abc123:matrix.example.org,!ops:matrix.example.org'
export MATRIX_REQUIRE_MENTION=true
export MATRIX_FREE_RESPONSE_ROOMS='!free:matrix.example.org'
export TEAMS_DELIVERY_MODE=incoming_webhook
export TEAMS_INCOMING_WEBHOOK_URL=https://...
export TEAMS_GRAPH_ACCESS_TOKEN=xxx
export TEAMS_CHAT_ID='19:chatid@thread.v2'
export TEAMS_HOME_CHANNEL='19:chatid@thread.v2'
export WEBHOOK_ENABLED=true
export WEBHOOK_SECRET=xxx
export WEBHOOK_PUBLIC_URL=https://your-tunnel.example.com
sanook gateway runMessaging channels are fail-closed by default: configure a home target or allowlist before accepting remote users, and internal errors are redacted before they reach chat surfaces. See Security.
Inside Telegram/Discord/Slack/Mattermost/Email/LINE/SMS/ntfy/Signal/WhatsApp/Matrix conversations, Hermes-style commands are handled without calling the model: /new, /reset, /model, /personality, /retry, /undo, /compress, /usage, /insights, /stop, /status, /sethome, and /help. Matrix and Mattermost also accept !new, !reset, !status, and !help aliases for clients that reserve /.
For ntfy, the topic is the identity and trust boundary: use a long random topic, a private/reserved topic with NTFY_TOKEN, or a self-hosted ntfy server with ACLs. Sanook authorizes inbound ntfy messages by topic, not by the user-controlled notification title.
For Mattermost, use a dedicated bot account/token, set MATTERMOST_ALLOWED_USERS to Mattermost user IDs, and optionally restrict shared channels with MATTERMOST_ALLOWED_CHANNELS. DMs respond without a mention; public/private channels require @botname unless MATTERMOST_REQUIRE_MENTION=false or the channel is listed in MATTERMOST_FREE_RESPONSE_CHANNELS. Cron/send uses MATTERMOST_HOME_CHANNEL, mattermost:channel_id, or mattermost:channel_id:root_post_id for a threaded reply.
For Home Assistant, create a Long-Lived Access Token, keep HASS_WATCH_DOMAINS/HASS_WATCH_ENTITIES narrow, and use HASS_IGNORE_ENTITIES plus HASS_COOLDOWN_SECONDS for noisy sensors. No state events are forwarded unless a watch filter or HASS_WATCH_ALL=true is configured. Conversation tools can read entities/states/services, while ha_call_service is approval-gated and blocks unsafe domains such as shell_command, command_line, python_script, pyscript, hassio, and rest_command.
For Signal, run signal-cli daemon --http 127.0.0.1:8080 locally, set SIGNAL_ACCOUNT, and keep SIGNAL_ALLOWED_USERS or SIGNAL_HOME_CHANNEL set so inbound DMs fail closed. Groups are disabled unless explicitly listed in SIGNAL_GROUP_ALLOWED_USERS or set to *; group send targets use signal:group:<groupId>.
For WhatsApp Cloud inbound messages, expose the gateway through a tunnel and set the Meta webhook callback URL to https://<your-tunnel>/whatsapp/webhook. Use the generated verify token for Meta's GET challenge, keep WHATSAPP_CLOUD_APP_SECRET set so Sanook can validate X-Hub-Signature-256, and check GET /whatsapp/webhook/health first. Send targets use country-code digits without + (for example whatsapp:15551234567), and normal WhatsApp 24-hour customer-service window limits still apply.
For Matrix, create a bot account on your homeserver, copy an access token from Element (or use MATRIX_USER_ID + MATRIX_PASSWORD), invite the bot to rooms, and keep MATRIX_ALLOWED_USERS set so inbound users fail closed. DMs respond without a mention; shared rooms require a bot mention unless MATRIX_REQUIRE_MENTION=false or the room is listed in MATRIX_FREE_RESPONSE_ROOMS. Cron/send uses MATRIX_HOME_ROOM or an explicit target like matrix:!abc123:matrix.example.org.
For Google Chat, use GOOGLE_CHAT_INCOMING_WEBHOOK_URL for the fastest proactive send path, or configure GOOGLE_CHAT_SERVICE_ACCOUNT_JSON plus GOOGLE_CHAT_HOME_CHANNEL (spaces/...) for Chat REST API delivery. Pub/Sub fields (GOOGLE_CHAT_PROJECT_ID, GOOGLE_CHAT_SUBSCRIPTION_NAME, GOOGLE_CHAT_ALLOWED_USERS) are saved now for inbound parity work. Cron/send uses googlechat, googlechat:spaces/..., or googlechat:spaces/.../threads/....
For BlueBubbles/iMessage, run a BlueBubbles Server and configure BLUEBUBBLES_SERVER_URL, BLUEBUBBLES_PASSWORD, and BLUEBUBBLES_HOME_CHANNEL (chat GUID, email, or +E.164 phone). Sanook resolves email/phone targets through /api/v1/chat/query, sends text via /api/v1/message/text, and keeps webhook fields (BLUEBUBBLES_WEBHOOK_HOST, BLUEBUBBLES_WEBHOOK_PORT, BLUEBUBBLES_WEBHOOK_PATH, mention gating) ready for inbound parity work.
For Microsoft Teams, use TEAMS_DELIVERY_MODE=incoming_webhook with a channel Incoming Webhook for simple proactive send/cron delivery, or TEAMS_DELIVERY_MODE=graph with TEAMS_GRAPH_ACCESS_TOKEN plus TEAMS_CHAT_ID or TEAMS_TEAM_ID + TEAMS_CHANNEL_ID. Teams chat IDs can contain colons, so quote explicit targets such as teams:'19:chatid@thread.v2' in shells.
For LINE inbound messages, expose the gateway through a tunnel and set the LINE Developers Console webhook URL to https://<your-tunnel>/line/webhook. Check the tunnel with GET /line/webhook/health first.
For SMS inbound messages, expose the gateway through a tunnel and set the Twilio Messaging webhook URL to https://<your-tunnel>/sms/webhook. Set SMS_WEBHOOK_URL to that exact URL so Sanook can validate X-Twilio-Signature; check GET /sms/webhook/health first.
For generic webhooks, create routes with sanook webhook subscribe <route> and point external services at https://<your-tunnel>/webhooks/<route>. Sanook accepts GitHub X-Hub-Signature-256, GitLab X-Gitlab-Token, or generic X-Webhook-Signature; check GET /webhooks/health first.
A skill is a SKILL.md file (front-matter + instructions) the agent loads on demand. Sanook ships with built-in skills and can install more.
sanook skill list # browse all skills
sanook skill add anthropics/skills # from a GitHub repo
sanook skill add https://…/SKILL.md # from a URL
sanook skill add ./my-skill # from a local path
sanook skill remove my-skill
⚠️ A skill is an instruction the agent will follow. Install only from sources you trust.
Scaffold a structured Obsidian workspace — the durable layer Sanook reads and writes alongside ~/.sanook/:
sanook brain init # interactive — asks where + a few identity questions
sanook brain init ~/notes/brain # non-interactive (with --yes)The wizard creates a full folder taxonomy (Projects/, Sessions/, Shared/ memory layer, Goals/, Research/, Skills/, …), an _Index.md in every folder, seed memory files, and a portable AI operating constitution (CLAUDE.md / GEMINI.md / AGENTS.md / SANOOK.md). It ships with research-backed operating rules — context-assembly (anti context-rot), an intake quarantine + injection-scan gate, bi-temporal fact validity, provenance tracking, a verification-gated Skills/ library, and sleep-time consolidation.
Every folder documents its own job. Each folder gets a generated _Index.md stating its role, what goes there, what doesn't, and an AI routing contract — and a top-level Vault Structure Map.md lists the whole taxonomy in one place. So both you and the agent always know where a note belongs. Identity lives in Shared/User-Persona/ (see Persona); what the agent learns over time lives in Shared/User-Memory/.
Create-if-missing — re-running never overwrites your notes. On init, Sanook also links your current repo (Projects/<slug>/ + SANOOK.md) and can wire a filesystem MCP server so the agent reads and writes the vault directly.
See Memory & second brain for the exact routing table (worklog, session notes, inbox, auto-memory, sessions).
Ranked search over the vault and the agent's memory, past sessions, and skills — one surface, no native binaries:
sanook index # incremental index of vault + memory + sessions + skills (O(delta))
sanook search "vercel edge deploy" # ranked hits with snippets
sanook search "race condition" --mode hybrid --source vault,memory --limit 5-
Zero-config floor — a pure-TypeScript BM25 inverted index (genuine corpus-stat IDF, title boost,
Intl.Segmenterword breaks for Thai). No SQLite, no Bun, no native binary, no API key, no network. -
Optional semantic —
--mode hybrid|semanticembeds through your existing provider key (OpenAI / Mistral / Google / …), stores compact Float32 vectors locally, and fuses with BM25 via Reciprocal Rank Fusion. Activates only when a key resolves; degrades silently to BM25 otherwise. Configure withsanook config set embeddingModel openai:text-embedding-3-small(orSANOOK_EMBEDDING_MODEL). - Incremental — only changed files are re-read (mtime+sha manifest); deleted files are evicted. Run after editing the vault, or wire it into a hook/cron.
The agent's recall tool uses the same engine, so remembered facts and vault notes are searchable the moment they exist.
Connect Model Context Protocol servers over stdio or remote Streamable-HTTP with the same config shape you already use elsewhere:
Discover and install servers from the official MCP registry:
sanook mcp search gitlab
sanook mcp info com.gitlab/mcp
sanook mcp install com.gitlab/mcp --name gitlab
sanook mcp preset dev
sanook mcp preset research
sanook mcp test gitlab
sanook mcp doctor
sanook web status
sanook web doctorUse --env KEY=value or --header KEY=value when a registry entry requires secrets, and --project to write to a trusted project .sanook/mcp.json. Some hosted MCP endpoints may return 401 Unauthorized until you pass an auth header, even when the registry entry does not declare it yet. You can still add servers manually: sanook mcp add fs npx -y @modelcontextprotocol/server-filesystem /path (stdio) or sanook mcp add remote https://example.com/mcp (a URL is detected as remote HTTP).
Their tools are merged into the agent's toolset automatically. /tools in the REPL lists everything currently available. sanook search is local retrieval over the second brain, sessions, memory, and skills; true internet search comes from configured MCP web/search/fetch servers. Use sanook web status to see which web candidates are configured, and sanook web doctor to probe their advertised tools.
sanook is also an MCP server: sanook mcp serve exposes your brain (sanook_search / sanook_recall / sanook_remember / sanook_index / sanook_stats) over stdio, so Claude Desktop, Cursor, or any MCP host can query it:
// in another host's MCP config
{ "mcpServers": { "sanook-brain": { "command": "sanook", "args": ["mcp", "serve"] } } }Project-local .sanook/mcp.json, .sanook/hooks.json, .sanook/skills/, and .sanook/commands/ are ignored until the project is trusted:
sanook trust status
sanook trust add # allow this project's .sanook mcp/hooks/skills/commands
sanook trust removeEverything lives under ~/.sanook/ (with per-project .sanook/ overrides where relevant):
~/.sanook/config.json brainPath, model, tuning knobs
~/.sanook/auth.json API keys (chmod 600)
~/.sanook/memory/ auto-memory store (memory.json + MEMORY.md view)
~/.sanook/usage/ token/cost ledger (events.jsonl)
~/.sanook/search/ brain-search index + optional embedding vectors
~/.sanook/sessions/ saved conversations (for --continue)
~/.sanook/skills/<name>/ installed SKILL.md files
~/.sanook/mcp.json MCP servers { "mcpServers": { … } }
~/.sanook/hooks.json PreToolUse / PostToolUse hooks
~/.sanook/gateway/ gateway token + task ledger
~/.sanook/trusted-projects.json
SANOOK.md project memory (hierarchical, cwd → project root → global)
{brainPath}/ your Obsidian vault — worklogs, session notes, Projects/, Shared/
Untrusted project config can set ordinary project defaults, but it cannot lower permissionMode to auto; trust the project first if you want project-local config to control mutation approval.
Quality-neutral knobs in ~/.sanook/config.json (or the matching SANOOK_* env var) to cut tokens/cost:
config set … |
env | effect |
|---|---|---|
cacheTtl 1h |
SANOOK_CACHE_TTL=1h |
keep the cached system preamble alive for 1h (default 5m) — cheaper to resume after a pause |
contextCompression selective |
SANOOK_CONTEXT_COMPRESSION=selective |
zero-LLM, query-aware selective compression for stale, very large tool outputs before each model step (default selective; set off to disable) |
contextCompression headroom |
SANOOK_CONTEXT_COMPRESSION=headroom |
wrap the Vercel AI SDK model with headroom-ai when you run a Headroom proxy/cloud setup (SANOOK_HEADROOM_BASE_URL / SANOOK_HEADROOM_API_KEY) |
compaction summarize |
SANOOK_COMPACTION=summarize |
when context gets long, condense it with a cheap model instead of truncating — better recall at the same budget (default truncate, zero-LLM) |
| — | SANOOK_SUBAGENT_MODEL=haiku |
run all sub-agent work (exploration/search) on a cheaper model while the main agent keeps the strong one |
summaryModel <spec> |
SANOOK_SUMMARY_MODEL=<spec> |
model used for summarize-compaction (default: the fast sibling of your main model) |
embeddingModel <spec> |
SANOOK_EMBEDDING_MODEL=<spec> |
model used for semantic search embeddings (for example openai:text-embedding-3-small) |
brainTranscript on |
SANOOK_BRAIN_TRANSCRIPT=1 |
append the full conversation (prompt + reply, every turn) to {brainPath}/Sessions/*-chat.md (default off) |
thinking on / thinking 4000
|
SANOOK_THINKING=on / 4000
|
opt-in Anthropic extended thinking on the main agent; use on, true, or yes for the default budget, off, false, or no to disable, or a positive integer for budgetTokens
|
Read-side savings are automatic: the agent reads file ranges (read_file with offset/limit) and edits with minimal old_string / replace_all rather than rewriting whole files.
Useful environment flags:
SANOOK_MODEL=sonnet # default model alias or provider:model
SANOOK_ALLOW_OUTSIDE_WORKSPACE=1 # allow file tools outside cwd/brain
SANOOK_GATEWAY_ALLOW_WRITE=1 # let sanook serve run mutating tools unattended
SANOOK_HOOKS_INHERIT_ENV=1 # pass full env to hooks instead of a minimal safe env
SANOOK_DISABLE_PERSISTENCE=1 # do not save sessions, memory, prompt history, or worklogs
SANOOK_DISABLE_UPDATE_CHECK=1 # do not show interactive update prompts
SANOOK_DISABLE_WORKLOG=1 # do not append second-brain worklogs
SANOOK_DISABLE_USAGE=1 # do not append token/cost usage events
SANOOK_TRUST_PROJECT=1 # temporary trust override for project .sanook extensionsSanook runs shell commands and edits files, so safety is built into the core rather than bolted on:
-
BYOK, direct keys only — OAuth and subscription tokens are rejected by an explicit guard (
ya29.,Bearer,sk-ant-oat…). This keeps you within every provider's terms of service. -
Permission gate — destructive commands (
rm -rf,git reset --hard,push --force, fork bombs, …), protected paths (.env,.git,node_modules, credential folders), and paths outside the workspace/brain are denied unless explicitly opted in. -
OS sandbox —
run_bashruns under Seatbelt (macOS) / bubblewrap (Linux) when available, confining shell writes to the workspace/brain/tmp — defense in depth beyond the regex blocklist (SANOOK_NO_SANDBOX=1to disable). -
Project trust gate — project
.sanook/mcp.json,.sanook/hooks.json,.sanook/skills/, and.sanook/commands/can execute or steer the agent, so they are ignored untilsanook trust add. - Secret redaction — API keys are stripped from error messages, saved sessions, memory, and worklogs.
- Safe fallback — provider fallback does not retry after a mutating tool call has already happened, avoiding duplicate side effects.
-
Gateway — HTTP binds to
127.0.0.1only and requires a bearer token on every non-health endpoint. - Telegram — fail-closed: a required allowlist, private-chat-only, per-chat rate-limiting, and generic error replies that never reveal internal paths.
-
Email — use a dedicated mailbox and app password; store only app passwords, require an allowlist/home address by default, and keep SMTP/IMAP credentials in
~/.sanook/gateway/config.json(chmod 600). -
LINE — use a long-lived Messaging API channel access token, keep a home/allowed target list by default, and store the token/secret in
~/.sanook/gateway/config.json(chmod 600). -
ntfy — treat the topic as a shared secret unless you protect it with ntfy auth/ACLs; Sanook requires
NTFY_ALLOWED_USERS/home topic orNTFY_ALLOW_ALL_USERSbefore subscribing. -
Mattermost — use a dedicated bot token, require
MATTERMOST_ALLOWED_USERSby default, optionally restrict channels withMATTERMOST_ALLOWED_CHANNELS, and keep public/private channels on mention-only mode unless you deliberately list free-response channels. -
Home Assistant — use a dedicated Long-Lived Access Token, keep watch filters narrow, leave
HASS_WATCH_ALLoff by default, and require approval for device-control calls throughha_call_service. -
Signal — keep
signal-cliHTTP bound to loopback, require DM allowlists by default, and enable groups only withSIGNAL_GROUP_ALLOWED_USERS(or*deliberately). -
WhatsApp Cloud — use a dedicated Meta app/access token, keep
WHATSAPP_CLOUD_APP_SECRETand a long verify token configured, require home/allowed wa_ids by default, and verify every inbound POST withX-Hub-Signature-256. -
Matrix — use a dedicated bot account/access token, require
MATRIX_ALLOWED_USERSby default, optionally restrict shared rooms withMATRIX_ALLOWED_ROOMS, and treat password login as a convenience fallback rather than the preferred production mode. -
Google Chat — keep service account JSON files chmod 600, do not paste private keys into shell history, keep incoming webhook URLs out of logs/support dumps, and restrict proactive sends with
GOOGLE_CHAT_HOME_CHANNELorGOOGLE_CHAT_ALLOWED_SPACES. -
Microsoft Teams — prefer narrowly scoped Graph permissions or a dedicated channel Incoming Webhook, keep tokens out of logs/support dumps, and treat
TEAMS_CLIENT_SECRETas future inbound bot credential material.
Hardened across several adversarial security reviews covering command injection, prompt injection, concurrency, and credential leakage.
npm install
npm run build # → dist/
npm test # vitest
npm run typecheck # tsc --noEmit (strict)
npm run dev -- "…" # run from source without buildingCI runs the suite across macOS / Linux / Windows on Node 22 and 24. Requires Node ≥ 22.
Built by Sanook AI
AI tools & education · 🇹🇭
Facebook
·
X / Twitter
TypeScript · Vercel AI SDK · no framework, no magic