kward 0.70.0 → 0.71.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/.github/workflows/pages.yml +1 -1
- data/CHANGELOG.md +48 -2
- data/Gemfile +2 -0
- data/Gemfile.lock +90 -2
- data/README.md +30 -6
- data/Rakefile +96 -0
- data/doc/agent-tools.md +43 -0
- data/doc/api.md +92 -0
- data/doc/authentication.md +39 -25
- data/doc/configuration.md +1 -15
- data/doc/context-tools.md +70 -0
- data/doc/plugins.md +2 -2
- data/doc/releasing.md +14 -5
- data/doc/rpc.md +3 -11
- data/doc/session-management.md +220 -0
- data/doc/usage.md +7 -8
- data/doc/workspace-tools.md +105 -0
- data/lib/kward/cli/commands.rb +8 -0
- data/lib/kward/cli/openrouter_commands.rb +55 -0
- data/lib/kward/cli/prompt_interface.rb +80 -6
- data/lib/kward/cli/rendering.rb +11 -6
- data/lib/kward/cli/sessions.rb +260 -11
- data/lib/kward/cli/settings.rb +0 -30
- data/lib/kward/cli/slash_commands.rb +24 -6
- data/lib/kward/cli.rb +13 -0
- data/lib/kward/compactor.rb +4 -1
- data/lib/kward/config_files.rb +4 -6
- data/lib/kward/conversation.rb +49 -20
- data/lib/kward/model/client.rb +37 -50
- data/lib/kward/model/context_usage.rb +13 -6
- data/lib/kward/model/model_info.rb +92 -16
- data/lib/kward/model/payloads.rb +2 -0
- data/lib/kward/openrouter_model_cache.rb +120 -0
- data/lib/kward/plugin_registry.rb +47 -1
- data/lib/kward/prompt_interface/banner.rb +16 -51
- data/lib/kward/prompt_interface/composer_controller.rb +60 -87
- data/lib/kward/prompt_interface/composer_renderer.rb +7 -1
- data/lib/kward/prompt_interface/key_handler.rb +31 -10
- data/lib/kward/prompt_interface/layout.rb +2 -2
- data/lib/kward/prompt_interface/prompt_renderer.rb +32 -13
- data/lib/kward/prompt_interface/question_prompt.rb +34 -42
- data/lib/kward/prompt_interface/runtime_state.rb +6 -1
- data/lib/kward/prompt_interface/screen.rb +1 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +513 -54
- data/lib/kward/prompt_interface/transcript_buffer.rb +7 -16
- data/lib/kward/prompt_interface/transcript_renderer.rb +3 -3
- data/lib/kward/prompt_interface.rb +22 -28
- data/lib/kward/prompts/commands.rb +2 -1
- data/lib/kward/prompts.rb +2 -2
- data/lib/kward/rpc/server.rb +3 -8
- data/lib/kward/rpc/session_manager.rb +17 -6
- data/lib/kward/session_store.rb +23 -4
- data/lib/kward/telemetry/logger.rb +5 -3
- data/lib/kward/tool_output_compactor.rb +127 -0
- data/lib/kward/tools/base.rb +8 -2
- data/lib/kward/tools/registry.rb +37 -6
- data/lib/kward/tools/retrieve_tool_output.rb +71 -0
- data/lib/kward/tools/search/web.rb +2 -2
- data/lib/kward/tools/summarize_file_structure.rb +29 -0
- data/lib/kward/tools/tool_call.rb +2 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workspace.rb +58 -2
- data/templates/default/fulldoc/html/css/kward.css +256 -7
- data/templates/default/fulldoc/html/full_list.erb +107 -0
- data/templates/default/fulldoc/html/js/kward.js +161 -2
- data/templates/default/fulldoc/html/setup.rb +8 -0
- data/templates/default/kward_navigation.rb +91 -0
- data/templates/default/layout/html/layout.erb +39 -8
- data/templates/default/layout/html/setup.rb +33 -38
- metadata +13 -3
- data/lib/kward/resources/avatar_kward_logo.rb +0 -50
- data/lib/kward/resources/pixel_logo.rb +0 -232
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
require "zlib"
|
|
2
|
-
|
|
3
|
-
# Namespace for the Kward CLI agent runtime.
|
|
4
|
-
module Kward
|
|
5
|
-
# Pixel-art logo data and rendering helpers.
|
|
6
|
-
module PixelLogo
|
|
7
|
-
PNG_SIGNATURE = "\x89PNG\r\n\x1a\n".b.freeze
|
|
8
|
-
TRANSPARENT_ALPHA = 128
|
|
9
|
-
|
|
10
|
-
module_function
|
|
11
|
-
|
|
12
|
-
def rows_from_png(path, width:, height:)
|
|
13
|
-
rows_from_pixels(indexed_png_pixels(path), width: width, height: height)
|
|
14
|
-
rescue StandardError
|
|
15
|
-
[]
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def rows_from_pixels(pixels, width:, height:)
|
|
19
|
-
scaled = scale_pixels(pixels, width: width, height: height)
|
|
20
|
-
render_rows(scaled)
|
|
21
|
-
rescue StandardError
|
|
22
|
-
[]
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def half_block_rows_from_png(path, width:, pixel_height:)
|
|
26
|
-
half_block_rows_from_pixels(indexed_png_pixels(path), width: width, pixel_height: pixel_height)
|
|
27
|
-
rescue StandardError
|
|
28
|
-
[]
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def half_block_rows_from_pixels(pixels, width:, pixel_height:)
|
|
32
|
-
scaled = scale_pixels(pixels, width: width, height: pixel_height)
|
|
33
|
-
render_half_block_rows(scaled)
|
|
34
|
-
rescue StandardError
|
|
35
|
-
[]
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def indexed_png_pixels(path)
|
|
39
|
-
data = File.binread(path)
|
|
40
|
-
raise "Invalid PNG" unless data.start_with?(PNG_SIGNATURE)
|
|
41
|
-
|
|
42
|
-
png_width = nil
|
|
43
|
-
png_height = nil
|
|
44
|
-
bit_depth = nil
|
|
45
|
-
color_type = nil
|
|
46
|
-
interlace = nil
|
|
47
|
-
palette = nil
|
|
48
|
-
transparency = []
|
|
49
|
-
idat = +"".b
|
|
50
|
-
|
|
51
|
-
each_chunk(data) do |type, chunk|
|
|
52
|
-
case type
|
|
53
|
-
when "IHDR"
|
|
54
|
-
png_width, png_height, bit_depth, color_type, _compression, _filter, interlace = chunk.unpack("NNCCCCC")
|
|
55
|
-
when "PLTE"
|
|
56
|
-
palette = chunk.bytes.each_slice(3).map { |red, green, blue| [red, green, blue, 255] }
|
|
57
|
-
when "tRNS"
|
|
58
|
-
transparency = chunk.bytes
|
|
59
|
-
when "IDAT"
|
|
60
|
-
idat << chunk
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
raise "Unsupported PNG" unless png_width && png_height && palette
|
|
65
|
-
raise "Unsupported PNG" unless bit_depth == 8 && color_type == 3 && interlace == 0
|
|
66
|
-
|
|
67
|
-
transparency.each_with_index do |alpha, index|
|
|
68
|
-
palette[index] = palette[index][0, 3] + [alpha] if palette[index]
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
unfilter_indexed_rows(Zlib::Inflate.inflate(idat), png_width, png_height).map do |row|
|
|
72
|
-
row.map { |index| palette[index] || [0, 0, 0, 0] }
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def each_chunk(data)
|
|
77
|
-
offset = PNG_SIGNATURE.bytesize
|
|
78
|
-
while offset < data.bytesize
|
|
79
|
-
length = data[offset, 4].unpack1("N")
|
|
80
|
-
type = data[offset + 4, 4]
|
|
81
|
-
chunk = data[offset + 8, length]
|
|
82
|
-
yield type, chunk
|
|
83
|
-
offset += length + 12
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def unfilter_indexed_rows(raw, width, height)
|
|
88
|
-
rows = []
|
|
89
|
-
previous = Array.new(width, 0)
|
|
90
|
-
offset = 0
|
|
91
|
-
|
|
92
|
-
height.times do
|
|
93
|
-
filter = raw.getbyte(offset)
|
|
94
|
-
offset += 1
|
|
95
|
-
scanline = raw.byteslice(offset, width).bytes
|
|
96
|
-
offset += width
|
|
97
|
-
row = unfilter_scanline(scanline, previous, filter)
|
|
98
|
-
rows << row
|
|
99
|
-
previous = row
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
rows
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def unfilter_scanline(scanline, previous, filter)
|
|
106
|
-
row = []
|
|
107
|
-
scanline.each_with_index do |byte, index|
|
|
108
|
-
left = index.zero? ? 0 : row[index - 1]
|
|
109
|
-
up = previous[index] || 0
|
|
110
|
-
up_left = index.zero? ? 0 : previous[index - 1]
|
|
111
|
-
row << case filter
|
|
112
|
-
when 0 then byte
|
|
113
|
-
when 1 then (byte + left) & 0xff
|
|
114
|
-
when 2 then (byte + up) & 0xff
|
|
115
|
-
when 3 then (byte + ((left + up) / 2)) & 0xff
|
|
116
|
-
when 4 then (byte + paeth(left, up, up_left)) & 0xff
|
|
117
|
-
else byte
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
row
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def paeth(left, up, up_left)
|
|
124
|
-
estimate = left + up - up_left
|
|
125
|
-
left_distance = (estimate - left).abs
|
|
126
|
-
up_distance = (estimate - up).abs
|
|
127
|
-
up_left_distance = (estimate - up_left).abs
|
|
128
|
-
return left if left_distance <= up_distance && left_distance <= up_left_distance
|
|
129
|
-
return up if up_distance <= up_left_distance
|
|
130
|
-
|
|
131
|
-
up_left
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def scale_pixels(pixels, width:, height:)
|
|
135
|
-
source_height = pixels.length
|
|
136
|
-
source_width = pixels.first.length
|
|
137
|
-
|
|
138
|
-
height.times.map do |target_y|
|
|
139
|
-
source_y_start = target_y * source_height / height
|
|
140
|
-
source_y_end = [(target_y + 1) * source_height / height, source_y_start + 1].max
|
|
141
|
-
width.times.map do |target_x|
|
|
142
|
-
source_x_start = target_x * source_width / width
|
|
143
|
-
source_x_end = [(target_x + 1) * source_width / width, source_x_start + 1].max
|
|
144
|
-
dominant_color(pixels, source_x_start...source_x_end, source_y_start...source_y_end)
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def dominant_color(pixels, x_range, y_range)
|
|
150
|
-
counts = Hash.new(0)
|
|
151
|
-
y_range.each do |y|
|
|
152
|
-
x_range.each do |x|
|
|
153
|
-
counts[visible_color(pixels[y][x])] += 1
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
counts.max_by { |_color, count| count }&.first
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def visible_color(color)
|
|
160
|
-
return nil unless color
|
|
161
|
-
|
|
162
|
-
red, green, blue, alpha = color
|
|
163
|
-
return nil if !alpha.nil? && alpha.to_i < TRANSPARENT_ALPHA
|
|
164
|
-
|
|
165
|
-
[red, green, blue]
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def render_rows(rows)
|
|
169
|
-
rows.map do |row|
|
|
170
|
-
current = nil
|
|
171
|
-
rendered = +""
|
|
172
|
-
row.each do |color|
|
|
173
|
-
if color != current
|
|
174
|
-
rendered << background_sgr(color)
|
|
175
|
-
current = color
|
|
176
|
-
end
|
|
177
|
-
rendered << " "
|
|
178
|
-
end
|
|
179
|
-
rendered << reset_sgr if current
|
|
180
|
-
rendered
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def render_half_block_rows(rows)
|
|
185
|
-
rows.each_slice(2).map do |top_row, bottom_row|
|
|
186
|
-
bottom_row ||= Array.new(top_row.length)
|
|
187
|
-
current_foreground = nil
|
|
188
|
-
current_background = nil
|
|
189
|
-
rendered = +""
|
|
190
|
-
top_row.each_with_index do |top, index|
|
|
191
|
-
bottom = bottom_row[index]
|
|
192
|
-
foreground, background, glyph = half_block_cell(top, bottom)
|
|
193
|
-
if background != current_background
|
|
194
|
-
rendered << background_sgr(background)
|
|
195
|
-
current_background = background
|
|
196
|
-
end
|
|
197
|
-
if foreground != current_foreground
|
|
198
|
-
rendered << foreground_sgr(foreground)
|
|
199
|
-
current_foreground = foreground
|
|
200
|
-
end
|
|
201
|
-
rendered << glyph
|
|
202
|
-
end
|
|
203
|
-
rendered << reset_sgr if current_foreground || current_background
|
|
204
|
-
rendered
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def half_block_cell(top, bottom)
|
|
209
|
-
if top && bottom
|
|
210
|
-
[top, bottom, "▀"]
|
|
211
|
-
elsif top
|
|
212
|
-
[top, nil, "▀"]
|
|
213
|
-
elsif bottom
|
|
214
|
-
[bottom, nil, "▄"]
|
|
215
|
-
else
|
|
216
|
-
[nil, nil, " "]
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def foreground_sgr(color)
|
|
221
|
-
color ? "\e[38;2;#{color.join(";")}m" : "\e[39m"
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def background_sgr(color)
|
|
225
|
-
color ? "\e[48;2;#{color.join(";")}m" : "\e[49m"
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
def reset_sgr
|
|
229
|
-
"\e[0m"
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
end
|