import { feature } from 'bun:bundle'; import React, { useEffect } from 'react'; import { useNotifications } from '../context/notifications.js'; import { Text } from '@anthropic/ink'; import { getGlobalConfig } from '../utils/config.js'; import { getRainbowColor } from '../utils/thinking.js'; // Local date, not UTC — 24h rolling wave across timezones. Sustained Twitter // buzz instead of a single UTC-midnight spike, gentler on soul-gen load. // Teaser window: April 1-7, 2026 only. Command stays live forever after. export function isBuddyTeaserWindow(): boolean { if (process.env.USER_TYPE === 'ant') return true; const d = new Date(); return d.getFullYear() === 2026 && d.getMonth() === 3 && d.getDate() <= 7; } export function isBuddyLive(): boolean { if (process.env.USER_TYPE === 'ant') return true; const d = new Date(); return d.getFullYear() > 2026 || (d.getFullYear() === 2026 && d.getMonth() >= 3); } function RainbowText({ text }: { text: string }): React.ReactNode { return ( <> {[...text].map((ch, i) => ( {ch} ))} ); } // Rainbow /buddy teaser shown on startup when no companion hatched yet. // Idle presence and reactions are handled by CompanionSprite directly. export function useBuddyNotification(): void { const { addNotification, removeNotification } = useNotifications(); useEffect(() => { if (!feature('BUDDY')) return; const config = getGlobalConfig(); if (config.companion || !isBuddyTeaserWindow()) return; addNotification({ key: 'buddy-teaser', jsx: , priority: 'immediate', timeoutMs: 15_000, }); return () => removeNotification('buddy-teaser'); }, [addNotification, removeNotification]); } export function findBuddyTriggerPositions(text: string): Array<{ start: number; end: number }> { if (!feature('BUDDY')) return []; const triggers: Array<{ start: number; end: number }> = []; const re = /\/buddy\b/g; let m: RegExpExecArray | null; while ((m = re.exec(text)) !== null) { triggers.push({ start: m.index, end: m.index + m[0].length }); } return triggers; }