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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -15
  3. data/CONTRIBUTING.md +74 -0
  4. data/Gemfile.lock +8 -2
  5. data/README.md +21 -1
  6. data/Rakefile +46 -2
  7. data/SECURITY.md +31 -0
  8. data/doc/agent-tools.md +1 -0
  9. data/doc/api.md +4 -0
  10. data/doc/composer.md +2 -2
  11. data/doc/configuration.md +82 -20
  12. data/doc/editor.md +47 -22
  13. data/doc/files.md +12 -7
  14. data/doc/getting-started.md +3 -0
  15. data/doc/pan.md +19 -15
  16. data/doc/permissions.md +1 -0
  17. data/doc/platform-support.md +48 -0
  18. data/doc/releasing.md +12 -4
  19. data/doc/rpc.md +2 -2
  20. data/doc/sandboxing.md +5 -1
  21. data/doc/security.md +4 -3
  22. data/doc/shell.md +101 -60
  23. data/doc/tabs.md +3 -0
  24. data/doc/troubleshooting.md +12 -2
  25. data/doc/usage.md +7 -6
  26. data/kward.gemspec +5 -4
  27. data/lib/kward/agent.rb +7 -3
  28. data/lib/kward/ansi.rb +110 -10
  29. data/lib/kward/auth/anthropic_oauth.rb +7 -7
  30. data/lib/kward/cli/auth_commands.rb +34 -13
  31. data/lib/kward/cli/commands.rb +83 -62
  32. data/lib/kward/cli/doctor.rb +39 -17
  33. data/lib/kward/cli/hook_commands.rb +22 -12
  34. data/lib/kward/cli/interactive_turn.rb +144 -21
  35. data/lib/kward/cli/project_skills_commands.rb +8 -4
  36. data/lib/kward/cli/prompt_interface.rb +27 -0
  37. data/lib/kward/cli/rendering.rb +15 -9
  38. data/lib/kward/cli/runtime_helpers.rb +260 -55
  39. data/lib/kward/cli/slash_commands.rb +21 -12
  40. data/lib/kward/cli/tabs.rb +91 -13
  41. data/lib/kward/cli/tool_summaries.rb +14 -0
  42. data/lib/kward/cli.rb +55 -7
  43. data/lib/kward/cli_transcript_formatter.rb +11 -4
  44. data/lib/kward/compaction/token_estimator.rb +12 -6
  45. data/lib/kward/config_files.rb +94 -63
  46. data/lib/kward/detached_run.rb +44 -0
  47. data/lib/kward/editor_prompt.rb +46 -0
  48. data/lib/kward/editor_prompt_session.rb +28 -0
  49. data/lib/kward/interactive_pty_runner.rb +102 -28
  50. data/lib/kward/{ekwsh.rb → kwsh.rb} +103 -24
  51. data/lib/kward/kwshrc.rb +233 -0
  52. data/lib/kward/markdown_code_block.rb +136 -0
  53. data/lib/kward/model/client.rb +35 -39
  54. data/lib/kward/model/model_info.rb +3 -2
  55. data/lib/kward/model/payloads.rb +0 -2
  56. data/lib/kward/model/provider_catalog.rb +5 -0
  57. data/lib/kward/model/stream_parser.rb +20 -4
  58. data/lib/kward/pan/index.html.erb +3 -3
  59. data/lib/kward/pan/server.rb +23 -3
  60. data/lib/kward/persistent_shell_session.rb +843 -0
  61. data/lib/kward/project_files.rb +19 -6
  62. data/lib/kward/prompt_interface/composer_renderer.rb +45 -41
  63. data/lib/kward/prompt_interface/composer_state.rb +33 -24
  64. data/lib/kward/prompt_interface/editor/auto_indent.rb +27 -22
  65. data/lib/kward/prompt_interface/editor/controller.rb +99 -33
  66. data/lib/kward/prompt_interface/editor/endwise.rb +13 -4
  67. data/lib/kward/prompt_interface/editor/modes/modern.rb +4 -0
  68. data/lib/kward/prompt_interface/editor/modes/vibe.rb +298 -47
  69. data/lib/kward/prompt_interface/editor/renderer.rb +109 -6
  70. data/lib/kward/prompt_interface/editor/runner.rb +362 -0
  71. data/lib/kward/prompt_interface/editor/runner_state.rb +78 -0
  72. data/lib/kward/prompt_interface/editor/state.rb +21 -10
  73. data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +68 -6
  74. data/lib/kward/prompt_interface/editor/vibe_state.rb +3 -3
  75. data/lib/kward/prompt_interface/editor/word_completion.rb +124 -0
  76. data/lib/kward/prompt_interface/file_overlay.rb +92 -22
  77. data/lib/kward/prompt_interface/key_handler.rb +75 -0
  78. data/lib/kward/prompt_interface/overlay_renderer.rb +7 -5
  79. data/lib/kward/prompt_interface/project_browser.rb +443 -17
  80. data/lib/kward/prompt_interface/runtime_state.rb +55 -2
  81. data/lib/kward/prompt_interface/screen.rb +2 -2
  82. data/lib/kward/prompt_interface/selection_prompt.rb +3 -1
  83. data/lib/kward/prompt_interface/slash_overlay.rb +19 -4
  84. data/lib/kward/prompt_interface/transcript_renderer.rb +12 -7
  85. data/lib/kward/prompt_interface.rb +169 -18
  86. data/lib/kward/prompts/commands.rb +2 -1
  87. data/lib/kward/prompts.rb +1 -1
  88. data/lib/kward/pty_output_sink.rb +47 -0
  89. data/lib/kward/rpc/auth_manager.rb +1 -1
  90. data/lib/kward/rpc/server.rb +2 -1
  91. data/lib/kward/rpc/transcript_normalizer.rb +7 -3
  92. data/lib/kward/scratchpad_languages.rb +74 -0
  93. data/lib/kward/scratchpad_runner.rb +155 -29
  94. data/lib/kward/shell_prompt.rb +52 -0
  95. data/lib/kward/shell_prompt_session.rb +58 -0
  96. data/lib/kward/terminal_keys.rb +13 -0
  97. data/lib/kward/terminal_text.rb +121 -0
  98. data/lib/kward/text_matcher.rb +18 -0
  99. data/lib/kward/tools/open_editor.rb +41 -0
  100. data/lib/kward/tools/prepare_shell_command.rb +28 -0
  101. data/lib/kward/tools/registry.rb +75 -8
  102. data/lib/kward/tools/replace_editor_buffer.rb +30 -0
  103. data/lib/kward/tools/run_shell_command.rb +24 -6
  104. data/lib/kward/tools/tool_call.rb +2 -1
  105. data/lib/kward/version.rb +1 -1
  106. data/templates/default/fulldoc/html/css/kward.css +0 -125
  107. data/templates/default/fulldoc/html/images/kward_workflow.svg +52 -0
  108. data/templates/default/fulldoc/html/setup.rb +1 -1
  109. data/templates/default/kward_navigation.rb +1 -0
  110. data/templates/default/layout/html/footer.erb +10 -0
  111. data/templates/default/layout/html/headers.erb +23 -0
  112. data/templates/default/layout/html/layout.erb +6 -18
  113. data/templates/default/layout/html/setup.rb +41 -2
  114. metadata +44 -8
  115. data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
