zillacore 0.0.1
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +126 -0
- data/README.md +1166 -0
- data/Rakefile +12 -0
- data/bin/zillacore +1521 -0
- data/certs/stowzilla.pem +26 -0
- data/docs/waybar-config.md +96 -0
- data/lib/user_registry.rb +159 -0
- data/lib/zillacore/agents.rb +203 -0
- data/lib/zillacore/brain.rb +197 -0
- data/lib/zillacore/card_index.rb +389 -0
- data/lib/zillacore/config.rb +263 -0
- data/lib/zillacore/cron.rb +629 -0
- data/lib/zillacore/deployments.rb +258 -0
- data/lib/zillacore/handlers/discord.rb +1643 -0
- data/lib/zillacore/handlers/fizzy.rb +1249 -0
- data/lib/zillacore/handlers/github.rb +598 -0
- data/lib/zillacore/handlers/zoho.rb +487 -0
- data/lib/zillacore/helpers.rb +760 -0
- data/lib/zillacore/planning.rb +237 -0
- data/lib/zillacore/prompts.rb +620 -0
- data/lib/zillacore/sessions.rb +282 -0
- data/lib/zillacore/skills.rb +276 -0
- data/lib/zillacore/users.rb +76 -0
- data/lib/zillacore/version.rb +6 -0
- data/lib/zillacore/zoho_mail_api.rb +109 -0
- data/lib/zillacore.rb +10 -0
- data/monitor/daemon.rb +99 -0
- data/monitor/deploy-env-macos.rb +131 -0
- data/monitor/menubar.rb +295 -0
- data/monitor/open-action.sh +15 -0
- data/monitor/setup-menubar.rb +78 -0
- data/monitor/setup-waybar-deploy-envs.rb +121 -0
- data/monitor/setup-waybar-deployments.rb +96 -0
- data/monitor/setup-waybar-module.rb +113 -0
- data/monitor/setup-xbar-plugin.rb +35 -0
- data/monitor/view-logs-macos.rb +210 -0
- data/monitor/view-logs-rofi.rb +194 -0
- data/monitor/view-logs.rb +119 -0
- data/monitor/waybar-config-updater.rb +56 -0
- data/monitor/waybar-deploy-env.rb +206 -0
- data/monitor/waybar-deployments.rb +239 -0
- data/monitor/waybar.rb +146 -0
- data/monitor/xbar.3s.rb +179 -0
- data/receiver.rb +956 -0
- data/templates/agents.json.example +10 -0
- data/templates/discord.json.example +17 -0
- data/templates/fizzy.json.example +24 -0
- data/templates/github.json.example +4 -0
- data/templates/testflight.json.example +8 -0
- data/templates/users.json.example +121 -0
- data/templates/zoho.json.example +27 -0
- data/views/dashboard.erb +437 -0
- data/zillacore.gemspec +30 -0
- data.tar.gz.sig +2 -0
- metadata +235 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# One-time setup script to install ZillaCore menubar plugin into xbar or SwiftBar
|
|
5
|
+
# Run this once — the plugin will then auto-refresh on its configured interval
|
|
6
|
+
|
|
7
|
+
PLUGIN_APPS = [
|
|
8
|
+
{
|
|
9
|
+
name: "SwiftBar",
|
|
10
|
+
plugin_dir: File.expand_path("~/Library/Application Support/SwiftBar/Plugins"),
|
|
11
|
+
app_path: "/Applications/SwiftBar.app"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "xbar",
|
|
15
|
+
plugin_dir: File.expand_path("~/Library/Application Support/xbar/plugins"),
|
|
16
|
+
app_path: "/Applications/xbar.app"
|
|
17
|
+
}
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
SYMLINK_NAME = "zillacore.2s.rb"
|
|
21
|
+
SOURCE_PATH = File.join(File.dirname(File.expand_path(__FILE__)), "menubar.rb")
|
|
22
|
+
|
|
23
|
+
def detect_plugin_app
|
|
24
|
+
PLUGIN_APPS.each do |app|
|
|
25
|
+
return { name: app[:name], plugin_dir: app[:plugin_dir] } if Dir.exist?(app[:plugin_dir]) || File.exist?(app[:app_path])
|
|
26
|
+
end
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def install_plugin(plugin_dir, source_path)
|
|
31
|
+
FileUtils.mkdir_p(plugin_dir)
|
|
32
|
+
link_path = File.join(plugin_dir, SYMLINK_NAME)
|
|
33
|
+
|
|
34
|
+
# Remove existing symlink/file if present
|
|
35
|
+
File.delete(link_path) if File.exist?(link_path) || File.symlink?(link_path)
|
|
36
|
+
|
|
37
|
+
File.symlink(source_path, link_path)
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
warn "✗ Failed to create symlink: #{e.message}"
|
|
40
|
+
warn " Source: #{source_path}"
|
|
41
|
+
warn " Target: #{link_path}"
|
|
42
|
+
exit 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def verify_executable!(path) # rubocop:disable Naming/PredicateMethod
|
|
46
|
+
unless File.executable?(path)
|
|
47
|
+
File.chmod(0o755, path)
|
|
48
|
+
warn " Fixed executable permission on #{path}"
|
|
49
|
+
end
|
|
50
|
+
File.executable?(path)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# --- Main ---
|
|
54
|
+
|
|
55
|
+
require "fileutils"
|
|
56
|
+
|
|
57
|
+
app = detect_plugin_app
|
|
58
|
+
|
|
59
|
+
unless app
|
|
60
|
+
puts "No xbar or SwiftBar installation detected."
|
|
61
|
+
puts ""
|
|
62
|
+
puts "Install one of the following to use the ZillaCore menu bar plugin:"
|
|
63
|
+
puts " • xbar: https://xbarapp.com"
|
|
64
|
+
puts " • SwiftBar: https://github.com/swiftbar/SwiftBar"
|
|
65
|
+
puts ""
|
|
66
|
+
puts "After installing, re-run this script:"
|
|
67
|
+
puts " ruby #{__FILE__}"
|
|
68
|
+
exit 0
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
puts "Detected #{app[:name]}"
|
|
72
|
+
install_plugin(app[:plugin_dir], SOURCE_PATH)
|
|
73
|
+
verify_executable!(SOURCE_PATH)
|
|
74
|
+
|
|
75
|
+
link_path = File.join(app[:plugin_dir], SYMLINK_NAME)
|
|
76
|
+
puts "✓ Installed ZillaCore plugin into #{app[:name]}"
|
|
77
|
+
puts " Symlink: #{link_path} → #{SOURCE_PATH}"
|
|
78
|
+
puts " Refresh interval: 2s"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# One-time setup: replaces the single zillacore-deployments module
|
|
5
|
+
# with per-environment modules so each dot gets its own border/click.
|
|
6
|
+
|
|
7
|
+
require "json"
|
|
8
|
+
require "fileutils"
|
|
9
|
+
|
|
10
|
+
WAYBAR_CONFIG = File.expand_path("~/.config/waybar/config.jsonc")
|
|
11
|
+
DEPLOY_SCRIPT = File.expand_path("~/.zillacore/bin/waybar-deploy-env")
|
|
12
|
+
DEPLOYMENTS_CONFIG = File.expand_path("~/.zillacore/deployments.json")
|
|
13
|
+
WAYBAR_STYLE = File.expand_path("~/.config/waybar/style.css")
|
|
14
|
+
|
|
15
|
+
# Create wrapper script that resolves from server.root
|
|
16
|
+
wrapper_dir = File.expand_path("~/.zillacore/bin")
|
|
17
|
+
FileUtils.mkdir_p(wrapper_dir)
|
|
18
|
+
File.write(DEPLOY_SCRIPT, <<~SCRIPT)
|
|
19
|
+
#!/usr/bin/env ruby
|
|
20
|
+
root_file = File.expand_path("~/.zillacore/server.root")
|
|
21
|
+
if File.exist?(root_file)
|
|
22
|
+
server_root = File.read(root_file).strip
|
|
23
|
+
script = File.join(server_root, "monitor", "waybar-deploy-env.rb")
|
|
24
|
+
if File.exist?(script)
|
|
25
|
+
ARGV.unshift if ARGV.empty?
|
|
26
|
+
load script
|
|
27
|
+
exit
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
require "json"
|
|
31
|
+
puts({ text: "", tooltip: "ZillaCore server root not found", class: "error" }.to_json)
|
|
32
|
+
SCRIPT
|
|
33
|
+
File.chmod(0o755, DEPLOY_SCRIPT)
|
|
34
|
+
|
|
35
|
+
def load_config
|
|
36
|
+
content = File.read(WAYBAR_CONFIG)
|
|
37
|
+
json_content = content.lines.reject { |line| line.strip.start_with?("//") }.join
|
|
38
|
+
JSON.parse(json_content)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def save_config(config)
|
|
42
|
+
File.write(WAYBAR_CONFIG, JSON.pretty_generate(config))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
deployments = JSON.parse(File.read(DEPLOYMENTS_CONFIG))
|
|
46
|
+
envs = deployments["environments"].keys
|
|
47
|
+
|
|
48
|
+
config = load_config
|
|
49
|
+
|
|
50
|
+
# Remove old single deployments module from all bar positions
|
|
51
|
+
%w[modules-left modules-center modules-right].each do |pos|
|
|
52
|
+
next unless config[pos]
|
|
53
|
+
|
|
54
|
+
config[pos].reject! { |m| m.to_s.include?("zillacore-deploy") }
|
|
55
|
+
end
|
|
56
|
+
config.delete("custom/zillacore-deployments")
|
|
57
|
+
|
|
58
|
+
# Remove any existing per-env modules
|
|
59
|
+
config.each_key do |key|
|
|
60
|
+
config.delete(key) if key.start_with?("custom/zillacore-deploy-")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Insert per-env modules into modules-center, before custom/zillacore
|
|
64
|
+
center = config["modules-center"] || []
|
|
65
|
+
zc_idx = center.index("custom/zillacore") || center.length
|
|
66
|
+
envs.each_with_index do |env, i|
|
|
67
|
+
mod_name = "custom/zillacore-deploy-#{env}"
|
|
68
|
+
center.insert(zc_idx + i, mod_name) unless center.include?(mod_name)
|
|
69
|
+
end
|
|
70
|
+
config["modules-center"] = center
|
|
71
|
+
|
|
72
|
+
# Add module configs for each env
|
|
73
|
+
envs.each do |env|
|
|
74
|
+
mod_name = "custom/zillacore-deploy-#{env}"
|
|
75
|
+
config[mod_name] = {
|
|
76
|
+
"exec" => "#{DEPLOY_SCRIPT} #{env}",
|
|
77
|
+
"return-type" => "json",
|
|
78
|
+
"interval" => 30,
|
|
79
|
+
"format" => "{}",
|
|
80
|
+
"tooltip" => true,
|
|
81
|
+
"escape" => false,
|
|
82
|
+
"on-click" => "#{DEPLOY_SCRIPT} #{env} --click",
|
|
83
|
+
"on-click-right" => "#{DEPLOY_SCRIPT} #{env} --deploy"
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
save_config(config)
|
|
88
|
+
puts "✓ Added per-environment deploy modules: #{envs.map { |e| "custom/zillacore-deploy-#{e}" }.join(", ")}"
|
|
89
|
+
|
|
90
|
+
# Update CSS — remove old single-module styles, add per-env styles
|
|
91
|
+
style = File.read(WAYBAR_STYLE)
|
|
92
|
+
|
|
93
|
+
# Remove old block
|
|
94
|
+
style.gsub!(%r{/\* ZillaCore deployment environment dots \*/.*?(?=\n\n|\n/\*|\z)}m, "")
|
|
95
|
+
style.gsub!(/\n*#custom-zillacore-deployments[^{]*\{[^}]*\}\n*/m, "")
|
|
96
|
+
|
|
97
|
+
# Add new per-env styles
|
|
98
|
+
unless style.include?("#custom-zillacore-deploy-")
|
|
99
|
+
css = <<~CSS
|
|
100
|
+
|
|
101
|
+
/* ZillaCore per-environment deploy dots */
|
|
102
|
+
[id^="custom-zillacore-deploy-"] {
|
|
103
|
+
font-size: 28px;
|
|
104
|
+
padding: 0 6px;
|
|
105
|
+
border-radius: 8px;
|
|
106
|
+
border: 2px solid transparent;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
[id^="custom-zillacore-deploy-"].deploy-recent {
|
|
110
|
+
border: 2px solid #4488ff;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
[id^="custom-zillacore-deploy-"].deploy-failed {
|
|
114
|
+
border: 2px solid #ff4444;
|
|
115
|
+
}
|
|
116
|
+
CSS
|
|
117
|
+
File.write(WAYBAR_STYLE, "#{style.strip}\n#{css}")
|
|
118
|
+
puts "✓ Updated waybar CSS with per-environment border styles"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
puts "✓ Restart waybar to apply: omarchy restart waybar"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# One-time setup: adds ZillaCore deployments module to waybar config
|
|
5
|
+
# and repositions the agent session module with more breathing room.
|
|
6
|
+
|
|
7
|
+
require "json"
|
|
8
|
+
|
|
9
|
+
WAYBAR_CONFIG = File.expand_path("~/.config/waybar/config.jsonc")
|
|
10
|
+
DEPLOY_SCRIPT = File.expand_path("~/Code/zillacore/monitor/waybar-deployments.rb")
|
|
11
|
+
WAYBAR_STYLE = File.expand_path("~/.config/waybar/style.css")
|
|
12
|
+
|
|
13
|
+
def load_config
|
|
14
|
+
content = File.read(WAYBAR_CONFIG)
|
|
15
|
+
json_content = content.lines.reject { |line| line.strip.start_with?("//") }.join
|
|
16
|
+
JSON.parse(json_content)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def save_config(config)
|
|
20
|
+
File.write(WAYBAR_CONFIG, JSON.pretty_generate(config))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
config = load_config
|
|
24
|
+
|
|
25
|
+
# Remove any existing deployment module
|
|
26
|
+
config["modules-center"]&.reject! { |m| m.to_s.include?("zillacore-deploy") }
|
|
27
|
+
config["modules-right"]&.reject! { |m| m.to_s.include?("zillacore-deploy") }
|
|
28
|
+
config.delete("custom/zillacore-deployments")
|
|
29
|
+
|
|
30
|
+
# Move agent session module from modules-right to modules-center (after indicators)
|
|
31
|
+
if config["modules-right"]&.delete("custom/zillacore")
|
|
32
|
+
config["modules-center"] ||= []
|
|
33
|
+
config["modules-center"] << "custom/zillacore" unless config["modules-center"].include?("custom/zillacore")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Add deployments module right before agent sessions in modules-center
|
|
37
|
+
center = config["modules-center"] || []
|
|
38
|
+
zc_idx = center.index("custom/zillacore")
|
|
39
|
+
if zc_idx
|
|
40
|
+
center.insert(zc_idx, "custom/zillacore-deployments") unless center.include?("custom/zillacore-deployments")
|
|
41
|
+
else
|
|
42
|
+
center << "custom/zillacore-deployments" unless center.include?("custom/zillacore-deployments")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Add module config
|
|
46
|
+
config["custom/zillacore-deployments"] = {
|
|
47
|
+
"exec" => DEPLOY_SCRIPT,
|
|
48
|
+
"return-type" => "json",
|
|
49
|
+
"interval" => 30,
|
|
50
|
+
"format" => "{}",
|
|
51
|
+
"tooltip" => true,
|
|
52
|
+
"format-alt" => "{}",
|
|
53
|
+
"escape" => false,
|
|
54
|
+
"on-click" => "#{DEPLOY_SCRIPT} --click",
|
|
55
|
+
"on-click-right" => "#{DEPLOY_SCRIPT} --deploy"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
save_config(config)
|
|
59
|
+
|
|
60
|
+
# Add CSS for the deployments module
|
|
61
|
+
style = File.read(WAYBAR_STYLE)
|
|
62
|
+
unless style.include?("#custom-zillacore-deployments")
|
|
63
|
+
css = <<~CSS
|
|
64
|
+
|
|
65
|
+
/* ZillaCore deployment environment dots */
|
|
66
|
+
#custom-zillacore-deployments {
|
|
67
|
+
margin-left: 100px;
|
|
68
|
+
margin-right: 40px;
|
|
69
|
+
font-size: 14px;
|
|
70
|
+
}
|
|
71
|
+
CSS
|
|
72
|
+
File.write(WAYBAR_STYLE, style + css)
|
|
73
|
+
puts "✓ Added deployment styles to waybar CSS"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Add padding-right to agent sessions module
|
|
77
|
+
unless style.include?("padding-right") && style.include?("#custom-zillacore")
|
|
78
|
+
updated_style = File.read(WAYBAR_STYLE)
|
|
79
|
+
if updated_style.include?("#custom-zillacore {")
|
|
80
|
+
updated_style.sub!(/(#custom-zillacore\s*\{[^}]*)(\})/) do
|
|
81
|
+
block = Regexp.last_match(1)
|
|
82
|
+
close = Regexp.last_match(2)
|
|
83
|
+
if block.include?("padding-right")
|
|
84
|
+
"#{block}#{close}"
|
|
85
|
+
else
|
|
86
|
+
"#{block}\n padding-right: 100px;\n#{close}"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
File.write(WAYBAR_STYLE, updated_style)
|
|
90
|
+
puts "✓ Added padding-right to agent session module"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
puts "✓ Deployments module added to waybar config"
|
|
95
|
+
puts " Positioned: [deploy-dots] [agent-sessions] in center bar"
|
|
96
|
+
puts " Restart waybar to apply: omarchy restart waybar"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# One-time setup script to add ZillaCore module to waybar config
|
|
5
|
+
# Run this once, then the module will update dynamically without config rewrites
|
|
6
|
+
|
|
7
|
+
require "json"
|
|
8
|
+
require "fileutils"
|
|
9
|
+
|
|
10
|
+
WAYBAR_CONFIG = File.expand_path("~/.config/waybar/config.jsonc")
|
|
11
|
+
WAYBAR_SCRIPT = File.expand_path("~/.zillacore/bin/waybar-status")
|
|
12
|
+
|
|
13
|
+
# Create a wrapper script that resolves the running server's waybar.rb dynamically
|
|
14
|
+
wrapper_dir = File.expand_path("~/.zillacore/bin")
|
|
15
|
+
FileUtils.mkdir_p(wrapper_dir)
|
|
16
|
+
wrapper_path = File.join(wrapper_dir, "waybar-status")
|
|
17
|
+
File.write(wrapper_path, <<~SCRIPT)
|
|
18
|
+
#!/usr/bin/env ruby
|
|
19
|
+
# Resolves the running ZillaCore server's waybar module dynamically.
|
|
20
|
+
# This allows worktrees / branches to work without reconfiguring waybar.
|
|
21
|
+
|
|
22
|
+
root_file = File.expand_path("~/.zillacore/server.root")
|
|
23
|
+
if File.exist?(root_file)
|
|
24
|
+
server_root = File.read(root_file).strip
|
|
25
|
+
waybar_script = File.join(server_root, "monitor", "waybar.rb")
|
|
26
|
+
if File.exist?(waybar_script)
|
|
27
|
+
load waybar_script
|
|
28
|
+
exit
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Fallback: no server root known, try the API directly
|
|
33
|
+
require "json"
|
|
34
|
+
require "net/http"
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
uri = URI("http://localhost:4567/api/status")
|
|
38
|
+
response = Net::HTTP.get_response(uri)
|
|
39
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
40
|
+
data = JSON.parse(response.body)
|
|
41
|
+
sessions = data["sessions"] || []
|
|
42
|
+
if sessions.empty?
|
|
43
|
+
puts({ text: "💤", tooltip: "No active agent sessions", class: "idle" }.to_json)
|
|
44
|
+
else
|
|
45
|
+
puts({ text: "🟢 \#{sessions.size}", tooltip: sessions.map { |s| s["agent"] }.join(", "), class: "working" }.to_json)
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
puts({ text: "⚠️", tooltip: "ZillaCore Error: HTTP \#{response.code}", class: "error" }.to_json)
|
|
49
|
+
end
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
puts({ text: "⚠️", tooltip: "ZillaCore Error: \#{e.message}", class: "error" }.to_json)
|
|
52
|
+
end
|
|
53
|
+
SCRIPT
|
|
54
|
+
File.chmod(0o755, wrapper_path)
|
|
55
|
+
|
|
56
|
+
def load_config
|
|
57
|
+
content = File.read(WAYBAR_CONFIG)
|
|
58
|
+
# Strip comments for JSON parsing
|
|
59
|
+
json_content = content.lines.reject { |line| line.strip.start_with?("//") }.join
|
|
60
|
+
JSON.parse(json_content)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def save_config(config)
|
|
64
|
+
File.write(WAYBAR_CONFIG, JSON.pretty_generate(config))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Load current config
|
|
68
|
+
config = load_config
|
|
69
|
+
|
|
70
|
+
# Remove old zillacore modules if they exist (from all module arrays)
|
|
71
|
+
%w[modules-left modules-center modules-right].each do |section|
|
|
72
|
+
next unless config[section].is_a?(Array)
|
|
73
|
+
|
|
74
|
+
config[section].reject! { |m| ["custom/zillacore", "group/zillacore-agents"].include?(m.to_s) }
|
|
75
|
+
end
|
|
76
|
+
config.each_key do |key|
|
|
77
|
+
config.delete(key) if ["custom/zillacore", "group/zillacore-agents"].include?(key.to_s)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Add single dynamic module at the end of modules-center (after deploy envs)
|
|
81
|
+
config["modules-center"] ||= []
|
|
82
|
+
config["modules-center"].push("custom/zillacore")
|
|
83
|
+
|
|
84
|
+
# Add module config
|
|
85
|
+
config["custom/zillacore"] = {
|
|
86
|
+
"exec" => WAYBAR_SCRIPT,
|
|
87
|
+
"return-type" => "json",
|
|
88
|
+
"interval" => 3,
|
|
89
|
+
"format" => "{}",
|
|
90
|
+
"tooltip" => true,
|
|
91
|
+
"on-click" => File.expand_path("~/.zillacore/bin/waybar-logs").to_s
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Create on-click wrapper too
|
|
95
|
+
logs_wrapper = File.expand_path("~/.zillacore/bin/waybar-logs")
|
|
96
|
+
File.write(logs_wrapper, <<~SCRIPT)
|
|
97
|
+
#!/usr/bin/env ruby
|
|
98
|
+
root_file = File.expand_path("~/.zillacore/server.root")
|
|
99
|
+
if File.exist?(root_file)
|
|
100
|
+
server_root = File.read(root_file).strip
|
|
101
|
+
script = File.join(server_root, "monitor", "view-logs-rofi.rb")
|
|
102
|
+
exec("ruby", script) if File.exist?(script)
|
|
103
|
+
end
|
|
104
|
+
warn "ZillaCore server root not found"
|
|
105
|
+
SCRIPT
|
|
106
|
+
File.chmod(0o755, logs_wrapper)
|
|
107
|
+
|
|
108
|
+
# Save updated config
|
|
109
|
+
save_config(config)
|
|
110
|
+
|
|
111
|
+
puts "✓ ZillaCore module added to waybar config"
|
|
112
|
+
puts " Module will update every 3 seconds without config rewrites"
|
|
113
|
+
puts " Restart waybar to apply: omarchy restart waybar"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# One-time setup: symlinks the ZillaCore xbar plugin into xbar's plugin directory
|
|
5
|
+
# Run this once on macOS after installing xbar
|
|
6
|
+
|
|
7
|
+
require "fileutils"
|
|
8
|
+
|
|
9
|
+
XBAR_PLUGIN_DIR = File.expand_path("~/Library/Application Support/xbar/plugins")
|
|
10
|
+
PLUGIN_SOURCE = File.expand_path("xbar.3s.rb", __dir__)
|
|
11
|
+
PLUGIN_DEST = File.join(XBAR_PLUGIN_DIR, "zillacore.3s.rb")
|
|
12
|
+
|
|
13
|
+
unless RUBY_PLATFORM.match?(/darwin/i)
|
|
14
|
+
puts "⚠ This script is for macOS only (xbar doesn't run on Linux)"
|
|
15
|
+
exit 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
unless File.directory?(XBAR_PLUGIN_DIR)
|
|
19
|
+
puts "⚠ xbar plugin directory not found: #{XBAR_PLUGIN_DIR}"
|
|
20
|
+
puts " Install xbar first: https://xbarapp.com"
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if File.exist?(PLUGIN_DEST)
|
|
25
|
+
puts "Removing existing plugin at #{PLUGIN_DEST}"
|
|
26
|
+
File.delete(PLUGIN_DEST)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
File.symlink(PLUGIN_SOURCE, PLUGIN_DEST)
|
|
30
|
+
File.chmod(0o755, PLUGIN_SOURCE)
|
|
31
|
+
|
|
32
|
+
puts "✓ ZillaCore xbar plugin installed"
|
|
33
|
+
puts " #{PLUGIN_SOURCE} → #{PLUGIN_DEST}"
|
|
34
|
+
puts " Refresh interval: 3 seconds"
|
|
35
|
+
puts " Restart xbar to activate"
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# ZillaCore macOS Log Viewer
|
|
5
|
+
# Opens agent log files in a terminal (wezterm by default, configurable via waybar.json)
|
|
6
|
+
# Set "log_viewer_command" in ~/.zillacore/waybar.json to override (supports wezterm/iTerm/Terminal.app)
|
|
7
|
+
# Uses macOS notifications via osascript for status messages
|
|
8
|
+
|
|
9
|
+
require "json"
|
|
10
|
+
require "socket"
|
|
11
|
+
|
|
12
|
+
SOCKET_PATH = "/tmp/zillacore-monitor.sock"
|
|
13
|
+
CONFIG_PATH = File.expand_path("~/.zillacore/waybar.json")
|
|
14
|
+
|
|
15
|
+
# Load agent configuration from JSON
|
|
16
|
+
# Returns [agents_hash, default_emoji] tuple
|
|
17
|
+
def load_agent_config
|
|
18
|
+
config = JSON.parse(File.read(CONFIG_PATH))
|
|
19
|
+
agents = {}
|
|
20
|
+
config["agents"].each do |agent|
|
|
21
|
+
agents[agent["name"].downcase] = agent["emoji"]
|
|
22
|
+
end
|
|
23
|
+
default_emoji = config["default_emoji"] || "❓"
|
|
24
|
+
[agents, default_emoji]
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
warn "Failed to load waybar.json: #{e.message}"
|
|
27
|
+
[{}, "❓"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
AGENTS, DEFAULT_EMOJI = load_agent_config
|
|
31
|
+
|
|
32
|
+
# Read agent state from daemon socket
|
|
33
|
+
# Returns hash with sessions/count/last_update, or error hash if daemon unavailable
|
|
34
|
+
def fetch_state
|
|
35
|
+
socket = UNIXSocket.new(SOCKET_PATH)
|
|
36
|
+
data = socket.read
|
|
37
|
+
socket.close
|
|
38
|
+
JSON.parse(data)
|
|
39
|
+
rescue Errno::ENOENT
|
|
40
|
+
{ "sessions" => [], "count" => 0, "error" => "daemon not running" }
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
{ "sessions" => [], "count" => 0, "error" => e.message }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def format_elapsed(seconds)
|
|
46
|
+
return "#{seconds}s" if seconds < 60
|
|
47
|
+
|
|
48
|
+
minutes = seconds / 60
|
|
49
|
+
return "#{minutes}m" if minutes < 60
|
|
50
|
+
|
|
51
|
+
hours = minutes / 60
|
|
52
|
+
"#{hours}h"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Send a macOS notification via osascript
|
|
56
|
+
# Silently fails if osascript is unavailable
|
|
57
|
+
def notify(title, message)
|
|
58
|
+
escaped_title = title.gsub('"', '\\"')
|
|
59
|
+
escaped_message = message.gsub('"', '\\"')
|
|
60
|
+
system("osascript", "-e", "display notification \"#{escaped_message}\" with title \"#{escaped_title}\"")
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
warn "Notification failed: #{e.message}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def format_context(card_key)
|
|
66
|
+
if card_key.start_with?("discord-")
|
|
67
|
+
"Discord chat"
|
|
68
|
+
elsif card_key.start_with?("card-")
|
|
69
|
+
card_key.split("-")[1]
|
|
70
|
+
else
|
|
71
|
+
card_key
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def load_log_viewer_command
|
|
76
|
+
config = JSON.parse(File.read(CONFIG_PATH))
|
|
77
|
+
config["log_viewer_command"]
|
|
78
|
+
rescue StandardError
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
DEFAULT_LOG_VIEWER = "/opt/homebrew/bin/wezterm"
|
|
83
|
+
LOG_VIEWER_COMMAND = load_log_viewer_command || DEFAULT_LOG_VIEWER
|
|
84
|
+
|
|
85
|
+
# Find an existing wezterm pane that's already tailing the given log file
|
|
86
|
+
def find_wezterm_pane_for(log_file)
|
|
87
|
+
json = `#{LOG_VIEWER_COMMAND} cli list --format json 2>/dev/null`
|
|
88
|
+
panes = JSON.parse(json)
|
|
89
|
+
panes.find { |p| p["title"]&.include?(log_file) || p["cwd"]&.include?(log_file) }
|
|
90
|
+
rescue StandardError
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Get the window ID of the first wezterm window
|
|
95
|
+
def find_wezterm_window_id
|
|
96
|
+
json = `#{LOG_VIEWER_COMMAND} cli list --format json 2>/dev/null`
|
|
97
|
+
panes = JSON.parse(json)
|
|
98
|
+
panes.first&.dig("window_id")
|
|
99
|
+
rescue StandardError
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def open_log(log_file)
|
|
104
|
+
escaped_path = log_file.gsub("'", "'\\\\''")
|
|
105
|
+
|
|
106
|
+
if LOG_VIEWER_COMMAND.include?("wezterm")
|
|
107
|
+
wezterm_running = system("pgrep -qf WezTerm")
|
|
108
|
+
if wezterm_running
|
|
109
|
+
# Check if this log is already being tailed in an existing tab
|
|
110
|
+
existing_pane = find_wezterm_pane_for(log_file)
|
|
111
|
+
if existing_pane
|
|
112
|
+
system(LOG_VIEWER_COMMAND, "cli", "activate-pane", "--pane-id", existing_pane["pane_id"].to_s)
|
|
113
|
+
else
|
|
114
|
+
# Spawn as a new tab in the first available window
|
|
115
|
+
window_id = find_wezterm_window_id
|
|
116
|
+
args = [LOG_VIEWER_COMMAND, "cli", "spawn"]
|
|
117
|
+
args += ["--window-id", window_id.to_s] if window_id
|
|
118
|
+
args += ["--", "tail", "-f", log_file]
|
|
119
|
+
system(*args)
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
system(LOG_VIEWER_COMMAND, "start", "--", "tail", "-f", log_file)
|
|
123
|
+
sleep 0.5 # Give wezterm time to launch before trying to activate
|
|
124
|
+
end
|
|
125
|
+
system("open", "-a", "WezTerm")
|
|
126
|
+
system("osascript", "-e", 'tell application "System Events" to set frontmost of process "WezTerm" to true')
|
|
127
|
+
elsif LOG_VIEWER_COMMAND.include?("iTerm")
|
|
128
|
+
script = <<~APPLESCRIPT
|
|
129
|
+
tell application "iTerm"
|
|
130
|
+
activate
|
|
131
|
+
create window with default profile command "tail -f '#{escaped_path}'"
|
|
132
|
+
end tell
|
|
133
|
+
APPLESCRIPT
|
|
134
|
+
system("osascript", "-e", script)
|
|
135
|
+
elsif LOG_VIEWER_COMMAND.include?("Terminal")
|
|
136
|
+
system("osascript", "-e", "tell application \"Terminal\" to do script \"tail -f '#{escaped_path}'\"")
|
|
137
|
+
system("osascript", "-e", 'tell application "Terminal" to activate')
|
|
138
|
+
else
|
|
139
|
+
system(LOG_VIEWER_COMMAND, "tail", "-f", log_file)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# --- Main invocation logic ---
|
|
144
|
+
|
|
145
|
+
# Mode 1: xbar submenu click — log file path passed as param1 (ARGV[0])
|
|
146
|
+
if ARGV[0]
|
|
147
|
+
log_file = ARGV[0]
|
|
148
|
+
unless File.exist?(log_file)
|
|
149
|
+
notify("ZillaCore", "Log file not found: #{log_file}")
|
|
150
|
+
exit 1
|
|
151
|
+
end
|
|
152
|
+
open_log(log_file)
|
|
153
|
+
exit 0
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Mode 2+: standalone invocation — fetch state from daemon
|
|
157
|
+
state = fetch_state
|
|
158
|
+
sessions = state["sessions"] || []
|
|
159
|
+
|
|
160
|
+
if state["error"]
|
|
161
|
+
notify("ZillaCore", state["error"])
|
|
162
|
+
exit 1
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if sessions.empty?
|
|
166
|
+
notify("ZillaCore", "No active agent sessions")
|
|
167
|
+
exit 0
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Single session — open directly
|
|
171
|
+
if sessions.size == 1
|
|
172
|
+
log_file = sessions[0]["log_file"]
|
|
173
|
+
if log_file && File.exist?(log_file)
|
|
174
|
+
open_log(log_file)
|
|
175
|
+
else
|
|
176
|
+
notify("ZillaCore", "Log file not found: #{log_file}")
|
|
177
|
+
end
|
|
178
|
+
exit 0
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Multiple sessions — use fzf if available
|
|
182
|
+
if system("which fzf > /dev/null 2>&1")
|
|
183
|
+
options = sessions.map do |s|
|
|
184
|
+
agent = s["agent"]
|
|
185
|
+
elapsed = format_elapsed(s["elapsed_seconds"])
|
|
186
|
+
context = format_context(s["card_key"])
|
|
187
|
+
emoji = AGENTS[agent.downcase] || DEFAULT_EMOJI
|
|
188
|
+
"#{emoji} #{agent}: #{context} (#{elapsed})|#{s["log_file"]}"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
menu_text = options.join("\n")
|
|
192
|
+
selected = `echo "#{menu_text}" | fzf --prompt="Agent Logs: "`.strip
|
|
193
|
+
|
|
194
|
+
unless selected.empty?
|
|
195
|
+
log_file = selected.split("|").last
|
|
196
|
+
if File.exist?(log_file)
|
|
197
|
+
open_log(log_file)
|
|
198
|
+
else
|
|
199
|
+
notify("ZillaCore", "Log file not found: #{log_file}")
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
else
|
|
203
|
+
# No fzf — open first session's log
|
|
204
|
+
log_file = sessions[0]["log_file"]
|
|
205
|
+
if log_file && File.exist?(log_file)
|
|
206
|
+
open_log(log_file)
|
|
207
|
+
else
|
|
208
|
+
notify("ZillaCore", "Log file not found: #{log_file}")
|
|
209
|
+
end
|
|
210
|
+
end
|