brainiac 0.0.6 → 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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +2 -2
  3. data/README.md +136 -6
  4. data/bin/brainiac +382 -11
  5. data/bin/brainiac-completion.bash +1 -1
  6. data/lib/brainiac/agents.rb +15 -38
  7. data/lib/brainiac/config.rb +40 -11
  8. data/lib/brainiac/handlers/discord/api.rb +196 -0
  9. data/lib/brainiac/handlers/discord/config.rb +134 -0
  10. data/lib/brainiac/handlers/discord/delivery.rb +196 -0
  11. data/lib/brainiac/handlers/discord/gateway.rb +212 -0
  12. data/lib/brainiac/handlers/discord/message.rb +933 -0
  13. data/lib/brainiac/handlers/discord/reactions.rb +215 -0
  14. data/lib/brainiac/handlers/discord.rb +14 -1892
  15. data/lib/brainiac/handlers/fizzy/assignment.rb +125 -0
  16. data/lib/brainiac/handlers/fizzy/comments.rb +730 -0
  17. data/lib/brainiac/handlers/fizzy/dedup.rb +74 -0
  18. data/lib/brainiac/handlers/fizzy/deploy.rb +152 -0
  19. data/lib/brainiac/{deployments.rb → handlers/fizzy/deployments.rb} +4 -2
  20. data/lib/brainiac/handlers/fizzy.rb +12 -1290
  21. data/lib/brainiac/handlers/github.rb +149 -212
  22. data/lib/brainiac/handlers/shared/git.rb +203 -0
  23. data/lib/brainiac/handlers/shared/inline_tags.rb +89 -0
  24. data/lib/brainiac/handlers/zoho.rb +79 -76
  25. data/lib/brainiac/helpers.rb +2 -121
  26. data/lib/brainiac/planning.rb +2 -2
  27. data/lib/brainiac/plugins.rb +129 -0
  28. data/lib/brainiac/restart.rb +94 -0
  29. data/lib/brainiac/routes/api.rb +427 -0
  30. data/lib/brainiac/version.rb +1 -1
  31. data/lib/brainiac/zoho_mail_api.rb +2 -1
  32. data/lib/brainiac.rb +7 -0
  33. data/monitor/daemon.rb +4 -27
  34. data/monitor/shared.rb +247 -0
  35. data/monitor/{waybar-deploy-env.rb → waybar/deploy_env.rb} +14 -86
  36. data/monitor/waybar/setup.rb +232 -0
  37. data/monitor/waybar/status.rb +51 -0
  38. data/monitor/{view-logs-rofi.rb → waybar/view_logs.rb} +21 -88
  39. data/monitor/{deploy-env-macos.rb → xbar/deploy_env.rb} +3 -2
  40. data/monitor/xbar/plugin.rb +155 -0
  41. data/monitor/{setup-menubar.rb → xbar/setup.rb} +14 -30
  42. data/receiver.rb +91 -450
  43. data/templates/brainiac.json.example +9 -0
  44. data/templates/cli-providers/kiro.json.example +8 -2
  45. data/templates/plugins.json.example +3 -0
  46. metadata +30 -20
  47. data/lib/user_registry.rb +0 -159
  48. data/monitor/menubar.rb +0 -295
  49. data/monitor/setup-waybar-deploy-envs.rb +0 -121
  50. data/monitor/setup-waybar-deployments.rb +0 -96
  51. data/monitor/setup-waybar-module.rb +0 -113
  52. data/monitor/setup-xbar-plugin.rb +0 -35
  53. data/monitor/view-logs.rb +0 -119
  54. data/monitor/waybar-config-updater.rb +0 -56
  55. data/monitor/waybar-deployments.rb +0 -239
  56. data/monitor/waybar.rb +0 -146
  57. data/monitor/xbar.3s.rb +0 -179
  58. /data/lib/brainiac/{card_index.rb → handlers/fizzy/card_index.rb} +0 -0
  59. /data/monitor/{open-action.sh → xbar/open_action.sh} +0 -0
  60. /data/monitor/{view-logs-macos.rb → xbar/view_logs.rb} +0 -0
data/receiver.rb CHANGED
@@ -19,10 +19,40 @@ require_relative "lib/brainiac/prompts"
19
19
  require_relative "lib/brainiac/planning"
20
20
  require_relative "lib/brainiac/helpers"
21
21
  require_relative "lib/brainiac/cron"
