@dungle-scrubs/tallow 0.8.5 → 0.8.7

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 (156) hide show
  1. package/README.md +121 -203
  2. package/dist/auth-hardening.d.ts +17 -23
  3. package/dist/auth-hardening.d.ts.map +1 -1
  4. package/dist/auth-hardening.js +78 -33
  5. package/dist/auth-hardening.js.map +1 -1
  6. package/dist/cli.js +152 -3
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts +1 -1
  9. package/dist/config.js +1 -1
  10. package/dist/fatal-errors.js +1 -1
  11. package/dist/fatal-errors.js.map +1 -1
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/interactive-mode-patch.d.ts +2 -0
  17. package/dist/interactive-mode-patch.d.ts.map +1 -1
  18. package/dist/interactive-mode-patch.js +36 -0
  19. package/dist/interactive-mode-patch.js.map +1 -1
  20. package/dist/plugins.d.ts +27 -2
  21. package/dist/plugins.d.ts.map +1 -1
  22. package/dist/plugins.js +163 -2
  23. package/dist/plugins.js.map +1 -1
  24. package/dist/project-trust-banner.d.ts +32 -0
  25. package/dist/project-trust-banner.d.ts.map +1 -0
  26. package/dist/project-trust-banner.js +60 -0
  27. package/dist/project-trust-banner.js.map +1 -0
  28. package/dist/sdk.d.ts +78 -2
  29. package/dist/sdk.d.ts.map +1 -1
  30. package/dist/sdk.js +375 -96
  31. package/dist/sdk.js.map +1 -1
  32. package/dist/startup-profile.d.ts +31 -0
  33. package/dist/startup-profile.d.ts.map +1 -0
  34. package/dist/startup-profile.js +30 -0
  35. package/dist/startup-profile.js.map +1 -0
  36. package/dist/startup-timing.d.ts +21 -0
  37. package/dist/startup-timing.d.ts.map +1 -0
  38. package/dist/startup-timing.js +50 -0
  39. package/dist/startup-timing.js.map +1 -0
  40. package/extensions/__integration__/shell-policy-confirm.test.ts +85 -4
  41. package/extensions/_icons/extension.json +10 -0
  42. package/extensions/_shared/__tests__/lazy-init.test.ts +84 -0
  43. package/extensions/_shared/__tests__/permissions.test.ts +59 -1
  44. package/extensions/_shared/__tests__/shell-policy.test.ts +67 -0
  45. package/extensions/_shared/interop-events.ts +4 -1
  46. package/extensions/_shared/lazy-init.ts +177 -0
  47. package/extensions/_shared/permissions.ts +355 -31
  48. package/extensions/_shared/shell-policy.ts +29 -7
  49. package/extensions/agent-commands-tool/extension.json +10 -0
  50. package/extensions/ask-user-question-tool/extension.json +10 -0
  51. package/extensions/background-task-tool/__tests__/render-style.test.ts +32 -0
  52. package/extensions/background-task-tool/extension.json +12 -0
  53. package/extensions/background-task-tool/index.ts +203 -65
  54. package/extensions/bash-tool-enhanced/__tests__/render-style.test.ts +45 -0
  55. package/extensions/bash-tool-enhanced/extension.json +11 -0
  56. package/extensions/bash-tool-enhanced/index.ts +53 -38
  57. package/extensions/cd-tool/extension.json +11 -0
  58. package/extensions/cheatsheet/extension.json +10 -0
  59. package/extensions/claude-bridge/extension.json +10 -0
  60. package/extensions/claude-bridge/index.ts +2 -1
  61. package/extensions/clear/extension.json +10 -0
  62. package/extensions/command-expansion/__tests__/expansion.test.ts +84 -1
  63. package/extensions/command-expansion/extension.json +10 -0
  64. package/extensions/command-expansion/index.ts +134 -18
  65. package/extensions/command-prompt/extension.json +11 -0
  66. package/extensions/context-files/__tests__/add-dir.test.ts +1 -1
  67. package/extensions/context-files/__tests__/lazy-init.test.ts +242 -0
  68. package/extensions/context-files/extension.json +11 -0
  69. package/extensions/context-files/index.ts +77 -15
  70. package/extensions/context-fork/__tests__/context-fork.test.ts +88 -0
  71. package/extensions/context-fork/extension.json +10 -0
  72. package/extensions/context-fork/index.ts +96 -16
  73. package/extensions/context-usage/extension.json +10 -0
  74. package/extensions/custom-footer/extension.json +10 -0
  75. package/extensions/debug/__tests__/diagnostics-commands.test.ts +244 -0
  76. package/extensions/debug/extension.json +38 -0
  77. package/extensions/debug/index.ts +149 -81
  78. package/extensions/edit-tool-enhanced/__tests__/render-style.test.ts +47 -0
  79. package/extensions/edit-tool-enhanced/extension.json +10 -0
  80. package/extensions/edit-tool-enhanced/index.ts +32 -9
  81. package/extensions/file-reference/extension.json +10 -0
  82. package/extensions/git-status/extension.json +10 -0
  83. package/extensions/health/extension.json +10 -0
  84. package/extensions/hooks/extension.json +38 -0
  85. package/extensions/init/extension.json +10 -0
  86. package/extensions/lsp/__tests__/lsp-timeouts.test.ts +236 -2
  87. package/extensions/lsp/__tests__/lsp-tools.test.ts +17 -1
  88. package/extensions/lsp/extension.json +18 -0
  89. package/extensions/lsp/index.ts +127 -10
  90. package/extensions/mcp-adapter-tool/__tests__/lazy-init.test.ts +349 -0
  91. package/extensions/mcp-adapter-tool/extension.json +12 -0
  92. package/extensions/mcp-adapter-tool/index.ts +133 -41
  93. package/extensions/minimal-skill-display/extension.json +10 -0
  94. package/extensions/output-styles-tool/extension.json +11 -0
  95. package/extensions/permissions/extension.json +11 -0
  96. package/extensions/permissions/index.ts +34 -12
  97. package/extensions/plan-mode-tool/extension.json +19 -0
  98. package/extensions/progress-indicator/extension.json +10 -0
  99. package/extensions/prompt-suggestions/extension.json +10 -0
  100. package/extensions/random-spinner/extension.json +10 -0
  101. package/extensions/read-tool-enhanced/__tests__/render-style.test.ts +49 -0
  102. package/extensions/read-tool-enhanced/extension.json +11 -0
  103. package/extensions/read-tool-enhanced/index.ts +58 -34
  104. package/extensions/rewind/extension.json +11 -0
  105. package/extensions/session-memory/extension.json +10 -0
  106. package/extensions/session-namer/extension.json +10 -0
  107. package/extensions/shell-interpolation/extension.json +10 -0
  108. package/extensions/show-system-prompt/extension.json +10 -0
  109. package/extensions/skill-commands/extension.json +10 -0
  110. package/extensions/slash-command-bridge/extension.json +23 -3
  111. package/extensions/stats/extension.json +11 -0
  112. package/extensions/subagent-tool/__tests__/auto-cheap-model.test.ts +87 -36
  113. package/extensions/subagent-tool/__tests__/background-retention.test.ts +135 -0
  114. package/extensions/subagent-tool/__tests__/model-router-explicit-resolution.test.ts +114 -0
  115. package/extensions/subagent-tool/__tests__/model-router-routing-options.test.ts +125 -0
  116. package/extensions/subagent-tool/__tests__/model-router-select-options.test.ts +157 -0
  117. package/extensions/subagent-tool/__tests__/model-router.test.ts +61 -0
  118. package/extensions/subagent-tool/__tests__/presentation-rendering.test.ts +326 -0
  119. package/extensions/subagent-tool/__tests__/process-liveness.test.ts +141 -0
  120. package/extensions/subagent-tool/__tests__/subagent-status-retention.test.ts +144 -0
  121. package/extensions/subagent-tool/extension.json +11 -0
  122. package/extensions/subagent-tool/index.ts +927 -345
  123. package/extensions/subagent-tool/model-router.ts +653 -20
  124. package/extensions/subagent-tool/process.ts +414 -16
  125. package/extensions/subagent-tool/widget.ts +171 -10
  126. package/extensions/tasks/__tests__/widget-subagents.test.ts +405 -0
  127. package/extensions/tasks/agents/index.ts +9 -18
  128. package/extensions/tasks/commands/register-tasks-extension.ts +288 -128
  129. package/extensions/tasks/extension.json +27 -1
  130. package/extensions/tasks/ui/index.ts +13 -11
  131. package/extensions/teams-tool/__tests__/dashboard-feed.test.ts +87 -0
  132. package/extensions/teams-tool/__tests__/message-retention.test.ts +149 -0
  133. package/extensions/teams-tool/dashboard/feed.ts +63 -18
  134. package/extensions/teams-tool/dashboard/state.ts +29 -9
  135. package/extensions/teams-tool/dashboard.ts +103 -63
  136. package/extensions/teams-tool/extension.json +20 -0
  137. package/extensions/teams-tool/sessions/spawn.ts +1 -1
  138. package/extensions/teams-tool/store.ts +80 -0
  139. package/extensions/theme-selector/extension.json +12 -0
  140. package/extensions/tool-display/extension.json +8 -0
  141. package/extensions/tool-display/index.ts +178 -1
  142. package/extensions/upstream-check/extension.json +10 -0
  143. package/extensions/web-fetch-tool/extension.json +10 -0
  144. package/extensions/web-search-tool/extension.json +10 -0
  145. package/extensions/wezterm-notify/__tests__/index.test.ts +232 -0
  146. package/extensions/wezterm-notify/__tests__/lua-behavior.test.ts +62 -0
  147. package/extensions/wezterm-notify/extension.json +32 -0
  148. package/extensions/wezterm-notify/index.ts +236 -0
  149. package/extensions/wezterm-notify/wezterm/tallow.lua +211 -0
  150. package/extensions/wezterm-pane-control/extension.json +11 -0
  151. package/extensions/write-tool-enhanced/extension.json +15 -1
  152. package/extensions/write-tool-enhanced/index.ts +25 -8
  153. package/package.json +6 -5
  154. package/schemas/settings.schema.json +75 -0
  155. package/skills/tallow-expert/SKILL.md +2 -2
  156. package/templates/commands/implement.md +21 -5
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,155 +64,85 @@ 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
 