@@ -14,10 +14,13 @@ require_relative "mcp_tool"
14
14
  require_relative "read_file"
15
15
  require_relative "read_skill"
16
16
  require_relative "run_shell_command"
17
+ require_relative "prepare_shell_command"
17
18
  require_relative "summarize_file_structure"
18
19
  require_relative "retrieve_tool_output"
19
20
  require_relative "web_search"
20
21
  require_relative "write_file"
22
+ require_relative "open_editor"
23
+ require_relative "replace_editor_buffer"
21
24
  require_relative "search/code"
22
25
  require_relative "search/web"
23
26
  require_relative "search/web_fetch"
@@ -71,15 +74,15 @@ module Kward
71
74
  # Builds tool objects and the schema list for the current frontend/config.
72
75
  #
73
76
  # @param workspace [Workspace] filesystem/shell boundary used by local tools
74
- # @param prompt [Object, nil] interactive prompt bridge; must implement
75
- # `ask_user_question` before that tool is advertised
77
+ # @param prompt [Object, nil] interactive prompt bridge; tools such as
78
+ # `ask_user_question` and `open_editor` are advertised only when supported
76
79
  # @param web_search [WebSearch] live web search implementation
77
80
  # @param web_fetch [WebFetch] specific URL fetch implementation
78
81
  # @param code_search [CodeSearch] public source/package search implementation
79
82
  # @param web_search_enabled [Boolean, nil] override for web search exposure
80
83
  # @param skills [Array<ConfigFiles::Skill>, nil] override discovered skills
81
84
  # @param ask_user_question_enabled [Boolean, nil] override question exposure
82
- def initialize(workspace: Workspace.new, prompt: nil, web_search: WebSearch.new, web_fetch: WebFetch.new, code_search: CodeSearch.new, web_search_enabled: nil, skills: nil, ask_user_question_enabled: nil, allowed_tool_names: nil, tool_output_compactor: ToolOutputCompactor.new, telemetry_logger: TelemetryLogger.new, context_budget_meter: nil, mcp_clients: nil, tool_approval: nil, approval_for_allowed_tools: false, permission_policy: nil, hook_manager: nil, hook_context: nil, git_committer: nil)
85
+ def initialize(workspace: Workspace.new, prompt: nil, web_search: WebSearch.new, web_fetch: WebFetch.new, code_search: CodeSearch.new, web_search_enabled: nil, skills: nil, ask_user_question_enabled: nil, allowed_tool_names: nil, editor_prompt_session: nil, tool_output_compactor: ToolOutputCompactor.new, telemetry_logger: TelemetryLogger.new, context_budget_meter: nil, mcp_clients: nil, tool_approval: nil, approval_for_allowed_tools: false, permission_policy: nil, hook_manager: nil, hook_context: nil, git_committer: nil)
83
86
  @workspace = workspace
84
87
  @prompt = prompt
85
88
  @web_search = web_search
@@ -89,6 +92,8 @@ module Kward
89
92
  @web_search_enabled = web_search_enabled
90
93
  @ask_user_question_enabled = ask_user_question_enabled
91
94
  @allowed_tool_names = allowed_tool_names&.map(&:to_s)
95
+ @editor_prompt_session = editor_prompt_session
96
+ @shell_prompt_session = nil
92
97
  @tool_output_compactor = tool_output_compactor
93
98
  @telemetry_logger = telemetry_logger
94
99
  @context_budget_meter = context_budget_meter