22
- require_relative "lib/brainiac/handlers/fizzy"
23
- require_relative "lib/brainiac/handlers/github"
24
- require_relative "lib/brainiac/card_index"
25
- require_relative "lib/brainiac/deployments"
22
+ require_relative "lib/brainiac/restart"
23
+ require_relative "lib/brainiac/plugins"
24
+ require_relative "lib/brainiac/handlers/shared/git"
25
+ require_relative "lib/brainiac/handlers/shared/inline_tags"
26
+
27
+ # Namespace for gem-based plugins (brainiac-whatsapp, brainiac-slack, etc.)
28
+ module Brainiac
29
+ module Plugins
30
+ end
31
+ end
32
+
33
+ # --- Conditional handler loading (based on ~/.brainiac/brainiac.json) ---
34
+
35
+ if handler_enabled?("fizzy")
36
+ require_relative "lib/brainiac/handlers/fizzy"
37
+ require_relative "lib/brainiac/handlers/fizzy/card_index"
38
+ require_relative "lib/brainiac/handlers/fizzy/deployments"
39
+ LOG.info "[Handlers] Fizzy handler enabled"
40
+ end
41
+
42
+ if handler_enabled?("github")
43
+ require_relative "lib/brainiac/handlers/github"
44
+ LOG.info "[Handlers] GitHub handler enabled"
45
+ end
46
+
47
+ if DISCORD_ENABLED && handler_enabled?("discord")
48
+ require_relative "lib/brainiac/handlers/discord"
49
+ LOG.info "[Handlers] Discord handler enabled"
50
+ end
51
+
52
+ if DISCORD_ENABLED && handler_enabled?("zoho")
53
+ require_relative "lib/brainiac/handlers/zoho"
54
+ LOG.info "[Handlers] Zoho handler enabled"
55
+ end
26
56
 
27
57
  # Reload hook registry — custom handlers register callbacks here
28
58
  module ReloadHooks
@@ -45,15 +75,18 @@ end
45
75
  CUSTOM_HANDLERS_DIR = File.join(BRAINIAC_DIR, "handlers")
46
76
  if Dir.exist?(CUSTOM_HANDLERS_DIR)
47
77
  Dir.glob(File.join(CUSTOM_HANDLERS_DIR, "*.rb")).each do |handler|
48
- LOG.info "[Handlers] Loading custom handler: #{File.basename(handler)}"
78
+ handler_name = File.basename(handler, ".rb")
79
+ unless handler_enabled?(handler_name)
80
+ LOG.info "[Handlers] Skipping custom handler (disabled): #{handler_name}"
81
+ next
82
+ end
83
+ LOG.info "[Handlers] Loading custom handler: #{handler_name}"
49
84
  require handler
50
85
  end
51
86
  end
52
87
 
53
- if DISCORD_ENABLED
54
- require_relative "lib/brainiac/handlers/discord"
55
- require_relative "lib/brainiac/handlers/zoho"
56
- end
88
+ # --- Load gem-based plugins (brainiac-*) ---
89
+ load_plugins!(self)
57
90
 
58
91
  # --- Sinatra config ---
59
92
  set :host_authorization, { permit_all: true }
@@ -398,247 +431,7 @@ if DISCORD_ENABLED
398
431
  end
399
432
 
400
433
  # --- Admin API routes ---
