#!/usr/bin/env run.sh {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE NoImplicitPrelude #-} -- | Ava - AI assistant via Telegram. -- -- Usage: -- ava # Uses TELEGRAM_BOT_TOKEN env var -- ava --token=XXX # Explicit token -- ava --model=MODEL # Override LLM model -- ava logs [--last=N] [SUBAGENT_ID] # View audit logs -- -- : out ava -- : dep aeson -- : dep http-conduit -- : dep stm -- : dep time -- : dep uuid module Omni.Ava where import Alpha import qualified Data.Aeson as Aeson import qualified Data.Text as Text import qualified Data.Text.IO as TextIO import qualified Data.Time as Time import qualified Omni.Agent.AuditLog as AuditLog import qualified Omni.Agent.Telegram as Telegram import qualified Omni.Cli as Cli import qualified Omni.Test as Test import qualified System.Console.Docopt as Docopt import qualified System.Directory as Dir import qualified System.IO as IO main :: IO () main = Cli.main plan plan :: Cli.Plan () plan = Cli.Plan { Cli.help = help, Cli.move = move, Cli.test = test, Cli.tidy = \_ -> pure () } help :: Cli.Docopt help = [Cli.docopt| ava - AI assistant via Telegram Usage: ava [--token=TOKEN] [--model=MODEL] ava logs [--last=N] [] ava test ava (-h | --help) Options: -h --help Show this help --token=TOKEN Telegram bot token (or use TELEGRAM_BOT_TOKEN env) --model=MODEL LLM model to use [default: anthropic/claude-sonnet-4] --last=N Number of recent log entries to show [default: 50] Show logs for a specific subagent (e.g. S-abc123) |] move :: Cli.Arguments -> IO () move args = do IO.hSetBuffering IO.stdout IO.LineBuffering IO.hSetBuffering IO.stderr IO.LineBuffering if args `Cli.has` Cli.command "logs" then showLogs args else do let maybeToken = fmap Text.pack (Cli.getArg args (Cli.longOption "token")) Telegram.startBot maybeToken showLogs :: Cli.Arguments -> IO () showLogs args = do let maybeSubagentId = Cli.getArg args (Cli.argument "subagent_id") let lastN = fromMaybe 50 (readMaybe =<< Cli.getArg args (Cli.longOption "last")) case maybeSubagentId of Just sidStr -> do let sid = AuditLog.SubagentId (Text.pack sidStr) let path = AuditLog.subagentLogPath sid exists <- Dir.doesFileExist path if exists then do entries <- AuditLog.readSubagentLogs sid putText <| "=== Subagent " <> Text.pack sidStr <> " (" <> tshow (length entries) <> " entries) ===" traverse_ printLogEntry entries else putText <| "No logs found for subagent: " <> Text.pack sidStr Nothing -> do entries <- AuditLog.getRecentAvaLogs lastN today <- Time.utctDay Text.pack (Time.formatTime Time.defaultTimeLocale "%Y-%m-%d" today) <> " (last " <> tshow lastN <> ") ===" traverse_ printLogEntry entries printLogEntry :: AuditLog.AuditLogEntry -> IO () printLogEntry entry = do let ts = Text.pack <| Time.formatTime Time.defaultTimeLocale "%H:%M:%S" (AuditLog.logTimestamp entry) let evType = tshow (AuditLog.logEventType entry) let agent = AuditLog.unAgentId (AuditLog.logAgentId entry) let content = case AuditLog.logContent entry of Aeson.String t -> Text.take 100 t Aeson.Object _ -> "" _ -> "" TextIO.putStrLn <| "[" <> ts <> "] " <> agent <> " | " <> evType <> ": " <> content test :: Test.Tree test = Test.group "Omni.Ava" [ Test.unit "help is non-empty" <| do let usage = str (Docopt.usage help) :: String null usage Test.@=? False ]