@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 CHANGED
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">Tallow</h1>
6
6
 
7
7
  <p align="center">
8
- An opt-in, fully featured coding agent for your terminal. Built on <a href="https://github.com/nicobrinkkemper/pi-coding-agent">pi</a>.
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.jpg" alt="Tallow multi-agent team coordinating a docs audit" />
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 opt-in by default: start minimal, then enable only what your project needs.
30
- It is drop-in compatible with Claude Code projects via `.claude/` bridging.
31
- Install extensions, themes, and agents in any combination.
32
- This is a personal project I build in my spare time, so please be patient with
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
- ## Features
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 global install:
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
- ### From source
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 mode
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 input from stdin
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 most recent session
111
+ # Continue the last session
111
112
  tallow --continue
112
113
 
113
- # Use a specific model
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
- # Load additional extensions
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 read,grep,find # Only read, grep, find
139
- tallow --tools readonly # Preset: read, grep, find, ls
140
- tallow --tools none # Chat only, no tools
121
+ tallow --tools readonly # read, grep, find, ls only
122
+ tallow --tools none # chat only, no tools
123
+ ```
141
124
 
142
- # Disable all extensions
143
- tallow --no-extensions
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
- # Print tallow home directory
146
- tallow --home
147
- ```
128
+ ## Configuration
129
+
130
+ Tallow stores its configuration in `~/.tallow/`:
148
131
 
149
- `--api-key` was removed to avoid leaking secrets in process arguments.
150
- Use `TALLOW_API_KEY` or `TALLOW_API_KEY_REF` instead.
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
- ### Piped input
143
+ Project-level overrides live in `.tallow/` within your repo.
153
144
 
154
- Pipe file contents or command output directly into Tallow:
145
+ ## Extending Tallow
155
146
 
156
- ```bash
157
- # Stdin becomes the prompt
158
- echo "What is 2+2?" | tallow
147
+ ### Themes
159
148
 
160
- # Stdin as context + explicit prompt
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
- # Pipe command output
164
- git log --oneline -20 | tallow -p "Summarize recent changes"
151
+ ```json
152
+ { "theme": "tokyo-night" }
165
153
  ```
166
154
 
167
- When stdin is piped, Tallow automatically enters print mode (single-shot).
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
- ### Shell interpolation
157
+ Override any TUI glyph in `settings.json` — only the keys you set change:
173
158
 
174
- Expand shell commands inline with `` !`command` `` syntax:
175
-
176
- ```
177
- !`ls -la`
178
- !`git status`
179
- !`git branch --show-current`
159
+ ```json
160
+ { "icons": { "success": "✔", "error": "✘" } }
180
161
  ```
181
162
 
182
- **Disabled by default.** Enable explicitly with either:
163
+ See the [icon reference](https://tallow.dungle-scrubs.com/getting-started/icons/) for all keys.
183
164
 
184
- - `TALLOW_ENABLE_SHELL_INTERPOLATION=1` (or `TALLOW_SHELL_INTERPOLATION=1`)
185
- - `"shellInterpolation": true` in `.tallow/settings.json` or `~/.tallow/settings.json`
165
+ ### Writing extensions
186
166
 
187
- When enabled, only allowlisted implicit commands run. The command output
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
- ### Slash commands
169
+ ```typescript
170
+ import type { ExtensionAPI } from "tallow";
194
171
 
195
- Inside an interactive session, type `/` to see available commands:
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
- | Command | Description |
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
- ## SDK
185
+ ### SDK
209
186
 
210
- Use Tallow programmatically in your own tools:
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
- ### SDK options
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
- - The `web_fetch` extension works best with a [Firecrawl](https://firecrawl.dev) API key
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.5";
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.5"; // x-release-please-version
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
- for (const entry of entries) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dungle-scrubs/tallow",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "An opinionated coding agent. Built on pi.",
5
5
  "piConfig": {
6
6
  "name": "tallow",
@@ -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 (49 bundled) |
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) |