@@ -109,6 +114,36 @@ module Kward
109
114
  @schemas = build_schema_tools.map { |tool| schema_with_metadata(tool) }.freeze
110
115
  end
111
116
 
117
+ # Builds a registry scoped to one editor prompt turn.
118
+ #
119
+ # Editor prompts may inspect the workspace, but their only write operation
120
+ # is the in-memory editor buffer. Filesystem and shell mutation tools are
121
+ # intentionally excluded from this scoped registry.
122
+ def for_editor_prompt(editor_prompt_session)
123
+ registry = dup
124
+ registry.instance_variable_set(:@editor_prompt_session, editor_prompt_session)
125
+ registry.instance_variable_set(:@shell_prompt_session, nil)
126
+ registry.instance_variable_set(:@allowed_tool_names, editor_prompt_tool_names)
127
+ registry.instance_variable_set(:@tools, registry.send(:build_tools).freeze)
128
+ registry.instance_variable_set(:@schemas, registry.send(:build_schema_tools).map { |tool| registry.send(:schema_with_metadata, tool) }.freeze)
129
+ registry
130
+ end
131
+
132
+ # Builds a registry scoped to one transient shell-agent turn.
133
+ #
134
+ # Shell prompts may inspect the workspace and execute explicitly requested
135
+ # commands through the active shell, but they cannot write workspace files
136
+ # through the ordinary file tools or execute against a second shell state.
137
+ def for_shell_prompt(shell_prompt_session)
138
+ registry = dup
139
+ registry.instance_variable_set(:@editor_prompt_session, nil)
140
+ registry.instance_variable_set(:@shell_prompt_session, shell_prompt_session)
141
+ registry.instance_variable_set(:@allowed_tool_names, shell_prompt_tool_names)
142
+ registry.instance_variable_set(:@tools, registry.send(:build_tools).freeze)
143
+ registry.instance_variable_set(:@schemas, registry.send(:build_schema_tools).map { |tool| registry.send(:schema_with_metadata, tool) }.freeze)
144
+ registry
145
+ end
146
+
112
147
  # Executes a model-requested tool call and appends the result to the
113
148
  # conversation transcript.
114
149
  #
@@ -223,7 +258,7 @@ module Kward
223
258
  "web"
224
259
  when Tools::ReadSkill
225
260
  "skill"
226
- when Tools::AskUserQuestion
261
+ when Tools::AskUserQuestion, Tools::OpenEditor
227
262
  "ui"
228
263
  else
229
264
  "builtin"
@@ -407,11 +442,12 @@ module Kward
407
442
  end
408
443
 
409
444
  def shell_payload(args)
445
+ shell_cwd = @shell_prompt_session&.shell&.cwd
410
446
  {
411
447
  tool_name: "run_shell_command",
412
448
  command: args[:command] || args["command"],
413
- timeout_seconds: args[:timeout_seconds] || args["timeout_seconds"] || Workspace::DEFAULT_COMMAND_TIMEOUT_SECONDS,
414
- cwd: @workspace.respond_to?(:root) ? @workspace.root.to_s : nil
449
+ timeout_seconds: args[:timeout_seconds] || args["timeout_seconds"] || @shell_prompt_session&.timeout_seconds || Workspace::DEFAULT_COMMAND_TIMEOUT_SECONDS,
450
+ cwd: shell_cwd || (@workspace.respond_to?(:root) ? @workspace.root.to_s : nil)
415
451
  }.compact
416
452
  end
417
453
 
@@ -486,6 +522,9 @@ module Kward
486
522
 
487
523
  def build_schema_tools
488
524
  tools = @tools.values_at(*CORE_TOOL_NAMES)
525
+ tools << @tools["replace_editor_buffer"] if @tools["replace_editor_buffer"]
526
+ tools << @tools["open_editor"] if @tools["open_editor"]
527
+ tools << @tools["prepare_shell_command"] if @tools["prepare_shell_command"]
489
528
  tools << @tools["git_commit"] if @tools["git_commit"]
490
529
  tools.concat(@tools.values_at("web_search", "fetch_content", "fetch_raw")) if web_search_available?
491
530
  tools.concat(@tools.values.select { |tool| tool.is_a?(Tools::MCPTool) })
@@ -507,18 +546,46 @@ module Kward
507
546
  end
508
547
 
509
548
  def core_tools
