openclacky 0.5.6 → 0.6.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 +43 -0
- data/docs/ui2-architecture.md +124 -0
- data/lib/clacky/agent.rb +245 -340
- data/lib/clacky/agent_config.rb +1 -7
- data/lib/clacky/cli.rb +156 -397
- data/lib/clacky/client.rb +68 -36
- data/lib/clacky/gitignore_parser.rb +26 -12
- data/lib/clacky/model_pricing.rb +6 -2
- data/lib/clacky/session_manager.rb +6 -2
- data/lib/clacky/tools/glob.rb +65 -9
- data/lib/clacky/tools/grep.rb +4 -120
- data/lib/clacky/tools/run_project.rb +5 -0
- data/lib/clacky/tools/safe_shell.rb +49 -13
- data/lib/clacky/tools/shell.rb +1 -49
- data/lib/clacky/tools/web_fetch.rb +2 -2
- data/lib/clacky/tools/web_search.rb +38 -26
- data/lib/clacky/ui2/README.md +214 -0
- data/lib/clacky/ui2/components/base_component.rb +163 -0
- data/lib/clacky/ui2/components/common_component.rb +89 -0
- data/lib/clacky/ui2/components/inline_input.rb +187 -0
- data/lib/clacky/ui2/components/input_area.rb +1029 -0
- data/lib/clacky/ui2/components/message_component.rb +76 -0
- data/lib/clacky/ui2/components/output_area.rb +112 -0
- data/lib/clacky/ui2/components/todo_area.rb +137 -0
- data/lib/clacky/ui2/components/tool_component.rb +106 -0
- data/lib/clacky/ui2/components/welcome_banner.rb +93 -0
- data/lib/clacky/ui2/layout_manager.rb +331 -0
- data/lib/clacky/ui2/line_editor.rb +201 -0
- data/lib/clacky/ui2/screen_buffer.rb +238 -0
- data/lib/clacky/ui2/theme_manager.rb +68 -0
- data/lib/clacky/ui2/themes/base_theme.rb +99 -0
- data/lib/clacky/ui2/themes/hacker_theme.rb +56 -0
- data/lib/clacky/ui2/themes/minimal_theme.rb +50 -0
- data/lib/clacky/ui2/ui_controller.rb +720 -0
- data/lib/clacky/ui2/view_renderer.rb +160 -0
- data/lib/clacky/ui2.rb +37 -0
- data/lib/clacky/utils/file_ignore_helper.rb +126 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky.rb +1 -6
- metadata +38 -6
- data/lib/clacky/ui/banner.rb +0 -155
- data/lib/clacky/ui/enhanced_prompt.rb +0 -786
- data/lib/clacky/ui/formatter.rb +0 -209
- data/lib/clacky/ui/statusbar.rb +0 -96
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "components/message_component"
|
|
4
|
+
require_relative "components/tool_component"
|
|
5
|
+
require_relative "components/common_component"
|
|
6
|
+
|
|
7
|
+
module Clacky
|
|
8
|
+
module UI2
|
|
9
|
+
# ViewRenderer coordinates all UI components and provides a unified rendering interface
|
|
10
|
+
class ViewRenderer
|
|
11
|
+
def initialize
|
|
12
|
+
@message_component = Components::MessageComponent.new
|
|
13
|
+
@tool_component = Components::ToolComponent.new
|
|
14
|
+
@common_component = Components::CommonComponent.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Render a user message
|
|
18
|
+
# @param content [String] Message content
|
|
19
|
+
# @param timestamp [Time, nil] Optional timestamp
|
|
20
|
+
# @return [String] Rendered message
|
|
21
|
+
def render_user_message(content, timestamp: nil)
|
|
22
|
+
@message_component.render(
|
|
23
|
+
role: "user",
|
|
24
|
+
content: content,
|
|
25
|
+
timestamp: timestamp
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Render an assistant message
|
|
30
|
+
# @param content [String] Message content
|
|
31
|
+
# @param timestamp [Time, nil] Optional timestamp
|
|
32
|
+
# @return [String] Rendered message
|
|
33
|
+
def render_assistant_message(content, timestamp: nil)
|
|
34
|
+
@message_component.render(
|
|
35
|
+
role: "assistant",
|
|
36
|
+
content: content,
|
|
37
|
+
timestamp: timestamp
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Render a system message
|
|
42
|
+
# @param content [String] Message content
|
|
43
|
+
# @param timestamp [Time, nil] Optional timestamp
|
|
44
|
+
# @return [String] Rendered message
|
|
45
|
+
def render_system_message(content, timestamp: nil)
|
|
46
|
+
@message_component.render(
|
|
47
|
+
role: "system",
|
|
48
|
+
content: content,
|
|
49
|
+
timestamp: timestamp
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Render a tool call
|
|
54
|
+
# @param tool_name [String] Tool name
|
|
55
|
+
# @param formatted_call [String] Formatted call description
|
|
56
|
+
# @return [String] Rendered tool call
|
|
57
|
+
def render_tool_call(tool_name:, formatted_call:)
|
|
58
|
+
@tool_component.render(
|
|
59
|
+
type: :call,
|
|
60
|
+
tool_name: tool_name,
|
|
61
|
+
formatted_call: formatted_call
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Render a tool result
|
|
66
|
+
# @param result [String] Tool result
|
|
67
|
+
# @return [String] Rendered tool result
|
|
68
|
+
def render_tool_result(result:)
|
|
69
|
+
@tool_component.render(
|
|
70
|
+
type: :result,
|
|
71
|
+
result: result
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Render a tool error
|
|
76
|
+
# @param error [String] Error message
|
|
77
|
+
# @return [String] Rendered tool error
|
|
78
|
+
def render_tool_error(error:)
|
|
79
|
+
@tool_component.render(
|
|
80
|
+
type: :error,
|
|
81
|
+
error: error
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Render a tool denied message
|
|
86
|
+
# @param tool_name [String] Tool name
|
|
87
|
+
# @return [String] Rendered tool denied
|
|
88
|
+
def render_tool_denied(tool_name:)
|
|
89
|
+
@tool_component.render(
|
|
90
|
+
type: :denied,
|
|
91
|
+
tool_name: tool_name
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Render a tool planned message
|
|
96
|
+
# @param tool_name [String] Tool name
|
|
97
|
+
# @return [String] Rendered tool planned
|
|
98
|
+
def render_tool_planned(tool_name:)
|
|
99
|
+
@tool_component.render(
|
|
100
|
+
type: :planned,
|
|
101
|
+
tool_name: tool_name
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Render thinking indicator
|
|
106
|
+
# @return [String] Thinking indicator
|
|
107
|
+
def render_thinking
|
|
108
|
+
@common_component.render_thinking
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Render progress message
|
|
112
|
+
# @param message [String] Progress message
|
|
113
|
+
# @return [String] Progress indicator
|
|
114
|
+
def render_progress(message)
|
|
115
|
+
@common_component.render_progress(message)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Render success message
|
|
119
|
+
# @param message [String] Success message
|
|
120
|
+
# @return [String] Success message
|
|
121
|
+
def render_success(message)
|
|
122
|
+
@common_component.render_success(message)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Render error message
|
|
126
|
+
# @param message [String] Error message
|
|
127
|
+
# @return [String] Error message
|
|
128
|
+
def render_error(message)
|
|
129
|
+
@common_component.render_error(message)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Render warning message
|
|
133
|
+
# @param message [String] Warning message
|
|
134
|
+
# @return [String] Warning message
|
|
135
|
+
def render_warning(message)
|
|
136
|
+
@common_component.render_warning(message)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Render task completion summary
|
|
140
|
+
# @param iterations [Integer] Number of iterations
|
|
141
|
+
# @param cost [Float] Cost in USD
|
|
142
|
+
# @param duration [Float] Duration in seconds
|
|
143
|
+
# @param cache_tokens [Integer] Cache read tokens
|
|
144
|
+
# @param cache_requests [Integer] Total cache requests count
|
|
145
|
+
# @param cache_hits [Integer] Cache hit requests count
|
|
146
|
+
# @return [String] Formatted completion summary
|
|
147
|
+
def render_task_complete(iterations:, cost:, duration: nil, cache_tokens: nil, cache_requests: nil, cache_hits: nil)
|
|
148
|
+
@common_component.render_task_complete(
|
|
149
|
+
iterations: iterations,
|
|
150
|
+
cost: cost,
|
|
151
|
+
duration: duration,
|
|
152
|
+
cache_tokens: cache_tokens,
|
|
153
|
+
cache_requests: cache_requests,
|
|
154
|
+
cache_hits: cache_hits
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
data/lib/clacky/ui2.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UI2 - MVC-based terminal UI system for Clacky
|
|
4
|
+
# Provides split-screen interface with scrollable output and fixed input
|
|
5
|
+
|
|
6
|
+
require_relative "ui2/theme_manager"
|
|
7
|
+
require_relative "ui2/screen_buffer"
|
|
8
|
+
require_relative "ui2/layout_manager"
|
|
9
|
+
require_relative "ui2/view_renderer"
|
|
10
|
+
require_relative "ui2/ui_controller"
|
|
11
|
+
|
|
12
|
+
require_relative "ui2/components/base_component"
|
|
13
|
+
require_relative "ui2/components/output_area"
|
|
14
|
+
require_relative "ui2/components/input_area"
|
|
15
|
+
require_relative "ui2/components/message_component"
|
|
16
|
+
require_relative "ui2/components/tool_component"
|
|
17
|
+
require_relative "ui2/components/common_component"
|
|
18
|
+
require_relative "ui2/components/welcome_banner"
|
|
19
|
+
|
|
20
|
+
module Clacky
|
|
21
|
+
module UI2
|
|
22
|
+
# Version of the UI2 system
|
|
23
|
+
VERSION = "1.0.0"
|
|
24
|
+
|
|
25
|
+
# Quick start: Create a UI controller and run
|
|
26
|
+
# @param config [Hash] Optional configuration (working_dir, mode, model)
|
|
27
|
+
# @example
|
|
28
|
+
# controller = Clacky::UI2::UIController.new
|
|
29
|
+
# controller.on_input { |input| puts "Got: #{input}" }
|
|
30
|
+
# controller.start
|
|
31
|
+
def self.start(config = {}, &block)
|
|
32
|
+
controller = UIController.new(config)
|
|
33
|
+
controller.on_input(&block) if block_given?
|
|
34
|
+
controller.start
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clacky
|
|
4
|
+
module Utils
|
|
5
|
+
# Helper module for file ignoring functionality shared between tools
|
|
6
|
+
module FileIgnoreHelper
|
|
7
|
+
# Default patterns to ignore when .gitignore is not available
|
|
8
|
+
DEFAULT_IGNORED_PATTERNS = [
|
|
9
|
+
'node_modules',
|
|
10
|
+
'vendor/bundle',
|
|
11
|
+
'.git',
|
|
12
|
+
'.svn',
|
|
13
|
+
'tmp',
|
|
14
|
+
'log',
|
|
15
|
+
'coverage',
|
|
16
|
+
'dist',
|
|
17
|
+
'build',
|
|
18
|
+
'.bundle',
|
|
19
|
+
'.sass-cache',
|
|
20
|
+
'.DS_Store',
|
|
21
|
+
'*.log'
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
# Config file patterns that should always be searchable/visible
|
|
25
|
+
CONFIG_FILE_PATTERNS = [
|
|
26
|
+
/\.env/,
|
|
27
|
+
/\.ya?ml$/,
|
|
28
|
+
/\.json$/,
|
|
29
|
+
/\.toml$/,
|
|
30
|
+
/\.ini$/,
|
|
31
|
+
/\.conf$/,
|
|
32
|
+
/\.config$/,
|
|
33
|
+
/config\//,
|
|
34
|
+
/\.config\//
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
# Find .gitignore file in the search path or parent directories
|
|
38
|
+
# Only searches within the search path and up to the current working directory
|
|
39
|
+
def self.find_gitignore(path)
|
|
40
|
+
search_path = File.directory?(path) ? path : File.dirname(path)
|
|
41
|
+
|
|
42
|
+
# Look for .gitignore in current and parent directories
|
|
43
|
+
current = File.expand_path(search_path)
|
|
44
|
+
cwd = File.expand_path(Dir.pwd)
|
|
45
|
+
root = File.expand_path('/')
|
|
46
|
+
|
|
47
|
+
# Limit search: only go up to current working directory
|
|
48
|
+
# This prevents finding .gitignore files from unrelated parent directories
|
|
49
|
+
# when searching in temporary directories (like /tmp in tests)
|
|
50
|
+
search_limit = if current.start_with?(cwd)
|
|
51
|
+
cwd
|
|
52
|
+
else
|
|
53
|
+
current
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
loop do
|
|
57
|
+
gitignore = File.join(current, '.gitignore')
|
|
58
|
+
return gitignore if File.exist?(gitignore)
|
|
59
|
+
|
|
60
|
+
# Stop if we've reached the search limit or root
|
|
61
|
+
break if current == search_limit || current == root
|
|
62
|
+
current = File.dirname(current)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check if file should be ignored based on .gitignore or default patterns
|
|
69
|
+
def self.should_ignore_file?(file, base_path, gitignore)
|
|
70
|
+
# Always calculate path relative to base_path for consistency
|
|
71
|
+
# Expand both paths to handle symlinks and relative paths correctly
|
|
72
|
+
expanded_file = File.expand_path(file)
|
|
73
|
+
expanded_base = File.expand_path(base_path)
|
|
74
|
+
|
|
75
|
+
# For files, use the directory as base
|
|
76
|
+
expanded_base = File.dirname(expanded_base) if File.file?(expanded_base)
|
|
77
|
+
|
|
78
|
+
# Calculate relative path
|
|
79
|
+
if expanded_file.start_with?(expanded_base)
|
|
80
|
+
relative_path = expanded_file[(expanded_base.length + 1)..-1] || File.basename(expanded_file)
|
|
81
|
+
else
|
|
82
|
+
# File is outside base path - use just the filename
|
|
83
|
+
relative_path = File.basename(expanded_file)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Clean up relative path
|
|
87
|
+
relative_path = relative_path.sub(/^\.\//, '') if relative_path
|
|
88
|
+
|
|
89
|
+
if gitignore
|
|
90
|
+
# Use .gitignore rules
|
|
91
|
+
gitignore.ignored?(relative_path)
|
|
92
|
+
else
|
|
93
|
+
# Use default ignore patterns - only match against relative path components
|
|
94
|
+
DEFAULT_IGNORED_PATTERNS.any? do |pattern|
|
|
95
|
+
if pattern.include?('*')
|
|
96
|
+
File.fnmatch(pattern, relative_path, File::FNM_PATHNAME | File::FNM_DOTMATCH)
|
|
97
|
+
else
|
|
98
|
+
# Match pattern as a path component (not substring of absolute path)
|
|
99
|
+
relative_path.start_with?("#{pattern}/") ||
|
|
100
|
+
relative_path.include?("/#{pattern}/") ||
|
|
101
|
+
relative_path == pattern ||
|
|
102
|
+
File.basename(relative_path) == pattern
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check if file is a config file (should not be ignored even if in .gitignore)
|
|
109
|
+
def self.is_config_file?(file)
|
|
110
|
+
CONFIG_FILE_PATTERNS.any? { |pattern| file.match?(pattern) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Check if file is binary (contains null bytes)
|
|
114
|
+
def self.binary_file?(file)
|
|
115
|
+
# Simple heuristic: check if file contains null bytes in first 8KB
|
|
116
|
+
return false unless File.exist?(file)
|
|
117
|
+
return false if File.size(file).zero?
|
|
118
|
+
|
|
119
|
+
sample = File.read(file, 8192, encoding: "ASCII-8BIT")
|
|
120
|
+
sample.include?("\x00")
|
|
121
|
+
rescue StandardError
|
|
122
|
+
true
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/clacky/version.rb
CHANGED
data/lib/clacky.rb
CHANGED
|
@@ -15,6 +15,7 @@ require_relative "clacky/session_manager"
|
|
|
15
15
|
require_relative "clacky/gitignore_parser"
|
|
16
16
|
require_relative "clacky/utils/limit_stack"
|
|
17
17
|
require_relative "clacky/utils/path_helper"
|
|
18
|
+
require_relative "clacky/utils/file_ignore_helper"
|
|
18
19
|
require_relative "clacky/tools/base"
|
|
19
20
|
|
|
20
21
|
require_relative "clacky/tools/shell"
|
|
@@ -31,12 +32,6 @@ require_relative "clacky/tools/safe_shell"
|
|
|
31
32
|
require_relative "clacky/tools/trash_manager"
|
|
32
33
|
require_relative "clacky/agent"
|
|
33
34
|
|
|
34
|
-
# UI components
|
|
35
|
-
require_relative "clacky/ui/banner"
|
|
36
|
-
require_relative "clacky/ui/enhanced_prompt"
|
|
37
|
-
require_relative "clacky/ui/statusbar"
|
|
38
|
-
require_relative "clacky/ui/formatter"
|
|
39
|
-
|
|
40
35
|
require_relative "clacky/cli"
|
|
41
36
|
|
|
42
37
|
module Clacky
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openclacky
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- windy
|
|
@@ -107,6 +107,20 @@ dependencies:
|
|
|
107
107
|
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
109
|
version: '0.8'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: base64
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: 0.3.0
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: 0.3.0
|
|
110
124
|
description: OpenClacky is a Ruby CLI tool for interacting with AI models via OpenAI-compatible
|
|
111
125
|
APIs. It provides chat functionality and autonomous AI agent capabilities with tool
|
|
112
126
|
use.
|
|
@@ -130,6 +144,7 @@ files:
|
|
|
130
144
|
- bin/openclacky
|
|
131
145
|
- clacky-legacy/clacky.gemspec
|
|
132
146
|
- clacky-legacy/clarky.gemspec
|
|
147
|
+
- docs/ui2-architecture.md
|
|
133
148
|
- lib/clacky.rb
|
|
134
149
|
- lib/clacky/agent.rb
|
|
135
150
|
- lib/clacky/agent_config.rb
|
|
@@ -157,11 +172,28 @@ files:
|
|
|
157
172
|
- lib/clacky/tools/web_search.rb
|
|
158
173
|
- lib/clacky/tools/write.rb
|
|
159
174
|
- lib/clacky/trash_directory.rb
|
|
160
|
-
- lib/clacky/
|
|
161
|
-
- lib/clacky/
|
|
162
|
-
- lib/clacky/
|
|
163
|
-
- lib/clacky/
|
|
175
|
+
- lib/clacky/ui2.rb
|
|
176
|
+
- lib/clacky/ui2/README.md
|
|
177
|
+
- lib/clacky/ui2/components/base_component.rb
|
|
178
|
+
- lib/clacky/ui2/components/common_component.rb
|
|
179
|
+
- lib/clacky/ui2/components/inline_input.rb
|
|
180
|
+
- lib/clacky/ui2/components/input_area.rb
|
|
181
|
+
- lib/clacky/ui2/components/message_component.rb
|
|
182
|
+
- lib/clacky/ui2/components/output_area.rb
|
|
183
|
+
- lib/clacky/ui2/components/todo_area.rb
|
|
184
|
+
- lib/clacky/ui2/components/tool_component.rb
|
|
185
|
+
- lib/clacky/ui2/components/welcome_banner.rb
|
|
186
|
+
- lib/clacky/ui2/layout_manager.rb
|
|
187
|
+
- lib/clacky/ui2/line_editor.rb
|
|
188
|
+
- lib/clacky/ui2/screen_buffer.rb
|
|
189
|
+
- lib/clacky/ui2/theme_manager.rb
|
|
190
|
+
- lib/clacky/ui2/themes/base_theme.rb
|
|
191
|
+
- lib/clacky/ui2/themes/hacker_theme.rb
|
|
192
|
+
- lib/clacky/ui2/themes/minimal_theme.rb
|
|
193
|
+
- lib/clacky/ui2/ui_controller.rb
|
|
194
|
+
- lib/clacky/ui2/view_renderer.rb
|
|
164
195
|
- lib/clacky/utils/arguments_parser.rb
|
|
196
|
+
- lib/clacky/utils/file_ignore_helper.rb
|
|
165
197
|
- lib/clacky/utils/limit_stack.rb
|
|
166
198
|
- lib/clacky/utils/path_helper.rb
|
|
167
199
|
- lib/clacky/version.rb
|
|
@@ -187,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
187
219
|
- !ruby/object:Gem::Version
|
|
188
220
|
version: '0'
|
|
189
221
|
requirements: []
|
|
190
|
-
rubygems_version:
|
|
222
|
+
rubygems_version: 4.0.4
|
|
191
223
|
specification_version: 4
|
|
192
224
|
summary: A command-line interface for AI models (Claude, OpenAI, etc.)
|
|
193
225
|
test_files: []
|
data/lib/clacky/ui/banner.rb
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "pastel"
|
|
4
|
-
|
|
5
|
-
module Clacky
|
|
6
|
-
module UI
|
|
7
|
-
# ASCII art banner and startup screen with Matrix/hacker aesthetic
|
|
8
|
-
class Banner
|
|
9
|
-
LOGO = <<~'LOGO'
|
|
10
|
-
██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗██╗ █████╗ ██████╗██╗ ██╗██╗ ██╗
|
|
11
|
-
██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██║ ██╔══██╗██╔════╝██║ ██╔╝╚██╗ ██╔╝
|
|
12
|
-
██║ ██║██████╔╝█████╗ ██╔██╗ ██║██║ ██║ ███████║██║ █████╔╝ ╚████╔╝
|
|
13
|
-
██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ ██╔══██║██║ ██╔═██╗ ╚██╔╝
|
|
14
|
-
╚██████╔╝██║ ███████╗██║ ╚████║╚██████╗███████╗██║ ██║╚██████╗██║ ██╗ ██║
|
|
15
|
-
╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝
|
|
16
|
-
LOGO
|
|
17
|
-
|
|
18
|
-
TAGLINE = "[>] AI Coding Assistant & Technical Co-founder"
|
|
19
|
-
|
|
20
|
-
TIPS = [
|
|
21
|
-
"[*] Ask questions, edit files, or run commands",
|
|
22
|
-
"[*] Be specific for the best results",
|
|
23
|
-
"[*] Create .clackyrules to customize interactions",
|
|
24
|
-
"[*] Type /help for more commands"
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
def initialize
|
|
28
|
-
@pastel = Pastel.new
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Display full startup banner
|
|
32
|
-
def display_startup
|
|
33
|
-
puts
|
|
34
|
-
puts @pastel.bright_green(LOGO)
|
|
35
|
-
puts
|
|
36
|
-
puts @pastel.bright_cyan(TAGLINE)
|
|
37
|
-
puts
|
|
38
|
-
display_tips
|
|
39
|
-
puts
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Display welcome message for agent mode
|
|
43
|
-
def display_agent_welcome(working_dir:, mode:, max_iterations:, max_cost:)
|
|
44
|
-
puts
|
|
45
|
-
puts separator("=")
|
|
46
|
-
puts @pastel.bright_green("[+] AGENT MODE INITIALIZED")
|
|
47
|
-
puts separator("=")
|
|
48
|
-
puts
|
|
49
|
-
puts info_line("Working Directory", working_dir)
|
|
50
|
-
puts info_line("Permission Mode", mode)
|
|
51
|
-
puts info_line("Max Iterations", max_iterations)
|
|
52
|
-
puts info_line("Max Cost", "$#{max_cost}")
|
|
53
|
-
puts
|
|
54
|
-
puts @pastel.dim("[!] Type 'exit' or 'quit' to terminate session")
|
|
55
|
-
puts separator("-")
|
|
56
|
-
puts
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Display session continuation info
|
|
60
|
-
def display_session_continue(session_id:, created_at:, tasks:, cost:)
|
|
61
|
-
puts
|
|
62
|
-
puts separator("=")
|
|
63
|
-
puts @pastel.bright_yellow("[~] RESUMING SESSION")
|
|
64
|
-
puts separator("=")
|
|
65
|
-
puts
|
|
66
|
-
puts info_line("Session ID", session_id)
|
|
67
|
-
puts info_line("Started", created_at)
|
|
68
|
-
puts info_line("Tasks Completed", tasks)
|
|
69
|
-
puts info_line("Total Cost", "$#{cost}")
|
|
70
|
-
puts separator("-")
|
|
71
|
-
puts
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Display task completion summary
|
|
75
|
-
def display_task_complete(iterations:, cost:, total_tasks:, total_cost:, cost_source:, cache_stats: {})
|
|
76
|
-
puts
|
|
77
|
-
puts separator("-")
|
|
78
|
-
puts @pastel.bright_green("[✓] TASK COMPLETED")
|
|
79
|
-
puts info_line("Iterations", iterations)
|
|
80
|
-
|
|
81
|
-
# Add cost source indicator
|
|
82
|
-
cost_suffix = case cost_source
|
|
83
|
-
when :api
|
|
84
|
-
" (by API)"
|
|
85
|
-
when :price
|
|
86
|
-
" (by PRICE)"
|
|
87
|
-
when :default
|
|
88
|
-
" (by DEFAULT)"
|
|
89
|
-
else
|
|
90
|
-
" (estimated)"
|
|
91
|
-
end
|
|
92
|
-
puts info_line("Cost", "$#{cost}#{cost_suffix}")
|
|
93
|
-
puts info_line("Session Total", "#{total_tasks} tasks, $#{total_cost}")
|
|
94
|
-
|
|
95
|
-
# Display cache statistics if available
|
|
96
|
-
if cache_stats[:total_requests] && cache_stats[:total_requests] > 0
|
|
97
|
-
puts
|
|
98
|
-
puts @pastel.cyan(" [Prompt Caching]")
|
|
99
|
-
puts info_line(" Cache Reads/Writes", "#{cache_stats[:cache_read_input_tokens]}/#{cache_stats[:cache_creation_input_tokens]} tokens")
|
|
100
|
-
|
|
101
|
-
hit_rate = (cache_stats[:cache_hit_requests].to_f / cache_stats[:total_requests] * 100).round(1)
|
|
102
|
-
puts info_line(" Cache Hit Rate", "#{hit_rate}% (#{cache_stats[:cache_hit_requests]}/#{cache_stats[:total_requests]} requests)")
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
puts separator("-")
|
|
106
|
-
puts
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Display error message
|
|
110
|
-
def display_error(message, details: nil)
|
|
111
|
-
puts
|
|
112
|
-
puts separator("-")
|
|
113
|
-
puts @pastel.bright_red("[✗] ERROR")
|
|
114
|
-
puts @pastel.red(" #{message}")
|
|
115
|
-
if details
|
|
116
|
-
puts @pastel.dim(" #{details}")
|
|
117
|
-
end
|
|
118
|
-
puts separator("-")
|
|
119
|
-
puts
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Display session end
|
|
123
|
-
def display_goodbye(total_tasks:, total_cost:)
|
|
124
|
-
puts
|
|
125
|
-
puts separator("=")
|
|
126
|
-
puts @pastel.bright_cyan("[#] SESSION TERMINATED")
|
|
127
|
-
puts separator("=")
|
|
128
|
-
puts
|
|
129
|
-
puts info_line("Total Tasks", total_tasks)
|
|
130
|
-
puts info_line("Total Cost", "$#{total_cost}")
|
|
131
|
-
puts
|
|
132
|
-
puts @pastel.dim("[*] All session data has been saved")
|
|
133
|
-
puts
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
private
|
|
137
|
-
|
|
138
|
-
def display_tips
|
|
139
|
-
TIPS.each do |tip|
|
|
140
|
-
puts @pastel.dim(tip)
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def info_line(label, value)
|
|
145
|
-
label_text = @pastel.cyan("[#{label}]")
|
|
146
|
-
value_text = @pastel.white(value)
|
|
147
|
-
" #{label_text} #{value_text}"
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def separator(char = "-")
|
|
151
|
-
@pastel.dim(char * 80)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|