Files
William Valentin d39d3ac367 docs: add Google Calendar section and new-tool checklist
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>
2026-02-10 11:51:56 -08:00

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.tssrc/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 /pair command.
  • System Prompt: Template search for SOUL.md/AGENTS.md/IDENTITY.md/USER.md/TOOLS.md (src/prompt/template.ts).

Code Conventions

  • ES Modules with .js extensions in all imports (e.g., import { foo } from './bar.js')
  • type keyword 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, _prefix for private fields
  • Files: camelCase for .ts, PascalCase for .tsx
  • Tests: co-located as *.test.ts next to source. Vitest with describe/it/expect.
  • Target: ES2022, NodeNext modules, strict mode. Requires Node.js >=22.
  • Error pattern: instanceof Error checks, 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). The full profile 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.ts with registerFooAuthCommand(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.