AI that shows you things, not just tells you.
Live Demo · Quick Start · Self-Host · Discord
- Quick Start
- What Is Scratchy?
- GenUI: Agents That Render UI
- Meet The Team
- Agent Skills
- Built-in Widgets
- What's Free vs Paid
- Pricing
- Self-Hosting Guide
- Architecture
- Security
- API Reference
- Configuration
- Testing
- Contributing
git clone https://github.com/yassinebkr/scratchy.git
cd scratchy
cp .env.example .env # Add your API keys
docker compose up -d # That's itOpen http://localhost:3002. First signup = admin.
No Docker? Manual setup
Node.js ≥ 22:
npm ci --legacy-peer-deps
export ENCRYPTION_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
npm startMost AI chat apps give you a text box and a stream of paragraphs. Scratchy gives you a canvas.
When you ask an agent to show you server metrics, it renders gauges. When you ask for a comparison, it builds a table. When you ask it to help you write an email, it shows a compose form. The agent decides what to show — you see interactive components, not walls of text.
This is Generative UI (GenUI): a protocol where agents describe UI declaratively, and the client renders it in real-time as tokens stream in. 34 component types — charts, forms, timelines, checklists, code blocks, cards, and more.
And it's not one agent — you choose from specialized agents, each with their own personality and expertise.
Traditional AI chat:
"Here's a table showing your server stats: CPU is at 73%, RAM is 4.2GB..."
Scratchy:
Three gauges appear showing CPU (73%, orange), RAM (4.2/8 GB, blue), Disk (52%, green). A stats grid shows uptime, request count, and error rate. All rendered live as the agent streams.
The agent writes declarative ops. The client renders components. No iframe hacks, no markdown tables pretending to be UI. Real interactive elements with hover states, click handlers, and live updates.
34 component types: hero, card, stats, gauge, progress, sparkline, chart-bar, chart-line, chart-pie, table, checklist, timeline, form, buttons, tabs, accordion, code, image, video, and more.
Scratchy ships with 4 specialist agents — each with a distinct personality, expertise, and soul file:
| Agent | Role | What It Does |
|---|---|---|
| Atlas | Code Architect | Systems-first coding. Plans architecture, then implements. Production-ready with proper error handling. |
| Iris | Design Engineer | UI/UX that works beautifully. Mobile-first, accessible, with proper design tokens. |
| Nova | Researcher | Finds, cross-references, and synthesizes information. Always cites sources, always flags uncertainty. |
| Echo | Writer | Clear, sharp writing. Docs, marketing copy, emails — no AI slop. Every sentence earns its place. |
Switch between agents depending on the task. Each agent uses GenUI canvas tools to render structured output — not just text.
Coming soon: Multi-agent team delegation, where an orchestrator splits complex requests across agents in parallel.
Each Scratchy agent is powered by a soul file — a markdown document with YAML frontmatter that defines the agent's personality, expertise, and activation triggers. Inspired by Anthropic's Agent Skills architecture.
Soul files use progressive disclosure:
- Frontmatter (always loaded): name, description, trigger keywords — tells the system when to activate the agent
- Body (loaded on use): full personality, rules, canvas tool preferences, weak areas
Example soul file structure:
---
name: atlas
description: >
Lead code agent. Systems-first coding, architecture, and implementation.
Use when user asks to build, create, code, debug, or review code.
triggers:
- build
- debug
- review my code
---
# Atlas — Code Architect
## Identity
You are Atlas, the lead code agent...
## Expertise
Primary: JavaScript, TypeScript, Node.js...The /api/agents endpoint returns skill metadata (description + triggers) for each agent, enabling smart agent switching in the UI.
Coming soon: User-created skills, skill marketplace, and a skill-creator agent.
Three open-core widgets ship with every Scratchy instance:
| Widget | What It Does |
|---|---|
| Notes | Create, edit, search notes. Agents can read and write to your notes contextually. |
| Calendar | Events with colors, date ranges, and reminders. Agents can check your schedule. |
| Gmail integration via OAuth. Agents can read your inbox and pre-fill compose forms — but only you can hit Send. |
All widget data is per-user, stored in SQLite, and never leaves your server.
The open-core version includes everything you need to run a personal AI workspace:
| ✅ Free (self-hosted) | 💰 Paid (managed hosting) |
|---|---|
| GenUI canvas (all 34 components) | Managed hosting (zero setup) |
| 4 specialist agents (Atlas, Iris, Nova, Echo) | Multi-user + seat management |
| Notes, Calendar, Email widgets | Opus model access |
| Streaming chat with TOON encoding | Priority support |
| Single-user workspace | Advanced quotas + analytics |
| BYOK (bring your own API keys) | — |
| Self-hosted, your data stays yours | — |
Coming soon: MCP tool integration, widget marketplace, i18n (language translations).
The free version is real. Not a demo, not a 14-day trial. Fork it, self-host it, build on it.
Self-hosted is free forever. Managed hosting plans:
| Plan | Price | Highlights |
|---|---|---|
| Free | €0 | Self-hosted, single-user, BYOK, all features |
| Pro | €29.99/mo | Managed hosting, Sonnet, 200 msg/day, 1 seat |
| Max | €59.99/mo | Managed hosting, Sonnet + Opus, 500 msg/day, 3 seats |
| Business | Custom | On-prem or managed, unlimited seats, SLA |
- Node.js ≥ 22 (or Docker)
- 1 CPU, 512MB RAM minimum (2 CPU / 2GB recommended)
- SQLite (included, no external DB needed)
git clone https://github.com/yassinebkr/scratchy.git
cd scratchy
cp .env.example .envEdit .env:
ENCRYPTION_KEY=<generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))">
OPENAI_API_KEY=sk-...
# Optional:
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...docker compose up -dnpm ci --legacy-peer-deps
export ENCRYPTION_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
export OPENAI_API_KEY=sk-...
npm startServer starts on port 3002. Override with PORT=8080 npm start.
Scratchy uses WebSocket — make sure your proxy passes Upgrade headers. Nginx example:
location / {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}Browser (Web Components)
├── sc-chat Chat interface + streaming
├── sc-canvas GenUI component renderer (34 types)
├── sc-terminal Terminal surface
└── sc-editor Code editor surface
│
│ WebSocket
▼
Server (Node.js)
├── Router HTTP API + static serving
├── WS Handler Per-client state, broadcast
├── Auth Argon2id + sessions + WebAuthn passkeys
├── Protocol GenUI parser, TOON codec, surfaces, A2UI bridge
├── State SQLite WAL (users, agents, canvas, memory, prefs)
├── Widgets Notes, Calendar, Email backends
└── Libraries Embeddings, Crawler, Billing, Indexer
│
│ Per-user instances
▼
NullClaw (Zig)
└── 678KB binary, ~1MB RAM per user, tool execution, streaming SSE
| Layer | Tech |
|---|---|
| Frontend | Web Components (no framework), Shadow DOM, ES modules |
| Backend | Node.js 22, native HTTP server, ws package |
| Database | SQLite with WAL mode |
| Auth | Argon2id, AES-256-GCM, WebAuthn |
| Agent Runtime | NullClaw (Zig, ~1MB per instance) |
| Encoding | TOON (Token-Oriented Object Notation, ~30% token savings) |
Self-hosted means your data never leaves your server. Auth uses Argon2id password hashing and AES-256-GCM encryption for stored API keys. WebAuthn passkeys supported.
Scratchy enforces a structural security boundary on email sending. This is not a config flag — it's an architectural constraint.
Email Security Details
| Action | Agent (WS) | Human (REST) |
|---|---|---|
| Read inbox | ✅ mail-inbox |
✅ GET /api/emails |
| Search | ✅ mail-search |
— |
| View email | ✅ mail-read |
✅ GET /api/emails/:id |
| Pre-fill compose | ✅ mail-compose |
✅ POST /api/emails |
| Save draft | ✅ mail-save-draft |
✅ POST /api/emails |
| Send | ❌ No action exists | ✅ POST /api/emails/:id/send |
The email widget module has no send function. The send endpoint lives in a REST-only handler unreachable from the WebSocket path. Gmail OAuth scopes are gmail.readonly + gmail.compose — no gmail.send.
Why structural enforcement? A config flag is an attack surface. A prompt instruction is ignorable. Code separation survives prompt injection, config bugs, and future changes.
Core API Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/health |
No | Health check |
| POST | /api/auth/signup |
No | Create account |
| POST | /api/auth/login |
No | Login |
| GET | /api/auth/me |
Yes | Current user |
| POST | /api/auth/logout |
Yes | End session |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/agents |
Yes | List agents |
| POST | /api/agents |
Yes | Create agent |
| GET/PUT/DELETE | /api/agents/:id |
Yes | Agent CRUD |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET/POST | /api/notes |
Yes | List / create notes |
| GET/PUT/DELETE | /api/notes/:id |
Yes | Note CRUD |
| GET/POST | /api/calendar |
Yes | List / create events |
| GET/PUT/DELETE | /api/calendar/:id |
Yes | Event CRUD |
| GET/POST | /api/emails |
Yes | List / create drafts |
| GET/DELETE | /api/emails/:id |
Yes | Draft CRUD |
| POST | /api/emails/:id/send |
Yes | Send via Gmail (human-only) |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET/PUT | /api/admin/config |
Admin | System configuration |
| GET | /api/admin/users |
Admin | User management |
| GET/PUT | /api/admin/users/:id/quotas |
Admin | Quota management |
Connect to ws://HOST:3002/ws?token=<session-token>. See PROTOCOL.md for the full message spec.
Environment Variables
| Variable | Required | Description |
|---|---|---|
ENCRYPTION_KEY |
Yes | 32-byte hex key for AES-256-GCM |
OPENAI_API_KEY |
Yes | OpenAI API key (embeddings + chat) |
PORT |
No | Server port (default: 3002) |
DATABASE_PATH |
No | SQLite path (default: ./data/scratchy.db) |
ANTHROPIC_API_KEY |
No | For Claude models |
GOOGLE_CLIENT_ID |
No | Gmail + Calendar OAuth |
GOOGLE_CLIENT_SECRET |
No | OAuth client secret |
GOOGLE_REDIRECT_URI |
No | OAuth callback URL |
STRIPE_SECRET_KEY |
No | Billing |
STRIPE_WEBHOOK_SECRET |
No | Stripe webhooks |
npm test # Run all tests
npm run test:verbose # Verbose outputUses Node.js built-in test runner. No external framework.
scratchy/
├── server/ # HTTP server, WS handler, auth, routes
├── protocol/ # GenUI, TOON, surfaces, A2UI bridge
├── state/ # SQLite state managers (users, agents, canvas, memory)
├── lib/ # Google OAuth, embeddings, crawler, billing, widgets
├── public/ # Web Components client (sc-chat, sc-canvas, etc.)
│ ├── components/ # 20+ Web Components
│ ├── lib/ # App bootstrap, WS client
│ ├── styles/ # CSS
│ └── i18n/ # Locale files (en, fr)
├── test/ # Node.js test runner tests
├── docker-compose.yml # Full stack deployment
├── PROTOCOL.md # GenUI protocol spec
└── COMPONENTS.md # Component type reference
- Fork → feature branch → PR
node -call JS files (syntax check before commit)npm testpasses- ESM imports only (no CommonJS)
- Components are declarative — agents describe, clients render
- Fixed: Race condition in chat streaming — late deltas arriving after
chat-stream-endno longer create orphaned message bubbles or append to finalized messages. Added_streamFinalizedguard inpublic/lib/app.js.
Built by Yassine • Discord • MIT License
