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
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")
|
|
@@ -294,9 +327,9 @@ def register_project(options)
|
|
|
294
327
|
exit 1
|
|
295
328
|
end
|
|
296
329
|
|
|
297
|
-
print "
|
|
330
|
+
print "Tags (comma-separated) [#{project_key}]: "
|
|
298
331
|
tags_input = $stdin.gets.chomp
|
|
299
|
-
|
|
332
|
+
project_tags = tags_input.empty? ? [project_key] : tags_input.split(",").map(&:strip)
|
|
300
333
|
|
|
301
334
|
print "GitHub repo [#{info["github_repo"]}]: "
|
|
302
335
|
github_repo = $stdin.gets.chomp
|
|
@@ -310,15 +343,24 @@ 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 = {
|
|
320
362
|
"repo_path" => info["repo_path"],
|
|
321
|
-
"
|
|
363
|
+
"tags" => project_tags,
|
|
322
364
|
"github_repo" => github_repo,
|
|
323
365
|
"agent_name" => agent_name,
|
|
324
366
|
"agent_model" => agent_model,
|
|
@@ -331,7 +373,7 @@ def register_project(options)
|
|
|
331
373
|
puts
|
|
332
374
|
puts "✓ Project '#{project_key}' registered successfully!"
|
|
333
375
|
puts " Repo: #{info["repo_path"]}"
|
|
334
|
-
puts " Tags: #{
|
|
376
|
+
puts " Tags: #{project_tags.join(", ")}"
|
|
335
377
|
puts " GitHub: #{github_repo}" if github_repo
|
|
336
378
|
end
|
|
337
379
|
|
|
@@ -365,7 +407,7 @@ def list_projects
|
|
|
365
407
|
default_marker = config["default"] ? " (default)" : ""
|
|
366
408
|
puts " #{key}#{default_marker}"
|
|
367
409
|
puts " Path: #{config["repo_path"]}"
|
|
368
|
-
puts " Tags: #{config["
|
|
410
|
+
puts " Tags: #{config["tags"] || config["project_tags"].join(", ")}"
|
|
369
411
|
puts " GitHub: #{config["github_repo"]}" if config["github_repo"]
|
|
370
412
|
puts " Agent: #{config["agent_cli"]} (model: #{config["agent_model"]})"
|
|
371
413
|
puts
|
|
@@ -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
|
+
|
|
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[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]"
|
|
@@ -853,7 +999,7 @@ when "discord"
|
|
|
853
999
|
else
|
|
854
1000
|
puts "Agents with Discord bots:"
|
|
855
1001
|
agents_with_tokens.each do |key, entry|
|
|
856
|
-
display = entry["
|
|
1002
|
+
display = entry["display_name"] || key.capitalize
|
|
857
1003
|
token = entry.dig("env", "DISCORD_BOT_TOKEN") || entry["discord_bot_token"]
|
|
858
1004
|
token_preview = "#{token[0..10]}..."
|
|
859
1005
|
puts " #{display} (#{key}): #{token_preview}"
|
|
@@ -909,31 +1055,31 @@ when "discord"
|
|
|
909
1055
|
HELP
|
|
910
1056
|
end
|
|
911
1057
|
|
|
912
|
-
when "card-map"
|
|
913
|
-
|
|
1058
|
+
when "work-items", "card-map"
|
|
1059
|
+
work_items_file = File.join(BRAINIAC_DIR, "work_items.json")
|
|
914
1060
|
cm_cmd = ARGV.shift
|
|
915
1061
|
case cm_cmd
|
|
916
1062
|
when "clean"
|
|
917
|
-
unless File.exist?(
|
|
918
|
-
puts "No
|
|
1063
|
+
unless File.exist?(work_items_file)
|
|
1064
|
+
puts "No work items found."
|
|
919
1065
|
exit 0
|
|
920
1066
|
end
|
|
921
|
-
map = JSON.parse(File.read(
|
|
1067
|
+
map = JSON.parse(File.read(work_items_file))
|
|
922
1068
|
before = map.size
|
|
923
1069
|
# Remove entries with no valid worktree on disk
|
|
924
1070
|
map.reject! { |_id, info| info["worktree"].nil? || !File.directory?(info["worktree"].to_s) }
|
|
925
1071
|
after = map.size
|
|
926
1072
|
removed = before - after
|
|
927
|
-
File.write(
|
|
928
|
-
puts "Cleaned
|
|
1073
|
+
File.write(work_items_file, JSON.pretty_generate(map))
|
|
1074
|
+
puts "Cleaned work items: removed #{removed} stale entries (#{before} → #{after})"
|
|
929
1075
|
when "list", "ls", nil
|
|
930
|
-
unless File.exist?(
|
|
931
|
-
puts "No
|
|
1076
|
+
unless File.exist?(work_items_file)
|
|
1077
|
+
puts "No work items found."
|
|
932
1078
|
exit 0
|
|
933
1079
|
end
|
|
934
|
-
map = JSON.parse(File.read(
|
|
1080
|
+
map = JSON.parse(File.read(work_items_file))
|
|
935
1081
|
if map.empty?
|
|
936
|
-
puts "
|
|
1082
|
+
puts "No active work items."
|
|
937
1083
|
else
|
|
938
1084
|
map.each_value do |info|
|
|
939
1085
|
num = info["number"] ? "##{info["number"]}" : "(no number)"
|
|
@@ -943,7 +1089,7 @@ when "card-map"
|
|
|
943
1089
|
end
|
|
944
1090
|
end
|
|
945
1091
|
else
|
|
946
|
-
puts "Usage: brainiac
|
|
1092
|
+
puts "Usage: brainiac work-items [clean|list]"
|
|
947
1093
|
puts " clean Remove stale entries (missing worktrees)"
|
|
948
1094
|
puts " list Show all card map entries"
|
|
949
1095
|
end
|
|
@@ -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,11 +1562,15 @@ 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
|
|
1408
1572
|
agents = registry.select { |_, e| e.is_a?(Hash) }.map do |key, entry|
|
|
1409
|
-
display = entry["
|
|
1573
|
+
display = entry["display_name"] || key.capitalize
|
|
1410
1574
|
details = []
|
|
1411
1575
|
if entry["role"]
|
|
1412
1576
|
roles = Array(entry["role"])
|
|
@@ -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, "display_name")
|
|
1618
|
+
registry[agent_key]["display_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
|
|
@@ -1520,7 +1725,7 @@ when "agent"
|
|
|
1520
1725
|
persona_text = input.empty? ? nil : input
|
|
1521
1726
|
end
|
|
1522
1727
|
|
|
1523
|
-
entry = { "
|
|
1728
|
+
entry = { "display_name" => name }
|
|
1524
1729
|
entry["local"] = true if local
|
|
1525
1730
|
entry["cli_provider"] = cli_provider if cli_provider
|
|
1526
1731
|
entry["role"] = roles.size == 1 ? roles.first : roles if roles.any?
|
|
@@ -1571,7 +1776,7 @@ when "agent"
|
|
|
1571
1776
|
exit 1
|
|
1572
1777
|
end
|
|
1573
1778
|
|
|
1574
|
-
display = registry[agent_key]["
|
|
1779
|
+
display = registry[agent_key]["display_name"] || name
|
|
1575
1780
|
registry.delete(agent_key)
|
|
1576
1781
|
File.write(agent_registry_file, JSON.pretty_generate(registry))
|
|
1577
1782
|
puts "✓ Removed agent '#{display}' (#{agent_key}) from registry"
|
|
@@ -1581,15 +1786,17 @@ 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)
|
|
1592
|
-
<name> set <field> <value> Update a config field (local, cli, role, persona,
|
|
1799
|
+
<name> set <field> <value> Update a config field (local, cli, role, persona, display_name)
|
|
1593
1800
|
<name> env <KEY> <VALUE> Set an env var for an agent
|
|
1594
1801
|
<name> env List env vars for an agent
|
|
1595
1802
|
<name> env --delete <KEY> Remove an env var from an agent
|
|
@@ -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
|
|
@@ -1608,7 +1817,7 @@ when "agent"
|
|
|
1608
1817
|
brainiac agent galen set local true
|
|
1609
1818
|
brainiac agent galen set persona "Dry, sardonic"
|
|
1610
1819
|
brainiac agent galen set role code-reviewer,general-engineer
|
|
1611
|
-
brainiac agent galen env
|
|
1820
|
+
brainiac agent galen env MY_TOKEN abc123
|
|
1612
1821
|
brainiac agent galen show
|
|
1613
1822
|
HELP
|
|
1614
1823
|
|
|
@@ -1633,11 +1842,11 @@ when "agent"
|
|
|
1633
1842
|
puts JSON.pretty_generate(display)
|
|
1634
1843
|
|
|
1635
1844
|
when "env"
|
|
1636
|
-
if
|
|
1845
|
+
if ["--delete", "unset", "delete", "rm"].include?(ARGV[0])
|
|
1637
1846
|
ARGV.shift
|
|
1638
1847
|
var_name = ARGV.shift
|
|
1639
1848
|
unless var_name
|
|
1640
|
-
puts "Usage: brainiac agent #{agent_cmd} env
|
|
1849
|
+
puts "Usage: brainiac agent #{agent_cmd} env unset <KEY>"
|
|
1641
1850
|
exit 1
|
|
1642
1851
|
end
|
|
1643
1852
|
registry = File.exist?(agent_registry_file) ? JSON.parse(File.read(agent_registry_file)) : {}
|
|
@@ -1689,7 +1898,7 @@ when "agent"
|
|
|
1689
1898
|
puts " cli <provider> Set CLI provider"
|
|
1690
1899
|
puts " role <roles> Set roles (comma-separated, or 'none' to clear)"
|
|
1691
1900
|
puts " persona <text> Set/update persona style file"
|
|
1692
|
-
puts "
|
|
1901
|
+
puts " display_name <name> Set display name for @mentions"
|
|
1693
1902
|
exit 1
|
|
1694
1903
|
end
|
|
1695
1904
|
|
|
@@ -1737,7 +1946,7 @@ when "agent"
|
|
|
1737
1946
|
persona_dir = File.join(BRAINIAC_DIR, "brain", "persona", agent_key)
|
|
1738
1947
|
FileUtils.mkdir_p(persona_dir)
|
|
1739
1948
|
persona_file = File.join(persona_dir, "style.md")
|
|
1740
|
-
display = registry[agent_key]["
|
|
1949
|
+
display = registry[agent_key]["display_name"] || agent_cmd.capitalize
|
|
1741
1950
|
File.write(persona_file, <<~MD)
|
|
1742
1951
|
---
|
|
1743
1952
|
name: #{agent_key}-style
|
|
@@ -1748,17 +1957,17 @@ when "agent"
|
|
|
1748
1957
|
MD
|
|
1749
1958
|
puts "✓ Wrote persona to #{persona_file}"
|
|
1750
1959
|
|
|
1751
|
-
when "
|
|
1960
|
+
when "display_name"
|
|
1752
1961
|
unless value
|
|
1753
|
-
puts "Usage: brainiac agent #{agent_cmd} set
|
|
1962
|
+
puts "Usage: brainiac agent #{agent_cmd} set display_name <name>"
|
|
1754
1963
|
exit 1
|
|
1755
1964
|
end
|
|
1756
|
-
registry[agent_key]["
|
|
1965
|
+
registry[agent_key]["display_name"] = value
|
|
1757
1966
|
File.write(agent_registry_file, JSON.pretty_generate(registry))
|
|
1758
|
-
puts "✓ Set
|
|
1967
|
+
puts "✓ Set display_name=#{value} for #{agent_cmd}"
|
|
1759
1968
|
|
|
1760
1969
|
else
|
|
1761
|
-
puts "Unknown field '#{field}'. Available: local, cli, role, persona,
|
|
1970
|
+
puts "Unknown field '#{field}'. Available: local, cli, role, persona, display_name"
|
|
1762
1971
|
exit 1
|
|
1763
1972
|
end
|
|
1764
1973
|
|
|
@@ -1793,7 +2002,7 @@ when "role"
|
|
|
1793
2002
|
roles = entry["role"].is_a?(Array) ? entry["role"] : [entry["role"]]
|
|
1794
2003
|
roles.each do |r|
|
|
1795
2004
|
agent_roles[r] ||= []
|
|
1796
|
-
agent_roles[r] << (entry["
|
|
2005
|
+
agent_roles[r] << (entry["display_name"] || key.capitalize)
|
|
1797
2006
|
end
|
|
1798
2007
|
end
|
|
1799
2008
|
|
|
@@ -1936,6 +2145,217 @@ when "completions"
|
|
|
1936
2145
|
puts File.read(completion_file)
|
|
1937
2146
|
end
|
|
1938
2147
|
|
|
2148
|
+
when "install"
|
|
2149
|
+
name = ARGV.shift
|
|
2150
|
+
unless name
|
|
2151
|
+
puts "Usage: brainiac install <plugin-name> [options]"
|
|
2152
|
+
puts ""
|
|
2153
|
+
puts "Installs a Brainiac plugin gem (brainiac-<name>) and registers it."
|
|
2154
|
+
puts ""
|
|
2155
|
+
puts "Options:"
|
|
2156
|
+
puts " --path <dir> Use a local gem directory (for development/private gems)"
|
|
2157
|
+
puts " --version <ver> Install a specific version from RubyGems"
|
|
2158
|
+
puts ""
|
|
2159
|
+
puts "Examples:"
|
|
2160
|
+
puts " brainiac install whatsapp # from RubyGems"
|
|
2161
|
+
puts " brainiac install fizzy --path ~/Code/brainiac-fizzy # local dev"
|
|
2162
|
+
puts " brainiac install slack --version 0.2.0 # specific version"
|
|
2163
|
+
puts ""
|
|
2164
|
+
puts "After installing, restart the server: brainiac restart"
|
|
2165
|
+
exit 1
|
|
2166
|
+
end
|
|
2167
|
+
|
|
2168
|
+
# Strip "brainiac-" prefix if user includes it
|
|
2169
|
+
name = name.sub(/^brainiac-/, "")
|
|
2170
|
+
version = nil
|
|
2171
|
+
local_path = nil
|
|
2172
|
+
|
|
2173
|
+
# Parse flags
|
|
2174
|
+
while ARGV.any?
|
|
2175
|
+
case ARGV[0]
|
|
2176
|
+
when "--version", "-v"
|
|
2177
|
+
ARGV.shift
|
|
2178
|
+
version = ARGV.shift
|
|
2179
|
+
when "--path", "-p"
|
|
2180
|
+
ARGV.shift
|
|
2181
|
+
local_path = File.expand_path(ARGV.shift || "")
|
|
2182
|
+
else
|
|
2183
|
+
break
|
|
2184
|
+
end
|
|
2185
|
+
end
|
|
2186
|
+
|
|
2187
|
+
plugins_file = File.join(BRAINIAC_DIR, "plugins.json")
|
|
2188
|
+
plugins_config = if File.exist?(plugins_file)
|
|
2189
|
+
JSON.parse(File.read(plugins_file))
|
|
2190
|
+
else
|
|
2191
|
+
{ "plugins" => [] }
|
|
2192
|
+
end
|
|
2193
|
+
|
|
2194
|
+
existing = (plugins_config["plugins"] || []).find { |p| (p.is_a?(Hash) ? p["name"] : p.to_s) == name }
|
|
2195
|
+
if existing
|
|
2196
|
+
puts "Plugin '#{name}' is already installed."
|
|
2197
|
+
puts " To reinstall: brainiac uninstall #{name} && brainiac install #{name}"
|
|
2198
|
+
exit 0
|
|
2199
|
+
end
|
|
2200
|
+
|
|
2201
|
+
gem_name = "brainiac-#{name}"
|
|
2202
|
+
|
|
2203
|
+
if local_path
|
|
2204
|
+
# Local path mode — validate the directory and register without gem install
|
|
2205
|
+
unless Dir.exist?(local_path)
|
|
2206
|
+
puts "Error: Path does not exist: #{local_path}"
|
|
2207
|
+
exit 1
|
|
2208
|
+
end
|
|
2209
|
+
|
|
2210
|
+
entry_file = File.join(local_path, "lib", "brainiac-#{name}.rb")
|
|
2211
|
+
unless File.exist?(entry_file)
|
|
2212
|
+
puts "Error: Expected entry file not found: #{entry_file}"
|
|
2213
|
+
puts " Plugin gems must have lib/brainiac-<name>.rb"
|
|
2214
|
+
exit 1
|
|
2215
|
+
end
|
|
2216
|
+
|
|
2217
|
+
puts "Registering #{gem_name} from local path: #{local_path}"
|
|
2218
|
+
|
|
2219
|
+
plugins_config["plugins"] ||= []
|
|
2220
|
+
entry = { "name" => name, "gem" => gem_name, "path" => local_path, "installed_at" => Time.now.iso8601 }
|
|
2221
|
+
plugins_config["plugins"] << entry
|
|
2222
|
+
|
|
2223
|
+
ensure_brainiac_dir
|
|
2224
|
+
File.write(plugins_file, JSON.pretty_generate(plugins_config))
|
|
2225
|
+
|
|
2226
|
+
update_handler_config(name, true)
|
|
2227
|
+
|
|
2228
|
+
puts ""
|
|
2229
|
+
puts "✓ Registered plugin '#{name}' from #{local_path}"
|
|
2230
|
+
puts " Restart the server to activate: brainiac restart"
|
|
2231
|
+
puts " Note: Source changes take effect on next server restart."
|
|
2232
|
+
else
|
|
2233
|
+
# RubyGems mode
|
|
2234
|
+
puts "Installing #{gem_name}..."
|
|
2235
|
+
|
|
2236
|
+
install_cmd = "gem install #{gem_name}"
|
|
2237
|
+
install_cmd += " --version '#{version}'" if version
|
|
2238
|
+
|
|
2239
|
+
system(install_cmd)
|
|
2240
|
+
unless $CHILD_STATUS.success?
|
|
2241
|
+
puts ""
|
|
2242
|
+
puts "Failed to install #{gem_name}."
|
|
2243
|
+
puts " Check that the gem exists: https://rubygems.org/gems/#{gem_name}"
|
|
2244
|
+
exit 1
|
|
2245
|
+
end
|
|
2246
|
+
|
|
2247
|
+
# Register in plugins.json
|
|
2248
|
+
plugins_config["plugins"] ||= []
|
|
2249
|
+
entry = { "name" => name, "gem" => gem_name, "installed_at" => Time.now.iso8601 }
|
|
2250
|
+
entry["version"] = version if version
|
|
2251
|
+
plugins_config["plugins"] << entry
|
|
2252
|
+
|
|
2253
|
+
ensure_brainiac_dir
|
|
2254
|
+
File.write(plugins_file, JSON.pretty_generate(plugins_config))
|
|
2255
|
+
|
|
2256
|
+
# Enable the handler
|
|
2257
|
+
update_handler_config(name, true)
|
|
2258
|
+
|
|
2259
|
+
puts ""
|
|
2260
|
+
puts "✓ Installed plugin '#{name}' (#{gem_name})"
|
|
2261
|
+
puts " Restart the server to activate: brainiac restart"
|
|
2262
|
+
end
|
|
2263
|
+
|
|
2264
|
+
when "uninstall"
|
|
2265
|
+
name = ARGV.shift
|
|
2266
|
+
unless name
|
|
2267
|
+
puts "Usage: brainiac uninstall <plugin-name>"
|
|
2268
|
+
puts ""
|
|
2269
|
+
puts "Removes a Brainiac plugin from the configuration."
|
|
2270
|
+
puts "The gem itself remains installed (use 'gem uninstall brainiac-<name>' to fully remove)."
|
|
2271
|
+
exit 1
|
|
2272
|
+
end
|
|
2273
|
+
|
|
2274
|
+
name = name.sub(/^brainiac-/, "")
|
|
2275
|
+
gem_name = "brainiac-#{name}"
|
|
2276
|
+
|
|
2277
|
+
plugins_file = File.join(BRAINIAC_DIR, "plugins.json")
|
|
2278
|
+
unless File.exist?(plugins_file)
|
|
2279
|
+
puts "Plugin '#{name}' is not installed (no plugins.json found)."
|
|
2280
|
+
exit 1
|
|
2281
|
+
end
|
|
2282
|
+
|
|
2283
|
+
plugins_config = JSON.parse(File.read(plugins_file))
|
|
2284
|
+
found = (plugins_config["plugins"] || []).any? { |p| (p.is_a?(Hash) ? p["name"] : p.to_s) == name }
|
|
2285
|
+
|
|
2286
|
+
unless found
|
|
2287
|
+
puts "Plugin '#{name}' is not installed."
|
|
2288
|
+
exit 1
|
|
2289
|
+
end
|
|
2290
|
+
|
|
2291
|
+
plugins_config["plugins"].reject! { |p| (p.is_a?(Hash) ? p["name"] : p.to_s) == name }
|
|
2292
|
+
File.write(plugins_file, JSON.pretty_generate(plugins_config))
|
|
2293
|
+
|
|
2294
|
+
# Disable the handler
|
|
2295
|
+
update_handler_config(name, false)
|
|
2296
|
+
|
|
2297
|
+
puts "✓ Removed plugin '#{name}' from Brainiac."
|
|
2298
|
+
puts " The gem #{gem_name} is still installed system-wide."
|
|
2299
|
+
puts " To fully remove: gem uninstall #{gem_name}"
|
|
2300
|
+
puts " Restart the server to apply: brainiac restart"
|
|
2301
|
+
|
|
2302
|
+
when "plugins"
|
|
2303
|
+
plugins_cmd = ARGV.shift
|
|
2304
|
+
|
|
2305
|
+
case plugins_cmd
|
|
2306
|
+
when "list", "ls", nil
|
|
2307
|
+
plugins_file = File.join(BRAINIAC_DIR, "plugins.json")
|
|
2308
|
+
plugins_config = if File.exist?(plugins_file)
|
|
2309
|
+
JSON.parse(File.read(plugins_file))
|
|
2310
|
+
else
|
|
2311
|
+
{ "plugins" => [] }
|
|
2312
|
+
end
|
|
2313
|
+
|
|
2314
|
+
plugins = plugins_config["plugins"] || []
|
|
2315
|
+
if plugins.empty?
|
|
2316
|
+
puts "No plugins installed."
|
|
2317
|
+
puts ""
|
|
2318
|
+
puts "Install plugins with: brainiac install <name>"
|
|
2319
|
+
puts " Example: brainiac install whatsapp"
|
|
2320
|
+
puts " Example: brainiac install fizzy --path ~/Code/brainiac-fizzy"
|
|
2321
|
+
else
|
|
2322
|
+
puts "Installed plugins:"
|
|
2323
|
+
plugins.each do |p|
|
|
2324
|
+
entry = p.is_a?(Hash) ? p : { "name" => p.to_s }
|
|
2325
|
+
name = entry["name"]
|
|
2326
|
+
gem_name = entry["gem"] || "brainiac-#{name}"
|
|
2327
|
+
local_path = entry["path"]
|
|
2328
|
+
installed_at = entry["installed_at"] ? " (#{entry["installed_at"][0..9]})" : ""
|
|
2329
|
+
|
|
2330
|
+
if local_path
|
|
2331
|
+
# Local plugin — check path exists
|
|
2332
|
+
if Dir.exist?(local_path)
|
|
2333
|
+
puts " ✓ #{name} → #{local_path}#{installed_at}"
|
|
2334
|
+
else
|
|
2335
|
+
puts " ✗ #{name} → #{local_path} (path missing)#{installed_at}"
|
|
2336
|
+
end
|
|
2337
|
+
else
|
|
2338
|
+
# Gem plugin — check gem is loadable
|
|
2339
|
+
version = entry["version"] || "latest"
|
|
2340
|
+
loadable = begin
|
|
2341
|
+
spec = Gem::Specification.find_by_name(gem_name)
|
|
2342
|
+
"✓ #{name} (#{gem_name} #{spec.version})"
|
|
2343
|
+
rescue Gem::MissingSpecError
|
|
2344
|
+
"✗ #{name} (#{gem_name} — gem not found)"
|
|
2345
|
+
end
|
|
2346
|
+
puts " #{loadable}#{installed_at}"
|
|
2347
|
+
end
|
|
2348
|
+
end
|
|
2349
|
+
end
|
|
2350
|
+
else
|
|
2351
|
+
puts "Usage: brainiac plugins [list]"
|
|
2352
|
+
puts ""
|
|
2353
|
+
puts "Managing plugins:"
|
|
2354
|
+
puts " brainiac install <name> Install a plugin"
|
|
2355
|
+
puts " brainiac uninstall <name> Remove a plugin"
|
|
2356
|
+
puts " brainiac plugins List installed plugins"
|
|
2357
|
+
end
|
|
2358
|
+
|
|
1939
2359
|
when "version", "--version", "-v"
|
|
1940
2360
|
puts "brainiac #{BRAINIAC_VERSION}"
|
|
1941
2361
|
|
|
@@ -1956,6 +2376,9 @@ when "help", "--help", "-h", nil
|
|
|
1956
2376
|
brainiac projects list List all registered projects (alias)
|
|
1957
2377
|
brainiac projects default <key> Set the default project
|
|
1958
2378
|
brainiac show <key> Show project configuration
|
|
2379
|
+
brainiac install <plugin> Install a Brainiac plugin (gem-based handler)
|
|
2380
|
+
brainiac uninstall <plugin> Remove an installed plugin
|
|
2381
|
+
brainiac plugins List installed plugins
|
|
1959
2382
|
brainiac brain <command> Manage agent long-term memory (brain)
|
|
1960
2383
|
brainiac discord <command> Manage the Discord bot
|
|
1961
2384
|
brainiac cron <command> Manage scheduled agent tasks
|
|
@@ -1970,6 +2393,11 @@ when "help", "--help", "-h", nil
|
|
|
1970
2393
|
Server Options:
|
|
1971
2394
|
-d, --daemon Run server in background (detached)
|
|
1972
2395
|
|
|
2396
|
+
Plugin Commands:
|
|
2397
|
+
install <name> Install a plugin (gem install brainiac-<name>)
|
|
2398
|
+
uninstall <name> Remove a plugin
|
|
2399
|
+
plugins List installed plugins
|
|
2400
|
+
|
|
1973
2401
|
Card Map Commands:
|
|
1974
2402
|
card-map list Show all card map entries
|
|
1975
2403
|
card-map clean Remove stale entries (missing worktrees)
|