odysseus-cli 0.2.0 → 0.9.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 +4 -4
- data/CHANGELOG.md +332 -0
- data/LICENSE.txt +26 -0
- data/README.md +472 -88
- data/bin/odysseus +180 -122
- data/lib/odysseus/cli/cli.rb +448 -821
- data/lib/odysseus/cli/doctor_commands.rb +93 -0
- data/lib/odysseus/cli/interactive_commands.rb +198 -0
- data/lib/odysseus/cli/rollback_commands.rb +107 -0
- data/lib/odysseus/cli/setup_commands.rb +176 -0
- data/lib/odysseus/cli/ui.rb +457 -0
- data/lib/odysseus/cli/version.rb +7 -0
- metadata +22 -53
- data/lib/odysseus/cli/gum.rb +0 -156
data/lib/odysseus/cli/gum.rb
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
# odysseus-cli/lib/odysseus/cli/gum.rb
|
|
2
|
-
# Wrapper for Charm's gum CLI tool
|
|
3
|
-
# https://github.com/charmbracelet/gum
|
|
4
|
-
|
|
5
|
-
require 'open3'
|
|
6
|
-
require 'tempfile'
|
|
7
|
-
|
|
8
|
-
module Odysseus
|
|
9
|
-
module CLI
|
|
10
|
-
module Gum
|
|
11
|
-
class << self
|
|
12
|
-
# Check if gum is installed and available
|
|
13
|
-
def available?
|
|
14
|
-
@available ||= system('which gum > /dev/null 2>&1')
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Display a spinner while executing a block
|
|
18
|
-
# Returns the block's result
|
|
19
|
-
def spin(title:, spinner: 'dot')
|
|
20
|
-
return yield unless available?
|
|
21
|
-
|
|
22
|
-
result = nil
|
|
23
|
-
error = nil
|
|
24
|
-
|
|
25
|
-
# We can't use gum spin directly with Ruby blocks, so we show spinner
|
|
26
|
-
# and run the block in a thread
|
|
27
|
-
spin_pid = spawn("gum spin --spinner #{spinner} --title #{shell_escape(title)} -- sleep infinity",
|
|
28
|
-
out: '/dev/null', err: '/dev/null')
|
|
29
|
-
|
|
30
|
-
begin
|
|
31
|
-
result = yield
|
|
32
|
-
rescue => e
|
|
33
|
-
error = e
|
|
34
|
-
ensure
|
|
35
|
-
Process.kill('TERM', spin_pid) rescue nil
|
|
36
|
-
Process.wait(spin_pid) rescue nil
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
raise error if error
|
|
40
|
-
result
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Interactive selection menu
|
|
44
|
-
# Returns selected option or nil if cancelled
|
|
45
|
-
def choose(options, header: nil)
|
|
46
|
-
return nil unless available?
|
|
47
|
-
|
|
48
|
-
args = ['gum', 'choose']
|
|
49
|
-
args += ['--header', header] if header
|
|
50
|
-
args += options
|
|
51
|
-
|
|
52
|
-
stdout, status = Open3.capture2(*args)
|
|
53
|
-
return nil unless status.success?
|
|
54
|
-
|
|
55
|
-
stdout.strip
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Yes/No confirmation dialog
|
|
59
|
-
# Returns true for yes, false for no
|
|
60
|
-
def confirm(message)
|
|
61
|
-
return true unless available?
|
|
62
|
-
|
|
63
|
-
system('gum', 'confirm', message)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Style text with borders, colors, padding
|
|
67
|
-
def style(text, border: nil, foreground: nil, background: nil, padding: nil, margin: nil, bold: false)
|
|
68
|
-
return text unless available?
|
|
69
|
-
|
|
70
|
-
args = ['gum', 'style']
|
|
71
|
-
args += ['--border', border] if border
|
|
72
|
-
args += ['--foreground', foreground.to_s] if foreground
|
|
73
|
-
args += ['--background', background.to_s] if background
|
|
74
|
-
args += ['--padding', padding.to_s] if padding
|
|
75
|
-
args += ['--margin', margin.to_s] if margin
|
|
76
|
-
args << '--bold' if bold
|
|
77
|
-
args << text
|
|
78
|
-
|
|
79
|
-
stdout, status = Open3.capture2(*args)
|
|
80
|
-
status.success? ? stdout : text
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Display a table from headers and rows
|
|
84
|
-
# Returns formatted table string
|
|
85
|
-
def table(headers:, rows:)
|
|
86
|
-
return simple_table(headers, rows) unless available?
|
|
87
|
-
|
|
88
|
-
# gum table reads CSV from stdin
|
|
89
|
-
csv_data = [headers.join(',')]
|
|
90
|
-
rows.each do |row|
|
|
91
|
-
csv_data << row.map { |cell| csv_escape(cell.to_s) }.join(',')
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
stdout, status = Open3.capture2('gum', 'table', stdin_data: csv_data.join("\n"))
|
|
95
|
-
status.success? ? stdout : simple_table(headers, rows)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Format text (markdown, code, etc.)
|
|
99
|
-
def format(text, type: 'markdown')
|
|
100
|
-
return text unless available?
|
|
101
|
-
|
|
102
|
-
stdout, status = Open3.capture2('gum', 'format', '-t', type, stdin_data: text)
|
|
103
|
-
status.success? ? stdout : text
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Display a log message with level styling
|
|
107
|
-
def log(message, level: 'info')
|
|
108
|
-
return puts(message) unless available?
|
|
109
|
-
|
|
110
|
-
system('gum', 'log', '-l', level, message)
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Join multiple styled blocks horizontally or vertically
|
|
114
|
-
def join(*texts, horizontal: false)
|
|
115
|
-
return texts.join("\n") unless available?
|
|
116
|
-
|
|
117
|
-
args = ['gum', 'join']
|
|
118
|
-
args << '--horizontal' if horizontal
|
|
119
|
-
args += texts
|
|
120
|
-
|
|
121
|
-
stdout, status = Open3.capture2(*args)
|
|
122
|
-
status.success? ? stdout : texts.join(horizontal ? ' ' : "\n")
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
private
|
|
126
|
-
|
|
127
|
-
def shell_escape(str)
|
|
128
|
-
"'#{str.gsub("'", "'\\\\''")}'"
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def csv_escape(str)
|
|
132
|
-
if str.include?(',') || str.include?('"') || str.include?("\n")
|
|
133
|
-
"\"#{str.gsub('"', '""')}\""
|
|
134
|
-
else
|
|
135
|
-
str
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Fallback simple table for when gum is not available
|
|
140
|
-
def simple_table(headers, rows)
|
|
141
|
-
widths = headers.map.with_index do |h, i|
|
|
142
|
-
[h.to_s.length, rows.map { |r| r[i].to_s.length }.max || 0].max
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
lines = []
|
|
146
|
-
lines << headers.map.with_index { |h, i| h.to_s.ljust(widths[i]) }.join(' ')
|
|
147
|
-
lines << widths.map { |w| '-' * w }.join(' ')
|
|
148
|
-
rows.each do |row|
|
|
149
|
-
lines << row.map.with_index { |c, i| c.to_s.ljust(widths[i]) }.join(' ')
|
|
150
|
-
end
|
|
151
|
-
lines.join("\n")
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|