kward 0.80.0 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/Gemfile.lock +2 -2
  4. data/README.md +1 -1
  5. data/doc/composer.md +5 -8
  6. data/doc/configuration.md +13 -3
  7. data/doc/permissions.md +1 -1
  8. data/doc/rpc.md +2 -1
  9. data/doc/sandboxing.md +4 -4
  10. data/doc/security.md +6 -9
  11. data/doc/shell.md +161 -196
  12. data/doc/skills.md +19 -5
  13. data/doc/tabs.md +1 -1
  14. data/doc/usage.md +6 -5
  15. data/lib/kward/agent.rb +11 -6
  16. data/lib/kward/ansi.rb +9 -1
  17. data/lib/kward/cli/commands.rb +7 -0
  18. data/lib/kward/cli/interactive_turn.rb +1 -1
  19. data/lib/kward/cli/memory_commands.rb +2 -2
  20. data/lib/kward/cli/plugins.rb +1 -1
  21. data/lib/kward/cli/project_skills.rb +99 -0
  22. data/lib/kward/cli/project_skills_commands.rb +87 -0
  23. data/lib/kward/cli/prompt_interface.rb +54 -4
  24. data/lib/kward/cli/rendering.rb +28 -2
  25. data/lib/kward/cli/runtime_helpers.rb +157 -19
  26. data/lib/kward/cli/sessions.rb +6 -4
  27. data/lib/kward/cli/settings.rb +1 -1
  28. data/lib/kward/cli/slash_commands.rb +7 -1
  29. data/lib/kward/cli/tabs.rb +4 -1
  30. data/lib/kward/cli.rb +21 -1
  31. data/lib/kward/compactor.rb +7 -2
  32. data/lib/kward/config_files.rb +30 -9
  33. data/lib/kward/conversation.rb +8 -5
  34. data/lib/kward/ekwsh.rb +37 -16
  35. data/lib/kward/hooks/audit_log.rb +5 -2
  36. data/lib/kward/interactive_pty_runner.rb +88 -22
  37. data/lib/kward/plugin_registry.rb +20 -13
  38. data/lib/kward/prompt_interface/composer_controller.rb +13 -1
  39. data/lib/kward/prompt_interface/editor/controller.rb +4 -0
  40. data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +35 -10
  41. data/lib/kward/prompt_interface/key_handler.rb +78 -43
  42. data/lib/kward/prompt_interface/overlay_renderer.rb +12 -0
  43. data/lib/kward/prompt_interface/screen.rb +5 -0
  44. data/lib/kward/prompt_interface.rb +93 -21
  45. data/lib/kward/prompts/commands.rb +2 -0
  46. data/lib/kward/prompts/templates.rb +11 -6
  47. data/lib/kward/prompts.rb +6 -6
  48. data/lib/kward/rpc/server.rb +1 -0
  49. data/lib/kward/session_store.rb +3 -2
  50. data/lib/kward/skills/registry.rb +67 -12
  51. data/lib/kward/skills/trust_coordinator.rb +45 -0
  52. data/lib/kward/skills/trust_store.rb +107 -0
  53. data/lib/kward/telemetry/logger.rb +5 -2
  54. data/lib/kward/version.rb +1 -1
  55. metadata +5 -1
data/doc/shell.md CHANGED
@@ -1,246 +1,263 @@
1
1
  # Embedded shell
2
2
 
3
- Use `/shell` when you want to run a sequence of local commands without leaving Kward's interactive TUI.
3
+ Kward lets you run your own shell commands without leaving the interactive TUI. Choose the workflow that matches what you are doing:
4
4
 
5
- `/shell` opens **ekwsh**, the embedded Kward shell. It is not a full terminal emulator. Kward keeps ownership of the composer, transcript, tab bar, and keybindings while each command runs through your normal shell one command at a time.
5
+ - **For one quick command, use `!command`.** It runs from the active workspace and returns you to the normal composer when it finishes.
6
+ - **For a longer stretch of shell work, use `/shell`.** It keeps its directory, environment, aliases, and history between commands.
7
+ - **For bounded output in the transcript view, use `/capture <command>`.** Inside `/shell`, use `capture <command>` for the same kind of readable output while keeping the shell's current state.
6
8
 
7
- This makes it good for:
9
+ Ordinary `!command` input and external commands inside `/shell` receive an interactive PTY. Pagers, Vim, SSH, REPLs, password prompts, and other interactive tools therefore work without a `pty` prefix.
8
10
 
9
- - checking Git state,
10
- - running focused tests,
11
- - inspecting files with command-line tools,
12
- - using project aliases,
13
- - keeping command output visible beside the current Kward session.
11
+ ## Run a one-off command
14
12
 
