231 lines
7.7 KiB
Markdown
231 lines
7.7 KiB
Markdown
# 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*
|