brainiac 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +136 -6
- data/bin/brainiac +385 -19
- data/bin/brainiac-completion.bash +1 -1
- data/lib/brainiac/agents.rb +15 -38
- data/lib/brainiac/config.rb +40 -11
- data/lib/brainiac/handlers/discord/api.rb +196 -0
- data/lib/brainiac/handlers/discord/config.rb +134 -0
- data/lib/brainiac/handlers/discord/delivery.rb +196 -0
- data/lib/brainiac/handlers/discord/gateway.rb +212 -0
- data/lib/brainiac/handlers/discord/message.rb +933 -0
- data/lib/brainiac/handlers/discord/reactions.rb +215 -0
- data/lib/brainiac/handlers/discord.rb +14 -1892
- data/lib/brainiac/handlers/fizzy/assignment.rb +125 -0
- data/lib/brainiac/handlers/fizzy/comments.rb +730 -0
- data/lib/brainiac/handlers/fizzy/dedup.rb +74 -0
- data/lib/brainiac/handlers/fizzy/deploy.rb +152 -0
- data/lib/brainiac/{deployments.rb → handlers/fizzy/deployments.rb} +4 -2
- data/lib/brainiac/handlers/fizzy.rb +12 -1290
- data/lib/brainiac/handlers/github.rb +149 -212
- data/lib/brainiac/handlers/shared/git.rb +203 -0
- data/lib/brainiac/handlers/shared/inline_tags.rb +89 -0
- data/lib/brainiac/handlers/zoho.rb +79 -76
- data/lib/brainiac/helpers.rb +2 -121
- data/lib/brainiac/planning.rb +2 -2
- data/lib/brainiac/plugins.rb +129 -0
- data/lib/brainiac/restart.rb +94 -0
- data/lib/brainiac/routes/api.rb +427 -0
- data/lib/brainiac/version.rb +1 -1
- data/lib/brainiac/zoho_mail_api.rb +2 -1
- data/lib/brainiac.rb +7 -0
- data/monitor/daemon.rb +4 -27
- data/monitor/shared.rb +247 -0
- data/monitor/{waybar-deploy-env.rb → waybar/deploy_env.rb} +14 -86
- data/monitor/waybar/setup.rb +232 -0
- data/monitor/waybar/status.rb +51 -0
- data/monitor/{view-logs-rofi.rb → waybar/view_logs.rb} +21 -88
- data/monitor/{deploy-env-macos.rb → xbar/deploy_env.rb} +3 -2
- data/monitor/xbar/plugin.rb +155 -0
- data/monitor/{setup-menubar.rb → xbar/setup.rb} +14 -30
- data/receiver.rb +91 -450
- data/templates/brainiac.json.example +9 -0
- data/templates/cli-providers/kiro.json.example +8 -2
- data/templates/plugins.json.example +3 -0
- metadata +30 -20
- data/lib/user_registry.rb +0 -159
- data/monitor/menubar.rb +0 -295
- data/monitor/setup-waybar-deploy-envs.rb +0 -121
- data/monitor/setup-waybar-deployments.rb +0 -96
- data/monitor/setup-waybar-module.rb +0 -113
- data/monitor/setup-xbar-plugin.rb +0 -35
- data/monitor/view-logs.rb +0 -119
- data/monitor/waybar-config-updater.rb +0 -56
- data/monitor/waybar-deployments.rb +0 -239
- data/monitor/waybar.rb +0 -146
- data/monitor/xbar.3s.rb +0 -179
- /data/lib/brainiac/{card_index.rb → handlers/fizzy/card_index.rb} +0 -0
- /data/monitor/{open-action.sh → xbar/open_action.sh} +0 -0
- /data/monitor/{view-logs-macos.rb → xbar/view_logs.rb} +0 -0
data/bin/brainiac
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
+
require "English"
|
|
3
4
|
require "json"
|
|
4
5
|
require "optparse"
|
|
5
6
|
require "fileutils"
|
|
@@ -49,6 +50,21 @@ def save_config(config)
|
|
|
49
50
|
File.write(CONFIG_FILE, JSON.pretty_generate(config))
|
|
50
51
|
end
|
|
51
52
|
|
|
53
|
+
# Resolve the default agent name: env var → brainiac.json → nil.
|
|
54
|
+
def default_agent_name
|
|
55
|
+
return ENV["AI_AGENT_NAME"] if ENV["AI_AGENT_NAME"] && !ENV["AI_AGENT_NAME"].empty?
|
|
56
|
+
|
|
57
|
+
brainiac_config_path = File.join(BRAINIAC_DIR, "brainiac.json")
|
|
58
|
+
if File.exist?(brainiac_config_path)
|
|
59
|
+
config = JSON.parse(File.read(brainiac_config_path))
|
|
60
|
+
return config["default_agent"] if config["default_agent"]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
nil
|
|
64
|
+
rescue JSON::ParserError
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
52
68
|
def find_server_pid
|
|
53
69
|
# First check PID file
|
|
54
70
|
if File.exist?(PID_FILE)
|
|
@@ -187,6 +203,23 @@ def stop_server
|
|
|
187
203
|
end
|
|
188
204
|
end
|
|
189
205
|
|
|
206
|
+
def update_handler_config(name, enabled)
|
|
207
|
+
brainiac_config_path = File.join(BRAINIAC_DIR, "brainiac.json")
|
|
208
|
+
config = if File.exist?(brainiac_config_path)
|
|
209
|
+
JSON.parse(File.read(brainiac_config_path))
|
|
210
|
+
else
|
|
211
|
+
{}
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
config["handlers"] ||= {}
|
|
215
|
+
config["handlers"][name] = enabled
|
|
216
|
+
File.write(brainiac_config_path, JSON.pretty_generate(config))
|
|
217
|
+
|
|
218
|
+
action = enabled ? "enabled" : "disabled"
|
|
219
|
+
puts "✓ Handler '#{name}' #{action}"
|
|
220
|
+
puts " Restart the server for changes to take effect: brainiac restart"
|
|
221
|
+
end
|
|
222
|
+
|
|
190
223
|
def start_server(daemon: false)
|
|
191
224
|
# Resolve the real path of the brainiac script (follows symlinks)
|
|
192
225
|
receiver_path = File.join(BRAINIAC_ROOT, "receiver.rb")
|
|
@@ -310,10 +343,19 @@ def register_project(options)
|
|
|
310
343
|
agent_model = $stdin.gets.chomp
|
|
311
344
|
agent_model = "auto" if agent_model.empty?
|
|
312
345
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
346
|
+
agent_default = default_agent_name
|
|
347
|
+
if agent_default
|
|
348
|
+
print "Agent name [#{agent_default}]: "
|
|
349
|
+
agent_name = $stdin.gets.chomp
|
|
350
|
+
agent_name = agent_default if agent_name.empty?
|
|
351
|
+
else
|
|
352
|
+
print "Agent name: "
|
|
353
|
+
agent_name = $stdin.gets.chomp
|
|
354
|
+
if agent_name.empty?
|
|
355
|
+
puts "Error: No default agent configured. Provide a name or run 'brainiac agent default <name>' first."
|
|
356
|
+
exit 1
|
|
357
|
+
end
|
|
358
|
+
end
|
|
317
359
|
|
|
318
360
|
# Build project config
|
|
319
361
|
project_config = {
|
|
@@ -524,7 +566,7 @@ def brain_search(query, scope: :knowledge, agent_name: nil)
|
|
|
524
566
|
|
|
525
567
|
collection = case scope
|
|
526
568
|
when :persona
|
|
527
|
-
agent_name ||=
|
|
569
|
+
agent_name ||= default_agent_name || "agent"
|
|
528
570
|
persona_collection_for(agent_name)
|
|
529
571
|
else
|
|
530
572
|
KNOWLEDGE_COLLECTION
|
|
@@ -611,6 +653,52 @@ when "setup"
|
|
|
611
653
|
puts "✓ Copied #{copied} example config(s) to ~/.brainiac/ (existing files preserved)"
|
|
612
654
|
end
|
|
613
655
|
|
|
656
|
+
# Copy CLI provider examples
|
|
657
|
+
providers_dir = File.join(BRAINIAC_DIR, "cli-providers")
|
|
658
|
+
FileUtils.mkdir_p(providers_dir)
|
|
659
|
+
providers_src = File.join(BRAINIAC_ROOT, "templates", "cli-providers")
|
|
660
|
+
if Dir.exist?(providers_src)
|
|
661
|
+
Dir.glob(File.join(providers_src, "*.example")).each do |template|
|
|
662
|
+
dest = File.join(providers_dir, File.basename(template, ".example"))
|
|
663
|
+
FileUtils.cp(template, dest) unless File.exist?(dest)
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# Handler configuration
|
|
668
|
+
brainiac_config_path = File.join(BRAINIAC_DIR, "brainiac.json")
|
|
669
|
+
puts ""
|
|
670
|
+
if File.exist?(brainiac_config_path)
|
|
671
|
+
puts "✓ Handler config already exists at ~/.brainiac/brainiac.json"
|
|
672
|
+
else
|
|
673
|
+
puts "Which integrations do you want to enable?"
|
|
674
|
+
puts "(You can change this later in ~/.brainiac/brainiac.json)"
|
|
675
|
+
puts ""
|
|
676
|
+
|
|
677
|
+
available_handlers = {
|
|
678
|
+
"fizzy" => "Fizzy (card management, agent dispatch via webhooks)",
|
|
679
|
+
"github" => "GitHub (PR reviews, CI failure handling, deploy tracking)",
|
|
680
|
+
"discord" => "Discord (conversational agent access via bot)",
|
|
681
|
+
"zoho" => "Zoho Mail (email notifications and triage)"
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
enabled_handlers = {}
|
|
685
|
+
available_handlers.each do |key, description|
|
|
686
|
+
print " Enable #{description}? [Y/n] "
|
|
687
|
+
answer = $stdin.gets&.strip&.downcase
|
|
688
|
+
enabled_handlers[key] = answer != "n"
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
config = { "handlers" => enabled_handlers }
|
|
692
|
+
File.write(brainiac_config_path, JSON.pretty_generate(config))
|
|
693
|
+
puts ""
|
|
694
|
+
puts "✓ Wrote handler config to ~/.brainiac/brainiac.json"
|
|
695
|
+
|
|
696
|
+
enabled_list = enabled_handlers.select { |_, v| v }.keys
|
|
697
|
+
disabled_list = enabled_handlers.reject { |_, v| v }.keys
|
|
698
|
+
puts " Enabled: #{enabled_list.join(", ")}" if enabled_list.any?
|
|
699
|
+
puts " Disabled: #{disabled_list.join(", ")}" if disabled_list.any?
|
|
700
|
+
end
|
|
701
|
+
|
|
614
702
|
puts ""
|
|
615
703
|
puts "Next steps:"
|
|
616
704
|
puts " 1. Edit config files in ~/.brainiac/ with your secrets and IDs"
|
|
@@ -684,6 +772,64 @@ when "show"
|
|
|
684
772
|
end
|
|
685
773
|
show_project(ARGV[0])
|
|
686
774
|
|
|
775
|
+
when "handler", "handlers"
|
|
776
|
+
handler_cmd = ARGV.shift
|
|
777
|
+
case handler_cmd
|
|
778
|
+
when "list", "ls", nil
|
|
779
|
+
brainiac_config_path = File.join(BRAINIAC_DIR, "brainiac.json")
|
|
780
|
+
config = if File.exist?(brainiac_config_path)
|
|
781
|
+
JSON.parse(File.read(brainiac_config_path))
|
|
782
|
+
else
|
|
783
|
+
{}
|
|
784
|
+
end
|
|
785
|
+
handlers = config["handlers"] || {}
|
|
786
|
+
|
|
787
|
+
# Built-in handlers
|
|
788
|
+
builtins = %w[fizzy github discord zoho]
|
|
789
|
+
puts "Built-in handlers:"
|
|
790
|
+
builtins.each do |name|
|
|
791
|
+
enabled = handlers.fetch(name, true)
|
|
792
|
+
status = enabled ? "✓ enabled" : "✗ disabled"
|
|
793
|
+
puts " #{name.ljust(10)} #{status}"
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
# Custom handlers
|
|
797
|
+
custom_dir = File.join(BRAINIAC_DIR, "handlers")
|
|
798
|
+
if Dir.exist?(custom_dir)
|
|
799
|
+
custom_files = Dir.glob(File.join(custom_dir, "*.rb"))
|
|
800
|
+
if custom_files.any?
|
|
801
|
+
puts ""
|
|
802
|
+
puts "Custom handlers (#{custom_dir}):"
|
|
803
|
+
custom_files.each do |f|
|
|
804
|
+
name = File.basename(f, ".rb")
|
|
805
|
+
enabled = handlers.fetch(name, true)
|
|
806
|
+
status = enabled ? "✓ enabled" : "✗ disabled"
|
|
807
|
+
puts " #{name.ljust(10)} #{status}"
|
|
808
|
+
end
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
when "enable"
|
|
813
|
+
name = ARGV.shift
|
|
814
|
+
unless name
|
|
815
|
+
puts "Usage: brainiac handler enable <name>"
|
|
816
|
+
exit 1
|
|
817
|
+
end
|
|
818
|
+
update_handler_config(name, true)
|
|
819
|
+
|
|
820
|
+
when "disable"
|
|
821
|
+
name = ARGV.shift
|
|
822
|
+
unless name
|
|
823
|
+
puts "Usage: brainiac handler disable <name>"
|
|
824
|
+
exit 1
|
|
825
|
+
end
|
|
826
|
+
update_handler_config(name, false)
|
|
827
|
+
|
|
828
|
+
else
|
|
829
|
+
puts "Usage: brainiac handler <list|enable|disable> [name]"
|
|
830
|
+
exit 1
|
|
831
|
+
end
|
|
832
|
+
|
|
687
833
|
when "config"
|
|
688
834
|
OptionParser.new do |opts|
|
|
689
835
|
opts.banner = "Usage: brainiac config [options]"
|
|
@@ -952,14 +1098,24 @@ when "brain"
|
|
|
952
1098
|
brain_cmd = ARGV.shift
|
|
953
1099
|
case brain_cmd
|
|
954
1100
|
when "init"
|
|
955
|
-
agent = ARGV[0] ||
|
|
1101
|
+
agent = ARGV[0] || default_agent_name
|
|
1102
|
+
unless agent
|
|
1103
|
+
puts "Usage: brainiac brain init <agent-name>"
|
|
1104
|
+
puts " Or set a default: brainiac agent default <name>"
|
|
1105
|
+
exit 1
|
|
1106
|
+
end
|
|
956
1107
|
brain_init(agent)
|
|
957
1108
|
when "status", "show"
|
|
958
|
-
agent = ARGV[0] ||
|
|
1109
|
+
agent = ARGV[0] || default_agent_name
|
|
1110
|
+
unless agent
|
|
1111
|
+
puts "Usage: brainiac brain status <agent-name>"
|
|
1112
|
+
puts " Or set a default: brainiac agent default <name>"
|
|
1113
|
+
exit 1
|
|
1114
|
+
end
|
|
959
1115
|
brain_status(agent)
|
|
960
1116
|
when "search", "query"
|
|
961
1117
|
scope = :knowledge
|
|
962
|
-
agent_name =
|
|
1118
|
+
agent_name = default_agent_name
|
|
963
1119
|
if ARGV.include?("--persona")
|
|
964
1120
|
ARGV.delete("--persona")
|
|
965
1121
|
scope = :persona
|
|
@@ -979,7 +1135,11 @@ when "brain"
|
|
|
979
1135
|
when "path"
|
|
980
1136
|
what = ARGV[0]
|
|
981
1137
|
if what == "persona"
|
|
982
|
-
agent = ARGV[1] ||
|
|
1138
|
+
agent = ARGV[1] || default_agent_name
|
|
1139
|
+
unless agent
|
|
1140
|
+
puts "Usage: brainiac brain path persona <agent-name>"
|
|
1141
|
+
exit 1
|
|
1142
|
+
end
|
|
983
1143
|
puts persona_dir_for(agent)
|
|
984
1144
|
else
|
|
985
1145
|
puts KNOWLEDGE_DIR
|
|
@@ -1021,7 +1181,7 @@ when "cron"
|
|
|
1021
1181
|
when "add", "create"
|
|
1022
1182
|
# Parse arguments
|
|
1023
1183
|
schedule = nil
|
|
1024
|
-
agent =
|
|
1184
|
+
agent = default_agent_name
|
|
1025
1185
|
project = nil
|
|
1026
1186
|
model = nil
|
|
1027
1187
|
effort = nil
|
|
@@ -1402,6 +1562,10 @@ when "agent"
|
|
|
1402
1562
|
case agent_cmd
|
|
1403
1563
|
when "list", "ls"
|
|
1404
1564
|
registry = File.exist?(agent_registry_file) ? JSON.parse(File.read(agent_registry_file)) : {}
|
|
1565
|
+
|
|
1566
|
+
# Determine current default agent
|
|
1567
|
+
default_agent = default_agent_name
|
|
1568
|
+
|
|
1405
1569
|
if registry.empty?
|
|
1406
1570
|
puts "No agents in registry."
|
|
1407
1571
|
else
|
|
@@ -1414,13 +1578,15 @@ when "agent"
|
|
|
1414
1578
|
end
|
|
1415
1579
|
details << "cli:#{entry["cli_provider"]}" if entry["cli_provider"]
|
|
1416
1580
|
details << "env:#{entry["env"].size}" if entry["env"]&.any?
|
|
1417
|
-
|
|
1581
|
+
is_default = default_agent && display.downcase == default_agent.downcase
|
|
1582
|
+
{ key: key, display: display, local: entry["local"], details: details.join(" | "), default: is_default }
|
|
1418
1583
|
end
|
|
1419
1584
|
|
|
1420
1585
|
local, remote = agents.partition { |a| a[:local] }
|
|
1421
1586
|
|
|
1422
1587
|
local.each do |a|
|
|
1423
1588
|
line = " #{a[:display]} (#{a[:key]})"
|
|
1589
|
+
line += " ★ default" if a[:default]
|
|
1424
1590
|
line += " #{a[:details]}" unless a[:details].empty?
|
|
1425
1591
|
puts line
|
|
1426
1592
|
end
|
|
@@ -1430,12 +1596,51 @@ when "agent"
|
|
|
1430
1596
|
puts " Remote:" if local.any?
|
|
1431
1597
|
remote.each do |a|
|
|
1432
1598
|
line = " #{a[:display]} (#{a[:key]})"
|
|
1599
|
+
line += " ★ default" if a[:default]
|
|
1433
1600
|
line += " #{a[:details]}" unless a[:details].empty?
|
|
1434
1601
|
puts line
|
|
1435
1602
|
end
|
|
1436
1603
|
end
|
|
1437
1604
|
end
|
|
1438
1605
|
|
|
1606
|
+
when "default"
|
|
1607
|
+
brainiac_config_path = File.join(BRAINIAC_DIR, "brainiac.json")
|
|
1608
|
+
brainiac_config = File.exist?(brainiac_config_path) ? JSON.parse(File.read(brainiac_config_path)) : {}
|
|
1609
|
+
name = ARGV.shift
|
|
1610
|
+
|
|
1611
|
+
if name
|
|
1612
|
+
# Set default agent
|
|
1613
|
+
registry = File.exist?(agent_registry_file) ? JSON.parse(File.read(agent_registry_file)) : {}
|
|
1614
|
+
agent_key = name.downcase.gsub(/[^a-z0-9-]/, "-")
|
|
1615
|
+
|
|
1616
|
+
# Resolve display name from registry if it exists
|
|
1617
|
+
display_name = if registry.dig(agent_key, "fizzy_name")
|
|
1618
|
+
registry[agent_key]["fizzy_name"]
|
|
1619
|
+
else
|
|
1620
|
+
name
|
|
1621
|
+
end
|
|
1622
|
+
|
|
1623
|
+
brainiac_config["default_agent"] = display_name
|
|
1624
|
+
ensure_brainiac_dir
|
|
1625
|
+
File.write(brainiac_config_path, JSON.pretty_generate(brainiac_config))
|
|
1626
|
+
puts "✓ Default agent set to '#{display_name}'"
|
|
1627
|
+
else
|
|
1628
|
+
# Show current default
|
|
1629
|
+
env_override = ENV.fetch("AI_AGENT_NAME", nil)
|
|
1630
|
+
config_default = brainiac_config["default_agent"]
|
|
1631
|
+
|
|
1632
|
+
if env_override
|
|
1633
|
+
puts "Default agent: #{env_override} (from AI_AGENT_NAME env var)"
|
|
1634
|
+
puts " Config value: #{config_default}" if config_default && config_default != env_override
|
|
1635
|
+
elsif config_default
|
|
1636
|
+
puts "Default agent: #{config_default}"
|
|
1637
|
+
else
|
|
1638
|
+
puts "No default agent configured."
|
|
1639
|
+
puts "Set one with: brainiac agent default <name>"
|
|
1640
|
+
puts " Or set AI_AGENT_NAME in your environment."
|
|
1641
|
+
end
|
|
1642
|
+
end
|
|
1643
|
+
|
|
1439
1644
|
when "create", "add"
|
|
1440
1645
|
name = ARGV.shift
|
|
1441
1646
|
unless name
|
|
@@ -1581,11 +1786,13 @@ when "agent"
|
|
|
1581
1786
|
puts <<~HELP
|
|
1582
1787
|
Usage: brainiac agent <name> <command> [args]
|
|
1583
1788
|
brainiac agent list
|
|
1789
|
+
brainiac agent default [name]
|
|
1584
1790
|
brainiac agent create <name> [options]
|
|
1585
1791
|
brainiac agent remove <name>
|
|
1586
1792
|
|
|
1587
1793
|
Commands:
|
|
1588
1794
|
list List all agents in the registry
|
|
1795
|
+
default [name] Show or set the default agent
|
|
1589
1796
|
create <name> [options] Add a new agent to the registry
|
|
1590
1797
|
remove <name> Remove an agent from the registry
|
|
1591
1798
|
<name> show Show agent configuration (tokens redacted)
|
|
@@ -1601,6 +1808,8 @@ when "agent"
|
|
|
1601
1808
|
--persona <text> Create a persona style file
|
|
1602
1809
|
|
|
1603
1810
|
Examples:
|
|
1811
|
+
brainiac agent list
|
|
1812
|
+
brainiac agent default Galen
|
|
1604
1813
|
brainiac agent create SecurityBot --local --role code-reviewer
|
|
1605
1814
|
brainiac agent create Jane --cli grok
|
|
1606
1815
|
brainiac agent create MyAgent # interactive mode
|
|
@@ -1723,14 +1932,9 @@ when "agent"
|
|
|
1723
1932
|
puts "Usage: brainiac agent #{agent_cmd} set role <roles|none>"
|
|
1724
1933
|
exit 1
|
|
1725
1934
|
end
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
puts "✓ Set role for #{agent_cmd}"
|
|
1730
|
-
next
|
|
1731
|
-
end
|
|
1732
|
-
roles = value.split(",").map(&:strip)
|
|
1733
|
-
registry[agent_key]["role"] = roles.size == 1 ? roles.first : roles
|
|
1935
|
+
roles = value == "none" ? [] : value.split(",").map(&:strip)
|
|
1936
|
+
resolved_role = roles.one? ? roles.first : roles
|
|
1937
|
+
roles.empty? ? registry[agent_key].delete("role") : registry[agent_key]["role"] = resolved_role
|
|
1734
1938
|
File.write(agent_registry_file, JSON.pretty_generate(registry))
|
|
1735
1939
|
puts "✓ Set role for #{agent_cmd}"
|
|
1736
1940
|
|
|
@@ -1941,6 +2145,160 @@ when "completions"
|
|
|
1941
2145
|
puts File.read(completion_file)
|
|
1942
2146
|
end
|
|
1943
2147
|
|
|
2148
|
+
when "install"
|
|
2149
|
+
name = ARGV.shift
|
|
2150
|
+
unless name
|
|
2151
|
+
puts "Usage: brainiac install <plugin-name>"
|
|
2152
|
+
puts ""
|
|
2153
|
+
puts "Installs a Brainiac plugin gem (brainiac-<name>) and registers it."
|
|
2154
|
+
puts ""
|
|
2155
|
+
puts "Examples:"
|
|
2156
|
+
puts " brainiac install whatsapp # installs brainiac-whatsapp gem"
|
|
2157
|
+
puts " brainiac install slack # installs brainiac-slack gem"
|
|
2158
|
+
puts ""
|
|
2159
|
+
puts "After installing, restart the server: brainiac restart"
|
|
2160
|
+
exit 1
|
|
2161
|
+
end
|
|
2162
|
+
|
|
2163
|
+
# Strip "brainiac-" prefix if user includes it
|
|
2164
|
+
name = name.sub(/^brainiac-/, "")
|
|
2165
|
+
version = nil
|
|
2166
|
+
|
|
2167
|
+
# Check for --version flag
|
|
2168
|
+
if ["--version", "-v"].include?(ARGV[0])
|
|
2169
|
+
ARGV.shift
|
|
2170
|
+
version = ARGV.shift
|
|
2171
|
+
end
|
|
2172
|
+
|
|
2173
|
+
plugins_file = File.join(BRAINIAC_DIR, "plugins.json")
|
|
2174
|
+
plugins_config = if File.exist?(plugins_file)
|
|
2175
|
+
JSON.parse(File.read(plugins_file))
|
|
2176
|
+
else
|
|
2177
|
+
{ "plugins" => [] }
|
|
2178
|
+
end
|
|
2179
|
+
|
|
2180
|
+
existing = (plugins_config["plugins"] || []).find { |p| (p.is_a?(Hash) ? p["name"] : p.to_s) == name }
|
|
2181
|
+
if existing
|
|
2182
|
+
puts "Plugin '#{name}' is already installed."
|
|
2183
|
+
puts " To reinstall: brainiac uninstall #{name} && brainiac install #{name}"
|
|
2184
|
+
exit 0
|
|
2185
|
+
end
|
|
2186
|
+
|
|
2187
|
+
gem_name = "brainiac-#{name}"
|
|
2188
|
+
puts "Installing #{gem_name}..."
|
|
2189
|
+
|
|
2190
|
+
install_cmd = "gem install #{gem_name}"
|
|
2191
|
+
install_cmd += " --version '#{version}'" if version
|
|
2192
|
+
|
|
2193
|
+
system(install_cmd)
|
|
2194
|
+
unless $CHILD_STATUS.success?
|
|
2195
|
+
puts ""
|
|
2196
|
+
puts "Failed to install #{gem_name}."
|
|
2197
|
+
puts " Check that the gem exists: https://rubygems.org/gems/#{gem_name}"
|
|
2198
|
+
exit 1
|
|
2199
|
+
end
|
|
2200
|
+
|
|
2201
|
+
# Register in plugins.json
|
|
2202
|
+
plugins_config["plugins"] ||= []
|
|
2203
|
+
entry = { "name" => name, "gem" => gem_name, "installed_at" => Time.now.iso8601 }
|
|
2204
|
+
entry["version"] = version if version
|
|
2205
|
+
plugins_config["plugins"] << entry
|
|
2206
|
+
|
|
2207
|
+
ensure_brainiac_dir
|
|
2208
|
+
File.write(plugins_file, JSON.pretty_generate(plugins_config))
|
|
2209
|
+
|
|
2210
|
+
# Enable the handler
|
|
2211
|
+
update_handler_config(name, true)
|
|
2212
|
+
|
|
2213
|
+
puts ""
|
|
2214
|
+
puts "✓ Installed plugin '#{name}' (#{gem_name})"
|
|
2215
|
+
puts " Restart the server to activate: brainiac restart"
|
|
2216
|
+
|
|
2217
|
+
when "uninstall"
|
|
2218
|
+
name = ARGV.shift
|
|
2219
|
+
unless name
|
|
2220
|
+
puts "Usage: brainiac uninstall <plugin-name>"
|
|
2221
|
+
puts ""
|
|
2222
|
+
puts "Removes a Brainiac plugin from the configuration."
|
|
2223
|
+
puts "The gem itself remains installed (use 'gem uninstall brainiac-<name>' to fully remove)."
|
|
2224
|
+
exit 1
|
|
2225
|
+
end
|
|
2226
|
+
|
|
2227
|
+
name = name.sub(/^brainiac-/, "")
|
|
2228
|
+
gem_name = "brainiac-#{name}"
|
|
2229
|
+
|
|
2230
|
+
plugins_file = File.join(BRAINIAC_DIR, "plugins.json")
|
|
2231
|
+
unless File.exist?(plugins_file)
|
|
2232
|
+
puts "Plugin '#{name}' is not installed (no plugins.json found)."
|
|
2233
|
+
exit 1
|
|
2234
|
+
end
|
|
2235
|
+
|
|
2236
|
+
plugins_config = JSON.parse(File.read(plugins_file))
|
|
2237
|
+
found = (plugins_config["plugins"] || []).any? { |p| (p.is_a?(Hash) ? p["name"] : p.to_s) == name }
|
|
2238
|
+
|
|
2239
|
+
unless found
|
|
2240
|
+
puts "Plugin '#{name}' is not installed."
|
|
2241
|
+
exit 1
|
|
2242
|
+
end
|
|
2243
|
+
|
|
2244
|
+
plugins_config["plugins"].reject! { |p| (p.is_a?(Hash) ? p["name"] : p.to_s) == name }
|
|
2245
|
+
File.write(plugins_file, JSON.pretty_generate(plugins_config))
|
|
2246
|
+
|
|
2247
|
+
# Disable the handler
|
|
2248
|
+
update_handler_config(name, false)
|
|
2249
|
+
|
|
2250
|
+
puts "✓ Removed plugin '#{name}' from Brainiac."
|
|
2251
|
+
puts " The gem #{gem_name} is still installed system-wide."
|
|
2252
|
+
puts " To fully remove: gem uninstall #{gem_name}"
|
|
2253
|
+
puts " Restart the server to apply: brainiac restart"
|
|
2254
|
+
|
|
2255
|
+
when "plugins"
|
|
2256
|
+
plugins_cmd = ARGV.shift
|
|
2257
|
+
|
|
2258
|
+
case plugins_cmd
|
|
2259
|
+
when "list", "ls", nil
|
|
2260
|
+
plugins_file = File.join(BRAINIAC_DIR, "plugins.json")
|
|
2261
|
+
plugins_config = if File.exist?(plugins_file)
|
|
2262
|
+
JSON.parse(File.read(plugins_file))
|
|
2263
|
+
else
|
|
2264
|
+
{ "plugins" => [] }
|
|
2265
|
+
end
|
|
2266
|
+
|
|
2267
|
+
plugins = plugins_config["plugins"] || []
|
|
2268
|
+
if plugins.empty?
|
|
2269
|
+
puts "No plugins installed."
|
|
2270
|
+
puts ""
|
|
2271
|
+
puts "Install plugins with: brainiac install <name>"
|
|
2272
|
+
puts " Example: brainiac install whatsapp"
|
|
2273
|
+
else
|
|
2274
|
+
puts "Installed plugins:"
|
|
2275
|
+
plugins.each do |p|
|
|
2276
|
+
entry = p.is_a?(Hash) ? p : { "name" => p.to_s }
|
|
2277
|
+
name = entry["name"]
|
|
2278
|
+
gem_name = entry["gem"] || "brainiac-#{name}"
|
|
2279
|
+
version = entry["version"] || "latest"
|
|
2280
|
+
installed_at = entry["installed_at"] ? " (#{entry["installed_at"][0..9]})" : ""
|
|
2281
|
+
|
|
2282
|
+
# Check if gem is actually loadable
|
|
2283
|
+
loadable = begin
|
|
2284
|
+
Gem::Specification.find_by_name(gem_name)
|
|
2285
|
+
"✓"
|
|
2286
|
+
rescue Gem::MissingSpecError
|
|
2287
|
+
"✗ (gem not found)"
|
|
2288
|
+
end
|
|
2289
|
+
|
|
2290
|
+
puts " #{loadable} #{name} (#{gem_name} #{version})#{installed_at}"
|
|
2291
|
+
end
|
|
2292
|
+
end
|
|
2293
|
+
else
|
|
2294
|
+
puts "Usage: brainiac plugins [list]"
|
|
2295
|
+
puts ""
|
|
2296
|
+
puts "Managing plugins:"
|
|
2297
|
+
puts " brainiac install <name> Install a plugin"
|
|
2298
|
+
puts " brainiac uninstall <name> Remove a plugin"
|
|
2299
|
+
puts " brainiac plugins List installed plugins"
|
|
2300
|
+
end
|
|
2301
|
+
|
|
1944
2302
|
when "version", "--version", "-v"
|
|
1945
2303
|
puts "brainiac #{BRAINIAC_VERSION}"
|
|
1946
2304
|
|
|
@@ -1961,6 +2319,9 @@ when "help", "--help", "-h", nil
|
|
|
1961
2319
|
brainiac projects list List all registered projects (alias)
|
|
1962
2320
|
brainiac projects default <key> Set the default project
|
|
1963
2321
|
brainiac show <key> Show project configuration
|
|
2322
|
+
brainiac install <plugin> Install a Brainiac plugin (gem-based handler)
|
|
2323
|
+
brainiac uninstall <plugin> Remove an installed plugin
|
|
2324
|
+
brainiac plugins List installed plugins
|
|
1964
2325
|
brainiac brain <command> Manage agent long-term memory (brain)
|
|
1965
2326
|
brainiac discord <command> Manage the Discord bot
|
|
1966
2327
|
brainiac cron <command> Manage scheduled agent tasks
|
|
@@ -1975,6 +2336,11 @@ when "help", "--help", "-h", nil
|
|
|
1975
2336
|
Server Options:
|
|
1976
2337
|
-d, --daemon Run server in background (detached)
|
|
1977
2338
|
|
|
2339
|
+
Plugin Commands:
|
|
2340
|
+
install <name> Install a plugin (gem install brainiac-<name>)
|
|
2341
|
+
uninstall <name> Remove a plugin
|
|
2342
|
+
plugins List installed plugins
|
|
2343
|
+
|
|
1978
2344
|
Card Map Commands:
|
|
1979
2345
|
card-map list Show all card map entries
|
|
1980
2346
|
card-map clean Remove stale entries (missing worktrees)
|
|
@@ -9,7 +9,7 @@ _brainiac() {
|
|
|
9
9
|
local brainiac_dir="${BRAINIAC_DIR:-$HOME/.brainiac}"
|
|
10
10
|
|
|
11
11
|
# Top-level commands
|
|
12
|
-
local commands="server stop restart logs status register unregister list show brain discord cron provider role agent config path version help setup projects card-map"
|
|
12
|
+
local commands="server stop restart logs status register unregister list show brain discord cron provider role agent config path version help setup projects card-map handler"
|
|
13
13
|
|
|
14
14
|
# Helper: list agent keys from registry
|
|
15
15
|
_brainiac_agents() {
|
data/lib/brainiac/agents.rb
CHANGED
|
@@ -20,49 +20,26 @@
|
|
|
20
20
|
# (card assignments). Agents without "local": true are still known for
|
|
21
21
|
# mention detection, display names, tokens, and cross-agent interactions —
|
|
22
22
|
# they just won't pick up card assignments on this machine.
|
|
23
|
-
#
|
|
24
|
-
# Legacy format with top-level `fizzy_token` / `discord_bot_token` keys is
|
|
25
|
-
# auto-migrated into the `env` hash at load time.
|
|
26
|
-
|
|
27
23
|
def load_agent_registry
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# Normalize keys: convert to lowercase, replace non-alphanumeric with hyphens
|
|
33
|
-
registry = {}
|
|
34
|
-
raw_registry.each do |key, entry|
|
|
35
|
-
normalized_key = key.downcase.gsub(/[^a-z0-9-]/, "-")
|
|
36
|
-
if registry.key?(normalized_key) && registry[normalized_key] != entry
|
|
37
|
-
LOG.warn "Duplicate agent key after normalization: '#{key}' → '#{normalized_key}' (already exists)"
|
|
38
|
-
end
|
|
39
|
-
registry[normalized_key] = entry
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Migrate legacy keys into env hash
|
|
43
|
-
registry.each_value do |entry|
|
|
44
|
-
next unless entry.is_a?(Hash)
|
|
45
|
-
|
|
46
|
-
entry["env"] ||= {}
|
|
47
|
-
# Migrate fizzy_token → FIZZY_TOKEN
|
|
48
|
-
if (ft = entry.delete("fizzy_token"))
|
|
49
|
-
entry["env"]["FIZZY_TOKEN"] ||= ft
|
|
50
|
-
end
|
|
51
|
-
# Migrate discord_bot_token → DISCORD_BOT_TOKEN
|
|
52
|
-
if (dt = entry.delete("discord_bot_token"))
|
|
53
|
-
entry["env"]["DISCORD_BOT_TOKEN"] ||= dt
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
return registry
|
|
24
|
+
unless File.exist?(AGENT_REGISTRY_FILE)
|
|
25
|
+
LOG.info "No agent registry found at #{AGENT_REGISTRY_FILE}"
|
|
26
|
+
return {}
|
|
57
27
|
end
|
|
58
28
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
29
|
+
raw_registry = JSON.parse(File.read(AGENT_REGISTRY_FILE))
|
|
30
|
+
LOG.info "Loaded agent registry (#{raw_registry.size} agents) from #{AGENT_REGISTRY_FILE}"
|
|
31
|
+
|
|
32
|
+
# Normalize keys: convert to lowercase, replace non-alphanumeric with hyphens
|
|
33
|
+
registry = {}
|
|
34
|
+
raw_registry.each do |key, entry|
|
|
35
|
+
normalized_key = key.downcase.gsub(/[^a-z0-9-]/, "-")
|
|
36
|
+
if registry.key?(normalized_key) && registry[normalized_key] != entry
|
|
37
|
+
LOG.warn "Duplicate agent key after normalization: '#{key}' → '#{normalized_key}' (already exists)"
|
|
38
|
+
end
|
|
39
|
+
registry[normalized_key] = entry
|
|
63
40
|
end
|
|
64
41
|
|
|
65
|
-
|
|
42
|
+
registry
|
|
66
43
|
rescue JSON::ParserError => e
|
|
67
44
|
LOG.error "Failed to parse agent registry: #{e.message}"
|
|
68
45
|
{}
|