15
- Use `/pty <command>` when you intentionally want to hand the terminal to a full-screen interactive command such as `less`.
13
+ Prefix a command with `!` in the normal composer:
16
14
 
17
- ## Start shell mode
15
+ ```text
16
+ !git status --short
17
+ !bundle exec ruby -Itest test/test_cli.rb
18
+ !less README.md
19
+ ```
20
+
21
+ The command runs from the active workspace root and temporarily owns the terminal. When it exits, Kward restores the composer. Safe, line-oriented output from commands such as `ls` is mirrored into the transient transcript view so a repaint cannot hide it. Shell output is never added to the AI conversation or sent to the model.
22
+
23
+ The line-oriented Git commands `git fetch`, `git ls-remote`, `git push`, `git remote`, and `git status` keep the composer visible as a frozen display while they run. Keyboard input still belongs to the child process. Other commands use the full-terminal handoff so full-screen programs retain the complete terminal.
24
+
25
+ Shell output can leave transient text in the transcript area. **After the command finishes, press Ctrl+L to redraw the durable conversation and clear that transient `!command` output.** While an interactive command is still running, keyboard input—including Ctrl+L and Kward's tab shortcuts—belongs to the child process.
26
+
27
+ Configured `ekwsh.yml` aliases also work after `!`:
28
+
29
+ ```yaml
30
+ aliases:
31
+ glog: "git log --decorate --stat --graph"
32
+ ```
33
+
34
+ ```text
35
+ !glog
36
+ ```
37
+
38
+ An alias that resolves to `kward edit <filename>` opens Kward's integrated editor in the current session instead of starting a nested Kward process:
39
+
40
+ ```yaml
41
+ aliases:
42
+ vibe: "kward edit"
43
+ ```
44
+
45
+ ```text
46
+ !vibe filename.txt
47
+ ```
48
+
49
+ Press Tab after `!` to complete configured aliases, executables from `PATH`, and paths from the active workspace:
50
+
51
+ ```text
52
+ !git sta<Tab>
53
+ !cat lib/kwa<Tab>
54
+ ```
55
+
56
+ The completion overlay lists matching commands and paths. Press Tab repeatedly to cycle through the listed candidates.
57
+
58
+ ## Work in shell mode
18
59
 
19
- Inside interactive Kward:
60
+ Enter the embedded shell when you expect to run several commands:
20
61
 
21
62
  ```text
22
63
  /shell
23
64
  ```
24
65
 
25
- The composer prompt changes to show the shell's current directory:
66
+ The prompt changes to show the shell's current directory:
26
67
 
27
68
  ```text
28
69
  Shell ~/code/project $
29
70
  ```
30
71
 
31
- Type commands as you would in a normal shell:
72
+ The directory and exported environment persist between commands:
32
73
 
33
74
  ```sh
34
- git status --short
35
- bundle exec ruby -Itest test/test_ekwsh.rb
36
- cd lib/kward
75
+ cd test
37
76
  pwd
77
+ export RAILS_ENV=test
78
+ bundle exec ruby -Itest test_example.rb
79
+ unset RAILS_ENV
38
80
  ```
39
81
 
40
- Leave shell mode with:
82
+ Simple assignment-only commands persist too:
41
83
 
42
84
  ```sh
43
- exit
85
+ FOO=bar
86
+ printf '%s\n' "$FOO"
44
87
  ```
45
88
 
46
- or press Ctrl+D on an empty shell prompt.
89
+ Leave shell mode with `exit`, `logout`, or Ctrl+D on an empty prompt.
47
90
 
48
- ## How commands run
91
+ `cd` changes only the embedded shell's directory. It does not change Kward's workspace root or the directory used by the model's other tools.
49
92
 
50
- `ekwsh` is POSIX-oriented and runs each command through `/bin/sh` using the shell's `-c` mode by default. You can configure another POSIX-compatible shell in `ekwsh.yml`. The shell is intentionally not started as a login shell so Kward-managed environment values, such as configured PATH entries, are not overwritten by login startup files.
93
+ ## Interactive and captured commands
51
94
 
52
- The current directory and exported environment are tracked by Kward between commands, so this works as expected:
95
+ External commands inside `/shell` are interactive by default:
53
96
 
54
97
  ```sh
55
- cd test
56
- pwd
57
- export FOO=bar
58
- printf '%s\n' "$FOO"
59
- unset FOO
98
+ git log
99
+ vim README.md
100
+ ruby
101
+ ssh example.com
60
102
  ```
61
103
 
