# frozen_string_literal: true require 'json' require 'open3' module PluginConfig class RuntimeVerifier STATUSES = %w[enabled disabled failed_to_load not_present].freeze def initialize(capture3: Open3.method(:capture3)) @capture3 = capture3 end def snapshot stdout, _stderr, status = @capture3.call('claude', 'plugin', 'list') return {} unless status.success? parse_plugin_list(stdout) rescue Errno::ENOENT {} end def verify_status(plugin_key, expected_status) current = snapshot actual = current[plugin_key] || 'not_present' if actual == expected_status { verified: true, details: "Plugin '#{plugin_key}' status is '#{expected_status}'" } else { verified: false, details: "Plugin '#{plugin_key}' expected '#{expected_status}', got '#{actual}'" } end end def self.diff(before, after) all_keys = (before.keys + after.keys).uniq changed = 0 unchanged = 0 all_keys.each do |key| if before[key] == after[key] unchanged += 1 else changed += 1 end end { changed: changed, unchanged: unchanged } end private def parse_plugin_list(output) plugins = {} output.each_line do |line| line = line.strip next if line.empty? # Expected format: "plugin-name@marketplace status" # or variations with columns/tabs parts = line.split(/\s{2,}|\t/) next unless parts.length >= 2 key = parts[0].strip status = parts[1].strip.downcase next unless key.include?('@') normalized = if STATUSES.include?(status) status elsif status.include?('enabled') 'enabled' elsif status.include?('disabled') 'disabled' elsif status.include?('fail') 'failed_to_load' else status end plugins[key] = normalized end plugins end end end