kward 0.72.0 → 0.73.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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -0
  3. data/CHANGELOG.md +53 -0
  4. data/Gemfile.lock +2 -2
  5. data/doc/configuration.md +1 -1
  6. data/doc/editor.md +23 -2
  7. data/doc/git.md +1 -0
  8. data/doc/rpc.md +2 -2
  9. data/doc/shell.md +56 -10
  10. data/doc/usage.md +27 -1
  11. data/lib/kward/ansi.rb +62 -23
  12. data/lib/kward/cli/plugins.rb +1 -1
  13. data/lib/kward/cli/rendering.rb +4 -1
  14. data/lib/kward/cli/runtime_helpers.rb +141 -7
  15. data/lib/kward/cli/settings.rb +0 -1
  16. data/lib/kward/cli/slash_commands.rb +213 -0
  17. data/lib/kward/cli/tabs.rb +34 -4
  18. data/lib/kward/cli/tool_summaries.rb +6 -0
  19. data/lib/kward/cli.rb +4 -12
  20. data/lib/kward/clipboard.rb +2 -3
  21. data/lib/kward/compactor.rb +7 -19
  22. data/lib/kward/config_files.rb +26 -4
  23. data/lib/kward/ekwsh.rb +239 -42
  24. data/lib/kward/image_attachments.rb +3 -1
  25. data/lib/kward/interactive_pty_runner.rb +151 -0
  26. data/lib/kward/local_command_runner.rb +155 -0
  27. data/lib/kward/local_pty_command_runner.rb +171 -0
  28. data/lib/kward/model/context_usage.rb +2 -2
  29. data/lib/kward/model/payloads.rb +2 -5
  30. data/lib/kward/prompt_history.rb +5 -3
  31. data/lib/kward/prompt_interface/editor/auto_indent.rb +5 -4
  32. data/lib/kward/prompt_interface/editor/controller.rb +262 -62
  33. data/lib/kward/prompt_interface/editor/modes/emacs.rb +21 -21
  34. data/lib/kward/prompt_interface/editor/modes/modern.rb +38 -37
  35. data/lib/kward/prompt_interface/editor/modes/vibe.rb +23 -173
  36. data/lib/kward/prompt_interface/editor/modes/vibe_insert_readline.rb +166 -0
  37. data/lib/kward/prompt_interface/editor/renderer.rb +6 -5
  38. data/lib/kward/prompt_interface/editor/state.rb +28 -6
  39. data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +5 -3
  40. data/lib/kward/prompt_interface/git_prompt.rb +12 -23
  41. data/lib/kward/prompt_interface/interactive/controller.rb +1 -1
  42. data/lib/kward/prompt_interface/key_handler.rb +93 -51
  43. data/lib/kward/prompt_interface/question_prompt.rb +1 -6
  44. data/lib/kward/prompt_interface/screen.rb +3 -3
  45. data/lib/kward/prompt_interface/selection_prompt.rb +3 -6
  46. data/lib/kward/prompt_interface/slash_overlay.rb +2 -0
  47. data/lib/kward/prompt_interface.rb +87 -221
  48. data/lib/kward/prompts/commands.rb +4 -0
  49. data/lib/kward/rpc/memory_methods.rb +83 -0
  50. data/lib/kward/rpc/server.rb +130 -83
  51. data/lib/kward/rpc/session_manager.rb +10 -74
  52. data/lib/kward/rpc/tool_metadata.rb +11 -0
  53. data/lib/kward/rpc/transcript_normalizer.rb +4 -39
  54. data/lib/kward/scratchpad_runner.rb +56 -0
  55. data/lib/kward/session_diff.rb +20 -3
  56. data/lib/kward/session_naming.rb +11 -0
  57. data/lib/kward/terminal_keys.rb +84 -0
  58. data/lib/kward/terminal_sequences.rb +42 -0
  59. data/lib/kward/tools/context_for_task.rb +2 -0
  60. data/lib/kward/version.rb +1 -1
  61. data/lib/kward/workers/git_guard.rb +25 -0
  62. data/lib/kward/workers/job.rb +99 -0
  63. data/lib/kward/workers/queue_runner.rb +166 -0
  64. data/lib/kward/workers/queue_store.rb +112 -0
  65. data/lib/kward/workers.rb +3 -0
  66. data/lib/kward/workspace.rb +15 -63
  67. data/templates/default/fulldoc/html/css/kward.css +33 -0
  68. data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
  69. data/templates/default/fulldoc/html/setup.rb +1 -0
  70. data/templates/default/layout/html/layout.erb +19 -32
  71. metadata +15 -1