62
- `cd` changes only the embedded shell's current directory. It does not change Kward's workspace root or the process directory used by the rest of Kward.
104
+ Kward gives each command the terminal, forwards keyboard input, and restores the shell prompt when the command exits. It prints the submitted command but no PTY start message or exit-status summary. The line-oriented Git commands `git fetch`, `git ls-remote`, `git push`, `git remote`, and `git status` keep the shell prompt visible as a frozen display. Safe, line-oriented output from commands that did not read keyboard input is kept in the transient transcript view; full-screen and genuinely interactive output stays terminal-owned.
63
105
 
64
- Shell output streams into the transcript area as commands run, but it is not added to the AI conversation history. Simple assignment-only commands such as `FOO=bar` persist into the embedded shell environment for later commands.
106
+ Use `capture` inside `/shell` when you want ordinary, readable output in Kward's transcript area instead of direct terminal control:
65
107
 
66
- Shell commands are persisted in a separate workspace-scoped shell history. They do not share the normal Kward prompt history used for chat prompts. The shell history limit is controlled by `history_limit` in `ekwsh.yml`.
108
+ ```sh
109
+ capture git status --short
110
+ capture bundle exec ruby -Itest test/test_ekwsh.rb
111
+ ```
67
112
 
68
- ## Built-ins
113
+ An `ekwsh` `capture` command:
69
114
 
70
- `ekwsh` handles a small set of built-ins itself:
115
+ - does not receive keyboard input,
116
+ - uses the timeout and output-size limit from `ekwsh.yml`,
117
+ - preserves safe color and styling,
118
+ - strips controls that could corrupt Kward's TUI,
119
+ - can be cancelled with Ctrl+C.
71
120
 
72
- | Built-in | What it does |
73
- | --- | --- |
74
- | `cd [dir]` | Change the embedded shell directory. Supports `cd`, `cd -`, and normal relative paths. |
75
- | `pwd` | Print the embedded shell directory. |
76
- | `export KEY[=value]` | Set or mark an environment variable for later commands. `export` and `export -p` list variables. |
77
- | `unset KEY` | Remove an environment variable from later commands. |
78
- | `alias [name]` | List configured aliases, show specific configured aliases, or set `alias name=value`. |
79
- | `unalias name` / `unalias -a` | Remove configured aliases. |
80
- | `clear` | Clear Kward's visible transcript. |
81
- | `exit` / `logout` | Leave shell mode. |
121
+ From the normal composer, `/capture` provides a bounded, noninteractive one-shot command using the active workspace root:
82
122
 
83
- Built-ins take precedence over aliases and executables.
123
+ ```text
124
+ /capture git status --short
125
+ ```
84
126
 
85
- ## Tabs
127
+ It captures stdout and stderr in the transcript view, but does not inherit `/shell` state or its configured runtime limits.
86
128
 
87
- Shell mode is tracked per Kward tab.
129
+ Interactive commands are not automatically timed out or output-limited. Ctrl+C is forwarded to the child.
88
130
 
89
- If you enter `/shell` in one tab, switch to another tab, and later switch back, that tab is still in shell mode. Its shell state and transcript snapshot are restored until you leave shell mode in that tab.
131
+ The older `/pty <command>` slash command and `pty <command>` shell built-in remain available for compatibility, but ordinary interactive commands no longer need them.
90
132
 
91
- This means each tab can have its own:
133
+ ## Shell state, history, and tabs
92
134
 
93
- - shell current directory,
135
+ Each Kward tab owns its `/shell` state. Switching away and back restores that tab's:
136
+
137
+ - current shell directory,
94
138
  - exported environment,
95
- - visible shell transcript,
96
- - command prompt state.
139
+ - runtime aliases,
140
+ - shell prompt and transcript view.
97
141
 
98
- Kward's normal tab-switching shortcuts continue to work in shell mode. If you switch tabs while a shell command is running, Kward cancels that foreground command and requeues the tab action so the TUI can switch cleanly.
142
+ Shell commands use a separate, workspace-scoped history rather than the normal chat-prompt history. Configure its size with `history_limit` in `ekwsh.yml`.
99
143
 
100
- ## Tab completion
144
+ Kward's tab shortcuts work at the shell prompt and while a captured command is running. During an interactive command, the child owns every key; exit or interrupt it before switching Kward tabs.
101
145
 
102
- Plain Tab completes shell input while in `ekwsh`.
146
+ ## Completion
103
147
 
104
- Completion includes:
148
+ Press Tab in `/shell` to complete:
105
149
 
106
- - built-in command names,
107
- - configured aliases,
108
- - executable names from `$PATH`,
109
- - file paths from the shell's current directory.
150
+ - built-in names,
151
+ - configured and runtime aliases,
152
+ - executables from `PATH`,
153
+ - files and directories from the shell's current directory.
110
154
 
