mitsupi 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +27 -18
  4. package/analyze-edits.py +232 -0
  5. package/distributions/README.md +8 -0
  6. package/distributions/mitsupi-common/README.md +11 -0
  7. package/distributions/mitsupi-common/package.json +95 -0
  8. package/distributions/mitsupi-loaded/README.md +13 -0
  9. package/distributions/mitsupi-loaded/package.json +38 -0
  10. package/{pi-extensions → extensions}/answer.ts +11 -11
  11. package/extensions/btw.ts +963 -0
  12. package/{pi-extensions → extensions}/context.ts +2 -2
  13. package/{pi-extensions → extensions}/control.ts +20 -13
  14. package/{pi-extensions → extensions}/files.ts +6 -8
  15. package/{pi-extensions → extensions}/go-to-bed.ts +2 -2
  16. package/{pi-extensions → extensions}/loop.ts +18 -11
  17. package/{pi-extensions → extensions}/multi-edit.ts +313 -83
  18. package/{pi-extensions → extensions}/review.ts +254 -82
  19. package/extensions/split-fork.ts +130 -0
  20. package/{pi-extensions → extensions}/todos.ts +41 -34
  21. package/{pi-extensions → extensions}/uv.ts +3 -2
  22. package/intercepted-commands/python +42 -2
  23. package/intercepted-commands/python3 +42 -2
  24. package/package.json +6 -3
  25. package/skills/google-workspace/SKILL.md +53 -8
  26. package/skills/google-workspace/scripts/auth.js +106 -28
  27. package/skills/google-workspace/scripts/common.js +201 -32
  28. package/skills/google-workspace/scripts/workspace.js +64 -9
  29. package/skills/native-web-search/SKILL.md +2 -0
  30. package/skills/native-web-search/search.mjs +102 -15
  31. package/skills/web-browser/SKILL.md +6 -0
  32. package/skills/web-browser/scripts/start.js +62 -25
  33. /package/{pi-extensions → extensions}/notify.ts +0 -0
  34. /package/{pi-extensions → extensions}/prompt-editor.ts +0 -0
  35. /package/{pi-extensions → extensions}/session-breakdown.ts +0 -0
  36. /package/{pi-extensions → extensions}/whimsical.ts +0 -0
  37. /package/{pi-themes → themes}/nightowl.json +0 -0
package/AGENTS.md CHANGED
@@ -9,4 +9,4 @@
9
9
 
10
10
  ## Extensions
11
11
 
