feat(setup): prompt operator pack output routing

This commit is contained in:
William Valentin
2026-02-16 15:19:27 -08:00
parent fa90d826de
commit ab80f305ef
5 changed files with 48 additions and 6 deletions
+1 -1
View File
@@ -769,7 +769,7 @@ Repeated failure/recovery notifications are throttled by `notify_cooldown`.
- `automation.minio_sync.interval: "6h"` - `automation.minio_sync.interval: "6h"`
- `automation.minio_sync.notify.channel: webchat` - `automation.minio_sync.notify.channel: webchat`
`flynn setup` now includes an Operator Pack option in Automation that preconfigures scheduled backups, heartbeat alerts, a daily briefing, and a default MinIO sync task. `flynn setup` now includes an Operator Pack option in Automation that preconfigures scheduled backups, heartbeat alerts, a daily briefing, and a default MinIO sync task, with prompts for output channel/peer routing.
## Gmail Pub/Sub Watcher ## Gmail Pub/Sub Watcher
+2 -2
View File
@@ -193,7 +193,7 @@
"status": "completed", "status": "completed",
"date": "2026-02-16", "date": "2026-02-16",
"updated": "2026-02-16", "updated": "2026-02-16",
"summary": "Implemented operator-focused hardening and onboarding polish: added setup Operator Pack flows in both Automation menu and first-run wizard to preconfigure scheduled backups, heartbeat alerts, daily briefing, and default MinIO sync; added heartbeat notification throttling via `automation.heartbeat.notify_cooldown`; and added `flynn doctor --strict` to treat warnings as failures. Updated docs/default config examples and onboarding tests accordingly.", "summary": "Implemented operator-focused hardening and onboarding polish: added setup Operator Pack flows in both Automation menu and first-run wizard to preconfigure scheduled backups, heartbeat alerts, daily briefing, and default MinIO sync; added operator-pack prompts for output channel/peer routing; added heartbeat notification throttling via `automation.heartbeat.notify_cooldown`; and added `flynn doctor --strict` to treat warnings as failures. Updated docs/default config examples and onboarding tests accordingly.",
"files_modified": [ "files_modified": [
"src/cli/setup/config.ts", "src/cli/setup/config.ts",
"src/cli/setup/config.test.ts", "src/cli/setup/config.test.ts",
@@ -3499,7 +3499,7 @@
} }
}, },
"overall_progress": { "overall_progress": {
"total_test_count": 1868, "total_test_count": 1869,
"all_tests_passing": true, "all_tests_passing": true,
"p0_completion": "3/3 (100%)", "p0_completion": "3/3 (100%)",
"p1_completion": "4/4 (100%)", "p1_completion": "4/4 (100%)",
+38
View File
@@ -43,6 +43,8 @@ describe('setupAutomation', () => {
false, // configure google services false, // configure google services
], ],
asks: [ asks: [
'telegram', // output channel
'987654321', // output peer
'0 3 * * *', // backup schedule '0 3 * * *', // backup schedule
'0 7 * * 1-5', // daily briefing schedule '0 7 * * 1-5', // daily briefing schedule
], ],
@@ -61,11 +63,47 @@ describe('setupAutomation', () => {
expect(backup.schedule).toBe('0 3 * * *'); expect(backup.schedule).toBe('0 3 * * *');
expect(heartbeat.enabled).toBe(true); expect(heartbeat.enabled).toBe(true);
expect(heartbeat.notify_cooldown).toBe('30m'); expect(heartbeat.notify_cooldown).toBe('30m');
expect((heartbeat.notify as Record<string, unknown>).channel).toBe('telegram');
expect((heartbeat.notify as Record<string, unknown>).peer).toBe('987654321');
expect(briefing.enabled).toBe(true); expect(briefing.enabled).toBe(true);
expect(briefing.schedule).toBe('0 7 * * 1-5'); expect(briefing.schedule).toBe('0 7 * * 1-5');
expect(minioSync.enabled).toBe(true); expect(minioSync.enabled).toBe(true);
}); });
it('allows overriding operator pack output routing', async () => {
const builder = new ConfigBuilder();
const p = makePrompter({
confirms: [
true, // enable operator pack
false, // include minio sync
false, // enable cron scheduler
false, // enable webhook receiver
false, // configure google services
],
asks: [
'discord', // output channel
'ops-room', // output peer
'0 4 * * *', // backup schedule
'0 9 * * *', // daily briefing schedule
],
});
await setupAutomation(p, builder);
const config = builder.build() as Record<string, unknown>;
const backup = config.backup as Record<string, unknown>;
const automation = config.automation as Record<string, unknown>;
const heartbeat = automation.heartbeat as Record<string, unknown>;
const briefing = automation.daily_briefing as Record<string, unknown>;
expect((backup.notify as Record<string, unknown>).channel).toBe('discord');
expect((backup.notify as Record<string, unknown>).peer).toBe('ops-room');
expect((heartbeat.notify as Record<string, unknown>).channel).toBe('discord');
expect((briefing.output as Record<string, unknown>).peer).toBe('ops-room');
expect(automation.minio_sync).toBeUndefined();
});
it('leaves operator pack disabled when not selected', async () => { it('leaves operator pack disabled when not selected', async () => {
const builder = new ConfigBuilder(); const builder = new ConfigBuilder();
+5 -3
View File
@@ -66,19 +66,21 @@ export async function setupAutomation(p: Prompter, builder: ConfigBuilder): Prom
const telegramPeer = config.telegram?.allowed_chat_ids?.[0]; const telegramPeer = config.telegram?.allowed_chat_ids?.[0];
const defaultOutputChannel = telegramPeer ? 'telegram' : 'webchat'; const defaultOutputChannel = telegramPeer ? 'telegram' : 'webchat';
const defaultOutputPeer = telegramPeer ? String(telegramPeer) : 'operator'; const defaultOutputPeer = telegramPeer ? String(telegramPeer) : 'operator';
const outputChannel = await p.ask('Operator notifications output channel', defaultOutputChannel);
const outputPeer = await p.ask('Operator notifications peer/chat ID', defaultOutputPeer);
const backupSchedule = await p.ask('Backup cron schedule', '0 2 * * *'); const backupSchedule = await p.ask('Backup cron schedule', '0 2 * * *');
const dailyBriefingSchedule = await p.ask('Daily briefing cron schedule', '0 8 * * *'); const dailyBriefingSchedule = await p.ask('Daily briefing cron schedule', '0 8 * * *');
const enableMinioSync = await p.confirm('Include default MinIO sync task?', true); const enableMinioSync = await p.confirm('Include default MinIO sync task?', true);
builder.applyOperatorPack({ builder.applyOperatorPack({
outputChannel: defaultOutputChannel, outputChannel,
outputPeer: defaultOutputPeer, outputPeer,
backupSchedule, backupSchedule,
dailyBriefingSchedule, dailyBriefingSchedule,
enableMinioSync, enableMinioSync,
}); });
p.println(`✓ Operator pack enabled (alerts routed to ${defaultOutputChannel}/${defaultOutputPeer})`); p.println(`✓ Operator pack enabled (alerts routed to ${outputChannel}/${outputPeer})`);
} }
const cron = await p.confirm('Enable cron scheduler?', false); const cron = await p.confirm('Enable cron scheduler?', false);
+2
View File
@@ -80,6 +80,8 @@ describe('first-run wizard integration', () => {
'n', // confirm: Add another channel? (no) 'n', // confirm: Add another channel? (no)
'y', // confirm: Configure automation now? (yes) 'y', // confirm: Configure automation now? (yes)
'y', // confirm: Enable operator automation pack? (yes) 'y', // confirm: Enable operator automation pack? (yes)
'', // ask: Operator notifications output channel (default)
'', // ask: Operator notifications peer/chat ID (default)
'', // ask: Backup cron schedule (default) '', // ask: Backup cron schedule (default)
'', // ask: Daily briefing cron schedule (default) '', // ask: Daily briefing cron schedule (default)
'', // confirm: Include default MinIO sync task? (default yes) '', // confirm: Include default MinIO sync task? (default yes)