agent-sh 0.15.0 → 0.15.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.
Files changed (116) hide show
  1. package/docs/README.md +14 -0
  2. package/docs/agent.md +398 -0
  3. package/docs/architecture.md +196 -0
  4. package/docs/context-management.md +200 -0
  5. package/docs/extensions.md +951 -0
  6. package/docs/library.md +84 -0
  7. package/docs/troubleshooting.md +65 -0
  8. package/docs/tui-composition.md +294 -0
  9. package/docs/usage.md +306 -0
  10. package/examples/extensions/ash-scheme/package.json +1 -1
  11. package/examples/extensions/ashi/EXTENDING.md +2 -2
  12. package/examples/extensions/ashi/README.md +2 -2
  13. package/examples/extensions/ashi/docs/ui-surface-protocol.md +1 -1
  14. package/examples/extensions/ashi/package.json +5 -3
  15. package/examples/extensions/ashi/src/cli.ts +6 -5
  16. package/examples/extensions/ashi/src/renderer.ts +22 -2
  17. package/examples/extensions/ashi/src/renderers/pi-tui/tool-group.ts +5 -8
  18. package/examples/extensions/ashi-ink/package.json +2 -2
  19. package/examples/extensions/claude-code-bridge/package.json +1 -1
  20. package/examples/extensions/opencode-bridge/package.json +1 -1
  21. package/package.json +3 -1
  22. package/src/agent/agent-loop.ts +1563 -0
  23. package/src/agent/entry-format.ts +19 -0
  24. package/src/agent/events.ts +151 -0
  25. package/src/agent/extensions/rolling-history/constants.ts +1 -0
  26. package/src/agent/extensions/rolling-history/index.ts +202 -0
  27. package/src/agent/extensions/rolling-history/recall.ts +131 -0
  28. package/src/agent/extensions/rolling-history/strategy.ts +404 -0
  29. package/src/agent/host-types.ts +192 -0
  30. package/src/agent/index.ts +591 -0
  31. package/src/agent/live-view.ts +279 -0
  32. package/src/agent/llm-client.ts +111 -0
  33. package/src/agent/llm-facade.ts +43 -0
  34. package/src/agent/normalize-args.ts +61 -0
  35. package/src/agent/nuclear-form.ts +382 -0
  36. package/src/agent/providers/deepseek.ts +39 -0
  37. package/src/agent/providers/ollama.ts +92 -0
  38. package/src/agent/providers/openai-compatible.ts +36 -0
  39. package/src/agent/providers/openai.ts +52 -0
  40. package/src/agent/providers/opencode.ts +142 -0
  41. package/src/agent/providers/openrouter.ts +105 -0
  42. package/src/agent/providers/zai-coding-plan.ts +33 -0
  43. package/src/agent/session-store.ts +336 -0
  44. package/src/agent/skills.ts +228 -0
  45. package/src/agent/store.ts +310 -0
  46. package/src/agent/subagent.ts +305 -0
  47. package/src/agent/system-prompt.ts +151 -0
  48. package/src/agent/token-budget.ts +12 -0
  49. package/src/agent/tool-protocol.ts +722 -0
  50. package/src/agent/tool-registry.ts +66 -0
  51. package/src/agent/tools/bash.ts +95 -0
  52. package/src/agent/tools/edit-file.ts +154 -0
  53. package/src/agent/tools/expand-home.ts +7 -0
  54. package/src/agent/tools/glob.ts +108 -0
  55. package/src/agent/tools/grep.ts +228 -0
  56. package/src/agent/tools/list-skills.ts +37 -0
  57. package/src/agent/tools/ls.ts +81 -0
  58. package/src/agent/tools/pwsh.ts +140 -0
  59. package/src/agent/tools/read-file.ts +164 -0
  60. package/src/agent/tools/write-file.ts +72 -0
  61. package/src/agent/types.ts +149 -0
  62. package/src/cli/args.ts +91 -0
  63. package/src/cli/auth/cli.ts +244 -0
  64. package/src/cli/auth/discover.ts +52 -0
  65. package/src/cli/auth/keys.ts +143 -0
  66. package/src/cli/index.ts +295 -0
  67. package/src/cli/init.ts +74 -0
  68. package/src/cli/install.ts +439 -0
  69. package/src/cli/shell-env.ts +68 -0
  70. package/src/cli/subcommands.ts +24 -0
  71. package/src/core/event-bus.ts +252 -0
  72. package/src/core/extension-loader.ts +347 -0
  73. package/src/core/index.ts +152 -0
  74. package/src/core/settings.ts +398 -0
  75. package/src/core/types.ts +61 -0
  76. package/src/extensions/file-autocomplete.ts +71 -0
  77. package/src/extensions/index.ts +38 -0
  78. package/src/extensions/slash-commands/events.ts +14 -0
  79. package/src/extensions/slash-commands/index.ts +269 -0
  80. package/src/shell/events.ts +73 -0
  81. package/src/shell/host-types.ts +150 -0
  82. package/src/shell/index.ts +159 -0
  83. package/src/shell/input-handler.ts +505 -0
  84. package/src/shell/output-parser.ts +156 -0
  85. package/src/shell/shell-context.ts +193 -0
  86. package/src/shell/shell.ts +414 -0
  87. package/src/shell/strategies/bash.ts +83 -0
  88. package/src/shell/strategies/fish.ts +77 -0
  89. package/src/shell/strategies/index.ts +24 -0
  90. package/src/shell/strategies/types.ts +64 -0
  91. package/src/shell/strategies/zsh.ts +92 -0
  92. package/src/shell/terminal.ts +124 -0
  93. package/src/shell/tui-input-view.ts +222 -0
  94. package/src/shell/tui-renderer.ts +1126 -0
  95. package/src/utils/ansi.ts +140 -0
  96. package/src/utils/box-frame.ts +138 -0
  97. package/src/utils/compositor.ts +157 -0
  98. package/src/utils/diff-renderer.ts +829 -0
  99. package/src/utils/diff.ts +244 -0
  100. package/src/utils/executor.ts +305 -0
  101. package/src/utils/file-watcher.ts +110 -0
  102. package/src/utils/floating-panel.ts +1160 -0
  103. package/src/utils/handler-registry.ts +110 -0
  104. package/src/utils/line-editor.ts +636 -0
  105. package/src/utils/markdown.ts +437 -0
  106. package/src/utils/message-utils.ts +113 -0
  107. package/src/utils/package-version.ts +12 -0
  108. package/src/utils/palette.ts +64 -0
  109. package/src/utils/ref-counter.ts +9 -0
  110. package/src/utils/ripgrep-path.ts +17 -0
  111. package/src/utils/shell-output-spill.ts +76 -0
  112. package/src/utils/stream-transform.ts +292 -0
  113. package/src/utils/terminal-buffer.ts +213 -0
  114. package/src/utils/tool-display.ts +315 -0
  115. package/src/utils/tool-interactive.ts +71 -0
  116. package/src/utils/tty.ts +14 -0
