sanook-cli 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +19 -0
- package/CHANGELOG.md +173 -0
- package/README.md +153 -20
- package/README.th.md +136 -0
- package/dist/agentContext.js +4 -0
- package/dist/approval.js +6 -0
- package/dist/bin.js +405 -57
- package/dist/brain.js +92 -59
- package/dist/brand.js +47 -0
- package/dist/checkpoint.js +37 -0
- package/dist/commands.js +86 -6
- package/dist/compaction.js +76 -5
- package/dist/config.js +100 -12
- package/dist/cost.js +60 -3
- package/dist/doctor.js +92 -0
- package/dist/gateway/auth.js +2 -2
- package/dist/gateway/ledger.js +2 -2
- package/dist/gateway/scheduler.js +1 -0
- package/dist/gateway/serve.js +6 -4
- package/dist/gateway/server.js +10 -2
- package/dist/git.js +11 -2
- package/dist/hooks.js +43 -17
- package/dist/knowledge.js +48 -49
- package/dist/loop.js +182 -66
- package/dist/lsp/client.js +173 -0
- package/dist/lsp/framing.js +56 -0
- package/dist/lsp/index.js +138 -0
- package/dist/lsp/servers.js +82 -0
- package/dist/mcp-server.js +244 -0
- package/dist/mcp.js +184 -29
- package/dist/memory-store.js +559 -0
- package/dist/memory.js +143 -29
- package/dist/orchestrate.js +150 -0
- package/dist/providers/codex.js +21 -7
- package/dist/providers/keys.js +3 -2
- package/dist/providers/models.js +22 -6
- package/dist/providers/registry.js +155 -1
- package/dist/repomap.js +93 -0
- package/dist/search/chunk.js +158 -0
- package/dist/search/embed-store.js +187 -0
- package/dist/search/engine.js +203 -0
- package/dist/search/fuse.js +35 -0
- package/dist/search/index-core.js +187 -0
- package/dist/search/indexer.js +241 -0
- package/dist/search/store.js +77 -0
- package/dist/session.js +42 -8
- package/dist/skill-install.js +10 -10
- package/dist/skills.js +12 -9
- package/dist/summarize.js +31 -0
- package/dist/tools/bash.js +21 -2
- package/dist/tools/diagnostics.js +41 -0
- package/dist/tools/edit.js +29 -7
- package/dist/tools/index.js +8 -1
- package/dist/tools/list.js +7 -2
- package/dist/tools/permission.js +90 -9
- package/dist/tools/read.js +23 -4
- package/dist/tools/remember.js +1 -1
- package/dist/tools/sandbox.js +61 -0
- package/dist/tools/search.js +105 -4
- package/dist/tools/task.js +195 -29
- package/dist/tools/timeout.js +35 -0
- package/dist/tools/util.js +10 -0
- package/dist/tools/write.js +6 -4
- package/dist/trust.js +89 -0
- package/dist/ui/app.js +228 -31
- package/dist/ui/banner.js +4 -9
- package/dist/ui/brain-wizard.js +2 -2
- package/dist/ui/history.js +30 -0
- package/dist/ui/mentions.js +44 -0
- package/dist/ui/render.js +55 -15
- package/dist/ui/setup.js +97 -12
- package/dist/ui/useEditor.js +83 -0
- package/dist/update.js +114 -0
- package/dist/worktree.js +173 -0
- package/package.json +11 -5
- package/scripts/postinstall.mjs +33 -0
- package/second-brain/.agents/_Index.md +30 -0
- package/second-brain/.agents/skills/_Index.md +30 -0
- package/second-brain/.agents/workflows/_Index.md +30 -0
- package/second-brain/AGENTS.md +4 -4
- package/second-brain/Acceptance/_Index.md +30 -0
- package/second-brain/Acceptance/golden-case-template.md +39 -0
- package/second-brain/Areas/_Index.md +30 -0
- package/second-brain/Bugs/System-OS/_Index.md +30 -0
- package/second-brain/Bugs/_Index.md +30 -0
- package/second-brain/CLAUDE.md +4 -1
- package/second-brain/Checklists/_Index.md +30 -0
- package/second-brain/Checklists/preflight-postflight-template.md +29 -0
- package/second-brain/Distillations/_Index.md +30 -0
- package/second-brain/Entities/_Index.md +30 -0
- package/second-brain/Entities/entity-template.md +33 -0
- package/second-brain/Evals/_Index.md +30 -0
- package/second-brain/Evals/correction-pairs.md +24 -0
- package/second-brain/Evals/failure-taxonomy.md +24 -0
- package/second-brain/Evals/golden-set.md +25 -0
- package/second-brain/Evals/quality-ledger.md +23 -0
- package/second-brain/Evals/self-eval-rubric.md +23 -0
- package/second-brain/GEMINI.md +4 -4
- package/second-brain/Goals/_Index.md +30 -0
- package/second-brain/Handoffs/_Index.md +30 -0
- package/second-brain/Home.md +7 -0
- package/second-brain/Intake/Raw Sources/_Index.md +30 -0
- package/second-brain/Intake/_Index.md +30 -0
- package/second-brain/Intake/_Quarantine/_Index.md +30 -0
- package/second-brain/Learning/_Index.md +30 -0
- package/second-brain/Playbooks/_Index.md +30 -0
- package/second-brain/Playbooks/playbook-template.md +23 -0
- package/second-brain/Projects/_Index.md +30 -0
- package/second-brain/Prompts/_Index.md +30 -0
- package/second-brain/README.md +2 -1
- package/second-brain/Research/_Index.md +30 -0
- package/second-brain/Retrospectives/_Index.md +30 -0
- package/second-brain/Reviews/_Index.md +30 -0
- package/second-brain/Runbooks/_Index.md +30 -0
- package/second-brain/Runbooks/eval-loop.md +24 -0
- package/second-brain/Sessions/_Index.md +30 -0
- package/second-brain/Shared/AI-Context-Index.md +20 -0
- package/second-brain/Shared/AI-Threads/_Index.md +30 -0
- package/second-brain/Shared/Archive/_Index.md +30 -0
- package/second-brain/Shared/Assets/_Index.md +30 -0
- package/second-brain/Shared/Context-Packs/_Index.md +30 -0
- package/second-brain/Shared/Context7-Docs/_Index.md +30 -0
- package/second-brain/Shared/Coordination/NOW.md +28 -0
- package/second-brain/Shared/Coordination/_Index.md +30 -0
- package/second-brain/Shared/Coordination/agent-registry.md +24 -0
- package/second-brain/Shared/Coordination/task-board/_Index.md +30 -0
- package/second-brain/Shared/Coordination/task-board/task-template.md +43 -0
- package/second-brain/Shared/Coordination/task-board.md +32 -0
- package/second-brain/Shared/Core-Facts/_Index.md +30 -0
- package/second-brain/Shared/Decision-Memory/_Index.md +30 -0
- package/second-brain/Shared/Glossary/_Index.md +30 -0
- package/second-brain/Shared/Memory-Inbox/_Index.md +30 -0
- package/second-brain/Shared/Operating-State/_Index.md +30 -0
- package/second-brain/Shared/Prompting/_Index.md +30 -0
- package/second-brain/Shared/Provenance/_Index.md +30 -0
- package/second-brain/Shared/Rules/_Index.md +30 -0
- package/second-brain/Shared/Rules/contextual-note-rule.md +30 -0
- package/second-brain/Shared/Rules/frontmatter-standard.md +10 -0
- package/second-brain/Shared/Rules/memory-write-protocol.md +28 -0
- package/second-brain/Shared/Rules/procedural-runbook-header.md +40 -0
- package/second-brain/Shared/Rules/review-and-staleness-policy.md +22 -0
- package/second-brain/Shared/Rules/rules-formatting.md +34 -0
- package/second-brain/Shared/Scripts/_Index.md +30 -0
- package/second-brain/Shared/Scripts-Archive/_Index.md +30 -0
- package/second-brain/Shared/Tech-Standards/_Index.md +30 -0
- package/second-brain/Shared/Tech-Standards/verification-standard.md +40 -0
- package/second-brain/Shared/User-Memory/_Index.md +30 -0
- package/second-brain/Shared/User-Persona/_Index.md +30 -0
- package/second-brain/Shared/User-Persona/owner-profile.md +25 -0
- package/second-brain/Shared/Working-Memory/_Index.md +30 -0
- package/second-brain/Shared/_Index.md +30 -0
- package/second-brain/Shared/mcp-servers/_Index.md +30 -0
- package/second-brain/Skills/_Index.md +30 -0
- package/second-brain/Templates/_Index.md +30 -0
- package/second-brain/Templates/bug.md +2 -0
- package/second-brain/Templates/handoff.md +2 -0
- package/second-brain/Templates/session.md +2 -0
- package/second-brain/Tools/_Index.md +30 -0
- package/second-brain/Traces/_Index.md +30 -0
- package/second-brain/Vault Structure Map.md +33 -1
- package/second-brain/copilot/_Index.md +30 -0
- package/skills/audit-license-compliance/SKILL.md +117 -0
- package/skills/author-codemod/SKILL.md +110 -0
- package/skills/build-audit-logging/SKILL.md +112 -0
- package/skills/build-cdc-streaming-pipeline/SKILL.md +123 -0
- package/skills/build-cli-tool/SKILL.md +108 -0
- package/skills/build-data-table/SKILL.md +141 -0
- package/skills/build-native-mobile-ui/SKILL.md +154 -0
- package/skills/build-offline-first-sync/SKILL.md +118 -0
- package/skills/build-realtime-channel/SKILL.md +122 -0
- package/skills/build-vector-search/SKILL.md +131 -0
- package/skills/compose-local-dev-stack/SKILL.md +149 -0
- package/skills/configure-bundler-build/SKILL.md +166 -0
- package/skills/configure-dns-tls/SKILL.md +142 -0
- package/skills/configure-reverse-proxy-lb/SKILL.md +129 -0
- package/skills/configure-security-headers-csp/SKILL.md +122 -0
- package/skills/contract-testing/SKILL.md +140 -0
- package/skills/datetime-timezone-correctness/SKILL.md +125 -0
- package/skills/debug-ci-pipeline-failure/SKILL.md +134 -0
- package/skills/debug-flaky-tests/SKILL.md +128 -0
- package/skills/defend-llm-prompt-injection/SKILL.md +110 -0
- package/skills/deliver-webhooks/SKILL.md +116 -0
- package/skills/design-api-pagination/SKILL.md +144 -0
- package/skills/design-authorization-model/SKILL.md +119 -0
- package/skills/design-backup-dr-recovery/SKILL.md +113 -0
- package/skills/design-event-sourcing-cqrs/SKILL.md +143 -0
- package/skills/design-multi-tenancy/SKILL.md +100 -0
- package/skills/design-protobuf-grpc-service/SKILL.md +146 -0
- package/skills/design-relational-schema/SKILL.md +129 -0
- package/skills/design-search-index-infra/SKILL.md +151 -0
- package/skills/design-state-machine/SKILL.md +108 -0
- package/skills/design-token-system/SKILL.md +109 -0
- package/skills/distributed-locks-leases/SKILL.md +120 -0
- package/skills/encrypt-sensitive-data/SKILL.md +148 -0
- package/skills/feature-flags-rollout/SKILL.md +130 -0
- package/skills/file-upload-object-storage/SKILL.md +107 -0
- package/skills/fuzz-dynamic-security-test/SKILL.md +111 -0
- package/skills/harden-llm-app-reliability/SKILL.md +126 -0
- package/skills/i18n-localization-setup/SKILL.md +113 -0
- package/skills/idempotency-keys/SKILL.md +107 -0
- package/skills/implement-push-notifications/SKILL.md +142 -0
- package/skills/ingest-webhook-secure/SKILL.md +120 -0
- package/skills/integrate-oauth-oidc/SKILL.md +126 -0
- package/skills/load-stress-test/SKILL.md +129 -0
- package/skills/map-privacy-data-gdpr/SKILL.md +146 -0
- package/skills/model-nosql-data/SKILL.md +118 -0
- package/skills/money-decimal-arithmetic/SKILL.md +123 -0
- package/skills/monitor-ml-drift/SKILL.md +109 -0
- package/skills/numeric-precision-units/SKILL.md +144 -0
- package/skills/optimize-llm-cost-latency/SKILL.md +103 -0
- package/skills/optimize-react-rerenders/SKILL.md +124 -0
- package/skills/orchestrate-agent-workflow/SKILL.md +100 -0
- package/skills/payments-billing-integration/SKILL.md +114 -0
- package/skills/pin-toolchain-versions/SKILL.md +116 -0
- package/skills/plan-strangler-migration/SKILL.md +95 -0
- package/skills/property-based-testing/SKILL.md +108 -0
- package/skills/publish-package-registry/SKILL.md +130 -0
- package/skills/recover-git-state/SKILL.md +119 -0
- package/skills/remediate-web-vulnerabilities/SKILL.md +125 -0
- package/skills/resilience-timeouts-retries/SKILL.md +104 -0
- package/skills/resolve-merge-rebase-conflict/SKILL.md +97 -0
- package/skills/rewrite-git-history/SKILL.md +109 -0
- package/skills/scaffold-cross-platform-app/SKILL.md +137 -0
- package/skills/schema-evolution-compatibility/SKILL.md +121 -0
- package/skills/send-transactional-email/SKILL.md +126 -0
- package/skills/serve-deploy-ml-model/SKILL.md +107 -0
- package/skills/setup-cdn-edge-waf/SKILL.md +107 -0
- package/skills/setup-devcontainer-env/SKILL.md +131 -0
- package/skills/setup-lint-format-precommit/SKILL.md +140 -0
- package/skills/setup-monorepo-tooling/SKILL.md +125 -0
- package/skills/ship-mobile-app-store-release/SKILL.md +137 -0
- package/skills/structured-output-llm/SKILL.md +86 -0
- package/skills/supply-chain-sbom-provenance/SKILL.md +120 -0
- package/skills/test-data-factories/SKILL.md +158 -0
- package/skills/threat-model-stride/SKILL.md +123 -0
- package/skills/train-evaluate-ml-model/SKILL.md +109 -0
- package/skills/unicode-text-correctness/SKILL.md +109 -0
- package/skills/visual-regression-testing/SKILL.md +120 -0
package/.env.example
CHANGED
|
@@ -21,3 +21,22 @@ ZHIPU_API_KEY=xxx # z.ai / open.bigmodel.cn (GLM — GLM
|
|
|
21
21
|
|
|
22
22
|
# default model (alias หรือ provider:model) — เช่น sonnet | opus | gpt | grok | groq:fast
|
|
23
23
|
SANOOK_MODEL=sonnet
|
|
24
|
+
|
|
25
|
+
# ── Token / cost tuning (optional) ──
|
|
26
|
+
# SANOOK_CACHE_TTL=1h # Anthropic prompt cache: 5m (default) หรือ 1h
|
|
27
|
+
# SANOOK_COMPACTION=summarize # summarize context แทน truncate เมื่อบทสนทนายาว
|
|
28
|
+
# SANOOK_SUMMARY_MODEL=haiku # model สำหรับ summarize-compaction (default = fast sibling)
|
|
29
|
+
# SANOOK_SUBAGENT_MODEL=haiku # ให้ sub-agent ใช้ model ถูกกว่า main agent
|
|
30
|
+
# SANOOK_THINKING=4000 # เปิด Anthropic extended thinking พร้อม budgetTokens cap
|
|
31
|
+
# SANOOK_EMBEDDING_MODEL=openai:text-embedding-3-small # semantic/hybrid search
|
|
32
|
+
# SANOOK_PRICING='{"openai:gpt-5.5":{"input":1.25,"output":10}}' # override price table
|
|
33
|
+
|
|
34
|
+
# ── Safety / privacy opt-ins ──
|
|
35
|
+
# SANOOK_ALLOW_OUTSIDE_WORKSPACE=1 # ให้ file tools แตะ path นอก cwd/second-brain
|
|
36
|
+
# SANOOK_NO_SANDBOX=1 # ปิด OS sandbox ของ run_bash (Seatbelt/bubblewrap)
|
|
37
|
+
# SANOOK_GATEWAY_ALLOW_WRITE=1 # ให้ sanook serve รัน write/bash แบบ unattended
|
|
38
|
+
# SANOOK_HOOKS_INHERIT_ENV=1 # ส่ง env เต็มให้ hooks (default ส่งเฉพาะ safe env)
|
|
39
|
+
# SANOOK_DISABLE_UPDATE_CHECK=1 # ปิด prompt เช็กอัปเดตใน TUI
|
|
40
|
+
# SANOOK_DISABLE_PERSISTENCE=1 # ไม่บันทึก sessions/memory
|
|
41
|
+
# SANOOK_DISABLE_WORKLOG=1 # ไม่เขียน worklog เข้า second-brain
|
|
42
|
+
# SANOOK_TRUST_PROJECT=1 # trust project .sanook mcp/hooks/skills/commands ชั่วคราว
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,178 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Fix: couldn't type in the REPL after first-run setup
|
|
6
|
+
|
|
7
|
+
The setup wizard and the REPL were two separate Ink renders (`render(SetupWizard)` → `unmount` → `render(App)`). After the first Ink instance unmounted, stdin raw-mode/keypress handling didn't reattach to the second, so the chat input was dead — you couldn't type anything. Now the wizard, the brain wizard, and the REPL live under **one Ink render** (a `Root` component swaps screens), so stdin stays continuous and input works the moment the REPL appears. (Regression-tested with ink-testing-library: typed characters reach the input box; phase routing verified.)
|
|
8
|
+
|
|
9
|
+
### Setup/first-run audit — 8 bugs found by adversarial review, all fixed
|
|
10
|
+
|
|
11
|
+
A multi-agent review of the first-run + setup + REPL flow surfaced (and independently verified) eight real, user-facing bugs — now fixed and regression-tested:
|
|
12
|
+
|
|
13
|
+
- **Empty API key silently completed setup.** `@inkjs/ui` PasswordInput fires `onSubmit` on Enter even when empty, so pressing Enter on a blank key advanced the wizard, finished, and saved *no key* — the first message then failed with "no API key" and no way back. The key step now rejects an empty submit (stays put, shows an inline error).
|
|
14
|
+
- **Wizard accepted OAuth/malformed keys it explicitly warned against.** The key step now runs the same `assertDirectApiKey` policy the runtime uses — paste a `sk-ant-oat…` subscription token and it's rejected *at the input* with a clear message, instead of being saved and blowing up later.
|
|
15
|
+
- **No way back from the key/model steps.** Picking the wrong provider dead-ended you into typing a key for the wrong service (Ctrl+C was the only escape). **Esc now returns to provider selection** from any step.
|
|
16
|
+
- **First-run env-detect trusted banned tokens.** An exported OAuth token (`ANTHROPIC_API_KEY=sk-ant-oat…`) made sanook print "✅ ready" and skip the wizard, then error on every message. `detectEnvProvider` now validates the key against policy (new `hasUsableEnvKey`) and falls through to the wizard when it's unusable.
|
|
17
|
+
- **First-run ignored an explicit `-m` flag.** `sanook -m groq` on a machine with only `OPENAI_API_KEY` printed "OpenAI ready" and ran a keyless Groq session. First-run now keys off the `-m` provider when given.
|
|
18
|
+
- **Codex auth step could wedge.** `detectCodex` had no timeout, so a hung `codex` binary left the step with no way forward. Added a 5s timeout (+ Esc always backs out).
|
|
19
|
+
- **Brain wizard name fields were pre-filled with the literal default** (`Owner` / `ผู้ช่วย`), so typing a custom name produced `OwnerPick`. Switched to a placeholder so the field starts empty (Enter still accepts the default).
|
|
20
|
+
- **Banner showed a stale model after `/model`.** It now tracks the live model.
|
|
21
|
+
|
|
22
|
+
### Fix: duplicate / empty model choices in the setup wizard
|
|
23
|
+
|
|
24
|
+
`mergeModelOptions` only deduped *remote* model ids against the curated list — never the curated list against itself. So aliases pointing at the same id rendered as **two identical-looking choices** (e.g. `haiku — claude-haiku-4-5` and `fast — claude-haiku-4-5`; OpenAI's `smart`/`gpt` both → `gpt-5.5`), which also collided on React keys (`Encountered two children with the same key` → options could duplicate or vanish). It now groups by model id and merges the alias names into one option (`haiku / fast — claude-haiku-4-5`). Separately, the old code dropped the `default` alias entirely, which **emptied the model list for LM Studio** (`{ default: 'local-model' }`) and hid Ollama's `qwen3` — those models are now selectable again. Locked in with tests (every provider yields unique option values; LM Studio/Ollama are non-empty).
|
|
25
|
+
|
|
26
|
+
### Setup wizard — better provider selection + working OpenAI Codex login
|
|
27
|
+
|
|
28
|
+
- **Provider menu**: each option now shows a one-line hint — `✓ เจอ key ใน env`, `local · ไม่ต้อง key`, `login ChatGPT · ไม่ใช้ API key`, or `ต้องมี API key` — and the list is ordered (popular cloud → others → local → Codex). The API-key step shows the expected key format.
|
|
29
|
+
- **OpenAI Codex (ChatGPT plan)**: picking Codex used to skip straight past auth. It now runs a dedicated step that detects whether the `codex` CLI is installed and logged in (reads `~/.codex/auth.json` — robust inside sandboxes where `codex login status` can panic), and guides you: `npm i -g @openai/codex` → `codex login` → re-check, then continues automatically once you're signed in. No API key required.
|
|
30
|
+
- **Codex runs**: `codex exec` now passes `--ask-for-approval never` (no hang waiting on approvals; safe under the default read-only sandbox) and removes `OPENAI_API_KEY` from the child env so it can't fight the ChatGPT-plan login (codex #2733/#3286). Verified the exact CLI surface against the official OpenAI Codex docs.
|
|
31
|
+
|
|
32
|
+
## 0.5.0
|
|
33
|
+
|
|
34
|
+
### Install UX — `sanook doctor` + post-install guidance (the "`sanook` is not recognized" fix)
|
|
35
|
+
|
|
36
|
+
The #1 first-run snag is `npm i sanook-cli` **without `-g`** → a local install that never lands on PATH, so typing `sanook` fails. Two root-cause fixes:
|
|
37
|
+
|
|
38
|
+
- **`sanook doctor`** — a new diagnose-and-fix command. Checks Node version, the npm global-bin dir, whether the `sanook` shim is installed there, whether that dir is on PATH, and whether a local install exists in the cwd — then prints the **exact, OS-safe remedy**. On Windows it emits the *safe* user-PATH PowerShell one-liner (`[Environment]::SetEnvironmentVariable(... 'User')`), deliberately **not** `setx %PATH%` (which truncates PATH at 1024 chars and duplicates the system PATH — a known corruption footgun). Runnable as `npx sanook doctor` even before a global install.
|
|
39
|
+
- **post-install hint** — right after `npm i`, the installer now prints the working command: local install → `npx sanook` or `npm i -g sanook-cli`; global install → "ready, type `sanook`". Never fails the install (always exits 0); stays quiet during repo-dev self-installs and CI.
|
|
40
|
+
|
|
41
|
+
### Cross-platform hardening — Windows / macOS / Linux
|
|
42
|
+
|
|
43
|
+
Audited the whole codebase (process spawning, paths, external deps, terminal) and fixed the real Windows breakers:
|
|
44
|
+
|
|
45
|
+
- **Child-process spawning on Windows**: `npx`/`npm`/`codex` are `.cmd` shims, so `spawn('npx', …)` failed with ENOENT — breaking **MCP servers** (incl. the second-brain filesystem MCP), **`sanook update`**, and the **Codex provider**. Now spawned with `shell` on `win32`. (`hooks` already used a shell; git/LSP already resolve correctly.)
|
|
46
|
+
- **`grep` works without ripgrep**: if `rg` isn't installed (common on a fresh Windows box) the grep tool now falls back to a pure-Node search (recursive walk, default-ignore set, binary/large-file skip, CRLF-aware) instead of a cryptic `spawn rg ENOENT`. Verified end-to-end with `rg` removed from PATH.
|
|
47
|
+
- **The agent knows its OS**: the system prompt now states the platform + shell, so on Windows it generates `dir`/`type`/`findstr` (or prefers the cross-platform read/list/glob/grep tools) instead of `ls`/`cat`/`grep` into cmd.exe.
|
|
48
|
+
- **CRLF-safe vault indexing**: the markdown chunker normalizes `\r\n` so frontmatter/sections parse identically on Windows.
|
|
49
|
+
- **Terminal**: respects `NO_COLOR` and auto-disables ANSI when output is piped/redirected (no `[2m` garbage on legacy cmd); `FORCE_COLOR` overrides.
|
|
50
|
+
- **Clear "not installed" errors** for `git` (and ripgrep) instead of raw `spawn … ENOENT`.
|
|
51
|
+
- (Prior turn) Node-version guard (≥ 22) with a clear message; install docs clarified for `-g` / `npx` / Windows `setx`.
|
|
52
|
+
|
|
53
|
+
### Skills — 16 more real-work skills, author+verify reviewed (130 → 146)
|
|
54
|
+
|
|
55
|
+
A second, larger batch filling concrete gaps in distributed-systems correctness, testing rigor, frontend, security, and AI — each one **independently verified** (a separate reviewer agent read the written file and judged frontmatter validity, structure, cross-reference resolution, technical accuracy, and *actionability* — would an agent actually be able to follow it; all 16 passed):
|
|
56
|
+
|
|
57
|
+
- **Backend/distributed**: `design-api-pagination` (cursor/keyset, stable ordering, Relay connections), `distributed-locks-leases` (Redlock caveats + fencing tokens, leader election), `design-state-machine` (explicit FSM/statecharts vs boolean-flag soup), `schema-evolution-compatibility` (backward/forward compat, reserve-don't-reuse, expand-then-contract).
|
|
58
|
+
- **Testing**: `debug-flaky-tests` (taxonomy + root-cause fixes, not retry-masking), `test-data-factories` (factories over fixtures, faker, deterministic seeds), `property-based-testing` (invariants + shrinking), `contract-testing` (consumer-driven Pact + can-i-deploy), `visual-regression-testing` (deterministic screenshots).
|
|
59
|
+
- **Frontend**: `build-data-table` (virtualized sortable/filterable grids), `optimize-react-rerenders` (profiler-driven memoization).
|
|
60
|
+
- **Security**: `configure-security-headers-csp` (strict CSP with nonces, HSTS, CORS done right), `encrypt-sensitive-data` (KMS envelope encryption, AEAD, key rotation).
|
|
61
|
+
- **AI/LLM**: `build-vector-search` (ANN indexes, hybrid + RRF, eval), `structured-output-llm` (json_schema/tool-calling + validate-and-repair).
|
|
62
|
+
- **DevEx**: `debug-ci-pipeline-failure` (reproduce locally, classify, fix root cause).
|
|
63
|
+
|
|
64
|
+
All load cleanly (catalog now 146), cross-reference only real sibling skills, and follow the When-to-Use + NOT-this-skill + dense Steps structure.
|
|
65
|
+
|
|
66
|
+
### Skills — 7 high-value additions, closing reliability/integration gaps (123 → 130)
|
|
67
|
+
|
|
68
|
+
New bundled skills filling conspicuous holes in the catalog, authored to the existing dense, cross-referenced bar:
|
|
69
|
+
|
|
70
|
+
- **resilience-timeouts-retries** — timeouts on everything, deadline propagation, retry-only-if-idempotent, exponential backoff + full jitter, circuit breakers, bulkheads, load-shedding (the general reliability primitive the catalog was missing alongside `rate-limiting`).
|
|
71
|
+
- **idempotency-keys** — make writes safe to repeat: idempotency by design (PUT/upsert/conditional) vs by key (the `Idempotency-Key` header pattern, dedup table, 409/422, outbox), consumer-side dedup, "effectively-once".
|
|
72
|
+
- **integrate-oauth-oidc** — "Log in with Google/GitHub/…": Authorization Code + PKCE, state/nonce, server-side token exchange, full ID-token validation, safe `email_verified` account linking, refresh rotation, native system-browser-only. Complements `auth-jwt-session` (which owns *your* session).
|
|
73
|
+
- **send-transactional-email** — the deliverability half: SPF/DKIM/DMARC alignment, provider-not-cold-MTA, transactional/marketing stream isolation, bounce/complaint → suppression, idempotent sends, sandbox testing.
|
|
74
|
+
- **design-multi-tenancy** — tenant isolation models (shared+RLS / schema / DB-per-tenant), the #1 cross-tenant-leak bug + defense-in-depth (app scoping + Postgres RLS), tenant context propagation, per-tenant migrations/export/delete.
|
|
75
|
+
- **build-cli-tool** — argument parsing, exit codes, stdout=data/stderr=logs, TTY/NO_COLOR, config precedence (flags>env>file), no-secrets-in-flags, dry-run — the CLI/UX design counterpart to `shell-script-robust`.
|
|
76
|
+
- **deliver-webhooks** — the *producer* side: HMAC signing + rotation, replay tolerance, at-least-once retries + dead-letter + replay, stable event ids for consumer dedup, SSRF defenses — complements `ingest-webhook-secure` (the receiver).
|
|
77
|
+
|
|
78
|
+
Each loads cleanly (frontmatter `name`/`description`/`when_to_use`), cross-references only real sibling skills, and follows the When-to-Use + NOT-this-skill + Steps structure.
|
|
79
|
+
|
|
80
|
+
### Onboarding & ease-of-use — no more dead-ends, clearer guidance
|
|
81
|
+
|
|
82
|
+
The first-run / no-key experience now guides instead of dumping a raw error, and the REPL surfaces more of what's available:
|
|
83
|
+
|
|
84
|
+
- **Headless with no key no longer dead-ends**: `sanook "task"` before any key is set now prints an actionable hint — run `sanook` for the wizard, or `export <ENV>=…` with the **provider console URL** to get a key — and, if a *different* provider's key is already in the environment, it suggests `sanook -m <that-provider> "…"`.
|
|
85
|
+
- **Smart first-run**: if an API key is already in the environment (e.g. you `export ANTHROPIC_API_KEY=…` before installing), sanook skips the setup wizard, picks that provider, and confirms `✅ ready` instead of asking you to re-enter everything.
|
|
86
|
+
- **Setup wizard** now shows the **console URL** for the chosen provider at the key step, and the run ends with a clear `✅ ตั้งค่าเสร็จ — พิมพ์งานได้เลย`.
|
|
87
|
+
- **Actionable key errors**: a missing key names the provider + console URL + how to set it; a wrong-format key shows a readable example (`sk-ant-…`, `AIza…`) instead of a raw regex (and the example is kept short so redaction doesn't mangle it).
|
|
88
|
+
- **Discoverability**: `/tools` now lists the orchestration tools (`task_parallel`/`task_spawn`/`task_collect`/`task_cancel`/`task_status`) and `diagnostics`; `/help` points to the shell-side commands (`search`/`index`/`brain`/`serve`/`mcp serve`/`config set`); the REPL empty-state hints `/help` and `/tools`.
|
|
89
|
+
|
|
90
|
+
### Token/cost tuning knobs — 1h cache, summarize-compaction, sub-agent routing, thinking budget
|
|
91
|
+
|
|
92
|
+
New `config.json` fields (and `SANOOK_*` env overrides) that trade tokens/cost without hurting quality — read once per turn via `agentTuning()`:
|
|
93
|
+
|
|
94
|
+
- **`cacheTtl: "5m" | "1h"`** (env `SANOOK_CACHE_TTL`): the Anthropic prompt-cache lifetime. `"1h"` keeps the cached preamble alive across pauses (lunch, a meeting) so resuming doesn't re-pay full price for it. Default `"5m"` (unchanged).
|
|
95
|
+
- **`compaction: "truncate" | "summarize"`** (env `SANOOK_COMPACTION`): when the conversation gets long, `"summarize"` condenses the dropped middle with a **cheap model** (the fast sibling of your main model, same key — `fastSibling()`) instead of truncating it — better recall at the same token budget. Used by `/compact` and proactively before very long turns; falls back to truncation if the summarizer fails (never blocks a turn). Default `"truncate"` (zero-LLM, unchanged). `src/compaction.ts` `summarizeCompact()` is pure (injected summarizer) and unit-tested with no network.
|
|
96
|
+
- **`SANOOK_SUBAGENT_MODEL`**: route all sub-agent work to a cheaper model (e.g. `haiku`) while the main agent keeps the strong one — big savings for exploration-heavy runs. Default: inherit the parent model.
|
|
97
|
+
- **`thinking: true | <budget>`** (env `SANOOK_THINKING`): opt-in Anthropic extended thinking on the **main** agent only (never sub-agents), with a hard `budgetTokens` cap so reasoning can't run away. Default off (no change).
|
|
98
|
+
|
|
99
|
+
### Token efficiency — line-range reads + targeted edits
|
|
100
|
+
|
|
101
|
+
Two quality-neutral cuts to per-turn token cost (the model asks for exactly what it needs):
|
|
102
|
+
|
|
103
|
+
- **`read_file` line ranges**: `read_file({ path, offset?, limit? })` returns just lines `offset..offset+limit` (with a `[lines A–B of N]` header) instead of the whole file. Paired with `grep` (which already returns line numbers), the model reads a small window around a symbol rather than an entire large file — large input-token savings, identical result. Default (no offset/limit) is unchanged. The system prompt now nudges grep→read-range over whole-file reads.
|
|
104
|
+
- **`edit_file` `replace_all`**: rename/repeated edits use a short `old_string` with `replace_all: true` instead of padding it with surrounding context for uniqueness (which the model otherwise sends twice, old + new). The ambiguous-match error now suggests `replace_all` rather than "add more context".
|
|
105
|
+
- **Terser replies**: the system prompt tells the agent not to paste back file contents / large code blocks it just read or edited (the user already sees the diff) — cuts output tokens with no loss.
|
|
106
|
+
- **Sub-agent model routing** (`SANOOK_SUBAGENT_MODEL`): opt-in env that routes all sub-agent work to a cheaper model (e.g. `haiku`) while the main agent keeps the strong model — big cost savings for exploration-heavy runs, default unchanged (inherit the parent model).
|
|
107
|
+
|
|
108
|
+
### LSP diagnostics — `diagnostics` tool (type errors without a full build)
|
|
109
|
+
|
|
110
|
+
A Node-native, zero-dependency Language Server Protocol client (`src/lsp/`) gives the agent a tight edit→check feedback loop: after editing a file it can pull the language server's diagnostics (type errors / lint) for that one file, no project-wide compile.
|
|
111
|
+
|
|
112
|
+
- **`diagnostics` tool**: `diagnostics({ path, content? })` → ranked errors/warnings as `✗ path:line:col message [code]`. Pass `content` to check an unsaved buffer. Respects worktree scoping (`agentCwd`) and the read-path guard.
|
|
113
|
+
- **Real LSP client** over Content-Length framing (distinct from MCP's newline framing): initialize handshake, `didOpen`/`didChange`, diagnostics that **settle** (a quiet period after the last publish so we return the final set, not an early empty one), and it answers server→client requests so a server can't stall. Positions convert from LSP 0-based to human 1-based.
|
|
114
|
+
- **Server pool**: servers are spawned once per (binary, workspace) and reused — re-checking a file becomes a `didChange`, so the slow project-load cost is paid once, not per call.
|
|
115
|
+
- **Zero-config floor**: sanook bundles no language servers (like ripgrep). It maps extension → conventional server (typescript-language-server, pyright, gopls, rust-analyzer, vscode-json-language-server, bash-language-server), detects whether it's installed (node_modules/.bin or PATH), and when it isn't, returns a clear install hint instead of crashing.
|
|
116
|
+
- Tests: the framing codec (split frames, multibyte, malformed-recovery), the client handshake + diagnostics-settle + server-request handling against a **fake server** (no real LSP needed), the server registry + binary detection, and the tool's graceful degradation. Verified end-to-end against a real `typescript-language-server` (caught TS2322 at the right position; pooled `didChange` cleared it after a fix).
|
|
117
|
+
|
|
118
|
+
### Real-time steering — interrupt a running turn + queue follow-ups (REPL)
|
|
119
|
+
|
|
120
|
+
The interactive REPL was input-locked while the agent worked, and Ctrl+C quit the whole app. Now a turn is steerable:
|
|
121
|
+
|
|
122
|
+
- **Interrupt mid-turn**: the running turn gets a real `AbortController` (wired into `runAgent`'s `signal`). Press **Esc** (or Ctrl+C) to stop the stream/tool loop right away and return to the prompt — without exiting the app. Partial output is kept for reference, the turn is dropped from the model history, and any files a tool already changed are recoverable via `/rewind`.
|
|
123
|
+
- **Type-ahead queue**: you can type while the agent is busy; pressing Enter **queues** the message (shown as `⏳ คิว …`) and it runs automatically as the next turn when the current one finishes. Interrupting clears the queue.
|
|
124
|
+
- Covered by an Ink integration test (mocked agent) asserting the signal is passed, input queues while busy, and Esc both aborts and clears the queue.
|
|
125
|
+
|
|
126
|
+
### Worktree isolation — safe parallel WRITE subagents (`task_parallel isolate:true`)
|
|
127
|
+
|
|
128
|
+
Parallel subagents that edit files would clobber a shared tree. Now each write subagent can run in its own throwaway **git worktree** (`src/worktree.ts`), branched from the current HEAD, and its changes are merged back afterward:
|
|
129
|
+
|
|
130
|
+
- **Per-agent working directory**: `AgentContext` gains a `cwd`, and every file tool (read/write/edit/list/glob/grep/bash) plus the write-confinement guard (`permission.allowedRoots`) now resolve through `agentCwd()`. A subagent's relative path (`src/foo.ts`) lands in ITS worktree, not the main tree — so isolation can't leak. The main agent is unchanged (no `cwd` ⇒ `process.cwd()`); the OS sandbox already confines writes to the active cwd, so a worktree is automatically sandboxed.
|
|
131
|
+
- **`task_parallel isolate:true`**: creates a worktree per write subagent, runs them concurrently in isolation, captures each worktree's diff, and applies them back to the main tree **sequentially** with `git apply --3way` — conflicts are reported (with the touched files), never silently overwritten. Worktrees are always cleaned up. Requires a git repo (falls back with a clear message otherwise).
|
|
132
|
+
- **`src/worktree.ts`**: `createWorktree` / `captureDiff` (incl. untracked) / `applyDiff` / `removeWorktree` / `runInWorktrees`, all over the existing `runGit` helper. The create→isolate→merge→cleanup lifecycle is unit-tested against a real throwaway git repo with an injected work callback (no agent/network), covering the round-trip, the parallel different-files case, empty diffs, non-git fallback, and conflict reporting.
|
|
133
|
+
|
|
134
|
+
### Subagent orchestration — parallel fan-out + background + nested (`task_parallel` / `task_spawn` / `task_collect` / `task_cancel` / `task_status`)
|
|
135
|
+
|
|
136
|
+
The single one-shot `task` subagent grows into a real orchestration layer (`src/orchestrate.ts`):
|
|
137
|
+
|
|
138
|
+
- **Parallel fan-out** (`task_parallel`): run up to 16 subagents concurrently with a real concurrency cap and **per-item error isolation** (one failure never sinks the batch); results returned in order. For independent work — explore N modules, review N angles — instead of serial subagents.
|
|
139
|
+
- **Background / async** (`task_spawn` → `task_collect` / `task_cancel` / `task_status`): spawn a detached subagent, get an id immediately, keep working, gather the result later in the same session, or cancel it. `task_collect` supports a timeout so the agent can poll instead of block; failures are captured as `error` state (never an unhandled rejection); `task_cancel` aborts via the subagent's `AbortSignal`.
|
|
140
|
+
- **Nested**: subagents may themselves `task` / `task_parallel` (bounded by the depth cap), so the main agent can decompose, then each branch can decompose again.
|
|
141
|
+
- **Parallel-safe context**: each subagent runs inside its own `AsyncLocalStorage.run()` scope, so concurrent/nested agents never bleed model/budget/depth into one another.
|
|
142
|
+
- Orchestration core is pure with an **injected runner** + injectable clock/id-gen, unit-tested with a fake runner (concurrency cap, error isolation, spawn/collect/cancel/timeout) — zero model calls in CI.
|
|
143
|
+
|
|
144
|
+
### Brain search — hybrid semantic search over the second brain (`sanook index` / `sanook search` / `sanook mcp serve`)
|
|
145
|
+
|
|
146
|
+
A Node-native search subsystem (`src/search/`) over the second-brain vault **plus** bi-temporal memory, past sessions, and skills — one ranked surface, zero new heavy deps.
|
|
147
|
+
|
|
148
|
+
- **Real BM25, zero-dependency floor**: a pure-TS inverted index (k1=1.2, b=0.75, genuine corpus-stat IDF, title field-boost) — no SQLite, no Bun, no native binary, works with no key and no network on any OS Node 22 runs on. Tokenization builds on the canonical `normalize()` and segments with `Intl.Segmenter` so **Thai** splits into real words.
|
|
149
|
+
- **Incremental indexer** (`sanook index`): mtime+size+sha256 manifest diff over the existing vault via `brainPath` — O(delta), unchanged files cost one `stat()`, deleted files are evicted precisely. No `ψ/`-style directory convention required. Folds in active memory facts (with an importance prior; quarantined/inbox facts excluded), recent sessions, and skills.
|
|
150
|
+
- **Optional BYOK semantic** (`--mode hybrid|semantic`): embeddings through your *existing* provider key (`@ai-sdk/{openai,mistral,google,…}` `embedMany`), stored as a compact Float32 sidecar, cosine in-process over a candidate set; fused with BM25 via **Reciprocal Rank Fusion** (scale-free, k=60). Lazy — absent without a key; a model change self-invalidates the cache. Degrades silently to BM25 on any embedding error.
|
|
151
|
+
- **MCP SERVER** (`sanook mcp serve`): sanook was MCP client-only; it now also *exposes* a stdio JSON-RPC server (`sanook_search` / `sanook_recall` / `sanook_remember` / `sanook_index` / `sanook_stats`), so Claude Desktop / Cursor / any MCP host can mount sanook's brain. Same zero-dep framing as the client; stdout stays protocol-clean.
|
|
152
|
+
- **`recall` upgraded**: the agent's recall tool now ranks by BM25 across all four corpora with snippets (was substring term-counting), and folds freshly-`remember`ed facts in on every call.
|
|
153
|
+
- Tests: BM25 ranking/IDF/no-posting-creep, heading chunker + frontmatter + wikilinks, RRF, incremental indexer (in-memory fs), cosine/serialize (fake vectors), engine modes + degradation, MCP server dispatch + a real piped-child round-trip (≈70 new).
|
|
154
|
+
|
|
155
|
+
### Competitive parity pass (table-stakes the OSS field now expects)
|
|
156
|
+
|
|
157
|
+
- **Remote MCP**: connect MCP servers over Streamable-HTTP (`{ "url": "https://…", "headers": {…} }`), not just stdio — half the hosted MCP ecosystem (GitHub/Slack/Postgres/…) is HTTP-only. `sanook mcp add <name> <url>` auto-detects remote.
|
|
158
|
+
- **Prompt caching**: the static system preamble (instructions + skills + brain + repo map) is sent as a cached Anthropic block, cutting cost/latency on multi-step turns; safely ignored by other providers.
|
|
159
|
+
- **OS sandbox for `run_bash`**: Seatbelt (macOS) / bubblewrap (Linux) confine shell **writes** to the workspace/brain/tmp — defense-in-depth over the regex blocklist. Reads + network unchanged; `SANOOK_NO_SANDBOX=1` to disable.
|
|
160
|
+
- **Checkpoint + `/rewind`**: a shadow-git snapshot before each turn; `/rewind` restores files **and** truncates the conversation (recoverable — it stashes the current state first).
|
|
161
|
+
- **Input ergonomics**: multiline (trailing `\` / Alt+Enter), `↑`/`↓` persisted prompt history, readline keys (Ctrl-A/E/U/K/W), and `@file` mentions that inline file contents.
|
|
162
|
+
- **Image / vision input**: `@image.png` attaches the image to vision-capable models (history keeps a lightweight placeholder, not the bytes).
|
|
163
|
+
- **Custom slash commands**: `.sanook/commands/<name>.md` prompt templates invoked as `/<name>` (project commands gated by trust).
|
|
164
|
+
- **Repo map**: a zero-dep, git-aware symbol map injected at session start so the agent selects files without blind grepping.
|
|
165
|
+
- **Reliability**: rate-limit/overload retry with exponential backoff, kept distinct from auth/billing failures (which fail fast); per-tool execution timeouts so a runaway read/grep can't hang the loop.
|
|
166
|
+
- **Minimal terminal-first TUI**: compact gradient banner, a real cursor + placeholder, and cleaner turn rendering.
|
|
167
|
+
|
|
168
|
+
### Fixes
|
|
169
|
+
|
|
170
|
+
- **Multi-turn history loss**: REPL/`--continue` now retain the full conversation (the user's earlier turns were being dropped — only assistant/tool messages were kept).
|
|
171
|
+
- **Budget cap was silent for ~all non-Anthropic models**: added approximate list prices for the other providers, a `pricing` override (`sanook config set pricing …` / `SANOOK_PRICING`), and a warning when `-b` is set for a model (or fallback model) with no pricing. Cost now carries over when the model falls back instead of resetting.
|
|
172
|
+
- MCP SSE parsing no longer aborts on a malformed earlier event; checkpoint restore pins the snapshot commit (correct even after an intervening commit) and removes files added after the snapshot.
|
|
173
|
+
|
|
174
|
+
- Tests: remote-MCP, sandbox, repo map, prompt-caching/system-preservation, rate-limit classification, tool timeout, checkpoint restore, cost merge, custom commands (≈200 total).
|
|
175
|
+
|
|
3
176
|
## 0.4.0 — second brain, GLM Coding Plan, Telegram, hardening
|
|
4
177
|
|
|
5
178
|
- **Second brain**: `sanook brain init` scaffolds a portable Obsidian "second-brain" workspace — full folder taxonomy (each with `_Index.md`), a central `Vault Structure Map.md`, seed memory files, and a portable AI operating constitution (`CLAUDE/GEMINI/AGENTS.md`). Research-backed rules: context-assembly (anti context-rot), intake quarantine + injection-scan, bi-temporal fact validity, provenance tracking, a verification-gated `Skills/` library, sleep-time consolidation. The agent now **loads the vault** into context, and `brain init` **auto-wires a filesystem MCP** to it. First-run wizard offers to create one (personalized).
|
package/README.md
CHANGED
|
@@ -2,19 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
# Sanook CLI
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**The open-source terminal AI coding agent that remembers across sessions.**
|
|
6
6
|
|
|
7
|
-
Bring your own key
|
|
7
|
+
Bring your own key · 12 providers · MCP · a built-in **"second brain"** that gives the AI durable memory across sessions — the thing Claude Code, Codex, and Gemini CLI lose at the session boundary.
|
|
8
8
|
|
|
9
|
-
[
|
|
9
|
+
🇹🇭 [อ่านภาษาไทย](README.th.md)
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/sanook-cli)
|
|
12
|
+
[](https://www.npmjs.com/package/sanook-cli)
|
|
10
13
|
[](LICENSE)
|
|
11
14
|
[](https://nodejs.org)
|
|
12
15
|
[](https://www.typescriptlang.org)
|
|
13
|
-
[](#development)
|
|
14
17
|
[](https://github.com/Sir-chawakorn/sanook-cli/actions/workflows/ci.yml)
|
|
15
18
|
|
|
16
19
|
[Quickstart](#quickstart) · [Providers](#providers) · [Usage](#usage) · [Gateway](#gateway--scheduling) · [Skills](#skills) · [MCP](#mcp) · [Security](#security)
|
|
17
20
|
|
|
21
|
+
<!-- 📹 DEMO GIF — record the close-session → reopen → "it remembered" loop (asciinema + agg), save to docs/demo.gif, then uncomment: -->
|
|
22
|
+
<!--  -->
|
|
23
|
+
|
|
18
24
|
</div>
|
|
19
25
|
|
|
20
26
|
---
|
|
@@ -31,12 +37,43 @@ prompt → LLM → tool call → result → loop → answer
|
|
|
31
37
|
|
|
32
38
|
It is **BYOK (bring your own key)** by design. Every provider connects with a **direct API key from that provider's own console** — Sanook never reuses OAuth or subscription credentials, because that violates provider terms and gets accounts banned.
|
|
33
39
|
|
|
40
|
+
## How it compares
|
|
41
|
+
|
|
42
|
+
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.
|
|
43
|
+
|
|
44
|
+
| | **Sanook** | Claude Code | Codex CLI | Gemini CLI |
|
|
45
|
+
|---|:---:|:---:|:---:|:---:|
|
|
46
|
+
| Open-source | ✅ | ❌ | ✅ | ✅ |
|
|
47
|
+
| Bring your own key | ✅ | — | ✅ | ✅ |
|
|
48
|
+
| Providers | **12** | 1 | 1 | 1 |
|
|
49
|
+
| Local models (Ollama / LM Studio) | ✅ | ❌ | ❌ | ❌ |
|
|
50
|
+
| MCP (stdio **+ remote HTTP**) | ✅ | ✅ | ✅ | ✅ |
|
|
51
|
+
| OS sandbox (Seatbelt / bubblewrap) | ✅ | ✅ | ✅ | ✅ |
|
|
52
|
+
| Checkpoint / rewind | ✅ | ✅ | ✅ | ✅ |
|
|
53
|
+
| Image / vision input | ✅ | ✅ | ✅ | ✅ |
|
|
54
|
+
| Prompt caching | ✅ | ✅ | ✅ | ✅ |
|
|
55
|
+
| **Durable cross-session memory** | ✅ | ❌ | ❌ | ❌ |
|
|
56
|
+
| **Local gateway + cron + Telegram** | ✅ | ❌ | ❌ | ❌ |
|
|
57
|
+
|
|
58
|
+
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.
|
|
59
|
+
|
|
34
60
|
## Quickstart
|
|
35
61
|
|
|
62
|
+
Install **globally** — the `-g` is required (needs **Node ≥ 22**; check with `node -v`):
|
|
63
|
+
|
|
36
64
|
```bash
|
|
37
65
|
npm install -g sanook-cli
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> ⚠️ **`'sanook' is not recognized` / command not found?** You installed it locally — `npm i sanook-cli` (without `-g`) drops it into the current folder, **not on your PATH**, so the `sanook` command isn't found. Fix: reinstall with `npm install -g sanook-cli`, or just run it via **`npx sanook`** (uses the local copy you already installed).
|
|
69
|
+
> Run **`npx sanook doctor`** to auto-diagnose Node version / PATH / install state and print the exact fix for your OS (incl. a safe Windows PATH one-liner).
|
|
70
|
+
|
|
71
|
+
Set an API key (or run `sanook` with no task for the interactive setup wizard):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
export ANTHROPIC_API_KEY=sk-ant-... # macOS / Linux
|
|
75
|
+
setx ANTHROPIC_API_KEY "sk-ant-..." # Windows (export won't work in cmd) — then open a NEW terminal
|
|
38
76
|
|
|
39
|
-
export ANTHROPIC_API_KEY=sk-ant-... # or run `sanook` and use the setup wizard
|
|
40
77
|
sanook "read package.json and list the dependencies"
|
|
41
78
|
```
|
|
42
79
|
|
|
@@ -52,15 +89,20 @@ sanook --json "..." # JSONL output for CI / scripts
|
|
|
52
89
|
|
|
53
90
|
| Area | What you get |
|
|
54
91
|
|---|---|
|
|
55
|
-
| **Agent loop** | Built on the Vercel AI SDK 6 (`streamText` + `stopWhen` + `fullStream`), with streamed output, a cost meter,
|
|
56
|
-
| **Tools** | `read_file` · `write_file` · `edit_file` (multi-tier matcher) · `list_dir` · `glob` · `grep` · `run_bash`, plus git tools — gated by a permission layer that denies destructive commands and
|
|
57
|
-
| **
|
|
58
|
-
| **
|
|
59
|
-
| **
|
|
92
|
+
| **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. |
|
|
93
|
+
| **Tools** | `read_file` · `write_file` · `edit_file` (multi-tier matcher) · `list_dir` · `glob` · `grep` · `run_bash`, 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. |
|
|
94
|
+
| **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`. |
|
|
95
|
+
| **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. |
|
|
96
|
+
| **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). |
|
|
97
|
+
| **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). |
|
|
98
|
+
| **Memory** | The agent writes its own notes (`remember`), recalls them across past sessions (`recall`), and `--continue` resumes the latest run for the current project. |
|
|
99
|
+
| **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. |
|
|
100
|
+
| **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. |
|
|
101
|
+
| **Custom commands** | Drop a `.sanook/commands/<name>.md` prompt template and call it as `/<name>` (project commands require trust). |
|
|
60
102
|
| **Subagents** | A `task` tool spawns a fresh-context sub-agent for scoped exploration without bloating the main context — read-only by default, depth-guarded. |
|
|
61
103
|
| **Gateway + cron** | `sanook serve` runs a long-lived daemon: a loopback OpenAI-compatible HTTP endpoint plus a cron scheduler. Ask it in plain language and it schedules itself. |
|
|
62
104
|
| **Channels** | A Telegram adapter (long-polling, no public URL) lets you drive the agent from your phone — locked down with a required allowlist and private-chat-only policy. |
|
|
63
|
-
| **MCP** | Connect any Model Context Protocol server (filesystem, GitHub, Postgres, …) via `~/.sanook/mcp.json`. |
|
|
105
|
+
| **MCP** | Connect any Model Context Protocol server over **stdio or remote Streamable-HTTP** (filesystem, GitHub, Postgres, hosted servers, …) via `~/.sanook/mcp.json`. |
|
|
64
106
|
| **Git** | Branch, uncommitted changes, and recent commits are injected automatically, with `git_status` / `git_diff` / `git_log` / `git_commit` tools. |
|
|
65
107
|
| **Hooks** | Run your own command before/after any tool. A non-zero `PreToolUse` exit blocks the tool — enforce lint, format, or policy. |
|
|
66
108
|
| **Plan mode** | `--plan` restricts the agent to read-only tools and asks it to produce a plan before touching anything. |
|
|
@@ -98,9 +140,11 @@ sanook models anthropic # curated ids (+ live verification if a key is set
|
|
|
98
140
|
```
|
|
99
141
|
sanook "<task>" run one task (headless)
|
|
100
142
|
sanook interactive REPL
|
|
101
|
-
sanook -c "<task>" resume the latest session
|
|
143
|
+
sanook -c "<task>" resume the latest session for this project
|
|
144
|
+
sanook --continue-any resume the newest session across all projects
|
|
102
145
|
sanook --plan "<task>" plan mode (read-only)
|
|
103
146
|
sanook --json "<task>" JSONL output for scripts / CI
|
|
147
|
+
sanook update update the CLI to the latest npm release
|
|
104
148
|
|
|
105
149
|
-m, --model <spec> model or provider:model-id
|
|
106
150
|
-b, --budget <usd> stop when estimated cost exceeds this
|
|
@@ -109,7 +153,20 @@ sanook --json "<task>" JSONL output for scripts / CI
|
|
|
109
153
|
-h, --help show help
|
|
110
154
|
```
|
|
111
155
|
|
|
112
|
-
**REPL slash commands:** `/model` · `/tools` · `/skills` · `/cost` · `/clear` · `/compact` · `/help` · `/quit`
|
|
156
|
+
**REPL slash commands:** `/model` · `/tools` · `/skills` · `/cost` · `/diff` · `/undo` · `/rewind` · `/clear` · `/compact` · `/help` · `/quit` — plus your own `.sanook/commands/*.md`. Input supports `↑`/`↓` history, `@file` mentions (text or image), and multiline (trailing `\` or Alt+Enter).
|
|
157
|
+
|
|
158
|
+
## Updating
|
|
159
|
+
|
|
160
|
+
Use the built-in updater whenever a new CLI version is available:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
sanook update
|
|
164
|
+
sanook update --check # check only
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
It checks the npm `latest` release for `sanook-cli` and, when newer than your installed version, runs `npm install -g sanook-cli@latest`.
|
|
168
|
+
|
|
169
|
+
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.
|
|
113
170
|
|
|
114
171
|
## Gateway & scheduling
|
|
115
172
|
|
|
@@ -122,7 +179,7 @@ sanook cron list
|
|
|
122
179
|
sanook cron rm <id>
|
|
123
180
|
```
|
|
124
181
|
|
|
125
|
-
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 speaks the OpenAI chat-completions shape, so existing clients work unchanged:
|
|
182
|
+
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:
|
|
126
183
|
|
|
127
184
|
```bash
|
|
128
185
|
curl http://127.0.0.1:8787/v1/chat/completions \
|
|
@@ -151,7 +208,7 @@ The channel is **fail-closed**: with no allowlist it refuses to start, it accept
|
|
|
151
208
|
|
|
152
209
|
## Skills
|
|
153
210
|
|
|
154
|
-
A skill is a `SKILL.md` file (front-matter + instructions) the agent loads on demand. Sanook ships with
|
|
211
|
+
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.
|
|
155
212
|
|
|
156
213
|
```bash
|
|
157
214
|
sanook skill list # browse all skills
|
|
@@ -176,21 +233,55 @@ It creates a full folder taxonomy (`Projects/`, `Sessions/`, `Shared/` memory la
|
|
|
176
233
|
|
|
177
234
|
Everything is **create-if-missing** — re-running never overwrites your notes. Point an Obsidian or filesystem MCP server at the workspace to let the agent read and write it.
|
|
178
235
|
|
|
236
|
+
### Brain search
|
|
237
|
+
|
|
238
|
+
Ranked search over the vault **and** the agent's memory, past sessions, and skills — one surface, no native binaries:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
sanook index # incremental index of vault + memory + sessions + skills (O(delta))
|
|
242
|
+
sanook search "vercel edge deploy" # ranked hits with snippets
|
|
243
|
+
sanook search "race condition" --mode hybrid --source vault,memory --limit 5
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- **Zero-config floor** — a pure-TypeScript BM25 inverted index (genuine corpus-stat IDF, title boost, `Intl.Segmenter` word breaks for Thai). No SQLite, no Bun, no native binary, no API key, no network.
|
|
247
|
+
- **Optional semantic** — `--mode hybrid|semantic` embeds 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 with `sanook config set embeddingModel openai:text-embedding-3-small` (or `SANOOK_EMBEDDING_MODEL`).
|
|
248
|
+
- **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.
|
|
249
|
+
|
|
250
|
+
The agent's `recall` tool uses the same engine, so remembered facts and vault notes are searchable the moment they exist.
|
|
251
|
+
|
|
179
252
|
## MCP
|
|
180
253
|
|
|
181
|
-
Connect Model Context Protocol servers
|
|
254
|
+
Connect Model Context Protocol servers over **stdio or remote Streamable-HTTP** with the same config shape you already use elsewhere:
|
|
182
255
|
|
|
183
256
|
```jsonc
|
|
184
257
|
// ~/.sanook/mcp.json
|
|
185
258
|
{
|
|
186
259
|
"mcpServers": {
|
|
187
|
-
"filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"] }
|
|
260
|
+
"filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"] },
|
|
261
|
+
"remote": { "url": "https://example.com/mcp", "headers": { "Authorization": "Bearer <token>" } }
|
|
188
262
|
}
|
|
189
263
|
}
|
|
190
264
|
```
|
|
191
265
|
|
|
266
|
+
Add servers from the CLI too: `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).
|
|
267
|
+
|
|
192
268
|
Their tools are merged into the agent's toolset automatically. `/tools` in the REPL lists everything currently available.
|
|
193
269
|
|
|
270
|
+
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:
|
|
271
|
+
|
|
272
|
+
```jsonc
|
|
273
|
+
// in another host's MCP config
|
|
274
|
+
{ "mcpServers": { "sanook-brain": { "command": "sanook", "args": ["mcp", "serve"] } } }
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Project-local `.sanook/mcp.json`, `.sanook/hooks.json`, `.sanook/skills/`, and `.sanook/commands/` are ignored until the project is trusted:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
sanook trust status
|
|
281
|
+
sanook trust add # allow this project's .sanook mcp/hooks/skills/commands
|
|
282
|
+
sanook trust remove
|
|
283
|
+
```
|
|
284
|
+
|
|
194
285
|
## Configuration
|
|
195
286
|
|
|
196
287
|
Everything lives under `~/.sanook/` (with per-project `.sanook/` overrides where relevant):
|
|
@@ -198,21 +289,55 @@ Everything lives under `~/.sanook/` (with per-project `.sanook/` overrides where
|
|
|
198
289
|
```
|
|
199
290
|
~/.sanook/auth.json API keys (chmod 600)
|
|
200
291
|
~/.sanook/memory/ auto-memory the agent writes
|
|
292
|
+
~/.sanook/search/ brain-search index + optional embedding vectors (regenerable via `sanook index`)
|
|
201
293
|
~/.sanook/sessions/ saved conversations (for --continue)
|
|
202
294
|
~/.sanook/skills/<name>/ installed SKILL.md files
|
|
203
295
|
~/.sanook/mcp.json MCP servers { "mcpServers": { … } }
|
|
204
296
|
~/.sanook/hooks.json PreToolUse / PostToolUse hooks
|
|
205
297
|
~/.sanook/gateway/ gateway token + task ledger
|
|
298
|
+
~/.sanook/trusted-projects.json project roots allowed to load project .sanook extensions
|
|
206
299
|
SANOOK.md project memory (hierarchical, like a system prompt)
|
|
207
300
|
```
|
|
208
301
|
|
|
302
|
+
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.
|
|
303
|
+
|
|
304
|
+
### Token / cost tuning
|
|
305
|
+
|
|
306
|
+
Quality-neutral knobs in `~/.sanook/config.json` (or the matching `SANOOK_*` env var) to cut tokens/cost:
|
|
307
|
+
|
|
308
|
+
| `config set …` | env | effect |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| `cacheTtl 1h` | `SANOOK_CACHE_TTL=1h` | keep the cached system preamble alive for 1h (default `5m`) — cheaper to resume after a pause |
|
|
311
|
+
| `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) |
|
|
312
|
+
| — | `SANOOK_SUBAGENT_MODEL=haiku` | run all sub-agent work (exploration/search) on a cheaper model while the main agent keeps the strong one |
|
|
313
|
+
| `summaryModel <spec>` | `SANOOK_SUMMARY_MODEL=<spec>` | model used for summarize-compaction (default: the fast sibling of your main model) |
|
|
314
|
+
| `thinking 4000` | `SANOOK_THINKING=4000` | opt-in Anthropic extended thinking on the main agent with a `budgetTokens` cap (default off) |
|
|
315
|
+
|
|
316
|
+
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.
|
|
317
|
+
|
|
318
|
+
Useful environment flags:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
SANOOK_MODEL=sonnet # default model alias or provider:model
|
|
322
|
+
SANOOK_ALLOW_OUTSIDE_WORKSPACE=1 # allow file tools outside cwd/brain
|
|
323
|
+
SANOOK_GATEWAY_ALLOW_WRITE=1 # let sanook serve run mutating tools unattended
|
|
324
|
+
SANOOK_HOOKS_INHERIT_ENV=1 # pass full env to hooks instead of a minimal safe env
|
|
325
|
+
SANOOK_DISABLE_PERSISTENCE=1 # do not save sessions or memory
|
|
326
|
+
SANOOK_DISABLE_UPDATE_CHECK=1 # do not show interactive update prompts
|
|
327
|
+
SANOOK_DISABLE_WORKLOG=1 # do not append second-brain worklogs
|
|
328
|
+
SANOOK_TRUST_PROJECT=1 # temporary trust override for project .sanook extensions
|
|
329
|
+
```
|
|
330
|
+
|
|
209
331
|
## Security
|
|
210
332
|
|
|
211
333
|
Sanook runs shell commands and edits files, so safety is built into the core rather than bolted on:
|
|
212
334
|
|
|
213
335
|
- **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.
|
|
214
|
-
- **Permission gate** — destructive commands (`rm -rf`, `git reset --hard`, `push --force`, fork bombs, …)
|
|
215
|
-
- **
|
|
336
|
+
- **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.
|
|
337
|
+
- **OS sandbox** — `run_bash` runs 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=1` to disable).
|
|
338
|
+
- **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 until `sanook trust add`.
|
|
339
|
+
- **Secret redaction** — API keys are stripped from error messages, saved sessions, memory, and worklogs.
|
|
340
|
+
- **Safe fallback** — provider fallback does not retry after a mutating tool call has already happened, avoiding duplicate side effects.
|
|
216
341
|
- **Gateway** — HTTP binds to `127.0.0.1` only and requires a bearer token on every non-health endpoint.
|
|
217
342
|
- **Telegram** — fail-closed: a required allowlist, private-chat-only, per-chat rate-limiting, and generic error replies that never reveal internal paths.
|
|
218
343
|
|
|
@@ -223,7 +348,7 @@ Hardened across several adversarial security reviews covering command injection,
|
|
|
223
348
|
```bash
|
|
224
349
|
npm install
|
|
225
350
|
npm run build # → dist/
|
|
226
|
-
npm test # vitest
|
|
351
|
+
npm test # vitest
|
|
227
352
|
npm run typecheck # tsc --noEmit (strict)
|
|
228
353
|
npm run dev -- "…" # run from source without building
|
|
229
354
|
```
|
|
@@ -234,6 +359,14 @@ CI runs the suite across macOS / Linux / Windows on Node 22 and 24. Requires **N
|
|
|
234
359
|
|
|
235
360
|
[Apache-2.0](LICENSE)
|
|
236
361
|
|
|
362
|
+
---
|
|
363
|
+
|
|
237
364
|
<div align="center">
|
|
365
|
+
|
|
366
|
+
**Built by [Sanook AI](https://www.facebook.com/sanookai)** — AI tools & education 🇹🇭
|
|
367
|
+
|
|
368
|
+
[Facebook](https://www.facebook.com/sanookai) · [X / Twitter](https://x.com/sanook_ai)
|
|
369
|
+
|
|
238
370
|
<sub>Built from scratch in TypeScript on the Vercel AI SDK — no framework, no magic.</sub>
|
|
371
|
+
|
|
239
372
|
</div>
|