401
-
402
- get "/api/projects" do
403
- content_type :json
404
- reload_projects!
405
- { projects: PROJECTS }.to_json
406
- end
407
-
408
- get "/api/projects/:key" do
409
- content_type :json
410
- reload_projects!
411
- project_key = params["key"]
412
- if PROJECTS.key?(project_key)
413
- { project: PROJECTS[project_key] }.to_json
414
- else
415
- halt 404, { error: "Project not found" }.to_json
416
- end
417
- end
418
-
419
- post "/api/reload" do
420
- content_type :json
421
- reload_projects!(force: true)
422
- reload_agent_registry!(force: true)
423
- reload_user_registry!(force: true)
424
- reload_github_config!(force: true)
425
- reload_deployments_config!(force: true)
426
- ReloadHooks.run_all!
427
- { status: "reloaded", projects: PROJECTS.keys, agents: all_agent_names.to_a, registry: AGENT_REGISTRY.keys,
428
- users: USER_REGISTRY["users"].size }.to_json
429
- end
430
-
431
- get "/api/agents" do
432
- content_type :json
433
- { default: AI_AGENT_NAME, agents: discover_kiro_agents, all_known: all_agent_names.to_a, roster: agent_roster }.to_json
434
- end
435
-
436
- get "/api/roles" do
437
- content_type :json
438
- roles = []
439
- if Dir.exist?(ROLES_DIR)
440
- Dir.glob(File.join(ROLES_DIR, "*.md")).each do |f|
441
- name = File.basename(f, ".md")
442
- agents = AGENT_REGISTRY.select { |_, e| e.is_a?(Hash) && Array(e["role"]).include?(name) }.map { |k, e| e["fizzy_name"] || k.capitalize }
443
- roles << { name: name, agents: agents }
444
- end
445
- end
446
- { roles: roles, dir: ROLES_DIR }.to_json
447
- end
448
-
449
- get "/api/users" do
450
- content_type :json
451
- reload_user_registry!
452
-
453
- filter = params["filter"]
454
- users = case filter
455
- when "humans" then human_users
456
- when "agents" then ai_agents
457
- else USER_REGISTRY["users"]
458
- end
459
-
460
- { users: users, total: USER_REGISTRY["users"].size, schema_version: USER_REGISTRY["schema_version"] }.to_json
461
- end
462
-
463
- get "/api/users/:identifier" do
464
- content_type :json
465
- reload_user_registry!
466
-
467
- identifier = params["identifier"]
468
- user = find_user(identifier)
469
-
470
- if user
471
- { user: user }.to_json
472
- else
473
- halt 404, { error: "User not found", identifier: identifier }.to_json
474
- end
475
- end
476
-
477
- # --- Brain API routes ---
478
-
479
- get "/api/brain" do
480
- content_type :json
481
- agent = params["agent"] || AI_AGENT_NAME
482
- persona_dir = persona_dir_for(agent)
483
- persona_col = persona_collection_for(agent)
484
-
485
- knowledge_files = File.directory?(KNOWLEDGE_DIR) ? Dir.glob(File.join(KNOWLEDGE_DIR, "**", "*.md")).map { |f| f.sub("#{KNOWLEDGE_DIR}/", "") } : []
486
- persona_files = File.directory?(persona_dir) ? Dir.glob(File.join(persona_dir, "**", "*.md")).map { |f| f.sub("#{persona_dir}/", "") } : []
487
-
488
- {
489
- agent: agent,
490
- knowledge: { dir: KNOWLEDGE_DIR, collection: KNOWLEDGE_COLLECTION, files: knowledge_files },
491
- persona: { dir: persona_dir, collection: persona_col, files: persona_files }
492
- }.to_json
493
- end
494
-
495
- get "/api/brain/search" do
496
- content_type :json
497
- query = params["q"]
498
- halt 400, { error: "Missing query parameter ?q=" }.to_json unless query && !query.empty?
499
-
500
- agent = params["agent"] || AI_AGENT_NAME
501
- scope = (params["scope"] || "knowledge").to_sym
502
- scope = :knowledge unless %i[knowledge persona].include?(scope)
503
- results = query_brain(query, agent_name: agent, scope: scope, max_results: (params["n"] || 5).to_i)
504
-
505
- { agent: agent, scope: scope, query: query, results: results }.to_json
506
- end
507
-
508
- get "/api/skills" do
509
- content_type :json
510
- skills = build_skill_index
511
- { total: skills.size, skills: skills }.to_json
512
- end
513
-
514
- post "/api/skills/curate" do
515
- content_type :json
516
- result = curate_skills
517
- result.to_json
518
- end
519
-
520
- get "/api/card-index" do
521
- content_type :json
522
- query = params["q"]
523
- if query && !query.empty?
524
- similar = CARD_INDEX.find_similar_cards(query)
525
- { query: query, matches: similar, total_indexed: CARD_INDEX.size }.to_json
526
- else
527
- { total: CARD_INDEX.size, cards: CARD_INDEX }.to_json
528
- end
529
- end
530
-
531
- get "/api/dispatch-depth" do
532
- content_type :json
533
- {
534
- max_depth: AGENT_DISPATCH_MAX_DEPTH,
535
- window_seconds: AGENT_DISPATCH_WINDOW,
536
- cards: AGENT_DISPATCH_DEPTH.transform_values do |v|
537
- { count: v[:count], last_human_at: v[:last_human_at]&.iso8601, blocked: v[:count] >= AGENT_DISPATCH_MAX_DEPTH }
538
- end
539
- }.to_json
540
- end
541
-
542
- get "/api/status" do
543
- content_type :json
544
- ACTIVE_SESSIONS_MUTEX.synchronize do
545
- # Clean up stale sessions first
546
- ACTIVE_SESSIONS.delete_if do |card_key, info|
547
- Process.kill(0, info[:pid])
548
- false # Keep alive sessions
549
- rescue Errno::ESRCH, Errno::EPERM
550
- archive_session(card_key, info)
551
- true # Remove dead sessions
552
- end
553
-
554
- sessions = ACTIVE_SESSIONS.map do |card_key, info|
555
- # Use stored agent_name if available, otherwise try to extract from card_key
556
- agent_name = if info[:agent_name]
557
- info[:agent_name]
558
- else
559
- # Fallback: extract from card_key for backwards compatibility
560
- # Formats: "discord-AGENT-CHANNEL-MESSAGE" or "card-ID"
561
- parts = card_key.split("-")
562
- agent_key = if parts[0] == "discord" && parts.size >= 4
563
- parts[1] # Second part is agent name
564
- else
565
- "Unknown"
566
- end
567
- fizzy_display_name(agent_key)
568
- end
569
-
570
- {
571
- card_key: card_key,
572
- agent: agent_name,
573
- pid: info[:pid],
574
- started_at: info[:started_at].iso8601,
575
- elapsed_seconds: (Time.now - info[:started_at]).to_i,
576
- log_file: info[:log_file],
577
- alive: true,
578
- children: child_processes_for(info[:pid])
579
- }
580
- end
581
-
582
- recent = RECENT_SESSIONS.map do |s|
583
- {
584
- card_key: s[:card_key],
585
- agent: s[:agent_name] || "Unknown",
586
- log_file: s[:log_file],
587
- started_at: s[:started_at]&.iso8601,
588
- finished_at: s[:finished_at]&.iso8601
589
- }
590
- end
591
-
592
- { sessions: sessions, count: sessions.size, recent: recent, version: BRAINIAC_VERSION }.to_json
593
- end
594
- end
595
-
596
- # Kill an entire agent session (parent process + all children)
597
- post "/api/sessions/kill/:card_key" do
598
- content_type :json
599
- card_key = params[:card_key]
600
- halt 400, { error: "missing card_key" }.to_json if card_key.to_s.empty?
601
-
602
- killed = kill_session(card_key)
603
- halt 404, { error: "session not found" }.to_json unless killed
604
-
605
- LOG.info "Killed agent session #{card_key} via API"
606
- { killed: card_key }.to_json
607
- end
608
-
609
- # Kill a specific child process of an active agent session
610
- post "/api/sessions/kill-process/:pid" do
611
- content_type :json
612
- target_pid = params[:pid].to_i
613
- halt 400, { error: "invalid pid" }.to_json if target_pid <= 0
614
-
615
- # Verify the target PID is actually a descendant of an active session
616
- valid = ACTIVE_SESSIONS_MUTEX.synchronize do
617
- ACTIVE_SESSIONS.any? do |_, info|
618
- child_processes_for(info[:pid]).any? { |c| c[:pid] == target_pid }
619
- end
620
- end
621
- halt 403, { error: "pid is not a child of any active agent session" }.to_json unless valid
622
-
623
- begin
624
- Process.kill("TERM", target_pid)
625
- # Give it a moment, then force kill if still alive
626
- Thread.new do
627
- sleep 3
628
- begin
629
- Process.kill(0, target_pid)
630
- Process.kill("KILL", target_pid)
631
- rescue Errno::ESRCH, Errno::EPERM # rubocop:disable Lint/SuppressedException
632
- end
633
- end
634
- LOG.info "Killed child process #{target_pid} (SIGTERM)"
635
- { killed: target_pid }.to_json
636
- rescue Errno::ESRCH
637
- halt 404, { error: "process not found" }.to_json
638
- rescue Errno::EPERM
639
- halt 403, { error: "permission denied" }.to_json
640
- end
641
- end
434
+ require_relative "lib/brainiac/routes/api"
642
435
 
