kward 0.84.0 → 0.85.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 +34 -0
- data/Gemfile.lock +2 -2
- data/README.md +2 -2
- data/doc/agent-tools.md +13 -1
- data/doc/api.md +17 -2
- data/doc/composer.md +1 -1
- data/doc/configuration.md +27 -4
- data/doc/extensibility.md +2 -1
- data/doc/git.md +3 -1
- data/doc/pan.md +6 -0
- data/doc/permissions.md +4 -4
- data/doc/plugins.md +464 -15
- data/doc/rpc.md +154 -16
- data/doc/sandboxing.md +11 -5
- data/doc/security.md +7 -1
- data/doc/session-management.md +5 -4
- data/doc/tabs.md +6 -2
- data/doc/transports.md +15 -0
- data/doc/usage.md +4 -1
- data/doc/workspace-tools.md +9 -0
- data/examples/plugins/space_invaders.rb +1 -1
- data/examples/plugins/stardate_footer.rb +2 -2
- data/examples/plugins/telegram/plugin.rb +1 -1
- data/lib/kward/agent.rb +24 -11
- data/lib/kward/cli/compaction.rb +9 -3
- data/lib/kward/cli/plugins.rb +81 -12
- data/lib/kward/cli/prompt_interface.rb +25 -5
- data/lib/kward/cli/rendering.rb +3 -0
- data/lib/kward/cli/runtime_helpers.rb +121 -27
- data/lib/kward/cli/sessions.rb +9 -5
- data/lib/kward/cli/settings/menus.rb +745 -0
- data/lib/kward/cli/settings/model.rb +327 -0
- data/lib/kward/cli/settings.rb +6 -1055
- data/lib/kward/cli/slash_commands.rb +45 -4
- data/lib/kward/cli/tabs.rb +167 -27
- data/lib/kward/{cli_transcript_formatter.rb → cli/transcript_formatter.rb} +3 -3
- data/lib/kward/cli/worktrees.rb +65 -2
- data/lib/kward/cli.rb +26 -24
- data/lib/kward/compactor.rb +18 -7
- data/lib/kward/config/core.rb +389 -0
- data/lib/kward/config/extensions.rb +96 -0
- data/lib/kward/config/prompts.rb +313 -0
- data/lib/kward/config/settings.rb +250 -0
- data/lib/kward/config_files.rb +14 -1008
- data/lib/kward/conversation.rb +31 -2
- data/lib/kward/image_attachments.rb +1 -1
- data/lib/kward/model/client.rb +2 -2
- data/lib/kward/model/copilot_models.rb +2 -2
- data/lib/kward/model/model_info.rb +20 -3
- data/lib/kward/{openrouter_model_cache.rb → model/openrouter_model_cache.rb} +3 -3
- data/lib/kward/model/payloads.rb +12 -3
- data/lib/kward/model/typesafe_client.rb +78 -0
- data/lib/kward/pan/server.rb +10 -7
- data/lib/kward/permissions/policy.rb +6 -2
- data/lib/kward/plugin_registry.rb +2 -659
- data/lib/kward/plugins/actions.rb +453 -0
- data/lib/kward/plugins/chat_contract.rb +121 -0
- data/lib/kward/{plugin_chat_runtime.rb → plugins/chat_runtime.rb} +62 -18
- data/lib/kward/plugins/host.rb +232 -0
- data/lib/kward/plugins/registry.rb +1190 -0
- data/lib/kward/plugins/resources.rb +206 -0
- data/lib/kward/plugins/turn_request.rb +36 -0
- data/lib/kward/plugins/ui.rb +219 -0
- data/lib/kward/prompt_interface/composer_renderer.rb +1 -1
- data/lib/kward/prompt_interface/composer_state.rb +1 -1
- data/lib/kward/prompt_interface/editor/controller.rb +1 -1
- data/lib/kward/prompt_interface/editor/runner.rb +1 -1
- data/lib/kward/prompt_interface/editor/state.rb +1 -1
- data/lib/kward/prompt_interface/layout.rb +1 -1
- data/lib/kward/prompt_interface/overlay_renderer.rb +1 -1
- data/lib/kward/prompt_interface/plugin_ui_requests.rb +82 -0
- data/lib/kward/prompt_interface/runtime_state.rb +6 -1
- data/lib/kward/prompt_interface/screen.rb +9 -2
- data/lib/kward/prompt_interface.rb +60 -11
- data/lib/kward/prompts/commands.rb +2 -1
- data/lib/kward/{adaptive_pty_output_sink.rb → pty/adaptive_output_sink.rb} +1 -1
- data/lib/kward/{interactive_pty_runner.rb → pty/interactive_runner.rb} +2 -2
- data/lib/kward/{local_command_runner.rb → pty/local_command_runner.rb} +1 -1
- data/lib/kward/{local_pty_command_runner.rb → pty/local_pty_runner.rb} +3 -9
- data/lib/kward/{pty_output_sink.rb → pty/output_sink.rb} +9 -4
- data/lib/kward/{pty_transcript_normalizer.rb → pty/transcript_normalizer.rb} +1 -1
- data/lib/kward/rpc/plugin_chat_manager.rb +30 -10
- data/lib/kward/rpc/prompt_bridge.rb +25 -0
- data/lib/kward/rpc/server.rb +91 -12
- data/lib/kward/rpc/session_manager.rb +147 -44
- data/lib/kward/rpc/session_tree_rows.rb +2 -2
- data/lib/kward/rpc/tool_metadata.rb +1 -1
- data/lib/kward/sandbox/command_runner.rb +1 -1
- data/lib/kward/{session_catalog.rb → sessions/catalog.rb} +1 -1
- data/lib/kward/{session_store.rb → sessions/store.rb} +9 -9
- data/lib/kward/{session_tree_nodes.rb → sessions/tree_nodes.rb} +3 -3
- data/lib/kward/{session_tree_renderer.rb → sessions/tree_renderer.rb} +4 -4
- data/lib/kward/{session_tree_tool_display.rb → sessions/tree_tool_display.rb} +1 -1
- data/lib/kward/{kwsh.rb → shell/kwsh.rb} +3 -3
- data/lib/kward/{persistent_shell_session.rb → shell/persistent_session.rb} +3 -3
- data/lib/kward/{shell_prompt_session.rb → shell/prompt_session.rb} +1 -1
- data/lib/kward/skills/trust_store.rb +1 -1
- data/lib/kward/tabs/driver.rb +194 -0
- data/lib/kward/{tab_store.rb → tabs/store.rb} +2 -2
- data/lib/kward/{clipboard.rb → terminal/clipboard.rb} +1 -1
- data/lib/kward/{terminal_image_support.rb → terminal/image_support.rb} +1 -1
- data/lib/kward/tools/base.rb +18 -0
- data/lib/kward/tools/context_for_task.rb +15 -6
- data/lib/kward/tools/edit_file.rb +9 -6
- data/lib/kward/tools/git_commit.rb +13 -7
- data/lib/kward/tools/list_directory.rb +4 -4
- data/lib/kward/tools/plugin_tool.rb +41 -0
- data/lib/kward/tools/prepare_shell_command.rb +1 -1
- data/lib/kward/tools/read_file.rb +7 -6
- data/lib/kward/tools/registry.rb +96 -13
- data/lib/kward/tools/run_shell_command.rb +10 -8
- data/lib/kward/tools/search/code.rb +1 -1
- data/lib/kward/tools/summarize_file_structure.rb +5 -5
- data/lib/kward/tools/tool_call.rb +2 -1
- data/lib/kward/tools/typesafe_evaluate.rb +81 -0
- data/lib/kward/tools/workspace_targets.rb +58 -0
- data/lib/kward/tools/write_file.rb +9 -6
- data/lib/kward/{export_path.rb → transcripts/export_path.rb} +1 -1
- data/lib/kward/{markdown_transcript.rb → transcripts/markdown_transcript.rb} +2 -2
- data/lib/kward/transport/contracts.rb +200 -0
- data/lib/kward/transport/gateway.rb +79 -35
- data/lib/kward/transport/plugin_chat_gateway.rb +3 -2
- data/lib/kward/transport.rb +1 -200
- data/lib/kward/version.rb +1 -1
- data/lib/kward/{workspace_factory.rb → workspace/factory.rb} +2 -2
- data/lib/kward/{git_worktree_manager.rb → workspace/git_worktree_manager.rb} +28 -0
- data/lib/kward/{workspace.rb → workspace/workspace.rb} +3 -3
- data/templates/default/fulldoc/html/css/kward.css +125 -0
- data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
- data/templates/default/fulldoc/html/setup.rb +1 -1
- data/templates/default/layout/html/layout.erb +16 -4
- metadata +67 -48
- data/lib/kward/tab_driver.rb +0 -90
- data/templates/default/fulldoc/html/images/kward_workflow.svg +0 -52
- /data/lib/kward/{editor_prompt.rb → cli/editor_prompt.rb} +0 -0
- /data/lib/kward/{editor_prompt_session.rb → cli/editor_prompt_session.rb} +0 -0
- /data/lib/kward/{diff_view_mode.rb → prompt_interface/editor/diff_view_mode.rb} +0 -0
- /data/lib/kward/{editor_mode.rb → prompt_interface/editor/editor_mode.rb} +0 -0
- /data/lib/kward/{markdown_code_block.rb → prompt_interface/editor/markdown_code_block.rb} +0 -0
- /data/lib/kward/{scratchpad_languages.rb → prompt_interface/editor/scratchpad_languages.rb} +0 -0
- /data/lib/kward/{scratchpad_runner.rb → prompt_interface/editor/scratchpad_runner.rb} +0 -0
- /data/lib/kward/{detached_run.rb → pty/detached_run.rb} +0 -0
- /data/lib/kward/{session_diff.rb → sessions/diff.rb} +0 -0
- /data/lib/kward/{session_naming.rb → sessions/naming.rb} +0 -0
- /data/lib/kward/{session_trash.rb → sessions/trash.rb} +0 -0
- /data/lib/kward/{kwshrc.rb → shell/kwshrc.rb} +0 -0
- /data/lib/kward/{shell_prompt.rb → shell/prompt.rb} +0 -0
- /data/lib/kward/{ansi.rb → terminal/ansi.rb} +0 -0
- /data/lib/kward/{terminal_keys.rb → terminal/keys.rb} +0 -0
- /data/lib/kward/{terminal_sequences.rb → terminal/sequences.rb} +0 -0
- /data/lib/kward/{terminal_text.rb → terminal/text.rb} +0 -0
- /data/lib/kward/{transcript_export.rb → transcripts/transcript_export.rb} +0 -0
- /data/lib/kward/{project_files.rb → workspace/files.rb} +0 -0
- /data/lib/kward/{path_guard.rb → workspace/path_guard.rb} +0 -0
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
# CLI settings menus behavior.
|
|
2
|
+
module Kward
|
|
3
|
+
class CLI
|
|
4
|
+
module Settings
|
|
5
|
+
module Menus
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def configure_settings(conversation = nil)
|
|
10
|
+
unless settings_overlay_available?
|
|
11
|
+
runtime_output("Settings overlay is unavailable in this prompt.")
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
initial_index = 0
|
|
16
|
+
loop do
|
|
17
|
+
choices = settings_category_choices
|
|
18
|
+
selected, selected_index = select_settings_menu_item("Settings category", choices, initial_index)
|
|
19
|
+
category = selected_settings_category(selected)
|
|
20
|
+
break unless category
|
|
21
|
+
|
|
22
|
+
initial_index = selected_index if selected_index
|
|
23
|
+
break if category == "done"
|
|
24
|
+
|
|
25
|
+
handle_settings_category(category, conversation)
|
|
26
|
+
end
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
runtime_output("Settings error: #{e.message}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns the category labels shown by the interactive settings overlay.
|
|
32
|
+
def settings_category_choices
|
|
33
|
+
[
|
|
34
|
+
"Model & Reasoning",
|
|
35
|
+
"Accounts",
|
|
36
|
+
"Memory",
|
|
37
|
+
"Interface",
|
|
38
|
+
"Tools & Search",
|
|
39
|
+
"Context & Compaction",
|
|
40
|
+
"Personalization",
|
|
41
|
+
"Logging",
|
|
42
|
+
"Advanced",
|
|
43
|
+
"Done"
|
|
44
|
+
]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Maps a selected settings label back to the internal category key.
|
|
48
|
+
def selected_settings_category(selected)
|
|
49
|
+
text = selected.to_s.downcase
|
|
50
|
+
return nil if text.empty?
|
|
51
|
+
return "done" if text.start_with?("done")
|
|
52
|
+
return "model" if text.start_with?("model")
|
|
53
|
+
return "accounts" if text.start_with?("accounts")
|
|
54
|
+
return "memory" if text.start_with?("memory")
|
|
55
|
+
return "interface" if text.start_with?("interface")
|
|
56
|
+
return "tools" if text.start_with?("tools")
|
|
57
|
+
return "context" if text.start_with?("context")
|
|
58
|
+
return "personalization" if text.start_with?("personalization")
|
|
59
|
+
return "logging" if text.start_with?("logging")
|
|
60
|
+
return "advanced" if text.start_with?("advanced")
|
|
61
|
+
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def handle_settings_category(category, conversation)
|
|
66
|
+
case category
|
|
67
|
+
when "model"
|
|
68
|
+
configure_model_settings(conversation)
|
|
69
|
+
when "accounts"
|
|
70
|
+
configure_account_settings
|
|
71
|
+
when "memory"
|
|
72
|
+
configure_memory_settings(conversation)
|
|
73
|
+
when "interface"
|
|
74
|
+
configure_interface_settings
|
|
75
|
+
when "tools"
|
|
76
|
+
configure_tools_settings
|
|
77
|
+
when "context"
|
|
78
|
+
configure_context_settings
|
|
79
|
+
when "personalization"
|
|
80
|
+
configure_personalization_settings(conversation)
|
|
81
|
+
when "logging"
|
|
82
|
+
configure_logging_settings
|
|
83
|
+
when "advanced"
|
|
84
|
+
show_advanced_settings
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def configure_model_settings(conversation)
|
|
89
|
+
initial_index = 0
|
|
90
|
+
loop do
|
|
91
|
+
selected, selected_index = select_settings_menu_item("Model & Reasoning", ["Provider", "Default model", "Local server preset", "Reasoning effort", "Back"], initial_index)
|
|
92
|
+
break unless selected
|
|
93
|
+
|
|
94
|
+
initial_index = selected_index if selected_index
|
|
95
|
+
case selected.to_s.downcase
|
|
96
|
+
when /\Aprovider/
|
|
97
|
+
configure_provider(conversation)
|
|
98
|
+
when /\Adefault model/
|
|
99
|
+
configure_model(conversation)
|
|
100
|
+
when /\Alocal server preset/
|
|
101
|
+
configure_local_server_preset(conversation)
|
|
102
|
+
when /\Areasoning effort/
|
|
103
|
+
configure_reasoning(conversation)
|
|
104
|
+
else
|
|
105
|
+
break
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def configure_provider(conversation)
|
|
111
|
+
selected = @prompt.select("Provider", provider_choices, title: "Settings")
|
|
112
|
+
provider = selected_provider(selected)
|
|
113
|
+
return unless provider
|
|
114
|
+
|
|
115
|
+
ConfigFiles.update_config("provider" => ModelInfo.config_provider_for_provider(provider))
|
|
116
|
+
reload_client_config
|
|
117
|
+
refresh_conversation_runtime(conversation)
|
|
118
|
+
refresh_composer_status
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def provider_choices
|
|
122
|
+
current = current_model_provider
|
|
123
|
+
configured_api_key_providers = ProviderCatalog.api_key_providers.filter_map do |provider|
|
|
124
|
+
provider.name if api_key_store.configured?(provider.id)
|
|
125
|
+
end
|
|
126
|
+
(["Codex", "Anthropic", "OpenRouter", "Copilot", "Local"] + configured_api_key_providers).uniq.map do |provider|
|
|
127
|
+
label = provider.dup
|
|
128
|
+
label += " (current)" if provider == current
|
|
129
|
+
label
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def selected_provider(selected)
|
|
134
|
+
text = selected.to_s.downcase
|
|
135
|
+
return "Codex" if text.start_with?("codex")
|
|
136
|
+
return "Anthropic" if text.start_with?("anthropic") || text.start_with?("claude")
|
|
137
|
+
return "OpenRouter" if text.start_with?("openrouter")
|
|
138
|
+
return "Copilot" if text.start_with?("copilot")
|
|
139
|
+
return "Local" if text.start_with?("local")
|
|
140
|
+
|
|
141
|
+
ProviderCatalog.find_by_name(selected)&.name
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def configure_local_server_preset(conversation)
|
|
145
|
+
choices = [
|
|
146
|
+
"Ollama (http://127.0.0.1:11434/v1)",
|
|
147
|
+
"LM Studio (http://127.0.0.1:1234/v1)",
|
|
148
|
+
"llama.cpp (http://127.0.0.1:8080/v1)",
|
|
149
|
+
"Custom endpoint (edit config)",
|
|
150
|
+
"Back"
|
|
151
|
+
]
|
|
152
|
+
selected = @prompt.select("Local server preset", choices, title: "Settings")
|
|
153
|
+
return unless selected
|
|
154
|
+
|
|
155
|
+
backend, url = case selected.to_s
|
|
156
|
+
when /\AOllama/ then ["ollama", "http://127.0.0.1:11434/v1"]
|
|
157
|
+
when /\ALM Studio/ then ["lm_studio", "http://127.0.0.1:1234/v1"]
|
|
158
|
+
when /\Allama\.cpp/ then ["llama_cpp", "http://127.0.0.1:8080/v1"]
|
|
159
|
+
when /\ACustom endpoint/
|
|
160
|
+
runtime_output("Set local_base_url in #{ConfigFiles.config_path}.")
|
|
161
|
+
return
|
|
162
|
+
else
|
|
163
|
+
return
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
ConfigFiles.update_config("local_backend" => backend, "local_base_url" => url)
|
|
167
|
+
reload_client_config
|
|
168
|
+
refresh_conversation_runtime(conversation)
|
|
169
|
+
refresh_composer_status
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def configure_account_settings
|
|
173
|
+
initial_index = 0
|
|
174
|
+
loop do
|
|
175
|
+
selected, selected_index = select_settings_menu_item("Accounts", account_setting_choices, initial_index)
|
|
176
|
+
break unless selected
|
|
177
|
+
|
|
178
|
+
initial_index = selected_index if selected_index
|
|
179
|
+
case selected.to_s.downcase
|
|
180
|
+
when /\Aopenai/
|
|
181
|
+
login(provider: "openai")
|
|
182
|
+
reload_client_config
|
|
183
|
+
when /\Aanthropic/, /\Aclaude/
|
|
184
|
+
login(provider: "anthropic")
|
|
185
|
+
reload_client_config
|
|
186
|
+
when /\Agithub/
|
|
187
|
+
login(provider: "github")
|
|
188
|
+
reload_client_config
|
|
189
|
+
when /\Aopenrouter/
|
|
190
|
+
login(provider: "openrouter")
|
|
191
|
+
reload_client_config
|
|
192
|
+
when /\Astatus/
|
|
193
|
+
print_auth_status
|
|
194
|
+
else
|
|
195
|
+
break
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def account_setting_choices
|
|
201
|
+
config = safely_read_config.to_h
|
|
202
|
+
[
|
|
203
|
+
"OpenAI login (#{File.exist?(OpenAIOAuth.default_auth_path) ? "configured" : "not configured"})",
|
|
204
|
+
"Anthropic login (#{File.exist?(AnthropicOAuth.default_auth_path) ? "configured" : "not configured"})",
|
|
205
|
+
"GitHub login (#{File.exist?(GithubOAuth.default_auth_path) ? "configured" : "not configured"})",
|
|
206
|
+
"OpenRouter API key (#{openrouter_key_status(config)})",
|
|
207
|
+
"Status",
|
|
208
|
+
"Back"
|
|
209
|
+
]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def openrouter_key_status(config)
|
|
213
|
+
return "configured via environment" unless ENV["OPENROUTER_API_KEY"].to_s.empty?
|
|
214
|
+
|
|
215
|
+
config["openrouter_api_key"].to_s.empty? ? "not configured" : "configured"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def configure_memory_settings(conversation)
|
|
219
|
+
initial_index = 0
|
|
220
|
+
loop do
|
|
221
|
+
selected, selected_index = select_settings_menu_item("Memory", memory_setting_choices, initial_index)
|
|
222
|
+
break unless selected
|
|
223
|
+
|
|
224
|
+
initial_index = selected_index if selected_index
|
|
225
|
+
case selected.to_s.downcase
|
|
226
|
+
when /\Aenable memory/, /\Adisable memory/
|
|
227
|
+
set_config_flag("memory", "enabled", !memory_enabled?)
|
|
228
|
+
conversation&.refresh_system_message!
|
|
229
|
+
runtime_output("Memory #{memory_enabled? ? "enabled" : "disabled"}.")
|
|
230
|
+
when /\Aenable auto-summary/, /\Adisable auto-summary/
|
|
231
|
+
set_config_flag("memory", "auto_summary", !memory_auto_summary_enabled?)
|
|
232
|
+
runtime_output("Memory auto-summary #{memory_auto_summary_enabled? ? "enabled" : "disabled"}.")
|
|
233
|
+
when /\Amanage/
|
|
234
|
+
runtime_output("Use /memory enable|disable|auto-summary enable|disable|core <text>|add <text>|list|forget <id>|promote <id>|relax <id>|inspect|why|summarize.")
|
|
235
|
+
else
|
|
236
|
+
break
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def memory_setting_choices
|
|
242
|
+
[
|
|
243
|
+
"#{memory_enabled? ? "Disable" : "Enable"} memory (currently #{on_off(memory_enabled?)})",
|
|
244
|
+
"#{memory_auto_summary_enabled? ? "Disable" : "Enable"} auto-summary (currently #{on_off(memory_auto_summary_enabled?)})",
|
|
245
|
+
"Manage memories with /memory",
|
|
246
|
+
"Back"
|
|
247
|
+
]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def memory_enabled?
|
|
251
|
+
memory = safely_read_config.to_h["memory"]
|
|
252
|
+
memory.is_a?(Hash) && memory["enabled"] == true
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def memory_auto_summary_enabled?
|
|
256
|
+
memory = safely_read_config.to_h["memory"]
|
|
257
|
+
memory.is_a?(Hash) && memory["auto_summary"] == true
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def configure_interface_settings
|
|
261
|
+
initial_index = 0
|
|
262
|
+
loop do
|
|
263
|
+
selected, selected_index = select_settings_menu_item("Interface", interface_setting_choices, initial_index)
|
|
264
|
+
break unless selected
|
|
265
|
+
|
|
266
|
+
initial_index = selected_index if selected_index
|
|
267
|
+
case selected.to_s.downcase
|
|
268
|
+
when /\Aoverlay alignment/
|
|
269
|
+
settings = ConfigFiles.overlay_settings
|
|
270
|
+
alignment = choose_overlay_setting("Overlay alignment", overlay_alignment_choices(settings), ConfigFiles::OVERLAY_ALIGNMENTS)
|
|
271
|
+
next unless alignment
|
|
272
|
+
|
|
273
|
+
update_overlay_settings("alignment" => alignment)
|
|
274
|
+
when /\Aoverlay width/
|
|
275
|
+
settings = ConfigFiles.overlay_settings
|
|
276
|
+
width = choose_overlay_setting("Overlay width", overlay_width_choices(settings), ConfigFiles::OVERLAY_WIDTHS)
|
|
277
|
+
next unless width
|
|
278
|
+
|
|
279
|
+
update_overlay_settings("width" => width)
|
|
280
|
+
when /\Afile icons/
|
|
281
|
+
configure_project_browser_icon_theme
|
|
282
|
+
when /\Ashow busy help/, /\Ahide busy help/
|
|
283
|
+
set_composer_busy_help(!composer_busy_help?)
|
|
284
|
+
runtime_output("Busy help #{composer_busy_help? ? "enabled" : "disabled"}. Restart the TUI to apply this setting.")
|
|
285
|
+
when /\Atab keybindings/
|
|
286
|
+
configure_tab_keybindings
|
|
287
|
+
when /\Aeditor mode/
|
|
288
|
+
configure_editor_mode
|
|
289
|
+
when /\Aeditor line numbers/
|
|
290
|
+
configure_editor_line_numbers
|
|
291
|
+
when /\Adiff view/
|
|
292
|
+
configure_diff_view
|
|
293
|
+
when /\Aenable auto-close pairs/, /\Adisable auto-close pairs/
|
|
294
|
+
set_config_flag("editor", "auto_close_pairs", !editor_auto_close_pairs_enabled?)
|
|
295
|
+
runtime_output("Editor auto-close pairs #{editor_auto_close_pairs_enabled? ? "enabled" : "disabled"}.")
|
|
296
|
+
when /\Aenable soft-wrap/, /\Adisable soft-wrap/
|
|
297
|
+
set_config_flag("editor", "soft_wrap", !editor_soft_wrap_enabled?)
|
|
298
|
+
runtime_output("Editor soft-wrap #{editor_soft_wrap_enabled? ? "enabled" : "disabled"}.")
|
|
299
|
+
when /\Aenable bar cursor/, /\Adisable bar cursor/
|
|
300
|
+
set_config_flag("editor", "bar_cursor", !editor_bar_cursor_enabled?)
|
|
301
|
+
runtime_output("Editor bar cursor #{editor_bar_cursor_enabled? ? "enabled" : "disabled"}.")
|
|
302
|
+
when /\Aenable session auto-resume/, /\Adisable session auto-resume/
|
|
303
|
+
set_config_flag("sessions", "auto_resume", !session_auto_resume_enabled?)
|
|
304
|
+
runtime_output("Session auto-resume #{session_auto_resume_enabled? ? "enabled" : "disabled"}.")
|
|
305
|
+
else
|
|
306
|
+
break
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def interface_setting_choices
|
|
312
|
+
settings = ConfigFiles.overlay_settings
|
|
313
|
+
[
|
|
314
|
+
"Overlay alignment (#{settings["alignment"]})",
|
|
315
|
+
"Overlay width (#{settings["width"]})",
|
|
316
|
+
"File icons (#{project_browser_icon_theme})",
|
|
317
|
+
"#{composer_busy_help? ? "Hide" : "Show"} busy help (currently #{on_off(composer_busy_help?)})",
|
|
318
|
+
"Tab keybindings (#{composer_tab_keybindings})",
|
|
319
|
+
"Editor mode (#{editor_mode})",
|
|
320
|
+
"Editor line numbers (#{editor_line_numbers})",
|
|
321
|
+
"Diff view (#{diff_view_label(diff_view)})",
|
|
322
|
+
"#{editor_auto_close_pairs_enabled? ? "Disable" : "Enable"} auto-close pairs (currently #{on_off(editor_auto_close_pairs_enabled?)})",
|
|
323
|
+
"#{editor_soft_wrap_enabled? ? "Disable" : "Enable"} soft-wrap (currently #{on_off(editor_soft_wrap_enabled?)})",
|
|
324
|
+
"#{editor_bar_cursor_enabled? ? "Disable" : "Enable"} bar cursor (currently #{on_off(editor_bar_cursor_enabled?)})",
|
|
325
|
+
"#{session_auto_resume_enabled? ? "Disable" : "Enable"} session auto-resume (currently #{on_off(session_auto_resume_enabled?)})",
|
|
326
|
+
"Back"
|
|
327
|
+
]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def project_browser_icon_theme
|
|
331
|
+
ConfigFiles.project_browser_icon_theme(safely_read_config.to_h)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def configure_project_browser_icon_theme
|
|
335
|
+
selected = @prompt.select("File icons", project_browser_icon_theme_choices, title: "Settings")
|
|
336
|
+
theme = selected.to_s.split.first.to_s.downcase
|
|
337
|
+
return unless ConfigFiles::PROJECT_BROWSER_ICON_THEMES.include?(theme)
|
|
338
|
+
|
|
339
|
+
update_nested_config("project_browser", "icons" => theme)
|
|
340
|
+
@prompt.update_project_browser_icon_theme(theme) if @prompt.respond_to?(:update_project_browser_icon_theme)
|
|
341
|
+
runtime_output("File icons set to #{theme}.")
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def project_browser_icon_theme_choices
|
|
345
|
+
current = project_browser_icon_theme
|
|
346
|
+
ConfigFiles::PROJECT_BROWSER_ICON_THEMES.map { |theme| theme == current ? "#{theme} (current)" : theme }
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def composer_busy_help?
|
|
350
|
+
ConfigFiles.composer_busy_help?(safely_read_config.to_h)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def composer_tab_keybindings
|
|
354
|
+
ConfigFiles.composer_tab_keybindings(safely_read_config.to_h)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def configure_tab_keybindings
|
|
358
|
+
selected = @prompt.select("Tab keybindings", tab_keybinding_choices, title: "Settings")
|
|
359
|
+
value = selected.to_s.split.first.to_s.downcase
|
|
360
|
+
return unless %w[auto ctrl alt].include?(value)
|
|
361
|
+
|
|
362
|
+
update_nested_config("composer", "tab_keybindings" => value)
|
|
363
|
+
runtime_output("Tab keybindings set to #{value}. Restart the TUI to apply this setting.")
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def tab_keybinding_choices
|
|
367
|
+
current = composer_tab_keybindings
|
|
368
|
+
%w[auto ctrl alt].map { |value| value == current ? "#{value} (current)" : value }
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def editor_mode
|
|
372
|
+
ConfigFiles.editor_mode(safely_read_config.to_h)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def configure_editor_mode
|
|
376
|
+
selected = @prompt.select("Editor mode", editor_mode_choices, title: "Settings")
|
|
377
|
+
value = selected.to_s.split.first.to_s.downcase
|
|
378
|
+
return unless %w[modern emacs vibe].include?(value)
|
|
379
|
+
|
|
380
|
+
update_nested_config("editor", "mode" => value)
|
|
381
|
+
runtime_output("Editor mode set to #{value}. New editor buffers will use this mode.")
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def editor_mode_choices
|
|
385
|
+
current = editor_mode
|
|
386
|
+
%w[modern emacs vibe].map { |value| value == current ? "#{value} (current)" : value }
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def editor_line_numbers
|
|
390
|
+
ConfigFiles.editor_line_numbers(safely_read_config.to_h)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def configure_editor_line_numbers
|
|
394
|
+
selected = @prompt.select("Editor line numbers", editor_line_number_choices, title: "Settings")
|
|
395
|
+
value = selected.to_s.split.first.to_s.downcase
|
|
396
|
+
return unless %w[absolute relative].include?(value)
|
|
397
|
+
|
|
398
|
+
update_nested_config("editor", "line_numbers" => value)
|
|
399
|
+
runtime_output("Editor line numbers set to #{value}.")
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def editor_line_number_choices
|
|
403
|
+
current = editor_line_numbers
|
|
404
|
+
%w[absolute relative].map { |value| value == current ? "#{value} (current)" : value }
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def diff_view
|
|
408
|
+
ConfigFiles.diff_view(safely_read_config.to_h)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def configure_diff_view
|
|
412
|
+
selected = @prompt.select("Diff view", diff_view_choices, title: "Settings")
|
|
413
|
+
value = selected.to_s.split.first.to_s.downcase.tr("-", "_")
|
|
414
|
+
return unless Kward::DiffViewMode::MODES.include?(value)
|
|
415
|
+
|
|
416
|
+
update_nested_config("editor", "diff_view" => value)
|
|
417
|
+
runtime_output("Diff view set to #{diff_view_label(value)}.")
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def diff_view_choices
|
|
421
|
+
current = diff_view
|
|
422
|
+
Kward::DiffViewMode::MODES.map do |value|
|
|
423
|
+
label = diff_view_label(value)
|
|
424
|
+
value == current ? "#{label} (current)" : label
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def diff_view_label(value)
|
|
429
|
+
Kward::DiffViewMode.label(value)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def editor_auto_close_pairs_enabled?
|
|
433
|
+
ConfigFiles.editor_auto_close_pairs?(safely_read_config.to_h)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def editor_soft_wrap_enabled?
|
|
437
|
+
ConfigFiles.editor_soft_wrap?(safely_read_config.to_h)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def editor_bar_cursor_enabled?
|
|
441
|
+
ConfigFiles.editor_bar_cursor?(safely_read_config.to_h)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def session_auto_resume_enabled?
|
|
445
|
+
ConfigFiles.session_auto_resume_enabled?(safely_read_config.to_h)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def set_composer_busy_help(enabled)
|
|
449
|
+
set_config_flag("composer", "busy_help", enabled)
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def configure_tools_settings
|
|
453
|
+
initial_index = 0
|
|
454
|
+
loop do
|
|
455
|
+
selected, selected_index = select_settings_menu_item("Tools & Search", tools_setting_choices, initial_index)
|
|
456
|
+
break unless selected
|
|
457
|
+
|
|
458
|
+
initial_index = selected_index if selected_index
|
|
459
|
+
case selected.to_s.downcase
|
|
460
|
+
when /\Aenable web search/, /\Adisable web search/
|
|
461
|
+
set_config_flag("web_search", "enabled", !web_search_enabled?)
|
|
462
|
+
runtime_output("Web search #{web_search_enabled? ? "enabled" : "disabled"}.")
|
|
463
|
+
when /\Aweb search provider/
|
|
464
|
+
configure_web_search_provider
|
|
465
|
+
when /\Aallow model-provider/, /\Adisallow model-provider/
|
|
466
|
+
set_config_flag("web_search", "allow_model_providers", !web_search_allow_model_providers?)
|
|
467
|
+
runtime_output("Model-provider web search #{web_search_allow_model_providers? ? "enabled" : "disabled"}.")
|
|
468
|
+
when /\Atrust project skills/, /\Auntrust project skills/
|
|
469
|
+
set_config_flag("skills", "trust_project", !project_skills_trusted?)
|
|
470
|
+
runtime_output("Project skills #{project_skills_trusted? ? "trusted" : "untrusted"}.")
|
|
471
|
+
when /\Aenable workspace guardrails/, /\Adisable workspace guardrails/
|
|
472
|
+
set_config_flag("tools", "workspace_guardrails", !workspace_guardrails_enabled?)
|
|
473
|
+
runtime_output("Workspace guardrails #{workspace_guardrails_enabled? ? "enabled" : "disabled"}.")
|
|
474
|
+
else
|
|
475
|
+
break
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def tools_setting_choices
|
|
481
|
+
[
|
|
482
|
+
"#{web_search_enabled? ? "Disable" : "Enable"} web search (currently #{on_off(web_search_enabled?)})",
|
|
483
|
+
"Web search provider (#{web_search_provider})",
|
|
484
|
+
"#{web_search_allow_model_providers? ? "Disallow" : "Allow"} model-provider web search (currently #{on_off(web_search_allow_model_providers?)})",
|
|
485
|
+
"#{project_skills_trusted? ? "Untrust" : "Trust"} project skills (currently #{on_off(project_skills_trusted?)})",
|
|
486
|
+
"#{workspace_guardrails_enabled? ? "Disable" : "Enable"} workspace guardrails (currently #{on_off(workspace_guardrails_enabled?)})",
|
|
487
|
+
"Back"
|
|
488
|
+
]
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def configure_web_search_provider
|
|
492
|
+
providers = WebSearch::PROVIDERS
|
|
493
|
+
selected = @prompt.select("Web search provider", providers.map { |provider| provider == web_search_provider ? "#{provider} (current)" : provider }, title: "Settings")
|
|
494
|
+
provider = providers.find { |value| selected.to_s.downcase.start_with?(value) }
|
|
495
|
+
return unless provider
|
|
496
|
+
|
|
497
|
+
update_nested_config("web_search", "provider" => provider)
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def web_search_config
|
|
501
|
+
ConfigFiles.web_search_config(safely_read_config.to_h)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def web_search_enabled?
|
|
505
|
+
web_search_config["enabled"] != false
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def web_search_provider
|
|
509
|
+
web_search_config["provider"].to_s.empty? ? "auto" : web_search_config["provider"].to_s
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def web_search_allow_model_providers?
|
|
513
|
+
web_search_config["allow_model_providers"] == true
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def project_skills_trusted?
|
|
517
|
+
ConfigFiles.project_skills_trusted?(safely_read_config.to_h)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def configure_context_settings
|
|
521
|
+
initial_index = 0
|
|
522
|
+
loop do
|
|
523
|
+
selected, selected_index = select_settings_menu_item("Context & Compaction", context_setting_choices, initial_index)
|
|
524
|
+
break unless selected
|
|
525
|
+
|
|
526
|
+
initial_index = selected_index if selected_index
|
|
527
|
+
case selected.to_s.downcase
|
|
528
|
+
when /\Aenable auto-compaction/, /\Adisable auto-compaction/
|
|
529
|
+
set_config_flag("compaction", "enabled", !compaction_enabled?)
|
|
530
|
+
runtime_output("Auto-compaction #{compaction_enabled? ? "enabled" : "disabled"}.")
|
|
531
|
+
when /\Astatus/
|
|
532
|
+
runtime_output(auto_compaction_status_line)
|
|
533
|
+
else
|
|
534
|
+
break
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def context_setting_choices
|
|
540
|
+
[
|
|
541
|
+
"#{compaction_enabled? ? "Disable" : "Enable"} auto-compaction (currently #{on_off(compaction_enabled?)})",
|
|
542
|
+
"Status",
|
|
543
|
+
"Back"
|
|
544
|
+
]
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def compaction_enabled?
|
|
548
|
+
Kward::Compaction::Settings.from_config(safely_read_config.to_h).enabled
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def configure_personalization_settings(conversation)
|
|
552
|
+
initial_index = 0
|
|
553
|
+
loop do
|
|
554
|
+
selected, selected_index = select_settings_menu_item("Personalization", personalization_setting_choices(conversation), initial_index)
|
|
555
|
+
break unless selected
|
|
556
|
+
|
|
557
|
+
initial_index = selected_index if selected_index
|
|
558
|
+
case selected.to_s.downcase
|
|
559
|
+
when /\Adefault persona/
|
|
560
|
+
configure_default_persona(conversation)
|
|
561
|
+
when /\Aenable global principles/, /\Adisable global principles/
|
|
562
|
+
toggle_global_principles(conversation)
|
|
563
|
+
when /\Aglobal principles \(ignored/
|
|
564
|
+
runtime_output("Global principles are ignored while a replacement system prompt is configured.")
|
|
565
|
+
when /\Aactive instructions/
|
|
566
|
+
show_active_instructions_summary(conversation)
|
|
567
|
+
else
|
|
568
|
+
break
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def personalization_setting_choices(conversation)
|
|
574
|
+
config = safely_read_config.to_h
|
|
575
|
+
replacement = ConfigFiles.system_prompt_file_path(config)
|
|
576
|
+
principles = if replacement
|
|
577
|
+
"Global principles (ignored by replacement)"
|
|
578
|
+
elsif ConfigFiles.include_config_principles?(config)
|
|
579
|
+
"Disable global principles"
|
|
580
|
+
else
|
|
581
|
+
"Enable global principles"
|
|
582
|
+
end
|
|
583
|
+
[
|
|
584
|
+
"Default persona (#{default_persona_label})",
|
|
585
|
+
principles,
|
|
586
|
+
"Active instructions summary",
|
|
587
|
+
"Back"
|
|
588
|
+
]
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def toggle_global_principles(conversation)
|
|
592
|
+
config = safely_read_config.to_h
|
|
593
|
+
settings = config["system_prompt"].is_a?(Hash) ? config["system_prompt"] : {}
|
|
594
|
+
ConfigFiles.update_config("system_prompt" => settings.merge("include_principles" => !ConfigFiles.include_config_principles?(config)))
|
|
595
|
+
conversation&.refresh_system_message!
|
|
596
|
+
@prompt.redraw if @prompt.respond_to?(:redraw)
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def default_persona_label
|
|
600
|
+
personas = safely_read_config.to_h["personas"]
|
|
601
|
+
value = personas.is_a?(Hash) ? personas["default"] : nil
|
|
602
|
+
value.to_s.empty? ? "none" : value.to_s
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
def configure_default_persona(conversation)
|
|
606
|
+
config = safely_read_config.to_h
|
|
607
|
+
personas = config["personas"].is_a?(Hash) ? config["personas"] : {}
|
|
608
|
+
entries = ConfigFiles.crew_character_labels(personas)
|
|
609
|
+
choices = entries.map { |key, label| key == personas["default"] ? "#{label} (#{key}, current)" : "#{label} (#{key})" }
|
|
610
|
+
if choices.empty?
|
|
611
|
+
runtime_output("No configured personas found. Edit #{ConfigFiles.config_path} to add personas.")
|
|
612
|
+
return
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
selected = @prompt.select("Default persona", choices, title: "Settings")
|
|
616
|
+
key = entries.keys.find { |candidate| selected.to_s.include?("(#{candidate}") }
|
|
617
|
+
return unless key
|
|
618
|
+
|
|
619
|
+
personas = personas.dup
|
|
620
|
+
personas["default"] = key
|
|
621
|
+
ConfigFiles.update_config("personas" => personas)
|
|
622
|
+
conversation&.refresh_system_message!
|
|
623
|
+
@prompt.redraw if @prompt.respond_to?(:redraw)
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def show_active_instructions_summary(conversation)
|
|
627
|
+
label = ConfigFiles.active_persona_label(workspace_root: current_workspace_root, model: current_model_id, config: safely_read_config.to_h)
|
|
628
|
+
config = safely_read_config.to_h
|
|
629
|
+
replacement_path = ConfigFiles.system_prompt_file_path(config)
|
|
630
|
+
lines = ["Active persona: #{label || "none"}"]
|
|
631
|
+
if replacement_path
|
|
632
|
+
lines << "System prompt: replacement (#{replacement_path})"
|
|
633
|
+
lines << "Global PRINCIPLES.md: ignored by replacement"
|
|
634
|
+
lines << "Workspace AGENTS.md: ignored by replacement"
|
|
635
|
+
else
|
|
636
|
+
lines << "System prompt: Kward default"
|
|
637
|
+
lines << "Global PRINCIPLES.md: #{ConfigFiles.include_config_principles?(config) ? (ConfigFiles.agents_prompt ? "present" : "absent") : "disabled"}"
|
|
638
|
+
lines << "Workspace AGENTS.md: #{ConfigFiles.workspace_agents_prompt(current_workspace_root) ? "present" : "absent"}"
|
|
639
|
+
end
|
|
640
|
+
lines << "Messages: #{conversation.messages.length}" if conversation&.respond_to?(:messages)
|
|
641
|
+
runtime_output(lines.join("\n"))
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
def configure_logging_settings
|
|
645
|
+
initial_index = 0
|
|
646
|
+
loop do
|
|
647
|
+
selected, selected_index = select_settings_menu_item("Logging", logging_setting_choices, initial_index)
|
|
648
|
+
break unless selected
|
|
649
|
+
|
|
650
|
+
initial_index = selected_index if selected_index
|
|
651
|
+
key = logging_key_for_choice(selected)
|
|
652
|
+
break unless key
|
|
653
|
+
|
|
654
|
+
set_logging_value(key, !logging_enabled?(key))
|
|
655
|
+
runtime_output("Logging #{key.tr("_", " ")} #{logging_enabled?(key) ? "enabled" : "disabled"}.")
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def logging_setting_choices
|
|
660
|
+
[
|
|
661
|
+
"#{logging_enabled?("enabled") ? "Disable" : "Enable"} local logging (currently #{on_off(logging_enabled?("enabled"))})",
|
|
662
|
+
"#{logging_enabled?("tokens") ? "Disable" : "Enable"} token logs (currently #{on_off(logging_enabled?("tokens"))})",
|
|
663
|
+
"#{logging_enabled?("performance") ? "Disable" : "Enable"} performance logs (currently #{on_off(logging_enabled?("performance"))})",
|
|
664
|
+
"#{logging_enabled?("tools") ? "Disable" : "Enable"} tool logs (currently #{on_off(logging_enabled?("tools"))})",
|
|
665
|
+
"#{logging_enabled?("errors") ? "Disable" : "Enable"} error logs (currently #{on_off(logging_enabled?("errors"))})",
|
|
666
|
+
"Back"
|
|
667
|
+
]
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
def logging_key_for_choice(selected)
|
|
671
|
+
text = selected.to_s.downcase
|
|
672
|
+
return "enabled" if text.include?("local logging")
|
|
673
|
+
return "tokens" if text.include?("token logs")
|
|
674
|
+
return "performance" if text.include?("performance logs")
|
|
675
|
+
return "tools" if text.include?("tool logs")
|
|
676
|
+
return "errors" if text.include?("error logs")
|
|
677
|
+
|
|
678
|
+
nil
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def logging_enabled?(key)
|
|
682
|
+
logging = safely_read_config.to_h["logging"]
|
|
683
|
+
logging.is_a?(Hash) && logging[key] == true
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def set_logging_value(key, value)
|
|
687
|
+
set_config_flag("logging", key, value)
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def show_advanced_settings
|
|
691
|
+
lines = [
|
|
692
|
+
"Config path: #{ConfigFiles.config_path}",
|
|
693
|
+
"Config directory: #{ConfigFiles.config_dir}",
|
|
694
|
+
"Cache directory: #{ConfigFiles.cache_dir}",
|
|
695
|
+
"Memory directory: #{ConfigFiles.memory_dir}",
|
|
696
|
+
"Plugin directory: #{ConfigFiles.plugin_dir}",
|
|
697
|
+
"Plugins: #{ConfigFiles.plugin_paths.length}",
|
|
698
|
+
"Skills: #{ConfigFiles.skills.length}",
|
|
699
|
+
"Prompt templates: #{ConfigFiles.prompt_templates(reserved_commands: BUILTIN_SLASH_COMMAND_NAMES).length}"
|
|
700
|
+
]
|
|
701
|
+
runtime_output(lines.join("\n"))
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def update_nested_config(section, values)
|
|
705
|
+
ConfigFiles.update_nested_config(section, values)
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def set_config_flag(section, key, enabled)
|
|
709
|
+
update_nested_config(section, key => enabled)
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
def select_settings_menu_item(message, choices, initial_index)
|
|
713
|
+
selected = @prompt.select(message, choices, title: "Settings", initial_index: initial_index)
|
|
714
|
+
[selected, choices.index(selected)]
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
def on_off(value)
|
|
718
|
+
value ? "on" : "off"
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
def login_interactively
|
|
722
|
+
unless login_picker_available?
|
|
723
|
+
runtime_output("Login provider picker is unavailable in this prompt.")
|
|
724
|
+
return
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
method = selected_login_method(@prompt.select("How would you like to connect?", login_method_choices, title: "Login"))
|
|
728
|
+
return unless method
|
|
729
|
+
|
|
730
|
+
selected = @prompt.select(login_provider_prompt(method), login_provider_choices(method), title: "Login")
|
|
731
|
+
provider = selected_login_provider(selected)
|
|
732
|
+
return unless provider
|
|
733
|
+
|
|
734
|
+
run_busy_local_command_and_requeue(activity: "running") do
|
|
735
|
+
login(provider: provider, auth_method: method)
|
|
736
|
+
reload_client_config
|
|
737
|
+
end
|
|
738
|
+
rescue StandardError => e
|
|
739
|
+
runtime_output("Login error: #{e.message}")
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
end
|