blob: 3640fc2dc14b5816398d22239975c559ad8db454 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
#!/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] [<subagent_id>]
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]
<subagent_id> 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 </ Time.getCurrentTime
putText <| "=== Ava logs for " <> 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 _ -> "<object>"
_ -> "<value>"
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
]
|