SakuraDocs

Sakura Development Guide

Table of Contents


Repository Structure

Sakura is a monorepo with three independent packages:

sakura/
├── src/              # CLI (main product)
├── api/              # Express API → AWS Lambda
├── web/              # Next.js marketing/dashboard site
├── test/             # CLI test suite
└── scripts/          # Deployment scripts

Sources: package.json, CLAUDE.md


Prerequisites

RequirementVersionNotes
Node.js≥ 20Required by all three packages
npmbundled with NodeUsed for all packages
AWS CLIanyRequired for AWS passthrough features
TypeScript^5.8.2Dev dependency in all packages

Sources: package.json, api/package.json


Environment Setup

1. Clone and install

git clone <repo-url>
cd sakura

# Install CLI dependencies
npm install

# Install API dependencies
cd api && npm install && cd ..

# Install web dependencies
cd web && npm install && cd ..

2. Configure the CLI

Run sakura init (or create manually) to generate .sakura/config.toml:

version = 1
defaultProfile = "default"

[profiles.default]
provider = "openai"
model = "gpt-5.2"
temperature = 0.2

[profiles.coding-claude]
provider = "anthropic"
model = "claude-sonnet-4-20250514"
temperature = 0.2

Sources: src/config.ts

3. API keys

The CLI resolves keys in this priority order:

  1. Environment variableOPENAI_API_KEY or ANTHROPIC_API_KEY
  2. Bundled keysdist/keys.json (for packaged distributions)
  3. AWS SSM/sakura/api/openai-key or /sakura/api/anthropic-key
  4. API proxysakura login (uses https://api.sakura-ai.dev)

Sources: src/auth/keys.ts, src/repl/index.ts

For local development, set environment variables:

export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...

4. API environment (.env)

cd api
cp .env.example .env   # if available, or create manually

The API reads secrets from AWS SSM Parameter Store in production. For local dev, set them directly in api/.env.

Sources: api/src/services/secrets.ts

5. MCP servers (optional)

Create .sakura/mcp.json in your project root or ~/.sakura/mcp.json globally:

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "@my/mcp-server"],
      "trustLevel": "trust_read_only"
    }
  }
}

Environment variables in MCP config support ${VAR_NAME} expansion.

Sources: src/mcp/config.ts


Running Locally

CLI (interactive REPL)

# Development mode (ts-node, no build required)
npm run dev

# Or after building
npm run build
node dist/sakura.js

Running sakura with no arguments starts the interactive REPL. Running with a subcommand (e.g., sakura ask "...") uses one-shot mode.

Sources: src/sakura.ts

API server

cd api
npm run dev    # Loads .env, runs ts-node ESM loader

The API runs as a standard Express server locally. In production it wraps with serverless-http for Lambda.

Sources: api/src/server.ts, api/src/lambda.ts

Web frontend

cd web
npm run dev    # Next.js dev server (hot reload)

Sources: web/next.config.mjs


Testing

All tests live in test/ and use Vitest.

Sources: test/

Run all tests

npm test

Run a single test file

npm test -- test/routing.test.ts

Filter by test name

npm test -- --grep "classifyAwsRisk"

Watch mode

npm test -- --watch

Test files and what they cover

FileCoverage
test/aws-guard.test.tsclassifyAwsRisk — readonly vs mutating classification
test/routing.test.tsrouteIntent — keyword-based intent routing
test/config.test.tsConfig loading, TOML parsing, profile resolution
test/plan-store.test.tsPlanStore save/load, Zod schema validation
test/sessions.test.tsSessionStore persistence
test/tool-registry.test.tsTool registration and lookup
test/tools.test.tsIndividual tool execution
test/slash-commands.test.tsREPL slash command parsing
test/diff.test.tsUnified diff computation
test/format.test.tsTerminal formatting helpers
test/prompts.test.tsPrompt building logic
test/files.test.tsFile read/write utilities
test/utils.test.tsMiscellaneous utilities

Sources: test/, package.json

Note: The API (api/) has no test suite currently. The web (web/) has no test suite currently.


Project Conventions

TypeScript

  • Strict mode everywhere — "strict": true in all tsconfig.json files
  • ESM modules"type": "module" in all package.json files; use .js extensions in imports even for .ts source files
  • Target: ES2022 with moduleResolution: "Bundler"
  • No implicit any

Sources: tsconfig.json, api/tsconfig.json

File naming

  • kebab-case for filenames: aws-handler.ts, tool-loop.ts, plan-store.ts
  • PascalCase for classes: AwsCliRunner, PlanStore, SessionStore, McpClient
  • camelCase for functions and variables

