brainiac 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +2 -2
  3. data/bin/brainiac +132 -75
  4. data/lib/brainiac/agents.rb +12 -36
  5. data/lib/brainiac/brain.rb +6 -6
  6. data/lib/brainiac/config.rb +2 -67
  7. data/lib/brainiac/handlers/discord/config.rb +1 -1
  8. data/lib/brainiac/handlers/discord/delivery.rb +1 -1
  9. data/lib/brainiac/handlers/discord/gateway.rb +1 -1
  10. data/lib/brainiac/handlers/discord/message.rb +13 -25
  11. data/lib/brainiac/handlers/discord/reactions.rb +1 -1
  12. data/lib/brainiac/handlers/github.rb +49 -169
  13. data/lib/brainiac/handlers/shared/git.rb +4 -17
  14. data/lib/brainiac/handlers/shared/inline_tags.rb +1 -1
  15. data/lib/brainiac/handlers/zoho.rb +29 -82
  16. data/lib/brainiac/helpers.rb +43 -336
  17. data/lib/brainiac/hooks.rb +86 -0
  18. data/lib/brainiac/plugins.rb +27 -2
  19. data/lib/brainiac/prompts.rb +34 -172
  20. data/lib/brainiac/restart.rb +30 -12
  21. data/lib/brainiac/routes/api.rb +2 -18
  22. data/lib/brainiac/users.rb +1 -7
  23. data/lib/brainiac/version.rb +1 -1
  24. data/lib/brainiac.rb +1 -1
  25. data/monitor/waybar/deploy_env.rb +3 -3
  26. data/monitor/xbar/plugin.rb +11 -17
  27. data/receiver.rb +9 -157
  28. data/templates/agents.json.example +1 -2
  29. data/templates/brainiac.json.example +0 -1
  30. data/templates/users.json.example +0 -3
  31. metadata +2 -10
  32. data/lib/brainiac/handlers/fizzy/assignment.rb +0 -125
  33. data/lib/brainiac/handlers/fizzy/card_index.rb +0 -389
  34. data/lib/brainiac/handlers/fizzy/comments.rb +0 -730
  35. data/lib/brainiac/handlers/fizzy/dedup.rb +0 -74
  36. data/lib/brainiac/handlers/fizzy/deploy.rb +0 -152
  37. data/lib/brainiac/handlers/fizzy/deployments.rb +0 -260
  38. data/lib/brainiac/handlers/fizzy.rb +0 -14
  39. data/lib/brainiac/planning.rb +0 -237
  40. data/templates/fizzy.json.example +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fa0fbfcf61f48755a21588273779a67326f98b7be00eaa187573ec35366f80e
4
- data.tar.gz: 8cb76479c7fe1ddd5961600a11cf5d9c63b8e7b31dddb4fed24584836e5772fa
3
+ metadata.gz: ccb3d560afc2531dc6ba8e5f7b8fdf16cce8ee8e7a9d0ffe662f4f1c25c21a60
4
+ data.tar.gz: a4c644e18a11ece4aa474b92d7e9335a7331989942f46f8c1c6611927b49461f
5
5
  SHA512:
6
- metadata.gz: 125e924e7e697358aee58aca6b81893e9c418cce718e35b4236a6d45c44c0b2899ccbefb978e279819e3355aaca3598794cdc89025ebce1f07dd9dba60f706e9
7
- data.tar.gz: d6f2c642bf78db0de88e36f7dcf7d3d6194f4f66a453d5219940ef259d942cbb03a723ad13db97d6bff751124596602a1a47e29dc5352f3b88349e990ab27dc3
6
+ metadata.gz: b1ff06751f2335f44f151f3405800e6060eda047ae40a92470cc03c29829f4966b5826a6acd010a5b5db0f8e0f432be88af7f9c323779ee5dd9dd954e1ec8548
7
+ data.tar.gz: 0b21425f705084aad4b7a09f2153915f389f608244e77c7797e65b101870b68d8e83ca830a2629fa432d6fb8c475cd97f15f08bbae85bf80412d3cddb193c625
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brainiac (0.0.7)
4
+ brainiac (0.0.9)
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.7)
93
+ brainiac (0.0.9)
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 "Fizzy tags (comma-separated) [#{project_key}]: "
330
+ print "Tags (comma-separated) [#{project_key}]: "
331
331
  tags_input = $stdin.gets.chomp
