kward 0.69.1 → 0.70.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 +22 -0
- data/doc/configuration.md +1 -1
- data/doc/getting-started.md +3 -1
- data/doc/usage.md +9 -2
- data/lib/kward/cli/prompt_interface.rb +5 -1
- data/lib/kward/cli/sessions.rb +197 -7
- data/lib/kward/cli/slash_commands.rb +16 -7
- data/lib/kward/cli.rb +1 -0
- data/lib/kward/conversation.rb +15 -0
- data/lib/kward/model/model_info.rb +9 -2
- data/lib/kward/prompt_interface/overlay_renderer.rb +24 -0
- data/lib/kward/prompt_interface/prompt_renderer.rb +13 -11
- data/lib/kward/prompt_interface/screen.rb +9 -4
- data/lib/kward/prompt_interface/selection_prompt.rb +7 -9
- data/lib/kward/prompt_interface/slash_overlay.rb +4 -4
- data/lib/kward/prompt_interface.rb +9 -4
- data/lib/kward/prompts/commands.rb +4 -2
- data/lib/kward/rpc/session_manager.rb +2 -2
- data/lib/kward/session_diff.rb +106 -9
- data/lib/kward/session_tree_renderer.rb +2 -1
- data/lib/kward/version.rb +1 -1
- data/templates/default/fulldoc/html/css/kward.css +314 -71
- data/templates/default/fulldoc/html/js/kward.js +100 -97
- data/templates/default/layout/html/layout.erb +21 -6
- data/templates/default/layout/html/setup.rb +1 -1
- metadata +1 -1
|
@@ -1,30 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
1
|
+
(() => {
|
|
28
2
|
const guideLinks = {
|
|
29
3
|
'doc/getting-started.md': 'file.getting-started.html',
|
|
30
4
|
'doc/usage.md': 'file.usage.html',
|
|
@@ -58,9 +32,12 @@ const setupGuideSearch = (signal) => {
|
|
|
58
32
|
return
|
|
59
33
|
}
|
|
60
34
|
|
|
35
|
+
let selectedIndex = -1
|
|
36
|
+
|
|
61
37
|
const closeResults = () => {
|
|
62
38
|
results.classList.remove('open')
|
|
63
39
|
results.innerHTML = ''
|
|
40
|
+
selectedIndex = -1
|
|
64
41
|
}
|
|
65
42
|
|
|
66
43
|
const excerpt = (text, query) => {
|
|
@@ -98,6 +75,18 @@ const setupGuideSearch = (signal) => {
|
|
|
98
75
|
.map((result) => result.item)
|
|
99
76
|
}
|
|
100
77
|
|
|
78
|
+
const updateActiveItem = () => {
|
|
79
|
+
const items = results.querySelectorAll('a')
|
|
80
|
+
items.forEach((item, i) => {
|
|
81
|
+
if (i === selectedIndex) {
|
|
82
|
+
item.classList.add('kward-search-active')
|
|
83
|
+
item.scrollIntoView({ block: 'nearest' })
|
|
84
|
+
} else {
|
|
85
|
+
item.classList.remove('kward-search-active')
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
101
90
|
const renderResults = () => {
|
|
102
91
|
const query = input.value.trim()
|
|
103
92
|
if (query.length < 2) {
|
|
@@ -107,6 +96,7 @@ const setupGuideSearch = (signal) => {
|
|
|
107
96
|
|
|
108
97
|
const matches = search(query)
|
|
109
98
|
results.innerHTML = ''
|
|
99
|
+
selectedIndex = -1
|
|
110
100
|
|
|
111
101
|
if (matches.length === 0) {
|
|
112
102
|
const empty = document.createElement('div')
|
|
@@ -139,17 +129,37 @@ const setupGuideSearch = (signal) => {
|
|
|
139
129
|
input.addEventListener('input', renderResults, { signal })
|
|
140
130
|
|
|
141
131
|
input.addEventListener('keydown', (event) => {
|
|
132
|
+
const items = results.querySelectorAll('a')
|
|
133
|
+
|
|
142
134
|
if (event.key === 'Escape') {
|
|
143
135
|
input.value = ''
|
|
144
136
|
closeResults()
|
|
145
137
|
input.blur()
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (items.length === 0) return
|
|
142
|
+
|
|
143
|
+
if (event.key === 'ArrowDown') {
|
|
144
|
+
event.preventDefault()
|
|
145
|
+
selectedIndex = Math.min(selectedIndex + 1, items.length - 1)
|
|
146
|
+
updateActiveItem()
|
|
147
|
+
} else if (event.key === 'ArrowUp') {
|
|
148
|
+
event.preventDefault()
|
|
149
|
+
selectedIndex = Math.max(selectedIndex - 1, 0)
|
|
150
|
+
updateActiveItem()
|
|
151
|
+
} else if (event.key === 'Enter') {
|
|
152
|
+
if (selectedIndex >= 0 && items[selectedIndex]) {
|
|
153
|
+
event.preventDefault()
|
|
154
|
+
window.location.href = items[selectedIndex].href
|
|
155
|
+
}
|
|
146
156
|
}
|
|
147
157
|
}, { signal })
|
|
148
158
|
|
|
149
159
|
form.addEventListener('submit', (event) => {
|
|
150
160
|
event.preventDefault()
|
|
151
161
|
const firstResult = results.querySelector('a')
|
|
152
|
-
if (firstResult)
|
|
162
|
+
if (firstResult) window.location.href = firstResult.href
|
|
153
163
|
}, { signal })
|
|
154
164
|
|
|
155
165
|
document.addEventListener('click', (event) => {
|
|
@@ -159,26 +169,80 @@ const setupGuideSearch = (signal) => {
|
|
|
159
169
|
|
|
160
170
|
const setupNavigation = (signal) => {
|
|
161
171
|
const toggle = document.querySelector('.kward-nav-toggle')
|
|
172
|
+
const nav = document.getElementById('kward-primary-nav')
|
|
173
|
+
|
|
174
|
+
const closeMenu = () => {
|
|
175
|
+
document.body.classList.remove('kward-nav-open')
|
|
176
|
+
if (toggle) toggle.setAttribute('aria-expanded', 'false')
|
|
177
|
+
document.querySelectorAll('.kward-nav-menu.open').forEach((menu) => {
|
|
178
|
+
menu.classList.remove('open')
|
|
179
|
+
const btn = menu.querySelector('.kward-nav-menu-button')
|
|
180
|
+
if (btn) btn.setAttribute('aria-expanded', 'false')
|
|
181
|
+
})
|
|
182
|
+
}
|
|
162
183
|
|
|
163
184
|
if (toggle) {
|
|
185
|
+
// The inline onclick handles the toggle. We only need to close
|
|
186
|
+
// sub-menus when the main menu closes.
|
|
164
187
|
toggle.addEventListener('click', () => {
|
|
165
|
-
|
|
166
|
-
|
|
188
|
+
if (!document.body.classList.contains('kward-nav-open')) {
|
|
189
|
+
document.querySelectorAll('.kward-nav-menu.open').forEach((menu) => {
|
|
190
|
+
menu.classList.remove('open')
|
|
191
|
+
const btn = menu.querySelector('.kward-nav-menu-button')
|
|
192
|
+
if (btn) btn.setAttribute('aria-expanded', 'false')
|
|
193
|
+
})
|
|
194
|
+
}
|
|
167
195
|
}, { signal })
|
|
168
196
|
}
|
|
169
197
|
|
|
170
198
|
document.querySelectorAll('.kward-nav-menu-button').forEach((button) => {
|
|
171
199
|
button.addEventListener('click', (event) => {
|
|
172
200
|
event.stopPropagation()
|
|
173
|
-
button.parentElement
|
|
201
|
+
const menu = button.parentElement
|
|
202
|
+
const wasOpen = menu.classList.contains('open')
|
|
203
|
+
document.querySelectorAll('.kward-nav-menu.open').forEach((m) => {
|
|
204
|
+
m.classList.remove('open')
|
|
205
|
+
const b = m.querySelector('.kward-nav-menu-button')
|
|
206
|
+
if (b) b.setAttribute('aria-expanded', 'false')
|
|
207
|
+
})
|
|
208
|
+
if (!wasOpen) {
|
|
209
|
+
menu.classList.add('open')
|
|
210
|
+
button.setAttribute('aria-expanded', 'true')
|
|
211
|
+
}
|
|
174
212
|
}, { signal })
|
|
175
213
|
})
|
|
176
214
|
|
|
215
|
+
// Close menu when a nav link is clicked (mobile navigation)
|
|
216
|
+
if (nav) {
|
|
217
|
+
nav.addEventListener('click', (event) => {
|
|
218
|
+
const link = event.target.closest('a[href]')
|
|
219
|
+
if (link) closeMenu()
|
|
220
|
+
}, { signal })
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Close on outside click
|
|
177
224
|
document.addEventListener('click', (event) => {
|
|
225
|
+
if (document.body.classList.contains('kward-nav-open')) {
|
|
226
|
+
if (nav && !nav.contains(event.target) && toggle && !toggle.contains(event.target)) {
|
|
227
|
+
closeMenu()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
178
230
|
document.querySelectorAll('.kward-nav-menu.open').forEach((menu) => {
|
|
179
|
-
if (!menu.contains(event.target))
|
|
231
|
+
if (!menu.contains(event.target)) {
|
|
232
|
+
menu.classList.remove('open')
|
|
233
|
+
const btn = menu.querySelector('.kward-nav-menu-button')
|
|
234
|
+
if (btn) btn.setAttribute('aria-expanded', 'false')
|
|
235
|
+
}
|
|
180
236
|
})
|
|
181
237
|
}, { signal })
|
|
238
|
+
|
|
239
|
+
// Close on Escape
|
|
240
|
+
document.addEventListener('keydown', (event) => {
|
|
241
|
+
if (event.key === 'Escape' && document.body.classList.contains('kward-nav-open')) {
|
|
242
|
+
closeMenu()
|
|
243
|
+
if (toggle) toggle.focus()
|
|
244
|
+
}
|
|
245
|
+
}, { signal })
|
|
182
246
|
}
|
|
183
247
|
|
|
184
248
|
const rewriteGuideLinks = () => {
|
|
@@ -221,76 +285,15 @@ const initializePage = () => {
|
|
|
221
285
|
if (pageController) pageController.abort()
|
|
222
286
|
pageController = new AbortController()
|
|
223
287
|
|
|
224
|
-
resetScrollPosition()
|
|
225
288
|
setupGuideSearch(pageController.signal)
|
|
226
289
|
setupNavigation(pageController.signal)
|
|
227
290
|
rewriteGuideLinks()
|
|
228
291
|
setupCodeCopy()
|
|
229
292
|
}
|
|
230
293
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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)
|
|
294
|
+
if (document.readyState === 'loading') {
|
|
295
|
+
document.addEventListener('DOMContentLoaded', initializePage, { once: true })
|
|
296
|
+
} else {
|
|
253
297
|
initializePage()
|
|
254
298
|
}
|
|
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)
|
|
299
|
+
})()
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<%= erb(:headers) %>
|
|
5
5
|
</head>
|
|
6
6
|
<body class="kward-docs <%= home_page? ? 'kward-home-body' : 'kward-content-body' %>">
|
|
7
|
-
<
|
|
7
|
+
<a href="#main" class="kward-skip-link">Skip to content</a>
|
|
8
8
|
|
|
9
9
|
<header class="kward-topbar">
|
|
10
10
|
<a class="kward-brand" href="<%= url_for('index.html') %>">
|
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
<strong>Kward</strong>
|
|
14
14
|
</span>
|
|
15
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
|
-
|
|
16
|
+
<button class="kward-nav-toggle" type="button" aria-expanded="false" aria-controls="kward-primary-nav" aria-label="Toggle navigation menu" onclick="document.body.classList.toggle('kward-nav-open'); this.setAttribute('aria-expanded', document.body.classList.contains('kward-nav-open'))">
|
|
17
|
+
<span class="kward-nav-toggle-bar"></span>
|
|
18
|
+
<span class="kward-nav-toggle-bar"></span>
|
|
19
|
+
<span class="kward-nav-toggle-bar"></span>
|
|
20
|
+
<span class="kward-sr-only">Menu</span>
|
|
18
21
|
</button>
|
|
19
22
|
<nav id="kward-primary-nav" class="kward-topnav" aria-label="Primary navigation">
|
|
20
23
|
<a href="<%= url_for('index.html') %>" class="<%= 'active' if home_page? %>">Home</a>
|
|
21
24
|
<div class="kward-nav-menu <%= 'active' if guide_page? %>">
|
|
22
25
|
<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="
|
|
26
|
+
<button class="kward-nav-menu-button" type="button" aria-label="Expand user guides" aria-expanded="false">⌄</button>
|
|
24
27
|
<div class="kward-nav-dropdown">
|
|
25
28
|
<% guide_groups.each do |title, items| %>
|
|
26
29
|
<section>
|
|
@@ -47,7 +50,17 @@
|
|
|
47
50
|
<section class="kward-hero">
|
|
48
51
|
<div class="kward-hero-copy">
|
|
49
52
|
<p class="kward-eyebrow">⌘ Ruby CLI Coding Agent</p>
|
|
50
|
-
<h1>
|
|
53
|
+
<h1>
|
|
54
|
+
<span class="kward-swosh" aria-label="Your terminal.">
|
|
55
|
+
<span class="kward-swosh-text kward-swosh-text-a" aria-hidden="true">Your terminal.</span>
|
|
56
|
+
<span class="kward-swosh-text kward-swosh-text-b" aria-hidden="true">Your RPC frontend.</span>
|
|
57
|
+
</span>
|
|
58
|
+
<br>
|
|
59
|
+
<span class="kward-swosh" aria-label="Your agent.">
|
|
60
|
+
<span class="kward-swosh-text kward-swosh-text-c" aria-hidden="true">Your agent.</span>
|
|
61
|
+
<span class="kward-swosh-text kward-swosh-text-d" aria-hidden="true">Your LLM engine.</span>
|
|
62
|
+
</span>
|
|
63
|
+
</h1>
|
|
51
64
|
<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
65
|
<div class="kward-actions">
|
|
53
66
|
<a class="kward-primary-button" href="<%= url_for('file.README.html') %>">Get Started ›</a>
|
|
@@ -137,5 +150,7 @@ kward --working-directory ~/code/project "Explain this project"</code></pre>
|
|
|
137
150
|
</main>
|
|
138
151
|
</div>
|
|
139
152
|
<% end %>
|
|
153
|
+
|
|
154
|
+
<script type="text/javascript" charset="utf-8" src="<%= url_for('js/kward.js') %>"></script>
|
|
140
155
|
</body>
|
|
141
156
|
</html>
|