superkick 0.1.0
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
- data/CLA.md +91 -0
- data/CLAUDE.md +2226 -0
- data/CONTRIBUTING.md +104 -0
- data/LICENSE +108 -0
- data/LICENSE-COMMERCIAL.md +39 -0
- data/PLAN.md +161 -0
- data/README.md +1155 -0
- data/exe/superkick +6 -0
- data/lib/superkick/agent/runtime.rb +82 -0
- data/lib/superkick/agent/runtimes/local.rb +74 -0
- data/lib/superkick/agent/runtimes.rb +4 -0
- data/lib/superkick/agent.rb +209 -0
- data/lib/superkick/agent_store.rb +85 -0
- data/lib/superkick/attach/client.rb +245 -0
- data/lib/superkick/attach/protocol.rb +71 -0
- data/lib/superkick/attach/server.rb +371 -0
- data/lib/superkick/budget_checker.rb +120 -0
- data/lib/superkick/buffer/client.rb +91 -0
- data/lib/superkick/buffer/server.rb +127 -0
- data/lib/superkick/cli/agent.rb +524 -0
- data/lib/superkick/cli/completion.rb +591 -0
- data/lib/superkick/cli/goal.rb +71 -0
- data/lib/superkick/cli/mcp.rb +34 -0
- data/lib/superkick/cli/monitor.rb +47 -0
- data/lib/superkick/cli/notifier.rb +39 -0
- data/lib/superkick/cli/repository.rb +46 -0
- data/lib/superkick/cli/server.rb +106 -0
- data/lib/superkick/cli/setup.rb +166 -0
- data/lib/superkick/cli/spawner.rb +85 -0
- data/lib/superkick/cli/team.rb +407 -0
- data/lib/superkick/cli.rb +175 -0
- data/lib/superkick/client_registry.rb +30 -0
- data/lib/superkick/configuration.rb +178 -0
- data/lib/superkick/connection.rb +56 -0
- data/lib/superkick/control/client.rb +78 -0
- data/lib/superkick/control/reply.rb +43 -0
- data/lib/superkick/control/server.rb +1271 -0
- data/lib/superkick/cost_accumulator.rb +53 -0
- data/lib/superkick/cost_extractor.rb +65 -0
- data/lib/superkick/cost_poller.rb +70 -0
- data/lib/superkick/driver/profile_source.rb +134 -0
- data/lib/superkick/driver.rb +179 -0
- data/lib/superkick/drivers/claude_code.rb +110 -0
- data/lib/superkick/drivers/codex.rb +57 -0
- data/lib/superkick/drivers/copilot.rb +75 -0
- data/lib/superkick/drivers/gemini.rb +86 -0
- data/lib/superkick/drivers/goose.rb +74 -0
- data/lib/superkick/drivers.rb +16 -0
- data/lib/superkick/drop.rb +80 -0
- data/lib/superkick/drops.rb +76 -0
- data/lib/superkick/environment_executor.rb +90 -0
- data/lib/superkick/goal.rb +95 -0
- data/lib/superkick/goals/agent_exit.rb +41 -0
- data/lib/superkick/goals/agent_signal.rb +42 -0
- data/lib/superkick/goals/command.rb +103 -0
- data/lib/superkick/history_buffer.rb +38 -0
- data/lib/superkick/hosted/attach/bridge.rb +52 -0
- data/lib/superkick/hosted/attach/client.rb +208 -0
- data/lib/superkick/hosted/attach/relay.rb +313 -0
- data/lib/superkick/hosted/attach/relay_store.rb +48 -0
- data/lib/superkick/hosted/bridge.rb +263 -0
- data/lib/superkick/hosted/buffer/bridge.rb +42 -0
- data/lib/superkick/hosted/buffer/client.rb +63 -0
- data/lib/superkick/hosted/buffer/relay.rb +126 -0
- data/lib/superkick/hosted/buffer/relay_store.rb +42 -0
- data/lib/superkick/hosted/control/client.rb +84 -0
- data/lib/superkick/hosted/mcp_proxy.rb +144 -0
- data/lib/superkick/inject_handler.rb +24 -0
- data/lib/superkick/injection_guard.rb +26 -0
- data/lib/superkick/injection_queue.rb +177 -0
- data/lib/superkick/injector.rb +65 -0
- data/lib/superkick/input_buffer.rb +171 -0
- data/lib/superkick/integrations/bugsnag/README.md +98 -0
- data/lib/superkick/integrations/bugsnag/spawner.rb +307 -0
- data/lib/superkick/integrations/bugsnag/templates/error_opened.liquid +17 -0
- data/lib/superkick/integrations/bugsnag.rb +7 -0
- data/lib/superkick/integrations/circleci/README.md +75 -0
- data/lib/superkick/integrations/circleci/monitor.rb +185 -0
- data/lib/superkick/integrations/circleci/probe.rb +36 -0
- data/lib/superkick/integrations/circleci/templates/ci_failure.liquid +8 -0
- data/lib/superkick/integrations/circleci/templates/ci_success.liquid +1 -0
- data/lib/superkick/integrations/circleci.rb +8 -0
- data/lib/superkick/integrations/datadog/README.md +253 -0
- data/lib/superkick/integrations/datadog/alert_goal.rb +94 -0
- data/lib/superkick/integrations/datadog/alert_monitor.rb +163 -0
- data/lib/superkick/integrations/datadog/alert_spawner.rb +201 -0
- data/lib/superkick/integrations/datadog/notification_templates/default.liquid +10 -0
- data/lib/superkick/integrations/datadog/notifier.rb +294 -0
- data/lib/superkick/integrations/datadog/spawner.rb +201 -0
- data/lib/superkick/integrations/datadog/templates/alert_changed.liquid +8 -0
- data/lib/superkick/integrations/datadog/templates/alert_escalated.liquid +8 -0
- data/lib/superkick/integrations/datadog/templates/alert_recovered.liquid +14 -0
- data/lib/superkick/integrations/datadog/templates/alert_triggered.liquid +29 -0
- data/lib/superkick/integrations/datadog/templates/error_opened.liquid +15 -0
- data/lib/superkick/integrations/datadog.rb +14 -0
- data/lib/superkick/integrations/docker/README.md +256 -0
- data/lib/superkick/integrations/docker/client.rb +295 -0
- data/lib/superkick/integrations/docker/runtime.rb +218 -0
- data/lib/superkick/integrations/docker.rb +4 -0
- data/lib/superkick/integrations/git/repository_source.rb +66 -0
- data/lib/superkick/integrations/git/version_control.rb +119 -0
- data/lib/superkick/integrations/git.rb +8 -0
- data/lib/superkick/integrations/github/README.md +300 -0
- data/lib/superkick/integrations/github/check_failed_spawner.rb +199 -0
- data/lib/superkick/integrations/github/drops.rb +114 -0
- data/lib/superkick/integrations/github/goal.rb +135 -0
- data/lib/superkick/integrations/github/issue_goal.rb +104 -0
- data/lib/superkick/integrations/github/issue_spawner.rb +160 -0
- data/lib/superkick/integrations/github/monitor.rb +251 -0
- data/lib/superkick/integrations/github/probe.rb +30 -0
- data/lib/superkick/integrations/github/repository_source.rb +228 -0
- data/lib/superkick/integrations/github/templates/check_failed.liquid +10 -0
- data/lib/superkick/integrations/github/templates/ci_failure.liquid +5 -0
- data/lib/superkick/integrations/github/templates/ci_success.liquid +1 -0
- data/lib/superkick/integrations/github/templates/issue_opened.liquid +20 -0
- data/lib/superkick/integrations/github/templates/pr_comment.liquid +2 -0
- data/lib/superkick/integrations/github/templates/pr_review.liquid +4 -0
- data/lib/superkick/integrations/github.rb +16 -0
- data/lib/superkick/integrations/honeybadger/README.md +97 -0
- data/lib/superkick/integrations/honeybadger/notification_templates/default.liquid +8 -0
- data/lib/superkick/integrations/honeybadger/notifier.rb +250 -0
- data/lib/superkick/integrations/honeybadger/spawner.rb +214 -0
- data/lib/superkick/integrations/honeybadger/templates/error_opened.liquid +17 -0
- data/lib/superkick/integrations/honeybadger.rb +9 -0
- data/lib/superkick/integrations/shell/README.md +83 -0
- data/lib/superkick/integrations/shell/monitor.rb +87 -0
- data/lib/superkick/integrations/shell/templates/shell_alert.liquid +6 -0
- data/lib/superkick/integrations/shell/templates/shell_success.liquid +6 -0
- data/lib/superkick/integrations/shell.rb +7 -0
- data/lib/superkick/integrations/shortcut/README.md +193 -0
- data/lib/superkick/integrations/shortcut/drops.rb +91 -0
- data/lib/superkick/integrations/shortcut/monitor.rb +582 -0
- data/lib/superkick/integrations/shortcut/probe.rb +34 -0
- data/lib/superkick/integrations/shortcut/spawner.rb +264 -0
- data/lib/superkick/integrations/shortcut/templates/related_story_changed.liquid +6 -0
- data/lib/superkick/integrations/shortcut/templates/story_blocker.liquid +8 -0
- data/lib/superkick/integrations/shortcut/templates/story_comment.liquid +5 -0
- data/lib/superkick/integrations/shortcut/templates/story_description_changed.liquid +19 -0
- data/lib/superkick/integrations/shortcut/templates/story_owner_changed.liquid +10 -0
- data/lib/superkick/integrations/shortcut/templates/story_ready.liquid +41 -0
- data/lib/superkick/integrations/shortcut/templates/story_state_changed.liquid +9 -0
- data/lib/superkick/integrations/shortcut/templates/story_unblocked.liquid +5 -0
- data/lib/superkick/integrations/shortcut.rb +11 -0
- data/lib/superkick/integrations/slack/README.md +297 -0
- data/lib/superkick/integrations/slack/drops.rb +70 -0
- data/lib/superkick/integrations/slack/notifier.rb +426 -0
- data/lib/superkick/integrations/slack/spawner.rb +251 -0
- data/lib/superkick/integrations/slack/templates/default.liquid +17 -0
- data/lib/superkick/integrations/slack/templates/slack_reply.liquid +3 -0
- data/lib/superkick/integrations/slack/templates/spawn/slack_message.liquid +10 -0
- data/lib/superkick/integrations/slack/thread_monitor.rb +161 -0
- data/lib/superkick/integrations/slack.rb +12 -0
- data/lib/superkick/liquid.rb +129 -0
- data/lib/superkick/local/repository_source.rb +148 -0
- data/lib/superkick/mcp_server.rb +596 -0
- data/lib/superkick/monitor.rb +215 -0
- data/lib/superkick/notification_dispatcher.rb +280 -0
- data/lib/superkick/notifier.rb +173 -0
- data/lib/superkick/notifier_state_store.rb +55 -0
- data/lib/superkick/notifier_template.rb +121 -0
- data/lib/superkick/notifiers/command.rb +124 -0
- data/lib/superkick/notifiers/terminal_bell.rb +41 -0
- data/lib/superkick/output_logger.rb +54 -0
- data/lib/superkick/poller.rb +126 -0
- data/lib/superkick/process_runner.rb +87 -0
- data/lib/superkick/pty_proxy.rb +403 -0
- data/lib/superkick/registry.rb +75 -0
- data/lib/superkick/repository_source.rb +195 -0
- data/lib/superkick/server.rb +211 -0
- data/lib/superkick/session_recorder.rb +154 -0
- data/lib/superkick/setup.rb +160 -0
- data/lib/superkick/spawn/agent_spawner.rb +311 -0
- data/lib/superkick/spawn/approval_store.rb +113 -0
- data/lib/superkick/spawn/handler.rb +144 -0
- data/lib/superkick/spawn/injector.rb +119 -0
- data/lib/superkick/spawn/workflow_executor.rb +196 -0
- data/lib/superkick/spawn/workflow_validator.rb +77 -0
- data/lib/superkick/spawner.rb +67 -0
- data/lib/superkick/supervisor.rb +516 -0
- data/lib/superkick/team/artifact_store.rb +92 -0
- data/lib/superkick/team/log.rb +140 -0
- data/lib/superkick/team/log_entry_drop.rb +34 -0
- data/lib/superkick/team/log_monitor.rb +84 -0
- data/lib/superkick/team/log_notifier.rb +96 -0
- data/lib/superkick/team/log_store.rb +40 -0
- data/lib/superkick/template_filters.rb +24 -0
- data/lib/superkick/template_renderer.rb +223 -0
- data/lib/superkick/templates/team_log/planning_agent.liquid +38 -0
- data/lib/superkick/templates/team_log/team_digest.liquid +45 -0
- data/lib/superkick/templates/team_log/teammate_message.liquid +7 -0
- data/lib/superkick/templates/team_log/worker_kickoff.liquid +37 -0
- data/lib/superkick/templates/workflow/workflow_triggered.liquid +22 -0
- data/lib/superkick/version.rb +5 -0
- data/lib/superkick/version_control.rb +135 -0
- data/lib/superkick/yaml_config.rb +302 -0
- data/lib/superkick.rb +198 -0
- data/plan.md +267 -0
- metadata +404 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superkick
|
|
4
|
+
class CLI < Thor
|
|
5
|
+
class MonitorCmd < Thor
|
|
6
|
+
package_name "superkick monitor"
|
|
7
|
+
|
|
8
|
+
desc "list", "List available monitor types and probe-detected configs"
|
|
9
|
+
option :no_probes, type: :boolean, default: false,
|
|
10
|
+
desc: "Skip running environment probes"
|
|
11
|
+
def list
|
|
12
|
+
Superkick::Monitor.registered.each do |type, klass|
|
|
13
|
+
$stdout.puts "#{type}:"
|
|
14
|
+
$stdout.puts " #{klass.description}" if klass.description
|
|
15
|
+
$stdout.puts " required config: #{klass.required_config.join(", ")}" unless klass.required_config.empty?
|
|
16
|
+
$stdout.puts " events: #{klass.event_types.join(", ")}" unless klass.event_types.empty?
|
|
17
|
+
|
|
18
|
+
probe_klass = klass.probe_class
|
|
19
|
+
if probe_klass
|
|
20
|
+
desc = probe_klass.respond_to?(:description) ? probe_klass.description : nil
|
|
21
|
+
$stdout.puts " probe: #{desc || "available"}"
|
|
22
|
+
end
|
|
23
|
+
$stdout.puts ""
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
unless options[:no_probes]
|
|
27
|
+
detected = Superkick::Monitor.detect_all(dir: Dir.pwd)
|
|
28
|
+
unless detected.empty?
|
|
29
|
+
$stdout.puts "Detected in current environment:"
|
|
30
|
+
detected.each do |name, config|
|
|
31
|
+
display_config = config.except("type")
|
|
32
|
+
$stdout.puts " #{name} (type: #{config["type"] || name}): #{display_config.map { |k, v| "#{k}=#{v}" }.join(", ")}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "install-templates", "Copy default monitor ERB templates to ~/.superkick/templates/"
|
|
39
|
+
option :force, type: :boolean, default: false, desc: "Overwrite existing templates"
|
|
40
|
+
def install_templates
|
|
41
|
+
TemplateRenderer.install_defaults(force: options[:force])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
default_command :list
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superkick
|
|
4
|
+
class CLI < Thor
|
|
5
|
+
class NotifierCmd < Thor
|
|
6
|
+
package_name "superkick notifier"
|
|
7
|
+
|
|
8
|
+
desc "list", "List available notifier types"
|
|
9
|
+
def list
|
|
10
|
+
client = Control.client_from
|
|
11
|
+
|
|
12
|
+
unless client.alive?
|
|
13
|
+
$stdout.puts "Superkick server is not running."
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
result = client.request("discover_notifiers")
|
|
18
|
+
notifiers = result[:available_notifiers] || []
|
|
19
|
+
|
|
20
|
+
if notifiers.empty?
|
|
21
|
+
$stdout.puts "No notifier types registered."
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
notifiers.each do |notifier|
|
|
26
|
+
label = notifier[:type].to_s
|
|
27
|
+
label += " (stateful)" if notifier[:stateful]
|
|
28
|
+
$stdout.puts "#{label}:"
|
|
29
|
+
$stdout.puts " #{notifier[:description]}" if notifier[:description]
|
|
30
|
+
$stdout.puts ""
|
|
31
|
+
end
|
|
32
|
+
rescue Control::Client::ServerUnavailable
|
|
33
|
+
$stdout.puts "Superkick server is not running."
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
default_command :list
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superkick
|
|
4
|
+
class CLI < Thor
|
|
5
|
+
class RepositoryCmd < Thor
|
|
6
|
+
package_name "superkick repository"
|
|
7
|
+
|
|
8
|
+
desc "list", "List configured repositories"
|
|
9
|
+
option :json, type: :boolean, default: false, desc: "Output as JSON"
|
|
10
|
+
def list
|
|
11
|
+
client = Control.client_from
|
|
12
|
+
|
|
13
|
+
unless client.alive?
|
|
14
|
+
$stdout.puts "Superkick server is not running."
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
result = client.request("discover_repositories")
|
|
19
|
+
repositories = result[:repositories] || []
|
|
20
|
+
|
|
21
|
+
if options[:json]
|
|
22
|
+
$stdout.puts JSON.pretty_generate(repositories)
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if repositories.empty?
|
|
27
|
+
$stdout.puts "No repositories configured."
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
repositories.each do |repo|
|
|
32
|
+
$stdout.puts "#{repo[:name]}:"
|
|
33
|
+
$stdout.puts " path: #{repo[:path]}" if repo[:path]
|
|
34
|
+
$stdout.puts " url: #{repo[:url]}" if repo[:url]
|
|
35
|
+
$stdout.puts " vcs: #{repo[:version_control]}" if repo[:version_control]
|
|
36
|
+
$stdout.puts " dependencies: #{repo[:dependencies].join(", ")}" if repo[:dependencies]&.any?
|
|
37
|
+
$stdout.puts ""
|
|
38
|
+
end
|
|
39
|
+
rescue Control::Client::ServerUnavailable
|
|
40
|
+
$stdout.puts "Superkick server is not running."
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
default_command :list
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superkick
|
|
4
|
+
class CLI < Thor
|
|
5
|
+
class Server < Thor
|
|
6
|
+
package_name "superkick server"
|
|
7
|
+
|
|
8
|
+
desc "start", "Start the Superkick server"
|
|
9
|
+
option :daemonize, type: :boolean, aliases: "-d", default: false,
|
|
10
|
+
desc: "Run in the background"
|
|
11
|
+
def start
|
|
12
|
+
Superkick::Server.start(daemonize: options[:daemonize])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "stop", "Send SIGTERM to the Superkick server"
|
|
16
|
+
def stop
|
|
17
|
+
unless File.exist?(Superkick.config.pid_path)
|
|
18
|
+
warn "Superkick server is not running (no PID file)."
|
|
19
|
+
exit(1)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
pid = File.read(Superkick.config.pid_path).strip.to_i
|
|
23
|
+
Process.kill("TERM", pid)
|
|
24
|
+
$stdout.puts "Sent SIGTERM to PID #{pid}."
|
|
25
|
+
rescue Errno::ESRCH
|
|
26
|
+
warn "Process #{pid} not found — removing stale PID file."
|
|
27
|
+
FileUtils.rm_f(Superkick.config.pid_path)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc "status", "Show server and agent status"
|
|
31
|
+
def status
|
|
32
|
+
client = Control.client_from
|
|
33
|
+
|
|
34
|
+
unless client.alive?
|
|
35
|
+
$stdout.puts "Superkick server is not running."
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
ping = client.request("ping")
|
|
40
|
+
agents = client.request("list_agents")[:agents] || []
|
|
41
|
+
spawners = client.request("list_spawners")[:spawners] || []
|
|
42
|
+
|
|
43
|
+
$stdout.puts "Superkick server running (v#{ping[:version]}, PID #{server_pid})"
|
|
44
|
+
$stdout.puts ""
|
|
45
|
+
|
|
46
|
+
if spawners.any?
|
|
47
|
+
$stdout.puts "Spawners:"
|
|
48
|
+
spawners.each do |sp|
|
|
49
|
+
$stdout.puts " #{sp[:name]} (#{sp[:type]}): #{sp[:status]}"
|
|
50
|
+
end
|
|
51
|
+
$stdout.puts ""
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if agents.empty?
|
|
55
|
+
$stdout.puts " No active agents."
|
|
56
|
+
else
|
|
57
|
+
spawned, interactive = agents.partition { |s| s[:spawn_info] }
|
|
58
|
+
|
|
59
|
+
if interactive.any?
|
|
60
|
+
$stdout.puts "Interactive agents:"
|
|
61
|
+
interactive.each { print_status_line(it) }
|
|
62
|
+
$stdout.puts ""
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if spawned.any?
|
|
66
|
+
$stdout.puts "Spawned agents:"
|
|
67
|
+
spawned.each do |s|
|
|
68
|
+
origin = s[:spawn_info][:spawner_name]
|
|
69
|
+
print_status_line(s, suffix: "via #{origin}")
|
|
70
|
+
end
|
|
71
|
+
$stdout.puts ""
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
rescue Control::Client::ServerUnavailable
|
|
75
|
+
$stdout.puts "Superkick server is not running."
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
desc "log", "Tail the server log"
|
|
79
|
+
option :lines, type: :numeric, aliases: "-n", default: 50,
|
|
80
|
+
desc: "Number of lines to show"
|
|
81
|
+
def log
|
|
82
|
+
unless File.exist?(Superkick.config.log_path)
|
|
83
|
+
warn "Log file not found: #{Superkick.config.log_path}"
|
|
84
|
+
exit(1)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
system("tail", "-n", options[:lines].to_s, "-f", Superkick.config.log_path)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
default_command :start
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def print_status_line(s, suffix: nil)
|
|
95
|
+
buf = s[:has_buffer] ? "pty-connected" : "no-pty"
|
|
96
|
+
line = " Agent #{s[:agent_id]}: #{s[:monitor_count]} monitor(s) [#{buf}]"
|
|
97
|
+
line += " (#{suffix})" if suffix
|
|
98
|
+
$stdout.puts line
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def server_pid
|
|
102
|
+
File.exist?(Superkick.config.pid_path) ? File.read(Superkick.config.pid_path).strip : "unknown"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-prompt"
|
|
4
|
+
|
|
5
|
+
module Superkick
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
class Setup < Thor
|
|
8
|
+
package_name "superkick setup"
|
|
9
|
+
|
|
10
|
+
desc "start", "Interactive first-time setup"
|
|
11
|
+
long_desc <<~DESC
|
|
12
|
+
Walks you through creating a Superkick configuration file, setting up
|
|
13
|
+
MCP integration with your AI CLI, and installing shell completions.
|
|
14
|
+
|
|
15
|
+
Each integration provides a well-documented config snippet with inline
|
|
16
|
+
comments explaining every option. The generated config.yml is ready to
|
|
17
|
+
use and easy to evolve.
|
|
18
|
+
DESC
|
|
19
|
+
option :driver, type: :string, desc: "AI CLI driver (skip driver prompt)"
|
|
20
|
+
option :force, type: :boolean, default: false, desc: "Overwrite existing config"
|
|
21
|
+
option :skip_mcp, type: :boolean, default: false, desc: "Skip MCP configuration"
|
|
22
|
+
option :skip_completions, type: :boolean, default: false, desc: "Skip shell completion offer"
|
|
23
|
+
def start
|
|
24
|
+
@prompt = TTY::Prompt.new
|
|
25
|
+
@setup = Superkick::Setup.new
|
|
26
|
+
|
|
27
|
+
say_welcome
|
|
28
|
+
return unless check_existing_config
|
|
29
|
+
|
|
30
|
+
driver = choose_driver
|
|
31
|
+
return unless driver
|
|
32
|
+
|
|
33
|
+
monitors = choose_items("monitors", @setup.available_monitors)
|
|
34
|
+
spawners = choose_items("spawners", @setup.available_spawners)
|
|
35
|
+
notifiers = choose_items("notifications", @setup.available_notifiers, defaults: [:terminal_bell])
|
|
36
|
+
repository_sources = choose_items("repository sources", @setup.available_repository_sources)
|
|
37
|
+
|
|
38
|
+
write_config(driver:, monitors:, spawners:, notifiers:, repository_sources:)
|
|
39
|
+
configure_mcp(driver) unless options[:skip_mcp]
|
|
40
|
+
offer_completions unless options[:skip_completions]
|
|
41
|
+
say_next_steps
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
default_command :start
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def say_welcome
|
|
49
|
+
$stdout.puts ""
|
|
50
|
+
$stdout.puts "Welcome to Superkick setup!"
|
|
51
|
+
$stdout.puts ""
|
|
52
|
+
$stdout.puts "This will create a config file at #{config_path}"
|
|
53
|
+
$stdout.puts "and configure your AI CLI to use Superkick's MCP server."
|
|
54
|
+
$stdout.puts ""
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def config_path
|
|
58
|
+
File.join(Superkick.config.base_dir, "config.yml")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def check_existing_config
|
|
62
|
+
return true unless File.exist?(config_path)
|
|
63
|
+
return true if options[:force]
|
|
64
|
+
|
|
65
|
+
@prompt.yes?("#{config_path} already exists. Overwrite?")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def choose_driver
|
|
69
|
+
if options[:driver]
|
|
70
|
+
driver_name = options[:driver].to_sym
|
|
71
|
+
begin
|
|
72
|
+
Driver.lookup(driver_name)
|
|
73
|
+
return driver_name
|
|
74
|
+
rescue ArgumentError
|
|
75
|
+
warn "Unknown driver: #{options[:driver]}"
|
|
76
|
+
return nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
drivers = @setup.detect_drivers
|
|
81
|
+
choices = drivers.map do |d|
|
|
82
|
+
label = d[:name].to_s
|
|
83
|
+
label += " (#{d[:cli_command]})" if d[:cli_command] != d[:name].to_s
|
|
84
|
+
label += " ✓" if d[:installed]
|
|
85
|
+
{name: label, value: d[:name]}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
@prompt.select("Which AI CLI are you using?", choices)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def choose_items(label, available, defaults: [])
|
|
92
|
+
return [] if available.empty?
|
|
93
|
+
|
|
94
|
+
choices = available.map do |item|
|
|
95
|
+
desc = item[:description]&.split(".")&.first
|
|
96
|
+
choice_label = item[:label]
|
|
97
|
+
choice_label += " — #{desc}" if desc
|
|
98
|
+
{name: choice_label, value: item[:type]}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
default_indices = defaults.filter_map do |d|
|
|
102
|
+
choices.index { it[:value] == d }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
$stdout.puts ""
|
|
106
|
+
@prompt.multi_select(
|
|
107
|
+
"Which #{label} would you like to configure?",
|
|
108
|
+
choices,
|
|
109
|
+
default: default_indices.map { it + 1 }
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def write_config(driver:, monitors:, spawners:, notifiers:, repository_sources:)
|
|
114
|
+
config_content = @setup.generate_config(
|
|
115
|
+
driver:,
|
|
116
|
+
monitors:,
|
|
117
|
+
spawners:,
|
|
118
|
+
notifiers:,
|
|
119
|
+
repository_sources:
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
FileUtils.mkdir_p(Superkick.config.base_dir)
|
|
123
|
+
File.write(config_path, config_content)
|
|
124
|
+
|
|
125
|
+
$stdout.puts ""
|
|
126
|
+
$stdout.puts "Wrote #{config_path}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def configure_mcp(driver_name)
|
|
130
|
+
return unless @prompt.yes?("\nConfigure MCP integration for #{driver_name}?")
|
|
131
|
+
|
|
132
|
+
Superkick.load_config!
|
|
133
|
+
Superkick.driver.install_mcp(exe_path: File.expand_path($PROGRAM_NAME))
|
|
134
|
+
$stdout.puts "MCP configured."
|
|
135
|
+
rescue => e
|
|
136
|
+
warn "MCP configuration failed: #{e.message}"
|
|
137
|
+
warn "You can run `superkick mcp configure` later."
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def offer_completions
|
|
141
|
+
shell = File.basename(ENV.fetch("SHELL", "bash"))
|
|
142
|
+
return unless %w[bash zsh].include?(shell)
|
|
143
|
+
|
|
144
|
+
$stdout.puts ""
|
|
145
|
+
$stdout.puts "To enable shell completions, add this to your .#{shell}rc:"
|
|
146
|
+
$stdout.puts ""
|
|
147
|
+
$stdout.puts " eval \"$(superkick completion #{shell})\""
|
|
148
|
+
$stdout.puts ""
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def say_next_steps
|
|
152
|
+
$stdout.puts "Setup complete! Next steps:"
|
|
153
|
+
$stdout.puts ""
|
|
154
|
+
$stdout.puts " 1. Review and edit #{config_path}"
|
|
155
|
+
$stdout.puts " (fill in tokens, adjust settings)"
|
|
156
|
+
$stdout.puts ""
|
|
157
|
+
$stdout.puts " 2. Start the server:"
|
|
158
|
+
$stdout.puts " superkick server start -d"
|
|
159
|
+
$stdout.puts ""
|
|
160
|
+
$stdout.puts " 3. Launch your agent:"
|
|
161
|
+
$stdout.puts " superkick agent start"
|
|
162
|
+
$stdout.puts ""
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superkick
|
|
4
|
+
class CLI < Thor
|
|
5
|
+
class Spawner < Thor
|
|
6
|
+
package_name "superkick spawner"
|
|
7
|
+
|
|
8
|
+
desc "list", "List configured spawner monitors and their status"
|
|
9
|
+
option :json, type: :boolean, default: false, desc: "Output as JSON"
|
|
10
|
+
def list
|
|
11
|
+
client = Control.client_from
|
|
12
|
+
|
|
13
|
+
unless client.alive?
|
|
14
|
+
$stdout.puts "Superkick server is not running."
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
resp = client.request("list_spawners")
|
|
19
|
+
spawners = resp[:spawners] || []
|
|
20
|
+
|
|
21
|
+
if options[:json]
|
|
22
|
+
$stdout.puts JSON.pretty_generate(spawners)
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if spawners.empty?
|
|
27
|
+
$stdout.puts "No spawners configured."
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
spawners.each do |sp|
|
|
32
|
+
line = "#{sp[:name]} (type: #{sp[:type]}) [#{sp[:status]}]"
|
|
33
|
+
workflow_parts = []
|
|
34
|
+
workflow_parts << "on_complete: #{sp[:on_complete_target]}" if sp[:on_complete_target]
|
|
35
|
+
workflow_parts << "on_fail: #{sp[:on_fail_target]}" if sp[:on_fail_target]
|
|
36
|
+
line += " -> #{workflow_parts.join(", ")}" if workflow_parts.any?
|
|
37
|
+
$stdout.puts line
|
|
38
|
+
$stdout.puts ""
|
|
39
|
+
end
|
|
40
|
+
rescue Control::Client::ServerUnavailable
|
|
41
|
+
$stdout.puts "Superkick server is not running."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc "start NAME", "Start a stopped spawner"
|
|
45
|
+
def start(name)
|
|
46
|
+
client = Control.client_from
|
|
47
|
+
reply = client.request("start_spawner", spawner_name: name)
|
|
48
|
+
|
|
49
|
+
if reply.success?
|
|
50
|
+
$stdout.puts "Starting spawner #{name}."
|
|
51
|
+
else
|
|
52
|
+
warn "Error: #{reply.error_message}"
|
|
53
|
+
exit(1)
|
|
54
|
+
end
|
|
55
|
+
rescue Control::Client::ServerUnavailable
|
|
56
|
+
warn "Superkick server is not running."
|
|
57
|
+
exit(1)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
desc "stop NAME", "Stop a running spawner"
|
|
61
|
+
def stop(name)
|
|
62
|
+
client = Control.client_from
|
|
63
|
+
reply = client.request("stop_spawner", spawner_name: name)
|
|
64
|
+
|
|
65
|
+
if reply.success?
|
|
66
|
+
$stdout.puts "Stopping spawner #{name}."
|
|
67
|
+
else
|
|
68
|
+
warn "Error: #{reply.error_message}"
|
|
69
|
+
exit(1)
|
|
70
|
+
end
|
|
71
|
+
rescue Control::Client::ServerUnavailable
|
|
72
|
+
warn "Superkick server is not running."
|
|
73
|
+
exit(1)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
desc "install-templates", "Copy default spawner ERB templates to ~/.superkick/templates/spawners/"
|
|
77
|
+
option :force, type: :boolean, default: false, desc: "Overwrite existing templates"
|
|
78
|
+
def install_templates
|
|
79
|
+
TemplateRenderer.install_spawner_defaults(force: options[:force])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
default_command :list
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|