96
- ## Usage
97
-
98
- ```bash
99
- # Interactive mode
100
- tallow
101
-
102
- # Single-shot prompt
103
- tallow -p "Fix the failing tests"
67
+ </details>
104
68
 
105
- # Pipe input from stdin
106
- echo "Explain this error" | tallow
107
- cat README.md | tallow -p "Summarize this"
108
- git diff | tallow -p "Review these changes"
69
+ ## Highlights
109
70
 
110
- # Continue most recent session
111
- tallow --continue
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.
112
74
 
113
- # Use a specific model
114
- tallow -m anthropic/claude-sonnet-4-20250514
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.
115
78
 
116
- # Set thinking level
117
- tallow --thinking high
79
+ **Context fork** Branch into an isolated subprocess with its own tools and model,
80
+ then merge results back into the main session.
118
81
 
119
- # Run without persisting session
120
- tallow --no-session
82
+ **Workspace rewind** Every conversation turn snapshots your file changes. Roll back
83
+ to any earlier turn when something goes wrong.
121
84
 
122
- # Load additional extensions
123
- tallow -e ./my-extension
85
+ **Background tasks** Kick off long-running work without blocking the session.
86
+ Track task lifecycle explicitly and check back when ready.
124
87
 
125
- # List saved sessions (shows auto-generated names)
126
- tallow --list
88
+ **LSP** Jump to definitions, find references, inspect types, and search
89
+ workspace symbols — no editor required.
127
90
 
128
- # Runtime auth via environment (not persisted)
129
- TALLOW_API_KEY=sk-ant-... tallow --provider anthropic
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.
130
94
 
131
- # Runtime auth via reference (resolved at runtime)
132
- TALLOW_API_KEY_REF=op://Services/Anthropic/api-key tallow --provider anthropic
95
+ **User-owned config** Agents, commands, and extensions install to `~/.tallow/`
96
+ where you own them. Edit, remove, or add your own.
133
97
 
134
- # Run in RPC or JSON mode
135
- tallow --mode rpc
136
-
137
- # 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
141
-
142
- # Disable all extensions
143
- tallow --no-extensions
144
-
145
- # Print tallow home directory
146
- tallow --home
147
- ```
148
-
149
- `--api-key` was removed to avoid leaking secrets in process arguments.
150
- Use `TALLOW_API_KEY` or `TALLOW_API_KEY_REF` instead.
151
-
152
- ### Piped input
153
-
154
- Pipe file contents or command output directly into Tallow:
98
+ ## Usage
155
99
 