@@ -1,6 +1,5 @@
1
- require "open3"
2
1
  require "pathname"
3
- require "timeout"
2
+ require_relative "local_command_runner"
4
3
  require_relative "session_diff"
5
4
 
6
5
  # Namespace for the Kward CLI agent runtime.
@@ -23,6 +22,7 @@ module Kward
23
22
  MAX_COMMAND_OUTPUT_BYTES = 128 * 1024
24
23
  MAX_EDIT_DIFF_BYTES = 8 * 1024
25
24
  DEFAULT_COMMAND_TIMEOUT_SECONDS = 30
25
+ EXPECTED_FILE_ERRORS = [SecurityError, Errno::ENOENT, Errno::EACCES, Errno::EPERM, Errno::EISDIR, Errno::ENOTDIR].freeze
26
26
 
27
27
  # Creates an object for workspace filesystem and shell operations.
28
28
  def initialize(root: Dir.pwd, max_file_bytes: MAX_FILE_BYTES, max_read_output_bytes: MAX_READ_OUTPUT_BYTES, max_read_output_lines: MAX_READ_OUTPUT_LINES, max_command_output_bytes: MAX_COMMAND_OUTPUT_BYTES, guardrails: true)
@@ -45,7 +45,7 @@ module Kward
45
45
  Dir.children(resolved).sort.map do |entry|
46
46
  File.directory?(File.join(resolved, entry)) ? "#{entry}/" : entry
47
47
  end.join("\n")
48
- rescue SecurityError, Errno::ENOENT => e
48
+ rescue *EXPECTED_FILE_ERRORS => e
49
49
  "Error: #{e.message}"
50
50
  end
51
51
 
@@ -82,7 +82,7 @@ module Kward
82
82
  else
83
83
  large_file_outline_response(path, content, offset: offset, limit: limit) || read_file_slice(content, offset: offset, limit: limit, max_bytes: output_budget)
84
84
  end
85
- rescue SecurityError, Errno::ENOENT => e
85
+ rescue *EXPECTED_FILE_ERRORS => e
86
86
  "Error: #{e.message}"
87
87
  end
88
88
 
@@ -98,7 +98,7 @@ module Kward
98
98
  return "Error: not a text file: #{path}" if binary_content?(content)
99
99
 
100
100
  file_structure_summary(path, content)
101
- rescue SecurityError, Errno::ENOENT => e
101
+ rescue *EXPECTED_FILE_ERRORS => e
102
102
  "Error: #{e.message}"
103
103
  end
104
104
 
@@ -120,7 +120,7 @@ module Kward
120
120
  output = "Wrote #{content.bytesize} bytes to #{path}"
121
121
  output << "\n#{truncated_diff(path, old_content, content)}" if old_content && old_content != content
122
122
  output
123
- rescue SecurityError, Errno::ENOENT => e
123
+ rescue *EXPECTED_FILE_ERRORS => e
124
124
  "Error: #{e.message}"
125
125
  end
126
126
 
@@ -143,7 +143,7 @@ module Kward
143
143
 
144
144
  File.write(resolved, result[:content])
145
145
  "Edited #{path}: replaced #{result[:count]} block(s)\n#{truncated_diff(path, content, result[:content])}"
146
- rescue SecurityError, Errno::ENOENT => e
146
+ rescue *EXPECTED_FILE_ERRORS => e
147
147
  "Error: #{e.message}"
148
148
  end
149
149
 
@@ -160,24 +160,14 @@ module Kward
160
160
  timeout_seconds = DEFAULT_COMMAND_TIMEOUT_SECONDS if timeout_seconds <= 0
161
161
  cancellation&.raise_if_cancelled!
162
162
 
163
- Open3.popen3(command, chdir: @root.to_s) do |stdin, stdout, stderr, wait_thread|
164
- stdin.close
165
- stdout_reader = Thread.new { stdout.read }
166
- stderr_reader = Thread.new { stderr.read }
167
- cancellation&.on_cancel { terminate_process(wait_thread.pid) }
168
- status = wait_for_process(wait_thread, timeout_seconds, cancellation)
169
-
170
- output = +"Exit status: #{status.exitstatus}\n"
171
- output << "\nSTDOUT:\n#{stdout_reader.value}" unless stdout_reader.value.empty?
172
- output << "\nSTDERR:\n#{stderr_reader.value}" unless stderr_reader.value.empty?
173
- truncate_output(output)
174
- rescue Timeout::Error
175
- terminate_process(wait_thread.pid)
176
- "Error: command timed out after #{timeout_seconds} seconds"
177
- ensure
178
- stdout_reader&.kill if stdout_reader&.alive?
179
- stderr_reader&.kill if stderr_reader&.alive?
180
- end
163
+ result = LocalCommandRunner.new(timeout_seconds: timeout_seconds, max_output_bytes: @max_command_output_bytes).run(command, cwd: @root.to_s, cancellation: cancellation)
164
+ return "Error: command timed out after #{timeout_seconds} seconds" if result.timed_out
165
+
166
+ output = +"Exit status: #{result.exit_status}\n"
167
+ output << "\nSTDOUT:\n#{result.stdout}" unless result.stdout.empty?
168
+ output << "\nSTDERR:\n#{result.stderr}" unless result.stderr.empty?
169
+ output << "\n... truncated to #{@max_command_output_bytes} bytes" if result.truncated
170
+ truncate_output(output)
181
171
  rescue Errno::ENOENT, ArgumentError => e