111
155
  Examples:
112
156
 
113
157
  ```sh
114
158
  pw<Tab> # pwd
115
- ll<Tab> # configured alias, if present
116
- cat lib/kw<Tab> # path completion
117
- cd doc<Tab> # directory-only completion for cd
159
+ ll<Tab> # configured alias
160
+ cat lib/kw<Tab> # path
161
+ cd doc<Tab> # directories only for cd
118
162
  ```
119
163
 
120
- Directory completions get a trailing slash. File and command completions get a trailing space when there is a single match.
121
-
122
- Paths with spaces are shell-escaped:
164
+ Paths with spaces are escaped:
123
165
 
124
166
  ```sh
125
167
  cat my<Tab>
126
168
  # => cat my\ file.txt
127
169
  ```
128
170
 
129
- Quoted path tokens are completed in the same quoting style:
171
+ Quoted paths stay quoted:
130
172
 
131
173
  ```sh
132
174
  cat "my<Tab>
133
175
  # => cat "my file.txt
134
176
  ```
135
177
 
136
- If there are multiple candidates, Kward applies any common prefix. When there is no longer common prefix, it prints a compact candidate list in the transcript. Command-name completion caches `$PATH` executables and refreshes when `PATH` changes through shell assignment, `export`, or `unset`.
137
-
138
- ## Colors and ANSI output
139
-
140
- `ekwsh` preserves safe ANSI SGR color/style sequences in command output, such as:
141
-
142
- - colors,
143
- - bold,
144
- - dim,
145
- - italic,
146
- - underline,
147
- - 256-color and truecolor SGR sequences.
148
-
149
- It strips terminal-control sequences that could corrupt Kward's TUI, such as cursor movement, clear-screen controls, OSC title changes, and alternate-screen controls.
150
-
151
- When rbenv is available, `ekwsh` also prepends the rbenv shims and bin directories to PATH and sets `RBENV_ROOT` when it was missing. This lets commands such as `ruby`, `bundle`, and `./exe/kward` use the same Ruby selected by rbenv without requiring `rbenv init` support in `ekwsh`.
178
+ When several candidates match, repeated Tab presses cycle through them and wrap back to the first. Candidate lists stay in the composer instead of being printed in the transcript. The executable cache refreshes when `PATH` changes through assignment, `export`, or `unset`.
152
179
 
153
- By default, `ekwsh` sets conservative color-friendly environment values:
180
+ ## Built-ins
154
181
 
155
- ```sh
156
- CLICOLOR=1
157
- COLORTERM=truecolor
158
- TERM=xterm-256color # only when TERM is missing or dumb
159
- ```
182
+ `ekwsh` handles a small set of commands itself so their state can persist:
160
183
 
161
- It does **not** force color by default. If a tool does not emit color because stdout is not a TTY, use a command-specific flag or configure forced color yourself:
184
+ | Built-in | What it does |
185
+ | --- | --- |
186
+ | `cd [dir]` | Change the shell directory. Supports `cd`, `cd -`, and relative paths. |
187
+ | `pwd` | Print the shell directory. |
188
+ | `export KEY[=value]` | Set an environment variable. `export` and `export -p` list variables. |
189
+ | `unset KEY` | Remove an environment variable. |
190
+ | `alias [name]` | List, inspect, or create aliases. |
191
+ | `unalias name` / `unalias -a` | Remove aliases. |
192
+ | `capture <command>` | Run with bounded, sanitized transcript output. |
193
+ | `pty <command>` | Compatibility spelling for interactive terminal handoff. |
194
+ | `clear` | Clear the visible transcript. |
195
+ | `exit` / `logout` | Leave shell mode. |
162
196
 
163
- ```sh
164
- git diff --color=always
165
- grep --color=always TODO lib/**/*.rb
166
- ```
197
+ Built-ins take precedence over aliases and executables.
167
198
 
168
- ## Global ekwsh config
199
+ ## Configure ekwsh
169
200
 
170
- You can configure environment variables and aliases in:
201
+ Global shell configuration lives at:
171
202
 
172
203
  ```text
173
204
  ~/.kward/ekwsh.yml
174
205
  ```
175
206
 
176
- If `KWARD_CONFIG_PATH` points to another config file, `ekwsh.yml` is read from that file's directory instead.
207
+ When `KWARD_CONFIG_PATH` selects another main config file, Kward reads `ekwsh.yml` from the same directory.
177
208
 
178
- Example:
209
+ A practical configuration might look like this:
179
210
 
