Add GCal tools setup guide to README (prerequisites, config, fields). Add gmail-auth, gcal-auth, setup to the CLI commands table. Add "Adding a New Tool" checklist to CLAUDE.md covering the full wiring chain including the TUI registration gotcha. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
pnpm build # Compile TypeScript to dist/
pnpm dev # Run daemon with watch mode (tsx watch)
pnpm start # Start production build
pnpm tui # Minimal TUI (readline)
pnpm tui:fs # Fullscreen TUI (React/Ink)
pnpm test # Run vitest in watch mode
pnpm test:run # Run tests once (CI)
pnpm lint # ESLint
pnpm typecheck # TypeScript check (no emit)
Run a single test file: pnpm test:run src/path/to/file.test.ts
Architecture
Flynn is a multi-channel AI assistant daemon. Messages flow: Channel Adapter → AgentOrchestrator → NativeAgent → ModelClient, with tools executed in the agent loop.
Core Abstractions
ModelClient (src/models/types.ts): chat(request): Promise<ChatResponse>. Providers: Anthropic, OpenAI, Gemini, Bedrock, Ollama, llama.cpp, GitHub Models, OpenRouter, Zhipu, xAI. Factory in src/daemon/index.ts (createClientFromConfig()). ModelRouter (src/models/router.ts) manages tiers (default/fast/complex/local) with fallback chains.
ChannelAdapter (src/channels/types.ts): connect(), disconnect(), send(), onMessage(). Adapters: Telegram, Discord, Slack, WhatsApp, WebChat. Registered in ChannelRegistry, each channel+sender pair gets its own session.
Tool (src/tools/types.ts): { name, description, inputSchema, execute(args): Promise<ToolResult> }. Three patterns:
- Static:
export const fooTool: Tool = { ... }(no deps) - Factory:
export function createFooTool(dep): Tool(single tool needing deps) - Multi-factory:
export function createFooTools(dep): Tool[](related tool set)
Registration chain: tool file → src/tools/builtin/index.ts → src/tools/index.ts → registered in src/daemon/index.ts.
Tool Policy (src/tools/policy.ts): Profiles (minimal/messaging/coding/full), groups (group:fs/runtime/web/memory), allow/deny with glob patterns.
NativeAgent (src/backends/native/agent.ts): Core agent loop with tool execution. AgentOrchestrator (src/backends/native/orchestrator.ts) wraps it with session management, compaction, memory extraction, and delegation to different model tiers.
Other Key Systems
- Config: YAML + Zod validation (
src/config/schema.ts). Supports${ENV_VAR}expansion. - Sessions: SQLite via
SessionStore(src/session/store.ts). TTL-based pruning. - Memory: Namespace-based files + hybrid search (keyword + vector). Embedding providers configurable.
- Hooks: Pattern-based confirmation engine (
src/hooks/). Actions: confirm/log/silent. - Sandbox: Docker per-session containers (
src/sandbox/manager.ts). - Automation: Cron scheduler, webhooks (HMAC), heartbeat monitor, Gmail watcher (
src/automation/). - Gateway: WebSocket JSON-RPC + HTTP server + vanilla JS dashboard (
src/gateway/). Lane queue for per-session request serialization. Gateway lock for single-client mode. Tailscale Serve integration. - Pairing: DM pairing codes for unknown sender authentication (
src/channels/pairing.ts). Gateway handlers + TUI/paircommand. - System Prompt: Template search for SOUL.md/AGENTS.md/IDENTITY.md/USER.md/TOOLS.md (
src/prompt/template.ts).
Code Conventions
- ES Modules with
.jsextensions in all imports (e.g.,import { foo } from './bar.js') typekeyword for type-only imports:import type { Config } from './schema.js'- Import order: stdlib → third-party → local
- Naming: PascalCase for types/classes, camelCase for functions/variables,
_prefixfor private fields - Files: camelCase for
.ts, PascalCase for.tsx - Tests: co-located as
*.test.tsnext to source. Vitest withdescribe/it/expect. - Target: ES2022, NodeNext modules, strict mode. Requires Node.js >=22.
- Error pattern:
instanceof Errorchecks, descriptive messages, try-catch in stream handlers
Adding a New Tool
Checklist for adding a tool (or tool set) to Flynn. Every step is required unless noted.
1. Create the tool file — src/tools/builtin/<name>.ts
Pick the right pattern:
- Static (
export const fooTool: Tool) — no runtime deps (e.g.system-info.ts) - Factory (
export function createFooTool(dep): Tool) — needs one dep (e.g.memory-read.ts) - Multi-factory (
export function createFooTools(dep): Tool[]) — related tool set (e.g.gmail.ts,gcal.ts)
Each tool needs: name (dotted, e.g. calendar.today), description, inputSchema (JSON Schema object), execute(args): Promise<ToolResult>.
2. Create tests — src/tools/builtin/<name>.test.ts
Co-located next to the source file. Use the hoisted mock pattern for external deps:
const { mockFn } = vi.hoisted(() => ({ mockFn: vi.fn() }));
vi.mock('some-module', () => ({ thing: mockFn }));
Cover: factory output (correct names/count), auth/config errors, happy paths, empty results, API errors.
3. Wire up the export chain
Three files, in order:
| File | Add |
|---|---|
src/tools/builtin/index.ts |
export { createFooTools } from './foo.js'; |
src/tools/index.ts |
Add createFooTools to the barrel re-export from ./builtin/index.js |
src/daemon/index.ts |
Import + conditional registration (see existing gmail/gcal blocks) |
4. Register in TUI — src/cli/tui.ts
The TUI has its own tool registration (separate from the daemon). Add the import to the dynamic import('../tools/index.js') destructure and add a registration block matching the daemon pattern. This is easy to forget.
5. Config schema (if tool needs config)
In src/config/schema.ts:
- Define a Zod schema (e.g.
const fooSchema = z.object({ ... }).optional()) - Add the field to the parent schema (e.g.
automationSchema) - Export the inferred type:
export type FooConfig = z.infer<typeof fooSchema>;
6. Tool policy — src/tools/policy.ts
- Add tool names to the appropriate profile sets (
messaging,coding, or both). Thefullprofile matches everything automatically. - Add a tool group entry:
'group:foo': ['foo.bar', 'foo.baz']
7. CLI auth command (if tool needs OAuth)
Mirror src/cli/gmail-auth.ts or src/cli/gcal-auth.ts:
- Create
src/cli/<name>-auth.tswithregisterFooAuthCommand(program) - Register in
src/cli/index.ts - Use the correct OAuth scope, token path, and config key
Verification
pnpm test:run src/tools/builtin/<name>.test.ts # new tests pass
pnpm typecheck # no type errors
pnpm test:run # full suite still passes
State Tracking
After implementing features, update docs/plans/state.json (test counts, progress, feature gap scorecard). Commit alongside the feature change.