brainiac 0.0.5 → 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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +136 -6
- data/bin/brainiac +385 -19
- data/bin/brainiac-completion.bash +1 -1
- data/lib/brainiac/agents.rb +15 -38
- data/lib/brainiac/config.rb +40 -11
- data/lib/brainiac/handlers/discord/api.rb +196 -0
- data/lib/brainiac/handlers/discord/config.rb +134 -0
- data/lib/brainiac/handlers/discord/delivery.rb +196 -0
- data/lib/brainiac/handlers/discord/gateway.rb +212 -0
- data/lib/brainiac/handlers/discord/message.rb +933 -0
- data/lib/brainiac/handlers/discord/reactions.rb +215 -0
- data/lib/brainiac/handlers/discord.rb +14 -1892
- data/lib/brainiac/handlers/fizzy/assignment.rb +125 -0
- data/lib/brainiac/handlers/fizzy/comments.rb +730 -0
- data/lib/brainiac/handlers/fizzy/dedup.rb +74 -0
- data/lib/brainiac/handlers/fizzy/deploy.rb +152 -0
- data/lib/brainiac/{deployments.rb → handlers/fizzy/deployments.rb} +4 -2
- data/lib/brainiac/handlers/fizzy.rb +12 -1290
- data/lib/brainiac/handlers/github.rb +149 -212
- data/lib/brainiac/handlers/shared/git.rb +203 -0
- data/lib/brainiac/handlers/shared/inline_tags.rb +89 -0
- data/lib/brainiac/handlers/zoho.rb +79 -76
- data/lib/brainiac/helpers.rb +2 -121
- data/lib/brainiac/planning.rb +2 -2
- data/lib/brainiac/plugins.rb +129 -0
- data/lib/brainiac/restart.rb +94 -0
- data/lib/brainiac/routes/api.rb +427 -0
- data/lib/brainiac/version.rb +1 -1
- data/lib/brainiac/zoho_mail_api.rb +2 -1
- data/lib/brainiac.rb +7 -0
- data/monitor/daemon.rb +4 -27
- data/monitor/shared.rb +247 -0
- data/monitor/{waybar-deploy-env.rb → waybar/deploy_env.rb} +14 -86
- data/monitor/waybar/setup.rb +232 -0
- data/monitor/waybar/status.rb +51 -0
- data/monitor/{view-logs-rofi.rb → waybar/view_logs.rb} +21 -88
- data/monitor/{deploy-env-macos.rb → xbar/deploy_env.rb} +3 -2
- data/monitor/xbar/plugin.rb +155 -0
- data/monitor/{setup-menubar.rb → xbar/setup.rb} +14 -30
- data/receiver.rb +91 -450
- data/templates/brainiac.json.example +9 -0
- data/templates/cli-providers/kiro.json.example +8 -2
- data/templates/plugins.json.example +3 -0
- metadata +30 -20
- data/lib/user_registry.rb +0 -159
- data/monitor/menubar.rb +0 -295
- data/monitor/setup-waybar-deploy-envs.rb +0 -121
- data/monitor/setup-waybar-deployments.rb +0 -96
- data/monitor/setup-waybar-module.rb +0 -113
- data/monitor/setup-xbar-plugin.rb +0 -35
- data/monitor/view-logs.rb +0 -119
- data/monitor/waybar-config-updater.rb +0 -56
- data/monitor/waybar-deployments.rb +0 -239
- data/monitor/waybar.rb +0 -146
- data/monitor/xbar.3s.rb +0 -179
- /data/lib/brainiac/{card_index.rb → handlers/fizzy/card_index.rb} +0 -0
- /data/monitor/{open-action.sh → xbar/open_action.sh} +0 -0
- /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("&", "&").gsub("<", "<").gsub(">", ">")
|
|
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
|
|
File without changes
|