package/docs/usage.md ADDED
@@ -0,0 +1,306 @@
1
+ # Usage Guide
2
+
3
+ ## Running agent-sh
4
+
5
+ The simplest way to run agent-sh — just provide an API key and model:
6
+
7
+ ```bash
8
+ # Using environment variables
9
+ OPENAI_API_KEY="your-key" agent-sh --model gpt-4o
10
+
11
+ # Using CLI flags
12
+ agent-sh --api-key "your-key" --base-url http://localhost:11434/v1 --model llama3
13
+
14
+ # Using npx
15
+ npx agent-sh --api-key "$KEY" --model gpt-4o
16
+ ```
17
+
18
+ Environment variables `OPENAI_API_KEY` and `OPENAI_BASE_URL` are supported as alternatives to CLI flags.
19
+
20
+ ### Other Options
21
+
22
+ ```bash
23
+ # Use a different shell
24
+ agent-sh --shell /bin/zsh
25
+
26
+ # Launch a non-default agent backend (per-session override; doesn't touch settings)
27
+ agent-sh --backend pi
28
+
29
+ # Development mode (no build step)
30
+ npm run dev
31
+
32
+ # Debug mode
33
+ DEBUG=1 agent-sh --api-key "$KEY" --model gpt-4o
34
+ ```
35
+
36
+ ### Subcommands
37
+
38
+ ```bash
39
+ agent-sh init # scaffold ~/.agent-sh/ (settings, examples, AGENTS.md)
40
+ agent-sh install <name> # install a bundled extension (e.g. agent-sh install pi-bridge)
41
+ agent-sh install ./path/to/ext # install from a local path
42
+ agent-sh uninstall <name> # remove an installed extension
43
+ agent-sh list # show extensions discovered from ~/.agent-sh/extensions/ and settings.json
44
+ agent-sh auth login [provider] # store an API key; provider list is discovered from built-ins, settings.json, and any extension that calls ctx.agent.providers.register
45
+ agent-sh auth logout <provider> # remove a stored key
46
+ agent-sh auth list # show configured providers and their key source ("(no auth required)" for local-daemon providers registered with noAuth: true)
47
+ ```
48
+
49
+ Keys stored via `auth` live in `~/.agent-sh/keys.json` (chmod 0600). Resolution order when launching is `settings.json` → `keys.json` → env var, so explicit configuration always wins over the auth store.
50
+
51
+ Any provider you declare under `providers` in `settings.json` is also accepted by `auth login <id>`. This lets you keep custom endpoints in version control (id, baseURL, model list) while the key stays in `keys.json` out of the committable file:
52
+
53
+ ```json
54
+ {
55
+ "providers": {
56
+ "my-llama": {
57
+ "baseURL": "http://localhost:8000/v1",
58
+ "defaultModel": "llama-3.1-70b",
59
+ "models": ["llama-3.1-70b"]
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ```bash
66
+ agent-sh auth login my-llama # prompts for the key, saves to keys.json
67
+ ```
68
+
69
+ `auth login <id>` also accepts ids it doesn't recognize (with a warning). This lets extensions that register their own provider at runtime tell users to run `agent-sh auth login <their-id>` — the key sits in `keys.json` until the extension loads and claims it. Such entries appear in `auth list` tagged `unattached`.
70
+
71
+ `install` accepts a bundled-extension name (see `agent-sh install` with no argument for the list), a `file:`/`./`/absolute path, or — once implemented — `npm:<pkg>` and `github:<user>/<repo>` specs.
72
+
73
+ ## Updating
74
+
75
+ To pick up the latest changes, re-run the install command — npm replaces the global install in place. No uninstall step needed.
76
+
77
+ ```bash
78
+ npm install -g agent-sh@latest # latest npm release (recommended)
79
+ ```
80
+
81
+ For unreleased changes on `main`, use the clone-and-link flow from the [Quick Start](../README.md#quick-start) — `npm install -g github:...` builds on your machine and can fail if the TypeScript toolchain doesn't extract cleanly.
82
+
83
+ ## Provider Examples
84
+
85
+ agent-sh works with any OpenAI-compatible API. Here are common configurations:
86
+
87
+ ### OpenAI
88
+
89
+ ```bash
90
+ export OPENAI_API_KEY="sk-..."
91
+ agent-sh --model gpt-4o
92
+ # or: agent-sh --model gpt-4o-mini
93
+ ```
94
+
95
+ ### DeepSeek
96
+
97
+ ```bash
98
+ export DEEPSEEK_API_KEY="sk-..."
99
+ agent-sh
100
+ ```
101
+
102
+ ### Ollama (Local)
103
+
104
+ ```bash
105
+ # No API key needed — Ollama doesn't require authentication
106
+ agent-sh --api-key dummy --base-url http://localhost:11434/v1 --model llama3
107
+ ```
108
+
109
+ ### OpenRouter
110
+
111
+ ```bash
112
+ agent-sh --api-key "$OPENROUTER_KEY" \
113
+ --base-url https://openrouter.ai/api/v1 \
114
+ --model anthropic/claude-sonnet-4-20250514
115
+ ```
116
+
117
+ ### Together AI
118
+
119
+ ```bash
120
+ agent-sh --api-key "$TOGETHER_KEY" \
121
+ --base-url https://api.together.xyz/v1 \
122
+ --model meta-llama/Llama-3-70b-chat-hf
123
+ ```
124
+
125
+ ### Groq
126
+
127
+ ```bash
128
+ agent-sh --api-key "$GROQ_KEY" \
129
+ --base-url https://api.groq.com/openai/v1 \
130
+ --model llama-3.3-70b-versatile
131
+ ```
132
+
133
+ ### LM Studio
134
+
135
+ ```bash
136
+ agent-sh --api-key dummy \
137
+ --base-url http://localhost:1234/v1 \
138
+ --model local-model
139
+ ```
140
+
141
+ ### vLLM
142
+
143
+ ```bash
144
+ agent-sh --api-key dummy \
145
+ --base-url http://localhost:8000/v1 \
146
+ --model your-model
147
+ ```
148
+
149
+ ## Using agent-sh as Your Default Shell
150
+
151
+ Add to the end of your `~/.zshrc` or `~/.bashrc`:
152
+
153
+ ```bash
154
+ if [[ -z "$AGENT_SH" && $- == *i* && -t 0 ]]; then
155
+ exec agent-sh --api-key "$OPENAI_API_KEY" --model gpt-4o
156
+ fi
157
+ ```
158
+
159
+ The `AGENT_SH` guard prevents infinite recursion. The checks ensure it only launches for interactive terminal sessions.
160
+
161
+ ## Configuration
162
+
163
+ agent-sh stores settings and query history in `~/.agent-sh/`. Configure via `~/.agent-sh/settings.json` — all fields are optional with sensible defaults.
164
+
165
+ ### Provider Profiles
166
+
167
+ Instead of passing `--api-key` and `--base-url` every time, define named providers in settings.json:
168
+
169
+ ```json
170
+ {
171
+ "defaultProvider": "openai",
172
+ "providers": {
173
+ "openai": {
174
+ "apiKey": "$OPENAI_API_KEY",
175
+ "defaultModel": "gpt-4o",
176
+ "models": ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"],
177
+ "contextWindow": 128000
178
+ },
179
+ "ollama": {
180
+ "apiKey": "not-needed",
181
+ "baseURL": "http://localhost:11434/v1",
182
+ "defaultModel": "llama3",
183
+ "models": ["llama3", "mistral", "codellama"]
184
+ },
185
+ "openrouter": {
186
+ "apiKey": "$OPENROUTER_KEY",
187
+ "baseURL": "https://openrouter.ai/api/v1",
188
+ "defaultModel": "anthropic/claude-sonnet-4.5",
189
+ "models": [
190
+ { "id": "anthropic/claude-sonnet-4.5", "contextWindow": 200000, "reasoning": true },
191
+ { "id": "google/gemini-2.5-pro", "contextWindow": 1000000 }
192
+ ]
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ Then just run:
199
+
200
+ ```bash
201
+ agent-sh # uses defaultProvider
202
+ agent-sh --provider ollama # use a specific provider
203
+ agent-sh --provider openai --model gpt-4-turbo # override the default model
204
+ ```
205
+
206
+ The `apiKey` field supports `$ENV_VAR` and `${ENV_VAR}` syntax — variables are expanded at runtime, so you don't store secrets in the file.
207
+
208
+ ### Declaring the context window
209
+
210
+ agent-sh adapts its auto-compaction trigger to the model's context window. There are two places to declare it:
211
+
212
+ - **Provider-level `contextWindow`** — applies to every model in that provider unless a more specific value is set.
213
+ - **Per-model `contextWindow`** (inside an entry of `models`) — overrides the provider-level value for a specific model, and also lets you tag reasoning-capable models via `reasoning: true`.
214
+
215
+ If neither is set, agent-sh falls back to a conservative 60k-token default.
216
+
217
+ Entries in `models` can be plain strings (just the model id, uses the provider-level `contextWindow`) or objects:
218
+
219
+ ```json
220
+ "models": [
221
+ "gpt-4o-mini",
222
+ { "id": "gpt-4o", "contextWindow": 128000 },
223
+ { "id": "o1-preview", "contextWindow": 128000, "reasoning": true }
224
+ ]
225
+ ```
226
+
227
+ ### Switching models at runtime
228
+
229
+ - **`/model`** — show the current model
230
+ - **`/model <name>`** — switch to a specific model (may cross providers; API key and base URL are reconfigured automatically)
231
+
232
+ Switching mid-conversation preserves your conversation state — only the LLM endpoint changes.
233
+
234
+ ### CLI Flags
235
+
236
+ | Flag | Environment Variable | Description |
237
+ |---|---|---|
238
+ | `--provider <name>` | — | Use a named provider from settings.json |
239
+ | `--model <name>` | — | Model name (overrides provider default) |
240
+ | `--api-key <key>` | `OPENAI_API_KEY` | API key for OpenAI-compatible API |
241
+ | `--base-url <url>` | `OPENAI_BASE_URL` | Base URL for API endpoint |
242
+ | `--shell <path>` | `SHELL` | Shell to use (default: `/bin/bash`) |
243
+ | `--backend <name>` | — | Agent backend to launch (e.g. `ash`, `pi`); per-session override of `settings.defaultBackend`, does not persist. Errors out if the named backend isn't registered. |
244
+ | `-e, --extensions` | — | Extensions to load (comma-separated, repeatable) |
245
+
246
+ **Precedence** (highest to lowest): CLI flags → environment variables → provider profile in settings.json → defaults.
247
+
248
+ ### General Settings
249
+
250
+ | Setting | Default | Description |
251
+ |---|---|---|
252
+ | `defaultProvider` | — | Which provider to use when no `--provider` flag is given |
253
+ | `defaultBackend` | `"ash"` | Which agent backend to activate. Set to an extension backend name (e.g. `"claude-code"`, `"pi"`) to use it by default |
254
+ | `extensions` | `[]` | Extensions to load (npm packages or file paths) |
255
+ | `historySize` | `500` | Max agent query history entries (persisted across sessions) |
256
+ | `shellTruncateThreshold` | `20` | Shell output lines before spill-to-tempfile |
257
+ | `shellHeadLines` / `shellTailLines` | `10` / `10` | Lines kept from start/end when output is spilled |
258
+ | `autoCompactThreshold` | `0.5` | Fraction of the model's context window at which conversation auto-compacts |
259
+ | `historyMaxBytes` | `104857600` | Max size of `~/.agent-sh/history` before front-truncation (100MB) |
260
+ | `historyStartupEntries` | `100` | Prior history entries injected as `[Prior session history]` preamble on launch |
261
+ | `maxCommandOutputLines` | `3` | Max tool output lines shown inline in TUI |
262
+ | `readOutputMaxLines` | `10` | Max read tool output lines shown inline (0 = hidden) |
263
+ | `diffMaxLines` | `Infinity` | Max diff lines rendered in the TUI. Defaults to no limit |
264
+ | `skillPaths` | `[]` | Extra directories to scan for skills (supports `~` expansion) |
265
+ | `diagnose` | `false` | Enable the `diagnose` tool — lets the agent evaluate JS expressions against its own runtime state (introspection; agent already has bash, so this is convenience, not new capability) |
266
+ | `startupBanner` | `true` | Show the startup banner (backend / model / extensions / skills) on launch |
267
+ | `promptIndicator` | `true` | Show a subtle agent-sh indicator in the shell prompt |
268
+ | `toolMode` | `"api"` | How tools are presented to the LLM. `"api"` sends all tool schemas. `"deferred"` bundles extension tools behind a `use_extension(name, args)` meta-tool (saves prompt tokens, loses schema fidelity). `"deferred-lookup"` keeps extension schemas dormant until the model calls `load_tool(names[])` — loaded tools then become first-class on the next turn with full schemas. `"inline"` describes tools as text. |
269
+ | `disabledExtensions` | `[]` | Names of user extensions in `~/.agent-sh/extensions/` to skip when auto-discovering. Match by basename without extension for files (`"peer-mesh"` matches `peer-mesh.ts`) or by directory name for dir-style extensions (`"superash"` matches `superash/index.ts`). Avoids having to rename files to `.disabled`. |
270
+ | `disabledBuiltins` | `[]` | Names of built-in extensions to disable. |
271
+
272
+ ## Startup Banner
273
+
274
+ On launch, agent-sh displays a structured startup banner showing:
275
+
276
+ - **Backend** — which agent backend is active (`ash`, `claude-code`, `pi`, etc.)
277
+ - **Model** — current model with provider in brackets (e.g. `gpt-4o [openai]`)
278
+ - **Extensions** — loaded extensions (from CLI `-e`, settings, or `~/.agent-sh/extensions/`)
279
+ - **Skills** — discovered skills (global + project)
280
+
281
+ Set `startupBanner: false` in settings to disable.
282
+
283
+ ## Shell Context
284
+
285
+ The agent automatically receives structured context about your shell session with each query:
286
+
287
+ - **Current working directory** — tracked via OSC 7 escape sequences
288
+ - **Recent commands and output** — new shell activity since the last turn is wrapped as `<shell_events>` inside `<query_context>` and prepended to your query
289
+ - **Long outputs are spilled to tempfiles** — outputs over `shellTruncateThreshold` lines are written to `<tmpdir>/agent-sh-<pid>/<id>.out` at capture; the agent sees head+tail plus the path and recovers the full text via the built-in `read_file` tool
290
+
291
+ This means you can run a failing command, then type `> fix this` and the agent knows exactly what happened — including a pointer to the full output if it got truncated. See [Context Management](context-management.md) for the full design.
292
+
293
+ ## Slash Commands
294
+
295
+ | Command | Description |
296
+ |---|---|
297
+ | `/help` | Show available commands |
298
+ | `/model [name]` | Show current model; with a name, switch to that model |
299
+ | `/backend [name]` | List backends, or switch to a named backend |
300
+ | `/thinking [level]` | Set reasoning effort (off, low, medium, high) |
301
+ | `/compact` | Compact conversation (free up context space) |
302
+ | `/context` | Show context budget usage (active tokens vs. budget) |
303
+ | `/history [on\|off\|status]` | Pause/resume cross-session history writes for this session (no cache invalidation) |
304
+ | `/reload` | Reload user extensions from `~/.agent-sh/extensions/` |
305
+
306
+ See [Context Management](context-management.md) for how `/compact` and `/context` work, and [Extensions: Custom Agent Backends](extensions.md#custom-agent-backends) for `/backend`.
@@ -6,6 +6,6 @@
6
6
  "main": "index.ts",
7
7
  "dependencies": {
8
8
  "@jcubic/lips": "1.0.0-beta.20",
9
- "agent-sh": "^0.14.0"
9
+ "agent-sh": "^0.15.0"
10
10
  }
11
11
  }
@@ -58,7 +58,7 @@ const myModel: RenderModel<...> = {
58
58
  ## Renderers
59
59
 
60
60
  The whole TUI is swappable. ashi (the substrate) depends only on the `Renderer`
61
- contract from [`@guanyilun/ashi/renderer`](src/renderer.ts) — the schema, theme,
61
+ contract from [`@guanyilun/ashi/renderer`](https://github.com/guanyilun/agent-sh/blob/main/examples/extensions/ashi/src/renderer.ts) — the schema, theme,
62
62
  chat controllers, and frontend never import a concrete TUI library. The built-in
63
63
  renderer is pi-tui (`src/renderers/pi-tui`).
64
64
 
@@ -113,6 +113,6 @@ agent-sh's shell clears OPOST on boot (pi-tui emits its own `\r`); ashi reads
113
113
  gets the conventional terminal for free; only a raw driver like pi-tui sets
114
114
  `rawOutput: true`.
115
115
 
116
- See [`examples/extensions/ashi-ink`](../ashi-ink) for a worked example — a **working**
116
+ See [`examples/extensions/ashi-ink`](https://github.com/guanyilun/agent-sh/tree/main/examples/extensions/ashi-ink) for a worked example — a **working**
117
117
  Ink (React) renderer (`ASHI_RENDERER=ink ashi -e ashi-ink`), verified with
118
118
  `ink-testing-library`.
@@ -13,7 +13,7 @@ Rendering is **decoupled** — even *how* ashi draws tool calls and results is a
13
13
  |---|---|
14
14
  | ![ashi rendering tool calls pi-style](https://raw.githubusercontent.com/guanyilun/agent-sh/main/assets/ashi-pi-style.png) | ![ashi rendering tool calls claude-code-style](https://raw.githubusercontent.com/guanyilun/agent-sh/main/assets/ashi-claude-code-style.png) |
15
15
 
16
- The claude-code-style renderer is [ashi-ink](../ashi-ink), a working Ink (React) renderer; see [Extending ashi](#extending-ashi) for the contract.
16
+ The claude-code-style renderer is [ashi-ink](https://github.com/guanyilun/agent-sh/tree/main/examples/extensions/ashi-ink), a working Ink (React) renderer; see [Extending ashi](#extending-ashi) for the contract.
17
17
 
18
18
  ## Install
19
19
 
@@ -159,7 +159,7 @@ Each tool inherits from `default` and is overridden by its own block. Unknown to
159
159
  ## Extending ashi
160
160
 
161
161
  Other extensions can customize chat and tool-result rendering — and even swap the whole
162
- TUI renderer (pi-tui, [Ink](../ashi-ink), …) — without forking ashi. See **[EXTENDING.md](EXTENDING.md)**
162
+ TUI renderer (pi-tui, [Ink](https://github.com/guanyilun/agent-sh/tree/main/examples/extensions/ashi-ink), …) — without forking ashi. See **[EXTENDING.md](EXTENDING.md)**
163
163
  for the chat/tool render hooks, the declarative tool render schema, and the renderer
164
164
  contract. For non-render concerns (commands, settings, tools, providers), use the
165
165
  standard [agent-sh extension API](https://github.com/guanyilun/agent-sh/blob/main/docs/extensions.md).
@@ -152,7 +152,7 @@ contract has no free-placement layer yet. Use the dock, dialogs, and notices abo
152
152
 
153
153
  ## Working example
154
154
 
155
- [`ashi-ui-demo.ts`](../../ashi-ui-demo.ts) exercises every surface through the typed helper. Load
155
+ [`ashi-ui-demo.ts`](https://github.com/guanyilun/agent-sh/blob/main/examples/extensions/ashi-ui-demo.ts) exercises every surface through the typed helper. Load
156
156
  it and try the commands:
157
157
 
158
158
  ```
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanyilun/ashi",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Ash in an interactive TUI — agent-sh's built-in agent without the shell underneath",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -28,7 +28,9 @@
28
28
  },
29
29
  "files": [
30
30
  "dist",
31
- "README.md"
31
+ "README.md",
32
+ "EXTENDING.md",
33
+ "docs"
32
34
  ],
33
35
  "scripts": {
34
36
  "dev": "tsx src/cli.ts",
@@ -68,7 +70,7 @@
68
70
  },
69
71
  "dependencies": {
70
72
  "@earendil-works/pi-tui": "^0.74.0",
71
- "agent-sh": "^0.14.11",
73
+ "agent-sh": "^0.15.0",
72
74
  "chalk": "^5.5.0",
73
75
  "cli-highlight": "^2.1.11"
74
76
  },
@@ -180,7 +180,7 @@ async function main(): Promise<void> {
180
180
 
181
181
  activateAgent(ctx);
182
182
  activateShellContext(ctx);
183
- const builtinExtensions = await loadBuiltinExtensions(ctx);
183
+ await loadBuiltinExtensions(ctx);
184
184
 
185
185
  const shell = new Shell({
186
186
  bus: core.bus,
@@ -263,10 +263,11 @@ async function main(): Promise<void> {
263
263
  await handle.rebuildChat();
264
264
  ctx.bus.emit("ui:info", { message: `continued session ${resumeId.slice(0, 12)}…` });
265
265
  } else {
266
- // New-session only: skip on resume so a restored transcript isn't prefixed with this.
267
- const loadedExtensions = [...new Set([...builtinExtensions, ...loaded])];
268
- if (loadedExtensions.length > 0) {
269
- ctx.bus.emit("ui:info", { message: `extensions: ${loadedExtensions.join(" · ")}` });
266
+ // New-session only: skip on resume so a restored transcript isn't prefixed
267
+ // with this. List user/installed extensions only — built-ins are always present.
268
+ const userExtensions = [...new Set(loaded)];
269
+ if (userExtensions.length > 0) {
270
+ ctx.bus.emit("ui:info", { message: `extensions: ${userExtensions.join(" · ")}` });
270
271
  }
271
272
  }
272
273
 
@@ -167,7 +167,17 @@ export interface ToolGroupView {
167
167
  }
168
168
 
169
169
  /** Default tool-group rendering (`├`/`└` tree): one styled line per row, no indent. */
170
- export function renderToolGroupLines(model: ToolGroupModel): string[] {
170
+ /** Tail-biased middle truncation — keeps the end (e.g. a filename) visible. */
171
+ function truncateMiddle(s: string, max: number): string {
172
+ if (max <= 0) return "";
173
+ if (s.length <= max) return s;
174
+ if (max === 1) return "…";
175
+ const keep = max - 1;
176
+ const tail = Math.ceil(keep * 0.65);
177
+ return (keep - tail > 0 ? s.slice(0, keep - tail) : "") + "…" + s.slice(s.length - tail);
178
+ }
179
+
180
+ export function renderToolGroupLines(model: ToolGroupModel, width?: number): string[] {
171
181
  const lines: string[] = [
172
182
  segmentsToString([
173
183
  { text: model.icon, style: { color: "warning" } },
@@ -187,7 +197,17 @@ export function renderToolGroupLines(model: ToolGroupModel): string[] {
187
197
  model.children.forEach((child, idx) => {
188
198
  const segs: Segment[] = [{ text: idx === model.children.length - 1 ? "└" : "├", style: { color: "muted" } }, " "];
189
199
  if (child.name !== model.kind) segs.push({ text: child.name, style: { bold: true, color: "toolTitle" } }, " ");
190
- segs.push({ text: child.detail, style: { color: "muted" } }, " ");
200
+ let detail = child.detail;
201
+ if (width && width > 0) {
202
+ // Everything else on the row is fixed-width; truncate the detail (path/
203
+ // command) so the row stays one line instead of wrapping.
204
+ let overhead = 3; // tree char + space, and the space after detail
205
+ if (child.name !== model.kind) overhead += child.name.length + 1;
206
+ if (!child.status) overhead += 2;
207
+ else overhead += 2 + (child.status.summary ? 1 + child.status.summary.length : 0);
208
+ detail = truncateMiddle(detail, Math.max(8, width - overhead));
209
+ }
210
+ segs.push({ text: detail, style: { color: "muted" } }, " ");
191
211
  if (!child.status) {
192
212
  segs.push(" ", { text: "…", style: { color: "muted" } });
193
213
  } else {
@@ -3,18 +3,15 @@ import { createNodes } from "./nodes.js";
3
3
 
4
4
  export function createPiTuiToolGroup(): ToolGroupView {
5
5
  const nodes = createNodes();
6
- const rows = nodes.container();
7
6
  const container = nodes.container();
8
7
  container.addChild(nodes.spacer(1));
9
- container.addChild(rows.node);
8
+ const text = nodes.text({ paddingX: 1 });
9
+ container.addChild(text.node);
10
10
 
11
11
  const update = (model: ToolGroupModel): void => {
12
- rows.clear();
13
- for (const line of renderToolGroupLines(model)) {
14
- const t = nodes.text({ paddingX: 1 });
15
- t.setText(line);
16
- rows.addChild(t.node);
17
- }
12
+ // paddingX:1 on both sides → content area is width-2; render width-aware so
13
+ // long paths truncate to fit rather than wrap.
14
+ text.setRenderFn((width) => renderToolGroupLines(model, Math.max(1, width - 2)));
18
15
  };
19
16
 
20
17
  return { node: container.node, update };
@@ -11,8 +11,8 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@earendil-works/pi-tui": "^0.74.0",
14
- "@guanyilun/ashi": "^0.2.0",
15
- "agent-sh": "^0.14.0",
14
+ "@guanyilun/ashi": "^0.3.0",
15
+ "agent-sh": "^0.15.0",
16
16
  "cli-table3": "^0.6.0",
17
17
  "ink": "^5.0.0",
18
18
  "ink-spinner": "^5.0.0",
@@ -6,7 +6,7 @@
6
6
  "main": "index.ts",
7
7
  "dependencies": {
8
8
  "@anthropic-ai/claude-agent-sdk": "^0.2.0",
9
- "agent-sh": "^0.14.0",
9
+ "agent-sh": "^0.15.0",
10
10
  "zod": "^4.0.0"
11
11
  }
12
12
  }
@@ -6,6 +6,6 @@
6
6
  "main": "index.ts",
7
7
  "dependencies": {
8
8
  "@opencode-ai/sdk": "^1.14.41",
9
- "agent-sh": "^0.14.0"
9
+ "agent-sh": "^0.15.0"
10
10
  }
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "A composable agent runtime — pair any frontend with any agent backend over one shared extension layer",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -140,6 +140,8 @@
140
140
  },
141
141
  "files": [
142
142
  "dist",
143
+ "src",
144
+ "docs",
143
145
  "examples/extensions"
144
146
  ],
145
147
  "scripts": {