510
- [
549
+ tools = [
511
550
  Tools::ListDirectory.new(workspace: @workspace),
512
551
  Tools::ReadFile.new(workspace: @workspace),
513
552
  Tools::WriteFile.new(workspace: @workspace),
514
553
  Tools::EditFile.new(workspace: @workspace),
515
- Tools::RunShellCommand.new(workspace: @workspace),
554
+ shell_command_tool,
516
555
  Tools::CodeSearch.new(code_search: @code_search),
517
556
  Tools::SummarizeFileStructure.new(workspace: @workspace),
518
557
  Tools::ContextForTask.new(workspace: @workspace),
519
558
  Tools::ContextBudgetStats.new(context_budget_meter: @context_budget_meter),
520
559
  Tools::RetrieveToolOutput.new
521
560
  ]
561
+ tools << Tools::ReplaceEditorBuffer.new(editor_prompt_session: @editor_prompt_session) if @editor_prompt_session
562
+ tools << Tools::OpenEditor.new(workspace: @workspace, prompt: @prompt) if editor_available?
563
+ tools << Tools::PrepareShellCommand.new(shell_prompt_session: @shell_prompt_session) if @shell_prompt_session
564
+ tools
565
+ end
566
+
567
+ def shell_command_tool
568
+ if @shell_prompt_session
569
+ Tools::RunShellCommand.new(shell_prompt_session: @shell_prompt_session)
570
+ else
571
+ Tools::RunShellCommand.new(workspace: @workspace)
572
+ end
573
+ end
574
+
575
+ def editor_available?
576
+ @prompt&.respond_to?(:edit_file)
577
+ end
578
+
579
+ def editor_prompt_tool_names
580
+ readonly_tools = CORE_TOOL_NAMES - %w[write_file edit_file run_shell_command]
581
+ readonly_tools + ["replace_editor_buffer"]
582
+ end
583
+
584
+ def shell_prompt_tool_names
585
+ readonly_tools = CORE_TOOL_NAMES - %w[write_file edit_file run_shell_command]
586
+ tools = readonly_tools + %w[run_shell_command prepare_shell_command]
587
+ tools << "open_editor" if editor_available?
588
+ tools
522
589
  end
523
590
 
524
591
  def web_search_available?
@@ -0,0 +1,30 @@
1
+ require_relative "base"
2
+
3
+ # Namespace for the Kward CLI agent runtime.
4
+ module Kward
5
+ # Model-callable tool wrappers and their argument schemas.
6
+ module Tools
7
+ # Replaces the active editor's in-memory draft without saving to disk.
8
+ class ReplaceEditorBuffer < Base
9
+ def initialize(editor_prompt_session:)
10
+ @editor_prompt_session = editor_prompt_session
11
+ super(
12
+ "replace_editor_buffer",
13
+ "Replace the complete contents of the active in-memory editor buffer. This does not save the file.",
14
+ properties: {
15
+ content: { type: "string", description: "Complete replacement contents for the editor buffer." }
16
+ },
17
+ required: ["content"]
18
+ )
19
+ end
20
+
21
+ def call(args, _conversation, cancellation: nil)
22
+ cancellation&.raise_if_cancelled!
23
+ content = argument(args, :content, "")
24
+ @editor_prompt_session.replace(content)
25
+ lines = content.to_s.lines.length
26
+ "Editor buffer replaced: #{lines} lines, #{content.to_s.bytesize} bytes. The file has not been saved."
27
+ end
28
+ end
29
+ end
30
+ end
@@ -5,17 +5,27 @@ require_relative "../workspace"
5
5
  module Kward
6
6
  # Model-callable tool wrappers and their argument schemas.
7
7
  module Tools
8
- # Tool wrapper for bounded shell commands in the workspace.
8
+ # Tool wrapper for bounded shell commands in the workspace or active shell.
9
9
  class RunShellCommand < Base
10
10
  # Builds the tool schema and stores the execution dependency.
11
- def initialize(workspace:)
11
+ def initialize(workspace: nil, shell_prompt_session: nil)
12
+ raise ArgumentError, "RunShellCommand requires a workspace or shell session" unless workspace || shell_prompt_session
13
+ raise ArgumentError, "RunShellCommand accepts only one execution target" if workspace && shell_prompt_session
14
+
12
15
  @workspace = workspace
16
+ @shell_prompt_session = shell_prompt_session
17
+ description = shell_prompt_session ?
18
+ "Run a bounded noninteractive command in the active embedded shell session." :
19
+ "Run a shell command from the workspace root."
20
+ timeout_description = shell_prompt_session ?
21
+ "Timeout seconds; defaults to the embedded shell setting." :
22
+ "Timeout seconds; default 30."
13
23
  super(
14
24
  "run_shell_command",
15
- "Run a shell command from the workspace root.",
25
+ description,
16
26
  properties: {
17
27
  command: { type: "string", description: "Command to run." },
18
- timeout_seconds: { type: "integer", description: "Timeout seconds; default 30." }
28
+ timeout_seconds: { type: "integer", description: timeout_description }
19
29
  },
20
30
  required: ["command"]
21
31
  )
@@ -24,9 +34,17 @@ module Kward
24
34
  # Executes the tool and returns model-facing output text.
25
35
  def call(args, _conversation, cancellation: nil)
26
36
  command = argument(args, :command, "")
27
- timeout_seconds = argument(args, :timeout_seconds, Workspace::DEFAULT_COMMAND_TIMEOUT_SECONDS)
37
+ timeout_seconds = argument(
38
+ args,
39
+ :timeout_seconds,
40
+ @shell_prompt_session ? @shell_prompt_session.timeout_seconds : Workspace::DEFAULT_COMMAND_TIMEOUT_SECONDS
41
+ )
28
42
 
29
- @workspace.run_shell_command(command, timeout_seconds: timeout_seconds, cancellation: cancellation)
43
+ if @shell_prompt_session
44
+ @shell_prompt_session.run(command, timeout_seconds: timeout_seconds, cancellation: cancellation)
45
+ else
46
+ @workspace.run_shell_command(command, timeout_seconds: timeout_seconds, cancellation: cancellation)
47
+ end
30
48
  end
31
49
  end
32
50
  end
@@ -26,7 +26,8 @@ module Kward
26
26
  "fetch_raw" => "fetch_raw",
27
27
  "git_commit" => "git_commit",
28
28
  "read_skill" => "read_skill",
29
- "ask_user_question" => "ask_user_question"
29
+ "ask_user_question" => "ask_user_question",
30
+ "open_editor" => "open_editor"
30
31
  }.freeze
