brainiac 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +2 -0
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +126 -0
  6. data/README.md +1166 -0
  7. data/Rakefile +12 -0
  8. data/bin/brainiac +1521 -0
  9. data/brainiac.gemspec +30 -0
  10. data/certs/stowzilla.pem +26 -0
  11. data/docs/waybar-config.md +96 -0
  12. data/lib/brainiac/agents.rb +203 -0
  13. data/lib/brainiac/brain.rb +197 -0
  14. data/lib/brainiac/card_index.rb +389 -0
  15. data/lib/brainiac/config.rb +263 -0
  16. data/lib/brainiac/cron.rb +629 -0
  17. data/lib/brainiac/deployments.rb +258 -0
  18. data/lib/brainiac/handlers/discord.rb +1643 -0
  19. data/lib/brainiac/handlers/fizzy.rb +1249 -0
  20. data/lib/brainiac/handlers/github.rb +598 -0
  21. data/lib/brainiac/handlers/zoho.rb +487 -0
  22. data/lib/brainiac/helpers.rb +760 -0
  23. data/lib/brainiac/planning.rb +237 -0
  24. data/lib/brainiac/prompts.rb +620 -0
  25. data/lib/brainiac/sessions.rb +282 -0
  26. data/lib/brainiac/skills.rb +276 -0
  27. data/lib/brainiac/users.rb +76 -0
  28. data/lib/brainiac/version.rb +6 -0
  29. data/lib/brainiac/zoho_mail_api.rb +109 -0
  30. data/lib/brainiac.rb +10 -0
  31. data/lib/user_registry.rb +159 -0
  32. data/monitor/daemon.rb +99 -0
  33. data/monitor/deploy-env-macos.rb +131 -0
  34. data/monitor/menubar.rb +295 -0
  35. data/monitor/open-action.sh +15 -0
  36. data/monitor/setup-menubar.rb +78 -0
  37. data/monitor/setup-waybar-deploy-envs.rb +121 -0
  38. data/monitor/setup-waybar-deployments.rb +96 -0
  39. data/monitor/setup-waybar-module.rb +113 -0
  40. data/monitor/setup-xbar-plugin.rb +35 -0
  41. data/monitor/view-logs-macos.rb +210 -0
  42. data/monitor/view-logs-rofi.rb +194 -0
  43. data/monitor/view-logs.rb +119 -0
  44. data/monitor/waybar-config-updater.rb +56 -0
  45. data/monitor/waybar-deploy-env.rb +206 -0
  46. data/monitor/waybar-deployments.rb +239 -0
  47. data/monitor/waybar.rb +146 -0
  48. data/monitor/xbar.3s.rb +179 -0
  49. data/receiver.rb +956 -0
  50. data/templates/agents.json.example +10 -0
  51. data/templates/discord.json.example +17 -0
  52. data/templates/fizzy.json.example +24 -0
  53. data/templates/github.json.example +4 -0
  54. data/templates/testflight.json.example +8 -0
  55. data/templates/users.json.example +121 -0
  56. data/templates/zoho.json.example +27 -0
  57. data/views/dashboard.erb +437 -0
  58. data.tar.gz.sig +0 -0
  59. metadata +235 -0
  60. metadata.gz.sig +0 -0
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Brainiac xbar Plugin (macOS menu bar)
5
+ # Reads from monitor daemon socket and outputs xbar-formatted text
6
+ # Filename encodes refresh interval: xbar.3s.rb = every 3 seconds
7
+ #
8
+ # <xbar.title>Brainiac Agent Monitor</xbar.title>
9
+ # <xbar.version>v1.0</xbar.version>
10
+ # <xbar.author>Brainiac</xbar.author>
11
+ # <xbar.desc>Shows active AI agent sessions in the macOS menu bar</xbar.desc>
12
+ # <xbar.dependencies>ruby</xbar.dependencies>
13
+
14
+ require "json"
15
+ require "shellwords"
16
+ require "socket"
17
+
18
+ SOCKET_PATH = "/tmp/brainiac-monitor.sock"
19
+ CONFIG_PATH = File.expand_path("~/.brainiac/waybar.json")
20
+
21
+ def load_agent_config
22
+ config = JSON.parse(File.read(CONFIG_PATH))
23
+ agents = {}
24
+ config["agents"].each do |agent|
25
+ agents[agent["name"].downcase] = { emoji: agent["emoji"], color: agent["color"] }
26
+ end
27
+ [agents, config["default_emoji"] || "❓"]
28
+ rescue StandardError
29
+ [{}, "❓"]
30
+ end
31
+
32
+ AGENTS, DEFAULT_EMOJI = load_agent_config
33
+
34
+ COLOR_MAP = {
35
+ "red" => "#ff5555", "green" => "#50fa7b", "blue" => "#8be9fd",
36
+ "yellow" => "#f1fa8c", "cyan" => "#8be9fd", "magenta" => "#ff79c6",
37
+ "purple" => "#bd93f9", "pink" => "#ff79c6", "white" => "#f8f8f2"
38
+ }.freeze
39
+
40
+ def hex_color(name)
41
+ COLOR_MAP[name] || name
42
+ end
43
+
44
+ def fetch_state
45
+ socket = UNIXSocket.new(SOCKET_PATH)
46
+ data = socket.read
47
+ socket.close
48
+ JSON.parse(data)
49
+ rescue Errno::ENOENT
50
+ { "error" => "daemon not running" }
51
+ rescue StandardError => e
52
+ { "error" => e.message }
53
+ end
54
+
55
+ def format_elapsed(seconds)
56
+ return "#{seconds}s" if seconds < 60
57
+
58
+ minutes = seconds / 60
59
+ return "#{minutes}m" if minutes < 60
60
+
61
+ "#{minutes / 60}h"
62
+ end
63
+
64
+ def format_context(card_key)
65
+ return "" unless card_key
66
+
67
+ if card_key.start_with?("discord-")
68
+ "Discord"
69
+ elsif card_key.start_with?("card-")
70
+ "##{card_key.split("-")[1]}"
71
+ else
72
+ card_key
73
+ end
74
+ end
75
+
76
+ def time_ago(iso_string)
77
+ return nil unless iso_string
78
+
79
+ seconds = (Time.now - Time.parse(iso_string)).to_i
80
+ "#{format_elapsed(seconds)} ago"
81
+ rescue StandardError
82
+ nil
83
+ end
84
+
85
+ ANSI_REGEX = /\e\[[0-9;]*[a-zA-Z]|\e\[\?[0-9;]*[a-zA-Z]/
86
+ LOG_PREVIEW_LINES = 15
87
+ LOG_LINE_MAX = 80
88
+ LOG_FONT = "SFMono-Regular"
89
+ LOG_SIZE = 12
90
+
91
+ def tail_log(log_file, lines: LOG_PREVIEW_LINES)
92
+ return [] unless log_file && File.exist?(log_file)
93
+
94
+ raw = `tail -n 50 #{log_file.shellescape} 2>/dev/null`
95
+ raw.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
96
+ .lines
97
+ .map { |l| l.gsub(ANSI_REGEX, "").gsub(/[^[:print:]\t]/, "").strip }
98
+ .reject(&:empty?)
99
+ .last(lines)
100
+ rescue StandardError
101
+ []
102
+ end
103
+
104
+ def format_log_line(text)
105
+ text.length > LOG_LINE_MAX ? "#{text[0, LOG_LINE_MAX]}…" : text
106
+ end
107
+
108
+ state = fetch_state
109
+
110
+ if state["error"]
111
+ puts "⚠️ | color=red"
112
+ puts "---"
113
+ puts "Brainiac: #{state["error"]} | color=red"
114
+ exit
115
+ end
116
+
117
+ sessions = state["sessions"] || []
118
+ recent = state["recent"] || []
119
+ view_logs_script = File.join(__dir__, "view-logs-macos.rb")
120
+
121
+ # Menu bar title
122
+ if sessions.any?
123
+ puts sessions.map { |s| AGENTS.dig(s["agent"]&.downcase, :emoji) || DEFAULT_EMOJI }.join(" ")
124
+ else
125
+ puts "💤"
126
+ end
127
+
128
+ puts "---"
129
+
130
+ # Active sessions
131
+ if sessions.any?
132
+ puts "Active | size=12"
133
+ sessions.each do |s|
134
+ agent = s["agent"] || "Unknown"
135
+ info = AGENTS[agent.downcase] || {}
136
+ emoji = info[:emoji] || DEFAULT_EMOJI
137
+ color = info[:color] ? " | color=#{hex_color(info[:color])}" : ""
138
+ elapsed = format_elapsed(s["elapsed_seconds"] || 0)
139
+ context = format_context(s["card_key"])
140
+
141
+ puts "#{emoji} #{agent}: #{context} (#{elapsed})#{color}"
142
+
143
+ log_lines = tail_log(s["log_file"])
144
+ if log_lines.any?
145
+ log_lines.each do |line|
146
+ puts "-- #{format_log_line(line)} | font=#{LOG_FONT} size=#{LOG_SIZE}"
147
+ end
148
+ puts "-- ---"
149
+ end
150
+
151
+ puts "-- Open Full Log | shell=#{view_logs_script} param1=#{s["log_file"]} terminal=false refresh=false" if s["log_file"]
152
+ end
153
+ else
154
+ puts "No active sessions | size=12"
155
+ end
156
+
157
+ # Recent completed sessions
158
+ if recent.any?
159
+ puts "---"
160
+ puts "Recent | size=12"
161
+ recent.each do |s|
162
+ agent = s["agent"] || "Unknown"
163
+ emoji = AGENTS.dig(agent.downcase, :emoji) || DEFAULT_EMOJI
164
+ context = format_context(s["card_key"])
165
+ ago = time_ago(s["finished_at"]) || "?"
166
+
167
+ puts "#{emoji} #{agent}: #{context} — #{ago}"
168
+
169
+ log_lines = tail_log(s["log_file"])
170
+ if log_lines.any?
171
+ log_lines.each do |line|
172
+ puts "-- #{format_log_line(line)} | font=#{LOG_FONT} size=#{LOG_SIZE}"
173
+ end
174
+ puts "-- ---"
175
+ end
176
+
177
+ puts "-- Open Full Log | shell=#{view_logs_script} param1=#{s["log_file"]} terminal=false refresh=false" if s["log_file"]
178
+ end
179
+ end