docs: add Phase 5a design and implementation plans
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
# Phase 5a Design: CLI Surface, Cron/Scheduling, Doctor Diagnostics
|
||||
|
||||
## Overview
|
||||
|
||||
Three independent Phase 5 features that make Flynn usable as a proper CLI tool and proactive agent. CLI provides the command interface, cron makes Flynn schedule-driven, doctor validates setup.
|
||||
|
||||
**Dependencies**: Phases 1-4 complete. 298 tests passing. Typecheck clean.
|
||||
|
||||
---
|
||||
|
||||
## 1. CLI Surface
|
||||
|
||||
### Architecture
|
||||
|
||||
Single entry point at `src/cli/index.ts` replaces both `src/index.ts` and `src/tui.ts`. Uses **commander** for subcommand routing. `package.json` gets a `bin` field pointing to `dist/cli/index.js`.
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Description | Notes |
|
||||
|---------|-------------|-------|
|
||||
| `flynn start` | Start daemon (foreground) | Delegates to `startDaemon()` |
|
||||
| `flynn tui` | Launch TUI | Moves existing `src/tui.ts` logic |
|
||||
| `flynn send <message>` | One-shot message, print response, exit | Standalone (no daemon needed) |
|
||||
| `flynn sessions` | List active sessions | Reads from SessionStore directly |
|
||||
| `flynn doctor` | Validate config + health | See Section 3 |
|
||||
| `flynn config` | Show resolved config (redacted secrets) | Loads and displays |
|
||||
| `flynn version` | Print version | From package.json |
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
src/cli/
|
||||
├── index.ts # Commander setup, bin entry point
|
||||
├── start.ts # 'flynn start' -- daemon startup
|
||||
├── tui.ts # 'flynn tui' -- moves existing TUI logic
|
||||
├── send.ts # 'flynn send' -- one-shot agent invocation
|
||||
├── sessions.ts # 'flynn sessions' -- list/inspect sessions
|
||||
├── doctor.ts # 'flynn doctor' -- diagnostics
|
||||
├── config-cmd.ts # 'flynn config' -- show resolved config
|
||||
└── shared.ts # Config loading, output formatting, error handling
|
||||
```
|
||||
|
||||
### Key Decisions
|
||||
|
||||
- **`flynn send` works without a daemon**: Creates a lightweight agent inline (like current TUI does), processes the message, prints the response, exits. No session persistence.
|
||||
- **`flynn start` replaces `src/index.ts`**: The old entry point becomes the `start` subcommand.
|
||||
- **`flynn tui` replaces `src/tui.ts`**: The old TUI entry point becomes the `tui` subcommand with `--fullscreen` flag preserved.
|
||||
- **`package.json` `bin` field**: `"bin": { "flynn": "dist/cli/index.js" }` enables `npx flynn` and global install.
|
||||
- **Shebang**: `#!/usr/bin/env node` at top of `src/cli/index.ts`.
|
||||
|
||||
### Config Loading in CLI
|
||||
|
||||
`shared.ts` exports `loadConfigSafe()` which:
|
||||
1. Checks `FLYNN_CONFIG` env var or `~/.config/flynn/config.yaml`
|
||||
2. Returns `{ config, error }` instead of throwing
|
||||
3. Used by commands that need config (start, send, tui, doctor)
|
||||
4. Commands that don't need config (version) skip it
|
||||
|
||||
---
|
||||
|
||||
## 2. Cron/Scheduling
|
||||
|
||||
### Architecture
|
||||
|
||||
`CronScheduler` implements `ChannelAdapter` from `src/channels/types.ts`. It registers with the `ChannelRegistry` like any other adapter. Each cron job gets a session ID of `cron:<job-name>`.
|
||||
|
||||
When a job fires:
|
||||
1. Emits `InboundMessage` through the channel adapter's `onMessage` handler
|
||||
2. Channel registry routes to unified message handler -> `NativeAgent`
|
||||
3. Agent processes and responds
|
||||
4. `CronScheduler.send()` receives the response
|
||||
5. Looks up the configured output channel from the channel registry
|
||||
6. Forwards the response to the output channel+peer
|
||||
|
||||
### Config Schema
|
||||
|
||||
```typescript
|
||||
const cronJobSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
schedule: z.string().min(1), // Cron expression: "0 9 * * *"
|
||||
message: z.string().min(1), // Message to send to agent
|
||||
output: z.object({
|
||||
channel: z.string().min(1), // e.g. "telegram"
|
||||
peer: z.string().min(1), // e.g. "12345" (chat ID)
|
||||
}),
|
||||
enabled: z.boolean().default(true),
|
||||
timezone: z.string().optional(), // default UTC
|
||||
});
|
||||
|
||||
// Top-level config addition:
|
||||
automation: z.object({
|
||||
cron: z.array(cronJobSchema).default([]),
|
||||
}).default({})
|
||||
```
|
||||
|
||||
### Example Config
|
||||
|
||||
```yaml
|
||||
automation:
|
||||
cron:
|
||||
- name: morning-briefing
|
||||
schedule: "0 9 * * *"
|
||||
message: "Give me a morning briefing: weather, calendar, news"
|
||||
output:
|
||||
channel: telegram
|
||||
peer: "12345"
|
||||
- name: daily-backup-check
|
||||
schedule: "0 22 * * *"
|
||||
message: "Check if today's backups completed successfully"
|
||||
output:
|
||||
channel: telegram
|
||||
peer: "12345"
|
||||
enabled: true
|
||||
timezone: "America/New_York"
|
||||
```
|
||||
|
||||
### Library
|
||||
|
||||
**croner** -- lightweight, zero-dep cron scheduler for Node.js. ESM-native, timezone support, cron expression validation.
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
src/automation/
|
||||
├── cron.ts # CronScheduler (ChannelAdapter) + CronJob management
|
||||
```
|
||||
|
||||
Types are inline (no separate types.ts needed for this scope).
|
||||
|
||||
### Lifecycle
|
||||
|
||||
- `connect()`: Creates `Cron` instances for each enabled job
|
||||
- `disconnect()`: Stops all cron instances
|
||||
- Registers shutdown handler via daemon lifecycle
|
||||
- Channel registry manages start/stop
|
||||
|
||||
### Output Forwarding
|
||||
|
||||
`CronScheduler` holds a reference to `ChannelRegistry`. When `send(peerId, message)` is called with the agent's response:
|
||||
- `peerId` is the cron job name
|
||||
- Looks up the job's output config
|
||||
- Gets the output channel adapter from registry
|
||||
- Calls `outputAdapter.send(job.output.peer, message)`
|
||||
- If output channel is unavailable, logs warning (don't crash)
|
||||
|
||||
---
|
||||
|
||||
## 3. Doctor Diagnostics
|
||||
|
||||
### Architecture
|
||||
|
||||
`flynn doctor` validates configuration and checks system health. Runs standalone (no daemon needed). Each check is independent and produces a status line.
|
||||
|
||||
### Checks
|
||||
|
||||
| # | Check | Pass criteria | Depends on |
|
||||
|---|-------|--------------|------------|
|
||||
| 1 | Config file exists | File present at expected path | Nothing |
|
||||
| 2 | Config parses | Valid YAML syntax | #1 |
|
||||
| 3 | Config validates | Zod schema passes | #2 |
|
||||
| 4 | Env vars resolved | No unresolved `${VAR}` refs | #2 |
|
||||
| 5 | Data directory writable | `~/.local/share/flynn` write test | Nothing |
|
||||
| 6 | Session DB accessible | SQLite open + query | #5 |
|
||||
| 7 | Model connectivity | Default model responds | #3 |
|
||||
| 8 | Telegram bot valid | `getMe` API call succeeds | #3 |
|
||||
| 9 | MCP servers available | Configured servers can start | #3 |
|
||||
| 10 | Skills loaded | All skill dirs load without error | #3 |
|
||||
|
||||
### Output Format
|
||||
|
||||
```
|
||||
Flynn Doctor
|
||||
============
|
||||
|
||||
[PASS] Config file exists (~/.config/flynn/config.yaml)
|
||||
[PASS] Config parses (valid YAML)
|
||||
[FAIL] Config validates: telegram.bot_token is required
|
||||
[SKIP] Env vars resolved (config invalid)
|
||||
[PASS] Data directory writable (~/.local/share/flynn)
|
||||
[PASS] Session DB accessible (sessions.db)
|
||||
[SKIP] Model connectivity (config invalid)
|
||||
[SKIP] Telegram bot valid (config invalid)
|
||||
[SKIP] MCP servers available (config invalid)
|
||||
[SKIP] Skills loaded (config invalid)
|
||||
|
||||
Results: 3 passed, 1 failed, 6 skipped
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
```typescript
|
||||
interface CheckResult {
|
||||
status: 'pass' | 'fail' | 'warn' | 'skip';
|
||||
label: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
type Check = (ctx: DoctorContext) => Promise<CheckResult>;
|
||||
|
||||
interface DoctorContext {
|
||||
configPath: string;
|
||||
config?: Config; // Set after config checks pass
|
||||
dataDir: string;
|
||||
}
|
||||
```
|
||||
|
||||
Each check is a function. Checks that depend on config are skipped if config loading failed. Exit code 0 if all pass/warn/skip, 1 if any fail.
|
||||
|
||||
### Model Connectivity Check
|
||||
|
||||
Sends a minimal `chat()` request: `{ messages: [{ role: 'user', content: 'ping' }], max_tokens: 1 }`. Success = any response without error. Timeout: 10 seconds.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **CLI surface first** -- foundation for the other two
|
||||
2. **Doctor diagnostics** -- uses CLI, no daemon dependency
|
||||
3. **Cron/scheduling** -- needs daemon running, builds on channel system
|
||||
|
||||
## New Dependencies
|
||||
|
||||
- `commander` -- CLI framework (~50KB)
|
||||
- `croner` -- Cron scheduler (~15KB, zero deps)
|
||||
|
||||
---
|
||||
|
||||
*Design Version: 1.0*
|
||||
*Created: 2026-02-05*
|
||||
*Features: CLI surface, cron/scheduling, doctor diagnostics*
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user