12
- Pi extensions live in `./pi-extensions`. When working in this repo, add or update extensions there. You can consult the `pi-mono` for reference, but do not modify code in `pi-mono`.
12
+ Pi extensions live in `./extensions`. When working in this repo, add or update extensions there. You can consult the `pi-mono` for reference, but do not modify code in `pi-mono`.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to agent-stuff are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 1.6.0
8
+
9
+ * Added a redesigned `btw` extension with side chat markdown rendering, tool visibility, deferred session creation, and main-context improvements.
10
+ * Added a `/split-fork` Ghostty fork command for opening split sessions.
11
+ * Added email-based multi-account authentication to the `google-workspace` skill.
12
+ * Added left/right arrow key paging in the todo detail overlay. (#15)
13
+ * Added a shared custom instructions toggle to review workflows.
14
+ * Updated extensions for the new command and API-key APIs, including namespaced keybindings and `sourceInfo` support.
15
+ * Improved the `multi-edit` extension with sequential same-file ordering, redundant-edit skipping, and clearer patch-mode diff output.
16
+ * Fixed the `web-browser` skill and session-control refresh behavior after forks. (#16)
17
+ * Fixed `intercepted-commands/python` and `intercepted-commands/python3` to avoid recursive `uv` spawn loops by resolving a uv-managed non-shim interpreter for `uv run --python`.
18
+
7
19
  ## 1.5.0
8
20
 
9
21
  * Added a `multi-edit` extension that replaces `edit` with support for batched `multi` edits and Codex-style `patch` payloads.
package/README.md CHANGED
@@ -30,28 +30,37 @@ All skills live in the [`skills`](skills) folder:
30
30
 
31
31
  ## Pi Coding Agent Extensions
32
32
 
33
- Custom extensions for Pi Coding Agent are in [`pi-extensions`](pi-extensions):
34
-
35
- * [`answer.ts`](pi-extensions/answer.ts) - Interactive TUI for answering questions one by one.
36
- * [`context.ts`](pi-extensions/context.ts) - Context breakdown (extensions, skills, AGENTS.md/CLAUDE.md) + token usage, including loaded-skill highlighting.
37
- * [`control.ts`](pi-extensions/control.ts) - Session control helpers (list controllable sessions, etc.).
38
- * [`files.ts`](pi-extensions/files.ts) - Unified file browser with git status + session references and reveal/open/edit/diff actions.
39
- * [`go-to-bed.ts`](pi-extensions/go-to-bed.ts) - Late-night safety guard with explicit confirmation after midnight.
40
- * [`loop.ts`](pi-extensions/loop.ts) - Prompt loop for rapid iterative coding with optional auto-continue.
41
- * [`multi-edit.ts`](pi-extensions/multi-edit.ts) - Replaces the built-in `edit` tool with batch `multi` edits and Codex-style `patch` support, including preflight validation.
42
- * [`notify.ts`](pi-extensions/notify.ts) - Native desktop notifications when the agent finishes.
43
- * [`prompt-editor.ts`](pi-extensions/prompt-editor.ts) - In-editor prompt mode selector with persistence, history, config, and shortcuts.
44
- * [`review.ts`](pi-extensions/review.ts) - Code review command (working tree, PR-style diff, commits, custom instructions, optional fix loop).
45
- * [`session-breakdown.ts`](pi-extensions/session-breakdown.ts) - TUI for 7/30/90-day session and cost analysis with usage graph.
46
- * [`todos.ts`](pi-extensions/todos.ts) - Todo manager extension with file-backed storage and TUI.
47
- * [`uv.ts`](pi-extensions/uv.ts) - Helpers for uv-based Python workflows.
48
- * [`whimsical.ts`](pi-extensions/whimsical.ts) - Replaces the default thinking message with random whimsical phrases.
33
+ Custom extensions for Pi Coding Agent are in [`extensions`](extensions):
34
+
35
+ * [`answer.ts`](extensions/answer.ts) - Interactive TUI for answering questions one by one.
36
+ * [`btw.ts`](extensions/btw.ts) - Simple `/btw` side-chat popover with optional summary injection back into the main chat on close.
37
+ * [`context.ts`](extensions/context.ts) - Context breakdown (extensions, skills, AGENTS.md/CLAUDE.md) + token usage, including loaded-skill highlighting.
38
+ * [`control.ts`](extensions/control.ts) - Session control helpers (list controllable sessions, etc.).
39
+ * [`files.ts`](extensions/files.ts) - Unified file browser with git status + session references and reveal/open/edit/diff actions.
40
+ * [`split-fork.ts`](extensions/split-fork.ts) - `/split-fork` command to branch the current session into a new pi process in a right-hand Ghostty split.
41
+ * [`go-to-bed.ts`](extensions/go-to-bed.ts) - Late-night safety guard with explicit confirmation after midnight.
42
+ * [`loop.ts`](extensions/loop.ts) - Prompt loop for rapid iterative coding with optional auto-continue.
43
+ * [`multi-edit.ts`](extensions/multi-edit.ts) - Replaces the built-in `edit` tool with batch `multi` edits and Codex-style `patch` support, including preflight validation.
44
+ * [`notify.ts`](extensions/notify.ts) - Native desktop notifications when the agent finishes.
45
+ * [`prompt-editor.ts`](extensions/prompt-editor.ts) - In-editor prompt mode selector with persistence, history, config, and shortcuts.
46
+ * [`review.ts`](extensions/review.ts) - Code review command (working tree, PR-style diff, commits, custom instructions, optional fix loop).
47
+ * [`session-breakdown.ts`](extensions/session-breakdown.ts) - TUI for 7/30/90-day session and cost analysis with usage graph.
48
+ * [`todos.ts`](extensions/todos.ts) - Todo manager extension with file-backed storage and TUI.
49
+ * [`uv.ts`](extensions/uv.ts) - Helpers for uv-based Python workflows.
50
+ * [`whimsical.ts`](extensions/whimsical.ts) - Replaces the default thinking message with random whimsical phrases.
49
51
 
50
52
  ## Pi Coding Agent Themes
51
53
 
52
- Custom themes are in [`pi-themes`](pi-themes):
54
+ Custom themes are in [`themes`](themes):
53
55
 
54
- * [`nightowl.json`](pi-themes/nightowl.json) - Night Owl-inspired theme.
56
+ * [`nightowl.json`](themes/nightowl.json) - Night Owl-inspired theme.
57
+
58
+ ## Distributions
59
+
60
+ This repo also contains distribution packages in [`distributions`](distributions):
61
+
62
+ * [`mitsupi-common`](distributions/mitsupi-common) - Minimal/default set (all resources except `anachb`, `apple-mail`, `oebb-scotty`, `openscad`, and `go-to-bed`).
63
+ * [`mitsupi-loaded`](distributions/mitsupi-loaded) - Add-on package that provides `anachb`, `apple-mail`, `oebb-scotty`, `openscad`, and `go-to-bed`.
55
64
 
56
65
  ## Plumbing Commands
57
66
 
@@ -0,0 +1,232 @@
1
+ # /// script
2
+ # requires-python = ">=3.12"
3
+ # dependencies = []
4
+ # ///
5
+ """Analyze edit tool invocations in pi session JSONL files.
6
+
7
+ Usage: uv run analyze-edits.py <session.jsonl> [session2.jsonl ...]
8
+
9
+ Reports how many times the Edit tool was invoked via each mode:
10
+ - single: classic path/oldText/newText
11
+ - multi(N): multi array with N edits
12
+ - single+multi(N): top-level edit + multi array (N total)
13
+ - patch: Codex-style patch
14
+
15
+ Also breaks down by file extension.
16
+ """
17
+
18
+ import json
19
+ import os
20
+ import sys
21
+ from collections import Counter
22
+ from dataclasses import dataclass
23
+ from pathlib import Path
24
+
25
+
26
+ def base_mode(mode: str) -> str:
27
+ if mode.startswith("multi(") or mode.startswith("single+multi("):
28
+ return "multi"
29
+ return mode
30
+
31
+
32
+ def classify_edit(args: dict) -> tuple[str, list[str]]:
33
+ """Returns (mode_label, list_of_file_extensions)."""
34
+ has_patch = "patch" in args
35
+ has_multi = "multi" in args
36
+ has_single = "path" in args and "oldText" in args
37
+
38
+ paths: list[str] = []
39
+
40
+ if has_patch:
41
+ # Parse paths from patch text
42
+ patch_text = args["patch"]
43
+ for line in patch_text.split("\n"):
44
+ line = line.strip()
45
+ for prefix in ("*** Add File: ", "*** Delete File: ", "*** Update File: "):
46
+ if line.startswith(prefix):
47
+ paths.append(line[len(prefix):])
48
+ return "patch", paths
49
+
50
+ multi_items = args.get("multi", [])
51
+
52
+ if has_single and has_multi:
53
+ paths.append(args["path"])
54
+ paths.extend(item.get("path", "") for item in multi_items)
55
+ return f"single+multi({1 + len(multi_items)})", paths
56
+
57
+ if has_multi:
58
+ paths.extend(item.get("path", "") for item in multi_items)
59
+ return f"multi({len(multi_items)})", paths
60
+
61
+ if has_single:
62
+ paths.append(args["path"])
63
+ return "single", paths
64
+
65
+ return "unknown", paths
66
+
67
+
68
+ def get_ext(path: str) -> str:
69
+ ext = Path(path).suffix
70
+ return ext if ext else "(no ext)"
71
+
72
+
73
+ @dataclass
74
+ class EditCall:
75
+ mode: str
76
+ extensions: list[str]
77
+ failed: bool
78
+
79
+
80
+ def analyze_session(filepath: str) -> list[EditCall]:
81
+ """Returns list of EditCall for each edit tool invocation."""
82
+ # First pass: collect edit tool calls and their IDs
83
+ calls: list[tuple[str, str, list[str]]] = [] # (toolCallId, mode, exts)
84
+ tool_call_ids: dict[str, int] = {} # toolCallId -> index in calls
85
+
86
+ # Also collect tool results to check for errors
87
+ tool_results: dict[str, bool] = {} # toolCallId -> isError
88
+
89
+ entries = []
90
+ with open(filepath) as f:
91
+ for line in f:
92
+ entries.append(json.loads(line))
93
+
94
+ for d in entries:
95
+ if d.get("type") != "message":
96
+ continue
97
+ msg = d.get("message", {})
98
+ role = msg.get("role")
99
+ if role == "assistant":
100
+ for c in msg.get("content", []):
101
+ if c.get("type") == "toolCall" and c.get("name") in ("edit", "Edit"):
102
+ mode, paths = classify_edit(c.get("arguments", {}))
103
+ exts = [get_ext(p) for p in paths]
104
+ tc_id = c.get("id", "")
105
+ idx = len(calls)
106
+ calls.append((tc_id, mode, exts))
107
+ tool_call_ids[tc_id] = idx
108
+ elif role == "toolResult":
109
+ tc_id = msg.get("toolCallId", "")
110
+ is_error = msg.get("isError", False)
111
+ tool_results[tc_id] = is_error
112
+
113
+ results = []
114
+ for tc_id, mode, exts in calls:
115
+ failed = tool_results.get(tc_id, False)
116
+ results.append(EditCall(mode=mode, extensions=exts, failed=failed))
117
+ return results
118
+
119
+
120
+ def main():
121
+ if len(sys.argv) < 2:
122
+ print(__doc__.strip())
123
+ sys.exit(1)
124
+
125
+ all_results: list[EditCall] = []
126
+ session_files = []
127
+
128
+ for arg in sys.argv[1:]:
129
+ p = Path(arg)
130
+ if p.is_dir():
131
+ session_files.extend(sorted(p.glob("**/*.jsonl")))
132
+ else:
133
+ session_files.append(p)
134
+
135
+ for sf in session_files:
136
+ all_results.extend(analyze_session(str(sf)))
137
+
138
+ if not all_results:
139
+ print("No edit tool calls found.")
140
+ return
141
+
142
+ total = len(all_results)
143
+ total_failed = sum(1 for r in all_results if r.failed)
144
+
145
+ # Count tool calls (not individual edits) by base mode
146
+ mode_calls: Counter[str] = Counter()
147
+ mode_fails: Counter[str] = Counter()
148
+ # Count tool calls by (base_mode, primary_ext)
149
+ # For a tool call, we pick the dominant extension
150
+ mode_ext_calls: Counter[tuple[str, str]] = Counter()
151
+ mode_ext_fails: Counter[tuple[str, str]] = Counter()
152
+ ext_calls: Counter[str] = Counter()
153
+ ext_fails: Counter[str] = Counter()
154
+
155
+ for r in all_results:
156
+ bm = base_mode(r.mode)
157
+ mode_calls[bm] += 1
158
+ if r.failed:
159
+ mode_fails[bm] += 1
160
+
161
+ # For extension: count one call per unique extension touched
162
+ exts = set(r.extensions) if r.extensions else {"(no ext)"}
163
+ for ext in exts:
164
+ mode_ext_calls[(bm, ext)] += 1
165
+ ext_calls[ext] += 1
166
+ if r.failed:
167
+ mode_ext_fails[(bm, ext)] += 1
168
+ ext_fails[ext] += 1
169
+
170
+ print(f"{'='*60}")
171
+ print(f"Edit Tool Analysis ({len(session_files)} session(s))")
172
+ print(f"{'='*60}")
173
+ print(f"\nTotal edit tool calls: {total}")
174
+ print(f"Total failures: {total_failed} ({total_failed/total*100:.1f}%)")
175
+
176
+ print(f"\n--- By Mode (tool calls) ---")
177
+ for mode, count in sorted(mode_calls.items(), key=lambda x: -x[1]):
178
+ pct = count / total * 100
179
+ fails = mode_fails[mode]
180
+ fail_pct = fails / count * 100 if count else 0
181
+ print(f" {mode:<12s} {count:>4d} ({pct:5.1f}%) fail: {fails:>3d} ({fail_pct:5.1f}%)")
182
+
183
+ print(f"\n--- By Extension (tool calls) ---")
184
+ for ext, count in sorted(ext_calls.items(), key=lambda x: -x[1]):
185
+ fails = ext_fails[ext]
186
+ fail_pct = fails / count * 100 if count else 0
187
+ print(f" {ext:<12s} {count:>4d} fail: {fails:>3d} ({fail_pct:5.1f}%)")
188
+
189
+ # Pivot table
190
+ all_modes = sorted(mode_calls.keys(), key=lambda m: -mode_calls[m])
191
+ all_exts = sorted(ext_calls.keys(), key=lambda e: -ext_calls[e])
192
+
193
+ col_w = 12
194
+ ext_w = max(12, *(len(e) for e in all_exts))
195
+
196
+ print(f"\n--- Extension × Mode (tool calls / failures) ---")
197
+ header = f" {'extension':<{ext_w}s}" + "".join(f" {m:>{col_w}s}" for m in all_modes) + f" {'total':>{col_w}s}"
198
+ print(header)
199
+ print(f" {'-'*(len(header)-2)}")
200
+ for ext in all_exts:
201
+ row = f" {ext:<{ext_w}s}"
202
+ row_total = 0
203
+ row_total_f = 0
204
+ for m in all_modes:
205
+ v = mode_ext_calls.get((m, ext), 0)
206
+ vf = mode_ext_fails.get((m, ext), 0)
207
+ row_total += v
208
+ row_total_f += vf
209
+ cell = f"{v}" if vf == 0 else f"{v} ({vf}✗)"
210
+ row += f" {cell:>{col_w}s}"
211
+ cell = f"{row_total}" if row_total_f == 0 else f"{row_total} ({row_total_f}✗)"
212
+ row += f" {cell:>{col_w}s}"
213
+ print(row)
214
+ # totals row
215
+ print(f" {'-'*(len(header)-2)}")
216
+ row = f" {'TOTAL':<{ext_w}s}"
217
+ grand = 0
218
+ grand_f = 0
219
+ for m in all_modes:
220
+ v = sum(mode_ext_calls.get((m, ext), 0) for ext in all_exts)
221
+ vf = sum(mode_ext_fails.get((m, ext), 0) for ext in all_exts)
222
+ grand += v
223
+ grand_f += vf
224
+ cell = f"{v}" if vf == 0 else f"{v} ({vf}✗)"
225
+ row += f" {cell:>{col_w}s}"
226
+ cell = f"{grand}" if grand_f == 0 else f"{grand} ({grand_f}✗)"
227
+ row += f" {cell:>{col_w}s}"
228
+ print(row)
229
+
230
+
231
+ if __name__ == "__main__":
232
+ main()
@@ -0,0 +1,8 @@
1
+ # Distributions
2
+
3
+ This folder contains publishable Pi package variants based on `mitsupi`:
4
+
5
+ - `mitsupi-common`: default/minimal profile
6
+ - `mitsupi-loaded`: add-on profile with travel/CAD extras
7
+
8
+ `mitsupi-loaded` is intended to be installed together with `mitsupi-common`.
@@ -0,0 +1,11 @@
1
+ # mitsupi-common
2
+
3
+ Default Mitsupi distribution.
4
+
5
+ Includes everything from `mitsupi` except:
6
+
7
+ - `extensions/go-to-bed.ts`
8
+ - `skills/anachb`
9
+ - `skills/apple-mail`
10
+ - `skills/oebb-scotty`
11
+ - `skills/openscad`
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "mitsupi-common",
3
+ "version": "1.5.0",
4
+ "description": "Common/default Mitsupi package without heavy travel and CAD extras",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/mitsuhiko/agent-stuff.git",
8
+ "directory": "distributions/mitsupi-common"
9
+ },
10
+ "readme": "README.md",
11
+ "keywords": [
12
+ "pi-package",
13
+ "pi-extension",
14
+ "pi-skill",
15
+ "pi-theme"
16
+ ],
17
+ "pi": {
18
+ "extensions": [
19
+ "../../extensions/answer.ts",
20
+ "../../extensions/btw.ts",
21
+ "../../extensions/context.ts",
22
+ "../../extensions/control.ts",
23
+ "../../extensions/files.ts",
24
+ "../../extensions/loop.ts",
25
+ "../../extensions/multi-edit.ts",
26
+ "../../extensions/notify.ts",
27
+ "../../extensions/prompt-editor.ts",
28
+ "../../extensions/session-breakdown.ts",
29
+ "../../extensions/split-fork.ts",
30
+ "../../extensions/todos.ts",
31
+ "../../extensions/uv.ts",
32
+ "../../extensions/whimsical.ts",
33
+ "node_modules/mitsupi/extensions/answer.ts",
34
+ "node_modules/mitsupi/extensions/btw.ts",
35
+ "node_modules/mitsupi/extensions/context.ts",
36
+ "node_modules/mitsupi/extensions/control.ts",
37
+ "node_modules/mitsupi/extensions/files.ts",
38
+ "node_modules/mitsupi/extensions/loop.ts",
39
+ "node_modules/mitsupi/extensions/multi-edit.ts",
40
+ "node_modules/mitsupi/extensions/notify.ts",
41
+ "node_modules/mitsupi/extensions/prompt-editor.ts",
42
+ "node_modules/mitsupi/extensions/session-breakdown.ts",
43
+ "node_modules/mitsupi/extensions/split-fork.ts",
44
+ "node_modules/mitsupi/extensions/todos.ts",
45
+ "node_modules/mitsupi/extensions/uv.ts",
46
+ "node_modules/mitsupi/extensions/whimsical.ts"
47
+ ],
48
+ "skills": [
49
+ "../../skills/commit",
50
+ "../../skills/frontend-design",
51
+ "../../skills/ghidra",
52
+ "../../skills/github",
53
+ "../../skills/google-workspace",
54
+ "../../skills/librarian",
55
+ "../../skills/mermaid",
56
+ "../../skills/native-web-search",
57
+ "../../skills/pi-share",
58
+ "../../skills/sentry",
59
+ "../../skills/summarize",
60
+ "../../skills/tmux",
61
+ "../../skills/update-changelog",
62
+ "../../skills/uv",
63
+ "../../skills/web-browser",
64
+ "node_modules/mitsupi/skills/commit",
65
+ "node_modules/mitsupi/skills/frontend-design",
66
+ "node_modules/mitsupi/skills/ghidra",
67
+ "node_modules/mitsupi/skills/github",
68
+ "node_modules/mitsupi/skills/google-workspace",
69
+ "node_modules/mitsupi/skills/librarian",
70
+ "node_modules/mitsupi/skills/mermaid",
71
+ "node_modules/mitsupi/skills/native-web-search",
72
+ "node_modules/mitsupi/skills/pi-share",
73
+ "node_modules/mitsupi/skills/sentry",
74
+ "node_modules/mitsupi/skills/summarize",
75
+ "node_modules/mitsupi/skills/tmux",
76
+ "node_modules/mitsupi/skills/update-changelog",
77
+ "node_modules/mitsupi/skills/uv",
78
+ "node_modules/mitsupi/skills/web-browser"
79
+ ],
80
+ "themes": [
81
+ "../../themes",
82
+ "node_modules/mitsupi/themes"
83
+ ],
84
+ "prompts": [
85
+ "../../commands",
86
+ "node_modules/mitsupi/commands"
87
+ ]
88
+ },
89
+ "dependencies": {
90
+ "mitsupi": "^1.5.0"
91
+ },
92
+ "bundledDependencies": [
93
+ "mitsupi"
94
+ ]
95
+ }
@@ -0,0 +1,13 @@
1
+ # mitsupi-loaded
2
+
3
+ Add-on Mitsupi distribution.
4
+
5
+ Includes only:
6
+
7
+ - `extensions/go-to-bed.ts`
8
+ - `skills/anachb`
9
+ - `skills/apple-mail`
10
+ - `skills/oebb-scotty`
11
+ - `skills/openscad`
12
+
13
+ Install together with `mitsupi-common` to get the full setup.
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "mitsupi-loaded",
3
+ "version": "1.5.0",
4
+ "description": "Add-on Mitsupi package with travel and CAD extras",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/mitsuhiko/agent-stuff.git",
8
+ "directory": "distributions/mitsupi-loaded"
9
+ },
10
+ "readme": "README.md",
11
+ "keywords": [
12
+ "pi-package",
13
+ "pi-extension",
14
+ "pi-skill"
15
+ ],
16
+ "pi": {
17
+ "extensions": [
18
+ "../../extensions/go-to-bed.ts",
19
+ "node_modules/mitsupi/extensions/go-to-bed.ts"
20
+ ],
21
+ "skills": [
22
+ "../../skills/anachb",
23
+ "../../skills/apple-mail",
24
+ "../../skills/oebb-scotty",
25
+ "../../skills/openscad",
26
+ "node_modules/mitsupi/skills/anachb",
27
+ "node_modules/mitsupi/skills/apple-mail",
28
+ "node_modules/mitsupi/skills/oebb-scotty",
29
+ "node_modules/mitsupi/skills/openscad"
30
+ ]
31
+ },
32
+ "dependencies": {
33
+ "mitsupi": "^1.5.0"
34
+ },
35
+ "bundledDependencies": [
36
+ "mitsupi"
37
+ ]
38
+ }
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { complete, type Model, type Api, type UserMessage } from "@mariozechner/pi-ai";
14
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
14
+ import type { ExtensionAPI, ExtensionContext, ModelRegistry } from "@mariozechner/pi-coding-agent";
15
15
  import { BorderedLoader } from "@mariozechner/pi-coding-agent";
