kward 0.68.0 → 0.69.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pages.yml +48 -0
  3. data/.yardopts +1 -0
  4. data/CHANGELOG.md +34 -0
  5. data/Gemfile.lock +8 -2
  6. data/README.md +32 -25
  7. data/Rakefile +14 -1
  8. data/doc/authentication.md +74 -56
  9. data/doc/code-search.md +55 -28
  10. data/doc/configuration.md +18 -0
  11. data/doc/extensibility.md +89 -128
  12. data/doc/getting-started.md +52 -54
  13. data/doc/memory.md +51 -118
  14. data/doc/personas.md +417 -0
  15. data/doc/plugins.md +55 -97
  16. data/doc/releasing.md +3 -1
  17. data/doc/rpc.md +1 -1
  18. data/doc/usage.md +125 -144
  19. data/doc/web-search.md +80 -14
  20. data/exe/kward +2 -0
  21. data/lib/kward/agent.rb +1 -1
  22. data/lib/kward/cli/commands.rb +10 -3
  23. data/lib/kward/cli/compaction.rb +3 -3
  24. data/lib/kward/cli/interactive_turn.rb +3 -1
  25. data/lib/kward/cli/memory_commands.rb +16 -16
  26. data/lib/kward/cli/plugins.rb +3 -3
  27. data/lib/kward/cli/prompt_interface.rb +15 -13
  28. data/lib/kward/cli/rendering.rb +35 -46
  29. data/lib/kward/cli/runtime_helpers.rb +13 -2
  30. data/lib/kward/cli/sessions.rb +21 -21
  31. data/lib/kward/cli/settings.rb +49 -43
  32. data/lib/kward/cli/slash_commands.rb +6 -4
  33. data/lib/kward/cli/stats.rb +2 -2
  34. data/lib/kward/cli/sysprompt.rb +57 -0
  35. data/lib/kward/cli/tool_summaries.rb +5 -1
  36. data/lib/kward/cli.rb +14 -2
  37. data/lib/kward/cli_transcript_formatter.rb +36 -5
  38. data/lib/kward/compactor.rb +2 -2
  39. data/lib/kward/config_files.rb +45 -10
  40. data/lib/kward/conversation.rb +41 -9
  41. data/lib/kward/memory/manager.rb +131 -14
  42. data/lib/kward/message_access.rb +6 -0
  43. data/lib/kward/model/context_usage.rb +11 -10
  44. data/lib/kward/model/model_info.rb +18 -1
  45. data/lib/kward/model/payloads.rb +89 -10
  46. data/lib/kward/model/stream_parser.rb +258 -25
  47. data/lib/kward/prompt_interface/question_prompt.rb +1 -1
  48. data/lib/kward/prompt_interface/transcript_renderer.rb +20 -11
  49. data/lib/kward/prompts.rb +61 -7
  50. data/lib/kward/rpc/server.rb +7 -2
  51. data/lib/kward/rpc/session_manager.rb +18 -2
  52. data/lib/kward/rpc/session_metrics.rb +2 -2
  53. data/lib/kward/rpc/transcript_normalizer.rb +47 -0
  54. data/lib/kward/session_store.rb +40 -1
  55. data/lib/kward/starter_pack_installer.rb +2 -2
  56. data/lib/kward/tools/fetch_content.rb +41 -0
  57. data/lib/kward/tools/fetch_raw.rb +40 -0
  58. data/lib/kward/tools/registry.rb +9 -2
  59. data/lib/kward/tools/search/web.rb +3 -3
  60. data/lib/kward/tools/search/web_fetch.rb +202 -0
  61. data/lib/kward/tools/tool_call.rb +2 -0
  62. data/lib/kward/version.rb +1 -1
  63. data/templates/default/fulldoc/html/css/kward.css +1501 -0
  64. data/templates/default/fulldoc/html/images/kward_logo.png +0 -0
  65. data/templates/default/fulldoc/html/js/kward.js +296 -0
  66. data/templates/default/fulldoc/html/setup.rb +8 -0
  67. data/templates/default/layout/html/breadcrumb.erb +11 -0
  68. data/templates/default/layout/html/layout.erb +141 -0
  69. data/templates/default/layout/html/setup.rb +139 -0
  70. metadata +14 -1