31
32
 
32
33
  module_function
data/lib/kward/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # Namespace for the Kward CLI agent runtime.
2
2
  module Kward
3
3
  # Current gem version.
4
- VERSION = "0.82.0"
4
+ VERSION = "0.84.0"
5
5
  end
@@ -570,127 +570,6 @@ body.kward-docs #footer {
570
570
  color: var(--kward-accent-bright);
571
571
  }
572
572
 
573
- /*
574
- Hero tagline "swosh": cycles each line through three phrases. The first
575
- line moves right-to-left: "Your terminal.", "Your RPC frontend.", then
576
- "Your workflow." The second line mirrors it left-to-right: "Your agent.",
577
- "Your LLM engine.", then "Your harness." Pure CSS — no JS. The global
578
- @media (prefers-reduced-motion) rule above collapses the animation to
579
- the static original copy ("Your terminal. Your agent.") so it remains
580
- accessible and calm.
581
- */
582
- .kward-swosh {
583
- box-sizing: content-box;
584
- display: inline-block;
585
- height: 0.98em;
586
- line-height: 0.98;
587
- margin: -0.16em -0.08em;
588
- overflow: hidden;
589
- padding: 0.16em 0.08em;
590
- position: relative;
591
- vertical-align: top;
592
- }
593
-
594
- .kward-hero h1 .kward-swosh-text {
595
- color: var(--kward-ink);
596
- display: inline-block;
597
- line-height: 0.98;
598
- white-space: nowrap;
599
- will-change: transform, opacity;
600
- }
601
-
602
- /* The longest text ("Your RPC frontend.") stays in flow to size the box;
603
- the shorter phrases overlay it absolutely. */
604
- .kward-swosh-text-a,
605
- .kward-swosh-text-e {
606
- left: 0.08em;
607
- position: absolute;
608
- top: 0.16em;
609
- }
610
-
611
- .kward-swosh-text-a {
612
- animation: kward-swosh-a 18s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
613
- }
614
-
615
- .kward-swosh-text-b {
616
- animation: kward-swosh-b 18s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
617
- position: relative;
618
- }
619
-
620
- .kward-swosh-text-e {
621
- animation: kward-swosh-e 18s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
622
- }
623
-
624
- @keyframes kward-swosh-a {
625
- 0%, 25% { transform: translateX(0); opacity: 1; filter: none; }
626
- 33% { transform: translateX(-140%); opacity: 0; filter: blur(3px); }
627
- 34%, 92% { transform: translateX(140%); opacity: 0; filter: none; }
628
- 100% { transform: translateX(0); opacity: 1; filter: none; }
629
- }
630
-
631
- @keyframes kward-swosh-b {
632
- 0%, 25% { transform: translateX(140%); opacity: 0; filter: none; }
633
- 33% { transform: translateX(0); opacity: 1; filter: none; }
634
- 58% { transform: translateX(0); opacity: 1; filter: none; }
635
- 66% { transform: translateX(-140%); opacity: 0; filter: blur(3px); }
636
- 67%, 100% { transform: translateX(140%); opacity: 0; filter: none; }
637
- }
638
-
639
- @keyframes kward-swosh-e {
640
- 0%, 58% { transform: translateX(140%); opacity: 0; filter: none; }
641
- 66% { transform: translateX(0); opacity: 1; filter: none; }
642
- 92% { transform: translateX(0); opacity: 1; filter: none; }
643
- 100% { transform: translateX(-140%); opacity: 0; filter: blur(3px); }
644
- }
645
-
646
- /*
647
- Second line, mirrored left-to-right. All texts keep the accent-bright
648
- color of the original "Your agent." span.
649
- */
650
- .kward-hero h1 .kward-swosh-text-c,
651
- .kward-hero h1 .kward-swosh-text-f {
652
- color: var(--kward-accent-bright);
653
- left: 0.08em;
654
- position: absolute;
655
- top: 0.16em;
656
- }
657
-
658
- .kward-hero h1 .kward-swosh-text-c {
659
- animation: kward-swosh-c 18s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
660
- }
661
-
662
- .kward-hero h1 .kward-swosh-text-d {
663
- animation: kward-swosh-d 18s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
664
- color: var(--kward-accent-bright);
665
- position: relative;
666
- }
667
-
668
- .kward-hero h1 .kward-swosh-text-f {
669
- animation: kward-swosh-f 18s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
670
- }
671
-
672
- @keyframes kward-swosh-c {
673
- 0%, 25% { transform: translateX(0); opacity: 1; filter: none; }
674
- 33% { transform: translateX(140%); opacity: 0; filter: blur(3px); }
675
- 34%, 92% { transform: translateX(-140%); opacity: 0; filter: none; }
676
- 100% { transform: translateX(0); opacity: 1; filter: none; }
677
- }
678
-
679
- @keyframes kward-swosh-d {
680
- 0%, 25% { transform: translateX(-140%); opacity: 0; filter: none; }
681
- 33% { transform: translateX(0); opacity: 1; filter: none; }
682
- 58% { transform: translateX(0); opacity: 1; filter: none; }
683
- 66% { transform: translateX(140%); opacity: 0; filter: blur(3px); }
684
- 67%, 100% { transform: translateX(-140%); opacity: 0; filter: none; }
685
- }
686
-
687
- @keyframes kward-swosh-f {
688
- 0%, 58% { transform: translateX(-140%); opacity: 0; filter: none; }
689
- 66% { transform: translateX(0); opacity: 1; filter: none; }
690
- 92% { transform: translateX(0); opacity: 1; filter: none; }
691
- 100% { transform: translateX(140%); opacity: 0; filter: blur(3px); }
692
- }
693
-
694
573
  .kward-lede {
695
574
  color: var(--kward-ink);
696
575
  font-size: 19px;
@@ -1041,10 +920,6 @@ body.kward-docs .kward-home #footer p {
1041
920
  font-size: clamp(32px, 10vw, 38px);
1042
921
  }