Module structure

Each subsystem is a directory with focused, single-responsibility files:

src/
├── aws/          # guard.ts (risk classification), runner.ts (CLI execution)
├── auth/         # keys.ts (API key resolution), session.ts (JWT storage)
├── mcp/          # config.ts, client.ts, lifecycle.ts, bridge.ts, builder.ts
├── planning/     # planner.ts, store.ts, multi-step.ts, subagent.ts
├── providers/    # provider.ts (interface), openai.ts, anthropic.ts, api.ts
├── repl/         # index.ts, tool-loop.ts, aws-handler.ts, slash-commands.ts
├── scaffold/     # registry.ts, command.ts, build.ts, phase.ts
├── sessions/     # store.ts
├── tools/        # registry.ts, executor.ts, index.ts, implementations/
└── util/         # format.ts, context.ts, git.ts, search.ts, etc.

Sources: src/

Provider interface

All LLM providers implement the LlmProvider interface. Never call provider SDKs directly outside of src/providers/:

export interface LlmProvider {
  readonly name: ProviderName;
  complete(req: ProviderRequest): Promise<ProviderResponse>;
  stream?(req: ProviderRequest): AsyncIterable<string>;
}

Sources: src/providers/provider.ts

Tool registration pattern

Tools self-register at import time using registerTool(). The src/tools/index.ts file imports all implementations to trigger registration:

// src/tools/index.ts
import "./implementations/read.js";
import "./implementations/write.js";
// ...

Each tool file calls registerTool({ name, description, trustLevel, parameters, execute }) at module load.

Sources: src/tools/index.ts, src/tools/registry.ts

Trust levels

Every tool declares a TrustLevel that controls whether user confirmation is required:

LevelBehavior
trustedAlways executes without prompt
trust_read_onlyExecutes for reads; prompts for writes (e.g., AWS mutating commands)
trust_working_dirExecutes within cwd; prompts for paths outside
not_trustedAlways prompts

Sources: src/tools/registry.ts, src/tools/executor.ts

Plan/apply safety gate

Mutating operations (AWS commands, code changes) always go through a plan → confirm → apply cycle:

Sources: src/aws/guard.ts, src/planning/store.ts, src/cli.ts

Prompt management

System prompts are fetched from the Sakura API (/api/v1/prompts/:id) and cached for 60 seconds. Never hardcode system prompts inline — use getPrompt(id, vars):

const prompt = await getPrompt("aws_translate", { user_prompt: userInput });

Sources: src/prompts-client.ts

Error handling

  • Use ExitError(exitCode) to terminate the process with a specific exit code — it is caught in main() and calls process.exit()
  • Tool errors return { success: false, output: "", error: "..." } — never throw from tool execute()
  • Provider errors propagate as thrown Error instances

Sources: src/errors.ts, src/tools/executor.ts

Context management

The tool loop auto-compacts conversation history when it exceeds 32,000 characters, using the compact_conversation prompt. History is also trimmed per-iteration to keep API Gateway payloads under the 30-second timeout limit.

Sources: src/repl/tool-loop.ts, src/util/context.ts


Architecture Patterns

Intent routing

Sources: src/routing.ts, src/repl/index.ts

MCP integration

MCP tools are registered with the prefix mcp__<serverName>__<toolName> and become available to the LLM tool loop automatically.

Sources: src/mcp/config.ts, src/mcp/lifecycle.ts, src/mcp/bridge.ts

Plugin system

Plugins are invoked via @tag prefixes in user input (e.g., @optimize build the auth module):

// Parsed before routing
const { input, tags } = parsePluginTags(rawInput);
const transformed = await applyPlugins(input, tags, ctx);

Sources: src/plugins/registry.ts, src/plugins/optimize.ts


Available Scripts

CLI (root)

CommandDescription
npm run devRun CLI with ts-node ESM loader (no build needed)
npm run buildCompile TypeScript → dist/
npm testRun all tests with Vitest
npm test -- test/<file>Run a single test file
npm test -- --grep "<name>"Filter tests by name
npm test -- --watchWatch mode

Sources: package.json

API

CommandDescription
npm run devDev server with .env file loading
npm run buildCompile TypeScript → dist/
npm startRun compiled server

Sources: api/package.json

Web

CommandDescription
npm run devNext.js dev server with hot reload
npm run buildStatic export → web/out/

Sources: web/next.config.mjs

Deployment scripts

ScriptDescription
scripts/deploy-api.sh