kward 0.82.0 → 0.84.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 +96 -15
- data/CONTRIBUTING.md +74 -0
- data/Gemfile.lock +8 -2
- data/README.md +21 -1
- data/Rakefile +46 -2
- data/SECURITY.md +31 -0
- data/doc/agent-tools.md +1 -0
- data/doc/api.md +4 -0
- data/doc/composer.md +2 -2
- data/doc/configuration.md +82 -20
- data/doc/editor.md +47 -22
- data/doc/files.md +12 -7
- data/doc/getting-started.md +3 -0
- data/doc/pan.md +19 -15
- data/doc/permissions.md +1 -0
- data/doc/platform-support.md +48 -0
- data/doc/releasing.md +12 -4
- data/doc/rpc.md +2 -2
- data/doc/sandboxing.md +5 -1
- data/doc/security.md +4 -3
- data/doc/shell.md +101 -60
- data/doc/tabs.md +3 -0
- data/doc/troubleshooting.md +12 -2
- data/doc/usage.md +7 -6
- data/kward.gemspec +5 -4
- data/lib/kward/agent.rb +7 -3
- data/lib/kward/ansi.rb +110 -10
- data/lib/kward/auth/anthropic_oauth.rb +7 -7
- data/lib/kward/cli/auth_commands.rb +34 -13
- data/lib/kward/cli/commands.rb +83 -62
- data/lib/kward/cli/doctor.rb +39 -17
- data/lib/kward/cli/hook_commands.rb +22 -12
- data/lib/kward/cli/interactive_turn.rb +144 -21
- data/lib/kward/cli/project_skills_commands.rb +8 -4
- data/lib/kward/cli/prompt_interface.rb +27 -0
- data/lib/kward/cli/rendering.rb +15 -9
- data/lib/kward/cli/runtime_helpers.rb +260 -55
- data/lib/kward/cli/slash_commands.rb +21 -12
- data/lib/kward/cli/tabs.rb +91 -13
- data/lib/kward/cli/tool_summaries.rb +14 -0
- data/lib/kward/cli.rb +55 -7
- data/lib/kward/cli_transcript_formatter.rb +11 -4
- data/lib/kward/compaction/token_estimator.rb +12 -6
- data/lib/kward/config_files.rb +94 -63
- data/lib/kward/detached_run.rb +44 -0
- data/lib/kward/editor_prompt.rb +46 -0
- data/lib/kward/editor_prompt_session.rb +28 -0
- data/lib/kward/interactive_pty_runner.rb +102 -28
- data/lib/kward/{ekwsh.rb → kwsh.rb} +103 -24
- data/lib/kward/kwshrc.rb +233 -0
- data/lib/kward/markdown_code_block.rb +136 -0
- data/lib/kward/model/client.rb +35 -39
- data/lib/kward/model/model_info.rb +3 -2
- data/lib/kward/model/payloads.rb +0 -2
- data/lib/kward/model/provider_catalog.rb +5 -0
- data/lib/kward/model/stream_parser.rb +20 -4
- data/lib/kward/pan/index.html.erb +3 -3
- data/lib/kward/pan/server.rb +23 -3
- data/lib/kward/persistent_shell_session.rb +843 -0
- data/lib/kward/project_files.rb +19 -6
- data/lib/kward/prompt_interface/composer_renderer.rb +45 -41
- data/lib/kward/prompt_interface/composer_state.rb +33 -24
- data/lib/kward/prompt_interface/editor/auto_indent.rb +27 -22
- data/lib/kward/prompt_interface/editor/controller.rb +99 -33
- data/lib/kward/prompt_interface/editor/endwise.rb +13 -4
- data/lib/kward/prompt_interface/editor/modes/modern.rb +4 -0
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +298 -47
- data/lib/kward/prompt_interface/editor/renderer.rb +109 -6
- data/lib/kward/prompt_interface/editor/runner.rb +362 -0
- data/lib/kward/prompt_interface/editor/runner_state.rb +78 -0
- data/lib/kward/prompt_interface/editor/state.rb +21 -10
- data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +68 -6
- data/lib/kward/prompt_interface/editor/vibe_state.rb +3 -3
- data/lib/kward/prompt_interface/editor/word_completion.rb +124 -0
- data/lib/kward/prompt_interface/file_overlay.rb +92 -22
- data/lib/kward/prompt_interface/key_handler.rb +75 -0
- data/lib/kward/prompt_interface/overlay_renderer.rb +7 -5
- data/lib/kward/prompt_interface/project_browser.rb +443 -17
- data/lib/kward/prompt_interface/runtime_state.rb +55 -2
- data/lib/kward/prompt_interface/screen.rb +2 -2
- data/lib/kward/prompt_interface/selection_prompt.rb +3 -1
- data/lib/kward/prompt_interface/slash_overlay.rb +19 -4
- data/lib/kward/prompt_interface/transcript_renderer.rb +12 -7
- data/lib/kward/prompt_interface.rb +169 -18
- data/lib/kward/prompts/commands.rb +2 -1
- data/lib/kward/prompts.rb +1 -1
- data/lib/kward/pty_output_sink.rb +47 -0
- data/lib/kward/rpc/auth_manager.rb +1 -1
- data/lib/kward/rpc/server.rb +2 -1
- data/lib/kward/rpc/transcript_normalizer.rb +7 -3
- data/lib/kward/scratchpad_languages.rb +74 -0
- data/lib/kward/scratchpad_runner.rb +155 -29
- data/lib/kward/shell_prompt.rb +52 -0
- data/lib/kward/shell_prompt_session.rb +58 -0
- data/lib/kward/terminal_keys.rb +13 -0
- data/lib/kward/terminal_text.rb +121 -0
- data/lib/kward/text_matcher.rb +18 -0
- data/lib/kward/tools/open_editor.rb +41 -0
- data/lib/kward/tools/prepare_shell_command.rb +28 -0
- data/lib/kward/tools/registry.rb +75 -8
- data/lib/kward/tools/replace_editor_buffer.rb +30 -0
- data/lib/kward/tools/run_shell_command.rb +24 -6
- data/lib/kward/tools/tool_call.rb +2 -1
- data/lib/kward/version.rb +1 -1
- data/templates/default/fulldoc/html/css/kward.css +0 -125
- data/templates/default/fulldoc/html/images/kward_workflow.svg +52 -0
- data/templates/default/fulldoc/html/setup.rb +1 -1
- data/templates/default/kward_navigation.rb +1 -0
- data/templates/default/layout/html/footer.erb +10 -0
- data/templates/default/layout/html/headers.erb +23 -0
- data/templates/default/layout/html/layout.erb +6 -18
- data/templates/default/layout/html/setup.rb +41 -2
- metadata +44 -8
- data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "fileutils"
|
|
2
|
-
require_relative "../../
|
|
2
|
+
require_relative "../../scratchpad_languages"
|
|
3
|
+
require_relative "runner"
|
|
3
4
|
|
|
4
5
|
# Namespace for the Kward CLI agent runtime.
|
|
5
6
|
module Kward
|
|
@@ -13,6 +14,10 @@ module Kward
|
|
|
13
14
|
!@editor_state.nil?
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
def editor_visible?
|
|
18
|
+
editor_active?
|
|
19
|
+
end
|
|
20
|
+
|
|
16
21
|
def open_selected_file_in_editor(fallback_to_typed_path: false)
|
|
17
22
|
path = selected_file_open_path
|
|
18
23
|
if path
|
|
@@ -58,6 +63,7 @@ module Kward
|
|
|
58
63
|
language: language
|
|
59
64
|
)
|
|
60
65
|
@editor_state.status = scratchpad_status_text(language)
|
|
66
|
+
@editor_agent_suspended = false
|
|
61
67
|
@prompt_label = "Edit>"
|
|
62
68
|
self.composer_input = ""
|
|
63
69
|
self.composer_cursor = 0
|
|
@@ -95,6 +101,7 @@ module Kward
|
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
@editor_state = EditorState.new(path: full_path, content: File.exist?(full_path) ? File.read(full_path) : "", new_file: !File.exist?(full_path), editor_mode: current_editor_mode)
|
|
104
|
+
@editor_agent_suspended = false
|
|
98
105
|
@prompt_label = "Edit>"
|
|
99
106
|
self.composer_input = ""
|
|
100
107
|
self.composer_cursor = 0
|
|
@@ -115,6 +122,7 @@ module Kward
|
|
|
115
122
|
diff_view_mode = current_diff_view_mode
|
|
116
123
|
viewer_content = diff_view_mode == DiffViewMode::SIDE_BY_SIDE ? side_by_side_diff_content(content.to_s) : content.to_s
|
|
117
124
|
@editor_state = EditorState.new(path: path.to_s, content: viewer_content, new_file: true, editor_mode: current_editor_mode, readonly: true, diff_view: diff_view_mode)
|
|
125
|
+
@editor_agent_suspended = false
|
|
118
126
|
@prompt_label = "Diff>"
|
|
119
127
|
self.composer_input = ""
|
|
120
128
|
self.composer_cursor = 0
|
|
@@ -236,13 +244,19 @@ module Kward
|
|
|
236
244
|
end
|
|
237
245
|
|
|
238
246
|
def close_editor
|
|
247
|
+
cancel_editor_runner
|
|
239
248
|
disable_editor_mouse_reporting(force: true)
|
|
240
249
|
restore_editor_cursor_shape_locked
|
|
250
|
+
@editor_runner_state = nil
|
|
251
|
+
@editor_runner_completion = nil
|
|
252
|
+
@editor_runner_output_visible = false
|
|
253
|
+
clear_editor_runner_selection
|
|
241
254
|
@editor_text_width = nil
|
|
242
255
|
@editor_save_as_active = false
|
|
243
256
|
@editor_save_as_buffer = ""
|
|
244
257
|
@editor_save_callback = nil
|
|
245
258
|
@editor_state = nil
|
|
259
|
+
@editor_agent_suspended = false
|
|
246
260
|
@prompt_label = "You>"
|
|
247
261
|
self.composer_input = ""
|
|
248
262
|
self.composer_cursor = 0
|
|
@@ -252,10 +266,17 @@ module Kward
|
|
|
252
266
|
|
|
253
267
|
def handle_editor_key(key)
|
|
254
268
|
return if key.nil?
|
|
269
|
+
return handle_editor_prompt_key(key) if @editor_prompt_active
|
|
270
|
+
return handle_editor_runner_output_key(key) if editor_runner_output_visible?
|
|
271
|
+
|
|
272
|
+
reset_editor_word_completion unless editor_word_completion_tab_key?(key) && !editor_search_active?
|
|
255
273
|
mouse_result = handle_editor_mouse_key(key)
|
|
256
274
|
return mouse_result unless mouse_result == false
|
|
257
275
|
return handle_editor_save_as_key(key) if @editor_save_as_active
|
|
258
276
|
return handle_readonly_editor_key(key) if @editor_state&.readonly?
|
|
277
|
+
return cancel_editor_runner if editor_runner_running? && @editor_runner_completion && key == TerminalKeys::CTRL_C
|
|
278
|
+
super_copy_result = handle_editor_super_copy_key(key)
|
|
279
|
+
return super_copy_result unless super_copy_result == false
|
|
259
280
|
return handle_vibe_key(key) if @editor_state&.vibe?
|
|
260
281
|
return handle_emacs_key(key) if @editor_state&.emacs?
|
|
261
282
|
return handle_modern_key(key) if @editor_state&.modern?
|
|
@@ -312,6 +333,80 @@ module Kward
|
|
|
312
333
|
end
|
|
313
334
|
end
|
|
314
335
|
|
|
336
|
+
def handle_editor_super_copy_key(key)
|
|
337
|
+
sequence = parse_csi_u_key(key)
|
|
338
|
+
return false unless sequence
|
|
339
|
+
return false unless super_modifier?(sequence[:modifier]) && ctrl_code(sequence[:code]) == 99
|
|
340
|
+
|
|
341
|
+
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
342
|
+
return editor_search_cancel if editor_search_active?
|
|
343
|
+
return false unless editor_selection_active?
|
|
344
|
+
|
|
345
|
+
if @editor_state.vibe? && vibe_visual_mode?
|
|
346
|
+
vibe_yank_visual_selection
|
|
347
|
+
else
|
|
348
|
+
copy_editor_selection
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def editor_prompt_action(instruction)
|
|
353
|
+
{
|
|
354
|
+
editor_prompt: {
|
|
355
|
+
instruction: instruction.to_s.strip,
|
|
356
|
+
display_input: ":prompt #{instruction.to_s.strip}"
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def begin_editor_prompt
|
|
362
|
+
@editor_prompt_active = true
|
|
363
|
+
@editor_prompt_input = ""
|
|
364
|
+
@editor_state.status = "Prompt: "
|
|
365
|
+
true
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def handle_editor_prompt_key(key)
|
|
369
|
+
sequence = parse_csi_u_key(key)
|
|
370
|
+
if sequence
|
|
371
|
+
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
372
|
+
return handle_editor_prompt_key("\r") if sequence[:code] == 13
|
|
373
|
+
return handle_editor_prompt_key("\e") if sequence[:code] == 27
|
|
374
|
+
return handle_editor_prompt_key("\b") if [8, 127].include?(sequence[:code])
|
|
375
|
+
|
|
376
|
+
text = csi_u_printable_text(sequence)
|
|
377
|
+
return handle_editor_prompt_key(text) if text
|
|
378
|
+
|
|
379
|
+
return true
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
case key
|
|
383
|
+
when "\e", TerminalKeys::CTRL_C, :escape
|
|
384
|
+
@editor_prompt_active = false
|
|
385
|
+
@editor_prompt_input = ""
|
|
386
|
+
@editor_state.status = EditorStatusText.default(readonly: @editor_state.readonly?, editor_mode: @editor_state.editor_mode)
|
|
387
|
+
true
|
|
388
|
+
when "\b", "\x7F"
|
|
389
|
+
@editor_prompt_input = @editor_prompt_input[0...-1]
|
|
390
|
+
@editor_state.status = "Prompt: #{@editor_prompt_input}"
|
|
391
|
+
when "\n", "\r"
|
|
392
|
+
instruction = @editor_prompt_input.strip
|
|
393
|
+
@editor_prompt_active = false
|
|
394
|
+
@editor_prompt_input = ""
|
|
395
|
+
if instruction.empty?
|
|
396
|
+
@editor_state.status = "Prompt instruction required"
|
|
397
|
+
true
|
|
398
|
+
else
|
|
399
|
+
editor_prompt_action(instruction)
|
|
400
|
+
end
|
|
401
|
+
else
|
|
402
|
+
if printable_key?(key)
|
|
403
|
+
@editor_prompt_input += key
|
|
404
|
+
@editor_state.status = "Prompt: #{@editor_prompt_input}"
|
|
405
|
+
end
|
|
406
|
+
true
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
315
410
|
def handle_editor_csi_u_key(key)
|
|
316
411
|
sequence = parse_csi_u_key(key)
|
|
317
412
|
return false unless sequence
|
|
@@ -1318,45 +1413,16 @@ module Kward
|
|
|
1318
1413
|
true
|
|
1319
1414
|
end
|
|
1320
1415
|
|
|
1321
|
-
def run_editor_buffer
|
|
1322
|
-
return false unless @editor_state
|
|
1323
|
-
|
|
1324
|
-
language = @editor_state.language || editor_syntax_language
|
|
1325
|
-
result = ScratchpadRunner.run(language, @editor_state.buffer)
|
|
1326
|
-
@editor_state.replace_range(0, @editor_state.buffer.length, result.buffer)
|
|
1327
|
-
@editor_state.status = "Ran #{language} (exit #{result.exit_status})"
|
|
1328
|
-
true
|
|
1329
|
-
rescue StandardError => e
|
|
1330
|
-
@editor_state.status = "Run failed: #{e.message}" if @editor_state
|
|
1331
|
-
false
|
|
1332
|
-
end
|
|
1333
|
-
|
|
1334
1416
|
def normalize_scratchpad_language(language)
|
|
1335
|
-
|
|
1336
|
-
when "", "text", "txt"
|
|
1337
|
-
:text
|
|
1338
|
-
when "markdown", "md"
|
|
1339
|
-
:markdown
|
|
1340
|
-
when "ruby", "rb"
|
|
1341
|
-
:ruby
|
|
1342
|
-
else
|
|
1343
|
-
:text
|
|
1344
|
-
end
|
|
1417
|
+
ScratchpadLanguages.normalize(language)
|
|
1345
1418
|
end
|
|
1346
1419
|
|
|
1347
1420
|
def scratchpad_display_path(language)
|
|
1348
|
-
|
|
1349
|
-
when :markdown
|
|
1350
|
-
"scratchpad.md"
|
|
1351
|
-
when :ruby
|
|
1352
|
-
"scratchpad.rb"
|
|
1353
|
-
else
|
|
1354
|
-
"scratchpad.txt"
|
|
1355
|
-
end
|
|
1421
|
+
ScratchpadLanguages.display_path(language)
|
|
1356
1422
|
end
|
|
1357
1423
|
|
|
1358
1424
|
def scratchpad_status_text(language)
|
|
1359
|
-
runnable = language
|
|
1425
|
+
runnable = ScratchpadLanguages.runnable?(language)
|
|
1360
1426
|
case current_editor_mode
|
|
1361
1427
|
when "vibe"
|
|
1362
1428
|
runnable ? "NORMAL · i insert · :w filename save · :q quit · :run run" : "NORMAL · i insert · :w filename save · :q quit"
|
|
@@ -136,7 +136,7 @@ module Kward
|
|
|
136
136
|
line_index, column = @editor_state.cursor_line_and_column
|
|
137
137
|
return nil unless editor_endwise_line_exists?(line_index)
|
|
138
138
|
|
|
139
|
-
language =
|
|
139
|
+
language = editor_effective_syntax_language(line_index)
|
|
140
140
|
definition = ENDWISE_LANGUAGES[language]
|
|
141
141
|
return nil unless definition
|
|
142
142
|
|
|
@@ -190,7 +190,8 @@ module Kward
|
|
|
190
190
|
line_index >= 0 && line_index < @editor_state.lines.length
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
-
def editor_endwise_line_opens_block?(line_index, language =
|
|
193
|
+
def editor_endwise_line_opens_block?(line_index, language = nil)
|
|
194
|
+
language ||= editor_effective_syntax_language(line_index)
|
|
194
195
|
code = editor_endwise_code_line_at(line_index, language)
|
|
195
196
|
return false if editor_endwise_ignored_definition?(code, language)
|
|
196
197
|
|
|
@@ -200,7 +201,7 @@ module Kward
|
|
|
200
201
|
end
|
|
201
202
|
|
|
202
203
|
def editor_endwise_closing_keyword_for_line(line_index, column, called_with_modifier: false)
|
|
203
|
-
language =
|
|
204
|
+
language = editor_effective_syntax_language(line_index)
|
|
204
205
|
definition = ENDWISE_LANGUAGES[language]
|
|
205
206
|
return nil unless definition
|
|
206
207
|
|
|
@@ -221,6 +222,9 @@ module Kward
|
|
|
221
222
|
return close if @editor_state.lines.length <= scan_line + 1
|
|
222
223
|
|
|
223
224
|
line_below = @editor_state.lines[scan_line + 1].to_s
|
|
225
|
+
next_language = editor_effective_syntax_language(scan_line + 1)
|
|
226
|
+
return close if next_language != language
|
|
227
|
+
|
|
224
228
|
code_below = editor_endwise_code_line_at(scan_line + 1, language)
|
|
225
229
|
closes_any_block = editor_endwise_closes_block?(code_below, language)
|
|
226
230
|
closes_this_block = editor_endwise_closes_with?(code_below, close)
|
|
@@ -254,6 +258,8 @@ module Kward
|
|
|
254
258
|
end
|
|
255
259
|
|
|
256
260
|
def editor_endwise_code_line_at(line_index, language)
|
|
261
|
+
return "" unless editor_effective_syntax_language(line_index) == language
|
|
262
|
+
|
|
257
263
|
definition = ENDWISE_LANGUAGES[language]
|
|
258
264
|
line = @editor_state.lines[line_index].to_s
|
|
259
265
|
return "" if editor_endwise_inside_block_comment?(line_index, definition.fetch(:block_comments))
|
|
@@ -263,7 +269,10 @@ module Kward
|
|
|
263
269
|
|
|
264
270
|
def editor_endwise_inside_block_comment?(line_index, block_comments)
|
|
265
271
|
active_comment = nil
|
|
266
|
-
|
|
272
|
+
first_index = editor_syntax_context_start(line_index)
|
|
273
|
+
first_index = 0 unless first_index
|
|
274
|
+
(first_index..line_index).each do |index|
|
|
275
|
+
line = @editor_state.lines[index].to_s
|
|
267
276
|
if active_comment
|
|
268
277
|
target_line = index == line_index
|
|
269
278
|
active_comment = nil if line.match?(active_comment.fetch(:end))
|
|
@@ -198,6 +198,8 @@ module Kward
|
|
|
198
198
|
|
|
199
199
|
def handle_modern_key_binding(key)
|
|
200
200
|
case key
|
|
201
|
+
when TerminalKeys::CTRL_PERIOD
|
|
202
|
+
begin_editor_prompt
|
|
201
203
|
when TerminalKeys::CTRL_SPACE
|
|
202
204
|
true
|
|
203
205
|
when TerminalKeys::CTRL_C
|
|
@@ -221,6 +223,8 @@ module Kward
|
|
|
221
223
|
end
|
|
222
224
|
|
|
223
225
|
case normalized_code
|
|
226
|
+
when 46
|
|
227
|
+
begin_editor_prompt
|
|
224
228
|
when 13
|
|
225
229
|
return false if editor_search_active?
|
|
226
230
|
|