180
211
  ```yaml
181
212
  shell: /bin/sh
182
- # timeout_seconds: 300
183
- # max_output_bytes: 1048576
184
- # history_limit: 1000
213
+ timeout_seconds: 300
214
+ max_output_bytes: 1048576
215
+ history_limit: 1000
185
216
 
186
217
  env:
187
218
  FORCE_COLOR: "1"
188
- CLICOLOR_FORCE: "1"
219
+ BUNDLE_WITHOUT: "production"
189
220
  RAILS_ENV: "test"
190
221
 
191
222
  aliases:
192
223
  ll: "ls -la"
193
224
  gs: "git status --short"
194
225
  gd: "git diff --color=always"
226
+ glog: "git log --decorate --stat --graph"
195
227
  be: "bundle exec"
196
228
  t: "bundle exec ruby -Itest"
197
229
  ```
198
230
 
199
- ### Runtime settings
200
-
201
- `ekwsh` accepts these top-level runtime settings:
231
+ ### Settings
202
232
 
203
233
  | Setting | Default | What it does |
204
234
  | --- | --- | --- |
205
- | `shell` | `/bin/sh` | Absolute path to the POSIX-compatible shell used with `-c`. Invalid or relative paths fall back to `/bin/sh`. |
206
- | `timeout_seconds` | `300` | Maximum runtime for one command. |
207
- | `max_output_bytes` | `1048576` | Maximum captured output for one command. |
208
- | `history_limit` | `1000` | Maximum persisted shell history entries per workspace. |
235
+ | `shell` | `/bin/sh` | POSIX-compatible shell used with `-c`. It must be an absolute executable path. |
236
+ | `timeout_seconds` | `300` | Maximum runtime for one captured command. |
237
+ | `max_output_bytes` | `1048576` | Maximum output retained for one captured command. |
238
+ | `history_limit` | `1000` | Maximum shell-history entries per workspace. |
209
239
 
210
- When a command exceeds `timeout_seconds`, `ekwsh` terminates it and reports the timeout. When command output exceeds `max_output_bytes`, `ekwsh` terminates the command, keeps bounded output, and reports the output limit. Press Ctrl+C while a command is running to terminate that command and return to the shell prompt without exiting Kward.
240
+ Invalid or relative `shell` paths fall back to `/bin/sh`. These timeout and output limits apply only to `capture` inside `/shell`, not interactive commands or the separate `/capture` slash command.
211
241
 
212
- ### Environment variables
242
+ ### Environment
213
243
 
214
- `env` values are applied when shell mode starts, after Kward's conservative color defaults.
244
+ Configured `env` values are applied when `/shell` starts. Keys must be valid environment-variable names; nil values and invalid keys are ignored, and other values are converted to strings.
215
245
 
216
- Keys must look like environment variable names:
246
+ Kward also supplies conservative terminal defaults:
217
247
 
218
- ```text
219
- A_Z, digits after the first character, and underscores
220
- ```
221
-
222
- Invalid keys are ignored. Values are converted to strings. Nil values are ignored.
223
-
224
- Configured env is useful for local preferences such as:
225
-
226
- ```yaml
227
- env:
228
- FORCE_COLOR: "1"
229
- BUNDLE_WITHOUT: "production"
230
- RAILS_ENV: "test"
248
+ ```sh
249
+ CLICOLOR=1
250
+ COLORTERM=truecolor
251
+ TERM=xterm-256color # only when TERM is missing or dumb
231
252
  ```
232
253
 
233
- For one shell session, you can also use the built-in `export` command:
254
+ It does not force color. Set `FORCE_COLOR`, `CLICOLOR_FORCE`, or a command-specific option such as `--color=always` when needed.
234
255
 
235
- ```sh
236
- export FORCE_COLOR=1
237
- ```
256
+ When rbenv is available, Kward adds its shims and bin directories to `PATH` and supplies `RBENV_ROOT` if it was missing. This lets `ruby`, `bundle`, and `./exe/kward` use the selected Ruby without sourcing shell startup files.
238
257
 
239
258
  ### Aliases
240
259
 
241
- Aliases expand the first word of a command once.
242
-
243
- With this config:
260
+ Aliases replace the first command word once and append any remaining arguments:
244
261
 
245
262
  ```yaml
246
263
  aliases:
@@ -248,8 +265,6 @@ aliases:
248
265
  t: "bundle exec ruby -Itest"
249
266
  ```
250
267
 
251
- these commands run as:
252
-
253
268
  ```sh
254
269
  ll lib
255
270
  # runs: ls -la lib
@@ -258,75 +273,25 @@ t test/test_ekwsh.rb
258
273
  # runs: bundle exec ruby -Itest test/test_ekwsh.rb
