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
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.6
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/deploy-env-macos.rb
162
- - monitor/menubar.rb
163
- - monitor/open-action.sh
164
- - monitor/setup-menubar.rb
165
- - monitor/setup-waybar-deploy-envs.rb
166
- - monitor/setup-waybar-deployments.rb
167
- - monitor/setup-waybar-module.rb
168
- - monitor/setup-xbar-plugin.rb
169
- - monitor/view-logs-macos.rb
170
- - monitor/view-logs-rofi.rb
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"