brainiac 0.0.6 → 0.0.7

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 +4 -4
  2. data/Gemfile.lock +2 -2
  3. data/README.md +136 -6
  4. data/bin/brainiac +382 -11
  5. data/bin/brainiac-completion.bash +1 -1
  6. data/lib/brainiac/agents.rb +15 -38
  7. data/lib/brainiac/config.rb +40 -11
  8. data/lib/brainiac/handlers/discord/api.rb +196 -0
  9. data/lib/brainiac/handlers/discord/config.rb +134 -0
  10. data/lib/brainiac/handlers/discord/delivery.rb +196 -0
  11. data/lib/brainiac/handlers/discord/gateway.rb +212 -0
  12. data/lib/brainiac/handlers/discord/message.rb +933 -0
  13. data/lib/brainiac/handlers/discord/reactions.rb +215 -0
  14. data/lib/brainiac/handlers/discord.rb +14 -1892
  15. data/lib/brainiac/handlers/fizzy/assignment.rb +125 -0
  16. data/lib/brainiac/handlers/fizzy/comments.rb +730 -0
  17. data/lib/brainiac/handlers/fizzy/dedup.rb +74 -0
  18. data/lib/brainiac/handlers/fizzy/deploy.rb +152 -0
  19. data/lib/brainiac/{deployments.rb → handlers/fizzy/deployments.rb} +4 -2
  20. data/lib/brainiac/handlers/fizzy.rb +12 -1290
  21. data/lib/brainiac/handlers/github.rb +149 -212
  22. data/lib/brainiac/handlers/shared/git.rb +203 -0
  23. data/lib/brainiac/handlers/shared/inline_tags.rb +89 -0
  24. data/lib/brainiac/handlers/zoho.rb +79 -76
  25. data/lib/brainiac/helpers.rb +2 -121
  26. data/lib/brainiac/planning.rb +2 -2
  27. data/lib/brainiac/plugins.rb +129 -0
  28. data/lib/brainiac/restart.rb +94 -0
  29. data/lib/brainiac/routes/api.rb +427 -0
  30. data/lib/brainiac/version.rb +1 -1
  31. data/lib/brainiac/zoho_mail_api.rb +2 -1
  32. data/lib/brainiac.rb +7 -0
  33. data/monitor/daemon.rb +4 -27
  34. data/monitor/shared.rb +247 -0
  35. data/monitor/{waybar-deploy-env.rb → waybar/deploy_env.rb} +14 -86
  36. data/monitor/waybar/setup.rb +232 -0
  37. data/monitor/waybar/status.rb +51 -0
  38. data/monitor/{view-logs-rofi.rb → waybar/view_logs.rb} +21 -88
  39. data/monitor/{deploy-env-macos.rb → xbar/deploy_env.rb} +3 -2
  40. data/monitor/xbar/plugin.rb +155 -0
  41. data/monitor/{setup-menubar.rb → xbar/setup.rb} +14 -30
  42. data/receiver.rb +91 -450
  43. data/templates/brainiac.json.example +9 -0
  44. data/templates/cli-providers/kiro.json.example +8 -2
  45. data/templates/plugins.json.example +3 -0
  46. metadata +30 -20
  47. data/lib/user_registry.rb +0 -159
  48. data/monitor/menubar.rb +0 -295
  49. data/monitor/setup-waybar-deploy-envs.rb +0 -121
  50. data/monitor/setup-waybar-deployments.rb +0 -96
  51. data/monitor/setup-waybar-module.rb +0 -113
  52. data/monitor/setup-xbar-plugin.rb +0 -35
  53. data/monitor/view-logs.rb +0 -119
  54. data/monitor/waybar-config-updater.rb +0 -56
  55. data/monitor/waybar-deployments.rb +0 -239
  56. data/monitor/waybar.rb +0 -146
  57. data/monitor/xbar.3s.rb +0 -179
  58. /data/lib/brainiac/{card_index.rb → handlers/fizzy/card_index.rb} +0 -0
  59. /data/monitor/{open-action.sh → xbar/open_action.sh} +0 -0
  60. /data/monitor/{view-logs-macos.rb → xbar/view_logs.rb} +0 -0
