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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +136 -6
- data/bin/brainiac +382 -11
- data/bin/brainiac-completion.bash +1 -1
- data/lib/brainiac/agents.rb +15 -38
- data/lib/brainiac/config.rb +40 -11
- data/lib/brainiac/handlers/discord/api.rb +196 -0
- data/lib/brainiac/handlers/discord/config.rb +134 -0
- data/lib/brainiac/handlers/discord/delivery.rb +196 -0
- data/lib/brainiac/handlers/discord/gateway.rb +212 -0
- data/lib/brainiac/handlers/discord/message.rb +933 -0
- data/lib/brainiac/handlers/discord/reactions.rb +215 -0
- data/lib/brainiac/handlers/discord.rb +14 -1892
- data/lib/brainiac/handlers/fizzy/assignment.rb +125 -0
- data/lib/brainiac/handlers/fizzy/comments.rb +730 -0
- data/lib/brainiac/handlers/fizzy/dedup.rb +74 -0
- data/lib/brainiac/handlers/fizzy/deploy.rb +152 -0
- data/lib/brainiac/{deployments.rb → handlers/fizzy/deployments.rb} +4 -2
- data/lib/brainiac/handlers/fizzy.rb +12 -1290
- data/lib/brainiac/handlers/github.rb +149 -212
- data/lib/brainiac/handlers/shared/git.rb +203 -0
- data/lib/brainiac/handlers/shared/inline_tags.rb +89 -0
- data/lib/brainiac/handlers/zoho.rb +79 -76
- data/lib/brainiac/helpers.rb +2 -121
- data/lib/brainiac/planning.rb +2 -2
- data/lib/brainiac/plugins.rb +129 -0
- data/lib/brainiac/restart.rb +94 -0
- data/lib/brainiac/routes/api.rb +427 -0
- data/lib/brainiac/version.rb +1 -1
- data/lib/brainiac/zoho_mail_api.rb +2 -1
- data/lib/brainiac.rb +7 -0
- data/monitor/daemon.rb +4 -27
- data/monitor/shared.rb +247 -0
- data/monitor/{waybar-deploy-env.rb → waybar/deploy_env.rb} +14 -86
- data/monitor/waybar/setup.rb +232 -0
- data/monitor/waybar/status.rb +51 -0
- data/monitor/{view-logs-rofi.rb → waybar/view_logs.rb} +21 -88
- data/monitor/{deploy-env-macos.rb → xbar/deploy_env.rb} +3 -2
- data/monitor/xbar/plugin.rb +155 -0
- data/monitor/{setup-menubar.rb → xbar/setup.rb} +14 -30
- data/receiver.rb +91 -450
- data/templates/brainiac.json.example +9 -0
- data/templates/cli-providers/kiro.json.example +8 -2
- data/templates/plugins.json.example +3 -0
- metadata +30 -20
- data/lib/user_registry.rb +0 -159
- data/monitor/menubar.rb +0 -295
- data/monitor/setup-waybar-deploy-envs.rb +0 -121
- data/monitor/setup-waybar-deployments.rb +0 -96
- data/monitor/setup-waybar-module.rb +0 -113
- data/monitor/setup-xbar-plugin.rb +0 -35
- data/monitor/view-logs.rb +0 -119
- data/monitor/waybar-config-updater.rb +0 -56
- data/monitor/waybar-deployments.rb +0 -239
- data/monitor/waybar.rb +0 -146
- data/monitor/xbar.3s.rb +0 -179
- /data/lib/brainiac/{card_index.rb → handlers/fizzy/card_index.rb} +0 -0
- /data/monitor/{open-action.sh → xbar/open_action.sh} +0 -0
- /data/monitor/{view-logs-macos.rb → xbar/view_logs.rb} +0 -0
data/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/
|
|
23
|
-
require_relative "lib/brainiac/
|
|
24
|
-
require_relative "lib/brainiac/
|
|
25
|
-
require_relative "lib/brainiac/
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
# ---
|
|
514
|
+
# --- Deployment environment tracking (requires fizzy handler) ---
|
|
741
515
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
-
|
|
843
|
-
|
|
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
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
|
|
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
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
-
|
|
948
|
-
|
|
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")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"binary": "kiro-cli",
|
|
3
|
-
"default_args": "chat --trust-all-tools --no-interactive
|
|
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
|
}
|