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
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# One-time setup: adds Brainiac deployments module to waybar config
|
|
5
|
-
# and repositions the agent session module with more breathing room.
|
|
6
|
-
|
|
7
|
-
require "json"
|
|
8
|
-
|
|
9
|
-
WAYBAR_CONFIG = File.expand_path("~/.config/waybar/config.jsonc")
|
|
10
|
-
DEPLOY_SCRIPT = File.expand_path("~/Code/brainiac/monitor/waybar-deployments.rb")
|
|
11
|
-
WAYBAR_STYLE = File.expand_path("~/.config/waybar/style.css")
|
|
12
|
-
|
|
13
|
-
def load_config
|
|
14
|
-
content = File.read(WAYBAR_CONFIG)
|
|
15
|
-
json_content = content.lines.reject { |line| line.strip.start_with?("//") }.join
|
|
16
|
-
JSON.parse(json_content)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def save_config(config)
|
|
20
|
-
File.write(WAYBAR_CONFIG, JSON.pretty_generate(config))
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
config = load_config
|
|
24
|
-
|
|
25
|
-
# Remove any existing deployment module
|
|
26
|
-
config["modules-center"]&.reject! { |m| m.to_s.include?("brainiac-deploy") }
|
|
27
|
-
config["modules-right"]&.reject! { |m| m.to_s.include?("brainiac-deploy") }
|
|
28
|
-
config.delete("custom/brainiac-deployments")
|
|
29
|
-
|
|
30
|
-
# Move agent session module from modules-right to modules-center (after indicators)
|
|
31
|
-
if config["modules-right"]&.delete("custom/brainiac")
|
|
32
|
-
config["modules-center"] ||= []
|
|
33
|
-
config["modules-center"] << "custom/brainiac" unless config["modules-center"].include?("custom/brainiac")
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Add deployments module right before agent sessions in modules-center
|
|
37
|
-
center = config["modules-center"] || []
|
|
38
|
-
zc_idx = center.index("custom/brainiac")
|
|
39
|
-
if zc_idx
|
|
40
|
-
center.insert(zc_idx, "custom/brainiac-deployments") unless center.include?("custom/brainiac-deployments")
|
|
41
|
-
else
|
|
42
|
-
center << "custom/brainiac-deployments" unless center.include?("custom/brainiac-deployments")
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Add module config
|
|
46
|
-
config["custom/brainiac-deployments"] = {
|
|
47
|
-
"exec" => DEPLOY_SCRIPT,
|
|
48
|
-
"return-type" => "json",
|
|
49
|
-
"interval" => 30,
|
|
50
|
-
"format" => "{}",
|
|
51
|
-
"tooltip" => true,
|
|
52
|
-
"format-alt" => "{}",
|
|
53
|
-
"escape" => false,
|
|
54
|
-
"on-click" => "#{DEPLOY_SCRIPT} --click",
|
|
55
|
-
"on-click-right" => "#{DEPLOY_SCRIPT} --deploy"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
save_config(config)
|
|
59
|
-
|
|
60
|
-
# Add CSS for the deployments module
|
|
61
|
-
style = File.read(WAYBAR_STYLE)
|
|
62
|
-
unless style.include?("#custom-brainiac-deployments")
|
|
63
|
-
css = <<~CSS
|
|
64
|
-
|
|
65
|
-
/* Brainiac deployment environment dots */
|
|
66
|
-
#custom-brainiac-deployments {
|
|
67
|
-
margin-left: 100px;
|
|
68
|
-
margin-right: 40px;
|
|
69
|
-
font-size: 14px;
|
|
70
|
-
}
|
|
71
|
-
CSS
|
|
72
|
-
File.write(WAYBAR_STYLE, style + css)
|
|
73
|
-
puts "✓ Added deployment styles to waybar CSS"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Add padding-right to agent sessions module
|
|
77
|
-
unless style.include?("padding-right") && style.include?("#custom-brainiac")
|
|
78
|
-
updated_style = File.read(WAYBAR_STYLE)
|
|
79
|
-
if updated_style.include?("#custom-brainiac {")
|
|
80
|
-
updated_style.sub!(/(#custom-brainiac\s*\{[^}]*)(\})/) do
|
|
81
|
-
block = Regexp.last_match(1)
|
|
82
|
-
close = Regexp.last_match(2)
|
|
83
|
-
if block.include?("padding-right")
|
|
84
|
-
"#{block}#{close}"
|
|
85
|
-
else
|
|
86
|
-
"#{block}\n padding-right: 100px;\n#{close}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
File.write(WAYBAR_STYLE, updated_style)
|
|
90
|
-
puts "✓ Added padding-right to agent session module"
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
puts "✓ Deployments module added to waybar config"
|
|
95
|
-
puts " Positioned: [deploy-dots] [agent-sessions] in center bar"
|
|
96
|
-
puts " Restart waybar to apply: omarchy restart waybar"
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# One-time setup script to add Brainiac module to waybar config
|
|
5
|
-
# Run this once, then the module will update dynamically without config rewrites
|
|
6
|
-
|
|
7
|
-
require "json"
|
|
8
|
-
require "fileutils"
|
|
9
|
-
|
|
10
|
-
WAYBAR_CONFIG = File.expand_path("~/.config/waybar/config.jsonc")
|
|
11
|
-
WAYBAR_SCRIPT = File.expand_path("~/.brainiac/bin/waybar-status")
|
|
12
|
-
|
|
13
|
-
# Create a wrapper script that resolves the running server's waybar.rb dynamically
|
|
14
|
-
wrapper_dir = File.expand_path("~/.brainiac/bin")
|
|
15
|
-
FileUtils.mkdir_p(wrapper_dir)
|
|
16
|
-
wrapper_path = File.join(wrapper_dir, "waybar-status")
|
|
17
|
-
File.write(wrapper_path, <<~SCRIPT)
|
|
18
|
-
#!/usr/bin/env ruby
|
|
19
|
-
# Resolves the running Brainiac server's waybar module dynamically.
|
|
20
|
-
# This allows worktrees / branches to work without reconfiguring waybar.
|
|
21
|
-
|
|
22
|
-
root_file = File.expand_path("~/.brainiac/server.root")
|
|
23
|
-
if File.exist?(root_file)
|
|
24
|
-
server_root = File.read(root_file).strip
|
|
25
|
-
waybar_script = File.join(server_root, "monitor", "waybar.rb")
|
|
26
|
-
if File.exist?(waybar_script)
|
|
27
|
-
load waybar_script
|
|
28
|
-
exit
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Fallback: no server root known, try the API directly
|
|
33
|
-
require "json"
|
|
34
|
-
require "net/http"
|
|
35
|
-
|
|
36
|
-
begin
|
|
37
|
-
uri = URI("http://localhost:4567/api/status")
|
|
38
|
-
response = Net::HTTP.get_response(uri)
|
|
39
|
-
if response.is_a?(Net::HTTPSuccess)
|
|
40
|
-
data = JSON.parse(response.body)
|
|
41
|
-
sessions = data["sessions"] || []
|
|
42
|
-
if sessions.empty?
|
|
43
|
-
puts({ text: "💤", tooltip: "No active agent sessions", class: "idle" }.to_json)
|
|
44
|
-
else
|
|
45
|
-
puts({ text: "🟢 \#{sessions.size}", tooltip: sessions.map { |s| s["agent"] }.join(", "), class: "working" }.to_json)
|
|
46
|
-
end
|
|
47
|
-
else
|
|
48
|
-
puts({ text: "⚠️", tooltip: "Brainiac Error: HTTP \#{response.code}", class: "error" }.to_json)
|
|
49
|
-
end
|
|
50
|
-
rescue StandardError => e
|
|
51
|
-
puts({ text: "⚠️", tooltip: "Brainiac Error: \#{e.message}", class: "error" }.to_json)
|
|
52
|
-
end
|
|
53
|
-
SCRIPT
|
|
54
|
-
File.chmod(0o755, wrapper_path)
|
|
55
|
-
|
|
56
|
-
def load_config
|
|
57
|
-
content = File.read(WAYBAR_CONFIG)
|
|
58
|
-
# Strip comments for JSON parsing
|
|
59
|
-
json_content = content.lines.reject { |line| line.strip.start_with?("//") }.join
|
|
60
|
-
JSON.parse(json_content)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def save_config(config)
|
|
64
|
-
File.write(WAYBAR_CONFIG, JSON.pretty_generate(config))
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Load current config
|
|
68
|
-
config = load_config
|
|
69
|
-
|
|
70
|
-
# Remove old brainiac modules if they exist (from all module arrays)
|
|
71
|
-
%w[modules-left modules-center modules-right].each do |section|
|
|
72
|
-
next unless config[section].is_a?(Array)
|
|
73
|
-
|
|
74
|
-
config[section].reject! { |m| ["custom/brainiac", "group/brainiac-agents"].include?(m.to_s) }
|
|
75
|
-
end
|
|
76
|
-
config.each_key do |key|
|
|
77
|
-
config.delete(key) if ["custom/brainiac", "group/brainiac-agents"].include?(key.to_s)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Add single dynamic module at the end of modules-center (after deploy envs)
|
|
81
|
-
config["modules-center"] ||= []
|
|
82
|
-
config["modules-center"].push("custom/brainiac")
|
|
83
|
-
|
|
84
|
-
# Add module config
|
|
85
|
-
config["custom/brainiac"] = {
|
|
86
|
-
"exec" => WAYBAR_SCRIPT,
|
|
87
|
-
"return-type" => "json",
|
|
88
|
-
"interval" => 3,
|
|
89
|
-
"format" => "{}",
|
|
90
|
-
"tooltip" => true,
|
|
91
|
-
"on-click" => File.expand_path("~/.brainiac/bin/waybar-logs").to_s
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
# Create on-click wrapper too
|
|
95
|
-
logs_wrapper = File.expand_path("~/.brainiac/bin/waybar-logs")
|
|
96
|
-
File.write(logs_wrapper, <<~SCRIPT)
|
|
97
|
-
#!/usr/bin/env ruby
|
|
98
|
-
root_file = File.expand_path("~/.brainiac/server.root")
|
|
99
|
-
if File.exist?(root_file)
|
|
100
|
-
server_root = File.read(root_file).strip
|
|
101
|
-
script = File.join(server_root, "monitor", "view-logs-rofi.rb")
|
|
102
|
-
exec("ruby", script) if File.exist?(script)
|
|
103
|
-
end
|
|
104
|
-
warn "Brainiac server root not found"
|
|
105
|
-
SCRIPT
|
|
106
|
-
File.chmod(0o755, logs_wrapper)
|
|
107
|
-
|
|
108
|
-
# Save updated config
|
|
109
|
-
save_config(config)
|
|
110
|
-
|
|
111
|
-
puts "✓ Brainiac module added to waybar config"
|
|
112
|
-
puts " Module will update every 3 seconds without config rewrites"
|
|
113
|
-
puts " Restart waybar to apply: omarchy restart waybar"
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# One-time setup: symlinks the Brainiac xbar plugin into xbar's plugin directory
|
|
5
|
-
# Run this once on macOS after installing xbar
|
|
6
|
-
|
|
7
|
-
require "fileutils"
|
|
8
|
-
|
|
9
|
-
XBAR_PLUGIN_DIR = File.expand_path("~/Library/Application Support/xbar/plugins")
|
|
10
|
-
PLUGIN_SOURCE = File.expand_path("xbar.3s.rb", __dir__)
|
|
11
|
-
PLUGIN_DEST = File.join(XBAR_PLUGIN_DIR, "brainiac.3s.rb")
|
|
12
|
-
|
|
13
|
-
unless RUBY_PLATFORM.match?(/darwin/i)
|
|
14
|
-
puts "⚠ This script is for macOS only (xbar doesn't run on Linux)"
|
|
15
|
-
exit 1
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
unless File.directory?(XBAR_PLUGIN_DIR)
|
|
19
|
-
puts "⚠ xbar plugin directory not found: #{XBAR_PLUGIN_DIR}"
|
|
20
|
-
puts " Install xbar first: https://xbarapp.com"
|
|
21
|
-
exit 1
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
if File.exist?(PLUGIN_DEST)
|
|
25
|
-
puts "Removing existing plugin at #{PLUGIN_DEST}"
|
|
26
|
-
File.delete(PLUGIN_DEST)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
File.symlink(PLUGIN_SOURCE, PLUGIN_DEST)
|
|
30
|
-
File.chmod(0o755, PLUGIN_SOURCE)
|
|
31
|
-
|
|
32
|
-
puts "✓ Brainiac xbar plugin installed"
|
|
33
|
-
puts " #{PLUGIN_SOURCE} → #{PLUGIN_DEST}"
|
|
34
|
-
puts " Refresh interval: 3 seconds"
|
|
35
|
-
puts " Restart xbar to activate"
|
data/monitor/view-logs.rb
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Brainiac Log Viewer
|
|
5
|
-
# Shows a rofi menu to select which agent log to tail
|
|
6
|
-
|
|
7
|
-
require "json"
|
|
8
|
-
require "net/http"
|
|
9
|
-
require "socket"
|
|
10
|
-
|
|
11
|
-
SOCKET_PATH = "/tmp/brainiac-monitor.sock"
|
|
12
|
-
API_URL = "http://localhost:4567/api/status"
|
|
13
|
-
CONFIG_PATH = File.expand_path("~/.brainiac/waybar.json")
|
|
14
|
-
|
|
15
|
-
# Load agent configuration from JSON
|
|
16
|
-
def load_agent_config
|
|
17
|
-
config = JSON.parse(File.read(CONFIG_PATH))
|
|
18
|
-
agents = {}
|
|
19
|
-
config["agents"].each do |agent|
|
|
20
|
-
agents[agent["name"].downcase] = agent["emoji"]
|
|
21
|
-
end
|
|
22
|
-
default_emoji = config["default_emoji"] || "❓"
|
|
23
|
-
[agents, default_emoji]
|
|
24
|
-
rescue StandardError => e
|
|
25
|
-
warn "Failed to load waybar.json: #{e.message}"
|
|
26
|
-
[{}, "❓"]
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
AGENTS, DEFAULT_EMOJI = load_agent_config
|
|
30
|
-
|
|
31
|
-
def fetch_state_from_socket
|
|
32
|
-
socket = UNIXSocket.new(SOCKET_PATH)
|
|
33
|
-
data = socket.read
|
|
34
|
-
socket.close
|
|
35
|
-
JSON.parse(data)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def fetch_state_from_api
|
|
39
|
-
uri = URI(API_URL)
|
|
40
|
-
response = Net::HTTP.get_response(uri)
|
|
41
|
-
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
42
|
-
|
|
43
|
-
JSON.parse(response.body)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def fetch_state
|
|
47
|
-
# Prefer socket (daemon mode) — faster, no HTTP overhead
|
|
48
|
-
fetch_state_from_socket
|
|
49
|
-
rescue Errno::ENOENT, Errno::ECONNREFUSED
|
|
50
|
-
# Daemon not running — fall back to direct API call
|
|
51
|
-
data = fetch_state_from_api
|
|
52
|
-
unless data
|
|
53
|
-
puts "Error: Server not reachable"
|
|
54
|
-
exit 1
|
|
55
|
-
end
|
|
56
|
-
data
|
|
57
|
-
rescue StandardError => e
|
|
58
|
-
puts "Error: #{e.message}"
|
|
59
|
-
exit 1
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def format_elapsed(seconds)
|
|
63
|
-
return "#{seconds}s" if seconds < 60
|
|
64
|
-
|
|
65
|
-
minutes = seconds / 60
|
|
66
|
-
return "#{minutes}m" if minutes < 60
|
|
67
|
-
|
|
68
|
-
hours = minutes / 60
|
|
69
|
-
"#{hours}h"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
state = fetch_state
|
|
73
|
-
sessions = state["sessions"] || []
|
|
74
|
-
|
|
75
|
-
if sessions.empty?
|
|
76
|
-
system("notify-send", "Brainiac", "No active agent sessions")
|
|
77
|
-
exit 0
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# If only one session, open it directly
|
|
81
|
-
if sessions.size == 1
|
|
82
|
-
log_file = sessions[0]["log_file"]
|
|
83
|
-
exec("alacritty", "-e", "tail", "-f", log_file) if log_file
|
|
84
|
-
exit 0
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Multiple sessions: use fzf if available, otherwise just open the first one
|
|
88
|
-
if system("which fzf > /dev/null 2>&1")
|
|
89
|
-
# Build fzf menu
|
|
90
|
-
options = sessions.map do |s|
|
|
91
|
-
agent = s["agent"]
|
|
92
|
-
elapsed = format_elapsed(s["elapsed_seconds"])
|
|
93
|
-
|
|
94
|
-
card_key = s["card_key"]
|
|
95
|
-
context = if card_key.start_with?("discord-")
|
|
96
|
-
"Discord chat"
|
|
97
|
-
elsif card_key.start_with?("card-")
|
|
98
|
-
card_key.split("-")[1]
|
|
99
|
-
else
|
|
100
|
-
card_key
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
emoji = AGENTS[agent.downcase] || DEFAULT_EMOJI
|
|
104
|
-
|
|
105
|
-
"#{emoji} #{agent}: #{context} (#{elapsed})|#{s["log_file"]}"
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
menu_text = options.join("\n")
|
|
109
|
-
selected = `echo "#{menu_text}" | fzf --prompt="Agent Logs: "`.strip
|
|
110
|
-
|
|
111
|
-
unless selected.empty?
|
|
112
|
-
log_file = selected.split("|").last
|
|
113
|
-
exec("alacritty", "-e", "tail", "-f", log_file)
|
|
114
|
-
end
|
|
115
|
-
else
|
|
116
|
-
# No menu system, just open the first log
|
|
117
|
-
log_file = sessions[0]["log_file"]
|
|
118
|
-
exec("alacritty", "-e", "tail", "-f", log_file) if log_file
|
|
119
|
-
end
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Brainiac Waybar Config Updater
|
|
5
|
-
# Dynamically updates waybar config with per-agent modules
|
|
6
|
-
|
|
7
|
-
require "json"
|
|
8
|
-
|
|
9
|
-
WAYBAR_CONFIG = File.expand_path("~/.config/waybar/config.jsonc")
|
|
10
|
-
WAYBAR_SCRIPT = File.expand_path("~/Code/brainiac/monitor/waybar.rb")
|
|
11
|
-
|
|
12
|
-
def load_config
|
|
13
|
-
content = File.read(WAYBAR_CONFIG)
|
|
14
|
-
# Strip comments for JSON parsing
|
|
15
|
-
json_content = content.lines.reject { |line| line.strip.start_with?("//") }.join
|
|
16
|
-
JSON.parse(json_content)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def save_config(config)
|
|
20
|
-
File.write(WAYBAR_CONFIG, JSON.pretty_generate(config))
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def brainiac_modules
|
|
24
|
-
output = `#{WAYBAR_SCRIPT} --config`
|
|
25
|
-
JSON.parse(output)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Load current config
|
|
29
|
-
config = load_config
|
|
30
|
-
|
|
31
|
-
# Get dynamic Brainiac modules
|
|
32
|
-
brainiac_data = brainiac_modules
|
|
33
|
-
modules = brainiac_data["modules"]
|
|
34
|
-
module_configs = brainiac_data["config"]
|
|
35
|
-
|
|
36
|
-
# Remove old brainiac modules and groups from modules-right
|
|
37
|
-
config["modules-right"].reject! { |m| m.to_s.start_with?("custom/brainiac") || m.to_s == "group/brainiac-agents" }
|
|
38
|
-
|
|
39
|
-
# Insert new modules at the beginning of modules-right
|
|
40
|
-
config["modules-right"] = modules + config["modules-right"]
|
|
41
|
-
|
|
42
|
-
# Remove old brainiac module configs and groups
|
|
43
|
-
config.each_key do |key|
|
|
44
|
-
config.delete(key) if key.to_s.start_with?("custom/brainiac") || key.to_s == "group/brainiac-agents"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Add new module configs
|
|
48
|
-
module_configs.each do |name, cfg|
|
|
49
|
-
config[name] = cfg
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Save updated config
|
|
53
|
-
save_config(config)
|
|
54
|
-
|
|
55
|
-
# Reload waybar
|
|
56
|
-
system("killall", "-SIGUSR2", "waybar")
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Brainiac Waybar Deployments Module
|
|
5
|
-
# Polls /api/deployments and outputs JSON for waybar
|
|
6
|
-
|
|
7
|
-
require "json"
|
|
8
|
-
require "net/http"
|
|
9
|
-
require "shellwords"
|
|
10
|
-
require "uri"
|
|
11
|
-
require "time"
|
|
12
|
-
|
|
13
|
-
SERVER_URL = "http://localhost:4567"
|
|
14
|
-
|
|
15
|
-
def fetch_deployments
|
|
16
|
-
uri = URI("#{SERVER_URL}/api/deployments")
|
|
17
|
-
response = Net::HTTP.get_response(uri)
|
|
18
|
-
JSON.parse(response.body)["deployments"] || []
|
|
19
|
-
rescue StandardError
|
|
20
|
-
nil
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def time_ago(iso_time)
|
|
24
|
-
return nil unless iso_time
|
|
25
|
-
|
|
26
|
-
seconds = (Time.now - Time.parse(iso_time)).to_i
|
|
27
|
-
return "#{seconds}s ago" if seconds < 60
|
|
28
|
-
|
|
29
|
-
minutes = seconds / 60
|
|
30
|
-
return "#{minutes}m ago" if minutes < 60
|
|
31
|
-
|
|
32
|
-
hours = minutes / 60
|
|
33
|
-
return "#{hours}h ago" if hours < 24
|
|
34
|
-
|
|
35
|
-
"#{hours / 24}d ago"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def generate_output
|
|
39
|
-
deployments = fetch_deployments
|
|
40
|
-
unless deployments
|
|
41
|
-
puts({ text: "", tooltip: "Deploy tracker: server unreachable", class: "error" }.to_json)
|
|
42
|
-
return
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
if deployments.empty?
|
|
46
|
-
puts({ text: "", tooltip: "No environments configured", class: "empty" }.to_json)
|
|
47
|
-
return
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
recent_window = 30 * 60 # 30 minutes
|
|
51
|
-
|
|
52
|
-
dots = deployments.map do |d|
|
|
53
|
-
if d["status"] == "occupied"
|
|
54
|
-
deploy_time = d["last_deploy_at"] || d["deployed_at"]
|
|
55
|
-
recent = deploy_time && (Time.now - Time.parse(deploy_time)) < recent_window
|
|
56
|
-
status = d["last_deploy_status"]
|
|
57
|
-
|
|
58
|
-
if status == "failed"
|
|
59
|
-
'<span color="#ff4444" background="#440000">●</span>'
|
|
60
|
-
elsif recent && status == "success"
|
|
61
|
-
'<span color="#4488ff">●</span>'
|
|
62
|
-
else
|
|
63
|
-
'<span color="#ff4444">●</span>'
|
|
64
|
-
end
|
|
65
|
-
else
|
|
66
|
-
'<span color="#44ff44">●</span>'
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
text = dots.join(" ")
|
|
70
|
-
|
|
71
|
-
# Determine CSS class based on deploy states
|
|
72
|
-
has_recent_success = deployments.any? do |d|
|
|
73
|
-
t = d["last_deploy_at"] || d["deployed_at"]
|
|
74
|
-
d["last_deploy_status"] == "success" && t && (Time.now - Time.parse(t)) < recent_window
|
|
75
|
-
end
|
|
76
|
-
has_failure = deployments.any? { |d| d["last_deploy_status"] == "failed" }
|
|
77
|
-
|
|
78
|
-
css_class = if has_failure
|
|
79
|
-
"deploy-failed"
|
|
80
|
-
elsif has_recent_success
|
|
81
|
-
"deploy-recent"
|
|
82
|
-
else
|
|
83
|
-
"deployments"
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
tooltip_lines = deployments.map do |d|
|
|
87
|
-
label = d["label"] || d["env"]
|
|
88
|
-
if d["status"] == "occupied"
|
|
89
|
-
card = d["card_number"] ? "##{d["card_number"]}" : d["branch"] || "unknown"
|
|
90
|
-
branch = d["branch"] ? " — #{d["branch"]}" : ""
|
|
91
|
-
ago = time_ago(d["deployed_at"])
|
|
92
|
-
status_icon = case d["last_deploy_status"]
|
|
93
|
-
when "failed" then "💥"
|
|
94
|
-
when "success"
|
|
95
|
-
t = d["last_deploy_at"] || d["deployed_at"]
|
|
96
|
-
t && (Time.now - Time.parse(t)) < recent_window ? "🚀✅" : "🔴"
|
|
97
|
-
else "🔴"
|
|
98
|
-
end
|
|
99
|
-
"#{status_icon} #{label}: #{card}#{branch}#{" (#{ago})" if ago}"
|
|
100
|
-
else
|
|
101
|
-
ago = time_ago(d["cleared_at"])
|
|
102
|
-
last = d["last_card"] ? " (was ##{d["last_card"]})" : ""
|
|
103
|
-
"🟢 #{label}: Available#{" #{ago}" if ago}#{last}"
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
puts({ text: text, tooltip: tooltip_lines.join("\n"), class: css_class }.to_json)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def resize_deploy_terminal
|
|
111
|
-
script = "sleep 0.5 && " \
|
|
112
|
-
'width=$(hyprctl monitors -j | ruby -rjson -e "puts JSON.parse(STDIN.read)[0][%q(width)]") && ' \
|
|
113
|
-
"delta=$(( (width / 2) - (width * 15 / 100) )) && " \
|
|
114
|
-
'hyprctl --batch "dispatch focuswindow class:brainiac-deploy; dispatch resizeactive -${delta} 0"'
|
|
115
|
-
spawn("bash", "-c", script, %i[out err] => "/dev/null")
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def handle_click
|
|
119
|
-
deployments = fetch_deployments
|
|
120
|
-
return unless deployments&.any?
|
|
121
|
-
|
|
122
|
-
# If any environment has a failed deploy, show the log
|
|
123
|
-
failed = deployments.find { |d| d["last_deploy_status"] == "failed" && d["last_deploy_log"] }
|
|
124
|
-
if failed && File.exist?(failed["last_deploy_log"].to_s)
|
|
125
|
-
spawn("alacritty", "-e", "bash", "-c",
|
|
126
|
-
"echo '=== Deploy failure: #{failed["label"] || failed["env"]} ===' && echo && cat #{Shellwords.escape(failed["last_deploy_log"])} && echo && echo 'Press Enter to close...' && read",
|
|
127
|
-
%i[out err] => "/dev/null")
|
|
128
|
-
return
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Otherwise open environment URLs
|
|
132
|
-
options = deployments.filter_map do |d|
|
|
133
|
-
url = d["url"]
|
|
134
|
-
next unless url
|
|
135
|
-
|
|
136
|
-
label = d["label"] || d["env"]
|
|
137
|
-
status = d["status"] == "occupied" ? "🔴" : "🟢"
|
|
138
|
-
card = d["card_number"] ? " ##{d["card_number"]}" : ""
|
|
139
|
-
["#{status} #{label}#{card}", url]
|
|
140
|
-
end
|
|
141
|
-
return if options.empty?
|
|
142
|
-
|
|
143
|
-
if options.length == 1
|
|
144
|
-
spawn("xdg-open", options[0][1], %i[out err] => "/dev/null")
|
|
145
|
-
else
|
|
146
|
-
labels = options.map(&:first)
|
|
147
|
-
choice = `timeout 30 zenity --list --title="Open Environment" --column="Environment" #{labels.map do |l|
|
|
148
|
-
Shellwords.escape(l)
|
|
149
|
-
end.join(" ")} 2>/dev/null`.strip
|
|
150
|
-
return if choice.empty?
|
|
151
|
-
|
|
152
|
-
selected = options.find { |label, _| label == choice }
|
|
153
|
-
spawn("xdg-open", selected[1], %i[out err] => "/dev/null") if selected
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def handle_deploy
|
|
158
|
-
deployments = fetch_deployments
|
|
159
|
-
return unless deployments&.any?
|
|
160
|
-
|
|
161
|
-
# Pick environment
|
|
162
|
-
envs = deployments.map { |d| [d["env"], d["label"] || d["env"]] }
|
|
163
|
-
if envs.length == 1
|
|
164
|
-
env_key = envs[0][0]
|
|
165
|
-
else
|
|
166
|
-
labels = envs.map { |key, label| "#{key}|#{label}" }
|
|
167
|
-
choice = `timeout 30 zenity --list --title="Deploy to..." --column="Env" --column="Label" #{labels.map do |l|
|
|
168
|
-
l.split("|").map do |p|
|
|
169
|
-
Shellwords.escape(p)
|
|
170
|
-
end.join(" ")
|
|
171
|
-
end.join(" ")} 2>/dev/null`.strip
|
|
172
|
-
return if choice.empty?
|
|
173
|
-
|
|
174
|
-
env_key = choice
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Get card number — pre-fill with current card if environment is occupied
|
|
178
|
-
selected_dep = deployments.find { |d| d["env"] == env_key }
|
|
179
|
-
prefill = selected_dep && selected_dep["status"] == "occupied" && selected_dep["card_number"] ? selected_dep["card_number"].to_s : ""
|
|
180
|
-
card_number = `timeout 60 zenity --entry --title="Deploy to #{env_key}" --text="Fizzy card number:"#{unless prefill.empty?
|
|
181
|
-
" --entry-text=#{Shellwords.escape(prefill)}"
|
|
182
|
-
end} 2>/dev/null`.strip
|
|
183
|
-
return if card_number.empty?
|
|
184
|
-
|
|
185
|
-
# Resolve worktree via glob (same pattern as fz shell function)
|
|
186
|
-
matches = Dir.glob(File.expand_path("~/Code/*fizzy-#{card_number}-*/"))
|
|
187
|
-
worktree = matches.find { |d| File.directory?(d) }
|
|
188
|
-
unless worktree
|
|
189
|
-
`timeout 10 zenity --error --text="No worktree found for card ##{card_number}" 2>/dev/null`
|
|
190
|
-
return
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
deploy_script = <<~BASH
|
|
194
|
-
cd #{Shellwords.escape(worktree)}
|
|
195
|
-
echo "🚀 #{env_key} deploy in progress..."
|
|
196
|
-
echo
|
|
197
|
-
logfile=$(mktemp)
|
|
198
|
-
./scripts/deploy.sh #{Shellwords.escape(env_key)} 2>&1 | tee "$logfile"
|
|
199
|
-
status=${PIPESTATUS[0]}
|
|
200
|
-
if [ $status -ne 0 ] && grep -q "checksums previously recorded in the dependency lock file" "$logfile"; then
|
|
201
|
-
echo
|
|
202
|
-
echo "⚠️ Terraform lock file mismatch — removing lock and running init -upgrade..."
|
|
203
|
-
echo
|
|
204
|
-
rm -f infrastructure/#{Shellwords.escape(env_key)}/.terraform.lock.hcl
|
|
205
|
-
(cd infrastructure/#{Shellwords.escape(env_key)} && terraform init -upgrade)
|
|
206
|
-
echo
|
|
207
|
-
echo "🔄 Retrying deploy..."
|
|
208
|
-
echo
|
|
209
|
-
./scripts/deploy.sh #{Shellwords.escape(env_key)} 2>&1
|
|
210
|
-
status=$?
|
|
211
|
-
fi
|
|
212
|
-
rm -f "$logfile"
|
|
213
|
-
echo
|
|
214
|
-
if [ $status -eq 0 ]; then echo "✅ Deploy complete"; else echo "❌ Deploy failed (exit $status)"; fi
|
|
215
|
-
echo "Press Enter to close..."
|
|
216
|
-
read
|
|
217
|
-
BASH
|
|
218
|
-
|
|
219
|
-
# Mark deploying via API so waybar turns orange immediately
|
|
220
|
-
begin
|
|
221
|
-
uri = URI("#{SERVER_URL}/api/deployments/#{env_key}/deploying")
|
|
222
|
-
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
|
|
223
|
-
req.body = { worktree: worktree }.to_json
|
|
224
|
-
Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) }
|
|
225
|
-
rescue StandardError
|
|
226
|
-
# Non-fatal — deploy proceeds even if server is unreachable
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
spawn("alacritty", "--class", "brainiac-deploy", "-e", "bash", "-c", deploy_script, %i[out err] => "/dev/null")
|
|
230
|
-
resize_deploy_terminal
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
if ARGV.include?("--click")
|
|
234
|
-
handle_click
|
|
235
|
-
elsif ARGV.include?("--deploy")
|
|
236
|
-
handle_deploy
|
|
237
|
-
else
|
|
238
|
-
generate_output
|
|
239
|
-
end
|