259
274
  ```
260
275
 
261
- Aliases are intentionally simple:
262
-
263
- - only the first word is expanded,
264
- - aliases do not recursively expand,
265
- - built-ins win over alias names,
266
- - aliases are not shell functions,
267
- - aliases are global for now.
268
-
269
- List configured aliases inside `ekwsh`:
270
-
271
- ```sh
272
- alias
273
- ```
274
-
275
- Show specific aliases:
276
-
277
- ```sh
278
- alias ll gs
279
- ```
280
-
281
- Aliases also appear in command-name Tab completion.
282
-
283
- ## Interactive PTY passthrough
284
-
285
- Use `/pty <command>` for commands that need to own the terminal temporarily, such as pagers:
286
-
287
- ```text
288
- /pty git log
289
- ```
290
-
291
- Inside `/shell`, use the `pty` built-in so the command inherits the embedded shell's current directory and environment:
292
-
293
- ```sh
294
- pty git log
295
- pty vim README.md
296
- ```
297
-
298
- Kward runs the command in a PTY, forwards your keyboard input to the process, and streams the process output directly to the terminal. This lets tools such as `less` receive keys like Space, `/`, `n`, and `q` normally. When the command exits, Kward restores its prompt and records only a short session summary in the transcript instead of raw full-screen terminal control output.
299
-
300
- `/pty` and shell `pty` are explicit on purpose. Normal `/shell` commands remain captured and transcript-friendly; `pty` is for commands where the child process should temporarily own the terminal.
276
+ Configured aliases work inside `/shell` and after `!`. Aliases created with the `alias` built-in belong only to the current `/shell` session and are not shared with one-shot `!command` input.
301
277
 
302
- ## `/shell` versus `!command`
303
-
304
- Kward has two ways to run local commands yourself:
305
-
306
- ```text
307
- !git status --short
308
- ```
278
+ For compatibility with older configs, Kward removes a leading `pty` or `capture` mode marker when a configured alias is invoked through `!`, because `!command` is always interactive.
309
279
 
310
- runs one command directly from the normal composer.
280
+ Aliases are intentionally simple: they do not expand recursively and are not shell functions. Use `alias` to list them, `alias name=value` to create a runtime alias, and `unalias` to remove one.
311
281
 
312
- ```text
313
- /shell
314
- ```
282
+ ## Terminal output and safety
315
283
 
316
- starts an embedded command mode for several commands with persistent shell state, aliases, completion, and per-tab shell context.
284
+ Interactive commands write directly to your terminal so full-screen tools can work. When a command does not read keyboard input and emits only line-oriented text plus safe color sequences, Kward mirrors that output into the transient transcript view after the command exits. Other interactive output is not sanitized and may contain terminal control sequences, so run only commands you trust with terminal access.
317
285
 
318
- Use `!command` for one-offs. Use `/shell` when you expect to run several commands in a row.
286
+ Commands run with `capture` inside `/shell` preserve safe ANSI color and style sequences while removing cursor movement, clear-screen controls, title changes, alternate-screen controls, and similar sequences that could damage the TUI transcript.
319
287
 
320
288
  ## Limitations
321
289
 
322
- `ekwsh` is deliberately not a full interactive terminal.
323
-
324
- Current limitations:
325
-
326
- - no job control,
327
- - no persistent shell functions,
328
- - no shell startup file sourcing,
329
- - no native readline from your login shell,
330
- - normal `/shell` command output is transcript-sanitized rather than treated as a full terminal UI.
290
+ `ekwsh` manages shell-like state, but it is not a persistent login shell or terminal emulator:
331
291
 
332
- External `/shell` commands run under a minimal PTY so terminal-aware tools can detect TTY output and terminal width. Full-screen interactive programs should still use explicit `/pty <command>` passthrough.
292
+ - each external command runs separately through the configured shell,
293
+ - there is no job control; a stopped child is terminated rather than leaving the terminal stranded,
294
+ - shell functions do not persist and shell startup files are not sourced,
295
+ - there is no login-shell readline integration,
296
+ - full-screen terminal state is not retained after an interactive command exits,
297
+ - safe line-oriented output may remain in the transient TUI transcript, but full-screen terminal state is not retained and no shell output becomes part of the AI conversation.
data/doc/skills.md CHANGED
@@ -70,19 +70,31 @@ Project-level skills live in the current workspace:
70
70
 
71
71
  The `.agents/skills` paths are the cross-client Agent Skills convention. Use them when you want skills to work across compatible agents.
72
72
 
73
- Project skills are skipped by default because repository-provided instructions may be untrusted. If you trust the repository, enable project skills from inside Kward:
73
+ Project skills require an explicit trust decision because repository-provided instructions may be untrusted. In the interactive TUI, Kward asks when it finds a new or changed project skill:
74
74
 
75
75
  ```text
