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.
Files changed (199) hide show
  1. checksums.yaml +7 -0
  2. data/CLA.md +91 -0
  3. data/CLAUDE.md +2226 -0
  4. data/CONTRIBUTING.md +104 -0
  5. data/LICENSE +108 -0
  6. data/LICENSE-COMMERCIAL.md +39 -0
  7. data/PLAN.md +161 -0
  8. data/README.md +1155 -0
  9. data/exe/superkick +6 -0
  10. data/lib/superkick/agent/runtime.rb +82 -0
  11. data/lib/superkick/agent/runtimes/local.rb +74 -0
  12. data/lib/superkick/agent/runtimes.rb +4 -0
  13. data/lib/superkick/agent.rb +209 -0
  14. data/lib/superkick/agent_store.rb +85 -0
  15. data/lib/superkick/attach/client.rb +245 -0
  16. data/lib/superkick/attach/protocol.rb +71 -0
  17. data/lib/superkick/attach/server.rb +371 -0
  18. data/lib/superkick/budget_checker.rb +120 -0
  19. data/lib/superkick/buffer/client.rb +91 -0
  20. data/lib/superkick/buffer/server.rb +127 -0
  21. data/lib/superkick/cli/agent.rb +524 -0
  22. data/lib/superkick/cli/completion.rb +591 -0
  23. data/lib/superkick/cli/goal.rb +71 -0
  24. data/lib/superkick/cli/mcp.rb +34 -0
  25. data/lib/superkick/cli/monitor.rb +47 -0
  26. data/lib/superkick/cli/notifier.rb +39 -0
  27. data/lib/superkick/cli/repository.rb +46 -0
  28. data/lib/superkick/cli/server.rb +106 -0
  29. data/lib/superkick/cli/setup.rb +166 -0
  30. data/lib/superkick/cli/spawner.rb +85 -0
  31. data/lib/superkick/cli/team.rb +407 -0
  32. data/lib/superkick/cli.rb +175 -0
  33. data/lib/superkick/client_registry.rb +30 -0
  34. data/lib/superkick/configuration.rb +178 -0
  35. data/lib/superkick/connection.rb +56 -0
  36. data/lib/superkick/control/client.rb +78 -0
  37. data/lib/superkick/control/reply.rb +43 -0
  38. data/lib/superkick/control/server.rb +1271 -0
  39. data/lib/superkick/cost_accumulator.rb +53 -0
  40. data/lib/superkick/cost_extractor.rb +65 -0
  41. data/lib/superkick/cost_poller.rb +70 -0
  42. data/lib/superkick/driver/profile_source.rb +134 -0
  43. data/lib/superkick/driver.rb +179 -0
  44. data/lib/superkick/drivers/claude_code.rb +110 -0
  45. data/lib/superkick/drivers/codex.rb +57 -0
  46. data/lib/superkick/drivers/copilot.rb +75 -0
  47. data/lib/superkick/drivers/gemini.rb +86 -0
  48. data/lib/superkick/drivers/goose.rb +74 -0
  49. data/lib/superkick/drivers.rb +16 -0
  50. data/lib/superkick/drop.rb +80 -0
  51. data/lib/superkick/drops.rb +76 -0
  52. data/lib/superkick/environment_executor.rb +90 -0
  53. data/lib/superkick/goal.rb +95 -0
  54. data/lib/superkick/goals/agent_exit.rb +41 -0
  55. data/lib/superkick/goals/agent_signal.rb +42 -0
  56. data/lib/superkick/goals/command.rb +103 -0
  57. data/lib/superkick/history_buffer.rb +38 -0
  58. data/lib/superkick/hosted/attach/bridge.rb +52 -0
  59. data/lib/superkick/hosted/attach/client.rb +208 -0
  60. data/lib/superkick/hosted/attach/relay.rb +313 -0
  61. data/lib/superkick/hosted/attach/relay_store.rb +48 -0
  62. data/lib/superkick/hosted/bridge.rb +263 -0
  63. data/lib/superkick/hosted/buffer/bridge.rb +42 -0
  64. data/lib/superkick/hosted/buffer/client.rb +63 -0
  65. data/lib/superkick/hosted/buffer/relay.rb +126 -0
  66. data/lib/superkick/hosted/buffer/relay_store.rb +42 -0
  67. data/lib/superkick/hosted/control/client.rb +84 -0
  68. data/lib/superkick/hosted/mcp_proxy.rb +144 -0
  69. data/lib/superkick/inject_handler.rb +24 -0
  70. data/lib/superkick/injection_guard.rb +26 -0
  71. data/lib/superkick/injection_queue.rb +177 -0
  72. data/lib/superkick/injector.rb +65 -0
  73. data/lib/superkick/input_buffer.rb +171 -0
  74. data/lib/superkick/integrations/bugsnag/README.md +98 -0
  75. data/lib/superkick/integrations/bugsnag/spawner.rb +307 -0
  76. data/lib/superkick/integrations/bugsnag/templates/error_opened.liquid +17 -0
  77. data/lib/superkick/integrations/bugsnag.rb +7 -0
  78. data/lib/superkick/integrations/circleci/README.md +75 -0
  79. data/lib/superkick/integrations/circleci/monitor.rb +185 -0
  80. data/lib/superkick/integrations/circleci/probe.rb +36 -0
  81. data/lib/superkick/integrations/circleci/templates/ci_failure.liquid +8 -0
  82. data/lib/superkick/integrations/circleci/templates/ci_success.liquid +1 -0
  83. data/lib/superkick/integrations/circleci.rb +8 -0
  84. data/lib/superkick/integrations/datadog/README.md +253 -0
  85. data/lib/superkick/integrations/datadog/alert_goal.rb +94 -0
  86. data/lib/superkick/integrations/datadog/alert_monitor.rb +163 -0
  87. data/lib/superkick/integrations/datadog/alert_spawner.rb +201 -0
  88. data/lib/superkick/integrations/datadog/notification_templates/default.liquid +10 -0
  89. data/lib/superkick/integrations/datadog/notifier.rb +294 -0
  90. data/lib/superkick/integrations/datadog/spawner.rb +201 -0
  91. data/lib/superkick/integrations/datadog/templates/alert_changed.liquid +8 -0
  92. data/lib/superkick/integrations/datadog/templates/alert_escalated.liquid +8 -0
  93. data/lib/superkick/integrations/datadog/templates/alert_recovered.liquid +14 -0
  94. data/lib/superkick/integrations/datadog/templates/alert_triggered.liquid +29 -0
  95. data/lib/superkick/integrations/datadog/templates/error_opened.liquid +15 -0
  96. data/lib/superkick/integrations/datadog.rb +14 -0
  97. data/lib/superkick/integrations/docker/README.md +256 -0
  98. data/lib/superkick/integrations/docker/client.rb +295 -0
  99. data/lib/superkick/integrations/docker/runtime.rb +218 -0
  100. data/lib/superkick/integrations/docker.rb +4 -0
  101. data/lib/superkick/integrations/git/repository_source.rb +66 -0
  102. data/lib/superkick/integrations/git/version_control.rb +119 -0
  103. data/lib/superkick/integrations/git.rb +8 -0
  104. data/lib/superkick/integrations/github/README.md +300 -0
  105. data/lib/superkick/integrations/github/check_failed_spawner.rb +199 -0
  106. data/lib/superkick/integrations/github/drops.rb +114 -0
  107. data/lib/superkick/integrations/github/goal.rb +135 -0
  108. data/lib/superkick/integrations/github/issue_goal.rb +104 -0
  109. data/lib/superkick/integrations/github/issue_spawner.rb +160 -0
  110. data/lib/superkick/integrations/github/monitor.rb +251 -0
  111. data/lib/superkick/integrations/github/probe.rb +30 -0
  112. data/lib/superkick/integrations/github/repository_source.rb +228 -0
  113. data/lib/superkick/integrations/github/templates/check_failed.liquid +10 -0
  114. data/lib/superkick/integrations/github/templates/ci_failure.liquid +5 -0
  115. data/lib/superkick/integrations/github/templates/ci_success.liquid +1 -0
  116. data/lib/superkick/integrations/github/templates/issue_opened.liquid +20 -0
  117. data/lib/superkick/integrations/github/templates/pr_comment.liquid +2 -0
  118. data/lib/superkick/integrations/github/templates/pr_review.liquid +4 -0
  119. data/lib/superkick/integrations/github.rb +16 -0
  120. data/lib/superkick/integrations/honeybadger/README.md +97 -0
  121. data/lib/superkick/integrations/honeybadger/notification_templates/default.liquid +8 -0
  122. data/lib/superkick/integrations/honeybadger/notifier.rb +250 -0
  123. data/lib/superkick/integrations/honeybadger/spawner.rb +214 -0
  124. data/lib/superkick/integrations/honeybadger/templates/error_opened.liquid +17 -0
  125. data/lib/superkick/integrations/honeybadger.rb +9 -0
  126. data/lib/superkick/integrations/shell/README.md +83 -0
  127. data/lib/superkick/integrations/shell/monitor.rb +87 -0
  128. data/lib/superkick/integrations/shell/templates/shell_alert.liquid +6 -0
  129. data/lib/superkick/integrations/shell/templates/shell_success.liquid +6 -0
  130. data/lib/superkick/integrations/shell.rb +7 -0
  131. data/lib/superkick/integrations/shortcut/README.md +193 -0
  132. data/lib/superkick/integrations/shortcut/drops.rb +91 -0
  133. data/lib/superkick/integrations/shortcut/monitor.rb +582 -0
  134. data/lib/superkick/integrations/shortcut/probe.rb +34 -0
  135. data/lib/superkick/integrations/shortcut/spawner.rb +264 -0
  136. data/lib/superkick/integrations/shortcut/templates/related_story_changed.liquid +6 -0
  137. data/lib/superkick/integrations/shortcut/templates/story_blocker.liquid +8 -0
  138. data/lib/superkick/integrations/shortcut/templates/story_comment.liquid +5 -0
  139. data/lib/superkick/integrations/shortcut/templates/story_description_changed.liquid +19 -0
  140. data/lib/superkick/integrations/shortcut/templates/story_owner_changed.liquid +10 -0
  141. data/lib/superkick/integrations/shortcut/templates/story_ready.liquid +41 -0
  142. data/lib/superkick/integrations/shortcut/templates/story_state_changed.liquid +9 -0
  143. data/lib/superkick/integrations/shortcut/templates/story_unblocked.liquid +5 -0
  144. data/lib/superkick/integrations/shortcut.rb +11 -0
  145. data/lib/superkick/integrations/slack/README.md +297 -0
  146. data/lib/superkick/integrations/slack/drops.rb +70 -0
  147. data/lib/superkick/integrations/slack/notifier.rb +426 -0
  148. data/lib/superkick/integrations/slack/spawner.rb +251 -0
  149. data/lib/superkick/integrations/slack/templates/default.liquid +17 -0
  150. data/lib/superkick/integrations/slack/templates/slack_reply.liquid +3 -0
  151. data/lib/superkick/integrations/slack/templates/spawn/slack_message.liquid +10 -0
  152. data/lib/superkick/integrations/slack/thread_monitor.rb +161 -0
  153. data/lib/superkick/integrations/slack.rb +12 -0
  154. data/lib/superkick/liquid.rb +129 -0
  155. data/lib/superkick/local/repository_source.rb +148 -0
  156. data/lib/superkick/mcp_server.rb +596 -0
  157. data/lib/superkick/monitor.rb +215 -0
  158. data/lib/superkick/notification_dispatcher.rb +280 -0
  159. data/lib/superkick/notifier.rb +173 -0
  160. data/lib/superkick/notifier_state_store.rb +55 -0
  161. data/lib/superkick/notifier_template.rb +121 -0
  162. data/lib/superkick/notifiers/command.rb +124 -0
  163. data/lib/superkick/notifiers/terminal_bell.rb +41 -0
  164. data/lib/superkick/output_logger.rb +54 -0
  165. data/lib/superkick/poller.rb +126 -0
  166. data/lib/superkick/process_runner.rb +87 -0
  167. data/lib/superkick/pty_proxy.rb +403 -0
  168. data/lib/superkick/registry.rb +75 -0
  169. data/lib/superkick/repository_source.rb +195 -0
  170. data/lib/superkick/server.rb +211 -0
  171. data/lib/superkick/session_recorder.rb +154 -0
  172. data/lib/superkick/setup.rb +160 -0
  173. data/lib/superkick/spawn/agent_spawner.rb +311 -0
  174. data/lib/superkick/spawn/approval_store.rb +113 -0
  175. data/lib/superkick/spawn/handler.rb +144 -0
  176. data/lib/superkick/spawn/injector.rb +119 -0
  177. data/lib/superkick/spawn/workflow_executor.rb +196 -0
  178. data/lib/superkick/spawn/workflow_validator.rb +77 -0
  179. data/lib/superkick/spawner.rb +67 -0
  180. data/lib/superkick/supervisor.rb +516 -0
  181. data/lib/superkick/team/artifact_store.rb +92 -0
  182. data/lib/superkick/team/log.rb +140 -0
  183. data/lib/superkick/team/log_entry_drop.rb +34 -0
  184. data/lib/superkick/team/log_monitor.rb +84 -0
  185. data/lib/superkick/team/log_notifier.rb +96 -0
  186. data/lib/superkick/team/log_store.rb +40 -0
  187. data/lib/superkick/template_filters.rb +24 -0
  188. data/lib/superkick/template_renderer.rb +223 -0
  189. data/lib/superkick/templates/team_log/planning_agent.liquid +38 -0
  190. data/lib/superkick/templates/team_log/team_digest.liquid +45 -0
  191. data/lib/superkick/templates/team_log/teammate_message.liquid +7 -0
  192. data/lib/superkick/templates/team_log/worker_kickoff.liquid +37 -0
  193. data/lib/superkick/templates/workflow/workflow_triggered.liquid +22 -0
  194. data/lib/superkick/version.rb +5 -0
  195. data/lib/superkick/version_control.rb +135 -0
  196. data/lib/superkick/yaml_config.rb +302 -0
  197. data/lib/superkick.rb +198 -0
  198. data/plan.md +267 -0
  199. 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