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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +136 -6
- data/bin/brainiac +382 -11
- 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
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brainiac
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andy Davis
|
|
@@ -140,47 +140,57 @@ files:
|
|
|
140
140
|
- lib/brainiac.rb
|
|
141
141
|
- lib/brainiac/agents.rb
|
|
142
142
|
- lib/brainiac/brain.rb
|
|
143
|
-
- lib/brainiac/card_index.rb
|
|
144
143
|
- lib/brainiac/config.rb
|
|
145
144
|
- lib/brainiac/cron.rb
|
|
146
|
-
- lib/brainiac/deployments.rb
|
|
147
145
|
- lib/brainiac/handlers/discord.rb
|
|
146
|
+
- lib/brainiac/handlers/discord/api.rb
|
|
147
|
+
- lib/brainiac/handlers/discord/config.rb
|
|
148
|
+
- lib/brainiac/handlers/discord/delivery.rb
|
|
149
|
+
- lib/brainiac/handlers/discord/gateway.rb
|
|
150
|
+
- lib/brainiac/handlers/discord/message.rb
|
|
151
|
+
- lib/brainiac/handlers/discord/reactions.rb
|
|
148
152
|
- lib/brainiac/handlers/fizzy.rb
|
|
153
|
+
- lib/brainiac/handlers/fizzy/assignment.rb
|
|
154
|
+
- lib/brainiac/handlers/fizzy/card_index.rb
|
|
155
|
+
- lib/brainiac/handlers/fizzy/comments.rb
|
|
156
|
+
- lib/brainiac/handlers/fizzy/dedup.rb
|
|
157
|
+
- lib/brainiac/handlers/fizzy/deploy.rb
|
|
158
|
+
- lib/brainiac/handlers/fizzy/deployments.rb
|
|
149
159
|
- lib/brainiac/handlers/github.rb
|
|
160
|
+
- lib/brainiac/handlers/shared/git.rb
|
|
161
|
+
- lib/brainiac/handlers/shared/inline_tags.rb
|
|
150
162
|
- lib/brainiac/handlers/zoho.rb
|
|
151
163
|
- lib/brainiac/helpers.rb
|
|
152
164
|
- lib/brainiac/planning.rb
|
|
165
|
+
- lib/brainiac/plugins.rb
|
|
153
166
|
- lib/brainiac/prompts.rb
|
|
167
|
+
- lib/brainiac/restart.rb
|
|
168
|
+
- lib/brainiac/routes/api.rb
|
|
154
169
|
- lib/brainiac/sessions.rb
|
|
155
170
|
- lib/brainiac/skills.rb
|
|
156
171
|
- lib/brainiac/users.rb
|
|
157
172
|
- lib/brainiac/version.rb
|
|
158
173
|
- lib/brainiac/zoho_mail_api.rb
|
|
159
|
-
- lib/user_registry.rb
|
|
160
174
|
- monitor/daemon.rb
|
|
161
|
-
- monitor/
|
|
162
|
-
- monitor/
|
|
163
|
-
- monitor/
|
|
164
|
-
- monitor/
|
|
165
|
-
- monitor/
|
|
166
|
-
- monitor/
|
|
167
|
-
- monitor/
|
|
168
|
-
- monitor/
|
|
169
|
-
- monitor/
|
|
170
|
-
- monitor/
|
|
171
|
-
- monitor/view-logs.rb
|
|
172
|
-
- monitor/waybar-config-updater.rb
|
|
173
|
-
- monitor/waybar-deploy-env.rb
|
|
174
|
-
- monitor/waybar-deployments.rb
|
|
175
|
-
- monitor/waybar.rb
|
|
176
|
-
- monitor/xbar.3s.rb
|
|
175
|
+
- monitor/shared.rb
|
|
176
|
+
- monitor/waybar/deploy_env.rb
|
|
177
|
+
- monitor/waybar/setup.rb
|
|
178
|
+
- monitor/waybar/status.rb
|
|
179
|
+
- monitor/waybar/view_logs.rb
|
|
180
|
+
- monitor/xbar/deploy_env.rb
|
|
181
|
+
- monitor/xbar/open_action.sh
|
|
182
|
+
- monitor/xbar/plugin.rb
|
|
183
|
+
- monitor/xbar/setup.rb
|
|
184
|
+
- monitor/xbar/view_logs.rb
|
|
177
185
|
- receiver.rb
|
|
178
186
|
- templates/agents.json.example
|
|
187
|
+
- templates/brainiac.json.example
|
|
179
188
|
- templates/cli-providers/grok.json.example
|
|
180
189
|
- templates/cli-providers/kiro.json.example
|
|
181
190
|
- templates/discord.json.example
|
|
182
191
|
- templates/fizzy.json.example
|
|
183
192
|
- templates/github.json.example
|
|
193
|
+
- templates/plugins.json.example
|
|
184
194
|
- templates/roles/code-reviewer.md.example
|
|
185
195
|
- templates/roles/general-engineer.md.example
|
|
186
196
|
- templates/roles/test-engineer.md.example
|
data/lib/user_registry.rb
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "json"
|
|
5
|
-
|
|
6
|
-
# UserRegistry - Centralized user identity tracking
|
|
7
|
-
#
|
|
8
|
-
# Resolves user identities across platforms (Discord, GitHub, Fizzy)
|
|
9
|
-
# and provides canonical names, aliases, and relationships.
|
|
10
|
-
#
|
|
11
|
-
# Usage:
|
|
12
|
-
# registry = UserRegistry.new
|
|
13
|
-
# user = registry.find_by_discord_id('832331260088287242')
|
|
14
|
-
# puts user['canonical_name'] # => "Adam Dalton"
|
|
15
|
-
# puts user['identities']['github']['username'] # => "dalton"
|
|
16
|
-
#
|
|
17
|
-
class UserRegistry
|
|
18
|
-
USERS_FILE = File.expand_path("~/.brainiac/users.json")
|
|
19
|
-
|
|
20
|
-
def initialize
|
|
21
|
-
@data = load_data
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Find user by Discord user ID
|
|
25
|
-
def find_by_discord_id(user_id)
|
|
26
|
-
@data["users"].find { |u| u.dig("identities", "discord", "user_id") == user_id.to_s }
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Find user by Discord username
|
|
30
|
-
def find_by_discord_username(username)
|
|
31
|
-
@data["users"].find { |u| u.dig("identities", "discord", "username") == username.to_s }
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Find user by GitHub username
|
|
35
|
-
def find_by_github_username(username)
|
|
36
|
-
@data["users"].find { |u| u.dig("identities", "github", "username") == username.to_s }
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Find user by Fizzy username
|
|
40
|
-
def find_by_fizzy_username(username)
|
|
41
|
-
@data["users"].find { |u| u.dig("identities", "fizzy", "username") == username.to_s }
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Find user by canonical name
|
|
45
|
-
def find_by_canonical_name(name)
|
|
46
|
-
@data["users"].find { |u| u["canonical_name"].downcase == name.downcase }
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Find user by any identifier (tries all platforms)
|
|
50
|
-
def find(identifier)
|
|
51
|
-
find_by_discord_id(identifier) ||
|
|
52
|
-
find_by_discord_username(identifier) ||
|
|
53
|
-
find_by_github_username(identifier) ||
|
|
54
|
-
find_by_fizzy_username(identifier) ||
|
|
55
|
-
find_by_canonical_name(identifier)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Get all users
|
|
59
|
-
def all
|
|
60
|
-
@data["users"]
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Get all human users (exclude AI agents)
|
|
64
|
-
def humans
|
|
65
|
-
@data["users"].reject { |u| u["notes"]&.include?("AI agent") }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Get all AI agents
|
|
69
|
-
def agents
|
|
70
|
-
@data["users"].select { |u| u["notes"]&.include?("AI agent") }
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Reload data from disk
|
|
74
|
-
def reload!
|
|
75
|
-
@data = load_data
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
private
|
|
79
|
-
|
|
80
|
-
def load_data
|
|
81
|
-
return { "users" => [] } unless File.exist?(USERS_FILE)
|
|
82
|
-
|
|
83
|
-
JSON.parse(File.read(USERS_FILE))
|
|
84
|
-
rescue JSON::ParserError => e
|
|
85
|
-
warn "Failed to parse #{USERS_FILE}: #{e.message}"
|
|
86
|
-
{ "users" => [] }
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# CLI interface when run directly
|
|
91
|
-
if __FILE__ == $PROGRAM_NAME
|
|
92
|
-
require "optparse"
|
|
93
|
-
|
|
94
|
-
options = {}
|
|
95
|
-
OptionParser.new do |opts|
|
|
96
|
-
opts.banner = "Usage: user_registry.rb [options] [identifier]"
|
|
97
|
-
|
|
98
|
-
opts.on("-d", "--discord-id ID", "Find by Discord user ID") do |id|
|
|
99
|
-
options[:discord_id] = id
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
opts.on("-u", "--discord-username USERNAME", "Find by Discord username") do |username|
|
|
103
|
-
options[:discord_username] = username
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
opts.on("-g", "--github USERNAME", "Find by GitHub username") do |username|
|
|
107
|
-
options[:github] = username
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
opts.on("-f", "--fizzy USERNAME", "Find by Fizzy username") do |username|
|
|
111
|
-
options[:fizzy] = username
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
opts.on("-l", "--list", "List all users") do
|
|
115
|
-
options[:list] = true
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
opts.on("--humans", "List only human users") do
|
|
119
|
-
options[:humans] = true
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
opts.on("--agents", "List only AI agents") do
|
|
123
|
-
options[:agents] = true
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
opts.on("-h", "--help", "Show this help") do
|
|
127
|
-
puts opts
|
|
128
|
-
exit
|
|
129
|
-
end
|
|
130
|
-
end.parse!
|
|
131
|
-
|
|
132
|
-
registry = UserRegistry.new
|
|
133
|
-
|
|
134
|
-
if options[:list]
|
|
135
|
-
puts JSON.pretty_generate(registry.all)
|
|
136
|
-
elsif options[:humans]
|
|
137
|
-
puts JSON.pretty_generate(registry.humans)
|
|
138
|
-
elsif options[:agents]
|
|
139
|
-
puts JSON.pretty_generate(registry.agents)
|
|
140
|
-
elsif options[:discord_id]
|
|
141
|
-
user = registry.find_by_discord_id(options[:discord_id])
|
|
142
|
-
puts user ? JSON.pretty_generate(user) : "User not found"
|
|
143
|
-
elsif options[:discord_username]
|
|
144
|
-
user = registry.find_by_discord_username(options[:discord_username])
|
|
145
|
-
puts user ? JSON.pretty_generate(user) : "User not found"
|
|
146
|
-
elsif options[:github]
|
|
147
|
-
user = registry.find_by_github_username(options[:github])
|
|
148
|
-
puts user ? JSON.pretty_generate(user) : "User not found"
|
|
149
|
-
elsif options[:fizzy]
|
|
150
|
-
user = registry.find_by_fizzy_username(options[:fizzy])
|
|
151
|
-
puts user ? JSON.pretty_generate(user) : "User not found"
|
|
152
|
-
elsif ARGV[0]
|
|
153
|
-
user = registry.find(ARGV[0])
|
|
154
|
-
puts user ? JSON.pretty_generate(user) : "User not found"
|
|
155
|
-
else
|
|
156
|
-
puts "No search criteria provided. Use --help for usage."
|
|
157
|
-
exit 1
|
|
158
|
-
end
|
|
159
|
-
end
|
data/monitor/menubar.rb
DELETED
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Brainiac macOS Menu Bar Plugin (xbar/SwiftBar)
|
|
5
|
-
# Reads from monitor daemon socket and outputs xbar-format text
|
|
6
|
-
# Mirrors monitor/waybar.rb patterns for macOS-native display
|
|
7
|
-
|
|
8
|
-
require "json"
|
|
9
|
-
require "net/http"
|
|
10
|
-
require "shellwords"
|
|
11
|
-
require "socket"
|
|
12
|
-
require "time"
|
|
13
|
-
require "uri"
|
|
14
|
-
|
|
15
|
-
SERVER_URL = "http://localhost:4567"
|
|
16
|
-
SOCKET_PATH = "/tmp/brainiac-monitor.sock"
|
|
17
|
-
CONFIG_PATH = File.expand_path("~/.brainiac/waybar.json")
|
|
18
|
-
DEFAULT_EMOJI = "❓"
|
|
19
|
-
SELF_PATH = File.realpath(__FILE__)
|
|
20
|
-
|
|
21
|
-
def load_config
|
|
22
|
-
JSON.parse(File.read(CONFIG_PATH))
|
|
23
|
-
rescue StandardError => e
|
|
24
|
-
warn "Failed to load waybar.json: #{e.message}"
|
|
25
|
-
{}
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
CONFIG = load_config.freeze
|
|
29
|
-
|
|
30
|
-
def load_agent_config
|
|
31
|
-
agents = {}
|
|
32
|
-
(CONFIG["agents"] || []).each do |agent|
|
|
33
|
-
agents[agent["name"].downcase] = { emoji: agent["emoji"], color: agent["color"] }
|
|
34
|
-
end
|
|
35
|
-
agents
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
AGENTS = load_agent_config.freeze
|
|
39
|
-
FIZZY_ACCOUNT_ID = CONFIG["fizzy_account_id"]
|
|
40
|
-
DISCORD_GUILD_ID = CONFIG["discord_guild_id"]
|
|
41
|
-
|
|
42
|
-
def fetch_state
|
|
43
|
-
socket = UNIXSocket.new(SOCKET_PATH)
|
|
44
|
-
data = socket.read
|
|
45
|
-
socket.close
|
|
46
|
-
JSON.parse(data)
|
|
47
|
-
rescue Errno::ENOENT
|
|
48
|
-
{ "sessions" => [], "count" => 0, "recent" => [], "error" => "daemon not running" }
|
|
49
|
-
rescue StandardError => e
|
|
50
|
-
{ "sessions" => [], "count" => 0, "recent" => [], "error" => e.message }
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def fetch_deployments
|
|
54
|
-
uri = URI("#{SERVER_URL}/api/deployments")
|
|
55
|
-
response = Net::HTTP.get_response(uri)
|
|
56
|
-
JSON.parse(response.body)["deployments"] || []
|
|
57
|
-
rescue StandardError
|
|
58
|
-
nil
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
DEPLOY_RECENT_WINDOW = 30 * 60
|
|
62
|
-
|
|
63
|
-
def deploy_dot(dep)
|
|
64
|
-
status = dep["last_deploy_status"]
|
|
65
|
-
if status == "deploying"
|
|
66
|
-
"🟠"
|
|
67
|
-
elsif status == "failed"
|
|
68
|
-
"💥"
|
|
69
|
-
elsif dep["status"] == "occupied"
|
|
70
|
-
deploy_time = dep["last_deploy_at"] || dep["deployed_at"]
|
|
71
|
-
recent = deploy_time && (Time.now - Time.parse(deploy_time)) < DEPLOY_RECENT_WINDOW
|
|
72
|
-
recent ? "🚀" : "🔴"
|
|
73
|
-
else
|
|
74
|
-
"🟢"
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
LOG_VIEWER_PATH = File.join(File.dirname(SELF_PATH), "view-logs-macos.rb")
|
|
79
|
-
DEPLOY_SCRIPT_PATH = File.join(File.dirname(SELF_PATH), "deploy-env-macos.rb")
|
|
80
|
-
|
|
81
|
-
ANSI_REGEX = /\e\[[0-9;]*[a-zA-Z]|\e\[\?[0-9;]*[a-zA-Z]/
|
|
82
|
-
LOG_PREVIEW_LINES = 15
|
|
83
|
-
LOG_LINE_MAX = 80
|
|
84
|
-
LOG_FONT = "SFMono-Regular"
|
|
85
|
-
LOG_SIZE = 12
|
|
86
|
-
|
|
87
|
-
def tail_log(log_file, lines: LOG_PREVIEW_LINES)
|
|
88
|
-
return [] unless log_file && File.exist?(log_file)
|
|
89
|
-
|
|
90
|
-
raw = `tail -n 50 #{log_file.shellescape} 2>/dev/null`
|
|
91
|
-
raw.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
|
|
92
|
-
.lines
|
|
93
|
-
.map { |l| l.gsub(ANSI_REGEX, "").gsub(/[^[:print:]\t]/, "").strip }
|
|
94
|
-
.reject(&:empty?)
|
|
95
|
-
.last(lines)
|
|
96
|
-
rescue StandardError
|
|
97
|
-
[]
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def format_log_line(text)
|
|
101
|
-
text.length > LOG_LINE_MAX ? "#{text[0, LOG_LINE_MAX]}…" : text
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def format_elapsed(seconds)
|
|
105
|
-
return "#{seconds}s" if seconds < 60
|
|
106
|
-
|
|
107
|
-
minutes = seconds / 60
|
|
108
|
-
return "#{minutes}m" if minutes < 60
|
|
109
|
-
|
|
110
|
-
"#{minutes / 60}h"
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def format_context(card_key)
|
|
114
|
-
return "" unless card_key
|
|
115
|
-
|
|
116
|
-
if card_key.start_with?("discord-")
|
|
117
|
-
"Discord"
|
|
118
|
-
elsif card_key.start_with?("card-")
|
|
119
|
-
"##{card_key.split("-")[1]}"
|
|
120
|
-
else
|
|
121
|
-
card_key
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def time_ago(iso_string)
|
|
126
|
-
return nil unless iso_string
|
|
127
|
-
|
|
128
|
-
seconds = (Time.now - Time.parse(iso_string)).to_i
|
|
129
|
-
"#{format_elapsed(seconds)} ago"
|
|
130
|
-
rescue StandardError
|
|
131
|
-
nil
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def log_action(log_file)
|
|
135
|
-
return "" unless log_file
|
|
136
|
-
|
|
137
|
-
" | shell=#{LOG_VIEWER_PATH} param1=#{log_file} terminal=false refresh=false"
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
OPEN_SCRIPT = File.join(File.dirname(SELF_PATH), "open-action.sh")
|
|
141
|
-
|
|
142
|
-
def full_log_action(log_file)
|
|
143
|
-
return "" unless log_file
|
|
144
|
-
|
|
145
|
-
" | shell=#{OPEN_SCRIPT} param1=#{log_file.shellescape} terminal=false refresh=false"
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def prompt_url(card_key)
|
|
149
|
-
return nil unless card_key
|
|
150
|
-
|
|
151
|
-
if card_key.start_with?("card-")
|
|
152
|
-
card_num = card_key.split("-")[1]
|
|
153
|
-
"https://app.fizzy.do/#{FIZZY_ACCOUNT_ID}/cards/#{card_num}" if FIZZY_ACCOUNT_ID && card_num
|
|
154
|
-
elsif card_key.start_with?("discord-") && DISCORD_GUILD_ID
|
|
155
|
-
parts = card_key.split("-")
|
|
156
|
-
# discord-AGENT-CHANNEL_ID-MESSAGE_ID (agent name may contain hyphens, IDs are last two numeric parts)
|
|
157
|
-
channel_id = parts[-2]
|
|
158
|
-
message_id = parts[-1]
|
|
159
|
-
"https://discord.com/channels/#{DISCORD_GUILD_ID}/#{channel_id}/#{message_id}" if channel_id && message_id
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def prompt_action(card_key)
|
|
164
|
-
url = prompt_url(card_key)
|
|
165
|
-
return "" unless url
|
|
166
|
-
|
|
167
|
-
" | shell=#{OPEN_SCRIPT} param1=#{url} terminal=false refresh=false"
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def worktree_path(log_file, card_key)
|
|
171
|
-
return nil unless log_file && card_key&.start_with?("card-")
|
|
172
|
-
|
|
173
|
-
dir = File.dirname(log_file, 2)
|
|
174
|
-
dir if File.directory?(dir) && dir != "/"
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def worktree_action(log_file, card_key)
|
|
178
|
-
path = worktree_path(log_file, card_key)
|
|
179
|
-
return "" unless path
|
|
180
|
-
|
|
181
|
-
" | shell=#{OPEN_SCRIPT} param1=#{path.shellescape} terminal=false refresh=false"
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
COLOR_MAP = {
|
|
185
|
-
"red" => "#ff5555", "green" => "#50fa7b", "blue" => "#8be9fd",
|
|
186
|
-
"yellow" => "#f1fa8c", "cyan" => "#8be9fd", "magenta" => "#ff79c6",
|
|
187
|
-
"purple" => "#bd93f9", "pink" => "#ff79c6", "white" => "#f8f8f2"
|
|
188
|
-
}.freeze
|
|
189
|
-
|
|
190
|
-
def hex_color(name)
|
|
191
|
-
COLOR_MAP[name] || name
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def generate_output
|
|
195
|
-
state = fetch_state
|
|
196
|
-
deployments = fetch_deployments
|
|
197
|
-
|
|
198
|
-
return ["⚠️", "---", state["error"], "---", "Refresh | refresh=true"].join("\n") if state["error"] && !deployments
|
|
199
|
-
|
|
200
|
-
sessions = state["sessions"] || []
|
|
201
|
-
recent = state["recent"] || []
|
|
202
|
-
lines = []
|
|
203
|
-
|
|
204
|
-
# Title line — agent emojis + deploy dots
|
|
205
|
-
parts = []
|
|
206
|
-
parts << sessions.map { |s| AGENTS.dig(s["agent"]&.downcase, :emoji) || DEFAULT_EMOJI }.join(" ") if sessions.any?
|
|
207
|
-
parts << deployments.map { |d| deploy_dot(d) }.join if deployments&.any?
|
|
208
|
-
title = parts.any? ? parts.join(" ") : "💤"
|
|
209
|
-
lines << title
|
|
210
|
-
lines << "---"
|
|
211
|
-
|
|
212
|
-
# Active sessions
|
|
213
|
-
if sessions.any?
|
|
214
|
-
lines << "Active | size=12"
|
|
215
|
-
sessions.each do |s|
|
|
216
|
-
agent_key = (s["agent"] || "").downcase
|
|
217
|
-
emoji = AGENTS.dig(agent_key, :emoji) || DEFAULT_EMOJI
|
|
218
|
-
color = AGENTS.dig(agent_key, :color)
|
|
219
|
-
color_str = color ? " color=#{hex_color(color)}" : ""
|
|
220
|
-
context = format_context(s["card_key"])
|
|
221
|
-
elapsed = format_elapsed(s["elapsed_seconds"] || 0)
|
|
222
|
-
lines << "#{emoji} #{s["agent"]}: #{context} (#{elapsed}) |#{color_str}"
|
|
223
|
-
|
|
224
|
-
tail_log(s["log_file"]).each do |line|
|
|
225
|
-
lines << "-- #{format_log_line(line)} | font=#{LOG_FONT} size=#{LOG_SIZE}"
|
|
226
|
-
end
|
|
227
|
-
lines << "-- ---" if s["log_file"]
|
|
228
|
-
lines << "-- Tail Log#{log_action(s["log_file"])}" if s["log_file"]
|
|
229
|
-
lines << "-- View Full Log#{full_log_action(s["log_file"])}" if s["log_file"]
|
|
230
|
-
lines << "-- Open Prompt#{prompt_action(s["card_key"])}" unless prompt_url(s["card_key"]).nil?
|
|
231
|
-
wt = worktree_path(s["log_file"], s["card_key"])
|
|
232
|
-
lines << "-- Open Worktree#{worktree_action(s["log_file"], s["card_key"])}" if wt
|
|
233
|
-
end
|
|
234
|
-
else
|
|
235
|
-
lines << "No active sessions | size=12"
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
# Recent completed sessions
|
|
239
|
-
if recent.any?
|
|
240
|
-
lines << "---"
|
|
241
|
-
lines << "Recent | size=12"
|
|
242
|
-
recent.each do |s|
|
|
243
|
-
agent_key = (s["agent"] || "").downcase
|
|
244
|
-
emoji = AGENTS.dig(agent_key, :emoji) || DEFAULT_EMOJI
|
|
245
|
-
context = format_context(s["card_key"])
|
|
246
|
-
ago = time_ago(s["finished_at"]) || "?"
|
|
247
|
-
lines << "#{emoji} #{s["agent"]}: #{context} — #{ago}"
|
|
248
|
-
|
|
249
|
-
tail_log(s["log_file"]).each do |line|
|
|
250
|
-
lines << "-- #{format_log_line(line)} | font=#{LOG_FONT} size=#{LOG_SIZE}"
|
|
251
|
-
end
|
|
252
|
-
lines << "-- ---" if s["log_file"]
|
|
253
|
-
lines << "-- Tail Log#{log_action(s["log_file"])}" if s["log_file"]
|
|
254
|
-
lines << "-- View Full Log#{full_log_action(s["log_file"])}" if s["log_file"]
|
|
255
|
-
lines << "-- Open Prompt#{prompt_action(s["card_key"])}" unless prompt_url(s["card_key"]).nil?
|
|
256
|
-
wt = worktree_path(s["log_file"], s["card_key"])
|
|
257
|
-
lines << "-- Open Worktree#{worktree_action(s["log_file"], s["card_key"])}" if wt
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# Deployments
|
|
262
|
-
if deployments&.any?
|
|
263
|
-
lines << "---"
|
|
264
|
-
lines << "Deployments | size=12"
|
|
265
|
-
deployments.each do |d|
|
|
266
|
-
label = d["label"] || d["env"]
|
|
267
|
-
env = d["env"]
|
|
268
|
-
dot = deploy_dot(d)
|
|
269
|
-
if d["status"] == "occupied"
|
|
270
|
-
card = d["card_number"] ? "##{d["card_number"]}" : d["branch"] || "unknown"
|
|
271
|
-
ago = time_ago(d["deployed_at"])
|
|
272
|
-
status_label = case d["last_deploy_status"]
|
|
273
|
-
when "deploying" then " — deploying…"
|
|
274
|
-
when "failed" then " — FAILED"
|
|
275
|
-
else ""
|
|
276
|
-
end
|
|
277
|
-
line = "#{dot} #{label}: #{card}#{status_label}#{" (#{ago})" if ago}"
|
|
278
|
-
url = d["url"]
|
|
279
|
-
lines << (url ? "#{line} | href=#{url}" : line)
|
|
280
|
-
else
|
|
281
|
-
ago = time_ago(d["cleared_at"])
|
|
282
|
-
last = d["last_card"] ? " (was ##{d["last_card"]})" : ""
|
|
283
|
-
lines << "#{dot} #{label}: Available#{" #{ago}" if ago}#{last}"
|
|
284
|
-
end
|
|
285
|
-
lines << "-- Deploy to #{label} | shell=#{DEPLOY_SCRIPT_PATH} param1=#{env} terminal=false refresh=true"
|
|
286
|
-
lines << "-- Open #{label} | shell=#{OPEN_SCRIPT} param1=#{d["url"]} terminal=false refresh=false" if d["status"] == "occupied" && d["url"]
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
lines << "---"
|
|
291
|
-
lines << "Refresh | refresh=true"
|
|
292
|
-
lines.join("\n")
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
puts generate_output
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# One-time setup: replaces the single brainiac-deployments module
|
|
5
|
-
# with per-environment modules so each dot gets its own border/click.
|
|
6
|
-
|
|
7
|
-
require "json"
|
|
8
|
-
require "fileutils"
|
|
9
|
-
|
|
10
|
-
WAYBAR_CONFIG = File.expand_path("~/.config/waybar/config.jsonc")
|
|
11
|
-
DEPLOY_SCRIPT = File.expand_path("~/.brainiac/bin/waybar-deploy-env")
|
|
12
|
-
DEPLOYMENTS_CONFIG = File.expand_path("~/.brainiac/deployments.json")
|
|
13
|
-
WAYBAR_STYLE = File.expand_path("~/.config/waybar/style.css")
|
|
14
|
-
|
|
15
|
-
# Create wrapper script that resolves from server.root
|
|
16
|
-
wrapper_dir = File.expand_path("~/.brainiac/bin")
|
|
17
|
-
FileUtils.mkdir_p(wrapper_dir)
|
|
18
|
-
File.write(DEPLOY_SCRIPT, <<~SCRIPT)
|
|
19
|
-
#!/usr/bin/env ruby
|
|
20
|
-
root_file = File.expand_path("~/.brainiac/server.root")
|
|
21
|
-
if File.exist?(root_file)
|
|
22
|
-
server_root = File.read(root_file).strip
|
|
23
|
-
script = File.join(server_root, "monitor", "waybar-deploy-env.rb")
|
|
24
|
-
if File.exist?(script)
|
|
25
|
-
ARGV.unshift if ARGV.empty?
|
|
26
|
-
load script
|
|
27
|
-
exit
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
require "json"
|
|
31
|
-
puts({ text: "", tooltip: "Brainiac server root not found", class: "error" }.to_json)
|
|
32
|
-
SCRIPT
|
|
33
|
-
File.chmod(0o755, DEPLOY_SCRIPT)
|
|
34
|
-
|
|
35
|
-
def load_config
|
|
36
|
-
content = File.read(WAYBAR_CONFIG)
|
|
37
|
-
json_content = content.lines.reject { |line| line.strip.start_with?("//") }.join
|
|
38
|
-
JSON.parse(json_content)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def save_config(config)
|
|
42
|
-
File.write(WAYBAR_CONFIG, JSON.pretty_generate(config))
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
deployments = JSON.parse(File.read(DEPLOYMENTS_CONFIG))
|
|
46
|
-
envs = deployments["environments"].keys
|
|
47
|
-
|
|
48
|
-
config = load_config
|
|
49
|
-
|
|
50
|
-
# Remove old single deployments module from all bar positions
|
|
51
|
-
%w[modules-left modules-center modules-right].each do |pos|
|
|
52
|
-
next unless config[pos]
|
|
53
|
-
|
|
54
|
-
config[pos].reject! { |m| m.to_s.include?("brainiac-deploy") }
|
|
55
|
-
end
|
|
56
|
-
config.delete("custom/brainiac-deployments")
|
|
57
|
-
|
|
58
|
-
# Remove any existing per-env modules
|
|
59
|
-
config.each_key do |key|
|
|
60
|
-
config.delete(key) if key.start_with?("custom/brainiac-deploy-")
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Insert per-env modules into modules-center, before custom/brainiac
|
|
64
|
-
center = config["modules-center"] || []
|
|
65
|
-
zc_idx = center.index("custom/brainiac") || center.length
|
|
66
|
-
envs.each_with_index do |env, i|
|
|
67
|
-
mod_name = "custom/brainiac-deploy-#{env}"
|
|
68
|
-
center.insert(zc_idx + i, mod_name) unless center.include?(mod_name)
|
|
69
|
-
end
|
|
70
|
-
config["modules-center"] = center
|
|
71
|
-
|
|
72
|
-
# Add module configs for each env
|
|
73
|
-
envs.each do |env|
|
|
74
|
-
mod_name = "custom/brainiac-deploy-#{env}"
|
|
75
|
-
config[mod_name] = {
|
|
76
|
-
"exec" => "#{DEPLOY_SCRIPT} #{env}",
|
|
77
|
-
"return-type" => "json",
|
|
78
|
-
"interval" => 30,
|
|
79
|
-
"format" => "{}",
|
|
80
|
-
"tooltip" => true,
|
|
81
|
-
"escape" => false,
|
|
82
|
-
"on-click" => "#{DEPLOY_SCRIPT} #{env} --click",
|
|
83
|
-
"on-click-right" => "#{DEPLOY_SCRIPT} #{env} --deploy"
|
|
84
|
-
}
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
save_config(config)
|
|
88
|
-
puts "✓ Added per-environment deploy modules: #{envs.map { |e| "custom/brainiac-deploy-#{e}" }.join(", ")}"
|
|
89
|
-
|
|
90
|
-
# Update CSS — remove old single-module styles, add per-env styles
|
|
91
|
-
style = File.read(WAYBAR_STYLE)
|
|
92
|
-
|
|
93
|
-
# Remove old block
|
|
94
|
-
style.gsub!(%r{/\* Brainiac deployment environment dots \*/.*?(?=\n\n|\n/\*|\z)}m, "")
|
|
95
|
-
style.gsub!(/\n*#custom-brainiac-deployments[^{]*\{[^}]*\}\n*/m, "")
|
|
96
|
-
|
|
97
|
-
# Add new per-env styles
|
|
98
|
-
unless style.include?("#custom-brainiac-deploy-")
|
|
99
|
-
css = <<~CSS
|
|
100
|
-
|
|
101
|
-
/* Brainiac per-environment deploy dots */
|
|
102
|
-
[id^="custom-brainiac-deploy-"] {
|
|
103
|
-
font-size: 28px;
|
|
104
|
-
padding: 0 6px;
|
|
105
|
-
border-radius: 8px;
|
|
106
|
-
border: 2px solid transparent;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
[id^="custom-brainiac-deploy-"].deploy-recent {
|
|
110
|
-
border: 2px solid #4488ff;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
[id^="custom-brainiac-deploy-"].deploy-failed {
|
|
114
|
-
border: 2px solid #ff4444;
|
|
115
|
-
}
|
|
116
|
-
CSS
|
|
117
|
-
File.write(WAYBAR_STYLE, "#{style.strip}\n#{css}")
|
|
118
|
-
puts "✓ Updated waybar CSS with per-environment border styles"
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
puts "✓ Restart waybar to apply: omarchy restart waybar"
|