76
- /settings
76
+ Allow / Deny / Review
77
77
  ```
78
78
 
79
- Then choose:
79
+ `Review` displays the bounded `SKILL.md` contents and lists referenced resources without executing them. Trust decisions are scoped to the current workspace and skill snapshot. A changed skill or newly added skill requires another decision. Use `/new` after changing trust for the active agent to rebuild.
80
+
81
+ You can inspect or manage the current workspace from the TUI:
80
82
 
81
83
  ```text
82
- Tools & Search → Trust project skills
84
+ /skills status
85
+ /skills trust
86
+ /skills untrust
83
87
  ```
84
88
 
85
- Or set it manually:
89
+ For non-interactive use, inspect or manage trust explicitly:
90
+
91
+ ```bash
92
+ kward --working-directory /path/to/project skills status
93
+ kward --working-directory /path/to/project skills review
94
+ kward --working-directory /path/to/project skills trust
95
+ ```
96
+
97
+ Trust records are stored in `~/.kward/trusted_project_skills.json`. The legacy global setting remains available:
86
98
 
87
99
  ```json
88
100
  {
@@ -92,6 +104,8 @@ Or set it manually:
92
104
  }
93
105
  ```
94
106
 
107
+ In the interactive TUI, skipped or malformed skill diagnostics are shown as synchronized `Runtime>` messages so they do not interrupt screen rendering. Other interactive warnings use the same channel; non-interactive commands continue to report diagnostics on stderr.
108
+
95
109
  ## How Kward uses skills
96
110
 
97
111
  Kward uses progressive loading:
data/doc/tabs.md CHANGED
@@ -61,7 +61,7 @@ Inspect, merge, or remove the binding explicitly:
61
61
 
62
62
  Removal refuses a dirty worktree and keeps its branch. A worktree that is missing or no longer points at the recorded branch is restored as unavailable rather than silently falling back to the original workspace.
63
63
 
64
- Worktree tabs are available for normal session tabs, not plugin-owned tabs. Kward's file tools, `@`/`$` completion, `/files` browser, integrated editor, and model-requested shell workers use the active worktree root. Model operations retain strict workspace guardrails. The interactive `/shell`, `!command`, and `/pty` features remain user-controlled and are not contained by the model command sandbox; use them only when that is intentional. Generic shell Git writes remain protected. When explicitly asked to commit, the agent can use the active tab's narrow `git_commit` tool; use the interactive `/git` flow when you want to review and commit changes yourself.
64
+ Worktree tabs are available for normal session tabs, not plugin-owned tabs. Kward's file tools, `@`/`$` completion, `/files` browser, integrated editor, and model-requested shell workers use the active worktree root. Model operations retain strict workspace guardrails. The user-directed `/shell`, `!command`, `/capture`, and `/pty` features remain host-process operations and are not contained by the model command sandbox; use them only when that is intentional. Generic shell Git writes remain protected. When explicitly asked to commit, the agent can use the active tab's narrow `git_commit` tool; use the interactive `/git` flow when you want to review and commit changes yourself.
65
65
 
66
66
  ## Common workflow
67
67
 
data/doc/usage.md CHANGED
@@ -66,7 +66,7 @@ Inside interactive mode, ask Kward to run a command:
66
66
  Run the focused test for the CLI status command.
67
67
  ```
68
68
 
69
- Or run a shell command yourself from the composer by prefixing it with `!`:
69
+ Or run an interactive PTY command yourself from the composer by prefixing it with `!`. Aliases configured in `ekwsh.yml` are expanded here too:
70
70
 
71
71
  ```text
72
72
  !git status --short
@@ -78,7 +78,7 @@ For several commands, enter the embedded Kward shell:
78
78
  /shell
79
79
  ```
80
80
 
81
- `/shell` opens `ekwsh`, a Kward-native command mode that keeps the tab bar and transcript visible. It preserves state such as the current directory, environment variables, and aliases between commands. Use `pty git log` or `/pty git log` when you intentionally want to hand the terminal to an interactive tool such as `less` or `vim`. See [Embedded shell](shell.md) for built-ins, completion, configuration, ANSI handling, PTY passthrough, and limitations.
81
+ `/shell` opens `ekwsh`, a Kward-native command mode that preserves state such as the current directory, environment variables, and aliases between commands. External commands receive an interactive PTY by default, so `git log`, `less`, Vim, SSH, and REPLs work without a prefix. Use `capture <command>` inside `/shell` or `/capture <command>` from the normal composer for bounded, transcript-friendly output. See [Embedded shell](shell.md) for built-ins, completion, configuration, ANSI handling, PTY passthrough, and limitations.
82
82
 
83
83
  ## Shell commands
84
84
 
