kward 0.79.0 → 0.80.1
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/ci.yml +6 -0
- data/CHANGELOG.md +65 -1
- data/Gemfile.lock +2 -2
- data/README.md +31 -38
- data/doc/agent-tools.md +9 -10
- data/doc/api.md +3 -3
- data/doc/authentication.md +79 -110
- data/doc/code-search.md +1 -1
- data/doc/composer.md +1 -1
- data/doc/configuration.md +69 -26
- data/doc/context-budgeting.md +6 -6
- data/doc/context-tools.md +2 -2
- data/doc/editor.md +7 -7
- data/doc/extensibility.md +7 -29
- data/doc/files.md +6 -6
- data/doc/getting-started.md +2 -2
- data/doc/git.md +8 -2
- data/doc/lifecycle-hooks.md +5 -4
- data/doc/local-models.md +2 -2
- data/doc/mcp.md +5 -5
- data/doc/permissions.md +13 -4
- data/doc/plugins.md +39 -4
- data/doc/prompt-templates.md +105 -0
- data/doc/providers.md +140 -0
- data/doc/releasing.md +10 -2
- data/doc/rpc.md +54 -31
- data/doc/sandboxing.md +22 -0
- data/doc/security.md +22 -7
- data/doc/session-management.md +2 -4
- data/doc/skills.md +2 -0
- data/doc/tabs.md +37 -0
- data/doc/telegram.md +138 -0
- data/doc/transports.md +209 -0
- data/doc/usage.md +11 -3
- data/doc/web-search.md +3 -3
- data/doc/workspace-tools.md +2 -2
- data/examples/plugins/stardate_footer.rb +10 -0
- data/examples/plugins/telegram/plugin.rb +39 -0
- data/examples/plugins/telegram/telegram_api.rb +136 -0
- data/examples/plugins/telegram/telegram_transport.rb +304 -0
- data/lib/kward/agent.rb +11 -6
- data/lib/kward/ansi.rb +1 -0
- data/lib/kward/auth/api_key_store.rb +99 -0
- data/lib/kward/cli/auth_commands.rb +72 -13
- data/lib/kward/cli/commands.rb +7 -0
- data/lib/kward/cli/git.rb +31 -8
- data/lib/kward/cli/memory_commands.rb +2 -2
- data/lib/kward/cli/plugins.rb +1 -0
- data/lib/kward/cli/prompt_interface.rb +44 -1
- data/lib/kward/cli/rendering.rb +6 -2
- data/lib/kward/cli/runtime_helpers.rb +5 -2
- data/lib/kward/cli/sessions.rb +1 -1
- data/lib/kward/cli/settings.rb +97 -33
- data/lib/kward/cli/slash_commands.rb +1 -1
- data/lib/kward/cli/tabs.rb +103 -37
- data/lib/kward/cli/transports.rb +67 -0
- data/lib/kward/cli/worktrees.rb +360 -0
- data/lib/kward/cli.rb +22 -0
- data/lib/kward/compactor.rb +7 -2
- data/lib/kward/config_files.rb +51 -12
- data/lib/kward/conversation.rb +13 -7
- data/lib/kward/git_worktree_manager.rb +291 -0
- data/lib/kward/hooks/audit_log.rb +5 -2
- data/lib/kward/model/azure_openai_config.rb +54 -0
- data/lib/kward/model/catalog.rb +113 -0
- data/lib/kward/model/client.rb +225 -20
- data/lib/kward/model/model_info.rb +49 -0
- data/lib/kward/model/payloads.rb +63 -4
- data/lib/kward/model/provider_catalog.rb +114 -0
- data/lib/kward/model/sources.rb +70 -0
- data/lib/kward/model/stream_parser.rb +83 -13
- data/lib/kward/permissions/policy.rb +18 -4
- data/lib/kward/plugin_chat_runtime.rb +374 -0
- data/lib/kward/plugin_registry.rb +99 -19
- data/lib/kward/private_file.rb +9 -3
- data/lib/kward/prompt_interface/approval_prompt.rb +2 -0
- data/lib/kward/prompt_interface/composer_renderer.rb +1 -5
- data/lib/kward/prompt_interface/editor/auto_indent.rb +46 -0
- data/lib/kward/prompt_interface/editor/buffer.rb +18 -4
- data/lib/kward/prompt_interface/editor/controller.rb +7 -7
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +2 -2
- data/lib/kward/prompt_interface/editor/renderer.rb +8 -8
- data/lib/kward/prompt_interface/editor/state.rb +3 -9
- data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +219 -19
- data/lib/kward/prompt_interface/file_overlay.rb +2 -2
- data/lib/kward/prompt_interface/git_prompt.rb +7 -3
- data/lib/kward/prompt_interface/interactive/controller.rb +4 -4
- data/lib/kward/prompt_interface/interactive/renderer.rb +4 -1
- data/lib/kward/prompt_interface/interactive/state.rb +2 -2
- data/lib/kward/prompt_interface/project_browser.rb +25 -6
- data/lib/kward/prompt_interface/runtime_state.rb +22 -6
- data/lib/kward/prompt_interface/screen.rb +3 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +7 -1
- data/lib/kward/prompt_interface/transcript_buffer.rb +24 -2
- data/lib/kward/prompt_interface.rb +53 -12
- data/lib/kward/prompts/commands.rb +1 -1
- data/lib/kward/prompts/templates.rb +11 -6
- data/lib/kward/prompts.rb +6 -5
- data/lib/kward/rpc/auth_manager.rb +112 -152
- data/lib/kward/rpc/config_manager.rb +46 -6
- data/lib/kward/rpc/plugin_chat_manager.rb +55 -196
- data/lib/kward/rpc/prompt_bridge.rb +8 -4
- data/lib/kward/rpc/redactor.rb +1 -1
- data/lib/kward/rpc/server.rb +60 -10
- data/lib/kward/rpc/session_manager.rb +109 -30
- data/lib/kward/session_store.rb +84 -24
- data/lib/kward/skills/registry.rb +16 -11
- data/lib/kward/tab_driver.rb +6 -3
- data/lib/kward/telemetry/logger.rb +5 -2
- data/lib/kward/tools/git_commit.rb +39 -0
- data/lib/kward/tools/registry.rb +7 -2
- data/lib/kward/tools/tool_call.rb +1 -0
- data/lib/kward/transport/gateway.rb +253 -0
- data/lib/kward/transport/host.rb +343 -0
- data/lib/kward/transport/manager.rb +179 -0
- data/lib/kward/transport/plugin_chat_gateway.rb +186 -0
- data/lib/kward/transport/runtime.rb +41 -0
- data/lib/kward/transport/store.rb +101 -0
- data/lib/kward/transport.rb +204 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workspace_factory.rb +19 -2
- data/templates/default/fulldoc/html/js/kward.js +2 -0
- data/templates/default/kward_navigation.rb +7 -2
- data/templates/default/layout/html/setup.rb +4 -0
- metadata +26 -1
data/lib/kward/cli/tabs.rb
CHANGED
|
@@ -8,6 +8,12 @@ module Kward
|
|
|
8
8
|
class CLI
|
|
9
9
|
# TUI session tab coordination and asynchronous turn execution.
|
|
10
10
|
module Tabs
|
|
11
|
+
WORKTREE_AGENT_CONTEXT = <<~PROMPT.strip.freeze
|
|
12
|
+
This is an active Git worktree tab. The workspace is sandboxed separately from the host repository metadata.
|
|
13
|
+
When the user explicitly asks you to commit, use the advertised `git_commit` tool. Do not use `run_shell_command` for `git add` or `git commit`, because shared Git metadata is intentionally protected there.
|
|
14
|
+
`git_commit` requires a commit message and accepts optional workspace-relative paths; omit paths to include all current changes in this worktree. Do not create a commit unless the user requested one.
|
|
15
|
+
PROMPT
|
|
16
|
+
|
|
11
17
|
TabRuntime = Struct.new(
|
|
12
18
|
:session,
|
|
13
19
|
:agent,
|
|
@@ -91,6 +97,7 @@ module Kward
|
|
|
91
97
|
@active_tab_index = 0
|
|
92
98
|
@tab_store = session_store ? TabStore.new(config_dir: session_store.config_dir, cwd: session_store.cwd) : nil
|
|
93
99
|
@tab_live_view = nil
|
|
100
|
+
@tab_live_view_token = nil
|
|
94
101
|
@restored_tabs = false
|
|
95
102
|
restored = restore_tabs(session_store) if session_store && agent.nil?
|
|
96
103
|
return restored if restored
|
|
@@ -120,11 +127,14 @@ module Kward
|
|
|
120
127
|
descriptors = Array(data["tabs"])
|
|
121
128
|
return nil if descriptors.empty?
|
|
122
129
|
|
|
123
|
-
descriptors.each_with_index do |
|
|
124
|
-
tab = build_tab_from_descriptor(
|
|
130
|
+
descriptors.each_with_index do |descriptor, index|
|
|
131
|
+
tab = build_tab_from_descriptor(descriptor, session_store, label: restored_tab_label(data, index))
|
|
125
132
|
@tabs << tab if tab
|
|
126
|
-
rescue StandardError
|
|
127
|
-
|
|
133
|
+
rescue StandardError => e
|
|
134
|
+
descriptor = descriptor.transform_keys(&:to_s) if descriptor.respond_to?(:transform_keys)
|
|
135
|
+
if descriptor.is_a?(Hash) && descriptor.key?("worktree")
|
|
136
|
+
@tabs << unavailable_worktree_tab(descriptor, e, label: restored_tab_label(data, index))
|
|
137
|
+
end
|
|
128
138
|
end
|
|
129
139
|
return nil if @tabs.empty?
|
|
130
140
|
|
|
@@ -136,13 +146,21 @@ module Kward
|
|
|
136
146
|
def build_tab_from_descriptor(descriptor, session_store, label: nil)
|
|
137
147
|
descriptor = descriptor.transform_keys(&:to_s)
|
|
138
148
|
if descriptor["kind"] == "session"
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
worktree = if descriptor.key?("worktree")
|
|
150
|
+
binding = GitWorktreeBinding.from_descriptor(descriptor["worktree"])
|
|
151
|
+
raise GitWorktreeManager::Error, "Invalid worktree binding." unless binding
|
|
152
|
+
|
|
153
|
+
binding
|
|
154
|
+
end
|
|
155
|
+
validate_worktree_binding!(worktree)
|
|
156
|
+
root = worktree_root_for(worktree, fallback: session_store.cwd)
|
|
157
|
+
session, conversation = restore_tab_session(session_store, descriptor.fetch("session_path"), workspace_root: root, strict: worktree&.active? == true)
|
|
158
|
+
agent = build_tab_agent(conversation, session, worktree: worktree)
|
|
159
|
+
return build_tab(session, agent, driver: SessionTabDriver.new(session: session, agent: agent, worktree: worktree), label: label)
|
|
142
160
|
end
|
|
143
161
|
|
|
144
162
|
tab_type = plugin_registry.tab_type_for_id(descriptor["plugin_tab_type"])
|
|
145
|
-
driver = if tab_type
|
|
163
|
+
driver = if tab_type&.local
|
|
146
164
|
host = PluginTabHost.new(client: @client, workspace_root: session_store.cwd)
|
|
147
165
|
tab_type.handler.call(host, descriptor)
|
|
148
166
|
else
|
|
@@ -154,7 +172,7 @@ module Kward
|
|
|
154
172
|
|
|
155
173
|
def open_plugin_tab(name, session_store)
|
|
156
174
|
tab_type = plugin_registry.tab_type_for(name)
|
|
157
|
-
return runtime_output("Plugin tab #{name.inspect} is not available.") unless tab_type
|
|
175
|
+
return runtime_output("Plugin tab #{name.inspect} is not available.") unless tab_type&.local
|
|
158
176
|
|
|
159
177
|
if tab_type.singleton == :global && (existing = @tabs.find { |tab| tab.driver.descriptor["plugin_tab_type"] == tab_type.id })
|
|
160
178
|
return switch_tab(@tabs.index(existing))
|
|
@@ -174,30 +192,37 @@ module Kward
|
|
|
174
192
|
runtime_output("Could not open plugin tab #{name.inspect}: #{e.message}")
|
|
175
193
|
end
|
|
176
194
|
|
|
177
|
-
def restore_tab_session(session_store, path)
|
|
195
|
+
def restore_tab_session(session_store, path, workspace_root: session_store.cwd, strict: false)
|
|
178
196
|
if File.file?(path)
|
|
179
|
-
session, conversation = session_store.load(path, workspace: configured_workspace(root:
|
|
197
|
+
session, conversation = session_store.load(path, workspace: configured_workspace(root: workspace_root, strict: strict), provider: current_model_provider, model: current_model_id, reasoning_effort: current_reasoning_effort)
|
|
180
198
|
return [track_session(session), conversation]
|
|
181
199
|
end
|
|
182
200
|
|
|
183
201
|
session = track_session(session_store.create(provider: current_model_provider, model: current_model_id, reasoning_effort: current_reasoning_effort))
|
|
184
|
-
conversation = new_conversation(workspace_root:
|
|
202
|
+
conversation = new_conversation(workspace_root: workspace_root)
|
|
185
203
|
session.attach(conversation)
|
|
186
204
|
[session, conversation]
|
|
187
205
|
end
|
|
188
206
|
|
|
189
|
-
def build_tab_agent(conversation, _session)
|
|
207
|
+
def build_tab_agent(conversation, _session, worktree: nil)
|
|
190
208
|
conversation.plugin_registry ||= plugin_registry if conversation.respond_to?(:plugin_registry)
|
|
191
|
-
|
|
209
|
+
strict = worktree&.active? == true
|
|
210
|
+
update_worktree_agent_context(conversation, strict)
|
|
211
|
+
workspace = configured_workspace(root: conversation.workspace_root, strict: strict)
|
|
192
212
|
prompt = TabQuestionPrompt.new(self)
|
|
193
|
-
hook_manager = lifecycle_hook_manager(conversation)
|
|
194
|
-
hook_context = lifecycle_hook_context(conversation)
|
|
213
|
+
hook_manager = strict ? nil : lifecycle_hook_manager(conversation)
|
|
214
|
+
hook_context = strict ? nil : lifecycle_hook_context(conversation)
|
|
215
|
+
git_committer = if strict
|
|
216
|
+
->(message:, paths:) { git_commit_for_agent(workspace.root.to_s, message: message, paths: paths) }
|
|
217
|
+
end
|
|
195
218
|
tool_registry = ToolRegistry.new(
|
|
196
219
|
workspace: workspace,
|
|
197
220
|
prompt: prompt,
|
|
198
221
|
tool_approval: tab_tool_approval_callback(prompt),
|
|
199
222
|
hook_manager: hook_manager,
|
|
200
|
-
hook_context: hook_context
|
|
223
|
+
hook_context: hook_context,
|
|
224
|
+
mcp_clients: strict ? [] : nil,
|
|
225
|
+
git_committer: git_committer
|
|
201
226
|
)
|
|
202
227
|
@footer_conversation = conversation
|
|
203
228
|
@footer_tool_registry = tool_registry
|
|
@@ -205,6 +230,7 @@ module Kward
|
|
|
205
230
|
client: @client,
|
|
206
231
|
tool_registry: tool_registry,
|
|
207
232
|
conversation: conversation,
|
|
233
|
+
warning_sink: ConfigFiles.warning_sink,
|
|
208
234
|
hook_manager: hook_manager,
|
|
209
235
|
hook_context: hook_context
|
|
210
236
|
)
|
|
@@ -212,6 +238,14 @@ module Kward
|
|
|
212
238
|
agent
|
|
213
239
|
end
|
|
214
240
|
|
|
241
|
+
def update_worktree_agent_context(conversation, strict)
|
|
242
|
+
desired = strict ? WORKTREE_AGENT_CONTEXT : nil
|
|
243
|
+
return if conversation.execution_profile_context == desired
|
|
244
|
+
return unless strict || conversation.execution_profile_context == WORKTREE_AGENT_CONTEXT
|
|
245
|
+
|
|
246
|
+
conversation.update_execution_profile_context!(desired)
|
|
247
|
+
end
|
|
248
|
+
|
|
215
249
|
def tab_tool_approval_callback(prompt)
|
|
216
250
|
return nil unless ConfigFiles.permission_policy(safely_read_config.to_h).enabled?
|
|
217
251
|
|
|
@@ -372,8 +406,9 @@ module Kward
|
|
|
372
406
|
return agent unless tab
|
|
373
407
|
|
|
374
408
|
tab.session = @active_session
|
|
375
|
-
|
|
376
|
-
tab.
|
|
409
|
+
worktree = reconcile_worktree_binding(tab, agent.conversation)
|
|
410
|
+
tab.agent = build_tab_agent(agent.conversation, tab.session, worktree: worktree)
|
|
411
|
+
tab.driver = SessionTabDriver.new(session: tab.session, agent: tab.agent, worktree: worktree)
|
|
377
412
|
assign_tab_question_prompt(tab.agent, tab)
|
|
378
413
|
tab.diff = @session_diff || (tab.session&.path ? SessionDiff.from_session_file(tab.session.path) : SessionDiff.new)
|
|
379
414
|
tab.snapshot = nil
|
|
@@ -389,6 +424,7 @@ module Kward
|
|
|
389
424
|
tab.shell = nil
|
|
390
425
|
tab.stream_state = new_tab_stream_state(tab.driver)
|
|
391
426
|
tab.markdown_chunks.clear
|
|
427
|
+
update_prompt_workspace_root(current_workspace_root)
|
|
392
428
|
update_prompt_tabs
|
|
393
429
|
persist_tabs
|
|
394
430
|
tab.agent
|
|
@@ -399,13 +435,23 @@ module Kward
|
|
|
399
435
|
return unless tab
|
|
400
436
|
|
|
401
437
|
if @prompt.respond_to?(:tab_view_snapshot)
|
|
402
|
-
tab.snapshot =
|
|
438
|
+
tab.snapshot = tab_view_snapshot_for(tab)
|
|
403
439
|
elsif @prompt.respond_to?(:composer_snapshot)
|
|
404
440
|
tab.snapshot = @prompt.composer_snapshot
|
|
405
441
|
end
|
|
406
442
|
tab.diff = @session_diff
|
|
407
443
|
end
|
|
408
444
|
|
|
445
|
+
def tab_view_snapshot_for(tab)
|
|
446
|
+
snapshot_method = @prompt.method(:tab_view_snapshot)
|
|
447
|
+
supports_transcript_option = snapshot_method.parameters.any? do |type, name|
|
|
448
|
+
type == :keyrest || ([:key, :keyreq].include?(type) && name == :include_transcript)
|
|
449
|
+
end
|
|
450
|
+
return snapshot_method.call unless supports_transcript_option
|
|
451
|
+
|
|
452
|
+
snapshot_method.call(include_transcript: tab.running? || !tab.shell.nil?)
|
|
453
|
+
end
|
|
454
|
+
|
|
409
455
|
def activate_tab(index, render: true)
|
|
410
456
|
tab = @tabs[index]
|
|
411
457
|
return nil unless tab
|
|
@@ -424,6 +470,8 @@ module Kward
|
|
|
424
470
|
end
|
|
425
471
|
tab.unread = false
|
|
426
472
|
restore_tab_composer_snapshot(tab.snapshot)
|
|
473
|
+
update_prompt_workspace_root(current_workspace_root)
|
|
474
|
+
@prompt.refresh_composer_status if @prompt.respond_to?(:refresh_composer_status)
|
|
427
475
|
update_plugin_tab_slash_commands(tab)
|
|
428
476
|
update_prompt_tabs
|
|
429
477
|
render_tab(tab) if render
|
|
@@ -640,12 +688,12 @@ module Kward
|
|
|
640
688
|
|
|
641
689
|
def refresh_active_tab
|
|
642
690
|
tab = active_tab
|
|
643
|
-
return unless tab
|
|
691
|
+
return unless tab&.thread && !tab.thread.alive? && tab.running?
|
|
644
692
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
693
|
+
tab.thread.join
|
|
694
|
+
return unless tab.status == "running"
|
|
695
|
+
|
|
696
|
+
tab.status = "ready"
|
|
649
697
|
update_prompt_tabs
|
|
650
698
|
end
|
|
651
699
|
|
|
@@ -653,23 +701,31 @@ module Kward
|
|
|
653
701
|
return unless prompt_interface?
|
|
654
702
|
|
|
655
703
|
stop_tab_live_view
|
|
656
|
-
|
|
657
|
-
@
|
|
704
|
+
token = Object.new
|
|
705
|
+
@tab_live_view_token = token
|
|
706
|
+
@tab_live_view = Thread.new { run_tab_live_view(tab, token) }
|
|
658
707
|
@tab_live_view.report_on_exception = false
|
|
659
708
|
end
|
|
660
709
|
|
|
661
710
|
def stop_tab_live_view
|
|
662
|
-
|
|
663
|
-
@
|
|
664
|
-
|
|
711
|
+
thread = @tab_live_view
|
|
712
|
+
@tab_live_view_token = nil
|
|
713
|
+
thread&.join(0.2)
|
|
714
|
+
@tab_live_view = nil if @tab_live_view.equal?(thread)
|
|
665
715
|
end
|
|
666
716
|
|
|
667
|
-
def run_tab_live_view(tab)
|
|
717
|
+
def run_tab_live_view(tab, token)
|
|
668
718
|
renderer = tab_live_renderer(tab)
|
|
669
|
-
|
|
719
|
+
while @tab_live_view_token.equal?(token)
|
|
670
720
|
events = tab.event_history[tab.seen_events..] || []
|
|
671
|
-
events.each
|
|
672
|
-
|
|
721
|
+
events.each do |event|
|
|
722
|
+
break unless @tab_live_view_token.equal?(token)
|
|
723
|
+
|
|
724
|
+
renderer.call(event, tab.driver)
|
|
725
|
+
tab.seen_events += 1
|
|
726
|
+
end
|
|
727
|
+
break unless @tab_live_view_token.equal?(token)
|
|
728
|
+
|
|
673
729
|
if tab.idle?
|
|
674
730
|
renderer.call(:flush, tab.driver)
|
|
675
731
|
break
|
|
@@ -677,7 +733,8 @@ module Kward
|
|
|
677
733
|
sleep 0.05
|
|
678
734
|
end
|
|
679
735
|
ensure
|
|
680
|
-
@
|
|
736
|
+
@tab_live_view_token = nil if @tab_live_view_token.equal?(token)
|
|
737
|
+
@tab_live_view = nil if @tab_live_view == Thread.current
|
|
681
738
|
end
|
|
682
739
|
|
|
683
740
|
def tab_live_renderer(tab)
|
|
@@ -726,6 +783,8 @@ module Kward
|
|
|
726
783
|
def tab_labels
|
|
727
784
|
@tabs.each_with_index.map do |tab, index|
|
|
728
785
|
label = tab.label.to_s.empty? ? default_tab_label(index) : tab.label.to_s
|
|
786
|
+
binding = worktree_binding_for(tab)
|
|
787
|
+
label = "#{label} · #{binding.branch}" if binding&.active?
|
|
729
788
|
{ name: label, color: tab_label_color(tab) }
|
|
730
789
|
end
|
|
731
790
|
end
|
|
@@ -752,7 +811,7 @@ module Kward
|
|
|
752
811
|
action, value = argument.to_s.strip.split(/\s+/, 2)
|
|
753
812
|
case action
|
|
754
813
|
when nil, ""
|
|
755
|
-
runtime_output(
|
|
814
|
+
runtime_output(tab_usage)
|
|
756
815
|
nil
|
|
757
816
|
when /^\d+$/
|
|
758
817
|
switch_tab_number(action)
|
|
@@ -774,12 +833,19 @@ module Kward
|
|
|
774
833
|
when "name", "rename"
|
|
775
834
|
rename_active_tab(value)
|
|
776
835
|
active_tab&.agent
|
|
836
|
+
when "worktree"
|
|
837
|
+
handle_worktree_command(value)
|
|
838
|
+
active_tab&.agent
|
|
777
839
|
else
|
|
778
|
-
runtime_output(
|
|
840
|
+
runtime_output(tab_usage)
|
|
779
841
|
nil
|
|
780
842
|
end
|
|
781
843
|
end
|
|
782
844
|
|
|
845
|
+
def tab_usage
|
|
846
|
+
"/tab 1-n | /tab move 1-n|left|right | /tab close | /tab new | /tab open <plugin-tab> | /tab name <label> | /tab worktree [activate|detach|status|merge|merge abort|remove]"
|
|
847
|
+
end
|
|
848
|
+
|
|
783
849
|
def switch_tab_number(number)
|
|
784
850
|
index = number.to_i - 1
|
|
785
851
|
return switch_tab(index) if @tabs && index.between?(0, @tabs.length - 1)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require_relative "../transport/runtime"
|
|
2
|
+
|
|
3
|
+
module Kward
|
|
4
|
+
class CLI
|
|
5
|
+
# Foreground transport lifecycle commands.
|
|
6
|
+
module Transports
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def handle_transport_command(arguments)
|
|
10
|
+
command = arguments.first.to_s
|
|
11
|
+
case command
|
|
12
|
+
when "list"
|
|
13
|
+
raise ArgumentError, command_usage("transport") unless arguments.length == 1
|
|
14
|
+
|
|
15
|
+
with_transport_runtime { |runtime| print_transport_entries(runtime.manager.list) }
|
|
16
|
+
when "status"
|
|
17
|
+
raise ArgumentError, command_usage("transport") unless arguments.length <= 2
|
|
18
|
+
|
|
19
|
+
with_transport_runtime do |runtime|
|
|
20
|
+
entries = arguments.length == 2 ? [runtime.manager.status(arguments[1])] : runtime.manager.status
|
|
21
|
+
print_transport_entries(entries)
|
|
22
|
+
end
|
|
23
|
+
when "run"
|
|
24
|
+
raise ArgumentError, command_usage("transport") unless (2..3).cover?(arguments.length)
|
|
25
|
+
|
|
26
|
+
workspace_root = arguments[2] ? expanded_working_directory(arguments[2]) : @working_directory
|
|
27
|
+
run_transport(arguments[1], workspace_root: workspace_root)
|
|
28
|
+
else
|
|
29
|
+
raise ArgumentError, command_usage("transport")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def print_transport_entries(entries)
|
|
34
|
+
entries.each do |entry|
|
|
35
|
+
@prompt.say("#{entry[:id]}\t#{entry[:state] || "stopped"}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def run_transport(name, workspace_root: nil)
|
|
40
|
+
runtime = Transport::Runtime.new(client: ensure_client!)
|
|
41
|
+
runtime.manager.start(name, workspace_root: workspace_root)
|
|
42
|
+
@prompt.say("Transport #{name} is running. Press Ctrl-C to stop.")
|
|
43
|
+
wait_for_transport_shutdown
|
|
44
|
+
ensure
|
|
45
|
+
runtime&.shutdown
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def with_transport_runtime
|
|
49
|
+
runtime = Transport::Runtime.new(client: ensure_client!)
|
|
50
|
+
yield runtime
|
|
51
|
+
ensure
|
|
52
|
+
runtime&.shutdown
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def wait_for_transport_shutdown
|
|
56
|
+
stopped = false
|
|
57
|
+
previous_handlers = {}
|
|
58
|
+
%w[INT TERM].each do |signal|
|
|
59
|
+
previous_handlers[signal] = Signal.trap(signal) { stopped = true }
|
|
60
|
+
end
|
|
61
|
+
sleep 0.1 until stopped
|
|
62
|
+
ensure
|
|
63
|
+
previous_handlers&.each { |signal, handler| Signal.trap(signal, handler) }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|