brainiac 0.0.6 → 0.0.8
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 +472 -44
- data/bin/brainiac-completion.bash +1 -1
- data/lib/brainiac/agents.rb +27 -74
- data/lib/brainiac/brain.rb +6 -6
- data/lib/brainiac/config.rb +40 -76
- 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/github.rb +134 -317
- data/lib/brainiac/handlers/shared/git.rb +190 -0
- data/lib/brainiac/handlers/shared/inline_tags.rb +89 -0
- data/lib/brainiac/handlers/zoho.rb +103 -153
- data/lib/brainiac/helpers.rb +43 -455
- data/lib/brainiac/hooks.rb +86 -0
- data/lib/brainiac/plugins.rb +154 -0
- data/lib/brainiac/prompts.rb +34 -172
- data/lib/brainiac/restart.rb +112 -0
- data/lib/brainiac/routes/api.rb +411 -0
- data/lib/brainiac/users.rb +1 -7
- data/lib/brainiac/version.rb +1 -1
- data/lib/brainiac/zoho_mail_api.rb +2 -1
- data/lib/brainiac.rb +8 -1
- data/monitor/daemon.rb +4 -27
- data/monitor/shared.rb +247 -0
- data/monitor/{waybar-deploy-env.rb → waybar/deploy_env.rb} +17 -89
- 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 +149 -0
- data/monitor/{setup-menubar.rb → xbar/setup.rb} +14 -30
- data/receiver.rb +44 -551
- data/templates/agents.json.example +1 -2
- data/templates/brainiac.json.example +8 -0
- data/templates/cli-providers/kiro.json.example +8 -2
- data/templates/plugins.json.example +3 -0
- data/templates/users.json.example +0 -3
- metadata +25 -23
- data/lib/brainiac/card_index.rb +0 -389
- data/lib/brainiac/deployments.rb +0 -258
- data/lib/brainiac/handlers/fizzy.rb +0 -1292
- data/lib/brainiac/planning.rb +0 -237
- 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/templates/fizzy.json.example +0 -24
- /data/monitor/{open-action.sh → xbar/open_action.sh} +0 -0
- /data/monitor/{view-logs-macos.rb → xbar/view_logs.rb} +0 -0
|
@@ -1,76 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
# Brainiac Log Viewer (
|
|
5
|
-
# Shows a
|
|
6
|
-
# Non-blocking, safe for waybar on-click
|
|
7
|
-
|
|
8
|
-
require "json"
|
|
9
|
-
require "net/http"
|
|
10
|
-
require "socket"
|
|
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
|
-
default_emoji = config["default_emoji"] || "❓"
|
|
24
|
-
[agents, default_emoji]
|
|
25
|
-
rescue StandardError => e
|
|
26
|
-
warn "Failed to load waybar.json: #{e.message}"
|
|
27
|
-
[{}, "❓"]
|
|
28
|
-
end
|
|
4
|
+
# Brainiac Log Viewer (Linux — rofi/fuzzel/wofi/zenity/fzf)
|
|
5
|
+
# Shows a menu to select which agent log to tail, with kill support.
|
|
29
6
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def fetch_state_from_socket
|
|
33
|
-
socket = UNIXSocket.new(SOCKET_PATH)
|
|
34
|
-
data = socket.read
|
|
35
|
-
socket.close
|
|
36
|
-
JSON.parse(data)
|
|
37
|
-
end
|
|
7
|
+
require_relative "../shared"
|
|
38
8
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
response = Net::HTTP.get_response(uri)
|
|
42
|
-
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
9
|
+
AGENTS = load_agent_config.freeze
|
|
10
|
+
INFRA_CMDS = %w[kiro-cli-chat ruby-lsp clangd gopls].freeze
|
|
43
11
|
|
|
44
|
-
|
|
45
|
-
end
|
|
12
|
+
state = fetch_state
|
|
46
13
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
fetch_state_from_socket
|
|
50
|
-
rescue Errno::ENOENT, Errno::ECONNREFUSED
|
|
51
|
-
# Daemon not running — fall back to direct API call
|
|
52
|
-
data = fetch_state_from_api
|
|
53
|
-
unless data
|
|
54
|
-
system("notify-send", "Brainiac", "Server not reachable")
|
|
55
|
-
exit 1
|
|
56
|
-
end
|
|
57
|
-
data
|
|
58
|
-
rescue StandardError => e
|
|
59
|
-
system("notify-send", "Brainiac Error", e.message)
|
|
14
|
+
if state["error"]
|
|
15
|
+
system("notify-send", "Brainiac Error", state["error"])
|
|
60
16
|
exit 1
|
|
61
17
|
end
|
|
62
18
|
|
|
63
|
-
def format_elapsed(seconds)
|
|
64
|
-
return "#{seconds}s" if seconds < 60
|
|
65
|
-
|
|
66
|
-
minutes = seconds / 60
|
|
67
|
-
return "#{minutes}m" if minutes < 60
|
|
68
|
-
|
|
69
|
-
hours = minutes / 60
|
|
70
|
-
"#{hours}h"
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
state = fetch_state
|
|
74
19
|
sessions = state["sessions"] || []
|
|
75
20
|
|
|
76
21
|
if sessions.empty?
|
|
@@ -78,24 +23,15 @@ if sessions.empty?
|
|
|
78
23
|
exit 0
|
|
79
24
|
end
|
|
80
25
|
|
|
81
|
-
INFRA_CMDS = %w[kiro-cli-chat ruby-lsp clangd gopls].freeze
|
|
82
|
-
|
|
83
26
|
# Build menu entries: sessions + their child processes
|
|
84
27
|
entries = []
|
|
85
28
|
sessions.each do |s|
|
|
86
29
|
agent = s["agent"]
|
|
87
30
|
elapsed = format_elapsed(s["elapsed_seconds"])
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"Discord chat"
|
|
91
|
-
elsif card_key.start_with?("card-")
|
|
92
|
-
card_key.split("-")[1]
|
|
93
|
-
else
|
|
94
|
-
card_key
|
|
95
|
-
end
|
|
96
|
-
emoji = AGENTS[agent.downcase] || DEFAULT_EMOJI
|
|
31
|
+
context = format_context(s["card_key"])
|
|
32
|
+
emoji = AGENTS.dig(agent&.downcase, :emoji) || DEFAULT_EMOJI
|
|
97
33
|
entries << { display: "#{emoji} #{agent}: #{context} (#{elapsed})", type: :log, log: s["log_file"] }
|
|
98
|
-
entries << { display: " ⛔ Kill session: #{agent} (#{context})", type: :kill_session, card_key: card_key, agent: agent }
|
|
34
|
+
entries << { display: " ⛔ Kill session: #{agent} (#{context})", type: :kill_session, card_key: s["card_key"], agent: agent }
|
|
99
35
|
|
|
100
36
|
(s["children"] || []).each do |c|
|
|
101
37
|
cmd_short = c["cmd"].to_s.split("/").last.to_s.split.first.to_s
|
|
@@ -116,7 +52,7 @@ if entries.size == 1 && entries[0][:type] == :log
|
|
|
116
52
|
end
|
|
117
53
|
|
|
118
54
|
def find_launcher
|
|
119
|
-
%w[rofi fuzzel wofi zenity].find { |cmd| system("which #{cmd} > /dev/null 2>&1") }
|
|
55
|
+
%w[rofi fuzzel wofi zenity fzf].find { |cmd| system("which #{cmd} > /dev/null 2>&1") }
|
|
120
56
|
end
|
|
121
57
|
|
|
122
58
|
def run_menu(launcher, entries)
|
|
@@ -147,12 +83,14 @@ def run_menu(launcher, entries)
|
|
|
147
83
|
io.close_write
|
|
148
84
|
io.read.strip
|
|
149
85
|
end
|
|
86
|
+
when "fzf"
|
|
87
|
+
`echo "#{menu_text}" | fzf --prompt="Agent Sessions: "`.strip
|
|
150
88
|
end
|
|
151
89
|
end
|
|
152
90
|
|
|
153
91
|
launcher = find_launcher
|
|
154
92
|
unless launcher
|
|
155
|
-
system("notify-send", "Brainiac", "No menu launcher found (install rofi, fuzzel, wofi, or
|
|
93
|
+
system("notify-send", "Brainiac", "No menu launcher found (install rofi, fuzzel, wofi, zenity, or fzf)")
|
|
156
94
|
exit 1
|
|
157
95
|
end
|
|
158
96
|
|
|
@@ -165,30 +103,25 @@ unless selected_line.to_s.empty?
|
|
|
165
103
|
when :log
|
|
166
104
|
spawn("alacritty", "-e", "tail", "-f", selected[:log]) if selected[:log]
|
|
167
105
|
when :kill_session
|
|
168
|
-
|
|
169
|
-
uri = URI("http://localhost:4567/api/sessions/kill/#{card_key}")
|
|
106
|
+
uri = URI("#{SERVER_URL}/api/sessions/kill/#{selected[:card_key]}")
|
|
170
107
|
response = Net::HTTP.post(uri, "", { "Content-Type" => "application/json" })
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
else
|
|
174
|
-
system("notify-send", "Brainiac", "Failed to kill session: #{selected[:agent]}")
|
|
175
|
-
end
|
|
108
|
+
msg = response.is_a?(Net::HTTPSuccess) ? "Killed session: #{selected[:agent]}" : "Failed to kill session: #{selected[:agent]}"
|
|
109
|
+
system("notify-send", "Brainiac", msg)
|
|
176
110
|
when :kill_child
|
|
177
|
-
pid = selected[:pid]
|
|
178
111
|
begin
|
|
179
|
-
Process.kill("TERM", pid)
|
|
112
|
+
Process.kill("TERM", selected[:pid])
|
|
180
113
|
rescue StandardError
|
|
181
114
|
nil
|
|
182
115
|
end
|
|
183
116
|
Thread.new do
|
|
184
117
|
sleep 3
|
|
185
118
|
begin
|
|
186
|
-
Process.kill("KILL", pid)
|
|
119
|
+
Process.kill("KILL", selected[:pid])
|
|
187
120
|
rescue StandardError
|
|
188
121
|
nil
|
|
189
122
|
end
|
|
190
123
|
end
|
|
191
|
-
system("notify-send", "Brainiac", "Killed #{selected[:cmd]} (PID #{pid})")
|
|
124
|
+
system("notify-send", "Brainiac", "Killed #{selected[:cmd]} (PID #{selected[:pid]})")
|
|
192
125
|
end
|
|
193
126
|
end
|
|
194
127
|
end
|
|
@@ -59,8 +59,9 @@ unless worktree
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
unless worktree
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
dialog = "display dialog \"No worktree found for card ##{card_number}\" " \
|
|
63
|
+
"buttons {\"OK\"} default button \"OK\" with title \"Deploy Failed\" with icon stop"
|
|
64
|
+
system("osascript", "-e", dialog)
|
|
64
65
|
exit
|
|
65
66
|
end
|
|
66
67
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Brainiac macOS Menu Bar Plugin (xbar/SwiftBar)
|
|
5
|
+
# Outputs xbar-format text: sessions, recent, deployments.
|
|
6
|
+
|
|
7
|
+
require "shellwords"
|
|
8
|
+
require "time"
|
|
9
|
+
require_relative "../shared"
|
|
10
|
+
|
|
11
|
+
SELF_PATH = File.realpath(__FILE__)
|
|
12
|
+
AGENTS = load_agent_config.freeze
|
|
13
|
+
CONFIG = load_monitor_config.freeze
|
|
14
|
+
|
|
15
|
+
# URL patterns for different session types — configured in ~/.brainiac/waybar.json or monitor config
|
|
16
|
+
# Plugins can add their patterns here via the config file.
|
|
17
|
+
|
|
18
|
+
LOG_VIEWER_PATH = File.join(File.dirname(SELF_PATH), "view_logs.rb")
|
|
19
|
+
DEPLOY_SCRIPT_PATH = File.join(File.dirname(SELF_PATH), "deploy_env.rb")
|
|
20
|
+
OPEN_SCRIPT = File.join(File.dirname(SELF_PATH), "open_action.sh")
|
|
21
|
+
|
|
22
|
+
def log_action(log_file)
|
|
23
|
+
return "" unless log_file
|
|
24
|
+
|
|
25
|
+
" | shell=#{LOG_VIEWER_PATH} param1=#{log_file} terminal=false refresh=false"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def full_log_action(log_file)
|
|
29
|
+
return "" unless log_file
|
|
30
|
+
|
|
31
|
+
" | shell=#{OPEN_SCRIPT} param1=#{log_file.shellescape} terminal=false refresh=false"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def prompt_url(session)
|
|
35
|
+
# Prefer URL provided directly by the source plugin in session data
|
|
36
|
+
return session["url"] if session.is_a?(Hash) && session["url"]
|
|
37
|
+
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def prompt_action(session)
|
|
42
|
+
url = prompt_url(session)
|
|
43
|
+
url ? " | shell=#{OPEN_SCRIPT} param1=#{url} terminal=false refresh=false" : ""
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def worktree_path(log_file, card_key)
|
|
47
|
+
return nil unless log_file && card_key&.start_with?("card-")
|
|
48
|
+
|
|
49
|
+
dir = File.dirname(log_file, 2)
|
|
50
|
+
dir if File.directory?(dir) && dir != "/"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def worktree_action(log_file, card_key)
|
|
54
|
+
path = worktree_path(log_file, card_key)
|
|
55
|
+
path ? " | shell=#{OPEN_SCRIPT} param1=#{path.shellescape} terminal=false refresh=false" : ""
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def render_session_submenu(session)
|
|
59
|
+
lines = tail_log(session["log_file"]).map { |line| "-- #{format_log_line(line)} | font=#{LOG_FONT} size=#{LOG_SIZE}" }
|
|
60
|
+
lines << "-- ---" if session["log_file"]
|
|
61
|
+
lines << "-- Tail Log#{log_action(session["log_file"])}" if session["log_file"]
|
|
62
|
+
lines << "-- View Full Log#{full_log_action(session["log_file"])}" if session["log_file"]
|
|
63
|
+
lines << "-- Open Prompt#{prompt_action(session)}" unless prompt_url(session).nil?
|
|
64
|
+
wt = worktree_path(session["log_file"], session["card_key"])
|
|
65
|
+
lines << "-- Open Worktree#{worktree_action(session["log_file"], session["card_key"])}" if wt
|
|
66
|
+
lines
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def generate_output
|
|
70
|
+
state = fetch_state
|
|
71
|
+
deployments = fetch_deployments
|
|
72
|
+
|
|
73
|
+
return ["⚠️", "---", state["error"], "---", "Refresh | refresh=true"].join("\n") if state["error"] && !deployments
|
|
74
|
+
|
|
75
|
+
sessions = state["sessions"] || []
|
|
76
|
+
recent = state["recent"] || []
|
|
77
|
+
lines = []
|
|
78
|
+
|
|
79
|
+
# Title line
|
|
80
|
+
parts = []
|
|
81
|
+
parts << sessions.map { |s| AGENTS.dig(s["agent"]&.downcase, :emoji) || DEFAULT_EMOJI }.join(" ") if sessions.any?
|
|
82
|
+
parts << deployments.map { |d| deploy_dot_emoji(d) }.join if deployments&.any?
|
|
83
|
+
lines << (parts.any? ? parts.join(" ") : "💤")
|
|
84
|
+
lines << "---"
|
|
85
|
+
|
|
86
|
+
# Active sessions
|
|
87
|
+
if sessions.any?
|
|
88
|
+
lines << "Active | size=12"
|
|
89
|
+
sessions.each do |s|
|
|
90
|
+
agent_key = (s["agent"] || "").downcase
|
|
91
|
+
emoji = AGENTS.dig(agent_key, :emoji) || DEFAULT_EMOJI
|
|
92
|
+
color = AGENTS.dig(agent_key, :color)
|
|
93
|
+
color_str = color ? " color=#{hex_color(color)}" : ""
|
|
94
|
+
context = format_context(s["card_key"])
|
|
95
|
+
elapsed = format_elapsed(s["elapsed_seconds"] || 0)
|
|
96
|
+
lines << "#{emoji} #{s["agent"]}: #{context} (#{elapsed}) |#{color_str}"
|
|
97
|
+
lines.concat(render_session_submenu(s))
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
lines << "No active sessions | size=12"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Recent
|
|
104
|
+
if recent.any?
|
|
105
|
+
lines << "---"
|
|
106
|
+
lines << "Recent | size=12"
|
|
107
|
+
recent.each do |s|
|
|
108
|
+
emoji = AGENTS.dig((s["agent"] || "").downcase, :emoji) || DEFAULT_EMOJI
|
|
109
|
+
context = format_context(s["card_key"])
|
|
110
|
+
ago = time_ago(s["finished_at"]) || "?"
|
|
111
|
+
lines << "#{emoji} #{s["agent"]}: #{context} — #{ago}"
|
|
112
|
+
lines.concat(render_session_submenu(s))
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Deployments
|
|
117
|
+
if deployments&.any?
|
|
118
|
+
lines << "---"
|
|
119
|
+
lines << "Deployments | size=12"
|
|
120
|
+
deployments.each do |d|
|
|
121
|
+
label = d["label"] || d["env"]
|
|
122
|
+
env = d["env"]
|
|
123
|
+
dot = deploy_dot_emoji(d)
|
|
124
|
+
if d["status"] == "occupied"
|
|
125
|
+
card = d["card_number"] ? "##{d["card_number"]}" : d["branch"] || "unknown"
|
|
126
|
+
ago = time_ago(d["deployed_at"])
|
|
127
|
+
status_label = case d["last_deploy_status"]
|
|
128
|
+
when "deploying" then " — deploying…"
|
|
129
|
+
when "failed" then " — FAILED"
|
|
130
|
+
else ""
|
|
131
|
+
end
|
|
132
|
+
line = "#{dot} #{label}: #{card}#{status_label}#{" (#{ago})" if ago}"
|
|
133
|
+
lines << (d["url"] ? "#{line} | href=#{d["url"]}" : line)
|
|
134
|
+
else
|
|
135
|
+
ago = time_ago(d["cleared_at"])
|
|
136
|
+
last = d["last_card"] ? " (was ##{d["last_card"]})" : ""
|
|
137
|
+
lines << "#{dot} #{label}: Available#{" #{ago}" if ago}#{last}"
|
|
138
|
+
end
|
|
139
|
+
lines << "-- Deploy to #{label} | shell=#{DEPLOY_SCRIPT_PATH} param1=#{env} terminal=false refresh=true"
|
|
140
|
+
lines << "-- Open #{label} | shell=#{OPEN_SCRIPT} param1=#{d["url"]} terminal=false refresh=false" if d["status"] == "occupied" && d["url"]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
lines << "---"
|
|
145
|
+
lines << "Refresh | refresh=true"
|
|
146
|
+
lines.join("\n")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
puts generate_output
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
# One-time setup
|
|
5
|
-
#
|
|
4
|
+
# One-time setup: installs Brainiac menu bar plugin into xbar or SwiftBar.
|
|
5
|
+
#
|
|
6
|
+
# Detects which app is installed and symlinks the plugin.
|
|
7
|
+
# Run this once — the plugin auto-refreshes on its configured interval.
|
|
8
|
+
|
|
9
|
+
require "fileutils"
|
|
6
10
|
|
|
7
11
|
PLUGIN_APPS = [
|
|
8
12
|
{
|
|
@@ -18,7 +22,7 @@ PLUGIN_APPS = [
|
|
|
18
22
|
].freeze
|
|
19
23
|
|
|
20
24
|
SYMLINK_NAME = "brainiac.2s.rb"
|
|
21
|
-
SOURCE_PATH = File.join(File.dirname(File.expand_path(__FILE__)), "
|
|
25
|
+
SOURCE_PATH = File.join(File.dirname(File.expand_path(__FILE__)), "plugin.rb")
|
|
22
26
|
|
|
23
27
|
def detect_plugin_app
|
|
24
28
|
PLUGIN_APPS.each do |app|
|
|
@@ -27,33 +31,8 @@ def detect_plugin_app
|
|
|
27
31
|
nil
|
|
28
32
|
end
|
|
29
33
|
|
|
30
|
-
def install_plugin(plugin_dir, source_path)
|
|
31
|
-
FileUtils.mkdir_p(plugin_dir)
|
|
32
|
-
link_path = File.join(plugin_dir, SYMLINK_NAME)
|
|
33
|
-
|
|
34
|
-
# Remove existing symlink/file if present
|
|
35
|
-
File.delete(link_path) if File.exist?(link_path) || File.symlink?(link_path)
|
|
36
|
-
|
|
37
|
-
File.symlink(source_path, link_path)
|
|
38
|
-
rescue StandardError => e
|
|
39
|
-
warn "✗ Failed to create symlink: #{e.message}"
|
|
40
|
-
warn " Source: #{source_path}"
|
|
41
|
-
warn " Target: #{link_path}"
|
|
42
|
-
exit 1
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def verify_executable!(path) # rubocop:disable Naming/PredicateMethod
|
|
46
|
-
unless File.executable?(path)
|
|
47
|
-
File.chmod(0o755, path)
|
|
48
|
-
warn " Fixed executable permission on #{path}"
|
|
49
|
-
end
|
|
50
|
-
File.executable?(path)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
34
|
# --- Main ---
|
|
54
35
|
|
|
55
|
-
require "fileutils"
|
|
56
|
-
|
|
57
36
|
app = detect_plugin_app
|
|
58
37
|
|
|
59
38
|
unless app
|
|
@@ -69,10 +48,15 @@ unless app
|
|
|
69
48
|
end
|
|
70
49
|
|
|
71
50
|
puts "Detected #{app[:name]}"
|
|
72
|
-
install_plugin(app[:plugin_dir], SOURCE_PATH)
|
|
73
|
-
verify_executable!(SOURCE_PATH)
|
|
74
51
|
|
|
52
|
+
FileUtils.mkdir_p(app[:plugin_dir])
|
|
75
53
|
link_path = File.join(app[:plugin_dir], SYMLINK_NAME)
|
|
54
|
+
|
|
55
|
+
File.delete(link_path) if File.exist?(link_path) || File.symlink?(link_path)
|
|
56
|
+
File.symlink(SOURCE_PATH, link_path)
|
|
57
|
+
File.chmod(0o755, SOURCE_PATH)
|
|
58
|
+
|
|
76
59
|
puts "✓ Installed Brainiac plugin into #{app[:name]}"
|
|
77
60
|
puts " Symlink: #{link_path} → #{SOURCE_PATH}"
|
|
78
61
|
puts " Refresh interval: 2s"
|
|
62
|
+
puts " Restart #{app[:name]} to activate"
|