182
172
  "Error: #{e.message}"
183
173
  end
@@ -522,43 +512,5 @@ module Kward
522
512
  output.byteslice(0, @max_command_output_bytes) << "\n... truncated to #{@max_command_output_bytes} bytes"
523
513
  end
524
514
 
525
- def wait_for_process(wait_thread, timeout_seconds, cancellation)
526
- deadline = Time.now + timeout_seconds
527
- loop do
528
- cancellation&.raise_if_cancelled!
529
- if wait_thread.join(0.05)
530
- cancellation&.raise_if_cancelled!
531
- return wait_thread.value
532
- end
533
- raise Timeout::Error if Time.now >= deadline
534
- end
535
- end
536
-
537
- def terminate_process(pid)
538
- return unless signal_process("TERM", pid)
539
-
540
- deadline = Time.now + 0.2
541
- while Time.now < deadline
542
- return unless process_running?(pid)
543
-
544
- sleep 0.02
545
- end
546
-
547
- signal_process("KILL", pid)
548
- end
549
-
550
- def process_running?(pid)
551
- Process.kill(0, pid)
552
- true
553
- rescue Errno::ESRCH
554
- false
555
- end
556
-
557
- def signal_process(signal, pid)
558
- Process.kill(signal, pid)
559
- true
560
- rescue Errno::ESRCH
561
- false
562
- end
563
515
  end
564
516
  end
@@ -879,6 +879,39 @@ body.kward-docs #footer {
879
879
  margin: 0;
880
880
  }
881
881
 