332
- fizzy_tags = tags_input.empty? ? [project_key] : tags_input.split(",").map(&:strip)
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
- "fizzy_tags" => fizzy_tags,
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: #{fizzy_tags.join(", ")}"
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["fizzy_tags"].join(", ")}"
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
- "fizzy" => "Fizzy (card management, agent dispatch via webhooks)",
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[fizzy github discord zoho]
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["fizzy_name"] || key.capitalize
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
- card_map_file = File.join(BRAINIAC_DIR, "card_map.json")
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?(card_map_file)
1064
- puts "No card_map.json found."
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(card_map_file))
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(card_map_file, JSON.pretty_generate(map))
1074
- puts "Cleaned card map: removed #{removed} stale entries (#{before} → #{after})"
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?(card_map_file)
1077
- puts "No card_map.json found."
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(card_map_file))
1080
+ map = JSON.parse(File.read(work_items_file))
1081
1081
  if map.empty?
1082
- puts "Card map is empty."
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 card-map [clean|list]"
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["fizzy_name"] || key.capitalize
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, "fizzy_name")
1618
- registry[agent_key]["fizzy_name"]
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 = { "fizzy_name" => name }
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]["fizzy_name"] || name
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, fizzy_name)
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 FIZZY_TOKEN fizzy_abc123
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 ARGV[0] == "--delete"
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 --delete <KEY>"
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 " fizzy_name <name> Set display name for Fizzy @mentions"
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]["fizzy_name"] || agent_cmd.capitalize
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 "fizzy_name"
1960
+ when "display_name"
1961
1961
  unless value
1962
- puts "Usage: brainiac agent #{agent_cmd} set fizzy_name <name>"
1962
+ puts "Usage: brainiac agent #{agent_cmd} set display_name <name>"
1963
1963
  exit 1
1964
1964
  end
1965
- registry[agent_key]["fizzy_name"] = value
1965
+ registry[agent_key]["display_name"] = value
1966
1966
  File.write(agent_registry_file, JSON.pretty_generate(registry))
1967
- puts "✓ Set fizzy_name=#{value} for #{agent_cmd}"
1967
+ puts "✓ Set display_name=#{value} for #{agent_cmd}"
1968
1968
 
1969
1969
  else
1970
- puts "Unknown field '#{field}'. Available: local, cli, role, persona, fizzy_name"
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["fizzy_name"] || key.capitalize)
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 # installs brainiac-whatsapp gem"
2157
- puts " brainiac install slack # installs brainiac-slack gem"
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
- # Check for --version flag
2168
- if ["--version", "-v"].include?(ARGV[0])
2169
- ARGV.shift
2170
- version = ARGV.shift
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
- install_cmd = "gem install #{gem_name}"
2191
- install_cmd += " --version '#{version}'" if version
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 "Failed to install #{gem_name}."
2197
- puts " Check that the gem exists: https://rubygems.org/gems/#{gem_name}"
2198
- exit 1
2199
- end
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
- # 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
2236
+ install_cmd = "gem install #{gem_name}"
2237
+ install_cmd += " --version '#{version}'" if version
2206
2238
 
2207
- ensure_brainiac_dir
2208
- File.write(plugins_file, JSON.pretty_generate(plugins_config))
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
- # Enable the handler
2211
- update_handler_config(name, true)
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
- puts ""
2214
- puts "✓ Installed plugin '#{name}' (#{gem_name})"
2215
- puts " Restart the server to activate: brainiac restart"
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
- version = entry["version"] || "latest"
2327
+ local_path = entry["path"]
2280
2328
  installed_at = entry["installed_at"] ? " (#{entry["installed_at"][0..9]})" : ""
2281
2329
 
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)"
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
@@ -7,10 +7,10 @@
7
7
  #
8
8
  # {
9
9
  # "galen": {
10
- # "fizzy_name": "Galen",
10
+ # "display_name": "Galen",
11
11
  # "local": true,
12
12
  # "env": {
13
- # "FIZZY_TOKEN": "fizzy_abc...",
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
- # Convenience: get the Fizzy token for an agent.
74
- def fizzy_token_for(agent_name)
75
- agent_env_var(agent_name, "FIZZY_TOKEN")
76
- end
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["fizzy_name"] || agent_name
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] = fizzy_display_name(name) }
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 (with their fizzy_name if specified)
143
+ # Include agents from the registry
157
144
  AGENT_REGISTRY.each do |key, entry|
158
- names << (entry["fizzy_name"] || key.capitalize)
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["fizzy_name"] || key.capitalize)
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
- # Fizzy renders mentions using first name only (e.g. "@Sleeper" not "@Sleeper Service").
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
 
@@ -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
- if fizzy_mentioned || fizzy_originated
177
- knowledge_threads << Thread.new { query_brain("fizzy CLI commands", scope: :knowledge, max_results: 2) }
178
- search_queries << "fizzy CLI commands"
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) }