brainiac 0.0.7 → 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/bin/brainiac +132 -75
- data/lib/brainiac/agents.rb +12 -36
- data/lib/brainiac/brain.rb +6 -6
- data/lib/brainiac/config.rb +2 -67
- data/lib/brainiac/handlers/discord/config.rb +1 -1
- data/lib/brainiac/handlers/discord/delivery.rb +1 -1
- data/lib/brainiac/handlers/discord/gateway.rb +1 -1
- data/lib/brainiac/handlers/discord/message.rb +10 -10
- data/lib/brainiac/handlers/discord/reactions.rb +1 -1
- data/lib/brainiac/handlers/github.rb +49 -169
- data/lib/brainiac/handlers/shared/git.rb +4 -17
- data/lib/brainiac/handlers/shared/inline_tags.rb +1 -1
- data/lib/brainiac/handlers/zoho.rb +29 -82
- data/lib/brainiac/helpers.rb +43 -336
- data/lib/brainiac/hooks.rb +86 -0
- data/lib/brainiac/plugins.rb +27 -2
- data/lib/brainiac/prompts.rb +34 -172
- data/lib/brainiac/restart.rb +30 -12
- data/lib/brainiac/routes/api.rb +2 -18
- data/lib/brainiac/users.rb +1 -7
- data/lib/brainiac/version.rb +1 -1
- data/lib/brainiac.rb +1 -1
- data/monitor/waybar/deploy_env.rb +3 -3
- data/monitor/xbar/plugin.rb +11 -17
- data/receiver.rb +9 -157
- data/templates/agents.json.example +1 -2
- data/templates/brainiac.json.example +0 -1
- data/templates/users.json.example +0 -3
- metadata +2 -10
- data/lib/brainiac/handlers/fizzy/assignment.rb +0 -125
- data/lib/brainiac/handlers/fizzy/card_index.rb +0 -389
- data/lib/brainiac/handlers/fizzy/comments.rb +0 -730
- data/lib/brainiac/handlers/fizzy/dedup.rb +0 -74
- data/lib/brainiac/handlers/fizzy/deploy.rb +0 -152
- data/lib/brainiac/handlers/fizzy/deployments.rb +0 -260
- data/lib/brainiac/handlers/fizzy.rb +0 -14
- data/lib/brainiac/planning.rb +0 -237
- data/templates/fizzy.json.example +0 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e339beb822c90dafae1ad7037263adcaac1915e3c3a41c578d4d70e8253c385a
|
|
4
|
+
data.tar.gz: dca3c1958e07d06be3642b30d220b4711443dcb11b74e6123fc7618539ee860e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18f9f5de76b193a39ea0e5ef82cacfcb6f503681fc0868d02784127285c57ef3f3ebb0157d9d79ebcf9f7e2842d190b44b45f49a3e986d16804479f08720ec67
|
|
7
|
+
data.tar.gz: 7d511730180afa46955140cac32122d35b0a93bf2df9e94c5d1ac6c60ad499a87c2baba819bdac509f75dde87fd51fa5f8ac8cdf74dbc44917f2aa344c65c9e1
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
brainiac (0.0.
|
|
4
|
+
brainiac (0.0.8)
|
|
5
5
|
puma (~> 7.2)
|
|
6
6
|
rackup (~> 2.3)
|
|
7
7
|
sinatra (~> 4.1)
|
|
@@ -90,7 +90,7 @@ DEPENDENCIES
|
|
|
90
90
|
CHECKSUMS
|
|
91
91
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
92
92
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
93
|
-
brainiac (0.0.
|
|
93
|
+
brainiac (0.0.8)
|
|
94
94
|
event_emitter (0.2.6) sha256=c72697bd5cce9d36594be1972c17f1c9a573236f44303a4d1d548080364e1391
|
|
95
95
|
json (2.19.9) sha256=9b9025b7cdddafa38d316eca0b2358488e42d417045c1b90d216a9fefe46b79a
|
|
96
96
|
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
data/bin/brainiac
CHANGED
|
@@ -327,9 +327,9 @@ def register_project(options)
|
|
|
327
327
|
exit 1
|
|
328
328
|
end
|
|
329
329
|
|
|
330
|
-
print "
|
|
330
|
+
print "Tags (comma-separated) [#{project_key}]: "
|
|
331
331
|
tags_input = $stdin.gets.chomp
|
|
332
|
-
|
|
332
|
+
project_tags = tags_input.empty? ? [project_key] : tags_input.split(",").map(&:strip)
|
|
333
333
|
|
|
334
334
|
print "GitHub repo [#{info["github_repo"]}]: "
|
|
335
335
|
github_repo = $stdin.gets.chomp
|
|
@@ -360,7 +360,7 @@ def register_project(options)
|
|
|
360
360
|
# Build project config
|
|
361
361
|
project_config = {
|
|
362
362
|
"repo_path" => info["repo_path"],
|
|
363
|
-
"
|
|
363
|
+
"tags" => project_tags,
|
|
364
364
|
"github_repo" => github_repo,
|
|
365
365
|
"agent_name" => agent_name,
|
|
366
366
|
"agent_model" => agent_model,
|
|
@@ -373,7 +373,7 @@ def register_project(options)
|
|
|
373
373
|
puts
|
|
374
374
|
puts "✓ Project '#{project_key}' registered successfully!"
|
|
375
375
|
puts " Repo: #{info["repo_path"]}"
|
|
376
|
-
puts " Tags: #{
|
|
376
|
+
puts " Tags: #{project_tags.join(", ")}"
|
|
377
377
|
puts " GitHub: #{github_repo}" if github_repo
|
|
378
378
|
end
|
|
379
379
|
|
|
@@ -407,7 +407,7 @@ def list_projects
|
|
|
407
407
|
default_marker = config["default"] ? " (default)" : ""
|
|
408
408
|
puts " #{key}#{default_marker}"
|
|
409
409
|
puts " Path: #{config["repo_path"]}"
|
|
410
|
-
puts " Tags: #{config["
|
|
410
|
+
puts " Tags: #{config["tags"] || config["project_tags"].join(", ")}"
|
|
411
411
|
puts " GitHub: #{config["github_repo"]}" if config["github_repo"]
|
|
412
412
|
puts " Agent: #{config["agent_cli"]} (model: #{config["agent_model"]})"
|
|
413
413
|
puts
|
|
@@ -675,7 +675,7 @@ when "setup"
|
|
|
675
675
|
puts ""
|
|
676
676
|
|
|
677
677
|
available_handlers = {
|
|
678
|
-
|
|
678
|
+
|
|
679
679
|
"github" => "GitHub (PR reviews, CI failure handling, deploy tracking)",
|
|
680
680
|
"discord" => "Discord (conversational agent access via bot)",
|
|
681
681
|
"zoho" => "Zoho Mail (email notifications and triage)"
|
|
@@ -785,7 +785,7 @@ when "handler", "handlers"
|
|
|
785
785
|
handlers = config["handlers"] || {}
|
|
786
786
|
|
|
787
787
|
# Built-in handlers
|
|
788
|
-
builtins = %w[
|
|
788
|
+
builtins = %w[github discord zoho]
|
|
789
789
|
puts "Built-in handlers:"
|
|
790
790
|
builtins.each do |name|
|
|
791
791
|
enabled = handlers.fetch(name, true)
|
|
@@ -999,7 +999,7 @@ when "discord"
|
|
|
999
999
|
else
|
|
1000
1000
|
puts "Agents with Discord bots:"
|
|
1001
1001
|
agents_with_tokens.each do |key, entry|
|
|
1002
|
-
display = entry["
|
|
1002
|
+
display = entry["display_name"] || key.capitalize
|
|
1003
1003
|
token = entry.dig("env", "DISCORD_BOT_TOKEN") || entry["discord_bot_token"]
|
|
1004
1004
|
token_preview = "#{token[0..10]}..."
|
|
1005
1005
|
puts " #{display} (#{key}): #{token_preview}"
|
|
@@ -1055,31 +1055,31 @@ when "discord"
|
|
|
1055
1055
|
HELP
|
|
1056
1056
|
end
|
|
1057
1057
|
|
|
1058
|
-
when "card-map"
|
|
1059
|
-
|
|
1058
|
+
when "work-items", "card-map"
|
|
1059
|
+
work_items_file = File.join(BRAINIAC_DIR, "work_items.json")
|
|
1060
1060
|
cm_cmd = ARGV.shift
|
|
1061
1061
|
case cm_cmd
|
|
1062
1062
|
when "clean"
|
|
1063
|
-
unless File.exist?(
|
|
1064
|
-
puts "No
|
|
1063
|
+
unless File.exist?(work_items_file)
|
|
1064
|
+
puts "No work items found."
|
|
1065
1065
|
exit 0
|
|
1066
1066
|
end
|
|
1067
|
-
map = JSON.parse(File.read(
|
|
1067
|
+
map = JSON.parse(File.read(work_items_file))
|
|
1068
1068
|
before = map.size
|
|
1069
1069
|
# Remove entries with no valid worktree on disk
|
|
1070
1070
|
map.reject! { |_id, info| info["worktree"].nil? || !File.directory?(info["worktree"].to_s) }
|
|
1071
1071
|
after = map.size
|
|
1072
1072
|
removed = before - after
|
|
1073
|
-
File.write(
|
|
1074
|
-
puts "Cleaned
|
|
1073
|
+
File.write(work_items_file, JSON.pretty_generate(map))
|
|
1074
|
+
puts "Cleaned work items: removed #{removed} stale entries (#{before} → #{after})"
|
|
1075
1075
|
when "list", "ls", nil
|
|
1076
|
-
unless File.exist?(
|
|
1077
|
-
puts "No
|
|
1076
|
+
unless File.exist?(work_items_file)
|
|
1077
|
+
puts "No work items found."
|
|
1078
1078
|
exit 0
|
|
1079
1079
|
end
|
|
1080
|
-
map = JSON.parse(File.read(
|
|
1080
|
+
map = JSON.parse(File.read(work_items_file))
|
|
1081
1081
|
if map.empty?
|
|
1082
|
-
puts "
|
|
1082
|
+
puts "No active work items."
|
|
1083
1083
|
else
|
|
1084
1084
|
map.each_value do |info|
|
|
1085
1085
|
num = info["number"] ? "##{info["number"]}" : "(no number)"
|
|
@@ -1089,7 +1089,7 @@ when "card-map"
|
|
|
1089
1089
|
end
|
|
1090
1090
|
end
|
|
1091
1091
|
else
|
|
1092
|
-
puts "Usage: brainiac
|
|
1092
|
+
puts "Usage: brainiac work-items [clean|list]"
|
|
1093
1093
|
puts " clean Remove stale entries (missing worktrees)"
|
|
1094
1094
|
puts " list Show all card map entries"
|
|
1095
1095
|
end
|
|
@@ -1570,7 +1570,7 @@ when "agent"
|
|
|
1570
1570
|
puts "No agents in registry."
|
|
1571
1571
|
else
|
|
1572
1572
|
agents = registry.select { |_, e| e.is_a?(Hash) }.map do |key, entry|
|
|
1573
|
-
display = entry["
|
|
1573
|
+
display = entry["display_name"] || key.capitalize
|
|
1574
1574
|
details = []
|
|
1575
1575
|
if entry["role"]
|
|
1576
1576
|
roles = Array(entry["role"])
|
|
@@ -1614,8 +1614,8 @@ when "agent"
|
|
|
1614
1614
|
agent_key = name.downcase.gsub(/[^a-z0-9-]/, "-")
|
|
1615
1615
|
|
|
1616
1616
|
# Resolve display name from registry if it exists
|
|
1617
|
-
display_name = if registry.dig(agent_key, "
|
|
1618
|
-
registry[agent_key]["
|
|
1617
|
+
display_name = if registry.dig(agent_key, "display_name")
|
|
1618
|
+
registry[agent_key]["display_name"]
|
|
1619
1619
|
else
|
|
1620
1620
|
name
|
|
1621
1621
|
end
|
|
@@ -1725,7 +1725,7 @@ when "agent"
|
|
|
1725
1725
|
persona_text = input.empty? ? nil : input
|
|
1726
1726
|
end
|
|
1727
1727
|
|
|
1728
|
-
entry = { "
|
|
1728
|
+
entry = { "display_name" => name }
|
|
1729
1729
|
entry["local"] = true if local
|
|
1730
1730
|
entry["cli_provider"] = cli_provider if cli_provider
|
|
1731
1731
|
entry["role"] = roles.size == 1 ? roles.first : roles if roles.any?
|
|
@@ -1776,7 +1776,7 @@ when "agent"
|
|
|
1776
1776
|
exit 1
|
|
1777
1777
|
end
|
|
1778
1778
|
|
|
1779
|
-
display = registry[agent_key]["
|
|
1779
|
+
display = registry[agent_key]["display_name"] || name
|
|
1780
1780
|
registry.delete(agent_key)
|
|
1781
1781
|
File.write(agent_registry_file, JSON.pretty_generate(registry))
|
|
1782
1782
|
puts "✓ Removed agent '#{display}' (#{agent_key}) from registry"
|
|
@@ -1796,7 +1796,7 @@ when "agent"
|
|
|
1796
1796
|
create <name> [options] Add a new agent to the registry
|
|
1797
1797
|
remove <name> Remove an agent from the registry
|
|
1798
1798
|
<name> show Show agent configuration (tokens redacted)
|
|
1799
|
-
<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)
|
|
1800
1800
|
<name> env <KEY> <VALUE> Set an env var for an agent
|
|
1801
1801
|
<name> env List env vars for an agent
|
|
1802
1802
|
<name> env --delete <KEY> Remove an env var from an agent
|
|
@@ -1817,7 +1817,7 @@ when "agent"
|
|
|
1817
1817
|
brainiac agent galen set local true
|
|
1818
1818
|
brainiac agent galen set persona "Dry, sardonic"
|
|
1819
1819
|
brainiac agent galen set role code-reviewer,general-engineer
|
|
1820
|
-
brainiac agent galen env
|
|
1820
|
+
brainiac agent galen env MY_TOKEN abc123
|
|
1821
1821
|
brainiac agent galen show
|
|
1822
1822
|
HELP
|
|
1823
1823
|
|
|
@@ -1842,11 +1842,11 @@ when "agent"
|
|
|
1842
1842
|
puts JSON.pretty_generate(display)
|
|
1843
1843
|
|
|
1844
1844
|
when "env"
|
|
1845
|
-
if
|
|
1845
|
+
if ["--delete", "unset", "delete", "rm"].include?(ARGV[0])
|
|
1846
1846
|
ARGV.shift
|
|
1847
1847
|
var_name = ARGV.shift
|
|
1848
1848
|
unless var_name
|
|
1849
|
-
puts "Usage: brainiac agent #{agent_cmd} env
|
|
1849
|
+
puts "Usage: brainiac agent #{agent_cmd} env unset <KEY>"
|
|
1850
1850
|
exit 1
|
|
1851
1851
|
end
|
|
1852
1852
|
registry = File.exist?(agent_registry_file) ? JSON.parse(File.read(agent_registry_file)) : {}
|
|
@@ -1898,7 +1898,7 @@ when "agent"
|
|
|
1898
1898
|
puts " cli <provider> Set CLI provider"
|
|
1899
1899
|
puts " role <roles> Set roles (comma-separated, or 'none' to clear)"
|
|
1900
1900
|
puts " persona <text> Set/update persona style file"
|
|
1901
|
-
puts "
|
|
1901
|
+
puts " display_name <name> Set display name for @mentions"
|
|
1902
1902
|
exit 1
|
|
1903
1903
|
end
|
|
1904
1904
|
|
|
@@ -1946,7 +1946,7 @@ when "agent"
|
|
|
1946
1946
|
persona_dir = File.join(BRAINIAC_DIR, "brain", "persona", agent_key)
|
|
1947
1947
|
FileUtils.mkdir_p(persona_dir)
|
|
1948
1948
|
persona_file = File.join(persona_dir, "style.md")
|
|
1949
|
-
display = registry[agent_key]["
|
|
1949
|
+
display = registry[agent_key]["display_name"] || agent_cmd.capitalize
|
|
1950
1950
|
File.write(persona_file, <<~MD)
|
|
1951
1951
|
---
|
|
1952
1952
|
name: #{agent_key}-style
|
|
@@ -1957,17 +1957,17 @@ when "agent"
|
|
|
1957
1957
|
MD
|
|
1958
1958
|
puts "✓ Wrote persona to #{persona_file}"
|
|
1959
1959
|
|
|
1960
|
-
when "
|
|
1960
|
+
when "display_name"
|
|
1961
1961
|
unless value
|
|
1962
|
-
puts "Usage: brainiac agent #{agent_cmd} set
|
|
1962
|
+
puts "Usage: brainiac agent #{agent_cmd} set display_name <name>"
|
|
1963
1963
|
exit 1
|
|
1964
1964
|
end
|
|
1965
|
-
registry[agent_key]["
|
|
1965
|
+
registry[agent_key]["display_name"] = value
|
|
1966
1966
|
File.write(agent_registry_file, JSON.pretty_generate(registry))
|
|
1967
|
-
puts "✓ Set
|
|
1967
|
+
puts "✓ Set display_name=#{value} for #{agent_cmd}"
|
|
1968
1968
|
|
|
1969
1969
|
else
|
|
1970
|
-
puts "Unknown field '#{field}'. Available: local, cli, role, persona,
|
|
1970
|
+
puts "Unknown field '#{field}'. Available: local, cli, role, persona, display_name"
|
|
1971
1971
|
exit 1
|
|
1972
1972
|
end
|
|
1973
1973
|
|
|
@@ -2002,7 +2002,7 @@ when "role"
|
|
|
2002
2002
|
roles = entry["role"].is_a?(Array) ? entry["role"] : [entry["role"]]
|
|
2003
2003
|
roles.each do |r|
|
|
2004
2004
|
agent_roles[r] ||= []
|
|
2005
|
-
agent_roles[r] << (entry["
|
|
2005
|
+
agent_roles[r] << (entry["display_name"] || key.capitalize)
|
|
2006
2006
|
end
|
|
2007
2007
|
end
|
|
2008
2008
|
|
|
@@ -2148,13 +2148,18 @@ when "completions"
|
|
|
2148
2148
|
when "install"
|
|
2149
2149
|
name = ARGV.shift
|
|
2150
2150
|
unless name
|
|
2151
|
-
puts "Usage: brainiac install <plugin-name>"
|
|
2151
|
+
puts "Usage: brainiac install <plugin-name> [options]"
|
|
2152
2152
|
puts ""
|
|
2153
2153
|
puts "Installs a Brainiac plugin gem (brainiac-<name>) and registers it."
|
|
2154
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 ""
|
|
2155
2159
|
puts "Examples:"
|
|
2156
|
-
puts " brainiac install whatsapp
|
|
2157
|
-
puts " brainiac install
|
|
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"
|
|
2158
2163
|
puts ""
|
|
2159
2164
|
puts "After installing, restart the server: brainiac restart"
|
|
2160
2165
|
exit 1
|
|
@@ -2163,11 +2168,20 @@ when "install"
|
|
|
2163
2168
|
# Strip "brainiac-" prefix if user includes it
|
|
2164
2169
|
name = name.sub(/^brainiac-/, "")
|
|
2165
2170
|
version = nil
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
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
|
|
2171
2185
|
end
|
|
2172
2186
|
|
|
2173
2187
|
plugins_file = File.join(BRAINIAC_DIR, "plugins.json")
|
|
@@ -2185,34 +2199,67 @@ when "install"
|
|
|
2185
2199
|
end
|
|
2186
2200
|
|
|
2187
2201
|
gem_name = "brainiac-#{name}"
|
|
2188
|
-
puts "Installing #{gem_name}..."
|
|
2189
2202
|
|
|
2190
|
-
|
|
2191
|
-
|
|
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)
|
|
2192
2227
|
|
|
2193
|
-
system(install_cmd)
|
|
2194
|
-
unless $CHILD_STATUS.success?
|
|
2195
2228
|
puts ""
|
|
2196
|
-
puts "
|
|
2197
|
-
puts "
|
|
2198
|
-
|
|
2199
|
-
|
|
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}..."
|
|
2200
2235
|
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
entry = { "name" => name, "gem" => gem_name, "installed_at" => Time.now.iso8601 }
|
|
2204
|
-
entry["version"] = version if version
|
|
2205
|
-
plugins_config["plugins"] << entry
|
|
2236
|
+
install_cmd = "gem install #{gem_name}"
|
|
2237
|
+
install_cmd += " --version '#{version}'" if version
|
|
2206
2238
|
|
|
2207
|
-
|
|
2208
|
-
|
|
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
|
|
2209
2246
|
|
|
2210
|
-
|
|
2211
|
-
|
|
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
|
|
2212
2252
|
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
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
|
|
2216
2263
|
|
|
2217
2264
|
when "uninstall"
|
|
2218
2265
|
name = ARGV.shift
|
|
@@ -2270,24 +2317,34 @@ when "plugins"
|
|
|
2270
2317
|
puts ""
|
|
2271
2318
|
puts "Install plugins with: brainiac install <name>"
|
|
2272
2319
|
puts " Example: brainiac install whatsapp"
|
|
2320
|
+
puts " Example: brainiac install fizzy --path ~/Code/brainiac-fizzy"
|
|
2273
2321
|
else
|
|
2274
2322
|
puts "Installed plugins:"
|
|
2275
2323
|
plugins.each do |p|
|
|
2276
2324
|
entry = p.is_a?(Hash) ? p : { "name" => p.to_s }
|
|
2277
2325
|
name = entry["name"]
|
|
2278
2326
|
gem_name = entry["gem"] || "brainiac-#{name}"
|
|
2279
|
-
|
|
2327
|
+
local_path = entry["path"]
|
|
2280
2328
|
installed_at = entry["installed_at"] ? " (#{entry["installed_at"][0..9]})" : ""
|
|
2281
2329
|
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
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}"
|
|
2288
2347
|
end
|
|
2289
|
-
|
|
2290
|
-
puts " #{loadable} #{name} (#{gem_name} #{version})#{installed_at}"
|
|
2291
2348
|
end
|
|
2292
2349
|
end
|
|
2293
2350
|
else
|
data/lib/brainiac/agents.rb
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
#
|
|
8
8
|
# {
|
|
9
9
|
# "galen": {
|
|
10
|
-
# "
|
|
10
|
+
# "display_name": "Galen",
|
|
11
11
|
# "local": true,
|
|
12
12
|
# "env": {
|
|
13
|
-
# "
|
|
13
|
+
# "SOME_TOKEN": "token_abc...",
|
|
14
14
|
# "DISCORD_BOT_TOKEN": "Bot_abc..."
|
|
15
15
|
# }
|
|
16
16
|
# }
|
|
@@ -70,30 +70,17 @@ def agent_env_var(agent_name, var_name)
|
|
|
70
70
|
agent_env_for(agent_name)[var_name]
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
#
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# Convenience: build env hash for fizzy CLI calls (backward compat).
|
|
79
|
-
# Falls back to default agent token when the given agent has no token.
|
|
80
|
-
def fizzy_env_for(agent_name)
|
|
81
|
-
token = fizzy_token_for(agent_name) || fizzy_token_for(AI_AGENT_NAME)
|
|
82
|
-
token ? { "FIZZY_TOKEN" => token } : {}
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def default_fizzy_env
|
|
86
|
-
fizzy_env_for(AI_AGENT_NAME)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def fizzy_display_name(agent_name)
|
|
73
|
+
# Get the display name for an agent (from agents.json registry).
|
|
74
|
+
# This is the human-readable canonical name (e.g., "GLaDOS" not "glados").
|
|
75
|
+
# Core owns this — it's the identity used across all channels and plugins.
|
|
76
|
+
def agent_display_name(agent_name)
|
|
90
77
|
return agent_name unless agent_name
|
|
91
78
|
|
|
92
79
|
key = agent_name.downcase.gsub(/[^a-z0-9-]/, "-")
|
|
93
80
|
entry = AGENT_REGISTRY[key]
|
|
94
81
|
return agent_name unless entry.is_a?(Hash)
|
|
95
82
|
|
|
96
|
-
entry["
|
|
83
|
+
entry["display_name"] || agent_name
|
|
97
84
|
end
|
|
98
85
|
|
|
99
86
|
# Get the role name(s) configured for an agent in agents.json.
|
|
@@ -132,7 +119,7 @@ end
|
|
|
132
119
|
|
|
133
120
|
def agent_roster
|
|
134
121
|
roster = {}
|
|
135
|
-
all_agent_names.each { |name| roster[name.downcase] =
|
|
122
|
+
all_agent_names.each { |name| roster[name.downcase] = agent_display_name(name) }
|
|
136
123
|
roster
|
|
137
124
|
end
|
|
138
125
|
|
|
@@ -153,9 +140,9 @@ def all_agent_names
|
|
|
153
140
|
names = Set.new([AI_AGENT_NAME])
|
|
154
141
|
PROJECTS.each_value { |config| names << config["agent_name"] if config["agent_name"] }
|
|
155
142
|
discover_kiro_agents.each { |name| names << name.capitalize }
|
|
156
|
-
# Include agents from the registry
|
|
143
|
+
# Include agents from the registry
|
|
157
144
|
AGENT_REGISTRY.each do |key, entry|
|
|
158
|
-
names << (entry["
|
|
145
|
+
names << (entry["display_name"] || key.capitalize)
|
|
159
146
|
end
|
|
160
147
|
names
|
|
161
148
|
end
|
|
@@ -175,7 +162,7 @@ def local_agent_names
|
|
|
175
162
|
AGENT_REGISTRY.each do |key, entry|
|
|
176
163
|
next unless entry.is_a?(Hash) && entry["local"]
|
|
177
164
|
|
|
178
|
-
names << (entry["
|
|
165
|
+
names << (entry["display_name"] || key.capitalize)
|
|
179
166
|
end
|
|
180
167
|
names
|
|
181
168
|
end
|
|
@@ -186,7 +173,7 @@ def detect_mentioned_agent(text)
|
|
|
186
173
|
all_agent_names.each do |name|
|
|
187
174
|
return name if downcased.include?("@#{name.downcase}")
|
|
188
175
|
|
|
189
|
-
#
|
|
176
|
+
# Some systems render mentions using first name only (e.g. "@Sleeper" not "@Sleeper Service").
|
|
190
177
|
# Fall back to matching the first word of multi-word agent names.
|
|
191
178
|
first_word = name.split.first.downcase
|
|
192
179
|
next if first_word == name.downcase # already checked above
|
|
@@ -195,17 +182,6 @@ def detect_mentioned_agent(text)
|
|
|
195
182
|
nil
|
|
196
183
|
end
|
|
197
184
|
|
|
198
|
-
def detect_mentioned_user_ids(text)
|
|
199
|
-
return [] unless FIZZY_CONFIG["authorized_users"]
|
|
200
|
-
|
|
201
|
-
mentioned_ids = []
|
|
202
|
-
FIZZY_CONFIG["authorized_users"].each do |user|
|
|
203
|
-
name = user["name"]
|
|
204
|
-
mentioned_ids << user["id"] if text.downcase.include?("@#{name.downcase}")
|
|
205
|
-
end
|
|
206
|
-
mentioned_ids
|
|
207
|
-
end
|
|
208
|
-
|
|
209
185
|
def comment_from_agent?(name)
|
|
210
186
|
return false unless name
|
|
211
187
|
|
data/lib/brainiac/brain.rb
CHANGED
|
@@ -162,9 +162,6 @@ def build_brain_context(agent_name: AI_AGENT_NAME, card_title: "", card_number:
|
|
|
162
162
|
primary_query = topics.first(5).join(" ")
|
|
163
163
|
primary_query = "project conventions" if primary_query.empty?
|
|
164
164
|
|
|
165
|
-
fizzy_mentioned = [card_title, comment_body].any? { |s| s&.match?(/fizzy/i) }
|
|
166
|
-
fizzy_originated = source == :fizzy
|
|
167
|
-
|
|
168
165
|
search_queries = [primary_query]
|
|
169
166
|
|
|
170
167
|
knowledge_threads = [
|
|
@@ -173,9 +170,12 @@ def build_brain_context(agent_name: AI_AGENT_NAME, card_title: "", card_number:
|
|
|
173
170
|
]
|
|
174
171
|
search_queries << agent_name
|
|
175
172
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
# Plugin hook: source-specific brain queries (e.g., plugins inject source-specific knowledge)
|
|
174
|
+
plugin_queries = Brainiac.emit(:build_brain_context,
|
|
175
|
+
source: source, card_title: card_title, comment_body: comment_body)
|
|
176
|
+
plugin_queries.flatten.compact.each do |query|
|
|
177
|
+
knowledge_threads << Thread.new { query_brain(query, scope: :knowledge, max_results: 2) }
|
|
178
|
+
search_queries << query
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
persona_thread = Thread.new { query_brain("personality tone voice communication style", agent_name: agent_name, scope: :persona, max_results: 5) }
|