@@ -0,0 +1,296 @@
1
+ const resetScrollPosition = () => {
2
+ if (window.location.hash) return
3
+
4
+ document.documentElement.scrollTop = 0
5
+ if (document.body) document.body.scrollTop = 0
6
+ window.scrollTo(0, 0)
7
+
8
+ const main = document.getElementById('main')
9
+ if (main) {
10
+ main.scrollTop = 0
11
+ main.scrollLeft = 0
12
+ }
13
+ }
14
+
15
+ if (!window.location.hash && 'scrollRestoration' in history) {
16
+ history.scrollRestoration = 'manual'
17
+ }
18
+
19
+ resetScrollPosition()
20
+ window.addEventListener('load', resetScrollPosition)
21
+ window.addEventListener('pageshow', resetScrollPosition)
22
+ window.addEventListener('DOMContentLoaded', resetScrollPosition)
23
+ window.setTimeout(resetScrollPosition, 0)
24
+ window.setTimeout(resetScrollPosition, 50)
25
+ window.setTimeout(resetScrollPosition, 250)
26
+ if (window.requestAnimationFrame) window.requestAnimationFrame(resetScrollPosition)
27
+
28
+ const guideLinks = {
29
+ 'doc/getting-started.md': 'file.getting-started.html',
30
+ 'doc/usage.md': 'file.usage.html',
31
+ 'doc/configuration.md': 'file.configuration.html',
32
+ 'doc/authentication.md': 'file.authentication.html',
33
+ 'doc/troubleshooting.md': 'file.troubleshooting.html',
34
+ 'doc/memory.md': 'file.memory.html',
35
+ 'doc/personas.md': 'file.personas.html',
36
+ 'doc/extensibility.md': 'file.extensibility.html',
37
+ 'doc/plugins.md': 'file.plugins.html',
38
+ 'doc/web-search.md': 'file.web-search.html',
39
+ 'doc/code-search.md': 'file.code-search.html',
40
+ 'doc/rpc.md': 'file.rpc.html',
41
+ 'doc/releasing.md': 'file.releasing.html'
42
+ }
43
+
44
+ let pageController = null
45
+
46
+ const setupGuideSearch = (signal) => {
47
+ const form = document.querySelector('.kward-guide-search')
48
+ const input = document.getElementById('kward-guide-search-input')
49
+ const results = document.getElementById('kward-guide-search-results')
50
+ const indexNode = document.getElementById('kward-guide-search-index')
51
+
52
+ if (!form || !input || !results || !indexNode) return
53
+
54
+ let index = []
55
+ try {
56
+ index = JSON.parse(indexNode.textContent || '[]')
57
+ } catch (_error) {
58
+ return
59
+ }
60
+
61
+ const closeResults = () => {
62
+ results.classList.remove('open')
63
+ results.innerHTML = ''
64
+ }
65
+
66
+ const excerpt = (text, query) => {
67
+ const normalizedText = text.toLowerCase()
68
+ const normalizedQuery = query.toLowerCase()
69
+ const matchIndex = normalizedText.indexOf(normalizedQuery)
70
+ const start = Math.max(0, matchIndex - 70)
71
+ const end = Math.min(text.length, (matchIndex < 0 ? 0 : matchIndex) + normalizedQuery.length + 120)
72
+ const prefix = start > 0 ? '…' : ''
73
+ const suffix = end < text.length ? '…' : ''
74
+ return `${prefix}${text.slice(start, end).trim()}${suffix}`
75
+ }
76
+
77
+ const search = (query) => {
78
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean)
79
+ if (terms.length === 0) return []
80
+
81
+ return index
82
+ .map((item) => {
83
+ const title = item.title || ''
84
+ const path = item.path || ''
85
+ const text = item.text || ''
86
+ const haystack = `${title} ${path} ${text}`.toLowerCase()
87
+ if (!terms.every((term) => haystack.includes(term))) return null
88
+
89
+ const normalizedTitle = title.toLowerCase()
90
+ const normalizedPath = path.toLowerCase()
91
+ const titleMatches = terms.filter((term) => normalizedTitle.includes(term)).length
92
+ const pathMatches = terms.filter((term) => normalizedPath.includes(term)).length
93
+ return { item, score: titleMatches * 3 + pathMatches * 2 }
94
+ })
95
+ .filter(Boolean)
96
+ .sort((left, right) => right.score - left.score || left.item.title.localeCompare(right.item.title))
97
+ .slice(0, 6)
98
+ .map((result) => result.item)
99
+ }
100
+
101
+ const renderResults = () => {
102
+ const query = input.value.trim()
103
+ if (query.length < 2) {
104
+ closeResults()
105
+ return
106
+ }
107
+
108
+ const matches = search(query)
109
+ results.innerHTML = ''
110
+
111
+ if (matches.length === 0) {
112
+ const empty = document.createElement('div')
113
+ empty.className = 'kward-guide-search-empty'
114
+ empty.textContent = 'No guide results'
115
+ results.appendChild(empty)
116
+ results.classList.add('open')
117
+ return
118
+ }
119
+
120
+ matches.forEach((item) => {
121
+ const link = document.createElement('a')
122
+ link.href = item.url
123
+ link.setAttribute('role', 'option')
124
+
125
+ const title = document.createElement('strong')
126
+ title.textContent = item.title
127
+ link.appendChild(title)
128
+
129
+ const summary = document.createElement('span')
130
+ summary.textContent = excerpt(item.text || '', query)
131
+ link.appendChild(summary)
132
+
133
+ results.appendChild(link)
134
+ })
135
+
136
+ results.classList.add('open')
137
+ }
138
+
139
+ input.addEventListener('input', renderResults, { signal })
140
+
141
+ input.addEventListener('keydown', (event) => {
142
+ if (event.key === 'Escape') {
143
+ input.value = ''
144
+ closeResults()
145
+ input.blur()
146
+ }
147
+ }, { signal })
148
+
149
+ form.addEventListener('submit', (event) => {
150
+ event.preventDefault()
151
+ const firstResult = results.querySelector('a')
152
+ if (firstResult) visitPage(firstResult.href)
153
+ }, { signal })
154
+
155
+ document.addEventListener('click', (event) => {
156
+ if (!form.contains(event.target)) closeResults()
157
+ }, { signal })
158
+ }
159
+
160
+ const setupNavigation = (signal) => {
161
+ const toggle = document.querySelector('.kward-nav-toggle')
162
+
163
+ if (toggle) {
164
+ toggle.addEventListener('click', () => {
165
+ const isOpen = document.body.classList.toggle('kward-nav-open')
166
+ toggle.setAttribute('aria-expanded', String(isOpen))
167
+ }, { signal })
168
+ }
169
+
170
+ document.querySelectorAll('.kward-nav-menu-button').forEach((button) => {
171
+ button.addEventListener('click', (event) => {
172
+ event.stopPropagation()
173
+ button.parentElement.classList.toggle('open')
174
+ }, { signal })
175
+ })
176
+
177
+ document.addEventListener('click', (event) => {
178
+ document.querySelectorAll('.kward-nav-menu.open').forEach((menu) => {
179
+ if (!menu.contains(event.target)) menu.classList.remove('open')
180
+ })
181
+ }, { signal })
182
+ }
183
+
184
+ const rewriteGuideLinks = () => {
185
+ document.querySelectorAll('#filecontents a[href]').forEach((link) => {
186
+ const target = guideLinks[link.getAttribute('href')]
187
+ if (target) link.setAttribute('href', target)
188
+ })
189
+ }
190
+
191
+ const setupCodeCopy = () => {
192
+ document.querySelectorAll('pre').forEach((block) => {
193
+ if (block.closest('.code-copy-wrapper')) return
194
+
195
+ const code = block.querySelector('code')
196
+ if (!code || !navigator.clipboard) return
197
+
198
+ const wrapper = document.createElement('div')
199
+ wrapper.className = 'code-copy-wrapper'
200
+ block.parentNode.insertBefore(wrapper, block)
201
+ wrapper.appendChild(block)
202
+
203
+ const button = document.createElement('button')
204
+ button.className = 'copy-code-button'
205
+ button.type = 'button'
206
+ button.textContent = 'Copy'
207
+
208
+ button.addEventListener('click', async () => {
209
+ await navigator.clipboard.writeText(code.textContent || '')
210
+ button.textContent = 'Copied'
211
+ window.setTimeout(() => {
212
+ button.textContent = 'Copy'
213
+ }, 1200)
214
+ })
215
+
216
+ wrapper.appendChild(button)
217
+ })
218
+ }
219
+
220
+ const initializePage = () => {
221
+ if (pageController) pageController.abort()
222
+ pageController = new AbortController()
223
+
224
+ resetScrollPosition()
225
+ setupGuideSearch(pageController.signal)
226
+ setupNavigation(pageController.signal)
227
+ rewriteGuideLinks()
228
+ setupCodeCopy()
229
+ }
230
+
231
+ const samePageUrl = (url) => {
232
+ return url.origin === window.location.origin &&
233
+ url.pathname === window.location.pathname &&
234
+ url.search === window.location.search
235
+ }
236
+
237
+ const navigableUrl = (url) => {
238
+ if (url.hash) return false
239
+ if (url.origin !== window.location.origin) return false
240
+ if (!url.pathname.endsWith('.html') && !url.pathname.endsWith('/')) return false
241
+ return !samePageUrl(url)
242
+ }
243
+
244
+ const replacePage = (html, url) => {
245
+ const nextDocument = new DOMParser().parseFromString(html, 'text/html')
246
+ const nextBody = nextDocument.body
247
+ if (!nextBody) throw new Error('Missing response body')
248
+
249
+ document.title = nextDocument.title
250
+ document.body.className = nextBody.className
251
+ document.body.innerHTML = nextBody.innerHTML
252
+ window.history.pushState({}, '', url.href)
253
+ initializePage()
254
+ }
255
+
256
+ const visitPage = async (href) => {
257
+ const url = new URL(href, window.location.href)
258
+
259
+ if (!navigableUrl(url)) {
260
+ window.location.href = url.href
261
+ return
262
+ }
263
+
264
+ document.documentElement.classList.add('kward-page-loading')
265
+
266
+ try {
267
+ const response = await fetch(url.href)
268
+ if (!response.ok) throw new Error(`Failed to load ${url.href}`)
269
+
270
+ const html = await response.text()
271
+ replacePage(html, url)
272
+ } catch (_error) {
273
+ window.location.href = url.href
274
+ } finally {
275
+ document.documentElement.classList.remove('kward-page-loading')
276
+ }
277
+ }
278
+
279
+ document.addEventListener('click', (event) => {
280
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return
281
+
282
+ const link = event.target.closest('a[href]')
283
+ if (!link || link.target || link.hasAttribute('download')) return
284
+
285
+ const url = new URL(link.href, window.location.href)
286
+ if (!navigableUrl(url)) return
287
+
288
+ event.preventDefault()
289
+ visitPage(url.href)
290
+ })
291
+
292
+ window.addEventListener('popstate', () => {
293
+ window.location.reload()
294
+ })
295
+
296
+ document.addEventListener('DOMContentLoaded', initializePage)
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ def generate_assets
4
+ super
5
+ asset('css/kward.css', file('css/kward.css', true))
6
+ asset('js/kward.js', file('js/kward.js', true))
7
+ asset('images/kward_logo.png', file('images/kward_logo.png', true))
8
+ end
@@ -0,0 +1,11 @@
1
+ <div id="menu">
2
+ <% if @contents || @file %>
3
+ <% if object != '_index.html' %><a href="<%= guide_page? ? url_for(guide_overview) : url_for_index %>">Index</a> &raquo; <% end %>
4
+ <span class="title"><%= @breadcrumb_title %></span>
5
+ <% elsif object.is_a?(CodeObjects::Base) %>
6
+ <a href="<%= url_for_index %>"><% if object.root? || object.type == :method %>Index<% else %>Index (<%= object.name.to_s[0,1] %>)<% end %></a> &raquo;
7
+ <%= @breadcrumb.map {|obj| "<span class='title'>" + linkify(obj, obj.name) + "</span>" }.join(" &raquo; ") %>
8
+ <%= @breadcrumb.size > 0 ? " &raquo; " : "" %>
9
+ <span class="title"><%= object.root? ? "Top Level Namespace" : object.name(true) %></span>
10
+ <% end %>
11
+ </div>
@@ -0,0 +1,141 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <%= erb(:headers) %>
5
+ </head>
6
+ <body class="kward-docs <%= home_page? ? 'kward-home-body' : 'kward-content-body' %>">
7
+ <div id="main_progress" aria-hidden="true"></div>
8
+
9
+ <header class="kward-topbar">
10
+ <a class="kward-brand" href="<%= url_for('index.html') %>">
11
+ <img class="kward-brand-logo" src="<%= url_for('images/kward_logo.png') %>" alt="" aria-hidden="true">
12
+ <span>
13
+ <strong>Kward</strong>
14
+ </span>
15
+ </a>
16
+ <button class="kward-nav-toggle" type="button" aria-expanded="false" aria-controls="kward-primary-nav" onclick="document.body.classList.toggle('kward-nav-open'); this.setAttribute('aria-expanded', document.body.classList.contains('kward-nav-open'))">
17
+ Menu
18
+ </button>
19
+ <nav id="kward-primary-nav" class="kward-topnav" aria-label="Primary navigation">
20
+ <a href="<%= url_for('index.html') %>" class="<%= 'active' if home_page? %>">Home</a>
21
+ <div class="kward-nav-menu <%= 'active' if guide_page? %>">
22
+ <a class="kward-nav-menu-link" href="<%= url_for('file.README.html') %>">User Guides</a>
23
+ <button class="kward-nav-menu-button" type="button" aria-label="Open user guides menu">⌄</button>
24
+ <div class="kward-nav-dropdown">
25
+ <% guide_groups.each do |title, items| %>
26
+ <section>
27
+ <h2><%= h title %></h2>
28
+ <% items.each do |label, link| %>
29
+ <a href="<%= url_for(link) %>"><%= h label %></a>
30
+ <% end %>
31
+ </section>
32
+ <% end %>
33
+ </div>
34
+ </div>
35
+ <a href="<%= url_for('Kward.html') %>" class="<%= 'active' if api_page? %>">API Docs</a>
36
+ </nav>
37
+ <form class="kward-guide-search" role="search">
38
+ <label class="kward-sr-only" for="kward-guide-search-input">Search user guides</label>
39
+ <input id="kward-guide-search-input" type="search" placeholder="Search guides" autocomplete="off" aria-label="Search user guides" aria-controls="kward-guide-search-results">
40
+ <div id="kward-guide-search-results" class="kward-guide-search-results" role="listbox" aria-label="Search results"></div>
41
+ </form>
42
+ <script type="application/json" id="kward-guide-search-index"><%= guide_search_index_json %></script>
43
+ </header>
44
+
45
+ <% if home_page? %>
46
+ <main id="main" class="kward-home">
47
+ <section class="kward-hero">
48
+ <div class="kward-hero-copy">
49
+ <p class="kward-eyebrow">⌘ Ruby CLI Coding Agent</p>
50
+ <h1>Your terminal.<br><span>Your agent.</span></h1>
51
+ <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>
52
+ <div class="kward-actions">
53
+ <a class="kward-primary-button" href="<%= url_for('file.README.html') %>">Get Started ›</a>
54
+ <a class="kward-secondary-button" href="https://github.com/kaiwood/kward" target="_blank" rel="noopener noreferrer">View on GitHub</a>
55
+ </div>
56
+ <p class="kward-supports">Supports <span>OpenAI</span>, <span>Anthropic</span>, <span>OpenRouter</span> &amp; more.</p>
57
+ </div>
58
+ <div class="kward-hero-art" aria-hidden="true">
59
+ <img src="<%= url_for('images/kward_logo.png') %>" alt="">
60
+ </div>
61
+ </section>
62
+
63
+ <section id="features" class="kward-feature-strip" aria-label="Feature summary">
64
+ <article><strong>☏</strong><h2>Chat in your terminal</h2><p>Multi-turn coding conversations.</p></article>
65
+ <article><strong>▤</strong><h2>Read, write, edit files</h2><p>With read-before-write guardrails.</p></article>
66
+ <article><strong>›_</strong><h2>Run shell commands</h2><p>Execute local commands safely.</p></article>
67
+ <article><strong>◎</strong><h2>Search the web</h2><p>Get live answers and inspect repos.</p></article>
68
+ <article><strong>✣</strong><h2>Extend with plugins</h2><p>Trusted Ruby plugins for workflows.</p></article>
69
+ <article><strong>▰</strong><h2>Sessions &amp; memory</h2><p>Save, resume, clone, compact, export.</p></article>
70
+ </section>
71
+
72
+ <section class="kward-install-card">
73
+ <div class="kward-install-copy">
74
+ <h2>⇩ Install</h2>
75
+ <p>Install Kward from RubyGems and get started in seconds.</p>
76
+ <pre><code>gem install kward</code></pre>
77
+ <p>Then initialize the starter pack, if you want the defaults.</p>
78
+ <pre><code>kward init</code></pre>
79
+ <a href="<%= url_for('file.getting-started.html') %>">View full installation docs →</a>
80
+ </div>
81
+ <div class="kward-terminal-card">
82
+ <div class="kward-tabs"><span>Quick Start</span><span>Run from Source</span></div>
83
+ <pre><code># Start an interactive chat
84
+ kward
85
+
86
+ # Show available commands and examples
87
+ kward help
88
+
89
+ # Sign in or save provider credentials
90
+ kward login
91
+
92
+ # Run one prompt and exit
93
+ kward "Explain this project"
94
+
95
+ # Use a specific working directory
96
+ kward --working-directory ~/code/project "Explain this project"</code></pre>
97
+ </div>
98
+ </section>
99
+
100
+ <section class="kward-capabilities">
101
+ <h2>⌘ What Kward can do</h2>
102
+ <ul>
103
+ <li>Keep a multi-turn coding conversation in your terminal.</li>
104
+ <li>Read, write, and edit workspace files with read-before-write guardrails.</li>
105
+ <li>Run local shell commands from the workspace.</li>
106
+ <li>Search the live web and inspect cached public GitHub repositories.</li>
107
+ <li>Save, resume, clone, compact, and export sessions.</li>
108
+ <li>Use optional memory, personas, prompt templates, and skills.</li>
109
+ <li>Extend the Agent with trusted Ruby plugins for custom workflows.</li>
110
+ <li>Serve an experimental JSON-RPC backend for UI clients.</li>
111
+ </ul>
112
+ </section>
113
+
114
+ <section class="kward-cta">
115
+ <div>
116
+ <strong>▻</strong>
117
+ <h2>Built for developers. Powered by you.</h2>
118
+ <p>Open source, extensible, and ready for your workflows.</p>
119
+ </div>
120
+ <a class="kward-primary-button" href="https://github.com/kaiwood/kward" target="_blank" rel="noopener noreferrer">View on GitHub</a>
121
+ </section>
122
+
123
+ <%= erb(:footer) %>
124
+ </main>
125
+ <% else %>
126
+ <div class="kward-page">
127
+ <main id="main" class="kward-content-page <%= guide_page? ? 'kward-guide-page' : 'kward-api-page' %>">
128
+ <div id="header">
129
+ <%= erb(:breadcrumb) %>
130
+ <%= erb(:search) %>
131
+ <div class="clear"></div>
132
+ </div>
133
+
134
+ <div id="content"><%= yieldall %></div>
135
+
136
+ <%= erb(:footer) %>
137
+ </main>
138
+ </div>
139
+ <% end %>
140
+ </body>
141
+ </html>
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module KwardDocsNavigation
6
+ GUIDE_GROUPS = [
7
+ [
8
+ "Start here",
9
+ [
10
+ ["Getting started", "file.getting-started.html"],
11
+ ["Usage", "file.usage.html"],
12
+ ["Configuration", "file.configuration.html"],
13
+ ["Authentication", "file.authentication.html"],
14
+ ["Troubleshooting", "file.troubleshooting.html"]
15
+ ]
16
+ ],
17
+ [
18
+ "Feature guides",
19
+ [
20
+ ["Memory", "file.memory.html"],
21
+ ["Personas", "file.personas.html"],
22
+ ["Extensibility", "file.extensibility.html"],
23
+ ["Plugins", "file.plugins.html"],
24
+ ["Web search", "file.web-search.html"],
25
+ ["Code search", "file.code-search.html"]
26
+ ]
27
+ ],
28
+ [
29
+ "Advanced/reference",
30
+ [
31
+ ["RPC protocol", "file.rpc.html"],
32
+ ["Releasing", "file.releasing.html"]
33
+ ]
34
+ ]
35
+ ].freeze
36
+
37
+ GUIDE_OVERVIEW = "file.README.html"
38
+ GUIDE_LINKS = ([GUIDE_OVERVIEW] + GUIDE_GROUPS.flat_map { |_title, items| items.map(&:last) }).freeze
39
+ GUIDE_SEARCH_FILES = [
40
+ ["Overview", "README.md", GUIDE_OVERVIEW],
41
+ *GUIDE_GROUPS.flat_map do |_title, items|
42
+ items.map do |label, link|
43
+ source = "doc/#{link.delete_prefix("file.").delete_suffix(".html")}.md"
44
+ [label, source, link]
45
+ end
46
+ end
47
+ ].freeze
48
+ GUIDE_FILE_LINKS = {
49
+ "doc/getting-started.md" => "file.getting-started.html",
50
+ "doc/usage.md" => "file.usage.html",
51
+ "doc/configuration.md" => "file.configuration.html",
52
+ "doc/authentication.md" => "file.authentication.html",
53
+ "doc/troubleshooting.md" => "file.troubleshooting.html",
54
+ "doc/memory.md" => "file.memory.html",
55
+ "doc/personas.md" => "file.personas.html",
56
+ "doc/extensibility.md" => "file.extensibility.html",
57
+ "doc/plugins.md" => "file.plugins.html",
58
+ "doc/web-search.md" => "file.web-search.html",
59
+ "doc/code-search.md" => "file.code-search.html",
60
+ "doc/rpc.md" => "file.rpc.html",
61
+ "doc/releasing.md" => "file.releasing.html"
62
+ }.freeze
63
+
64
+ def guide_groups
65
+ GUIDE_GROUPS
66
+ end
67
+
68
+ def guide_overview
69
+ GUIDE_OVERVIEW
70
+ end
71
+
72
+ def guide_search_index_json
73
+ JSON.generate(guide_search_index).gsub("</", "<\\/")
74
+ end
75
+
76
+ def guide_search_index
77
+ GUIDE_SEARCH_FILES.filter_map do |title, source, link|
78
+ next unless File.file?(source)
79
+
80
+ content = File.read(source)
81
+ {
82
+ title: title,
83
+ path: source,
84
+ url: url_for(link),
85
+ text: content.gsub(/[`*_#>\[\]()]/, " ").gsub(/\s+/, " ").strip
86
+ }
87
+ end
88
+ end
89
+
90
+ def current_docs_path
91
+ serializer = options.serializer
92
+ return "index.html" unless serializer
93
+
94
+ File.basename(serializer.serialized_path(object))
95
+ end
96
+
97
+ def readme_file?
98
+ defined?(@file) && @file&.name == "README"
99
+ end
100
+
101
+ def guide_file?
102
+ defined?(@file) && GUIDE_FILE_LINKS.key?(@file&.filename.to_s)
103
+ end
104
+
105
+ def home_page?
106
+ options.index && readme_file?
107
+ end
108
+
109
+ def guide_page?
110
+ (readme_file? && !options.index) || guide_file? || GUIDE_LINKS.include?(current_docs_path)
111
+ end
112
+
113
+ def api_page?
114
+ !home_page? && !guide_page?
115
+ end
116
+
117
+ def diskfile
118
+ @file.attributes[:markup] ||= markup_for_file('', @file.filename)
119
+ data = rewrite_guide_links(htmlify(@file.contents, @file.attributes[:markup]))
120
+ "<div id='filecontents'>" + data + "</div>"
121
+ end
122
+
123
+ def rewrite_guide_links(html)
124
+ GUIDE_FILE_LINKS.each do |source, target|
125
+ html = html.gsub(%(href="#{source}"), %(href="#{target}"))
126
+ end
127
+ html
128
+ end
129
+ end
130
+
131
+ include KwardDocsNavigation
132
+
133
+ def javascripts
134
+ super + %w(js/kward.js)
135
+ end
136
+
137
+ def stylesheets
138
+ super + %w(css/kward.css)
139
+ end
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.68.0
4
+ version: 0.69.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/pages.yml"
119
120
  - ".yardopts"
120
121
  - CHANGELOG.md
121
122
  - Gemfile
@@ -129,6 +130,7 @@ files:
129
130
  - doc/extensibility.md
130
131
  - doc/getting-started.md
131
132
  - doc/memory.md
133
+ - doc/personas.md
132
134
  - doc/plugins.md
133
135
  - doc/releasing.md
134
136
  - doc/rpc.md
@@ -161,6 +163,7 @@ files:
161
163
  - lib/kward/cli/settings.rb
162
164
  - lib/kward/cli/slash_commands.rb
163
165
  - lib/kward/cli/stats.rb
166
+ - lib/kward/cli/sysprompt.rb
164
167
  - lib/kward/cli/tool_summaries.rb
165
168
  - lib/kward/cli_transcript_formatter.rb
166
169
  - lib/kward/clipboard.rb
@@ -239,6 +242,8 @@ files:
239
242
  - lib/kward/tools/base.rb
240
243
  - lib/kward/tools/code_search.rb
241
244
  - lib/kward/tools/edit_file.rb
245
+ - lib/kward/tools/fetch_content.rb
246
+ - lib/kward/tools/fetch_raw.rb
242
247
  - lib/kward/tools/list_directory.rb
243
248
  - lib/kward/tools/read_file.rb
244
249
  - lib/kward/tools/read_skill.rb
@@ -246,6 +251,7 @@ files:
246
251
  - lib/kward/tools/run_shell_command.rb
247
252
  - lib/kward/tools/search/code.rb
248
253
  - lib/kward/tools/search/web.rb
254
+ - lib/kward/tools/search/web_fetch.rb
249
255
  - lib/kward/tools/tool_call.rb
250
256
  - lib/kward/tools/web_search.rb
251
257
  - lib/kward/tools/write_file.rb
@@ -253,6 +259,13 @@ files:
253
259
  - lib/kward/version.rb
254
260
  - lib/kward/workspace.rb
255
261
  - lib/main.rb
262
+ - templates/default/fulldoc/html/css/kward.css
263
+ - templates/default/fulldoc/html/images/kward_logo.png
264
+ - templates/default/fulldoc/html/js/kward.js
265
+ - templates/default/fulldoc/html/setup.rb
266
+ - templates/default/layout/html/breadcrumb.erb
267
+ - templates/default/layout/html/layout.erb
268
+ - templates/default/layout/html/setup.rb
256
269
  homepage: https://github.com/kaiwood/kward
257
270
  licenses:
258
271
  - MIT