brute_cli 0.3.0 → 0.4.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/exe/brute +11 -26
- data/lib/brute_cli/buffer_output/error.rb +27 -0
- data/lib/brute_cli/buffer_output/model_line.rb +37 -0
- data/lib/brute_cli/buffer_output/separator.rb +24 -0
- data/lib/brute_cli/buffer_output/stats_bar.rb +43 -0
- data/lib/brute_cli/buffer_output.rb +11 -0
- data/lib/brute_cli/configuration.rb +14 -0
- data/lib/brute_cli/emoji.rb +25 -22
- data/lib/brute_cli/execution.rb +260 -0
- data/lib/brute_cli/phase/content_phase.rb +29 -0
- data/lib/brute_cli/phase/tool_call.rb +21 -0
- data/lib/brute_cli/phase/tool_phase.rb +29 -0
- data/lib/brute_cli/phase.rb +10 -0
- data/lib/brute_cli/repl.rb +99 -444
- data/lib/brute_cli/spinner/dots.rb +24 -0
- data/lib/brute_cli/spinner/nyan.rb +53 -0
- data/lib/brute_cli/spinner/puff_puff_pass.rb +32 -0
- data/lib/brute_cli/spinner.rb +30 -0
- data/lib/brute_cli/styles.rb +3 -0
- data/lib/brute_cli/tool_output/delegate.rb +16 -0
- data/lib/brute_cli/tool_output/fetch.rb +16 -0
- data/lib/brute_cli/tool_output/fs_search.rb +16 -0
- data/lib/brute_cli/tool_output/patch.rb +20 -0
- data/lib/brute_cli/tool_output/question.rb +9 -0
- data/lib/brute_cli/tool_output/read.rb +16 -0
- data/lib/brute_cli/tool_output/remove.rb +16 -0
- data/lib/brute_cli/tool_output/shell.rb +25 -0
- data/lib/brute_cli/tool_output/todo_read.rb +16 -0
- data/lib/brute_cli/tool_output/todo_write.rb +16 -0
- data/lib/brute_cli/tool_output/undo.rb +16 -0
- data/lib/brute_cli/tool_output/write.rb +20 -0
- data/lib/brute_cli/tool_output.rb +141 -0
- data/lib/brute_cli/version.rb +1 -1
- data/lib/brute_cli.rb +15 -12
- metadata +32 -4
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-spinner"
|
|
4
|
+
|
|
5
|
+
module BruteCLI
|
|
6
|
+
module Spinner
|
|
7
|
+
class Nyan < Base
|
|
8
|
+
RAINBOW = [
|
|
9
|
+
"\e[38;2;255;56;96m", "\e[38;2;255;165;0m",
|
|
10
|
+
"\e[38;2;255;220;0m", "\e[38;2;0;219;68m",
|
|
11
|
+
"\e[38;2;0;186;255m", "\e[38;2;107;80;255m",
|
|
12
|
+
"\e[38;2;255;96;255m",
|
|
13
|
+
].freeze
|
|
14
|
+
RESET = "\e[0m"
|
|
15
|
+
|
|
16
|
+
def start
|
|
17
|
+
stop if spinning?
|
|
18
|
+
@tty = TTY::Spinner.new(
|
|
19
|
+
":spinner #{label}",
|
|
20
|
+
frames: frames,
|
|
21
|
+
interval: interval,
|
|
22
|
+
output: $stdout,
|
|
23
|
+
clear: true,
|
|
24
|
+
)
|
|
25
|
+
@tty.auto_spin
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def stop
|
|
29
|
+
@tty&.stop
|
|
30
|
+
@tty = nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def spinning?
|
|
34
|
+
@tty&.spinning? || false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def label
|
|
38
|
+
"Thinking..."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def frames
|
|
42
|
+
bar = "━" * 12
|
|
43
|
+
bar.length.times.map do |offset|
|
|
44
|
+
bar.chars.map.with_index { |c, i| RAINBOW[(i + offset) % RAINBOW.length] + c }.join + RESET
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def interval
|
|
49
|
+
8
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-spinner"
|
|
4
|
+
|
|
5
|
+
module BruteCLI
|
|
6
|
+
module Spinner
|
|
7
|
+
class PuffPuffPass < Base
|
|
8
|
+
FRAMES = (1..4).map { |n| (Emoji::SMOKE + " ") * n }.freeze
|
|
9
|
+
|
|
10
|
+
def start
|
|
11
|
+
stop if spinning?
|
|
12
|
+
@tty = TTY::Spinner.new(
|
|
13
|
+
":spinner",
|
|
14
|
+
frames: FRAMES,
|
|
15
|
+
interval: 200,
|
|
16
|
+
output: $stdout,
|
|
17
|
+
clear: true,
|
|
18
|
+
)
|
|
19
|
+
@tty.auto_spin
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def stop
|
|
23
|
+
@tty&.stop
|
|
24
|
+
@tty = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def spinning?
|
|
28
|
+
@tty&.spinning? || false
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BruteCLI
|
|
4
|
+
module Spinner
|
|
5
|
+
# Abstract base class for CLI spinners.
|
|
6
|
+
#
|
|
7
|
+
# Subclasses must implement:
|
|
8
|
+
# #start – begin the spinner animation
|
|
9
|
+
# #stop – halt the spinner animation
|
|
10
|
+
# #spinning? – whether the spinner is currently animating
|
|
11
|
+
#
|
|
12
|
+
class Base
|
|
13
|
+
def start
|
|
14
|
+
raise NotImplementedError, "#{self.class.name} must implement #start"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def stop
|
|
18
|
+
raise NotImplementedError, "#{self.class.name} must implement #stop"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def spinning?
|
|
22
|
+
raise NotImplementedError, "#{self.class.name} must implement #spinning?"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
require "brute_cli/spinner/nyan"
|
|
29
|
+
require "brute_cli/spinner/dots"
|
|
30
|
+
require "brute_cli/spinner/puff_puff_pass"
|
data/lib/brute_cli/styles.rb
CHANGED
|
@@ -33,6 +33,9 @@ module BruteCLI
|
|
|
33
33
|
# Re-derive all accent constants from a new color.
|
|
34
34
|
# Call this before any output (e.g. right after option parsing).
|
|
35
35
|
def self.apply_theme!(name)
|
|
36
|
+
|
|
37
|
+
# honestly what the fuck is this shit???????????
|
|
38
|
+
|
|
36
39
|
color = THEMES.fetch(name) do
|
|
37
40
|
raise ArgumentError, "Unknown theme #{name.inspect}. Valid themes: #{THEMES.keys.join(', ')}"
|
|
38
41
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BruteCLI
|
|
4
|
+
module ToolOutput
|
|
5
|
+
class Patch < Base
|
|
6
|
+
ICON = Emoji::HAMMER
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def summary
|
|
11
|
+
path = arg(:file_path)
|
|
12
|
+
path ? path.to_s.colorize(DIM) : ""
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def body_lines
|
|
16
|
+
diff_lines
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BruteCLI
|
|
4
|
+
module ToolOutput
|
|
5
|
+
class Shell < Base
|
|
6
|
+
ICON = Emoji::COMPUTER
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def summary
|
|
11
|
+
cmd = arg(:command)
|
|
12
|
+
cmd ? cmd.to_s[0..60].colorize(DIM) : ""
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def body_lines
|
|
16
|
+
stdout = result_val(:stdout)
|
|
17
|
+
return [] unless stdout && !stdout.to_s.strip.empty?
|
|
18
|
+
|
|
19
|
+
lines = stdout.strip.lines.map(&:chomp)
|
|
20
|
+
lines = lines.first(15) + ["... (truncated)"] if lines.size > 15
|
|
21
|
+
lines.map { |l| l.colorize(DIM) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BruteCLI
|
|
4
|
+
module ToolOutput
|
|
5
|
+
class Write < Base
|
|
6
|
+
ICON = Emoji::WRITING
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def summary
|
|
11
|
+
path = arg(:file_path)
|
|
12
|
+
path ? path.to_s.colorize(DIM) : ""
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def body_lines
|
|
16
|
+
diff_lines
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BruteCLI
|
|
4
|
+
module ToolOutput
|
|
5
|
+
class Base
|
|
6
|
+
include BruteCLI
|
|
7
|
+
|
|
8
|
+
ICON = Emoji::GEAR
|
|
9
|
+
|
|
10
|
+
TODO_STATUS = {
|
|
11
|
+
"pending" => Emoji::SQUARE,
|
|
12
|
+
"in_progress" => Emoji::ARROWS,
|
|
13
|
+
"completed" => Emoji::CHECK,
|
|
14
|
+
"cancelled" => Emoji::CROSS,
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def initialize(tool_call, width:)
|
|
18
|
+
@call = tool_call
|
|
19
|
+
@width = width
|
|
20
|
+
@args = normalize_args(@call.arguments)
|
|
21
|
+
@result = @call.result
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_s
|
|
25
|
+
lines = [header_line]
|
|
26
|
+
if @call.resolved?
|
|
27
|
+
lines.concat(body_lines)
|
|
28
|
+
lines.concat(error_lines) if error?
|
|
29
|
+
end
|
|
30
|
+
lines.join("\n")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def icon = self.class::ICON
|
|
36
|
+
def name = @call.name.to_s
|
|
37
|
+
|
|
38
|
+
def header_line
|
|
39
|
+
"#{icon} #{name.colorize(ACCENT_BG)} #{summary}".rstrip
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def summary = ""
|
|
43
|
+
|
|
44
|
+
def body_lines
|
|
45
|
+
[] # subclasses override
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# ── Shared helpers ──
|
|
49
|
+
|
|
50
|
+
def arg(key)
|
|
51
|
+
@args[key.to_s] || @args[key.to_sym]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def result_val(key)
|
|
55
|
+
@result.is_a?(Hash) && (@result[key.to_s] || @result[key.to_sym])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def error?
|
|
59
|
+
@result.is_a?(Hash) && (@result[:error] || @result["error"])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def error_lines
|
|
63
|
+
msg = error_message
|
|
64
|
+
msg = msg[0..70] + "..." if msg.length > 70
|
|
65
|
+
["#{"FAILED".colorize(ERROR_BG)} #{msg.colorize(DIM)}"]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def error_message
|
|
69
|
+
if @result.is_a?(Hash)
|
|
70
|
+
(
|
|
71
|
+
@result[:message] ||
|
|
72
|
+
@result["message"] ||
|
|
73
|
+
@result[:error] ||
|
|
74
|
+
@result["error"]
|
|
75
|
+
).to_s
|
|
76
|
+
else
|
|
77
|
+
""
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def diff_lines
|
|
82
|
+
diff = result_val(:diff)
|
|
83
|
+
if diff && !diff.to_s.strip.empty?
|
|
84
|
+
[BruteCLI::Bat.diff_mode(diff, width: @width).chomp]
|
|
85
|
+
else
|
|
86
|
+
[]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def todo_lines(todos)
|
|
91
|
+
return [] unless todos && !todos.empty?
|
|
92
|
+
|
|
93
|
+
todos.map do |t|
|
|
94
|
+
t = t.transform_keys(&:to_s) if t.is_a?(Hash)
|
|
95
|
+
status = t["status"].to_s
|
|
96
|
+
ico = TODO_STATUS[status] || Emoji::SQUARE
|
|
97
|
+
content = t["content"] || t["id"] || "?"
|
|
98
|
+
" #{ico} #{content}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def normalize_args(args)
|
|
103
|
+
args.is_a?(Hash) ? args : {}
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
require_relative "tool_output/read"
|
|
108
|
+
require_relative "tool_output/write"
|
|
109
|
+
require_relative "tool_output/patch"
|
|
110
|
+
require_relative "tool_output/shell"
|
|
111
|
+
require_relative "tool_output/fs_search"
|
|
112
|
+
require_relative "tool_output/fetch"
|
|
113
|
+
require_relative "tool_output/remove"
|
|
114
|
+
require_relative "tool_output/undo"
|
|
115
|
+
require_relative "tool_output/delegate"
|
|
116
|
+
require_relative "tool_output/question"
|
|
117
|
+
require_relative "tool_output/todo_read"
|
|
118
|
+
require_relative "tool_output/todo_write"
|
|
119
|
+
|
|
120
|
+
MAP = {
|
|
121
|
+
"read" => Read,
|
|
122
|
+
"write" => Write,
|
|
123
|
+
"patch" => Patch,
|
|
124
|
+
"shell" => Shell,
|
|
125
|
+
"fs_search" => FsSearch,
|
|
126
|
+
"fetch" => Fetch,
|
|
127
|
+
"remove" => Remove,
|
|
128
|
+
"undo" => Undo,
|
|
129
|
+
"delegate" => Delegate,
|
|
130
|
+
"question" => Question,
|
|
131
|
+
"todo_read" => TodoRead,
|
|
132
|
+
"todo_write" => TodoWrite,
|
|
133
|
+
}.freeze
|
|
134
|
+
|
|
135
|
+
# Returns a ToolOutput instance for the given ToolCall.
|
|
136
|
+
def self.for(tool_call, width: 80)
|
|
137
|
+
klass = MAP[tool_call.name.to_s] || Base
|
|
138
|
+
klass.new(tool_call, width: width)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
data/lib/brute_cli/version.rb
CHANGED
data/lib/brute_cli.rb
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require 'brute'
|
|
4
4
|
|
|
5
|
-
begin
|
|
5
|
+
begin # why?
|
|
6
6
|
require 'brute_flow'
|
|
7
7
|
rescue LoadError
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
require 'tty-screen'
|
|
11
|
-
require 'tty-spinner'
|
|
12
11
|
require 'brute_cli/version'
|
|
13
12
|
require 'brute_cli/styles'
|
|
14
13
|
require 'brute_cli/emoji'
|
|
@@ -16,20 +15,24 @@ require 'brute_cli/bat'
|
|
|
16
15
|
require 'brute_cli/stream_formatter'
|
|
17
16
|
require 'brute_cli/fzf_menu'
|
|
18
17
|
require 'brute_cli/commands'
|
|
18
|
+
require 'brute_cli/spinner'
|
|
19
|
+
require 'brute_cli/configuration'
|
|
20
|
+
require 'brute_cli/buffer_output'
|
|
21
|
+
require 'brute_cli/execution'
|
|
19
22
|
require 'brute_cli/repl'
|
|
20
23
|
|
|
21
24
|
module BruteCLI
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"888 .o8
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
MONIKER = <<~MONIKER
|
|
26
|
+
.o8 .
|
|
27
|
+
"888 .o8
|
|
28
|
+
888oooo. oooo d8b oooo oooo .o888oo .ooooo.
|
|
29
|
+
d88' `88b `888""8P `888 `888 888 d88' `88b
|
|
30
|
+
888 888 888 888 888 888 888ooo888
|
|
31
|
+
888 888 888 888 888 888 . 888 .o
|
|
32
|
+
`Y8bod8P' d888b `V88V"V8P' "888" `Y8bod8P'
|
|
33
|
+
MONIKER
|
|
31
34
|
|
|
32
|
-
#
|
|
35
|
+
# museum of modern art, bruv...
|
|
33
36
|
|
|
34
37
|
def self.error(message)
|
|
35
38
|
$stderr.puts "#{"ERROR".colorize(ERROR_BG)} #{message.colorize(ERROR_FG)}"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brute_cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brute Contributors
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 1980-01-
|
|
10
|
+
date: 1980-01-01 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: brute
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.4.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.4.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: brute_flow
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -187,13 +187,41 @@ files:
|
|
|
187
187
|
- exe/brute
|
|
188
188
|
- lib/brute_cli.rb
|
|
189
189
|
- lib/brute_cli/bat.rb
|
|
190
|
+
- lib/brute_cli/buffer_output.rb
|
|
191
|
+
- lib/brute_cli/buffer_output/error.rb
|
|
192
|
+
- lib/brute_cli/buffer_output/model_line.rb
|
|
193
|
+
- lib/brute_cli/buffer_output/separator.rb
|
|
194
|
+
- lib/brute_cli/buffer_output/stats_bar.rb
|
|
190
195
|
- lib/brute_cli/commands.rb
|
|
196
|
+
- lib/brute_cli/configuration.rb
|
|
191
197
|
- lib/brute_cli/emoji.rb
|
|
198
|
+
- lib/brute_cli/execution.rb
|
|
192
199
|
- lib/brute_cli/fzf_menu.rb
|
|
200
|
+
- lib/brute_cli/phase.rb
|
|
201
|
+
- lib/brute_cli/phase/content_phase.rb
|
|
202
|
+
- lib/brute_cli/phase/tool_call.rb
|
|
203
|
+
- lib/brute_cli/phase/tool_phase.rb
|
|
193
204
|
- lib/brute_cli/question_screen.rb
|
|
194
205
|
- lib/brute_cli/repl.rb
|
|
206
|
+
- lib/brute_cli/spinner.rb
|
|
207
|
+
- lib/brute_cli/spinner/dots.rb
|
|
208
|
+
- lib/brute_cli/spinner/nyan.rb
|
|
209
|
+
- lib/brute_cli/spinner/puff_puff_pass.rb
|
|
195
210
|
- lib/brute_cli/stream_formatter.rb
|
|
196
211
|
- lib/brute_cli/styles.rb
|
|
212
|
+
- lib/brute_cli/tool_output.rb
|
|
213
|
+
- lib/brute_cli/tool_output/delegate.rb
|
|
214
|
+
- lib/brute_cli/tool_output/fetch.rb
|
|
215
|
+
- lib/brute_cli/tool_output/fs_search.rb
|
|
216
|
+
- lib/brute_cli/tool_output/patch.rb
|
|
217
|
+
- lib/brute_cli/tool_output/question.rb
|
|
218
|
+
- lib/brute_cli/tool_output/read.rb
|
|
219
|
+
- lib/brute_cli/tool_output/remove.rb
|
|
220
|
+
- lib/brute_cli/tool_output/shell.rb
|
|
221
|
+
- lib/brute_cli/tool_output/todo_read.rb
|
|
222
|
+
- lib/brute_cli/tool_output/todo_write.rb
|
|
223
|
+
- lib/brute_cli/tool_output/undo.rb
|
|
224
|
+
- lib/brute_cli/tool_output/write.rb
|
|
197
225
|
- lib/brute_cli/version.rb
|
|
198
226
|
licenses:
|
|
199
227
|
- MIT
|