🍎 macOS · 🐧 Linux · WSL

Recommended β€” npm

npm install -g sanook-cli
# or run once without installing:
npx sanook-cli

One-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 | bash

Homebrew

brew trust Sir-chawakorn/tap    # once on newer Homebrew
brew tap Sir-chawakorn/tap
brew install sanook-cli

πŸͺŸ Windows

Recommended β€” npm (PowerShell or cmd)

npm install -g sanook-cli
# or:
npx sanook-cli

One-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 | iex

WinGet (after PR #391114 merges)

winget install Sanook.SanookCLI

Portable zip (no WinGet yet): sanook-cli-win-x64.zip

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 recognized You ran npm i sanook-cli without -g β€” reinstall with npm install -g sanook-cli, or use npx sanook
Node too old Install Node β‰₯ 22 Β· scripts refuse older versions
Homebrew refuses tap Run brew trust Sir-chawakorn/tap once, then retry
Not sure what's wrong npx sanook doctor β€” prints OS-specific fixes (incl. Windows PATH one-liner)

Overview

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.

How it compares

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.

Memory & second brain

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/&lt;slug&gt;/ 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:2px

How 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.

What goes where

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=1

It 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

Quickstart

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

Features

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.

Persona

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 profile

A 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.

Dashboard

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.

Providers

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 details

Usage

sanook "<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] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--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).

Updating

Use the built-in updater whenever a new CLI version is available:

sanook update
sanook update --check   # check only

It 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.

Gateway & scheduling

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:[email protected] --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

Messaging channels

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 [email protected] --password "$EMAIL_PASSWORD" \
  --imap-host imap.example.com --smtp-host smtp.example.com --home-address [email protected]
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 [email protected] --allowed-users [email protected],+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:[email protected]'
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:[email protected] --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;-;[email protected]' "deploy finished"
sanook send --to teams "deploy finished"
sanook send --to teams:'19:[email protected]' "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 --json

Environment 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 H