data/monitor/waybar.rb DELETED
@@ -1,146 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Brainiac Waybar Module
5
- # Reads from monitor daemon socket and outputs JSON for waybar
6
- # Single module that updates content dynamically (no config rewrites)
7
-
8
- require "json"
9
- require "socket"
10
- require "net/http"
11
-
12
- SOCKET_PATH = "/tmp/brainiac-monitor.sock"
13
- API_URL = "http://localhost:4567/api/status"
14
- CONFIG_PATH = File.expand_path("~/.brainiac/waybar.json")
15
-
16
- # Load agent configuration from JSON
17
- def load_agent_config
18
- config = JSON.parse(File.read(CONFIG_PATH))
19
- agents = {}
20
- config["agents"].each do |agent|
21
- agents[agent["name"].downcase] = agent["emoji"]
22
- end
23
- agents
24
- rescue StandardError => e
25
- warn "Failed to load waybar.json: #{e.message}"
26
- {}
27
- end
28
-
29
- AGENTS = load_agent_config.freeze
30
- DEFAULT_EMOJI = "❓"
31
-
32
- def normalize_agent_name(name)
33
- name.downcase
34
- end
35
-
36
- def fetch_state_from_socket
37
- socket = UNIXSocket.new(SOCKET_PATH)
38
- data = socket.read
39
- socket.close
40
- JSON.parse(data)
41
- end
42
-
43
- def fetch_state_from_api
44
- uri = URI(API_URL)
45
- response = Net::HTTP.get_response(uri)
46
- return nil unless response.is_a?(Net::HTTPSuccess)
47
-
48
- JSON.parse(response.body)
49
- end
50
-
51
- def fetch_state
52
- # Prefer socket (daemon mode) — faster, no HTTP overhead
53
- fetch_state_from_socket
54
- rescue Errno::ENOENT, Errno::ECONNREFUSED
55
- # Daemon not running — fall back to direct API call
56
- fetch_state_from_api || { "sessions" => [], "count" => 0, "error" => "server not reachable" }
57
- rescue StandardError => e
58
- { "sessions" => [], "count" => 0, "error" => e.message }
59
- end
60
-
61
- def format_elapsed(seconds)
62
- return "#{seconds}s" if seconds < 60
63
-
64
- minutes = seconds / 60
65
- return "#{minutes}m" if minutes < 60
66
-
67
- hours = minutes / 60
68
- "#{hours}h"
69
- end
70
-
71
- INFRA_CMDS = %w[kiro-cli-chat ruby-lsp clangd gopls].freeze
72
-
73
- def infra_process?(cmd_short)
74
- INFRA_CMDS.any? { |ic| cmd_short.start_with?(ic) }
75
- end
76
-
77
- def escape_pango(str)
78
- str.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
79
- end
80
-
81
- def generate_output
82
- state = fetch_state
83
-
84
- if state["error"]
85
- return {
86
- text: "⚠️",
87
- tooltip: "Brainiac Error: #{escape_pango(state["error"])}",
88
- class: "error"
89
- }
90
- end
91
-
92
- sessions = state["sessions"] || []
93
-
94
- if sessions.empty?
95
- return {
96
- text: "💤",
97
- tooltip: "No active agent sessions",
98
- class: "idle"
99
- }
100
- end
101
-
102
- # Build text: show emoji for each active agent
103
- text_parts = sessions.map { |s| AGENTS[normalize_agent_name(s["agent"])] || DEFAULT_EMOJI }
104
- text = text_parts.join(" ")
105
-
106
- # Build tooltip
107
- tooltip_lines = sessions.map do |s|
108
- agent_display = s["agent"]
109
- emoji = AGENTS[normalize_agent_name(agent_display)] || DEFAULT_EMOJI
110
- elapsed = format_elapsed(s["elapsed_seconds"])
111
-
112
- card_key = s["card_key"]
113
- context = if card_key.start_with?("discord-")
114
- "Discord chat"
115
- elsif card_key.start_with?("card-")
116
- card_key.split("-")[1]
117
- else
118
- card_key
119
- end
120
-
121
- lines = ["#{emoji} #{agent_display}: #{context} (#{elapsed})"]
122
-
123
- children = (s["children"] || []).reject do |c|
124
- cmd_short = c["cmd"].to_s.split("/").last.to_s.split.first.to_s
125
- infra_process?(cmd_short)
126
- end
127
-
128
- children.each do |c|
129
- cmd_short = c["cmd"].to_s.split("/").last.to_s.split.first.to_s
130
- cmd_short = c["cmd"].to_s[0..40] if cmd_short.empty?
131
- lines << " └ #{escape_pango(cmd_short)} (#{format_elapsed(c["elapsed_seconds"])}) [PID #{c["pid"]}]"
132
- end
133
-
134
- lines.join("\n")
135
- end
136
-
137
- tooltip_lines << "\n[Click to manage]"
138
-
139
- {
140
- text: text,
141
- tooltip: tooltip_lines.join("\n"),
142
- class: "working"
143
- }
144
- end
145
-
146
- puts generate_output.to_json
data/monitor/xbar.3s.rb DELETED
@@ -1,179 +0,0 @@
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
File without changes
File without changes