@@ -123,8 +123,9 @@ Slash commands run local actions in the current session. Most do not send a prom
123
123
  | `/git` | review uncommitted changes, stage files, and commit. |
124
124
  | `/diff` | open the file changes recorded in the current session. |
125
125
  | `/files` | browse project files in a nested tree and open them in the editor. |
126
- | `/shell` | run workspace commands in the embedded Kward shell. |
127
- | `/pty <command>` | hand the terminal to an interactive command such as `git log`/`less` or `vim`. |
126
+ | `/shell` | run stateful workspace commands with interactive PTY support. |
127
+ | `/capture <command>` | run a bounded one-shot command and retain its output in the transcript. |
128
+ | `/pty <command>` | explicitly hand the terminal to an interactive command; retained for compatibility with existing workflows. |
128
129
  | `/settings` | configure models, accounts, memory, interface, tools, context, personalization, and logging. |
129
130
  | `/status` | see session, model, and context status. |
130
131
  | `/new` | start a fresh session in the current tab. |
@@ -168,7 +169,7 @@ Prompt templates, configured skills, and plugins can add more slash commands. Th
168
169
 
169
170
  ## Prompt history
170
171
 
171
- The [Interactive composer](composer.md) guide covers multiline editing, slash and file completion, reasoning shortcuts, busy input, cancellation, attachments, and terminal compatibility.
172
+ The [Interactive composer](composer.md) guide covers multiline editing, slash and file completion, reasoning selection, busy input, cancellation, attachments, and terminal compatibility.
172
173
 
173
174
  In interactive mode, Kward keeps prompt history per workspace under `~/.kward/history/`. Press Up/Down to recall previous prompts across restarts.
174
175
 
data/lib/kward/agent.rb CHANGED
@@ -28,11 +28,12 @@ module Kward
28
28
  # lowest layer that owns the behavior, and use `Agent` only for cross-step turn
29
29
  # coordination.
30
30
  class Agent
31
- def initialize(client:, tool_registry: ToolRegistry.new, conversation: Conversation.new, telemetry_logger: TelemetryLogger.new, hook_manager: nil, hook_context: nil)
31
+ def initialize(client:, tool_registry: ToolRegistry.new, conversation: Conversation.new, telemetry_logger: nil, warning_sink: nil, hook_manager: nil, hook_context: nil)
32
32
  @client = client
33
33
  @tool_registry = tool_registry
34
34
  @conversation = conversation
35
- @telemetry_logger = telemetry_logger
35
+ @warning_sink = warning_sink
36
+ @telemetry_logger = telemetry_logger || TelemetryLogger.new(warning_sink: warning_sink)
36
37
  @hook_manager = hook_manager
37
38
  @hook_context = hook_context
38
39
  end
@@ -191,9 +192,9 @@ module Kward
191
192
 
192
193
  def auto_compact_if_needed
193
194
  context_window = @client.current_context_window if @client.respond_to?(:current_context_window)
194
- Compactor.new(conversation: @conversation, client: @client).auto_compact_if_needed(context_window: context_window)
195
+ Compactor.new(conversation: @conversation, client: @client, warning_sink: @warning_sink).auto_compact_if_needed(context_window: context_window)
195
196
  rescue StandardError => e
196
- warn "Auto-compaction failed: #{e.message}"
197
+ emit_warning "Auto-compaction failed: #{e.message}"
197
198
  nil
198
199
  end
199
200
 
@@ -201,14 +202,18 @@ module Kward
201
202
  settings = Compaction::Settings.from_config
202
203
  return nil unless settings.enabled
203
204
 
204
- Compactor.new(conversation: @conversation, client: @client, settings: settings).compact(
205
+ Compactor.new(conversation: @conversation, client: @client, settings: settings, warning_sink: @warning_sink).compact(
205
206
  custom_instructions: "The previous model request exceeded the context window. Preserve the current task state and critical details needed to retry."
206
207
  )
207
208
  rescue Compaction::NothingToCompact, Compaction::AlreadyCompacted, StandardError => compaction_error
208
- warn "Context overflow recovery failed: #{compaction_error.message}; original error: #{error.message}"
209
+ emit_warning "Context overflow recovery failed: #{compaction_error.message}; original error: #{error.message}"
209
210
  nil
210
211
  end
211
212
 
213
+ def emit_warning(message)
214
+ @warning_sink ? @warning_sink.call(message) : warn(message)
215
+ end
216
+
212
217
  def chat(on_reasoning_delta: nil, on_retry: nil, cancellation: nil, steering: nil, options: {}, tool_registry: nil)
213
218
  reasoning_delta = lambda do |delta|
214
219
  cancellation&.raise_if_cancelled!