156
100
  ```bash
157
- # Stdin becomes the prompt
158
- echo "What is 2+2?" | tallow
101
+ # Interactive session
102
+ tallow
159
103
 
160
- # Stdin as context + explicit prompt
161
- cat src/main.ts | tallow -p "Find bugs in this code"
104
+ # Single-shot prompt
105
+ tallow -p "Fix the failing tests"
162
106
 
163
- # Pipe command output
164
- git log --oneline -20 | tallow -p "Summarize recent changes"
165
- ```
107
+ # Pipe in context
108
+ git diff | tallow -p "Review these changes"
109
+ cat src/main.ts | tallow -p "Find bugs in this code"
166
110
 
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.
111
+ # Continue the last session
112
+ tallow --continue
171
113
 
172
- ### Shell interpolation
114
+ # Pick a model and thinking level
115
+ tallow -m anthropic/claude-sonnet-4-20250514 --thinking high
173
116
 
174
- Expand shell commands inline with `` !`command` `` syntax:
117
+ # List saved sessions
118
+ tallow --list
175
119
 
120
+ # Restrict available tools
121
+ tallow --tools readonly # read, grep, find, ls only
122
+ tallow --tools none # chat only, no tools
176
123
  ```
177
- !`ls -la`
178
- !`git status`
179
- !`git branch --show-current`
180
- ```
181
-
182
- **Disabled by default.** Enable explicitly with either:
183
-
184
- - `TALLOW_ENABLE_SHELL_INTERPOLATION=1` (or `TALLOW_SHELL_INTERPOLATION=1`)
185
- - `"shellInterpolation": true` in `.tallow/settings.json` or `~/.tallow/settings.json`
186
124
 
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).
125
+ ### Extension catalog + least-privilege startup
192
126
 