16
16
  import {
17
17
  type Component,
@@ -75,15 +75,12 @@ const HAIKU_MODEL_ID = "claude-haiku-4-5";
75
75
  */
76
76
  async function selectExtractionModel(
77
77
  currentModel: Model<Api>,
78
- modelRegistry: {
79
- find: (provider: string, modelId: string) => Model<Api> | undefined;
80
- getApiKey: (model: Model<Api>) => Promise<string | undefined>;
81
- },
78
+ modelRegistry: ModelRegistry,
82
79
  ): Promise<Model<Api>> {
83
80
  const codexModel = modelRegistry.find("openai-codex", CODEX_MODEL_ID);
84
81
  if (codexModel) {
85
- const apiKey = await modelRegistry.getApiKey(codexModel);
86
- if (apiKey) {
82
+ const auth = await modelRegistry.getApiKeyAndHeaders(codexModel);
83
+ if (auth.ok) {
87
84
  return codexModel;
88
85
  }
89
86
  }
@@ -93,8 +90,8 @@ async function selectExtractionModel(
93
90
  return currentModel;
94
91
  }
95
92
 
96
- const apiKey = await modelRegistry.getApiKey(haikuModel);
97
- if (!apiKey) {
93
+ const auth = await modelRegistry.getApiKeyAndHeaders(haikuModel);
94
+ if (!auth.ok) {
98
95
  return currentModel;
99
96
  }
100
97
 
@@ -457,7 +454,10 @@ export default function (pi: ExtensionAPI) {
457
454
  loader.onAbort = () => done(null);
458
455
 
459
456
  const doExtract = async () => {
460
- const apiKey = await ctx.modelRegistry.getApiKey(extractionModel);
457
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(extractionModel);
458
+ if (!auth.ok) {
459
+ throw new Error(auth.error);
460
+ }
461
461
  const userMessage: UserMessage = {
462
462
  role: "user",
463
463
  content: [{ type: "text", text: lastAssistantText! }],
@@ -467,7 +467,7 @@ export default function (pi: ExtensionAPI) {
467
467
  const response = await complete(
468
468
  extractionModel,
469
469
  { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
470
- { apiKey, signal: loader.signal },
470
+ { apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
471
471
  );
472
472
 
473
473
  if (response.stopReason === "aborted") {