kscript 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.
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+
10
+ module Kscript
11
+ class KkRenameUtils < Base
12
+ attr_reader :source_pattern, :target_pattern, :directory
13
+
14
+ def initialize(source_pattern = nil, target_pattern = nil, directory = Dir.pwd, *_args, **opts)
15
+ super(**opts.merge(service: 'kk_rename'))
16
+ @source_pattern = source_pattern
17
+ @target_pattern = target_pattern
18
+ @directory = directory
19
+ end
20
+
21
+ def run
22
+ with_error_handling do
23
+ rename
24
+ end
25
+ end
26
+
27
+ def rename
28
+ Dir.entries(@directory).each do |filename|
29
+ process_file(filename)
30
+ end
31
+ end
32
+
33
+ def self.arguments
34
+ '<pattern> <replacement> [path]'
35
+ end
36
+
37
+ def self.usage
38
+ "kscript rename foo bar ./src\nkscript rename 'test' 'prod' ~/projects"
39
+ end
40
+
41
+ def self.group
42
+ 'project'
43
+ end
44
+
45
+ def self.author
46
+ 'kk'
47
+ end
48
+
49
+ def self.description
50
+ 'Batch rename files by pattern.'
51
+ end
52
+
53
+ private
54
+
55
+ def process_file(filename)
56
+ return unless should_process?(filename)
57
+
58
+ new_name = generate_new_name(filename)
59
+ return unless new_name
60
+
61
+ rename_file(filename, new_name)
62
+ end
63
+
64
+ def should_process?(filename)
65
+ File.file?(File.join(@directory, filename)) && filename =~ /#{@source_pattern}/
66
+ end
67
+
68
+ def generate_new_name(filename)
69
+ eval("\"#{filename}\"".gsub(/#{@source_pattern}/, @target_pattern))
70
+ rescue StandardError => e
71
+ logger.kerror("Error processing #{filename}: #{e.message}")
72
+ nil
73
+ end
74
+
75
+ def rename_file(old_name, new_name)
76
+ File.rename(File.join(@directory, old_name), File.join(@directory, new_name))
77
+ logger.kinfo("Renamed: #{old_name} -> #{new_name}")
78
+ rescue StandardError => e
79
+ logger.kerror("Error renaming #{old_name}: #{e.message}")
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+ require 'nokogiri'
10
+
11
+ module Kscript
12
+ class KkShUtils < Base
13
+ CHT_SH_URL = 'https://cht.sh'
14
+
15
+ attr_reader :command
16
+
17
+ # Initialize with shell command to look up
18
+ # @param command [String] command to get help for
19
+ def initialize(command = nil, *_args, **opts)
20
+ super(**opts.merge(service: 'kk_sh'))
21
+ @command = command
22
+ end
23
+
24
+ def run
25
+ with_error_handling do
26
+ help
27
+ end
28
+ end
29
+
30
+ def help
31
+ if command
32
+ fetch_help
33
+ else
34
+ logger.kinfo("Usage: #{$PROGRAM_NAME} <command>")
35
+ exit 1
36
+ end
37
+ end
38
+
39
+ # Fetch and display command documentation
40
+ def fetch_help
41
+ response = make_request
42
+ display_result(response)
43
+ rescue HTTP::Error => e
44
+ display_error(e)
45
+ end
46
+
47
+ def self.arguments
48
+ '[subcommand] [args...]'
49
+ end
50
+
51
+ def self.usage
52
+ "kscript sh 'ls'\nkscript sh 'echo hello'"
53
+ end
54
+
55
+ def self.group
56
+ 'system'
57
+ end
58
+
59
+ def self.author
60
+ 'kk'
61
+ end
62
+
63
+ def self.description
64
+ 'Query sh command usage and cheatsheets.'
65
+ end
66
+
67
+ private
68
+
69
+ # Make HTTP request to cht.sh
70
+ # @return [HTTP::Response] response from cht.sh
71
+ def make_request
72
+ HTTP.get("#{CHT_SH_URL}/#{command}")
73
+ end
74
+
75
+ # Display command documentation
76
+ # @param response [HTTP::Response] response from cht.sh
77
+ def display_result(response)
78
+ if response.status.success?
79
+ text = extract_plain_text(response.body)
80
+ logger.kinfo(text)
81
+ else
82
+ logger.kerror("Failed to retrieve data: #{response.status}")
83
+ end
84
+ end
85
+
86
+ # 提取纯文本内容
87
+ def extract_plain_text(body)
88
+ body = body.to_s
89
+ begin
90
+ doc = Nokogiri::HTML(body)
91
+ doc.search('script,style').remove
92
+ text = doc.text.lines.map(&:strip)
93
+ # 过滤掉包含广告/社交/Follow等内容的行
94
+ text = text.reject { |line| line =~ /Follow @|twitter|github|sponsor|donate|chubin|^!function/ }
95
+ text.reject!(&:empty?)
96
+ # 去除顶部多余空白行,只保留正文
97
+ text = text.drop_while(&:empty?)
98
+ text.join("\n")
99
+ rescue StandardError
100
+ body.gsub(/\e\[[\d;]*m/, '').lines.reject do |line|
101
+ line =~ /Follow @|twitter|github|sponsor|donate|chubin|^!function/
102
+ end.drop_while { |line| line.strip.empty? }.join
103
+ end
104
+ end
105
+
106
+ # Display error message
107
+ # @param error [StandardError] error to display
108
+ def display_error(error)
109
+ logger.kerror("An error occurred: #{error.message}")
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+
10
+ module Kscript
11
+ class KkSyscheckUtils < Base
12
+ def run
13
+ with_error_handling do
14
+ check
15
+ end
16
+ end
17
+
18
+ def check
19
+ logger.kinfo('======= 🍎 macOS System Resource Monitor Report =======')
20
+ logger.kinfo("📅 Date Time: #{Time.now}")
21
+ logger.kinfo('')
22
+
23
+ # macOS ps command for process monitoring
24
+ cpu_cmd = 'ps aux | sort -nrk 3 | head -n 10'
25
+ mem_cmd = 'ps aux | sort -nrk 4 | head -n 10'
26
+
27
+ run('CPU Usage (Top 10)', cpu_cmd)
28
+ run('Memory Usage (Top 10)', mem_cmd)
29
+
30
+ if `which powermetrics`.strip.empty?
31
+ logger.kinfo("\n👉 GPU Usage:")
32
+ logger.kwarn('⚠️ powermetrics not installed, please run: xcode-select --install')
33
+ else
34
+ run('GPU Usage', 'powermetrics --samplers gpu_power -n 1', sudo: true)
35
+ end
36
+
37
+ logger.kinfo("\n👉 Top 10 Processes by Network Connections:")
38
+ lsof_output = `lsof -i -nP | grep ESTABLISHED`
39
+ counts = Hash.new(0)
40
+ lsof_output.each_line do |line|
41
+ process = line.split.first
42
+ counts[process] += 1
43
+ end
44
+ counts.sort_by { |_, v| -v }.first(10).each do |proc, count|
45
+ logger.kinfo("#{proc}: #{count} connections")
46
+ end
47
+
48
+ logger.kinfo("\n👉 System Overview:")
49
+ cpu_core = `sysctl -n hw.ncpu`.strip
50
+ mem_size = `sysctl -n hw.memsize`.to_i / 1024 / 1024 / 1024
51
+ logger.kinfo("CPU Cores: #{cpu_core}")
52
+ logger.kinfo("Physical Memory: #{mem_size} GB")
53
+
54
+ vm_stats = `vm_stat`
55
+ vm_stats.each_line do |line|
56
+ logger.kinfo(line) if line =~ /Pages (active|wired down|free):/
57
+ end
58
+
59
+ logger.kinfo("\n✅ System resource check completed!")
60
+ end
61
+
62
+ def self.description
63
+ 'Show macOS system resource monitor report.'
64
+ end
65
+
66
+ def self.arguments
67
+ ''
68
+ end
69
+
70
+ def self.usage
71
+ "kscript syscheck\nkscript syscheck --detail"
72
+ end
73
+
74
+ def self.group
75
+ 'macos'
76
+ end
77
+
78
+ def self.author
79
+ 'kk'
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+
10
+ # 彩色输出定义
11
+ RED = "\e[1;31m"
12
+ GREEN = "\e[1;32m"
13
+ YELLOW = "\e[1;33m"
14
+ CYAN = "\e[1;36m"
15
+ NC = "\e[0m" # No Color
16
+
17
+ module Kscript
18
+ class KkTopUtils < Base
19
+ def run
20
+ with_error_handling do
21
+ print_report
22
+ end
23
+ end
24
+
25
+ def print_report
26
+ logger.kinfo("System Resource Top Report - #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}")
27
+ print_header('Top 10 Processes by CPU Usage')
28
+ print_process_list(:cpu)
29
+ print_header('Top 10 Processes by Memory Usage')
30
+ print_process_list(:mem)
31
+ end
32
+
33
+ def print_header(title)
34
+ logger.kinfo('')
35
+ logger.kinfo('===============================')
36
+ logger.kinfo(" #{title}")
37
+ logger.kinfo('===============================')
38
+ end
39
+
40
+ def print_process_list(sort_field)
41
+ lines = `ps aux`.split("\n")
42
+ lines.shift
43
+ processes = lines.map { |line| line.split(/\s+/, 11) }
44
+ index = sort_field == :cpu ? 2 : 3
45
+ top = processes.sort_by { |p| -p[index].to_f }.first(10)
46
+ logger.kinfo(format('%-10s %-8s %-5s %-5s %-10s', 'USER', 'PID', '%CPU', '%MEM', 'COMMAND'))
47
+ top.each do |p|
48
+ logger.kinfo(format('%-10s %-8s %-5s %-5s %-10s', p[0], p[1], p[2], p[3], p[10][0..30]))
49
+ end
50
+ end
51
+
52
+ def self.description
53
+ 'Show top 10 processes by CPU/memory on macOS.'
54
+ end
55
+
56
+ def self.arguments
57
+ ''
58
+ end
59
+
60
+ def self.usage
61
+ 'kscript top'
62
+ end
63
+
64
+ def self.group
65
+ 'macos'
66
+ end
67
+
68
+ def self.author
69
+ 'kk'
70
+ end
71
+ end
72
+ end
73
+
74
+ Kscript::KkTopUtils.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+
10
+ require 'net/http'
11
+ require 'json'
12
+
13
+ module Kscript
14
+ class KkUsdUtils < Base
15
+ API_URL = 'https://api.exchangerate-api.com/v4/latest/USD'
16
+
17
+ def initialize(currency_code = nil, *_args, **opts)
18
+ super(**opts.merge(service: 'kk_usd'))
19
+ @currency_code = currency_code
20
+ end
21
+
22
+ def run
23
+ with_error_handling do
24
+ fetch_rates
25
+ end
26
+ end
27
+
28
+ def fetch_rates
29
+ uri = URI(API_URL)
30
+ response = Net::HTTP.get(uri)
31
+ data = JSON.parse(response)
32
+ if @currency_code && data['rates'][@currency_code.upcase]
33
+ rate = data['rates'][@currency_code.upcase]
34
+ if human_output?
35
+ logger.kinfo("1 USD = #{rate} #{@currency_code.upcase}")
36
+ else
37
+ logger.kinfo("USD -> #{@currency_code.upcase}", rate: rate)
38
+ end
39
+ elsif @currency_code
40
+ if human_output?
41
+ end
42
+ logger.kerror("Currency code not found: #{@currency_code}")
43
+ elsif human_output?
44
+ logger.kinfo('USD Exchange Rates:')
45
+ data['rates'].each { |k, v| logger.kinfo(" 1 USD = #{v} #{k}") }
46
+ else
47
+ logger.kinfo('USD rates', rates: data['rates'])
48
+ end
49
+ end
50
+
51
+ def self.arguments
52
+ '[currency_code]'
53
+ end
54
+
55
+ def self.usage
56
+ "kscript usd CNY\nkscript usd EUR"
57
+ end
58
+
59
+ def self.group
60
+ 'finance'
61
+ end
62
+
63
+ def self.author
64
+ 'kk'
65
+ end
66
+
67
+ def self.description
68
+ 'Get latest USD exchange rates.'
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+
10
+ module Kscript
11
+ class KkWgAclUtils < Base
12
+ WIREGUARD_PORT = 51_821
13
+ ALLOWED_IPS = %w[127.0.0.1].freeze
14
+
15
+ def run
16
+ with_error_handling do
17
+ apply_rules
18
+ end
19
+ end
20
+
21
+ def apply_rules
22
+ add_whitelist_rules
23
+ ensure_firewall_enabled
24
+ display_current_rules
25
+ end
26
+
27
+ def self.arguments
28
+ '[subcommand] [options]'
29
+ end
30
+
31
+ def self.usage
32
+ "kscript wg_acl add --ip=10.0.0.2\nkscript wg_acl list"
33
+ end
34
+
35
+ def self.group
36
+ 'network'
37
+ end
38
+
39
+ def self.author
40
+ 'kk'
41
+ end
42
+
43
+ def self.description
44
+ 'Manage WireGuard firewall ACL rules.'
45
+ end
46
+
47
+ private
48
+
49
+ # Fetch current UFW rules
50
+ # @return [Array<String>] list of current firewall rules
51
+ def fetch_current_rules
52
+ `sudo ufw status`.lines.map(&:strip)
53
+ end
54
+
55
+ # Check if a specific rule exists
56
+ # @param ip [String] IP address to check
57
+ # @param port [Integer] port number to check
58
+ # @return [Boolean] true if rule exists
59
+ def rule_exists?(ip, port)
60
+ @current_rules.any? do |line|
61
+ line.match?(/#{Regexp.escape(ip)}.*ALLOW.*#{port}/)
62
+ end
63
+ end
64
+
65
+ # Add whitelist rules for allowed IPs
66
+ def add_whitelist_rules
67
+ ALLOWED_IPS.each do |ip|
68
+ if rule_exists?(ip, WIREGUARD_PORT)
69
+ logger.kinfo("✅ Rule exists: #{ip} → #{WIREGUARD_PORT}, skipping.")
70
+ else
71
+ logger.kinfo("👉 Adding rule: allow #{ip} to access port #{WIREGUARD_PORT}")
72
+ system("sudo ufw allow from #{ip} to any port #{WIREGUARD_PORT}")
73
+ end
74
+ end
75
+ end
76
+
77
+ # Ensure UFW firewall is enabled
78
+ def ensure_firewall_enabled
79
+ ufw_status = `sudo ufw status`.strip
80
+ if ufw_status.start_with?('Status: inactive')
81
+ logger.kinfo('🔧 UFW is currently disabled, enabling...')
82
+ system('sudo ufw enable')
83
+ else
84
+ logger.kinfo('✅ UFW is enabled.')
85
+ end
86
+ end
87
+
88
+ # Display current firewall rules
89
+ def display_current_rules
90
+ logger.kinfo("\n📋 Current firewall rules:")
91
+ system('sudo ufw status verbose')
92
+ logger.kinfo("\n✅ Firewall rules update completed!")
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2025 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ require 'kscript'
9
+ require 'bcrypt'
10
+
11
+ module Kscript
12
+ class KkWgPassUtils < Base
13
+ def initialize(length = 32, *_args, **opts)
14
+ super(**opts.merge(service: 'kk_wg_pass'))
15
+ @length = length.to_i
16
+ end
17
+
18
+ def run
19
+ with_error_handling do
20
+ generate
21
+ end
22
+ end
23
+
24
+ def generate
25
+ password = Array.new(@length) { rand(33..126).chr }.join
26
+ logger.kinfo('Generated WireGuard password', password: password)
27
+ logger.kinfo(password)
28
+ end
29
+
30
+ def self.arguments
31
+ '[length]'
32
+ end
33
+
34
+ def self.usage
35
+ "kscript wg_pass [length]\nkscript wg_pass 32"
36
+ end
37
+
38
+ def self.group
39
+ 'network'
40
+ end
41
+
42
+ def self.author
43
+ 'kk'
44
+ end
45
+
46
+ def self.description
47
+ 'Generate a random password for WireGuard.'
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kscript
4
+ module PluginLoader
5
+ PLUGIN_DIR = File.expand_path('plugins', __dir__)
6
+
7
+ def self.load_all
8
+ Dir.glob(File.join(PLUGIN_DIR, 'kk_*.rb')).sort.each do |file|
9
+ # 解析出类名
10
+ basename = File.basename(file, '.rb')
11
+ class_name = basename.split('_').map(&:capitalize).join
12
+ # 只在未定义时 require
13
+ require file unless Kscript.const_defined?(class_name, false)
14
+ end
15
+ end
16
+
17
+ # 返回所有已注册插件的元信息
18
+ def self.plugin_infos
19
+ Kscript::Plugin.all.map do |name, klass|
20
+ {
21
+ name: name,
22
+ class: klass,
23
+ description: klass.respond_to?(:description) ? klass.description : nil,
24
+ arguments: klass.respond_to?(:arguments) ? klass.arguments : nil,
25
+ usage: klass.respond_to?(:usage) ? klass.usage : nil,
26
+ group: klass.respond_to?(:group) ? klass.group : nil,
27
+ author: klass.respond_to?(:author) ? klass.author : nil
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ module Kscript
7
+ module Utils
8
+ class Config
9
+ DEFAULT_PATH = File.expand_path('~/.kscriptrc')
10
+
11
+ def self.load
12
+ @load ||= new
13
+ end
14
+
15
+ def initialize
16
+ @data = File.exist?(DEFAULT_PATH) ? YAML.load_file(DEFAULT_PATH) : {}
17
+ end
18
+
19
+ def [](key)
20
+ @data[key.to_s] || ENV["KSCRIPT_#{key.to_s.upcase}"]
21
+ end
22
+
23
+ def log_level
24
+ self['log_level']
25
+ end
26
+
27
+ def trace_id
28
+ self['trace_id']
29
+ end
30
+
31
+ # 自动检测并安装 shell 补全脚本
32
+ def self.ensure_completion_installed(shell = nil)
33
+ shell ||= ENV['SHELL']
34
+ home = Dir.respond_to?(:home) ? Dir.home : ENV['HOME']
35
+ return unless shell && home
36
+
37
+ if shell.include?('zsh')
38
+ completion_path = File.join(home, '.zsh/completions/_kscript')
39
+ shell_type = 'zsh'
40
+ elsif shell.include?('bash')
41
+ completion_path = File.join(home, '.bash_completion.d/kscript')
42
+ shell_type = 'bash'
43
+ else
44
+ return
45
+ end
46
+
47
+ # 已存在则跳过
48
+ return if File.exist?(completion_path)
49
+
50
+ # 生成补全脚本内容
51
+ require_relative 'cli'
52
+ script = case shell_type
53
+ when 'zsh' then Kscript::CLI.new.completion('zsh', capture: true)
54
+ when 'bash' then Kscript::CLI.new.completion('bash', capture: true)
55
+ end
56
+ FileUtils.mkdir_p(File.dirname(completion_path))
57
+ File.write(completion_path, script)
58
+ puts "\e[32m[kscript] Shell completion installed to #{completion_path}\e[0m"
59
+ rescue StandardError => e
60
+ warn "[kscript] Failed to install shell completion: #{e.message}"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Kk
4
+ #
5
+ # This software is released under the MIT License.
6
+ # https://opensource.org/licenses/MIT
7
+
8
+ module Kscript
9
+ VERSION = '0.1.0'
10
+ end