1043
922
 
1044
- .kward-swosh {
1045
- max-width: 100%;
1046
- }
1047
-
1048
923
  .kward-feature-strip,
1049
924
  .kward-capabilities ul {
1050
925
  grid-template-columns: 1fr;
@@ -0,0 +1,52 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700" role="img" aria-labelledby="title description">
2
+ <title id="title">Kward coding workflow</title>
3
+ <desc id="description">A Kward terminal session reads and edits the CLI, runs focused tests, and reports a successful authentication error-handling change.</desc>
4
+ <defs>
5
+ <linearGradient id="background" x1="0" y1="0" x2="1" y2="1">
6
+ <stop offset="0" stop-color="#11170f"/>
7
+ <stop offset="1" stop-color="#070b07"/>
8
+ </linearGradient>
9
+ <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
10
+ <feDropShadow dx="0" dy="18" stdDeviation="22" flood-color="#000" flood-opacity=".45"/>
11
+ </filter>
12
+ </defs>
13
+ <rect width="1200" height="700" fill="#070b07"/>
14
+ <rect x="28" y="28" width="1144" height="644" rx="20" fill="url(#background)" stroke="#465035" stroke-width="2" filter="url(#shadow)"/>
15
+ <path d="M28 82h1144" stroke="#293024" stroke-width="2"/>
16
+ <circle cx="62" cy="55" r="8" fill="#ff5f57"/>
17
+ <circle cx="88" cy="55" r="8" fill="#febc2e"/>
18
+ <circle cx="114" cy="55" r="8" fill="#28c840"/>
19
+ <text x="600" y="63" text-anchor="middle" fill="#777c68" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">kward · ~/code/kward</text>
20
+
21
+ <g font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">
22
+ <circle cx="65" cy="119" r="7" fill="#9caf35"/>
23
+ <text x="84" y="126" fill="#efe8d0" font-size="20" font-weight="700">Kward v0.83.0 is online.</text>
24
+ <text x="64" y="159" fill="#777c68" font-size="16">Workspace</text>
25
+ <text x="180" y="159" fill="#d2df62" font-size="16">kaiwood/kward</text>
26
+ <text x="64" y="184" fill="#777c68" font-size="16">Branch</text>
27
+ <text x="180" y="184" fill="#d2df62" font-size="16">main</text>
28
+ <text x="64" y="219" fill="#efe8d0" font-size="18" font-weight="700">State your business.</text>
29
+
30
+ <text x="64" y="276" fill="#afa88f" font-size="16" font-weight="700">You&gt;</text>
31
+ <text x="126" y="276" fill="#efe8d0" font-size="18">Make one-shot authentication failures concise and add focused tests.</text>
32
+
33
+ <rect x="64" y="309" width="1072" height="164" rx="12" fill="#0d120d" stroke="#293024"/>
34
+ <text x="88" y="342" fill="#9caf35" font-size="15" font-weight="700">TOOL ACTIVITY</text>
35
+ <text x="88" y="378" fill="#d2df62" font-size="17">Read</text>
36
+ <text x="174" y="378" fill="#efe8d0" font-size="17">lib/kward/cli.rb</text>
37
+ <text x="88" y="414" fill="#d2df62" font-size="17">Edit</text>
38
+ <text x="174" y="414" fill="#efe8d0" font-size="17">lib/kward/cli.rb</text>
39
+ <text x="1016" y="414" text-anchor="end" fill="#9caf35" font-size="16">+34</text>
40
+ <text x="88" y="450" fill="#d2df62" font-size="17">Test</text>
41
+ <text x="174" y="450" fill="#efe8d0" font-size="17">3 runs · 26 assertions</text>
42
+ <text x="1110" y="450" text-anchor="end" fill="#9caf35" font-size="17" font-weight="700">0 failures</text>
43
+
44
+ <text x="64" y="526" fill="#9caf35" font-size="16" font-weight="700">Kward&gt;</text>
45
+ <text x="154" y="526" fill="#efe8d0" font-size="18">Authentication failures now return an actionable message without a traceback.</text>
46
+ <text x="154" y="559" fill="#afa88f" font-size="17">Debug backtraces remain available with KWARD_DEBUG=1.</text>
47
+
48
+ <path d="M52 608h1096" stroke="#293024"/>
49
+ <text x="64" y="642" fill="#afa88f" font-size="15">main*</text>
50
+ <text x="1128" y="642" text-anchor="end" fill="#777c68" font-size="15">18% · Codex gpt-5.6-sol · medium</text>
51
+ </g>
52
+ </svg>
@@ -9,7 +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
+ asset('images/kward_workflow.svg', file('images/kward_workflow.svg', true))
13
13
  end
14
14
 
15
15
  def stylesheets_full_list
@@ -12,6 +12,7 @@ module KwardDocsNavigationData
12
12
  ["Authentication", "file.authentication.html"],
13
13
  ["Model providers", "file.providers.html"],
14
14
  ["Security and trust", "file.security.html"],
15
+ ["Platform support", "file.platform-support.html"],
15
16
  ["Troubleshooting", "file.troubleshooting.html"]
16
17
  ]