882
+ .kward-screen-card {
883
+ align-self: center;
884
+ background: linear-gradient(180deg, rgba(31, 38, 27, 0.98), rgba(5, 8, 5, 0.98));
885
+ border: 1px solid rgba(156, 175, 53, 0.42);
886
+ border-radius: 14px;
887
+ box-shadow: 0 24px 70px rgba(0, 0, 0, 0.34);
888
+ box-sizing: border-box;
889
+ max-width: 100%;
890
+ overflow: hidden;
891
+ padding-top: 34px;
892
+ position: relative;
893
+ }
894
+
895
+ .kward-screen-card::before {
896
+ background:
897
+ radial-gradient(circle at 12px 50%, #ff5f57 0 5px, transparent 5.5px),
898
+ radial-gradient(circle at 32px 50%, #febc2e 0 5px, transparent 5.5px),
899
+ radial-gradient(circle at 52px 50%, #28c840 0 5px, transparent 5.5px);
900
+ border-bottom: 1px solid rgba(149, 169, 52, 0.22);
901
+ content: "";
902
+ height: 34px;
903
+ left: 0;
904
+ position: absolute;
905
+ right: 0;
906
+ top: 0;
907
+ }
908
+
909
+ .kward-screen-card img {
910
+ display: block;
911
+ height: auto;
912
+ max-width: 100%;
913
+ }
914
+
882
915
  .kward-capabilities {
883
916
  padding: 28px;
884
917
  }
@@ -9,6 +9,7 @@ def generate_assets
9
9
  asset('css/kward.css', file('css/kward.css', true))
10
10
  asset('js/kward.js', file('js/kward.js', true))
11
11
  asset('images/kward_logo.png', file('images/kward_logo.png', true))
12
+ asset('images/kward_screen_1.png', file('images/kward_screen_1.png', true))
12
13
  end
13
14
 
14
15
  def stylesheets_full_list
@@ -94,7 +94,7 @@
94
94
  <span class="kward-swosh-text kward-swosh-text-f" aria-hidden="true">Your harness.</span>
95
95
  </span>
96
96
  </h1>
97
- <p class="kward-lede">Kward is an extendable Ruby CLI coding agent that helps you understand your project, edit files, run commands, search the web, and automate workflows—right from your terminal.</p>
97
+ <p class="kward-lede">Kward is a general-purpose coding agent written in Ruby: terminal-native, project-aware, pipeable, and designed around your workflow.</p>
98
98
  <div class="kward-actions">
99
99
  <a class="kward-primary-button" href="<%= url_for('file.README.html') %>">Get Started ›</a>
100
100
  <a class="kward-secondary-button" href="https://github.com/kaiwood/kward" target="_blank" rel="noopener noreferrer">View on GitHub</a>
@@ -107,12 +107,12 @@
107
107
  </section>
108
108
 
109
109
  <section id="features" class="kward-feature-strip" aria-label="Feature summary">
110
- <article><strong>☏</strong><h2>Chat in your terminal</h2><p>Multi-turn coding conversations.</p></article>
111
- <article><strong>▤</strong><h2>Read, write, edit files</h2><p>With read-before-write guardrails.</p></article>
112
- <article><strong>›_</strong><h2>Run shell commands</h2><p>Execute local commands safely.</p></article>
113
- <article><strong>◎</strong><h2>Search the web</h2><p>Get live answers and inspect repos.</p></article>
114
- <article><strong>✣</strong><h2>Extend with plugins</h2><p>Trusted Ruby plugins for workflows.</p></article>
115
- <article><strong>▰</strong><h2>Sessions &amp; memory</h2><p>Save, resume, clone, compact, export.</p></article>
110
+ <article><strong>⌘</strong><h2>Project-aware agent loop</h2><p>Inspect, reason, patch, verify.</p></article>
111
+ <article><strong>▤</strong><h2>Unix filter mode</h2><p>Pipe diffs, logs, and code through Kward.</p></article>
112
+ <article><strong>▰</strong><h2>Branchable sessions</h2><p>Resume, rewind, fork, compact, export.</p></article>
113
+ <article><strong>›_</strong><h2>Worker pipelines</h2><p>Queue focused agent tasks from the TUI.</p></article>
114
+ <article><strong>◎</strong><h2>Live source intelligence</h2><p>Search the web and read public repos.</p></article>
115
+ <article><strong>✣</strong><h2>Ruby-native extensions</h2><p>Shape Kward with plugins, skills, and prompts.</p></article>
116
116
  </section>
117
117
 
118
118
  <section class="kward-install-card">
@@ -124,36 +124,23 @@
124
124
  <pre><code>kward init</code></pre>
125
125
  <a href="<%= url_for('file.getting-started.html') %>">View full installation docs →</a>
126
126
  </div>
127
- <div class="kward-terminal-card">
128
- <div class="kward-tabs"><span>Quick Start</span><span>Run from Source</span></div>
129
- <pre><code># Start an interactive chat
130
- kward
131
-
132
- # Show available commands and examples
133
- kward help
134
-
135
- # Sign in or save provider credentials
136
- kward login
137
-
138
- # Run one prompt and exit
139
- kward "Explain this project"
140
-
141
- # Use a specific working directory
142
- kward --working-directory ~/code/project "Explain this project"</code></pre>
127
+ <div class="kward-screen-card">
128
+ <img src="<%= url_for('images/kward_screen_1.png') %>" alt="Kward terminal interface screenshot">
143
129
  </div>
144
130
  </section>
145
131
 
146
132
  <section class="kward-capabilities">
147
- <h2>⌘ What Kward can do</h2>
133
+ <h2>⌘ Built for real development loops</h2>
148
134
  <ul>
149
- <li>Keep a multi-turn coding conversation in your terminal.</li>
150
- <li>Read, write, and edit workspace files with read-before-write guardrails.</li>
151
- <li>Run local shell commands from the workspace.</li>
152
- <li>Search the live web and inspect cached public GitHub repositories.</li>
153
- <li>Save, resume, clone, compact, and export sessions.</li>
154
- <li>Use optional memory, personas, prompt templates, and skills.</li>
155
- <li>Extend the Agent with trusted Ruby plugins for custom workflows.</li>
156
- <li>Serve an experimental JSON-RPC backend for UI clients.</li>
135
+ <li>Inspect, patch, verify, repeat against a real workspace.</li>
136
+ <li>Edit files in Kward’s built-in editor with syntax highlighting, search, undo, and familiar keybindings.</li>
137
+ <li>Review diffs, stage files, and commit changes from the integrated Git workflow.</li>
138
+ <li>Pipe diffs, logs, code, and generated text through Kward as a Unix filter.</li>
139
+ <li>Organize long-running work with tabs, sessions, rewind, forks, compaction, and exports.</li>
140
+ <li>Dispatch worker tasks, leave them running, review the results later.</li>
141
+ <li>Ground answers with live web search and public source repositories before guessing.</li>
142
+ <li>Shape Kward with memory, skills, prompts, personas, and trusted Ruby plugins.</li>
143
+ <li>Build custom UIs on top of Kward’s JSON-RPC backend.</li>
157
144
  </ul>
158
145
  </section>
159
146
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kward
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.72.0
4
+ version: 0.73.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kai Wood
@@ -116,6 +116,7 @@ executables:
116
116
  extensions: []
117
117
  extra_rdoc_files: []
118
118
  files:
119
+ - ".github/workflows/ci.yml"
119
120
  - ".github/workflows/pages.yml"
120
121
  - ".yardopts"
121
122
  - CHANGELOG.md
@@ -192,6 +193,9 @@ files:
192
193
  - lib/kward/events.rb
193
194
  - lib/kward/export_path.rb
194
195
  - lib/kward/image_attachments.rb
196
+ - lib/kward/interactive_pty_runner.rb
197
+ - lib/kward/local_command_runner.rb
198
+ - lib/kward/local_pty_command_runner.rb
195
199
  - lib/kward/markdown_transcript.rb
196
200
  - lib/kward/memory/manager.rb
197
201
  - lib/kward/message_access.rb
@@ -227,6 +231,7 @@ files:
227
231
  - lib/kward/prompt_interface/editor/modes/emacs.rb
228
232
  - lib/kward/prompt_interface/editor/modes/modern.rb
229
233
  - lib/kward/prompt_interface/editor/modes/vibe.rb
234
+ - lib/kward/prompt_interface/editor/modes/vibe_insert_readline.rb
230
235
  - lib/kward/prompt_interface/editor/renderer.rb
231
236
  - lib/kward/prompt_interface/editor/search.rb
232
237
  - lib/kward/prompt_interface/editor/selections.rb
@@ -260,6 +265,7 @@ files:
260
265
  - lib/kward/rpc/attachment_normalizer.rb
261
266
  - lib/kward/rpc/auth_manager.rb
262
267
  - lib/kward/rpc/config_manager.rb
268
+ - lib/kward/rpc/memory_methods.rb
263
269
  - lib/kward/rpc/prompt_bridge.rb
264
270
  - lib/kward/rpc/redactor.rb
265
271
  - lib/kward/rpc/runtime_payloads.rb
@@ -272,7 +278,9 @@ files:
272
278
  - lib/kward/rpc/tool_metadata.rb
273
279
  - lib/kward/rpc/transcript_normalizer.rb
274
280
  - lib/kward/rpc/transport.rb
281
+ - lib/kward/scratchpad_runner.rb
275
282
  - lib/kward/session_diff.rb
283
+ - lib/kward/session_naming.rb
276
284
  - lib/kward/session_store.rb
277
285
  - lib/kward/session_trash.rb
278
286
  - lib/kward/session_tree_nodes.rb
@@ -284,6 +292,8 @@ files:
284
292
  - lib/kward/tab_store.rb
285
293
  - lib/kward/telemetry/logger.rb
286
294
  - lib/kward/telemetry/stats.rb
295
+ - lib/kward/terminal_keys.rb
296
+ - lib/kward/terminal_sequences.rb
287
297
  - lib/kward/text_boundary.rb
288
298
  - lib/kward/tool_output_compactor.rb
289
299
  - lib/kward/tools/ask_user_question.rb
@@ -311,8 +321,11 @@ files:
311
321
  - lib/kward/version.rb
312
322
  - lib/kward/workers.rb
313
323
  - lib/kward/workers/git_guard.rb
324
+ - lib/kward/workers/job.rb
314
325
  - lib/kward/workers/live_view.rb
315
326
  - lib/kward/workers/manager.rb
327
+ - lib/kward/workers/queue_runner.rb
328
+ - lib/kward/workers/queue_store.rb
316
329
  - lib/kward/workers/store.rb
317
330
  - lib/kward/workers/tool_policy.rb
318
331
  - lib/kward/workers/worker.rb
@@ -322,6 +335,7 @@ files:
322
335
  - templates/default/fulldoc/html/css/kward.css
323
336
  - templates/default/fulldoc/html/full_list.erb
324
337
  - templates/default/fulldoc/html/images/kward_logo.png
338
+ - templates/default/fulldoc/html/images/kward_screen_1.png
325
339
  - templates/default/fulldoc/html/js/kward.js
326
340
  - templates/default/fulldoc/html/setup.rb
327
341
  - templates/default/kward_navigation.rb