@dungle-scrubs/tallow 0.8.5 → 0.8.6
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.
- package/README.md +108 -209
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/extensions/claude-bridge/index.ts +2 -1
- package/extensions/wezterm-notify/extension.json +15 -0
- package/extensions/wezterm-notify/index.ts +86 -0
- package/extensions/wezterm-notify/wezterm/tallow.lua +197 -0
- package/package.json +1 -1
- package/skills/tallow-expert/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1 align="center">Tallow</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
|
|
8
|
+
A modular coding agent for your terminal. Built on <a href="https://github.com/nicobrinkkemper/pi-coding-agent">pi</a>.
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -23,64 +23,35 @@
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
|
-
<img src="assets/screenshot.
|
|
26
|
+
<img src="assets/screenshot-annotated.png" alt="Tallow session showing status bar, auto-named session, and model selection" />
|
|
27
|
+
<br />
|
|
28
|
+
<sub>Shown with a customized <a href="https://wezfurlong.org/wezterm/">WezTerm</a> configuration.</sub>
|
|
27
29
|
</p>
|
|
28
30
|
|
|
29
|
-
Tallow is
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
issue and PR response times.
|
|
31
|
+
Tallow is a terminal coding agent that starts minimal and scales up. Install only the
|
|
32
|
+
extensions, themes, and agents your project needs, or enable everything. It drops into
|
|
33
|
+
existing Claude Code projects via `.claude/` bridging, so nothing breaks when you switch.
|
|
34
|
+
Ships with 50 extensions, 34 themes, and 10 specialized agents.
|
|
34
35
|
|
|
35
|
-
##
|
|
36
|
-
|
|
37
|
-
- **Most valuable capabilities (non-exhaustive):**
|
|
38
|
-
- **Multi-model routing** — route work by intent/cost (`auto-cheap`, `auto-balanced`,
|
|
39
|
-
`auto-premium`) across available providers
|
|
40
|
-
- **Multi-agent teams** — coordinate specialized agents with shared task boards,
|
|
41
|
-
dependencies, messaging, and archive/resume
|
|
42
|
-
- **Context fork** — run isolated subprocess workflows with separate tools/models and
|
|
43
|
-
merge results back cleanly
|
|
44
|
-
- **Workspace rewind snapshots** — roll file changes back to earlier conversation turns
|
|
45
|
-
- **Task primitives + background execution** — explicit task lifecycle tracking and
|
|
46
|
-
non-blocking long-running work
|
|
47
|
-
- **Built-in LSP navigation** — definitions, references, hover, and workspace symbol
|
|
48
|
-
lookup
|
|
49
|
-
- **Opt-in and modular** — install only the pieces you need, skip the rest
|
|
50
|
-
- **Claude Code compatible** — `.claude/` + `.tallow/` directories are bridged so existing
|
|
51
|
-
project workflows keep working
|
|
52
|
-
- **Fully featured when you want it** — 49 bundled extensions, 34 themes, 8 slash commands,
|
|
53
|
-
and 10 specialized agents
|
|
54
|
-
- **Session naming** — auto-generated descriptive names for each session, shown in footer
|
|
55
|
-
and `--list`
|
|
56
|
-
- **Debug mode** — structured JSONL diagnostic logging with `/diag` command
|
|
57
|
-
- **SDK** — embed Tallow in your own scripts and orchestrators
|
|
58
|
-
- **User-owned config** — agents and commands are installed to `~/.tallow/` where you can
|
|
59
|
-
edit, remove, or add your own
|
|
60
|
-
|
|
61
|
-
Read the full [documentation](https://tallow.dungle-scrubs.com).
|
|
62
|
-
|
|
63
|
-
## Requirements
|
|
64
|
-
|
|
65
|
-
- Node.js ≥ 22
|
|
66
|
-
- An API key for at least one supported LLM provider (Anthropic, OpenAI, Google, etc.)
|
|
67
|
-
|
|
68
|
-
## Installation
|
|
69
|
-
|
|
70
|
-
### Global install
|
|
36
|
+
## Quick start
|
|
71
37
|
|
|
72
38
|
```bash
|
|
73
|
-
bun install -g tallow
|
|
74
|
-
tallow install
|
|
39
|
+
npm install -g tallow # or: pnpm add -g tallow / bun install -g tallow
|
|
40
|
+
tallow install # pick extensions, themes, agents
|
|
41
|
+
tallow # start coding
|
|
75
42
|
```
|
|
76
43
|
|
|
77
|
-
Or without
|
|
44
|
+
Or try it without installing globally:
|
|
78
45
|
|
|
79
46
|
```bash
|
|
80
|
-
bunx tallow install
|
|
47
|
+
npx tallow install # or: pnpm dlx tallow install / bunx tallow install
|
|
81
48
|
```
|
|
82
49
|
|
|
83
|
-
|
|
50
|
+
> Requires Node.js ≥ 22 and an API key for at least one LLM provider
|
|
51
|
+
> (Anthropic, OpenAI, Google, etc.)
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary><strong>Install from source</strong></summary>
|
|
84
55
|
|
|
85
56
|
```bash
|
|
86
57
|
git clone https://github.com/dungle-scrubs/tallow.git
|
|
@@ -93,121 +64,127 @@ node dist/install.js
|
|
|
93
64
|
The installer walks you through selecting extensions, themes, and agents,
|
|
94
65
|
then links the `tallow` binary globally.
|
|
95
66
|
|
|
67
|
+
</details>
|
|
68
|
+
|
|
69
|
+
## Highlights
|
|
70
|
+
|
|
71
|
+
**Multi-model routing** — Route tasks by intent and cost across providers.
|
|
72
|
+
`auto-cheap` for boilerplate, `auto-balanced` for everyday work, `auto-premium`
|
|
73
|
+
when accuracy matters.
|
|
74
|
+
|
|
75
|
+
**Multi-agent teams** — Spawn specialized agents that share a task board with
|
|
76
|
+
dependencies, messaging, and archive/resume. Coordinate complex work across
|
|
77
|
+
multiple models in parallel.
|
|
78
|
+
|
|
79
|
+
**Context fork** — Branch into an isolated subprocess with its own tools and model,
|
|
80
|
+
then merge results back into the main session.
|
|
81
|
+
|
|
82
|
+
**Workspace rewind** — Every conversation turn snapshots your file changes. Roll back
|
|
83
|
+
to any earlier turn when something goes wrong.
|
|
84
|
+
|
|
85
|
+
**Background tasks** — Kick off long-running work without blocking the session.
|
|
86
|
+
Track task lifecycle explicitly and check back when ready.
|
|
87
|
+
|
|
88
|
+
**LSP** — Jump to definitions, find references, inspect types, and search
|
|
89
|
+
workspace symbols — no editor required.
|
|
90
|
+
|
|
91
|
+
**Claude Code compatible** — Projects with `.claude/` directories (skills, agents,
|
|
92
|
+
commands) work without changes. Both `.tallow/` and `.claude/` are scanned;
|
|
93
|
+
`.tallow/` takes precedence.
|
|
94
|
+
|
|
95
|
+
**User-owned config** — Agents, commands, and extensions install to `~/.tallow/`
|
|
96
|
+
where you own them. Edit, remove, or add your own.
|
|
97
|
+
|
|
96
98
|
## Usage
|
|
97
99
|
|
|
98
100
|
```bash
|
|
99
|
-
# Interactive
|
|
101
|
+
# Interactive session
|
|
100
102
|
tallow
|
|
101
103
|
|
|
102
104
|
# Single-shot prompt
|
|
103
105
|
tallow -p "Fix the failing tests"
|
|
104
106
|
|
|
105
|
-
# Pipe
|
|
106
|
-
echo "Explain this error" | tallow
|
|
107
|
-
cat README.md | tallow -p "Summarize this"
|
|
107
|
+
# Pipe in context
|
|
108
108
|
git diff | tallow -p "Review these changes"
|
|
109
|
+
cat src/main.ts | tallow -p "Find bugs in this code"
|
|
109
110
|
|
|
110
|
-
# Continue
|
|
111
|
+
# Continue the last session
|
|
111
112
|
tallow --continue
|
|
112
113
|
|
|
113
|
-
#
|
|
114
|
-
tallow -m anthropic/claude-sonnet-4-20250514
|
|
115
|
-
|
|
116
|
-
# Set thinking level
|
|
117
|
-
tallow --thinking high
|
|
118
|
-
|
|
119
|
-
# Run without persisting session
|
|
120
|
-
tallow --no-session
|
|
114
|
+
# Pick a model and thinking level
|
|
115
|
+
tallow -m anthropic/claude-sonnet-4-20250514 --thinking high
|
|
121
116
|
|
|
122
|
-
#
|
|
123
|
-
tallow -e ./my-extension
|
|
124
|
-
|
|
125
|
-
# List saved sessions (shows auto-generated names)
|
|
117
|
+
# List saved sessions
|
|
126
118
|
tallow --list
|
|
127
119
|
|
|
128
|
-
# Runtime auth via environment (not persisted)
|
|
129
|
-
TALLOW_API_KEY=sk-ant-... tallow --provider anthropic
|
|
130
|
-
|
|
131
|
-
# Runtime auth via reference (resolved at runtime)
|
|
132
|
-
TALLOW_API_KEY_REF=op://Services/Anthropic/api-key tallow --provider anthropic
|
|
133
|
-
|
|
134
|
-
# Run in RPC or JSON mode
|
|
135
|
-
tallow --mode rpc
|
|
136
|
-
|
|
137
120
|
# Restrict available tools
|
|
138
|
-
tallow --tools
|
|
139
|
-
tallow --tools
|
|
140
|
-
|
|
121
|
+
tallow --tools readonly # read, grep, find, ls only
|
|
122
|
+
tallow --tools none # chat only, no tools
|
|
123
|
+
```
|
|
141
124
|
|
|
142
|
-
|
|
143
|
-
|
|
125
|
+
See the [full CLI reference](https://tallow.dungle-scrubs.com) for all flags
|
|
126
|
+
and modes (RPC, JSON, piped stdin, shell interpolation, etc.)
|
|
144
127
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
128
|
+
## Configuration
|
|
129
|
+
|
|
130
|
+
Tallow stores its configuration in `~/.tallow/`:
|
|
148
131
|
|
|
149
|
-
|
|
150
|
-
|
|
132
|
+
| Path | Purpose |
|
|
133
|
+
|------|---------|
|
|
134
|
+
| `settings.json` | Global settings (theme, icons, keybindings) |
|
|
135
|
+
| `.env` | Environment variables loaded at startup (supports `op://` refs) |
|
|
136
|
+
| `auth.json` | Provider auth references (see [SECURITY.md](SECURITY.md)) |
|
|
137
|
+
| `models.json` | Model configuration |
|
|
138
|
+
| `agents/` | Agent profiles — yours to edit |
|
|
139
|
+
| `commands/` | Slash commands — yours to edit |
|
|
140
|
+
| `extensions/` | User extensions (override bundled ones by name) |
|
|
141
|
+
| `sessions/` | Persisted conversation sessions |
|
|
151
142
|
|
|
152
|
-
|
|
143
|
+
Project-level overrides live in `.tallow/` within your repo.
|
|
153
144
|
|
|
154
|
-
|
|
145
|
+
## Extending Tallow
|
|
155
146
|
|
|
156
|
-
|
|
157
|
-
# Stdin becomes the prompt
|
|
158
|
-
echo "What is 2+2?" | tallow
|
|
147
|
+
### Themes
|
|
159
148
|
|
|
160
|
-
|
|
161
|
-
cat src/main.ts | tallow -p "Find bugs in this code"
|
|
149
|
+
Switch themes in-session with `/theme`, or set a default:
|
|
162
150
|
|
|
163
|
-
|
|
164
|
-
|
|
151
|
+
```json
|
|
152
|
+
{ "theme": "tokyo-night" }
|
|
165
153
|
```
|
|
166
154
|
|
|
167
|
-
|
|
168
|
-
If both stdin and `-p` are provided, stdin is prepended as context before the
|
|
169
|
-
prompt. Piped input is capped at 10 MB. JSON mode (`--mode json`) also
|
|
170
|
-
accepts piped stdin.
|
|
155
|
+
### Icons
|
|
171
156
|
|
|
172
|
-
|
|
157
|
+
Override any TUI glyph in `settings.json` — only the keys you set change:
|
|
173
158
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
```
|
|
177
|
-
!`ls -la`
|
|
178
|
-
!`git status`
|
|
179
|
-
!`git branch --show-current`
|
|
159
|
+
```json
|
|
160
|
+
{ "icons": { "success": "✔", "error": "✘" } }
|
|
180
161
|
```
|
|
181
162
|
|
|
182
|
-
|
|
163
|
+
See the [icon reference](https://tallow.dungle-scrubs.com/getting-started/icons/) for all keys.
|
|
183
164
|
|
|
184
|
-
|
|
185
|
-
- `"shellInterpolation": true` in `.tallow/settings.json` or `~/.tallow/settings.json`
|
|
165
|
+
### Writing extensions
|
|
186
166
|
|
|
187
|
-
|
|
188
|
-
replaces the pattern before reaching the agent. 5-second timeout, 1 MB
|
|
189
|
-
max output, non-recursive. High-risk explicit shell commands require
|
|
190
|
-
confirmation (`TALLOW_ALLOW_UNSAFE_SHELL=1` bypasses confirmation in
|
|
191
|
-
non-interactive environments).
|
|
167
|
+
Extensions are TypeScript files that receive the pi `ExtensionAPI`:
|
|
192
168
|
|
|
193
|
-
|
|
169
|
+
```typescript
|
|
170
|
+
import type { ExtensionAPI } from "tallow";
|
|
194
171
|
|
|
195
|
-
|
|
172
|
+
export default function myExtension(api: ExtensionAPI): void {
|
|
173
|
+
api.registerCommand("greet", {
|
|
174
|
+
description: "Say hello",
|
|
175
|
+
handler: async (_args, ctx) => {
|
|
176
|
+
ctx.ui.notify("Hello from my extension!", "info");
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
```
|
|
196
181
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
| `/implement` | Implement a feature from a description |
|
|
200
|
-
| `/implement-and-review` | Implement then self-review |
|
|
201
|
-
| `/review` | Review recent changes |
|
|
202
|
-
| `/fix` | Fix a bug from a description |
|
|
203
|
-
| `/test` | Write or fix tests |
|
|
204
|
-
| `/scout-and-plan` | Explore the codebase and create a plan |
|
|
205
|
-
| `/scaffold` | Scaffold a new project |
|
|
206
|
-
| `/question` | Introspect on agent reasoning without triggering actions |
|
|
182
|
+
Place it in `~/.tallow/extensions/my-extension/index.ts`. If it shares a name
|
|
183
|
+
with a bundled extension, yours takes precedence.
|
|
207
184
|
|
|
208
|
-
|
|
185
|
+
### SDK
|
|
209
186
|
|
|
210
|
-
|
|
187
|
+
Embed Tallow in your own scripts:
|
|
211
188
|
|
|
212
189
|
```typescript
|
|
213
190
|
import { createTallowSession } from "tallow";
|
|
@@ -227,99 +204,21 @@ await session.prompt("What files are in this directory?");
|
|
|
227
204
|
session.dispose();
|
|
228
205
|
```
|
|
229
206
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
const tallow = await createTallowSession({
|
|
234
|
-
cwd: "/path/to/project",
|
|
235
|
-
provider: "anthropic",
|
|
236
|
-
modelId: "claude-sonnet-4-20250514",
|
|
237
|
-
thinkingLevel: "high",
|
|
238
|
-
session: { type: "memory" }, // Don't persist
|
|
239
|
-
noBundledExtensions: true, // Start clean
|
|
240
|
-
additionalExtensions: ["./my-ext"], // Add your own
|
|
241
|
-
systemPrompt: "You are a test bot.", // Override system prompt
|
|
242
|
-
apiKey: process.env.ANTHROPIC_API_KEY, // Runtime only, not persisted
|
|
243
|
-
});
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Configuration
|
|
247
|
-
|
|
248
|
-
Tallow stores its configuration in `~/.tallow/`:
|
|
249
|
-
|
|
250
|
-
| Path | Purpose |
|
|
251
|
-
|------|---------|
|
|
252
|
-
| `~/.tallow/settings.json` | Global settings |
|
|
253
|
-
| `~/.tallow/.env` | Environment variables loaded at startup (supports `op://` refs) |
|
|
254
|
-
| `~/.tallow/auth.json` | Provider auth references (see [SECURITY.md](SECURITY.md)) |
|
|
255
|
-
| `~/.tallow/models.json` | Model configuration |
|
|
256
|
-
| `~/.tallow/keybindings.json` | Keybinding overrides |
|
|
257
|
-
| `~/.tallow/agents/` | Agent profiles (installed from templates, yours to edit) |
|
|
258
|
-
| `~/.tallow/commands/` | Slash commands (installed from templates, yours to edit) |
|
|
259
|
-
| `~/.tallow/extensions/` | User extensions (override bundled ones by name) |
|
|
260
|
-
| `~/.tallow/sessions/` | Persisted conversation sessions |
|
|
261
|
-
|
|
262
|
-
Project-level configuration lives in `.tallow/` within your project directory.
|
|
263
|
-
|
|
264
|
-
## Icons
|
|
265
|
-
|
|
266
|
-
Override any TUI glyph in `~/.tallow/settings.json`:
|
|
267
|
-
|
|
268
|
-
```json
|
|
269
|
-
{
|
|
270
|
-
"icons": {
|
|
271
|
-
"success": "✔",
|
|
272
|
-
"error": "✘",
|
|
273
|
-
"spinner": ["⠋", "⠙", "⠹", "⠸"]
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
Only keys you set are overridden — everything else keeps its default.
|
|
279
|
-
See the [icon reference](https://tallow.dungle-scrubs.com/getting-started/icons/) for all available keys.
|
|
280
|
-
|
|
281
|
-
## Themes
|
|
282
|
-
|
|
283
|
-
Switch themes inside an interactive session with the `/theme` command,
|
|
284
|
-
or set one in `~/.tallow/settings.json`:
|
|
285
|
-
|
|
286
|
-
```json
|
|
287
|
-
{
|
|
288
|
-
"theme": "tokyo-night"
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
## Writing extensions
|
|
293
|
-
|
|
294
|
-
Extensions are TypeScript files that receive the pi `ExtensionAPI`:
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
import type { ExtensionAPI } from "tallow";
|
|
298
|
-
|
|
299
|
-
export default function myExtension(api: ExtensionAPI): void {
|
|
300
|
-
api.registerCommand("greet", {
|
|
301
|
-
description: "Say hello",
|
|
302
|
-
handler: async (_args, ctx) => {
|
|
303
|
-
ctx.ui.notify("Hello from my extension!", "info");
|
|
304
|
-
},
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
Place your extension in `~/.tallow/extensions/my-extension/index.ts`.
|
|
310
|
-
If it shares a name with a bundled extension, yours takes precedence.
|
|
207
|
+
See the [SDK docs](https://tallow.dungle-scrubs.com) for all options.
|
|
311
208
|
|
|
312
209
|
## Known limitations
|
|
313
210
|
|
|
314
211
|
- Requires Node.js 22+ (uses modern ESM features)
|
|
315
212
|
- Session persistence is local — no cloud sync
|
|
316
|
-
-
|
|
317
|
-
for JS-heavy pages
|
|
213
|
+
- `web_fetch` works best with a [Firecrawl](https://firecrawl.dev) API key for JS-heavy pages
|
|
318
214
|
|
|
319
215
|
## Contributing
|
|
320
216
|
|
|
321
217
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
|
|
322
218
|
|
|
219
|
+
This is a personal project I build in my spare time — please be patient with
|
|
220
|
+
issue and PR response times.
|
|
221
|
+
|
|
323
222
|
## License
|
|
324
223
|
|
|
325
224
|
[MIT](LICENSE) © Kevin Frilot
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type RuntimePathProvider } from "./runtime-path-provider.js";
|
|
2
2
|
export declare const APP_NAME = "tallow";
|
|
3
|
-
export declare const TALLOW_VERSION = "0.8.
|
|
3
|
+
export declare const TALLOW_VERSION = "0.8.6";
|
|
4
4
|
export declare const CONFIG_DIR = ".tallow";
|
|
5
5
|
/** ~/.tallow (or override from ~/.config/tallow-work-dirs) — all user config, sessions, auth, extensions */
|
|
6
6
|
export declare const TALLOW_HOME: string;
|
package/dist/config.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { createRuntimePathProvider } from "./runtime-path-provider.js";
|
|
7
7
|
// ─── Identity ────────────────────────────────────────────────────────────────
|
|
8
8
|
export const APP_NAME = "tallow";
|
|
9
|
-
export const TALLOW_VERSION = "0.8.
|
|
9
|
+
export const TALLOW_VERSION = "0.8.6"; // x-release-please-version
|
|
10
10
|
export const CONFIG_DIR = ".tallow";
|
|
11
11
|
// ─── Paths ───────────────────────────────────────────────────────────────────
|
|
12
12
|
/** ~/.tallow (or override from ~/.config/tallow-work-dirs) — all user config, sessions, auth, extensions */
|
|
@@ -135,7 +135,8 @@ export function getNonCollidingSkillPaths(
|
|
|
135
135
|
return paths;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
const sortedEntries = [...entries].sort((left, right) => left.name.localeCompare(right.name));
|
|
139
|
+
for (const entry of sortedEntries) {
|
|
139
140
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
140
141
|
if (knownSkillNames.has(entry.name)) continue;
|
|
141
142
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wezterm-notify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Signal agent turn status to WezTerm via OSC 1337 user variables — enables tab spinner and done-color indicators.",
|
|
5
|
+
"category": "integration",
|
|
6
|
+
"tags": ["terminal", "wezterm", "notifications"],
|
|
7
|
+
"files": ["index.ts"],
|
|
8
|
+
"relationships": [
|
|
9
|
+
{
|
|
10
|
+
"type": "enhances",
|
|
11
|
+
"extension": "wezterm-pane-control"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"npmDependencies": {}
|
|
15
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WezTerm Turn Status Extension
|
|
3
|
+
*
|
|
4
|
+
* Signals agent turn status to WezTerm via OSC 1337 SetUserVar sequences.
|
|
5
|
+
* WezTerm Lua config reads `pi_status` from `pane:get_user_vars()` to drive
|
|
6
|
+
* tab bar indicators — a spinner while the agent is working, and a color
|
|
7
|
+
* change when it finishes.
|
|
8
|
+
*
|
|
9
|
+
* Gated behind `WEZTERM_PANE` — silent no-op outside WezTerm.
|
|
10
|
+
*
|
|
11
|
+
* A heartbeat (pi_heartbeat) fires every 500ms during a turn to trigger
|
|
12
|
+
* WezTerm's `update-right-status` event, which advances the spinner frame.
|
|
13
|
+
*/
|
|
14
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Set a WezTerm user variable via OSC 1337 escape sequence.
|
|
18
|
+
* WezTerm reads these in format-tab-title via `pane:get_user_vars()`.
|
|
19
|
+
*
|
|
20
|
+
* @param name - Variable name (e.g. "pi_status")
|
|
21
|
+
* @param value - Variable value (will be base64-encoded)
|
|
22
|
+
*/
|
|
23
|
+
function setWezTermUserVar(name: string, value: string): void {
|
|
24
|
+
const encoded = Buffer.from(value).toString("base64");
|
|
25
|
+
process.stdout.write(`\x1b]1337;SetUserVar=${name}=${encoded}\x07`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Start a heartbeat that re-emits pi_heartbeat every 500ms.
|
|
30
|
+
* Each write triggers WezTerm to re-render the tab bar, driving the
|
|
31
|
+
* spinner animation in format-tab-title.
|
|
32
|
+
*
|
|
33
|
+
* @returns Cleanup function to stop the heartbeat
|
|
34
|
+
*/
|
|
35
|
+
function startHeartbeat(): () => void {
|
|
36
|
+
let frame = 0;
|
|
37
|
+
const interval = setInterval(() => {
|
|
38
|
+
setWezTermUserVar("pi_heartbeat", String(frame++));
|
|
39
|
+
}, 500);
|
|
40
|
+
return () => clearInterval(interval);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Register event handlers to signal turn status to WezTerm.
|
|
45
|
+
* Only activates when `WEZTERM_PANE` is set.
|
|
46
|
+
*
|
|
47
|
+
* @param pi - Extension API for registering event handlers
|
|
48
|
+
*/
|
|
49
|
+
export default function weztermNotify(pi: ExtensionAPI): void {
|
|
50
|
+
if (!process.env.WEZTERM_PANE) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let stopHeartbeat: (() => void) | null = null;
|
|
55
|
+
|
|
56
|
+
pi.on("turn_start", async () => {
|
|
57
|
+
setWezTermUserVar("pi_status", "working");
|
|
58
|
+
stopHeartbeat?.();
|
|
59
|
+
stopHeartbeat = startHeartbeat();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
pi.on("turn_end", async () => {
|
|
63
|
+
stopHeartbeat?.();
|
|
64
|
+
stopHeartbeat = null;
|
|
65
|
+
setWezTermUserVar("pi_status", "done");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
pi.on("input", async () => {
|
|
69
|
+
stopHeartbeat?.();
|
|
70
|
+
stopHeartbeat = null;
|
|
71
|
+
setWezTermUserVar("pi_status", "");
|
|
72
|
+
return { action: "continue" as const };
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
pi.on("session_start", async () => {
|
|
76
|
+
stopHeartbeat?.();
|
|
77
|
+
stopHeartbeat = null;
|
|
78
|
+
setWezTermUserVar("pi_status", "");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
pi.on("session_shutdown", async () => {
|
|
82
|
+
stopHeartbeat?.();
|
|
83
|
+
stopHeartbeat = null;
|
|
84
|
+
setWezTermUserVar("pi_status", "");
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
--- tallow WezTerm integration
|
|
2
|
+
--- Adds agent turn status indicators to the tab bar.
|
|
3
|
+
---
|
|
4
|
+
--- Standalone usage (module owns format-tab-title + update-right-status):
|
|
5
|
+
---
|
|
6
|
+
--- local tallow = require("tallow")
|
|
7
|
+
--- tallow.setup()
|
|
8
|
+
---
|
|
9
|
+
--- If you already have custom handlers, do not call setup().
|
|
10
|
+
--- Use helper functions instead:
|
|
11
|
+
---
|
|
12
|
+
--- local tallow = require("tallow")
|
|
13
|
+
--- wezterm.on("update-right-status", function(window, pane)
|
|
14
|
+
--- tallow.tick()
|
|
15
|
+
--- -- your existing update-right-status logic
|
|
16
|
+
--- end)
|
|
17
|
+
---
|
|
18
|
+
--- wezterm.on("format-tab-title", function(tab)
|
|
19
|
+
--- local any_working, any_done_unseen = tallow.get_tab_status(tab)
|
|
20
|
+
--- -- your existing format-tab-title logic using these status flags
|
|
21
|
+
--- end)
|
|
22
|
+
|
|
23
|
+
local wezterm = require("wezterm")
|
|
24
|
+
|
|
25
|
+
local M = {}
|
|
26
|
+
|
|
27
|
+
local SPINNER_CHARS = { "◰", "◳", "◲", "◱" }
|
|
28
|
+
|
|
29
|
+
local defaults = {
|
|
30
|
+
spinner_color = "#d8a274",
|
|
31
|
+
done_color = "#61afef",
|
|
32
|
+
active_color = "#ccb266",
|
|
33
|
+
inactive_color = "#737373",
|
|
34
|
+
spinner_interval_seconds = 0.5,
|
|
35
|
+
max_title_length = 24,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
---Copy a table shallowly.
|
|
39
|
+
---@param value table
|
|
40
|
+
---@return table
|
|
41
|
+
local function copy_table(value)
|
|
42
|
+
local out = {}
|
|
43
|
+
for k, v in pairs(value) do
|
|
44
|
+
out[k] = v
|
|
45
|
+
end
|
|
46
|
+
return out
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
---Merge optional overrides onto defaults.
|
|
50
|
+
---@param opts table|nil
|
|
51
|
+
---@return table
|
|
52
|
+
local function resolve_options(opts)
|
|
53
|
+
local out = copy_table(defaults)
|
|
54
|
+
if not opts then
|
|
55
|
+
return out
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
for k, v in pairs(opts) do
|
|
59
|
+
out[k] = v
|
|
60
|
+
end
|
|
61
|
+
return out
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
---Resolve the title text shown in tab rendering.
|
|
65
|
+
---@param tab table TabInformation
|
|
66
|
+
---@param max_len number
|
|
67
|
+
---@return string
|
|
68
|
+
local function resolve_title(tab, max_len)
|
|
69
|
+
local title = tostring(tab.tab_index + 1)
|
|
70
|
+
|
|
71
|
+
if tab.tab_title and #tab.tab_title > 0 then
|
|
72
|
+
title = tab.tab_title
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if #title > max_len then
|
|
76
|
+
title = title:sub(1, max_len - 2) .. ".."
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
return title
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
---Aggregate pi_status across all panes in a tab.
|
|
83
|
+
---@param tab table TabInformation from format-tab-title
|
|
84
|
+
---@return boolean any_working
|
|
85
|
+
---@return boolean any_done_unseen
|
|
86
|
+
function M.get_tab_status(tab)
|
|
87
|
+
local any_working = false
|
|
88
|
+
local any_done_unseen = false
|
|
89
|
+
|
|
90
|
+
local mux_tab = wezterm.mux.get_tab(tab.tab_id)
|
|
91
|
+
if not mux_tab then
|
|
92
|
+
return false, false
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if not wezterm.GLOBAL.pi_seen then
|
|
96
|
+
wezterm.GLOBAL.pi_seen = {}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
for _, pane in ipairs(mux_tab:panes()) do
|
|
100
|
+
local vars = pane:get_user_vars()
|
|
101
|
+
local status = vars.pi_status or ""
|
|
102
|
+
local pid = tostring(pane:pane_id())
|
|
103
|
+
|
|
104
|
+
if status == "working" then
|
|
105
|
+
any_working = true
|
|
106
|
+
wezterm.GLOBAL.pi_seen[pid] = nil
|
|
107
|
+
elseif status == "done" then
|
|
108
|
+
if tab.is_active then
|
|
109
|
+
wezterm.GLOBAL.pi_seen[pid] = true
|
|
110
|
+
elseif not wezterm.GLOBAL.pi_seen[pid] then
|
|
111
|
+
any_done_unseen = true
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
wezterm.GLOBAL.pi_seen[pid] = nil
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
return any_working, any_done_unseen
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
---Advance spinner frame using wall-clock throttling.
|
|
122
|
+
---@param opts table|nil Optional overrides
|
|
123
|
+
function M.tick(opts)
|
|
124
|
+
local resolved = resolve_options(opts)
|
|
125
|
+
local now = os.clock()
|
|
126
|
+
local last = wezterm.GLOBAL.tallow_spinner_last or 0
|
|
127
|
+
|
|
128
|
+
if (now - last) >= resolved.spinner_interval_seconds then
|
|
129
|
+
local frame = wezterm.GLOBAL.tallow_spinner_frame or 0
|
|
130
|
+
wezterm.GLOBAL.tallow_spinner_frame = (frame + 1) % #SPINNER_CHARS
|
|
131
|
+
wezterm.GLOBAL.tallow_spinner_last = now
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
---Get current spinner glyph for the active frame.
|
|
136
|
+
---@return string
|
|
137
|
+
function M.spinner_char()
|
|
138
|
+
local frame = wezterm.GLOBAL.tallow_spinner_frame or 0
|
|
139
|
+
return SPINNER_CHARS[(frame % #SPINNER_CHARS) + 1]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
---Render default tab title elements with tallow status indicators.
|
|
143
|
+
---@param tab table TabInformation from format-tab-title
|
|
144
|
+
---@param opts table|nil Optional color/timing overrides
|
|
145
|
+
---@return table
|
|
146
|
+
function M.render_tab_title(tab, opts)
|
|
147
|
+
local resolved = resolve_options(opts)
|
|
148
|
+
local any_working, any_done_unseen = M.get_tab_status(tab)
|
|
149
|
+
local title = resolve_title(tab, resolved.max_title_length)
|
|
150
|
+
local elements = {}
|
|
151
|
+
|
|
152
|
+
if any_working then
|
|
153
|
+
table.insert(elements, { Foreground = { Color = resolved.spinner_color } })
|
|
154
|
+
table.insert(elements, { Text = " " .. M.spinner_char() .. " " })
|
|
155
|
+
else
|
|
156
|
+
table.insert(elements, { Text = " " })
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
local fg = tab.is_active and resolved.active_color or resolved.inactive_color
|
|
160
|
+
if any_done_unseen then
|
|
161
|
+
fg = resolved.done_color
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
table.insert(elements, { Foreground = { Color = fg } })
|
|
165
|
+
table.insert(elements, { Text = title .. " " })
|
|
166
|
+
return elements
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
---Register default event handlers for tallow tab indicators.
|
|
170
|
+
---
|
|
171
|
+
---This owns both `update-right-status` and `format-tab-title`.
|
|
172
|
+
---If your config already defines either handler, use helper methods
|
|
173
|
+
---(`tick`, `get_tab_status`, `spinner_char`, `render_tab_title`) and
|
|
174
|
+
---compose manually in your own handlers instead.
|
|
175
|
+
---
|
|
176
|
+
---@param opts table|nil Optional color/timing overrides
|
|
177
|
+
function M.setup(opts)
|
|
178
|
+
local resolved = resolve_options(opts)
|
|
179
|
+
|
|
180
|
+
if wezterm.GLOBAL.tallow_handlers_registered then
|
|
181
|
+
wezterm.GLOBAL.tallow_handler_options = resolved
|
|
182
|
+
return
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
wezterm.GLOBAL.tallow_handlers_registered = true
|
|
186
|
+
wezterm.GLOBAL.tallow_handler_options = resolved
|
|
187
|
+
|
|
188
|
+
wezterm.on("update-right-status", function()
|
|
189
|
+
M.tick(wezterm.GLOBAL.tallow_handler_options)
|
|
190
|
+
end)
|
|
191
|
+
|
|
192
|
+
wezterm.on("format-tab-title", function(tab)
|
|
193
|
+
return M.render_tab_title(tab, wezterm.GLOBAL.tallow_handler_options)
|
|
194
|
+
end)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
return M
|
package/package.json
CHANGED
|
@@ -34,7 +34,7 @@ Relay that answer to the user.
|
|
|
34
34
|
| Component | Location |
|
|
35
35
|
|-----------|----------|
|
|
36
36
|
| Core source | `src/` (agent-runner.ts, atomic-write.ts, auth-hardening.ts, cli.ts, config.ts, extensions-global.d.ts, fatal-errors.ts, index.ts, install.ts, interactive-mode-patch.ts, pid-manager.ts, plugins.ts, process-cleanup.ts, project-trust.ts, runtime-path-provider.ts, sdk.ts, session-migration.ts, session-utils.ts) |
|
|
37
|
-
| Extensions | `extensions/` — extension.json + index.ts each (
|
|
37
|
+
| Extensions | `extensions/` — extension.json + index.ts each (50 bundled) |
|
|
38
38
|
| Skills | `skills/` — subdirs with SKILL.md |
|
|
39
39
|
| Agents | `agents/` — markdown with YAML frontmatter |
|
|
40
40
|
| Themes | `themes/` — JSON files (34 dark-only themes) |
|