17
18
  ],
@@ -0,0 +1,10 @@
1
+ <div id="footer">
2
+ <% if home_page? %>
3
+ Kward is open source software released under the
4
+ <a href="https://github.com/kaiwood/kward/blob/main/LICENSE">MIT License</a>.
5
+ <% else %>
6
+ Generated with
7
+ <a href="https://yardoc.org" title="YARD documentation tool" target="_parent">YARD</a>
8
+ <%= YARD::VERSION %>.
9
+ <% end %>
10
+ </div>
@@ -0,0 +1,23 @@
1
+ <meta charset="<%= charset %>">
2
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3
+ <title><%= h docs_page_title %></title>
4
+ <meta name="description" content="<%= h docs_page_description %>">
5
+ <meta name="theme-color" content="#070b07">
6
+ <link rel="canonical" href="<%= h docs_canonical_url %>">
7
+ <link rel="icon" type="image/png" href="<%= url_for('images/kward_logo.png') %>">
8
+ <meta property="og:type" content="website">
9
+ <meta property="og:site_name" content="Kward">
10
+ <meta property="og:title" content="<%= h docs_page_title %>">
11
+ <meta property="og:description" content="<%= h docs_page_description %>">
12
+ <meta property="og:url" content="<%= h docs_canonical_url %>">
13
+ <meta property="og:image" content="https://kaiwood.github.io/kward/images/kward_logo.png">
14
+ <meta name="twitter:card" content="summary">
15
+ <meta name="twitter:title" content="<%= h docs_page_title %>">
16
+ <meta name="twitter:description" content="<%= h docs_page_description %>">
17
+ <% stylesheets.each do |stylesheet| %>
18
+ <link rel="stylesheet" href="<%= mtime_url(stylesheet) %>" type="text/css">
19
+ <% end %>
20
+ <%= erb :script_setup %>
21
+ <% javascripts.each do |javascript| %>
22
+ <script type="text/javascript" charset="utf-8" src="<%= mtime_url(javascript) %>"></script>
23
+ <% end %>
@@ -80,23 +80,11 @@
80
80
  <main id="main" class="kward-home">
81
81
  <section class="kward-hero">
82
82
  <div class="kward-hero-copy">
83
- <p class="kward-eyebrow">⌘ Ruby CLI Coding Agent</p>
84
- <h1>
85
- <span class="kward-swosh" aria-label="Your terminal. Your RPC frontend. Your workflow.">
86
- <span class="kward-swosh-text kward-swosh-text-a" aria-hidden="true">Your terminal.</span>
87
- <span class="kward-swosh-text kward-swosh-text-b" aria-hidden="true">Your RPC frontend.</span>
88
- <span class="kward-swosh-text kward-swosh-text-e" aria-hidden="true">Your workflow.</span>
89
- </span>
90
- <br>
91
- <span class="kward-swosh" aria-label="Your agent. Your LLM engine. Your harness.">
92
- <span class="kward-swosh-text kward-swosh-text-c" aria-hidden="true">Your agent.</span>
93
- <span class="kward-swosh-text kward-swosh-text-d" aria-hidden="true">Your LLM engine.</span>
94
- <span class="kward-swosh-text kward-swosh-text-f" aria-hidden="true">Your harness.</span>
95
- </span>
96
- </h1>
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>
83
+ <p class="kward-eyebrow">⌘ Ruby-native coding agent</p>
84
+ <h1>A coding agent that works <span>where you do.</span></h1>
85
+ <p class="kward-lede">Inspect code, edit files, run tests, and resume work from your terminal—with your choice of model provider.</p>
98
86
  <div class="kward-actions">
99
- <a class="kward-primary-button" href="<%= url_for('file.README.html') %>">Get Started ›</a>
87
+ <a class="kward-primary-button" href="<%= url_for('file.getting-started.html') %>">Get Started ›</a>
100
88
  <a class="kward-secondary-button" href="https://github.com/kaiwood/kward" target="_blank" rel="noopener noreferrer">View on GitHub</a>
101
89
  </div>
102
90
  <p class="kward-supports">Supports <span>OpenAI</span>, <span>Anthropic</span>, <span>OpenRouter</span> &amp; more.</p>
@@ -110,7 +98,7 @@
110
98
  <article><strong>⌘</strong><h2>Project-aware agent loop</h2><p>Inspect, reason, patch, verify.</p></article>
111
99
  <article><strong>▤</strong><h2>Unix filter mode</h2><p>Pipe diffs, logs, and code through Kward.</p></article>
112
100
  <article><strong>▰</strong><h2>Branchable sessions</h2><p>Resume, rewind, fork, compact, export.</p></article>
113
- <article><strong>◈</strong><h2>Human-in-the-loop permissions</h2><p>Optionally approve edits, commands, web, and MCP calls.</p></article>
101
+ <article><strong>⌗</strong><h2>Built-in shell and editor</h2><p>Run commands and edit files safely in the workspace.</p></article>
114
102
  <article><strong>◎</strong><h2>Live source intelligence</h2><p>Search the web and read public repos.</p></article>