193
- ### Slash commands
127
+ Use the extension catalog to inspect what each extension does:
194
128
 
195
- Inside an interactive session, type `/` to see available commands:
196
-
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 |
207
-
208
- ## SDK
209
-
210
- Use Tallow programmatically in your own tools:
211
-
212
- ```typescript
213
- import { createTallowSession } from "tallow";
214
-
215
- const { session } = await createTallowSession({
216
- provider: "anthropic",
217
- modelId: "claude-sonnet-4-20250514",
218
- });
129
+ ```bash
130
+ tallow extensions # table view (default)
131
+ tallow extensions --json # machine-readable catalog
132
+ tallow extensions tasks # details for one extension ID
133
+ ```
219
134
 
220
- session.subscribe((event) => {
221
- if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
222
- process.stdout.write(event.assistantMessageEvent.delta);
223
- }
224
- });
135
+ For least-privilege sessions, start from an explicit allowlist:
225
136
 
226
- await session.prompt("What files are in this directory?");
227
- session.dispose();
137
+ ```bash
138
+ tallow --extensions-only --extension tasks --extension lsp
139
+ tallow --extensions-only --extension read-tool-enhanced --extension write-tool-enhanced
228
140
  ```
229
141
 
230
- ### SDK options
142
+ Repeat `--extension <selector>` to add only what the task needs.
231
143
 
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
- ```
144
+ See the [full CLI reference](https://tallow.dungle-scrubs.com) for all flags
145
+ and modes (RPC, JSON, piped stdin, shell interpolation, etc.)
245
146
 
246
147
  ## Configuration
247
148
 
@@ -249,47 +150,38 @@ Tallow stores its configuration in `~/.tallow/`:
249
150
 
250
151
  | Path | Purpose |
251
152
  |------|---------|
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 |
153
+ | `settings.json` | Global settings (theme, icons, keybindings) |
154
+ | `.env` | Environment variables loaded at startup (supports `op://` refs) |
155
+ | `auth.json` | Provider auth references (see [SECURITY.md](SECURITY.md)) |
156
+ | `models.json` | Model configuration |
157
+ | `agents/` | Agent profiles — yours to edit |
158
+ | `commands/` | Slash commands yours to edit |
159
+ | `extensions/` | User extensions (override bundled ones by name) |
160
+ | `sessions/` | Persisted conversation sessions |
161
+
162
+ Project-level overrides live in `.tallow/` within your repo.
261
163
 
262
- Project-level configuration lives in `.tallow/` within your project directory.
164
+ ## Extending Tallow
263
165
 
264
- ## Icons
166
+ ### Themes
265
167
 
266
- Override any TUI glyph in `~/.tallow/settings.json`:
168
+ Switch themes in-session with `/theme`, or set a default:
267
169
 
268
170
  ```json
269
- {
270
- "icons": {
271
- "success": "✔",
272
- "error": "✘",
273
- "spinner": ["⠋", "⠙", "⠹", "⠸"]
274
- }
275
- }
171
+ { "theme": "tokyo-night" }
276
172
  ```
277
173
 
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
174
+ ### Icons
282
175
 
283
- Switch themes inside an interactive session with the `/theme` command,
284
- or set one in `~/.tallow/settings.json`:
176
+ Override any TUI glyph in `settings.json` only the keys you set change:
285
177
 
286
178
  ```json
287
- {
288
- "theme": "tokyo-night"
289
- }
179
+ { "icons": { "success": "✔", "error": "✘" } }
290
180
  ```
291
181
 
292
- ## Writing extensions
182
+ See the [icon reference](https://tallow.dungle-scrubs.com/getting-started/icons/) for all keys.
183
+
184
+ ### Writing extensions
293
185
 
294
186
  Extensions are TypeScript files that receive the pi `ExtensionAPI`:
295
187
 
@@ -306,20 +198,46 @@ export default function myExtension(api: ExtensionAPI): void {
306
198
  }
307
199
  ```
308
200
 
309
- Place your extension in `~/.tallow/extensions/my-extension/index.ts`.
310
- If it shares a name with a bundled extension, yours takes precedence.
201
+ Place it in `~/.tallow/extensions/my-extension/index.ts`. If it shares a name
202
+ with a bundled extension, yours takes precedence.
203
+
204
+ ### SDK
205
+
206
+ Embed Tallow in your own scripts:
207
+
208
+ ```typescript
209
+ import { createTallowSession } from "tallow";
210
+
211
+ const { session } = await createTallowSession({
212
+ provider: "anthropic",
213
+ modelId: "claude-sonnet-4-20250514",
214
+ });
215
+
216
+ session.subscribe((event) => {
217
+ if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
218
+ process.stdout.write(event.assistantMessageEvent.delta);
219
+ }
220
+ });
221
+
222
+ await session.prompt("What files are in this directory?");
223
+ session.dispose();
224
+ ```
225
+
226
+ See the [SDK docs](https://tallow.dungle-scrubs.com) for all options.
311
227
 
312
228
  ## Known limitations
313
229
 
314
230
  - Requires Node.js 22+ (uses modern ESM features)
315
231
  - 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
232
+ - `web_fetch` works best with a [Firecrawl](https://firecrawl.dev) API key for JS-heavy pages
318
233
 
319
234
  ## Contributing
320
235
 
321
236
  See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
322
237
 
238
+ This is a personal project I build in my spare time — please be patient with
239
+ issue and PR response times.
240
+
323
241
  ## License
324
242
 
325
243
  [MIT](LICENSE) © Kevin Frilot
@@ -1,4 +1,4 @@
1
- import { type AuthCredential, AuthStorage } from "@mariozechner/pi-coding-agent";
1
+ import { AuthStorage } from "@mariozechner/pi-coding-agent";
2
2
  /** Result of the plaintext migration run. */
3
3
  export interface MigrationResult {
4
4
  /** Providers migrated from plaintext/op:// to secure references. */
@@ -22,30 +22,24 @@ export interface ApiKeySecretStore {
22
22
  export interface SecureAuthStorageOptions {
23
23
  readonly secretStore?: ApiKeySecretStore;
24
24
  }
25
- /**
26
- * AuthStorage wrapper that guarantees api_key credentials are persisted as
27
- * references only (keychain/opchain/env/shell), never as raw values.
28
- */
29
- export declare class SecureAuthStorage extends AuthStorage {
25
+ /** Return value from {@link createSecureAuthStorage}. */
26
+ export interface SecureAuthStorageResult {
27
+ /** AuthStorage instance with secure persistence. */
28
+ readonly authStorage: AuthStorage;
29
+ /** Providers migrated from plaintext to secure references on creation. */
30
30
  readonly migration: MigrationResult;
31
- private readonly authPathValue;
32
- private readonly secretStore;
33
- /**
34
- * Create a secure auth storage instance and run one-time migration.
35
- *
36
- * @param authPath - Absolute auth.json path
37
- * @param options - Optional testing dependencies
38
- */
39
- constructor(authPath: string, options?: SecureAuthStorageOptions);
40
- /**
41
- * Persist a provider credential.
42
- * API keys are converted to secure references before writing to disk.
43
- *
44
- * @param provider - Provider ID
45
- * @param credential - Credential payload
46
- */
47
- set(provider: string, credential: AuthCredential): void;
48
31
  }
32
+ /**
33
+ * Create an AuthStorage instance with secure persistence.
34
+ * Raw API keys are normalized to references at the storage-backend layer,
35
+ * ensuring they never reach disk in plaintext. Runs one-time migration
36
+ * for any existing plaintext keys.
37
+ *
38
+ * @param authPath - Absolute auth.json path
39
+ * @param options - Optional testing dependencies
40
+ * @returns AuthStorage instance and migration result
41
+ */
42
+ export declare function createSecureAuthStorage(authPath: string, options?: SecureAuthStorageOptions): SecureAuthStorageResult;
49
43
  /**
50
44
  * Fail when auth.json permissions are insecure.
51
45
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth-hardening.d.ts","sourceRoot":"","sources":["../src/auth-hardening.ts"],"names":[],"mappings":"AAIA,OAAO,EAEN,KAAK,cAAc,EACnB,WAAW,EACX,MAAM,+BAA+B,CAAC;AASvC,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C;AAED,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,WAAW,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CAChD;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACzC;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;IACjD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IAEpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;IAEhD;;;;;OAKG;gBACS,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B;IAYpE;;;;;;OAMG;IACM,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,GAAG,IAAI;CAehE;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAUtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,iBAA6C,GACxD,gBAAgB,CAMlB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,MAAM,EAChB,WAAW,GAAE,iBAA6C,GACxD,eAAe,CA0BjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,SAAS,CAUhE"}
1
+ {"version":3,"file":"auth-hardening.d.ts","sourceRoot":"","sources":["../src/auth-hardening.ts"],"names":[],"mappings":"AAIA,OAAO,EAGN,WAAW,EAGX,MAAM,+BAA+B,CAAC;AASvC,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C;AAED,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,WAAW,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CAChD;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACzC;AAED,yDAAyD;AACzD,MAAM,WAAW,uBAAuB;IACvC,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;CACpC;AA+ED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,wBAA6B,GACpC,uBAAuB,CAUzB;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAUtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,iBAA6C,GACxD,gBAAgB,CAMlB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,MAAM,EAChB,WAAW,GAAE,iBAA6C,GACxD,eAAe,CA0BjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,SAAS,CAUhE"}
@@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { platform } from "node:os";
4
4
  import { dirname } from "node:path";
5
- import { AuthStorage, } from "@mariozechner/pi-coding-agent";
5
+ import { AuthStorage, FileAuthStorageBackend, } from "@mariozechner/pi-coding-agent";
6
6
  import { atomicWriteFileSync, restoreFromBackup } from "./atomic-write.js";
7
7
  const AUTH_FILE_MODE = 0o600;
8
8
  const AUTH_DIRECTORY_MODE = 0o700;
@@ -10,50 +10,95 @@ const KEYCHAIN_ACCOUNT = "tallow";
10
10
  const KEYCHAIN_SERVICE_PREFIX = "tallow.api-key";
11
11
  const ENV_REFERENCE_PATTERN = /^[A-Z][A-Z0-9]*_[A-Z0-9_]*$/;
12
12
  /**
13
- * AuthStorage wrapper that guarantees api_key credentials are persisted as
14
- * references only (keychain/opchain/env/shell), never as raw values.
13
+ * AuthStorageBackend that intercepts writes to normalize API key credentials.
14
+ * Wraps FileAuthStorageBackend raw keys are converted to secure references
15
+ * (keychain/opchain/env/shell) before they reach disk.
15
16
  */
16
- export class SecureAuthStorage extends AuthStorage {
17
- migration;
18
- authPathValue;
17
+ class SecureFileAuthStorageBackend {
18
+ inner;
19
19
  secretStore;
20
20
  /**
21
- * Create a secure auth storage instance and run one-time migration.
22
- *
23
21
  * @param authPath - Absolute auth.json path
24
- * @param options - Optional testing dependencies
22
+ * @param secretStore - Backend for raw key storage
25
23
  */
26
- constructor(authPath, options = {}) {
27
- super(authPath);
28
- this.authPathValue = authPath;
29
- this.secretStore = options.secretStore ?? createApiKeySecretStore();
30
- assertSecureAuthFilePermissions(authPath);
31
- this.migration = migratePlaintextApiKeys(authPath, this.secretStore);
32
- if (this.migration.migratedProviders.length > 0) {
33
- super.reload();
34
- }
24
+ constructor(authPath, secretStore) {
25
+ this.inner = new FileAuthStorageBackend(authPath);
26
+ this.secretStore = secretStore;
35
27
  }
36
28
  /**
37
- * Persist a provider credential.
38
- * API keys are converted to secure references before writing to disk.
29
+ * Execute fn under lock, normalizing any API keys in the write payload.
39
30
  *
40
- * @param provider - Provider ID
41
- * @param credential - Credential payload
31
+ * @param fn - Lock callback receiving current content
32
+ * @returns Result from fn
33
+ */
34
+ withLock(fn) {
35
+ return this.inner.withLock((current) => {
36
+ const lockResult = fn(current);
37
+ if (lockResult.next !== undefined) {
38
+ lockResult.next = this.normalizeStorageContent(lockResult.next);
39
+ }
40
+ return lockResult;
41
+ });
42
+ }
43
+ /**
44
+ * Async variant of withLock.
45
+ *
46
+ * @param fn - Async lock callback receiving current content
47
+ * @returns Result from fn
48
+ */
49
+ withLockAsync(fn) {
50
+ return this.inner.withLockAsync(async (current) => {
51
+ const lockResult = await fn(current);
52
+ if (lockResult.next !== undefined) {
53
+ lockResult.next = this.normalizeStorageContent(lockResult.next);
54
+ }
55
+ return lockResult;
56
+ });
57
+ }
58
+ /**
59
+ * Parse JSON content and normalize any raw API keys to secure references.
60
+ *
61
+ * @param jsonContent - Serialized auth data
62
+ * @returns Normalized JSON content
42
63
  */
43
- set(provider, credential) {
44
- if (credential.type !== "api_key") {
45
- super.set(provider, credential);
46
- assertSecureAuthFilePermissions(this.authPathValue);
47
- return;
64
+ normalizeStorageContent(jsonContent) {
65
+ try {
66
+ const data = JSON.parse(jsonContent);
67
+ let changed = false;
68
+ for (const [provider, credential] of Object.entries(data)) {
69
+ if (credential?.type !== "api_key" || typeof credential.key !== "string")
70
+ continue;
71
+ const normalized = normalizeApiKeyValue(provider, credential.key, this.secretStore);
72
+ if (normalized !== credential.key) {
73
+ data[provider].key = normalized;
74
+ changed = true;
75
+ }
76
+ }
77
+ return changed ? JSON.stringify(data, null, 2) : jsonContent;
78
+ }
79
+ catch {
80
+ return jsonContent;
48
81
  }
49
- const secureCredential = {
50
- type: "api_key",
51
- key: normalizeApiKeyValue(provider, credential.key, this.secretStore),
52
- };
53
- super.set(provider, secureCredential);
54
- assertSecureAuthFilePermissions(this.authPathValue);
55
82
  }
56
83
  }
84
+ /**
85
+ * Create an AuthStorage instance with secure persistence.
86
+ * Raw API keys are normalized to references at the storage-backend layer,
87
+ * ensuring they never reach disk in plaintext. Runs one-time migration
88
+ * for any existing plaintext keys.
89
+ *
90
+ * @param authPath - Absolute auth.json path
91
+ * @param options - Optional testing dependencies
92
+ * @returns AuthStorage instance and migration result
93
+ */
94
+ export function createSecureAuthStorage(authPath, options = {}) {
95
+ const secretStore = options.secretStore ?? createApiKeySecretStore();
96
+ assertSecureAuthFilePermissions(authPath);
97
+ const migration = migratePlaintextApiKeys(authPath, secretStore);
98
+ const backend = new SecureFileAuthStorageBackend(authPath, secretStore);
99
+ const authStorage = AuthStorage.fromStorage(backend);
100
+ return { authStorage, migration };
101
+ }
57
102
  /**
58
103
  * Fail when auth.json permissions are insecure.
59
104
  *