kward 0.69.1 → 0.71.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/.github/workflows/pages.yml +1 -1
- data/CHANGELOG.md +68 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +90 -2
- data/README.md +30 -6
- data/Rakefile +96 -0
- data/doc/agent-tools.md +43 -0
- data/doc/api.md +92 -0
- data/doc/authentication.md +39 -25
- data/doc/configuration.md +2 -16
- data/doc/context-tools.md +70 -0
- data/doc/getting-started.md +3 -1
- data/doc/plugins.md +2 -2
- data/doc/releasing.md +14 -5
- data/doc/rpc.md +3 -11
- data/doc/session-management.md +220 -0
- data/doc/usage.md +13 -7
- data/doc/workspace-tools.md +105 -0
- data/lib/kward/cli/commands.rb +8 -0
- data/lib/kward/cli/openrouter_commands.rb +55 -0
- data/lib/kward/cli/prompt_interface.rb +85 -7
- data/lib/kward/cli/rendering.rb +11 -6
- data/lib/kward/cli/sessions.rb +454 -15
- data/lib/kward/cli/settings.rb +0 -30
- data/lib/kward/cli/slash_commands.rb +38 -11
- data/lib/kward/cli.rb +14 -0
- data/lib/kward/compactor.rb +4 -1
- data/lib/kward/config_files.rb +4 -6
- data/lib/kward/conversation.rb +49 -5
- data/lib/kward/model/client.rb +37 -50
- data/lib/kward/model/context_usage.rb +13 -6
- data/lib/kward/model/model_info.rb +92 -9
- data/lib/kward/model/payloads.rb +2 -0
- data/lib/kward/openrouter_model_cache.rb +120 -0
- data/lib/kward/plugin_registry.rb +47 -1
- data/lib/kward/prompt_interface/banner.rb +16 -51
- data/lib/kward/prompt_interface/composer_controller.rb +60 -87
- data/lib/kward/prompt_interface/composer_renderer.rb +7 -1
- data/lib/kward/prompt_interface/key_handler.rb +31 -10
- data/lib/kward/prompt_interface/layout.rb +2 -2
- data/lib/kward/prompt_interface/overlay_renderer.rb +24 -0
- data/lib/kward/prompt_interface/prompt_renderer.rb +23 -2
- data/lib/kward/prompt_interface/question_prompt.rb +34 -42
- data/lib/kward/prompt_interface/runtime_state.rb +6 -1
- data/lib/kward/prompt_interface/screen.rb +10 -4
- data/lib/kward/prompt_interface/selection_prompt.rb +518 -61
- data/lib/kward/prompt_interface/slash_overlay.rb +4 -4
- data/lib/kward/prompt_interface/transcript_buffer.rb +7 -16
- data/lib/kward/prompt_interface/transcript_renderer.rb +3 -3
- data/lib/kward/prompt_interface.rb +31 -32
- data/lib/kward/prompts/commands.rb +6 -3
- data/lib/kward/prompts.rb +2 -2
- data/lib/kward/rpc/server.rb +3 -8
- data/lib/kward/rpc/session_manager.rb +19 -8
- data/lib/kward/session_diff.rb +106 -9
- data/lib/kward/session_store.rb +23 -4
- data/lib/kward/session_tree_renderer.rb +2 -1
- data/lib/kward/telemetry/logger.rb +5 -3
- data/lib/kward/tool_output_compactor.rb +127 -0
- data/lib/kward/tools/base.rb +8 -2
- data/lib/kward/tools/registry.rb +37 -6
- data/lib/kward/tools/retrieve_tool_output.rb +71 -0
- data/lib/kward/tools/search/web.rb +2 -2
- data/lib/kward/tools/summarize_file_structure.rb +29 -0
- data/lib/kward/tools/tool_call.rb +2 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workspace.rb +58 -2
- data/templates/default/fulldoc/html/css/kward.css +570 -78
- data/templates/default/fulldoc/html/full_list.erb +107 -0
- data/templates/default/fulldoc/html/js/kward.js +259 -97
- data/templates/default/fulldoc/html/setup.rb +8 -0
- data/templates/default/kward_navigation.rb +91 -0
- data/templates/default/layout/html/layout.erb +59 -13
- data/templates/default/layout/html/setup.rb +34 -39
- metadata +13 -3
- data/lib/kward/resources/avatar_kward_logo.rb +0 -50
- data/lib/kward/resources/pixel_logo.rb +0 -232
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html <%= "lang=\"#{html_lang}\"" unless html_lang.nil? %>>
|
|
3
|
+
<head>
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5
|
+
<meta charset="<%= charset %>">
|
|
6
|
+
<% stylesheets_full_list.each do |stylesheet| %>
|
|
7
|
+
<link rel="stylesheet" href="<%= mtime_url(stylesheet) %>" type="text/css" media="screen">
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<% javascripts_full_list.each do |javascript| %>
|
|
11
|
+
<script type="text/javascript" charset="utf-8" src="<%= mtime_url(javascript) %>"></script>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<title><%= @list_title %></title>
|
|
15
|
+
<base id="base_target" target="_parent">
|
|
16
|
+
</head>
|
|
17
|
+
<body class="kward-docs kward-content-body kward-full-list-body">
|
|
18
|
+
<a href="#content" class="kward-skip-link">Skip to content</a>
|
|
19
|
+
|
|
20
|
+
<header class="kward-topbar">
|
|
21
|
+
<a class="kward-brand" href="index.html">
|
|
22
|
+
<img class="kward-brand-logo" src="images/kward_logo.png" alt="" aria-hidden="true">
|
|
23
|
+
<span>
|
|
24
|
+
<strong>Kward</strong>
|
|
25
|
+
</span>
|
|
26
|
+
</a>
|
|
27
|
+
<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'))">
|
|
28
|
+
<span class="kward-nav-toggle-bar"></span>
|
|
29
|
+
<span class="kward-nav-toggle-bar"></span>
|
|
30
|
+
<span class="kward-nav-toggle-bar"></span>
|
|
31
|
+
<span class="kward-sr-only">Menu</span>
|
|
32
|
+
</button>
|
|
33
|
+
<nav id="kward-primary-nav" class="kward-topnav" aria-label="Primary navigation">
|
|
34
|
+
<a href="index.html">Home</a>
|
|
35
|
+
<div class="kward-nav-menu">
|
|
36
|
+
<a class="kward-nav-menu-link" href="file.README.html">Guides</a>
|
|
37
|
+
<button class="kward-nav-menu-button" type="button" aria-label="Expand guides" aria-expanded="false">⌄</button>
|
|
38
|
+
<div class="kward-nav-dropdown">
|
|
39
|
+
<% guide_groups.each do |title, items| %>
|
|
40
|
+
<section>
|
|
41
|
+
<h2><%= h title %></h2>
|
|
42
|
+
<% items.each do |label, link| %>
|
|
43
|
+
<a href="<%= url_for(link) %>"><%= h label %></a>
|
|
44
|
+
<% end %>
|
|
45
|
+
</section>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="kward-nav-menu">
|
|
50
|
+
<a class="kward-nav-menu-link" href="file.extensibility.html">Advanced</a>
|
|
51
|
+
<button class="kward-nav-menu-button" type="button" aria-label="Expand advanced guides" aria-expanded="false">⌄</button>
|
|
52
|
+
<div class="kward-nav-dropdown kward-advanced-nav-dropdown">
|
|
53
|
+
<% extension_groups.each do |title, items| %>
|
|
54
|
+
<section>
|
|
55
|
+
<h2><%= h title %></h2>
|
|
56
|
+
<% items.each do |label, link| %>
|
|
57
|
+
<a href="<%= url_for(link) %>"><%= h label %></a>
|
|
58
|
+
<% end %>
|
|
59
|
+
</section>
|
|
60
|
+
<% end %>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="kward-nav-menu active">
|
|
64
|
+
<a class="kward-nav-menu-link" href="file.api.html">API Docs</a>
|
|
65
|
+
<button class="kward-nav-menu-button" type="button" aria-label="Expand API docs" aria-expanded="false">⌄</button>
|
|
66
|
+
<div class="kward-nav-dropdown kward-api-nav-dropdown">
|
|
67
|
+
<% api_groups.each do |title, items| %>
|
|
68
|
+
<section>
|
|
69
|
+
<h2><%= h title %></h2>
|
|
70
|
+
<% items.each do |label, link| %>
|
|
71
|
+
<a href="<%= url_for(link) %>"><%= h label %></a>
|
|
72
|
+
<% end %>
|
|
73
|
+
</section>
|
|
74
|
+
<% end %>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</nav>
|
|
78
|
+
<a class="kward-api-index-link" href="class_list.html">Search API index</a>
|
|
79
|
+
</header>
|
|
80
|
+
|
|
81
|
+
<main class="kward-page">
|
|
82
|
+
<div id="content" class="kward-content-page kward-api-page">
|
|
83
|
+
<div class="fixed_header">
|
|
84
|
+
<h1 id="full_list_header"><%= @list_title %></h1>
|
|
85
|
+
<div id="full_list_nav">
|
|
86
|
+
<% menu_lists.each do |list| %>
|
|
87
|
+
<span><a target="_self" href="<%= url_for_list list[:type] %>">
|
|
88
|
+
<%= list[:title] %>
|
|
89
|
+
</a></span>
|
|
90
|
+
<% end %>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div id="search">
|
|
94
|
+
<label for="search-class">Search:</label>
|
|
95
|
+
<input id="search-class" type="text">
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<ul id="full_list" class="<%= @list_class || @list_type %>">
|
|
100
|
+
<%= erb "full_list_#{@list_type}" %>
|
|
101
|
+
</ul>
|
|
102
|
+
</div>
|
|
103
|
+
</main>
|
|
104
|
+
|
|
105
|
+
<script type="text/javascript" charset="utf-8" src="js/kward.js"></script>
|
|
106
|
+
</body>
|
|
107
|
+
</html>
|
|
@@ -1,47 +1,94 @@
|
|
|
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',
|
|
31
5
|
'doc/configuration.md': 'file.configuration.html',
|
|
32
6
|
'doc/authentication.md': 'file.authentication.html',
|
|
33
7
|
'doc/troubleshooting.md': 'file.troubleshooting.html',
|
|
8
|
+
'doc/session-management.md': 'file.session-management.html',
|
|
34
9
|
'doc/memory.md': 'file.memory.html',
|
|
35
10
|
'doc/personas.md': 'file.personas.html',
|
|
36
11
|
'doc/extensibility.md': 'file.extensibility.html',
|
|
37
12
|
'doc/plugins.md': 'file.plugins.html',
|
|
13
|
+
'doc/agent-tools.md': 'file.agent-tools.html',
|
|
14
|
+
'doc/workspace-tools.md': 'file.workspace-tools.html',
|
|
38
15
|
'doc/web-search.md': 'file.web-search.html',
|
|
39
16
|
'doc/code-search.md': 'file.code-search.html',
|
|
17
|
+
'doc/context-tools.md': 'file.context-tools.html',
|
|
40
18
|
'doc/rpc.md': 'file.rpc.html',
|
|
41
19
|
'doc/releasing.md': 'file.releasing.html'
|
|
42
20
|
}
|
|
43
21
|
|
|
44
22
|
let pageController = null
|
|
23
|
+
let navigating = false
|
|
24
|
+
|
|
25
|
+
const scrollToCurrentHash = () => {
|
|
26
|
+
if (!window.location.hash) {
|
|
27
|
+
window.scrollTo({ top: 0 })
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const id = decodeURIComponent(window.location.hash.slice(1))
|
|
32
|
+
const target = document.getElementById(id) || document.getElementsByName(id)[0]
|
|
33
|
+
if (target) target.scrollIntoView()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const replacePage = async (url, pushState = true) => {
|
|
37
|
+
if (navigating) return
|
|
38
|
+
navigating = true
|
|
39
|
+
document.documentElement.classList.add('kward-navigating')
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(url, { headers: { 'X-Requested-With': 'Kward-Docs' } })
|
|
43
|
+
if (!response.ok) throw new Error(`Navigation failed with ${response.status}`)
|
|
44
|
+
|
|
45
|
+
const html = await response.text()
|
|
46
|
+
const nextDocument = new DOMParser().parseFromString(html, 'text/html')
|
|
47
|
+
if (!nextDocument.body) throw new Error('Navigation response did not include a body')
|
|
48
|
+
|
|
49
|
+
document.title = nextDocument.title
|
|
50
|
+
document.body.className = nextDocument.body.className
|
|
51
|
+
document.body.innerHTML = nextDocument.body.innerHTML
|
|
52
|
+
|
|
53
|
+
if (pushState) window.history.pushState({ kwardDocs: true }, '', url)
|
|
54
|
+
initializePage()
|
|
55
|
+
scrollToCurrentHash()
|
|
56
|
+
} catch (_error) {
|
|
57
|
+
window.location.href = url
|
|
58
|
+
} finally {
|
|
59
|
+
navigating = false
|
|
60
|
+
document.documentElement.classList.remove('kward-navigating')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const isNavigableLink = (event, link) => {
|
|
65
|
+
if (event.defaultPrevented || event.button !== 0) return false
|
|
66
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return false
|
|
67
|
+
if (link.target && link.target !== '_self') return false
|
|
68
|
+
if (link.download || link.dataset.noTurbo) return false
|
|
69
|
+
|
|
70
|
+
const url = new URL(link.href, window.location.href)
|
|
71
|
+
if (url.origin !== window.location.origin) return false
|
|
72
|
+
if (url.pathname === window.location.pathname && url.search === window.location.search) return false
|
|
73
|
+
|
|
74
|
+
return url.pathname === '/' || url.pathname.endsWith('/') || url.pathname.endsWith('.html')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const visitLink = (link) => replacePage(link.href)
|
|
78
|
+
|
|
79
|
+
const setupTurbolinks = (signal) => {
|
|
80
|
+
document.addEventListener('click', (event) => {
|
|
81
|
+
const link = event.target.closest('a[href]')
|
|
82
|
+
if (!link || !isNavigableLink(event, link)) return
|
|
83
|
+
|
|
84
|
+
event.preventDefault()
|
|
85
|
+
visitLink(link)
|
|
86
|
+
}, { signal })
|
|
87
|
+
|
|
88
|
+
window.addEventListener('popstate', () => {
|
|
89
|
+
replacePage(window.location.href, false)
|
|
90
|
+
}, { signal })
|
|
91
|
+
}
|
|
45
92
|
|
|
46
93
|
const setupGuideSearch = (signal) => {
|
|
47
94
|
const form = document.querySelector('.kward-guide-search')
|
|
@@ -58,9 +105,12 @@ const setupGuideSearch = (signal) => {
|
|
|
58
105
|
return
|
|
59
106
|
}
|
|
60
107
|
|
|
108
|
+
let selectedIndex = -1
|
|
109
|
+
|
|
61
110
|
const closeResults = () => {
|
|
62
111
|
results.classList.remove('open')
|
|
63
112
|
results.innerHTML = ''
|
|
113
|
+
selectedIndex = -1
|
|
64
114
|
}
|
|
65
115
|
|
|
66
116
|
const excerpt = (text, query) => {
|
|
@@ -98,6 +148,18 @@ const setupGuideSearch = (signal) => {
|
|
|
98
148
|
.map((result) => result.item)
|
|
99
149
|
}
|
|
100
150
|
|
|
151
|
+
const updateActiveItem = () => {
|
|
152
|
+
const items = results.querySelectorAll('a')
|
|
153
|
+
items.forEach((item, i) => {
|
|
154
|
+
if (i === selectedIndex) {
|
|
155
|
+
item.classList.add('kward-search-active')
|
|
156
|
+
item.scrollIntoView({ block: 'nearest' })
|
|
157
|
+
} else {
|
|
158
|
+
item.classList.remove('kward-search-active')
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
101
163
|
const renderResults = () => {
|
|
102
164
|
const query = input.value.trim()
|
|
103
165
|
if (query.length < 2) {
|
|
@@ -107,6 +169,7 @@ const setupGuideSearch = (signal) => {
|
|
|
107
169
|
|
|
108
170
|
const matches = search(query)
|
|
109
171
|
results.innerHTML = ''
|
|
172
|
+
selectedIndex = -1
|
|
110
173
|
|
|
111
174
|
if (matches.length === 0) {
|
|
112
175
|
const empty = document.createElement('div')
|
|
@@ -139,17 +202,37 @@ const setupGuideSearch = (signal) => {
|
|
|
139
202
|
input.addEventListener('input', renderResults, { signal })
|
|
140
203
|
|
|
141
204
|
input.addEventListener('keydown', (event) => {
|
|
205
|
+
const items = results.querySelectorAll('a')
|
|
206
|
+
|
|
142
207
|
if (event.key === 'Escape') {
|
|
143
208
|
input.value = ''
|
|
144
209
|
closeResults()
|
|
145
210
|
input.blur()
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (items.length === 0) return
|
|
215
|
+
|
|
216
|
+
if (event.key === 'ArrowDown') {
|
|
217
|
+
event.preventDefault()
|
|
218
|
+
selectedIndex = Math.min(selectedIndex + 1, items.length - 1)
|
|
219
|
+
updateActiveItem()
|
|
220
|
+
} else if (event.key === 'ArrowUp') {
|
|
221
|
+
event.preventDefault()
|
|
222
|
+
selectedIndex = Math.max(selectedIndex - 1, 0)
|
|
223
|
+
updateActiveItem()
|
|
224
|
+
} else if (event.key === 'Enter') {
|
|
225
|
+
if (selectedIndex >= 0 && items[selectedIndex]) {
|
|
226
|
+
event.preventDefault()
|
|
227
|
+
visitLink(items[selectedIndex])
|
|
228
|
+
}
|
|
146
229
|
}
|
|
147
230
|
}, { signal })
|
|
148
231
|
|
|
149
232
|
form.addEventListener('submit', (event) => {
|
|
150
233
|
event.preventDefault()
|
|
151
234
|
const firstResult = results.querySelector('a')
|
|
152
|
-
if (firstResult)
|
|
235
|
+
if (firstResult) visitLink(firstResult)
|
|
153
236
|
}, { signal })
|
|
154
237
|
|
|
155
238
|
document.addEventListener('click', (event) => {
|
|
@@ -159,26 +242,80 @@ const setupGuideSearch = (signal) => {
|
|
|
159
242
|
|
|
160
243
|
const setupNavigation = (signal) => {
|
|
161
244
|
const toggle = document.querySelector('.kward-nav-toggle')
|
|
245
|
+
const nav = document.getElementById('kward-primary-nav')
|
|
246
|
+
|
|
247
|
+
const closeMenu = () => {
|
|
248
|
+
document.body.classList.remove('kward-nav-open')
|
|
249
|
+
if (toggle) toggle.setAttribute('aria-expanded', 'false')
|
|
250
|
+
document.querySelectorAll('.kward-nav-menu.open').forEach((menu) => {
|
|
251
|
+
menu.classList.remove('open')
|
|
252
|
+
const btn = menu.querySelector('.kward-nav-menu-button')
|
|
253
|
+
if (btn) btn.setAttribute('aria-expanded', 'false')
|
|
254
|
+
})
|
|
255
|
+
}
|
|
162
256
|
|
|
163
257
|
if (toggle) {
|
|
258
|
+
// The inline onclick handles the toggle. We only need to close
|
|
259
|
+
// sub-menus when the main menu closes.
|
|
164
260
|
toggle.addEventListener('click', () => {
|
|
165
|
-
|
|
166
|
-
|
|
261
|
+
if (!document.body.classList.contains('kward-nav-open')) {
|
|
262
|
+
document.querySelectorAll('.kward-nav-menu.open').forEach((menu) => {
|
|
263
|
+
menu.classList.remove('open')
|
|
264
|
+
const btn = menu.querySelector('.kward-nav-menu-button')
|
|
265
|
+
if (btn) btn.setAttribute('aria-expanded', 'false')
|
|
266
|
+
})
|
|
267
|
+
}
|
|
167
268
|
}, { signal })
|
|
168
269
|
}
|
|
169
270
|
|
|
170
271
|
document.querySelectorAll('.kward-nav-menu-button').forEach((button) => {
|
|
171
272
|
button.addEventListener('click', (event) => {
|
|
172
273
|
event.stopPropagation()
|
|
173
|
-
button.parentElement
|
|
274
|
+
const menu = button.parentElement
|
|
275
|
+
const wasOpen = menu.classList.contains('open')
|
|
276
|
+
document.querySelectorAll('.kward-nav-menu.open').forEach((m) => {
|
|
277
|
+
m.classList.remove('open')
|
|
278
|
+
const b = m.querySelector('.kward-nav-menu-button')
|
|
279
|
+
if (b) b.setAttribute('aria-expanded', 'false')
|
|
280
|
+
})
|
|
281
|
+
if (!wasOpen) {
|
|
282
|
+
menu.classList.add('open')
|
|
283
|
+
button.setAttribute('aria-expanded', 'true')
|
|
284
|
+
}
|
|
174
285
|
}, { signal })
|
|
175
286
|
})
|
|
176
287
|
|
|
288
|
+
// Close menu when a nav link is clicked (mobile navigation)
|
|
289
|
+
if (nav) {
|
|
290
|
+
nav.addEventListener('click', (event) => {
|
|
291
|
+
const link = event.target.closest('a[href]')
|
|
292
|
+
if (link) closeMenu()
|
|
293
|
+
}, { signal })
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Close on outside click
|
|
177
297
|
document.addEventListener('click', (event) => {
|
|
298
|
+
if (document.body.classList.contains('kward-nav-open')) {
|
|
299
|
+
if (nav && !nav.contains(event.target) && toggle && !toggle.contains(event.target)) {
|
|
300
|
+
closeMenu()
|
|
301
|
+
}
|
|
302
|
+
}
|
|
178
303
|
document.querySelectorAll('.kward-nav-menu.open').forEach((menu) => {
|
|
179
|
-
if (!menu.contains(event.target))
|
|
304
|
+
if (!menu.contains(event.target)) {
|
|
305
|
+
menu.classList.remove('open')
|
|
306
|
+
const btn = menu.querySelector('.kward-nav-menu-button')
|
|
307
|
+
if (btn) btn.setAttribute('aria-expanded', 'false')
|
|
308
|
+
}
|
|
180
309
|
})
|
|
181
310
|
}, { signal })
|
|
311
|
+
|
|
312
|
+
// Close on Escape
|
|
313
|
+
document.addEventListener('keydown', (event) => {
|
|
314
|
+
if (event.key === 'Escape' && document.body.classList.contains('kward-nav-open')) {
|
|
315
|
+
closeMenu()
|
|
316
|
+
if (toggle) toggle.focus()
|
|
317
|
+
}
|
|
318
|
+
}, { signal })
|
|
182
319
|
}
|
|
183
320
|
|
|
184
321
|
const rewriteGuideLinks = () => {
|
|
@@ -188,6 +325,90 @@ const rewriteGuideLinks = () => {
|
|
|
188
325
|
})
|
|
189
326
|
}
|
|
190
327
|
|
|
328
|
+
const setupTableOfContents = () => {
|
|
329
|
+
const existingToc = document.getElementById('toc')
|
|
330
|
+
if (document.querySelector('.kward-no-toc')) {
|
|
331
|
+
if (existingToc) existingToc.remove()
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const fileContents = document.getElementById('filecontents')
|
|
336
|
+
const content = document.getElementById('content')
|
|
337
|
+
if (!fileContents || !content || content.querySelector('#toc')) return
|
|
338
|
+
|
|
339
|
+
const headingTags = ['h2', 'h3', 'h4', 'h5', 'h6']
|
|
340
|
+
if (fileContents.querySelectorAll('h1').length > 1) headingTags.unshift('h1')
|
|
341
|
+
|
|
342
|
+
const headings = Array.from(fileContents.querySelectorAll(headingTags.join(', ')))
|
|
343
|
+
.filter((heading) => !heading.closest('.method_details .docstring') && heading.id !== 'filecontents')
|
|
344
|
+
if (headings.length === 0) return
|
|
345
|
+
|
|
346
|
+
const topLevel = document.createElement('ol')
|
|
347
|
+
topLevel.className = 'top'
|
|
348
|
+
|
|
349
|
+
let currentList = topLevel
|
|
350
|
+
let currentItem = null
|
|
351
|
+
let counter = 0
|
|
352
|
+
let lastLevel = parseInt(headingTags[0].slice(1), 10)
|
|
353
|
+
|
|
354
|
+
headings.forEach((heading) => {
|
|
355
|
+
const level = parseInt(heading.tagName.slice(1), 10)
|
|
356
|
+
|
|
357
|
+
if (!heading.id) {
|
|
358
|
+
let proposedId = heading.getAttribute('toc-id')
|
|
359
|
+
if (!proposedId) {
|
|
360
|
+
proposedId = heading.textContent.replace(/[^a-z0-9-]/gi, '_')
|
|
361
|
+
if (document.getElementById(proposedId)) {
|
|
362
|
+
proposedId += counter
|
|
363
|
+
counter += 1
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
heading.id = proposedId
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (level > lastLevel) {
|
|
370
|
+
while (level > lastLevel) {
|
|
371
|
+
if (!currentItem) {
|
|
372
|
+
currentItem = document.createElement('li')
|
|
373
|
+
currentList.appendChild(currentItem)
|
|
374
|
+
}
|
|
375
|
+
const nestedList = document.createElement('ol')
|
|
376
|
+
currentItem.appendChild(nestedList)
|
|
377
|
+
currentList = nestedList
|
|
378
|
+
currentItem = null
|
|
379
|
+
lastLevel += 1
|
|
380
|
+
}
|
|
381
|
+
} else if (level < lastLevel) {
|
|
382
|
+
while (level < lastLevel && currentList.parentElement) {
|
|
383
|
+
currentList = currentList.parentElement.parentElement
|
|
384
|
+
lastLevel -= 1
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const title = heading.getAttribute('toc-title') || heading.textContent
|
|
389
|
+
const item = document.createElement('li')
|
|
390
|
+
const link = document.createElement('a')
|
|
391
|
+
link.href = `#${heading.id}`
|
|
392
|
+
link.textContent = title
|
|
393
|
+
item.appendChild(link)
|
|
394
|
+
currentList.appendChild(item)
|
|
395
|
+
currentItem = item
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
const toc = document.createElement('div')
|
|
399
|
+
toc.id = 'toc'
|
|
400
|
+
toc.innerHTML = '<p class="title hide_toc"><a href="#"><strong>Table of Contents</strong></a></p>'
|
|
401
|
+
toc.appendChild(topLevel)
|
|
402
|
+
content.insertBefore(toc, content.firstChild)
|
|
403
|
+
|
|
404
|
+
const hideLink = toc.querySelector('.hide_toc')
|
|
405
|
+
hideLink.addEventListener('click', (event) => {
|
|
406
|
+
event.preventDefault()
|
|
407
|
+
const hidden = toc.classList.toggle('hidden')
|
|
408
|
+
topLevel.style.display = hidden ? 'none' : ''
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
|
|
191
412
|
const setupCodeCopy = () => {
|
|
192
413
|
document.querySelectorAll('pre').forEach((block) => {
|
|
193
414
|
if (block.closest('.code-copy-wrapper')) return
|
|
@@ -221,76 +442,17 @@ const initializePage = () => {
|
|
|
221
442
|
if (pageController) pageController.abort()
|
|
222
443
|
pageController = new AbortController()
|
|
223
444
|
|
|
224
|
-
|
|
445
|
+
setupTurbolinks(pageController.signal)
|
|
225
446
|
setupGuideSearch(pageController.signal)
|
|
226
447
|
setupNavigation(pageController.signal)
|
|
227
448
|
rewriteGuideLinks()
|
|
449
|
+
setupTableOfContents()
|
|
228
450
|
setupCodeCopy()
|
|
229
451
|
}
|
|
230
452
|
|
|
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)
|
|
453
|
+
if (document.readyState === 'loading') {
|
|
454
|
+
document.addEventListener('DOMContentLoaded', initializePage, { once: true })
|
|
455
|
+
} else {
|
|
253
456
|
initializePage()
|
|
254
457
|
}
|
|
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)
|
|
458
|
+
})()
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../kward_navigation"
|
|
4
|
+
|
|
5
|
+
include KwardDocsNavigationData
|
|
6
|
+
|
|
3
7
|
def generate_assets
|
|
4
8
|
super
|
|
5
9
|
asset('css/kward.css', file('css/kward.css', true))
|
|
6
10
|
asset('js/kward.js', file('js/kward.js', true))
|
|
7
11
|
asset('images/kward_logo.png', file('images/kward_logo.png', true))
|
|
8
12
|
end
|
|
13
|
+
|
|
14
|
+
def stylesheets_full_list
|
|
15
|
+
super + %w(css/kward.css)
|
|
16
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KwardDocsNavigationData
|
|
4
|
+
GUIDE_GROUPS = [
|
|
5
|
+
[
|
|
6
|
+
"Start here",
|
|
7
|
+
[
|
|
8
|
+
["Getting started", "file.getting-started.html"],
|
|
9
|
+
["Usage", "file.usage.html"],
|
|
10
|
+
["Configuration", "file.configuration.html"],
|
|
11
|
+
["Authentication", "file.authentication.html"],
|
|
12
|
+
["Troubleshooting", "file.troubleshooting.html"]
|
|
13
|
+
]
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
"Feature guides",
|
|
17
|
+
[
|
|
18
|
+
["Sessions", "file.session-management.html"],
|
|
19
|
+
["Memory", "file.memory.html"],
|
|
20
|
+
["Personas", "file.personas.html"]
|
|
21
|
+
]
|
|
22
|
+
]
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
EXTENSION_GROUPS = [
|
|
26
|
+
[
|
|
27
|
+
"Customize",
|
|
28
|
+
[
|
|
29
|
+
["Extensibility", "file.extensibility.html"],
|
|
30
|
+
["Plugins", "file.plugins.html"]
|
|
31
|
+
]
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
"Integrate",
|
|
35
|
+
[
|
|
36
|
+
["RPC protocol", "file.rpc.html"],
|
|
37
|
+
["Releasing", "file.releasing.html"]
|
|
38
|
+
]
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"Agent tools",
|
|
42
|
+
[
|
|
43
|
+
["Overview", "file.agent-tools.html"],
|
|
44
|
+
["Workspace tools", "file.workspace-tools.html"],
|
|
45
|
+
["Web search", "file.web-search.html"],
|
|
46
|
+
["Code search", "file.code-search.html"],
|
|
47
|
+
["Context tools", "file.context-tools.html"]
|
|
48
|
+
]
|
|
49
|
+
]
|
|
50
|
+
].freeze
|
|
51
|
+
|
|
52
|
+
API_OVERVIEW = "file.api.html"
|
|
53
|
+
API_GROUPS = [
|
|
54
|
+
[
|
|
55
|
+
"Reference",
|
|
56
|
+
[
|
|
57
|
+
["Overview", API_OVERVIEW],
|
|
58
|
+
["Classes & Modules", "class_list.html"],
|
|
59
|
+
["Methods", "method_list.html"],
|
|
60
|
+
["Files", "file_list.html"]
|
|
61
|
+
]
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
"Key namespaces",
|
|
65
|
+
[
|
|
66
|
+
["Kward", "Kward.html"],
|
|
67
|
+
["Tools", "Kward/Tools.html"],
|
|
68
|
+
["RPC", "Kward/RPC.html"],
|
|
69
|
+
["CLI", "Kward/CLI.html"],
|
|
70
|
+
["Prompts", "Kward/Prompts.html"],
|
|
71
|
+
["Skills", "Kward/Skills.html"]
|
|
72
|
+
]
|
|
73
|
+
]
|
|
74
|
+
].freeze
|
|
75
|
+
|
|
76
|
+
def guide_groups
|
|
77
|
+
GUIDE_GROUPS
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def extension_groups
|
|
81
|
+
EXTENSION_GROUPS
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def api_groups
|
|
85
|
+
API_GROUPS
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def api_overview
|
|
89
|
+
API_OVERVIEW
|
|
90
|
+
end
|
|
91
|
+
end
|