d39d3ac367
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>
132 lines
6.8 KiB
Markdown
132 lines
6.8 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
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 `/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:
|
|
```typescript
|
|
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
|
|
|
|
```bash
|
|
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.
|