/** * `celilo subscribers list` — show registered build-bus subscribers. * * Reads the static-config subscriber list ([[openspec/changes/build-bus-poll-cd/proposal.md]] Phase * 2-lite — registry-server switchboard is deferred). Doesn't print * the HMAC secret in cleartext — only a truncated hash so operators * can disambiguate subscribers without leaking credentials into * terminal history. */ import { createHash } from 'node:crypto'; import { loadSubscribers, subscriberStorePath } from '../../services/build-bus'; import type { CommandResult } from '../types'; export function handleSubscribersList(): CommandResult { let subscribers: ReturnType; try { subscribers = loadSubscribers(); } catch (err) { return { success: false, error: `Could not load subscribers: ${err instanceof Error ? err.message : String(err)}`, }; } if (subscribers.length === 0) { return { success: true, message: `No subscribers configured.\n\nStore: ${subscriberStorePath()}\nAdd one with: celilo subscribers add --secret `, }; } const lines: string[] = [`Subscribers (${subscribers.length}) — ${subscriberStorePath()}`, '']; for (const s of subscribers) { const fingerprint = createHash('sha256').update(s.secret).digest('hex').slice(0, 8); const label = s.name ? `${s.name} <${s.url}>` : s.url; lines.push(` ${label}`); lines.push(` secret fingerprint: ${fingerprint}…`); const matchParts: string[] = []; if (s.match.registry) matchParts.push(`registry=${s.match.registry}`); if (s.match.tag) matchParts.push(`tag=${s.match.tag}`); if (s.match.packagePattern) matchParts.push(`pkg=${s.match.packagePattern}`); lines.push(` match: ${matchParts.length > 0 ? matchParts.join(', ') : '(any event)'}`); lines.push(''); } return { success: true, message: lines.join('\n').trim() }; }