643
436
  # --- Dashboard ---
644
437
 
@@ -660,25 +453,6 @@ get "/dashboard" do
660
453
  erb :dashboard, layout: false
661
454
  end
662
455
 
663
- get "/api/logs" do
664
- content_type "text/plain"
665
- log_file = params["file"]
666
- lines = (params["lines"] || 200).to_i
667
-
668
- halt 400, "Missing ?file= parameter" unless log_file && !log_file.empty?
669
- halt 400, "Invalid path" if log_file.include?("..") || !log_file.start_with?("/")
670
- halt 404, "File not found" unless File.exist?(log_file)
671
-
672
- # Only allow reading log files from known project tmp dirs or brainiac tmp
673
- allowed = PROJECTS.values.map { |p| File.join(p["repo_path"], "tmp") }
674
- allowed << File.join(BRAINIAC_DIR, "tmp")
675
- halt 403, "Forbidden" unless allowed.any? { |dir| log_file.start_with?(dir) }
676
-
677
- # Read last N lines and strip ANSI escape codes
678
- all_lines = File.readlines(log_file).last(lines)
679
- all_lines.join.gsub(/\e\[[\d;]*[a-zA-Z]/, "").gsub(/\e\[\?[\d;]*[a-zA-Z]/, "")
680
- end
681
-
682
456
  # --- Discord API + startup ---
683
457
 
684
458
  if DISCORD_ENABLED
@@ -737,197 +511,62 @@ else
737
511
  end
738
512
  end
739
513
 
740
- # --- Cron API + startup ---
514
+ # --- Deployment environment tracking (requires fizzy handler) ---
741
515
 
742
- get "/api/cron/script" do
743
- content_type "text/plain"
744
- path = params["path"]
745
- halt 400, "Missing ?path= parameter" unless path && !path.empty?
746
- halt 400, "Invalid path" if path.include?("..")
747
-
748
- # Only allow reading scripts that are actually referenced by a cron job
749
- reload_cron_jobs!
750
- valid = CRON_JOBS.values.any? do |j|
751
- j[:script] == path || j["script"] == path ||
752
- (j[:prompt] || j["prompt"] || "").include?(path)
516
+ if defined?(DEPLOYMENTS_CONFIG)
517
+ get "/api/deployments" do
518
+ content_type :json
519
+ reload_deployments_config!
520
+ reload_deployment_state!
521
+ { deployments: deployment_status }.to_json
753
522
  end
754
- halt 403, "Not a registered cron script" unless valid
755
- halt 404, "File not found" unless File.exist?(path)
756
-
757
- File.read(path)
758
- end
759
-
760
- get "/api/cron" do
761
- content_type :json
762
- reload_cron_jobs!
763
- {
764
- enabled: true,
765
- jobs: CRON_JOBS,
766
- thread_alive: CRON_THREAD[:ref]&.alive? || false
767
- }.to_json
768
- end
769
-
770
- post "/api/cron/add" do
771
- content_type :json
772
- request.body.rewind
773
- payload = JSON.parse(request.body.read)
774
-
775
- result = add_cron_job(
776
- id: payload["id"],
777
- schedule: payload["schedule"],
778
- agent: payload["agent"],
779
- project: payload["project"],
780
- prompt: payload["prompt"],
781
- script: payload["script"],
782
- model: payload["model"],
783
- effort: payload["effort"],
784
- discord_channel_id: payload["discord_channel_id"],
785
- forum_title: payload["forum_title"],
786
- forum_reply_to_latest: payload["forum_reply_to_latest"] || false,
787
- repeat_count: payload["repeat_count"]
788
- )
789
-
790
- result.to_json
791
- end
792
-
793
- post "/api/cron/remove" do
794
- content_type :json
795
- request.body.rewind
796
- payload = JSON.parse(request.body.read)
797
-
798
- result = remove_cron_job(payload["id"])
799
- result.to_json
800
- end
801
-
802
- post "/api/cron/toggle" do
803
- content_type :json
804
- request.body.rewind
805
- payload = JSON.parse(request.body.read)
806
-
807
- result = toggle_cron_job(payload["id"], payload["enabled"])
808
- result.to_json
809
- end
810
-
811
- post "/api/cron/update" do
812
- content_type :json
813
- request.body.rewind
814
- payload = JSON.parse(request.body.read)
815
-
816
- result = update_cron_job(
817
- payload["id"],
818
- schedule: payload["schedule"],
819
- discord_channel_id: payload["discord_channel_id"],
820
- forum_title: payload["forum_title"],
821
- forum_reply_to_latest: payload["forum_reply_to_latest"]
822
- )
823
- result.to_json
824
- end
825
-
826
- post "/api/cron/reload" do
827
- content_type :json
828
- reload_cron_jobs!
829
- { status: "reloaded", jobs: CRON_JOBS.size }.to_json
830
- end
831
523
 
832
- get "/api/cron/logs" do
833
- content_type :json
834
- job_id = params["id"]
835
- halt 400, { error: "Missing ?id= parameter" }.to_json unless job_id && !job_id.empty?
836
-
837
- logs = []
838
- PROJECTS.each_value do |proj|
839
- tmp_dir = File.join(proj["repo_path"], "tmp")
840
- next unless Dir.exist?(tmp_dir)
524
+ post "/api/deployments/:env" do
525
+ content_type :json
526
+ env_key = params["env"]
527
+ request.body.rewind
528
+ payload = JSON.parse(request.body.read)
841
529
 
842
- Dir.glob(File.join(tmp_dir, "{agent-cron,cron-script}-#{job_id}-*.log")).each do |f|
843
- logs << { file: f, size: File.size(f), modified: File.mtime(f).iso8601 }
530
+ result = deploy_to_environment(env_key, worktree_path: payload["worktree"], deployed_by: payload["deployed_by"])
531
+ if result[:error]
532
+ halt 404, result.to_json
533
+ else
534
+ { status: "deployed", env: env_key, deployment: result }.to_json
844
535
  end
536
+ rescue JSON::ParserError
537
+ halt 400, { error: "Invalid JSON" }.to_json
845
538
  end
846
- logs.sort_by! { |l| l[:modified] }.reverse!
847
- logs.first(20).to_json
848
- end
849
-
850
- get "/api/gif" do
851
- content_type :json
852
- query = params[:q].to_s.strip
853
- halt 400, { error: "Missing ?q= parameter" }.to_json if query.empty?
854
-
855
- api_key = DISCORD_CONFIG["giphy_api_key"]
856
- halt 503, { error: "No giphy_api_key configured in discord.json" }.to_json unless api_key
857
539
 
858
- begin
859
- uri = URI("https://api.giphy.com/v1/gifs/search")
860
- uri.query = URI.encode_www_form(api_key: api_key, q: query, limit: 5, rating: "pg-13")
861
- response = Net::HTTP.get_response(uri)
862
-
863
- if response.code.to_i == 200
864
- results = JSON.parse(response.body)["data"] || []
865
- gifs = results.map { |g| { url: g.dig("images", "original", "url") || g["url"], title: g["title"] } }
866
- { query: query, results: gifs }.to_json
540
+ delete "/api/deployments/:env" do
541
+ content_type :json
542
+ env_key = params["env"]
543
+ state = load_deployment_state
544
+ if state.key?(env_key)
545
+ state[env_key] = { "status" => "available", "cleared_at" => Time.now.iso8601, "last_card" => state[env_key]["card_number"] }
546
+ save_deployment_state(state)
547
+ DEPLOYMENT_STATE.replace(state)
548
+ LOG.info "[Deploy] Manually cleared #{env_key}"
549
+ { status: "cleared", env: env_key }.to_json
867
550
  else
868
- LOG.warn "[GIF] Giphy API returned #{response.code}: #{response.body[0..200]}"
869
- halt 502, { error: "Giphy API error: #{response.code}" }.to_json
551
+ halt 404, { error: "Unknown environment: #{env_key}" }.to_json
870
552
  end
871
- rescue StandardError => e
872
- LOG.error "[GIF] Search failed: #{e.message}"
873
- halt 500, { error: e.message }.to_json
874
- end
875
- end
876
-
877
- # --- Deployment environment tracking ---
878
-
879
- get "/api/deployments" do
880
- content_type :json
881
- reload_deployments_config!
882
- reload_deployment_state!
883
- { deployments: deployment_status }.to_json
884
- end
885
-
886
- post "/api/deployments/:env" do
887
- content_type :json
888
- env_key = params["env"]
889
- request.body.rewind
890
- payload = JSON.parse(request.body.read)
891
-
892
- result = deploy_to_environment(env_key, worktree_path: payload["worktree"], deployed_by: payload["deployed_by"])
893
- if result[:error]
894
- halt 404, result.to_json
895
- else
896
- { status: "deployed", env: env_key, deployment: result }.to_json
897
- end
898
- rescue JSON::ParserError
899
- halt 400, { error: "Invalid JSON" }.to_json
900
- end
901
-
902
- delete "/api/deployments/:env" do
903
- content_type :json
904
- env_key = params["env"]
905
- state = load_deployment_state
906
- if state.key?(env_key)
907
- state[env_key] = { "status" => "available", "cleared_at" => Time.now.iso8601, "last_card" => state[env_key]["card_number"] }
908
- save_deployment_state(state)
909
- DEPLOYMENT_STATE.replace(state)
910
- LOG.info "[Deploy] Manually cleared #{env_key}"
911
- { status: "cleared", env: env_key }.to_json
912
- else
913
- halt 404, { error: "Unknown environment: #{env_key}" }.to_json
914
553
  end
915
- end
916
554
 
917
- post "/api/deployments/:env/deploying" do
918
- content_type :json
919
- env_key = params["env"]
920
- config = DEPLOYMENTS_CONFIG["environments"] || {}
921
- halt 404, { error: "Unknown environment: #{env_key}" }.to_json unless config.key?(env_key)
922
- request.body.rewind
923
- payload = begin
924
- JSON.parse(request.body.read)
925
- rescue StandardError
926
- {}
555
+ post "/api/deployments/:env/deploying" do
556
+ content_type :json
557
+ env_key = params["env"]
558
+ config = DEPLOYMENTS_CONFIG["environments"] || {}
559
+ halt 404, { error: "Unknown environment: #{env_key}" }.to_json unless config.key?(env_key)
560
+ request.body.rewind
561
+ payload = begin
562
+ JSON.parse(request.body.read)
563
+ rescue StandardError
564
+ {}
565
+ end
566
+ mark_deploying(env_key, worktree_path: payload["worktree"] || "")
567
+ LOG.info "[Deploy] #{env_key} marked deploying via API"
568
+ { status: "deploying", env: env_key }.to_json
927
569
  end
928
- mark_deploying(env_key, worktree_path: payload["worktree"] || "")
929
- LOG.info "[Deploy] #{env_key} marked deploying via API"
930
- { status: "deploying", env: env_key }.to_json
931
570
  end
932
571
 
933
572
  LOG.info "[Cron] Starting cron thread..."
@@ -944,8 +583,10 @@ CURATOR_THREAD = Thread.new do
944
583
  end
945
584
  end
946
585
 
947
- LOG.info "[CardIndex] Starting background backfill..."
948
- CARD_INDEX.backfill
586
+ if defined?(CARD_INDEX)
587
+ LOG.info "[CardIndex] Starting background backfill..."
588
+ CARD_INDEX.backfill
589
+ end
949
590
 
950
591
  LOG.info "[Monitor] Starting daemon..."
951
592
  daemon_path = File.join(__dir__, "monitor", "daemon.rb")
@@ -0,0 +1,9 @@
1
+ {
2
+ "default_agent": "Galen",
3
+ "handlers": {
4
+ "fizzy": true,
5
+ "github": true,
6
+ "discord": true,
7
+ "zoho": false
8
+ }
9
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "binary": "kiro-cli",
3
- "default_args": "chat --trust-all-tools --no-interactive --trust-tools execute_bash,fs_write,fs_read,code,grep,glob,web_search,web_fetch,use_subagent,use_aws",
3
+ "default_args": "chat --trust-all-tools --no-interactive",
4
4
  "agent_flag": "--agent",
5
5
  "model_flag": "--model",
6
6
  "effort_flag": "--effort",
@@ -9,7 +9,13 @@
9
9
  "models": {
10
10
  "opus": "claude-opus-4.6",
11
11
  "sonnet": "claude-sonnet-4.6",
12
- "haiku": "claude-haiku-4.5"
12
+ "haiku": "claude-haiku-4.5",
13
+ "deepseek": "deepseek-3.2",
14
+ "minimax": "minimax-m2.5",
15
+ "minimax25": "minimax-m2.5",
16
+ "minimax21": "minimax-m2.1",
17
+ "qwen": "qwen3-coder-next",
18
+ "auto": "auto"
13
19
  },
14
20
  "efforts": ["low", "medium", "high", "xhigh", "max"]
15
21
  }
@@ -0,0 +1,3 @@
1
+ {
2
+ "plugins": []
3
+ }