115
103
  <article><strong>✣</strong><h2>Ruby-native extensions</h2><p>Shape Kward with plugins, skills, and prompts.</p></article>
116
104
  </section>
@@ -125,7 +113,7 @@
125
113
  <a href="<%= url_for('file.getting-started.html') %>">View full installation docs →</a>
126
114
  </div>
127
115
  <div class="kward-screen-card">
128
- <img src="<%= url_for('images/kward_screen_1.png') %>" alt="Kward terminal interface screenshot">
116
+ <img src="<%= url_for('images/kward_workflow.svg') %>" alt="Kward completing a focused coding change and test run">
129
117
  </div>
130
118
  </section>
131
119
 
@@ -24,16 +24,23 @@ module KwardDocsNavigation
24
24
  end
25
25
  end
26
26
  ].freeze
27
+ DOCS_LINK_TITLES = (
28
+ [["Overview", GUIDE_OVERVIEW], ["Advanced", EXTENSION_OVERVIEW], ["API Docs", API_OVERVIEW]] +
29
+ (GUIDE_GROUPS + EXTENSION_GROUPS + API_GROUPS).flat_map { |_title, items| items }
30
+ ).to_h { |label, link| [link, label] }.freeze
27
31
  GUIDE_FILE_LINKS = {
28
32
  "doc/getting-started.md" => "file.getting-started.html",
29
33
  "doc/usage.md" => "file.usage.html",
30
34
  "doc/configuration.md" => "file.configuration.html",
31
35
  "doc/authentication.md" => "file.authentication.html",
32
36
  "doc/providers.md" => "file.providers.html",
37
+ "doc/security.md" => "file.security.html",
38
+ "doc/platform-support.md" => "file.platform-support.html",
33
39
  "doc/permissions.md" => "file.permissions.html",
34
40
  "doc/sandboxing.md" => "file.sandboxing.html",
35
41
  "doc/troubleshooting.md" => "file.troubleshooting.html",
36
42
  "doc/session-management.md" => "file.session-management.html",
43
+ "doc/composer.md" => "file.composer.html",
37
44
  "doc/tabs.md" => "file.tabs.html",
38
45
  "doc/files.md" => "file.files.html",
39
46
  "doc/editor.md" => "file.editor.html",
@@ -42,6 +49,9 @@ module KwardDocsNavigation
42
49
  "doc/memory.md" => "file.memory.html",
43
50
  "doc/personas.md" => "file.personas.html",
44
51
  "doc/skills.md" => "file.skills.html",
52
+ "doc/prompt-templates.md" => "file.prompt-templates.html",
53
+ "doc/pan.md" => "file.pan.html",
54
+ "doc/mcp.md" => "file.mcp.html",
45
55
  "doc/local-models.md" => "file.local-models.html",
46
56
  "doc/extensibility.md" => "file.extensibility.html",
47
57
  "doc/plugins.md" => "file.plugins.html",
@@ -79,7 +89,7 @@ module KwardDocsNavigation
79
89
  title: title,
80
90
  path: source,
81
91
  url: url_for(link),
82
- text: content.gsub(/[`*_#>\[\]()]/, " ").gsub(/\s+/, " ").strip
92
+ text: content.gsub(/<[^>]+>/, " ").gsub(/[`*_#>\[\]()]/, " ").gsub(/\s+/, " ").strip
83
93
  }
84
94
  end
85
95
  end
@@ -111,6 +121,35 @@ module KwardDocsNavigation
111
121
  options.index && readme_file?
112
122
  end
113
123
 
124
+ def docs_page_title
125
+ return "Kward — Ruby Coding Agent for Your Terminal" if home_page?
126
+
127
+ page = DOCS_LINK_TITLES[docs_output_path] || @page_title.to_s.sub(/\AFile:\s*/, "")
128
+ "#{page} | Kward Docs"
129
+ end
130
+
131
+ def docs_page_description
132
+ if home_page?
133
+ "Kward is an extensible Ruby coding agent for inspecting code, editing files, running tests, and resuming work from your terminal."
134
+ else
135
+ "Documentation for Kward, the extensible Ruby coding agent for your terminal."
136
+ end
137
+ end
138
+
139
+ def docs_canonical_url
140
+ return "https://kaiwood.github.io/kward/" if home_page?
141
+
142
+ "https://kaiwood.github.io/kward/#{docs_output_path}"
143
+ end
144
+
145
+ def docs_output_path
146
+ if defined?(@file) && @file
147
+ GUIDE_FILE_LINKS[@file.filename.to_s] || API_FILE_LINKS[@file.filename.to_s] || current_docs_path
148
+ else
149
+ current_docs_path
150
+ end
151
+ end
152
+
114
153
  def guide_page?
115
154
  (readme_file? && !options.index) || guide_file? || GUIDE_LINKS.include?(current_docs_path)
116
155
  end
@@ -133,7 +172,7 @@ module KwardDocsNavigation
133
172
  GUIDE_FILE_LINKS.merge(API_FILE_LINKS).each do |source, target|
134
173
  html = html.gsub(%(href="#{source}"), %(href="#{target}"))
135
174
  end
136
- html
175
+ html.gsub(%(src="templates/default/fulldoc/html/images/), %(src="images/))
137
176
  end
138
177
  end
139
178